diff --git a/.github/ISSUE_TEMPLATE/1.web_bug_report.yml b/.github/ISSUE_TEMPLATE/1.web_bug_report.yml deleted file mode 100644 index 20e27d103c..0000000000 --- a/.github/ISSUE_TEMPLATE/1.web_bug_report.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Bug Report (Web Interface) -description: If you are using Mastodon's web interface and something is not working as expected -labels: [bug, 'status/to triage', 'area/web interface'] -body: - - type: markdown - attributes: - value: | - Make sure that you are submitting a new bug that was not previously reported or already fixed. - - Please use a concise and distinct title for the issue. - - type: textarea - attributes: - label: Steps to reproduce the problem - description: What were you trying to do? - value: | - 1. - 2. - 3. - ... - validations: - required: true - - type: input - attributes: - label: Expected behaviour - description: What should have happened? - validations: - required: true - - type: input - attributes: - label: Actual behaviour - description: What happened? - validations: - required: true - - type: textarea - attributes: - label: Detailed description - validations: - required: false - - type: input - attributes: - label: Mastodon instance - description: The address of the Mastodon instance where you experienced the issue - placeholder: mastodon.social - validations: - required: true - - type: input - attributes: - label: Mastodon version - description: | - This is displayed at the bottom of the About page, eg. `v4.1.2+nightly-20230627` - placeholder: v4.1.2 - validations: - required: true - - type: input - attributes: - label: Browser name and version - description: | - What browser are you using when getting this bug? Please specify the version as well. - placeholder: Firefox 105.0.3 - validations: - required: true - - type: input - attributes: - label: Operating system - description: | - What OS are you running? Please specify the version as well. - placeholder: macOS 13.4.1 - validations: - required: true - - type: textarea - attributes: - label: Technical details - description: | - Any additional technical details you may have. This can include the full error log, inspector's output… - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/2.server_bug_report.yml b/.github/ISSUE_TEMPLATE/2.server_bug_report.yml deleted file mode 100644 index 49d5f57209..0000000000 --- a/.github/ISSUE_TEMPLATE/2.server_bug_report.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: Bug Report (server / API) -description: | - If something is not working as expected, but is not from using the web interface. -labels: [bug, 'status/to triage'] -body: - - type: markdown - attributes: - value: | - Make sure that you are submitting a new bug that was not previously reported or already fixed. - - Please use a concise and distinct title for the issue. - - type: textarea - attributes: - label: Steps to reproduce the problem - description: What were you trying to do? - value: | - 1. - 2. - 3. - ... - validations: - required: true - - type: input - attributes: - label: Expected behaviour - description: What should have happened? - validations: - required: true - - type: input - attributes: - label: Actual behaviour - description: What happened? - validations: - required: true - - type: textarea - attributes: - label: Detailed description - validations: - required: false - - type: input - attributes: - label: Mastodon instance - description: The address of the Mastodon instance where you experienced the issue - placeholder: mastodon.social - validations: - required: false - - type: input - attributes: - label: Mastodon version - description: | - This is displayed at the bottom of the About page, eg. `v4.1.2+nightly-20230627` - placeholder: v4.1.2 - validations: - required: false - - type: textarea - attributes: - label: Technical details - description: | - Any additional technical details you may have, like logs or error traces - value: | - If this is happening on your own Mastodon server, please fill out those: - - Ruby version: (from `ruby --version`, eg. v3.1.2) - - Node.js version: (from `node --version`, eg. v18.16.0) - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/3.feature_request.yml b/.github/ISSUE_TEMPLATE/3.feature_request.yml deleted file mode 100644 index 2cabcf61e0..0000000000 --- a/.github/ISSUE_TEMPLATE/3.feature_request.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Feature Request -description: I have a suggestion -labels: [suggestion] -body: - - type: markdown - attributes: - value: | - Please use a concise and distinct title for the issue. - - Consider: Could it be implemented as a 3rd party app using the REST API instead? - - type: textarea - attributes: - label: Pitch - description: Describe your idea for a feature. Make sure it has not already been suggested/implemented/turned down before. - validations: - required: true - - type: textarea - attributes: - label: Motivation - description: Why do you think this feature is needed? Who would benefit from it? - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index f5d3196528..0000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: GitHub Discussions - url: https://github.com/mastodon/mastodon/discussions - about: Please ask and answer questions here. diff --git a/README.md b/README.md index 12670f9325..d18877acb7 100644 --- a/README.md +++ b/README.md @@ -1,181 +1,29 @@ -# Chuckya +# Chuckya (standalone frontend) -Chuckya is a close-to-upstream soft fork of Mastodon Glitch Edition (more commonly known as glitch-soc) that aims to introduce more experimental features/fixes with the goal of making the overall experience more enjoyable. Although it's mainly developed for and used on the [wetdry.world](https://wetdry.world) instance, it can be deployed by any server admin as a drop-in, backwards-compatible replacement for Mastodon. +This is a somewhat hacky fork of Chuckya that adds standalone support (based on https://iceshrimp.dev/iceshrimp/masto-fe-standalone) (meaning your browser can OAuth against an arbitrary instance). It's currently tested to work (for the most part) with Iceshrimp and Iceshrimp.NET. -Here are some of the changes compared to glitch-soc: +To set this up yourself, clone the repo into e.g. `/home/user/masto-fe-standalone` and run `yarn && yarn build:production`. -- Emoji reactions (glitch-soc/mastodon#2462) -- Tenor GIF picker (originally from [koyu.space](https://github.com/koyuspace/mastodon)) -- Mastodon Modern theme (licensed under CC-BY-SA 4.0, [original repo](https://codeberg.org/Freeplay/Mastodon-Modern)) -- Workaround for OpenGraph video embeds when using [Jortage](https://jortage.com) -- Multiple fixes for oEmbed/OpenGraph embeds -- Polls can be posted alongside media (glitch-soc/mastodon#2524) -- Polls can have only one option -- Restores status trend half-life to 2 hours -- Allows dashes in custom emote names -- Emojis can be put side-by-side -- Minor media attachment tweaks -- Custom favicon +Then configure nginx for a subdomain like this: -Changes previously in Chuckya that made their way into vanilla Mastodon: +``` +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} -- Unicode emojis use [`jdecked/twemoji`](https://github.com/jdecked/twemoji) v15 graphics (mastodon/mastodon#28404) +server { + include sites/example.com/inc/ssl.conf; + server_name masto.example.com; -Setup instructions are the same as [glitch-soc's](https://glitch-soc.github.io/docs); just replace the glitch-soc repo URL with `https://github.com/TheEssem/mastodon`. + location / { + root /home/user/masto-fe-standalone/public/; + index index.html; + try_files $uri /index.html; + } +} +``` -Original glitch-soc readme is below. +And open `https://masto.example.com` in your browser, type in your instance domain, press the button & follow the OAuth flow. -# Mastodon Glitch Edition - -[![Ruby Testing](https://github.com/glitch-soc/mastodon/actions/workflows/test-ruby.yml/badge.svg)](https://github.com/glitch-soc/mastodon/actions/workflows/test-ruby.yml) -[![Crowdin](https://badges.crowdin.net/glitch-soc/localized.svg)][glitch-crowdin] - -[glitch-crowdin]: https://crowdin.com/project/glitch-soc - -So here's the deal: we all work on this code, and anyone who uses that does so absolutely at their own risk. can you dig it? - -- You can view documentation for this project at [glitch-soc.github.io/docs/](https://glitch-soc.github.io/docs/). -- And contributing guidelines are available [here](CONTRIBUTING.md) and [here](https://glitch-soc.github.io/docs/contributing/). - -Mastodon Glitch Edition is a fork of [Mastodon](https://github.com/mastodon/mastodon). Upstream's README file is reproduced below. - ---- - -

- - - Mastodon -

- -[![GitHub release](https://img.shields.io/github/release/mastodon/mastodon.svg)][releases] -[![Ruby Testing](https://github.com/mastodon/mastodon/actions/workflows/test-ruby.yml/badge.svg)](https://github.com/mastodon/mastodon/actions/workflows/test-ruby.yml) -[![Crowdin](https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg)][crowdin] - -[releases]: https://github.com/mastodon/mastodon/releases -[crowdin]: https://crowdin.com/project/mastodon - -Mastodon is a **free, open-source social network server** based on ActivityPub where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, and video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub!) - -Click below to **learn more** in a video: - -[![Screenshot](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/ezgif-2-60f1b00403.gif)][youtube_demo] - -[youtube_demo]: https://www.youtube.com/watch?v=IPSbNdBmWKE - -## Navigation - -- [Project homepage 🐘](https://joinmastodon.org) -- [Support the development via Patreon][patreon] -- [View sponsors](https://joinmastodon.org/sponsors) -- [Blog](https://blog.joinmastodon.org) -- [Documentation](https://docs.joinmastodon.org) -- [Roadmap](https://joinmastodon.org/roadmap) -- [Official Docker image](https://github.com/mastodon/mastodon/pkgs/container/mastodon) -- [Browse Mastodon servers](https://joinmastodon.org/communities) -- [Browse Mastodon apps](https://joinmastodon.org/apps) - -[patreon]: https://www.patreon.com/mastodon - -## Features - - - -### No vendor lock-in: Fully interoperable with any conforming platform - -It doesn't have to be Mastodon; whatever implements ActivityPub is part of the social network! [Learn more](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/) - -### Real-time, chronological timeline updates - -Updates of people you're following appear in real-time in the UI via WebSockets. There's a firehose view as well! - -### Media attachments like images and short videos - -Upload and view images and WebM/MP4 videos attached to the updates. Videos with no audio track are treated like GIFs; normal videos loop continuously! - -### Safety and moderation tools - -Mastodon includes private posts, locked accounts, phrase filtering, muting, blocking, and all sorts of other features, along with a reporting and moderation system. [Learn more](https://blog.joinmastodon.org/2018/07/cage-the-mastodon/) - -### OAuth2 and a straightforward REST API - -Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Streaming APIs. This results in a rich app ecosystem with a lot of choices! - -## Deployment - -### Tech stack - -- **Ruby on Rails** powers the REST API and other web pages -- **React.js** and Redux are used for the dynamic parts of the interface -- **Node.js** powers the streaming API - -### Requirements - -- **PostgreSQL** 12+ -- **Redis** 4+ -- **Ruby** 3.1+ -- **Node.js** 16+ - -The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation. - -## Development - -### Vagrant - -A **Vagrant** configuration is included for development purposes. To use it, complete the following steps: - -- Install Vagrant and Virtualbox -- Install the `vagrant-hostsupdater` plugin: `vagrant plugin install vagrant-hostsupdater` -- Run `vagrant up` -- Run `vagrant ssh -c "cd /vagrant && bin/dev"` -- Open `http://mastodon.local` in your browser - -### MacOS - -To set up **MacOS** for native development, complete the following steps: - -- Use a Ruby version manager to install the specified version from `.ruby-version` -- Run `brew install postgresql@14 redis imagemagick libidn` to install required dependencies -- Navigate to Mastodon's root directory and run `brew install nvm` then `nvm use` to use the version from `.nvmrc` -- Run `corepack enable && corepack prepare` -- Run `bundle exec rails db:setup` (optionally prepend `RAILS_ENV=development` to target the dev environment) -- Finally, run `bin/dev` which will launch the local services via `overmind` (if installed) or `foreman` - -### Docker - -For development with **Docker**, complete the following steps: - -- Install Docker Desktop -- Run `docker compose -f .devcontainer/docker-compose.yml up -d` -- Run `docker compose -f .devcontainer/docker-compose.yml exec app .devcontainer/post-create.sh` -- Finally, run `docker compose -f .devcontainer/docker-compose.yml exec app bin/dev` - -If you are using an IDE with [support for the Development Container specification](https://containers.dev/supporting), it will run the above `docker compose` commands automatically. For **Visual Studio Code** this requires the [Dev Container extension](https://containers.dev/supporting#dev-containers). - -### GitHub Codespaces - -To get you coding in just a few minutes, GitHub Codespaces provides a web-based version of Visual Studio Code and a cloud-hosted development environment fully configured with the software needed for this project.. - -- Click this button to create a new codespace:
- [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=52281283&devcontainer_path=.devcontainer%2Fcodespaces%2Fdevcontainer.json) -- Wait for the environment to build. This will take a few minutes. -- When the editor is ready, run `bin/dev` in the terminal. -- After a few seconds, a popup will appear with a button labeled _Open in Browser_. This will open Mastodon. -- On the _Ports_ tab, right click on the “stream” row and select _Port visibility_ → _Public_. - -## Contributing - -Mastodon is **free, open-source software** licensed under **AGPLv3**. - -You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository or submit translations using Crowdin. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md). If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon). - -**IRC channel**: #mastodon on irc.libera.chat - -## License - -Copyright (C) 2016-2024 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md)) - -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 . +Should anything break, open `https://masto.example.com/logout.html` or clear local storage manually. diff --git a/app/javascript/flavours/glitch/components/edited_timestamp/index.jsx b/app/javascript/flavours/glitch/components/edited_timestamp/index.jsx index 0849c069d1..0ebc52721e 100644 --- a/app/javascript/flavours/glitch/components/edited_timestamp/index.jsx +++ b/app/javascript/flavours/glitch/components/edited_timestamp/index.jsx @@ -65,7 +65,7 @@ class EditedTimestamp extends PureComponent { return ( ); diff --git a/app/javascript/flavours/glitch/components/router.tsx b/app/javascript/flavours/glitch/components/router.tsx index 0637c35ada..fd3f9c6f4d 100644 --- a/app/javascript/flavours/glitch/components/router.tsx +++ b/app/javascript/flavours/glitch/components/router.tsx @@ -49,25 +49,13 @@ function normalizePath( ); } - if ( - layoutFromWindow() === 'multi-column' && - !location.pathname?.startsWith('/deck') - ) { - location.pathname = `/deck${location.pathname}`; + if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) { + originalPush(`/deck${path}`, state); + } else { + originalPush(path, state); } - - return location; } -browserHistory.push = (path: HistoryPath, state?: MastodonLocationState) => { - const location = normalizePath(path, state); - - location.state = location.state ?? {}; - location.state.fromMastodon = true; - - originalPush(location); -}; - browserHistory.replace = (path: HistoryPath, state?: MastodonLocationState) => { const location = normalizePath(path, state); @@ -78,7 +66,7 @@ browserHistory.replace = (path: HistoryPath, state?: MastodonLocationState) => { location.state.fromMastodon = true; } - originalReplace(location); + originalReplace(path, state); }; export const Router: React.FC = ({ children }) => { diff --git a/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx b/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx index 7fda25ded5..3c613f7915 100644 --- a/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx @@ -45,8 +45,6 @@ export const ActionBar = () => { let menu = []; - menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' }); - menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' }); menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' }); menu.push(null); menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' }); @@ -65,7 +63,7 @@ export const ActionBar = () => { return ( ({ const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning }) => { if (needsLockWarning) { - return }} />} />; + return }} />} />; } if (hashtagWarning) { diff --git a/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx b/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx index 10d4097eef..40e96239f3 100644 --- a/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx +++ b/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx @@ -91,7 +91,7 @@ class LocalSettingsNavigation extends PureComponent { + - + ), }} /> diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx index 01a47639e6..5fe87eb593 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx @@ -325,7 +325,7 @@ class DetailedStatus extends ImmutablePureComponent {
- + {visibilityLink} diff --git a/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx index 2f0c07e78b..d0e232d841 100644 --- a/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx @@ -63,7 +63,7 @@ class DeprecatedSettingsModal extends PureComponent {
    { settings.map((setting_name) => (
  • - +
  • )) }
diff --git a/app/javascript/flavours/glitch/features/ui/components/link_footer.jsx b/app/javascript/flavours/glitch/features/ui/components/link_footer.jsx index 6741552731..2115442fab 100644 --- a/app/javascript/flavours/glitch/features/ui/components/link_footer.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/link_footer.jsx @@ -64,37 +64,9 @@ class LinkFooter extends PureComponent { return (

- {domain}: - {' '} - - {statusPageUrl && ( - <> - {DividingCircle} - - - )} - {canInvite && ( - <> - {DividingCircle} - - - )} - {canProfileDirectory && ( - <> - {DividingCircle} - - - )} + Masto-FE-standalone {DividingCircle} - -

- -

- Mastodon: - {' '} - - {DividingCircle} - + {DividingCircle} {DividingCircle} diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx index c257b8cdf5..a71809c6ba 100644 --- a/app/javascript/flavours/glitch/features/ui/index.jsx +++ b/app/javascript/flavours/glitch/features/ui/index.jsx @@ -178,7 +178,7 @@ class SwitchingColumnsArea extends PureComponent { if (singleColumn) { redirect = ; } else { - redirect = ; + redirect = ; } } else if (singleUserMode && owner && initialState?.accounts[owner]) { redirect = ; @@ -194,11 +194,10 @@ class SwitchingColumnsArea extends PureComponent { {redirect} - {singleColumn ? : null} - {singleColumn && pathName.startsWith('/deck/') ? : null} + {singleColumn ? : null} + {pathName.startsWith('/deck/') ? : null} {/* Redirect old bookmarks (without /deck) with home-like routes to the advanced interface */} - {!singleColumn && pathName === '/getting-started' ? : null} - {!singleColumn && pathName === '/home' ? : null} + {!singleColumn && pathName === '/home' ? : null} diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js index f0d8c96c1e..e6a6f50df5 100644 --- a/app/javascript/flavours/glitch/reducers/local_settings.js +++ b/app/javascript/flavours/glitch/reducers/local_settings.js @@ -14,8 +14,8 @@ const initialState = ImmutableMap({ confirm_missing_media_description: false, confirm_boost_missing_media_description: false, confirm_before_clearing_draft: true, - prepend_cw_re: true, - preselect_on_reply: true, + prepend_cw_re: false, + preselect_on_reply: false, inline_preview_cards: true, hicolor_privacy_icons: false, show_content_type_choice: false, @@ -30,7 +30,7 @@ const initialState = ImmutableMap({ enabled : true, auto : ImmutableMap({ all : false, - notifications : true, + notifications : false, lengthy : true, reblogs : false, replies : false, diff --git a/app/javascript/flavours/glitch/stream.js b/app/javascript/flavours/glitch/stream.js index ff3af5fd88..2113849274 100644 --- a/app/javascript/flavours/glitch/stream.js +++ b/app/javascript/flavours/glitch/stream.js @@ -236,8 +236,9 @@ const createConnection = (streamingAPIBaseURL, accessToken, channelName, { conne channelName = params.shift(); if (streamingAPIBaseURL.startsWith('ws')) { + params.push(`access_token=${accessToken}`); // @ts-expect-error - const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken); + const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming?${params.join('&')}`, accessToken); // @ts-expect-error ws.onopen = connected; diff --git a/app/javascript/flavours/glitch/utils/backend_links.js b/app/javascript/flavours/glitch/utils/backend_links.js index 2028a1e608..fc20052907 100644 --- a/app/javascript/flavours/glitch/utils/backend_links.js +++ b/app/javascript/flavours/glitch/utils/backend_links.js @@ -1,6 +1,6 @@ -export const preferencesLink = '/settings/preferences'; -export const profileLink = '/settings/profile'; -export const signOutLink = '/auth/sign_out'; +export const preferencesLink = undefined; +export const profileLink = undefined; +export const signOutLink = '/logout.html'; export const privacyPolicyLink = '/privacy-policy'; export const accountAdminLink = (id) => `/admin/accounts/${id}`; export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${account_id}/statuses/${status_id}`; diff --git a/app/javascript/flavours/glitch/utils/log_out.ts b/app/javascript/flavours/glitch/utils/log_out.ts index d4db471a6e..e590a1077c 100644 --- a/app/javascript/flavours/glitch/utils/log_out.ts +++ b/app/javascript/flavours/glitch/utils/log_out.ts @@ -29,7 +29,7 @@ export const logOut = () => { submitButton.setAttribute('type', 'submit'); form.appendChild(submitButton); - form.method = 'post'; + form.method = 'get'; form.action = signOutLink; form.style.display = 'none'; diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index fd44b3952b..09b49f7936 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -324,7 +324,7 @@ "footer.invite": "Invite people", "footer.keyboard_shortcuts": "Keyboard shortcuts", "footer.privacy_policy": "Privacy policy", - "footer.source_code": "View source code", + "footer.source_code": "Source code", "footer.status": "Status", "generic.saved": "Saved", "getting_started.heading": "Getting started", diff --git a/config/webpack/shared.js b/config/webpack/shared.js index df61dff312..987110a179 100644 --- a/config/webpack/shared.js +++ b/config/webpack/shared.js @@ -41,8 +41,7 @@ module.exports = { entry: entries, output: { - filename: 'js/[name]-[chunkhash].js', - chunkFilename: 'js/[name]-[chunkhash].chunk.js', + filename: 'js/[name].js', hotUpdateChunkFilename: 'js/[id]-[hash].hot-update.js', hashFunction: 'sha256', crossOriginLoading: 'anonymous', @@ -87,8 +86,7 @@ module.exports = { }, ), new MiniCssExtractPlugin({ - filename: 'css/[name]-[contenthash:8].css', - chunkFilename: 'css/[name]-[contenthash:8].chunk.css', + filename: 'css/[name].css', }), new AssetsManifestPlugin({ integrity: true, diff --git a/package.json b/package.json index c8fc6e0221..ca0e435e2b 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,9 @@ "streaming" ], "scripts": { - "build:development": "cross-env RAILS_ENV=development NODE_ENV=development ./bin/webpack", - "build:production": "cross-env RAILS_ENV=production NODE_ENV=production ./bin/webpack", + "build:development": "cross-env NODE_ENV=development webpack --config config/webpack/development.js", + "build:production": "cross-env NODE_ENV=production webpack --config config/webpack/production.js", + "build": "cross-env NODE_ENV=production webpack --config config/webpack/production.js", "fix:js": "eslint . --ext=.js,.jsx,.ts,.tsx --cache --report-unused-disable-directives --fix", "fix:css": "stylelint --fix \"**/*.{css,scss}\"", "fix": "yarn fix:js && yarn fix:css", diff --git a/public/auth.js b/public/auth.js new file mode 100644 index 0000000000..6066b68962 --- /dev/null +++ b/public/auth.js @@ -0,0 +1,101 @@ +document.addEventListener("DOMContentLoaded", async function() { + await ready(); +}); + +async function ready() { + const domain = localStorage.getItem('domain'); + let accessToken = localStorage.getItem(`access_token`); + + if (domain) document.getElementById('instance').value = domain; + + const urlParams = new URLSearchParams(window.location.search); + const code = urlParams.get('code'); + + if (domain && code && !accessToken) await getToken(code, domain).then(res => accessToken = res); + if (accessToken) { + window.location.href = '/prepare.html'; + } +} + +async function auth() { + setMessage('Please wait'); + const instance = document.getElementById('instance').value; + const domain = instance.match(/(?:https?:\/\/)?(.*)/)[1]; + if (!domain) { + setMessage('Invalid instance', false); + return; + } + + localStorage.setItem('domain', domain); + + // We need to run this every time in cases like Iceshrimp, where the client id/secret aren't reusable (yet) because they contain use-once session information + await registerApp(domain); + + authorize(domain); +} + +async function registerApp(domain) { + setMessage('Registering app'); + + const appsUrl = `https://${domain}/api/v1/apps`; + const formData = new FormData(); + formData.append('client_name', 'Masto-FE standalone'); + formData.append('redirect_uris', document.location.origin + document.location.pathname); + formData.append('scopes', 'read write follow push'); + + // eslint-disable-next-line promise/catch-or-return + await fetch(appsUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams(formData), + }) + .then(async res => { + const app = await res.json(); + localStorage.setItem(`client_id`, app.client_id); + localStorage.setItem(`client_secret`, app.client_secret); + }); +} + +function authorize(domain) { + setMessage('Authorizing'); + const clientId = localStorage.getItem(`client_id`); + document.location.href = `https://${domain}/oauth/authorize?response_type=code&client_id=${clientId}&redirect_uri=${document.location.origin + document.location.pathname}&scope=read+write+follow+push`; +} + +async function getToken(code, domain) { + setMessage('Getting token'); + + const tokenUrl = `https://${domain}/oauth/token`; + const clientId = localStorage.getItem(`client_id`); + const clientSecret = localStorage.getItem(`client_secret`); + + const formData = new FormData(); + formData.append('grant_type', 'authorization_code'); + formData.append('code', code); + formData.append('client_id', clientId); + formData.append('client_secret', clientSecret); + formData.append('scope', 'read write follow push'); + formData.append('redirect_uri', document.location.origin + document.location.pathname); + + + // eslint-disable-next-line promise/catch-or-return + return fetch(tokenUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams(formData), + }) + .then(async res => { + const app = await res.json(); + if (app.access_token) localStorage.setItem(`access_token`, app.access_token); + return app.access_token; + }); +} + +function setMessage(message, disabled = true) { + document.getElementById('message').textContent = message; + document.getElementById('btn').disabled = disabled; +} \ No newline at end of file diff --git a/public/images/mascot.svg b/public/images/mascot.svg new file mode 100644 index 0000000000..23384b6617 --- /dev/null +++ b/public/images/mascot.svg @@ -0,0 +1,11 @@ + +image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000000..abf9880378 --- /dev/null +++ b/public/index.html @@ -0,0 +1,33 @@ + + + + + + Masto-FE standalone + + + + + + + + + + + + + + + + + + + + + + + +

+
+ + diff --git a/public/login.html b/public/login.html new file mode 100644 index 0000000000..90e56024af --- /dev/null +++ b/public/login.html @@ -0,0 +1,13 @@ + + + + + Login | Masto-FE standalone + + + + + + + + \ No newline at end of file diff --git a/public/logout.html b/public/logout.html new file mode 100644 index 0000000000..f49e3dc501 --- /dev/null +++ b/public/logout.html @@ -0,0 +1,14 @@ + + + + + Logout | Masto-FE standalone + + + +Clearing local storage and redirecting back to login... + + \ No newline at end of file diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000000..c1c0f9dda7 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,12 @@ +{ + "background_color": "#191b22", + "categories": ["social"], + "description": "Masto-FE standalone", + "display": "standalone", + "name": "Masto-FE standalone", + "serviceworker": { + "src": "/sw.js" + }, + "start_url": "/getting-started", + "theme_color": "#282c37" +} diff --git a/public/prepare.html b/public/prepare.html new file mode 100644 index 0000000000..e6e5f1e0cc --- /dev/null +++ b/public/prepare.html @@ -0,0 +1,11 @@ + + + + + Login | Masto-FE standalone + + + +

Preparing state object...

+ + \ No newline at end of file diff --git a/public/verify-state.js b/public/verify-state.js new file mode 100644 index 0000000000..8d2c222ddd --- /dev/null +++ b/public/verify-state.js @@ -0,0 +1,1094 @@ +loadState().then(_ => null); + +async function loadState() { + const domain = localStorage.getItem('domain'); + const access_token = localStorage.getItem('access_token'); + const storedState = localStorage.getItem('initial_state'); + + if (!domain || !access_token) { + window.location.href = '/login.html'; + return; + } + + if (storedState && window.location.pathname !== '/prepare.html') { + document.getElementById('initial-state').textContent = storedState; + } + + const apiUrl = `https://${domain}/api`; + const instance = await fetch(`${apiUrl}/v1/instance`).then(async p => await p.json()); + const options = {headers: {Authorization: `Bearer ${access_token}`}}; + const credentials = await fetch(`${apiUrl}/v1/accounts/verify_credentials`, options).then(async p => await p.json()); + const state = { + "accounts": { + "plc":{ + "accepts_direct_messages_from":"everybody", + "acct": credentials.acct, + "avatar": credentials.avatar, + "avatar_static": credentials.avatar_static, + "bot": credentials.bot, + "created_at": credentials.created_at, + "display_name": credentials.display_name, + "emojis":[], + "fields":[], + "follow_requests_count":0, + "followers_count": credentials.followers_count, + "following_count": credentials.following_count, + "fqn":`${credentials.acct}@${domain}`, + "header": credentials.header, + "header_static": credentials.header_static, + "id": credentials.id, + "last_status_at": credentials.created_at, + "locked": credentials.locked, + "note":"", + "source": credentials.source, + "statuses_count": credentials.statuses_count, + "url": credentials.url, + "username": credentials.acct + } + }, + "char_limit": instance.configuration.statuses.max_characters, + "compose": { + "allow_content_types": [ + "text/x.misskeymarkdown" + ], + "default_privacy": credentials.source.privacy, + "default_sensitive": credentials.source.sensitive, + "me": credentials.id + }, + "media_attachments": { + "accept_content_types": instance.configuration.media_attachments.supported_mime_types + }, + "meta": { + "access_token": access_token, + "admin": "0", + "advanced_layout": true, + "auto_play_gif": false, + "boost_modal": false, + "compact_reaction": false, + "delete_modal": true, + "display_sensitive_media": false, + "domain": domain, + "enable_reaction": true, + "locale": "en", + "mascot": "/images/mascot.svg", + "me": credentials.id, + "reduce_motion": false, + "show_quote_button": true, + "base_url": `https://${domain}`, + "streaming_api_base_url": `wss://${domain}`, + "title": `${instance.title}`, + "unfollow_modal": true, + "source_url": 'https://iceshrimp.dev/iceshrimp/masto-fe-standalone', + "version": instance.version + }, + "max_toot_chars": instance.configuration.statuses.max_characters, + "poll_limits": { + "max_expiration": instance.configuration.polls.max_expiration, + "max_option_chars": instance.configuration.polls.max_characters_per_option, + "max_options": instance.configuration.polls.max_options, + "min_expiration": instance.configuration.polls.min_expiration + }, + "push_subscription": null, + "rights": { + "admin": false, + "delete_others_notice": false + }, + "settings": { + "frequentlyUsedLanguages": { + "en": 1 + } + }, + "languages":[ + [ + "aa", + "Afar", + "Afaraf" + ], + [ + "ab", + "Abkhaz", + "аҧсуа бызшәа" + ], + [ + "ae", + "Avestan", + "avesta" + ], + [ + "af", + "Afrikaans", + "Afrikaans" + ], + [ + "ak", + "Akan", + "Akan" + ], + [ + "am", + "Amharic", + "አማርኛ" + ], + [ + "an", + "Aragonese", + "aragonés" + ], + [ + "ar", + "Arabic", + "اللغة العربية" + ], + [ + "as", + "Assamese", + "অসমীয়া" + ], + [ + "av", + "Avaric", + "авар мацӀ" + ], + [ + "ay", + "Aymara", + "aymar aru" + ], + [ + "az", + "Azerbaijani", + "azərbaycan dili" + ], + [ + "ba", + "Bashkir", + "башҡорт теле" + ], + [ + "be", + "Belarusian", + "беларуская мова" + ], + [ + "bg", + "Bulgarian", + "български език" + ], + [ + "bh", + "Bihari", + "भोजपुरी" + ], + [ + "bi", + "Bislama", + "Bislama" + ], + [ + "bm", + "Bambara", + "bamanankan" + ], + [ + "bn", + "Bengali", + "বাংলা" + ], + [ + "bo", + "Tibetan", + "བོད་ཡིག" + ], + [ + "br", + "Breton", + "brezhoneg" + ], + [ + "bs", + "Bosnian", + "bosanski jezik" + ], + [ + "ca", + "Catalan", + "Català" + ], + [ + "ce", + "Chechen", + "нохчийн мотт" + ], + [ + "ch", + "Chamorro", + "Chamoru" + ], + [ + "co", + "Corsican", + "corsu" + ], + [ + "cr", + "Cree", + "ᓀᐦᐃᔭᐍᐏᐣ" + ], + [ + "cs", + "Czech", + "čeština" + ], + [ + "cu", + "Old Church Slavonic", + "ѩзыкъ словѣньскъ" + ], + [ + "cv", + "Chuvash", + "чӑваш чӗлхи" + ], + [ + "cy", + "Welsh", + "Cymraeg" + ], + [ + "da", + "Danish", + "dansk" + ], + [ + "de", + "German", + "Deutsch" + ], + [ + "dv", + "Divehi", + "Dhivehi" + ], + [ + "dz", + "Dzongkha", + "རྫོང་ཁ" + ], + [ + "ee", + "Ewe", + "Eʋegbe" + ], + [ + "el", + "Greek", + "Ελληνικά" + ], + [ + "en", + "English", + "English" + ], + [ + "eo", + "Esperanto", + "Esperanto" + ], + [ + "es", + "Spanish", + "Español" + ], + [ + "et", + "Estonian", + "eesti" + ], + [ + "eu", + "Basque", + "euskara" + ], + [ + "fa", + "Persian", + "فارسی" + ], + [ + "ff", + "Fula", + "Fulfulde" + ], + [ + "fi", + "Finnish", + "suomi" + ], + [ + "fj", + "Fijian", + "Vakaviti" + ], + [ + "fo", + "Faroese", + "føroyskt" + ], + [ + "fr", + "French", + "Français" + ], + [ + "fy", + "Western Frisian", + "Frysk" + ], + [ + "ga", + "Irish", + "Gaeilge" + ], + [ + "gd", + "Scottish Gaelic", + "Gàidhlig" + ], + [ + "gl", + "Galician", + "galego" + ], + [ + "gu", + "Gujarati", + "ગુજરાતી" + ], + [ + "gv", + "Manx", + "Gaelg" + ], + [ + "ha", + "Hausa", + "هَوُسَ" + ], + [ + "he", + "Hebrew", + "עברית" + ], + [ + "hi", + "Hindi", + "हिन्दी" + ], + [ + "ho", + "Hiri Motu", + "Hiri Motu" + ], + [ + "hr", + "Croatian", + "Hrvatski" + ], + [ + "ht", + "Haitian", + "Kreyòl ayisyen" + ], + [ + "hu", + "Hungarian", + "magyar" + ], + [ + "hy", + "Armenian", + "Հայերեն" + ], + [ + "hz", + "Herero", + "Otjiherero" + ], + [ + "ia", + "Interlingua", + "Interlingua" + ], + [ + "id", + "Indonesian", + "Bahasa Indonesia" + ], + [ + "ie", + "Interlingue", + "Interlingue" + ], + [ + "ig", + "Igbo", + "Asụsụ Igbo" + ], + [ + "ii", + "Nuosu", + "ꆈꌠ꒿ Nuosuhxop" + ], + [ + "ik", + "Inupiaq", + "Iñupiaq" + ], + [ + "io", + "Ido", + "Ido" + ], + [ + "is", + "Icelandic", + "Íslenska" + ], + [ + "it", + "Italian", + "Italiano" + ], + [ + "iu", + "Inuktitut", + "ᐃᓄᒃᑎᑐᑦ" + ], + [ + "ja", + "Japanese", + "日本語" + ], + [ + "jv", + "Javanese", + "basa Jawa" + ], + [ + "ka", + "Georgian", + "ქართული" + ], + [ + "kg", + "Kongo", + "Kikongo" + ], + [ + "ki", + "Kikuyu", + "Gĩkũyũ" + ], + [ + "kj", + "Kwanyama", + "Kuanyama" + ], + [ + "kk", + "Kazakh", + "қазақ тілі" + ], + [ + "kl", + "Kalaallisut", + "kalaallisut" + ], + [ + "km", + "Khmer", + "ខេមរភាសា" + ], + [ + "kn", + "Kannada", + "ಕನ್ನಡ" + ], + [ + "ko", + "Korean", + "한국어" + ], + [ + "kr", + "Kanuri", + "Kanuri" + ], + [ + "ks", + "Kashmiri", + "कश्मीरी" + ], + [ + "ku", + "Kurmanji (Kurdish)", + "Kurmancî" + ], + [ + "kv", + "Komi", + "коми кыв" + ], + [ + "kw", + "Cornish", + "Kernewek" + ], + [ + "ky", + "Kyrgyz", + "Кыргызча" + ], + [ + "la", + "Latin", + "latine" + ], + [ + "lb", + "Luxembourgish", + "Lëtzebuergesch" + ], + [ + "lg", + "Ganda", + "Luganda" + ], + [ + "li", + "Limburgish", + "Limburgs" + ], + [ + "ln", + "Lingala", + "Lingála" + ], + [ + "lo", + "Lao", + "ລາວ" + ], + [ + "lt", + "Lithuanian", + "lietuvių kalba" + ], + [ + "lu", + "Luba-Katanga", + "Tshiluba" + ], + [ + "lv", + "Latvian", + "latviešu valoda" + ], + [ + "mg", + "Malagasy", + "fiteny malagasy" + ], + [ + "mh", + "Marshallese", + "Kajin M̧ajeļ" + ], + [ + "mi", + "Māori", + "te reo Māori" + ], + [ + "mk", + "Macedonian", + "македонски јазик" + ], + [ + "ml", + "Malayalam", + "മലയാളം" + ], + [ + "mn", + "Mongolian", + "Монгол хэл" + ], + [ + "mr", + "Marathi", + "मराठी" + ], + [ + "ms", + "Malay", + "Bahasa Melayu" + ], + [ + "mt", + "Maltese", + "Malti" + ], + [ + "my", + "Burmese", + "ဗမာစာ" + ], + [ + "na", + "Nauru", + "Ekakairũ Naoero" + ], + [ + "nb", + "Norwegian Bokmål", + "Norsk bokmål" + ], + [ + "nd", + "Northern Ndebele", + "isiNdebele" + ], + [ + "ne", + "Nepali", + "नेपाली" + ], + [ + "ng", + "Ndonga", + "Owambo" + ], + [ + "nl", + "Dutch", + "Nederlands" + ], + [ + "nn", + "Norwegian Nynorsk", + "Norsk Nynorsk" + ], + [ + "no", + "Norwegian", + "Norsk" + ], + [ + "nr", + "Southern Ndebele", + "isiNdebele" + ], + [ + "nv", + "Navajo", + "Diné bizaad" + ], + [ + "ny", + "Chichewa", + "chiCheŵa" + ], + [ + "oc", + "Occitan", + "occitan" + ], + [ + "oj", + "Ojibwe", + "ᐊᓂᔑᓈᐯᒧᐎᓐ" + ], + [ + "om", + "Oromo", + "Afaan Oromoo" + ], + [ + "or", + "Oriya", + "ଓଡ଼ିଆ" + ], + [ + "os", + "Ossetian", + "ирон æвзаг" + ], + [ + "pa", + "Panjabi", + "ਪੰਜਾਬੀ" + ], + [ + "pi", + "Pāli", + "पाऴि" + ], + [ + "pl", + "Polish", + "Polski" + ], + [ + "ps", + "Pashto", + "پښتو" + ], + [ + "pt", + "Portuguese", + "Português" + ], + [ + "qu", + "Quechua", + "Runa Simi" + ], + [ + "rm", + "Romansh", + "rumantsch grischun" + ], + [ + "rn", + "Kirundi", + "Ikirundi" + ], + [ + "ro", + "Romanian", + "Română" + ], + [ + "ru", + "Russian", + "Русский" + ], + [ + "rw", + "Kinyarwanda", + "Ikinyarwanda" + ], + [ + "sa", + "Sanskrit", + "संस्कृतम्" + ], + [ + "sc", + "Sardinian", + "sardu" + ], + [ + "sd", + "Sindhi", + "सिन्धी" + ], + [ + "se", + "Northern Sami", + "Davvisámegiella" + ], + [ + "sg", + "Sango", + "yângâ tî sängö" + ], + [ + "si", + "Sinhala", + "සිංහල" + ], + [ + "sk", + "Slovak", + "slovenčina" + ], + [ + "sl", + "Slovenian", + "slovenščina" + ], + [ + "sn", + "Shona", + "chiShona" + ], + [ + "so", + "Somali", + "Soomaaliga" + ], + [ + "sq", + "Albanian", + "Shqip" + ], + [ + "sr", + "Serbian", + "српски језик" + ], + [ + "ss", + "Swati", + "SiSwati" + ], + [ + "st", + "Southern Sotho", + "Sesotho" + ], + [ + "su", + "Sundanese", + "Basa Sunda" + ], + [ + "sv", + "Swedish", + "Svenska" + ], + [ + "sw", + "Swahili", + "Kiswahili" + ], + [ + "ta", + "Tamil", + "தமிழ்" + ], + [ + "te", + "Telugu", + "తెలుగు" + ], + [ + "tg", + "Tajik", + "тоҷикӣ" + ], + [ + "th", + "Thai", + "ไทย" + ], + [ + "ti", + "Tigrinya", + "ትግርኛ" + ], + [ + "tk", + "Turkmen", + "Türkmen" + ], + [ + "tl", + "Tagalog", + "Wikang Tagalog" + ], + [ + "tn", + "Tswana", + "Setswana" + ], + [ + "to", + "Tonga", + "faka Tonga" + ], + [ + "tr", + "Turkish", + "Türkçe" + ], + [ + "ts", + "Tsonga", + "Xitsonga" + ], + [ + "tt", + "Tatar", + "татар теле" + ], + [ + "tw", + "Twi", + "Twi" + ], + [ + "ty", + "Tahitian", + "Reo Tahiti" + ], + [ + "ug", + "Uyghur", + "ئۇيغۇرچە‎" + ], + [ + "uk", + "Ukrainian", + "Українська" + ], + [ + "ur", + "Urdu", + "اردو" + ], + [ + "uz", + "Uzbek", + "Ўзбек" + ], + [ + "ve", + "Venda", + "Tshivenḓa" + ], + [ + "vi", + "Vietnamese", + "Tiếng Việt" + ], + [ + "vo", + "Volapük", + "Volapük" + ], + [ + "wa", + "Walloon", + "walon" + ], + [ + "wo", + "Wolof", + "Wollof" + ], + [ + "xh", + "Xhosa", + "isiXhosa" + ], + [ + "yi", + "Yiddish", + "ייִדיש" + ], + [ + "yo", + "Yoruba", + "Yorùbá" + ], + [ + "za", + "Zhuang", + "Saɯ cueŋƅ" + ], + [ + "zh", + "Chinese", + "中文" + ], + [ + "zu", + "Zulu", + "isiZulu" + ], + [ + "ast", + "Asturian", + "Asturianu" + ], + [ + "ckb", + "Sorani (Kurdish)", + "سۆرانی" + ], + [ + "cnr", + "Montenegrin", + "crnogorski" + ], + [ + "jbo", + "Lojban", + "la .lojban." + ], + [ + "kab", + "Kabyle", + "Taqbaylit" + ], + [ + "kmr", + "Kurmanji (Kurdish)", + "Kurmancî" + ], + [ + "ldn", + "Láadan", + "Láadan" + ], + [ + "lfn", + "Lingua Franca Nova", + "lingua franca nova" + ], + [ + "sco", + "Scots", + "Scots" + ], + [ + "sma", + "Southern Sami", + "Åarjelsaemien Gïele" + ], + [ + "smj", + "Lule Sami", + "Julevsámegiella" + ], + [ + "szl", + "Silesian", + "ślůnsko godka" + ], + [ + "tok", + "Toki Pona", + "toki pona" + ], + [ + "zba", + "Balaibalan", + "باليبلن" + ], + [ + "zgh", + "Standard Moroccan Tamazight", + "ⵜⴰⵎⴰⵣⵉⵖⵜ" + ] + ], + }; + + const json = JSON.stringify(state); + if (window.location.pathname !== '/prepare.html') document.getElementById('initial-state').textContent = json; + localStorage.setItem("initial_state", json); + if (window.location.pathname === '/prepare.html') window.location.href = '/'; +} \ No newline at end of file