diff --git a/.dockerignore b/.dockerignore
deleted file mode 100644
index 3de57a360..000000000
--- a/.dockerignore
+++ /dev/null
@@ -1,12 +0,0 @@
-node_modules/
-dist/
-logs/
-.DS_Store
-.git/
-config/local.json
-pleroma-backend/
-test/e2e/reports/
-test/e2e-playwright/test-results/
-test/e2e-playwright/playwright-report/
-__screenshots__/
-
diff --git a/.forgejo/issue_template/bug.yaml b/.forgejo/issue_template/bug.yaml
deleted file mode 100644
index 082ee496e..000000000
--- a/.forgejo/issue_template/bug.yaml
+++ /dev/null
@@ -1,87 +0,0 @@
-name: 'Bug report'
-about: 'Bug report for Pleroma FE'
-labels:
- - Bug
-body:
-- type: input
- id: env-browser
- attributes:
- label: Browser and OS
- description: What browser are you using, including version, and what OS are you running?
- placeholder: Firefox 140, Arch Linux
- validations:
- required: true
-- type: input
- id: env-instance
- attributes:
- label: Instance URL
- validations:
- required: false
-- type: input
- id: env-backend
- attributes:
- label: Backend version information
- description: Backend version being used. (See Settings->Show advanced->Developer)
- placeholder: Pleroma BE 2.10
- validations:
- required: true
-- type: input
- id: env-frontend
- attributes:
- label: Frontend version information
- description: Frontend version being used. (See Settings->Show advanced->Developer)
- placeholder: Pleroma FE 2.10
- validations:
- required: true
-- type: input
- id: env-extensions
- attributes:
- label: Browser extensions
- description: List of browser extensions you are using, like uBlock, rikaichamp etc. If none leave empty.
- validations:
- required: false
-- type: input
- id: env-modifications
- attributes:
- label: Known instance/user customizations
- description: Whether you are using a Pleroma FE fork, any mods mods or instance level styles among others.
- validations:
- required: false
-- type: textarea
- id: bug-text
- attributes:
- label: Bug description
- description: A short description of the bug. Images can be helpful.
- validations:
- required: true
-- type: textarea
- id: bug-reproducer
- attributes:
- label: Reproduction steps
- description: Ordered list of reproduction steps needed to make the bug happen. If you don't have reproduction steps, leave this empty.
- placeholder: |
- 1. Log in with a fresh browser session
- 2. Open timeline X
- 3. Click on button Y
- 4. Z broke
- validations:
- required: false
-- type: textarea
- id: bug-seriousness
- attributes:
- label: Bug seriousness
- value: |
- * How annoying it is:
- * How often does it happen:
- * How many people does it affect:
- * Is there a workaround for it:
-- type: checkboxes
- id: duplicate-issues
- attributes:
- label: Duplicate issues
- hide_label: true
- description: Before submitting this issue, search for same or similar issues on the [Pleroma FE bug tracker](https://git.pleroma.social/pleroma/pleroma-fe/issues).
- options:
- - label: I've searched for same or similar issues before submitting this issue.
- required: true
- visible: [form]
diff --git a/.forgejo/issue_template/suggestion.yaml b/.forgejo/issue_template/suggestion.yaml
deleted file mode 100644
index c1531d8e3..000000000
--- a/.forgejo/issue_template/suggestion.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: 'Feature request / Suggestion / Improvement'
-about: 'Feature requests, suggestions and improvements for Pleroma FE'
-labels:
- - Feature Request / Enhancement
-body:
-- type: textarea
- id: issue-text
- attributes:
- label: Proposal
- placeholder: Make groups happen!
- validations:
- required: true
-- type: checkboxes
- id: duplicate-issues
- attributes:
- label: Duplicate issues
- hide_label: true
- description: Before submitting this issue, search for same or similar requests on the [Pleroma FE bug tracker](https://git.pleroma.social/pleroma/pleroma-fe/issues).
- options:
- - label: I've searched for same or similar requests before submitting this issue.
- required: true
- visible: [form]
diff --git a/.forgejo/pull_request_template.md b/.forgejo/pull_request_template.md
deleted file mode 100644
index d2d7689bd..000000000
--- a/.forgejo/pull_request_template.md
+++ /dev/null
@@ -1,12 +0,0 @@
-### Checklist
-- [ ] Adding a changelog: In the `changelog.d` directory, create a file named `.`.
-
-
diff --git a/.gitignore b/.gitignore
index c4a96ee1e..01ffda9a8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,11 +4,8 @@ dist/
npm-debug.log
test/unit/coverage
test/e2e/reports
-test/e2e-playwright/test-results
-test/e2e-playwright/playwright-report
selenium-debug.log
.idea/
-.gitlab-ci-local/
config/local.json
src/assets/emoji.json
logs/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e2946b162..99c85dd36 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,7 +1,7 @@
# This file is a template, and might need editing before it works on your project.
# Official framework image. Look for the different tagged releases at:
# https://hub.docker.com/r/library/node/tags/
-image: node:20
+image: node:18
stages:
- check-changelog
@@ -34,23 +34,12 @@ check-changelog:
- apk add git
- sh ./tools/check-changelog
-lint-eslint:
+lint:
stage: lint
script:
- yarn
- - yarn ci-eslint
-
-lint-biome:
- stage: lint
- script:
- - yarn
- - yarn ci-biome
-
-lint-stylelint:
- stage: lint
- script:
- - yarn
- - yarn ci-stylelint
+ - yarn lint
+ - yarn stylelint
test:
stage: test
@@ -71,135 +60,6 @@ test:
- test/**/__screenshots__
when: on_failure
-e2e-pleroma:
- stage: test
- image: mcr.microsoft.com/playwright:v1.61.0-jammy
- services:
- - name: postgres:15-alpine
- alias: db
- - name: $PLEROMA_IMAGE
- alias: pleroma
- entrypoint: ["/bin/ash", "-c"]
- command:
- - |
- set -eu
-
- SEED_SENTINEL_PATH=/var/lib/pleroma/.e2e_seeded
- CONFIG_OVERRIDE_PATH=/var/lib/pleroma/config.exs
-
- echo '-- Waiting for database...'
- while ! pg_isready -U ${DB_USER:-pleroma} -d postgres://${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-pleroma} -t 1; do
- sleep 1s
- done
-
- echo '-- Writing E2E config overrides...'
- cat > $CONFIG_OVERRIDE_PATH </dev/null; then
- kill -TERM $PLEROMA_PID
- wait $PLEROMA_PID || true
- fi
- }
-
- trap cleanup INT TERM
-
- echo '-- Waiting for API...'
- api_ok=false
- for _i in $(seq 1 120); do
- if wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null 2>&1; then
- api_ok=true
- break
- fi
- sleep 1s
- done
-
- if [ $api_ok != true ]; then
- echo 'Timed out waiting for Pleroma API to become available'
- exit 1
- fi
-
- if [ ! -f $SEED_SENTINEL_PATH ]; then
- if [ -n ${E2E_ADMIN_USERNAME:-} ] && [ -n ${E2E_ADMIN_PASSWORD:-} ] && [ -n ${E2E_ADMIN_EMAIL:-} ]; then
- echo '-- Seeding admin user' $E2E_ADMIN_USERNAME '...'
- if ! /opt/pleroma/bin/pleroma_ctl user new $E2E_ADMIN_USERNAME $E2E_ADMIN_EMAIL --admin --password $E2E_ADMIN_PASSWORD -y; then
- echo '-- User already exists or creation failed, ensuring admin + confirmed...'
- /opt/pleroma/bin/pleroma_ctl user set $E2E_ADMIN_USERNAME --admin --confirmed
- fi
- else
- echo '-- Skipping admin seeding (missing E2E_ADMIN_* env)'
- fi
-
- touch $SEED_SENTINEL_PATH
- fi
-
- wait $PLEROMA_PID
- tags:
- - amd64
- - himem
- variables:
- PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
- FF_NETWORK_PER_BUILD: "true"
- PLEROMA_IMAGE: git.pleroma.social/pleroma/pleroma:stable
- POSTGRES_USER: pleroma
- POSTGRES_PASSWORD: pleroma
- POSTGRES_DB: pleroma
- DB_USER: pleroma
- DB_PASS: pleroma
- DB_NAME: pleroma
- DB_HOST: db
- DB_PORT: 5432
- DOMAIN: localhost
- INSTANCE_NAME: Pleroma E2E
- E2E_ADMIN_USERNAME: admin
- E2E_ADMIN_PASSWORD: adminadmin
- E2E_ADMIN_EMAIL: admin@example.com
- ADMIN_EMAIL: $E2E_ADMIN_EMAIL
- NOTIFY_EMAIL: $E2E_ADMIN_EMAIL
- VITE_PROXY_TARGET: http://pleroma:4000
- VITE_PROXY_ORIGIN: http://localhost:4000
- E2E_BASE_URL: http://localhost:8099
- script:
- - npm install -g yarn@1.22.22
- - yarn --frozen-lockfile
- - |
- echo "-- Waiting for Pleroma API..."
- api_ok="false"
- for _i in $(seq 1 120); do
- if wget -qO- http://pleroma:4000/api/v1/instance >/dev/null 2>&1; then
- api_ok="true"
- break
- fi
- sleep 1s
- done
- if [ "$api_ok" != "true" ]; then
- echo "Timed out waiting for Pleroma API to become available"
- exit 1
- fi
- - yarn e2e:pw
- artifacts:
- when: on_failure
- paths:
- - test/e2e-playwright/test-results
- - test/e2e-playwright/playwright-report
-
build:
stage: build
tags:
diff --git a/.gitlab/merge_request_templates/Release.md b/.gitlab/merge_request_templates/Release.md
deleted file mode 100644
index d02e14a73..000000000
--- a/.gitlab/merge_request_templates/Release.md
+++ /dev/null
@@ -1,8 +0,0 @@
-### Release checklist
-* [ ] Bump version in `package.json`
-* [ ] Compile a changelog with the `tools/collect-changelog` script
-* [ ] Create an MR with an announcement to pleroma.social
-#### post-merge
-* [ ] Tag the release on the merge commit
-* [ ] Make the tag into a Gitlab Releaseā¢
-* [ ] Merge `master` into `develop` (in case the fixes are already in develop, use `git merge -s ours --no-commit` and manually merge the changelogs)
diff --git a/.node-version b/.node-version
index 5bd681170..08b7109d0 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-20.19.0
+18.20.8
diff --git a/.stylelintrc.json b/.stylelintrc.json
index afdfd5f5b..c91107595 100644
--- a/.stylelintrc.json
+++ b/.stylelintrc.json
@@ -12,8 +12,6 @@
"custom-property-pattern": null,
"keyframes-name-pattern": null,
"scss/operator-no-newline-after": null,
- "declaration-property-value-no-unknown": true,
- "scss/declaration-property-value-no-unknown": true,
"declaration-block-no-redundant-longhand-properties": [
true,
{
diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml
deleted file mode 100644
index af0bb98e3..000000000
--- a/.woodpecker/build.yaml
+++ /dev/null
@@ -1,43 +0,0 @@
-when:
- - event: pull_request
- evaluate: 'CI_COMMIT_SOURCE_BRANCH != "weblate" && not(CI_COMMIT_SOURCE_BRANCH startsWith "renovate/")'
- - event: push
- branch: ${CI_REPO_DEFAULT_BRANCH}
- - event: manual
- branch: ${CI_REPO_DEFAULT_BRANCH}
-
-depends_on:
- - test
- - test-e2e
-
-labels:
- platform: linux/amd64
- memory: 'high'
-
-steps:
- build:
- image: docker.io/node:20-alpine
- commands:
- - apk add --no-cache zip git
- - yarn --frozen-lockfile
- - yarn build
- - if [ "${CI_PIPELINE_EVENT}" = "push" ] || [ "${CI_PIPELINE_EVENT}" = "manual" ]; then zip -9qr ${CI_REPO_DEFAULT_BRANCH}.zip dist/; fi
-
- upload-artifacts:
- image: docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0
- when:
- - event: push
- branch: ${CI_REPO_DEFAULT_BRANCH}
- - event: manual
- branch: ${CI_REPO_DEFAULT_BRANCH}
- settings:
- user:
- from_secret: pleroma-ci-user
- password:
- from_secret: pleroma-ci-password
- update: true
- owner: 'pleroma'
- package_name: 'pleroma-fe-builds'
- package_version: ${CI_REPO_DEFAULT_BRANCH}
- file_source: ./${CI_REPO_DEFAULT_BRANCH}.zip
- file_name: latest.zip
diff --git a/.woodpecker/changelog.yaml b/.woodpecker/changelog.yaml
deleted file mode 100644
index b6c35afbb..000000000
--- a/.woodpecker/changelog.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-when:
- - event: pull_request
- evaluate: 'CI_COMMIT_SOURCE_BRANCH != "weblate" && not(CI_COMMIT_SOURCE_BRANCH startsWith "renovate/")'
-
-steps:
- check-changelog:
- image: docker.io/alpine:3.23
- commands:
- - apk add --no-cache git
- - sh ./tools/check-changelog
diff --git a/.woodpecker/lint.yaml b/.woodpecker/lint.yaml
deleted file mode 100644
index 257887338..000000000
--- a/.woodpecker/lint.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-when:
- - event: pull_request
- evaluate: 'CI_COMMIT_SOURCE_BRANCH != "weblate" && not(CI_COMMIT_SOURCE_BRANCH startsWith "renovate/")'
- - event: push
- branch: ${CI_REPO_DEFAULT_BRANCH}
- - event: manual
- branch: ${CI_REPO_DEFAULT_BRANCH}
-
-steps:
- install-depends:
- image: &node-image
- docker.io/node:20-alpine
- commands:
- - yarn --frozen-lockfile
-
- eslint:
- image: *node-image
- depends_on: install-depends
- commands:
- - yarn ci-eslint
-
- biome:
- image: *node-image
- depends_on: install-depends
- commands:
- - yarn ci-biome
-
- stylelint:
- image: *node-image
- depends_on: install-depends
- commands:
- - yarn ci-stylelint
diff --git a/.woodpecker/test-e2e.yaml b/.woodpecker/test-e2e.yaml
deleted file mode 100644
index 2a7d1511d..000000000
--- a/.woodpecker/test-e2e.yaml
+++ /dev/null
@@ -1,101 +0,0 @@
-when:
- - event: pull_request
- evaluate: 'CI_COMMIT_SOURCE_BRANCH != "weblate" && not(CI_COMMIT_SOURCE_BRANCH startsWith "renovate/")'
- - event: push
- branch: ${CI_REPO_DEFAULT_BRANCH}
- - event: manual
- branch: ${CI_REPO_DEFAULT_BRANCH}
-
-labels:
- platform: linux/amd64
- memory: 'high'
-
-variables:
- artifacts_uploader_settings: &artifacts_uploader_settings
- user:
- from_secret: pleroma-ci-user
- password:
- from_secret: pleroma-ci-password
- owner: 'pleroma'
- package_name: 'pleroma-fe-test-artifacts'
- script_file_entrypoint: &script_file_entrypoint
- - /bin/sh
- - -c
- - 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
-
-steps:
- test:
- image: mcr.microsoft.com/playwright:v1.61.0-jammy
- entrypoint: *script_file_entrypoint
- environment:
- APT_CACHE_DIR: apt-cache
- DEBIAN_FRONTEND: noninteractive
- E2E_BASE_URL: http://localhost:8099
- FF_NETWORK_PER_BUILD: "true"
- PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
- VITE_PROXY_ORIGIN: "http://pleroma:4000"
- VITE_PROXY_TARGET: "http://pleroma:4000"
- commands:
- - |
- if [ "${CI_PIPELINE_EVENT}" != "pull_request" ]; then
- mkdir -pv $APT_CACHE_DIR && apt-get -qq update
- apt-get install -y zip
- fi
- - npm install -g yarn@1.22.22
- - yarn --frozen-lockfile
- - |
- echo "-- Waiting for Pleroma API..."
- api_ok="false"
- for _i in $(seq 1 120); do
- if wget -qO- http://pleroma:4000/api/v1/instance >/dev/null 2>&1; then
- api_ok="true"
- break
- fi
- sleep 1s
- done
- if [ "$api_ok" != "true" ]; then
- echo "Timed out waiting for Pleroma API to become available"
- exit 1
- fi
- - |
- if ! yarn e2e:pw; then
- [ "${CI_PIPELINE_EVENT}" = "pull_request" ] || zip -9qr ${CI_COMMIT_SHA:0:8}-e2e.zip ./test/e2e-playwright/test-results ./test/e2e-playwright/playwright-report
- exit 1
- fi
-
- upload-artifacts:
- image: docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0
- when:
- - event: push
- branch: ${CI_REPO_DEFAULT_BRANCH}
- status: [failure]
- - event: manual
- branch: ${CI_REPO_DEFAULT_BRANCH}
- status: [failure]
- settings:
- <<: *artifacts_uploader_settings
- package_version: ${CI_REPO_DEFAULT_BRANCH}-${CI_COMMIT_SHA:0:8}
- file_source: ./${CI_COMMIT_SHA:0:8}-e2e.zip
- file_name: ${CI_COMMIT_SHA:0:8}-e2e.zip
-
-services:
- postgres:
- image: docker.io/postgres:13-alpine
- environment:
- POSTGRES_DB: pleroma_test
- POSTGRES_USER: postgres
- POSTGRES_PASSWORD: postgres
-
- pleroma:
- image: git.pleroma.social/pleroma/pleroma:stable-e2e
- environment:
- ADMIN_EMAIL: "admin@example.com"
- NOTIFY_EMAIL: "admin@example.com"
- DB_USER: postgres
- DB_PASS: postgres
- DB_NAME: pleroma_test
- DB_HOST: postgres
- INSTANCE_NAME: Pleroma E2E
- E2E_ADMIN_USERNAME: admin
- E2E_ADMIN_PASSWORD: adminadmin
- E2E_ADMIN_EMAIL: "admin@example.com"
diff --git a/.woodpecker/test.yaml b/.woodpecker/test.yaml
deleted file mode 100644
index 89f6f2f93..000000000
--- a/.woodpecker/test.yaml
+++ /dev/null
@@ -1,61 +0,0 @@
-when:
- - event: pull_request
- evaluate: 'CI_COMMIT_SOURCE_BRANCH != "weblate" && not(CI_COMMIT_SOURCE_BRANCH startsWith "renovate/")'
- - event: push
- branch: ${CI_REPO_DEFAULT_BRANCH}
- - event: manual
- branch: ${CI_REPO_DEFAULT_BRANCH}
-
-labels:
- platform: linux/amd64
- memory: 'high'
-
-variables:
- artifacts_uploader_settings: &artifacts_uploader_settings
- user:
- from_secret: pleroma-ci-user
- password:
- from_secret: pleroma-ci-password
- owner: 'pleroma'
- package_name: 'pleroma-fe-test-artifacts'
- script_file_entrypoint: &script_file_entrypoint
- - /bin/sh
- - -c
- - 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
-
-steps:
- test:
- image: mcr.microsoft.com/playwright:v1.61.0-jammy
- environment:
- APT_CACHE_DIR: apt-cache
- DEBIAN_FRONTEND: noninteractive
- FF_NETWORK_PER_BUILD: "true"
- PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
- entrypoint: *script_file_entrypoint
- commands:
- - |
- if [ "${CI_PIPELINE_EVENT}" != "pull_request" ]; then
- mkdir -pv $APT_CACHE_DIR && apt-get -qq update
- apt-get -y install zip
- fi
- - yarn --frozen-lockfile
- - |
- if ! yarn unit-ci; then
- [ "${CI_PIPELINE_EVENT}" = "pull_request" ] || zip -9qr ${CI_COMMIT_SHA:0:8}-screenshots.zip $(find . -type d -name __screenshots__)
- exit 1
- fi
-
- upload-artifacts:
- image: docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0
- when:
- - event: push
- branch: ${CI_REPO_DEFAULT_BRANCH}
- status: [failure]
- - event: manual
- branch: ${CI_REPO_DEFAULT_BRANCH}
- status: [failure]
- settings:
- <<: *artifacts_uploader_settings
- package_version: ${CI_REPO_DEFAULT_BRANCH}-${CI_COMMIT_SHA:0:8}
- file_source: ./${CI_COMMIT_SHA:0:8}-screenshots.zip
- file_name: ${CI_COMMIT_SHA:0:8}-screenshots.zip
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1eb5a9cb4..c2f0e7d17 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,76 +2,6 @@
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
-
-## 2.10.1
-### Fixed
-- fixed being unable to set actor type from profile page
-- fixed error when clicking mute menu itself (instead of submenu items)
-- fixed mute -> domain status submenu not working
-
-### Internal
-- Add playwright E2E-tests with an optional docker-based backend
-
-## 2.10.0
-### Changed
-- Temporary changes modal now shows actual countdown instead of fixed timeout
-- Disabled elements are more disabled now
-- Rearranged and split settings to make more sense and be less of a wall of text
-- On mobile settings now take up full width and presented in navigation style
-improved styles for settings
-
-### Added
-- Most of the remaining AdminFE tabs were added into Admin Dashboard
-- It's now possible to customize PWA Manfiest from PleromaFE
-- Make every configuration option default-overridable by instance admins
-
-### Fixed
-- Fixed settings not appearing if user never touched "show advanced" toggle
-- Fix display of the broken/deleted/banned users
-- Fixed incorrect emoji display in post interaction lists
-- Fixed list title not being saved when editing
-- Fixed poll notifications not being expandable
-
-
-## 2.9.3
-### Fixed
-- Being unable to update profile
-
-## 2.9.2
-### Changed
-- BREAKING: due to some internal technical changes logging into AdminFE through PleromaFE is no longer possible
-- User card/profile got an overhaul
-- Profile editing overhaul
-- Visually combined subject and content fields in post form
-- Moved post form's emoji button into input field
-- Minor visual changes and fixes
-- Clicking on fav/rt/emoji notifications' contents expands/collapses it
-- Reduced time taken processing theme by half
-- Splash screen only appears if loading takes more than 2 seconds
-
-### Added
-- Mutes received an update, adding support for regex, muting based on username and expiration time.
-- Mutes are now synchronized across sessions
-- Support for expiring mutes and blocks (if available)
-- Clicking on emoji shows bigger version of it alongside with its shortcode
- - Admins also are able to copy it into a local pack
-- Added support for Akkoma and IceShrimp.NET backends
-- Compatibility with stricter CSP (Akkoma backend)
-- Added a way to upload new packs from a URL or ZIP file via the Admin Dashboard
-- Unify show/hide content buttons
-- Add support for detachable scrollTop button
-- Option to left-align user bio
-- Cache assets and emojis with service worker
-- Indicate currently active V3 theme as a body element class
-- Add arithmetic blend ISS function
-
-### Fixed
-- Display counter for status action buttons when they are in the menu
-- Fix bookmark button alignment in the extra actions menu
-- Instance favicons are no longer stretched
-- A lot more scalable UI fixes
- - Emoji picker now should work fine when emoji size is increased
-
## 2.8.0
### Changed
- BREAKING: static/img/nsfw.2958239.png is now static/img/nsfw.DepQPhG0.png, which may affect people who specify exactly this path as the cover image
@@ -104,8 +34,8 @@ This does not guarantee that browsers will or will not work.
- Support displaying time in absolute format
- Add draft management system
- Compress most kinds of images on upload.
-- Added option to always convert images to JPEG format instead of using WebP when compressing images.
-- Added configurable image compression option in general settings, allowing users to control whether images are compressed before upload.
+- Added option to always convert images to JPEG format instead of using WebP when compressing images.
+- Added configurable image compression option in general settings, allowing users to control whether images are compressed before upload.
- Inform users that Smithereen public polls are public
- Splash screen + loading indicator to make process of identifying initialization issues and load performance
- UI for making v3 themes and palettes, support for bundling v3 themes
diff --git a/README.md b/README.md
index 16d32dcd2..6a37195d5 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
# For Translators
-To translate Pleroma-FE, use our weblate server: https://translate.pleroma.social/. If you need to add your language it should be added as a json file in [src/i18n/](https://git.pleroma.social/pleroma/pleroma-fe/src/src/i18n/) folder and added in a list within [src/i18n/languages.js](https://git.pleroma.social/pleroma/pleroma-fe/src/src/i18n/languages.js).
+To translate Pleroma-FE, use our weblate server: https://translate.pleroma.social/. If you need to add your language it should be added as a json file in [src/i18n/](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/) folder and added in a list within [src/i18n/languages.js](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/languages.js).
Pleroma-FE will set your language by your browser locale, but you can change language in settings.
@@ -32,10 +32,10 @@ yarn unit
# For Contributors:
-You can create file `/config/local.json` (see [example](https://git.pleroma.social/pleroma/pleroma-fe/src/config/local.example.json)) to enable some convenience dev options:
+You can create file `/config/local.json` (see [example](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/config/local.example.json)) to enable some convenience dev options:
* `target`: makes local dev server redirect to some existing instance's BE instead of local BE, useful for testing things in near-production environment and searching for real-life use-cases.
-* `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/pleroma/frontend_configurations`. Only works in dev mode.
+* `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/statusnet/config.json`. Only works in dev mode.
FE Build process also leaves current commit hash in global variable `___pleromafe_commit_hash` so that you can easily see which pleroma-fe commit instance is running, also helps pinpointing which commit was used when FE was bundled into BE.
diff --git a/biome.json b/biome.json
deleted file mode 100644
index 5ef6f79ee..000000000
--- a/biome.json
+++ /dev/null
@@ -1,150 +0,0 @@
-{
- "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
- "vcs": {
- "enabled": true,
- "clientKind": "git",
- "useIgnoreFile": true
- },
- "files": {
- "includes": ["**", "!!**/dist", "!!tools/emojis.json"]
- },
- "formatter": {
- "enabled": true,
- "indentStyle": "space"
- },
- "linter": {
- "enabled": true,
- "domains": {
- "vue": "recommended"
- },
- "rules": {
- "recommended": false,
- "complexity": {
- "noAdjacentSpacesInRegex": "error",
- "noExtraBooleanCast": "error",
- "noUselessCatch": "error",
- "noUselessEscapeInRegex": "error"
- },
- "correctness": {
- "noConstAssign": "error",
- "noConstantCondition": "error",
- "noEmptyCharacterClassInRegex": "error",
- "noEmptyPattern": "error",
- "noGlobalObjectCalls": "error",
- "noInvalidBuiltinInstantiation": "error",
- "noInvalidConstructorSuper": "error",
- "noNonoctalDecimalEscape": "error",
- "noPrecisionLoss": "error",
- "noSelfAssign": "error",
- "noSetterReturn": "error",
- "noSwitchDeclarations": "error",
- "noUndeclaredVariables": "error",
- "noUnreachable": "error",
- "noUnreachableSuper": "error",
- "noUnsafeFinally": "error",
- "noUnsafeOptionalChaining": "error",
- "noUnusedLabels": "error",
- "noUnusedPrivateClassMembers": "error",
- "noUnusedVariables": "error",
- "noUnusedImports": "error",
- "useIsNan": "error",
- "useValidForDirection": "error",
- "useValidTypeof": "error",
- "useYield": "error"
- },
- "suspicious": {
- "noAsyncPromiseExecutor": "error",
- "noCatchAssign": "error",
- "noClassAssign": "error",
- "noCompareNegZero": "error",
- "noConstantBinaryExpressions": "error",
- "noControlCharactersInRegex": "error",
- "noDebugger": "error",
- "noDuplicateCase": "error",
- "noDuplicateClassMembers": "error",
- "noDuplicateElseIf": "error",
- "noDuplicateObjectKeys": "error",
- "noDuplicateParameters": "error",
- "noEmptyBlockStatements": "error",
- "noFallthroughSwitchClause": "error",
- "noFunctionAssign": "error",
- "noGlobalAssign": "error",
- "noImportAssign": "error",
- "noIrregularWhitespace": "error",
- "noMisleadingCharacterClass": "error",
- "noPrototypeBuiltins": "error",
- "noRedeclare": "error",
- "noShadowRestrictedNames": "error",
- "noSparseArray": "error",
- "noUnsafeNegation": "error",
- "noUselessRegexBackrefs": "error",
- "noWith": "error",
- "useGetterReturn": "error"
- }
- }
- },
- "javascript": {
- "formatter": {
- "quoteStyle": "single",
- "semicolons": "asNeeded"
- },
- "globals": []
- },
- "overrides": [
- {
- "includes": ["**/*.spec.js", "test/fixtures/*.js"],
- "javascript": {
- "globals": [
- "vi",
- "describe",
- "it",
- "test",
- "expect",
- "before",
- "beforeEach",
- "after",
- "afterEach"
- ]
- }
- },
- {
- "includes": ["**/*.vue"],
- "linter": {
- "rules": {
- "style": {
- "useConst": "off",
- "useImportType": "off"
- },
- "correctness": {
- "noUnusedVariables": "off",
- "noUnusedImports": "off"
- }
- }
- }
- }
- ],
- "assist": {
- "enabled": true,
- "actions": {
- "source": {
- "organizeImports": {
- "level": "on",
- "options": {
- "groups": [
- [":NODE:", ":PACKAGE:", "!src/**", "!@fortawesome/**"],
- ":BLANK_LINE:",
- [":PATH:", "src/components/**"],
- ":BLANK_LINE:",
- [":PATH:", "src/stores/**"],
- ":BLANK_LINE:",
- [":PATH:", "src/**", "src/stores/**", "src/components/**"],
- ":BLANK_LINE:",
- "@fortawesome/fontawesome-svg-core",
- "@fortawesome/*"
- ]
- }
- }
- }
- }
- }
-}
diff --git a/build/check-versions.mjs b/build/check-versions.mjs
index c22004b00..73c1eeb15 100644
--- a/build/check-versions.mjs
+++ b/build/check-versions.mjs
@@ -1,5 +1,5 @@
-import chalk from 'chalk'
import semver from 'semver'
+import chalk from 'chalk'
import packageConfig from '../package.json' with { type: 'json' }
@@ -7,8 +7,8 @@ var versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
- versionRequirement: packageConfig.engines.node,
- },
+ versionRequirement: packageConfig.engines.node
+ }
]
export default function () {
@@ -16,26 +16,20 @@ export default function () {
for (let i = 0; i < versionRequirements.length; i++) {
const mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
- warnings.push(
- mod.name +
- ': ' +
- chalk.red(mod.currentVersion) +
- ' should be ' +
- chalk.green(mod.versionRequirement),
+ warnings.push(mod.name + ': ' +
+ chalk.red(mod.currentVersion) + ' should be ' +
+ chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
- console.warn(
- chalk.yellow(
- '\nTo use this template, you must update following to modules:\n',
- ),
- )
+ console.warn(chalk.yellow('\nTo use this template, you must update following to modules:\n'))
for (let i = 0; i < warnings.length; i++) {
const warning = warnings[i]
console.warn(' ' + warning)
}
+ console.warn()
process.exit(1)
}
}
diff --git a/build/commit_hash.js b/build/commit_hash.js
index c60355804..c104af5d9 100644
--- a/build/commit_hash.js
+++ b/build/commit_hash.js
@@ -1,8 +1,8 @@
import childProcess from 'child_process'
-export const getCommitHash = () => {
- const subst = '$Format:%h$'
- if (!subst.match(/Format:/)) {
+export const getCommitHash = (() => {
+ const subst = "$Format:%h$"
+ if(!subst.match(/Format:/)) {
return subst
} else {
try {
@@ -15,4 +15,4 @@ export const getCommitHash = () => {
return 'UNKNOWN'
}
}
-}
+})
diff --git a/build/copy_plugin.js b/build/copy_plugin.js
index 4f020f359..a783fe7ff 100644
--- a/build/copy_plugin.js
+++ b/build/copy_plugin.js
@@ -1,8 +1,8 @@
-import { cp } from 'node:fs/promises'
-import { resolve } from 'node:path'
import serveStatic from 'serve-static'
+import { resolve } from 'node:path'
+import { cp } from 'node:fs/promises'
-const getPrefix = (s) => {
+const getPrefix = s => {
const padEnd = s.endsWith('/') ? s : s + '/'
return padEnd.startsWith('/') ? padEnd : '/' + padEnd
}
@@ -13,31 +13,28 @@ const copyPlugin = ({ inUrl, inFs }) => {
let copyTarget
const handler = serveStatic(inFs)
- return [
- {
- name: 'copy-plugin-serve',
- apply: 'serve',
- configureServer(server) {
- server.middlewares.use(prefix, handler)
- },
+ return [{
+ name: 'copy-plugin-serve',
+ apply: 'serve',
+ configureServer (server) {
+ server.middlewares.use(prefix, handler)
+ }
+ }, {
+ name: 'copy-plugin-build',
+ apply: 'build',
+ configResolved (config) {
+ copyTarget = resolve(config.root, config.build.outDir, subdir)
},
- {
- name: 'copy-plugin-build',
- apply: 'build',
- configResolved(config) {
- copyTarget = resolve(config.root, config.build.outDir, subdir)
- },
- closeBundle: {
- order: 'post',
- sequential: true,
- async handler() {
- console.info(`Copying '${inFs}' to ${copyTarget}...`)
- await cp(inFs, copyTarget, { recursive: true })
- console.info('Done.')
- },
- },
- },
- ]
+ closeBundle: {
+ order: 'post',
+ sequential: true,
+ async handler () {
+ console.log(`Copying '${inFs}' to ${copyTarget}...`)
+ await cp(inFs, copyTarget, { recursive: true })
+ console.log('Done.')
+ }
+ }
+ }]
}
export default copyPlugin
diff --git a/build/emojis_plugin.js b/build/emojis_plugin.js
index 9872f5331..aed52066d 100644
--- a/build/emojis_plugin.js
+++ b/build/emojis_plugin.js
@@ -1,23 +1,21 @@
-import { access } from 'node:fs/promises'
import { resolve } from 'node:path'
-
-import { languages } from '../src/i18n/languages.js'
+import { access } from 'node:fs/promises'
+import { languages, langCodeToCldrName } from '../src/i18n/languages.js'
const annotationsImportPrefix = '@kazvmoe-infra/unicode-emoji-json/annotations/'
const specialAnnotationsLocale = {
- ja_easy: 'ja',
+ ja_easy: 'ja'
}
-const internalToAnnotationsLocale = (internal) =>
- specialAnnotationsLocale[internal] || internal
+const internalToAnnotationsLocale = (internal) => specialAnnotationsLocale[internal] || internal
// This gets all the annotations that are accessible (whose language
// can be chosen in the settings). Data for other languages are
// discarded because there is no way for it to be fetched.
const getAllAccessibleAnnotations = async (projectRoot) => {
- const imports = (
- await Promise.all(
- languages.map(async (lang) => {
+ const imports = (await Promise.all(
+ languages
+ .map(async lang => {
const destLang = internalToAnnotationsLocale(lang)
const importModule = `${annotationsImportPrefix}${destLang}.json`
const importFile = resolve(projectRoot, 'node_modules', importModule)
@@ -25,18 +23,11 @@ const getAllAccessibleAnnotations = async (projectRoot) => {
await access(importFile)
return `'${lang}': () => import('${importModule}')`
} catch (e) {
- if (e.message.match(/ENOENT/)) {
- console.warn(`Missing emoji annotations locale: ${destLang}`)
- } else {
- console.error('test', e.message)
- }
return
}
- }),
- )
- )
- .filter((k) => k)
- .join(',\n')
+ })))
+ .filter(k => k)
+ .join(',\n')
return `
export const annotationsLoader = {
@@ -52,21 +43,21 @@ const emojisPlugin = () => {
let projectRoot
return {
name: 'emojis-plugin',
- configResolved(conf) {
+ configResolved (conf) {
projectRoot = conf.root
},
- resolveId(id) {
+ resolveId (id) {
if (id === emojiAnnotationsId) {
return emojiAnnotationsIdResolved
}
return null
},
- async load(id) {
+ async load (id) {
if (id === emojiAnnotationsIdResolved) {
return await getAllAccessibleAnnotations(projectRoot)
}
return null
- },
+ }
}
}
diff --git a/build/msw_plugin.js b/build/msw_plugin.js
index c4e9098c5..f544348fc 100644
--- a/build/msw_plugin.js
+++ b/build/msw_plugin.js
@@ -1,5 +1,5 @@
-import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
+import { readFile } from 'node:fs/promises'
const target = 'node_modules/msw/lib/mockServiceWorker.js'
@@ -8,10 +8,10 @@ const mswPlugin = () => {
return {
name: 'msw-plugin',
apply: 'serve',
- configResolved(conf) {
+ configResolved (conf) {
projectRoot = conf.root
},
- configureServer(server) {
+ configureServer (server) {
server.middlewares.use(async (req, res, next) => {
if (req.path === '/mockServiceWorker.js') {
const file = await readFile(resolve(projectRoot, target))
@@ -21,7 +21,7 @@ const mswPlugin = () => {
next()
}
})
- },
+ }
}
}
diff --git a/build/service_worker_messages.js b/build/service_worker_messages.js
index 0948aa919..c078e8563 100644
--- a/build/service_worker_messages.js
+++ b/build/service_worker_messages.js
@@ -1,12 +1,11 @@
+import { languages, langCodeToJsonName } from '../src/i18n/languages.js'
import { readFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
-import { langCodeToJsonName, languages } from '../src/i18n/languages.js'
-
const i18nDir = resolve(
dirname(dirname(fileURLToPath(import.meta.url))),
- 'src/i18n',
+ 'src/i18n'
)
export const i18nFiles = languages.reduce((acc, lang) => {
@@ -17,15 +16,13 @@ export const i18nFiles = languages.reduce((acc, lang) => {
}, {})
export const generateServiceWorkerMessages = async () => {
- const msgArray = await Promise.all(
- Object.entries(i18nFiles).map(async ([lang, file]) => {
- const fileContent = await readFile(file, 'utf-8')
- const msg = {
- notifications: JSON.parse(fileContent).notifications || {},
- }
- return [lang, msg]
- }),
- )
+ const msgArray = await Promise.all(Object.entries(i18nFiles).map(async ([lang, file]) => {
+ const fileContent = await readFile(file, 'utf-8')
+ const msg = {
+ notifications: JSON.parse(fileContent).notifications || {}
+ }
+ return [lang, msg]
+ }))
return msgArray.reduce((acc, [lang, msg]) => {
acc[lang] = msg
return acc
diff --git a/build/sw_plugin.js b/build/sw_plugin.js
index 2f0a4819d..a2c792b7d 100644
--- a/build/sw_plugin.js
+++ b/build/sw_plugin.js
@@ -1,12 +1,9 @@
-import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
-import { exactRegex } from '@rolldown/pluginutils'
+import { dirname, resolve } from 'node:path'
+import { readFile } from 'node:fs/promises'
import { build } from 'vite'
-
-import {
- generateServiceWorkerMessages,
- i18nFiles,
-} from './service_worker_messages.js'
+import * as esbuild from 'esbuild'
+import { generateServiceWorkerMessages, i18nFiles } from './service_worker_messages.js'
const getSWMessagesAsText = async () => {
const messages = await generateServiceWorkerMessages()
@@ -14,24 +11,123 @@ const getSWMessagesAsText = async () => {
}
const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)))
+const swEnvName = 'virtual:pleroma-fe/service_worker_env'
+const swEnvNameResolved = '\0' + swEnvName
+const getDevSwEnv = () => `self.serviceWorkerOption = { assets: [] };`
+const getProdSwEnv = ({ assets }) => `self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };`
+
+export const devSwPlugin = ({
+ swSrc,
+ swDest,
+ transformSW,
+ alias
+}) => {
+ const swFullSrc = resolve(projectRoot, swSrc)
+ const esbuildAlias = {}
+ Object.entries(alias).forEach(([source, dest]) => {
+ esbuildAlias[source] = dest.startsWith('/') ? projectRoot + dest : dest
+ })
+
+ return {
+ name: 'dev-sw-plugin',
+ apply: 'serve',
+ configResolved (conf) {
+ },
+ resolveId (id) {
+ const name = id.startsWith('/') ? id.slice(1) : id
+ if (name === swDest) {
+ return swFullSrc
+ } else if (name === swEnvName) {
+ return swEnvNameResolved
+ }
+ return null
+ },
+ async load (id) {
+ if (id === swFullSrc) {
+ return readFile(swFullSrc, 'utf-8')
+ } else if (id === swEnvNameResolved) {
+ return getDevSwEnv()
+ }
+ return null
+ },
+ /**
+ * vite does not bundle the service worker
+ * during dev, and firefox does not support ESM as service worker
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1360870
+ */
+ async transform (code, id) {
+ if (id === swFullSrc && transformSW) {
+ const res = await esbuild.build({
+ entryPoints: [swSrc],
+ bundle: true,
+ write: false,
+ outfile: 'sw-pleroma.js',
+ alias: esbuildAlias,
+ plugins: [{
+ name: 'vite-like-root-resolve',
+ setup (b) {
+ b.onResolve(
+ { filter: new RegExp(/^\//) },
+ args => ({
+ path: resolve(projectRoot, args.path.slice(1))
+ })
+ )
+ }
+ }, {
+ name: 'sw-messages',
+ setup (b) {
+ b.onResolve(
+ { filter: new RegExp('^' + swMessagesName + '$') },
+ args => ({
+ path: args.path,
+ namespace: 'sw-messages'
+ }))
+ b.onLoad(
+ { filter: /.*/, namespace: 'sw-messages' },
+ async () => ({
+ contents: await getSWMessagesAsText()
+ }))
+ }
+ }, {
+ name: 'sw-env',
+ setup (b) {
+ b.onResolve(
+ { filter: new RegExp('^' + swEnvName + '$') },
+ args => ({
+ path: args.path,
+ namespace: 'sw-env'
+ }))
+ b.onLoad(
+ { filter: /.*/, namespace: 'sw-env' },
+ () => ({
+ contents: getDevSwEnv()
+ }))
+ }
+ }]
+ })
+ const text = res.outputFiles[0].text
+ return text
+ }
+ }
+ }
+}
+
// Idea taken from
// https://github.com/vite-pwa/vite-plugin-pwa/blob/main/src/plugins/build.ts
// rollup does not support compiling to iife if we want to code-split;
// however, we must compile the service worker to iife because of browser support.
// Run another vite build just for the service worker targeting iife at
// the end of the build.
-export const buildSwPlugin = ({ swSrc, swDest }) => {
- const swFullSrc = resolve(projectRoot, swSrc)
- const swEnvName = 'virtual:pleroma-fe/service_worker_env'
- const swEnvNameResolved = '\0' + swEnvName
-
+export const buildSwPlugin = ({
+ swSrc,
+ swDest,
+}) => {
let config
-
return {
name: 'build-sw-plugin',
enforce: 'post',
- configResolved(resolvedConfig) {
- resolvedConfig
+ apply: 'build',
+ configResolved (resolvedConfig) {
config = {
define: resolvedConfig.define,
resolve: resolvedConfig.resolve,
@@ -39,89 +135,53 @@ export const buildSwPlugin = ({ swSrc, swDest }) => {
publicDir: false,
build: {
...resolvedConfig.build,
- emptyOutDir: false,
- rolldownOptions: {
- input: {
- main: swSrc,
- },
- context: 'self',
- output: {
- entryFileNames: swDest,
- codeSplitting: false,
- format: 'iife',
- },
+ lib: {
+ entry: swSrc,
+ formats: ['iife'],
+ name: 'sw_pleroma'
},
+ emptyOutDir: false,
+ rollupOptions: {
+ output: {
+ entryFileNames: swDest
+ }
+ }
},
- configFile: false,
+ configFile: false
}
},
generateBundle: {
order: 'post',
sequential: true,
- async handler(_, bundle) {
+ async handler (_, bundle) {
const assets = Object.keys(bundle)
- .filter((name) => !/\.map$/.test(name))
- .map((name) => '/' + name)
-
+ .filter(name => !/\.map$/.test(name))
+ .map(name => '/' + name)
config.plugins.push({
name: 'build-sw-env-plugin',
- mode: 'production',
- resolveId: {
- filter: { id: exactRegex(swEnvName) },
- handler: () => swEnvNameResolved,
- },
- load: {
- filter: { id: exactRegex(swEnvNameResolved) },
- handler() {
- return `self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };`
- },
+ resolveId (id) {
+ if (id === swEnvName) {
+ return swEnvNameResolved
+ }
+ return null
},
+ load (id) {
+ if (id === swEnvNameResolved) {
+ return getProdSwEnv({ assets })
+ }
+ return null
+ }
})
- },
- },
- resolveId: {
- filter: { id: new RegExp(swDest) },
- handler() {
- return swFullSrc
- },
- },
- load: {
- filter: { id: new RegExp(swFullSrc) },
- async handler() {
- config.plugins.push({
- name: 'dummy-sw-env',
- mode: 'development',
- resolveId: {
- filter: { id: exactRegex(swEnvName) },
- handler: () => swEnvNameResolved,
- },
- load: {
- filter: { id: exactRegex(swEnvNameResolved) },
- handler: () => 'self.serviceWorkerOption = { assets: [] }',
- },
- })
-
- try {
- const swBundle = await build(config)
- return swBundle.output[0]
- } catch (e) {
- console.error('Error building ServiceWorker:', e)
- }
- },
+ }
},
closeBundle: {
order: 'post',
sequential: true,
- async handler() {
- if (process.env.VITEST) return
- console.info('Building service worker for production')
- try {
- await build(config)
- } catch (e) {
- console.error('Error building ServiceWorker:', e)
- }
- },
- },
+ async handler () {
+ console.log('Building service worker for production')
+ await build(config)
+ }
+ }
}
}
@@ -131,9 +191,9 @@ const swMessagesNameResolved = '\0' + swMessagesName
export const swMessagesPlugin = () => {
return {
name: 'sw-messages-plugin',
- resolveId(id) {
+ resolveId (id) {
if (id === swMessagesName) {
- Object.values(i18nFiles).forEach((f) => {
+ Object.values(i18nFiles).forEach(f => {
this.addWatchFile(f)
})
return swMessagesNameResolved
@@ -141,11 +201,11 @@ export const swMessagesPlugin = () => {
return null
}
},
- async load(id) {
+ async load (id) {
if (id === swMessagesNameResolved) {
return await getSWMessagesAsText()
}
return null
- },
+ }
}
}
diff --git a/build/update-emoji.js b/build/update-emoji.js
index 4ff7e1de8..5d578ba61 100644
--- a/build/update-emoji.js
+++ b/build/update-emoji.js
@@ -1,21 +1,22 @@
-import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with {
- type: 'json',
-}
+
+import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with { type: 'json' }
import fs from 'fs'
-Object.keys(emojis).map((k) => {
- emojis[k].map((e) => {
- delete e.unicode_version
- delete e.emoji_version
- delete e.skin_tone_support_unicode_version
+Object.keys(emojis)
+ .map(k => {
+ emojis[k].map(e => {
+ delete e.unicode_version
+ delete e.emoji_version
+ delete e.skin_tone_support_unicode_version
+ })
})
-})
const res = {}
-Object.keys(emojis).map((k) => {
- const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase()
- res[groupId] = emojis[k]
-})
+Object.keys(emojis)
+ .map(k => {
+ const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase()
+ res[groupId] = emojis[k]
+ })
console.info('Updating emojis...')
fs.writeFileSync('src/assets/emoji.json', JSON.stringify(res))
diff --git a/changelog.d/action-button-extra-counter.add b/changelog.d/action-button-extra-counter.add
new file mode 100644
index 000000000..7d5c77447
--- /dev/null
+++ b/changelog.d/action-button-extra-counter.add
@@ -0,0 +1 @@
+Display counter for status action buttons when they are on the menu
diff --git a/changelog.d/akkoma-sharkey-net-support.add b/changelog.d/akkoma-sharkey-net-support.add
new file mode 100644
index 000000000..4b4bff7fe
--- /dev/null
+++ b/changelog.d/akkoma-sharkey-net-support.add
@@ -0,0 +1 @@
+Added support for Akkoma and IceShrimp.NET backend
diff --git a/changelog.d/api-refactor.skip b/changelog.d/akkoma.skip
similarity index 100%
rename from changelog.d/api-refactor.skip
rename to changelog.d/akkoma.skip
diff --git a/changelog.d/arithmetic-blend.add b/changelog.d/arithmetic-blend.add
new file mode 100644
index 000000000..c579dca28
--- /dev/null
+++ b/changelog.d/arithmetic-blend.add
@@ -0,0 +1,2 @@
+Add arithmetic blend ISS function
+
diff --git a/changelog.d/attrs-parsing.fix b/changelog.d/attrs-parsing.fix
deleted file mode 100644
index e36e59a86..000000000
--- a/changelog.d/attrs-parsing.fix
+++ /dev/null
@@ -1 +0,0 @@
-Fix HTML attribute parsing for escaped quotes
\ No newline at end of file
diff --git a/changelog.d/better-scroll-button.add b/changelog.d/better-scroll-button.add
new file mode 100644
index 000000000..b206869d1
--- /dev/null
+++ b/changelog.d/better-scroll-button.add
@@ -0,0 +1 @@
+Add support for detachable scrollTop button
diff --git a/changelog.d/bookmark-button-align.fix b/changelog.d/bookmark-button-align.fix
new file mode 100644
index 000000000..64bc2c807
--- /dev/null
+++ b/changelog.d/bookmark-button-align.fix
@@ -0,0 +1 @@
+Fix bookmark button alignment in the extra actions menu
diff --git a/changelog.d/csp.add b/changelog.d/csp.add
new file mode 100644
index 000000000..260337b97
--- /dev/null
+++ b/changelog.d/csp.add
@@ -0,0 +1 @@
+Compatibility with stricter CSP (Akkoma backend)
diff --git a/changelog.d/fast.change b/changelog.d/fast.change
deleted file mode 100644
index 1f0a89092..000000000
--- a/changelog.d/fast.change
+++ /dev/null
@@ -1 +0,0 @@
-Migrated to Vite 8 and optimized our imports, more stuff is loaded on-demand, reducing the initial load time and transfer size
diff --git a/changelog.d/ci-pr-uploads-removal.skip b/changelog.d/filter-fixes.skip
similarity index 100%
rename from changelog.d/ci-pr-uploads-removal.skip
rename to changelog.d/filter-fixes.skip
diff --git a/changelog.d/fix-emojis-breaking-bio.fix b/changelog.d/fix-emojis-breaking-bio.fix
deleted file mode 100644
index 62a607d8a..000000000
--- a/changelog.d/fix-emojis-breaking-bio.fix
+++ /dev/null
@@ -1 +0,0 @@
-Fix emojis breaking user bio/description editing
diff --git a/changelog.d/instance-store-migration.skip b/changelog.d/fix-wrap.skip
similarity index 100%
rename from changelog.d/instance-store-migration.skip
rename to changelog.d/fix-wrap.skip
diff --git a/changelog.d/more-fixes.skip b/changelog.d/migrate-auth-flow-pinia.skip
similarity index 100%
rename from changelog.d/more-fixes.skip
rename to changelog.d/migrate-auth-flow-pinia.skip
diff --git a/changelog.d/woodpecker-pr-pipeline.skip b/changelog.d/migrate-oauth-tokens-module-to-pinia-store.skip
similarity index 100%
rename from changelog.d/woodpecker-pr-pipeline.skip
rename to changelog.d/migrate-oauth-tokens-module-to-pinia-store.skip
diff --git a/changelog.d/minor.add b/changelog.d/minor.add
deleted file mode 100644
index 5f4934173..000000000
--- a/changelog.d/minor.add
+++ /dev/null
@@ -1,6 +0,0 @@
-button to remove all drafts
-option to remove forced aspect ratio for user profiles (requested)
-showing user tags (mrf policies for user + custom if present)
-version information now is also in about page
-mention autosuggest now sorts by recent activity
-non-square emoji support (toggleable by user)
diff --git a/changelog.d/minor.change b/changelog.d/minor.change
deleted file mode 100644
index 979d36955..000000000
--- a/changelog.d/minor.change
+++ /dev/null
@@ -1,7 +0,0 @@
-overall improved spacings in status action buttons and post form
-logout confirm button is now dangerous
-reply/quote now is a radio group and wraps, fixes overflow on languages where labels are too wide
-personal note input is now bigger
-moved "edit pinned" to the bottom for status action buttons.
-dots status action button drops down instead of up to avoid hiding the action buttons
-improved attachment description (alt text) input and display
diff --git a/changelog.d/minor.fix b/changelog.d/minor.fix
deleted file mode 100644
index 420836364..000000000
--- a/changelog.d/minor.fix
+++ /dev/null
@@ -1,12 +0,0 @@
-navbar wide logo cropping search input
-danger buttons being too bright
-user background upload failure no longer breaks new uploads + displays an error
-importing theme from old theme editor
-removed duplicate federationpolicy entry in admin tab
-repeater name overflowing content
-reply popover is now shown if replied-to status is muted
-second language input not having header
-post form's bottom left buttons not showing their toggled state
-some font overrides not working
-popovers opening outside of window's boundaries
-occasional blank page when showing new posts
diff --git a/changelog.d/mute-dropdown.fix b/changelog.d/mute-dropdown.fix
deleted file mode 100644
index 33f12a571..000000000
--- a/changelog.d/mute-dropdown.fix
+++ /dev/null
@@ -1 +0,0 @@
-Fixed status action mute hiding itself on click
diff --git a/changelog.d/mutes-sync.add b/changelog.d/mutes-sync.add
new file mode 100644
index 000000000..e8e0e462a
--- /dev/null
+++ b/changelog.d/mutes-sync.add
@@ -0,0 +1 @@
+Synchronized mutes, advanced mute control (regexp, expiry, naming)
diff --git a/changelog.d/profile-error.fix b/changelog.d/profile-error.fix
new file mode 100644
index 000000000..f123db5ae
--- /dev/null
+++ b/changelog.d/profile-error.fix
@@ -0,0 +1 @@
+Fix error styling for user profiles
diff --git a/changelog.d/profilebg.add b/changelog.d/profilebg.add
deleted file mode 100644
index a2c79074a..000000000
--- a/changelog.d/profilebg.add
+++ /dev/null
@@ -1 +0,0 @@
-displaying other user's backgrounds (if supported by BE)
diff --git a/changelog.d/quote-by-url.add b/changelog.d/quote-by-url.add
deleted file mode 100644
index ef401f93c..000000000
--- a/changelog.d/quote-by-url.add
+++ /dev/null
@@ -1 +0,0 @@
-Add quoting by URL and in replies
diff --git a/changelog.d/reply-quote-config.fix b/changelog.d/reply-quote-config.fix
deleted file mode 100644
index b6ac4e5e9..000000000
--- a/changelog.d/reply-quote-config.fix
+++ /dev/null
@@ -1 +0,0 @@
-Fix reply form crash when quote-reply settings are unavailable
diff --git a/static/empty.css b/changelog.d/small-fixes.skip
similarity index 100%
rename from static/empty.css
rename to changelog.d/small-fixes.skip
diff --git a/changelog.d/sw-cache-assets.add b/changelog.d/sw-cache-assets.add
new file mode 100644
index 000000000..5f7414eee
--- /dev/null
+++ b/changelog.d/sw-cache-assets.add
@@ -0,0 +1 @@
+Cache assets and emojis with service worker
diff --git a/changelog.d/sync-config.add b/changelog.d/sync-config.add
deleted file mode 100644
index 76a5a1cca..000000000
--- a/changelog.d/sync-config.add
+++ /dev/null
@@ -1,2 +0,0 @@
-settings synchronization
-user highlight synchronization
diff --git a/changelog.d/theme3-body-class.add b/changelog.d/theme3-body-class.add
new file mode 100644
index 000000000..f3d36fd70
--- /dev/null
+++ b/changelog.d/theme3-body-class.add
@@ -0,0 +1 @@
+Indicate currently active V3 theme as a body element class
diff --git a/changelog.d/unify-show-hide-buttons.add b/changelog.d/unify-show-hide-buttons.add
new file mode 100644
index 000000000..663bc38a5
--- /dev/null
+++ b/changelog.d/unify-show-hide-buttons.add
@@ -0,0 +1 @@
+Unify show/hide content buttons
diff --git a/changelog.d/user-management.add b/changelog.d/user-management.add
deleted file mode 100644
index ccd217f19..000000000
--- a/changelog.d/user-management.add
+++ /dev/null
@@ -1 +0,0 @@
-User administration + post scope/sensitivity admin change support
diff --git a/docker-compose.e2e.yml b/docker-compose.e2e.yml
deleted file mode 100644
index c740e69c4..000000000
--- a/docker-compose.e2e.yml
+++ /dev/null
@@ -1,59 +0,0 @@
-services:
- db:
- image: postgres:15-alpine
- environment:
- POSTGRES_USER: pleroma
- POSTGRES_PASSWORD: pleroma
- POSTGRES_DB: pleroma
- healthcheck:
- test: ["CMD-SHELL", "pg_isready -U pleroma -d pleroma"]
- interval: 2s
- timeout: 2s
- retries: 30
-
- pleroma:
- image: ${PLEROMA_IMAGE:-git.pleroma.social/pleroma/pleroma:stable}
- environment:
- DB_USER: pleroma
- DB_PASS: pleroma
- DB_NAME: pleroma
- DB_HOST: db
- DB_PORT: 5432
- DOMAIN: localhost
- INSTANCE_NAME: Pleroma E2E
- ADMIN_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com}
- NOTIFY_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com}
- E2E_ADMIN_USERNAME: ${E2E_ADMIN_USERNAME:-admin}
- E2E_ADMIN_PASSWORD: ${E2E_ADMIN_PASSWORD:-adminadmin}
- E2E_ADMIN_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com}
- depends_on:
- db:
- condition: service_healthy
- volumes:
- - ./docker/pleroma/entrypoint.e2e.sh:/opt/pleroma/entrypoint.e2e.sh:ro
- entrypoint: ["/bin/ash", "/opt/pleroma/entrypoint.e2e.sh"]
- healthcheck:
- # NOTE: "localhost" may resolve to ::1 in some images (IPv6) while Pleroma only
- # listens on IPv4 in this container. Use 127.0.0.1 to avoid false negatives.
- test: ["CMD-SHELL", "test -f /var/lib/pleroma/.e2e_seeded && wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null || exit 1"]
- interval: 5s
- timeout: 3s
- retries: 60
- ports:
- - 4000:4000
-
- e2e:
- build:
- context: .
- dockerfile: docker/e2e/Dockerfile.e2e
- depends_on:
- pleroma:
- condition: service_healthy
- environment:
- CI: "1"
- VITE_PROXY_TARGET: http://pleroma:4000
- VITE_PROXY_ORIGIN: http://localhost:4000
- E2E_BASE_URL: http://localhost:8099
- E2E_ADMIN_USERNAME: ${E2E_ADMIN_USERNAME:-admin}
- E2E_ADMIN_PASSWORD: ${E2E_ADMIN_PASSWORD:-adminadmin}
- command: ["yarn", "e2e:pw"]
diff --git a/docker/e2e/Dockerfile.e2e b/docker/e2e/Dockerfile.e2e
deleted file mode 100644
index 7e3fbfbf1..000000000
--- a/docker/e2e/Dockerfile.e2e
+++ /dev/null
@@ -1,16 +0,0 @@
-FROM mcr.microsoft.com/playwright:v1.61.0-jammy
-
-WORKDIR /app
-
-ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
-
-RUN npm install -g yarn@1.22.22
-
-COPY package.json yarn.lock ./
-RUN yarn --frozen-lockfile
-
-COPY . .
-
-ENV CI=1
-
-CMD ["yarn", "e2e:pw"]
diff --git a/docker/pleroma/entrypoint.e2e.sh b/docker/pleroma/entrypoint.e2e.sh
deleted file mode 100644
index 96920eeae..000000000
--- a/docker/pleroma/entrypoint.e2e.sh
+++ /dev/null
@@ -1,71 +0,0 @@
-#!/bin/ash
-
-set -eu
-
-SEED_SENTINEL_PATH="/var/lib/pleroma/.e2e_seeded"
-CONFIG_OVERRIDE_PATH="/var/lib/pleroma/config.exs"
-
-echo "-- Waiting for database..."
-while ! pg_isready -U "${DB_USER:-pleroma}" -d "postgres://${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-pleroma}" -t 1; do
- sleep 1s
-done
-
-echo "-- Writing E2E config overrides..."
-cat > "$CONFIG_OVERRIDE_PATH" <<'EOF'
-import Config
-
-config :pleroma, Pleroma.Captcha,
- enabled: false
-
-config :pleroma, :instance,
- registrations_open: true,
- account_activation_required: false,
- approval_required: false
-EOF
-
-echo "-- Running migrations..."
-/opt/pleroma/bin/pleroma_ctl migrate
-
-echo "-- Starting!"
-/opt/pleroma/bin/pleroma start &
-PLEROMA_PID="$!"
-
-cleanup() {
- if [ -n "${PLEROMA_PID:-}" ] && kill -0 "$PLEROMA_PID" 2>/dev/null; then
- kill -TERM "$PLEROMA_PID"
- wait "$PLEROMA_PID" || true
- fi
-}
-
-trap cleanup INT TERM
-
-echo "-- Waiting for API..."
-api_ok="false"
-for _i in $(seq 1 120); do
- if wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null 2>&1; then
- api_ok="true"
- break
- fi
- sleep 1s
-done
-
-if [ "$api_ok" != "true" ]; then
- echo "Timed out waiting for Pleroma API to become available"
- exit 1
-fi
-
-if [ ! -f "$SEED_SENTINEL_PATH" ]; then
- if [ -n "${E2E_ADMIN_USERNAME:-}" ] && [ -n "${E2E_ADMIN_PASSWORD:-}" ] && [ -n "${E2E_ADMIN_EMAIL:-}" ]; then
- echo "-- Seeding admin user (${E2E_ADMIN_USERNAME})..."
- if ! /opt/pleroma/bin/pleroma_ctl user new "$E2E_ADMIN_USERNAME" "$E2E_ADMIN_EMAIL" --admin --password "$E2E_ADMIN_PASSWORD" -y; then
- echo "-- User already exists (or creation failed), ensuring admin + confirmed..."
- /opt/pleroma/bin/pleroma_ctl user set "$E2E_ADMIN_USERNAME" --admin --confirmed
- fi
- else
- echo "-- Skipping admin seeding (missing E2E_ADMIN_* env)"
- fi
-
- touch "$SEED_SENTINEL_PATH"
-fi
-
-wait "$PLEROMA_PID"
diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 8ca076931..dfc5f9dc3 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -7,9 +7,9 @@
PleromaFE gets its configuration from several sources, in order of preference (the one above overrides ones below it)
-1. `/api/pleroma/frontend_configurations` - this is generated by backend and includes FE/Client-specific data. PleromaFE uses the `pleroma_fe` field of it. For more info on changing config on BE, look [here](../backend/configuration/cheatsheet.md#frontend_configurations)
-2. `/static/config.json` - this is a static FE-provided file, containing only FE specific configuration. This file is completely optional and could be removed but is useful as a fallback if some configuration JSON property isn't present in BE-provided config. It's also a reference point to check what default configuration are and what JSON properties even exist. In local dev mode it could be used to override BE configuration, more about that in HACKING.md. File is located [here](https://git.pleroma.social/pleroma/pleroma-fe/src/public/static/config.json).
-3. Built-in defaults. Those are hard-coded defaults that are used when `/static/config.json` is not available and BE-provided configuration JSON is missing some JSON properties. ( [Code](https://git.pleroma.social/pleroma/pleroma-fe/src/src/stores/instance.js) )
+1. `/api/statusnet/config.json` - this is generated on Backend and contains multiple things including instance name, char limit etc. It also contains FE/Client-specific data, PleromaFE uses `pleromafe` field of it. For more info on changing config on BE, look [here](../backend/configuration/cheatsheet.md#frontend_configurations)
+2. `/static/config.json` - this is a static FE-provided file, containing only FE specific configuration. This file is completely optional and could be removed but is useful as a fallback if some configuration JSON property isn't present in BE-provided config. It's also a reference point to check what default configuration are and what JSON properties even exist. In local dev mode it could be used to override BE configuration, more about that in HACKING.md. File is located [here](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/static/config.json).
+3. Built-in defaults. Those are hard-coded defaults that are used when `/static/config.json` is not available and BE-provided configuration JSON is missing some JSON properties. ( [Code](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/modules/instance.js) )
## Instance-defaults
diff --git a/docs/HACKING.md b/docs/HACKING.md
index 88760b77a..a5c491136 100644
--- a/docs/HACKING.md
+++ b/docs/HACKING.md
@@ -79,7 +79,7 @@ server {
In 99% cases PleromaFE uses [MastoAPI](https://docs.joinmastodon.org/api/) with [Pleroma Extensions](../backend/API/differences_in_mastoapi_responses.md) to fetch the data. The rest is either QvitterAPI leftovers or pleroma-exclusive APIs. QvitterAPI doesn't exactly have documentation and uses different JSON structure and sometimes different parameters and workflows, [this](https://twitter-api.readthedocs.io/en/latest/index.html) could be a good reference though. Some pleroma-exclusive API may still be using QvitterAPI JSON structure.
-PleromaFE supports both formats by transforming them into internal format which is basically QvitterAPI one with some additions and renaming. All data is passed trough [Entity Normalizer](https://git.pleroma.social/pleroma/pleroma-fe/src/src/services/entity_normalizer/entity_normalizer.service.js) which can serve as a reference of API and what's actually used, it's also a host for all the hacks and data transformation.
+PleromaFE supports both formats by transforming them into internal format which is basically QvitterAPI one with some additions and renaming. All data is passed trough [Entity Normalizer](https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/src/services/entity_normalizer/entity_normalizer.service.js) which can serve as a reference of API and what's actually used, it's also a host for all the hacks and data transformation.
For most part, PleromaFE tries to store all the info it can get in global vuex store - every user and post are passed trough updating mechanism where data is either added or merged with existing data, reactively updating the information throughout UI, so if in newest request user's post counter increased, it will be instantly updated in open user profile cards. This is also used to find users, posts and sometimes to build timelines and/or request parameters.
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 417ff8cf3..01bdb2038 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -1,34 +1,37 @@
-import js from '@eslint/js'
-import { defineConfig, globalIgnores } from 'eslint/config'
-import vue from 'eslint-plugin-vue'
-import globals from 'globals'
+import vue from "eslint-plugin-vue";
+import js from "@eslint/js";
+import globals from "globals";
-export default defineConfig([
+
+export default [
...vue.configs['flat/recommended'],
- globalIgnores(['**/*.js', 'build/', 'dist/', 'config/']),
+ js.configs.recommended,
{
- files: ['src/**/*.vue'],
- plugins: { js },
- extends: ['js/recommended'],
+ files: ["**/*.js", "**/*.mjs", "**/*.vue"],
+ ignores: ["build/*.js", "config/*.js"],
+
languageOptions: {
ecmaVersion: 2024,
- sourceType: 'module',
+ sourceType: "module",
parserOptions: {
- parser: '@babel/eslint-parser',
+ parser: "@babel/eslint-parser",
},
globals: {
...globals.browser,
...globals.vitest,
...globals.chai,
...globals.commonjs,
- ...globals.serviceworker,
- },
+ ...globals.serviceworker
+ }
},
rules: {
+ 'arrow-parens': 0,
+ 'generator-star-spacing': 0,
+ 'no-debugger': 0,
'vue/require-prop-types': 0,
'vue/multi-word-component-names': 0,
- },
- },
-])
+ }
+ }
+]
diff --git a/index.html b/index.html
index 26eeee19b..96c20c4b7 100644
--- a/index.html
+++ b/index.html
@@ -11,12 +11,14 @@
-
+
+
+
To use Pleroma, please enable JavaScript.
-
+
Art by pipivovott
diff --git a/package.json b/package.json
index f594d7a99..5e71a3996 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
{
"name": "pleroma_fe",
- "version": "2.10.1",
+ "version": "2.7.1",
"description": "Pleroma frontend, the default frontend of Pleroma social network server",
- "author": "Pleroma contributors ",
+ "author": "Pleroma contributors ",
"private": false,
"scripts": {
"dev": "node build/update-emoji.js && vite dev",
@@ -10,117 +10,109 @@
"unit": "node build/update-emoji.js && vitest --run",
"unit-ci": "node build/update-emoji.js && vitest --run --browser.headless",
"unit:watch": "node build/update-emoji.js && vitest",
- "e2e:pw": "playwright test --config test/e2e-playwright/playwright.config.mjs",
- "e2e": "sh ./tools/e2e/run.sh",
+ "e2e": "node test/e2e/runner.js",
"test": "yarn run unit && yarn run e2e",
- "ci-biome": "yarn exec biome check",
- "ci-eslint": "yarn exec eslint",
- "ci-stylelint": "yarn exec stylelint '**/*.scss' '**/*.vue'",
- "lint": "yarn ci-biome; yarn ci-eslint; yarn ci-stylelint",
- "lint-fix": "yarn exec eslint -- --fix; yarn exec stylelint '**/*.scss' '**/*.vue' --fix; biome check --write"
+ "stylelint": "yarn exec stylelint '**/*.scss' '**/*.vue'",
+ "lint": "eslint src test/unit/specs test/e2e/specs",
+ "lint-fix": "eslint --fix src test/unit/specs test/e2e/specs"
},
"dependencies": {
- "@babel/runtime": "7.28.4",
+ "@babel/runtime": "7.27.1",
"@chenfengyuan/vue-qrcode": "2.0.0",
- "@fortawesome/fontawesome-svg-core": "7.1.0",
- "@fortawesome/free-regular-svg-icons": "7.1.0",
- "@fortawesome/free-solid-svg-icons": "7.1.0",
- "@fortawesome/vue-fontawesome": "3.1.2",
- "@kazvmoe-infra/pinch-zoom-element": "1.3.0",
+ "@fortawesome/fontawesome-svg-core": "6.7.2",
+ "@fortawesome/free-regular-svg-icons": "6.7.2",
+ "@fortawesome/free-solid-svg-icons": "6.7.2",
+ "@fortawesome/vue-fontawesome": "3.0.8",
+ "@floatingghost/pinch-zoom-element": "1.3.1",
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
- "@ruffle-rs/ruffle": "0.1.0-nightly.2025.6.22",
+ "@ruffle-rs/ruffle": "0.1.0-nightly.2025.1.13",
"@vuelidate/core": "2.0.3",
"@vuelidate/validators": "2.0.4",
"@web3-storage/parse-link-header": "^3.1.0",
"body-scroll-lock": "3.1.5",
"chromatism": "3.0.0",
"click-outside-vue3": "4.0.1",
- "cropperjs": "2.0.1",
+ "cropperjs": "2.0.0",
"escape-html": "1.0.3",
"globals": "^16.0.0",
"hash-sum": "^2.0.0",
"js-cookie": "3.0.5",
"localforage": "1.10.0",
"parse-link-header": "2.0.0",
- "phoenix": "1.8.1",
- "pinia": "^3.0.4",
+ "phoenix": "1.7.21",
+ "pinia": "^3.0.0",
"punycode.js": "2.3.1",
"qrcode": "1.5.4",
"querystring-es3": "0.2.1",
"url": "0.11.4",
"utf8": "3.0.0",
"uuid": "11.1.0",
- "vue": "3.5.22",
+ "vue": "3.5.17",
"vue-i18n": "11",
- "vue-router": "4.6.4",
+ "vue-router": "4.5.1",
"vue-virtual-scroller": "^2.0.0-beta.7",
"vuex": "4.1.0"
},
"devDependencies": {
- "@babel/core": "7.28.5",
- "@babel/eslint-parser": "7.28.5",
- "@babel/plugin-transform-runtime": "7.28.5",
- "@babel/preset-env": "7.28.5",
- "@babel/register": "7.28.3",
- "@biomejs/biome": "2.3.11",
- "@pinia/testing": "1.0.3",
+ "@babel/core": "7.27.1",
+ "@babel/eslint-parser": "7.27.1",
+ "@babel/plugin-transform-runtime": "7.27.1",
+ "@babel/preset-env": "7.27.2",
+ "@babel/register": "7.27.1",
"@ungap/event-target": "0.2.4",
- "@vitejs/devtools": "^0.3.1",
- "@vitejs/plugin-vue": "^6.0.7",
- "@vitejs/plugin-vue-jsx": "^5.1.5",
- "@vitest/browser-playwright": "^4.1.7",
- "@vitest/browser": "^4.1.7",
- "@vitest/ui": "^4.1.7",
+ "@vitejs/plugin-vue": "^5.2.1",
+ "@vitejs/plugin-vue-jsx": "^4.1.1",
+ "@vitest/browser": "^3.0.7",
+ "@vitest/ui": "^3.0.7",
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
- "@vue/babel-plugin-jsx": "1.5.0",
- "@vue/compiler-sfc": "3.5.22",
+ "@vue/babel-plugin-jsx": "1.4.0",
+ "@vue/compiler-sfc": "3.5.17",
"@vue/test-utils": "2.4.6",
"autoprefixer": "10.4.21",
"babel-plugin-lodash": "3.3.4",
- "chai": "5.3.3",
- "chalk": "5.6.2",
+ "chai": "5.2.0",
+ "chalk": "5.4.1",
"chromedriver": "135.0.4",
"connect-history-api-fallback": "2.0.0",
"cross-spawn": "7.0.6",
"custom-event-polyfill": "1.0.7",
- "eslint": "9.39.2",
+ "eslint": "9.26.0",
+ "vue-eslint-parser": "10.1.3",
"eslint-config-standard": "17.1.0",
"eslint-formatter-friendly": "7.0.0",
- "eslint-plugin-import": "2.32.0",
- "eslint-plugin-n": "17.23.1",
+ "eslint-plugin-import": "2.31.0",
+ "eslint-plugin-n": "17.18.0",
"eslint-plugin-promise": "7.2.1",
- "eslint-plugin-vue": "10.6.2",
+ "eslint-plugin-vue": "10.1.0",
"eventsource-polyfill": "0.9.6",
"express": "5.1.0",
"function-bind": "1.1.2",
"http-proxy-middleware": "3.0.5",
"iso-639-1": "3.1.5",
"lodash": "4.17.21",
- "msw": "2.14.6",
- "nightwatch": "3.12.2",
- "oxc": "^1.0.1",
- "playwright": "1.61.0",
- "postcss": "8.5.6",
+ "msw": "2.10.2",
+ "nightwatch": "3.12.1",
+ "playwright": "1.52.0",
+ "postcss": "8.5.3",
"postcss-html": "^1.5.0",
"postcss-scss": "^4.0.6",
- "sass-embedded": "^1.100.0",
+ "sass": "1.89.2",
"selenium-server": "3.141.59",
- "semver": "7.7.3",
+ "semver": "7.7.2",
"serve-static": "2.2.0",
"shelljs": "0.10.0",
"sinon": "20.0.0",
- "sinon-chai": "4.0.1",
- "stylelint": "16.25.0",
+ "sinon-chai": "4.0.0",
+ "stylelint": "16.19.1",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^16.0.0",
"stylelint-config-recommended-scss": "^14.0.0",
"stylelint-config-recommended-vue": "^1.6.0",
"stylelint-config-standard": "38.0.0",
- "vite": "^8.0.0",
- "vite-plugin-eslint2": "^5.1.0",
- "vite-plugin-stylelint": "^6.1.0",
- "vitest": "^4.1.7",
- "vue-eslint-parser": "10.2.0"
+ "vite": "^6.1.0",
+ "vite-plugin-eslint2": "^5.0.3",
+ "vite-plugin-stylelint": "^6.0.0",
+ "vitest": "^3.0.7"
},
"type": "module",
"engines": {
diff --git a/postcss.config.js b/postcss.config.js
index b7fc12838..95ebbf2a6 100644
--- a/postcss.config.js
+++ b/postcss.config.js
@@ -1,5 +1,7 @@
import autoprefixer from 'autoprefixer'
export default {
- plugins: [autoprefixer],
+ plugins: [
+ autoprefixer
+ ]
}
diff --git a/public/static/empty.css b/public/static/empty.css
new file mode 100644
index 000000000..3dfa88151
--- /dev/null
+++ b/public/static/empty.css
@@ -0,0 +1 @@
+// nothing here //
diff --git a/public/static/palettes/index.json b/public/static/palettes/index.json
index 3c4e37e44..2cd110d1e 100644
--- a/public/static/palettes/index.json
+++ b/public/static/palettes/index.json
@@ -1,26 +1,6 @@
{
- "pleroma-dark": [
- "Pleroma Dark",
- "#121a24",
- "#182230",
- "#b9b9ba",
- "#d8a070",
- "#d31014",
- "#0fa00f",
- "#0095ff",
- "#ffa500"
- ],
- "pleroma-light": [
- "Pleroma Light",
- "#f2f4f6",
- "#dbe0e8",
- "#304055",
- "#f86f0f",
- "#d31014",
- "#0fa00f",
- "#0095ff",
- "#ffa500"
- ],
+ "pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
+ "pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
"classic-dark": {
"name": "Classic Dark",
"bg": "#161c20",
@@ -32,28 +12,8 @@
"cBlue": "#0095ff",
"cOrange": "#ffa500"
},
- "bird": [
- "Bird",
- "#f8fafd",
- "#e6ecf0",
- "#14171a",
- "#0084b8",
- "#e0245e",
- "#17bf63",
- "#1b95e0",
- "#fab81e"
- ],
- "pleroma-amoled": [
- "Pleroma Dark AMOLED",
- "#000000",
- "#111111",
- "#b0b0b1",
- "#d8a070",
- "#aa0000",
- "#0fa00f",
- "#0095ff",
- "#d59500"
- ],
+ "bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
+ "pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"],
"tomorrow-night": {
"name": "Tomorrow Night",
"bg": "#1d1f21",
@@ -76,28 +36,8 @@
"cGreen": "#50FA7B",
"cOrange": "#FFB86C"
},
- "ir-black": [
- "Ir Black",
- "#000000",
- "#242422",
- "#b5b3aa",
- "#ff6c60",
- "#FF6C60",
- "#A8FF60",
- "#96CBFE",
- "#FFFFB6"
- ],
- "monokai": [
- "Monokai",
- "#272822",
- "#383830",
- "#f8f8f2",
- "#f92672",
- "#F92672",
- "#a6e22e",
- "#66d9ef",
- "#f4bf75"
- ],
+ "ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
+ "monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ],
"purple-stream": {
"name": "Purple stream",
"bg": "#17171A",
diff --git a/public/static/splash.css b/public/static/splash.css
index caf57dacb..abdc19fc2 100644
--- a/public/static/splash.css
+++ b/public/static/splash.css
@@ -5,14 +5,15 @@ body {
#splash {
--scale: 1;
-
width: 100vw;
height: 100vh;
display: grid;
grid-template-rows: auto;
grid-template-columns: auto;
align-content: center;
- place-items: center;
+ align-items: center;
+ justify-content: center;
+ justify-items: center;
flex-direction: column;
background: #0f161e;
font-family: sans-serif;
@@ -20,20 +21,13 @@ body {
position: absolute;
z-index: 9999;
font-size: calc(1vw + 1vh + 1vmin);
- opacity: 1;
- transition: opacity 500ms ease-out 2s;
-}
-
-#splash.hidden,
-#splash.initial-hidden {
- opacity: 0;
}
#splash-credit {
position: absolute;
- font-size: 1em;
- bottom: 1em;
- right: 1em;
+ font-size: 14px;
+ bottom: 16px;
+ right: 16px;
}
#splash-container {
@@ -65,17 +59,16 @@ body {
z-index: 2;
grid-template-rows: repeat(8, 1fr);
grid-template-columns: repeat(5, 1fr);
- grid-template-areas:
- "P P . L L"
- "P P . L L"
- "P P . L L"
- "P P . L L"
- "P P . . ."
- "P P . . ."
- "P P . E E"
- "P P . E E";
+ grid-template-areas: "P P . L L"
+ "P P . L L"
+ "P P . L L"
+ "P P . L L"
+ "P P . . ."
+ "P P . . ."
+ "P P . E E"
+ "P P . E E";
- --logoChunkSize: calc(2em * 0.5 * var(--scale));
+ --logoChunkSize: calc(2em * 0.5 * var(--scale))
}
.chunk {
@@ -85,7 +78,7 @@ body {
#chunk-P {
grid-area: P;
- border-top-left-radius: calc(var(--logoChunkSize) / 2);
+ border-top-left-radius: calc(var(--logoChunkSize) / 2);
}
#chunk-L {
diff --git a/renovate.json b/renovate.json
index 4bd832f5f..39a2b6e9a 100644
--- a/renovate.json
+++ b/renovate.json
@@ -1,4 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
- "extends": ["config:base"]
+ "extends": [
+ "config:base"
+ ]
}
diff --git a/src/App.js b/src/App.js
index 934b96713..a251682dc 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,267 +1,187 @@
-import { throttle } from 'lodash'
-import { mapState } from 'pinia'
-import { defineAsyncComponent } from 'vue'
-
-import DesktopNav from 'src/components/desktop_nav/desktop_nav.vue'
-import FeaturesPanel from 'src/components/features_panel/features_panel.vue'
-import GlobalError from 'src/components/global_error/global_error.vue'
-import GlobalNoticeList from 'src/components/global_notice_list/global_notice_list.vue'
-import InstanceSpecificPanel from 'src/components/instance_specific_panel/instance_specific_panel.vue'
-import MobileNav from 'src/components/mobile_nav/mobile_nav.vue'
-import MobilePostStatusButton from 'src/components/mobile_post_status_button/mobile_post_status_button.vue'
-import NavPanel from 'src/components/nav_panel/nav_panel.vue'
-import UserPanel from 'src/components/user_panel/user_panel.vue'
+import UserPanel from './components/user_panel/user_panel.vue'
+import NavPanel from './components/nav_panel/nav_panel.vue'
+import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
+import FeaturesPanel from './components/features_panel/features_panel.vue'
+import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
+import ShoutPanel from './components/shout_panel/shout_panel.vue'
+import MediaModal from './components/media_modal/media_modal.vue'
+import SideDrawer from './components/side_drawer/side_drawer.vue'
+import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
+import MobileNav from './components/mobile_nav/mobile_nav.vue'
+import DesktopNav from './components/desktop_nav/desktop_nav.vue'
+import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
+import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue'
+import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
+import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
+import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
import { getOrCreateServiceWorker } from './services/sw/sw'
-import { windowHeight, windowWidth } from './services/window_utils/window_utils'
+import { windowWidth, windowHeight } from './services/window_utils/window_utils'
+import { mapGetters } from 'vuex'
+import { defineAsyncComponent } from 'vue'
+import { useShoutStore } from './stores/shout'
+import { useInterfaceStore } from './stores/interface'
-import { useEmojiStore } from 'src/stores/emoji.js'
-import { useI18nStore } from 'src/stores/i18n.js'
-import { useInstanceStore } from 'src/stores/instance.js'
-import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
-import { useInterfaceStore } from 'src/stores/interface.js'
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-import { useShoutStore } from 'src/stores/shout.js'
-
-// Helper to unwrap reactive proxies
-window.toValue = (x) => JSON.parse(JSON.stringify(x))
+import { throttle } from 'lodash'
export default {
name: 'app',
components: {
UserPanel,
NavPanel,
- Notifications: defineAsyncComponent(
- () => import('src/components/notifications/notifications.vue'),
- ),
+ Notifications: defineAsyncComponent(() => import('./components/notifications/notifications.vue')),
InstanceSpecificPanel,
FeaturesPanel,
- WhoToFollowPanel: defineAsyncComponent(
- () =>
- import('src/components/who_to_follow_panel/who_to_follow_panel.vue'),
- ),
- ShoutPanel: defineAsyncComponent(
- () => import('src/components/shout_panel/shout_panel.vue'),
- ),
- MediaModal: defineAsyncComponent(
- () => import('src/components/media_modal/media_modal.vue'),
- ),
+ WhoToFollowPanel,
+ ShoutPanel,
+ MediaModal,
+ SideDrawer,
MobilePostStatusButton,
MobileNav,
DesktopNav,
- SettingsModal: defineAsyncComponent(
- () => import('src/components/settings_modal/settings_modal.vue'),
- ),
- UpdateNotification: defineAsyncComponent(
- () =>
- import('src/components/update_notification/update_notification.vue'),
- ),
- PostStatusModal: defineAsyncComponent(
- () => import('src/components/post_status_modal/post_status_modal.vue'),
- ),
- UserReportingModal: defineAsyncComponent(
- () =>
- import('src/components/user_reporting_modal/user_reporting_modal.vue'),
- ),
- EditStatusModal: defineAsyncComponent(
- () => import('src/components/edit_status_modal/edit_status_modal.vue'),
- ),
- StatusHistoryModal: defineAsyncComponent(
- () =>
- import('src/components/status_history_modal/status_history_modal.vue'),
- ),
- GlobalError,
- GlobalNoticeList,
+ SettingsModal: defineAsyncComponent(() => import('./components/settings_modal/settings_modal.vue')),
+ UpdateNotification: defineAsyncComponent(() => import('./components/update_notification/update_notification.vue')),
+ UserReportingModal,
+ PostStatusModal,
+ EditStatusModal,
+ StatusHistoryModal,
+ GlobalNoticeList
},
data: () => ({
- mobileActivePanel: 'timeline',
+ mobileActivePanel: 'timeline'
}),
- provide() {
- return {
- allowNonSquareEmoji: useMergedConfigStore().mergedConfig.nonSquareEmoji,
- }
- },
watch: {
- themeApplied() {
+ themeApplied () {
this.removeSplash()
},
- currentTheme() {
+ currentTheme () {
this.setThemeBodyClass()
},
- layoutType() {
+ layoutType () {
document.getElementById('modal').classList = ['-' + this.layoutType]
- },
+ }
},
- created() {
+ created () {
// Load the locale from the storage
- const value = useMergedConfigStore().mergedConfig.interfaceLanguage
- useI18nStore().setLanguage(value)
- useEmojiStore().loadUnicodeEmojiData(value)
-
+ const val = this.$store.getters.mergedConfig.interfaceLanguage
+ this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
document.getElementById('modal').classList = ['-' + this.layoutType]
// Create bound handlers
this.updateScrollState = throttle(this.scrollHandler, 200)
this.updateMobileState = throttle(this.resizeHandler, 200)
},
- mounted() {
+ mounted () {
window.addEventListener('resize', this.updateMobileState)
this.scrollParent.addEventListener('scroll', this.updateScrollState)
- if (this.themeApplied) {
+ if (useInterfaceStore().themeApplied) {
this.setThemeBodyClass()
this.removeSplash()
}
getOrCreateServiceWorker()
},
- unmounted() {
+ unmounted () {
window.removeEventListener('resize', this.updateMobileState)
this.scrollParent.removeEventListener('scroll', this.updateScrollState)
},
computed: {
- currentTheme() {
- if (this.styleDataUsed) {
- const styleMeta = this.styleDataUsed.find(
- (x) => x.component === '@meta',
- )
+ themeApplied () {
+ return useInterfaceStore().themeApplied
+ },
+ currentTheme () {
+ if (useInterfaceStore().styleDataUsed) {
+ const styleMeta = useInterfaceStore().styleDataUsed.find(x => x.component === '@meta')
if (styleMeta !== undefined) {
- return styleMeta.directives.name.replaceAll(' ', '-').toLowerCase()
+ return styleMeta.directives.name.replaceAll(" ", "-").toLowerCase()
}
}
return 'stock'
},
- layoutModalClass() {
+ layoutModalClass () {
return '-' + this.layoutType
},
- classes() {
+ classes () {
return [
{
'-reverse': this.reverseLayout,
'-no-sticky-headers': this.noSticky,
- '-has-new-post-button': this.newPostButtonShown,
+ '-has-new-post-button': this.newPostButtonShown
},
- '-' + this.layoutType,
+ '-' + this.layoutType
]
},
- navClasses() {
- const { navbarColumnStretch } = useMergedConfigStore().mergedConfig
+ navClasses () {
+ const { navbarColumnStretch } = this.$store.getters.mergedConfig
return [
'-' + this.layoutType,
- ...(navbarColumnStretch ? ['-column-stretch'] : []),
+ ...(navbarColumnStretch ? ['-column-stretch'] : [])
]
},
- currentUser() {
- return this.$store.state.users.currentUser
- },
- userBackground() {
- return this.currentUser.background_image
- },
- foreignProfileBackground() {
- return (
- useMergedConfigStore().mergedConfig.allowForeignUserBackground &&
- useInterfaceStore().foreignProfileBackground
- )
- },
- instanceBackground() {
- return useMergedConfigStore().mergedConfig.hideInstanceWallpaper
+ currentUser () { return this.$store.state.users.currentUser },
+ userBackground () { return this.currentUser.background_image },
+ instanceBackground () {
+ return this.mergedConfig.hideInstanceWallpaper
? null
- : this.instanceBackgroundUrl
+ : this.$store.state.instance.background
},
- background() {
- return (
- this.foreignProfileBackground ||
- this.userBackground ||
- this.instanceBackground
- )
- },
- bgStyle() {
+ background () { return this.userBackground || this.instanceBackground },
+ bgStyle () {
if (this.background) {
return {
- '--body-background-image': `url(${this.background})`,
+ '--body-background-image': `url(${this.background})`
}
}
},
- shoutJoined() {
- return useShoutStore().joined
+ shout () { return useShoutStore().joined },
+ suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
+ showInstanceSpecificPanel () {
+ return this.$store.state.instance.showInstanceSpecificPanel &&
+ !this.$store.getters.mergedConfig.hideISP &&
+ this.$store.state.instance.instanceSpecificPanelContent
},
- isChats() {
+ isChats () {
return this.$route.name === 'chat' || this.$route.name === 'chats'
},
- isListEdit() {
+ isListEdit () {
return this.$route.name === 'lists-edit'
},
- newPostButtonShown() {
+ newPostButtonShown () {
if (this.isChats) return false
if (this.isListEdit) return false
- return (
- useMergedConfigStore().mergedConfig.alwaysShowNewPostButton ||
- this.layoutType === 'mobile'
- )
+ return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
},
- shoutboxPosition() {
- return (
- useMergedConfigStore().mergedConfig.alwaysShowNewPostButton || false
- )
+ showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
+ editingAvailable () { return this.$store.state.instance.editingAvailable },
+ shoutboxPosition () {
+ return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false
},
- hideShoutbox() {
- return useMergedConfigStore().mergedConfig.hideShoutbox
+ hideShoutbox () {
+ return this.$store.getters.mergedConfig.hideShoutbox
},
- reverseLayout() {
- const { thirdColumnMode, sidebarRight: reverseSetting } =
- useMergedConfigStore().mergedConfig
+ layoutType () { return useInterfaceStore().layoutType },
+ privateMode () { return this.$store.state.instance.private },
+ reverseLayout () {
+ const { thirdColumnMode, sidebarRight: reverseSetting } = this.$store.getters.mergedConfig
if (this.layoutType !== 'wide') {
return reverseSetting
} else {
- return thirdColumnMode === 'notifications'
- ? reverseSetting
- : !reverseSetting
+ return thirdColumnMode === 'notifications' ? reverseSetting : !reverseSetting
}
},
- noSticky() {
- return useMergedConfigStore().mergedConfig.disableStickyHeaders
- },
- showScrollbars() {
- return useMergedConfigStore().mergedConfig.showScrollbars
- },
- scrollParent() {
- return window /* this.$refs.appContentRef */
- },
- showInstanceSpecificPanel() {
- return (
- this.instanceSpecificPanelPresent &&
- !useMergedConfigStore().mergedConfig.hideISP
- )
- },
- ...mapState(useMergedConfigStore, ['mergedConfig']),
- ...mapState(useInterfaceStore, [
- 'themeApplied',
- 'styleDataUsed',
- 'layoutType',
- ]),
- ...mapState(useInstanceStore, ['styleDataUsed']),
- ...mapState(useInstanceCapabilitiesStore, [
- 'suggestionsEnabled',
- 'editingAvailable',
- ]),
- ...mapState(useInstanceStore, {
- instanceBackgroundUrl: (store) => store.instanceIdentity.background,
- showFeaturesPanel: (store) => store.instanceIdentity.showFeaturesPanel,
- instanceSpecificPanelPresent: (store) =>
- store.instanceIdentity.showInstanceSpecificPanel &&
- store.instanceIdentity.instanceSpecificPanelContent,
- }),
+ noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
+ showScrollbars () { return this.$store.getters.mergedConfig.showScrollbars },
+ scrollParent () { return window; /* this.$refs.appContentRef */ },
+ ...mapGetters(['mergedConfig'])
},
methods: {
- resizeHandler() {
+ resizeHandler () {
useInterfaceStore().setLayoutWidth(windowWidth())
useInterfaceStore().setLayoutHeight(windowHeight())
},
- scrollHandler() {
- const scrollPosition =
- this.scrollParent === window
- ? window.scrollY
- : this.scrollParent.scrollTop
+ scrollHandler () {
+ const scrollPosition = this.scrollParent === window ? window.scrollY : this.scrollParent.scrollTop
if (scrollPosition != 0) {
this.$refs.appContentRef.classList.add(['-scrolled'])
@@ -269,10 +189,10 @@ export default {
this.$refs.appContentRef.classList.remove(['-scrolled'])
}
},
- setThemeBodyClass() {
+ setThemeBodyClass () {
const themeName = this.currentTheme
const classList = Array.from(document.body.classList)
- const oldTheme = classList.filter((c) => c.startsWith('theme-'))
+ const oldTheme = classList.filter(c => c.startsWith('theme-'))
if (themeName !== null && themeName !== '') {
const newTheme = `theme-${themeName.toLowerCase()}`
@@ -288,19 +208,14 @@ export default {
document.body.classList.remove(...oldTheme)
}
},
- removeSplash() {
- document.querySelector('#status').textContent = this.$t(
- 'splash.fun_' + Math.ceil(Math.random() * 4),
- )
+ removeSplash () {
+ document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4))
const splashscreenRoot = document.querySelector('#splash')
splashscreenRoot.addEventListener('transitionend', () => {
splashscreenRoot.remove()
})
- setTimeout(() => {
- splashscreenRoot.remove() // forcibly remove it, should fix my plasma browser widget t. HJ
- }, 600)
splashscreenRoot.classList.add('hidden')
document.querySelector('#app').classList.remove('hidden')
- },
- },
+ }
+ }
}
diff --git a/src/App.scss b/src/App.scss
index be3f0df22..6e837a0e7 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -3,7 +3,7 @@
@use "panel";
@import '@fortawesome/fontawesome-svg-core/styles.css';
-@import '@kazvmoe-infra/pinch-zoom-element/dist/pinch-zoom.css';
+@import '@floatingghost/pinch-zoom-element/dist/pinch-zoom.css';
:root {
--status-margin: 0.75em;
@@ -21,7 +21,7 @@
}
html {
- font-size: var(--textSize, 1rem);
+ font-size: var(--textSize, 14px);
--navbar-height: var(--navbarSize, 3.5rem);
--emoji-size: var(--emojiSize, 32px);
@@ -50,7 +50,7 @@ body {
// have a cursor/pointer to operate them
@media (any-pointer: fine) {
* {
- scrollbar-color: var(--icon) transparent;
+ scrollbar-color: var(--fg) transparent;
&::-webkit-scrollbar {
background: transparent;
@@ -130,7 +130,7 @@ body {
}
// Body should have background to scrollbar otherwise it will use white (body color?)
html {
- scrollbar-color: var(--icon) var(--wallpaper);
+ scrollbar-color: var(--fg) var(--wallpaper);
background: var(--wallpaper);
}
}
@@ -144,25 +144,6 @@ h4 {
margin: 0;
}
-code {
- background: var(--bg);
- border: 1px solid var(--fg);
- border-radius: var(--roundness);
- padding: 0 0.2em;
-
- & pre,
- pre & {
- display: block;
- overflow-x: auto;
- padding: 0.2em;
- }
-
- &.pre {
- white-space: pre;
- display: block;
- }
-}
-
.iconLetter {
display: inline-block;
text-align: center;
@@ -219,7 +200,6 @@ nav {
background-color: var(--wallpaper);
background-image: var(--body-background-image);
background-position: 50%;
- transition: background-image 1s;
}
.underlay {
@@ -402,10 +382,6 @@ nav {
font-family: sans-serif;
font-family: var(--font);
- &.-transparent {
- backdrop-filter: blur(0.125em) contrast(60%);
- }
-
&::-moz-focus-inner {
border: none;
}
@@ -430,14 +406,6 @@ nav {
button:not(.button-default) {
color: var(--text);
font-size: 100%;
- text-align: initial;
- padding: 0;
- background: none;
- border: none;
- outline: none;
- display: inline;
- font-family: inherit;
- line-height: unset;
}
&.disabled {
@@ -445,6 +413,45 @@ nav {
}
}
+.list-item {
+ border-color: var(--border);
+ border-style: solid;
+ border-width: 0;
+ border-top-width: 1px;
+
+ &.-active,
+ &:hover {
+ border-top-width: 1px;
+ border-bottom-width: 1px;
+ }
+
+ &.-active + &,
+ &:hover + & {
+ border-top-width: 0;
+ }
+
+ &:hover + .menu-item-collapsible:not(.-expanded) + &,
+ &.-active + .menu-item-collapsible:not(.-expanded) + & {
+ border-top-width: 0;
+ }
+
+ &[aria-expanded="true"] {
+ border-bottom-width: 1px;
+ }
+
+ &:first-child {
+ border-top-right-radius: var(--roundness);
+ border-top-left-radius: var(--roundness);
+ border-top-width: 0;
+ }
+
+ &:last-child {
+ border-bottom-right-radius: var(--roundness);
+ border-bottom-left-radius: var(--roundness);
+ border-bottom-width: 0;
+ }
+}
+
.menu-item,
.list-item {
display: block;
@@ -463,6 +470,22 @@ nav {
--__line-height: 1.5em;
--__horizontal-gap: 0.75em;
--__vertical-gap: 0.5em;
+
+ &.-non-interactive {
+ cursor: auto;
+ }
+
+ a,
+ button:not(.button-default) {
+ text-align: initial;
+ padding: 0;
+ background: none;
+ border: none;
+ outline: none;
+ display: inline;
+ font-family: inherit;
+ line-height: unset;
+ }
}
.button-unstyled {
@@ -486,12 +509,6 @@ nav {
}
}
-label {
- &.-disabled {
- color: var(--textFaint);
- }
-}
-
input,
textarea {
border: none;
@@ -508,10 +525,6 @@ textarea {
height: unset;
}
- &::placeholder {
- color: var(--textFaint)
- }
-
--_padding: 0.5em;
border: none;
@@ -532,10 +545,6 @@ textarea {
&[disabled="disabled"],
&.disabled {
cursor: not-allowed;
- color: var(--textFaint);
-
- /* stylelint-disable-next-line declaration-no-important */
- background-color: transparent !important;
}
&[type="range"] {
@@ -561,8 +570,6 @@ textarea {
& + label::before {
opacity: 0.5;
}
-
- background-color: var(--background);
}
+ label::before {
@@ -662,8 +669,7 @@ option {
list-style: none;
display: grid;
grid-auto-flow: row dense;
- grid-template-columns: repeat(auto-fit, minmax(20em, 1fr));
- grid-gap: 0.5em;
+ grid-template-columns: 1fr 1fr;
li {
border: 1px solid var(--border);
@@ -673,6 +679,11 @@ option {
}
}
+.btn-block {
+ display: block;
+ width: 100%;
+}
+
.btn-group {
position: relative;
display: inline-flex;
@@ -684,6 +695,7 @@ option {
--_roundness-right: 0;
position: relative;
+ flex: 1 1 auto;
}
> *:first-child,
@@ -730,15 +742,17 @@ option {
}
&.-dot {
- min-height: 0.6em;
- max-height: 0.6em;
- min-width: 0.6em;
- max-width: 0.6em;
- left: calc(50% + 0.5em);
- top: calc(50% - 1em);
- line-height: 0;
+ min-height: 8px;
+ max-height: 8px;
+ min-width: 8px;
+ max-width: 8px;
padding: 0;
- margin: 0;
+ line-height: 0;
+ font-size: 0;
+ left: calc(50% - 4px);
+ top: calc(50% - 4px);
+ margin-left: 6px;
+ margin-top: -6px;
}
&.-counter {
@@ -760,19 +774,6 @@ option {
padding: 0 0.25em;
border-radius: var(--roundness);
border: 1px solid var(--border);
-
- &.-dismissible {
- display: flex;
- padding-left: 0.5em;
- margin: 0;
- align-items: baseline;
- line-height: 2;
-
- span {
- display: block;
- flex: 1 0 auto;
- }
- }
}
.faint {
@@ -782,20 +783,21 @@ option {
color: var(--text);
}
-.notice-dismissible {
- display: flex;
- padding: 0.75em 1em;
- align-items: baseline;
- line-height: 1.5;
+.visibility-notice {
+ padding: 0.5em;
+ border: 1px solid var(--textFaint);
+ border-radius: var(--roundness);
+}
- p,
- span {
- display: block;
- flex: 1 1 auto;
- margin: 0;
- }
+.notice-dismissible {
+ padding-right: 4rem;
+ position: relative;
.dismiss {
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: 0.5em;
color: inherit;
}
}
@@ -934,7 +936,12 @@ option {
#splash {
pointer-events: none;
- // transition: opacity 0.5s;
+ transition: opacity 0.5s;
+ opacity: 1;
+
+ &.hidden {
+ opacity: 0;
+ }
#status {
&.css-ok {
@@ -1073,7 +1080,7 @@ option {
scale: 1.0063 0.9938;
translate: 0 -10%;
transform: rotateZ(var(--defaultZ));
- animation-timing-function: ease-in-out;
+ animation-timing-function: ease-in-ou;
}
90% {
diff --git a/src/App.vue b/src/App.vue
index 84a6f7d3d..e5e088bc3 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -28,10 +28,10 @@
>
-
-
-
-
+
+
+
+
@@ -60,8 +60,8 @@
/>
-
-
diff --git a/src/api/admin.js b/src/api/admin.js
deleted file mode 100644
index c33f863a7..000000000
--- a/src/api/admin.js
+++ /dev/null
@@ -1,491 +0,0 @@
-import { promisedRequest } from './helpers.js'
-
-const REPORTS = '/api/v1/pleroma/admin/reports'
-const CONFIG_URL = '/api/v1/pleroma/admin/config'
-const DESCRIPTIONS_URL = '/api/v1/pleroma/admin/config/descriptions'
-
-const ANNOUNCEMENTS_URL = (id = '') =>
- `/api/v1/pleroma/admin/announcements/${id}`
-
-const FRONTENDS_URL = '/api/v1/pleroma/admin/frontends'
-const FRONTENDS_INSTALL_URL = '/api/v1/pleroma/admin/frontends/install'
-
-const USERS_URL = (nickname = '') => `/api/v1/pleroma/admin/users/${nickname}`
-const USERS_URL_LIST = ({
- page,
- pageSize,
- filters = {},
- query = '',
- name = '',
- email = '',
-}) => {
- const {
- local = false,
- external = false,
- active = false,
- needApproval = false,
- unconfirmed = false,
- deactivated = false,
- isAdmin = true,
- isModerator = true,
- } = filters
- const filters_str = [
- local && 'local',
- external && 'external',
- active && 'active',
- needApproval && 'need_approval',
- unconfirmed && 'unconfirmed',
- deactivated && 'deactivated',
- isAdmin && 'is_admin',
- isModerator && 'is_moderator',
- ]
- .filter((x) => x)
- .join(',')
- return `/api/v1/pleroma/admin/users?page=${page}&page_size=${pageSize}&filters=${filters_str}&query=${query}&name=${name}&email=${email}`
-}
-
-const TAG_USER_URL = '/api/pleroma/admin/users/tag'
-
-const PERMISSION_GROUP_URL = (right) =>
- `/api/pleroma/admin/users/permission_group/${right}`
-const ACTIVATE_USERS_URL = '/api/pleroma/admin/users/activate'
-const DEACTIVATE_USERS_URL = '/api/pleroma/admin/users/deactivate'
-const SUGGEST_USERS_URL = '/api/pleroma/admin/users/suggest'
-const UNSUGGEST_USERS_URL = '/api/pleroma/admin/users/unsuggest'
-const APPROVE_USERS_URL = '/api/v1/pleroma/admin/users/approve'
-const CONFIRM_USERS_URL = '/api/v1/pleroma/admin/users/confirm_email'
-const RESEND_CONFIRMATION_EMAIL_URL =
- '/api/v1/pleroma/admin/users/resend_confirmation_email'
-const LIST_STATUSES_URL = ({ id, page, pageSize, godmode, withReblogs }) =>
- `/api/v1/pleroma/admin/users/${id}/statuses?page_size=${pageSize}&page=${page}&godmode=${godmode}&with_reblogs=${withReblogs}`
-const CHANGE_STATUS_SCOPE_URL = (id) => `/api/v1/pleroma/admin/statuses/${id}`
-const REQUIRE_PASSWORD_CHANGE_URL =
- '/api/v1/pleroma/admin/users/force_password_reset'
-
-const DISABLE_MFA_URL = '/api/v1/pleroma/admin/users/disable_mfa'
-const EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji'
-const EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import'
-const EMOJI_PACK_URL = (name) => `/api/v1/pleroma/emoji/pack?name=${name}`
-const EMOJI_PACKS_DL_REMOTE_URL = '/api/v1/pleroma/emoji/packs/download'
-const EMOJI_PACKS_DL_REMOTE_ZIP_URL = '/api/v1/pleroma/emoji/packs/download_zip'
-const EMOJI_PACKS_LS_REMOTE_URL = (url, page, pageSize) =>
- `/api/v1/pleroma/emoji/packs/remote?url=${url}&page=${page}&page_size=${pageSize}`
-const EMOJI_UPDATE_FILE_URL = (name) =>
- `/api/v1/pleroma/emoji/packs/files?name=${name}`
-
-//
-
-export const setUsersTags = ({
- tags,
- credentials,
- value,
- screen_names: nicknames,
-}) =>
- promisedRequest({
- url: TAG_USER_URL,
- method: value ? 'PUT' : 'DELETE',
- credentials,
- payload: {
- nicknames,
- tags,
- },
- })
-
-export const setUsersRight = ({
- right,
- credentials,
- value,
- screen_names: nicknames,
-}) =>
- promisedRequest({
- url: PERMISSION_GROUP_URL(right),
- method: value ? 'POST' : 'DELETE',
- credentials,
- payload: {
- nicknames,
- },
- })
-
-export const setUsersActivationStatus = ({
- credentials,
- screen_names: nicknames,
- value,
-}) =>
- promisedRequest({
- url: value ? ACTIVATE_USERS_URL : DEACTIVATE_USERS_URL,
- method: 'PATCH',
- credentials,
- payload: {
- nicknames,
- },
- }).then((response) => response.users)
-
-export const setUsersApprovalStatus = ({
- credentials,
- screen_names: nicknames,
-}) =>
- promisedRequest({
- url: APPROVE_USERS_URL,
- method: 'PATCH',
- credentials,
- payload: {
- nicknames,
- },
- }).then((response) => response.users)
-
-export const setUsersConfirmationStatus = ({
- credentials,
- screen_names: nicknames,
-}) =>
- promisedRequest({
- url: CONFIRM_USERS_URL,
- method: 'PATCH',
- credentials,
- payload: {
- nicknames,
- },
- }).then((response) => response.users)
-
-export const setUsersSuggestionStatus = ({
- credentials,
- screen_names: nicknames,
- value,
-}) =>
- promisedRequest({
- url: value ? SUGGEST_USERS_URL : UNSUGGEST_USERS_URL,
- method: 'PATCH',
- credentials,
- payload: {
- nicknames,
- },
- }).then((response) => response.users)
-
-export const getUserData = ({ credentials, screen_name: nickname }) =>
- promisedRequest({
- url: USERS_URL(nickname),
- method: 'GET',
- credentials,
- })
-
-export const deleteAccounts = ({ credentials, screen_names: nicknames }) =>
- promisedRequest({
- url: USERS_URL(),
- method: 'DELETE',
- credentials,
- payload: {
- nicknames,
- },
- })
-
-export const getAnnouncements = ({ id, credentials }) =>
- promisedRequest({ url: ANNOUNCEMENTS_URL(id), credentials })
-
-// the reported list is hardly useful because standards are for dating i guess,
-// so make sure to fetchIfMissing right afterward using this call
-export const listUsers = ({ opts, credentials }) =>
- promisedRequest({
- url: USERS_URL_LIST(opts),
- credentials,
- method: 'GET',
- })
-
-export const resendConfirmationEmail = ({
- screen_names: nicknames,
- credentials,
-}) =>
- promisedRequest({
- url: RESEND_CONFIRMATION_EMAIL_URL,
- credentials,
- method: 'PATCH',
- payload: {
- nicknames,
- },
- })
-
-export const requirePasswordChange = ({
- screen_names: nicknames,
- credentials,
-}) =>
- promisedRequest({
- url: REQUIRE_PASSWORD_CHANGE_URL,
- credentials,
- method: 'PATCH',
- payload: {
- nicknames,
- },
- })
-
-export const disableMFA = ({ screen_name: nickname, credentials }) =>
- promisedRequest({
- url: DISABLE_MFA_URL,
- credentials,
- method: 'PUT',
- payload: {
- nickname,
- },
- })
-
-export const listStatuses = ({ opts, credentials }) =>
- promisedRequest({
- url: LIST_STATUSES_URL(opts),
- credentials,
- method: 'GET',
- })
-
-export const changeStatusScope = ({
- opts: { id, sensitive, visibility },
- credentials,
-}) => {
- var payload = {}
- if (typeof sensitive !== 'undefined') {
- payload['sensitive'] = sensitive
- }
- if (typeof visibility !== 'undefined') {
- payload['visibility'] = visibility
- }
-
- return promisedRequest({
- url: CHANGE_STATUS_SCOPE_URL(id),
- credentials,
- method: 'PUT',
- payload,
- })
-}
-
-export const announcementToPayload = ({
- content,
- startsAt,
- endsAt,
- allDay,
-}) => {
- const payload = { content }
-
- if (typeof startsAt !== 'undefined') {
- payload.starts_at = startsAt ? new Date(startsAt).toISOString() : null
- }
-
- if (typeof endsAt !== 'undefined') {
- payload.ends_at = endsAt ? new Date(endsAt).toISOString() : null
- }
-
- if (typeof allDay !== 'undefined') {
- payload.all_day = allDay
- }
-
- return payload
-}
-
-export const postAnnouncement = ({
- credentials,
- content,
- startsAt,
- endsAt,
- allDay,
-}) =>
- promisedRequest({
- url: ANNOUNCEMENTS_URL(),
- credentials,
- method: 'POST',
- payload: announcementToPayload({ content, startsAt, endsAt, allDay }),
- })
-
-export const editAnnouncement = ({
- id,
- credentials,
- content,
- startsAt,
- endsAt,
- allDay,
-}) =>
- promisedRequest({
- url: ANNOUNCEMENTS_URL(id),
- credentials,
- method: 'PATCH',
- payload: announcementToPayload({ content, startsAt, endsAt, allDay }),
- })
-
-export const deleteAnnouncement = ({ id, credentials }) =>
- promisedRequest({
- url: ANNOUNCEMENTS_URL(id),
- credentials,
- method: 'DELETE',
- })
-
-export const setReportState = ({ id, state, credentials }) => {
- return promisedRequest({
- url: REPORTS,
- credentials,
- method: 'PATCH',
- payload: {
- reports: [
- {
- id,
- state,
- },
- ],
- },
- })
-}
-
-export const getInstanceDBConfig = ({ credentials }) =>
- promisedRequest({
- url: CONFIG_URL,
- credentials,
- })
-
-export const getInstanceConfigDescriptions = ({ credentials }) =>
- promisedRequest({
- url: DESCRIPTIONS_URL,
- credentials,
- })
-
-export const getAvailableFrontends = ({ credentials }) =>
- promisedRequest({
- url: FRONTENDS_URL,
- credentials,
- })
-
-export const pushInstanceDBConfig = ({ credentials, payload }) =>
- promisedRequest({
- url: CONFIG_URL,
- method: 'POST',
- credentials,
- payload,
- })
-
-export const installFrontend = ({ credentials, payload }) =>
- promisedRequest({
- url: FRONTENDS_INSTALL_URL,
- credentials,
- method: 'POST',
- payload,
- })
-
-// Emoji packs
-export const deleteEmojiPack = ({ name }) =>
- promisedRequest({
- url: EMOJI_PACK_URL(name),
- method: 'DELETE',
- })
-
-export const reloadEmoji = ({ credentials }) =>
- promisedRequest({
- url: EMOJI_RELOAD_URL,
- method: 'POST',
- credentials,
- })
-
-export const importEmojiFromFS = ({ credentials }) =>
- promisedRequest({
- url: EMOJI_IMPORT_FS_URL,
- credentials,
- })
-
-export const createEmojiPack = ({ name, credentials }) =>
- promisedRequest({
- url: EMOJI_PACK_URL(name),
- method: 'POST',
- credentials,
- })
-
-export const listRemoteEmojiPacks = ({
- instance,
- page,
- pageSize,
- credentials,
-}) => {
- if (!instance.startsWith('http')) {
- instance = 'https://' + instance
- }
-
- return promisedRequest({
- url: EMOJI_PACKS_LS_REMOTE_URL(instance, page, pageSize),
- credentials,
- })
-}
-
-export const downloadRemoteEmojiPack = ({
- instance,
- packName,
- as,
- credentials,
-}) =>
- promisedRequest({
- url: EMOJI_PACKS_DL_REMOTE_URL,
- credentials,
- method: 'POST',
- payload: {
- url: instance,
- name: packName,
- as,
- },
- })
-
-export const downloadRemoteEmojiPackZIP = ({
- url,
- packName,
- file,
- credentials,
-}) => {
- const data = new FormData()
- if (file) data.set('file', file)
- if (url) data.set('url', url)
- data.set('name', packName)
-
- return promisedRequest({
- url: EMOJI_PACKS_DL_REMOTE_ZIP_URL,
- method: 'POST',
- formData: data,
- })
-}
-
-export const saveEmojiPackMetadata = ({ name, newData, credentials }) =>
- promisedRequest({
- url: EMOJI_PACK_URL(name),
- credentials,
- method: 'PATCH',
- payload: { metadata: newData },
- })
-
-export const addNewEmojiFile = ({ packName, file, shortcode, filename }) => {
- const data = new FormData()
- if (filename.trim() !== '') {
- data.set('filename', filename)
- }
- if (shortcode.trim() !== '') {
- data.set('shortcode', shortcode)
- }
- data.set('file', file)
-
- return promisedRequest({
- url: EMOJI_UPDATE_FILE_URL(packName),
- method: 'POST',
- formData: data,
- })
-}
-
-export const updateEmojiFile = ({
- packName,
- shortcode,
- newShortcode,
- newFilename,
- credentials,
- force,
-}) =>
- promisedRequest({
- url: EMOJI_UPDATE_FILE_URL(packName),
- credentials,
- method: 'PATCH',
- payload: {
- shortcode,
- new_shortcode: newShortcode,
- new_filename: newFilename,
- force,
- },
- })
-
-export const deleteEmojiFile = ({ packName, shortcode }) =>
- promisedRequest({
- url: `${EMOJI_UPDATE_FILE_URL(packName)}&shortcode=${shortcode}`,
- method: 'DELETE',
- })
diff --git a/src/api/chats.js b/src/api/chats.js
deleted file mode 100644
index 51e83c74f..000000000
--- a/src/api/chats.js
+++ /dev/null
@@ -1,87 +0,0 @@
-import { paramsString, promisedRequest } from './helpers.js'
-
-import { parseChat } from 'src/services/entity_normalizer/entity_normalizer.service.js'
-
-const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats'
-const PLEROMA_CHAT_URL = (id) => `/api/v1/pleroma/chats/by-account-id/${id}`
-const PLEROMA_CHAT_MESSAGES_URL = (id, { maxId, sinceId, limit }) =>
- `/api/v1/pleroma/chats/${id}/messages${paramsString({ maxId, sinceId, limit })}`
-const PLEROMA_CHAT_READ_URL = (id) => `/api/v1/pleroma/chats/${id}/read`
-const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) =>
- `/api/v1/pleroma/chats/${chatId}/messages/${messageId}`
-
-export const chats = ({ credentials }) =>
- promisedRequest({
- url: PLEROMA_CHATS_URL,
- credentials,
- }).then(({ data }) => ({
- chatList: data.map(parseChat).filter((c) => c),
- }))
-
-export const getOrCreateChat = ({ accountId, credentials }) =>
- promisedRequest({
- url: PLEROMA_CHAT_URL(accountId),
- method: 'POST',
- credentials,
- })
-
-export const chatMessages = ({
- id,
- credentials,
- maxId,
- sinceId,
- limit = 20,
-}) => {
- return promisedRequest({
- url: PLEROMA_CHAT_MESSAGES_URL(id, { maxId, sinceId, limit }),
- method: 'GET',
- credentials,
- })
-}
-
-export const sendChatMessage = ({
- id,
- content,
- mediaId = null,
- idempotencyKey,
- credentials,
-}) => {
- const payload = {
- content,
- }
-
- if (mediaId) {
- payload.media_id = mediaId
- }
-
- const headers = {}
-
- if (idempotencyKey) {
- headers['idempotency-key'] = idempotencyKey
- }
-
- return promisedRequest({
- url: PLEROMA_CHAT_MESSAGES_URL(id),
- method: 'POST',
- payload,
- credentials,
- headers,
- })
-}
-
-export const readChat = ({ id, lastReadId, credentials }) =>
- promisedRequest({
- url: PLEROMA_CHAT_READ_URL(id),
- method: 'POST',
- payload: {
- last_read_id: lastReadId,
- },
- credentials,
- })
-
-export const deleteChatMessage = ({ chatId, messageId, credentials }) =>
- promisedRequest({
- url: PLEROMA_DELETE_CHAT_MESSAGE_URL(chatId, messageId),
- method: 'DELETE',
- credentials,
- })
diff --git a/src/api/helpers.js b/src/api/helpers.js
deleted file mode 100644
index f965750c5..000000000
--- a/src/api/helpers.js
+++ /dev/null
@@ -1,140 +0,0 @@
-import { snakeCase } from 'lodash'
-
-import { StatusCodeError } from 'src/services/errors/errors'
-
-export const paramsString = (params = {}) => {
- if (params == null || params === undefined) return ''
-
- if (typeof params !== 'object' || Array.isArray(params)) {
- throw new Error('Params are not an object!')
- }
-
- const entries = (() => {
- if (params instanceof Map) {
- return params.entries()
- } else {
- return Object.entries(params)
- }
- })()
-
- if (entries.length === 0) return ''
-
- const arrays = []
- const nonArrays = []
-
- entries.forEach(([k, v]) => {
- if (v == null) return // Drop nulls
- if (
- (typeof v === 'object' && !Array.isArray(v)) ||
- typeof v === 'function'
- ) {
- throw new Error('Param cannot be non-primitive!')
- }
- if (Array.isArray(v)) {
- arrays.push([k, v])
- } else {
- nonArrays.push([k, v])
- }
- })
-
- arrays.forEach(([k, array]) => {
- array.forEach((v) => {
- if (
- typeof v === 'object' ||
- typeof v === 'function' ||
- typeof v === 'undefined'
- )
- throw new Error('Array param cannot contain non-primitives!')
- })
- })
-
- return (
- '?' +
- [
- ...nonArrays.map(([k, v]) => [snakeCase(k), v]),
- // turning [a,[1,2,3]] into [[a[],1],[a[],2],[a[],3]]
- ...arrays.reduce(
- (acc, [k, arrayValue]) => [
- ...acc,
- ...arrayValue.map((v) => [snakeCase(k) + '[]', v]),
- ],
- [],
- ),
- ]
- .map(([k, v]) => `${k}=${window.encodeURIComponent(v)}`)
- .join('&')
- )
-}
-
-export const promisedRequest = async ({
- method,
- url,
- payload,
- formData,
- cache,
- credentials,
- headers = {},
-}) => {
- const options = {
- method,
- credentials: 'same-origin',
- headers: {
- Accept: 'application/json',
- ...headers,
- },
- }
-
- if (!formData) {
- options.headers['Content-Type'] = 'application/json'
- }
-
- if (cache) {
- options.cache = cache
- }
-
- if (formData || payload) {
- options.body = formData || JSON.stringify(payload)
- }
-
- if (credentials) {
- options.headers = {
- ...options.headers,
- ...authHeaders(credentials),
- }
- }
-
- const response = await fetch(url, options)
- const data = await (async () => {
- const [contentType] = response.headers
- .get('content-type')
- .split(';')
- .map((x) => x.toLowerCase().trim())
- const contentLength = parseInt(response.headers.get('content-length'))
- if (contentLength === 0) return null
-
- switch (contentType) {
- case 'text/plain':
- return await response.text()
- case 'application/json':
- return await response.json()
- default:
- return await response.bytes()
- }
- })()
-
- const { ok, status } = response
-
- if (ok) {
- return { response, status, data }
- } else {
- throw new StatusCodeError(response.status, data, { url, options }, response)
- }
-}
-
-const authHeaders = (accessToken) => {
- if (accessToken) {
- return { Authorization: `Bearer ${accessToken}` }
- } else {
- return {}
- }
-}
diff --git a/src/api/mfa.js b/src/api/mfa.js
deleted file mode 100644
index 8c1677fbe..000000000
--- a/src/api/mfa.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { promisedRequest } from './helpers.js'
-
-export const verifyOTPCode = ({
- clientId,
- clientSecret,
- instance,
- mfaToken,
- code,
-}) => {
- const formData = new window.FormData()
-
- formData.append('client_id', clientId)
- formData.append('client_secret', clientSecret)
- formData.append('mfa_token', mfaToken)
- formData.append('code', code)
- formData.append('challenge_type', 'totp')
-
- return promisedRequest({
- url: '/oauth/mfa/challenge',
- method: 'POST',
- formData,
- })
-}
-
-export const verifyRecoveryCode = ({
- clientId,
- clientSecret,
- instance,
- mfaToken,
- code,
-}) => {
- const formData = new window.FormData()
-
- formData.append('client_id', clientId)
- formData.append('client_secret', clientSecret)
- formData.append('mfa_token', mfaToken)
- formData.append('code', code)
- formData.append('challenge_type', 'recovery')
-
- return promisedRequest({
- url: `${instance}/oauth/mfa/challenge`,
- method: 'POST',
- formData,
- })
-}
diff --git a/src/api/oauth.js b/src/api/oauth.js
deleted file mode 100644
index f0fd2950d..000000000
--- a/src/api/oauth.js
+++ /dev/null
@@ -1,146 +0,0 @@
-import { paramsString, promisedRequest } from './helpers.js'
-
-const REDIRECT_URI = `${window.location.origin}/oauth-callback`
-
-export const MASTODON_APP_VERIFY_URL = '/api/v1/apps/verify_credentials'
-export const MASTODON_APP_URL = '/api/v1/apps'
-export const OAUTH_TOKEN_URL = '/oauth/token'
-export const OAUTH_MFA_CHALLENGE_URL = '/oauth/mfa/challenge'
-export const OAUTH_REVOKE_URL = '/oauth/revoke'
-
-export const createApp = () => {
- const formData = new window.FormData()
-
- formData.append('client_name', 'PleromaFE')
- formData.append('website', 'https://pleroma.social')
- formData.append('redirect_uris', REDIRECT_URI)
- formData.append('scopes', 'read write follow push admin')
-
- return promisedRequest({
- method: 'POST',
- url: MASTODON_APP_URL,
- formData,
- }).then(({ data, ...rest }) => ({
- ...rest,
- data: {
- ...data,
- clientId: data.client_id,
- clientSecret: data.client_secret,
- },
- }))
-}
-
-export const verifyAppToken = ({ credentials }) =>
- promisedRequest({
- url: MASTODON_APP_VERIFY_URL,
- credentials,
- })
-
-export const getLoginUrl = ({ instance, clientId }) => {
- const data = {
- responseType: 'code',
- clientId,
- redirectUri: REDIRECT_URI,
- scope: 'read write follow push admin',
- }
-
- return `${instance}/oauth/authorize${paramsString(data)}`
-}
-
-export const getTokenWithCredentials = ({
- clientId,
- clientSecret,
- username,
- password,
-}) => {
- const formData = new window.FormData()
-
- formData.append('client_id', clientId)
- formData.append('client_secret', clientSecret)
- formData.append('grant_type', 'password')
- formData.append('username', username)
- formData.append('password', password)
-
- return promisedRequest({
- url: OAUTH_TOKEN_URL,
- method: 'POST',
- formData,
- })
-}
-
-export const getToken = ({ clientId, clientSecret, code }) => {
- const formData = new window.FormData()
-
- formData.append('client_id', clientId)
- formData.append('client_secret', clientSecret)
- formData.append('grant_type', 'authorization_code')
- formData.append('code', code)
- formData.append('redirect_uri', `${window.location.origin}/oauth-callback`)
-
- return promisedRequest({
- url: OAUTH_TOKEN_URL,
- method: 'POST',
- formData,
- })
-}
-
-export const getClientToken = ({ clientId, clientSecret }) => {
- const formData = new window.FormData()
-
- formData.append('client_id', clientId)
- formData.append('client_secret', clientSecret)
- formData.append('grant_type', 'client_credentials')
- formData.append('redirect_uri', `${window.location.origin}/oauth-callback`)
-
- return promisedRequest({
- url: OAUTH_TOKEN_URL,
- method: 'POST',
- formData,
- })
-}
-
-export const verifyOTPCode = ({ app, mfaToken, code }) => {
- const formData = new window.FormData()
-
- formData.append('client_id', app.client_id)
- formData.append('client_secret', app.client_secret)
- formData.append('mfa_token', mfaToken)
- formData.append('code', code)
- formData.append('challenge_type', 'totp')
-
- return promisedRequest({
- url: OAUTH_MFA_CHALLENGE_URL,
- method: 'POST',
- formData,
- })
-}
-
-export const verifyRecoveryCode = ({ app, mfaToken, code }) => {
- const formData = new window.FormData()
-
- formData.append('client_id', app.client_id)
- formData.append('client_secret', app.client_secret)
- formData.append('mfa_token', mfaToken)
- formData.append('code', code)
- formData.append('challenge_type', 'recovery')
-
- return promisedRequest({
- url: OAUTH_MFA_CHALLENGE_URL,
- method: 'POST',
- formData,
- })
-}
-
-export const revokeToken = ({ app, token }) => {
- const formData = new window.FormData()
-
- formData.append('client_id', app.clientId)
- formData.append('client_secret', app.clientSecret)
- formData.append('token', token)
-
- return promisedRequest({
- url: OAUTH_REVOKE_URL,
- method: 'POST',
- formData,
- })
-}
diff --git a/src/api/public.js b/src/api/public.js
deleted file mode 100644
index e001ba749..000000000
--- a/src/api/public.js
+++ /dev/null
@@ -1,279 +0,0 @@
-import { paramsString, promisedRequest } from './helpers.js'
-import { MASTODON_USER_TIMELINE_URL } from './timelines.js'
-
-import {
- parseSource,
- parseStatus,
- parseUser,
-} from 'src/services/entity_normalizer/entity_normalizer.service.js'
-
-const MASTODON_SUGGESTIONS_URL = '/api/v1/suggestions'
-const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
-const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
-const MASTODON_PASSWORD_RESET_URL = ({ email }) =>
- `/auth/password${paramsString({ email })}`
-
-const MASTODON_FOLLOWING_URL = (
- id,
- { minId, maxId, sinceId, limit, withRelationships },
-) =>
- `/api/v1/accounts/${id}/following${paramsString({ minId, maxId, sinceId, limit, withRelationships })}`
-const MASTODON_FOLLOWERS_URL = (
- id,
- { minId, maxId, sinceId, limit, withRelationships },
-) =>
- `/api/v1/accounts/${id}/followers${paramsString({ minId, maxId, sinceId, limit, withRelationships })}`
-
-export const MASTODON_STATUS_URL = (id) => `/api/v1/statuses/${id}`
-const MASTODON_STATUS_CONTEXT_URL = (id) => `/api/v1/statuses/${id}/context`
-const MASTODON_STATUS_SOURCE_URL = (id) => `/api/v1/statuses/${id}/source`
-const MASTODON_STATUS_HISTORY_URL = (id) => `/api/v1/statuses/${id}/history`
-const MASTODON_USER_URL = '/api/v1/accounts'
-const MASTODON_USER_LOOKUP_URL = ({ acct }) =>
- `/api/v1/accounts/lookup${paramsString({ acct })}`
-const MASTODON_POLL_URL = (id = '') => `/api/v1/polls/${id}`
-const MASTODON_STATUS_FAVORITEDBY_URL = (id) =>
- `/api/v1/statuses/${id}/favourited_by`
-const MASTODON_STATUS_REBLOGGEDBY_URL = (id) =>
- `/api/v1/statuses/${id}/reblogged_by`
-const MASTODON_SEARCH_2 = ({
- q,
- resolve,
- limit,
- offset,
- following,
- type,
- withRelationships,
- accountId,
- excludeUnreviewed,
-}) =>
- `/api/v2/search${paramsString({ q, resolve, limit, offset, following, type, withRelationships, accountId, excludeUnreviewed })}`
-const MASTODON_USER_SEARCH_URL = ({ q, resolve }) =>
- `/api/v1/accounts/search${paramsString({ q, resolve })}`
-const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers'
-const PLEROMA_EMOJI_REACTIONS_URL = (id) =>
- `/api/v1/pleroma/statuses/${id}/reactions`
-const PLEROMA_SCROBBLES_URL = (id, { maxId, sinceId, minId, limit, offset }) =>
- `/api/v1/pleroma/accounts/${id}/scrobbles${paramsString({ maxId, sinceId, minId, limit, offset })}`
-
-const EMOJI_PACKS_URL = (page, pageSize) =>
- `/api/v1/pleroma/emoji/packs${paramsString({ page, pageSize })}`
-
-// Params needed:
-// nickname
-// email
-// fullname
-// password
-// password_confirm
-//
-// Optional
-// bio
-// homepage
-// location
-// token
-// language
-export const register = ({ params, credentials }) => {
- const { nickname, ...rest } = params
- return promisedRequest({
- url: MASTODON_REGISTRATION_URL,
- method: 'POST',
- credentials,
- payload: {
- nickname,
- locale: 'en_US',
- agreement: true,
- ...rest,
- },
- })
-}
-
-export const getCaptcha = () =>
- promisedRequest({
- url: '/api/pleroma/captcha',
- })
-
-export const fetchUser = ({ id, credentials }) =>
- promisedRequest({
- url: `${MASTODON_USER_URL}/${id}`,
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
-
-export const fetchUserByName = ({ name, credentials }) =>
- promisedRequest({
- url: MASTODON_USER_LOOKUP_URL({ acct: name }),
- credentials,
- })
- .then(({ data }) => data.id)
- .catch((error) => {
- if (error && error.statusCode === 404) {
- // Either the backend does not support lookup endpoint,
- // or there is no user with such name. Fallback and treat name as id.
- return name
- } else {
- throw error
- }
- })
- .then((id) => fetchUser({ id, credentials }))
-
-export const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) =>
- promisedRequest({
- url: MASTODON_FOLLOWING_URL(id, { maxId, sinceId, limit }),
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
-
-export const fetchFollowers = ({
- id,
- maxId,
- sinceId,
- limit = 20,
- credentials,
-}) =>
- promisedRequest({
- url: MASTODON_FOLLOWERS_URL(id, {
- maxId,
- sinceId,
- limit,
- withRelationships: true,
- }),
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
-
-export const fetchConversation = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_STATUS_CONTEXT_URL(id),
- credentials,
- }).then((result) => ({
- ...result,
- data: {
- ...result.data,
- ancestors: result.data.ancestors.map(parseStatus),
- descendants: result.data.descendants.map(parseStatus),
- },
- }))
-
-export const fetchStatus = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_STATUS_URL(id),
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
-
-export const fetchStatusSource = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_STATUS_SOURCE_URL(id),
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: parseSource(data) }))
-
-export const fetchStatusHistory = ({ status, credentials }) =>
- promisedRequest({
- url: MASTODON_STATUS_HISTORY_URL(status.id),
- credentials,
- }).then(({ data, ...rest }) => {
- return [...data].reverse().map((item) => {
- item.originalStatus = status
- return { ...rest, data: parseStatus(item) }
- })
- })
-
-export const listEmojiPacks = ({ page, pageSize, credentials }) =>
- promisedRequest({
- url: EMOJI_PACKS_URL(page, pageSize),
- })
-
-export const fetchPinnedStatuses = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_USER_TIMELINE_URL(id, { pinned: true }),
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseStatus) }))
-
-export const verifyCredentials = ({ credentials }) =>
- promisedRequest({
- url: MASTODON_LOGIN_URL,
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
-
-export const resetPassword = ({ email }) => {
- return promisedRequest({
- url: MASTODON_PASSWORD_RESET_URL({ email }),
- method: 'POST',
- })
-}
-
-export const suggestions = ({ credentials }) =>
- promisedRequest({
- url: MASTODON_SUGGESTIONS_URL,
- credentials,
- })
-
-export const fetchPoll = ({ pollId, credentials }) =>
- promisedRequest({
- url: MASTODON_POLL_URL(encodeURIComponent(pollId)),
- method: 'GET',
- credentials,
- })
-
-export const fetchFavoritedByUsers = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_STATUS_FAVORITEDBY_URL(id),
- method: 'GET',
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
-
-export const fetchRebloggedByUsers = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_STATUS_REBLOGGEDBY_URL(id),
- method: 'GET',
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
-
-export const fetchEmojiReactions = ({ id, credentials }) =>
- promisedRequest({
- url: PLEROMA_EMOJI_REACTIONS_URL(id),
- credentials,
- }).then(({ data, ...rest }) => ({
- ...rest,
- data: data.map((r) => {
- r.accounts = r.accounts.map(parseUser)
- return r
- }),
- }))
-
-export const searchUsers = ({ credentials, query }) =>
- promisedRequest({
- url: MASTODON_USER_SEARCH_URL({ q: query, resolve: true }),
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
-
-export const search2 = ({
- credentials,
- q,
- resolve,
- limit,
- offset,
- following,
- type,
-}) => {
- return promisedRequest({
- url: MASTODON_SEARCH_2({
- q,
- resolve,
- limit,
- offset,
- following,
- type,
- withRelationships: true,
- }),
- credentials,
- }).then(({ data, ...rest }) => {
- data.accounts = data.accounts.slice(0, limit).map((u) => parseUser(u))
- data.statuses = data.statuses.slice(0, limit).map((s) => parseStatus(s))
- return { ...rest, data }
- })
-}
-
-export const fetchKnownDomains = ({ credentials }) =>
- promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials })
-
-export const fetchScrobbles = ({ accountId, limit = 1 }) =>
- promisedRequest({
- url: PLEROMA_SCROBBLES_URL(accountId, { limit }),
- })
diff --git a/src/api/timelines.js b/src/api/timelines.js
deleted file mode 100644
index 0679b1eec..000000000
--- a/src/api/timelines.js
+++ /dev/null
@@ -1,198 +0,0 @@
-import { paramsString, promisedRequest } from './helpers.js'
-
-import {
- parseLinkHeaderPagination,
- parseNotification,
- parseStatus,
-} from 'src/services/entity_normalizer/entity_normalizer.service.js'
-
-const MASTODON_USER_HOME_TIMELINE_URL = ({
- minId,
- sinceId,
- maxId,
- limit,
- replyVisibility,
-}) =>
- `/api/v1/timelines/home${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
-const MASTODON_LIST_TIMELINE_URL = (
- id,
- { minId, sinceId, maxId, limit, replyVisibility },
-) =>
- `/api/v1/timelines/list/${id}${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
-const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = ({
- minId,
- sinceId,
- maxId,
- limit,
- replyVisibility,
-}) =>
- `/api/v1/timelines/direct${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
-const MASTODON_PUBLIC_TIMELINE = ({
- minId,
- sinceId,
- maxId,
- limit,
- replyVisibility,
- local,
- remote,
- onlyMedia,
-}) =>
- `/api/v1/timelines/public${paramsString({ minId, sinceId, maxId, limit, replyVisibility, local, remote, onlyMedia })}`
-const MASTODON_TAG_TIMELINE_URL = (
- tag,
- { minId, sinceId, maxId, limit, replyVisibility },
-) =>
- `/api/v1/timelines/tag/${tag}${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
-export const MASTODON_USER_TIMELINE_URL = (
- id,
- { minId, sinceId, maxId, limit, replyVisibility, pinned, onlyMedia },
-) =>
- `/api/v1/accounts/${id}/statuses${paramsString({ minId, sinceId, maxId, limit, replyVisibility, pinned, onlyMedia })}`
-const MASTODON_USER_FAVORITES_TIMELINE_URL = ({
- minId,
- sinceId,
- maxId,
- limit,
- replyVisibility,
-}) =>
- `/api/v1/favourites${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
-const MASTODON_BOOKMARK_TIMELINE_URL = ({
- minId,
- sinceId,
- maxId,
- limit,
- replyVisibility,
- folderId,
-}) =>
- `/api/v1/bookmarks${paramsString({ minId, sinceId, maxId, limit, replyVisibility, folderId })}`
-const PLEROMA_STATUS_QUOTES_URL = (
- id,
- { minId, sinceId, maxId, limit, replyVisibility },
-) =>
- `/api/v1/pleroma/statuses/${id}/quotes${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
-const PLEROMA_USER_FAVORITES_TIMELINE_URL = (
- id,
- { minId, sinceId, maxId, limit, replyVisibility },
-) =>
- `/api/v1/pleroma/accounts/${id}/favourites${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
-const AKKOMA_BUBBLE_TIMELINE_URL = ({
- minId,
- sinceId,
- maxId,
- limit,
- replyVisibility,
-}) =>
- `/api/v1/timelines/bubble${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
-
-const MASTODON_USER_NOTIFICATIONS_URL = ({
- minId,
- sinceId,
- maxId,
- limit,
- includeTypes,
- replyVisibility,
-}) =>
- `/api/v1/notifications${paramsString({ minId, sinceId, maxId, limit, includeTypes, replyVisibility })}`
-
-export const fetchTimeline = ({
- timeline,
- credentials,
- sinceId,
- minId,
- maxId,
- userId,
- listId,
- statusId,
- tag,
- withMuted,
- replyVisibility = 'all',
- includeTypes = [],
- bookmarkFolderId,
-}) => {
- const timelineUrls = {
- friends: MASTODON_USER_HOME_TIMELINE_URL,
- public: MASTODON_PUBLIC_TIMELINE,
- publicAndExternal: MASTODON_PUBLIC_TIMELINE,
- dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL,
- user: MASTODON_USER_TIMELINE_URL,
- media: MASTODON_USER_TIMELINE_URL,
- list: MASTODON_LIST_TIMELINE_URL,
- favorites: MASTODON_USER_FAVORITES_TIMELINE_URL,
- publicFavorites: PLEROMA_USER_FAVORITES_TIMELINE_URL,
- bookmarks: MASTODON_BOOKMARK_TIMELINE_URL,
- bubble: AKKOMA_BUBBLE_TIMELINE_URL,
- tag: MASTODON_TAG_TIMELINE_URL,
- quotes: PLEROMA_STATUS_QUOTES_URL,
-
- notifications: MASTODON_USER_NOTIFICATIONS_URL,
- }
- const urlFunc = timelineUrls[timeline]
-
- const twoArgs = new Set([
- 'user',
- 'media',
- 'list',
- 'publicFavorites',
- 'tag',
- 'quotes',
- ])
-
- const params = {
- minId,
- sinceId,
- maxId,
- limit: 20,
- }
-
- const id = (() => {
- switch (timeline) {
- case 'user':
- case 'media':
- return userId
- case 'list':
- return listId
- case 'quotes':
- return statusId
- case 'tag':
- return tag
- }
- })()
-
- const isNotifications = timeline === 'notifications'
-
- if (timeline === 'media') {
- params.onlyMedia = true
- }
- if (timeline === 'public') {
- params.local = true
- }
- if (timeline !== 'favorites' && timeline !== 'bookmarks') {
- params.withMuted = withMuted
- }
- if (replyVisibility !== 'all') {
- params.replyVisibility = replyVisibility
- }
- if (timeline === 'bookmarks' && bookmarkFolderId) {
- params.folderId = bookmarkFolderId
- }
-
- if (isNotifications && includeTypes.length > 0) {
- params.includeTypes = includeTypes
- }
-
- const url = twoArgs.has(timeline) ? urlFunc(id, params) : urlFunc(params)
- return promisedRequest({ url, credentials }).then((result) => {
- const pagination = parseLinkHeaderPagination(
- result.response.headers.get('Link'),
- {
- flakeId: timeline !== 'bookmarks' && timeline !== 'notifications',
- },
- )
-
- return {
- ...result,
- data: result.data.map(isNotifications ? parseNotification : parseStatus),
- pagination,
- }
- })
-}
diff --git a/src/api/user.js b/src/api/user.js
deleted file mode 100644
index 17e13195d..000000000
--- a/src/api/user.js
+++ /dev/null
@@ -1,921 +0,0 @@
-import { concat, last } from 'lodash'
-
-import { paramsString, promisedRequest } from './helpers.js'
-import { fetchFriends, MASTODON_STATUS_URL } from './public.js'
-
-import {
- parseAttachment,
- parseStatus,
- parseUser,
-} from 'src/services/entity_normalizer/entity_normalizer.service.js'
-
-const MUTES_IMPORT_URL = '/api/pleroma/mutes_import'
-const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import'
-const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
-const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
-const CHANGE_EMAIL_URL = '/api/pleroma/change_email'
-const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
-const MOVE_ACCOUNT_URL = '/api/pleroma/move_account'
-const ALIASES_URL = '/api/pleroma/aliases'
-const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
-const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
-
-const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa'
-const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
-
-const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp'
-const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp'
-const MFA_DISABLE_OTP_URL = '/api/pleroma/accounts/mfa/totp'
-
-const MASTODON_DISMISS_NOTIFICATION_URL = (id) =>
- `/api/v1/notifications/${id}/dismiss`
-const MASTODON_FAVORITE_URL = (id) => `/api/v1/statuses/${id}/favourite`
-const MASTODON_UNFAVORITE_URL = (id) => `/api/v1/statuses/${id}/unfavourite`
-const MASTODON_RETWEET_URL = (id) => `/api/v1/statuses/${id}/reblog`
-const MASTODON_UNRETWEET_URL = (id) => `/api/v1/statuses/${id}/unreblog`
-const MASTODON_DELETE_URL = (id) => `/api/v1/statuses/${id}`
-const MASTODON_FOLLOW_URL = (id) => `/api/v1/accounts/${id}/follow`
-const MASTODON_UNFOLLOW_URL = (id) => `/api/v1/accounts/${id}/unfollow`
-
-const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests'
-const MASTODON_APPROVE_USER_URL = (id) =>
- `/api/v1/follow_requests/${id}/authorize`
-const MASTODON_DENY_USER_URL = (id) => `/api/v1/follow_requests/${id}/reject`
-const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) =>
- `/api/v1/accounts/relationships/${paramsString({ id, withSuspended })}`
-const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists`
-export const MASTODON_LIST_URL = (id = '') => `/api/v1/lists/${id}`
-export const MASTODON_LIST_ACCOUNTS_URL = (id) => `/api/v1/lists/${id}/accounts`
-const MASTODON_USER_BLOCKS_URL = ({
- maxId,
- sinceId,
- limit,
- withRelationships,
-}) =>
- `/api/v1/blocks/${paramsString({ maxId, sinceId, limit, withRelationships })}`
-const MASTODON_USER_MUTES_URL = ({
- maxId,
- sinceId,
- limit,
- withRelationships,
-}) =>
- `/api/v1/mutes/${paramsString({ maxId, sinceId, limit, withRelationships })}`
-const MASTODON_BLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/block`
-const MASTODON_UNBLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/unblock`
-const MASTODON_MUTE_USER_URL = (id) => `/api/v1/accounts/${id}/mute`
-const MASTODON_UNMUTE_USER_URL = (id) => `/api/v1/accounts/${id}/unmute`
-const MASTODON_REMOVE_USER_FROM_FOLLOWERS = (id) =>
- `/api/v1/accounts/${id}/remove_from_followers`
-const MASTODON_USER_NOTE_URL = (id) => `/api/v1/accounts/${id}/note`
-const MASTODON_BOOKMARK_STATUS_URL = (id) => `/api/v1/statuses/${id}/bookmark`
-const MASTODON_UNBOOKMARK_STATUS_URL = (id) =>
- `/api/v1/statuses/${id}/unbookmark`
-const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
-const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
-const MASTODON_VOTE_URL = (id) => `/api/v1/polls/${id}/votes`
-const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials'
-const MASTODON_REPORT_USER_URL = '/api/v1/reports'
-const MASTODON_PIN_OWN_STATUS = (id) => `/api/v1/statuses/${id}/pin`
-const MASTODON_UNPIN_OWN_STATUS = (id) => `/api/v1/statuses/${id}/unpin`
-const MASTODON_MUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/mute`
-const MASTODON_UNMUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/unmute`
-const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks'
-const MASTODON_ANNOUNCEMENTS_URL = '/api/v1/announcements'
-const MASTODON_ANNOUNCEMENTS_DISMISS_URL = (id) =>
- `/api/v1/announcements/${id}/dismiss`
-const PLEROMA_EMOJI_REACT_URL = (id, emoji) =>
- `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
-const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) =>
- `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
-const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
-const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders'
-const PLEROMA_BOOKMARK_FOLDER_URL = (id) =>
- `/api/v1/pleroma/bookmark_folders/${id}`
-
-// #Posts
-export const favorite = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_FAVORITE_URL(id),
- method: 'POST',
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
-
-export const unfavorite = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_UNFAVORITE_URL(id),
- method: 'POST',
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
-
-export const retweet = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_RETWEET_URL(id),
- method: 'POST',
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
-
-export const unretweet = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_UNRETWEET_URL(id),
- method: 'POST',
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
-
-export const reactWithEmoji = ({ id, emoji, credentials }) =>
- promisedRequest({
- url: PLEROMA_EMOJI_REACT_URL(id, emoji),
- method: 'PUT',
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
-
-export const unreactWithEmoji = ({ id, emoji, credentials }) =>
- promisedRequest({
- url: PLEROMA_EMOJI_UNREACT_URL(id, emoji),
- method: 'DELETE',
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
-
-export const bookmarkStatus = ({ id, credentials, ...options }) =>
- promisedRequest({
- url: MASTODON_BOOKMARK_STATUS_URL(id),
- credentials,
- method: 'POST',
- payload: {
- folder_id: options.folder_id,
- },
- })
-
-export const unbookmarkStatus = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_UNBOOKMARK_STATUS_URL(id),
- credentials,
- method: 'POST',
- })
-
-export const pinOwnStatus = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_PIN_OWN_STATUS(id),
- credentials,
- method: 'POST',
- }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
-
-export const unpinOwnStatus = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_UNPIN_OWN_STATUS(id),
- credentials,
- method: 'POST',
- }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
-
-export const muteConversation = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_MUTE_CONVERSATION(id),
- credentials,
- method: 'POST',
- }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
-
-export const unmuteConversation = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_UNMUTE_CONVERSATION(id),
- credentials,
- method: 'POST',
- }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
-
-export const vote = ({ pollId, choices, credentials }) => {
- return promisedRequest({
- url: MASTODON_VOTE_URL(encodeURIComponent(pollId)),
- method: 'POST',
- credentials,
- payload: {
- choices,
- },
- })
-}
-
-// #Posting
-export const postStatus = ({
- credentials,
- status,
- spoilerText,
- visibility,
- sensitive,
- poll,
- mediaIds = [],
- inReplyToStatusId,
- quoteId,
- contentType,
- preview,
- idempotencyKey,
-}) => {
- const form = new FormData()
- const pollOptions = poll.options || []
-
- form.append('status', status)
- form.append('source', 'Pleroma FE')
- if (spoilerText) form.append('spoiler_text', spoilerText)
- if (visibility) form.append('visibility', visibility)
- if (sensitive) form.append('sensitive', sensitive)
- if (contentType) form.append('content_type', contentType)
- mediaIds.forEach((val) => {
- form.append('media_ids[]', val)
- })
- if (pollOptions.some((option) => option !== '')) {
- const normalizedPoll = {
- expires_in: parseInt(poll.expiresIn, 10),
- multiple: poll.multiple,
- }
- Object.keys(normalizedPoll).forEach((key) => {
- form.append(`poll[${key}]`, normalizedPoll[key])
- })
-
- pollOptions.forEach((option) => {
- form.append('poll[options][]', option)
- })
- }
- if (inReplyToStatusId) {
- form.append('in_reply_to_id', inReplyToStatusId)
- }
- if (quoteId) {
- form.append('quote_id', quoteId)
- }
- if (preview) {
- form.append('preview', 'true')
- }
-
- const headers = {}
- if (idempotencyKey) {
- headers['idempotency-key'] = idempotencyKey
- }
-
- return promisedRequest({
- url: MASTODON_POST_STATUS_URL,
- formData: form,
- method: 'POST',
- credentials,
- headers,
- }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
-}
-
-export const editStatus = ({
- id,
- credentials,
- status,
- spoilerText,
- sensitive,
- poll,
- mediaIds = [],
- contentType,
-}) => {
- const form = new FormData()
- const pollOptions = poll.options || []
-
- form.append('status', status)
- if (spoilerText) form.append('spoiler_text', spoilerText)
- if (sensitive) form.append('sensitive', sensitive)
- if (contentType) form.append('content_type', contentType)
- mediaIds.forEach((val) => {
- form.append('media_ids[]', val)
- })
-
- if (pollOptions.some((option) => option !== '')) {
- const normalizedPoll = {
- expires_in: parseInt(poll.expiresIn, 10),
- multiple: poll.multiple,
- }
- Object.keys(normalizedPoll).forEach((key) => {
- form.append(`poll[${key}]`, normalizedPoll[key])
- })
-
- pollOptions.forEach((option) => {
- form.append('poll[options][]', option)
- })
- }
-
- return promisedRequest({
- url: MASTODON_STATUS_URL(id),
- formData: form,
- method: 'PUT',
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
-}
-
-export const deleteStatus = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_DELETE_URL(id),
- credentials,
- method: 'DELETE',
- })
-
-export const uploadMedia = ({ formData, credentials }) =>
- promisedRequest({
- url: MASTODON_MEDIA_UPLOAD_URL,
- formData,
- method: 'POST',
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: parseAttachment(data) }))
-
-export const setMediaDescription = ({ id, description, credentials }) =>
- promisedRequest({
- url: `${MASTODON_MEDIA_UPLOAD_URL}/${id}`,
- method: 'PUT',
- credentials,
- payload: {
- description,
- },
- }).then(({ data, ...rest }) => ({ ...rest, data: parseAttachment(data) }))
-
-// #Notifications
-export const dismissNotification = ({ credentials, id }) =>
- promisedRequest({
- url: MASTODON_DISMISS_NOTIFICATION_URL(id),
- method: 'POST',
- payload: { id },
- credentials,
- })
-
-export const markNotificationsAsSeen = ({
- id,
- credentials,
- single = false,
-}) => {
- const formData = new FormData()
-
- if (single) {
- formData.append('id', id)
- } else {
- formData.append('max_id', id)
- }
-
- return promisedRequest({
- url: NOTIFICATION_READ_URL,
- formData,
- credentials,
- method: 'POST',
- })
-}
-
-// #Announcements
-export const getAnnouncements = ({ credentials }) =>
- promisedRequest({ url: MASTODON_ANNOUNCEMENTS_URL, credentials })
-
-export const dismissAnnouncement = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_ANNOUNCEMENTS_DISMISS_URL(id),
- credentials,
- method: 'POST',
- })
-
-// #Imports
-export const importMutes = ({ file, credentials }) => {
- const formData = new FormData()
- formData.append('list', file)
- return promisedRequest({
- url: MUTES_IMPORT_URL,
- formData,
- method: 'POST',
- credentials,
- }).then((response) => response.ok)
-}
-
-export const importBlocks = ({ file, credentials }) => {
- const formData = new FormData()
- formData.append('list', file)
- return promisedRequest({
- url: BLOCKS_IMPORT_URL,
- formData,
- method: 'POST',
- credentials,
- }).then((response) => response.ok)
-}
-
-export const importFollows = ({ file, credentials }) => {
- const formData = new FormData()
- formData.append('list', file)
- return promisedRequest({
- url: FOLLOW_IMPORT_URL,
- formData,
- method: 'POST',
- credentials,
- }).then((response) => response.ok)
-}
-
-export const exportFriends = ({ id, credentials }) => {
- // biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO refactor this
- return new Promise(async (resolve, reject) => {
- try {
- let friends = []
- let more = true
- while (more) {
- const maxId = friends.length > 0 ? last(friends).id : undefined
- const users = await fetchFriends({
- id,
- maxId,
- credentials,
- withRelationships: true,
- })
- friends = concat(friends, users)
- if (users.length === 0) {
- more = false
- }
- }
- resolve(friends)
- } catch (err) {
- reject(err)
- }
- })
-}
-
-// #Profile settings
-export const updateNotificationSettings = ({ credentials, settings }) => {
- return promisedRequest({
- url: NOTIFICATION_SETTINGS_URL,
- credentials,
- method: 'PUT',
- payload: settings,
- })
-}
-
-export const updateProfileImages = ({
- credentials,
- avatar = null,
- avatarName = null,
- banner = null,
- background = null,
-}) => {
- const form = new FormData()
- if (avatar !== null) {
- if (avatarName !== null) {
- form.append('avatar', avatar, avatarName)
- } else {
- form.append('avatar', avatar)
- }
- }
- if (banner !== null) form.append('header', banner)
- if (background !== null) form.append('pleroma_background_image', background)
- return promisedRequest({
- url: MASTODON_PROFILE_UPDATE_URL,
- credentials,
- method: 'PATCH',
- formData: form,
- }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
-}
-
-export const updateProfile = ({ credentials, params }) => {
- const formData = new FormData()
-
- for (const name in params) {
- if (name === 'fields_attributes') {
- params[name].forEach((param, i) => {
- formData.append(name + `[${i}][name]`, param.name)
- formData.append(name + `[${i}][value]`, param.value)
- })
- } else {
- if (typeof params[name] === 'object') {
- console.warn(
- 'Object detected in updateProfile API call. This will not work, use updateProfileJSON instead.',
- )
- console.warn('Object:\n' + JSON.stringify(params[name], null, 2))
- }
- formData.append(name, params[name])
- }
- }
-
- return promisedRequest({
- url: MASTODON_PROFILE_UPDATE_URL,
- credentials,
- method: 'PATCH',
- formData,
- }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
-}
-
-export const updateProfileJSON = ({ credentials, params }) =>
- promisedRequest({
- url: MASTODON_PROFILE_UPDATE_URL,
- credentials,
- payload: params,
- method: 'PATCH',
- }).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
-
-export const changeEmail = ({ credentials, email, password }) => {
- const form = new FormData()
-
- form.append('email', email)
- form.append('password', password)
-
- return promisedRequest({
- url: CHANGE_EMAIL_URL,
- formData: form,
- method: 'POST',
- credentials,
- })
-}
-
-export const moveAccount = ({ credentials, password, targetAccount }) => {
- const form = new FormData()
-
- form.append('password', password)
- form.append('target_account', targetAccount)
-
- return promisedRequest({
- url: MOVE_ACCOUNT_URL,
- formData: form,
- method: 'POST',
- credentials,
- })
-}
-
-export const changePassword = ({
- credentials,
- password,
- newPassword,
- newPasswordConfirmation,
-}) => {
- const form = new FormData()
-
- form.append('password', password)
- form.append('new_password', newPassword)
- form.append('new_password_confirmation', newPasswordConfirmation)
-
- return promisedRequest({
- url: CHANGE_PASSWORD_URL,
- formData: form,
- method: 'POST',
- credentials,
- })
-}
-
-// #MFA
-export const settingsMFA = ({ credentials }) =>
- promisedRequest({
- url: MFA_SETTINGS_URL,
- credentials,
- method: 'GET',
- })
-
-export const mfaDisableOTP = ({ credentials, password }) => {
- const form = new FormData()
-
- form.append('password', password)
-
- return promisedRequest({
- url: MFA_DISABLE_OTP_URL,
- formData: form,
- method: 'DELETE',
- credentials,
- })
-}
-
-export const mfaConfirmOTP = ({ credentials, password, token }) => {
- const form = new FormData()
-
- form.append('password', password)
- form.append('code', token)
-
- return promisedRequest({
- url: MFA_CONFIRM_OTP_URL,
- formData: form,
- credentials,
- method: 'POST',
- })
-}
-export const mfaSetupOTP = ({ credentials }) =>
- promisedRequest({
- url: MFA_SETUP_OTP_URL,
- credentials,
- method: 'GET',
- })
-export const generateMfaBackupCodes = ({ credentials }) =>
- promisedRequest({
- url: MFA_BACKUP_CODES_URL,
- credentials,
- method: 'GET',
- })
-
-// #Aliases
-export const addAlias = ({ credentials, alias }) =>
- promisedRequest({
- url: ALIASES_URL,
- method: 'PUT',
- credentials,
- payload: { alias },
- })
-
-export const deleteAlias = ({ credentials, alias }) =>
- promisedRequest({
- url: ALIASES_URL,
- method: 'DELETE',
- credentials,
- payload: { alias },
- })
-
-export const listAliases = ({ credentials }) =>
- promisedRequest({
- url: ALIASES_URL,
- method: 'GET',
- credentials,
- params: {
- _cacheBooster: new Date().getTime(),
- },
- })
-
-// User manipulation
-export const fetchUserRelationship = ({ id, withSuspended, credentials }) =>
- promisedRequest({
- url: MASTODON_USER_RELATIONSHIPS_URL({ id, withSuspended }),
- credentials,
- })
-
-export const followUser = ({ id, credentials, ...options }) => {
- const payload = {}
-
- if (options.reblogs !== undefined) {
- payload.reblogs = options.reblogs
- }
-
- if (options.notify !== undefined) {
- payload.notify = options.notify
- }
-
- return promisedRequest({
- url: MASTODON_FOLLOW_URL(id),
- payload,
- credentials,
- method: 'POST',
- })
-}
-
-export const unfollowUser = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_UNFOLLOW_URL(id),
- credentials,
- method: 'POST',
- })
-export const fetchUserInLists = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_USER_IN_LISTS(id),
- credentials,
- })
-
-export const removeUserFromFollowers = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_REMOVE_USER_FROM_FOLLOWERS(id),
- credentials,
- method: 'POST',
- })
-
-export const fetchFollowRequests = ({ credentials }) =>
- promisedRequest({
- url: MASTODON_FOLLOW_REQUESTS_URL,
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
-
-export const approveUser = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_APPROVE_USER_URL(id),
- credentials,
- method: 'POST',
- })
-
-export const denyUser = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_DENY_USER_URL(id),
- credentials,
- method: 'POST',
- })
-
-export const editUserNote = ({ id, credentials, comment }) =>
- promisedRequest({
- url: MASTODON_USER_NOTE_URL(id),
- credentials,
- payload: {
- comment,
- },
- method: 'POST',
- })
-
-export const fetchMutes = ({ maxId, credentials }) =>
- promisedRequest({
- url: MASTODON_USER_MUTES_URL({ maxId, withRelationships: true }),
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
-
-export const muteUser = ({ id, expiresIn, credentials }) => {
- const payload = {}
- if (expiresIn) {
- payload.expires_in = expiresIn
- }
-
- return promisedRequest({
- url: MASTODON_MUTE_USER_URL(id),
- credentials,
- method: 'POST',
- payload,
- })
-}
-
-export const unmuteUser = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_UNMUTE_USER_URL(id),
- credentials,
- method: 'POST',
- })
-
-export const fetchBlocks = ({ maxId, credentials }) =>
- promisedRequest({
- url: MASTODON_USER_BLOCKS_URL({ maxId, withRelationships: true }),
- credentials,
- }).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
-
-export const blockUser = ({ id, expiresIn, credentials }) => {
- const payload = {}
- if (expiresIn) {
- payload.duration = expiresIn
- }
-
- return promisedRequest({
- url: MASTODON_BLOCK_USER_URL(id),
- credentials,
- method: 'POST',
- payload,
- })
-}
-
-export const unblockUser = ({ id, credentials }) =>
- promisedRequest({
- url: MASTODON_UNBLOCK_USER_URL(id),
- credentials,
- method: 'POST',
- })
-
-export const reportUser = ({
- credentials,
- userId,
- statusIds,
- comment,
- forward,
-}) =>
- promisedRequest({
- url: MASTODON_REPORT_USER_URL,
- method: 'POST',
- payload: {
- account_id: userId,
- status_ids: statusIds,
- comment,
- forward,
- },
- credentials,
- })
-
-// #Domain mutes
-export const fetchDomainMutes = ({ credentials }) =>
- promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials })
-
-export const muteDomain = ({ domain, credentials }) =>
- promisedRequest({
- url: MASTODON_DOMAIN_BLOCKS_URL,
- method: 'POST',
- payload: { domain },
- credentials,
- })
-
-export const unmuteDomain = ({ domain, credentials }) =>
- promisedRequest({
- url: MASTODON_DOMAIN_BLOCKS_URL,
- method: 'DELETE',
- payload: { domain },
- credentials,
- })
-
-// #Backups
-export const addBackup = ({ credentials }) =>
- promisedRequest({
- url: PLEROMA_BACKUP_URL,
- method: 'POST',
- credentials,
- })
-
-export const listBackups = ({ credentials }) =>
- promisedRequest({
- url: PLEROMA_BACKUP_URL,
- method: 'GET',
- credentials,
- params: {
- _cacheBooster: new Date().getTime(),
- },
- })
-
-// #OAuth
-export const fetchOAuthTokens = ({ credentials }) =>
- promisedRequest({
- url: '/api/oauth_tokens.json',
- credentials,
- })
-
-export const revokeOAuthToken = ({ id, credentials }) =>
- promisedRequest({
- url: `/api/oauth_tokens/${id}`,
- credentials,
- method: 'DELETE',
- })
-
-// #Lists
-export const fetchLists = ({ credentials }) =>
- promisedRequest({
- url: MASTODON_LIST_URL(),
- credentials,
- })
-
-export const createList = ({ title, credentials }) =>
- promisedRequest({
- url: MASTODON_LIST_URL(),
- credentials,
- method: 'POST',
- payload: { title },
- })
-
-export const getList = ({ listId, credentials }) =>
- promisedRequest({
- url: MASTODON_LIST_URL(listId),
- credentials,
- })
-
-export const updateList = ({ listId, title, credentials }) =>
- promisedRequest({
- url: MASTODON_LIST_URL(listId),
-
- credentials,
- method: 'PUT',
- payload: { title },
- })
-
-export const getListAccounts = ({ listId, credentials }) =>
- promisedRequest({
- url: MASTODON_LIST_ACCOUNTS_URL(listId),
- credentials,
- }).then((data) => data.map(({ id }) => id))
-
-export const addAccountsToList = ({ listId, accountIds, credentials }) =>
- promisedRequest({
- url: MASTODON_LIST_ACCOUNTS_URL(listId),
- credentials,
- method: 'POST',
- payload: { account_ids: accountIds },
- })
-
-export const removeAccountsFromList = ({ listId, accountIds, credentials }) =>
- promisedRequest({
- url: MASTODON_LIST_ACCOUNTS_URL(listId),
- credentials,
- method: 'DELETE',
- payload: { account_ids: accountIds },
- })
-
-export const deleteList = ({ listId, credentials }) =>
- promisedRequest({
- url: MASTODON_LIST_URL(listId),
- method: 'DELETE',
- credentials,
- })
-
-// #Bookmarks
-export const fetchBookmarkFolders = ({ credentials }) =>
- promisedRequest({
- url: PLEROMA_BOOKMARK_FOLDERS_URL,
- credentials,
- })
-
-export const createBookmarkFolder = ({ name, emoji, credentials }) =>
- promisedRequest({
- url: PLEROMA_BOOKMARK_FOLDERS_URL,
- credentials,
- method: 'POST',
- payload: { name, emoji },
- })
-
-export const updateBookmarkFolder = ({ folderId, name, emoji, credentials }) =>
- promisedRequest({
- url: PLEROMA_BOOKMARK_FOLDER_URL(folderId),
- credentials,
- method: 'PATCH',
- payload: { name, emoji },
- })
-
-export const deleteBookmarkFolder = ({ folderId, credentials }) =>
- promisedRequest({
- url: PLEROMA_BOOKMARK_FOLDER_URL(folderId),
- method: 'DELETE',
- credentials,
- })
-
-// #So long and thanks for all the fish
-export const deleteAccount = ({ credentials, password }) => {
- const formData = new FormData()
-
- formData.append('password', password)
-
- return promisedRequest({
- url: DELETE_ACCOUNT_URL,
- formData,
- method: 'POST',
- credentials,
- })
-}
diff --git a/src/api/websocket.js b/src/api/websocket.js
deleted file mode 100644
index d952372e5..000000000
--- a/src/api/websocket.js
+++ /dev/null
@@ -1,176 +0,0 @@
-import { paramsString } from './helpers.js'
-
-import {
- parseChat,
- parseNotification,
- parseStatus,
-} from 'src/services/entity_normalizer/entity_normalizer.service.js'
-
-const MASTODON_STREAMING = ({ accessToken, stream }) =>
- `/api/v1/streaming${paramsString({ accessToken, stream })}`
-
-export const getMastodonSocketURI = ({ credentials, stream }) => {
- return MASTODON_STREAMING({ accessToken: credentials, stream })
-}
-
-const MASTODON_STREAMING_EVENTS = new Set([
- 'update',
- 'notification',
- 'delete',
- 'filters_changed',
- 'status.update',
-])
-
-const PLEROMA_STREAMING_EVENTS = new Set([
- 'pleroma:chat_update',
- 'pleroma:respond',
-])
-
-// A thin wrapper around WebSocket API that allows adding a pre-processor to it
-// Uses EventTarget and a CustomEvent to proxy events
-export const ProcessedWS = ({
- url,
- preprocessor = handleMastoWS,
- id = 'Unknown',
- credentials,
-}) => {
- const eventTarget = new EventTarget()
- const socket = new WebSocket(url)
- if (!socket) throw new Error(`Failed to create socket ${id}`)
- const proxy = (original, eventName, processor = (a) => a) => {
- original.addEventListener(eventName, (eventData) => {
- eventTarget.dispatchEvent(
- new CustomEvent(eventName, { detail: processor(eventData) }),
- )
- })
- }
- socket.addEventListener('open', (wsEvent) => {
- console.debug(`[WS][${id}] Socket connected`, wsEvent)
- if (credentials) {
- socket.send(
- JSON.stringify({
- type: 'pleroma:authenticate',
- token: credentials,
- }),
- )
- }
- })
- socket.addEventListener('error', (wsEvent) => {
- console.debug(`[WS][${id}] Socket errored`, wsEvent)
- })
- socket.addEventListener('close', (wsEvent) => {
- console.debug(
- `[WS][${id}] Socket disconnected with code ${wsEvent.code}`,
- wsEvent,
- )
- })
- // Commented code reason: very spammy, uncomment to enable message debug logging
- /*
- socket.addEventListener('message', (wsEvent) => {
- console.debug(
- `[WS][${id}] Message received`,
- wsEvent
- )
- })
- /**/
-
- const onAuthenticated = () => {
- eventTarget.dispatchEvent(new CustomEvent('pleroma:authenticated'))
- }
-
- proxy(socket, 'open')
- proxy(socket, 'close')
- proxy(socket, 'message', (event) => preprocessor(event, { onAuthenticated }))
- proxy(socket, 'error')
-
- // 1000 = Normal Closure
- eventTarget.close = () => {
- socket.close(1000, 'Shutting down socket')
- }
- eventTarget.getState = () => socket.readyState
- eventTarget.subscribe = (stream, args = {}) => {
- console.debug(`[WS][${id}] Subscribing to stream ${stream} with args`, args)
- socket.send(
- JSON.stringify({
- type: 'subscribe',
- stream,
- ...args,
- }),
- )
- }
- eventTarget.unsubscribe = (stream, args = {}) => {
- console.debug(
- `[WS][${id}] Unsubscribing from stream ${stream} with args`,
- args,
- )
- socket.send(
- JSON.stringify({
- type: 'unsubscribe',
- stream,
- ...args,
- }),
- )
- }
-
- return eventTarget
-}
-
-export const handleMastoWS = (
- wsEvent,
- {
- onAuthenticated = () => {
- /* no-op */
- },
- } = {},
-) => {
- const { data } = wsEvent
- if (!data) return
- const parsedEvent = JSON.parse(data)
- const { event, payload } = parsedEvent
- if (
- MASTODON_STREAMING_EVENTS.has(event) ||
- PLEROMA_STREAMING_EVENTS.has(event)
- ) {
- // MastoBE and PleromaBE both send payload for delete as a PLAIN string
- if (event === 'delete') {
- return { event, id: payload }
- }
- const data = payload ? JSON.parse(payload) : null
- if (event === 'pleroma:respond') {
- if (data.type === 'pleroma:authenticate') {
- if (data.result === 'success') {
- console.debug('[WS] Successfully authenticated')
- onAuthenticated()
- } else {
- if (data.error === 'already_authenticated') {
- onAuthenticated()
- } else {
- console.error('[WS] Unable to authenticate:', data.error)
- wsEvent.target.close()
- }
- }
- }
- return null
- } else if (event === 'update') {
- return { event, status: parseStatus(data) }
- } else if (event === 'status.update') {
- return { event, status: parseStatus(data) }
- } else if (event === 'notification') {
- return { event, notification: parseNotification(data) }
- } else if (event === 'pleroma:chat_update') {
- return { event, chatUpdate: parseChat(data) }
- }
- } else {
- console.warn('Unknown event', wsEvent)
- return null
- }
-}
-
-export const WSConnectionStatus = Object.freeze({
- JOINED: 1,
- CLOSED: 2,
- ERROR: 3,
- DISABLED: 4,
- STARTING: 5,
- STARTING_INITIAL: 6,
-})
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 4ccfa4d41..65c4c9205 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -1,51 +1,29 @@
/* global process */
-
-import vClickOutside from 'click-outside-vue3'
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
+import vClickOutside from 'click-outside-vue3'
import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
-import RichContent from 'src/components/rich_content/rich_content.jsx'
-import Status from 'src/components/status/status.vue'
-import StillImage from 'src/components/still-image/still-image.vue'
-
-import { config } from '@fortawesome/fontawesome-svg-core'
-import {
- FontAwesomeIcon,
- FontAwesomeLayers,
-} from '@fortawesome/vue-fontawesome'
-
+import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
+import { config } from '@fortawesome/fontawesome-svg-core';
config.autoAddCss = false
import App from '../App.vue'
-import FaviconService from '../services/favicon_service/favicon_service.js'
-import { applyStyleConfig } from '../services/style_setter/style_setter.js'
-import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
-import {
- windowHeight,
- windowWidth,
-} from '../services/window_utils/window_utils'
import routes from './routes'
-
-import { useAuthFlowStore } from 'src/stores/auth_flow'
-import { useEmojiStore } from 'src/stores/emoji.js'
-import { useI18nStore } from 'src/stores/i18n'
-import { useInstanceStore } from 'src/stores/instance.js'
-import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
-import { useInterfaceStore } from 'src/stores/interface.js'
-import { useLocalConfigStore } from 'src/stores/local_config.js'
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-import { useOAuthStore } from 'src/stores/oauth.js'
-import { useSyncConfigStore } from 'src/stores/sync_config.js'
-import { useUserHighlightStore } from 'src/stores/user_highlight.js'
-
import VBodyScrollLock from 'src/directives/body_scroll_lock'
-import {
- INSTANCE_DEFAULT_CONFIG_DEFINITIONS,
- INSTANCE_IDENTITY_DEFAULT_DEFINITIONS,
- INSTANCE_IDENTIY_EXTERNAL,
-} from 'src/modules/default_config_state.js'
+
+import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
+import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
+import { applyConfig } from '../services/style_setter/style_setter.js'
+import FaviconService from '../services/favicon_service/favicon_service.js'
+import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
+
+import { useOAuthStore } from 'src/stores/oauth'
+import { useI18nStore } from 'src/stores/i18n'
+import { useInterfaceStore } from 'src/stores/interface'
+import { useAnnouncementsStore } from 'src/stores/announcements'
+import { useAuthFlowStore } from 'src/stores/auth_flow'
let staticInitialResults = null
@@ -54,9 +32,7 @@ const parsedInitialResults = () => {
return null
}
if (!staticInitialResults) {
- staticInitialResults = JSON.parse(
- document.getElementById('initial-results').textContent,
- )
+ staticInitialResults = JSON.parse(document.getElementById('initial-results').textContent)
}
return staticInitialResults
}
@@ -78,7 +54,7 @@ const preloadFetch = async (request) => {
return {
ok: true,
json: () => requestData,
- text: () => requestData,
+ text: () => requestData
}
}
@@ -87,38 +63,20 @@ const getInstanceConfig = async ({ store }) => {
const res = await preloadFetch('/api/v1/instance')
if (res.ok) {
const data = await res.json()
- const textLimit = data.max_toot_chars
+ const textlimit = data.max_toot_chars
const vapidPublicKey = data.pleroma.vapid_public_key
- useInstanceCapabilitiesStore().set(
- 'pleromaExtensionsAvailable',
- data.pleroma,
- )
- useInstanceStore().set({
- path: 'limits.textLimit',
- value: textLimit,
- })
- useInstanceStore().set({
- path: 'accountApprovalRequired',
- value: data.approval_required,
- })
- useInstanceStore().set({
- path: 'birthdayRequired',
- value: !!data.pleroma?.metadata.birthday_required,
- })
- useInstanceStore().set({
- path: 'birthdayMinAge',
- value: data.pleroma?.metadata.birthday_min_age || 0,
- })
+ store.dispatch('setInstanceOption', { name: 'pleromaExtensionsAvailable', value: data.pleroma })
+ store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
+ store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
+ store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma?.metadata.birthday_required })
+ store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma?.metadata.birthday_min_age || 0 })
if (vapidPublicKey) {
- useInstanceStore().set({
- path: 'vapidPublicKey',
- value: vapidPublicKey,
- })
+ store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
}
} else {
- throw res
+ throw (res)
}
} catch (error) {
console.error('Could not load instance config, potentially fatal')
@@ -135,12 +93,10 @@ const getBackendProvidedConfig = async () => {
const data = await res.json()
return data.pleroma_fe
} else {
- throw res
+ throw (res)
}
} catch (error) {
- console.error(
- 'Could not load backend-provided frontend config, potentially fatal',
- )
+ console.error('Could not load backend-provided frontend config, potentially fatal')
console.error(error)
}
}
@@ -151,13 +107,11 @@ const getStaticConfig = async () => {
if (res.ok) {
return res.json()
} else {
- throw res
+ throw (res)
}
} catch (error) {
- console.warn(
- 'Failed to load static/config.json, continuing without it.',
- error,
- )
+ console.warn('Failed to load static/config.json, continuing without it.')
+ console.warn(error)
return {}
}
}
@@ -175,25 +129,51 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
config = Object.assign({}, staticConfig, apiConfig)
}
- Object.keys(INSTANCE_IDENTITY_DEFAULT_DEFINITIONS).forEach((source) => {
- if (source === 'name') return
- if (INSTANCE_IDENTIY_EXTERNAL.has(source)) return
- useInstanceStore().set({
- value:
- config[source] ?? INSTANCE_IDENTITY_DEFAULT_DEFINITIONS[source].default,
- path: `instanceIdentity.${source}`,
- })
+ const copyInstanceOption = (name) => {
+ store.dispatch('setInstanceOption', { name, value: config[name] })
+ }
+
+ copyInstanceOption('theme')
+ copyInstanceOption('style')
+ copyInstanceOption('palette')
+ copyInstanceOption('embeddedToS')
+ copyInstanceOption('nsfwCensorImage')
+ copyInstanceOption('background')
+ copyInstanceOption('hidePostStats')
+ copyInstanceOption('hideBotIndication')
+ copyInstanceOption('hideUserStats')
+ copyInstanceOption('hideFilteredStatuses')
+ copyInstanceOption('logo')
+
+ store.dispatch('setInstanceOption', {
+ name: 'logoMask',
+ value: typeof config.logoMask === 'undefined'
+ ? true
+ : config.logoMask
})
- Object.keys(INSTANCE_DEFAULT_CONFIG_DEFINITIONS).forEach((source) =>
- useInstanceStore().set({
- value:
- config[source] ?? INSTANCE_DEFAULT_CONFIG_DEFINITIONS[source].default,
- path: `prefsStorage.${source}`,
- }),
- )
-
+ store.dispatch('setInstanceOption', {
+ name: 'logoMargin',
+ value: typeof config.logoMargin === 'undefined'
+ ? 0
+ : config.logoMargin
+ })
+ copyInstanceOption('logoLeft')
useAuthFlowStore().setInitialStrategy(config.loginMethod)
+
+ copyInstanceOption('redirectRootNoLogin')
+ copyInstanceOption('redirectRootLogin')
+ copyInstanceOption('showInstanceSpecificPanel')
+ copyInstanceOption('minimalScopesMode')
+ copyInstanceOption('hideMutedPosts')
+ copyInstanceOption('collapseMessageWithSubject')
+ copyInstanceOption('scopeCopy')
+ copyInstanceOption('subjectLineBehavior')
+ copyInstanceOption('postContentType')
+ copyInstanceOption('alwaysShowSubjectInput')
+ copyInstanceOption('showFeaturesPanel')
+ copyInstanceOption('hideSitename')
+ copyInstanceOption('sidebarRight')
}
const getTOS = async ({ store }) => {
@@ -201,9 +181,9 @@ const getTOS = async ({ store }) => {
const res = await window.fetch('/static/terms-of-service.html')
if (res.ok) {
const html = await res.text()
- useInstanceStore().set({ path: 'instanceIdentity.tos', value: html })
+ store.dispatch('setInstanceOption', { name: 'tos', value: html })
} else {
- throw res
+ throw (res)
}
} catch (e) {
console.warn("Can't load TOS\n", e)
@@ -215,12 +195,9 @@ const getInstancePanel = async ({ store }) => {
const res = await preloadFetch('/instance/panel.html')
if (res.ok) {
const html = await res.text()
- useInstanceStore().set({
- path: 'instanceIdentity.instanceSpecificPanelContent',
- value: html,
- })
+ store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html })
} else {
- throw res
+ throw (res)
}
} catch (e) {
console.warn("Can't load instance panel\n", e)
@@ -232,39 +209,41 @@ const getStickers = async ({ store }) => {
const res = await window.fetch('/static/stickers.json')
if (res.ok) {
const values = await res.json()
- const stickers = (
- await Promise.all(
- Object.entries(values).map(async ([name, path]) => {
- const resPack = await window.fetch(path + 'pack.json')
- let meta = {}
- if (resPack.ok) {
- meta = await resPack.json()
- }
- return {
- pack: name,
- path,
- meta,
- }
- }),
- )
- ).sort((a, b) => {
+ const stickers = (await Promise.all(
+ Object.entries(values).map(async ([name, path]) => {
+ const resPack = await window.fetch(path + 'pack.json')
+ let meta = {}
+ if (resPack.ok) {
+ meta = await resPack.json()
+ }
+ return {
+ pack: name,
+ path,
+ meta
+ }
+ })
+ )).sort((a, b) => {
return a.meta.title.localeCompare(b.meta.title)
})
- useEmojiStore().setStickers(stickers)
+ store.dispatch('setInstanceOption', { name: 'stickers', value: stickers })
} else {
- throw res
+ throw (res)
}
} catch (e) {
console.warn("Can't load stickers\n", e)
}
}
+const getAppSecret = async ({ store }) => {
+ const oauth = useOAuthStore()
+ if (oauth.userToken) {
+ store.commit('setBackendInteractor', backendInteractorService(oauth.getToken))
+ }
+}
+
const resolveStaffAccounts = ({ store, accounts }) => {
- const nicknames = accounts.map((uri) => uri.split('/').pop())
- useInstanceStore().set({
- path: 'staffAccounts',
- value: nicknames,
- })
+ const nicknames = accounts.map(uri => uri.split('/').pop())
+ store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
}
const getNodeInfo = async ({ store }) => {
@@ -275,187 +254,97 @@ const getNodeInfo = async ({ store }) => {
const data = await res.json()
const metadata = data.metadata
const features = metadata.features
- useInstanceStore().set({
- path: 'instanceIdentity.name',
- value: metadata.nodeName,
+ store.dispatch('setInstanceOption', { name: 'name', value: metadata.nodeName })
+ store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
+ store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
+ store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
+ store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
+ store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
+ store.dispatch('setInstanceOption', {
+ name: 'pleromaCustomEmojiReactionsAvailable',
+ value:
+ features.includes('pleroma_custom_emoji_reactions') ||
+ features.includes('custom_emoji_reactions')
})
- useInstanceStore().set({
- path: 'registrationOpen',
- value: data.openRegistrations,
- })
- useInstanceCapabilitiesStore().set(
- 'mediaProxyAvailable',
- features.includes('media_proxy'),
- )
- useInstanceCapabilitiesStore().set(
- 'safeDM',
- features.includes('safe_dm_mentions'),
- )
- useInstanceCapabilitiesStore().set(
- 'shoutAvailable',
- features.includes('chat'),
- )
- useInstanceCapabilitiesStore().set(
- 'pleromaChatMessagesAvailable',
- features.includes('pleroma_chat_messages'),
- )
- useInstanceCapabilitiesStore().set(
- 'pleromaCustomEmojiReactionsAvailable',
+ store.dispatch('setInstanceOption', { name: 'pleromaBookmarkFoldersAvailable', value: features.includes('pleroma:bookmark_folders') })
+ store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
+ store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
+ store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
+ store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
+ store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
+ store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
+ store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
+ store.dispatch('setInstanceOption', { name: 'localBubbleInstances', value: metadata.localBubbleInstances ?? [] })
- features.includes('pleroma_custom_emoji_reactions') ||
- features.includes('custom_emoji_reactions'),
- )
- useInstanceCapabilitiesStore().set(
- 'pleromaBookmarkFoldersAvailable',
- features.includes('pleroma:bookmark_folders'),
- )
- useInstanceCapabilitiesStore().set(
- 'gopherAvailable',
- features.includes('gopher'),
- )
- useInstanceCapabilitiesStore().set(
- 'pollsAvailable',
- features.includes('polls'),
- )
- useInstanceCapabilitiesStore().set(
- 'editingAvailable',
- features.includes('editing'),
- )
- useInstanceCapabilitiesStore().set(
- 'mailerEnabled',
- metadata.mailerEnabled,
- )
- useInstanceCapabilitiesStore().set(
- 'quotingAvailable',
- features.includes('quote_posting'),
- )
- useInstanceCapabilitiesStore().set(
- 'groupActorAvailable',
- features.includes('pleroma:group_actors'),
- )
- useInstanceCapabilitiesStore().set(
- 'blockExpiration',
- features.includes('pleroma:block_expiration'),
- )
- useInstanceStore().set({
- path: 'localBubbleInstances',
- value: metadata.localBubbleInstances ?? [],
- })
- useInstanceCapabilitiesStore().set(
- 'localBubble',
- (metadata.localBubbleInstances ?? []).length > 0,
- )
-
- useInstanceStore().set({
- path: 'limits.pollLimits',
- value: metadata.pollLimits,
- })
const uploadLimits = metadata.uploadLimits
- useInstanceStore().set({
- path: 'limits.uploadlimit',
- value: parseInt(uploadLimits.general),
- })
- useInstanceStore().set({
- path: 'limits.avatarlimit',
- value: parseInt(uploadLimits.avatar),
- })
- useInstanceStore().set({
- path: 'limits.backgroundlimit',
- value: parseInt(uploadLimits.background),
- })
- useInstanceStore().set({
- path: 'limits.bannerlimit',
- value: parseInt(uploadLimits.banner),
- })
- useInstanceStore().set({
- path: 'limits.fieldsLimits',
- value: metadata.fieldsLimits,
- })
+ store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
+ store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadLimits.avatar) })
+ store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadLimits.background) })
+ store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadLimits.banner) })
+ store.dispatch('setInstanceOption', { name: 'fieldsLimits', value: metadata.fieldsLimits })
- useInstanceStore().set({
- path: 'restrictedNicknames',
- value: metadata.restrictedNicknames,
- })
- useInstanceCapabilitiesStore().set('postFormats', metadata.postFormats)
+ store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
+ store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
const suggestions = metadata.suggestions
- useInstanceCapabilitiesStore().set(
- 'suggestionsEnabled',
- suggestions.enabled,
- )
- // this is unused, why?
- useInstanceCapabilitiesStore().set('suggestionsWeb', suggestions.web)
+ store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled })
+ store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web })
const software = data.software
- useInstanceStore().set({
- path: 'backendVersion',
- value: software.version,
- })
- useInstanceStore().set({
- path: 'backendRepository',
- value: software.repository,
- })
+ store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
+ store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository })
const priv = metadata.private
- useInstanceStore().set({ path: 'privateMode', value: priv })
+ store.dispatch('setInstanceOption', { name: 'private', value: priv })
const frontendVersion = window.___pleromafe_commit_hash
- useInstanceStore().set({
- path: 'frontendVersion',
- value: frontendVersion,
- })
+ store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
const federation = metadata.federation
- useInstanceCapabilitiesStore().set(
- 'tagPolicyAvailable',
- typeof federation.mrf_policies === 'undefined'
+ store.dispatch('setInstanceOption', {
+ name: 'tagPolicyAvailable',
+ value: typeof federation.mrf_policies === 'undefined'
? false
- : metadata.federation.mrf_policies.includes('TagPolicy'),
- )
-
- useInstanceStore().set({
- path: 'federationPolicy',
- value: federation,
+ : metadata.federation.mrf_policies.includes('TagPolicy')
})
- useInstanceStore().set({
- path: 'federating',
- value:
- typeof federation.enabled === 'undefined' ? true : federation.enabled,
+
+ store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
+ store.dispatch('setInstanceOption', {
+ name: 'federating',
+ value: typeof federation.enabled === 'undefined'
+ ? true
+ : federation.enabled
})
const accountActivationRequired = metadata.accountActivationRequired
- useInstanceStore().set({
- path: 'accountActivationRequired',
- value: accountActivationRequired,
- })
+ store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired })
const accounts = metadata.staffAccounts
resolveStaffAccounts({ store, accounts })
} else {
- throw res
+ throw (res)
}
} catch (e) {
- console.warn('Could not load nodeinfo', e)
+ console.warn('Could not load nodeinfo')
+ console.warn(e)
}
}
const setConfig = async ({ store }) => {
// apiConfig, staticConfig
- const configInfos = await Promise.all([
- getBackendProvidedConfig({ store }),
- getStaticConfig(),
- ])
+ const configInfos = await Promise.all([getBackendProvidedConfig({ store }), getStaticConfig()])
const apiConfig = configInfos[0]
const staticConfig = configInfos[1]
+ getAppSecret({ store })
await setSettings({ store, apiConfig, staticConfig })
}
const checkOAuthToken = async ({ store }) => {
const oauth = useOAuthStore()
- if (oauth.userToken) {
- return store.dispatch('loginUser', oauth.userToken)
+ if (oauth.getUserToken) {
+ return store.dispatch('loginUser', oauth.getUserToken)
}
return Promise.resolve()
}
@@ -467,16 +356,6 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
// "Plugins are only applied to stores created after the plugins themselves, and after pinia is passed to the app, otherwise they won't be applied."
app.use(pinia)
- app.config.errorHandler = (error, instance, info) => {
- console.error(
- 'Global Vue Error Handler caught an error:',
- error,
- instance,
- info,
- )
- useInterfaceStore().setGlobalError({ error, instance, info })
- }
-
const waitForAllStoresToLoad = async () => {
// the stores that do not persist technically do not need to be awaited here,
// but that involves either hard-coding the stores in some place (prone to errors)
@@ -485,37 +364,29 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
if (process.env.NODE_ENV === 'development') {
// do some checks to avoid common errors
if (!Object.keys(allStores).length) {
- throw new Error(
- 'No stores are available. Check the code in src/boot/after_store.js',
- )
+ throw new Error('No stores are available. Check the code in src/boot/after_store.js')
}
}
await Promise.all(
- Object.entries(allStores).map(async ([name, mod]) => {
- const isStoreName = (name) => name.startsWith('use')
- if (process.env.NODE_ENV === 'development') {
- if (Object.keys(mod).filter(isStoreName).length !== 1) {
- throw new Error(
- 'Each store file must export exactly one store as a named export. Check your code in src/stores/',
- )
+ Object.entries(allStores)
+ .map(async ([name, mod]) => {
+ const isStoreName = name => name.startsWith('use')
+ if (process.env.NODE_ENV === 'development') {
+ if (Object.keys(mod).filter(isStoreName).length !== 1) {
+ throw new Error('Each store file must export exactly one store as a named export. Check your code in src/stores/')
+ }
}
- }
- const storeFuncName = Object.keys(mod).find(isStoreName)
- if (storeFuncName && typeof mod[storeFuncName] === 'function') {
- const p = mod[storeFuncName]().$persistLoaded
- if (!(p instanceof Promise)) {
- throw new Error(
- `${name} store's $persistLoaded is not a Promise. The persist plugin is not applied.`,
- )
+ const storeFuncName = Object.keys(mod).find(isStoreName)
+ if (storeFuncName && typeof mod[storeFuncName] === 'function') {
+ const p = mod[storeFuncName]().$persistLoaded
+ if (!(p instanceof Promise)) {
+ throw new Error(`${name} store's $persistLoaded is not a Promise. The persist plugin is not applied.`)
+ }
+ await p
+ } else {
+ throw new Error(`Store module ${name} does not export a 'use...' function`)
}
- await p
- } else {
- throw new Error(
- `Store module ${name} does not export a 'use...' function`,
- )
- }
- }),
- )
+ }))
}
try {
@@ -526,45 +397,30 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
}
if (storageError) {
- useInterfaceStore().pushGlobalNotice({
- messageKey: 'errors.storage_unavailable',
- level: 'error',
- })
+ useInterfaceStore().pushGlobalNotice({ messageKey: 'errors.storage_unavailable', level: 'error' })
}
useInterfaceStore().setLayoutWidth(windowWidth())
useInterfaceStore().setLayoutHeight(windowHeight())
- window.syncConfig = useSyncConfigStore()
- window.mergedConfig = useMergedConfigStore()
- window.localConfig = useLocalConfigStore()
- window.highlightConfig = useUserHighlightStore()
-
FaviconService.initFaviconService()
initServiceWorker(store)
window.addEventListener('focus', () => updateFocus())
const overrides = window.___pleromafe_dev_overrides || {}
- const server =
- typeof overrides.target !== 'undefined'
- ? overrides.target
- : window.location.origin
- useInstanceStore().set({ path: 'server', value: server })
+ const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
+ store.dispatch('setInstanceOption', { name: 'server', value: server })
await setConfig({ store })
try {
- await useInterfaceStore()
- .applyTheme()
- .catch((e) => {
- console.error('Error setting theme', e)
- })
+ await useInterfaceStore().applyTheme().catch((e) => { console.error('Error setting theme', e) })
} catch (e) {
window.splashError(e)
return Promise.reject(e)
}
- applyStyleConfig(useMergedConfigStore().mergedConfig, i18n.global)
+ applyConfig(store.state.config, i18n.global)
// Now we can try getting the server settings and logging in
// Most of these are preloaded into the index.html so blocking is minimized
@@ -572,9 +428,13 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
checkOAuthToken({ store }),
getInstancePanel({ store }),
getNodeInfo({ store }),
- getInstanceConfig({ store }),
- ]).catch((e) => Promise.reject(e))
+ getInstanceConfig({ store })
+ ]).catch(e => Promise.reject(e))
+ // Start fetching things that don't need to block the UI
+ store.dispatch('fetchMutes')
+ store.dispatch('loadDrafts')
+ useAnnouncementsStore().startFetchingAnnouncements()
getTOS({ store })
getStickers({ store })
@@ -582,11 +442,11 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
history: createWebHistory(),
routes: routes(store),
scrollBehavior: (to, _from, savedPosition) => {
- if (to.matched.some((m) => m.meta.dontScroll)) {
+ if (to.matched.some(m => m.meta.dontScroll)) {
return false
}
return savedPosition || { left: 0, top: 0 }
- },
+ }
})
useI18nStore().setI18n(i18n)
@@ -608,9 +468,6 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
app.component('FAIcon', FontAwesomeIcon)
app.component('FALayers', FontAwesomeLayers)
- app.component('Status', Status)
- app.component('RichContent', RichContent)
- app.component('StillImage', StillImage)
// remove after vue 3.3
app.config.unwrapInjectedRef = true
diff --git a/src/boot/routes.js b/src/boot/routes.js
index 9b00a8004..02abf8ce6 100644
--- a/src/boot/routes.js
+++ b/src/boot/routes.js
@@ -1,28 +1,42 @@
-import { defineAsyncComponent } from 'vue'
-
-import BookmarkTimeline from 'src/components/bookmark_timeline/bookmark_timeline.vue'
-import BubbleTimeline from 'src/components/bubble_timeline/bubble_timeline.vue'
-import ConversationPage from 'src/components/conversation-page/conversation-page.vue'
-import DMs from 'src/components/dm_timeline/dm_timeline.vue'
-import FriendsTimeline from 'src/components/friends_timeline/friends_timeline.vue'
+import PublicTimeline from 'components/public_timeline/public_timeline.vue'
+import BubbleTimeline from 'components/bubble_timeline/bubble_timeline.vue'
+import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue'
+import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
+import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
+import BookmarkTimeline from 'components/bookmark_timeline/bookmark_timeline.vue'
+import ConversationPage from 'components/conversation-page/conversation-page.vue'
+import Interactions from 'components/interactions/interactions.vue'
+import DMs from 'components/dm_timeline/dm_timeline.vue'
+import ChatList from 'components/chat_list/chat_list.vue'
+import Chat from 'components/chat/chat.vue'
+import UserProfile from 'components/user_profile/user_profile.vue'
+import Search from 'components/search/search.vue'
+import Registration from 'components/registration/registration.vue'
+import PasswordReset from 'components/password_reset/password_reset.vue'
+import FollowRequests from 'components/follow_requests/follow_requests.vue'
+import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
+import Notifications from 'components/notifications/notifications.vue'
+import AuthForm from 'components/auth_form/auth_form.js'
+import ShoutPanel from 'components/shout_panel/shout_panel.vue'
+import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
+import About from 'components/about/about.vue'
+import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
+import Lists from 'components/lists/lists.vue'
+import ListsTimeline from 'components/lists_timeline/lists_timeline.vue'
+import ListsEdit from 'components/lists_edit/lists_edit.vue'
import NavPanel from 'src/components/nav_panel/nav_panel.vue'
-import PublicAndExternalTimeline from 'src/components/public_and_external_timeline/public_and_external_timeline.vue'
-import PublicTimeline from 'src/components/public_timeline/public_timeline.vue'
-import QuotesTimeline from 'src/components/quotes_timeline/quotes_timeline.vue'
-import RemoteUserResolver from 'src/components/remote_user_resolver/remote_user_resolver.vue'
-import TagTimeline from 'src/components/tag_timeline/tag_timeline.vue'
-
-import { useInstanceStore } from 'src/stores/instance.js'
-import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
+import AnnouncementsPage from 'components/announcements_page/announcements_page.vue'
+import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue'
+import Drafts from 'components/drafts/drafts.vue'
+import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue'
+import BookmarkFolderEdit from '../components/bookmark_folder_edit/bookmark_folder_edit.vue'
export default (store) => {
const validateAuthenticatedRoute = (to, from, next) => {
if (store.state.users.currentUser) {
next()
} else {
- next(
- useInstanceStore().instanceIdentity.redirectRootNoLogin || '/main/all',
- )
+ next(store.state.instance.redirectRootNoLogin || '/main/all')
}
}
@@ -31,283 +45,64 @@ export default (store) => {
name: 'root',
path: '/',
redirect: () => {
- return (
- (store.state.users.currentUser
- ? useInstanceStore().instanceIdentity.redirectRootLogin
- : useInstanceStore().instanceIdentity.redirectRootNoLogin) ||
- '/main/all'
- )
- },
- },
- {
- name: 'public-external-timeline',
- path: '/main/all',
- component: PublicAndExternalTimeline,
- },
- {
- name: 'public-timeline',
- path: '/main/public',
- component: PublicTimeline,
- },
- {
- name: 'friends',
- path: '/main/friends',
- component: FriendsTimeline,
- beforeEnter: validateAuthenticatedRoute,
+ return (store.state.users.currentUser
+ ? store.state.instance.redirectRootLogin
+ : store.state.instance.redirectRootNoLogin) || '/main/all'
+ }
},
+ { name: 'public-external-timeline', path: '/main/all', component: PublicAndExternalTimeline },
+ { name: 'public-timeline', path: '/main/public', component: PublicTimeline },
+ { name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute },
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
{ name: 'bubble', path: '/bubble', component: BubbleTimeline },
- {
- name: 'conversation',
- path: '/notice/:id',
- component: ConversationPage,
- meta: { dontScroll: true },
- },
+ { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
{ name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline },
{
name: 'remote-user-profile-acct',
path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',
component: RemoteUserResolver,
- beforeEnter: validateAuthenticatedRoute,
+ beforeEnter: validateAuthenticatedRoute
},
{
name: 'remote-user-profile',
path: '/remote-users/:hostname/:username',
component: RemoteUserResolver,
- beforeEnter: validateAuthenticatedRoute,
- },
- {
- name: 'external-user-profile',
- path: '/users/$:id',
- component: defineAsyncComponent(
- () => import('src/components/user_profile/user_profile.vue'),
- ),
- },
- {
- name: 'user-profile-admin-view',
- path: '/users/$:id/admin_view',
- component: defineAsyncComponent(
- () => import('src/components/user_profile/user_profile_admin_view.vue'),
- ),
- },
- {
- name: 'interactions',
- path: '/users/:username/interactions',
- component: defineAsyncComponent(
- () => import('src/components/interactions/interactions.vue'),
- ),
- beforeEnter: validateAuthenticatedRoute,
- },
- {
- name: 'dms',
- path: '/users/:username/dms',
- component: DMs,
- beforeEnter: validateAuthenticatedRoute,
- },
- {
- name: 'registration',
- path: '/registration',
- component: defineAsyncComponent(
- () => import('src/components/registration/registration.vue'),
- ),
- },
- {
- name: 'password-reset',
- path: '/password-reset',
- component: defineAsyncComponent(
- () => import('src/components/password_reset/password_reset.vue'),
- ),
- props: true,
- },
- {
- name: 'registration-token',
- path: '/registration/:token',
- component: defineAsyncComponent(
- () => import('src/components/registration/registration.vue'),
- ),
- },
- {
- name: 'friend-requests',
- path: '/friend-requests',
- component: defineAsyncComponent(
- () => import('src/components/follow_requests/follow_requests.vue'),
- ),
- beforeEnter: validateAuthenticatedRoute,
- },
- {
- name: 'notifications',
- path: '/:username/notifications',
- component: defineAsyncComponent(
- () => import('src/components/notifications/notifications.vue'),
- ),
- props: () => ({ disableTeleport: true }),
- beforeEnter: validateAuthenticatedRoute,
- },
- {
- name: 'login',
- path: '/login',
- component: defineAsyncComponent(
- () => import('src/components/auth_form/auth_form.js'),
- ),
- },
- {
- name: 'shout-panel',
- path: '/shout-panel',
- component: defineAsyncComponent(
- () => import('src/components/shout_panel/shout_panel.vue'),
- ),
- props: () => ({ floating: false }),
- },
- {
- name: 'oauth-callback',
- path: '/oauth-callback',
- component: defineAsyncComponent(
- () => import('src/components/oauth_callback/oauth_callback.vue'),
- ),
- props: (route) => ({ code: route.query.code }),
- },
- {
- name: 'search',
- path: '/search',
- component: defineAsyncComponent(
- () => import('src/components/search/search.vue'),
- ),
- props: (route) => ({ query: route.query.query }),
- },
- {
- name: 'who-to-follow',
- path: '/who-to-follow',
- component: defineAsyncComponent(
- () => import('src/components/who_to_follow/who_to_follow.vue'),
- ),
- beforeEnter: validateAuthenticatedRoute,
- },
- {
- name: 'about',
- path: '/about',
- component: defineAsyncComponent(
- () => import('src/components/about/about.vue'),
- ),
- },
- {
- name: 'announcements',
- path: '/announcements',
- component: defineAsyncComponent(
- () =>
- import('src/components/announcements_page/announcements_page.vue'),
- ),
- },
- {
- name: 'drafts',
- path: '/drafts',
- component: defineAsyncComponent(
- () => import('src/components/drafts/drafts.vue'),
- ),
- },
- {
- name: 'user-profile',
- path: '/users/:name',
- component: defineAsyncComponent(
- () => import('src/components/user_profile/user_profile.vue'),
- ),
- },
- {
- name: 'legacy-user-profile',
- path: '/:name',
- component: defineAsyncComponent(
- () => import('src/components/user_profile/user_profile.vue'),
- ),
- },
- {
- name: 'lists',
- path: '/lists',
- component: defineAsyncComponent(
- () => import('src/components/lists/lists.vue'),
- ),
- },
- {
- name: 'lists-timeline',
- path: '/lists/:id',
- component: defineAsyncComponent(
- () => import('src/components/lists_timeline/lists_timeline.vue'),
- ),
- },
- {
- name: 'lists-edit',
- path: '/lists/:id/edit',
- component: defineAsyncComponent(
- () => import('src/components/lists_edit/lists_edit.vue'),
- ),
- },
- {
- name: 'lists-new',
- path: '/lists/new',
- component: defineAsyncComponent(
- () => import('src/components/lists_edit/lists_edit.vue'),
- ),
- },
- {
- name: 'edit-navigation',
- path: '/nav-edit',
- component: NavPanel,
- props: () => ({ forceExpand: true, forceEditMode: true }),
- beforeEnter: validateAuthenticatedRoute,
- },
- {
- name: 'bookmark-folders',
- path: '/bookmark_folders',
- component: defineAsyncComponent(
- () => import('src/components/bookmark_folders/bookmark_folders.vue'),
- ),
- },
- {
- name: 'bookmark-folder-new',
- path: '/bookmarks/new-folder',
- component: defineAsyncComponent(
- () =>
- import(
- 'src/components/bookmark_folder_edit/bookmark_folder_edit.vue'
- ),
- ),
- },
- {
- name: 'bookmark-folder',
- path: '/bookmarks/:id',
- component: BookmarkTimeline,
- },
- {
- name: 'bookmark-folder-edit',
- path: '/bookmarks/:id/edit',
- component: defineAsyncComponent(
- () =>
- import(
- 'src/components/bookmark_folder_edit/bookmark_folder_edit.vue'
- ),
- ),
+ beforeEnter: validateAuthenticatedRoute
},
+ { name: 'external-user-profile', path: '/users/$:id', component: UserProfile },
+ { name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
+ { name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
+ { name: 'registration', path: '/registration', component: Registration },
+ { name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true },
+ { name: 'registration-token', path: '/registration/:token', component: Registration },
+ { name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
+ { name: 'notifications', path: '/:username/notifications', component: Notifications, props: () => ({ disableTeleport: true }), beforeEnter: validateAuthenticatedRoute },
+ { name: 'login', path: '/login', component: AuthForm },
+ { name: 'shout-panel', path: '/shout-panel', component: ShoutPanel, props: () => ({ floating: false }) },
+ { name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
+ { name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
+ { name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
+ { name: 'about', path: '/about', component: About },
+ { name: 'announcements', path: '/announcements', component: AnnouncementsPage },
+ { name: 'drafts', path: '/drafts', component: Drafts },
+ { name: 'user-profile', path: '/users/:name', component: UserProfile },
+ { name: 'legacy-user-profile', path: '/:name', component: UserProfile },
+ { name: 'lists', path: '/lists', component: Lists },
+ { name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline },
+ { name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit },
+ { name: 'lists-new', path: '/lists/new', component: ListsEdit },
+ { name: 'edit-navigation', path: '/nav-edit', component: NavPanel, props: () => ({ forceExpand: true, forceEditMode: true }), beforeEnter: validateAuthenticatedRoute },
+ { name: 'bookmark-folders', path: '/bookmark_folders', component: BookmarkFolders },
+ { name: 'bookmark-folder-new', path: '/bookmarks/new-folder', component: BookmarkFolderEdit },
+ { name: 'bookmark-folder', path: '/bookmarks/:id', component: BookmarkTimeline },
+ { name: 'bookmark-folder-edit', path: '/bookmarks/:id/edit', component: BookmarkFolderEdit }
]
- if (useInstanceCapabilitiesStore().pleromaChatMessagesAvailable) {
+ if (store.state.instance.pleromaChatMessagesAvailable) {
routes = routes.concat([
- {
- name: 'chat',
- path: '/users/:username/chats/:recipient_id',
- component: defineAsyncComponent(
- () => import('src/components/chat/chat.vue'),
- ),
- meta: { dontScroll: false },
- beforeEnter: validateAuthenticatedRoute,
- },
- {
- name: 'chats',
- path: '/users/:username/chats',
- component: defineAsyncComponent(
- () => import('src/components/chat_list/chat_list.vue'),
- ),
- meta: { dontScroll: false },
- beforeEnter: validateAuthenticatedRoute,
- },
+ { name: 'chat', path: '/users/:username/chats/:recipient_id', component: Chat, meta: { dontScroll: false }, beforeEnter: validateAuthenticatedRoute },
+ { name: 'chats', path: '/users/:username/chats', component: ChatList, meta: { dontScroll: false }, beforeEnter: validateAuthenticatedRoute }
])
}
diff --git a/src/components/about/about.js b/src/components/about/about.js
index e293895d5..1df258450 100644
--- a/src/components/about/about.js
+++ b/src/components/about/about.js
@@ -1,16 +1,8 @@
-import { mapState } from 'pinia'
-
-import FeaturesPanel from 'src/components/features_panel/features_panel.vue'
-import InstanceSpecificPanel from 'src/components/instance_specific_panel/instance_specific_panel.vue'
-import MRFTransparencyPanel from 'src/components/mrf_transparency_panel/mrf_transparency_panel.vue'
-import StaffPanel from 'src/components/staff_panel/staff_panel.vue'
-import TermsOfServicePanel from 'src/components/terms_of_service_panel/terms_of_service_panel.vue'
-
-import { useInstanceStore } from 'src/stores/instance.js'
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-
-const pleromaFeCommitUrl =
- 'https://git.pleroma.social/pleroma/pleroma-fe/commit/'
+import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_panel.vue'
+import FeaturesPanel from '../features_panel/features_panel.vue'
+import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue'
+import StaffPanel from '../staff_panel/staff_panel.vue'
+import MRFTransparencyPanel from '../mrf_transparency_panel/mrf_transparency_panel.vue'
const About = {
components: {
@@ -18,28 +10,16 @@ const About = {
FeaturesPanel,
TermsOfServicePanel,
StaffPanel,
- MRFTransparencyPanel,
+ MRFTransparencyPanel
},
computed: {
- showFeaturesPanel() {
- return useInstanceStore().instanceIdentity.showFeaturesPanel
- },
- frontendVersionLink() {
- return pleromaFeCommitUrl + this.frontendVersion
- },
- ...mapState(useInstanceStore, [
- 'backendVersion',
- 'backendRepository',
- 'frontendVersion',
- ]),
- showInstanceSpecificPanel() {
- return (
- useInstanceStore().instanceIdentity.showInstanceSpecificPanel &&
- !useMergedConfigStore().mergedConfig.hideISP &&
- useInstanceStore().instanceIdentity.instanceSpecificPanelContent
- )
- },
- },
+ showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
+ showInstanceSpecificPanel () {
+ return this.$store.state.instance.showInstanceSpecificPanel &&
+ !this.$store.getters.mergedConfig.hideISP &&
+ this.$store.state.instance.instanceSpecificPanelContent
+ }
+ }
}
export default About
diff --git a/src/components/about/about.vue b/src/components/about/about.vue
index f7bc0d00d..8a551485f 100644
--- a/src/components/about/about.vue
+++ b/src/components/about/about.vue
@@ -1,47 +1,11 @@
-
+
-
diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js
index 9b4775b97..9a63f57eb 100644
--- a/src/components/account_actions/account_actions.js
+++ b/src/components/account_actions/account_actions.js
@@ -1,111 +1,99 @@
-import { mapState } from 'pinia'
-import { defineAsyncComponent } from 'vue'
-
-import Popover from 'src/components/popover/popover.vue'
-import ProgressButton from 'src/components/progress_button/progress_button.vue'
+import { mapState } from 'vuex'
+import ProgressButton from '../progress_button/progress_button.vue'
+import Popover from '../popover/popover.vue'
import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
-
-import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
+import ConfirmModal from '../confirm_modal/confirm_modal.vue'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faEllipsisV
+} from '@fortawesome/free-solid-svg-icons'
import { useReportsStore } from 'src/stores/reports'
-import { library } from '@fortawesome/fontawesome-svg-core'
-import { faEllipsisV } from '@fortawesome/free-solid-svg-icons'
-
-library.add(faEllipsisV)
+library.add(
+ faEllipsisV
+)
const AccountActions = {
- props: ['user', 'relationship'],
- data() {
+ props: [
+ 'user', 'relationship'
+ ],
+ data () {
return {
showingConfirmBlock: false,
- showingConfirmRemoveFollower: false,
+ showingConfirmRemoveFollower: false
}
},
components: {
ProgressButton,
Popover,
UserListMenu,
- ConfirmModal: defineAsyncComponent(
- () => import('src/components/confirm_modal/confirm_modal.vue'),
- ),
- UserTimedFilterModal: defineAsyncComponent(
- () =>
- import(
- 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue'
- ),
- ),
+ ConfirmModal
},
methods: {
- showConfirmRemoveUserFromFollowers() {
- this.showingConfirmRemoveFollower = true
+ showConfirmBlock () {
+ this.showingConfirmBlock = true
},
- hideConfirmRemoveUserFromFollowers() {
- this.showingConfirmRemoveFollower = false
- },
- hideConfirmBlock() {
+ hideConfirmBlock () {
this.showingConfirmBlock = false
},
- showRepeats() {
+ showConfirmRemoveUserFromFollowers () {
+ this.showingConfirmRemoveFollower = true
+ },
+ hideConfirmRemoveUserFromFollowers () {
+ this.showingConfirmRemoveFollower = false
+ },
+ showRepeats () {
this.$store.dispatch('showReblogs', this.user.id)
},
- hideRepeats() {
+ hideRepeats () {
this.$store.dispatch('hideReblogs', this.user.id)
},
- blockUser() {
- if (this.$refs.timedBlockDialog) {
- this.$refs.timedBlockDialog.optionallyPrompt()
+ blockUser () {
+ if (!this.shouldConfirmBlock) {
+ this.doBlockUser()
} else {
- if (!this.shouldConfirmBlock) {
- this.doBlockUser()
- } else {
- this.showingConfirmBlock = true
- }
+ this.showConfirmBlock()
}
},
- doBlockUser() {
- this.$store.dispatch('blockUser', { id: this.user.id })
+ doBlockUser () {
+ this.$store.dispatch('blockUser', this.user.id)
this.hideConfirmBlock()
},
- unblockUser() {
+ unblockUser () {
this.$store.dispatch('unblockUser', this.user.id)
},
- removeUserFromFollowers() {
+ removeUserFromFollowers () {
if (!this.shouldConfirmRemoveUserFromFollowers) {
this.doRemoveUserFromFollowers()
} else {
this.showConfirmRemoveUserFromFollowers()
}
},
- doRemoveUserFromFollowers() {
+ doRemoveUserFromFollowers () {
this.$store.dispatch('removeUserFromFollowers', this.user.id)
this.hideConfirmRemoveUserFromFollowers()
},
- reportUser() {
+ reportUser () {
useReportsStore().openUserReportingModal({ userId: this.user.id })
},
- openChat() {
+ openChat () {
this.$router.push({
name: 'chat',
- params: {
- username: this.$store.state.users.currentUser.screen_name,
- recipient_id: this.user.id,
- },
+ params: { username: this.$store.state.users.currentUser.screen_name, recipient_id: this.user.id }
})
- },
+ }
},
computed: {
- shouldConfirmBlock() {
- return useMergedConfigStore().mergedConfig.modalOnBlock
+ shouldConfirmBlock () {
+ return this.$store.getters.mergedConfig.modalOnBlock
},
- shouldConfirmRemoveUserFromFollowers() {
- return useMergedConfigStore().mergedConfig.modalOnRemoveUserFromFollowers
+ shouldConfirmRemoveUserFromFollowers () {
+ return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
},
- ...mapState(useInstanceCapabilitiesStore, [
- 'blockExpiration',
- 'pleromaChatMessagesAvailable',
- ]),
- },
+ ...mapState({
+ pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
+ })
+ }
}
export default AccountActions
diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue
index 0c93872de..fd4837ee4 100644
--- a/src/components/account_actions/account_actions.vue
+++ b/src/components/account_actions/account_actions.vue
@@ -3,6 +3,7 @@
@@ -94,9 +95,8 @@
-
-
+
-
-
-
+
diff --git a/src/components/alert.style.js b/src/components/alert.style.js
index 6876faca8..868514764 100644
--- a/src/components/alert.style.js
+++ b/src/components/alert.style.js
@@ -1,58 +1,57 @@
export default {
name: 'Alert',
selector: '.alert',
- validInnerComponents: ['Text', 'Icon', 'Link', 'Border', 'ButtonUnstyled'],
+ validInnerComponents: [
+ 'Text',
+ 'Icon',
+ 'Link',
+ 'Border',
+ 'ButtonUnstyled'
+ ],
variants: {
normal: '.neutral',
- info: '.info',
error: '.error',
warning: '.warning',
- success: '.success',
+ success: '.success'
},
editor: {
border: 1,
- aspect: '3 / 1',
+ aspect: '3 / 1'
},
defaultRules: [
{
directives: {
background: '--text',
opacity: 0.5,
- blur: '9px',
- },
+ blur: '9px'
+ }
},
{
parent: {
- component: 'Alert',
+ component: 'Alert'
},
component: 'Border',
directives: {
- textColor: '--parent',
- },
+ textColor: '--parent'
+ }
},
{
variant: 'error',
directives: {
- background: '--cRed',
- },
+ background: '--cRed'
+ }
},
{
variant: 'warning',
directives: {
- background: '--cOrange',
- },
+ background: '--cOrange'
+ }
},
{
variant: 'success',
directives: {
- background: '--cGreen',
- },
- },
- {
- variant: 'info',
- directives: {
- background: '--cBlue',
- },
- },
- ],
+ background: '--cGreen'
+ }
+ }
+ ]
}
diff --git a/src/components/announcement/announcement.js b/src/components/announcement/announcement.js
index ee427533f..d1b8257d8 100644
--- a/src/components/announcement/announcement.js
+++ b/src/components/announcement/announcement.js
@@ -1,126 +1,109 @@
import { mapState } from 'vuex'
-
-import AnnouncementEditor from 'src/components/announcement_editor/announcement_editor.vue'
+import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
+import RichContent from '../rich_content/rich_content.jsx'
import localeService from '../../services/locale/locale.service.js'
-
-import { useAnnouncementsStore } from 'src/stores/announcements.js'
+import { useAnnouncementsStore } from 'src/stores/announcements'
const Announcement = {
components: {
AnnouncementEditor,
+ RichContent
},
- data() {
+ data () {
return {
editing: false,
editedAnnouncement: {
content: '',
startsAt: undefined,
endsAt: undefined,
- allDay: undefined,
+ allDay: undefined
},
- editError: '',
+ editError: ''
}
},
props: {
- announcement: Object,
+ announcement: Object
},
computed: {
...mapState({
- currentUser: (state) => state.users.currentUser,
+ currentUser: state => state.users.currentUser
}),
- canEditAnnouncement() {
- return (
- this.currentUser &&
- this.currentUser.privileges.has('announcements_manage_announcements')
- )
+ canEditAnnouncement () {
+ return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
},
- content() {
+ content () {
return this.announcement.content
},
- isRead() {
+ isRead () {
return this.announcement.read
},
- publishedAt() {
+ publishedAt () {
const time = this.announcement.published_at
if (!time) {
return
}
- return this.formatTimeOrDate(
- time,
- localeService.internalToBrowserLocale(this.$i18n.locale),
- )
+ return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
},
- startsAt() {
+ startsAt () {
const time = this.announcement.starts_at
if (!time) {
return
}
- return this.formatTimeOrDate(
- time,
- localeService.internalToBrowserLocale(this.$i18n.locale),
- )
+ return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
},
- endsAt() {
+ endsAt () {
const time = this.announcement.ends_at
if (!time) {
return
}
- return this.formatTimeOrDate(
- time,
- localeService.internalToBrowserLocale(this.$i18n.locale),
- )
+ return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
},
- inactive() {
+ inactive () {
return this.announcement.inactive
- },
+ }
},
methods: {
- markAsRead() {
+ markAsRead () {
if (!this.isRead) {
- return useAnnouncementsStore().markAnnouncementAsRead(
- this.announcement.id,
- )
+ return useAnnouncementsStore().markAnnouncementAsRead(this.announcement.id)
}
},
- deleteAnnouncement() {
+ deleteAnnouncement () {
return useAnnouncementsStore().deleteAnnouncement(this.announcement.id)
},
- formatTimeOrDate(time, locale) {
+ formatTimeOrDate (time, locale) {
const d = new Date(time)
- return this.announcement.all_day
- ? d.toLocaleDateString(locale)
- : d.toLocaleString(locale)
+ return this.announcement.all_day ? d.toLocaleDateString(locale) : d.toLocaleString(locale)
},
- enterEditMode() {
+ enterEditMode () {
this.editedAnnouncement.content = this.announcement.pleroma.raw_content
this.editedAnnouncement.startsAt = this.announcement.starts_at
this.editedAnnouncement.endsAt = this.announcement.ends_at
this.editedAnnouncement.allDay = this.announcement.all_day
this.editing = true
},
- submitEdit() {
- useAnnouncementsStore()
- .editAnnouncement({
- id: this.announcement.id,
- ...this.editedAnnouncement,
- })
+ submitEdit () {
+ useAnnouncementsStore().editAnnouncement({
+ id: this.announcement.id,
+ ...this.editedAnnouncement
+ })
.then(() => {
this.editing = false
})
- .catch((error) => {
+ .catch(error => {
this.editError = error.error
})
},
- cancelEdit() {
+ cancelEdit () {
this.editing = false
},
- clearError() {
+ clearError () {
this.editError = undefined
- },
- },
+ }
+ }
}
export default Announcement
diff --git a/src/components/announcement_editor/announcement_editor.js b/src/components/announcement_editor/announcement_editor.js
index c816083a4..79a03afe1 100644
--- a/src/components/announcement_editor/announcement_editor.js
+++ b/src/components/announcement_editor/announcement_editor.js
@@ -1,13 +1,13 @@
-import Checkbox from 'src/components/checkbox/checkbox.vue'
+import Checkbox from '../checkbox/checkbox.vue'
const AnnouncementEditor = {
components: {
- Checkbox,
+ Checkbox
},
props: {
announcement: Object,
- disabled: Boolean,
- },
+ disabled: Boolean
+ }
}
export default AnnouncementEditor
diff --git a/src/components/announcements_page/announcements_page.js b/src/components/announcements_page/announcements_page.js
index 0f7933e5f..9ce0b45f5 100644
--- a/src/components/announcements_page/announcements_page.js
+++ b/src/components/announcements_page/announcements_page.js
@@ -1,65 +1,59 @@
import { mapState } from 'vuex'
-
-import Announcement from 'src/components/announcement/announcement.vue'
-import AnnouncementEditor from 'src/components/announcement_editor/announcement_editor.vue'
-
-import { useAnnouncementsStore } from 'src/stores/announcements.js'
+import Announcement from '../announcement/announcement.vue'
+import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
+import { useAnnouncementsStore } from 'src/stores/announcements'
const AnnouncementsPage = {
components: {
Announcement,
- AnnouncementEditor,
+ AnnouncementEditor
},
- data() {
+ data () {
return {
newAnnouncement: {
content: '',
startsAt: undefined,
endsAt: undefined,
- allDay: false,
+ allDay: false
},
posting: false,
- error: undefined,
+ error: undefined
}
},
- mounted() {
+ mounted () {
useAnnouncementsStore().fetchAnnouncements()
},
computed: {
...mapState({
- currentUser: (state) => state.users.currentUser,
+ currentUser: state => state.users.currentUser
}),
- announcements() {
+ announcements () {
return useAnnouncementsStore().announcements
},
- canPostAnnouncement() {
- return (
- this.currentUser &&
- this.currentUser.privileges.has('announcements_manage_announcements')
- )
- },
+ canPostAnnouncement () {
+ return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
+ }
},
methods: {
- postAnnouncement() {
+ postAnnouncement () {
this.posting = true
- useAnnouncementsStore()
- .postAnnouncement(this.newAnnouncement)
+ useAnnouncementsStore().postAnnouncement(this.newAnnouncement)
.then(() => {
this.newAnnouncement.content = ''
this.startsAt = undefined
this.endsAt = undefined
})
- .catch((error) => {
+ .catch(error => {
this.error = error.error
})
.finally(() => {
this.posting = false
})
},
- clearError() {
+ clearError () {
this.error = undefined
- },
- },
+ }
+ }
}
export default AnnouncementsPage
diff --git a/src/components/async_component_error/async_component_error.vue b/src/components/async_component_error/async_component_error.vue
index baf430950..2ff8974c1 100644
--- a/src/components/async_component_error/async_component_error.vue
+++ b/src/components/async_component_error/async_component_error.vue
@@ -21,10 +21,10 @@
export default {
emits: ['resetAsyncComponent'],
methods: {
- retry() {
+ retry () {
this.$emit('resetAsyncComponent')
- },
- },
+ }
+ }
}
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
index f7d6ebf10..21d793930 100644
--- a/src/components/attachment/attachment.js
+++ b/src/components/attachment/attachment.js
@@ -1,28 +1,24 @@
-import { mapState } from 'pinia'
-import { defineAsyncComponent } from 'vue'
-
-import Popover from 'src/components/popover/popover.vue'
+import StillImage from '../still-image/still-image.vue'
+import Flash from '../flash/flash.vue'
+import VideoAttachment from '../video_attachment/video_attachment.vue'
import nsfwImage from '../../assets/nsfw.png'
-
-import { useInstanceStore } from 'src/stores/instance.js'
-import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
-import { useMediaViewerStore } from 'src/stores/media_viewer'
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-
+import fileTypeService from '../../services/file_type/file_type.service.js'
+import { mapGetters } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
- faAlignRight,
faFile,
- faImage,
faMusic,
- faPencilAlt,
- faPlayCircle,
- faSearchPlus,
- faStop,
- faTimes,
- faTrashAlt,
+ faImage,
faVideo,
+ faPlayCircle,
+ faTimes,
+ faStop,
+ faSearchPlus,
+ faTrashAlt,
+ faPencilAlt,
+ faAlignRight
} from '@fortawesome/free-solid-svg-icons'
+import { useMediaViewerStore } from 'src/stores/media_viewer'
library.add(
faFile,
@@ -35,7 +31,7 @@ library.add(
faSearchPlus,
faTrashAlt,
faPencilAlt,
- faAlignRight,
+ faAlignRight
)
const Attachment = {
@@ -50,75 +46,72 @@ const Attachment = {
'remove',
'shiftUp',
'shiftDn',
- 'edit',
+ 'edit'
],
- emits: ['play', 'pause', 'naturalSizeLoad'],
- data() {
+ data () {
return {
localDescription: this.description || this.attachment.description,
- nsfwImage:
- useInstanceStore().instanceIdentity.nsfwCensorImage || nsfwImage,
- hideNsfwLocal: useMergedConfigStore().mergedConfig.hideNsfw,
- preloadImage: useMergedConfigStore().mergedConfig.preloadImage,
+ nsfwImage: this.$store.state.instance.nsfwCensorImage || nsfwImage,
+ hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw,
+ preloadImage: this.$store.getters.mergedConfig.preloadImage,
loading: false,
- img: this.attachment.type === 'image' && document.createElement('img'),
+ img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img'),
modalOpen: false,
showHidden: false,
flashLoaded: false,
+ showDescription: false
}
},
components: {
- Flash: defineAsyncComponent(() => import('src/components/flash/flash.vue')),
-
- VideoAttachment: defineAsyncComponent(
- () => import('src/components/video_attachment/video_attachment.vue'),
- ),
- Popover,
+ Flash,
+ StillImage,
+ VideoAttachment
},
computed: {
- classNames() {
+ classNames () {
return [
{
'-loading': this.loading,
'-nsfw-placeholder': this.hidden,
'-editable': this.edit !== undefined,
- '-compact': this.compact,
+ '-compact': this.compact
},
- '-type-' + this.attachment.type,
+ '-type-' + this.type,
this.size && '-size-' + this.size,
- `-${this.useContainFit ? 'contain' : 'cover'}-fit`,
+ `-${this.useContainFit ? 'contain' : 'cover'}-fit`
]
},
- usePlaceholder() {
+ usePlaceholder () {
return this.size === 'hide'
},
- useContainFit() {
- return this.mergedConfig.useContainFit
+ useContainFit () {
+ return this.$store.getters.mergedConfig.useContainFit
},
- placeholderName() {
+ placeholderName () {
if (this.attachment.description === '' || !this.attachment.description) {
- return this.attachment.type.toUpperCase()
+ return this.type.toUpperCase()
}
return this.attachment.description
},
- placeholderIconClass() {
- if (this.attachment.type === 'image') return 'image'
- if (this.attachment.type === 'video') return 'video'
- if (this.attachment.type === 'audio') return 'music'
+ placeholderIconClass () {
+ if (this.type === 'image') return 'image'
+ if (this.type === 'video') return 'video'
+ if (this.type === 'audio') return 'music'
return 'file'
},
- referrerpolicy() {
- return useInstanceCapabilitiesStore().mediaProxyAvailable
- ? ''
- : 'no-referrer'
+ referrerpolicy () {
+ return this.$store.state.instance.mediaProxyAvailable ? '' : 'no-referrer'
},
- hidden() {
+ type () {
+ return fileTypeService.fileType(this.attachment.mimetype)
+ },
+ hidden () {
return this.nsfw && this.hideNsfwLocal && !this.showHidden
},
- isEmpty() {
- return this.attachment.type === 'html' && !this.attachment.oembed
+ isEmpty () {
+ return (this.type === 'html' && !this.attachment.oembed)
},
- useModal() {
+ useModal () {
let modalTypes = []
switch (this.size) {
case 'hide':
@@ -131,63 +124,64 @@ const Attachment = {
: ['image']
break
}
- return modalTypes.includes(this.attachment.type)
+ return modalTypes.includes(this.type)
},
- videoTag() {
+ videoTag () {
return this.useModal ? 'button' : 'span'
},
- ...mapState(useMergedConfigStore, ['mergedConfig']),
+ ...mapGetters(['mergedConfig'])
},
watch: {
- 'attachment.description'(newVal) {
+ 'attachment.description' (newVal) {
this.localDescription = newVal
},
- localDescription(newVal) {
+ localDescription (newVal) {
this.onEdit(newVal)
- },
+ }
},
methods: {
- linkClicked({ target }) {
+ linkClicked ({ target }) {
if (target.tagName === 'A') {
window.open(target.href, '_blank')
}
},
- openModal() {
+ openModal () {
if (this.useModal) {
this.$emit('setMedia')
useMediaViewerStore().setCurrentMedia(this.attachment)
- } else if (this.attachment.type === 'unknown') {
+ } else if (this.type === 'unknown') {
window.open(this.attachment.url)
}
},
- openModalForce() {
+ openModalForce () {
this.$emit('setMedia')
useMediaViewerStore().setCurrentMedia(this.attachment)
},
- onEdit(event) {
+ onEdit (event) {
this.edit && this.edit(this.attachment, event)
},
- onRemove() {
+ onRemove () {
this.remove && this.remove(this.attachment)
},
- onShiftUp() {
+ onShiftUp () {
this.shiftUp && this.shiftUp(this.attachment)
},
- onShiftDn() {
+ onShiftDn () {
this.shiftDn && this.shiftDn(this.attachment)
},
- stopFlash() {
+ stopFlash () {
this.$refs.flash.closePlayer()
},
- setFlashLoaded(event) {
+ setFlashLoaded (event) {
this.flashLoaded = event
},
- toggleHidden(event) {
+ toggleDescription () {
+ this.showDescription = !this.showDescription
+ },
+ toggleHidden (event) {
if (
- this.mergedConfig.useOneClickNsfw &&
- !this.showHidden &&
- (this.attachment.type !== 'video' ||
- this.mergedConfig.playVideosInModal)
+ (this.mergedConfig.useOneClickNsfw && !this.showHidden) &&
+ (this.type !== 'video' || this.mergedConfig.playVideosInModal)
) {
this.openModal(event)
return
@@ -207,12 +201,12 @@ const Attachment = {
this.showHidden = !this.showHidden
}
},
- onImageLoad(image) {
+ onImageLoad (image) {
const width = image.naturalWidth
const height = image.naturalHeight
this.$emit('naturalSizeLoad', { id: this.attachment.id, width, height })
- },
- },
+ }
+ }
}
export default Attachment
diff --git a/src/components/attachment/attachment.scss b/src/components/attachment/attachment.scss
index 3c3ba7751..16346c97c 100644
--- a/src/components/attachment/attachment.scss
+++ b/src/components/attachment/attachment.scss
@@ -107,9 +107,9 @@
.play-icon {
position: absolute;
- font-size: 4.5em;
- top: calc(50% - 2.25rem);
- left: calc(50% - 2.25rem);
+ font-size: 64px;
+ top: calc(50% - 32px);
+ left: calc(50% - 32px);
color: rgb(255 255 255 / 75%);
text-shadow: 0 0 2px rgb(0 0 0 / 40%);
@@ -134,7 +134,7 @@
width: 2em;
height: 2em;
margin-left: 0.5em;
- font-size: 1em;
+ font-size: 1.25em;
}
}
@@ -265,27 +265,3 @@
}
}
}
-
-.description-popover {
- padding: 1em;
- width: 50ch;
- max-width: 90vw;
- overflow: hidden;
- box-sizing: border-box;
-
- summary {
- display: inline-block;
- margin-bottom: 0.5em;
- font-weight: bold;
- pointer-events: none;
- }
-
- span {
- display: block;
- overflow-y: auto;
- max-height: 10.5em;
- text-wrap: pretty;
- line-height: 1.5;
- white-space: pre-wrap;
- }
-}
diff --git a/src/components/attachment/attachment.style.js b/src/components/attachment/attachment.style.js
new file mode 100644
index 000000000..a9455e367
--- /dev/null
+++ b/src/components/attachment/attachment.style.js
@@ -0,0 +1,27 @@
+export default {
+ name: 'Attachment',
+ selector: '.Attachment',
+ notEditable: true,
+ validInnerComponents: [
+ 'Border',
+ 'Button',
+ 'Input'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ roundness: 3
+ }
+ },
+ {
+ component: 'Button',
+ parent: {
+ component: 'Attachment'
+ },
+ directives: {
+ background: '#FFFFFF',
+ opacity: 0.5
+ }
+ }
+ ]
+}
diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue
index 1a1b84628..0701a393e 100644
--- a/src/components/attachment/attachment.vue
+++ b/src/components/attachment/attachment.vue
@@ -6,7 +6,7 @@
@click="openModal"
>
-
+ @keydown.enter.prevent=""
+ >
+
+ {{ localDescription }}
+
@@ -75,32 +80,24 @@
class="attachment-buttons"
>
-
-
-
-
-
-
- {{ $t('status.attachment_description') }}
- {{ localDescription }}
-
-
-
+
+
+
@@ -108,7 +105,7 @@
@@ -116,7 +113,7 @@
@@ -124,7 +121,7 @@
@@ -132,7 +129,7 @@
@@ -141,7 +138,7 @@
@@ -232,7 +229,7 @@
-
+ @keydown.enter.prevent=""
+ >
+
+ {{ localDescription }}
+
diff --git a/src/components/auth_form/auth_form.js b/src/components/auth_form/auth_form.js
index 7d853d119..243cbf574 100644
--- a/src/components/auth_form/auth_form.js
+++ b/src/components/auth_form/auth_form.js
@@ -1,34 +1,28 @@
-import { mapState } from 'pinia'
import { h, resolveComponent } from 'vue'
-
-import LoginForm from 'src/components/login_form/login_form.vue'
-import MFARecoveryForm from 'src/components/mfa_form/recovery_form.vue'
-import MFATOTPForm from 'src/components/mfa_form/totp_form.vue'
-
-import { useAuthFlowStore } from 'src/stores/auth_flow.js'
+import LoginForm from '../login_form/login_form.vue'
+import MFARecoveryForm from '../mfa_form/recovery_form.vue'
+import MFATOTPForm from '../mfa_form/totp_form.vue'
+import { mapState } from 'pinia'
+import { useAuthFlowStore } from 'src/stores/auth_flow'
const AuthForm = {
name: 'AuthForm',
- render() {
+ render () {
return h(resolveComponent(this.authForm))
},
computed: {
- authForm() {
- if (this.requiredTOTP) {
- return 'MFATOTPForm'
- }
- if (this.requiredRecovery) {
- return 'MFARecoveryForm'
- }
+ authForm () {
+ if (this.requiredTOTP) { return 'MFATOTPForm' }
+ if (this.requiredRecovery) { return 'MFARecoveryForm' }
return 'LoginForm'
},
- ...mapState(useAuthFlowStore, ['requiredTOTP', 'requiredRecovery']),
+ ...mapState(useAuthFlowStore, ['requiredTOTP', 'requiredRecovery'])
},
components: {
MFARecoveryForm,
MFATOTPForm,
- LoginForm,
- },
+ LoginForm
+ }
}
export default AuthForm
diff --git a/src/components/autosuggest/autosuggest.js b/src/components/autosuggest/autosuggest.js
index 37b7706db..f58f17bb6 100644
--- a/src/components/autosuggest/autosuggest.js
+++ b/src/components/autosuggest/autosuggest.js
@@ -2,55 +2,51 @@ const debounceMilliseconds = 500
export default {
props: {
- query: {
- // function to query results and return a promise
+ query: { // function to query results and return a promise
type: Function,
- required: true,
+ required: true
},
- filter: {
- // function to filter results in real time
- type: Function,
+ filter: { // function to filter results in real time
+ type: Function
},
placeholder: {
type: String,
- default: 'Search...',
- },
+ default: 'Search...'
+ }
},
- data() {
+ data () {
return {
term: '',
timeout: null,
results: [],
- resultsVisible: false,
+ resultsVisible: false
}
},
computed: {
- filtered() {
+ filtered () {
return this.filter ? this.filter(this.results) : this.results
- },
+ }
},
watch: {
- term(val) {
+ term (val) {
this.fetchResults(val)
- },
+ }
},
methods: {
- fetchResults(term) {
+ fetchResults (term) {
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.results = []
if (term) {
- this.query(term).then((results) => {
- this.results = results
- })
+ this.query(term).then((results) => { this.results = results })
}
}, debounceMilliseconds)
},
- onInputClick() {
+ onInputClick () {
this.resultsVisible = true
},
- onClickOutside() {
+ onClickOutside () {
this.resultsVisible = false
- },
- },
+ }
+ }
}
diff --git a/src/components/avatar_list/avatar_list.js b/src/components/avatar_list/avatar_list.js
index 7806fba81..9b6301b22 100644
--- a/src/components/avatar_list/avatar_list.js
+++ b/src/components/avatar_list/avatar_list.js
@@ -1,28 +1,21 @@
-import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
-
-import { useInstanceStore } from 'src/stores/instance.js'
-
+import UserAvatar from '../user_avatar/user_avatar.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const AvatarList = {
props: ['users'],
computed: {
- slicedUsers() {
+ slicedUsers () {
return this.users ? this.users.slice(0, 15) : []
- },
+ }
},
components: {
- UserAvatar,
+ UserAvatar
},
methods: {
- userProfileLink(user) {
- return generateProfileLink(
- user.id,
- user.screen_name,
- useInstanceStore().restrictedNicknames,
- )
- },
- },
+ userProfileLink (user) {
+ return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
+ }
+ }
}
export default AvatarList
diff --git a/src/components/badge.style.js b/src/components/badge.style.js
index ab28429cc..0697cac6c 100644
--- a/src/components/badge.style.js
+++ b/src/components/badge.style.js
@@ -1,27 +1,30 @@
export default {
name: 'Badge',
selector: '.badge',
- validInnerComponents: ['Text', 'Icon'],
+ validInnerComponents: [
+ 'Text',
+ 'Icon'
+ ],
variants: {
- notification: '.-notification',
+ notification: '.-notification'
},
defaultRules: [
{
component: 'Root',
directives: {
- '--badgeNotification': 'color | --cRed',
- },
+ '--badgeNotification': 'color | --cRed'
+ }
},
{
directives: {
- background: '--cGreen',
- },
+ background: '--cGreen'
+ }
},
{
variant: 'notification',
directives: {
- background: '--cRed',
- },
- },
- ],
+ background: '--cRed'
+ }
+ }
+ ]
}
diff --git a/src/components/basic_user_card/basic_user_card.js b/src/components/basic_user_card/basic_user_card.js
index ae1f1c9c6..31de2d75f 100644
--- a/src/components/basic_user_card/basic_user_card.js
+++ b/src/components/basic_user_card/basic_user_card.js
@@ -1,42 +1,24 @@
-import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
-import UserLink from 'src/components/user_link/user_link.vue'
-import UserPopover from 'src/components/user_popover/user_popover.vue'
-
-import { useInstanceStore } from 'src/stores/instance.js'
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-
+import UserPopover from '../user_popover/user_popover.vue'
+import UserAvatar from '../user_avatar/user_avatar.vue'
+import UserLink from '../user_link/user_link.vue'
+import RichContent from 'src/components/rich_content/rich_content.jsx'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const BasicUserCard = {
- props: {
- user: {
- type: Object,
- },
- showLineLabels: {
- type: Boolean,
- default: false,
- },
- },
+ props: [
+ 'user'
+ ],
components: {
UserPopover,
UserAvatar,
-
- UserLink,
+ RichContent,
+ UserLink
},
methods: {
- userProfileLink(user) {
- return generateProfileLink(
- user.id,
- user.screen_name,
- useInstanceStore().restrictedNicknames,
- )
- },
- },
- computed: {
- allowNonSquareEmoji() {
- return useMergedConfigStore().mergedConfig.nonSquareEmoji
- },
- },
+ userProfileLink (user) {
+ return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
+ }
+ }
}
export default BasicUserCard
diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue
index 49786bf1b..29a60cd1e 100644
--- a/src/components/basic_user_card/basic_user_card.vue
+++ b/src/components/basic_user_card/basic_user_card.vue
@@ -23,22 +23,13 @@
:title="user.name"
class="basic-user-card-user-name"
>
-
- {{ $t('admin_dash.users.labels.name_colon') }}
- {{ ' ' }}
-
-
- {{ $t('admin_dash.users.labels.handle_colon') }}
- {{ ' ' }}
-
.basic-user-card {
display: flex;
- flex: 1 1 10em;
- min-width: 1em;
+ flex: 1 0;
margin: 0;
- line-height: 1.25;
--emoji-size: 1em;
@@ -79,7 +68,7 @@
&-user-name-value,
&-screen-name {
- display: inline;
+ display: inline-block;
max-width: 100%;
overflow: hidden;
white-space: nowrap;
diff --git a/src/components/block_card/block_card.js b/src/components/block_card/block_card.js
index 6484accff..0bf4e37bc 100644
--- a/src/components/block_card/block_card.js
+++ b/src/components/block_card/block_card.js
@@ -1,50 +1,40 @@
-import { mapState } from 'pinia'
-
-import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
-import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue'
-
-import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
+import BasicUserCard from '../basic_user_card/basic_user_card.vue'
const BlockCard = {
props: ['userId'],
+ data () {
+ return {
+ progress: false
+ }
+ },
computed: {
- user() {
+ user () {
return this.$store.getters.findUser(this.userId)
},
- relationship() {
+ relationship () {
return this.$store.getters.relationship(this.userId)
},
- blocked() {
+ blocked () {
return this.relationship.blocking
- },
- blockExpiryAvailable() {
- return Object.hasOwn(this.user, 'block_expires_at')
- },
- blockExpiry() {
- return this.user.block_expires_at === false
- ? this.$t('user_card.block_expires_forever')
- : this.$t('user_card.block_expires_at', [
- new Date(this.user.mute_expires_at).toLocaleString(),
- ])
- },
- ...mapState(useInstanceCapabilitiesStore, ['blockExpiration']),
+ }
},
components: {
- BasicUserCard,
- UserTimedFilterModal,
+ BasicUserCard
},
methods: {
- unblockUser() {
- this.$store.dispatch('unblockUser', this.user.id)
+ unblockUser () {
+ this.progress = true
+ this.$store.dispatch('unblockUser', this.user.id).then(() => {
+ this.progress = false
+ })
},
- blockUser() {
- if (this.blockExpiration) {
- this.$refs.timedBlockDialog.optionallyPrompt()
- } else {
- this.$store.dispatch('blockUser', { id: this.user.id })
- }
- },
- },
+ blockUser () {
+ this.progress = true
+ this.$store.dispatch('blockUser', this.user.id).then(() => {
+ this.progress = false
+ })
+ }
+ }
}
export default BlockCard
diff --git a/src/components/block_card/block_card.vue b/src/components/block_card/block_card.vue
index be6069c14..b14ef8448 100644
--- a/src/components/block_card/block_card.vue
+++ b/src/components/block_card/block_card.vue
@@ -1,36 +1,34 @@
-
+
-
- {{ blockExpiry }}
-
- {{ ' ' }}
- {{ $t('user_card.unblock') }}
+
+ {{ $t('user_card.unblock_progress') }}
+
+
+ {{ $t('user_card.unblock') }}
+
- {{ $t('user_card.block') }}
+
+ {{ $t('user_card.block_progress') }}
+
+
+ {{ $t('user_card.block') }}
+
-
-
-
-
+
diff --git a/src/components/bookmark_folder_card/bookmark_folder_card.js b/src/components/bookmark_folder_card/bookmark_folder_card.js
new file mode 100644
index 000000000..bf274d9d7
--- /dev/null
+++ b/src/components/bookmark_folder_card/bookmark_folder_card.js
@@ -0,0 +1,22 @@
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faEllipsisH
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faEllipsisH
+)
+
+const BookmarkFolderCard = {
+ props: [
+ 'folder',
+ 'allBookmarks'
+ ],
+ computed: {
+ firstLetter () {
+ return this.folder ? this.folder.name[0] : null
+ }
+ }
+}
+
+export default BookmarkFolderCard
diff --git a/src/components/bookmark_folder_card/bookmark_folder_card.vue b/src/components/bookmark_folder_card/bookmark_folder_card.vue
new file mode 100644
index 000000000..9e8bef618
--- /dev/null
+++ b/src/components/bookmark_folder_card/bookmark_folder_card.vue
@@ -0,0 +1,111 @@
+
+
+
+
+
+ {{ $t('nav.all_bookmarks') }}
+
+
+
+
+
+
+
+ {{ folder.emoji }}
+
+
+ {{ firstLetter }} {{ folder.name }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/bookmark_folder_edit/bookmark_folder_edit.js b/src/components/bookmark_folder_edit/bookmark_folder_edit.js
index ae5aebedd..bb96585bf 100644
--- a/src/components/bookmark_folder_edit/bookmark_folder_edit.js
+++ b/src/components/bookmark_folder_edit/bookmark_folder_edit.js
@@ -1,13 +1,10 @@
-import EmojiPicker from 'src/components/emoji_picker/emoji_picker.vue'
-
-import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
-import { useInterfaceStore } from 'src/stores/interface.js'
-import { useOAuthStore } from 'src/stores/oauth.js'
-
-import { fetchBookmarkFolders } from 'src/api/user.js'
+import EmojiPicker from '../emoji_picker/emoji_picker.vue'
+import apiService from '../../services/api/api.service'
+import { useInterfaceStore } from 'src/stores/interface'
+import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders'
const BookmarkFolderEdit = {
- data() {
+ data () {
return {
name: '',
nameDraft: '',
@@ -16,61 +13,54 @@ const BookmarkFolderEdit = {
emojiDraft: '',
emojiUrlDraft: null,
emojiPickerExpanded: false,
- reallyDelete: false,
+ reallyDelete: false
}
},
components: {
- EmojiPicker,
+ EmojiPicker
},
- created() {
+ created () {
if (!this.id) return
+ const credentials = this.$store.state.users.currentUser.credentials
+ apiService.fetchBookmarkFolders({ credentials })
+ .then((folders) => {
+ const folder = folders.find(folder => folder.id === this.id)
+ if (!folder) return
- fetchBookmarkFolders({
- credentials: useOAuthStore().token,
- }).then(({ data: folders }) => {
- const folder = folders.find((folder) => folder.id === this.id)
- if (!folder) return
-
- this.nameDraft = this.name = folder.name
- this.emojiDraft = this.emoji = folder.emoji
- this.emojiUrlDraft = this.emojiUrl = folder.emoji_url
- })
+ this.nameDraft = this.name = folder.name
+ this.emojiDraft = this.emoji = folder.emoji
+ this.emojiUrlDraft = this.emojiUrl = folder.emoji_url
+ })
},
computed: {
- id() {
+ id () {
return this.$route.params.id
- },
+ }
},
methods: {
- selectEmoji(event) {
+ selectEmoji (event) {
this.emojiDraft = event.insertion
this.emojiUrlDraft = event.insertionUrl
},
- showEmojiPicker() {
+ showEmojiPicker () {
if (!this.emojiPickerExpanded) {
this.$refs.picker.showPicker()
}
},
- onShowPicker() {
+ onShowPicker () {
this.emojiPickerExpanded = true
},
- onClosePicker() {
+ onClosePicker () {
this.emojiPickerExpanded = false
},
- updateFolder() {
- useBookmarkFoldersStore()
- .updateBookmarkFolder({
- folderId: this.id,
- name: this.nameDraft,
- emoji: this.emojiDraft,
- })
+ updateFolder () {
+ useBookmarkFoldersStore().updateBookmarkFolder({ folderId: this.id, name: this.nameDraft, emoji: this.emojiDraft })
.then(() => {
this.$router.push({ name: 'bookmark-folders' })
})
},
- createFolder() {
- useBookmarkFoldersStore()
- .createBookmarkFolder({ name: this.nameDraft, emoji: this.emojiDraft })
+ createFolder () {
+ useBookmarkFoldersStore().createBookmarkFolder({ name: this.nameDraft, emoji: this.emojiDraft })
.then(() => {
this.$router.push({ name: 'bookmark-folders' })
})
@@ -78,15 +68,15 @@ const BookmarkFolderEdit = {
useInterfaceStore().pushGlobalNotice({
messageKey: 'bookmark_folders.error',
messageArgs: [e.message],
- level: 'error',
+ level: 'error'
})
})
},
- deleteFolder() {
+ deleteFolder () {
useBookmarkFoldersStore().deleteBookmarkFolder({ folderId: this.id })
this.$router.push({ name: 'bookmark-folders' })
- },
- },
+ }
+ }
}
export default BookmarkFolderEdit
diff --git a/src/components/bookmark_folders/bookmark_folders.js b/src/components/bookmark_folders/bookmark_folders.js
index cd12d4c3a..096f3769d 100644
--- a/src/components/bookmark_folders/bookmark_folders.js
+++ b/src/components/bookmark_folders/bookmark_folders.js
@@ -1,29 +1,28 @@
-import FolderCard from 'src/components/folder_card/folder_card.vue'
-
-import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
+import BookmarkFolderCard from '../bookmark_folder_card/bookmark_folder_card.vue'
+import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders'
const BookmarkFolders = {
- data() {
+ data () {
return {
- isNew: false,
+ isNew: false
}
},
components: {
- FolderCard,
+ BookmarkFolderCard
},
computed: {
- bookmarkFolders() {
+ bookmarkFolders () {
return useBookmarkFoldersStore().allFolders
- },
+ }
},
methods: {
- cancelNewFolder() {
+ cancelNewFolder () {
this.isNew = false
},
- newFolder() {
+ newFolder () {
this.isNew = true
- },
- },
+ }
+ }
}
export default BookmarkFolders
diff --git a/src/components/bookmark_folders/bookmark_folders.vue b/src/components/bookmark_folders/bookmark_folders.vue
index 56c9b62ce..fdd461064 100644
--- a/src/components/bookmark_folders/bookmark_folders.vue
+++ b/src/components/bookmark_folders/bookmark_folders.vue
@@ -12,28 +12,14 @@
-
-
-
-
- {{ $t('nav.all_bookmarks') }}
-
-
-
+
diff --git a/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js b/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js
index be1fb06ea..dc46b91b3 100644
--- a/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js
+++ b/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js
@@ -1,20 +1,20 @@
import { mapState } from 'pinia'
-
-import { getBookmarkFolderEntries } from 'src/components/navigation/filter.js'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
-
-import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
+import { getBookmarkFolderEntries } from 'src/components/navigation/filter.js'
+import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders'
export const BookmarkFoldersMenuContent = {
- props: ['showPin'],
+ props: [
+ 'showPin'
+ ],
components: {
- NavigationEntry,
+ NavigationEntry
},
computed: {
...mapState(useBookmarkFoldersStore, {
- folders: getBookmarkFolderEntries,
- }),
- },
+ folders: getBookmarkFolderEntries
+ })
+ }
}
export default BookmarkFoldersMenuContent
diff --git a/src/components/bookmark_timeline/bookmark_timeline.js b/src/components/bookmark_timeline/bookmark_timeline.js
index bf633c18c..9571d630f 100644
--- a/src/components/bookmark_timeline/bookmark_timeline.js
+++ b/src/components/bookmark_timeline/bookmark_timeline.js
@@ -1,38 +1,32 @@
-import Timeline from 'src/components/timeline/timeline.vue'
+import Timeline from '../timeline/timeline.vue'
const Bookmarks = {
- created() {
+ created () {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
- this.$store.dispatch('startFetchingTimeline', {
- timeline: 'bookmarks',
- bookmarkFolderId: this.folderId || null,
- })
+ this.$store.dispatch('startFetchingTimeline', { timeline: 'bookmarks', bookmarkFolderId: this.folderId || null })
},
components: {
- Timeline,
+ Timeline
},
computed: {
- folderId() {
+ folderId () {
return this.$route.params.id
},
- timeline() {
+ timeline () {
return this.$store.state.statuses.timelines.bookmarks
- },
+ }
},
watch: {
- folderId() {
+ folderId () {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
this.$store.dispatch('stopFetchingTimeline', 'bookmarks')
- this.$store.dispatch('startFetchingTimeline', {
- timeline: 'bookmarks',
- bookmarkFolderId: this.folderId || null,
- })
- },
+ this.$store.dispatch('startFetchingTimeline', { timeline: 'bookmarks', bookmarkFolderId: this.folderId || null })
+ }
},
- unmounted() {
+ unmounted () {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
this.$store.dispatch('stopFetchingTimeline', 'bookmarks')
- },
+ }
}
export default Bookmarks
diff --git a/src/components/border.style.js b/src/components/border.style.js
index e7cc31c57..7f2c30163 100644
--- a/src/components/border.style.js
+++ b/src/components/border.style.js
@@ -6,8 +6,8 @@ export default {
{
directives: {
textColor: '$mod(--parent 10)',
- textAuto: 'no-auto',
- },
- },
- ],
+ textAuto: 'no-auto'
+ }
+ }
+ ]
}
diff --git a/src/components/bubble_timeline/bubble_timeline.js b/src/components/bubble_timeline/bubble_timeline.js
index fcbc31ad2..6f73dd2b8 100644
--- a/src/components/bubble_timeline/bubble_timeline.js
+++ b/src/components/bubble_timeline/bubble_timeline.js
@@ -1,20 +1,18 @@
-import Timeline from 'src/components/timeline/timeline.vue'
-
+import Timeline from '../timeline/timeline.vue'
const BubbleTimeline = {
components: {
- Timeline,
+ Timeline
},
computed: {
- timeline() {
- return this.$store.state.statuses.timelines.bubble
- },
+ timeline () { return this.$store.state.statuses.timelines.bubble }
},
- created() {
+ created () {
this.$store.dispatch('startFetchingTimeline', { timeline: 'bubble' })
},
- unmounted() {
+ unmounted () {
this.$store.dispatch('stopFetchingTimeline', 'bubble')
- },
+ }
+
}
export default BubbleTimeline
diff --git a/src/components/button.style.js b/src/components/button.style.js
index 1644accc4..887ff91b5 100644
--- a/src/components/button.style.js
+++ b/src/components/button.style.js
@@ -10,24 +10,26 @@ export default {
// normal: '' // normal state is implicitly added, it is always included
toggled: '.toggled',
focused: ':focus-within',
- pressed: ':active',
+ pressed: ':focus:active',
hover: ':is(:hover, :focus-visible):not(:disabled)',
- disabled: ':disabled',
+ disabled: ':disabled'
},
// Variants are mutually exclusive, each component implicitly has "normal" variant, and all other variants inherit from it.
variants: {
// Variants save on computation time since adding new variant just adds one more "set".
// normal: '', // you can override normal variant, it will be appenended to the main class
- danger: '.-danger',
- transparent: '.-transparent',
+ danger: '.danger'
// Overall the compuation difficulty is N*((1/6)M^3+M) where M is number of distinct states and N is number of variants.
// This (currently) is further multipled by number of places where component can exist.
},
editor: {
- aspect: '2 / 1',
+ aspect: '2 / 1'
},
// This lists all other components that can possibly exist within one. Recursion is currently not supported (and probably won't be supported ever).
- validInnerComponents: ['Text', 'Icon'],
+ validInnerComponents: [
+ 'Text',
+ 'Icon'
+ ],
// Default rules, used as "default theme", essentially.
defaultRules: [
{
@@ -36,11 +38,9 @@ export default {
'--buttonDefaultHoverGlow': 'shadow | 0 0 1 2 --text / 0.4',
'--buttonDefaultFocusGlow': 'shadow | 0 0 1 2 --link / 0.5',
'--buttonDefaultShadow': 'shadow | 0 0 2 #000000',
- '--buttonDefaultBevel':
- 'shadow | $borderSide(#FFFFFF top 0.2 1), $borderSide(#000000 bottom 0.2 1)',
- '--buttonPressedBevel':
- 'shadow | inset 0 0 4 #000000, $borderSide(#FFFFFF bottom 0.2 1), $borderSide(#000000 top 0.2 1)',
- },
+ '--buttonDefaultBevel': 'shadow | $borderSide(#FFFFFF top 0.2 1), $borderSide(#000000 bottom 0.2 1)',
+ '--buttonPressedBevel': 'shadow | inset 0 0 4 #000000, $borderSide(#FFFFFF bottom 0.2 1), $borderSide(#000000 top 0.2 1)'
+ }
},
{
// component: 'Button', // no need to specify components every time unless you're specifying how other component should look
@@ -48,128 +48,89 @@ export default {
directives: {
background: '--fg',
shadow: ['--buttonDefaultShadow', '--buttonDefaultBevel'],
- roundness: 3,
- },
- },
- {
- variant: 'danger',
- directives: {
- background: '$blend(--cRed 0.25 --inheritedBackground)',
- },
- },
- {
- variant: 'transparent',
- directives: {
- opacity: 0.5,
- },
- },
- {
- component: 'Text',
- parent: {
- component: 'Button',
- variant: 'transparent',
- },
- directives: {
- textColor: '--text',
- },
- },
- {
- component: 'Icon',
- parent: {
- component: 'Button',
- variant: 'transparent',
- },
- directives: {
- textColor: '--text',
- },
+ roundness: 3
+ }
},
{
state: ['hover'],
directives: {
- shadow: ['--buttonDefaultHoverGlow', '--buttonDefaultBevel'],
- },
+ shadow: ['--buttonDefaultHoverGlow', '--buttonDefaultBevel']
+ }
},
{
state: ['focused'],
directives: {
- shadow: ['--buttonDefaultFocusGlow', '--buttonDefaultBevel'],
- },
+ shadow: ['--buttonDefaultFocusGlow', '--buttonDefaultBevel']
+ }
},
{
state: ['pressed'],
directives: {
- shadow: ['--buttonDefaultShadow', '--buttonPressedBevel'],
- },
+ shadow: ['--buttonDefaultShadow', '--buttonPressedBevel']
+ }
},
{
state: ['pressed', 'hover'],
directives: {
- shadow: ['--buttonPressedBevel', '--buttonDefaultHoverGlow'],
- },
+ shadow: ['--buttonPressedBevel', '--buttonDefaultHoverGlow']
+ }
},
{
state: ['toggled'],
directives: {
background: '--accent,-24.2',
- shadow: ['--buttonDefaultShadow', '--buttonPressedBevel'],
- },
+ shadow: ['--buttonDefaultShadow', '--buttonPressedBevel']
+ }
},
{
state: ['toggled', 'hover'],
directives: {
background: '--accent,-24.2',
- shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'],
- },
+ shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel']
+ }
},
{
state: ['toggled', 'focused'],
directives: {
background: '--accent,-24.2',
- shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'],
- },
- },
- {
- state: ['toggled', 'hover', 'focused'],
- directives: {
- background: '--accent,-24.2',
- shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'],
- },
+ shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel']
+ }
},
{
state: ['toggled', 'disabled'],
directives: {
background: '$blend(--accent 0.25 --parent)',
- shadow: ['--buttonPressedBevel'],
- },
+ shadow: ['--buttonPressedBevel']
+ }
},
{
state: ['disabled'],
directives: {
background: '$blend(--inheritedBackground 0.25 --parent)',
- shadow: ['--buttonDefaultBevel'],
- },
+ shadow: ['--buttonDefaultBevel']
+ }
},
{
component: 'Text',
parent: {
component: 'Button',
- state: ['disabled'],
+ state: ['disabled']
},
directives: {
textOpacity: 0.25,
- textOpacityMode: 'blend',
- },
+ textOpacityMode: 'blend'
+ }
},
{
component: 'Icon',
parent: {
component: 'Button',
- state: ['disabled'],
+ state: ['disabled']
},
directives: {
textOpacity: 0.25,
- textOpacityMode: 'blend',
- },
- },
- ],
+ textOpacityMode: 'blend'
+ }
+ }
+ ]
}
diff --git a/src/components/button_unstyled.style.js b/src/components/button_unstyled.style.js
index c35fb8f69..9e1a2ca90 100644
--- a/src/components/button_unstyled.style.js
+++ b/src/components/button_unstyled.style.js
@@ -7,86 +7,91 @@ export default {
toggled: '.toggled',
disabled: ':disabled',
hover: ':is(:hover, :focus-visible):not(:disabled)',
- focused: ':focus-within:not(:is(:focus-visible))',
+ focused: ':focus-within:not(:is(:focus-visible))'
},
- validInnerComponents: ['Text', 'Link', 'Icon', 'Badge'],
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Badge'
+ ],
defaultRules: [
{
directives: {
- shadow: [],
- },
+ shadow: []
+ }
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
- state: ['hover'],
+ state: ['hover']
},
directives: {
- textColor: '--parent--text',
- },
+ textColor: '--parent--text'
+ }
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
- state: ['toggled'],
+ state: ['toggled']
},
directives: {
- textColor: '--parent--text',
- },
+ textColor: '--parent--text'
+ }
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
- state: ['toggled', 'hover'],
+ state: ['toggled', 'hover']
},
directives: {
- textColor: '--parent--text',
- },
+ textColor: '--parent--text'
+ }
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
- state: ['toggled', 'focused'],
+ state: ['toggled', 'focused']
},
directives: {
- textColor: '--parent--text',
- },
+ textColor: '--parent--text'
+ }
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
- state: ['toggled', 'focused', 'hover'],
+ state: ['toggled', 'focused', 'hover']
},
directives: {
- textColor: '--parent--text',
- },
+ textColor: '--parent--text'
+ }
},
{
component: 'Text',
parent: {
component: 'ButtonUnstyled',
- state: ['disabled'],
+ state: ['disabled']
},
directives: {
textOpacity: 0.25,
- textOpacityMode: 'blend',
- },
+ textOpacityMode: 'blend'
+ }
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
- state: ['disabled'],
+ state: ['disabled']
},
directives: {
textOpacity: 0.25,
- textOpacityMode: 'blend',
- },
- },
- ],
+ textOpacityMode: 'blend'
+ }
+ }
+ ]
}
diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js
index 16a03ab1d..56f389e60 100644
--- a/src/components/chat/chat.js
+++ b/src/components/chat/chat.js
@@ -1,34 +1,25 @@
-import { throttle } from 'lodash'
-import { mapState as mapPiniaState } from 'pinia'
+import _ from 'lodash'
+import { WSConnectionStatus } from '../../services/api/api.service.js'
import { mapGetters, mapState } from 'vuex'
-
-import ChatMessage from 'src/components/chat_message/chat_message.vue'
-import ChatTitle from 'src/components/chat_title/chat_title.vue'
-import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
+import { mapState as mapPiniaState } from 'pinia'
+import ChatMessage from '../chat_message/chat_message.vue'
+import PostStatusForm from '../post_status_form/post_status_form.vue'
+import ChatTitle from '../chat_title/chat_title.vue'
import chatService from '../../services/chat_service/chat_service.js'
-import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js'
import { promiseInterval } from '../../services/promise_interval/promise_interval.js'
-import {
- getNewTopPosition,
- getScrollPosition,
- isBottomedOut,
- isScrollable,
-} from './chat_layout_utils.js'
-
-import { useInterfaceStore } from 'src/stores/interface.js'
-import { useOAuthStore } from 'src/stores/oauth.js'
-
-import {
- chatMessages,
- getOrCreateChat,
- sendChatMessage,
-} from 'src/api/chats.js'
-import { WSConnectionStatus } from 'src/api/websocket.js'
-
+import { getScrollPosition, getNewTopPosition, isBottomedOut, isScrollable } from './chat_layout_utils.js'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons'
+import {
+ faChevronDown,
+ faChevronLeft
+} from '@fortawesome/free-solid-svg-icons'
+import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js'
+import { useInterfaceStore } from 'src/stores/interface.js'
-library.add(faChevronDown, faChevronLeft)
+library.add(
+ faChevronDown,
+ faChevronLeft
+)
const BOTTOMED_OUT_OFFSET = 10
const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 10
@@ -40,94 +31,78 @@ const Chat = {
components: {
ChatMessage,
ChatTitle,
- PostStatusForm,
+ PostStatusForm
},
- data() {
+ data () {
return {
jumpToBottomButtonVisible: false,
hoveredMessageChainId: undefined,
lastScrollPosition: {},
scrollableContainerHeight: '100%',
errorLoadingChat: false,
- messageRetriers: {},
+ messageRetriers: {}
}
},
- created() {
+ created () {
this.startFetching()
window.addEventListener('resize', this.handleResize)
},
- mounted() {
+ mounted () {
window.addEventListener('scroll', this.handleScroll)
if (typeof document.hidden !== 'undefined') {
- document.addEventListener(
- 'visibilitychange',
- this.handleVisibilityChange,
- false,
- )
+ document.addEventListener('visibilitychange', this.handleVisibilityChange, false)
}
this.$nextTick(() => {
this.handleResize()
})
},
- unmounted() {
+ unmounted () {
window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('resize', this.handleResize)
- if (typeof document.hidden !== 'undefined')
- document.removeEventListener(
- 'visibilitychange',
- this.handleVisibilityChange,
- false,
- )
+ if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
this.$store.dispatch('clearCurrentChat')
},
computed: {
- recipient() {
+ recipient () {
return this.currentChat && this.currentChat.account
},
- recipientId() {
+ recipientId () {
return this.$route.params.recipient_id
},
- formPlaceholder() {
+ formPlaceholder () {
if (this.recipient) {
- return this.$t('chats.message_user', {
- nickname: this.recipient.screen_name_ui,
- })
+ return this.$t('chats.message_user', { nickname: this.recipient.screen_name_ui })
} else {
return ''
}
},
- chatViewItems() {
+ chatViewItems () {
return chatService.getView(this.currentChatMessageService)
},
- newMessageCount() {
- return (
- this.currentChatMessageService &&
- this.currentChatMessageService.newMessageCount
- )
+ newMessageCount () {
+ return this.currentChatMessageService && this.currentChatMessageService.newMessageCount
},
- streamingEnabled() {
- return (
- this.mergedConfig.useStreamingApi &&
- this.mastoUserSocketStatus === WSConnectionStatus.JOINED
- )
+ streamingEnabled () {
+ return this.mergedConfig.useStreamingApi && this.mastoUserSocketStatus === WSConnectionStatus.JOINED
},
...mapGetters([
'currentChat',
'currentChatMessageService',
'findOpenedChatByRecipientId',
- 'mergedConfig',
+ 'mergedConfig'
]),
...mapPiniaState(useInterfaceStore, {
- mobileLayout: (store) => store.layoutType === 'mobile',
+ mobileLayout: store => store.layoutType === 'mobile'
}),
...mapState({
- mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus,
- currentUser: (state) => state.users.currentUser,
- }),
+ backendInteractor: state => state.api.backendInteractor,
+ mastoUserSocketStatus: state => state.api.mastoUserSocketStatus,
+ currentUser: state => state.users.currentUser
+ })
},
watch: {
- chatViewItems() {
+ chatViewItems () {
// We don't want to scroll to the bottom on a new message when the user is viewing older messages.
// Therefore we need to know whether the scroll position was at the bottom before the DOM update.
const bottomedOutBeforeUpdate = this.bottomedOut(BOTTOMED_OUT_OFFSET)
@@ -140,23 +115,23 @@ const Chat = {
$route: function () {
this.startFetching()
},
- mastoUserSocketStatus(newValue) {
+ mastoUserSocketStatus (newValue) {
if (newValue === WSConnectionStatus.JOINED) {
this.fetchChat({ isFirstFetch: true })
}
- },
+ }
},
methods: {
// Used to animate the avatar near the first message of the message chain when any message belonging to the chain is hovered
- onMessageHover({ isHovered, messageChainId }) {
+ onMessageHover ({ isHovered, messageChainId }) {
this.hoveredMessageChainId = isHovered ? messageChainId : undefined
},
- onFilesDropped() {
+ onFilesDropped () {
this.$nextTick(() => {
this.handleResize()
})
},
- handleVisibilityChange() {
+ handleVisibilityChange () {
this.$nextTick(() => {
if (!document.hidden && this.bottomedOut(BOTTOMED_OUT_OFFSET)) {
this.scrollDown({ forceRead: true })
@@ -164,7 +139,7 @@ const Chat = {
})
},
// "Sticks" scroll to bottom instead of top, helps with OSK resizing the viewport
- handleResize(opts = {}) {
+ handleResize (opts = {}) {
const { delayed = false } = opts
if (delayed) {
@@ -185,56 +160,40 @@ const Chat = {
this.lastScrollPosition = getScrollPosition()
})
},
- scrollDown(options = {}) {
+ scrollDown (options = {}) {
const { behavior = 'auto', forceRead = false } = options
this.$nextTick(() => {
- window.scrollTo({
- top: document.documentElement.scrollHeight,
- behavior,
- })
+ window.scrollTo({ top: document.documentElement.scrollHeight, behavior })
})
if (forceRead) {
this.readChat()
}
},
- readChat() {
- if (
- !(
- this.currentChatMessageService && this.currentChatMessageService.maxId
- )
- ) {
- return
- }
- if (document.hidden) {
- return
- }
+ readChat () {
+ if (!(this.currentChatMessageService && this.currentChatMessageService.maxId)) { return }
+ if (document.hidden) { return }
const lastReadId = this.currentChatMessageService.maxId
this.$store.dispatch('readChat', {
id: this.currentChat.id,
- lastReadId,
+ lastReadId
})
},
- bottomedOut(offset) {
+ bottomedOut (offset) {
return isBottomedOut(offset)
},
- reachedTop() {
+ reachedTop () {
return window.scrollY <= 0
},
- cullOlderCheck() {
+ cullOlderCheck () {
window.setTimeout(() => {
if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
- this.$store.dispatch(
- 'cullOlderMessages',
- this.currentChatMessageService.chatId,
- )
+ this.$store.dispatch('cullOlderMessages', this.currentChatMessageService.chatId)
}
}, 5000)
},
- handleScroll: throttle(function () {
+ handleScroll: _.throttle(function () {
this.lastScrollPosition = getScrollPosition()
- if (!this.currentChat) {
- return
- }
+ if (!this.currentChat) { return }
if (this.reachedTop()) {
this.fetchChat({ maxId: this.currentChatMessageService.minId })
@@ -254,40 +213,30 @@ const Chat = {
this.jumpToBottomButtonVisible = true
}
}, 200),
- handleScrollUp(positionBeforeLoading) {
+ handleScrollUp (positionBeforeLoading) {
const positionAfterLoading = getScrollPosition()
window.scrollTo({
- top: getNewTopPosition(positionBeforeLoading, positionAfterLoading),
+ top: getNewTopPosition(positionBeforeLoading, positionAfterLoading)
})
},
- fetchChat({ isFirstFetch = false, fetchLatest = false, maxId }) {
+ fetchChat ({ isFirstFetch = false, fetchLatest = false, maxId }) {
const chatMessageService = this.currentChatMessageService
- if (!chatMessageService) {
- return
- }
- if (fetchLatest && this.streamingEnabled) {
- return
- }
+ if (!chatMessageService) { return }
+ if (fetchLatest && this.streamingEnabled) { return }
const chatId = chatMessageService.chatId
const fetchOlderMessages = !!maxId
const sinceId = fetchLatest && chatMessageService.maxId
- return chatMessages({
- id: chatId,
- maxId,
- sinceId,
- credentials: useOAuthStore().token,
- }).then(({ data: messages }) => {
- // Clear the current chat in case we're recovering from a ws connection loss.
- if (isFirstFetch) {
- chatService.clear(chatMessageService)
- }
+ return this.backendInteractor.chatMessages({ id: chatId, maxId, sinceId })
+ .then((messages) => {
+ // Clear the current chat in case we're recovering from a ws connection loss.
+ if (isFirstFetch) {
+ chatService.clear(chatMessageService)
+ }
- const positionBeforeUpdate = getScrollPosition()
- this.$store
- .dispatch('addChatMessages', { chatId, messages })
- .then(() => {
+ const positionBeforeUpdate = getScrollPosition()
+ this.$store.dispatch('addChatMessages', { chatId, messages }).then(() => {
this.$nextTick(() => {
if (fetchOlderMessages) {
this.handleScrollUp(positionBeforeUpdate)
@@ -298,23 +247,17 @@ const Chat = {
// If this is the case, we want to fetch the messages until the scrollable container
// is fully populated so that the user has the ability to scroll up and load the history.
if (!isScrollable() && messages.length > 0) {
- this.fetchChat({
- maxId: this.currentChatMessageService.minId,
- })
+ this.fetchChat({ maxId: this.currentChatMessageService.minId })
}
})
})
- })
+ })
},
- async startFetching() {
+ async startFetching () {
let chat = this.findOpenedChatByRecipientId(this.recipientId)
if (!chat) {
try {
- const { data } = await getOrCreateChat({
- accountId: this.recipientId,
- credentials: useOAuthStore().token,
- })
- chat = data
+ chat = await this.backendInteractor.getOrCreateChat({ accountId: this.recipientId })
} catch (e) {
console.error('Error creating or getting a chat', e)
this.errorLoadingChat = true
@@ -328,14 +271,13 @@ const Chat = {
this.doStartFetching()
}
},
- doStartFetching() {
+ doStartFetching () {
this.$store.dispatch('startFetchingCurrentChat', {
- fetcher: () =>
- promiseInterval(() => this.fetchChat({ fetchLatest: true }), 5000),
+ fetcher: () => promiseInterval(() => this.fetchChat({ fetchLatest: true }), 5000)
})
this.fetchChat({ isFirstFetch: true })
},
- handleAttachmentPosting() {
+ handleAttachmentPosting () {
this.$nextTick(() => {
this.handleResize()
// When the posting form size changes because of a media attachment, we need an extra resize
@@ -343,11 +285,11 @@ const Chat = {
this.scrollDown({ forceRead: true })
})
},
- sendMessage({ status, media, idempotencyKey }) {
+ sendMessage ({ status, media, idempotencyKey }) {
const params = {
id: this.currentChat.id,
content: status,
- idempotencyKey,
+ idempotencyKey
}
if (media[0]) {
@@ -359,74 +301,52 @@ const Chat = {
chatId: this.currentChat.id,
content: status,
userId: this.currentUser.id,
- idempotencyKey,
+ idempotencyKey
})
- this.$store
- .dispatch('addChatMessages', {
- chatId: this.currentChat.id,
- messages: [fakeMessage],
- })
- .then(() => {
- this.handleAttachmentPosting()
- })
-
- return this.doSendMessage({
- params,
- fakeMessage,
- retriesLeft: MAX_RETRIES,
+ this.$store.dispatch('addChatMessages', {
+ chatId: this.currentChat.id,
+ messages: [fakeMessage]
+ }).then(() => {
+ this.handleAttachmentPosting()
})
+
+ return this.doSendMessage({ params, fakeMessage, retriesLeft: MAX_RETRIES })
},
- doSendMessage({ params, fakeMessage, retriesLeft = MAX_RETRIES }) {
+ doSendMessage ({ params, fakeMessage, retriesLeft = MAX_RETRIES }) {
if (retriesLeft <= 0) return
- sendChatMessage({
- params,
- credentials: useOAuthStore().token,
- })
- .then(({ data }) => {
+ this.backendInteractor.sendChatMessage(params)
+ .then(data => {
this.$store.dispatch('addChatMessages', {
chatId: this.currentChat.id,
updateMaxId: false,
- messages: [{ ...data, fakeId: fakeMessage.id }],
+ messages: [{ ...data, fakeId: fakeMessage.id }]
})
return data
})
- .catch((error) => {
+ .catch(error => {
console.error('Error sending message', error)
this.$store.dispatch('handleMessageError', {
chatId: this.currentChat.id,
fakeId: fakeMessage.id,
- isRetry: retriesLeft !== MAX_RETRIES,
+ isRetry: retriesLeft !== MAX_RETRIES
})
- if (
- (error.statusCode >= 500 && error.statusCode < 600) ||
- error.message === 'Failed to fetch'
- ) {
- this.messageRetriers[fakeMessage.id] = setTimeout(
- () => {
- this.doSendMessage({
- params,
- fakeMessage,
- retriesLeft: retriesLeft - 1,
- })
- },
- 1000 * 2 ** (MAX_RETRIES - retriesLeft),
- )
+ if ((error.statusCode >= 500 && error.statusCode < 600) || error.message === 'Failed to fetch') {
+ this.messageRetriers[fakeMessage.id] = setTimeout(() => {
+ this.doSendMessage({ params, fakeMessage, retriesLeft: retriesLeft - 1 })
+ }, 1000 * (2 ** (MAX_RETRIES - retriesLeft)))
}
return {}
})
return Promise.resolve(fakeMessage)
},
- goBack() {
- this.$router.push({
- name: 'chats',
- params: { username: this.currentUser.screen_name },
- })
- },
- },
+ goBack () {
+ this.$router.push({ name: 'chats', params: { username: this.currentUser.screen_name } })
+ }
+ }
}
export default Chat
diff --git a/src/components/chat/chat.style.js b/src/components/chat/chat.style.js
index 55cf657c2..9ae2b7d71 100644
--- a/src/components/chat/chat.style.js
+++ b/src/components/chat/chat.style.js
@@ -1,13 +1,19 @@
export default {
name: 'Chat',
selector: '.chat-message-list',
- validInnerComponents: ['Text', 'Link', 'Icon', 'Avatar', 'ChatMessage'],
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Avatar',
+ 'ChatMessage'
+ ],
defaultRules: [
{
directives: {
background: '--bg',
- blur: '5px',
- },
- },
- ],
+ blur: '5px'
+ }
+ }
+ ]
}
diff --git a/src/components/chat/chat.vue b/src/components/chat/chat.vue
index cedbdce69..dfd197e56 100644
--- a/src/components/chat/chat.vue
+++ b/src/components/chat/chat.vue
@@ -73,7 +73,6 @@
:disable-notice="true"
:disable-lock-warning="true"
:disable-polls="true"
- :disable-quotes="true"
:disable-sensitivity-checkbox="true"
:disable-submit="errorLoadingChat || !currentChat"
:disable-preview="true"
diff --git a/src/components/chat/chat_layout_utils.js b/src/components/chat/chat_layout_utils.js
index 10d0a5e45..c187892d9 100644
--- a/src/components/chat/chat_layout_utils.js
+++ b/src/components/chat/chat_layout_utils.js
@@ -3,17 +3,14 @@ export const getScrollPosition = () => {
return {
scrollTop: window.scrollY,
scrollHeight: document.documentElement.scrollHeight,
- offsetHeight: window.innerHeight,
+ offsetHeight: window.innerHeight
}
}
// A helper function that is used to keep the scroll position fixed as the new elements are added to the top
// Takes two scroll positions, before and after the update.
export const getNewTopPosition = (previousPosition, newPosition) => {
- return (
- previousPosition.scrollTop +
- (newPosition.scrollHeight - previousPosition.scrollHeight)
- )
+ return previousPosition.scrollTop + (newPosition.scrollHeight - previousPosition.scrollHeight)
}
export const isBottomedOut = (offset = 0) => {
diff --git a/src/components/chat_list/chat_list.js b/src/components/chat_list/chat_list.js
index 597fcc709..95708d1dd 100644
--- a/src/components/chat_list/chat_list.js
+++ b/src/components/chat_list/chat_list.js
@@ -1,38 +1,37 @@
-import { mapGetters, mapState } from 'vuex'
-
-import ChatListItem from 'src/components/chat_list_item/chat_list_item.vue'
-import ChatNew from 'src/components/chat_new/chat_new.vue'
-import List from 'src/components/list/list.vue'
+import { mapState, mapGetters } from 'vuex'
+import ChatListItem from '../chat_list_item/chat_list_item.vue'
+import ChatNew from '../chat_new/chat_new.vue'
+import List from '../list/list.vue'
const ChatList = {
components: {
ChatListItem,
List,
- ChatNew,
+ ChatNew
},
computed: {
...mapState({
- currentUser: (state) => state.users.currentUser,
+ currentUser: state => state.users.currentUser
}),
- ...mapGetters(['sortedChatList']),
+ ...mapGetters(['sortedChatList'])
},
- data() {
+ data () {
return {
- isNew: false,
+ isNew: false
}
},
- created() {
+ created () {
this.$store.dispatch('fetchChats', { latest: true })
},
methods: {
- cancelNewChat() {
+ cancelNewChat () {
this.isNew = false
this.$store.dispatch('fetchChats', { latest: true })
},
- newChat() {
+ newChat () {
this.isNew = true
- },
- },
+ }
+ }
}
export default ChatList
diff --git a/src/components/chat_list/chat_list.vue b/src/components/chat_list/chat_list.vue
index 2e79ea100..881df2988 100644
--- a/src/components/chat_list/chat_list.vue
+++ b/src/components/chat_list/chat_list.vue
@@ -22,7 +22,7 @@
v-if="sortedChatList.length > 0"
class="timeline"
>
-
+
state.users.currentUser,
+ currentUser: state => state.users.currentUser
}),
- attachmentInfo() {
- if (this.chat.lastMessage.attachments.length === 0) {
- return
- }
+ attachmentInfo () {
+ if (this.chat.lastMessage.attachments.length === 0) { return }
- const types = this.chat.lastMessage.attachments.map((file) => file.type)
+ const types = this.chat.lastMessage.attachments.map(file => fileType.fileType(file.mimetype))
if (types.includes('video')) {
return this.$t('file_type.video')
} else if (types.includes('audio')) {
@@ -36,36 +36,34 @@ const ChatListItem = {
return this.$t('file_type.file')
}
},
- messageForStatusContent() {
+ messageForStatusContent () {
const message = this.chat.lastMessage
const messageEmojis = message ? message.emojis : []
const isYou = message && message.account_id === this.currentUser.id
- const content = message ? this.attachmentInfo || message.content : ''
- const messagePreview = isYou
- ? `${this.$t('chats.you')} ${content}`
- : content
+ const content = message ? (this.attachmentInfo || message.content) : ''
+ const messagePreview = isYou ? `${this.$t('chats.you')} ${content}` : content
return {
summary: '',
emojis: messageEmojis,
raw_html: messagePreview,
text: messagePreview,
- attachments: [],
+ attachments: []
}
- },
+ }
},
methods: {
- openChat() {
+ openChat () {
if (this.chat.id) {
this.$router.push({
name: 'chat',
params: {
username: this.currentUser.screen_name,
- recipient_id: this.chat.account.id,
- },
+ recipient_id: this.chat.account.id
+ }
})
}
- },
- },
+ }
+ }
}
export default ChatListItem
diff --git a/src/components/chat_list_item/chat_list_item.scss b/src/components/chat_list_item/chat_list_item.scss
index d570c3e1b..dcef6380e 100644
--- a/src/components/chat_list_item/chat_list_item.scss
+++ b/src/components/chat_list_item/chat_list_item.scss
@@ -4,7 +4,6 @@
overflow: hidden;
box-sizing: border-box;
cursor: pointer;
- width: 100%;
:focus {
outline: none;
diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js
index 2066a8194..837f6d214 100644
--- a/src/components/chat_message/chat_message.js
+++ b/src/components/chat_message/chat_message.js
@@ -1,23 +1,24 @@
+import { mapState, mapGetters } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
-import { mapState } from 'vuex'
-
-import Attachment from 'src/components/attachment/attachment.vue'
-import ChatMessageDate from 'src/components/chat_message_date/chat_message_date.vue'
-import Gallery from 'src/components/gallery/gallery.vue'
-import LinkPreview from 'src/components/link-preview/link-preview.vue'
-import Popover from 'src/components/popover/popover.vue'
-import StatusContent from 'src/components/status_content/status_content.vue'
-import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
-import UserPopover from 'src/components/user_popover/user_popover.vue'
-
-import { useInstanceStore } from 'src/stores/instance.js'
-import { useInterfaceStore } from 'src/stores/interface'
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-
+import Popover from '../popover/popover.vue'
+import Attachment from '../attachment/attachment.vue'
+import UserAvatar from '../user_avatar/user_avatar.vue'
+import Gallery from '../gallery/gallery.vue'
+import LinkPreview from '../link-preview/link-preview.vue'
+import StatusContent from '../status_content/status_content.vue'
+import ChatMessageDate from '../chat_message_date/chat_message_date.vue'
+import { defineAsyncComponent } from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faEllipsisH, faTimes } from '@fortawesome/free-solid-svg-icons'
+import {
+ faTimes,
+ faEllipsisH
+} from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
-library.add(faTimes, faEllipsisH)
+library.add(
+ faTimes,
+ faEllipsisH
+)
const ChatMessage = {
name: 'ChatMessage',
@@ -26,7 +27,7 @@ const ChatMessage = {
'edited',
'noHeading',
'chatViewItem',
- 'hoveredMessageChain',
+ 'hoveredMessageChain'
],
emits: ['hover'],
components: {
@@ -37,80 +38,73 @@ const ChatMessage = {
Gallery,
LinkPreview,
ChatMessageDate,
- UserPopover,
+ UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
},
computed: {
// Returns HH:MM (hours and minutes) in local time.
- createdAt() {
+ createdAt () {
const time = this.chatViewItem.data.created_at
- return time.toLocaleTimeString('en', {
- hour: '2-digit',
- minute: '2-digit',
- hour12: false,
- })
+ return time.toLocaleTimeString('en', { hour: '2-digit', minute: '2-digit', hour12: false })
},
- isCurrentUser() {
+ isCurrentUser () {
return this.message.account_id === this.currentUser.id
},
- message() {
+ message () {
return this.chatViewItem.data
},
- isMessage() {
+ isMessage () {
return this.chatViewItem.type === 'message'
},
- messageForStatusContent() {
+ messageForStatusContent () {
return {
summary: '',
emojis: this.message.emojis,
raw_html: this.message.content || '',
text: this.message.content || '',
- attachments: this.message.attachments,
+ attachments: this.message.attachments
}
},
- hasAttachment() {
+ hasAttachment () {
return this.message.attachments.length > 0
},
...mapPiniaState(useInterfaceStore, {
- betterShadow: (store) => store.browserSupport.cssFilter,
+ betterShadow: store => store.browserSupport.cssFilter
}),
...mapState({
- currentUser: (state) => state.users.currentUser,
- restrictedNicknames: (state) => useInstanceStore().restrictedNicknames,
+ currentUser: state => state.users.currentUser,
+ restrictedNicknames: state => state.instance.restrictedNicknames
}),
- popoverMarginStyle() {
+ popoverMarginStyle () {
if (this.isCurrentUser) {
return {}
} else {
return { left: 50 }
}
},
- ...mapPiniaState(useMergedConfigStore, ['mergedConfig', 'findUser']),
+ ...mapGetters(['mergedConfig', 'findUser'])
},
- data() {
+ data () {
return {
hovered: false,
- menuOpened: false,
+ menuOpened: false
}
},
methods: {
- onHover(bool) {
- this.$emit('hover', {
- isHovered: bool,
- messageChainId: this.chatViewItem.messageChainId,
- })
+ onHover (bool) {
+ this.$emit('hover', { isHovered: bool, messageChainId: this.chatViewItem.messageChainId })
},
- async deleteMessage() {
+ async deleteMessage () {
const confirmed = window.confirm(this.$t('chats.delete_confirm'))
if (confirmed) {
await this.$store.dispatch('deleteChatMessage', {
messageId: this.chatViewItem.data.id,
- chatId: this.chatViewItem.data.chat_id,
+ chatId: this.chatViewItem.data.chat_id
})
}
this.hovered = false
this.menuOpened = false
- },
- },
+ }
+ }
}
export default ChatMessage
diff --git a/src/components/chat_message/chat_message.style.js b/src/components/chat_message/chat_message.style.js
index f7632bc6f..9b57ad371 100644
--- a/src/components/chat_message/chat_message.style.js
+++ b/src/components/chat_message/chat_message.style.js
@@ -2,21 +2,29 @@ export default {
name: 'ChatMessage',
selector: '.chat-message',
variants: {
- outgoing: '.outgoing',
+ outgoing: '.outgoing'
},
- validInnerComponents: ['Text', 'Icon', 'Border', 'PollGraph'],
+ validInnerComponents: [
+ 'Text',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'RichContent',
+ 'Attachment',
+ 'PollGraph'
+ ],
defaultRules: [
{
directives: {
background: '--bg, 2',
- backgroundNoCssColor: 'yes',
- },
+ backgroundNoCssColor: 'yes'
+ }
},
{
variant: 'outgoing',
directives: {
- background: '--bg, 5',
- },
- },
- ],
+ background: '--bg, 5'
+ }
+ }
+ ]
}
diff --git a/src/components/chat_message_date/chat_message_date.vue b/src/components/chat_message_date/chat_message_date.vue
index f0cadb6e7..98349b753 100644
--- a/src/components/chat_message_date/chat_message_date.vue
+++ b/src/components/chat_message_date/chat_message_date.vue
@@ -11,19 +11,16 @@ export default {
name: 'Timeago',
props: ['date'],
computed: {
- displayDate() {
+ displayDate () {
const today = new Date()
today.setHours(0, 0, 0, 0)
if (this.date.getTime() === today.getTime()) {
return this.$t('display_date.today')
} else {
- return this.date.toLocaleDateString(
- localeService.internalToBrowserLocale(this.$i18n.locale),
- { day: 'numeric', month: 'long' },
- )
+ return this.date.toLocaleDateString(localeService.internalToBrowserLocale(this.$i18n.locale), { day: 'numeric', month: 'long' })
}
- },
- },
+ }
+ }
}
diff --git a/src/components/chat_new/chat_new.js b/src/components/chat_new/chat_new.js
index 0ee4e292b..71585995a 100644
--- a/src/components/chat_new/chat_new.js
+++ b/src/components/chat_new/chat_new.js
@@ -1,39 +1,39 @@
-import { mapGetters, mapState } from 'vuex'
-
-import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
-import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
-
-import { useOAuthStore } from 'src/stores/oauth.js'
-
+import { mapState, mapGetters } from 'vuex'
+import BasicUserCard from '../basic_user_card/basic_user_card.vue'
+import UserAvatar from '../user_avatar/user_avatar.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons'
+import {
+ faSearch,
+ faChevronLeft
+} from '@fortawesome/free-solid-svg-icons'
-library.add(faSearch, faChevronLeft)
+library.add(
+ faSearch,
+ faChevronLeft
+)
const chatNew = {
components: {
BasicUserCard,
- UserAvatar,
+ UserAvatar
},
- data() {
+ data () {
return {
suggestions: [],
userIds: [],
loading: false,
- query: '',
+ query: ''
}
},
- async created() {
- const { chats } = await chats({
- credentials: useOAuthStore().token,
- })
- chats.forEach((chat) => this.suggestions.push(chat.account))
+ async created () {
+ const { chats } = await this.backendInteractor.chats()
+ chats.forEach(chat => this.suggestions.push(chat.account))
},
computed: {
- users() {
- return this.userIds.map((userId) => this.findUser(userId))
+ users () {
+ return this.userIds.map(userId => this.findUser(userId))
},
- availableUsers() {
+ availableUsers () {
if (this.query.length !== 0) {
return this.users
} else {
@@ -41,28 +41,29 @@ const chatNew = {
}
},
...mapState({
- currentUser: (state) => state.users.currentUser,
+ currentUser: state => state.users.currentUser,
+ backendInteractor: state => state.api.backendInteractor
}),
- ...mapGetters(['findUser']),
+ ...mapGetters(['findUser'])
},
methods: {
- goBack() {
+ goBack () {
this.$emit('cancel')
},
- goToChat(user) {
+ goToChat (user) {
this.$router.push({ name: 'chat', params: { recipient_id: user.id } })
},
- onInput() {
+ onInput () {
this.search(this.query)
},
- addUser(user) {
+ addUser (user) {
this.selectedUserIds.push(user.id)
this.query = ''
},
- removeUser(userId) {
- this.selectedUserIds = this.selectedUserIds.filter((id) => id !== userId)
+ removeUser (userId) {
+ this.selectedUserIds = this.selectedUserIds.filter(id => id !== userId)
},
- search(query) {
+ search (query) {
if (!query) {
this.loading = false
return
@@ -70,14 +71,13 @@ const chatNew = {
this.loading = true
this.userIds = []
- this.$store
- .dispatch('search', { q: query, resolve: true, type: 'accounts' })
- .then((data) => {
+ this.$store.dispatch('search', { q: query, resolve: true, type: 'accounts' })
+ .then(data => {
this.loading = false
- this.userIds = data.accounts.map((a) => a.id)
+ this.userIds = data.accounts.map(a => a.id)
})
- },
- },
+ }
+ }
}
export default chatNew
diff --git a/src/components/chat_title/chat_title.js b/src/components/chat_title/chat_title.js
index 1d12384cc..b87211265 100644
--- a/src/components/chat_title/chat_title.js
+++ b/src/components/chat_title/chat_title.js
@@ -1,25 +1,23 @@
-import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
-import UserPopover from 'src/components/user_popover/user_popover.vue'
-
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
+import UserAvatar from '../user_avatar/user_avatar.vue'
+import RichContent from 'src/components/rich_content/rich_content.jsx'
+import { defineAsyncComponent } from 'vue'
export default {
name: 'ChatTitle',
components: {
UserAvatar,
-
- UserPopover,
+ RichContent,
+ UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
},
- props: ['user', 'withAvatar'],
+ props: [
+ 'user', 'withAvatar'
+ ],
computed: {
- title() {
+ title () {
return this.user ? this.user.screen_name_ui : ''
},
- htmlTitle() {
+ htmlTitle () {
return this.user ? this.user.name_html : ''
- },
- allowNonSquareEmoji() {
- return useMergedConfigStore().mergedConfig.nonSquareEmoji
- },
- },
+ }
+ }
}
diff --git a/src/components/chat_title/chat_title.vue b/src/components/chat_title/chat_title.vue
index 313e66ce3..72660cca0 100644
--- a/src/components/chat_title/chat_title.vue
+++ b/src/components/chat_title/chat_title.vue
@@ -19,8 +19,6 @@
:title="'@'+(user && user.screen_name_ui)"
:html="htmlTitle"
:emoji="user.emoji || []"
- :allow-non-square-emoji="allowNonSquareEmoji"
- :is-local="user.is_local"
/>
diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue
index 4a3ea49bc..c8bba4c44 100644
--- a/src/components/checkbox/checkbox.vue
+++ b/src/components/checkbox/checkbox.vue
@@ -1,7 +1,7 @@
export default {
- props: ['radio', 'modelValue', 'indeterminate', 'disabled'],
+ props: [
+ 'radio',
+ 'modelValue',
+ 'indeterminate',
+ 'disabled'
+ ],
emits: ['update:modelValue'],
data: (vm) => ({
- indeterminateTransitionFix: vm.indeterminate,
+ indeterminateTransitionFix: vm.indeterminate
}),
watch: {
- indeterminate(e) {
+ indeterminate (e) {
if (e) {
this.indeterminateTransitionFix = true
}
- },
+ }
},
methods: {
- onTransitionEnd() {
+ onTransitionEnd () {
if (!this.indeterminate) {
this.indeterminateTransitionFix = false
}
- },
- },
+ }
+ }
}
diff --git a/src/components/color_input/color_input.scss b/src/components/color_input/color_input.scss
index fad2bfca7..3128381f5 100644
--- a/src/components/color_input/color_input.scss
+++ b/src/components/color_input/color_input.scss
@@ -1,27 +1,18 @@
.color-input {
display: inline-flex;
- flex-wrap: wrap;
- max-width: 10em;
-
- &.-compact {
- max-width: none;
- }
.label {
flex: 1 1 auto;
- grid-area: label;
}
.opt {
- grid-area: checkbox;
margin-right: 0.5em;
}
&-field.input {
- flex: 1 1 10em;
- max-width: 10em;
- grid-area: input;
- display: flex;
+ display: inline-flex;
+ flex: 0 0 0;
+ max-width: 9em;
align-items: stretch;
input {
diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue
index 208bdf2dc..bcdf435fc 100644
--- a/src/components/color_input/color_input.vue
+++ b/src/components/color_input/color_input.vue
@@ -1,7 +1,7 @@
-
-
+
+
diff --git a/src/components/confirm_modal/confirm_modal.js b/src/components/confirm_modal/confirm_modal.js
index dbabb4b45..3e2bc2cb7 100644
--- a/src/components/confirm_modal/confirm_modal.js
+++ b/src/components/confirm_modal/confirm_modal.js
@@ -1,9 +1,4 @@
-import DialogModal from 'src/components/dialog_modal/dialog_modal.vue'
-
-import { library } from '@fortawesome/fontawesome-svg-core'
-import { faCircleQuestion } from '@fortawesome/free-solid-svg-icons'
-
-library.add(faCircleQuestion)
+import DialogModal from '../dialog_modal/dialog_modal.vue'
/**
* This component emits the following events:
@@ -14,32 +9,30 @@ library.add(faCircleQuestion)
*/
const ConfirmModal = {
components: {
- DialogModal,
+ DialogModal
},
props: {
title: {
- type: String,
+ type: String
},
cancelText: {
- type: String,
+ type: String
},
confirmText: {
- type: String,
- },
- confirmDanger: {
- type: Boolean,
- },
+ type: String
+ }
},
emits: ['cancelled', 'accepted'],
- computed: {},
+ computed: {
+ },
methods: {
- onCancel() {
+ onCancel () {
this.$emit('cancelled')
},
- onAccept() {
+ onAccept () {
this.$emit('accepted')
- },
- },
+ }
+ }
}
export default ConfirmModal
diff --git a/src/components/confirm_modal/confirm_modal.vue b/src/components/confirm_modal/confirm_modal.vue
index 774da4180..9af00edc4 100644
--- a/src/components/confirm_modal/confirm_modal.vue
+++ b/src/components/confirm_modal/confirm_modal.vue
@@ -8,25 +8,11 @@
-
-
-
-
+
+
-
@@ -41,43 +27,3 @@
-
diff --git a/src/components/confirm_modal/mute_confirm.js b/src/components/confirm_modal/mute_confirm.js
index c2f5ff888..1bef9f620 100644
--- a/src/components/confirm_modal/mute_confirm.js
+++ b/src/components/confirm_modal/mute_confirm.js
@@ -1,92 +1,109 @@
-import { mapState } from 'pinia'
-import { defineAsyncComponent } from 'vue'
+import { unitToSeconds } from 'src/services/date_utils/date_utils.js'
+import { mapGetters } from 'vuex'
+import ConfirmModal from './confirm_modal.vue'
import Select from 'src/components/select/select.vue'
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-
export default {
props: ['type', 'user', 'status'],
emits: ['hide', 'show', 'muted'],
data: () => ({
showing: false,
+ muteExpiryAmount: 2,
+ muteExpiryUnit: 'hours'
}),
components: {
- ConfirmModal: defineAsyncComponent(
- () => import('src/components/confirm_modal/confirm_modal.vue'),
- ),
-
- Select,
+ ConfirmModal,
+ Select
},
computed: {
- domain() {
+ muteExpiryValue () {
+ unitToSeconds(this.muteExpiryUnit, this.muteExpiryAmount)
+ },
+ muteExpiryUnits () {
+ return ['minutes', 'hours', 'days']
+ },
+ domain () {
return this.user.fqn.split('@')[1]
},
- keypath() {
+ keypath () {
if (this.type === 'domain') {
- return 'user_card.mute_domain_confirm'
+ return 'status.mute_domain_confirm'
} else if (this.type === 'conversation') {
- return 'user_card.mute_conversation_confirm'
+ return 'status.mute_conversation_confirm'
+ } else {
+ return 'user_card.mute_confirm'
}
},
- conversationIsMuted() {
+ userIsMuted () {
+ return this.$store.getters.relationship(this.user.id).muting
+ },
+ conversationIsMuted () {
return this.status.conversation_muted
},
- domainIsMuted() {
- return new Set(this.$store.state.users.currentUser.domainMutes).has(
- this.domain,
- )
+ domainIsMuted () {
+ return new Set(this.$store.state.users.currentUser.domainMutes).has(this.domain)
},
- shouldConfirm() {
+ shouldConfirm () {
switch (this.type) {
case 'domain': {
return this.mergedConfig.modalOnMuteDomain
}
- default: {
- // conversation
+ case 'conversation': {
return this.mergedConfig.modalOnMuteConversation
}
+ default: {
+ return this.mergedConfig.modalOnMute
+ }
}
},
- ...mapState(useMergedConfigStore, ['mergedConfig']),
+ ...mapGetters(['mergedConfig'])
},
methods: {
- optionallyPrompt() {
+ optionallyPrompt () {
if (this.shouldConfirm) {
this.show()
} else {
this.doMute()
}
},
- show() {
+ show () {
this.showing = true
this.$emit('show')
},
- hide() {
+ hide () {
this.showing = false
this.$emit('hide')
},
- doMute() {
+ doMute () {
switch (this.type) {
case 'domain': {
if (!this.domainIsMuted) {
- this.$store.dispatch('muteDomain', this.domain)
+ this.$store.dispatch('muteDomain', { id: this.domain, expiresIn: this.muteExpiryValue })
} else {
- this.$store.dispatch('unmuteDomain', this.domain)
+ this.$store.dispatch('unmuteDomain', { id: this.domain })
}
break
}
case 'conversation': {
if (!this.conversationIsMuted) {
- this.$store.dispatch('muteConversation', { id: this.status.id })
+ this.$store.dispatch('muteConversation', { id: this.status.id, expiresIn: this.muteExpiryValue })
} else {
this.$store.dispatch('unmuteConversation', { id: this.status.id })
}
break
}
+ default: {
+ if (!this.userIsMuted) {
+ this.$store.dispatch('muteUser', { id: this.user.id, expiresIn: this.muteExpiryValue })
+ } else {
+ this.$store.dispatch('unmuteUser', { id: this.user.id })
+ }
+ break
+ }
}
this.$emit('muted')
this.hide()
- },
- },
+ }
+ }
}
diff --git a/src/components/confirm_modal/mute_confirm.vue b/src/components/confirm_modal/mute_confirm.vue
index 108a72477..bf7ab338f 100644
--- a/src/components/confirm_modal/mute_confirm.vue
+++ b/src/components/confirm_modal/mute_confirm.vue
@@ -1,5 +1,5 @@
-
-
+
+
+
+ {{ $t('user_card.mute_duration_prompt') }}
+
+
+ {{ ' ' }}
+
+
+ {{ $t(`time.unit.${unit}_short`, ['']) }}
+
+
+
+
+
diff --git a/src/components/contrast_ratio/contrast_ratio.vue b/src/components/contrast_ratio/contrast_ratio.vue
index 1f8b62809..93799e4c2 100644
--- a/src/components/contrast_ratio/contrast_ratio.vue
+++ b/src/components/contrast_ratio/contrast_ratio.vue
@@ -63,68 +63,54 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import {
faAdjust,
faExclamationTriangle,
- faThumbsUp,
+ faThumbsUp
} from '@fortawesome/free-solid-svg-icons'
-library.add(faAdjust, faExclamationTriangle, faThumbsUp)
+library.add(
+ faAdjust,
+ faExclamationTriangle,
+ faThumbsUp
+)
export default {
components: {
- Tooltip,
+ Tooltip
},
props: {
large: {
required: false,
type: Boolean,
- default: false,
+ default: false
},
// TODO: Make theme switcher compute theme initially so that contrast
// component won't be called without contrast data
contrast: {
required: false,
type: Object,
- default: () => ({
- /* no-op */
- }),
+ default: () => ({})
},
showRatio: {
required: false,
type: Boolean,
- default: false,
- },
+ default: false
+ }
},
computed: {
- hint() {
- const levelVal = this.contrast.aaa
- ? 'aaa'
- : this.contrast.aa
- ? 'aa'
- : 'bad'
+ hint () {
+ const levelVal = this.contrast.aaa ? 'aaa' : (this.contrast.aa ? 'aa' : 'bad')
const level = this.$t(`settings.style.common.contrast.level.${levelVal}`)
const context = this.$t('settings.style.common.contrast.context.text')
const ratio = this.contrast.text
- return this.$t('settings.style.common.contrast.hint', {
- level,
- context,
- ratio,
- })
+ return this.$t('settings.style.common.contrast.hint', { level, context, ratio })
},
- hint_18pt() {
- const levelVal = this.contrast.laaa
- ? 'aaa'
- : this.contrast.laa
- ? 'aa'
- : 'bad'
+ hint_18pt () {
+ const levelVal = this.contrast.laaa ? 'aaa' : (this.contrast.laa ? 'aa' : 'bad')
const level = this.$t(`settings.style.common.contrast.level.${levelVal}`)
const context = this.$t('settings.style.common.contrast.context.18pt')
const ratio = this.contrast.text
- return this.$t('settings.style.common.contrast.hint', {
- level,
- context,
- ratio,
- })
- },
- },
+ return this.$t('settings.style.common.contrast.hint', { level, context, ratio })
+ }
+ }
}
diff --git a/src/components/conversation-page/conversation-page.js b/src/components/conversation-page/conversation-page.js
index 84494332d..8f996be12 100644
--- a/src/components/conversation-page/conversation-page.js
+++ b/src/components/conversation-page/conversation-page.js
@@ -1,14 +1,14 @@
-import Conversation from 'src/components/conversation/conversation.vue'
+import Conversation from '../conversation/conversation.vue'
const conversationPage = {
components: {
- Conversation,
+ Conversation
},
computed: {
- statusId() {
+ statusId () {
return this.$route.params.id
- },
- },
+ }
+ }
}
export default conversationPage
diff --git a/src/components/conversation-page/conversation-page.vue b/src/components/conversation-page/conversation-page.vue
index df5dd4bbd..8cc0a55fe 100644
--- a/src/components/conversation-page/conversation-page.vue
+++ b/src/components/conversation-page/conversation-page.vue
@@ -1,7 +1,8 @@
-
diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index 1bc9f3858..491a8543f 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -1,26 +1,25 @@
-import { clone, filter, findIndex, get, reduce } from 'lodash'
+import { reduce, filter, findIndex, clone, get } from 'lodash'
+import Status from '../status/status.vue'
+import ThreadTree from '../thread_tree/thread_tree.vue'
+import { WSConnectionStatus } from '../../services/api/api.service.js'
+import { mapGetters, mapState } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
-import { mapState } from 'vuex'
-
-import QuickFilterSettings from 'src/components/quick_filter_settings/quick_filter_settings.vue'
-import QuickViewSettings from 'src/components/quick_view_settings/quick_view_settings.vue'
-import ThreadTree from 'src/components/thread_tree/thread_tree.vue'
-
+import QuickFilterSettings from '../quick_filter_settings/quick_filter_settings.vue'
+import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue'
import { useInterfaceStore } from 'src/stores/interface'
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-import { useOAuthStore } from 'src/stores/oauth.js'
-
-import { fetchConversation, fetchStatus } from 'src/api/public.js'
-import { WSConnectionStatus } from 'src/api/websocket.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faAngleDoubleDown,
faAngleDoubleLeft,
- faChevronLeft,
+ faChevronLeft
} from '@fortawesome/free-solid-svg-icons'
-library.add(faAngleDoubleDown, faAngleDoubleLeft, faChevronLeft)
+library.add(
+ faAngleDoubleDown,
+ faAngleDoubleLeft,
+ faChevronLeft
+)
const sortById = (a, b) => {
const idA = a.type === 'retweet' ? a.retweeted_status.id : a.id
@@ -44,130 +43,102 @@ const sortAndFilterConversation = (conversation, statusoid) => {
if (statusoid.type === 'retweet') {
conversation = filter(
conversation,
- (status) =>
- status.type === 'retweet' ||
- status.id !== statusoid.retweeted_status.id,
+ (status) => (status.type === 'retweet' || status.id !== statusoid.retweeted_status.id)
)
} else {
conversation = filter(conversation, (status) => status.type !== 'retweet')
}
- return conversation.filter((_) => _).sort(sortById)
+ return conversation.filter(_ => _).sort(sortById)
}
const conversation = {
- props: {
- statusId: {
- // Main thing
- type: String,
- required: true,
- },
- collapsable: {
- // Whether conversation can be collapsed
- // i.e. when it's not a page
- type: Boolean,
- default: false,
- },
- isPage: {
- // Whether conversation is rendered as a standalone page
- // as opposed to embedded into a timeline
- type: Boolean,
- default: false,
- },
- pinnedStatusIdsObject: {
- // Used for user profile, map of pinned statuses
- type: Object,
- default: null,
- },
- inProfile: {
- // Whether conversation is rendered in a user profile
- // used for overriding muted status
- type: Boolean,
- default: false,
- },
- profileUserId: {
- // used with inProfile, user id of the profile
- type: String,
- default: null,
- },
- virtualHidden: {
- // Whether conversation is suspended. Controls rendering of statuses
- type: Boolean,
- default: false,
- },
- },
- data() {
+ data () {
return {
highlight: null,
expanded: false,
threadDisplayStatusObject: {}, // id => 'showing' | 'hidden'
+ statusContentPropertiesObject: {},
inlineDivePosition: null,
- loadStatusError: null,
- unsuspendibleIds: new Set(),
+ loadStatusError: null
}
},
- created() {
+ props: [
+ 'statusId',
+ 'collapsable',
+ 'isPage',
+ 'pinnedStatusIdsObject',
+ 'inProfile',
+ 'profileUserId',
+ 'virtualHidden'
+ ],
+ created () {
if (this.isPage) {
this.fetchConversation()
}
},
computed: {
- maxDepthToShowByDefault() {
+ maxDepthToShowByDefault () {
// maxDepthInThread = max number of depths that is *visible*
// since our depth starts with 0 and "showing" means "showing children"
// there is a -2 here
- const maxDepth = this.mergedConfig.maxDepthInThread - 2
+ const maxDepth = this.$store.getters.mergedConfig.maxDepthInThread - 2
return maxDepth >= 1 ? maxDepth : 1
},
- streamingEnabled() {
- return (
- this.mergedConfig.useStreamingApi &&
- this.mastoUserSocketStatus === WSConnectionStatus.JOINED
- )
+ streamingEnabled () {
+ return this.mergedConfig.useStreamingApi && this.mastoUserSocketStatus === WSConnectionStatus.JOINED
},
- displayStyle() {
- return this.mergedConfig.conversationDisplay
+ displayStyle () {
+ return this.$store.getters.mergedConfig.conversationDisplay
},
- isTreeView() {
+ isTreeView () {
return !this.isLinearView
},
- treeViewIsSimple() {
- return !this.mergedConfig.conversationTreeAdvanced
+ treeViewIsSimple () {
+ return !this.$store.getters.mergedConfig.conversationTreeAdvanced
},
- isLinearView() {
+ isLinearView () {
return this.displayStyle === 'linear'
},
- shouldFadeAncestors() {
- return this.mergedConfig.conversationTreeFadeAncestors
+ shouldFadeAncestors () {
+ return this.$store.getters.mergedConfig.conversationTreeFadeAncestors
},
- otherRepliesButtonPosition() {
- return this.mergedConfig.conversationOtherRepliesButton
+ otherRepliesButtonPosition () {
+ return this.$store.getters.mergedConfig.conversationOtherRepliesButton
},
- showOtherRepliesButtonBelowStatus() {
+ showOtherRepliesButtonBelowStatus () {
return this.otherRepliesButtonPosition === 'below'
},
- showOtherRepliesButtonInsideStatus() {
+ showOtherRepliesButtonInsideStatus () {
return this.otherRepliesButtonPosition === 'inside'
},
- suspendable() {
- return this.unsuspendibleIds.size > 0
+ suspendable () {
+ if (this.isTreeView) {
+ return Object.entries(this.statusContentProperties)
+ .every(([, prop]) => !prop.replying && prop.mediaPlaying.length === 0)
+ }
+ if (this.$refs.statusComponent && this.$refs.statusComponent[0]) {
+ return this.$refs.statusComponent.every(s => s.suspendable)
+ } else {
+ return true
+ }
},
- hideStatus() {
+ hideStatus () {
return this.virtualHidden && this.suspendable
},
- status() {
+ status () {
return this.$store.state.statuses.allStatusesObject[this.statusId]
},
- originalStatusId() {
+ originalStatusId () {
if (this.status.retweeted_status) {
return this.status.retweeted_status.id
} else {
return this.statusId
}
},
- conversationId() {
+ conversationId () {
return this.getConversationId(this.statusId)
},
- conversation() {
+ conversation () {
if (!this.status) {
return []
}
@@ -176,9 +147,7 @@ const conversation = {
return [this.status]
}
- const conversation = clone(
- this.$store.state.statuses.conversationsObject[this.conversationId],
- )
+ const conversation = clone(this.$store.state.statuses.conversationsObject[this.conversationId])
const statusIndex = findIndex(conversation, { id: this.originalStatusId })
if (statusIndex !== -1) {
conversation[statusIndex] = this.status
@@ -186,188 +155,144 @@ const conversation = {
return sortAndFilterConversation(conversation, this.status)
},
- statusMap() {
+ statusMap () {
return this.conversation.reduce((res, s) => {
res[s.id] = s
return res
}, {})
},
- threadTree() {
- const reverseLookupTable = this.conversation.reduce(
- (table, status, index) => {
- table[status.id] = index
- return table
- },
- {},
- )
+ threadTree () {
+ const reverseLookupTable = this.conversation.reduce((table, status, index) => {
+ table[status.id] = index
+ return table
+ }, {})
- const threads = this.conversation.reduce(
- (a, cur) => {
- const id = cur.id
- a.forest[id] = this.getReplies(id).map((s) => s.id)
+ const threads = this.conversation.reduce((a, cur) => {
+ const id = cur.id
+ a.forest[id] = this.getReplies(id)
+ .map(s => s.id)
- return a
- },
- {
- forest: {},
- },
- )
+ return a
+ }, {
+ forest: {}
+ })
- const walk = (forest, topLevel, depth = 0, processed = {}) =>
- topLevel
- .map((id) => {
- if (processed[id]) {
- return []
- }
+ const walk = (forest, topLevel, depth = 0, processed = {}) => topLevel.map(id => {
+ if (processed[id]) {
+ return []
+ }
- processed[id] = true
- return [
- {
- status: this.conversation[reverseLookupTable[id]],
- id,
- depth,
- },
- walk(forest, forest[id], depth + 1, processed),
- ].reduce((a, b) => a.concat(b), [])
- })
- .reduce((a, b) => a.concat(b), [])
+ processed[id] = true
+ return [{
+ status: this.conversation[reverseLookupTable[id]],
+ id,
+ depth
+ }, walk(forest, forest[id], depth + 1, processed)].reduce((a, b) => a.concat(b), [])
+ }).reduce((a, b) => a.concat(b), [])
- const linearized = walk(
- threads.forest,
- this.topLevel.map((k) => k.id),
- )
+ const linearized = walk(threads.forest, this.topLevel.map(k => k.id))
return linearized
},
- replyIds() {
- return this.conversation
- .map((k) => k.id)
+ replyIds () {
+ return this.conversation.map(k => k.id)
.reduce((res, id) => {
- res[id] = (this.replies[id] || []).map((k) => k.id)
+ res[id] = (this.replies[id] || []).map(k => k.id)
return res
}, {})
},
- totalReplyCount() {
+ totalReplyCount () {
const sizes = {}
const subTreeSizeFor = (id) => {
if (sizes[id]) {
return sizes[id]
}
- sizes[id] =
- 1 +
- this.replyIds[id]
- .map((cid) => subTreeSizeFor(cid))
- .reduce((a, b) => a + b, 0)
+ sizes[id] = 1 + this.replyIds[id].map(cid => subTreeSizeFor(cid)).reduce((a, b) => a + b, 0)
return sizes[id]
}
- this.conversation.map((k) => k.id).map(subTreeSizeFor)
+ this.conversation.map(k => k.id).map(subTreeSizeFor)
return Object.keys(sizes).reduce((res, id) => {
res[id] = sizes[id] - 1 // exclude itself
return res
}, {})
},
- totalReplyDepth() {
+ totalReplyDepth () {
const depths = {}
const subTreeDepthFor = (id) => {
if (depths[id]) {
return depths[id]
}
- depths[id] =
- 1 +
- this.replyIds[id]
- .map((cid) => subTreeDepthFor(cid))
- .reduce((a, b) => (a > b ? a : b), 0)
+ depths[id] = 1 + this.replyIds[id].map(cid => subTreeDepthFor(cid)).reduce((a, b) => a > b ? a : b, 0)
return depths[id]
}
- this.conversation.map((k) => k.id).map(subTreeDepthFor)
+ this.conversation.map(k => k.id).map(subTreeDepthFor)
return Object.keys(depths).reduce((res, id) => {
res[id] = depths[id] - 1 // exclude itself
return res
}, {})
},
- depths() {
+ depths () {
return this.threadTree.reduce((a, k) => {
a[k.id] = k.depth
return a
}, {})
},
- topLevel() {
- const topLevel = this.conversation.reduce(
- (tl, cur) =>
- tl.filter(
- (k) =>
- this.getReplies(cur.id)
- .map((v) => v.id)
- .indexOf(k.id) === -1,
- ),
- this.conversation,
- )
+ topLevel () {
+ const topLevel = this.conversation.reduce((tl, cur) =>
+ tl.filter(k => this.getReplies(cur.id).map(v => v.id).indexOf(k.id) === -1), this.conversation)
return topLevel
},
- otherTopLevelCount() {
+ otherTopLevelCount () {
return this.topLevel.length - 1
},
- showingTopLevel() {
+ showingTopLevel () {
if (this.canDive && this.diveRoot) {
return [this.statusMap[this.diveRoot]]
}
return this.topLevel
},
- diveRoot() {
+ diveRoot () {
const statusId = this.inlineDivePosition || this.statusId
const isTopLevel = !this.parentOf(statusId)
return isTopLevel ? null : statusId
},
- diveDepth() {
+ diveDepth () {
return this.canDive && this.diveRoot ? this.depths[this.diveRoot] : 0
},
- diveMode() {
+ diveMode () {
return this.canDive && !!this.diveRoot
},
- shouldShowAllConversationButton() {
+ shouldShowAllConversationButton () {
// The "show all conversation" button tells the user that there exist
// other toplevel statuses, so do not show it if there is only a single root
- return (
- this.isTreeView &&
- this.isExpanded &&
- this.diveMode &&
- this.topLevel.length > 1
- )
+ return this.isTreeView && this.isExpanded && this.diveMode && this.topLevel.length > 1
},
- shouldShowAncestors() {
- return (
- this.isTreeView &&
- this.isExpanded &&
- this.ancestorsOf(this.diveRoot).length
- )
+ shouldShowAncestors () {
+ return this.isTreeView && this.isExpanded && this.ancestorsOf(this.diveRoot).length
},
- replies() {
+ replies () {
let i = 1
- return reduce(
- this.conversation,
- (result, { id, in_reply_to_status_id: irid }) => {
- if (irid) {
- result[irid] = result[irid] || []
- result[irid].push({
- name: `#${i}`,
- id,
- })
- }
- i++
- return result
- },
- {},
- )
+ return reduce(this.conversation, (result, { id, in_reply_to_status_id: irid }) => {
+ if (irid) {
+ result[irid] = result[irid] || []
+ result[irid].push({
+ name: `#${i}`,
+ id
+ })
+ }
+ i++
+ return result
+ }, {})
},
- isExpanded() {
+ isExpanded () {
return !!(this.expanded || this.isPage)
},
- hiddenStyle() {
+ hiddenStyle () {
const height = (this.status && this.status.virtualHeight) || '120px'
return this.virtualHidden ? { height } : {}
},
- threadDisplayStatus() {
+ threadDisplayStatus () {
return this.conversation.reduce((a, k) => {
const id = k.id
const depth = this.depths[id]
@@ -375,7 +300,7 @@ const conversation = {
if (this.threadDisplayStatusObject[id]) {
return this.threadDisplayStatusObject[id]
}
- if (depth - this.diveDepth <= this.maxDepthToShowByDefault) {
+ if ((depth - this.diveDepth) <= this.maxDepthToShowByDefault) {
return 'showing'
} else {
return 'hidden'
@@ -386,87 +311,106 @@ const conversation = {
return a
}, {})
},
- canDive() {
+ statusContentProperties () {
+ return this.conversation.reduce((a, k) => {
+ const id = k.id
+ const props = (() => {
+ const def = {
+ showingTall: false,
+ expandingSubject: false,
+ showingLongSubject: false,
+ isReplying: false,
+ mediaPlaying: []
+ }
+
+ if (this.statusContentPropertiesObject[id]) {
+ return {
+ ...def,
+ ...this.statusContentPropertiesObject[id]
+ }
+ }
+ return def
+ })()
+
+ a[id] = props
+ return a
+ }, {})
+ },
+ canDive () {
return this.isTreeView && this.isExpanded
},
- ...mapPiniaState(useMergedConfigStore, ['mergedConfig']),
+ maybeHighlight () {
+ return this.isExpanded ? this.highlight : null
+ },
+ ...mapGetters(['mergedConfig']),
...mapState({
- mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus,
+ mastoUserSocketStatus: state => state.api.mastoUserSocketStatus
}),
...mapPiniaState(useInterfaceStore, {
- mobileLayout: (store) => store.layoutType === 'mobile',
- }),
+ mobileLayout: store => store.layoutType === 'mobile'
+ })
},
components: {
+ Status,
ThreadTree,
QuickFilterSettings,
- QuickViewSettings,
+ QuickViewSettings
},
watch: {
- statusId(newVal, oldVal) {
+ statusId (newVal, oldVal) {
const newConversationId = this.getConversationId(newVal)
const oldConversationId = this.getConversationId(oldVal)
- if (
- newConversationId &&
- oldConversationId &&
- newConversationId === oldConversationId
- ) {
+ if (newConversationId && oldConversationId && newConversationId === oldConversationId) {
this.setHighlight(this.originalStatusId)
} else {
this.fetchConversation()
}
},
- expanded(value) {
+ expanded (value) {
if (value) {
this.fetchConversation()
} else {
this.resetDisplayState()
}
},
- virtualHidden() {
- this.$store.dispatch('setVirtualHeight', {
- statusId: this.statusId,
- height: `${this.$el.clientHeight}px`,
- })
- },
+ virtualHidden () {
+ this.$store.dispatch(
+ 'setVirtualHeight',
+ { statusId: this.statusId, height: `${this.$el.clientHeight}px` }
+ )
+ }
},
methods: {
- fetchConversation() {
+ fetchConversation () {
if (this.status) {
- fetchConversation({
- id: this.statusId,
- credentials: useOAuthStore().token,
- }).then(({ data: { ancestors, descendants } }) => {
- this.$store.dispatch('addNewStatuses', { statuses: ancestors })
- this.$store.dispatch('addNewStatuses', { statuses: descendants })
- this.setHighlight(this.originalStatusId)
- })
+ this.$store.state.api.backendInteractor.fetchConversation({ id: this.statusId })
+ .then(({ ancestors, descendants }) => {
+ this.$store.dispatch('addNewStatuses', { statuses: ancestors })
+ this.$store.dispatch('addNewStatuses', { statuses: descendants })
+ this.setHighlight(this.originalStatusId)
+ })
} else {
this.loadStatusError = null
- fetchStatus({
- id: this.statusId,
- credentials: useOAuthStore().token,
- })
- .then(({ data: status }) => {
+ this.$store.state.api.backendInteractor.fetchStatus({ id: this.statusId })
+ .then((status) => {
this.$store.dispatch('addNewStatuses', { statuses: [status] })
this.fetchConversation()
})
.catch((error) => {
- console.error(error)
this.loadStatusError = error
})
}
},
- isFocused(id) {
- return this.isExpanded && id === this.highlight
+ isFocused (id) {
+ return (this.isExpanded) && id === this.highlight
},
- getReplies(id) {
+ getReplies (id) {
return this.replies[id] || []
},
- maybeHighlight() {
+ getHighlight () {
return this.isExpanded ? this.highlight : null
},
- setHighlight(id) {
+ setHighlight (id) {
if (!id) return
this.highlight = id
@@ -477,38 +421,44 @@ const conversation = {
this.$store.dispatch('fetchFavsAndRepeats', id)
this.$store.dispatch('fetchEmojiReactionsBy', id)
},
- toggleExpanded() {
+ toggleExpanded () {
this.expanded = !this.expanded
},
- getConversationId(statusId) {
+ getConversationId (statusId) {
const status = this.$store.state.statuses.allStatusesObject[statusId]
- return get(
- status,
- 'retweeted_status.statusnet_conversation_id',
- get(status, 'statusnet_conversation_id'),
- )
+ return get(status, 'retweeted_status.statusnet_conversation_id', get(status, 'statusnet_conversation_id'))
},
- setThreadDisplay(id, nextStatus) {
+ setThreadDisplay (id, nextStatus) {
this.threadDisplayStatusObject = {
...this.threadDisplayStatusObject,
- [id]: nextStatus,
+ [id]: nextStatus
}
},
- toggleThreadDisplay(id) {
+ toggleThreadDisplay (id) {
const curStatus = this.threadDisplayStatus[id]
const nextStatus = curStatus === 'showing' ? 'hidden' : 'showing'
this.setThreadDisplay(id, nextStatus)
},
- setThreadDisplayRecursively(id, nextStatus) {
+ setThreadDisplayRecursively (id, nextStatus) {
this.setThreadDisplay(id, nextStatus)
- this.getReplies(id)
- .map((k) => k.id)
- .map((id) => this.setThreadDisplayRecursively(id, nextStatus))
+ this.getReplies(id).map(k => k.id).map(id => this.setThreadDisplayRecursively(id, nextStatus))
},
- showThreadRecursively(id) {
+ showThreadRecursively (id) {
this.setThreadDisplayRecursively(id, 'showing')
},
- leastVisibleAncestor(id) {
+ setStatusContentProperty (id, name, value) {
+ this.statusContentPropertiesObject = {
+ ...this.statusContentPropertiesObject,
+ [id]: {
+ ...this.statusContentPropertiesObject[id],
+ [name]: value
+ }
+ }
+ },
+ toggleStatusContentProperty (id, name) {
+ this.setStatusContentProperty(id, name, !this.statusContentProperties[id][name])
+ },
+ leastVisibleAncestor (id) {
let cur = id
let parent = this.parentOf(cur)
while (cur) {
@@ -522,20 +472,18 @@ const conversation = {
// nothing found, fall back to toplevel
return this.topLevel[0] ? this.topLevel[0].id : undefined
},
- diveIntoStatus(id) {
+ diveIntoStatus (id) {
this.tryScrollTo(id)
},
- diveToTopLevel() {
- this.tryScrollTo(
- this.topLevelAncestorOrSelfId(this.diveRoot) || this.topLevel[0].id,
- )
+ diveToTopLevel () {
+ this.tryScrollTo(this.topLevelAncestorOrSelfId(this.diveRoot) || this.topLevel[0].id)
},
// only used when we are not on a page
- undive() {
+ undive () {
this.inlineDivePosition = null
this.setHighlight(this.statusId)
},
- tryScrollTo(id) {
+ tryScrollTo (id) {
if (!id) {
return
}
@@ -564,13 +512,13 @@ const conversation = {
this.setHighlight(id)
})
},
- goToCurrent() {
+ goToCurrent () {
this.tryScrollTo(this.diveRoot || this.topLevel[0].id)
},
- statusById(id) {
+ statusById (id) {
return this.statusMap[id]
},
- parentOf(id) {
+ parentOf (id) {
const status = this.statusById(id)
if (!status) {
return undefined
@@ -581,11 +529,11 @@ const conversation = {
}
return parentId
},
- parentOrSelf(id) {
+ parentOrSelf (id) {
return this.parentOf(id) || id
},
// Ancestors of some status, from top to bottom
- ancestorsOf(id) {
+ ancestorsOf (id) {
const ancestors = []
let cur = this.parentOf(id)
while (cur) {
@@ -594,7 +542,7 @@ const conversation = {
}
return ancestors
},
- topLevelAncestorOrSelfId(id) {
+ topLevelAncestorOrSelfId (id) {
let cur = id
let parent = this.parentOf(id)
while (parent) {
@@ -603,18 +551,11 @@ const conversation = {
}
return cur
},
- resetDisplayState() {
+ resetDisplayState () {
this.undive()
this.threadDisplayStatusObject = {}
- },
- onStatusSuspendStateChange({ id, suspend }) {
- if (!suspend) {
- this.unsuspendibleIds.add(id)
- } else {
- this.unsuspendibleIds.delete(id)
- }
- },
- },
+ }
+ }
}
export default conversation
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
index 9bd8a96f9..2f3de3a86 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -88,28 +88,42 @@
class="thread-ancestor"
:class="{'thread-ancestor-has-other-replies': getReplies(status.id).length > 1, '-faded': shouldFadeAncestors}"
>
- diveIntoStatus(status.id)"
- @suspendable-state-change="onStatusSuspendStateChange"
+ @toggle-expanded="toggleExpanded"
/>
-
diff --git a/src/components/desktop_nav/desktop_nav.js b/src/components/desktop_nav/desktop_nav.js
index df855500a..98d408a7e 100644
--- a/src/components/desktop_nav/desktop_nav.js
+++ b/src/components/desktop_nav/desktop_nav.js
@@ -1,25 +1,20 @@
import SearchBar from 'components/search_bar/search_bar.vue'
-import { mapActions, mapState } from 'pinia'
-import { defineAsyncComponent } from 'vue'
-
-import { useInstanceStore } from 'src/stores/instance.js'
-import { useInterfaceStore } from 'src/stores/interface'
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-
+import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
- faBell,
- faBullhorn,
- faCog,
- faComments,
- faHome,
- faInfoCircle,
- faSearch,
faSignInAlt,
faSignOutAlt,
- faTachometerAlt,
+ faHome,
+ faComments,
+ faBell,
faUserPlus,
+ faBullhorn,
+ faSearch,
+ faTachometerAlt,
+ faCog,
+ faInfoCircle
} from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
library.add(
faSignInAlt,
@@ -32,100 +27,91 @@ library.add(
faSearch,
faTachometerAlt,
faCog,
- faInfoCircle,
+ faInfoCircle
)
export default {
components: {
SearchBar,
- ConfirmModal: defineAsyncComponent(
- () => import('src/components/confirm_modal/confirm_modal.vue'),
- ),
+ ConfirmModal
},
data: () => ({
searchBarHidden: true,
- supportsMask:
- window.CSS &&
- window.CSS.supports &&
- (window.CSS.supports('mask-size', 'contain') ||
+ supportsMask: window.CSS && window.CSS.supports && (
+ window.CSS.supports('mask-size', 'contain') ||
window.CSS.supports('-webkit-mask-size', 'contain') ||
window.CSS.supports('-moz-mask-size', 'contain') ||
window.CSS.supports('-ms-mask-size', 'contain') ||
- window.CSS.supports('-o-mask-size', 'contain')),
- showingConfirmLogout: false,
+ window.CSS.supports('-o-mask-size', 'contain')
+ ),
+ showingConfirmLogout: false
}),
computed: {
- enableMask() {
- return this.supportsMask && this.logoMask
- },
- logoStyle() {
+ enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
+ logoStyle () {
return {
- visibility: this.enableMask ? 'hidden' : 'visible',
+ visibility: this.enableMask ? 'hidden' : 'visible'
}
},
- logoMaskStyle() {
+ logoMaskStyle () {
return this.enableMask
? {
- 'mask-image': `url(${this.logo})`,
+ 'mask-image': `url(${this.$store.state.instance.logo})`
}
: {
- 'background-color': this.enableMask ? '' : 'transparent',
+ 'background-color': this.enableMask ? '' : 'transparent'
}
},
- logoBgStyle() {
- return Object.assign(
- {
- margin: `${this.logoMargin} 0`,
- opacity: this.searchBarHidden ? 1 : 0,
- },
- this.enableMask
- ? {}
- : {
- 'background-color': this.enableMask ? '' : 'transparent',
- },
- )
- },
- ...mapState(useInstanceStore, ['privateMode']),
- ...mapState(useInstanceStore, {
- logoMask: (store) => store.instanceIdentity.logoMask,
- logo: (store) => store.instanceIdentity.logo,
- logoLeft: (store) => store.instanceIdentity.logoLeft,
- logoMargin: (store) => store.instanceIdentity.logoMargin,
- sitename: (store) => store.instanceIdentity.name,
- hideSitename: (store) => store.instanceIdentity.hideSitename,
- }),
- currentUser() {
- return this.$store.state.users.currentUser
- },
- shouldConfirmLogout() {
- return useMergedConfigStore().mergedConfig.modalOnLogout
+ logoBgStyle () {
+ return Object.assign({
+ margin: `${this.$store.state.instance.logoMargin} 0`,
+ opacity: this.searchBarHidden ? 1 : 0
+ }, this.enableMask
+ ? {}
+ : {
+ 'background-color': this.enableMask ? '' : 'transparent'
+ })
},
+ logo () { return this.$store.state.instance.logo },
+ sitename () { return this.$store.state.instance.name },
+ hideSitename () { return this.$store.state.instance.hideSitename },
+ logoLeft () { return this.$store.state.instance.logoLeft },
+ currentUser () { return this.$store.state.users.currentUser },
+ privateMode () { return this.$store.state.instance.private },
+ shouldConfirmLogout () {
+ return this.$store.getters.mergedConfig.modalOnLogout
+ }
},
methods: {
- scrollToTop() {
+ scrollToTop () {
window.scrollTo(0, 0)
},
- showConfirmLogout() {
+ showConfirmLogout () {
this.showingConfirmLogout = true
},
- hideConfirmLogout() {
+ hideConfirmLogout () {
this.showingConfirmLogout = false
},
- logout() {
+ logout () {
if (!this.shouldConfirmLogout) {
this.doLogout()
} else {
this.showConfirmLogout()
}
},
- doLogout() {
+ doLogout () {
this.$router.replace('/main/public')
this.$store.dispatch('logout')
this.hideConfirmLogout()
},
- onSearchBarToggled(hidden) {
+ onSearchBarToggled (hidden) {
this.searchBarHidden = hidden
},
- ...mapActions(useInterfaceStore, ['openSettingsModal']),
- },
+ openSettingsModal () {
+ useInterfaceStore().openSettingsModal('user')
+ },
+ openAdminModal () {
+ useInterfaceStore().openSettingsModal('admin')
+ }
+ }
}
diff --git a/src/components/desktop_nav/desktop_nav.scss b/src/components/desktop_nav/desktop_nav.scss
index 9755ac590..35a57d9fe 100644
--- a/src/components/desktop_nav/desktop_nav.scss
+++ b/src/components/desktop_nav/desktop_nav.scss
@@ -9,7 +9,7 @@
.inner-nav {
display: grid;
grid-template-rows: var(--navbar-height);
- grid-template-columns: minmax(5em, 1fr) auto minmax(5em, 1fr);
+ grid-template-columns: 2fr auto 2fr;
grid-template-areas: "sitename logo actions";
box-sizing: border-box;
padding: 0 1.2em;
@@ -31,7 +31,7 @@
}
&.-logoLeft .inner-nav {
- grid-template-columns: auto minmax(5em, 1fr) minmax(5em, 1fr);
+ grid-template-columns: auto 2fr 2fr;
grid-template-areas: "logo sitename actions";
}
@@ -92,18 +92,23 @@
.actions {
grid-area: actions;
- justify-content: flex-end;
- text-align: right;
- z-index: 1;
}
.item {
+ flex: 1;
line-height: var(--navbar-height);
height: var(--navbar-height);
+ overflow: hidden;
display: flex;
+ flex-wrap: wrap;
+
+ &.right {
+ justify-content: flex-end;
+ text-align: right;
+ }
}
.spacer {
- min-width: 1em;
+ width: 1em;
}
}
diff --git a/src/components/desktop_nav/desktop_nav.vue b/src/components/desktop_nav/desktop_nav.vue
index 477a7634d..49382f8ee 100644
--- a/src/components/desktop_nav/desktop_nav.vue
+++ b/src/components/desktop_nav/desktop_nav.vue
@@ -32,64 +32,61 @@
>
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
{{ $t('login.logout_confirm') }}
-
+
diff --git a/src/components/dialog_modal/dialog_modal.js b/src/components/dialog_modal/dialog_modal.js
index 083f7a21d..b39851fe7 100644
--- a/src/components/dialog_modal/dialog_modal.js
+++ b/src/components/dialog_modal/dialog_modal.js
@@ -1,23 +1,19 @@
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-
const DialogModal = {
props: {
darkOverlay: {
default: true,
- type: Boolean,
+ type: Boolean
},
onCancel: {
- default: () => {
- /* no-op */
- },
- type: Function,
- },
+ default: () => {},
+ type: Function
+ }
},
computed: {
- mobileCenter() {
- return useMergedConfigStore().mergedConfig.modalMobileCenter
- },
- },
+ mobileCenter () {
+ return this.$store.getters.mergedConfig.modalMobileCenter
+ }
+ }
}
export default DialogModal
diff --git a/src/components/dialog_modal/dialog_modal.vue b/src/components/dialog_modal/dialog_modal.vue
index 2b9c7a5d8..939655f65 100644
--- a/src/components/dialog_modal/dialog_modal.vue
+++ b/src/components/dialog_modal/dialog_modal.vue
@@ -45,10 +45,10 @@
inset: 0;
justify-content: center;
place-items: center center;
- overflow: auto;
}
.dialog-modal.panel {
+ max-height: 80vh;
max-width: 90vw;
z-index: 2001;
cursor: default;
diff --git a/src/components/dm_timeline/dm_timeline.js b/src/components/dm_timeline/dm_timeline.js
index d54044ff7..8b5393a98 100644
--- a/src/components/dm_timeline/dm_timeline.js
+++ b/src/components/dm_timeline/dm_timeline.js
@@ -1,14 +1,14 @@
-import Timeline from 'src/components/timeline/timeline.vue'
+import Timeline from '../timeline/timeline.vue'
const DMs = {
computed: {
- timeline() {
+ timeline () {
return this.$store.state.statuses.timelines.dms
- },
+ }
},
components: {
- Timeline,
- },
+ Timeline
+ }
}
export default DMs
diff --git a/src/components/domain_mute_card/domain_mute_card.js b/src/components/domain_mute_card/domain_mute_card.js
index d83896618..f234dcb0f 100644
--- a/src/components/domain_mute_card/domain_mute_card.js
+++ b/src/components/domain_mute_card/domain_mute_card.js
@@ -1,26 +1,26 @@
-import ProgressButton from 'src/components/progress_button/progress_button.vue'
+import ProgressButton from '../progress_button/progress_button.vue'
const DomainMuteCard = {
props: ['domain'],
components: {
- ProgressButton,
+ ProgressButton
},
computed: {
- user() {
+ user () {
return this.$store.state.users.currentUser
},
- muted() {
+ muted () {
return this.user.domainMutes.includes(this.domain)
- },
+ }
},
methods: {
- unmuteDomain() {
+ unmuteDomain () {
return this.$store.dispatch('unmuteDomain', this.domain)
},
- muteDomain() {
+ muteDomain () {
return this.$store.dispatch('muteDomain', this.domain)
- },
- },
+ }
+ }
}
export default DomainMuteCard
diff --git a/src/components/draft/draft.js b/src/components/draft/draft.js
index fac5790aa..55ee11a15 100644
--- a/src/components/draft/draft.js
+++ b/src/components/draft/draft.js
@@ -1,44 +1,42 @@
-import { cloneDeep } from 'lodash'
-import { defineAsyncComponent } from 'vue'
-
-import Gallery from 'src/components/gallery/gallery.vue'
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
+import EditStatusForm from 'src/components/edit_status_form/edit_status_form.vue'
+import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
import StatusContent from 'src/components/status_content/status_content.vue'
-
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
+import Gallery from 'src/components/gallery/gallery.vue'
+import { cloneDeep } from 'lodash'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faPollH } from '@fortawesome/free-solid-svg-icons'
+import {
+ faPollH
+} from '@fortawesome/free-solid-svg-icons'
-library.add(faPollH)
+library.add(
+ faPollH
+)
const Draft = {
components: {
PostStatusForm,
- EditStatusForm: defineAsyncComponent(
- () => import('src/components/edit_status_form/edit_status_form.vue'),
- ),
- ConfirmModal: defineAsyncComponent(
- () => import('src/components/confirm_modal/confirm_modal.vue'),
- ),
+ EditStatusForm,
+ ConfirmModal,
StatusContent,
- Gallery,
+ Gallery
},
props: {
draft: {
type: Object,
- required: true,
- },
+ required: true
+ }
},
- data() {
+ data () {
return {
referenceDraft: cloneDeep(this.draft),
editing: false,
- showingConfirmDialog: false,
+ showingConfirmDialog: false
}
},
computed: {
- relAttrs() {
+ relAttrs () {
if (this.draft.type === 'edit') {
return { statusId: this.draft.refId }
} else if (this.draft.type === 'reply') {
@@ -47,55 +45,60 @@ const Draft = {
return {}
}
},
- safeToSave() {
- return (
- this.draft.status ||
+ safeToSave () {
+ return this.draft.status ||
this.draft.files?.length ||
- this.draft.hasPoll ||
- this.draft.hasQuote
- )
+ this.draft.hasPoll
},
- postStatusFormProps() {
+ postStatusFormProps () {
return {
draftId: this.draft.id,
- ...this.relAttrs,
+ ...this.relAttrs
}
},
- refStatus() {
- return this.draft.refId
- ? this.$store.state.statuses.allStatusesObject[this.draft.refId]
- : undefined
+ refStatus () {
+ return this.draft.refId ? this.$store.state.statuses.allStatusesObject[this.draft.refId] : undefined
},
- localCollapseSubjectDefault() {
- return useMergedConfigStore().mergedConfig.collapseMessageWithSubject
+ localCollapseSubjectDefault () {
+ return this.$store.getters.mergedConfig.collapseMessageWithSubject
},
+ nsfwClickthrough () {
+ if (!this.draft.nsfw) {
+ return false
+ }
+ if (this.draft.summary && this.localCollapseSubjectDefault) {
+ return false
+ }
+ return true
+ }
},
watch: {
- editing(newVal) {
+ editing (newVal) {
if (newVal) return
if (this.safeToSave) {
this.$store.dispatch('addOrSaveDraft', { draft: this.draft })
} else {
this.$store.dispatch('addOrSaveDraft', { draft: this.referenceDraft })
}
- },
+ }
},
methods: {
- toggleEditing() {
+ toggleEditing () {
this.editing = !this.editing
},
- abandon() {
+ abandon () {
this.showingConfirmDialog = true
},
- doAbandon() {
- this.$store.dispatch('abandonDraft', { id: this.draft.id }).then(() => {
- this.hideConfirmDialog()
- })
+ doAbandon () {
+ this.$store.dispatch('abandonDraft', { id: this.draft.id })
+ .then(() => {
+ this.hideConfirmDialog()
+ })
},
- hideConfirmDialog() {
+ hideConfirmDialog () {
this.showingConfirmDialog = false
- },
- },
+ }
+ }
}
export default Draft
diff --git a/src/components/draft/draft.vue b/src/components/draft/draft.vue
index c91675e35..433ebae31 100644
--- a/src/components/draft/draft.vue
+++ b/src/components/draft/draft.vue
@@ -39,7 +39,7 @@
class="faint"
>{{ $t('drafts.empty') }}
-
-
{{ $t('drafts.abandon_confirm') }}
-
+
import('src/components/dialog_modal/dialog_modal.vue'),
- ),
+ DialogModal
},
- emits: ['save', 'discard'],
+ emits: [
+ 'save',
+ 'discard'
+ ],
computed: {
- action() {
- if (useMergedConfigStore().mergedConfig.autoSaveDraft) {
+ action () {
+ if (this.$store.getters.mergedConfig.autoSaveDraft) {
return 'save'
} else {
- return useMergedConfigStore().mergedConfig.unsavedPostAction
+ return this.$store.getters.mergedConfig.unsavedPostAction
}
},
- shouldConfirm() {
+ shouldConfirm () {
return this.action === 'confirm'
- },
+ }
},
methods: {
- requestClose() {
+ requestClose () {
if (this.shouldConfirm) {
this.showing = true
} else if (this.action === 'save') {
@@ -36,18 +35,18 @@ const DraftCloser = {
this.discard()
}
},
- save() {
+ save () {
this.$emit('save')
this.showing = false
},
- discard() {
+ discard () {
this.$emit('discard')
this.showing = false
},
- cancel() {
+ cancel () {
this.showing = false
- },
- },
+ }
+ }
}
export default DraftCloser
diff --git a/src/components/draft_closer/draft_closer.vue b/src/components/draft_closer/draft_closer.vue
index bb8e5ec55..1afb1f44d 100644
--- a/src/components/draft_closer/draft_closer.vue
+++ b/src/components/draft_closer/draft_closer.vue
@@ -1,6 +1,6 @@
-
-
+
diff --git a/src/components/drafts/drafts.js b/src/components/drafts/drafts.js
index 3d93edb14..201417f66 100644
--- a/src/components/drafts/drafts.js
+++ b/src/components/drafts/drafts.js
@@ -1,39 +1,16 @@
-import { defineAsyncComponent } from 'vue'
-
import Draft from 'src/components/draft/draft.vue'
import List from 'src/components/list/list.vue'
const Drafts = {
components: {
Draft,
- List,
- ConfirmModal: defineAsyncComponent(
- () => import('src/components/confirm_modal/confirm_modal.vue'),
- ),
- },
- data() {
- return {
- showingConfirmDialog: false,
- }
+ List
},
computed: {
- drafts() {
+ drafts () {
return this.$store.getters.draftsArray
- },
- },
- methods: {
- abandonAll() {
- this.showingConfirmDialog = true
- },
- doAbandonAll() {
- this.$store
- .dispatch('abandonAllDrafts')
- .then(() => this.hideConfirmDialog())
- },
- hideConfirmDialog() {
- this.showingConfirmDialog = false
- },
- },
+ }
+ }
}
export default Drafts
diff --git a/src/components/drafts/drafts.vue b/src/components/drafts/drafts.vue
index 4da2b0e9a..1cce255da 100644
--- a/src/components/drafts/drafts.vue
+++ b/src/components/drafts/drafts.vue
@@ -13,67 +13,36 @@
>
{{ $t('drafts.no_drafts') }}
-
-
-
-
-
-
-
-
- {{ $t('drafts.clean_drafts') }}
-
-
-
+
+
+
+
+
-
-
- {{ $t('drafts.abandon_all_confirm') }}
-
-
diff --git a/src/components/edit_status_form/edit_status_form.js b/src/components/edit_status_form/edit_status_form.js
index f7fd3d04d..323763370 100644
--- a/src/components/edit_status_form/edit_status_form.js
+++ b/src/components/edit_status_form/edit_status_form.js
@@ -1,21 +1,21 @@
-import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
+import PostStatusForm from '../post_status_form/post_status_form.vue'
import statusPosterService from '../../services/status_poster/status_poster.service.js'
const EditStatusForm = {
components: {
- PostStatusForm,
+ PostStatusForm
},
props: {
params: {
type: Object,
- required: true,
- },
+ required: true
+ }
},
methods: {
- requestClose() {
+ requestClose () {
this.$refs.postStatusForm.requestClose()
},
- doEditStatus({ status, spoilerText, sensitive, media, contentType, poll }) {
+ doEditStatus ({ status, spoilerText, sensitive, media, contentType, poll }) {
const params = {
store: this.$store,
statusId: this.params.statusId,
@@ -24,22 +24,21 @@ const EditStatusForm = {
sensitive,
poll,
media,
- contentType,
+ contentType
}
- return statusPosterService
- .editStatus(params)
+ return statusPosterService.editStatus(params)
.then((data) => {
return data
})
.catch((err) => {
console.error('Error editing status', err)
return {
- error: err.message,
+ error: err.message
}
})
- },
- },
+ }
+ }
}
export default EditStatusForm
diff --git a/src/components/edit_status_form/edit_status_form.vue b/src/components/edit_status_form/edit_status_form.vue
index 1452be422..0a7ec760a 100644
--- a/src/components/edit_status_form/edit_status_form.vue
+++ b/src/components/edit_status_form/edit_status_form.vue
@@ -4,7 +4,6 @@
v-bind="params"
:post-handler="doEditStatus"
:disable-polls="true"
- :disable-quotes="true"
:disable-visibility-selector="true"
/>
diff --git a/src/components/edit_status_modal/edit_status_modal.js b/src/components/edit_status_modal/edit_status_modal.js
index c3ba7e4cb..4c10c21a0 100644
--- a/src/components/edit_status_modal/edit_status_modal.js
+++ b/src/components/edit_status_modal/edit_status_modal.js
@@ -1,38 +1,34 @@
-import { get } from 'lodash'
-import { defineAsyncComponent } from 'vue'
-
-import Modal from 'src/components/modal/modal.vue'
-
-import { useEditStatusStore } from 'src/stores/editStatus.js'
+import EditStatusForm from '../edit_status_form/edit_status_form.vue'
+import Modal from '../modal/modal.vue'
+import get from 'lodash/get'
+import { useEditStatusStore } from 'src/stores/editStatus'
const EditStatusModal = {
components: {
- EditStatusForm: defineAsyncComponent(
- () => import('src/components/edit_status_form/edit_status_form.vue'),
- ),
- Modal,
+ EditStatusForm,
+ Modal
},
- data() {
+ data () {
return {
- resettingForm: false,
+ resettingForm: false
}
},
computed: {
- isLoggedIn() {
+ isLoggedIn () {
return !!this.$store.state.users.currentUser
},
- modalActivated() {
+ modalActivated () {
return useEditStatusStore().modalActivated
},
- isFormVisible() {
+ isFormVisible () {
return this.isLoggedIn && !this.resettingForm && this.modalActivated
},
- params() {
+ params () {
return useEditStatusStore().params || {}
- },
+ }
},
watch: {
- params(newVal, oldVal) {
+ params (newVal, oldVal) {
if (get(newVal, 'statusId') !== get(oldVal, 'statusId')) {
this.resettingForm = true
this.$nextTick(() => {
@@ -40,22 +36,20 @@ const EditStatusModal = {
})
}
},
- isFormVisible(val) {
+ isFormVisible (val) {
if (val) {
- this.$nextTick(
- () => this.$el && this.$el.querySelector('textarea').focus(),
- )
+ this.$nextTick(() => this.$el && this.$el.querySelector('textarea').focus())
}
- },
+ }
},
methods: {
- closeModal() {
+ closeModal () {
this.$refs.editStatusForm.requestClose()
},
- doCloseModal() {
+ doCloseModal () {
useEditStatusStore().closeEditStatusModal()
- },
- },
+ }
+ }
}
export default EditStatusModal
diff --git a/src/components/edit_status_modal/edit_status_modal.vue b/src/components/edit_status_modal/edit_status_modal.vue
index 502a937f6..3d192b1ee 100644
--- a/src/components/edit_status_modal/edit_status_modal.vue
+++ b/src/components/edit_status_modal/edit_status_modal.vue
@@ -16,7 +16,7 @@
:params="params"
@posted="doCloseModal"
@draft-done="doCloseModal"
- @close-accepted="doCloseModal"
+ @can-close="doCloseModal"
/>
diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
index 0cce2494d..f3b6dfe9b 100644
--- a/src/components/emoji_input/emoji_input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -1,20 +1,20 @@
-import { take } from 'lodash'
-
-import Popover from 'src/components/popover/popover.vue'
-import ScreenReaderNotice from 'src/components/screen_reader_notice/screen_reader_notice.vue'
-import { ensureFinalFallback } from '../../i18n/languages.js'
import Completion from '../../services/completion/completion.js'
-import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
import genRandomSeed from '../../services/random_seed/random_seed.service.js'
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
+import Popover from 'src/components/popover/popover.vue'
+import ScreenReaderNotice from 'src/components/screen_reader_notice/screen_reader_notice.vue'
import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
-
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-
+import { take } from 'lodash'
+import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
+import { ensureFinalFallback } from '../../i18n/languages.js'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faSmileBeam } from '@fortawesome/free-regular-svg-icons'
+import {
+ faSmileBeam
+} from '@fortawesome/free-regular-svg-icons'
-library.add(faSmileBeam)
+library.add(
+ faSmileBeam
+)
/**
* EmojiInput - augmented inputs for emoji and autocomplete support in inputs
@@ -60,14 +60,14 @@ const EmojiInput = {
* For commonly used suggestors (emoji, users, both) use suggestor.js
*/
required: true,
- type: Function,
+ type: Function
},
modelValue: {
/**
* Used for v-model
*/
required: true,
- type: String,
+ type: String
},
enableEmojiPicker: {
/**
@@ -75,7 +75,7 @@ const EmojiInput = {
*/
required: false,
type: Boolean,
- default: false,
+ default: false
},
hideEmojiButton: {
/**
@@ -84,7 +84,7 @@ const EmojiInput = {
*/
required: false,
type: Boolean,
- default: false,
+ default: false
},
enableStickerPicker: {
/**
@@ -92,7 +92,7 @@ const EmojiInput = {
*/
required: false,
type: Boolean,
- default: false,
+ default: false
},
placement: {
/**
@@ -101,15 +101,15 @@ const EmojiInput = {
*/
required: false,
type: String, // 'auto', 'top', 'bottom'
- default: 'auto',
+ default: 'auto'
},
newlineOnCtrlEnter: {
required: false,
type: Boolean,
- default: false,
- },
+ default: false
+ }
},
- data() {
+ data () {
return {
randomSeed: genRandomSeed(),
input: undefined,
@@ -122,65 +122,58 @@ const EmojiInput = {
disableClickOutside: false,
suggestions: [],
overlayStyle: {},
- pickerShown: false,
+ pickerShown: false
}
},
components: {
Popover,
EmojiPicker,
UnicodeDomainIndicator,
- ScreenReaderNotice,
+ ScreenReaderNotice
},
computed: {
- padEmoji() {
- return useMergedConfigStore().mergedConfig.padEmoji
+ padEmoji () {
+ return this.$store.getters.mergedConfig.padEmoji
},
- defaultCandidateIndex() {
- return useMergedConfigStore().mergedConfig.autocompleteSelect ? 0 : -1
+ defaultCandidateIndex () {
+ return this.$store.getters.mergedConfig.autocompleteSelect ? 0 : -1
},
- preText() {
+ preText () {
return this.modelValue.slice(0, this.caret)
},
- postText() {
+ postText () {
return this.modelValue.slice(this.caret)
},
- showSuggestions() {
- return (
- this.focused &&
+ showSuggestions () {
+ return this.focused &&
this.suggestions &&
this.suggestions.length > 0 &&
!this.pickerShown &&
!this.temporarilyHideSuggestions
- )
},
- textAtCaret() {
+ textAtCaret () {
return this.wordAtCaret?.word
},
- wordAtCaret() {
+ wordAtCaret () {
if (this.modelValue && this.caret) {
- const word =
- Completion.wordAtPosition(this.modelValue, this.caret - 1) || {}
+ const word = Completion.wordAtPosition(this.modelValue, this.caret - 1) || {}
return word
}
},
- languages() {
- return ensureFinalFallback(
- useMergedConfigStore().mergedConfig.interfaceLanguage,
- )
+ languages () {
+ return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
},
- maybeLocalizedEmojiNamesAndKeywords() {
- return (emoji) => {
+ maybeLocalizedEmojiNamesAndKeywords () {
+ return emoji => {
const names = [emoji.displayText]
const keywords = []
if (emoji.displayTextI18n) {
- names.push(
- this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args),
- )
+ names.push(this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args))
}
if (emoji.annotations) {
- this.languages.forEach((lang) => {
+ this.languages.forEach(lang => {
names.push(emoji.annotations[lang]?.name)
keywords.push(...(emoji.annotations[lang]?.keywords || []))
@@ -188,13 +181,13 @@ const EmojiInput = {
}
return {
- names: names.filter((k) => k),
- keywords: keywords.filter((k) => k),
+ names: names.filter(k => k),
+ keywords: keywords.filter(k => k)
}
}
},
- maybeLocalizedEmojiName() {
- return (emoji) => {
+ maybeLocalizedEmojiName () {
+ return emoji => {
if (!emoji.annotations) {
return emoji.displayText
}
@@ -212,18 +205,22 @@ const EmojiInput = {
return emoji.displayText
}
},
- suggestionListId() {
+ onInputScroll () {
+ this.$refs.hiddenOverlay.scrollTo({
+ top: this.input.scrollTop,
+ left: this.input.scrollLeft
+ })
+ },
+ suggestionListId () {
return `suggestions-${this.randomSeed}`
},
- suggestionItemId() {
+ suggestionItemId () {
return (index) => `suggestion-item-${index}-${this.randomSeed}`
- },
+ }
},
- mounted() {
+ mounted () {
const { root, hiddenOverlayCaret, suggestorPopover } = this.$refs
- const input =
- root.querySelector('.emoji-input > input') ||
- root.querySelector('.emoji-input > textarea')
+ const input = root.querySelector('.emoji-input > input') || root.querySelector('.emoji-input > textarea')
if (!input) return
this.input = input
this.caretEl = hiddenOverlayCaret
@@ -242,6 +239,7 @@ const EmojiInput = {
this.overlayStyle.fontSize = style.fontSize
this.overlayStyle.wordWrap = style.wordWrap
this.overlayStyle.whiteSpace = style.whiteSpace
+ this.resize()
input.addEventListener('blur', this.onBlur)
input.addEventListener('focus', this.onFocus)
input.addEventListener('paste', this.onPaste)
@@ -252,7 +250,7 @@ const EmojiInput = {
input.addEventListener('input', this.onInput)
input.addEventListener('scroll', this.onInputScroll)
},
- unmounted() {
+ unmounted () {
const { input } = this
if (input) {
input.removeEventListener('blur', this.onBlur)
@@ -282,40 +280,29 @@ const EmojiInput = {
this.suggestions = []
return
}
- const matchedSuggestions = await this.suggest(
- newWord,
- this.maybeLocalizedEmojiNamesAndKeywords,
- )
+ const matchedSuggestions = await this.suggest(newWord, this.maybeLocalizedEmojiNamesAndKeywords)
// Async: cancel if textAtCaret has changed during wait
if (this.textAtCaret !== newWord || matchedSuggestions.length <= 0) {
this.suggestions = []
return
}
- this.suggestions = take(matchedSuggestions, 5).map(
- ({ imageUrl, ...rest }) => ({
+ this.suggestions = take(matchedSuggestions, 5)
+ .map(({ imageUrl, ...rest }) => ({
...rest,
- img: imageUrl || '',
- }),
- )
+ img: imageUrl || ''
+ }))
this.highlighted = this.defaultCandidateIndex
this.$refs.screenReaderNotice.announce(
this.$t(
'tool_tip.autocomplete_available',
{ number: this.suggestions.length },
- this.suggestions.length,
- ),
+ this.suggestions.length
+ )
)
- },
+ }
},
methods: {
- onInputScroll(e) {
- this.$refs.hiddenOverlay.scrollTo({
- top: this.input.scrollTop,
- left: this.input.scrollLeft,
- })
- this.setCaret(e)
- },
- triggerShowPicker() {
+ triggerShowPicker () {
this.$nextTick(() => {
this.$refs.picker.showPicker()
this.scrollIntoView()
@@ -328,25 +315,22 @@ const EmojiInput = {
this.disableClickOutside = false
}, 0)
},
- togglePicker() {
+ togglePicker () {
this.input.focus()
if (!this.pickerShown) {
this.scrollIntoView()
this.$refs.picker.showPicker()
+ this.$refs.picker.startEmojiLoad()
} else {
this.$refs.picker.hidePicker()
}
},
- replace(replacement) {
- const newValue = Completion.replaceWord(
- this.modelValue,
- this.wordAtCaret,
- replacement,
- )
+ replace (replacement) {
+ const newValue = Completion.replaceWord(this.modelValue, this.wordAtCaret, replacement)
this.$emit('update:modelValue', newValue)
this.caret = 0
},
- insert({ insertion, keepOpen, surroundingSpace = true }) {
+ insert ({ insertion, keepOpen, surroundingSpace = true }) {
const before = this.modelValue.substring(0, this.caret) || ''
const after = this.modelValue.substring(this.caret) || ''
@@ -365,24 +349,18 @@ const EmojiInput = {
* them, masto seem to be rendering :emoji::emoji: correctly now so why not
*/
const isSpaceRegex = /\s/
- const spaceBefore =
- surroundingSpace &&
- !isSpaceRegex.exec(before.slice(-1)) &&
- before.length &&
- this.padEmoji > 0
- ? ' '
- : ''
- const spaceAfter =
- surroundingSpace && !isSpaceRegex.exec(after[0]) && this.padEmoji
- ? ' '
- : ''
+ const spaceBefore = (surroundingSpace && !isSpaceRegex.exec(before.slice(-1)) && before.length && this.padEmoji > 0) ? ' ' : ''
+ const spaceAfter = (surroundingSpace && !isSpaceRegex.exec(after[0]) && this.padEmoji) ? ' ' : ''
- const newValue = [before, spaceBefore, insertion, spaceAfter, after].join(
- '',
- )
+ const newValue = [
+ before,
+ spaceBefore,
+ insertion,
+ spaceAfter,
+ after
+ ].join('')
this.$emit('update:modelValue', newValue)
- const position =
- this.caret + (insertion + spaceAfter + spaceBefore).length
+ const position = this.caret + (insertion + spaceAfter + spaceBefore).length
if (!keepOpen) {
this.input.focus()
}
@@ -394,20 +372,13 @@ const EmojiInput = {
this.caret = position
})
},
- replaceText(e, suggestion) {
+ replaceText (e, suggestion) {
const len = this.suggestions.length || 0
- if (this.textAtCaret.length === 1) {
- return
- }
+ if (this.textAtCaret.length === 1) { return }
if (len > 0 || suggestion) {
- const chosenSuggestion =
- suggestion || this.suggestions[this.highlighted]
+ const chosenSuggestion = suggestion || this.suggestions[this.highlighted]
const replacement = chosenSuggestion.replacement
- const newValue = Completion.replaceWord(
- this.modelValue,
- this.wordAtCaret,
- replacement,
- )
+ const newValue = Completion.replaceWord(this.modelValue, this.wordAtCaret, replacement)
this.$emit('update:modelValue', newValue)
this.highlighted = 0
const position = this.wordAtCaret.start + replacement.length
@@ -422,7 +393,7 @@ const EmojiInput = {
e.preventDefault()
}
},
- cycleBackward(e) {
+ cycleBackward (e) {
const len = this.suggestions.length || 0
this.highlighted -= 1
@@ -435,7 +406,7 @@ const EmojiInput = {
e.preventDefault()
}
},
- cycleForward(e) {
+ cycleForward (e) {
const len = this.suggestions.length || 0
this.highlighted += 1
@@ -447,28 +418,26 @@ const EmojiInput = {
e.preventDefault()
}
},
- scrollIntoView() {
+ scrollIntoView () {
const rootRef = this.$refs.picker.$el
/* Scroller is either `window` (replies in TL), sidebar (main post form,
* replies in notifs) or mobile post form. Note that getting and setting
* scroll is different for `Window` and `Element`s
*/
- const scrollerRef =
- this.$el.closest('.sidebar-scroller') ||
- this.$el.closest('.post-form-modal-view') ||
- window
- const currentScroll =
- scrollerRef === window ? scrollerRef.scrollY : scrollerRef.scrollTop
- const scrollerHeight =
- scrollerRef === window
- ? scrollerRef.innerHeight
- : scrollerRef.offsetHeight
+ const scrollerRef = this.$el.closest('.sidebar-scroller') ||
+ this.$el.closest('.post-form-modal-view') ||
+ window
+ const currentScroll = scrollerRef === window
+ ? scrollerRef.scrollY
+ : scrollerRef.scrollTop
+ const scrollerHeight = scrollerRef === window
+ ? scrollerRef.innerHeight
+ : scrollerRef.offsetHeight
const scrollerBottomBorder = currentScroll + scrollerHeight
// We check where the bottom border of root element is, this uses findOffset
// to find offset relative to scrollable container (scroller)
- const rootBottomBorder =
- rootRef.offsetHeight + findOffset(rootRef, scrollerRef).top
+ const rootBottomBorder = rootRef.offsetHeight + findOffset(rootRef, scrollerRef).top
const bottomDelta = Math.max(0, rootBottomBorder - scrollerBottomBorder)
// could also check top delta but there's no case for it
@@ -490,13 +459,13 @@ const EmojiInput = {
}
})
},
- onPickerShown() {
+ onPickerShown () {
this.pickerShown = true
},
- onPickerClosed() {
+ onPickerClosed () {
this.pickerShown = false
},
- onBlur(e) {
+ onBlur (e) {
// Clicking on any suggestion removes focus from autocomplete,
// preventing click handler ever executing.
this.blurTimeout = setTimeout(() => {
@@ -504,10 +473,10 @@ const EmojiInput = {
this.setCaret(e)
}, 200)
},
- onClick(e, suggestion) {
+ onClick (e, suggestion) {
this.replaceText(e, suggestion)
},
- onFocus(e) {
+ onFocus (e) {
if (this.blurTimeout) {
clearTimeout(this.blurTimeout)
this.blurTimeout = null
@@ -517,7 +486,7 @@ const EmojiInput = {
this.setCaret(e)
this.temporarilyHideSuggestions = false
},
- onKeyUp(e) {
+ onKeyUp (e) {
const { key } = e
this.setCaret(e)
@@ -529,10 +498,10 @@ const EmojiInput = {
this.temporarilyHideSuggestions = false
}
},
- onPaste(e) {
+ onPaste (e) {
this.setCaret(e)
},
- onKeyDown(e) {
+ onKeyDown (e) {
const { ctrlKey, shiftKey, key } = e
if (this.newlineOnCtrlEnter && ctrlKey && key === 'Enter') {
this.insert({ insertion: '\n', surroundingSpace: false })
@@ -576,30 +545,32 @@ const EmojiInput = {
}
}
},
- onInput(e) {
+ onInput (e) {
this.setCaret(e)
this.$emit('update:modelValue', e.target.value)
},
- onStickerUploaded(e) {
+ onStickerUploaded (e) {
this.$emit('sticker-uploaded', e)
},
- onStickerUploadFailed(e) {
+ onStickerUploadFailed (e) {
this.$emit('sticker-upload-Failed', e)
},
- setCaret({ target: { selectionStart } }) {
+ setCaret ({ target: { selectionStart } }) {
this.caret = selectionStart
this.$nextTick(() => {
- this.$refs.suggestorPopover?.updateStyles()
+ this.$refs.suggestorPopover.updateStyles()
})
},
- autoCompleteItemLabel(suggestion) {
+ resize () {
+ },
+ autoCompleteItemLabel (suggestion) {
if (suggestion.user) {
return suggestion.displayText + ' ' + suggestion.detailText
} else {
return this.maybeLocalizedEmojiName(suggestion)
}
- },
- },
+ }
+ }
}
export default EmojiInput
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index 8b69f3d9d..f9788d874 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -2,7 +2,7 @@
-
{{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
-
-
+
{{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
-
+
diff --git a/src/components/follow_requests/follow_requests.js b/src/components/follow_requests/follow_requests.js
index 513298afc..704a76c66 100644
--- a/src/components/follow_requests/follow_requests.js
+++ b/src/components/follow_requests/follow_requests.js
@@ -1,14 +1,14 @@
-import FollowRequestCard from 'src/components/follow_request_card/follow_request_card.vue'
+import FollowRequestCard from '../follow_request_card/follow_request_card.vue'
const FollowRequests = {
components: {
- FollowRequestCard,
+ FollowRequestCard
},
computed: {
- requests() {
+ requests () {
return this.$store.state.api.followRequests
- },
- },
+ }
+ }
}
export default FollowRequests
diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js
index 697d83ee2..ffc866788 100644
--- a/src/components/font_control/font_control.js
+++ b/src/components/font_control/font_control.js
@@ -1,32 +1,35 @@
+import Select from '../select/select.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import Popover from 'src/components/popover/popover.vue'
-import Select from 'src/components/select/select.vue'
-import LocalSettingIndicator from 'src/components/settings_modal/helpers/local_setting_indicator.vue'
-
-import { useInterfaceStore } from 'src/stores/interface.js'
+import { useInterfaceStore } from 'src/stores/interface'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faExclamationTriangle,
- faFont,
faKeyboard,
+ faFont
} from '@fortawesome/free-solid-svg-icons'
-library.add(faExclamationTriangle, faKeyboard, faFont)
+library.add(
+ faExclamationTriangle,
+ faKeyboard,
+ faFont
+)
export default {
components: {
Select,
Checkbox,
- Popover,
- LocalSettingIndicator,
+ Popover
},
- props: ['name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit'],
- mounted() {
+ props: [
+ 'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit'
+ ],
+ mounted () {
useInterfaceStore().queryLocalFonts()
},
emits: ['update:modelValue'],
- data() {
+ data () {
return {
manualEntry: false,
availableOptions: [
@@ -34,24 +37,24 @@ export default {
'serif',
'sans-serif',
'monospace',
- ...(this.options || []),
- ].filter((_) => _),
+ ...(this.options || [])
+ ].filter(_ => _)
}
},
methods: {
- toggleManualEntry() {
+ toggleManualEntry () {
this.manualEntry = !this.manualEntry
- },
+ }
},
computed: {
- present() {
- return this.modelValue != null
+ present () {
+ return typeof this.modelValue !== 'undefined'
},
- localFontsList() {
+ localFontsList () {
return useInterfaceStore().localFonts
},
- localFontsSize() {
+ localFontsSize () {
return useInterfaceStore().localFonts?.length
- },
- },
+ }
+ }
}
diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue
index 99bdb8603..5f4ed105c 100644
--- a/src/components/font_control/font_control.vue
+++ b/src/components/font_control/font_control.vue
@@ -1,30 +1,25 @@
-
-
-
- {{ ' ' }}
-
-
- {{ label }}
-
-
-
-
+
+ {{ $t('settings.style.themes3.font.label', { label }) }}
+
{{ ' ' }}
+
+ {{ $t('settings.style.themes3.define') }}
+
-
+
+
+
diff --git a/src/components/link-preview/link-preview.js b/src/components/link-preview/link-preview.js
index 808030fbb..add7c5631 100644
--- a/src/components/link-preview/link-preview.js
+++ b/src/components/link-preview/link-preview.js
@@ -1,34 +1,38 @@
-import { mapState } from 'pinia'
-
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
+import { mapGetters } from 'vuex'
const LinkPreview = {
name: 'LinkPreview',
- props: ['card', 'size', 'nsfw'],
- data() {
+ props: [
+ 'card',
+ 'size',
+ 'nsfw'
+ ],
+ data () {
return {
- imageLoaded: false,
+ imageLoaded: false
}
},
computed: {
- useImage() {
+ useImage () {
// Currently BE shoudn't give cards if tagged NSFW, this is a bit paranoid
// as it makes sure to hide the image if somehow NSFW tagged preview can
// exist.
return this.card.image && !this.censored && this.size !== 'hide'
},
- censored() {
+ censored () {
return this.nsfw && this.hideNsfwConfig
},
- useDescription() {
+ useDescription () {
return this.card.description && /\S/.test(this.card.description)
},
- hideNsfwConfig() {
+ hideNsfwConfig () {
return this.mergedConfig.hideNsfw
},
- ...mapState(useMergedConfigStore, ['mergedConfig']),
+ ...mapGetters([
+ 'mergedConfig'
+ ])
},
- created() {
+ created () {
if (this.useImage) {
const newImg = new Image()
newImg.onload = () => {
@@ -36,7 +40,7 @@ const LinkPreview = {
}
newImg.src = this.card.image
}
- },
+ }
}
export default LinkPreview
diff --git a/src/components/link-preview/link-preview.vue b/src/components/link-preview/link-preview.vue
index 7ff8433f4..18fc77df4 100644
--- a/src/components/link-preview/link-preview.vue
+++ b/src/components/link-preview/link-preview.vue
@@ -48,7 +48,6 @@
img {
width: 100%;
height: 100%;
- max-height: 10em;
object-fit: cover;
border-radius: var(--roundness);
}
diff --git a/src/components/link.style.js b/src/components/link.style.js
index 141a1f023..d13cef338 100644
--- a/src/components/link.style.js
+++ b/src/components/link.style.js
@@ -3,22 +3,22 @@ export default {
selector: 'a',
virtual: true,
states: {
- faint: '.faint',
+ faint: '.faint'
},
defaultRules: [
{
component: 'Link',
directives: {
- textColor: '--link',
- },
+ textColor: '--link'
+ }
},
{
component: 'Link',
state: ['faint'],
directives: {
textOpacity: 0.5,
- textOpacityMode: 'fake',
- },
- },
- ],
+ textOpacityMode: 'fake'
+ }
+ }
+ ]
}
diff --git a/src/components/list/list.css b/src/components/list/list.css
deleted file mode 100644
index 365c1ee92..000000000
--- a/src/components/list/list.css
+++ /dev/null
@@ -1,78 +0,0 @@
-.List {
- --__line-height: 1.5em;
- --__horizontal-gap: 0.75em;
- --__vertical-gap: 0.5em;
-
- display: flex;
- flex-direction: column;
-
- .list {
- flex: 1;
- }
-
- .list-item {
- display: flex;
- align-items: center;
-
- &[aria-expanded="true"] {
- border-bottom-width: 1px;
- }
-
- &:first-child {
- border-top-right-radius: var(--roundness);
- border-top-left-radius: var(--roundness);
- border-top-width: 0;
- }
-
- &:last-child {
- border-bottom-right-radius: var(--roundness);
- border-bottom-left-radius: var(--roundness);
- border-bottom-width: 0;
- }
-
- &:not(:last-child) {
- border-bottom: 1px dotted var(--border);
- }
-
- &:empty {
- border: none;
- }
- }
-
- .header {
- display: flex;
- align-items: center;
- padding: var(--__vertical-gap) var(--__horizontal-gap);
- border-bottom: 1px solid;
- border-bottom-color: var(--border);
-
- .actions {
- flex: 1;
- }
- }
-
- .footer {
- padding: 0.9em;
- text-align: center;
-
- a {
- cursor: pointer;
- }
- }
-
- .checkbox-wrapper {
- padding-right: var(--__horizontal-gap);
- flex: none;
- }
-
- &.-scrollable {
- overflow-y: hidden;
- display: flex;
- flex-direction: column;
-
- .list {
- overflow-y: auto;
- flex: 1 1 auto;
- }
- }
-}
diff --git a/src/components/list/list.js b/src/components/list/list.js
deleted file mode 100644
index c7b924258..000000000
--- a/src/components/list/list.js
+++ /dev/null
@@ -1,158 +0,0 @@
-import { isEmpty } from 'lodash'
-
-import Checkbox from 'src/components/checkbox/checkbox.vue'
-
-const List = {
- props: {
- boxOnly: {
- type: Boolean,
- default: false,
- },
- fetchFunction: {
- type: Function,
- default: null,
- },
- getKey: {
- type: Function,
- default: (item) => item.id,
- },
- getClass: {
- type: Function,
- default: () => '',
- },
- preSelect: {
- type: Array,
- default: [],
- },
- nonInteractive: {
- type: Boolean,
- default: false,
- },
- scrollable: {
- type: Boolean,
- default: false,
- },
- selectable: {
- type: Boolean,
- default: false,
- },
- externalItems: {
- type: Array,
- default: null,
- },
- },
- emits: ['fetchRequested', 'select'],
- components: {
- Checkbox,
- },
- data() {
- return {
- items: [],
- selected: new Set(this.preSelect),
- loading: false,
- bottomedOut: true,
- error: null,
- page: 1,
- total: null,
- }
- },
- computed: {
- allKeys() {
- return new Set(this.finalItems.map(this.getKey))
- },
- selectedItems() {
- return this.items.filter((item) => this.selected.has(this.getKey(item)))
- },
- allSelected() {
- return (
- this.selected.size !== 0 &&
- this.selected.size === this.finalItems.length
- )
- },
- noneSelected() {
- return this.selected.size === 0
- },
- someSelected() {
- return !this.allSelected && !this.noneSelected
- },
- finalItems() {
- return this.externalItems || this.items
- },
- },
- created() {
- window.addEventListener('scroll', this.scrollLoad)
-
- if (this.fetchFunction && this.items.length === 0) {
- this.fetchEntries()
- }
- },
- unmounted() {
- window.removeEventListener('scroll', this.scrollLoad)
- },
- methods: {
- fetchEntries() {
- if (this.loading) return
-
- this.loading = true
- this.error = null
-
- this.fetchFunction(this.page)
- .then((result) => {
- this.loading = false
- this.bottomedOut = isEmpty(result.items)
- if (this.externalItems) return
- this.page += 1
- this.total = result.count
- this.items.push(...result.items)
- })
- .catch((error) => {
- this.loading = false
- this.error = error
- console.error('Error loading list data:', error)
- })
- },
- reset() {
- this.items = []
- this.page = 1
- this.total = null
- this.error = null
- this.loading = false
- this.fetchEntries()
- },
- scrollLoad(e) {
- if (this.fetchFunction) {
- const bodyBRect = document.body.getBoundingClientRect()
- const height = Math.max(bodyBRect.height, -bodyBRect.y)
- if (
- this.$el.offsetHeight > 0 &&
- window.innerHeight + window.pageYOffset >= height - 750
- ) {
- this.fetchEntries()
- }
- }
- },
- isSelected(item) {
- return this.selected.has(this.getKey(item))
- },
- toggle(checked, item) {
- const key = this.getKey(item)
- if (checked) {
- this.selected.add(key)
- } else {
- this.selected.delete(key)
- }
-
- this.$emit('select', this.selected)
- },
- toggleAll(value) {
- if (value) {
- this.selected = new Set([...this.allKeys])
- } else {
- this.selected = new Set([])
- }
- this.$emit('select', this.selected)
- },
- },
-}
-
-export default List
diff --git a/src/components/list/list.vue b/src/components/list/list.vue
index 19c52cf4b..5d2c49b3c 100644
--- a/src/components/list/list.vue
+++ b/src/components/list/list.vue
@@ -1,90 +1,48 @@
-
-
- toggle(checked, item)"
- @click.stop
- />
-
-
-
-
-
-
-
+
-
-
-
+
diff --git a/src/components/list/list_item.style.js b/src/components/list/list_item.style.js
new file mode 100644
index 000000000..49b2b035f
--- /dev/null
+++ b/src/components/list/list_item.style.js
@@ -0,0 +1,48 @@
+export default {
+ name: 'ListItem',
+ selector: '.list-item',
+ states: {
+ active: '.-active',
+ hover: ':is(:hover, :focus-visible, :has(:focus-visible)):not(.-non-interactive)'
+ },
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'ButtonUnstyled',
+ 'RichContent',
+ 'Input',
+ 'Avatar'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg',
+ opacity: 0
+ }
+ },
+ {
+ state: ['active'],
+ directives: {
+ background: '--inheritedBackground, 10',
+ opacity: 1
+ }
+ },
+ {
+ state: ['hover'],
+ directives: {
+ background: '--inheritedBackground, 10',
+ opacity: 1
+ }
+ },
+ {
+ state: ['hover', 'active'],
+ directives: {
+ background: '--inheritedBackground, 20',
+ opacity: 1
+ }
+ }
+ ]
+}
diff --git a/src/components/lists/lists.js b/src/components/lists/lists.js
index 7545d9126..8dcb48b52 100644
--- a/src/components/lists/lists.js
+++ b/src/components/lists/lists.js
@@ -1,29 +1,28 @@
-import FolderCard from 'src/components/folder_card/folder_card.vue'
-
-import { useListsStore } from 'src/stores/lists.js'
+import { useListsStore } from 'src/stores/lists'
+import ListsCard from '../lists_card/lists_card.vue'
const Lists = {
- data() {
+ data () {
return {
- isNew: false,
+ isNew: false
}
},
components: {
- FolderCard,
+ ListsCard
},
computed: {
- lists() {
+ lists () {
return useListsStore().allLists
- },
+ }
},
methods: {
- cancelNewList() {
+ cancelNewList () {
this.isNew = false
},
- newList() {
+ newList () {
this.isNew = true
- },
- },
+ }
+ }
}
export default Lists
diff --git a/src/components/lists/lists.vue b/src/components/lists/lists.vue
index f3987205e..05df5b72f 100644
--- a/src/components/lists/lists.vue
+++ b/src/components/lists/lists.vue
@@ -14,12 +14,10 @@
-
diff --git a/src/components/lists_card/lists_card.js b/src/components/lists_card/lists_card.js
new file mode 100644
index 000000000..b503caec4
--- /dev/null
+++ b/src/components/lists_card/lists_card.js
@@ -0,0 +1,16 @@
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faEllipsisH
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faEllipsisH
+)
+
+const ListsCard = {
+ props: [
+ 'list'
+ ]
+}
+
+export default ListsCard
diff --git a/src/components/lists_card/lists_card.vue b/src/components/lists_card/lists_card.vue
new file mode 100644
index 000000000..a5dc6371e
--- /dev/null
+++ b/src/components/lists_card/lists_card.vue
@@ -0,0 +1,38 @@
+
+
+
+ {{ list.title }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/lists_edit/lists_edit.js b/src/components/lists_edit/lists_edit.js
index d7a8525a8..ca36ab886 100644
--- a/src/components/lists_edit/lists_edit.js
+++ b/src/components/lists_edit/lists_edit.js
@@ -1,19 +1,22 @@
+import { mapState, mapGetters } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
-import { mapGetters, mapState } from 'vuex'
-
-import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
-import ListsUserSearch from 'src/components/lists_user_search/lists_user_search.vue'
+import BasicUserCard from '../basic_user_card/basic_user_card.vue'
+import ListsUserSearch from '../lists_user_search/lists_user_search.vue'
import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
+import UserAvatar from '../user_avatar/user_avatar.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
-import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
-
-import { useInterfaceStore } from 'src/stores/interface.js'
-import { useListsStore } from 'src/stores/lists.js'
-
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons'
+import {
+ faSearch,
+ faChevronLeft
+} from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
+import { useListsStore } from 'src/stores/lists'
-library.add(faSearch, faChevronLeft)
+library.add(
+ faSearch,
+ faChevronLeft
+)
const ListsNew = {
components: {
@@ -21,9 +24,9 @@ const ListsNew = {
UserAvatar,
ListsUserSearch,
TabSwitcher,
- PanelLoading,
+ PanelLoading
},
- data() {
+ data () {
return {
title: '',
titleDraft: '',
@@ -32,51 +35,46 @@ const ListsNew = {
searchUserIds: [],
addedUserIds: new Set([]), // users we added from search, to undo
searchLoading: false,
- reallyDelete: false,
+ reallyDelete: false
}
},
- created() {
+ created () {
if (!this.id) return
- useListsStore()
- .fetchList({ listId: this.id })
+ useListsStore().fetchList({ listId: this.id })
.then(() => {
this.title = this.findListTitle(this.id)
this.titleDraft = this.title
})
- useListsStore()
- .fetchListAccounts({ listId: this.id })
+ useListsStore().fetchListAccounts({ listId: this.id })
.then(() => {
this.membersUserIds = this.findListAccounts(this.id)
- this.membersUserIds.forEach((userId) => {
+ this.membersUserIds.forEach(userId => {
this.$store.dispatch('fetchUserIfMissing', userId)
})
})
},
computed: {
- id() {
+ id () {
return this.$route.params.id
},
- membersUsers() {
+ membersUsers () {
return [...this.membersUserIds, ...this.addedUserIds]
- .map((userId) => this.findUser(userId))
- .filter((user) => user)
+ .map(userId => this.findUser(userId)).filter(user => user)
},
- searchUsers() {
- return this.searchUserIds
- .map((userId) => this.findUser(userId))
- .filter((user) => user)
+ searchUsers () {
+ return this.searchUserIds.map(userId => this.findUser(userId)).filter(user => user)
},
...mapState({
- currentUser: (state) => state.users.currentUser,
+ currentUser: state => state.users.currentUser
}),
...mapPiniaState(useListsStore, ['findListTitle', 'findListAccounts']),
- ...mapGetters(['findUser']),
+ ...mapGetters(['findUser'])
},
methods: {
- onInput() {
+ onInput () {
this.search(this.query)
},
- toggleRemoveMember(user) {
+ toggleRemoveMember (user) {
if (this.removedUserIds.has(user.id)) {
this.id && this.addUser(user)
this.removedUserIds.delete(user.id)
@@ -85,7 +83,7 @@ const ListsNew = {
this.removedUserIds.add(user.id)
}
},
- toggleAddFromSearch(user) {
+ toggleAddFromSearch (user) {
if (this.addedUserIds.has(user.id)) {
this.id && this.removeUser(user.id)
this.addedUserIds.delete(user.id)
@@ -94,41 +92,40 @@ const ListsNew = {
this.addedUserIds.add(user.id)
}
},
- isRemoved(user) {
+ isRemoved (user) {
return this.removedUserIds.has(user.id)
},
- isAdded(user) {
+ isAdded (user) {
return this.addedUserIds.has(user.id)
},
- addUser(user) {
+ addUser (user) {
useListsStore().addListAccount({ accountId: user.id, listId: this.id })
},
- removeUser(userId) {
+ removeUser (userId) {
useListsStore().removeListAccount({ accountId: userId, listId: this.id })
},
- onSearchLoading() {
+ onSearchLoading () {
this.searchLoading = true
},
- onSearchLoadingDone() {
+ onSearchLoadingDone () {
this.searchLoading = false
},
- onSearchResults(results) {
+ onSearchResults (results) {
this.searchLoading = false
this.searchUserIds = results
},
- updateListTitle() {
+ updateListTitle () {
useListsStore().setList({ listId: this.id, title: this.titleDraft })
- this.title = this.findListTitle(this.id)
+ .then(() => {
+ this.title = this.findListTitle(this.id)
+ })
},
- createList() {
- useListsStore()
- .createList({ title: this.titleDraft })
+ createList () {
+ useListsStore().createList({ title: this.titleDraft })
.then((list) => {
- useListsStore().setListAccounts({
- listId: list.id,
- accountIds: [...this.addedUserIds],
- })
- return list.id
+ return useListsStore()
+ .setListAccounts({ listId: list.id, accountIds: [...this.addedUserIds] })
+ .then(() => list.id)
})
.then((listId) => {
this.$router.push({ name: 'lists-timeline', params: { id: listId } })
@@ -137,15 +134,15 @@ const ListsNew = {
useInterfaceStore().pushGlobalNotice({
messageKey: 'lists.error',
messageArgs: [e.message],
- level: 'error',
+ level: 'error'
})
})
},
- deleteList() {
+ deleteList () {
useListsStore().deleteList({ listId: this.id })
this.$router.push({ name: 'lists' })
- },
- },
+ }
+ }
}
export default ListsNew
diff --git a/src/components/lists_edit/lists_edit.vue b/src/components/lists_edit/lists_edit.vue
index 25b818deb..3ff8c1072 100644
--- a/src/components/lists_edit/lists_edit.vue
+++ b/src/components/lists_edit/lists_edit.vue
@@ -50,7 +50,7 @@
state.users.currentUser,
- }),
- },
+ currentUser: state => state.users.currentUser,
+ privateMode: state => state.instance.private,
+ federating: state => state.instance.federating
+ })
+ }
}
export default ListsMenuContent
diff --git a/src/components/lists_timeline/lists_timeline.js b/src/components/lists_timeline/lists_timeline.js
index a06220a37..eae82a867 100644
--- a/src/components/lists_timeline/lists_timeline.js
+++ b/src/components/lists_timeline/lists_timeline.js
@@ -1,20 +1,16 @@
-import Timeline from 'src/components/timeline/timeline.vue'
-
-import { useListsStore } from 'src/stores/lists.js'
-
+import { useListsStore } from 'src/stores/lists'
+import Timeline from '../timeline/timeline.vue'
const ListsTimeline = {
- data() {
+ data () {
return {
- listId: null,
+ listId: null
}
},
components: {
- Timeline,
+ Timeline
},
computed: {
- timeline() {
- return this.$store.state.statuses.timelines.list
- },
+ timeline () { return this.$store.state.statuses.timelines.list }
},
watch: {
$route: function (route) {
@@ -23,25 +19,19 @@ const ListsTimeline = {
this.$store.dispatch('stopFetchingTimeline', 'list')
this.$store.commit('clearTimeline', { timeline: 'list' })
useListsStore().fetchList({ listId: this.listId })
- this.$store.dispatch('startFetchingTimeline', {
- timeline: 'list',
- listId: this.listId,
- })
+ this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId })
}
- },
+ }
},
- created() {
+ created () {
this.listId = this.$route.params.id
useListsStore().fetchList({ listId: this.listId })
- this.$store.dispatch('startFetchingTimeline', {
- timeline: 'list',
- listId: this.listId,
- })
+ this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId })
},
- unmounted() {
+ unmounted () {
this.$store.dispatch('stopFetchingTimeline', 'list')
this.$store.commit('clearTimeline', { timeline: 'list' })
- },
+ }
}
export default ListsTimeline
diff --git a/src/components/lists_user_search/lists_user_search.js b/src/components/lists_user_search/lists_user_search.js
index aed3f1ce7..c92ec0eee 100644
--- a/src/components/lists_user_search/lists_user_search.js
+++ b/src/components/lists_user_search/lists_user_search.js
@@ -1,29 +1,33 @@
-import { debounce } from 'lodash'
-
-import Checkbox from 'src/components/checkbox/checkbox.vue'
-
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons'
+import {
+ faSearch,
+ faChevronLeft
+} from '@fortawesome/free-solid-svg-icons'
+import { debounce } from 'lodash'
+import Checkbox from '../checkbox/checkbox.vue'
-library.add(faSearch, faChevronLeft)
+library.add(
+ faSearch,
+ faChevronLeft
+)
const ListsUserSearch = {
components: {
- Checkbox,
+ Checkbox
},
emits: ['loading', 'loadingDone', 'results'],
- data() {
+ data () {
return {
loading: false,
query: '',
- followingOnly: true,
+ followingOnly: true
}
},
methods: {
onInput: debounce(function () {
this.search(this.query)
}, 2000),
- search(query) {
+ search (query) {
if (!query) {
this.loading = false
return
@@ -32,25 +36,16 @@ const ListsUserSearch = {
this.loading = true
this.$emit('loading')
this.userIds = []
- this.$store
- .dispatch('search', {
- q: query,
- resolve: true,
- type: 'accounts',
- following: this.followingOnly,
- })
- .then((data) => {
- this.$emit(
- 'results',
- data.accounts.map((a) => a.id),
- )
+ this.$store.dispatch('search', { q: query, resolve: true, type: 'accounts', following: this.followingOnly })
+ .then(data => {
+ this.$emit('results', data.accounts.map(a => a.id))
})
.finally(() => {
this.loading = false
this.$emit('loadingDone')
})
- },
- },
+ }
+ }
}
export default ListsUserSearch
diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js
index a333e6970..9566aa903 100644
--- a/src/components/login_form/login_form.js
+++ b/src/components/login_form/login_form.js
@@ -1,92 +1,98 @@
-import { mapActions, mapState as mapPiniaState } from 'pinia'
import { mapState } from 'vuex'
-
-import { useAuthFlowStore } from 'src/stores/auth_flow.js'
-import { useInstanceStore } from 'src/stores/instance.js'
+import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia'
+import oauthApi from '../../services/new_api/oauth.js'
import { useOAuthStore } from 'src/stores/oauth.js'
-
-import { getLoginUrl, getTokenWithCredentials } from 'src/api/oauth.js'
-
+import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faTimes } from '@fortawesome/free-solid-svg-icons'
+import {
+ faTimes
+} from '@fortawesome/free-solid-svg-icons'
-library.add(faTimes)
+library.add(
+ faTimes
+)
const LoginForm = {
data: () => ({
user: {},
- error: false,
+ error: false
}),
computed: {
+ isPasswordAuth () { return this.requiredPassword },
+ isTokenAuth () { return this.requiredToken },
+ ...mapStores(useOAuthStore),
...mapState({
- loggingIn: (state) => state.users.loggingIn,
- }),
- ...mapPiniaState(useOAuthStore, ['clientId', 'clientSecret']),
- ...mapPiniaState(useInstanceStore, ['server', 'registrationOpen']),
- ...mapPiniaState(useAuthFlowStore, {
- isTokenAuth: (store) => store.requiredToken,
- isPasswordAuth: (store) => !store.requiredToken,
+ registrationOpen: state => state.instance.registrationOpen,
+ instance: state => state.instance,
+ loggingIn: state => state.users.loggingIn,
}),
+ ...mapPiniaState(useAuthFlowStore, ['requiredPassword', 'requiredToken', 'requiredMFA'])
},
methods: {
...mapActions(useAuthFlowStore, ['requireMFA', 'login']),
- ...mapActions(useOAuthStore, ['ensureAppToken']),
- submit() {
+ submit () {
this.isTokenAuth ? this.submitToken() : this.submitPassword()
},
- submitToken() {
+ submitToken () {
+ const data = {
+ instance: this.instance.server,
+ commit: this.$store.commit
+ }
+
// NOTE: we do not really need the app token, but obtaining a token and
// calling verify_credentials is the only way to ensure the app still works.
- this.ensureAppToken().then(() => {
- window.location.href = getLoginUrl({
- clientId: this.clientId,
- instance: this.server,
+ this.oauthStore.ensureAppToken()
+ .then(() => {
+ const app = {
+ clientId: this.oauthStore.clientId,
+ clientSecret: this.oauthStore.clientSecret,
+ }
+ oauthApi.login({ ...app, ...data })
})
- })
},
- submitPassword() {
+ submitPassword () {
this.error = false
// NOTE: we do not really need the app token, but obtaining a token and
// calling verify_credentials is the only way to ensure the app still works.
- this.ensureAppToken().then(() => {
- getTokenWithCredentials({
- clientId: this.clientId,
- clientSecret: this.clientSecret,
- instance: this.server,
- username: this.user.username,
- password: this.user.password,
- })
- .then(({ data: result }) => {
- this.login(result).then(() => {
- this.$router.push({ name: 'friends' })
- })
- })
- .catch((error) => {
- if (error.errorData?.error === 'mfa_required') {
- this.requireMFA({ settings: error })
- } else if (error.identifier === 'password_reset_required') {
- this.$router.push({
- name: 'password-reset',
- params: { passwordResetRequested: true },
- })
+ this.oauthStore.ensureAppToken().then(() => {
+ const app = {
+ clientId: this.oauthStore.clientId,
+ clientSecret: this.oauthStore.clientSecret,
+ }
+
+ oauthApi.getTokenWithCredentials(
+ {
+ ...app,
+ instance: this.instance.server,
+ username: this.user.username,
+ password: this.user.password
+ }
+ ).then((result) => {
+ if (result.error) {
+ if (result.error === 'mfa_required') {
+ this.requireMFA({ settings: result })
+ } else if (result.identifier === 'password_reset_required') {
+ this.$router.push({ name: 'password-reset', params: { passwordResetRequested: true } })
} else {
- this.error = error
+ this.error = result.error
this.focusOnPasswordInput()
}
return
+ }
+ this.login(result).then(() => {
+ this.$router.push({ name: 'friends' })
})
+ })
})
},
- clearError() {
- this.error = false
- },
- focusOnPasswordInput() {
+ clearError () { this.error = false },
+ focusOnPasswordInput () {
const passwordInput = this.$refs.passwordInput
passwordInput.focus()
passwordInput.setSelectionRange(0, passwordInput.value.length)
- },
- },
+ }
+ }
}
export default LoginForm
diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index fe5994f7c..9a57f8250 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -1,35 +1,37 @@
-import { defineAsyncComponent } from 'vue'
-
-import Modal from 'src/components/modal/modal.vue'
+import StillImage from '../still-image/still-image.vue'
+import VideoAttachment from '../video_attachment/video_attachment.vue'
+import Modal from '../modal/modal.vue'
+import PinchZoom from '../pinch_zoom/pinch_zoom.vue'
+import SwipeClick from '../swipe_click/swipe_click.vue'
import GestureService from '../../services/gesture_service/gesture_service'
-
-import { useMediaViewerStore } from 'src/stores/media_viewer.js'
-
+import Flash from 'src/components/flash/flash.vue'
+import fileTypeService from '../../services/file_type/file_type.service.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faChevronLeft,
faChevronRight,
faCircleNotch,
- faTimes,
+ faTimes
} from '@fortawesome/free-solid-svg-icons'
+import { useMediaViewerStore } from 'src/stores/media_viewer'
-library.add(faChevronLeft, faChevronRight, faCircleNotch, faTimes)
+library.add(
+ faChevronLeft,
+ faChevronRight,
+ faCircleNotch,
+ faTimes
+)
const MediaModal = {
components: {
- VideoAttachment: defineAsyncComponent(
- () => import('src/components/video_attachment/video_attachment.vue'),
- ),
- PinchZoom: defineAsyncComponent(
- () => import('src/components/pinch_zoom/pinch_zoom.vue'),
- ),
- SwipeClick: defineAsyncComponent(
- () => import('src/components/swipe_click/swipe_click.vue'),
- ),
+ StillImage,
+ VideoAttachment,
+ PinchZoom,
+ SwipeClick,
Modal,
- Flash: defineAsyncComponent(() => import('src/components/flash/flash.vue')),
+ Flash
},
- data() {
+ data () {
return {
loading: false,
swipeDirection: GestureService.DIRECTION_LEFT,
@@ -38,36 +40,42 @@ const MediaModal = {
return window.innerWidth * considerableMoveRatio
},
pinchZoomMinScale: 1,
- pinchZoomScaleResetLimit: 1.2,
+ pinchZoomScaleResetLimit: 1.2
}
},
computed: {
- showing() {
+ showing () {
return useMediaViewerStore().activated
},
- media() {
+ media () {
return useMediaViewerStore().media
},
- description() {
+ description () {
return this.currentMedia.description
},
- currentIndex() {
+ currentIndex () {
return useMediaViewerStore().currentIndex
},
- currentMedia() {
+ currentMedia () {
return this.media[this.currentIndex]
},
- canNavigate() {
+ canNavigate () {
return this.media.length > 1
},
- swipeDisableClickThreshold() {
+ type () {
+ return this.currentMedia ? this.getType(this.currentMedia) : null
+ },
+ swipeDisableClickThreshold () {
// If there is only one media, allow more mouse movements to close the modal
// because there is less chance that the user wants to switch to another image
- return () => (this.canNavigate ? 1 : 30)
- },
+ return () => this.canNavigate ? 1 : 30
+ }
},
methods: {
- hide() {
+ getType (media) {
+ return fileTypeService.fileType(media.mimetype)
+ },
+ hide () {
// HACK: Closing immediately via a touch will cause the click
// to be processed on the content below the overlay
const transitionTime = 100 // ms
@@ -75,7 +83,7 @@ const MediaModal = {
useMediaViewerStore().closeMediaViewer()
}, transitionTime)
},
- hideIfNotSwiped(event) {
+ hideIfNotSwiped (event) {
// If we have swiped over SwipeClick, do not trigger hide
const comp = this.$refs.swipeClick
if (!comp) {
@@ -84,39 +92,33 @@ const MediaModal = {
comp.$gesture.click(event)
}
},
- goPrev() {
+ goPrev () {
if (this.canNavigate) {
- const prevIndex =
- this.currentIndex === 0
- ? this.media.length - 1
- : this.currentIndex - 1
+ const prevIndex = this.currentIndex === 0 ? this.media.length - 1 : (this.currentIndex - 1)
const newMedia = this.media[prevIndex]
- if (newMedia.type === 'image') {
+ if (this.getType(newMedia) === 'image') {
this.loading = true
}
useMediaViewerStore().setCurrentMedia(newMedia)
}
},
- goNext() {
+ goNext () {
if (this.canNavigate) {
- const nextIndex =
- this.currentIndex === this.media.length - 1
- ? 0
- : this.currentIndex + 1
+ const nextIndex = this.currentIndex === this.media.length - 1 ? 0 : (this.currentIndex + 1)
const newMedia = this.media[nextIndex]
- if (newMedia.type === 'image') {
+ if (this.getType(newMedia) === 'image') {
this.loading = true
}
useMediaViewerStore().setCurrentMedia(newMedia)
}
},
- onImageLoaded() {
+ onImageLoaded () {
this.loading = false
},
- handleSwipePreview(offsets) {
+ handleSwipePreview (offsets) {
this.$refs.pinchZoom.setTransform({ scale: 1, x: offsets[0], y: 0 })
},
- handleSwipeEnd(sign) {
+ handleSwipeEnd (sign) {
this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 })
if (sign > 0) {
this.goNext()
@@ -124,36 +126,33 @@ const MediaModal = {
this.goPrev()
}
},
- handleKeyupEvent(e) {
- if (this.showing && e.keyCode === 27) {
- // escape
+ handleKeyupEvent (e) {
+ if (this.showing && e.keyCode === 27) { // escape
this.hide()
}
},
- handleKeydownEvent(e) {
+ handleKeydownEvent (e) {
if (!this.showing) {
return
}
- if (e.keyCode === 39) {
- // arrow right
+ if (e.keyCode === 39) { // arrow right
this.goNext()
- } else if (e.keyCode === 37) {
- // arrow left
+ } else if (e.keyCode === 37) { // arrow left
this.goPrev()
}
- },
+ }
},
- mounted() {
+ mounted () {
window.addEventListener('popstate', this.hide)
document.addEventListener('keyup', this.handleKeyupEvent)
document.addEventListener('keydown', this.handleKeydownEvent)
},
- unmounted() {
+ unmounted () {
window.removeEventListener('popstate', this.hide)
document.removeEventListener('keyup', this.handleKeyupEvent)
document.removeEventListener('keydown', this.handleKeydownEvent)
- },
+ }
}
export default MediaModal
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index f2f47a0b6..5cc8c50a3 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -5,7 +5,7 @@
@backdrop-clicked="hideIfNotSwiped"
>
-
- {{ $t('status.attachment_description') }}
- {{ description }}
-
+ {{ description }}
+
{{ $t('media_modal.counter', { current: currentIndex + 1, total: media.length }, currentIndex + 1) }}
@@ -162,43 +159,19 @@ $modal-view-button-icon-margin: 0.5em;
.counter {
/* Hardcoded since background is also hardcoded */
color: white;
- text-shadow: 0 0 1em black, 0 0 1em black, 0 0 1em black;
- margin: 1em 2em;
- overflow: hidden;
-
- }
-
- .description + .counter {
- margin-top: 0;
+ margin-top: 1em;
+ text-shadow: 0 0 10px black, 0 0 10px black;
+ padding: 0.2em 2em;
}
.description {
flex: 0 0 auto;
- max-width: 80ch;
+ overflow-y: auto;
+ min-height: 1em;
+ max-width: 500px;
max-height: 9.5em;
-
- summary {
- margin-bottom: 0.5em;
- display: inline-block;
- font-weight: bold;
- pointer-events: none;
- }
-
- span {
- display: block;
- overflow-y: auto;
- min-height: 1em;
- text-wrap: pretty;
- max-height: 10.5em;
- white-space: pre-wrap;
- line-height: 1.5;
- scrollbar-color: white transparent;
-
- &::-webkit-scrollbar-button,
- &::-webkit-scrollbar-thumb {
- background-color: white;
- }
- }
+ overflow-wrap: break-word;
+ text-wrap: pretty;
}
.modal-image {
diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js
index 2cb6a96e6..f2cbfc405 100644
--- a/src/components/media_upload/media_upload.js
+++ b/src/components/media_upload/media_upload.js
@@ -1,40 +1,41 @@
-import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
+/* eslint-env browser */
import statusPosterService from '../../services/status_poster/status_poster.service.js'
-
-import { useInstanceStore } from 'src/stores/instance.js'
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
+import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faCircleNotch, faUpload } from '@fortawesome/free-solid-svg-icons'
+import { faUpload, faCircleNotch } from '@fortawesome/free-solid-svg-icons'
-library.add(faUpload, faCircleNotch)
+library.add(
+ faUpload,
+ faCircleNotch
+)
const mediaUpload = {
- data() {
+ data () {
return {
uploadCount: 0,
- uploadReady: true,
+ uploadReady: true
}
},
computed: {
- uploading() {
+ uploading () {
return this.uploadCount > 0
- },
+ }
},
methods: {
- onClick() {
+ onClick () {
if (this.uploadReady) {
this.$refs.input.click()
}
},
- async resizeImage(file) {
+ async resizeImage (file) {
// Skip if not an image or if it's a GIF
if (!file.type.startsWith('image/') || file.type === 'image/gif') {
return file
}
// Skip if image compression is disabled
- if (!useMergedConfigStore().mergedConfig.imageCompression) {
+ if (!this.$store.getters.mergedConfig.imageCompression) {
return file
}
@@ -73,67 +74,46 @@ const mediaUpload = {
// Check WebP support by trying to create a WebP canvas
const testCanvas = document.createElement('canvas')
- const supportsWebP = testCanvas
- .toDataURL('image/webp')
- .startsWith('data:image/webp')
+ const supportsWebP = testCanvas.toDataURL('image/webp').startsWith('data:image/webp')
// Convert to WebP if supported and alwaysUseJpeg is false, otherwise JPEG
- const type =
- !useMergedConfigStore().mergedConfig.alwaysUseJpeg && supportsWebP
- ? 'image/webp'
- : 'image/jpeg'
+ const type = (!this.$store.getters.mergedConfig.alwaysUseJpeg && supportsWebP) ? 'image/webp' : 'image/jpeg'
const extension = type === 'image/webp' ? '.webp' : '.jpg'
// Remove the original extension and add new one
const newFileName = file.name.replace(/\.[^/.]+$/, '') + extension
- canvas.toBlob(
- (blob) => {
- resolve(
- new File([blob], newFileName, {
- type,
- lastModified: Date.now(),
- }),
- )
- },
- type,
- 0.85,
- )
+ canvas.toBlob((blob) => {
+ resolve(new File([blob], newFileName, {
+ type,
+ lastModified: Date.now()
+ }))
+ }, type, 0.85)
}
img.src = URL.createObjectURL(file)
})
},
- async isAnimatedPng(file) {
+ async isAnimatedPng (file) {
const buffer = await file.arrayBuffer()
const view = new Uint8Array(buffer)
// Look for animated PNG chunks (acTL)
for (let i = 0; i < view.length - 8; i++) {
- if (
- view[i] === 0x61 && // a
- view[i + 1] === 0x63 && // c
- view[i + 2] === 0x54 && // T
- view[i + 3] === 0x4c
- ) {
- // L
+ if (view[i] === 0x61 && // a
+ view[i + 1] === 0x63 && // c
+ view[i + 2] === 0x54 && // T
+ view[i + 3] === 0x4C) { // L
return true
}
}
return false
},
- async uploadFile(file) {
+ async uploadFile (file) {
const self = this
const store = this.$store
- if (file.size > useInstanceStore().uploadlimit) {
+ if (file.size > store.state.instance.uploadlimit) {
const filesize = fileSizeFormatService.fileSizeFormat(file.size)
- const allowedsize = fileSizeFormatService.fileSizeFormat(
- useInstanceStore().uploadlimit,
- )
- self.$emit('upload-failed', 'file_too_big', {
- filesize: filesize.num,
- filesizeunit: filesize.unit,
- allowedsize: allowedsize.num,
- allowedsizeunit: allowedsize.unit,
- })
+ const allowedsize = fileSizeFormatService.fileSizeFormat(store.state.instance.uploadlimit)
+ self.$emit('upload-failed', 'file_too_big', { filesize: filesize.num, filesizeunit: filesize.unit, allowedsize: allowedsize.num, allowedsizeunit: allowedsize.unit })
return
}
@@ -145,38 +125,36 @@ const mediaUpload = {
self.$emit('uploading')
self.uploadCount++
- statusPosterService.uploadMedia({ store, formData }).then(
- (fileData) => {
+ statusPosterService.uploadMedia({ store, formData })
+ .then((fileData) => {
self.$emit('uploaded', fileData)
self.decreaseUploadCount()
- },
- (error) => {
+ }, (error) => {
console.error('Error uploading file', error)
self.$emit('upload-failed', 'default')
self.decreaseUploadCount()
- },
- )
+ })
},
- decreaseUploadCount() {
+ decreaseUploadCount () {
this.uploadCount--
if (this.uploadCount === 0) {
this.$emit('all-uploaded')
}
},
- clearFile() {
+ clearFile () {
this.uploadReady = false
this.$nextTick(() => {
this.uploadReady = true
})
},
- multiUpload(files) {
+ multiUpload (files) {
for (const file of files) {
this.uploadFile(file)
}
},
- change({ target }) {
+ change ({ target }) {
this.multiUpload(target.files)
- },
+ }
},
props: {
dropFiles: Object,
@@ -184,16 +162,16 @@ const mediaUpload = {
normalButton: Boolean,
acceptTypes: {
type: String,
- default: '*/*',
- },
+ default: '*/*'
+ }
},
watch: {
dropFiles: function (fileInfos) {
if (!this.uploading) {
this.multiUpload(fileInfos)
}
- },
- },
+ }
+ }
}
export default mediaUpload
diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js
index 48f6d1d8e..ca5c24618 100644
--- a/src/components/mention_link/mention_link.js
+++ b/src/components/mention_link/mention_link.js
@@ -1,169 +1,155 @@
-import { mapState as mapPiniaState } from 'pinia'
-import { mapState } from 'vuex'
-
-import UnicodeDomainIndicator from 'src/components/unicode_domain_indicator/unicode_domain_indicator.vue'
-import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
-import UserPopover from 'src/components/user_popover/user_popover.vue'
-import {
- highlightClass,
- highlightStyle,
-} from '../../services/user_highlighter/user_highlighter.js'
-
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-import { useUserHighlightStore } from 'src/stores/user_highlight.js'
-
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
-
+import { mapGetters, mapState } from 'vuex'
+import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
+import UserAvatar from '../user_avatar/user_avatar.vue'
+import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
+import { defineAsyncComponent } from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faAt } from '@fortawesome/free-solid-svg-icons'
+import {
+ faAt
+} from '@fortawesome/free-solid-svg-icons'
-library.add(faAt)
+library.add(
+ faAt
+)
const MentionLink = {
name: 'MentionLink',
components: {
UserAvatar,
UnicodeDomainIndicator,
- UserPopover,
+ UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
},
props: {
url: {
required: true,
- type: String,
+ type: String
},
content: {
required: true,
- type: String,
+ type: String
},
userId: {
required: false,
- type: String,
+ type: String
},
userScreenName: {
required: false,
- type: String,
- },
+ type: String
+ }
},
- data() {
+ data () {
return {
- hasSelection: false,
+ hasSelection: false
}
},
methods: {
- onClick() {
+ onClick () {
if (this.shouldShowTooltip) return
const link = generateProfileLink(
this.userId || this.user.id,
- this.userScreenName || this.user.screen_name,
+ this.userScreenName || this.user.screen_name
)
this.$router.push(link)
},
- handleSelection() {
+ handleSelection () {
if (this.$refs.full) {
- this.hasSelection = document
- .getSelection()
- .containsNode(this.$refs.full, true)
+ this.hasSelection = document.getSelection().containsNode(this.$refs.full, true)
}
- },
+ }
},
- mounted() {
+ mounted () {
document.addEventListener('selectionchange', this.handleSelection)
},
- unmounted() {
+ unmounted () {
document.removeEventListener('selectionchange', this.handleSelection)
},
computed: {
- user() {
- return (
- this.url && this.$store && this.$store.getters.findUserByUrl(this.url)
- )
+ user () {
+ return this.url && this.$store && this.$store.getters.findUserByUrl(this.url)
},
- isYou() {
+ isYou () {
// FIXME why user !== currentUser???
return this.user && this.user.id === this.currentUser.id
},
- userName() {
+ userName () {
return this.user && this.userNameFullUi.split('@')[0]
},
- serverName() {
+ serverName () {
// XXX assumed that domain does not contain @
- return (
- this.user &&
- (this.userNameFullUi.split('@')[1] ||
- this.$store.getters.instanceDomain)
- )
+ return this.user && (this.userNameFullUi.split('@')[1] || this.$store.getters.instanceDomain)
},
- userNameFull() {
+ userNameFull () {
return this.user && this.user.screen_name
},
- userNameFullUi() {
+ userNameFullUi () {
return this.user && this.user.screen_name_ui
},
- highlightData() {
- return this.highlight[this.user?.screen_name]
+ highlight () {
+ return this.user && this.mergedConfig.highlight[this.user.screen_name]
},
- highlightType() {
- return this.highlightData && '-' + this.highlightData.type
+ highlightType () {
+ return this.highlight && ('-' + this.highlight.type)
},
- highlightClass() {
- return this.highlightData && highlightClass(this.user)
+ highlightClass () {
+ if (this.highlight) return highlightClass(this.user)
},
- style() {
- if (this.highlightData) {
+ style () {
+ if (this.highlight) {
+ /* eslint-disable no-unused-vars */
const {
backgroundColor,
backgroundPosition,
backgroundImage,
...rest
- } = highlightStyle(this.highlightData)
+ } = highlightStyle(this.highlight)
+ /* eslint-enable no-unused-vars */
return rest
}
},
- classnames() {
+ classnames () {
return [
{
'-you': this.isYou && this.shouldBoldenYou,
- '-highlighted': !!this.highlightData,
- '-has-selection': this.hasSelection,
+ '-highlighted': this.highlight,
+ '-has-selection': this.hasSelection
},
- this.highlightType,
+ this.highlightType
]
},
- isRemote() {
+ isRemote () {
return this.userName !== this.userNameFull
},
- shouldShowFullUserName() {
+ shouldShowFullUserName () {
const conf = this.mergedConfig.mentionLinkDisplay
if (conf === 'short') {
return false
} else if (conf === 'full') {
return true
- } else {
- // full_for_remote
+ } else { // full_for_remote
return this.isRemote
}
},
- shouldShowTooltip() {
+ shouldShowTooltip () {
return this.mergedConfig.mentionLinkShowTooltip
},
- shouldShowAvatar() {
+ shouldShowAvatar () {
return this.mergedConfig.mentionLinkShowAvatar
},
- shouldShowYous() {
+ shouldShowYous () {
return this.mergedConfig.mentionLinkShowYous
},
- shouldBoldenYou() {
+ shouldBoldenYou () {
return this.mergedConfig.mentionLinkBoldenYou
},
- shouldFadeDomain() {
+ shouldFadeDomain () {
return this.mergedConfig.mentionLinkFadeDomain
},
- ...mapPiniaState(useMergedConfigStore, ['mergedConfig']),
- ...mapPiniaState(useUserHighlightStore, ['highlight']),
+ ...mapGetters(['mergedConfig']),
...mapState({
- currentUser: (state) => state.users.currentUser,
- }),
- },
+ currentUser: state => state.users.currentUser
+ })
+ }
}
export default MentionLink
diff --git a/src/components/mention_link/mention_link.scss b/src/components/mention_link/mention_link.scss
index 3aa7816be..c3261e17d 100644
--- a/src/components/mention_link/mention_link.scss
+++ b/src/components/mention_link/mention_link.scss
@@ -7,6 +7,7 @@
& .new,
& .original {
display: inline;
+ border-radius: 2px;
}
.mention-avatar {
diff --git a/src/components/mentions/mentions.js b/src/components/mentions/mentions.js
index 48e06a950..841d5aa48 100644
--- a/src/components/mentions/mentions.js
+++ b/src/components/mentions/mentions.js
@@ -1,14 +1,14 @@
-import Timeline from 'src/components/timeline/timeline.vue'
+import Timeline from '../timeline/timeline.vue'
const Mentions = {
computed: {
- timeline() {
+ timeline () {
return this.$store.state.statuses.timelines.mentions
- },
+ }
},
components: {
- Timeline,
- },
+ Timeline
+ }
}
export default Mentions
diff --git a/src/components/mentions_line/mentions_line.js b/src/components/mentions_line/mentions_line.js
index 88d2b3257..a4a0c7246 100644
--- a/src/components/mentions_line/mentions_line.js
+++ b/src/components/mentions_line/mentions_line.js
@@ -1,8 +1,5 @@
-import { mapState } from 'pinia'
-
import MentionLink from 'src/components/mention_link/mention_link.vue'
-
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
+import { mapGetters } from 'vuex'
export const MENTIONS_LIMIT = 5
@@ -11,30 +8,30 @@ const MentionsLine = {
props: {
mentions: {
required: true,
- type: Array,
- },
+ type: Array
+ }
},
data: () => ({ expanded: false }),
components: {
- MentionLink,
+ MentionLink
},
computed: {
- mentionsComputed() {
+ mentionsComputed () {
return this.mentions.slice(0, MENTIONS_LIMIT)
},
- extraMentions() {
+ extraMentions () {
return this.mentions.slice(MENTIONS_LIMIT)
},
- manyMentions() {
+ manyMentions () {
return this.extraMentions.length > 0
},
- ...mapState(useMergedConfigStore, ['mergedConfig']),
+ ...mapGetters(['mergedConfig'])
},
methods: {
- toggleShowMore() {
+ toggleShowMore () {
this.expanded = !this.expanded
- },
- },
+ }
+ }
}
export default MentionsLine
diff --git a/src/components/menu_item.style.js b/src/components/menu_item.style.js
index a5567b49b..883355efa 100644
--- a/src/components/menu_item.style.js
+++ b/src/components/menu_item.style.js
@@ -1,105 +1,113 @@
export default {
name: 'MenuItem',
selector: '.menu-item',
- validInnerComponents: ['Text', 'Icon', 'Border'],
+ validInnerComponents: [
+ 'Text',
+ 'Icon',
+ 'Input',
+ 'Border',
+ 'ButtonUnstyled',
+ 'Badge',
+ 'Avatar'
+ ],
states: {
hover: ':is(:hover, :focus-visible, :has(:focus-visible)):not(.disabled)',
active: '.-active',
- disabled: '.disabled',
+ disabled: '.disabled'
},
defaultRules: [
{
directives: {
background: '--bg',
- opacity: 0,
- },
+ opacity: 0
+ }
},
{
state: ['hover'],
directives: {
background: '$mod(--bg 5)',
- opacity: 1,
- },
+ opacity: 1
+ }
},
{
state: ['active'],
directives: {
background: '$mod(--bg 10)',
- opacity: 1,
- },
+ opacity: 1
+ }
},
{
state: ['active', 'hover'],
directives: {
background: '$mod(--bg 15)',
- opacity: 1,
- },
+ opacity: 1
+ }
},
{
component: 'Text',
parent: {
component: 'MenuItem',
- state: ['hover'],
+ state: ['hover']
},
directives: {
textColor: '--link',
- textAuto: 'no-preserve',
- },
+ textAuto: 'no-preserve'
+ }
},
{
component: 'Text',
parent: {
component: 'MenuItem',
- state: ['active'],
+ state: ['active']
},
directives: {
textColor: '--link',
- textAuto: 'no-preserve',
- },
+ textAuto: 'no-preserve'
+ }
},
{
component: 'Icon',
parent: {
component: 'MenuItem',
- state: ['active'],
+ state: ['active']
},
directives: {
textColor: '--link',
- textAuto: 'no-preserve',
- },
+ textAuto: 'no-preserve'
+ }
},
{
component: 'Icon',
parent: {
component: 'MenuItem',
- state: ['hover'],
+ state: ['hover']
},
directives: {
textColor: '--link',
- textAuto: 'no-preserve',
- },
+ textAuto: 'no-preserve'
+ }
},
{
component: 'Text',
parent: {
component: 'MenuItem',
- state: ['disabled'],
+ state: ['disabled']
},
directives: {
textOpacity: 0.25,
- textOpacityMode: 'blend',
- },
+ textOpacityMode: 'blend'
+ }
},
{
component: 'Icon',
parent: {
component: 'MenuItem',
- state: ['disabled'],
+ state: ['disabled']
},
directives: {
textOpacity: 0.25,
- textOpacityMode: 'blend',
- },
- },
- ],
+ textOpacityMode: 'blend'
+ }
+ }
+ ]
}
diff --git a/src/components/mfa_form/recovery_form.js b/src/components/mfa_form/recovery_form.js
index e04218bd8..84479b1ec 100644
--- a/src/components/mfa_form/recovery_form.js
+++ b/src/components/mfa_form/recovery_form.js
@@ -1,60 +1,64 @@
-import { mapActions, mapState, mapStores } from 'pinia'
-
-import { useAuthFlowStore } from 'src/stores/auth_flow.js'
-import { useInstanceStore } from 'src/stores/instance.js'
+import mfaApi from '../../services/new_api/mfa.js'
+import { mapState } from 'vuex'
+import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia'
import { useOAuthStore } from 'src/stores/oauth.js'
-
-import { verifyRecoveryCode } from 'src/api/mfa.js'
-
+import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faTimes } from '@fortawesome/free-solid-svg-icons'
+import {
+ faTimes
+} from '@fortawesome/free-solid-svg-icons'
-library.add(faTimes)
+library.add(
+ faTimes
+)
export default {
data: () => ({
code: null,
- error: false,
+ error: false
}),
computed: {
+ ...mapPiniaState(useAuthFlowStore, {
+ authSettings: store => store.settings
+ }),
...mapStores(useOAuthStore),
- ...mapState(useOAuthStore, ['clientId', 'clientSecret']),
- ...mapState(useAuthFlowStore, ['settings']),
- ...mapState(useInstanceStore, ['server']),
+ ...mapState({
+ instance: 'instance',
+ })
},
methods: {
...mapActions(useAuthFlowStore, ['requireTOTP', 'abortMFA', 'login']),
- clearError() {
- this.error = false
- },
+ clearError () { this.error = false },
- focusOnCodeInput() {
+ focusOnCodeInput () {
const codeInput = this.$refs.codeInput
codeInput.focus()
codeInput.setSelectionRange(0, codeInput.value.length)
},
- submit() {
+ submit () {
+ const { clientId, clientSecret } = this.oauthStore
+
const data = {
- clientId: this.clientId,
- clientSecret: this.clientSecret,
- instance: this.server,
- mfaToken: this.settings.mfa_token,
- code: this.code,
+ clientId,
+ clientSecret,
+ instance: this.instance.server,
+ mfaToken: this.authSettings.mfa_token,
+ code: this.code
}
- verifyRecoveryCode(data)
- .then((result) => {
- this.login(result).then(() => {
- this.$router.push({ name: 'friends' })
- })
- })
- .catch((error) => {
- this.error = error
+ mfaApi.verifyRecoveryCode(data).then((result) => {
+ if (result.error) {
+ this.error = result.error
this.code = null
this.focusOnCodeInput()
return
+ }
+
+ this.login(result).then(() => {
+ this.$router.push({ name: 'friends' })
})
- },
- },
+ })
+ }
+ }
}
diff --git a/src/components/mfa_form/totp_form.js b/src/components/mfa_form/totp_form.js
index 056098c25..e369d8a5d 100644
--- a/src/components/mfa_form/totp_form.js
+++ b/src/components/mfa_form/totp_form.js
@@ -1,63 +1,64 @@
-import { mapActions, mapState, mapStores } from 'pinia'
-
-import { useAuthFlowStore } from 'src/stores/auth_flow.js'
-import { useInstanceStore } from 'src/stores/instance.js'
+import mfaApi from '../../services/new_api/mfa.js'
+import { mapState } from 'vuex'
+import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia'
import { useOAuthStore } from 'src/stores/oauth.js'
-
-import { verifyOTPCode } from 'src/api/mfa.js'
-
+import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faTimes } from '@fortawesome/free-solid-svg-icons'
+import {
+ faTimes
+} from '@fortawesome/free-solid-svg-icons'
-library.add(faTimes)
+library.add(
+ faTimes
+)
export default {
data: () => ({
code: null,
- error: false,
+ error: false
}),
computed: {
- ...mapState(useAuthFlowStore, {
- authSettings: (store) => store.settings,
+ ...mapPiniaState(useAuthFlowStore, {
+ authSettings: store => store.settings
}),
- ...mapState(useInstanceStore, ['server']),
...mapStores(useOAuthStore),
+ ...mapState({
+ instance: 'instance',
+ })
},
methods: {
...mapActions(useAuthFlowStore, ['requireRecovery', 'abortMFA', 'login']),
- clearError() {
- this.error = false
- },
+ clearError () { this.error = false },
- focusOnCodeInput() {
+ focusOnCodeInput () {
const codeInput = this.$refs.codeInput
codeInput.focus()
codeInput.setSelectionRange(0, codeInput.value.length)
},
- submit() {
+ submit () {
const { clientId, clientSecret } = this.oauthStore
const data = {
clientId,
clientSecret,
- instance: this.server,
+ instance: this.instance.server,
mfaToken: this.authSettings.mfa_token,
- code: this.code,
+ code: this.code
}
- verifyOTPCode(data)
- .then(({ data: result }) => {
- this.login(result).then(() => {
- this.$router.push({ name: 'friends' })
- })
- })
- .catch((error) => {
- this.error = error
+ mfaApi.verifyOTPCode(data).then((result) => {
+ if (result.error) {
+ this.error = result.error
this.code = null
this.focusOnCodeInput()
return
+ }
+
+ this.login(result).then(() => {
+ this.$router.push({ name: 'friends' })
})
- },
- },
+ })
+ }
+ }
}
diff --git a/src/components/mobile_drawer.style.js b/src/components/mobile_drawer.style.js
index 306bd5c17..398bc186e 100644
--- a/src/components/mobile_drawer.style.js
+++ b/src/components/mobile_drawer.style.js
@@ -1,13 +1,41 @@
export default {
name: 'MobileDrawer',
selector: '.mobile-drawer',
- validInnerComponents: ['MenuItem'],
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'ButtonUnstyled',
+ 'Input',
+ 'PanelHeader',
+ 'MenuItem',
+ 'Notification',
+ 'Alert',
+ 'UserCard'
+ ],
defaultRules: [
{
directives: {
background: '--bg',
- backgroundNoCssColor: 'yes',
- },
+ backgroundNoCssColor: 'yes'
+ }
},
- ],
+ {
+ component: 'PanelHeader',
+ parent: { component: 'MobileDrawer' },
+ directives: {
+ background: '--fg',
+ shadow: [{
+ x: 0,
+ y: 0,
+ blur: 4,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.6
+ }]
+ }
+ }
+ ]
}
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
index b47376d53..2085d24e3 100644
--- a/src/components/mobile_nav/mobile_nav.js
+++ b/src/components/mobile_nav/mobile_nav.js
@@ -1,113 +1,99 @@
-import { mapState } from 'pinia'
-import { defineAsyncComponent } from 'vue'
-import { mapGetters } from 'vuex'
-
-import NavigationPins from 'src/components/navigation/navigation_pins.vue'
+import SideDrawer from '../side_drawer/side_drawer.vue'
+import Notifications from '../notifications/notifications.vue'
+import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import GestureService from '../../services/gesture_service/gesture_service'
+import NavigationPins from 'src/components/navigation/navigation_pins.vue'
+
import {
- countExtraNotifications,
unseenNotificationsFromStore,
+ countExtraNotifications
} from '../../services/notification_utils/notification_utils'
-import { useAnnouncementsStore } from 'src/stores/announcements.js'
-import { useInstanceStore } from 'src/stores/instance.js'
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
+import { mapGetters } from 'vuex'
+import { mapState } from 'pinia'
+import { useAnnouncementsStore } from 'src/stores/announcements'
+import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
- faArrowUp,
- faBars,
- faBell,
- faCheckDouble,
- faMinus,
faTimes,
+ faBell,
+ faBars,
+ faArrowUp,
+ faMinus,
+ faCheckDouble
} from '@fortawesome/free-solid-svg-icons'
-library.add(faTimes, faBell, faBars, faArrowUp, faMinus, faCheckDouble)
+library.add(
+ faTimes,
+ faBell,
+ faBars,
+ faArrowUp,
+ faMinus,
+ faCheckDouble
+)
const MobileNav = {
components: {
- SideDrawer: defineAsyncComponent(
- () => import('src/components/side_drawer/side_drawer.vue'),
- ),
- Notifications: defineAsyncComponent(
- () => import('src/components/notifications/notifications.vue'),
- ),
+ SideDrawer,
+ Notifications,
NavigationPins,
- ConfirmModal: defineAsyncComponent(
- () => import('src/components/confirm_modal/confirm_modal.vue'),
- ),
+ ConfirmModal
},
data: () => ({
notificationsCloseGesture: undefined,
notificationsOpen: false,
notificationsAtTop: true,
- showingConfirmLogout: false,
+ showingConfirmLogout: false
}),
- created() {
+ created () {
this.notificationsCloseGesture = GestureService.swipeGesture(
GestureService.DIRECTION_RIGHT,
() => this.closeMobileNotifications(true),
- 50,
+ 50
)
},
computed: {
- currentUser() {
+ currentUser () {
return this.$store.state.users.currentUser
},
- unseenNotifications() {
- return unseenNotificationsFromStore(
- this.$store,
- useMergedConfigStore().mergedConfig.notificationVisibility,
- useMergedConfigStore().mergedConfig.ignoreInactionableSeen,
- )
+ unseenNotifications () {
+ return unseenNotificationsFromStore(this.$store)
},
- unseenNotificationsCount() {
- return (
- this.unseenNotifications.length +
- countExtraNotifications(
- this.$store,
- useMergedConfigStore().mergedConfig,
- useAnnouncementsStore().unreadAnnouncementCount,
- )
- )
+ unseenNotificationsCount () {
+ return this.unseenNotifications.length + countExtraNotifications(this.$store)
},
- unseenCount() {
+ unseenCount () {
return this.unseenNotifications.length
},
- unseenCountBadgeText() {
+ unseenCountBadgeText () {
return `${this.unseenCount ? this.unseenCount : ''}`
},
- hideSitename() {
- return useInstanceStore().hideSitename
- },
- sitename() {
- return useInstanceStore().name
- },
- isChat() {
+ hideSitename () { return this.$store.state.instance.hideSitename },
+ sitename () { return this.$store.state.instance.name },
+ isChat () {
return this.$route.name === 'chat'
},
...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
- ...mapState(useMergedConfigStore, {
- pinnedItems: (store) =>
- new Set(store.prefsStorage.collections.pinnedNavItems).has('chats'),
+ ...mapState(useServerSideStorageStore, {
+ pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems).has('chats')
}),
- shouldConfirmLogout() {
- return useMergedConfigStore().mergedConfig.modalOnLogout
+ shouldConfirmLogout () {
+ return this.$store.getters.mergedConfig.modalOnLogout
},
- closingDrawerMarksAsSeen() {
- return useMergedConfigStore().mergedConfig.closingDrawerMarksAsSeen
+ closingDrawerMarksAsSeen () {
+ return this.$store.getters.mergedConfig.closingDrawerMarksAsSeen
},
- ...mapGetters(['unreadChatCount']),
+ ...mapGetters(['unreadChatCount'])
},
methods: {
- toggleMobileSidebar() {
+ toggleMobileSidebar () {
this.$refs.sideDrawer.toggleDrawer()
},
- openMobileNotifications() {
+ openMobileNotifications () {
this.notificationsOpen = true
},
- closeMobileNotifications(markRead) {
+ closeMobileNotifications (markRead) {
if (this.notificationsOpen) {
// make sure to mark notifs seen only when the notifs were open and not
// from close-calls.
@@ -117,50 +103,53 @@ const MobileNav = {
}
}
},
- notificationsTouchStart(e) {
+ notificationsTouchStart (e) {
GestureService.beginSwipe(e, this.notificationsCloseGesture)
},
- notificationsTouchMove(e) {
+ notificationsTouchMove (e) {
GestureService.updateSwipe(e, this.notificationsCloseGesture)
},
- scrollToTop() {
+ scrollToTop () {
window.scrollTo(0, 0)
},
- scrollMobileNotificationsToTop() {
+ scrollMobileNotificationsToTop () {
this.$refs.mobileNotifications.scrollTo(0, 0)
},
- showConfirmLogout() {
+ showConfirmLogout () {
this.showingConfirmLogout = true
},
- hideConfirmLogout() {
+ hideConfirmLogout () {
this.showingConfirmLogout = false
},
- logout() {
+ logout () {
if (!this.shouldConfirmLogout) {
this.doLogout()
} else {
this.showConfirmLogout()
}
},
- doLogout() {
+ doLogout () {
this.$router.replace('/main/public')
this.$store.dispatch('logout')
this.hideConfirmLogout()
},
- markNotificationsAsSeen() {
+ markNotificationsAsSeen () {
this.$store.dispatch('markNotificationsAsSeen')
},
- onScroll({ target: { scrollTop, clientHeight, scrollHeight } }) {
+ onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) {
this.notificationsAtTop = scrollTop > 0
- },
+ if (scrollTop + clientHeight >= scrollHeight) {
+ this.$refs.notifications.fetchOlderNotifications()
+ }
+ }
},
watch: {
- $route() {
+ $route () {
// handles closing notificaitons when you press any router-link on the
// notifications.
this.closeMobileNotifications()
- },
- },
+ }
+ }
}
export default MobileNav
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index 743b7deb0..eb950cba0 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -94,7 +94,6 @@
/>
-
-
{{ $t('login.logout_confirm') }}
-
+
diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.js b/src/components/mobile_post_status_button/mobile_post_status_button.js
index 4969352f6..031013559 100644
--- a/src/components/mobile_post_status_button/mobile_post_status_button.js
+++ b/src/components/mobile_post_status_button/mobile_post_status_button.js
@@ -1,56 +1,57 @@
import { debounce } from 'lodash'
-
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-import { usePostStatusStore } from 'src/stores/post_status.js'
-
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faPen } from '@fortawesome/free-solid-svg-icons'
+import {
+ faPen
+} from '@fortawesome/free-solid-svg-icons'
+import { usePostStatusStore } from 'src/stores/post_status'
-library.add(faPen)
+library.add(
+ faPen
+)
-const HIDDEN_FOR_PAGES = new Set(['chats', 'chat', 'lists-edit'])
+const HIDDEN_FOR_PAGES = new Set([
+ 'chats',
+ 'chat',
+ 'lists-edit'
+])
const MobilePostStatusButton = {
- data() {
+ data () {
return {
hidden: false,
scrollingDown: false,
inputActive: false,
oldScrollPos: 0,
- amountScrolled: 0,
+ amountScrolled: 0
}
},
- created() {
+ created () {
if (this.autohideFloatingPostButton) {
this.activateFloatingPostButtonAutohide()
}
window.addEventListener('resize', this.handleOSK)
},
- unmounted() {
+ unmounted () {
if (this.autohideFloatingPostButton) {
this.deactivateFloatingPostButtonAutohide()
}
window.removeEventListener('resize', this.handleOSK)
},
computed: {
- isLoggedIn() {
+ isLoggedIn () {
return !!this.$store.state.users.currentUser
},
- isHidden() {
- if (HIDDEN_FOR_PAGES.has(this.$route.name)) {
- return true
- }
+ isHidden () {
+ if (HIDDEN_FOR_PAGES.has(this.$route.name)) { return true }
- return (
- this.autohideFloatingPostButton && (this.hidden || this.inputActive)
- )
+ return this.autohideFloatingPostButton && (this.hidden || this.inputActive)
},
- isPersistent() {
- return !!useMergedConfigStore().mergedConfig.alwaysShowNewPostButton
- },
- autohideFloatingPostButton() {
- return !!useMergedConfigStore().mergedConfig.autohideFloatingPostButton
+ isPersistent () {
+ return !!this.$store.getters.mergedConfig.alwaysShowNewPostButton
},
+ autohideFloatingPostButton () {
+ return !!this.$store.getters.mergedConfig.autohideFloatingPostButton
+ }
},
watch: {
autohideFloatingPostButton: function (isEnabled) {
@@ -59,21 +60,21 @@ const MobilePostStatusButton = {
} else {
this.deactivateFloatingPostButtonAutohide()
}
- },
+ }
},
methods: {
- activateFloatingPostButtonAutohide() {
+ activateFloatingPostButtonAutohide () {
window.addEventListener('scroll', this.handleScrollStart)
window.addEventListener('scroll', this.handleScrollEnd)
},
- deactivateFloatingPostButtonAutohide() {
+ deactivateFloatingPostButtonAutohide () {
window.removeEventListener('scroll', this.handleScrollStart)
window.removeEventListener('scroll', this.handleScrollEnd)
},
- openPostForm() {
+ openPostForm () {
usePostStatusStore().openPostStatusModal()
},
- handleOSK() {
+ handleOSK () {
// This is a big hack: we're guessing from changed window sizes if the
// on-screen keyboard is active or not. This is only really important
// for phones in portrait mode and it's more important to show the button
@@ -93,28 +94,20 @@ const MobilePostStatusButton = {
this.inputActive = false
}
},
- handleScrollStart: debounce(
- function () {
- if (window.scrollY > this.oldScrollPos) {
- this.hidden = true
- } else {
- this.hidden = false
- }
- this.oldScrollPos = window.scrollY
- },
- 100,
- { leading: true, trailing: false },
- ),
-
- handleScrollEnd: debounce(
- function () {
+ handleScrollStart: debounce(function () {
+ if (window.scrollY > this.oldScrollPos) {
+ this.hidden = true
+ } else {
this.hidden = false
- this.oldScrollPos = window.scrollY
- },
- 100,
- { leading: false, trailing: true },
- ),
- },
+ }
+ this.oldScrollPos = window.scrollY
+ }, 100, { leading: true, trailing: false }),
+
+ handleScrollEnd: debounce(function () {
+ this.hidden = false
+ this.oldScrollPos = window.scrollY
+ }, 100, { leading: false, trailing: true })
+ }
}
export default MobilePostStatusButton
diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue
index a022f7427..032e7dbeb 100644
--- a/src/components/modal/modal.vue
+++ b/src/components/modal/modal.vue
@@ -13,27 +13,27 @@
diff --git a/src/components/modal/modals.style.js b/src/components/modal/modals.style.js
index 9c8c279f7..1cb4a34a7 100644
--- a/src/components/modal/modals.style.js
+++ b/src/components/modal/modals.style.js
@@ -3,6 +3,8 @@ export default {
selector: ['.modal-view', '#modal', '.shout-panel'],
lazy: true,
notEditable: true,
- validInnerComponents: ['Panel'],
- defaultRules: [],
+ validInnerComponents: [
+ 'Panel'
+ ],
+ defaultRules: []
}
diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js
index ca4852ab3..bd57a353a 100644
--- a/src/components/moderation_tools/moderation_tools.js
+++ b/src/components/moderation_tools/moderation_tools.js
@@ -1,14 +1,9 @@
-import { last } from 'lodash'
-
-import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
-import Popover from 'src/components/popover/popover.vue'
-
-import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
-import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
-
import { library } from '@fortawesome/fontawesome-svg-core'
import { faChevronDown } from '@fortawesome/free-solid-svg-icons'
+import DialogModal from '../dialog_modal/dialog_modal.vue'
+import Popover from '../popover/popover.vue'
+
library.add(faChevronDown)
const FORCE_NSFW = 'mrf_tag:media-force-nsfw'
@@ -18,654 +13,106 @@ const DISABLE_REMOTE_SUBSCRIPTION = 'mrf_tag:disable-remote-subscription'
const DISABLE_ANY_SUBSCRIPTION = 'mrf_tag:disable-any-subscription'
const SANDBOX = 'mrf_tag:sandbox'
const QUARANTINE = 'mrf_tag:quarantine'
-const TAGS = new Set([
- FORCE_NSFW,
- STRIP_MEDIA,
- FORCE_UNLISTED,
- DISABLE_REMOTE_SUBSCRIPTION,
- DISABLE_ANY_SUBSCRIPTION,
- SANDBOX,
- QUARANTINE,
-])
-
-const ENTRIES = [
- {
- check: '!state:activated',
- label: 'user_card.admin_menu.activate_account',
- },
- {
- check: 'state:activated',
- label: 'user_card.admin_menu.deactivate_account',
- },
- {
- separator: true,
- },
- {
- check: '!state:confirmed',
- label: 'user_card.admin_menu.confirm_account',
- },
- {
- check: 'action:resend_confirmation',
- conditions: ['!state:confirmed'],
- label: 'user_card.admin_menu.resend_confirmation',
- },
- // No API for revocation
- // {
- // check: 'state:confirmed',
- // label: 'user_card.admin_menu.unconfirm_account',
- // },
- {
- check: '!state:approved',
- conditions: ['property:local'],
- label: 'user_card.admin_menu.approve_account',
- },
- // No API for revocation
- // {
- // check: 'state:approved',
- // label: 'user_card.admin_menu.unapprove_account',
- // },
- {
- check: '!state:suggested',
- // conditions: ['property:local'], // TODO Should we allow non-local users in suggested?
- label: 'user_card.admin_menu.suggest_account',
- },
- {
- check: 'state:suggested',
- label: 'user_card.admin_menu.remove_suggested_account',
- },
- {
- separator: true,
- },
- {
- check: 'action:statuses',
- label: 'user_card.admin_menu.show_statuses',
- conditions: ['count:1'],
- },
- {
- separator: true,
- },
- {
- check: 'action:disable_mfa',
- label: 'user_card.admin_menu.disable_mfa',
- },
- {
- check: 'action:require_password_change',
- label: 'user_card.admin_menu.require_password_change',
- },
- {
- separator: true,
- },
- {
- check: '!rights:moderator',
- label: 'user_card.admin_menu.grant_moderator',
- conditions: ['property:local', 'state:activated'],
- },
- {
- check: 'rights:moderator',
- label: 'user_card.admin_menu.revoke_moderator',
- conditions: ['property:local', 'state:activated'],
- },
- {
- check: '!rights:admin',
- label: 'user_card.admin_menu.grant_admin',
- conditions: ['property:local', 'state:activated'],
- },
- {
- check: 'rights:admin',
- label: 'user_card.admin_menu.revoke_admin',
- conditions: ['property:local', 'state:activated'],
- },
- {
- separator: true,
- },
- {
- check: FORCE_NSFW,
- label: 'user_card.admin_menu.force_nsfw',
- },
- {
- check: STRIP_MEDIA,
- label: 'user_card.admin_menu.strip_media',
- },
- {
- check: FORCE_UNLISTED,
- label: 'user_card.admin_menu.force_unlisted',
- },
- {
- check: SANDBOX,
- label: 'user_card.admin_menu.sandbox',
- },
- {
- check: DISABLE_ANY_SUBSCRIPTION,
- conditions: ['property:local'],
- label: 'user_card.admin_menu.disable_any_subscription',
- },
- {
- check: DISABLE_REMOTE_SUBSCRIPTION,
- conditions: ['property:local'],
- label: 'user_card.admin_menu.disable_remote_subscription',
- },
- {
- check: QUARANTINE,
- conditions: ['property:local'],
- label: 'user_card.admin_menu.quarantine',
- },
- {
- separator: true,
- },
- {
- check: 'action:delete',
- label: 'user_card.admin_menu.delete_account',
- },
-]
const ModerationTools = {
- props: {
- users: {
- type: Array,
- required: true,
- },
- },
- created() {
- if (this.users.length !== 1) return
- useAdminSettingsStore().getUserData({ user: this.users[0] })
- },
- data() {
+ props: [
+ 'user'
+ ],
+ data () {
return {
- open: false,
- confirmDialogShow: false,
- confirmDialogTitle: null,
- confirmDialogContent: null,
- confirmDialogConfirm: null,
- confirmDialogAction: null,
- confirmDialogGroup: null,
- confirmDialogName: null,
+ tags: {
+ FORCE_NSFW,
+ STRIP_MEDIA,
+ FORCE_UNLISTED,
+ DISABLE_REMOTE_SUBSCRIPTION,
+ DISABLE_ANY_SUBSCRIPTION,
+ SANDBOX,
+ QUARANTINE
+ },
+ showDeleteUserDialog: false,
+ toggled: false
}
},
components: {
- ConfirmModal,
- Popover,
+ DialogModal,
+ Popover
},
computed: {
- ready() {
- return this.users.every((u) => u.adminData)
+ tagsSet () {
+ return new Set(this.user.tags)
},
- entries() {
- return ENTRIES.map(({ check, label, separator, conditions }) => {
- if (separator) return 'separator'
- const [, negateToken, group, name] =
- /^([!~]?)([a-z-_]+):([a-z-_]+)$/.exec(check)
-
- const hasTag = this.tagsSet.has(`${group}:${name}`)
- const noTag = this.tagsSet.has(`!${group}:${name}`)
- const maybeTag = this.tagsSet.has(`~${group}:${name}`)
-
- // We are checking for condition to show element, i.e. only show "activate" if user is "deactivated"
- const checkNegated = negateToken === '!' || negateToken === '~'
-
- // Naturally, new value should also be the same
- const value = checkNegated
-
- const action = (() => {
- switch (group) {
- case 'rights':
- return () => this.setRight(name, value)
- case 'state':
- return () => this.setStatus(name, value)
- case 'mrf_tag':
- return () => this.setTag(`${group}:${name}`, noTag)
- case 'action': {
- switch (name) {
- case 'delete':
- return () => this.deleteUsers()
- case 'resend_confirmation':
- return () => this.resendConfirmationEmail()
- case 'disable_mfa':
- return () => this.disableMFA()
- case 'statuses':
- return () =>
- this.$router.push(`/users/\$${this.users[0].id}/admin_view`)
- case 'require_password_change':
- return () => this.requirePasswordChange()
- default:
- throw new Error(`Unknown action group: ${name}`)
- }
- }
- default:
- throw new Error(`Unknown moderation group: ${group}`)
- }
- })()
-
- let checkboxClass = ''
- if (maybeTag) {
- checkboxClass = 'menu-checkbox-indeterminate'
- } else if (hasTag) {
- checkboxClass = 'menu-checkbox-checked'
- }
-
- return {
- check,
- negateToken,
- checkbox: group === 'mrf_tag',
- checkboxClass,
- conditions,
- group,
- name,
- action,
- label,
- value,
- }
- })
- .filter((entry) => {
- if (entry === 'separator') return true
- const { group, name, value, conditions } = entry
-
- if (conditions) {
- // Checking that all items match positive criteria
- const positive = conditions.every((condition) =>
- this.totalSet.has(condition),
- )
- // Checking that there are no items that don't match criteria
- const negative = conditions.some((condition) =>
- this.totalSet.has('!' + condition),
- )
- if (!(positive && !negative)) return false
- }
-
- switch (group) {
- case 'action':
- if (name === 'statuses') return this.privileged('users_read')
- else return true
- case 'rights':
- return this.canGrantRole(name, value)
- case 'state':
- return this.canChangeState(name, value)
- case 'mrf_tag':
- return this.canUseTagPolicy
- default: {
- throw new Error(`Unknown moderation group: ${group}`)
- }
- }
- })
- .reduce((acc, entry, index) => {
- // Removing any double separators as well
- // as separators at very end and bery beginning
- if (entry === 'separator') {
- if (
- acc.length === 0 ||
- last(acc) === 'separator' ||
- index === ENTRIES.length - 1
- ) {
- return acc
- }
- }
- return [...acc, entry]
- }, [])
+ canGrantRole () {
+ return this.user.is_local && !this.user.deactivated && this.$store.state.users.currentUser.role === 'admin'
},
- rightsSet() {
- return this.users.reduce((acc, user) => {
- if (user.rights.admin) {
- acc.add('rights:admin')
- } else {
- acc.add('!rights:admin')
- }
- if (user.rights.moderator) {
- acc.add('rights:moderator')
- } else {
- acc.add('!rights:moderator')
- }
- return acc
- }, new Set())
+ canChangeActivationState () {
+ return this.privileged('users_manage_activation_state')
},
- stateSet() {
- return this.users.reduce((acc, user) => {
- if (!user.deactivated) {
- acc.add('state:activated')
- } else {
- acc.add('!state:activated')
- }
- if (user.adminData?.is_confirmed) {
- acc.add('state:confirmed')
- } else {
- acc.add('!state:confirmed')
- }
- if (user.adminData?.is_approved) {
- acc.add('state:approved')
- } else {
- acc.add('!state:approved')
- }
- if (user.adminData?.is_suggested) {
- acc.add('state:suggested')
- } else {
- acc.add('!state:suggested')
- }
- return acc
- }, new Set())
- },
- tagsSet() {
- const present = new Set()
- const missing = new Set()
-
- this.users.forEach((user) => {
- TAGS.forEach((tag) => {
- if (user.tags.has(tag)) {
- present.add(tag)
- } else {
- missing.add(tag)
- }
- })
- })
-
- const result = new Set()
-
- // Each tag can have three states for given group of users
- TAGS.forEach((tag) => {
- if (present.has(tag) && missing.has(tag)) {
- // Some users have tag, some don't: "~tag"
- result.add(`~${tag}`)
- } else if (missing.has(tag)) {
- // No users have tag: "!tag"
- result.add(`!${tag}`)
- } else {
- // All users have tag: "tag"
- result.add(tag)
- }
- })
-
- return result
- },
- propertySet() {
- return this.users.reduce((acc, user) => {
- if (user.is_local) {
- acc.add('property:local')
- } else {
- acc.add('!property:local')
- }
- return acc
- }, new Set())
- },
- disabled() {
- return !this.ready || this.users.length === 0
- },
- totalSet() {
- return new Set([
- ...this.rightsSet,
- ...this.stateSet,
- ...this.tagsSet,
- ...this.propertySet,
- `count:${this.users.length}`,
- ])
- },
- canDeleteAccount() {
+ canDeleteAccount () {
return this.privileged('users_delete')
},
- canUseTagPolicy() {
- return (
- useInstanceCapabilitiesStore().tagPolicyAvailable &&
- this.privileged('users_manage_tags')
- )
- },
- isAdmin() {
- this.$store.state.users.currentUser.role === 'admin'
- },
+ canUseTagPolicy () {
+ return this.$store.state.instance.tagPolicyAvailable && this.privileged('users_manage_tags')
+ }
},
methods: {
- canGrantRole(name, value) {
- const setEntry = `${value ? '!' : ''}rights:${name}`
-
- return this.isAdmin && this.totalSet.has(setEntry)
+ hasTag (tagName) {
+ return this.tagsSet.has(tagName)
},
- canChangeState(name, value) {
- const setEntry = `${value ? '!' : ''}state:${name}`
- const privilege = (() => {
- switch (name) {
- case 'activated':
- return 'users_manage_activation_state'
- case 'approved':
- return 'users_manage_invites'
- case 'confirmed':
- return 'users_manage_credentials'
- default:
- return null
- }
- })()
-
- return this.privileged(privilege) && this.totalSet.has(setEntry)
+ privileged (privilege) {
+ return this.$store.state.users.currentUser.privileges.includes(privilege)
},
- doConfirmDialogAction() {
- if (typeof this.confirmDialogAction !== 'function') {
- console.error('Confirm Dialog action is not a function!!')
+ toggleTag (tag) {
+ const store = this.$store
+ if (this.tagsSet.has(tag)) {
+ store.state.api.backendInteractor.untagUser({ user: this.user, tag }).then(response => {
+ if (!response.ok) { return }
+ store.commit('untagUser', { user: this.user, tag })
+ })
+ } else {
+ store.state.api.backendInteractor.tagUser({ user: this.user, tag }).then(response => {
+ if (!response.ok) { return }
+ store.commit('tagUser', { user: this.user, tag })
+ })
}
-
- this.confirmDialogAction()
- this.clearConfirmDialog()
},
- clearConfirmDialog() {
- this.confirmDialogShow = false
- this.confirmDialogTitle = null
- this.confirmDialogContent = null
- this.confirmDialogContent2 = null
- this.confirmDialogDanger = false
- this.confirmDialogConfirm = null
- this.confirmDialogAction = null
- this.confirmDialogGroup = null
- this.confirmDialogName = null
+ toggleRight (right) {
+ const store = this.$store
+ if (this.user.rights[right]) {
+ store.state.api.backendInteractor.deleteRight({ user: this.user, right }).then(response => {
+ if (!response.ok) { return }
+ store.commit('updateRight', { user: this.user, right, value: false })
+ })
+ } else {
+ store.state.api.backendInteractor.addRight({ user: this.user, right }).then(response => {
+ if (!response.ok) { return }
+ store.commit('updateRight', { user: this.user, right, value: true })
+ })
+ }
},
- privileged(privilege) {
- if (this.isAdmin) return true
- return this.$store.state.users.currentUser.privileges.has(privilege)
+ toggleActivationStatus () {
+ this.$store.dispatch('toggleActivationStatus', { user: this.user })
},
- setTag(tag, value) {
- useAdminSettingsStore().setUsersTags({
- users: this.users,
- value,
- tags: [tag],
- })
+ deleteUserDialog (show) {
+ this.showDeleteUserDialog = show
},
- setRight(right, value) {
- useAdminSettingsStore().setUsersRight({ users: this.users, value, right })
- },
- setStatus(name, value) {
- const noun = (() => {
- switch (name) {
- case 'activated':
- return 'Activation'
- case 'confirmed':
- return 'Confirmation'
- case 'approved':
- return 'Approval'
- case 'suggested':
- return 'Suggestion'
- }
- })()
-
- useAdminSettingsStore()[`setUsers${noun}Status`]({
- users: this.users,
- value,
- })
- },
- resendConfirmationEmail() {
- useAdminSettingsStore().resendConfirmationEmail({ users: this.users })
- },
- requirePasswordChange() {
- useAdminSettingsStore().requirePasswordChange({ users: this.users })
- },
- disableMFA() {
- this.users.forEach((user) => {
- useAdminSettingsStore().disableMFA({ user })
- })
- },
- deleteUsers() {
- const { id, name } = this.users[0]
-
- useAdminSettingsStore()
- .deleteUsers({ users: this.users })
- .then((userIds) => {
- if (userIds.length > 1) return
-
- const isProfile =
- this.$route.name === 'external-user-profile' ||
- this.$route.name === 'user-profile'
- const isTargetUser =
- this.$route.params.name === name || this.$route.params.id === id
-
+ deleteUser () {
+ const store = this.$store
+ const user = this.user
+ const { id, name } = user
+ store.state.api.backendInteractor.deleteUser({ user })
+ .then(() => {
+ this.$store.dispatch('markStatusesAsDeleted', status => user.id === status.user.id)
+ const isProfile = this.$route.name === 'external-user-profile' || this.$route.name === 'user-profile'
+ const isTargetUser = this.$route.params.name === name || this.$route.params.id === id
if (isProfile && isTargetUser) {
window.history.back()
}
})
},
- setOpen(value) {
- this.open = value
- },
- maybeShowConfirm(close, { group, name, action, value }) {
- close()
- this.confirmDialogName = name
- this.confirmDialogGroup = group
- this.confirmDialogAction = () => action()
-
- switch (group) {
- case 'action': {
- switch (name) {
- case 'delete': {
- this.confirmDialogShow = true
- this.confirmDialogTitle = this.$t(
- 'user_card.admin_menu.confirm_modal.delete_title',
- )
- this.confirmDialogDanger = true
- this.confirmDialogContent =
- 'user_card.admin_menu.confirm_modal.delete_content'
- this.confirmDialogContent2 =
- 'user_card.admin_menu.confirm_modal.delete_content_2'
- this.confirmDialogConfirm = this.$t(
- 'user_card.admin_menu.confirm_modal.delete',
- )
- break
- }
- case 'resend_confirmation': {
- this.confirmDialogShow = true
- this.confirmDialogTitle = this.$t(
- 'user_card.admin_menu.confirm_modal.resend_confirmation_title',
- )
- this.confirmDialogContent =
- 'user_card.admin_menu.confirm_modal.resend_confirmation_content'
- this.confirmDialogConfirm = this.$t(
- 'user_card.admin_menu.confirm_modal.send',
- )
- break
- }
- case 'disable_mfa': {
- this.confirmDialogShow = true
- this.confirmDialogTitle = this.$t(
- 'user_card.admin_menu.confirm_modal.disable_mfa_title',
- )
- this.confirmDialogContent =
- 'user_card.admin_menu.confirm_modal.disable_mfa_content'
- this.confirmDialogConfirm = this.$t('settings.confirm')
- break
- }
- case 'require_password_change': {
- this.confirmDialogShow = true
- this.confirmDialogTitle = this.$t(
- 'user_card.admin_menu.confirm_modal.require_password_change_title',
- )
- this.confirmDialogContent =
- 'user_card.admin_menu.confirm_modal.require_password_change_content'
- this.confirmDialogConfirm = this.$t('settings.confirm')
- break
- }
- }
- break
- }
- case 'state': {
- switch (name) {
- case 'activated': {
- this.confirmDialogShow = true
-
- this.confirmDialogTitle = this.$t(
- 'user_card.admin_menu.confirm_modal.activate_title',
- )
- this.confirmDialogContent = value
- ? 'user_card.admin_menu.confirm_modal.activate_content'
- : 'user_card.admin_menu.confirm_modal.deactivate_content'
- this.confirmDialogConfirm = value
- ? this.$t('user_card.admin_menu.confirm_modal.activate')
- : this.$t('user_card.admin_menu.confirm_modal.deactivate')
- break
- }
- // Confirmation and Approval statuses cannot be revokedn(no API)
- case 'confirmed': {
- this.confirmDialogTitle = this.$t(
- 'user_card.admin_menu.confirm_modal.confirm_title',
- )
- this.confirmDialogContent = //value
- /*?*/ 'user_card.admin_menu.confirm_modal.confirm_content'
- //: 'user_card.admin_menu.confirm_modal.confirm_revoke_content'
- this.confirmDialogConfirm = value
- /*?*/ this.$t('user_card.admin_menu.confirm_modal.confirm')
- //: this.$t('user_card.admin_menu.confirm_modal.revoke')
- break
- }
- case 'approved': {
- this.confirmDialogTitle = this.$t(
- 'user_card.admin_menu.confirm_modal.approval_title',
- )
- this.confirmDialogContent = //value
- /*?*/ 'user_card.admin_menu.confirm_modal.approval_content'
- //: 'user_card.admin_menu.confirm_modal.approval_revoke_content'
- this.confirmDialogConfirm = value
- /*?*/ this.$t('user_card.admin_menu.confirm_modal.approve')
- //: this.$t('user_card.admin_menu.confirm_modal.revoke')
- break
- }
- case 'suggested': {
- this.confirmDialogTitle = this.$t(
- 'user_card.admin_menu.confirm_modal.suggest_title',
- )
- this.confirmDialogContent = value
- ? 'user_card.admin_menu.confirm_modal.add_suggest_content'
- : 'user_card.admin_menu.confirm_modal.remove_suggest_content'
- this.confirmDialogConfirm = value
- ? this.$t('user_card.admin_menu.confirm_modal.add')
- : this.$t('user_card.admin_menu.confirm_modal.remove')
- break
- }
- }
- break
- }
- case 'rights': {
- this.confirmDialogTitle = this.$t(
- 'user_card.admin_menu.confirm_modal.user_rights_title',
- )
- this.confirmDialogContent = value
- ? 'user_card.admin_menu.confirm_modal.grant_role_content'
- : 'user_card.admin_menu.confirm_modal.revoke_role_content'
- this.confirmDialogConfirm = value
- ? this.$t('user_card.admin_menu.confirm_modal.grant')
- : this.$t('user_card.admin_menu.confirm_modal.revoke')
- break
- }
- case 'mrf_tag': {
- this.confirmDialogTitle = this.$t(
- 'user_card.admin_menu.user_tag_title',
- )
- this.confirmDialogContent = value
- ? 'user_card.admin_menu.confirm_modal.assign_tag_content'
- : 'user_card.admin_menu.confirm_modal.unassign_tag_content'
- this.confirmDialogConfirm = value
- ? this.$t('user_card.admin_menu.confirm_modal.assign')
- : this.$t('user_card.admin_menu.confirm_modal.unassign')
- break
- }
- }
-
- if (this.users.length > 1) {
- this.confirmDialogShow = true
- }
-
- if (!this.confirmDialogShow) {
- this.doConfirmDialogAction()
- }
- },
- },
+ setToggled (value) {
+ this.toggled = value
+ }
+ }
}
export default ModerationTools
diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue
index 4da0be0ee..9c8894bff 100644
--- a/src/components/moderation_tools/moderation_tools.vue
+++ b/src/components/moderation_tools/moderation_tools.vue
@@ -3,37 +3,154 @@
-
+
@@ -123,8 +208,6 @@
}
.moderation-tools-button {
- white-space: nowrap;
-
svg,
i {
font-size: 0.8em;
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.js b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
index b2048984d..13cfb52ee 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.js
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
@@ -1,7 +1,5 @@
+import { mapState } from 'vuex'
import { get } from 'lodash'
-import { mapState } from 'pinia'
-
-import { useInstanceStore } from 'src/stores/instance.js'
/**
* This is for backwards compatibility. We originally didn't recieve
@@ -10,7 +8,7 @@ import { useInstanceStore } from 'src/stores/instance.js'
* to add an extra "info" key.
*/
const toInstanceReasonObject = (instances, info, key) => {
- return instances.map((instance) => {
+ return instances.map(instance => {
if (info[key] && info[key][instance] && info[key][instance].reason) {
return { instance, reason: info[key][instance].reason }
}
@@ -20,78 +18,57 @@ const toInstanceReasonObject = (instances, info, key) => {
const MRFTransparencyPanel = {
computed: {
- ...mapState(useInstanceStore, {
- federationPolicy: (state) => state.federationPolicy,
- mrfPolicies: (state) => get(state, 'federationPolicy.mrf_policies', []),
- quarantineInstances: (state) =>
- toInstanceReasonObject(
- get(state, 'federationPolicy.quarantined_instances', []),
- get(state, 'federationPolicy.quarantined_instances_info', []),
- 'quarantined_instances',
- ),
- acceptInstances: (state) =>
- toInstanceReasonObject(
- get(state, 'federationPolicy.mrf_simple.accept', []),
- get(state, 'federationPolicy.mrf_simple_info', []),
- 'accept',
- ),
- rejectInstances: (state) =>
- toInstanceReasonObject(
- get(state, 'federationPolicy.mrf_simple.reject', []),
- get(state, 'federationPolicy.mrf_simple_info', []),
- 'reject',
- ),
- ftlRemovalInstances: (state) =>
- toInstanceReasonObject(
- get(
- state,
- 'federationPolicy.mrf_simple.federated_timeline_removal',
- [],
- ),
- get(state, 'federationPolicy.mrf_simple_info', []),
- 'federated_timeline_removal',
- ),
- mediaNsfwInstances: (state) =>
- toInstanceReasonObject(
- get(state, 'federationPolicy.mrf_simple.media_nsfw', []),
- get(state, 'federationPolicy.mrf_simple_info', []),
- 'media_nsfw',
- ),
- mediaRemovalInstances: (state) =>
- toInstanceReasonObject(
- get(state, 'federationPolicy.mrf_simple.media_removal', []),
- get(state, 'federationPolicy.mrf_simple_info', []),
- 'media_removal',
- ),
- keywordsFtlRemoval: (state) =>
- get(
- state,
- 'federationPolicy.mrf_keyword.federated_timeline_removal',
- [],
- ),
- keywordsReject: (state) =>
- get(state, 'federationPolicy.mrf_keyword.reject', []),
- keywordsReplace: (state) =>
- get(state, 'federationPolicy.mrf_keyword.replace', []),
+ ...mapState({
+ federationPolicy: state => get(state, 'instance.federationPolicy'),
+ mrfPolicies: state => get(state, 'instance.federationPolicy.mrf_policies', []),
+ quarantineInstances: state => toInstanceReasonObject(
+ get(state, 'instance.federationPolicy.quarantined_instances', []),
+ get(state, 'instance.federationPolicy.quarantined_instances_info', []),
+ 'quarantined_instances'
+ ),
+ acceptInstances: state => toInstanceReasonObject(
+ get(state, 'instance.federationPolicy.mrf_simple.accept', []),
+ get(state, 'instance.federationPolicy.mrf_simple_info', []),
+ 'accept'
+ ),
+ rejectInstances: state => toInstanceReasonObject(
+ get(state, 'instance.federationPolicy.mrf_simple.reject', []),
+ get(state, 'instance.federationPolicy.mrf_simple_info', []),
+ 'reject'
+ ),
+ ftlRemovalInstances: state => toInstanceReasonObject(
+ get(state, 'instance.federationPolicy.mrf_simple.federated_timeline_removal', []),
+ get(state, 'instance.federationPolicy.mrf_simple_info', []),
+ 'federated_timeline_removal'
+ ),
+ mediaNsfwInstances: state => toInstanceReasonObject(
+ get(state, 'instance.federationPolicy.mrf_simple.media_nsfw', []),
+ get(state, 'instance.federationPolicy.mrf_simple_info', []),
+ 'media_nsfw'
+ ),
+ mediaRemovalInstances: state => toInstanceReasonObject(
+ get(state, 'instance.federationPolicy.mrf_simple.media_removal', []),
+ get(state, 'instance.federationPolicy.mrf_simple_info', []),
+ 'media_removal'
+ ),
+ keywordsFtlRemoval: state => get(state, 'instance.federationPolicy.mrf_keyword.federated_timeline_removal', []),
+ keywordsReject: state => get(state, 'instance.federationPolicy.mrf_keyword.reject', []),
+ keywordsReplace: state => get(state, 'instance.federationPolicy.mrf_keyword.replace', [])
}),
- hasInstanceSpecificPolicies() {
- return (
- this.quarantineInstances.length ||
+ hasInstanceSpecificPolicies () {
+ return this.quarantineInstances.length ||
this.acceptInstances.length ||
this.rejectInstances.length ||
this.ftlRemovalInstances.length ||
this.mediaNsfwInstances.length ||
this.mediaRemovalInstances.length
- )
},
- hasKeywordPolicies() {
- return (
- this.keywordsFtlRemoval.length ||
+ hasKeywordPolicies () {
+ return this.keywordsFtlRemoval.length ||
this.keywordsReject.length ||
this.keywordsReplace.length
- )
- },
- },
+ }
+ }
}
export default MRFTransparencyPanel
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.scss b/src/components/mrf_transparency_panel/mrf_transparency_panel.scss
index 8e2fa553a..a262ed1c5 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.scss
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.scss
@@ -4,13 +4,13 @@
table {
width: 100%;
text-align: left;
- padding-left: 0.5em;
- padding-bottom: 1.1em;
+ padding-left: 10px;
+ padding-bottom: 20px;
th,
td {
- width: 11em;
- max-width: 25em;
+ width: 180px;
+ max-width: 360px;
overflow: hidden;
vertical-align: text-top;
}
diff --git a/src/components/mute_card/mute_card.js b/src/components/mute_card/mute_card.js
index 0d247930a..cbec0e9bb 100644
--- a/src/components/mute_card/mute_card.js
+++ b/src/components/mute_card/mute_card.js
@@ -1,41 +1,40 @@
-import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
-import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue'
+import BasicUserCard from '../basic_user_card/basic_user_card.vue'
const MuteCard = {
props: ['userId'],
+ data () {
+ return {
+ progress: false
+ }
+ },
computed: {
- user() {
+ user () {
return this.$store.getters.findUser(this.userId)
},
- relationship() {
+ relationship () {
return this.$store.getters.relationship(this.userId)
},
- muted() {
+ muted () {
return this.relationship.muting
- },
- muteExpiryAvailable() {
- return Object.hasOwn(this.user, 'mute_expires_at')
- },
- muteExpiry() {
- return this.user.mute_expires_at === false
- ? this.$t('user_card.mute_expires_forever')
- : this.$t('user_card.mute_expires_at', [
- new Date(this.user.mute_expires_at).toLocaleString(),
- ])
- },
+ }
},
components: {
- BasicUserCard,
- UserTimedFilterModal,
+ BasicUserCard
},
methods: {
- unmuteUser() {
- this.$store.dispatch('unmuteUser', this.userId)
+ unmuteUser () {
+ this.progress = true
+ this.$store.dispatch('unmuteUser', this.userId).then(() => {
+ this.progress = false
+ })
},
- muteUser() {
- this.$refs.timedMuteDialog.optionallyPrompt()
- },
- },
+ muteUser () {
+ this.progress = true
+ this.$store.dispatch('muteUser', this.userId).then(() => {
+ this.progress = false
+ })
+ }
+ }
}
export default MuteCard
diff --git a/src/components/mute_card/mute_card.vue b/src/components/mute_card/mute_card.vue
index a9418cffa..97e91cbca 100644
--- a/src/components/mute_card/mute_card.vue
+++ b/src/components/mute_card/mute_card.vue
@@ -1,36 +1,34 @@
-
+
-
- {{ muteExpiry }}
-
- {{ ' ' }}
- {{ $t('user_card.unmute') }}
+
+ {{ $t('user_card.unmute_progress') }}
+
+
+ {{ $t('user_card.unmute') }}
+
- {{ $t('user_card.mute') }}
+
+ {{ $t('user_card.mute_progress') }}
+
+
+ {{ $t('user_card.mute') }}
+
-
-
-
-
+
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index ae6264217..a155abe0c 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -1,35 +1,32 @@
-import { mapState as mapPiniaState } from 'pinia'
-import { mapGetters, mapState } from 'vuex'
-
import BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue'
-import Checkbox from 'src/components/checkbox/checkbox.vue'
import ListsMenuContent from 'src/components/lists_menu/lists_menu_content.vue'
+import { mapState, mapGetters } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
+import { TIMELINES, ROOT_ITEMS } from 'src/components/navigation/navigation.js'
import { filterNavigation } from 'src/components/navigation/filter.js'
-import { ROOT_ITEMS, TIMELINES } from 'src/components/navigation/navigation.js'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import NavigationPins from 'src/components/navigation/navigation_pins.vue'
+import Checkbox from 'src/components/checkbox/checkbox.vue'
import { useAnnouncementsStore } from 'src/stores/announcements'
-import { useInstanceStore } from 'src/stores/instance.js'
-import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
-import { useSyncConfigStore } from 'src/stores/sync_config.js'
+import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
- faBell,
+ faUsers,
+ faGlobe,
+ faCity,
faBookmark,
- faBullhorn,
+ faEnvelope,
faChevronDown,
faChevronUp,
- faCity,
faComments,
- faEnvelope,
- faFilePen,
- faGlobe,
+ faBell,
faInfoCircle,
- faList,
faStream,
- faUsers,
+ faList,
+ faBullhorn,
+ faFilePen
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -46,94 +43,80 @@ library.add(
faStream,
faList,
faBullhorn,
- faFilePen,
+ faFilePen
)
const NavPanel = {
props: ['forceExpand', 'forceEditMode'],
+ created () {
+ },
components: {
BookmarkFoldersMenuContent,
ListsMenuContent,
NavigationEntry,
NavigationPins,
- Checkbox,
+ Checkbox
},
- data() {
+ data () {
return {
editMode: false,
showTimelines: false,
showLists: false,
showBookmarkFolders: false,
- timelinesList: Object.entries(TIMELINES).map(([k, v]) => ({
- ...v,
- name: k,
- })),
- rootList: Object.entries(ROOT_ITEMS).map(([k, v]) => ({ ...v, name: k })),
+ timelinesList: Object.entries(TIMELINES).map(([k, v]) => ({ ...v, name: k })),
+ rootList: Object.entries(ROOT_ITEMS).map(([k, v]) => ({ ...v, name: k }))
}
},
methods: {
- toggleTimelines() {
+ toggleTimelines () {
this.showTimelines = !this.showTimelines
},
- toggleLists() {
+ toggleLists () {
this.showLists = !this.showLists
},
- toggleBookmarkFolders() {
+ toggleBookmarkFolders () {
this.showBookmarkFolders = !this.showBookmarkFolders
},
- toggleEditMode() {
+ toggleEditMode () {
this.editMode = !this.editMode
},
- toggleCollapse() {
- useSyncConfigStore().setSimplePrefAndSave({
- path: 'collapseNav',
- value: !this.collapsed,
- })
- useSyncConfigStore().pushSyncConfig()
+ toggleCollapse () {
+ useServerSideStorageStore().setPreference({ path: 'simple.collapseNav', value: !this.collapsed })
+ useServerSideStorageStore().pushServerSideStorage()
},
- isPinned(item) {
+ isPinned (item) {
return this.pinnedItems.has(item)
},
- togglePin(item) {
+ togglePin (item) {
if (this.isPinned(item)) {
- useSyncConfigStore().removeCollectionPreference({
- path: 'collections.pinnedNavItems',
- value: item,
- })
+ useServerSideStorageStore().removeCollectionPreference({ path: 'collections.pinnedNavItems', value: item })
} else {
- useSyncConfigStore().addCollectionPreference({
- path: 'collections.pinnedNavItems',
- value: item,
- })
+ useServerSideStorageStore().addCollectionPreference({ path: 'collections.pinnedNavItems', value: item })
}
- useSyncConfigStore().pushSyncConfig()
- },
+ useServerSideStorageStore().pushServerSideStorage()
+ }
},
computed: {
...mapPiniaState(useAnnouncementsStore, {
unreadAnnouncementCount: 'unreadAnnouncementCount',
- supportsAnnouncements: (store) => store.supportsAnnouncements,
+ supportsAnnouncements: store => store.supportsAnnouncements
}),
- ...mapPiniaState(useInstanceCapabilitiesStore, [
- 'pleromaChatMessagesAvailable',
- 'pleromaBookmarkFoldersAvailable',
- 'localBubble',
- ]),
- ...mapPiniaState(useInstanceStore, ['federating']),
- ...mapPiniaState(useInstanceStore, {
- privateMode: (store) => store.private,
- }),
- ...mapPiniaState(useSyncConfigStore, {
- collapsed: (store) => store.prefsStorage.simple.collapseNav,
- pinnedItems: (store) =>
- new Set(store.prefsStorage.collections.pinnedNavItems),
+ ...mapPiniaState(useServerSideStorageStore, {
+ collapsed: store => store.prefsStorage.simple.collapseNav,
+ pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems)
}),
...mapState({
- currentUser: (state) => state.users.currentUser,
- followRequestCount: (state) => state.api.followRequests.length,
+ currentUser: state => state.users.currentUser,
+ followRequestCount: state => state.api.followRequests.length,
+ privateMode: state => state.instance.private,
+ federating: state => state.instance.federating,
+ pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
+ bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable,
+ bubbleTimeline: state => state.instance.localBubbleInstances.length > 0
}),
- timelinesItems() {
+ timelinesItems () {
return filterNavigation(
- Object.entries({ ...TIMELINES })
+ Object
+ .entries({ ...TIMELINES })
// do not show in timeliens list since it's in a better place now
.filter(([key]) => key !== 'bookmarks')
.map(([k, v]) => ({ ...v, name: k })),
@@ -143,27 +126,29 @@ const NavPanel = {
isFederating: this.federating,
isPrivate: this.privateMode,
currentUser: this.currentUser,
- supportsBubbleTimeline: this.localBubble,
- supportsBookmarkFolders: this.pleromaBookmarkFoldersAvailable,
- },
+ supportsBubbleTimeline: this.bubbleTimeline,
+ supportsBookmarkFolders: this.bookmarkFolders
+ }
)
},
- rootItems() {
+ rootItems () {
return filterNavigation(
- Object.entries({ ...ROOT_ITEMS }).map(([k, v]) => ({ ...v, name: k })),
+ Object
+ .entries({ ...ROOT_ITEMS })
+ .map(([k, v]) => ({ ...v, name: k })),
{
hasChats: this.pleromaChatMessagesAvailable,
hasAnnouncements: this.supportsAnnouncements,
isFederating: this.federating,
isPrivate: this.privateMode,
currentUser: this.currentUser,
- supportsBubbleTimeline: this.localBubble,
- supportsBookmarkFolders: this.pleromaBookmarkFoldersAvailable,
- },
+ supportsBubbleTimeline: this.bubbleTimeline,
+ supportsBookmarkFolders: this.bookmarkFolders
+ }
)
},
- ...mapGetters(['unreadChatCount']),
- },
+ ...mapGetters(['unreadChatCount'])
+ }
}
export default NavPanel
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index ae5c6eae4..10903bcae 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -84,7 +84,7 @@
/>
{
+export const filterNavigation = (list = [], {
+ hasChats,
+ hasAnnouncements,
+ isFederating,
+ isPrivate,
+ currentUser,
+ supportsBookmarkFolders,
+ supportsBubbleTimeline
+}) => {
return list.filter(({ criteria, anon, anonRoute }) => {
const set = new Set(criteria || [])
if (!isFederating && set.has('federating')) return false
if (!currentUser && isPrivate && set.has('!private')) return false
if (!currentUser && !(anon || anonRoute)) return false
- if ((!currentUser || !currentUser.locked) && set.has('lockedUser'))
- return false
+ if ((!currentUser || !currentUser.locked) && set.has('lockedUser')) return false
if (!hasChats && set.has('chats')) return false
if (!hasAnnouncements && set.has('announcements')) return false
- if (!supportsBubbleTimeline && set.has('supportsBubbleTimeline'))
- return false
- if (!supportsBookmarkFolders && set.has('supportsBookmarkFolders'))
- return false
- if (supportsBookmarkFolders && set.has('!supportsBookmarkFolders'))
- return false
+ if (!supportsBubbleTimeline && set.has('supportsBubbleTimeline')) return false
+ if (!supportsBookmarkFolders && set.has('supportsBookmarkFolders')) return false
+ if (supportsBookmarkFolders && set.has('!supportsBookmarkFolders')) return false
return true
})
}
-export const getListEntries = (store) =>
- store.allLists.map((list) => ({
- name: 'list-' + list.id,
- routeObject: { name: 'lists-timeline', params: { id: list.id } },
- labelRaw: list.title,
- iconLetter: list.title[0],
- }))
+export const getListEntries = store => store.allLists.map(list => ({
+ name: 'list-' + list.id,
+ routeObject: { name: 'lists-timeline', params: { id: list.id } },
+ labelRaw: list.title,
+ iconLetter: list.title[0]
+}))
-export const getBookmarkFolderEntries = (store) =>
- store.allFolders
- ? store.allFolders.map((folder) => ({
- name: 'bookmark-folder-' + folder.id,
- routeObject: { name: 'bookmark-folder', params: { id: folder.id } },
- labelRaw: folder.name,
- iconEmoji: folder.emoji,
- iconEmojiUrl: folder.emoji_url,
- iconLetter: folder.name[0],
- }))
- : []
+export const getBookmarkFolderEntries = store => store.allFolders ? store.allFolders.map(folder => ({
+ name: 'bookmark-folder-' + folder.id,
+ routeObject: { name: 'bookmark-folder', params: { id: folder.id } },
+ labelRaw: folder.name,
+ iconEmoji: folder.emoji,
+ iconEmojiUrl: folder.emoji_url,
+ iconLetter: folder.name[0]
+})) : []
diff --git a/src/components/navigation/navigation.js b/src/components/navigation/navigation.js
index 66fb0d347..d1c2b6763 100644
--- a/src/components/navigation/navigation.js
+++ b/src/components/navigation/navigation.js
@@ -4,39 +4,42 @@ export const USERNAME_ROUTES = new Set([
'interactions',
'notifications',
'chat',
- 'chats',
+ 'chats'
])
// routes that take :name property
-export const NAME_ROUTES = new Set(['user-profile', 'legacy-user-profile'])
+export const NAME_ROUTES = new Set([
+ 'user-profile',
+ 'legacy-user-profile'
+])
export const TIMELINES = {
home: {
route: 'friends',
icon: 'home',
label: 'nav.home_timeline',
- criteria: ['!private'],
+ criteria: ['!private']
},
public: {
route: 'public-timeline',
anon: true,
icon: 'users',
label: 'nav.public_tl',
- criteria: ['!private'],
+ criteria: ['!private']
},
bubble: {
route: 'bubble',
anon: true,
icon: 'city',
label: 'nav.bubble',
- criteria: ['!private', 'federating', 'supportsBubbleTimeline'],
+ criteria: ['!private', 'federating', 'supportsBubbleTimeline']
},
twkn: {
route: 'public-external-timeline',
anon: true,
icon: 'globe',
label: 'nav.twkn',
- criteria: ['!private', 'federating'],
+ criteria: ['!private', 'federating']
},
// bookmarks are still technically a timeline so we should show it in the dropdown
bookmarks: {
@@ -47,13 +50,13 @@ export const TIMELINES = {
favorites: {
routeObject: { name: 'user-profile', query: { tab: 'favorites' } },
icon: 'star',
- label: 'user_card.favorites',
+ label: 'user_card.favorites'
},
dms: {
route: 'dms',
icon: 'envelope',
- label: 'nav.dms',
- },
+ label: 'nav.dms'
+ }
}
export const ROOT_ITEMS = {
@@ -64,12 +67,12 @@ export const ROOT_ITEMS = {
// shows bookmarks entry in a better suited location
// hides it when bookmark folders are supported since
// we show custom component instead of it
- criteria: ['!supportsBookmarkFolders'],
+ criteria: ['!supportsBookmarkFolders']
},
interactions: {
route: 'interactions',
icon: 'bell',
- label: 'nav.interactions',
+ label: 'nav.interactions'
},
chats: {
route: 'chats',
@@ -77,7 +80,7 @@ export const ROOT_ITEMS = {
label: 'nav.chats',
badgeStyle: 'notification',
badgeGetter: 'unreadChatCount',
- criteria: ['chats'],
+ criteria: ['chats']
},
friendRequests: {
route: 'friend-requests',
@@ -85,13 +88,13 @@ export const ROOT_ITEMS = {
label: 'nav.friend_requests',
badgeStyle: 'notification',
criteria: ['lockedUser'],
- badgeGetter: 'followRequestCount',
+ badgeGetter: 'followRequestCount'
},
about: {
route: 'about',
anon: true,
icon: 'info-circle',
- label: 'nav.about',
+ label: 'nav.about'
},
announcements: {
route: 'announcements',
@@ -100,18 +103,18 @@ export const ROOT_ITEMS = {
store: 'announcements',
badgeStyle: 'notification',
badgeGetter: 'unreadAnnouncementCount',
- criteria: ['announcements'],
+ criteria: ['announcements']
},
drafts: {
route: 'drafts',
icon: 'file-pen',
label: 'nav.drafts',
badgeStyle: 'neutral',
- badgeGetter: 'draftCount',
- },
+ badgeGetter: 'draftCount'
+ }
}
-export function routeTo(item, currentUser) {
+export function routeTo (item, currentUser) {
if (!item.route && !item.routeObject) return null
let route
@@ -119,7 +122,7 @@ export function routeTo(item, currentUser) {
if (item.routeObject) {
route = item.routeObject
} else {
- route = { name: item.anon || currentUser ? item.route : item.anonRoute }
+ route = { name: (item.anon || currentUser) ? item.route : item.anonRoute }
}
if (USERNAME_ROUTES.has(route.name)) {
diff --git a/src/components/navigation/navigation_entry.js b/src/components/navigation/navigation_entry.js
index 7a43000ce..11db1c9e3 100644
--- a/src/components/navigation/navigation_entry.js
+++ b/src/components/navigation/navigation_entry.js
@@ -1,57 +1,48 @@
-import { mapState as mapPiniaState, mapStores } from 'pinia'
import { mapState } from 'vuex'
-
import { routeTo } from 'src/components/navigation/navigation.js'
import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue'
-
-import { useAnnouncementsStore } from 'src/stores/announcements.js'
-import { useSyncConfigStore } from 'src/stores/sync_config.js'
-
import { library } from '@fortawesome/fontawesome-svg-core'
import { faThumbtack } from '@fortawesome/free-solid-svg-icons'
+import { mapStores, mapState as mapPiniaState } from 'pinia'
+
+import { useAnnouncementsStore } from 'src/stores/announcements'
+import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
library.add(faThumbtack)
const NavigationEntry = {
props: ['item', 'showPin'],
components: {
- OptionalRouterLink,
+ OptionalRouterLink
},
methods: {
- isPinned(value) {
+ isPinned (value) {
return this.pinnedItems.has(value)
},
- togglePin(value) {
+ togglePin (value) {
if (this.isPinned(value)) {
- useSyncConfigStore().removeCollectionPreference({
- path: 'collections.pinnedNavItems',
- value,
- })
+ useServerSideStorageStore().removeCollectionPreference({ path: 'collections.pinnedNavItems', value })
} else {
- useSyncConfigStore().addCollectionPreference({
- path: 'collections.pinnedNavItems',
- value,
- })
+ useServerSideStorageStore().addCollectionPreference({ path: 'collections.pinnedNavItems', value })
}
- useSyncConfigStore().pushSyncConfig()
- },
+ useServerSideStorageStore().pushServerSideStorage()
+ }
},
computed: {
- routeTo() {
+ routeTo () {
return routeTo(this.item, this.currentUser)
},
- getters() {
+ getters () {
return this.$store.getters
},
...mapStores(useAnnouncementsStore),
...mapState({
- currentUser: (state) => state.users.currentUser,
+ currentUser: state => state.users.currentUser
}),
- ...mapPiniaState(useSyncConfigStore, {
- pinnedItems: (store) =>
- new Set(store.prefsStorage.collections.pinnedNavItems),
+ ...mapPiniaState(useServerSideStorageStore, {
+ pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems)
}),
- },
+ }
}
export default NavigationEntry
diff --git a/src/components/navigation/navigation_pins.js b/src/components/navigation/navigation_pins.js
index c3649e4b3..f9a900fc6 100644
--- a/src/components/navigation/navigation_pins.js
+++ b/src/components/navigation/navigation_pins.js
@@ -1,37 +1,27 @@
-import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
+import { TIMELINES, ROOT_ITEMS, routeTo } from 'src/components/navigation/navigation.js'
+import { getBookmarkFolderEntries, getListEntries, filterNavigation } from 'src/components/navigation/filter.js'
-import {
- filterNavigation,
- getBookmarkFolderEntries,
- getListEntries,
-} from 'src/components/navigation/filter.js'
-import {
- ROOT_ITEMS,
- routeTo,
- TIMELINES,
-} from 'src/components/navigation/navigation.js'
-
-import { useAnnouncementsStore } from 'src/stores/announcements'
-import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders'
-import { useInstanceStore } from 'src/stores/instance.js'
-import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
-import { useListsStore } from 'src/stores/lists'
-import { useSyncConfigStore } from 'src/stores/sync_config.js'
+import StillImage from 'src/components/still-image/still-image.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
- faBell,
- faBookmark,
- faCity,
- faComments,
- faEnvelope,
- faGlobe,
- faInfoCircle,
- faList,
- faStream,
faUsers,
+ faGlobe,
+ faCity,
+ faBookmark,
+ faEnvelope,
+ faComments,
+ faBell,
+ faInfoCircle,
+ faStream,
+ faList
} from '@fortawesome/free-solid-svg-icons'
+import { useListsStore } from 'src/stores/lists'
+import { useAnnouncementsStore } from 'src/stores/announcements'
+import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders'
+import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
library.add(
faUsers,
@@ -43,85 +33,85 @@ library.add(
faBell,
faInfoCircle,
faStream,
- faList,
+ faList
)
const NavPanel = {
props: ['limit'],
methods: {
- getRouteTo(item) {
+ getRouteTo (item) {
return routeTo(item, this.currentUser)
- },
+ }
+ },
+ components: {
+ StillImage
},
- components: {},
computed: {
- getters() {
+ getters () {
return this.$store.getters
},
...mapPiniaState(useListsStore, {
- lists: getListEntries,
+ lists: getListEntries
}),
...mapPiniaState(useAnnouncementsStore, {
- supportsAnnouncements: (store) => store.supportsAnnouncements,
+ supportsAnnouncements: store => store.supportsAnnouncements
}),
...mapPiniaState(useBookmarkFoldersStore, {
bookmarks: getBookmarkFolderEntries,
}),
- ...mapPiniaState(useSyncConfigStore, {
- pinnedItems: (store) =>
- new Set(store.prefsStorage.collections.pinnedNavItems),
+ ...mapPiniaState(useServerSideStorageStore, {
+ pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems)
}),
- ...mapPiniaState(useInstanceStore, ['privateMode', 'federating']),
- ...mapPiniaState(useInstanceCapabilitiesStore, [
- 'pleromaChatMessagesAvailable',
- 'localBubble',
- ]),
...mapState({
- currentUser: (state) => state.users.currentUser,
- followRequestCount: (state) => state.api.followRequests.length,
+ currentUser: state => state.users.currentUser,
+ followRequestCount: state => state.api.followRequests.length,
+ privateMode: state => state.instance.private,
+ federating: state => state.instance.federating,
+ pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
+ bubbleTimeline: state => state.instance.localBubbleInstances.length > 0
}),
- pinnedList() {
+ pinnedList () {
if (!this.currentUser) {
- return filterNavigation(
- [
- { ...TIMELINES.public, name: 'public' },
- { ...TIMELINES.twkn, name: 'twkn' },
- { ...ROOT_ITEMS.about, name: 'about' },
- ],
- {
- hasChats: this.pleromaChatMessagesAvailable,
- hasAnnouncements: this.supportsAnnouncements,
- isFederating: this.federating,
- isPrivate: this.privateMode,
- currentUser: this.currentUser,
- supportsBubbleTimeline: this.localBubble,
- supportsBookmarkFolders: this.bookmarks,
- },
- )
- }
- return filterNavigation(
- [
- ...Object.entries({ ...TIMELINES })
- .filter(([k]) => this.pinnedItems.has(k))
- .map(([k, v]) => ({ ...v, name: k })),
- ...this.lists.filter((k) => this.pinnedItems.has(k.name)),
- ...this.bookmarks.filter((k) => this.pinnedItems.has(k.name)),
- ...Object.entries({ ...ROOT_ITEMS })
- .filter(([k]) => this.pinnedItems.has(k))
- .map(([k, v]) => ({ ...v, name: k })),
+ return filterNavigation([
+ { ...TIMELINES.public, name: 'public' },
+ { ...TIMELINES.twkn, name: 'twkn' },
+ { ...ROOT_ITEMS.about, name: 'about' }
],
{
hasChats: this.pleromaChatMessagesAvailable,
hasAnnouncements: this.supportsAnnouncements,
- supportsBubbleTimeline: this.localBubble,
- supportsBookmarkFolders: this.bookmarks,
isFederating: this.federating,
isPrivate: this.privateMode,
currentUser: this.currentUser,
- },
+ supportsBubbleTimeline: this.bubbleTimeline,
+ supportsBookmarkFolders: this.bookmarks
+ })
+ }
+ return filterNavigation(
+ [
+ ...Object
+ .entries({ ...TIMELINES })
+ .filter(([k]) => this.pinnedItems.has(k))
+ .map(([k, v]) => ({ ...v, name: k })),
+ ...this.lists.filter((k) => this.pinnedItems.has(k.name)),
+ ...this.bookmarks.filter((k) => this.pinnedItems.has(k.name)),
+ ...Object
+ .entries({ ...ROOT_ITEMS })
+ .filter(([k]) => this.pinnedItems.has(k))
+ .map(([k, v]) => ({ ...v, name: k }))
+ ],
+ {
+ hasChats: this.pleromaChatMessagesAvailable,
+ hasAnnouncements: this.supportsAnnouncements,
+ supportsBubbleTimeline: this.bubbleTimeline,
+ supportsBookmarkFolders: this.bookmarks,
+ isFederating: this.federating,
+ isPrivate: this.privateMode,
+ currentUser: this.currentUser
+ }
).slice(0, this.limit)
- },
- },
+ }
+ }
}
export default NavPanel
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index b6ee27f9c..043be1b1a 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -1,38 +1,29 @@
-import { defineAsyncComponent } from 'vue'
+import StatusContent from '../status_content/status_content.vue'
import { mapState } from 'vuex'
-
-import Report from 'src/components/report/report.vue'
-import StatusContent from 'src/components/status_content/status_content.vue'
-import Timeago from 'src/components/timeago/timeago.vue'
-import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
-import UserLink from 'src/components/user_link/user_link.vue'
-import UserPopover from 'src/components/user_popover/user_popover.vue'
+import Status from '../status/status.vue'
+import UserAvatar from '../user_avatar/user_avatar.vue'
+import UserCard from '../user_card/user_card.vue'
+import Timeago from '../timeago/timeago.vue'
+import Report from '../report/report.vue'
+import UserLink from '../user_link/user_link.vue'
+import RichContent from 'src/components/rich_content/rich_content.jsx'
+import UserPopover from '../user_popover/user_popover.vue'
+import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
-import {
- highlightClass,
- highlightStyle,
-} from '../../services/user_highlighter/user_highlighter.js'
-
-import { useInstanceStore } from 'src/stores/instance.js'
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-import { useOAuthStore } from 'src/stores/oauth.js'
-import { useUserHighlightStore } from 'src/stores/user_highlight.js'
-
-import { approveUser, denyUser } from 'src/api/user.js'
+import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
-
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faCheck,
- faCompressAlt,
- faExpandAlt,
- faEyeSlash,
- faRetweet,
- faStar,
- faSuitcaseRolling,
faTimes,
- faUser,
+ faStar,
+ faRetweet,
faUserPlus,
+ faEyeSlash,
+ faUser,
+ faSuitcaseRolling,
+ faExpandAlt,
+ faCompressAlt
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -45,17 +36,16 @@ library.add(
faEyeSlash,
faSuitcaseRolling,
faExpandAlt,
- faCompressAlt,
+ faCompressAlt
)
const Notification = {
- data() {
+ data () {
return {
- selecting: false,
statusExpanded: false,
unmuted: false,
showingApproveConfirmDialog: false,
- showingDenyConfirmDialog: false,
+ showingDenyConfirmDialog: false
}
},
props: ['notification'],
@@ -63,170 +53,120 @@ const Notification = {
components: {
StatusContent,
UserAvatar,
+ UserCard,
Timeago,
-
+ Status,
Report,
-
+ RichContent,
UserPopover,
UserLink,
- ConfirmModal: defineAsyncComponent(
- () => import('src/components/confirm_modal/confirm_modal.vue'),
- ),
- },
- mounted() {
- document.addEventListener('selectionchange', this.onContentSelect)
- },
- unmounted() {
- document.removeEventListener('selectionchange', this.onContentSelect)
+ ConfirmModal
},
methods: {
- toggleStatusExpanded() {
- if (!this.expandable) return
+ toggleStatusExpanded () {
this.statusExpanded = !this.statusExpanded
},
- onContentSelect() {
- const { isCollapsed, anchorNode, offsetNode } = document.getSelection()
- if (isCollapsed) {
- this.selecting = false
- return
- }
- const within =
- this.$refs.root.contains(anchorNode) ||
- this.$refs.root.contains(offsetNode)
- if (within) {
- this.selecting = true
- } else {
- this.selecting = false
- }
+ generateUserProfileLink (user) {
+ return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
},
- onContentClick(e) {
- if (
- !this.selecting &&
- !e.target.closest('a') &&
- !e.target.closest('button')
- ) {
- this.toggleStatusExpanded()
- }
- },
- generateUserProfileLink(user) {
- return generateProfileLink(
- user.id,
- user.screen_name,
- useInstanceStore().restrictedNicknames,
- )
- },
- getUser(notification) {
+ getUser (notification) {
return this.$store.state.users.usersObject[notification.from_profile.id]
},
- interacted() {
+ interacted () {
this.$emit('interacted')
},
- toggleMute() {
+ toggleMute () {
this.unmuted = !this.unmuted
},
- showApproveConfirmDialog() {
+ showApproveConfirmDialog () {
this.showingApproveConfirmDialog = true
},
- hideApproveConfirmDialog() {
+ hideApproveConfirmDialog () {
this.showingApproveConfirmDialog = false
},
- showDenyConfirmDialog() {
+ showDenyConfirmDialog () {
this.showingDenyConfirmDialog = true
},
- hideDenyConfirmDialog() {
+ hideDenyConfirmDialog () {
this.showingDenyConfirmDialog = false
},
- approveUser() {
+ approveUser () {
if (this.shouldConfirmApprove) {
this.showApproveConfirmDialog()
} else {
this.doApprove()
}
},
- doApprove() {
- approveUser({
- id: this.user.id,
- credentials: useOAuthStore().token,
- })
+ doApprove () {
+ this.$emit('interacted')
+ this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user)
- this.$store.dispatch('markSingleNotificationAsSeen', {
- id: this.notification.id,
- })
+ this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id })
this.$store.dispatch('updateNotification', {
id: this.notification.id,
- updater: (notification) => {
+ updater: notification => {
notification.type = 'follow'
- },
+ }
})
this.hideApproveConfirmDialog()
},
- denyUser() {
+ denyUser () {
if (this.shouldConfirmDeny) {
this.showDenyConfirmDialog()
} else {
this.doDeny()
}
},
- doDeny() {
- denyUser({
- id: this.user.id,
- credentials: useOAuthStore().token,
- }).then(() => {
- this.$store.dispatch('dismissNotificationLocal', {
- id: this.notification.id,
+ doDeny () {
+ this.$emit('interacted')
+ this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
+ .then(() => {
+ this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id })
+ this.$store.dispatch('removeFollowRequest', this.user)
})
- this.$store.dispatch('removeFollowRequest', this.user)
- })
this.hideDenyConfirmDialog()
- },
+ }
},
computed: {
- userClass() {
+ userClass () {
return highlightClass(this.notification.from_profile)
},
- userStyle() {
- const user = this.notification.from_profile.screen_name
- return highlightStyle(useUserHighlightStore().get(user))
+ userStyle () {
+ const highlight = this.$store.getters.mergedConfig.highlight
+ const user = this.notification.from_profile
+ return highlightStyle(highlight[user.screen_name])
},
- expandable() {
- return new Set(['like', 'pleroma:emoji_reaction', 'repeat', 'poll']).has(
- this.notification.type,
- )
- },
- user() {
+ user () {
return this.$store.getters.findUser(this.notification.from_profile.id)
},
- userProfileLink() {
+ userProfileLink () {
return this.generateUserProfileLink(this.user)
},
- targetUser() {
+ targetUser () {
return this.$store.getters.findUser(this.notification.target.id)
},
- targetUserProfileLink() {
+ targetUserProfileLink () {
return this.generateUserProfileLink(this.targetUser)
},
- needMute() {
+ needMute () {
return this.$store.getters.relationship(this.user.id).muting
},
- isStatusNotification() {
+ isStatusNotification () {
return isStatusNotification(this.notification.type)
},
- mergedConfig() {
- return useMergedConfigStore().mergedConfig
+ mergedConfig () {
+ return this.$store.getters.mergedConfig
},
- allowNonSquareEmoji() {
- return this.mergedConfig.nonSquareEmoji
- },
- shouldConfirmApprove() {
+ shouldConfirmApprove () {
return this.mergedConfig.modalOnApproveFollow
},
- shouldConfirmDeny() {
+ shouldConfirmDeny () {
return this.mergedConfig.modalOnDenyFollow
},
...mapState({
- currentUser: (state) => state.users.currentUser,
- }),
- },
+ currentUser: state => state.users.currentUser
+ })
+ }
}
export default Notification
diff --git a/src/components/notification/notification.scss b/src/components/notification/notification.scss
index 934d3e58d..e8895ce59 100644
--- a/src/components/notification/notification.scss
+++ b/src/components/notification/notification.scss
@@ -1,15 +1,10 @@
// TODO Copypaste from Status, should unify it somehow
-
.Notification {
border-bottom: 1px solid;
border-color: var(--border);
overflow-wrap: break-word;
text-wrap: pretty;
- .status-content {
- cursor: pointer;
- }
-
&.Status {
/* stylelint-disable-next-line declaration-no-important */
background-color: transparent !important;
diff --git a/src/components/notification/notification.style.js b/src/components/notification/notification.style.js
index 49e28cf2e..c6d317d1c 100644
--- a/src/components/notification/notification.style.js
+++ b/src/components/notification/notification.style.js
@@ -6,8 +6,13 @@ export default {
'Link',
'Icon',
'Border',
+ 'Button',
+ 'ButtonUnstyled',
+ 'RichContent',
+ 'Input',
'Avatar',
- 'PollGraph',
+ 'Attachment',
+ 'PollGraph'
],
- defaultRules: [],
+ defaultRules: []
}
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 39bd15426..0930e0990 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -1,26 +1,17 @@
-
+
@@ -71,8 +62,6 @@
:title="'@'+notification.from_profile.screen_name_ui"
:html="notification.from_profile.name_html"
:emoji="notification.from_profile.emoji"
- :allow-non-square-emoji="allowNonSquareEmoji"
- :is-local="notification.from_profile.is_local"
/>
@@ -137,7 +126,6 @@
:src="notification.emoji_url"
:alt="notification.emoji"
:title="notification.emoji"
- :class="{ ['-wide']: allowNonSquareEmoji }"
>
-
{{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
-
-
+
{{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
-
+
diff --git a/src/components/notifications/notification_filters.vue b/src/components/notifications/notification_filters.vue
index 27e1bb179..0b740ea0a 100644
--- a/src/components/notifications/notification_filters.vue
+++ b/src/components/notifications/notification_filters.vue
@@ -106,33 +106,31 @@
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index 49349b127..ced97d57f 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -1,32 +1,28 @@
-import { mapState } from 'pinia'
import { computed } from 'vue'
import { mapGetters } from 'vuex'
-
-import ExtraNotifications from 'src/components/extra_notifications/extra_notifications.vue'
-import Notification from 'src/components/notification/notification.vue'
-import FaviconService from '../../services/favicon_service/favicon_service.js'
-import {
- ACTIONABLE_NOTIFICATION_TYPES,
- countExtraNotifications,
- filteredNotificationsFromStore,
- notificationsFromStore,
- unseenNotificationsFromStore,
-} from '../../services/notification_utils/notification_utils.js'
-import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
+import { mapState } from 'pinia'
+import Notification from '../notification/notification.vue'
+import ExtraNotifications from '../extra_notifications/extra_notifications.vue'
import NotificationFilters from './notification_filters.vue'
-
-import { useAnnouncementsStore } from 'src/stores/announcements.js'
-import { useInterfaceStore } from 'src/stores/interface.js'
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-
-import { library } from '@fortawesome/fontawesome-svg-core'
+import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
import {
- faArrowUp,
- faCircleNotch,
- faMinus,
-} from '@fortawesome/free-solid-svg-icons'
+ notificationsFromStore,
+ filteredNotificationsFromStore,
+ unseenNotificationsFromStore,
+ countExtraNotifications,
+ ACTIONABLE_NOTIFICATION_TYPES
+} from '../../services/notification_utils/notification_utils.js'
+import FaviconService from '../../services/favicon_service/favicon_service.js'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faCircleNotch, faArrowUp, faMinus } from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
+import { useAnnouncementsStore } from 'src/stores/announcements'
-library.add(faCircleNotch, faArrowUp, faMinus)
+library.add(
+ faCircleNotch,
+ faArrowUp,
+ faMinus
+)
const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30
@@ -34,7 +30,7 @@ const Notifications = {
components: {
Notification,
NotificationFilters,
- ExtraNotifications,
+ ExtraNotifications
},
props: {
// Disables panel styles, unread mark, potentially other notification-related actions
@@ -45,124 +41,93 @@ const Notifications = {
// Do not show extra notifications
noExtra: {
type: Boolean,
- default: false,
+ default: false
},
// Disable teleporting (i.e. for /users/user/notifications)
- disableTeleport: Boolean,
+ disableTeleport: Boolean
},
- data() {
+ data () {
return {
showScrollTop: false,
bottomedOut: false,
// How many seen notifications to display in the list. The more there are,
// the heavier the page becomes. This count is increased when loading
// older notifications, and cut back to default whenever hitting "Read!".
- seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT,
+ seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT
}
},
- provide() {
+ provide () {
return {
- popoversZLayer: computed(() => this.popoversZLayer),
+ popoversZLayer: computed(() => this.popoversZLayer)
}
},
computed: {
- mainClass() {
+ mainClass () {
return this.minimalMode ? '' : 'panel panel-default'
},
- notifications() {
+ notifications () {
return notificationsFromStore(this.$store)
},
- error() {
+ error () {
return this.$store.state.notifications.error
},
- unseenNotifications() {
- return unseenNotificationsFromStore(
- this.$store,
- useMergedConfigStore().mergedConfig.notificationVisibility,
- useMergedConfigStore().mergedConfig.ignoreInactionableSeen,
- )
+ unseenNotifications () {
+ return unseenNotificationsFromStore(this.$store)
},
- filteredNotifications() {
+ filteredNotifications () {
if (this.unseenAtTop) {
return [
- ...filteredNotificationsFromStore(
- this.$store,
- useMergedConfigStore().mergedConfig.notificationVisibility,
- ).filter((n) => this.shouldShowUnseen(n)),
- ...filteredNotificationsFromStore(
- this.$store,
- useMergedConfigStore().mergedConfig.notificationVisibility,
- ).filter((n) => !this.shouldShowUnseen(n)),
+ ...filteredNotificationsFromStore(this.$store).filter(n => this.shouldShowUnseen(n)),
+ ...filteredNotificationsFromStore(this.$store).filter(n => !this.shouldShowUnseen(n))
]
} else {
- return filteredNotificationsFromStore(
- this.$store,
- useMergedConfigStore().mergedConfig.notificationVisibility,
- this.filterMode,
- )
+ return filteredNotificationsFromStore(this.$store, this.filterMode)
}
},
- unseenCountBadgeText() {
+ unseenCountBadgeText () {
return `${this.unseenCount ? this.unseenCount : ''}${this.extraNotificationsCount ? '*' : ''}`
},
- unseenCount() {
+ unseenCount () {
return this.unseenNotifications.length
},
- ignoreInactionableSeen() {
- return useMergedConfigStore().mergedConfig.ignoreInactionableSeen
+ ignoreInactionableSeen () { return this.$store.getters.mergedConfig.ignoreInactionableSeen },
+ extraNotificationsCount () {
+ return countExtraNotifications(this.$store)
},
- extraNotificationsCount() {
- return countExtraNotifications(
- this.$store,
- useMergedConfigStore().mergedConfig,
- useAnnouncementsStore().unreadAnnouncementCount,
- )
+ unseenCountTitle () {
+ return this.unseenNotifications.length + (this.unreadChatCount) + this.unreadAnnouncementCount
},
- unseenCountTitle() {
- return (
- this.unseenNotifications.length +
- this.unreadChatCount +
- this.unreadAnnouncementCount
- )
- },
- loading() {
+ loading () {
return this.$store.state.notifications.loading
},
- noHeading() {
+ noHeading () {
const { layoutType } = useInterfaceStore()
return this.minimalMode || layoutType === 'mobile'
},
- teleportTarget() {
+ teleportTarget () {
const { layoutType } = useInterfaceStore()
const map = {
wide: '#notifs-column',
- mobile: '#mobile-notifications',
+ mobile: '#mobile-notifications'
}
return map[layoutType] || '#notifs-sidebar'
},
- popoversZLayer() {
+ popoversZLayer () {
const { layoutType } = useInterfaceStore()
return layoutType === 'mobile' ? 'navbar' : null
},
- notificationsToDisplay() {
- return this.filteredNotifications.slice(
- 0,
- this.unseenCount + this.seenToDisplayCount,
- )
+ notificationsToDisplay () {
+ return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
},
- noSticky() {
- return useMergedConfigStore().mergedConfig.disableStickyHeaders
- },
- unseenAtTop() {
- return useMergedConfigStore().mergedConfig.unseenAtTop
- },
- showExtraNotifications() {
+ noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
+ unseenAtTop () { return this.$store.getters.mergedConfig.unseenAtTop },
+ showExtraNotifications () {
return !this.noExtra
},
...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
- ...mapGetters(['unreadChatCount']),
+ ...mapGetters(['unreadChatCount'])
},
- mounted() {
+ mounted () {
this.scrollerRef = this.$refs.root.closest('.column.-scrollable')
if (!this.scrollerRef) {
this.scrollerRef = this.$refs.root.closest('.mobile-notifications')
@@ -172,12 +137,12 @@ const Notifications = {
}
this.scrollerRef.addEventListener('scroll', this.updateScrollPosition)
},
- unmounted() {
+ unmounted () {
if (!this.scrollerRef) return
this.scrollerRef.removeEventListener('scroll', this.updateScrollPosition)
},
watch: {
- unseenCountTitle(count) {
+ unseenCountTitle (count) {
if (count > 0) {
FaviconService.drawFaviconBadge()
useInterfaceStore().setPageTitle(`(${count})`)
@@ -186,13 +151,10 @@ const Notifications = {
useInterfaceStore().setPageTitle('')
}
},
- teleportTarget() {
+ teleportTarget () {
// handle scroller change
this.$nextTick(() => {
- this.scrollerRef.removeEventListener(
- 'scroll',
- this.updateScrollPosition,
- )
+ this.scrollerRef.removeEventListener('scroll', this.updateScrollPosition)
this.scrollerRef = this.$refs.root.closest('.column.-scrollable')
if (!this.scrollerRef) {
this.scrollerRef = this.$refs.root.closest('.mobile-notifications')
@@ -200,18 +162,17 @@ const Notifications = {
this.scrollerRef.addEventListener('scroll', this.updateScrollPosition)
this.updateScrollPosition()
})
- },
+ }
},
methods: {
- scrollToTop() {
+ scrollToTop () {
const scrollable = this.scrollerRef
scrollable.scrollTo({ top: this.$refs.root.offsetTop })
},
- updateScrollPosition() {
- this.showScrollTop =
- this.$refs.root.offsetTop < this.scrollerRef.scrollTop
+ updateScrollPosition () {
+ this.showScrollTop = this.$refs.root.offsetTop < this.scrollerRef.scrollTop
},
- shouldShowUnseen(notification) {
+ shouldShowUnseen (notification) {
if (notification.seen) return false
const actionable = ACTIONABLE_NOTIFICATION_TYPES.has(notification.type)
@@ -221,29 +182,26 @@ const Notifications = {
* everything else (likes/repeats/reacts) cannot be acted and therefore we just clear
* the "seen" status upon any clicks on them
*/
- notificationClicked(notification) {
+ notificationClicked (notification) {
const { id } = notification
this.$store.dispatch('notificationClicked', { id })
},
- notificationInteracted(notification) {
+ notificationInteracted (notification) {
const { id } = notification
this.$store.dispatch('markSingleNotificationAsSeen', { id })
},
- markAsSeen() {
+ markAsSeen () {
this.$store.dispatch('markNotificationsAsSeen')
this.seenToDisplayCount = DEFAULT_SEEN_TO_DISPLAY_COUNT
},
- fetchOlderNotifications() {
+ fetchOlderNotifications () {
if (this.loading) {
return
}
const seenCount = this.filteredNotifications.length - this.unseenCount
if (this.seenToDisplayCount < seenCount) {
- this.seenToDisplayCount = Math.min(
- this.seenToDisplayCount + 20,
- seenCount,
- )
+ this.seenToDisplayCount = Math.min(this.seenToDisplayCount + 20, seenCount)
return
} else if (this.seenToDisplayCount > seenCount) {
this.seenToDisplayCount = seenCount
@@ -252,21 +210,19 @@ const Notifications = {
const store = this.$store
const credentials = store.state.users.currentUser.credentials
store.commit('setNotificationsLoading', { value: true })
- notificationsFetcher
- .fetchAndUpdate({
- store,
- credentials,
- older: true,
- })
- .then((notifs) => {
- store.commit('setNotificationsLoading', { value: false })
- if (notifs.length === 0) {
- this.bottomedOut = true
- }
- this.seenToDisplayCount += notifs.length
- })
- },
- },
+ notificationsFetcher.fetchAndUpdate({
+ store,
+ credentials,
+ older: true
+ }).then(notifs => {
+ store.commit('setNotificationsLoading', { value: false })
+ if (notifs.length === 0) {
+ this.bottomedOut = true
+ }
+ this.seenToDisplayCount += notifs.length
+ })
+ }
+ }
}
export default Notifications
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index 32839ebbf..79ef5b300 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -119,10 +119,6 @@
max-width: 1.25em;
height: 1.25em;
width: auto;
-
- &.-wide {
- max-width: 3.75em;
- }
}
.emoji-reaction-emoji-image {
diff --git a/src/components/oauth_callback/oauth_callback.js b/src/components/oauth_callback/oauth_callback.js
index 04f79425e..02e4c9ffc 100644
--- a/src/components/oauth_callback/oauth_callback.js
+++ b/src/components/oauth_callback/oauth_callback.js
@@ -1,27 +1,25 @@
-import { useInstanceStore } from 'src/stores/instance.js'
+import oauth from '../../services/new_api/oauth.js'
import { useOAuthStore } from 'src/stores/oauth.js'
-import { getToken } from 'src/api/oauth.js'
-
const oac = {
props: ['code'],
- mounted() {
+ mounted () {
if (this.code) {
const oauthStore = useOAuthStore()
const { clientId, clientSecret } = oauthStore
- getToken({
+ oauth.getToken({
clientId,
clientSecret,
- instance: useInstanceStore().server,
- code: this.code,
- }).then(({ data: result }) => {
+ instance: this.$store.state.instance.server,
+ code: this.code
+ }).then((result) => {
oauthStore.setToken(result.access_token)
this.$store.dispatch('loginUser', result.access_token)
this.$router.push({ name: 'friends' })
})
}
- },
+ }
}
export default oac
diff --git a/src/components/opacity_input/opacity_input.vue b/src/components/opacity_input/opacity_input.vue
index 761aeb216..041219feb 100644
--- a/src/components/opacity_input/opacity_input.vue
+++ b/src/components/opacity_input/opacity_input.vue
@@ -8,7 +8,7 @@
class="label"
:class="{ faint: !present || disabled }"
>
- {{ label || $t('settings.style.themes3.editor.opacity') }}
+ {{ label }}
diff --git a/src/components/optional_router_link/optional_router_link.vue b/src/components/optional_router_link/optional_router_link.vue
index b0d553801..d56ad268a 100644
--- a/src/components/optional_router_link/optional_router_link.vue
+++ b/src/components/optional_router_link/optional_router_link.vue
@@ -18,6 +18,6 @@
diff --git a/src/components/palette_editor/palette_editor.vue b/src/components/palette_editor/palette_editor.vue
index eec9fe361..252928b9e 100644
--- a/src/components/palette_editor/palette_editor.vue
+++ b/src/components/palette_editor/palette_editor.vue
@@ -1,65 +1,60 @@
-
- updatePalette(key, value)"
- />
-
-
-
-
- {{ $t('settings.style.themes3.palette.import') }}
-
-
-
- {{ $t('settings.style.themes3.palette.export') }}
-
-
-
-
- {{ $t('settings.style.themes3.palette.apply') }}
-
-
+
updatePalette(key, value)"
+ />
+
+
+ {{ $t('settings.style.themes3.palette.import') }}
+
+
+
+ {{ $t('settings.style.themes3.palette.export') }}
+
+
+ {{ $t('settings.style.themes3.palette.apply') }}
+
diff --git a/src/components/quote/quote_form.js b/src/components/quote/quote_form.js
deleted file mode 100644
index cffae123b..000000000
--- a/src/components/quote/quote_form.js
+++ /dev/null
@@ -1,120 +0,0 @@
-import { debounce } from 'lodash'
-
-import Checkbox from 'src/components/checkbox/checkbox.vue'
-import Quote from './quote.vue'
-
-import { useInstanceStore } from 'src/stores/instance.js'
-
-export default {
- components: {
- Quote,
- Checkbox,
- },
- name: 'QuoteForm',
- props: {
- visible: {
- type: Boolean,
- },
- url: {
- type: String,
- required: false,
- default: '',
- },
- id: {
- type: String,
- required: false,
- default: '',
- },
- },
- data() {
- return {
- text: this.url,
- loading: false,
- error: false,
- debounceSetQuote: debounce((value) => {
- this.fetchStatus(value)
- }, 1000),
- }
- },
- created() {
- if (this.url && !this.id) {
- this.fetchStatus(this.url)
- } else if (this.id) {
- this.text =
- window.location.protocol +
- '//' +
- this.instanceHost +
- '/notice/' +
- this.id
- }
- },
- computed: {
- instanceHost() {
- return new URL(useInstanceStore().server).host
- },
- noticeRegex() {
- return new RegExp(
- `^([^/:]*:?//|)(${window.location.host}|${this.instanceHost})/notice/(.*)$`,
- )
- },
- quoteVisible() {
- return (!!this.id || this.loading) && !this.error
- },
- },
- watch: {
- text(value) {
- this.debounceSetQuote(value)
- this.$emit('update:url', value)
- },
- visible(value) {
- if (value && this.url) {
- this.fetchStatus(this.url)
- }
- },
- },
- methods: {
- clear() {
- this.text = this.url
- this.loading = false
- this.error = false
- },
- setLoading(value) {
- this.loading = value
- },
- handleError(error) {
- this.id = null
- this.error = !!error
- },
- fetchStatus(value) {
- this.error = false
-
- const notice = this.noticeRegex.exec(value)
- if (notice && notice.length === 4) {
- this.$emit('update:id', notice[3])
- } else if (value) {
- this.loading = true
- this.$store
- .dispatch('search', {
- q: value,
- resolve: true,
- offset: 0,
- limit: 1,
- type: 'statuses',
- })
- .then((data) => {
- if (data && data.statuses && data.statuses.length === 1) {
- this.$emit('update:id', data.statuses[0].id)
- } else {
- this.handleError(true)
- }
- })
- .catch(this.handleError)
- .finally(() => {
- this.loading = false
- })
- } else {
- this.$emit('update:id', null)
- }
- },
- },
-}
diff --git a/src/components/quote/quote_form.vue b/src/components/quote/quote_form.vue
deleted file mode 100644
index 2e7200d7f..000000000
--- a/src/components/quote/quote_form.vue
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/components/quotes_timeline/quotes_timeline.js b/src/components/quotes_timeline/quotes_timeline.js
index f92f109be..a5f42da56 100644
--- a/src/components/quotes_timeline/quotes_timeline.js
+++ b/src/components/quotes_timeline/quotes_timeline.js
@@ -1,36 +1,26 @@
-import Timeline from 'src/components/timeline/timeline.vue'
+import Timeline from '../timeline/timeline.vue'
const QuotesTimeline = {
- created() {
+ created () {
this.$store.commit('clearTimeline', { timeline: 'quotes' })
- this.$store.dispatch('startFetchingTimeline', {
- timeline: 'quotes',
- statusId: this.statusId,
- })
+ this.$store.dispatch('startFetchingTimeline', { timeline: 'quotes', statusId: this.statusId })
},
components: {
- Timeline,
+ Timeline
},
computed: {
- statusId() {
- return this.$route.params.id
- },
- timeline() {
- return this.$store.state.statuses.timelines.quotes
- },
+ statusId () { return this.$route.params.id },
+ timeline () { return this.$store.state.statuses.timelines.quotes }
},
watch: {
- statusId() {
+ statusId () {
this.$store.commit('clearTimeline', { timeline: 'quotes' })
- this.$store.dispatch('startFetchingTimeline', {
- timeline: 'quotes',
- statusId: this.statusId,
- })
- },
+ this.$store.dispatch('startFetchingTimeline', { timeline: 'quotes', statusId: this.statusId })
+ }
},
- unmounted() {
+ unmounted () {
this.$store.dispatch('stopFetchingTimeline', 'quotes')
- },
+ }
}
export default QuotesTimeline
diff --git a/src/components/range_input/range_input.vue b/src/components/range_input/range_input.vue
index 91d3dcc3b..2f8645c0b 100644
--- a/src/components/range_input/range_input.vue
+++ b/src/components/range_input/range_input.vue
@@ -54,22 +54,13 @@
diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js
index 60f0fd16b..53a678680 100644
--- a/src/components/registration/registration.js
+++ b/src/components/registration/registration.js
@@ -1,20 +1,13 @@
import useVuelidate from '@vuelidate/core'
import { required, requiredIf, sameAs } from '@vuelidate/validators'
-import { mapState as mapPiniaState } from 'pinia'
import { mapActions, mapState } from 'vuex'
-
-import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
-import TermsOfServicePanel from 'src/components/terms_of_service_panel/terms_of_service_panel.vue'
+import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue'
import localeService from '../../services/locale/locale.service.js'
-
-import { useInstanceStore } from 'src/stores/instance.js'
-
import { DAY } from 'src/services/date_utils/date_utils.js'
+import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue'
const registration = {
- setup() {
- return { v$: useVuelidate() }
- },
+ setup () { return { v$: useVuelidate() } },
data: () => ({
user: {
email: '',
@@ -24,15 +17,15 @@ const registration = {
confirm: '',
birthday: '',
reason: '',
- language: [''],
+ language: ['']
},
- captcha: {},
+ captcha: {}
}),
components: {
InterfaceLanguageSwitcher,
- TermsOfServicePanel,
+ TermsOfServicePanel
},
- validations() {
+ validations () {
return {
user: {
email: { required: requiredIf(() => this.accountActivationRequired) },
@@ -41,23 +34,20 @@ const registration = {
password: { required },
confirm: {
required,
- sameAs: sameAs(this.user.password),
+ sameAs: sameAs(this.user.password)
},
birthday: {
required: requiredIf(() => this.birthdayRequired),
- maxValue: (value) => {
- return (
- !this.birthdayRequired ||
- new Date(value).getTime() <= this.birthdayMin.getTime()
- )
- },
+ maxValue: value => {
+ return !this.birthdayRequired || new Date(value).getTime() <= this.birthdayMin.getTime()
+ }
},
reason: { required: requiredIf(() => this.accountApprovalRequired) },
- language: {},
- },
+ language: {}
+ }
}
},
- created() {
+ created () {
if ((!this.registrationOpen && !this.token) || this.signedIn) {
this.$router.push({ name: 'root' })
}
@@ -65,16 +55,14 @@ const registration = {
this.setCaptcha()
},
computed: {
- token() {
- return this.$route.params.token
- },
- bioPlaceholder() {
+ token () { return this.$route.params.token },
+ bioPlaceholder () {
return this.replaceNewlines(this.$t('registration.bio_placeholder'))
},
- reasonPlaceholder() {
+ reasonPlaceholder () {
return this.replaceNewlines(this.$t('registration.reason_placeholder'))
},
- birthdayMin() {
+ birthdayMin () {
const minAge = this.birthdayMinAge
const today = new Date()
today.setUTCMilliseconds(0)
@@ -85,41 +73,31 @@ const registration = {
minDate.setTime(today.getTime() - minAge * DAY)
return minDate
},
- birthdayMinAttr() {
+ birthdayMinAttr () {
return this.birthdayMin.toJSON().replace(/T.+$/, '')
},
- birthdayMinFormatted() {
- const browserLocale = localeService.internalToBrowserLocale(
- this.$i18n.locale,
- )
- return (
- this.user.birthday &&
- new Date(Date.parse(this.birthdayMin)).toLocaleDateString(
- browserLocale,
- { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' },
- )
- )
+ birthdayMinFormatted () {
+ const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)
+ return this.user.birthday && new Date(Date.parse(this.birthdayMin)).toLocaleDateString(browserLocale, { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' })
},
- ...mapPiniaState(useInstanceStore, {
- registrationOpen: (store) => store.registrationOpen,
- embeddedToS: (store) => store.embeddedToS,
- termsOfService: (store) => store.tos,
- accountActivationRequired: (store) => store.accountActivationRequired,
- accountApprovalRequired: (store) => store.accountApprovalRequired,
- birthdayRequired: (store) => store.birthdayRequired,
- birthdayMinAge: (store) => store.birthdayMinAge,
- }),
...mapState({
+ registrationOpen: (state) => state.instance.registrationOpen,
signedIn: (state) => !!state.users.currentUser,
isPending: (state) => state.users.signUpPending,
serverValidationErrors: (state) => state.users.signUpErrors,
signUpNotice: (state) => state.users.signUpNotice,
hasSignUpNotice: (state) => !!state.users.signUpNotice.message,
- }),
+ termsOfService: (state) => state.instance.tos,
+ embeddedToS: (state) => state.instance.embeddedToS,
+ accountActivationRequired: (state) => state.instance.accountActivationRequired,
+ accountApprovalRequired: (state) => state.instance.accountApprovalRequired,
+ birthdayRequired: (state) => state.instance.birthdayRequired,
+ birthdayMinAge: (state) => state.instance.birthdayMinAge
+ })
},
methods: {
...mapActions(['signUp', 'getCaptcha']),
- async submit() {
+ async submit () {
this.user.nickname = this.user.username
this.user.token = this.token
@@ -127,9 +105,7 @@ const registration = {
this.user.captcha_token = this.captcha.token
this.user.captcha_answer_data = this.captcha.answer_data
if (this.user.language) {
- this.user.language = localeService.internalToBackendLocaleMulti(
- this.user.language.filter((k) => k),
- )
+ this.user.language = localeService.internalToBackendLocaleMulti(this.user.language.filter(k => k))
}
this.v$.$touch()
@@ -148,15 +124,13 @@ const registration = {
}
}
},
- setCaptcha() {
- this.getCaptcha().then((cpt) => {
- this.captcha = cpt
- })
+ setCaptcha () {
+ this.getCaptcha().then(cpt => { this.captcha = cpt })
},
- replaceNewlines(str) {
+ replaceNewlines (str) {
return str.replace(/\s*\n\s*/g, ' \n')
- },
- },
+ }
+ }
}
export default registration
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index b969a416e..f6b056e6b 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -395,7 +395,7 @@
}
form textarea {
- line-height: 1;
+ line-height: 16px;
resize: vertical;
}
diff --git a/src/components/remote_follow/remote_follow.js b/src/components/remote_follow/remote_follow.js
index a583b6f09..951b59419 100644
--- a/src/components/remote_follow/remote_follow.js
+++ b/src/components/remote_follow/remote_follow.js
@@ -1,9 +1,9 @@
export default {
props: ['user'],
computed: {
- subscribeUrl() {
+ subscribeUrl () {
const serverUrl = new URL(this.user.statusnet_profile_url)
return `${serverUrl.protocol}//${serverUrl.host}/main/ostatus`
- },
- },
+ }
+ }
}
diff --git a/src/components/remote_user_resolver/remote_user_resolver.js b/src/components/remote_user_resolver/remote_user_resolver.js
index 75304fe52..9b5e511e0 100644
--- a/src/components/remote_user_resolver/remote_user_resolver.js
+++ b/src/components/remote_user_resolver/remote_user_resolver.js
@@ -1,22 +1,15 @@
-import { useOAuthStore } from 'src/stores/oauth.js'
-
-import { fetchUser } from 'src/api/public.js'
-
const RemoteUserResolver = {
data: () => ({
- error: false,
+ error: false
}),
- mounted() {
+ mounted () {
this.redirect()
},
methods: {
- redirect() {
- const id = this.$route.params.username + '@' + this.$route.params.hostname
- fetchUser({
- id,
- credentials: useOAuthStore().token,
- })
- .then(({ data: externalUser }) => {
+ redirect () {
+ const acct = this.$route.params.username + '@' + this.$route.params.hostname
+ this.$store.state.api.backendInteractor.fetchUser({ id: acct })
+ .then((externalUser) => {
if (externalUser.error) {
this.error = true
} else {
@@ -24,15 +17,15 @@ const RemoteUserResolver = {
const id = externalUser.id
this.$router.replace({
name: 'external-user-profile',
- params: { id },
+ params: { id }
})
}
})
.catch(() => {
this.error = true
})
- },
- },
+ }
+ }
}
export default RemoteUserResolver
diff --git a/src/components/remove_follower_button/remove_follower_button.js b/src/components/remove_follower_button/remove_follower_button.js
index cbe92dce1..052a519ff 100644
--- a/src/components/remove_follower_button/remove_follower_button.js
+++ b/src/components/remove_follower_button/remove_follower_button.js
@@ -1,54 +1,48 @@
-import { defineAsyncComponent } from 'vue'
-
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
+import ConfirmModal from '../confirm_modal/confirm_modal.vue'
export default {
props: ['user', 'relationship'],
- data() {
+ data () {
return {
inProgress: false,
- showingConfirmRemoveFollower: false,
+ showingConfirmRemoveFollower: false
}
},
components: {
- ConfirmModal: defineAsyncComponent(
- () => import('src/components/confirm_modal/confirm_modal.vue'),
- ),
+ ConfirmModal
},
computed: {
- label() {
+ label () {
if (this.inProgress) {
return this.$t('user_card.follow_progress')
} else {
return this.$t('user_card.remove_follower')
}
},
- shouldConfirmRemoveUserFromFollowers() {
- return useMergedConfigStore().mergedConfig.modalOnRemoveUserFromFollowers
- },
+ shouldConfirmRemoveUserFromFollowers () {
+ return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
+ }
},
methods: {
- showConfirmRemoveUserFromFollowers() {
+ showConfirmRemoveUserFromFollowers () {
this.showingConfirmRemoveFollower = true
},
- hideConfirmRemoveUserFromFollowers() {
+ hideConfirmRemoveUserFromFollowers () {
this.showingConfirmRemoveFollower = false
},
- onClick() {
+ onClick () {
if (!this.shouldConfirmRemoveUserFromFollowers) {
this.doRemoveUserFromFollowers()
} else {
this.showConfirmRemoveUserFromFollowers()
}
},
- doRemoveUserFromFollowers() {
+ doRemoveUserFromFollowers () {
this.inProgress = true
- this.$store
- .dispatch('removeUserFromFollowers', this.relationship.id)
- .then(() => {
- this.inProgress = false
- })
+ this.$store.dispatch('removeUserFromFollowers', this.relationship.id).then(() => {
+ this.inProgress = false
+ })
this.hideConfirmRemoveUserFromFollowers()
- },
- },
+ }
+ }
}
diff --git a/src/components/remove_follower_button/remove_follower_button.vue b/src/components/remove_follower_button/remove_follower_button.vue
index 93eaf66c4..3054770d9 100644
--- a/src/components/remove_follower_button/remove_follower_button.vue
+++ b/src/components/remove_follower_button/remove_follower_button.vue
@@ -8,7 +8,7 @@
>
{{ label }}
-
-
+
diff --git a/src/components/report/report.js b/src/components/report/report.js
index e9d14c515..6e9f21eea 100644
--- a/src/components/report/report.js
+++ b/src/components/report/report.js
@@ -1,44 +1,37 @@
-import Select from 'src/components/select/select.vue'
-import StatusContent from 'src/components/status_content/status_content.vue'
-import Timeago from 'src/components/timeago/timeago.vue'
-
-import { useInstanceStore } from 'src/stores/instance.js'
-import { useReportsStore } from 'src/stores/reports.js'
-
+import { useReportsStore } from 'src/stores/reports'
+import Select from '../select/select.vue'
+import StatusContent from '../status_content/status_content.vue'
+import Timeago from '../timeago/timeago.vue'
+import RichContent from 'src/components/rich_content/rich_content.jsx'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const Report = {
- props: ['reportId'],
+ props: [
+ 'reportId'
+ ],
components: {
Select,
StatusContent,
Timeago,
+ RichContent
},
computed: {
- report() {
+ report () {
return useReportsStore().reports[this.reportId] || {}
},
state: {
- get: function () {
- return this.report.state
- },
- set: function (val) {
- this.setReportState(val)
- },
- },
+ get: function () { return this.report.state },
+ set: function (val) { this.setReportState(val) }
+ }
},
methods: {
- generateUserProfileLink(user) {
- return generateProfileLink(
- user.id,
- user.screen_name,
- useInstanceStore().restrictedNicknames,
- )
+ generateUserProfileLink (user) {
+ return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
},
- setReportState(state) {
+ setReportState (state) {
return useReportsStore().setReportState({ id: this.report.id, state })
- },
- },
+ }
+ }
}
export default Report
diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx
index fc17a529f..9466603cd 100644
--- a/src/components/rich_content/rich_content.jsx
+++ b/src/components/rich_content/rich_content.jsx
@@ -1,18 +1,11 @@
-import { flattenDeep, unescape as ldUnescape } from 'lodash'
-
-import HashtagLink from 'src/components/hashtag_link/hashtag_link.vue'
-import { MENTIONS_LIMIT } from 'src/components/mentions_line/mentions_line.js'
-import MentionsLine from 'src/components/mentions_line/mentions_line.vue'
-import StillImage from 'src/components/still-image/still-image.vue'
-import StillImageEmojiPopover from 'src/components/still-image/still-image-emoji-popover.vue'
-
-import { convertHtmlToLines } from 'src/services/html_converter/html_line_converter.service.js'
+import { unescape, flattenDeep } from 'lodash'
+import { getTagName, processTextForEmoji, getAttrs } from 'src/services/html_converter/utility.service.js'
import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js'
-import {
- getAttrs,
- getTagName,
- processTextForEmoji,
-} from 'src/services/html_converter/utility.service.js'
+import { convertHtmlToLines } from 'src/services/html_converter/html_line_converter.service.js'
+import StillImage from 'src/components/still-image/still-image.vue'
+import MentionsLine from 'src/components/mentions_line/mentions_line.vue'
+import { MENTIONS_LIMIT } from 'src/components/mentions_line/mentions_line.js'
+import HashtagLink from 'src/components/hashtag_link/hashtag_link.vue'
import './rich_content.scss'
@@ -34,7 +27,7 @@ const MAYBE_LINE_BREAKING_ELEMENTS = [
'h2',
'h3',
'h4',
- 'h5',
+ 'h5'
]
/**
@@ -59,68 +52,44 @@ export default {
name: 'RichContent',
components: {
MentionsLine,
- HashtagLink,
+ HashtagLink
},
props: {
// Original html content
html: {
required: true,
- type: String,
+ type: String
},
attentions: {
required: false,
- default: () => [],
+ default: () => []
},
// Emoji object, as in status.emojis, note the "s" at the end...
emoji: {
required: true,
- type: Array,
+ type: Array
},
// Whether to handle links or not (posts: yes, everything else: no)
handleLinks: {
required: false,
type: Boolean,
- default: false,
+ default: false
},
// Meme arrows
greentext: {
required: false,
type: Boolean,
- default: false,
+ default: false
},
// Faint style (for notifs)
faint: {
required: false,
type: Boolean,
- default: false,
- },
- // Collapse newlines
- collapse: {
- required: false,
- type: Boolean,
- default: false,
- },
- /* Content comes from current instance
- *
- * This is used for emoji stealing popover.
- * By default we assume it is, so that steal
- * emoji button isn't shown where it probably
- * should not be.
- */
- isLocal: {
- required: false,
- type: Boolean,
- default: true,
- },
- // Allow wide emoji (max 3:1 ratio)
- allowNonSquareEmoji: {
- required: false,
- type: Boolean,
- default: false,
- },
+ default: false
+ }
},
// NEVER EVER TOUCH DATA INSIDE RENDER
- render() {
+ render () {
// Pre-process HTML
const { newHtml: html } = preProcessPerLine(this.html, this.greentext)
let currentMentions = null // Current chain of mentions, we group all mentions together
@@ -137,7 +106,10 @@ export default {
let tagsIndex = 0
const renderImage = (tag) => {
- return
+ return
}
const renderHashtag = (attrs, children, encounteredTextReverse) => {
@@ -147,14 +119,12 @@ export default {
lastTags.push(linkData)
}
const { url, tag, content } = linkData
- return
+ return
}
const renderMention = (attrs, children) => {
const linkData = getLinkData(attrs, children, mentionIndex++)
- linkData.notifying = this.attentions.some(
- (a) => a.statusnet_profile_url === linkData.url,
- )
+ linkData.notifying = this.attentions.some(a => a.statusnet_profile_url === linkData.url)
writtenMentions.push(linkData)
if (currentMentions === null) {
currentMentions = []
@@ -164,7 +134,7 @@ export default {
invisibleMentions.push(linkData)
}
if (currentMentions.length === 1) {
- return
+ return
} else {
return ''
}
@@ -183,28 +153,23 @@ export default {
// in MentionsLine
lastSpacing = item
// Don't remove last space in a container (fixes poast mentions)
- return index !== array.length - 1 && currentMentions !== null
- ? item.trim()
- : item
+ return (index !== array.length - 1) && (currentMentions !== null) ? item.trim() : item
}
currentMentions = null
if (item.includes(':')) {
- item = [
- '',
- processTextForEmoji(item, this.emoji, ({ shortcode, url }) => {
- return (
-
- )
- }),
- ]
+ item = ['', processTextForEmoji(
+ item,
+ this.emoji,
+ ({ shortcode, url }) => {
+ return
+ }
+ )]
}
return item
}
@@ -223,24 +188,18 @@ export default {
* we have a tag right next to mentions
*/
const mentionsLinePadding =
- // Padding is only needed if we just finished parsing mentions
- previouslyMentions &&
- // Don't add padding if content is string and has padding already
- !(
- children &&
- typeof children[0] === 'string' &&
- children[0].match(/^\s/)
- )
- ? lastSpacing
- : ''
+ // Padding is only needed if we just finished parsing mentions
+ previouslyMentions &&
+ // Don't add padding if content is string and has padding already
+ !(children && typeof children[0] === 'string' && children[0].match(/^\s/))
+ ? lastSpacing
+ : ''
if (MAYBE_LINE_BREAKING_ELEMENTS.includes(Tag)) {
// all the elements that can cause a line change
currentMentions = null
- } else if (Tag === 'img') {
- // replace images with StillImage
+ } else if (Tag === 'img') { // replace images with StillImage
return ['', [mentionsLinePadding, renderImage(opener)], '']
- } else if (Tag === 'a' && this.handleLinks) {
- // replace mentions with MentionLink
+ } else if (Tag === 'a' && this.handleLinks) { // replace mentions with MentionLink
if (fullAttrs.class && fullAttrs.class.includes('mention')) {
// Handling mentions here
return renderMention(attrs, children)
@@ -248,11 +207,7 @@ export default {
currentMentions = null
}
} else if (Tag === 'span') {
- if (
- this.handleLinks &&
- fullAttrs.class &&
- fullAttrs.class.includes('h-card')
- ) {
+ if (this.handleLinks && fullAttrs.class && fullAttrs.class.includes('h-card')) {
return ['', children.map(processItem), '']
}
}
@@ -260,8 +215,11 @@ export default {
if (children !== undefined) {
return [
'',
- [mentionsLinePadding, [opener, children.map(processItem), closer]],
- '',
+ [
+ mentionsLinePadding,
+ [opener, children.map(processItem), closer]
+ ],
+ ''
]
} else {
return ['', [mentionsLinePadding, item], '']
@@ -277,31 +235,29 @@ export default {
const emptyText = item.trim() === ''
if (emptyText) return item
if (!encounteredTextReverse) encounteredTextReverse = true
- return ldUnescape(item)
+ return unescape(item)
} else if (Array.isArray(item)) {
// Handle tag nodes
const [opener, children] = item
const Tag = opener === '' ? '' : getTagName(opener)
switch (Tag) {
- case 'a': {
- // replace mentions with MentionLink
+ case 'a': { // replace mentions with MentionLink
if (!this.handleLinks) break
const fullAttrs = getAttrs(opener, () => true)
const attrs = getAttrs(opener, () => true)
// should only be this
if (
(fullAttrs.class && fullAttrs.class.includes('hashtag')) || // Pleroma style
- fullAttrs.rel === 'tag' // Mastodon style
+ (fullAttrs.rel === 'tag') // Mastodon style
) {
return renderHashtag(attrs, children, encounteredTextReverse)
} else {
attrs.target = '_blank'
- const newChildren = [...children]
- .reverse()
- .map(processItemReverse)
- .reverse()
+ const newChildren = [...children].reverse().map(processItemReverse).reverse()
- return {newChildren}
+ return
+ { newChildren }
+
}
}
case '':
@@ -313,9 +269,11 @@ export default {
const newChildren = Array.isArray(children)
? [...children].reverse().map(processItemReverse).reverse()
: children
- return {newChildren}
+ return
+ { newChildren }
+
} else {
- return
+ return
}
}
return item
@@ -323,39 +281,25 @@ export default {
const pass1 = convertHtmlToTree(html).map(processItem)
const pass2 = [...pass1].reverse().map(processItemReverse).reverse()
-
// DO NOT USE SLOTS they cause a re-render feedback loop here.
// slots updated -> rerender -> emit -> update up the tree -> rerender -> ...
// at least until vue3?
- const result = (
-
- {this.collapse
- ? pass2.map((x) => {
- if (!Array.isArray(x)) return x.replace(/\n/g, ' ')
- return x.map((y) => (y.type === 'br' ? ' ' : y))
- })
- : pass2}
-
- )
+ const result =
+ { pass2 }
+
const event = {
lastTags,
writtenMentions,
writtenTags,
- invisibleMentions,
+ invisibleMentions
}
// DO NOT MOVE TO UPDATE. BAD IDEA.
this.$emit('parseReady', event)
return result
- },
+ }
}
const getLinkData = (attrs, children, index) => {
@@ -372,7 +316,7 @@ const getLinkData = (attrs, children, index) => {
url: attrs.href,
tag: attrs['data-tag'],
content: flattenDeep(children).join(''),
- textContent,
+ textContent
}
}
@@ -388,36 +332,31 @@ export const preProcessPerLine = (html, greentext) => {
const greentextHandle = new Set(['p', 'div'])
const lines = convertHtmlToLines(html)
- const newHtml = lines
- .reverse()
- .map((item, index, array) => {
- if (!item.text) return item
- const string = item.text
+ const newHtml = lines.reverse().map((item, index, array) => {
+ if (!item.text) return item
+ const string = item.text
- // Greentext stuff
- if (
- // Only if greentext is engaged
- greentext &&
+ // Greentext stuff
+ if (
+ // Only if greentext is engaged
+ greentext &&
// Only handle p's and divs. Don't want to affect blockquotes, code etc
- item.level.every((l) => greentextHandle.has(l)) &&
+ item.level.every(l => greentextHandle.has(l)) &&
// Only if line begins with '>' or '<'
(string.includes('>') || string.includes('<'))
- ) {
- const cleanedString = string
- .replace(/<[^>]+?>/gi, '') // remove all tags
- .replace(/@\w+/gi, '') // remove mentions (even failed ones)
- .trim()
- if (cleanedString.startsWith('>')) {
- return `${string} `
- } else if (cleanedString.startsWith('<')) {
- return `${string} `
- }
+ ) {
+ const cleanedString = string.replace(/<[^>]+?>/gi, '') // remove all tags
+ .replace(/@\w+/gi, '') // remove mentions (even failed ones)
+ .trim()
+ if (cleanedString.startsWith('>')) {
+ return `${string} `
+ } else if (cleanedString.startsWith('<')) {
+ return `${string} `
}
+ }
- return string
- })
- .reverse()
- .join('')
+ return string
+ }).reverse().join('')
return { newHtml }
}
diff --git a/src/components/rich_content/rich_content.scss b/src/components/rich_content/rich_content.scss
index 33effc623..140be2f98 100644
--- a/src/components/rich_content/rich_content.scss
+++ b/src/components/rich_content/rich_content.scss
@@ -2,7 +2,6 @@
font-family: var(--font);
&.-faint {
- color: var(--text);
/* stylelint-disable declaration-no-important */
--text: var(--textFaint) !important;
--link: var(--linkFaint) !important;
@@ -64,11 +63,6 @@
.img {
display: inline-block;
-
- // fix vertical alignment of stealable emoji
- button {
- display: inline-flex;
- }
}
.emoji {
@@ -77,14 +71,6 @@
height: var(--emoji-size, 32px);
}
- &.-allow-non-square-emoji {
- .emoji {
- width: auto;
- max-width: calc(var(--emoji-size, 32px) * 3);
- min-width: var(--emoji-size, 32px);
- }
- }
-
.img,
video {
max-width: 100%;
diff --git a/src/components/rich_content/rich_content.style.js b/src/components/rich_content/rich_content.style.js
new file mode 100644
index 000000000..eaba9c4b1
--- /dev/null
+++ b/src/components/rich_content/rich_content.style.js
@@ -0,0 +1,20 @@
+export default {
+ name: 'RichContent',
+ selector: '.RichContent',
+ notEditable: true,
+ transparent: true,
+ validInnerComponents: [
+ 'Text',
+ 'FunText',
+ 'Link'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ '--font': 'generic | inherit',
+ '--monoFont': 'generic | monospace',
+ textNoCssColor: 'yes'
+ }
+ }
+ ]
+}
diff --git a/src/components/root.style.js b/src/components/root.style.js
index 54c4c6095..5075e33c8 100644
--- a/src/components/root.style.js
+++ b/src/components/root.style.js
@@ -27,8 +27,8 @@ export default {
// Selection colors
'--selectionBackground': 'color | --accent',
- '--selectionText': 'color | $textColor(--accent --text no-preserve)',
- },
- },
- ],
+ '--selectionText': 'color | $textColor(--accent --text no-preserve)'
+ }
+ }
+ ]
}
diff --git a/src/components/roundness_input/roundness_input.vue b/src/components/roundness_input/roundness_input.vue
index caf21763b..1da71ca79 100644
--- a/src/components/roundness_input/roundness_input.vue
+++ b/src/components/roundness_input/roundness_input.vue
@@ -33,17 +33,19 @@
diff --git a/src/components/scope_selector/scope_selector.js b/src/components/scope_selector/scope_selector.js
index 9d3a3a718..52cda368b 100644
--- a/src/components/scope_selector/scope_selector.js
+++ b/src/components/scope_selector/scope_selector.js
@@ -1,92 +1,69 @@
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEnvelope,
- faGlobe,
faLock,
faLockOpen,
+ faGlobe
} from '@fortawesome/free-solid-svg-icons'
-library.add(faEnvelope, faGlobe, faLock, faLockOpen)
+library.add(
+ faEnvelope,
+ faGlobe,
+ faLock,
+ faLockOpen
+)
const ScopeSelector = {
- props: {
- showAll: {
- required: true,
- type: Boolean,
- },
- userDefault: {
- required: true,
- type: String,
- },
- originalScope: {
- required: false,
- type: String,
- },
- initialScope: {
- required: false,
- type: String,
- },
- onScopeChange: {
- required: true,
- type: Function,
- },
- unstyled: {
- required: false,
- type: Boolean,
- default: true,
- },
- },
- data() {
+ props: [
+ 'showAll',
+ 'userDefault',
+ 'originalScope',
+ 'initialScope',
+ 'onScopeChange'
+ ],
+ data () {
return {
- currentScope: this.initialScope,
+ currentScope: this.initialScope
}
},
computed: {
- showNothing() {
- return (
- !this.showPublic &&
- !this.showUnlisted &&
- !this.showPrivate &&
- !this.showDirect
- )
+ showNothing () {
+ return !this.showPublic && !this.showUnlisted && !this.showPrivate && !this.showDirect
},
- showPublic() {
+ showPublic () {
return this.originalScope !== 'direct' && this.shouldShow('public')
},
- showUnlisted() {
+ showUnlisted () {
return this.originalScope !== 'direct' && this.shouldShow('unlisted')
},
- showPrivate() {
+ showPrivate () {
return this.originalScope !== 'direct' && this.shouldShow('private')
},
- showDirect() {
+ showDirect () {
return this.shouldShow('direct')
},
- css() {
- const style = this.unstyled ? 'button-unstyled' : 'button-default'
+ css () {
return {
- public: [style, { toggled: this.currentScope === 'public' }],
- unlisted: [style, { toggled: this.currentScope === 'unlisted' }],
- private: [style, { toggled: this.currentScope === 'private' }],
- direct: [style, { toggled: this.currentScope === 'direct' }],
+ public: { toggled: this.currentScope === 'public' },
+ unlisted: { toggled: this.currentScope === 'unlisted' },
+ private: { toggled: this.currentScope === 'private' },
+ direct: { toggled: this.currentScope === 'direct' }
}
- },
+ }
},
methods: {
- shouldShow(scope) {
- return (
- this.showAll ||
+ shouldShow (scope) {
+ return this.showAll ||
this.currentScope === scope ||
this.originalScope === scope ||
this.userDefault === scope ||
scope === 'direct'
- )
},
- changeVis(scope) {
+ changeVis (scope) {
this.currentScope = scope
this.onScopeChange && this.onScopeChange(scope)
- },
- },
+ }
+ }
}
export default ScopeSelector
diff --git a/src/components/scope_selector/scope_selector.vue b/src/components/scope_selector/scope_selector.vue
index cbe51a07f..b90ae0205 100644
--- a/src/components/scope_selector/scope_selector.vue
+++ b/src/components/scope_selector/scope_selector.vue
@@ -1,11 +1,11 @@
.ScopeSelector {
- display: inline-block;
-
.scope {
display: inline-block;
+ cursor: pointer;
min-width: 1.3em;
min-height: 1.3em;
text-align: center;
- padding: 0.5em 0.25em
}
}
diff --git a/src/components/screen_reader_notice/screen_reader_notice.js b/src/components/screen_reader_notice/screen_reader_notice.js
index b834be531..794b855ac 100644
--- a/src/components/screen_reader_notice/screen_reader_notice.js
+++ b/src/components/screen_reader_notice/screen_reader_notice.js
@@ -2,22 +2,20 @@ const ScreenReaderNotice = {
props: {
ariaLive: {
type: String,
- default: 'assertive',
- },
+ default: 'assertive'
+ }
},
- data() {
+ data () {
return {
- currentText: '',
+ currentText: ''
}
},
methods: {
- announce(text) {
+ announce (text) {
this.currentText = text
- setTimeout(() => {
- this.currentText = ''
- }, 1000)
- },
- },
+ setTimeout(() => { this.currentText = '' }, 1000)
+ }
+ }
}
export default ScreenReaderNotice
diff --git a/src/components/scroll_top_button/scroll_top_button.js b/src/components/scroll_top_button/scroll_top_button.js
index e4b908f0b..bdc45b9b4 100644
--- a/src/components/scroll_top_button/scroll_top_button.js
+++ b/src/components/scroll_top_button/scroll_top_button.js
@@ -3,16 +3,16 @@ const ScrollTopButton = {
fast: {
type: Boolean,
required: false,
- default: false,
- },
+ default: false
+ }
},
methods: {
scrollToTop() {
- const speed = this.fast ? 'instant' : 'smooth'
+ const speed = this.fast ? 'instant' : 'smooth';
window.scrollTo({ top: 0, behavior: speed })
- },
- },
+ }
+ }
}
export default ScrollTopButton
diff --git a/src/components/scrollbar.style.js b/src/components/scrollbar.style.js
index fbe952076..1168f67d8 100644
--- a/src/components/scrollbar.style.js
+++ b/src/components/scrollbar.style.js
@@ -1,16 +1,12 @@
export default {
name: 'Scrollbar',
- selector: [
- '::-webkit-scrollbar-button',
- '::-webkit-scrollbar-thumb',
- '::-webkit-resizer',
- ],
+ selector: ['::-webkit-scrollbar-button', '::-webkit-scrollbar-thumb', '::-webkit-resizer'],
notEditable: true, // for now
defaultRules: [
{
directives: {
- background: '--wallpaper',
- },
- },
- ],
+ background: '--wallpaper'
+ }
+ }
+ ]
}
diff --git a/src/components/scrollbar_element.style.js b/src/components/scrollbar_element.style.js
index 6f238e1f8..ef1ea8136 100644
--- a/src/components/scrollbar_element.style.js
+++ b/src/components/scrollbar_element.style.js
@@ -5,7 +5,7 @@ const border = (top, shadow) => ({
spread: 0,
color: shadow ? '#000000' : '#FFFFFF',
alpha: 0.2,
- inset: true,
+ inset: true
})
const buttonInsetFakeBorders = [border(true, false), border(false, true)]
@@ -16,7 +16,7 @@ const buttonOuterShadow = {
blur: 2,
spread: 0,
color: '#000000',
- alpha: 1,
+ alpha: 1
}
const hoverGlow = {
@@ -25,7 +25,7 @@ const hoverGlow = {
blur: 4,
spread: 0,
color: '--text',
- alpha: 1,
+ alpha: 1
}
export default {
@@ -35,66 +35,68 @@ export default {
states: {
pressed: ':active',
hover: ':is(:hover, :focus-visible, :has(:focus-visible)):not(:disabled)',
- disabled: ':disabled',
+ disabled: ':disabled'
},
- validInnerComponents: ['Text'],
+ validInnerComponents: [
+ 'Text'
+ ],
defaultRules: [
{
directives: {
background: '--fg',
shadow: [buttonOuterShadow, ...buttonInsetFakeBorders],
- roundness: 3,
- },
+ roundness: 3
+ }
},
{
state: ['hover'],
directives: {
- shadow: [hoverGlow, ...buttonInsetFakeBorders],
- },
+ shadow: [hoverGlow, ...buttonInsetFakeBorders]
+ }
},
{
state: ['pressed'],
directives: {
- shadow: [buttonOuterShadow, ...inputInsetFakeBorders],
- },
+ shadow: [buttonOuterShadow, ...inputInsetFakeBorders]
+ }
},
{
state: ['hover', 'pressed'],
directives: {
- shadow: [hoverGlow, ...inputInsetFakeBorders],
- },
+ shadow: [hoverGlow, ...inputInsetFakeBorders]
+ }
},
{
state: ['toggled'],
directives: {
background: '--accent,-24.2',
- shadow: [buttonOuterShadow, ...inputInsetFakeBorders],
- },
+ shadow: [buttonOuterShadow, ...inputInsetFakeBorders]
+ }
},
{
state: ['toggled', 'hover'],
directives: {
background: '--accent,-24.2',
- shadow: [hoverGlow, ...inputInsetFakeBorders],
- },
+ shadow: [hoverGlow, ...inputInsetFakeBorders]
+ }
},
{
state: ['disabled'],
directives: {
background: '$blend(--inheritedBackground 0.25 --parent)',
- shadow: [...buttonInsetFakeBorders],
- },
+ shadow: [...buttonInsetFakeBorders]
+ }
},
{
component: 'Text',
parent: {
component: 'Button',
- state: ['disabled'],
+ state: ['disabled']
},
directives: {
textOpacity: 0.25,
- textOpacityMode: 'blend',
- },
- },
- ],
+ textOpacityMode: 'blend'
+ }
+ }
+ ]
}
diff --git a/src/components/search/search.js b/src/components/search/search.js
index 0a1f779bb..877d6f300 100644
--- a/src/components/search/search.js
+++ b/src/components/search/search.js
@@ -1,23 +1,31 @@
-import { map, uniqBy } from 'lodash'
-
-import Conversation from 'src/components/conversation/conversation.vue'
-import FollowCard from 'src/components/follow_card/follow_card.vue'
+import FollowCard from '../follow_card/follow_card.vue'
+import Conversation from '../conversation/conversation.vue'
+import Status from '../status/status.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
-
+import map from 'lodash/map'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faCircleNotch, faSearch } from '@fortawesome/free-solid-svg-icons'
+import {
+ faCircleNotch,
+ faSearch
+} from '@fortawesome/free-solid-svg-icons'
+import { uniqBy } from 'lodash'
-library.add(faCircleNotch, faSearch)
+library.add(
+ faCircleNotch,
+ faSearch
+)
const Search = {
components: {
FollowCard,
Conversation,
-
- TabSwitcher,
+ Status,
+ TabSwitcher
},
- props: ['query'],
- data() {
+ props: [
+ 'query'
+ ],
+ data () {
return {
loaded: false,
loading: false,
@@ -29,37 +37,36 @@ const Search = {
statusesOffset: 0,
lastStatusFetchCount: 0,
- lastQuery: '',
+ lastQuery: ''
}
},
computed: {
- users() {
- return this.userIds.map((userId) => this.$store.getters.findUser(userId))
+ users () {
+ return this.userIds.map(userId => this.$store.getters.findUser(userId))
},
- visibleStatuses() {
+ visibleStatuses () {
const allStatusesObject = this.$store.state.statuses.allStatusesObject
- return this.statuses.filter(
- (status) =>
- allStatusesObject[status.id] && !allStatusesObject[status.id].deleted,
+ return this.statuses.filter(status =>
+ allStatusesObject[status.id] && !allStatusesObject[status.id].deleted
)
- },
+ }
},
- mounted() {
+ mounted () {
this.search(this.query)
},
watch: {
- query(newValue) {
+ query (newValue) {
this.searchTerm = newValue
this.search(newValue)
- },
+ }
},
methods: {
- newQuery(query) {
+ newQuery (query) {
this.$router.push({ name: 'search', query: { query } })
this.$refs.searchInput.focus()
},
- search(query, searchType = null) {
+ search (query, searchType = null) {
if (!query) {
this.loading = false
return
@@ -76,14 +83,8 @@ const Search = {
this.lastStatusFetchCount = 0
}
- this.$store
- .dispatch('search', {
- q: query,
- resolve: true,
- offset: this.statusesOffset,
- type: searchType,
- })
- .then((data) => {
+ this.$store.dispatch('search', { q: query, resolve: true, offset: this.statusesOffset, type: searchType })
+ .then(data => {
this.loading = false
const oldLength = this.statuses.length
@@ -103,14 +104,14 @@ const Search = {
this.lastQuery = query
})
},
- resultCount(tabName) {
+ resultCount (tabName) {
const length = this[tabName].length
return length === 0 ? '' : ` (${length})`
},
- onResultTabSwitch(key) {
+ onResultTabSwitch (key) {
this.currenResultTab = key
},
- getActiveTab() {
+ getActiveTab () {
if (this.visibleStatuses.length > 0) {
return 'statuses'
} else if (this.users.length > 0) {
@@ -121,10 +122,10 @@ const Search = {
return 'statuses'
},
- lastHistoryRecord(hashtag) {
+ lastHistoryRecord (hashtag) {
return hashtag.history && hashtag.history[0]
- },
- },
+ }
+ }
}
export default Search
diff --git a/src/components/search/search.vue b/src/components/search/search.vue
index 1f3982606..4302f93f9 100644
--- a/src/components/search/search.vue
+++ b/src/components/search/search.vue
@@ -58,10 +58,12 @@
({
searchTerm: undefined,
hidden: true,
- error: false,
+ error: false
}),
watch: {
$route: function (route) {
if (route.name === 'search') {
this.searchTerm = route.query.query
}
- },
+ }
},
methods: {
- find(searchTerm) {
+ find (searchTerm) {
this.$router.push({ name: 'search', query: { query: searchTerm } })
this.$refs.searchInput.focus()
},
- toggleHidden() {
+ toggleHidden () {
this.hidden = !this.hidden
this.$emit('toggled', this.hidden)
this.$nextTick(() => {
@@ -29,8 +35,8 @@ const SearchBar = {
this.$refs.searchInput.focus()
}
})
- },
- },
+ }
+ }
}
export default SearchBar
diff --git a/src/components/search_bar/search_bar.vue b/src/components/search_bar/search_bar.vue
index 9a9c971fb..d06b4e77d 100644
--- a/src/components/search_bar/search_bar.vue
+++ b/src/components/search_bar/search_bar.vue
@@ -51,6 +51,8 @@
class="cancel-icon fa-scale-110 fa-old-padding"
/>
+
+
@@ -59,14 +61,18 @@
diff --git a/src/components/settings_modal/admin_tabs/admin_user_card.js b/src/components/settings_modal/admin_tabs/admin_user_card.js
deleted file mode 100644
index ba0f0e9f7..000000000
--- a/src/components/settings_modal/admin_tabs/admin_user_card.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
-import ModerationTools from 'src/components/moderation_tools/moderation_tools.vue'
-
-const AdminUserCard = {
- props: {
- userId: {
- type: String,
- },
- },
- components: {
- BasicUserCard,
- ModerationTools,
- },
- computed: {
- user() {
- return this.$store.getters.findUser(this.userId)
- },
- isAdmin() {
- return this.user.rights.admin
- },
- isModerator() {
- return this.user.rights.moderator
- },
- isActivated() {
- return !this.user.deactivated
- },
- },
-}
-
-export default AdminUserCard
diff --git a/src/components/settings_modal/admin_tabs/admin_user_card.scss b/src/components/settings_modal/admin_tabs/admin_user_card.scss
deleted file mode 100644
index a1f685aec..000000000
--- a/src/components/settings_modal/admin_tabs/admin_user_card.scss
+++ /dev/null
@@ -1,29 +0,0 @@
-.AdminUserCard {
- details {
- white-space: normal;
-
- summary {
- font-weight: bold;
- }
-
- span {
- display: block;
- max-height: 6.5em;
- overflow-y: auto;
- resize: vertical;
- }
- }
-
- .right-side {
- align-items: baseline;
- justify-content: end;
- display: flex;
- flex-wrap: wrap;
- gap: 0.5em;
- margin-top: 0.5em;
-
- .alert {
- margin: 0;
- }
- }
-}
diff --git a/src/components/settings_modal/admin_tabs/admin_user_card.vue b/src/components/settings_modal/admin_tabs/admin_user_card.vue
deleted file mode 100644
index 00405dfa1..000000000
--- a/src/components/settings_modal/admin_tabs/admin_user_card.vue
+++ /dev/null
@@ -1,107 +0,0 @@
-
-
-
-
-
-
-
-
-
- {{ $t('user_card.admin_data.registration_reason') }}
-
-
- {{ user.adminData.registration_reason }}
-
-
-
-
- {{ $t('admin_dash.users.indicator.admin') }}
-
-
- {{ $t('admin_dash.users.indicator.moderator') }}
-
-
- {{ $t('admin_dash.users.indicator.active') }}
-
-
- {{ $t('admin_dash.users.indicator.deactivated') }}
-
-
- {{ $t('admin_dash.users.indicator.confirmed') }}
-
-
- {{ $t('admin_dash.users.indicator.unconfirmed') }}
-
-
- {{ $t('admin_dash.users.indicator.approved') }}
-
-
- {{ $t('admin_dash.users.indicator.unapproved') }}
-
-
- {{ $t('admin_dash.users.indicator.suggested') }}
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/settings_modal/admin_tabs/auth_tab.js b/src/components/settings_modal/admin_tabs/auth_tab.js
deleted file mode 100644
index 7ccae2ca0..000000000
--- a/src/components/settings_modal/admin_tabs/auth_tab.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import AttachmentSetting from '../helpers/attachment_setting.vue'
-import BooleanSetting from '../helpers/boolean_setting.vue'
-import ChoiceSetting from '../helpers/choice_setting.vue'
-import GroupSetting from '../helpers/group_setting.vue'
-import IntegerSetting from '../helpers/integer_setting.vue'
-import ListSetting from '../helpers/list_setting.vue'
-import MapSetting from '../helpers/map_setting.vue'
-import SharedComputedObject from '../helpers/shared_computed_object.js'
-import StringSetting from '../helpers/string_setting.vue'
-import TupleSetting from '../helpers/tuple_setting.vue'
-
-import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
-
-const AuthTab = {
- provide() {
- return {
- defaultDraftMode: true,
- defaultSource: 'admin',
- }
- },
- components: {
- BooleanSetting,
- ChoiceSetting,
- IntegerSetting,
- StringSetting,
- TupleSetting,
- AttachmentSetting,
- GroupSetting,
- ListSetting,
- MapSetting,
- },
- computed: {
- ...SharedComputedObject(),
- LDAPEnabled() {
- return useAdminSettingsStore().draft[':pleroma'][':ldap'][':enabled']
- },
- },
-}
-
-export default AuthTab
diff --git a/src/components/settings_modal/admin_tabs/auth_tab.vue b/src/components/settings_modal/admin_tabs/auth_tab.vue
deleted file mode 100644
index ef32aa4e0..000000000
--- a/src/components/settings_modal/admin_tabs/auth_tab.vue
+++ /dev/null
@@ -1,105 +0,0 @@
-
-
-
-
{{ $t('admin_dash.auth.MFA') }}
-
-
- {{ $t('admin_dash.auth.TOTP') }}
-
-
-
- {{ $t('admin_dash.auth.backup_codes') }}
-
-
-
-
-
{{ $t('admin_dash.auth.OAuth') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{{ $t('admin_dash.auth.LDAP') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js
index 56361587d..7f575bcb4 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.js
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.js
@@ -1,30 +1,14 @@
-import Checkbox from 'components/checkbox/checkbox.vue'
-import Popover from 'components/popover/popover.vue'
-import Select from 'components/select/select.vue'
-import StillImage from 'components/still-image/still-image.vue'
-import { clone } from 'lodash'
-import { defineAsyncComponent } from 'vue'
-
+import { clone, assign } from 'lodash'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
-import EmojiEditingPopover from '../helpers/emoji_editing_popover.vue'
-import ModifiedIndicator from '../helpers/modified_indicator.vue'
-import SharedComputedObject from '../helpers/shared_computed_object.js'
import StringSetting from '../helpers/string_setting.vue'
-
-import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
-import { useEmojiStore } from 'src/stores/emoji.js'
-import { useInstanceStore } from 'src/stores/instance.js'
-import { useInterfaceStore } from 'src/stores/interface.js'
-
-import { library } from '@fortawesome/fontawesome-svg-core'
-import {
- faArrowsRotate,
- faDownload,
- faFolderOpen,
- faServer,
-} from '@fortawesome/free-solid-svg-icons'
-
-library.add(faArrowsRotate, faFolderOpen, faDownload, faServer)
+import Checkbox from 'components/checkbox/checkbox.vue'
+import StillImage from 'components/still-image/still-image.vue'
+import Select from 'components/select/select.vue'
+import Popover from 'components/popover/popover.vue'
+import ConfirmModal from 'components/confirm_modal/confirm_modal.vue'
+import ModifiedIndicator from '../helpers/modified_indicator.vue'
+import EmojiEditingPopover from '../helpers/emoji_editing_popover.vue'
+import { useInterfaceStore } from 'src/stores/interface'
const EmojiTab = {
components: {
@@ -34,182 +18,183 @@ const EmojiTab = {
StillImage,
Select,
Popover,
- ConfirmModal: defineAsyncComponent(
- () => import('src/components/confirm_modal/confirm_modal.vue'),
- ),
-
+ ConfirmModal,
ModifiedIndicator,
- EmojiEditingPopover,
+ EmojiEditingPopover
},
- data() {
+ data () {
return {
- knownLocalPacks: {},
- knownRemotePacks: {},
- editedMetadata: {},
+ knownLocalPacks: { },
+ knownRemotePacks: { },
+ editedMetadata: { },
packName: '',
newPackName: '',
deleteModalVisible: false,
remotePackInstance: '',
- remotePackDownloadAs: '',
-
- remotePackURL: '',
- remotePackFile: null,
+ remotePackDownloadAs: ''
}
},
- provide() {
+ provide () {
return { emojiAddr: this.emojiAddr }
},
computed: {
- ...SharedComputedObject(),
- pack() {
+ pack () {
return this.packName !== '' ? this.knownPacks[this.packName] : undefined
},
- packMeta() {
- if (this.packName === '') return {}
+ packMeta () {
if (this.editedMetadata[this.packName] === undefined) {
this.editedMetadata[this.packName] = clone(this.pack.pack)
}
return this.editedMetadata[this.packName]
},
- knownPacks() {
+ knownPacks () {
// Copy the object itself but not the children, so they are still passed by reference and modified
const result = clone(this.knownLocalPacks)
for (const instName in this.knownRemotePacks) {
for (const instPack in this.knownRemotePacks[instName]) {
- result[`${instPack}@${instName}`] =
- this.knownRemotePacks[instName][instPack]
+ result[`${instPack}@${instName}`] = this.knownRemotePacks[instName][instPack]
}
}
return result
},
- downloadWillReplaceLocal() {
- return (
- (this.remotePackDownloadAs.trim() === '' &&
- this.pack.remote &&
- this.pack.remote.baseName in this.knownLocalPacks) ||
- this.remotePackDownloadAs in this.knownLocalPacks
- )
- },
+ downloadWillReplaceLocal () {
+ return (this.remotePackDownloadAs.trim() === '' && this.pack.remote && this.pack.remote.baseName in this.knownLocalPacks) ||
+ (this.remotePackDownloadAs in this.knownLocalPacks)
+ }
},
methods: {
- reloadEmoji() {
- useAdminSettingsStore().reloadEmoji()
+ reloadEmoji () {
+ this.$store.state.api.backendInteractor.reloadEmoji()
},
- importFromFS() {
- useAdminSettingsStore().importEmojiFromFS()
+ importFromFS () {
+ this.$store.state.api.backendInteractor.importEmojiFromFS()
},
- emojiAddr(name) {
+ emojiAddr (name) {
if (this.pack.remote !== undefined) {
// Remote pack
return `${this.pack.remote.instance}/emoji/${encodeURIComponent(this.pack.remote.baseName)}/${name}`
} else {
- return `${useInstanceStore().server}/emoji/${encodeURIComponent(this.packName)}/${name}`
+ return `${this.$store.state.instance.server}/emoji/${encodeURIComponent(this.packName)}/${name}`
}
},
- createEmojiPack() {
- useAdminSettingsStore()
- .createEmojiPack({ name: this.newPackName })
- .then((resp) => resp.json())
- .then((resp) => {
- if (resp === 'ok') {
- return this.refreshPackList()
- } else {
- this.displayError(resp.error)
- return Promise.reject(resp)
- }
- })
- .then(() => {
- this.packName = this.newPackName
- this.newPackName = ''
- })
- },
- deleteEmojiPack() {
- useAdminSettingsStore()
- .deleteEmojiPack({ name: this.packName })
- .then((resp) => resp.json())
- .then((resp) => {
- if (resp === 'ok') {
- return this.refreshPackList()
- } else {
- this.displayError(resp.error)
- return Promise.reject(resp)
- }
- })
- .then(() => {
- delete this.editedMetadata[this.packName]
+ createEmojiPack () {
+ this.$store.state.api.backendInteractor.createEmojiPack(
+ { name: this.newPackName }
+ ).then(resp => resp.json()).then(resp => {
+ if (resp === 'ok') {
+ return this.refreshPackList()
+ } else {
+ this.displayError(resp.error)
+ return Promise.reject(resp)
+ }
+ }).then(() => {
+ this.$refs.createPackPopover.hidePopover()
- this.deleteModalVisible = false
- this.packName = ''
- })
+ this.packName = this.newPackName
+ this.newPackName = ''
+ })
+ },
+ deleteEmojiPack () {
+ this.$store.state.api.backendInteractor.deleteEmojiPack(
+ { name: this.packName }
+ ).then(resp => resp.json()).then(resp => {
+ if (resp === 'ok') {
+ return this.refreshPackList()
+ } else {
+ this.displayError(resp.error)
+ return Promise.reject(resp)
+ }
+ }).then(() => {
+ delete this.editedMetadata[this.packName]
+
+ this.deleteModalVisible = false
+ this.packName = ''
+ })
},
- metaEdited(prop) {
+ metaEdited (prop) {
if (!this.pack) return
const def = this.pack.pack[prop] || ''
const edited = this.packMeta[prop] || ''
return edited !== def
},
- savePackMetadata() {
- useAdminSettingsStore()
- .saveEmojiPackMetadata({ name: this.packName, newData: this.packMeta })
- .then((resp) => resp.json())
- .then((resp) => {
- if (resp.error !== undefined) {
- this.displayError(resp.error)
- return
+ savePackMetadata () {
+ this.$store.state.api.backendInteractor.saveEmojiPackMetadata({ name: this.packName, newData: this.packMeta }).then(
+ resp => resp.json()
+ ).then(resp => {
+ if (resp.error !== undefined) {
+ this.displayError(resp.error)
+ return
+ }
+
+ // Update actual pack data
+ this.pack.pack = resp
+ // Delete edited pack data, should auto-update itself
+ delete this.editedMetadata[this.packName]
+ })
+ },
+
+ updatePackFiles (newFiles) {
+ this.pack.files = newFiles
+ this.sortPackFiles(this.packName)
+ },
+
+ loadPacksPaginated (listFunction) {
+ const pageSize = 25
+ const allPacks = {}
+
+ return listFunction({ instance: this.remotePackInstance, page: 1, pageSize: 0 })
+ .then(data => data.json())
+ .then(data => {
+ if (data.error !== undefined) { return Promise.reject(data.error) }
+
+ let resultingPromise = Promise.resolve({})
+ for (let i = 0; i < Math.ceil(data.count / pageSize); i++) {
+ resultingPromise = resultingPromise.then(() => listFunction({ instance: this.remotePackInstance, page: i, pageSize })
+ ).then(data => data.json()).then(pageData => {
+ if (pageData.error !== undefined) { return Promise.reject(pageData.error) }
+
+ assign(allPacks, pageData.packs)
+ })
}
- // Update actual pack data
- this.pack.pack = resp
- // Delete edited pack data, should auto-update itself
- delete this.editedMetadata[this.packName]
+ return resultingPromise
+ })
+ .then(() => allPacks)
+ .catch(data => {
+ this.displayError(data)
})
},
- updatePackFiles(newFiles, packName) {
- this.knownPacks[packName].files = newFiles
- this.sortPackFiles(packName)
- },
-
- refreshPackList() {
- useEmojiStore()
- .getAdminPacks(
- this.remotePackInstance,
- useAdminSettingsStore().listEmojiPacks,
- )
- .then((allPacks) => {
+ refreshPackList () {
+ this.loadPacksPaginated(this.$store.state.api.backendInteractor.listEmojiPacks)
+ .then(allPacks => {
this.knownLocalPacks = allPacks
for (const name of Object.keys(this.knownLocalPacks)) {
this.sortPackFiles(name)
}
})
},
- listRemotePacks() {
- useEmojiStore()
- .getAdminPacks(
- this.remotePackInstance,
- useAdminSettingsStore().listRemoteEmojiPacks,
- )
- .then((allPacks) => {
+ listRemotePacks () {
+ this.loadPacksPaginated(this.$store.state.api.backendInteractor.listRemoteEmojiPacks)
+ .then(allPacks => {
let inst = this.remotePackInstance
- if (!inst.startsWith('http')) {
- inst = 'https://' + inst
- }
+ if (!inst.startsWith('http')) { inst = 'https://' + inst }
const instUrl = new URL(inst)
inst = instUrl.host
for (const packName in allPacks) {
allPacks[packName].remote = {
baseName: packName,
- instance: instUrl.origin,
+ instance: instUrl.origin
}
}
@@ -217,102 +202,57 @@ const EmojiTab = {
for (const pack in this.knownRemotePacks[inst]) {
this.sortPackFiles(`${pack}@${inst}`)
}
+
+ this.$refs.remotePackPopover.hidePopover()
})
- .catch((data) => {
+ .catch(data => {
this.displayError(data)
})
},
- downloadRemotePack() {
+ downloadRemotePack () {
if (this.remotePackDownloadAs.trim() === '') {
this.remotePackDownloadAs = this.pack.remote.baseName
}
- useAdminSettingsStore()
- .downloadRemoteEmojiPack({
- instance: this.pack.remote.instance,
- packName: this.pack.remote.baseName,
- as: this.remotePackDownloadAs,
- })
- .then((data) => data.json())
- .then((resp) => {
+ this.$store.state.api.backendInteractor.downloadRemoteEmojiPack({
+ instance: this.pack.remote.instance, packName: this.pack.remote.baseName, as: this.remotePackDownloadAs
+ })
+ .then(data => data.json())
+ .then(resp => {
if (resp === 'ok') {
+ this.$refs.dlPackPopover.hidePopover()
+
return this.refreshPackList()
} else {
this.displayError(resp.error)
return Promise.reject(resp)
}
- })
- .then(() => {
+ }).then(() => {
this.packName = this.remotePackDownloadAs
this.remotePackDownloadAs = ''
})
},
- downloadRemoteURLPack() {
- useAdminSettingsStore()
- .downloadRemoteEmojiPackZIP({
- url: this.remotePackURL,
- packName: this.newPackName,
- })
- .then((data) => data.json())
- .then((resp) => {
- if (resp === 'ok') {
- return this.refreshPackList()
- } else {
- this.displayError(resp.error)
- return Promise.reject(resp)
- }
- })
- .then(() => {
- this.packName = this.newPackName
- this.newPackName = ''
- this.remotePackURL = ''
- })
- },
- downloadRemoteFilePack() {
- useAdminSettingsStore()
- .downloadRemoteEmojiPackZIP({
- file: this.remotePackFile[0],
- packName: this.newPackName,
- })
- .then((data) => data.json())
- .then((resp) => {
- if (resp === 'ok') {
- return this.refreshPackList()
- } else {
- this.displayError(resp.error)
- return Promise.reject(resp)
- }
- })
- .then(() => {
- this.packName = this.newPackName
- this.newPackName = ''
- this.remotePackURL = ''
- })
- },
-
- displayError(msg) {
+ displayError (msg) {
useInterfaceStore().pushGlobalNotice({
messageKey: 'admin_dash.emoji.error',
messageArgs: [msg],
- level: 'error',
+ level: 'error'
})
},
- sortPackFiles(nameOfPack) {
+ sortPackFiles (nameOfPack) {
// Sort by key
- const sorted = Object.keys(this.knownPacks[nameOfPack].files)
- .sort()
- .reduce((acc, key) => {
- if (key.length === 0) return acc
- acc[key] = this.knownPacks[nameOfPack].files[key]
- return acc
- }, {})
+ const sorted = Object.keys(this.knownPacks[nameOfPack].files).sort().reduce((acc, key) => {
+ if (key.length === 0) return acc
+ acc[key] = this.knownPacks[nameOfPack].files[key]
+ return acc
+ }, {})
this.knownPacks[nameOfPack].files = sorted
- },
+ }
},
- mounted() {
+ mounted () {
this.refreshPackList()
- },
+ }
}
export default EmojiTab
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.scss b/src/components/settings_modal/admin_tabs/emoji_tab.scss
index fd5b53caa..eefada63a 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.scss
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.scss
@@ -1,59 +1,10 @@
-.EmojiTab {
- .setting-list {
- margin: 0.5em 2em;
- }
-
- .setting-section {
+.emoji-tab {
+ .btn-group .btn:not(:first-child) {
margin-left: 0.5em;
- margin-right: 0.5em;
}
- .toolbar {
- display: flex;
- flex-wrap: wrap;
- gap: 0.5em;
-
- .header-buttons {
- display: flex;
- flex: 1 0 auto;
- justify-content: end;
- align-items: end;
-
- &:not(.btn-group) {
- gap: 0.5em;
- }
-
- .header-text {
- flex: 1 0 auto;
- }
- }
-
- .button-default {
- flex: 0 0 auto;
- padding: 0.5em;
- font-size: 1rem;
- }
-
- .popover-wrapper {
- display: flex;
- }
- }
-
- .selector-buttons,
- .meta-buttons {
- display: flex;
- flex-wrap: wrap;
- gap: 0.5em;
- margin-left: 1em;
- }
-
- h5 {
+ .pack-info-wrapper {
margin-top: 1em;
- margin-bottom: 0.25em;
- }
-
- h3.toolbar {
- align-items: end;
}
.emoji-info-input {
@@ -67,8 +18,8 @@
}
.emoji {
- width: 2em;
- height: 2em;
+ width: 32px;
+ height: 32px;
}
.emoji-unsaved {
@@ -79,22 +30,6 @@
display: flex;
flex-wrap: wrap;
gap: 1em;
-
- .button-unstyled {
- display: flex;
- }
-
- .emoji-item,
- .placeholder {
- width: 2em;
- height: 2em;
- opacity: 0.5;
- }
-
- .placeholder {
- background: var(--textFaint);
- border-radius: 0.5em;
- }
}
}
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
index b118e0c52..4bf64c921 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.vue
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -1,71 +1,110 @@