diff options
author | 2022-10-03 16:46:38 +0200 | |
---|---|---|
committer | 2022-10-03 16:46:38 +0200 | |
commit | 5249294a166c901469eeac1d3297e913b4a125e7 (patch) | |
tree | d68ff7bebbc0135d23c18520417c668155aa56f2 /web/source/settings-panel | |
parent | [performance] add user cache and database (#879) (diff) | |
download | gotosocial-5249294a166c901469eeac1d3297e913b4a125e7.tar.xz |
[chore] Bundler restructure (#880)
* re-structure bundler, settings panel files
* add more info logging
* tidy up CSS syntax errors
* split into lib/ files
* livereloading server
* fix factor function for production builds
* remove testing console.log
* default to production env, saves 300kb bundle size
Diffstat (limited to 'web/source/settings-panel')
29 files changed, 0 insertions, 3452 deletions
diff --git a/web/source/settings-panel/admin/actions.js b/web/source/settings-panel/admin/actions.js deleted file mode 100644 index d4980d021..000000000 --- a/web/source/settings-panel/admin/actions.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); -const React = require("react"); -const Redux = require("react-redux"); - -const Submit = require("../components/submit"); - -const api = require("../lib/api"); -const submit = require("../lib/submit"); - -module.exports = function AdminActionPanel() { - const dispatch = Redux.useDispatch(); - - const [days, setDays] = React.useState(30); - - const [errorMsg, setError] = React.useState(""); - const [statusMsg, setStatus] = React.useState(""); - - const removeMedia = submit( - () => dispatch(api.admin.mediaCleanup(days)), - {setStatus, setError} - ); - - return ( - <> - <h1>Admin Actions</h1> - <div> - <h2>Media cleanup</h2> - <p> - Clean up remote media older than the specified number of days. - If the remote instance is still online they will be refetched when needed. - Also cleans up unused headers and avatars from the media cache. - </p> - <div> - <label htmlFor="days">Days: </label> - <input id="days" type="number" value={days} onChange={(e) => setDays(e.target.value)}/> - </div> - <Submit onClick={removeMedia} label="Remove media" errorMsg={errorMsg} statusMsg={statusMsg} /> - </div> - </> - ); -};
\ No newline at end of file diff --git a/web/source/settings-panel/admin/emoji.js b/web/source/settings-panel/admin/emoji.js deleted file mode 100644 index 1ef4a54a3..000000000 --- a/web/source/settings-panel/admin/emoji.js +++ /dev/null @@ -1,212 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); -const React = require("react"); -const Redux = require("react-redux"); -const {Switch, Route, Link, Redirect, useRoute, useLocation} = require("wouter"); - -const Submit = require("../components/submit"); -const FakeToot = require("../components/fake-toot"); -const { formFields } = require("../components/form-fields"); - -const api = require("../lib/api"); -const adminActions = require("../redux/reducers/admin").actions; -const submit = require("../lib/submit"); - -const base = "/settings/admin/custom-emoji"; - -module.exports = function CustomEmoji() { - return ( - <Switch> - <Route path={`${base}/:emojiId`}> - <EmojiDetailWrapped /> - </Route> - <EmojiOverview /> - </Switch> - ); -}; - -function EmojiOverview() { - const dispatch = Redux.useDispatch(); - const [loaded, setLoaded] = React.useState(false); - - const [errorMsg, setError] = React.useState(""); - - React.useEffect(() => { - if (!loaded) { - Promise.try(() => { - return dispatch(api.admin.fetchCustomEmoji()); - }).then(() => { - setLoaded(true); - }).catch((e) => { - setLoaded(true); - setError(e.message); - }); - } - }, []); - - if (!loaded) { - return ( - <> - <h1>Custom Emoji</h1> - Loading... - </> - ); - } - - return ( - <> - <h1>Custom Emoji</h1> - <EmojiList/> - <NewEmoji/> - {errorMsg.length > 0 && - <div className="error accent">{errorMsg}</div> - } - </> - ); -} - -const NewEmojiForm = formFields(adminActions.updateNewEmojiVal, (state) => state.admin.newEmoji); -function NewEmoji() { - const dispatch = Redux.useDispatch(); - const newEmojiForm = Redux.useSelector((state) => state.admin.newEmoji); - - const [errorMsg, setError] = React.useState(""); - const [statusMsg, setStatus] = React.useState(""); - - const uploadEmoji = submit( - () => dispatch(api.admin.newEmoji()), - { - setStatus, setError, - onSuccess: function() { - URL.revokeObjectURL(newEmojiForm.image); - return Promise.all([ - dispatch(adminActions.updateNewEmojiVal(["image", undefined])), - dispatch(adminActions.updateNewEmojiVal(["imageFile", undefined])), - dispatch(adminActions.updateNewEmojiVal(["shortcode", ""])), - ]); - } - } - ); - - React.useEffect(() => { - if (newEmojiForm.shortcode.length == 0) { - if (newEmojiForm.imageFile != undefined) { - let [name, ext] = newEmojiForm.imageFile.name.split("."); - dispatch(adminActions.updateNewEmojiVal(["shortcode", name])); - } - } - }); - - let emojiOrShortcode = `:${newEmojiForm.shortcode}:`; - - if (newEmojiForm.image != undefined) { - emojiOrShortcode = <img - className="emoji" - src={newEmojiForm.image} - title={`:${newEmojiForm.shortcode}:`} - alt={newEmojiForm.shortcode} - />; - } - - return ( - <div> - <h2>Add new custom emoji</h2> - - <FakeToot> - Look at this new custom emoji {emojiOrShortcode} isn't it cool? - </FakeToot> - - <NewEmojiForm.File - id="image" - name="Image" - fileType="image/png,image/gif" - showSize={true} - maxSize={50 * 1000} - /> - - <NewEmojiForm.TextInput - id="shortcode" - name="Shortcode (without : :), must be unique on the instance" - placeHolder="blobcat" - /> - - <Submit onClick={uploadEmoji} label="Upload" errorMsg={errorMsg} statusMsg={statusMsg} /> - </div> - ); -} - -function EmojiList() { - const emoji = Redux.useSelector((state) => state.admin.emoji); - - return ( - <div> - <h2>Overview</h2> - <div className="list emoji-list"> - {Object.entries(emoji).map(([category, entries]) => { - return <EmojiCategory key={category} category={category} entries={entries}/>; - })} - </div> - </div> - ); -} - -function EmojiCategory({category, entries}) { - return ( - <div className="entry"> - <b>{category}</b> - <div className="emoji-group"> - {entries.map((e) => { - return ( - // <Link key={e.static_url} to={`${base}/${e.shortcode}`}> - <Link key={e.static_url} to={`${base}`}> - <a> - <img src={e.static_url} alt={e.shortcode} title={`:${e.shortcode}:`}/> - </a> - </Link> - ); - })} - </div> - </div> - ); -} - -function EmojiDetailWrapped() { - /* We wrap the component to generate formFields with a setter depending on the domain - if formFields() is used inside the same component that is re-rendered with their state, - inputs get re-created on every change, causing them to lose focus, and bad performance - */ - let [_match, {emojiId}] = useRoute(`${base}/:emojiId`); - - function alterEmoji([key, val]) { - return adminActions.updateDomainBlockVal([emojiId, key, val]); - } - - const fields = formFields(alterEmoji, (state) => state.admin.blockedInstances[emojiId]); - - return <EmojiDetail id={emojiId} Form={fields} />; -} - -function EmojiDetail({id, Form}) { - return ( - "Not implemented yet" - ); -}
\ No newline at end of file diff --git a/web/source/settings-panel/admin/federation.js b/web/source/settings-panel/admin/federation.js deleted file mode 100644 index 7afc3c699..000000000 --- a/web/source/settings-panel/admin/federation.js +++ /dev/null @@ -1,382 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); -const React = require("react"); -const Redux = require("react-redux"); -const {Switch, Route, Link, Redirect, useRoute, useLocation} = require("wouter"); -const fileDownload = require("js-file-download"); - -const { formFields } = require("../components/form-fields"); - -const api = require("../lib/api"); -const adminActions = require("../redux/reducers/admin").actions; -const submit = require("../lib/submit"); - -const base = "/settings/admin/federation"; - -// const { -// TextInput, -// TextArea, -// File -// } = require("../components/form-fields").formFields(adminActions.setAdminSettingsVal, (state) => state.instances.adminSettings); - -module.exports = function AdminSettings() { - const dispatch = Redux.useDispatch(); - // const instance = Redux.useSelector(state => state.instances.adminSettings); - const loadedBlockedInstances = Redux.useSelector(state => state.admin.loadedBlockedInstances); - - React.useEffect(() => { - if (!loadedBlockedInstances ) { - Promise.try(() => { - return dispatch(api.admin.fetchDomainBlocks()); - }); - } - }, []); - - if (!loadedBlockedInstances) { - return ( - <div> - <h1>Federation</h1> - Loading... - </div> - ); - } - - return ( - <Switch> - <Route path={`${base}/:domain`}> - <InstancePageWrapped /> - </Route> - <InstanceOverview /> - </Switch> - ); -}; - -function InstanceOverview() { - const [filter, setFilter] = React.useState(""); - const blockedInstances = Redux.useSelector(state => state.admin.blockedInstances); - const [_location, setLocation] = useLocation(); - - function filterFormSubmit(e) { - e.preventDefault(); - setLocation(`${base}/${filter}`); - } - - return ( - <> - <h1>Federation</h1> - Here you can see an overview of blocked instances. - - <div className="instance-list"> - <h2>Blocked instances</h2> - <form action={`${base}/view`} className="filter" role="search" onSubmit={filterFormSubmit}> - <input name="domain" value={filter} onChange={(e) => setFilter(e.target.value)}/> - <Link to={`${base}/${filter}`}><a className="button">Add block</a></Link> - </form> - <div className="list"> - {Object.values(blockedInstances).filter((a) => a.domain.startsWith(filter)).map((entry) => { - return ( - <Link key={entry.domain} to={`${base}/${entry.domain}`}> - <a className="entry nounderline"> - <span id="domain"> - {entry.domain} - </span> - <span id="date"> - {new Date(entry.created_at).toLocaleString()} - </span> - </a> - </Link> - ); - })} - </div> - </div> - - <BulkBlocking/> - </> - ); -} - -const Bulk = formFields(adminActions.updateBulkBlockVal, (state) => state.admin.bulkBlock); -function BulkBlocking() { - const dispatch = Redux.useDispatch(); - const {bulkBlock, blockedInstances} = Redux.useSelector(state => state.admin); - - const [errorMsg, setError] = React.useState(""); - const [statusMsg, setStatus] = React.useState(""); - - function importBlocks() { - setStatus("Processing"); - setError(""); - return Promise.try(() => { - return dispatch(api.admin.bulkDomainBlock()); - }).then(({success, invalidDomains}) => { - return Promise.try(() => { - return resetBulk(); - }).then(() => { - dispatch(adminActions.updateBulkBlockVal(["list", invalidDomains.join("\n")])); - - let stat = ""; - if (success == 0) { - return setError("No valid domains in import"); - } else if (success == 1) { - stat = "Imported 1 domain"; - } else { - stat = `Imported ${success} domains`; - } - - if (invalidDomains.length > 0) { - if (invalidDomains.length == 1) { - stat += ", input contained 1 invalid domain."; - } else { - stat += `, input contained ${invalidDomains.length} invalid domains.`; - } - } else { - stat += "!"; - } - - setStatus(stat); - }); - }).catch((e) => { - console.error(e); - setError(e.message); - setStatus(""); - }); - } - - function exportBlocks() { - return Promise.try(() => { - setStatus("Exporting"); - setError(""); - let asJSON = bulkBlock.exportType.startsWith("json"); - let _asCSV = bulkBlock.exportType.startsWith("csv"); - - let exportList = Object.values(blockedInstances).map((entry) => { - if (asJSON) { - return { - domain: entry.domain, - public_comment: entry.public_comment - }; - } else { - return entry.domain; - } - }); - - if (bulkBlock.exportType == "json") { - return dispatch(adminActions.updateBulkBlockVal(["list", JSON.stringify(exportList)])); - } else if (bulkBlock.exportType == "json-download") { - return fileDownload(JSON.stringify(exportList), "block-export.json"); - } else if (bulkBlock.exportType == "plain") { - return dispatch(adminActions.updateBulkBlockVal(["list", exportList.join("\n")])); - } - }).then(() => { - setStatus("Exported!"); - }).catch((e) => { - setError(e.message); - setStatus(""); - }); - } - - function resetBulk(e) { - if (e != undefined) { - e.preventDefault(); - } - return dispatch(adminActions.resetBulkBlockVal()); - } - - function disableInfoFields(props={}) { - if (bulkBlock.list[0] == "[") { - return { - ...props, - disabled: true, - placeHolder: "Domain list is a JSON import, input disabled" - }; - } else { - return props; - } - } - - return ( - <div className="bulk"> - <h2>Import / Export <a onClick={resetBulk}>reset</a></h2> - <Bulk.TextArea - id="list" - name="Domains, one per line" - placeHolder={`google.com\nfacebook.com`} - /> - - <Bulk.TextArea - id="public_comment" - name="Public comment" - inputProps={disableInfoFields({rows: 3})} - /> - - <Bulk.TextArea - id="private_comment" - name="Private comment" - inputProps={disableInfoFields({rows: 3})} - /> - - <Bulk.Checkbox - id="obfuscate" - name="Obfuscate domains? " - inputProps={disableInfoFields()} - /> - - <div className="hidden"> - <Bulk.File - id="json" - fileType="application/json" - withPreview={false} - /> - </div> - - <div className="messagebutton"> - <div> - <button type="submit" onClick={importBlocks}>Import</button> - </div> - - <div> - <button type="submit" onClick={exportBlocks}>Export</button> - - <Bulk.Select id="exportType" name="Export type" options={ - <> - <option value="plain">One per line in text field</option> - <option value="json">JSON in text field</option> - <option value="json-download">JSON file download</option> - <option disabled value="csv">CSV in text field (glitch-soc)</option> - <option disabled value="csv-download">CSV file download (glitch-soc)</option> - </> - }/> - </div> - <br/> - <div> - {errorMsg.length > 0 && - <div className="error accent">{errorMsg}</div> - } - {statusMsg.length > 0 && - <div className="accent">{statusMsg}</div> - } - </div> - </div> - </div> - ); -} - -function BackButton() { - return ( - <Link to={base}> - <a className="button">< back</a> - </Link> - ); -} - -function InstancePageWrapped() { - /* We wrap the component to generate formFields with a setter depending on the domain - if formFields() is used inside the same component that is re-rendered with their state, - inputs get re-created on every change, causing them to lose focus, and bad performance - */ - let [_match, {domain}] = useRoute(`${base}/:domain`); - - if (domain == "view") { // from form field submission - let realDomain = (new URL(document.location)).searchParams.get("domain"); - if (realDomain == undefined) { - return <Redirect to={base}/>; - } else { - domain = realDomain; - } - } - - function alterDomain([key, val]) { - return adminActions.updateDomainBlockVal([domain, key, val]); - } - - const fields = formFields(alterDomain, (state) => state.admin.newInstanceBlocks[domain]); - - return <InstancePage domain={domain} Form={fields} />; -} - -function InstancePage({domain, Form}) { - const dispatch = Redux.useDispatch(); - const entry = Redux.useSelector(state => state.admin.newInstanceBlocks[domain]); - const [_location, setLocation] = useLocation(); - - React.useEffect(() => { - if (entry == undefined) { - dispatch(api.admin.getEditableDomainBlock(domain)); - } - }, []); - - const [errorMsg, setError] = React.useState(""); - const [statusMsg, setStatus] = React.useState(""); - - if (entry == undefined) { - return "Loading..."; - } - - const updateBlock = submit( - () => dispatch(api.admin.updateDomainBlock(domain)), - {setStatus, setError} - ); - - const removeBlock = submit( - () => dispatch(api.admin.removeDomainBlock(domain)), - {setStatus, setError, startStatus: "Removing", successStatus: "Removed!", onSuccess: () => { - setLocation(base); - }} - ); - - return ( - <div> - <h1><BackButton/> Federation settings for: {domain}</h1> - {entry.new && "No stored block yet, you can add one below:"} - - <Form.TextArea - id="public_comment" - name="Public comment" - /> - - <Form.TextArea - id="private_comment" - name="Private comment" - /> - - <Form.Checkbox - id="obfuscate" - name="Obfuscate domain? " - /> - - <div className="messagebutton"> - <button type="submit" onClick={updateBlock}>{entry.new ? "Add block" : "Save block"}</button> - - {!entry.new && - <button className="danger" onClick={removeBlock}>Remove block</button> - } - - {errorMsg.length > 0 && - <div className="error accent">{errorMsg}</div> - } - {statusMsg.length > 0 && - <div className="accent">{statusMsg}</div> - } - </div> - </div> - ); -}
\ No newline at end of file diff --git a/web/source/settings-panel/admin/settings.js b/web/source/settings-panel/admin/settings.js deleted file mode 100644 index 845a1f924..000000000 --- a/web/source/settings-panel/admin/settings.js +++ /dev/null @@ -1,110 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); -const React = require("react"); -const Redux = require("react-redux"); - -const Submit = require("../components/submit"); - -const api = require("../lib/api"); -const submit = require("../lib/submit"); - -const adminActions = require("../redux/reducers/instances").actions; - -const { - TextInput, - TextArea, - File -} = require("../components/form-fields").formFields(adminActions.setAdminSettingsVal, (state) => state.instances.adminSettings); - -module.exports = function AdminSettings() { - const dispatch = Redux.useDispatch(); - - const [errorMsg, setError] = React.useState(""); - const [statusMsg, setStatus] = React.useState(""); - - const updateSettings = submit( - () => dispatch(api.admin.updateInstance()), - {setStatus, setError} - ); - - return ( - <div> - <h1>Instance Settings</h1> - <TextInput - id="title" - name="Title" - placeHolder="My GoToSocial instance" - /> - - <TextArea - id="short_description" - name="Short description" - placeHolder="A small testing instance for the GoToSocial alpha." - /> - <TextArea - id="description" - name="Full description" - placeHolder="A small testing instance for the GoToSocial alpha." - /> - - <TextInput - id="contact_account.username" - name="Contact user (local account username)" - placeHolder="admin" - /> - <TextInput - id="email" - name="Contact email" - placeHolder="admin@example.com" - /> - - <TextArea - id="terms" - name="Terms & Conditions" - placeHolder="" - /> - - {/* <div className="file-upload"> - <h3>Instance avatar</h3> - <div> - <img className="preview avatar" src={instance.avatar} alt={instance.avatar ? `Avatar image for the instance` : "No instance avatar image set"} /> - <File - id="avatar" - fileType="image/*" - /> - </div> - </div> - - <div className="file-upload"> - <h3>Instance header</h3> - <div> - <img className="preview header" src={instance.header} alt={instance.header ? `Header image for the instance` : "No instance header image set"} /> - <File - id="header" - fileType="image/*" - /> - </div> - </div> */} - <Submit onClick={updateSettings} label="Save" errorMsg={errorMsg} statusMsg={statusMsg} /> - </div> - ); -};
\ No newline at end of file diff --git a/web/source/settings-panel/components/error.jsx b/web/source/settings-panel/components/error.jsx deleted file mode 100644 index 13dc686b7..000000000 --- a/web/source/settings-panel/components/error.jsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); -const React = require("react"); - -module.exports = function ErrorFallback({error, resetErrorBoundary}) { - return ( - <div className="error"> - <p> - {"An error occured, please report this on the "} - <a href="https://github.com/superseriousbusiness/gotosocial/issues">GoToSocial issue tracker</a> - {" or "} - <a href="https://matrix.to/#/#gotosocial-help:superseriousbusiness.org">Matrix support room</a>. - <br/>Include the details below: - </p> - <pre> - {error.name}: {error.message} - </pre> - <pre> - {error.stack} - </pre> - <p> - <button onClick={resetErrorBoundary}>Try again</button> or <a href="">refresh the page</a> - </p> - </div> - ); -};
\ No newline at end of file diff --git a/web/source/settings-panel/components/fake-toot.jsx b/web/source/settings-panel/components/fake-toot.jsx deleted file mode 100644 index f79e24eb9..000000000 --- a/web/source/settings-panel/components/fake-toot.jsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const React = require("react"); -const Redux = require("react-redux"); - -module.exports = function FakeToot({children}) { - const account = Redux.useSelector((state) => state.user.profile); - - return ( - <div className="toot expanded"> - <div className="contentgrid"> - <span className="avatar"> - <img src={account.avatar} alt=""/> - </span> - <span className="displayname">{account.display_name.trim().length > 0 ? account.display_name : account.username}</span> - <span className="username">@{account.username}</span> - <div className="text"> - <div className="content"> - {children} - </div> - </div> - </div> - </div> - ); -};
\ No newline at end of file diff --git a/web/source/settings-panel/components/form-fields.jsx b/web/source/settings-panel/components/form-fields.jsx deleted file mode 100644 index cb402c3b2..000000000 --- a/web/source/settings-panel/components/form-fields.jsx +++ /dev/null @@ -1,167 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const React = require("react"); -const Redux = require("react-redux"); -const d = require("dotty"); -const prettierBytes = require("prettier-bytes"); - -function eventListeners(dispatch, setter, obj) { - return { - onTextChange: function (key) { - return function (e) { - dispatch(setter([key, e.target.value])); - }; - }, - - onCheckChange: function (key) { - return function (e) { - dispatch(setter([key, e.target.checked])); - }; - }, - - onFileChange: function (key, withPreview) { - return function (e) { - let file = e.target.files[0]; - if (withPreview) { - let old = d.get(obj, key); - if (old != undefined) { - URL.revokeObjectURL(old); // no error revoking a non-Object URL as provided by instance - } - let objectURL = URL.createObjectURL(file); - dispatch(setter([key, objectURL])); - } - dispatch(setter([`${key}File`, file])); - }; - } - }; -} - -function get(state, id, defaultVal) { - let value; - if (id.includes(".")) { - value = d.get(state, id); - } else { - value = state[id]; - } - if (value == undefined) { - value = defaultVal; - } - return value; -} - -// function removeFile(name) { -// return function(e) { -// e.preventDefault(); -// dispatch(user.setProfileVal([name, ""])); -// dispatch(user.setProfileVal([`${name}File`, ""])); -// }; -// } - -module.exports = { - formFields: function formFields(setter, selector) { - function FormField({ - type, id, name, className="", placeHolder="", fileType="", children=null, - options=null, inputProps={}, withPreview=true, showSize=false, maxSize=Infinity - }) { - const dispatch = Redux.useDispatch(); - let state = Redux.useSelector(selector); - let { - onTextChange, - onCheckChange, - onFileChange - } = eventListeners(dispatch, setter, state); - - let field; - let defaultLabel = true; - if (type == "text") { - field = <input type="text" id={id} value={get(state, id, "")} placeholder={placeHolder} className={className} onChange={onTextChange(id)} {...inputProps}/>; - } else if (type == "textarea") { - field = <textarea type="text" id={id} value={get(state, id, "")} placeholder={placeHolder} className={className} onChange={onTextChange(id)} rows={8} {...inputProps}/>; - } else if (type == "checkbox") { - field = <input type="checkbox" id={id} checked={get(state, id, false)} className={className} onChange={onCheckChange(id)} {...inputProps}/>; - } else if (type == "select") { - field = ( - <select id={id} value={get(state, id, "")} className={className} onChange={onTextChange(id)} {...inputProps}> - {options} - </select> - ); - } else if (type == "file") { - defaultLabel = false; - let file = get(state, `${id}File`); - - let size = null; - if (showSize && file) { - size = `(${prettierBytes(file.size)})`; - - if (file.size > maxSize) { - size = <span className="error-text">{size}</span>; - } - } - - field = ( - <> - <label htmlFor={id} className="file-input button">Browse</label> - <span> - {file ? file.name : "no file selected"} {size} - </span> - {/* <a onClick={removeFile("header")}>remove</a> */} - <input className="hidden" id={id} type="file" accept={fileType} onChange={onFileChange(id, withPreview)} {...inputProps}/> - </> - ); - } else { - defaultLabel = false; - field = `unsupported FormField ${type}, this is a developer error`; - } - - let label = <label htmlFor={id}>{name}</label>; - return ( - <div className={`form-field ${type}`}> - {defaultLabel ? label : null} {field} - {children} - </div> - ); - } - - return { - TextInput: function(props) { - return <FormField type="text" {...props} />; - }, - - TextArea: function(props) { - return <FormField type="textarea" {...props} />; - }, - - Checkbox: function(props) { - return <FormField type="checkbox" {...props} />; - }, - - Select: function(props) { - return <FormField type="select" {...props} />; - }, - - File: function(props) { - return <FormField type="file" {...props} />; - }, - }; - }, - - eventListeners -};
\ No newline at end of file diff --git a/web/source/settings-panel/components/languages.jsx b/web/source/settings-panel/components/languages.jsx deleted file mode 100644 index 1522495da..000000000 --- a/web/source/settings-panel/components/languages.jsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const React = require("react"); - -module.exports = function Languages() { - return <React.Fragment> - <option value="AF">Afrikaans</option> - <option value="SQ">Albanian</option> - <option value="AR">Arabic</option> - <option value="HY">Armenian</option> - <option value="EU">Basque</option> - <option value="BN">Bengali</option> - <option value="BG">Bulgarian</option> - <option value="CA">Catalan</option> - <option value="KM">Cambodian</option> - <option value="ZH">Chinese (Mandarin)</option> - <option value="HR">Croatian</option> - <option value="CS">Czech</option> - <option value="DA">Danish</option> - <option value="NL">Dutch</option> - <option value="EN">English</option> - <option value="ET">Estonian</option> - <option value="FJ">Fiji</option> - <option value="FI">Finnish</option> - <option value="FR">French</option> - <option value="KA">Georgian</option> - <option value="DE">German</option> - <option value="EL">Greek</option> - <option value="GU">Gujarati</option> - <option value="HE">Hebrew</option> - <option value="HI">Hindi</option> - <option value="HU">Hungarian</option> - <option value="IS">Icelandic</option> - <option value="ID">Indonesian</option> - <option value="GA">Irish</option> - <option value="IT">Italian</option> - <option value="JA">Japanese</option> - <option value="JW">Javanese</option> - <option value="KO">Korean</option> - <option value="LA">Latin</option> - <option value="LV">Latvian</option> - <option value="LT">Lithuanian</option> - <option value="MK">Macedonian</option> - <option value="MS">Malay</option> - <option value="ML">Malayalam</option> - <option value="MT">Maltese</option> - <option value="MI">Maori</option> - <option value="MR">Marathi</option> - <option value="MN">Mongolian</option> - <option value="NE">Nepali</option> - <option value="NO">Norwegian</option> - <option value="FA">Persian</option> - <option value="PL">Polish</option> - <option value="PT">Portuguese</option> - <option value="PA">Punjabi</option> - <option value="QU">Quechua</option> - <option value="RO">Romanian</option> - <option value="RU">Russian</option> - <option value="SM">Samoan</option> - <option value="SR">Serbian</option> - <option value="SK">Slovak</option> - <option value="SL">Slovenian</option> - <option value="ES">Spanish</option> - <option value="SW">Swahili</option> - <option value="SV">Swedish </option> - <option value="TA">Tamil</option> - <option value="TT">Tatar</option> - <option value="TE">Telugu</option> - <option value="TH">Thai</option> - <option value="BO">Tibetan</option> - <option value="TO">Tonga</option> - <option value="TR">Turkish</option> - <option value="UK">Ukrainian</option> - <option value="UR">Urdu</option> - <option value="UZ">Uzbek</option> - <option value="VI">Vietnamese</option> - <option value="CY">Welsh</option> - <option value="XH">Xhosa</option> - </React.Fragment>; -}; diff --git a/web/source/settings-panel/components/login.jsx b/web/source/settings-panel/components/login.jsx deleted file mode 100644 index c67e99acd..000000000 --- a/web/source/settings-panel/components/login.jsx +++ /dev/null @@ -1,102 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); -const React = require("react"); -const Redux = require("react-redux"); - -const { setInstance } = require("../redux/reducers/oauth").actions; -const api = require("../lib/api"); - -module.exports = function Login({error}) { - const dispatch = Redux.useDispatch(); - const [ instanceField, setInstanceField ] = React.useState(""); - const [ errorMsg, setErrorMsg ] = React.useState(); - const instanceFieldRef = React.useRef(""); - - React.useEffect(() => { - // check if current domain runs an instance - let currentDomain = window.location.origin; - Promise.try(() => { - return dispatch(api.instance.fetchWithoutStore(currentDomain)); - }).then(() => { - if (instanceFieldRef.current.length == 0) { // user hasn't started typing yet - dispatch(setInstance(currentDomain)); - instanceFieldRef.current = currentDomain; - setInstanceField(currentDomain); - } - }).catch((e) => { - console.log("Current domain does not host a valid instance: ", e); - }); - }, []); - - function tryInstance() { - let domain = instanceFieldRef.current; - Promise.try(() => { - return dispatch(api.instance.fetchWithoutStore(domain)).catch((e) => { - // TODO: clearer error messages for common errors - console.log(e); - throw e; - }); - }).then(() => { - dispatch(setInstance(domain)); - - return dispatch(api.oauth.register()).catch((e) => { - console.log(e); - throw e; - }); - }).then(() => { - return dispatch(api.oauth.authorize()); // will send user off-page - }).catch((e) => { - setErrorMsg( - <> - <b>{e.type}</b> - <span>{e.message}</span> - </> - ); - }); - } - - function updateInstanceField(e) { - if (e.key == "Enter") { - tryInstance(instanceField); - } else { - setInstanceField(e.target.value); - instanceFieldRef.current = e.target.value; - } - } - - return ( - <section className="login"> - <h1>OAUTH Login:</h1> - {error} - <form onSubmit={(e) => e.preventDefault()}> - <label htmlFor="instance">Instance: </label> - <input value={instanceField} onChange={updateInstanceField} id="instance"/> - {errorMsg && - <div className="error"> - {errorMsg} - </div> - } - <button onClick={tryInstance}>Authenticate</button> - </form> - </section> - ); -};
\ No newline at end of file diff --git a/web/source/settings-panel/components/nav-button.jsx b/web/source/settings-panel/components/nav-button.jsx deleted file mode 100644 index 3c76711fb..000000000 --- a/web/source/settings-panel/components/nav-button.jsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const React = require("react"); -const { Link, useRoute } = require("wouter"); - -module.exports = function NavButton({href, name}) { - const [isActive] = useRoute(`${href}/:anything?`); - return ( - <Link href={href}> - <a className={isActive ? "active" : ""} data-content={name}> - {name} - </a> - </Link> - ); -};
\ No newline at end of file diff --git a/web/source/settings-panel/components/submit.jsx b/web/source/settings-panel/components/submit.jsx deleted file mode 100644 index 0187fc81f..000000000 --- a/web/source/settings-panel/components/submit.jsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const React = require("react"); - -module.exports = function Submit({onClick, label, errorMsg, statusMsg}) { - return ( - <div className="messagebutton"> - <button type="submit" onClick={onClick}>{ label }</button> - {errorMsg.length > 0 && - <div className="error accent">{errorMsg}</div> - } - {statusMsg.length > 0 && - <div className="accent">{statusMsg}</div> - } - </div> - ); -}; diff --git a/web/source/settings-panel/index.js b/web/source/settings-panel/index.js deleted file mode 100644 index 34720e818..000000000 --- a/web/source/settings-panel/index.js +++ /dev/null @@ -1,178 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); -const React = require("react"); -const ReactDom = require("react-dom/client"); -const Redux = require("react-redux"); -const { Switch, Route, Redirect } = require("wouter"); -const { Provider } = require("react-redux"); -const { PersistGate } = require("redux-persist/integration/react"); - -const { store, persistor } = require("./redux"); -const api = require("./lib/api"); -const oauth = require("./redux/reducers/oauth").actions; -const { AuthenticationError } = require("./lib/errors"); - -const Login = require("./components/login"); - -require("./style.css"); - -// TODO: nested categories? -const nav = { - "User": { - "Profile": require("./user/profile.js"), - "Settings": require("./user/settings.js"), - }, - "Admin": { - adminOnly: true, - "Instance Settings": require("./admin/settings.js"), - "Actions": require("./admin/actions"), - "Federation": require("./admin/federation.js"), - "Custom Emoji": require("./admin/emoji.js"), - } -}; - -const { sidebar, panelRouter } = require("./lib/get-views")(nav); - -function App() { - const dispatch = Redux.useDispatch(); - - const { loginState, isAdmin } = Redux.useSelector((state) => state.oauth); - const reduxTempStatus = Redux.useSelector((state) => state.temporary.status); - - const [errorMsg, setErrorMsg] = React.useState(); - const [tokenChecked, setTokenChecked] = React.useState(false); - - React.useEffect(() => { - if (loginState == "login" || loginState == "callback") { - Promise.try(() => { - // Process OAUTH authorization token from URL if available - if (loginState == "callback") { - let urlParams = new URLSearchParams(window.location.search); - let code = urlParams.get("code"); - - if (code == undefined) { - setErrorMsg(new Error("Waiting for OAUTH callback but no ?code= provided. You can try logging in again:")); - } else { - return dispatch(api.oauth.tokenize(code)); - } - } - }).then(() => { - // Fetch current instance info - return dispatch(api.instance.fetch()); - }).then(() => { - // Check currently stored auth token for validity if available - return dispatch(api.user.fetchAccount()); - }).then(() => { - setTokenChecked(true); - - return dispatch(api.oauth.checkIfAdmin()); - }).catch((e) => { - if (e instanceof AuthenticationError) { - dispatch(oauth.remove()); - e.message = "Stored OAUTH token no longer valid, please log in again."; - } - setErrorMsg(e); - console.error(e); - }); - } - }, []); - - let ErrorElement = null; - if (errorMsg != undefined) { - ErrorElement = ( - <div className="error"> - <b>{errorMsg.type}</b> - <span>{errorMsg.message}</span> - </div> - ); - } - - const LogoutElement = ( - <button className="logout" onClick={() => { dispatch(api.oauth.logout()); }}> - Log out - </button> - ); - - if (reduxTempStatus != undefined) { - return ( - <section> - {reduxTempStatus} - </section> - ); - } else if (tokenChecked && loginState == "login") { - return ( - <> - <div className="sidebar"> - {sidebar.all} - {isAdmin && sidebar.admin} - {LogoutElement} - </div> - <section className="with-sidebar"> - {ErrorElement} - <Switch> - {panelRouter.all} - {isAdmin && panelRouter.admin} - <Route> {/* default route */} - <Redirect to="/settings/user" /> - </Route> - </Switch> - </section> - </> - ); - } else if (loginState == "none") { - return ( - <Login error={ErrorElement} /> - ); - } else { - let status; - - if (loginState == "login") { - status = "Verifying stored login..."; - } else if (loginState == "callback") { - status = "Processing OAUTH callback..."; - } - - return ( - <section> - <div> - {status} - </div> - {ErrorElement} - {LogoutElement} - </section> - ); - } - -} - -function Main() { - return ( - <Provider store={store}> - <PersistGate loading={"loading..."} persistor={persistor}> - <App /> - </PersistGate> - </Provider> - ); -} - -const root = ReactDom.createRoot(document.getElementById("root")); -root.render(<React.StrictMode><Main /></React.StrictMode>);
\ No newline at end of file diff --git a/web/source/settings-panel/lib/api/admin.js b/web/source/settings-panel/lib/api/admin.js deleted file mode 100644 index 1df47b693..000000000 --- a/web/source/settings-panel/lib/api/admin.js +++ /dev/null @@ -1,192 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); -const isValidDomain = require("is-valid-domain"); - -const instance = require("../../redux/reducers/instances").actions; -const admin = require("../../redux/reducers/admin").actions; - -module.exports = function ({ apiCall, getChanges }) { - const adminAPI = { - updateInstance: function updateInstance() { - return function (dispatch, getState) { - return Promise.try(() => { - const state = getState().instances.adminSettings; - - const update = getChanges(state, { - formKeys: ["title", "short_description", "description", "contact_account.username", "email", "terms"], - renamedKeys: {"contact_account.username": "contact_username"}, - // fileKeys: ["avatar", "header"] - }); - - return dispatch(apiCall("PATCH", "/api/v1/instance", update, "form")); - }).then((data) => { - return dispatch(instance.setInstanceInfo(data)); - }); - }; - }, - - fetchDomainBlocks: function fetchDomainBlocks() { - return function (dispatch, _getState) { - return Promise.try(() => { - return dispatch(apiCall("GET", "/api/v1/admin/domain_blocks")); - }).then((data) => { - return dispatch(admin.setBlockedInstances(data)); - }); - }; - }, - - updateDomainBlock: function updateDomainBlock(domain) { - return function (dispatch, getState) { - return Promise.try(() => { - const state = getState().admin.newInstanceBlocks[domain]; - const update = getChanges(state, { - formKeys: ["domain", "obfuscate", "public_comment", "private_comment"], - }); - - return dispatch(apiCall("POST", "/api/v1/admin/domain_blocks", update, "form")); - }).then((block) => { - return Promise.all([ - dispatch(admin.newDomainBlock([domain, block])), - dispatch(admin.setDomainBlock([domain, block])) - ]); - }); - }; - }, - - getEditableDomainBlock: function getEditableDomainBlock(domain) { - return function (dispatch, getState) { - let data = getState().admin.blockedInstances[domain]; - return dispatch(admin.newDomainBlock([domain, data])); - }; - }, - - bulkDomainBlock: function bulkDomainBlock() { - return function (dispatch, getState) { - let invalidDomains = []; - let success = 0; - - return Promise.try(() => { - const state = getState().admin.bulkBlock; - let list = state.list; - let domains; - - let fields = getChanges(state, { - formKeys: ["obfuscate", "public_comment", "private_comment"] - }); - - let defaultDate = new Date().toUTCString(); - - if (list[0] == "[") { - domains = JSON.parse(state.list); - } else { - domains = list.split("\n").map((line_) => { - let line = line_.trim(); - if (line.length == 0) { - return null; - } - - if (!isValidDomain(line, {wildcard: true, allowUnicode: true})) { - invalidDomains.push(line); - return null; - } - - return { - domain: line, - created_at: defaultDate, - ...fields - }; - }).filter((a) => a != null); - } - - if (domains.length == 0) { - return; - } - - const update = { - domains: new Blob([JSON.stringify(domains)], {type: "application/json"}) - }; - - return dispatch(apiCall("POST", "/api/v1/admin/domain_blocks?import=true", update, "form")); - }).then((blocks) => { - if (blocks != undefined) { - return Promise.each(blocks, (block) => { - success += 1; - return dispatch(admin.setDomainBlock([block.domain, block])); - }); - } - }).then(() => { - return { - success, - invalidDomains - }; - }); - }; - }, - - removeDomainBlock: function removeDomainBlock(domain) { - return function (dispatch, getState) { - return Promise.try(() => { - const id = getState().admin.blockedInstances[domain].id; - return dispatch(apiCall("DELETE", `/api/v1/admin/domain_blocks/${id}`)); - }).then((removed) => { - return dispatch(admin.removeDomainBlock(removed.domain)); - }); - }; - }, - - mediaCleanup: function mediaCleanup(days) { - return function (dispatch, _getState) { - return Promise.try(() => { - return dispatch(apiCall("POST", `/api/v1/admin/media_cleanup?remote_cache_days=${days}`)); - }); - }; - }, - - fetchCustomEmoji: function fetchCustomEmoji() { - return function (dispatch, _getState) { - return Promise.try(() => { - return dispatch(apiCall("GET", "/api/v1/custom_emojis")); - }).then((emoji) => { - return dispatch(admin.setEmoji(emoji)); - }); - }; - }, - - newEmoji: function newEmoji() { - return function (dispatch, getState) { - return Promise.try(() => { - const state = getState().admin.newEmoji; - - const update = getChanges(state, { - formKeys: ["shortcode"], - fileKeys: ["image"] - }); - - return dispatch(apiCall("POST", "/api/v1/admin/custom_emojis", update, "form")); - }).then((emoji) => { - return dispatch(admin.addEmoji(emoji)); - }); - }; - } - }; - return adminAPI; -};
\ No newline at end of file diff --git a/web/source/settings-panel/lib/api/index.js b/web/source/settings-panel/lib/api/index.js deleted file mode 100644 index e699011bd..000000000 --- a/web/source/settings-panel/lib/api/index.js +++ /dev/null @@ -1,185 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); -const { isPlainObject } = require("is-plain-object"); -const d = require("dotty"); - -const { APIError, AuthenticationError } = require("../errors"); -const { setInstanceInfo, setNamedInstanceInfo } = require("../../redux/reducers/instances").actions; -const oauth = require("../../redux/reducers/oauth").actions; - -function apiCall(method, route, payload, type = "json") { - return function (dispatch, getState) { - const state = getState(); - let base = state.oauth.instance; - let auth = state.oauth.token; - console.log(method, base, route, "auth:", auth != undefined); - - return Promise.try(() => { - let url = new URL(base); - let [path, query] = route.split("?"); - url.pathname = path; - if (query != undefined) { - url.search = query; - } - let body; - - let headers = { - "Accept": "application/json", - }; - - if (payload != undefined) { - if (type == "json") { - headers["Content-Type"] = "application/json"; - body = JSON.stringify(payload); - } else if (type == "form") { - const formData = new FormData(); - Object.entries(payload).forEach(([key, val]) => { - if (isPlainObject(val)) { - Object.entries(val).forEach(([key2, val2]) => { - if (val2 != undefined) { - formData.set(`${key}[${key2}]`, val2); - } - }); - } else { - if (val != undefined) { - formData.set(key, val); - } - } - }); - body = formData; - } - } - - if (auth != undefined) { - headers["Authorization"] = auth; - } - - return fetch(url.toString(), { - method, - headers, - body - }); - }).then((res) => { - // try parse json even with error - let json = res.json().catch((e) => { - throw new APIError(`JSON parsing error: ${e.message}`); - }); - - return Promise.all([res, json]); - }).then(([res, json]) => { - if (!res.ok) { - if (auth != undefined && (res.status == 401 || res.status == 403)) { - // stored access token is invalid - throw new AuthenticationError("401: Authentication error", {json, status: res.status}); - } else { - throw new APIError(json.error, { json }); - } - } else { - return json; - } - }); - }; -} - -function getChanges(state, keys) { - const { formKeys = [], fileKeys = [], renamedKeys = {} } = keys; - const update = {}; - - formKeys.forEach((key) => { - let value = d.get(state, key); - if (value == undefined) { - return; - } - if (renamedKeys[key]) { - key = renamedKeys[key]; - } - d.put(update, key, value); - }); - - fileKeys.forEach((key) => { - let file = d.get(state, `${key}File`); - if (file != undefined) { - if (renamedKeys[key]) { - key = renamedKeys[key]; - } - d.put(update, key, file); - } - }); - - return update; -} - -function getCurrentUrl() { - return `${window.location.origin}${window.location.pathname}`; -} - -function fetchInstanceWithoutStore(domain) { - return function (dispatch, getState) { - return Promise.try(() => { - let lookup = getState().instances.info[domain]; - if (lookup != undefined) { - return lookup; - } - - // apiCall expects to pull the domain from state, - // but we don't want to store it there yet - // so we mock the API here with our function argument - let fakeState = { - oauth: { instance: domain } - }; - - return apiCall("GET", "/api/v1/instance")(dispatch, () => fakeState); - }).then((json) => { - if (json && json.uri) { // TODO: validate instance json more? - dispatch(setNamedInstanceInfo([domain, json])); - return json; - } - }); - }; -} - -function fetchInstance() { - return function (dispatch, _getState) { - return Promise.try(() => { - return dispatch(apiCall("GET", "/api/v1/instance")); - }).then((json) => { - if (json && json.uri) { - dispatch(setInstanceInfo(json)); - return json; - } - }); - }; -} - -let submoduleArgs = { apiCall, getCurrentUrl, getChanges }; - -module.exports = { - instance: { - fetchWithoutStore: fetchInstanceWithoutStore, - fetch: fetchInstance - }, - oauth: require("./oauth")(submoduleArgs), - user: require("./user")(submoduleArgs), - admin: require("./admin")(submoduleArgs), - apiCall, - getChanges -};
\ No newline at end of file diff --git a/web/source/settings-panel/lib/api/oauth.js b/web/source/settings-panel/lib/api/oauth.js deleted file mode 100644 index 76d0e9d2f..000000000 --- a/web/source/settings-panel/lib/api/oauth.js +++ /dev/null @@ -1,124 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); - -const { OAUTHError, AuthenticationError } = require("../errors"); - -const oauth = require("../../redux/reducers/oauth").actions; -const temporary = require("../../redux/reducers/temporary").actions; -const admin = require("../../redux/reducers/admin").actions; - -module.exports = function oauthAPI({ apiCall, getCurrentUrl }) { - return { - - register: function register(scopes = []) { - return function (dispatch, _getState) { - return Promise.try(() => { - return dispatch(apiCall("POST", "/api/v1/apps", { - client_name: "GoToSocial Settings", - scopes: scopes.join(" "), - redirect_uris: getCurrentUrl(), - website: getCurrentUrl() - })); - }).then((json) => { - json.scopes = scopes; - dispatch(oauth.setRegistration(json)); - }); - }; - }, - - authorize: function authorize() { - return function (dispatch, getState) { - let state = getState(); - let reg = state.oauth.registration; - let base = new URL(state.oauth.instance); - - base.pathname = "/oauth/authorize"; - base.searchParams.set("client_id", reg.client_id); - base.searchParams.set("redirect_uri", getCurrentUrl()); - base.searchParams.set("response_type", "code"); - base.searchParams.set("scope", reg.scopes.join(" ")); - - dispatch(oauth.setLoginState("callback")); - dispatch(temporary.setStatus("Redirecting to instance login...")); - - // send user to instance's login flow - window.location.assign(base.href); - }; - }, - - tokenize: function tokenize(code) { - return function (dispatch, getState) { - let reg = getState().oauth.registration; - - return Promise.try(() => { - if (reg == undefined || reg.client_id == undefined) { - throw new OAUTHError("Callback code present, but no client registration is available from localStorage. \nNote: localStorage is unavailable in Private Browsing."); - } - - return dispatch(apiCall("POST", "/oauth/token", { - client_id: reg.client_id, - client_secret: reg.client_secret, - redirect_uri: getCurrentUrl(), - grant_type: "authorization_code", - code: code - })); - }).then((json) => { - window.history.replaceState({}, document.title, window.location.pathname); - return dispatch(oauth.login(json)); - }); - }; - }, - - checkIfAdmin: function checkIfAdmin() { - return function (dispatch, getState) { - const state = getState(); - let stored = state.oauth.isAdmin; - if (stored != undefined) { - return stored; - } - - // newer GoToSocial version will include a `role` in the Account data, check that first - // TODO: check account data for admin status - - // no role info, try fetching an admin-only route and see if we get an error - return Promise.try(() => { - return dispatch(apiCall("GET", "/api/v1/admin/domain_blocks")); - }).then((data) => { - return Promise.all([ - dispatch(oauth.setAdmin(true)), - dispatch(admin.setBlockedInstances(data)) - ]); - }).catch(AuthenticationError, () => { - return dispatch(oauth.setAdmin(false)); - }); - }; - }, - - logout: function logout() { - return function (dispatch, _getState) { - // TODO: GoToSocial does not have a logout API route yet - - return dispatch(oauth.remove()); - }; - } - }; -};
\ No newline at end of file diff --git a/web/source/settings-panel/lib/api/user.js b/web/source/settings-panel/lib/api/user.js deleted file mode 100644 index 18b54bd73..000000000 --- a/web/source/settings-panel/lib/api/user.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); - -const user = require("../../redux/reducers/user").actions; - -module.exports = function ({ apiCall, getChanges }) { - function updateCredentials(selector, keys) { - return function (dispatch, getState) { - return Promise.try(() => { - const state = selector(getState()); - - const update = getChanges(state, keys); - - return dispatch(apiCall("PATCH", "/api/v1/accounts/update_credentials", update, "form")); - }).then((account) => { - return dispatch(user.setAccount(account)); - }); - }; - } - - return { - fetchAccount: function fetchAccount() { - return function (dispatch, _getState) { - return Promise.try(() => { - return dispatch(apiCall("GET", "/api/v1/accounts/verify_credentials")); - }).then((account) => { - return dispatch(user.setAccount(account)); - }); - }; - }, - - updateProfile: function updateProfile() { - const formKeys = ["display_name", "locked", "source", "custom_css", "source.note"]; - const renamedKeys = { - "source.note": "note" - }; - const fileKeys = ["header", "avatar"]; - - return updateCredentials((state) => state.user.profile, {formKeys, renamedKeys, fileKeys}); - }, - - updateSettings: function updateProfile() { - const formKeys = ["source"]; - - return updateCredentials((state) => state.user.settings, {formKeys}); - } - }; -};
\ No newline at end of file diff --git a/web/source/settings-panel/lib/errors.js b/web/source/settings-panel/lib/errors.js deleted file mode 100644 index c2f781cb2..000000000 --- a/web/source/settings-panel/lib/errors.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const createError = require("create-error"); - -module.exports = { - APIError: createError("APIError"), - OAUTHError: createError("OAUTHError"), - AuthenticationError: createError("AuthenticationError"), -};
\ No newline at end of file diff --git a/web/source/settings-panel/lib/get-views.js b/web/source/settings-panel/lib/get-views.js deleted file mode 100644 index 39f627435..000000000 --- a/web/source/settings-panel/lib/get-views.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const React = require("react"); -const Redux = require("react-redux"); -const { Link, Route, Switch, Redirect } = require("wouter"); -const { ErrorBoundary } = require("react-error-boundary"); - -const ErrorFallback = require("../components/error"); -const NavButton = require("../components/nav-button"); - -function urlSafe(str) { - return str.toLowerCase().replace(/\s+/g, "-"); -} - -module.exports = function getViews(struct) { - const sidebar = { - all: [], - admin: [], - }; - - const panelRouter = { - all: [], - admin: [], - }; - - Object.entries(struct).forEach(([name, entries]) => { - let sidebarEl = sidebar.all; - let panelRouterEl = panelRouter.all; - - if (entries.adminOnly) { - sidebarEl = sidebar.admin; - panelRouterEl = panelRouter.admin; - delete entries.adminOnly; - } - - let base = `/settings/${urlSafe(name)}`; - - let links = []; - - let firstRoute; - - Object.entries(entries).forEach(([name, ViewComponent]) => { - let url = `${base}/${urlSafe(name)}`; - - if (firstRoute == undefined) { - firstRoute = url; - } - - panelRouterEl.push(( - <Route path={`${url}/:page?`} key={url}> - <ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => { }}> - {/* FIXME: implement onReset */} - <ViewComponent /> - </ErrorBoundary> - </Route> - )); - - links.push( - <NavButton key={url} href={url} name={name} /> - ); - }); - - panelRouterEl.push( - <Route key={base} path={base}> - <Redirect to={firstRoute} /> - </Route> - ); - - sidebarEl.push( - <React.Fragment key={name}> - <Link href={firstRoute}> - <a> - <h2>{name}</h2> - </a> - </Link> - <nav> - {links} - </nav> - </React.Fragment> - ); - }); - - return { sidebar, panelRouter }; -};
\ No newline at end of file diff --git a/web/source/settings-panel/lib/panel.js b/web/source/settings-panel/lib/panel.js deleted file mode 100644 index df723bc74..000000000 --- a/web/source/settings-panel/lib/panel.js +++ /dev/null @@ -1,134 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); -const React = require("react"); -const ReactDom = require("react-dom"); - -const oauthLib = require("./oauth"); - -module.exports = function createPanel(clientName, scope, Component) { - ReactDom.render(<Panel/>, document.getElementById("root")); - - function Panel() { - const [oauth, setOauth] = React.useState(); - const [hasAuth, setAuth] = React.useState(false); - const [oauthState, setOauthState] = React.useState(localStorage.getItem("oauth")); - - React.useEffect(() => { - let state = localStorage.getItem("oauth"); - if (state != undefined) { - state = JSON.parse(state); - let restoredOauth = oauthLib(state.config, state); - Promise.try(() => { - return restoredOauth.callback(); - }).then(() => { - setAuth(true); - }); - setOauth(restoredOauth); - } - }, [setAuth, setOauth]); - - if (!hasAuth && oauth && oauth.isAuthorized()) { - setAuth(true); - } - - if (oauth && oauth.isAuthorized()) { - return <Component oauth={oauth} />; - } else if (oauthState != undefined) { - return "processing oauth..."; - } else { - return <Auth setOauth={setOauth} />; - } - } - - function Auth({setOauth}) { - const [ instance, setInstance ] = React.useState(""); - - React.useEffect(() => { - let isStillMounted = true; - // check if current domain runs an instance - let thisUrl = new URL(window.location.origin); - thisUrl.pathname = "/api/v1/instance"; - Promise.try(() => { - return fetch(thisUrl.href); - }).then((res) => { - if (res.status == 200) { - return res.json(); - } - }).then((json) => { - if (json && json.uri && isStillMounted) { - setInstance(json.uri); - } - }).catch((e) => { - console.log("error checking instance response:", e); - }); - - return () => { - // cleanup function - isStillMounted = false; - }; - }, []); - - function doAuth() { - return Promise.try(() => { - return new URL(instance); - }).catch(TypeError, () => { - return new URL(`https://${instance}`); - }).then((parsedURL) => { - let url = parsedURL.toString(); - let oauth = oauthLib({ - instance: url, - client_name: clientName, - scope: scope, - website: window.location.href - }); - setOauth(oauth); - setInstance(url); - return oauth.register().then(() => { - return oauth; - }); - }).then((oauth) => { - return oauth.authorize(); - }).catch((e) => { - console.log("error authenticating:", e); - }); - } - - function updateInstance(e) { - if (e.key == "Enter") { - doAuth(); - } else { - setInstance(e.target.value); - } - } - - return ( - <section className="login"> - <h1>OAUTH Login:</h1> - <form onSubmit={(e) => e.preventDefault()}> - <label htmlFor="instance">Instance: </label> - <input value={instance} onChange={updateInstance} id="instance"/> - <button onClick={doAuth}>Authenticate</button> - </form> - </section> - ); - } -};
\ No newline at end of file diff --git a/web/source/settings-panel/lib/submit.js b/web/source/settings-panel/lib/submit.js deleted file mode 100644 index f268b5cf9..000000000 --- a/web/source/settings-panel/lib/submit.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); - -module.exports = function submit(func, { - setStatus, setError, - startStatus="PATCHing", successStatus="Saved!", - onSuccess, - onError -}) { - return function() { - setStatus(startStatus); - setError(""); - return Promise.try(() => { - return func(); - }).then(() => { - setStatus(successStatus); - if (onSuccess != undefined) { - return onSuccess(); - } - }).catch((e) => { - setError(e.message); - setStatus(""); - console.error(e); - if (onError != undefined) { - onError(e); - } - }); - }; -};
\ No newline at end of file diff --git a/web/source/settings-panel/redux/index.js b/web/source/settings-panel/redux/index.js deleted file mode 100644 index e0dbe9b23..000000000 --- a/web/source/settings-panel/redux/index.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const { createStore, combineReducers, applyMiddleware } = require("redux"); -const { persistStore, persistReducer } = require("redux-persist"); -const thunk = require("redux-thunk").default; -const { composeWithDevTools } = require("redux-devtools-extension"); - -const persistConfig = { - key: "gotosocial-settings", - storage: require("redux-persist/lib/storage").default, - stateReconciler: require("redux-persist/lib/stateReconciler/autoMergeLevel2").default, - whitelist: ["oauth"], - blacklist: ["temporary"] -}; - -const combinedReducers = combineReducers({ - oauth: require("./reducers/oauth").reducer, - instances: require("./reducers/instances").reducer, - temporary: require("./reducers/temporary").reducer, - user: require("./reducers/user").reducer, - admin: require("./reducers/admin").reducer, -}); - -const persistedReducer = persistReducer(persistConfig, combinedReducers); -const composedEnhancer = composeWithDevTools(applyMiddleware(thunk)); - -const store = createStore(persistedReducer, composedEnhancer); -const persistor = persistStore(store); - -module.exports = { store, persistor };
\ No newline at end of file diff --git a/web/source/settings-panel/redux/reducers/admin.js b/web/source/settings-panel/redux/reducers/admin.js deleted file mode 100644 index 20d3d748d..000000000 --- a/web/source/settings-panel/redux/reducers/admin.js +++ /dev/null @@ -1,131 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const { createSlice } = require("@reduxjs/toolkit"); -const defaultValue = require("default-value"); - -function sortBlocks(blocks) { - return blocks.sort((a, b) => { // alphabetical sort - return a.domain.localeCompare(b.domain); - }); -} - -function emptyBlock() { - return { - public_comment: "", - private_comment: "", - obfuscate: false - }; -} - -function emptyEmojiForm() { - return { - shortcode: "" - }; -} - -module.exports = createSlice({ - name: "admin", - initialState: { - loadedBlockedInstances: false, - blockedInstances: undefined, - bulkBlock: { - list: "", - exportType: "plain", - ...emptyBlock() - }, - newInstanceBlocks: {}, - emoji: {}, - newEmoji: emptyEmojiForm() - }, - reducers: { - setBlockedInstances: (state, { payload }) => { - state.blockedInstances = {}; - sortBlocks(payload).forEach((entry) => { - state.blockedInstances[entry.domain] = entry; - }); - state.loadedBlockedInstances = true; - }, - - newDomainBlock: (state, { payload: [domain, data] }) => { - if (data == undefined) { - data = { - new: true, - domain, - ...emptyBlock() - }; - } - state.newInstanceBlocks[domain] = data; - }, - - setDomainBlock: (state, { payload: [domain, data = {}] }) => { - state.blockedInstances[domain] = data; - }, - - removeDomainBlock: (state, {payload: domain}) => { - delete state.blockedInstances[domain]; - }, - - updateDomainBlockVal: (state, { payload: [domain, key, val] }) => { - state.newInstanceBlocks[domain][key] = val; - }, - - updateBulkBlockVal: (state, { payload: [key, val] }) => { - state.bulkBlock[key] = val; - }, - - resetBulkBlockVal: (state, { _payload }) => { - state.bulkBlock = { - list: "", - exportType: "plain", - ...emptyBlock() - }; - }, - - exportToField: (state, { _payload }) => { - state.bulkBlock.list = Object.values(state.blockedInstances).map((entry) => { - return entry.domain; - }).join("\n"); - }, - - setEmoji: (state, {payload}) => { - state.emoji = {}; - payload.forEach((emoji) => { - if (emoji.category == undefined) { - emoji.category = "Unsorted"; - } - state.emoji[emoji.category] = defaultValue(state.emoji[emoji.category], []); - state.emoji[emoji.category].push(emoji); - }); - }, - - updateNewEmojiVal: (state, { payload: [key, val] }) => { - state.newEmoji[key] = val; - }, - - addEmoji: (state, {payload: emoji}) => { - if (emoji.category == undefined) { - emoji.category = "Unsorted"; - } - state.emoji[emoji.category] = defaultValue(state.emoji[emoji.category], []); - state.emoji[emoji.category].push(emoji); - }, - } -});
\ No newline at end of file diff --git a/web/source/settings-panel/redux/reducers/instances.js b/web/source/settings-panel/redux/reducers/instances.js deleted file mode 100644 index 3ad5bb7cb..000000000 --- a/web/source/settings-panel/redux/reducers/instances.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const {createSlice} = require("@reduxjs/toolkit"); -const d = require("dotty"); - -module.exports = createSlice({ - name: "instances", - initialState: { - info: {}, - }, - reducers: { - setNamedInstanceInfo: (state, {payload}) => { - let [key, info] = payload; - state.info[key] = info; - }, - setInstanceInfo: (state, {payload}) => { - state.current = payload; - state.adminSettings = payload; - }, - setAdminSettingsVal: (state, {payload: [key, val]}) => { - d.put(state.adminSettings, key, val); - } - } -});
\ No newline at end of file diff --git a/web/source/settings-panel/redux/reducers/oauth.js b/web/source/settings-panel/redux/reducers/oauth.js deleted file mode 100644 index c332a7d06..000000000 --- a/web/source/settings-panel/redux/reducers/oauth.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const {createSlice} = require("@reduxjs/toolkit"); - -module.exports = createSlice({ - name: "oauth", - initialState: { - loginState: 'none', - }, - reducers: { - setInstance: (state, {payload}) => { - state.instance = payload; - }, - setRegistration: (state, {payload}) => { - state.registration = payload; - }, - setLoginState: (state, {payload}) => { - state.loginState = payload; - }, - login: (state, {payload}) => { - state.token = `${payload.token_type} ${payload.access_token}`; - state.loginState = "login"; - }, - remove: (state, {_payload}) => { - delete state.token; - delete state.registration; - delete state.isAdmin; - state.loginState = "none"; - }, - setAdmin: (state, {payload}) => { - state.isAdmin = payload; - } - } -});
\ No newline at end of file diff --git a/web/source/settings-panel/redux/reducers/temporary.js b/web/source/settings-panel/redux/reducers/temporary.js deleted file mode 100644 index c887d2eee..000000000 --- a/web/source/settings-panel/redux/reducers/temporary.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const {createSlice} = require("@reduxjs/toolkit"); - -module.exports = createSlice({ - name: "temporary", - initialState: { - }, - reducers: { - setStatus: function(state, {payload}) { - state.status = payload; - } - } -});
\ No newline at end of file diff --git a/web/source/settings-panel/redux/reducers/user.js b/web/source/settings-panel/redux/reducers/user.js deleted file mode 100644 index b4463c9f9..000000000 --- a/web/source/settings-panel/redux/reducers/user.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const { createSlice } = require("@reduxjs/toolkit"); -const d = require("dotty"); -const defaultValue = require("default-value"); - -module.exports = createSlice({ - name: "user", - initialState: { - profile: {}, - settings: {} - }, - reducers: { - setAccount: (state, { payload }) => { - payload.source = defaultValue(payload.source, {}); - payload.source.language = defaultValue(payload.source.language.toUpperCase(), "EN"); - payload.source.status_format = defaultValue(payload.source.status_format, "plain"); - payload.source.sensitive = defaultValue(payload.source.sensitive, false); - - state.profile = payload; - // /user/settings only needs a copy of the 'source' obj - state.settings = { - source: payload.source - }; - }, - setProfileVal: (state, { payload: [key, val] }) => { - d.put(state.profile, key, val); - }, - setSettingsVal: (state, { payload: [key, val] }) => { - d.put(state.settings, key, val); - } - } -});
\ No newline at end of file diff --git a/web/source/settings-panel/style.css b/web/source/settings-panel/style.css deleted file mode 100644 index 35d11fa08..000000000 --- a/web/source/settings-panel/style.css +++ /dev/null @@ -1,498 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -body { - grid-template-rows: auto 1fr; -} - -.content { - grid-column: 1 / span 3; /* stretch entire width, to fit panel + sidebar nav */ -} - -section { - grid-column: 2; -} - -#root { - display: grid; - grid-template-columns: 1fr 90ch 1fr; - width: 100vw; - max-width: 100vw; - box-sizing: border-box; - - section.with-sidebar { - border-left: none; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - - & > div { - border-left: 0.2rem solid $border-accent; - padding-left: 0.4rem; - display: flex; - flex-direction: column; - gap: 0.5rem; - margin: 2rem 0; - - h2 { - margin: 0; - margin-bottom: 0.5rem; - } - - &:only-child { - border-left: none; - } - - &:first-child { - margin-top: 0; - } - - &:last-child { - margin-bottom: 0; - } - } - } - - .sidebar { - align-self: start; - justify-self: end; - background: $settings-nav-bg; - border: $boxshadow-border; - box-shadow: $boxshadow; - border-radius: $br; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - display: flex; - flex-direction: column; - min-width: 12rem; - - a { - text-decoration: none; - } - - a:first-child h2 { - border-top-left-radius: $br; - } - - h2 { - margin: 0; - padding: 0.5rem; - font-size: 0.9rem; - font-weight: bold; - text-transform: uppercase; - color: $settings-nav-header-fg; - background: $settings-nav-header-bg; - } - - nav { - display: flex; - flex-direction: column; - - a { - padding: 1rem; - text-decoration: none; - transition: 0.1s; - color: $fg; - - &:hover { - color: $settings-nav-fg-hover; - background: $settings-nav-bg-hover; - } - - &.active { - color: $settings-nav-fg-active; - background: $settings-nav-bg-active; - font-weight: bold; - text-decoration: underline; - } - - /* reserve space for bold version of the element, so .active doesn't - change container size */ - &::after { - font-weight: bold; - text-decoration: underline; - display: block; - content: attr(data-content); - height: 1px; - color: transparent; - overflow: hidden; - visibility: hidden; - } - } - } - - - nav:last-child a:last-child { - border-bottom-left-radius: $br; - border-bottom: none; - } - } -} - -.capitalize { - text-transform: capitalize; -} - -section { - margin-bottom: 1rem; -} - -input, select, textarea { - box-sizing: border-box; -} - -.error { - color: $error-fg; - background: $error-bg; - border: 0.02rem solid $error-fg; - border-radius: $br; - font-weight: bold; - padding: 0.5rem; - white-space: pre-wrap; - - a { - color: $error-link; - } - - pre { - background: $bg; - color: $fg; - padding: 1rem; - overflow: auto; - margin: 0; - } -} - -.hidden { - display: none; -} - -.messagebutton, .messagebutton > div { - display: flex; - align-items: center; - flex-wrap: wrap; - - div.padded { - margin-left: 1rem; - } - - button, .button { - white-space: nowrap; - margin-right: 1rem; - } -} - -.messagebutton > div { - button, .button { - margin-top: 1rem; - } -} - -.notImplemented { - border: 2px solid rgb(70, 79, 88); - background: repeating-linear-gradient( - -45deg, - #525c66, - #525c66 10px, - rgb(70, 79, 88) 10px, - rgb(70, 79, 88) 20px - ) !important; -} - -section.with-sidebar > div { - display: flex; - flex-direction: column; - gap: 1rem; - - input, textarea { - width: 100%; - line-height: 1.5rem; - } - - input[type=checkbox] { - justify-self: start; - width: initial; - } - - input:read-only { - border: none; - } - - input:invalid { - border-color: red; - } - - textarea { - width: 100%; - } - - h1 { - margin-bottom: 0.5rem; - } - - .moreinfolink { - font-size: 0.9em; - } - - .labelinput .border { - border-radius: 0.2rem; - border: 0.15rem solid $border_accent; - padding: 0.3rem; - display: flex; - flex-direction: column; - } - - .file-input.button { - display: inline-block; - font-size: 1rem; - font-weight: normal; - padding: 0.3rem 0.3rem; - align-self: flex-start; - margin-right: 0.2rem; - } - - .labelinput, .labelselect { - display: flex; - flex-direction: column; - gap: 0.4rem; - } - - .labelcheckbox { - display: flex; - gap: 0.4rem; - } - - .titlesave { - display: flex; - flex-wrap: wrap; - gap: 0.4rem; - } -} - -.file-upload > div { - display: flex; - gap: 1rem; - - img { - height: 8rem; - border: 0.2rem solid $border-accent; - } - - img.avatar { - width: 8rem; - } - - img.header { - width: 24rem; - } -} - -.user-profile { - .overview { - display: grid; - grid-template-columns: 70% 30%; - - .basic { - margin-top: -4.5rem; - - .avatar { - height: 5rem; - width: 5rem; - } - - .displayname { - font-size: 1.3rem; - padding-top: 0; - padding-bottom: 0; - margin-top: 0.7rem; - } - } - - .files { - width: 100%; - margin: 1rem; - margin-right: 0; - display: flex; - flex-direction: column; - justify-content: center; - - div.form-field { - width: 100%; - display: flex; - - span { - flex: 1 1 auto; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - padding: 0.3rem 0; - } - } - - h3 { - margin-top: 0; - margin-bottom: 0.5rem; - } - - div:first-child { - margin-bottom: 1rem; - } - - span { - font-style: italic; - } - } - } -} - -.form-field label { - font-weight: bold; -} - -.list { - display: flex; - flex-direction: column; - margin-top: 0.5rem; - max-height: 40rem; - overflow: auto; - - .entry { - display: flex; - flex-wrap: wrap; - background: $settings-entry-bg; - - &:hover { - background: $settings-entry-hover-bg; - } - } -} - -.instance-list { - .filter { - display: flex; - gap: 0.5rem; - - input { - width: auto; - flex: 1 1 auto; - } - } - - .entry { - padding: 0.3rem; - margin: 0.2rem 0; - - #domain { - flex: 1 1 auto; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - } -} - -.bulk h2 { - display: flex; - justify-content: space-between; -} - -.emoji-list { - background: $settings-entry-bg; - - .entry { - padding: 0.5rem; - flex-direction: column; - - .emoji-group { - display: flex; - - a { - border-radius: $br; - padding: 0.4rem; - line-height: 0; - - img { - height: 2rem; - width: 2rem; - object-fit: contain; - vertical-align: middle; - } - - &:hover { - background: $settings-entry-hover-bg; - } - } - } - - &:hover { - background: inherit; - } - } -} - -.toot { - padding-top: 0.5rem; - .contentgrid { - padding: 0 0.5rem; - } -} - -@media screen and (max-width: 100ch) { - #root { - padding: 1rem; - grid-template-columns: 100%; - grid-template-rows: auto auto; - - .sidebar { - justify-self: auto; - margin-bottom: 2rem; - } - - .sidebar, section.with-sidebar { - border-top-left-radius: $br; - border-top-right-radius: $br; - border-bottom-left-radius: $br; - border-bottom-right-radius: $br; - } - - .sidebar a:first-child h2 { - border-top-right-radius: $br; - } - } - - section { - grid-column: 1; - } - - .user-profile .overview { - grid-template-columns: 100%; - grid-template-rows: auto auto; - - .files { - margin: 0; - margin-top: 1rem; - } - } - - main section { - padding: 0.75rem; - } - - .instance-list .filter { - flex-direction: column; - } -}
\ No newline at end of file diff --git a/web/source/settings-panel/user/profile.js b/web/source/settings-panel/user/profile.js deleted file mode 100644 index 7cf3a7b52..000000000 --- a/web/source/settings-panel/user/profile.js +++ /dev/null @@ -1,113 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); -const React = require("react"); -const Redux = require("react-redux"); - -const Submit = require("../components/submit"); - -const api = require("../lib/api"); -const user = require("../redux/reducers/user").actions; -const submit = require("../lib/submit"); - -const { formFields } = require("../components/form-fields"); - -const { - TextInput, - TextArea, - Checkbox, - File -} = formFields(user.setProfileVal, (state) => state.user.profile); - -module.exports = function UserProfile() { - const dispatch = Redux.useDispatch(); - const account = Redux.useSelector(state => state.user.profile); - const instance = Redux.useSelector(state => state.instances.current); - - const allowCustomCSS = instance.configuration.accounts.allow_custom_css; - - const [errorMsg, setError] = React.useState(""); - const [statusMsg, setStatus] = React.useState(""); - - const saveProfile = submit( - () => dispatch(api.user.updateProfile()), - {setStatus, setError} - ); - - return ( - <div className="user-profile"> - <h1>Profile</h1> - <div className="overview"> - <div className="profile"> - <div className="headerimage"> - <img className="headerpreview" src={account.header} alt={account.header ? `header image for ${account.username}` : "None set"} /> - </div> - <div className="basic"> - <div id="profile-basic-filler2"></div> - <span className="avatar"><img className="avatarpreview" src={account.avatar} alt={account.avatar ? `avatar image for ${account.username}` : "None set"} /></span> - <div className="displayname">{account.display_name.trim().length > 0 ? account.display_name : account.username}</div> - <div className="username"><span>@{account.username}</span></div> - </div> - </div> - <div className="files"> - <div> - <h3>Header</h3> - <File - id="header" - fileType="image/*" - /> - </div> - <div> - <h3>Avatar</h3> - <File - id="avatar" - fileType="image/*" - /> - </div> - </div> - </div> - <TextInput - id="display_name" - name="Name" - placeHolder="A GoToSocial user" - /> - <TextArea - id="source.note" - name="Bio" - placeHolder="Just trying out GoToSocial, my pronouns are they/them and I like sloths." - /> - <Checkbox - id="locked" - name="Manually approve follow requests? " - /> - { !allowCustomCSS ? null : - <TextArea - id="custom_css" - name="Custom CSS" - className="monospace" - > - <a href="https://docs.gotosocial.org/en/latest/user_guide/custom_css" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about custom profile CSS (opens in a new tab)</a> - </TextArea> - } - <Submit onClick={saveProfile} label="Save profile info" errorMsg={errorMsg} statusMsg={statusMsg} /> - </div> - ); -};
\ No newline at end of file diff --git a/web/source/settings-panel/user/settings.js b/web/source/settings-panel/user/settings.js deleted file mode 100644 index ccb3e911d..000000000 --- a/web/source/settings-panel/user/settings.js +++ /dev/null @@ -1,140 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); -const React = require("react"); -const Redux = require("react-redux"); - -const api = require("../lib/api"); -const user = require("../redux/reducers/user").actions; -const submit = require("../lib/submit"); - -const Languages = require("../components/languages"); -const Submit = require("../components/submit"); - -const { - Checkbox, - Select, -} = require("../components/form-fields").formFields(user.setSettingsVal, (state) => state.user.settings); - -module.exports = function UserSettings() { - const dispatch = Redux.useDispatch(); - - const [errorMsg, setError] = React.useState(""); - const [statusMsg, setStatus] = React.useState(""); - - const updateSettings = submit( - () => dispatch(api.user.updateSettings()), - {setStatus, setError} - ); - - return ( - <> - <div className="user-settings"> - <h1>Post settings</h1> - <Select id="source.language" name="Default post language" options={ - <Languages/> - }> - </Select> - <Select id="source.privacy" name="Default post privacy" options={ - <> - <option value="private">Private / followers-only</option> - <option value="unlisted">Unlisted</option> - <option value="public">Public</option> - </> - }> - <a href="https://docs.gotosocial.org/en/latest/user_guide/posts/#privacy-settings" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about post privacy settings (opens in a new tab)</a> - </Select> - <Select id="source.status_format" name="Default post format" options={ - <> - <option value="plain">Plain (default)</option> - <option value="markdown">Markdown</option> - </> - }> - <a href="https://docs.gotosocial.org/en/latest/user_guide/posts/#input-types" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about post format settings (opens in a new tab)</a> - </Select> - <Checkbox - id="source.sensitive" - name="Mark my posts as sensitive by default" - /> - - <Submit onClick={updateSettings} label="Save post settings" errorMsg={errorMsg} statusMsg={statusMsg}/> - </div> - <div> - <PasswordChange/> - </div> - </> - ); -}; - -function PasswordChange() { - const dispatch = Redux.useDispatch(); - - const [errorMsg, setError] = React.useState(""); - const [statusMsg, setStatus] = React.useState(""); - - const [oldPassword, setOldPassword] = React.useState(""); - const [newPassword, setNewPassword] = React.useState(""); - const [newPasswordConfirm, setNewPasswordConfirm] = React.useState(""); - - function changePassword() { - if (newPassword !== newPasswordConfirm) { - setError("New password and confirm new password did not match!"); - return; - } - - setStatus("PATCHing"); - setError(""); - return Promise.try(() => { - let data = { - old_password: oldPassword, - new_password: newPassword - }; - return dispatch(api.apiCall("POST", "/api/v1/user/password_change", data, "form")); - }).then(() => { - setStatus("Saved!"); - setOldPassword(""); - setNewPassword(""); - setNewPasswordConfirm(""); - }).catch((e) => { - setError(e.message); - setStatus(""); - }); - } - - return ( - <> - <h1>Change password</h1> - <div className="labelinput"> - <label htmlFor="password">Current password</label> - <input name="password" id="password" type="password" autoComplete="current-password" value={oldPassword} onChange={(e) => setOldPassword(e.target.value)} /> - </div> - <div className="labelinput"> - <label htmlFor="new-password">New password</label> - <input name="new-password" id="new-password" type="password" autoComplete="new-password" value={newPassword} onChange={(e) => setNewPassword(e.target.value)} /> - </div> - <div className="labelinput"> - <label htmlFor="confirm-new-password">Confirm new password</label> - <input name="confirm-new-password" id="confirm-new-password" type="password" autoComplete="new-password" value={newPasswordConfirm} onChange={(e) => setNewPasswordConfirm(e.target.value)} /> - </div> - <Submit onClick={changePassword} label="Save new password" errorMsg={errorMsg} statusMsg={statusMsg}/> - </> - ); -}
\ No newline at end of file |