diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e277002b8..06fbf45f9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,12 +34,23 @@ check-changelog: - apk add git - sh ./tools/check-changelog -lint: +lint-eslint: stage: lint script: - yarn - - yarn lint - - yarn stylelint + - yarn ci-eslint + +lint-biome: + stage: lint + script: + - yarn + - yarn ci-biome + +lint-stylelint: + stage: lint + script: + - yarn + - yarn ci-stylelint test: stage: test diff --git a/biome.json b/biome.json new file mode 100644 index 000000000..d64639d52 --- /dev/null +++ b/biome.json @@ -0,0 +1,145 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "includes": ["**", "!!**/dist", "!!tools/emojis.json"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space" + }, + "linter": { + "enabled": true, + "domains": { + "vue": "recommended" + }, + "rules": { + "recommended": false, + "complexity": { + "noAdjacentSpacesInRegex": "error", + "noExtraBooleanCast": "error", + "noUselessCatch": "error", + "noUselessEscapeInRegex": "error" + }, + "correctness": { + "noConstAssign": "error", + "noConstantCondition": "error", + "noEmptyCharacterClassInRegex": "error", + "noEmptyPattern": "error", + "noGlobalObjectCalls": "error", + "noInvalidBuiltinInstantiation": "error", + "noInvalidConstructorSuper": "error", + "noNonoctalDecimalEscape": "error", + "noPrecisionLoss": "error", + "noSelfAssign": "error", + "noSetterReturn": "error", + "noSwitchDeclarations": "error", + "noUndeclaredVariables": "error", + "noUnreachable": "error", + "noUnreachableSuper": "error", + "noUnsafeFinally": "error", + "noUnsafeOptionalChaining": "error", + "noUnusedLabels": "error", + "noUnusedPrivateClassMembers": "error", + "noUnusedVariables": "error", + "useIsNan": "error", + "useValidForDirection": "error", + "useValidTypeof": "error", + "useYield": "error" + }, + "suspicious": { + "noAsyncPromiseExecutor": "error", + "noCatchAssign": "error", + "noClassAssign": "error", + "noCompareNegZero": "error", + "noConstantBinaryExpressions": "error", + "noControlCharactersInRegex": "error", + "noDebugger": "error", + "noDuplicateCase": "error", + "noDuplicateClassMembers": "error", + "noDuplicateElseIf": "error", + "noDuplicateObjectKeys": "error", + "noDuplicateParameters": "error", + "noEmptyBlockStatements": "error", + "noFallthroughSwitchClause": "error", + "noFunctionAssign": "error", + "noGlobalAssign": "error", + "noImportAssign": "error", + "noIrregularWhitespace": "error", + "noMisleadingCharacterClass": "error", + "noPrototypeBuiltins": "error", + "noRedeclare": "error", + "noShadowRestrictedNames": "error", + "noSparseArray": "error", + "noUnsafeNegation": "error", + "noUselessRegexBackrefs": "error", + "noWith": "error", + "useGetterReturn": "error" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "asNeeded" + }, + "globals": [] + }, + "overrides": [ + { + "includes": ["**/*.spec.js", "test/fixtures/*.js"], + "javascript": { + "globals": [ + "vi", + "describe", + "it", + "test", + "expect", + "before", + "beforeEach", + "after", + "afterEach" + ] + } + }, + { + "includes": ["**/*.vue"], + "linter": { + "rules": { + "style": { + "useConst": "off", + "useImportType": "off" + }, + "correctness": { + "noUnusedVariables": "off", + "noUnusedImports": "off" + } + } + } + } + ], + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": { + "level": "on", + "options": { + "groups": [ + [":NODE:", ":PACKAGE:", "!src/**", "!@fortawesome/**"], + ":BLANK_LINE:", + [":PATH:", "src/**"], + ":BLANK_LINE:", + "@fortawesome/fontawesome-svg-core", + "@fortawesome/*" + ] + } + } + } + } + } +} diff --git a/build/check-versions.mjs b/build/check-versions.mjs index 73c1eeb15..8c5968a30 100644 --- a/build/check-versions.mjs +++ b/build/check-versions.mjs @@ -1,5 +1,5 @@ -import semver from 'semver' import chalk from 'chalk' +import semver from 'semver' import packageConfig from '../package.json' with { type: 'json' } @@ -7,8 +7,8 @@ var versionRequirements = [ { name: 'node', currentVersion: semver.clean(process.version), - versionRequirement: packageConfig.engines.node - } + versionRequirement: packageConfig.engines.node, + }, ] export default function () { @@ -16,15 +16,22 @@ export default function () { for (let i = 0; i < versionRequirements.length; i++) { const mod = versionRequirements[i] if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { - warnings.push(mod.name + ': ' + - chalk.red(mod.currentVersion) + ' should be ' + - chalk.green(mod.versionRequirement) + warnings.push( + mod.name + + ': ' + + chalk.red(mod.currentVersion) + + ' should be ' + + chalk.green(mod.versionRequirement), ) } } if (warnings.length) { - console.warn(chalk.yellow('\nTo use this template, you must update following to modules:\n')) + console.warn( + chalk.yellow( + '\nTo use this template, you must update following to modules:\n', + ), + ) for (let i = 0; i < warnings.length; i++) { const warning = warnings[i] console.warn(' ' + warning) diff --git a/build/commit_hash.js b/build/commit_hash.js index c104af5d9..c60355804 100644 --- a/build/commit_hash.js +++ b/build/commit_hash.js @@ -1,8 +1,8 @@ import childProcess from 'child_process' -export const getCommitHash = (() => { - const subst = "$Format:%h$" - if(!subst.match(/Format:/)) { +export const getCommitHash = () => { + const subst = '$Format:%h$' + if (!subst.match(/Format:/)) { return subst } else { try { @@ -15,4 +15,4 @@ export const getCommitHash = (() => { return 'UNKNOWN' } } -}) +} diff --git a/build/copy_plugin.js b/build/copy_plugin.js index 694048a36..4f020f359 100644 --- a/build/copy_plugin.js +++ b/build/copy_plugin.js @@ -1,8 +1,8 @@ -import serveStatic from 'serve-static' -import { resolve } from 'node:path' import { cp } from 'node:fs/promises' +import { resolve } from 'node:path' +import serveStatic from 'serve-static' -const getPrefix = s => { +const getPrefix = (s) => { const padEnd = s.endsWith('/') ? s : s + '/' return padEnd.startsWith('/') ? padEnd : '/' + padEnd } @@ -13,28 +13,31 @@ const copyPlugin = ({ inUrl, inFs }) => { let copyTarget const handler = serveStatic(inFs) - return [{ - name: 'copy-plugin-serve', - apply: 'serve', - configureServer (server) { - server.middlewares.use(prefix, handler) - } - }, { - name: 'copy-plugin-build', - apply: 'build', - configResolved (config) { - copyTarget = resolve(config.root, config.build.outDir, subdir) + return [ + { + name: 'copy-plugin-serve', + apply: 'serve', + configureServer(server) { + server.middlewares.use(prefix, handler) + }, }, - closeBundle: { - order: 'post', - sequential: true, - async handler () { - console.info(`Copying '${inFs}' to ${copyTarget}...`) - await cp(inFs, copyTarget, { recursive: true }) - console.info('Done.') - } - } - }] + { + name: 'copy-plugin-build', + apply: 'build', + configResolved(config) { + copyTarget = resolve(config.root, config.build.outDir, subdir) + }, + closeBundle: { + order: 'post', + sequential: true, + async handler() { + console.info(`Copying '${inFs}' to ${copyTarget}...`) + await cp(inFs, copyTarget, { recursive: true }) + console.info('Done.') + }, + }, + }, + ] } export default copyPlugin diff --git a/build/emojis_plugin.js b/build/emojis_plugin.js index aed52066d..7979086dd 100644 --- a/build/emojis_plugin.js +++ b/build/emojis_plugin.js @@ -1,21 +1,23 @@ -import { resolve } from 'node:path' import { access } from 'node:fs/promises' -import { languages, langCodeToCldrName } from '../src/i18n/languages.js' +import { resolve } from 'node:path' + +import { languages } from '../src/i18n/languages.js' const annotationsImportPrefix = '@kazvmoe-infra/unicode-emoji-json/annotations/' const specialAnnotationsLocale = { - ja_easy: 'ja' + ja_easy: 'ja', } -const internalToAnnotationsLocale = (internal) => specialAnnotationsLocale[internal] || internal +const internalToAnnotationsLocale = (internal) => + specialAnnotationsLocale[internal] || internal // This gets all the annotations that are accessible (whose language // can be chosen in the settings). Data for other languages are // discarded because there is no way for it to be fetched. const getAllAccessibleAnnotations = async (projectRoot) => { - const imports = (await Promise.all( - languages - .map(async lang => { + const imports = ( + await Promise.all( + languages.map(async (lang) => { const destLang = internalToAnnotationsLocale(lang) const importModule = `${annotationsImportPrefix}${destLang}.json` const importFile = resolve(projectRoot, 'node_modules', importModule) @@ -23,11 +25,14 @@ const getAllAccessibleAnnotations = async (projectRoot) => { await access(importFile) return `'${lang}': () => import('${importModule}')` } catch (e) { + console.error(e) return } - }))) - .filter(k => k) - .join(',\n') + }), + ) + ) + .filter((k) => k) + .join(',\n') return ` export const annotationsLoader = { @@ -43,21 +48,21 @@ const emojisPlugin = () => { let projectRoot return { name: 'emojis-plugin', - configResolved (conf) { + configResolved(conf) { projectRoot = conf.root }, - resolveId (id) { + resolveId(id) { if (id === emojiAnnotationsId) { return emojiAnnotationsIdResolved } return null }, - async load (id) { + async load(id) { if (id === emojiAnnotationsIdResolved) { return await getAllAccessibleAnnotations(projectRoot) } return null - } + }, } } diff --git a/build/msw_plugin.js b/build/msw_plugin.js index f544348fc..c4e9098c5 100644 --- a/build/msw_plugin.js +++ b/build/msw_plugin.js @@ -1,5 +1,5 @@ -import { resolve } from 'node:path' import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' const target = 'node_modules/msw/lib/mockServiceWorker.js' @@ -8,10 +8,10 @@ const mswPlugin = () => { return { name: 'msw-plugin', apply: 'serve', - configResolved (conf) { + configResolved(conf) { projectRoot = conf.root }, - configureServer (server) { + configureServer(server) { server.middlewares.use(async (req, res, next) => { if (req.path === '/mockServiceWorker.js') { const file = await readFile(resolve(projectRoot, target)) @@ -21,7 +21,7 @@ const mswPlugin = () => { next() } }) - } + }, } } diff --git a/build/service_worker_messages.js b/build/service_worker_messages.js index c078e8563..0948aa919 100644 --- a/build/service_worker_messages.js +++ b/build/service_worker_messages.js @@ -1,11 +1,12 @@ -import { languages, langCodeToJsonName } from '../src/i18n/languages.js' import { readFile } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' +import { langCodeToJsonName, languages } from '../src/i18n/languages.js' + const i18nDir = resolve( dirname(dirname(fileURLToPath(import.meta.url))), - 'src/i18n' + 'src/i18n', ) export const i18nFiles = languages.reduce((acc, lang) => { @@ -16,13 +17,15 @@ export const i18nFiles = languages.reduce((acc, lang) => { }, {}) export const generateServiceWorkerMessages = async () => { - const msgArray = await Promise.all(Object.entries(i18nFiles).map(async ([lang, file]) => { - const fileContent = await readFile(file, 'utf-8') - const msg = { - notifications: JSON.parse(fileContent).notifications || {} - } - return [lang, msg] - })) + const msgArray = await Promise.all( + Object.entries(i18nFiles).map(async ([lang, file]) => { + const fileContent = await readFile(file, 'utf-8') + const msg = { + notifications: JSON.parse(fileContent).notifications || {}, + } + return [lang, msg] + }), + ) return msgArray.reduce((acc, [lang, msg]) => { acc[lang] = msg return acc diff --git a/build/sw_plugin.js b/build/sw_plugin.js index d970c5c09..03c5978d7 100644 --- a/build/sw_plugin.js +++ b/build/sw_plugin.js @@ -1,9 +1,13 @@ -import { fileURLToPath } from 'node:url' -import { dirname, resolve } from 'node:path' import { readFile } from 'node:fs/promises' -import { build } from 'vite' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' import * as esbuild from 'esbuild' -import { generateServiceWorkerMessages, i18nFiles } from './service_worker_messages.js' +import { build } from 'vite' + +import { + generateServiceWorkerMessages, + i18nFiles, +} from './service_worker_messages.js' const getSWMessagesAsText = async () => { const messages = await generateServiceWorkerMessages() @@ -14,14 +18,10 @@ const projectRoot = dirname(dirname(fileURLToPath(import.meta.url))) const swEnvName = 'virtual:pleroma-fe/service_worker_env' const swEnvNameResolved = '\0' + swEnvName const getDevSwEnv = () => `self.serviceWorkerOption = { assets: [] };` -const getProdSwEnv = ({ assets }) => `self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };` +const getProdSwEnv = ({ assets }) => + `self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };` -export const devSwPlugin = ({ - swSrc, - swDest, - transformSW, - alias -}) => { +export const devSwPlugin = ({ swSrc, swDest, transformSW, alias }) => { const swFullSrc = resolve(projectRoot, swSrc) const esbuildAlias = {} Object.entries(alias).forEach(([source, dest]) => { @@ -31,9 +31,10 @@ export const devSwPlugin = ({ return { name: 'dev-sw-plugin', apply: 'serve', - configResolved (conf) { + configResolved() { + /* no-op */ }, - resolveId (id) { + resolveId(id) { const name = id.startsWith('/') ? id.slice(1) : id if (name === swDest) { return swFullSrc @@ -42,7 +43,7 @@ export const devSwPlugin = ({ } return null }, - async load (id) { + async load(id) { if (id === swFullSrc) { return readFile(swFullSrc, 'utf-8') } else if (id === swEnvNameResolved) { @@ -55,7 +56,7 @@ export const devSwPlugin = ({ * during dev, and firefox does not support ESM as service worker * https://bugzilla.mozilla.org/show_bug.cgi?id=1360870 */ - async transform (code, id) { + async transform(code, id) { if (id === swFullSrc && transformSW) { const res = await esbuild.build({ entryPoints: [swSrc], @@ -63,52 +64,54 @@ export const devSwPlugin = ({ write: false, outfile: 'sw-pleroma.js', alias: esbuildAlias, - plugins: [{ - name: 'vite-like-root-resolve', - setup (b) { - b.onResolve( - { filter: new RegExp(/^\//) }, - args => ({ - path: resolve(projectRoot, args.path.slice(1)) - }) - ) - } - }, { - name: 'sw-messages', - setup (b) { - b.onResolve( - { filter: new RegExp('^' + swMessagesName + '$') }, - args => ({ - path: args.path, - namespace: 'sw-messages' + plugins: [ + { + name: 'vite-like-root-resolve', + setup(b) { + b.onResolve({ filter: new RegExp(/^\//) }, (args) => ({ + path: resolve(projectRoot, args.path.slice(1)), })) - b.onLoad( - { filter: /.*/, namespace: 'sw-messages' }, - async () => ({ - contents: await getSWMessagesAsText() + }, + }, + { + name: 'sw-messages', + setup(b) { + b.onResolve( + { filter: new RegExp('^' + swMessagesName + '$') }, + (args) => ({ + path: args.path, + namespace: 'sw-messages', + }), + ) + b.onLoad( + { filter: /.*/, namespace: 'sw-messages' }, + async () => ({ + contents: await getSWMessagesAsText(), + }), + ) + }, + }, + { + name: 'sw-env', + setup(b) { + b.onResolve( + { filter: new RegExp('^' + swEnvName + '$') }, + (args) => ({ + path: args.path, + namespace: 'sw-env', + }), + ) + b.onLoad({ filter: /.*/, namespace: 'sw-env' }, () => ({ + contents: getDevSwEnv(), })) - } - }, { - name: 'sw-env', - setup (b) { - b.onResolve( - { filter: new RegExp('^' + swEnvName + '$') }, - args => ({ - path: args.path, - namespace: 'sw-env' - })) - b.onLoad( - { filter: /.*/, namespace: 'sw-env' }, - () => ({ - contents: getDevSwEnv() - })) - } - }] + }, + }, + ], }) const text = res.outputFiles[0].text return text } - } + }, } } @@ -118,16 +121,13 @@ export const devSwPlugin = ({ // however, we must compile the service worker to iife because of browser support. // Run another vite build just for the service worker targeting iife at // the end of the build. -export const buildSwPlugin = ({ - swSrc, - swDest, -}) => { +export const buildSwPlugin = ({ swSrc, swDest }) => { let config return { name: 'build-sw-plugin', enforce: 'post', apply: 'build', - configResolved (resolvedConfig) { + configResolved(resolvedConfig) { config = { define: resolvedConfig.define, resolve: resolvedConfig.resolve, @@ -138,50 +138,50 @@ export const buildSwPlugin = ({ lib: { entry: swSrc, formats: ['iife'], - name: 'sw_pleroma' + name: 'sw_pleroma', }, emptyOutDir: false, rollupOptions: { output: { - entryFileNames: swDest - } - } + entryFileNames: swDest, + }, + }, }, - configFile: false + configFile: false, } }, generateBundle: { order: 'post', sequential: true, - async handler (_, bundle) { + async handler(_, bundle) { const assets = Object.keys(bundle) - .filter(name => !/\.map$/.test(name)) - .map(name => '/' + name) + .filter((name) => !/\.map$/.test(name)) + .map((name) => '/' + name) config.plugins.push({ name: 'build-sw-env-plugin', - resolveId (id) { + resolveId(id) { if (id === swEnvName) { return swEnvNameResolved } return null }, - load (id) { + load(id) { if (id === swEnvNameResolved) { return getProdSwEnv({ assets }) } return null - } + }, }) - } + }, }, closeBundle: { order: 'post', sequential: true, - async handler () { + async handler() { console.info('Building service worker for production') await build(config) - } - } + }, + }, } } @@ -191,9 +191,9 @@ const swMessagesNameResolved = '\0' + swMessagesName export const swMessagesPlugin = () => { return { name: 'sw-messages-plugin', - resolveId (id) { + resolveId(id) { if (id === swMessagesName) { - Object.values(i18nFiles).forEach(f => { + Object.values(i18nFiles).forEach((f) => { this.addWatchFile(f) }) return swMessagesNameResolved @@ -201,11 +201,11 @@ export const swMessagesPlugin = () => { return null } }, - async load (id) { + async load(id) { if (id === swMessagesNameResolved) { return await getSWMessagesAsText() } return null - } + }, } } diff --git a/build/update-emoji.js b/build/update-emoji.js index 5d578ba61..4ff7e1de8 100644 --- a/build/update-emoji.js +++ b/build/update-emoji.js @@ -1,22 +1,21 @@ - -import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with { type: 'json' } +import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with { + type: 'json', +} import fs from 'fs' -Object.keys(emojis) - .map(k => { - emojis[k].map(e => { - delete e.unicode_version - delete e.emoji_version - delete e.skin_tone_support_unicode_version - }) +Object.keys(emojis).map((k) => { + emojis[k].map((e) => { + delete e.unicode_version + delete e.emoji_version + delete e.skin_tone_support_unicode_version }) +}) const res = {} -Object.keys(emojis) - .map(k => { - const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase() - res[groupId] = emojis[k] - }) +Object.keys(emojis).map((k) => { + const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase() + res[groupId] = emojis[k] +}) console.info('Updating emojis...') fs.writeFileSync('src/assets/emoji.json', JSON.stringify(res)) diff --git a/changelog.d/biome.skip b/changelog.d/biome.skip new file mode 100644 index 000000000..e69de29bb diff --git a/eslint.config.mjs b/eslint.config.mjs index 01bdb2038..417ff8cf3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,37 +1,34 @@ -import vue from "eslint-plugin-vue"; -import js from "@eslint/js"; -import globals from "globals"; +import js from '@eslint/js' +import { defineConfig, globalIgnores } from 'eslint/config' +import vue from 'eslint-plugin-vue' +import globals from 'globals' - -export default [ +export default defineConfig([ ...vue.configs['flat/recommended'], - js.configs.recommended, + globalIgnores(['**/*.js', 'build/', 'dist/', 'config/']), { - files: ["**/*.js", "**/*.mjs", "**/*.vue"], - ignores: ["build/*.js", "config/*.js"], - + files: ['src/**/*.vue'], + plugins: { js }, + extends: ['js/recommended'], languageOptions: { ecmaVersion: 2024, - sourceType: "module", + sourceType: 'module', parserOptions: { - parser: "@babel/eslint-parser", + parser: '@babel/eslint-parser', }, globals: { ...globals.browser, ...globals.vitest, ...globals.chai, ...globals.commonjs, - ...globals.serviceworker - } + ...globals.serviceworker, + }, }, rules: { - 'arrow-parens': 0, - 'generator-star-spacing': 0, - 'no-debugger': 0, 'vue/require-prop-types': 0, 'vue/multi-word-component-names': 0, - } - } -] + }, + }, +]) diff --git a/package.json b/package.json index 01a5fad37..b00ed545a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pleroma_fe", - "version": "2.9.3", + "version": "2.10.0", "description": "Pleroma frontend, the default frontend of Pleroma social network server", "author": "Pleroma contributors ", "private": false, @@ -13,9 +13,11 @@ "e2e:pw": "playwright test --config test/e2e-playwright/playwright.config.mjs", "e2e": "sh ./tools/e2e/run.sh", "test": "yarn run unit && yarn run e2e", - "stylelint": "yarn exec stylelint '**/*.scss' '**/*.vue'", - "lint": "eslint src test/unit/specs test/e2e/specs test/e2e-playwright/specs test/e2e-playwright/playwright.config.mjs", - "lint-fix": "eslint --fix src test/unit/specs test/e2e/specs test/e2e-playwright/specs test/e2e-playwright/playwright.config.mjs" + "ci-biome": "yarn exec biome check", + "ci-eslint": "yarn exec eslint", + "ci-stylelint": "yarn exec stylelint '**/*.scss' '**/*.vue'", + "lint": "yarn ci-biome; yarn ci-eslint; yarn ci-stylelint", + "lint-fix": "yarn exec eslint --fix; yarn exec stylelint '**/*.scss' '**/*.vue' --fix; biome check --write" }, "dependencies": { "@babel/runtime": "7.28.4", @@ -60,6 +62,7 @@ "@babel/plugin-transform-runtime": "7.28.5", "@babel/preset-env": "7.28.5", "@babel/register": "7.28.3", + "@biomejs/biome": "2.3.11", "@ungap/event-target": "0.2.4", "@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue-jsx": "^4.1.1", @@ -78,7 +81,6 @@ "cross-spawn": "7.0.6", "custom-event-polyfill": "1.0.7", "eslint": "9.39.2", - "vue-eslint-parser": "10.2.0", "eslint-config-standard": "17.1.0", "eslint-formatter-friendly": "7.0.0", "eslint-plugin-import": "2.32.0", @@ -113,7 +115,8 @@ "vite": "^6.1.0", "vite-plugin-eslint2": "^5.0.3", "vite-plugin-stylelint": "^6.0.0", - "vitest": "^3.0.7" + "vitest": "^3.0.7", + "vue-eslint-parser": "10.2.0" }, "type": "module", "engines": { diff --git a/postcss.config.js b/postcss.config.js index 95ebbf2a6..b7fc12838 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,7 +1,5 @@ import autoprefixer from 'autoprefixer' export default { - plugins: [ - autoprefixer - ] + plugins: [autoprefixer], } diff --git a/public/static/palettes/index.json b/public/static/palettes/index.json index 2cd110d1e..3c4e37e44 100644 --- a/public/static/palettes/index.json +++ b/public/static/palettes/index.json @@ -1,6 +1,26 @@ { - "pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], - "pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], + "pleroma-dark": [ + "Pleroma Dark", + "#121a24", + "#182230", + "#b9b9ba", + "#d8a070", + "#d31014", + "#0fa00f", + "#0095ff", + "#ffa500" + ], + "pleroma-light": [ + "Pleroma Light", + "#f2f4f6", + "#dbe0e8", + "#304055", + "#f86f0f", + "#d31014", + "#0fa00f", + "#0095ff", + "#ffa500" + ], "classic-dark": { "name": "Classic Dark", "bg": "#161c20", @@ -12,8 +32,28 @@ "cBlue": "#0095ff", "cOrange": "#ffa500" }, - "bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"], - "pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"], + "bird": [ + "Bird", + "#f8fafd", + "#e6ecf0", + "#14171a", + "#0084b8", + "#e0245e", + "#17bf63", + "#1b95e0", + "#fab81e" + ], + "pleroma-amoled": [ + "Pleroma Dark AMOLED", + "#000000", + "#111111", + "#b0b0b1", + "#d8a070", + "#aa0000", + "#0fa00f", + "#0095ff", + "#d59500" + ], "tomorrow-night": { "name": "Tomorrow Night", "bg": "#1d1f21", @@ -36,8 +76,28 @@ "cGreen": "#50FA7B", "cOrange": "#FFB86C" }, - "ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ], - "monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ], + "ir-black": [ + "Ir Black", + "#000000", + "#242422", + "#b5b3aa", + "#ff6c60", + "#FF6C60", + "#A8FF60", + "#96CBFE", + "#FFFFB6" + ], + "monokai": [ + "Monokai", + "#272822", + "#383830", + "#f8f8f2", + "#f92672", + "#F92672", + "#a6e22e", + "#66d9ef", + "#f4bf75" + ], "purple-stream": { "name": "Purple stream", "bg": "#17171A", diff --git a/public/static/splash.css b/public/static/splash.css index f56f33d07..caf57dacb 100644 --- a/public/static/splash.css +++ b/public/static/splash.css @@ -65,16 +65,17 @@ body { z-index: 2; grid-template-rows: repeat(8, 1fr); grid-template-columns: repeat(5, 1fr); - grid-template-areas: "P P . L L" - "P P . L L" - "P P . L L" - "P P . L L" - "P P . . ." - "P P . . ." - "P P . E E" - "P P . E E"; + grid-template-areas: + "P P . L L" + "P P . L L" + "P P . L L" + "P P . L L" + "P P . . ." + "P P . . ." + "P P . E E" + "P P . E E"; - --logoChunkSize: calc(2em * 0.5 * var(--scale)) + --logoChunkSize: calc(2em * 0.5 * var(--scale)); } .chunk { @@ -84,7 +85,7 @@ body { #chunk-P { grid-area: P; - border-top-left-radius: calc(var(--logoChunkSize) / 2); + border-top-left-radius: calc(var(--logoChunkSize) / 2); } #chunk-L { diff --git a/renovate.json b/renovate.json index 39a2b6e9a..4bd832f5f 100644 --- a/renovate.json +++ b/renovate.json @@ -1,6 +1,4 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:base" - ] + "extends": ["config:base"] } diff --git a/src/App.js b/src/App.js index 0027d908a..33645c63d 100644 --- a/src/App.js +++ b/src/App.js @@ -1,34 +1,36 @@ -import UserPanel from './components/user_panel/user_panel.vue' -import NavPanel from './components/nav_panel/nav_panel.vue' -import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue' -import FeaturesPanel from './components/features_panel/features_panel.vue' -import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue' -import ShoutPanel from './components/shout_panel/shout_panel.vue' -import MediaModal from './components/media_modal/media_modal.vue' -import SideDrawer from './components/side_drawer/side_drawer.vue' -import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue' -import MobileNav from './components/mobile_nav/mobile_nav.vue' -import DesktopNav from './components/desktop_nav/desktop_nav.vue' -import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue' -import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue' -import PostStatusModal from './components/post_status_modal/post_status_modal.vue' -import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue' -import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue' -import { getOrCreateServiceWorker } from './services/sw/sw' -import { windowWidth, windowHeight } from './services/window_utils/window_utils' -import { mapGetters } from 'vuex' -import { defineAsyncComponent } from 'vue' -import { useShoutStore } from './stores/shout' -import { useInterfaceStore } from './stores/interface' - import { throttle } from 'lodash' +import { defineAsyncComponent } from 'vue' +import { mapGetters } from 'vuex' + +import DesktopNav from './components/desktop_nav/desktop_nav.vue' +import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue' +import FeaturesPanel from './components/features_panel/features_panel.vue' +import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue' +import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue' +import MediaModal from './components/media_modal/media_modal.vue' +import MobileNav from './components/mobile_nav/mobile_nav.vue' +import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue' +import NavPanel from './components/nav_panel/nav_panel.vue' +import PostStatusModal from './components/post_status_modal/post_status_modal.vue' +import ShoutPanel from './components/shout_panel/shout_panel.vue' +import SideDrawer from './components/side_drawer/side_drawer.vue' +import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue' +import UserPanel from './components/user_panel/user_panel.vue' +import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue' +import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue' +import { getOrCreateServiceWorker } from './services/sw/sw' +import { windowHeight, windowWidth } from './services/window_utils/window_utils' +import { useInterfaceStore } from './stores/interface' +import { useShoutStore } from './stores/shout' export default { name: 'app', components: { UserPanel, NavPanel, - Notifications: defineAsyncComponent(() => import('./components/notifications/notifications.vue')), + Notifications: defineAsyncComponent( + () => import('./components/notifications/notifications.vue'), + ), InstanceSpecificPanel, FeaturesPanel, WhoToFollowPanel, @@ -38,29 +40,33 @@ export default { MobilePostStatusButton, MobileNav, DesktopNav, - SettingsModal: defineAsyncComponent(() => import('./components/settings_modal/settings_modal.vue')), - UpdateNotification: defineAsyncComponent(() => import('./components/update_notification/update_notification.vue')), + SettingsModal: defineAsyncComponent( + () => import('./components/settings_modal/settings_modal.vue'), + ), + UpdateNotification: defineAsyncComponent( + () => import('./components/update_notification/update_notification.vue'), + ), UserReportingModal, PostStatusModal, EditStatusModal, StatusHistoryModal, - GlobalNoticeList + GlobalNoticeList, }, data: () => ({ - mobileActivePanel: 'timeline' + mobileActivePanel: 'timeline', }), watch: { - themeApplied () { + themeApplied() { this.removeSplash() }, - currentTheme () { + currentTheme() { this.setThemeBodyClass() }, - layoutType () { + layoutType() { document.getElementById('modal').classList = ['-' + this.layoutType] - } + }, }, - created () { + created() { // Load the locale from the storage const val = this.$store.getters.mergedConfig.interfaceLanguage this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) @@ -70,7 +76,7 @@ export default { this.updateScrollState = throttle(this.scrollHandler, 200) this.updateMobileState = throttle(this.resizeHandler, 200) }, - mounted () { + mounted() { window.addEventListener('resize', this.updateMobileState) this.scrollParent.addEventListener('scroll', this.updateScrollState) @@ -80,108 +86,145 @@ export default { } getOrCreateServiceWorker() }, - unmounted () { + unmounted() { window.removeEventListener('resize', this.updateMobileState) this.scrollParent.removeEventListener('scroll', this.updateScrollState) }, computed: { - themeApplied () { + themeApplied() { return useInterfaceStore().themeApplied }, - currentTheme () { + currentTheme() { if (useInterfaceStore().styleDataUsed) { - const styleMeta = useInterfaceStore().styleDataUsed.find(x => x.component === '@meta') + const styleMeta = useInterfaceStore().styleDataUsed.find( + (x) => x.component === '@meta', + ) if (styleMeta !== undefined) { - return styleMeta.directives.name.replaceAll(" ", "-").toLowerCase() + return styleMeta.directives.name.replaceAll(' ', '-').toLowerCase() } } return 'stock' }, - layoutModalClass () { + layoutModalClass() { return '-' + this.layoutType }, - classes () { + classes() { return [ { '-reverse': this.reverseLayout, '-no-sticky-headers': this.noSticky, - '-has-new-post-button': this.newPostButtonShown + '-has-new-post-button': this.newPostButtonShown, }, - '-' + this.layoutType + '-' + this.layoutType, ] }, - navClasses () { + navClasses() { const { navbarColumnStretch } = this.$store.getters.mergedConfig return [ '-' + this.layoutType, - ...(navbarColumnStretch ? ['-column-stretch'] : []) + ...(navbarColumnStretch ? ['-column-stretch'] : []), ] }, - currentUser () { return this.$store.state.users.currentUser }, - userBackground () { return this.currentUser.background_image }, - instanceBackground () { + currentUser() { + return this.$store.state.users.currentUser + }, + userBackground() { + return this.currentUser.background_image + }, + instanceBackground() { return this.mergedConfig.hideInstanceWallpaper ? null : this.$store.state.instance.background }, - background () { return this.userBackground || this.instanceBackground }, - bgStyle () { + background() { + return this.userBackground || this.instanceBackground + }, + bgStyle() { if (this.background) { return { - '--body-background-image': `url(${this.background})` + '--body-background-image': `url(${this.background})`, } } }, - shout () { return useShoutStore().joined }, - suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled }, - showInstanceSpecificPanel () { - return this.$store.state.instance.showInstanceSpecificPanel && + shout() { + return useShoutStore().joined + }, + suggestionsEnabled() { + return this.$store.state.instance.suggestionsEnabled + }, + showInstanceSpecificPanel() { + return ( + this.$store.state.instance.showInstanceSpecificPanel && !this.$store.getters.mergedConfig.hideISP && this.$store.state.instance.instanceSpecificPanelContent + ) }, - isChats () { + isChats() { return this.$route.name === 'chat' || this.$route.name === 'chats' }, - isListEdit () { + isListEdit() { return this.$route.name === 'lists-edit' }, - newPostButtonShown () { + newPostButtonShown() { if (this.isChats) return false if (this.isListEdit) return false - return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile' + return ( + this.$store.getters.mergedConfig.alwaysShowNewPostButton || + this.layoutType === 'mobile' + ) }, - showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }, - editingAvailable () { return this.$store.state.instance.editingAvailable }, - shoutboxPosition () { + showFeaturesPanel() { + return this.$store.state.instance.showFeaturesPanel + }, + editingAvailable() { + return this.$store.state.instance.editingAvailable + }, + shoutboxPosition() { return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false }, - hideShoutbox () { + hideShoutbox() { return this.$store.getters.mergedConfig.hideShoutbox }, - layoutType () { return useInterfaceStore().layoutType }, - privateMode () { return this.$store.state.instance.private }, - reverseLayout () { - const { thirdColumnMode, sidebarRight: reverseSetting } = this.$store.getters.mergedConfig + layoutType() { + return useInterfaceStore().layoutType + }, + privateMode() { + return this.$store.state.instance.private + }, + reverseLayout() { + const { thirdColumnMode, sidebarRight: reverseSetting } = + this.$store.getters.mergedConfig if (this.layoutType !== 'wide') { return reverseSetting } else { - return thirdColumnMode === 'notifications' ? reverseSetting : !reverseSetting + return thirdColumnMode === 'notifications' + ? reverseSetting + : !reverseSetting } }, - noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders }, - showScrollbars () { return this.$store.getters.mergedConfig.showScrollbars }, - scrollParent () { return window; /* this.$refs.appContentRef */ }, - ...mapGetters(['mergedConfig']) + noSticky() { + return this.$store.getters.mergedConfig.disableStickyHeaders + }, + showScrollbars() { + return this.$store.getters.mergedConfig.showScrollbars + }, + scrollParent() { + return window /* this.$refs.appContentRef */ + }, + ...mapGetters(['mergedConfig']), }, methods: { - resizeHandler () { + resizeHandler() { useInterfaceStore().setLayoutWidth(windowWidth()) useInterfaceStore().setLayoutHeight(windowHeight()) }, - scrollHandler () { - const scrollPosition = this.scrollParent === window ? window.scrollY : this.scrollParent.scrollTop + scrollHandler() { + const scrollPosition = + this.scrollParent === window + ? window.scrollY + : this.scrollParent.scrollTop if (scrollPosition != 0) { this.$refs.appContentRef.classList.add(['-scrolled']) @@ -189,10 +232,10 @@ export default { this.$refs.appContentRef.classList.remove(['-scrolled']) } }, - setThemeBodyClass () { + setThemeBodyClass() { const themeName = this.currentTheme const classList = Array.from(document.body.classList) - const oldTheme = classList.filter(c => c.startsWith('theme-')) + const oldTheme = classList.filter((c) => c.startsWith('theme-')) if (themeName !== null && themeName !== '') { const newTheme = `theme-${themeName.toLowerCase()}` @@ -208,8 +251,10 @@ export default { document.body.classList.remove(...oldTheme) } }, - removeSplash () { - document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4)) + removeSplash() { + document.querySelector('#status').textContent = this.$t( + 'splash.fun_' + Math.ceil(Math.random() * 4), + ) const splashscreenRoot = document.querySelector('#splash') splashscreenRoot.addEventListener('transitionend', () => { splashscreenRoot.remove() @@ -219,6 +264,6 @@ export default { }, 600) splashscreenRoot.classList.add('hidden') document.querySelector('#app').classList.remove('hidden') - } - } + }, + }, } diff --git a/src/boot/after_store.js b/src/boot/after_store.js index ad994af6a..1a2be5bd7 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -1,30 +1,39 @@ /* global process */ + +import vClickOutside from 'click-outside-vue3' import { createApp } from 'vue' import { createRouter, createWebHistory } from 'vue-router' -import vClickOutside from 'click-outside-vue3' import VueVirtualScroller from 'vue-virtual-scroller' import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' -import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome' -import { config } from '@fortawesome/fontawesome-svg-core'; +import { config } from '@fortawesome/fontawesome-svg-core' +import { + FontAwesomeIcon, + FontAwesomeLayers, +} from '@fortawesome/vue-fontawesome' + config.autoAddCss = false -import App from '../App.vue' -import routes from './routes' import VBodyScrollLock from 'src/directives/body_scroll_lock' - -import { windowWidth, windowHeight } from '../services/window_utils/window_utils' -import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' -import { applyConfig } from '../services/style_setter/style_setter.js' -import FaviconService from '../services/favicon_service/favicon_service.js' -import { initServiceWorker, updateFocus } from '../services/sw/sw.js' - -import { useOAuthStore } from 'src/stores/oauth' -import { useI18nStore } from 'src/stores/i18n' -import { useInterfaceStore } from 'src/stores/interface' +import { + instanceDefaultConfig, + staticOrApiConfigDefault, +} from 'src/modules/default_config_state.js' import { useAnnouncementsStore } from 'src/stores/announcements' import { useAuthFlowStore } from 'src/stores/auth_flow' -import { staticOrApiConfigDefault, instanceDefaultConfig } from 'src/modules/default_config_state.js' +import { useI18nStore } from 'src/stores/i18n' +import { useInterfaceStore } from 'src/stores/interface' +import { useOAuthStore } from 'src/stores/oauth' +import App from '../App.vue' +import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' +import FaviconService from '../services/favicon_service/favicon_service.js' +import { applyConfig } from '../services/style_setter/style_setter.js' +import { initServiceWorker, updateFocus } from '../services/sw/sw.js' +import { + windowHeight, + windowWidth, +} from '../services/window_utils/window_utils' +import routes from './routes' let staticInitialResults = null @@ -33,7 +42,9 @@ const parsedInitialResults = () => { return null } if (!staticInitialResults) { - staticInitialResults = JSON.parse(document.getElementById('initial-results').textContent) + staticInitialResults = JSON.parse( + document.getElementById('initial-results').textContent, + ) } return staticInitialResults } @@ -55,7 +66,7 @@ const preloadFetch = async (request) => { return { ok: true, json: () => requestData, - text: () => requestData + text: () => requestData, } } @@ -67,17 +78,35 @@ const getInstanceConfig = async ({ store }) => { const textlimit = data.max_toot_chars const vapidPublicKey = data.pleroma.vapid_public_key - store.dispatch('setInstanceOption', { name: 'pleromaExtensionsAvailable', value: data.pleroma }) - store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit }) - store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required }) - store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma?.metadata.birthday_required }) - store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma?.metadata.birthday_min_age || 0 }) + store.dispatch('setInstanceOption', { + name: 'pleromaExtensionsAvailable', + value: data.pleroma, + }) + store.dispatch('setInstanceOption', { + name: 'textlimit', + value: textlimit, + }) + store.dispatch('setInstanceOption', { + name: 'accountApprovalRequired', + value: data.approval_required, + }) + store.dispatch('setInstanceOption', { + name: 'birthdayRequired', + value: !!data.pleroma?.metadata.birthday_required, + }) + store.dispatch('setInstanceOption', { + name: 'birthdayMinAge', + value: data.pleroma?.metadata.birthday_min_age || 0, + }) if (vapidPublicKey) { - store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) + store.dispatch('setInstanceOption', { + name: 'vapidPublicKey', + value: vapidPublicKey, + }) } } else { - throw (res) + throw res } } catch (error) { console.error('Could not load instance config, potentially fatal') @@ -94,10 +123,12 @@ const getBackendProvidedConfig = async () => { const data = await res.json() return data.pleroma_fe } else { - throw (res) + throw res } } catch (error) { - console.error('Could not load backend-provided frontend config, potentially fatal') + console.error( + 'Could not load backend-provided frontend config, potentially fatal', + ) console.error(error) } } @@ -108,7 +139,7 @@ const getStaticConfig = async () => { if (res.ok) { return res.json() } else { - throw (res) + throw res } } catch (error) { console.warn('Failed to load static/config.json, continuing without it.') @@ -149,7 +180,7 @@ const getTOS = async ({ store }) => { const html = await res.text() store.dispatch('setInstanceOption', { name: 'tos', value: html }) } else { - throw (res) + throw res } } catch (e) { console.warn("Can't load TOS\n", e) @@ -161,9 +192,12 @@ const getInstancePanel = async ({ store }) => { const res = await preloadFetch('/instance/panel.html') if (res.ok) { const html = await res.text() - store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html }) + store.dispatch('setInstanceOption', { + name: 'instanceSpecificPanelContent', + value: html, + }) } else { - throw (res) + throw res } } catch (e) { console.warn("Can't load instance panel\n", e) @@ -175,25 +209,27 @@ const getStickers = async ({ store }) => { const res = await window.fetch('/static/stickers.json') if (res.ok) { const values = await res.json() - const stickers = (await Promise.all( - Object.entries(values).map(async ([name, path]) => { - const resPack = await window.fetch(path + 'pack.json') - let meta = {} - if (resPack.ok) { - meta = await resPack.json() - } - return { - pack: name, - path, - meta - } - }) - )).sort((a, b) => { + const stickers = ( + await Promise.all( + Object.entries(values).map(async ([name, path]) => { + const resPack = await window.fetch(path + 'pack.json') + let meta = {} + if (resPack.ok) { + meta = await resPack.json() + } + return { + pack: name, + path, + meta, + } + }), + ) + ).sort((a, b) => { return a.meta.title.localeCompare(b.meta.title) }) store.dispatch('setInstanceOption', { name: 'stickers', value: stickers }) } else { - throw (res) + throw res } } catch (e) { console.warn("Can't load stickers\n", e) @@ -203,13 +239,19 @@ const getStickers = async ({ store }) => { const getAppSecret = async ({ store }) => { const oauth = useOAuthStore() if (oauth.userToken) { - store.commit('setBackendInteractor', backendInteractorService(oauth.getToken)) + store.commit( + 'setBackendInteractor', + backendInteractorService(oauth.getToken), + ) } } const resolveStaffAccounts = ({ store, accounts }) => { - const nicknames = accounts.map(uri => uri.split('/').pop()) - store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames }) + const nicknames = accounts.map((uri) => uri.split('/').pop()) + store.dispatch('setInstanceOption', { + name: 'staffAccounts', + value: nicknames, + }) } const getNodeInfo = async ({ store }) => { @@ -220,77 +262,167 @@ const getNodeInfo = async ({ store }) => { const data = await res.json() const metadata = data.metadata const features = metadata.features - store.dispatch('setInstanceOption', { name: 'name', value: metadata.nodeName }) - store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations }) - store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') }) - store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') }) - store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') }) - store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') }) + store.dispatch('setInstanceOption', { + name: 'name', + value: metadata.nodeName, + }) + store.dispatch('setInstanceOption', { + name: 'registrationOpen', + value: data.openRegistrations, + }) + store.dispatch('setInstanceOption', { + name: 'mediaProxyAvailable', + value: features.includes('media_proxy'), + }) + store.dispatch('setInstanceOption', { + name: 'safeDM', + value: features.includes('safe_dm_mentions'), + }) + store.dispatch('setInstanceOption', { + name: 'shoutAvailable', + value: features.includes('chat'), + }) + store.dispatch('setInstanceOption', { + name: 'pleromaChatMessagesAvailable', + value: features.includes('pleroma_chat_messages'), + }) store.dispatch('setInstanceOption', { name: 'pleromaCustomEmojiReactionsAvailable', value: features.includes('pleroma_custom_emoji_reactions') || - features.includes('custom_emoji_reactions') + features.includes('custom_emoji_reactions'), + }) + store.dispatch('setInstanceOption', { + name: 'pleromaBookmarkFoldersAvailable', + value: features.includes('pleroma:bookmark_folders'), + }) + store.dispatch('setInstanceOption', { + name: 'gopherAvailable', + value: features.includes('gopher'), + }) + store.dispatch('setInstanceOption', { + name: 'pollsAvailable', + value: features.includes('polls'), + }) + store.dispatch('setInstanceOption', { + name: 'editingAvailable', + value: features.includes('editing'), + }) + store.dispatch('setInstanceOption', { + name: 'pollLimits', + value: metadata.pollLimits, + }) + store.dispatch('setInstanceOption', { + name: 'mailerEnabled', + value: metadata.mailerEnabled, + }) + store.dispatch('setInstanceOption', { + name: 'quotingAvailable', + value: features.includes('quote_posting'), + }) + store.dispatch('setInstanceOption', { + name: 'groupActorAvailable', + value: features.includes('pleroma:group_actors'), + }) + store.dispatch('setInstanceOption', { + name: 'blockExpiration', + value: features.includes('pleroma:block_expiration'), + }) + store.dispatch('setInstanceOption', { + name: 'localBubbleInstances', + value: metadata.localBubbleInstances ?? [], }) - store.dispatch('setInstanceOption', { name: 'pleromaBookmarkFoldersAvailable', value: features.includes('pleroma:bookmark_folders') }) - store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) - store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') }) - store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') }) - store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits }) - store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled }) - store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') }) - store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') }) - store.dispatch('setInstanceOption', { name: 'blockExpiration', value: features.includes('pleroma:block_expiration') }) - store.dispatch('setInstanceOption', { name: 'localBubbleInstances', value: metadata.localBubbleInstances ?? [] }) const uploadLimits = metadata.uploadLimits - store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) }) - store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadLimits.avatar) }) - store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadLimits.background) }) - store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadLimits.banner) }) - store.dispatch('setInstanceOption', { name: 'fieldsLimits', value: metadata.fieldsLimits }) + store.dispatch('setInstanceOption', { + name: 'uploadlimit', + value: parseInt(uploadLimits.general), + }) + store.dispatch('setInstanceOption', { + name: 'avatarlimit', + value: parseInt(uploadLimits.avatar), + }) + store.dispatch('setInstanceOption', { + name: 'backgroundlimit', + value: parseInt(uploadLimits.background), + }) + store.dispatch('setInstanceOption', { + name: 'bannerlimit', + value: parseInt(uploadLimits.banner), + }) + store.dispatch('setInstanceOption', { + name: 'fieldsLimits', + value: metadata.fieldsLimits, + }) - store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames }) - store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats }) + store.dispatch('setInstanceOption', { + name: 'restrictedNicknames', + value: metadata.restrictedNicknames, + }) + store.dispatch('setInstanceOption', { + name: 'postFormats', + value: metadata.postFormats, + }) const suggestions = metadata.suggestions - store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled }) - store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web }) + store.dispatch('setInstanceOption', { + name: 'suggestionsEnabled', + value: suggestions.enabled, + }) + store.dispatch('setInstanceOption', { + name: 'suggestionsWeb', + value: suggestions.web, + }) const software = data.software - store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version }) - store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository }) + store.dispatch('setInstanceOption', { + name: 'backendVersion', + value: software.version, + }) + store.dispatch('setInstanceOption', { + name: 'backendRepository', + value: software.repository, + }) const priv = metadata.private store.dispatch('setInstanceOption', { name: 'private', value: priv }) const frontendVersion = window.___pleromafe_commit_hash - store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion }) + store.dispatch('setInstanceOption', { + name: 'frontendVersion', + value: frontendVersion, + }) const federation = metadata.federation store.dispatch('setInstanceOption', { name: 'tagPolicyAvailable', - value: typeof federation.mrf_policies === 'undefined' - ? false - : metadata.federation.mrf_policies.includes('TagPolicy') + value: + typeof federation.mrf_policies === 'undefined' + ? false + : metadata.federation.mrf_policies.includes('TagPolicy'), }) - store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation }) + store.dispatch('setInstanceOption', { + name: 'federationPolicy', + value: federation, + }) store.dispatch('setInstanceOption', { name: 'federating', - value: typeof federation.enabled === 'undefined' - ? true - : federation.enabled + value: + typeof federation.enabled === 'undefined' ? true : federation.enabled, }) const accountActivationRequired = metadata.accountActivationRequired - store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired }) + store.dispatch('setInstanceOption', { + name: 'accountActivationRequired', + value: accountActivationRequired, + }) const accounts = metadata.staffAccounts resolveStaffAccounts({ store, accounts }) } else { - throw (res) + throw res } } catch (e) { console.warn('Could not load nodeinfo') @@ -300,7 +432,10 @@ const getNodeInfo = async ({ store }) => { const setConfig = async ({ store }) => { // apiConfig, staticConfig - const configInfos = await Promise.all([getBackendProvidedConfig({ store }), getStaticConfig()]) + const configInfos = await Promise.all([ + getBackendProvidedConfig({ store }), + getStaticConfig(), + ]) const apiConfig = configInfos[0] const staticConfig = configInfos[1] @@ -331,29 +466,37 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { if (process.env.NODE_ENV === 'development') { // do some checks to avoid common errors if (!Object.keys(allStores).length) { - throw new Error('No stores are available. Check the code in src/boot/after_store.js') + throw new Error( + 'No stores are available. Check the code in src/boot/after_store.js', + ) } } await Promise.all( - Object.entries(allStores) - .map(async ([name, mod]) => { - const isStoreName = name => name.startsWith('use') - if (process.env.NODE_ENV === 'development') { - if (Object.keys(mod).filter(isStoreName).length !== 1) { - throw new Error('Each store file must export exactly one store as a named export. Check your code in src/stores/') - } + Object.entries(allStores).map(async ([name, mod]) => { + const isStoreName = (name) => name.startsWith('use') + if (process.env.NODE_ENV === 'development') { + if (Object.keys(mod).filter(isStoreName).length !== 1) { + throw new Error( + 'Each store file must export exactly one store as a named export. Check your code in src/stores/', + ) } - const storeFuncName = Object.keys(mod).find(isStoreName) - if (storeFuncName && typeof mod[storeFuncName] === 'function') { - const p = mod[storeFuncName]().$persistLoaded - if (!(p instanceof Promise)) { - throw new Error(`${name} store's $persistLoaded is not a Promise. The persist plugin is not applied.`) - } - await p - } else { - throw new Error(`Store module ${name} does not export a 'use...' function`) + } + const storeFuncName = Object.keys(mod).find(isStoreName) + if (storeFuncName && typeof mod[storeFuncName] === 'function') { + const p = mod[storeFuncName]().$persistLoaded + if (!(p instanceof Promise)) { + throw new Error( + `${name} store's $persistLoaded is not a Promise. The persist plugin is not applied.`, + ) } - })) + await p + } else { + throw new Error( + `Store module ${name} does not export a 'use...' function`, + ) + } + }), + ) } try { @@ -364,7 +507,10 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { } if (storageError) { - useInterfaceStore().pushGlobalNotice({ messageKey: 'errors.storage_unavailable', level: 'error' }) + useInterfaceStore().pushGlobalNotice({ + messageKey: 'errors.storage_unavailable', + level: 'error', + }) } useInterfaceStore().setLayoutWidth(windowWidth()) @@ -376,12 +522,19 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { window.addEventListener('focus', () => updateFocus()) const overrides = window.___pleromafe_dev_overrides || {} - const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin + const server = + typeof overrides.target !== 'undefined' + ? overrides.target + : window.location.origin store.dispatch('setInstanceOption', { name: 'server', value: server }) await setConfig({ store }) try { - await useInterfaceStore().applyTheme().catch((e) => { console.error('Error setting theme', e) }) + await useInterfaceStore() + .applyTheme() + .catch((e) => { + console.error('Error setting theme', e) + }) } catch (e) { window.splashError(e) return Promise.reject(e) @@ -395,8 +548,8 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { checkOAuthToken({ store }), getInstancePanel({ store }), getNodeInfo({ store }), - getInstanceConfig({ store }) - ]).catch(e => Promise.reject(e)) + getInstanceConfig({ store }), + ]).catch((e) => Promise.reject(e)) // Start fetching things that don't need to block the UI store.dispatch('fetchMutes') @@ -409,11 +562,11 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { history: createWebHistory(), routes: routes(store), scrollBehavior: (to, _from, savedPosition) => { - if (to.matched.some(m => m.meta.dontScroll)) { + if (to.matched.some((m) => m.meta.dontScroll)) { return false } return savedPosition || { left: 0, top: 0 } - } + }, }) useI18nStore().setI18n(i18n) diff --git a/src/boot/routes.js b/src/boot/routes.js index 02abf8ce6..3296755a1 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -1,35 +1,36 @@ -import PublicTimeline from 'components/public_timeline/public_timeline.vue' -import BubbleTimeline from 'components/bubble_timeline/bubble_timeline.vue' -import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue' -import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue' -import TagTimeline from 'components/tag_timeline/tag_timeline.vue' -import BookmarkTimeline from 'components/bookmark_timeline/bookmark_timeline.vue' -import ConversationPage from 'components/conversation-page/conversation-page.vue' -import Interactions from 'components/interactions/interactions.vue' -import DMs from 'components/dm_timeline/dm_timeline.vue' -import ChatList from 'components/chat_list/chat_list.vue' -import Chat from 'components/chat/chat.vue' -import UserProfile from 'components/user_profile/user_profile.vue' -import Search from 'components/search/search.vue' -import Registration from 'components/registration/registration.vue' -import PasswordReset from 'components/password_reset/password_reset.vue' -import FollowRequests from 'components/follow_requests/follow_requests.vue' -import OAuthCallback from 'components/oauth_callback/oauth_callback.vue' -import Notifications from 'components/notifications/notifications.vue' -import AuthForm from 'components/auth_form/auth_form.js' -import ShoutPanel from 'components/shout_panel/shout_panel.vue' -import WhoToFollow from 'components/who_to_follow/who_to_follow.vue' import About from 'components/about/about.vue' -import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue' -import Lists from 'components/lists/lists.vue' -import ListsTimeline from 'components/lists_timeline/lists_timeline.vue' -import ListsEdit from 'components/lists_edit/lists_edit.vue' -import NavPanel from 'src/components/nav_panel/nav_panel.vue' import AnnouncementsPage from 'components/announcements_page/announcements_page.vue' -import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue' +import AuthForm from 'components/auth_form/auth_form.js' +import BookmarkTimeline from 'components/bookmark_timeline/bookmark_timeline.vue' +import BubbleTimeline from 'components/bubble_timeline/bubble_timeline.vue' +import Chat from 'components/chat/chat.vue' +import ChatList from 'components/chat_list/chat_list.vue' +import ConversationPage from 'components/conversation-page/conversation-page.vue' +import DMs from 'components/dm_timeline/dm_timeline.vue' import Drafts from 'components/drafts/drafts.vue' -import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue' +import FollowRequests from 'components/follow_requests/follow_requests.vue' +import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue' +import Interactions from 'components/interactions/interactions.vue' +import Lists from 'components/lists/lists.vue' +import ListsEdit from 'components/lists_edit/lists_edit.vue' +import ListsTimeline from 'components/lists_timeline/lists_timeline.vue' +import Notifications from 'components/notifications/notifications.vue' +import OAuthCallback from 'components/oauth_callback/oauth_callback.vue' +import PasswordReset from 'components/password_reset/password_reset.vue' +import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue' +import PublicTimeline from 'components/public_timeline/public_timeline.vue' +import Registration from 'components/registration/registration.vue' +import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue' +import Search from 'components/search/search.vue' +import ShoutPanel from 'components/shout_panel/shout_panel.vue' +import TagTimeline from 'components/tag_timeline/tag_timeline.vue' +import UserProfile from 'components/user_profile/user_profile.vue' +import WhoToFollow from 'components/who_to_follow/who_to_follow.vue' + +import NavPanel from 'src/components/nav_panel/nav_panel.vue' import BookmarkFolderEdit from '../components/bookmark_folder_edit/bookmark_folder_edit.vue' +import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue' +import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue' export default (store) => { const validateAuthenticatedRoute = (to, from, next) => { @@ -45,46 +46,124 @@ export default (store) => { name: 'root', path: '/', redirect: () => { - return (store.state.users.currentUser - ? store.state.instance.redirectRootLogin - : store.state.instance.redirectRootNoLogin) || '/main/all' - } + return ( + (store.state.users.currentUser + ? store.state.instance.redirectRootLogin + : store.state.instance.redirectRootNoLogin) || '/main/all' + ) + }, + }, + { + name: 'public-external-timeline', + path: '/main/all', + component: PublicAndExternalTimeline, + }, + { + name: 'public-timeline', + path: '/main/public', + component: PublicTimeline, + }, + { + name: 'friends', + path: '/main/friends', + component: FriendsTimeline, + beforeEnter: validateAuthenticatedRoute, }, - { name: 'public-external-timeline', path: '/main/all', component: PublicAndExternalTimeline }, - { name: 'public-timeline', path: '/main/public', component: PublicTimeline }, - { name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute }, { name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline }, { name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline }, { name: 'bubble', path: '/bubble', component: BubbleTimeline }, - { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, + { + name: 'conversation', + path: '/notice/:id', + component: ConversationPage, + meta: { dontScroll: true }, + }, { name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline }, { name: 'remote-user-profile-acct', path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)', component: RemoteUserResolver, - beforeEnter: validateAuthenticatedRoute + beforeEnter: validateAuthenticatedRoute, }, { name: 'remote-user-profile', path: '/remote-users/:hostname/:username', component: RemoteUserResolver, - beforeEnter: validateAuthenticatedRoute + beforeEnter: validateAuthenticatedRoute, + }, + { + name: 'external-user-profile', + path: '/users/$:id', + component: UserProfile, + }, + { + name: 'interactions', + path: '/users/:username/interactions', + component: Interactions, + beforeEnter: validateAuthenticatedRoute, + }, + { + name: 'dms', + path: '/users/:username/dms', + component: DMs, + beforeEnter: validateAuthenticatedRoute, }, - { name: 'external-user-profile', path: '/users/$:id', component: UserProfile }, - { name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute }, - { name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute }, { name: 'registration', path: '/registration', component: Registration }, - { name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true }, - { name: 'registration-token', path: '/registration/:token', component: Registration }, - { name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute }, - { name: 'notifications', path: '/:username/notifications', component: Notifications, props: () => ({ disableTeleport: true }), beforeEnter: validateAuthenticatedRoute }, + { + name: 'password-reset', + path: '/password-reset', + component: PasswordReset, + props: true, + }, + { + name: 'registration-token', + path: '/registration/:token', + component: Registration, + }, + { + name: 'friend-requests', + path: '/friend-requests', + component: FollowRequests, + beforeEnter: validateAuthenticatedRoute, + }, + { + name: 'notifications', + path: '/:username/notifications', + component: Notifications, + props: () => ({ disableTeleport: true }), + beforeEnter: validateAuthenticatedRoute, + }, { name: 'login', path: '/login', component: AuthForm }, - { name: 'shout-panel', path: '/shout-panel', component: ShoutPanel, props: () => ({ floating: false }) }, - { name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) }, - { name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) }, - { name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute }, + { + name: 'shout-panel', + path: '/shout-panel', + component: ShoutPanel, + props: () => ({ floating: false }), + }, + { + name: 'oauth-callback', + path: '/oauth-callback', + component: OAuthCallback, + props: (route) => ({ code: route.query.code }), + }, + { + name: 'search', + path: '/search', + component: Search, + props: (route) => ({ query: route.query.query }), + }, + { + name: 'who-to-follow', + path: '/who-to-follow', + component: WhoToFollow, + beforeEnter: validateAuthenticatedRoute, + }, { name: 'about', path: '/about', component: About }, - { name: 'announcements', path: '/announcements', component: AnnouncementsPage }, + { + name: 'announcements', + path: '/announcements', + component: AnnouncementsPage, + }, { name: 'drafts', path: '/drafts', component: Drafts }, { name: 'user-profile', path: '/users/:name', component: UserProfile }, { name: 'legacy-user-profile', path: '/:name', component: UserProfile }, @@ -92,17 +171,51 @@ export default (store) => { { name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline }, { name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit }, { name: 'lists-new', path: '/lists/new', component: ListsEdit }, - { name: 'edit-navigation', path: '/nav-edit', component: NavPanel, props: () => ({ forceExpand: true, forceEditMode: true }), beforeEnter: validateAuthenticatedRoute }, - { name: 'bookmark-folders', path: '/bookmark_folders', component: BookmarkFolders }, - { name: 'bookmark-folder-new', path: '/bookmarks/new-folder', component: BookmarkFolderEdit }, - { name: 'bookmark-folder', path: '/bookmarks/:id', component: BookmarkTimeline }, - { name: 'bookmark-folder-edit', path: '/bookmarks/:id/edit', component: BookmarkFolderEdit } + { + name: 'edit-navigation', + path: '/nav-edit', + component: NavPanel, + props: () => ({ forceExpand: true, forceEditMode: true }), + beforeEnter: validateAuthenticatedRoute, + }, + { + name: 'bookmark-folders', + path: '/bookmark_folders', + component: BookmarkFolders, + }, + { + name: 'bookmark-folder-new', + path: '/bookmarks/new-folder', + component: BookmarkFolderEdit, + }, + { + name: 'bookmark-folder', + path: '/bookmarks/:id', + component: BookmarkTimeline, + }, + { + name: 'bookmark-folder-edit', + path: '/bookmarks/:id/edit', + component: BookmarkFolderEdit, + }, ] if (store.state.instance.pleromaChatMessagesAvailable) { routes = routes.concat([ - { name: 'chat', path: '/users/:username/chats/:recipient_id', component: Chat, meta: { dontScroll: false }, beforeEnter: validateAuthenticatedRoute }, - { name: 'chats', path: '/users/:username/chats', component: ChatList, meta: { dontScroll: false }, beforeEnter: validateAuthenticatedRoute } + { + name: 'chat', + path: '/users/:username/chats/:recipient_id', + component: Chat, + meta: { dontScroll: false }, + beforeEnter: validateAuthenticatedRoute, + }, + { + name: 'chats', + path: '/users/:username/chats', + component: ChatList, + meta: { dontScroll: false }, + beforeEnter: validateAuthenticatedRoute, + }, ]) } diff --git a/src/components/about/about.js b/src/components/about/about.js index 1df258450..d091dfd0a 100644 --- a/src/components/about/about.js +++ b/src/components/about/about.js @@ -1,8 +1,8 @@ -import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_panel.vue' import FeaturesPanel from '../features_panel/features_panel.vue' -import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue' -import StaffPanel from '../staff_panel/staff_panel.vue' +import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_panel.vue' import MRFTransparencyPanel from '../mrf_transparency_panel/mrf_transparency_panel.vue' +import StaffPanel from '../staff_panel/staff_panel.vue' +import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue' const About = { components: { @@ -10,16 +10,20 @@ const About = { FeaturesPanel, TermsOfServicePanel, StaffPanel, - MRFTransparencyPanel + MRFTransparencyPanel, }, computed: { - showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }, - showInstanceSpecificPanel () { - return this.$store.state.instance.showInstanceSpecificPanel && + showFeaturesPanel() { + return this.$store.state.instance.showFeaturesPanel + }, + showInstanceSpecificPanel() { + return ( + this.$store.state.instance.showInstanceSpecificPanel && !this.$store.getters.mergedConfig.hideISP && this.$store.state.instance.instanceSpecificPanelContent - } - } + ) + }, + }, } export default About diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js index 2ac74ea76..ee94dc544 100644 --- a/src/components/account_actions/account_actions.js +++ b/src/components/account_actions/account_actions.js @@ -1,27 +1,23 @@ import { mapState } from 'vuex' -import ProgressButton from '../progress_button/progress_button.vue' -import Popover from '../popover/popover.vue' -import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue' -import ConfirmModal from '../confirm_modal/confirm_modal.vue' -import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faEllipsisV -} from '@fortawesome/free-solid-svg-icons' -import { useReportsStore } from 'src/stores/reports' -library.add( - faEllipsisV -) +import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue' +import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue' +import { useReportsStore } from 'src/stores/reports' +import ConfirmModal from '../confirm_modal/confirm_modal.vue' +import Popover from '../popover/popover.vue' +import ProgressButton from '../progress_button/progress_button.vue' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { faEllipsisV } from '@fortawesome/free-solid-svg-icons' + +library.add(faEllipsisV) const AccountActions = { - props: [ - 'user', 'relationship' - ], - data () { + props: ['user', 'relationship'], + data() { return { showingConfirmBlock: false, - showingConfirmRemoveFollower: false + showingConfirmRemoveFollower: false, } }, components: { @@ -29,25 +25,25 @@ const AccountActions = { Popover, UserListMenu, ConfirmModal, - UserTimedFilterModal + UserTimedFilterModal, }, methods: { - showConfirmRemoveUserFromFollowers () { + showConfirmRemoveUserFromFollowers() { this.showingConfirmRemoveFollower = true }, - hideConfirmRemoveUserFromFollowers () { + hideConfirmRemoveUserFromFollowers() { this.showingConfirmRemoveFollower = false }, - hideConfirmBlock () { + hideConfirmBlock() { this.showingConfirmBlock = false }, - showRepeats () { + showRepeats() { this.$store.dispatch('showReblogs', this.user.id) }, - hideRepeats () { + hideRepeats() { this.$store.dispatch('hideReblogs', this.user.id) }, - blockUser () { + blockUser() { if (this.$refs.timedBlockDialog) { this.$refs.timedBlockDialog.optionallyPrompt() } else { @@ -58,46 +54,50 @@ const AccountActions = { } } }, - doBlockUser () { + doBlockUser() { this.$store.dispatch('blockUser', { id: this.user.id }) this.hideConfirmBlock() }, - unblockUser () { + unblockUser() { this.$store.dispatch('unblockUser', this.user.id) }, - removeUserFromFollowers () { + removeUserFromFollowers() { if (!this.shouldConfirmRemoveUserFromFollowers) { this.doRemoveUserFromFollowers() } else { this.showConfirmRemoveUserFromFollowers() } }, - doRemoveUserFromFollowers () { + doRemoveUserFromFollowers() { this.$store.dispatch('removeUserFromFollowers', this.user.id) this.hideConfirmRemoveUserFromFollowers() }, - reportUser () { + reportUser() { useReportsStore().openUserReportingModal({ userId: this.user.id }) }, - openChat () { + openChat() { this.$router.push({ name: 'chat', - params: { username: this.$store.state.users.currentUser.screen_name, recipient_id: this.user.id } + params: { + username: this.$store.state.users.currentUser.screen_name, + recipient_id: this.user.id, + }, }) - } + }, }, computed: { - shouldConfirmBlock () { + shouldConfirmBlock() { return this.$store.getters.mergedConfig.modalOnBlock }, - shouldConfirmRemoveUserFromFollowers () { + shouldConfirmRemoveUserFromFollowers() { return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers }, ...mapState({ - blockExpirationSupported: state => state.instance.blockExpiration, - pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable - }) - } + blockExpirationSupported: (state) => state.instance.blockExpiration, + pleromaChatMessagesAvailable: (state) => + state.instance.pleromaChatMessagesAvailable, + }), + }, } export default AccountActions diff --git a/src/components/alert.style.js b/src/components/alert.style.js index 868514764..8a6f842ed 100644 --- a/src/components/alert.style.js +++ b/src/components/alert.style.js @@ -1,57 +1,51 @@ export default { name: 'Alert', selector: '.alert', - validInnerComponents: [ - 'Text', - 'Icon', - 'Link', - 'Border', - 'ButtonUnstyled' - ], + validInnerComponents: ['Text', 'Icon', 'Link', 'Border', 'ButtonUnstyled'], variants: { normal: '.neutral', error: '.error', warning: '.warning', - success: '.success' + success: '.success', }, editor: { border: 1, - aspect: '3 / 1' + aspect: '3 / 1', }, defaultRules: [ { directives: { background: '--text', opacity: 0.5, - blur: '9px' - } + blur: '9px', + }, }, { parent: { - component: 'Alert' + component: 'Alert', }, component: 'Border', directives: { - textColor: '--parent' - } + textColor: '--parent', + }, }, { variant: 'error', directives: { - background: '--cRed' - } + background: '--cRed', + }, }, { variant: 'warning', directives: { - background: '--cOrange' - } + background: '--cOrange', + }, }, { variant: 'success', directives: { - background: '--cGreen' - } - } - ] + background: '--cGreen', + }, + }, + ], } diff --git a/src/components/announcement/announcement.js b/src/components/announcement/announcement.js index d1b8257d8..906b84ce2 100644 --- a/src/components/announcement/announcement.js +++ b/src/components/announcement/announcement.js @@ -1,109 +1,129 @@ import { mapState } from 'vuex' + +import { useAnnouncementsStore } from 'src/stores/announcements' +import localeService from '../../services/locale/locale.service.js' import AnnouncementEditor from '../announcement_editor/announcement_editor.vue' import RichContent from '../rich_content/rich_content.jsx' -import localeService from '../../services/locale/locale.service.js' -import { useAnnouncementsStore } from 'src/stores/announcements' const Announcement = { components: { AnnouncementEditor, - RichContent + RichContent, }, - data () { + data() { return { editing: false, editedAnnouncement: { content: '', startsAt: undefined, endsAt: undefined, - allDay: undefined + allDay: undefined, }, - editError: '' + editError: '', } }, props: { - announcement: Object + announcement: Object, }, computed: { ...mapState({ - currentUser: state => state.users.currentUser + currentUser: (state) => state.users.currentUser, }), - canEditAnnouncement () { - return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements') + canEditAnnouncement() { + return ( + this.currentUser && + this.currentUser.privileges.includes( + 'announcements_manage_announcements', + ) + ) }, - content () { + content() { return this.announcement.content }, - isRead () { + isRead() { return this.announcement.read }, - publishedAt () { + publishedAt() { const time = this.announcement.published_at if (!time) { return } - return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale)) + return this.formatTimeOrDate( + time, + localeService.internalToBrowserLocale(this.$i18n.locale), + ) }, - startsAt () { + startsAt() { const time = this.announcement.starts_at if (!time) { return } - return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale)) + return this.formatTimeOrDate( + time, + localeService.internalToBrowserLocale(this.$i18n.locale), + ) }, - endsAt () { + endsAt() { const time = this.announcement.ends_at if (!time) { return } - return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale)) + return this.formatTimeOrDate( + time, + localeService.internalToBrowserLocale(this.$i18n.locale), + ) }, - inactive () { + inactive() { return this.announcement.inactive - } + }, }, methods: { - markAsRead () { + markAsRead() { if (!this.isRead) { - return useAnnouncementsStore().markAnnouncementAsRead(this.announcement.id) + return useAnnouncementsStore().markAnnouncementAsRead( + this.announcement.id, + ) } }, - deleteAnnouncement () { + deleteAnnouncement() { return useAnnouncementsStore().deleteAnnouncement(this.announcement.id) }, - formatTimeOrDate (time, locale) { + formatTimeOrDate(time, locale) { const d = new Date(time) - return this.announcement.all_day ? d.toLocaleDateString(locale) : d.toLocaleString(locale) + return this.announcement.all_day + ? d.toLocaleDateString(locale) + : d.toLocaleString(locale) }, - enterEditMode () { + enterEditMode() { this.editedAnnouncement.content = this.announcement.pleroma.raw_content this.editedAnnouncement.startsAt = this.announcement.starts_at this.editedAnnouncement.endsAt = this.announcement.ends_at this.editedAnnouncement.allDay = this.announcement.all_day this.editing = true }, - submitEdit () { - useAnnouncementsStore().editAnnouncement({ - id: this.announcement.id, - ...this.editedAnnouncement - }) + submitEdit() { + useAnnouncementsStore() + .editAnnouncement({ + id: this.announcement.id, + ...this.editedAnnouncement, + }) .then(() => { this.editing = false }) - .catch(error => { + .catch((error) => { this.editError = error.error }) }, - cancelEdit () { + cancelEdit() { this.editing = false }, - clearError () { + clearError() { this.editError = undefined - } - } + }, + }, } export default Announcement diff --git a/src/components/announcement_editor/announcement_editor.js b/src/components/announcement_editor/announcement_editor.js index 79a03afe1..6d22ac1fd 100644 --- a/src/components/announcement_editor/announcement_editor.js +++ b/src/components/announcement_editor/announcement_editor.js @@ -2,12 +2,12 @@ import Checkbox from '../checkbox/checkbox.vue' const AnnouncementEditor = { components: { - Checkbox + Checkbox, }, props: { announcement: Object, - disabled: Boolean - } + disabled: Boolean, + }, } export default AnnouncementEditor diff --git a/src/components/announcements_page/announcements_page.js b/src/components/announcements_page/announcements_page.js index 9ce0b45f5..0c4383d2e 100644 --- a/src/components/announcements_page/announcements_page.js +++ b/src/components/announcements_page/announcements_page.js @@ -1,59 +1,66 @@ import { mapState } from 'vuex' + +import { useAnnouncementsStore } from 'src/stores/announcements' import Announcement from '../announcement/announcement.vue' import AnnouncementEditor from '../announcement_editor/announcement_editor.vue' -import { useAnnouncementsStore } from 'src/stores/announcements' const AnnouncementsPage = { components: { Announcement, - AnnouncementEditor + AnnouncementEditor, }, - data () { + data() { return { newAnnouncement: { content: '', startsAt: undefined, endsAt: undefined, - allDay: false + allDay: false, }, posting: false, - error: undefined + error: undefined, } }, - mounted () { + mounted() { useAnnouncementsStore().fetchAnnouncements() }, computed: { ...mapState({ - currentUser: state => state.users.currentUser + currentUser: (state) => state.users.currentUser, }), - announcements () { + announcements() { return useAnnouncementsStore().announcements }, - canPostAnnouncement () { - return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements') - } + canPostAnnouncement() { + return ( + this.currentUser && + this.currentUser.privileges.includes( + 'announcements_manage_announcements', + ) + ) + }, }, methods: { - postAnnouncement () { + postAnnouncement() { this.posting = true - useAnnouncementsStore().postAnnouncement(this.newAnnouncement) + useAnnouncementsStore() + .postAnnouncement(this.newAnnouncement) .then(() => { this.newAnnouncement.content = '' this.startsAt = undefined this.endsAt = undefined }) - .catch(error => { + .catch((error) => { this.error = error.error }) .finally(() => { this.posting = false }) }, - clearError () { + clearError() { this.error = undefined - } - } + }, + }, } export default AnnouncementsPage diff --git a/src/components/async_component_error/async_component_error.vue b/src/components/async_component_error/async_component_error.vue index 2ff8974c1..baf430950 100644 --- a/src/components/async_component_error/async_component_error.vue +++ b/src/components/async_component_error/async_component_error.vue @@ -21,10 +21,10 @@ export default { emits: ['resetAsyncComponent'], methods: { - retry () { + retry() { this.$emit('resetAsyncComponent') - } - } + }, + }, } diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index 21d793930..31fceba60 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -1,24 +1,26 @@ -import StillImage from '../still-image/still-image.vue' -import Flash from '../flash/flash.vue' -import VideoAttachment from '../video_attachment/video_attachment.vue' +import { mapGetters } from 'vuex' + +import { useMediaViewerStore } from 'src/stores/media_viewer' import nsfwImage from '../../assets/nsfw.png' import fileTypeService from '../../services/file_type/file_type.service.js' -import { mapGetters } from 'vuex' +import Flash from '../flash/flash.vue' +import StillImage from '../still-image/still-image.vue' +import VideoAttachment from '../video_attachment/video_attachment.vue' + import { library } from '@fortawesome/fontawesome-svg-core' import { + faAlignRight, faFile, - faMusic, faImage, - faVideo, - faPlayCircle, - faTimes, - faStop, - faSearchPlus, - faTrashAlt, + faMusic, faPencilAlt, - faAlignRight + faPlayCircle, + faSearchPlus, + faStop, + faTimes, + faTrashAlt, + faVideo, } from '@fortawesome/free-solid-svg-icons' -import { useMediaViewerStore } from 'src/stores/media_viewer' library.add( faFile, @@ -31,7 +33,7 @@ library.add( faSearchPlus, faTrashAlt, faPencilAlt, - faAlignRight + faAlignRight, ) const Attachment = { @@ -46,72 +48,74 @@ const Attachment = { 'remove', 'shiftUp', 'shiftDn', - 'edit' + 'edit', ], - data () { + data() { return { localDescription: this.description || this.attachment.description, nsfwImage: this.$store.state.instance.nsfwCensorImage || nsfwImage, hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw, preloadImage: this.$store.getters.mergedConfig.preloadImage, loading: false, - img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img'), + img: + fileTypeService.fileType(this.attachment.mimetype) === 'image' && + document.createElement('img'), modalOpen: false, showHidden: false, flashLoaded: false, - showDescription: false + showDescription: false, } }, components: { Flash, StillImage, - VideoAttachment + VideoAttachment, }, computed: { - classNames () { + classNames() { return [ { '-loading': this.loading, '-nsfw-placeholder': this.hidden, '-editable': this.edit !== undefined, - '-compact': this.compact + '-compact': this.compact, }, '-type-' + this.type, this.size && '-size-' + this.size, - `-${this.useContainFit ? 'contain' : 'cover'}-fit` + `-${this.useContainFit ? 'contain' : 'cover'}-fit`, ] }, - usePlaceholder () { + usePlaceholder() { return this.size === 'hide' }, - useContainFit () { + useContainFit() { return this.$store.getters.mergedConfig.useContainFit }, - placeholderName () { + placeholderName() { if (this.attachment.description === '' || !this.attachment.description) { return this.type.toUpperCase() } return this.attachment.description }, - placeholderIconClass () { + placeholderIconClass() { if (this.type === 'image') return 'image' if (this.type === 'video') return 'video' if (this.type === 'audio') return 'music' return 'file' }, - referrerpolicy () { + referrerpolicy() { return this.$store.state.instance.mediaProxyAvailable ? '' : 'no-referrer' }, - type () { + type() { return fileTypeService.fileType(this.attachment.mimetype) }, - hidden () { + hidden() { return this.nsfw && this.hideNsfwLocal && !this.showHidden }, - isEmpty () { - return (this.type === 'html' && !this.attachment.oembed) + isEmpty() { + return this.type === 'html' && !this.attachment.oembed }, - useModal () { + useModal() { let modalTypes = [] switch (this.size) { case 'hide': @@ -126,26 +130,26 @@ const Attachment = { } return modalTypes.includes(this.type) }, - videoTag () { + videoTag() { return this.useModal ? 'button' : 'span' }, - ...mapGetters(['mergedConfig']) + ...mapGetters(['mergedConfig']), }, watch: { - 'attachment.description' (newVal) { + 'attachment.description'(newVal) { this.localDescription = newVal }, - localDescription (newVal) { + localDescription(newVal) { this.onEdit(newVal) - } + }, }, methods: { - linkClicked ({ target }) { + linkClicked({ target }) { if (target.tagName === 'A') { window.open(target.href, '_blank') } }, - openModal () { + openModal() { if (this.useModal) { this.$emit('setMedia') useMediaViewerStore().setCurrentMedia(this.attachment) @@ -153,34 +157,35 @@ const Attachment = { window.open(this.attachment.url) } }, - openModalForce () { + openModalForce() { this.$emit('setMedia') useMediaViewerStore().setCurrentMedia(this.attachment) }, - onEdit (event) { + onEdit(event) { this.edit && this.edit(this.attachment, event) }, - onRemove () { + onRemove() { this.remove && this.remove(this.attachment) }, - onShiftUp () { + onShiftUp() { this.shiftUp && this.shiftUp(this.attachment) }, - onShiftDn () { + onShiftDn() { this.shiftDn && this.shiftDn(this.attachment) }, - stopFlash () { + stopFlash() { this.$refs.flash.closePlayer() }, - setFlashLoaded (event) { + setFlashLoaded(event) { this.flashLoaded = event }, - toggleDescription () { + toggleDescription() { this.showDescription = !this.showDescription }, - toggleHidden (event) { + toggleHidden(event) { if ( - (this.mergedConfig.useOneClickNsfw && !this.showHidden) && + this.mergedConfig.useOneClickNsfw && + !this.showHidden && (this.type !== 'video' || this.mergedConfig.playVideosInModal) ) { this.openModal(event) @@ -201,12 +206,12 @@ const Attachment = { this.showHidden = !this.showHidden } }, - onImageLoad (image) { + onImageLoad(image) { const width = image.naturalWidth const height = image.naturalHeight this.$emit('naturalSizeLoad', { id: this.attachment.id, width, height }) - } - } + }, + }, } export default Attachment diff --git a/src/components/auth_form/auth_form.js b/src/components/auth_form/auth_form.js index 243cbf574..ce88aa6f9 100644 --- a/src/components/auth_form/auth_form.js +++ b/src/components/auth_form/auth_form.js @@ -1,28 +1,33 @@ +import { mapState } from 'pinia' import { h, resolveComponent } from 'vue' + +import { useAuthFlowStore } from 'src/stores/auth_flow' import LoginForm from '../login_form/login_form.vue' import MFARecoveryForm from '../mfa_form/recovery_form.vue' import MFATOTPForm from '../mfa_form/totp_form.vue' -import { mapState } from 'pinia' -import { useAuthFlowStore } from 'src/stores/auth_flow' const AuthForm = { name: 'AuthForm', - render () { + render() { return h(resolveComponent(this.authForm)) }, computed: { - authForm () { - if (this.requiredTOTP) { return 'MFATOTPForm' } - if (this.requiredRecovery) { return 'MFARecoveryForm' } + authForm() { + if (this.requiredTOTP) { + return 'MFATOTPForm' + } + if (this.requiredRecovery) { + return 'MFARecoveryForm' + } return 'LoginForm' }, - ...mapState(useAuthFlowStore, ['requiredTOTP', 'requiredRecovery']) + ...mapState(useAuthFlowStore, ['requiredTOTP', 'requiredRecovery']), }, components: { MFARecoveryForm, MFATOTPForm, - LoginForm - } + LoginForm, + }, } export default AuthForm diff --git a/src/components/autosuggest/autosuggest.js b/src/components/autosuggest/autosuggest.js index f58f17bb6..37b7706db 100644 --- a/src/components/autosuggest/autosuggest.js +++ b/src/components/autosuggest/autosuggest.js @@ -2,51 +2,55 @@ const debounceMilliseconds = 500 export default { props: { - query: { // function to query results and return a promise + query: { + // function to query results and return a promise type: Function, - required: true + required: true, }, - filter: { // function to filter results in real time - type: Function + filter: { + // function to filter results in real time + type: Function, }, placeholder: { type: String, - default: 'Search...' - } + default: 'Search...', + }, }, - data () { + data() { return { term: '', timeout: null, results: [], - resultsVisible: false + resultsVisible: false, } }, computed: { - filtered () { + filtered() { return this.filter ? this.filter(this.results) : this.results - } + }, }, watch: { - term (val) { + term(val) { this.fetchResults(val) - } + }, }, methods: { - fetchResults (term) { + fetchResults(term) { clearTimeout(this.timeout) this.timeout = setTimeout(() => { this.results = [] if (term) { - this.query(term).then((results) => { this.results = results }) + this.query(term).then((results) => { + this.results = results + }) } }, debounceMilliseconds) }, - onInputClick () { + onInputClick() { this.resultsVisible = true }, - onClickOutside () { + onClickOutside() { this.resultsVisible = false - } - } + }, + }, } diff --git a/src/components/avatar_list/avatar_list.js b/src/components/avatar_list/avatar_list.js index 9b6301b22..aad157686 100644 --- a/src/components/avatar_list/avatar_list.js +++ b/src/components/avatar_list/avatar_list.js @@ -1,21 +1,25 @@ -import UserAvatar from '../user_avatar/user_avatar.vue' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' +import UserAvatar from '../user_avatar/user_avatar.vue' const AvatarList = { props: ['users'], computed: { - slicedUsers () { + slicedUsers() { return this.users ? this.users.slice(0, 15) : [] - } + }, }, components: { - UserAvatar + UserAvatar, }, methods: { - userProfileLink (user) { - return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames) - } - } + userProfileLink(user) { + return generateProfileLink( + user.id, + user.screen_name, + this.$store.state.instance.restrictedNicknames, + ) + }, + }, } export default AvatarList diff --git a/src/components/badge.style.js b/src/components/badge.style.js index 0697cac6c..ab28429cc 100644 --- a/src/components/badge.style.js +++ b/src/components/badge.style.js @@ -1,30 +1,27 @@ export default { name: 'Badge', selector: '.badge', - validInnerComponents: [ - 'Text', - 'Icon' - ], + validInnerComponents: ['Text', 'Icon'], variants: { - notification: '.-notification' + notification: '.-notification', }, defaultRules: [ { component: 'Root', directives: { - '--badgeNotification': 'color | --cRed' - } + '--badgeNotification': 'color | --cRed', + }, }, { directives: { - background: '--cGreen' - } + background: '--cGreen', + }, }, { variant: 'notification', directives: { - background: '--cRed' - } - } - ] + background: '--cRed', + }, + }, + ], } diff --git a/src/components/basic_user_card/basic_user_card.js b/src/components/basic_user_card/basic_user_card.js index 31de2d75f..cc4cbce44 100644 --- a/src/components/basic_user_card/basic_user_card.js +++ b/src/components/basic_user_card/basic_user_card.js @@ -1,24 +1,26 @@ -import UserPopover from '../user_popover/user_popover.vue' -import UserAvatar from '../user_avatar/user_avatar.vue' -import UserLink from '../user_link/user_link.vue' import RichContent from 'src/components/rich_content/rich_content.jsx' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' +import UserAvatar from '../user_avatar/user_avatar.vue' +import UserLink from '../user_link/user_link.vue' +import UserPopover from '../user_popover/user_popover.vue' const BasicUserCard = { - props: [ - 'user' - ], + props: ['user'], components: { UserPopover, UserAvatar, RichContent, - UserLink + UserLink, }, methods: { - userProfileLink (user) { - return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames) - } - } + userProfileLink(user) { + return generateProfileLink( + user.id, + user.screen_name, + this.$store.state.instance.restrictedNicknames, + ) + }, + }, } export default BasicUserCard diff --git a/src/components/block_card/block_card.js b/src/components/block_card/block_card.js index 9a618db3f..754c2fb12 100644 --- a/src/components/block_card/block_card.js +++ b/src/components/block_card/block_card.js @@ -5,42 +5,44 @@ import BasicUserCard from '../basic_user_card/basic_user_card.vue' const BlockCard = { props: ['userId'], computed: { - user () { + user() { return this.$store.getters.findUser(this.userId) }, - relationship () { + relationship() { return this.$store.getters.relationship(this.userId) }, - blocked () { + blocked() { return this.relationship.blocking }, - blockExpiryAvailable () { + blockExpiryAvailable() { return this.user.block_expires_at !== undefined }, - blockExpiry () { + blockExpiry() { return this.user.block_expires_at == null ? this.$t('user_card.block_expires_forever') - : this.$t('user_card.block_expires_at', [new Date(this.user.mute_expires_at).toLocaleString()]) + : this.$t('user_card.block_expires_at', [ + new Date(this.user.mute_expires_at).toLocaleString(), + ]) }, ...mapState({ - blockExpirationSupported: state => state.instance.blockExpiration, - }) + blockExpirationSupported: (state) => state.instance.blockExpiration, + }), }, components: { - BasicUserCard + BasicUserCard, }, methods: { - unblockUser () { + unblockUser() { this.$store.dispatch('unblockUser', this.user.id) }, - blockUser () { + blockUser() { if (this.blockExpirationSupported) { this.$refs.timedBlockDialog.optionallyPrompt() } else { this.$store.dispatch('blockUser', { id: this.user.id }) } - } - } + }, + }, } export default BlockCard diff --git a/src/components/bookmark_folder_card/bookmark_folder_card.js b/src/components/bookmark_folder_card/bookmark_folder_card.js index bf274d9d7..37b3f2e5e 100644 --- a/src/components/bookmark_folder_card/bookmark_folder_card.js +++ b/src/components/bookmark_folder_card/bookmark_folder_card.js @@ -1,22 +1,15 @@ import { library } from '@fortawesome/fontawesome-svg-core' -import { - faEllipsisH -} from '@fortawesome/free-solid-svg-icons' +import { faEllipsisH } from '@fortawesome/free-solid-svg-icons' -library.add( - faEllipsisH -) +library.add(faEllipsisH) const BookmarkFolderCard = { - props: [ - 'folder', - 'allBookmarks' - ], + props: ['folder', 'allBookmarks'], computed: { - firstLetter () { + firstLetter() { return this.folder ? this.folder.name[0] : null - } - } + }, + }, } export default BookmarkFolderCard diff --git a/src/components/bookmark_folder_edit/bookmark_folder_edit.js b/src/components/bookmark_folder_edit/bookmark_folder_edit.js index bb96585bf..a658a0f40 100644 --- a/src/components/bookmark_folder_edit/bookmark_folder_edit.js +++ b/src/components/bookmark_folder_edit/bookmark_folder_edit.js @@ -1,10 +1,10 @@ -import EmojiPicker from '../emoji_picker/emoji_picker.vue' -import apiService from '../../services/api/api.service' -import { useInterfaceStore } from 'src/stores/interface' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders' +import { useInterfaceStore } from 'src/stores/interface' +import apiService from '../../services/api/api.service' +import EmojiPicker from '../emoji_picker/emoji_picker.vue' const BookmarkFolderEdit = { - data () { + data() { return { name: '', nameDraft: '', @@ -13,54 +13,59 @@ const BookmarkFolderEdit = { emojiDraft: '', emojiUrlDraft: null, emojiPickerExpanded: false, - reallyDelete: false + reallyDelete: false, } }, components: { - EmojiPicker + EmojiPicker, }, - created () { + created() { if (!this.id) return const credentials = this.$store.state.users.currentUser.credentials - apiService.fetchBookmarkFolders({ credentials }) - .then((folders) => { - const folder = folders.find(folder => folder.id === this.id) - if (!folder) return + apiService.fetchBookmarkFolders({ credentials }).then((folders) => { + const folder = folders.find((folder) => folder.id === this.id) + if (!folder) return - this.nameDraft = this.name = folder.name - this.emojiDraft = this.emoji = folder.emoji - this.emojiUrlDraft = this.emojiUrl = folder.emoji_url - }) + this.nameDraft = this.name = folder.name + this.emojiDraft = this.emoji = folder.emoji + this.emojiUrlDraft = this.emojiUrl = folder.emoji_url + }) }, computed: { - id () { + id() { return this.$route.params.id - } + }, }, methods: { - selectEmoji (event) { + selectEmoji(event) { this.emojiDraft = event.insertion this.emojiUrlDraft = event.insertionUrl }, - showEmojiPicker () { + showEmojiPicker() { if (!this.emojiPickerExpanded) { this.$refs.picker.showPicker() } }, - onShowPicker () { + onShowPicker() { this.emojiPickerExpanded = true }, - onClosePicker () { + onClosePicker() { this.emojiPickerExpanded = false }, - updateFolder () { - useBookmarkFoldersStore().updateBookmarkFolder({ folderId: this.id, name: this.nameDraft, emoji: this.emojiDraft }) + updateFolder() { + useBookmarkFoldersStore() + .updateBookmarkFolder({ + folderId: this.id, + name: this.nameDraft, + emoji: this.emojiDraft, + }) .then(() => { this.$router.push({ name: 'bookmark-folders' }) }) }, - createFolder () { - useBookmarkFoldersStore().createBookmarkFolder({ name: this.nameDraft, emoji: this.emojiDraft }) + createFolder() { + useBookmarkFoldersStore() + .createBookmarkFolder({ name: this.nameDraft, emoji: this.emojiDraft }) .then(() => { this.$router.push({ name: 'bookmark-folders' }) }) @@ -68,15 +73,15 @@ const BookmarkFolderEdit = { useInterfaceStore().pushGlobalNotice({ messageKey: 'bookmark_folders.error', messageArgs: [e.message], - level: 'error' + level: 'error', }) }) }, - deleteFolder () { + deleteFolder() { useBookmarkFoldersStore().deleteBookmarkFolder({ folderId: this.id }) this.$router.push({ name: 'bookmark-folders' }) - } - } + }, + }, } export default BookmarkFolderEdit diff --git a/src/components/bookmark_folders/bookmark_folders.js b/src/components/bookmark_folders/bookmark_folders.js index 096f3769d..8e09abb31 100644 --- a/src/components/bookmark_folders/bookmark_folders.js +++ b/src/components/bookmark_folders/bookmark_folders.js @@ -1,28 +1,28 @@ -import BookmarkFolderCard from '../bookmark_folder_card/bookmark_folder_card.vue' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders' +import BookmarkFolderCard from '../bookmark_folder_card/bookmark_folder_card.vue' const BookmarkFolders = { - data () { + data() { return { - isNew: false + isNew: false, } }, components: { - BookmarkFolderCard + BookmarkFolderCard, }, computed: { - bookmarkFolders () { + bookmarkFolders() { return useBookmarkFoldersStore().allFolders - } + }, }, methods: { - cancelNewFolder () { + cancelNewFolder() { this.isNew = false }, - newFolder () { + newFolder() { this.isNew = true - } - } + }, + }, } export default BookmarkFolders diff --git a/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js b/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js index dc46b91b3..e84b3bc85 100644 --- a/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js +++ b/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js @@ -1,20 +1,19 @@ import { mapState } from 'pinia' -import NavigationEntry from 'src/components/navigation/navigation_entry.vue' + import { getBookmarkFolderEntries } from 'src/components/navigation/filter.js' +import NavigationEntry from 'src/components/navigation/navigation_entry.vue' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders' export const BookmarkFoldersMenuContent = { - props: [ - 'showPin' - ], + props: ['showPin'], components: { - NavigationEntry + NavigationEntry, }, computed: { ...mapState(useBookmarkFoldersStore, { - folders: getBookmarkFolderEntries - }) - } + folders: getBookmarkFolderEntries, + }), + }, } export default BookmarkFoldersMenuContent diff --git a/src/components/bookmark_timeline/bookmark_timeline.js b/src/components/bookmark_timeline/bookmark_timeline.js index 9571d630f..a241b6ac7 100644 --- a/src/components/bookmark_timeline/bookmark_timeline.js +++ b/src/components/bookmark_timeline/bookmark_timeline.js @@ -1,32 +1,38 @@ import Timeline from '../timeline/timeline.vue' const Bookmarks = { - created () { + created() { this.$store.commit('clearTimeline', { timeline: 'bookmarks' }) - this.$store.dispatch('startFetchingTimeline', { timeline: 'bookmarks', bookmarkFolderId: this.folderId || null }) + this.$store.dispatch('startFetchingTimeline', { + timeline: 'bookmarks', + bookmarkFolderId: this.folderId || null, + }) }, components: { - Timeline + Timeline, }, computed: { - folderId () { + folderId() { return this.$route.params.id }, - timeline () { + timeline() { return this.$store.state.statuses.timelines.bookmarks - } + }, }, watch: { - folderId () { + folderId() { this.$store.commit('clearTimeline', { timeline: 'bookmarks' }) this.$store.dispatch('stopFetchingTimeline', 'bookmarks') - this.$store.dispatch('startFetchingTimeline', { timeline: 'bookmarks', bookmarkFolderId: this.folderId || null }) - } + this.$store.dispatch('startFetchingTimeline', { + timeline: 'bookmarks', + bookmarkFolderId: this.folderId || null, + }) + }, }, - unmounted () { + unmounted() { this.$store.commit('clearTimeline', { timeline: 'bookmarks' }) this.$store.dispatch('stopFetchingTimeline', 'bookmarks') - } + }, } export default Bookmarks diff --git a/src/components/border.style.js b/src/components/border.style.js index 7f2c30163..e7cc31c57 100644 --- a/src/components/border.style.js +++ b/src/components/border.style.js @@ -6,8 +6,8 @@ export default { { directives: { textColor: '$mod(--parent 10)', - textAuto: 'no-auto' - } - } - ] + textAuto: 'no-auto', + }, + }, + ], } diff --git a/src/components/bubble_timeline/bubble_timeline.js b/src/components/bubble_timeline/bubble_timeline.js index 6f73dd2b8..d3835e0e8 100644 --- a/src/components/bubble_timeline/bubble_timeline.js +++ b/src/components/bubble_timeline/bubble_timeline.js @@ -1,18 +1,20 @@ import Timeline from '../timeline/timeline.vue' + const BubbleTimeline = { components: { - Timeline + Timeline, }, computed: { - timeline () { return this.$store.state.statuses.timelines.bubble } + timeline() { + return this.$store.state.statuses.timelines.bubble + }, }, - created () { + created() { this.$store.dispatch('startFetchingTimeline', { timeline: 'bubble' }) }, - unmounted () { + unmounted() { this.$store.dispatch('stopFetchingTimeline', 'bubble') - } - + }, } export default BubbleTimeline diff --git a/src/components/button.style.js b/src/components/button.style.js index 5cffefd91..3fb308b89 100644 --- a/src/components/button.style.js +++ b/src/components/button.style.js @@ -12,25 +12,22 @@ export default { focused: ':focus-within', pressed: ':active', hover: ':is(:hover, :focus-visible):not(:disabled)', - disabled: ':disabled' + disabled: ':disabled', }, // Variants are mutually exclusive, each component implicitly has "normal" variant, and all other variants inherit from it. variants: { // Variants save on computation time since adding new variant just adds one more "set". // normal: '', // you can override normal variant, it will be appenended to the main class danger: '.-danger', - transparent: '.-transparent' + transparent: '.-transparent', // Overall the compuation difficulty is N*((1/6)M^3+M) where M is number of distinct states and N is number of variants. // This (currently) is further multipled by number of places where component can exist. }, editor: { - aspect: '2 / 1' + aspect: '2 / 1', }, // This lists all other components that can possibly exist within one. Recursion is currently not supported (and probably won't be supported ever). - validInnerComponents: [ - 'Text', - 'Icon' - ], + validInnerComponents: ['Text', 'Icon'], // Default rules, used as "default theme", essentially. defaultRules: [ { @@ -39,9 +36,11 @@ export default { '--buttonDefaultHoverGlow': 'shadow | 0 0 1 2 --text / 0.4', '--buttonDefaultFocusGlow': 'shadow | 0 0 1 2 --link / 0.5', '--buttonDefaultShadow': 'shadow | 0 0 2 #000000', - '--buttonDefaultBevel': 'shadow | $borderSide(#FFFFFF top 0.2 1), $borderSide(#000000 bottom 0.2 1)', - '--buttonPressedBevel': 'shadow | inset 0 0 4 #000000, $borderSide(#FFFFFF bottom 0.2 1), $borderSide(#000000 top 0.2 1)' - } + '--buttonDefaultBevel': + 'shadow | $borderSide(#FFFFFF top 0.2 1), $borderSide(#000000 bottom 0.2 1)', + '--buttonPressedBevel': + 'shadow | inset 0 0 4 #000000, $borderSide(#FFFFFF bottom 0.2 1), $borderSide(#000000 top 0.2 1)', + }, }, { // component: 'Button', // no need to specify components every time unless you're specifying how other component should look @@ -49,128 +48,128 @@ export default { directives: { background: '--fg', shadow: ['--buttonDefaultShadow', '--buttonDefaultBevel'], - roundness: 3 - } + roundness: 3, + }, }, { variant: 'danger', directives: { - background: '--cRed' - } + background: '--cRed', + }, }, { variant: 'transparent', directives: { - opacity: 0.5 - } + opacity: 0.5, + }, }, { component: 'Text', parent: { component: 'Button', - variant: 'transparent' + variant: 'transparent', }, directives: { - textColor: '--text' - } + textColor: '--text', + }, }, { component: 'Icon', parent: { component: 'Button', - variant: 'transparent' + variant: 'transparent', }, directives: { - textColor: '--text' - } + textColor: '--text', + }, }, { state: ['hover'], directives: { - shadow: ['--buttonDefaultHoverGlow', '--buttonDefaultBevel'] - } + shadow: ['--buttonDefaultHoverGlow', '--buttonDefaultBevel'], + }, }, { state: ['focused'], directives: { - shadow: ['--buttonDefaultFocusGlow', '--buttonDefaultBevel'] - } + shadow: ['--buttonDefaultFocusGlow', '--buttonDefaultBevel'], + }, }, { state: ['pressed'], directives: { - shadow: ['--buttonDefaultShadow', '--buttonPressedBevel'] - } + shadow: ['--buttonDefaultShadow', '--buttonPressedBevel'], + }, }, { state: ['pressed', 'hover'], directives: { - shadow: ['--buttonPressedBevel', '--buttonDefaultHoverGlow'] - } + shadow: ['--buttonPressedBevel', '--buttonDefaultHoverGlow'], + }, }, { state: ['toggled'], directives: { background: '--accent,-24.2', - shadow: ['--buttonDefaultShadow', '--buttonPressedBevel'] - } + shadow: ['--buttonDefaultShadow', '--buttonPressedBevel'], + }, }, { state: ['toggled', 'hover'], directives: { background: '--accent,-24.2', - shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'] - } + shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'], + }, }, { state: ['toggled', 'focused'], directives: { background: '--accent,-24.2', - shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'] - } + shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'], + }, }, { state: ['toggled', 'hover', 'focused'], directives: { background: '--accent,-24.2', - shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'] - } + shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'], + }, }, { state: ['toggled', 'disabled'], directives: { background: '$blend(--accent 0.25 --parent)', - shadow: ['--buttonPressedBevel'] - } + shadow: ['--buttonPressedBevel'], + }, }, { state: ['disabled'], directives: { background: '$blend(--inheritedBackground 0.25 --parent)', - shadow: ['--buttonDefaultBevel'] - } + shadow: ['--buttonDefaultBevel'], + }, }, { component: 'Text', parent: { component: 'Button', - state: ['disabled'] + state: ['disabled'], }, directives: { textOpacity: 0.25, - textOpacityMode: 'blend' - } + textOpacityMode: 'blend', + }, }, { component: 'Icon', parent: { component: 'Button', - state: ['disabled'] + state: ['disabled'], }, directives: { textOpacity: 0.25, - textOpacityMode: 'blend' - } - } - ] + textOpacityMode: 'blend', + }, + }, + ], } diff --git a/src/components/button_unstyled.style.js b/src/components/button_unstyled.style.js index 9e1a2ca90..c35fb8f69 100644 --- a/src/components/button_unstyled.style.js +++ b/src/components/button_unstyled.style.js @@ -7,91 +7,86 @@ export default { toggled: '.toggled', disabled: ':disabled', hover: ':is(:hover, :focus-visible):not(:disabled)', - focused: ':focus-within:not(:is(:focus-visible))' + focused: ':focus-within:not(:is(:focus-visible))', }, - validInnerComponents: [ - 'Text', - 'Link', - 'Icon', - 'Badge' - ], + validInnerComponents: ['Text', 'Link', 'Icon', 'Badge'], defaultRules: [ { directives: { - shadow: [] - } + shadow: [], + }, }, { component: 'Icon', parent: { component: 'ButtonUnstyled', - state: ['hover'] + state: ['hover'], }, directives: { - textColor: '--parent--text' - } + textColor: '--parent--text', + }, }, { component: 'Icon', parent: { component: 'ButtonUnstyled', - state: ['toggled'] + state: ['toggled'], }, directives: { - textColor: '--parent--text' - } + textColor: '--parent--text', + }, }, { component: 'Icon', parent: { component: 'ButtonUnstyled', - state: ['toggled', 'hover'] + state: ['toggled', 'hover'], }, directives: { - textColor: '--parent--text' - } + textColor: '--parent--text', + }, }, { component: 'Icon', parent: { component: 'ButtonUnstyled', - state: ['toggled', 'focused'] + state: ['toggled', 'focused'], }, directives: { - textColor: '--parent--text' - } + textColor: '--parent--text', + }, }, { component: 'Icon', parent: { component: 'ButtonUnstyled', - state: ['toggled', 'focused', 'hover'] + state: ['toggled', 'focused', 'hover'], }, directives: { - textColor: '--parent--text' - } + textColor: '--parent--text', + }, }, { component: 'Text', parent: { component: 'ButtonUnstyled', - state: ['disabled'] + state: ['disabled'], }, directives: { textOpacity: 0.25, - textOpacityMode: 'blend' - } + textOpacityMode: 'blend', + }, }, { component: 'Icon', parent: { component: 'ButtonUnstyled', - state: ['disabled'] + state: ['disabled'], }, directives: { textOpacity: 0.25, - textOpacityMode: 'blend' - } - } - ] + textOpacityMode: 'blend', + }, + }, + ], } diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index 56f389e60..6dd69ada4 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -1,25 +1,26 @@ import _ from 'lodash' -import { WSConnectionStatus } from '../../services/api/api.service.js' -import { mapGetters, mapState } from 'vuex' import { mapState as mapPiniaState } from 'pinia' -import ChatMessage from '../chat_message/chat_message.vue' -import PostStatusForm from '../post_status_form/post_status_form.vue' -import ChatTitle from '../chat_title/chat_title.vue' -import chatService from '../../services/chat_service/chat_service.js' -import { promiseInterval } from '../../services/promise_interval/promise_interval.js' -import { getScrollPosition, getNewTopPosition, isBottomedOut, isScrollable } from './chat_layout_utils.js' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faChevronDown, - faChevronLeft -} from '@fortawesome/free-solid-svg-icons' -import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js' -import { useInterfaceStore } from 'src/stores/interface.js' +import { mapGetters, mapState } from 'vuex' -library.add( - faChevronDown, - faChevronLeft -) +import { useInterfaceStore } from 'src/stores/interface.js' +import { WSConnectionStatus } from '../../services/api/api.service.js' +import chatService from '../../services/chat_service/chat_service.js' +import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js' +import { promiseInterval } from '../../services/promise_interval/promise_interval.js' +import ChatMessage from '../chat_message/chat_message.vue' +import ChatTitle from '../chat_title/chat_title.vue' +import PostStatusForm from '../post_status_form/post_status_form.vue' +import { + getNewTopPosition, + getScrollPosition, + isBottomedOut, + isScrollable, +} from './chat_layout_utils.js' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons' + +library.add(faChevronDown, faChevronLeft) const BOTTOMED_OUT_OFFSET = 10 const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 10 @@ -31,78 +32,95 @@ const Chat = { components: { ChatMessage, ChatTitle, - PostStatusForm + PostStatusForm, }, - data () { + data() { return { jumpToBottomButtonVisible: false, hoveredMessageChainId: undefined, lastScrollPosition: {}, scrollableContainerHeight: '100%', errorLoadingChat: false, - messageRetriers: {} + messageRetriers: {}, } }, - created () { + created() { this.startFetching() window.addEventListener('resize', this.handleResize) }, - mounted () { + mounted() { window.addEventListener('scroll', this.handleScroll) if (typeof document.hidden !== 'undefined') { - document.addEventListener('visibilitychange', this.handleVisibilityChange, false) + document.addEventListener( + 'visibilitychange', + this.handleVisibilityChange, + false, + ) } this.$nextTick(() => { this.handleResize() }) }, - unmounted () { + unmounted() { window.removeEventListener('scroll', this.handleScroll) window.removeEventListener('resize', this.handleResize) - if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false) + if (typeof document.hidden !== 'undefined') + document.removeEventListener( + 'visibilitychange', + this.handleVisibilityChange, + false, + ) this.$store.dispatch('clearCurrentChat') }, computed: { - recipient () { + recipient() { return this.currentChat && this.currentChat.account }, - recipientId () { + recipientId() { return this.$route.params.recipient_id }, - formPlaceholder () { + formPlaceholder() { if (this.recipient) { - return this.$t('chats.message_user', { nickname: this.recipient.screen_name_ui }) + return this.$t('chats.message_user', { + nickname: this.recipient.screen_name_ui, + }) } else { return '' } }, - chatViewItems () { + chatViewItems() { return chatService.getView(this.currentChatMessageService) }, - newMessageCount () { - return this.currentChatMessageService && this.currentChatMessageService.newMessageCount + newMessageCount() { + return ( + this.currentChatMessageService && + this.currentChatMessageService.newMessageCount + ) }, - streamingEnabled () { - return this.mergedConfig.useStreamingApi && this.mastoUserSocketStatus === WSConnectionStatus.JOINED + streamingEnabled() { + return ( + this.mergedConfig.useStreamingApi && + this.mastoUserSocketStatus === WSConnectionStatus.JOINED + ) }, ...mapGetters([ 'currentChat', 'currentChatMessageService', 'findOpenedChatByRecipientId', - 'mergedConfig' + 'mergedConfig', ]), ...mapPiniaState(useInterfaceStore, { - mobileLayout: store => store.layoutType === 'mobile' + mobileLayout: (store) => store.layoutType === 'mobile', }), ...mapState({ - backendInteractor: state => state.api.backendInteractor, - mastoUserSocketStatus: state => state.api.mastoUserSocketStatus, - currentUser: state => state.users.currentUser - }) + backendInteractor: (state) => state.api.backendInteractor, + mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus, + currentUser: (state) => state.users.currentUser, + }), }, watch: { - chatViewItems () { + chatViewItems() { // We don't want to scroll to the bottom on a new message when the user is viewing older messages. // Therefore we need to know whether the scroll position was at the bottom before the DOM update. const bottomedOutBeforeUpdate = this.bottomedOut(BOTTOMED_OUT_OFFSET) @@ -115,23 +133,23 @@ const Chat = { $route: function () { this.startFetching() }, - mastoUserSocketStatus (newValue) { + mastoUserSocketStatus(newValue) { if (newValue === WSConnectionStatus.JOINED) { this.fetchChat({ isFirstFetch: true }) } - } + }, }, methods: { // Used to animate the avatar near the first message of the message chain when any message belonging to the chain is hovered - onMessageHover ({ isHovered, messageChainId }) { + onMessageHover({ isHovered, messageChainId }) { this.hoveredMessageChainId = isHovered ? messageChainId : undefined }, - onFilesDropped () { + onFilesDropped() { this.$nextTick(() => { this.handleResize() }) }, - handleVisibilityChange () { + handleVisibilityChange() { this.$nextTick(() => { if (!document.hidden && this.bottomedOut(BOTTOMED_OUT_OFFSET)) { this.scrollDown({ forceRead: true }) @@ -139,7 +157,7 @@ const Chat = { }) }, // "Sticks" scroll to bottom instead of top, helps with OSK resizing the viewport - handleResize (opts = {}) { + handleResize(opts = {}) { const { delayed = false } = opts if (delayed) { @@ -160,40 +178,56 @@ const Chat = { this.lastScrollPosition = getScrollPosition() }) }, - scrollDown (options = {}) { + scrollDown(options = {}) { const { behavior = 'auto', forceRead = false } = options this.$nextTick(() => { - window.scrollTo({ top: document.documentElement.scrollHeight, behavior }) + window.scrollTo({ + top: document.documentElement.scrollHeight, + behavior, + }) }) if (forceRead) { this.readChat() } }, - readChat () { - if (!(this.currentChatMessageService && this.currentChatMessageService.maxId)) { return } - if (document.hidden) { return } + readChat() { + if ( + !( + this.currentChatMessageService && this.currentChatMessageService.maxId + ) + ) { + return + } + if (document.hidden) { + return + } const lastReadId = this.currentChatMessageService.maxId this.$store.dispatch('readChat', { id: this.currentChat.id, - lastReadId + lastReadId, }) }, - bottomedOut (offset) { + bottomedOut(offset) { return isBottomedOut(offset) }, - reachedTop () { + reachedTop() { return window.scrollY <= 0 }, - cullOlderCheck () { + cullOlderCheck() { window.setTimeout(() => { if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) { - this.$store.dispatch('cullOlderMessages', this.currentChatMessageService.chatId) + this.$store.dispatch( + 'cullOlderMessages', + this.currentChatMessageService.chatId, + ) } }, 5000) }, handleScroll: _.throttle(function () { this.lastScrollPosition = getScrollPosition() - if (!this.currentChat) { return } + if (!this.currentChat) { + return + } if (this.reachedTop()) { this.fetchChat({ maxId: this.currentChatMessageService.minId }) @@ -213,22 +247,27 @@ const Chat = { this.jumpToBottomButtonVisible = true } }, 200), - handleScrollUp (positionBeforeLoading) { + handleScrollUp(positionBeforeLoading) { const positionAfterLoading = getScrollPosition() window.scrollTo({ - top: getNewTopPosition(positionBeforeLoading, positionAfterLoading) + top: getNewTopPosition(positionBeforeLoading, positionAfterLoading), }) }, - fetchChat ({ isFirstFetch = false, fetchLatest = false, maxId }) { + fetchChat({ isFirstFetch = false, fetchLatest = false, maxId }) { const chatMessageService = this.currentChatMessageService - if (!chatMessageService) { return } - if (fetchLatest && this.streamingEnabled) { return } + if (!chatMessageService) { + return + } + if (fetchLatest && this.streamingEnabled) { + return + } const chatId = chatMessageService.chatId const fetchOlderMessages = !!maxId const sinceId = fetchLatest && chatMessageService.maxId - return this.backendInteractor.chatMessages({ id: chatId, maxId, sinceId }) + return this.backendInteractor + .chatMessages({ id: chatId, maxId, sinceId }) .then((messages) => { // Clear the current chat in case we're recovering from a ws connection loss. if (isFirstFetch) { @@ -236,28 +275,34 @@ const Chat = { } const positionBeforeUpdate = getScrollPosition() - this.$store.dispatch('addChatMessages', { chatId, messages }).then(() => { - this.$nextTick(() => { - if (fetchOlderMessages) { - this.handleScrollUp(positionBeforeUpdate) - } + this.$store + .dispatch('addChatMessages', { chatId, messages }) + .then(() => { + this.$nextTick(() => { + if (fetchOlderMessages) { + this.handleScrollUp(positionBeforeUpdate) + } - // In vertical screens, the first batch of fetched messages may not always take the - // full height of the scrollable container. - // If this is the case, we want to fetch the messages until the scrollable container - // is fully populated so that the user has the ability to scroll up and load the history. - if (!isScrollable() && messages.length > 0) { - this.fetchChat({ maxId: this.currentChatMessageService.minId }) - } + // In vertical screens, the first batch of fetched messages may not always take the + // full height of the scrollable container. + // If this is the case, we want to fetch the messages until the scrollable container + // is fully populated so that the user has the ability to scroll up and load the history. + if (!isScrollable() && messages.length > 0) { + this.fetchChat({ + maxId: this.currentChatMessageService.minId, + }) + } + }) }) - }) }) }, - async startFetching () { + async startFetching() { let chat = this.findOpenedChatByRecipientId(this.recipientId) if (!chat) { try { - chat = await this.backendInteractor.getOrCreateChat({ accountId: this.recipientId }) + chat = await this.backendInteractor.getOrCreateChat({ + accountId: this.recipientId, + }) } catch (e) { console.error('Error creating or getting a chat', e) this.errorLoadingChat = true @@ -271,13 +316,14 @@ const Chat = { this.doStartFetching() } }, - doStartFetching () { + doStartFetching() { this.$store.dispatch('startFetchingCurrentChat', { - fetcher: () => promiseInterval(() => this.fetchChat({ fetchLatest: true }), 5000) + fetcher: () => + promiseInterval(() => this.fetchChat({ fetchLatest: true }), 5000), }) this.fetchChat({ isFirstFetch: true }) }, - handleAttachmentPosting () { + handleAttachmentPosting() { this.$nextTick(() => { this.handleResize() // When the posting form size changes because of a media attachment, we need an extra resize @@ -285,11 +331,11 @@ const Chat = { this.scrollDown({ forceRead: true }) }) }, - sendMessage ({ status, media, idempotencyKey }) { + sendMessage({ status, media, idempotencyKey }) { const params = { id: this.currentChat.id, content: status, - idempotencyKey + idempotencyKey, } if (media[0]) { @@ -301,52 +347,72 @@ const Chat = { chatId: this.currentChat.id, content: status, userId: this.currentUser.id, - idempotencyKey + idempotencyKey, }) - this.$store.dispatch('addChatMessages', { - chatId: this.currentChat.id, - messages: [fakeMessage] - }).then(() => { - this.handleAttachmentPosting() - }) + this.$store + .dispatch('addChatMessages', { + chatId: this.currentChat.id, + messages: [fakeMessage], + }) + .then(() => { + this.handleAttachmentPosting() + }) - return this.doSendMessage({ params, fakeMessage, retriesLeft: MAX_RETRIES }) + return this.doSendMessage({ + params, + fakeMessage, + retriesLeft: MAX_RETRIES, + }) }, - doSendMessage ({ params, fakeMessage, retriesLeft = MAX_RETRIES }) { + doSendMessage({ params, fakeMessage, retriesLeft = MAX_RETRIES }) { if (retriesLeft <= 0) return - this.backendInteractor.sendChatMessage(params) - .then(data => { + this.backendInteractor + .sendChatMessage(params) + .then((data) => { this.$store.dispatch('addChatMessages', { chatId: this.currentChat.id, updateMaxId: false, - messages: [{ ...data, fakeId: fakeMessage.id }] + messages: [{ ...data, fakeId: fakeMessage.id }], }) return data }) - .catch(error => { + .catch((error) => { console.error('Error sending message', error) this.$store.dispatch('handleMessageError', { chatId: this.currentChat.id, fakeId: fakeMessage.id, - isRetry: retriesLeft !== MAX_RETRIES + isRetry: retriesLeft !== MAX_RETRIES, }) - if ((error.statusCode >= 500 && error.statusCode < 600) || error.message === 'Failed to fetch') { - this.messageRetriers[fakeMessage.id] = setTimeout(() => { - this.doSendMessage({ params, fakeMessage, retriesLeft: retriesLeft - 1 }) - }, 1000 * (2 ** (MAX_RETRIES - retriesLeft))) + if ( + (error.statusCode >= 500 && error.statusCode < 600) || + error.message === 'Failed to fetch' + ) { + this.messageRetriers[fakeMessage.id] = setTimeout( + () => { + this.doSendMessage({ + params, + fakeMessage, + retriesLeft: retriesLeft - 1, + }) + }, + 1000 * 2 ** (MAX_RETRIES - retriesLeft), + ) } return {} }) return Promise.resolve(fakeMessage) }, - goBack () { - this.$router.push({ name: 'chats', params: { username: this.currentUser.screen_name } }) - } - } + goBack() { + this.$router.push({ + name: 'chats', + params: { username: this.currentUser.screen_name }, + }) + }, + }, } export default Chat diff --git a/src/components/chat/chat.style.js b/src/components/chat/chat.style.js index 9ae2b7d71..55cf657c2 100644 --- a/src/components/chat/chat.style.js +++ b/src/components/chat/chat.style.js @@ -1,19 +1,13 @@ export default { name: 'Chat', selector: '.chat-message-list', - validInnerComponents: [ - 'Text', - 'Link', - 'Icon', - 'Avatar', - 'ChatMessage' - ], + validInnerComponents: ['Text', 'Link', 'Icon', 'Avatar', 'ChatMessage'], defaultRules: [ { directives: { background: '--bg', - blur: '5px' - } - } - ] + blur: '5px', + }, + }, + ], } diff --git a/src/components/chat/chat_layout_utils.js b/src/components/chat/chat_layout_utils.js index c187892d9..10d0a5e45 100644 --- a/src/components/chat/chat_layout_utils.js +++ b/src/components/chat/chat_layout_utils.js @@ -3,14 +3,17 @@ export const getScrollPosition = () => { return { scrollTop: window.scrollY, scrollHeight: document.documentElement.scrollHeight, - offsetHeight: window.innerHeight + offsetHeight: window.innerHeight, } } // A helper function that is used to keep the scroll position fixed as the new elements are added to the top // Takes two scroll positions, before and after the update. export const getNewTopPosition = (previousPosition, newPosition) => { - return previousPosition.scrollTop + (newPosition.scrollHeight - previousPosition.scrollHeight) + return ( + previousPosition.scrollTop + + (newPosition.scrollHeight - previousPosition.scrollHeight) + ) } export const isBottomedOut = (offset = 0) => { diff --git a/src/components/chat_list/chat_list.js b/src/components/chat_list/chat_list.js index 95708d1dd..4c3194ae1 100644 --- a/src/components/chat_list/chat_list.js +++ b/src/components/chat_list/chat_list.js @@ -1,4 +1,5 @@ -import { mapState, mapGetters } from 'vuex' +import { mapGetters, mapState } from 'vuex' + import ChatListItem from '../chat_list_item/chat_list_item.vue' import ChatNew from '../chat_new/chat_new.vue' import List from '../list/list.vue' @@ -7,31 +8,31 @@ const ChatList = { components: { ChatListItem, List, - ChatNew + ChatNew, }, computed: { ...mapState({ - currentUser: state => state.users.currentUser + currentUser: (state) => state.users.currentUser, }), - ...mapGetters(['sortedChatList']) + ...mapGetters(['sortedChatList']), }, - data () { + data() { return { - isNew: false + isNew: false, } }, - created () { + created() { this.$store.dispatch('fetchChats', { latest: true }) }, methods: { - cancelNewChat () { + cancelNewChat() { this.isNew = false this.$store.dispatch('fetchChats', { latest: true }) }, - newChat () { + newChat() { this.isNew = true - } - } + }, + }, } export default ChatList diff --git a/src/components/chat_list_item/chat_list_item.js b/src/components/chat_list_item/chat_list_item.js index 8f8c491f5..0923a8568 100644 --- a/src/components/chat_list_item/chat_list_item.js +++ b/src/components/chat_list_item/chat_list_item.js @@ -1,31 +1,34 @@ import { mapState } from 'vuex' -import StatusBody from '../status_content/status_content.vue' + import fileType from 'src/services/file_type/file_type.service' -import UserAvatar from '../user_avatar/user_avatar.vue' import AvatarList from '../avatar_list/avatar_list.vue' -import Timeago from '../timeago/timeago.vue' import ChatTitle from '../chat_title/chat_title.vue' +import StatusBody from '../status_content/status_content.vue' +import Timeago from '../timeago/timeago.vue' +import UserAvatar from '../user_avatar/user_avatar.vue' const ChatListItem = { name: 'ChatListItem', - props: [ - 'chat' - ], + props: ['chat'], components: { UserAvatar, AvatarList, Timeago, ChatTitle, - StatusBody + StatusBody, }, computed: { ...mapState({ - currentUser: state => state.users.currentUser + currentUser: (state) => state.users.currentUser, }), - attachmentInfo () { - if (this.chat.lastMessage.attachments.length === 0) { return } + attachmentInfo() { + if (this.chat.lastMessage.attachments.length === 0) { + return + } - const types = this.chat.lastMessage.attachments.map(file => fileType.fileType(file.mimetype)) + const types = this.chat.lastMessage.attachments.map((file) => + fileType.fileType(file.mimetype), + ) if (types.includes('video')) { return this.$t('file_type.video') } else if (types.includes('audio')) { @@ -36,34 +39,36 @@ const ChatListItem = { return this.$t('file_type.file') } }, - messageForStatusContent () { + messageForStatusContent() { const message = this.chat.lastMessage const messageEmojis = message ? message.emojis : [] const isYou = message && message.account_id === this.currentUser.id - const content = message ? (this.attachmentInfo || message.content) : '' - const messagePreview = isYou ? `${this.$t('chats.you')} ${content}` : content + const content = message ? this.attachmentInfo || message.content : '' + const messagePreview = isYou + ? `${this.$t('chats.you')} ${content}` + : content return { summary: '', emojis: messageEmojis, raw_html: messagePreview, text: messagePreview, - attachments: [] + attachments: [], } - } + }, }, methods: { - openChat () { + openChat() { if (this.chat.id) { this.$router.push({ name: 'chat', params: { username: this.currentUser.screen_name, - recipient_id: this.chat.account.id - } + recipient_id: this.chat.account.id, + }, }) } - } - } + }, + }, } export default ChatListItem diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js index 837f6d214..f3cc495c2 100644 --- a/src/components/chat_message/chat_message.js +++ b/src/components/chat_message/chat_message.js @@ -1,24 +1,20 @@ -import { mapState, mapGetters } from 'vuex' import { mapState as mapPiniaState } from 'pinia' -import Popover from '../popover/popover.vue' +import { defineAsyncComponent } from 'vue' +import { mapGetters, mapState } from 'vuex' + +import { useInterfaceStore } from 'src/stores/interface' import Attachment from '../attachment/attachment.vue' -import UserAvatar from '../user_avatar/user_avatar.vue' +import ChatMessageDate from '../chat_message_date/chat_message_date.vue' import Gallery from '../gallery/gallery.vue' import LinkPreview from '../link-preview/link-preview.vue' +import Popover from '../popover/popover.vue' import StatusContent from '../status_content/status_content.vue' -import ChatMessageDate from '../chat_message_date/chat_message_date.vue' -import { defineAsyncComponent } from 'vue' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faTimes, - faEllipsisH -} from '@fortawesome/free-solid-svg-icons' -import { useInterfaceStore } from 'src/stores/interface' +import UserAvatar from '../user_avatar/user_avatar.vue' -library.add( - faTimes, - faEllipsisH -) +import { library } from '@fortawesome/fontawesome-svg-core' +import { faEllipsisH, faTimes } from '@fortawesome/free-solid-svg-icons' + +library.add(faTimes, faEllipsisH) const ChatMessage = { name: 'ChatMessage', @@ -27,7 +23,7 @@ const ChatMessage = { 'edited', 'noHeading', 'chatViewItem', - 'hoveredMessageChain' + 'hoveredMessageChain', ], emits: ['hover'], components: { @@ -38,73 +34,82 @@ const ChatMessage = { Gallery, LinkPreview, ChatMessageDate, - UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue')) + UserPopover: defineAsyncComponent( + () => import('../user_popover/user_popover.vue'), + ), }, computed: { // Returns HH:MM (hours and minutes) in local time. - createdAt () { + createdAt() { const time = this.chatViewItem.data.created_at - return time.toLocaleTimeString('en', { hour: '2-digit', minute: '2-digit', hour12: false }) + return time.toLocaleTimeString('en', { + hour: '2-digit', + minute: '2-digit', + hour12: false, + }) }, - isCurrentUser () { + isCurrentUser() { return this.message.account_id === this.currentUser.id }, - message () { + message() { return this.chatViewItem.data }, - isMessage () { + isMessage() { return this.chatViewItem.type === 'message' }, - messageForStatusContent () { + messageForStatusContent() { return { summary: '', emojis: this.message.emojis, raw_html: this.message.content || '', text: this.message.content || '', - attachments: this.message.attachments + attachments: this.message.attachments, } }, - hasAttachment () { + hasAttachment() { return this.message.attachments.length > 0 }, ...mapPiniaState(useInterfaceStore, { - betterShadow: store => store.browserSupport.cssFilter + betterShadow: (store) => store.browserSupport.cssFilter, }), ...mapState({ - currentUser: state => state.users.currentUser, - restrictedNicknames: state => state.instance.restrictedNicknames + currentUser: (state) => state.users.currentUser, + restrictedNicknames: (state) => state.instance.restrictedNicknames, }), - popoverMarginStyle () { + popoverMarginStyle() { if (this.isCurrentUser) { return {} } else { return { left: 50 } } }, - ...mapGetters(['mergedConfig', 'findUser']) + ...mapGetters(['mergedConfig', 'findUser']), }, - data () { + data() { return { hovered: false, - menuOpened: false + menuOpened: false, } }, methods: { - onHover (bool) { - this.$emit('hover', { isHovered: bool, messageChainId: this.chatViewItem.messageChainId }) + onHover(bool) { + this.$emit('hover', { + isHovered: bool, + messageChainId: this.chatViewItem.messageChainId, + }) }, - async deleteMessage () { + async deleteMessage() { const confirmed = window.confirm(this.$t('chats.delete_confirm')) if (confirmed) { await this.$store.dispatch('deleteChatMessage', { messageId: this.chatViewItem.data.id, - chatId: this.chatViewItem.data.chat_id + chatId: this.chatViewItem.data.chat_id, }) } this.hovered = false this.menuOpened = false - } - } + }, + }, } export default ChatMessage diff --git a/src/components/chat_message/chat_message.style.js b/src/components/chat_message/chat_message.style.js index 76b565823..f7632bc6f 100644 --- a/src/components/chat_message/chat_message.style.js +++ b/src/components/chat_message/chat_message.style.js @@ -2,26 +2,21 @@ export default { name: 'ChatMessage', selector: '.chat-message', variants: { - outgoing: '.outgoing' + outgoing: '.outgoing', }, - validInnerComponents: [ - 'Text', - 'Icon', - 'Border', - 'PollGraph' - ], + validInnerComponents: ['Text', 'Icon', 'Border', 'PollGraph'], defaultRules: [ { directives: { background: '--bg, 2', - backgroundNoCssColor: 'yes' - } + backgroundNoCssColor: 'yes', + }, }, { variant: 'outgoing', directives: { - background: '--bg, 5' - } - } - ] + background: '--bg, 5', + }, + }, + ], } diff --git a/src/components/chat_message_date/chat_message_date.vue b/src/components/chat_message_date/chat_message_date.vue index 98349b753..f0cadb6e7 100644 --- a/src/components/chat_message_date/chat_message_date.vue +++ b/src/components/chat_message_date/chat_message_date.vue @@ -11,16 +11,19 @@ export default { name: 'Timeago', props: ['date'], computed: { - displayDate () { + displayDate() { const today = new Date() today.setHours(0, 0, 0, 0) if (this.date.getTime() === today.getTime()) { return this.$t('display_date.today') } else { - return this.date.toLocaleDateString(localeService.internalToBrowserLocale(this.$i18n.locale), { day: 'numeric', month: 'long' }) + return this.date.toLocaleDateString( + localeService.internalToBrowserLocale(this.$i18n.locale), + { day: 'numeric', month: 'long' }, + ) } - } - } + }, + }, } diff --git a/src/components/chat_new/chat_new.js b/src/components/chat_new/chat_new.js index 71585995a..50e0f7a8f 100644 --- a/src/components/chat_new/chat_new.js +++ b/src/components/chat_new/chat_new.js @@ -1,39 +1,35 @@ -import { mapState, mapGetters } from 'vuex' +import { mapGetters, mapState } from 'vuex' + import BasicUserCard from '../basic_user_card/basic_user_card.vue' import UserAvatar from '../user_avatar/user_avatar.vue' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faSearch, - faChevronLeft -} from '@fortawesome/free-solid-svg-icons' -library.add( - faSearch, - faChevronLeft -) +import { library } from '@fortawesome/fontawesome-svg-core' +import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons' + +library.add(faSearch, faChevronLeft) const chatNew = { components: { BasicUserCard, - UserAvatar + UserAvatar, }, - data () { + data() { return { suggestions: [], userIds: [], loading: false, - query: '' + query: '', } }, - async created () { + async created() { const { chats } = await this.backendInteractor.chats() - chats.forEach(chat => this.suggestions.push(chat.account)) + chats.forEach((chat) => this.suggestions.push(chat.account)) }, computed: { - users () { - return this.userIds.map(userId => this.findUser(userId)) + users() { + return this.userIds.map((userId) => this.findUser(userId)) }, - availableUsers () { + availableUsers() { if (this.query.length !== 0) { return this.users } else { @@ -41,29 +37,29 @@ const chatNew = { } }, ...mapState({ - currentUser: state => state.users.currentUser, - backendInteractor: state => state.api.backendInteractor + currentUser: (state) => state.users.currentUser, + backendInteractor: (state) => state.api.backendInteractor, }), - ...mapGetters(['findUser']) + ...mapGetters(['findUser']), }, methods: { - goBack () { + goBack() { this.$emit('cancel') }, - goToChat (user) { + goToChat(user) { this.$router.push({ name: 'chat', params: { recipient_id: user.id } }) }, - onInput () { + onInput() { this.search(this.query) }, - addUser (user) { + addUser(user) { this.selectedUserIds.push(user.id) this.query = '' }, - removeUser (userId) { - this.selectedUserIds = this.selectedUserIds.filter(id => id !== userId) + removeUser(userId) { + this.selectedUserIds = this.selectedUserIds.filter((id) => id !== userId) }, - search (query) { + search(query) { if (!query) { this.loading = false return @@ -71,13 +67,14 @@ const chatNew = { this.loading = true this.userIds = [] - this.$store.dispatch('search', { q: query, resolve: true, type: 'accounts' }) - .then(data => { + this.$store + .dispatch('search', { q: query, resolve: true, type: 'accounts' }) + .then((data) => { this.loading = false - this.userIds = data.accounts.map(a => a.id) + this.userIds = data.accounts.map((a) => a.id) }) - } - } + }, + }, } export default chatNew diff --git a/src/components/chat_title/chat_title.js b/src/components/chat_title/chat_title.js index b87211265..3447f5163 100644 --- a/src/components/chat_title/chat_title.js +++ b/src/components/chat_title/chat_title.js @@ -1,23 +1,24 @@ -import UserAvatar from '../user_avatar/user_avatar.vue' -import RichContent from 'src/components/rich_content/rich_content.jsx' import { defineAsyncComponent } from 'vue' +import RichContent from 'src/components/rich_content/rich_content.jsx' +import UserAvatar from '../user_avatar/user_avatar.vue' + export default { name: 'ChatTitle', components: { UserAvatar, RichContent, - UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue')) + UserPopover: defineAsyncComponent( + () => import('../user_popover/user_popover.vue'), + ), }, - props: [ - 'user', 'withAvatar' - ], + props: ['user', 'withAvatar'], computed: { - title () { + title() { return this.user ? this.user.screen_name_ui : '' }, - htmlTitle () { + htmlTitle() { return this.user ? this.user.name_html : '' - } - } + }, + }, } diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue index 1e0f5ad05..cbe3dd80f 100644 --- a/src/components/checkbox/checkbox.vue +++ b/src/components/checkbox/checkbox.vue @@ -36,30 +36,25 @@ diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue index 58aee575c..a6fc2047b 100644 --- a/src/components/color_input/color_input.vue +++ b/src/components/color_input/color_input.vue @@ -64,95 +64,95 @@ diff --git a/src/components/component_preview/component_preview.js b/src/components/component_preview/component_preview.js index 9f830cd72..70696f56b 100644 --- a/src/components/component_preview/component_preview.js +++ b/src/components/component_preview/component_preview.js @@ -1,13 +1,15 @@ import Checkbox from 'src/components/checkbox/checkbox.vue' import ColorInput from 'src/components/color_input/color_input.vue' - import genRandomSeed from 'src/services/random_seed/random_seed.service.js' -import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js' +import { + adoptStyleSheets, + createStyleSheet, +} from 'src/services/style_setter/style_setter.js' export default { components: { Checkbox, - ColorInput + ColorInput, }, props: [ 'shadow', @@ -17,41 +19,41 @@ export default { 'previewCss', 'disabled', 'invalid', - 'noColorControl' + 'noColorControl', ], emits: ['update:shadow'], - data () { + data() { return { colorOverride: undefined, lightGrid: false, zoom: 100, - randomSeed: genRandomSeed() + randomSeed: genRandomSeed(), } }, - mounted () { + mounted() { this.update() }, computed: { - hideControls () { + hideControls() { return typeof this.shadow === 'string' - } + }, }, watch: { - previewCss () { + previewCss() { this.update() }, - previewStyle () { + previewStyle() { this.update() }, - zoom () { + zoom() { this.update() - } + }, }, methods: { - updateProperty (axis, value) { + updateProperty(axis, value) { this.$emit('update:shadow', { axis, value: Number(value) }) }, - update () { + update() { const sheet = createStyleSheet('style-component-preview', 90) sheet.clear() @@ -60,23 +62,25 @@ export default { if (this.colorOverride) result.push(`--background: ${this.colorOverride}`) const styleRule = [ - '#component-preview-', this.randomSeed, ' {\n', + '#component-preview-', + this.randomSeed, + ' {\n', '.preview-block {\n', `zoom: ${this.zoom / 100};`, this.previewStyle, '\n}', - '\n}' + '\n}', ].join('') sheet.addRule(styleRule) - sheet.addRule([ - '#component-preview-', this.randomSeed, ' {\n', - ...result, - '\n}' - ].join('')) + sheet.addRule( + ['#component-preview-', this.randomSeed, ' {\n', ...result, '\n}'].join( + '', + ), + ) sheet.ready = true adoptStyleSheets() - } - } + }, + }, } diff --git a/src/components/confirm_modal/confirm_modal.js b/src/components/confirm_modal/confirm_modal.js index 3e2bc2cb7..cd83194be 100644 --- a/src/components/confirm_modal/confirm_modal.js +++ b/src/components/confirm_modal/confirm_modal.js @@ -9,30 +9,29 @@ import DialogModal from '../dialog_modal/dialog_modal.vue' */ const ConfirmModal = { components: { - DialogModal + DialogModal, }, props: { title: { - type: String + type: String, }, cancelText: { - type: String + type: String, }, confirmText: { - type: String - } + type: String, + }, }, emits: ['cancelled', 'accepted'], - computed: { - }, + computed: {}, methods: { - onCancel () { + onCancel() { this.$emit('cancelled') }, - onAccept () { + onAccept() { this.$emit('accepted') - } - } + }, + }, } export default ConfirmModal diff --git a/src/components/confirm_modal/mute_confirm.js b/src/components/confirm_modal/mute_confirm.js index a279dc716..560f6346c 100644 --- a/src/components/confirm_modal/mute_confirm.js +++ b/src/components/confirm_modal/mute_confirm.js @@ -1,64 +1,67 @@ import { mapGetters } from 'vuex' -import ConfirmModal from './confirm_modal.vue' import Select from 'src/components/select/select.vue' +import ConfirmModal from './confirm_modal.vue' export default { props: ['type', 'user', 'status'], emits: ['hide', 'show', 'muted'], data: () => ({ - showing: false + showing: false, }), components: { ConfirmModal, - Select + Select, }, computed: { - domain () { + domain() { return this.user.fqn.split('@')[1] }, - keypath () { + keypath() { if (this.type === 'domain') { return 'status.mute_domain_confirm' } else if (this.type === 'conversation') { return 'status.mute_conversation_confirm' } }, - conversationIsMuted () { + conversationIsMuted() { return this.status.conversation_muted }, - domainIsMuted () { - return new Set(this.$store.state.users.currentUser.domainMutes).has(this.domain) + domainIsMuted() { + return new Set(this.$store.state.users.currentUser.domainMutes).has( + this.domain, + ) }, - shouldConfirm () { + shouldConfirm() { switch (this.type) { case 'domain': { return this.mergedConfig.modalOnMuteDomain } - default: { // conversation + default: { + // conversation return this.mergedConfig.modalOnMuteConversation } } }, - ...mapGetters(['mergedConfig']) + ...mapGetters(['mergedConfig']), }, methods: { - optionallyPrompt () { + optionallyPrompt() { if (this.shouldConfirm) { this.show() } else { this.doMute() } }, - show () { + show() { this.showing = true this.$emit('show') }, - hide () { + hide() { this.showing = false this.$emit('hide') }, - doMute () { + doMute() { switch (this.type) { case 'domain': { if (!this.domainIsMuted) { @@ -79,6 +82,6 @@ export default { } this.$emit('muted') this.hide() - } - } + }, + }, } diff --git a/src/components/contrast_ratio/contrast_ratio.vue b/src/components/contrast_ratio/contrast_ratio.vue index 93799e4c2..1f8b62809 100644 --- a/src/components/contrast_ratio/contrast_ratio.vue +++ b/src/components/contrast_ratio/contrast_ratio.vue @@ -63,54 +63,68 @@ import { library } from '@fortawesome/fontawesome-svg-core' import { faAdjust, faExclamationTriangle, - faThumbsUp + faThumbsUp, } from '@fortawesome/free-solid-svg-icons' -library.add( - faAdjust, - faExclamationTriangle, - faThumbsUp -) +library.add(faAdjust, faExclamationTriangle, faThumbsUp) export default { components: { - Tooltip + Tooltip, }, props: { large: { required: false, type: Boolean, - default: false + default: false, }, // TODO: Make theme switcher compute theme initially so that contrast // component won't be called without contrast data contrast: { required: false, type: Object, - default: () => ({}) + default: () => ({ + /* no-op */ + }), }, showRatio: { required: false, type: Boolean, - default: false - } + default: false, + }, }, computed: { - hint () { - const levelVal = this.contrast.aaa ? 'aaa' : (this.contrast.aa ? 'aa' : 'bad') + hint() { + const levelVal = this.contrast.aaa + ? 'aaa' + : this.contrast.aa + ? 'aa' + : 'bad' const level = this.$t(`settings.style.common.contrast.level.${levelVal}`) const context = this.$t('settings.style.common.contrast.context.text') const ratio = this.contrast.text - return this.$t('settings.style.common.contrast.hint', { level, context, ratio }) + return this.$t('settings.style.common.contrast.hint', { + level, + context, + ratio, + }) }, - hint_18pt () { - const levelVal = this.contrast.laaa ? 'aaa' : (this.contrast.laa ? 'aa' : 'bad') + hint_18pt() { + const levelVal = this.contrast.laaa + ? 'aaa' + : this.contrast.laa + ? 'aa' + : 'bad' const level = this.$t(`settings.style.common.contrast.level.${levelVal}`) const context = this.$t('settings.style.common.contrast.context.18pt') const ratio = this.contrast.text - return this.$t('settings.style.common.contrast.hint', { level, context, ratio }) - } - } + return this.$t('settings.style.common.contrast.hint', { + level, + context, + ratio, + }) + }, + }, } diff --git a/src/components/conversation-page/conversation-page.js b/src/components/conversation-page/conversation-page.js index 8f996be12..d4705303e 100644 --- a/src/components/conversation-page/conversation-page.js +++ b/src/components/conversation-page/conversation-page.js @@ -2,13 +2,13 @@ import Conversation from '../conversation/conversation.vue' const conversationPage = { components: { - Conversation + Conversation, }, computed: { - statusId () { + statusId() { return this.$route.params.id - } - } + }, + }, } export default conversationPage diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 491a8543f..3caf4add7 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -1,25 +1,22 @@ -import { reduce, filter, findIndex, clone, get } from 'lodash' -import Status from '../status/status.vue' -import ThreadTree from '../thread_tree/thread_tree.vue' -import { WSConnectionStatus } from '../../services/api/api.service.js' -import { mapGetters, mapState } from 'vuex' +import { clone, filter, findIndex, get, reduce } from 'lodash' import { mapState as mapPiniaState } from 'pinia' +import { mapGetters, mapState } from 'vuex' + +import { useInterfaceStore } from 'src/stores/interface' +import { WSConnectionStatus } from '../../services/api/api.service.js' import QuickFilterSettings from '../quick_filter_settings/quick_filter_settings.vue' import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue' -import { useInterfaceStore } from 'src/stores/interface' +import Status from '../status/status.vue' +import ThreadTree from '../thread_tree/thread_tree.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faAngleDoubleDown, faAngleDoubleLeft, - faChevronLeft + faChevronLeft, } from '@fortawesome/free-solid-svg-icons' -library.add( - faAngleDoubleDown, - faAngleDoubleLeft, - faChevronLeft -) +library.add(faAngleDoubleDown, faAngleDoubleLeft, faChevronLeft) const sortById = (a, b) => { const idA = a.type === 'retweet' ? a.retweeted_status.id : a.id @@ -43,23 +40,25 @@ const sortAndFilterConversation = (conversation, statusoid) => { if (statusoid.type === 'retweet') { conversation = filter( conversation, - (status) => (status.type === 'retweet' || status.id !== statusoid.retweeted_status.id) + (status) => + status.type === 'retweet' || + status.id !== statusoid.retweeted_status.id, ) } else { conversation = filter(conversation, (status) => status.type !== 'retweet') } - return conversation.filter(_ => _).sort(sortById) + return conversation.filter((_) => _).sort(sortById) } const conversation = { - data () { + data() { return { highlight: null, expanded: false, threadDisplayStatusObject: {}, // id => 'showing' | 'hidden' statusContentPropertiesObject: {}, inlineDivePosition: null, - loadStatusError: null + loadStatusError: null, } }, props: [ @@ -69,76 +68,80 @@ const conversation = { 'pinnedStatusIdsObject', 'inProfile', 'profileUserId', - 'virtualHidden' + 'virtualHidden', ], - created () { + created() { if (this.isPage) { this.fetchConversation() } }, computed: { - maxDepthToShowByDefault () { + maxDepthToShowByDefault() { // maxDepthInThread = max number of depths that is *visible* // since our depth starts with 0 and "showing" means "showing children" // there is a -2 here const maxDepth = this.$store.getters.mergedConfig.maxDepthInThread - 2 return maxDepth >= 1 ? maxDepth : 1 }, - streamingEnabled () { - return this.mergedConfig.useStreamingApi && this.mastoUserSocketStatus === WSConnectionStatus.JOINED + streamingEnabled() { + return ( + this.mergedConfig.useStreamingApi && + this.mastoUserSocketStatus === WSConnectionStatus.JOINED + ) }, - displayStyle () { + displayStyle() { return this.$store.getters.mergedConfig.conversationDisplay }, - isTreeView () { + isTreeView() { return !this.isLinearView }, - treeViewIsSimple () { + treeViewIsSimple() { return !this.$store.getters.mergedConfig.conversationTreeAdvanced }, - isLinearView () { + isLinearView() { return this.displayStyle === 'linear' }, - shouldFadeAncestors () { + shouldFadeAncestors() { return this.$store.getters.mergedConfig.conversationTreeFadeAncestors }, - otherRepliesButtonPosition () { + otherRepliesButtonPosition() { return this.$store.getters.mergedConfig.conversationOtherRepliesButton }, - showOtherRepliesButtonBelowStatus () { + showOtherRepliesButtonBelowStatus() { return this.otherRepliesButtonPosition === 'below' }, - showOtherRepliesButtonInsideStatus () { + showOtherRepliesButtonInsideStatus() { return this.otherRepliesButtonPosition === 'inside' }, - suspendable () { + suspendable() { if (this.isTreeView) { - return Object.entries(this.statusContentProperties) - .every(([, prop]) => !prop.replying && prop.mediaPlaying.length === 0) + return Object.entries(this.statusContentProperties).every( + ([, prop]) => !prop.replying && prop.mediaPlaying.length === 0, + ) } if (this.$refs.statusComponent && this.$refs.statusComponent[0]) { - return this.$refs.statusComponent.every(s => s.suspendable) + return this.$refs.statusComponent.every((s) => s.suspendable) } else { return true } }, - hideStatus () { + hideStatus() { return this.virtualHidden && this.suspendable }, - status () { + status() { return this.$store.state.statuses.allStatusesObject[this.statusId] }, - originalStatusId () { + originalStatusId() { if (this.status.retweeted_status) { return this.status.retweeted_status.id } else { return this.statusId } }, - conversationId () { + conversationId() { return this.getConversationId(this.statusId) }, - conversation () { + conversation() { if (!this.status) { return [] } @@ -147,7 +150,9 @@ const conversation = { return [this.status] } - const conversation = clone(this.$store.state.statuses.conversationsObject[this.conversationId]) + const conversation = clone( + this.$store.state.statuses.conversationsObject[this.conversationId], + ) const statusIndex = findIndex(conversation, { id: this.originalStatusId }) if (statusIndex !== -1) { conversation[statusIndex] = this.status @@ -155,144 +160,188 @@ const conversation = { return sortAndFilterConversation(conversation, this.status) }, - statusMap () { + statusMap() { return this.conversation.reduce((res, s) => { res[s.id] = s return res }, {}) }, - threadTree () { - const reverseLookupTable = this.conversation.reduce((table, status, index) => { - table[status.id] = index - return table - }, {}) + threadTree() { + const reverseLookupTable = this.conversation.reduce( + (table, status, index) => { + table[status.id] = index + return table + }, + {}, + ) - const threads = this.conversation.reduce((a, cur) => { - const id = cur.id - a.forest[id] = this.getReplies(id) - .map(s => s.id) + const threads = this.conversation.reduce( + (a, cur) => { + const id = cur.id + a.forest[id] = this.getReplies(id).map((s) => s.id) - return a - }, { - forest: {} - }) + return a + }, + { + forest: {}, + }, + ) - const walk = (forest, topLevel, depth = 0, processed = {}) => topLevel.map(id => { - if (processed[id]) { - return [] - } + const walk = (forest, topLevel, depth = 0, processed = {}) => + topLevel + .map((id) => { + if (processed[id]) { + return [] + } - processed[id] = true - return [{ - status: this.conversation[reverseLookupTable[id]], - id, - depth - }, walk(forest, forest[id], depth + 1, processed)].reduce((a, b) => a.concat(b), []) - }).reduce((a, b) => a.concat(b), []) + processed[id] = true + return [ + { + status: this.conversation[reverseLookupTable[id]], + id, + depth, + }, + walk(forest, forest[id], depth + 1, processed), + ].reduce((a, b) => a.concat(b), []) + }) + .reduce((a, b) => a.concat(b), []) - const linearized = walk(threads.forest, this.topLevel.map(k => k.id)) + const linearized = walk( + threads.forest, + this.topLevel.map((k) => k.id), + ) return linearized }, - replyIds () { - return this.conversation.map(k => k.id) + replyIds() { + return this.conversation + .map((k) => k.id) .reduce((res, id) => { - res[id] = (this.replies[id] || []).map(k => k.id) + res[id] = (this.replies[id] || []).map((k) => k.id) return res }, {}) }, - totalReplyCount () { + totalReplyCount() { const sizes = {} const subTreeSizeFor = (id) => { if (sizes[id]) { return sizes[id] } - sizes[id] = 1 + this.replyIds[id].map(cid => subTreeSizeFor(cid)).reduce((a, b) => a + b, 0) + sizes[id] = + 1 + + this.replyIds[id] + .map((cid) => subTreeSizeFor(cid)) + .reduce((a, b) => a + b, 0) return sizes[id] } - this.conversation.map(k => k.id).map(subTreeSizeFor) + this.conversation.map((k) => k.id).map(subTreeSizeFor) return Object.keys(sizes).reduce((res, id) => { res[id] = sizes[id] - 1 // exclude itself return res }, {}) }, - totalReplyDepth () { + totalReplyDepth() { const depths = {} const subTreeDepthFor = (id) => { if (depths[id]) { return depths[id] } - depths[id] = 1 + this.replyIds[id].map(cid => subTreeDepthFor(cid)).reduce((a, b) => a > b ? a : b, 0) + depths[id] = + 1 + + this.replyIds[id] + .map((cid) => subTreeDepthFor(cid)) + .reduce((a, b) => (a > b ? a : b), 0) return depths[id] } - this.conversation.map(k => k.id).map(subTreeDepthFor) + this.conversation.map((k) => k.id).map(subTreeDepthFor) return Object.keys(depths).reduce((res, id) => { res[id] = depths[id] - 1 // exclude itself return res }, {}) }, - depths () { + depths() { return this.threadTree.reduce((a, k) => { a[k.id] = k.depth return a }, {}) }, - topLevel () { - const topLevel = this.conversation.reduce((tl, cur) => - tl.filter(k => this.getReplies(cur.id).map(v => v.id).indexOf(k.id) === -1), this.conversation) + topLevel() { + const topLevel = this.conversation.reduce( + (tl, cur) => + tl.filter( + (k) => + this.getReplies(cur.id) + .map((v) => v.id) + .indexOf(k.id) === -1, + ), + this.conversation, + ) return topLevel }, - otherTopLevelCount () { + otherTopLevelCount() { return this.topLevel.length - 1 }, - showingTopLevel () { + showingTopLevel() { if (this.canDive && this.diveRoot) { return [this.statusMap[this.diveRoot]] } return this.topLevel }, - diveRoot () { + diveRoot() { const statusId = this.inlineDivePosition || this.statusId const isTopLevel = !this.parentOf(statusId) return isTopLevel ? null : statusId }, - diveDepth () { + diveDepth() { return this.canDive && this.diveRoot ? this.depths[this.diveRoot] : 0 }, - diveMode () { + diveMode() { return this.canDive && !!this.diveRoot }, - shouldShowAllConversationButton () { + shouldShowAllConversationButton() { // The "show all conversation" button tells the user that there exist // other toplevel statuses, so do not show it if there is only a single root - return this.isTreeView && this.isExpanded && this.diveMode && this.topLevel.length > 1 + return ( + this.isTreeView && + this.isExpanded && + this.diveMode && + this.topLevel.length > 1 + ) }, - shouldShowAncestors () { - return this.isTreeView && this.isExpanded && this.ancestorsOf(this.diveRoot).length + shouldShowAncestors() { + return ( + this.isTreeView && + this.isExpanded && + this.ancestorsOf(this.diveRoot).length + ) }, - replies () { + replies() { let i = 1 - return reduce(this.conversation, (result, { id, in_reply_to_status_id: irid }) => { - if (irid) { - result[irid] = result[irid] || [] - result[irid].push({ - name: `#${i}`, - id - }) - } - i++ - return result - }, {}) + return reduce( + this.conversation, + (result, { id, in_reply_to_status_id: irid }) => { + if (irid) { + result[irid] = result[irid] || [] + result[irid].push({ + name: `#${i}`, + id, + }) + } + i++ + return result + }, + {}, + ) }, - isExpanded () { + isExpanded() { return !!(this.expanded || this.isPage) }, - hiddenStyle () { + hiddenStyle() { const height = (this.status && this.status.virtualHeight) || '120px' return this.virtualHidden ? { height } : {} }, - threadDisplayStatus () { + threadDisplayStatus() { return this.conversation.reduce((a, k) => { const id = k.id const depth = this.depths[id] @@ -300,7 +349,7 @@ const conversation = { if (this.threadDisplayStatusObject[id]) { return this.threadDisplayStatusObject[id] } - if ((depth - this.diveDepth) <= this.maxDepthToShowByDefault) { + if (depth - this.diveDepth <= this.maxDepthToShowByDefault) { return 'showing' } else { return 'hidden' @@ -311,7 +360,7 @@ const conversation = { return a }, {}) }, - statusContentProperties () { + statusContentProperties() { return this.conversation.reduce((a, k) => { const id = k.id const props = (() => { @@ -320,13 +369,13 @@ const conversation = { expandingSubject: false, showingLongSubject: false, isReplying: false, - mediaPlaying: [] + mediaPlaying: [], } if (this.statusContentPropertiesObject[id]) { return { ...def, - ...this.statusContentPropertiesObject[id] + ...this.statusContentPropertiesObject[id], } } return def @@ -336,54 +385,59 @@ const conversation = { return a }, {}) }, - canDive () { + canDive() { return this.isTreeView && this.isExpanded }, - maybeHighlight () { + maybeHighlight() { return this.isExpanded ? this.highlight : null }, ...mapGetters(['mergedConfig']), ...mapState({ - mastoUserSocketStatus: state => state.api.mastoUserSocketStatus + mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus, }), ...mapPiniaState(useInterfaceStore, { - mobileLayout: store => store.layoutType === 'mobile' - }) + mobileLayout: (store) => store.layoutType === 'mobile', + }), }, components: { Status, ThreadTree, QuickFilterSettings, - QuickViewSettings + QuickViewSettings, }, watch: { - statusId (newVal, oldVal) { + statusId(newVal, oldVal) { const newConversationId = this.getConversationId(newVal) const oldConversationId = this.getConversationId(oldVal) - if (newConversationId && oldConversationId && newConversationId === oldConversationId) { + if ( + newConversationId && + oldConversationId && + newConversationId === oldConversationId + ) { this.setHighlight(this.originalStatusId) } else { this.fetchConversation() } }, - expanded (value) { + expanded(value) { if (value) { this.fetchConversation() } else { this.resetDisplayState() } }, - virtualHidden () { - this.$store.dispatch( - 'setVirtualHeight', - { statusId: this.statusId, height: `${this.$el.clientHeight}px` } - ) - } + virtualHidden() { + this.$store.dispatch('setVirtualHeight', { + statusId: this.statusId, + height: `${this.$el.clientHeight}px`, + }) + }, }, methods: { - fetchConversation () { + fetchConversation() { if (this.status) { - this.$store.state.api.backendInteractor.fetchConversation({ id: this.statusId }) + this.$store.state.api.backendInteractor + .fetchConversation({ id: this.statusId }) .then(({ ancestors, descendants }) => { this.$store.dispatch('addNewStatuses', { statuses: ancestors }) this.$store.dispatch('addNewStatuses', { statuses: descendants }) @@ -391,7 +445,8 @@ const conversation = { }) } else { this.loadStatusError = null - this.$store.state.api.backendInteractor.fetchStatus({ id: this.statusId }) + this.$store.state.api.backendInteractor + .fetchStatus({ id: this.statusId }) .then((status) => { this.$store.dispatch('addNewStatuses', { statuses: [status] }) this.fetchConversation() @@ -401,16 +456,16 @@ const conversation = { }) } }, - isFocused (id) { - return (this.isExpanded) && id === this.highlight + isFocused(id) { + return this.isExpanded && id === this.highlight }, - getReplies (id) { + getReplies(id) { return this.replies[id] || [] }, - getHighlight () { + getHighlight() { return this.isExpanded ? this.highlight : null }, - setHighlight (id) { + setHighlight(id) { if (!id) return this.highlight = id @@ -421,44 +476,54 @@ const conversation = { this.$store.dispatch('fetchFavsAndRepeats', id) this.$store.dispatch('fetchEmojiReactionsBy', id) }, - toggleExpanded () { + toggleExpanded() { this.expanded = !this.expanded }, - getConversationId (statusId) { + getConversationId(statusId) { const status = this.$store.state.statuses.allStatusesObject[statusId] - return get(status, 'retweeted_status.statusnet_conversation_id', get(status, 'statusnet_conversation_id')) + return get( + status, + 'retweeted_status.statusnet_conversation_id', + get(status, 'statusnet_conversation_id'), + ) }, - setThreadDisplay (id, nextStatus) { + setThreadDisplay(id, nextStatus) { this.threadDisplayStatusObject = { ...this.threadDisplayStatusObject, - [id]: nextStatus + [id]: nextStatus, } }, - toggleThreadDisplay (id) { + toggleThreadDisplay(id) { const curStatus = this.threadDisplayStatus[id] const nextStatus = curStatus === 'showing' ? 'hidden' : 'showing' this.setThreadDisplay(id, nextStatus) }, - setThreadDisplayRecursively (id, nextStatus) { + setThreadDisplayRecursively(id, nextStatus) { this.setThreadDisplay(id, nextStatus) - this.getReplies(id).map(k => k.id).map(id => this.setThreadDisplayRecursively(id, nextStatus)) + this.getReplies(id) + .map((k) => k.id) + .map((id) => this.setThreadDisplayRecursively(id, nextStatus)) }, - showThreadRecursively (id) { + showThreadRecursively(id) { this.setThreadDisplayRecursively(id, 'showing') }, - setStatusContentProperty (id, name, value) { + setStatusContentProperty(id, name, value) { this.statusContentPropertiesObject = { ...this.statusContentPropertiesObject, [id]: { ...this.statusContentPropertiesObject[id], - [name]: value - } + [name]: value, + }, } }, - toggleStatusContentProperty (id, name) { - this.setStatusContentProperty(id, name, !this.statusContentProperties[id][name]) + toggleStatusContentProperty(id, name) { + this.setStatusContentProperty( + id, + name, + !this.statusContentProperties[id][name], + ) }, - leastVisibleAncestor (id) { + leastVisibleAncestor(id) { let cur = id let parent = this.parentOf(cur) while (cur) { @@ -472,18 +537,20 @@ const conversation = { // nothing found, fall back to toplevel return this.topLevel[0] ? this.topLevel[0].id : undefined }, - diveIntoStatus (id) { + diveIntoStatus(id) { this.tryScrollTo(id) }, - diveToTopLevel () { - this.tryScrollTo(this.topLevelAncestorOrSelfId(this.diveRoot) || this.topLevel[0].id) + diveToTopLevel() { + this.tryScrollTo( + this.topLevelAncestorOrSelfId(this.diveRoot) || this.topLevel[0].id, + ) }, // only used when we are not on a page - undive () { + undive() { this.inlineDivePosition = null this.setHighlight(this.statusId) }, - tryScrollTo (id) { + tryScrollTo(id) { if (!id) { return } @@ -512,13 +579,13 @@ const conversation = { this.setHighlight(id) }) }, - goToCurrent () { + goToCurrent() { this.tryScrollTo(this.diveRoot || this.topLevel[0].id) }, - statusById (id) { + statusById(id) { return this.statusMap[id] }, - parentOf (id) { + parentOf(id) { const status = this.statusById(id) if (!status) { return undefined @@ -529,11 +596,11 @@ const conversation = { } return parentId }, - parentOrSelf (id) { + parentOrSelf(id) { return this.parentOf(id) || id }, // Ancestors of some status, from top to bottom - ancestorsOf (id) { + ancestorsOf(id) { const ancestors = [] let cur = this.parentOf(id) while (cur) { @@ -542,7 +609,7 @@ const conversation = { } return ancestors }, - topLevelAncestorOrSelfId (id) { + topLevelAncestorOrSelfId(id) { let cur = id let parent = this.parentOf(id) while (parent) { @@ -551,11 +618,11 @@ const conversation = { } return cur }, - resetDisplayState () { + resetDisplayState() { this.undive() this.threadDisplayStatusObject = {} - } - } + }, + }, } export default conversation diff --git a/src/components/desktop_nav/desktop_nav.js b/src/components/desktop_nav/desktop_nav.js index 98d408a7e..8ab54b3df 100644 --- a/src/components/desktop_nav/desktop_nav.js +++ b/src/components/desktop_nav/desktop_nav.js @@ -1,20 +1,22 @@ import SearchBar from 'components/search_bar/search_bar.vue' + +import { useInterfaceStore } from 'src/stores/interface' import ConfirmModal from '../confirm_modal/confirm_modal.vue' + import { library } from '@fortawesome/fontawesome-svg-core' import { + faBell, + faBullhorn, + faCog, + faComments, + faHome, + faInfoCircle, + faSearch, faSignInAlt, faSignOutAlt, - faHome, - faComments, - faBell, - faUserPlus, - faBullhorn, - faSearch, faTachometerAlt, - faCog, - faInfoCircle + faUserPlus, } from '@fortawesome/free-solid-svg-icons' -import { useInterfaceStore } from 'src/stores/interface' library.add( faSignInAlt, @@ -27,91 +29,109 @@ library.add( faSearch, faTachometerAlt, faCog, - faInfoCircle + faInfoCircle, ) export default { components: { SearchBar, - ConfirmModal + ConfirmModal, }, data: () => ({ searchBarHidden: true, - supportsMask: window.CSS && window.CSS.supports && ( - window.CSS.supports('mask-size', 'contain') || + supportsMask: + window.CSS && + window.CSS.supports && + (window.CSS.supports('mask-size', 'contain') || window.CSS.supports('-webkit-mask-size', 'contain') || window.CSS.supports('-moz-mask-size', 'contain') || window.CSS.supports('-ms-mask-size', 'contain') || - window.CSS.supports('-o-mask-size', 'contain') - ), - showingConfirmLogout: false + window.CSS.supports('-o-mask-size', 'contain')), + showingConfirmLogout: false, }), computed: { - enableMask () { return this.supportsMask && this.$store.state.instance.logoMask }, - logoStyle () { + enableMask() { + return this.supportsMask && this.$store.state.instance.logoMask + }, + logoStyle() { return { - visibility: this.enableMask ? 'hidden' : 'visible' + visibility: this.enableMask ? 'hidden' : 'visible', } }, - logoMaskStyle () { + logoMaskStyle() { return this.enableMask ? { - 'mask-image': `url(${this.$store.state.instance.logo})` + 'mask-image': `url(${this.$store.state.instance.logo})`, } : { - 'background-color': this.enableMask ? '' : 'transparent' + 'background-color': this.enableMask ? '' : 'transparent', } }, - logoBgStyle () { - return Object.assign({ - margin: `${this.$store.state.instance.logoMargin} 0`, - opacity: this.searchBarHidden ? 1 : 0 - }, this.enableMask - ? {} - : { - 'background-color': this.enableMask ? '' : 'transparent' - }) + logoBgStyle() { + return Object.assign( + { + margin: `${this.$store.state.instance.logoMargin} 0`, + opacity: this.searchBarHidden ? 1 : 0, + }, + this.enableMask + ? {} + : { + 'background-color': this.enableMask ? '' : 'transparent', + }, + ) }, - logo () { return this.$store.state.instance.logo }, - sitename () { return this.$store.state.instance.name }, - hideSitename () { return this.$store.state.instance.hideSitename }, - logoLeft () { return this.$store.state.instance.logoLeft }, - currentUser () { return this.$store.state.users.currentUser }, - privateMode () { return this.$store.state.instance.private }, - shouldConfirmLogout () { + logo() { + return this.$store.state.instance.logo + }, + sitename() { + return this.$store.state.instance.name + }, + hideSitename() { + return this.$store.state.instance.hideSitename + }, + logoLeft() { + return this.$store.state.instance.logoLeft + }, + currentUser() { + return this.$store.state.users.currentUser + }, + privateMode() { + return this.$store.state.instance.private + }, + shouldConfirmLogout() { return this.$store.getters.mergedConfig.modalOnLogout - } + }, }, methods: { - scrollToTop () { + scrollToTop() { window.scrollTo(0, 0) }, - showConfirmLogout () { + showConfirmLogout() { this.showingConfirmLogout = true }, - hideConfirmLogout () { + hideConfirmLogout() { this.showingConfirmLogout = false }, - logout () { + logout() { if (!this.shouldConfirmLogout) { this.doLogout() } else { this.showConfirmLogout() } }, - doLogout () { + doLogout() { this.$router.replace('/main/public') this.$store.dispatch('logout') this.hideConfirmLogout() }, - onSearchBarToggled (hidden) { + onSearchBarToggled(hidden) { this.searchBarHidden = hidden }, - openSettingsModal () { + openSettingsModal() { useInterfaceStore().openSettingsModal('user') }, - openAdminModal () { + openAdminModal() { useInterfaceStore().openSettingsModal('admin') - } - } + }, + }, } diff --git a/src/components/dialog_modal/dialog_modal.js b/src/components/dialog_modal/dialog_modal.js index b39851fe7..8070d3429 100644 --- a/src/components/dialog_modal/dialog_modal.js +++ b/src/components/dialog_modal/dialog_modal.js @@ -2,18 +2,20 @@ const DialogModal = { props: { darkOverlay: { default: true, - type: Boolean + type: Boolean, }, onCancel: { - default: () => {}, - type: Function - } + default: () => { + /* no-op */ + }, + type: Function, + }, }, computed: { - mobileCenter () { + mobileCenter() { return this.$store.getters.mergedConfig.modalMobileCenter - } - } + }, + }, } export default DialogModal diff --git a/src/components/dm_timeline/dm_timeline.js b/src/components/dm_timeline/dm_timeline.js index 8b5393a98..c977efe30 100644 --- a/src/components/dm_timeline/dm_timeline.js +++ b/src/components/dm_timeline/dm_timeline.js @@ -2,13 +2,13 @@ import Timeline from '../timeline/timeline.vue' const DMs = { computed: { - timeline () { + timeline() { return this.$store.state.statuses.timelines.dms - } + }, }, components: { - Timeline - } + Timeline, + }, } export default DMs diff --git a/src/components/domain_mute_card/domain_mute_card.js b/src/components/domain_mute_card/domain_mute_card.js index f234dcb0f..71da2684c 100644 --- a/src/components/domain_mute_card/domain_mute_card.js +++ b/src/components/domain_mute_card/domain_mute_card.js @@ -3,24 +3,24 @@ import ProgressButton from '../progress_button/progress_button.vue' const DomainMuteCard = { props: ['domain'], components: { - ProgressButton + ProgressButton, }, computed: { - user () { + user() { return this.$store.state.users.currentUser }, - muted () { + muted() { return this.user.domainMutes.includes(this.domain) - } + }, }, methods: { - unmuteDomain () { + unmuteDomain() { return this.$store.dispatch('unmuteDomain', this.domain) }, - muteDomain () { + muteDomain() { return this.$store.dispatch('muteDomain', this.domain) - } - } + }, + }, } export default DomainMuteCard diff --git a/src/components/draft/draft.js b/src/components/draft/draft.js index 55ee11a15..971a75b10 100644 --- a/src/components/draft/draft.js +++ b/src/components/draft/draft.js @@ -1,18 +1,15 @@ -import PostStatusForm from 'src/components/post_status_form/post_status_form.vue' -import EditStatusForm from 'src/components/edit_status_form/edit_status_form.vue' -import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue' -import StatusContent from 'src/components/status_content/status_content.vue' -import Gallery from 'src/components/gallery/gallery.vue' import { cloneDeep } from 'lodash' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faPollH -} from '@fortawesome/free-solid-svg-icons' +import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue' +import EditStatusForm from 'src/components/edit_status_form/edit_status_form.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' -library.add( - faPollH -) +import { library } from '@fortawesome/fontawesome-svg-core' +import { faPollH } from '@fortawesome/free-solid-svg-icons' + +library.add(faPollH) const Draft = { components: { @@ -20,23 +17,23 @@ const Draft = { EditStatusForm, ConfirmModal, StatusContent, - Gallery + Gallery, }, props: { draft: { type: Object, - required: true - } + required: true, + }, }, - data () { + data() { return { referenceDraft: cloneDeep(this.draft), editing: false, - showingConfirmDialog: false + showingConfirmDialog: false, } }, computed: { - relAttrs () { + relAttrs() { if (this.draft.type === 'edit') { return { statusId: this.draft.refId } } else if (this.draft.type === 'reply') { @@ -45,24 +42,24 @@ const Draft = { return {} } }, - safeToSave () { - return this.draft.status || - this.draft.files?.length || - this.draft.hasPoll + safeToSave() { + return this.draft.status || this.draft.files?.length || this.draft.hasPoll }, - postStatusFormProps () { + postStatusFormProps() { return { draftId: this.draft.id, - ...this.relAttrs + ...this.relAttrs, } }, - refStatus () { - return this.draft.refId ? this.$store.state.statuses.allStatusesObject[this.draft.refId] : undefined + refStatus() { + return this.draft.refId + ? this.$store.state.statuses.allStatusesObject[this.draft.refId] + : undefined }, - localCollapseSubjectDefault () { + localCollapseSubjectDefault() { return this.$store.getters.mergedConfig.collapseMessageWithSubject }, - nsfwClickthrough () { + nsfwClickthrough() { if (!this.draft.nsfw) { return false } @@ -70,35 +67,34 @@ const Draft = { return false } return true - } + }, }, watch: { - editing (newVal) { + editing(newVal) { if (newVal) return if (this.safeToSave) { this.$store.dispatch('addOrSaveDraft', { draft: this.draft }) } else { this.$store.dispatch('addOrSaveDraft', { draft: this.referenceDraft }) } - } + }, }, methods: { - toggleEditing () { + toggleEditing() { this.editing = !this.editing }, - abandon () { + abandon() { this.showingConfirmDialog = true }, - doAbandon () { - this.$store.dispatch('abandonDraft', { id: this.draft.id }) - .then(() => { - this.hideConfirmDialog() - }) + doAbandon() { + this.$store.dispatch('abandonDraft', { id: this.draft.id }).then(() => { + this.hideConfirmDialog() + }) }, - hideConfirmDialog () { + hideConfirmDialog() { this.showingConfirmDialog = false - } - } + }, + }, } export default Draft diff --git a/src/components/draft_closer/draft_closer.js b/src/components/draft_closer/draft_closer.js index e50ea05ab..d724ab4ac 100644 --- a/src/components/draft_closer/draft_closer.js +++ b/src/components/draft_closer/draft_closer.js @@ -1,32 +1,29 @@ import DialogModal from 'src/components/dialog_modal/dialog_modal.vue' const DraftCloser = { - data () { + data() { return { - showing: false + showing: false, } }, components: { - DialogModal + DialogModal, }, - emits: [ - 'save', - 'discard' - ], + emits: ['save', 'discard'], computed: { - action () { + action() { if (this.$store.getters.mergedConfig.autoSaveDraft) { return 'save' } else { return this.$store.getters.mergedConfig.unsavedPostAction } }, - shouldConfirm () { + shouldConfirm() { return this.action === 'confirm' - } + }, }, methods: { - requestClose () { + requestClose() { if (this.shouldConfirm) { this.showing = true } else if (this.action === 'save') { @@ -35,18 +32,18 @@ const DraftCloser = { this.discard() } }, - save () { + save() { this.$emit('save') this.showing = false }, - discard () { + discard() { this.$emit('discard') this.showing = false }, - cancel () { + cancel() { this.showing = false - } - } + }, + }, } export default DraftCloser diff --git a/src/components/drafts/drafts.js b/src/components/drafts/drafts.js index 201417f66..8acde3c17 100644 --- a/src/components/drafts/drafts.js +++ b/src/components/drafts/drafts.js @@ -4,13 +4,13 @@ import List from 'src/components/list/list.vue' const Drafts = { components: { Draft, - List + List, }, computed: { - drafts () { + drafts() { return this.$store.getters.draftsArray - } - } + }, + }, } export default Drafts diff --git a/src/components/edit_status_form/edit_status_form.js b/src/components/edit_status_form/edit_status_form.js index 323763370..b8df92794 100644 --- a/src/components/edit_status_form/edit_status_form.js +++ b/src/components/edit_status_form/edit_status_form.js @@ -1,21 +1,21 @@ -import PostStatusForm from '../post_status_form/post_status_form.vue' import statusPosterService from '../../services/status_poster/status_poster.service.js' +import PostStatusForm from '../post_status_form/post_status_form.vue' const EditStatusForm = { components: { - PostStatusForm + PostStatusForm, }, props: { params: { type: Object, - required: true - } + required: true, + }, }, methods: { - requestClose () { + requestClose() { this.$refs.postStatusForm.requestClose() }, - doEditStatus ({ status, spoilerText, sensitive, media, contentType, poll }) { + doEditStatus({ status, spoilerText, sensitive, media, contentType, poll }) { const params = { store: this.$store, statusId: this.params.statusId, @@ -24,21 +24,22 @@ const EditStatusForm = { sensitive, poll, media, - contentType + contentType, } - return statusPosterService.editStatus(params) + return statusPosterService + .editStatus(params) .then((data) => { return data }) .catch((err) => { console.error('Error editing status', err) return { - error: err.message + error: err.message, } }) - } - } + }, + }, } export default EditStatusForm diff --git a/src/components/edit_status_modal/edit_status_modal.js b/src/components/edit_status_modal/edit_status_modal.js index 4c10c21a0..8a16e4234 100644 --- a/src/components/edit_status_modal/edit_status_modal.js +++ b/src/components/edit_status_modal/edit_status_modal.js @@ -1,34 +1,35 @@ +import get from 'lodash/get' + +import { useEditStatusStore } from 'src/stores/editStatus' import EditStatusForm from '../edit_status_form/edit_status_form.vue' import Modal from '../modal/modal.vue' -import get from 'lodash/get' -import { useEditStatusStore } from 'src/stores/editStatus' const EditStatusModal = { components: { EditStatusForm, - Modal + Modal, }, - data () { + data() { return { - resettingForm: false + resettingForm: false, } }, computed: { - isLoggedIn () { + isLoggedIn() { return !!this.$store.state.users.currentUser }, - modalActivated () { + modalActivated() { return useEditStatusStore().modalActivated }, - isFormVisible () { + isFormVisible() { return this.isLoggedIn && !this.resettingForm && this.modalActivated }, - params () { + params() { return useEditStatusStore().params || {} - } + }, }, watch: { - params (newVal, oldVal) { + params(newVal, oldVal) { if (get(newVal, 'statusId') !== get(oldVal, 'statusId')) { this.resettingForm = true this.$nextTick(() => { @@ -36,20 +37,22 @@ const EditStatusModal = { }) } }, - isFormVisible (val) { + isFormVisible(val) { if (val) { - this.$nextTick(() => this.$el && this.$el.querySelector('textarea').focus()) + this.$nextTick( + () => this.$el && this.$el.querySelector('textarea').focus(), + ) } - } + }, }, methods: { - closeModal () { + closeModal() { this.$refs.editStatusForm.requestClose() }, - doCloseModal () { + doCloseModal() { useEditStatusStore().closeEditStatusModal() - } - } + }, + }, } export default EditStatusModal diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js index f6ba6e245..24794640e 100644 --- a/src/components/emoji_input/emoji_input.js +++ b/src/components/emoji_input/emoji_input.js @@ -1,20 +1,18 @@ -import Completion from '../../services/completion/completion.js' -import genRandomSeed from '../../services/random_seed/random_seed.service.js' -import EmojiPicker from '../emoji_picker/emoji_picker.vue' +import { take } from 'lodash' + import Popover from 'src/components/popover/popover.vue' import ScreenReaderNotice from 'src/components/screen_reader_notice/screen_reader_notice.vue' -import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue' -import { take } from 'lodash' -import { findOffset } from '../../services/offset_finder/offset_finder.service.js' import { ensureFinalFallback } from '../../i18n/languages.js' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faSmileBeam -} from '@fortawesome/free-regular-svg-icons' +import Completion from '../../services/completion/completion.js' +import { findOffset } from '../../services/offset_finder/offset_finder.service.js' +import genRandomSeed from '../../services/random_seed/random_seed.service.js' +import EmojiPicker from '../emoji_picker/emoji_picker.vue' +import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue' -library.add( - faSmileBeam -) +import { library } from '@fortawesome/fontawesome-svg-core' +import { faSmileBeam } from '@fortawesome/free-regular-svg-icons' + +library.add(faSmileBeam) /** * EmojiInput - augmented inputs for emoji and autocomplete support in inputs @@ -60,14 +58,14 @@ const EmojiInput = { * For commonly used suggestors (emoji, users, both) use suggestor.js */ required: true, - type: Function + type: Function, }, modelValue: { /** * Used for v-model */ required: true, - type: String + type: String, }, enableEmojiPicker: { /** @@ -75,7 +73,7 @@ const EmojiInput = { */ required: false, type: Boolean, - default: false + default: false, }, hideEmojiButton: { /** @@ -84,7 +82,7 @@ const EmojiInput = { */ required: false, type: Boolean, - default: false + default: false, }, enableStickerPicker: { /** @@ -92,7 +90,7 @@ const EmojiInput = { */ required: false, type: Boolean, - default: false + default: false, }, placement: { /** @@ -101,15 +99,15 @@ const EmojiInput = { */ required: false, type: String, // 'auto', 'top', 'bottom' - default: 'auto' + default: 'auto', }, newlineOnCtrlEnter: { required: false, type: Boolean, - default: false - } + default: false, + }, }, - data () { + data() { return { randomSeed: genRandomSeed(), input: undefined, @@ -122,58 +120,65 @@ const EmojiInput = { disableClickOutside: false, suggestions: [], overlayStyle: {}, - pickerShown: false + pickerShown: false, } }, components: { Popover, EmojiPicker, UnicodeDomainIndicator, - ScreenReaderNotice + ScreenReaderNotice, }, computed: { - padEmoji () { + padEmoji() { return this.$store.getters.mergedConfig.padEmoji }, - defaultCandidateIndex () { + defaultCandidateIndex() { return this.$store.getters.mergedConfig.autocompleteSelect ? 0 : -1 }, - preText () { + preText() { return this.modelValue.slice(0, this.caret) }, - postText () { + postText() { return this.modelValue.slice(this.caret) }, - showSuggestions () { - return this.focused && + showSuggestions() { + return ( + this.focused && this.suggestions && this.suggestions.length > 0 && !this.pickerShown && !this.temporarilyHideSuggestions + ) }, - textAtCaret () { + textAtCaret() { return this.wordAtCaret?.word }, - wordAtCaret () { + wordAtCaret() { if (this.modelValue && this.caret) { - const word = Completion.wordAtPosition(this.modelValue, this.caret - 1) || {} + const word = + Completion.wordAtPosition(this.modelValue, this.caret - 1) || {} return word } }, - languages () { - return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage) + languages() { + return ensureFinalFallback( + this.$store.getters.mergedConfig.interfaceLanguage, + ) }, - maybeLocalizedEmojiNamesAndKeywords () { - return emoji => { + maybeLocalizedEmojiNamesAndKeywords() { + return (emoji) => { const names = [emoji.displayText] const keywords = [] if (emoji.displayTextI18n) { - names.push(this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args)) + names.push( + this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args), + ) } if (emoji.annotations) { - this.languages.forEach(lang => { + this.languages.forEach((lang) => { names.push(emoji.annotations[lang]?.name) keywords.push(...(emoji.annotations[lang]?.keywords || [])) @@ -181,13 +186,13 @@ const EmojiInput = { } return { - names: names.filter(k => k), - keywords: keywords.filter(k => k) + names: names.filter((k) => k), + keywords: keywords.filter((k) => k), } } }, - maybeLocalizedEmojiName () { - return emoji => { + maybeLocalizedEmojiName() { + return (emoji) => { if (!emoji.annotations) { return emoji.displayText } @@ -205,16 +210,18 @@ const EmojiInput = { return emoji.displayText } }, - suggestionListId () { + suggestionListId() { return `suggestions-${this.randomSeed}` }, - suggestionItemId () { + suggestionItemId() { return (index) => `suggestion-item-${index}-${this.randomSeed}` - } + }, }, - mounted () { + mounted() { const { root, hiddenOverlayCaret, suggestorPopover } = this.$refs - const input = root.querySelector('.emoji-input > input') || root.querySelector('.emoji-input > textarea') + const input = + root.querySelector('.emoji-input > input') || + root.querySelector('.emoji-input > textarea') if (!input) return this.input = input this.caretEl = hiddenOverlayCaret @@ -243,7 +250,7 @@ const EmojiInput = { input.addEventListener('input', this.onInput) input.addEventListener('scroll', this.onInputScroll) }, - unmounted () { + unmounted() { const { input } = this if (input) { input.removeEventListener('blur', this.onBlur) @@ -273,36 +280,40 @@ const EmojiInput = { this.suggestions = [] return } - const matchedSuggestions = await this.suggest(newWord, this.maybeLocalizedEmojiNamesAndKeywords) + const matchedSuggestions = await this.suggest( + newWord, + this.maybeLocalizedEmojiNamesAndKeywords, + ) // Async: cancel if textAtCaret has changed during wait if (this.textAtCaret !== newWord || matchedSuggestions.length <= 0) { this.suggestions = [] return } - this.suggestions = take(matchedSuggestions, 5) - .map(({ imageUrl, ...rest }) => ({ + this.suggestions = take(matchedSuggestions, 5).map( + ({ imageUrl, ...rest }) => ({ ...rest, - img: imageUrl || '' - })) + img: imageUrl || '', + }), + ) this.highlighted = this.defaultCandidateIndex this.$refs.screenReaderNotice.announce( this.$t( 'tool_tip.autocomplete_available', { number: this.suggestions.length }, - this.suggestions.length - ) + this.suggestions.length, + ), ) - } + }, }, methods: { - onInputScroll (e) { + onInputScroll(e) { this.$refs.hiddenOverlay.scrollTo({ top: this.input.scrollTop, - left: this.input.scrollLeft + left: this.input.scrollLeft, }) this.setCaret(e) }, - triggerShowPicker () { + triggerShowPicker() { this.$nextTick(() => { this.$refs.picker.showPicker() this.scrollIntoView() @@ -315,7 +326,7 @@ const EmojiInput = { this.disableClickOutside = false }, 0) }, - togglePicker () { + togglePicker() { this.input.focus() if (!this.pickerShown) { this.scrollIntoView() @@ -325,12 +336,16 @@ const EmojiInput = { this.$refs.picker.hidePicker() } }, - replace (replacement) { - const newValue = Completion.replaceWord(this.modelValue, this.wordAtCaret, replacement) + replace(replacement) { + const newValue = Completion.replaceWord( + this.modelValue, + this.wordAtCaret, + replacement, + ) this.$emit('update:modelValue', newValue) this.caret = 0 }, - insert ({ insertion, keepOpen, surroundingSpace = true }) { + insert({ insertion, keepOpen, surroundingSpace = true }) { const before = this.modelValue.substring(0, this.caret) || '' const after = this.modelValue.substring(this.caret) || '' @@ -349,18 +364,24 @@ const EmojiInput = { * them, masto seem to be rendering :emoji::emoji: correctly now so why not */ const isSpaceRegex = /\s/ - const spaceBefore = (surroundingSpace && !isSpaceRegex.exec(before.slice(-1)) && before.length && this.padEmoji > 0) ? ' ' : '' - const spaceAfter = (surroundingSpace && !isSpaceRegex.exec(after[0]) && this.padEmoji) ? ' ' : '' + const spaceBefore = + surroundingSpace && + !isSpaceRegex.exec(before.slice(-1)) && + before.length && + this.padEmoji > 0 + ? ' ' + : '' + const spaceAfter = + surroundingSpace && !isSpaceRegex.exec(after[0]) && this.padEmoji + ? ' ' + : '' - const newValue = [ - before, - spaceBefore, - insertion, - spaceAfter, - after - ].join('') + const newValue = [before, spaceBefore, insertion, spaceAfter, after].join( + '', + ) this.$emit('update:modelValue', newValue) - const position = this.caret + (insertion + spaceAfter + spaceBefore).length + const position = + this.caret + (insertion + spaceAfter + spaceBefore).length if (!keepOpen) { this.input.focus() } @@ -372,13 +393,20 @@ const EmojiInput = { this.caret = position }) }, - replaceText (e, suggestion) { + replaceText(e, suggestion) { const len = this.suggestions.length || 0 - if (this.textAtCaret.length === 1) { return } + if (this.textAtCaret.length === 1) { + return + } if (len > 0 || suggestion) { - const chosenSuggestion = suggestion || this.suggestions[this.highlighted] + const chosenSuggestion = + suggestion || this.suggestions[this.highlighted] const replacement = chosenSuggestion.replacement - const newValue = Completion.replaceWord(this.modelValue, this.wordAtCaret, replacement) + const newValue = Completion.replaceWord( + this.modelValue, + this.wordAtCaret, + replacement, + ) this.$emit('update:modelValue', newValue) this.highlighted = 0 const position = this.wordAtCaret.start + replacement.length @@ -393,7 +421,7 @@ const EmojiInput = { e.preventDefault() } }, - cycleBackward (e) { + cycleBackward(e) { const len = this.suggestions.length || 0 this.highlighted -= 1 @@ -406,7 +434,7 @@ const EmojiInput = { e.preventDefault() } }, - cycleForward (e) { + cycleForward(e) { const len = this.suggestions.length || 0 this.highlighted += 1 @@ -418,26 +446,28 @@ const EmojiInput = { e.preventDefault() } }, - scrollIntoView () { + scrollIntoView() { const rootRef = this.$refs.picker.$el /* Scroller is either `window` (replies in TL), sidebar (main post form, * replies in notifs) or mobile post form. Note that getting and setting * scroll is different for `Window` and `Element`s */ - const scrollerRef = this.$el.closest('.sidebar-scroller') || - this.$el.closest('.post-form-modal-view') || - window - const currentScroll = scrollerRef === window - ? scrollerRef.scrollY - : scrollerRef.scrollTop - const scrollerHeight = scrollerRef === window - ? scrollerRef.innerHeight - : scrollerRef.offsetHeight + const scrollerRef = + this.$el.closest('.sidebar-scroller') || + this.$el.closest('.post-form-modal-view') || + window + const currentScroll = + scrollerRef === window ? scrollerRef.scrollY : scrollerRef.scrollTop + const scrollerHeight = + scrollerRef === window + ? scrollerRef.innerHeight + : scrollerRef.offsetHeight const scrollerBottomBorder = currentScroll + scrollerHeight // We check where the bottom border of root element is, this uses findOffset // to find offset relative to scrollable container (scroller) - const rootBottomBorder = rootRef.offsetHeight + findOffset(rootRef, scrollerRef).top + const rootBottomBorder = + rootRef.offsetHeight + findOffset(rootRef, scrollerRef).top const bottomDelta = Math.max(0, rootBottomBorder - scrollerBottomBorder) // could also check top delta but there's no case for it @@ -459,13 +489,13 @@ const EmojiInput = { } }) }, - onPickerShown () { + onPickerShown() { this.pickerShown = true }, - onPickerClosed () { + onPickerClosed() { this.pickerShown = false }, - onBlur (e) { + onBlur(e) { // Clicking on any suggestion removes focus from autocomplete, // preventing click handler ever executing. this.blurTimeout = setTimeout(() => { @@ -473,10 +503,10 @@ const EmojiInput = { this.setCaret(e) }, 200) }, - onClick (e, suggestion) { + onClick(e, suggestion) { this.replaceText(e, suggestion) }, - onFocus (e) { + onFocus(e) { if (this.blurTimeout) { clearTimeout(this.blurTimeout) this.blurTimeout = null @@ -486,7 +516,7 @@ const EmojiInput = { this.setCaret(e) this.temporarilyHideSuggestions = false }, - onKeyUp (e) { + onKeyUp(e) { const { key } = e this.setCaret(e) @@ -498,10 +528,10 @@ const EmojiInput = { this.temporarilyHideSuggestions = false } }, - onPaste (e) { + onPaste(e) { this.setCaret(e) }, - onKeyDown (e) { + onKeyDown(e) { const { ctrlKey, shiftKey, key } = e if (this.newlineOnCtrlEnter && ctrlKey && key === 'Enter') { this.insert({ insertion: '\n', surroundingSpace: false }) @@ -545,30 +575,30 @@ const EmojiInput = { } } }, - onInput (e) { + onInput(e) { this.setCaret(e) this.$emit('update:modelValue', e.target.value) }, - onStickerUploaded (e) { + onStickerUploaded(e) { this.$emit('sticker-uploaded', e) }, - onStickerUploadFailed (e) { + onStickerUploadFailed(e) { this.$emit('sticker-upload-Failed', e) }, - setCaret ({ target: { selectionStart } }) { + setCaret({ target: { selectionStart } }) { this.caret = selectionStart this.$nextTick(() => { this.$refs.suggestorPopover.updateStyles() }) }, - autoCompleteItemLabel (suggestion) { + autoCompleteItemLabel(suggestion) { if (suggestion.user) { return suggestion.displayText + ' ' + suggestion.detailText } else { return this.maybeLocalizedEmojiName(suggestion) } - } - } + }, + }, } export default EmojiInput diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js index f2daf2f46..79d97cff7 100644 --- a/src/components/emoji_input/suggestor.js +++ b/src/components/emoji_input/suggestor.js @@ -10,7 +10,7 @@ * doesn't support user linking you can just provide only emoji. */ -export default data => { +export default (data) => { const emojiCurry = suggestEmoji(data.emoji) const usersCurry = data.store && suggestUsers(data.store) return (input, nameKeywordLocalizer) => { @@ -25,22 +25,35 @@ export default data => { } } -export const suggestEmoji = emojis => (input, nameKeywordLocalizer) => { +export const suggestEmoji = (emojis) => (input, nameKeywordLocalizer) => { const noPrefix = input.toLowerCase().substr(1) return emojis - .map(emoji => ({ ...emoji, ...nameKeywordLocalizer(emoji) })) - .filter((emoji) => (emoji.names.concat(emoji.keywords)).filter(kw => kw.toLowerCase().match(noPrefix)).length) - .map(k => { + .map((emoji) => ({ ...emoji, ...nameKeywordLocalizer(emoji) })) + .filter( + (emoji) => + emoji.names + .concat(emoji.keywords) + .filter((kw) => kw.toLowerCase().match(noPrefix)).length, + ) + .map((k) => { let score = 0 // An exact match always wins - score += Math.max(...k.names.map(name => name.toLowerCase() === noPrefix ? 200 : 0), 0) + score += Math.max( + ...k.names.map((name) => (name.toLowerCase() === noPrefix ? 200 : 0)), + 0, + ) // Prioritize custom emoji a lot score += k.imageUrl ? 100 : 0 // Prioritize prefix matches somewhat - score += Math.max(...k.names.map(kw => kw.toLowerCase().startsWith(noPrefix) ? 10 : 0), 0) + score += Math.max( + ...k.names.map((kw) => + kw.toLowerCase().startsWith(noPrefix) ? 10 : 0, + ), + 0, + ) // Sort by length score -= k.displayText.length @@ -78,7 +91,7 @@ export const suggestUsers = ({ dispatch, state }) => { }) } - return async input => { + return async (input) => { const noPrefix = input.toLowerCase().substr(1) if (previousQuery === noPrefix) return suggestions @@ -92,37 +105,42 @@ export const suggestUsers = ({ dispatch, state }) => { await debounceUserSearch(noPrefix) } - const newSuggestions = state.users.users.filter( - user => - user.screen_name && user.name && ( - user.screen_name.toLowerCase().startsWith(noPrefix) || - user.name.toLowerCase().startsWith(noPrefix)) - ).slice(0, 20).sort((a, b) => { - let aScore = 0 - let bScore = 0 + const newSuggestions = state.users.users + .filter( + (user) => + user.screen_name && + user.name && + (user.screen_name.toLowerCase().startsWith(noPrefix) || + user.name.toLowerCase().startsWith(noPrefix)), + ) + .slice(0, 20) + .sort((a, b) => { + let aScore = 0 + let bScore = 0 - // Matches on screen name (i.e. user@instance) makes a priority - aScore += a.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0 - bScore += b.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0 + // Matches on screen name (i.e. user@instance) makes a priority + aScore += a.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0 + bScore += b.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0 - // Matches on name takes second priority - aScore += a.name.toLowerCase().startsWith(noPrefix) ? 1 : 0 - bScore += b.name.toLowerCase().startsWith(noPrefix) ? 1 : 0 + // Matches on name takes second priority + aScore += a.name.toLowerCase().startsWith(noPrefix) ? 1 : 0 + bScore += b.name.toLowerCase().startsWith(noPrefix) ? 1 : 0 - const diff = (bScore - aScore) * 10 + const diff = (bScore - aScore) * 10 - // Then sort alphabetically - const nameAlphabetically = a.name > b.name ? 1 : -1 - const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1 + // Then sort alphabetically + const nameAlphabetically = a.name > b.name ? 1 : -1 + const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1 - return diff + nameAlphabetically + screenNameAlphabetically - }).map((user) => ({ - user, - displayText: user.screen_name_ui, - detailText: user.name, - imageUrl: user.profile_image_url_original, - replacement: '@' + user.screen_name + ' ' - })) + return diff + nameAlphabetically + screenNameAlphabetically + }) + .map((user) => ({ + user, + displayText: user.screen_name_ui, + detailText: user.name, + imageUrl: user.profile_image_url_original, + replacement: '@' + user.screen_name + ' ', + })) suggestions = newSuggestions || [] return suggestions diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index 8e572d1d2..e4221f706 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -1,24 +1,26 @@ +import { chunk, debounce, trim } from 'lodash' import { defineAsyncComponent } from 'vue' -import Checkbox from '../checkbox/checkbox.vue' + import Popover from 'src/components/popover/popover.vue' -import StillImage from '../still-image/still-image.vue' import { ensureFinalFallback } from '../../i18n/languages.js' +import Checkbox from '../checkbox/checkbox.vue' +import StillImage from '../still-image/still-image.vue' + import { library } from '@fortawesome/fontawesome-svg-core' import { - faBoxOpen, - faStickyNote, - faSmileBeam, - faSmile, - faUser, - faPaw, - faIceCream, - faBus, faBasketballBall, - faLightbulb, + faBoxOpen, + faBus, faCode, - faFlag + faFlag, + faIceCream, + faLightbulb, + faPaw, + faSmile, + faSmileBeam, + faStickyNote, + faUser, } from '@fortawesome/free-solid-svg-icons' -import { debounce, trim, chunk } from 'lodash' library.add( faBoxOpen, @@ -32,7 +34,7 @@ library.add( faBasketballBall, faLightbulb, faCode, - faFlag + faFlag, ) const UNICODE_EMOJI_GROUP_ICON = { @@ -44,16 +46,16 @@ const UNICODE_EMOJI_GROUP_ICON = { activities: 'basketball-ball', objects: 'lightbulb', symbols: 'code', - flags: 'flag' + flags: 'flag', } const maybeLocalizedKeywords = (emoji, languages, nameLocalizer) => { const res = [emoji.displayText, nameLocalizer(emoji)] if (emoji.annotations) { - languages.forEach(lang => { + languages.forEach((lang) => { const keywords = emoji.annotations[lang]?.keywords || [] const name = emoji.annotations[lang]?.name - res.push(...(keywords.concat([name]).filter(k => k))) + res.push(...keywords.concat([name]).filter((k) => k)) }) } return res @@ -66,8 +68,8 @@ const filterByKeyword = (list, keyword = '', languages, nameLocalizer) => { const orderedEmojiList = [] for (const emoji of list) { const indices = maybeLocalizedKeywords(emoji, languages, nameLocalizer) - .map(k => k.toLowerCase().indexOf(keywordLowercase)) - .filter(k => k > -1) + .map((k) => k.toLowerCase().indexOf(keywordLowercase)) + .filter((k) => k > -1) const indexOfKeyword = indices.length ? Math.min(...indices) : -1 @@ -84,11 +86,13 @@ const filterByKeyword = (list, keyword = '', languages, nameLocalizer) => { const getOffset = (elem) => { const style = elem.style.transform const res = /translateY\((\d+)px\)/.exec(style) - if (!res) { return 0 } + if (!res) { + return 0 + } return res[1] } -const toHeaderId = id => { +const toHeaderId = (id) => { return id.replace(/^row-\d+-/, '') } @@ -97,20 +101,20 @@ const EmojiPicker = { enableStickerPicker: { required: false, type: Boolean, - default: true + default: true, }, hideCustomEmoji: { required: false, type: Boolean, - default: false - } + default: false, + }, }, inject: { popoversZLayer: { - default: '' - } + default: '', + }, }, - data () { + data() { return { keyword: '', activeGroup: 'custom', @@ -125,20 +129,22 @@ const EmojiPicker = { emojiRefs: {}, filteredEmojiGroups: [], emojiSize: 0, - width: 0 + width: 0, } }, components: { - StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')), + StickerPicker: defineAsyncComponent( + () => import('../sticker_picker/sticker_picker.vue'), + ), Checkbox, StillImage, - Popover + Popover, }, methods: { - groupScroll (e) { + groupScroll(e) { e.currentTarget.scrollLeft += e.deltaY + e.deltaX }, - updateEmojiSize () { + updateEmojiSize() { const css = window.getComputedStyle(this.$refs.popover.$el) const fontSize = css.getPropertyValue('font-size') || '1rem' const emojiSize = css.getPropertyValue('--emojiSize') || '2.2rem' @@ -163,56 +169,68 @@ const EmojiPicker = { emojiSizeReal = emojiSizeValue } - const fullEmojiSize = emojiSizeReal + (2 * 0.2 * fontSizeMultiplier * 14) + const fullEmojiSize = emojiSizeReal + 2 * 0.2 * fontSizeMultiplier * 14 this.emojiSize = fullEmojiSize }, - showPicker () { + showPicker() { this.$refs.popover.showPopover() this.$nextTick(() => { this.onShowing() }) }, - hidePicker () { + hidePicker() { this.$refs.popover.hidePopover() }, - setAnchorEl (el) { + setAnchorEl(el) { this.$refs.popover.setAnchorEl(el) }, - setGroupRef (name) { - return el => { this.groupRefs[name] = el } + setGroupRef(name) { + return (el) => { + this.groupRefs[name] = el + } }, - onPopoverShown () { + onPopoverShown() { this.$emit('show') }, - onPopoverClosed () { + onPopoverClosed() { this.$emit('close') }, - onStickerUploaded (e) { + onStickerUploaded(e) { this.$emit('sticker-uploaded', e) }, - onStickerUploadFailed (e) { + onStickerUploadFailed(e) { this.$emit('sticker-upload-failed', e) }, - onEmoji (emoji) { - const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement + onEmoji(emoji) { + const value = emoji.imageUrl + ? `:${emoji.displayText}:` + : emoji.replacement if (!this.keepOpen) { this.$refs.popover.hidePopover() } - this.$emit('emoji', { insertion: value, insertionUrl: emoji.imageUrl, keepOpen: this.keepOpen }) + this.$emit('emoji', { + insertion: value, + insertionUrl: emoji.imageUrl, + keepOpen: this.keepOpen, + }) }, - onScroll (startIndex, endIndex, visibleStartIndex, visibleEndIndex) { + onScroll(startIndex, endIndex, visibleStartIndex, visibleEndIndex) { const target = this.$refs['emoji-groups'].$el this.scrolledGroup(target, visibleStartIndex, visibleEndIndex) }, - scrolledGroup (target, start, end) { + scrolledGroup(target, start, end) { const top = target.scrollTop + 5 this.$nextTick(() => { - this.emojiItems.slice(start, end + 1).forEach(group => { + this.emojiItems.slice(start, end + 1).forEach((group) => { const headerId = toHeaderId(group.id) const ref = this.groupRefs['group-' + group.id] - if (!ref) { return } + if (!ref) { + return + } const elem = ref.$el.parentElement - if (!elem) { return } + if (!elem) { + return + } if (elem && getOffset(elem) <= top) { this.activeGroup = headerId } @@ -220,7 +238,7 @@ const EmojiPicker = { this.scrollHeader() }) }, - scrollHeader () { + scrollHeader() { // Scroll the active tab's header into view const headerRef = this.groupRefs['group-header-' + this.activeGroup] const left = headerRef.offsetLeft @@ -228,7 +246,9 @@ const EmojiPicker = { const headerCont = this.$refs.header const currentScroll = headerCont.scrollLeft const currentScrollRight = currentScroll + headerCont.clientWidth - const setScroll = s => { headerCont.scrollLeft = s } + const setScroll = (s) => { + headerCont.scrollLeft = s + } const margin = 7 // .emoji-tabs-item: padding if (left - margin < currentScroll) { @@ -237,12 +257,12 @@ const EmojiPicker = { setScroll(right + margin - headerCont.clientWidth) } }, - highlight (groupId) { + highlight(groupId) { this.setShowStickers(false) - const indexInList = this.emojiItems.findIndex(k => k.id === groupId) + const indexInList = this.emojiItems.findIndex((k) => k.id === groupId) this.$refs['emoji-groups'].scrollToItem(indexInList) }, - updateScrolledClass (target) { + updateScrolledClass(target) { if (target.scrollTop <= 5) { this.groupsScrolledClass = 'scrolled-top' } else if (target.scrollTop >= target.scrollTopMax - 5) { @@ -251,16 +271,21 @@ const EmojiPicker = { this.groupsScrolledClass = 'scrolled-middle' } }, - toggleStickers () { + toggleStickers() { this.showingStickers = !this.showingStickers }, - setShowStickers (value) { + setShowStickers(value) { this.showingStickers = value }, - filterByKeyword (list, keyword) { - return filterByKeyword(list, keyword, this.languages, this.maybeLocalizedEmojiName) + filterByKeyword(list, keyword) { + return filterByKeyword( + list, + keyword, + this.languages, + this.maybeLocalizedEmojiName, + ) }, - onShowing () { + onShowing() { const oldContentLoaded = this.contentLoaded this.updateEmojiSize() this.recalculateItemPerRow() @@ -277,59 +302,59 @@ const EmojiPicker = { }) } }, - getFilteredEmojiGroups () { + getFilteredEmojiGroups() { return this.allEmojiGroups - .map(group => ({ + .map((group) => ({ ...group, - emojis: this.filterByKeyword(group.emojis, trim(this.keyword)) + emojis: this.filterByKeyword(group.emojis, trim(this.keyword)), })) - .filter(group => group.emojis.length > 0) + .filter((group) => group.emojis.length > 0) }, - recalculateItemPerRow () { + recalculateItemPerRow() { this.$nextTick(() => { if (!this.$refs['emoji-groups']) { return } this.width = this.$refs['emoji-groups'].$el.clientWidth }) - } + }, }, watch: { - keyword () { + keyword() { this.onScroll() this.debouncedHandleKeywordChange() }, - allCustomGroups () { + allCustomGroups() { this.filteredEmojiGroups = this.getFilteredEmojiGroups() - } + }, }, computed: { - minItemSize () { + minItemSize() { return this.emojiSize }, // used to watch it - fontSize () { + fontSize() { this.$nextTick(() => { this.updateEmojiSize() }) return this.$store.getters.mergedConfig.fontSize }, - emojiHeight () { + emojiHeight() { return this.emojiSize }, - itemPerRow () { + itemPerRow() { return this.width ? Math.floor(this.width / this.emojiSize) : 6 }, - activeGroupView () { + activeGroupView() { return this.showingStickers ? '' : this.activeGroup }, - stickersAvailable () { + stickersAvailable() { if (this.$store.state.instance.stickers) { return this.$store.state.instance.stickers.length > 0 } return 0 }, - allCustomGroups () { + allCustomGroups() { if (this.hideCustomEmoji || this.hideCustomEmojiInPicker) { return {} } @@ -339,46 +364,49 @@ const EmojiPicker = { } return emojis }, - defaultGroup () { + defaultGroup() { return Object.keys(this.allCustomGroups)[0] }, - unicodeEmojiGroups () { - return this.$store.getters.standardEmojiGroupList.map(group => ({ + unicodeEmojiGroups() { + return this.$store.getters.standardEmojiGroupList.map((group) => ({ id: `standard-${group.id}`, text: this.$t(`emoji.unicode_groups.${group.id}`), icon: UNICODE_EMOJI_GROUP_ICON[group.id], - emojis: group.emojis + emojis: group.emojis, })) }, - allEmojiGroups () { + allEmojiGroups() { return Object.entries(this.allCustomGroups) .map(([, v]) => v) .concat(this.unicodeEmojiGroups) }, - stickerPickerEnabled () { + stickerPickerEnabled() { return (this.$store.state.instance.stickers || []).length !== 0 }, - debouncedHandleKeywordChange () { + debouncedHandleKeywordChange() { return debounce(() => { this.filteredEmojiGroups = this.getFilteredEmojiGroups() }, 500) }, - emojiItems () { - return this.filteredEmojiGroups.map(group => - chunk(group.emojis, this.itemPerRow) - .map((items, index) => ({ + emojiItems() { + return this.filteredEmojiGroups + .map((group) => + chunk(group.emojis, this.itemPerRow).map((items, index) => ({ ...group, id: index === 0 ? group.id : `row-${index}-${group.id}`, emojis: items, - isFirstRow: index === 0 - }))) + isFirstRow: index === 0, + })), + ) .reduce((a, c) => a.concat(c), []) }, - languages () { - return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage) + languages() { + return ensureFinalFallback( + this.$store.getters.mergedConfig.interfaceLanguage, + ) }, - maybeLocalizedEmojiName () { - return emoji => { + maybeLocalizedEmojiName() { + return (emoji) => { if (!emoji.annotations) { return emoji.displayText } @@ -396,10 +424,10 @@ const EmojiPicker = { return emoji.displayText } }, - isInModal () { + isInModal() { return this.popoversZLayer === 'modals' - } - } + }, + }, } export default EmojiPicker diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js index f5e1b68f6..8a121befc 100644 --- a/src/components/emoji_reactions/emoji_reactions.js +++ b/src/components/emoji_reactions/emoji_reactions.js @@ -1,18 +1,11 @@ +import StillImage from 'src/components/still-image/still-image.vue' import UserAvatar from '../user_avatar/user_avatar.vue' import UserListPopover from '../user_list_popover/user_list_popover.vue' -import StillImage from 'src/components/still-image/still-image.vue' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faPlus, - faMinus, - faCheck -} from '@fortawesome/free-solid-svg-icons' -library.add( - faPlus, - faMinus, - faCheck -) +import { library } from '@fortawesome/fontawesome-svg-core' +import { faCheck, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons' + +library.add(faPlus, faMinus, faCheck) const EMOJI_REACTION_COUNT_CUTOFF = 12 @@ -21,57 +14,62 @@ const EmojiReactions = { components: { UserAvatar, UserListPopover, - StillImage + StillImage, }, props: ['status'], data: () => ({ - showAll: false + showAll: false, }), computed: { - tooManyReactions () { + tooManyReactions() { return this.status.emoji_reactions.length > EMOJI_REACTION_COUNT_CUTOFF }, - emojiReactions () { + emojiReactions() { return this.showAll ? this.status.emoji_reactions : this.status.emoji_reactions.slice(0, EMOJI_REACTION_COUNT_CUTOFF) }, - showMoreString () { + showMoreString() { return `+${this.status.emoji_reactions.length - EMOJI_REACTION_COUNT_CUTOFF}` }, - accountsForEmoji () { + accountsForEmoji() { return this.status.emoji_reactions.reduce((acc, reaction) => { acc[reaction.name] = reaction.accounts || [] return acc }, {}) }, - loggedIn () { + loggedIn() { return !!this.$store.state.users.currentUser }, - remoteInteractionLink () { - return this.$store.getters.remoteInteractionLink({ statusId: this.status.id }) - } + remoteInteractionLink() { + return this.$store.getters.remoteInteractionLink({ + statusId: this.status.id, + }) + }, }, methods: { - toggleShowAll () { + toggleShowAll() { this.showAll = !this.showAll }, - reactedWith (emoji) { - return this.status.emoji_reactions.find(r => r.name === emoji).me + reactedWith(emoji) { + return this.status.emoji_reactions.find((r) => r.name === emoji).me }, - async fetchEmojiReactionsByIfMissing () { - const hasNoAccounts = this.status.emoji_reactions.find(r => !r.accounts) + async fetchEmojiReactionsByIfMissing() { + const hasNoAccounts = this.status.emoji_reactions.find((r) => !r.accounts) if (hasNoAccounts) { - return await this.$store.dispatch('fetchEmojiReactionsBy', this.status.id) + return await this.$store.dispatch( + 'fetchEmojiReactionsBy', + this.status.id, + ) } }, - reactWith (emoji) { + reactWith(emoji) { this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji }) }, - unreact (emoji) { + unreact(emoji) { this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji }) }, - async emojiOnClick (emoji) { + async emojiOnClick(emoji) { if (!this.loggedIn) return await this.fetchEmojiReactionsByIfMissing() @@ -81,19 +79,23 @@ const EmojiReactions = { this.reactWith(emoji) } }, - counterTriggerAttrs (reaction) { + counterTriggerAttrs(reaction) { return { class: [ 'emoji-reaction-count-button', { '-picked-reaction': this.reactedWith(reaction.name), - toggled: this.reactedWith(reaction.name) - } + toggled: this.reactedWith(reaction.name), + }, ], - 'aria-label': this.$t('status.reaction_count_label', { num: reaction.count }, reaction.count) + 'aria-label': this.$t( + 'status.reaction_count_label', + { num: reaction.count }, + reaction.count, + ), } - } - } + }, + }, } export default EmojiReactions diff --git a/src/components/exporter/exporter.js b/src/components/exporter/exporter.js index fc75372e3..12213d9e3 100644 --- a/src/components/exporter/exporter.js +++ b/src/components/exporter/exporter.js @@ -1,45 +1,47 @@ import { library } from '@fortawesome/fontawesome-svg-core' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' -library.add( - faCircleNotch -) +library.add(faCircleNotch) const Exporter = { props: { getContent: { type: Function, - required: true + required: true, }, filename: { type: String, - default: 'export.csv' + default: 'export.csv', }, exportButtonLabel: { type: String }, - processingMessage: { type: String } + processingMessage: { type: String }, }, - data () { + data() { return { - processing: false + processing: false, } }, methods: { - process () { + process() { this.processing = true - this.getContent() - .then((content) => { - const fileToDownload = document.createElement('a') - fileToDownload.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(content)) - fileToDownload.setAttribute('download', this.filename) - fileToDownload.style.display = 'none' - document.body.appendChild(fileToDownload) - fileToDownload.click() - document.body.removeChild(fileToDownload) - // Add delay before hiding processing state since browser takes some time to handle file download - setTimeout(() => { this.processing = false }, 2000) - }) - } - } + this.getContent().then((content) => { + const fileToDownload = document.createElement('a') + fileToDownload.setAttribute( + 'href', + 'data:text/plain;charset=utf-8,' + encodeURIComponent(content), + ) + fileToDownload.setAttribute('download', this.filename) + fileToDownload.style.display = 'none' + document.body.appendChild(fileToDownload) + fileToDownload.click() + document.body.removeChild(fileToDownload) + // Add delay before hiding processing state since browser takes some time to handle file download + setTimeout(() => { + this.processing = false + }, 2000) + }) + }, + }, } export default Exporter diff --git a/src/components/extra_notifications/extra_notifications.js b/src/components/extra_notifications/extra_notifications.js index 673b28662..1deebd878 100644 --- a/src/components/extra_notifications/extra_notifications.js +++ b/src/components/extra_notifications/extra_notifications.js @@ -1,55 +1,72 @@ -import { mapGetters } from 'vuex' import { mapState as mapPiniaState } from 'pinia' +import { mapGetters } from 'vuex' + import { useAnnouncementsStore } from 'src/stores/announcements' +import { useInterfaceStore } from 'src/stores/interface' import { library } from '@fortawesome/fontawesome-svg-core' import { - faUserPlus, + faBullhorn, faComments, - faBullhorn + faUserPlus, } from '@fortawesome/free-solid-svg-icons' -import { useInterfaceStore } from 'src/stores/interface' - -library.add( - faUserPlus, - faComments, - faBullhorn -) +library.add(faUserPlus, faComments, faBullhorn) const ExtraNotifications = { computed: { - shouldShowChats () { - return this.mergedConfig.showExtraNotifications && this.mergedConfig.showChatsInExtraNotifications && this.unreadChatCount + shouldShowChats() { + return ( + this.mergedConfig.showExtraNotifications && + this.mergedConfig.showChatsInExtraNotifications && + this.unreadChatCount + ) }, - shouldShowAnnouncements () { - return this.mergedConfig.showExtraNotifications && this.mergedConfig.showAnnouncementsInExtraNotifications && this.unreadAnnouncementCount + shouldShowAnnouncements() { + return ( + this.mergedConfig.showExtraNotifications && + this.mergedConfig.showAnnouncementsInExtraNotifications && + this.unreadAnnouncementCount + ) }, - shouldShowFollowRequests () { - return this.mergedConfig.showExtraNotifications && this.mergedConfig.showFollowRequestsInExtraNotifications && this.followRequestCount + shouldShowFollowRequests() { + return ( + this.mergedConfig.showExtraNotifications && + this.mergedConfig.showFollowRequestsInExtraNotifications && + this.followRequestCount + ) }, - hasAnythingToShow () { - return this.shouldShowChats || this.shouldShowAnnouncements || this.shouldShowFollowRequests + hasAnythingToShow() { + return ( + this.shouldShowChats || + this.shouldShowAnnouncements || + this.shouldShowFollowRequests + ) }, - shouldShowCustomizationTip () { - return this.mergedConfig.showExtraNotificationsTip && this.hasAnythingToShow + shouldShowCustomizationTip() { + return ( + this.mergedConfig.showExtraNotificationsTip && this.hasAnythingToShow + ) }, - currentUser () { + currentUser() { return this.$store.state.users.currentUser }, ...mapGetters(['unreadChatCount', 'followRequestCount', 'mergedConfig']), ...mapPiniaState(useAnnouncementsStore, { - unreadAnnouncementCount: 'unreadAnnouncementCount' - }) + unreadAnnouncementCount: 'unreadAnnouncementCount', + }), }, methods: { - openNotificationSettings () { + openNotificationSettings() { return useInterfaceStore().openSettingsModalTab('notifications') }, - dismissConfigurationTip () { - return this.$store.dispatch('setOption', { name: 'showExtraNotificationsTip', value: false }) - } - } + dismissConfigurationTip() { + return this.$store.dispatch('setOption', { + name: 'showExtraNotificationsTip', + value: false, + }) + }, + }, } export default ExtraNotifications diff --git a/src/components/features_panel/features_panel.js b/src/components/features_panel/features_panel.js index d177efebe..e92cb975e 100644 --- a/src/components/features_panel/features_panel.js +++ b/src/components/features_panel/features_panel.js @@ -2,15 +2,33 @@ import fileSizeFormatService from '../../services/file_size_format/file_size_for const FeaturesPanel = { computed: { - shout: function () { return this.$store.state.instance.shoutAvailable }, - pleromaChatMessages: function () { return this.$store.state.instance.pleromaChatMessagesAvailable }, - gopher: function () { return this.$store.state.instance.gopherAvailable }, - whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled }, - mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable }, - minimalScopesMode: function () { return this.$store.state.instance.minimalScopesMode }, - textlimit: function () { return this.$store.state.instance.textlimit }, - uploadlimit: function () { return fileSizeFormatService.fileSizeFormat(this.$store.state.instance.uploadlimit) } - } + shout: function () { + return this.$store.state.instance.shoutAvailable + }, + pleromaChatMessages: function () { + return this.$store.state.instance.pleromaChatMessagesAvailable + }, + gopher: function () { + return this.$store.state.instance.gopherAvailable + }, + whoToFollow: function () { + return this.$store.state.instance.suggestionsEnabled + }, + mediaProxy: function () { + return this.$store.state.instance.mediaProxyAvailable + }, + minimalScopesMode: function () { + return this.$store.state.instance.minimalScopesMode + }, + textlimit: function () { + return this.$store.state.instance.textlimit + }, + uploadlimit: function () { + return fileSizeFormatService.fileSizeFormat( + this.$store.state.instance.uploadlimit, + ) + }, + }, } export default FeaturesPanel diff --git a/src/components/flash/flash.js b/src/components/flash/flash.js index 87c1d650c..3c25abea1 100644 --- a/src/components/flash/flash.js +++ b/src/components/flash/flash.js @@ -1,53 +1,54 @@ import RuffleService from '../../services/ruffle_service/ruffle_service.js' + import { library } from '@fortawesome/fontawesome-svg-core' import { + faExclamationTriangle, faStop, - faExclamationTriangle } from '@fortawesome/free-solid-svg-icons' -library.add( - faStop, - faExclamationTriangle -) +library.add(faStop, faExclamationTriangle) const Flash = { props: ['src'], - data () { + data() { return { player: false, // can be true, "hidden", false. hidden = element exists loaded: false, - ruffleInstance: null + ruffleInstance: null, } }, methods: { - openPlayer () { + openPlayer() { if (this.player) return // prevent double-loading, or re-loading on failure this.player = 'hidden' RuffleService.getRuffle().then((ruffle) => { const player = ruffle.newest().createPlayer() player.config = { - letterbox: 'on' + letterbox: 'on', } const container = this.$refs.container container.appendChild(player) player.style.width = '100%' player.style.height = '100%' - player.load(this.src).then(() => { - this.player = true - }).catch((e) => { - console.error('Error loading ruffle', e) - this.player = 'error' - }) + player + .load(this.src) + .then(() => { + this.player = true + }) + .catch((e) => { + console.error('Error loading ruffle', e) + this.player = 'error' + }) this.ruffleInstance = player this.$emit('playerOpened') }) }, - closePlayer () { + closePlayer() { this.ruffleInstance && this.ruffleInstance.remove() this.player = false this.$emit('playerClosed') - } - } + }, + }, } export default Flash diff --git a/src/components/follow_button/follow_button.js b/src/components/follow_button/follow_button.js index 443aa9bcc..eb545b28d 100644 --- a/src/components/follow_button/follow_button.js +++ b/src/components/follow_button/follow_button.js @@ -1,24 +1,27 @@ +import { + requestFollow, + requestUnfollow, +} from '../../services/follow_manipulate/follow_manipulate' import ConfirmModal from '../confirm_modal/confirm_modal.vue' -import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate' export default { props: ['relationship', 'user', 'labelFollowing', 'buttonClass'], components: { - ConfirmModal + ConfirmModal, }, - data () { + data() { return { inProgress: false, - showingConfirmUnfollow: false + showingConfirmUnfollow: false, } }, computed: { - shouldConfirmUnfollow () { + shouldConfirmUnfollow() { return this.$store.getters.mergedConfig.modalOnUnfollow }, - isPressed () { + isPressed() { return this.inProgress || this.relationship.following }, - title () { + title() { if (this.inProgress || this.relationship.following) { return this.$t('user_card.follow_unfollow') } else if (this.relationship.requested) { @@ -27,7 +30,7 @@ export default { return this.$t('user_card.follow') } }, - label () { + label() { if (this.inProgress) { return this.$t('user_card.follow_progress') } else if (this.relationship.following) { @@ -38,42 +41,47 @@ export default { return this.$t('user_card.follow') } }, - disabled () { + disabled() { return this.inProgress || this.user.deactivated - } + }, }, methods: { - showConfirmUnfollow () { + showConfirmUnfollow() { this.showingConfirmUnfollow = true }, - hideConfirmUnfollow () { + hideConfirmUnfollow() { this.showingConfirmUnfollow = false }, - onClick () { - this.relationship.following || this.relationship.requested ? this.unfollow() : this.follow() + onClick() { + this.relationship.following || this.relationship.requested + ? this.unfollow() + : this.follow() }, - follow () { + follow() { this.inProgress = true requestFollow(this.relationship.id, this.$store).then(() => { this.inProgress = false }) }, - unfollow () { + unfollow() { if (this.shouldConfirmUnfollow) { this.showConfirmUnfollow() } else { this.doUnfollow() } }, - doUnfollow () { + doUnfollow() { const store = this.$store this.inProgress = true requestUnfollow(this.relationship.id, store).then(() => { this.inProgress = false - store.commit('removeStatus', { timeline: 'friends', userId: this.relationship.id }) + store.commit('removeStatus', { + timeline: 'friends', + userId: this.relationship.id, + }) }) this.hideConfirmUnfollow() - } - } + }, + }, } diff --git a/src/components/follow_card/follow_card.js b/src/components/follow_card/follow_card.js index b26b27a74..e4c84dcdd 100644 --- a/src/components/follow_card/follow_card.js +++ b/src/components/follow_card/follow_card.js @@ -1,30 +1,27 @@ import BasicUserCard from '../basic_user_card/basic_user_card.vue' -import RemoteFollow from '../remote_follow/remote_follow.vue' import FollowButton from '../follow_button/follow_button.vue' +import RemoteFollow from '../remote_follow/remote_follow.vue' import RemoveFollowerButton from '../remove_follower_button/remove_follower_button.vue' const FollowCard = { - props: [ - 'user', - 'noFollowsYou' - ], + props: ['user', 'noFollowsYou'], components: { BasicUserCard, RemoteFollow, FollowButton, - RemoveFollowerButton + RemoveFollowerButton, }, computed: { - isMe () { + isMe() { return this.$store.state.users.currentUser.id === this.user.id }, - loggedIn () { + loggedIn() { return this.$store.state.users.currentUser }, - relationship () { + relationship() { return this.$store.getters.relationship(this.user.id) - } - } + }, + }, } export default FollowCard diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js index b0873bb10..c037ddf42 100644 --- a/src/components/follow_request_card/follow_request_card.js +++ b/src/components/follow_request_card/follow_request_card.js @@ -1,46 +1,48 @@ +import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js' import BasicUserCard from '../basic_user_card/basic_user_card.vue' import ConfirmModal from '../confirm_modal/confirm_modal.vue' -import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js' const FollowRequestCard = { props: ['user'], components: { BasicUserCard, - ConfirmModal + ConfirmModal, }, - data () { + data() { return { showingApproveConfirmDialog: false, - showingDenyConfirmDialog: false + showingDenyConfirmDialog: false, } }, methods: { - findFollowRequestNotificationId () { + findFollowRequestNotificationId() { const notif = notificationsFromStore(this.$store).find( - (notif) => notif.from_profile.id === this.user.id && notif.type === 'follow_request' + (notif) => + notif.from_profile.id === this.user.id && + notif.type === 'follow_request', ) return notif && notif.id }, - showApproveConfirmDialog () { + showApproveConfirmDialog() { this.showingApproveConfirmDialog = true }, - hideApproveConfirmDialog () { + hideApproveConfirmDialog() { this.showingApproveConfirmDialog = false }, - showDenyConfirmDialog () { + showDenyConfirmDialog() { this.showingDenyConfirmDialog = true }, - hideDenyConfirmDialog () { + hideDenyConfirmDialog() { this.showingDenyConfirmDialog = false }, - approveUser () { + approveUser() { if (this.shouldConfirmApprove) { this.showApproveConfirmDialog() } else { this.doApprove() } }, - doApprove () { + doApprove() { this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) this.$store.dispatch('removeFollowRequest', this.user) @@ -48,40 +50,41 @@ const FollowRequestCard = { this.$store.dispatch('markSingleNotificationAsSeen', { id: notifId }) this.$store.dispatch('updateNotification', { id: notifId, - updater: notification => { + updater: (notification) => { notification.type = 'follow' - } + }, }) this.hideApproveConfirmDialog() }, - denyUser () { + denyUser() { if (this.shouldConfirmDeny) { this.showDenyConfirmDialog() } else { this.doDeny() } }, - doDeny () { + doDeny() { const notifId = this.findFollowRequestNotificationId() - this.$store.state.api.backendInteractor.denyUser({ id: this.user.id }) + this.$store.state.api.backendInteractor + .denyUser({ id: this.user.id }) .then(() => { this.$store.dispatch('dismissNotificationLocal', { id: notifId }) this.$store.dispatch('removeFollowRequest', this.user) }) this.hideDenyConfirmDialog() - } + }, }, computed: { - mergedConfig () { + mergedConfig() { return this.$store.getters.mergedConfig }, - shouldConfirmApprove () { + shouldConfirmApprove() { return this.mergedConfig.modalOnApproveFollow }, - shouldConfirmDeny () { + shouldConfirmDeny() { return this.mergedConfig.modalOnDenyFollow - } - } + }, + }, } export default FollowRequestCard diff --git a/src/components/follow_requests/follow_requests.js b/src/components/follow_requests/follow_requests.js index 704a76c66..7dbecee53 100644 --- a/src/components/follow_requests/follow_requests.js +++ b/src/components/follow_requests/follow_requests.js @@ -2,13 +2,13 @@ import FollowRequestCard from '../follow_request_card/follow_request_card.vue' const FollowRequests = { components: { - FollowRequestCard + FollowRequestCard, }, computed: { - requests () { + requests() { return this.$store.state.api.followRequests - } - } + }, + }, } export default FollowRequests diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js index ffc866788..08d25d52d 100644 --- a/src/components/font_control/font_control.js +++ b/src/components/font_control/font_control.js @@ -1,35 +1,29 @@ -import Select from '../select/select.vue' import Checkbox from 'src/components/checkbox/checkbox.vue' import Popover from 'src/components/popover/popover.vue' import { useInterfaceStore } from 'src/stores/interface' +import Select from '../select/select.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faExclamationTriangle, + faFont, faKeyboard, - faFont } from '@fortawesome/free-solid-svg-icons' -library.add( - faExclamationTriangle, - faKeyboard, - faFont -) +library.add(faExclamationTriangle, faKeyboard, faFont) export default { components: { Select, Checkbox, - Popover + Popover, }, - props: [ - 'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit' - ], - mounted () { + props: ['name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit'], + mounted() { useInterfaceStore().queryLocalFonts() }, emits: ['update:modelValue'], - data () { + data() { return { manualEntry: false, availableOptions: [ @@ -37,24 +31,24 @@ export default { 'serif', 'sans-serif', 'monospace', - ...(this.options || []) - ].filter(_ => _) + ...(this.options || []), + ].filter((_) => _), } }, methods: { - toggleManualEntry () { + toggleManualEntry() { this.manualEntry = !this.manualEntry - } + }, }, computed: { - present () { + present() { return typeof this.modelValue !== 'undefined' }, - localFontsList () { + localFontsList() { return useInterfaceStore().localFonts }, - localFontsSize () { + localFontsSize() { return useInterfaceStore().localFonts?.length - } - } + }, + }, } diff --git a/src/components/friends_timeline/friends_timeline.js b/src/components/friends_timeline/friends_timeline.js index 948b23a49..c0c032a8f 100644 --- a/src/components/friends_timeline/friends_timeline.js +++ b/src/components/friends_timeline/friends_timeline.js @@ -1,11 +1,14 @@ import Timeline from '../timeline/timeline.vue' + const FriendsTimeline = { components: { - Timeline + Timeline, }, computed: { - timeline () { return this.$store.state.statuses.timelines.friends } - } + timeline() { + return this.$store.state.statuses.timelines.friends + }, + }, } export default FriendsTimeline diff --git a/src/components/fun_text.style.js b/src/components/fun_text.style.js index 2d3ac1549..eff88feb7 100644 --- a/src/components/fun_text.style.js +++ b/src/components/fun_text.style.js @@ -4,37 +4,37 @@ export default { virtual: true, variants: { greentext: '.greentext', - cyantext: '.cyantext' + cyantext: '.cyantext', }, states: { - faint: '.faint' + faint: '.faint', }, defaultRules: [ { directives: { textColor: '--text', - textAuto: 'preserve' - } + textAuto: 'preserve', + }, }, { state: ['faint'], directives: { - textOpacity: 0.5 - } + textOpacity: 0.5, + }, }, { variant: 'greentext', directives: { textColor: '--cGreen', - textAuto: 'preserve' - } + textAuto: 'preserve', + }, }, { variant: 'cyantext', directives: { textColor: '--cBlue', - textAuto: 'preserve' - } - } - ] + textAuto: 'preserve', + }, + }, + ], } diff --git a/src/components/gallery/gallery.js b/src/components/gallery/gallery.js index 676c830b0..9934a7905 100644 --- a/src/components/gallery/gallery.js +++ b/src/components/gallery/gallery.js @@ -1,6 +1,7 @@ +import { set, sumBy } from 'lodash' + import { useMediaViewerStore } from 'src/stores/media_viewer' import Attachment from '../attachment/attachment.vue' -import { sumBy, set } from 'lodash' const Gallery = { props: [ @@ -17,52 +18,71 @@ const Gallery = { 'shiftUpAttachment', 'shiftDnAttachment', 'editAttachment', - 'grid' + 'grid', ], - data () { + data() { return { sizes: {}, - hidingLong: true + hidingLong: true, } }, components: { Attachment }, computed: { - rows () { + rows() { if (!this.attachments) { return [] } - const attachments = this.limit > 0 - ? this.attachments.slice(0, this.limit) - : this.attachments + const attachments = + this.limit > 0 + ? this.attachments.slice(0, this.limit) + : this.attachments if (this.size === 'hide') { - return attachments.map(item => ({ minimal: true, items: [item] })) + return attachments.map((item) => ({ minimal: true, items: [item] })) } const rows = this.grid ? [{ grid: true, items: attachments }] - : attachments.reduce((acc, attachment, i) => { - if (attachment.mimetype.includes('audio')) { - return [...acc, { audio: true, items: [attachment] }, { items: [] }] - } - if (!( - attachment.mimetype.includes('image') || - attachment.mimetype.includes('video') || - attachment.mimetype.includes('flash') - )) { - return [...acc, { minimal: true, items: [attachment] }, { items: [] }] - } - const maxPerRow = 3 - const attachmentsRemaining = this.attachments.length - i + 1 - const currentRow = acc[acc.length - 1].items - currentRow.push(attachment) - if (currentRow.length >= maxPerRow && attachmentsRemaining > maxPerRow) { - return [...acc, { items: [] }] - } else { - return acc - } - }, [{ items: [] }]).filter(_ => _.items.length > 0) + : attachments + .reduce( + (acc, attachment, i) => { + if (attachment.mimetype.includes('audio')) { + return [ + ...acc, + { audio: true, items: [attachment] }, + { items: [] }, + ] + } + if ( + !( + attachment.mimetype.includes('image') || + attachment.mimetype.includes('video') || + attachment.mimetype.includes('flash') + ) + ) { + return [ + ...acc, + { minimal: true, items: [attachment] }, + { items: [] }, + ] + } + const maxPerRow = 3 + const attachmentsRemaining = this.attachments.length - i + 1 + const currentRow = acc[acc.length - 1].items + currentRow.push(attachment) + if ( + currentRow.length >= maxPerRow && + attachmentsRemaining > maxPerRow + ) { + return [...acc, { items: [] }] + } else { + return acc + } + }, + [{ items: [] }], + ) + .filter((_) => _.items.length > 0) return rows }, - attachmentsDimensionalScore () { + attachmentsDimensionalScore() { return this.rows.reduce((acc, row) => { let size = 0 if (row.minimal) { @@ -75,7 +95,7 @@ const Gallery = { return acc + size }, 0) }, - tooManyAttachments () { + tooManyAttachments() { if (this.editable || this.size === 'small') { return false } else if (this.size === 'hide') { @@ -83,38 +103,38 @@ const Gallery = { } else { return this.attachmentsDimensionalScore > 1 } - } + }, }, methods: { - onNaturalSizeLoad ({ id, width, height }) { + onNaturalSizeLoad({ id, width, height }) { set(this.sizes, id, { width, height }) }, - rowStyle (row) { + rowStyle(row) { if (row.audio) { return { 'padding-bottom': '25%' } // fixed reduced height for audio } else if (!row.minimal && !row.grid) { - return { 'padding-bottom': `${(100 / (row.items.length + 0.6))}%` } + return { 'padding-bottom': `${100 / (row.items.length + 0.6)}%` } } }, - itemStyle (id, row) { - const total = sumBy(row, item => this.getAspectRatio(item.id)) + itemStyle(id, row) { + const total = sumBy(row, (item) => this.getAspectRatio(item.id)) return { flex: `${this.getAspectRatio(id) / total} 1 0%` } }, - getAspectRatio (id) { + getAspectRatio(id) { const size = this.sizes[id] return size ? size.width / size.height : 1 }, - toggleHidingLong (event) { + toggleHidingLong(event) { this.hidingLong = event }, - openGallery () { + openGallery() { useMediaViewerStore().setMedia(this.attachments) useMediaViewerStore().setCurrentMedia(this.attachments[0]) }, - onMedia () { + onMedia() { useMediaViewerStore().setMedia(this.attachments) - } - } + }, + }, } export default Gallery diff --git a/src/components/global_notice_list/global_notice_list.js b/src/components/global_notice_list/global_notice_list.js index cf14d1a07..4a2726d7b 100644 --- a/src/components/global_notice_list/global_notice_list.js +++ b/src/components/global_notice_list/global_notice_list.js @@ -1,24 +1,21 @@ -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faTimes -} from '@fortawesome/free-solid-svg-icons' import { useInterfaceStore } from 'src/stores/interface' -library.add( - faTimes -) +import { library } from '@fortawesome/fontawesome-svg-core' +import { faTimes } from '@fortawesome/free-solid-svg-icons' + +library.add(faTimes) const GlobalNoticeList = { computed: { - notices () { + notices() { return useInterfaceStore().globalNotices - } + }, }, methods: { - closeNotice (notice) { + closeNotice(notice) { useInterfaceStore().removeGlobalNotice(notice) - } - } + }, + }, } export default GlobalNoticeList diff --git a/src/components/hashtag_link/hashtag_link.js b/src/components/hashtag_link/hashtag_link.js index a2433c2ab..09cfaf199 100644 --- a/src/components/hashtag_link/hashtag_link.js +++ b/src/components/hashtag_link/hashtag_link.js @@ -5,20 +5,20 @@ const HashtagLink = { props: { url: { required: true, - type: String + type: String, }, content: { required: true, - type: String + type: String, }, tag: { required: false, type: String, - default: '' - } + default: '', + }, }, methods: { - onClick () { + onClick() { const tag = this.tag || extractTagFromUrl(this.url) if (tag) { const link = this.generateTagLink(tag) @@ -27,10 +27,10 @@ const HashtagLink = { window.open(this.url, '_blank') } }, - generateTagLink (tag) { + generateTagLink(tag) { return `/tag/${tag}` - } - } + }, + }, } export default HashtagLink diff --git a/src/components/icon.style.js b/src/components/icon.style.js index 4d30f389b..b13485c1b 100644 --- a/src/components/icon.style.js +++ b/src/components/icon.style.js @@ -7,8 +7,8 @@ export default { component: 'Icon', directives: { textColor: '$blend(--stack 0.5 --parent--text)', - textAuto: 'no-auto' - } - } - ] + textAuto: 'no-auto', + }, + }, + ], } diff --git a/src/components/image_cropper/image_cropper.js b/src/components/image_cropper/image_cropper.js index afca328e8..c8529c8e6 100644 --- a/src/components/image_cropper/image_cropper.js +++ b/src/components/image_cropper/image_cropper.js @@ -1,29 +1,26 @@ import 'cropperjs' // This adds all of the cropperjs's components into DOM -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faCircleNotch -} from '@fortawesome/free-solid-svg-icons' -library.add( - faCircleNotch -) +import { library } from '@fortawesome/fontawesome-svg-core' +import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' + +library.add(faCircleNotch) const ImageCropper = { props: { // Mime-types to accept, i.e. which filetypes to accept (.gif, .png, etc.) mimes: { type: String, - default: 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon' + default: 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon', }, // Fixed aspect-ratio for selection box aspectRatio: { - type: Number - } + type: Number, + }, }, - data () { + data() { return { dataUrl: undefined, - filename: undefined + filename: undefined, } }, emits: [ @@ -31,12 +28,12 @@ const ImageCropper = { 'close', // cropper is closed ], methods: { - destroy () { + destroy() { this.$refs.input.value = '' this.dataUrl = undefined this.$emit('close') }, - submit (cropping = true) { + submit(cropping = true) { let cropperPromise if (cropping) { cropperPromise = this.$refs.cropperSelection.$toCanvas() @@ -44,14 +41,14 @@ const ImageCropper = { cropperPromise = Promise.resolve() } - cropperPromise.then(canvas => { + cropperPromise.then((canvas) => { this.$emit('submit', { canvas, file: this.file }) }) }, - pickImage () { + pickImage() { this.$refs.input.click() }, - readFile () { + readFile() { const fileInput = this.$refs.input if (fileInput.files != null && fileInput.files[0] != null) { this.file = fileInput.files[0] @@ -66,10 +63,10 @@ const ImageCropper = { }, inSelection(selection, maxSelection) { return ( - selection.x >= maxSelection.x - && selection.y >= maxSelection.y - && (selection.x + selection.width) <= (maxSelection.x + maxSelection.width) - && (selection.y + selection.height) <= (maxSelection.y + maxSelection.height) + selection.x >= maxSelection.x && + selection.y >= maxSelection.y && + selection.x + selection.width <= maxSelection.x + maxSelection.width && + selection.y + selection.height <= maxSelection.y + maxSelection.height ) }, onCropperSelectionChange(event) { @@ -84,11 +81,11 @@ const ImageCropper = { } if (!this.inSelection(selection, maxSelection)) { - event.preventDefault(); + event.preventDefault() } - } + }, }, - mounted () { + mounted() { // listen for input file changes const fileInput = this.$refs.input fileInput.addEventListener('change', this.readFile) @@ -96,7 +93,7 @@ const ImageCropper = { beforeUnmount: function () { const fileInput = this.$refs.input fileInput.removeEventListener('change', this.readFile) - } + }, } export default ImageCropper diff --git a/src/components/importer/importer.js b/src/components/importer/importer.js index da86a223c..0403cc944 100644 --- a/src/components/importer/importer.js +++ b/src/components/importer/importer.js @@ -1,49 +1,49 @@ import { library } from '@fortawesome/fontawesome-svg-core' -import { - faCircleNotch, - faTimes -} from '@fortawesome/free-solid-svg-icons' +import { faCircleNotch, faTimes } from '@fortawesome/free-solid-svg-icons' -library.add( - faCircleNotch, - faTimes -) +library.add(faCircleNotch, faTimes) const Importer = { props: { submitHandler: { type: Function, - required: true + required: true, }, submitButtonLabel: { type: String }, successMessage: { type: String }, - errorMessage: { type: String } + errorMessage: { type: String }, }, - data () { + data() { return { file: null, error: false, success: false, - submitting: false + submitting: false, } }, methods: { - change () { + change() { this.file = this.$refs.input.files[0] }, - submit () { + submit() { this.dismiss() this.submitting = true this.submitHandler(this.file) - .then(() => { this.success = true }) - .catch(() => { this.error = true }) - .finally(() => { this.submitting = false }) + .then(() => { + this.success = true + }) + .catch(() => { + this.error = true + }) + .finally(() => { + this.submitting = false + }) }, - dismiss () { + dismiss() { this.success = false this.error = false - } - } + }, + }, } export default Importer diff --git a/src/components/input.style.js b/src/components/input.style.js index 00f51cbf6..2663392fb 100644 --- a/src/components/input.style.js +++ b/src/components/input.style.js @@ -4,91 +4,96 @@ export default { states: { hover: ':is(:hover, :focus-visible):not(.disabled)', focused: ':focus-within', - disabled: '.disabled' + disabled: '.disabled', }, variants: { checkbox: '.-checkbox', - radio: '.-radio' + radio: '.-radio', }, - validInnerComponents: [ - 'Text', - 'Icon' - ], + validInnerComponents: ['Text', 'Icon'], defaultRules: [ { component: 'Root', directives: { - '--defaultInputBevel': 'shadow | $borderSide(#FFFFFF bottom 0.2), $borderSide(#000000 top 0.2), inset 0 0 2 #000000 / 0.15, 1 0 1 1 --text / 0.15, -1 0 1 1 --text / 0.15', + '--defaultInputBevel': + 'shadow | $borderSide(#FFFFFF bottom 0.2), $borderSide(#000000 top 0.2), inset 0 0 2 #000000 / 0.15, 1 0 1 1 --text / 0.15, -1 0 1 1 --text / 0.15', '--defaultInputHoverGlow': 'shadow | 0 0 4 --text / 0.5', - '--defaultInputFocusGlow': 'shadow | 0 0 4 4 --link / 0.5' - } + '--defaultInputFocusGlow': 'shadow | 0 0 4 4 --link / 0.5', + }, }, { variant: 'checkbox', directives: { - roundness: 1 - } + roundness: 1, + }, }, { directives: { '--font': 'generic | inherit', background: '--fg, -5', roundness: 3, - shadow: [{ - x: 0, - y: 0, - blur: 2, - spread: 0, - color: '#000000', - alpha: 1 - }, '--defaultInputBevel'] - } + shadow: [ + { + x: 0, + y: 0, + blur: 2, + spread: 0, + color: '#000000', + alpha: 1, + }, + '--defaultInputBevel', + ], + }, }, { state: ['hover'], directives: { - shadow: ['--defaultInputHoverGlow', '--defaultInputBevel'] - } + shadow: ['--defaultInputHoverGlow', '--defaultInputBevel'], + }, }, { state: ['focused'], directives: { - shadow: ['--defaultInputFocusGlow', '--defaultInputBevel'] - } + shadow: ['--defaultInputFocusGlow', '--defaultInputBevel'], + }, }, { state: ['focused', 'hover'], directives: { - shadow: ['--defaultInputFocusGlow', '--defaultInputHoverGlow', '--defaultInputBevel'] - } + shadow: [ + '--defaultInputFocusGlow', + '--defaultInputHoverGlow', + '--defaultInputBevel', + ], + }, }, { state: ['disabled'], directives: { - background: '--parent' - } + background: '--parent', + }, }, { component: 'Text', parent: { component: 'Input', - state: ['disabled'] + state: ['disabled'], }, directives: { textOpacity: 0.25, - textOpacityMode: 'blend' - } + textOpacityMode: 'blend', + }, }, { component: 'Icon', parent: { component: 'Input', - state: ['disabled'] + state: ['disabled'], }, directives: { textOpacity: 0.25, - textOpacityMode: 'blend' - } - } - ] + textOpacityMode: 'blend', + }, + }, + ], } diff --git a/src/components/instance_specific_panel/instance_specific_panel.js b/src/components/instance_specific_panel/instance_specific_panel.js index 09e3d0557..eead52f40 100644 --- a/src/components/instance_specific_panel/instance_specific_panel.js +++ b/src/components/instance_specific_panel/instance_specific_panel.js @@ -1,9 +1,9 @@ const InstanceSpecificPanel = { computed: { - instanceSpecificPanelContent () { + instanceSpecificPanelContent() { return this.$store.state.instance.instanceSpecificPanelContent - } - } + }, + }, } export default InstanceSpecificPanel diff --git a/src/components/interactions/interactions.js b/src/components/interactions/interactions.js index fc441b908..87e9e1b87 100644 --- a/src/components/interactions/interactions.js +++ b/src/components/interactions/interactions.js @@ -1,5 +1,5 @@ -import Notifications from '../notifications/notifications.vue' import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' +import Notifications from '../notifications/notifications.vue' const tabModeDict = { mentions: ['mention'], @@ -8,26 +8,29 @@ const tabModeDict = { follows: ['follow'], reactions: ['pleroma:emoji_reaction'], reports: ['pleroma:report'], - moves: ['move'] + moves: ['move'], } const Interactions = { - data () { + data() { return { - allowFollowingMove: this.$store.state.users.currentUser.allow_following_move, + allowFollowingMove: + this.$store.state.users.currentUser.allow_following_move, filterMode: tabModeDict.mentions, - canSeeReports: this.$store.state.users.currentUser.privileges.includes('reports_manage_reports') + canSeeReports: this.$store.state.users.currentUser.privileges.includes( + 'reports_manage_reports', + ), } }, methods: { - onModeSwitch (key) { + onModeSwitch(key) { this.filterMode = tabModeDict[key] - } + }, }, components: { Notifications, - TabSwitcher - } + TabSwitcher, + }, } export default Interactions diff --git a/src/components/interface_language_switcher/interface_language_switcher.js b/src/components/interface_language_switcher/interface_language_switcher.js index 721c19887..1cdedadd3 100644 --- a/src/components/interface_language_switcher/interface_language_switcher.js +++ b/src/components/interface_language_switcher/interface_language_switcher.js @@ -1,62 +1,63 @@ -import localeService from '../../services/locale/locale.service.js' +import { v4 as uuidv4 } from 'uuid' -import Select from '../select/select.vue' import ProfileSettingIndicator from 'src/components/settings_modal/helpers/profile_setting_indicator.vue' - -import { v4 as uuidv4 } from 'uuid'; +import localeService from '../../services/locale/locale.service.js' +import Select from '../select/select.vue' export default { components: { Select, - ProfileSettingIndicator + ProfileSettingIndicator, }, props: { // List of languages (or just one language) modelValue: { type: [Array, String], - required: true + required: true, }, // Is this setting stored in user profile (true) or elsewhere (false) // Doesn't affect storage, just shows an icon if true profile: { type: Boolean, - default: false - } + default: false, + }, }, emits: ['update:modelValue'], computed: { - languages () { + languages() { return localeService.languages }, - uniqueId () { - return uuidv4() + uniqueId() { + return uuidv4() }, controlledLanguage: { get: function () { - return Array.isArray(this.modelValue) ? this.modelValue : [this.modelValue] + return Array.isArray(this.modelValue) + ? this.modelValue + : [this.modelValue] }, set: function (val) { this.$emit('update:modelValue', val) - } - } + }, + }, }, methods: { - getLanguageName (code) { + getLanguageName(code) { return localeService.getLanguageName(code) }, - addLanguage () { + addLanguage() { this.controlledLanguage = [...this.controlledLanguage, ''] }, - setLanguageAt (index, val) { + setLanguageAt(index, val) { const lang = [...this.controlledLanguage] lang[index] = val this.controlledLanguage = lang }, - removeLanguageAt (index) { + removeLanguageAt(index) { const lang = [...this.controlledLanguage] lang.splice(index, 1) this.controlledLanguage = lang - } - } + }, + }, } diff --git a/src/components/link-preview/link-preview.js b/src/components/link-preview/link-preview.js index add7c5631..ac91f916d 100644 --- a/src/components/link-preview/link-preview.js +++ b/src/components/link-preview/link-preview.js @@ -2,37 +2,31 @@ import { mapGetters } from 'vuex' const LinkPreview = { name: 'LinkPreview', - props: [ - 'card', - 'size', - 'nsfw' - ], - data () { + props: ['card', 'size', 'nsfw'], + data() { return { - imageLoaded: false + imageLoaded: false, } }, computed: { - useImage () { + useImage() { // Currently BE shoudn't give cards if tagged NSFW, this is a bit paranoid // as it makes sure to hide the image if somehow NSFW tagged preview can // exist. return this.card.image && !this.censored && this.size !== 'hide' }, - censored () { + censored() { return this.nsfw && this.hideNsfwConfig }, - useDescription () { + useDescription() { return this.card.description && /\S/.test(this.card.description) }, - hideNsfwConfig () { + hideNsfwConfig() { return this.mergedConfig.hideNsfw }, - ...mapGetters([ - 'mergedConfig' - ]) + ...mapGetters(['mergedConfig']), }, - created () { + created() { if (this.useImage) { const newImg = new Image() newImg.onload = () => { @@ -40,7 +34,7 @@ const LinkPreview = { } newImg.src = this.card.image } - } + }, } export default LinkPreview diff --git a/src/components/link.style.js b/src/components/link.style.js index d13cef338..141a1f023 100644 --- a/src/components/link.style.js +++ b/src/components/link.style.js @@ -3,22 +3,22 @@ export default { selector: 'a', virtual: true, states: { - faint: '.faint' + faint: '.faint', }, defaultRules: [ { component: 'Link', directives: { - textColor: '--link' - } + textColor: '--link', + }, }, { component: 'Link', state: ['faint'], directives: { textOpacity: 0.5, - textOpacityMode: 'fake' - } - } - ] + textOpacityMode: 'fake', + }, + }, + ], } diff --git a/src/components/list/list.vue b/src/components/list/list.vue index 5d2c49b3c..f34a5f073 100644 --- a/src/components/list/list.vue +++ b/src/components/list/list.vue @@ -29,20 +29,20 @@ export default { props: { items: { type: Array, - default: () => [] + default: () => [], }, getKey: { type: Function, - default: item => item.id + default: (item) => item.id, }, getClass: { type: Function, - default: () => '' + default: () => '', }, nonInteractive: { type: Boolean, - default: false - } - } + default: false, + }, + }, } diff --git a/src/components/lists/lists.js b/src/components/lists/lists.js index 8dcb48b52..446178245 100644 --- a/src/components/lists/lists.js +++ b/src/components/lists/lists.js @@ -2,27 +2,27 @@ import { useListsStore } from 'src/stores/lists' import ListsCard from '../lists_card/lists_card.vue' const Lists = { - data () { + data() { return { - isNew: false + isNew: false, } }, components: { - ListsCard + ListsCard, }, computed: { - lists () { + lists() { return useListsStore().allLists - } + }, }, methods: { - cancelNewList () { + cancelNewList() { this.isNew = false }, - newList () { + newList() { this.isNew = true - } - } + }, + }, } export default Lists diff --git a/src/components/lists_card/lists_card.js b/src/components/lists_card/lists_card.js index b503caec4..81b811534 100644 --- a/src/components/lists_card/lists_card.js +++ b/src/components/lists_card/lists_card.js @@ -1,16 +1,10 @@ import { library } from '@fortawesome/fontawesome-svg-core' -import { - faEllipsisH -} from '@fortawesome/free-solid-svg-icons' +import { faEllipsisH } from '@fortawesome/free-solid-svg-icons' -library.add( - faEllipsisH -) +library.add(faEllipsisH) const ListsCard = { - props: [ - 'list' - ] + props: ['list'], } export default ListsCard diff --git a/src/components/lists_edit/lists_edit.js b/src/components/lists_edit/lists_edit.js index c62bf9769..64a2e942c 100644 --- a/src/components/lists_edit/lists_edit.js +++ b/src/components/lists_edit/lists_edit.js @@ -1,22 +1,18 @@ -import { mapState, mapGetters } from 'vuex' import { mapState as mapPiniaState } from 'pinia' -import BasicUserCard from '../basic_user_card/basic_user_card.vue' -import ListsUserSearch from '../lists_user_search/lists_user_search.vue' +import { mapGetters, mapState } from 'vuex' + import PanelLoading from 'src/components/panel_loading/panel_loading.vue' -import UserAvatar from '../user_avatar/user_avatar.vue' import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faSearch, - faChevronLeft -} from '@fortawesome/free-solid-svg-icons' import { useInterfaceStore } from 'src/stores/interface' import { useListsStore } from 'src/stores/lists' +import BasicUserCard from '../basic_user_card/basic_user_card.vue' +import ListsUserSearch from '../lists_user_search/lists_user_search.vue' +import UserAvatar from '../user_avatar/user_avatar.vue' -library.add( - faSearch, - faChevronLeft -) +import { library } from '@fortawesome/fontawesome-svg-core' +import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons' + +library.add(faSearch, faChevronLeft) const ListsNew = { components: { @@ -24,9 +20,9 @@ const ListsNew = { UserAvatar, ListsUserSearch, TabSwitcher, - PanelLoading + PanelLoading, }, - data () { + data() { return { title: '', titleDraft: '', @@ -35,46 +31,51 @@ const ListsNew = { searchUserIds: [], addedUserIds: new Set([]), // users we added from search, to undo searchLoading: false, - reallyDelete: false + reallyDelete: false, } }, - created () { + created() { if (!this.id) return - useListsStore().fetchList({ listId: this.id }) + useListsStore() + .fetchList({ listId: this.id }) .then(() => { this.title = this.findListTitle(this.id) this.titleDraft = this.title }) - useListsStore().fetchListAccounts({ listId: this.id }) + useListsStore() + .fetchListAccounts({ listId: this.id }) .then(() => { this.membersUserIds = this.findListAccounts(this.id) - this.membersUserIds.forEach(userId => { + this.membersUserIds.forEach((userId) => { this.$store.dispatch('fetchUserIfMissing', userId) }) }) }, computed: { - id () { + id() { return this.$route.params.id }, - membersUsers () { + membersUsers() { return [...this.membersUserIds, ...this.addedUserIds] - .map(userId => this.findUser(userId)).filter(user => user) + .map((userId) => this.findUser(userId)) + .filter((user) => user) }, - searchUsers () { - return this.searchUserIds.map(userId => this.findUser(userId)).filter(user => user) + searchUsers() { + return this.searchUserIds + .map((userId) => this.findUser(userId)) + .filter((user) => user) }, ...mapState({ - currentUser: state => state.users.currentUser + currentUser: (state) => state.users.currentUser, }), ...mapPiniaState(useListsStore, ['findListTitle', 'findListAccounts']), - ...mapGetters(['findUser']) + ...mapGetters(['findUser']), }, methods: { - onInput () { + onInput() { this.search(this.query) }, - toggleRemoveMember (user) { + toggleRemoveMember(user) { if (this.removedUserIds.has(user.id)) { this.id && this.addUser(user) this.removedUserIds.delete(user.id) @@ -83,7 +84,7 @@ const ListsNew = { this.removedUserIds.add(user.id) } }, - toggleAddFromSearch (user) { + toggleAddFromSearch(user) { if (this.addedUserIds.has(user.id)) { this.id && this.removeUser(user.id) this.addedUserIds.delete(user.id) @@ -92,37 +93,41 @@ const ListsNew = { this.addedUserIds.add(user.id) } }, - isRemoved (user) { + isRemoved(user) { return this.removedUserIds.has(user.id) }, - isAdded (user) { + isAdded(user) { return this.addedUserIds.has(user.id) }, - addUser (user) { + addUser(user) { useListsStore().addListAccount({ accountId: user.id, listId: this.id }) }, - removeUser (userId) { + removeUser(userId) { useListsStore().removeListAccount({ accountId: userId, listId: this.id }) }, - onSearchLoading () { + onSearchLoading() { this.searchLoading = true }, - onSearchLoadingDone () { + onSearchLoadingDone() { this.searchLoading = false }, - onSearchResults (results) { + onSearchResults(results) { this.searchLoading = false this.searchUserIds = results }, - updateListTitle () { + updateListTitle() { useListsStore().setList({ listId: this.id, title: this.titleDraft }) this.title = this.findListTitle(this.id) }, - createList () { - useListsStore().createList({ title: this.titleDraft }) + createList() { + useListsStore() + .createList({ title: this.titleDraft }) .then((list) => { return useListsStore() - .setListAccounts({ listId: list.id, accountIds: [...this.addedUserIds] }) + .setListAccounts({ + listId: list.id, + accountIds: [...this.addedUserIds], + }) .then(() => list.id) }) .then((listId) => { @@ -132,15 +137,15 @@ const ListsNew = { useInterfaceStore().pushGlobalNotice({ messageKey: 'lists.error', messageArgs: [e.message], - level: 'error' + level: 'error', }) }) }, - deleteList () { + deleteList() { useListsStore().deleteList({ listId: this.id }) this.$router.push({ name: 'lists' }) - } - } + }, + }, } export default ListsNew diff --git a/src/components/lists_menu/lists_menu_content.js b/src/components/lists_menu/lists_menu_content.js index 726fd5fc4..7574c4c3f 100644 --- a/src/components/lists_menu/lists_menu_content.js +++ b/src/components/lists_menu/lists_menu_content.js @@ -1,26 +1,25 @@ -import { mapState } from 'vuex' import { mapState as mapPiniaState } from 'pinia' -import NavigationEntry from 'src/components/navigation/navigation_entry.vue' +import { mapState } from 'vuex' + import { getListEntries } from 'src/components/navigation/filter.js' +import NavigationEntry from 'src/components/navigation/navigation_entry.vue' import { useListsStore } from 'src/stores/lists' export const ListsMenuContent = { - props: [ - 'showPin' - ], + props: ['showPin'], components: { - NavigationEntry + NavigationEntry, }, computed: { ...mapPiniaState(useListsStore, { - lists: getListEntries + lists: getListEntries, }), ...mapState({ - currentUser: state => state.users.currentUser, - privateMode: state => state.instance.private, - federating: state => state.instance.federating - }) - } + currentUser: (state) => state.users.currentUser, + privateMode: (state) => state.instance.private, + federating: (state) => state.instance.federating, + }), + }, } export default ListsMenuContent diff --git a/src/components/lists_timeline/lists_timeline.js b/src/components/lists_timeline/lists_timeline.js index eae82a867..ae1554eb6 100644 --- a/src/components/lists_timeline/lists_timeline.js +++ b/src/components/lists_timeline/lists_timeline.js @@ -1,16 +1,19 @@ import { useListsStore } from 'src/stores/lists' import Timeline from '../timeline/timeline.vue' + const ListsTimeline = { - data () { + data() { return { - listId: null + listId: null, } }, components: { - Timeline + Timeline, }, computed: { - timeline () { return this.$store.state.statuses.timelines.list } + timeline() { + return this.$store.state.statuses.timelines.list + }, }, watch: { $route: function (route) { @@ -19,19 +22,25 @@ const ListsTimeline = { this.$store.dispatch('stopFetchingTimeline', 'list') this.$store.commit('clearTimeline', { timeline: 'list' }) useListsStore().fetchList({ listId: this.listId }) - this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId }) + this.$store.dispatch('startFetchingTimeline', { + timeline: 'list', + listId: this.listId, + }) } - } + }, }, - created () { + created() { this.listId = this.$route.params.id useListsStore().fetchList({ listId: this.listId }) - this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId }) + this.$store.dispatch('startFetchingTimeline', { + timeline: 'list', + listId: this.listId, + }) }, - unmounted () { + unmounted() { this.$store.dispatch('stopFetchingTimeline', 'list') this.$store.commit('clearTimeline', { timeline: 'list' }) - } + }, } export default ListsTimeline diff --git a/src/components/lists_user_search/lists_user_search.js b/src/components/lists_user_search/lists_user_search.js index c92ec0eee..699e6c156 100644 --- a/src/components/lists_user_search/lists_user_search.js +++ b/src/components/lists_user_search/lists_user_search.js @@ -1,33 +1,29 @@ -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faSearch, - faChevronLeft -} from '@fortawesome/free-solid-svg-icons' import { debounce } from 'lodash' + import Checkbox from '../checkbox/checkbox.vue' -library.add( - faSearch, - faChevronLeft -) +import { library } from '@fortawesome/fontawesome-svg-core' +import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons' + +library.add(faSearch, faChevronLeft) const ListsUserSearch = { components: { - Checkbox + Checkbox, }, emits: ['loading', 'loadingDone', 'results'], - data () { + data() { return { loading: false, query: '', - followingOnly: true + followingOnly: true, } }, methods: { onInput: debounce(function () { this.search(this.query) }, 2000), - search (query) { + search(query) { if (!query) { this.loading = false return @@ -36,16 +32,25 @@ const ListsUserSearch = { this.loading = true this.$emit('loading') this.userIds = [] - this.$store.dispatch('search', { q: query, resolve: true, type: 'accounts', following: this.followingOnly }) - .then(data => { - this.$emit('results', data.accounts.map(a => a.id)) + this.$store + .dispatch('search', { + q: query, + resolve: true, + type: 'accounts', + following: this.followingOnly, + }) + .then((data) => { + this.$emit( + 'results', + data.accounts.map((a) => a.id), + ) }) .finally(() => { this.loading = false this.$emit('loadingDone') }) - } - } + }, + }, } export default ListsUserSearch diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index 9566aa903..7c43923c9 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -1,56 +1,61 @@ +import { mapActions, mapState as mapPiniaState, mapStores } from 'pinia' import { mapState } from 'vuex' -import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia' -import oauthApi from '../../services/new_api/oauth.js' -import { useOAuthStore } from 'src/stores/oauth.js' -import { useAuthFlowStore } from 'src/stores/auth_flow.js' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faTimes -} from '@fortawesome/free-solid-svg-icons' -library.add( - faTimes -) +import { useAuthFlowStore } from 'src/stores/auth_flow.js' +import { useOAuthStore } from 'src/stores/oauth.js' +import oauthApi from '../../services/new_api/oauth.js' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { faTimes } from '@fortawesome/free-solid-svg-icons' + +library.add(faTimes) const LoginForm = { data: () => ({ user: {}, - error: false + error: false, }), computed: { - isPasswordAuth () { return this.requiredPassword }, - isTokenAuth () { return this.requiredToken }, + isPasswordAuth() { + return this.requiredPassword + }, + isTokenAuth() { + return this.requiredToken + }, ...mapStores(useOAuthStore), ...mapState({ - registrationOpen: state => state.instance.registrationOpen, - instance: state => state.instance, - loggingIn: state => state.users.loggingIn, + registrationOpen: (state) => state.instance.registrationOpen, + instance: (state) => state.instance, + loggingIn: (state) => state.users.loggingIn, }), - ...mapPiniaState(useAuthFlowStore, ['requiredPassword', 'requiredToken', 'requiredMFA']) + ...mapPiniaState(useAuthFlowStore, [ + 'requiredPassword', + 'requiredToken', + 'requiredMFA', + ]), }, methods: { ...mapActions(useAuthFlowStore, ['requireMFA', 'login']), - submit () { + submit() { this.isTokenAuth ? this.submitToken() : this.submitPassword() }, - submitToken () { + submitToken() { const data = { instance: this.instance.server, - commit: this.$store.commit + commit: this.$store.commit, } // NOTE: we do not really need the app token, but obtaining a token and // calling verify_credentials is the only way to ensure the app still works. - this.oauthStore.ensureAppToken() - .then(() => { - const app = { - clientId: this.oauthStore.clientId, - clientSecret: this.oauthStore.clientSecret, - } - oauthApi.login({ ...app, ...data }) - }) + this.oauthStore.ensureAppToken().then(() => { + const app = { + clientId: this.oauthStore.clientId, + clientSecret: this.oauthStore.clientSecret, + } + oauthApi.login({ ...app, ...data }) + }) }, - submitPassword () { + submitPassword() { this.error = false // NOTE: we do not really need the app token, but obtaining a token and @@ -61,38 +66,43 @@ const LoginForm = { clientSecret: this.oauthStore.clientSecret, } - oauthApi.getTokenWithCredentials( - { + oauthApi + .getTokenWithCredentials({ ...app, instance: this.instance.server, username: this.user.username, - password: this.user.password - } - ).then((result) => { - if (result.error) { - if (result.error === 'mfa_required') { - this.requireMFA({ settings: result }) - } else if (result.identifier === 'password_reset_required') { - this.$router.push({ name: 'password-reset', params: { passwordResetRequested: true } }) - } else { - this.error = result.error - this.focusOnPasswordInput() - } - return - } - this.login(result).then(() => { - this.$router.push({ name: 'friends' }) + password: this.user.password, + }) + .then((result) => { + if (result.error) { + if (result.error === 'mfa_required') { + this.requireMFA({ settings: result }) + } else if (result.identifier === 'password_reset_required') { + this.$router.push({ + name: 'password-reset', + params: { passwordResetRequested: true }, + }) + } else { + this.error = result.error + this.focusOnPasswordInput() + } + return + } + this.login(result).then(() => { + this.$router.push({ name: 'friends' }) + }) }) - }) }) }, - clearError () { this.error = false }, - focusOnPasswordInput () { + clearError() { + this.error = false + }, + focusOnPasswordInput() { const passwordInput = this.$refs.passwordInput passwordInput.focus() passwordInput.setSelectionRange(0, passwordInput.value.length) - } - } + }, + }, } export default LoginForm diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 9a57f8250..6199a6e32 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -1,26 +1,22 @@ -import StillImage from '../still-image/still-image.vue' -import VideoAttachment from '../video_attachment/video_attachment.vue' +import Flash from 'src/components/flash/flash.vue' +import { useMediaViewerStore } from 'src/stores/media_viewer' +import fileTypeService from '../../services/file_type/file_type.service.js' +import GestureService from '../../services/gesture_service/gesture_service' import Modal from '../modal/modal.vue' import PinchZoom from '../pinch_zoom/pinch_zoom.vue' +import StillImage from '../still-image/still-image.vue' import SwipeClick from '../swipe_click/swipe_click.vue' -import GestureService from '../../services/gesture_service/gesture_service' -import Flash from 'src/components/flash/flash.vue' -import fileTypeService from '../../services/file_type/file_type.service.js' +import VideoAttachment from '../video_attachment/video_attachment.vue' + import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronLeft, faChevronRight, faCircleNotch, - faTimes + faTimes, } from '@fortawesome/free-solid-svg-icons' -import { useMediaViewerStore } from 'src/stores/media_viewer' -library.add( - faChevronLeft, - faChevronRight, - faCircleNotch, - faTimes -) +library.add(faChevronLeft, faChevronRight, faCircleNotch, faTimes) const MediaModal = { components: { @@ -29,9 +25,9 @@ const MediaModal = { PinchZoom, SwipeClick, Modal, - Flash + Flash, }, - data () { + data() { return { loading: false, swipeDirection: GestureService.DIRECTION_LEFT, @@ -40,42 +36,42 @@ const MediaModal = { return window.innerWidth * considerableMoveRatio }, pinchZoomMinScale: 1, - pinchZoomScaleResetLimit: 1.2 + pinchZoomScaleResetLimit: 1.2, } }, computed: { - showing () { + showing() { return useMediaViewerStore().activated }, - media () { + media() { return useMediaViewerStore().media }, - description () { + description() { return this.currentMedia.description }, - currentIndex () { + currentIndex() { return useMediaViewerStore().currentIndex }, - currentMedia () { + currentMedia() { return this.media[this.currentIndex] }, - canNavigate () { + canNavigate() { return this.media.length > 1 }, - type () { + type() { return this.currentMedia ? this.getType(this.currentMedia) : null }, - swipeDisableClickThreshold () { + swipeDisableClickThreshold() { // If there is only one media, allow more mouse movements to close the modal // because there is less chance that the user wants to switch to another image - return () => this.canNavigate ? 1 : 30 - } + return () => (this.canNavigate ? 1 : 30) + }, }, methods: { - getType (media) { + getType(media) { return fileTypeService.fileType(media.mimetype) }, - hide () { + hide() { // HACK: Closing immediately via a touch will cause the click // to be processed on the content below the overlay const transitionTime = 100 // ms @@ -83,7 +79,7 @@ const MediaModal = { useMediaViewerStore().closeMediaViewer() }, transitionTime) }, - hideIfNotSwiped (event) { + hideIfNotSwiped(event) { // If we have swiped over SwipeClick, do not trigger hide const comp = this.$refs.swipeClick if (!comp) { @@ -92,9 +88,12 @@ const MediaModal = { comp.$gesture.click(event) } }, - goPrev () { + goPrev() { if (this.canNavigate) { - const prevIndex = this.currentIndex === 0 ? this.media.length - 1 : (this.currentIndex - 1) + const prevIndex = + this.currentIndex === 0 + ? this.media.length - 1 + : this.currentIndex - 1 const newMedia = this.media[prevIndex] if (this.getType(newMedia) === 'image') { this.loading = true @@ -102,9 +101,12 @@ const MediaModal = { useMediaViewerStore().setCurrentMedia(newMedia) } }, - goNext () { + goNext() { if (this.canNavigate) { - const nextIndex = this.currentIndex === this.media.length - 1 ? 0 : (this.currentIndex + 1) + const nextIndex = + this.currentIndex === this.media.length - 1 + ? 0 + : this.currentIndex + 1 const newMedia = this.media[nextIndex] if (this.getType(newMedia) === 'image') { this.loading = true @@ -112,13 +114,13 @@ const MediaModal = { useMediaViewerStore().setCurrentMedia(newMedia) } }, - onImageLoaded () { + onImageLoaded() { this.loading = false }, - handleSwipePreview (offsets) { + handleSwipePreview(offsets) { this.$refs.pinchZoom.setTransform({ scale: 1, x: offsets[0], y: 0 }) }, - handleSwipeEnd (sign) { + handleSwipeEnd(sign) { this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 }) if (sign > 0) { this.goNext() @@ -126,33 +128,36 @@ const MediaModal = { this.goPrev() } }, - handleKeyupEvent (e) { - if (this.showing && e.keyCode === 27) { // escape + handleKeyupEvent(e) { + if (this.showing && e.keyCode === 27) { + // escape this.hide() } }, - handleKeydownEvent (e) { + handleKeydownEvent(e) { if (!this.showing) { return } - if (e.keyCode === 39) { // arrow right + if (e.keyCode === 39) { + // arrow right this.goNext() - } else if (e.keyCode === 37) { // arrow left + } else if (e.keyCode === 37) { + // arrow left this.goPrev() } - } + }, }, - mounted () { + mounted() { window.addEventListener('popstate', this.hide) document.addEventListener('keyup', this.handleKeyupEvent) document.addEventListener('keydown', this.handleKeydownEvent) }, - unmounted () { + unmounted() { window.removeEventListener('popstate', this.hide) document.removeEventListener('keyup', this.handleKeyupEvent) document.removeEventListener('keydown', this.handleKeydownEvent) - } + }, } export default MediaModal diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js index f2cbfc405..8dc3d6c65 100644 --- a/src/components/media_upload/media_upload.js +++ b/src/components/media_upload/media_upload.js @@ -1,34 +1,32 @@ /* eslint-env browser */ -import statusPosterService from '../../services/status_poster/status_poster.service.js' + import fileSizeFormatService from '../../services/file_size_format/file_size_format.js' +import statusPosterService from '../../services/status_poster/status_poster.service.js' import { library } from '@fortawesome/fontawesome-svg-core' -import { faUpload, faCircleNotch } from '@fortawesome/free-solid-svg-icons' +import { faCircleNotch, faUpload } from '@fortawesome/free-solid-svg-icons' -library.add( - faUpload, - faCircleNotch -) +library.add(faUpload, faCircleNotch) const mediaUpload = { - data () { + data() { return { uploadCount: 0, - uploadReady: true + uploadReady: true, } }, computed: { - uploading () { + uploading() { return this.uploadCount > 0 - } + }, }, methods: { - onClick () { + onClick() { if (this.uploadReady) { this.$refs.input.click() } }, - async resizeImage (file) { + async resizeImage(file) { // Skip if not an image or if it's a GIF if (!file.type.startsWith('image/') || file.type === 'image/gif') { return file @@ -74,46 +72,67 @@ const mediaUpload = { // Check WebP support by trying to create a WebP canvas const testCanvas = document.createElement('canvas') - const supportsWebP = testCanvas.toDataURL('image/webp').startsWith('data:image/webp') + const supportsWebP = testCanvas + .toDataURL('image/webp') + .startsWith('data:image/webp') // Convert to WebP if supported and alwaysUseJpeg is false, otherwise JPEG - const type = (!this.$store.getters.mergedConfig.alwaysUseJpeg && supportsWebP) ? 'image/webp' : 'image/jpeg' + const type = + !this.$store.getters.mergedConfig.alwaysUseJpeg && supportsWebP + ? 'image/webp' + : 'image/jpeg' const extension = type === 'image/webp' ? '.webp' : '.jpg' // Remove the original extension and add new one const newFileName = file.name.replace(/\.[^/.]+$/, '') + extension - canvas.toBlob((blob) => { - resolve(new File([blob], newFileName, { - type, - lastModified: Date.now() - })) - }, type, 0.85) + canvas.toBlob( + (blob) => { + resolve( + new File([blob], newFileName, { + type, + lastModified: Date.now(), + }), + ) + }, + type, + 0.85, + ) } img.src = URL.createObjectURL(file) }) }, - async isAnimatedPng (file) { + async isAnimatedPng(file) { const buffer = await file.arrayBuffer() const view = new Uint8Array(buffer) // Look for animated PNG chunks (acTL) for (let i = 0; i < view.length - 8; i++) { - if (view[i] === 0x61 && // a - view[i + 1] === 0x63 && // c - view[i + 2] === 0x54 && // T - view[i + 3] === 0x4C) { // L + if ( + view[i] === 0x61 && // a + view[i + 1] === 0x63 && // c + view[i + 2] === 0x54 && // T + view[i + 3] === 0x4c + ) { + // L return true } } return false }, - async uploadFile (file) { + async uploadFile(file) { const self = this const store = this.$store if (file.size > store.state.instance.uploadlimit) { const filesize = fileSizeFormatService.fileSizeFormat(file.size) - const allowedsize = fileSizeFormatService.fileSizeFormat(store.state.instance.uploadlimit) - self.$emit('upload-failed', 'file_too_big', { filesize: filesize.num, filesizeunit: filesize.unit, allowedsize: allowedsize.num, allowedsizeunit: allowedsize.unit }) + const allowedsize = fileSizeFormatService.fileSizeFormat( + store.state.instance.uploadlimit, + ) + self.$emit('upload-failed', 'file_too_big', { + filesize: filesize.num, + filesizeunit: filesize.unit, + allowedsize: allowedsize.num, + allowedsizeunit: allowedsize.unit, + }) return } @@ -125,36 +144,38 @@ const mediaUpload = { self.$emit('uploading') self.uploadCount++ - statusPosterService.uploadMedia({ store, formData }) - .then((fileData) => { + statusPosterService.uploadMedia({ store, formData }).then( + (fileData) => { self.$emit('uploaded', fileData) self.decreaseUploadCount() - }, (error) => { + }, + (error) => { console.error('Error uploading file', error) self.$emit('upload-failed', 'default') self.decreaseUploadCount() - }) + }, + ) }, - decreaseUploadCount () { + decreaseUploadCount() { this.uploadCount-- if (this.uploadCount === 0) { this.$emit('all-uploaded') } }, - clearFile () { + clearFile() { this.uploadReady = false this.$nextTick(() => { this.uploadReady = true }) }, - multiUpload (files) { + multiUpload(files) { for (const file of files) { this.uploadFile(file) } }, - change ({ target }) { + change({ target }) { this.multiUpload(target.files) - } + }, }, props: { dropFiles: Object, @@ -162,16 +183,16 @@ const mediaUpload = { normalButton: Boolean, acceptTypes: { type: String, - default: '*/*' - } + default: '*/*', + }, }, watch: { dropFiles: function (fileInfos) { if (!this.uploading) { this.multiUpload(fileInfos) } - } - } + }, + }, } export default mediaUpload diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js index ca5c24618..4c211b5e9 100644 --- a/src/components/mention_link/mention_link.js +++ b/src/components/mention_link/mention_link.js @@ -1,155 +1,165 @@ -import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' -import { mapGetters, mapState } from 'vuex' -import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' -import UserAvatar from '../user_avatar/user_avatar.vue' -import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue' import { defineAsyncComponent } from 'vue' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faAt -} from '@fortawesome/free-solid-svg-icons' +import { mapGetters, mapState } from 'vuex' -library.add( - faAt -) +import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' +import { + highlightClass, + highlightStyle, +} from '../../services/user_highlighter/user_highlighter.js' +import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue' +import UserAvatar from '../user_avatar/user_avatar.vue' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { faAt } from '@fortawesome/free-solid-svg-icons' + +library.add(faAt) const MentionLink = { name: 'MentionLink', components: { UserAvatar, UnicodeDomainIndicator, - UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue')) + UserPopover: defineAsyncComponent( + () => import('../user_popover/user_popover.vue'), + ), }, props: { url: { required: true, - type: String + type: String, }, content: { required: true, - type: String + type: String, }, userId: { required: false, - type: String + type: String, }, userScreenName: { required: false, - type: String - } + type: String, + }, }, - data () { + data() { return { - hasSelection: false + hasSelection: false, } }, methods: { - onClick () { + onClick() { if (this.shouldShowTooltip) return const link = generateProfileLink( this.userId || this.user.id, - this.userScreenName || this.user.screen_name + this.userScreenName || this.user.screen_name, ) this.$router.push(link) }, - handleSelection () { + handleSelection() { if (this.$refs.full) { - this.hasSelection = document.getSelection().containsNode(this.$refs.full, true) + this.hasSelection = document + .getSelection() + .containsNode(this.$refs.full, true) } - } + }, }, - mounted () { + mounted() { document.addEventListener('selectionchange', this.handleSelection) }, - unmounted () { + unmounted() { document.removeEventListener('selectionchange', this.handleSelection) }, computed: { - user () { - return this.url && this.$store && this.$store.getters.findUserByUrl(this.url) + user() { + return ( + this.url && this.$store && this.$store.getters.findUserByUrl(this.url) + ) }, - isYou () { + isYou() { // FIXME why user !== currentUser??? return this.user && this.user.id === this.currentUser.id }, - userName () { + userName() { return this.user && this.userNameFullUi.split('@')[0] }, - serverName () { + serverName() { // XXX assumed that domain does not contain @ - return this.user && (this.userNameFullUi.split('@')[1] || this.$store.getters.instanceDomain) + return ( + this.user && + (this.userNameFullUi.split('@')[1] || + this.$store.getters.instanceDomain) + ) }, - userNameFull () { + userNameFull() { return this.user && this.user.screen_name }, - userNameFullUi () { + userNameFullUi() { return this.user && this.user.screen_name_ui }, - highlight () { + highlight() { return this.user && this.mergedConfig.highlight[this.user.screen_name] }, - highlightType () { - return this.highlight && ('-' + this.highlight.type) + highlightType() { + return this.highlight && '-' + this.highlight.type }, - highlightClass () { + highlightClass() { if (this.highlight) return highlightClass(this.user) }, - style () { + style() { if (this.highlight) { - /* eslint-disable no-unused-vars */ const { backgroundColor, backgroundPosition, backgroundImage, ...rest } = highlightStyle(this.highlight) - /* eslint-enable no-unused-vars */ return rest } }, - classnames () { + classnames() { return [ { '-you': this.isYou && this.shouldBoldenYou, '-highlighted': this.highlight, - '-has-selection': this.hasSelection + '-has-selection': this.hasSelection, }, - this.highlightType + this.highlightType, ] }, - isRemote () { + isRemote() { return this.userName !== this.userNameFull }, - shouldShowFullUserName () { + shouldShowFullUserName() { const conf = this.mergedConfig.mentionLinkDisplay if (conf === 'short') { return false } else if (conf === 'full') { return true - } else { // full_for_remote + } else { + // full_for_remote return this.isRemote } }, - shouldShowTooltip () { + shouldShowTooltip() { return this.mergedConfig.mentionLinkShowTooltip }, - shouldShowAvatar () { + shouldShowAvatar() { return this.mergedConfig.mentionLinkShowAvatar }, - shouldShowYous () { + shouldShowYous() { return this.mergedConfig.mentionLinkShowYous }, - shouldBoldenYou () { + shouldBoldenYou() { return this.mergedConfig.mentionLinkBoldenYou }, - shouldFadeDomain () { + shouldFadeDomain() { return this.mergedConfig.mentionLinkFadeDomain }, ...mapGetters(['mergedConfig']), ...mapState({ - currentUser: state => state.users.currentUser - }) - } + currentUser: (state) => state.users.currentUser, + }), + }, } export default MentionLink diff --git a/src/components/mentions/mentions.js b/src/components/mentions/mentions.js index 841d5aa48..10167ac77 100644 --- a/src/components/mentions/mentions.js +++ b/src/components/mentions/mentions.js @@ -2,13 +2,13 @@ import Timeline from '../timeline/timeline.vue' const Mentions = { computed: { - timeline () { + timeline() { return this.$store.state.statuses.timelines.mentions - } + }, }, components: { - Timeline - } + Timeline, + }, } export default Mentions diff --git a/src/components/mentions_line/mentions_line.js b/src/components/mentions_line/mentions_line.js index a4a0c7246..e6aa392a0 100644 --- a/src/components/mentions_line/mentions_line.js +++ b/src/components/mentions_line/mentions_line.js @@ -1,6 +1,7 @@ -import MentionLink from 'src/components/mention_link/mention_link.vue' import { mapGetters } from 'vuex' +import MentionLink from 'src/components/mention_link/mention_link.vue' + export const MENTIONS_LIMIT = 5 const MentionsLine = { @@ -8,30 +9,30 @@ const MentionsLine = { props: { mentions: { required: true, - type: Array - } + type: Array, + }, }, data: () => ({ expanded: false }), components: { - MentionLink + MentionLink, }, computed: { - mentionsComputed () { + mentionsComputed() { return this.mentions.slice(0, MENTIONS_LIMIT) }, - extraMentions () { + extraMentions() { return this.mentions.slice(MENTIONS_LIMIT) }, - manyMentions () { + manyMentions() { return this.extraMentions.length > 0 }, - ...mapGetters(['mergedConfig']) + ...mapGetters(['mergedConfig']), }, methods: { - toggleShowMore () { + toggleShowMore() { this.expanded = !this.expanded - } - } + }, + }, } export default MentionsLine diff --git a/src/components/menu_item.style.js b/src/components/menu_item.style.js index 859932062..a5567b49b 100644 --- a/src/components/menu_item.style.js +++ b/src/components/menu_item.style.js @@ -1,109 +1,105 @@ export default { name: 'MenuItem', selector: '.menu-item', - validInnerComponents: [ - 'Text', - 'Icon', - 'Border' - ], + validInnerComponents: ['Text', 'Icon', 'Border'], states: { hover: ':is(:hover, :focus-visible, :has(:focus-visible)):not(.disabled)', active: '.-active', - disabled: '.disabled' + disabled: '.disabled', }, defaultRules: [ { directives: { background: '--bg', - opacity: 0 - } + opacity: 0, + }, }, { state: ['hover'], directives: { background: '$mod(--bg 5)', - opacity: 1 - } + opacity: 1, + }, }, { state: ['active'], directives: { background: '$mod(--bg 10)', - opacity: 1 - } + opacity: 1, + }, }, { state: ['active', 'hover'], directives: { background: '$mod(--bg 15)', - opacity: 1 - } + opacity: 1, + }, }, { component: 'Text', parent: { component: 'MenuItem', - state: ['hover'] + state: ['hover'], }, directives: { textColor: '--link', - textAuto: 'no-preserve' - } + textAuto: 'no-preserve', + }, }, { component: 'Text', parent: { component: 'MenuItem', - state: ['active'] + state: ['active'], }, directives: { textColor: '--link', - textAuto: 'no-preserve' - } + textAuto: 'no-preserve', + }, }, { component: 'Icon', parent: { component: 'MenuItem', - state: ['active'] + state: ['active'], }, directives: { textColor: '--link', - textAuto: 'no-preserve' - } + textAuto: 'no-preserve', + }, }, { component: 'Icon', parent: { component: 'MenuItem', - state: ['hover'] + state: ['hover'], }, directives: { textColor: '--link', - textAuto: 'no-preserve' - } + textAuto: 'no-preserve', + }, }, { component: 'Text', parent: { component: 'MenuItem', - state: ['disabled'] + state: ['disabled'], }, directives: { textOpacity: 0.25, - textOpacityMode: 'blend' - } + textOpacityMode: 'blend', + }, }, { component: 'Icon', parent: { component: 'MenuItem', - state: ['disabled'] + state: ['disabled'], }, directives: { textOpacity: 0.25, - textOpacityMode: 'blend' - } - } - ] + textOpacityMode: 'blend', + }, + }, + ], } diff --git a/src/components/mfa_form/recovery_form.js b/src/components/mfa_form/recovery_form.js index 84479b1ec..5fa5c187a 100644 --- a/src/components/mfa_form/recovery_form.js +++ b/src/components/mfa_form/recovery_form.js @@ -1,42 +1,42 @@ -import mfaApi from '../../services/new_api/mfa.js' +import { mapActions, mapState as mapPiniaState, mapStores } from 'pinia' import { mapState } from 'vuex' -import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia' -import { useOAuthStore } from 'src/stores/oauth.js' -import { useAuthFlowStore } from 'src/stores/auth_flow.js' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faTimes -} from '@fortawesome/free-solid-svg-icons' -library.add( - faTimes -) +import { useAuthFlowStore } from 'src/stores/auth_flow.js' +import { useOAuthStore } from 'src/stores/oauth.js' +import mfaApi from '../../services/new_api/mfa.js' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { faTimes } from '@fortawesome/free-solid-svg-icons' + +library.add(faTimes) export default { data: () => ({ code: null, - error: false + error: false, }), computed: { ...mapPiniaState(useAuthFlowStore, { - authSettings: store => store.settings + authSettings: (store) => store.settings, }), ...mapStores(useOAuthStore), ...mapState({ instance: 'instance', - }) + }), }, methods: { ...mapActions(useAuthFlowStore, ['requireTOTP', 'abortMFA', 'login']), - clearError () { this.error = false }, + clearError() { + this.error = false + }, - focusOnCodeInput () { + focusOnCodeInput() { const codeInput = this.$refs.codeInput codeInput.focus() codeInput.setSelectionRange(0, codeInput.value.length) }, - submit () { + submit() { const { clientId, clientSecret } = this.oauthStore const data = { @@ -44,7 +44,7 @@ export default { clientSecret, instance: this.instance.server, mfaToken: this.authSettings.mfa_token, - code: this.code + code: this.code, } mfaApi.verifyRecoveryCode(data).then((result) => { @@ -59,6 +59,6 @@ export default { this.$router.push({ name: 'friends' }) }) }) - } - } + }, + }, } diff --git a/src/components/mfa_form/totp_form.js b/src/components/mfa_form/totp_form.js index e369d8a5d..d1378b078 100644 --- a/src/components/mfa_form/totp_form.js +++ b/src/components/mfa_form/totp_form.js @@ -1,42 +1,42 @@ -import mfaApi from '../../services/new_api/mfa.js' +import { mapActions, mapState as mapPiniaState, mapStores } from 'pinia' import { mapState } from 'vuex' -import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia' -import { useOAuthStore } from 'src/stores/oauth.js' -import { useAuthFlowStore } from 'src/stores/auth_flow.js' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faTimes -} from '@fortawesome/free-solid-svg-icons' -library.add( - faTimes -) +import { useAuthFlowStore } from 'src/stores/auth_flow.js' +import { useOAuthStore } from 'src/stores/oauth.js' +import mfaApi from '../../services/new_api/mfa.js' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { faTimes } from '@fortawesome/free-solid-svg-icons' + +library.add(faTimes) export default { data: () => ({ code: null, - error: false + error: false, }), computed: { ...mapPiniaState(useAuthFlowStore, { - authSettings: store => store.settings + authSettings: (store) => store.settings, }), ...mapStores(useOAuthStore), ...mapState({ instance: 'instance', - }) + }), }, methods: { ...mapActions(useAuthFlowStore, ['requireRecovery', 'abortMFA', 'login']), - clearError () { this.error = false }, + clearError() { + this.error = false + }, - focusOnCodeInput () { + focusOnCodeInput() { const codeInput = this.$refs.codeInput codeInput.focus() codeInput.setSelectionRange(0, codeInput.value.length) }, - submit () { + submit() { const { clientId, clientSecret } = this.oauthStore const data = { @@ -44,7 +44,7 @@ export default { clientSecret, instance: this.instance.server, mfaToken: this.authSettings.mfa_token, - code: this.code + code: this.code, } mfaApi.verifyOTPCode(data).then((result) => { @@ -59,6 +59,6 @@ export default { this.$router.push({ name: 'friends' }) }) }) - } - } + }, + }, } diff --git a/src/components/mobile_drawer.style.js b/src/components/mobile_drawer.style.js index 0f2cf4e59..306bd5c17 100644 --- a/src/components/mobile_drawer.style.js +++ b/src/components/mobile_drawer.style.js @@ -1,15 +1,13 @@ export default { name: 'MobileDrawer', selector: '.mobile-drawer', - validInnerComponents: [ - 'MenuItem' - ], + validInnerComponents: ['MenuItem'], defaultRules: [ { directives: { background: '--bg', - backgroundNoCssColor: 'yes' - } - } - ] + backgroundNoCssColor: 'yes', + }, + }, + ], } diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js index 2085d24e3..873bdc40d 100644 --- a/src/components/mobile_nav/mobile_nav.js +++ b/src/components/mobile_nav/mobile_nav.js @@ -1,99 +1,98 @@ -import SideDrawer from '../side_drawer/side_drawer.vue' -import Notifications from '../notifications/notifications.vue' -import ConfirmModal from '../confirm_modal/confirm_modal.vue' -import GestureService from '../../services/gesture_service/gesture_service' -import NavigationPins from 'src/components/navigation/navigation_pins.vue' - -import { - unseenNotificationsFromStore, - countExtraNotifications -} from '../../services/notification_utils/notification_utils' - -import { mapGetters } from 'vuex' import { mapState } from 'pinia' +import { mapGetters } from 'vuex' + +import NavigationPins from 'src/components/navigation/navigation_pins.vue' import { useAnnouncementsStore } from 'src/stores/announcements' import { useServerSideStorageStore } from 'src/stores/serverSideStorage' +import GestureService from '../../services/gesture_service/gesture_service' +import { + countExtraNotifications, + unseenNotificationsFromStore, +} from '../../services/notification_utils/notification_utils' +import ConfirmModal from '../confirm_modal/confirm_modal.vue' +import Notifications from '../notifications/notifications.vue' +import SideDrawer from '../side_drawer/side_drawer.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { - faTimes, - faBell, - faBars, faArrowUp, + faBars, + faBell, + faCheckDouble, faMinus, - faCheckDouble + faTimes, } from '@fortawesome/free-solid-svg-icons' -library.add( - faTimes, - faBell, - faBars, - faArrowUp, - faMinus, - faCheckDouble -) +library.add(faTimes, faBell, faBars, faArrowUp, faMinus, faCheckDouble) const MobileNav = { components: { SideDrawer, Notifications, NavigationPins, - ConfirmModal + ConfirmModal, }, data: () => ({ notificationsCloseGesture: undefined, notificationsOpen: false, notificationsAtTop: true, - showingConfirmLogout: false + showingConfirmLogout: false, }), - created () { + created() { this.notificationsCloseGesture = GestureService.swipeGesture( GestureService.DIRECTION_RIGHT, () => this.closeMobileNotifications(true), - 50 + 50, ) }, computed: { - currentUser () { + currentUser() { return this.$store.state.users.currentUser }, - unseenNotifications () { + unseenNotifications() { return unseenNotificationsFromStore(this.$store) }, - unseenNotificationsCount () { - return this.unseenNotifications.length + countExtraNotifications(this.$store) + unseenNotificationsCount() { + return ( + this.unseenNotifications.length + countExtraNotifications(this.$store) + ) }, - unseenCount () { + unseenCount() { return this.unseenNotifications.length }, - unseenCountBadgeText () { + unseenCountBadgeText() { return `${this.unseenCount ? this.unseenCount : ''}` }, - hideSitename () { return this.$store.state.instance.hideSitename }, - sitename () { return this.$store.state.instance.name }, - isChat () { + hideSitename() { + return this.$store.state.instance.hideSitename + }, + sitename() { + return this.$store.state.instance.name + }, + isChat() { return this.$route.name === 'chat' }, ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']), ...mapState(useServerSideStorageStore, { - pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems).has('chats') + pinnedItems: (store) => + new Set(store.prefsStorage.collections.pinnedNavItems).has('chats'), }), - shouldConfirmLogout () { + shouldConfirmLogout() { return this.$store.getters.mergedConfig.modalOnLogout }, - closingDrawerMarksAsSeen () { + closingDrawerMarksAsSeen() { return this.$store.getters.mergedConfig.closingDrawerMarksAsSeen }, - ...mapGetters(['unreadChatCount']) + ...mapGetters(['unreadChatCount']), }, methods: { - toggleMobileSidebar () { + toggleMobileSidebar() { this.$refs.sideDrawer.toggleDrawer() }, - openMobileNotifications () { + openMobileNotifications() { this.notificationsOpen = true }, - closeMobileNotifications (markRead) { + closeMobileNotifications(markRead) { if (this.notificationsOpen) { // make sure to mark notifs seen only when the notifs were open and not // from close-calls. @@ -103,53 +102,53 @@ const MobileNav = { } } }, - notificationsTouchStart (e) { + notificationsTouchStart(e) { GestureService.beginSwipe(e, this.notificationsCloseGesture) }, - notificationsTouchMove (e) { + notificationsTouchMove(e) { GestureService.updateSwipe(e, this.notificationsCloseGesture) }, - scrollToTop () { + scrollToTop() { window.scrollTo(0, 0) }, - scrollMobileNotificationsToTop () { + scrollMobileNotificationsToTop() { this.$refs.mobileNotifications.scrollTo(0, 0) }, - showConfirmLogout () { + showConfirmLogout() { this.showingConfirmLogout = true }, - hideConfirmLogout () { + hideConfirmLogout() { this.showingConfirmLogout = false }, - logout () { + logout() { if (!this.shouldConfirmLogout) { this.doLogout() } else { this.showConfirmLogout() } }, - doLogout () { + doLogout() { this.$router.replace('/main/public') this.$store.dispatch('logout') this.hideConfirmLogout() }, - markNotificationsAsSeen () { + markNotificationsAsSeen() { this.$store.dispatch('markNotificationsAsSeen') }, - onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) { + onScroll({ target: { scrollTop, clientHeight, scrollHeight } }) { this.notificationsAtTop = scrollTop > 0 if (scrollTop + clientHeight >= scrollHeight) { this.$refs.notifications.fetchOlderNotifications() } - } + }, }, watch: { - $route () { + $route() { // handles closing notificaitons when you press any router-link on the // notifications. this.closeMobileNotifications() - } - } + }, + }, } export default MobileNav diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.js b/src/components/mobile_post_status_button/mobile_post_status_button.js index 031013559..dfa7b6fe2 100644 --- a/src/components/mobile_post_status_button/mobile_post_status_button.js +++ b/src/components/mobile_post_status_button/mobile_post_status_button.js @@ -1,57 +1,55 @@ import { debounce } from 'lodash' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faPen -} from '@fortawesome/free-solid-svg-icons' + import { usePostStatusStore } from 'src/stores/post_status' -library.add( - faPen -) +import { library } from '@fortawesome/fontawesome-svg-core' +import { faPen } from '@fortawesome/free-solid-svg-icons' -const HIDDEN_FOR_PAGES = new Set([ - 'chats', - 'chat', - 'lists-edit' -]) +library.add(faPen) + +const HIDDEN_FOR_PAGES = new Set(['chats', 'chat', 'lists-edit']) const MobilePostStatusButton = { - data () { + data() { return { hidden: false, scrollingDown: false, inputActive: false, oldScrollPos: 0, - amountScrolled: 0 + amountScrolled: 0, } }, - created () { + created() { if (this.autohideFloatingPostButton) { this.activateFloatingPostButtonAutohide() } window.addEventListener('resize', this.handleOSK) }, - unmounted () { + unmounted() { if (this.autohideFloatingPostButton) { this.deactivateFloatingPostButtonAutohide() } window.removeEventListener('resize', this.handleOSK) }, computed: { - isLoggedIn () { + isLoggedIn() { return !!this.$store.state.users.currentUser }, - isHidden () { - if (HIDDEN_FOR_PAGES.has(this.$route.name)) { return true } + isHidden() { + if (HIDDEN_FOR_PAGES.has(this.$route.name)) { + return true + } - return this.autohideFloatingPostButton && (this.hidden || this.inputActive) + return ( + this.autohideFloatingPostButton && (this.hidden || this.inputActive) + ) }, - isPersistent () { + isPersistent() { return !!this.$store.getters.mergedConfig.alwaysShowNewPostButton }, - autohideFloatingPostButton () { + autohideFloatingPostButton() { return !!this.$store.getters.mergedConfig.autohideFloatingPostButton - } + }, }, watch: { autohideFloatingPostButton: function (isEnabled) { @@ -60,21 +58,21 @@ const MobilePostStatusButton = { } else { this.deactivateFloatingPostButtonAutohide() } - } + }, }, methods: { - activateFloatingPostButtonAutohide () { + activateFloatingPostButtonAutohide() { window.addEventListener('scroll', this.handleScrollStart) window.addEventListener('scroll', this.handleScrollEnd) }, - deactivateFloatingPostButtonAutohide () { + deactivateFloatingPostButtonAutohide() { window.removeEventListener('scroll', this.handleScrollStart) window.removeEventListener('scroll', this.handleScrollEnd) }, - openPostForm () { + openPostForm() { usePostStatusStore().openPostStatusModal() }, - handleOSK () { + handleOSK() { // This is a big hack: we're guessing from changed window sizes if the // on-screen keyboard is active or not. This is only really important // for phones in portrait mode and it's more important to show the button @@ -94,20 +92,28 @@ const MobilePostStatusButton = { this.inputActive = false } }, - handleScrollStart: debounce(function () { - if (window.scrollY > this.oldScrollPos) { - this.hidden = true - } else { - this.hidden = false - } - this.oldScrollPos = window.scrollY - }, 100, { leading: true, trailing: false }), + handleScrollStart: debounce( + function () { + if (window.scrollY > this.oldScrollPos) { + this.hidden = true + } else { + this.hidden = false + } + this.oldScrollPos = window.scrollY + }, + 100, + { leading: true, trailing: false }, + ), - handleScrollEnd: debounce(function () { - this.hidden = false - this.oldScrollPos = window.scrollY - }, 100, { leading: false, trailing: true }) - } + handleScrollEnd: debounce( + function () { + this.hidden = false + this.oldScrollPos = window.scrollY + }, + 100, + { leading: false, trailing: true }, + ), + }, } export default MobilePostStatusButton diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue index 032e7dbeb..a022f7427 100644 --- a/src/components/modal/modal.vue +++ b/src/components/modal/modal.vue @@ -13,27 +13,27 @@ diff --git a/src/components/modal/modals.style.js b/src/components/modal/modals.style.js index 1cb4a34a7..9c8c279f7 100644 --- a/src/components/modal/modals.style.js +++ b/src/components/modal/modals.style.js @@ -3,8 +3,6 @@ export default { selector: ['.modal-view', '#modal', '.shout-panel'], lazy: true, notEditable: true, - validInnerComponents: [ - 'Panel' - ], - defaultRules: [] + validInnerComponents: ['Panel'], + defaultRules: [], } diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js index bd57a353a..fdb8a74b6 100644 --- a/src/components/moderation_tools/moderation_tools.js +++ b/src/components/moderation_tools/moderation_tools.js @@ -1,9 +1,9 @@ -import { library } from '@fortawesome/fontawesome-svg-core' -import { faChevronDown } from '@fortawesome/free-solid-svg-icons' - import DialogModal from '../dialog_modal/dialog_modal.vue' import Popover from '../popover/popover.vue' +import { library } from '@fortawesome/fontawesome-svg-core' +import { faChevronDown } from '@fortawesome/free-solid-svg-icons' + library.add(faChevronDown) const FORCE_NSFW = 'mrf_tag:media-force-nsfw' @@ -15,10 +15,8 @@ const SANDBOX = 'mrf_tag:sandbox' const QUARANTINE = 'mrf_tag:quarantine' const ModerationTools = { - props: [ - 'user' - ], - data () { + props: ['user'], + data() { return { tags: { FORCE_NSFW, @@ -27,92 +25,124 @@ const ModerationTools = { DISABLE_REMOTE_SUBSCRIPTION, DISABLE_ANY_SUBSCRIPTION, SANDBOX, - QUARANTINE + QUARANTINE, }, showDeleteUserDialog: false, - toggled: false + toggled: false, } }, components: { DialogModal, - Popover + Popover, }, computed: { - tagsSet () { + tagsSet() { return new Set(this.user.tags) }, - canGrantRole () { - return this.user.is_local && !this.user.deactivated && this.$store.state.users.currentUser.role === 'admin' + canGrantRole() { + return ( + this.user.is_local && + !this.user.deactivated && + this.$store.state.users.currentUser.role === 'admin' + ) }, - canChangeActivationState () { + canChangeActivationState() { return this.privileged('users_manage_activation_state') }, - canDeleteAccount () { + canDeleteAccount() { return this.privileged('users_delete') }, - canUseTagPolicy () { - return this.$store.state.instance.tagPolicyAvailable && this.privileged('users_manage_tags') - } + canUseTagPolicy() { + return ( + this.$store.state.instance.tagPolicyAvailable && + this.privileged('users_manage_tags') + ) + }, }, methods: { - hasTag (tagName) { + hasTag(tagName) { return this.tagsSet.has(tagName) }, - privileged (privilege) { + privileged(privilege) { return this.$store.state.users.currentUser.privileges.includes(privilege) }, - toggleTag (tag) { + toggleTag(tag) { const store = this.$store if (this.tagsSet.has(tag)) { - store.state.api.backendInteractor.untagUser({ user: this.user, tag }).then(response => { - if (!response.ok) { return } - store.commit('untagUser', { user: this.user, tag }) - }) + store.state.api.backendInteractor + .untagUser({ user: this.user, tag }) + .then((response) => { + if (!response.ok) { + return + } + store.commit('untagUser', { user: this.user, tag }) + }) } else { - store.state.api.backendInteractor.tagUser({ user: this.user, tag }).then(response => { - if (!response.ok) { return } - store.commit('tagUser', { user: this.user, tag }) - }) + store.state.api.backendInteractor + .tagUser({ user: this.user, tag }) + .then((response) => { + if (!response.ok) { + return + } + store.commit('tagUser', { user: this.user, tag }) + }) } }, - toggleRight (right) { + toggleRight(right) { const store = this.$store if (this.user.rights[right]) { - store.state.api.backendInteractor.deleteRight({ user: this.user, right }).then(response => { - if (!response.ok) { return } - store.commit('updateRight', { user: this.user, right, value: false }) - }) + store.state.api.backendInteractor + .deleteRight({ user: this.user, right }) + .then((response) => { + if (!response.ok) { + return + } + store.commit('updateRight', { + user: this.user, + right, + value: false, + }) + }) } else { - store.state.api.backendInteractor.addRight({ user: this.user, right }).then(response => { - if (!response.ok) { return } - store.commit('updateRight', { user: this.user, right, value: true }) - }) + store.state.api.backendInteractor + .addRight({ user: this.user, right }) + .then((response) => { + if (!response.ok) { + return + } + store.commit('updateRight', { user: this.user, right, value: true }) + }) } }, - toggleActivationStatus () { + toggleActivationStatus() { this.$store.dispatch('toggleActivationStatus', { user: this.user }) }, - deleteUserDialog (show) { + deleteUserDialog(show) { this.showDeleteUserDialog = show }, - deleteUser () { + deleteUser() { const store = this.$store const user = this.user const { id, name } = user - store.state.api.backendInteractor.deleteUser({ user }) - .then(() => { - this.$store.dispatch('markStatusesAsDeleted', status => user.id === status.user.id) - const isProfile = this.$route.name === 'external-user-profile' || this.$route.name === 'user-profile' - const isTargetUser = this.$route.params.name === name || this.$route.params.id === id - if (isProfile && isTargetUser) { - window.history.back() - } - }) + store.state.api.backendInteractor.deleteUser({ user }).then(() => { + this.$store.dispatch( + 'markStatusesAsDeleted', + (status) => user.id === status.user.id, + ) + const isProfile = + this.$route.name === 'external-user-profile' || + this.$route.name === 'user-profile' + const isTargetUser = + this.$route.params.name === name || this.$route.params.id === id + if (isProfile && isTargetUser) { + window.history.back() + } + }) }, - setToggled (value) { + setToggled(value) { this.toggled = value - } - } + }, + }, } export default ModerationTools diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.js b/src/components/mrf_transparency_panel/mrf_transparency_panel.js index 13cfb52ee..2bd82f486 100644 --- a/src/components/mrf_transparency_panel/mrf_transparency_panel.js +++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.js @@ -1,5 +1,5 @@ -import { mapState } from 'vuex' import { get } from 'lodash' +import { mapState } from 'vuex' /** * This is for backwards compatibility. We originally didn't recieve @@ -8,7 +8,7 @@ import { get } from 'lodash' * to add an extra "info" key. */ const toInstanceReasonObject = (instances, info, key) => { - return instances.map(instance => { + return instances.map((instance) => { if (info[key] && info[key][instance] && info[key][instance].reason) { return { instance, reason: info[key][instance].reason } } @@ -19,56 +19,82 @@ const toInstanceReasonObject = (instances, info, key) => { const MRFTransparencyPanel = { computed: { ...mapState({ - federationPolicy: state => get(state, 'instance.federationPolicy'), - mrfPolicies: state => get(state, 'instance.federationPolicy.mrf_policies', []), - quarantineInstances: state => toInstanceReasonObject( - get(state, 'instance.federationPolicy.quarantined_instances', []), - get(state, 'instance.federationPolicy.quarantined_instances_info', []), - 'quarantined_instances' - ), - acceptInstances: state => toInstanceReasonObject( - get(state, 'instance.federationPolicy.mrf_simple.accept', []), - get(state, 'instance.federationPolicy.mrf_simple_info', []), - 'accept' - ), - rejectInstances: state => toInstanceReasonObject( - get(state, 'instance.federationPolicy.mrf_simple.reject', []), - get(state, 'instance.federationPolicy.mrf_simple_info', []), - 'reject' - ), - ftlRemovalInstances: state => toInstanceReasonObject( - get(state, 'instance.federationPolicy.mrf_simple.federated_timeline_removal', []), - get(state, 'instance.federationPolicy.mrf_simple_info', []), - 'federated_timeline_removal' - ), - mediaNsfwInstances: state => toInstanceReasonObject( - get(state, 'instance.federationPolicy.mrf_simple.media_nsfw', []), - get(state, 'instance.federationPolicy.mrf_simple_info', []), - 'media_nsfw' - ), - mediaRemovalInstances: state => toInstanceReasonObject( - get(state, 'instance.federationPolicy.mrf_simple.media_removal', []), - get(state, 'instance.federationPolicy.mrf_simple_info', []), - 'media_removal' - ), - keywordsFtlRemoval: state => get(state, 'instance.federationPolicy.mrf_keyword.federated_timeline_removal', []), - keywordsReject: state => get(state, 'instance.federationPolicy.mrf_keyword.reject', []), - keywordsReplace: state => get(state, 'instance.federationPolicy.mrf_keyword.replace', []) + federationPolicy: (state) => get(state, 'instance.federationPolicy'), + mrfPolicies: (state) => + get(state, 'instance.federationPolicy.mrf_policies', []), + quarantineInstances: (state) => + toInstanceReasonObject( + get(state, 'instance.federationPolicy.quarantined_instances', []), + get( + state, + 'instance.federationPolicy.quarantined_instances_info', + [], + ), + 'quarantined_instances', + ), + acceptInstances: (state) => + toInstanceReasonObject( + get(state, 'instance.federationPolicy.mrf_simple.accept', []), + get(state, 'instance.federationPolicy.mrf_simple_info', []), + 'accept', + ), + rejectInstances: (state) => + toInstanceReasonObject( + get(state, 'instance.federationPolicy.mrf_simple.reject', []), + get(state, 'instance.federationPolicy.mrf_simple_info', []), + 'reject', + ), + ftlRemovalInstances: (state) => + toInstanceReasonObject( + get( + state, + 'instance.federationPolicy.mrf_simple.federated_timeline_removal', + [], + ), + get(state, 'instance.federationPolicy.mrf_simple_info', []), + 'federated_timeline_removal', + ), + mediaNsfwInstances: (state) => + toInstanceReasonObject( + get(state, 'instance.federationPolicy.mrf_simple.media_nsfw', []), + get(state, 'instance.federationPolicy.mrf_simple_info', []), + 'media_nsfw', + ), + mediaRemovalInstances: (state) => + toInstanceReasonObject( + get(state, 'instance.federationPolicy.mrf_simple.media_removal', []), + get(state, 'instance.federationPolicy.mrf_simple_info', []), + 'media_removal', + ), + keywordsFtlRemoval: (state) => + get( + state, + 'instance.federationPolicy.mrf_keyword.federated_timeline_removal', + [], + ), + keywordsReject: (state) => + get(state, 'instance.federationPolicy.mrf_keyword.reject', []), + keywordsReplace: (state) => + get(state, 'instance.federationPolicy.mrf_keyword.replace', []), }), - hasInstanceSpecificPolicies () { - return this.quarantineInstances.length || + hasInstanceSpecificPolicies() { + return ( + this.quarantineInstances.length || this.acceptInstances.length || this.rejectInstances.length || this.ftlRemovalInstances.length || this.mediaNsfwInstances.length || this.mediaRemovalInstances.length + ) }, - hasKeywordPolicies () { - return this.keywordsFtlRemoval.length || + hasKeywordPolicies() { + return ( + this.keywordsFtlRemoval.length || this.keywordsReject.length || this.keywordsReplace.length - } - } + ) + }, + }, } export default MRFTransparencyPanel diff --git a/src/components/mute_card/mute_card.js b/src/components/mute_card/mute_card.js index 895586888..aaad96863 100644 --- a/src/components/mute_card/mute_card.js +++ b/src/components/mute_card/mute_card.js @@ -1,39 +1,41 @@ -import BasicUserCard from '../basic_user_card/basic_user_card.vue' import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue' +import BasicUserCard from '../basic_user_card/basic_user_card.vue' const MuteCard = { props: ['userId'], computed: { - user () { + user() { return this.$store.getters.findUser(this.userId) }, - relationship () { + relationship() { return this.$store.getters.relationship(this.userId) }, - muted () { + muted() { return this.relationship.muting }, - muteExpiryAvailable () { + muteExpiryAvailable() { return this.user.mute_expires_at !== undefined }, - muteExpiry () { + muteExpiry() { return this.user.mute_expires_at == null ? this.$t('user_card.mute_expires_forever') - : this.$t('user_card.mute_expires_at', [new Date(this.user.mute_expires_at).toLocaleString()]) - } + : this.$t('user_card.mute_expires_at', [ + new Date(this.user.mute_expires_at).toLocaleString(), + ]) + }, }, components: { BasicUserCard, - UserTimedFilterModal + UserTimedFilterModal, }, methods: { - unmuteUser () { + unmuteUser() { this.$store.dispatch('unmuteUser', this.userId) }, - muteUser () { + muteUser() { this.$refs.timedMuteDialog.optionallyPrompt() - } - } + }, + }, } export default MuteCard diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index a155abe0c..d80cd0a5d 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -1,32 +1,32 @@ -import BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue' -import ListsMenuContent from 'src/components/lists_menu/lists_menu_content.vue' -import { mapState, mapGetters } from 'vuex' import { mapState as mapPiniaState } from 'pinia' -import { TIMELINES, ROOT_ITEMS } from 'src/components/navigation/navigation.js' +import { mapGetters, mapState } from 'vuex' + +import BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue' +import Checkbox from 'src/components/checkbox/checkbox.vue' +import ListsMenuContent from 'src/components/lists_menu/lists_menu_content.vue' import { filterNavigation } from 'src/components/navigation/filter.js' +import { ROOT_ITEMS, TIMELINES } from 'src/components/navigation/navigation.js' import NavigationEntry from 'src/components/navigation/navigation_entry.vue' import NavigationPins from 'src/components/navigation/navigation_pins.vue' -import Checkbox from 'src/components/checkbox/checkbox.vue' - import { useAnnouncementsStore } from 'src/stores/announcements' import { useServerSideStorageStore } from 'src/stores/serverSideStorage' import { library } from '@fortawesome/fontawesome-svg-core' import { - faUsers, - faGlobe, - faCity, + faBell, faBookmark, - faEnvelope, + faBullhorn, faChevronDown, faChevronUp, + faCity, faComments, - faBell, + faEnvelope, + faFilePen, + faGlobe, faInfoCircle, - faStream, faList, - faBullhorn, - faFilePen + faStream, + faUsers, } from '@fortawesome/free-solid-svg-icons' library.add( @@ -43,80 +43,92 @@ library.add( faStream, faList, faBullhorn, - faFilePen + faFilePen, ) const NavPanel = { props: ['forceExpand', 'forceEditMode'], - created () { - }, components: { BookmarkFoldersMenuContent, ListsMenuContent, NavigationEntry, NavigationPins, - Checkbox + Checkbox, }, - data () { + data() { return { editMode: false, showTimelines: false, showLists: false, showBookmarkFolders: false, - timelinesList: Object.entries(TIMELINES).map(([k, v]) => ({ ...v, name: k })), - rootList: Object.entries(ROOT_ITEMS).map(([k, v]) => ({ ...v, name: k })) + timelinesList: Object.entries(TIMELINES).map(([k, v]) => ({ + ...v, + name: k, + })), + rootList: Object.entries(ROOT_ITEMS).map(([k, v]) => ({ ...v, name: k })), } }, methods: { - toggleTimelines () { + toggleTimelines() { this.showTimelines = !this.showTimelines }, - toggleLists () { + toggleLists() { this.showLists = !this.showLists }, - toggleBookmarkFolders () { + toggleBookmarkFolders() { this.showBookmarkFolders = !this.showBookmarkFolders }, - toggleEditMode () { + toggleEditMode() { this.editMode = !this.editMode }, - toggleCollapse () { - useServerSideStorageStore().setPreference({ path: 'simple.collapseNav', value: !this.collapsed }) + toggleCollapse() { + useServerSideStorageStore().setPreference({ + path: 'simple.collapseNav', + value: !this.collapsed, + }) useServerSideStorageStore().pushServerSideStorage() }, - isPinned (item) { + isPinned(item) { return this.pinnedItems.has(item) }, - togglePin (item) { + togglePin(item) { if (this.isPinned(item)) { - useServerSideStorageStore().removeCollectionPreference({ path: 'collections.pinnedNavItems', value: item }) + useServerSideStorageStore().removeCollectionPreference({ + path: 'collections.pinnedNavItems', + value: item, + }) } else { - useServerSideStorageStore().addCollectionPreference({ path: 'collections.pinnedNavItems', value: item }) + useServerSideStorageStore().addCollectionPreference({ + path: 'collections.pinnedNavItems', + value: item, + }) } useServerSideStorageStore().pushServerSideStorage() - } + }, }, computed: { ...mapPiniaState(useAnnouncementsStore, { unreadAnnouncementCount: 'unreadAnnouncementCount', - supportsAnnouncements: store => store.supportsAnnouncements + supportsAnnouncements: (store) => store.supportsAnnouncements, }), ...mapPiniaState(useServerSideStorageStore, { - collapsed: store => store.prefsStorage.simple.collapseNav, - pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems) + collapsed: (store) => store.prefsStorage.simple.collapseNav, + pinnedItems: (store) => + new Set(store.prefsStorage.collections.pinnedNavItems), }), ...mapState({ - currentUser: state => state.users.currentUser, - followRequestCount: state => state.api.followRequests.length, - privateMode: state => state.instance.private, - federating: state => state.instance.federating, - pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, - bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable, - bubbleTimeline: state => state.instance.localBubbleInstances.length > 0 + currentUser: (state) => state.users.currentUser, + followRequestCount: (state) => state.api.followRequests.length, + privateMode: (state) => state.instance.private, + federating: (state) => state.instance.federating, + pleromaChatMessagesAvailable: (state) => + state.instance.pleromaChatMessagesAvailable, + bookmarkFolders: (state) => + state.instance.pleromaBookmarkFoldersAvailable, + bubbleTimeline: (state) => state.instance.localBubbleInstances.length > 0, }), - timelinesItems () { + timelinesItems() { return filterNavigation( - Object - .entries({ ...TIMELINES }) + Object.entries({ ...TIMELINES }) // do not show in timeliens list since it's in a better place now .filter(([key]) => key !== 'bookmarks') .map(([k, v]) => ({ ...v, name: k })), @@ -127,15 +139,13 @@ const NavPanel = { isPrivate: this.privateMode, currentUser: this.currentUser, supportsBubbleTimeline: this.bubbleTimeline, - supportsBookmarkFolders: this.bookmarkFolders - } + supportsBookmarkFolders: this.bookmarkFolders, + }, ) }, - rootItems () { + rootItems() { return filterNavigation( - Object - .entries({ ...ROOT_ITEMS }) - .map(([k, v]) => ({ ...v, name: k })), + Object.entries({ ...ROOT_ITEMS }).map(([k, v]) => ({ ...v, name: k })), { hasChats: this.pleromaChatMessagesAvailable, hasAnnouncements: this.supportsAnnouncements, @@ -143,12 +153,12 @@ const NavPanel = { isPrivate: this.privateMode, currentUser: this.currentUser, supportsBubbleTimeline: this.bubbleTimeline, - supportsBookmarkFolders: this.bookmarkFolders - } + supportsBookmarkFolders: this.bookmarkFolders, + }, ) }, - ...mapGetters(['unreadChatCount']) - } + ...mapGetters(['unreadChatCount']), + }, } export default NavPanel diff --git a/src/components/navigation/filter.js b/src/components/navigation/filter.js index 54abb67b4..0255db6aa 100644 --- a/src/components/navigation/filter.js +++ b/src/components/navigation/filter.js @@ -1,39 +1,50 @@ -export const filterNavigation = (list = [], { - hasChats, - hasAnnouncements, - isFederating, - isPrivate, - currentUser, - supportsBookmarkFolders, - supportsBubbleTimeline -}) => { +export const filterNavigation = ( + list = [], + { + hasChats, + hasAnnouncements, + isFederating, + isPrivate, + currentUser, + supportsBookmarkFolders, + supportsBubbleTimeline, + }, +) => { return list.filter(({ criteria, anon, anonRoute }) => { const set = new Set(criteria || []) if (!isFederating && set.has('federating')) return false if (!currentUser && isPrivate && set.has('!private')) return false if (!currentUser && !(anon || anonRoute)) return false - if ((!currentUser || !currentUser.locked) && set.has('lockedUser')) return false + if ((!currentUser || !currentUser.locked) && set.has('lockedUser')) + return false if (!hasChats && set.has('chats')) return false if (!hasAnnouncements && set.has('announcements')) return false - if (!supportsBubbleTimeline && set.has('supportsBubbleTimeline')) return false - if (!supportsBookmarkFolders && set.has('supportsBookmarkFolders')) return false - if (supportsBookmarkFolders && set.has('!supportsBookmarkFolders')) return false + if (!supportsBubbleTimeline && set.has('supportsBubbleTimeline')) + return false + if (!supportsBookmarkFolders && set.has('supportsBookmarkFolders')) + return false + if (supportsBookmarkFolders && set.has('!supportsBookmarkFolders')) + return false return true }) } -export const getListEntries = store => store.allLists.map(list => ({ - name: 'list-' + list.id, - routeObject: { name: 'lists-timeline', params: { id: list.id } }, - labelRaw: list.title, - iconLetter: list.title[0] -})) +export const getListEntries = (store) => + store.allLists.map((list) => ({ + name: 'list-' + list.id, + routeObject: { name: 'lists-timeline', params: { id: list.id } }, + labelRaw: list.title, + iconLetter: list.title[0], + })) -export const getBookmarkFolderEntries = store => store.allFolders ? store.allFolders.map(folder => ({ - name: 'bookmark-folder-' + folder.id, - routeObject: { name: 'bookmark-folder', params: { id: folder.id } }, - labelRaw: folder.name, - iconEmoji: folder.emoji, - iconEmojiUrl: folder.emoji_url, - iconLetter: folder.name[0] -})) : [] +export const getBookmarkFolderEntries = (store) => + store.allFolders + ? store.allFolders.map((folder) => ({ + name: 'bookmark-folder-' + folder.id, + routeObject: { name: 'bookmark-folder', params: { id: folder.id } }, + labelRaw: folder.name, + iconEmoji: folder.emoji, + iconEmojiUrl: folder.emoji_url, + iconLetter: folder.name[0], + })) + : [] diff --git a/src/components/navigation/navigation.js b/src/components/navigation/navigation.js index d1c2b6763..66fb0d347 100644 --- a/src/components/navigation/navigation.js +++ b/src/components/navigation/navigation.js @@ -4,42 +4,39 @@ export const USERNAME_ROUTES = new Set([ 'interactions', 'notifications', 'chat', - 'chats' + 'chats', ]) // routes that take :name property -export const NAME_ROUTES = new Set([ - 'user-profile', - 'legacy-user-profile' -]) +export const NAME_ROUTES = new Set(['user-profile', 'legacy-user-profile']) export const TIMELINES = { home: { route: 'friends', icon: 'home', label: 'nav.home_timeline', - criteria: ['!private'] + criteria: ['!private'], }, public: { route: 'public-timeline', anon: true, icon: 'users', label: 'nav.public_tl', - criteria: ['!private'] + criteria: ['!private'], }, bubble: { route: 'bubble', anon: true, icon: 'city', label: 'nav.bubble', - criteria: ['!private', 'federating', 'supportsBubbleTimeline'] + criteria: ['!private', 'federating', 'supportsBubbleTimeline'], }, twkn: { route: 'public-external-timeline', anon: true, icon: 'globe', label: 'nav.twkn', - criteria: ['!private', 'federating'] + criteria: ['!private', 'federating'], }, // bookmarks are still technically a timeline so we should show it in the dropdown bookmarks: { @@ -50,13 +47,13 @@ export const TIMELINES = { favorites: { routeObject: { name: 'user-profile', query: { tab: 'favorites' } }, icon: 'star', - label: 'user_card.favorites' + label: 'user_card.favorites', }, dms: { route: 'dms', icon: 'envelope', - label: 'nav.dms' - } + label: 'nav.dms', + }, } export const ROOT_ITEMS = { @@ -67,12 +64,12 @@ export const ROOT_ITEMS = { // shows bookmarks entry in a better suited location // hides it when bookmark folders are supported since // we show custom component instead of it - criteria: ['!supportsBookmarkFolders'] + criteria: ['!supportsBookmarkFolders'], }, interactions: { route: 'interactions', icon: 'bell', - label: 'nav.interactions' + label: 'nav.interactions', }, chats: { route: 'chats', @@ -80,7 +77,7 @@ export const ROOT_ITEMS = { label: 'nav.chats', badgeStyle: 'notification', badgeGetter: 'unreadChatCount', - criteria: ['chats'] + criteria: ['chats'], }, friendRequests: { route: 'friend-requests', @@ -88,13 +85,13 @@ export const ROOT_ITEMS = { label: 'nav.friend_requests', badgeStyle: 'notification', criteria: ['lockedUser'], - badgeGetter: 'followRequestCount' + badgeGetter: 'followRequestCount', }, about: { route: 'about', anon: true, icon: 'info-circle', - label: 'nav.about' + label: 'nav.about', }, announcements: { route: 'announcements', @@ -103,18 +100,18 @@ export const ROOT_ITEMS = { store: 'announcements', badgeStyle: 'notification', badgeGetter: 'unreadAnnouncementCount', - criteria: ['announcements'] + criteria: ['announcements'], }, drafts: { route: 'drafts', icon: 'file-pen', label: 'nav.drafts', badgeStyle: 'neutral', - badgeGetter: 'draftCount' - } + badgeGetter: 'draftCount', + }, } -export function routeTo (item, currentUser) { +export function routeTo(item, currentUser) { if (!item.route && !item.routeObject) return null let route @@ -122,7 +119,7 @@ export function routeTo (item, currentUser) { if (item.routeObject) { route = item.routeObject } else { - route = { name: (item.anon || currentUser) ? item.route : item.anonRoute } + route = { name: item.anon || currentUser ? item.route : item.anonRoute } } if (USERNAME_ROUTES.has(route.name)) { diff --git a/src/components/navigation/navigation_entry.js b/src/components/navigation/navigation_entry.js index 11db1c9e3..75d4dffdd 100644 --- a/src/components/navigation/navigation_entry.js +++ b/src/components/navigation/navigation_entry.js @@ -1,48 +1,56 @@ +import { mapState as mapPiniaState, mapStores } from 'pinia' import { mapState } from 'vuex' + import { routeTo } from 'src/components/navigation/navigation.js' import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue' -import { library } from '@fortawesome/fontawesome-svg-core' -import { faThumbtack } from '@fortawesome/free-solid-svg-icons' -import { mapStores, mapState as mapPiniaState } from 'pinia' - import { useAnnouncementsStore } from 'src/stores/announcements' import { useServerSideStorageStore } from 'src/stores/serverSideStorage' +import { library } from '@fortawesome/fontawesome-svg-core' +import { faThumbtack } from '@fortawesome/free-solid-svg-icons' + library.add(faThumbtack) const NavigationEntry = { props: ['item', 'showPin'], components: { - OptionalRouterLink + OptionalRouterLink, }, methods: { - isPinned (value) { + isPinned(value) { return this.pinnedItems.has(value) }, - togglePin (value) { + togglePin(value) { if (this.isPinned(value)) { - useServerSideStorageStore().removeCollectionPreference({ path: 'collections.pinnedNavItems', value }) + useServerSideStorageStore().removeCollectionPreference({ + path: 'collections.pinnedNavItems', + value, + }) } else { - useServerSideStorageStore().addCollectionPreference({ path: 'collections.pinnedNavItems', value }) + useServerSideStorageStore().addCollectionPreference({ + path: 'collections.pinnedNavItems', + value, + }) } useServerSideStorageStore().pushServerSideStorage() - } + }, }, computed: { - routeTo () { + routeTo() { return routeTo(this.item, this.currentUser) }, - getters () { + getters() { return this.$store.getters }, ...mapStores(useAnnouncementsStore), ...mapState({ - currentUser: state => state.users.currentUser + currentUser: (state) => state.users.currentUser, }), ...mapPiniaState(useServerSideStorageStore, { - pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems) + pinnedItems: (store) => + new Set(store.prefsStorage.collections.pinnedNavItems), }), - } + }, } export default NavigationEntry diff --git a/src/components/navigation/navigation_pins.js b/src/components/navigation/navigation_pins.js index f9a900fc6..e14edce14 100644 --- a/src/components/navigation/navigation_pins.js +++ b/src/components/navigation/navigation_pins.js @@ -1,27 +1,35 @@ -import { mapState } from 'vuex' import { mapState as mapPiniaState } from 'pinia' -import { TIMELINES, ROOT_ITEMS, routeTo } from 'src/components/navigation/navigation.js' -import { getBookmarkFolderEntries, getListEntries, filterNavigation } from 'src/components/navigation/filter.js' +import { mapState } from 'vuex' +import { + filterNavigation, + getBookmarkFolderEntries, + getListEntries, +} from 'src/components/navigation/filter.js' +import { + ROOT_ITEMS, + routeTo, + TIMELINES, +} from 'src/components/navigation/navigation.js' import StillImage from 'src/components/still-image/still-image.vue' +import { useAnnouncementsStore } from 'src/stores/announcements' +import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders' +import { useListsStore } from 'src/stores/lists' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' import { library } from '@fortawesome/fontawesome-svg-core' import { - faUsers, - faGlobe, - faCity, - faBookmark, - faEnvelope, - faComments, faBell, + faBookmark, + faCity, + faComments, + faEnvelope, + faGlobe, faInfoCircle, + faList, faStream, - faList + faUsers, } from '@fortawesome/free-solid-svg-icons' -import { useListsStore } from 'src/stores/lists' -import { useAnnouncementsStore } from 'src/stores/announcements' -import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage' library.add( faUsers, @@ -33,72 +41,74 @@ library.add( faBell, faInfoCircle, faStream, - faList + faList, ) const NavPanel = { props: ['limit'], methods: { - getRouteTo (item) { + getRouteTo(item) { return routeTo(item, this.currentUser) - } + }, }, components: { - StillImage + StillImage, }, computed: { - getters () { + getters() { return this.$store.getters }, ...mapPiniaState(useListsStore, { - lists: getListEntries + lists: getListEntries, }), ...mapPiniaState(useAnnouncementsStore, { - supportsAnnouncements: store => store.supportsAnnouncements + supportsAnnouncements: (store) => store.supportsAnnouncements, }), ...mapPiniaState(useBookmarkFoldersStore, { bookmarks: getBookmarkFolderEntries, }), ...mapPiniaState(useServerSideStorageStore, { - pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems) + pinnedItems: (store) => + new Set(store.prefsStorage.collections.pinnedNavItems), }), ...mapState({ - currentUser: state => state.users.currentUser, - followRequestCount: state => state.api.followRequests.length, - privateMode: state => state.instance.private, - federating: state => state.instance.federating, - pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, - bubbleTimeline: state => state.instance.localBubbleInstances.length > 0 + currentUser: (state) => state.users.currentUser, + followRequestCount: (state) => state.api.followRequests.length, + privateMode: (state) => state.instance.private, + federating: (state) => state.instance.federating, + pleromaChatMessagesAvailable: (state) => + state.instance.pleromaChatMessagesAvailable, + bubbleTimeline: (state) => state.instance.localBubbleInstances.length > 0, }), - pinnedList () { + pinnedList() { if (!this.currentUser) { - return filterNavigation([ - { ...TIMELINES.public, name: 'public' }, - { ...TIMELINES.twkn, name: 'twkn' }, - { ...ROOT_ITEMS.about, name: 'about' } - ], - { - hasChats: this.pleromaChatMessagesAvailable, - hasAnnouncements: this.supportsAnnouncements, - isFederating: this.federating, - isPrivate: this.privateMode, - currentUser: this.currentUser, - supportsBubbleTimeline: this.bubbleTimeline, - supportsBookmarkFolders: this.bookmarks - }) + return filterNavigation( + [ + { ...TIMELINES.public, name: 'public' }, + { ...TIMELINES.twkn, name: 'twkn' }, + { ...ROOT_ITEMS.about, name: 'about' }, + ], + { + hasChats: this.pleromaChatMessagesAvailable, + hasAnnouncements: this.supportsAnnouncements, + isFederating: this.federating, + isPrivate: this.privateMode, + currentUser: this.currentUser, + supportsBubbleTimeline: this.bubbleTimeline, + supportsBookmarkFolders: this.bookmarks, + }, + ) } return filterNavigation( [ - ...Object - .entries({ ...TIMELINES }) + ...Object.entries({ ...TIMELINES }) .filter(([k]) => this.pinnedItems.has(k)) .map(([k, v]) => ({ ...v, name: k })), ...this.lists.filter((k) => this.pinnedItems.has(k.name)), ...this.bookmarks.filter((k) => this.pinnedItems.has(k.name)), - ...Object - .entries({ ...ROOT_ITEMS }) + ...Object.entries({ ...ROOT_ITEMS }) .filter(([k]) => this.pinnedItems.has(k)) - .map(([k, v]) => ({ ...v, name: k })) + .map(([k, v]) => ({ ...v, name: k })), ], { hasChats: this.pleromaChatMessagesAvailable, @@ -107,11 +117,11 @@ const NavPanel = { supportsBookmarkFolders: this.bookmarks, isFederating: this.federating, isPrivate: this.privateMode, - currentUser: this.currentUser - } + currentUser: this.currentUser, + }, ).slice(0, this.limit) - } - } + }, + }, } export default NavPanel diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 8052e3a73..275827c43 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -1,29 +1,34 @@ -import StatusContent from '../status_content/status_content.vue' import { mapState } from 'vuex' + +import RichContent from 'src/components/rich_content/rich_content.jsx' +import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' +import { isStatusNotification } from '../../services/notification_utils/notification_utils.js' +import { + highlightClass, + highlightStyle, +} from '../../services/user_highlighter/user_highlighter.js' +import ConfirmModal from '../confirm_modal/confirm_modal.vue' +import Report from '../report/report.vue' import Status from '../status/status.vue' +import StatusContent from '../status_content/status_content.vue' +import Timeago from '../timeago/timeago.vue' import UserAvatar from '../user_avatar/user_avatar.vue' import UserCard from '../user_card/user_card.vue' -import Timeago from '../timeago/timeago.vue' -import Report from '../report/report.vue' import UserLink from '../user_link/user_link.vue' -import RichContent from 'src/components/rich_content/rich_content.jsx' import UserPopover from '../user_popover/user_popover.vue' -import ConfirmModal from '../confirm_modal/confirm_modal.vue' -import { isStatusNotification } from '../../services/notification_utils/notification_utils.js' -import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' -import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' + import { library } from '@fortawesome/fontawesome-svg-core' import { faCheck, - faTimes, - faStar, - faRetweet, - faUserPlus, - faEyeSlash, - faUser, - faSuitcaseRolling, + faCompressAlt, faExpandAlt, - faCompressAlt + faEyeSlash, + faRetweet, + faStar, + faSuitcaseRolling, + faTimes, + faUser, + faUserPlus, } from '@fortawesome/free-solid-svg-icons' library.add( @@ -36,17 +41,17 @@ library.add( faEyeSlash, faSuitcaseRolling, faExpandAlt, - faCompressAlt + faCompressAlt, ) const Notification = { - data () { + data() { return { selecting: false, statusExpanded: false, unmuted: false, showingApproveConfirmDialog: false, - showingDenyConfirmDialog: false + showingDenyConfirmDialog: false, } }, props: ['notification'], @@ -61,141 +66,158 @@ const Notification = { RichContent, UserPopover, UserLink, - ConfirmModal + ConfirmModal, }, - mounted () { + mounted() { document.addEventListener('selectionchange', this.onContentSelect) }, - unmounted () { + unmounted() { document.removeEventListener('selectionchange', this.onContentSelect) }, methods: { - toggleStatusExpanded () { + toggleStatusExpanded() { if (!this.expandable) return this.statusExpanded = !this.statusExpanded }, - onContentSelect () { + onContentSelect() { const { isCollapsed, anchorNode, offsetNode } = document.getSelection() if (isCollapsed) { this.selecting = false return } - const within = this.$refs.root.contains(anchorNode) || this.$refs.root.contains(offsetNode) + const within = + this.$refs.root.contains(anchorNode) || + this.$refs.root.contains(offsetNode) if (within) { this.selecting = true } else { this.selecting = false } }, - onContentClick (e) { - if (!this.selecting && !e.target.closest('a') && !e.target.closest('button')) { + onContentClick(e) { + if ( + !this.selecting && + !e.target.closest('a') && + !e.target.closest('button') + ) { this.toggleStatusExpanded() } }, - generateUserProfileLink (user) { - return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames) + generateUserProfileLink(user) { + return generateProfileLink( + user.id, + user.screen_name, + this.$store.state.instance.restrictedNicknames, + ) }, - getUser (notification) { + getUser(notification) { return this.$store.state.users.usersObject[notification.from_profile.id] }, - interacted () { + interacted() { this.$emit('interacted') }, - toggleMute () { + toggleMute() { this.unmuted = !this.unmuted }, - showApproveConfirmDialog () { + showApproveConfirmDialog() { this.showingApproveConfirmDialog = true }, - hideApproveConfirmDialog () { + hideApproveConfirmDialog() { this.showingApproveConfirmDialog = false }, - showDenyConfirmDialog () { + showDenyConfirmDialog() { this.showingDenyConfirmDialog = true }, - hideDenyConfirmDialog () { + hideDenyConfirmDialog() { this.showingDenyConfirmDialog = false }, - approveUser () { + approveUser() { if (this.shouldConfirmApprove) { this.showApproveConfirmDialog() } else { this.doApprove() } }, - doApprove () { + doApprove() { this.$emit('interacted') this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) this.$store.dispatch('removeFollowRequest', this.user) - this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id }) + this.$store.dispatch('markSingleNotificationAsSeen', { + id: this.notification.id, + }) this.$store.dispatch('updateNotification', { id: this.notification.id, - updater: notification => { + updater: (notification) => { notification.type = 'follow' - } + }, }) this.hideApproveConfirmDialog() }, - denyUser () { + denyUser() { if (this.shouldConfirmDeny) { this.showDenyConfirmDialog() } else { this.doDeny() } }, - doDeny () { + doDeny() { this.$emit('interacted') - this.$store.state.api.backendInteractor.denyUser({ id: this.user.id }) + this.$store.state.api.backendInteractor + .denyUser({ id: this.user.id }) .then(() => { - this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id }) + this.$store.dispatch('dismissNotificationLocal', { + id: this.notification.id, + }) this.$store.dispatch('removeFollowRequest', this.user) }) this.hideDenyConfirmDialog() - } + }, }, computed: { - userClass () { + userClass() { return highlightClass(this.notification.from_profile) }, - userStyle () { + userStyle() { const highlight = this.$store.getters.mergedConfig.highlight const user = this.notification.from_profile return highlightStyle(highlight[user.screen_name]) }, - expandable () { - return (new Set(['like', 'pleroma:emoji_reaction', 'repeat', 'poll'])).has(this.notification.type) + expandable() { + return new Set(['like', 'pleroma:emoji_reaction', 'repeat', 'poll']).has( + this.notification.type, + ) }, - user () { + user() { return this.$store.getters.findUser(this.notification.from_profile.id) }, - userProfileLink () { + userProfileLink() { return this.generateUserProfileLink(this.user) }, - targetUser () { + targetUser() { return this.$store.getters.findUser(this.notification.target.id) }, - targetUserProfileLink () { + targetUserProfileLink() { return this.generateUserProfileLink(this.targetUser) }, - needMute () { + needMute() { return this.$store.getters.relationship(this.user.id).muting }, - isStatusNotification () { + isStatusNotification() { return isStatusNotification(this.notification.type) }, - mergedConfig () { + mergedConfig() { return this.$store.getters.mergedConfig }, - shouldConfirmApprove () { + shouldConfirmApprove() { return this.mergedConfig.modalOnApproveFollow }, - shouldConfirmDeny () { + shouldConfirmDeny() { return this.mergedConfig.modalOnDenyFollow }, ...mapState({ - currentUser: state => state.users.currentUser - }) - } + currentUser: (state) => state.users.currentUser, + }), + }, } export default Notification diff --git a/src/components/notification/notification.style.js b/src/components/notification/notification.style.js index 05c1f6f23..49e28cf2e 100644 --- a/src/components/notification/notification.style.js +++ b/src/components/notification/notification.style.js @@ -7,7 +7,7 @@ export default { 'Icon', 'Border', 'Avatar', - 'PollGraph' + 'PollGraph', ], - defaultRules: [] + defaultRules: [], } diff --git a/src/components/notifications/notification_filters.vue b/src/components/notifications/notification_filters.vue index 0b740ea0a..7be8eb76b 100644 --- a/src/components/notifications/notification_filters.vue +++ b/src/components/notifications/notification_filters.vue @@ -107,30 +107,29 @@ diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index ced97d57f..9e349a2eb 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -1,28 +1,30 @@ +import { mapState } from 'pinia' import { computed } from 'vue' import { mapGetters } from 'vuex' -import { mapState } from 'pinia' -import Notification from '../notification/notification.vue' -import ExtraNotifications from '../extra_notifications/extra_notifications.vue' -import NotificationFilters from './notification_filters.vue' -import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js' -import { - notificationsFromStore, - filteredNotificationsFromStore, - unseenNotificationsFromStore, - countExtraNotifications, - ACTIONABLE_NOTIFICATION_TYPES -} from '../../services/notification_utils/notification_utils.js' -import FaviconService from '../../services/favicon_service/favicon_service.js' -import { library } from '@fortawesome/fontawesome-svg-core' -import { faCircleNotch, faArrowUp, faMinus } from '@fortawesome/free-solid-svg-icons' -import { useInterfaceStore } from 'src/stores/interface' -import { useAnnouncementsStore } from 'src/stores/announcements' -library.add( - faCircleNotch, +import { useAnnouncementsStore } from 'src/stores/announcements' +import { useInterfaceStore } from 'src/stores/interface' +import FaviconService from '../../services/favicon_service/favicon_service.js' +import { + ACTIONABLE_NOTIFICATION_TYPES, + countExtraNotifications, + filteredNotificationsFromStore, + notificationsFromStore, + unseenNotificationsFromStore, +} from '../../services/notification_utils/notification_utils.js' +import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js' +import ExtraNotifications from '../extra_notifications/extra_notifications.vue' +import Notification from '../notification/notification.vue' +import NotificationFilters from './notification_filters.vue' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { faArrowUp, - faMinus -) + faCircleNotch, + faMinus, +} from '@fortawesome/free-solid-svg-icons' + +library.add(faCircleNotch, faArrowUp, faMinus) const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30 @@ -30,7 +32,7 @@ const Notifications = { components: { Notification, NotificationFilters, - ExtraNotifications + ExtraNotifications, }, props: { // Disables panel styles, unread mark, potentially other notification-related actions @@ -41,93 +43,110 @@ const Notifications = { // Do not show extra notifications noExtra: { type: Boolean, - default: false + default: false, }, // Disable teleporting (i.e. for /users/user/notifications) - disableTeleport: Boolean + disableTeleport: Boolean, }, - data () { + data() { return { showScrollTop: false, bottomedOut: false, // How many seen notifications to display in the list. The more there are, // the heavier the page becomes. This count is increased when loading // older notifications, and cut back to default whenever hitting "Read!". - seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT + seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT, } }, - provide () { + provide() { return { - popoversZLayer: computed(() => this.popoversZLayer) + popoversZLayer: computed(() => this.popoversZLayer), } }, computed: { - mainClass () { + mainClass() { return this.minimalMode ? '' : 'panel panel-default' }, - notifications () { + notifications() { return notificationsFromStore(this.$store) }, - error () { + error() { return this.$store.state.notifications.error }, - unseenNotifications () { + unseenNotifications() { return unseenNotificationsFromStore(this.$store) }, - filteredNotifications () { + filteredNotifications() { if (this.unseenAtTop) { return [ - ...filteredNotificationsFromStore(this.$store).filter(n => this.shouldShowUnseen(n)), - ...filteredNotificationsFromStore(this.$store).filter(n => !this.shouldShowUnseen(n)) + ...filteredNotificationsFromStore(this.$store).filter((n) => + this.shouldShowUnseen(n), + ), + ...filteredNotificationsFromStore(this.$store).filter( + (n) => !this.shouldShowUnseen(n), + ), ] } else { return filteredNotificationsFromStore(this.$store, this.filterMode) } }, - unseenCountBadgeText () { + unseenCountBadgeText() { return `${this.unseenCount ? this.unseenCount : ''}${this.extraNotificationsCount ? '*' : ''}` }, - unseenCount () { + unseenCount() { return this.unseenNotifications.length }, - ignoreInactionableSeen () { return this.$store.getters.mergedConfig.ignoreInactionableSeen }, - extraNotificationsCount () { + ignoreInactionableSeen() { + return this.$store.getters.mergedConfig.ignoreInactionableSeen + }, + extraNotificationsCount() { return countExtraNotifications(this.$store) }, - unseenCountTitle () { - return this.unseenNotifications.length + (this.unreadChatCount) + this.unreadAnnouncementCount + unseenCountTitle() { + return ( + this.unseenNotifications.length + + this.unreadChatCount + + this.unreadAnnouncementCount + ) }, - loading () { + loading() { return this.$store.state.notifications.loading }, - noHeading () { + noHeading() { const { layoutType } = useInterfaceStore() return this.minimalMode || layoutType === 'mobile' }, - teleportTarget () { + teleportTarget() { const { layoutType } = useInterfaceStore() const map = { wide: '#notifs-column', - mobile: '#mobile-notifications' + mobile: '#mobile-notifications', } return map[layoutType] || '#notifs-sidebar' }, - popoversZLayer () { + popoversZLayer() { const { layoutType } = useInterfaceStore() return layoutType === 'mobile' ? 'navbar' : null }, - notificationsToDisplay () { - return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount) + notificationsToDisplay() { + return this.filteredNotifications.slice( + 0, + this.unseenCount + this.seenToDisplayCount, + ) }, - noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders }, - unseenAtTop () { return this.$store.getters.mergedConfig.unseenAtTop }, - showExtraNotifications () { + noSticky() { + return this.$store.getters.mergedConfig.disableStickyHeaders + }, + unseenAtTop() { + return this.$store.getters.mergedConfig.unseenAtTop + }, + showExtraNotifications() { return !this.noExtra }, ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']), - ...mapGetters(['unreadChatCount']) + ...mapGetters(['unreadChatCount']), }, - mounted () { + mounted() { this.scrollerRef = this.$refs.root.closest('.column.-scrollable') if (!this.scrollerRef) { this.scrollerRef = this.$refs.root.closest('.mobile-notifications') @@ -137,12 +156,12 @@ const Notifications = { } this.scrollerRef.addEventListener('scroll', this.updateScrollPosition) }, - unmounted () { + unmounted() { if (!this.scrollerRef) return this.scrollerRef.removeEventListener('scroll', this.updateScrollPosition) }, watch: { - unseenCountTitle (count) { + unseenCountTitle(count) { if (count > 0) { FaviconService.drawFaviconBadge() useInterfaceStore().setPageTitle(`(${count})`) @@ -151,10 +170,13 @@ const Notifications = { useInterfaceStore().setPageTitle('') } }, - teleportTarget () { + teleportTarget() { // handle scroller change this.$nextTick(() => { - this.scrollerRef.removeEventListener('scroll', this.updateScrollPosition) + this.scrollerRef.removeEventListener( + 'scroll', + this.updateScrollPosition, + ) this.scrollerRef = this.$refs.root.closest('.column.-scrollable') if (!this.scrollerRef) { this.scrollerRef = this.$refs.root.closest('.mobile-notifications') @@ -162,17 +184,18 @@ const Notifications = { this.scrollerRef.addEventListener('scroll', this.updateScrollPosition) this.updateScrollPosition() }) - } + }, }, methods: { - scrollToTop () { + scrollToTop() { const scrollable = this.scrollerRef scrollable.scrollTo({ top: this.$refs.root.offsetTop }) }, - updateScrollPosition () { - this.showScrollTop = this.$refs.root.offsetTop < this.scrollerRef.scrollTop + updateScrollPosition() { + this.showScrollTop = + this.$refs.root.offsetTop < this.scrollerRef.scrollTop }, - shouldShowUnseen (notification) { + shouldShowUnseen(notification) { if (notification.seen) return false const actionable = ACTIONABLE_NOTIFICATION_TYPES.has(notification.type) @@ -182,26 +205,29 @@ const Notifications = { * everything else (likes/repeats/reacts) cannot be acted and therefore we just clear * the "seen" status upon any clicks on them */ - notificationClicked (notification) { + notificationClicked(notification) { const { id } = notification this.$store.dispatch('notificationClicked', { id }) }, - notificationInteracted (notification) { + notificationInteracted(notification) { const { id } = notification this.$store.dispatch('markSingleNotificationAsSeen', { id }) }, - markAsSeen () { + markAsSeen() { this.$store.dispatch('markNotificationsAsSeen') this.seenToDisplayCount = DEFAULT_SEEN_TO_DISPLAY_COUNT }, - fetchOlderNotifications () { + fetchOlderNotifications() { if (this.loading) { return } const seenCount = this.filteredNotifications.length - this.unseenCount if (this.seenToDisplayCount < seenCount) { - this.seenToDisplayCount = Math.min(this.seenToDisplayCount + 20, seenCount) + this.seenToDisplayCount = Math.min( + this.seenToDisplayCount + 20, + seenCount, + ) return } else if (this.seenToDisplayCount > seenCount) { this.seenToDisplayCount = seenCount @@ -210,19 +236,21 @@ const Notifications = { const store = this.$store const credentials = store.state.users.currentUser.credentials store.commit('setNotificationsLoading', { value: true }) - notificationsFetcher.fetchAndUpdate({ - store, - credentials, - older: true - }).then(notifs => { - store.commit('setNotificationsLoading', { value: false }) - if (notifs.length === 0) { - this.bottomedOut = true - } - this.seenToDisplayCount += notifs.length - }) - } - } + notificationsFetcher + .fetchAndUpdate({ + store, + credentials, + older: true, + }) + .then((notifs) => { + store.commit('setNotificationsLoading', { value: false }) + if (notifs.length === 0) { + this.bottomedOut = true + } + this.seenToDisplayCount += notifs.length + }) + }, + }, } export default Notifications diff --git a/src/components/oauth_callback/oauth_callback.js b/src/components/oauth_callback/oauth_callback.js index 02e4c9ffc..82c3db047 100644 --- a/src/components/oauth_callback/oauth_callback.js +++ b/src/components/oauth_callback/oauth_callback.js @@ -1,25 +1,27 @@ -import oauth from '../../services/new_api/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js' +import oauth from '../../services/new_api/oauth.js' const oac = { props: ['code'], - mounted () { + mounted() { if (this.code) { const oauthStore = useOAuthStore() const { clientId, clientSecret } = oauthStore - oauth.getToken({ - clientId, - clientSecret, - instance: this.$store.state.instance.server, - code: this.code - }).then((result) => { - oauthStore.setToken(result.access_token) - this.$store.dispatch('loginUser', result.access_token) - this.$router.push({ name: 'friends' }) - }) + oauth + .getToken({ + clientId, + clientSecret, + instance: this.$store.state.instance.server, + code: this.code, + }) + .then((result) => { + oauthStore.setToken(result.access_token) + this.$store.dispatch('loginUser', result.access_token) + this.$router.push({ name: 'friends' }) + }) } - } + }, } export default oac diff --git a/src/components/opacity_input/opacity_input.vue b/src/components/opacity_input/opacity_input.vue index 19de233d7..b8012741f 100644 --- a/src/components/opacity_input/opacity_input.vue +++ b/src/components/opacity_input/opacity_input.vue @@ -36,16 +36,14 @@ import Checkbox from '../checkbox/checkbox.vue' export default { components: { - Checkbox + Checkbox, }, - props: [ - 'name', 'label', 'modelValue', 'fallback', 'disabled' - ], + props: ['name', 'label', 'modelValue', 'fallback', 'disabled'], emits: ['update:modelValue'], computed: { - present () { + present() { return typeof this.modelValue !== 'undefined' - } - } + }, + }, } diff --git a/src/components/optional_router_link/optional_router_link.vue b/src/components/optional_router_link/optional_router_link.vue index d56ad268a..b0d553801 100644 --- a/src/components/optional_router_link/optional_router_link.vue +++ b/src/components/optional_router_link/optional_router_link.vue @@ -18,6 +18,6 @@ diff --git a/src/components/palette_editor/palette_editor.vue b/src/components/palette_editor/palette_editor.vue index 3040696d0..21b40bccb 100644 --- a/src/components/palette_editor/palette_editor.vue +++ b/src/components/palette_editor/palette_editor.vue @@ -49,22 +49,15 @@ import { computed } from 'vue' import ColorInput from 'src/components/color_input/color_input.vue' import { + newExporter, newImporter, - newExporter } from 'src/services/export_import/export_import.js' - -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faFileImport, - faFileExport -} from '@fortawesome/free-solid-svg-icons' - import { useInterfaceStore } from 'src/stores/interface' -library.add( - faFileImport, - faFileExport -) +import { library } from '@fortawesome/fontawesome-svg-core' +import { faFileExport, faFileImport } from '@fortawesome/free-solid-svg-icons' + +library.add(faFileImport, faFileExport) const paletteKeys = [ 'bg', @@ -76,30 +69,31 @@ const paletteKeys = [ 'cBlue', 'cGreen', 'cOrange', - 'wallpaper' + 'wallpaper', ] const props = defineProps(['modelValue', 'compact', 'apply', 'disabled']) const emit = defineEmits(['update:modelValue', 'applyPalette']) -const getExportedObject = () => paletteKeys.reduce((acc, key) => { - const value = props.modelValue[key] - if (value == null) { - return acc - } else { - return { ...acc, [key]: props.modelValue[key] } - } -}, {}) +const getExportedObject = () => + paletteKeys.reduce((acc, key) => { + const value = props.modelValue[key] + if (value == null) { + return acc + } else { + return { ...acc, [key]: props.modelValue[key] } + } + }, {}) const paletteExporter = newExporter({ filename: 'pleroma_palette', extension: 'json', - getExportedObject + getExportedObject, }) const paletteImporter = newImporter({ accept: '.json', - onImport (parsed) { + onImport(parsed) { emit('update:modelValue', parsed) - } + }, }) const exportPalette = () => { @@ -136,7 +130,7 @@ const fallback = (key) => { const updatePalette = (paletteKey, value) => { emit('update:modelValue', { ...props.modelValue, - [paletteKey]: value + [paletteKey]: value, }) } diff --git a/src/components/panel.style.js b/src/components/panel.style.js index 2c8956ae1..ff8c2bf3b 100644 --- a/src/components/panel.style.js +++ b/src/components/panel.style.js @@ -9,15 +9,9 @@ export default { 'PanelHeader', 'Post', 'Notification', - 'MenuItem' - ], - validInnerComponentsLite: [ - 'Text', - 'Link', - 'Icon', - 'Border', - 'PanelHeader' + 'MenuItem', ], + validInnerComponentsLite: ['Text', 'Link', 'Icon', 'Border', 'PanelHeader'], defaultRules: [ { directives: { @@ -25,23 +19,25 @@ export default { background: '--bg', roundness: 3, blur: '5px', - shadow: [{ - x: 0, - y: 0, - blur: 3, - spread: 0, - color: '#000000', - alpha: 0.5 - }, - { - x: 0, - y: 4, - blur: 6, - spread: 3, - color: '#000000', - alpha: 0.3 - }] - } - } - ] + shadow: [ + { + x: 0, + y: 0, + blur: 3, + spread: 0, + color: '#000000', + alpha: 0.5, + }, + { + x: 0, + y: 4, + blur: 6, + spread: 3, + color: '#000000', + alpha: 0.3, + }, + ], + }, + }, + ], } diff --git a/src/components/panel_header.style.js b/src/components/panel_header.style.js index 4e5f97760..67673e1bf 100644 --- a/src/components/panel_header.style.js +++ b/src/components/panel_header.style.js @@ -7,7 +7,7 @@ export default { 'Icon', 'Button', 'ButtonUnstyled', - 'Alert' + 'Alert', ], defaultRules: [ { @@ -15,24 +15,26 @@ export default { directives: { backgroundNoCssColor: 'yes', background: '--fg', - shadow: [{ - x: 0, - y: 1, - blur: 3, - spread: 0, - color: '#000000', - alpha: 0.4 - }, - { - x: 0, - y: 1, - blur: 0, - spread: 0, - color: '#ffffff', - alpha: 0.2, - inset: true - }] - } - } - ] + shadow: [ + { + x: 0, + y: 1, + blur: 3, + spread: 0, + color: '#000000', + alpha: 0.4, + }, + { + x: 0, + y: 1, + blur: 0, + spread: 0, + color: '#ffffff', + alpha: 0.2, + inset: true, + }, + ], + }, + }, + ], } diff --git a/src/components/panel_loading/panel_loading.vue b/src/components/panel_loading/panel_loading.vue index 7a8321220..df29e5df0 100644 --- a/src/components/panel_loading/panel_loading.vue +++ b/src/components/panel_loading/panel_loading.vue @@ -15,9 +15,7 @@ import { library } from '@fortawesome/fontawesome-svg-core' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' -library.add( - faCircleNotch -) +library.add(faCircleNotch) export default {} diff --git a/src/components/password_reset/password_reset.js b/src/components/password_reset/password_reset.js index 3d94f5e72..4fd53f8dd 100644 --- a/src/components/password_reset/password_reset.js +++ b/src/components/password_reset/password_reset.js @@ -1,34 +1,32 @@ import { mapState } from 'vuex' -import passwordResetApi from '../../services/new_api/password_reset.js' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faTimes -} from '@fortawesome/free-solid-svg-icons' -library.add( - faTimes -) +import passwordResetApi from '../../services/new_api/password_reset.js' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { faTimes } from '@fortawesome/free-solid-svg-icons' + +library.add(faTimes) const passwordReset = { data: () => ({ user: { - email: '' + email: '', }, isPending: false, success: false, throttled: false, - error: null + error: null, }), computed: { ...mapState({ signedIn: (state) => !!state.users.currentUser, - instance: state => state.instance + instance: (state) => state.instance, }), - mailerEnabled () { + mailerEnabled() { return this.instance.mailerEnabled - } + }, }, - created () { + created() { if (this.signedIn) { this.$router.push({ name: 'root' }) } @@ -36,36 +34,38 @@ const passwordReset = { props: { passwordResetRequested: { default: false, - type: Boolean - } + type: Boolean, + }, }, methods: { - dismissError () { + dismissError() { this.error = null }, - submit () { + submit() { this.isPending = true const email = this.user.email const instance = this.instance.server - passwordResetApi({ instance, email }).then(({ status }) => { - this.isPending = false - this.user.email = '' + passwordResetApi({ instance, email }) + .then(({ status }) => { + this.isPending = false + this.user.email = '' - if (status === 204) { - this.success = true - this.error = null - } else if (status === 429) { - this.throttled = true - this.error = this.$t('password_reset.too_many_requests') - } - }).catch(() => { - this.isPending = false - this.user.email = '' - this.error = this.$t('general.generic_error') - }) - } - } + if (status === 204) { + this.success = true + this.error = null + } else if (status === 429) { + this.throttled = true + this.error = this.$t('password_reset.too_many_requests') + } + }) + .catch(() => { + this.isPending = false + this.user.email = '' + this.error = this.$t('general.generic_error') + }) + }, + }, } export default passwordReset diff --git a/src/components/pinch_zoom/pinch_zoom.js b/src/components/pinch_zoom/pinch_zoom.js index 82670ddfa..448f90e4b 100644 --- a/src/components/pinch_zoom/pinch_zoom.js +++ b/src/components/pinch_zoom/pinch_zoom.js @@ -2,12 +2,12 @@ import PinchZoom from '@kazvmoe-infra/pinch-zoom-element' export default { methods: { - setTransform ({ scale, x, y }) { + setTransform({ scale, x, y }) { this.$el.setTransform({ scale, x, y }) - } + }, }, - created () { + created() { // Make lint happy - (() => PinchZoom)() - } + ;(() => PinchZoom)() + }, } diff --git a/src/components/poll/poll.js b/src/components/poll/poll.js index a1b7808f2..594a7be88 100644 --- a/src/components/poll/poll.js +++ b/src/components/poll/poll.js @@ -1,8 +1,9 @@ -import Timeago from 'components/timeago/timeago.vue' -import genRandomSeed from '../../services/random_seed/random_seed.service.js' -import RichContent from 'components/rich_content/rich_content.jsx' import Checkbox from 'components/checkbox/checkbox.vue' +import RichContent from 'components/rich_content/rich_content.jsx' +import Timeago from 'components/timeago/timeago.vue' + import { usePollsStore } from 'src/stores/polls' +import genRandomSeed from '../../services/random_seed/random_seed.service.js' export default { name: 'Poll', @@ -10,83 +11,85 @@ export default { components: { Timeago, RichContent, - Checkbox + Checkbox, }, - data () { + data() { return { loading: false, choices: [], - randomSeed: genRandomSeed() + randomSeed: genRandomSeed(), } }, - created () { + created() { if (!usePollsStore().pollsObject[this.pollId]) { usePollsStore().mergeOrAddPoll(this.basePoll) } usePollsStore().trackPoll(this.pollId) }, - unmounted () { + unmounted() { usePollsStore().untrackPoll(this.pollId) }, computed: { - pollId () { + pollId() { return this.basePoll.id }, - poll () { + poll() { const storePoll = usePollsStore().pollsObject[this.pollId] return storePoll || {} }, - options () { + options() { return (this.poll && this.poll.options) || [] }, - expiresAt () { + expiresAt() { return (this.poll && this.poll.expires_at) || null }, - expired () { + expired() { return (this.poll && this.poll.expired) || false }, - expirationLabel () { + expirationLabel() { if (this.$store.getters.mergedConfig.useAbsoluteTimeFormat) { return this.expired ? 'polls.expired_at' : 'polls.expires_at' } else { return this.expired ? 'polls.expired' : 'polls.expires_in' } }, - loggedIn () { + loggedIn() { return this.$store.state.users.currentUser }, - showResults () { + showResults() { return this.poll.voted || this.expired || !this.loggedIn }, - totalVotesCount () { + totalVotesCount() { return this.poll.votes_count }, - containerClass () { + containerClass() { return { - loading: this.loading + loading: this.loading, } }, - choiceIndices () { + choiceIndices() { // Convert array of booleans into an array of indices of the // items that were 'true', so [true, false, false, true] becomes // [0, 3]. return this.choices .map((entry, index) => entry && index) - .filter(value => typeof value === 'number') + .filter((value) => typeof value === 'number') }, - isDisabled () { + isDisabled() { const noChoice = this.choiceIndices.length === 0 return this.loading || noChoice - } + }, }, methods: { - percentageForOption (count) { - return this.totalVotesCount === 0 ? 0 : Math.round(count / this.totalVotesCount * 100) + percentageForOption(count) { + return this.totalVotesCount === 0 + ? 0 + : Math.round((count / this.totalVotesCount) * 100) }, - resultTitle (option) { + resultTitle(option) { return `${option.votes_count}/${this.totalVotesCount} ${this.$t('polls.votes')}` }, - activateOption (index, value) { + activateOption(index, value) { let result if (this.poll.multiple) { result = this.choices || this.options.map(() => false) @@ -96,17 +99,21 @@ export default { result[index] = value this.choices = result }, - optionId (index) { + optionId(index) { return `poll${this.poll.id}-${index}` }, - vote () { + vote() { if (this.choiceIndices.length === 0) return this.loading = true - usePollsStore().votePoll( - { id: this.statusId, pollId: this.poll.id, choices: this.choiceIndices } - ).then(() => { - this.loading = false - }) - } - } + usePollsStore() + .votePoll({ + id: this.statusId, + pollId: this.poll.id, + choices: this.choiceIndices, + }) + .then(() => { + this.loading = false + }) + }, + }, } diff --git a/src/components/poll/poll_form.js b/src/components/poll/poll_form.js index 7660db0c2..92b471314 100644 --- a/src/components/poll/poll_form.js +++ b/src/components/poll/poll_form.js @@ -1,35 +1,34 @@ import * as DateUtils from 'src/services/date_utils/date_utils.js' import { pollFallback } from 'src/services/poll/poll.service.js' -import { library } from '@fortawesome/fontawesome-svg-core' import Select from '../select/select.vue' -import { - faTimes, - faPlus -} from '@fortawesome/free-solid-svg-icons' -library.add( - faTimes, - faPlus -) +import { library } from '@fortawesome/fontawesome-svg-core' +import { faPlus, faTimes } from '@fortawesome/free-solid-svg-icons' + +library.add(faTimes, faPlus) export default { components: { - Select + Select, }, name: 'PollForm', props: { visible: {}, params: { type: Object, - required: true - } + required: true, + }, }, computed: { pollType: { - get () { return pollFallback(this.params, 'pollType') }, - set (newVal) { this.params.pollType = newVal } + get() { + return pollFallback(this.params, 'pollType') + }, + set(newVal) { + this.params.pollType = newVal + }, }, - options () { + options() { const hasOptions = !!this.params.options if (!hasOptions) { this.params.options = pollFallback(this.params, 'options') @@ -37,54 +36,62 @@ export default { return this.params.options }, expiryAmount: { - get () { return pollFallback(this.params, 'expiryAmount') }, - set (newVal) { this.params.expiryAmount = newVal } + get() { + return pollFallback(this.params, 'expiryAmount') + }, + set(newVal) { + this.params.expiryAmount = newVal + }, }, expiryUnit: { - get () { return pollFallback(this.params, 'expiryUnit') }, - set (newVal) { this.params.expiryUnit = newVal } + get() { + return pollFallback(this.params, 'expiryUnit') + }, + set(newVal) { + this.params.expiryUnit = newVal + }, }, - pollLimits () { + pollLimits() { return this.$store.state.instance.pollLimits }, - maxOptions () { + maxOptions() { return this.pollLimits.max_options }, - maxLength () { + maxLength() { return this.pollLimits.max_option_chars }, - expiryUnits () { + expiryUnits() { const allUnits = ['minutes', 'hours', 'days'] const expiry = this.convertExpiryFromUnit return allUnits.filter( - unit => this.pollLimits.max_expiration >= expiry(unit, 1) + (unit) => this.pollLimits.max_expiration >= expiry(unit, 1), ) }, - minExpirationInCurrentUnit () { + minExpirationInCurrentUnit() { return Math.ceil( this.convertExpiryToUnit( this.expiryUnit, - this.pollLimits.min_expiration - ) + this.pollLimits.min_expiration, + ), ) }, - maxExpirationInCurrentUnit () { + maxExpirationInCurrentUnit() { return Math.floor( this.convertExpiryToUnit( this.expiryUnit, - this.pollLimits.max_expiration - ) + this.pollLimits.max_expiration, + ), ) - } + }, }, methods: { - clear () { + clear() { this.pollType = 'single' this.options = ['', ''] this.expiryAmount = 10 this.expiryUnit = 'minutes' }, - nextOption (index) { + nextOption(index) { const element = this.$el.querySelector(`#poll-${index + 1}`) if (element) { element.focus() @@ -98,30 +105,34 @@ export default { } } }, - addOption () { + addOption() { if (this.options.length < this.maxOptions) { this.options.push('') return true } return false }, - deleteOption (index) { + deleteOption(index) { if (this.options.length > 2) { this.options.splice(index, 1) } }, - convertExpiryToUnit (unit, amount) { + convertExpiryToUnit(unit, amount) { // Note: we want seconds and not milliseconds return DateUtils.secondsToUnit(unit, amount) }, - convertExpiryFromUnit (unit, amount) { + convertExpiryFromUnit(unit, amount) { return DateUtils.unitToSeconds(unit, amount) }, - expiryAmountChange () { - this.expiryAmount = - Math.max(this.minExpirationInCurrentUnit, this.expiryAmount) - this.expiryAmount = - Math.min(this.maxExpirationInCurrentUnit, this.expiryAmount) - } - } + expiryAmountChange() { + this.expiryAmount = Math.max( + this.minExpirationInCurrentUnit, + this.expiryAmount, + ) + this.expiryAmount = Math.min( + this.maxExpirationInCurrentUnit, + this.expiryAmount, + ) + }, + }, } diff --git a/src/components/poll/poll_graph.style.js b/src/components/poll/poll_graph.style.js index 247a266a2..69e2fd3ad 100644 --- a/src/components/poll/poll_graph.style.js +++ b/src/components/poll/poll_graph.style.js @@ -5,8 +5,8 @@ export default { { directives: { background: '--accent', - opacity: 0.5 - } - } - ] + opacity: 0.5, + }, + }, + ], } diff --git a/src/components/popover.style.js b/src/components/popover.style.js index 455b5f5e0..22c3e43d2 100644 --- a/src/components/popover.style.js +++ b/src/components/popover.style.js @@ -3,25 +3,25 @@ export default { selector: '.popover', lazy: true, variants: { - modal: '.modal' + modal: '.modal', }, - validInnerComponents: [ - 'MenuItem' - ], + validInnerComponents: ['MenuItem'], defaultRules: [ { directives: { background: '--bg', blur: '10px', - shadow: [{ - x: 2, - y: 2, - blur: 3, - spread: 0, - color: '#000000', - alpha: 0.5 - }] - } - } - ] + shadow: [ + { + x: 2, + y: 2, + blur: 3, + spread: 0, + color: '#000000', + alpha: 0.5, + }, + ], + }, + }, + ], } diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js index e8e857918..3b83bd58f 100644 --- a/src/components/popover/popover.js +++ b/src/components/popover/popover.js @@ -57,15 +57,16 @@ const Popover = { triggerAttrs: { type: Object, - default: {} - } + default: {}, + }, }, - inject: { // override popover z layer + inject: { + // override popover z layer popoversZLayer: { - default: '' - } + default: '', + }, }, - data () { + data() { return { // lockReEntry is a flag that is set when mouse cursor is leaving the popover's content // so that if mouse goes back into popover it won't be re-shown again to prevent annoyance @@ -83,14 +84,16 @@ const Popover = { graceTimeout: null, parentPopover: null, disableClickOutside: false, - childrenShown: new Set() + childrenShown: new Set(), } }, computed: { - allTriggerAttrs () { + allTriggerAttrs() { if (process.env.NODE_ENV === 'development') { if ('aria-hidden' in this.triggerAttrs) { - throw new Error('Do not use aria-hidden in triggerAttrs. Instead set hideTrigger to true') + throw new Error( + 'Do not use aria-hidden in triggerAttrs. Instead set hideTrigger to true', + ) } } @@ -104,18 +107,20 @@ const Popover = { } return attrs - } + }, }, methods: { - setAnchorEl (el) { + setAnchorEl(el) { this.anchorEl = el this.updateStyles() }, - containerBoundingClientRect () { - const container = this.boundToSelector ? this.$el.closest(this.boundToSelector) : this.$el.offsetParent + containerBoundingClientRect() { + const container = this.boundToSelector + ? this.$el.closest(this.boundToSelector) + : this.$el.offsetParent return container.getBoundingClientRect() }, - updateStyles () { + updateStyles() { if (this.hidden) { this.styles = {} return @@ -123,7 +128,10 @@ const Popover = { // Popover will be anchored around this element, trigger ref is the container, so // its children are what are inside the slot. Expect only one v-slot:trigger. - const anchorEl = this.anchorEl || (this.$refs.trigger && this.$refs.trigger.children[0]) || this.$el + const anchorEl = + this.anchorEl || + (this.$refs.trigger && this.$refs.trigger.children[0]) || + this.$el // SVGs don't have offsetWidth/Height, use fallback const anchorHeight = anchorEl.offsetHeight || anchorEl.clientHeight const anchorWidth = anchorEl.offsetWidth || anchorEl.clientWidth @@ -138,7 +146,7 @@ const Popover = { // Screen position of the origin point for popover = center of the anchor const origin = { x: anchorScreenBox.left + anchorWidth * 0.5, - y: anchorScreenBox.top + anchorHeight * 0.5 + y: anchorScreenBox.top + anchorHeight * 0.5, } const content = this.$refs.content const overlayCenter = this.overlayCenters @@ -146,7 +154,8 @@ const Popover = { : null // Minor optimization, don't call a slow reflow call if we don't have to - const parentScreenBox = this.boundTo && + const parentScreenBox = + this.boundTo && (this.boundTo.x === 'container' || this.boundTo.y === 'container') && this.containerBoundingClientRect() @@ -154,25 +163,27 @@ const Popover = { // What are the screen bounds for the popover? Viewport vs container // when using viewport, using default margin values to dodge the navbar - const xBounds = this.boundTo && this.boundTo.x === 'container' - ? { - min: parentScreenBox.left + (margin.left || 0), - max: parentScreenBox.right - (margin.right || 0) - } - : { - min: 0 + (margin.left || 10), - max: window.innerWidth - (margin.right || 10) - } + const xBounds = + this.boundTo && this.boundTo.x === 'container' + ? { + min: parentScreenBox.left + (margin.left || 0), + max: parentScreenBox.right - (margin.right || 0), + } + : { + min: 0 + (margin.left || 10), + max: window.innerWidth - (margin.right || 10), + } - const yBounds = this.boundTo && this.boundTo.y === 'container' - ? { - min: parentScreenBox.top + (margin.top || 0), - max: parentScreenBox.bottom - (margin.bottom || 0) - } - : { - min: 0 + (margin.top || 50), - max: window.innerHeight - (margin.bottom || 5) - } + const yBounds = + this.boundTo && this.boundTo.y === 'container' + ? { + min: parentScreenBox.top + (margin.top || 0), + max: parentScreenBox.bottom - (margin.bottom || 0), + } + : { + min: 0 + (margin.top || 50), + max: window.innerHeight - (margin.bottom || 5), + } let horizOffset = 0 let vertOffset = 0 @@ -227,8 +238,12 @@ const Popover = { // Handle special cases, first force to displaying on top if there's no space on bottom, // regardless of what placement value was. Then check if there's no space on top, and // force to bottom, again regardless of what placement value was. - const topBoundary = origin.y - anchorHeight * 0.5 + (this.removePadding ? topPadding : 0) - const bottomBoundary = origin.y + anchorHeight * 0.5 - (this.removePadding ? bottomPadding : 0) + const topBoundary = + origin.y - anchorHeight * 0.5 + (this.removePadding ? topPadding : 0) + const bottomBoundary = + origin.y + + anchorHeight * 0.5 - + (this.removePadding ? bottomPadding : 0) if (bottomBoundary + content.offsetHeight > yBounds.max) usingTop = true if (topBoundary - content.offsetHeight < yBounds.min) usingTop = false @@ -246,8 +261,10 @@ const Popover = { // Handle special cases, first force to displaying on left if there's no space on right, // regardless of what placement value was. Then check if there's no space on right, and // force to left, again regardless of what placement value was. - const leftBoundary = origin.x - anchorWidth * 0.5 + (this.removePadding ? leftPadding : 0) - const rightBoundary = origin.x + anchorWidth * 0.5 - (this.removePadding ? rightPadding : 0) + const leftBoundary = + origin.x - anchorWidth * 0.5 + (this.removePadding ? leftPadding : 0) + const rightBoundary = + origin.x + anchorWidth * 0.5 - (this.removePadding ? rightPadding : 0) if (rightBoundary + content.offsetWidth > xBounds.max) usingLeft = true if (leftBoundary - content.offsetWidth < xBounds.min) usingLeft = false @@ -262,17 +279,18 @@ const Popover = { this.styles = { left: `${Math.round(translateX)}px`, - top: `${Math.round(translateY)}px` + top: `${Math.round(translateY)}px`, } if (this.popoversZLayer) { - this.styles['--ZI_popover_override'] = `var(--ZI_${this.popoversZLayer}_popovers)` + this.styles['--ZI_popover_override'] = + `var(--ZI_${this.popoversZLayer}_popovers)` } if (parentScreenBox) { this.styles.maxWidth = `${Math.round(parentScreenBox.width)}px` } }, - showPopover () { + showPopover() { if (this.disabled) return this.disableClickOutside = true setTimeout(() => { @@ -291,7 +309,7 @@ const Popover = { this.updateStyles() }) }, - hidePopover () { + hidePopover() { if (this.disabled) return if (!this.hidden) this.$emit('close') this.hidden = true @@ -302,12 +320,12 @@ const Popover = { this.scrollable?.removeEventListener('scroll', this.onScroll) this.scrollable?.removeEventListener('resize', this.onResize) }, - resizePopover () { + resizePopover() { setTimeout(() => { this.updateStyles() }, 1) }, - onMouseenter () { + onMouseenter() { if (this.trigger === 'hover') { this.lockReEntry = false clearTimeout(this.graceTimeout) @@ -315,12 +333,12 @@ const Popover = { this.showPopover() } }, - onMouseleave () { + onMouseleave() { if (this.trigger === 'hover' && this.childrenShown.size === 0) { this.graceTimeout = setTimeout(() => this.hidePopover(), 1) } }, - onMouseenterContent () { + onMouseenterContent() { if (this.trigger === 'hover' && !this.lockReEntry) { this.lockReEntry = true clearTimeout(this.graceTimeout) @@ -328,12 +346,12 @@ const Popover = { this.showPopover() } }, - onMouseleaveContent () { + onMouseleaveContent() { if (this.trigger === 'hover' && this.childrenShown.size === 0) { this.graceTimeout = setTimeout(() => this.hidePopover(), 1) } }, - onClick () { + onClick() { if (this.trigger === 'click') { if (this.hidden) { this.showPopover() @@ -342,7 +360,7 @@ const Popover = { } } }, - onClickOutside (e) { + onClickOutside(e) { if (this.disableClickOutside) return if (this.hidden) return if (this.$refs.content && this.$refs.content.contains(e.target)) return @@ -351,35 +369,42 @@ const Popover = { this.hidePopover() if (this.parentPopover) this.parentPopover.onClickOutside(e) }, - onScroll () { + onScroll() { this.updateStyles() }, - onResize () { + onResize() { const content = this.$refs.content if (!content) return - if (this.oldSize.width !== content.offsetWidth || this.oldSize.height !== content.offsetHeight) { + if ( + this.oldSize.width !== content.offsetWidth || + this.oldSize.height !== content.offsetHeight + ) { this.updateStyles() - this.oldSize = { width: content.offsetWidth, height: content.offsetHeight } + this.oldSize = { + width: content.offsetWidth, + height: content.offsetHeight, + } } }, - onChildPopoverState (childRef, state) { + onChildPopoverState(childRef, state) { if (state) { this.childrenShown.add(childRef) } else { this.childrenShown.delete(childRef) } - } + }, }, - updated () { + updated() { // Monitor changes to content size, update styles only when content sizes have changed, // that should be the only time we need to move the popover box if we don't care about scroll // or resize this.onResize() }, - mounted () { + mounted() { this.teleport = true - let scrollable = this.$refs.trigger.closest('.column.-scrollable') || - this.$refs.trigger.closest('.mobile-notifications') + let scrollable = + this.$refs.trigger.closest('.column.-scrollable') || + this.$refs.trigger.closest('.mobile-notifications') if (!scrollable) scrollable = window this.scrollable = scrollable let parent = this.$parent @@ -388,9 +413,9 @@ const Popover = { } this.parentPopover = parent }, - beforeUnmount () { + beforeUnmount() { this.hidePopover() - } + }, } export default Popover diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 238be88df..396aa94f0 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -1,41 +1,41 @@ -import statusPoster from '../../services/status_poster/status_poster.service.js' -import genRandomSeed from '../../services/random_seed/random_seed.service.js' -import MediaUpload from '../media_upload/media_upload.vue' -import ScopeSelector from '../scope_selector/scope_selector.vue' -import EmojiInput from '../emoji_input/emoji_input.vue' -import PollForm from '../poll/poll_form.vue' -import Attachment from '../attachment/attachment.vue' +import { debounce, map, reject, uniqBy } from 'lodash' +import { mapActions, mapState } from 'pinia' +import { mapGetters } from 'vuex' + +import DraftCloser from 'src/components/draft_closer/draft_closer.vue' import Gallery from 'src/components/gallery/gallery.vue' -import StatusContent from '../status_content/status_content.vue' import Popover from 'src/components/popover/popover.vue' +import { pollFormToMasto } from 'src/services/poll/poll.service.js' +import { useInterfaceStore } from 'src/stores/interface.js' +import { useMediaViewerStore } from 'src/stores/media_viewer.js' +import { propsToNative } from '../../services/attributes_helper/attributes_helper.service.js' import fileTypeService from '../../services/file_type/file_type.service.js' import { findOffset } from '../../services/offset_finder/offset_finder.service.js' -import { propsToNative } from '../../services/attributes_helper/attributes_helper.service.js' -import { pollFormToMasto } from 'src/services/poll/poll.service.js' -import { reject, map, uniqBy, debounce } from 'lodash' -import suggestor from '../emoji_input/suggestor.js' -import { mapGetters } from 'vuex' -import { mapState, mapActions } from 'pinia' +import genRandomSeed from '../../services/random_seed/random_seed.service.js' +import statusPoster from '../../services/status_poster/status_poster.service.js' +import Attachment from '../attachment/attachment.vue' import Checkbox from '../checkbox/checkbox.vue' +import EmojiInput from '../emoji_input/emoji_input.vue' +import suggestor from '../emoji_input/suggestor.js' +import MediaUpload from '../media_upload/media_upload.vue' +import PollForm from '../poll/poll_form.vue' +import ScopeSelector from '../scope_selector/scope_selector.vue' import Select from '../select/select.vue' -import DraftCloser from 'src/components/draft_closer/draft_closer.vue' +import StatusContent from '../status_content/status_content.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { - faSmileBeam, - faPollH, - faUpload, faBan, - faTimes, - faCircleNotch, faChevronDown, faChevronLeft, - faChevronRight + faChevronRight, + faCircleNotch, + faPollH, + faSmileBeam, + faTimes, + faUpload, } from '@fortawesome/free-solid-svg-icons' -import { useInterfaceStore } from 'src/stores/interface.js' -import { useMediaViewerStore } from 'src/stores/media_viewer.js' - library.add( faSmileBeam, faPollH, @@ -45,7 +45,7 @@ library.add( faCircleNotch, faChevronDown, faChevronLeft, - faChevronRight + faChevronRight, ) const buildMentionsString = ({ user, attentions = [] }, currentUser) => { @@ -117,7 +117,7 @@ const PostStatusForm = { 'emojiPickerPlacement', 'optimisticPosting', 'profileMention', - 'draftId' + 'draftId', ], emits: [ 'posted', @@ -126,7 +126,7 @@ const PostStatusForm = { 'mediaplay', 'mediapause', 'can-close', - 'update' + 'update', ], components: { MediaUpload, @@ -139,9 +139,9 @@ const PostStatusForm = { StatusContent, Gallery, DraftCloser, - Popover + Popover, }, - mounted () { + mounted() { this.updateIdempotencyKey() this.resize(this.$refs.textarea) @@ -154,28 +154,41 @@ const PostStatusForm = { this.$refs.textarea.focus() } }, - data () { + data() { const preset = this.$route.query.message let statusText = preset || '' const { scopeCopy } = this.$store.getters.mergedConfig - const [statusType, refId] = typeAndRefId({ replyTo: this.replyTo, profileMention: this.profileMention && this.repliedUser?.id, statusId: this.statusId }) + const [statusType, refId] = typeAndRefId({ + replyTo: this.replyTo, + profileMention: this.profileMention && this.repliedUser?.id, + statusId: this.statusId, + }) // If we are starting a new post, do not associate it with old drafts - let statusParams = !this.disableDraft && (this.draftId || statusType !== 'new') ? this.getDraft(statusType, refId) : null + let statusParams = + !this.disableDraft && (this.draftId || statusType !== 'new') + ? this.getDraft(statusType, refId) + : null if (!statusParams) { if (statusType === 'reply' || statusType === 'mention') { const currentUser = this.$store.state.users.currentUser - statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser) + statusText = buildMentionsString( + { user: this.repliedUser, attentions: this.attentions }, + currentUser, + ) } - const scope = ((this.copyMessageScope && scopeCopy) || this.copyMessageScope === 'direct') - ? this.copyMessageScope - : this.$store.state.users.currentUser.default_scope + const scope = + (this.copyMessageScope && scopeCopy) || + this.copyMessageScope === 'direct' + ? this.copyMessageScope + : this.$store.state.users.currentUser.default_scope - const { postContentType: contentType, sensitiveByDefault } = this.$store.getters.mergedConfig + const { postContentType: contentType, sensitiveByDefault } = + this.$store.getters.mergedConfig statusParams = { type: statusType, @@ -189,7 +202,7 @@ const PostStatusForm = { mediaDescriptions: {}, visibility: scope, contentType, - quoting: false + quoting: false, } if (statusType === 'edit') { @@ -205,7 +218,7 @@ const PostStatusForm = { hasPoll: false, mediaDescriptions: this.statusMediaDescriptions || {}, visibility: this.statusScope || scope, - contentType: statusContentType + contentType: statusContentType, } } } @@ -226,101 +239,109 @@ const PostStatusForm = { emojiInputShown: false, idempotencyKey: '', saveInhibited: true, - saveable: false + saveable: false, } }, computed: { - users () { + users() { return this.$store.state.users.users }, - userDefaultScope () { + userDefaultScope() { return this.$store.state.users.currentUser.default_scope }, - showAllScopes () { + showAllScopes() { return !this.mergedConfig.minimalScopesMode }, - hideExtraActions () { + hideExtraActions() { return this.disableDraft || this.hideDraft }, - emojiUserSuggestor () { + emojiUserSuggestor() { return suggestor({ emoji: [ ...this.$store.getters.standardEmojiList, - ...this.$store.state.instance.customEmoji + ...this.$store.state.instance.customEmoji, ], - store: this.$store + store: this.$store, }) }, - emojiSuggestor () { + emojiSuggestor() { return suggestor({ emoji: [ ...this.$store.getters.standardEmojiList, - ...this.$store.state.instance.customEmoji - ] + ...this.$store.state.instance.customEmoji, + ], }) }, - emoji () { + emoji() { return this.$store.getters.standardEmojiList || [] }, - customEmoji () { + customEmoji() { return this.$store.state.instance.customEmoji || [] }, - statusLength () { + statusLength() { return this.newStatus.status.length }, - spoilerTextLength () { + spoilerTextLength() { return this.newStatus.spoilerText.length }, - statusLengthLimit () { + statusLengthLimit() { return this.$store.state.instance.textlimit }, - hasStatusLengthLimit () { + hasStatusLengthLimit() { return this.statusLengthLimit > 0 }, - charactersLeft () { - return this.statusLengthLimit - (this.statusLength + this.spoilerTextLength) + charactersLeft() { + return ( + this.statusLengthLimit - (this.statusLength + this.spoilerTextLength) + ) }, - isOverLengthLimit () { - return this.hasStatusLengthLimit && (this.charactersLeft < 0) + isOverLengthLimit() { + return this.hasStatusLengthLimit && this.charactersLeft < 0 }, - minimalScopesMode () { + minimalScopesMode() { return this.$store.state.instance.minimalScopesMode }, - alwaysShowSubject () { + alwaysShowSubject() { return this.mergedConfig.alwaysShowSubjectInput }, - postFormats () { + postFormats() { return this.$store.state.instance.postFormats || [] }, - safeDMEnabled () { + safeDMEnabled() { return this.$store.state.instance.safeDM }, - pollsAvailable () { - return this.$store.state.instance.pollsAvailable && + pollsAvailable() { + return ( + this.$store.state.instance.pollsAvailable && this.$store.state.instance.pollLimits.max_options >= 2 && this.disablePolls !== true + ) }, - hideScopeNotice () { - return this.disableNotice || this.$store.getters.mergedConfig.hideScopeNotice + hideScopeNotice() { + return ( + this.disableNotice || this.$store.getters.mergedConfig.hideScopeNotice + ) }, - pollContentError () { - return this.pollFormVisible && - this.newStatus.poll && - this.newStatus.poll.error + pollContentError() { + return ( + this.pollFormVisible && this.newStatus.poll && this.newStatus.poll.error + ) }, - showPreview () { + showPreview() { return !this.disablePreview && (!!this.preview || this.previewLoading) }, - emptyStatus () { - return this.newStatus.status.trim() === '' && this.newStatus.files.length === 0 + emptyStatus() { + return ( + this.newStatus.status.trim() === '' && this.newStatus.files.length === 0 + ) }, - uploadFileLimitReached () { + uploadFileLimitReached() { return this.newStatus.files.length >= this.fileLimit }, - isEdit () { + isEdit() { return typeof this.statusId !== 'undefined' && this.statusId.trim() !== '' }, - quotable () { + quotable() { if (!this.$store.state.instance.quotingAvailable) { return false } @@ -329,14 +350,17 @@ const PostStatusForm = { return false } - const repliedStatus = this.$store.state.statuses.allStatusesObject[this.replyTo] + const repliedStatus = + this.$store.state.statuses.allStatusesObject[this.replyTo] if (!repliedStatus) { return false } - if (repliedStatus.visibility === 'public' || - repliedStatus.visibility === 'unlisted' || - repliedStatus.visibility === 'local') { + if ( + repliedStatus.visibility === 'public' || + repliedStatus.visibility === 'unlisted' || + repliedStatus.visibility === 'local' + ) { return true } else if (repliedStatus.visibility === 'private') { return repliedStatus.user.id === this.$store.state.users.currentUser.id @@ -344,16 +368,16 @@ const PostStatusForm = { return false }, - debouncedMaybeAutoSaveDraft () { + debouncedMaybeAutoSaveDraft() { return debounce(this.maybeAutoSaveDraft, 3000) }, - pollFormVisible () { + pollFormVisible() { return this.newStatus.hasPoll }, - shouldAutoSaveDraft () { + shouldAutoSaveDraft() { return this.$store.getters.mergedConfig.autoSaveDraft }, - autoSaveState () { + autoSaveState() { if (this.saveable) { return this.$t('post_status.auto_save_saving') } else if (this.newStatus.id) { @@ -362,35 +386,39 @@ const PostStatusForm = { return this.$t('post_status.auto_save_nothing_new') } }, - safeToSaveDraft () { + safeToSaveDraft() { return ( - this.newStatus.status || - this.newStatus.spoilerText || - this.newStatus.files?.length || - this.newStatus.hasPoll - ) && this.saveable + (this.newStatus.status || + this.newStatus.spoilerText || + this.newStatus.files?.length || + this.newStatus.hasPoll) && + this.saveable + ) }, - hasEmptyDraft () { - return this.newStatus.id && !( - this.newStatus.status || + hasEmptyDraft() { + return ( + this.newStatus.id && + !( + this.newStatus.status || this.newStatus.spoilerText || this.newStatus.files?.length || this.newStatus.hasPoll + ) ) }, ...mapGetters(['mergedConfig']), ...mapState(useInterfaceStore, { - mobileLayout: store => store.mobileLayout - }) + mobileLayout: (store) => store.mobileLayout, + }), }, watch: { newStatus: { deep: true, - handler () { + handler() { this.statusChanged() - } + }, }, - saveable (val) { + saveable(val) { // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event#usage_notes // MDN says we'd better add the beforeunload event listener only when needed, and remove it when it's no longer needed if (val) { @@ -398,22 +426,22 @@ const PostStatusForm = { } else { this.removeBeforeUnloadListener() } - } + }, }, - beforeUnmount () { + beforeUnmount() { this.maybeAutoSaveDraft() this.removeBeforeUnloadListener() }, methods: { ...mapActions(useMediaViewerStore, ['increment']), - statusChanged () { + statusChanged() { this.autoPreview() this.updateIdempotencyKey() this.debouncedMaybeAutoSaveDraft() this.saveable = true this.saveInhibited = false }, - clearStatus () { + clearStatus() { const newStatus = this.newStatus this.saveInhibited = true this.newStatus = { @@ -425,7 +453,7 @@ const PostStatusForm = { poll: {}, hasPoll: false, mediaDescriptions: {}, - quoting: false + quoting: false, } this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile() this.clearPollForm() @@ -441,23 +469,36 @@ const PostStatusForm = { if (this.preview) this.previewStatus() this.saveable = false }, - async postStatus (event, newStatus) { - if (this.posting && !this.optimisticPosting) { return } - if (this.disableSubmit) { return } - if (this.emojiInputShown) { return } + async postStatus(event, newStatus) { + if (this.posting && !this.optimisticPosting) { + return + } + if (this.disableSubmit) { + return + } + if (this.emojiInputShown) { + return + } if (this.submitOnEnter) { event.stopPropagation() event.preventDefault() } - if (this.optimisticPosting && (this.emptyStatus || this.isOverLengthLimit)) { return } + if ( + this.optimisticPosting && + (this.emptyStatus || this.isOverLengthLimit) + ) { + return + } if (this.emptyStatus) { this.error = this.$t('post_status.empty_status_error') return } - const poll = this.newStatus.hasPoll ? pollFormToMasto(this.newStatus.poll) : {} + const poll = this.newStatus.hasPoll + ? pollFormToMasto(this.newStatus.poll) + : {} if (this.pollContentError) { this.error = this.pollContentError return @@ -473,7 +514,9 @@ const PostStatusForm = { return } - const replyOrQuoteAttr = newStatus.quoting ? 'quoteId' : 'inReplyToStatusId' + const replyOrQuoteAttr = newStatus.quoting + ? 'quoteId' + : 'inReplyToStatusId' const postingOptions = { status: newStatus.status, @@ -485,10 +528,12 @@ const PostStatusForm = { [replyOrQuoteAttr]: this.replyTo, contentType: newStatus.contentType, poll, - idempotencyKey: this.idempotencyKey + idempotencyKey: this.idempotencyKey, } - const postHandler = this.postHandler ? this.postHandler : statusPoster.postStatus + const postHandler = this.postHandler + ? this.postHandler + : statusPoster.postStatus postHandler(postingOptions).then((data) => { if (!data.error) { @@ -501,7 +546,7 @@ const PostStatusForm = { this.posting = false }) }, - previewStatus () { + previewStatus() { if (this.emptyStatus && this.newStatus.spoilerText.trim() === '') { this.preview = { error: this.$t('post_status.preview_empty') } this.previewLoading = false @@ -509,89 +554,100 @@ const PostStatusForm = { } const newStatus = this.newStatus this.previewLoading = true - const replyOrQuoteAttr = newStatus.quoting ? 'quoteId' : 'inReplyToStatusId' - statusPoster.postStatus({ - status: newStatus.status, - spoilerText: newStatus.spoilerText || null, - visibility: newStatus.visibility, - sensitive: newStatus.nsfw, - media: [], - store: this.$store, - [replyOrQuoteAttr]: this.replyTo, - contentType: newStatus.contentType, - poll: {}, - preview: true - }).then((data) => { - // Don't apply preview if not loading, because it means - // user has closed the preview manually. - if (!this.previewLoading) return - if (!data.error) { - this.preview = data - } else { - this.preview = { error: data.error } - } - }).catch((error) => { - this.preview = { error } - }).finally(() => { - this.previewLoading = false - }) + const replyOrQuoteAttr = newStatus.quoting + ? 'quoteId' + : 'inReplyToStatusId' + statusPoster + .postStatus({ + status: newStatus.status, + spoilerText: newStatus.spoilerText || null, + visibility: newStatus.visibility, + sensitive: newStatus.nsfw, + media: [], + store: this.$store, + [replyOrQuoteAttr]: this.replyTo, + contentType: newStatus.contentType, + poll: {}, + preview: true, + }) + .then((data) => { + // Don't apply preview if not loading, because it means + // user has closed the preview manually. + if (!this.previewLoading) return + if (!data.error) { + this.preview = data + } else { + this.preview = { error: data.error } + } + }) + .catch((error) => { + this.preview = { error } + }) + .finally(() => { + this.previewLoading = false + }) }, - debouncePreviewStatus: debounce(function () { this.previewStatus() }, 500), - autoPreview () { + debouncePreviewStatus: debounce(function () { + this.previewStatus() + }, 500), + autoPreview() { if (!this.preview) return this.previewLoading = true this.debouncePreviewStatus() }, - closePreview () { + closePreview() { this.preview = null this.previewLoading = false }, - togglePreview () { + togglePreview() { if (this.showPreview) { this.closePreview() } else { this.previewStatus() } }, - addMediaFile (fileInfo) { + addMediaFile(fileInfo) { this.newStatus.files.push(fileInfo) this.$emit('resize', { delayed: true }) }, - removeMediaFile (fileInfo) { + removeMediaFile(fileInfo) { const index = this.newStatus.files.indexOf(fileInfo) this.newStatus.files.splice(index, 1) this.$emit('resize') }, - editAttachment (fileInfo, newText) { + editAttachment(fileInfo, newText) { this.newStatus.mediaDescriptions[fileInfo.id] = newText }, - shiftUpMediaFile (fileInfo) { + shiftUpMediaFile(fileInfo) { const { files } = this.newStatus const index = this.newStatus.files.indexOf(fileInfo) files.splice(index, 1) files.splice(index - 1, 0, fileInfo) }, - shiftDnMediaFile (fileInfo) { + shiftDnMediaFile(fileInfo) { const { files } = this.newStatus const index = this.newStatus.files.indexOf(fileInfo) files.splice(index, 1) files.splice(index + 1, 0, fileInfo) }, - uploadFailed (errString, templateArgs) { + uploadFailed(errString, templateArgs) { templateArgs = templateArgs || {} - this.error = this.$t('upload.error.base') + ' ' + this.$t('upload.error.' + errString, templateArgs) + this.error = + this.$t('upload.error.base') + + ' ' + + this.$t('upload.error.' + errString, templateArgs) }, - startedUploadingFiles () { + startedUploadingFiles() { this.uploadingFiles = true }, - finishedUploadingFiles () { + finishedUploadingFiles() { this.$emit('resize') this.uploadingFiles = false }, - type (fileInfo) { + type(fileInfo) { return fileTypeService.fileType(fileInfo.mimetype) }, - paste (e) { + paste(e) { this.autoPreview() this.resize(e) if (e.clipboardData.files.length > 0) { @@ -603,7 +659,7 @@ const PostStatusForm = { this.dropFiles = [e.clipboardData.files[0]] } }, - fileDrop (e) { + fileDrop(e) { if (e.dataTransfer && e.dataTransfer.types.includes('Files')) { e.preventDefault() // allow dropping text like before this.dropFiles = e.dataTransfer.files @@ -611,7 +667,7 @@ const PostStatusForm = { this.showDropIcon = 'hide' } }, - fileDragStop () { + fileDragStop() { // The false-setting is done with delay because just using leave-events // directly caused unwanted flickering, this is not perfect either but // much less noticable. @@ -619,21 +675,23 @@ const PostStatusForm = { this.showDropIcon = 'fade' this.dropStopTimeout = setTimeout(() => (this.showDropIcon = 'hide'), 500) }, - fileDrag (e) { + fileDrag(e) { e.dataTransfer.dropEffect = this.uploadFileLimitReached ? 'none' : 'copy' if (e.dataTransfer && e.dataTransfer.types.includes('Files')) { clearTimeout(this.dropStopTimeout) this.showDropIcon = 'show' } }, - onEmojiInputInput () { + onEmojiInputInput() { this.$nextTick(() => { this.resize(this.$refs.textarea) }) }, - resize (e) { + resize(e) { const target = e.target || e - if (!(target instanceof window.Element)) { return } + if (!(target instanceof window.Element)) { + return + } // Reset to default height for empty form, nothing else to do here. if (target.value === '') { @@ -648,12 +706,14 @@ const PostStatusForm = { * replies in notifs) or mobile post form. Note that getting and setting * scroll is different for `Window` and `Element`s */ - const bottomBottomPaddingStr = window.getComputedStyle(bottomRef)['padding-bottom'] + const bottomBottomPaddingStr = + window.getComputedStyle(bottomRef)['padding-bottom'] const bottomBottomPadding = pxStringToNumber(bottomBottomPaddingStr) - const scrollerRef = this.$el.closest('.column.-scrollable') || - this.$el.closest('.post-form-modal-view') || - window + const scrollerRef = + this.$el.closest('.column.-scrollable') || + this.$el.closest('.post-form-modal-view') || + window // Getting info about padding we have to account for, removing 'px' part const topPaddingStr = window.getComputedStyle(target)['padding-top'] @@ -682,18 +742,20 @@ const PostStatusForm = { */ // this part has to be BEFORE the content size update - const currentScroll = scrollerRef === window - ? scrollerRef.scrollY - : scrollerRef.scrollTop - const scrollerHeight = scrollerRef === window - ? scrollerRef.innerHeight - : scrollerRef.offsetHeight + const currentScroll = + scrollerRef === window ? scrollerRef.scrollY : scrollerRef.scrollTop + const scrollerHeight = + scrollerRef === window + ? scrollerRef.innerHeight + : scrollerRef.offsetHeight const scrollerBottomBorder = currentScroll + scrollerHeight // BEGIN content size update target.style.height = 'auto' const heightWithoutPadding = Math.floor(target.scrollHeight - vertPadding) - let newHeight = this.maxHeight ? Math.min(heightWithoutPadding, this.maxHeight) : heightWithoutPadding + let newHeight = this.maxHeight + ? Math.min(heightWithoutPadding, this.maxHeight) + : heightWithoutPadding // This is a bit of a hack to combat target.scrollHeight being different on every other input // on some browsers for whatever reason. Don't change the height if difference is 1px or less. if (Math.abs(newHeight - oldHeight) <= 1) { @@ -705,7 +767,10 @@ const PostStatusForm = { // We check where the bottom border of form-bottom element is, this uses findOffset // to find offset relative to scrollable container (scroller) - const bottomBottomBorder = bottomRef.offsetHeight + findOffset(bottomRef, scrollerRef).top + bottomBottomPadding + const bottomBottomBorder = + bottomRef.offsetHeight + + findOffset(bottomRef, scrollerRef).top + + bottomBottomPadding const isBottomObstructed = scrollerBottomBorder < bottomBottomBorder const isFormBiggerThanScroller = scrollerHeight < formRef.offsetHeight @@ -714,9 +779,13 @@ const PostStatusForm = { // Keep form-bottom always visible so that submit button is in view EXCEPT // if form element bigger than scroller and caret isn't at the end, so that // if you scroll up and edit middle of text you won't get scrolled back to bottom - const shouldScrollToBottom = isBottomObstructed && - !(isFormBiggerThanScroller && - this.$refs.textarea.selectionStart !== this.$refs.textarea.value.length) + const shouldScrollToBottom = + isBottomObstructed && + !( + isFormBiggerThanScroller && + this.$refs.textarea.selectionStart !== + this.$refs.textarea.value.length + ) const totalDelta = shouldScrollToBottom ? bottomChangeDelta : 0 const targetScroll = Math.round(currentScroll + totalDelta) @@ -726,53 +795,60 @@ const PostStatusForm = { scrollerRef.scrollTop = targetScroll } }, - clearError () { + clearError() { this.error = null }, - changeVis (visibility) { + changeVis(visibility) { this.newStatus.visibility = visibility }, - togglePollForm () { + togglePollForm() { this.newStatus.hasPoll = !this.newStatus.hasPoll }, - setPoll (poll) { + setPoll(poll) { this.newStatus.poll = poll }, - clearPollForm () { + clearPollForm() { if (this.$refs.pollForm) { this.$refs.pollForm.clear() } }, - dismissScopeNotice () { - this.$store.dispatch('setOption', { name: 'hideScopeNotice', value: true }) + dismissScopeNotice() { + this.$store.dispatch('setOption', { + name: 'hideScopeNotice', + value: true, + }) }, - setMediaDescription (id) { + setMediaDescription(id) { const description = this.newStatus.mediaDescriptions[id] if (!description || description.trim() === '') return - return statusPoster.setMediaDescription({ store: this.$store, id, description }) + return statusPoster.setMediaDescription({ + store: this.$store, + id, + description, + }) }, - setAllMediaDescriptions () { - const ids = this.newStatus.files.map(file => file.id) - return Promise.all(ids.map(id => this.setMediaDescription(id))) + setAllMediaDescriptions() { + const ids = this.newStatus.files.map((file) => file.id) + return Promise.all(ids.map((id) => this.setMediaDescription(id))) }, - handleEmojiInputShow (value) { + handleEmojiInputShow(value) { this.emojiInputShown = value }, - updateIdempotencyKey () { + updateIdempotencyKey() { this.idempotencyKey = Date.now().toString() }, - openProfileTab () { + openProfileTab() { useInterfaceStore().openSettingsModalTab('profile') }, - propsToNative (props) { + propsToNative(props) { return propsToNative(props) }, - saveDraft () { - if (!this.disableDraft && - !this.saveInhibited) { + saveDraft() { + if (!this.disableDraft && !this.saveInhibited) { if (this.safeToSaveDraft) { - return this.$store.dispatch('addOrSaveDraft', { draft: this.newStatus }) - .then(id => { + return this.$store + .dispatch('addOrSaveDraft', { draft: this.newStatus }) + .then((id) => { if (this.newStatus.id !== id) { this.newStatus.id = id } @@ -784,32 +860,34 @@ const PostStatusForm = { }) } else if (this.hasEmptyDraft) { // There is a draft, but there is nothing in it, clear it - return this.abandonDraft() - .then(() => { - this.saveable = false - if (!this.shouldAutoSaveDraft) { - this.clearStatus() - this.$emit('draft-done') - } - }) + return this.abandonDraft().then(() => { + this.saveable = false + if (!this.shouldAutoSaveDraft) { + this.clearStatus() + this.$emit('draft-done') + } + }) } } return Promise.resolve() }, - maybeAutoSaveDraft () { + maybeAutoSaveDraft() { if (this.shouldAutoSaveDraft) { this.saveDraft(false) } }, - abandonDraft () { + abandonDraft() { return this.$store.dispatch('abandonDraft', { id: this.newStatus.id }) }, - getDraft (statusType, refId) { + getDraft(statusType, refId) { const maybeDraft = this.$store.state.drafts.drafts[this.draftId] if (this.draftId && maybeDraft) { return maybeDraft } else { - const existingDrafts = this.$store.getters.draftsByTypeAndRefId(statusType, refId) + const existingDrafts = this.$store.getters.draftsByTypeAndRefId( + statusType, + refId, + ) if (existingDrafts.length) { return existingDrafts[0] @@ -817,35 +895,35 @@ const PostStatusForm = { } // No draft available, fall back }, - requestClose () { + requestClose() { if (!this.saveable) { this.$emit('can-close') } else { this.$refs.draftCloser.requestClose() } }, - saveAndCloseDraft () { + saveAndCloseDraft() { this.saveDraft().then(() => { this.$emit('can-close') }) }, - discardAndCloseDraft () { + discardAndCloseDraft() { this.abandonDraft().then(() => { this.$emit('can-close') }) }, - addBeforeUnloadListener () { + addBeforeUnloadListener() { this._beforeUnloadListener ||= () => { this.saveDraft() } window.addEventListener('beforeunload', this._beforeUnloadListener) }, - removeBeforeUnloadListener () { + removeBeforeUnloadListener() { if (this._beforeUnloadListener) { window.removeEventListener('beforeunload', this._beforeUnloadListener) } - } - } + }, + }, } export default PostStatusForm diff --git a/src/components/post_status_modal/post_status_modal.js b/src/components/post_status_modal/post_status_modal.js index ee6cd6444..2c643e9db 100644 --- a/src/components/post_status_modal/post_status_modal.js +++ b/src/components/post_status_modal/post_status_modal.js @@ -1,34 +1,35 @@ -import PostStatusForm from '../post_status_form/post_status_form.vue' -import Modal from '../modal/modal.vue' import get from 'lodash/get' + import { usePostStatusStore } from 'src/stores/post_status' +import Modal from '../modal/modal.vue' +import PostStatusForm from '../post_status_form/post_status_form.vue' const PostStatusModal = { components: { PostStatusForm, - Modal + Modal, }, - data () { + data() { return { - resettingForm: false + resettingForm: false, } }, computed: { - isLoggedIn () { + isLoggedIn() { return !!this.$store.state.users.currentUser }, - modalActivated () { + modalActivated() { return usePostStatusStore().modalActivated }, - isFormVisible () { + isFormVisible() { return this.isLoggedIn && !this.resettingForm && this.modalActivated }, - params () { + params() { return usePostStatusStore().params || {} - } + }, }, watch: { - params (newVal, oldVal) { + params(newVal, oldVal) { if (get(newVal, 'repliedUser.id') !== get(oldVal, 'repliedUser.id')) { this.resettingForm = true this.$nextTick(() => { @@ -36,21 +37,23 @@ const PostStatusModal = { }) } }, - isFormVisible (val) { + isFormVisible(val) { if (val) { - this.$nextTick(() => this.$el && this.$el.querySelector('textarea').focus()) + this.$nextTick( + () => this.$el && this.$el.querySelector('textarea').focus(), + ) } - } + }, }, methods: { - closeModal () { + closeModal() { usePostStatusStore().closePostStatusModal() }, - resetAndClose () { + resetAndClose() { usePostStatusStore().resetPostStatusModal() usePostStatusStore().closePostStatusModal() - } - } + }, + }, } export default PostStatusModal diff --git a/src/components/progress_button/progress_button.vue b/src/components/progress_button/progress_button.vue index 283a51af5..318260409 100644 --- a/src/components/progress_button/progress_button.vue +++ b/src/components/progress_button/progress_button.vue @@ -16,23 +16,26 @@ export default { props: { disabled: { - type: Boolean + type: Boolean, }, - click: { // click event handler. Must return a promise + click: { + // click event handler. Must return a promise type: Function, - default: () => Promise.resolve() - } + default: () => Promise.resolve(), + }, }, - data () { + data() { return { - progress: false + progress: false, } }, methods: { - onClick () { + onClick() { this.progress = true - this.click().then(() => { this.progress = false }) - } - } + this.click().then(() => { + this.progress = false + }) + }, + }, } diff --git a/src/components/public_and_external_timeline/public_and_external_timeline.js b/src/components/public_and_external_timeline/public_and_external_timeline.js index bfcce6ae6..b6e53a4cf 100644 --- a/src/components/public_and_external_timeline/public_and_external_timeline.js +++ b/src/components/public_and_external_timeline/public_and_external_timeline.js @@ -1,17 +1,22 @@ import Timeline from '../timeline/timeline.vue' + const PublicAndExternalTimeline = { components: { - Timeline + Timeline, }, computed: { - timeline () { return this.$store.state.statuses.timelines.publicAndExternal } + timeline() { + return this.$store.state.statuses.timelines.publicAndExternal + }, }, - created () { - this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' }) + created() { + this.$store.dispatch('startFetchingTimeline', { + timeline: 'publicAndExternal', + }) }, - unmounted () { + unmounted() { this.$store.dispatch('stopFetchingTimeline', 'publicAndExternal') - } + }, } export default PublicAndExternalTimeline diff --git a/src/components/public_timeline/public_timeline.js b/src/components/public_timeline/public_timeline.js index 306935445..b9d136e29 100644 --- a/src/components/public_timeline/public_timeline.js +++ b/src/components/public_timeline/public_timeline.js @@ -1,18 +1,20 @@ import Timeline from '../timeline/timeline.vue' + const PublicTimeline = { components: { - Timeline + Timeline, }, computed: { - timeline () { return this.$store.state.statuses.timelines.public } + timeline() { + return this.$store.state.statuses.timelines.public + }, }, - created () { + created() { this.$store.dispatch('startFetchingTimeline', { timeline: 'public' }) }, - unmounted () { + unmounted() { this.$store.dispatch('stopFetchingTimeline', 'public') - } - + }, } export default PublicTimeline diff --git a/src/components/quick_filter_settings/quick_filter_settings.js b/src/components/quick_filter_settings/quick_filter_settings.js index f5e4721d0..3572dbb18 100644 --- a/src/components/quick_filter_settings/quick_filter_settings.js +++ b/src/components/quick_filter_settings/quick_filter_settings.js @@ -1,99 +1,132 @@ -import Popover from '../popover/popover.vue' -import { mapGetters } from 'vuex' import { mapState } from 'pinia' +import { mapGetters } from 'vuex' + +import { useInterfaceStore } from 'src/stores/interface' +import Popover from '../popover/popover.vue' + import { library } from '@fortawesome/fontawesome-svg-core' import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons' -import { useInterfaceStore } from 'src/stores/interface' -library.add( - faFilter, - faFont, - faWrench -) +library.add(faFilter, faFont, faWrench) const QuickFilterSettings = { props: { conversation: Boolean, - nested: Boolean + nested: Boolean, }, components: { - Popover + Popover, }, methods: { - setReplyVisibility (visibility) { - this.$store.dispatch('setOption', { name: 'replyVisibility', value: visibility }) + setReplyVisibility(visibility) { + this.$store.dispatch('setOption', { + name: 'replyVisibility', + value: visibility, + }) this.$store.dispatch('queueFlushAll') }, - openTab (tab) { + openTab(tab) { useInterfaceStore().openSettingsModalTab(tab) - } + }, }, computed: { ...mapGetters(['mergedConfig']), ...mapState(useInterfaceStore, { - mobileLayout: state => state.layoutType === 'mobile' + mobileLayout: (state) => state.layoutType === 'mobile', }), - triggerAttrs () { + triggerAttrs() { if (this.mobileLayout) { return {} } else { return { - title: this.$t('timeline.quick_filter_settings') + title: this.$t('timeline.quick_filter_settings'), } } }, - mainClass () { + mainClass() { if (this.mobileLayout) { return 'main-button' } else { return 'dropdown-item' } }, - loggedIn () { + loggedIn() { return !!this.$store.state.users.currentUser }, replyVisibilitySelf: { - get () { return this.mergedConfig.replyVisibility === 'self' }, - set () { this.setReplyVisibility('self') } + get() { + return this.mergedConfig.replyVisibility === 'self' + }, + set() { + this.setReplyVisibility('self') + }, }, replyVisibilityFollowing: { - get () { return this.mergedConfig.replyVisibility === 'following' }, - set () { this.setReplyVisibility('following') } + get() { + return this.mergedConfig.replyVisibility === 'following' + }, + set() { + this.setReplyVisibility('following') + }, }, replyVisibilityAll: { - get () { return this.mergedConfig.replyVisibility === 'all' }, - set () { this.setReplyVisibility('all') } + get() { + return this.mergedConfig.replyVisibility === 'all' + }, + set() { + this.setReplyVisibility('all') + }, }, hideMedia: { - get () { return this.mergedConfig.hideAttachments || this.mergedConfig.hideAttachmentsInConv }, - set () { + get() { + return ( + this.mergedConfig.hideAttachments || + this.mergedConfig.hideAttachmentsInConv + ) + }, + set() { const value = !this.hideMedia this.$store.dispatch('setOption', { name: 'hideAttachments', value }) - this.$store.dispatch('setOption', { name: 'hideAttachmentsInConv', value }) - } + this.$store.dispatch('setOption', { + name: 'hideAttachmentsInConv', + value, + }) + }, }, hideMutedPosts: { - get () { return this.mergedConfig.hideFilteredStatuses }, - set () { + get() { + return this.mergedConfig.hideFilteredStatuses + }, + set() { const value = !this.hideMutedPosts - this.$store.dispatch('setOption', { name: 'hideFilteredStatuses', value }) - } + this.$store.dispatch('setOption', { + name: 'hideFilteredStatuses', + value, + }) + }, }, muteBotStatuses: { - get () { return this.mergedConfig.muteBotStatuses }, - set () { + get() { + return this.mergedConfig.muteBotStatuses + }, + set() { const value = !this.muteBotStatuses this.$store.dispatch('setOption', { name: 'muteBotStatuses', value }) - } + }, }, muteSensitiveStatuses: { - get () { return this.mergedConfig.muteSensitiveStatuses }, - set () { + get() { + return this.mergedConfig.muteSensitiveStatuses + }, + set() { const value = !this.muteSensitiveStatuses - this.$store.dispatch('setOption', { name: 'muteSensitiveStatuses', value }) - } - } - } + this.$store.dispatch('setOption', { + name: 'muteSensitiveStatuses', + value, + }) + }, + }, + }, } export default QuickFilterSettings diff --git a/src/components/quick_view_settings/quick_view_settings.js b/src/components/quick_view_settings/quick_view_settings.js index 5c98e0f88..6c5a9f667 100644 --- a/src/components/quick_view_settings/quick_view_settings.js +++ b/src/components/quick_view_settings/quick_view_settings.js @@ -1,82 +1,110 @@ +import { mapState } from 'pinia' +import { mapGetters } from 'vuex' + import Popover from 'src/components/popover/popover.vue' import QuickFilterSettings from 'src/components/quick_filter_settings/quick_filter_settings.vue' -import { mapGetters } from 'vuex' -import { mapState } from 'pinia' -import { library } from '@fortawesome/fontawesome-svg-core' -import { faList, faFolderTree, faBars, faWrench } from '@fortawesome/free-solid-svg-icons' import { useInterfaceStore } from 'src/stores/interface' -library.add( - faList, - faFolderTree, +import { library } from '@fortawesome/fontawesome-svg-core' +import { faBars, - faWrench -) + faFolderTree, + faList, + faWrench, +} from '@fortawesome/free-solid-svg-icons' + +library.add(faList, faFolderTree, faBars, faWrench) const QuickViewSettings = { props: { - conversation: Boolean + conversation: Boolean, }, components: { Popover, - QuickFilterSettings + QuickFilterSettings, }, methods: { - setConversationDisplay (visibility) { - this.$store.dispatch('setOption', { name: 'conversationDisplay', value: visibility }) + setConversationDisplay(visibility) { + this.$store.dispatch('setOption', { + name: 'conversationDisplay', + value: visibility, + }) }, - openTab (tab) { + openTab(tab) { useInterfaceStore().openSettingsModalTab(tab) - } + }, }, computed: { ...mapGetters(['mergedConfig']), ...mapState(useInterfaceStore, { - mobileLayout: state => state.layoutType === 'mobile' + mobileLayout: (state) => state.layoutType === 'mobile', }), - loggedIn () { + loggedIn() { return !!this.$store.state.users.currentUser }, conversationDisplay: { - get () { return this.mergedConfig.conversationDisplay }, - set (newVal) { this.setConversationDisplay(newVal) } + get() { + return this.mergedConfig.conversationDisplay + }, + set(newVal) { + this.setConversationDisplay(newVal) + }, }, autoUpdate: { - get () { return this.mergedConfig.streaming }, - set () { + get() { + return this.mergedConfig.streaming + }, + set() { const value = !this.autoUpdate this.$store.dispatch('setOption', { name: 'streaming', value }) - } + }, }, collapseWithSubjects: { - get () { return this.mergedConfig.collapseMessageWithSubject }, - set () { + get() { + return this.mergedConfig.collapseMessageWithSubject + }, + set() { const value = !this.collapseWithSubjects - this.$store.dispatch('setOption', { name: 'collapseMessageWithSubject', value }) - } + this.$store.dispatch('setOption', { + name: 'collapseMessageWithSubject', + value, + }) + }, }, showUserAvatars: { - get () { return this.mergedConfig.mentionLinkShowAvatar }, - set () { + get() { + return this.mergedConfig.mentionLinkShowAvatar + }, + set() { const value = !this.showUserAvatars - this.$store.dispatch('setOption', { name: 'mentionLinkShowAvatar', value }) - } + this.$store.dispatch('setOption', { + name: 'mentionLinkShowAvatar', + value, + }) + }, }, muteBotStatuses: { - get () { return this.mergedConfig.muteBotStatuses }, - set () { + get() { + return this.mergedConfig.muteBotStatuses + }, + set() { const value = !this.muteBotStatuses this.$store.dispatch('setOption', { name: 'muteBotStatuses', value }) - } + }, }, muteSensitiveStatuses: { - get () { return this.mergedConfig.muteSensitiveStatuses }, - set () { + get() { + return this.mergedConfig.muteSensitiveStatuses + }, + set() { const value = !this.muteSensitiveStatuses - this.$store.dispatch('setOption', { name: 'muteSensitiveStatuses', value }) - } - } - } + this.$store.dispatch('setOption', { + name: 'muteSensitiveStatuses', + value, + }) + }, + }, + }, } export default QuickViewSettings diff --git a/src/components/quotes_timeline/quotes_timeline.js b/src/components/quotes_timeline/quotes_timeline.js index a5f42da56..b9b3e8f10 100644 --- a/src/components/quotes_timeline/quotes_timeline.js +++ b/src/components/quotes_timeline/quotes_timeline.js @@ -1,26 +1,36 @@ import Timeline from '../timeline/timeline.vue' const QuotesTimeline = { - created () { + created() { this.$store.commit('clearTimeline', { timeline: 'quotes' }) - this.$store.dispatch('startFetchingTimeline', { timeline: 'quotes', statusId: this.statusId }) + this.$store.dispatch('startFetchingTimeline', { + timeline: 'quotes', + statusId: this.statusId, + }) }, components: { - Timeline + Timeline, }, computed: { - statusId () { return this.$route.params.id }, - timeline () { return this.$store.state.statuses.timelines.quotes } + statusId() { + return this.$route.params.id + }, + timeline() { + return this.$store.state.statuses.timelines.quotes + }, }, watch: { - statusId () { + statusId() { this.$store.commit('clearTimeline', { timeline: 'quotes' }) - this.$store.dispatch('startFetchingTimeline', { timeline: 'quotes', statusId: this.statusId }) - } + this.$store.dispatch('startFetchingTimeline', { + timeline: 'quotes', + statusId: this.statusId, + }) + }, }, - unmounted () { + unmounted() { this.$store.dispatch('stopFetchingTimeline', 'quotes') - } + }, } export default QuotesTimeline diff --git a/src/components/range_input/range_input.vue b/src/components/range_input/range_input.vue index 2f8645c0b..91d3dcc3b 100644 --- a/src/components/range_input/range_input.vue +++ b/src/components/range_input/range_input.vue @@ -54,13 +54,22 @@ diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js index 53a678680..8d1081acb 100644 --- a/src/components/registration/registration.js +++ b/src/components/registration/registration.js @@ -1,13 +1,16 @@ import useVuelidate from '@vuelidate/core' import { required, requiredIf, sameAs } from '@vuelidate/validators' import { mapActions, mapState } from 'vuex' -import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue' -import localeService from '../../services/locale/locale.service.js' + import { DAY } from 'src/services/date_utils/date_utils.js' +import localeService from '../../services/locale/locale.service.js' +import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue' import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue' const registration = { - setup () { return { v$: useVuelidate() } }, + setup() { + return { v$: useVuelidate() } + }, data: () => ({ user: { email: '', @@ -17,15 +20,15 @@ const registration = { confirm: '', birthday: '', reason: '', - language: [''] + language: [''], }, - captcha: {} + captcha: {}, }), components: { InterfaceLanguageSwitcher, - TermsOfServicePanel + TermsOfServicePanel, }, - validations () { + validations() { return { user: { email: { required: requiredIf(() => this.accountActivationRequired) }, @@ -34,20 +37,23 @@ const registration = { password: { required }, confirm: { required, - sameAs: sameAs(this.user.password) + sameAs: sameAs(this.user.password), }, birthday: { required: requiredIf(() => this.birthdayRequired), - maxValue: value => { - return !this.birthdayRequired || new Date(value).getTime() <= this.birthdayMin.getTime() - } + maxValue: (value) => { + return ( + !this.birthdayRequired || + new Date(value).getTime() <= this.birthdayMin.getTime() + ) + }, }, reason: { required: requiredIf(() => this.accountApprovalRequired) }, - language: {} - } + language: {}, + }, } }, - created () { + created() { if ((!this.registrationOpen && !this.token) || this.signedIn) { this.$router.push({ name: 'root' }) } @@ -55,14 +61,16 @@ const registration = { this.setCaptcha() }, computed: { - token () { return this.$route.params.token }, - bioPlaceholder () { + token() { + return this.$route.params.token + }, + bioPlaceholder() { return this.replaceNewlines(this.$t('registration.bio_placeholder')) }, - reasonPlaceholder () { + reasonPlaceholder() { return this.replaceNewlines(this.$t('registration.reason_placeholder')) }, - birthdayMin () { + birthdayMin() { const minAge = this.birthdayMinAge const today = new Date() today.setUTCMilliseconds(0) @@ -73,12 +81,20 @@ const registration = { minDate.setTime(today.getTime() - minAge * DAY) return minDate }, - birthdayMinAttr () { + birthdayMinAttr() { return this.birthdayMin.toJSON().replace(/T.+$/, '') }, - birthdayMinFormatted () { - const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale) - return this.user.birthday && new Date(Date.parse(this.birthdayMin)).toLocaleDateString(browserLocale, { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' }) + birthdayMinFormatted() { + const browserLocale = localeService.internalToBrowserLocale( + this.$i18n.locale, + ) + return ( + this.user.birthday && + new Date(Date.parse(this.birthdayMin)).toLocaleDateString( + browserLocale, + { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' }, + ) + ) }, ...mapState({ registrationOpen: (state) => state.instance.registrationOpen, @@ -89,15 +105,17 @@ const registration = { hasSignUpNotice: (state) => !!state.users.signUpNotice.message, termsOfService: (state) => state.instance.tos, embeddedToS: (state) => state.instance.embeddedToS, - accountActivationRequired: (state) => state.instance.accountActivationRequired, - accountApprovalRequired: (state) => state.instance.accountApprovalRequired, + accountActivationRequired: (state) => + state.instance.accountActivationRequired, + accountApprovalRequired: (state) => + state.instance.accountApprovalRequired, birthdayRequired: (state) => state.instance.birthdayRequired, - birthdayMinAge: (state) => state.instance.birthdayMinAge - }) + birthdayMinAge: (state) => state.instance.birthdayMinAge, + }), }, methods: { ...mapActions(['signUp', 'getCaptcha']), - async submit () { + async submit() { this.user.nickname = this.user.username this.user.token = this.token @@ -105,7 +123,9 @@ const registration = { this.user.captcha_token = this.captcha.token this.user.captcha_answer_data = this.captcha.answer_data if (this.user.language) { - this.user.language = localeService.internalToBackendLocaleMulti(this.user.language.filter(k => k)) + this.user.language = localeService.internalToBackendLocaleMulti( + this.user.language.filter((k) => k), + ) } this.v$.$touch() @@ -124,13 +144,15 @@ const registration = { } } }, - setCaptcha () { - this.getCaptcha().then(cpt => { this.captcha = cpt }) + setCaptcha() { + this.getCaptcha().then((cpt) => { + this.captcha = cpt + }) }, - replaceNewlines (str) { + replaceNewlines(str) { return str.replace(/\s*\n\s*/g, ' \n') - } - } + }, + }, } export default registration diff --git a/src/components/remote_follow/remote_follow.js b/src/components/remote_follow/remote_follow.js index 951b59419..a583b6f09 100644 --- a/src/components/remote_follow/remote_follow.js +++ b/src/components/remote_follow/remote_follow.js @@ -1,9 +1,9 @@ export default { props: ['user'], computed: { - subscribeUrl () { + subscribeUrl() { const serverUrl = new URL(this.user.statusnet_profile_url) return `${serverUrl.protocol}//${serverUrl.host}/main/ostatus` - } - } + }, + }, } diff --git a/src/components/remote_user_resolver/remote_user_resolver.js b/src/components/remote_user_resolver/remote_user_resolver.js index 9b5e511e0..430f56c84 100644 --- a/src/components/remote_user_resolver/remote_user_resolver.js +++ b/src/components/remote_user_resolver/remote_user_resolver.js @@ -1,14 +1,16 @@ const RemoteUserResolver = { data: () => ({ - error: false + error: false, }), - mounted () { + mounted() { this.redirect() }, methods: { - redirect () { - const acct = this.$route.params.username + '@' + this.$route.params.hostname - this.$store.state.api.backendInteractor.fetchUser({ id: acct }) + redirect() { + const acct = + this.$route.params.username + '@' + this.$route.params.hostname + this.$store.state.api.backendInteractor + .fetchUser({ id: acct }) .then((externalUser) => { if (externalUser.error) { this.error = true @@ -17,15 +19,15 @@ const RemoteUserResolver = { const id = externalUser.id this.$router.replace({ name: 'external-user-profile', - params: { id } + params: { id }, }) } }) .catch(() => { this.error = true }) - } - } + }, + }, } export default RemoteUserResolver diff --git a/src/components/remove_follower_button/remove_follower_button.js b/src/components/remove_follower_button/remove_follower_button.js index 052a519ff..b8f195815 100644 --- a/src/components/remove_follower_button/remove_follower_button.js +++ b/src/components/remove_follower_button/remove_follower_button.js @@ -2,47 +2,49 @@ import ConfirmModal from '../confirm_modal/confirm_modal.vue' export default { props: ['user', 'relationship'], - data () { + data() { return { inProgress: false, - showingConfirmRemoveFollower: false + showingConfirmRemoveFollower: false, } }, components: { - ConfirmModal + ConfirmModal, }, computed: { - label () { + label() { if (this.inProgress) { return this.$t('user_card.follow_progress') } else { return this.$t('user_card.remove_follower') } }, - shouldConfirmRemoveUserFromFollowers () { + shouldConfirmRemoveUserFromFollowers() { return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers - } + }, }, methods: { - showConfirmRemoveUserFromFollowers () { + showConfirmRemoveUserFromFollowers() { this.showingConfirmRemoveFollower = true }, - hideConfirmRemoveUserFromFollowers () { + hideConfirmRemoveUserFromFollowers() { this.showingConfirmRemoveFollower = false }, - onClick () { + onClick() { if (!this.shouldConfirmRemoveUserFromFollowers) { this.doRemoveUserFromFollowers() } else { this.showConfirmRemoveUserFromFollowers() } }, - doRemoveUserFromFollowers () { + doRemoveUserFromFollowers() { this.inProgress = true - this.$store.dispatch('removeUserFromFollowers', this.relationship.id).then(() => { - this.inProgress = false - }) + this.$store + .dispatch('removeUserFromFollowers', this.relationship.id) + .then(() => { + this.inProgress = false + }) this.hideConfirmRemoveUserFromFollowers() - } - } + }, + }, } diff --git a/src/components/report/report.js b/src/components/report/report.js index 6e9f21eea..a4df8eb6c 100644 --- a/src/components/report/report.js +++ b/src/components/report/report.js @@ -1,37 +1,43 @@ +import RichContent from 'src/components/rich_content/rich_content.jsx' +import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import { useReportsStore } from 'src/stores/reports' import Select from '../select/select.vue' import StatusContent from '../status_content/status_content.vue' import Timeago from '../timeago/timeago.vue' -import RichContent from 'src/components/rich_content/rich_content.jsx' -import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' const Report = { - props: [ - 'reportId' - ], + props: ['reportId'], components: { Select, StatusContent, Timeago, - RichContent + RichContent, }, computed: { - report () { + report() { return useReportsStore().reports[this.reportId] || {} }, state: { - get: function () { return this.report.state }, - set: function (val) { this.setReportState(val) } - } + get: function () { + return this.report.state + }, + set: function (val) { + this.setReportState(val) + }, + }, }, methods: { - generateUserProfileLink (user) { - return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames) + generateUserProfileLink(user) { + return generateProfileLink( + user.id, + user.screen_name, + this.$store.state.instance.restrictedNicknames, + ) }, - setReportState (state) { + setReportState(state) { return useReportsStore().setReportState({ id: this.report.id, state }) - } - } + }, + }, } export default Report diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx index 5adc2443a..99dd8035d 100644 --- a/src/components/rich_content/rich_content.jsx +++ b/src/components/rich_content/rich_content.jsx @@ -1,11 +1,17 @@ -import { unescape, flattenDeep } from 'lodash' -import { getTagName, processTextForEmoji, getAttrs } from 'src/services/html_converter/utility.service.js' -import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js' -import { convertHtmlToLines } from 'src/services/html_converter/html_line_converter.service.js' -import StillImageEmojiPopover from 'src/components/still-image/still-image-emoji-popover.vue' -import MentionsLine from 'src/components/mentions_line/mentions_line.vue' -import { MENTIONS_LIMIT } from 'src/components/mentions_line/mentions_line.js' +import { flattenDeep, unescape as ldUnescape } from 'lodash' + import HashtagLink from 'src/components/hashtag_link/hashtag_link.vue' +import { MENTIONS_LIMIT } from 'src/components/mentions_line/mentions_line.js' +import MentionsLine from 'src/components/mentions_line/mentions_line.vue' +import StillImage from 'src/components/still-image/still-image.vue' +import StillImageEmojiPopover from 'src/components/still-image/still-image-emoji-popover.vue' +import { convertHtmlToLines } from 'src/services/html_converter/html_line_converter.service.js' +import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js' +import { + getAttrs, + getTagName, + processTextForEmoji, +} from 'src/services/html_converter/utility.service.js' import './rich_content.scss' @@ -27,7 +33,7 @@ const MAYBE_LINE_BREAKING_ELEMENTS = [ 'h2', 'h3', 'h4', - 'h5' + 'h5', ] /** @@ -52,46 +58,46 @@ export default { name: 'RichContent', components: { MentionsLine, - HashtagLink + HashtagLink, }, props: { // Original html content html: { required: true, - type: String + type: String, }, attentions: { required: false, - default: () => [] + default: () => [], }, // Emoji object, as in status.emojis, note the "s" at the end... emoji: { required: true, - type: Array + type: Array, }, // Whether to handle links or not (posts: yes, everything else: no) handleLinks: { required: false, type: Boolean, - default: false + default: false, }, // Meme arrows greentext: { required: false, type: Boolean, - default: false + default: false, }, // Faint style (for notifs) faint: { required: false, type: Boolean, - default: false + default: false, }, // Collapse newlines collapse: { required: false, type: Boolean, - default: false + default: false, }, /* Content comes from current instance * @@ -103,11 +109,11 @@ export default { isLocal: { required: false, type: Boolean, - default: true - } + default: true, + }, }, // NEVER EVER TOUCH DATA INSIDE RENDER - render () { + render() { // Pre-process HTML const { newHtml: html } = preProcessPerLine(this.html, this.greentext) let currentMentions = null // Current chain of mentions, we group all mentions together @@ -124,10 +130,7 @@ export default { let tagsIndex = 0 const renderImage = (tag) => { - return + return } const renderHashtag = (attrs, children, encounteredTextReverse) => { @@ -137,12 +140,14 @@ export default { lastTags.push(linkData) } const { url, tag, content } = linkData - return + return } const renderMention = (attrs, children) => { const linkData = getLinkData(attrs, children, mentionIndex++) - linkData.notifying = this.attentions.some(a => a.statusnet_profile_url === linkData.url) + linkData.notifying = this.attentions.some( + (a) => a.statusnet_profile_url === linkData.url, + ) writtenMentions.push(linkData) if (currentMentions === null) { currentMentions = [] @@ -152,7 +157,7 @@ export default { invisibleMentions.push(linkData) } if (currentMentions.length === 1) { - return + return } else { return '' } @@ -171,26 +176,28 @@ export default { // in MentionsLine lastSpacing = item // Don't remove last space in a container (fixes poast mentions) - return (index !== array.length - 1) && (currentMentions !== null) ? item.trim() : item + return index !== array.length - 1 && currentMentions !== null + ? item.trim() + : item } currentMentions = null if (item.includes(':')) { - item = ['', processTextForEmoji( - item, - this.emoji, - ({ shortcode, url }) => { - return - } - )] + item = [ + '', + processTextForEmoji(item, this.emoji, ({ shortcode, url }) => { + return ( + + ) + }), + ] } return item } @@ -209,18 +216,24 @@ export default { * we have a tag right next to mentions */ const mentionsLinePadding = - // Padding is only needed if we just finished parsing mentions - previouslyMentions && - // Don't add padding if content is string and has padding already - !(children && typeof children[0] === 'string' && children[0].match(/^\s/)) - ? lastSpacing - : '' + // Padding is only needed if we just finished parsing mentions + previouslyMentions && + // Don't add padding if content is string and has padding already + !( + children && + typeof children[0] === 'string' && + children[0].match(/^\s/) + ) + ? lastSpacing + : '' if (MAYBE_LINE_BREAKING_ELEMENTS.includes(Tag)) { // all the elements that can cause a line change currentMentions = null - } else if (Tag === 'img') { // replace images with StillImage + } else if (Tag === 'img') { + // replace images with StillImage return ['', [mentionsLinePadding, renderImage(opener)], ''] - } else if (Tag === 'a' && this.handleLinks) { // replace mentions with MentionLink + } else if (Tag === 'a' && this.handleLinks) { + // replace mentions with MentionLink if (fullAttrs.class && fullAttrs.class.includes('mention')) { // Handling mentions here return renderMention(attrs, children) @@ -228,7 +241,11 @@ export default { currentMentions = null } } else if (Tag === 'span') { - if (this.handleLinks && fullAttrs.class && fullAttrs.class.includes('h-card')) { + if ( + this.handleLinks && + fullAttrs.class && + fullAttrs.class.includes('h-card') + ) { return ['', children.map(processItem), ''] } } @@ -236,11 +253,8 @@ export default { if (children !== undefined) { return [ '', - [ - mentionsLinePadding, - [opener, children.map(processItem), closer] - ], - '' + [mentionsLinePadding, [opener, children.map(processItem), closer]], + '', ] } else { return ['', [mentionsLinePadding, item], ''] @@ -256,29 +270,31 @@ export default { const emptyText = item.trim() === '' if (emptyText) return item if (!encounteredTextReverse) encounteredTextReverse = true - return unescape(item) + return ldUnescape(item) } else if (Array.isArray(item)) { // Handle tag nodes const [opener, children] = item const Tag = opener === '' ? '' : getTagName(opener) switch (Tag) { - case 'a': { // replace mentions with MentionLink + case 'a': { + // replace mentions with MentionLink if (!this.handleLinks) break const fullAttrs = getAttrs(opener, () => true) const attrs = getAttrs(opener, () => true) // should only be this if ( (fullAttrs.class && fullAttrs.class.includes('hashtag')) || // Pleroma style - (fullAttrs.rel === 'tag') // Mastodon style + fullAttrs.rel === 'tag' // Mastodon style ) { return renderHashtag(attrs, children, encounteredTextReverse) } else { attrs.target = '_blank' - const newChildren = [...children].reverse().map(processItemReverse).reverse() + const newChildren = [...children] + .reverse() + .map(processItemReverse) + .reverse() - return - { newChildren } - + return {newChildren} } } case '': @@ -290,11 +306,9 @@ export default { const newChildren = Array.isArray(children) ? [...children].reverse().map(processItemReverse).reverse() : children - return - { newChildren } - + return {newChildren} } else { - return + return } } return item @@ -306,30 +320,29 @@ export default { // DO NOT USE SLOTS they cause a re-render feedback loop here. // slots updated -> rerender -> emit -> update up the tree -> rerender -> ... // at least until vue3? - const result = - - { - this.collapse - ? pass2.map(x => { - if (!Array.isArray(x)) return x.replace(/\n/g, ' ') - return x.map(y => y.type === 'br' ? ' ' : y) - }) - : pass2 - } - + const result = ( + + {this.collapse + ? pass2.map((x) => { + if (!Array.isArray(x)) return x.replace(/\n/g, ' ') + return x.map((y) => (y.type === 'br' ? ' ' : y)) + }) + : pass2} + + ) const event = { lastTags, writtenMentions, writtenTags, - invisibleMentions + invisibleMentions, } // DO NOT MOVE TO UPDATE. BAD IDEA. this.$emit('parseReady', event) return result - } + }, } const getLinkData = (attrs, children, index) => { @@ -346,7 +359,7 @@ const getLinkData = (attrs, children, index) => { url: attrs.href, tag: attrs['data-tag'], content: flattenDeep(children).join(''), - textContent + textContent, } } @@ -362,31 +375,36 @@ export const preProcessPerLine = (html, greentext) => { const greentextHandle = new Set(['p', 'div']) const lines = convertHtmlToLines(html) - const newHtml = lines.reverse().map((item, index, array) => { - if (!item.text) return item - const string = item.text + const newHtml = lines + .reverse() + .map((item, index, array) => { + if (!item.text) return item + const string = item.text - // Greentext stuff - if ( - // Only if greentext is engaged - greentext && + // Greentext stuff + if ( + // Only if greentext is engaged + greentext && // Only handle p's and divs. Don't want to affect blockquotes, code etc - item.level.every(l => greentextHandle.has(l)) && + item.level.every((l) => greentextHandle.has(l)) && // Only if line begins with '>' or '<' (string.includes('>') || string.includes('<')) - ) { - const cleanedString = string.replace(/<[^>]+?>/gi, '') // remove all tags - .replace(/@\w+/gi, '') // remove mentions (even failed ones) - .trim() - if (cleanedString.startsWith('>')) { - return `${string}` - } else if (cleanedString.startsWith('<')) { - return `${string}` + ) { + const cleanedString = string + .replace(/<[^>]+?>/gi, '') // remove all tags + .replace(/@\w+/gi, '') // remove mentions (even failed ones) + .trim() + if (cleanedString.startsWith('>')) { + return `${string}` + } else if (cleanedString.startsWith('<')) { + return `${string}` + } } - } - return string - }).reverse().join('') + return string + }) + .reverse() + .join('') return { newHtml } } diff --git a/src/components/root.style.js b/src/components/root.style.js index 5075e33c8..54c4c6095 100644 --- a/src/components/root.style.js +++ b/src/components/root.style.js @@ -27,8 +27,8 @@ export default { // Selection colors '--selectionBackground': 'color | --accent', - '--selectionText': 'color | $textColor(--accent --text no-preserve)' - } - } - ] + '--selectionText': 'color | $textColor(--accent --text no-preserve)', + }, + }, + ], } diff --git a/src/components/roundness_input/roundness_input.vue b/src/components/roundness_input/roundness_input.vue index 1da71ca79..04b623e86 100644 --- a/src/components/roundness_input/roundness_input.vue +++ b/src/components/roundness_input/roundness_input.vue @@ -36,16 +36,14 @@ import Checkbox from '../checkbox/checkbox.vue' export default { components: { - Checkbox + Checkbox, }, - props: [ - 'name', 'label', 'modelValue', 'fallback', 'disabled' - ], + props: ['name', 'label', 'modelValue', 'fallback', 'disabled'], emits: ['update:modelValue'], computed: { - present () { + present() { return typeof this.modelValue !== 'undefined' - } - } + }, + }, } diff --git a/src/components/scope_selector/scope_selector.js b/src/components/scope_selector/scope_selector.js index 462892cb6..9d3a3a718 100644 --- a/src/components/scope_selector/scope_selector.js +++ b/src/components/scope_selector/scope_selector.js @@ -1,90 +1,92 @@ import { library } from '@fortawesome/fontawesome-svg-core' import { - faEnvelope, - faLock, - faLockOpen, - faGlobe -} from '@fortawesome/free-solid-svg-icons' - -library.add( faEnvelope, faGlobe, faLock, - faLockOpen -) + faLockOpen, +} from '@fortawesome/free-solid-svg-icons' + +library.add(faEnvelope, faGlobe, faLock, faLockOpen) const ScopeSelector = { props: { showAll: { required: true, - type: Boolean + type: Boolean, }, userDefault: { required: true, - type: String + type: String, }, originalScope: { required: false, - type: String + type: String, }, initialScope: { required: false, - type: String + type: String, }, onScopeChange: { required: true, - type: Function + type: Function, }, unstyled: { required: false, type: Boolean, - default: true - } + default: true, + }, }, - data () { + data() { return { - currentScope: this.initialScope + currentScope: this.initialScope, } }, computed: { - showNothing () { - return !this.showPublic && !this.showUnlisted && !this.showPrivate && !this.showDirect + showNothing() { + return ( + !this.showPublic && + !this.showUnlisted && + !this.showPrivate && + !this.showDirect + ) }, - showPublic () { + showPublic() { return this.originalScope !== 'direct' && this.shouldShow('public') }, - showUnlisted () { + showUnlisted() { return this.originalScope !== 'direct' && this.shouldShow('unlisted') }, - showPrivate () { + showPrivate() { return this.originalScope !== 'direct' && this.shouldShow('private') }, - showDirect () { + showDirect() { return this.shouldShow('direct') }, - css () { + css() { const style = this.unstyled ? 'button-unstyled' : 'button-default' return { public: [style, { toggled: this.currentScope === 'public' }], unlisted: [style, { toggled: this.currentScope === 'unlisted' }], private: [style, { toggled: this.currentScope === 'private' }], - direct: [style, { toggled: this.currentScope === 'direct' }] + direct: [style, { toggled: this.currentScope === 'direct' }], } - } + }, }, methods: { - shouldShow (scope) { - return this.showAll || + shouldShow(scope) { + return ( + this.showAll || this.currentScope === scope || this.originalScope === scope || this.userDefault === scope || scope === 'direct' + ) }, - changeVis (scope) { + changeVis(scope) { this.currentScope = scope this.onScopeChange && this.onScopeChange(scope) - } - } + }, + }, } export default ScopeSelector diff --git a/src/components/screen_reader_notice/screen_reader_notice.js b/src/components/screen_reader_notice/screen_reader_notice.js index 794b855ac..b834be531 100644 --- a/src/components/screen_reader_notice/screen_reader_notice.js +++ b/src/components/screen_reader_notice/screen_reader_notice.js @@ -2,20 +2,22 @@ const ScreenReaderNotice = { props: { ariaLive: { type: String, - default: 'assertive' - } + default: 'assertive', + }, }, - data () { + data() { return { - currentText: '' + currentText: '', } }, methods: { - announce (text) { + announce(text) { this.currentText = text - setTimeout(() => { this.currentText = '' }, 1000) - } - } + setTimeout(() => { + this.currentText = '' + }, 1000) + }, + }, } export default ScreenReaderNotice diff --git a/src/components/scroll_top_button/scroll_top_button.js b/src/components/scroll_top_button/scroll_top_button.js index bdc45b9b4..e4b908f0b 100644 --- a/src/components/scroll_top_button/scroll_top_button.js +++ b/src/components/scroll_top_button/scroll_top_button.js @@ -3,16 +3,16 @@ const ScrollTopButton = { fast: { type: Boolean, required: false, - default: false - } + default: false, + }, }, methods: { scrollToTop() { - const speed = this.fast ? 'instant' : 'smooth'; + const speed = this.fast ? 'instant' : 'smooth' window.scrollTo({ top: 0, behavior: speed }) - } - } + }, + }, } export default ScrollTopButton diff --git a/src/components/scrollbar.style.js b/src/components/scrollbar.style.js index 1168f67d8..fbe952076 100644 --- a/src/components/scrollbar.style.js +++ b/src/components/scrollbar.style.js @@ -1,12 +1,16 @@ export default { name: 'Scrollbar', - selector: ['::-webkit-scrollbar-button', '::-webkit-scrollbar-thumb', '::-webkit-resizer'], + selector: [ + '::-webkit-scrollbar-button', + '::-webkit-scrollbar-thumb', + '::-webkit-resizer', + ], notEditable: true, // for now defaultRules: [ { directives: { - background: '--wallpaper' - } - } - ] + background: '--wallpaper', + }, + }, + ], } diff --git a/src/components/scrollbar_element.style.js b/src/components/scrollbar_element.style.js index ef1ea8136..6f238e1f8 100644 --- a/src/components/scrollbar_element.style.js +++ b/src/components/scrollbar_element.style.js @@ -5,7 +5,7 @@ const border = (top, shadow) => ({ spread: 0, color: shadow ? '#000000' : '#FFFFFF', alpha: 0.2, - inset: true + inset: true, }) const buttonInsetFakeBorders = [border(true, false), border(false, true)] @@ -16,7 +16,7 @@ const buttonOuterShadow = { blur: 2, spread: 0, color: '#000000', - alpha: 1 + alpha: 1, } const hoverGlow = { @@ -25,7 +25,7 @@ const hoverGlow = { blur: 4, spread: 0, color: '--text', - alpha: 1 + alpha: 1, } export default { @@ -35,68 +35,66 @@ export default { states: { pressed: ':active', hover: ':is(:hover, :focus-visible, :has(:focus-visible)):not(:disabled)', - disabled: ':disabled' + disabled: ':disabled', }, - validInnerComponents: [ - 'Text' - ], + validInnerComponents: ['Text'], defaultRules: [ { directives: { background: '--fg', shadow: [buttonOuterShadow, ...buttonInsetFakeBorders], - roundness: 3 - } + roundness: 3, + }, }, { state: ['hover'], directives: { - shadow: [hoverGlow, ...buttonInsetFakeBorders] - } + shadow: [hoverGlow, ...buttonInsetFakeBorders], + }, }, { state: ['pressed'], directives: { - shadow: [buttonOuterShadow, ...inputInsetFakeBorders] - } + shadow: [buttonOuterShadow, ...inputInsetFakeBorders], + }, }, { state: ['hover', 'pressed'], directives: { - shadow: [hoverGlow, ...inputInsetFakeBorders] - } + shadow: [hoverGlow, ...inputInsetFakeBorders], + }, }, { state: ['toggled'], directives: { background: '--accent,-24.2', - shadow: [buttonOuterShadow, ...inputInsetFakeBorders] - } + shadow: [buttonOuterShadow, ...inputInsetFakeBorders], + }, }, { state: ['toggled', 'hover'], directives: { background: '--accent,-24.2', - shadow: [hoverGlow, ...inputInsetFakeBorders] - } + shadow: [hoverGlow, ...inputInsetFakeBorders], + }, }, { state: ['disabled'], directives: { background: '$blend(--inheritedBackground 0.25 --parent)', - shadow: [...buttonInsetFakeBorders] - } + shadow: [...buttonInsetFakeBorders], + }, }, { component: 'Text', parent: { component: 'Button', - state: ['disabled'] + state: ['disabled'], }, directives: { textOpacity: 0.25, - textOpacityMode: 'blend' - } - } - ] + textOpacityMode: 'blend', + }, + }, + ], } diff --git a/src/components/search/search.js b/src/components/search/search.js index 877d6f300..b8812d0ac 100644 --- a/src/components/search/search.js +++ b/src/components/search/search.js @@ -1,31 +1,25 @@ -import FollowCard from '../follow_card/follow_card.vue' -import Conversation from '../conversation/conversation.vue' -import Status from '../status/status.vue' -import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' -import map from 'lodash/map' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faCircleNotch, - faSearch -} from '@fortawesome/free-solid-svg-icons' import { uniqBy } from 'lodash' +import map from 'lodash/map' -library.add( - faCircleNotch, - faSearch -) +import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' +import Conversation from '../conversation/conversation.vue' +import FollowCard from '../follow_card/follow_card.vue' +import Status from '../status/status.vue' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { faCircleNotch, faSearch } from '@fortawesome/free-solid-svg-icons' + +library.add(faCircleNotch, faSearch) const Search = { components: { FollowCard, Conversation, Status, - TabSwitcher + TabSwitcher, }, - props: [ - 'query' - ], - data () { + props: ['query'], + data() { return { loaded: false, loading: false, @@ -37,36 +31,37 @@ const Search = { statusesOffset: 0, lastStatusFetchCount: 0, - lastQuery: '' + lastQuery: '', } }, computed: { - users () { - return this.userIds.map(userId => this.$store.getters.findUser(userId)) + users() { + return this.userIds.map((userId) => this.$store.getters.findUser(userId)) }, - visibleStatuses () { + visibleStatuses() { const allStatusesObject = this.$store.state.statuses.allStatusesObject - return this.statuses.filter(status => - allStatusesObject[status.id] && !allStatusesObject[status.id].deleted + return this.statuses.filter( + (status) => + allStatusesObject[status.id] && !allStatusesObject[status.id].deleted, ) - } + }, }, - mounted () { + mounted() { this.search(this.query) }, watch: { - query (newValue) { + query(newValue) { this.searchTerm = newValue this.search(newValue) - } + }, }, methods: { - newQuery (query) { + newQuery(query) { this.$router.push({ name: 'search', query: { query } }) this.$refs.searchInput.focus() }, - search (query, searchType = null) { + search(query, searchType = null) { if (!query) { this.loading = false return @@ -83,8 +78,14 @@ const Search = { this.lastStatusFetchCount = 0 } - this.$store.dispatch('search', { q: query, resolve: true, offset: this.statusesOffset, type: searchType }) - .then(data => { + this.$store + .dispatch('search', { + q: query, + resolve: true, + offset: this.statusesOffset, + type: searchType, + }) + .then((data) => { this.loading = false const oldLength = this.statuses.length @@ -104,14 +105,14 @@ const Search = { this.lastQuery = query }) }, - resultCount (tabName) { + resultCount(tabName) { const length = this[tabName].length return length === 0 ? '' : ` (${length})` }, - onResultTabSwitch (key) { + onResultTabSwitch(key) { this.currenResultTab = key }, - getActiveTab () { + getActiveTab() { if (this.visibleStatuses.length > 0) { return 'statuses' } else if (this.users.length > 0) { @@ -122,10 +123,10 @@ const Search = { return 'statuses' }, - lastHistoryRecord (hashtag) { + lastHistoryRecord(hashtag) { return hashtag.history && hashtag.history[0] - } - } + }, + }, } export default Search diff --git a/src/components/search_bar/search_bar.js b/src/components/search_bar/search_bar.js index 3b297f098..ebe30d303 100644 --- a/src/components/search_bar/search_bar.js +++ b/src/components/search_bar/search_bar.js @@ -1,33 +1,27 @@ import { library } from '@fortawesome/fontawesome-svg-core' -import { - faTimes, - faSearch -} from '@fortawesome/free-solid-svg-icons' +import { faSearch, faTimes } from '@fortawesome/free-solid-svg-icons' -library.add( - faTimes, - faSearch -) +library.add(faTimes, faSearch) const SearchBar = { data: () => ({ searchTerm: undefined, hidden: true, - error: false + error: false, }), watch: { $route: function (route) { if (route.name === 'search') { this.searchTerm = route.query.query } - } + }, }, methods: { - find (searchTerm) { + find(searchTerm) { this.$router.push({ name: 'search', query: { query: searchTerm } }) this.$refs.searchInput.focus() }, - toggleHidden () { + toggleHidden() { this.hidden = !this.hidden this.$emit('toggled', this.hidden) this.$nextTick(() => { @@ -35,8 +29,8 @@ const SearchBar = { this.$refs.searchInput.focus() } }) - } - } + }, + }, } export default SearchBar diff --git a/src/components/select/select.js b/src/components/select/select.js index 34d64fd22..9bf9f7e23 100644 --- a/src/components/select/select.js +++ b/src/components/select/select.js @@ -1,19 +1,9 @@ import { library } from '@fortawesome/fontawesome-svg-core' -import { - faChevronDown -} from '@fortawesome/free-solid-svg-icons' +import { faChevronDown } from '@fortawesome/free-solid-svg-icons' -library.add( - faChevronDown -) +library.add(faChevronDown) export default { emits: ['update:modelValue'], - props: [ - 'modelValue', - 'disabled', - 'unstyled', - 'kind', - 'attrs' - ] + props: ['modelValue', 'disabled', 'unstyled', 'kind', 'attrs'], } diff --git a/src/components/select/select_motion.vue b/src/components/select/select_motion.vue index 45e278fc6..21262c256 100644 --- a/src/components/select/select_motion.vue +++ b/src/components/select/select_motion.vue @@ -54,20 +54,20 @@ import { computed, defineEmits, defineProps, nextTick } from 'vue' const props = defineProps({ modelValue: { type: Array, - required: true + required: true, }, selectedId: { type: Number, - required: true + required: true, }, disabled: { type: Boolean, - default: false + default: false, }, getAddValue: { type: Function, - required: true - } + required: true, + }, }) const emit = defineEmits(['update:modelValue', 'update:selectedId']) @@ -116,7 +116,10 @@ const del = async () => { emit('update:modelValue', newModel) await nextTick() - emit('update:selectedId', newModel.length === 0 ? undefined : Math.max(props.selectedId - 1, 0)) + emit( + 'update:selectedId', + newModel.length === 0 ? undefined : Math.max(props.selectedId - 1, 0), + ) } diff --git a/src/components/selectable_list/selectable_list.js b/src/components/selectable_list/selectable_list.js index 10980d46a..b08a3e9a5 100644 --- a/src/components/selectable_list/selectable_list.js +++ b/src/components/selectable_list/selectable_list.js @@ -1,48 +1,48 @@ -import List from '../list/list.vue' import Checkbox from '../checkbox/checkbox.vue' +import List from '../list/list.vue' const SelectableList = { components: { List, - Checkbox + Checkbox, }, props: { items: { type: Array, - default: () => [] + default: () => [], }, getKey: { type: Function, - default: item => item.id - } + default: (item) => item.id, + }, }, - data () { + data() { return { - selected: [] + selected: [], } }, computed: { - allKeys () { + allKeys() { return this.items.map(this.getKey) }, - filteredSelected () { - return this.allKeys.filter(key => this.selected.indexOf(key) !== -1) + filteredSelected() { + return this.allKeys.filter((key) => this.selected.indexOf(key) !== -1) }, - allSelected () { + allSelected() { return this.filteredSelected.length === this.items.length }, - noneSelected () { + noneSelected() { return this.filteredSelected.length === 0 }, - someSelected () { + someSelected() { return !this.allSelected && !this.noneSelected - } + }, }, methods: { - isSelected (item) { + isSelected(item) { return this.filteredSelected.indexOf(this.getKey(item)) !== -1 }, - toggle (checked, item) { + toggle(checked, item) { const key = this.getKey(item) const oldChecked = this.isSelected(key) if (checked !== oldChecked) { @@ -53,14 +53,14 @@ const SelectableList = { } } }, - toggleAll (value) { + toggleAll(value) { if (value) { this.selected = this.allKeys.slice(0) } else { this.selected = [] } - } - } + }, + }, } export default SelectableList diff --git a/src/components/settings_modal/admin_tabs/auth_tab.js b/src/components/settings_modal/admin_tabs/auth_tab.js index a078291e6..627150587 100644 --- a/src/components/settings_modal/admin_tabs/auth_tab.js +++ b/src/components/settings_modal/admin_tabs/auth_tab.js @@ -1,20 +1,19 @@ +import AttachmentSetting from '../helpers/attachment_setting.vue' import BooleanSetting from '../helpers/boolean_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue' -import IntegerSetting from '../helpers/integer_setting.vue' -import StringSetting from '../helpers/string_setting.vue' -import TupleSetting from '../helpers/tuple_setting.vue' import GroupSetting from '../helpers/group_setting.vue' -import AttachmentSetting from '../helpers/attachment_setting.vue' +import IntegerSetting from '../helpers/integer_setting.vue' import ListSetting from '../helpers/list_setting.vue' import MapSetting from '../helpers/map_setting.vue' - import SharedComputedObject from '../helpers/shared_computed_object.js' +import StringSetting from '../helpers/string_setting.vue' +import TupleSetting from '../helpers/tuple_setting.vue' const AuthTab = { - provide () { + provide() { return { defaultDraftMode: true, - defaultSource: 'admin' + defaultSource: 'admin', } }, components: { @@ -26,14 +25,16 @@ const AuthTab = { AttachmentSetting, GroupSetting, ListSetting, - MapSetting + MapSetting, }, computed: { ...SharedComputedObject(), - LDAPEnabled () { - return this.$store.state.adminSettings.draft[':pleroma'][':ldap'][':enabled'] + LDAPEnabled() { + return this.$store.state.adminSettings.draft[':pleroma'][':ldap'][ + ':enabled' + ] }, - } + }, } export default AuthTab diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js index 7c137e35f..e53c2f219 100644 --- a/src/components/settings_modal/admin_tabs/emoji_tab.js +++ b/src/components/settings_modal/admin_tabs/emoji_tab.js @@ -1,30 +1,26 @@ -import { clone, assign } from 'lodash' -import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' -import StringSetting from '../helpers/string_setting.vue' import Checkbox from 'components/checkbox/checkbox.vue' -import StillImage from 'components/still-image/still-image.vue' -import Select from 'components/select/select.vue' -import Popover from 'components/popover/popover.vue' import ConfirmModal from 'components/confirm_modal/confirm_modal.vue' -import ModifiedIndicator from '../helpers/modified_indicator.vue' -import EmojiEditingPopover from '../helpers/emoji_editing_popover.vue' -import { useInterfaceStore } from 'src/stores/interface' +import Popover from 'components/popover/popover.vue' +import Select from 'components/select/select.vue' +import StillImage from 'components/still-image/still-image.vue' +import { assign, clone } from 'lodash' +import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' +import { useInterfaceStore } from 'src/stores/interface' +import EmojiEditingPopover from '../helpers/emoji_editing_popover.vue' +import ModifiedIndicator from '../helpers/modified_indicator.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' +import StringSetting from '../helpers/string_setting.vue' + import { library } from '@fortawesome/fontawesome-svg-core' import { faArrowsRotate, + faDownload, faFolderOpen, faServer, - faDownload } from '@fortawesome/free-solid-svg-icons' -library.add( - faArrowsRotate, - faFolderOpen, - faDownload, - faServer -) +library.add(faArrowsRotate, faFolderOpen, faDownload, faServer) const EmojiTab = { components: { @@ -36,14 +32,14 @@ const EmojiTab = { Popover, ConfirmModal, ModifiedIndicator, - EmojiEditingPopover + EmojiEditingPopover, }, - data () { + data() { return { - knownLocalPacks: { }, - knownRemotePacks: { }, - editedMetadata: { }, + knownLocalPacks: {}, + knownRemotePacks: {}, + editedMetadata: {}, packName: '', newPackName: '', deleteModalVisible: false, @@ -51,20 +47,20 @@ const EmojiTab = { remotePackDownloadAs: '', remotePackURL: '', - remotePackFile: null + remotePackFile: null, } }, - provide () { + provide() { return { emojiAddr: this.emojiAddr } }, computed: { ...SharedComputedObject(), - pack () { + pack() { return this.packName !== '' ? this.knownPacks[this.packName] : undefined }, - packMeta () { + packMeta() { if (this.packName === '') return {} if (this.editedMetadata[this.packName] === undefined) { this.editedMetadata[this.packName] = clone(this.pack.pack) @@ -72,31 +68,36 @@ const EmojiTab = { return this.editedMetadata[this.packName] }, - knownPacks () { + knownPacks() { // Copy the object itself but not the children, so they are still passed by reference and modified const result = clone(this.knownLocalPacks) for (const instName in this.knownRemotePacks) { for (const instPack in this.knownRemotePacks[instName]) { - result[`${instPack}@${instName}`] = this.knownRemotePacks[instName][instPack] + result[`${instPack}@${instName}`] = + this.knownRemotePacks[instName][instPack] } } return result }, - downloadWillReplaceLocal () { - return (this.remotePackDownloadAs.trim() === '' && this.pack.remote && this.pack.remote.baseName in this.knownLocalPacks) || - (this.remotePackDownloadAs in this.knownLocalPacks) - } + downloadWillReplaceLocal() { + return ( + (this.remotePackDownloadAs.trim() === '' && + this.pack.remote && + this.pack.remote.baseName in this.knownLocalPacks) || + this.remotePackDownloadAs in this.knownLocalPacks + ) + }, }, methods: { - reloadEmoji () { + reloadEmoji() { this.$store.state.api.backendInteractor.reloadEmoji() }, - importFromFS () { + importFromFS() { this.$store.state.api.backendInteractor.importEmojiFromFS() }, - emojiAddr (name) { + emojiAddr(name) { if (this.pack.remote !== undefined) { // Remote pack return `${this.pack.remote.instance}/emoji/${encodeURIComponent(this.pack.remote.baseName)}/${name}` @@ -105,115 +106,141 @@ const EmojiTab = { } }, - createEmojiPack () { - this.$store.state.api.backendInteractor.createEmojiPack( - { name: this.newPackName } - ).then(resp => resp.json()).then(resp => { - if (resp === 'ok') { - return this.refreshPackList() - } else { - this.displayError(resp.error) - return Promise.reject(resp) - } - }).then(() => { - this.packName = this.newPackName - this.newPackName = '' - }) + createEmojiPack() { + this.$store.state.api.backendInteractor + .createEmojiPack({ name: this.newPackName }) + .then((resp) => resp.json()) + .then((resp) => { + if (resp === 'ok') { + return this.refreshPackList() + } else { + this.displayError(resp.error) + return Promise.reject(resp) + } + }) + .then(() => { + this.packName = this.newPackName + this.newPackName = '' + }) }, - deleteEmojiPack () { - this.$store.state.api.backendInteractor.deleteEmojiPack( - { name: this.packName } - ).then(resp => resp.json()).then(resp => { - if (resp === 'ok') { - return this.refreshPackList() - } else { - this.displayError(resp.error) - return Promise.reject(resp) - } - }).then(() => { - delete this.editedMetadata[this.packName] + deleteEmojiPack() { + this.$store.state.api.backendInteractor + .deleteEmojiPack({ name: this.packName }) + .then((resp) => resp.json()) + .then((resp) => { + if (resp === 'ok') { + return this.refreshPackList() + } else { + this.displayError(resp.error) + return Promise.reject(resp) + } + }) + .then(() => { + delete this.editedMetadata[this.packName] - this.deleteModalVisible = false - this.packName = '' - }) + this.deleteModalVisible = false + this.packName = '' + }) }, - metaEdited (prop) { + metaEdited(prop) { if (!this.pack) return const def = this.pack.pack[prop] || '' const edited = this.packMeta[prop] || '' return edited !== def }, - savePackMetadata () { - this.$store.state.api.backendInteractor.saveEmojiPackMetadata({ name: this.packName, newData: this.packMeta }).then( - resp => resp.json() - ).then(resp => { - if (resp.error !== undefined) { - this.displayError(resp.error) - return - } + savePackMetadata() { + this.$store.state.api.backendInteractor + .saveEmojiPackMetadata({ name: this.packName, newData: this.packMeta }) + .then((resp) => resp.json()) + .then((resp) => { + if (resp.error !== undefined) { + this.displayError(resp.error) + return + } - // Update actual pack data - this.pack.pack = resp - // Delete edited pack data, should auto-update itself - delete this.editedMetadata[this.packName] - }) + // Update actual pack data + this.pack.pack = resp + // Delete edited pack data, should auto-update itself + delete this.editedMetadata[this.packName] + }) }, - updatePackFiles (newFiles, packName) { + updatePackFiles(newFiles, packName) { this.knownPacks[packName].files = newFiles this.sortPackFiles(packName) }, - loadPacksPaginated (listFunction) { + loadPacksPaginated(listFunction) { const pageSize = 25 const allPacks = {} - return listFunction({ instance: this.remotePackInstance, page: 1, pageSize: 0 }) - .then(data => data.json()) - .then(data => { - if (data.error !== undefined) { return Promise.reject(data.error) } + return listFunction({ + instance: this.remotePackInstance, + page: 1, + pageSize: 0, + }) + .then((data) => data.json()) + .then((data) => { + if (data.error !== undefined) { + return Promise.reject(data.error) + } let resultingPromise = Promise.resolve({}) for (let i = 0; i < Math.ceil(data.count / pageSize); i++) { - resultingPromise = resultingPromise.then(() => listFunction({ instance: this.remotePackInstance, page: i, pageSize }) - ).then(data => data.json()).then(pageData => { - if (pageData.error !== undefined) { return Promise.reject(pageData.error) } + resultingPromise = resultingPromise + .then(() => + listFunction({ + instance: this.remotePackInstance, + page: i, + pageSize, + }), + ) + .then((data) => data.json()) + .then((pageData) => { + if (pageData.error !== undefined) { + return Promise.reject(pageData.error) + } - assign(allPacks, pageData.packs) - }) + assign(allPacks, pageData.packs) + }) } return resultingPromise }) .then(() => allPacks) - .catch(data => { + .catch((data) => { this.displayError(data) }) }, - refreshPackList () { - this.loadPacksPaginated(this.$store.state.api.backendInteractor.listEmojiPacks) - .then(allPacks => { - this.knownLocalPacks = allPacks - for (const name of Object.keys(this.knownLocalPacks)) { - this.sortPackFiles(name) - } - }) + refreshPackList() { + this.loadPacksPaginated( + this.$store.state.api.backendInteractor.listEmojiPacks, + ).then((allPacks) => { + this.knownLocalPacks = allPacks + for (const name of Object.keys(this.knownLocalPacks)) { + this.sortPackFiles(name) + } + }) }, - listRemotePacks () { - this.loadPacksPaginated(this.$store.state.api.backendInteractor.listRemoteEmojiPacks) - .then(allPacks => { + listRemotePacks() { + this.loadPacksPaginated( + this.$store.state.api.backendInteractor.listRemoteEmojiPacks, + ) + .then((allPacks) => { let inst = this.remotePackInstance - if (!inst.startsWith('http')) { inst = 'https://' + inst } + if (!inst.startsWith('http')) { + inst = 'https://' + inst + } const instUrl = new URL(inst) inst = instUrl.host for (const packName in allPacks) { allPacks[packName].remote = { baseName: packName, - instance: instUrl.origin + instance: instUrl.origin, } } @@ -222,89 +249,101 @@ const EmojiTab = { this.sortPackFiles(`${pack}@${inst}`) } }) - .catch(data => { + .catch((data) => { this.displayError(data) }) }, - downloadRemotePack () { + downloadRemotePack() { if (this.remotePackDownloadAs.trim() === '') { this.remotePackDownloadAs = this.pack.remote.baseName } - this.$store.state.api.backendInteractor.downloadRemoteEmojiPack({ - instance: this.pack.remote.instance, packName: this.pack.remote.baseName, as: this.remotePackDownloadAs - }) - .then(data => data.json()) - .then(resp => { + this.$store.state.api.backendInteractor + .downloadRemoteEmojiPack({ + instance: this.pack.remote.instance, + packName: this.pack.remote.baseName, + as: this.remotePackDownloadAs, + }) + .then((data) => data.json()) + .then((resp) => { if (resp === 'ok') { return this.refreshPackList() } else { this.displayError(resp.error) return Promise.reject(resp) } - }).then(() => { + }) + .then(() => { this.packName = this.remotePackDownloadAs this.remotePackDownloadAs = '' }) }, - downloadRemoteURLPack () { - this.$store.state.api.backendInteractor.downloadRemoteEmojiPackZIP({ - url: this.remotePackURL, packName: this.newPackName - }) - .then(data => data.json()) - .then(resp => { + downloadRemoteURLPack() { + this.$store.state.api.backendInteractor + .downloadRemoteEmojiPackZIP({ + url: this.remotePackURL, + packName: this.newPackName, + }) + .then((data) => data.json()) + .then((resp) => { if (resp === 'ok') { return this.refreshPackList() } else { this.displayError(resp.error) return Promise.reject(resp) } - }).then(() => { + }) + .then(() => { this.packName = this.newPackName this.newPackName = '' this.remotePackURL = '' }) }, - downloadRemoteFilePack () { - this.$store.state.api.backendInteractor.downloadRemoteEmojiPackZIP({ - file: this.remotePackFile[0], packName: this.newPackName - }) - .then(data => data.json()) - .then(resp => { + downloadRemoteFilePack() { + this.$store.state.api.backendInteractor + .downloadRemoteEmojiPackZIP({ + file: this.remotePackFile[0], + packName: this.newPackName, + }) + .then((data) => data.json()) + .then((resp) => { if (resp === 'ok') { return this.refreshPackList() } else { this.displayError(resp.error) return Promise.reject(resp) } - }).then(() => { + }) + .then(() => { this.packName = this.newPackName this.newPackName = '' this.remotePackURL = '' }) }, - displayError (msg) { + displayError(msg) { useInterfaceStore().pushGlobalNotice({ messageKey: 'admin_dash.emoji.error', messageArgs: [msg], - level: 'error' + level: 'error', }) }, - sortPackFiles (nameOfPack) { + sortPackFiles(nameOfPack) { // Sort by key - const sorted = Object.keys(this.knownPacks[nameOfPack].files).sort().reduce((acc, key) => { - if (key.length === 0) return acc - acc[key] = this.knownPacks[nameOfPack].files[key] - return acc - }, {}) + const sorted = Object.keys(this.knownPacks[nameOfPack].files) + .sort() + .reduce((acc, key) => { + if (key.length === 0) return acc + acc[key] = this.knownPacks[nameOfPack].files[key] + return acc + }, {}) this.knownPacks[nameOfPack].files = sorted - } + }, }, - mounted () { + mounted() { this.refreshPackList() - } + }, } export default EmojiTab diff --git a/src/components/settings_modal/admin_tabs/federation_tab.js b/src/components/settings_modal/admin_tabs/federation_tab.js index 97b4e0040..33c688eb4 100644 --- a/src/components/settings_modal/admin_tabs/federation_tab.js +++ b/src/components/settings_modal/admin_tabs/federation_tab.js @@ -1,19 +1,18 @@ -import BooleanSetting from '../helpers/boolean_setting.vue' -import IntegerSetting from '../helpers/integer_setting.vue' -import StringSetting from '../helpers/string_setting.vue' -import GroupSetting from '../helpers/group_setting.vue' import AttachmentSetting from '../helpers/attachment_setting.vue' +import BooleanSetting from '../helpers/boolean_setting.vue' +import GroupSetting from '../helpers/group_setting.vue' +import IntegerSetting from '../helpers/integer_setting.vue' import ListSetting from '../helpers/list_setting.vue' import ListTupleSetting from '../helpers/list_tuple_setting.vue' import MapSetting from '../helpers/map_setting.vue' - import SharedComputedObject from '../helpers/shared_computed_object.js' +import StringSetting from '../helpers/string_setting.vue' const FederationTab = { - provide () { + provide() { return { defaultDraftMode: true, - defaultSource: 'admin' + defaultSource: 'admin', } }, components: { @@ -24,11 +23,11 @@ const FederationTab = { ListSetting, ListTupleSetting, GroupSetting, - MapSetting + MapSetting, }, computed: { - ...SharedComputedObject() - } + ...SharedComputedObject(), + }, } export default FederationTab diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.js b/src/components/settings_modal/admin_tabs/frontends_tab.js index c63e76214..7563adae0 100644 --- a/src/components/settings_modal/admin_tabs/frontends_tab.js +++ b/src/components/settings_modal/admin_tabs/frontends_tab.js @@ -1,32 +1,28 @@ +import PanelLoading from 'src/components/panel_loading/panel_loading.vue' +import Popover from 'src/components/popover/popover.vue' +import { useInterfaceStore } from 'src/stores/interface' import BooleanSetting from '../helpers/boolean_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue' -import IntegerSetting from '../helpers/integer_setting.vue' -import StringSetting from '../helpers/string_setting.vue' import GroupSetting from '../helpers/group_setting.vue' -import Popover from 'src/components/popover/popover.vue' -import PanelLoading from 'src/components/panel_loading/panel_loading.vue' -import { useInterfaceStore } from 'src/stores/interface' - +import IntegerSetting from '../helpers/integer_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faGlobe -} from '@fortawesome/free-solid-svg-icons' +import StringSetting from '../helpers/string_setting.vue' -library.add( - faGlobe -) +import { library } from '@fortawesome/fontawesome-svg-core' +import { faGlobe } from '@fortawesome/free-solid-svg-icons' + +library.add(faGlobe) const FrontendsTab = { - provide () { + provide() { return { defaultDraftMode: true, - defaultSource: 'admin' + defaultSource: 'admin', } }, - data () { + data() { return { - working: false + working: false, } }, components: { @@ -36,26 +32,26 @@ const FrontendsTab = { StringSetting, GroupSetting, PanelLoading, - Popover + Popover, }, - created () { + created() { if (this.user.rights.admin) { this.$store.dispatch('loadFrontendsStuff') } }, computed: { ...SharedComputedObject(), - frontends () { + frontends() { return this.$store.state.adminSettings.frontends - } + }, }, methods: { - canInstall (frontend) { - const fe = this.frontends.find(f => f.name === frontend.name) + canInstall(frontend) { + const fe = this.frontends.find((f) => f.name === frontend.name) if (!fe) return false return fe.refs.includes(frontend.ref) }, - getSuggestedRef (frontend) { + getSuggestedRef(frontend) { if (this.adminDraft) { const defaultFe = this.adminDraft[':pleroma'][':frontends'][':primary'] if (defaultFe?.name === frontend.name && this.canInstall(defaultFe)) { @@ -67,13 +63,14 @@ const FrontendsTab = { return frontend.refs[0] } }, - update (frontend, suggestRef) { + update(frontend, suggestRef) { const ref = suggestRef || this.getSuggestedRef(frontend) const { name } = frontend const payload = { name, ref } this.working = true - this.$store.state.api.backendInteractor.installFrontend({ payload }) + this.$store.state.api.backendInteractor + .installFrontend({ payload }) .finally(() => { this.working = false }) @@ -86,29 +83,32 @@ const FrontendsTab = { messageKey: 'admin_dash.frontend.failure_installing_frontend', messageArgs: { version: name + '/' + ref, - reason: reason.error + reason: reason.error, }, - timeout: 5000 + timeout: 5000, }) } else { useInterfaceStore().pushGlobalNotice({ level: 'success', messageKey: 'admin_dash.frontend.success_installing_frontend', messageArgs: { - version: name + '/' + ref + version: name + '/' + ref, }, - timeout: 2000 + timeout: 2000, }) } }) }, - setDefault (frontend, suggestRef) { + setDefault(frontend, suggestRef) { const ref = suggestRef || this.getSuggestedRef(frontend) const { name } = frontend - this.$store.commit('updateAdminDraft', { path: [':pleroma', ':frontends', ':primary'], value: { name, ref } }) - } - } + this.$store.commit('updateAdminDraft', { + path: [':pleroma', ':frontends', ':primary'], + value: { name, ref }, + }) + }, + }, } export default FrontendsTab diff --git a/src/components/settings_modal/admin_tabs/http_tab.js b/src/components/settings_modal/admin_tabs/http_tab.js index 82e2bb010..ea76ebe6f 100644 --- a/src/components/settings_modal/admin_tabs/http_tab.js +++ b/src/components/settings_modal/admin_tabs/http_tab.js @@ -1,22 +1,22 @@ -import BooleanSetting from '../helpers/boolean_setting.vue' -import ChoiceSetting from '../helpers/choice_setting.vue' -import IntegerSetting from '../helpers/integer_setting.vue' -import StringSetting from '../helpers/string_setting.vue' -import GroupSetting from '../helpers/group_setting.vue' -import AttachmentSetting from '../helpers/attachment_setting.vue' -import ListSetting from '../helpers/list_setting.vue' -import TupleSetting from '../helpers/tuple_setting.vue' -import MapSetting from '../helpers/map_setting.vue' -import ProxySetting from '../helpers/proxy_setting.vue' - -import SharedComputedObject from '../helpers/shared_computed_object.js' import { get } from 'lodash' +import AttachmentSetting from '../helpers/attachment_setting.vue' +import BooleanSetting from '../helpers/boolean_setting.vue' +import ChoiceSetting from '../helpers/choice_setting.vue' +import GroupSetting from '../helpers/group_setting.vue' +import IntegerSetting from '../helpers/integer_setting.vue' +import ListSetting from '../helpers/list_setting.vue' +import MapSetting from '../helpers/map_setting.vue' +import ProxySetting from '../helpers/proxy_setting.vue' +import SharedComputedObject from '../helpers/shared_computed_object.js' +import StringSetting from '../helpers/string_setting.vue' +import TupleSetting from '../helpers/tuple_setting.vue' + const HTTPTab = { - provide () { + provide() { return { defaultDraftMode: true, - defaultSource: 'admin' + defaultSource: 'admin', } }, components: { @@ -29,18 +29,23 @@ const HTTPTab = { GroupSetting, ListSetting, TupleSetting, - ProxySetting + ProxySetting, }, computed: { ...SharedComputedObject(), - sslOptions () { - const desc = get(this.$store.state.adminSettings.descriptions, ':pleroma.:http.:adapter.:ssl_options.:versions') - return new Set(desc.suggestions.map(option => ({ - label: option.replace(':tlsv', 'TLS v'), - value: option - }))) + sslOptions() { + const desc = get( + this.$store.state.adminSettings.descriptions, + ':pleroma.:http.:adapter.:ssl_options.:versions', + ) + return new Set( + desc.suggestions.map((option) => ({ + label: option.replace(':tlsv', 'TLS v'), + value: option, + })), + ) }, - } + }, } export default HTTPTab diff --git a/src/components/settings_modal/admin_tabs/instance_tab.js b/src/components/settings_modal/admin_tabs/instance_tab.js index ddbd58a88..67d04c303 100644 --- a/src/components/settings_modal/admin_tabs/instance_tab.js +++ b/src/components/settings_modal/admin_tabs/instance_tab.js @@ -1,22 +1,22 @@ -import BooleanSetting from '../helpers/boolean_setting.vue' -import ChoiceSetting from '../helpers/choice_setting.vue' -import IntegerSetting from '../helpers/integer_setting.vue' -import StringSetting from '../helpers/string_setting.vue' -import ColorSetting from '../helpers/color_setting.vue' -import GroupSetting from '../helpers/group_setting.vue' -import AttachmentSetting from '../helpers/attachment_setting.vue' -import ListSetting from '../helpers/list_setting.vue' -import PWAManifestIconsSetting from '../helpers/pwa_manifest_icons_setting.vue' -import MapSetting from '../helpers/map_setting.vue' - -import SharedComputedObject from '../helpers/shared_computed_object.js' import { get } from 'lodash' +import AttachmentSetting from '../helpers/attachment_setting.vue' +import BooleanSetting from '../helpers/boolean_setting.vue' +import ChoiceSetting from '../helpers/choice_setting.vue' +import ColorSetting from '../helpers/color_setting.vue' +import GroupSetting from '../helpers/group_setting.vue' +import IntegerSetting from '../helpers/integer_setting.vue' +import ListSetting from '../helpers/list_setting.vue' +import MapSetting from '../helpers/map_setting.vue' +import PWAManifestIconsSetting from '../helpers/pwa_manifest_icons_setting.vue' +import SharedComputedObject from '../helpers/shared_computed_object.js' +import StringSetting from '../helpers/string_setting.vue' + const InstanceTab = { - provide () { + provide() { return { defaultDraftMode: true, - defaultSource: 'admin' + defaultSource: 'admin', } }, components: { @@ -29,25 +29,40 @@ const InstanceTab = { ListSetting, PWAManifestIconsSetting, MapSetting, - GroupSetting + GroupSetting, }, computed: { ...SharedComputedObject(), - providersOptions () { - const desc = get(this.$store.state.adminSettings.descriptions, [':pleroma', 'Pleroma.Web.Metadata', ':providers']) - return new Set(desc.suggestions.map(option => ({ - label: option.replace('Pleroma.Web.Metadata.Providers.', ''), - value: option - }))) + providersOptions() { + const desc = get(this.$store.state.adminSettings.descriptions, [ + ':pleroma', + 'Pleroma.Web.Metadata', + ':providers', + ]) + return new Set( + desc.suggestions.map((option) => ({ + label: option.replace('Pleroma.Web.Metadata.Providers.', ''), + value: option, + })), + ) }, - limitLocalContentOptions () { - const desc = get(this.$store.state.adminSettings.descriptions, [':pleroma', ':instance', ':limit_to_local_content']) - return new Set(desc.suggestions.map(option => ({ - label: option !== 'false' ? this.$t('admin_dash.instance.' + option) : this.$t('general.no'), - value: option - }))) - } - } + limitLocalContentOptions() { + const desc = get(this.$store.state.adminSettings.descriptions, [ + ':pleroma', + ':instance', + ':limit_to_local_content', + ]) + return new Set( + desc.suggestions.map((option) => ({ + label: + option !== 'false' + ? this.$t('admin_dash.instance.' + option) + : this.$t('general.no'), + value: option, + })), + ) + }, + }, } export default InstanceTab diff --git a/src/components/settings_modal/admin_tabs/instance_tab.vue b/src/components/settings_modal/admin_tabs/instance_tab.vue index fa55cef41..585fe303c 100644 --- a/src/components/settings_modal/admin_tabs/instance_tab.vue +++ b/src/components/settings_modal/admin_tabs/instance_tab.vue @@ -49,10 +49,16 @@
  • - +
  • - +
  • diff --git a/src/components/settings_modal/admin_tabs/job_queues_tab.js b/src/components/settings_modal/admin_tabs/job_queues_tab.js index b0f583c55..66a0e7e18 100644 --- a/src/components/settings_modal/admin_tabs/job_queues_tab.js +++ b/src/components/settings_modal/admin_tabs/job_queues_tab.js @@ -1,19 +1,18 @@ +import AttachmentSetting from '../helpers/attachment_setting.vue' import BooleanSetting from '../helpers/boolean_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue' +import GroupSetting from '../helpers/group_setting.vue' import IntegerSetting from '../helpers/integer_setting.vue' +import ListSetting from '../helpers/list_setting.vue' +import SharedComputedObject from '../helpers/shared_computed_object.js' import StringSetting from '../helpers/string_setting.vue' import TupleSetting from '../helpers/tuple_setting.vue' -import GroupSetting from '../helpers/group_setting.vue' -import AttachmentSetting from '../helpers/attachment_setting.vue' -import ListSetting from '../helpers/list_setting.vue' - -import SharedComputedObject from '../helpers/shared_computed_object.js' const JobQueuesTab = { - provide () { + provide() { return { defaultDraftMode: true, - defaultSource: 'admin' + defaultSource: 'admin', } }, components: { @@ -24,11 +23,11 @@ const JobQueuesTab = { TupleSetting, AttachmentSetting, GroupSetting, - ListSetting + ListSetting, }, computed: { - ...SharedComputedObject() - } + ...SharedComputedObject(), + }, } export default JobQueuesTab diff --git a/src/components/settings_modal/admin_tabs/limits_tab.js b/src/components/settings_modal/admin_tabs/limits_tab.js index c1126f49c..1f9a7be54 100644 --- a/src/components/settings_modal/admin_tabs/limits_tab.js +++ b/src/components/settings_modal/admin_tabs/limits_tab.js @@ -1,20 +1,19 @@ import BooleanSetting from '../helpers/boolean_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue' import IntegerSetting from '../helpers/integer_setting.vue' -import StringSetting from '../helpers/string_setting.vue' - import SharedComputedObject from '../helpers/shared_computed_object.js' +import StringSetting from '../helpers/string_setting.vue' const LimitsTab = { components: { BooleanSetting, ChoiceSetting, IntegerSetting, - StringSetting + StringSetting, }, computed: { - ...SharedComputedObject() - } + ...SharedComputedObject(), + }, } export default LimitsTab diff --git a/src/components/settings_modal/admin_tabs/links_tab.js b/src/components/settings_modal/admin_tabs/links_tab.js index 026b099a5..b3b5f1f2e 100644 --- a/src/components/settings_modal/admin_tabs/links_tab.js +++ b/src/components/settings_modal/admin_tabs/links_tab.js @@ -1,21 +1,20 @@ -import BooleanSetting from '../helpers/boolean_setting.vue' -import ChoiceSetting from '../helpers/choice_setting.vue' -import IntegerSetting from '../helpers/integer_setting.vue' -import StringSetting from '../helpers/string_setting.vue' -import GroupSetting from '../helpers/group_setting.vue' -import AttachmentSetting from '../helpers/attachment_setting.vue' -import ListSetting from '../helpers/list_setting.vue' - -import Checkbox from 'src/components/checkbox/checkbox.vue' - -import SharedComputedObject from '../helpers/shared_computed_object.js' import { get } from 'lodash' +import Checkbox from 'src/components/checkbox/checkbox.vue' +import AttachmentSetting from '../helpers/attachment_setting.vue' +import BooleanSetting from '../helpers/boolean_setting.vue' +import ChoiceSetting from '../helpers/choice_setting.vue' +import GroupSetting from '../helpers/group_setting.vue' +import IntegerSetting from '../helpers/integer_setting.vue' +import ListSetting from '../helpers/list_setting.vue' +import SharedComputedObject from '../helpers/shared_computed_object.js' +import StringSetting from '../helpers/string_setting.vue' + const LinksTab = { - provide () { + provide() { return { defaultDraftMode: true, - defaultSource: 'admin' + defaultSource: 'admin', } }, components: { @@ -26,84 +25,109 @@ const LinksTab = { AttachmentSetting, GroupSetting, ListSetting, - Checkbox + Checkbox, }, computed: { - classIsPresent () { - return this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Formatter'][':class'] !== false + classIsPresent() { + return ( + this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Formatter'][ + ':class' + ] !== false + ) }, - relIsPresent () { - return this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Formatter'][':rel'] !== false + relIsPresent() { + return ( + this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Formatter'][ + ':rel' + ] !== false + ) }, - truncateIsPresent () { - return this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Formatter'][':truncate'] !== false + truncateIsPresent() { + return ( + this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Formatter'][ + ':truncate' + ] !== false + ) }, - truncateDescription () { - return get(this.$store.state.adminSettings.descriptions, [':pleroma', 'Pleroma.Formatter', ':truncate']) + truncateDescription() { + return get(this.$store.state.adminSettings.descriptions, [ + ':pleroma', + 'Pleroma.Formatter', + ':truncate', + ]) }, - ttlSettersOptions () { - const desc = get(this.$store.state.adminSettings.descriptions, ':pleroma.:rich_media.:ttl_setters') - return new Set(desc.suggestions.map(option => ({ - label: option.replace('Pleroma.Web.RichMedia.Parser.TTL.', ''), - value: option - }))) + ttlSettersOptions() { + const desc = get( + this.$store.state.adminSettings.descriptions, + ':pleroma.:rich_media.:ttl_setters', + ) + return new Set( + desc.suggestions.map((option) => ({ + label: option.replace('Pleroma.Web.RichMedia.Parser.TTL.', ''), + value: option, + })), + ) }, - parsersOptions () { - const desc = get(this.$store.state.adminSettings.descriptions, ':pleroma.:rich_media.:parsers') - return new Set(desc.suggestions.map(option => ({ - label: option.replace('Pleroma.Web.RichMedia.Parsers.', ''), - value: option - }))) + parsersOptions() { + const desc = get( + this.$store.state.adminSettings.descriptions, + ':pleroma.:rich_media.:parsers', + ) + return new Set( + desc.suggestions.map((option) => ({ + label: option.replace('Pleroma.Web.RichMedia.Parsers.', ''), + value: option, + })), + ) }, - validateTLDOptions () { - return [{ - label: this.$t('general.yes'), - value: true - }, { - label: this.$t('general.no'), - value: false - }, { - label: this.$t('admin_dash.links.no_scheme'), - value: ':no_scheme' - }] + validateTLDOptions() { + return [ + { + label: this.$t('general.yes'), + value: true, + }, + { + label: this.$t('general.no'), + value: false, + }, + { + label: this.$t('admin_dash.links.no_scheme'), + value: ':no_scheme', + }, + ] }, - mediaProxyEnabled () { - return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][':enabled'] + mediaProxyEnabled() { + return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][ + ':enabled' + ] }, - mediaInvalidationProvider () { - return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][':invalidation'][':provider'] + mediaInvalidationProvider() { + return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][ + ':invalidation' + ][':provider'] }, - ...SharedComputedObject() + ...SharedComputedObject(), }, methods: { - checkRel (e) { - this.$store.commit( - 'updateAdminDraft', - { - path: [':pleroma','Pleroma.Formatter',':rel'], - value: e ? '' : false - } - ) + checkRel(e) { + this.$store.commit('updateAdminDraft', { + path: [':pleroma', 'Pleroma.Formatter', ':rel'], + value: e ? '' : false, + }) }, - checkClass (e) { - this.$store.commit( - 'updateAdminDraft', - { - path: [':pleroma','Pleroma.Formatter',':class'], - value: e ? '' : false - } - ) + checkClass(e) { + this.$store.commit('updateAdminDraft', { + path: [':pleroma', 'Pleroma.Formatter', ':class'], + value: e ? '' : false, + }) }, - checkTruncate (e) { - this.$store.commit( - 'updateAdminDraft', - { - path: [':pleroma','Pleroma.Formatter',':truncate'], - value: e ? 20 : false - } - ) - } - } + checkTruncate(e) { + this.$store.commit('updateAdminDraft', { + path: [':pleroma', 'Pleroma.Formatter', ':truncate'], + value: e ? 20 : false, + }) + }, + }, } export default LinksTab diff --git a/src/components/settings_modal/admin_tabs/mailer_tab.js b/src/components/settings_modal/admin_tabs/mailer_tab.js index 44b2f33df..0b909334b 100644 --- a/src/components/settings_modal/admin_tabs/mailer_tab.js +++ b/src/components/settings_modal/admin_tabs/mailer_tab.js @@ -1,18 +1,17 @@ +import AttachmentSetting from '../helpers/attachment_setting.vue' import BooleanSetting from '../helpers/boolean_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue' -import IntegerSetting from '../helpers/integer_setting.vue' -import StringSetting from '../helpers/string_setting.vue' -import GroupSetting from '../helpers/group_setting.vue' import ColorSetting from '../helpers/color_setting.vue' -import AttachmentSetting from '../helpers/attachment_setting.vue' - +import GroupSetting from '../helpers/group_setting.vue' +import IntegerSetting from '../helpers/integer_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' +import StringSetting from '../helpers/string_setting.vue' const MailerTab = { - provide () { + provide() { return { defaultDraftMode: true, - defaultSource: 'admin' + defaultSource: 'admin', } }, components: { @@ -22,44 +21,50 @@ const MailerTab = { StringSetting, AttachmentSetting, ColorSetting, - GroupSetting + GroupSetting, }, computed: { - adaptersLabels () { + adaptersLabels() { const prefix = 'Swoosh.Adapters.' const descriptions = this.$store.state.adminSettings.descriptions - const options = descriptions[':pleroma']['Pleroma.Emails.Mailer'][':adapter'].suggestions + const options = + descriptions[':pleroma']['Pleroma.Emails.Mailer'][':adapter'] + .suggestions - return Object.fromEntries(options.map(value => [ - value, value.replace(prefix, '') - ])) + return Object.fromEntries( + options.map((value) => [value, value.replace(prefix, '')]), + ) }, - startTLSLabels () { + startTLSLabels() { return { ':always': this.$t('admin_dash.generic_enforcement.always'), ':if_available': this.$t('admin_dash.generic_enforcement.if_available'), - ':never': this.$t('admin_dash.generic_enforcement.never') + ':never': this.$t('admin_dash.generic_enforcement.never'), } // return Object.fromEntries(options.map(value => [ // value, value.replace(prefix, '') // ])) }, - adapter () { - return this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Emails.Mailer'][':adapter'] + adapter() { + return this.$store.state.adminSettings.draft[':pleroma'][ + 'Pleroma.Emails.Mailer' + ][':adapter'] }, - mailerEnabled () { - return this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Emails.Mailer'][':enabled'] + mailerEnabled() { + return this.$store.state.adminSettings.draft[':pleroma'][ + 'Pleroma.Emails.Mailer' + ][':enabled'] }, - ...SharedComputedObject() + ...SharedComputedObject(), }, methods: { - adapterHasKey (key) { + adapterHasKey(key) { const descriptions = this.$store.state.adminSettings.descriptions const mailerStuff = descriptions[':pleroma']['Pleroma.Emails.Mailer'] const adapterStuff = mailerStuff[':subgroup,' + this.adapter] - return Object.prototype.hasOwnProperty.call(adapterStuff, key) - } - } + return Object.hasOwn(adapterStuff, key) + }, + }, } export default MailerTab diff --git a/src/components/settings_modal/admin_tabs/media_proxy_tab.js b/src/components/settings_modal/admin_tabs/media_proxy_tab.js index af82593bc..6c7231312 100644 --- a/src/components/settings_modal/admin_tabs/media_proxy_tab.js +++ b/src/components/settings_modal/admin_tabs/media_proxy_tab.js @@ -1,18 +1,17 @@ +import AttachmentSetting from '../helpers/attachment_setting.vue' import BooleanSetting from '../helpers/boolean_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue' -import IntegerSetting from '../helpers/integer_setting.vue' -import StringSetting from '../helpers/string_setting.vue' import GroupSetting from '../helpers/group_setting.vue' -import AttachmentSetting from '../helpers/attachment_setting.vue' +import IntegerSetting from '../helpers/integer_setting.vue' import ListSetting from '../helpers/list_setting.vue' - import SharedComputedObject from '../helpers/shared_computed_object.js' +import StringSetting from '../helpers/string_setting.vue' const MediaProxyTab = { - provide () { + provide() { return { defaultDraftMode: true, - defaultSource: 'admin' + defaultSource: 'admin', } }, components: { @@ -22,17 +21,21 @@ const MediaProxyTab = { StringSetting, AttachmentSetting, GroupSetting, - ListSetting + ListSetting, }, computed: { - mediaProxyEnabled () { - return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][':enabled'] + mediaProxyEnabled() { + return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][ + ':enabled' + ] }, - mediaInvalidationProvider () { - return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][':invalidation'][':provider'] + mediaInvalidationProvider() { + return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][ + ':invalidation' + ][':provider'] }, - ...SharedComputedObject() - } + ...SharedComputedObject(), + }, } export default MediaProxyTab diff --git a/src/components/settings_modal/admin_tabs/monitoring_tab.js b/src/components/settings_modal/admin_tabs/monitoring_tab.js index 8593b9d69..1ba9770f0 100644 --- a/src/components/settings_modal/admin_tabs/monitoring_tab.js +++ b/src/components/settings_modal/admin_tabs/monitoring_tab.js @@ -1,26 +1,22 @@ +import AttachmentSetting from '../helpers/attachment_setting.vue' import BooleanSetting from '../helpers/boolean_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue' -import IntegerSetting from '../helpers/integer_setting.vue' -import StringSetting from '../helpers/string_setting.vue' import GroupSetting from '../helpers/group_setting.vue' -import AttachmentSetting from '../helpers/attachment_setting.vue' +import IntegerSetting from '../helpers/integer_setting.vue' import ListSetting from '../helpers/list_setting.vue' - import SharedComputedObject from '../helpers/shared_computed_object.js' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faGlobe -} from '@fortawesome/free-solid-svg-icons' +import StringSetting from '../helpers/string_setting.vue' -library.add( - faGlobe -) +import { library } from '@fortawesome/fontawesome-svg-core' +import { faGlobe } from '@fortawesome/free-solid-svg-icons' + +library.add(faGlobe) const MonitoringTab = { - provide () { + provide() { return { defaultDraftMode: true, - defaultSource: 'admin' + defaultSource: 'admin', } }, components: { @@ -30,13 +26,12 @@ const MonitoringTab = { StringSetting, AttachmentSetting, GroupSetting, - ListSetting + ListSetting, }, computed: { - ...SharedComputedObject() + ...SharedComputedObject(), }, - methods: { - } + methods: {}, } export default MonitoringTab diff --git a/src/components/settings_modal/admin_tabs/other_tab.js b/src/components/settings_modal/admin_tabs/other_tab.js index dc6550d27..a31bcf307 100644 --- a/src/components/settings_modal/admin_tabs/other_tab.js +++ b/src/components/settings_modal/admin_tabs/other_tab.js @@ -1,21 +1,20 @@ +import AttachmentSetting from '../helpers/attachment_setting.vue' import BooleanSetting from '../helpers/boolean_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue' -import IntegerSetting from '../helpers/integer_setting.vue' -import StringSetting from '../helpers/string_setting.vue' import ColorSetting from '../helpers/color_setting.vue' import GroupSetting from '../helpers/group_setting.vue' -import AttachmentSetting from '../helpers/attachment_setting.vue' +import IntegerSetting from '../helpers/integer_setting.vue' import ListSetting from '../helpers/list_setting.vue' -import PWAManifestIconsSetting from '../helpers/pwa_manifest_icons_setting.vue' import MapSetting from '../helpers/map_setting.vue' - +import PWAManifestIconsSetting from '../helpers/pwa_manifest_icons_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' +import StringSetting from '../helpers/string_setting.vue' const OtherTab = { - provide () { + provide() { return { defaultDraftMode: true, - defaultSource: 'admin' + defaultSource: 'admin', } }, components: { @@ -28,11 +27,11 @@ const OtherTab = { ListSetting, PWAManifestIconsSetting, MapSetting, - GroupSetting + GroupSetting, }, computed: { - ...SharedComputedObject() - } + ...SharedComputedObject(), + }, } export default OtherTab diff --git a/src/components/settings_modal/admin_tabs/posts_tab.js b/src/components/settings_modal/admin_tabs/posts_tab.js index d9f213594..654b18ed8 100644 --- a/src/components/settings_modal/admin_tabs/posts_tab.js +++ b/src/components/settings_modal/admin_tabs/posts_tab.js @@ -1,21 +1,20 @@ +import AttachmentSetting from '../helpers/attachment_setting.vue' import BooleanSetting from '../helpers/boolean_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue' -import IntegerSetting from '../helpers/integer_setting.vue' -import StringSetting from '../helpers/string_setting.vue' import ColorSetting from '../helpers/color_setting.vue' import GroupSetting from '../helpers/group_setting.vue' -import AttachmentSetting from '../helpers/attachment_setting.vue' +import IntegerSetting from '../helpers/integer_setting.vue' import ListSetting from '../helpers/list_setting.vue' -import PWAManifestIconsSetting from '../helpers/pwa_manifest_icons_setting.vue' import MapSetting from '../helpers/map_setting.vue' - +import PWAManifestIconsSetting from '../helpers/pwa_manifest_icons_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' +import StringSetting from '../helpers/string_setting.vue' const PostsTab = { - provide () { + provide() { return { defaultDraftMode: true, - defaultSource: 'admin' + defaultSource: 'admin', } }, components: { @@ -28,11 +27,11 @@ const PostsTab = { ListSetting, PWAManifestIconsSetting, MapSetting, - GroupSetting + GroupSetting, }, computed: { - ...SharedComputedObject() - } + ...SharedComputedObject(), + }, } export default PostsTab diff --git a/src/components/settings_modal/admin_tabs/rates_tab.js b/src/components/settings_modal/admin_tabs/rates_tab.js index c602dcc8a..ddf18b035 100644 --- a/src/components/settings_modal/admin_tabs/rates_tab.js +++ b/src/components/settings_modal/admin_tabs/rates_tab.js @@ -1,20 +1,19 @@ import RateSetting from '../helpers/rate_setting.vue' - import SharedComputedObject from '../helpers/shared_computed_object.js' const RatesTab = { - provide () { + provide() { return { defaultDraftMode: true, - defaultSource: 'admin' + defaultSource: 'admin', } }, components: { RateSetting, }, computed: { - ...SharedComputedObject() - } + ...SharedComputedObject(), + }, } export default RatesTab diff --git a/src/components/settings_modal/admin_tabs/registrations_tab.js b/src/components/settings_modal/admin_tabs/registrations_tab.js index 3ce6c8044..8ec5e0d4b 100644 --- a/src/components/settings_modal/admin_tabs/registrations_tab.js +++ b/src/components/settings_modal/admin_tabs/registrations_tab.js @@ -1,19 +1,18 @@ +import AttachmentSetting from '../helpers/attachment_setting.vue' import BooleanSetting from '../helpers/boolean_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue' +import GroupSetting from '../helpers/group_setting.vue' import IntegerSetting from '../helpers/integer_setting.vue' +import ListSetting from '../helpers/list_setting.vue' +import SharedComputedObject from '../helpers/shared_computed_object.js' import StringSetting from '../helpers/string_setting.vue' import TupleSetting from '../helpers/tuple_setting.vue' -import GroupSetting from '../helpers/group_setting.vue' -import AttachmentSetting from '../helpers/attachment_setting.vue' -import ListSetting from '../helpers/list_setting.vue' - -import SharedComputedObject from '../helpers/shared_computed_object.js' const RegistrationsTab = { - provide () { + provide() { return { defaultDraftMode: true, - defaultSource: 'admin' + defaultSource: 'admin', } }, components: { @@ -24,11 +23,11 @@ const RegistrationsTab = { TupleSetting, AttachmentSetting, GroupSetting, - ListSetting + ListSetting, }, computed: { - ...SharedComputedObject() - } + ...SharedComputedObject(), + }, } export default RegistrationsTab diff --git a/src/components/settings_modal/admin_tabs/uploads_tab.js b/src/components/settings_modal/admin_tabs/uploads_tab.js index 40a184db1..760206499 100644 --- a/src/components/settings_modal/admin_tabs/uploads_tab.js +++ b/src/components/settings_modal/admin_tabs/uploads_tab.js @@ -1,46 +1,51 @@ import BooleanSetting from '../helpers/boolean_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue' import IntegerSetting from '../helpers/integer_setting.vue' +import SharedComputedObject from '../helpers/shared_computed_object.js' import StringSetting from '../helpers/string_setting.vue' -import SharedComputedObject from '../helpers/shared_computed_object.js' - const UploadsTab = { - provide () { + provide() { return { defaultDraftMode: true, - defaultSource: 'admin' + defaultSource: 'admin', } }, - data () { + data() { return { - uploaders: [{ - key: 'Pleroma.Uploaders.Local', - value: 'Pleroma.Uploaders.Local', - label: this.$t('admin_dash.uploads.local_uploader') - }, { - key: 'Pleroma.Uploaders.IPFS', - value: 'Pleroma.Uploaders.IPFS', - label: 'IPFS' - }, { - key: 'Pleroma.Uploaders.S3', - value: 'Pleroma.Uploaders.S3', - label: 'S3' - }] + uploaders: [ + { + key: 'Pleroma.Uploaders.Local', + value: 'Pleroma.Uploaders.Local', + label: this.$t('admin_dash.uploads.local_uploader'), + }, + { + key: 'Pleroma.Uploaders.IPFS', + value: 'Pleroma.Uploaders.IPFS', + label: 'IPFS', + }, + { + key: 'Pleroma.Uploaders.S3', + value: 'Pleroma.Uploaders.S3', + label: 'S3', + }, + ], } }, components: { BooleanSetting, ChoiceSetting, IntegerSetting, - StringSetting + StringSetting, }, computed: { - uploader () { - return this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Upload'][':uploader'] + uploader() { + return this.$store.state.adminSettings.draft[':pleroma'][ + 'Pleroma.Upload' + ][':uploader'] }, - ...SharedComputedObject() - } + ...SharedComputedObject(), + }, } export default UploadsTab diff --git a/src/components/settings_modal/helpers/attachment_setting.js b/src/components/settings_modal/helpers/attachment_setting.js index c4c04b2b6..61e00a353 100644 --- a/src/components/settings_modal/helpers/attachment_setting.js +++ b/src/components/settings_modal/helpers/attachment_setting.js @@ -1,7 +1,7 @@ -import Setting from './setting.js' -import { fileTypeExt } from 'src/services/file_type/file_type.service.js' -import MediaUpload from 'src/components/media_upload/media_upload.vue' import Attachment from 'src/components/attachment/attachment.vue' +import MediaUpload from 'src/components/media_upload/media_upload.vue' +import { fileTypeExt } from 'src/services/file_type/file_type.service.js' +import Setting from './setting.js' export default { ...Setting, @@ -11,34 +11,36 @@ export default { acceptTypes: { type: String, required: false, - default: 'image/*' - } + default: 'image/*', + }, }, components: { ...Setting.components, MediaUpload, - Attachment + Attachment, }, computed: { ...Setting.computed, - attachment () { + attachment() { const path = this.realDraftMode ? this.draft : this.state // The "server" part is primarily for local dev, but could be useful for alt-domain or multiuser usage. - const url = path.includes('://') ? path : this.$store.state.instance.server + path + const url = path.includes('://') + ? path + : this.$store.state.instance.server + path return { mimetype: fileTypeExt(url), - url + url, } - } + }, }, methods: { ...Setting.methods, - setMediaFile (fileInfo) { + setMediaFile(fileInfo) { if (this.realDraftMode) { this.draft = fileInfo.url } else { this.configSink(this.path, fileInfo.url) } - } - } + }, + }, } diff --git a/src/components/settings_modal/helpers/boolean_setting.js b/src/components/settings_modal/helpers/boolean_setting.js index 199d3d0f0..e7b56f58c 100644 --- a/src/components/settings_modal/helpers/boolean_setting.js +++ b/src/components/settings_modal/helpers/boolean_setting.js @@ -5,27 +5,27 @@ export default { ...Setting, props: { ...Setting.props, - indeterminateState: [String, Object] + indeterminateState: [String, Object], }, components: { ...Setting.components, - Checkbox + Checkbox, }, computed: { ...Setting.computed, - isIndeterminate () { + isIndeterminate() { return this.visibleState === this.indeterminateState - } + }, }, methods: { ...Setting.methods, - getValue (e) { + getValue(e) { // Basic tri-state toggle implementation if (!!this.indeterminateState && !e && this.visibleState === true) { // If we have indeterminate state, switching from true to false first goes through indeterminate return this.indeterminateState } return e - } - } + }, + }, } diff --git a/src/components/settings_modal/helpers/choice_setting.js b/src/components/settings_modal/helpers/choice_setting.js index 10e043082..e5bc8420e 100644 --- a/src/components/settings_modal/helpers/choice_setting.js +++ b/src/components/settings_modal/helpers/choice_setting.js @@ -5,50 +5,50 @@ export default { ...Setting, components: { ...Setting.components, - Select + Select, }, props: { ...Setting.props, overrideOptions: { type: Boolean, - required: false + required: false, }, options: { type: Array, - required: false + required: false, }, optionLabelMap: { type: Object, required: false, - default: {} - } + default: {}, + }, }, computed: { ...Setting.computed, - realOptions () { + realOptions() { if (this.overrideOptions) { return this.options } if (this.realSource === 'admin') { if ( !this.backendDescriptionSuggestions?.length || - this.backendDescriptionSuggestions?.length === 0 + this.backendDescriptionSuggestions?.length === 0 ) { return this.options } - return this.backendDescriptionSuggestions.map(x => ({ + return this.backendDescriptionSuggestions.map((x) => ({ key: x, value: x, - label: this.optionLabelMap[x] || x + label: this.optionLabelMap[x] || x, })) } return this.options - } + }, }, methods: { ...Setting.methods, - getValue (e) { + getValue(e) { return e - } - } + }, + }, } diff --git a/src/components/settings_modal/helpers/color_setting.js b/src/components/settings_modal/helpers/color_setting.js index 40fa038fe..d9b1e1397 100644 --- a/src/components/settings_modal/helpers/color_setting.js +++ b/src/components/settings_modal/helpers/color_setting.js @@ -1,16 +1,16 @@ -import Setting from './setting.js' import ColorInput from 'src/components/color_input/color_input.vue' +import Setting from './setting.js' export default { ...Setting, components: { ...Setting.components, - ColorInput + ColorInput, }, methods: { ...Setting.methods, - getValue (e) { + getValue(e) { return e - } - } + }, + }, } diff --git a/src/components/settings_modal/helpers/draft_buttons.vue b/src/components/settings_modal/helpers/draft_buttons.vue index b07ea1ec4..cc7010619 100644 --- a/src/components/settings_modal/helpers/draft_buttons.vue +++ b/src/components/settings_modal/helpers/draft_buttons.vue @@ -58,16 +58,15 @@ diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue index d25b02da2..9fdcdd233 100644 --- a/src/components/settings_modal/helpers/emoji_editing_popover.vue +++ b/src/components/settings_modal/helpers/emoji_editing_popover.vue @@ -149,10 +149,10 @@ diff --git a/src/components/settings_modal/helpers/float_setting.vue b/src/components/settings_modal/helpers/float_setting.vue index 15edb3c3e..a1c495cd1 100644 --- a/src/components/settings_modal/helpers/float_setting.vue +++ b/src/components/settings_modal/helpers/float_setting.vue @@ -10,7 +10,7 @@ import NumberSetting from './number_setting.vue' export default { components: { - NumberSetting - } + NumberSetting, + }, } diff --git a/src/components/settings_modal/helpers/help_indicator.vue b/src/components/settings_modal/helpers/help_indicator.vue index c48f97b70..add6ab753 100644 --- a/src/components/settings_modal/helpers/help_indicator.vue +++ b/src/components/settings_modal/helpers/help_indicator.vue @@ -19,15 +19,14 @@ diff --git a/src/components/settings_modal/helpers/integer_setting.vue b/src/components/settings_modal/helpers/integer_setting.vue index 8206c0c45..e9b227645 100644 --- a/src/components/settings_modal/helpers/integer_setting.vue +++ b/src/components/settings_modal/helpers/integer_setting.vue @@ -11,7 +11,7 @@ import NumberSetting from './number_setting.vue' export default { components: { - NumberSetting - } + NumberSetting, + }, } diff --git a/src/components/settings_modal/helpers/list_setting.js b/src/components/settings_modal/helpers/list_setting.js index def6064d0..c1d504cd5 100644 --- a/src/components/settings_modal/helpers/list_setting.js +++ b/src/components/settings_modal/helpers/list_setting.js @@ -3,43 +3,43 @@ import Setting from './setting.js' export default { ...Setting, - data () { + data() { return { newValue: '', } }, components: { ...Setting.components, - Checkbox + Checkbox, }, props: { ...Setting.props, ignoreSuggestions: { required: false, - type: Boolean + type: Boolean, }, overrideAvailableOptions: { required: false, - type: Boolean + type: Boolean, }, options: { required: false, - type: Set + type: Set, }, allowNew: { required: false, type: Boolean, - default: true + default: true, }, forceNew: { required: false, type: Boolean, - default: false - } + default: false, + }, }, computed: { ...Setting.computed, - showNew () { + showNew() { if (this.forceNew) return true if (!this.allowNew) return false @@ -52,10 +52,10 @@ export default { return true } }, - valueSet () { + valueSet() { return new Set(this.visibleState) }, - suggestionsSet () { + suggestionsSet() { const suggestions = this.backendDescriptionSuggestions if (suggestions) { return new Set(suggestions) @@ -63,14 +63,14 @@ export default { return new Set() } }, - extraEntries () { + extraEntries() { if (this.ignoreSuggestions) return [...this.valueSet.values()] if (!this.suggestionsSet) return [] return [...this.valueSet.values()].filter((x) => { return !this.builtinEntriesValueSet.has(x) }) }, - builtinEntries () { + builtinEntries() { if (this.ignoreSuggestions) return [] if (this.overrideAvailableOptions) { return [...this.options] @@ -80,19 +80,19 @@ export default { const builtins = [...this.suggestionsSet.values()] return builtins.map((option) => ({ label: option, - value: option + value: option, })) }, - builtinEntriesValueSet () { - return new Set(this.builtinEntries.map(x => x.value)) - } + builtinEntriesValueSet() { + return new Set(this.builtinEntries.map((x) => x.value)) + }, }, methods: { ...Setting.methods, - optionPresent (option) { + optionPresent(option) { return this.valueSet.has(option) }, - getValue ({ event, value, index, eventType }) { + getValue({ event, value, index, eventType }) { switch (eventType) { case 'toggle': { this.newValue = '' @@ -128,6 +128,6 @@ export default { return [...pre, string, ...post] } } - } - } + }, + }, } diff --git a/src/components/settings_modal/helpers/list_tuple_setting.js b/src/components/settings_modal/helpers/list_tuple_setting.js index 2cbd93788..33cc725b1 100644 --- a/src/components/settings_modal/helpers/list_tuple_setting.js +++ b/src/components/settings_modal/helpers/list_tuple_setting.js @@ -2,14 +2,14 @@ import ListSetting from './list_setting.js' export default { ...ListSetting, - data () { + data() { return { - newValue: ['',''] + newValue: ['', ''], } }, methods: { ...ListSetting.methods, - getValue ({ event, index, eventType, tuple }) { + getValue({ event, index, eventType, tuple }) { switch (eventType) { case 'add': { if (!this.newValue[0] || !this.newValue[1]) return this.visibleState @@ -39,6 +39,6 @@ export default { } } } - } - } + }, + }, } diff --git a/src/components/settings_modal/helpers/map_setting.js b/src/components/settings_modal/helpers/map_setting.js index ad93ceacf..8615c90ca 100644 --- a/src/components/settings_modal/helpers/map_setting.js +++ b/src/components/settings_modal/helpers/map_setting.js @@ -7,28 +7,31 @@ export default { allowNew: { required: false, type: Boolean, - default: true - } + default: true, + }, }, - data () { + data() { return { - newValue: ['',''] // avoiding extra complexity by just using an array instead of an object + newValue: ['', ''], // avoiding extra complexity by just using an array instead of an object } }, computed: { ...Setting.computed, // state that we'll show in the UI, i.e. transforming map into list - displayState () { + displayState() { return Object.entries(this.visibleState) - } + }, }, methods: { ...Setting.methods, - getValue ({ event, key, eventType, isKey }) { + getValue({ event, key, eventType, isKey }) { switch (eventType) { case 'add': { if (key === '') return this.visibleState - const res = {...this.visibleState, ...Object.fromEntries([this.newValue])} + const res = { + ...this.visibleState, + ...Object.fromEntries([this.newValue]), + } this.newValue = ['', ''] return res } @@ -36,35 +39,35 @@ export default { case 'remove': { // initial state for this type is empty array if (Array.isArray(this.visibleState)) return this.visibleState - const newEntries = Object.entries(this.visibleState).filter(([k]) => k !== key) + const newEntries = Object.entries(this.visibleState).filter( + ([k]) => k !== key, + ) - if (newEntries.length === 0 ) return [] + if (newEntries.length === 0) return [] return Object.fromEntries(newEntries) } case 'edit': { const string = event.target.value - const newEntries = Object - .entries(this.visibleState) - .map(([k, v]) => { - if (isKey) { - if (k === key) { - return [string, v] - } else { - return [k, v] - } + const newEntries = Object.entries(this.visibleState).map(([k, v]) => { + if (isKey) { + if (k === key) { + return [string, v] } else { - if (k === key) { - return [k, string] - } else { - return [k, v] - } + return [k, v] } - }) + } else { + if (k === key) { + return [k, string] + } else { + return [k, v] + } + } + }) return Object.fromEntries(newEntries) } } - } - } + }, + }, } diff --git a/src/components/settings_modal/helpers/modified_indicator.vue b/src/components/settings_modal/helpers/modified_indicator.vue index a747cebd2..c723dfe19 100644 --- a/src/components/settings_modal/helpers/modified_indicator.vue +++ b/src/components/settings_modal/helpers/modified_indicator.vue @@ -24,12 +24,11 @@ diff --git a/src/components/settings_modal/helpers/number_setting.js b/src/components/settings_modal/helpers/number_setting.js index afbf5ed58..e037ece28 100644 --- a/src/components/settings_modal/helpers/number_setting.js +++ b/src/components/settings_modal/helpers/number_setting.js @@ -7,33 +7,33 @@ export default { min: { type: Number, required: false, - default: 1 + default: 1, }, max: { type: Number, required: false, - default: 1 + default: 1, }, step: { type: Number, required: false, - default: 1 + default: 1, }, truncate: { type: Number, required: false, - default: 1 - } + default: 1, + }, }, methods: { ...Setting.methods, - getValue (e) { + getValue(e) { if (!this.truncate === 1) { return parseInt(e.target.value) } else if (this.truncate > 1) { return Math.trunc(e.target.value / this.truncate) * this.truncate } return parseFloat(e.target.value) - } - } + }, + }, } diff --git a/src/components/settings_modal/helpers/profile_setting_indicator.vue b/src/components/settings_modal/helpers/profile_setting_indicator.vue index d160781b1..3b45b125d 100644 --- a/src/components/settings_modal/helpers/profile_setting_indicator.vue +++ b/src/components/settings_modal/helpers/profile_setting_indicator.vue @@ -24,16 +24,15 @@ diff --git a/src/components/settings_modal/helpers/proxy_setting.js b/src/components/settings_modal/helpers/proxy_setting.js index ca717b60b..4195b8430 100644 --- a/src/components/settings_modal/helpers/proxy_setting.js +++ b/src/components/settings_modal/helpers/proxy_setting.js @@ -1,18 +1,19 @@ import Checkbox from 'src/components/checkbox/checkbox.vue' import Setting from './setting.js' -const getUrl = state => state?.tuple ? state.tuple[1] + ':' + state.tuple[2] : state -const getSocks = state => state?.tuple +const getUrl = (state) => + state?.tuple ? state.tuple[1] + ':' + state.tuple[2] : state +const getSocks = (state) => state?.tuple export default { ...Setting, - data () { + data() { return { urlField: '', - socksField: false + socksField: false, } }, - created () { + created() { Setting.created() this.urlField = getUrl(this.realDraftMode ? this.draft : this.state) this.socksField = getSocks(this.realDraftMode ? this.draft : this.state) @@ -20,23 +21,23 @@ export default { computed: { ...Setting.computed, // state that we'll show in the UI, i.e. transforming map into list - displayState () { + displayState() { if (this.visibleState?.tuple) { return this.visibleState.tuple[1] + ':' + this.visibleState.tuple[2] } return this.visibleState }, - socksState () { + socksState() { return getSocks(this.visibleState) - } + }, }, components: { ...Setting.components, - Checkbox + Checkbox, }, methods: { ...Setting.methods, - getValue ({ event, isProxy}) { + getValue({ event, isProxy }) { if (isProxy) { this.socksField = event } else { @@ -44,10 +45,10 @@ export default { } if (this.socksField) { - return { tuple: [ ':socks5', ...this.urlField.split(':') ] } + return { tuple: [':socks5', ...this.urlField.split(':')] } } else { return this.urlField } - } - } + }, + }, } diff --git a/src/components/settings_modal/helpers/pwa_manifest_icons_setting.js b/src/components/settings_modal/helpers/pwa_manifest_icons_setting.js index 9986f6f39..6d6cdd121 100644 --- a/src/components/settings_modal/helpers/pwa_manifest_icons_setting.js +++ b/src/components/settings_modal/helpers/pwa_manifest_icons_setting.js @@ -1,10 +1,10 @@ import { clone } from 'lodash' -import { fileTypeExt } from 'src/services/file_type/file_type.service.js' -import Setting from './setting.js' -import Select from 'src/components/select/select.vue' import Attachment from 'src/components/attachment/attachment.vue' import MediaUpload from 'src/components/media_upload/media_upload.vue' +import Select from 'src/components/select/select.vue' +import { fileTypeExt } from 'src/services/file_type/file_type.service.js' +import Setting from './setting.js' export default { ...Setting, @@ -12,60 +12,62 @@ export default { ...Setting.components, Select, Attachment, - MediaUpload + MediaUpload, }, computed: { ...Setting.computed, - purposeOptions () { - return ['any','monochrome','maskable'].map(value => ({ + purposeOptions() { + return ['any', 'monochrome', 'maskable'].map((value) => ({ value, key: value, - label: this.$t('admin_dash.instance.pwa.icon.' + value) + label: this.$t('admin_dash.instance.pwa.icon.' + value), })) - } + }, }, methods: { ...Setting.methods, - attachment (e) { + attachment(e) { const path = e[':src'] if (!path) { return { mimetype: '', - url: '' + url: '', } } - const url = path.includes('://') ? path : this.$store.state.instance.server + path + const url = path.includes('://') + ? path + : this.$store.state.instance.server + path return { mimetype: fileTypeExt(url), - url + url, } }, - setMediaFile ({ event, index }) { + setMediaFile({ event, index }) { this.update({ event: { target: { - value: event.url + value: event.url, }, }, index, eventType: 'edit', - field: ':src' + field: ':src', }) }, - setPurpose ({ event, index }) { + setPurpose({ event, index }) { this.update({ event: { target: { - value: event + value: event, }, }, index, eventType: 'edit', - field: ':purpose' + field: ':purpose', }) }, - getValue ({ event, field, index, eventType }) { + getValue({ event, field, index, eventType }) { switch (eventType) { case 'add': { const res = [...this.visibleState, {}] @@ -85,7 +87,7 @@ export default { const item = clone(this.visibleState[index]) const string = event.target.value - if (!string) { + if (!string) { delete item[field] } else { item[field] = string @@ -94,6 +96,6 @@ export default { return [...pre, item, ...post] } } - } - } + }, + }, } diff --git a/src/components/settings_modal/helpers/pwa_manifest_icons_setting.vue b/src/components/settings_modal/helpers/pwa_manifest_icons_setting.vue index 20e50fe25..f8054d266 100644 --- a/src/components/settings_modal/helpers/pwa_manifest_icons_setting.vue +++ b/src/components/settings_modal/helpers/pwa_manifest_icons_setting.vue @@ -85,8 +85,8 @@ @update:model-value="event => setPurpose({ event, index })" >