diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 3de57a360..000000000 --- a/.dockerignore +++ /dev/null @@ -1,12 +0,0 @@ -node_modules/ -dist/ -logs/ -.DS_Store -.git/ -config/local.json -pleroma-backend/ -test/e2e/reports/ -test/e2e-playwright/test-results/ -test/e2e-playwright/playwright-report/ -__screenshots__/ - diff --git a/.gitignore b/.gitignore index c4a96ee1e..01ffda9a8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,11 +4,8 @@ dist/ npm-debug.log test/unit/coverage test/e2e/reports -test/e2e-playwright/test-results -test/e2e-playwright/playwright-report selenium-debug.log .idea/ -.gitlab-ci-local/ config/local.json src/assets/emoji.json logs/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 06fbf45f9..99c85dd36 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,23 +34,12 @@ check-changelog: - apk add git - sh ./tools/check-changelog -lint-eslint: +lint: stage: lint script: - yarn - - yarn ci-eslint - -lint-biome: - stage: lint - script: - - yarn - - yarn ci-biome - -lint-stylelint: - stage: lint - script: - - yarn - - yarn ci-stylelint + - yarn lint + - yarn stylelint test: stage: test @@ -71,135 +60,6 @@ test: - test/**/__screenshots__ when: on_failure -e2e-pleroma: - stage: test - image: mcr.microsoft.com/playwright:v1.57.0-jammy - services: - - name: postgres:15-alpine - alias: db - - name: $PLEROMA_IMAGE - alias: pleroma - entrypoint: ["/bin/ash", "-c"] - command: - - | - set -eu - - SEED_SENTINEL_PATH=/var/lib/pleroma/.e2e_seeded - CONFIG_OVERRIDE_PATH=/var/lib/pleroma/config.exs - - echo '-- Waiting for database...' - while ! pg_isready -U ${DB_USER:-pleroma} -d postgres://${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-pleroma} -t 1; do - sleep 1s - done - - echo '-- Writing E2E config overrides...' - cat > $CONFIG_OVERRIDE_PATH </dev/null; then - kill -TERM $PLEROMA_PID - wait $PLEROMA_PID || true - fi - } - - trap cleanup INT TERM - - echo '-- Waiting for API...' - api_ok=false - for _i in $(seq 1 120); do - if wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null 2>&1; then - api_ok=true - break - fi - sleep 1s - done - - if [ $api_ok != true ]; then - echo 'Timed out waiting for Pleroma API to become available' - exit 1 - fi - - if [ ! -f $SEED_SENTINEL_PATH ]; then - if [ -n ${E2E_ADMIN_USERNAME:-} ] && [ -n ${E2E_ADMIN_PASSWORD:-} ] && [ -n ${E2E_ADMIN_EMAIL:-} ]; then - echo '-- Seeding admin user' $E2E_ADMIN_USERNAME '...' - if ! /opt/pleroma/bin/pleroma_ctl user new $E2E_ADMIN_USERNAME $E2E_ADMIN_EMAIL --admin --password $E2E_ADMIN_PASSWORD -y; then - echo '-- User already exists or creation failed, ensuring admin + confirmed...' - /opt/pleroma/bin/pleroma_ctl user set $E2E_ADMIN_USERNAME --admin --confirmed - fi - else - echo '-- Skipping admin seeding (missing E2E_ADMIN_* env)' - fi - - touch $SEED_SENTINEL_PATH - fi - - wait $PLEROMA_PID - tags: - - amd64 - - himem - variables: - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1" - FF_NETWORK_PER_BUILD: "true" - PLEROMA_IMAGE: git.pleroma.social:5050/pleroma/pleroma:stable - POSTGRES_USER: pleroma - POSTGRES_PASSWORD: pleroma - POSTGRES_DB: pleroma - DB_USER: pleroma - DB_PASS: pleroma - DB_NAME: pleroma - DB_HOST: db - DB_PORT: 5432 - DOMAIN: localhost - INSTANCE_NAME: Pleroma E2E - E2E_ADMIN_USERNAME: admin - E2E_ADMIN_PASSWORD: adminadmin - E2E_ADMIN_EMAIL: admin@example.com - ADMIN_EMAIL: $E2E_ADMIN_EMAIL - NOTIFY_EMAIL: $E2E_ADMIN_EMAIL - VITE_PROXY_TARGET: http://pleroma:4000 - VITE_PROXY_ORIGIN: http://localhost:4000 - E2E_BASE_URL: http://localhost:8080 - script: - - npm install -g yarn@1.22.22 - - yarn --frozen-lockfile - - | - echo "-- Waiting for Pleroma API..." - api_ok="false" - for _i in $(seq 1 120); do - if wget -qO- http://pleroma:4000/api/v1/instance >/dev/null 2>&1; then - api_ok="true" - break - fi - sleep 1s - done - if [ "$api_ok" != "true" ]; then - echo "Timed out waiting for Pleroma API to become available" - exit 1 - fi - - yarn e2e:pw - artifacts: - when: on_failure - paths: - - test/e2e-playwright/test-results - - test/e2e-playwright/playwright-report - build: stage: build tags: diff --git a/.gitlab/merge_request_templates/Release.md b/.gitlab/merge_request_templates/Release.md deleted file mode 100644 index d02e14a73..000000000 --- a/.gitlab/merge_request_templates/Release.md +++ /dev/null @@ -1,8 +0,0 @@ -### Release checklist -* [ ] Bump version in `package.json` -* [ ] Compile a changelog with the `tools/collect-changelog` script -* [ ] Create an MR with an announcement to pleroma.social -#### post-merge -* [ ] Tag the release on the merge commit -* [ ] Make the tag into a Gitlab Releaseā„¢ -* [ ] Merge `master` into `develop` (in case the fixes are already in develop, use `git merge -s ours --no-commit` and manually merge the changelogs) diff --git a/.stylelintrc.json b/.stylelintrc.json index afdfd5f5b..c91107595 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -12,8 +12,6 @@ "custom-property-pattern": null, "keyframes-name-pattern": null, "scss/operator-no-newline-after": null, - "declaration-property-value-no-unknown": true, - "scss/declaration-property-value-no-unknown": true, "declaration-block-no-redundant-longhand-properties": [ true, { diff --git a/CHANGELOG.md b/CHANGELOG.md index b3d38ea24..c2f0e7d17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,67 +2,6 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - -## 2.10.0 -### Changed -- Temporary changes modal now shows actual countdown instead of fixed timeout -- Disabled elements are more disabled now -- Rearranged and split settings to make more sense and be less of a wall of text -- On mobile settings now take up full width and presented in navigation style -improved styles for settings - -### Added -- Most of the remaining AdminFE tabs were added into Admin Dashboard -- It's now possible to customize PWA Manfiest from PleromaFE -- Make every configuration option default-overridable by instance admins - -### Fixed -- Fixed settings not appearing if user never touched "show advanced" toggle -- Fix display of the broken/deleted/banned users -- Fixed incorrect emoji display in post interaction lists -- Fixed list title not being saved when editing -- Fixed poll notifications not being expandable - - -## 2.9.3 -### Fixed -- Being unable to update profile - -## 2.9.2 -### Changed -- BREAKING: due to some internal technical changes logging into AdminFE through PleromaFE is no longer possible -- User card/profile got an overhaul -- Profile editing overhaul -- Visually combined subject and content fields in post form -- Moved post form's emoji button into input field -- Minor visual changes and fixes -- Clicking on fav/rt/emoji notifications' contents expands/collapses it -- Reduced time taken processing theme by half -- Splash screen only appears if loading takes more than 2 seconds - -### Added -- Mutes received an update, adding support for regex, muting based on username and expiration time. -- Mutes are now synchronized across sessions -- Support for expiring mutes and blocks (if available) -- Clicking on emoji shows bigger version of it alongside with its shortcode - - Admins also are able to copy it into a local pack -- Added support for Akkoma and IceShrimp.NET backends -- Compatibility with stricter CSP (Akkoma backend) -- Added a way to upload new packs from a URL or ZIP file via the Admin Dashboard -- Unify show/hide content buttons -- Add support for detachable scrollTop button -- Option to left-align user bio -- Cache assets and emojis with service worker -- Indicate currently active V3 theme as a body element class -- Add arithmetic blend ISS function - -### Fixed -- Display counter for status action buttons when they are in the menu -- Fix bookmark button alignment in the extra actions menu -- Instance favicons are no longer stretched -- A lot more scalable UI fixes - - Emoji picker now should work fine when emoji size is increased - ## 2.8.0 ### Changed - BREAKING: static/img/nsfw.2958239.png is now static/img/nsfw.DepQPhG0.png, which may affect people who specify exactly this path as the cover image @@ -95,8 +34,8 @@ This does not guarantee that browsers will or will not work. - Support displaying time in absolute format - Add draft management system - Compress most kinds of images on upload. -- Added option to always convert images to JPEG format instead of using WebP when compressing images. -- Added configurable image compression option in general settings, allowing users to control whether images are compressed before upload. +- Added option to always convert images to JPEG format instead of using WebP when compressing images. +- Added configurable image compression option in general settings, allowing users to control whether images are compressed before upload. - Inform users that Smithereen public polls are public - Splash screen + loading indicator to make process of identifying initialization issues and load performance - UI for making v3 themes and palettes, support for bundling v3 themes diff --git a/biome.json b/biome.json deleted file mode 100644 index d64639d52..000000000 --- a/biome.json +++ /dev/null @@ -1,145 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json", - "vcs": { - "enabled": true, - "clientKind": "git", - "useIgnoreFile": true - }, - "files": { - "includes": ["**", "!!**/dist", "!!tools/emojis.json"] - }, - "formatter": { - "enabled": true, - "indentStyle": "space" - }, - "linter": { - "enabled": true, - "domains": { - "vue": "recommended" - }, - "rules": { - "recommended": false, - "complexity": { - "noAdjacentSpacesInRegex": "error", - "noExtraBooleanCast": "error", - "noUselessCatch": "error", - "noUselessEscapeInRegex": "error" - }, - "correctness": { - "noConstAssign": "error", - "noConstantCondition": "error", - "noEmptyCharacterClassInRegex": "error", - "noEmptyPattern": "error", - "noGlobalObjectCalls": "error", - "noInvalidBuiltinInstantiation": "error", - "noInvalidConstructorSuper": "error", - "noNonoctalDecimalEscape": "error", - "noPrecisionLoss": "error", - "noSelfAssign": "error", - "noSetterReturn": "error", - "noSwitchDeclarations": "error", - "noUndeclaredVariables": "error", - "noUnreachable": "error", - "noUnreachableSuper": "error", - "noUnsafeFinally": "error", - "noUnsafeOptionalChaining": "error", - "noUnusedLabels": "error", - "noUnusedPrivateClassMembers": "error", - "noUnusedVariables": "error", - "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 8c5968a30..73c1eeb15 100644 --- a/build/check-versions.mjs +++ b/build/check-versions.mjs @@ -1,5 +1,5 @@ -import chalk from 'chalk' import semver from 'semver' +import chalk from 'chalk' import packageConfig from '../package.json' with { type: 'json' } @@ -7,8 +7,8 @@ var versionRequirements = [ { name: 'node', currentVersion: semver.clean(process.version), - versionRequirement: packageConfig.engines.node, - }, + versionRequirement: packageConfig.engines.node + } ] export default function () { @@ -16,22 +16,15 @@ 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 c60355804..c104af5d9 100644 --- a/build/commit_hash.js +++ b/build/commit_hash.js @@ -1,8 +1,8 @@ import childProcess from 'child_process' -export const getCommitHash = () => { - const subst = '$Format:%h$' - if (!subst.match(/Format:/)) { +export const getCommitHash = (() => { + const subst = "$Format:%h$" + if(!subst.match(/Format:/)) { return subst } else { try { @@ -15,4 +15,4 @@ export const getCommitHash = () => { return 'UNKNOWN' } } -} +}) diff --git a/build/copy_plugin.js b/build/copy_plugin.js index 4f020f359..a783fe7ff 100644 --- a/build/copy_plugin.js +++ b/build/copy_plugin.js @@ -1,8 +1,8 @@ -import { cp } from 'node:fs/promises' -import { resolve } from 'node:path' import serveStatic from 'serve-static' +import { resolve } from 'node:path' +import { cp } from 'node:fs/promises' -const getPrefix = (s) => { +const getPrefix = s => { const padEnd = s.endsWith('/') ? s : s + '/' return padEnd.startsWith('/') ? padEnd : '/' + padEnd } @@ -13,31 +13,28 @@ const copyPlugin = ({ inUrl, inFs }) => { let copyTarget const handler = serveStatic(inFs) - return [ - { - name: 'copy-plugin-serve', - apply: 'serve', - configureServer(server) { - server.middlewares.use(prefix, handler) - }, + return [{ + name: 'copy-plugin-serve', + apply: 'serve', + configureServer (server) { + server.middlewares.use(prefix, handler) + } + }, { + name: 'copy-plugin-build', + apply: 'build', + configResolved (config) { + copyTarget = resolve(config.root, config.build.outDir, subdir) }, - { - name: 'copy-plugin-build', - apply: 'build', - configResolved(config) { - copyTarget = resolve(config.root, config.build.outDir, subdir) - }, - closeBundle: { - order: 'post', - sequential: true, - async handler() { - console.info(`Copying '${inFs}' to ${copyTarget}...`) - await cp(inFs, copyTarget, { recursive: true }) - console.info('Done.') - }, - }, - }, - ] + closeBundle: { + order: 'post', + sequential: true, + async handler () { + console.log(`Copying '${inFs}' to ${copyTarget}...`) + await cp(inFs, copyTarget, { recursive: true }) + console.log('Done.') + } + } + }] } export default copyPlugin diff --git a/build/emojis_plugin.js b/build/emojis_plugin.js index 7979086dd..aed52066d 100644 --- a/build/emojis_plugin.js +++ b/build/emojis_plugin.js @@ -1,23 +1,21 @@ -import { access } from 'node:fs/promises' import { resolve } from 'node:path' - -import { languages } from '../src/i18n/languages.js' +import { access } from 'node:fs/promises' +import { languages, langCodeToCldrName } from '../src/i18n/languages.js' const annotationsImportPrefix = '@kazvmoe-infra/unicode-emoji-json/annotations/' const specialAnnotationsLocale = { - ja_easy: 'ja', + ja_easy: 'ja' } -const internalToAnnotationsLocale = (internal) => - specialAnnotationsLocale[internal] || internal +const internalToAnnotationsLocale = (internal) => specialAnnotationsLocale[internal] || internal // This gets all the annotations that are accessible (whose language // can be chosen in the settings). Data for other languages are // discarded because there is no way for it to be fetched. const getAllAccessibleAnnotations = async (projectRoot) => { - const imports = ( - await Promise.all( - languages.map(async (lang) => { + const imports = (await Promise.all( + languages + .map(async lang => { const destLang = internalToAnnotationsLocale(lang) const importModule = `${annotationsImportPrefix}${destLang}.json` const importFile = resolve(projectRoot, 'node_modules', importModule) @@ -25,14 +23,11 @@ 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 = { @@ -48,21 +43,21 @@ const emojisPlugin = () => { let projectRoot return { name: 'emojis-plugin', - configResolved(conf) { + configResolved (conf) { projectRoot = conf.root }, - resolveId(id) { + resolveId (id) { if (id === emojiAnnotationsId) { return emojiAnnotationsIdResolved } return null }, - async load(id) { + async load (id) { if (id === emojiAnnotationsIdResolved) { return await getAllAccessibleAnnotations(projectRoot) } return null - }, + } } } diff --git a/build/msw_plugin.js b/build/msw_plugin.js index c4e9098c5..f544348fc 100644 --- a/build/msw_plugin.js +++ b/build/msw_plugin.js @@ -1,5 +1,5 @@ -import { readFile } from 'node:fs/promises' import { resolve } from 'node:path' +import { readFile } from 'node:fs/promises' const target = 'node_modules/msw/lib/mockServiceWorker.js' @@ -8,10 +8,10 @@ const mswPlugin = () => { return { name: 'msw-plugin', apply: 'serve', - configResolved(conf) { + configResolved (conf) { projectRoot = conf.root }, - configureServer(server) { + configureServer (server) { server.middlewares.use(async (req, res, next) => { if (req.path === '/mockServiceWorker.js') { const file = await readFile(resolve(projectRoot, target)) @@ -21,7 +21,7 @@ const mswPlugin = () => { next() } }) - }, + } } } diff --git a/build/service_worker_messages.js b/build/service_worker_messages.js index 0948aa919..c078e8563 100644 --- a/build/service_worker_messages.js +++ b/build/service_worker_messages.js @@ -1,12 +1,11 @@ +import { languages, langCodeToJsonName } from '../src/i18n/languages.js' import { readFile } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' -import { langCodeToJsonName, languages } from '../src/i18n/languages.js' - const i18nDir = resolve( dirname(dirname(fileURLToPath(import.meta.url))), - 'src/i18n', + 'src/i18n' ) export const i18nFiles = languages.reduce((acc, lang) => { @@ -17,15 +16,13 @@ export const i18nFiles = languages.reduce((acc, lang) => { }, {}) export const generateServiceWorkerMessages = async () => { - const msgArray = await Promise.all( - Object.entries(i18nFiles).map(async ([lang, file]) => { - const fileContent = await readFile(file, 'utf-8') - const msg = { - notifications: JSON.parse(fileContent).notifications || {}, - } - return [lang, msg] - }), - ) + const msgArray = await Promise.all(Object.entries(i18nFiles).map(async ([lang, file]) => { + const fileContent = await readFile(file, 'utf-8') + const msg = { + notifications: JSON.parse(fileContent).notifications || {} + } + return [lang, msg] + })) return msgArray.reduce((acc, [lang, msg]) => { acc[lang] = msg return acc diff --git a/build/sw_plugin.js b/build/sw_plugin.js index 03c5978d7..a2c792b7d 100644 --- a/build/sw_plugin.js +++ b/build/sw_plugin.js @@ -1,13 +1,9 @@ -import { readFile } from 'node:fs/promises' -import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' -import * as esbuild from 'esbuild' +import { dirname, resolve } from 'node:path' +import { readFile } from 'node:fs/promises' import { build } from 'vite' - -import { - generateServiceWorkerMessages, - i18nFiles, -} from './service_worker_messages.js' +import * as esbuild from 'esbuild' +import { generateServiceWorkerMessages, i18nFiles } from './service_worker_messages.js' const getSWMessagesAsText = async () => { const messages = await generateServiceWorkerMessages() @@ -18,10 +14,14 @@ 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,10 +31,9 @@ export const devSwPlugin = ({ swSrc, swDest, transformSW, alias }) => { return { name: 'dev-sw-plugin', apply: 'serve', - configResolved() { - /* no-op */ + configResolved (conf) { }, - resolveId(id) { + resolveId (id) { const name = id.startsWith('/') ? id.slice(1) : id if (name === swDest) { return swFullSrc @@ -43,7 +42,7 @@ export const devSwPlugin = ({ swSrc, swDest, transformSW, alias }) => { } return null }, - async load(id) { + async load (id) { if (id === swFullSrc) { return readFile(swFullSrc, 'utf-8') } else if (id === swEnvNameResolved) { @@ -56,7 +55,7 @@ export const devSwPlugin = ({ swSrc, swDest, transformSW, alias }) => { * 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], @@ -64,54 +63,52 @@ export const devSwPlugin = ({ swSrc, swDest, transformSW, alias }) => { 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)), + 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' })) - }, - }, - { - 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(), + b.onLoad( + { filter: /.*/, namespace: 'sw-messages' }, + async () => ({ + contents: await getSWMessagesAsText() })) - }, - }, - ], + } + }, { + name: 'sw-env', + setup (b) { + b.onResolve( + { filter: new RegExp('^' + swEnvName + '$') }, + args => ({ + path: args.path, + namespace: 'sw-env' + })) + b.onLoad( + { filter: /.*/, namespace: 'sw-env' }, + () => ({ + contents: getDevSwEnv() + })) + } + }] }) const text = res.outputFiles[0].text return text } - }, + } } } @@ -121,13 +118,16 @@ export const devSwPlugin = ({ swSrc, swDest, transformSW, alias }) => { // 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 = ({ swSrc, swDest }) => { 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() { - console.info('Building service worker for production') + async handler () { + console.log('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 4ff7e1de8..5d578ba61 100644 --- a/build/update-emoji.js +++ b/build/update-emoji.js @@ -1,21 +1,22 @@ -import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with { - type: 'json', -} + +import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with { type: 'json' } import fs from 'fs' -Object.keys(emojis).map((k) => { - emojis[k].map((e) => { - delete e.unicode_version - delete e.emoji_version - delete e.skin_tone_support_unicode_version +Object.keys(emojis) + .map(k => { + emojis[k].map(e => { + delete e.unicode_version + delete e.emoji_version + delete e.skin_tone_support_unicode_version + }) }) -}) const res = {} -Object.keys(emojis).map((k) => { - const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase() - res[groupId] = emojis[k] -}) +Object.keys(emojis) + .map(k => { + const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase() + res[groupId] = emojis[k] + }) console.info('Updating emojis...') fs.writeFileSync('src/assets/emoji.json', JSON.stringify(res)) diff --git a/changelog.d/action-button-extra-counter.add b/changelog.d/action-button-extra-counter.add new file mode 100644 index 000000000..7d5c77447 --- /dev/null +++ b/changelog.d/action-button-extra-counter.add @@ -0,0 +1 @@ +Display counter for status action buttons when they are on the menu diff --git a/changelog.d/actor-type.fix b/changelog.d/actor-type.fix deleted file mode 100644 index a2c873c1a..000000000 --- a/changelog.d/actor-type.fix +++ /dev/null @@ -1 +0,0 @@ -fixed being unable to set actor type from profile page diff --git a/changelog.d/akkoma-sharkey-net-support.add b/changelog.d/akkoma-sharkey-net-support.add new file mode 100644 index 000000000..4b4bff7fe --- /dev/null +++ b/changelog.d/akkoma-sharkey-net-support.add @@ -0,0 +1 @@ +Added support for Akkoma and IceShrimp.NET backend diff --git a/changelog.d/biome.skip b/changelog.d/akkoma.skip similarity index 100% rename from changelog.d/biome.skip rename to changelog.d/akkoma.skip diff --git a/changelog.d/arithmetic-blend.add b/changelog.d/arithmetic-blend.add new file mode 100644 index 000000000..c579dca28 --- /dev/null +++ b/changelog.d/arithmetic-blend.add @@ -0,0 +1,2 @@ +Add arithmetic blend ISS function + diff --git a/changelog.d/better-scroll-button.add b/changelog.d/better-scroll-button.add new file mode 100644 index 000000000..b206869d1 --- /dev/null +++ b/changelog.d/better-scroll-button.add @@ -0,0 +1 @@ +Add support for detachable scrollTop button diff --git a/changelog.d/bookmark-button-align.fix b/changelog.d/bookmark-button-align.fix new file mode 100644 index 000000000..64bc2c807 --- /dev/null +++ b/changelog.d/bookmark-button-align.fix @@ -0,0 +1 @@ +Fix bookmark button alignment in the extra actions menu diff --git a/changelog.d/csp.add b/changelog.d/csp.add new file mode 100644 index 000000000..260337b97 --- /dev/null +++ b/changelog.d/csp.add @@ -0,0 +1 @@ +Compatibility with stricter CSP (Akkoma backend) diff --git a/changelog.d/e2e-tests.add b/changelog.d/e2e-tests.add deleted file mode 100644 index ba62b25ac..000000000 --- a/changelog.d/e2e-tests.add +++ /dev/null @@ -1 +0,0 @@ -Add playwright E2E-tests with an optional docker-based backend diff --git a/changelog.d/e2e.skip b/changelog.d/e2e.skip deleted file mode 100644 index e84c25121..000000000 --- a/changelog.d/e2e.skip +++ /dev/null @@ -1 +0,0 @@ -fix e2e diff --git a/changelog.d/filter-fixes.skip b/changelog.d/filter-fixes.skip new file mode 100644 index 000000000..e69de29bb diff --git a/changelog.d/fix-wrap.skip b/changelog.d/fix-wrap.skip new file mode 100644 index 000000000..e69de29bb diff --git a/changelog.d/migrate-auth-flow-pinia.skip b/changelog.d/migrate-auth-flow-pinia.skip new file mode 100644 index 000000000..e69de29bb diff --git a/changelog.d/migrate-oauth-tokens-module-to-pinia-store.skip b/changelog.d/migrate-oauth-tokens-module-to-pinia-store.skip new file mode 100644 index 000000000..e69de29bb diff --git a/changelog.d/mutes-sync.add b/changelog.d/mutes-sync.add new file mode 100644 index 000000000..e8e0e462a --- /dev/null +++ b/changelog.d/mutes-sync.add @@ -0,0 +1 @@ +Synchronized mutes, advanced mute control (regexp, expiry, naming) diff --git a/changelog.d/profile-error.fix b/changelog.d/profile-error.fix new file mode 100644 index 000000000..f123db5ae --- /dev/null +++ b/changelog.d/profile-error.fix @@ -0,0 +1 @@ +Fix error styling for user profiles diff --git a/changelog.d/small-fixes.skip b/changelog.d/small-fixes.skip new file mode 100644 index 000000000..e69de29bb diff --git a/changelog.d/sw-cache-assets.add b/changelog.d/sw-cache-assets.add new file mode 100644 index 000000000..5f7414eee --- /dev/null +++ b/changelog.d/sw-cache-assets.add @@ -0,0 +1 @@ +Cache assets and emojis with service worker diff --git a/changelog.d/theme3-body-class.add b/changelog.d/theme3-body-class.add new file mode 100644 index 000000000..f3d36fd70 --- /dev/null +++ b/changelog.d/theme3-body-class.add @@ -0,0 +1 @@ +Indicate currently active V3 theme as a body element class diff --git a/changelog.d/unify-show-hide-buttons.add b/changelog.d/unify-show-hide-buttons.add new file mode 100644 index 000000000..663bc38a5 --- /dev/null +++ b/changelog.d/unify-show-hide-buttons.add @@ -0,0 +1 @@ +Unify show/hide content buttons diff --git a/docker-compose.e2e.yml b/docker-compose.e2e.yml deleted file mode 100644 index 75a4979a1..000000000 --- a/docker-compose.e2e.yml +++ /dev/null @@ -1,57 +0,0 @@ -services: - db: - image: postgres:15-alpine - environment: - POSTGRES_USER: pleroma - POSTGRES_PASSWORD: pleroma - POSTGRES_DB: pleroma - healthcheck: - test: ["CMD-SHELL", "pg_isready -U pleroma -d pleroma"] - interval: 2s - timeout: 2s - retries: 30 - - pleroma: - image: ${PLEROMA_IMAGE:-git.pleroma.social:5050/pleroma/pleroma:stable} - environment: - DB_USER: pleroma - DB_PASS: pleroma - DB_NAME: pleroma - DB_HOST: db - DB_PORT: 5432 - DOMAIN: localhost - INSTANCE_NAME: Pleroma E2E - ADMIN_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com} - NOTIFY_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com} - E2E_ADMIN_USERNAME: ${E2E_ADMIN_USERNAME:-admin} - E2E_ADMIN_PASSWORD: ${E2E_ADMIN_PASSWORD:-adminadmin} - E2E_ADMIN_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com} - depends_on: - db: - condition: service_healthy - volumes: - - ./docker/pleroma/entrypoint.e2e.sh:/opt/pleroma/entrypoint.e2e.sh:ro - entrypoint: ["/bin/ash", "/opt/pleroma/entrypoint.e2e.sh"] - healthcheck: - # NOTE: "localhost" may resolve to ::1 in some images (IPv6) while Pleroma only - # listens on IPv4 in this container. Use 127.0.0.1 to avoid false negatives. - test: ["CMD-SHELL", "test -f /var/lib/pleroma/.e2e_seeded && wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null || exit 1"] - interval: 5s - timeout: 3s - retries: 60 - - e2e: - build: - context: . - dockerfile: docker/e2e/Dockerfile.e2e - depends_on: - pleroma: - condition: service_healthy - environment: - CI: "1" - VITE_PROXY_TARGET: http://pleroma:4000 - VITE_PROXY_ORIGIN: http://localhost:4000 - E2E_BASE_URL: http://localhost:8080 - E2E_ADMIN_USERNAME: ${E2E_ADMIN_USERNAME:-admin} - E2E_ADMIN_PASSWORD: ${E2E_ADMIN_PASSWORD:-adminadmin} - command: ["yarn", "e2e:pw"] diff --git a/docker/e2e/Dockerfile.e2e b/docker/e2e/Dockerfile.e2e deleted file mode 100644 index e84359ceb..000000000 --- a/docker/e2e/Dockerfile.e2e +++ /dev/null @@ -1,16 +0,0 @@ -FROM mcr.microsoft.com/playwright:v1.57.0-jammy - -WORKDIR /app - -ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 - -RUN npm install -g yarn@1.22.22 - -COPY package.json yarn.lock ./ -RUN yarn --frozen-lockfile - -COPY . . - -ENV CI=1 - -CMD ["yarn", "e2e:pw"] diff --git a/docker/pleroma/entrypoint.e2e.sh b/docker/pleroma/entrypoint.e2e.sh deleted file mode 100644 index 96920eeae..000000000 --- a/docker/pleroma/entrypoint.e2e.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/ash - -set -eu - -SEED_SENTINEL_PATH="/var/lib/pleroma/.e2e_seeded" -CONFIG_OVERRIDE_PATH="/var/lib/pleroma/config.exs" - -echo "-- Waiting for database..." -while ! pg_isready -U "${DB_USER:-pleroma}" -d "postgres://${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-pleroma}" -t 1; do - sleep 1s -done - -echo "-- Writing E2E config overrides..." -cat > "$CONFIG_OVERRIDE_PATH" <<'EOF' -import Config - -config :pleroma, Pleroma.Captcha, - enabled: false - -config :pleroma, :instance, - registrations_open: true, - account_activation_required: false, - approval_required: false -EOF - -echo "-- Running migrations..." -/opt/pleroma/bin/pleroma_ctl migrate - -echo "-- Starting!" -/opt/pleroma/bin/pleroma start & -PLEROMA_PID="$!" - -cleanup() { - if [ -n "${PLEROMA_PID:-}" ] && kill -0 "$PLEROMA_PID" 2>/dev/null; then - kill -TERM "$PLEROMA_PID" - wait "$PLEROMA_PID" || true - fi -} - -trap cleanup INT TERM - -echo "-- Waiting for API..." -api_ok="false" -for _i in $(seq 1 120); do - if wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null 2>&1; then - api_ok="true" - break - fi - sleep 1s -done - -if [ "$api_ok" != "true" ]; then - echo "Timed out waiting for Pleroma API to become available" - exit 1 -fi - -if [ ! -f "$SEED_SENTINEL_PATH" ]; then - if [ -n "${E2E_ADMIN_USERNAME:-}" ] && [ -n "${E2E_ADMIN_PASSWORD:-}" ] && [ -n "${E2E_ADMIN_EMAIL:-}" ]; then - echo "-- Seeding admin user (${E2E_ADMIN_USERNAME})..." - if ! /opt/pleroma/bin/pleroma_ctl user new "$E2E_ADMIN_USERNAME" "$E2E_ADMIN_EMAIL" --admin --password "$E2E_ADMIN_PASSWORD" -y; then - echo "-- User already exists (or creation failed), ensuring admin + confirmed..." - /opt/pleroma/bin/pleroma_ctl user set "$E2E_ADMIN_USERNAME" --admin --confirmed - fi - else - echo "-- Skipping admin seeding (missing E2E_ADMIN_* env)" - fi - - touch "$SEED_SENTINEL_PATH" -fi - -wait "$PLEROMA_PID" diff --git a/eslint.config.mjs b/eslint.config.mjs index 417ff8cf3..01bdb2038 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,34 +1,37 @@ -import js from '@eslint/js' -import { defineConfig, globalIgnores } from 'eslint/config' -import vue from 'eslint-plugin-vue' -import globals from 'globals' +import vue from "eslint-plugin-vue"; +import js from "@eslint/js"; +import globals from "globals"; -export default defineConfig([ + +export default [ ...vue.configs['flat/recommended'], - globalIgnores(['**/*.js', 'build/', 'dist/', 'config/']), + js.configs.recommended, { - files: ['src/**/*.vue'], - plugins: { js }, - extends: ['js/recommended'], + files: ["**/*.js", "**/*.mjs", "**/*.vue"], + ignores: ["build/*.js", "config/*.js"], + languageOptions: { ecmaVersion: 2024, - sourceType: 'module', + sourceType: "module", parserOptions: { - parser: '@babel/eslint-parser', + parser: "@babel/eslint-parser", }, globals: { ...globals.browser, ...globals.vitest, ...globals.chai, ...globals.commonjs, - ...globals.serviceworker, - }, + ...globals.serviceworker + } }, rules: { + 'arrow-parens': 0, + 'generator-star-spacing': 0, + 'no-debugger': 0, 'vue/require-prop-types': 0, 'vue/multi-word-component-names': 0, - }, - }, -]) + } + } +] diff --git a/index.html b/index.html index 26eeee19b..96c20c4b7 100644 --- a/index.html +++ b/index.html @@ -11,12 +11,14 @@ - + + + -
+
diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue index cbe3dd80f..c8bba4c44 100644 --- a/src/components/checkbox/checkbox.vue +++ b/src/components/checkbox/checkbox.vue @@ -1,7 +1,7 @@ diff --git a/src/components/contrast_ratio/contrast_ratio.vue b/src/components/contrast_ratio/contrast_ratio.vue index 1f8b62809..93799e4c2 100644 --- a/src/components/contrast_ratio/contrast_ratio.vue +++ b/src/components/contrast_ratio/contrast_ratio.vue @@ -63,68 +63,54 @@ import { library } from '@fortawesome/fontawesome-svg-core' import { faAdjust, faExclamationTriangle, - faThumbsUp, + faThumbsUp } from '@fortawesome/free-solid-svg-icons' -library.add(faAdjust, faExclamationTriangle, faThumbsUp) +library.add( + faAdjust, + faExclamationTriangle, + faThumbsUp +) export default { components: { - Tooltip, + Tooltip }, props: { large: { required: false, type: Boolean, - default: false, + default: false }, // TODO: Make theme switcher compute theme initially so that contrast // component won't be called without contrast data contrast: { required: false, type: Object, - default: () => ({ - /* no-op */ - }), + default: () => ({}) }, showRatio: { required: false, type: Boolean, - default: false, - }, + default: false + } }, computed: { - hint() { - const levelVal = this.contrast.aaa - ? 'aaa' - : this.contrast.aa - ? 'aa' - : 'bad' + hint () { + const levelVal = this.contrast.aaa ? 'aaa' : (this.contrast.aa ? 'aa' : 'bad') const level = this.$t(`settings.style.common.contrast.level.${levelVal}`) const context = this.$t('settings.style.common.contrast.context.text') const ratio = this.contrast.text - return this.$t('settings.style.common.contrast.hint', { - level, - context, - ratio, - }) + return this.$t('settings.style.common.contrast.hint', { level, context, ratio }) }, - hint_18pt() { - const levelVal = this.contrast.laaa - ? 'aaa' - : this.contrast.laa - ? 'aa' - : 'bad' + hint_18pt () { + const levelVal = this.contrast.laaa ? 'aaa' : (this.contrast.laa ? 'aa' : 'bad') const level = this.$t(`settings.style.common.contrast.level.${levelVal}`) const context = this.$t('settings.style.common.contrast.context.18pt') const ratio = this.contrast.text - return this.$t('settings.style.common.contrast.hint', { - level, - context, - ratio, - }) - }, - }, + return this.$t('settings.style.common.contrast.hint', { level, context, ratio }) + } + } } diff --git a/src/components/conversation-page/conversation-page.js b/src/components/conversation-page/conversation-page.js index d4705303e..8f996be12 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 3caf4add7..491a8543f 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -1,22 +1,25 @@ -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 { reduce, filter, findIndex, clone, get } from 'lodash' import Status from '../status/status.vue' import ThreadTree from '../thread_tree/thread_tree.vue' +import { WSConnectionStatus } from '../../services/api/api.service.js' +import { mapGetters, mapState } from 'vuex' +import { mapState as mapPiniaState } from 'pinia' +import 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 { 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 @@ -40,25 +43,23 @@ 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: [ @@ -68,80 +69,76 @@ 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 [] } @@ -150,9 +147,7 @@ const conversation = { return [this.status] } - const conversation = clone( - this.$store.state.statuses.conversationsObject[this.conversationId], - ) + const conversation = clone(this.$store.state.statuses.conversationsObject[this.conversationId]) const statusIndex = findIndex(conversation, { id: this.originalStatusId }) if (statusIndex !== -1) { conversation[statusIndex] = this.status @@ -160,188 +155,144 @@ const conversation = { return sortAndFilterConversation(conversation, this.status) }, - statusMap() { + statusMap () { return this.conversation.reduce((res, s) => { res[s.id] = s return res }, {}) }, - threadTree() { - const reverseLookupTable = this.conversation.reduce( - (table, status, index) => { - table[status.id] = index - return table - }, - {}, - ) + threadTree () { + const reverseLookupTable = this.conversation.reduce((table, status, index) => { + table[status.id] = index + return table + }, {}) - const threads = this.conversation.reduce( - (a, cur) => { - const id = cur.id - a.forest[id] = this.getReplies(id).map((s) => s.id) + const threads = this.conversation.reduce((a, cur) => { + const id = cur.id + a.forest[id] = this.getReplies(id) + .map(s => s.id) - return a - }, - { - forest: {}, - }, - ) + return a + }, { + forest: {} + }) - const walk = (forest, topLevel, depth = 0, processed = {}) => - topLevel - .map((id) => { - if (processed[id]) { - return [] - } + const walk = (forest, topLevel, depth = 0, processed = {}) => topLevel.map(id => { + if (processed[id]) { + return [] + } - processed[id] = true - return [ - { - status: this.conversation[reverseLookupTable[id]], - id, - depth, - }, - walk(forest, forest[id], depth + 1, processed), - ].reduce((a, b) => a.concat(b), []) - }) - .reduce((a, b) => a.concat(b), []) + processed[id] = true + return [{ + status: this.conversation[reverseLookupTable[id]], + id, + depth + }, walk(forest, forest[id], depth + 1, processed)].reduce((a, b) => a.concat(b), []) + }).reduce((a, b) => a.concat(b), []) - const linearized = walk( - threads.forest, - this.topLevel.map((k) => k.id), - ) + const linearized = walk(threads.forest, this.topLevel.map(k => k.id)) return linearized }, - replyIds() { - return this.conversation - .map((k) => k.id) + replyIds () { + return this.conversation.map(k => k.id) .reduce((res, id) => { - res[id] = (this.replies[id] || []).map((k) => k.id) + res[id] = (this.replies[id] || []).map(k => k.id) return res }, {}) }, - totalReplyCount() { + totalReplyCount () { const sizes = {} const subTreeSizeFor = (id) => { if (sizes[id]) { return sizes[id] } - sizes[id] = - 1 + - this.replyIds[id] - .map((cid) => subTreeSizeFor(cid)) - .reduce((a, b) => a + b, 0) + sizes[id] = 1 + this.replyIds[id].map(cid => subTreeSizeFor(cid)).reduce((a, b) => a + b, 0) return sizes[id] } - this.conversation.map((k) => k.id).map(subTreeSizeFor) + this.conversation.map(k => k.id).map(subTreeSizeFor) return Object.keys(sizes).reduce((res, id) => { res[id] = sizes[id] - 1 // exclude itself return res }, {}) }, - totalReplyDepth() { + totalReplyDepth () { const depths = {} const subTreeDepthFor = (id) => { if (depths[id]) { return depths[id] } - depths[id] = - 1 + - this.replyIds[id] - .map((cid) => subTreeDepthFor(cid)) - .reduce((a, b) => (a > b ? a : b), 0) + depths[id] = 1 + this.replyIds[id].map(cid => subTreeDepthFor(cid)).reduce((a, b) => a > b ? a : b, 0) return depths[id] } - this.conversation.map((k) => k.id).map(subTreeDepthFor) + this.conversation.map(k => k.id).map(subTreeDepthFor) return Object.keys(depths).reduce((res, id) => { res[id] = depths[id] - 1 // exclude itself return res }, {}) }, - depths() { + depths () { return this.threadTree.reduce((a, k) => { a[k.id] = k.depth return a }, {}) }, - topLevel() { - const topLevel = this.conversation.reduce( - (tl, cur) => - tl.filter( - (k) => - this.getReplies(cur.id) - .map((v) => v.id) - .indexOf(k.id) === -1, - ), - this.conversation, - ) + topLevel () { + const topLevel = this.conversation.reduce((tl, cur) => + tl.filter(k => this.getReplies(cur.id).map(v => v.id).indexOf(k.id) === -1), this.conversation) return topLevel }, - otherTopLevelCount() { + otherTopLevelCount () { return this.topLevel.length - 1 }, - showingTopLevel() { + showingTopLevel () { if (this.canDive && this.diveRoot) { return [this.statusMap[this.diveRoot]] } return this.topLevel }, - diveRoot() { + diveRoot () { const statusId = this.inlineDivePosition || this.statusId const isTopLevel = !this.parentOf(statusId) return isTopLevel ? null : statusId }, - diveDepth() { + diveDepth () { return this.canDive && this.diveRoot ? this.depths[this.diveRoot] : 0 }, - diveMode() { + diveMode () { return this.canDive && !!this.diveRoot }, - shouldShowAllConversationButton() { + shouldShowAllConversationButton () { // The "show all conversation" button tells the user that there exist // other toplevel statuses, so do not show it if there is only a single root - return ( - this.isTreeView && - this.isExpanded && - this.diveMode && - this.topLevel.length > 1 - ) + return this.isTreeView && this.isExpanded && this.diveMode && this.topLevel.length > 1 }, - shouldShowAncestors() { - return ( - this.isTreeView && - this.isExpanded && - this.ancestorsOf(this.diveRoot).length - ) + shouldShowAncestors () { + return this.isTreeView && this.isExpanded && this.ancestorsOf(this.diveRoot).length }, - replies() { + replies () { let i = 1 - return reduce( - this.conversation, - (result, { id, in_reply_to_status_id: irid }) => { - if (irid) { - result[irid] = result[irid] || [] - result[irid].push({ - name: `#${i}`, - id, - }) - } - i++ - return result - }, - {}, - ) + return reduce(this.conversation, (result, { id, in_reply_to_status_id: irid }) => { + if (irid) { + result[irid] = result[irid] || [] + result[irid].push({ + name: `#${i}`, + id + }) + } + i++ + return result + }, {}) }, - isExpanded() { + isExpanded () { return !!(this.expanded || this.isPage) }, - hiddenStyle() { + hiddenStyle () { const height = (this.status && this.status.virtualHeight) || '120px' return this.virtualHidden ? { height } : {} }, - threadDisplayStatus() { + threadDisplayStatus () { return this.conversation.reduce((a, k) => { const id = k.id const depth = this.depths[id] @@ -349,7 +300,7 @@ const conversation = { if (this.threadDisplayStatusObject[id]) { return this.threadDisplayStatusObject[id] } - if (depth - this.diveDepth <= this.maxDepthToShowByDefault) { + if ((depth - this.diveDepth) <= this.maxDepthToShowByDefault) { return 'showing' } else { return 'hidden' @@ -360,7 +311,7 @@ const conversation = { return a }, {}) }, - statusContentProperties() { + statusContentProperties () { return this.conversation.reduce((a, k) => { const id = k.id const props = (() => { @@ -369,13 +320,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 @@ -385,59 +336,54 @@ 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 }) @@ -445,8 +391,7 @@ 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() @@ -456,16 +401,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 @@ -476,54 +421,44 @@ const conversation = { this.$store.dispatch('fetchFavsAndRepeats', id) this.$store.dispatch('fetchEmojiReactionsBy', id) }, - toggleExpanded() { + toggleExpanded () { this.expanded = !this.expanded }, - getConversationId(statusId) { + getConversationId (statusId) { const status = this.$store.state.statuses.allStatusesObject[statusId] - return get( - status, - 'retweeted_status.statusnet_conversation_id', - get(status, 'statusnet_conversation_id'), - ) + return get(status, 'retweeted_status.statusnet_conversation_id', get(status, 'statusnet_conversation_id')) }, - setThreadDisplay(id, nextStatus) { + setThreadDisplay (id, nextStatus) { this.threadDisplayStatusObject = { ...this.threadDisplayStatusObject, - [id]: nextStatus, + [id]: nextStatus } }, - toggleThreadDisplay(id) { + toggleThreadDisplay (id) { const curStatus = this.threadDisplayStatus[id] const nextStatus = curStatus === 'showing' ? 'hidden' : 'showing' this.setThreadDisplay(id, nextStatus) }, - setThreadDisplayRecursively(id, nextStatus) { + setThreadDisplayRecursively (id, nextStatus) { this.setThreadDisplay(id, nextStatus) - this.getReplies(id) - .map((k) => k.id) - .map((id) => this.setThreadDisplayRecursively(id, nextStatus)) + this.getReplies(id).map(k => k.id).map(id => this.setThreadDisplayRecursively(id, nextStatus)) }, - showThreadRecursively(id) { + showThreadRecursively (id) { this.setThreadDisplayRecursively(id, 'showing') }, - 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) { @@ -537,20 +472,18 @@ const conversation = { // nothing found, fall back to toplevel return this.topLevel[0] ? this.topLevel[0].id : undefined }, - diveIntoStatus(id) { + diveIntoStatus (id) { this.tryScrollTo(id) }, - diveToTopLevel() { - this.tryScrollTo( - this.topLevelAncestorOrSelfId(this.diveRoot) || this.topLevel[0].id, - ) + diveToTopLevel () { + this.tryScrollTo(this.topLevelAncestorOrSelfId(this.diveRoot) || this.topLevel[0].id) }, // only used when we are not on a page - undive() { + undive () { this.inlineDivePosition = null this.setHighlight(this.statusId) }, - tryScrollTo(id) { + tryScrollTo (id) { if (!id) { return } @@ -579,13 +512,13 @@ const conversation = { this.setHighlight(id) }) }, - goToCurrent() { + goToCurrent () { this.tryScrollTo(this.diveRoot || this.topLevel[0].id) }, - statusById(id) { + statusById (id) { return this.statusMap[id] }, - parentOf(id) { + parentOf (id) { const status = this.statusById(id) if (!status) { return undefined @@ -596,11 +529,11 @@ const conversation = { } return parentId }, - parentOrSelf(id) { + parentOrSelf (id) { return this.parentOf(id) || id }, // Ancestors of some status, from top to bottom - ancestorsOf(id) { + ancestorsOf (id) { const ancestors = [] let cur = this.parentOf(id) while (cur) { @@ -609,7 +542,7 @@ const conversation = { } return ancestors }, - topLevelAncestorOrSelfId(id) { + topLevelAncestorOrSelfId (id) { let cur = id let parent = this.parentOf(id) while (parent) { @@ -618,11 +551,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 8ab54b3df..98d408a7e 100644 --- a/src/components/desktop_nav/desktop_nav.js +++ b/src/components/desktop_nav/desktop_nav.js @@ -1,22 +1,20 @@ 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, - faTachometerAlt, + faHome, + faComments, + faBell, faUserPlus, + faBullhorn, + faSearch, + faTachometerAlt, + faCog, + faInfoCircle } from '@fortawesome/free-solid-svg-icons' +import { useInterfaceStore } from 'src/stores/interface' library.add( faSignInAlt, @@ -29,109 +27,91 @@ 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 8070d3429..b39851fe7 100644 --- a/src/components/dialog_modal/dialog_modal.js +++ b/src/components/dialog_modal/dialog_modal.js @@ -2,20 +2,18 @@ const DialogModal = { props: { darkOverlay: { default: true, - type: Boolean, + type: Boolean }, onCancel: { - default: () => { - /* no-op */ - }, - type: Function, - }, + default: () => {}, + type: Function + } }, computed: { - mobileCenter() { + mobileCenter () { return this.$store.getters.mergedConfig.modalMobileCenter - }, - }, + } + } } export default DialogModal diff --git a/src/components/dialog_modal/dialog_modal.vue b/src/components/dialog_modal/dialog_modal.vue index 2b9c7a5d8..939655f65 100644 --- a/src/components/dialog_modal/dialog_modal.vue +++ b/src/components/dialog_modal/dialog_modal.vue @@ -45,10 +45,10 @@ inset: 0; justify-content: center; place-items: center center; - overflow: auto; } .dialog-modal.panel { + max-height: 80vh; max-width: 90vw; z-index: 2001; cursor: default; diff --git a/src/components/dm_timeline/dm_timeline.js b/src/components/dm_timeline/dm_timeline.js index c977efe30..8b5393a98 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 71da2684c..f234dcb0f 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 971a75b10..55ee11a15 100644 --- a/src/components/draft/draft.js +++ b/src/components/draft/draft.js @@ -1,15 +1,18 @@ +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 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' - import { library } from '@fortawesome/fontawesome-svg-core' -import { faPollH } from '@fortawesome/free-solid-svg-icons' +import { + faPollH +} from '@fortawesome/free-solid-svg-icons' -library.add(faPollH) +library.add( + faPollH +) const Draft = { components: { @@ -17,23 +20,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') { @@ -42,24 +45,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 } @@ -67,34 +70,35 @@ 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 d724ab4ac..e50ea05ab 100644 --- a/src/components/draft_closer/draft_closer.js +++ b/src/components/draft_closer/draft_closer.js @@ -1,29 +1,32 @@ 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') { @@ -32,18 +35,18 @@ const DraftCloser = { this.discard() } }, - save() { + save () { this.$emit('save') this.showing = false }, - discard() { + discard () { this.$emit('discard') this.showing = false }, - cancel() { + cancel () { this.showing = false - }, - }, + } + } } export default DraftCloser diff --git a/src/components/drafts/drafts.js b/src/components/drafts/drafts.js index 8acde3c17..201417f66 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 b8df92794..323763370 100644 --- a/src/components/edit_status_form/edit_status_form.js +++ b/src/components/edit_status_form/edit_status_form.js @@ -1,21 +1,21 @@ -import statusPosterService from '../../services/status_poster/status_poster.service.js' import PostStatusForm from '../post_status_form/post_status_form.vue' +import statusPosterService from '../../services/status_poster/status_poster.service.js' const EditStatusForm = { components: { - PostStatusForm, + PostStatusForm }, props: { params: { type: Object, - required: true, - }, + required: true + } }, methods: { - requestClose() { + requestClose () { this.$refs.postStatusForm.requestClose() }, - doEditStatus({ status, spoilerText, sensitive, media, contentType, poll }) { + doEditStatus ({ status, spoilerText, sensitive, media, contentType, poll }) { const params = { store: this.$store, statusId: this.params.statusId, @@ -24,22 +24,21 @@ const EditStatusForm = { sensitive, poll, media, - contentType, + contentType } - return statusPosterService - .editStatus(params) + return statusPosterService.editStatus(params) .then((data) => { return data }) .catch((err) => { console.error('Error editing status', err) return { - error: err.message, + error: err.message } }) - }, - }, + } + } } export default EditStatusForm diff --git a/src/components/edit_status_modal/edit_status_modal.js b/src/components/edit_status_modal/edit_status_modal.js index 8a16e4234..4c10c21a0 100644 --- a/src/components/edit_status_modal/edit_status_modal.js +++ b/src/components/edit_status_modal/edit_status_modal.js @@ -1,35 +1,34 @@ -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(() => { @@ -37,22 +36,20 @@ const EditStatusModal = { }) } }, - isFormVisible(val) { + isFormVisible (val) { if (val) { - this.$nextTick( - () => this.$el && this.$el.querySelector('textarea').focus(), - ) + this.$nextTick(() => this.$el && this.$el.querySelector('textarea').focus()) } - }, + } }, methods: { - closeModal() { + closeModal () { this.$refs.editStatusForm.requestClose() }, - doCloseModal() { + doCloseModal () { useEditStatusStore().closeEditStatusModal() - }, - }, + } + } } export default EditStatusModal diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js index 24794640e..f3b6dfe9b 100644 --- a/src/components/emoji_input/emoji_input.js +++ b/src/components/emoji_input/emoji_input.js @@ -1,18 +1,20 @@ -import { take } from 'lodash' - -import Popover from 'src/components/popover/popover.vue' -import ScreenReaderNotice from 'src/components/screen_reader_notice/screen_reader_notice.vue' -import { ensureFinalFallback } from '../../i18n/languages.js' import Completion from '../../services/completion/completion.js' -import { findOffset } from '../../services/offset_finder/offset_finder.service.js' import genRandomSeed from '../../services/random_seed/random_seed.service.js' import EmojiPicker from '../emoji_picker/emoji_picker.vue' +import Popover from 'src/components/popover/popover.vue' +import ScreenReaderNotice from 'src/components/screen_reader_notice/screen_reader_notice.vue' import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue' - +import { take } from 'lodash' +import { findOffset } from '../../services/offset_finder/offset_finder.service.js' +import { ensureFinalFallback } from '../../i18n/languages.js' import { library } from '@fortawesome/fontawesome-svg-core' -import { faSmileBeam } from '@fortawesome/free-regular-svg-icons' +import { + faSmileBeam +} from '@fortawesome/free-regular-svg-icons' -library.add(faSmileBeam) +library.add( + faSmileBeam +) /** * EmojiInput - augmented inputs for emoji and autocomplete support in inputs @@ -58,14 +60,14 @@ const EmojiInput = { * For commonly used suggestors (emoji, users, both) use suggestor.js */ required: true, - type: Function, + type: Function }, modelValue: { /** * Used for v-model */ required: true, - type: String, + type: String }, enableEmojiPicker: { /** @@ -73,7 +75,7 @@ const EmojiInput = { */ required: false, type: Boolean, - default: false, + default: false }, hideEmojiButton: { /** @@ -82,7 +84,7 @@ const EmojiInput = { */ required: false, type: Boolean, - default: false, + default: false }, enableStickerPicker: { /** @@ -90,7 +92,7 @@ const EmojiInput = { */ required: false, type: Boolean, - default: false, + default: false }, placement: { /** @@ -99,15 +101,15 @@ const EmojiInput = { */ required: false, type: String, // 'auto', 'top', 'bottom' - default: 'auto', + default: 'auto' }, newlineOnCtrlEnter: { required: false, type: Boolean, - default: false, - }, + default: false + } }, - data() { + data () { return { randomSeed: genRandomSeed(), input: undefined, @@ -120,65 +122,58 @@ 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 || [])) @@ -186,13 +181,13 @@ const EmojiInput = { } return { - names: names.filter((k) => k), - keywords: keywords.filter((k) => k), + names: names.filter(k => k), + keywords: keywords.filter(k => k) } } }, - maybeLocalizedEmojiName() { - return (emoji) => { + maybeLocalizedEmojiName () { + return emoji => { if (!emoji.annotations) { return emoji.displayText } @@ -210,18 +205,22 @@ const EmojiInput = { return emoji.displayText } }, - suggestionListId() { + onInputScroll () { + this.$refs.hiddenOverlay.scrollTo({ + top: this.input.scrollTop, + left: this.input.scrollLeft + }) + }, + suggestionListId () { return `suggestions-${this.randomSeed}` }, - suggestionItemId() { + suggestionItemId () { return (index) => `suggestion-item-${index}-${this.randomSeed}` - }, + } }, - mounted() { + mounted () { const { root, hiddenOverlayCaret, suggestorPopover } = this.$refs - const input = - root.querySelector('.emoji-input > input') || - root.querySelector('.emoji-input > textarea') + const input = root.querySelector('.emoji-input > input') || root.querySelector('.emoji-input > textarea') if (!input) return this.input = input this.caretEl = hiddenOverlayCaret @@ -240,6 +239,7 @@ const EmojiInput = { this.overlayStyle.fontSize = style.fontSize this.overlayStyle.wordWrap = style.wordWrap this.overlayStyle.whiteSpace = style.whiteSpace + this.resize() input.addEventListener('blur', this.onBlur) input.addEventListener('focus', this.onFocus) input.addEventListener('paste', this.onPaste) @@ -250,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) @@ -280,40 +280,29 @@ const EmojiInput = { this.suggestions = [] return } - const matchedSuggestions = await this.suggest( - newWord, - this.maybeLocalizedEmojiNamesAndKeywords, - ) + const matchedSuggestions = await this.suggest(newWord, this.maybeLocalizedEmojiNamesAndKeywords) // Async: cancel if textAtCaret has changed during wait if (this.textAtCaret !== newWord || matchedSuggestions.length <= 0) { this.suggestions = [] return } - this.suggestions = take(matchedSuggestions, 5).map( - ({ imageUrl, ...rest }) => ({ + this.suggestions = take(matchedSuggestions, 5) + .map(({ imageUrl, ...rest }) => ({ ...rest, - img: imageUrl || '', - }), - ) + img: imageUrl || '' + })) this.highlighted = this.defaultCandidateIndex this.$refs.screenReaderNotice.announce( this.$t( 'tool_tip.autocomplete_available', { number: this.suggestions.length }, - this.suggestions.length, - ), + this.suggestions.length + ) ) - }, + } }, methods: { - onInputScroll(e) { - this.$refs.hiddenOverlay.scrollTo({ - top: this.input.scrollTop, - left: this.input.scrollLeft, - }) - this.setCaret(e) - }, - triggerShowPicker() { + triggerShowPicker () { this.$nextTick(() => { this.$refs.picker.showPicker() this.scrollIntoView() @@ -326,7 +315,7 @@ const EmojiInput = { this.disableClickOutside = false }, 0) }, - togglePicker() { + togglePicker () { this.input.focus() if (!this.pickerShown) { this.scrollIntoView() @@ -336,16 +325,12 @@ 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) || '' @@ -364,24 +349,18 @@ const EmojiInput = { * them, masto seem to be rendering :emoji::emoji: correctly now so why not */ const isSpaceRegex = /\s/ - const spaceBefore = - surroundingSpace && - !isSpaceRegex.exec(before.slice(-1)) && - before.length && - this.padEmoji > 0 - ? ' ' - : '' - const spaceAfter = - surroundingSpace && !isSpaceRegex.exec(after[0]) && this.padEmoji - ? ' ' - : '' + const spaceBefore = (surroundingSpace && !isSpaceRegex.exec(before.slice(-1)) && before.length && this.padEmoji > 0) ? ' ' : '' + const spaceAfter = (surroundingSpace && !isSpaceRegex.exec(after[0]) && this.padEmoji) ? ' ' : '' - const newValue = [before, spaceBefore, insertion, spaceAfter, after].join( - '', - ) + const newValue = [ + before, + spaceBefore, + insertion, + spaceAfter, + after + ].join('') this.$emit('update:modelValue', newValue) - const position = - this.caret + (insertion + spaceAfter + spaceBefore).length + const position = this.caret + (insertion + spaceAfter + spaceBefore).length if (!keepOpen) { this.input.focus() } @@ -393,20 +372,13 @@ const EmojiInput = { this.caret = position }) }, - replaceText(e, suggestion) { + replaceText (e, suggestion) { const len = this.suggestions.length || 0 - if (this.textAtCaret.length === 1) { - return - } + if (this.textAtCaret.length === 1) { return } if (len > 0 || suggestion) { - const chosenSuggestion = - suggestion || this.suggestions[this.highlighted] + const chosenSuggestion = suggestion || this.suggestions[this.highlighted] const replacement = chosenSuggestion.replacement - const newValue = Completion.replaceWord( - this.modelValue, - this.wordAtCaret, - replacement, - ) + const newValue = Completion.replaceWord(this.modelValue, this.wordAtCaret, replacement) this.$emit('update:modelValue', newValue) this.highlighted = 0 const position = this.wordAtCaret.start + replacement.length @@ -421,7 +393,7 @@ const EmojiInput = { e.preventDefault() } }, - cycleBackward(e) { + cycleBackward (e) { const len = this.suggestions.length || 0 this.highlighted -= 1 @@ -434,7 +406,7 @@ const EmojiInput = { e.preventDefault() } }, - cycleForward(e) { + cycleForward (e) { const len = this.suggestions.length || 0 this.highlighted += 1 @@ -446,28 +418,26 @@ const EmojiInput = { e.preventDefault() } }, - scrollIntoView() { + scrollIntoView () { const rootRef = this.$refs.picker.$el /* Scroller is either `window` (replies in TL), sidebar (main post form, * replies in notifs) or mobile post form. Note that getting and setting * scroll is different for `Window` and `Element`s */ - const scrollerRef = - this.$el.closest('.sidebar-scroller') || - this.$el.closest('.post-form-modal-view') || - window - const currentScroll = - scrollerRef === window ? scrollerRef.scrollY : scrollerRef.scrollTop - const scrollerHeight = - scrollerRef === window - ? scrollerRef.innerHeight - : scrollerRef.offsetHeight + const scrollerRef = this.$el.closest('.sidebar-scroller') || + this.$el.closest('.post-form-modal-view') || + window + const currentScroll = scrollerRef === window + ? scrollerRef.scrollY + : scrollerRef.scrollTop + const scrollerHeight = scrollerRef === window + ? scrollerRef.innerHeight + : scrollerRef.offsetHeight const scrollerBottomBorder = currentScroll + scrollerHeight // We check where the bottom border of root element is, this uses findOffset // to find offset relative to scrollable container (scroller) - const rootBottomBorder = - rootRef.offsetHeight + findOffset(rootRef, scrollerRef).top + const rootBottomBorder = rootRef.offsetHeight + findOffset(rootRef, scrollerRef).top const bottomDelta = Math.max(0, rootBottomBorder - scrollerBottomBorder) // could also check top delta but there's no case for it @@ -489,13 +459,13 @@ const EmojiInput = { } }) }, - onPickerShown() { + onPickerShown () { this.pickerShown = true }, - onPickerClosed() { + onPickerClosed () { this.pickerShown = false }, - onBlur(e) { + onBlur (e) { // Clicking on any suggestion removes focus from autocomplete, // preventing click handler ever executing. this.blurTimeout = setTimeout(() => { @@ -503,10 +473,10 @@ const EmojiInput = { this.setCaret(e) }, 200) }, - onClick(e, suggestion) { + onClick (e, suggestion) { this.replaceText(e, suggestion) }, - onFocus(e) { + onFocus (e) { if (this.blurTimeout) { clearTimeout(this.blurTimeout) this.blurTimeout = null @@ -516,7 +486,7 @@ const EmojiInput = { this.setCaret(e) this.temporarilyHideSuggestions = false }, - onKeyUp(e) { + onKeyUp (e) { const { key } = e this.setCaret(e) @@ -528,10 +498,10 @@ const EmojiInput = { this.temporarilyHideSuggestions = false } }, - onPaste(e) { + onPaste (e) { this.setCaret(e) }, - onKeyDown(e) { + onKeyDown (e) { const { ctrlKey, shiftKey, key } = e if (this.newlineOnCtrlEnter && ctrlKey && key === 'Enter') { this.insert({ insertion: '\n', surroundingSpace: false }) @@ -575,30 +545,32 @@ const EmojiInput = { } } }, - onInput(e) { + onInput (e) { this.setCaret(e) this.$emit('update:modelValue', e.target.value) }, - onStickerUploaded(e) { + onStickerUploaded (e) { this.$emit('sticker-uploaded', e) }, - onStickerUploadFailed(e) { + onStickerUploadFailed (e) { this.$emit('sticker-upload-Failed', e) }, - setCaret({ target: { selectionStart } }) { + setCaret ({ target: { selectionStart } }) { this.caret = selectionStart this.$nextTick(() => { this.$refs.suggestorPopover.updateStyles() }) }, - autoCompleteItemLabel(suggestion) { + resize () { + }, + autoCompleteItemLabel (suggestion) { if (suggestion.user) { return suggestion.displayText + ' ' + suggestion.detailText } else { return this.maybeLocalizedEmojiName(suggestion) } - }, - }, + } + } } export default EmojiInput diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue index a1cba33bc..f9788d874 100644 --- a/src/components/emoji_input/emoji_input.vue +++ b/src/components/emoji_input/emoji_input.vue @@ -123,7 +123,7 @@ margin: 0.2em 0.25em; font-size: 1.3em; cursor: pointer; - line-height: 1.2em; + line-height: 24px; &:hover i { color: var(--text); @@ -133,7 +133,7 @@ .emoji-picker-panel { position: absolute; z-index: 20; - margin-top: 0.2em; + margin-top: 2px; &.hide { display: none; @@ -152,7 +152,7 @@ } &.with-picker input { - padding-right: 2em; + padding-right: 30px; } .hidden-overlay { @@ -215,8 +215,8 @@ } .detailText { - font-size: 0.6em; - line-height: 0.6em; + font-size: 9px; + line-height: 9px; } } } diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js index 79d97cff7..f2daf2f46 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,35 +25,22 @@ 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 @@ -91,7 +78,7 @@ export const suggestUsers = ({ dispatch, state }) => { }) } - return async (input) => { + return async input => { const noPrefix = input.toLowerCase().substr(1) if (previousQuery === noPrefix) return suggestions @@ -105,42 +92,37 @@ 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 e4221f706..17a317a4d 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -1,26 +1,24 @@ -import { chunk, debounce, trim } from 'lodash' import { defineAsyncComponent } from 'vue' - -import Popover from 'src/components/popover/popover.vue' -import { ensureFinalFallback } from '../../i18n/languages.js' 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 { library } from '@fortawesome/fontawesome-svg-core' import { - faBasketballBall, faBoxOpen, - faBus, - faCode, - faFlag, - faIceCream, - faLightbulb, - faPaw, - faSmile, - faSmileBeam, faStickyNote, + faSmileBeam, + faSmile, faUser, + faPaw, + faIceCream, + faBus, + faBasketballBall, + faLightbulb, + faCode, + faFlag } from '@fortawesome/free-solid-svg-icons' +import { debounce, trim, chunk } from 'lodash' library.add( faBoxOpen, @@ -34,7 +32,7 @@ library.add( faBasketballBall, faLightbulb, faCode, - faFlag, + faFlag ) const UNICODE_EMOJI_GROUP_ICON = { @@ -46,16 +44,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 @@ -68,8 +66,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 @@ -86,13 +84,11 @@ 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+-/, '') } @@ -101,20 +97,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', @@ -129,30 +125,28 @@ 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 fontSize = css.getPropertyValue('font-size') || '14px' const emojiSize = css.getPropertyValue('--emojiSize') || '2.2rem' - const fontSizeUnit = fontSize.replace(/[0-9,.]+/, '').trim() + const fontSizeUnit = fontSize.replace(/[0-9,.]+/, '') const fontSizeValue = Number(fontSize.replace(/[^0-9,.]+/, '')) - const emojiSizeUnit = emojiSize.replace(/[0-9,.]+/, '').trim() + const emojiSizeUnit = emojiSize.replace(/[0-9,.]+/, '') const emojiSizeValue = Number(emojiSize.replace(/[^0-9,.]+/, '')) let fontSizeMultiplier @@ -169,68 +163,56 @@ 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 } @@ -238,7 +220,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 @@ -246,9 +228,7 @@ 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) { @@ -257,12 +237,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) { @@ -271,21 +251,16 @@ 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() @@ -302,59 +277,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 {} } @@ -364,49 +339,46 @@ 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 } @@ -424,10 +396,10 @@ const EmojiPicker = { return emoji.displayText } }, - isInModal() { + isInModal () { return this.popoversZLayer === 'modals' - }, - }, + } + } } export default EmojiPicker diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss index e318cdb40..05600c790 100644 --- a/src/components/emoji_picker/emoji_picker.scss +++ b/src/components/emoji_picker/emoji_picker.scss @@ -154,12 +154,10 @@ // Autoprefixed seem to ignore this one, and also syntax is different /* stylelint-disable mask-composite */ /* stylelint-disable declaration-property-value-no-unknown */ - /* stylelint-disable scss/declaration-property-value-no-unknown */ /* TODO check if this is still needed */ mask-composite: xor; /* stylelint-enable declaration-property-value-no-unknown */ - /* stylelint-enable scss/declaration-property-value-no-unknown */ /* stylelint-enable mask-composite */ mask-composite: exclude; diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js index 8a121befc..f5e1b68f6 100644 --- a/src/components/emoji_reactions/emoji_reactions.js +++ b/src/components/emoji_reactions/emoji_reactions.js @@ -1,11 +1,18 @@ -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 { faCheck, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons' +import { + faPlus, + faMinus, + faCheck +} from '@fortawesome/free-solid-svg-icons' -library.add(faPlus, faMinus, faCheck) +library.add( + faPlus, + faMinus, + faCheck +) const EMOJI_REACTION_COUNT_CUTOFF = 12 @@ -14,62 +21,57 @@ 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() @@ -79,23 +81,19 @@ 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 12213d9e3..fc75372e3 100644 --- a/src/components/exporter/exporter.js +++ b/src/components/exporter/exporter.js @@ -1,47 +1,45 @@ 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/exporter/exporter.vue b/src/components/exporter/exporter.vue index e36d9dd62..79defdf6f 100644 --- a/src/components/exporter/exporter.vue +++ b/src/components/exporter/exporter.vue @@ -23,9 +23,6 @@ diff --git a/src/components/link-preview/link-preview.js b/src/components/link-preview/link-preview.js index ac91f916d..add7c5631 100644 --- a/src/components/link-preview/link-preview.js +++ b/src/components/link-preview/link-preview.js @@ -2,31 +2,37 @@ 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 = () => { @@ -34,7 +40,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 141a1f023..d13cef338 100644 --- a/src/components/link.style.js +++ b/src/components/link.style.js @@ -3,22 +3,22 @@ export default { selector: 'a', virtual: true, states: { - faint: '.faint', + faint: '.faint' }, defaultRules: [ { component: 'Link', directives: { - textColor: '--link', - }, + textColor: '--link' + } }, { component: 'Link', state: ['faint'], directives: { textOpacity: 0.5, - textOpacityMode: 'fake', - }, - }, - ], + textOpacityMode: 'fake' + } + } + ] } diff --git a/src/components/list/list.vue b/src/components/list/list.vue index f34a5f073..5d2c49b3c 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/list/list_item.style.js b/src/components/list/list_item.style.js new file mode 100644 index 000000000..49b2b035f --- /dev/null +++ b/src/components/list/list_item.style.js @@ -0,0 +1,48 @@ +export default { + name: 'ListItem', + selector: '.list-item', + states: { + active: '.-active', + hover: ':is(:hover, :focus-visible, :has(:focus-visible)):not(.-non-interactive)' + }, + validInnerComponents: [ + 'Text', + 'Link', + 'Icon', + 'Border', + 'Button', + 'ButtonUnstyled', + 'RichContent', + 'Input', + 'Avatar' + ], + defaultRules: [ + { + directives: { + background: '--bg', + opacity: 0 + } + }, + { + state: ['active'], + directives: { + background: '--inheritedBackground, 10', + opacity: 1 + } + }, + { + state: ['hover'], + directives: { + background: '--inheritedBackground, 10', + opacity: 1 + } + }, + { + state: ['hover', 'active'], + directives: { + background: '--inheritedBackground, 20', + opacity: 1 + } + } + ] +} diff --git a/src/components/lists/lists.js b/src/components/lists/lists.js index 446178245..8dcb48b52 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 81b811534..b503caec4 100644 --- a/src/components/lists_card/lists_card.js +++ b/src/components/lists_card/lists_card.js @@ -1,10 +1,16 @@ 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 64a2e942c..ca36ab886 100644 --- a/src/components/lists_edit/lists_edit.js +++ b/src/components/lists_edit/lists_edit.js @@ -1,18 +1,22 @@ +import { mapState, mapGetters } from 'vuex' import { mapState as mapPiniaState } from 'pinia' -import { mapGetters, mapState } from 'vuex' - -import PanelLoading from 'src/components/panel_loading/panel_loading.vue' -import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' -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 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 { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons' +import { + faSearch, + faChevronLeft +} from '@fortawesome/free-solid-svg-icons' +import { useInterfaceStore } from 'src/stores/interface' +import { useListsStore } from 'src/stores/lists' -library.add(faSearch, faChevronLeft) +library.add( + faSearch, + faChevronLeft +) const ListsNew = { components: { @@ -20,9 +24,9 @@ const ListsNew = { UserAvatar, ListsUserSearch, TabSwitcher, - PanelLoading, + PanelLoading }, - data() { + data () { return { title: '', titleDraft: '', @@ -31,51 +35,46 @@ const ListsNew = { searchUserIds: [], addedUserIds: new Set([]), // users we added from search, to undo searchLoading: false, - reallyDelete: false, + reallyDelete: false } }, - created() { + created () { if (!this.id) return - useListsStore() - .fetchList({ listId: this.id }) + useListsStore().fetchList({ listId: this.id }) .then(() => { this.title = this.findListTitle(this.id) this.titleDraft = this.title }) - useListsStore() - .fetchListAccounts({ listId: this.id }) + useListsStore().fetchListAccounts({ listId: this.id }) .then(() => { this.membersUserIds = this.findListAccounts(this.id) - this.membersUserIds.forEach((userId) => { + this.membersUserIds.forEach(userId => { this.$store.dispatch('fetchUserIfMissing', userId) }) }) }, computed: { - id() { + id () { return this.$route.params.id }, - membersUsers() { + membersUsers () { return [...this.membersUserIds, ...this.addedUserIds] - .map((userId) => this.findUser(userId)) - .filter((user) => user) + .map(userId => this.findUser(userId)).filter(user => user) }, - searchUsers() { - return this.searchUserIds - .map((userId) => this.findUser(userId)) - .filter((user) => user) + searchUsers () { + return this.searchUserIds.map(userId => this.findUser(userId)).filter(user => user) }, ...mapState({ - currentUser: (state) => state.users.currentUser, + currentUser: state => state.users.currentUser }), ...mapPiniaState(useListsStore, ['findListTitle', 'findListAccounts']), - ...mapGetters(['findUser']), + ...mapGetters(['findUser']) }, methods: { - onInput() { + onInput () { this.search(this.query) }, - toggleRemoveMember(user) { + toggleRemoveMember (user) { if (this.removedUserIds.has(user.id)) { this.id && this.addUser(user) this.removedUserIds.delete(user.id) @@ -84,7 +83,7 @@ const ListsNew = { this.removedUserIds.add(user.id) } }, - toggleAddFromSearch(user) { + toggleAddFromSearch (user) { if (this.addedUserIds.has(user.id)) { this.id && this.removeUser(user.id) this.addedUserIds.delete(user.id) @@ -93,41 +92,39 @@ const ListsNew = { this.addedUserIds.add(user.id) } }, - isRemoved(user) { + isRemoved (user) { return this.removedUserIds.has(user.id) }, - isAdded(user) { + isAdded (user) { return this.addedUserIds.has(user.id) }, - addUser(user) { + addUser (user) { useListsStore().addListAccount({ accountId: user.id, listId: this.id }) }, - removeUser(userId) { + removeUser (userId) { useListsStore().removeListAccount({ accountId: userId, listId: this.id }) }, - onSearchLoading() { + onSearchLoading () { this.searchLoading = true }, - onSearchLoadingDone() { + onSearchLoadingDone () { this.searchLoading = false }, - onSearchResults(results) { + onSearchResults (results) { this.searchLoading = false this.searchUserIds = results }, - updateListTitle() { + updateListTitle () { useListsStore().setList({ listId: this.id, title: this.titleDraft }) - this.title = this.findListTitle(this.id) + .then(() => { + this.title = this.findListTitle(this.id) + }) }, - createList() { - useListsStore() - .createList({ title: this.titleDraft }) + createList () { + useListsStore().createList({ title: this.titleDraft }) .then((list) => { return useListsStore() - .setListAccounts({ - listId: list.id, - accountIds: [...this.addedUserIds], - }) + .setListAccounts({ listId: list.id, accountIds: [...this.addedUserIds] }) .then(() => list.id) }) .then((listId) => { @@ -137,15 +134,15 @@ const ListsNew = { useInterfaceStore().pushGlobalNotice({ messageKey: 'lists.error', messageArgs: [e.message], - level: 'error', + level: 'error' }) }) }, - deleteList() { + deleteList () { useListsStore().deleteList({ listId: this.id }) this.$router.push({ name: 'lists' }) - }, - }, + } + } } export default ListsNew diff --git a/src/components/lists_menu/lists_menu_content.js b/src/components/lists_menu/lists_menu_content.js index 7574c4c3f..726fd5fc4 100644 --- a/src/components/lists_menu/lists_menu_content.js +++ b/src/components/lists_menu/lists_menu_content.js @@ -1,25 +1,26 @@ -import { mapState as mapPiniaState } from 'pinia' import { mapState } from 'vuex' - -import { getListEntries } from 'src/components/navigation/filter.js' +import { mapState as mapPiniaState } from 'pinia' import NavigationEntry from 'src/components/navigation/navigation_entry.vue' +import { getListEntries } from 'src/components/navigation/filter.js' 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 ae1554eb6..eae82a867 100644 --- a/src/components/lists_timeline/lists_timeline.js +++ b/src/components/lists_timeline/lists_timeline.js @@ -1,19 +1,16 @@ 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) { @@ -22,25 +19,19 @@ const ListsTimeline = { this.$store.dispatch('stopFetchingTimeline', 'list') this.$store.commit('clearTimeline', { timeline: 'list' }) useListsStore().fetchList({ listId: this.listId }) - this.$store.dispatch('startFetchingTimeline', { - timeline: 'list', - listId: this.listId, - }) + this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId }) } - }, + } }, - created() { + created () { this.listId = this.$route.params.id useListsStore().fetchList({ listId: this.listId }) - this.$store.dispatch('startFetchingTimeline', { - timeline: 'list', - listId: this.listId, - }) + this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId }) }, - unmounted() { + unmounted () { this.$store.dispatch('stopFetchingTimeline', 'list') this.$store.commit('clearTimeline', { timeline: 'list' }) - }, + } } export default ListsTimeline diff --git a/src/components/lists_user_search/lists_user_search.js b/src/components/lists_user_search/lists_user_search.js index 699e6c156..c92ec0eee 100644 --- a/src/components/lists_user_search/lists_user_search.js +++ b/src/components/lists_user_search/lists_user_search.js @@ -1,29 +1,33 @@ +import { 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' -import { library } from '@fortawesome/fontawesome-svg-core' -import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons' - -library.add(faSearch, faChevronLeft) +library.add( + faSearch, + faChevronLeft +) const ListsUserSearch = { components: { - Checkbox, + Checkbox }, emits: ['loading', 'loadingDone', 'results'], - data() { + data () { return { loading: false, query: '', - followingOnly: true, + followingOnly: true } }, methods: { onInput: debounce(function () { this.search(this.query) }, 2000), - search(query) { + search (query) { if (!query) { this.loading = false return @@ -32,25 +36,16 @@ const ListsUserSearch = { this.loading = true this.$emit('loading') this.userIds = [] - this.$store - .dispatch('search', { - q: query, - resolve: true, - type: 'accounts', - following: this.followingOnly, - }) - .then((data) => { - this.$emit( - 'results', - data.accounts.map((a) => a.id), - ) + this.$store.dispatch('search', { q: query, resolve: true, type: 'accounts', following: this.followingOnly }) + .then(data => { + this.$emit('results', data.accounts.map(a => a.id)) }) .finally(() => { this.loading = false this.$emit('loadingDone') }) - }, - }, + } + } } export default ListsUserSearch diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index 7c43923c9..9566aa903 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -1,61 +1,56 @@ -import { mapActions, mapState as mapPiniaState, mapStores } from 'pinia' import { mapState } from 'vuex' - -import { useAuthFlowStore } from 'src/stores/auth_flow.js' -import { useOAuthStore } from 'src/stores/oauth.js' +import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia' import oauthApi from '../../services/new_api/oauth.js' - +import { useOAuthStore } from 'src/stores/oauth.js' +import { useAuthFlowStore } from 'src/stores/auth_flow.js' import { library } from '@fortawesome/fontawesome-svg-core' -import { faTimes } from '@fortawesome/free-solid-svg-icons' +import { + faTimes +} from '@fortawesome/free-solid-svg-icons' -library.add(faTimes) +library.add( + faTimes +) const LoginForm = { data: () => ({ user: {}, - error: false, + error: false }), computed: { - isPasswordAuth() { - return this.requiredPassword - }, - isTokenAuth() { - return this.requiredToken - }, + 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 @@ -66,43 +61,38 @@ 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 + 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() } - this.login(result).then(() => { - this.$router.push({ name: 'friends' }) - }) + 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 6199a6e32..9a57f8250 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -1,22 +1,26 @@ -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 StillImage from '../still-image/still-image.vue' +import VideoAttachment from '../video_attachment/video_attachment.vue' import Modal from '../modal/modal.vue' import PinchZoom from '../pinch_zoom/pinch_zoom.vue' -import StillImage from '../still-image/still-image.vue' import SwipeClick from '../swipe_click/swipe_click.vue' -import VideoAttachment from '../video_attachment/video_attachment.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 { 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: { @@ -25,9 +29,9 @@ const MediaModal = { PinchZoom, SwipeClick, Modal, - Flash, + Flash }, - data() { + data () { return { loading: false, swipeDirection: GestureService.DIRECTION_LEFT, @@ -36,42 +40,42 @@ const MediaModal = { return window.innerWidth * considerableMoveRatio }, pinchZoomMinScale: 1, - pinchZoomScaleResetLimit: 1.2, + pinchZoomScaleResetLimit: 1.2 } }, computed: { - showing() { + showing () { return useMediaViewerStore().activated }, - media() { + media () { return useMediaViewerStore().media }, - description() { + description () { return this.currentMedia.description }, - currentIndex() { + currentIndex () { return useMediaViewerStore().currentIndex }, - currentMedia() { + currentMedia () { return this.media[this.currentIndex] }, - canNavigate() { + canNavigate () { return this.media.length > 1 }, - 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 @@ -79,7 +83,7 @@ const MediaModal = { useMediaViewerStore().closeMediaViewer() }, transitionTime) }, - hideIfNotSwiped(event) { + hideIfNotSwiped (event) { // If we have swiped over SwipeClick, do not trigger hide const comp = this.$refs.swipeClick if (!comp) { @@ -88,12 +92,9 @@ 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 @@ -101,12 +102,9 @@ 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 @@ -114,13 +112,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() @@ -128,36 +126,33 @@ const MediaModal = { this.goPrev() } }, - handleKeyupEvent(e) { - if (this.showing && e.keyCode === 27) { - // escape + handleKeyupEvent (e) { + if (this.showing && e.keyCode === 27) { // escape this.hide() } }, - handleKeydownEvent(e) { + handleKeydownEvent (e) { if (!this.showing) { return } - if (e.keyCode === 39) { - // arrow right + if (e.keyCode === 39) { // arrow right this.goNext() - } else if (e.keyCode === 37) { - // arrow left + } else if (e.keyCode === 37) { // arrow left this.goPrev() } - }, + } }, - mounted() { + mounted () { window.addEventListener('popstate', this.hide) document.addEventListener('keyup', this.handleKeyupEvent) document.addEventListener('keydown', this.handleKeydownEvent) }, - unmounted() { + unmounted () { window.removeEventListener('popstate', this.hide) document.removeEventListener('keyup', this.handleKeyupEvent) document.removeEventListener('keydown', this.handleKeydownEvent) - }, + } } export default MediaModal diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 7f625755c..5cc8c50a3 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -168,7 +168,7 @@ $modal-view-button-icon-margin: 0.5em; flex: 0 0 auto; overflow-y: auto; min-height: 1em; - max-width: 35.8em; + max-width: 500px; max-height: 9.5em; overflow-wrap: break-word; text-wrap: pretty; diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js index 8dc3d6c65..f2cbfc405 100644 --- a/src/components/media_upload/media_upload.js +++ b/src/components/media_upload/media_upload.js @@ -1,32 +1,34 @@ /* eslint-env browser */ - -import fileSizeFormatService from '../../services/file_size_format/file_size_format.js' import statusPosterService from '../../services/status_poster/status_poster.service.js' +import fileSizeFormatService from '../../services/file_size_format/file_size_format.js' import { library } from '@fortawesome/fontawesome-svg-core' -import { faCircleNotch, faUpload } from '@fortawesome/free-solid-svg-icons' +import { faUpload, faCircleNotch } from '@fortawesome/free-solid-svg-icons' -library.add(faUpload, faCircleNotch) +library.add( + faUpload, + faCircleNotch +) const mediaUpload = { - data() { + data () { return { uploadCount: 0, - uploadReady: true, + uploadReady: true } }, computed: { - uploading() { + uploading () { return this.uploadCount > 0 - }, + } }, methods: { - onClick() { + onClick () { if (this.uploadReady) { this.$refs.input.click() } }, - async resizeImage(file) { + async resizeImage (file) { // Skip if not an image or if it's a GIF if (!file.type.startsWith('image/') || file.type === 'image/gif') { return file @@ -72,67 +74,46 @@ const mediaUpload = { // Check WebP support by trying to create a WebP canvas const testCanvas = document.createElement('canvas') - const supportsWebP = testCanvas - .toDataURL('image/webp') - .startsWith('data:image/webp') + const supportsWebP = testCanvas.toDataURL('image/webp').startsWith('data:image/webp') // Convert to WebP if supported and alwaysUseJpeg is false, otherwise JPEG - const type = - !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 } @@ -144,38 +125,36 @@ const mediaUpload = { self.$emit('uploading') self.uploadCount++ - statusPosterService.uploadMedia({ store, formData }).then( - (fileData) => { + statusPosterService.uploadMedia({ store, formData }) + .then((fileData) => { self.$emit('uploaded', fileData) self.decreaseUploadCount() - }, - (error) => { + }, (error) => { console.error('Error uploading file', error) self.$emit('upload-failed', 'default') self.decreaseUploadCount() - }, - ) + }) }, - decreaseUploadCount() { + decreaseUploadCount () { this.uploadCount-- if (this.uploadCount === 0) { this.$emit('all-uploaded') } }, - clearFile() { + clearFile () { this.uploadReady = false this.$nextTick(() => { this.uploadReady = true }) }, - multiUpload(files) { + multiUpload (files) { for (const file of files) { this.uploadFile(file) } }, - change({ target }) { + change ({ target }) { this.multiUpload(target.files) - }, + } }, props: { dropFiles: Object, @@ -183,16 +162,16 @@ const mediaUpload = { normalButton: Boolean, acceptTypes: { type: String, - default: '*/*', - }, + default: '*/*' + } }, watch: { dropFiles: function (fileInfos) { if (!this.uploading) { this.multiUpload(fileInfos) } - }, - }, + } + } } export default mediaUpload diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js index 4c211b5e9..ca5c24618 100644 --- a/src/components/mention_link/mention_link.js +++ b/src/components/mention_link/mention_link.js @@ -1,165 +1,155 @@ -import { defineAsyncComponent } from 'vue' -import { mapGetters, mapState } from 'vuex' - 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 { mapGetters, mapState } from 'vuex' +import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import UserAvatar from '../user_avatar/user_avatar.vue' - +import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue' +import { defineAsyncComponent } from 'vue' import { library } from '@fortawesome/fontawesome-svg-core' -import { faAt } from '@fortawesome/free-solid-svg-icons' +import { + faAt +} from '@fortawesome/free-solid-svg-icons' -library.add(faAt) +library.add( + faAt +) const MentionLink = { name: 'MentionLink', components: { UserAvatar, UnicodeDomainIndicator, - UserPopover: 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/mention_link/mention_link.scss b/src/components/mention_link/mention_link.scss index 3aa7816be..c3261e17d 100644 --- a/src/components/mention_link/mention_link.scss +++ b/src/components/mention_link/mention_link.scss @@ -7,6 +7,7 @@ & .new, & .original { display: inline; + border-radius: 2px; } .mention-avatar { diff --git a/src/components/mentions/mentions.js b/src/components/mentions/mentions.js index 10167ac77..841d5aa48 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 e6aa392a0..a4a0c7246 100644 --- a/src/components/mentions_line/mentions_line.js +++ b/src/components/mentions_line/mentions_line.js @@ -1,6 +1,5 @@ -import { mapGetters } from 'vuex' - import MentionLink from 'src/components/mention_link/mention_link.vue' +import { mapGetters } from 'vuex' export const MENTIONS_LIMIT = 5 @@ -9,30 +8,30 @@ const MentionsLine = { props: { mentions: { required: true, - type: Array, - }, + type: Array + } }, data: () => ({ expanded: false }), components: { - MentionLink, + MentionLink }, computed: { - mentionsComputed() { + mentionsComputed () { return this.mentions.slice(0, MENTIONS_LIMIT) }, - extraMentions() { + extraMentions () { return this.mentions.slice(MENTIONS_LIMIT) }, - manyMentions() { + manyMentions () { return this.extraMentions.length > 0 }, - ...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 a5567b49b..883355efa 100644 --- a/src/components/menu_item.style.js +++ b/src/components/menu_item.style.js @@ -1,105 +1,113 @@ export default { name: 'MenuItem', selector: '.menu-item', - validInnerComponents: ['Text', 'Icon', 'Border'], + validInnerComponents: [ + 'Text', + 'Icon', + 'Input', + 'Border', + 'ButtonUnstyled', + 'Badge', + 'Avatar' + ], states: { hover: ':is(:hover, :focus-visible, :has(:focus-visible)):not(.disabled)', active: '.-active', - disabled: '.disabled', + disabled: '.disabled' }, defaultRules: [ { directives: { background: '--bg', - opacity: 0, - }, + opacity: 0 + } }, { state: ['hover'], directives: { background: '$mod(--bg 5)', - opacity: 1, - }, + opacity: 1 + } }, { state: ['active'], directives: { background: '$mod(--bg 10)', - opacity: 1, - }, + opacity: 1 + } }, { state: ['active', 'hover'], directives: { background: '$mod(--bg 15)', - opacity: 1, - }, + opacity: 1 + } }, { component: 'Text', parent: { component: 'MenuItem', - state: ['hover'], + state: ['hover'] }, directives: { textColor: '--link', - textAuto: 'no-preserve', - }, + textAuto: 'no-preserve' + } }, { component: 'Text', parent: { component: 'MenuItem', - state: ['active'], + state: ['active'] }, directives: { textColor: '--link', - textAuto: 'no-preserve', - }, + textAuto: 'no-preserve' + } }, { component: 'Icon', parent: { component: 'MenuItem', - state: ['active'], + state: ['active'] }, directives: { textColor: '--link', - textAuto: 'no-preserve', - }, + textAuto: 'no-preserve' + } }, { component: 'Icon', parent: { component: 'MenuItem', - state: ['hover'], + state: ['hover'] }, directives: { textColor: '--link', - textAuto: 'no-preserve', - }, + textAuto: 'no-preserve' + } }, { component: 'Text', parent: { component: 'MenuItem', - state: ['disabled'], + state: ['disabled'] }, directives: { textOpacity: 0.25, - textOpacityMode: 'blend', - }, + textOpacityMode: 'blend' + } }, { component: 'Icon', parent: { component: 'MenuItem', - state: ['disabled'], + state: ['disabled'] }, directives: { textOpacity: 0.25, - textOpacityMode: 'blend', - }, - }, - ], + textOpacityMode: 'blend' + } + } + ] } diff --git a/src/components/mfa_form/recovery_form.js b/src/components/mfa_form/recovery_form.js index 5fa5c187a..84479b1ec 100644 --- a/src/components/mfa_form/recovery_form.js +++ b/src/components/mfa_form/recovery_form.js @@ -1,42 +1,42 @@ -import { mapActions, mapState as mapPiniaState, mapStores } from 'pinia' -import { mapState } from 'vuex' - -import { useAuthFlowStore } from 'src/stores/auth_flow.js' -import { useOAuthStore } from 'src/stores/oauth.js' import mfaApi from '../../services/new_api/mfa.js' - +import { mapState } from 'vuex' +import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia' +import { useOAuthStore } from 'src/stores/oauth.js' +import { useAuthFlowStore } from 'src/stores/auth_flow.js' import { library } from '@fortawesome/fontawesome-svg-core' -import { faTimes } from '@fortawesome/free-solid-svg-icons' +import { + faTimes +} from '@fortawesome/free-solid-svg-icons' -library.add(faTimes) +library.add( + faTimes +) export default { data: () => ({ code: null, - error: false, + error: false }), computed: { ...mapPiniaState(useAuthFlowStore, { - authSettings: (store) => store.settings, + 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 d1378b078..e369d8a5d 100644 --- a/src/components/mfa_form/totp_form.js +++ b/src/components/mfa_form/totp_form.js @@ -1,42 +1,42 @@ -import { mapActions, mapState as mapPiniaState, mapStores } from 'pinia' -import { mapState } from 'vuex' - -import { useAuthFlowStore } from 'src/stores/auth_flow.js' -import { useOAuthStore } from 'src/stores/oauth.js' import mfaApi from '../../services/new_api/mfa.js' - +import { mapState } from 'vuex' +import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia' +import { useOAuthStore } from 'src/stores/oauth.js' +import { useAuthFlowStore } from 'src/stores/auth_flow.js' import { library } from '@fortawesome/fontawesome-svg-core' -import { faTimes } from '@fortawesome/free-solid-svg-icons' +import { + faTimes +} from '@fortawesome/free-solid-svg-icons' -library.add(faTimes) +library.add( + faTimes +) export default { data: () => ({ code: null, - error: false, + error: false }), computed: { ...mapPiniaState(useAuthFlowStore, { - authSettings: (store) => store.settings, + 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 306bd5c17..398bc186e 100644 --- a/src/components/mobile_drawer.style.js +++ b/src/components/mobile_drawer.style.js @@ -1,13 +1,41 @@ export default { name: 'MobileDrawer', selector: '.mobile-drawer', - validInnerComponents: ['MenuItem'], + validInnerComponents: [ + 'Text', + 'Link', + 'Icon', + 'Border', + 'Button', + 'ButtonUnstyled', + 'Input', + 'PanelHeader', + 'MenuItem', + 'Notification', + 'Alert', + 'UserCard' + ], defaultRules: [ { directives: { background: '--bg', - backgroundNoCssColor: 'yes', - }, + backgroundNoCssColor: 'yes' + } }, - ], + { + component: 'PanelHeader', + parent: { component: 'MobileDrawer' }, + directives: { + background: '--fg', + shadow: [{ + x: 0, + y: 0, + blur: 4, + spread: 0, + color: '#000000', + alpha: 0.6 + }] + } + } + ] } diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js index 873bdc40d..2085d24e3 100644 --- a/src/components/mobile_nav/mobile_nav.js +++ b/src/components/mobile_nav/mobile_nav.js @@ -1,98 +1,99 @@ -import { mapState } from 'pinia' -import { mapGetters } from 'vuex' - +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 { 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 { - faArrowUp, - faBars, - faBell, - faCheckDouble, - faMinus, faTimes, + faBell, + faBars, + faArrowUp, + faMinus, + faCheckDouble } from '@fortawesome/free-solid-svg-icons' -library.add(faTimes, faBell, faBars, faArrowUp, faMinus, faCheckDouble) +library.add( + faTimes, + faBell, + faBars, + faArrowUp, + faMinus, + faCheckDouble +) const MobileNav = { components: { SideDrawer, 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. @@ -102,53 +103,53 @@ const MobileNav = { } } }, - notificationsTouchStart(e) { + notificationsTouchStart (e) { GestureService.beginSwipe(e, this.notificationsCloseGesture) }, - notificationsTouchMove(e) { + notificationsTouchMove (e) { GestureService.updateSwipe(e, this.notificationsCloseGesture) }, - scrollToTop() { + scrollToTop () { window.scrollTo(0, 0) }, - scrollMobileNotificationsToTop() { + scrollMobileNotificationsToTop () { this.$refs.mobileNotifications.scrollTo(0, 0) }, - showConfirmLogout() { + showConfirmLogout () { this.showingConfirmLogout = true }, - hideConfirmLogout() { + hideConfirmLogout () { this.showingConfirmLogout = false }, - logout() { + logout () { if (!this.shouldConfirmLogout) { this.doLogout() } else { this.showConfirmLogout() } }, - doLogout() { + doLogout () { this.$router.replace('/main/public') this.$store.dispatch('logout') this.hideConfirmLogout() }, - markNotificationsAsSeen() { + markNotificationsAsSeen () { this.$store.dispatch('markNotificationsAsSeen') }, - onScroll({ target: { scrollTop, clientHeight, scrollHeight } }) { + onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) { this.notificationsAtTop = scrollTop > 0 if (scrollTop + clientHeight >= scrollHeight) { this.$refs.notifications.fetchOlderNotifications() } - }, + } }, watch: { - $route() { + $route () { // handles closing notificaitons when you press any router-link on the // notifications. this.closeMobileNotifications() - }, - }, + } + } } export default MobileNav diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.js b/src/components/mobile_post_status_button/mobile_post_status_button.js index dfa7b6fe2..031013559 100644 --- a/src/components/mobile_post_status_button/mobile_post_status_button.js +++ b/src/components/mobile_post_status_button/mobile_post_status_button.js @@ -1,55 +1,57 @@ 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' -import { library } from '@fortawesome/fontawesome-svg-core' -import { faPen } from '@fortawesome/free-solid-svg-icons' +library.add( + faPen +) -library.add(faPen) - -const HIDDEN_FOR_PAGES = new Set(['chats', 'chat', 'lists-edit']) +const HIDDEN_FOR_PAGES = new Set([ + 'chats', + 'chat', + 'lists-edit' +]) const MobilePostStatusButton = { - data() { + data () { return { hidden: false, scrollingDown: false, inputActive: false, oldScrollPos: 0, - amountScrolled: 0, + amountScrolled: 0 } }, - created() { + created () { if (this.autohideFloatingPostButton) { this.activateFloatingPostButtonAutohide() } window.addEventListener('resize', this.handleOSK) }, - unmounted() { + unmounted () { if (this.autohideFloatingPostButton) { this.deactivateFloatingPostButtonAutohide() } window.removeEventListener('resize', this.handleOSK) }, computed: { - isLoggedIn() { + isLoggedIn () { return !!this.$store.state.users.currentUser }, - isHidden() { - if (HIDDEN_FOR_PAGES.has(this.$route.name)) { - return true - } + isHidden () { + if (HIDDEN_FOR_PAGES.has(this.$route.name)) { return true } - return ( - this.autohideFloatingPostButton && (this.hidden || this.inputActive) - ) + return this.autohideFloatingPostButton && (this.hidden || this.inputActive) }, - isPersistent() { + isPersistent () { return !!this.$store.getters.mergedConfig.alwaysShowNewPostButton }, - autohideFloatingPostButton() { + autohideFloatingPostButton () { return !!this.$store.getters.mergedConfig.autohideFloatingPostButton - }, + } }, watch: { autohideFloatingPostButton: function (isEnabled) { @@ -58,21 +60,21 @@ const MobilePostStatusButton = { } else { this.deactivateFloatingPostButtonAutohide() } - }, + } }, methods: { - activateFloatingPostButtonAutohide() { + activateFloatingPostButtonAutohide () { window.addEventListener('scroll', this.handleScrollStart) window.addEventListener('scroll', this.handleScrollEnd) }, - deactivateFloatingPostButtonAutohide() { + deactivateFloatingPostButtonAutohide () { window.removeEventListener('scroll', this.handleScrollStart) window.removeEventListener('scroll', this.handleScrollEnd) }, - openPostForm() { + openPostForm () { usePostStatusStore().openPostStatusModal() }, - handleOSK() { + handleOSK () { // This is a big hack: we're guessing from changed window sizes if the // on-screen keyboard is active or not. This is only really important // for phones in portrait mode and it's more important to show the button @@ -92,28 +94,20 @@ const MobilePostStatusButton = { this.inputActive = false } }, - handleScrollStart: debounce( - function () { - if (window.scrollY > this.oldScrollPos) { - this.hidden = true - } else { - this.hidden = false - } - this.oldScrollPos = window.scrollY - }, - 100, - { leading: true, trailing: false }, - ), - - handleScrollEnd: debounce( - function () { + handleScrollStart: debounce(function () { + if (window.scrollY > this.oldScrollPos) { + this.hidden = true + } else { this.hidden = false - this.oldScrollPos = window.scrollY - }, - 100, - { leading: false, trailing: true }, - ), - }, + } + this.oldScrollPos = window.scrollY + }, 100, { leading: true, trailing: false }), + + handleScrollEnd: debounce(function () { + this.hidden = false + this.oldScrollPos = window.scrollY + }, 100, { leading: false, trailing: true }) + } } export default MobilePostStatusButton diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue index a022f7427..032e7dbeb 100644 --- a/src/components/modal/modal.vue +++ b/src/components/modal/modal.vue @@ -13,27 +13,27 @@ diff --git a/src/components/modal/modals.style.js b/src/components/modal/modals.style.js index 9c8c279f7..1cb4a34a7 100644 --- a/src/components/modal/modals.style.js +++ b/src/components/modal/modals.style.js @@ -3,6 +3,8 @@ export default { selector: ['.modal-view', '#modal', '.shout-panel'], lazy: true, notEditable: true, - validInnerComponents: ['Panel'], - defaultRules: [], + validInnerComponents: [ + 'Panel' + ], + defaultRules: [] } diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js index fdb8a74b6..bd57a353a 100644 --- a/src/components/moderation_tools/moderation_tools.js +++ b/src/components/moderation_tools/moderation_tools.js @@ -1,9 +1,9 @@ -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' +import DialogModal from '../dialog_modal/dialog_modal.vue' +import Popover from '../popover/popover.vue' + library.add(faChevronDown) const FORCE_NSFW = 'mrf_tag:media-force-nsfw' @@ -15,8 +15,10 @@ const SANDBOX = 'mrf_tag:sandbox' const QUARANTINE = 'mrf_tag:quarantine' const ModerationTools = { - props: ['user'], - data() { + props: [ + 'user' + ], + data () { return { tags: { FORCE_NSFW, @@ -25,124 +27,92 @@ 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/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue index da33b89dc..9c8894bff 100644 --- a/src/components/moderation_tools/moderation_tools.vue +++ b/src/components/moderation_tools/moderation_tools.vue @@ -208,8 +208,6 @@ } .moderation-tools-button { - white-space: nowrap; - svg, i { font-size: 0.8em; diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.js b/src/components/mrf_transparency_panel/mrf_transparency_panel.js index 2bd82f486..13cfb52ee 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 { get } from 'lodash' import { mapState } from 'vuex' +import { get } from 'lodash' /** * This is for backwards compatibility. We originally didn't recieve @@ -8,7 +8,7 @@ import { mapState } from 'vuex' * 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,82 +19,56 @@ 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/mrf_transparency_panel/mrf_transparency_panel.scss b/src/components/mrf_transparency_panel/mrf_transparency_panel.scss index 8e2fa553a..a262ed1c5 100644 --- a/src/components/mrf_transparency_panel/mrf_transparency_panel.scss +++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.scss @@ -4,13 +4,13 @@ table { width: 100%; text-align: left; - padding-left: 0.5em; - padding-bottom: 1.1em; + padding-left: 10px; + padding-bottom: 20px; th, td { - width: 11em; - max-width: 25em; + width: 180px; + max-width: 360px; overflow: hidden; vertical-align: text-top; } diff --git a/src/components/mute_card/mute_card.js b/src/components/mute_card/mute_card.js index d5226ef06..cbec0e9bb 100644 --- a/src/components/mute_card/mute_card.js +++ b/src/components/mute_card/mute_card.js @@ -1,41 +1,40 @@ -import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue' import BasicUserCard from '../basic_user_card/basic_user_card.vue' const MuteCard = { props: ['userId'], + data () { + return { + progress: false + } + }, computed: { - user() { + user () { return this.$store.getters.findUser(this.userId) }, - relationship() { + relationship () { return this.$store.getters.relationship(this.userId) }, - muted() { + muted () { return this.relationship.muting - }, - muteExpiryAvailable() { - return Object.hasOwn(this.user, 'mute_expires_at') - }, - muteExpiry() { - return this.user.mute_expires_at === false - ? this.$t('user_card.mute_expires_forever') - : this.$t('user_card.mute_expires_at', [ - new Date(this.user.mute_expires_at).toLocaleString(), - ]) - }, + } }, components: { - BasicUserCard, - UserTimedFilterModal, + BasicUserCard }, methods: { - unmuteUser() { - this.$store.dispatch('unmuteUser', this.userId) + unmuteUser () { + this.progress = true + this.$store.dispatch('unmuteUser', this.userId).then(() => { + this.progress = false + }) }, - muteUser() { - this.$refs.timedMuteDialog.optionallyPrompt() - }, - }, + muteUser () { + this.progress = true + this.$store.dispatch('muteUser', this.userId).then(() => { + this.progress = false + }) + } + } } export default MuteCard diff --git a/src/components/mute_card/mute_card.vue b/src/components/mute_card/mute_card.vue index 83520dc22..97e91cbca 100644 --- a/src/components/mute_card/mute_card.vue +++ b/src/components/mute_card/mute_card.vue @@ -1,35 +1,33 @@ diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index d80cd0a5d..a155abe0c 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -1,32 +1,32 @@ -import { mapState as mapPiniaState } from 'pinia' -import { mapGetters, mapState } from 'vuex' - import BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue' -import Checkbox from 'src/components/checkbox/checkbox.vue' import ListsMenuContent from 'src/components/lists_menu/lists_menu_content.vue' +import { mapState, mapGetters } from 'vuex' +import { mapState as mapPiniaState } from 'pinia' +import { TIMELINES, ROOT_ITEMS } from 'src/components/navigation/navigation.js' import { filterNavigation } from 'src/components/navigation/filter.js' -import { ROOT_ITEMS, TIMELINES } from 'src/components/navigation/navigation.js' import NavigationEntry from 'src/components/navigation/navigation_entry.vue' import NavigationPins from 'src/components/navigation/navigation_pins.vue' +import Checkbox from 'src/components/checkbox/checkbox.vue' + import { useAnnouncementsStore } from 'src/stores/announcements' import { useServerSideStorageStore } from 'src/stores/serverSideStorage' import { library } from '@fortawesome/fontawesome-svg-core' import { - faBell, + faUsers, + faGlobe, + faCity, faBookmark, - faBullhorn, + faEnvelope, faChevronDown, faChevronUp, - faCity, faComments, - faEnvelope, - faFilePen, - faGlobe, + faBell, faInfoCircle, - faList, faStream, - faUsers, + faList, + faBullhorn, + faFilePen } from '@fortawesome/free-solid-svg-icons' library.add( @@ -43,92 +43,80 @@ library.add( faStream, faList, faBullhorn, - faFilePen, + faFilePen ) const NavPanel = { props: ['forceExpand', 'forceEditMode'], + created () { + }, components: { BookmarkFoldersMenuContent, ListsMenuContent, NavigationEntry, NavigationPins, - Checkbox, + Checkbox }, - data() { + data () { return { editMode: false, showTimelines: false, showLists: false, showBookmarkFolders: false, - timelinesList: Object.entries(TIMELINES).map(([k, v]) => ({ - ...v, - name: k, - })), - rootList: Object.entries(ROOT_ITEMS).map(([k, v]) => ({ ...v, name: k })), + timelinesList: Object.entries(TIMELINES).map(([k, v]) => ({ ...v, name: k })), + rootList: Object.entries(ROOT_ITEMS).map(([k, v]) => ({ ...v, name: k })) } }, methods: { - toggleTimelines() { + toggleTimelines () { this.showTimelines = !this.showTimelines }, - toggleLists() { + toggleLists () { this.showLists = !this.showLists }, - toggleBookmarkFolders() { + toggleBookmarkFolders () { this.showBookmarkFolders = !this.showBookmarkFolders }, - toggleEditMode() { + toggleEditMode () { this.editMode = !this.editMode }, - toggleCollapse() { - 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 })), @@ -139,13 +127,15 @@ 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, @@ -153,12 +143,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 0255db6aa..54abb67b4 100644 --- a/src/components/navigation/filter.js +++ b/src/components/navigation/filter.js @@ -1,50 +1,39 @@ -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 66fb0d347..d1c2b6763 100644 --- a/src/components/navigation/navigation.js +++ b/src/components/navigation/navigation.js @@ -4,39 +4,42 @@ export const USERNAME_ROUTES = new Set([ 'interactions', 'notifications', 'chat', - 'chats', + 'chats' ]) // routes that take :name property -export const NAME_ROUTES = new Set(['user-profile', 'legacy-user-profile']) +export const NAME_ROUTES = new Set([ + 'user-profile', + 'legacy-user-profile' +]) export const TIMELINES = { home: { route: 'friends', icon: 'home', label: 'nav.home_timeline', - criteria: ['!private'], + criteria: ['!private'] }, public: { route: 'public-timeline', anon: true, icon: 'users', label: 'nav.public_tl', - criteria: ['!private'], + criteria: ['!private'] }, bubble: { route: 'bubble', anon: true, icon: 'city', label: 'nav.bubble', - criteria: ['!private', 'federating', 'supportsBubbleTimeline'], + criteria: ['!private', 'federating', 'supportsBubbleTimeline'] }, twkn: { route: 'public-external-timeline', anon: true, icon: 'globe', label: 'nav.twkn', - criteria: ['!private', 'federating'], + criteria: ['!private', 'federating'] }, // bookmarks are still technically a timeline so we should show it in the dropdown bookmarks: { @@ -47,13 +50,13 @@ export const TIMELINES = { favorites: { routeObject: { name: 'user-profile', query: { tab: 'favorites' } }, icon: 'star', - label: 'user_card.favorites', + label: 'user_card.favorites' }, dms: { route: 'dms', icon: 'envelope', - label: 'nav.dms', - }, + label: 'nav.dms' + } } export const ROOT_ITEMS = { @@ -64,12 +67,12 @@ export const ROOT_ITEMS = { // shows bookmarks entry in a better suited location // hides it when bookmark folders are supported since // we show custom component instead of it - criteria: ['!supportsBookmarkFolders'], + criteria: ['!supportsBookmarkFolders'] }, interactions: { route: 'interactions', icon: 'bell', - label: 'nav.interactions', + label: 'nav.interactions' }, chats: { route: 'chats', @@ -77,7 +80,7 @@ export const ROOT_ITEMS = { label: 'nav.chats', badgeStyle: 'notification', badgeGetter: 'unreadChatCount', - criteria: ['chats'], + criteria: ['chats'] }, friendRequests: { route: 'friend-requests', @@ -85,13 +88,13 @@ export const ROOT_ITEMS = { label: 'nav.friend_requests', badgeStyle: 'notification', criteria: ['lockedUser'], - badgeGetter: 'followRequestCount', + badgeGetter: 'followRequestCount' }, about: { route: 'about', anon: true, icon: 'info-circle', - label: 'nav.about', + label: 'nav.about' }, announcements: { route: 'announcements', @@ -100,18 +103,18 @@ export const ROOT_ITEMS = { store: 'announcements', badgeStyle: 'notification', badgeGetter: 'unreadAnnouncementCount', - criteria: ['announcements'], + criteria: ['announcements'] }, drafts: { route: 'drafts', icon: 'file-pen', label: 'nav.drafts', badgeStyle: 'neutral', - badgeGetter: 'draftCount', - }, + badgeGetter: 'draftCount' + } } -export function routeTo(item, currentUser) { +export function routeTo (item, currentUser) { if (!item.route && !item.routeObject) return null let route @@ -119,7 +122,7 @@ export function routeTo(item, currentUser) { if (item.routeObject) { route = item.routeObject } else { - route = { name: item.anon || currentUser ? item.route : item.anonRoute } + route = { name: (item.anon || currentUser) ? item.route : item.anonRoute } } if (USERNAME_ROUTES.has(route.name)) { diff --git a/src/components/navigation/navigation_entry.js b/src/components/navigation/navigation_entry.js index 75d4dffdd..11db1c9e3 100644 --- a/src/components/navigation/navigation_entry.js +++ b/src/components/navigation/navigation_entry.js @@ -1,56 +1,48 @@ -import { mapState as mapPiniaState, mapStores } from 'pinia' import { mapState } from 'vuex' - import { routeTo } from 'src/components/navigation/navigation.js' import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue' -import { useAnnouncementsStore } from 'src/stores/announcements' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage' - import { library } from '@fortawesome/fontawesome-svg-core' import { faThumbtack } from '@fortawesome/free-solid-svg-icons' +import { mapStores, mapState as mapPiniaState } from 'pinia' + +import { useAnnouncementsStore } from 'src/stores/announcements' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' library.add(faThumbtack) const NavigationEntry = { props: ['item', 'showPin'], components: { - OptionalRouterLink, + OptionalRouterLink }, methods: { - isPinned(value) { + isPinned (value) { return this.pinnedItems.has(value) }, - togglePin(value) { + togglePin (value) { if (this.isPinned(value)) { - 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 e14edce14..f9a900fc6 100644 --- a/src/components/navigation/navigation_pins.js +++ b/src/components/navigation/navigation_pins.js @@ -1,35 +1,27 @@ -import { mapState as mapPiniaState } from 'pinia' import { mapState } from 'vuex' +import { mapState as mapPiniaState } from 'pinia' +import { TIMELINES, ROOT_ITEMS, routeTo } from 'src/components/navigation/navigation.js' +import { getBookmarkFolderEntries, getListEntries, filterNavigation } from 'src/components/navigation/filter.js' -import { - filterNavigation, - getBookmarkFolderEntries, - getListEntries, -} from 'src/components/navigation/filter.js' -import { - ROOT_ITEMS, - routeTo, - TIMELINES, -} from 'src/components/navigation/navigation.js' import 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 { - faBell, - faBookmark, - faCity, - faComments, - faEnvelope, - faGlobe, - faInfoCircle, - faList, - faStream, faUsers, + faGlobe, + faCity, + faBookmark, + faEnvelope, + faComments, + faBell, + faInfoCircle, + faStream, + faList } from '@fortawesome/free-solid-svg-icons' +import { useListsStore } from 'src/stores/lists' +import { useAnnouncementsStore } from 'src/stores/announcements' +import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' library.add( faUsers, @@ -41,74 +33,72 @@ 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, @@ -117,11 +107,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 275827c43..043be1b1a 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -1,34 +1,29 @@ -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 { mapState } from 'vuex' +import Status from '../status/status.vue' import UserAvatar from '../user_avatar/user_avatar.vue' import UserCard from '../user_card/user_card.vue' +import Timeago from '../timeago/timeago.vue' +import Report from '../report/report.vue' import UserLink from '../user_link/user_link.vue' +import RichContent from 'src/components/rich_content/rich_content.jsx' import UserPopover from '../user_popover/user_popover.vue' - +import ConfirmModal from '../confirm_modal/confirm_modal.vue' +import { isStatusNotification } from '../../services/notification_utils/notification_utils.js' +import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' +import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import { library } from '@fortawesome/fontawesome-svg-core' import { faCheck, - faCompressAlt, - faExpandAlt, - faEyeSlash, - faRetweet, - faStar, - faSuitcaseRolling, faTimes, - faUser, + faStar, + faRetweet, faUserPlus, + faEyeSlash, + faUser, + faSuitcaseRolling, + faExpandAlt, + faCompressAlt } from '@fortawesome/free-solid-svg-icons' library.add( @@ -41,17 +36,16 @@ library.add( faEyeSlash, faSuitcaseRolling, faExpandAlt, - faCompressAlt, + faCompressAlt ) const Notification = { - data() { + data () { return { - selecting: false, statusExpanded: false, unmuted: false, showingApproveConfirmDialog: false, - showingDenyConfirmDialog: false, + showingDenyConfirmDialog: false } }, props: ['notification'], @@ -66,158 +60,113 @@ const Notification = { RichContent, UserPopover, UserLink, - ConfirmModal, - }, - mounted() { - document.addEventListener('selectionchange', this.onContentSelect) - }, - unmounted() { - document.removeEventListener('selectionchange', this.onContentSelect) + ConfirmModal }, methods: { - toggleStatusExpanded() { - if (!this.expandable) return + toggleStatusExpanded () { this.statusExpanded = !this.statusExpanded }, - onContentSelect() { - const { isCollapsed, anchorNode, offsetNode } = document.getSelection() - if (isCollapsed) { - this.selecting = false - return - } - const within = - this.$refs.root.contains(anchorNode) || - this.$refs.root.contains(offsetNode) - if (within) { - this.selecting = true - } else { - this.selecting = false - } + generateUserProfileLink (user) { + return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames) }, - onContentClick(e) { - if ( - !this.selecting && - !e.target.closest('a') && - !e.target.closest('button') - ) { - this.toggleStatusExpanded() - } - }, - generateUserProfileLink(user) { - return generateProfileLink( - user.id, - user.screen_name, - 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, - ) - }, - 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.scss b/src/components/notification/notification.scss index 934d3e58d..e8895ce59 100644 --- a/src/components/notification/notification.scss +++ b/src/components/notification/notification.scss @@ -1,15 +1,10 @@ // TODO Copypaste from Status, should unify it somehow - .Notification { border-bottom: 1px solid; border-color: var(--border); overflow-wrap: break-word; text-wrap: pretty; - .status-content { - cursor: pointer; - } - &.Status { /* stylelint-disable-next-line declaration-no-important */ background-color: transparent !important; diff --git a/src/components/notification/notification.style.js b/src/components/notification/notification.style.js index 49e28cf2e..c6d317d1c 100644 --- a/src/components/notification/notification.style.js +++ b/src/components/notification/notification.style.js @@ -6,8 +6,13 @@ export default { 'Link', 'Icon', 'Border', + 'Button', + 'ButtonUnstyled', + 'RichContent', + 'Input', 'Avatar', - 'PollGraph', + 'Attachment', + 'PollGraph' ], - defaultRules: [], + defaultRules: [] } diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index 7165289dc..0930e0990 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -1,7 +1,6 @@