Merge branch 'weight-removal' into shigusegubu-themes3
This commit is contained in:
commit
f8ba217f0c
55 changed files with 2354 additions and 4109 deletions
223
.gitlab-ci.yml
223
.gitlab-ci.yml
|
|
@ -1,223 +0,0 @@
|
|||
# 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
|
||||
|
||||
stages:
|
||||
- check-changelog
|
||||
- lint
|
||||
- build
|
||||
- test
|
||||
- deploy
|
||||
|
||||
# https://git.pleroma.social/help/ci/yaml/workflow.md#switch-between-branch-pipelines-and-merge-request-pipelines
|
||||
workflow:
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
|
||||
check-changelog:
|
||||
stage: check-changelog
|
||||
image: alpine
|
||||
rules:
|
||||
- if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma-fe' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^renovate/
|
||||
when: never
|
||||
- if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma-fe' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == 'weblate'
|
||||
when: never
|
||||
- if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"
|
||||
before_script: ''
|
||||
after_script: ''
|
||||
cache: {}
|
||||
script:
|
||||
- apk add git
|
||||
- sh ./tools/check-changelog
|
||||
|
||||
lint-eslint:
|
||||
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
|
||||
|
||||
test:
|
||||
stage: test
|
||||
tags:
|
||||
- amd64
|
||||
- himem
|
||||
variables:
|
||||
APT_CACHE_DIR: apt-cache
|
||||
script:
|
||||
- mkdir -pv $APT_CACHE_DIR && apt-get -qq update
|
||||
- yarn
|
||||
- yarn playwright install firefox
|
||||
- yarn playwright install-deps
|
||||
- yarn unit-ci
|
||||
artifacts:
|
||||
# When the test fails, upload screenshots for better context on why it fails
|
||||
paths:
|
||||
- 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 <<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 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
|
||||
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:
|
||||
- amd64
|
||||
- himem
|
||||
script:
|
||||
- yarn
|
||||
- yarn build
|
||||
artifacts:
|
||||
paths:
|
||||
- dist/
|
||||
|
||||
docs-deploy:
|
||||
stage: deploy
|
||||
image: alpine:latest
|
||||
only:
|
||||
- develop@pleroma/pleroma-fe
|
||||
before_script:
|
||||
- apk add curl
|
||||
script:
|
||||
- curl -X POST -F"token=$DOCS_PIPELINE_TRIGGER" -F'ref=master' https://git.pleroma.social/api/v4/projects/673/trigger/pipeline
|
||||
|
|
@ -14,7 +14,7 @@ labels:
|
|||
|
||||
steps:
|
||||
build:
|
||||
image: docker.io/node:20-alpine
|
||||
image: docker.io/node:26-alpine
|
||||
commands:
|
||||
- apk add --no-cache zip git
|
||||
- yarn --frozen-lockfile
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ labels:
|
|||
|
||||
steps:
|
||||
build:
|
||||
image: docker.io/node:20-alpine
|
||||
image: docker.io/node:26-alpine
|
||||
commands:
|
||||
- yarn --frozen-lockfile
|
||||
- yarn build
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ when:
|
|||
steps:
|
||||
install-depends:
|
||||
image: &node-image
|
||||
docker.io/node:20-alpine
|
||||
docker.io/node:26-alpine
|
||||
commands:
|
||||
- yarn --frozen-lockfile
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ In 99% cases PleromaFE uses [MastoAPI](https://docs.joinmastodon.org/api/) with
|
|||
|
||||
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.
|
||||
|
||||
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.
|
||||
For most part, PleromaFE tries to store all the info it can get in global pinia 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.
|
||||
|
||||
PleromaFE also tries to persist this store, however only stable data is stored, such as user authentication and preferences, user highlights. Persistence is performed by saving and loading chunk of vuex store in browser's LocalStorage/IndexedDB.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,15 @@
|
|||
import js from '@eslint/js'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
import vue from 'eslint-plugin-vue'
|
||||
import globals from 'globals'
|
||||
|
||||
export default defineConfig([
|
||||
...vue.configs['flat/recommended'],
|
||||
globalIgnores(['**/*.js', 'build/', 'dist/', 'config/']),
|
||||
{
|
||||
files: ['src/**/*.vue'],
|
||||
plugins: { js },
|
||||
extends: ['js/recommended'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2024,
|
||||
sourceType: 'module',
|
||||
|
||||
parserOptions: {
|
||||
parser: '@babel/eslint-parser',
|
||||
},
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.vitest,
|
||||
...globals.chai,
|
||||
...globals.commonjs,
|
||||
...globals.serviceworker,
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
|
|
|
|||
68
package.json
68
package.json
|
|
@ -21,7 +21,7 @@
|
|||
"lint-fix": "yarn exec eslint -- --fix; yarn exec stylelint '**/*.scss' '**/*.vue' --fix; biome check --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.28.4",
|
||||
"@babel/runtime": "^8.0.0",
|
||||
"@chenfengyuan/vue-qrcode": "2.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "7.1.0",
|
||||
"@fortawesome/free-regular-svg-icons": "7.1.0",
|
||||
|
|
@ -38,79 +38,58 @@
|
|||
"click-outside-vue3": "4.0.1",
|
||||
"cropperjs": "2.0.1",
|
||||
"escape-html": "1.0.3",
|
||||
"globals": "^16.0.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"js-cookie": "3.0.5",
|
||||
"localforage": "1.10.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"parse-link-header": "2.0.0",
|
||||
"phoenix": "1.8.1",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia": "4.0.3",
|
||||
"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-i18n": "11",
|
||||
"vue-router": "4.6.4",
|
||||
"vue-virtual-scroller": "^2.0.0-beta.7",
|
||||
"vuex": "4.1.0"
|
||||
"vue": "3.5.42",
|
||||
"vue-i18n": "11.4.0",
|
||||
"vue-router": "5.3.1",
|
||||
"vue-virtual-scroller": "^3.0.5"
|
||||
},
|
||||
"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",
|
||||
"@babel/core": "^8.0.0",
|
||||
"@babel/eslint-parser": "^8.0.0",
|
||||
"@babel/plugin-transform-runtime": "^8.0.0",
|
||||
"@babel/preset-env": "^8.0.0",
|
||||
"@babel/register": "^8.0.0",
|
||||
"@biomejs/biome": "2.5.11",
|
||||
"@pinia/testing": "1.0.3",
|
||||
"@ungap/event-target": "0.2.4",
|
||||
"@vitejs/devtools": "^0.3.1",
|
||||
"@vitejs/plugin-vue": "^6.0.7",
|
||||
"@vitejs/plugin-vue": "^6.0.8",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.5",
|
||||
"@vitest/browser": "^4.1.7",
|
||||
"@vitest/browser-playwright": "^4.1.7",
|
||||
"@vitest/coverage-v8": "^4.1.10",
|
||||
"@vitest/ui": "^4.1.7",
|
||||
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
|
||||
"@vue/babel-plugin-jsx": "1.5.0",
|
||||
"@vitest/browser": "^4.1.11",
|
||||
"@vitest/browser-playwright": "^4.1.11",
|
||||
"@vitest/coverage-v8": "^4.1.11",
|
||||
"@vitest/ui": "^4.1.11",
|
||||
"@vue/babel-plugin-jsx": "3.0.0",
|
||||
"@vue/compiler-sfc": "3.5.22",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"@vue/devtools-api": "^8.1.5",
|
||||
"@vue/test-utils": "2.5.0",
|
||||
"autoprefixer": "10.4.21",
|
||||
"chai": "5.3.3",
|
||||
"chalk": "5.6.2",
|
||||
"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-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-promise": "7.2.1",
|
||||
"eslint-plugin-vue": "10.6.2",
|
||||
"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-es": "4.17.21",
|
||||
"msw": "2.14.6",
|
||||
"nightwatch": "3.12.2",
|
||||
"oxc": "^1.0.1",
|
||||
"playwright": "1.61.0",
|
||||
"postcss": "8.5.6",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-scss": "^4.0.6",
|
||||
"sass-embedded": "^1.100.0",
|
||||
"selenium-server": "3.141.59",
|
||||
"semver": "7.7.3",
|
||||
"serve-static": "2.2.0",
|
||||
"shelljs": "0.10.0",
|
||||
"sinon": "20.0.0",
|
||||
"sinon-chai": "4.0.1",
|
||||
"stylelint": "16.25.0",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-recommended": "^16.0.0",
|
||||
|
|
@ -120,12 +99,13 @@
|
|||
"vite": "^8.0.0",
|
||||
"vite-plugin-eslint2": "^5.1.0",
|
||||
"vite-plugin-stylelint": "^6.1.0",
|
||||
"vitest": "^4.1.7",
|
||||
"vite-plugin-vue-devtools": "^8.2.1",
|
||||
"vitest": "^4.1.11",
|
||||
"vue-eslint-parser": "10.2.0"
|
||||
},
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">= 16.0.0"
|
||||
"node": ">= 26.0.0"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,11 @@ 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 = ({ blockFromStrangers, hideNotificationContents }) =>
|
||||
`/api/pleroma/notification_settings${paramsString({ blockFromStrangers, hideNotificationContents })}`
|
||||
const NOTIFICATION_SETTINGS_URL = ({
|
||||
blockFromStrangers,
|
||||
hideNotificationContents,
|
||||
}) =>
|
||||
`/api/pleroma/notification_settings${paramsString({ blockFromStrangers, hideNotificationContents })}`
|
||||
export const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
|
||||
|
||||
const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa'
|
||||
|
|
|
|||
|
|
@ -71,7 +71,6 @@ const chatNew = {
|
|||
|
||||
this.loading = true
|
||||
this.userIds = []
|
||||
this.$store
|
||||
useSearchStore()
|
||||
.search({ q: query, resolve: true, type: 'accounts' })
|
||||
.then((data) => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
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 StatusContent from 'src/components/status_content/status_content.vue'
|
||||
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useDraftsStore } from 'src/stores/drafts.js'
|
||||
import { useStatusesStore } from 'src/stores/statuses.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
|
|
@ -33,7 +32,6 @@ const Draft = {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
referenceDraft: cloneDeep(this.draft),
|
||||
editing: false,
|
||||
showingConfirmDialog: false,
|
||||
}
|
||||
|
|
@ -50,14 +48,6 @@ const Draft = {
|
|||
return {}
|
||||
}
|
||||
},
|
||||
safeToSave() {
|
||||
return (
|
||||
this.draft.status ||
|
||||
this.draft.files?.length ||
|
||||
this.draft.hasPoll ||
|
||||
this.draft.hasQuote
|
||||
)
|
||||
},
|
||||
postStatusFormProps() {
|
||||
return {
|
||||
draftId: this.draft.id,
|
||||
|
|
@ -69,18 +59,12 @@ const Draft = {
|
|||
? useStatusesStore().allStatuses.get(this.draft.refId)
|
||||
: undefined
|
||||
},
|
||||
localCollapseSubjectDefault() {
|
||||
return useMergedConfigStore().mergedConfig.collapseMessageWithSubject
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
editing(newVal) {
|
||||
if (newVal) return
|
||||
if (this.safeToSave) {
|
||||
this.$store.dispatch('addOrSaveDraft', { draft: this.draft })
|
||||
} else {
|
||||
this.$store.dispatch('addOrSaveDraft', { draft: this.referenceDraft })
|
||||
}
|
||||
// (Post|Edit)StatusForm handles draft saving
|
||||
this.$refs.form.saveDraft()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -91,9 +75,11 @@ const Draft = {
|
|||
this.showingConfirmDialog = true
|
||||
},
|
||||
doAbandon() {
|
||||
this.$store.dispatch('abandonDraft', { id: this.draft.id }).then(() => {
|
||||
this.hideConfirmDialog()
|
||||
})
|
||||
useDraftsStore()
|
||||
.abandonDraft(this.draft.id)
|
||||
.then(() => {
|
||||
this.hideConfirmDialog()
|
||||
})
|
||||
},
|
||||
hideConfirmDialog() {
|
||||
this.showingConfirmDialog = false
|
||||
|
|
|
|||
|
|
@ -67,11 +67,13 @@
|
|||
<div v-if="editing">
|
||||
<PostStatusForm
|
||||
v-if="draft.type !== 'edit'"
|
||||
ref="form"
|
||||
:hide-draft="true"
|
||||
v-bind="postStatusFormProps"
|
||||
/>
|
||||
<EditStatusForm
|
||||
v-else
|
||||
ref="form"
|
||||
:hide-draft="true"
|
||||
:params="postStatusFormProps"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import { defineAsyncComponent } from 'vue'
|
|||
import Draft from 'src/components/draft/draft.vue'
|
||||
import List from 'src/components/list/list.vue'
|
||||
|
||||
import { useDraftsStore } from 'src/stores/drafts.js'
|
||||
|
||||
const Drafts = {
|
||||
components: {
|
||||
Draft,
|
||||
|
|
@ -18,7 +20,7 @@ const Drafts = {
|
|||
},
|
||||
computed: {
|
||||
drafts() {
|
||||
return this.$store.getters.draftsArray
|
||||
return useDraftsStore().draftsArray
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -26,8 +28,8 @@ const Drafts = {
|
|||
this.showingConfirmDialog = true
|
||||
},
|
||||
doAbandonAll() {
|
||||
this.$store
|
||||
.dispatch('abandonAllDrafts')
|
||||
useDraftsStore()
|
||||
.abandonAllDrafts()
|
||||
.then(() => this.hideConfirmDialog())
|
||||
},
|
||||
hideConfirmDialog() {
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@ const EditStatusForm = {
|
|||
requestClose() {
|
||||
this.$refs.postStatusForm.requestClose()
|
||||
},
|
||||
saveDraft() {
|
||||
this.$refs.postStatusForm.saveDraft()
|
||||
},
|
||||
doEditStatus({ status, spoilerText, sensitive, media, contentType, poll }) {
|
||||
const params = {
|
||||
store: this.$store,
|
||||
statusId: this.params.statusId,
|
||||
status,
|
||||
spoilerText,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const ExtraNotifications = {
|
|||
return (
|
||||
this.mergedConfig.showExtraNotifications &&
|
||||
this.mergedConfig.showAnnouncementsInExtraNotifications &&
|
||||
this.unreadAnnouncementCount
|
||||
this.unreadAnnouncementsCount
|
||||
)
|
||||
},
|
||||
shouldShowFollowRequests() {
|
||||
|
|
@ -56,7 +56,7 @@ const ExtraNotifications = {
|
|||
return useUsersStore().currentUser
|
||||
},
|
||||
...mapState(useAnnouncementsStore, {
|
||||
unreadAnnouncementCount: 'unreadAnnouncementCount',
|
||||
unreadAnnouncementsCount: 'unreadAnnouncementsCount',
|
||||
}),
|
||||
...mapState(useMergedConfigStore, ['mergedConfig']),
|
||||
...mapState(useChatsStore, ['unreadChatsCount']),
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
class="fa-scale-110 icon"
|
||||
icon="bullhorn"
|
||||
/>
|
||||
{{ $t('notifications.unread_announcements', { num: unreadAnnouncementCount }, unreadAnnouncementCount) }}
|
||||
{{ $t('notifications.unread_announcements', { num: unreadAnnouncementsCount }, unreadAnnouncementsCount) }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -122,7 +122,6 @@ const mediaUpload = {
|
|||
},
|
||||
async uploadFile(file) {
|
||||
const self = this
|
||||
const store = this.$store
|
||||
if (file.size > useInstanceStore().uploadlimit) {
|
||||
const filesize = fileSizeFormatService.fileSizeFormat(file.size)
|
||||
const allowedsize = fileSizeFormatService.fileSizeFormat(
|
||||
|
|
@ -145,7 +144,7 @@ const mediaUpload = {
|
|||
self.$emit('uploading')
|
||||
self.uploadCount++
|
||||
|
||||
statusPosterService.uploadMedia({ store, formData }).then(
|
||||
statusPosterService.uploadMedia({ formData }).then(
|
||||
(fileData) => {
|
||||
self.$emit('uploaded', fileData)
|
||||
self.decreaseUploadCount()
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ const MobileNav = {
|
|||
countExtraNotifications(
|
||||
useMergedConfigStore().mergedConfig,
|
||||
useChatsStore().unreadChatsCount,
|
||||
useAnnouncementsStore().unreadAnnouncementCount,
|
||||
useAnnouncementsStore().unreadAnnouncementsCount,
|
||||
useFollowRequestsStore().followRequestsCount,
|
||||
)
|
||||
)
|
||||
|
|
@ -96,7 +96,7 @@ const MobileNav = {
|
|||
closingDrawerMarksAsSeen() {
|
||||
return useMergedConfigStore().mergedConfig.closingDrawerMarksAsSeen
|
||||
},
|
||||
...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
|
||||
...mapState(useAnnouncementsStore, ['unreadAnnouncementsCount']),
|
||||
...mapState(useMergedConfigStore, {
|
||||
pinnedItems: (store) =>
|
||||
new Set(store.prefsStorage.collections.pinnedNavItems).has('chats'),
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
icon="bars"
|
||||
/>
|
||||
<div
|
||||
v-if="(unreadChatsCount && !chatsPinned) || unreadAnnouncementCount"
|
||||
v-if="(unreadChatsCount && !chatsPinned) || unreadAnnouncementsCount"
|
||||
class="badge -dot -notification"
|
||||
/>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ const NavPanel = {
|
|||
},
|
||||
computed: {
|
||||
...mapState(useAnnouncementsStore, {
|
||||
unreadAnnouncementCount: 'unreadAnnouncementCount',
|
||||
unreadAnnouncementsCount: 'unreadAnnouncementsCount',
|
||||
supportsAnnouncements: (store) => store.supportsAnnouncements,
|
||||
}),
|
||||
...mapState(useInstanceCapabilitiesStore, [
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export const ROOT_ITEMS = {
|
|||
icon: 'comments',
|
||||
label: 'nav.chats',
|
||||
badgeStyle: 'notification',
|
||||
badgeGetter: 'unreadChatsCount',
|
||||
badgeGetter: 'unreadChats',
|
||||
criteria: ['chats'],
|
||||
},
|
||||
friendRequests: {
|
||||
|
|
@ -85,7 +85,7 @@ export const ROOT_ITEMS = {
|
|||
label: 'nav.friend_requests',
|
||||
badgeStyle: 'notification',
|
||||
criteria: ['lockedUser'],
|
||||
badgeGetter: 'followRequestsCount',
|
||||
badgeGetter: 'followRequests',
|
||||
},
|
||||
about: {
|
||||
route: 'about',
|
||||
|
|
@ -99,7 +99,7 @@ export const ROOT_ITEMS = {
|
|||
label: 'nav.announcements',
|
||||
store: 'announcements',
|
||||
badgeStyle: 'notification',
|
||||
badgeGetter: 'unreadAnnouncementCount',
|
||||
badgeGetter: 'unreadAnnouncements',
|
||||
criteria: ['announcements'],
|
||||
},
|
||||
drafts: {
|
||||
|
|
@ -107,7 +107,7 @@ export const ROOT_ITEMS = {
|
|||
icon: 'file-pen',
|
||||
label: 'nav.drafts',
|
||||
badgeStyle: 'neutral',
|
||||
badgeGetter: 'draftCount',
|
||||
badgeGetter: 'drafts',
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import { mapState, mapStores } from 'pinia'
|
||||
import { mapState } from 'pinia'
|
||||
|
||||
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 { useChatsStore } from 'src/stores/chats.js'
|
||||
import { useDraftsStore } from 'src/stores/drafts.js'
|
||||
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
|
||||
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||
import { useUsersStore } from 'src/stores/users.js'
|
||||
|
||||
|
|
@ -40,11 +43,19 @@ const NavigationEntry = {
|
|||
routeTo() {
|
||||
return routeTo(this.item, this.currentUser)
|
||||
},
|
||||
getters() {
|
||||
return this.$store.getters
|
||||
badges() {
|
||||
return {
|
||||
drafts: this.draftsCount,
|
||||
unreadAnnouncements: this.unreadAnnouncementsCount,
|
||||
followRequests: this.followRequestsCount,
|
||||
unreadChats: this.unreadChatsCount,
|
||||
}
|
||||
},
|
||||
...mapStores(useAnnouncementsStore),
|
||||
...mapState(useAnnouncementsStore, ['unreadAnnouncementsCount']),
|
||||
...mapState(useDraftsStore, ['draftsCount']),
|
||||
...mapState(useUsersStore, ['currentUser']),
|
||||
...mapState(useChatsStore, ['unreadChatsCount']),
|
||||
...mapState(useFollowRequestsStore, ['followRequestsCount']),
|
||||
...mapState(useSyncConfigStore, {
|
||||
pinnedItems: (store) =>
|
||||
new Set(store.prefsStorage.collections.pinnedNavItems),
|
||||
|
|
|
|||
|
|
@ -47,17 +47,11 @@
|
|||
</component>
|
||||
<slot />
|
||||
<div
|
||||
v-if="item.badgeGetter && getters[item.badgeGetter]"
|
||||
v-if="item.badgeGetter && badges[item.badgeGetter]"
|
||||
class="badge"
|
||||
:class="[`-${item.badgeStyle}`]"
|
||||
>
|
||||
{{ getters[item.badgeGetter] }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="item.badgeGetter && item.store && this[`${item.store}Store`][item.badgeGetter]"
|
||||
class="badge badge-notification"
|
||||
>
|
||||
{{ this[`${item.store}Store`][item.badgeGetter] }}
|
||||
{{ badges[item.badgeGetter] }}
|
||||
</div>
|
||||
<button
|
||||
v-if="showPin && currentUser"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import {
|
|||
|
||||
import { useAnnouncementsStore } from 'src/stores/announcements'
|
||||
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders'
|
||||
import { useChatsStore } from 'src/stores/chats.js'
|
||||
import { useDraftsStore } from 'src/stores/drafts.js'
|
||||
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
|
|
@ -56,18 +58,27 @@ const NavPanel = {
|
|||
},
|
||||
components: {},
|
||||
computed: {
|
||||
getters() {
|
||||
return this.$store.getters
|
||||
badges() {
|
||||
return {
|
||||
drafts: this.draftsCount,
|
||||
unreadAnnouncements: this.unreadAnnouncementsCount,
|
||||
followRequests: this.followRequestsCount,
|
||||
unreadChats: this.unreadChatsCount,
|
||||
}
|
||||
},
|
||||
...mapState(useListsStore, {
|
||||
lists: getListEntries,
|
||||
}),
|
||||
...mapState(useAnnouncementsStore, {
|
||||
supportsAnnouncements: (store) => store.supportsAnnouncements,
|
||||
unreadAnnouncementsCount: 'unreadAnnouncementsCount',
|
||||
}),
|
||||
...mapState(useDraftsStore, ['draftsCount']),
|
||||
...mapState(useFollowRequestsStore, ['followRequestsCount']),
|
||||
...mapState(useBookmarkFoldersStore, {
|
||||
bookmarks: getBookmarkFolderEntries,
|
||||
}),
|
||||
...mapState(useChatsStore, ['unreadChatsCount']),
|
||||
...mapState(useSyncConfigStore, {
|
||||
pinnedItems: (store) =>
|
||||
new Set(store.prefsStorage.collections.pinnedNavItems),
|
||||
|
|
@ -78,7 +89,6 @@ const NavPanel = {
|
|||
'localBubble',
|
||||
]),
|
||||
...mapState(useUsersStore, ['currentUser']),
|
||||
...mapState(useFollowRequestsStore, ['followRequestsCount']),
|
||||
pinnedList() {
|
||||
if (!this.currentUser) {
|
||||
return filterNavigation(
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
:src="item.iconEmojiUrl"
|
||||
/>
|
||||
<div
|
||||
v-if="item.badgeGetter && getters[item.badgeGetter]"
|
||||
v-if="item.badgeGetter && badges[item.badgeGetter]"
|
||||
class="badge -dot"
|
||||
:class="[`-${item.badgeStyle}`]"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
highlightStyle,
|
||||
} from '../../services/user_highlighter/user_highlighter.js'
|
||||
|
||||
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useNotificationsStore } from 'src/stores/notifications.js'
|
||||
|
|
@ -148,8 +149,7 @@ const Notification = {
|
|||
id: this.user.id,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
// TODO Fix this
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
useFollowRequestsStore().remove(this.user.id)
|
||||
useNotificationsStore().markSingleNotificationAsSeen(this.notification.id)
|
||||
this.hideApproveConfirmDialog()
|
||||
},
|
||||
|
|
@ -166,8 +166,7 @@ const Notification = {
|
|||
credentials: useOAuthStore().token,
|
||||
}).then(() => {
|
||||
useNotificationsStore().dismissNotificationLocal(this.notification.id)
|
||||
// TODO Fix this
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
useFollowRequestsStore().remove(this.user.id)
|
||||
})
|
||||
this.hideDenyConfirmDialog()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ const Notifications = {
|
|||
return countExtraNotifications(
|
||||
useMergedConfigStore().mergedConfig,
|
||||
useChatsStore().unreadChatsCount,
|
||||
useAnnouncementsStore().unreadAnnouncementCount,
|
||||
useAnnouncementsStore().unreadAnnouncementsCount,
|
||||
useFollowRequestsStore().followRequestsCount,
|
||||
)
|
||||
},
|
||||
|
|
@ -118,7 +118,7 @@ const Notifications = {
|
|||
return (
|
||||
this.unseenNotifications.length +
|
||||
this.unreadChatsCount +
|
||||
this.unreadAnnouncementCount
|
||||
this.unreadAnnouncementsCount
|
||||
)
|
||||
},
|
||||
loading() {
|
||||
|
|
@ -157,7 +157,7 @@ const Notifications = {
|
|||
showExtraNotifications() {
|
||||
return !this.noExtra
|
||||
},
|
||||
...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
|
||||
...mapState(useAnnouncementsStore, ['unreadAnnouncementsCount']),
|
||||
...mapState(useChatsStore, ['unreadChatsCount']),
|
||||
...mapState(useInterfaceStore, ['layoutType']),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { findOffset } from '../../services/offset_finder/offset_finder.service.j
|
|||
import genRandomSeed from '../../services/random_seed/random_seed.service.js'
|
||||
import statusPoster from '../../services/status_poster/status_poster.service.js'
|
||||
|
||||
import { useDraftsStore } from 'src/stores/drafts.js'
|
||||
import { useEmojiStore } from 'src/stores/emoji.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
|
|
@ -400,8 +401,6 @@ const PostStatusForm = {
|
|||
contentType: this.newStatus.contentType,
|
||||
poll,
|
||||
idempotencyKey: this.idempotencyKey,
|
||||
|
||||
store: this.$store,
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -412,7 +411,6 @@ const PostStatusForm = {
|
|||
...useEmojiStore().standardEmojiList,
|
||||
...useEmojiStore().customEmoji,
|
||||
],
|
||||
store: this.$store,
|
||||
})
|
||||
},
|
||||
emojiSuggestor() {
|
||||
|
|
@ -574,7 +572,7 @@ const PostStatusForm = {
|
|||
...mapState(useUsersStore, ['currentUser']),
|
||||
...mapState(useMergedConfigStore, ['mergedConfig']),
|
||||
...mapState(useInterfaceStore, {
|
||||
mobileLayout: (store) => store.mobileLayout,
|
||||
mobileLayout: (state) => state.mobileLayout,
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -751,7 +749,6 @@ const PostStatusForm = {
|
|||
const description = this.newStatus.mediaDescriptions[id]
|
||||
if (!description || description.trim() === '') return
|
||||
return statusPoster.setMediaDescription({
|
||||
store: this.$store,
|
||||
id,
|
||||
description,
|
||||
})
|
||||
|
|
@ -985,13 +982,13 @@ const PostStatusForm = {
|
|||
saveDraft() {
|
||||
if (!this.disableDraft && !this.saveInhibited) {
|
||||
if (this.safeToSaveDraft) {
|
||||
return this.$store
|
||||
.dispatch('addOrSaveDraft', {
|
||||
draft: {
|
||||
type: this.statusType,
|
||||
refId: this.refId,
|
||||
...this.newStatus,
|
||||
},
|
||||
return useDraftsStore()
|
||||
.addOrSaveDraft({
|
||||
type: this.statusType,
|
||||
refId: this.refId,
|
||||
...this.newStatus,
|
||||
// Draft ID overwrites status ID (which is undefined for fresh statuses)
|
||||
id: this.draftId,
|
||||
})
|
||||
.then((id) => {
|
||||
if (this.newStatus.id !== id) {
|
||||
|
|
@ -1024,14 +1021,14 @@ const PostStatusForm = {
|
|||
}
|
||||
},
|
||||
abandonDraft() {
|
||||
return this.$store.dispatch('abandonDraft', { id: this.draftId })
|
||||
return useDraftsStore().abandonDraft(this.draftId)
|
||||
},
|
||||
getDraft() {
|
||||
const maybeDraft = this.$store.state.drafts.drafts[this.draftId]
|
||||
const maybeDraft = useDraftsStore().drafts.get(this.draftId)
|
||||
if (this.draftId && maybeDraft) {
|
||||
return maybeDraft
|
||||
} else {
|
||||
const existingDrafts = this.$store.getters.draftsByTypeAndRefId(
|
||||
const existingDrafts = useDraftsStore().draftsByTypeAndRefId(
|
||||
this.statusType,
|
||||
this.refId,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import { useAdminSettingsStore } from 'src/stores/admin_settings.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 { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||
import { useProfileConfigStore } from 'src/stores/profile_config.js'
|
||||
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -413,8 +413,8 @@ export default {
|
|||
hardReset() {
|
||||
switch (this.realSource) {
|
||||
case 'admin':
|
||||
return this.$store
|
||||
.dispatch('resetAdminSetting', { path: this.path })
|
||||
return useAdminSettingsStore()
|
||||
.resetAdminSetting({ path: this.path })
|
||||
.then(() => {
|
||||
this.draft = this.state
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
// eslint-disable-next-line no-unused
|
||||
|
||||
import { mapState } from 'pinia'
|
||||
import { Fragment } from 'vue'
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j
|
|||
import { useInterfaceStore } from 'src/stores/interface.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 { useProfileConfigStore } from 'src/stores/profile_config.js'
|
||||
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||
import { useUsersStore } from 'src/stores/users.js'
|
||||
|
||||
import { updateProfile } from 'src/api/user.js'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { mapActions, mapState } from 'pinia'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import { USERNAME_ROUTES } from 'src/components/navigation/navigation.js'
|
||||
import UserCard from 'src/components/user_card/user_card.vue'
|
||||
|
|
@ -8,6 +7,7 @@ import { unseenNotifications } from '../../services/notification_utils/notificat
|
|||
|
||||
import { useAnnouncementsStore } from 'src/stores/announcements'
|
||||
import { useChatsStore } from 'src/stores/chats.js'
|
||||
import { useDraftsStore } from 'src/stores/drafts.js'
|
||||
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
|
|
@ -62,10 +62,6 @@ const SideDrawer = {
|
|||
GestureService.DIRECTION_LEFT,
|
||||
this.toggleDrawer,
|
||||
)
|
||||
|
||||
if (this.currentUser?.locked) {
|
||||
this.$store.dispatch('startFetchingFollowRequests')
|
||||
}
|
||||
},
|
||||
components: {
|
||||
UserCard,
|
||||
|
|
@ -101,7 +97,7 @@ const SideDrawer = {
|
|||
...mapState(useFollowRequestsStore, ['followRequestsCount']),
|
||||
...mapState(useAnnouncementsStore, [
|
||||
'supportsAnnouncements',
|
||||
'unreadAnnouncementCount',
|
||||
'unreadAnnouncementsCount',
|
||||
]),
|
||||
...mapState(useInstanceCapabilitiesStore, [
|
||||
'pleromaChatMessagesAvailable',
|
||||
|
|
@ -114,7 +110,7 @@ const SideDrawer = {
|
|||
hideSitename: (store) => store.instanceIdentity.hideSitename,
|
||||
}),
|
||||
...mapState(useChatsStore, ['unreadChatsCount']),
|
||||
...mapGetters(['draftCount']),
|
||||
...mapState(useDraftsStore, ['draftCount']),
|
||||
},
|
||||
methods: {
|
||||
toggleDrawer() {
|
||||
|
|
|
|||
|
|
@ -248,10 +248,10 @@
|
|||
icon="bullhorn"
|
||||
/> {{ $t("nav.announcements") }}
|
||||
<span
|
||||
v-if="unreadAnnouncementCount"
|
||||
v-if="unreadAnnouncementsCount"
|
||||
class="badge -notification"
|
||||
>
|
||||
{{ unreadAnnouncementCount }}
|
||||
{{ unreadAnnouncementsCount }}
|
||||
</span>
|
||||
</router-link>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -97,9 +97,6 @@ const StatusActionButtons = {
|
|||
replying: this.replying,
|
||||
emojiPickerShown: this.emojiPickerShown,
|
||||
emit: this.$emit,
|
||||
dispatch: this.$store.dispatch,
|
||||
state: this.$store.state,
|
||||
getters: this.$store.getters,
|
||||
router: this.$router,
|
||||
currentUser: this.currentUser,
|
||||
loggedIn: !!this.currentUser,
|
||||
|
|
|
|||
|
|
@ -29,14 +29,13 @@ const StickerPicker = {
|
|||
}
|
||||
},
|
||||
pick(sticker, name) {
|
||||
const store = this.$store
|
||||
// TODO remove this workaround by finding a way to bypass reuploads
|
||||
fetch(sticker).then((res) => {
|
||||
res.blob().then((blob) => {
|
||||
const file = new File([blob], name, { mimetype: 'image/png' })
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
statusPosterService.uploadMedia({ store, formData }).then(
|
||||
statusPosterService.uploadMedia({ formData }).then(
|
||||
(fileData) => {
|
||||
this.$emit('uploaded', fileData)
|
||||
this.clear()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
// eslint-disable-next-line no-unused
|
||||
|
||||
import { Fragment } from 'vue'
|
||||
|
||||
import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome'
|
||||
|
|
|
|||
|
|
@ -418,7 +418,6 @@ export default {
|
|||
...useEmojiStore().standardEmojiList,
|
||||
...useEmojiStore().customEmoji,
|
||||
],
|
||||
store: this.$store,
|
||||
})
|
||||
},
|
||||
emojiSuggestor() {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
import { cloneDeep, each, get, merge, set } from 'lodash'
|
||||
import { cloneDeep, get, set } from 'lodash'
|
||||
|
||||
import { storage } from './storage.js'
|
||||
|
||||
import { useInterfaceStore } from 'src/stores/interface'
|
||||
|
||||
let loaded = false
|
||||
|
||||
const defaultReducer = (state, paths) =>
|
||||
paths.length === 0
|
||||
? state
|
||||
|
|
@ -14,86 +10,10 @@ const defaultReducer = (state, paths) =>
|
|||
return substate
|
||||
}, {})
|
||||
|
||||
const saveImmedeatelyActions = [
|
||||
'markNotificationsAsSeen',
|
||||
'setHighlight',
|
||||
'setOption',
|
||||
'setClientData',
|
||||
'setToken',
|
||||
'clearToken',
|
||||
]
|
||||
|
||||
const defaultStorage = (() => {
|
||||
return storage
|
||||
})()
|
||||
|
||||
export default function createPersistedState({
|
||||
key = 'vuex-lz',
|
||||
paths = [],
|
||||
getState = (key, storage) => {
|
||||
const value = storage.getItem(key)
|
||||
return value
|
||||
},
|
||||
setState = (key, state, storage) => {
|
||||
if (!loaded) {
|
||||
console.info('waiting for old state to be loaded...')
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
return storage.setItem(key, state)
|
||||
}
|
||||
},
|
||||
reducer = defaultReducer,
|
||||
storage = defaultStorage,
|
||||
subscriber = (store) => (handler) => store.subscribe(handler),
|
||||
} = {}) {
|
||||
return getState(key, storage).then((savedState) => {
|
||||
return (store) => {
|
||||
try {
|
||||
if (savedState !== null && typeof savedState === 'object') {
|
||||
// build user cache
|
||||
const usersState = savedState.users || {}
|
||||
usersState.usersObject = {}
|
||||
const users = usersState.users || []
|
||||
each(users, (user) => {
|
||||
usersState.usersObject[user.id] = user
|
||||
})
|
||||
savedState.users = usersState
|
||||
|
||||
store.replaceState(merge({}, store.state, savedState))
|
||||
}
|
||||
loaded = true
|
||||
} catch (e) {
|
||||
console.error("Couldn't load state")
|
||||
console.error(e)
|
||||
loaded = true
|
||||
}
|
||||
subscriber(store)((mutation, state) => {
|
||||
try {
|
||||
if (saveImmedeatelyActions.includes(mutation.type)) {
|
||||
setState(key, reducer(cloneDeep(state), paths), storage).then(
|
||||
(success) => {
|
||||
if (success !== undefined) {
|
||||
if (mutation.type === 'setOption') {
|
||||
useInterfaceStore().settingsSaved({ success })
|
||||
}
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
if (mutation.type === 'setOption') {
|
||||
useInterfaceStore().settingsSaved({ error })
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Couldn't persist state:")
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* This persists state for pinia, which falls back to read from the vuex state
|
||||
* if pinia persisted state does not exist.
|
||||
|
|
|
|||
33
src/main.js
33
src/main.js
|
|
@ -1,9 +1,7 @@
|
|||
/* global process */
|
||||
|
||||
import { createPinia } from 'pinia'
|
||||
import { createStore } from 'vuex'
|
||||
|
||||
import 'custom-event-polyfill'
|
||||
import './lib/event_target_polyfill.js'
|
||||
|
||||
// Polyfill for Array.prototype.toSorted (ES2023)
|
||||
|
|
@ -17,11 +15,8 @@ import { createI18n } from 'vue-i18n'
|
|||
|
||||
import afterStoreSetup from './boot/after_store.js'
|
||||
import messages from './i18n/messages.js'
|
||||
import createPersistedState, {
|
||||
piniaPersistPlugin,
|
||||
} from './lib/persisted_state.js'
|
||||
import { piniaPersistPlugin } from './lib/persisted_state.js'
|
||||
import { piniaPushNotificationsPlugin } from './lib/push_notifications_plugin.js'
|
||||
import vuexModules from './modules/index.js'
|
||||
|
||||
import { piniaLanguagePlugin } from 'src/lib/language.js'
|
||||
import { piniaStylePlugin } from 'src/lib/style.js'
|
||||
|
|
@ -37,10 +32,6 @@ const i18n = createI18n({
|
|||
|
||||
messages.setLanguage(i18n.global, currentLocale)
|
||||
|
||||
const persistedStateOptions = {
|
||||
paths: ['oauth', 'config'],
|
||||
}
|
||||
|
||||
;(async () => {
|
||||
const isFox = Math.floor(Math.random() * 2) > 0 ? '_fox' : ''
|
||||
|
||||
|
|
@ -69,20 +60,12 @@ const persistedStateOptions = {
|
|||
|
||||
try {
|
||||
let storageError
|
||||
const plugins = []
|
||||
const pinia = createPinia()
|
||||
pinia.use(piniaPersistPlugin())
|
||||
pinia.use(piniaLanguagePlugin)
|
||||
pinia.use(piniaStylePlugin)
|
||||
pinia.use(piniaPushNotificationsPlugin)
|
||||
|
||||
try {
|
||||
const persistedState = await createPersistedState(persistedStateOptions)
|
||||
plugins.push(persistedState)
|
||||
} catch (e) {
|
||||
console.error('Storage error', e)
|
||||
storageError = e
|
||||
}
|
||||
document.querySelector('#splash').classList.remove('initial-hidden')
|
||||
document.querySelector('#mascot').src =
|
||||
`/static/pleromatan_apology${isFox}_small.webp`
|
||||
|
|
@ -93,18 +76,8 @@ const persistedStateOptions = {
|
|||
'update.art_by',
|
||||
{ linkToArtist: 'pipivovott' },
|
||||
)
|
||||
const store = createStore({
|
||||
modules: vuexModules,
|
||||
plugins,
|
||||
options: {
|
||||
devtools: process.env.NODE_ENV !== 'production',
|
||||
},
|
||||
strict: false, // Socket modifies itself, let's ignore this for now.
|
||||
// strict: process.env.NODE_ENV !== 'production'
|
||||
})
|
||||
window.vuex = store
|
||||
// Temporarily passing pinia and vuex stores along with storageError result until migration is fully complete.
|
||||
return await afterStoreSetup({ pinia, store, storageError, i18n })
|
||||
// Temporarily passing pinia stores along with storageError result until migration is fully complete.
|
||||
return await afterStoreSetup({ pinia, storageError, i18n })
|
||||
} catch (e) {
|
||||
splashError(i18n, e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
import { storage } from 'src/lib/storage.js'
|
||||
|
||||
export const defaultState = {
|
||||
drafts: {},
|
||||
}
|
||||
|
||||
export const mutations = {
|
||||
addOrSaveDraft(state, { draft }) {
|
||||
state.drafts[draft.id] = draft
|
||||
},
|
||||
abandonDraft(state, { id }) {
|
||||
delete state.drafts[id]
|
||||
},
|
||||
loadDrafts(state, data) {
|
||||
state.drafts = data
|
||||
},
|
||||
}
|
||||
|
||||
const storageKey = 'pleroma-fe-drafts'
|
||||
|
||||
/*
|
||||
* Note: we do not use the persist state plugin because
|
||||
* it is not impossible for a user to have two windows at
|
||||
* the same time. The persist state plugin is just overriding
|
||||
* everything with the current state. This isn't good because
|
||||
* if a draft is created in one window and another draft is
|
||||
* created in another, the draft in the first window will just
|
||||
* be overriden.
|
||||
* Here, we can't guarantee 100% atomicity unless one uses
|
||||
* different keys, which will just pollute the whole storage.
|
||||
* It is indeed best to have backend support for this.
|
||||
*/
|
||||
const getStorageData = async () =>
|
||||
(await storage.getItem(storageKey)) ||
|
||||
{
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
const saveDraftToStorage = async (draft) => {
|
||||
const currentData = await getStorageData()
|
||||
currentData[draft.id] = JSON.parse(JSON.stringify(draft))
|
||||
await storage.setItem(storageKey, currentData)
|
||||
}
|
||||
|
||||
const deleteDraftFromStorage = async (ids) => {
|
||||
const currentData = await getStorageData()
|
||||
ids.forEach((id) => {
|
||||
delete currentData[id]
|
||||
})
|
||||
await storage.setItem(storageKey, currentData)
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
async addOrSaveDraft(store, { draft }) {
|
||||
const id = draft.id || new Date().getTime().toString()
|
||||
const draftWithId = { ...draft, id }
|
||||
store.commit('addOrSaveDraft', { draft: draftWithId })
|
||||
await saveDraftToStorage(draftWithId)
|
||||
return id
|
||||
},
|
||||
async abandonDraft(store, { id }) {
|
||||
store.commit('abandonDraft', { id })
|
||||
await deleteDraftFromStorage([id])
|
||||
},
|
||||
async abandonAllDrafts(store) {
|
||||
const ids = Object.keys(store.state.drafts)
|
||||
ids.forEach((id) => store.commit('abandonDraft', { id }))
|
||||
await deleteDraftFromStorage(ids)
|
||||
},
|
||||
async loadDrafts(store) {
|
||||
const currentData = await getStorageData()
|
||||
store.commit('loadDrafts', currentData)
|
||||
},
|
||||
}
|
||||
|
||||
export const getters = {
|
||||
draftsByTypeAndRefId(state) {
|
||||
return (type, refId) => {
|
||||
return Object.values(state.drafts).filter(
|
||||
(draft) => draft.type === type && draft.refId === refId,
|
||||
)
|
||||
}
|
||||
},
|
||||
draftsArray(state) {
|
||||
return Object.values(state.drafts)
|
||||
},
|
||||
draftCount(state) {
|
||||
return Object.values(state.drafts).length
|
||||
},
|
||||
}
|
||||
|
||||
const drafts = {
|
||||
state: defaultState,
|
||||
mutations,
|
||||
getters,
|
||||
actions,
|
||||
}
|
||||
|
||||
export default drafts
|
||||
|
|
@ -1,5 +1 @@
|
|||
import drafts from './drafts.js'
|
||||
|
||||
export default {
|
||||
drafts,
|
||||
}
|
||||
export default {}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ export class RegistrationError extends Error {
|
|||
// the error is probably a JSON object with a single key, "errors", whose value is another JSON object containing the real errors
|
||||
if (typeof error === 'string') {
|
||||
error = JSON.parse(error)
|
||||
// eslint-disable-next-line
|
||||
if (Object.hasOwn(error, 'error')) {
|
||||
error = JSON.parse(error.error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export const useAnnouncementsStore = defineStore('announcements', {
|
|||
userActions: {},
|
||||
}),
|
||||
getters: {
|
||||
unreadAnnouncementCount() {
|
||||
unreadAnnouncementsCount() {
|
||||
if (!useUsersStore().currentUser) {
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
76
src/stores/drafts.js
Normal file
76
src/stores/drafts.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { defineStore } from 'pinia'
|
||||
|
||||
import { storage } from 'src/lib/storage.js'
|
||||
|
||||
const storageKey = 'pleroma-fe-drafts'
|
||||
|
||||
/*
|
||||
* Note: we do not use the persist state plugin because
|
||||
* it is not impossible for a user to have two windows at
|
||||
* the same time. The persist state plugin is just overriding
|
||||
* everything with the current state. This isn't good because
|
||||
* if a draft is created in one window and another draft is
|
||||
* created in another, the draft in the first window will just
|
||||
* be overriden.
|
||||
* Here, we can't guarantee 100% atomicity unless one uses
|
||||
* different keys, which will just pollute the whole storage.
|
||||
* It is indeed best to have backend support for this.
|
||||
*/
|
||||
const getStorageData = async () => await storage.getItem(storageKey)
|
||||
|
||||
const saveDraftToStorage = async (draft) => {
|
||||
const currentData = (await getStorageData()) ?? {}
|
||||
currentData[draft.id] = JSON.parse(JSON.stringify(draft))
|
||||
await storage.setItem(storageKey, currentData)
|
||||
}
|
||||
|
||||
const deleteDraftFromStorage = async (ids) => {
|
||||
const currentData = (await getStorageData()) ?? {}
|
||||
ids.forEach((id) => {
|
||||
delete currentData[id]
|
||||
})
|
||||
await storage.setItem(storageKey, currentData)
|
||||
}
|
||||
|
||||
export const useDraftsStore = defineStore('drafts', {
|
||||
state: () => ({
|
||||
drafts: new Map(),
|
||||
}),
|
||||
getters: {
|
||||
draftsByTypeAndRefId(state) {
|
||||
return (type, refId) => {
|
||||
return [...state.drafts.values()].filter(
|
||||
(draft) => draft.type === type && draft.refId === refId,
|
||||
)
|
||||
}
|
||||
},
|
||||
draftsArray(state) {
|
||||
return [...state.drafts.values()]
|
||||
},
|
||||
draftsCount(state) {
|
||||
return state.drafts.size
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async abandonDraft(id) {
|
||||
this.drafts.delete(id)
|
||||
await deleteDraftFromStorage([id])
|
||||
},
|
||||
async loadDrafts() {
|
||||
const currentData = await getStorageData()
|
||||
this.drafts = new Map(Object.entries(currentData))
|
||||
},
|
||||
async addOrSaveDraft(draft) {
|
||||
const id = draft.id ?? new Date().getTime().toString()
|
||||
const draftWithId = { ...draft, id }
|
||||
this.drafts.set(id, draftWithId)
|
||||
await saveDraftToStorage(draftWithId)
|
||||
return id
|
||||
},
|
||||
async abandonAllDrafts(store) {
|
||||
const ids = [...this.drafts.keys()]
|
||||
ids.forEach((id) => this.abandonDraft(id))
|
||||
await deleteDraftFromStorage(ids)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
@ -38,10 +38,6 @@ export const useInstanceCapabilitiesStore = defineStore(
|
|||
}
|
||||
|
||||
this[capability] = value
|
||||
|
||||
if (capability === 'shoutAvailable') {
|
||||
window.vuex.dispatch('initializeSocket')
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -97,7 +97,9 @@ export const settingsMap = {
|
|||
}
|
||||
|
||||
export const defaultState = () => ({
|
||||
config: Object.fromEntries(Object.keys(settingsMap).map((key) => [key, null]))
|
||||
config: Object.fromEntries(
|
||||
Object.keys(settingsMap).map((key) => [key, null]),
|
||||
),
|
||||
})
|
||||
|
||||
export const useProfileConfigStore = defineStore('profileConfig', {
|
||||
|
|
@ -123,7 +125,7 @@ export const useProfileConfigStore = defineStore('profileConfig', {
|
|||
return
|
||||
}
|
||||
|
||||
useUsersStore().addNewUsers(result)
|
||||
const [user] = useUsersStore().addNewUsers(result)
|
||||
this.update(user)
|
||||
} catch (e) {
|
||||
console.warn('Error setting server-side option:', e)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { defineStore } from 'pinia'
|
|||
import { useAnnouncementsStore } from 'src/stores/announcements.js'
|
||||
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
|
||||
import { useChatsStore } from 'src/stores/chats.js'
|
||||
import { useDraftsStore } from 'src/stores/drafts.js'
|
||||
import { useEmojiStore } from 'src/stores/emoji.js'
|
||||
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
|
|
@ -14,11 +15,11 @@ import { useListsStore } from 'src/stores/lists.js'
|
|||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useNotificationsStore } from 'src/stores/notifications.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useProfileConfigStore } from 'src/stores/profile_config.js'
|
||||
import { useShoutStore } from 'src/stores/shout.js'
|
||||
import { useStatusesStore } from 'src/stores/statuses.js'
|
||||
import { useStreamingStore } from 'src/stores/streaming.js'
|
||||
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||
import { useProfileConfigStore } from 'src/stores/profile_config.js'
|
||||
import { useTimelinesStore } from 'src/stores/timelines.js'
|
||||
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
|
||||
|
||||
|
|
@ -611,13 +612,6 @@ export const useUsersStore = defineStore('users', {
|
|||
|
||||
// Login/Logout
|
||||
async loginUser(accessToken) {
|
||||
const store = window.vuex
|
||||
const dispatch =
|
||||
store?.dispatch ??
|
||||
(() => {
|
||||
/* no-op */
|
||||
}) // for tests
|
||||
|
||||
this.loggingIn = true
|
||||
|
||||
try {
|
||||
|
|
@ -670,7 +664,7 @@ export const useUsersStore = defineStore('users', {
|
|||
useSyncConfigStore().setFlag({ flag: 'configMigration', value: 0 })
|
||||
/**/
|
||||
|
||||
if (user.token) {
|
||||
if (user.token && useInstanceCapabilitiesStore().shoutAvailable) {
|
||||
// Shoutbox
|
||||
useShoutStore().initializeSocket()
|
||||
useShoutStore().initializeShout()
|
||||
|
|
@ -689,7 +683,6 @@ export const useUsersStore = defineStore('users', {
|
|||
useBookmarkFoldersStore().startFetching()
|
||||
|
||||
if (user.locked) {
|
||||
dispatch('startFetchingFollowRequests')
|
||||
useFollowRequestsStore().startFetching()
|
||||
}
|
||||
|
||||
|
|
@ -701,7 +694,7 @@ export const useUsersStore = defineStore('users', {
|
|||
useAnnouncementsStore().startFetching()
|
||||
|
||||
this.fetchMutes()
|
||||
dispatch('loadDrafts')
|
||||
useDraftsStore().loadDrafts()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
|
|
@ -723,7 +716,6 @@ export const useUsersStore = defineStore('users', {
|
|||
}
|
||||
},
|
||||
logout() {
|
||||
const store = window.vuex
|
||||
const oauth = useOAuthStore()
|
||||
|
||||
// Pause fetching
|
||||
|
|
@ -739,8 +731,6 @@ export const useUsersStore = defineStore('users', {
|
|||
useFollowRequestsStore().stopFetching()
|
||||
}
|
||||
|
||||
store?.dispatch('stopFetchingFollowRequests')
|
||||
|
||||
// NOTE: No need to verify the app still exists, because if it doesn't,
|
||||
// the token will be invalid too
|
||||
return oauth
|
||||
|
|
@ -798,7 +788,7 @@ export const useUsersStore = defineStore('users', {
|
|||
useListsStore().startFetching()
|
||||
useBookmarkFoldersStore().startFetching()
|
||||
useChatsStore().startFetching()
|
||||
store?.dispatch('startFetchingFollowRequests')
|
||||
useFollowRequestsStore().startFetching()
|
||||
})
|
||||
.finally(() => {
|
||||
useNotificationsStore().resume()
|
||||
|
|
|
|||
22
test/fixtures/mock_store.js
vendored
22
test/fixtures/mock_store.js
vendored
|
|
@ -1,22 +0,0 @@
|
|||
import { cloneDeep } from 'lodash'
|
||||
import { createStore } from 'vuex'
|
||||
|
||||
import vuexModules from 'src/modules/index.js'
|
||||
|
||||
const tweakModules = (modules) => {
|
||||
const res = {}
|
||||
Object.entries(modules).forEach(([name, module]) => {
|
||||
const m = { ...module }
|
||||
m.state = cloneDeep(module.state)
|
||||
res[name] = m
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
const makeMockStore = () => {
|
||||
return createStore({
|
||||
modules: tweakModules(vuexModules),
|
||||
})
|
||||
}
|
||||
|
||||
export default makeMockStore
|
||||
18
test/fixtures/setup_test.js
vendored
18
test/fixtures/setup_test.js
vendored
|
|
@ -5,26 +5,15 @@ import VueVirtualScroller from 'vue-virtual-scroller'
|
|||
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 makeMockStore from './mock_store'
|
||||
|
||||
import routes from 'src/boot/routes'
|
||||
|
||||
export const $t = (msg) => msg
|
||||
const $i18n = { t: (msg) => msg }
|
||||
|
||||
const applyAfterStore = (store, afterStore) => {
|
||||
afterStore(store)
|
||||
return store
|
||||
}
|
||||
|
||||
const getDefaultOpts = ({
|
||||
afterStore = () => {
|
||||
/* no-op */
|
||||
},
|
||||
} = {}) => ({
|
||||
const getDefaultOpts = () => ({
|
||||
global: {
|
||||
plugins: [
|
||||
applyAfterStore(makeMockStore(), afterStore),
|
||||
VueVirtualScroller,
|
||||
createRouter({
|
||||
history: createMemoryHistory(),
|
||||
|
|
@ -87,9 +76,8 @@ const customBehaviors = () => {
|
|||
|
||||
config.plugins.VueWrapper.install(customBehaviors)
|
||||
|
||||
export const mountOpts = (allOpts = {}) => {
|
||||
const { afterStore, ...opts } = allOpts
|
||||
const defaultOpts = getDefaultOpts({ afterStore })
|
||||
export const mountOpts = (opts = {}) => {
|
||||
const defaultOpts = getDefaultOpts()
|
||||
const mergedOpts = {
|
||||
...opts,
|
||||
global: {
|
||||
|
|
|
|||
|
|
@ -29,11 +29,6 @@ const message3 = {
|
|||
|
||||
const global = {
|
||||
mocks: {
|
||||
$store: {
|
||||
state: {
|
||||
api: {},
|
||||
},
|
||||
},
|
||||
$route: {
|
||||
params: {
|
||||
recipient_id: 2,
|
||||
|
|
|
|||
|
|
@ -1,193 +0,0 @@
|
|||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
|
||||
import { $t, mountOpts, waitForEvent } from '../../../fixtures/setup_test'
|
||||
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useUsersStore } from 'src/stores/users.js'
|
||||
|
||||
const autoSaveOrNot = (caseFn, caseTitle, runFn) => {
|
||||
caseFn(`${caseTitle} with auto-save`, function () {
|
||||
return runFn.bind(this)(true)
|
||||
})
|
||||
|
||||
caseFn(`${caseTitle} with no auto-save`, function () {
|
||||
return runFn.bind(this)(false)
|
||||
})
|
||||
}
|
||||
|
||||
const saveManually = async (wrapper) => {
|
||||
const morePostActions = wrapper.findByText(
|
||||
'button',
|
||||
$t('post_status.more_post_actions'),
|
||||
)
|
||||
await morePostActions.trigger('click')
|
||||
|
||||
const btn = wrapper.findByText(
|
||||
'button',
|
||||
$t('post_status.save_to_drafts_button'),
|
||||
)
|
||||
await btn.trigger('click')
|
||||
}
|
||||
|
||||
const waitSaveTime = 4000
|
||||
|
||||
const currentUser = {
|
||||
id: 'current-user',
|
||||
default_scope: 'public',
|
||||
locked: false,
|
||||
}
|
||||
|
||||
describe('Draft saving', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createTestingPinia())
|
||||
useUsersStore().currentUser = currentUser
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
autoSaveOrNot(
|
||||
it,
|
||||
'should save when the button is clicked',
|
||||
async (autoSave) => {
|
||||
const wrapper = mount(PostStatusForm, mountOpts())
|
||||
const store = useMergedConfigStore()
|
||||
store.mergedConfig = {
|
||||
autoSaveDraft: autoSave,
|
||||
}
|
||||
expect(wrapper.vm.$store.getters.draftCount).to.equal(0)
|
||||
|
||||
const textarea = wrapper.get('textarea')
|
||||
await textarea.setValue('mew mew')
|
||||
|
||||
await saveManually(wrapper)
|
||||
expect(wrapper.vm.$store.getters.draftCount).to.equal(1)
|
||||
expect(wrapper.vm.$store.getters.draftsArray[0].status).to.equal(
|
||||
'mew mew',
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
it('should auto-save if it is enabled', async function () {
|
||||
vi.useFakeTimers()
|
||||
const wrapper = mount(PostStatusForm, mountOpts())
|
||||
const store = useMergedConfigStore()
|
||||
store.mergedConfig = {
|
||||
autoSaveDraft: true,
|
||||
}
|
||||
expect(wrapper.vm.$store.getters.draftCount).to.equal(0)
|
||||
const textarea = wrapper.get('textarea')
|
||||
await textarea.setValue('mew mew')
|
||||
|
||||
expect(wrapper.vm.$store.getters.draftCount).to.equal(0)
|
||||
await vi.advanceTimersByTimeAsync(waitSaveTime)
|
||||
expect(wrapper.vm.$store.getters.draftCount).to.equal(1)
|
||||
expect(wrapper.vm.$store.getters.draftsArray[0].status).to.equal('mew mew')
|
||||
})
|
||||
|
||||
it('should auto-save when close if auto-save is on', async () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
closeable: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
const store = useMergedConfigStore()
|
||||
store.mergedConfig = {
|
||||
autoSaveDraft: true,
|
||||
}
|
||||
expect(wrapper.vm.$store.getters.draftCount).to.equal(0)
|
||||
const textarea = wrapper.get('textarea')
|
||||
await textarea.setValue('mew mew')
|
||||
wrapper.vm.requestClose()
|
||||
expect(wrapper.vm.$store.getters.draftCount).to.equal(1)
|
||||
await waitForEvent(wrapper, 'close-accepted')
|
||||
})
|
||||
|
||||
it('should save when close if auto-save is off, and unsavedPostAction is save', async () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
closeable: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
const store = useMergedConfigStore()
|
||||
store.mergedConfig = {
|
||||
autoSaveDraft: false,
|
||||
unsavedPostAction: 'save',
|
||||
}
|
||||
expect(wrapper.vm.$store.getters.draftCount).to.equal(0)
|
||||
const textarea = wrapper.get('textarea')
|
||||
await textarea.setValue('mew mew')
|
||||
wrapper.vm.requestClose()
|
||||
expect(wrapper.vm.$store.getters.draftCount).to.equal(1)
|
||||
await waitForEvent(wrapper, 'close-accepted')
|
||||
})
|
||||
|
||||
it('should discard when close if auto-save is off, and unsavedPostAction is discard', async () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
closeable: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
const store = useMergedConfigStore()
|
||||
store.mergedConfig = {
|
||||
autoSaveDraft: false,
|
||||
unsavedPostAction: 'discard',
|
||||
}
|
||||
expect(wrapper.vm.$store.getters.draftCount).to.equal(0)
|
||||
const textarea = wrapper.get('textarea')
|
||||
await textarea.setValue('mew mew')
|
||||
wrapper.vm.requestClose()
|
||||
await waitForEvent(wrapper, 'close-accepted')
|
||||
expect(wrapper.vm.$store.getters.draftCount).to.equal(0)
|
||||
})
|
||||
|
||||
it('should confirm when close if auto-save is off, and unsavedPostAction is confirm', async () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
closeable: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
const store = useMergedConfigStore(createTestingPinia())
|
||||
store.mergedConfig = {
|
||||
autoSaveDraft: false,
|
||||
unsavedPostAction: 'confirm',
|
||||
}
|
||||
expect(wrapper.vm.$store.getters.draftCount).to.equal(0)
|
||||
const textarea = wrapper.get('textarea')
|
||||
await textarea.setValue('mew mew')
|
||||
wrapper.vm.requestClose()
|
||||
await nextTick()
|
||||
await flushPromises()
|
||||
const saveButton = await vi.waitFor(() => {
|
||||
const button = wrapper.findByText(
|
||||
'button',
|
||||
$t('post_status.close_confirm_save_button'),
|
||||
)
|
||||
if (!button) throw new Error('Save button not present')
|
||||
return button
|
||||
})
|
||||
expect(saveButton).to.be.ok
|
||||
await saveButton.trigger('click')
|
||||
console.info('clicked')
|
||||
expect(wrapper.vm.$store.getters.draftCount).to.equal(1)
|
||||
await flushPromises()
|
||||
await waitForEvent(wrapper, 'close-accepted')
|
||||
})
|
||||
})
|
||||
|
|
@ -1,16 +1,44 @@
|
|||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { $t, mountOpts, waitForEvent } from 'test/fixtures/setup_test.js'
|
||||
import { vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
|
||||
import { mountOpts } from '../../../fixtures/setup_test'
|
||||
|
||||
import { useDraftsStore } from 'src/stores/drafts.js'
|
||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useStatusesStore } from 'src/stores/statuses.js'
|
||||
import { useUsersStore } from 'src/stores/users.js'
|
||||
|
||||
const autoSaveOrNot = (caseFn, caseTitle, runFn) => {
|
||||
caseFn(`${caseTitle} with auto-save`, function () {
|
||||
return runFn.bind(this)(true)
|
||||
})
|
||||
|
||||
caseFn(`${caseTitle} with no auto-save`, function () {
|
||||
return runFn.bind(this)(false)
|
||||
})
|
||||
}
|
||||
|
||||
const saveManually = async (wrapper) => {
|
||||
const morePostActions = wrapper.findByText(
|
||||
'button',
|
||||
$t('post_status.more_post_actions'),
|
||||
)
|
||||
await morePostActions.trigger('click')
|
||||
|
||||
const btn = wrapper.findByText(
|
||||
'button',
|
||||
$t('post_status.save_to_drafts_button'),
|
||||
)
|
||||
await btn.trigger('click')
|
||||
}
|
||||
|
||||
const waitSaveTime = 4000
|
||||
|
||||
const currentUser = {
|
||||
id: 'current-user',
|
||||
default_scope: 'public',
|
||||
|
|
@ -36,303 +64,476 @@ const repliedStatus2 = {
|
|||
}
|
||||
|
||||
describe('PostStatusForm', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
setActivePinia(createTestingPinia())
|
||||
useUsersStore().currentUser = currentUser
|
||||
useStatusesStore().allStatuses = new Map([
|
||||
[repliedStatus.id, repliedStatus],
|
||||
])
|
||||
})
|
||||
describe('Basic functionality', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
setActivePinia(createTestingPinia())
|
||||
useUsersStore().currentUser = currentUser
|
||||
useStatusesStore().allStatuses = new Map([
|
||||
[repliedStatus.id, repliedStatus],
|
||||
])
|
||||
})
|
||||
|
||||
it('Clean empty initial state', () => {
|
||||
const wrapper = mount(PostStatusForm, mountOpts())
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('new')
|
||||
expect(wrapper.vm.newStatus.spoilerText).to.eql('')
|
||||
expect(wrapper.vm.newStatus.mentions).to.eql('')
|
||||
expect(wrapper.vm.newStatus.status).to.eql('')
|
||||
})
|
||||
it('Clean empty initial state', () => {
|
||||
const wrapper = mount(PostStatusForm, mountOpts())
|
||||
|
||||
it('Reset cleans form to pristine state equal to state form was when created', () => {
|
||||
const wrapper = mount(PostStatusForm, mountOpts())
|
||||
expect(wrapper.vm.statusType).to.equal('new')
|
||||
expect(wrapper.vm.newStatus.spoilerText).to.eql('')
|
||||
expect(wrapper.vm.newStatus.mentions).to.eql('')
|
||||
expect(wrapper.vm.newStatus.status).to.eql('')
|
||||
})
|
||||
|
||||
const initial = { ...wrapper.vm.newStatus }
|
||||
wrapper.vm.clearStatus()
|
||||
it('Reset cleans form to pristine state equal to state form was when created', () => {
|
||||
const wrapper = mount(PostStatusForm, mountOpts())
|
||||
|
||||
expect(wrapper.vm.newStatus).to.eql(initial)
|
||||
})
|
||||
const initial = { ...wrapper.vm.newStatus }
|
||||
wrapper.vm.clearStatus()
|
||||
|
||||
it('Initializes a reply form', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
expect(wrapper.vm.newStatus).to.eql(initial)
|
||||
})
|
||||
|
||||
it('Initializes a reply form', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
repliedStatus: repliedStatus,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
useInstanceCapabilitiesStore().quotingAvailable = true
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('reply')
|
||||
expect(wrapper.vm.isReply).to.equal(true)
|
||||
expect(wrapper.vm.refId).to.equal('status-1')
|
||||
expect(wrapper.vm.quotable).to.equal(true)
|
||||
expect(wrapper.vm.inReplyToStatusId).to.equal('status-1')
|
||||
expect(wrapper.vm.newStatus.quote).to.be.null
|
||||
expect(wrapper.vm.newStatus.poll).to.be.null
|
||||
expect(wrapper.vm.newStatus.spoilerText).to.eql('')
|
||||
expect(wrapper.vm.newStatus.mentions).to.eql('@replied')
|
||||
expect(wrapper.vm.newStatus.status).to.eql('@replied ')
|
||||
expect(wrapper.vm.newStatus.visibility).to.eql('public')
|
||||
})
|
||||
|
||||
it('Copies scope and subject line, disables quoting for locked posts', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
repliedStatus: repliedStatus2,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
useInstanceCapabilitiesStore().quotingAvailable = true
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('reply')
|
||||
expect(wrapper.vm.isReply).to.equal(true)
|
||||
expect(wrapper.vm.quotable).to.equal(false)
|
||||
expect(wrapper.vm.newStatus.quote).to.be.null
|
||||
expect(wrapper.vm.newStatus.poll).to.be.null
|
||||
expect(wrapper.vm.newStatus.spoilerText).to.eql('re: subject')
|
||||
expect(wrapper.vm.newStatus.mentions).to.eql('@replied')
|
||||
expect(wrapper.vm.newStatus.status).to.eql('@replied ')
|
||||
expect(wrapper.vm.newStatus.visibility).to.eql('private')
|
||||
|
||||
expect(wrapper.vm.postingOptions.status).to.eql('@replied ')
|
||||
expect(wrapper.vm.postingOptions.spoilerText).to.eql('re: subject')
|
||||
expect(wrapper.vm.postingOptions.visibility).to.eql('private')
|
||||
expect(wrapper.vm.postingOptions.sensitive).to.eql(false)
|
||||
expect(wrapper.vm.postingOptions.media).to.eql([])
|
||||
expect(wrapper.vm.postingOptions.inReplyToStatusId).to.eql('status-2')
|
||||
expect(wrapper.vm.postingOptions.quoteId).to.be.null
|
||||
expect(wrapper.vm.postingOptions.contentType).to.eql('text/plain')
|
||||
expect(wrapper.vm.postingOptions.poll).to.be.null
|
||||
})
|
||||
|
||||
it('Forces direct mode when replying to a DM, mastodon style subject handling', () => {
|
||||
// We need to initialize pinia first which is happening here...
|
||||
const options = mountOpts({
|
||||
props: {
|
||||
repliedStatus: repliedStatus,
|
||||
repliedStatus: { ...repliedStatus2, visibility: 'direct' },
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
useInstanceCapabilitiesStore().quotingAvailable = true
|
||||
// ...set our settings...
|
||||
useMergedConfigStore().mergedConfig = {
|
||||
...useMergedConfigStore().mergedConfig,
|
||||
subjectLineBehavior: 'masto',
|
||||
}
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('reply')
|
||||
expect(wrapper.vm.isReply).to.equal(true)
|
||||
expect(wrapper.vm.refId).to.equal('status-1')
|
||||
expect(wrapper.vm.quotable).to.equal(true)
|
||||
expect(wrapper.vm.inReplyToStatusId).to.equal('status-1')
|
||||
expect(wrapper.vm.newStatus.quote).to.be.null
|
||||
expect(wrapper.vm.newStatus.poll).to.be.null
|
||||
expect(wrapper.vm.newStatus.spoilerText).to.eql('')
|
||||
expect(wrapper.vm.newStatus.mentions).to.eql('@replied')
|
||||
expect(wrapper.vm.newStatus.status).to.eql('@replied ')
|
||||
expect(wrapper.vm.newStatus.visibility).to.eql('public')
|
||||
// ...and only then mount our component
|
||||
const wrapper = mount(PostStatusForm, options)
|
||||
|
||||
// Otherwise we get multiple instances of pinia that don't talk to each other
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('reply')
|
||||
expect(wrapper.vm.isReply).to.equal(true)
|
||||
expect(wrapper.vm.quotable).to.equal(false)
|
||||
expect(wrapper.vm.newStatus.quote).to.be.null
|
||||
expect(wrapper.vm.newStatus.poll).to.be.null
|
||||
expect(wrapper.vm.newStatus.spoilerText).to.eql('subject')
|
||||
expect(wrapper.vm.newStatus.mentions).to.eql('@replied')
|
||||
expect(wrapper.vm.newStatus.status).to.eql('@replied ')
|
||||
expect(wrapper.vm.newStatus.visibility).to.eql('direct')
|
||||
})
|
||||
|
||||
it('Sets status to statusText without mentions if mentions line is enabled', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
repliedStatus: repliedStatus2,
|
||||
statusText: 'testing',
|
||||
mentionsLine: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('reply')
|
||||
expect(wrapper.vm.isReply).to.equal(true)
|
||||
expect(wrapper.vm.newStatus.mentions).to.eql('@replied')
|
||||
expect(wrapper.vm.newStatus.status).to.eql('testing')
|
||||
})
|
||||
|
||||
it('Sets mention when asked for it', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
profileMention: repliedUser,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('mention')
|
||||
expect(wrapper.vm.isReply).to.equal(false)
|
||||
expect(wrapper.vm.newStatus.mentions).to.eql('@replied')
|
||||
expect(wrapper.vm.newStatus.status).to.eql('@replied ')
|
||||
})
|
||||
|
||||
it('Initializes quote when reply/quote toggled to quote', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
repliedStatus: repliedStatus2,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('reply')
|
||||
expect(wrapper.vm.isReply).to.equal(true)
|
||||
|
||||
wrapper.vm.quoteThreadToggled = true
|
||||
|
||||
expect(wrapper.vm.newStatus.quote).to.eql({
|
||||
thread: true,
|
||||
id: 'status-2',
|
||||
})
|
||||
})
|
||||
|
||||
it('Resets quote when reply/quote toggled to reply', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
repliedStatus: repliedStatus2,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('reply')
|
||||
expect(wrapper.vm.isReply).to.equal(true)
|
||||
|
||||
wrapper.vm.quoteThreadToggled = true
|
||||
wrapper.vm.quoteThreadToggled = false
|
||||
|
||||
expect(wrapper.vm.newStatus.quote).to.be.null
|
||||
})
|
||||
|
||||
it('Initializes and reset quote when toggling quote attachment', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
repliedStatus: repliedStatus2,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('reply')
|
||||
expect(wrapper.vm.isReply).to.equal(true)
|
||||
|
||||
wrapper.vm.toggleQuoteForm()
|
||||
expect(wrapper.vm.newStatus.quote).to.eql({
|
||||
thread: false,
|
||||
id: null,
|
||||
url: '',
|
||||
})
|
||||
wrapper.vm.toggleQuoteForm()
|
||||
expect(wrapper.vm.newStatus.quote).to.be.null
|
||||
})
|
||||
|
||||
it('Status editing', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
statusId: 'edited',
|
||||
statusText: 'text',
|
||||
statusSubject: 'heading',
|
||||
statusIsSensitive: true,
|
||||
statusPoll: {},
|
||||
statusQuote: {},
|
||||
statusFiles: [],
|
||||
statusMediaDescriptions: {},
|
||||
statusVisibility: 'unlisted',
|
||||
statusContentType: 'text/markdown',
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('edit')
|
||||
expect(wrapper.vm.isReply).to.equal(false) // edits don't support changing reply-to so it's pretty much ignored
|
||||
expect(wrapper.vm.isEdit).to.equal(true)
|
||||
expect(wrapper.vm.newStatus.quote).to.eql({})
|
||||
expect(wrapper.vm.newStatus.poll).to.eql({})
|
||||
expect(wrapper.vm.newStatus.spoilerText).to.eql('heading')
|
||||
expect(wrapper.vm.newStatus.mentions).to.eql('')
|
||||
expect(wrapper.vm.newStatus.status).to.eql('text')
|
||||
expect(wrapper.vm.newStatus.visibility).to.eql('unlisted')
|
||||
expect(wrapper.vm.newStatus.contentType).to.eql('text/markdown')
|
||||
expect(wrapper.vm.newStatus.nsfw).to.equal(true)
|
||||
expect(wrapper.vm.newStatus.files).to.eql([])
|
||||
})
|
||||
|
||||
it('Posting should reset idempotency key', async () => {
|
||||
vi.setSystemTime(new Date(2027, 1, 1, 13))
|
||||
const wrapper = mount(PostStatusForm, mountOpts())
|
||||
const oldIdempotency = wrapper.vm.idempotencyKey
|
||||
|
||||
vi.setSystemTime(new Date(2028, 1, 1, 13))
|
||||
|
||||
wrapper.vm.newStatus.status = 'Testing'
|
||||
await wrapper.vm.postStatus()
|
||||
|
||||
expect(wrapper.vm.idempotencyKey).to.not.eql(oldIdempotency)
|
||||
})
|
||||
})
|
||||
|
||||
it('Copies scope and subject line, disables quoting for locked posts', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
repliedStatus: repliedStatus2,
|
||||
},
|
||||
}),
|
||||
)
|
||||
describe('Attachments', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
setActivePinia(createTestingPinia())
|
||||
useUsersStore().currentUser = currentUser
|
||||
useStatusesStore().allStatuses = new Map([
|
||||
[repliedStatus.id, repliedStatus],
|
||||
])
|
||||
})
|
||||
|
||||
useInstanceCapabilitiesStore().quotingAvailable = true
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('reply')
|
||||
expect(wrapper.vm.isReply).to.equal(true)
|
||||
expect(wrapper.vm.quotable).to.equal(false)
|
||||
expect(wrapper.vm.newStatus.quote).to.be.null
|
||||
expect(wrapper.vm.newStatus.poll).to.be.null
|
||||
expect(wrapper.vm.newStatus.spoilerText).to.eql('re: subject')
|
||||
expect(wrapper.vm.newStatus.mentions).to.eql('@replied')
|
||||
expect(wrapper.vm.newStatus.status).to.eql('@replied ')
|
||||
expect(wrapper.vm.newStatus.visibility).to.eql('private')
|
||||
// TODO Probably better to separate attachment upload/manipulation into its own component?
|
||||
// we need to upload-on-submit for compression setting anyway
|
||||
it('Attachments manipulations (moving, adding, removing)', () => {
|
||||
const wrapper = mount(PostStatusForm, mountOpts())
|
||||
|
||||
expect(wrapper.vm.postingOptions.status).to.eql('@replied ')
|
||||
expect(wrapper.vm.postingOptions.spoilerText).to.eql('re: subject')
|
||||
expect(wrapper.vm.postingOptions.visibility).to.eql('private')
|
||||
expect(wrapper.vm.postingOptions.sensitive).to.eql(false)
|
||||
expect(wrapper.vm.postingOptions.media).to.eql([])
|
||||
expect(wrapper.vm.postingOptions.inReplyToStatusId).to.eql('status-2')
|
||||
expect(wrapper.vm.postingOptions.quoteId).to.be.null
|
||||
expect(wrapper.vm.postingOptions.contentType).to.eql('text/plain')
|
||||
expect(wrapper.vm.postingOptions.poll).to.be.null
|
||||
const i1 = { id: '1', url: 'a' }
|
||||
const i2 = { id: '2', url: 'b' }
|
||||
const i3 = { id: '3', url: 'c' }
|
||||
const i4 = { id: '4', url: 'd' }
|
||||
const iX = { id: 'x', url: 'x' }
|
||||
|
||||
wrapper.vm.newStatus.files = [i3, i1, iX, i2]
|
||||
|
||||
wrapper.vm.removeMediaFile(iX)
|
||||
expect(wrapper.vm.newStatus.files).to.eql([i3, i1, i2])
|
||||
|
||||
wrapper.vm.shiftUpMediaFile(i1)
|
||||
expect(wrapper.vm.newStatus.files).to.eql([i1, i3, i2])
|
||||
|
||||
wrapper.vm.shiftUpMediaFile(i1) // should ignore
|
||||
expect(wrapper.vm.newStatus.files).to.eql([i1, i3, i2])
|
||||
|
||||
wrapper.vm.shiftDnMediaFile(i3)
|
||||
expect(wrapper.vm.newStatus.files).to.eql([i1, i2, i3])
|
||||
|
||||
wrapper.vm.shiftDnMediaFile(i3) // should ignore
|
||||
expect(wrapper.vm.newStatus.files).to.eql([i1, i2, i3])
|
||||
|
||||
wrapper.vm.addMediaFile(i4)
|
||||
expect(wrapper.vm.newStatus.files).to.eql([i1, i2, i3, i4])
|
||||
})
|
||||
|
||||
it('Attachment descriptions', () => {
|
||||
const wrapper = mount(PostStatusForm, mountOpts())
|
||||
|
||||
const i1 = { id: '1', url: 'a' }
|
||||
|
||||
wrapper.vm.addMediaFile(i1)
|
||||
expect(wrapper.vm.newStatus.files).to.eql([i1])
|
||||
|
||||
wrapper.vm.editAttachment(i1, 'description')
|
||||
expect(wrapper.vm.newStatus.mediaDescriptions['1']).to.eql('description')
|
||||
})
|
||||
})
|
||||
|
||||
it('Forces direct mode when replying to a DM, mastodon style subject handling', () => {
|
||||
// We need to initialize pinia first which is happening here...
|
||||
const options = mountOpts({
|
||||
props: {
|
||||
repliedStatus: { ...repliedStatus2, visibility: 'direct' },
|
||||
describe('Draft saving', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
useUsersStore().currentUser = currentUser
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
autoSaveOrNot(
|
||||
it,
|
||||
'should save when the button is clicked',
|
||||
async (autoSave) => {
|
||||
const wrapper = mount(PostStatusForm, mountOpts())
|
||||
const store = useMergedConfigStore()
|
||||
store.mergedConfig = {
|
||||
autoSaveDraft: autoSave,
|
||||
}
|
||||
expect(useDraftsStore().draftsCount).to.equal(0)
|
||||
|
||||
const textarea = wrapper.get('textarea')
|
||||
await textarea.setValue('mew mew')
|
||||
|
||||
await saveManually(wrapper)
|
||||
expect(useDraftsStore().draftsCount).to.equal(1)
|
||||
expect(useDraftsStore().draftsArray[0].status).to.equal('mew mew')
|
||||
},
|
||||
)
|
||||
|
||||
it('should auto-save if it is enabled', async function () {
|
||||
vi.useFakeTimers()
|
||||
const wrapper = mount(PostStatusForm, mountOpts())
|
||||
const store = useMergedConfigStore()
|
||||
store.mergedConfig = {
|
||||
autoSaveDraft: true,
|
||||
}
|
||||
expect(useDraftsStore().draftsCount).to.equal(0)
|
||||
const textarea = wrapper.get('textarea')
|
||||
await textarea.setValue('mew mew')
|
||||
|
||||
expect(useDraftsStore().draftsCount).to.equal(0)
|
||||
await vi.advanceTimersByTimeAsync(waitSaveTime)
|
||||
expect(useDraftsStore().draftsCount).to.equal(1)
|
||||
expect(useDraftsStore().draftsArray[0].status).to.equal('mew mew')
|
||||
})
|
||||
|
||||
// ...set our settings...
|
||||
useMergedConfigStore().mergedConfig = {
|
||||
...useMergedConfigStore().mergedConfig,
|
||||
subjectLineBehavior: 'masto',
|
||||
}
|
||||
|
||||
// ...and only then mount our component
|
||||
const wrapper = mount(PostStatusForm, options)
|
||||
|
||||
// Otherwise we get multiple instances of pinia that don't talk to each other
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('reply')
|
||||
expect(wrapper.vm.isReply).to.equal(true)
|
||||
expect(wrapper.vm.quotable).to.equal(false)
|
||||
expect(wrapper.vm.newStatus.quote).to.be.null
|
||||
expect(wrapper.vm.newStatus.poll).to.be.null
|
||||
expect(wrapper.vm.newStatus.spoilerText).to.eql('subject')
|
||||
expect(wrapper.vm.newStatus.mentions).to.eql('@replied')
|
||||
expect(wrapper.vm.newStatus.status).to.eql('@replied ')
|
||||
expect(wrapper.vm.newStatus.visibility).to.eql('direct')
|
||||
})
|
||||
|
||||
it('Sets status to statusText without mentions if mentions line is enabled', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
repliedStatus: repliedStatus2,
|
||||
statusText: 'testing',
|
||||
mentionsLine: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('reply')
|
||||
expect(wrapper.vm.isReply).to.equal(true)
|
||||
expect(wrapper.vm.newStatus.mentions).to.eql('@replied')
|
||||
expect(wrapper.vm.newStatus.status).to.eql('testing')
|
||||
})
|
||||
|
||||
it('Sets mention when asked for it', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
profileMention: repliedUser,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('mention')
|
||||
expect(wrapper.vm.isReply).to.equal(false)
|
||||
expect(wrapper.vm.newStatus.mentions).to.eql('@replied')
|
||||
expect(wrapper.vm.newStatus.status).to.eql('@replied ')
|
||||
})
|
||||
|
||||
it('Initializes quote when reply/quote toggled to quote', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
repliedStatus: repliedStatus2,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('reply')
|
||||
expect(wrapper.vm.isReply).to.equal(true)
|
||||
|
||||
wrapper.vm.quoteThreadToggled = true
|
||||
|
||||
expect(wrapper.vm.newStatus.quote).to.eql({ thread: true, id: 'status-2' })
|
||||
})
|
||||
|
||||
it('Resets quote when reply/quote toggled to reply', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
repliedStatus: repliedStatus2,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('reply')
|
||||
expect(wrapper.vm.isReply).to.equal(true)
|
||||
|
||||
wrapper.vm.quoteThreadToggled = true
|
||||
wrapper.vm.quoteThreadToggled = false
|
||||
|
||||
expect(wrapper.vm.newStatus.quote).to.be.null
|
||||
})
|
||||
|
||||
it('Initializes and reset quote when toggling quote attachment', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
repliedStatus: repliedStatus2,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('reply')
|
||||
expect(wrapper.vm.isReply).to.equal(true)
|
||||
|
||||
wrapper.vm.toggleQuoteForm()
|
||||
expect(wrapper.vm.newStatus.quote).to.eql({
|
||||
thread: false,
|
||||
id: null,
|
||||
url: '',
|
||||
it('should auto-save when close if auto-save is on', async () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
closeable: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
const store = useMergedConfigStore()
|
||||
store.mergedConfig = {
|
||||
autoSaveDraft: true,
|
||||
}
|
||||
expect(useDraftsStore().draftsCount).to.equal(0)
|
||||
const textarea = wrapper.get('textarea')
|
||||
await textarea.setValue('mew mew')
|
||||
wrapper.vm.requestClose()
|
||||
expect(useDraftsStore().draftsCount).to.equal(1)
|
||||
await waitForEvent(wrapper, 'close-accepted')
|
||||
})
|
||||
|
||||
it('should save when close if auto-save is off, and unsavedPostAction is save', async () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
closeable: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
const store = useMergedConfigStore()
|
||||
store.mergedConfig = {
|
||||
autoSaveDraft: false,
|
||||
unsavedPostAction: 'save',
|
||||
}
|
||||
expect(useDraftsStore().draftsCount).to.equal(0)
|
||||
const textarea = wrapper.get('textarea')
|
||||
await textarea.setValue('mew mew')
|
||||
wrapper.vm.requestClose()
|
||||
expect(useDraftsStore().draftsCount).to.equal(1)
|
||||
await waitForEvent(wrapper, 'close-accepted')
|
||||
})
|
||||
|
||||
it('should discard when close if auto-save is off, and unsavedPostAction is discard', async () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
closeable: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
const store = useMergedConfigStore()
|
||||
store.mergedConfig = {
|
||||
autoSaveDraft: false,
|
||||
unsavedPostAction: 'discard',
|
||||
}
|
||||
expect(useDraftsStore().draftsCount).to.equal(0)
|
||||
const textarea = wrapper.get('textarea')
|
||||
await textarea.setValue('mew mew')
|
||||
wrapper.vm.requestClose()
|
||||
await waitForEvent(wrapper, 'close-accepted')
|
||||
expect(useDraftsStore().draftsCount).to.equal(0)
|
||||
})
|
||||
|
||||
it('should confirm when close if auto-save is off, and unsavedPostAction is confirm', async () => {
|
||||
const store = useMergedConfigStore()
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
closeable: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
store.mergedConfig = {
|
||||
autoSaveDraft: false,
|
||||
unsavedPostAction: 'confirm',
|
||||
}
|
||||
expect(useDraftsStore().draftsCount).to.equal(0)
|
||||
const textarea = wrapper.get('textarea')
|
||||
await textarea.setValue('mew mew')
|
||||
wrapper.vm.requestClose()
|
||||
await nextTick()
|
||||
await flushPromises()
|
||||
const saveButton = await vi.waitFor(() => {
|
||||
const button = wrapper.findByText(
|
||||
'button',
|
||||
$t('post_status.close_confirm_save_button'),
|
||||
)
|
||||
if (!button) throw new Error('Save button not present')
|
||||
return button
|
||||
})
|
||||
expect(saveButton).to.be.ok
|
||||
await saveButton.trigger('click')
|
||||
console.info('clicked')
|
||||
expect(useDraftsStore().draftsCount).to.equal(1)
|
||||
await flushPromises()
|
||||
await waitForEvent(wrapper, 'close-accepted')
|
||||
})
|
||||
wrapper.vm.toggleQuoteForm()
|
||||
expect(wrapper.vm.newStatus.quote).to.be.null
|
||||
})
|
||||
|
||||
it('Status editing', () => {
|
||||
const wrapper = mount(
|
||||
PostStatusForm,
|
||||
mountOpts({
|
||||
props: {
|
||||
statusId: 'edited',
|
||||
statusText: 'text',
|
||||
statusSubject: 'heading',
|
||||
statusIsSensitive: true,
|
||||
statusPoll: {},
|
||||
statusQuote: {},
|
||||
statusFiles: [],
|
||||
statusMediaDescriptions: {},
|
||||
statusVisibility: 'unlisted',
|
||||
statusContentType: 'text/markdown',
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(wrapper.vm.statusType).to.equal('edit')
|
||||
expect(wrapper.vm.isReply).to.equal(false) // edits don't support changing reply-to so it's pretty much ignored
|
||||
expect(wrapper.vm.isEdit).to.equal(true)
|
||||
expect(wrapper.vm.newStatus.quote).to.eql({})
|
||||
expect(wrapper.vm.newStatus.poll).to.eql({})
|
||||
expect(wrapper.vm.newStatus.spoilerText).to.eql('heading')
|
||||
expect(wrapper.vm.newStatus.mentions).to.eql('')
|
||||
expect(wrapper.vm.newStatus.status).to.eql('text')
|
||||
expect(wrapper.vm.newStatus.visibility).to.eql('unlisted')
|
||||
expect(wrapper.vm.newStatus.contentType).to.eql('text/markdown')
|
||||
expect(wrapper.vm.newStatus.nsfw).to.equal(true)
|
||||
expect(wrapper.vm.newStatus.files).to.eql([])
|
||||
})
|
||||
|
||||
it('Posting should reset idempotency key', async () => {
|
||||
vi.setSystemTime(new Date(2027, 1, 1, 13))
|
||||
const wrapper = mount(PostStatusForm, mountOpts())
|
||||
const oldIdempotency = wrapper.vm.idempotencyKey
|
||||
|
||||
vi.setSystemTime(new Date(2028, 1, 1, 13))
|
||||
|
||||
wrapper.vm.newStatus.status = 'Testing'
|
||||
await wrapper.vm.postStatus()
|
||||
|
||||
expect(wrapper.vm.idempotencyKey).to.not.eql(oldIdempotency)
|
||||
})
|
||||
|
||||
// TODO Probably better to separate attachment upload/manipulation into its own component?
|
||||
// we need to upload-on-submit for compression setting anyway
|
||||
it('Attachments manipulations (moving, adding, removing)', () => {
|
||||
const wrapper = mount(PostStatusForm, mountOpts())
|
||||
|
||||
const i1 = { id: '1', url: 'a' }
|
||||
const i2 = { id: '2', url: 'b' }
|
||||
const i3 = { id: '3', url: 'c' }
|
||||
const i4 = { id: '4', url: 'd' }
|
||||
const iX = { id: 'x', url: 'x' }
|
||||
|
||||
wrapper.vm.newStatus.files = [i3, i1, iX, i2]
|
||||
|
||||
wrapper.vm.removeMediaFile(iX)
|
||||
expect(wrapper.vm.newStatus.files).to.eql([i3, i1, i2])
|
||||
|
||||
wrapper.vm.shiftUpMediaFile(i1)
|
||||
expect(wrapper.vm.newStatus.files).to.eql([i1, i3, i2])
|
||||
|
||||
wrapper.vm.shiftUpMediaFile(i1) // should ignore
|
||||
expect(wrapper.vm.newStatus.files).to.eql([i1, i3, i2])
|
||||
|
||||
wrapper.vm.shiftDnMediaFile(i3)
|
||||
expect(wrapper.vm.newStatus.files).to.eql([i1, i2, i3])
|
||||
|
||||
wrapper.vm.shiftDnMediaFile(i3) // should ignore
|
||||
expect(wrapper.vm.newStatus.files).to.eql([i1, i2, i3])
|
||||
|
||||
wrapper.vm.addMediaFile(i4)
|
||||
expect(wrapper.vm.newStatus.files).to.eql([i1, i2, i3, i4])
|
||||
})
|
||||
|
||||
it('Attachment descriptions', () => {
|
||||
const wrapper = mount(PostStatusForm, mountOpts())
|
||||
|
||||
const i1 = { id: '1', url: 'a' }
|
||||
|
||||
wrapper.vm.addMediaFile(i1)
|
||||
expect(wrapper.vm.newStatus.files).to.eql([i1])
|
||||
|
||||
wrapper.vm.editAttachment(i1, 'description')
|
||||
expect(wrapper.vm.newStatus.mediaDescriptions['1']).to.eql('description')
|
||||
})
|
||||
// TODO: Drafts (needs vuex to pinia migration)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount, shallowMount } from '@vue/test-utils'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { mountOpts } from 'test/fixtures/setup_test.js'
|
||||
|
||||
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
||||
import { mountOpts } from '../../../fixtures/setup_test'
|
||||
|
||||
const attentions = []
|
||||
|
||||
|
|
|
|||
|
|
@ -37,10 +37,7 @@ describe('The SyncConfig store', () => {
|
|||
|
||||
it('should initialize storage if none present', async () => {
|
||||
const store = useSyncConfigStore()
|
||||
// PushSyncConfig is very simple but uses vuex to push data
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
await store.initSyncConfig({ ...user })
|
||||
expect(store.cache._version).to.eql(VERSION)
|
||||
expect(store.cache._timestamp).to.be.a('number')
|
||||
|
|
@ -50,10 +47,7 @@ describe('The SyncConfig store', () => {
|
|||
|
||||
it('should initialize storage with proper flags for new users if none present', async () => {
|
||||
const store = useSyncConfigStore()
|
||||
// PushSyncConfig is very simple but uses vuex to push data
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
await store.initSyncConfig({ ...user, created_at: new Date() })
|
||||
expect(store.cache._version).to.eql(VERSION)
|
||||
expect(store.cache._timestamp).to.be.a('number')
|
||||
|
|
@ -63,10 +57,7 @@ describe('The SyncConfig store', () => {
|
|||
|
||||
it('should merge flags even if remote timestamp is older', async () => {
|
||||
const store = useSyncConfigStore()
|
||||
// PushSyncConfig is very simple but uses vuex to push data
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
store.cache = {
|
||||
_timestamp: Date.now(),
|
||||
_version: VERSION,
|
||||
|
|
@ -96,10 +87,7 @@ describe('The SyncConfig store', () => {
|
|||
|
||||
it('should trim journal to 500 entries', async () => {
|
||||
const store = useSyncConfigStore()
|
||||
// PushSyncConfig is very simple but uses vuex to push data
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
store.cache = {
|
||||
_timestamp: Date.now(),
|
||||
_version: VERSION,
|
||||
|
|
@ -138,10 +126,7 @@ describe('The SyncConfig store', () => {
|
|||
it('should reset local timestamp to remote if contents are the same', async () => {
|
||||
const store = useSyncConfigStore()
|
||||
store.cache = null
|
||||
// PushSyncConfig is very simple but uses vuex to push data
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
|
||||
await store.initSyncConfig({
|
||||
...user,
|
||||
|
|
@ -161,10 +146,7 @@ describe('The SyncConfig store', () => {
|
|||
|
||||
it('should use remote version if local missing', async () => {
|
||||
const store = useSyncConfigStore()
|
||||
// PushSyncConfig is very simple but uses vuex to push data
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
await store.initSyncConfig(store, user)
|
||||
expect(store.cache._version).to.eql(VERSION)
|
||||
expect(store.cache._timestamp).to.be.a('number')
|
||||
|
|
@ -208,9 +190,7 @@ describe('The SyncConfig store', () => {
|
|||
})
|
||||
vi.spyOn(storage, 'setItem').mockResolvedValue()
|
||||
const store = useSyncConfigStore()
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
|
||||
await store.initSyncConfig({
|
||||
...user,
|
||||
|
|
@ -240,9 +220,7 @@ describe('The SyncConfig store', () => {
|
|||
})
|
||||
vi.spyOn(storage, 'setItem').mockResolvedValue()
|
||||
const store = useSyncConfigStore()
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
|
||||
await store.initSyncConfig({
|
||||
...user,
|
||||
|
|
@ -282,9 +260,7 @@ describe('The SyncConfig store', () => {
|
|||
vi.spyOn(storage, 'setItem').mockResolvedValue()
|
||||
const store = useSyncConfigStore()
|
||||
const setPreference = vi.spyOn(store, 'setPreference')
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
|
||||
await store.initSyncConfig({
|
||||
...user,
|
||||
|
|
@ -318,9 +294,7 @@ describe('The SyncConfig store', () => {
|
|||
})
|
||||
vi.spyOn(storage, 'setItem').mockResolvedValue()
|
||||
const store = useSyncConfigStore()
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
|
||||
await store.initSyncConfig({
|
||||
...user,
|
||||
|
|
@ -357,9 +331,7 @@ describe('The SyncConfig store', () => {
|
|||
const localStore = useLocalConfigStore()
|
||||
localStore.set({ path: 'fontInterface', value: 'Current interface' })
|
||||
const store = useSyncConfigStore()
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
|
||||
await store.initSyncConfig({ ...user })
|
||||
|
||||
|
|
@ -372,10 +344,7 @@ describe('The SyncConfig store', () => {
|
|||
describe('setPreference', () => {
|
||||
it('should set preference and update journal log accordingly', () => {
|
||||
const store = useSyncConfigStore()
|
||||
// PushSyncConfig is very simple but uses vuex to push data
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
store.setPreference({ path: 'simple.palette', value: '1' })
|
||||
expect(store.prefsStorage.simple.palette).to.eql('1')
|
||||
expect(store.prefsStorage._journal).to.have.length(1)
|
||||
|
|
@ -390,10 +359,7 @@ describe('The SyncConfig store', () => {
|
|||
|
||||
it('should keep journal to a minimum', () => {
|
||||
const store = useSyncConfigStore()
|
||||
// PushSyncConfig is very simple but uses vuex to push data
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
store.setPreference({ path: 'simple.palette', value: 1 })
|
||||
store.setPreference({ path: 'simple.palette', value: 2 })
|
||||
store.addCollectionPreference({ path: 'collections.palette', value: 2 })
|
||||
|
|
@ -423,10 +389,7 @@ describe('The SyncConfig store', () => {
|
|||
|
||||
it('should remove duplicate entries from journal', () => {
|
||||
const store = useSyncConfigStore()
|
||||
// PushSyncConfig is very simple but uses vuex to push data
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
store.setPreference({ path: 'simple.palette', value: 1 })
|
||||
store.setPreference({ path: 'simple.palette', value: 1 })
|
||||
store.addCollectionPreference({ path: 'collections.palette', value: 2 })
|
||||
|
|
@ -440,10 +403,7 @@ describe('The SyncConfig store', () => {
|
|||
// TODO We need a proper test for object-based stores
|
||||
it.skip('should remove depth = 3 set/unset entries from journal', () => {
|
||||
const store = useSyncConfigStore()
|
||||
// PushSyncConfig is very simple but uses vuex to push data
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
store.setPreference({ path: 'simple.fontInput', value: 'test' })
|
||||
store.unsetPreference({ path: 'simple.fontInput' })
|
||||
store.updateCache(store, { username: 'test' })
|
||||
|
|
@ -455,10 +415,7 @@ describe('The SyncConfig store', () => {
|
|||
|
||||
it('should not allow unsetting depth <= 2', () => {
|
||||
const store = useSyncConfigStore()
|
||||
// PushSyncConfig is very simple but uses vuex to push data
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
store.setPreference({ path: 'simple.object.foo', value: 1 })
|
||||
expect(() => store.unsetPreference({ path: 'simple' })).to.throw()
|
||||
expect(() =>
|
||||
|
|
@ -468,10 +425,7 @@ describe('The SyncConfig store', () => {
|
|||
|
||||
it('should not allow (un)setting depth > 3', () => {
|
||||
const store = useSyncConfigStore()
|
||||
// PushSyncConfig is very simple but uses vuex to push data
|
||||
store.pushSyncConfig = () => {
|
||||
/* no-op */
|
||||
}
|
||||
store.pushSyncConfig = vi.fn()
|
||||
store.setPreference({ path: 'simple.object', value: {} })
|
||||
expect(() =>
|
||||
store.setPreference({ path: 'simple.object.lv3', value: 1 }),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { dirname, resolve } from 'node:path'
|
|||
import { fileURLToPath } from 'node:url'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
import { playwright } from '@vitest/browser-playwright'
|
||||
import { defineConfig } from 'vite'
|
||||
import eslint from 'vite-plugin-eslint2'
|
||||
|
|
@ -120,6 +121,7 @@ export default defineConfig(async ({ mode, command }) => {
|
|||
const swDest = 'sw-pleroma.js'
|
||||
const alias = {
|
||||
src: '/src',
|
||||
test: '/test',
|
||||
components: '/src/components',
|
||||
...(mode === 'test' ? { vue: 'vue/dist/vue.esm-bundler.js' } : {}),
|
||||
}
|
||||
|
|
@ -141,6 +143,7 @@ export default defineConfig(async ({ mode, command }) => {
|
|||
},
|
||||
},
|
||||
}),
|
||||
vueDevTools(),
|
||||
vueJsx(),
|
||||
buildSwPlugin({ swSrc, swDest }),
|
||||
swMessagesPlugin(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue