commit: 1c5cfea174612c87a7fc6382aecf7fb485482a6e
parent 93a87b56460393033ee672d31e8da12d57739395
Author: HJ <30-hj@users.noreply.git.pleroma.social>
Date: Thu, 26 Dec 2024 23:32:44 +0000
Merge branch 'develop' into 'kludge/null-status'
# Conflicts:
# src/services/entity_normalizer/entity_normalizer.service.js
Diffstat:
575 files changed, 43082 insertions(+), 15334 deletions(-)
diff --git a/.babelrc b/.babelrc
@@ -1,5 +1,5 @@
{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-jsx"],
- "comments": false
+ "comments": true
}
diff --git a/.browserslistrc b/.browserslistrc
@@ -0,0 +1,7 @@
+>0.2%
+not op_mini all
+Safari > 15
+Firefox >= 115
+Firefox ESR
+Android > 4
+not dead
diff --git a/.eslintrc.js b/.eslintrc.js
@@ -1,7 +1,7 @@
module.exports = {
root: true,
parserOptions: {
- parser: 'babel-eslint',
+ parser: '@babel/eslint-parser',
sourceType: 'module'
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
@@ -21,6 +21,7 @@ module.exports = {
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
- 'vue/require-prop-types': 0
+ 'vue/require-prop-types': 0,
+ 'vue/multi-word-component-names': 0
}
}
diff --git a/.gitattributes b/.gitattributes
@@ -0,0 +1 @@
+/build/webpack.prod.conf.js export-subst
diff --git a/.gitignore b/.gitignore
@@ -7,3 +7,5 @@ test/e2e/reports
selenium-debug.log
.idea/
config/local.json
+static/emoji.json
+logs/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
@@ -4,11 +4,36 @@
image: node:16
stages:
+ - check-changelog
- lint
- build
- test
- deploy
+# https://git.pleroma.social/help/ci/yaml/workflow.md#switch-between-branch-pipelines-and-merge-request-pipelines
+workflow:
+ rules:
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+ - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
+ when: never
+ - if: $CI_COMMIT_BRANCH
+
+check-changelog:
+ stage: check-changelog
+ image: alpine
+ rules:
+ - if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma-fe' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^renovate/
+ when: never
+ - if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma-fe' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == 'weblate'
+ when: never
+ - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"
+ before_script: ''
+ after_script: ''
+ cache: {}
+ script:
+ - apk add git
+ - sh ./tools/check-changelog
+
lint:
stage: lint
script:
@@ -18,6 +43,9 @@ lint:
test:
stage: test
+ tags:
+ - amd64
+ - himem
variables:
APT_CACHE_DIR: apt-cache
script:
@@ -29,6 +57,9 @@ test:
build:
stage: build
+ tags:
+ - amd64
+ - himem
script:
- yarn
- npm run build
diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md
@@ -0,0 +1,25 @@
+# Environment info
+
+<!-- Everything is optional and where applicable but the more information the better. -->
+* Browser, version, OS, platform:
+* Instance URL:
+* Frontend version (see settings -> about):
+* Backend version (see settings -> about):
+* Browser extensions (ublock, rikaichamp etc):
+* Known instance/user customizations (i.e. pleromafe mods/forks, instance styles etc)
+
+# Bug description & reproduction steps
+
+<!-- Type out here how to reproduce the bug, what goes wrong and what should go right -->
+<!-- Screenshots and videos help a lot ;) any observations might also help -->
+<!-- Also mention if there any errors in browser's console if relevant -->
+
+# Bug seriousness
+
+<!-- Everything is optional and free-form -->
+* How annoying it is:
+* How often does it happen:
+* How many people does it affect:
+* Is there a workaround for it:
+
+/label ~Bug
diff --git a/.gitlab/issue_templates/Suggestion.md b/.gitlab/issue_templates/Suggestion.md
@@ -0,0 +1,11 @@
+# Behavior suggestion/Feature request
+<!--
+Type out what you want to see changed or what feature you want to see added to
+PleormaFE. Please also explain how it would benefit users (or admins/moderators)
+and what intended usecase is. Any background information (i.e. porting behavior
+from other frontends/services, specific situations, personal preferences etc.)
+as well as examples would be greatly appreciated.
+-->
+
+/label ~suggestion
+
diff --git a/.gitlab/issue_templates/default.md b/.gitlab/issue_templates/default.md
@@ -0,0 +1,7 @@
+<!--
+please use one of the templates if applicable, otherwise - type out here
+in free-form
+-->
+
+/label ~needs-triage
+
diff --git a/.gitlab/merge_request_templates/default.md b/.gitlab/merge_request_templates/default.md
@@ -0,0 +1,30 @@
+<!--
+Feel free to submit merge requests that are work-in-progress, but mark them as
+Draft: or WIP:.
+Merge requests that have Draft or WIP status will not be merged and have less chances
+of being reviewed, but you can still ask people to take a look if you need advice.
+-->
+# Changes
+
+*
+*
+*
+
+<!-- List what your merge request changes and how -->
+<!--
+Try to not to break existing behavior, if your changes do break existing behavior
+make it configurable to toggle between old behavior and new. Which one should be
+default is up to discussion.
+-->
+<!-- If your merge request resolves some issue link it like so: "Closes #99999" -->
+<!--
+If merge request adds some new feature that depends on backend:
+
+1. Make sure it gracefully degrades if backend hasn't been updated to support the feature,
+ we try to make PleromaFE compatible with older versions of BE so that people can still
+ update frontend safely without updating backend since it's costly and much riskier.
+2. Link related BE merge request here
+-->
+<!-- Screenshots are welcome -->
+
+/label ~needs-review
diff --git a/.node-version b/.node-version
@@ -1 +1 @@
-16.16.0
+16.18.1
diff --git a/.stylelintrc.json b/.stylelintrc.json
@@ -1,19 +1,41 @@
{
"extends": [
"stylelint-rscss/config",
- "stylelint-config-recommended",
- "stylelint-config-standard"
+ "stylelint-config-standard",
+ "stylelint-config-recommended-scss",
+ "stylelint-config-html",
+ "stylelint-config-recommended-vue/scss"
],
"rules": {
"declaration-no-important": true,
"rscss/no-descendant-combinator": false,
"rscss/class-format": [
- true,
+ false,
{
"component": "pascal-case",
"variant": "^-[a-z]\\w+",
"element": "^[a-z]\\w+"
}
+ ],
+ "selector-class-pattern": null,
+ "import-notation": null,
+ "custom-property-pattern": null,
+ "keyframes-name-pattern": null,
+ "scss/operator-no-newline-after": null,
+ "declaration-block-no-redundant-longhand-properties": [
+ true,
+ {
+ "ignoreShorthands": [
+ "grid-template",
+ "margin",
+ "padding",
+ "border",
+ "border-width",
+ "border-style",
+ "border-color",
+ "border-radius"
+ ]
+ }
]
}
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
@@ -3,41 +3,203 @@ 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/).
-## Unreleased
+## 2.7.1
+Bugfix release. Added small optimizations to emoji picker that should make it a bit more responsive, however it needs rather large change to make it more performant which might come in a major release.
+
### Fixed
-- AdminFE button no longer scrolls page to top when clicked
+- Instance default theme not respected
+- Nested panel header having wrong sticky position if navbar height != panel header height
+- Toggled buttons having bad contrast (when using v2 theme)
+
+### Changed
+- Simplify the OAuth client_name to 'PleromaFE'
+- Small optimizations to emoji picker
+
+
+## 2.7.0
+
+### Known issues
+We got some reports related to emoji picker performance, this hopefully will be fixed in 2.7.1.
+
+### Notes
+This release overhauls how themes work, themes now need to be "compiled", which can cause some delay when loading for the first time and temporarily look "wrong" in some places (popups, menus, dialogs). Please do report any issues, especially if your theme looks wrong or breaks interface when loading. Also report issues if you're experiencing constant performance issues.
+
+To admins: remember that you can update PleromaFE to recent `master` or `develop` in admin dashboard in "Front-ends" tab, scroll down to find PleromaFE box and click "Reinstall `master`" or dropdown and then "Reinstall `develop`". Currently there is no mechanism to check if there is an update or not.
+
+### Changed
+- Overhauled the way themes work, migrating to new Pleroma Interface Style Sheets system aka "Themes 3".
+- Notifications are no longer sorted by "seen" status since interacting with them can change their read status and makes UI jumpy. Old behavior can be restored in settings.
+- Notifications are now shown through a ServiceWorker (since mobile chrome does not allow them otherwise), it's always enabled, even if previously we only enabled it for WebPush notifications only. If you don't like websites "running" while closed, check how to disable them in your browser. Old way to show notifications will be used as a fallback but might not have all the new features.
+- Reorganized Settings modal to move out visual stuff into Appearance tab
+
+### Added
+- Emoji pack management to the admin panel
+- Support `status` notification type (subscriptions/bell, fixes PleromaFE on newer PleromaBE versions)
+- Poll end notifications.
+- Added option to not mark all notifications when closing notifications drawer on mobile, this creates a new button to mark all as seen.
+- Option to always "show" notifications when using web push for better compatibility with some browsers (chrome, edge, safari)
+- Option to toggle what notification types appear in native notifications, by default less important ones (likes, repeats, etc) will no longer show up in native notifications.
+- Option to treat non-interactive notifications (likes, repeats et all) as seen for visual purposes (no read mark, ignored in counters, still can show in native notifications)
+- Ability to resize UI (and certain components) scale independent of browser/text scale
+- Ability to override certain aspects of UI style independent of theme used (UI roundness, fonts, underlay)
+- Theme selector with visual previews of the theme
+- Display loading and error indicator for conversation page
+- Option to only show scrobbles that are recent enough
+- Interacting (opening reply box etc) or simply clicking on non-interactive notifications now marks them as read. Clicking on native notifications for non-interactive ones also marks them as seen.
+- Support group actors
+- Focusing into a tab clears all current desktop notifications
+- Ability to change size of emoji
+- Ability to view APNG (Animated PNG) attachments.
+- Support showing extra notifications in the notifications column
+- Create a link to the URL of the scrobble when it's present
+- Allow hiding custom emojis in picker.
+- Ability to mute sensitive posts (ported from eintei).
+- Native notifications now also have "badge" property that matches instance's favicon (visible in Android Chromium at least)
+- Display public favorites on user profiles
+- Display quotes count on posts and add quotes list page
+- Show a dedicated registration notice page when further action is required after registering
+
+### Fixed
+- Synchronized requested notification types with backend, hopefully should fix missing notifications for polls and follow requests
+- Error that appeared on mobile Chromium (and derivatives) when native notifications are allowed
+- Being unable to set notification visibility for reports and follow requests
+- Native notifications appearing as many times as there are open tabs. Clicking on notification will focus last focused tab.
+- The expiry date indication won't be shown if the poll never expires
+- Profile mentions causing a 422 error on newer PleromaBE versions.
+- Color inputs are less ugly now
+- Unread notifications should now properly catch up between sessions (eventually) in polling mode
+- Video posters on Safari
+
+
+## 2.6.1
+### Fixed
+- fix admin dashboard not having any feedback on frontend installation
+- Fix frontend admin tab crashing when no primary frontend is set
+- Add aria attributes to react and extra buttons
+
+## 2.6.0
+### Added
+- add the initial i18n translation file for Taiwanese (Hokkien), and modify some related files.
+- Implemented a very basic instance administration screen
+- Implement quoting
+
+### Fixed
+- Keep aspect ratio of custom emoji reaction in notification
+- Fix openSettingsModalTab so that it correctly opens Settings modal instead of Admin modal
+- Add alt text to emoji picker buttons
+- Use export-subst gitattribute to allow tarball builds
+- fix reports now showing reason/content
+- Fix HTML attribute parsing, discard attributes not strating with a letter
+- Make MentionsLine aware of line breaking by non-br elements
+- Fix a bug where mentioning a user twice will not fill the mention into the textarea
+- Fix parsing non-ascii tags
+- Fix OAuth2 token lingering after revocation
+- fix regex issue in HTML parser/renderer
+- don't display quoted status twice
+- fix typo in code that prevented cards from showing at all
+- Fix react button not working if reaction accounts are not loaded
+- Fix react button misalignment on safari ios
+- Fix pinned statuses gone when reloading user timeline
+- Fix scrolling emoji selector in modal in safari ios
+
+## 2.5.1
+### Fixed
+- Checkboxes in settings can now work with screenreaders
+- Autocomplete in edit boxes can now work with screenreaders
+- Status interact buttons now have focus indicator for anonymous users
+- Top bar buttons now correctly have text labels
+- It is now possible to register if the site admin requires birthday to register
+- User cards from search results will correctly popup
+- Fix notification attachment icon overflow
+- Editing mute words is less laggy
+- Repeater's name will no longer mess up with the directionality of the text sitting on the same line
+- Unauthenticated access will give better error messages
+- It is now easier to close the media viewer with a mouse when there is only one image
+- Deleting profile fields can work properly
+- Clicking the react button will correctly focus the search box
+- Clicking buttons on the top-bar will no longer bring you to the top of the page
+- Emoji picker is much faster to load
+- `blockquote`s have a better display style
+- Announcements posting and editing are now available to everyone with such a privilege, not just admins
+- Adding or removing list members will actually work
+- Emojis without a pack are now correctly displayed in emoji picker
+- Changing notification settings will actually work
+
+### Added
+- You can now set and see birthdays
+- Optional confirmation dialogs when performing various actions
+- You can now set fallback languages
+
+## 2.5.0 - 23.12.2022
+### Fixed
+- UI no longer lags when switching between mobile and desktop mode
+- Popovers no longer constrained by DOM hierarchy, shouldn't be cut off by anything
+- Emoji autocomplete popover and picker popover stick to the text cursor.
+- Attachments are ALWAYS in same order as user uploaded, no more "videos first"
- Pinned statuses no longer appear at bottom of user timeline (still appear as part of the timeline when fetched deep enough)
- Fixed many many bugs related to new mentions, including spacing and alignment issues
- Links in profile bios now properly open in new tabs
+- "Always show mobile button" is working now
- Inline images now respect their intended width/height attributes
- Links with `&` in them work properly now
-- Interaction list popovers now properly emojify names
-- Completely hidden posts still had 1px border
-- Attachments are ALWAYS in same order as user uploaded, no more "videos first"
- Attachment description is prefilled with backend-provided default when uploading
- Proper visual feedback that next image is loading when browsing
+- Additional HTML sanitization on frontend side in case backend sanitization fails
+- Interaction list popovers now properly emojify names
+- AdminFE button no longer scrolls page to top when clicked
+- User handles with non-ascii domains now have less intrusive indicator for the domain name
+- Completely hidden posts still no longer have 1px border
+- A lot of accessibility improvements
### Changed
-- (You)s are optional (opt-in) now, bolding your nickname is also optional (opt-out)
+- Using Vue 3 now
+- A lot of internal dependencies updated
+- "(You)s" are optional (opt-in) now, bolding your nickname is also optional (opt-out)
- User highlight background now also covers the `@`
- Reverted back to textual `@`, svg version is opt-in.
-- Settings window has been throughly rearranged to make make more sense and make navication settings easier.
+- Settings window has been thoroughly rearranged to make more sense and make navigation settings easier.
- Uploaded attachments are uniform with displayed attachments
- Flash is watchable in media-modal (takes up nearly full screen though due to sizing issues)
-- Notifications about likes/repeats/emoji reacts are now minimized so they always take up same amount of space irrelevant to size of post.
+- Notifications about likes/repeats/emoji reacts are now minimized so they always take up same amount of space irrelevant to size of post. (You can expand them to full if need be)
+- Slight width/spacing adjustments
+- More sizing stuff is font-size dependent now
+- Scrollbars are styled/colorized now
+- Scrollbars are toggleable (for stuff that didn't have visible scrollbars before) (opt-in)
+- Updated localization files
+- Top bar is more useful in mobile mode now.
+- "Show new" button is way more compact in mobile mode
+- Slightly adjusted placement and spacing of the topbar buttons so it's less easy to accidentally log yourself out
### Added
+- 3 column mode: only enables when there's space for it (opt-out, customizable)
+- Apologetic pleroma-tan
+- New button on timeline header to change some of the new and often-used settings
+- Support for lists
+- Added ability to edit posts and view post edit history etc.
+- Added ability to add personal note to users
+- Added initial support for admin announcements
+- Added ui for account migration
+- Added ui for backups
+- Added ability to force-unfollow a user from you
+- Emoji are now grouped by pack
+- Ability to pin navigation items and collapse the navigation menu
+- Ability to rearrange order of attachments when uploading
+- Ability to scroll column (or page) to top via panel header button
- Options to show domains in mentions
- Option to show user avatars in mention links (opt-in)
- Option to disable the tooltip for mentions
- Option to completely hide muted threads
+- Option to customize what clicking user avatar does in user popover
+- Notifications for poll results
+- "Favorites" link in navigation
+- Very early and somewhat experimental system for automatic settings sync (used only for pinned navigation and apologetic pleroma-tan)
+- Implemented remote interaction with statuses for anon visitors
- Ability to open videos in modal even if you disabled that feature, via an icon button
- New button on attachment that indicates that attachment has a description and shows a bar filled with description
- Attachments are truncated just like post contents
- Media modal now also displays description and counter position in gallery (i.e. 1/5)
-- Ability to rearrange order of attachments when uploading
- Enabled users to zoom and pan images in media viewer with mouse and touch
-- Added frontend ui for account migration
+- Timelines/panels and conversations have sticky headers now (a bit glitchy on some browsers like safari) (opt-out)
## [2.4.2] - 2022-01-09
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
@@ -10,3 +10,5 @@ Contributors of this project.
- shpuld (shpuld@shitposter.club): CSS and styling
- Vincent Guth (https://unsplash.com/photos/XrwVIFy6rTw): Background images.
- hj (hj@shigusegubu.club): Code
+- Sean King (seanking@kazv.moe): Code
+- tusooa (tusooa@kazv.moe): Code
diff --git a/README.md b/README.md
@@ -1,18 +1,19 @@
# Pleroma-FE
-> A single column frontend designed for Pleroma.
+> Highly-customizable frontend designed for Pleroma.
-![screenshot](/uploads/796c5ecf985ed1e2b0943ee0df131ed0/DJVqSJ0.png)
+![screenshot](./image-1.png)
# For Translators
-To translate Pleroma-FE, add your language to [src/i18n/messages.js](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/messages.js). Pleroma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.js.
+To translate Pleroma-FE, use our weblate server: https://translate.pleroma.social/. If you need to add your language it should be added as a json file in [src/i18n/](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/) folder and added in a list within [src/i18n/languages.js](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/languages.js).
-# FOR ADMINS
+Pleroma-FE will set your language by your browser locale, but you can change language in settings.
-You don't need to build Pleroma-FE yourself. Those using the Pleroma backend will be able to use it out of the box.
+# For instance admins
+You don't need to build Pleroma-FE yourself. Those using the Pleroma backend will be able to use it out of the box. Information of customizing PleromaFE settings/defaults is in our [guide](https://docs-develop.pleroma.social/frontend/CONFIGURATION/) and in case you want to build your own custom version there's [another](https://docs-develop.pleroma.social/frontend/HACKING/)
-## Build Setup
+# Build Setup
``` bash
# install dependencies
@@ -20,13 +21,13 @@ npm install -g yarn
yarn
# serve with hot reload at localhost:8080
-npm run dev
+yarn dev
# build for production with minification
-npm run build
+yarn build
# run unit tests
-npm run unit
+yarn unit
```
# For Contributors:
@@ -40,10 +41,4 @@ FE Build process also leaves current commit hash in global variable `___pleromaf
# Configuration
-Edit config.json for configuration.
-
-## Options
-
-### Login methods
-
-```loginMethod``` can be set to either ```password``` (the default) or ```token```, which will use the full oauth redirection flow, which is useful for SSO situations.
+Set configuration settings in AdminFE, additionally you can edit config.json. For more details see [documentation](https://docs-develop.pleroma.social/frontend/CONFIGURATION/).
diff --git a/build/build.js b/build/build.js
@@ -18,6 +18,9 @@ console.log(
var spinner = ora('building for production...')
spinner.start()
+var updateEmoji = require('./update-emoji').updateEmoji
+updateEmoji()
+
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
rm('-rf', assetsPath)
mkdir('-p', assetsPath)
@@ -33,4 +36,8 @@ webpack(webpackConfig, function (err, stats) {
chunks: false,
chunkModules: false
}) + '\n')
+ if (stats.hasErrors()) {
+ console.error('See above for errors.')
+ process.exit(1)
+ }
})
diff --git a/build/dev-server.js b/build/dev-server.js
@@ -10,6 +10,9 @@ var webpackConfig = process.env.NODE_ENV === 'testing'
? require('./webpack.prod.conf')
: require('./webpack.dev.conf')
+var updateEmoji = require('./update-emoji').updateEmoji
+updateEmoji()
+
// default port where dev server listens for incoming traffic
var port = process.env.PORT || config.dev.port
// Define HTTP proxies to your custom API backend
@@ -29,18 +32,20 @@ var devMiddleware = require('webpack-dev-middleware')(compiler, {
})
var hotMiddleware = require('webpack-hot-middleware')(compiler)
+
+// FIXME: The statement below gives error about hooks being required in webpack 5.
// force page reload when html-webpack-plugin template changes
-compiler.plugin('compilation', function (compilation) {
- compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
- // FIXME: This supposed to reload whole page when index.html is changed,
- // however now it reloads entire page on every breath, i suppose the order
- // of plugins changed or something. It's a minor thing and douesn't hurt
- // disabling it, constant reloads hurt much more
+// compiler.plugin('compilation', function (compilation) {
+// compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
+// // FIXME: This supposed to reload whole page when index.html is changed,
+// // however now it reloads entire page on every breath, i suppose the order
+// // of plugins changed or something. It's a minor thing and douesn't hurt
+// // disabling it, constant reloads hurt much more
- // hotMiddleware.publish({ action: 'reload' })
- // cb()
- })
-})
+// // hotMiddleware.publish({ action: 'reload' })
+// // cb()
+// })
+// })
// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
@@ -48,7 +53,7 @@ Object.keys(proxyTable).forEach(function (context) {
if (typeof options === 'string') {
options = { target: options }
}
- app.use(proxyMiddleware(context, options))
+ app.use(proxyMiddleware.createProxyMiddleware(context, options))
})
// handle fallback for HTML5 history API
diff --git a/build/update-emoji.js b/build/update-emoji.js
@@ -0,0 +1,27 @@
+
+module.exports = {
+ updateEmoji () {
+ const emojis = require('@kazvmoe-infra/unicode-emoji-json/data-by-group')
+ const fs = require('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
+ })
+ })
+
+ const res = {}
+ Object.keys(emojis)
+ .map(k => {
+ const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase()
+ res[groupId] = emojis[k]
+ })
+
+ console.info('Updating emojis...')
+ fs.writeFileSync('static/emoji.json', JSON.stringify(res))
+ console.info('Done.')
+ }
+}
diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js
@@ -2,9 +2,11 @@ var path = require('path')
var config = require('../config')
var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../')
-var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin')
+var ServiceWorkerWebpackPlugin = require('serviceworker-webpack5-plugin')
var CopyPlugin = require('copy-webpack-plugin');
var { VueLoaderPlugin } = require('vue-loader')
+var ESLintPlugin = require('eslint-webpack-plugin');
+var StylelintPlugin = require('stylelint-webpack-plugin');
var env = process.env.NODE_ENV
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
@@ -22,7 +24,8 @@ module.exports = {
output: {
path: config.build.assetsRoot,
publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
- filename: '[name].js'
+ filename: '[name].js',
+ chunkFilename: '[name].js'
},
optimization: {
splitChunks: {
@@ -40,26 +43,16 @@ module.exports = {
'assets': path.resolve(__dirname, '../src/assets'),
'components': path.resolve(__dirname, '../src/components'),
'vue-i18n': 'vue-i18n/dist/vue-i18n.runtime.esm-bundler.js'
+ },
+ fallback: {
+ 'querystring': require.resolve('querystring-es3'),
+ 'url': require.resolve('url/')
}
},
module: {
noParse: /node_modules\/localforage\/dist\/localforage.js/,
rules: [
{
- enforce: 'pre',
- test: /\.(js|vue)$/,
- include: projectRoot,
- exclude: /node_modules/,
- use: {
- loader: 'eslint-loader',
- options: {
- formatter: require('eslint-friendly-formatter'),
- sourceMap: config.build.productionSourceMap,
- extract: true
- }
- }
- },
- {
enforce: 'post',
test: /\.(json5?|ya?ml)$/, // target json, json5, yaml and yml files
type: 'javascript/auto',
@@ -90,22 +83,16 @@ module.exports = {
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
- use: {
- loader: 'url-loader',
- options: {
- limit: 10000,
- name: utils.assetsPath('img/[name].[hash:7].[ext]')
- }
+ type: 'asset',
+ generator: {
+ filename: utils.assetsPath('img/[name].[hash:7][ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
- use: {
- loader: 'url-loader',
- options: {
- limit: 10000,
- name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
- }
+ type: 'asset',
+ generator: {
+ filename: utils.assetsPath('fonts/[name].[hash:7][ext]')
}
},
{
@@ -120,14 +107,18 @@ module.exports = {
entry: path.join(__dirname, '..', 'src/sw.js'),
filename: 'sw-pleroma.js'
}),
+ new ESLintPlugin({
+ extensions: ['js', 'vue'],
+ formatter: require('eslint-formatter-friendly')
+ }),
+ new StylelintPlugin({}),
new VueLoaderPlugin(),
// This copies Ruffle's WASM to a directory so that JS side can access it
new CopyPlugin({
patterns: [
{
- from: "node_modules/@ruffle-rs/ruffle/*",
- to: "static/ruffle",
- flatten: true
+ from: "node_modules/@ruffle-rs/ruffle/**/*",
+ to: "static/ruffle/[name][ext]"
},
],
options: {
diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js
@@ -16,7 +16,7 @@ module.exports = merge(baseWebpackConfig, {
},
mode: 'development',
// eval-source-map is faster for development
- devtool: '#eval-source-map',
+ devtool: 'eval-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': config.dev.env,
diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js
@@ -5,26 +5,38 @@ var webpack = require('webpack')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var MiniCssExtractPlugin = require('mini-css-extract-plugin')
+const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
var HtmlWebpackPlugin = require('html-webpack-plugin')
var env = process.env.NODE_ENV === 'testing'
? require('../config/test.env')
: config.build.env
-let commitHash = require('child_process')
- .execSync('git rev-parse --short HEAD')
- .toString();
+let commitHash = (() => {
+ const subst = "$Format:%h$";
+ if(!subst.match(/Format:/)) {
+ return subst;
+ } else {
+ return require('child_process')
+ .execSync('git rev-parse --short HEAD')
+ .toString();
+ }
+})();
var webpackConfig = merge(baseWebpackConfig, {
mode: 'production',
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, extract: true })
},
- devtool: config.build.productionSourceMap ? '#source-map' : false,
+ devtool: config.build.productionSourceMap ? 'source-map' : false,
optimization: {
minimize: true,
splitChunks: {
chunks: 'all'
- }
+ },
+ minimizer: [
+ `...`,
+ new CssMinimizerPlugin()
+ ]
},
output: {
path: config.build.assetsRoot,
@@ -60,9 +72,7 @@ var webpackConfig = merge(baseWebpackConfig, {
ignoreCustomComments: [/server-generated-meta/]
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
- },
- // necessary to consistently work with multiple chunks via CommonsChunkPlugin
- chunksSortMode: 'dependency'
+ }
}),
// split vendor js into its own file
// extract webpack runtime and module manifest to its own file in order to
diff --git a/changelog.d/backend-repo-url.skip b/changelog.d/backend-repo-url.skip
diff --git a/changelog.d/better-shadow-control.fix b/changelog.d/better-shadow-control.fix
@@ -0,0 +1 @@
+Updated shadow editor, hopefully fixed long-standing bugs, added ability to specify shadow's name.
diff --git a/changelog.d/bookmark-folders.add b/changelog.d/bookmark-folders.add
@@ -0,0 +1 @@
+Support bookmark folders
diff --git a/changelog.d/browsers-support.change b/changelog.d/browsers-support.change
@@ -0,0 +1,9 @@
+Updated our build system to support browsers:
+ Safari >= 15
+ Firefox >= 115
+ Android > 4
+ no Opera Mini support
+ no IE support
+ no "dead" (unmaintained) browsers support
+
+This does not guarantee that browsers will or will not work.
diff --git a/changelog.d/checkbox.fix b/changelog.d/checkbox.fix
@@ -0,0 +1 @@
+checkbox vertical alignment has been fixed
diff --git a/changelog.d/colorfuncs.fix b/changelog.d/colorfuncs.fix
@@ -0,0 +1 @@
+Fix some of the color manipulation functions
diff --git a/changelog.d/custom.add b/changelog.d/custom.add
@@ -0,0 +1 @@
+Added support for fetching /{resource}.custom.ext to allow adding instance-specific themes without altering sourcetree
diff --git a/changelog.d/date-absolute.add b/changelog.d/date-absolute.add
@@ -0,0 +1 @@
+Support displaying time in absolute format
diff --git a/changelog.d/deprecate-subscribe.change b/changelog.d/deprecate-subscribe.change
@@ -0,0 +1 @@
+Use /api/v1/accounts/:id/follow for account subscriptions instead of the deprecated routes
+\ No newline at end of file
diff --git a/changelog.d/emoji-size.fix b/changelog.d/emoji-size.fix
@@ -0,0 +1 @@
+fix emoji inconsistencies in notifications, fix some emoji not scaling with interface
diff --git a/changelog.d/misc-markup.fix b/changelog.d/misc-markup.fix
@@ -0,0 +1 @@
+Fix small markup inconsistencies
diff --git a/changelog.d/multiple-status-mute-reasons.fix b/changelog.d/multiple-status-mute-reasons.fix
@@ -0,0 +1 @@
+Fix whitespaces for multiple status mute reasons, display bot status reason
diff --git a/changelog.d/non-anonymous-polls.add b/changelog.d/non-anonymous-polls.add
@@ -0,0 +1 @@
+Inform users that Smithereen public polls are public
+\ No newline at end of file
diff --git a/changelog.d/oauth-app-name.change b/changelog.d/oauth-app-name.change
@@ -0,0 +1 @@
+Simplify the OAuth client_name to 'PleromaFE'
diff --git a/changelog.d/panel-stack.fix b/changelog.d/panel-stack.fix
@@ -0,0 +1 @@
+proper sticky header for conversations on user page
diff --git a/changelog.d/piss-fix.skip b/changelog.d/piss-fix.skip
diff --git a/changelog.d/piss-serialization.skip b/changelog.d/piss-serialization.skip
diff --git a/changelog.d/quote-buttons.fix b/changelog.d/quote-buttons.fix
@@ -0,0 +1 @@
+reply-or-quote buttons now take less space
diff --git a/changelog.d/show-bookmarks-on-mobile.fix b/changelog.d/show-bookmarks-on-mobile.fix
@@ -0,0 +1 @@
+Bookmarks visible again on mobile
diff --git a/changelog.d/splashfix.skip b/changelog.d/splashfix.skip
diff --git a/changelog.d/splashscreen.add b/changelog.d/splashscreen.add
@@ -0,0 +1 @@
+Splash screen + loading indicator to make process of identifying initialization issues and load performance
diff --git a/changelog.d/tabs.change b/changelog.d/tabs.change
@@ -0,0 +1 @@
+Tabs now have indentation for better visibility of which tab is currently active
diff --git a/changelog.d/themes3.add b/changelog.d/themes3.add
@@ -0,0 +1 @@
+UI for making v3 themes and palettes, support for bundling v3 themes
diff --git a/changelog.d/user-link.add b/changelog.d/user-link.add
@@ -0,0 +1 @@
+Make UserLink wrappable
diff --git a/changelog.d/weird-absolute-time-format.fix b/changelog.d/weird-absolute-time-format.fix
@@ -0,0 +1 @@
+Show only month and day instead of weird "day, hour" format. While at it, fixed typo "defualt" in a comment.
+\ No newline at end of file
diff --git a/docs/HACKING.md b/docs/HACKING.md
@@ -25,7 +25,17 @@ This could be a bit trickier, you basically need steps 1-4 from *develop build*
### Replacing your instance's frontend with custom FE build
-This is the most easiest way to use and test FE build: you just need to copy or symlink contents of `dist` folder into backend's [static directory](../backend/configuration/static_dir.md), by default it is located in `instance/static`, or in `/var/lib/pleroma/static` for OTP release installations, create it if it doesn't exist already. Be aware that running `yarn build` wipes the contents of `dist` folder.
+#### New way (via AdminFE, a bit janky but works)
+
+In backend's [static directory](../backend/configuration/static_dir.md) there should be a folder called `frontends` if you installed any frontends from AdminFE before, otherwise you can create it yourself (ensuring correct permissions). Backend will serve given frontend from path `frontends/{frontend}/{reference}`, where `{frontend}` is name of frontend (`pleroma-fe`) and `{reference}` is version. You could make a production build, move `dist` folder into `frontends/pleroma-fe` and rename it into something like `myCustomVersion`. To actually make backend serve this frontend by default, in AdminFE you'll need to set name/reference in Settings -> Frontend -> Frontends -> Primary.
+
+You could also install from a zip file (i.e. CI build) but AdminFE UI is a bit buggy and lacking, so this approach is not recommended.
+
+Take note that frontend management is in early development and currently there's no way for user to change frontend or version for themselves, primary frontend becomes default frontend for all users and visitors.
+
+#### Old way (replaces everything, hard to maintain, not recommended)
+
+Copy or symlink contents of `dist` folder into backend's [static directory](../backend/configuration/static_dir.md), by default it is located in `instance/static`, or in `/var/lib/pleroma/static` for OTP release installations, create it if it doesn't exist already. Be aware that running `yarn build` wipes the contents of `dist` folder, and this could remove emojis, other frontends etc. and therefore this approach is not recommended.
### Running production build locally or on a separate server
diff --git a/image-1.png b/image-1.png
Binary files differ.
diff --git a/image.png b/image.png
Binary files differ.
diff --git a/index.html b/index.html
@@ -3,12 +3,165 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
+ <!-- putting styles here to avoid having to wait for styles to load up -->
+ <style id="splashscreen">
+ #splash {
+ --scale: 1;
+ width: 100vw;
+ height: 100vh;
+ display: grid;
+ grid-template-rows: auto;
+ grid-template-columns: auto;
+ align-content: center;
+ align-items: center;
+ justify-content: center;
+ justify-items: center;
+ flex-direction: column;
+ background: #0f161e;
+ font-family: sans-serif;
+ color: #b9b9ba;
+ position: absolute;
+ z-index: 9999;
+ font-size: calc(1vw + 1vh + 1vmin);
+ }
+
+ #splash-credit {
+ position: absolute;
+ font-size: 14px;
+ bottom: 16px;
+ right: 16px;
+ }
+
+ #splash-container {
+ align-items: center;
+ }
+
+ #mascot-container {
+ display: flex;
+ align-items: flex-end;
+ justify-content: center;
+ perspective: 60em;
+ perspective-origin: 0 -15em;
+ transform-style: preserve-3d;
+ }
+
+ #mascot {
+ width: calc(10em * var(--scale));
+ height: calc(10em * var(--scale));
+ object-fit: contain;
+ object-position: bottom;
+ transform: translateZ(-2em);
+ }
+
+ #throbber {
+ display: grid;
+ width: calc(5em * 0.5 * var(--scale));
+ height: calc(8em * 0.5 * var(--scale));
+ margin-left: 4.1em;
+ z-index: 2;
+ grid-template-rows: repeat(8, 1fr);
+ grid-template-columns: repeat(5, 1fr);
+ grid-template-areas: "P P . L L"
+ "P P . L L"
+ "P P . L L"
+ "P P . L L"
+ "P P . . ."
+ "P P . . ."
+ "P P . E E"
+ "P P . E E";
+
+ --logoChunkSize: calc(2em * 0.5 * var(--scale))
+ }
+
+ .chunk {
+ background-color: #e2b188;
+ box-shadow: 0.01em 0.01em 0.1em 0 #e2b188;
+ }
+
+ #chunk-P {
+ grid-area: P;
+ border-top-left-radius: calc(var(--logoChunkSize) / 2);
+ }
+
+ #chunk-L {
+ grid-area: L;
+ border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
+ }
+
+ #chunk-E {
+ grid-area: E;
+ border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
+ }
+
+ #status {
+ margin-top: 1em;
+ line-height: 2;
+ width: 100%;
+ text-align: center;
+ }
+
+ #statusError {
+ display: none;
+ margin-top: 1em;
+ font-size: calc(1vw + 1vh + 1vmin);
+ line-height: 2;
+ width: 100%;
+ text-align: center;
+ }
+
+ #statusStack {
+ display: none;
+ margin-top: 1em;
+ font-size: calc((1vw + 1vh + 1vmin) / 2.5);
+ width: calc(100vw - 5em);
+ padding: 1em;
+ text-overflow: ellipsis;
+ overflow-x: hidden;
+ text-align: left;
+ line-height: 2;
+ }
+
+ @media (prefers-reduced-motion) {
+ #throbber {
+ animation: none !important;
+ }
+ }
+ </style>
+ <style id="pleroma-eager-styles" type="text/css"></style>
+ <style id="pleroma-lazy-styles" type="text/css"></style>
<!--server-generated-meta-->
- <link rel="icon" type="image/png" href="/favicon.png">
</head>
- <body class="hidden">
+ <body style="margin: 0; padding: 0">
<noscript>To use Pleroma, please enable JavaScript.</noscript>
- <div id="app"></div>
+ <div id="splash">
+ <!-- we are hiding entire graphic so no point showing credit -->
+ <div aria-hidden="true" id="splash-credit">
+ Art by pipivovott
+ </div>
+ <div id="splash-container">
+ <div aria-hidden="true" id="mascot-container">
+ <div id="throbber">
+ <div class="chunk" id="chunk-P">
+ </div>
+ <div class="chunk" id="chunk-L">
+ </div>
+ <div class="chunk" id="chunk-E">
+ </div>
+ </div>
+ <img id="mascot" src="/static/pleromatan_apology.png">
+ </div>
+ <div id="status" class="css-ok">
+ <!-- (。>﹏<) -->
+ <!-- it's a pseudographic, don't want screenreader read out nonsense -->
+ <span aria-hidden="true" class="initial-text">(。>﹏<)</span>
+ </div>
+ <code id="statusError"></code>
+ <pre id="statusStack"></pre>
+ </div>
+ </div>
+ <div id="app" class="hidden"></div>
+ <div id="modal"></div>
<!-- built files will be auto injected -->
+ <div id="popovers" />
</body>
</html>
diff --git a/package.json b/package.json
@@ -1,9 +1,9 @@
{
"name": "pleroma_fe",
- "version": "1.0.0",
- "description": "A Qvitter-style frontend for certain GS servers.",
- "author": "Roger Braun <roger@rogerbraun.net>",
- "private": true,
+ "version": "2.7.1",
+ "description": "Pleroma frontend, the default frontend of Pleroma social network server",
+ "author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/CONTRIBUTORS.md>",
+ "private": false,
"scripts": {
"dev": "node build/dev-server.js",
"build": "node build/build.js",
@@ -11,121 +11,128 @@
"unit:watch": "karma start test/unit/karma.conf.js --single-run=false",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
- "stylelint": "npx stylelint src/components/status/status.scss",
+ "stylelint": "npx stylelint '**/*.scss' '**/*.vue'",
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
},
"dependencies": {
- "@babel/runtime": "7.18.9",
+ "@babel/runtime": "7.21.5",
"@chenfengyuan/vue-qrcode": "2.0.0",
- "@fortawesome/fontawesome-svg-core": "6.1.1",
- "@fortawesome/free-regular-svg-icons": "6.1.1",
- "@fortawesome/free-solid-svg-icons": "6.1.1",
- "@fortawesome/vue-fontawesome": "3.0.1",
+ "@fortawesome/fontawesome-svg-core": "6.4.0",
+ "@fortawesome/free-regular-svg-icons": "6.4.0",
+ "@fortawesome/free-solid-svg-icons": "6.4.0",
+ "@fortawesome/vue-fontawesome": "3.0.3",
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
- "@ruffle-rs/ruffle": "^0.1.0-nightly.2022.7.12",
- "@vuelidate/core": "2.0.0-alpha.43",
- "@vuelidate/validators": "2.0.0-alpha.31",
- "body-scroll-lock": "2.7.1",
+ "@kazvmoe-infra/unicode-emoji-json": "0.4.0",
+ "@ruffle-rs/ruffle": "0.1.0-nightly.2024.8.21",
+ "@vuelidate/core": "2.0.3",
+ "@vuelidate/validators": "2.0.4",
+ "body-scroll-lock": "3.1.5",
"chromatism": "3.0.0",
"click-outside-vue3": "4.0.1",
- "cropperjs": "1.5.12",
- "diff": "3.5.0",
+ "cropperjs": "1.5.13",
"escape-html": "1.0.3",
- "js-cookie": "^3.0.1",
+ "hash-sum": "^2.0.0",
+ "js-cookie": "3.0.5",
"localforage": "1.10.0",
- "parse-link-header": "1.0.1",
- "phoenix": "1.6.2",
- "punycode.js": "2.1.0",
- "qrcode": "1",
- "utf8": "^3.0.0",
- "vue": "^3.2.31",
- "vue-i18n": "9.2.0-beta.40",
- "vue-router": "4.1.2",
- "vue-template-compiler": "2.6.11",
- "vuex": "4.0.2"
+ "pako": "^2.1.0",
+ "parse-link-header": "2.0.0",
+ "phoenix": "1.7.7",
+ "punycode.js": "2.3.0",
+ "qrcode": "1.5.3",
+ "querystring-es3": "0.2.1",
+ "url": "0.11.0",
+ "utf8": "3.0.0",
+ "vue": "3.2.45",
+ "vue-i18n": "9.2.2",
+ "vue-router": "4.1.6",
+ "vue-template-compiler": "2.7.14",
+ "vue-virtual-scroller": "^2.0.0-beta.7",
+ "vuex": "4.1.0"
},
"devDependencies": {
- "@babel/core": "7.18.9",
- "@babel/plugin-transform-runtime": "7.18.9",
- "@babel/preset-env": "7.18.9",
- "@babel/register": "7.18.9",
- "@intlify/vue-i18n-loader": "^5.0.0",
- "@ungap/event-target": "0.2.3",
- "@vue/babel-helper-vue-jsx-merge-props": "1.2.1",
- "@vue/babel-plugin-jsx": "1.1.1",
- "@vue/compiler-sfc": "^3.1.0",
- "@vue/test-utils": "2.0.2",
- "autoprefixer": "6.7.7",
- "babel-eslint": "7.2.3",
- "babel-loader": "8.2.5",
+ "@babel/core": "7.21.8",
+ "@babel/eslint-parser": "7.21.8",
+ "@babel/plugin-transform-runtime": "7.21.4",
+ "@babel/preset-env": "7.21.5",
+ "@babel/register": "7.21.0",
+ "@intlify/vue-i18n-loader": "5.0.1",
+ "@ungap/event-target": "0.2.4",
+ "@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
+ "@vue/babel-plugin-jsx": "1.2.2",
+ "@vue/compiler-sfc": "3.2.45",
+ "@vue/test-utils": "2.2.8",
+ "autoprefixer": "10.4.19",
+ "babel-loader": "9.1.3",
"babel-plugin-lodash": "3.3.4",
- "chai": "3.5.0",
+ "chai": "4.3.7",
"chalk": "1.1.3",
- "chromedriver": "87.0.7",
- "connect-history-api-fallback": "1.6.0",
- "copy-webpack-plugin": "6.4.1",
- "cross-spawn": "4.0.2",
- "css-loader": "0.28.11",
+ "chromedriver": "108.0.0",
+ "connect-history-api-fallback": "2.0.0",
+ "copy-webpack-plugin": "11.0.0",
+ "cross-spawn": "7.0.3",
+ "css-loader": "6.10.0",
+ "css-minimizer-webpack-plugin": "4.2.2",
"custom-event-polyfill": "1.0.7",
- "eslint": "5.16.0",
- "eslint-config-standard": "12.0.0",
- "eslint-friendly-formatter": "2.0.7",
- "eslint-loader": "2.2.1",
- "eslint-plugin-import": "2.26.0",
- "eslint-plugin-node": "7.0.1",
- "eslint-plugin-promise": "4.3.1",
- "eslint-plugin-standard": "4.1.0",
- "eslint-plugin-vue": "5.2.3",
+ "eslint": "8.33.0",
+ "eslint-config-standard": "17.0.0",
+ "eslint-formatter-friendly": "7.0.0",
+ "eslint-plugin-import": "2.27.5",
+ "eslint-plugin-n": "15.6.1",
+ "eslint-plugin-promise": "6.1.1",
+ "eslint-plugin-vue": "9.9.0",
+ "eslint-webpack-plugin": "3.2.0",
"eventsource-polyfill": "0.9.6",
- "express": "4.18.1",
- "file-loader": "3.0.1",
+ "express": "4.18.2",
"function-bind": "1.1.1",
- "html-webpack-plugin": "3.2.0",
- "http-proxy-middleware": "0.21.0",
- "inject-loader": "2.0.1",
+ "html-webpack-plugin": "5.5.1",
+ "http-proxy-middleware": "2.0.6",
"iso-639-1": "2.1.15",
- "isparta-loader": "2.0.0",
"json-loader": "0.5.7",
- "karma": "6.4.0",
- "karma-coverage": "1.1.2",
- "karma-firefox-launcher": "1.3.0",
+ "karma": "6.4.4",
+ "karma-coverage": "2.2.0",
+ "karma-firefox-launcher": "2.1.3",
"karma-mocha": "2.0.1",
"karma-mocha-reporter": "2.2.5",
"karma-sinon-chai": "2.0.2",
"karma-sourcemap-loader": "0.3.8",
- "karma-spec-reporter": "0.0.34",
- "karma-webpack": "4.0.2",
+ "karma-spec-reporter": "0.0.36",
+ "karma-webpack": "5.0.0",
"lodash": "4.17.21",
- "lolex": "1.6.0",
- "mini-css-extract-plugin": "0.12.0",
- "mocha": "3.5.3",
- "nightwatch": "0.9.21",
- "opn": "4.0.2",
+ "mini-css-extract-plugin": "2.7.6",
+ "mocha": "10.2.0",
+ "nightwatch": "2.6.25",
+ "opn": "5.5.0",
"ora": "0.4.1",
- "postcss-loader": "3.0.0",
- "raw-loader": "0.5.1",
- "sass": "1.54.0",
- "sass-loader": "7.3.1",
+ "postcss": "8.4.23",
+ "postcss-html": "^1.5.0",
+ "postcss-loader": "7.0.2",
+ "postcss-scss": "^4.0.6",
+ "sass": "1.60.0",
+ "sass-loader": "13.2.2",
"selenium-server": "2.53.1",
- "semver": "5.7.1",
- "serviceworker-webpack-plugin": "1.0.1",
+ "semver": "7.3.8",
+ "serviceworker-webpack5-plugin": "2.0.0",
"shelljs": "0.8.5",
- "sinon": "2.4.1",
- "sinon-chai": "2.14.0",
- "stylelint": "13.13.1",
- "stylelint-config-standard": "20.0.0",
+ "sinon": "15.0.4",
+ "sinon-chai": "3.7.0",
+ "stylelint": "14.16.1",
+ "stylelint-config-html": "^1.1.0",
+ "stylelint-config-recommended-scss": "^8.0.0",
+ "stylelint-config-recommended-vue": "^1.4.0",
+ "stylelint-config-standard": "29.0.0",
"stylelint-rscss": "0.4.0",
- "url-loader": "1.1.2",
- "vue-loader": "^16.0.0",
- "vue-style-loader": "4.1.2",
- "webpack": "4.46.0",
+ "stylelint-webpack-plugin": "^3.3.0",
+ "vue-loader": "17.0.1",
+ "vue-style-loader": "4.1.3",
+ "webpack": "5.75.0",
"webpack-dev-middleware": "3.7.3",
- "webpack-hot-middleware": "2.25.1",
+ "webpack-hot-middleware": "2.25.3",
"webpack-merge": "0.20.0"
},
"engines": {
- "node": ">= 4.0.0",
+ "node": ">= 16.0.0",
"npm": ">= 3.0.0"
- }
+ },
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
diff --git a/preview.style.js b/preview.style.js
diff --git a/src/App.js b/src/App.js
@@ -4,14 +4,15 @@ import InstanceSpecificPanel from './components/instance_specific_panel/instance
import FeaturesPanel from './components/features_panel/features_panel.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
import ShoutPanel from './components/shout_panel/shout_panel.vue'
-import SettingsModal from './components/settings_modal/settings_modal.vue'
import MediaModal from './components/media_modal/media_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue'
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
import MobileNav from './components/mobile_nav/mobile_nav.vue'
import DesktopNav from './components/desktop_nav/desktop_nav.vue'
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
+import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue'
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
+import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
import { mapGetters } from 'vuex'
@@ -32,24 +33,43 @@ export default {
MobilePostStatusButton,
MobileNav,
DesktopNav,
- SettingsModal,
+ SettingsModal: defineAsyncComponent(() => import('./components/settings_modal/settings_modal.vue')),
+ UpdateNotification: defineAsyncComponent(() => import('./components/update_notification/update_notification.vue')),
UserReportingModal,
PostStatusModal,
+ EditStatusModal,
+ StatusHistoryModal,
GlobalNoticeList
},
data: () => ({
mobileActivePanel: 'timeline'
}),
+ watch: {
+ themeApplied (value) {
+ this.removeSplash()
+ }
+ },
created () {
// Load the locale from the storage
const val = this.$store.getters.mergedConfig.interfaceLanguage
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
window.addEventListener('resize', this.updateMobileState)
},
+ mounted () {
+ if (this.$store.state.interface.themeApplied) {
+ this.removeSplash()
+ }
+ },
unmounted () {
window.removeEventListener('resize', this.updateMobileState)
},
computed: {
+ themeApplied () {
+ return this.$store.state.interface.themeApplied
+ },
+ layoutModalClass () {
+ return '-' + this.layoutType
+ },
classes () {
return [
{
@@ -60,6 +80,13 @@ export default {
'-' + this.layoutType
]
},
+ navClasses () {
+ const { navbarColumnStretch } = this.$store.getters.mergedConfig
+ return [
+ '-' + this.layoutType,
+ ...(navbarColumnStretch ? ['-column-stretch'] : [])
+ ]
+ },
currentUser () { return this.$store.state.users.currentUser },
userBackground () { return this.currentUser.background_image },
instanceBackground () {
@@ -85,11 +112,16 @@ export default {
isChats () {
return this.$route.name === 'chat' || this.$route.name === 'chats'
},
+ isListEdit () {
+ return this.$route.name === 'lists-edit'
+ },
newPostButtonShown () {
if (this.isChats) return false
+ if (this.isListEdit) return false
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
},
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
+ editingAvailable () { return this.$store.state.instance.editingAvailable },
shoutboxPosition () {
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false
},
@@ -114,6 +146,15 @@ export default {
updateMobileState () {
this.$store.dispatch('setLayoutWidth', windowWidth())
this.$store.dispatch('setLayoutHeight', windowHeight())
+ },
+ removeSplash () {
+ document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4))
+ const splashscreenRoot = document.querySelector('#splash')
+ splashscreenRoot.addEventListener('transitionend', () => {
+ splashscreenRoot.remove()
+ })
+ splashscreenRoot.classList.add('hidden')
+ document.querySelector('#app').classList.remove('hidden')
}
}
}
diff --git a/src/App.scss b/src/App.scss
@@ -1,22 +1,36 @@
// stylelint-disable rscss/class-format
-@import './_variables.scss';
+/* stylelint-disable no-descending-specificity */
+@import "./panel";
:root {
- --navbar-height: 3.5rem;
+ --status-margin: 0.75em;
--post-line-height: 1.4;
+ // Z-Index stuff
+ --ZI_media_modal: 9000;
+ --ZI_modals_popovers: 8500;
+ --ZI_modals: 8000;
+ --ZI_navbar_popovers: 7500;
+ --ZI_navbar: 7000;
+ --ZI_popovers: 6000;
+
+ // Fallback for when stuff is loading
+ --background: var(--bg);
}
html {
- font-size: 14px;
+ font-size: var(--textSize, 14px);
+
+ --navbar-height: var(--navbarSize, 3.5rem);
+ --emoji-size: var(--emojiSize, 32px);
+ --panel-header-height: var(--panelHeaderSize, 3.2rem);
// overflow-x: clip causes my browser's tab to crash with SIGILL lul
}
body {
font-family: sans-serif;
- font-family: var(--interfaceFont, sans-serif);
+ font-family: var(--font);
margin: 0;
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overscroll-behavior-y: none;
@@ -33,17 +47,35 @@ body {
// have a cursor/pointer to operate them
@media (any-pointer: fine) {
* {
- scrollbar-color: var(--btn) transparent;
+ scrollbar-color: var(--fg) transparent;
&::-webkit-scrollbar {
background: transparent;
}
+ &::-webkit-scrollbar-corner {
+ background: transparent;
+ }
+
+ &::-webkit-resizer {
+ /* stylelint-disable-next-line declaration-no-important */
+ background-color: transparent !important;
+ background-image:
+ linear-gradient(
+ 135deg,
+ transparent calc(50% - 1px),
+ var(--textFaint) 50%,
+ transparent calc(50% + 1px),
+ transparent calc(75% - 1px),
+ var(--textFaint) 75%,
+ transparent calc(75% + 1px),
+ );
+ }
+
&::-webkit-scrollbar-button,
&::-webkit-scrollbar-thumb {
- background-color: var(--btn);
- box-shadow: var(--buttonShadow);
- border-radius: var(--btnRadius);
+ box-shadow: var(--shadow);
+ border-radius: var(--roundness);
}
// horizontal/vertical/increment/decrement are webkit-specific stuff
@@ -52,7 +84,7 @@ body {
&::-webkit-scrollbar-button {
--___bgPadding: 2px;
- color: var(--btnText);
+ color: var(--text);
background-repeat: no-repeat, no-repeat;
&:horizontal {
@@ -60,15 +92,15 @@ body {
&:increment {
background-image:
- linear-gradient(45deg, var(--btnText) 50%, transparent 51%),
- linear-gradient(-45deg, transparent 50%, var(--btnText) 51%);
+ linear-gradient(45deg, var(--text) 50%, transparent 51%),
+ linear-gradient(-45deg, transparent 50%, var(--text) 51%);
background-position: top var(--___bgPadding) left 50%, right 50% bottom var(--___bgPadding);
}
&:decrement {
background-image:
- linear-gradient(45deg, transparent 50%, var(--btnText) 51%),
- linear-gradient(-45deg, var(--btnText) 50%, transparent 51%);
+ linear-gradient(45deg, transparent 50%, var(--text) calc(50% + 1px)),
+ linear-gradient(-45deg, var(--text) 50%, transparent 51%);
background-position: bottom var(--___bgPadding) right 50%, left 50% top var(--___bgPadding);
}
}
@@ -78,15 +110,15 @@ body {
&:increment {
background-image:
- linear-gradient(-45deg, transparent 50%, var(--btnText) 51%),
- linear-gradient(45deg, transparent 50%, var(--btnText) 51%);
+ linear-gradient(-45deg, transparent 50%, var(--text) 51%),
+ linear-gradient(45deg, transparent 50%, var(--text) 51%);
background-position: right var(--___bgPadding) top 50%, left var(--___bgPadding) top 50%;
}
&:decrement {
background-image:
- linear-gradient(-45deg, var(--btnText) 50%, transparent 51%),
- linear-gradient(45deg, var(--btnText) 50%, transparent 51%);
+ linear-gradient(-45deg, var(--text) 50%, transparent 51%),
+ linear-gradient(45deg, var(--text) 50%, transparent 51%);
background-position: left var(--___bgPadding) top 50%, right var(--___bgPadding) top 50%;
}
}
@@ -95,38 +127,38 @@ body {
}
// Body should have background to scrollbar otherwise it will use white (body color?)
html {
- scrollbar-color: var(--selectedMenu) var(--wallpaper);
+ scrollbar-color: var(--fg) var(--wallpaper);
background: var(--wallpaper);
}
}
a {
text-decoration: none;
- color: $fallback--link;
- color: var(--link, $fallback--link);
+ color: var(--link);
}
h4 {
margin: 0;
}
-i[class*=icon-],
-.svg-inline--fa {
- color: $fallback--icon;
- color: var(--icon, $fallback--icon);
+.iconLetter {
+ display: inline-block;
+ text-align: center;
+ font-weight: 1000;
+}
+
+i[class*="icon-"],
+.svg-inline--fa,
+.iconLetter {
+ color: var(--icon);
}
nav {
- z-index: 1000;
- color: var(--topBarText);
- background-color: $fallback--fg;
- background-color: var(--topBar, $fallback--fg);
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
- box-shadow: 0 0 4px rgba(0, 0, 0, 0.6);
- box-shadow: var(--topBarShadow);
+ z-index: var(--ZI_navbar);
+ box-shadow: var(--shadow);
box-sizing: border-box;
height: var(--navbar-height);
+ font-size: calc(var(--navbar-height) / 3.5);
position: fixed;
}
@@ -134,6 +166,11 @@ nav {
grid-area: sidebar;
}
+#modal {
+ position: absolute;
+ z-index: var(--ZI_modals);
+}
+
.column.-scrollable {
top: var(--navbar-height);
position: sticky;
@@ -163,25 +200,26 @@ nav {
}
.underlay {
- grid-column-start: 1;
- grid-column-end: span 3;
- grid-row-start: 1;
- grid-row-end: 1;
+ grid-column: 1 / span 3;
+ grid-row: 1 / 1;
pointer-events: none;
- background-color: rgba(0, 0, 0, 0.15);
- background-color: var(--underlay, rgba(0, 0, 0, 0.15));
+ background-color: var(--underlay);
z-index: -1000;
}
.app-layout {
--miniColumn: 25rem;
- --maxiColumn: minmax(var(--miniColumn), 45rem);
- --columnGap: 1em;
- --status-margin: 0.75em;
+ --maxiColumn: 45rem;
+ --columnGap: 1rem;
+ --effectiveSidebarColumnWidth: minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)));
+ --effectiveNotifsColumnWidth: minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn)));
+ --effectiveContentColumnWidth: minmax(var(--miniColumn), var(--contentColumnWidth, var(--maxiColumn)));
position: relative;
display: grid;
- grid-template-columns: var(--miniColumn) var(--maxiColumn);
+ grid-template-columns:
+ var(--effectiveSidebarColumnWidth)
+ var(--effectiveContentColumnWidth);
grid-template-areas: "sidebar content";
grid-template-rows: 1fr;
box-sizing: border-box;
@@ -198,8 +236,7 @@ nav {
display: grid;
grid-template-columns: 100%;
box-sizing: border-box;
- grid-row-start: 1;
- grid-row-end: 1;
+ grid-row: 1 / 1;
margin: 0 calc(var(--___columnMargin) / 2);
padding: calc(var(--___columnMargin)) 0;
row-gap: var(--___columnMargin);
@@ -274,16 +311,25 @@ nav {
align-content: start;
}
- &.-reverse:not(.-wide):not(.-mobile) {
- grid-template-columns: var(--maxiColumn) var(--miniColumn);
+ &.-reverse:not(.-wide, .-mobile) {
+ grid-template-columns:
+ var(--effectiveContentColumnWidth)
+ var(--effectiveSidebarColumnWidth);
grid-template-areas: "content sidebar";
}
&.-wide {
- grid-template-columns: var(--miniColumn) var(--maxiColumn) var(--miniColumn);
+ grid-template-columns:
+ var(--effectiveSidebarColumnWidth)
+ var(--effectiveContentColumnWidth)
+ var(--effectiveNotifsColumnWidth);
grid-template-areas: "sidebar content notifs";
&.-reverse {
+ grid-template-columns:
+ var(--effectiveNotifsColumnWidth)
+ var(--effectiveContentColumnWidth)
+ var(--effectiveSidebarColumnWidth);
grid-template-areas: "notifs content sidebar";
}
}
@@ -294,11 +340,8 @@ nav {
padding: 0;
.column {
- margin-left: 0;
- margin-right: 0;
padding-top: 0;
- margin-top: var(--navbar-height);
- margin-bottom: 0;
+ margin: var(--navbar-height) 0 0 0;
}
.panel-heading,
@@ -329,100 +372,112 @@ nav {
.button-default {
user-select: none;
- color: $fallback--text;
- color: var(--btnText, $fallback--text);
- background-color: $fallback--fg;
- background-color: var(--btn, $fallback--fg);
+ color: var(--text);
border: none;
- border-radius: $fallback--btnRadius;
- border-radius: var(--btnRadius, $fallback--btnRadius);
cursor: pointer;
- box-shadow: $fallback--buttonShadow;
- box-shadow: var(--buttonShadow);
+ background-color: var(--background);
+ box-shadow: var(--shadow);
font-size: 1em;
font-family: sans-serif;
- font-family: var(--interfaceFont, sans-serif);
+ font-family: var(--font);
- &.-sublime {
- background: transparent;
+ &::-moz-focus-inner {
+ border: none;
}
- i[class*=icon-],
- .svg-inline--fa {
- color: $fallback--text;
- color: var(--btnText, $fallback--text);
+ &:disabled {
+ cursor: not-allowed;
}
+}
- &::-moz-focus-inner {
- border: none;
+.menu-item,
+.list-item {
+ display: block;
+ box-sizing: border-box;
+ border: none;
+ outline: none;
+ text-align: initial;
+ font-size: inherit;
+ font-family: inherit;
+ font-weight: 400;
+ cursor: pointer;
+ color: inherit;
+ clear: both;
+ position: relative;
+ white-space: nowrap;
+ border-color: var(--border);
+ border-style: solid;
+ border-width: 0;
+ border-top-width: 1px;
+ width: 100%;
+ line-height: var(--__line-height);
+ padding: var(--__vertical-gap) var(--__horizontal-gap);
+ background: transparent;
+
+ --__line-height: 1.5em;
+ --__horizontal-gap: 0.75em;
+ --__vertical-gap: 0.5em;
+
+ &.-non-interactive {
+ cursor: auto;
}
+ &.-active,
&:hover {
- box-shadow: 0 0 4px rgba(255, 255, 255, 0.3);
- box-shadow: var(--buttonHoverShadow);
- }
-
- &:active {
- box-shadow: 0 0 4px 0 rgba(255, 255, 255, 0.3), 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset;
- box-shadow: var(--buttonPressedShadow);
- color: $fallback--text;
- color: var(--btnPressedText, $fallback--text);
- background-color: $fallback--fg;
- background-color: var(--btnPressed, $fallback--fg);
-
- svg,
- i {
- color: $fallback--text;
- color: var(--btnPressedText, $fallback--text);
- }
+ border-top-width: 1px;
+ border-bottom-width: 1px;
}
- &:disabled {
- cursor: not-allowed;
- color: $fallback--text;
- color: var(--btnDisabledText, $fallback--text);
- background-color: $fallback--fg;
- background-color: var(--btnDisabled, $fallback--fg);
-
- svg,
- i {
- color: $fallback--text;
- color: var(--btnDisabledText, $fallback--text);
- }
+ &.-active + &,
+ &:hover + & {
+ border-top-width: 0;
}
- &.toggled {
- color: $fallback--text;
- color: var(--btnToggledText, $fallback--text);
- background-color: $fallback--fg;
- background-color: var(--btnToggled, $fallback--fg);
- box-shadow: 0 0 4px 0 rgba(255, 255, 255, 0.3), 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset;
- box-shadow: var(--buttonPressedShadow);
+ &:hover + .menu-item-collapsible:not(.-expanded) + &,
+ &.-active + .menu-item-collapsible:not(.-expanded) + & {
+ border-top-width: 0;
+ }
- svg,
- i {
- color: $fallback--text;
- color: var(--btnToggledText, $fallback--text);
- }
+ &[aria-expanded="true"] {
+ border-bottom-width: 1px;
+ }
+
+ a,
+ button:not(.button-default) {
+ text-align: initial;
+ padding: 0;
+ background: none;
+ border: none;
+ outline: none;
+ display: inline;
+ font-size: 100%;
+ font-family: inherit;
+ line-height: unset;
+ color: var(--text);
}
- &.danger {
- // TODO: add better color variable
- color: $fallback--text;
- color: var(--alertErrorPanelText, $fallback--text);
- background-color: $fallback--alertError;
- background-color: var(--alertError, $fallback--alertError);
+ &:first-child {
+ border-top-right-radius: var(--roundness);
+ border-top-left-radius: var(--roundness);
+ border-top-width: 0;
+ }
+
+ &:last-child {
+ border-bottom-right-radius: var(--roundness);
+ border-bottom-left-radius: var(--roundness);
+ border-bottom-width: 0;
}
}
.button-unstyled {
- background: none;
border: none;
outline: none;
display: inline;
text-align: initial;
font-size: 100%;
font-family: inherit;
+ box-shadow: var(--shadow);
+ background-color: transparent;
padding: 0;
line-height: unset;
cursor: pointer;
@@ -430,28 +485,23 @@ nav {
color: inherit;
&.-link {
- color: $fallback--link;
- color: var(--link, $fallback--link);
- }
-
- &.-fullwidth {
- width: 100%;
- }
-
- &.-hover-highlight {
- &:hover svg {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
- }
+ /* stylelint-disable-next-line declaration-no-important */
+ color: var(--link) !important;
}
}
input,
-textarea,
+textarea {
+ border: none;
+ display: inline-block;
+ outline: none;
+}
+
.input {
&.unstyled {
border-radius: 0;
- background: none;
+ /* stylelint-disable-next-line declaration-no-important */
+ background: none !important;
box-shadow: none;
height: unset;
}
@@ -459,16 +509,10 @@ textarea,
--_padding: 0.5em;
border: none;
- border-radius: $fallback--inputRadius;
- border-radius: var(--inputRadius, $fallback--inputRadius);
- box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset, 0 0 2px 0 rgba(0, 0, 0, 1) inset;
- box-shadow: var(--inputShadow);
- background-color: $fallback--fg;
- background-color: var(--input, $fallback--fg);
- color: $fallback--lightText;
- color: var(--inputText, $fallback--lightText);
- font-family: sans-serif;
- font-family: var(--inputFont, sans-serif);
+ background-color: var(--background);
+ color: var(--text);
+ box-shadow: var(--shadow);
+ font-family: var(--font);
font-size: 1em;
margin: 0;
box-sizing: border-box;
@@ -479,13 +523,12 @@ textarea,
padding: 0 var(--_padding);
&:disabled,
- &[disabled=disabled],
+ &[disabled="disabled"],
&.disabled {
cursor: not-allowed;
- opacity: 0.5;
}
- &[type=range] {
+ &[type="range"] {
background: none;
border: none;
margin: 0;
@@ -493,13 +536,13 @@ textarea,
flex: 1;
}
- &[type=radio] {
+ &[type="radio"] {
display: none;
&:checked + label::before {
- box-shadow: 0 0 2px black inset, 0 0 0 4px $fallback--fg inset;
- box-shadow: var(--inputShadow), 0 0 0 4px var(--fg, $fallback--fg) inset;
- background-color: var(--accent, $fallback--link);
+ box-shadow: var(--shadow);
+ background-color: var(--background);
+ color: var(--text);
}
&:disabled {
@@ -513,16 +556,14 @@ textarea,
+ label::before {
flex-shrink: 0;
display: inline-block;
- content: '';
+ content: "•";
transition: box-shadow 200ms;
width: 1.1em;
height: 1.1em;
border-radius: 100%; // Radio buttons should always be circle
- box-shadow: 0 0 2px black inset;
- box-shadow: var(--inputShadow);
+ background-color: var(--background);
+ box-shadow: var(--shadow);
margin-right: 0.5em;
- background-color: $fallback--fg;
- background-color: var(--input, $fallback--fg);
vertical-align: top;
text-align: center;
line-height: 1.1;
@@ -533,12 +574,11 @@ textarea,
}
}
- &[type=checkbox] {
- display: none;
-
+ &[type="checkbox"] {
&:checked + label::before {
- color: $fallback--text;
- color: var(--inputText, $fallback--text);
+ color: var(--text);
+ background-color: var(--background);
+ box-shadow: var(--shadow);
}
&:disabled {
@@ -552,17 +592,13 @@ textarea,
+ label::before {
flex-shrink: 0;
display: inline-block;
- content: '✓';
+ content: "✓";
transition: color 200ms;
width: 1.1em;
height: 1.1em;
- border-radius: $fallback--checkboxRadius;
- border-radius: var(--checkboxRadius, $fallback--checkboxRadius);
- box-shadow: 0 0 2px black inset;
- box-shadow: var(--inputShadow);
+ border-radius: var(--roundness);
+ box-shadow: var(--shadow);
margin-right: 0.5em;
- background-color: $fallback--fg;
- background-color: var(--input, $fallback--fg);
vertical-align: top;
text-align: center;
line-height: 1.1;
@@ -578,29 +614,52 @@ textarea,
}
}
+.input,
+.button-default {
+ --_roundness-left: var(--roundness);
+ --_roundness-right: var(--roundness);
+
+ border-top-left-radius: var(--_roundness-left);
+ border-bottom-left-radius: var(--_roundness-left);
+ border-top-right-radius: var(--_roundness-right);
+ border-bottom-right-radius: var(--_roundness-right);
+}
+
// Textareas should have stock line-height + vertical padding instead of huge line-height
-textarea {
+textarea.input {
padding: var(--_padding);
line-height: var(--post-line-height);
}
option {
- color: $fallback--text;
- color: var(--text, $fallback--text);
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
+ color: var(--text);
+ background-color: var(--background);
}
.hide-number-spinner {
- -moz-appearance: textfield;
+ appearance: textfield;
- &[type=number]::-webkit-inner-spin-button,
- &[type=number]::-webkit-outer-spin-button {
+ &[type="number"]::-webkit-inner-spin-button,
+ &[type="number"]::-webkit-outer-spin-button {
opacity: 0;
display: none;
}
}
+.cards-list {
+ list-style: none;
+ display: grid;
+ grid-auto-flow: row dense;
+ grid-template-columns: 1fr 1fr;
+
+ li {
+ border: 1px solid var(--border);
+ border-radius: var(--roundness);
+ padding: 0.5em;
+ margin: 0.25em;
+ }
+}
+
.btn-block {
display: block;
width: 100%;
@@ -611,24 +670,26 @@ option {
display: inline-flex;
vertical-align: middle;
- button {
+ > *,
+ > * .button-default {
+ --_roundness-left: 0;
+ --_roundness-right: 0;
+
position: relative;
flex: 1 1 auto;
+ }
- &:not(:last-child) {
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
- }
+ > *:first-child,
+ > *:first-child .button-default {
+ --_roundness-left: var(--roundness);
+ }
- &:not(:first-child) {
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
- }
+ > *:last-child,
+ > *:last-child .button-default {
+ --_roundness-right: var(--roundness);
}
}
-@import './panel.scss';
-
.fa {
color: grey;
}
@@ -644,7 +705,7 @@ option {
max-width: 10em;
min-width: 1.7em;
height: 1.3em;
- padding: 0.15em 0.15em;
+ padding: 0.15em;
vertical-align: middle;
font-weight: normal;
font-style: normal;
@@ -655,74 +716,58 @@ option {
overflow: hidden;
text-overflow: ellipsis;
- &.badge-notification {
- background-color: $fallback--cRed;
- background-color: var(--badgeNotification, $fallback--cRed);
- color: white;
- color: var(--badgeNotificationText, white);
- }
-}
-
-.alert {
- margin: 0 0.35em;
- padding: 0 0.25em;
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
-
- &.error {
- background-color: $fallback--alertError;
- background-color: var(--alertError, $fallback--alertError);
- color: $fallback--text;
- color: var(--alertErrorText, $fallback--text);
-
- .panel-heading & {
- color: $fallback--text;
- color: var(--alertErrorPanelText, $fallback--text);
- }
+ &.-dot,
+ &.-counter {
+ margin: 0;
+ position: absolute;
}
- &.warning {
- background-color: $fallback--alertWarning;
- background-color: var(--alertWarning, $fallback--alertWarning);
- color: $fallback--text;
- color: var(--alertWarningText, $fallback--text);
-
- .panel-heading & {
- color: $fallback--text;
- color: var(--alertWarningPanelText, $fallback--text);
- }
+ &.-dot {
+ min-height: 8px;
+ max-height: 8px;
+ min-width: 8px;
+ max-width: 8px;
+ padding: 0;
+ line-height: 0;
+ font-size: 0;
+ left: calc(50% - 4px);
+ top: calc(50% - 4px);
+ margin-left: 6px;
+ margin-top: -6px;
}
- &.success {
- background-color: var(--alertSuccess, $fallback--alertWarning);
- color: var(--alertSuccessText, $fallback--text);
-
- .panel-heading & {
- color: var(--alertSuccessPanelText, $fallback--text);
- }
+ &.-counter {
+ border-radius: var(--roundness);
+ font-size: 0.75em;
+ line-height: 1;
+ text-align: right;
+ padding: 0.2em;
+ min-width: 0;
+ left: calc(50% - 0.5em);
+ top: calc(50% - 0.4em);
+ margin-left: 0.7em;
+ margin-top: -1em;
}
}
-.faint {
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+.alert {
+ margin: 0 0.35em;
+ padding: 0 0.25em;
+ border-radius: var(--roundness);
+ border: 1px solid var(--border);
}
-.faint-link {
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+.faint {
+ --text: var(--textFaint);
+ --link: var(--linkFaint);
- &:hover {
- text-decoration: underline;
- }
+ color: var(--text);
}
.visibility-notice {
padding: 0.5em;
- border: 1px solid $fallback--faint;
- border: 1px solid var(--faint, $fallback--faint);
- border-radius: $fallback--inputRadius;
- border-radius: var(--inputRadius, $fallback--inputRadius);
+ border: 1px solid var(--textFaint);
+ border-radius: var(--roundness);
}
.notice-dismissible {
@@ -739,17 +784,33 @@ option {
}
.fa-scale-110 {
- &.svg-inline--fa {
+ &.svg-inline--fa,
+ &.iconLetter {
font-size: 1.1em;
}
+
+ &.svg-inline--fa {
+ vertical-align: -0.15em;
+ }
}
.fa-old-padding {
- &.svg-inline--fa {
+ &.iconLetter,
+ &.svg-inline--fa,
+ &-layer {
padding: 0 0.3em;
}
}
+.veryfaint {
+ opacity: 0.25;
+}
+
+.timeago {
+ --link: var(--text);
+ --linkFaint: var(--textFaint);
+}
+
.login-hint {
text-align: center;
@@ -828,10 +889,194 @@ option {
// Vue transitions
.fade-enter-active,
.fade-leave-active {
- transition: opacity 0.2s;
+ transition: opacity 0.3s;
}
.fade-enter-from,
.fade-leave-active {
opacity: 0;
}
+/* stylelint-enable no-descending-specificity */
+
+.visible-for-screenreader-only {
+ display: block;
+ width: 1px;
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ visibility: visible;
+ clip: rect(0 0 0 0);
+ padding: 0;
+ position: absolute;
+}
+
+*::selection {
+ color: var(--selectionText);
+ background-color: var(--selectionBackground);
+}
+
+#splash {
+ pointer-events: none;
+ transition: opacity 2s;
+ opacity: 1;
+
+ &.hidden {
+ opacity: 0;
+ }
+
+ #status {
+ &.css-ok {
+ &::before {
+ display: inline-block;
+ content: "CSS OK";
+ }
+ }
+
+ .initial-text {
+ display: none;
+ }
+ }
+
+ #throbber {
+ animation-duration: 3s;
+ animation-name: bounce;
+ animation-iteration-count: infinite;
+ animation-direction: normal;
+ transform-origin: bottom center;
+
+ &.dead {
+ animation-name: dead;
+ animation-duration: 2s;
+ animation-iteration-count: 1;
+ transform: rotateX(90deg) rotateY(0) rotateZ(-45deg);
+ }
+
+ @keyframes dead {
+ 0% {
+ transform: rotateX(0) rotateY(0) rotateZ(0);
+ }
+
+ 5% {
+ transform: rotateX(0) rotateY(0) rotateZ(1deg);
+ }
+
+ 10% {
+ transform: rotateX(0) rotateY(0) rotateZ(-2deg);
+ }
+
+ 15% {
+ transform: rotateX(0) rotateY(0) rotateZ(3deg);
+ }
+
+ 20% {
+ transform: rotateX(0) rotateY(0) rotateZ(0);
+ }
+
+ 25% {
+ transform: rotateX(0) rotateY(0) rotateZ(0);
+ }
+
+ 30% {
+ transform: rotateX(10deg) rotateY(0) rotateZ(0);
+ }
+
+ 35% {
+ transform: rotateX(-10deg) rotateY(0) rotateZ(0);
+ }
+
+ 40% {
+ transform: rotateX(10deg) rotateY(0) rotateZ(0);
+ }
+
+ 45% {
+ transform: rotateX(-10deg) rotateY(0) rotateZ(0);
+ }
+
+ 50% {
+ transform: rotateX(10deg) rotateY(0) rotateZ(0);
+ }
+
+ 100% {
+ transform: rotateX(90deg) rotateY(0) rotateZ(-45deg);
+ transition-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); /* easeInQuint */
+ }
+ }
+
+ @keyframes bounce {
+ 0% {
+ scale: 1 1;
+ translate: 0 0;
+ animation-timing-function: ease-out;
+ }
+
+ 10% {
+ scale: 1.2 0.8;
+ translate: 0 0;
+ transform: rotateZ(var(--defaultZ));
+ animation-timing-function: ease-out;
+ }
+
+ 30% {
+ scale: 0.9 1.1;
+ translate: 0 -40%;
+ transform: rotateZ(var(--defaultZ));
+ animation-timing-function: ease-in;
+ }
+
+ 40% {
+ scale: 1.1 0.9;
+ translate: 0 -50%;
+ transform: rotateZ(var(--defaultZ));
+ animation-timing-function: ease-in;
+ }
+
+ 45% {
+ scale: 0.9 1.1;
+ translate: 0 -45%;
+ transform: rotateZ(var(--defaultZ));
+ animation-timing-function: ease-in;
+ }
+
+ 50% {
+ scale: 1.05 0.95;
+ translate: 0 -40%;
+ animation-timing-function: ease-in;
+ }
+
+ 55% {
+ scale: 0.985 1.025;
+ translate: 0 -35%;
+ transform: rotateZ(var(--defaultZ));
+ animation-timing-function: ease-in;
+ }
+
+ 60% {
+ scale: 1.0125 0.9985;
+ translate: 0 -30%;
+ transform: rotateZ(var(--defaultZ));
+ animation-timing-function: ease-in;
+ }
+
+ 80% {
+ scale: 1.0063 0.9938;
+ translate: 0 -10%;
+ transform: rotateZ(var(--defaultZ));
+ animation-timing-function: ease-in-ou;
+ }
+
+ 90% {
+ scale: 1.2 0.8;
+ translate: 0 0;
+ transform: rotateZ(var(--defaultZ));
+ animation-timing-function: ease-out;
+ }
+
+ 100% {
+ scale: 1 1;
+ translate: 0 0;
+ transform: rotateZ(var(--defaultZ));
+ animation-timing-function: ease-out;
+ }
+ }
+ }
+}
diff --git a/src/App.vue b/src/App.vue
@@ -1,5 +1,6 @@
<template>
<div
+ v-show="$store.state.interface.themeApplied"
id="app-loaded"
:style="bgStyle"
>
@@ -8,15 +9,22 @@
class="app-bg-wrapper"
/>
<MobileNav v-if="layoutType === 'mobile'" />
- <DesktopNav v-else />
+ <DesktopNav
+ v-else
+ :class="navClasses"
+ />
<Notifications v-if="currentUser" />
<div
id="content"
class="app-layout container"
:class="classes"
>
- <div class="underlay"/>
- <div id="sidebar" class="column -scrollable" :class="{ '-show-scrollbar': showScrollbars }">
+ <div class="underlay" />
+ <div
+ id="sidebar"
+ class="column -scrollable"
+ :class="{ '-show-scrollbar': showScrollbars }"
+ >
<user-panel />
<template v-if="layoutType !== 'mobile'">
<nav-panel />
@@ -26,7 +34,11 @@
<div id="notifs-sidebar" />
</template>
</div>
- <div id="main-scroller" class="column main" :class="{ '-full-height': isChats }">
+ <main
+ id="main-scroller"
+ class="column main"
+ :class="{ '-full-height': isChats || isListEdit }"
+ >
<div
v-if="!currentUser"
class="login-hint panel panel-default"
@@ -39,10 +51,14 @@
</router-link>
</div>
<router-view />
- </div>
- <div id="notifs-column" class="column -scrollable" :class="{ '-show-scrollbar': showScrollbars }"/>
+ </main>
+ <div
+ id="notifs-column"
+ class="column -scrollable"
+ :class="{ '-show-scrollbar': showScrollbars }"
+ />
</div>
- <media-modal />
+ <MediaModal />
<shout-panel
v-if="currentUser && shout && !hideShoutbox"
:floating="true"
@@ -52,8 +68,10 @@
<MobilePostStatusButton />
<UserReportingModal />
<PostStatusModal />
- <SettingsModal />
- <div id="modal" />
+ <EditStatusModal v-if="editingAvailable" />
+ <StatusHistoryModal v-if="editingAvailable" />
+ <SettingsModal :class="layoutModalClass" />
+ <UpdateNotification />
<GlobalNoticeList />
</div>
</template>
diff --git a/src/_mixins.scss b/src/_mixins.scss
@@ -0,0 +1,18 @@
+@mixin unfocused-style {
+ @content;
+
+ &:focus:not(:focus-visible, :hover) {
+ @content;
+ }
+}
+
+@mixin focused-style {
+ &:hover,
+ &:focus {
+ @content;
+ }
+
+ &:focus-visible {
+ @content;
+ }
+}
diff --git a/src/_variables.scss b/src/_variables.scss
@@ -1,34 +0,0 @@
-$main-color: #f58d2c;
-$main-background: white;
-$darkened-background: whitesmoke;
-
-$fallback--bg: #121a24;
-$fallback--fg: #182230;
-$fallback--faint: rgba(185, 185, 186, .5);
-$fallback--text: #b9b9ba;
-$fallback--link: #d8a070;
-$fallback--icon: #666;
-$fallback--lightBg: rgb(21, 30, 42);
-$fallback--lightText: #b9b9ba;
-$fallback--border: #222;
-$fallback--cRed: #ff0000;
-$fallback--cBlue: #0095ff;
-$fallback--cGreen: #0fa00f;
-$fallback--cOrange: orange;
-
-$fallback--alertError: rgba(211,16,20,.5);
-$fallback--alertWarning: rgba(111,111,20,.5);
-
-$fallback--panelRadius: 10px;
-$fallback--checkboxRadius: 2px;
-$fallback--btnRadius: 4px;
-$fallback--inputRadius: 4px;
-$fallback--tooltipRadius: 5px;
-$fallback--avatarRadius: 4px;
-$fallback--avatarAltRadius: 10px;
-$fallback--attachmentRadius: 10px;
-$fallback--chatMessageRadius: 10px;
-
-$fallback--buttonShadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
-
-$status-margin: 0.75em;
diff --git a/src/assets/pleromatan_apology.png b/src/assets/pleromatan_apology.png
@@ -0,0 +1 @@
+../../static/pleromatan_apology.png
+\ No newline at end of file
diff --git a/src/assets/pleromatan_apology_fox.png b/src/assets/pleromatan_apology_fox.png
@@ -0,0 +1 @@
+../../static/pleromatan_apology_fox.png
+\ No newline at end of file
diff --git a/src/assets/pleromatan_apology_fox_mask.png b/src/assets/pleromatan_apology_fox_mask.png
Binary files differ.
diff --git a/src/assets/pleromatan_apology_mask.png b/src/assets/pleromatan_apology_mask.png
Binary files differ.
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
@@ -1,6 +1,8 @@
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import vClickOutside from 'click-outside-vue3'
+import VueVirtualScroller from 'vue-virtual-scroller'
+import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
@@ -11,9 +13,9 @@ import VBodyScrollLock from 'src/directives/body_scroll_lock'
import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
-import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
-import { applyTheme } from '../services/style_setter/style_setter.js'
+import { applyConfig } from '../services/style_setter/style_setter.js'
import FaviconService from '../services/favicon_service/favicon_service.js'
+import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
let staticInitialResults = null
@@ -58,6 +60,8 @@ const getInstanceConfig = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
+ store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma.metadata.birthday_required })
+ store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma.metadata.birthday_min_age || 0 })
if (vapidPublicKey) {
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
@@ -118,6 +122,9 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
store.dispatch('setInstanceOption', { name, value: config[name] })
}
+ copyInstanceOption('theme')
+ copyInstanceOption('style')
+ copyInstanceOption('palette')
copyInstanceOption('nsfwCensorImage')
copyInstanceOption('background')
copyInstanceOption('hidePostStats')
@@ -155,8 +162,6 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('showFeaturesPanel')
copyInstanceOption('hideSitename')
copyInstanceOption('sidebarRight')
-
- return store.dispatch('setTheme', config['theme'])
}
const getTOS = async ({ store }) => {
@@ -197,7 +202,7 @@ const getStickers = async ({ store }) => {
const stickers = (await Promise.all(
Object.entries(values).map(async ([name, path]) => {
const resPack = await window.fetch(path + 'pack.json')
- var meta = {}
+ let meta = {}
if (resPack.ok) {
meta = await resPack.json()
}
@@ -238,7 +243,7 @@ const resolveStaffAccounts = ({ store, accounts }) => {
const getNodeInfo = async ({ store }) => {
try {
- const res = await preloadFetch('/nodeinfo/2.0.json')
+ const res = await preloadFetch('/nodeinfo/2.1.json')
if (res.ok) {
const data = await res.json()
const metadata = data.metadata
@@ -249,10 +254,15 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
+ store.dispatch('setInstanceOption', { name: 'pleromaCustomEmojiReactionsAvailable', value: features.includes('pleroma_custom_emoji_reactions') })
+ store.dispatch('setInstanceOption', { name: 'pleromaBookmarkFoldersAvailable', value: features.includes('pleroma:bookmark_folders') })
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
+ store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
+ store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
+ store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
const uploadLimits = metadata.uploadLimits
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
@@ -270,6 +280,7 @@ const getNodeInfo = async ({ store }) => {
const software = data.software
store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
+ store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository })
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' })
const priv = metadata.private
@@ -319,16 +330,10 @@ const setConfig = async ({ store }) => {
}
const checkOAuthToken = async ({ store }) => {
- return new Promise(async (resolve, reject) => {
- if (store.getters.getUserToken()) {
- try {
- await store.dispatch('loginUser', store.getters.getUserToken())
- } catch (e) {
- console.error(e)
- }
- }
- resolve()
- })
+ if (store.getters.getUserToken()) {
+ return store.dispatch('loginUser', store.getters.getUserToken())
+ }
+ return Promise.resolve()
}
const afterStoreSetup = async ({ store, i18n }) => {
@@ -336,29 +341,24 @@ const afterStoreSetup = async ({ store, i18n }) => {
store.dispatch('setLayoutHeight', windowHeight())
FaviconService.initFaviconService()
+ initServiceWorker(store)
+
+ window.addEventListener('focus', () => updateFocus())
const overrides = window.___pleromafe_dev_overrides || {}
const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
store.dispatch('setInstanceOption', { name: 'server', value: server })
await setConfig({ store })
-
- const { customTheme, customThemeSource } = store.state.config
- const { theme } = store.state.instance
- const customThemePresent = customThemeSource || customTheme
-
- if (customThemePresent) {
- if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) {
- applyTheme(customThemeSource)
- } else {
- applyTheme(customTheme)
- }
- } else if (theme) {
- // do nothing, it will load asynchronously
- } else {
- console.error('Failed to load any theme!')
+ try {
+ await store.dispatch('applyTheme').catch((e) => { console.error('Error setting theme', e) })
+ } catch (e) {
+ window.splashError(e)
+ return Promise.reject(e)
}
+ applyConfig(store.state.config, i18n.global)
+
// Now we can try getting the server settings and logging in
// Most of these are preloaded into the index.html so blocking is minimized
await Promise.all([
@@ -366,10 +366,11 @@ const afterStoreSetup = async ({ store, i18n }) => {
getInstancePanel({ store }),
getNodeInfo({ store }),
getInstanceConfig({ store })
- ])
+ ]).catch(e => Promise.reject(e))
// Start fetching things that don't need to block the UI
store.dispatch('fetchMutes')
+ store.dispatch('startFetchingAnnouncements')
getTOS({ store })
getStickers({ store })
@@ -390,14 +391,24 @@ const afterStoreSetup = async ({ store, i18n }) => {
app.use(store)
app.use(i18n)
+ // Little thing to get out of invalid theme state
+ window.resetThemes = () => {
+ store.dispatch('resetThemeV3')
+ store.dispatch('resetThemeV3Palette')
+ store.dispatch('resetThemeV2')
+ }
+
app.use(vClickOutside)
app.use(VBodyScrollLock)
+ app.use(VueVirtualScroller)
app.component('FAIcon', FontAwesomeIcon)
app.component('FALayers', FontAwesomeLayers)
- app.mount('#app')
+ // remove after vue 3.3
+ app.config.unwrapInjectedRef = true
+ app.mount('#app')
return app
}
diff --git a/src/boot/routes.js b/src/boot/routes.js
@@ -20,6 +20,14 @@ import ShoutPanel from 'components/shout_panel/shout_panel.vue'
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
import About from 'components/about/about.vue'
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
+import Lists from 'components/lists/lists.vue'
+import ListsTimeline from 'components/lists_timeline/lists_timeline.vue'
+import ListsEdit from 'components/lists_edit/lists_edit.vue'
+import NavPanel from 'src/components/nav_panel/nav_panel.vue'
+import AnnouncementsPage from 'components/announcements_page/announcements_page.vue'
+import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue'
+import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue'
+import BookmarkFolderEdit from '../components/bookmark_folder_edit/bookmark_folder_edit.vue'
export default (store) => {
const validateAuthenticatedRoute = (to, from, next) => {
@@ -31,7 +39,8 @@ export default (store) => {
}
let routes = [
- { name: 'root',
+ {
+ name: 'root',
path: '/',
redirect: _to => {
return (store.state.users.currentUser
@@ -45,17 +54,20 @@ export default (store) => {
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
- { name: 'remote-user-profile-acct',
+ { name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline },
+ {
+ name: 'remote-user-profile-acct',
path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',
component: RemoteUserResolver,
beforeEnter: validateAuthenticatedRoute
},
- { name: 'remote-user-profile',
+ {
+ name: 'remote-user-profile',
path: '/remote-users/:hostname/:username',
component: RemoteUserResolver,
beforeEnter: validateAuthenticatedRoute
},
- { name: 'external-user-profile', path: '/users/:id', component: UserProfile },
+ { name: 'external-user-profile', path: '/users/$:id', component: UserProfile },
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
{ name: 'registration', path: '/registration', component: Registration },
@@ -69,7 +81,18 @@ export default (store) => {
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
{ name: 'about', path: '/about', component: About },
- { name: 'user-profile', path: '/:_(users)?/:name', component: UserProfile }
+ { name: 'announcements', path: '/announcements', component: AnnouncementsPage },
+ { name: 'user-profile', path: '/users/:name', component: UserProfile },
+ { name: 'legacy-user-profile', path: '/:name', component: UserProfile },
+ { name: 'lists', path: '/lists', component: Lists },
+ { name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline },
+ { name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit },
+ { name: 'lists-new', path: '/lists/new', component: ListsEdit },
+ { name: 'edit-navigation', path: '/nav-edit', component: NavPanel, props: () => ({ forceExpand: true, forceEditMode: true }), beforeEnter: validateAuthenticatedRoute },
+ { name: 'bookmark-folders', path: '/bookmark_folders', component: BookmarkFolders },
+ { name: 'bookmark-folder-new', path: '/bookmarks/new-folder', component: BookmarkFolderEdit },
+ { name: 'bookmark-folder', path: '/bookmarks/:id', component: BookmarkTimeline },
+ { name: 'bookmark-folder-edit', path: '/bookmarks/:id/edit', component: BookmarkFolderEdit }
]
if (store.state.instance.pleromaChatMessagesAvailable) {
diff --git a/src/components/about/about.vue b/src/components/about/about.vue
@@ -8,7 +8,4 @@
</div>
</template>
-<script src="./about.js" ></script>
-
-<style lang="scss">
-</style>
+<script src="./about.js"></script>
diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js
@@ -1,6 +1,8 @@
import { mapState } from 'vuex'
import ProgressButton from '../progress_button/progress_button.vue'
import Popover from '../popover/popover.vue'
+import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
+import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEllipsisV
@@ -15,13 +17,30 @@ const AccountActions = {
'user', 'relationship'
],
data () {
- return { }
+ return {
+ showingConfirmBlock: false,
+ showingConfirmRemoveFollower: false
+ }
},
components: {
ProgressButton,
- Popover
+ Popover,
+ UserListMenu,
+ ConfirmModal
},
methods: {
+ showConfirmBlock () {
+ this.showingConfirmBlock = true
+ },
+ hideConfirmBlock () {
+ this.showingConfirmBlock = false
+ },
+ showConfirmRemoveUserFromFollowers () {
+ this.showingConfirmRemoveFollower = true
+ },
+ hideConfirmRemoveUserFromFollowers () {
+ this.showingConfirmRemoveFollower = false
+ },
showRepeats () {
this.$store.dispatch('showReblogs', this.user.id)
},
@@ -29,11 +48,30 @@ const AccountActions = {
this.$store.dispatch('hideReblogs', this.user.id)
},
blockUser () {
+ if (!this.shouldConfirmBlock) {
+ this.doBlockUser()
+ } else {
+ this.showConfirmBlock()
+ }
+ },
+ doBlockUser () {
this.$store.dispatch('blockUser', this.user.id)
+ this.hideConfirmBlock()
},
unblockUser () {
this.$store.dispatch('unblockUser', this.user.id)
},
+ removeUserFromFollowers () {
+ if (!this.shouldConfirmRemoveUserFromFollowers) {
+ this.doRemoveUserFromFollowers()
+ } else {
+ this.showConfirmRemoveUserFromFollowers()
+ }
+ },
+ doRemoveUserFromFollowers () {
+ this.$store.dispatch('removeUserFromFollowers', this.user.id)
+ this.hideConfirmRemoveUserFromFollowers()
+ },
reportUser () {
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
},
@@ -45,6 +83,12 @@ const AccountActions = {
}
},
computed: {
+ shouldConfirmBlock () {
+ return this.$store.getters.mergedConfig.modalOnBlock
+ },
+ shouldConfirmRemoveUserFromFollowers () {
+ return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
+ },
...mapState({
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
})
diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue
@@ -6,19 +6,19 @@
:bound-to="{ x: 'container' }"
remove-padding
>
- <template v-slot:content>
+ <template #content>
<div class="dropdown-menu">
<template v-if="relationship.following">
<button
v-if="relationship.showing_reblogs"
- class="btn button-default dropdown-item"
+ class="dropdown-item menu-item"
@click="hideRepeats"
>
{{ $t('user_card.hide_repeats') }}
</button>
<button
v-if="!relationship.showing_reblogs"
- class="btn button-default dropdown-item"
+ class="dropdown-item menu-item"
@click="showRepeats"
>
{{ $t('user_card.show_repeats') }}
@@ -28,36 +28,44 @@
class="dropdown-divider"
/>
</template>
+ <UserListMenu :user="user" />
+ <button
+ v-if="relationship.followed_by"
+ class="dropdown-item menu-item"
+ @click="removeUserFromFollowers"
+ >
+ {{ $t('user_card.remove_follower') }}
+ </button>
<button
v-if="relationship.blocking"
- class="btn button-default btn-block dropdown-item"
+ class="dropdown-item menu-item"
@click="unblockUser"
>
{{ $t('user_card.unblock') }}
</button>
<button
v-else
- class="btn button-default btn-block dropdown-item"
+ class="dropdown-item menu-item"
@click="blockUser"
>
{{ $t('user_card.block') }}
</button>
<button
- class="btn button-default btn-block dropdown-item"
+ class="dropdown-item menu-item"
@click="reportUser"
>
{{ $t('user_card.report') }}
</button>
<button
v-if="pleromaChatMessagesAvailable"
- class="btn button-default btn-block dropdown-item"
+ class="dropdown-item menu-item"
@click="openChat"
>
{{ $t('user_card.message') }}
</button>
</div>
</template>
- <template v-slot:trigger>
+ <template #trigger>
<button class="button-unstyled ellipsis-button">
<FAIcon
class="icon"
@@ -66,24 +74,62 @@
</button>
</template>
</Popover>
+ <teleport to="#modal">
+ <confirm-modal
+ v-if="showingConfirmBlock"
+ :title="$t('user_card.block_confirm_title')"
+ :confirm-text="$t('user_card.block_confirm_accept_button')"
+ :cancel-text="$t('user_card.block_confirm_cancel_button')"
+ @accepted="doBlockUser"
+ @cancelled="hideConfirmBlock"
+ >
+ <i18n-t
+ keypath="user_card.block_confirm"
+ tag="span"
+ scope="global"
+ >
+ <template #user>
+ <span
+ v-text="user.screen_name_ui"
+ />
+ </template>
+ </i18n-t>
+ </confirm-modal>
+ </teleport>
+ <teleport to="#modal">
+ <confirm-modal
+ v-if="showingConfirmRemoveFollower"
+ :title="$t('user_card.remove_follower_confirm_title')"
+ :confirm-text="$t('user_card.remove_follower_confirm_accept_button')"
+ :cancel-text="$t('user_card.remove_follower_confirm_cancel_button')"
+ @accepted="doRemoveUserFromFollowers"
+ @cancelled="hideConfirmRemoveUserFromFollowers"
+ >
+ <i18n-t
+ keypath="user_card.remove_follower_confirm"
+ tag="span"
+ scope="global"
+ >
+ <template #user>
+ <span
+ v-text="user.screen_name_ui"
+ />
+ </template>
+ </i18n-t>
+ </confirm-modal>
+ </teleport>
</div>
</template>
<script src="./account_actions.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
.AccountActions {
.ellipsis-button {
width: 2.5em;
margin: -0.5em 0;
padding: 0.5em 0;
text-align: center;
-
- &:not(:hover) .icon {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
- }
}
}
</style>
diff --git a/src/components/alert.style.js b/src/components/alert.style.js
@@ -0,0 +1,57 @@
+export default {
+ name: 'Alert',
+ selector: '.alert',
+ validInnerComponents: [
+ 'Text',
+ 'Icon',
+ 'Link',
+ 'Border',
+ 'ButtonUnstyled'
+ ],
+ variants: {
+ normal: '.neutral',
+ error: '.error',
+ warning: '.warning',
+ success: '.success'
+ },
+ editor: {
+ border: 1,
+ aspect: '3 / 1'
+ },
+ defaultRules: [
+ {
+ directives: {
+ background: '--text',
+ opacity: 0.5,
+ blur: '9px'
+ }
+ },
+ {
+ parent: {
+ component: 'Alert'
+ },
+ component: 'Border',
+ directives: {
+ textColor: '--parent'
+ }
+ },
+ {
+ variant: 'error',
+ directives: {
+ background: '--cRed'
+ }
+ },
+ {
+ variant: 'warning',
+ directives: {
+ background: '--cOrange'
+ }
+ },
+ {
+ variant: 'success',
+ directives: {
+ background: '--cGreen'
+ }
+ }
+ ]
+}
diff --git a/src/components/announcement/announcement.js b/src/components/announcement/announcement.js
@@ -0,0 +1,108 @@
+import { mapState } from 'vuex'
+import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
+import RichContent from '../rich_content/rich_content.jsx'
+import localeService from '../../services/locale/locale.service.js'
+
+const Announcement = {
+ components: {
+ AnnouncementEditor,
+ RichContent
+ },
+ data () {
+ return {
+ editing: false,
+ editedAnnouncement: {
+ content: '',
+ startsAt: undefined,
+ endsAt: undefined,
+ allDay: undefined
+ },
+ editError: ''
+ }
+ },
+ props: {
+ announcement: Object
+ },
+ computed: {
+ ...mapState({
+ currentUser: state => state.users.currentUser
+ }),
+ canEditAnnouncement () {
+ return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
+ },
+ content () {
+ return this.announcement.content
+ },
+ isRead () {
+ return this.announcement.read
+ },
+ publishedAt () {
+ const time = this.announcement.published_at
+ if (!time) {
+ return
+ }
+
+ return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
+ },
+ startsAt () {
+ const time = this.announcement.starts_at
+ if (!time) {
+ return
+ }
+
+ return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
+ },
+ endsAt () {
+ const time = this.announcement.ends_at
+ if (!time) {
+ return
+ }
+
+ return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
+ },
+ inactive () {
+ return this.announcement.inactive
+ }
+ },
+ methods: {
+ markAsRead () {
+ if (!this.isRead) {
+ return this.$store.dispatch('markAnnouncementAsRead', this.announcement.id)
+ }
+ },
+ deleteAnnouncement () {
+ return this.$store.dispatch('deleteAnnouncement', this.announcement.id)
+ },
+ formatTimeOrDate (time, locale) {
+ const d = new Date(time)
+ return this.announcement.all_day ? d.toLocaleDateString(locale) : d.toLocaleString(locale)
+ },
+ enterEditMode () {
+ this.editedAnnouncement.content = this.announcement.pleroma.raw_content
+ this.editedAnnouncement.startsAt = this.announcement.starts_at
+ this.editedAnnouncement.endsAt = this.announcement.ends_at
+ this.editedAnnouncement.allDay = this.announcement.all_day
+ this.editing = true
+ },
+ submitEdit () {
+ this.$store.dispatch('editAnnouncement', {
+ id: this.announcement.id,
+ ...this.editedAnnouncement
+ })
+ .then(() => {
+ this.editing = false
+ })
+ .catch(error => {
+ this.editError = error.error
+ })
+ },
+ cancelEdit () {
+ this.editing = false
+ },
+ clearError () {
+ this.editError = undefined
+ }
+ }
+}
+
+export default Announcement
diff --git a/src/components/announcement/announcement.vue b/src/components/announcement/announcement.vue
@@ -0,0 +1,134 @@
+<template>
+ <div class="announcement">
+ <div class="heading">
+ <h4>{{ $t('announcements.title') }}</h4>
+ </div>
+ <div class="body">
+ <rich-content
+ v-if="!editing"
+ :html="content"
+ :emoji="announcement.emojis"
+ :handle-links="true"
+ />
+ <announcement-editor
+ v-else
+ :announcement="editedAnnouncement"
+ />
+ </div>
+ <div class="footer">
+ <div
+ v-if="!editing"
+ class="times"
+ >
+ <span v-if="publishedAt">
+ {{ $t('announcements.published_time_display', { time: publishedAt }) }}
+ </span>
+ <span v-if="startsAt">
+ {{ $t('announcements.start_time_display', { time: startsAt }) }}
+ </span>
+ <span v-if="endsAt">
+ {{ $t('announcements.end_time_display', { time: endsAt }) }}
+ </span>
+ </div>
+ <div
+ v-if="!editing"
+ class="actions"
+ >
+ <button
+ v-if="currentUser"
+ class="btn button-default"
+ :class="{ toggled: isRead }"
+ :disabled="inactive"
+ :title="inactive ? $t('announcements.inactive_message') : ''"
+ @click="markAsRead"
+ >
+ {{ $t('announcements.mark_as_read_action') }}
+ </button>
+ <button
+ v-if="canEditAnnouncement"
+ class="btn button-default"
+ @click="enterEditMode"
+ >
+ {{ $t('announcements.edit_action') }}
+ </button>
+ <button
+ v-if="canEditAnnouncement"
+ class="btn button-default"
+ @click="deleteAnnouncement"
+ >
+ {{ $t('announcements.delete_action') }}
+ </button>
+ </div>
+ <div
+ v-else
+ class="actions"
+ >
+ <button
+ class="btn button-default"
+ @click="submitEdit"
+ >
+ {{ $t('announcements.submit_edit_action') }}
+ </button>
+ <button
+ class="btn button-default"
+ @click="cancelEdit"
+ >
+ {{ $t('announcements.cancel_edit_action') }}
+ </button>
+ <div
+ v-if="editing && editError"
+ class="alert error"
+ >
+ {{ $t('announcements.edit_error', { error }) }}
+ <button
+ class="button-unstyled"
+ @click="clearError"
+ >
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="times"
+ :title="$t('announcements.close_error')"
+ />
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script src="./announcement.js"></script>
+
+<style lang="scss">
+.announcement {
+ border-bottom: 1px solid var(--border);
+ border-radius: 0;
+ padding: var(--status-margin);
+
+ .heading,
+ .body {
+ margin-bottom: var(--status-margin);
+ }
+
+ .footer {
+ display: flex;
+ flex-direction: column;
+
+ .times {
+ display: flex;
+ flex-direction: column;
+ }
+ }
+
+ .footer .actions {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-evenly;
+
+ .btn {
+ flex: 1;
+ margin: 1em;
+ max-width: 10em;
+ }
+ }
+}
+</style>
diff --git a/src/components/announcement_editor/announcement_editor.js b/src/components/announcement_editor/announcement_editor.js
@@ -0,0 +1,13 @@
+import Checkbox from '../checkbox/checkbox.vue'
+
+const AnnouncementEditor = {
+ components: {
+ Checkbox
+ },
+ props: {
+ announcement: Object,
+ disabled: Boolean
+ }
+}
+
+export default AnnouncementEditor
diff --git a/src/components/announcement_editor/announcement_editor.vue b/src/components/announcement_editor/announcement_editor.vue
@@ -0,0 +1,63 @@
+<template>
+ <div class="announcement-editor">
+ <textarea
+ ref="textarea"
+ v-model="announcement.content"
+ class="input post-textarea"
+ rows="1"
+ cols="1"
+ :placeholder="$t('announcements.post_placeholder')"
+ :disabled="disabled"
+ />
+ <span class="announcement-metadata">
+ <label for="announcement-start-time">{{ $t('announcements.start_time_prompt') }}</label>
+ <input
+ id="announcement-start-time"
+ v-model="announcement.startsAt"
+ class="input"
+ :type="announcement.allDay ? 'date' : 'datetime-local'"
+ :disabled="disabled"
+ >
+ </span>
+ <span class="announcement-metadata">
+ <label for="announcement-end-time">{{ $t('announcements.end_time_prompt') }}</label>
+ <input
+ id="announcement-end-time"
+ v-model="announcement.endsAt"
+ class="input"
+ :type="announcement.allDay ? 'date' : 'datetime-local'"
+ :disabled="disabled"
+ >
+ </span>
+ <span class="announcement-metadata">
+ <Checkbox
+ id="announcement-all-day"
+ v-model="announcement.allDay"
+ :disabled="disabled"
+ >
+ {{ $t('announcements.all_day_prompt') }}
+ </Checkbox>
+ </span>
+ </div>
+</template>
+
+<script src="./announcement_editor.js"></script>
+
+<style lang="scss">
+.announcement-editor {
+ display: flex;
+ align-items: stretch;
+ flex-direction: column;
+
+ .announcement-metadata {
+ margin-top: 0.5em;
+ }
+
+ .post-textarea {
+ resize: vertical;
+ height: 10em;
+ overflow: none;
+ box-sizing: content-box;
+ }
+}
+</style>
diff --git a/src/components/announcements_page/announcements_page.js b/src/components/announcements_page/announcements_page.js
@@ -0,0 +1,58 @@
+import { mapState } from 'vuex'
+import Announcement from '../announcement/announcement.vue'
+import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
+
+const AnnouncementsPage = {
+ components: {
+ Announcement,
+ AnnouncementEditor
+ },
+ data () {
+ return {
+ newAnnouncement: {
+ content: '',
+ startsAt: undefined,
+ endsAt: undefined,
+ allDay: false
+ },
+ posting: false,
+ error: undefined
+ }
+ },
+ mounted () {
+ this.$store.dispatch('fetchAnnouncements')
+ },
+ computed: {
+ ...mapState({
+ currentUser: state => state.users.currentUser
+ }),
+ announcements () {
+ return this.$store.state.announcements.announcements
+ },
+ canPostAnnouncement () {
+ return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
+ }
+ },
+ methods: {
+ postAnnouncement () {
+ this.posting = true
+ this.$store.dispatch('postAnnouncement', this.newAnnouncement)
+ .then(() => {
+ this.newAnnouncement.content = ''
+ this.startsAt = undefined
+ this.endsAt = undefined
+ })
+ .catch(error => {
+ this.error = error.error
+ })
+ .finally(() => {
+ this.posting = false
+ })
+ },
+ clearError () {
+ this.error = undefined
+ }
+ }
+}
+
+export default AnnouncementsPage
diff --git a/src/components/announcements_page/announcements_page.vue b/src/components/announcements_page/announcements_page.vue
@@ -0,0 +1,78 @@
+<template>
+ <div class="panel panel-default announcements-page">
+ <div class="panel-heading">
+ <h1 class="title">
+ {{ $t('announcements.page_header') }}
+ </h1>
+ </div>
+ <div class="panel-body">
+ <section
+ v-if="canPostAnnouncement"
+ >
+ <div class="post-form">
+ <div class="heading">
+ <h4>{{ $t('announcements.post_form_header') }}</h4>
+ </div>
+ <div class="body">
+ <announcement-editor
+ :announcement="newAnnouncement"
+ :disabled="posting"
+ />
+ </div>
+ <div class="footer">
+ <button
+ class="btn button-default post-button"
+ :disabled="posting"
+ @click.prevent="postAnnouncement"
+ >
+ {{ $t('announcements.post_action') }}
+ </button>
+ <div
+ v-if="error"
+ class="alert error"
+ >
+ {{ $t('announcements.post_error', { error }) }}
+ <button
+ class="button-unstyled"
+ @click="clearError"
+ >
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="times"
+ :title="$t('announcements.close_error')"
+ />
+ </button>
+ </div>
+ </div>
+ </div>
+ </section>
+ <section
+ v-for="announcement in announcements"
+ :key="announcement.id"
+ >
+ <announcement
+ :announcement="announcement"
+ />
+ </section>
+ </div>
+ </div>
+</template>
+
+<script src="./announcements_page.js"></script>
+
+<style lang="scss">
+.announcements-page {
+ .post-form {
+ padding: var(--status-margin);
+
+ .heading,
+ .body {
+ margin-bottom: var(--status-margin);
+ }
+
+ .post-button {
+ min-width: 10em;
+ }
+ }
+}
+</style>
diff --git a/src/components/async_component_error/async_component_error.vue b/src/components/async_component_error/async_component_error.vue
@@ -34,9 +34,10 @@ export default {
height: 100%;
align-items: center;
justify-content: center;
+
.btn {
- margin: .5em;
- padding: .5em 2em;
+ margin: 0.5em;
+ padding: 0.5em 2em;
}
}
</style>
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
@@ -36,6 +36,7 @@ library.add(
const Attachment = {
props: [
'attachment',
+ 'compact',
'description',
'hideDescription',
'nsfw',
@@ -71,7 +72,8 @@ const Attachment = {
{
'-loading': this.loading,
'-nsfw-placeholder': this.hidden,
- '-editable': this.edit !== undefined
+ '-editable': this.edit !== undefined,
+ '-compact': this.compact
},
'-type-' + this.type,
this.size && '-size-' + this.size,
@@ -129,6 +131,9 @@ const Attachment = {
...mapGetters(['mergedConfig'])
},
watch: {
+ 'attachment.description' (newVal) {
+ this.localDescription = newVal
+ },
localDescription (newVal) {
this.onEdit(newVal)
}
diff --git a/src/components/attachment/attachment.scss b/src/components/attachment/attachment.scss
@@ -1,5 +1,3 @@
-@import '../../_variables.scss';
-
.Attachment {
display: inline-flex;
flex-direction: column;
@@ -9,10 +7,8 @@
height: 100%;
border-style: solid;
border-width: 1px;
- border-radius: $fallback--attachmentRadius;
- border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-radius: var(--roundness);
+ border-color: var(--border);
.attachment-wrapper {
flex: 1 1 auto;
@@ -84,6 +80,13 @@
}
}
+ .video-container {
+ border: none;
+ outline: none;
+ color: inherit;
+ background: transparent;
+ }
+
.audio-container {
display: flex;
align-items: flex-end;
@@ -102,14 +105,13 @@
padding-top: 0.5em;
}
-
.play-icon {
position: absolute;
font-size: 64px;
top: calc(50% - 32px);
left: calc(50% - 32px);
- color: rgba(255, 255, 255, 0.75);
- text-shadow: 0 0 2px rgba(0, 0, 0, 0.4);
+ color: rgb(255 255 255 / 75%);
+ text-shadow: 0 0 2px rgb(0 0 0 / 40%);
&::before {
margin: 0;
@@ -127,23 +129,26 @@
.attachment-button {
padding: 0;
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
+ border-radius: var(--roundness);
text-align: center;
width: 2em;
height: 2em;
margin-left: 0.5em;
font-size: 1.25em;
- // TODO: theming? hard to theme with unknown background image color
- background: rgba(230, 230, 230, 0.7);
+ }
+ }
- .svg-inline--fa {
- color: rgba(0, 0, 0, 0.6);
- }
+ &.-contain-fit {
+ img,
+ canvas {
+ object-fit: contain;
+ }
+ }
- &:hover .svg-inline--fa {
- color: rgba(0, 0, 0, 0.9);
- }
+ &.-cover-fit {
+ img,
+ canvas {
+ object-fit: cover;
}
}
@@ -160,8 +165,9 @@
.image {
flex: 1;
+
img {
- border: 0px;
+ border: 0;
border-radius: 5px;
height: 100%;
object-fit: cover;
@@ -172,9 +178,10 @@
flex: 2;
margin: 8px;
word-break: break-all;
+
h1 {
font-size: 1rem;
- margin: 0px;
+ margin: 0;
}
}
}
@@ -202,8 +209,7 @@
&.-placeholder {
display: inline-block;
- color: $fallback--link;
- color: var(--postLink, $fallback--link);
+ color: var(--link);
overflow: hidden;
white-space: nowrap;
height: auto;
@@ -252,17 +258,9 @@
cursor: progress;
}
- &.-contain-fit {
- img,
- canvas {
- object-fit: contain;
- }
- }
-
- &.-cover-fit {
- img,
- canvas {
- object-fit: cover;
+ &.-compact {
+ .placeholder-container {
+ padding-bottom: 0.5em;
}
}
}
diff --git a/src/components/attachment/attachment.style.js b/src/components/attachment/attachment.style.js
@@ -0,0 +1,25 @@
+export default {
+ name: 'Attachment',
+ selector: '.Attachment',
+ notEditable: true,
+ validInnerComponents: [
+ 'Border',
+ 'ButtonUnstyled',
+ 'Input'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ roundness: 3
+ }
+ },
+ {
+ component: 'ButtonUnstyled',
+ parent: { component: 'Attachment' },
+ directives: {
+ background: '#FFFFFF',
+ opacity: 0.5
+ }
+ }
+ ]
+}
diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue
@@ -38,7 +38,7 @@
v-if="edit"
v-model="localDescription"
type="text"
- class="description-field"
+ class="input description-field"
:placeholder="$t('post_status.media_description')"
@keydown.enter.prevent=""
>
@@ -162,10 +162,11 @@
target="_blank"
>
<FAIcon
- size="5x"
+ :size="compact ? '2x' : '5x'"
:icon="placeholderIconClass"
+ :title="localDescription"
/>
- <p>
+ <p v-if="!compact">
{{ localDescription }}
</p>
</a>
@@ -174,7 +175,6 @@
:is="videoTag"
v-if="type === 'video' && !hidden"
class="video-container"
- :class="{ 'button-unstyled': 'isModal' }"
:href="attachment.url"
@click.stop.prevent="openModal"
>
@@ -252,7 +252,7 @@
v-if="edit"
v-model="localDescription"
type="text"
- class="description-field"
+ class="input description-field"
:placeholder="$t('post_status.media_description')"
@keydown.enter.prevent=""
>
diff --git a/src/components/autosuggest/autosuggest.vue b/src/components/autosuggest/autosuggest.vue
@@ -1,3 +1,4 @@
+<!-- FIXME THIS NEEDS TO BE REFACTORED TO USE POPOVER -->
<template>
<div
v-click-outside="onClickOutside"
@@ -6,12 +7,12 @@
<input
v-model="term"
:placeholder="placeholder"
- class="autosuggest-input"
+ class="input autosuggest-input"
@click="onInputClick"
>
<div
v-if="resultsVisible && filtered.length > 0"
- class="autosuggest-results"
+ class="panel autosuggest-results"
>
<slot
v-for="item in filtered"
@@ -24,8 +25,6 @@
<script src="./autosuggest.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.autosuggest {
position: relative;
@@ -40,18 +39,15 @@
top: 100%;
right: 0;
max-height: 400px;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
+ background-color: var(--bg);
border-style: solid;
border-width: 1px;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- border-radius: $fallback--inputRadius;
- border-radius: var(--inputRadius, $fallback--inputRadius);
+ border-color: var(--border);
+ border-radius: var(--roundness);
border-top-left-radius: 0;
border-top-right-radius: 0;
- box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
- box-shadow: var(--panelShadow);
+ box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
+ box-shadow: var(--shadow);
overflow-y: auto;
z-index: 1;
}
diff --git a/src/components/avatar_list/avatar_list.vue b/src/components/avatar_list/avatar_list.vue
@@ -14,11 +14,9 @@
</div>
</template>
-<script src="./avatar_list.js" ></script>
+<script src="./avatar_list.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.avatars {
display: flex;
margin: 0;
@@ -36,8 +34,7 @@
}
.avatar-small {
- border-radius: $fallback--avatarAltRadius;
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
+ border-radius: var(--roundness);
height: 24px;
width: 24px;
}
diff --git a/src/components/badge.style.js b/src/components/badge.style.js
@@ -0,0 +1,30 @@
+export default {
+ name: 'Badge',
+ selector: '.badge',
+ validInnerComponents: [
+ 'Text',
+ 'Icon'
+ ],
+ variants: {
+ notification: '.-notification'
+ },
+ defaultRules: [
+ {
+ component: 'Root',
+ directives: {
+ '--badgeNotification': 'color | --cRed'
+ }
+ },
+ {
+ directives: {
+ background: '--cGreen'
+ }
+ },
+ {
+ variant: 'notification',
+ directives: {
+ background: '--cRed'
+ }
+ }
+ ]
+}
diff --git a/src/components/basic_user_card/basic_user_card.js b/src/components/basic_user_card/basic_user_card.js
@@ -1,5 +1,6 @@
-import UserCard from '../user_card/user_card.vue'
+import UserPopover from '../user_popover/user_popover.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
+import UserLink from '../user_link/user_link.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@@ -7,20 +8,13 @@ const BasicUserCard = {
props: [
'user'
],
- data () {
- return {
- userExpanded: false
- }
- },
components: {
- UserCard,
+ UserPopover,
UserAvatar,
- RichContent
+ RichContent,
+ UserLink
},
methods: {
- toggleUserExpanded () {
- this.userExpanded = !this.userExpanded
- },
userProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
}
diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue
@@ -1,24 +1,22 @@
<template>
<div class="basic-user-card">
- <router-link :to="userProfileLink(user)">
- <UserAvatar
- class="avatar"
- :user="user"
- @click.prevent="toggleUserExpanded"
- />
- </router-link>
- <div
- v-if="userExpanded"
- class="basic-user-card-expanded-content"
+ <router-link
+ :to="userProfileLink(user)"
+ @click.prevent
>
- <UserCard
+ <UserPopover
:user-id="user.id"
- :rounded="true"
- :bordered="true"
- />
- </div>
+ :overlay-centers="true"
+ overlay-centers-selector=".avatar"
+ >
+ <UserAvatar
+ class="user-avatar avatar"
+ :user="user"
+ @click.prevent
+ />
+ </UserPopover>
+ </router-link>
<div
- v-else
class="basic-user-card-collapsed-content"
>
<div
@@ -32,12 +30,10 @@
/>
</div>
<div>
- <router-link
+ <user-link
class="basic-user-card-screen-name"
- :to="userProfileLink(user)"
- >
- @{{ user.screen_name_ui }}
- </router-link>
+ :user="user"
+ />
</div>
<slot />
</div>
@@ -51,7 +47,8 @@
display: flex;
flex: 1 0;
margin: 0;
- padding: 0.6em 1em;
+
+ --emoji-size: 1em;
&-collapsed-content {
margin-left: 0.7em;
diff --git a/src/components/block_card/block_card.vue b/src/components/block_card/block_card.vue
@@ -37,6 +37,7 @@
.block-card-content-container {
margin-top: 0.5em;
text-align: right;
+
button {
width: 10em;
}
diff --git a/src/components/bookmark_folder_card/bookmark_folder_card.js b/src/components/bookmark_folder_card/bookmark_folder_card.js
@@ -0,0 +1,22 @@
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faEllipsisH
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faEllipsisH
+)
+
+const BookmarkFolderCard = {
+ props: [
+ 'folder',
+ 'allBookmarks'
+ ],
+ computed: {
+ firstLetter () {
+ return this.folder ? this.folder.name[0] : null
+ }
+ }
+}
+
+export default BookmarkFolderCard
diff --git a/src/components/bookmark_folder_card/bookmark_folder_card.vue b/src/components/bookmark_folder_card/bookmark_folder_card.vue
@@ -0,0 +1,111 @@
+<template>
+ <div
+ v-if="allBookmarks"
+ class="bookmark-folder-card"
+ >
+ <router-link
+ :to="{ name: 'bookmarks' }"
+ class="bookmark-folder-name"
+ >
+ <span class="icon">
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 menu-icon"
+ icon="bookmark"
+ />
+ </span>{{ $t('nav.all_bookmarks') }}
+ </router-link>
+ </div>
+ <div
+ v-else
+ class="bookmark-folder-card"
+ >
+ <router-link
+ :to="{ name: 'bookmark-folder', params: { id: folder.id } }"
+ class="bookmark-folder-name"
+ >
+ <img
+ v-if="folder.emoji_url"
+ class="iconEmoji iconEmoji-image"
+ :src="folder.emoji_url"
+ :alt="folder.emoji"
+ :title="folder.emoji"
+ >
+ <span
+ v-else-if="folder.emoji"
+ class="iconEmoji"
+ >
+ <span>
+ {{ folder.emoji }}
+ </span>
+ </span>
+ <span
+ v-else-if="firstLetter"
+ class="icon iconLetter fa-scale-110"
+ >{{ firstLetter }}</span>{{ folder.name }}
+ </router-link>
+ <router-link
+ :to="{ name: 'bookmark-folder-edit', params: { id: folder.id } }"
+ class="button-folder-edit"
+ >
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="ellipsis-h"
+ />
+ </router-link>
+ </div>
+</template>
+
+<script src="./bookmark_folder_card.js"></script>
+
+<style lang="scss">
+.bookmark-folder-card {
+ display: flex;
+ align-items: center;
+}
+
+a.bookmark-folder-name {
+ display: flex;
+ align-items: center;
+ flex-grow: 1;
+
+ .icon,
+ .iconLetter,
+ .iconEmoji {
+ display: inline-block;
+ height: 2.5rem;
+ width: 2.5rem;
+ margin-right: 0.5rem;
+ }
+
+ .icon,
+ .iconLetter {
+ font-size: 1.5rem;
+ line-height: 2.5rem;
+ text-align: center;
+ }
+
+ .iconEmoji {
+ text-align: center;
+ object-fit: contain;
+ vertical-align: middle;
+
+ > span {
+ font-size: 1.5rem;
+ line-height: 2.5rem;
+ }
+ }
+
+ img.iconEmoji {
+ padding: 0.25em;
+ box-sizing: border-box;
+ }
+}
+
+.bookmark-folder-name,
+.button-folder-edit {
+ margin: 0;
+ padding: 1em;
+ color: var(--link);
+}
+</style>
diff --git a/src/components/bookmark_folder_edit/bookmark_folder_edit.js b/src/components/bookmark_folder_edit/bookmark_folder_edit.js
@@ -0,0 +1,80 @@
+import EmojiPicker from '../emoji_picker/emoji_picker.vue'
+import apiService from '../../services/api/api.service'
+
+const BookmarkFolderEdit = {
+ data () {
+ return {
+ name: '',
+ nameDraft: '',
+ emoji: '',
+ emojiUrl: null,
+ emojiDraft: '',
+ emojiUrlDraft: null,
+ emojiPickerExpanded: false,
+ reallyDelete: false
+ }
+ },
+ components: {
+ EmojiPicker
+ },
+ created () {
+ if (!this.id) return
+ const credentials = this.$store.state.users.currentUser.credentials
+ apiService.fetchBookmarkFolders({ credentials })
+ .then((folders) => {
+ const folder = folders.find(folder => folder.id === this.id)
+ if (!folder) return
+
+ this.nameDraft = this.name = folder.name
+ this.emojiDraft = this.emoji = folder.emoji
+ this.emojiUrlDraft = this.emojiUrl = folder.emoji_url
+ })
+ },
+ computed: {
+ id () {
+ return this.$route.params.id
+ }
+ },
+ methods: {
+ selectEmoji (event) {
+ this.emojiDraft = event.insertion
+ this.emojiUrlDraft = event.insertionUrl
+ },
+ showEmojiPicker () {
+ if (!this.emojiPickerExpanded) {
+ this.$refs.picker.showPicker()
+ }
+ },
+ onShowPicker () {
+ this.emojiPickerExpanded = true
+ },
+ onClosePicker () {
+ this.emojiPickerExpanded = false
+ },
+ updateFolder () {
+ this.$store.dispatch('setBookmarkFolder', { folderId: this.id, name: this.nameDraft, emoji: this.emojiDraft })
+ .then(() => {
+ this.$router.push({ name: 'bookmark-folders' })
+ })
+ },
+ createFolder () {
+ this.$store.dispatch('createBookmarkFolder', { name: this.nameDraft, emoji: this.emojiDraft })
+ .then(() => {
+ this.$router.push({ name: 'bookmark-folders' })
+ })
+ .catch((e) => {
+ this.$store.dispatch('pushGlobalNotice', {
+ messageKey: 'bookmark_folders.error',
+ messageArgs: [e.message],
+ level: 'error'
+ })
+ })
+ },
+ deleteFolder () {
+ this.$store.dispatch('deleteBookmarkFolder', { folderId: this.id })
+ this.$router.push({ name: 'bookmark-folders' })
+ }
+ }
+}
+
+export default BookmarkFolderEdit
diff --git a/src/components/bookmark_folder_edit/bookmark_folder_edit.vue b/src/components/bookmark_folder_edit/bookmark_folder_edit.vue
@@ -0,0 +1,200 @@
+<template>
+ <div class="panel-default panel BookmarkFolderEdit">
+ <div
+ ref="header"
+ class="panel-heading folder-edit-heading"
+ >
+ <button
+ class="button-unstyled go-back-button"
+ @click="$router.back"
+ >
+ <FAIcon
+ size="lg"
+ icon="chevron-left"
+ />
+ </button>
+ <h1 class="title">
+ <i18n-t
+ v-if="id"
+ keypath="bookmark_folders.editing_folder"
+ scope="global"
+ >
+ <template #folderName>
+ {{ name }}
+ </template>
+ </i18n-t>
+ <i18n-t
+ v-else
+ keypath="bookmark_folders.creating_folder"
+ scope="global"
+ />
+ </h1>
+ </div>
+ <div class="panel-body">
+ <div class="input-wrap">
+ <label for="folder-edit-title">{{ $t('bookmark_folders.emoji') }}</label>
+ <button
+ class="input input-emoji"
+ :title="$t('bookmark_folder.emoji_pick')"
+ @click="showEmojiPicker"
+ >
+ <img
+ v-if="emojiUrlDraft"
+ class="iconEmoji iconEmoji-image"
+ :src="emojiUrlDraft"
+ :alt="emojiDraft"
+ :title="emojiDraft"
+ >
+ <span
+ v-else-if="emojiDraft"
+ class="iconEmoji"
+ >
+ <span>
+ {{ emojiDraft }}
+ </span>
+ </span>
+ </button>
+ <EmojiPicker
+ ref="picker"
+ class="emoji-picker-panel"
+ @emoji="selectEmoji"
+ @show="onShowPicker"
+ @close="onClosePicker"
+ />
+ </div>
+ <div class="input-wrap">
+ <label for="folder-edit-title">{{ $t('bookmark_folders.name') }}</label>
+ <input
+ id="folder-edit-title"
+ ref="name"
+ v-model="nameDraft"
+ class="input"
+ >
+ </div>
+ </div>
+ <div class="panel-footer">
+ <span class="spacer" />
+ <button
+ v-if="!id"
+ class="btn button-default footer-button"
+ @click="createFolder"
+ >
+ {{ $t('bookmark_folders.create') }}
+ </button>
+ <button
+ v-else-if="!reallyDelete"
+ class="btn button-default footer-button"
+ @click="reallyDelete = true"
+ >
+ {{ $t('bookmark_folders.delete') }}
+ </button>
+ <template v-else>
+ {{ $t('bookmark_folders.really_delete') }}
+ <button
+ class="btn button-default footer-button"
+ @click="deleteFolder"
+ >
+ {{ $t('general.yes') }}
+ </button>
+ <button
+ class="btn button-default footer-button"
+ @click="reallyDelete = false"
+ >
+ {{ $t('general.no') }}
+ </button>
+ </template>
+ <div
+ v-if="id && !reallyDelete"
+ >
+ <button
+ class="btn button-default follow-button"
+ @click="updateFolder"
+ >
+ {{ $t('bookmark_folders.update_folder') }}
+ </button>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script src="./bookmark_folder_edit.js"></script>
+
+<style lang="scss">
+.BookmarkFolderEdit {
+ --panel-body-padding: 0.5em;
+
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+
+ .folder-edit-heading {
+ grid-template-columns: auto minmax(50%, 1fr);
+ }
+
+ .panel-body {
+ display: flex;
+ gap: 0.5em;
+ }
+
+ .emoji-picker-panel {
+ position: absolute;
+ z-index: 20;
+ margin-top: 2px;
+
+ &.hide {
+ display: none;
+ }
+ }
+
+ .input-emoji {
+ height: 2.5em;
+ width: 2.5em;
+ padding: 0;
+
+ .iconEmoji {
+ display: inline-block;
+ text-align: center;
+ object-fit: contain;
+ vertical-align: middle;
+ height: 2.5em;
+ width: 2.5em;
+
+ > span {
+ font-size: 1.5rem;
+ line-height: 2.5rem;
+ }
+ }
+
+ img.iconEmoji {
+ padding: 0.25em;
+ box-sizing: border-box;
+ }
+ }
+
+ .input-wrap {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5em;
+ }
+
+ .go-back-button {
+ text-align: center;
+ line-height: 1;
+ height: 100%;
+ align-self: start;
+ width: var(--__panel-heading-height-inner);
+ }
+
+ .btn {
+ margin: 0 0.5em;
+ }
+
+ .panel-footer {
+ grid-template-columns: minmax(10%, 1fr);
+
+ .footer-button {
+ min-width: 9em;
+ }
+ }
+}
+</style>
diff --git a/src/components/bookmark_folders/bookmark_folders.js b/src/components/bookmark_folders/bookmark_folders.js
@@ -0,0 +1,27 @@
+import BookmarkFolderCard from '../bookmark_folder_card/bookmark_folder_card.vue'
+
+const BookmarkFolders = {
+ data () {
+ return {
+ isNew: false
+ }
+ },
+ components: {
+ BookmarkFolderCard
+ },
+ computed: {
+ bookmarkFolders () {
+ return this.$store.state.bookmarkFolders.allFolders
+ }
+ },
+ methods: {
+ cancelNewFolder () {
+ this.isNew = false
+ },
+ newFolder () {
+ this.isNew = true
+ }
+ }
+}
+
+export default BookmarkFolders
diff --git a/src/components/bookmark_folders/bookmark_folders.vue b/src/components/bookmark_folders/bookmark_folders.vue
@@ -0,0 +1,37 @@
+<template>
+ <div class="Bookmark-folders panel panel-default">
+ <div class="panel-heading">
+ <h1 class="title">
+ {{ $t('nav.bookmark_folders') }}
+ </h1>
+ <router-link
+ :to="{ name: 'bookmark-folder-new' }"
+ class="button-default btn new-folder-button"
+ >
+ {{ $t("bookmark_folders.new") }}
+ </router-link>
+ </div>
+ <div class="panel-body">
+ <BookmarkFolderCard
+ :all-bookmarks="true"
+ class="list-item"
+ />
+ <BookmarkFolderCard
+ v-for="folder in bookmarkFolders.slice().reverse()"
+ :key="folder"
+ :folder="folder"
+ class="list-item"
+ />
+ </div>
+ </div>
+</template>
+
+<script src="./bookmark_folders.js"></script>
+
+<style lang="scss">
+.Bookmark-folders {
+ .new-folder-button {
+ padding: 0 0.5em;
+ }
+}
+</style>
diff --git a/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js b/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js
@@ -0,0 +1,16 @@
+import { mapState } from 'vuex'
+import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
+import { getBookmarkFolderEntries } from 'src/components/navigation/filter.js'
+
+export const BookmarkFoldersMenuContent = {
+ components: {
+ NavigationEntry
+ },
+ computed: {
+ ...mapState({
+ folders: getBookmarkFolderEntries
+ })
+ }
+}
+
+export default BookmarkFoldersMenuContent
diff --git a/src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue b/src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue
@@ -0,0 +1,19 @@
+<template>
+ <ul>
+ <NavigationEntry
+ :item="{
+ name: 'bookmarks',
+ routeObject: { name: 'bookmarks' },
+ label: 'nav.all_bookmarks',
+ icon: 'bookmark'
+ }"
+ />
+ <NavigationEntry
+ v-for="item in folders"
+ :key="item.id"
+ :item="item"
+ />
+ </ul>
+</template>
+
+<script src="./bookmark_folders_menu_content.js"></script>
diff --git a/src/components/bookmark_timeline/bookmark_timeline.js b/src/components/bookmark_timeline/bookmark_timeline.js
@@ -1,16 +1,31 @@
import Timeline from '../timeline/timeline.vue'
const Bookmarks = {
+ created () {
+ this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
+ this.$store.dispatch('startFetchingTimeline', { timeline: 'bookmarks', bookmarkFolderId: this.folderId || null })
+ },
+ components: {
+ Timeline
+ },
computed: {
+ folderId () {
+ return this.$route.params.id
+ },
timeline () {
return this.$store.state.statuses.timelines.bookmarks
}
},
- components: {
- Timeline
+ watch: {
+ folderId () {
+ this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
+ this.$store.dispatch('stopFetchingTimeline', 'bookmarks')
+ this.$store.dispatch('startFetchingTimeline', { timeline: 'bookmarks', bookmarkFolderId: this.folderId || null })
+ }
},
unmounted () {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
+ this.$store.dispatch('stopFetchingTimeline', 'bookmarks')
}
}
diff --git a/src/components/bookmark_timeline/bookmark_timeline.vue b/src/components/bookmark_timeline/bookmark_timeline.vue
@@ -3,6 +3,7 @@
:title="$t('nav.bookmarks')"
:timeline="timeline"
:timeline-name="'bookmarks'"
+ :bookmark-folder-id="folderId"
/>
</template>
diff --git a/src/components/border.style.js b/src/components/border.style.js
@@ -0,0 +1,13 @@
+export default {
+ name: 'Border',
+ selector: '/*border*/',
+ virtual: true,
+ defaultRules: [
+ {
+ directives: {
+ textColor: '$mod(--parent 10)',
+ textAuto: 'no-auto'
+ }
+ }
+ ]
+}
diff --git a/src/components/button.style.js b/src/components/button.style.js
@@ -0,0 +1,129 @@
+export default {
+ name: 'Button', // Name of the component
+ selector: '.button-default', // CSS selector/prefix
+ // outOfTreeSelector: '' // out-of-tree selector is used when other components are laid over it but it's not part of the tree, see Underlay component
+ // States, system witll calculate ALL possible combinations of those and prepend "normal" to them + standalone "normal" state
+ states: {
+ // States are a bit expensive - the amount of combinations generated is about (1/6)n^3+n, so adding more state increased number of combination by an order of magnitude!
+ // All states inherit from "normal" state, there is no other inheirtance, i.e. hover+disabled only inherits from "normal", not from hover nor disabled.
+ // However, cascading still works, so resulting state will be result of merging of all relevant states/variants
+ // normal: '' // normal state is implicitly added, it is always included
+ toggled: '.toggled',
+ focused: ':focus-visible',
+ pressed: ':focus:active',
+ hover: ':hover:not(:disabled)',
+ disabled: ':disabled'
+ },
+ // Variants are mutually exclusive, each component implicitly has "normal" variant, and all other variants inherit from it.
+ variants: {
+ // Variants save on computation time since adding new variant just adds one more "set".
+ // normal: '', // you can override normal variant, it will be appenended to the main class
+ danger: '.danger'
+ // Overall the compuation difficulty is N*((1/6)M^3+M) where M is number of distinct states and N is number of variants.
+ // This (currently) is further multipled by number of places where component can exist.
+ },
+ editor: {
+ aspect: '2 / 1'
+ },
+ // This lists all other components that can possibly exist within one. Recursion is currently not supported (and probably won't be supported ever).
+ validInnerComponents: [
+ 'Text',
+ 'Icon'
+ ],
+ // Default rules, used as "default theme", essentially.
+ defaultRules: [
+ {
+ component: 'Root',
+ directives: {
+ '--buttonDefaultHoverGlow': 'shadow | 0 0 4 --text / 0.5',
+ '--buttonDefaultFocusGlow': 'shadow | 0 0 4 4 --link / 0.5',
+ '--buttonDefaultShadow': 'shadow | 0 0 2 #000000',
+ '--buttonDefaultBevel': 'shadow | $borderSide(#FFFFFF top 0.2 2), $borderSide(#000000 bottom 0.2 2)',
+ '--buttonPressedBevel': 'shadow | $borderSide(#FFFFFF bottom 0.2 2), $borderSide(#000000 top 0.2 2)'
+ }
+ },
+ {
+ // component: 'Button', // no need to specify components every time unless you're specifying how other component should look
+ // like within it
+ directives: {
+ background: '--fg',
+ shadow: ['--buttonDefaultShadow', '--buttonDefaultBevel'],
+ roundness: 3
+ }
+ },
+ {
+ state: ['hover'],
+ directives: {
+ shadow: ['--buttonDefaultHoverGlow', '--buttonDefaultBevel']
+ }
+ },
+ {
+ state: ['focused'],
+ directives: {
+ shadow: ['--buttonDefaultFocusGlow', '--buttonDefaultBevel']
+ }
+ },
+ {
+ state: ['pressed'],
+ directives: {
+ shadow: ['--buttonDefaultShadow', '--buttonPressedBevel']
+ }
+ },
+ {
+ state: ['pressed', 'hover'],
+ directives: {
+ shadow: ['--buttonPressedBevel', '--buttonDefaultHoverGlow']
+ }
+ },
+ {
+ state: ['toggled'],
+ directives: {
+ background: '--inheritedBackground,-14.2',
+ shadow: ['--buttonDefaultShadow', '--buttonPressedBevel']
+ }
+ },
+ {
+ state: ['toggled', 'hover'],
+ directives: {
+ background: '--inheritedBackground,-14.2',
+ shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel']
+ }
+ },
+ {
+ state: ['toggled', 'disabled'],
+ directives: {
+ background: '$blend(--inheritedBackground 0.25 --parent)',
+ shadow: ['--buttonPressedBevel']
+ }
+ },
+ {
+ state: ['disabled'],
+ directives: {
+ background: '$blend(--inheritedBackground 0.25 --parent)',
+ shadow: ['--buttonDefaultBevel']
+ }
+ },
+ {
+ component: 'Text',
+ parent: {
+ component: 'Button',
+ state: ['disabled']
+ },
+ directives: {
+ textOpacity: 0.25,
+ textOpacityMode: 'blend'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'Button',
+ state: ['disabled']
+ },
+ directives: {
+ textOpacity: 0.25,
+ textOpacityMode: 'blend'
+ }
+ }
+ ]
+}
diff --git a/src/components/button_unstyled.style.js b/src/components/button_unstyled.style.js
@@ -0,0 +1,97 @@
+export default {
+ name: 'ButtonUnstyled',
+ selector: '.button-unstyled',
+ notEditable: true,
+ states: {
+ toggled: '.toggled',
+ disabled: ':disabled',
+ hover: ':hover:not(:disabled)',
+ focused: ':focus-within'
+ },
+ validInnerComponents: [
+ 'Text',
+ 'Icon',
+ 'Badge'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '#ffffff',
+ opacity: 0,
+ shadow: []
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'ButtonUnstyled',
+ state: ['hover']
+ },
+ directives: {
+ textColor: '--parent--text'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'ButtonUnstyled',
+ state: ['toggled']
+ },
+ directives: {
+ textColor: '--parent--text'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'ButtonUnstyled',
+ state: ['toggled', 'hover']
+ },
+ directives: {
+ textColor: '--parent--text'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'ButtonUnstyled',
+ state: ['toggled', 'focused']
+ },
+ directives: {
+ textColor: '--parent--text'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'ButtonUnstyled',
+ state: ['toggled', 'focused', 'hover']
+ },
+ directives: {
+ textColor: '--parent--text'
+ }
+ },
+ {
+ component: 'Text',
+ parent: {
+ component: 'ButtonUnstyled',
+ state: ['disabled']
+ },
+ directives: {
+ textOpacity: 0.25,
+ textOpacityMode: 'blend'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'ButtonUnstyled',
+ state: ['disabled']
+ },
+ directives: {
+ textOpacity: 0.25,
+ textOpacityMode: 'blend'
+ }
+ }
+ ]
+}
diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js
@@ -57,6 +57,7 @@ const Chat = {
},
unmounted () {
window.removeEventListener('scroll', this.handleScroll)
+ window.removeEventListener('resize', this.handleResize)
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
this.$store.dispatch('clearCurrentChat')
},
@@ -107,7 +108,7 @@ const Chat = {
}
})
},
- '$route': function () {
+ $route: function () {
this.startFetching()
},
mastoUserSocketStatus (newValue) {
@@ -135,7 +136,7 @@ const Chat = {
},
// "Sticks" scroll to bottom instead of top, helps with OSK resizing the viewport
handleResize (opts = {}) {
- const { expand = false, delayed = false } = opts
+ const { delayed = false } = opts
if (delayed) {
setTimeout(() => {
@@ -146,10 +147,10 @@ const Chat = {
this.$nextTick(() => {
const { offsetHeight = undefined } = getScrollPosition()
- const diff = this.lastScrollPosition.offsetHeight - offsetHeight
- if (diff !== 0 || (!this.bottomedOut() && expand)) {
+ const diff = offsetHeight - this.lastScrollPosition.offsetHeight
+ if (diff !== 0 && !this.bottomedOut()) {
this.$nextTick(() => {
- window.scrollTo({ top: window.scrollY + diff })
+ window.scrollBy({ top: -Math.trunc(diff) })
})
}
this.lastScrollPosition = getScrollPosition()
@@ -187,6 +188,7 @@ const Chat = {
}, 5000)
},
handleScroll: _.throttle(function () {
+ this.lastScrollPosition = getScrollPosition()
if (!this.currentChat) { return }
if (this.reachedTop()) {
diff --git a/src/components/chat/chat.scss b/src/components/chat/chat.scss
@@ -11,15 +11,15 @@
.chat-view-body {
box-sizing: border-box;
- background-color: var(--chatBg, $fallback--bg);
display: flex;
flex-direction: column;
width: 100%;
overflow: visible;
min-height: calc(100vh - var(--navbar-height));
- margin: 0 0 0 0;
- border-radius: 10px 10px 0 0;
- border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0;
+ margin: 0;
+ border-radius: var(--roundness);
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
&::after {
border-radius: 0;
@@ -37,8 +37,6 @@
.footer {
position: sticky;
bottom: 0;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
z-index: 1;
}
@@ -61,12 +59,10 @@
position: absolute;
right: 1.3em;
top: -3.2em;
- background-color: $fallback--fg;
- background-color: var(--btn, $fallback--fg);
display: flex;
justify-content: center;
align-items: center;
- box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 1px 1px rgb(0 0 0 / 30%), 0 2px 4px rgb(0 0 0 / 30%);
z-index: 10;
transition: 0.35s all;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
@@ -79,12 +75,6 @@
visibility: visible;
}
- i {
- font-size: 1em;
- color: $fallback--text;
- color: var(--text, $fallback--text);
- }
-
.unread-message-count {
font-size: 0.8em;
left: 50%;
diff --git a/src/components/chat/chat.style.js b/src/components/chat/chat.style.js
@@ -0,0 +1,19 @@
+export default {
+ name: 'Chat',
+ selector: '.chat-message-list',
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Avatar',
+ 'ChatMessage'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg',
+ blur: '5px'
+ }
+ }
+ ]
+}
diff --git a/src/components/chat/chat.vue b/src/components/chat/chat.vue
@@ -26,7 +26,7 @@
</div>
</div>
<div
- class="message-list"
+ class="chat-message-list message-list"
:style="{ height: scrollableContainerHeight }"
>
<template v-if="!errorLoadingChat">
@@ -61,7 +61,7 @@
<FAIcon icon="chevron-down" />
<div
v-if="newMessageCount"
- class="badge badge-notification unread-chat-count unread-message-count"
+ class="badge -notification unread-chat-count unread-message-count"
>
{{ newMessageCount }}
</div>
@@ -95,6 +95,5 @@
<script src="./chat.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-@import './chat.scss';
+@import "./chat";
</style>
diff --git a/src/components/chat_list/chat_list.vue b/src/components/chat_list/chat_list.vue
@@ -7,9 +7,9 @@
class="chat-list panel panel-default"
>
<div class="panel-heading -sticky">
- <span class="title">
+ <h1 class="title">
{{ $t("chats.chats") }}
- </span>
+ </h1>
<button
class="button-default"
@click="newChat"
@@ -23,7 +23,7 @@
class="timeline"
>
<List :items="sortedChatList">
- <template v-slot:item="{item}">
+ <template #item="{item}">
<ChatListItem
:key="item.id"
:compact="false"
@@ -45,8 +45,6 @@
<script src="./chat_list.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.chat-list {
min-height: 25em;
margin-bottom: 0;
@@ -57,8 +55,7 @@
font-size: 1.2em;
display: flex;
justify-content: center;
- color: $fallback--text;
- color: var(--faint, $fallback--text);
+ color: var(--textFaint);
}
</style>
diff --git a/src/components/chat_list_item/chat_list_item.scss b/src/components/chat_list_item/chat_list_item.scss
@@ -1,8 +1,6 @@
.chat-list-item {
display: flex;
flex-direction: row;
- padding: 0.75em;
- height: 5em;
overflow: hidden;
box-sizing: border-box;
cursor: pointer;
@@ -11,11 +9,6 @@
outline: none;
}
- &:hover {
- background-color: var(--selectedPost, $fallback--lightBg);
- box-shadow: 0 0 3px 1px rgba(0, 0, 0, 0.1);
- }
-
.chat-list-item-left {
margin-right: 1em;
}
@@ -29,7 +22,7 @@
.heading {
width: 100%;
- display: inline-flex;
+ display: flex;
justify-content: space-between;
line-height: 1em;
}
@@ -47,18 +40,17 @@
}
.chat-preview {
- display: inline-flex;
+ display: flex;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin: 0.35em 0;
- color: $fallback--text;
- color: var(--faint, $fallback--text);
+ color: var(--textFaint);
width: 100%;
}
a {
- color: var(--faintLink, $fallback--link);
+ color: var(--linkFaint);
text-decoration: none;
pointer-events: none;
}
@@ -67,25 +59,19 @@
canvas {
display: none;
}
+
img {
visibility: visible;
}
}
- .Avatar {
- border-radius: $fallback--avatarAltRadius;
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
- }
-
.chat-preview-body {
--emoji-size: 1.4em;
+
+ padding-right: 1em;
}
.time-wrapper {
line-height: var(--post-line-height);
}
-
- .chat-preview-body {
- padding-right: 1em;
- }
}
diff --git a/src/components/chat_list_item/chat_list_item.vue b/src/components/chat_list_item/chat_list_item.vue
@@ -36,7 +36,7 @@
/>
<div
v-if="chat.unread > 0"
- class="badge badge-notification unread-chat-count"
+ class="badge -notification unread-chat-count"
>
{{ chat.unread }}
</div>
@@ -48,6 +48,5 @@
<script src="./chat_list_item.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-@import './chat_list_item.scss';
+@import "./chat_list_item";
</style>
diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js
@@ -6,7 +6,7 @@ import Gallery from '../gallery/gallery.vue'
import LinkPreview from '../link-preview/link-preview.vue'
import StatusContent from '../status_content/status_content.vue'
import ChatMessageDate from '../chat_message_date/chat_message_date.vue'
-import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
+import { defineAsyncComponent } from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes,
@@ -35,7 +35,8 @@ const ChatMessage = {
UserAvatar,
Gallery,
LinkPreview,
- ChatMessageDate
+ ChatMessageDate,
+ UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
},
computed: {
// Returns HH:MM (hours and minutes) in local time.
@@ -49,9 +50,6 @@ const ChatMessage = {
message () {
return this.chatViewItem.data
},
- userProfileLink () {
- return generateProfileLink(this.author.id, this.author.screen_name, this.$store.state.instance.restrictedNicknames)
- },
isMessage () {
return this.chatViewItem.type === 'message'
},
diff --git a/src/components/chat_message/chat_message.scss b/src/components/chat_message/chat_message.scss
@@ -1,12 +1,10 @@
-@import '../../_variables.scss';
-
.chat-message-wrapper {
-
&.hovered-message-chain {
.animated.Avatar {
canvas {
display: none;
}
+
img {
visibility: visible;
}
@@ -27,11 +25,6 @@
.menu-icon {
cursor: pointer;
-
- &:hover, .extra-button-popover.open & {
- color: $fallback--text;
- color: var(--text, $fallback--text);
- }
}
.popover {
@@ -54,39 +47,25 @@
width: 32px;
}
- .link-preview, .attachments {
+ .link-preview,
+ .attachments {
margin-bottom: 1em;
}
- .chat-message-inner {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- max-width: 80%;
- min-width: 10em;
- width: 100%;
-
- &.with-media {
- width: 100%;
-
- .status {
- width: 100%;
- }
- }
- }
-
.status {
- border-radius: $fallback--chatMessageRadius;
- border-radius: var(--chatMessageRadius, $fallback--chatMessageRadius);
+ background-color: var(--background);
+ color: var(--text);
+ border-radius: var(--roundness);
display: flex;
padding: 0.75em;
+ border: 1px solid var(--border);
}
.created-at {
position: relative;
float: right;
font-size: 0.8em;
- margin: -1em 0 -0.5em 0;
+ margin: -1em 0 -0.5em;
font-style: italic;
opacity: 0.8;
}
@@ -103,57 +82,34 @@
}
.pending {
- .status-content.media-body, .created-at {
+ .status-content.media-body,
+ .created-at {
color: var(--faint);
}
}
.error {
- .status-content.media-body, .created-at {
- color: $fallback--cRed;
- color: var(--badgeNotification, $fallback--cRed);
- }
- }
-
- .incoming {
- a {
- color: var(--chatMessageIncomingLink, $fallback--link);
- }
-
- .status {
- color: var(--chatMessageIncomingText, $fallback--text);
- background-color: var(--chatMessageIncomingBg, $fallback--bg);
- border: 1px solid var(--chatMessageIncomingBorder, --border);
- }
-
+ .status-content.media-body,
.created-at {
- a {
- color: var(--chatMessageIncomingText, $fallback--text);
- }
+ color: var(--badgeNotification);
}
+ }
- .chat-message-menu {
- left: 0.4rem;
- }
+ .chat-message-inner {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ max-width: 80%;
+ min-width: 10em;
+ width: 100%;
}
.outgoing {
display: flex;
- flex-direction: row;
- flex-wrap: wrap;
+ flex-flow: row wrap;
align-content: end;
justify-content: flex-end;
- a {
- color: var(--chatMessageOutgoingLink, $fallback--link);
- }
-
- .status {
- color: var(--chatMessageOutgoingText, $fallback--text);
- background-color: var(--chatMessageOutgoingBg, $fallback--lightBg);
- border: 1px solid var(--chatMessageOutgoingBorder, --lightBg);
- }
-
.chat-message-inner {
align-items: flex-end;
}
@@ -163,10 +119,23 @@
}
}
+ .incoming {
+ .chat-message-menu {
+ left: 0.4rem;
+ }
+ }
+
+ .chat-message-inner.with-media {
+ width: 100%;
+
+ .status {
+ width: 100%;
+ }
+ }
+
.visible {
opacity: 1;
}
-
}
.chat-message-date-separator {
@@ -174,6 +143,5 @@
margin: 1.4em 0;
font-size: 0.9em;
user-select: none;
- color: $fallback--text;
- color: var(--faintedText, $fallback--text);
+ color: var(--textFaint);
}
diff --git a/src/components/chat_message/chat_message.style.js b/src/components/chat_message/chat_message.style.js
@@ -0,0 +1,30 @@
+export default {
+ name: 'ChatMessage',
+ selector: '.chat-message',
+ variants: {
+ outgoing: '.outgoing'
+ },
+ validInnerComponents: [
+ 'Text',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'RichContent',
+ 'Attachment',
+ 'PollGraph'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg, 2',
+ backgroundNoCssColor: 'yes'
+ }
+ },
+ {
+ variant: 'outgoing',
+ directives: {
+ background: '--bg, 5'
+ }
+ }
+ ]
+}
diff --git a/src/components/chat_message/chat_message.vue b/src/components/chat_message/chat_message.vue
@@ -14,16 +14,16 @@
v-if="!isCurrentUser"
class="avatar-wrapper"
>
- <router-link
+ <UserPopover
v-if="chatViewItem.isHead"
- :to="userProfileLink"
+ :user-id="author.id"
>
<UserAvatar
:compact="true"
:better-shadow="betterShadow"
:user="author"
/>
- </router-link>
+ </UserPopover>
</div>
<div class="chat-message-inner">
<div
@@ -33,7 +33,7 @@
<div
class="media status"
:class="{ 'without-attachment': !hasAttachment, 'pending': chatViewItem.data.pending, 'error': chatViewItem.data.error }"
- style="position: relative"
+ style="position: relative;"
@mouseenter="hovered = true"
@mouseleave="hovered = false"
>
@@ -44,23 +44,23 @@
<Popover
trigger="click"
placement="top"
- :bound-to-selector="isCurrentUser ? '' : '.scrollable-message-list'"
+ bound-to-selector=".chat-view-inner"
:bound-to="{ x: 'container' }"
:margin="popoverMarginStyle"
@show="menuOpened = true"
@close="menuOpened = false"
>
- <template v-slot:content>
+ <template #content>
<div class="dropdown-menu">
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
@click="deleteMessage"
>
<FAIcon icon="times" /> {{ $t("chats.delete") }}
</button>
</div>
</template>
- <template v-slot:trigger>
+ <template #trigger>
<button
class="button-default menu-icon"
:title="$t('chats.more')"
@@ -75,7 +75,7 @@
:status="messageForStatusContent"
:full-content="true"
>
- <template v-slot:footer>
+ <template #footer>
<span
class="created-at"
>
@@ -96,8 +96,8 @@
</div>
</template>
-<script src="./chat_message.js" ></script>
+<script src="./chat_message.js"></script>
<style lang="scss">
-@import './chat_message.scss';
+@import "./chat_message";
</style>
diff --git a/src/components/chat_new/chat_new.scss b/src/components/chat_new/chat_new.scss
@@ -1,7 +1,7 @@
.chat-new {
.input-wrap {
display: flex;
- margin: 0.7em 0.5em 0.7em 0.5em;
+ margin: 0.7em 0.5em;
input {
width: 100%;
@@ -16,11 +16,6 @@
padding-bottom: 0.7rem;
}
- .basic-user-card:hover {
- cursor: pointer;
- background-color: var(--selectedPost, $fallback--lightBg);
- }
-
.go-back-button {
text-align: center;
line-height: 1;
diff --git a/src/components/chat_new/chat_new.vue b/src/components/chat_new/chat_new.vue
@@ -16,27 +16,29 @@
/>
</button>
</div>
- <div class="input-wrap">
- <div class="input-search">
- <FAIcon
- class="search-icon fa-scale-110 fa-old-padding"
- icon="search"
- />
+ <div class="panel-body">
+ <div class="input-wrap">
+ <div class="input-search">
+ <FAIcon
+ class="search-icon fa-scale-110 fa-old-padding"
+ icon="search"
+ />
+ </div>
+ <input
+ ref="search"
+ v-model="query"
+ class="input"
+ placeholder="Search people"
+ @input="onInput"
+ >
</div>
- <input
- ref="search"
- v-model="query"
- placeholder="Search people"
- @input="onInput"
- >
- </div>
- <div class="member-list">
- <div
- v-for="user in availableUsers"
- :key="user.id"
- class="member"
- >
- <div @click.capture.prevent="goToChat(user)">
+ <div class="member-list">
+ <div
+ v-for="user in availableUsers"
+ :key="user.id"
+ class="list-item"
+ @click.capture.prevent="goToChat(user)"
+ >
<BasicUserCard :user="user" />
</div>
</div>
@@ -46,6 +48,5 @@
<script src="./chat_new.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-@import './chat_new.scss';
+@import "./chat_new";
</style>
diff --git a/src/components/chat_title/chat_title.js b/src/components/chat_title/chat_title.js
@@ -1,12 +1,13 @@
-import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import UserAvatar from '../user_avatar/user_avatar.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
+import { defineAsyncComponent } from 'vue'
export default {
name: 'ChatTitle',
components: {
UserAvatar,
- RichContent
+ RichContent,
+ UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
},
props: [
'user', 'withAvatar'
@@ -18,10 +19,5 @@ export default {
htmlTitle () {
return this.user ? this.user.name_html : ''
}
- },
- methods: {
- getUserProfileLink (user) {
- return generateProfileLink(user.id, user.screen_name)
- }
}
}
diff --git a/src/components/chat_title/chat_title.vue b/src/components/chat_title/chat_title.vue
@@ -3,16 +3,16 @@
class="chat-title"
:title="title"
>
- <router-link
- class="avatar-container"
+ <UserPopover
v-if="withAvatar && user"
- :to="getUserProfileLink(user)"
+ class="avatar-container"
+ :user-id="user.id"
>
<UserAvatar
class="titlebar-avatar"
:user="user"
/>
- </router-link>
+ </UserPopover>
<RichContent
v-if="user"
class="username"
@@ -26,15 +26,13 @@
<script src="./chat_title.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.chat-title {
display: flex;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
- --emoji-size: 14px;
+ --emoji-size: 1em;
.username {
max-width: 100%;
@@ -54,8 +52,7 @@
margin-right: 0.5em;
height: 1.5em;
width: 1.5em;
- border-radius: $fallback--avatarAltRadius;
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
+ border-radius: var(--roundness);
&.animated::before {
display: none;
diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue
@@ -1,19 +1,33 @@
<template>
<label
class="checkbox"
- :class="{ disabled, indeterminate }"
+ :class="{ disabled, indeterminate, 'indeterminate-fix': indeterminateTransitionFix }"
>
+ <span
+ v-if="!!$slots.before"
+ class="label -before"
+ :class="{ faint: disabled }"
+ >
+ <slot name="before" />
+ </span>
<input
type="checkbox"
+ class="visible-for-screenreader-only"
:disabled="disabled"
:checked="modelValue"
:indeterminate="indeterminate"
@change="$emit('update:modelValue', $event.target.checked)"
>
- <i class="checkbox-indicator" />
+ <i
+ class="input -checkbox checkbox-indicator"
+ :aria-hidden="true"
+ :class="{ disabled }"
+ @transitionend.capture="onTransitionEnd"
+ />
<span
v-if="!!$slots.default"
- class="label"
+ class="label -after"
+ :class="{ faint: disabled }"
>
<slot />
</span>
@@ -22,43 +36,68 @@
<script>
export default {
- emits: ['update:modelValue'],
props: [
'modelValue',
'indeterminate',
'disabled'
- ]
+ ],
+ emits: ['update:modelValue'],
+ data: (vm) => ({
+ indeterminateTransitionFix: vm.indeterminate
+ }),
+ watch: {
+ indeterminate (e) {
+ if (e) {
+ this.indeterminateTransitionFix = true
+ }
+ }
+ },
+ methods: {
+ onTransitionEnd (e) {
+ if (!this.indeterminate) {
+ this.indeterminateTransitionFix = false
+ }
+ }
+ }
}
</script>
<style lang="scss">
-@import '../../_variables.scss';
+@import "../../mixins";
.checkbox {
position: relative;
display: inline-block;
min-height: 1.2em;
- &-indicator {
+ &-indicator,
+ & .label {
+ vertical-align: middle;
+ }
+
+ & > &-indicator {
+ /* Reset .input stuff */
+ padding: 0;
+ margin: 0;
position: relative;
- padding-left: 1.2em;
+ line-height: inherit;
+ display: inline-block;
+ width: 1.2em;
+ height: 1.2em;
+ box-shadow: none;
}
&-indicator::before {
position: absolute;
- right: 0;
- top: 0;
+ inset: 0;
display: block;
- content: '✓';
+ content: "✓";
transition: color 200ms;
width: 1.1em;
height: 1.1em;
- border-radius: $fallback--checkboxRadius;
- border-radius: var(--checkboxRadius, $fallback--checkboxRadius);
- box-shadow: 0px 0px 2px black inset;
- box-shadow: var(--inputShadow);
- background-color: $fallback--fg;
- background-color: var(--input, $fallback--fg);
+ border-radius: var(--roundness);
+ box-shadow: var(--shadow);
+ background-color: var(--background);
vertical-align: top;
text-align: center;
line-height: 1.1em;
@@ -68,35 +107,37 @@ export default {
box-sizing: border-box;
}
- &.disabled {
- .checkbox-indicator::before,
- .label {
- opacity: .5;
- }
- .label {
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+ .disabled {
+ .checkbox-indicator::before {
+ background-color: var(--background);
}
}
- input[type=checkbox] {
- display: none;
-
+ input[type="checkbox"] {
&:checked + .checkbox-indicator::before {
- color: $fallback--text;
- color: var(--inputText, $fallback--text);
+ color: var(--text);
}
&:indeterminate + .checkbox-indicator::before {
- content: '–';
- color: $fallback--text;
- color: var(--inputText, $fallback--text);
+ content: "–";
+ color: var(--text);
}
+ }
+ &.indeterminate-fix {
+ input[type="checkbox"] + .checkbox-indicator::before {
+ content: "–";
+ }
}
- & > span {
- margin-left: .5em;
+ & > .label {
+ &.-after {
+ margin-left: 0.5em;
+ }
+
+ &.-before {
+ margin-right: 0.5em;
+ }
}
}
</style>
diff --git a/src/components/color_input/color_input.scss b/src/components/color_input/color_input.scss
@@ -1,19 +1,23 @@
-@import '../../_variables.scss';
-
.color-input {
display: inline-flex;
+ .label {
+ flex: 1 1 auto;
+ }
+
+ .opt {
+ margin-right: 0.5em;
+ }
+
&-field.input {
display: inline-flex;
flex: 0 0 0;
max-width: 9em;
align-items: stretch;
- padding: .2em 8px;
input {
+ color: var(--text);
background: none;
- color: $fallback--lightText;
- color: var(--inputText, $fallback--lightText);
border: none;
padding: 0;
margin: 0;
@@ -23,46 +27,79 @@
min-width: 3em;
padding: 0;
}
+ }
+
+ .nativeColor {
+ cursor: pointer;
+ flex: 0 0 auto;
+ padding: 0;
- &.nativeColor {
- flex: 0 0 2em;
- min-width: 2em;
- align-self: center;
- height: 100%;
+ input {
+ appearance: none;
+ max-width: 0;
+ min-width: 0;
+ max-height: 0;
+ /* stylelint-disable-next-line declaration-no-important */
+ opacity: 0 !important;
}
}
+
.computedIndicator,
+ .validIndicator,
+ .invalidIndicator,
.transparentIndicator {
flex: 0 0 2em;
+ margin: 0.2em 0.5em;
min-width: 2em;
- align-self: center;
- height: 100%;
+ align-self: stretch;
+ min-height: 1.1em;
+ border-radius: var(--roundness);
+ }
+
+ .invalidIndicator {
+ background: transparent;
+ box-sizing: border-box;
+ border: 2px solid var(--cRed);
}
+
.transparentIndicator {
// forgot to install counter-strike source, ooops
- background-color: #FF00FF;
+ background-color: #f0f;
position: relative;
- &::before, &::after {
+
+ &::before,
+ &::after {
display: block;
- content: '';
- background-color: #000000;
+ content: "";
+ background-color: #000;
position: absolute;
height: 50%;
width: 50%;
}
+
&::after {
top: 0;
left: 0;
+ border-top-left-radius: var(--roundness);
}
+
&::before {
bottom: 0;
right: 0;
+ border-bottom-right-radius: var(--roundness);
}
}
- }
- .label {
- flex: 1 1 auto;
+ &.disabled,
+ &:disabled {
+ .nativeColor input,
+ .computedIndicator,
+ .validIndicator,
+ .invalidIndicator,
+ .transparentIndicator {
+ /* stylelint-disable-next-line declaration-no-important */
+ opacity: 0.25 !important;
+ }
+ }
}
-
}
diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue
@@ -6,50 +6,77 @@
<label
:for="name"
class="label"
+ :class="{ faint: !present || disabled }"
>
{{ label }}
</label>
<Checkbox
- v-if="typeof fallback !== 'undefined' && showOptionalTickbox"
+ v-if="typeof fallback !== 'undefined' && showOptionalCheckbox && !hideOptionalCheckbox"
:model-value="present"
:disabled="disabled"
class="opt"
- @update:modelValue="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
+ @update:modelValue="updateValue(typeof modelValue === 'undefined' ? fallback : undefined)"
/>
- <div class="input color-input-field">
+ <div
+ class="input color-input-field"
+ :class="{ disabled: !present || disabled }"
+ >
<input
:id="name + '-t'"
class="textColor unstyled"
+ :class="{ disabled: !present || disabled }"
type="text"
:value="modelValue || fallback"
:disabled="!present || disabled"
- @input="$emit('update:modelValue', $event.target.value)"
+ @input="updateValue($event.target.value)"
>
- <input
+ <div
v-if="validColor"
- :id="name"
- class="nativeColor unstyled"
- type="color"
- :value="modelValue || fallback"
- :disabled="!present || disabled"
- @input="$emit('update:modelValue', $event.target.value)"
- >
+ class="validIndicator"
+ :style="{backgroundColor: modelValue || fallback}"
+ />
<div
- v-if="transparentColor"
+ v-else-if="transparentColor"
class="transparentIndicator"
/>
<div
- v-if="computedColor"
+ v-else-if="computedColor"
class="computedIndicator"
:style="{backgroundColor: fallback}"
/>
+ <div
+ v-else
+ class="invalidIndicator"
+ />
+ <label class="nativeColor">
+ <FAIcon icon="eye-dropper" />
+ <input
+ :id="name"
+ class="unstyled"
+ type="color"
+ :value="modelValue || fallback"
+ :disabled="!present || disabled"
+ :class="{ disabled: !present || disabled }"
+ @input="updateValue($event.target.value)"
+ >
+ </label>
</div>
</div>
</template>
-<style lang="scss" src="./color_input.scss"></style>
<script>
import Checkbox from '../checkbox/checkbox.vue'
import { hex2rgb } from '../../services/color_convert/color_convert.js'
+import { throttle } from 'lodash'
+
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faEyeDropper
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faEyeDropper
+)
+
export default {
components: {
Checkbox
@@ -85,10 +112,16 @@ export default {
default: false
},
// Show "optional" tickbox, for when value might become mandatory
- showOptionalTickbox: {
+ showOptionalCheckbox: {
required: false,
type: Boolean,
default: true
+ },
+ // Force "optional" tickbox to hide
+ hideOptionalCheckbox: {
+ required: false,
+ type: Boolean,
+ default: false
}
},
emits: ['update:modelValue'],
@@ -103,17 +136,14 @@ export default {
return this.modelValue === 'transparent'
},
computedColor () {
- return this.modelValue && this.modelValue.startsWith('--')
+ return this.modelValue && (this.modelValue.startsWith('--') || this.modelValue.startsWith('$'))
}
+ },
+ methods: {
+ updateValue: throttle(function (value) {
+ this.$emit('update:modelValue', value)
+ }, 100)
}
}
</script>
-
-<style lang="scss">
-.color-control {
- input.text-input {
- max-width: 7em;
- flex: 1;
- }
-}
-</style>
+<style lang="scss" src="./color_input.scss"></style>
diff --git a/src/components/component_preview/component_preview.vue b/src/components/component_preview/component_preview.vue
@@ -0,0 +1,323 @@
+<template>
+ <div
+ class="ComponentPreview"
+ :class="{ '-shadow-controls': shadowControl }"
+ >
+ <!-- eslint-disable vue/no-v-html vue/no-v-text-v-html-on-component -->
+ <component
+ :is="'style'"
+ v-html="previewCss"
+ />
+ <!-- eslint-enable vue/no-v-html vue/no-v-text-v-html-on-component -->
+ <label
+ v-show="shadowControl"
+ role="heading"
+ class="header"
+ :class="{ faint: disabled }"
+ >
+ {{ $t('settings.style.shadows.offset') }}
+ </label>
+ <label
+ v-show="shadowControl && !hideControls"
+ class="x-shift-number"
+ >
+ {{ $t('settings.style.shadows.offset-x') }}
+ <input
+ :value="shadow?.x"
+ :disabled="disabled"
+ :class="{ disabled }"
+ class="input input-number"
+ type="number"
+ @input="e => updateProperty('x', e.target.value)"
+ >
+ </label>
+ <label
+ v-show="shadowControl && !hideControls"
+ class="y-shift-number"
+ >
+ {{ $t('settings.style.shadows.offset-y') }}
+ <input
+ :value="shadow?.y"
+ :disabled="disabled"
+ :class="{ disabled }"
+ class="input input-number"
+ type="number"
+ @input="e => updateProperty('y', e.target.value)"
+ >
+ </label>
+ <input
+ v-show="shadowControl && !hideControls"
+ :value="shadow?.x"
+ :disabled="disabled"
+ :class="{ disabled }"
+ class="input input-range x-shift-slider"
+ type="range"
+ max="20"
+ min="-20"
+ @input="e => updateProperty('x', e.target.value)"
+ >
+ <input
+ v-show="shadowControl && !hideControls"
+ :value="shadow?.y"
+ :disabled="disabled"
+ :class="{ disabled }"
+ class="input input-range y-shift-slider"
+ type="range"
+ max="20"
+ min="-20"
+ @input="e => updateProperty('y', e.target.value)"
+ >
+ <div
+ class="preview-window"
+ :class="{ '-light-grid': lightGrid }"
+ >
+ <div
+ class="preview-block"
+ :class="previewClass"
+ :style="style"
+ >
+ {{ $t('settings.style.themes3.editor.test_string') }}
+ </div>
+ <div
+ v-if="invalid"
+ class="invalid-container"
+ >
+ <div class="alert error invalid-label">
+ {{ $t('settings.style.themes3.editor.invalid') }}
+ </div>
+ </div>
+ </div>
+ <div class="assists">
+ <Checkbox
+ v-model="lightGrid"
+ name="lightGrid"
+ class="input-light-grid"
+ >
+ {{ $t('settings.style.shadows.light_grid') }}
+ </Checkbox>
+ <div class="style-control">
+ <label class="label">
+ {{ $t('settings.style.shadows.zoom') }}
+ </label>
+ <input
+ v-model="zoom"
+ class="input input-number y-shift-number"
+ type="number"
+ >
+ </div>
+ <ColorInput
+ v-if="!noColorControl"
+ v-model="colorOverride"
+ class="input-color-input"
+ fallback="#606060"
+ :label="$t('settings.style.shadows.color_override')"
+ />
+ </div>
+ </div>
+</template>
+
+<script>
+import Checkbox from 'src/components/checkbox/checkbox.vue'
+import ColorInput from 'src/components/color_input/color_input.vue'
+
+export default {
+ components: {
+ Checkbox,
+ ColorInput
+ },
+ props: [
+ 'shadow',
+ 'shadowControl',
+ 'previewClass',
+ 'previewStyle',
+ 'previewCss',
+ 'disabled',
+ 'invalid',
+ 'noColorControl'
+ ],
+ emits: ['update:shadow'],
+ data () {
+ return {
+ colorOverride: undefined,
+ lightGrid: false,
+ zoom: 100
+ }
+ },
+ computed: {
+ style () {
+ const result = [
+ this.previewStyle,
+ `zoom: ${this.zoom / 100}`
+ ]
+ if (this.colorOverride) result.push(`--background: ${this.colorOverride}`)
+ return result
+ },
+ hideControls () {
+ return typeof this.shadow === 'string'
+ }
+ },
+ methods: {
+ updateProperty (axis, value) {
+ this.$emit('update:shadow', { axis, value: Number(value) })
+ }
+ }
+}
+</script>
+<style lang="scss">
+.ComponentPreview {
+ display: grid;
+ grid-template-columns: 1em 1fr 1fr 1em;
+ grid-template-rows: 2em 1fr 1fr 1fr 1em 2em max-content;
+ grid-template-areas:
+ "header header header header "
+ "preview preview preview y-slide"
+ "preview preview preview y-slide"
+ "preview preview preview y-slide"
+ "x-slide x-slide x-slide . "
+ "x-num x-num y-num y-num "
+ "assists assists assists assists";
+ grid-gap: 0.5em;
+
+ &:not(.-shadow-controls) {
+ grid-template-areas:
+ "header header header header "
+ "preview preview preview y-slide"
+ "preview preview preview y-slide"
+ "preview preview preview y-slide"
+ "assists assists assists assists";
+ grid-template-rows: 2em 1fr 1fr 1fr max-content;
+ }
+
+ .header {
+ grid-area: header;
+ justify-self: center;
+ align-self: baseline;
+ line-height: 2;
+ }
+
+ .invalid-container {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ display: grid;
+ align-items: center;
+ justify-items: center;
+ background-color: rgba(100 0 0 / 50%);
+
+ .alert {
+ padding: 0.5em 1em;
+ }
+ }
+
+ .assists {
+ grid-area: assists;
+ display: grid;
+ grid-auto-flow: rows;
+ grid-auto-rows: 2em;
+ grid-gap: 0.5em;
+ }
+
+ .input-light-grid {
+ justify-self: center;
+ }
+
+ .input-number {
+ min-width: 2em;
+ }
+
+ .x-shift-number {
+ grid-area: x-num;
+ justify-self: right;
+ }
+
+ .y-shift-number {
+ grid-area: y-num;
+ justify-self: left;
+ }
+
+ .x-shift-number,
+ .y-shift-number {
+ input {
+ max-width: 4em;
+ }
+ }
+
+ .x-shift-slider {
+ grid-area: x-slide;
+ height: auto;
+ align-self: start;
+ min-width: 10em;
+ }
+
+ .y-shift-slider {
+ grid-area: y-slide;
+ writing-mode: vertical-lr;
+ justify-self: left;
+ min-height: 10em;
+ }
+
+ .x-shift-slider,
+ .y-shift-slider {
+ padding: 0;
+ }
+
+ .preview-window {
+ --__grid-color1: rgb(102 102 102);
+ --__grid-color2: rgb(153 153 153);
+ --__grid-color1-disabled: rgba(102 102 102 / 20%);
+ --__grid-color2-disabled: rgba(153 153 153 / 20%);
+
+ &.-light-grid {
+ --__grid-color1: rgb(205 205 205);
+ --__grid-color2: rgb(255 255 255);
+ --__grid-color1-disabled: rgba(205 205 205 / 20%);
+ --__grid-color2-disabled: rgba(255 255 255 / 20%);
+ }
+
+ position: relative;
+ grid-area: preview;
+ aspect-ratio: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 10em;
+ min-height: 10em;
+ background-color: var(--__grid-color2);
+ background-image:
+ linear-gradient(45deg, var(--__grid-color1) 25%, transparent 25%),
+ linear-gradient(-45deg, var(--__grid-color1) 25%, transparent 25%),
+ linear-gradient(45deg, transparent 75%, var(--__grid-color1) 75%),
+ linear-gradient(-45deg, transparent 75%, var(--__grid-color1) 75%);
+ background-size: 20px 20px;
+ background-position: 0 0, 0 10px, 10px -10px, -10px 0;
+ border-radius: var(--roundness);
+
+ &.disabled {
+ background-color: var(--__grid-color2-disabled);
+ background-image:
+ linear-gradient(45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
+ linear-gradient(-45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
+ linear-gradient(45deg, transparent 75%, var(--__grid-color1-disabled) 75%),
+ linear-gradient(-45deg, transparent 75%, var(--__grid-color1-disabled) 75%);
+ }
+
+ .preview-block {
+ background: var(--background, var(--bg));
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-width: 33%;
+ min-height: 33%;
+ max-width: 80%;
+ max-height: 80%;
+ border-width: 0;
+ border-style: solid;
+ border-color: var(--border);
+ border-radius: var(--roundness);
+ box-shadow: var(--shadow);
+ }
+ }
+}
+</style>
diff --git a/src/components/confirm_modal/confirm_modal.js b/src/components/confirm_modal/confirm_modal.js
@@ -0,0 +1,37 @@
+import DialogModal from '../dialog_modal/dialog_modal.vue'
+
+/**
+ * This component emits the following events:
+ * cancelled, emitted when the action should not be performed;
+ * accepted, emitted when the action should be performed;
+ *
+ * The caller should close this dialog after receiving any of the two events.
+ */
+const ConfirmModal = {
+ components: {
+ DialogModal
+ },
+ props: {
+ title: {
+ type: String
+ },
+ cancelText: {
+ type: String
+ },
+ confirmText: {
+ type: String
+ }
+ },
+ computed: {
+ },
+ methods: {
+ onCancel () {
+ this.$emit('cancelled')
+ },
+ onAccept () {
+ this.$emit('accepted')
+ }
+ }
+}
+
+export default ConfirmModal
diff --git a/src/components/confirm_modal/confirm_modal.vue b/src/components/confirm_modal/confirm_modal.vue
@@ -0,0 +1,29 @@
+<template>
+ <dialog-modal
+ v-body-scroll-lock="true"
+ class="confirm-modal"
+ :on-cancel="onCancel"
+ >
+ <template #header>
+ <span v-text="title" />
+ </template>
+
+ <slot />
+
+ <template #footer>
+ <button
+ class="btn button-default"
+ @click.prevent="onAccept"
+ v-text="confirmText"
+ />
+
+ <button
+ class="btn button-default"
+ @click.prevent="onCancel"
+ v-text="cancelText"
+ />
+ </template>
+ </dialog-modal>
+</template>
+
+<script src="./confirm_modal.js"></script>
diff --git a/src/components/contrast_ratio/contrast_ratio.vue b/src/components/contrast_ratio/contrast_ratio.vue
@@ -3,39 +3,62 @@
v-if="contrast"
class="contrast-ratio"
>
- <span
- :title="hint"
+ <span v-if="showRatio">
+ {{ contrast.text }}
+ </span>
+ <Tooltip
+ :text="hint"
class="rating"
>
<span v-if="contrast.aaa">
- <FAIcon icon="thumbs-up" />
+ <FAIcon
+ icon="thumbs-up"
+ :size="showRatio ? 'lg' : ''"
+ />
</span>
<span v-if="!contrast.aaa && contrast.aa">
- <FAIcon icon="adjust" />
+ <FAIcon
+ icon="adjust"
+ :size="showRatio ? 'lg' : ''"
+ />
</span>
<span v-if="!contrast.aaa && !contrast.aa">
- <FAIcon icon="exclamation-triangle" />
+ <FAIcon
+ icon="exclamation-triangle"
+ :size="showRatio ? 'lg' : ''"
+ />
</span>
- </span>
- <span
+ </Tooltip>
+ <Tooltip
v-if="contrast && large"
+ :text="hint_18pt"
class="rating"
- :title="hint_18pt"
>
<span v-if="contrast.laaa">
- <FAIcon icon="thumbs-up" />
+ <FAIcon
+ icon="thumbs-up"
+ :size="showRatio ? 'large' : ''"
+ />
</span>
<span v-if="!contrast.laaa && contrast.laa">
- <FAIcon icon="adjust" />
+ <FAIcon
+ icon="adjust"
+ :size="showRatio ? 'lg' : ''"
+ />
</span>
<span v-if="!contrast.laaa && !contrast.laa">
- <FAIcon icon="exclamation-triangle" />
+ <FAIcon
+ icon="exclamation-triangle"
+ :size="showRatio ? 'lg' : ''"
+ />
</span>
- </span>
+ </Tooltip>
</span>
</template>
<script>
+import Tooltip from 'src/components/tooltip/tooltip.vue'
+
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faAdjust,
@@ -50,6 +73,9 @@ library.add(
)
export default {
+ components: {
+ Tooltip
+ },
props: {
large: {
required: false,
@@ -62,6 +88,11 @@ export default {
required: false,
type: Object,
default: () => ({})
+ },
+ showRatio: {
+ required: false,
+ type: Boolean,
+ default: false
}
},
computed: {
@@ -87,9 +118,7 @@ export default {
.contrast-ratio {
display: flex;
justify-content: flex-end;
-
- margin-top: -4px;
- margin-bottom: 5px;
+ align-items: baseline;
.label {
margin-right: 1em;
@@ -97,7 +126,6 @@ export default {
.rating {
display: inline-block;
- text-align: center;
margin-left: 0.5em;
}
}
diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
@@ -1,6 +1,10 @@
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 QuickFilterSettings from '../quick_filter_settings/quick_filter_settings.vue'
+import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -52,7 +56,8 @@ const conversation = {
expanded: false,
threadDisplayStatusObject: {}, // id => 'showing' | 'hidden'
statusContentPropertiesObject: {},
- inlineDivePosition: null
+ inlineDivePosition: null,
+ loadStatusError: null
}
},
props: [
@@ -77,6 +82,9 @@ const conversation = {
const maxDepth = this.$store.getters.mergedConfig.maxDepthInThread - 2
return maxDepth >= 1 ? maxDepth : 1
},
+ streamingEnabled () {
+ return this.mergedConfig.useStreamingApi && this.mastoUserSocketStatus === WSConnectionStatus.JOINED
+ },
displayStyle () {
return this.$store.getters.mergedConfig.conversationDisplay
},
@@ -271,7 +279,7 @@ const conversation = {
result[irid] = result[irid] || []
result[irid].push({
name: `#${i}`,
- id: id
+ id
})
}
i++
@@ -339,11 +347,17 @@ const conversation = {
},
maybeHighlight () {
return this.isExpanded ? this.highlight : null
- }
+ },
+ ...mapGetters(['mergedConfig']),
+ ...mapState({
+ mastoUserSocketStatus: state => state.api.mastoUserSocketStatus
+ })
},
components: {
Status,
- ThreadTree
+ ThreadTree,
+ QuickFilterSettings,
+ QuickViewSettings
},
watch: {
statusId (newVal, oldVal) {
@@ -379,11 +393,15 @@ const conversation = {
this.setHighlight(this.originalStatusId)
})
} else {
+ this.loadStatusError = null
this.$store.state.api.backendInteractor.fetchStatus({ id: this.statusId })
.then((status) => {
this.$store.dispatch('addNewStatuses', { statuses: [status] })
this.fetchConversation()
})
+ .catch((error) => {
+ this.loadStatusError = error
+ })
}
},
getReplies (id) {
@@ -395,6 +413,11 @@ const conversation = {
setHighlight (id) {
if (!id) return
this.highlight = id
+
+ if (!this.streamingEnabled) {
+ this.$store.dispatch('fetchStatus', id)
+ }
+
this.$store.dispatch('fetchFavsAndRepeats', id)
this.$store.dispatch('fetchEmojiReactionsBy', id)
},
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
@@ -9,7 +9,9 @@
v-if="isExpanded"
class="panel-heading conversation-heading -sticky"
>
- <span class="title"> {{ $t('timeline.conversation') }} </span>
+ <h1 class="title">
+ {{ $t('timeline.conversation') }}
+ </h1>
<button
v-if="collapsable"
class="button-unstyled -link"
@@ -17,8 +19,38 @@
>
{{ $t('timeline.collapse') }}
</button>
+ <QuickFilterSettings
+ v-if="!collapsable"
+ :conversation="true"
+ class="rightside-button"
+ />
+ <QuickViewSettings
+ v-if="!collapsable"
+ :conversation="true"
+ class="rightside-button"
+ />
</div>
- <div class="conversation-body panel-body">
+ <div
+ v-if="isPage && !status"
+ class="conversation-body"
+ :class="{ 'panel-body': isExpanded }"
+ >
+ <p v-if="!loadStatusError">
+ <FAIcon
+ spin
+ icon="circle-notch"
+ />
+ {{ $t('status.loading') }}
+ </p>
+ <p v-else>
+ {{ $t('status.load_error', { error: loadStatusError }) }}
+ </p>
+ </div>
+ <div
+ v-else
+ class="conversation-body"
+ :class="{ 'panel-body': isExpanded }"
+ >
<div
v-if="isTreeView"
class="thread-body"
@@ -31,8 +63,8 @@
keypath="status.show_all_conversation_with_icon"
tag="button"
class="button-unstyled -link"
- @click.prevent="diveToTopLevel"
scope="global"
+ @click.prevent="diveToTopLevel"
>
<template #icon>
<FAIcon
@@ -50,7 +82,7 @@
v-if="shouldShowAncestors"
class="thread-ancestors"
>
- <div
+ <article
v-for="status in ancestorsOf(diveRoot)"
:key="status.id"
class="thread-ancestor"
@@ -120,7 +152,7 @@
</i18n-t>
</div>
</div>
- </div>
+ </article>
</div>
<thread-tree
v-for="status in showingTopLevel"
@@ -158,39 +190,42 @@
v-if="isLinearView"
class="thread-body"
>
- <status
- v-for="status in conversation"
- :key="status.id"
- ref="statusComponent"
- :inline-expanded="collapsable && isExpanded"
- :statusoid="status"
- :expandable="!isExpanded"
- :show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]"
- :focused="focused(status.id)"
- :in-conversation="isExpanded"
- :highlight="getHighlight()"
- :replies="getReplies(status.id)"
- :in-profile="inProfile"
- :profile-user-id="profileUserId"
- class="conversation-status status-fadein panel-body"
+ <article>
+ <status
+ v-for="status in conversation"
+ :key="status.id"
+ ref="statusComponent"
+ :inline-expanded="collapsable && isExpanded"
+ :statusoid="status"
+ :expandable="!isExpanded"
+ :show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]"
+ :focused="focused(status.id)"
+ :in-conversation="isExpanded"
+ :highlight="getHighlight()"
+ :replies="getReplies(status.id)"
+ :in-profile="inProfile"
+ :profile-user-id="profileUserId"
+ class="conversation-status status-fadein panel-body"
- :toggle-thread-display="toggleThreadDisplay"
- :thread-display-status="threadDisplayStatus"
- :show-thread-recursively="showThreadRecursively"
- :total-reply-count="totalReplyCount"
- :total-reply-depth="totalReplyDepth"
- :status-content-properties="statusContentProperties"
- :set-status-content-property="setStatusContentProperty"
- :toggle-status-content-property="toggleStatusContentProperty"
+ :toggle-thread-display="toggleThreadDisplay"
+ :thread-display-status="threadDisplayStatus"
+ :show-thread-recursively="showThreadRecursively"
+ :total-reply-count="totalReplyCount"
+ :total-reply-depth="totalReplyDepth"
+ :status-content-properties="statusContentProperties"
+ :set-status-content-property="setStatusContentProperty"
+ :toggle-status-content-property="toggleStatusContentProperty"
- @goto="setHighlight"
- @toggleExpanded="toggleExpanded"
- />
+ @goto="setHighlight"
+ @toggleExpanded="toggleExpanded"
+ />
+ </article>
</div>
</div>
</div>
<div
v-else
+ class="Conversation -hidden"
:style="hiddenStyle"
/>
</template>
@@ -198,17 +233,19 @@
<script src="./conversation.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.Conversation {
z-index: 1;
+ &.-hidden {
+ background: var(--__panel-background);
+ backdrop-filter: var(--__panel-backdrop-filter);
+ }
+
.conversation-dive-to-top-level-box {
- padding: var(--status-margin, $status-margin);
- border-bottom-width: 1px;
- border-bottom-style: solid;
- border-bottom-color: var(--border, $fallback--border);
+ padding: var(--status-margin);
+ border-bottom: 1px solid var(--border);
border-radius: 0;
+
/* Make the button stretch along the whole row */
display: flex;
align-items: stretch;
@@ -216,67 +253,82 @@
}
.thread-ancestors {
- margin-left: var(--status-margin, $status-margin);
- border-left: 2px solid var(--border, $fallback--border);
+ margin-left: var(--status-margin);
+ border-left: 2px solid var(--border);
}
- .thread-ancestor.-faded .StatusContent {
- --link: var(--faintLink);
- --text: var(--faint);
- color: var(--text);
+ .thread-ancestor.-faded .RichContent {
+ /* stylelint-disable declaration-no-important */
+ --text: var(--textFaint) !important;
+ --link: var(--linkFaint) !important;
+ --funtextGreentext: var(--funtextGreentextFaint) !important;
+ --funtextCyantext: var(--funtextCyantextFaint) !important;
+ /* stylelint-enable declaration-no-important */
}
.thread-ancestor-dive-box {
- padding-left: var(--status-margin, $status-margin);
- border-bottom-width: 1px;
- border-bottom-style: solid;
- border-bottom-color: var(--border, $fallback--border);
+ padding-left: var(--status-margin);
+ border-bottom: 1px solid var(--border);
border-radius: 0;
+
/* Make the button stretch along the whole row */
- &, &-inner {
+ &,
+ &-inner {
display: flex;
align-items: stretch;
flex-direction: column;
}
}
+
.thread-ancestor-dive-box-inner {
- padding: var(--status-margin, $status-margin);
+ padding: var(--status-margin);
}
.conversation-status {
- border-bottom-width: 1px;
- border-bottom-style: solid;
- border-bottom-color: var(--border, $fallback--border);
+ border-bottom: 1px solid var(--border);
border-radius: 0;
}
.thread-ancestor-has-other-replies .conversation-status,
+ &:last-child:not(.-expanded) .conversation-status,
+ &.-expanded .conversation-status:last-child,
.thread-ancestor:last-child .conversation-status,
.thread-ancestor:last-child .thread-ancestor-dive-box,
- &:last-child .conversation-status,
&.-expanded .thread-tree .conversation-status {
border-bottom: none;
}
.thread-ancestors + .thread-tree > .conversation-status {
- border-top-width: 1px;
- border-top-style: solid;
- border-top-color: var(--border, $fallback--border);
+ border-top: 1px solid var(--border);
}
/* expanded conversation in timeline */
&.status-fadein.-expanded .thread-body {
- border-left-width: 4px;
- border-left-style: solid;
- border-left-color: $fallback--cRed;
- border-left-color: var(--cRed, $fallback--cRed);
- border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
- border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
- border-bottom: 1px solid var(--border, $fallback--border);
+ border-left: 4px solid var(--cRed);
+ border-radius: var(--roundness);
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-bottom: 1px solid var(--border);
}
&.-expanded.status-fadein {
- margin: calc(var(--status-margin, $status-margin) / 2);
+ --___margin: calc(var(--status-margin) / 2);
+
+ background: var(--background);
+ margin: var(--___margin);
+
+ &::before {
+ z-index: -1;
+ content: "";
+ display: block;
+ position: absolute;
+ top: calc(var(--___margin) * -1);
+ bottom: calc(var(--___margin) * -1);
+ left: calc(var(--___margin) * -1);
+ right: calc(var(--___margin) * -1);
+ background: var(--background);
+ backdrop-filter: var(--__panel-backdrop-filter);
+ }
}
}
</style>
diff --git a/src/components/desktop_nav/desktop_nav.js b/src/components/desktop_nav/desktop_nav.js
@@ -1,4 +1,5 @@
import SearchBar from 'components/search_bar/search_bar.vue'
+import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faSignInAlt,
@@ -30,7 +31,8 @@ library.add(
export default {
components: {
- SearchBar
+ SearchBar,
+ ConfirmModal
},
data: () => ({
searchBarHidden: true,
@@ -40,50 +42,75 @@ export default {
window.CSS.supports('-moz-mask-size', 'contain') ||
window.CSS.supports('-ms-mask-size', 'contain') ||
window.CSS.supports('-o-mask-size', 'contain')
- )
+ ),
+ showingConfirmLogout: false
}),
computed: {
enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
logoStyle () {
return {
- 'visibility': this.enableMask ? 'hidden' : 'visible'
+ visibility: this.enableMask ? 'hidden' : 'visible'
}
},
logoMaskStyle () {
- return this.enableMask ? {
- 'mask-image': `url(${this.$store.state.instance.logo})`
- } : {
- 'background-color': this.enableMask ? '' : 'transparent'
- }
+ return this.enableMask
+ ? {
+ 'mask-image': `url(${this.$store.state.instance.logo})`
+ }
+ : {
+ 'background-color': this.enableMask ? '' : 'transparent'
+ }
},
logoBgStyle () {
return Object.assign({
- 'margin': `${this.$store.state.instance.logoMargin} 0`,
+ margin: `${this.$store.state.instance.logoMargin} 0`,
opacity: this.searchBarHidden ? 1 : 0
- }, this.enableMask ? {} : {
- 'background-color': this.enableMask ? '' : 'transparent'
- })
+ }, 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 }
+ privateMode () { return this.$store.state.instance.private },
+ shouldConfirmLogout () {
+ return this.$store.getters.mergedConfig.modalOnLogout
+ }
},
methods: {
scrollToTop () {
window.scrollTo(0, 0)
},
+ showConfirmLogout () {
+ this.showingConfirmLogout = true
+ },
+ hideConfirmLogout () {
+ this.showingConfirmLogout = false
+ },
logout () {
+ if (!this.shouldConfirmLogout) {
+ this.doLogout()
+ } else {
+ this.showConfirmLogout()
+ }
+ },
+ doLogout () {
this.$router.replace('/main/public')
this.$store.dispatch('logout')
+ this.hideConfirmLogout()
},
onSearchBarToggled (hidden) {
this.searchBarHidden = hidden
},
openSettingsModal () {
- this.$store.dispatch('openSettingsModal')
+ this.$store.dispatch('openSettingsModal', 'user')
+ },
+ openAdminModal () {
+ this.$store.dispatch('openSettingsModal', 'admin')
}
}
}
diff --git a/src/components/desktop_nav/desktop_nav.scss b/src/components/desktop_nav/desktop_nav.scss
@@ -1,14 +1,13 @@
-@import '../../_variables.scss';
-
.DesktopNav {
width: 100%;
+ z-index: var(--ZI_navbar);
input {
color: var(--inputTopbarText, var(--inputText));
}
a {
- color: var(--topBarLink, $fallback--link);
+ color: var(--link);
}
.inner-nav {
@@ -22,34 +21,38 @@
max-width: 980px;
}
+ &.-column-stretch .inner-nav {
+ --miniColumn: 25rem;
+ --maxiColumn: 45rem;
+ --columnGap: 1em;
+
+ max-width:
+ calc(
+ var(--sidebarColumnWidth, var(--miniColumn)) +
+ var(--contentColumnWidth, var(--maxiColumn)) +
+ var(--columnGap)
+ );
+ }
+
&.-logoLeft .inner-nav {
grid-template-columns: auto 2fr 2fr;
grid-template-areas: "logo sitename actions";
}
- .button-default {
- &, svg {
- color: $fallback--text;
- color: var(--btnTopBarText, $fallback--text);
- }
-
- &:active {
- background-color: $fallback--fg;
- background-color: var(--btnPressedTopBar, $fallback--fg);
- color: $fallback--text;
- color: var(--btnPressedTopBarText, $fallback--text);
- }
-
- &:disabled {
- color: $fallback--text;
- color: var(--btnDisabledTopBarText, $fallback--text);
- }
+ &.-column-stretch.-wide .inner-nav {
+ max-width:
+ calc(
+ var(--sidebarColumnWidth, var(--miniColumn)) +
+ var(--contentColumnWidth, var(--maxiColumn)) +
+ var(--notifsColumnWidth, var(--miniColumn)) +
+ var(--columnGap)
+ );
+ }
- &.toggled {
- color: $fallback--text;
- color: var(--btnToggledTopBarText, $fallback--text);
- background-color: $fallback--fg;
- background-color: var(--btnToggledTopBar, $fallback--fg)
+ .button-default {
+ &,
+ svg {
+ color: var(--text);
}
}
@@ -61,6 +64,7 @@
transition-duration: 100ms;
@media all and (min-width: 800px) {
+ /* stylelint-disable-next-line declaration-no-important */
opacity: 1 !important;
}
@@ -68,8 +72,7 @@
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
- background-color: $fallback--fg;
- background-color: var(--topBarText, $fallback--fg);
+ background-color: var(--text);
position: absolute;
top: 0;
bottom: 0;
@@ -90,8 +93,7 @@
text-align: center;
.svg-inline--fa {
- color: $fallback--link;
- color: var(--topBarLink, $fallback--link);
+ color: var(--link);
}
}
@@ -116,4 +118,8 @@
text-align: right;
}
}
+
+ .spacer {
+ width: 1em;
+ }
}
diff --git a/src/components/desktop_nav/desktop_nav.vue b/src/components/desktop_nav/desktop_nav.vue
@@ -20,6 +20,7 @@
class="logo"
:to="{ name: 'root' }"
:style="logoBgStyle"
+ :title="sitename"
>
<div
class="mask"
@@ -38,43 +39,55 @@
/>
<button
class="button-unstyled nav-icon"
+ :title="$t('nav.preferences')"
@click.stop="openSettingsModal"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="cog"
- :title="$t('nav.preferences')"
/>
</button>
- <a
+ <button
v-if="currentUser && currentUser.role === 'admin'"
- href="/pleroma/admin/#/login-pleroma"
- class="nav-icon"
+ class="button-unstyled nav-icon"
target="_blank"
- @click.stop
+ :title="$t('nav.administration')"
+ @click.stop="openAdminModal"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="tachometer-alt"
- :title="$t('nav.administration')"
/>
- </a>
+ </button>
+ <span class="spacer" />
<button
v-if="currentUser"
class="button-unstyled nav-icon"
- @click.prevent="logout"
+ :title="$t('login.logout')"
+ @click.stop.prevent="logout"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="sign-out-alt"
- :title="$t('login.logout')"
/>
</button>
</div>
</div>
+ <teleport to="#modal">
+ <confirm-modal
+ v-if="showingConfirmLogout"
+ :title="$t('login.logout_confirm_title')"
+ :confirm-text="$t('login.logout_confirm_accept_button')"
+ :cancel-text="$t('login.logout_confirm_cancel_button')"
+ @accepted="doLogout"
+ @cancelled="hideConfirmLogout"
+ >
+ {{ $t('login.logout_confirm') }}
+ </confirm-modal>
+ </teleport>
</nav>
</template>
<script src="./desktop_nav.js"></script>
diff --git a/src/components/dialog_modal/dialog_modal.vue b/src/components/dialog_modal/dialog_modal.vue
@@ -8,11 +8,11 @@
@click.stop=""
>
<div class="panel-heading dialog-modal-heading">
- <div class="title">
+ <h1 class="title">
<slot name="header" />
- </div>
+ </h1>
</div>
- <div class="dialog-modal-content">
+ <div class="panel-body dialog-modal-content">
<slot name="default" />
</div>
<div class="dialog-modal-footer user-interactions panel-footer">
@@ -25,8 +25,6 @@
<script src="./dialog_modal.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
// TODO: unify with other modals.
.dark-overlay {
&::before {
@@ -38,8 +36,8 @@
position: fixed;
right: 0;
top: 0;
- background: rgba(27,31,35,.5);
- z-index: 99;
+ background: rgb(27 31 35 / 50%);
+ z-index: 2000;
}
}
@@ -51,11 +49,9 @@
margin: 15vh auto;
position: fixed;
transform: translateX(-50%);
- z-index: 999;
+ z-index: 2001;
cursor: default;
display: block;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
.dialog-modal-heading {
.title {
@@ -65,25 +61,20 @@
.dialog-modal-content {
margin: 0;
- padding: 1rem 1rem;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
+ padding: 1rem;
white-space: normal;
}
.dialog-modal-footer {
margin: 0;
- padding: .5em .5em;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
- border-top: 1px solid $fallback--border;
- border-top: 1px solid var(--border, $fallback--border);
+ padding: 0.5em;
+ border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
button {
width: auto;
- margin-left: .5rem;
+ margin-left: 0.5rem;
}
}
}
diff --git a/src/components/domain_mute_card/domain_mute_card.vue b/src/components/domain_mute_card/domain_mute_card.vue
@@ -9,7 +9,7 @@
class="btn button-default"
>
{{ $t('domain_mute_card.unmute') }}
- <template v-slot:progress>
+ <template #progress>
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
@@ -19,7 +19,7 @@
class="btn button-default"
>
{{ $t('domain_mute_card.mute') }}
- <template v-slot:progress>
+ <template #progress>
{{ $t('domain_mute_card.mute_progress') }}
</template>
</ProgressButton>
diff --git a/src/components/edit_status_modal/edit_status_modal.js b/src/components/edit_status_modal/edit_status_modal.js
@@ -0,0 +1,75 @@
+import PostStatusForm from '../post_status_form/post_status_form.vue'
+import Modal from '../modal/modal.vue'
+import statusPosterService from '../../services/status_poster/status_poster.service.js'
+import get from 'lodash/get'
+
+const EditStatusModal = {
+ components: {
+ PostStatusForm,
+ Modal
+ },
+ data () {
+ return {
+ resettingForm: false
+ }
+ },
+ computed: {
+ isLoggedIn () {
+ return !!this.$store.state.users.currentUser
+ },
+ modalActivated () {
+ return this.$store.state.editStatus.modalActivated
+ },
+ isFormVisible () {
+ return this.isLoggedIn && !this.resettingForm && this.modalActivated
+ },
+ params () {
+ return this.$store.state.editStatus.params || {}
+ }
+ },
+ watch: {
+ params (newVal, oldVal) {
+ if (get(newVal, 'statusId') !== get(oldVal, 'statusId')) {
+ this.resettingForm = true
+ this.$nextTick(() => {
+ this.resettingForm = false
+ })
+ }
+ },
+ isFormVisible (val) {
+ if (val) {
+ this.$nextTick(() => this.$el && this.$el.querySelector('textarea').focus())
+ }
+ }
+ },
+ methods: {
+ doEditStatus ({ status, spoilerText, sensitive, media, contentType, poll }) {
+ const params = {
+ store: this.$store,
+ statusId: this.$store.state.editStatus.params.statusId,
+ status,
+ spoilerText,
+ sensitive,
+ poll,
+ media,
+ contentType
+ }
+
+ return statusPosterService.editStatus(params)
+ .then((data) => {
+ return data
+ })
+ .catch((err) => {
+ console.error('Error editing status', err)
+ return {
+ error: err.message
+ }
+ })
+ },
+ closeModal () {
+ this.$store.dispatch('closeEditStatusModal')
+ }
+ }
+}
+
+export default EditStatusModal
diff --git a/src/components/edit_status_modal/edit_status_modal.vue b/src/components/edit_status_modal/edit_status_modal.vue
@@ -0,0 +1,51 @@
+<template>
+ <Modal
+ v-if="isFormVisible"
+ class="edit-form-modal-view"
+ @backdropClicked="closeModal"
+ >
+ <div class="edit-form-modal-panel panel">
+ <div class="panel-heading">
+ <h1 class="title">
+ {{ $t('post_status.edit_status') }}
+ </h1>
+ </div>
+ <PostStatusForm
+ class="panel-body"
+ v-bind="params"
+ :post-handler="doEditStatus"
+ :disable-polls="true"
+ :disable-visibility-selector="true"
+ @posted="closeModal"
+ />
+ </div>
+ </Modal>
+</template>
+
+<script src="./edit_status_modal.js"></script>
+
+<style lang="scss">
+.modal-view.edit-form-modal-view {
+ align-items: flex-start;
+}
+
+.edit-form-modal-panel {
+ flex-shrink: 0;
+ margin-top: 25%;
+ margin-bottom: 2em;
+ width: 100%;
+ max-width: 700px;
+
+ @media (orientation: landscape) {
+ margin-top: 8%;
+ }
+
+ .form-bottom-left {
+ max-width: 6.5em;
+
+ .emoji-icon {
+ justify-content: right;
+ }
+ }
+}
+</style>
diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
@@ -1,8 +1,12 @@
import Completion from '../../services/completion/completion.js'
+import genRandomSeed from '../../services/random_seed/random_seed.service.js'
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
+import 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
@@ -107,47 +111,134 @@ const EmojiInput = {
},
data () {
return {
+ randomSeed: genRandomSeed(),
input: undefined,
- highlighted: 0,
+ caretEl: undefined,
+ highlighted: -1,
caret: 0,
focused: false,
blurTimeout: null,
- showPicker: false,
temporarilyHideSuggestions: false,
- keepOpen: false,
disableClickOutside: false,
- suggestions: []
+ suggestions: [],
+ overlayStyle: {},
+ pickerShown: false
}
},
components: {
- EmojiPicker
+ Popover,
+ EmojiPicker,
+ UnicodeDomainIndicator,
+ ScreenReaderNotice
},
computed: {
padEmoji () {
return this.$store.getters.mergedConfig.padEmoji
},
+ defaultCandidateIndex () {
+ return this.$store.getters.mergedConfig.autocompleteSelect ? 0 : -1
+ },
+ preText () {
+ return this.modelValue.slice(0, this.caret)
+ },
+ postText () {
+ return this.modelValue.slice(this.caret)
+ },
showSuggestions () {
return this.focused &&
this.suggestions &&
this.suggestions.length > 0 &&
- !this.showPicker &&
+ !this.pickerShown &&
!this.temporarilyHideSuggestions
},
textAtCaret () {
- return (this.wordAtCaret || {}).word || ''
+ return this.wordAtCaret?.word
},
wordAtCaret () {
if (this.modelValue && this.caret) {
const word = Completion.wordAtPosition(this.modelValue, this.caret - 1) || {}
return word
}
+ },
+ languages () {
+ return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
+ },
+ maybeLocalizedEmojiNamesAndKeywords () {
+ return emoji => {
+ const names = [emoji.displayText]
+ const keywords = []
+
+ if (emoji.displayTextI18n) {
+ names.push(this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args))
+ }
+
+ if (emoji.annotations) {
+ this.languages.forEach(lang => {
+ names.push(emoji.annotations[lang]?.name)
+
+ keywords.push(...(emoji.annotations[lang]?.keywords || []))
+ })
+ }
+
+ return {
+ names: names.filter(k => k),
+ keywords: keywords.filter(k => k)
+ }
+ }
+ },
+ maybeLocalizedEmojiName () {
+ return emoji => {
+ if (!emoji.annotations) {
+ return emoji.displayText
+ }
+
+ if (emoji.displayTextI18n) {
+ return this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args)
+ }
+
+ for (const lang of this.languages) {
+ if (emoji.annotations[lang]?.name) {
+ return emoji.annotations[lang].name
+ }
+ }
+
+ return emoji.displayText
+ }
+ },
+ onInputScroll () {
+ this.$refs.hiddenOverlay.scrollTo({
+ top: this.input.scrollTop,
+ left: this.input.scrollLeft
+ })
+ },
+ suggestionListId () {
+ return `suggestions-${this.randomSeed}`
+ },
+ suggestionItemId () {
+ return (index) => `suggestion-item-${index}-${this.randomSeed}`
}
},
mounted () {
- const { root } = this.$refs
+ const { root, hiddenOverlayCaret, suggestorPopover } = this.$refs
const input = root.querySelector('.emoji-input > input') || root.querySelector('.emoji-input > textarea')
if (!input) return
this.input = input
+ this.caretEl = hiddenOverlayCaret
+ if (suggestorPopover.setAnchorEl) {
+ suggestorPopover.setAnchorEl(this.caretEl) // unit test compat
+ this.$refs.picker.setAnchorEl(this.caretEl)
+ } else {
+ console.warn('setAnchorEl not found, are we in a unit test?')
+ }
+ const style = getComputedStyle(this.input)
+ this.overlayStyle.padding = style.padding
+ this.overlayStyle.border = style.border
+ this.overlayStyle.margin = style.margin
+ this.overlayStyle.lineHeight = style.lineHeight
+ this.overlayStyle.fontFamily = style.fontFamily
+ 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)
@@ -157,6 +248,7 @@ const EmojiInput = {
input.addEventListener('click', this.onClickInput)
input.addEventListener('transitionend', this.onTransition)
input.addEventListener('input', this.onInput)
+ input.addEventListener('scroll', this.onInputScroll)
},
unmounted () {
const { input } = this
@@ -169,46 +261,48 @@ const EmojiInput = {
input.removeEventListener('click', this.onClickInput)
input.removeEventListener('transitionend', this.onTransition)
input.removeEventListener('input', this.onInput)
+ input.removeEventListener('scroll', this.onInputScroll)
}
},
watch: {
- showSuggestions: function (newValue) {
+ showSuggestions: function (newValue, oldValue) {
this.$emit('shown', newValue)
+ if (newValue) {
+ this.$refs.suggestorPopover.showPopover()
+ } else {
+ this.$refs.suggestorPopover.hidePopover()
+ }
},
textAtCaret: async function (newWord) {
+ if (newWord === undefined) return
const firstchar = newWord.charAt(0)
- this.suggestions = []
- if (newWord === firstchar) return
- const matchedSuggestions = await this.suggest(newWord)
+ if (newWord === firstchar) {
+ this.suggestions = []
+ return
+ }
+ const matchedSuggestions = await this.suggest(newWord, this.maybeLocalizedEmojiNamesAndKeywords)
// Async: cancel if textAtCaret has changed during wait
- if (this.textAtCaret !== newWord) return
- if (matchedSuggestions.length <= 0) return
+ if (this.textAtCaret !== newWord || matchedSuggestions.length <= 0) {
+ this.suggestions = []
+ return
+ }
this.suggestions = take(matchedSuggestions, 5)
.map(({ imageUrl, ...rest }) => ({
...rest,
img: imageUrl || ''
}))
- },
- suggestions: {
- handler (newValue) {
- this.$nextTick(this.resize)
- },
- deep: true
+ this.highlighted = this.defaultCandidateIndex
+ this.$refs.screenReaderNotice.announce(
+ this.$tc('tool_tip.autocomplete_available',
+ this.suggestions.length,
+ { number: this.suggestions.length }))
}
},
methods: {
- focusPickerInput () {
- const pickerEl = this.$refs.picker.$el
- if (!pickerEl) return
- const pickerInput = pickerEl.querySelector('input')
- if (pickerInput) pickerInput.focus()
- },
triggerShowPicker () {
- this.showPicker = true
- this.$refs.picker.startEmojiLoad()
this.$nextTick(() => {
+ this.$refs.picker.showPicker()
this.scrollIntoView()
- this.focusPickerInput()
})
// This temporarily disables "click outside" handler
// since external trigger also means click originates
@@ -220,11 +314,12 @@ const EmojiInput = {
},
togglePicker () {
this.input.focus()
- this.showPicker = !this.showPicker
- if (this.showPicker) {
+ if (!this.pickerShown) {
this.scrollIntoView()
+ this.$refs.picker.showPicker()
this.$refs.picker.startEmojiLoad()
- this.$nextTick(this.focusPickerInput)
+ } else {
+ this.$refs.picker.hidePicker()
}
},
replace (replacement) {
@@ -261,7 +356,6 @@ const EmojiInput = {
spaceAfter,
after
].join('')
- this.keepOpen = keepOpen
this.$emit('update:modelValue', newValue)
const position = this.caret + (insertion + spaceAfter + spaceBefore).length
if (!keepOpen) {
@@ -298,30 +392,31 @@ const EmojiInput = {
},
cycleBackward (e) {
const len = this.suggestions.length || 0
- if (len > 1) {
- this.highlighted -= 1
- if (this.highlighted < 0) {
- this.highlighted = this.suggestions.length - 1
- }
+
+ this.highlighted -= 1
+ if (this.highlighted === -1) {
+ this.input.focus()
+ } else if (this.highlighted < -1) {
+ this.highlighted = len - 1
+ }
+ if (len > 0) {
e.preventDefault()
- } else {
- this.highlighted = 0
}
},
cycleForward (e) {
const len = this.suggestions.length || 0
- if (len > 1) {
- this.highlighted += 1
- if (this.highlighted >= len) {
- this.highlighted = 0
- }
+
+ this.highlighted += 1
+ if (this.highlighted >= len) {
+ this.highlighted = -1
+ this.input.focus()
+ }
+ if (len > 0) {
e.preventDefault()
- } else {
- this.highlighted = 0
}
},
scrollIntoView () {
- const rootRef = this.$refs['picker'].$el
+ 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
@@ -361,8 +456,11 @@ const EmojiInput = {
}
})
},
- onTransition (e) {
- this.resize()
+ onPickerShown () {
+ this.pickerShown = true
+ },
+ onPickerClosed () {
+ this.pickerShown = false
},
onBlur (e) {
// Clicking on any suggestion removes focus from autocomplete,
@@ -370,7 +468,6 @@ const EmojiInput = {
this.blurTimeout = setTimeout(() => {
this.focused = false
this.setCaret(e)
- this.resize()
}, 200)
},
onClick (e, suggestion) {
@@ -382,18 +479,13 @@ const EmojiInput = {
this.blurTimeout = null
}
- if (!this.keepOpen) {
- this.showPicker = false
- }
this.focused = true
this.setCaret(e)
- this.resize()
this.temporarilyHideSuggestions = false
},
onKeyUp (e) {
const { key } = e
this.setCaret(e)
- this.resize()
// Setting hider in keyUp to prevent suggestions from blinking
// when moving away from suggested spot
@@ -405,7 +497,6 @@ const EmojiInput = {
},
onPaste (e) {
this.setCaret(e)
- this.resize()
},
onKeyDown (e) {
const { ctrlKey, shiftKey, key } = e
@@ -450,58 +541,31 @@ const EmojiInput = {
this.input.focus()
}
}
-
- this.showPicker = false
- this.resize()
},
onInput (e) {
- this.showPicker = false
this.setCaret(e)
- this.resize()
this.$emit('update:modelValue', e.target.value)
},
- onClickInput (e) {
- this.showPicker = false
- },
- onClickOutside (e) {
- if (this.disableClickOutside) return
- this.showPicker = false
- },
onStickerUploaded (e) {
- this.showPicker = false
this.$emit('sticker-uploaded', e)
},
onStickerUploadFailed (e) {
- this.showPicker = false
this.$emit('sticker-upload-Failed', e)
},
setCaret ({ target: { selectionStart } }) {
this.caret = selectionStart
+ this.$nextTick(() => {
+ this.$refs.suggestorPopover.updateStyles()
+ })
},
resize () {
- const panel = this.$refs.panel
- if (!panel) return
- const picker = this.$refs.picker.$el
- const panelBody = this.$refs['panel-body']
- const { offsetHeight, offsetTop } = this.input
- const offsetBottom = offsetTop + offsetHeight
-
- this.setPlacement(panelBody, panel, offsetBottom)
- this.setPlacement(picker, picker, offsetBottom)
},
- setPlacement (container, target, offsetBottom) {
- if (!container || !target) return
-
- target.style.top = offsetBottom + 'px'
- target.style.bottom = 'auto'
-
- if (this.placement === 'top' || (this.placement === 'auto' && this.overflowsBottom(container))) {
- target.style.top = 'auto'
- target.style.bottom = this.input.offsetHeight + 'px'
+ autoCompleteItemLabel (suggestion) {
+ if (suggestion.user) {
+ return suggestion.displayText + ' ' + suggestion.detailText
+ } else {
+ return this.maybeLocalizedEmojiName(suggestion)
}
- },
- overflowsBottom (el) {
- return el.getBoundingClientRect().bottom > window.innerHeight
}
}
}
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
@@ -1,16 +1,40 @@
<template>
<div
ref="root"
- v-click-outside="onClickOutside"
- class="emoji-input"
+ class="input emoji-input"
:class="{ 'with-picker': !hideEmojiButton }"
>
- <slot />
+ <slot
+ :id="'textbox-' + randomSeed"
+ :aria-owns="suggestionListId"
+ aria-autocomplete="both"
+ :aria-expanded="showSuggestions"
+ :aria-activedescendant="(!showSuggestions || highlighted === -1) ? '' : suggestionItemId(highlighted)"
+ />
+ <!-- TODO: make the 'x' disappear if at the end maybe? -->
+ <div
+ ref="hiddenOverlay"
+ class="hidden-overlay"
+ :style="overlayStyle"
+ :aria-hidden="true"
+ >
+ <span>{{ preText }}</span>
+ <span
+ ref="hiddenOverlayCaret"
+ class="caret"
+ >x</span>
+ <span>{{ postText }}</span>
+ </div>
+ <screen-reader-notice
+ ref="screenReaderNotice"
+ aria-live="assertive"
+ />
<template v-if="enableEmojiPicker">
<button
v-if="!hideEmojiButton"
class="button-unstyled emoji-picker-icon"
type="button"
+ :title="$t('emoji.add_emoji')"
@click.prevent="togglePicker"
>
<FAIcon :icon="['far', 'smile-beam']" />
@@ -18,168 +42,186 @@
<EmojiPicker
v-if="enableEmojiPicker"
ref="picker"
- :class="{ hide: !showPicker }"
:enable-sticker-picker="enableStickerPicker"
class="emoji-picker-panel"
@emoji="insert"
@sticker-uploaded="onStickerUploaded"
@sticker-upload-failed="onStickerUploadFailed"
+ @show="onPickerShown"
+ @close="onPickerClosed"
/>
</template>
- <div
- ref="panel"
+ <Popover
+ ref="suggestorPopover"
class="autocomplete-panel"
- :class="{ hide: !showSuggestions }"
+ placement="bottom"
+ :trigger-attrs="{ 'aria-hidden': true }"
>
- <div
- ref="panel-body"
- class="autocomplete-panel-body"
- >
+ <template #content>
<div
- v-for="(suggestion, index) in suggestions"
- :key="index"
- class="autocomplete-item"
- :class="{ highlighted: index === highlighted }"
- @click.stop.prevent="onClick($event, suggestion)"
+ :id="suggestionListId"
+ ref="panel-body"
+ class="autocomplete-panel-body"
+ role="listbox"
>
- <span class="image">
- <img
- v-if="suggestion.img"
- :src="suggestion.img"
- >
- <span v-else>{{ suggestion.replacement }}</span>
- </span>
- <div class="label">
- <span class="displayText">{{ suggestion.displayText }}</span>
- <span class="detailText">{{ suggestion.detailText }}</span>
+ <div
+ v-for="(suggestion, index) in suggestions"
+ :id="suggestionItemId(index)"
+ :key="index"
+ class="menu-item autocomplete-item"
+ role="option"
+ :class="{ '-active': index === highlighted }"
+ :aria-label="autoCompleteItemLabel(suggestion)"
+ :aria-selected="index === highlighted"
+ @click.stop.prevent="onClick($event, suggestion)"
+ >
+ <span class="image">
+ <img
+ v-if="suggestion.img"
+ :src="suggestion.img"
+ >
+ <span v-else>{{ suggestion.replacement }}</span>
+ </span>
+ <div class="label">
+ <span
+ v-if="suggestion.user"
+ class="displayText"
+ >
+ {{ suggestion.displayText }}<UnicodeDomainIndicator
+ :user="suggestion.user"
+ :at="false"
+ />
+ </span>
+ <span
+ v-if="!suggestion.user"
+ class="displayText"
+ >
+ {{ maybeLocalizedEmojiName(suggestion) }}
+ </span>
+ <span class="detailText">{{ suggestion.detailText }}</span>
+ </div>
</div>
</div>
- </div>
- </div>
+ </template>
+ </Popover>
</div>
</template>
<script src="./emoji_input.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
-.emoji-input {
+.input.emoji-input {
+ padding: 0;
display: flex;
flex-direction: column;
position: relative;
- &.with-picker input {
- padding-right: 30px;
- }
-
.emoji-picker-icon {
position: absolute;
top: 0;
right: 0;
- margin: .2em .25em;
+ margin: 0.2em 0.25em;
font-size: 1.3em;
cursor: pointer;
line-height: 24px;
&:hover i {
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
}
+
.emoji-picker-panel {
position: absolute;
z-index: 20;
margin-top: 2px;
&.hide {
- display: none
+ display: none;
}
}
- .autocomplete {
- &-panel {
- position: absolute;
- z-index: 20;
- margin-top: 2px;
+ input,
+ textarea {
+ flex: 1 0 auto;
+ color: inherit;
+ /* stylelint-disable-next-line declaration-no-important */
+ background: none !important;
+ box-shadow: none;
+ border: none;
+ outline: none;
+ }
- &.hide {
- display: none
- }
+ &.with-picker input {
+ padding-right: 30px;
+ }
- &-body {
- margin: 0 0.5em 0 0.5em;
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
- box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5);
- box-shadow: var(--popupShadow);
- min-width: 75%;
- background-color: $fallback--bg;
- background-color: var(--popover, $fallback--bg);
- color: $fallback--link;
- color: var(--popoverText, $fallback--link);
- --faint: var(--popoverFaintText, $fallback--faint);
- --faintLink: var(--popoverFaintLink, $fallback--faint);
- --lightText: var(--popoverLightText, $fallback--lightText);
- --postLink: var(--popoverPostLink, $fallback--link);
- --postFaintLink: var(--popoverPostFaintLink, $fallback--link);
- --icon: var(--popoverIcon, $fallback--icon);
- }
+ .hidden-overlay {
+ opacity: 0;
+ pointer-events: none;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ left: 0;
+ overflow: hidden;
+
+ /* DEBUG STUFF */
+ color: red;
+
+ /* set opacity to non-zero to see the overlay */
+
+ .caret {
+ width: 0;
+ margin-right: calc(-1ch - 1px);
+ border: 1px solid red;
}
+ }
+}
- &-item {
- display: flex;
- cursor: pointer;
- padding: 0.2em 0.4em;
- border-bottom: 1px solid rgba(0, 0, 0, 0.4);
- height: 32px;
-
- .image {
- width: 32px;
- height: 32px;
- line-height: 32px;
- text-align: center;
- font-size: 32px;
-
- margin-right: 4px;
-
- img {
- width: 32px;
- height: 32px;
- object-fit: contain;
- }
+.autocomplete {
+ &-panel {
+ position: absolute;
+ }
+
+ &-item.menu-item {
+ display: flex;
+ padding-top: 0;
+ padding-bottom: 0;
+
+ .image {
+ width: calc(var(--__line-height) + var(--__vertical-gap) * 2);
+ height: calc(var(--__line-height) + var(--__vertical-gap) * 2);
+ line-height: var(--__line-height);
+ text-align: center;
+ margin-right: var(--__horizontal-gap);
+
+ img {
+ width: calc(var(--__line-height) + var(--__vertical-gap) * 2);
+ height: calc(var(--__line-height) + var(--__vertical-gap) * 2);
+ object-fit: contain;
}
- .label {
- display: flex;
- flex-direction: column;
- justify-content: center;
- margin: 0 0.1em 0 0.2em;
+ span {
+ font-size: var(--__line-height);
+ line-height: var(--__line-height);
+ }
+ }
- .displayText {
- line-height: 1.5;
- }
+ .label {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ margin: 0 0.1em 0 0.2em;
- .detailText {
- font-size: 9px;
- line-height: 9px;
- }
+ .displayText {
+ line-height: 1.5;
}
- &.highlighted {
- background-color: $fallback--fg;
- background-color: var(--selectedMenuPopover, $fallback--fg);
- color: var(--selectedMenuPopoverText, $fallback--text);
- --faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
- --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
+ .detailText {
+ font-size: 9px;
+ line-height: 9px;
}
}
}
-
- input, textarea {
- flex: 1 0 auto;
- }
}
</style>
diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js
@@ -2,7 +2,7 @@
* suggest - generates a suggestor function to be used by emoji-input
* data: object providing source information for specific types of suggestions:
* data.emoji - optional, an array of all emoji available i.e.
- * (state.instance.emoji + state.instance.customEmoji)
+ * (getters.standardEmojiList + state.instance.customEmoji)
* data.users - optional, an array of all known users
* updateUsersList - optional, a function to search and append to users
*
@@ -13,10 +13,10 @@
export default data => {
const emojiCurry = suggestEmoji(data.emoji)
const usersCurry = data.store && suggestUsers(data.store)
- return input => {
+ return (input, nameKeywordLocalizer) => {
const firstChar = input[0]
if (firstChar === ':' && data.emoji) {
- return emojiCurry(input)
+ return emojiCurry(input, nameKeywordLocalizer)
}
if (firstChar === '@' && usersCurry) {
return usersCurry(input)
@@ -25,34 +25,34 @@ export default data => {
}
}
-export const suggestEmoji = emojis => input => {
+export const suggestEmoji = emojis => (input, nameKeywordLocalizer) => {
const noPrefix = input.toLowerCase().substr(1)
return emojis
- .filter(({ displayText }) => displayText.toLowerCase().match(noPrefix))
- .sort((a, b) => {
- let aScore = 0
- let bScore = 0
+ .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
- aScore += a.displayText.toLowerCase() === noPrefix ? 200 : 0
- bScore += b.displayText.toLowerCase() === noPrefix ? 200 : 0
+ score += Math.max(...k.names.map(name => name.toLowerCase() === noPrefix ? 200 : 0), 0)
// Prioritize custom emoji a lot
- aScore += a.imageUrl ? 100 : 0
- bScore += b.imageUrl ? 100 : 0
+ score += k.imageUrl ? 100 : 0
// Prioritize prefix matches somewhat
- aScore += a.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
- bScore += b.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
+ score += Math.max(...k.names.map(kw => kw.toLowerCase().startsWith(noPrefix) ? 10 : 0), 0)
// Sort by length
- aScore -= a.displayText.length
- bScore -= b.displayText.length
+ score -= k.displayText.length
+ k.score = score
+ return k
+ })
+ .sort((a, b) => {
// Break ties alphabetically
const alphabetically = a.displayText > b.displayText ? 0.5 : -0.5
- return bScore - aScore + alphabetically
+ return b.score - a.score + alphabetically
})
}
@@ -94,8 +94,9 @@ export const suggestUsers = ({ dispatch, state }) => {
const newSuggestions = state.users.users.filter(
user =>
- user.screen_name.toLowerCase().startsWith(noPrefix) ||
- user.name.toLowerCase().startsWith(noPrefix)
+ 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
@@ -116,11 +117,12 @@ export const suggestUsers = ({ dispatch, state }) => {
return diff + nameAlphabetically + screenNameAlphabetically
/* eslint-disable camelcase */
- }).map(({ screen_name, screen_name_ui, name, profile_image_url_original }) => ({
- displayText: screen_name_ui,
- detailText: name,
- imageUrl: profile_image_url_original,
- replacement: '@' + screen_name + ' '
+ }).map((user) => ({
+ user,
+ displayText: user.screen_name_ui,
+ detailText: user.name,
+ imageUrl: user.profile_image_url_original,
+ replacement: '@' + user.screen_name + ' '
}))
/* eslint-enable camelcase */
diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js
@@ -1,33 +1,76 @@
import { defineAsyncComponent } from 'vue'
import Checkbox from '../checkbox/checkbox.vue'
+import Popover from 'src/components/popover/popover.vue'
+import StillImage from '../still-image/still-image.vue'
+import { ensureFinalFallback } from '../../i18n/languages.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faBoxOpen,
faStickyNote,
- faSmileBeam
+ faSmileBeam,
+ faSmile,
+ faUser,
+ faPaw,
+ faIceCream,
+ faBus,
+ faBasketballBall,
+ faLightbulb,
+ faCode,
+ faFlag
} from '@fortawesome/free-solid-svg-icons'
-import { trim } from 'lodash'
+import { debounce, trim, chunk } from 'lodash'
library.add(
faBoxOpen,
faStickyNote,
- faSmileBeam
+ faSmileBeam,
+ faSmile,
+ faUser,
+ faPaw,
+ faIceCream,
+ faBus,
+ faBasketballBall,
+ faLightbulb,
+ faCode,
+ faFlag
)
-// At widest, approximately 20 emoji are visible in a row,
-// loading 3 rows, could be overkill for narrow picker
-const LOAD_EMOJI_BY = 60
+const UNICODE_EMOJI_GROUP_ICON = {
+ 'smileys-and-emotion': 'smile',
+ 'people-and-body': 'user',
+ 'animals-and-nature': 'paw',
+ 'food-and-drink': 'ice-cream',
+ 'travel-and-places': 'bus',
+ activities: 'basketball-ball',
+ objects: 'lightbulb',
+ symbols: 'code',
+ flags: 'flag'
+}
-// When to start loading new batch emoji, in pixels
-const LOAD_EMOJI_MARGIN = 64
+const maybeLocalizedKeywords = (emoji, languages, nameLocalizer) => {
+ const res = [emoji.displayText, nameLocalizer(emoji)]
+ if (emoji.annotations) {
+ languages.forEach(lang => {
+ const keywords = emoji.annotations[lang]?.keywords || []
+ const name = emoji.annotations[lang]?.name
+ res.push(...(keywords.concat([name]).filter(k => k)))
+ })
+ }
+ return res
+}
-const filterByKeyword = (list, keyword = '') => {
+const filterByKeyword = (list, keyword = '', languages, nameLocalizer) => {
if (keyword === '') return list
const keywordLowercase = keyword.toLowerCase()
- let orderedEmojiList = []
+ const orderedEmojiList = []
for (const emoji of list) {
- const indexOfKeyword = emoji.displayText.toLowerCase().indexOf(keywordLowercase)
+ const indices = maybeLocalizedKeywords(emoji, languages, nameLocalizer)
+ .map(k => k.toLowerCase().indexOf(keywordLowercase))
+ .filter(k => k > -1)
+
+ const indexOfKeyword = indices.length ? Math.min(...indices) : -1
+
if (indexOfKeyword > -1) {
if (!Array.isArray(orderedEmojiList[indexOfKeyword])) {
orderedEmojiList[indexOfKeyword] = []
@@ -38,14 +81,35 @@ const filterByKeyword = (list, keyword = '') => {
return orderedEmojiList.flat()
}
+const getOffset = (elem) => {
+ const style = elem.style.transform
+ const res = /translateY\((\d+)px\)/.exec(style)
+ if (!res) { return 0 }
+ return res[1]
+}
+
+const toHeaderId = id => {
+ return id.replace(/^row-\d+-/, '')
+}
+
const EmojiPicker = {
props: {
enableStickerPicker: {
required: false,
type: Boolean,
+ default: true
+ },
+ hideCustomEmoji: {
+ required: false,
+ type: Boolean,
default: false
}
},
+ inject: {
+ popoversZLayer: {
+ default: ''
+ }
+ },
data () {
return {
keyword: '',
@@ -53,16 +117,62 @@ const EmojiPicker = {
showingStickers: false,
groupsScrolledClass: 'scrolled-top',
keepOpen: false,
- customEmojiBufferSlice: LOAD_EMOJI_BY,
customEmojiTimeout: null,
- customEmojiLoadAllConfirmed: false
+ hideCustomEmojiInPicker: false,
+ // Lazy-load only after the first time `showing` becomes true.
+ contentLoaded: false,
+ groupRefs: {},
+ emojiRefs: {},
+ filteredEmojiGroups: [],
+ emojiSize: 0,
+ width: 0
}
},
components: {
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
- Checkbox
+ Checkbox,
+ StillImage,
+ Popover
},
methods: {
+ updateEmojiSize () {
+ const css = window.getComputedStyle(this.$refs.popover.$el)
+ const emojiSize = css.getPropertyValue('--emojiSize')
+ const emojiSizeUnit = emojiSize.replace(/[0-9,.]+/, '')
+ const emojiSizeValue = Number(emojiSize.replace(/[^0-9,.]+/, ''))
+ const fontSize = css.getPropertyValue('font-size').replace(/[^0-9,.]+/, '')
+
+ let emojiSizeReal
+ if (emojiSizeUnit.endsWith('em')) {
+ emojiSizeReal = emojiSizeValue * fontSize
+ } else {
+ emojiSizeReal = emojiSizeValue
+ }
+
+ const fullEmojiSize = emojiSizeReal + (2 * 0.2 * fontSize)
+ this.emojiSize = fullEmojiSize
+ },
+ showPicker () {
+ this.$refs.popover.showPopover()
+ this.$nextTick(() => {
+ this.onShowing()
+ })
+ },
+ hidePicker () {
+ this.$refs.popover.hidePopover()
+ },
+ setAnchorEl (el) {
+ this.$refs.popover.setAnchorEl(el)
+ },
+ setGroupRef (name) {
+ return el => { this.groupRefs[name] = el }
+ },
+ onPopoverShown () {
+ this.$emit('show')
+ },
+ onPopoverClosed () {
+ this.$emit('close')
+ },
onStickerUploaded (e) {
this.$emit('sticker-uploaded', e)
},
@@ -71,23 +181,53 @@ const EmojiPicker = {
},
onEmoji (emoji) {
const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement
- this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
+ if (!this.keepOpen) {
+ this.$refs.popover.hidePopover()
+ }
+ this.$emit('emoji', { insertion: value, insertionUrl: emoji.imageUrl, keepOpen: this.keepOpen })
},
- onScroll (e) {
- const target = (e && e.target) || this.$refs['emoji-groups']
- this.updateScrolledClass(target)
- this.scrolledGroup(target)
- this.triggerLoadMore(target)
+ onScroll (startIndex, endIndex, visibleStartIndex, visibleEndIndex) {
+ const target = this.$refs['emoji-groups'].$el
+ this.scrolledGroup(target, visibleStartIndex, visibleEndIndex)
},
- highlight (key) {
- const ref = this.$refs['group-' + key]
- const top = ref.offsetTop
- this.setShowStickers(false)
- this.activeGroup = key
+ scrolledGroup (target, start, end) {
+ const top = target.scrollTop + 5
this.$nextTick(() => {
- this.$refs['emoji-groups'].scrollTop = top + 1
+ this.emojiItems.slice(start, end + 1).forEach(group => {
+ const headerId = toHeaderId(group.id)
+ const ref = this.groupRefs['group-' + group.id]
+ if (!ref) { return }
+ const elem = ref.$el.parentElement
+ if (!elem) { return }
+ if (elem && getOffset(elem) <= top) {
+ this.activeGroup = headerId
+ }
+ })
+ this.scrollHeader()
})
},
+ scrollHeader () {
+ // Scroll the active tab's header into view
+ const headerRef = this.groupRefs['group-header-' + this.activeGroup]
+ const left = headerRef.offsetLeft
+ const right = left + headerRef.offsetWidth
+ const headerCont = this.$refs.header
+ const currentScroll = headerCont.scrollLeft
+ const currentScrollRight = currentScroll + headerCont.clientWidth
+ const setScroll = s => { headerCont.scrollLeft = s }
+
+ const margin = 7 // .emoji-tabs-item: padding
+ if (left - margin < currentScroll) {
+ setScroll(left - margin)
+ } else if (right + margin > currentScrollRight) {
+ setScroll(right + margin - headerCont.clientWidth)
+ }
+ },
+ highlight (groupId) {
+ this.setShowStickers(false)
+ const indexInList = this.emojiItems.findIndex(k => k.id === groupId)
+ this.$refs['emoji-groups'].scrollToItem(indexInList)
+ },
updateScrolledClass (target) {
if (target.scrollTop <= 5) {
this.groupsScrolledClass = 'scrolled-top'
@@ -97,74 +237,75 @@ const EmojiPicker = {
this.groupsScrolledClass = 'scrolled-middle'
}
},
- triggerLoadMore (target) {
- const ref = this.$refs['group-end-custom']
- if (!ref) return
- const bottom = ref.offsetTop + ref.offsetHeight
-
- const scrollerBottom = target.scrollTop + target.clientHeight
- const scrollerTop = target.scrollTop
- const scrollerMax = target.scrollHeight
-
- // Loads more emoji when they come into view
- const approachingBottom = bottom - scrollerBottom < LOAD_EMOJI_MARGIN
- // Always load when at the very top in case there's no scroll space yet
- const atTop = scrollerTop < 5
- // Don't load when looking at unicode category or at the very bottom
- const bottomAboveViewport = bottom < scrollerTop || scrollerBottom === scrollerMax
- if (!bottomAboveViewport && (approachingBottom || atTop)) {
- this.loadEmoji()
- }
+ toggleStickers () {
+ this.showingStickers = !this.showingStickers
},
- scrolledGroup (target) {
- const top = target.scrollTop + 5
- this.$nextTick(() => {
- this.emojisView.forEach(group => {
- const ref = this.$refs['group-' + group.id]
- if (ref.offsetTop <= top) {
- this.activeGroup = group.id
- }
- })
- })
+ setShowStickers (value) {
+ this.showingStickers = value
},
- loadEmoji () {
- const allLoaded = this.customEmojiBuffer.length === this.filteredEmoji.length
-
- if (allLoaded) {
- return
- }
-
- this.customEmojiBufferSlice += LOAD_EMOJI_BY
+ filterByKeyword (list, keyword) {
+ return filterByKeyword(list, keyword, this.languages, this.maybeLocalizedEmojiName)
},
- startEmojiLoad (forceUpdate = false) {
- if (!forceUpdate) {
- this.keyword = ''
- }
+ onShowing () {
+ const oldContentLoaded = this.contentLoaded
+ this.updateEmojiSize()
+ this.recalculateItemPerRow()
this.$nextTick(() => {
- this.$refs['emoji-groups'].scrollTop = 0
+ this.$refs.search.focus()
})
- const bufferSize = this.customEmojiBuffer.length
- const bufferPrefilledAll = bufferSize === this.filteredEmoji.length
- if (bufferPrefilledAll && !forceUpdate) {
- return
+ this.contentLoaded = true
+ this.filteredEmojiGroups = this.getFilteredEmojiGroups()
+ if (!oldContentLoaded) {
+ this.$nextTick(() => {
+ if (this.defaultGroup) {
+ this.highlight(this.defaultGroup)
+ }
+ })
}
- this.customEmojiBufferSlice = LOAD_EMOJI_BY
},
- toggleStickers () {
- this.showingStickers = !this.showingStickers
+ getFilteredEmojiGroups () {
+ return this.allEmojiGroups
+ .map(group => ({
+ ...group,
+ emojis: this.filterByKeyword(group.emojis, trim(this.keyword))
+ }))
+ .filter(group => group.emojis.length > 0)
},
- setShowStickers (value) {
- this.showingStickers = value
+ recalculateItemPerRow () {
+ this.$nextTick(() => {
+ if (!this.$refs['emoji-groups']) {
+ return
+ }
+ this.width = this.$refs['emoji-groups'].$el.clientWidth
+ })
}
},
watch: {
keyword () {
- this.customEmojiLoadAllConfirmed = false
this.onScroll()
- this.startEmojiLoad(true)
+ this.debouncedHandleKeywordChange()
+ },
+ allCustomGroups () {
+ this.filteredEmojiGroups = this.getFilteredEmojiGroups()
}
},
computed: {
+ minItemSize () {
+ return this.emojiSize
+ },
+ // used to watch it
+ fontSize () {
+ this.$nextTick(() => {
+ this.updateEmojiSize()
+ })
+ return this.$store.getters.mergedConfig.fontSize
+ },
+ emojiHeight () {
+ return this.emojiSize
+ },
+ itemPerRow () {
+ return this.width ? Math.floor(this.width / this.emojiSize) : 6
+ },
activeGroupView () {
return this.showingStickers ? '' : this.activeGroup
},
@@ -174,39 +315,75 @@ const EmojiPicker = {
}
return 0
},
- filteredEmoji () {
- return filterByKeyword(
- this.$store.state.instance.customEmoji || [],
- trim(this.keyword)
- )
- },
- customEmojiBuffer () {
- return this.filteredEmoji.slice(0, this.customEmojiBufferSlice)
- },
- emojis () {
- const standardEmojis = this.$store.state.instance.emoji || []
- const customEmojis = this.customEmojiBuffer
-
- return [
- {
- id: 'custom',
- text: this.$t('emoji.custom'),
- icon: 'smile-beam',
- emojis: customEmojis
- },
- {
- id: 'standard',
- text: this.$t('emoji.unicode'),
- icon: 'box-open',
- emojis: filterByKeyword(standardEmojis, trim(this.keyword))
- }
- ]
+ allCustomGroups () {
+ if (this.hideCustomEmoji || this.hideCustomEmojiInPicker) {
+ return {}
+ }
+ const emojis = this.$store.getters.groupedCustomEmojis
+ if (emojis.unpacked) {
+ emojis.unpacked.text = this.$t('emoji.unpacked')
+ }
+ return emojis
},
- emojisView () {
- return this.emojis.filter(value => value.emojis.length > 0)
+ defaultGroup () {
+ return Object.keys(this.allCustomGroups)[0]
+ },
+ 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
+ }))
+ },
+ allEmojiGroups () {
+ return Object.entries(this.allCustomGroups)
+ .map(([_, v]) => v)
+ .concat(this.unicodeEmojiGroups)
},
stickerPickerEnabled () {
return (this.$store.state.instance.stickers || []).length !== 0
+ },
+ debouncedHandleKeywordChange () {
+ return debounce(() => {
+ this.filteredEmojiGroups = this.getFilteredEmojiGroups()
+ }, 500)
+ },
+ 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
+ })))
+ .reduce((a, c) => a.concat(c), [])
+ },
+ languages () {
+ return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
+ },
+ maybeLocalizedEmojiName () {
+ return emoji => {
+ if (!emoji.annotations) {
+ return emoji.displayText
+ }
+
+ if (emoji.displayTextI18n) {
+ return this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args)
+ }
+
+ for (const lang of this.languages) {
+ if (emoji.annotations[lang]?.name) {
+ return emoji.annotations[lang].name
+ }
+ }
+
+ return emoji.displayText
+ }
+ },
+ isInModal () {
+ return this.popoversZLayer === 'modals'
}
}
}
diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
@@ -1,98 +1,114 @@
-@import '../../_variables.scss';
-
.emoji-picker {
+ --__emoji-picker-header: 2.2em;
+
+ width: 25em;
+ max-width: calc(100vw - 20px); // popover gives 10px margin from window edge
display: flex;
flex-direction: column;
- position: absolute;
- right: 0;
- left: 0;
- margin: 0 !important;
- z-index: 100;
- background-color: $fallback--bg;
- background-color: var(--popover, $fallback--bg);
- color: $fallback--link;
- color: var(--popoverText, $fallback--link);
- --lightText: var(--popoverLightText, $fallback--faint);
- --faint: var(--popoverFaintText, $fallback--faint);
- --faintLink: var(--popoverFaintLink, $fallback--faint);
- --lightText: var(--popoverLightText, $fallback--lightText);
- --icon: var(--popoverIcon, $fallback--icon);
+
+ &-header-image {
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ width: var(--__emoji-picker-header);
+ max-width: var(--__emoji-picker-header);
+ height: var(--__emoji-picker-header);
+ max-height: var(--__emoji-picker-header);
+
+ .still-image {
+ width: var(--__emoji-picker-header);
+ max-width: var(--__emoji-picker-header);
+ height: var(--__emoji-picker-header);
+ max-height: var(--__emoji-picker-header);
+ object-fit: contain;
+
+ --_still_image-label-scale: 0.5;
+ }
+ }
.keep-open,
- .too-many-emoji {
- padding: 7px;
+ .too-many-emoji,
+ .hide-custom-emoji {
+ padding: 0.5em;
line-height: normal;
}
+ .hide-custom-emoji {
+ padding-top: 0;
+ }
+
.too-many-emoji {
display: flex;
flex-direction: column;
}
.keep-open-label {
- padding: 0 7px;
+ padding: 0 0.5em;
display: flex;
}
.heading {
display: flex;
- height: 32px;
- padding: 10px 7px 5px;
+ padding: 0.7em 0.5em 0;
}
.content {
display: flex;
flex-direction: column;
flex: 1 1 auto;
- min-height: 0px;
+ min-height: 0;
}
.emoji-tabs {
flex-grow: 1;
- }
-
- .emoji-groups {
- min-height: 200px;
+ display: flex;
+ flex-flow: row nowrap;
+ overflow-x: auto;
+ overflow-y: hidden;
}
.additional-tabs {
+ display: flex;
border-left: 1px solid;
- border-left-color: $fallback--icon;
- border-left-color: var(--icon, $fallback--icon);
- padding-left: 7px;
+ border-left-color: var(--border);
+ padding-left: 0.5em;
flex: 0 0 auto;
}
.additional-tabs,
.emoji-tabs {
- display: block;
- min-width: 0;
flex-basis: auto;
- flex-shrink: 1;
+ display: flex;
+ align-content: center;
+ scrollbar-width: thin;
&-item {
- padding: 0 7px;
+ padding: 0 0.5em;
cursor: pointer;
- font-size: 1.85em;
+ width: var(--__emoji-picker-header);
+ max-width: var(--__emoji-picker-header);
+ height: var(--__emoji-picker-header);
+ max-height: var(--__emoji-picker-header);
+ display: flex;
+ align-items: center;
+
+ .svg-inline--fa {
+ font-size: 1.85em;
+ }
&.disabled {
opacity: 0.5;
pointer-events: none;
}
- &.active {
- border-bottom: 4px solid;
-
- svg {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
- }
+ &.toggled {
+ border-bottom: 0.2em solid;
}
}
}
.sticker-picker {
- flex: 1 1 auto
+ flex: 1 1 auto;
}
.stickers,
@@ -113,7 +129,7 @@
.emoji {
&-search {
- padding: 5px;
+ padding: 0.3em;
flex: 0 0 auto;
input {
@@ -122,22 +138,28 @@
}
&-groups {
+ height: 100%;
+ min-height: 200px;
flex: 1 1 1px;
position: relative;
overflow: auto;
+ scrollbar-gutter: stable both-edges;
user-select: none;
- mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
- linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
- linear-gradient(to top, white, white);
+ mask:
+ linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
+ linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
+ linear-gradient(to top, white, white);
transition: mask-size 150ms;
mask-size: 100% 20px, 100% 20px, auto;
// Autoprefixed seem to ignore this one, and also syntax is different
- -webkit-mask-composite: xor;
+ mask-composite: xor;
mask-composite: exclude;
+
&.scrolled {
&-top {
mask-size: 100% 20px, 100% 0, auto;
}
+
&-bottom {
mask-size: 100% 0, 100% 20px, auto;
}
@@ -148,13 +170,13 @@
display: flex;
align-items: center;
flex-wrap: wrap;
- padding-left: 5px;
justify-content: left;
&-title {
font-size: 0.85em;
width: 100%;
margin: 0;
+ padding-left: 0.3em;
&.disabled {
display: none;
@@ -163,24 +185,30 @@
}
&-item {
- width: 32px;
- height: 32px;
+ width: var(--emoji-size);
+ height: var(--emoji-size);
box-sizing: border-box;
display: flex;
- font-size: 32px;
+ line-height: var(--emoji-size);
align-items: center;
justify-content: center;
- margin: 4px;
-
+ margin: 0.2em;
cursor: pointer;
- img {
+ .emoji-picker-emoji.-custom {
object-fit: contain;
- max-width: 100%;
- max-height: 100%;
+ width: var(--emoji-size);
+ max-width: var(--emoji-size);
+ height: var(--emoji-size);
+ max-height: var(--emoji-size);
+
+ --_still_image-label-scale: 0.5;
}
- }
+ .emoji-picker-emoji.-unicode {
+ font-size: 1.6em;
+ overflow: hidden;
+ }
+ }
}
-
}
diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue
@@ -1,105 +1,172 @@
<template>
- <div class="emoji-picker panel panel-default panel-body">
- <div class="heading">
- <span class="emoji-tabs">
+ <Popover
+ ref="popover"
+ trigger="click"
+ popover-class="emoji-picker popover-default"
+ :trigger-attrs="{ 'aria-hidden': true, tabindex: -1 }"
+ @show="onPopoverShown"
+ @close="onPopoverClosed"
+ >
+ <template #content>
+ <div class="heading">
+ <!--
+ Body scroll lock needs to be on every scrollable element on safari iOS.
+ Here we tell it to enable scrolling for this element.
+ See https://github.com/willmcpo/body-scroll-lock#vanilla-js
+ -->
<span
- v-for="group in emojis"
- :key="group.id"
- class="emoji-tabs-item"
- :class="{
- active: activeGroupView === group.id,
- disabled: group.emojis.length === 0
- }"
- :title="group.text"
- @click.prevent="highlight(group.id)"
+ ref="header"
+ v-body-scroll-lock="isInModal"
+ class="emoji-tabs"
>
- <FAIcon
- :icon="group.icon"
- fixed-width
- />
+ <span
+ v-for="group in filteredEmojiGroups"
+ :ref="setGroupRef('group-header-' + group.id)"
+ :key="group.id"
+ class="button-unstyled emoji-tabs-item"
+ :class="{
+ toggled: activeGroupView === group.id
+ }"
+ :title="group.text"
+ role="button"
+ @click.prevent="highlight(group.id)"
+ >
+ <span
+ v-if="group.image"
+ class="emoji-picker-header-image"
+ >
+ <still-image
+ :alt="group.text"
+ :src="group.image"
+ />
+ </span>
+ <FAIcon
+ v-else
+ :icon="group.icon"
+ fixed-width
+ />
+ </span>
</span>
- </span>
- <span
- v-if="stickerPickerEnabled"
- class="additional-tabs"
- >
<span
- class="stickers-tab-icon additional-tabs-item"
- :class="{active: showingStickers}"
- :title="$t('emoji.stickers')"
- @click.prevent="toggleStickers"
+ v-if="stickerPickerEnabled"
+ class="additional-tabs"
>
- <FAIcon
- icon="sticky-note"
- fixed-width
- />
+ <span
+ class="button-unstyled stickers-tab-icon additional-tabs-item"
+ :class="{toggled: showingStickers}"
+ :title="$t('emoji.stickers')"
+ @click.prevent="toggleStickers"
+ >
+ <FAIcon
+ icon="sticky-note"
+ fixed-width
+ />
+ </span>
</span>
- </span>
- </div>
- <div class="content">
+ </div>
<div
- class="emoji-content"
- :class="{hidden: showingStickers}"
+ v-if="contentLoaded"
+ class="content"
>
- <div class="emoji-search">
- <input
- v-model="keyword"
- type="text"
- class="form-control"
- :placeholder="$t('emoji.search_emoji')"
- @input="$event.target.composing = false"
- >
- </div>
<div
- ref="emoji-groups"
- class="emoji-groups"
- :class="groupsScrolledClass"
- @scroll="onScroll"
+ class="emoji-content"
+ :class="{hidden: showingStickers}"
>
+ <div class="emoji-search">
+ <input
+ ref="search"
+ v-model="keyword"
+ type="text"
+ class="input form-control"
+ :placeholder="$t('emoji.search_emoji')"
+ @input="$event.target.composing = false"
+ >
+ </div>
+ <!-- Enables scrolling for this element on safari iOS. See comments for header. -->
+ <DynamicScroller
+ ref="emoji-groups"
+ v-body-scroll-lock="isInModal"
+ class="emoji-groups"
+ :class="groupsScrolledClass"
+ :min-item-size="minItemSize"
+ :buffer="minItemSize"
+ :items="emojiItems"
+ :emit-update="true"
+ @update="onScroll"
+ @visible="recalculateItemPerRow"
+ @resize="recalculateItemPerRow"
+ >
+ <template #default="{ item: group, index, active }">
+ <DynamicScrollerItem
+ :ref="setGroupRef('group-' + group.id)"
+ :item="group"
+ :active="active"
+ :data-index="index"
+ :size-dependencies="[group.emojis.length]"
+ >
+ <div
+ class="emoji-group"
+ >
+ <h6
+ v-if="group.isFirstRow"
+ class="emoji-group-title"
+ >
+ {{ group.text }}
+ </h6>
+ <span
+ v-for="emoji in group.emojis"
+ :key="group.id + emoji.displayText"
+ :title="maybeLocalizedEmojiName(emoji)"
+ class="emoji-item"
+ role="button"
+ @click.stop.prevent="onEmoji(emoji)"
+ >
+ <span
+ v-if="!emoji.imageUrl"
+ class="emoji-picker-emoji -unicode"
+ >{{ emoji.replacement }}</span>
+ <still-image
+ v-else
+ class="emoji-picker-emoji -custom"
+ loading="lazy"
+ :alt="maybeLocalizedEmojiName(emoji)"
+ :src="emoji.imageUrl"
+ :data-emoji-name="group.id + emoji.displayText"
+ />
+ </span>
+ </div>
+ </DynamicScrollerItem>
+ </template>
+ </DynamicScroller>
+ <div class="keep-open">
+ <Checkbox v-model="keepOpen">
+ {{ $t('emoji.keep_open') }}
+ </Checkbox>
+ </div>
<div
- v-for="group in emojisView"
- :key="group.id"
- class="emoji-group"
+ v-if="!hideCustomEmoji"
+ class="hide-custom-emoji"
>
- <h6
- :ref="'group-' + group.id"
- class="emoji-group-title"
+ <Checkbox
+ v-model="hideCustomEmojiInPicker"
+ @change="onShowing"
>
- {{ group.text }}
- </h6>
- <span
- v-for="emoji in group.emojis"
- :key="group.id + emoji.displayText"
- :title="emoji.displayText"
- class="emoji-item"
- @click.stop.prevent="onEmoji(emoji)"
- >
- <span v-if="!emoji.imageUrl">{{ emoji.replacement }}</span>
- <img
- v-else
- :src="emoji.imageUrl"
- >
- </span>
- <span :ref="'group-end-' + group.id" />
+ {{ $t('emoji.hide_custom_emoji') }}
+ </Checkbox>
</div>
</div>
- <div class="keep-open">
- <Checkbox v-model="keepOpen">
- {{ $t('emoji.keep_open') }}
- </Checkbox>
+ <div
+ v-if="showingStickers"
+ class="stickers-content"
+ >
+ <sticker-picker
+ @uploaded="onStickerUploaded"
+ @upload-failed="onStickerUploadFailed"
+ />
</div>
</div>
- <div
- v-if="showingStickers"
- class="stickers-content"
- >
- <sticker-picker
- @uploaded="onStickerUploaded"
- @upload-failed="onStickerUploadFailed"
- />
- </div>
- </div>
- </div>
+ </template>
+ </Popover>
</template>
<script src="./emoji_picker.js"></script>
diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
@@ -1,5 +1,17 @@
import UserAvatar from '../user_avatar/user_avatar.vue'
import UserListPopover from '../user_list_popover/user_list_popover.vue'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faPlus,
+ faMinus,
+ faCheck
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faPlus,
+ faMinus,
+ faCheck
+)
const EMOJI_REACTION_COUNT_CUTOFF = 12
@@ -33,6 +45,9 @@ const EmojiReactions = {
},
loggedIn () {
return !!this.$store.state.users.currentUser
+ },
+ remoteInteractionLink () {
+ return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
}
},
methods: {
@@ -42,10 +57,10 @@ const EmojiReactions = {
reactedWith (emoji) {
return this.status.emoji_reactions.find(r => r.name === emoji).me
},
- fetchEmojiReactionsByIfMissing () {
+ async fetchEmojiReactionsByIfMissing () {
const hasNoAccounts = this.status.emoji_reactions.find(r => !r.accounts)
if (hasNoAccounts) {
- this.$store.dispatch('fetchEmojiReactionsBy', this.status.id)
+ return await this.$store.dispatch('fetchEmojiReactionsBy', this.status.id)
}
},
reactWith (emoji) {
@@ -54,14 +69,26 @@ const EmojiReactions = {
unreact (emoji) {
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
},
- emojiOnClick (emoji, event) {
+ async emojiOnClick (emoji, event) {
if (!this.loggedIn) return
+ await this.fetchEmojiReactionsByIfMissing()
if (this.reactedWith(emoji)) {
this.unreact(emoji)
} else {
this.reactWith(emoji)
}
+ },
+ counterTriggerAttrs (reaction) {
+ return {
+ class: [
+ 'btn',
+ 'button-default',
+ 'emoji-reaction-count-button',
+ { '-picked-reaction': this.reactedWith(reaction.name) }
+ ],
+ 'aria-label': this.$tc('status.reaction_count_label', reaction.count, { num: reaction.count })
+ }
}
}
}
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
@@ -1,20 +1,64 @@
<template>
- <div class="emoji-reactions">
- <UserListPopover
+ <div class="EmojiReactions">
+ <span
v-for="(reaction) in emojiReactions"
- :key="reaction.name"
- :users="accountsForEmoji[reaction.name]"
+ :key="reaction.url || reaction.name"
+ class="emoji-reaction-container btn-group"
>
- <button
+ <component
+ :is="loggedIn ? 'button' : 'a'"
+ v-bind="!loggedIn ? { href: remoteInteractionLink } : {}"
+ role="button"
class="emoji-reaction btn button-default"
- :class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
+ :class="{ '-picked-reaction': reactedWith(reaction.name) }"
+ :title="reaction.url ? reaction.name : undefined"
+ :aria-pressed="reactedWith(reaction.name)"
@click="emojiOnClick(reaction.name, $event)"
- @mouseenter="fetchEmojiReactionsByIfMissing()"
>
- <span class="reaction-emoji">{{ reaction.name }}</span>
- <span>{{ reaction.count }}</span>
- </button>
- </UserListPopover>
+ <span
+ class="reaction-emoji"
+ >
+ <img
+ v-if="reaction.url"
+ :src="reaction.url"
+ class="reaction-emoji-content"
+ width="1em"
+ >
+ <span
+ v-else
+ class="reaction-emoji reaction-emoji-content"
+ >{{ reaction.name }}</span>
+ </span>
+ <FALayers>
+ <FAIcon
+ v-if="reactedWith(reaction.name)"
+ class="active-marker"
+ transform="shrink-6 up-9"
+ icon="check"
+ />
+ <FAIcon
+ v-if="!reactedWith(reaction.name)"
+ class="focus-marker"
+ transform="shrink-6 up-9"
+ icon="plus"
+ />
+ <FAIcon
+ v-else
+ class="focus-marker"
+ transform="shrink-6 up-9"
+ icon="minus"
+ />
+ </FALayers>
+ </component>
+ <UserListPopover
+ :users="accountsForEmoji[reaction.name]"
+ class="emoji-reaction-popover"
+ :trigger-attrs="counterTriggerAttrs(reaction)"
+ @show="fetchEmojiReactionsByIfMissing()"
+ >
+ <span class="emoji-reaction-counts">{{ reaction.count }}</span>
+ </UserListPopover>
+ </span>
<a
v-if="tooManyReactions"
class="emoji-reaction-expand faint"
@@ -26,57 +70,130 @@
</div>
</template>
-<script src="./emoji_reactions.js" ></script>
+<script src="./emoji_reactions.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
+@import "../../mixins";
-.emoji-reactions {
+.EmojiReactions {
display: flex;
margin-top: 0.25em;
flex-wrap: wrap;
-}
-.emoji-reaction {
- padding: 0 0.5em;
- margin-right: 0.5em;
- margin-top: 0.5em;
- display: flex;
- align-items: center;
- justify-content: center;
- box-sizing: border-box;
- .reaction-emoji {
- width: 1.25em;
- margin-right: 0.25em;
- }
- &:focus {
- outline: none;
- }
+ --emoji-size: calc(var(--emojiSize, 1.25em) * var(--emojiReactionsScale, 1));
- &.not-clickable {
- cursor: default;
- &:hover {
- box-shadow: $fallback--buttonShadow;
- box-shadow: var(--buttonShadow);
+ .emoji-reaction-container {
+ display: flex;
+ align-items: stretch;
+ margin-top: 0.5em;
+ margin-right: 0.5em;
+
+ .emoji-reaction-popover {
+ padding: 0;
+
+ .emoji-reaction-count-button {
+ margin: 0;
+ height: 100%;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ box-sizing: border-box;
+ min-width: 2em;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+
+ &.-picked-reaction {
+ border: 1px solid var(--accent);
+ margin-right: -1px;
+ }
+ }
}
}
-}
-.emoji-reaction-expand {
- padding: 0 0.5em;
- margin-right: 0.5em;
- margin-top: 0.5em;
- display: flex;
- align-items: center;
- justify-content: center;
- &:hover {
- text-decoration: underline;
+ .emoji-reaction {
+ padding-left: 0.5em;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ margin: 0;
+
+ .reaction-emoji {
+ width: var(--emoji-size);
+ height: var(--emoji-size);
+ margin-right: 0.25em;
+ line-height: var(--emoji-size);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .reaction-emoji-content {
+ max-width: 100%;
+ max-height: 100%;
+ width: auto;
+ height: auto;
+ line-height: inherit;
+ overflow: hidden;
+ font-size: calc(var(--emoji-size) * 0.8);
+ margin: 0;
+ }
+
+ &:focus {
+ outline: none;
+ }
+
+ .svg-inline--fa {
+ color: var(--text);
+ }
+
+ &.-picked-reaction {
+ border: 1px solid var(--accent);
+ margin-left: -1px; // offset the border, can't use inset shadows either
+ margin-right: -1px;
+
+ .svg-inline--fa {
+ color: var(--accent);
+ }
+ }
+
+ @include unfocused-style {
+ .focus-marker {
+ visibility: hidden;
+ }
+
+ .active-marker {
+ visibility: visible;
+ }
+ }
+
+ @include focused-style {
+ .svg-inline--fa {
+ color: var(--accent);
+ }
+
+ .focus-marker {
+ visibility: visible;
+ }
+
+ .active-marker {
+ visibility: hidden;
+ }
+ }
}
-}
-.picked-reaction {
- border: 1px solid var(--accent, $fallback--link);
- margin-left: -1px; // offset the border, can't use inset shadows either
- margin-right: calc(0.5em - 1px);
-}
+ .emoji-reaction-expand {
+ padding: 0 0.5em;
+ margin-right: 0.5em;
+ margin-top: 0.5em;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
</style>
diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js
@@ -1,4 +1,7 @@
import Popover from '../popover/popover.vue'
+import genRandomSeed from '../../services/random_seed/random_seed.service.js'
+import ConfirmModal from '../confirm_modal/confirm_modal.vue'
+import StatusBookmarkFolderMenu from '../status_bookmark_folder_menu/status_bookmark_folder_menu.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEllipsisH,
@@ -6,7 +9,10 @@ import {
faEyeSlash,
faThumbtack,
faShareAlt,
- faExternalLinkAlt
+ faExternalLinkAlt,
+ faHistory,
+ faPlus,
+ faTimes
} from '@fortawesome/free-solid-svg-icons'
import {
faBookmark as faBookmarkReg,
@@ -21,19 +27,50 @@ library.add(
faThumbtack,
faShareAlt,
faExternalLinkAlt,
- faFlag
+ faFlag,
+ faHistory,
+ faPlus,
+ faTimes
)
const ExtraButtons = {
- props: [ 'status' ],
- components: { Popover },
+ props: ['status'],
+ components: {
+ Popover,
+ ConfirmModal,
+ StatusBookmarkFolderMenu
+ },
+ data () {
+ return {
+ expanded: false,
+ showingDeleteDialog: false,
+ randomSeed: genRandomSeed()
+ }
+ },
methods: {
+ onShow () {
+ this.expanded = true
+ },
+ onClose () {
+ this.expanded = false
+ },
deleteStatus () {
- const confirmed = window.confirm(this.$t('status.delete_confirm'))
- if (confirmed) {
- this.$store.dispatch('deleteStatus', { id: this.status.id })
+ if (this.shouldConfirmDelete) {
+ this.showDeleteStatusConfirmDialog()
+ } else {
+ this.doDeleteStatus()
}
},
+ doDeleteStatus () {
+ this.$store.dispatch('deleteStatus', { id: this.status.id })
+ this.hideDeleteStatusConfirmDialog()
+ },
+ showDeleteStatusConfirmDialog () {
+ this.showingDeleteDialog = true
+ },
+ hideDeleteStatusConfirmDialog () {
+ this.showingDeleteDialog = false
+ },
pinStatus () {
this.$store.dispatch('pinStatus', this.status.id)
.then(() => this.$emit('onSuccess'))
@@ -71,14 +108,32 @@ const ExtraButtons = {
},
reportStatus () {
this.$store.dispatch('openUserReportingModal', { userId: this.status.user.id, statusIds: [this.status.id] })
+ },
+ editStatus () {
+ this.$store.dispatch('fetchStatusSource', { id: this.status.id })
+ .then(data => this.$store.dispatch('openEditStatusModal', {
+ statusId: this.status.id,
+ subject: data.spoiler_text,
+ statusText: data.text,
+ statusIsSensitive: this.status.nsfw,
+ statusPoll: this.status.poll,
+ statusFiles: [...this.status.attachments],
+ visibility: this.status.visibility,
+ statusContentType: data.content_type
+ }))
+ },
+ showStatusHistory () {
+ const originalStatus = { ...this.status }
+ const stripFieldsList = ['attachments', 'created_at', 'emojis', 'text', 'raw_html', 'nsfw', 'poll', 'summary', 'summary_raw_html']
+ stripFieldsList.forEach(p => delete originalStatus[p])
+ this.$store.dispatch('openStatusHistoryModal', originalStatus)
}
},
computed: {
currentUser () { return this.$store.state.users.currentUser },
canDelete () {
if (!this.currentUser) { return }
- const superuser = this.currentUser.rights.moderator || this.currentUser.rights.admin
- return superuser || this.status.user.id === this.currentUser.id
+ return this.currentUser.privileges.includes('messages_delete') || this.status.user.id === this.currentUser.id
},
ownStatus () {
return this.status.user.id === this.currentUser.id
@@ -89,8 +144,30 @@ const ExtraButtons = {
canMute () {
return !!this.currentUser
},
+ canBookmark () {
+ return !!this.currentUser
+ },
+ bookmarkFolders () {
+ return this.$store.state.instance.pleromaBookmarkFoldersAvailable
+ },
statusLink () {
return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
+ },
+ isEdited () {
+ return this.status.edited_at !== null
+ },
+ editingAvailable () { return this.$store.state.instance.editingAvailable },
+ shouldConfirmDelete () {
+ return this.$store.getters.mergedConfig.modalOnDelete
+ },
+ triggerAttrs () {
+ return {
+ title: this.$t('status.more_actions'),
+ id: `popup-trigger-${this.randomSeed}`,
+ 'aria-controls': `popup-menu-${this.randomSeed}`,
+ 'aria-expanded': this.expanded,
+ 'aria-haspopup': 'menu'
+ }
}
}
}
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
@@ -2,16 +2,24 @@
<Popover
class="ExtraButtons"
trigger="click"
+ :trigger-attrs="triggerAttrs"
placement="top"
:offset="{ y: 5 }"
:bound-to="{ x: 'container' }"
remove-padding
+ @show="onShow"
+ @close="onClose"
>
- <template v-slot:content="{close}">
- <div class="dropdown-menu">
+ <template #content="{close}">
+ <div
+ :id="`popup-menu-${randomSeed}`"
+ class="dropdown-menu"
+ role="menu"
+ >
<button
v-if="canMute && !status.thread_muted"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="muteConversation"
>
<FAIcon
@@ -21,7 +29,8 @@
</button>
<button
v-if="canMute && status.thread_muted"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="unmuteConversation"
>
<FAIcon
@@ -31,7 +40,8 @@
</button>
<button
v-if="!status.pinned && canPin"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="pinStatus"
@click="close"
>
@@ -42,7 +52,8 @@
</button>
<button
v-if="status.pinned && canPin"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="unpinStatus"
@click="close"
>
@@ -51,31 +62,64 @@
icon="thumbtack"
/><span>{{ $t("status.unpin") }}</span>
</button>
+ <template v-if="canBookmark">
+ <button
+ v-if="!status.bookmarked"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
+ @click.prevent="bookmarkStatus"
+ @click="close"
+ >
+ <FAIcon
+ fixed-width
+ :icon="['far', 'bookmark']"
+ /><span>{{ $t("status.bookmark") }}</span>
+ </button>
+ <button
+ v-if="status.bookmarked"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
+ @click.prevent="unbookmarkStatus"
+ @click="close"
+ >
+ <FAIcon
+ fixed-width
+ icon="bookmark"
+ /><span>{{ $t("status.unbookmark") }}</span>
+ </button>
+ <StatusBookmarkFolderMenu
+ v-if="status.bookmarked && bookmarkFolders"
+ :status="status"
+ />
+ </template>
<button
- v-if="!status.bookmarked"
- class="button-default dropdown-item dropdown-item-icon"
- @click.prevent="bookmarkStatus"
+ v-if="ownStatus && editingAvailable"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
+ @click.prevent="editStatus"
@click="close"
>
<FAIcon
fixed-width
- :icon="['far', 'bookmark']"
- /><span>{{ $t("status.bookmark") }}</span>
+ icon="pen"
+ /><span>{{ $t("status.edit") }}</span>
</button>
<button
- v-if="status.bookmarked"
- class="button-default dropdown-item dropdown-item-icon"
- @click.prevent="unbookmarkStatus"
+ v-if="isEdited && editingAvailable"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
+ @click.prevent="showStatusHistory"
@click="close"
>
<FAIcon
fixed-width
- icon="bookmark"
- /><span>{{ $t("status.unbookmark") }}</span>
+ icon="history"
+ /><span>{{ $t("status.status_history") }}</span>
</button>
<button
v-if="canDelete"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="deleteStatus"
@click="close"
>
@@ -85,7 +129,8 @@
/><span>{{ $t("status.delete") }}</span>
</button>
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="copyLink"
@click="close"
>
@@ -96,7 +141,8 @@
</button>
<a
v-if="!status.is_local"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
title="Source"
:href="status.external_url"
target="_blank"
@@ -107,7 +153,8 @@
/><span>{{ $t("status.external_source") }}</span>
</a>
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="reportStatus"
@click="close"
>
@@ -118,36 +165,73 @@
</button>
</div>
</template>
- <template v-slot:trigger>
- <button class="button-unstyled popover-trigger">
- <FAIcon
- class="fa-scale-110 fa-old-padding"
- icon="ellipsis-h"
- />
- </button>
+ <template #trigger>
+ <span class="button-unstyled popover-trigger">
+ <FALayers class="fa-old-padding-layer">
+ <FAIcon
+ class="fa-scale-110 "
+ icon="ellipsis-h"
+ />
+ <FAIcon
+ v-show="!expanded"
+ class="focus-marker"
+ transform="shrink-6 up-8 right-16"
+ icon="plus"
+ />
+ <FAIcon
+ v-show="expanded"
+ class="focus-marker"
+ transform="shrink-6 up-8 right-16"
+ icon="times"
+ />
+ </FALayers>
+ </span>
+ <teleport to="#modal">
+ <ConfirmModal
+ v-if="showingDeleteDialog"
+ :title="$t('status.delete_confirm_title')"
+ :cancel-text="$t('status.delete_confirm_cancel_button')"
+ :confirm-text="$t('status.delete_confirm_accept_button')"
+ @cancelled="hideDeleteStatusConfirmDialog"
+ @accepted="doDeleteStatus"
+ >
+ {{ $t('status.delete_confirm') }}
+ </ConfirmModal>
+ </teleport>
</template>
</Popover>
</template>
-<script src="./extra_buttons.js" ></script>
+<script src="./extra_buttons.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
+@import "../../mixins";
.ExtraButtons {
- /* override of popover internal stuff */
- .popover-trigger-button {
- width: auto;
- }
-
.popover-trigger {
position: static;
padding: 10px;
margin: -10px;
&:hover .svg-inline--fa {
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
+ }
+ }
+
+ .popover-trigger-button {
+ /* override of popover internal stuff */
+ width: auto;
+
+ @include unfocused-style {
+ .focus-marker {
+ visibility: hidden;
+ }
+ }
+
+ @include focused-style {
+ .focus-marker {
+ visibility: visible;
+ }
}
}
}
diff --git a/src/components/extra_notifications/extra_notifications.js b/src/components/extra_notifications/extra_notifications.js
@@ -0,0 +1,48 @@
+import { mapGetters } from 'vuex'
+
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faUserPlus,
+ faComments,
+ faBullhorn
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faUserPlus,
+ faComments,
+ faBullhorn
+)
+
+const ExtraNotifications = {
+ computed: {
+ shouldShowChats () {
+ return this.mergedConfig.showExtraNotifications && this.mergedConfig.showChatsInExtraNotifications && this.unreadChatCount
+ },
+ shouldShowAnnouncements () {
+ return this.mergedConfig.showExtraNotifications && this.mergedConfig.showAnnouncementsInExtraNotifications && this.unreadAnnouncementCount
+ },
+ shouldShowFollowRequests () {
+ return this.mergedConfig.showExtraNotifications && this.mergedConfig.showFollowRequestsInExtraNotifications && this.followRequestCount
+ },
+ hasAnythingToShow () {
+ return this.shouldShowChats || this.shouldShowAnnouncements || this.shouldShowFollowRequests
+ },
+ shouldShowCustomizationTip () {
+ return this.mergedConfig.showExtraNotificationsTip && this.hasAnythingToShow
+ },
+ currentUser () {
+ return this.$store.state.users.currentUser
+ },
+ ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount', 'followRequestCount', 'mergedConfig'])
+ },
+ methods: {
+ openNotificationSettings () {
+ return this.$store.dispatch('openSettingsModalTab', 'notifications')
+ },
+ dismissConfigurationTip () {
+ return this.$store.dispatch('setOption', { name: 'showExtraNotificationsTip', value: false })
+ }
+ }
+}
+
+export default ExtraNotifications
diff --git a/src/components/extra_notifications/extra_notifications.vue b/src/components/extra_notifications/extra_notifications.vue
@@ -0,0 +1,111 @@
+<template>
+ <div class="ExtraNotifications">
+ <div
+ v-if="shouldShowChats"
+ class="notification unseen"
+ >
+ <div class="notification-overlay" />
+ <router-link
+ class="button-unstyled -link extra-notification"
+ :to="{ name: 'chats', params: { username: currentUser.screen_name } }"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 icon"
+ icon="comments"
+ />
+ {{ $tc('notifications.unread_chats', unreadChatCount, { num: unreadChatCount }) }}
+ </router-link>
+ </div>
+ <div
+ v-if="shouldShowAnnouncements"
+ class="notification unseen"
+ >
+ <div class="notification-overlay" />
+ <router-link
+ class="button-unstyled -link extra-notification"
+ :to="{ name: 'announcements' }"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 icon"
+ icon="bullhorn"
+ />
+ {{ $tc('notifications.unread_announcements', unreadAnnouncementCount, { num: unreadAnnouncementCount }) }}
+ </router-link>
+ </div>
+ <div
+ v-if="shouldShowFollowRequests"
+ class="notification unseen"
+ >
+ <div class="notification-overlay" />
+ <router-link
+ class="button-unstyled -link extra-notification"
+ :to="{ name: 'friend-requests' }"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 icon"
+ icon="user-plus"
+ />
+ {{ $tc('notifications.unread_follow_requests', followRequestCount, { num: followRequestCount }) }}
+ </router-link>
+ </div>
+ <i18n-t
+ v-if="shouldShowCustomizationTip"
+ tag="span"
+ class="notification tip extra-notification"
+ keypath="notifications.configuration_tip"
+ scope="global"
+ >
+ <template #theSettings>
+ <button
+ class="button-unstyled -link"
+ @click="openNotificationSettings"
+ >
+ {{ $t('notifications.configuration_tip_settings') }}
+ </button>
+ </template>
+ <template #dismiss>
+ <button
+ class="button-unstyled -link"
+ @click="dismissConfigurationTip"
+ >
+ {{ $t('notifications.configuration_tip_dismiss') }}
+ </button>
+ </template>
+ </i18n-t>
+ </div>
+</template>
+
+<script src="./extra_notifications.js" />
+
+<style lang="scss">
+.ExtraNotifications {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+
+ .notification {
+ width: 100%;
+ border-bottom: 1px solid;
+ border-color: var(--border);
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .extra-notification {
+ padding: 1em;
+ }
+
+ .icon {
+ margin-right: 0.5em;
+ }
+
+ .tip {
+ display: inline;
+ }
+}
+</style>
diff --git a/src/components/favorite_button/favorite_button.js b/src/components/favorite_button/favorite_button.js
@@ -1,13 +1,21 @@
import { mapGetters } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faStar } from '@fortawesome/free-solid-svg-icons'
+import {
+ faStar,
+ faPlus,
+ faMinus,
+ faCheck
+} from '@fortawesome/free-solid-svg-icons'
import {
faStar as faStarRegular
} from '@fortawesome/free-regular-svg-icons'
library.add(
faStar,
- faStarRegular
+ faStarRegular,
+ faPlus,
+ faMinus,
+ faCheck
)
const FavoriteButton = {
@@ -31,7 +39,10 @@ const FavoriteButton = {
}
},
computed: {
- ...mapGetters(['mergedConfig'])
+ ...mapGetters(['mergedConfig']),
+ remoteInteractionLink () {
+ return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
+ }
}
}
diff --git a/src/components/favorite_button/favorite_button.vue b/src/components/favorite_button/favorite_button.vue
@@ -7,19 +7,52 @@
:title="$t('tool_tip.favorite')"
@click.prevent="favorite()"
>
- <FAIcon
- class="fa-scale-110 fa-old-padding"
- :icon="[status.favorited ? 'fas' : 'far', 'star']"
- :spin="animated"
- />
+ <FALayers class="fa-scale-110 fa-old-padding-layer">
+ <FAIcon
+ class="fa-scale-110"
+ :icon="[status.favorited ? 'fas' : 'far', 'star']"
+ :spin="animated"
+ />
+ <FAIcon
+ v-if="status.favorited"
+ class="active-marker"
+ transform="shrink-6 up-9 right-12"
+ icon="check"
+ />
+ <FAIcon
+ v-if="!status.favorited"
+ class="focus-marker"
+ transform="shrink-6 up-9 right-12"
+ icon="plus"
+ />
+ <FAIcon
+ v-else
+ class="focus-marker"
+ transform="shrink-6 up-9 right-12"
+ icon="minus"
+ />
+ </FALayers>
</button>
- <span v-else>
- <FAIcon
- class="fa-scale-110 fa-old-padding"
- :title="$t('tool_tip.favorite')"
- :icon="['far', 'star']"
- />
- </span>
+ <a
+ v-else
+ class="button-unstyled interactive"
+ target="_blank"
+ role="button"
+ :title="$t('tool_tip.favorite')"
+ :href="remoteInteractionLink"
+ >
+ <FALayers class="fa-scale-110 fa-old-padding-layer">
+ <FAIcon
+ class="fa-scale-110"
+ :icon="['far', 'star']"
+ />
+ <FAIcon
+ class="focus-marker"
+ transform="shrink-6 up-9 right-12"
+ icon="plus"
+ />
+ </FALayers>
+ </a>
<span
v-if="!mergedConfig.hidePostStats && status.fave_num > 0"
class="action-counter"
@@ -29,10 +62,10 @@
</div>
</template>
-<script src="./favorite_button.js" ></script>
+<script src="./favorite_button.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
+@import "../../mixins";
.FavoriteButton {
display: flex;
@@ -54,8 +87,27 @@
&:hover .svg-inline--fa,
&.-favorited .svg-inline--fa {
- color: $fallback--cOrange;
- color: var(--cOrange, $fallback--cOrange);
+ color: var(--cOrange);
+ }
+
+ @include unfocused-style {
+ .focus-marker {
+ visibility: hidden;
+ }
+
+ .active-marker {
+ visibility: visible;
+ }
+ }
+
+ @include focused-style {
+ .focus-marker {
+ visibility: visible;
+ }
+
+ .active-marker {
+ visibility: hidden;
+ }
}
}
}
diff --git a/src/components/features_panel/features_panel.vue b/src/components/features_panel/features_panel.vue
@@ -2,9 +2,9 @@
<div class="features-panel">
<div class="panel panel-default base01-background">
<div class="panel-heading timeline-heading base02-background base04">
- <div class="title">
+ <h1 class="title">
{{ $t('features_panel.title') }}
- </div>
+ </h1>
</div>
<div class="panel-body features-panel">
<ul>
@@ -32,7 +32,7 @@
</div>
</template>
-<script src="./features_panel.js" ></script>
+<script src="./features_panel.js"></script>
<style lang="scss">
.features-panel li {
diff --git a/src/components/flash/flash.js b/src/components/flash/flash.js
@@ -11,7 +11,7 @@ library.add(
)
const Flash = {
- props: [ 'src' ],
+ props: ['src'],
data () {
return {
player: false, // can be true, "hidden", false. hidden = element exists
diff --git a/src/components/flash/flash.vue b/src/components/flash/flash.vue
@@ -42,7 +42,6 @@
<script src="./flash.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
.Flash {
display: inline-block;
width: 100%;
@@ -78,7 +77,7 @@
.hidden {
display: none;
- visibility: 'hidden';
+ visibility: "hidden";
}
}
</style>
diff --git a/src/components/follow_button/follow_button.js b/src/components/follow_button/follow_button.js
@@ -1,12 +1,20 @@
+import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
export default {
props: ['relationship', 'user', 'labelFollowing', 'buttonClass'],
+ components: {
+ ConfirmModal
+ },
data () {
return {
- inProgress: false
+ inProgress: false,
+ showingConfirmUnfollow: false
}
},
computed: {
+ shouldConfirmUnfollow () {
+ return this.$store.getters.mergedConfig.modalOnUnfollow
+ },
isPressed () {
return this.inProgress || this.relationship.following
},
@@ -35,6 +43,12 @@ export default {
}
},
methods: {
+ showConfirmUnfollow () {
+ this.showingConfirmUnfollow = true
+ },
+ hideConfirmUnfollow () {
+ this.showingConfirmUnfollow = false
+ },
onClick () {
this.relationship.following || this.relationship.requested ? this.unfollow() : this.follow()
},
@@ -45,12 +59,21 @@ export default {
})
},
unfollow () {
+ if (this.shouldConfirmUnfollow) {
+ this.showConfirmUnfollow()
+ } else {
+ this.doUnfollow()
+ }
+ },
+ doUnfollow () {
const store = this.$store
this.inProgress = true
requestUnfollow(this.relationship.id, store).then(() => {
this.inProgress = false
store.commit('removeStatus', { timeline: 'friends', userId: this.relationship.id })
})
+
+ this.hideConfirmUnfollow()
}
}
}
diff --git a/src/components/follow_button/follow_button.vue b/src/components/follow_button/follow_button.vue
@@ -7,6 +7,28 @@
@click="onClick"
>
{{ label }}
+ <teleport to="#modal">
+ <confirm-modal
+ v-if="showingConfirmUnfollow"
+ :title="$t('user_card.unfollow_confirm_title')"
+ :confirm-text="$t('user_card.unfollow_confirm_accept_button')"
+ :cancel-text="$t('user_card.unfollow_confirm_cancel_button')"
+ @accepted="doUnfollow"
+ @cancelled="hideConfirmUnfollow"
+ >
+ <i18n-t
+ scope="global"
+ keypath="user_card.unfollow_confirm"
+ tag="span"
+ >
+ <template #user>
+ <span
+ v-text="user.screen_name_ui"
+ />
+ </template>
+ </i18n-t>
+ </confirm-modal>
+ </teleport>
</button>
</template>
diff --git a/src/components/follow_card/follow_card.js b/src/components/follow_card/follow_card.js
@@ -1,6 +1,7 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import RemoteFollow from '../remote_follow/remote_follow.vue'
import FollowButton from '../follow_button/follow_button.vue'
+import RemoveFollowerButton from '../remove_follower_button/remove_follower_button.vue'
const FollowCard = {
props: [
@@ -10,7 +11,8 @@ const FollowCard = {
components: {
BasicUserCard,
RemoteFollow,
- FollowButton
+ FollowButton,
+ RemoveFollowerButton
},
computed: {
isMe () {
diff --git a/src/components/follow_card/follow_card.vue b/src/components/follow_card/follow_card.vue
@@ -22,6 +22,12 @@
class="follow-card-follow-button"
:user="user"
/>
+ <RemoveFollowerButton
+ v-if="noFollowsYou && relationship.followed_by"
+ :user="user"
+ :relationship="relationship"
+ class="follow-card-button"
+ />
</template>
</div>
</basic-user-card>
@@ -34,12 +40,17 @@
&-content-container {
flex-shrink: 0;
display: flex;
- flex-direction: row;
+ flex-flow: row wrap;
justify-content: space-between;
- flex-wrap: wrap;
line-height: 1.5em;
}
+ &-button {
+ margin-top: 0.5em;
+ padding: 0 1.5em;
+ margin-left: 1em;
+ }
+
&-follow-button {
margin-top: 0.5em;
margin-left: auto;
diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js
@@ -1,10 +1,18 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
+import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js'
const FollowRequestCard = {
props: ['user'],
components: {
- BasicUserCard
+ BasicUserCard,
+ ConfirmModal
+ },
+ data () {
+ return {
+ showingApproveConfirmDialog: false,
+ showingDenyConfirmDialog: false
+ }
},
methods: {
findFollowRequestNotificationId () {
@@ -13,7 +21,26 @@ const FollowRequestCard = {
)
return notif && notif.id
},
+ showApproveConfirmDialog () {
+ this.showingApproveConfirmDialog = true
+ },
+ hideApproveConfirmDialog () {
+ this.showingApproveConfirmDialog = false
+ },
+ showDenyConfirmDialog () {
+ this.showingDenyConfirmDialog = true
+ },
+ hideDenyConfirmDialog () {
+ this.showingDenyConfirmDialog = false
+ },
approveUser () {
+ if (this.shouldConfirmApprove) {
+ this.showApproveConfirmDialog()
+ } else {
+ this.doApprove()
+ }
+ },
+ doApprove () {
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user)
@@ -25,14 +52,34 @@ const FollowRequestCard = {
notification.type = 'follow'
}
})
+ this.hideApproveConfirmDialog()
},
denyUser () {
+ if (this.shouldConfirmDeny) {
+ this.showDenyConfirmDialog()
+ } else {
+ this.doDeny()
+ }
+ },
+ doDeny () {
const notifId = this.findFollowRequestNotificationId()
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
.then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
this.$store.dispatch('removeFollowRequest', this.user)
})
+ this.hideDenyConfirmDialog()
+ }
+ },
+ computed: {
+ mergedConfig () {
+ return this.$store.getters.mergedConfig
+ },
+ shouldConfirmApprove () {
+ return this.mergedConfig.modalOnApproveFollow
+ },
+ shouldConfirmDeny () {
+ return this.mergedConfig.modalOnDenyFollow
}
}
}
diff --git a/src/components/follow_request_card/follow_request_card.vue b/src/components/follow_request_card/follow_request_card.vue
@@ -14,6 +14,28 @@
{{ $t('user_card.deny') }}
</button>
</div>
+ <teleport to="#modal">
+ <confirm-modal
+ v-if="showingApproveConfirmDialog"
+ :title="$t('user_card.approve_confirm_title')"
+ :confirm-text="$t('user_card.approve_confirm_accept_button')"
+ :cancel-text="$t('user_card.approve_confirm_cancel_button')"
+ @accepted="doApprove"
+ @cancelled="hideApproveConfirmDialog"
+ >
+ {{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
+ </confirm-modal>
+ <confirm-modal
+ v-if="showingDenyConfirmDialog"
+ :title="$t('user_card.deny_confirm_title')"
+ :confirm-text="$t('user_card.deny_confirm_accept_button')"
+ :cancel-text="$t('user_card.deny_confirm_cancel_button')"
+ @accepted="doDeny"
+ @cancelled="hideDenyConfirmDialog"
+ >
+ {{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
+ </confirm-modal>
+ </teleport>
</basic-user-card>
</template>
@@ -22,8 +44,8 @@
<style lang="scss">
.follow-request-card-content-container {
display: flex;
- flex-direction: row;
- flex-wrap: wrap;
+ flex-flow: row wrap;
+
button {
margin-top: 0.5em;
margin-right: 0.5em;
diff --git a/src/components/follow_requests/follow_requests.vue b/src/components/follow_requests/follow_requests.vue
@@ -1,9 +1,9 @@
<template>
<div class="settings panel panel-default">
<div class="panel-heading">
- <div class="title">
+ <h1 class="title">
{{ $t('nav.friend_requests') }}
- </div>
+ </h1>
</div>
<div class="panel-body">
<FollowRequestCard
diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js
@@ -1,63 +1,59 @@
-import { set } from 'lodash'
import Select from '../select/select.vue'
+import Checkbox from 'src/components/checkbox/checkbox.vue'
+import Popover from 'src/components/popover/popover.vue'
+
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faExclamationTriangle,
+ faKeyboard,
+ faFont
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faExclamationTriangle,
+ faKeyboard,
+ faFont
+)
export default {
components: {
- Select
+ Select,
+ Checkbox,
+ Popover
},
props: [
'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit'
],
+ mounted () {
+ this.$store.dispatch('queryLocalFonts')
+ },
emits: ['update:modelValue'],
data () {
return {
- lValue: this.modelValue,
+ manualEntry: false,
availableOptions: [
this.noInherit ? '' : 'inherit',
- 'custom',
- ...(this.options || []),
'serif',
+ 'sans-serif',
'monospace',
- 'sans-serif'
+ ...(this.options || [])
].filter(_ => _)
}
},
- beforeUpdate () {
- this.lValue = this.modelValue
+ methods: {
+ toggleManualEntry () {
+ this.manualEntry = !this.manualEntry
+ }
},
computed: {
present () {
- return typeof this.lValue !== 'undefined'
- },
- dValue () {
- return this.lValue || this.fallback || {}
- },
- family: {
- get () {
- return this.dValue.family
- },
- set (v) {
- set(this.lValue, 'family', v)
- this.$emit('update:modelValue', this.lValue)
- }
+ return typeof this.modelValue !== 'undefined'
},
- isCustom () {
- return this.preset === 'custom'
+ localFontsList () {
+ return this.$store.state.interface.localFonts
},
- preset: {
- get () {
- if (this.family === 'serif' ||
- this.family === 'sans-serif' ||
- this.family === 'monospace' ||
- this.family === 'inherit') {
- return this.family
- } else {
- return 'custom'
- }
- },
- set (v) {
- this.family = v === 'custom' ? '' : v
- }
+ localFontsSize () {
+ return this.$store.state.interface.localFonts?.length
}
}
}
diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue
@@ -1,70 +1,144 @@
<template>
- <div
- class="font-control style-control"
- :class="{ custom: isCustom }"
- >
+ <div class="font-control">
<label
- :for="preset === 'custom' ? name : name + '-font-switcher'"
+ :id="name + '-label'"
+ :for="manualEntry ? name : name + '-font-switcher'"
class="label"
>
{{ label }}
</label>
- <input
+ {{ ' ' }}
+ <Checkbox
v-if="typeof fallback !== 'undefined'"
:id="name + '-o'"
- class="opt exlcude-disabled"
- type="checkbox"
- :checked="present"
+ :model-value="present"
@change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
>
- <label
- v-if="typeof fallback !== 'undefined'"
- class="opt-l"
- :for="name + '-o'"
- />
- {{ ' ' }}
- <Select
- :id="name + '-font-switcher'"
- v-model="preset"
- :disabled="!present"
- class="font-switcher"
- >
- <option
- v-for="option in availableOptions"
- :key="option"
- :value="option"
+ {{ $t('settings.style.themes3.define') }}
+ </Checkbox>
+ <p v-if="modelValue?.family">
+ <label
+ v-if="manualEntry"
+ :id="name + '-label'"
+ :for="manualEntry ? name : name + '-font-switcher'"
+ class="label"
>
- {{ option === 'custom' ? $t('settings.style.fonts.custom') : option }}
- </option>
- </Select>
- <input
- v-if="isCustom"
- :id="name"
- v-model="family"
- class="custom-font"
- type="text"
- >
+ <i18n-t
+ keypath="settings.style.themes3.font.entry"
+ tag="span"
+ scope="global"
+ >
+ <template #fontFamily>
+ <code>font-family</code>
+ </template>
+ </i18n-t>
+ </label>
+ <label
+ v-else
+ :id="name + '-label'"
+ :for="manualEntry ? name : name + '-font-switcher'"
+ class="label"
+ >
+ {{ $t('settings.style.themes3.font.select') }}
+ </label>
+ {{ ' ' }}
+ <span
+ v-if="manualEntry"
+ class="btn-group"
+ >
+ <button
+ class="btn button-default"
+ :title="$t('settings.style.themes3.font.lookup_local_fonts')"
+ @click="toggleManualEntry"
+ >
+ <FAIcon
+ fixed-width
+ icon="font"
+ />
+ </button>
+ <input
+ :id="name"
+ :model-value="modelValue.family"
+ class="input custom-font"
+ type="text"
+ @update:modelValue="$emit('update:modelValue', { ...(modelValue || {}), family: $event.target.value })"
+ >
+ </span>
+ <span
+ v-else
+ class="btn-group"
+ >
+ <button
+ class="btn button-default"
+ :title="$t('settings.style.themes3.font.enter_manually')"
+ @click="toggleManualEntry"
+ >
+ <FAIcon
+ fixed-width
+ icon="keyboard"
+ />
+ </button>
+ <Select
+ :id="name + '-local-font-switcher'"
+ :model-value="modelValue?.family"
+ class="custom-font"
+ @update:modelValue="v => $emit('update:modelValue', { ...(modelValue || {}), family: v })"
+ >
+ <optgroup
+ :label="$t('settings.style.themes3.font.group-builtin')"
+ >
+ <option
+ v-for="option in availableOptions"
+ :key="option"
+ :value="option"
+ :style="{ fontFamily: option === 'inherit' ? null : option }"
+ >
+ {{ $t('settings.style.themes3.font.builtin.' + option) }}
+ </option>
+ </optgroup>
+ <optgroup
+ v-if="localFontsSize > 0"
+ :label="$t('settings.style.themes3.font.group-local')"
+ >
+ <option
+ v-for="option in localFontsList"
+ :key="option"
+ :value="option"
+ :style="{ fontFamily: option }"
+ >
+ {{ option }}
+ </option>
+ </optgroup>
+ <optgroup
+ v-else
+ :label="$t('settings.style.themes3.font.group-local')"
+ >
+ <option disabled>
+ {{ $t('settings.style.themes3.font.local-unavailable1') }}
+ </option>
+ <option disabled>
+ {{ $t('settings.style.themes3.font.local-unavailable2') }}
+ </option>
+ </optgroup>
+ </Select>
+ </span>
+ </p>
</div>
</template>
-<script src="./font_control.js" ></script>
+<script src="./font_control.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
.font-control {
- input.custom-font {
- min-width: 10em;
- }
- &.custom {
- /* TODO Should make proper joiners... */
- .font-switcher {
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
- }
- .custom-font {
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
- }
+ .custom-font {
+ min-width: 20em;
+ max-width: 20em;
}
}
+
+.invalid-tooltip {
+ margin: 0.5em 1em;
+ min-width: 10em;
+ text-align: center;
+}
</style>
diff --git a/src/components/fun_text.style.js b/src/components/fun_text.style.js
@@ -0,0 +1,40 @@
+export default {
+ name: 'FunText',
+ selector: '/*fun-text*/',
+ virtual: true,
+ variants: {
+ greentext: '.greentext',
+ cyantext: '.cyantext'
+ },
+ states: {
+ faint: '.faint'
+ },
+ defaultRules: [
+ {
+ directives: {
+ textColor: '--text',
+ textAuto: 'preserve'
+ }
+ },
+ {
+ state: ['faint'],
+ directives: {
+ textOpacity: 0.5
+ }
+ },
+ {
+ variant: 'greentext',
+ directives: {
+ textColor: '--cGreen',
+ textAuto: 'preserve'
+ }
+ },
+ {
+ variant: 'cyantext',
+ directives: {
+ textColor: '--cBlue',
+ textAuto: 'preserve'
+ }
+ }
+ ]
+}
diff --git a/src/components/gallery/gallery.js b/src/components/gallery/gallery.js
@@ -4,6 +4,7 @@ import { sumBy, set } from 'lodash'
const Gallery = {
props: [
'attachments',
+ 'compact',
'limitRows',
'descriptions',
'limit',
diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue
@@ -20,6 +20,7 @@
v-for="(attachment, attachmentIndex) in row.items"
:key="attachment.id"
class="gallery-item"
+ :compact="compact"
:nsfw="nsfw"
:attachment="attachment"
:size="size"
@@ -86,8 +87,6 @@
<script src='./gallery.js'></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.Gallery {
.gallery-rows {
display: flex;
@@ -100,6 +99,53 @@
width: 100%;
flex-grow: 1;
+ .gallery-row-inner {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ flex-flow: row wrap;
+ align-content: stretch;
+
+ .gallery-item {
+ margin: 0 0.5em 0 0;
+ flex-grow: 1;
+ height: 100%;
+ box-sizing: border-box;
+ // to make failed images a bit more noticeable on chromium
+ min-width: 2em;
+
+ &:last-child {
+ margin: 0;
+ }
+ }
+
+ &.-grid {
+ width: 100%;
+ height: auto;
+ position: relative;
+ display: grid;
+ grid-gap: 0.5em;
+ grid-template-columns: repeat(auto-fill, minmax(15em, 1fr));
+
+ .gallery-item {
+ margin: 0;
+ height: 200px;
+ }
+ }
+ }
+
+ &.-grid,
+ &.-minimal {
+ height: auto;
+
+ .gallery-row-inner {
+ position: relative;
+ }
+ }
+
&:not(:first-child) {
margin-top: 0.5em;
}
@@ -114,7 +160,7 @@
linear-gradient(to top, white, white);
/* Autoprefixed seem to ignore this one, and also syntax is different */
- -webkit-mask-composite: xor;
+ mask-composite: xor;
mask-composite: exclude;
}
}
@@ -138,54 +184,5 @@
padding: 0 2em;
}
}
-
- .gallery-row {
- &.-grid,
- &.-minimal {
- height: auto;
- .gallery-row-inner {
- position: relative;
- }
- }
- }
-
- .gallery-row-inner {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- display: flex;
- flex-direction: row;
- flex-wrap: nowrap;
- align-content: stretch;
-
- &.-grid {
- width: 100%;
- height: auto;
- position: relative;
- display: grid;
- grid-column-gap: 0.5em;
- grid-row-gap: 0.5em;
- grid-template-columns: repeat(auto-fill, minmax(15em, 1fr));
-
- .gallery-item {
- margin: 0;
- height: 200px;
- }
- }
- }
-
- .gallery-item {
- margin: 0 0.5em 0 0;
- flex-grow: 1;
- height: 100%;
- box-sizing: border-box;
- // to make failed images a bit more noticeable on chromium
- min-width: 2em;
- &:last-child {
- margin: 0;
- }
- }
}
</style>
diff --git a/src/components/global_notice_list/global_notice_list.vue b/src/components/global_notice_list/global_notice_list.vue
@@ -4,7 +4,7 @@
v-for="(notice, index) in notices"
:key="index"
class="alert global-notice"
- :class="{ ['global-' + notice.level]: true }"
+ :class="{ [notice.level]: true }"
>
<div class="notice-message">
{{ $t(notice.messageKey, notice.messageArgs) }}
@@ -25,14 +25,12 @@
<script src="./global_notice_list.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.global-notice-list {
position: fixed;
- top: 50px;
+ top: calc(var(--navbar-height) + 0.5em);
width: 100%;
pointer-events: none;
- z-index: 1001;
+ z-index: var(--ZI_modals_popovers);
display: flex;
flex-direction: column;
align-items: center;
@@ -52,45 +50,8 @@
}
}
- .global-error {
- background-color: var(--alertPopupError, $fallback--cRed);
- color: var(--alertPopupErrorText, $fallback--text);
-
- .svg-inline--fa {
- color: var(--alertPopupErrorText, $fallback--text);
- }
- }
-
- .global-warning {
- background-color: var(--alertPopupWarning, $fallback--cOrange);
- color: var(--alertPopupWarningText, $fallback--text);
-
- .svg-inline--fa {
- color: var(--alertPopupWarningText, $fallback--text);
- }
- }
-
- .global-success {
- background-color: var(--alertPopupSuccess, $fallback--cGreen);
- color: var(--alertPopupSuccessText, $fallback--text);
- .svg-inline--fa {
- color: var(--alertPopupSuccessText, $fallback--text);
- }
- }
-
- .global-info {
- background-color: var(--alertPopupNeutral, $fallback--fg);
- color: var(--alertPopupNeutralText, $fallback--text);
- .svg-inline--fa {
- color: var(--alertPopupNeutralText, $fallback--text);
- }
- }
-
.close-notice {
padding-right: 0.2em;
- .svg-inline--fa:hover {
- opacity: 0.6;
- }
}
}
</style>
diff --git a/src/components/hashtag_link/hashtag_link.vue b/src/components/hashtag_link/hashtag_link.vue
@@ -14,6 +14,6 @@
</span>
</template>
-<script src="./hashtag_link.js"/>
+<script src="./hashtag_link.js" />
-<style lang="scss" src="./hashtag_link.scss"/>
+<style lang="scss" src="./hashtag_link.scss" />
diff --git a/src/components/icon.style.js b/src/components/icon.style.js
@@ -0,0 +1,14 @@
+export default {
+ name: 'Icon',
+ virtual: true,
+ selector: '.svg-inline--fa',
+ defaultRules: [
+ {
+ component: 'Icon',
+ directives: {
+ textColor: '$blend(--stack 0.5 --parent--text)',
+ textAuto: 'no-auto'
+ }
+ }
+ ]
+}
diff --git a/src/components/image_cropper/image_cropper.js b/src/components/image_cropper/image_cropper.js
@@ -95,7 +95,7 @@ const ImageCropper = {
const fileInput = this.$refs.input
if (fileInput.files != null && fileInput.files[0] != null) {
this.file = fileInput.files[0]
- let reader = new window.FileReader()
+ const reader = new window.FileReader()
reader.onload = (e) => {
this.dataUrl = e.target.result
this.$emit('open')
diff --git a/src/components/image_cropper/image_cropper.vue b/src/components/image_cropper/image_cropper.vue
@@ -41,7 +41,7 @@
<input
ref="input"
type="file"
- class="image-cropper-img-input"
+ class="input image-cropper-img-input"
:accept="mimes"
>
</div>
diff --git a/src/components/importer/importer.vue b/src/components/importer/importer.vue
@@ -3,6 +3,7 @@
<form>
<input
ref="input"
+ class="input"
type="file"
@change="change"
>
diff --git a/src/components/input.style.js b/src/components/input.style.js
@@ -0,0 +1,94 @@
+export default {
+ name: 'Input',
+ selector: '.input',
+ states: {
+ hover: ':hover:not(.disabled)',
+ focused: ':focus-within',
+ disabled: '.disabled'
+ },
+ variants: {
+ checkbox: '.-checkbox',
+ radio: '.-radio'
+ },
+ validInnerComponents: [
+ 'Text',
+ 'Icon'
+ ],
+ defaultRules: [
+ {
+ component: 'Root',
+ directives: {
+ '--defaultInputBevel': 'shadow | $borderSide(#FFFFFF bottom 0.2), $borderSide(#000000 top 0.2)',
+ '--defaultInputHoverGlow': 'shadow | 0 0 4 --text / 0.5',
+ '--defaultInputFocusGlow': 'shadow | 0 0 4 4 --link / 0.5'
+ }
+ },
+ {
+ variant: 'checkbox',
+ directives: {
+ roundness: 1
+ }
+ },
+ {
+ directives: {
+ '--font': 'generic | inherit',
+ background: '--fg, -5',
+ roundness: 3,
+ shadow: [{
+ x: 0,
+ y: 0,
+ blur: 2,
+ spread: 0,
+ color: '#000000',
+ alpha: 1
+ }, '--defaultInputBevel']
+ }
+ },
+ {
+ state: ['hover'],
+ directives: {
+ shadow: ['--defaultInputHoverGlow', '--defaultInputBevel']
+ }
+ },
+ {
+ state: ['focused'],
+ directives: {
+ shadow: ['--defaultInputFocusGlow', '--defaultInputBevel']
+ }
+ },
+ {
+ state: ['focused', 'hover'],
+ directives: {
+ shadow: ['--defaultInputFocusGlow', '--defaultInputHoverGlow', '--defaultInputBevel']
+ }
+ },
+ {
+ state: ['disabled'],
+ directives: {
+ background: '--parent'
+ }
+ },
+ {
+ component: 'Text',
+ parent: {
+ component: 'Input',
+ state: ['disabled']
+ },
+ directives: {
+ textOpacity: 0.25,
+ textOpacityMode: 'blend'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'Input',
+ state: ['disabled']
+ },
+ directives: {
+ textOpacity: 0.25,
+ textOpacityMode: 'blend'
+ }
+ }
+ ]
+}
diff --git a/src/components/instance_specific_panel/instance_specific_panel.vue b/src/components/instance_specific_panel/instance_specific_panel.vue
@@ -10,4 +10,4 @@
</div>
</template>
-<script src="./instance_specific_panel.js" ></script>
+<script src="./instance_specific_panel.js"></script>
diff --git a/src/components/interactions/interactions.js b/src/components/interactions/interactions.js
@@ -3,8 +3,11 @@ import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
const tabModeDict = {
mentions: ['mention'],
+ statuses: ['status'],
'likes+repeats': ['repeat', 'like'],
follows: ['follow'],
+ reactions: ['pleroma:emoji_reaction'],
+ reports: ['pleroma:report'],
moves: ['move']
}
@@ -12,7 +15,8 @@ const Interactions = {
data () {
return {
allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
- filterMode: tabModeDict['mentions']
+ filterMode: tabModeDict.mentions,
+ canSeeReports: this.$store.state.users.currentUser.privileges.includes('reports_manage_reports')
}
},
methods: {
diff --git a/src/components/interactions/interactions.vue b/src/components/interactions/interactions.vue
@@ -1,19 +1,23 @@
<template>
<div class="panel panel-default">
<div class="panel-heading">
- <div class="title">
+ <h1 class="title">
{{ $t("nav.interactions") }}
- </div>
+ </h1>
</div>
<tab-switcher
ref="tabSwitcher"
:on-switch="onModeSwitch"
>
<span
- key="mentions"
+ key="statuses"
:label="$t('nav.mentions')"
/>
<span
+ key="statuses"
+ :label="$t('interactions.statuses')"
+ />
+ <span
key="likes+repeats"
:label="$t('interactions.favs_repeats')"
/>
@@ -22,6 +26,15 @@
:label="$t('interactions.follows')"
/>
<span
+ key="reactions"
+ :label="$t('interactions.emoji_reactions')"
+ />
+ <span
+ v-if="canSeeReports"
+ key="reports"
+ :label="$t('interactions.reports')"
+ />
+ <span
v-if="!allowFollowingMove"
key="moves"
:label="$t('interactions.moves')"
@@ -30,6 +43,7 @@
<Notifications
ref="notifications"
:no-heading="true"
+ :no-extra="true"
:minimal-mode="true"
:filter-mode="filterMode"
/>
diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
@@ -1,21 +1,46 @@
<template>
- <div>
- <label for="interface-language-switcher">
+ <div class="interface-language-switcher">
+ <label>
{{ promptText }}
</label>
- {{ ' ' }}
- <Select
- id="interface-language-switcher"
- v-model="controlledLanguage"
- >
- <option
- v-for="lang in languages"
- :key="lang.code"
- :value="lang.code"
+ <ul class="setting-list">
+ <li
+ v-for="index of controlledLanguage.keys()"
+ :key="index"
>
- {{ lang.name }}
- </option>
- </Select>
+ <label>
+ {{ index === 0 ? $t('settings.primary_language') : $tc('settings.fallback_language', index, { index }) }}
+ <Select
+ class="language-select"
+ :model-value="controlledLanguage[index]"
+ @update:modelValue="val => setLanguageAt(index, val)"
+ >
+ <option
+ v-for="lang in languages"
+ :key="lang.code"
+ :value="lang.code"
+ >
+ {{ lang.name }}
+ </option>
+ </Select>
+ </label>
+ <button
+ v-if="controlledLanguage.length > 1 && index !== 0"
+ class="button-default btn"
+ @click="() => removeLanguageAt(index)"
+ >
+ {{ $t('settings.remove_language') }}
+ </button>
+ </li>
+ <li>
+ <button
+ class="button-default btn"
+ @click="addLanguage"
+ >
+ {{ $t('settings.add_language') }}
+ </button>
+ </li>
+ </ul>
</div>
</template>
@@ -25,6 +50,7 @@ import Select from '../select/select.vue'
export default {
components: {
+ // eslint-disable-next-line vue/no-reserved-component-names
Select
},
props: {
@@ -33,7 +59,7 @@ export default {
required: true
},
language: {
- type: String,
+ type: [Array, String],
required: true
},
setLanguage: {
@@ -47,7 +73,9 @@ export default {
},
controlledLanguage: {
- get: function () { return this.language },
+ get: function () {
+ return Array.isArray(this.language) ? this.language : [this.language]
+ },
set: function (val) {
this.setLanguage(val)
}
@@ -57,7 +85,28 @@ export default {
methods: {
getLanguageName (code) {
return localeService.getLanguageName(code)
+ },
+ addLanguage () {
+ this.controlledLanguage = [...this.controlledLanguage, '']
+ },
+ setLanguageAt (index, val) {
+ const lang = [...this.controlledLanguage]
+ lang[index] = val
+ this.controlledLanguage = lang
+ },
+ removeLanguageAt (index) {
+ const lang = [...this.controlledLanguage]
+ lang.splice(index, 1)
+ this.controlledLanguage = lang
}
}
}
</script>
+
+<style lang="scss">
+.interface-language-switcher {
+ .language-select {
+ margin-right: 1em;
+ }
+}
+</style>
diff --git a/src/components/link-preview/link-preview.vue b/src/components/link-preview/link-preview.vue
@@ -33,8 +33,6 @@
<script src="./link-preview.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.link-preview-card {
display: flex;
flex-direction: row;
@@ -46,12 +44,12 @@
flex-shrink: 0;
width: 120px;
max-width: 25%;
+
img {
width: 100%;
height: 100%;
object-fit: cover;
- border-radius: $fallback--attachmentRadius;
- border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
+ border-radius: var(--roundness);
}
}
@@ -67,7 +65,7 @@
}
.card-description {
- margin: 0.5em 0 0 0;
+ margin: 0.5em 0 0;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-word;
@@ -81,13 +79,10 @@
margin: 2em 0;
}
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
border-style: solid;
border-width: 1px;
- border-radius: $fallback--attachmentRadius;
- border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-radius: var(--roundness);
+ border-color: var(--border);
}
</style>
diff --git a/src/components/link.style.js b/src/components/link.style.js
@@ -0,0 +1,24 @@
+export default {
+ name: 'Link',
+ selector: 'a',
+ virtual: true,
+ states: {
+ faint: '.faint'
+ },
+ defaultRules: [
+ {
+ component: 'Link',
+ directives: {
+ textColor: '--link'
+ }
+ },
+ {
+ component: 'Link',
+ state: ['faint'],
+ directives: {
+ textOpacity: 0.5,
+ textOpacityMode: 'fake'
+ }
+ }
+ ]
+}
diff --git a/src/components/list/list.vue b/src/components/list/list.vue
@@ -1,9 +1,14 @@
<template>
- <div class="list">
+ <div
+ class="list"
+ role="list"
+ >
<div
v-for="item in items"
:key="getKey(item)"
class="list-item"
+ :class="[getClass(item), nonInteractive ? '-non-interactive' : '']"
+ role="listitem"
>
<slot
name="item"
@@ -29,24 +34,15 @@ export default {
getKey: {
type: Function,
default: item => item.id
+ },
+ getClass: {
+ type: Function,
+ default: item => ''
+ },
+ nonInteractive: {
+ type: Boolean,
+ default: false
}
}
}
</script>
-
-<style lang="scss">
-@import '../../_variables.scss';
-
-.list {
- &-item:not(:last-child) {
- border-bottom: 1px solid;
- border-bottom-color: $fallback--border;
- border-bottom-color: var(--border, $fallback--border);
- }
-
- &-empty-content {
- text-align: center;
- padding: 10px;
- }
-}
-</style>
diff --git a/src/components/list/list_item.style.js b/src/components/list/list_item.style.js
@@ -0,0 +1,48 @@
+export default {
+ name: 'ListItem',
+ selector: '.list-item',
+ states: {
+ active: '.-active',
+ hover: ':hover: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
@@ -0,0 +1,27 @@
+import ListsCard from '../lists_card/lists_card.vue'
+
+const Lists = {
+ data () {
+ return {
+ isNew: false
+ }
+ },
+ components: {
+ ListsCard
+ },
+ computed: {
+ lists () {
+ return this.$store.state.lists.allLists
+ }
+ },
+ methods: {
+ cancelNewList () {
+ this.isNew = false
+ },
+ newList () {
+ this.isNew = true
+ }
+ }
+}
+
+export default Lists
diff --git a/src/components/lists/lists.vue b/src/components/lists/lists.vue
@@ -0,0 +1,35 @@
+<template>
+ <div class="Lists panel panel-default">
+ <div class="panel-heading">
+ <div class="title">
+ <h1 class="title">
+ {{ $t('lists.lists') }}
+ </h1>
+ </div>
+ <router-link
+ :to="{ name: 'lists-new' }"
+ class="button-default btn new-list-button"
+ >
+ {{ $t("lists.new") }}
+ </router-link>
+ </div>
+ <div class="panel-body">
+ <ListsCard
+ v-for="list in lists.slice().reverse()"
+ :key="list"
+ :list="list"
+ class="list-item"
+ />
+ </div>
+ </div>
+</template>
+
+<script src="./lists.js"></script>
+
+<style lang="scss">
+.Lists {
+ .new-list-button {
+ padding: 0 0.5em;
+ }
+}
+</style>
diff --git a/src/components/lists_card/lists_card.js b/src/components/lists_card/lists_card.js
@@ -0,0 +1,16 @@
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faEllipsisH
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faEllipsisH
+)
+
+const ListsCard = {
+ props: [
+ 'list'
+ ]
+}
+
+export default ListsCard
diff --git a/src/components/lists_card/lists_card.vue b/src/components/lists_card/lists_card.vue
@@ -0,0 +1,38 @@
+<template>
+ <div class="list-card">
+ <router-link
+ :to="{ name: 'lists-timeline', params: { id: list.id } }"
+ class="list-name"
+ >
+ {{ list.title }}
+ </router-link>
+ <router-link
+ :to="{ name: 'lists-edit', params: { id: list.id } }"
+ class="button-list-edit"
+ >
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="ellipsis-h"
+ />
+ </router-link>
+ </div>
+</template>
+
+<script src="./lists_card.js"></script>
+
+<style lang="scss">
+.list-card {
+ display: flex;
+}
+
+.list-name {
+ flex-grow: 1;
+}
+
+.list-name,
+.button-list-edit {
+ margin: 0;
+ padding: 1em;
+ color: var(--link);
+}
+</style>
diff --git a/src/components/lists_edit/lists_edit.js b/src/components/lists_edit/lists_edit.js
@@ -0,0 +1,145 @@
+import { mapState, mapGetters } from 'vuex'
+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 {
+ faSearch,
+ faChevronLeft
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faSearch,
+ faChevronLeft
+)
+
+const ListsNew = {
+ components: {
+ BasicUserCard,
+ UserAvatar,
+ ListsUserSearch,
+ TabSwitcher,
+ PanelLoading
+ },
+ data () {
+ return {
+ title: '',
+ titleDraft: '',
+ membersUserIds: [],
+ removedUserIds: new Set([]), // users we added for members, to undo
+ searchUserIds: [],
+ addedUserIds: new Set([]), // users we added from search, to undo
+ searchLoading: false,
+ reallyDelete: false
+ }
+ },
+ created () {
+ if (!this.id) return
+ this.$store.dispatch('fetchList', { listId: this.id })
+ .then(() => {
+ this.title = this.findListTitle(this.id)
+ this.titleDraft = this.title
+ })
+ this.$store.dispatch('fetchListAccounts', { listId: this.id })
+ .then(() => {
+ this.membersUserIds = this.findListAccounts(this.id)
+ this.membersUserIds.forEach(userId => {
+ this.$store.dispatch('fetchUserIfMissing', userId)
+ })
+ })
+ },
+ computed: {
+ id () {
+ return this.$route.params.id
+ },
+ membersUsers () {
+ return [...this.membersUserIds, ...this.addedUserIds]
+ .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
+ }),
+ ...mapGetters(['findUser', 'findListTitle', 'findListAccounts'])
+ },
+ methods: {
+ onInput () {
+ this.search(this.query)
+ },
+ toggleRemoveMember (user) {
+ if (this.removedUserIds.has(user.id)) {
+ this.id && this.addUser(user)
+ this.removedUserIds.delete(user.id)
+ } else {
+ this.id && this.removeUser(user.id)
+ this.removedUserIds.add(user.id)
+ }
+ },
+ toggleAddFromSearch (user) {
+ if (this.addedUserIds.has(user.id)) {
+ this.id && this.removeUser(user.id)
+ this.addedUserIds.delete(user.id)
+ } else {
+ this.id && this.addUser(user)
+ this.addedUserIds.add(user.id)
+ }
+ },
+ isRemoved (user) {
+ return this.removedUserIds.has(user.id)
+ },
+ isAdded (user) {
+ return this.addedUserIds.has(user.id)
+ },
+ addUser (user) {
+ this.$store.dispatch('addListAccount', { accountId: user.id, listId: this.id })
+ },
+ removeUser (userId) {
+ this.$store.dispatch('removeListAccount', { accountId: userId, listId: this.id })
+ },
+ onSearchLoading (results) {
+ this.searchLoading = true
+ },
+ onSearchLoadingDone (results) {
+ this.searchLoading = false
+ },
+ onSearchResults (results) {
+ this.searchLoading = false
+ this.searchUserIds = results
+ },
+ updateListTitle () {
+ this.$store.dispatch('setList', { listId: this.id, title: this.titleDraft })
+ .then(() => {
+ this.title = this.findListTitle(this.id)
+ })
+ },
+ createList () {
+ this.$store.dispatch('createList', { title: this.titleDraft })
+ .then((list) => {
+ return this
+ .$store
+ .dispatch('setListAccounts', { listId: list.id, accountIds: [...this.addedUserIds] })
+ .then(() => list.id)
+ })
+ .then((listId) => {
+ this.$router.push({ name: 'lists-timeline', params: { id: listId } })
+ })
+ .catch((e) => {
+ this.$store.dispatch('pushGlobalNotice', {
+ messageKey: 'lists.error',
+ messageArgs: [e.message],
+ level: 'error'
+ })
+ })
+ },
+ deleteList () {
+ this.$store.dispatch('deleteList', { listId: this.id })
+ this.$router.push({ name: 'lists' })
+ }
+ }
+}
+
+export default ListsNew
diff --git a/src/components/lists_edit/lists_edit.vue b/src/components/lists_edit/lists_edit.vue
@@ -0,0 +1,229 @@
+<template>
+ <div class="panel-default panel ListEdit">
+ <div
+ ref="header"
+ class="panel-heading list-edit-heading"
+ >
+ <button
+ class="button-unstyled go-back-button"
+ @click="$router.back"
+ >
+ <FAIcon
+ size="lg"
+ icon="chevron-left"
+ />
+ </button>
+ <div class="title">
+ <i18n-t
+ v-if="id"
+ keypath="lists.editing_list"
+ scope="global"
+ >
+ <template #listTitle>
+ {{ title }}
+ </template>
+ </i18n-t>
+ <i18n-t
+ v-else
+ keypath="lists.creating_list"
+ scope="global"
+ />
+ </div>
+ </div>
+ <div class="panel-body">
+ <div class="input-wrap">
+ <label for="list-edit-title">{{ $t('lists.title') }}</label>
+ {{ ' ' }}
+ <input
+ id="list-edit-title"
+ ref="title"
+ v-model="titleDraft"
+ class="input"
+ >
+ <button
+ v-if="id"
+ class="btn button-default follow-button"
+ @click="updateListTitle"
+ >
+ {{ $t('lists.update_title') }}
+ </button>
+ </div>
+ <tab-switcher
+ class="list-member-management"
+ :scrollable-tabs="true"
+ >
+ <div
+ v-if="id || addedUserIds.size > 0"
+ :label="$t('lists.manage_members')"
+ class="members-list"
+ >
+ <div class="users-list">
+ <div
+ v-for="user in membersUsers"
+ :key="user.id"
+ class="member"
+ >
+ <BasicUserCard
+ :user="user"
+ >
+ <button
+ class="btn button-default follow-button"
+ @click="toggleRemoveMember(user)"
+ >
+ {{ isRemoved(user) ? $t('general.undo') : $t('lists.remove_from_list') }}
+ </button>
+ </BasicUserCard>
+ </div>
+ </div>
+ </div>
+
+ <div
+ class="search-list"
+ :label="$t('lists.add_members')"
+ >
+ <ListsUserSearch
+ @results="onSearchResults"
+ @loading="onSearchLoading"
+ @loadingDone="onSearchLoadingDone"
+ />
+ <div
+ v-if="searchLoading"
+ class="loading"
+ >
+ <PanelLoading />
+ </div>
+ <div
+ v-else
+ class="users-list"
+ >
+ <div
+ v-for="user in searchUsers"
+ :key="user.id"
+ class="member"
+ >
+ <BasicUserCard
+ :user="user"
+ >
+ <span
+ v-if="membersUserIds.includes(user.id)"
+ >
+ {{ $t('lists.is_in_list') }}
+ </span>
+ <button
+ v-if="!membersUserIds.includes(user.id)"
+ class="btn button-default follow-button"
+ @click="toggleAddFromSearch(user)"
+ >
+ {{ isAdded(user) ? $t('general.undo') : $t('lists.add_to_list') }}
+ </button>
+ <button
+ v-else
+ class="btn button-default follow-button"
+ @click="toggleRemoveMember(user)"
+ >
+ {{ isRemoved(user) ? $t('general.undo') : $t('lists.remove_from_list') }}
+ </button>
+ </BasicUserCard>
+ </div>
+ </div>
+ </div>
+ </tab-switcher>
+ </div>
+ <div class="panel-footer">
+ <span class="spacer" />
+ <button
+ v-if="!id"
+ class="btn button-default footer-button"
+ @click="createList"
+ >
+ {{ $t('lists.create') }}
+ </button>
+ <button
+ v-else-if="!reallyDelete"
+ class="btn button-default footer-button"
+ @click="reallyDelete = true"
+ >
+ {{ $t('lists.delete') }}
+ </button>
+ <template v-else>
+ {{ $t('lists.really_delete') }}
+ <button
+ class="btn button-default footer-button"
+ @click="deleteList"
+ >
+ {{ $t('general.yes') }}
+ </button>
+ <button
+ class="btn button-default footer-button"
+ @click="reallyDelete = false"
+ >
+ {{ $t('general.no') }}
+ </button>
+ </template>
+ </div>
+ </div>
+</template>
+
+<script src="./lists_edit.js"></script>
+
+<style lang="scss">
+.ListEdit {
+ --panel-body-padding: 0.5em;
+
+ height: calc(100vh - var(--navbar-height));
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+
+ .list-edit-heading {
+ grid-template-columns: auto minmax(50%, 1fr);
+ }
+
+ .panel-body {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ overflow: hidden;
+ }
+
+ .list-member-management {
+ flex: 1 0 auto;
+ }
+
+ .search-icon {
+ margin-right: 0.3em;
+ }
+
+ .users-list {
+ padding-bottom: 0.7rem;
+ overflow-y: auto;
+ }
+
+ & .search-list,
+ & .members-list {
+ overflow: hidden;
+ flex-direction: column;
+ min-height: 0;
+ }
+
+ .go-back-button {
+ text-align: center;
+ line-height: 1;
+ height: 100%;
+ align-self: start;
+ width: var(--__panel-heading-height-inner);
+ }
+
+ .btn {
+ margin: 0 0.5em;
+ }
+
+ .panel-footer {
+ grid-template-columns: minmax(10%, 1fr);
+
+ .footer-button {
+ min-width: 9em;
+ }
+ }
+}
+</style>
diff --git a/src/components/lists_menu/lists_menu_content.js b/src/components/lists_menu/lists_menu_content.js
@@ -0,0 +1,22 @@
+import { mapState } from 'vuex'
+import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
+import { getListEntries } from 'src/components/navigation/filter.js'
+
+export const ListsMenuContent = {
+ props: [
+ 'showPin'
+ ],
+ components: {
+ NavigationEntry
+ },
+ computed: {
+ ...mapState({
+ lists: getListEntries,
+ currentUser: state => state.users.currentUser,
+ privateMode: state => state.instance.private,
+ federating: state => state.instance.federating
+ })
+ }
+}
+
+export default ListsMenuContent
diff --git a/src/components/lists_menu/lists_menu_content.vue b/src/components/lists_menu/lists_menu_content.vue
@@ -0,0 +1,12 @@
+<template>
+ <ul>
+ <NavigationEntry
+ v-for="item in lists"
+ :key="item.name"
+ :show-pin="showPin"
+ :item="item"
+ />
+ </ul>
+</template>
+
+<script src="./lists_menu_content.js"></script>
diff --git a/src/components/lists_timeline/lists_timeline.js b/src/components/lists_timeline/lists_timeline.js
@@ -0,0 +1,36 @@
+import Timeline from '../timeline/timeline.vue'
+const ListsTimeline = {
+ data () {
+ return {
+ listId: null
+ }
+ },
+ components: {
+ Timeline
+ },
+ computed: {
+ timeline () { return this.$store.state.statuses.timelines.list }
+ },
+ watch: {
+ $route: function (route) {
+ if (route.name === 'lists-timeline' && route.params.id !== this.listId) {
+ this.listId = route.params.id
+ this.$store.dispatch('stopFetchingTimeline', 'list')
+ this.$store.commit('clearTimeline', { timeline: 'list' })
+ this.$store.dispatch('fetchList', { listId: this.listId })
+ this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId })
+ }
+ }
+ },
+ created () {
+ this.listId = this.$route.params.id
+ this.$store.dispatch('fetchList', { listId: this.listId })
+ this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId })
+ },
+ unmounted () {
+ this.$store.dispatch('stopFetchingTimeline', 'list')
+ this.$store.commit('clearTimeline', { timeline: 'list' })
+ }
+}
+
+export default ListsTimeline
diff --git a/src/components/lists_timeline/lists_timeline.vue b/src/components/lists_timeline/lists_timeline.vue
@@ -0,0 +1,10 @@
+<template>
+ <Timeline
+ title="list.name"
+ :timeline="timeline"
+ :list-id="listId"
+ timeline-name="list"
+ />
+</template>
+
+<script src="./lists_timeline.js"></script>
diff --git a/src/components/lists_user_search/lists_user_search.js b/src/components/lists_user_search/lists_user_search.js
@@ -0,0 +1,51 @@
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faSearch,
+ faChevronLeft
+} from '@fortawesome/free-solid-svg-icons'
+import { debounce } from 'lodash'
+import Checkbox from '../checkbox/checkbox.vue'
+
+library.add(
+ faSearch,
+ faChevronLeft
+)
+
+const ListsUserSearch = {
+ components: {
+ Checkbox
+ },
+ emits: ['loading', 'loadingDone', 'results'],
+ data () {
+ return {
+ loading: false,
+ query: '',
+ followingOnly: true
+ }
+ },
+ methods: {
+ onInput: debounce(function () {
+ this.search(this.query)
+ }, 2000),
+ search (query) {
+ if (!query) {
+ this.loading = false
+ return
+ }
+
+ 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))
+ })
+ .finally(() => {
+ this.loading = false
+ this.$emit('loadingDone')
+ })
+ }
+ }
+}
+
+export default ListsUserSearch
diff --git a/src/components/lists_user_search/lists_user_search.vue b/src/components/lists_user_search/lists_user_search.vue
@@ -0,0 +1,46 @@
+<template>
+ <div class="ListsUserSearch">
+ <div class="input-wrap">
+ <div class="input-search">
+ <FAIcon
+ class="search-icon fa-scale-110 fa-old-padding"
+ icon="search"
+ />
+ </div>
+ <input
+ ref="search"
+ v-model="query"
+ class="input"
+ :placeholder="$t('lists.search')"
+ @input="onInput"
+ >
+ </div>
+ <div class="input-wrap">
+ <Checkbox
+ v-model="followingOnly"
+ @change="onInput"
+ >
+ {{ $t('lists.following_only') }}
+ </Checkbox>
+ </div>
+ </div>
+</template>
+
+<script src="./lists_user_search.js"></script>
+<style lang="scss">
+.ListsUserSearch {
+ .input-wrap {
+ display: flex;
+ margin: 0.7em 0.5em;
+
+ input {
+ width: 100%;
+ }
+ }
+
+ .search-icon {
+ margin-right: 0.3em;
+ }
+}
+
+</style>
diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js
@@ -83,7 +83,7 @@ const LoginForm = {
},
clearError () { this.error = false },
focusOnPasswordInput () {
- let passwordInput = this.$refs.passwordInput
+ const passwordInput = this.$refs.passwordInput
passwordInput.focus()
passwordInput.setSelectionRange(0, passwordInput.value.length)
}
diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue
@@ -3,7 +3,9 @@
<!-- Default panel contents -->
<div class="panel-heading">
- {{ $t('login.login') }}
+ <h1 class="title">
+ {{ $t('login.login') }}
+ </h1>
</div>
<div class="panel-body">
@@ -18,7 +20,7 @@
id="username"
v-model="user.username"
:disabled="loggingIn"
- class="form-control"
+ class="input form-control"
:placeholder="$t('login.placeholder')"
>
</div>
@@ -29,7 +31,7 @@
ref="passwordInput"
v-model="user.password"
:disabled="loggingIn"
- class="form-control"
+ class="input form-control"
type="password"
>
</div>
@@ -90,11 +92,9 @@
</div>
</template>
-<script src="./login_form.js" ></script>
+<script src="./login_form.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.login-form {
display: flex;
flex-direction: column;
@@ -110,7 +110,7 @@
}
.login-bottom {
- margin-top: 1.0em;
+ margin-top: 1em;
display: flex;
flex-direction: row;
align-items: center;
@@ -121,7 +121,7 @@
display: flex;
flex-direction: column;
padding: 0.3em 0.5em 0.6em;
- line-height:24px;
+ line-height: 24px;
}
.form-bottom {
@@ -142,7 +142,6 @@
.error {
text-align: center;
-
animation-name: shakeError;
animation-duration: 0.4s;
animation-timing-function: ease-in-out;
diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
@@ -63,6 +63,11 @@ const MediaModal = {
},
type () {
return this.currentMedia ? this.getType(this.currentMedia) : null
+ },
+ swipeDisableClickThreshold () {
+ // If there is only one media, allow more mouse movements to close the modal
+ // because there is less chance that the user wants to switch to another image
+ return () => this.canNavigate ? 1 : 30
}
},
methods: {
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
@@ -10,6 +10,7 @@
class="modal-image-container"
:direction="swipeDirection"
:threshold="swipeThreshold"
+ :disable-click-threshold="swipeDisableClickThreshold"
@preview-requested="handleSwipePreview"
@swipe-finished="handleSwipeEnd"
@swipeless-clicked="hide"
@@ -120,32 +121,12 @@ $modal-view-button-icon-half-height: calc(#{$modal-view-button-icon-height} / 2)
$modal-view-button-icon-width: 3em;
$modal-view-button-icon-margin: 0.5em;
-.modal-view.media-modal-view {
- z-index: 9000;
- flex-direction: column;
-
- .modal-view-button-arrow,
- .modal-view-button-hide {
- opacity: 0.75;
-
- &:focus,
- &:hover {
- outline: none;
- box-shadow: none;
- }
-
- &:hover {
- opacity: 1;
- }
- }
- overflow: hidden;
-}
-
.media-modal-view {
@keyframes media-fadein {
from {
opacity: 0;
}
+
to {
opacity: 1;
}
@@ -226,7 +207,7 @@ $modal-view-button-icon-margin: 0.5em;
appearance: none;
overflow: visible;
cursor: pointer;
- transition: opacity 333ms cubic-bezier(.4,0,.22,1);
+ transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);
height: $modal-view-button-icon-height;
width: $modal-view-button-icon-width;
@@ -236,9 +217,9 @@ $modal-view-button-icon-margin: 0.5em;
width: $modal-view-button-icon-width;
font-size: 1rem;
line-height: $modal-view-button-icon-height;
- color: #FFF;
+ color: #fff;
text-align: center;
- background-color: rgba(0,0,0,.3);
+ background-color: rgb(0 0 0 / 30%);
}
}
@@ -254,13 +235,14 @@ $modal-view-button-icon-margin: 0.5em;
position: absolute;
top: 0;
line-height: $modal-view-button-icon-height;
- color: #FFF;
+ color: #fff;
text-align: center;
- background-color: rgba(0,0,0,.3);
+ background-color: rgb(0 0 0 / 30%);
}
&--prev {
left: 0;
+
.arrow-icon {
left: $modal-view-button-icon-margin;
}
@@ -268,6 +250,7 @@ $modal-view-button-icon-margin: 0.5em;
&--next {
right: 0;
+
.arrow-icon {
right: $modal-view-button-icon-margin;
}
@@ -278,10 +261,33 @@ $modal-view-button-icon-margin: 0.5em;
position: absolute;
top: 0;
right: 0;
+
.button-icon {
top: $modal-view-button-icon-margin;
right: $modal-view-button-icon-margin;
}
}
}
+
+.modal-view.media-modal-view {
+ z-index: var(--ZI_media_modal);
+ flex-direction: column;
+
+ .modal-view-button-arrow,
+ .modal-view-button-hide {
+ opacity: 0.75;
+
+ &:focus,
+ &:hover {
+ outline: none;
+ box-shadow: none;
+ }
+
+ &:hover {
+ opacity: 1;
+ }
+ }
+
+ overflow: hidden;
+}
</style>
diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js
@@ -23,6 +23,11 @@ const mediaUpload = {
}
},
methods: {
+ onClick () {
+ if (this.uploadReady) {
+ this.$refs.input.click()
+ }
+ },
uploadFile (file) {
const self = this
const store = this.$store
@@ -42,7 +47,8 @@ const mediaUpload = {
.then((fileData) => {
self.$emit('uploaded', fileData)
self.decreaseUploadCount()
- }, (error) => { // eslint-disable-line handle-callback-err
+ }, (error) => {
+ console.error('Error uploading file', error)
self.$emit('upload-failed', 'default')
self.decreaseUploadCount()
})
@@ -68,12 +74,17 @@ const mediaUpload = {
this.multiUpload(target.files)
}
},
- props: [
- 'dropFiles',
- 'disabled'
- ],
+ props: {
+ dropFiles: Object,
+ disabled: Boolean,
+ normalButton: Boolean,
+ acceptTypes: {
+ type: String,
+ default: '*/*'
+ }
+ },
watch: {
- 'dropFiles': function (fileInfos) {
+ dropFiles: function (fileInfos) {
if (!this.uploading) {
this.multiUpload(fileInfos)
}
diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue
@@ -1,8 +1,9 @@
<template>
- <label
+ <button
class="media-upload"
- :class="{ disabled: disabled }"
+ :class="[normalButton ? 'button-default btn' : 'button-unstyled', { disabled }]"
:title="$t('tool_tip.media_upload')"
+ @click="onClick"
>
<FAIcon
v-if="uploading"
@@ -15,27 +16,33 @@
class="new-icon"
icon="upload"
/>
+ <template v-if="normalButton">
+ {{ ' ' }}
+ {{ uploading ? $t('general.loading') : $t('tool_tip.media_upload') }}
+ </template>
<input
v-if="uploadReady"
+ ref="input"
class="hidden-input-file"
:disabled="disabled"
type="file"
multiple="true"
+ :accept="acceptTypes"
@change="change"
>
- </label>
+ </button>
</template>
-<script src="./media_upload.js" ></script>
+<script src="./media_upload.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.media-upload {
- cursor: pointer; // We use <label> for interactivity... i wonder if it's fine
-
.hidden-input-file {
display: none;
}
}
- </style>
+
+label.media-upload {
+ cursor: pointer; // We use <label> for interactivity... i wonder if it's fine
+}
+</style>
diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js
@@ -2,6 +2,8 @@ import generateProfileLink from 'src/services/user_profile_link_generator/user_p
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
@@ -14,7 +16,9 @@ library.add(
const MentionLink = {
name: 'MentionLink',
components: {
- UserAvatar
+ UserAvatar,
+ UnicodeDomainIndicator,
+ UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
},
props: {
url: {
@@ -34,15 +38,32 @@ const MentionLink = {
type: String
}
},
+ data () {
+ return {
+ hasSelection: false
+ }
+ },
methods: {
onClick () {
+ if (this.shouldShowTooltip) return
const link = generateProfileLink(
this.userId || this.user.id,
this.userScreenName || this.user.screen_name
)
this.$router.push(link)
+ },
+ handleSelection () {
+ if (this.$refs.full) {
+ this.hasSelection = document.getSelection().containsNode(this.$refs.full, true)
+ }
}
},
+ mounted () {
+ document.addEventListener('selectionchange', this.handleSelection)
+ },
+ unmounted () {
+ document.removeEventListener('selectionchange', this.handleSelection)
+ },
computed: {
user () {
return this.url && this.$store && this.$store.getters.findUserByUrl(this.url)
@@ -88,7 +109,8 @@ const MentionLink = {
return [
{
'-you': this.isYou && this.shouldBoldenYou,
- '-highlighted': this.highlight
+ '-highlighted': this.highlight,
+ '-has-selection': this.hasSelection
},
this.highlightType
]
@@ -110,7 +132,7 @@ const MentionLink = {
}
},
shouldShowTooltip () {
- return this.mergedConfig.mentionLinkShowTooltip && this.mergedConfig.mentionLinkDisplay === 'short' && this.isRemote
+ return this.mergedConfig.mentionLinkShowTooltip
},
shouldShowAvatar () {
return this.mergedConfig.mentionLinkShowAvatar
diff --git a/src/components/mention_link/mention_link.scss b/src/components/mention_link/mention_link.scss
@@ -1,10 +1,7 @@
-@import '../../_variables.scss';
-
.MentionLink {
position: relative;
white-space: normal;
display: inline;
- color: var(--link);
word-break: normal;
& .new,
@@ -14,7 +11,7 @@
}
.mention-avatar {
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
+ border-radius: var(--roundness);
width: 1.5em;
height: 1.5em;
vertical-align: middle;
@@ -55,12 +52,18 @@
.new {
&.-you {
- & .shortName,
- & .full {
+ .shortName {
font-weight: 600;
}
}
+ &.-has-selection {
+ --color: var(--selectionText);
+ --link: var(--selectionText);
+
+ background-color: var(--selectionBackground);
+ }
+
.at {
color: var(--link);
opacity: 0.8;
@@ -72,8 +75,7 @@
}
&.-striped {
- & .shortName,
- & .full {
+ & .shortName {
background-image:
repeating-linear-gradient(
135deg,
@@ -86,30 +88,25 @@
}
&.-solid {
- & .shortName,
- & .full {
+ .shortName {
background-image: linear-gradient(var(--____highlight-tintColor2), var(--____highlight-tintColor2));
}
}
&.-side {
- & .shortName,
- & .userNameFull {
+ .shortName {
box-shadow: 0 -5px 3px -4px inset var(--____highlight-solidColor);
}
}
}
- &:hover .new .full {
- opacity: 1;
- pointer-events: initial;
- }
-
.serverName.-faded {
- color: var(--faintLink, $fallback--link);
+ color: var(--linkFaint);
}
+}
- .full .-faded {
- color: var(--faint, $fallback--faint);
- }
+.mention-link-popover {
+ max-width: 70ch;
+ max-height: 20rem;
+ overflow: hidden;
}
diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue
@@ -9,69 +9,67 @@
class="original"
target="_blank"
v-html="content"
- /><!-- eslint-enable vue/no-v-html --><span
- v-if="user"
- class="new"
- :style="style"
- :class="classnames"
+ /><!-- eslint-enable vue/no-v-html -->
+ <UserPopover
+ v-else
+ :user-id="user.id"
+ :disabled="!shouldShowTooltip"
>
- <a
- class="short button-unstyled"
- :class="{ '-with-tooltip': shouldShowTooltip }"
- :href="url"
- @click.prevent="onClick"
+ <span
+ v-if="user"
+ class="new"
+ :style="style"
+ :class="classnames"
>
- <!-- eslint-disable vue/no-v-html -->
- <UserAvatar
- v-if="shouldShowAvatar"
- class="mention-avatar"
- :user="user"
- /><span
- class="shortName"
- ><FAIcon
- v-if="useAtIcon"
- size="sm"
- icon="at"
- class="at"
- />{{ !useAtIcon ? '@' : '' }}<span
- class="userName"
- v-html="userName"
- /><span
- v-if="shouldShowFullUserName"
- class="serverName"
- :class="{ '-faded': shouldFadeDomain }"
- v-html="'@' + serverName"
- />
- </span>
- <span
- v-if="isYou && shouldShowYous"
- :class="{ '-you': shouldBoldenYou }"
- > {{ ' ' + $t('status.you') }}</span>
- <!-- eslint-enable vue/no-v-html -->
- </a><span
- v-if="shouldShowTooltip"
- class="full popover-default"
- :class="[highlightType]"
- >
- <span
- class="userNameFull"
+ <a
+ class="short"
+ :class="{ '-with-tooltip': shouldShowTooltip }"
+ :href="url"
+ @click.prevent="onClick"
>
<!-- eslint-disable vue/no-v-html -->
- @<span
+ <UserAvatar
+ v-if="shouldShowAvatar"
+ class="mention-avatar"
+ :user="user"
+ /><span
+ class="shortName"
+ ><FAIcon
+ v-if="useAtIcon"
+ size="sm"
+ icon="at"
+ class="at"
+ />{{ !useAtIcon ? '@' : '' }}<span
class="userName"
v-html="userName"
/><span
+ v-if="shouldShowFullUserName"
class="serverName"
:class="{ '-faded': shouldFadeDomain }"
v-html="'@' + serverName"
+ /><UnicodeDomainIndicator
+ v-if="shouldShowFullUserName"
+ :user="user"
/>
+ </span>
+ <span
+ v-if="isYou && shouldShowYous"
+ :class="{ '-you': shouldBoldenYou }"
+ > {{ ' ' + $t('status.you') }}</span>
+ <!-- eslint-enable vue/no-v-html -->
+ </a><span
+ ref="full"
+ class="full"
+ >
+ <!-- eslint-disable vue/no-v-html -->
+ @<span v-html="userName" /><span v-html="'@' + serverName" />
<!-- eslint-enable vue/no-v-html -->
</span>
</span>
- </span>
+ </UserPopover>
</span>
</template>
-<script src="./mention_link.js"/>
+<script src="./mention_link.js" />
-<style lang="scss" src="./mention_link.scss"/>
+<style lang="scss" src="./mention_link.scss" />
diff --git a/src/components/mentions_line/mentions_line.scss b/src/components/mentions_line/mentions_line.scss
@@ -2,7 +2,7 @@
word-break: break-all;
.mention-link:not(:first-child)::before {
- content: ' ';
+ content: " ";
}
.showMoreLess {
diff --git a/src/components/mentions_line/mentions_line.vue b/src/components/mentions_line/mentions_line.vue
@@ -13,23 +13,22 @@
<span
v-if="expanded"
class="fullExtraMentions"
- >
- <MentionLink
- v-for="mention in extraMentions"
- :key="mention.index"
- class="mention-link"
- :content="mention.content"
- :url="mention.url"
- />
+ >{{ ' ' }}<MentionLink
+ v-for="mention in extraMentions"
+ :key="mention.index"
+ class="mention-link"
+ :content="mention.content"
+ :url="mention.url"
+ />
</span><button
v-if="!expanded"
- class="button-unstyled showMoreLess"
+ class="button-unstyled -link showMoreLess"
@click="toggleShowMore"
>
{{ $t('status.plus_more', { number: extraMentions.length }) }}
</button><button
v-if="expanded"
- class="button-unstyled showMoreLess"
+ class="button-unstyled -link showMoreLess"
@click="toggleShowMore"
>
{{ $t('general.show_less') }}
@@ -37,5 +36,5 @@
</span>
</span>
</template>
-<script src="./mentions_line.js" ></script>
+<script src="./mentions_line.js"></script>
<style lang="scss" src="./mentions_line.scss" />
diff --git a/src/components/menu_item.style.js b/src/components/menu_item.style.js
@@ -0,0 +1,90 @@
+export default {
+ name: 'MenuItem',
+ selector: '.menu-item',
+ validInnerComponents: [
+ 'Text',
+ 'Icon',
+ 'Input',
+ 'Border',
+ 'ButtonUnstyled',
+ 'Badge',
+ 'Avatar'
+ ],
+ states: {
+ hover: ':hover',
+ active: '.-active'
+ },
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg',
+ opacity: 0
+ }
+ },
+ {
+ state: ['hover'],
+ directives: {
+ background: '$mod(--bg 5)',
+ opacity: 1
+ }
+ },
+ {
+ state: ['active'],
+ directives: {
+ background: '$mod(--bg 10)',
+ opacity: 1
+ }
+ },
+ {
+ state: ['active', 'hover'],
+ directives: {
+ background: '$mod(--bg 15)',
+ opacity: 1
+ }
+ },
+ {
+ component: 'Text',
+ parent: {
+ component: 'MenuItem',
+ state: ['hover']
+ },
+ directives: {
+ textColor: '--link',
+ textAuto: 'no-preserve'
+ }
+ },
+ {
+ component: 'Text',
+ parent: {
+ component: 'MenuItem',
+ state: ['active']
+ },
+ directives: {
+ textColor: '--link',
+ textAuto: 'no-preserve'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'MenuItem',
+ state: ['active']
+ },
+ directives: {
+ textColor: '--link',
+ textAuto: 'no-preserve'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'MenuItem',
+ state: ['hover']
+ },
+ directives: {
+ textColor: '--link',
+ textAuto: 'no-preserve'
+ }
+ }
+ ]
+}
diff --git a/src/components/mfa_form/recovery_form.vue b/src/components/mfa_form/recovery_form.vue
@@ -3,7 +3,9 @@
<!-- Default panel contents -->
<div class="panel-heading">
- {{ $t('login.heading.recovery') }}
+ <h1 class="title">
+ {{ $t('login.heading.recovery') }}
+ </h1>
</div>
<div class="panel-body">
@@ -16,7 +18,7 @@
<input
id="code"
v-model="code"
- class="form-control"
+ class="input form-control"
>
</div>
@@ -69,4 +71,4 @@
</div>
</div>
</template>
-<script src="./recovery_form.js" ></script>
+<script src="./recovery_form.js"></script>
diff --git a/src/components/mfa_form/totp_form.vue b/src/components/mfa_form/totp_form.vue
@@ -3,7 +3,9 @@
<!-- Default panel contents -->
<div class="panel-heading">
- {{ $t('login.heading.totp') }}
+ <h1 class="title">
+ {{ $t('login.heading.totp') }}
+ </h1>
</div>
<div class="panel-body">
@@ -18,7 +20,7 @@
<input
id="code"
v-model="code"
- class="form-control"
+ class="input form-control"
>
</div>
diff --git a/src/components/mobile_drawer.style.js b/src/components/mobile_drawer.style.js
@@ -0,0 +1,41 @@
+export default {
+ name: 'MobileDrawer',
+ selector: '.mobile-drawer',
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'ButtonUnstyled',
+ 'Input',
+ 'PanelHeader',
+ 'MenuItem',
+ 'Notification',
+ 'Alert',
+ 'UserCard'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg',
+ 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
@@ -1,34 +1,49 @@
import SideDrawer from '../side_drawer/side_drawer.vue'
import Notifications from '../notifications/notifications.vue'
-import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
+import ConfirmModal from '../confirm_modal/confirm_modal.vue'
+import {
+ unseenNotificationsFromStore,
+ countExtraNotifications
+} from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service'
+import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import { mapGetters } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes,
faBell,
- faBars
+ faBars,
+ faArrowUp,
+ faMinus,
+ faCheckDouble
} from '@fortawesome/free-solid-svg-icons'
library.add(
faTimes,
faBell,
- faBars
+ faBars,
+ faArrowUp,
+ faMinus,
+ faCheckDouble
)
const MobileNav = {
components: {
SideDrawer,
- Notifications
+ Notifications,
+ NavigationPins,
+ ConfirmModal
},
data: () => ({
notificationsCloseGesture: undefined,
- notificationsOpen: false
+ notificationsOpen: false,
+ notificationsAtTop: true,
+ showingConfirmLogout: false
}),
created () {
this.notificationsCloseGesture = GestureService.swipeGesture(
GestureService.DIRECTION_RIGHT,
- this.closeMobileNotifications,
+ () => this.closeMobileNotifications(true),
50
)
},
@@ -40,13 +55,29 @@ const MobileNav = {
return unseenNotificationsFromStore(this.$store)
},
unseenNotificationsCount () {
+ return this.unseenNotifications.length + countExtraNotifications(this.$store)
+ },
+ unseenCount () {
return this.unseenNotifications.length
},
+ unseenCountBadgeText () {
+ return `${this.unseenCount ? this.unseenCount : ''}`
+ },
hideSitename () { return this.$store.state.instance.hideSitename },
sitename () { return this.$store.state.instance.name },
isChat () {
return this.$route.name === 'chat'
},
+ ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount']),
+ chatsPinned () {
+ return new Set(this.$store.state.serverSideStorage.prefsStorage.collections.pinnedNavItems).has('chats')
+ },
+ shouldConfirmLogout () {
+ return this.$store.getters.mergedConfig.modalOnLogout
+ },
+ closingDrawerMarksAsSeen () {
+ return this.$store.getters.mergedConfig.closingDrawerMarksAsSeen
+ },
...mapGetters(['unreadChatCount'])
},
methods: {
@@ -56,12 +87,14 @@ const MobileNav = {
openMobileNotifications () {
this.notificationsOpen = true
},
- closeMobileNotifications () {
+ closeMobileNotifications (markRead) {
if (this.notificationsOpen) {
// make sure to mark notifs seen only when the notifs were open and not
// from close-calls.
this.notificationsOpen = false
- this.markNotificationsAsSeen()
+ if (markRead && this.closingDrawerMarksAsSeen) {
+ this.markNotificationsAsSeen()
+ }
}
},
notificationsTouchStart (e) {
@@ -73,15 +106,32 @@ const MobileNav = {
scrollToTop () {
window.scrollTo(0, 0)
},
+ scrollMobileNotificationsToTop () {
+ this.$refs.mobileNotifications.scrollTo(0, 0)
+ },
+ showConfirmLogout () {
+ this.showingConfirmLogout = true
+ },
+ hideConfirmLogout () {
+ this.showingConfirmLogout = false
+ },
logout () {
+ if (!this.shouldConfirmLogout) {
+ this.doLogout()
+ } else {
+ this.showConfirmLogout()
+ }
+ },
+ doLogout () {
this.$router.replace('/main/public')
this.$store.dispatch('logout')
+ this.hideConfirmLogout()
},
markNotificationsAsSeen () {
- // this.$refs.notifications.markAsSeen()
this.$store.dispatch('markNotificationsAsSeen')
},
onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) {
+ this.notificationsAtTop = scrollTop > 0
if (scrollTop + clientHeight >= scrollHeight) {
this.$refs.notifications.fetchOlderNotifications()
}
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
@@ -10,6 +10,8 @@
<div class="item">
<button
class="button-unstyled mobile-nav-button"
+ :title="$t('nav.mobile_sidebar')"
+ :aria-expanaded="$refs.sideDrawer && !$refs.sideDrawer.closed"
@click.stop.prevent="toggleMobileSidebar()"
>
<FAIcon
@@ -17,23 +19,16 @@
icon="bars"
/>
<div
- v-if="unreadChatCount"
- class="alert-dot"
+ v-if="(unreadChatCount && !chatsPinned) || unreadAnnouncementCount"
+ class="badge -dot -notification"
/>
</button>
- <router-link
- v-if="!hideSitename"
- class="site-name"
- :to="{ name: 'root' }"
- active-class="home"
- >
- {{ sitename }}
- </router-link>
- </div>
- <div class="item right">
+ <NavigationPins class="pins" />
+ </div> <div class="item right">
<button
v-if="currentUser"
class="button-unstyled mobile-nav-button"
+ :title="unseenNotificationsCount ? $t('nav.mobile_notifications_unread_active') : $t('nav.mobile_notifications')"
@click.stop.prevent="openMobileNotifications()"
>
<FAIcon
@@ -42,59 +37,105 @@
/>
<div
v-if="unseenNotificationsCount"
- class="alert-dot"
+ class="badge -dot -notification"
/>
</button>
</div>
</nav>
- <div
+ <aside
v-if="currentUser"
- class="mobile-notifications-drawer"
+ class="mobile-notifications-drawer mobile-drawer"
:class="{ '-closed': !notificationsOpen }"
@touchstart.stop="notificationsTouchStart"
@touchmove.stop="notificationsTouchMove"
>
- <div class="mobile-notifications-header">
- <span class="title">{{ $t('notifications.notifications') }}</span>
- <a
- class="mobile-nav-button"
- @click.stop.prevent="closeMobileNotifications()"
+ <div class="panel-heading mobile-notifications-header">
+ <h1 class="title">
+ {{ $t('notifications.notifications') }}
+ <span
+ v-if="unseenCountBadgeText"
+ class="badge -notification unseen-count"
+ >{{ unseenCountBadgeText }}</span>
+ </h1>
+ <span class="spacer" />
+ <button
+ v-if="notificationsAtTop"
+ class="button-unstyled mobile-nav-button"
+ :title="$t('general.scroll_to_top')"
+ @click.stop.prevent="scrollMobileNotificationsToTop"
+ >
+ <FALayers class="fa-scale-110 fa-old-padding-layer">
+ <FAIcon icon="arrow-up" />
+ <FAIcon
+ icon="minus"
+ transform="up-7"
+ />
+ </FALayers>
+ </button>
+ <button
+ v-if="!closingDrawerMarksAsSeen"
+ class="button-unstyled mobile-nav-button"
+ :title="$t('nav.mobile_notifications_mark_as_seen')"
+ @click.stop.prevent="markNotificationsAsSeen()"
+ >
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="check-double"
+ />
+ </button>
+ <button
+ class="button-unstyled mobile-nav-button"
+ :title="$t('nav.mobile_notifications_close')"
+ @click.stop.prevent="closeMobileNotifications(true)"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="times"
/>
- </a>
+ </button>
</div>
<div
- class="mobile-notifications"
id="mobile-notifications"
+ ref="mobileNotifications"
+ class="mobile-notifications"
@scroll="onScroll"
- >
- </div>
- </div>
+ />
+ </aside>
<SideDrawer
ref="sideDrawer"
:logout="logout"
/>
+ <teleport to="#modal">
+ <confirm-modal
+ v-if="showingConfirmLogout"
+ :title="$t('login.logout_confirm_title')"
+ :confirm-text="$t('login.logout_confirm_accept_button')"
+ :cancel-text="$t('login.logout_confirm_cancel_button')"
+ @accepted="doLogout"
+ @cancelled="hideConfirmLogout"
+ >
+ {{ $t('login.logout_confirm') }}
+ </confirm-modal>
+ </teleport>
</div>
</template>
<script src="./mobile_nav.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.MobileNav {
+ z-index: var(--ZI_navbar);
+
.mobile-nav {
display: grid;
line-height: var(--navbar-height);
- grid-template-rows: 50px;
+ grid-template-rows: var(--navbar-height);
grid-template-columns: 2fr auto;
width: 100%;
box-sizing: border-box;
+
a {
- color: var(--topBarLink, $fallback--link);
+ color: var(--link);
}
}
@@ -113,7 +154,7 @@
}
.site-name {
- padding: 0 .3em;
+ padding: 0 0.3em;
display: inline-block;
}
@@ -122,19 +163,6 @@
display: flex;
}
- .alert-dot {
- border-radius: 100%;
- height: 8px;
- width: 8px;
- position: absolute;
- left: calc(50% - 4px);
- top: calc(50% - 4px);
- margin-left: 6px;
- margin-top: -6px;
- background-color: $fallback--cRed;
- background-color: var(--badgeNotification, $fallback--cRed);
- }
-
.mobile-notifications-drawer {
width: 100%;
height: 100vh;
@@ -142,13 +170,13 @@
position: fixed;
top: 0;
left: 0;
- box-shadow: 1px 1px 4px rgba(0,0,0,.6);
- box-shadow: var(--panelShadow);
+ box-shadow: var(--shadow);
transition-property: transform;
transition-duration: 0.25s;
transform: translateX(0);
- z-index: 1001;
+ z-index: var(--ZI_navbar);
-webkit-overflow-scrolling: touch;
+ background: var(--background);
&.-closed {
transform: translateX(100%);
@@ -160,16 +188,16 @@
display: flex;
align-items: center;
justify-content: space-between;
- z-index: 1;
+ z-index: calc(var(--ZI_navbar) + 100);
width: 100%;
- height: 50px;
- line-height: 50px;
+ height: 3.5em;
+ line-height: 3.5em;
position: absolute;
- color: var(--topBarText);
- background-color: $fallback--fg;
- background-color: var(--topBar, $fallback--fg);
- box-shadow: 0px 0px 4px rgba(0,0,0,.6);
- box-shadow: var(--topBarShadow);
+ box-shadow: var(--shadow);
+
+ .spacer {
+ flex: 1;
+ }
.title {
font-size: 1.3em;
@@ -177,36 +205,52 @@
}
}
+ .pins {
+ flex: 1;
+
+ .pinned-item {
+ flex-grow: 1;
+ }
+ }
+
.mobile-notifications {
- margin-top: 50px;
+ margin-top: 3.5em;
width: 100vw;
height: calc(100vh - var(--navbar-height));
overflow-x: hidden;
overflow-y: scroll;
- color: $fallback--text;
- color: var(--text, $fallback--text);
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
-
.notifications {
padding: 0;
border-radius: 0;
box-shadow: none;
+
.panel {
border-radius: 0;
margin: 0;
box-shadow: none;
}
- .panel:after {
+
+ .panel::after {
border-radius: 0;
}
+
.panel .panel-heading {
border-radius: 0;
box-shadow: none;
}
}
}
+
+ .confirm-modal.dark-overlay {
+ &::before {
+ z-index: 3000;
+ }
+
+ .dialog-modal.panel {
+ z-index: 3001;
+ }
+ }
}
</style>
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
@@ -10,7 +10,8 @@ library.add(
const HIDDEN_FOR_PAGES = new Set([
'chats',
- 'chat'
+ 'chat',
+ 'lists-edit'
])
const MobilePostStatusButton = {
diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.vue b/src/components/mobile_post_status_button/mobile_post_status_button.vue
@@ -3,6 +3,7 @@
v-if="isLoggedIn"
class="MobilePostButton button-default new-status-button"
:class="{ 'hidden': isHidden, 'always-show': isPersistent }"
+ :title="$t('post_status.new_status')"
@click="openPostForm"
>
<FAIcon icon="pen" />
@@ -12,8 +13,6 @@
<script src="./mobile_post_status_button.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.MobilePostButton {
&.button-default {
width: 5em;
@@ -24,14 +23,11 @@
right: 1.5em;
// TODO: this needs its own color, it has to stand out enough and link color
// is not very optimal for this particular use.
- background-color: $fallback--fg;
- background-color: var(--btn, $fallback--fg);
display: flex;
justify-content: center;
align-items: center;
- box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3), 0px 4px 6px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 2px 2px rgb(0 0 0 / 30%), 0 4px 6px rgb(0 0 0 / 30%);
z-index: 10;
-
transition: 0.35s transform;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
}
@@ -42,8 +38,7 @@
svg {
font-size: 1.5em;
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
}
diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue
@@ -12,6 +12,9 @@
<script>
export default {
+ provide: {
+ popoversZLayer: 'modals'
+ },
props: {
isOpen: {
type: Boolean,
@@ -26,7 +29,7 @@ export default {
classes () {
return {
'modal-background': !this.noBackground,
- 'open': this.isOpen
+ open: this.isOpen
}
}
}
@@ -35,7 +38,7 @@ export default {
<style lang="scss">
.modal-view {
- z-index: 2000;
+ z-index: var(--ZI_modals);
position: fixed;
top: 0;
left: 0;
@@ -56,7 +59,7 @@ export default {
&.modal-background {
pointer-events: initial;
- background-color: rgba(0, 0, 0, 0.5);
+ background-color: rgb(0 0 0 / 50%);
}
&.open {
@@ -66,10 +69,11 @@ export default {
@keyframes modal-background-fadein {
from {
- background-color: rgba(0, 0, 0, 0);
+ background-color: rgb(0 0 0 / 0%);
}
+
to {
- background-color: rgba(0, 0, 0, 0.5);
+ background-color: rgb(0 0 0 / 50%);
}
}
</style>
diff --git a/src/components/modal/modals.style.js b/src/components/modal/modals.style.js
@@ -0,0 +1,10 @@
+export default {
+ name: 'Modals',
+ selector: ['.modal-view', '#modal', '.shout-panel'],
+ lazy: true,
+ notEditable: true,
+ validInnerComponents: [
+ 'Panel'
+ ],
+ defaultRules: []
+}
diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js
@@ -41,14 +41,26 @@ const ModerationTools = {
tagsSet () {
return new Set(this.user.tags)
},
- hasTagPolicy () {
- return this.$store.state.instance.tagPolicyAvailable
+ canGrantRole () {
+ return this.user.is_local && !this.user.deactivated && this.$store.state.users.currentUser.role === 'admin'
+ },
+ canChangeActivationState () {
+ return this.privileged('users_manage_activation_state')
+ },
+ canDeleteAccount () {
+ return this.privileged('users_delete')
+ },
+ canUseTagPolicy () {
+ return this.$store.state.instance.tagPolicyAvailable && this.privileged('users_manage_tags')
}
},
methods: {
hasTag (tagName) {
return this.tagsSet.has(tagName)
},
+ privileged (privilege) {
+ return this.$store.state.users.currentUser.privileges.includes(privilege)
+ },
toggleTag (tag) {
const store = this.$store
if (this.tagsSet.has(tag)) {
diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue
@@ -8,113 +8,116 @@
@show="setToggled(true)"
@close="setToggled(false)"
>
- <template v-slot:content>
+ <template #content>
<div class="dropdown-menu">
- <span v-if="user.is_local">
+ <span v-if="canGrantRole">
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleRight("admin")"
>
{{ $t(!!user.rights.admin ? 'user_card.admin_menu.revoke_admin' : 'user_card.admin_menu.grant_admin') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleRight("moderator")"
>
{{ $t(!!user.rights.moderator ? 'user_card.admin_menu.revoke_moderator' : 'user_card.admin_menu.grant_moderator') }}
</button>
<div
+ v-if="canChangeActivationState || canDeleteAccount"
role="separator"
class="dropdown-divider"
/>
</span>
<button
- class="button-default dropdown-item"
+ v-if="canChangeActivationState"
+ class="menu-item dropdown-item menu-item"
@click="toggleActivationStatus()"
>
{{ $t(!!user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account') }}
</button>
<button
- class="button-default dropdown-item"
+ v-if="canDeleteAccount"
+ class="menu-item dropdown-item menu-item"
@click="deleteUserDialog(true)"
>
{{ $t('user_card.admin_menu.delete_account') }}
</button>
<div
- v-if="hasTagPolicy"
+ v-if="canUseTagPolicy"
role="separator"
class="dropdown-divider"
/>
- <span v-if="hasTagPolicy">
+ <span v-if="canUseTagPolicy">
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.FORCE_NSFW)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_NSFW) }"
/>
{{ $t('user_card.admin_menu.force_nsfw') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.STRIP_MEDIA)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.STRIP_MEDIA) }"
/>
{{ $t('user_card.admin_menu.strip_media') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.FORCE_UNLISTED)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_UNLISTED) }"
/>
{{ $t('user_card.admin_menu.force_unlisted') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.SANDBOX)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.SANDBOX) }"
/>
{{ $t('user_card.admin_menu.sandbox') }}
</button>
<button
v-if="user.is_local"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_REMOTE_SUBSCRIPTION) }"
/>
{{ $t('user_card.admin_menu.disable_remote_subscription') }}
</button>
<button
v-if="user.is_local"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_ANY_SUBSCRIPTION) }"
/>
{{ $t('user_card.admin_menu.disable_any_subscription') }}
</button>
<button
v-if="user.is_local"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.QUARANTINE)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.QUARANTINE) }"
/>
{{ $t('user_card.admin_menu.quarantine') }}
@@ -122,7 +125,7 @@
</span>
</div>
</template>
- <template v-slot:trigger>
+ <template #trigger>
<button
class="btn button-default btn-block moderation-tools-button"
:class="{ toggled }"
@@ -137,11 +140,11 @@
v-if="showDeleteUserDialog"
:on-cancel="deleteUserDialog.bind(this, false)"
>
- <template v-slot:header>
+ <template #header>
{{ $t('user_card.admin_menu.delete_user') }}
</template>
<p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p>
- <template v-slot:footer>
+ <template #footer>
<button
class="btn button-default"
@click="deleteUserDialog(false)"
@@ -163,18 +166,19 @@
<script src="./moderation_tools.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.moderation-tools-popover {
height: 100%;
+
.trigger {
+ /* stylelint-disable-next-line declaration-no-important */
display: flex !important;
height: 100%;
}
}
.moderation-tools-button {
- svg,i {
+ 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
@@ -9,10 +9,10 @@ import { get } from 'lodash'
*/
const toInstanceReasonObject = (instances, info, key) => {
return instances.map(instance => {
- if (info[key] && info[key][instance] && info[key][instance]['reason']) {
- return { instance: instance, reason: info[key][instance]['reason'] }
+ if (info[key] && info[key][instance] && info[key][instance].reason) {
+ return { instance, reason: info[key][instance].reason }
}
- return { instance: instance, reason: '' }
+ return { instance, reason: '' }
})
}
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.scss b/src/components/mrf_transparency_panel/mrf_transparency_panel.scss
@@ -2,19 +2,21 @@
margin: 1em;
table {
- width:100%;
+ width: 100%;
text-align: left;
- padding-left:10px;
- padding-bottom:20px;
+ padding-left: 10px;
+ padding-bottom: 20px;
- th, td {
+ th,
+ td {
width: 180px;
max-width: 360px;
- overflow: hidden;
+ overflow: hidden;
vertical-align: text-top;
}
- th+th, td+td {
+ th + th,
+ td + td {
width: auto;
}
}
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
@@ -227,6 +227,5 @@
<script src="./mrf_transparency_panel.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-@import './mrf_transparency_panel.scss';
+@import "./mrf_transparency_panel";
</style>
diff --git a/src/components/mute_card/mute_card.vue b/src/components/mute_card/mute_card.vue
@@ -37,6 +37,7 @@
.mute-card-content-container {
margin-top: 0.5em;
text-align: right;
+
button {
width: 10em;
}
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
@@ -1,5 +1,11 @@
-import TimelineMenuContent from '../timeline_menu/timeline_menu_content.vue'
+import BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue'
+import ListsMenuContent from 'src/components/lists_menu/lists_menu_content.vue'
import { mapState, mapGetters } from 'vuex'
+import { TIMELINES, ROOT_ITEMS } from 'src/components/navigation/navigation.js'
+import { filterNavigation } from 'src/components/navigation/filter.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 { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -12,7 +18,9 @@ import {
faComments,
faBell,
faInfoCircle,
- faStream
+ faStream,
+ faList,
+ faBullhorn
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -25,26 +33,58 @@ library.add(
faComments,
faBell,
faInfoCircle,
- faStream
+ faStream,
+ faList,
+ faBullhorn
)
-
const NavPanel = {
+ props: ['forceExpand', 'forceEditMode'],
created () {
- if (this.currentUser && this.currentUser.locked) {
- this.$store.dispatch('startFetchingFollowRequests')
- }
},
components: {
- TimelineMenuContent
+ BookmarkFoldersMenuContent,
+ ListsMenuContent,
+ NavigationEntry,
+ NavigationPins,
+ Checkbox
},
data () {
return {
- showTimelines: false
+ 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 }))
}
},
methods: {
toggleTimelines () {
this.showTimelines = !this.showTimelines
+ },
+ toggleLists () {
+ this.showLists = !this.showLists
+ },
+ toggleBookmarkFolders () {
+ this.showBookmarkFolders = !this.showBookmarkFolders
+ },
+ toggleEditMode () {
+ this.editMode = !this.editMode
+ },
+ toggleCollapse () {
+ this.$store.commit('setPreference', { path: 'simple.collapseNav', value: !this.collapsed })
+ this.$store.dispatch('pushServerSideStorage')
+ },
+ isPinned (item) {
+ return this.pinnedItems.has(item)
+ },
+ togglePin (item) {
+ if (this.isPinned(item)) {
+ this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedNavItems', value: item })
+ } else {
+ this.$store.commit('addCollectionPreference', { path: 'collections.pinnedNavItems', value: item })
+ }
+ this.$store.dispatch('pushServerSideStorage')
}
},
computed: {
@@ -53,9 +93,43 @@ const NavPanel = {
followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private,
federating: state => state.instance.federating,
- pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
+ pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
+ supportsAnnouncements: state => state.announcements.supportsAnnouncements,
+ pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems),
+ collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav,
+ bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable
}),
- ...mapGetters(['unreadChatCount'])
+ timelinesItems () {
+ return filterNavigation(
+ Object
+ .entries({ ...TIMELINES })
+ .map(([k, v]) => ({ ...v, name: k })),
+ {
+ hasChats: this.pleromaChatMessagesAvailable,
+ hasAnnouncements: this.supportsAnnouncements,
+ isFederating: this.federating,
+ isPrivate: this.privateMode,
+ currentUser: this.currentUser,
+ supportsBookmarkFolders: this.bookmarkFolders
+ }
+ )
+ },
+ rootItems () {
+ return filterNavigation(
+ Object
+ .entries({ ...ROOT_ITEMS })
+ .map(([k, v]) => ({ ...v, name: k })),
+ {
+ hasChats: this.pleromaChatMessagesAvailable,
+ hasAnnouncements: this.supportsAnnouncements,
+ isFederating: this.federating,
+ isPrivate: this.privateMode,
+ currentUser: this.currentUser,
+ supportsBookmarkFolders: this.bookmarkFolders
+ }
+ )
+ },
+ ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
}
}
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
@@ -1,104 +1,145 @@
<template>
<div class="NavPanel">
<div class="panel panel-default">
- <ul>
- <li v-if="currentUser || !privateMode">
- <button
- class="button-unstyled menu-item"
- @click="toggleTimelines"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110"
- icon="stream"
- />{{ $t("nav.timelines") }}
- <FAIcon
- class="timelines-chevron"
- fixed-width
- :icon="showTimelines ? 'chevron-up' : 'chevron-down'"
+ <div
+ v-if="!forceExpand"
+ class="panel-heading nav-panel-heading"
+ >
+ <NavigationPins :limit="6" />
+ <div class="spacer" />
+ <button
+ class="button-unstyled"
+ @click="toggleCollapse"
+ >
+ <FAIcon
+ class="navigation-chevron"
+ fixed-width
+ :icon="collapsed ? 'chevron-down' : 'chevron-up'"
+ />
+ </button>
+ </div>
+ <ul
+ v-if="!collapsed || forceExpand"
+ class="panel-body"
+ >
+ <NavigationEntry
+ v-if="currentUser || !privateMode"
+ :show-pin="false"
+ :item="{ icon: 'stream', label: 'nav.timelines' }"
+ :aria-expanded="showTimelines ? 'true' : 'false'"
+ @click="toggleTimelines"
+ >
+ <FAIcon
+ class="timelines-chevron"
+ fixed-width
+ :icon="showTimelines ? 'chevron-up' : 'chevron-down'"
+ />
+ </NavigationEntry>
+ <div
+ v-show="showTimelines"
+ class="timelines-background menu-item-collapsible"
+ :class="{ '-expanded': showTimelines }"
+ >
+ <div class="timelines">
+ <NavigationEntry
+ v-for="item in timelinesItems"
+ :key="item.name"
+ :show-pin="editMode || forceEditMode"
+ :item="item"
/>
- </button>
- <div
- v-show="showTimelines"
- class="timelines-background"
- >
- <TimelineMenuContent class="timelines" />
</div>
- </li>
- <li v-if="currentUser">
+ </div>
+ <NavigationEntry
+ v-if="currentUser"
+ :show-pin="false"
+ :item="{ icon: 'list', label: 'nav.lists' }"
+ :aria-expanded="showLists ? 'true' : 'false'"
+ @click="toggleLists"
+ >
<router-link
- class="menu-item"
- :to="{ name: 'interactions', params: { username: currentUser.screen_name } }"
+ :title="$t('lists.manage_lists')"
+ class="button-unstyled extra-button"
+ :to="{ name: 'lists' }"
+ @click.stop
>
<FAIcon
fixed-width
- class="fa-scale-110"
- icon="bell"
- />{{ $t("nav.interactions") }}
- </router-link>
- </li>
- <li v-if="currentUser && pleromaChatMessagesAvailable">
- <router-link
- class="menu-item"
- :to="{ name: 'chats', params: { username: currentUser.screen_name } }"
- >
- <div
- v-if="unreadChatCount"
- class="badge badge-notification"
- >
- {{ unreadChatCount }}
- </div>
- <FAIcon
- fixed-width
- class="fa-scale-110"
- icon="comments"
- />{{ $t("nav.chats") }}
- </router-link>
- </li>
- <li v-if="currentUser && currentUser.locked">
- <router-link
- class="menu-item"
- :to="{ name: 'friend-requests' }"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110"
- icon="user-plus"
- />{{ $t("nav.friend_requests") }}
- <span
- v-if="followRequestCount > 0"
- class="badge badge-notification"
- >
- {{ followRequestCount }}
- </span>
+ icon="wrench"
+ />
</router-link>
- </li>
- <li>
+ <FAIcon
+ class="timelines-chevron"
+ fixed-width
+ :icon="showLists ? 'chevron-up' : 'chevron-down'"
+ />
+ </NavigationEntry>
+ <div
+ v-show="showLists"
+ class="timelines-background menu-item-collapsible"
+ :class="{ '-expanded': showLists }"
+ >
+ <ListsMenuContent
+ :show-pin="editMode || forceEditMode"
+ class="timelines"
+ />
+ </div>
+ <NavigationEntry
+ v-if="currentUser && bookmarkFolders"
+ :show-pin="false"
+ :item="{ icon: 'bookmark', label: 'nav.bookmarks' }"
+ :aria-expanded="showBookmarkFolders ? 'true' : 'false'"
+ @click="toggleBookmarkFolders"
+ >
<router-link
- class="menu-item"
- :to="{ name: 'about' }"
+ :title="$t('bookmarks.manage_bookmark_folders')"
+ class="button-unstyled extra-button"
+ :to="{ name: 'bookmark-folders' }"
+ @click.stop
>
<FAIcon
fixed-width
- class="fa-scale-110"
- icon="info-circle"
- />{{ $t("nav.about") }}
+ icon="wrench"
+ />
</router-link>
- </li>
+ <FAIcon
+ class="timelines-chevron"
+ fixed-width
+ :icon="showBookmarkFolders ? 'chevron-up' : 'chevron-down'"
+ />
+ </NavigationEntry>
+ <div
+ v-show="showBookmarkFolders"
+ class="timelines-background menu-item-collapsible"
+ :class="{ '-expanded': showBookmarkFolders }"
+ >
+ <BookmarkFoldersMenuContent
+ class="timelines"
+ />
+ </div>
+ <NavigationEntry
+ v-for="item in rootItems"
+ :key="item.name"
+ :show-pin="editMode || forceEditMode"
+ :item="item"
+ />
+ <NavigationEntry
+ v-if="!forceEditMode && currentUser"
+ :show-pin="false"
+ :item="{ labelRaw: editMode ? $t('nav.edit_finish') : $t('nav.edit_pinned'), icon: editMode ? 'check' : 'wrench' }"
+ @click="toggleEditMode"
+ />
</ul>
</div>
</div>
</template>
-<script src="./nav_panel.js" ></script>
+<script src="./nav_panel.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.NavPanel {
.panel {
overflow: hidden;
- box-shadow: var(--panelShadow);
+ box-shadow: var(--shadow);
}
ul {
@@ -107,68 +148,10 @@
padding: 0;
}
- li {
- position: relative;
- border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- padding: 0;
-
- &:first-child .menu-item {
- border-top-right-radius: $fallback--panelRadius;
- border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
- border-top-left-radius: $fallback--panelRadius;
- border-top-left-radius: var(--panelRadius, $fallback--panelRadius);
- }
-
- &:last-child .menu-item {
- border-bottom-right-radius: $fallback--panelRadius;
- border-bottom-right-radius: var(--panelRadius, $fallback--panelRadius);
- border-bottom-left-radius: $fallback--panelRadius;
- border-bottom-left-radius: var(--panelRadius, $fallback--panelRadius);
- }
- }
-
- li:last-child {
- border: none;
- }
-
- .menu-item {
- display: block;
- box-sizing: border-box;
- height: 3.5em;
- line-height: 3.5em;
- padding: 0 1em;
- width: 100%;
- color: $fallback--link;
- color: var(--link, $fallback--link);
-
- &:hover {
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- color: $fallback--link;
- color: var(--selectedMenuText, $fallback--link);
- --faint: var(--selectedMenuFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuLightText, $fallback--lightText);
- --icon: var(--selectedMenuIcon, $fallback--icon);
- }
-
- &.router-link-active {
- font-weight: bolder;
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- color: $fallback--text;
- color: var(--selectedMenuText, $fallback--text);
- --faint: var(--selectedMenuFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuLightText, $fallback--lightText);
- --icon: var(--selectedMenuIcon, $fallback--icon);
-
- &:hover {
- text-decoration: underline;
- }
- }
+ .navigation-chevron {
+ margin-left: 0.8em;
+ margin-right: 0.8em;
+ font-size: 1.1em;
}
.timelines-chevron {
@@ -178,26 +161,12 @@
.timelines-background {
padding: 0 0 0 0.6em;
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- border-top: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- }
-
- .timelines {
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
- }
-
- .fa-scale-110 {
- margin-right: 0.8em;
}
- .badge {
- position: absolute;
- right: 0.6rem;
- top: 1.25em;
+ .nav-panel-heading {
+ // breaks without a unit
+ // stylelint-disable-next-line length-zero-no-unit
+ --panel-heading-height-padding: 0px;
}
}
</style>
diff --git a/src/components/navigation/filter.js b/src/components/navigation/filter.js
@@ -0,0 +1,29 @@
+export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFederating, isPrivate, currentUser, supportsBookmarkFolders }) => {
+ 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 (!hasChats && set.has('chats')) return false
+ if (!hasAnnouncements && set.has('announcements')) return false
+ if (supportsBookmarkFolders && set.has('!supportsBookmarkFolders')) return false
+ return true
+ })
+}
+
+export const getListEntries = state => state.lists.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 = state => state.bookmarkFolders.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
@@ -0,0 +1,108 @@
+// routes that take :username property
+export const USERNAME_ROUTES = new Set([
+ 'dms',
+ 'interactions',
+ 'notifications',
+ 'chat',
+ 'chats'
+])
+
+// routes that take :name property
+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']
+ },
+ public: {
+ route: 'public-timeline',
+ anon: true,
+ icon: 'users',
+ label: 'nav.public_tl',
+ criteria: ['!private']
+ },
+ twkn: {
+ route: 'public-external-timeline',
+ anon: true,
+ icon: 'globe',
+ label: 'nav.twkn',
+ criteria: ['!private', 'federating']
+ },
+ bookmarks: {
+ route: 'bookmarks',
+ icon: 'bookmark',
+ label: 'nav.bookmarks',
+ criteria: ['!supportsBookmarkFolders']
+ },
+ favorites: {
+ routeObject: { name: 'user-profile', query: { tab: 'favorites' } },
+ icon: 'star',
+ label: 'user_card.favorites'
+ },
+ dms: {
+ route: 'dms',
+ icon: 'envelope',
+ label: 'nav.dms'
+ }
+}
+
+export const ROOT_ITEMS = {
+ interactions: {
+ route: 'interactions',
+ icon: 'bell',
+ label: 'nav.interactions'
+ },
+ chats: {
+ route: 'chats',
+ icon: 'comments',
+ label: 'nav.chats',
+ badgeGetter: 'unreadChatCount',
+ criteria: ['chats']
+ },
+ friendRequests: {
+ route: 'friend-requests',
+ icon: 'user-plus',
+ label: 'nav.friend_requests',
+ criteria: ['lockedUser'],
+ badgeGetter: 'followRequestCount'
+ },
+ about: {
+ route: 'about',
+ anon: true,
+ icon: 'info-circle',
+ label: 'nav.about'
+ },
+ announcements: {
+ route: 'announcements',
+ icon: 'bullhorn',
+ label: 'nav.announcements',
+ badgeGetter: 'unreadAnnouncementCount',
+ criteria: ['announcements']
+ }
+}
+
+export function routeTo (item, currentUser) {
+ if (!item.route && !item.routeObject) return null
+
+ let route
+
+ if (item.routeObject) {
+ route = item.routeObject
+ } else {
+ route = { name: (item.anon || currentUser) ? item.route : item.anonRoute }
+ }
+
+ if (USERNAME_ROUTES.has(route.name)) {
+ route.params = { username: currentUser.screen_name }
+ } else if (NAME_ROUTES.has(route.name)) {
+ route.params = { name: currentUser.screen_name }
+ }
+
+ return route
+}
diff --git a/src/components/navigation/navigation_entry.js b/src/components/navigation/navigation_entry.js
@@ -0,0 +1,41 @@
+import { mapState } from 'vuex'
+import { routeTo } from 'src/components/navigation/navigation.js'
+import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faThumbtack } from '@fortawesome/free-solid-svg-icons'
+
+library.add(faThumbtack)
+
+const NavigationEntry = {
+ props: ['item', 'showPin'],
+ components: {
+ OptionalRouterLink
+ },
+ methods: {
+ isPinned (value) {
+ return this.pinnedItems.has(value)
+ },
+ togglePin (value) {
+ if (this.isPinned(value)) {
+ this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedNavItems', value })
+ } else {
+ this.$store.commit('addCollectionPreference', { path: 'collections.pinnedNavItems', value })
+ }
+ this.$store.dispatch('pushServerSideStorage')
+ }
+ },
+ computed: {
+ routeTo () {
+ return routeTo(this.item, this.currentUser)
+ },
+ getters () {
+ return this.$store.getters
+ },
+ ...mapState({
+ currentUser: state => state.users.currentUser,
+ pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)
+ })
+ }
+}
+
+export default NavigationEntry
diff --git a/src/components/navigation/navigation_entry.vue b/src/components/navigation/navigation_entry.vue
@@ -0,0 +1,146 @@
+<template>
+ <OptionalRouterLink
+ v-slot="{ isActive, href, navigate } = {}"
+ :to="routeTo"
+ >
+ <li
+ class="NavigationEntry menu-item"
+ :class="{ '-active': isActive }"
+ v-bind="$attrs"
+ >
+ <component
+ :is="routeTo ? 'a' : 'button'"
+ class="main-link"
+ :href="href"
+ @click="navigate"
+ >
+ <span>
+ <FAIcon
+ v-if="item.icon"
+ fixed-width
+ class="fa-scale-110 menu-icon"
+ :icon="item.icon"
+ />
+ </span>
+ <img
+ v-if="item.iconEmojiUrl"
+ class="menu-icon iconEmoji iconEmoji-image"
+ :src="item.iconEmojiUrl"
+ :alt="item.iconEmoji"
+ :title="item.iconEmoji"
+ >
+ <span
+ v-else-if="item.iconEmoji"
+ class="menu-icon iconEmoji"
+ >
+ <span>
+ {{ item.iconEmoji }}
+ </span>
+ </span>
+ <span
+ v-else-if="item.iconLetter"
+ class="icon iconLetter fa-scale-110 menu-icon"
+ >{{ item.iconLetter }}</span>
+ <span class="label">
+ {{ item.labelRaw || $t(item.label) }}
+ </span>
+ </component>
+ <slot />
+ <div
+ v-if="item.badgeGetter && getters[item.badgeGetter]"
+ class="badge -notification"
+ >
+ {{ getters[item.badgeGetter] }}
+ </div>
+ <button
+ v-if="showPin && currentUser"
+ type="button"
+ class="button-unstyled extra-button"
+ :title="$t(isPinned ? 'general.unpin' : 'general.pin' )"
+ :aria-pressed="!!isPinned"
+ @click.stop.prevent="togglePin(item.name)"
+ >
+ <FAIcon
+ v-if="showPin && currentUser"
+ fixed-width
+ class="fa-scale-110"
+ :class="{ 'veryfaint': !isPinned(item.name) }"
+ :transform="!isPinned(item.name) ? 'rotate-45' : ''"
+ icon="thumbtack"
+ />
+ </button>
+ </li>
+ </OptionalRouterLink>
+</template>
+
+<script src="./navigation_entry.js"></script>
+
+<style lang="scss">
+.NavigationEntry.menu-item {
+ --__line-height: 2.5em;
+ --__horizontal-gap: 0.5em;
+ --__vertical-gap: 0.4em;
+
+ padding: 0;
+ display: flex;
+ align-items: baseline;
+
+ &[aria-expanded] {
+ padding-right: var(--__horizontal-gap);
+ }
+
+ .main-link {
+ line-height: var(--__line-height);
+ box-sizing: border-box;
+ flex: 1;
+ padding: var(--__vertical-gap) var(--__horizontal-gap);
+ }
+
+ .menu-icon {
+ line-height: var(--__line-height);
+ padding: 0;
+ width: var(--__line-height);
+ margin-right: var(--__horizontal-gap);
+ }
+
+ .timelines-chevron {
+ line-height: var(--__line-height);
+ padding: 0;
+ width: var(--__line-height);
+ margin-right: 0;
+ }
+
+ .extra-button {
+ line-height: var(--__line-height);
+ padding: 0;
+ width: var(--__line-height);
+ text-align: center;
+
+ &:last-child {
+ margin-right: calc(-1 * var(--__horizontal-gap));
+ }
+ }
+
+ .badge {
+ margin: 0 var(--__horizontal-gap);
+ }
+
+ .iconEmoji {
+ display: inline-block;
+ text-align: center;
+ object-fit: contain;
+ vertical-align: middle;
+ height: var(--__line-height);
+ width: var(--__line-height);
+
+ > span {
+ font-size: 1.5rem;
+ }
+ }
+
+ img.iconEmoji {
+ padding: 0.25rem;
+ box-sizing: border-box;
+ }
+}
+</style>
diff --git a/src/components/navigation/navigation_pins.js b/src/components/navigation/navigation_pins.js
@@ -0,0 +1,90 @@
+import { mapState } from 'vuex'
+import { TIMELINES, ROOT_ITEMS, routeTo } from 'src/components/navigation/navigation.js'
+import { getListEntries, filterNavigation } from 'src/components/navigation/filter.js'
+
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faUsers,
+ faGlobe,
+ faBookmark,
+ faEnvelope,
+ faComments,
+ faBell,
+ faInfoCircle,
+ faStream,
+ faList
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faUsers,
+ faGlobe,
+ faBookmark,
+ faEnvelope,
+ faComments,
+ faBell,
+ faInfoCircle,
+ faStream,
+ faList
+)
+
+const NavPanel = {
+ props: ['limit'],
+ methods: {
+ getRouteTo (item) {
+ return routeTo(item, this.currentUser)
+ }
+ },
+ computed: {
+ getters () {
+ return this.$store.getters
+ },
+ ...mapState({
+ lists: getListEntries,
+ 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,
+ supportsAnnouncements: state => state.announcements.supportsAnnouncements,
+ pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)
+ }),
+ 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
+ })
+ }
+ return filterNavigation(
+ [
+ ...Object
+ .entries({ ...TIMELINES })
+ .filter(([k]) => this.pinnedItems.has(k))
+ .map(([k, v]) => ({ ...v, name: k })),
+ ...this.lists.filter((k) => this.pinnedItems.has(k.name)),
+ ...Object
+ .entries({ ...ROOT_ITEMS })
+ .filter(([k]) => this.pinnedItems.has(k))
+ .map(([k, v]) => ({ ...v, name: k }))
+ ],
+ {
+ hasChats: this.pleromaChatMessagesAvailable,
+ hasAnnouncements: this.supportsAnnouncements,
+ isFederating: this.federating,
+ isPrivate: this.privateMode,
+ currentUser: this.currentUser
+ }
+ ).slice(0, this.limit)
+ }
+ }
+}
+
+export default NavPanel
diff --git a/src/components/navigation/navigation_pins.vue b/src/components/navigation/navigation_pins.vue
@@ -0,0 +1,57 @@
+<template>
+ <span class="NavigationPins">
+ <router-link
+ v-for="item in pinnedList"
+ :key="item.name"
+ class="button-unstyled pinned-item"
+ active-class="toggled"
+ :to="getRouteTo(item)"
+ :title="item.labelRaw || $t(item.label)"
+ >
+ <FAIcon
+ v-if="item.icon"
+ fixed-width
+ :icon="item.icon"
+ />
+ <span
+ v-if="item.iconLetter"
+ class="iconLetter fa-scale-110 fa-old-padding"
+ >{{ item.iconLetter }}</span>
+ <div
+ v-if="item.badgeGetter && getters[item.badgeGetter]"
+ class="badge -dot -notification"
+ />
+ </router-link>
+ </span>
+</template>
+
+<script src="./navigation_pins.js"></script>
+
+<style lang="scss">
+.NavigationPins {
+ display: flex;
+ flex-wrap: wrap;
+ overflow: hidden;
+ height: 100%;
+
+ .pinned-item {
+ position: relative;
+ flex: 1 0 3em;
+ min-width: 2em;
+ text-align: center;
+ overflow: visible;
+ box-sizing: border-box;
+ height: 100%;
+
+ & .svg-inline--fa,
+ & .iconLetter {
+ margin: 0;
+ }
+
+ &.toggled {
+ margin-bottom: -4px;
+ border-bottom: 4px solid;
+ }
+ }
+}
+</style>
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
@@ -4,7 +4,11 @@ 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'
@@ -17,7 +21,9 @@ import {
faUserPlus,
faEyeSlash,
faUser,
- faSuitcaseRolling
+ faSuitcaseRolling,
+ faExpandAlt,
+ faCompressAlt
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -28,29 +34,38 @@ library.add(
faUserPlus,
faUser,
faEyeSlash,
- faSuitcaseRolling
+ faSuitcaseRolling,
+ faExpandAlt,
+ faCompressAlt
)
const Notification = {
data () {
return {
- userExpanded: false,
+ statusExpanded: false,
betterShadow: this.$store.state.interface.browserSupport.cssFilter,
- unmuted: false
+ unmuted: false,
+ showingApproveConfirmDialog: false,
+ showingDenyConfirmDialog: false
}
},
- props: [ 'notification' ],
+ props: ['notification'],
+ emits: ['interacted'],
components: {
StatusContent,
UserAvatar,
UserCard,
Timeago,
Status,
- RichContent
+ Report,
+ RichContent,
+ UserPopover,
+ UserLink,
+ ConfirmModal
},
methods: {
- toggleUserExpanded () {
- this.userExpanded = !this.userExpanded
+ toggleStatusExpanded () {
+ this.statusExpanded = !this.statusExpanded
},
generateUserProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
@@ -58,10 +73,33 @@ const Notification = {
getUser (notification) {
return this.$store.state.users.usersObject[notification.from_profile.id]
},
+ interacted () {
+ this.$emit('interacted')
+ },
toggleMute () {
this.unmuted = !this.unmuted
},
+ showApproveConfirmDialog () {
+ this.showingApproveConfirmDialog = true
+ },
+ hideApproveConfirmDialog () {
+ this.showingApproveConfirmDialog = false
+ },
+ showDenyConfirmDialog () {
+ this.showingDenyConfirmDialog = true
+ },
+ hideDenyConfirmDialog () {
+ this.showingDenyConfirmDialog = false
+ },
approveUser () {
+ if (this.shouldConfirmApprove) {
+ this.showApproveConfirmDialog()
+ } else {
+ this.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 })
@@ -71,13 +109,23 @@ const Notification = {
notification.type = 'follow'
}
})
+ this.hideApproveConfirmDialog()
},
denyUser () {
+ if (this.shouldConfirmDeny) {
+ this.showDenyConfirmDialog()
+ } else {
+ this.doDeny()
+ }
+ },
+ doDeny () {
+ this.$emit('interacted')
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
.then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id })
this.$store.dispatch('removeFollowRequest', this.user)
})
+ this.hideDenyConfirmDialog()
}
},
computed: {
@@ -107,6 +155,15 @@ const Notification = {
isStatusNotification () {
return isStatusNotification(this.notification.type)
},
+ mergedConfig () {
+ return this.$store.getters.mergedConfig
+ },
+ shouldConfirmApprove () {
+ return this.mergedConfig.modalOnApproveFollow
+ },
+ shouldConfirmDeny () {
+ return this.mergedConfig.modalOnDenyFollow
+ },
...mapState({
currentUser: state => state.users.currentUser
})
diff --git a/src/components/notification/notification.scss b/src/components/notification/notification.scss
@@ -1,13 +1,16 @@
-@import '../../_variables.scss';
-
// TODO Copypaste from Status, should unify it somehow
.Notification {
- border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- word-wrap: break-word;
- word-break: break-word;
- --emoji-size: 14px;
+ border-bottom: 1px solid;
+ border-color: var(--border);
+ word-wrap: break-word;
+ word-break: break-word;
+
+ &.Status {
+ /* stylelint-disable-next-line declaration-no-important */
+ background-color: transparent !important;
+ }
+
+ --emoji-size: 1em;
&:hover {
--_still-image-img-visibility: visible;
@@ -23,6 +26,7 @@
overflow: hidden;
display: flex;
flex-wrap: nowrap;
+ gap: 1ex;
& .status-username,
& .mute-thread,
@@ -54,7 +58,7 @@
margin-left: 0.2em;
&::before {
- content: ' ';
+ content: " ";
}
}
@@ -70,28 +74,22 @@
}
&.-type--repeat .type-icon {
- color: $fallback--cGreen;
- color: var(--cGreen, $fallback--cGreen);
+ color: var(--cGreen);
}
&.-type--follow .type-icon {
- color: $fallback--cBlue;
- color: var(--cBlue, $fallback--cBlue);
+ color: var(--cBlue);
}
&.-type--follow-request .type-icon {
- color: $fallback--cBlue;
- color: var(--cBlue, $fallback--cBlue);
+ color: var(--cBlue);
}
&.-type--like .type-icon {
- color: orange;
- color: $fallback--cOrange;
- color: var(--cOrange, $fallback--cOrange);
+ color: var(--cOrange);
}
&.-type--move .type-icon {
- color: $fallback--cBlue;
- color: var(--cBlue, $fallback--cBlue);
+ color: var(--cBlue);
}
}
diff --git a/src/components/notification/notification.style.js b/src/components/notification/notification.style.js
@@ -0,0 +1,18 @@
+export default {
+ name: 'Notification',
+ selector: '.Notification',
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'ButtonUnstyled',
+ 'RichContent',
+ 'Input',
+ 'Avatar',
+ 'Attachment',
+ 'PollGraph'
+ ],
+ defaultRules: []
+}
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
@@ -1,19 +1,24 @@
<template>
- <Status
- v-if="notification.type === 'mention'"
- class="Notification"
- :compact="true"
- :statusoid="notification.status"
- />
- <div v-else>
+ <article
+ v-if="notification.type === 'mention' || notification.type === 'status'"
+ >
+ <Status
+ class="Notification"
+ :compact="true"
+ :statusoid="notification.status"
+ @interacted="interacted"
+ />
+ </article>
+ <article v-else>
<div
v-if="needMute && !unmuted"
class="Notification container -muted"
>
<small>
- <router-link :to="userProfileLink">
- {{ notification.from_profile.screen_name_ui }}
- </router-link>
+ <user-link
+ :user="notification.from_profile"
+ :at="false"
+ />
</small>
<button
class="button-unstyled unmute"
@@ -34,21 +39,21 @@
<a
class="avatar-container"
:href="$router.resolve(userProfileLink).href"
- @click.stop.prevent.capture="toggleUserExpanded"
+ @click.prevent
>
- <UserAvatar
- :compact="true"
- :better-shadow="betterShadow"
- :user="notification.from_profile"
- />
+ <UserPopover
+ :user-id="notification.from_profile.id"
+ :overlay-centers="true"
+ >
+ <UserAvatar
+ class="post-avatar"
+ :compact="true"
+ :better-shadow="betterShadow"
+ :user="notification.from_profile"
+ />
+ </UserPopover>
</a>
<div class="notification-right">
- <UserCard
- v-if="userExpanded"
- :user-id="getUser(notification).id"
- :rounded="true"
- :bordered="true"
- />
<span class="notification-details">
<div class="name-and-action">
<!-- eslint-disable vue/no-v-html -->
@@ -116,10 +121,23 @@
scope="global"
keypath="notifications.reacted_with"
>
- <span class="emoji-reaction-emoji">{{ notification.emoji }}</span>
+ <img
+ v-if="notification.emoji_url"
+ class="emoji-reaction-emoji emoji-reaction-emoji-image"
+ :src="notification.emoji_url"
+ :alt="notification.emoji"
+ :title="notification.emoji"
+ >
+ <span
+ v-else
+ class="emoji-reaction-emoji"
+ >{{ notification.emoji }}</span>
</i18n-t>
</small>
</span>
+ <span v-if="notification.type === 'pleroma:report'">
+ <small>{{ $t('notifications.submitted_report') }}</small>
+ </span>
<span v-if="notification.type === 'poll'">
<FAIcon
class="type-icon"
@@ -136,13 +154,25 @@
<router-link
v-if="notification.status"
:to="{ name: 'conversation', params: { id: notification.status.id } }"
- class="faint-link"
+ class="timeago-link faint"
>
<Timeago
:time="notification.created_at"
:auto-update="240"
/>
</router-link>
+ <button
+ class="button-unstyled expand-icon"
+ :title="$t('tool_tip.toggle_expand')"
+ :aria-expanded="statusExpanded"
+ @click.prevent="toggleStatusExpanded"
+ >
+ <FAIcon
+ class="fa-scale-110"
+ fixed-width
+ :icon="statusExpanded ? 'compress-alt' : 'expand-alt'"
+ />
+ </button>
</div>
<div
v-else
@@ -158,6 +188,8 @@
<button
v-if="needMute"
class="button-unstyled"
+ :title="$t('tool_tip.toggle_mute')"
+ :aria-expanded="!unmuted"
@click.prevent="toggleMute"
>
<FAIcon
@@ -170,12 +202,10 @@
v-if="notification.type === 'follow' || notification.type === 'follow_request'"
class="follow-text"
>
- <router-link
- :to="userProfileLink"
+ <user-link
class="follow-name"
- >
- @{{ notification.from_profile.screen_name_ui }}
- </router-link>
+ :user="notification.from_profile"
+ />
<div
v-if="notification.type === 'follow_request'"
style="white-space: nowrap;"
@@ -206,20 +236,45 @@
v-else-if="notification.type === 'move'"
class="move-text"
>
- <router-link :to="targetUserProfileLink">
- @{{ notification.target.screen_name_ui }}
- </router-link>
+ <user-link
+ :user="notification.target"
+ />
</div>
+ <Report
+ v-else-if="notification.type === 'pleroma:report'"
+ :report-id="notification.report.id"
+ />
<template v-else>
<StatusContent
- class="faint"
- :compact="true"
- :status="notification.action"
+ :compact="!statusExpanded"
+ :status="notification.status"
/>
</template>
</div>
</div>
- </div>
+ <teleport to="#modal">
+ <confirm-modal
+ v-if="showingApproveConfirmDialog"
+ :title="$t('user_card.approve_confirm_title')"
+ :confirm-text="$t('user_card.approve_confirm_accept_button')"
+ :cancel-text="$t('user_card.approve_confirm_cancel_button')"
+ @accepted="doApprove"
+ @cancelled="hideApproveConfirmDialog"
+ >
+ {{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
+ </confirm-modal>
+ <confirm-modal
+ v-if="showingDenyConfirmDialog"
+ :title="$t('user_card.deny_confirm_title')"
+ :confirm-text="$t('user_card.deny_confirm_accept_button')"
+ :cancel-text="$t('user_card.deny_confirm_cancel_button')"
+ @accepted="doDeny"
+ @cancelled="hideDenyConfirmDialog"
+ >
+ {{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
+ </confirm-modal>
+ </teleport>
+ </article>
</template>
<script src="./notification.js"></script>
diff --git a/src/components/notifications/notification_filters.vue b/src/components/notifications/notification_filters.vue
@@ -5,74 +5,83 @@
placement="bottom"
:bound-to="{ x: 'container' }"
>
- <template v-slot:content>
+ <template #content>
<div class="dropdown-menu">
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click="toggleNotificationFilter('likes')"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.likes }"
/>{{ $t('settings.notification_visibility_likes') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click="toggleNotificationFilter('repeats')"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.repeats }"
/>{{ $t('settings.notification_visibility_repeats') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click="toggleNotificationFilter('follows')"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.follows }"
/>{{ $t('settings.notification_visibility_follows') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click="toggleNotificationFilter('mentions')"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.mentions }"
/>{{ $t('settings.notification_visibility_mentions') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
+ @click="toggleNotificationFilter('statuses')"
+ >
+ <span
+ class="input menu-checkbox"
+ :class="{ 'menu-checkbox-checked': filters.statuses }"
+ />{{ $t('settings.notification_visibility_statuses') }}
+ </button>
+ <button
+ class="menu-item dropdown-item"
@click="toggleNotificationFilter('emojiReactions')"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.emojiReactions }"
/>{{ $t('settings.notification_visibility_emoji_reactions') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click="toggleNotificationFilter('moves')"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.moves }"
/>{{ $t('settings.notification_visibility_moves') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click="toggleNotificationFilter('polls')"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.polls }"
/>{{ $t('settings.notification_visibility_polls') }}
</button>
</div>
</template>
- <template v-slot:trigger>
+ <template #trigger>
<button class="filter-trigger-button button-unstyled">
<FAIcon icon="filter" />
</button>
@@ -109,22 +118,3 @@ export default {
}
}
</script>
-
-<style lang="scss">
-
-.NotificationFilters {
- align-self: stretch;
-
- > button {
- line-height: 100%;
- height: 100%;
- width: var(--__panel-heading-height-inner);
- text-align: center;
-
- svg {
- font-size: 1.2em;
- }
- }
-}
-
-</style>
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
@@ -1,18 +1,24 @@
+import { computed } from 'vue'
import { mapGetters } from 'vuex'
import Notification from '../notification/notification.vue'
+import ExtraNotifications from '../extra_notifications/extra_notifications.vue'
import NotificationFilters from './notification_filters.vue'
import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
import {
notificationsFromStore,
filteredNotificationsFromStore,
- unseenNotificationsFromStore
+ unseenNotificationsFromStore,
+ countExtraNotifications,
+ ACTIONABLE_NOTIFICATION_TYPES
} from '../../services/notification_utils/notification_utils.js'
import FaviconService from '../../services/favicon_service/favicon_service.js'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
+import { faCircleNotch, faArrowUp, faMinus } from '@fortawesome/free-solid-svg-icons'
library.add(
- faCircleNotch
+ faCircleNotch,
+ faArrowUp,
+ faMinus
)
const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30
@@ -20,19 +26,26 @@ const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30
const Notifications = {
components: {
Notification,
- NotificationFilters
+ NotificationFilters,
+ ExtraNotifications
},
props: {
// Disables panel styles, unread mark, potentially other notification-related actions
// meant for "Interactions" timeline
minimalMode: Boolean,
- // Custom filter mode, an array of strings, possible values 'mention', 'repeat', 'like', 'follow', used to override global filter for use in "Interactions" timeline
+ // Custom filter mode, an array of strings, possible values 'mention', 'status', 'repeat', 'like', 'follow', used to override global filter for use in "Interactions" timeline
filterMode: Array,
+ // Do not show extra notifications
+ noExtra: {
+ type: Boolean,
+ default: false
+ },
// Disable teleporting (i.e. for /users/user/notifications)
disableTeleport: Boolean
},
data () {
return {
+ showScrollTop: false,
bottomedOut: false,
// How many seen notifications to display in the list. The more there are,
// the heavier the page becomes. This count is increased when loading
@@ -40,6 +53,11 @@ const Notifications = {
seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT
}
},
+ provide () {
+ return {
+ popoversZLayer: computed(() => this.popoversZLayer)
+ }
+ },
computed: {
mainClass () {
return this.minimalMode ? '' : 'panel panel-default'
@@ -48,22 +66,36 @@ const Notifications = {
return notificationsFromStore(this.$store)
},
error () {
- return this.$store.state.statuses.notifications.error
+ return this.$store.state.notifications.error
},
unseenNotifications () {
return unseenNotificationsFromStore(this.$store)
},
filteredNotifications () {
- return filteredNotificationsFromStore(this.$store, this.filterMode)
+ if (this.unseenAtTop) {
+ return [
+ ...filteredNotificationsFromStore(this.$store).filter(n => this.shouldShowUnseen(n)),
+ ...filteredNotificationsFromStore(this.$store).filter(n => !this.shouldShowUnseen(n))
+ ]
+ } else {
+ return filteredNotificationsFromStore(this.$store, this.filterMode)
+ }
+ },
+ unseenCountBadgeText () {
+ return `${this.unseenCount ? this.unseenCount : ''}${this.extraNotificationsCount ? '*' : ''}`
},
unseenCount () {
return this.unseenNotifications.length
},
+ ignoreInactionableSeen () { return this.$store.getters.mergedConfig.ignoreInactionableSeen },
+ extraNotificationsCount () {
+ return countExtraNotifications(this.$store)
+ },
unseenCountTitle () {
- return this.unseenCount + (this.unreadChatCount)
+ return this.unseenNotifications.length + (this.unreadChatCount) + this.unreadAnnouncementCount
},
loading () {
- return this.$store.state.statuses.notifications.loading
+ return this.$store.state.notifications.loading
},
noHeading () {
const { layoutType } = this.$store.state.interface
@@ -77,10 +109,33 @@ const Notifications = {
}
return map[layoutType] || '#notifs-sidebar'
},
+ popoversZLayer () {
+ const { layoutType } = this.$store.state.interface
+ return layoutType === 'mobile' ? 'navbar' : null
+ },
notificationsToDisplay () {
return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
},
- ...mapGetters(['unreadChatCount'])
+ noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
+ unseenAtTop () { return this.$store.getters.mergedConfig.unseenAtTop },
+ showExtraNotifications () {
+ return !this.noExtra
+ },
+ ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
+ },
+ mounted () {
+ this.scrollerRef = this.$refs.root.closest('.column.-scrollable')
+ if (!this.scrollerRef) {
+ this.scrollerRef = this.$refs.root.closest('.mobile-notifications')
+ }
+ if (!this.scrollerRef) {
+ this.scrollerRef = this.$refs.root.closest('.column.main')
+ }
+ this.scrollerRef.addEventListener('scroll', this.updateScrollPosition)
+ },
+ unmounted () {
+ if (!this.scrollerRef) return
+ this.scrollerRef.removeEventListener('scroll', this.updateScrollPosition)
},
watch: {
unseenCountTitle (count) {
@@ -91,9 +146,46 @@ const Notifications = {
FaviconService.clearFaviconBadge()
this.$store.dispatch('setPageTitle', '')
}
+ },
+ teleportTarget () {
+ // handle scroller change
+ this.$nextTick(() => {
+ this.scrollerRef.removeEventListener('scroll', this.updateScrollPosition)
+ this.scrollerRef = this.$refs.root.closest('.column.-scrollable')
+ if (!this.scrollerRef) {
+ this.scrollerRef = this.$refs.root.closest('.mobile-notifications')
+ }
+ this.scrollerRef.addEventListener('scroll', this.updateScrollPosition)
+ this.updateScrollPosition()
+ })
}
},
methods: {
+ scrollToTop () {
+ const scrollable = this.scrollerRef
+ scrollable.scrollTo({ top: this.$refs.root.offsetTop })
+ },
+ updateScrollPosition () {
+ this.showScrollTop = this.$refs.root.offsetTop < this.scrollerRef.scrollTop
+ },
+ shouldShowUnseen (notification) {
+ if (notification.seen) return false
+
+ const actionable = ACTIONABLE_NOTIFICATION_TYPES.has(notification.type)
+ return this.ignoreInactionableSeen ? actionable : true
+ },
+ /* "Interacted" really refers to "actionable" notifications that require user input,
+ * everything else (likes/repeats/reacts) cannot be acted and therefore we just clear
+ * the "seen" status upon any clicks on them
+ */
+ notificationClicked (notification) {
+ const { id } = notification
+ this.$store.dispatch('notificationClicked', { id })
+ },
+ notificationInteracted (notification) {
+ const { id } = notification
+ this.$store.dispatch('markSingleNotificationAsSeen', { id })
+ },
markAsSeen () {
this.$store.dispatch('markNotificationsAsSeen')
this.seenToDisplayCount = DEFAULT_SEEN_TO_DISPLAY_COUNT
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
@@ -1,5 +1,3 @@
-@import '../../_variables.scss';
-
.Notifications {
&:not(.minimal) {
// a bit of a hack to allow scrolling below notifications
@@ -7,8 +5,7 @@
}
.loadmore-error {
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
.notification {
@@ -25,19 +22,26 @@
&.unseen {
.notification-overlay {
- background-image: linear-gradient(135deg, var(--badgeNotification, $fallback--cRed) 4px, transparent 10px)
+ background-image: linear-gradient(135deg, var(--badgeNotification) 4px, transparent 10px);
}
}
}
}
+/* stylelint-disable-next-line no-descending-specificity */
.notification {
box-sizing: border-box;
+ /* TODO cleanup this */
+ .Status {
+ flex: 1;
+ }
+
&:hover .animated.Avatar {
canvas {
display: none;
}
+
img {
visibility: visible;
}
@@ -58,26 +62,22 @@
width: 32px;
height: 32px;
}
-
- --link: var(--faintLink);
- --text: var(--faint);
}
.follow-request-accept {
&:hover {
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
}
.follow-request-reject {
&:hover {
- color: $fallback--cRed;
- color: var(--cRed, $fallback--cRed);
+ color: var(--cRed);
}
}
- .follow-text, .move-text {
+ .follow-text,
+ .move-text {
padding: 0.5em 0;
overflow-wrap: break-word;
display: flex;
@@ -92,11 +92,6 @@
}
}
- /* TODO cleanup this */
- .Status {
- flex: 1;
- }
-
time {
white-space: nowrap;
}
@@ -110,10 +105,28 @@
min-width: 3em;
text-align: right;
}
+
+ .timeago-link {
+ margin-right: 0.2em;
+ }
+
+ .expand-icon {
+ .svg-inline--fa {
+ margin-left: 0.25em;
+ }
+ }
}
.emoji-reaction-emoji {
font-size: 1.3em;
+ max-width: 1.25em;
+ height: 1.25em;
+ width: auto;
+ }
+
+ .emoji-reaction-emoji-image {
+ vertical-align: middle;
+ object-fit: contain;
}
.notification-details {
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
@@ -1,6 +1,11 @@
<template>
- <teleport :disabled="minimalMode || disableTeleport" :to="teleportTarget">
- <div
+ <teleport
+ :disabled="minimalMode || disableTeleport"
+ :to="teleportTarget"
+ >
+ <component
+ :is="noHeading ? 'div' : 'aside'"
+ ref="root"
:class="{ minimal: minimalMode }"
class="Notifications"
>
@@ -9,31 +14,66 @@
v-if="!noHeading"
class="notifications-heading panel-heading -sticky"
>
- <div class="title">
+ <h1 class="title">
{{ $t('notifications.notifications') }}
<span
- v-if="unseenCount"
- class="badge badge-notification unseen-count"
- >{{ unseenCount }}</span>
+ v-if="unseenCountBadgeText"
+ class="badge -notification unseen-count"
+ >{{ unseenCountBadgeText }}</span>
+ </h1>
+ <div
+ v-if="showScrollTop"
+ class="rightside-button"
+ >
+ <button
+ class="button-unstyled scroll-to-top-button"
+ type="button"
+ :title="$t('general.scroll_to_top')"
+ @click="scrollToTop"
+ >
+ <FALayers class="fa-scale-110 fa-old-padding-layer">
+ <FAIcon icon="arrow-up" />
+ <FAIcon
+ icon="minus"
+ transform="up-7"
+ />
+ </FALayers>
+ </button>
</div>
<button
v-if="unseenCount"
class="button-default read-button"
+ type="button"
@click.prevent="markAsSeen"
>
{{ $t('notifications.read') }}
</button>
- <NotificationFilters />
+ <NotificationFilters class="rightside-button" />
</div>
- <div class="panel-body">
+ <div
+ class="panel-body"
+ role="feed"
+ >
+ <div
+ v-if="showExtraNotifications"
+ role="listitem"
+ class="notification"
+ >
+ <extra-notifications />
+ </div>
<div
v-for="notification in notificationsToDisplay"
:key="notification.id"
+ role="listitem"
class="notification"
- :class="{unseen: !minimalMode && !notification.seen}"
+ :class="{unseen: !minimalMode && shouldShowUnseen(notification)}"
+ @click="e => notificationClicked(notification)"
>
<div class="notification-overlay" />
- <notification :notification="notification" />
+ <notification
+ :notification="notification"
+ @interacted="e => notificationInteracted(notification)"
+ />
</div>
</div>
<div class="panel-footer">
@@ -45,7 +85,7 @@
</div>
<button
v-else-if="!loading"
- class="button-unstyled -link -fullwidth"
+ class="button-unstyled -link text-center"
@click.prevent="fetchOlderNotifications()"
>
<div class="new-status-notification text-center">
@@ -64,7 +104,7 @@
</div>
</div>
</div>
- </div>
+ </component>
</teleport>
</template>
diff --git a/src/components/opacity_input/opacity_input.vue b/src/components/opacity_input/opacity_input.vue
@@ -6,8 +6,9 @@
<label
:for="name"
class="label"
+ :class="{ faint: !present || disabled }"
>
- {{ $t('settings.style.common.opacity') }}
+ {{ label }}
</label>
<Checkbox
v-if="typeof fallback !== 'undefined'"
@@ -18,10 +19,11 @@
/>
<input
:id="name"
- class="input-number"
+ class="input input-number"
type="number"
:value="modelValue || fallback"
:disabled="!present || disabled"
+ :class="{ disabled: !present || disabled }"
max="1"
min="0"
step=".05"
@@ -37,7 +39,7 @@ export default {
Checkbox
},
props: [
- 'name', 'modelValue', 'fallback', 'disabled'
+ 'name', 'label', 'modelValue', 'fallback', 'disabled'
],
emits: ['update:modelValue'],
computed: {
diff --git a/src/components/optional_router_link/optional_router_link.vue b/src/components/optional_router_link/optional_router_link.vue
@@ -0,0 +1,23 @@
+<template>
+ <!-- eslint-disable vue/no-multiple-template-root -->
+ <router-link
+ v-if="to"
+ v-slot="props"
+ :to="to"
+ custom
+ >
+ <slot
+ v-bind="props"
+ />
+ </router-link>
+ <slot
+ v-else
+ v-bind="{}"
+ />
+</template>
+
+<script>
+export default {
+ props: ['to']
+}
+</script>
diff --git a/src/components/palette_editor/palette_editor.vue b/src/components/palette_editor/palette_editor.vue
@@ -0,0 +1,193 @@
+<template>
+ <div
+ class="PaletteEditor"
+ :class="{ '-compact': compact, '-apply': apply }"
+ >
+ <ColorInput
+ v-for="key in paletteKeys"
+ :key="key"
+ :name="key"
+ :model-value="props.modelValue[key]"
+ :fallback="fallback(key)"
+ :label="$t('settings.style.themes3.palette.' + key)"
+ @update:modelValue="value => updatePalette(key, value)"
+ />
+ <button
+ class="btn button-default palette-import-button"
+ @click="importPalette"
+ >
+ <FAIcon icon="file-import" />
+ {{ $t('settings.style.themes3.palette.import') }}
+ </button>
+ <button
+ class="btn button-default palette-export-button"
+ @click="exportPalette"
+ >
+ <FAIcon icon="file-export" />
+ {{ $t('settings.style.themes3.palette.export') }}
+ </button>
+ <button
+ v-if="apply"
+ class="btn button-default palette-apply-button"
+ @click="applyPalette"
+ >
+ {{ $t('settings.style.themes3.palette.apply') }}
+ </button>
+ </div>
+</template>
+
+<script setup>
+import ColorInput from 'src/components/color_input/color_input.vue'
+import {
+ newImporter,
+ newExporter
+} from 'src/services/export_import/export_import.js'
+
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faFileImport,
+ faFileExport
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faFileImport,
+ faFileExport
+)
+
+const paletteKeys = [
+ 'bg',
+ 'fg',
+ 'text',
+ 'link',
+ 'accent',
+ 'cRed',
+ 'cBlue',
+ 'cGreen',
+ 'cOrange',
+ 'wallpaper'
+]
+
+const props = defineProps(['modelValue', 'compact', 'apply'])
+const emit = defineEmits(['update:modelValue', 'applyPalette'])
+const getExportedObject = () => paletteKeys.reduce((acc, key) => {
+ const value = props.modelValue[key]
+ if (value == null) {
+ return acc
+ } else {
+ return { ...acc, [key]: props.modelValue[key] }
+ }
+}, {})
+
+const paletteExporter = newExporter({
+ filename: 'pleroma_palette',
+ extension: 'json',
+ getExportedObject
+})
+const paletteImporter = newImporter({
+ accept: '.json',
+ onImport (parsed, filename) {
+ emit('update:modelValue', parsed)
+ }
+})
+
+const exportPalette = () => {
+ paletteExporter.exportData()
+}
+
+const importPalette = () => {
+ paletteImporter.importData()
+}
+
+const applyPalette = (data) => {
+ emit('applyPalette', getExportedObject())
+}
+
+const fallback = (key) => {
+ if (key === 'accent') {
+ return props.modelValue.link
+ }
+ if (key === 'link') {
+ return props.modelValue.accent
+ }
+ if (key.startsWith('extra')) {
+ return '#FF00FF'
+ }
+ if (key.startsWith('wallpaper')) {
+ return '#008080'
+ }
+}
+
+const updatePalette = (paletteKey, value) => {
+ emit('update:modelValue', {
+ ...props.modelValue,
+ [paletteKey]: value
+ })
+}
+</script>
+
+<style lang="scss">
+.PaletteEditor {
+ display: grid;
+ justify-content: space-around;
+ grid-template-columns: repeat(4, 1fr);
+ grid-template-rows: repeat(5, 1fr) auto;
+ grid-gap: 0.5em;
+ align-items: baseline;
+
+ .palette-import-button {
+ grid-column: 1 / span 2;
+ }
+
+ .palette-export-button {
+ grid-column: 3 / span 2;
+ }
+
+ .palette-apply-button {
+ grid-column: 1 / span 2;
+ }
+
+ .color-input.style-control {
+ margin: 0;
+ }
+
+ &.-compact {
+ grid-template-columns: repeat(2, 1fr);
+ grid-template-rows: repeat(5, 1fr) auto;
+
+ .palette-import-button {
+ grid-column: 1;
+ }
+
+ .palette-export-button {
+ grid-column: 2;
+ }
+
+ &.-apply {
+ grid-template-rows: repeat(5, 1fr) auto auto;
+
+ .palette-apply-button {
+ grid-column: 1 / span 2;
+ }
+ }
+
+ .-mobile & {
+ grid-template-columns: 1fr;
+ grid-template-rows: repeat(10, 1fr) auto;
+
+ .palette-import-button {
+ grid-column: 1;
+ }
+
+ .palette-export-button {
+ grid-column: 1;
+ }
+
+ &.-apply {
+ .palette-apply-button {
+ grid-column: 1;
+ }
+ }
+ }
+ }
+}
+</style>
diff --git a/src/components/panel.style.js b/src/components/panel.style.js
@@ -0,0 +1,51 @@
+export default {
+ name: 'Panel',
+ selector: '.panel',
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'ButtonUnstyled',
+ 'Input',
+ 'PanelHeader',
+ 'MenuItem',
+ 'Post',
+ 'Notification',
+ 'Alert',
+ 'UserCard',
+ 'Chat',
+ 'Attachment',
+ 'Tab',
+ 'ListItem'
+ ],
+ validInnerComponentsLite: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'Input',
+ 'PanelHeader',
+ 'Alert'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ backgroundNoCssColor: 'yes',
+ background: '--bg',
+ roundness: 3,
+ blur: '5px',
+ shadow: [{
+ x: 1,
+ y: 1,
+ blur: 4,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.6
+ }]
+ }
+ }
+ ]
+}
diff --git a/src/components/panel_header.style.js b/src/components/panel_header.style.js
@@ -0,0 +1,24 @@
+export default {
+ name: 'PanelHeader',
+ selector: '.panel-heading',
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Button',
+ 'ButtonUnstyled',
+ 'Badge',
+ 'Alert',
+ 'Avatar'
+ ],
+ defaultRules: [
+ {
+ component: 'PanelHeader',
+ directives: {
+ backgroundNoCssColor: 'yes',
+ background: '--fg',
+ shadow: []
+ }
+ }
+ ]
+}
diff --git a/src/components/panel_loading/panel_loading.vue b/src/components/panel_loading/panel_loading.vue
@@ -23,21 +23,18 @@ export default {}
</script>
<style lang="scss">
-@import 'src/_variables.scss';
-
.panel-loading {
display: flex;
height: 100%;
align-items: center;
justify-content: center;
font-size: 2em;
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
+
.loading-text svg {
line-height: 0;
vertical-align: middle;
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
}
</style>
diff --git a/src/components/password_reset/password_reset.vue b/src/components/password_reset/password_reset.vue
@@ -1,7 +1,9 @@
<template>
<div class="settings panel panel-default">
<div class="panel-heading">
- {{ $t('password_reset.password_reset') }}
+ <h1 class="title">
+ {{ $t('password_reset.password_reset') }}
+ </h1>
</div>
<div class="panel-body">
<form
@@ -30,7 +32,7 @@
<div v-else>
<p
v-if="passwordResetRequested"
- class="password-reset-required error"
+ class="alert password-reset-required error"
>
{{ $t('password_reset.password_reset_required') }}
</p>
@@ -43,7 +45,7 @@
v-model="user.email"
:disabled="isPending"
:placeholder="$t('password_reset.placeholder')"
- class="form-control"
+ class="input form-control"
type="input"
>
</div>
@@ -77,8 +79,6 @@
<script src="./password_reset.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.password-reset-form {
display: flex;
flex-direction: column;
@@ -117,11 +117,6 @@
margin: 0.3em 0 1em;
}
- .password-reset-required {
- background-color: var(--alertError, $fallback--alertError);
- padding: 10px 0;
- }
-
.notice-dismissible {
padding-right: 2rem;
}
diff --git a/src/components/poll/poll.js b/src/components/poll/poll.js
@@ -1,4 +1,5 @@
import Timeago from 'components/timeago/timeago.vue'
+import genRandomSeed from '../../services/random_seed/random_seed.service.js'
import RichContent from 'components/rich_content/rich_content.jsx'
import { forEach, map } from 'lodash'
@@ -12,7 +13,8 @@ export default {
data () {
return {
loading: false,
- choices: []
+ choices: [],
+ randomSeed: genRandomSeed()
}
},
created () {
@@ -36,7 +38,7 @@ export default {
return (this.poll && this.poll.options) || []
},
expiresAt () {
- return (this.poll && this.poll.expires_at) || 0
+ return (this.poll && this.poll.expires_at) || null
},
expired () {
return (this.poll && this.poll.expired) || false
diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue
@@ -4,53 +4,66 @@
:class="containerClass"
>
<div
- v-for="(option, index) in options"
- :key="index"
- class="poll-option"
+ :role="showResults ? 'section' : (poll.multiple ? 'group' : 'radiogroup')"
>
<div
- v-if="showResults"
- :title="resultTitle(option)"
- class="option-result"
+ v-for="(option, index) in options"
+ :key="index"
+ class="poll-option"
>
- <div class="option-result-label">
- <span class="result-percentage">
- {{ percentageForOption(option.votes_count) }}%
- </span>
- <RichContent
- :html="option.title_html"
- :handle-links="false"
- :emoji="emoji"
+ <div
+ v-if="showResults"
+ :title="resultTitle(option)"
+ class="option-result"
+ >
+ <div class="option-result-label">
+ <span class="result-percentage">
+ {{ percentageForOption(option.votes_count) }}%
+ </span>
+ <RichContent
+ :html="option.title_html"
+ :handle-links="false"
+ :emoji="emoji"
+ />
+ </div>
+ <div
+ class="result-fill"
+ :style="{ 'width': `${percentageForOption(option.votes_count)}%` }"
/>
</div>
<div
- class="result-fill"
- :style="{ 'width': `${percentageForOption(option.votes_count)}%` }"
- />
- </div>
- <div
- v-else
- @click="activateOption(index)"
- >
- <input
- v-if="poll.multiple"
- type="checkbox"
- :disabled="loading"
- :value="index"
- >
- <input
v-else
- type="radio"
- :disabled="loading"
- :value="index"
+ tabindex="0"
+ :role="poll.multiple ? 'checkbox' : 'radio'"
+ :aria-labelledby="`option-vote-${randomSeed}-${index}`"
+ :aria-checked="choices[index]"
+ class="input unstyled"
+ @click="activateOption(index)"
>
- <label class="option-vote">
- <RichContent
- :html="option.title_html"
- :handle-links="false"
- :emoji="emoji"
- />
- </label>
+ <!-- TODO: USE CHECKBOX -->
+ <input
+ v-if="poll.multiple"
+ type="checkbox"
+ class="input -checkbox poll-checkbox"
+ :disabled="loading"
+ :value="index"
+ >
+ <input
+ v-else
+ type="radio"
+ :disabled="loading"
+ :value="index"
+ class="input -radio"
+ >
+ <label class="option-vote">
+ <RichContent
+ :id="`option-vote-${randomSeed}-${index}`"
+ :html="option.title_html"
+ :handle-links="false"
+ :emoji="emoji"
+ />
+ </label>
+ </div>
</div>
</div>
<div class="footer faint">
@@ -63,15 +76,25 @@
>
{{ $t('polls.vote') }}
</button>
+ <span
+ v-if="poll.pleroma?.non_anonymous"
+ :title="$t('polls.non_anonymous_title')"
+ >
+ {{ $t('polls.non_anonymous') }}
+ ·
+ </span>
<div class="total">
<template v-if="typeof poll.voters_count === 'number'">
- {{ $tc("polls.people_voted_count", poll.voters_count, { count: poll.voters_count }) }} ·
+ {{ $tc("polls.people_voted_count", poll.voters_count, { count: poll.voters_count }) }}
</template>
<template v-else>
- {{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }} ·
+ {{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }}
</template>
+ <span v-if="expiresAt !== null">
+ ·
+ </span>
</div>
- <span>
+ <span v-if="expiresAt !== null">
<i18n-t
scope="global"
:keypath="expired ? 'polls.expired' : 'polls.expires_in'"
@@ -90,25 +113,29 @@
<script src="./poll.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.poll {
.votes {
display: flex;
flex-direction: column;
margin: 0 0 0.5em;
}
+
.poll-option {
margin: 0.75em 0.5em;
+
+ .input {
+ line-height: inherit;
+ }
}
+
.option-result {
height: 100%;
display: flex;
flex-direction: row;
position: relative;
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
+ color: var(--textLight);
}
+
.option-result-label {
display: flex;
align-items: center;
@@ -116,40 +143,46 @@
z-index: 1;
word-break: break-word;
}
+
.result-percentage {
width: 3.5em;
flex-shrink: 0;
}
+
.result-fill {
height: 100%;
position: absolute;
- color: $fallback--text;
- color: var(--pollText, $fallback--text);
- background-color: $fallback--lightBg;
- background-color: var(--poll, $fallback--lightBg);
- border-radius: $fallback--panelRadius;
- border-radius: var(--panelRadius, $fallback--panelRadius);
+ border-radius: var(--roundness);
top: 0;
left: 0;
transition: width 0.5s;
}
+
.option-vote {
display: flex;
align-items: center;
}
+
input {
width: 3.5em;
}
+
.footer {
display: flex;
align-items: center;
}
+
&.loading * {
cursor: progress;
}
+
.poll-vote-button {
padding: 0 0.5em;
margin-right: 0.5em;
}
+
+ .poll-checkbox {
+ display: none;
+ }
}
</style>
diff --git a/src/components/poll/poll_form.js b/src/components/poll/poll_form.js
@@ -94,19 +94,10 @@ export default {
},
convertExpiryToUnit (unit, amount) {
// Note: we want seconds and not milliseconds
- switch (unit) {
- case 'minutes': return (1000 * amount) / DateUtils.MINUTE
- case 'hours': return (1000 * amount) / DateUtils.HOUR
- case 'days': return (1000 * amount) / DateUtils.DAY
- }
+ return DateUtils.secondsToUnit(unit, amount)
},
convertExpiryFromUnit (unit, amount) {
- // Note: we want seconds and not milliseconds
- switch (unit) {
- case 'minutes': return 0.001 * amount * DateUtils.MINUTE
- case 'hours': return 0.001 * amount * DateUtils.HOUR
- case 'days': return 0.001 * amount * DateUtils.DAY
- }
+ return DateUtils.unitToSeconds(unit, amount)
},
expiryAmountChange () {
this.expiryAmount =
diff --git a/src/components/poll/poll_form.vue b/src/components/poll/poll_form.vue
@@ -13,7 +13,7 @@
:id="`poll-${index}`"
v-model="options[index]"
size="1"
- class="poll-option-input"
+ class="input poll-option-input"
type="text"
:placeholder="$t('polls.option')"
:maxlength="maxLength"
@@ -67,7 +67,7 @@
<input
v-model="expiryAmount"
type="number"
- class="expiry-amount hide-number-spinner"
+ class="input expiry-amount hide-number-spinner"
:min="minExpirationInCurrentUnit"
:max="maxExpirationInCurrentUnit"
@change="expiryAmountChange"
@@ -84,7 +84,7 @@
:key="unit"
:value="unit"
>
- {{ $t(`time.${unit}_short`, ['']) }}
+ {{ $tc(`time.unit.${unit}_short`, expiryAmount, ['']) }}
</option>
</Select>
</div>
@@ -95,8 +95,6 @@
<script src="./poll_form.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.poll-form {
display: flex;
flex-direction: column;
@@ -117,6 +115,7 @@
.input-container {
width: 100%;
+
input {
// Hack: dodge the floating X icon
padding-right: 2.5em;
diff --git a/src/components/poll/poll_graph.style.js b/src/components/poll/poll_graph.style.js
@@ -0,0 +1,12 @@
+export default {
+ name: 'PollGraph',
+ selector: '.result-fill',
+ defaultRules: [
+ {
+ directives: {
+ background: '--accent',
+ opacity: 0.5
+ }
+ }
+ ]
+}
diff --git a/src/components/popover.style.js b/src/components/popover.style.js
@@ -0,0 +1,36 @@
+export default {
+ name: 'Popover',
+ selector: '.popover',
+ lazy: true,
+ variants: {
+ modal: '.modal'
+ },
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'ButtonUnstyled',
+ 'Input',
+ 'MenuItem',
+ 'Post',
+ 'UserCard'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg',
+ blur: '10px',
+ shadow: [{
+ x: 2,
+ y: 2,
+ blur: 3,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.5
+ }]
+ }
+ }
+ ]
+}
diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js
@@ -4,7 +4,7 @@ const Popover = {
// Action to trigger popover: either 'hover' or 'click'
trigger: String,
- // Either 'top' or 'bottom'
+ // 'top', 'bottom', 'left', 'right'
placement: String,
// Takes object with properties 'x' and 'y', values of these can be
@@ -31,40 +31,95 @@ const Popover = {
// If true, subtract padding when calculating position for the popover,
// use it when popover offset looks to be different on top vs bottom.
- removePadding: Boolean
+ removePadding: Boolean,
+
+ // self-explanatory (i hope)
+ disabled: Boolean,
+
+ // Instead of putting popover next to anchor, overlay popover's center on top of anchor's center
+ overlayCenters: Boolean,
+
+ // What selector (witin popover!) to use for determining center of popover
+ overlayCentersSelector: String,
+
+ // Lets hover popover stay when clicking inside of it
+ stayOnClick: Boolean,
+
+ // Use styled button (to avoid nested buttons)
+ normalButton: Boolean,
+
+ triggerAttrs: {
+ type: Object,
+ default: {}
+ }
+ },
+ inject: { // override popover z layer
+ popoversZLayer: {
+ default: ''
+ }
},
data () {
return {
+ // lockReEntry is a flag that is set when mouse cursor is leaving the popover's content
+ // so that if mouse goes back into popover it won't be re-shown again to prevent annoyance
+ // with popovers refusing to be hidden when user wants to interact with something in below popover
+ anchorEl: null,
+ // There's an issue where having teleport enabled by default causes things just...
+ // not render at all, i.e. main post status form and its emoji inputs
+ teleport: false,
+ lockReEntry: false,
hidden: true,
- styles: { opacity: 0 },
- oldSize: { width: 0, height: 0 }
+ styles: {},
+ oldSize: { width: 0, height: 0 },
+ scrollable: null,
+ // used to avoid blinking if hovered onto popover
+ graceTimeout: null,
+ parentPopover: null,
+ disableClickOutside: false,
+ childrenShown: new Set()
}
},
methods: {
+ setAnchorEl (el) {
+ this.anchorEl = el
+ this.updateStyles()
+ },
containerBoundingClientRect () {
const container = this.boundToSelector ? this.$el.closest(this.boundToSelector) : this.$el.offsetParent
return container.getBoundingClientRect()
},
updateStyles () {
if (this.hidden) {
- this.styles = {
- opacity: 0
- }
+ this.styles = {}
return
}
// Popover will be anchored around this element, trigger ref is the container, so
// its children are what are inside the slot. Expect only one v-slot:trigger.
- const anchorEl = (this.$refs.trigger && this.$refs.trigger.children[0]) || this.$el
+ const anchorEl = this.anchorEl || (this.$refs.trigger && this.$refs.trigger.children[0]) || this.$el
// SVGs don't have offsetWidth/Height, use fallback
- const anchorWidth = anchorEl.offsetWidth || anchorEl.clientWidth
const anchorHeight = anchorEl.offsetHeight || anchorEl.clientHeight
- const screenBox = anchorEl.getBoundingClientRect()
- // Screen position of the origin point for popover
- const origin = { x: screenBox.left + screenBox.width * 0.5, y: screenBox.top }
+ const anchorWidth = anchorEl.offsetWidth || anchorEl.clientWidth
+ const anchorScreenBox = anchorEl.getBoundingClientRect()
+
+ const anchorStyle = getComputedStyle(anchorEl)
+ const topPadding = parseFloat(anchorStyle.paddingTop)
+ const bottomPadding = parseFloat(anchorStyle.paddingBottom)
+ const rightPadding = parseFloat(anchorStyle.paddingRight)
+ const leftPadding = parseFloat(anchorStyle.paddingLeft)
+
+ // Screen position of the origin point for popover = center of the anchor
+ const origin = {
+ x: anchorScreenBox.left + anchorWidth * 0.5,
+ y: anchorScreenBox.top + anchorHeight * 0.5
+ }
const content = this.$refs.content
+ const overlayCenter = this.overlayCenters
+ ? this.$refs.content.querySelector(this.overlayCentersSelector)
+ : null
+
// Minor optimization, don't call a slow reflow call if we don't have to
- const parentBounds = this.boundTo &&
+ const parentScreenBox = this.boundTo &&
(this.boundTo.x === 'container' || this.boundTo.y === 'container') &&
this.containerBoundingClientRect()
@@ -72,82 +127,179 @@ const Popover = {
// What are the screen bounds for the popover? Viewport vs container
// when using viewport, using default margin values to dodge the navbar
- const xBounds = this.boundTo && this.boundTo.x === 'container' ? {
- min: parentBounds.left + (margin.left || 0),
- max: parentBounds.right - (margin.right || 0)
- } : {
- min: 0 + (margin.left || 10),
- max: window.innerWidth - (margin.right || 10)
- }
+ const xBounds = this.boundTo && this.boundTo.x === 'container'
+ ? {
+ min: parentScreenBox.left + (margin.left || 0),
+ max: parentScreenBox.right - (margin.right || 0)
+ }
+ : {
+ min: 0 + (margin.left || 10),
+ max: window.innerWidth - (margin.right || 10)
+ }
- const yBounds = this.boundTo && this.boundTo.y === 'container' ? {
- min: parentBounds.top + (margin.top || 0),
- max: parentBounds.bottom - (margin.bottom || 0)
- } : {
- min: 0 + (margin.top || 50),
- max: window.innerHeight - (margin.bottom || 5)
- }
+ const yBounds = this.boundTo && this.boundTo.y === 'container'
+ ? {
+ min: parentScreenBox.top + (margin.top || 0),
+ max: parentScreenBox.bottom - (margin.bottom || 0)
+ }
+ : {
+ min: 0 + (margin.top || 50),
+ max: window.innerHeight - (margin.bottom || 5)
+ }
let horizOffset = 0
+ let vertOffset = 0
+
+ if (overlayCenter) {
+ const box = content.getBoundingClientRect()
+ const overlayCenterScreenBox = overlayCenter.getBoundingClientRect()
+ const leftInnerOffset = overlayCenterScreenBox.left - box.left
+ const topInnerOffset = overlayCenterScreenBox.top - box.top
+ horizOffset = -leftInnerOffset - overlayCenter.offsetWidth * 0.5
+ vertOffset = -topInnerOffset - overlayCenter.offsetHeight * 0.5
+ } else {
+ horizOffset = content.offsetWidth * -0.5
+ vertOffset = content.offsetHeight * -0.5
+ }
+
+ const leftBorder = origin.x + horizOffset
+ const rightBorder = leftBorder + content.offsetWidth
+ const topBorder = origin.y + vertOffset
+ const bottomBorder = topBorder + content.offsetHeight
// If overflowing from left, move it so that it doesn't
- if ((origin.x - content.offsetWidth * 0.5) < xBounds.min) {
- horizOffset += -(origin.x - content.offsetWidth * 0.5) + xBounds.min
+ if (leftBorder < xBounds.min) {
+ horizOffset += xBounds.min - leftBorder
}
// If overflowing from right, move it so that it doesn't
- if ((origin.x + horizOffset + content.offsetWidth * 0.5) > xBounds.max) {
- horizOffset -= (origin.x + horizOffset + content.offsetWidth * 0.5) - xBounds.max
+ if (rightBorder > xBounds.max) {
+ horizOffset -= rightBorder - xBounds.max
}
- // Default to whatever user wished with placement prop
- let usingTop = this.placement !== 'bottom'
-
- // Handle special cases, first force to displaying on top if there's not space on bottom,
- // regardless of what placement value was. Then check if there's not space on top, and
- // force to bottom, again regardless of what placement value was.
- if (origin.y + content.offsetHeight > yBounds.max) usingTop = true
- if (origin.y - content.offsetHeight < yBounds.min) usingTop = false
+ // If overflowing from top, move it so that it doesn't
+ if (topBorder < yBounds.min) {
+ vertOffset += yBounds.min - topBorder
+ }
- let vPadding = 0
- if (this.removePadding && usingTop) {
- const anchorStyle = getComputedStyle(anchorEl)
- vPadding = parseFloat(anchorStyle.paddingTop) + parseFloat(anchorStyle.paddingBottom)
+ // If overflowing from bottom, move it so that it doesn't
+ if (bottomBorder > yBounds.max) {
+ vertOffset -= bottomBorder - yBounds.max
}
- const yOffset = (this.offset && this.offset.y) || 0
- const translateY = usingTop
- ? -anchorHeight + vPadding - yOffset - content.offsetHeight
- : yOffset
+ let translateX = 0
+ let translateY = 0
+
+ if (overlayCenter) {
+ translateX = origin.x + horizOffset
+ translateY = origin.y + vertOffset
+ } else if (this.placement !== 'right' && this.placement !== 'left') {
+ // Default to whatever user wished with placement prop
+ let usingTop = this.placement !== 'bottom'
- const xOffset = (this.offset && this.offset.x) || 0
- const translateX = anchorWidth * 0.5 - content.offsetWidth * 0.5 + horizOffset + xOffset
+ // Handle special cases, first force to displaying on top if there's not space on bottom,
+ // regardless of what placement value was. Then check if there's not space on top, and
+ // force to bottom, again regardless of what placement value was.
+ const topBoundary = origin.y - anchorHeight * 0.5 + (this.removePadding ? topPadding : 0)
+ const bottomBoundary = origin.y + anchorHeight * 0.5 - (this.removePadding ? bottomPadding : 0)
+ if (bottomBoundary + content.offsetHeight > yBounds.max) usingTop = true
+ if (topBoundary - content.offsetHeight < yBounds.min) usingTop = false
+
+ const yOffset = (this.offset && this.offset.y) || 0
+ translateY = usingTop
+ ? topBoundary - yOffset - content.offsetHeight
+ : bottomBoundary + yOffset
+
+ const xOffset = (this.offset && this.offset.x) || 0
+ translateX = origin.x + horizOffset + xOffset
+ } else {
+ // Default to whatever user wished with placement prop
+ let usingRight = this.placement !== 'left'
+
+ // Handle special cases, first force to displaying on top if there's not space on bottom,
+ // regardless of what placement value was. Then check if there's not space on top, and
+ // force to bottom, again regardless of what placement value was.
+ const rightBoundary = origin.x - anchorWidth * 0.5 + (this.removePadding ? rightPadding : 0)
+ const leftBoundary = origin.x + anchorWidth * 0.5 - (this.removePadding ? leftPadding : 0)
+ if (leftBoundary + content.offsetWidth > xBounds.max) usingRight = true
+ if (rightBoundary - content.offsetWidth < xBounds.min) usingRight = false
+
+ const xOffset = (this.offset && this.offset.x) || 0
+ translateX = usingRight
+ ? rightBoundary - xOffset - content.offsetWidth
+ : leftBoundary + xOffset
+
+ const yOffset = (this.offset && this.offset.y) || 0
+ translateY = origin.y + vertOffset + yOffset
+ }
- // Note, separate translateX and translateY avoids blurry text on chromium,
- // single translate or translate3d resulted in blurry text.
this.styles = {
- opacity: 1,
- transform: `translateX(${Math.round(translateX)}px) translateY(${Math.round(translateY)}px)`
+ left: `${Math.round(translateX)}px`,
+ top: `${Math.round(translateY)}px`
+ }
+
+ if (this.popoversZLayer) {
+ this.styles['--ZI_popover_override'] = `var(--ZI_${this.popoversZLayer}_popovers)`
+ }
+ if (parentScreenBox) {
+ this.styles.maxWidth = `${Math.round(parentScreenBox.width)}px`
}
},
showPopover () {
+ if (this.disabled) return
+ this.disableClickOutside = true
+ setTimeout(() => {
+ this.disableClickOutside = false
+ }, 0)
const wasHidden = this.hidden
this.hidden = false
+ this.parentPopover && this.parentPopover.onChildPopoverState(this, true)
+ if (this.trigger === 'click' || this.stayOnClick) {
+ document.addEventListener('click', this.onClickOutside)
+ }
+ this.scrollable.addEventListener('scroll', this.onScroll)
+ this.scrollable.addEventListener('resize', this.onResize)
this.$nextTick(() => {
if (wasHidden) this.$emit('show')
this.updateStyles()
})
},
hidePopover () {
+ if (this.disabled) return
if (!this.hidden) this.$emit('close')
this.hidden = true
- this.styles = { opacity: 0 }
+ this.parentPopover && this.parentPopover.onChildPopoverState(this, false)
+ if (this.trigger === 'click') {
+ document.removeEventListener('click', this.onClickOutside)
+ }
+ this.scrollable.removeEventListener('scroll', this.onScroll)
+ this.scrollable.removeEventListener('resize', this.onResize)
},
onMouseenter (e) {
- if (this.trigger === 'hover') this.showPopover()
+ if (this.trigger === 'hover') {
+ this.lockReEntry = false
+ clearTimeout(this.graceTimeout)
+ this.graceTimeout = null
+ this.showPopover()
+ }
},
onMouseleave (e) {
- if (this.trigger === 'hover') this.hidePopover()
+ if (this.trigger === 'hover' && this.childrenShown.size === 0) {
+ this.graceTimeout = setTimeout(() => this.hidePopover(), 1)
+ }
+ },
+ onMouseenterContent (e) {
+ if (this.trigger === 'hover' && !this.lockReEntry) {
+ this.lockReEntry = true
+ clearTimeout(this.graceTimeout)
+ this.graceTimeout = null
+ this.showPopover()
+ }
+ },
+ onMouseleaveContent (e) {
+ if (this.trigger === 'hover' && this.childrenShown.size === 0) {
+ this.graceTimeout = setTimeout(() => this.hidePopover(), 1)
+ }
},
onClick (e) {
if (this.trigger === 'click') {
@@ -159,9 +311,26 @@ const Popover = {
}
},
onClickOutside (e) {
+ if (this.disableClickOutside) return
if (this.hidden) return
+ if (this.$refs.content && this.$refs.content.contains(e.target)) return
if (this.$el.contains(e.target)) return
+ if (this.childrenShown.size > 0) return
this.hidePopover()
+ if (this.parentPopover) this.parentPopover.onClickOutside(e)
+ },
+ onScroll (e) {
+ this.updateStyles()
+ },
+ onResize (e) {
+ this.updateStyles()
+ },
+ onChildPopoverState (childRef, state) {
+ if (state) {
+ this.childrenShown.add(childRef)
+ } else {
+ this.childrenShown.delete(childRef)
+ }
}
},
updated () {
@@ -175,11 +344,19 @@ const Popover = {
this.oldSize = { width: content.offsetWidth, height: content.offsetHeight }
}
},
- created () {
- document.addEventListener('click', this.onClickOutside)
+ mounted () {
+ this.teleport = true
+ let scrollable = this.$refs.trigger.closest('.column.-scrollable') ||
+ this.$refs.trigger.closest('.mobile-notifications')
+ if (!scrollable) scrollable = window
+ this.scrollable = scrollable
+ let parent = this.$parent
+ while (parent && parent.$.type.name !== 'Popover') {
+ parent = parent.$parent
+ }
+ this.parentPopover = parent
},
- unmounted () {
- document.removeEventListener('click', this.onClickOutside)
+ beforeUnmount () {
this.hidePopover()
}
}
diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue
@@ -1,157 +1,130 @@
<template>
- <div
+ <span
@mouseenter="onMouseenter"
@mouseleave="onMouseleave"
>
<button
ref="trigger"
- class="button-unstyled popover-trigger-button"
+ class="popover-trigger-button"
+ :class="normalButton ? 'button-default btn' : 'button-unstyled'"
type="button"
+ v-bind="triggerAttrs"
@click="onClick"
>
<slot name="trigger" />
</button>
- <div
- v-if="!hidden"
- ref="content"
- :style="styles"
- class="popover"
- :class="popoverClass || 'popover-default'"
+ <teleport
+ :disabled="!teleport"
+ to="#popovers"
>
- <slot
- name="content"
- class="popover-inner"
- :close="hidePopover"
- />
- </div>
- </div>
+ <transition name="fade">
+ <div
+ v-if="!hidden"
+ ref="content"
+ :style="styles"
+ class="popover"
+ :class="popoverClass || 'popover-default'"
+ @mouseenter="onMouseenterContent"
+ @mouseleave="onMouseleaveContent"
+ @click="onClickContent"
+ >
+ <slot
+ name="content"
+ class="popover-inner"
+ :close="hidePopover"
+ />
+ </div>
+ </transition>
+ </teleport>
+ </span>
</template>
<script src="./popover.js" />
<style lang="scss">
-@import '../../_variables.scss';
-
.popover-trigger-button {
display: inline-block;
}
.popover {
- z-index: 500;
- position: absolute;
+ z-index: var(--ZI_popover_override, var(--ZI_popovers));
+ position: fixed;
min-width: 0;
+ max-width: calc(100vw - 20px);
+ box-shadow: var(--shadow);
}
.popover-default {
- transition: opacity 0.3s;
-
- &:after {
- content: '';
+ &::after {
+ content: "";
position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- z-index: 3;
- box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
- box-shadow: var(--panelShadow);
+ top: -1px;
+ bottom: -1px;
+ left: -1px;
+ right: -1px;
+ z-index: -1px;
+ box-shadow: var(--shadow);
pointer-events: none;
}
- border-radius: $fallback--btnRadius;
- border-radius: var(--btnRadius, $fallback--btnRadius);
-
- background-color: $fallback--bg;
- background-color: var(--popover, $fallback--bg);
- color: $fallback--text;
- color: var(--popoverText, $fallback--text);
- --faint: var(--popoverFaintText, $fallback--faint);
- --faintLink: var(--popoverFaintLink, $fallback--faint);
- --lightText: var(--popoverLightText, $fallback--lightText);
- --postLink: var(--popoverPostLink, $fallback--link);
- --postFaintLink: var(--popoverPostFaintLink, $fallback--link);
- --icon: var(--popoverIcon, $fallback--icon);
+ border-radius: var(--roundness);
+ border-color: var(--border);
+ border-style: solid;
+ border-width: 1px;
+ background-color: var(--background);
}
.dropdown-menu {
display: block;
- padding: .5rem 0;
+ padding: 0;
font-size: 1em;
text-align: left;
list-style: none;
max-width: 100vw;
- z-index: 200;
+ z-index: var(--ZI_popover_override, var(--ZI_popovers));
white-space: nowrap;
+ background-color: var(--background);
.dropdown-divider {
height: 0;
- margin: .5rem 0;
+ margin: 0.5rem 0;
overflow: hidden;
- border-top: 1px solid $fallback--border;
- border-top: 1px solid var(--border, $fallback--border);
+ border-top: 1px solid var(--border);
}
.dropdown-item {
- line-height: 21px;
- overflow: hidden;
- display: block;
- padding: 0.5em 0.75em;
- clear: both;
- font-weight: 400;
- text-align: inherit;
- white-space: nowrap;
border: none;
- border-radius: 0px;
- background-color: transparent;
- box-shadow: none;
- width: 100%;
- height: 100%;
- box-sizing: border-box;
-
- --btnText: var(--popoverText, $fallback--text);
&-icon {
svg {
- width: 22px;
- margin-right: 0.75rem;
- color: var(--menuPopoverIcon, $fallback--icon)
+ width: var(--__line-height);
+ margin-right: var(--__horizontal-gap);
}
}
- &:active, &:hover {
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenuPopover, $fallback--lightBg);
- box-shadow: none;
- --btnText: var(--selectedMenuPopoverText, $fallback--link);
- --faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
- --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
- svg {
- color: var(--selectedMenuPopoverIcon, $fallback--icon);
- --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
+ &.-has-submenu {
+ .chevron-icon {
+ margin-right: 0.25rem;
+ margin-left: 2rem;
}
}
.menu-checkbox {
display: inline-block;
vertical-align: middle;
- min-width: 22px;
- max-width: 22px;
- min-height: 22px;
- max-height: 22px;
- line-height: 22px;
+ min-width: calc(var(--__line-height) + 1px);
+ max-width: calc(var(--__line-height) + 1px);
+ min-height: calc(var(--__line-height) + 1px);
+ max-height: calc(var(--__line-height) + 1px);
+ line-height: var(--__line-height);
text-align: center;
- border-radius: 0px;
- background-color: $fallback--fg;
- background-color: var(--input, $fallback--fg);
- box-shadow: 0px 0px 2px black inset;
- box-shadow: var(--inputShadow);
- margin-right: 0.75em;
+ border-radius: 0;
+ box-shadow: var(--shadow);
+ margin-right: var(--__horizontal-gap);
&.menu-checkbox-checked::after {
font-size: 1.25em;
- content: '✓';
+ content: "✓";
}
&.-radio {
@@ -159,36 +132,10 @@
&.menu-checkbox-checked::after {
font-size: 2em;
- content: '•';
+ content: "•";
}
}
}
-
- }
-
- .button-default.dropdown-item {
- &,
- i[class*=icon-] {
- color: $fallback--text;
- color: var(--btnText, $fallback--text);
- }
-
- &:active {
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenuPopover, $fallback--lightBg);
- color: $fallback--link;
- color: var(--selectedMenuPopoverText, $fallback--link);
- }
-
- &:disabled {
- color: $fallback--text;
- color: var(--btnDisabledText, $fallback--text);
- }
-
- &.toggled {
- color: $fallback--text;
- color: var(--btnToggledText, $fallback--text);
- }
}
}
</style>
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
@@ -1,4 +1,5 @@
import statusPoster from '../../services/status_poster/status_poster.service.js'
+import genRandomSeed from '../../services/random_seed/random_seed.service.js'
import MediaUpload from '../media_upload/media_upload.vue'
import ScopeSelector from '../scope_selector/scope_selector.vue'
import EmojiInput from '../emoji_input/emoji_input.vue'
@@ -8,6 +9,7 @@ import Gallery from 'src/components/gallery/gallery.vue'
import StatusContent from '../status_content/status_content.vue'
import fileTypeService from '../../services/file_type/file_type.service.js'
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
+import { propsToNative } from '../../services/attributes_helper/attributes_helper.service.js'
import { reject, map, uniqBy, debounce } from 'lodash'
import suggestor from '../emoji_input/suggestor.js'
import { mapGetters, mapState } from 'vuex'
@@ -41,7 +43,7 @@ const buildMentionsString = ({ user, attentions = [] }, currentUser) => {
allAttentions = uniqBy(allAttentions, 'id')
allAttentions = reject(allAttentions, { id: currentUser.id })
- let mentions = map(allAttentions, (attention) => {
+ const mentions = map(allAttentions, (attention) => {
return `@${attention.screen_name}`
})
@@ -55,6 +57,14 @@ const pxStringToNumber = (str) => {
const PostStatusForm = {
props: [
+ 'statusId',
+ 'statusText',
+ 'statusIsSensitive',
+ 'statusPoll',
+ 'statusFiles',
+ 'statusMediaDescriptions',
+ 'statusScope',
+ 'statusContentType',
'replyTo',
'repliedUser',
'attentions',
@@ -62,6 +72,7 @@ const PostStatusForm = {
'subject',
'disableSubject',
'disableScopeSelector',
+ 'disableVisibilitySelector',
'disableNotice',
'disableLockWarning',
'disablePolls',
@@ -76,7 +87,8 @@ const PostStatusForm = {
'fileLimit',
'submitOnEnter',
'emojiPickerPlacement',
- 'optimisticPosting'
+ 'optimisticPosting',
+ 'profileMention'
],
emits: [
'posted',
@@ -114,7 +126,7 @@ const PostStatusForm = {
const { scopeCopy } = this.$store.getters.mergedConfig
- if (this.replyTo) {
+ if (this.replyTo || this.profileMention) {
const currentUser = this.$store.state.users.currentUser
statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
}
@@ -125,22 +137,40 @@ const PostStatusForm = {
const { postContentType: contentType, sensitiveByDefault } = this.$store.getters.mergedConfig
+ let statusParams = {
+ spoilerText: this.subject || '',
+ status: statusText,
+ nsfw: !!sensitiveByDefault,
+ files: [],
+ poll: {},
+ mediaDescriptions: {},
+ visibility: scope,
+ contentType
+ }
+
+ if (this.statusId) {
+ const statusContentType = this.statusContentType || contentType
+ statusParams = {
+ spoilerText: this.subject || '',
+ status: this.statusText || '',
+ nsfw: this.statusIsSensitive || !!sensitiveByDefault,
+ files: this.statusFiles || [],
+ poll: this.statusPoll || {},
+ mediaDescriptions: this.statusMediaDescriptions || {},
+ visibility: this.statusScope || scope,
+ contentType: statusContentType,
+ quoting: false
+ }
+ }
+
return {
+ randomSeed: genRandomSeed(),
dropFiles: [],
uploadingFiles: false,
error: null,
posting: false,
highlighted: 0,
- newStatus: {
- spoilerText: this.subject || '',
- status: statusText,
- nsfw: !!sensitiveByDefault,
- files: [],
- poll: {},
- mediaDescriptions: {},
- visibility: scope,
- contentType
- },
+ newStatus: statusParams,
caret: 0,
pollFormVisible: false,
showDropIcon: 'hide',
@@ -164,7 +194,7 @@ const PostStatusForm = {
emojiUserSuggestor () {
return suggestor({
emoji: [
- ...this.$store.state.instance.emoji,
+ ...this.$store.getters.standardEmojiList,
...this.$store.state.instance.customEmoji
],
store: this.$store
@@ -173,13 +203,13 @@ const PostStatusForm = {
emojiSuggestor () {
return suggestor({
emoji: [
- ...this.$store.state.instance.emoji,
+ ...this.$store.getters.standardEmojiList,
...this.$store.state.instance.customEmoji
]
})
},
emoji () {
- return this.$store.state.instance.emoji || []
+ return this.$store.getters.standardEmojiList || []
},
customEmoji () {
return this.$store.state.instance.customEmoji || []
@@ -236,13 +266,40 @@ const PostStatusForm = {
uploadFileLimitReached () {
return this.newStatus.files.length >= this.fileLimit
},
+ isEdit () {
+ return typeof this.statusId !== 'undefined' && this.statusId.trim() !== ''
+ },
+ quotable () {
+ if (!this.$store.state.instance.quotingAvailable) {
+ return false
+ }
+
+ if (!this.replyTo) {
+ return false
+ }
+
+ const repliedStatus = this.$store.state.statuses.allStatusesObject[this.replyTo]
+ if (!repliedStatus) {
+ return false
+ }
+
+ if (repliedStatus.visibility === 'public' ||
+ repliedStatus.visibility === 'unlisted' ||
+ repliedStatus.visibility === 'local') {
+ return true
+ } else if (repliedStatus.visibility === 'private') {
+ return repliedStatus.user.id === this.$store.state.users.currentUser.id
+ }
+
+ return false
+ },
...mapGetters(['mergedConfig']),
...mapState({
mobileLayout: state => state.interface.mobileLayout
})
},
watch: {
- 'newStatus': {
+ newStatus: {
deep: true,
handler () {
this.statusChanged()
@@ -263,7 +320,8 @@ const PostStatusForm = {
visibility: newStatus.visibility,
contentType: newStatus.contentType,
poll: {},
- mediaDescriptions: {}
+ mediaDescriptions: {},
+ quoting: false
}
this.pollFormVisible = false
this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
@@ -273,7 +331,7 @@ const PostStatusForm = {
this.$refs.textarea.focus()
})
}
- let el = this.$el.querySelector('textarea')
+ const el = this.$el.querySelector('textarea')
el.style.height = 'auto'
el.style.height = undefined
this.error = null
@@ -311,6 +369,8 @@ const PostStatusForm = {
return
}
+ const replyOrQuoteAttr = newStatus.quoting ? 'quoteId' : 'inReplyToStatusId'
+
const postingOptions = {
status: newStatus.status,
spoilerText: newStatus.spoilerText || null,
@@ -318,7 +378,7 @@ const PostStatusForm = {
sensitive: newStatus.nsfw,
media: newStatus.files,
store: this.$store,
- inReplyToStatusId: this.replyTo,
+ [replyOrQuoteAttr]: this.replyTo,
contentType: newStatus.contentType,
poll,
idempotencyKey: this.idempotencyKey
@@ -344,6 +404,7 @@ const PostStatusForm = {
}
const newStatus = this.newStatus
this.previewLoading = true
+ const replyOrQuoteAttr = newStatus.quoting ? 'quoteId' : 'inReplyToStatusId'
statusPoster.postStatus({
status: newStatus.status,
spoilerText: newStatus.spoilerText || null,
@@ -351,7 +412,7 @@ const PostStatusForm = {
sensitive: newStatus.nsfw,
media: [],
store: this.$store,
- inReplyToStatusId: this.replyTo,
+ [replyOrQuoteAttr]: this.replyTo,
contentType: newStatus.contentType,
poll: {},
preview: true
@@ -392,7 +453,7 @@ const PostStatusForm = {
this.$emit('resize', { delayed: true })
},
removeMediaFile (fileInfo) {
- let index = this.newStatus.files.indexOf(fileInfo)
+ const index = this.newStatus.files.indexOf(fileInfo)
this.newStatus.files.splice(index, 1)
this.$emit('resize')
},
@@ -462,7 +523,7 @@ const PostStatusForm = {
},
onEmojiInputInput (e) {
this.$nextTick(() => {
- this.resize(this.$refs['textarea'])
+ this.resize(this.$refs.textarea)
})
},
resize (e) {
@@ -473,12 +534,11 @@ const PostStatusForm = {
if (target.value === '') {
target.style.height = null
this.$emit('resize')
- this.$refs['emoji-input'].resize()
return
}
- const formRef = this.$refs['form']
- const bottomRef = this.$refs['bottom']
+ const formRef = this.$refs.form
+ const bottomRef = this.$refs.bottom
/* 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
@@ -560,11 +620,9 @@ const PostStatusForm = {
} else {
scrollerRef.scrollTop = targetScroll
}
-
- this.$refs['emoji-input'].resize()
},
showEmojiPicker () {
- this.$refs['textarea'].focus()
+ this.$refs.textarea.focus()
this.$refs['emoji-input'].triggerShowPicker()
},
clearError () {
@@ -604,6 +662,9 @@ const PostStatusForm = {
},
openProfileTab () {
this.$store.dispatch('openSettingsModalTab', 'profile')
+ },
+ propsToNative (props) {
+ return propsToNative(props)
}
}
}
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
@@ -30,6 +30,9 @@
<span>{{ $t('post_status.scope_notice.public') }}</span>
<a
class="fa-scale-110 fa-old-padding dismiss"
+ :title="$t('post_status.scope_notice_dismiss')"
+ role="button"
+ tabindex="0"
@click.prevent="dismissScopeNotice()"
>
<FAIcon icon="times" />
@@ -42,6 +45,9 @@
<span>{{ $t('post_status.scope_notice.unlisted') }}</span>
<a
class="fa-scale-110 fa-old-padding dismiss"
+ :title="$t('post_status.scope_notice_dismiss')"
+ role="button"
+ tabindex="0"
@click.prevent="dismissScopeNotice()"
>
<FAIcon icon="times" />
@@ -54,6 +60,9 @@
<span>{{ $t('post_status.scope_notice.private') }}</span>
<a
class="fa-scale-110 fa-old-padding dismiss"
+ :title="$t('post_status.scope_notice_dismiss')"
+ role="button"
+ tabindex="0"
@click.prevent="dismissScopeNotice()"
>
<FAIcon icon="times" />
@@ -67,6 +76,13 @@
<span v-else>{{ $t('post_status.direct_warning_to_all') }}</span>
</p>
<div
+ v-if="isEdit"
+ class="visibility-notice edit-warning"
+ >
+ <p>{{ $t('post_status.edit_remote_warning') }}</p>
+ <p>{{ $t('post_status.edit_unsupported_warning') }}</p>
+ </div>
+ <div
v-if="!disablePreview"
class="preview-heading faint"
>
@@ -87,6 +103,36 @@
icon="circle-notch"
/>
</div>
+ <div
+ v-if="quotable"
+ role="radiogroup"
+ class="btn-group reply-or-quote-selector"
+ >
+ <button
+ :id="`reply-or-quote-option-${randomSeed}-reply`"
+ class="btn button-default reply-or-quote-option"
+ :class="{ toggled: !newStatus.quoting }"
+ tabindex="0"
+ role="radio"
+ :aria-labelledby="`reply-or-quote-option-${randomSeed}-reply`"
+ :aria-checked="!newStatus.quoting"
+ @click="newStatus.quoting = false"
+ >
+ {{ $t('post_status.reply_option') }}
+ </button>
+ <button
+ :id="`reply-or-quote-option-${randomSeed}-quote`"
+ class="btn button-default reply-or-quote-option"
+ :class="{ toggled: newStatus.quoting }"
+ tabindex="0"
+ role="radio"
+ :aria-labelledby="`reply-or-quote-option-${randomSeed}-quote`"
+ :aria-checked="newStatus.quoting"
+ @click="newStatus.quoting = true"
+ >
+ {{ $t('post_status.quote_option') }}
+ </button>
+ </div>
</div>
<div
v-if="showPreview"
@@ -115,61 +161,68 @@
v-model="newStatus.spoilerText"
enable-emoji-picker
:suggest="emojiSuggestor"
- class="form-control"
+ class="input form-control"
>
- <input
- v-model="newStatus.spoilerText"
- type="text"
- :placeholder="$t('post_status.content_warning')"
- :disabled="posting && !optimisticPosting"
- size="1"
- class="form-post-subject"
- >
+ <template #default="inputProps">
+ <input
+ v-model="newStatus.spoilerText"
+ type="text"
+ :placeholder="$t('post_status.content_warning')"
+ :disabled="posting && !optimisticPosting"
+ v-bind="propsToNative(inputProps)"
+ size="1"
+ class="input form-post-subject"
+ >
+ </template>
</EmojiInput>
<EmojiInput
ref="emoji-input"
v-model="newStatus.status"
:suggest="emojiUserSuggestor"
:placement="emojiPickerPlacement"
- class="form-control main-input"
+ class="input form-control main-input"
+ enable-sticker-picker
enable-emoji-picker
hide-emoji-button
:newline-on-ctrl-enter="submitOnEnter"
- enable-sticker-picker
@input="onEmojiInputInput"
@sticker-uploaded="addMediaFile"
@sticker-upload-failed="uploadFailed"
@shown="handleEmojiInputShow"
>
- <textarea
- ref="textarea"
- v-model="newStatus.status"
- :placeholder="placeholder || $t('post_status.default')"
- rows="1"
- cols="1"
- :disabled="posting && !optimisticPosting"
- class="form-post-body"
- :class="{ 'scrollable-form': !!maxHeight }"
- @keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
- @keydown.meta.enter="postStatus($event, newStatus)"
- @keydown.ctrl.enter="!submitOnEnter && postStatus($event, newStatus)"
- @input="resize"
- @compositionupdate="resize"
- @paste="paste"
- />
- <p
- v-if="hasStatusLengthLimit"
- class="character-counter faint"
- :class="{ error: isOverLengthLimit }"
- >
- {{ charactersLeft }}
- </p>
+ <template #default="inputProps">
+ <textarea
+ ref="textarea"
+ v-model="newStatus.status"
+ :placeholder="placeholder || $t('post_status.default')"
+ rows="1"
+ cols="1"
+ :disabled="posting && !optimisticPosting"
+ class="input form-post-body"
+ :class="{ 'scrollable-form': !!maxHeight }"
+ v-bind="propsToNative(inputProps)"
+ @keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
+ @keydown.meta.enter="postStatus($event, newStatus)"
+ @keydown.ctrl.enter="!submitOnEnter && postStatus($event, newStatus)"
+ @input="resize"
+ @compositionupdate="resize"
+ @paste="paste"
+ />
+ <p
+ v-if="hasStatusLengthLimit"
+ class="character-counter faint"
+ :class="{ error: isOverLengthLimit }"
+ >
+ {{ charactersLeft }}
+ </p>
+ </template>
</EmojiInput>
<div
v-if="!disableScopeSelector"
class="visibility-tray"
>
<scope-selector
+ v-if="!disableVisibilitySelector"
:show-all="showAllScopes"
:user-default="userDefaultScope"
:original-scope="copyMessageScope"
@@ -182,9 +235,9 @@
class="text-format"
>
<Select
- id="post-content-type"
v-model="newStatus.contentType"
- class="form-control"
+ class="input form-control"
+ :attrs="{ 'aria-label': $t('post_status.content_type_selection') }"
>
<option
v-for="postFormat in postFormats"
@@ -257,12 +310,10 @@
>
{{ $t('post_status.post') }}
</button>
- <!-- touchstart is used to keep the OSK at the same position after a message send -->
<button
v-else
:disabled="uploadingFiles || disableSubmit"
class="btn button-default"
- @touchstart.stop.prevent="postStatus($event, newStatus)"
@click.stop.prevent="postStatus($event, newStatus)"
>
{{ $t('post_status.post') }}
@@ -323,8 +374,6 @@
<script src="./post_status_form.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.post-status-form {
position: relative;
@@ -359,18 +408,21 @@
.preview-heading {
display: flex;
- padding-left: 0.5em;
+ flex-wrap: wrap;
}
.preview-toggle {
- flex: 1;
+ flex: 10 0 auto;
cursor: pointer;
user-select: none;
+ padding-left: 0.5em;
&:hover {
text-decoration: underline;
}
- svg, i {
+
+ svg,
+ i {
margin-left: 0.2em;
font-size: 0.8em;
transform: rotate(90deg);
@@ -383,23 +435,26 @@
.preview-error {
font-style: italic;
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+ color: var(--textFaint);
}
.preview-status {
- border: 1px solid $fallback--border;
- border: 1px solid var(--border, $fallback--border);
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
+ border: 1px solid var(--border);
+ border-radius: var(--roundness);
padding: 0.5em;
margin: 0;
}
+ .reply-or-quote-selector {
+ flex: 1 0 auto;
+ margin-bottom: 0.5em;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ }
+
.text-format {
.only-format {
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+ color: var(--textFaint);
}
}
@@ -410,33 +465,13 @@
align-items: baseline;
}
- .media-upload-icon, .poll-icon, .emoji-icon {
- font-size: 1.85em;
- line-height: 1.1;
- flex: 1;
- padding: 0 0.1em;
- display: flex;
- align-items: center;
-
- &.selected, &:hover {
- // needs to be specific to override icon default color
- svg, i, label {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
- }
+ .visibility-notice.edit-warning {
+ > :first-child {
+ margin-top: 0;
}
- &.disabled {
- svg, i {
- cursor: not-allowed;
- color: $fallback--icon;
- color: var(--btnDisabledText, $fallback--icon);
-
- &:hover {
- color: $fallback--icon;
- color: var(--btnDisabledText, $fallback--icon);
- }
- }
+ > :last-child {
+ margin-bottom: 0;
}
}
@@ -456,16 +491,28 @@
justify-content: right;
}
+ .media-upload-icon,
+ .poll-icon,
+ .emoji-icon {
+ font-size: 1.85em;
+ line-height: 1.1;
+ flex: 1;
+ padding: 0 0.1em;
+ display: flex;
+ align-items: center;
+ }
+
.error {
text-align: center;
}
.media-upload-wrapper {
- margin-right: .2em;
- margin-bottom: .5em;
+ margin-right: 0.2em;
+ margin-bottom: 0.5em;
width: 18em;
- img, video {
+ img,
+ video {
object-fit: contain;
max-height: 10em;
}
@@ -505,7 +552,7 @@
line-height: 1.85;
}
- .form-post-body {
+ .input.form-post-body {
// TODO: make a resizable textarea component?
box-sizing: content-box; // needed for easier computation of dynamic size
overflow: hidden;
@@ -516,6 +563,7 @@
height: calc(var(--post-line-height) * 1em);
min-height: calc(var(--post-line-height) * 1em);
resize: none;
+ background: transparent;
&.scrollable-form {
overflow-y: auto;
@@ -534,23 +582,18 @@
margin: 0 0.5em;
&.error {
- color: $fallback--cRed;
- color: var(--cRed, $fallback--cRed);
+ color: var(--cRed);
}
}
- .btn[disabled] {
- cursor: not-allowed;
- }
-
@keyframes fade-in {
from { opacity: 0; }
- to { opacity: 0.6; }
+ to { opacity: 0.6; }
}
@keyframes fade-out {
from { opacity: 0.6; }
- to { opacity: 0; }
+ to { opacity: 0; }
}
.drop-indicator {
@@ -562,14 +605,10 @@
align-items: center;
justify-content: center;
opacity: 0.6;
- color: $fallback--text;
- color: var(--text, $fallback--text);
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
- border: 2px dashed $fallback--text;
- border: 2px dashed var(--text, $fallback--text);
+ color: var(--text);
+ background-color: var(--bg);
+ border-radius: var(--roundness);
+ border: 2px dashed var(--text);
}
}
</style>
diff --git a/src/components/post_status_modal/post_status_modal.js b/src/components/post_status_modal/post_status_modal.js
@@ -44,6 +44,10 @@ const PostStatusModal = {
methods: {
closeModal () {
this.$store.dispatch('closePostStatusModal')
+ },
+ resetAndClose () {
+ this.$store.dispatch('resetPostStatusModal')
+ this.$store.dispatch('closePostStatusModal')
}
}
}
diff --git a/src/components/post_status_modal/post_status_modal.vue b/src/components/post_status_modal/post_status_modal.vue
@@ -7,12 +7,14 @@
>
<div class="post-form-modal-panel panel">
<div class="panel-heading">
- {{ $t('post_status.new_status') }}
+ <h1 class="title">
+ {{ $t('post_status.new_status') }}
+ </h1>
</div>
<PostStatusForm
class="panel-body"
v-bind="params"
- @posted="closeModal"
+ @posted="resetAndClose"
/>
</div>
</Modal>
diff --git a/src/components/quick_filter_settings/quick_filter_settings.js b/src/components/quick_filter_settings/quick_filter_settings.js
@@ -0,0 +1,77 @@
+import Popover from '../popover/popover.vue'
+import { mapGetters } from 'vuex'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faFilter,
+ faFont,
+ faWrench
+)
+
+const QuickFilterSettings = {
+ props: {
+ conversation: Boolean
+ },
+ components: {
+ Popover
+ },
+ methods: {
+ setReplyVisibility (visibility) {
+ this.$store.dispatch('setOption', { name: 'replyVisibility', value: visibility })
+ this.$store.dispatch('queueFlushAll')
+ },
+ openTab (tab) {
+ this.$store.dispatch('openSettingsModalTab', tab)
+ }
+ },
+ computed: {
+ ...mapGetters(['mergedConfig']),
+ loggedIn () {
+ return !!this.$store.state.users.currentUser
+ },
+ replyVisibilitySelf: {
+ get () { return this.mergedConfig.replyVisibility === 'self' },
+ set () { this.setReplyVisibility('self') }
+ },
+ replyVisibilityFollowing: {
+ get () { return this.mergedConfig.replyVisibility === 'following' },
+ set () { this.setReplyVisibility('following') }
+ },
+ replyVisibilityAll: {
+ get () { return this.mergedConfig.replyVisibility === 'all' },
+ set () { this.setReplyVisibility('all') }
+ },
+ hideMedia: {
+ get () { return this.mergedConfig.hideAttachments || this.mergedConfig.hideAttachmentsInConv },
+ set () {
+ const value = !this.hideMedia
+ this.$store.dispatch('setOption', { name: 'hideAttachments', value })
+ this.$store.dispatch('setOption', { name: 'hideAttachmentsInConv', value })
+ }
+ },
+ hideMutedPosts: {
+ get () { return this.mergedConfig.hideFilteredStatuses },
+ set () {
+ const value = !this.hideMutedPosts
+ this.$store.dispatch('setOption', { name: 'hideFilteredStatuses', value })
+ }
+ },
+ muteBotStatuses: {
+ get () { return this.mergedConfig.muteBotStatuses },
+ set () {
+ const value = !this.muteBotStatuses
+ this.$store.dispatch('setOption', { name: 'muteBotStatuses', value })
+ }
+ },
+ muteSensitiveStatuses: {
+ get () { return this.mergedConfig.muteSensitiveStatuses },
+ set () {
+ const value = !this.muteSensitiveStatuses
+ this.$store.dispatch('setOption', { name: 'muteSensitiveStatuses', value })
+ }
+ }
+ }
+}
+
+export default QuickFilterSettings
diff --git a/src/components/quick_filter_settings/quick_filter_settings.vue b/src/components/quick_filter_settings/quick_filter_settings.vue
@@ -0,0 +1,125 @@
+<template>
+ <Popover
+ trigger="click"
+ class="QuickFilterSettings"
+ :bound-to="{ x: 'container' }"
+ :trigger-attrs="{ title: $t('timeline.quick_filter_settings') }"
+ >
+ <template #content>
+ <div
+ class="dropdown-menu"
+ role="menu"
+ >
+ <div
+ v-if="loggedIn"
+ role="group"
+ >
+ <button
+ v-if="!conversation"
+ class="menu-item dropdown-item"
+ :aria-checked="replyVisibilityAll"
+ role="menuitemradio"
+ @click="replyVisibilityAll = true"
+ >
+ <span
+ class="input menu-checkbox -radio"
+ :class="{ 'menu-checkbox-checked': replyVisibilityAll }"
+ :aria-hidden="true"
+ />{{ $t('settings.reply_visibility_all') }}
+ </button>
+ <button
+ v-if="!conversation"
+ class="menu-item dropdown-item"
+ :aria-checked="replyVisibilityFollowing"
+ role="menuitemradio"
+ @click="replyVisibilityFollowing = true"
+ >
+ <span
+ class="input menu-checkbox -radio"
+ :class="{ 'menu-checkbox-checked': replyVisibilityFollowing }"
+ :aria-hidden="true"
+ />{{ $t('settings.reply_visibility_following_short') }}
+ </button>
+ <button
+ v-if="!conversation"
+ class="menu-item dropdown-item"
+ :aria-checked="replyVisibilitySelf"
+ role="menuitemradio"
+ @click="replyVisibilitySelf = true"
+ >
+ <span
+ class="input menu-checkbox -radio"
+ :class="{ 'menu-checkbox-checked': replyVisibilitySelf }"
+ :aria-hidden="true"
+ />{{ $t('settings.reply_visibility_self_short') }}
+ </button>
+ <div
+ v-if="!conversation"
+ role="separator"
+ class="dropdown-divider"
+ />
+ </div>
+ <button
+ class="menu-item dropdown-item"
+ role="menuitemcheckbox"
+ :aria-checked="muteBotStatuses"
+ @click="muteBotStatuses = !muteBotStatuses"
+ >
+ <span
+ class="input menu-checkbox"
+ :class="{ 'menu-checkbox-checked': muteBotStatuses }"
+ :aria-hidden="true"
+ />{{ $t('settings.mute_bot_posts') }}
+ </button>
+ <button
+ class="menu-item dropdown-item"
+ role="menuitemcheckbox"
+ :aria-checked="muteSensitiveStatuses"
+ @click="muteSensitiveStatuses = !muteSensitiveStatuses"
+ >
+ <span
+ class="input menu-checkbox"
+ :class="{ 'menu-checkbox-checked': muteSensitiveStatuses }"
+ :aria-hidden="true"
+ />{{ $t('settings.mute_sensitive_posts') }}
+ </button>
+ <button
+ class="menu-item dropdown-item"
+ role="menuitemcheckbox"
+ :aria-checked="hideMedia"
+ @click="hideMedia = !hideMedia"
+ >
+ <span
+ class="input menu-checkbox"
+ :class="{ 'menu-checkbox-checked': hideMedia }"
+ :aria-hidden="true"
+ />{{ $t('settings.hide_media_previews') }}
+ </button>
+ <button
+ class="menu-item dropdown-item"
+ role="menuitemcheckbox"
+ :aria-checked="hideMutedPosts"
+ @click="hideMutedPosts = !hideMutedPosts"
+ >
+ <span
+ class="input menu-checkbox"
+ :class="{ 'menu-checkbox-checked': hideMutedPosts }"
+ :aria-hidden="true"
+ />{{ $t('settings.hide_all_muted_posts') }}
+ </button>
+ <button
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
+ @click="openTab('filtering')"
+ >
+ <FAIcon icon="font" />{{ $t('settings.word_filter_and_more') }}
+ </button>
+ </div>
+ </template>
+ <template #trigger>
+ <FAIcon icon="filter" />
+ </template>
+ </Popover>
+</template>
+
+<script src="./quick_filter_settings.js"></script>
diff --git a/src/components/quick_view_settings/quick_view_settings.js b/src/components/quick_view_settings/quick_view_settings.js
@@ -0,0 +1,75 @@
+import Popover from '../popover/popover.vue'
+import { mapGetters } from 'vuex'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faList, faFolderTree, faBars, faWrench } from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faList,
+ faFolderTree,
+ faBars,
+ faWrench
+)
+
+const QuickViewSettings = {
+ props: {
+ conversation: Boolean
+ },
+ components: {
+ Popover
+ },
+ methods: {
+ setConversationDisplay (visibility) {
+ this.$store.dispatch('setOption', { name: 'conversationDisplay', value: visibility })
+ },
+ openTab (tab) {
+ this.$store.dispatch('openSettingsModalTab', tab)
+ }
+ },
+ computed: {
+ ...mapGetters(['mergedConfig']),
+ loggedIn () {
+ return !!this.$store.state.users.currentUser
+ },
+ conversationDisplay: {
+ get () { return this.mergedConfig.conversationDisplay },
+ set (newVal) { this.setConversationDisplay(newVal) }
+ },
+ autoUpdate: {
+ get () { return this.mergedConfig.streaming },
+ set () {
+ const value = !this.autoUpdate
+ this.$store.dispatch('setOption', { name: 'streaming', value })
+ }
+ },
+ collapseWithSubjects: {
+ get () { return this.mergedConfig.collapseMessageWithSubject },
+ set () {
+ const value = !this.collapseWithSubjects
+ this.$store.dispatch('setOption', { name: 'collapseMessageWithSubject', value })
+ }
+ },
+ showUserAvatars: {
+ get () { return this.mergedConfig.mentionLinkShowAvatar },
+ set () {
+ const value = !this.showUserAvatars
+ this.$store.dispatch('setOption', { name: 'mentionLinkShowAvatar', value })
+ }
+ },
+ muteBotStatuses: {
+ get () { return this.mergedConfig.muteBotStatuses },
+ set () {
+ const value = !this.muteBotStatuses
+ this.$store.dispatch('setOption', { name: 'muteBotStatuses', value })
+ }
+ },
+ muteSensitiveStatuses: {
+ get () { return this.mergedConfig.muteSensitiveStatuses },
+ set () {
+ const value = !this.muteSensitiveStatuses
+ this.$store.dispatch('setOption', { name: 'muteSensitiveStatuses', value })
+ }
+ }
+ }
+}
+
+export default QuickViewSettings
diff --git a/src/components/quick_view_settings/quick_view_settings.vue b/src/components/quick_view_settings/quick_view_settings.vue
@@ -0,0 +1,102 @@
+<template>
+ <Popover
+ trigger="click"
+ class="QuickViewSettings"
+ :bound-to="{ x: 'container' }"
+ :trigger-attrs="{ title: $t('timeline.quick_view_settings') }"
+ >
+ <template #content>
+ <div
+ class="dropdown-menu"
+ role="menu"
+ >
+ <div role="group">
+ <button
+ class="menu-item dropdown-item"
+ :aria-checked="conversationDisplay === 'tree'"
+ role="menuitemradio"
+ @click="conversationDisplay = 'tree'"
+ >
+ <span
+ class="input menu-checkbox -radio"
+ :aria-hidden="true"
+ :class="{ 'menu-checkbox-checked': conversationDisplay === 'tree' }"
+ /><FAIcon
+ icon="folder-tree"
+ :aria-hidden="true"
+ /> {{ $t('settings.conversation_display_tree_quick') }}
+ </button>
+ <button
+ class="menu-item dropdown-item"
+ :aria-checked="conversationDisplay === 'linear'"
+ role="menuitemradio"
+ @click="conversationDisplay = 'linear'"
+ >
+ <span
+ class="input menu-checkbox -radio"
+ :class="{ 'menu-checkbox-checked': conversationDisplay === 'linear' }"
+ :aria-hidden="true"
+ /><FAIcon
+ icon="list"
+ :aria-hidden="true"
+ /> {{ $t('settings.conversation_display_linear_quick') }}
+ </button>
+ </div>
+ <div
+ role="separator"
+ class="dropdown-divider"
+ />
+ <button
+ class="menu-item dropdown-item"
+ role="menuitemcheckbox"
+ :aria-checked="showUserAvatars"
+ @click="showUserAvatars = !showUserAvatars"
+ >
+ <span
+ class="input menu-checkbox"
+ :class="{ 'menu-checkbox-checked': showUserAvatars }"
+ :aria-hidden="true"
+ />{{ $t('settings.mention_link_show_avatar_quick') }}
+ </button>
+ <button
+ v-if="!conversation"
+ class="menu-item dropdown-item"
+ role="menuitemcheckbox"
+ :aria-checked="autoUpdate"
+ @click="autoUpdate = !autoUpdate"
+ >
+ <span
+ class="input menu-checkbox"
+ :class="{ 'menu-checkbox-checked': autoUpdate }"
+ :aria-hidden="true"
+ />{{ $t('settings.auto_update') }}
+ </button>
+ <button
+ v-if="!conversation"
+ class="menu-item dropdown-item"
+ role="menuitemcheckbox"
+ :aria-checked="collapseWithSubjects"
+ @click="collapseWithSubjects = !collapseWithSubjects"
+ >
+ <span
+ class="input menu-checkbox"
+ :class="{ 'menu-checkbox-checked': collapseWithSubjects }"
+ :aria-hidden="true"
+ />{{ $t('settings.collapse_subject') }}
+ </button>
+ <button
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
+ @click="openTab('general')"
+ >
+ <FAIcon icon="wrench" />{{ $t('settings.more_settings') }}
+ </button>
+ </div>
+ </template>
+ <template #trigger>
+ <FAIcon icon="bars" />
+ </template>
+ </Popover>
+</template>
+
+<script src="./quick_view_settings.js"></script>
diff --git a/src/components/quotes_timeline/quotes_timeline.js b/src/components/quotes_timeline/quotes_timeline.js
@@ -0,0 +1,26 @@
+import Timeline from '../timeline/timeline.vue'
+
+const QuotesTimeline = {
+ created () {
+ this.$store.commit('clearTimeline', { timeline: 'quotes' })
+ this.$store.dispatch('startFetchingTimeline', { timeline: 'quotes', statusId: this.statusId })
+ },
+ components: {
+ Timeline
+ },
+ computed: {
+ statusId () { return this.$route.params.id },
+ timeline () { return this.$store.state.statuses.timelines.quotes }
+ },
+ watch: {
+ statusId () {
+ this.$store.commit('clearTimeline', { timeline: 'quotes' })
+ this.$store.dispatch('startFetchingTimeline', { timeline: 'quotes', statusId: this.statusId })
+ }
+ },
+ unmounted () {
+ this.$store.dispatch('stopFetchingTimeline', 'quotes')
+ }
+}
+
+export default QuotesTimeline
diff --git a/src/components/quotes_timeline/quotes_timeline.vue b/src/components/quotes_timeline/quotes_timeline.vue
@@ -0,0 +1,10 @@
+<template>
+ <Timeline
+ :title="$t('nav.quotes')"
+ :timeline="timeline"
+ :timeline-name="'quotes'"
+ :status-id="statusId"
+ />
+</template>
+
+<script src='./quotes_timeline.js'></script>
diff --git a/src/components/range_input/range_input.vue b/src/components/range_input/range_input.vue
@@ -4,6 +4,7 @@
:class="{ disabled: !present || disabled }"
>
<label
+ :id="name + '-label'"
:for="name"
class="label"
>
@@ -12,7 +13,8 @@
<input
v-if="typeof fallback !== 'undefined'"
:id="name + '-o'"
- class="opt"
+ :aria-labelledby="name + '-label'"
+ class="input -checkbox opt visible-for-screenreader-only"
type="checkbox"
:checked="present"
@change="$emit('update:modelValue', !present ? fallback : undefined)"
@@ -21,10 +23,11 @@
v-if="typeof fallback !== 'undefined'"
class="opt-l"
:for="name + '-o'"
+ :aria-hidden="true"
/>
<input
:id="name"
- class="input-number"
+ class="input input-number"
type="range"
:value="modelValue || fallback"
:disabled="!present || disabled"
@@ -34,9 +37,10 @@
@input="$emit('update:modelValue', $event.target.value)"
>
<input
- :id="name"
- class="input-number"
+ :id="name + '-numeric'"
+ class="input input-number"
type="number"
+ :aria-labelledby="name + '-label'"
:value="modelValue || fallback"
:disabled="!present || disabled"
:max="hardMax"
diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
@@ -1,68 +1,52 @@
import Popover from '../popover/popover.vue'
+import EmojiPicker from '../emoji_picker/emoji_picker.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
+import { faPlus, faTimes } from '@fortawesome/free-solid-svg-icons'
import { faSmileBeam } from '@fortawesome/free-regular-svg-icons'
-import { trim } from 'lodash'
-library.add(faSmileBeam)
+library.add(
+ faPlus,
+ faTimes,
+ faSmileBeam
+)
const ReactButton = {
props: ['status'],
data () {
return {
- filterWord: ''
+ filterWord: '',
+ expanded: false
}
},
components: {
- Popover
+ Popover,
+ EmojiPicker
},
methods: {
- addReaction (event, emoji, close) {
+ addReaction (event) {
+ const emoji = event.insertion
const existingReaction = this.status.emoji_reactions.find(r => r.name === emoji)
if (existingReaction && existingReaction.me) {
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
} else {
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
}
- close()
},
- focusInput () {
- this.$nextTick(() => {
- const input = this.$el.querySelector('input')
- if (input) input.focus()
- })
+ show () {
+ if (!this.expanded) {
+ this.$refs.picker.showPicker()
+ }
+ },
+ onShow () {
+ this.expanded = true
+ },
+ onClose () {
+ this.expanded = false
}
},
computed: {
- commonEmojis () {
- return [
- { displayText: 'thumbsup', replacement: '👍' },
- { displayText: 'angry', replacement: '😠' },
- { displayText: 'eyes', replacement: '👀' },
- { displayText: 'joy', replacement: '😂' },
- { displayText: 'fire', replacement: '🔥' }
- ]
- },
- emojis () {
- if (this.filterWord !== '') {
- const filterWordLowercase = trim(this.filterWord.toLowerCase())
- let orderedEmojiList = []
- for (const emoji of this.$store.state.instance.emoji) {
- if (emoji.replacement === this.filterWord) return [emoji]
-
- const indexOfFilterWord = emoji.displayText.toLowerCase().indexOf(filterWordLowercase)
- if (indexOfFilterWord > -1) {
- if (!Array.isArray(orderedEmojiList[indexOfFilterWord])) {
- orderedEmojiList[indexOfFilterWord] = []
- }
- orderedEmojiList[indexOfFilterWord].push(emoji)
- }
- }
- return orderedEmojiList.flat()
- }
- return this.$store.state.instance.emoji || []
- },
- mergedConfig () {
- return this.$store.getters.mergedConfig
+ hideCustomEmoji () {
+ return !this.$store.state.instance.pleromaCustomEmojiReactionsAvailable
}
}
}
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
@@ -1,63 +1,47 @@
<template>
- <Popover
- trigger="click"
- class="ReactButton"
- placement="top"
- :offset="{ y: 5 }"
- :bound-to="{ x: 'container' }"
- remove-padding
- @show="focusInput"
- >
- <template v-slot:content="{close}">
- <div class="reaction-picker-filter">
- <input
- v-model="filterWord"
- @input="$event.target.composing = false"
- size="1"
- :placeholder="$t('emoji.search_emoji')"
- >
- </div>
- <div class="reaction-picker">
- <span
- v-for="emoji in commonEmojis"
- :key="emoji.replacement"
- class="emoji-button"
- :title="emoji.displayText"
- @click="addReaction($event, emoji.replacement, close)"
- >
- {{ emoji.replacement }}
- </span>
- <div class="reaction-picker-divider" />
- <span
- v-for="(emoji, key) in emojis"
- :key="key"
- class="emoji-button"
- :title="emoji.displayText"
- @click="addReaction($event, emoji.replacement, close)"
- >
- {{ emoji.replacement }}
- </span>
- <div class="reaction-bottom-fader" />
- </div>
- </template>
- <template v-slot:trigger>
- <button
- class="button-unstyled popover-trigger"
- :title="$t('tool_tip.add_reaction')"
- >
+ <span class="ReactButton">
+ <EmojiPicker
+ ref="picker"
+ :enable-sticker-picker="false"
+ :hide-custom-emoji="hideCustomEmoji"
+ class="emoji-picker-panel"
+ @emoji="addReaction"
+ @show="onShow"
+ @close="onClose"
+ />
+ <span
+ class="button-unstyled popover-trigger"
+ role="button"
+ :tabindex="0"
+ :title="$t('tool_tip.add_reaction')"
+ @click.stop.prevent="show"
+ >
+ <FALayers>
<FAIcon
class="fa-scale-110 fa-old-padding"
:icon="['far', 'smile-beam']"
/>
- </button>
- </template>
- </Popover>
+ <FAIcon
+ v-show="!expanded"
+ class="focus-marker"
+ transform="shrink-6 up-9 right-17"
+ icon="plus"
+ />
+ <FAIcon
+ v-show="expanded"
+ class="focus-marker"
+ transform="shrink-6 up-9 right-17"
+ icon="times"
+ />
+ </FALayers>
+ </span>
+ </span>
</template>
-<script src="./react_button.js" ></script>
+<script src="./react_button.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
+@import "../../mixins";
.ReactButton {
.reaction-picker-filter {
@@ -73,7 +57,7 @@
height: 1px;
width: 100%;
margin: 0.5em;
- background-color: var(--border, $fallback--border);
+ background-color: var(--border);
}
.reaction-picker {
@@ -87,20 +71,19 @@
text-align: center;
align-content: flex-start;
user-select: none;
-
- mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
- linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
- linear-gradient(to top, white, white);
+ mask:
+ linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
+ linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
+ linear-gradient(to top, white, white);
transition: mask-size 150ms;
mask-size: 100% 20px, 100% 20px, auto;
/* Autoprefixed seem to ignore this one, and also syntax is different */
- -webkit-mask-composite: xor;
+ mask-composite: xor;
mask-composite: exclude;
.emoji-button {
cursor: pointer;
-
flex-basis: 20%;
line-height: 1.5;
align-content: center;
@@ -111,18 +94,20 @@
}
}
- /* override of popover internal stuff */
- .popover-trigger-button {
- width: auto;
- }
-
.popover-trigger {
padding: 10px;
margin: -10px;
- &:hover .svg-inline--fa {
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ @include unfocused-style {
+ .focus-marker {
+ visibility: hidden;
+ }
+ }
+
+ @include focused-style {
+ .focus-marker {
+ visibility: visible;
+ }
}
}
}
diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js
@@ -3,6 +3,7 @@ import { required, requiredIf, sameAs } from '@vuelidate/validators'
import { mapActions, mapState } from 'vuex'
import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue'
import localeService from '../../services/locale/locale.service.js'
+import { DAY } from 'src/services/date_utils/date_utils.js'
const registration = {
setup () { return { v$: useVuelidate() } },
@@ -13,8 +14,9 @@ const registration = {
username: '',
password: '',
confirm: '',
+ birthday: '',
reason: '',
- language: ''
+ language: ['']
},
captcha: {}
}),
@@ -32,6 +34,12 @@ const registration = {
required,
sameAs: sameAs(this.user.password)
},
+ birthday: {
+ required: requiredIf(() => this.birthdayRequired),
+ maxValue: value => {
+ return !this.birthdayRequired || new Date(value).getTime() <= this.birthdayMin.getTime()
+ }
+ },
reason: { required: requiredIf(() => this.accountApprovalRequired) },
language: {}
}
@@ -52,14 +60,36 @@ const registration = {
reasonPlaceholder () {
return this.replaceNewlines(this.$t('registration.reason_placeholder'))
},
+ birthdayMin () {
+ const minAge = this.birthdayMinAge
+ const today = new Date()
+ today.setUTCMilliseconds(0)
+ today.setUTCSeconds(0)
+ today.setUTCMinutes(0)
+ today.setUTCHours(0)
+ const minDate = new Date()
+ minDate.setTime(today.getTime() - minAge * DAY)
+ return minDate
+ },
+ birthdayMinAttr () {
+ return this.birthdayMin.toJSON().replace(/T.+$/, '')
+ },
+ birthdayMinFormatted () {
+ const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)
+ return this.user.birthday && new Date(Date.parse(this.birthdayMin)).toLocaleDateString(browserLocale, { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' })
+ },
...mapState({
registrationOpen: (state) => state.instance.registrationOpen,
signedIn: (state) => !!state.users.currentUser,
isPending: (state) => state.users.signUpPending,
serverValidationErrors: (state) => state.users.signUpErrors,
+ signUpNotice: (state) => state.users.signUpNotice,
+ hasSignUpNotice: (state) => !!state.users.signUpNotice.message,
termsOfService: (state) => state.instance.tos,
accountActivationRequired: (state) => state.instance.accountActivationRequired,
- accountApprovalRequired: (state) => state.instance.accountApprovalRequired
+ accountApprovalRequired: (state) => state.instance.accountApprovalRequired,
+ birthdayRequired: (state) => state.instance.birthdayRequired,
+ birthdayMinAge: (state) => state.instance.birthdayMinAge
})
},
methods: {
@@ -72,15 +102,19 @@ const registration = {
this.user.captcha_token = this.captcha.token
this.user.captcha_answer_data = this.captcha.answer_data
if (this.user.language) {
- this.user.language = localeService.internalToBackendLocale(this.user.language)
+ this.user.language = localeService.internalToBackendLocaleMulti(this.user.language.filter(k => k))
}
this.v$.$touch()
if (!this.v$.$invalid) {
try {
- await this.signUp(this.user)
- this.$router.push({ name: 'friends' })
+ const status = await this.signUp(this.user)
+ if (status === 'ok') {
+ this.$router.push({ name: 'friends' })
+ }
+ // If status is not 'ok' (i.e. it needs further actions to be done
+ // before you can login), display sign up notice, do not switch anywhere
} catch (error) {
console.warn('Registration failed: ', error)
this.setCaptcha()
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
@@ -1,9 +1,14 @@
<template>
<div class="settings panel panel-default">
<div class="panel-heading">
- {{ $t('registration.registration') }}
+ <h1 class="title">
+ {{ $t('registration.registration') }}
+ </h1>
</div>
- <div class="panel-body">
+ <div
+ v-if="!hasSignUpNotice"
+ class="panel-body"
+ >
<form
class="registration-form"
@submit.prevent="submit(user)"
@@ -22,7 +27,8 @@
id="sign-up-username"
v-model.trim="v$.user.username.$model"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
+ :aria-required="true"
:placeholder="$t('registration.username_placeholder')"
>
</div>
@@ -49,7 +55,8 @@
id="sign-up-fullname"
v-model.trim="v$.user.fullname.$model"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
+ :aria-required="true"
:placeholder="$t('registration.fullname_placeholder')"
>
</div>
@@ -71,13 +78,14 @@
<label
class="form--label"
for="email"
- >{{ $t('registration.email') }}</label>
+ >{{ accountActivationRequired ? $t('registration.email') : $t('registration.email_optional') }}</label>
<input
id="email"
v-model="v$.user.email.$model"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
type="email"
+ :aria-required="accountActivationRequired"
>
</div>
<div
@@ -95,12 +103,12 @@
<label
class="form--label"
for="bio"
- >{{ $t('registration.bio') }} ({{ $t('general.optional') }})</label>
+ >{{ $t('registration.bio_optional') }}</label>
<textarea
id="bio"
v-model="user.bio"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
:placeholder="bioPlaceholder"
/>
</div>
@@ -117,8 +125,9 @@
id="sign-up-password"
v-model="user.password"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
type="password"
+ :aria-required="true"
>
</div>
<div
@@ -144,8 +153,9 @@
id="sign-up-password-confirmation"
v-model="user.confirm"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
type="password"
+ :aria-required="true"
>
</div>
<div
@@ -153,10 +163,10 @@
class="form-error"
>
<ul>
- <li v-if="!v$.user.confirm.required">
+ <li v-if="v$.user.confirm.required.$invalid">
<span>{{ $t('registration.validations.password_confirmation_required') }}</span>
</li>
- <li v-if="!v$.user.confirm.sameAsPassword">
+ <li v-if="v$.user.confirm.sameAs.$invalid">
<span>{{ $t('registration.validations.password_confirmation_match') }}</span>
</li>
</ul>
@@ -164,6 +174,40 @@
<div
class="form-group"
+ :class="{ 'form-group--error': v$.user.birthday.$error }"
+ >
+ <label
+ class="form--label"
+ for="sign-up-birthday"
+ >
+ {{ birthdayRequired ? $t('registration.birthday') : $t('registration.birthday_optional') }}
+ </label>
+ <input
+ id="sign-up-birthday"
+ v-model="user.birthday"
+ :disabled="isPending"
+ class="input form-control"
+ type="date"
+ :max="birthdayRequired ? birthdayMinAttr : undefined"
+ :aria-required="birthdayRequired"
+ >
+ </div>
+ <div
+ v-if="v$.user.birthday.$dirty"
+ class="form-error"
+ >
+ <ul>
+ <li v-if="v$.user.birthday.required.$invalid">
+ <span>{{ $t('registration.validations.birthday_required') }}</span>
+ </li>
+ <li v-if="v$.user.birthday.maxValue.$invalid">
+ <span>{{ $tc('registration.validations.birthday_min_age', { date: birthdayMinFormatted }) }}</span>
+ </li>
+ </ul>
+ </div>
+
+ <div
+ class="form-group"
:class="{ 'form-group--error': v$.user.language.$error }"
>
<interface-language-switcher
@@ -171,6 +215,7 @@
:prompt-text="$t('registration.email_language')"
:language="v$.user.language.$model"
:set-language="val => v$.user.language.$model = val"
+ @click.stop.prevent
/>
</div>
@@ -186,7 +231,7 @@
id="reason"
v-model="user.reason"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
:placeholder="reasonPlaceholder"
/>
</div>
@@ -213,7 +258,7 @@
id="captcha-answer"
v-model="captcha.solution"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
type="text"
autocomplete="off"
autocorrect="off"
@@ -232,7 +277,7 @@
id="token"
v-model="token"
disabled="true"
- class="form-control"
+ class="input form-control"
type="text"
>
</div>
@@ -267,14 +312,16 @@
</div>
</form>
</div>
+ <div v-else>
+ <p class="registration-notice">
+ {{ signUpNotice.message }}
+ </p>
+ </div>
</div>
</template>
<script src="./registration.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-$validations-cRed: #f04124;
-
.registration-form {
display: flex;
flex-direction: column;
@@ -316,13 +363,12 @@ $validations-cRed: #f04124;
.form-group--error {
animation-name: shakeError;
- animation-duration: .6s;
+ animation-duration: 0.6s;
animation-timing-function: ease-in-out;
}
.form-group--error .form--label {
- color: $validations-cRed;
- color: var(--cRed, $validations-cRed);
+ color: var(--cRed);
}
.form-error {
@@ -345,7 +391,7 @@ $validations-cRed: #f04124;
}
form textarea {
- line-height:16px;
+ line-height: 16px;
resize: vertical;
}
@@ -364,6 +410,10 @@ $validations-cRed: #f04124;
}
}
+.registration-notice {
+ margin: 0.6em;
+}
+
@media all and (max-width: 800px) {
.registration-form .container {
flex-direction: column-reverse;
diff --git a/src/components/remote_follow/remote_follow.js b/src/components/remote_follow/remote_follow.js
@@ -1,5 +1,5 @@
export default {
- props: [ 'user' ],
+ props: ['user'],
computed: {
subscribeUrl () {
// eslint-disable-next-line no-undef
diff --git a/src/components/remote_user_resolver/remote_user_resolver.vue b/src/components/remote_user_resolver/remote_user_resolver.vue
@@ -1,7 +1,9 @@
<template>
<div class="panel panel-default">
<div class="panel-heading">
- {{ $t('remote_user_resolver.remote_user_resolver') }}
+ <h1 class="title">
+ {{ $t('remote_user_resolver.remote_user_resolver') }}
+ </h1>
</div>
<div class="panel-body">
<p>
@@ -15,6 +17,3 @@
</template>
<script src="./remote_user_resolver.js"></script>
-
-<style lang="scss">
-</style>
diff --git a/src/components/remove_follower_button/remove_follower_button.js b/src/components/remove_follower_button/remove_follower_button.js
@@ -0,0 +1,48 @@
+import ConfirmModal from '../confirm_modal/confirm_modal.vue'
+
+export default {
+ props: ['user', 'relationship'],
+ data () {
+ return {
+ inProgress: false,
+ showingConfirmRemoveFollower: false
+ }
+ },
+ components: {
+ ConfirmModal
+ },
+ computed: {
+ label () {
+ if (this.inProgress) {
+ return this.$t('user_card.follow_progress')
+ } else {
+ return this.$t('user_card.remove_follower')
+ }
+ },
+ shouldConfirmRemoveUserFromFollowers () {
+ return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
+ }
+ },
+ methods: {
+ showConfirmRemoveUserFromFollowers () {
+ this.showingConfirmRemoveFollower = true
+ },
+ hideConfirmRemoveUserFromFollowers () {
+ this.showingConfirmRemoveFollower = false
+ },
+ onClick () {
+ if (!this.shouldConfirmRemoveUserFromFollowers) {
+ this.doRemoveUserFromFollowers()
+ } else {
+ this.showConfirmRemoveUserFromFollowers()
+ }
+ },
+ doRemoveUserFromFollowers () {
+ this.inProgress = true
+ this.$store.dispatch('removeUserFromFollowers', this.relationship.id).then(() => {
+ this.inProgress = false
+ })
+ this.hideConfirmRemoveUserFromFollowers()
+ }
+ }
+}
diff --git a/src/components/remove_follower_button/remove_follower_button.vue b/src/components/remove_follower_button/remove_follower_button.vue
@@ -0,0 +1,35 @@
+<template>
+ <button
+ class="btn button-default follow-button"
+ :class="{ toggled: inProgress }"
+ :disabled="inProgress"
+ :title="$t('user_card.remove_follower')"
+ @click="onClick"
+ >
+ {{ label }}
+ <teleport to="#modal">
+ <confirm-modal
+ v-if="showingConfirmRemoveFollower"
+ :title="$t('user_card.remove_follower_confirm_title')"
+ :confirm-text="$t('user_card.remove_follower_confirm_accept_button')"
+ :cancel-text="$t('user_card.remove_follower_confirm_cancel_button')"
+ @accepted="doRemoveUserFromFollowers"
+ @cancelled="hideConfirmRemoveUserFromFollowers"
+ >
+ <i18n-t
+ scope="global"
+ keypath="user_card.remove_follower_confirm"
+ tag="span"
+ >
+ <template #user>
+ <span
+ v-text="user.screen_name_ui"
+ />
+ </template>
+ </i18n-t>
+ </confirm-modal>
+ </teleport>
+ </button>
+</template>
+
+<script src="./remove_follower_button.js"></script>
diff --git a/src/components/reply_button/reply_button.js b/src/components/reply_button/reply_button.js
@@ -1,7 +1,15 @@
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faReply } from '@fortawesome/free-solid-svg-icons'
+import {
+ faReply,
+ faPlus,
+ faTimes
+} from '@fortawesome/free-solid-svg-icons'
-library.add(faReply)
+library.add(
+ faReply,
+ faPlus,
+ faTimes
+)
const ReplyButton = {
name: 'ReplyButton',
@@ -9,6 +17,9 @@ const ReplyButton = {
computed: {
loggedIn () {
return !!this.$store.state.users.currentUser
+ },
+ remoteInteractionLink () {
+ return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
}
}
}
diff --git a/src/components/reply_button/reply_button.vue b/src/components/reply_button/reply_button.vue
@@ -7,18 +7,46 @@
:title="$t('tool_tip.reply')"
@click.prevent="$emit('toggle')"
>
- <FAIcon
- class="fa-scale-110 fa-old-padding"
- icon="reply"
- />
+ <FALayers class="fa-old-padding-layer">
+ <FAIcon
+ class="fa-scale-110"
+ icon="reply"
+ />
+ <FAIcon
+ v-if="!replying"
+ class="focus-marker"
+ transform="shrink-6 up-8 right-11"
+ icon="plus"
+ />
+ <FAIcon
+ v-else
+ class="focus-marker"
+ transform="shrink-6 up-8 right-11"
+ icon="times"
+ />
+ </FALayers>
</button>
- <span v-else>
- <FAIcon
- icon="reply"
- class="fa-scale-110 fa-old-padding"
- :title="$t('tool_tip.reply')"
- />
- </span>
+ <a
+ v-else
+ class="button-unstyled interactive"
+ target="_blank"
+ role="button"
+ :href="remoteInteractionLink"
+ :title="$t('tool_tip.reply')"
+ >
+ <FALayers class="fa-old-padding-layer">
+ <FAIcon
+ class="fa-scale-110"
+ icon="reply"
+ />
+ <FAIcon
+ v-if="!replying"
+ class="focus-marker"
+ transform="shrink-6 up-8 right-16"
+ icon="plus"
+ />
+ </FALayers>
+ </a>
<span
v-if="status.replies_count > 0"
class="action-counter"
@@ -31,7 +59,7 @@
<script src="./reply_button.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
+@import "../../mixins";
.ReplyButton {
display: flex;
@@ -49,10 +77,20 @@
.interactive {
&:hover .svg-inline--fa,
&.-active .svg-inline--fa {
- color: $fallback--cBlue;
- color: var(--cBlue, $fallback--cBlue);
+ color: var(--cBlue);
}
- }
+ @include unfocused-style {
+ .focus-marker {
+ visibility: hidden;
+ }
+ }
+
+ @include focused-style {
+ .focus-marker {
+ visibility: visible;
+ }
+ }
+ }
}
</style>
diff --git a/src/components/report/report.js b/src/components/report/report.js
@@ -0,0 +1,36 @@
+import Select from '../select/select.vue'
+import StatusContent from '../status_content/status_content.vue'
+import Timeago from '../timeago/timeago.vue'
+import RichContent from 'src/components/rich_content/rich_content.jsx'
+import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
+
+const Report = {
+ props: [
+ 'reportId'
+ ],
+ components: {
+ Select,
+ StatusContent,
+ Timeago,
+ RichContent
+ },
+ computed: {
+ report () {
+ return this.$store.state.reports.reports[this.reportId] || {}
+ },
+ state: {
+ get: function () { return this.report.state },
+ set: function (val) { this.setReportState(val) }
+ }
+ },
+ methods: {
+ generateUserProfileLink (user) {
+ return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
+ },
+ setReportState (state) {
+ return this.$store.dispatch('setReportState', { id: this.report.id, state })
+ }
+ }
+}
+
+export default Report
diff --git a/src/components/report/report.scss b/src/components/report/report.scss
@@ -0,0 +1,37 @@
+.Report {
+ .report-content {
+ margin: 0.5em 0 1em;
+ }
+
+ .report-state {
+ margin: 0.5em 0 1em;
+ }
+
+ .reported-status {
+ border: 1px solid var(--border);
+ border-radius: var(--roundness);
+ display: block;
+ padding: 0.5em;
+ margin: 0.5em 0;
+
+ .status-content {
+ pointer-events: none;
+ }
+
+ .reported-status-heading {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ margin-bottom: 0.2em;
+ }
+
+ .reported-status-name {
+ font-weight: bold;
+ }
+ }
+
+ .note {
+ width: 100%;
+ margin-bottom: 0.5em;
+ }
+}
diff --git a/src/components/report/report.vue b/src/components/report/report.vue
@@ -0,0 +1,74 @@
+<template>
+ <div class="Report">
+ <div class="reported-user">
+ <span>{{ $t('report.reported_user') }}</span>
+ <router-link :to="generateUserProfileLink(report.acct)">
+ @{{ report.acct.screen_name }}
+ </router-link>
+ </div>
+ <div class="reporter">
+ <span>{{ $t('report.reporter') }}</span>
+ <router-link :to="generateUserProfileLink(report.actor)">
+ @{{ report.actor.screen_name }}
+ </router-link>
+ </div>
+ <div class="report-state">
+ <span>{{ $t('report.state') }}</span>
+ <Select
+ :id="report-state"
+ v-model="state"
+ class="input form-control"
+ >
+ <option
+ v-for="state in ['open', 'closed', 'resolved']"
+ :key="state"
+ :value="state"
+ >
+ {{ $t('report.state_' + state) }}
+ </option>
+ </Select>
+ </div>
+ <RichContent
+ class="report-content"
+ :html="report.content"
+ :emoji="[]"
+ />
+ <div v-if="report.statuses.length">
+ <small>{{ $t('report.reported_statuses') }}</small>
+ <router-link
+ v-for="status in report.statuses"
+ :key="status.id"
+ :to="{ name: 'conversation', params: { id: status.id } }"
+ class="reported-status"
+ >
+ <div class="reported-status-heading">
+ <span class="reported-status-name">{{ status.user.name }}</span>
+ <Timeago
+ :time="status.created_at"
+ :auto-update="240"
+ class="faint"
+ />
+ </div>
+ <status-content :status="status" />
+ </router-link>
+ </div>
+ <div v-if="report.notes.length">
+ <small>{{ $t('report.notes') }}</small>
+ <div
+ v-for="note in report.notes"
+ :key="note.id"
+ class="note"
+ >
+ <span>{{ note.content }}</span>
+ <Timeago
+ :time="note.created_at"
+ :auto-update="240"
+ class="faint"
+ />
+ </div>
+ </div>
+ </div>
+</template>
+
+<script src="./report.js"></script>
+<style src="./report.scss" lang="scss"></style>
diff --git a/src/components/retweet_button/retweet_button.js b/src/components/retweet_button/retweet_button.js
@@ -1,17 +1,39 @@
+import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faRetweet } from '@fortawesome/free-solid-svg-icons'
+import {
+ faRetweet,
+ faPlus,
+ faMinus,
+ faCheck
+} from '@fortawesome/free-solid-svg-icons'
-library.add(faRetweet)
+library.add(
+ faRetweet,
+ faPlus,
+ faMinus,
+ faCheck
+)
const RetweetButton = {
props: ['status', 'loggedIn', 'visibility'],
+ components: {
+ ConfirmModal
+ },
data () {
return {
- animated: false
+ animated: false,
+ showingConfirmDialog: false
}
},
methods: {
retweet () {
+ if (!this.status.repeated && this.shouldConfirmRepeat) {
+ this.showConfirmDialog()
+ } else {
+ this.doRetweet()
+ }
+ },
+ doRetweet () {
if (!this.status.repeated) {
this.$store.dispatch('retweet', { id: this.status.id })
} else {
@@ -21,11 +43,24 @@ const RetweetButton = {
setTimeout(() => {
this.animated = false
}, 500)
+ this.hideConfirmDialog()
+ },
+ showConfirmDialog () {
+ this.showingConfirmDialog = true
+ },
+ hideConfirmDialog () {
+ this.showingConfirmDialog = false
}
},
computed: {
mergedConfig () {
return this.$store.getters.mergedConfig
+ },
+ remoteInteractionLink () {
+ return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
+ },
+ shouldConfirmRepeat () {
+ return this.mergedConfig.modalOnRepeat
}
}
}
diff --git a/src/components/retweet_button/retweet_button.vue b/src/components/retweet_button/retweet_button.vue
@@ -7,11 +7,31 @@
:title="$t('tool_tip.repeat')"
@click.prevent="retweet()"
>
- <FAIcon
- class="fa-scale-110 fa-old-padding"
- icon="retweet"
- :spin="animated"
- />
+ <FALayers class="fa-old-padding-layer">
+ <FAIcon
+ class="fa-scale-110"
+ icon="retweet"
+ :spin="animated"
+ />
+ <FAIcon
+ v-if="status.repeated"
+ class="active-marker"
+ transform="shrink-6 up-9 right-12"
+ icon="check"
+ />
+ <FAIcon
+ v-if="!status.repeated"
+ class="focus-marker"
+ transform="shrink-6 up-9 right-12"
+ icon="plus"
+ />
+ <FAIcon
+ v-else
+ class="focus-marker"
+ transform="shrink-6 up-9 right-12"
+ icon="minus"
+ />
+ </FALayers>
</button>
<span v-else-if="loggedIn">
<FAIcon
@@ -20,26 +40,51 @@
:title="$t('timeline.no_retweet_hint')"
/>
</span>
- <span v-else>
- <FAIcon
- class="fa-scale-110 fa-old-padding"
- icon="retweet"
- :title="$t('tool_tip.repeat')"
- />
- </span>
+ <a
+ v-else
+ class="button-unstyled interactive"
+ target="_blank"
+ role="button"
+ :title="$t('tool_tip.repeat')"
+ :href="remoteInteractionLink"
+ >
+ <FALayers class="fa-old-padding-layer">
+ <FAIcon
+ class="fa-scale-110"
+ icon="retweet"
+ />
+ <FAIcon
+ class="focus-marker"
+ transform="shrink-6 up-9 right-12"
+ icon="plus"
+ />
+ </FALayers>
+ </a>
<span
v-if="!mergedConfig.hidePostStats && status.repeat_num > 0"
class="no-event"
>
{{ status.repeat_num }}
</span>
+ <teleport to="#modal">
+ <confirm-modal
+ v-if="showingConfirmDialog"
+ :title="$t('status.repeat_confirm_title')"
+ :confirm-text="$t('status.repeat_confirm_accept_button')"
+ :cancel-text="$t('status.repeat_confirm_cancel_button')"
+ @accepted="doRetweet"
+ @cancelled="hideConfirmDialog"
+ >
+ {{ $t('status.repeat_confirm') }}
+ </confirm-modal>
+ </teleport>
</div>
</template>
-<script src="./retweet_button.js" ></script>
+<script src="./retweet_button.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
+@import "../../mixins";
.RetweetButton {
display: flex;
@@ -61,8 +106,27 @@
&:hover .svg-inline--fa,
&.-repeated .svg-inline--fa {
- color: $fallback--cGreen;
- color: var(--cGreen, $fallback--cGreen);
+ color: var(--cGreen);
+ }
+
+ @include unfocused-style {
+ .focus-marker {
+ visibility: hidden;
+ }
+
+ .active-marker {
+ visibility: visible;
+ }
+ }
+
+ @include focused-style {
+ .focus-marker {
+ visibility: visible;
+ }
+
+ .active-marker {
+ visibility: hidden;
+ }
}
}
}
diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx
@@ -8,6 +8,27 @@ import HashtagLink from 'src/components/hashtag_link/hashtag_link.vue'
import './rich_content.scss'
+const MAYBE_LINE_BREAKING_ELEMENTS = [
+ 'blockquote',
+ 'br',
+ 'hr',
+ 'ul',
+ 'ol',
+ 'li',
+ 'p',
+ 'table',
+ 'tbody',
+ 'td',
+ 'th',
+ 'thead',
+ 'tr',
+ 'h1',
+ 'h2',
+ 'h3',
+ 'h4',
+ 'h5'
+]
+
/**
* RichContent, The Über-powered component for rendering Post HTML.
*
@@ -58,6 +79,12 @@ export default {
required: false,
type: Boolean,
default: false
+ },
+ // Faint style (for notifs)
+ faint: {
+ required: false,
+ type: Boolean,
+ default: false
}
},
// NEVER EVER TOUCH DATA INSIDE RENDER
@@ -149,7 +176,10 @@ export default {
// Handle tag nodes
if (Array.isArray(item)) {
const [opener, children, closer] = item
- const Tag = getTagName(opener)
+ let Tag = getTagName(opener)
+ if (Tag.toLowerCase() === 'script') Tag = 'js-exploit'
+ if (Tag.toLowerCase() === 'style') Tag = 'css-exploit'
+ const fullAttrs = getAttrs(opener, () => true)
const attrs = getAttrs(opener)
const previouslyMentions = currentMentions !== null
/* During grouping of mentions we trim all the empty text elements
@@ -163,25 +193,22 @@ export default {
!(children && typeof children[0] === 'string' && children[0].match(/^\s/))
? lastSpacing
: ''
- switch (Tag) {
- case 'br':
+ if (MAYBE_LINE_BREAKING_ELEMENTS.includes(Tag)) {
+ // all the elements that can cause a line change
+ currentMentions = null
+ } else if (Tag === 'img') { // replace images with StillImage
+ return ['', [mentionsLinePadding, renderImage(opener)], '']
+ } else if (Tag === 'a' && this.handleLinks) { // replace mentions with MentionLink
+ if (fullAttrs.class && fullAttrs.class.includes('mention')) {
+ // Handling mentions here
+ return renderMention(attrs, children)
+ } else {
currentMentions = null
- break
- case 'img': // replace images with StillImage
- return ['', [mentionsLinePadding, renderImage(opener)], '']
- case 'a': // replace mentions with MentionLink
- if (!this.handleLinks) break
- if (attrs['class'] && attrs['class'].includes('mention')) {
- // Handling mentions here
- return renderMention(attrs, children)
- } else {
- currentMentions = null
- break
- }
- case 'span':
- if (this.handleLinks && attrs['class'] && attrs['class'].includes('h-card')) {
- return ['', children.map(processItem), '']
- }
+ }
+ } else if (Tag === 'span') {
+ if (this.handleLinks && fullAttrs.class && fullAttrs.class.includes('h-card')) {
+ return ['', children.map(processItem), '']
+ }
}
if (children !== undefined) {
@@ -213,13 +240,14 @@ export default {
const [opener, children] = item
const Tag = opener === '' ? '' : getTagName(opener)
switch (Tag) {
- case 'a': // replace mentions with MentionLink
+ case 'a': { // replace mentions with MentionLink
if (!this.handleLinks) break
- const attrs = getAttrs(opener)
+ const fullAttrs = getAttrs(opener, () => true)
+ const attrs = getAttrs(opener, () => true)
// should only be this
if (
- (attrs['class'] && attrs['class'].includes('hashtag')) || // Pleroma style
- (attrs['rel'] === 'tag') // Mastodon style
+ (fullAttrs.class && fullAttrs.class.includes('hashtag')) || // Pleroma style
+ (fullAttrs.rel === 'tag') // Mastodon style
) {
return renderHashtag(attrs, children, encounteredTextReverse)
} else {
@@ -230,6 +258,7 @@ export default {
{ newChildren }
</a>
}
+ }
case '':
return [...children].reverse().map(processItemReverse).reverse()
}
@@ -254,7 +283,7 @@ export default {
// DO NOT USE SLOTS they cause a re-render feedback loop here.
// slots updated -> rerender -> emit -> update up the tree -> rerender -> ...
// at least until vue3?
- const result = <span class="RichContent">
+ const result = <span class={['RichContent', this.faint ? '-faint' : '']}>
{ pass2 }
</span>
diff --git a/src/components/rich_content/rich_content.scss b/src/components/rich_content/rich_content.scss
@@ -1,7 +1,20 @@
.RichContent {
+ font-family: var(--font);
+
+ &.-faint {
+ /* stylelint-disable declaration-no-important */
+ --text: var(--textFaint) !important;
+ --link: var(--linkFaint) !important;
+ --funtextGreentext: var(--funtextGreentextFaint) !important;
+ --funtextCyantext: var(--funtextCyantextFaint) !important;
+ /* stylelint-enable declaration-no-important */
+ }
+
blockquote {
- margin: 0.2em 0 0.2em 2em;
+ margin: 0.2em 0 0.2em 0.2em;
font-style: italic;
+ border-left: 0.2em solid var(--textFaint);
+ padding-left: 1em;
}
pre {
@@ -13,15 +26,15 @@
kbd,
var,
pre {
- font-family: var(--postCodeFont, monospace);
+ font-family: var(--monoFont);
}
p {
- margin: 0 0 1em 0;
+ margin: 0 0 1em;
}
p:last-child {
- margin: 0 0 0 0;
+ margin: 0;
}
h1 {
@@ -61,4 +74,17 @@
vertical-align: middle;
object-fit: contain;
}
+
+ .greentext {
+ color: var(--funtextGreentext);
+ }
+
+ .cyantext {
+ color: var(--funtextCyantext);
+ }
+}
+
+a .RichContent {
+ /* stylelint-disable-next-line declaration-no-important */
+ color: var(--link) !important;
}
diff --git a/src/components/rich_content/rich_content.style.js b/src/components/rich_content/rich_content.style.js
@@ -0,0 +1,19 @@
+export default {
+ name: 'RichContent',
+ selector: '.RichContent',
+ notEditable: true,
+ validInnerComponents: [
+ 'Text',
+ 'FunText',
+ 'Link'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ '--font': 'generic | inherit',
+ '--monoFont': 'generic | monospace',
+ textNoCssColor: 'yes'
+ }
+ }
+ ]
+}
diff --git a/src/components/root.style.js b/src/components/root.style.js
@@ -0,0 +1,50 @@
+export default {
+ name: 'Root',
+ selector: ':root',
+ notEditable: true,
+ validInnerComponents: [
+ 'Underlay',
+ 'Modals',
+ 'Popover',
+ 'TopBar',
+ 'Scrollbar',
+ 'ScrollbarElement',
+ 'MobileDrawer',
+ 'Alert',
+ 'Button' // mobile post button
+ ],
+ validInnerComponentsLite: [
+ 'Underlay',
+ 'Scrollbar',
+ 'ScrollbarElement'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ // These are here just to establish order,
+ // themes should override those
+ '--bg': 'color | #121a24',
+ '--fg': 'color | #182230',
+ '--text': 'color | #b9b9ba',
+ '--link': 'color | #d8a070',
+ '--accent': 'color | #d8a070',
+ '--cRed': 'color | #FF0000',
+ '--cBlue': 'color | #0095ff',
+ '--cGreen': 'color | #0fa00f',
+ '--cOrange': 'color | #ffa500',
+
+ // Fonts
+ '--font': 'generic | sans-serif',
+ '--monoFont': 'generic | monospace',
+
+ // Fallback no-background-image color
+ // (also useful in some other places like scrollbars)
+ '--wallpaper': 'color | --bg, -2',
+
+ // Selection colors
+ '--selectionBackground': 'color | --accent',
+ '--selectionText': 'color | $textColor(--accent --text no-preserve)'
+ }
+ }
+ ]
+}
diff --git a/src/components/roundness_input/roundness_input.vue b/src/components/roundness_input/roundness_input.vue
@@ -0,0 +1,51 @@
+<template>
+ <div
+ class="roundness-control style-control"
+ :class="{ disabled: !present || disabled }"
+ >
+ <label
+ :for="name"
+ class="label"
+ :class="{ faint: !present || disabled }"
+ >
+ {{ label }}
+ </label>
+ <Checkbox
+ v-if="typeof fallback !== 'undefined'"
+ :model-value="present"
+ :disabled="disabled"
+ class="opt"
+ @update:modelValue="$emit('update:modelValue', !present ? fallback : undefined)"
+ />
+ <input
+ :id="name"
+ class="input input-number"
+ type="number"
+ :value="modelValue || fallback"
+ :disabled="!present || disabled"
+ :class="{ disabled: !present || disabled }"
+ max="999"
+ min="0"
+ step="1"
+ @input="$emit('update:modelValue', $event.target.value)"
+ >
+ </div>
+</template>
+
+<script>
+import Checkbox from '../checkbox/checkbox.vue'
+export default {
+ components: {
+ Checkbox
+ },
+ props: [
+ 'name', 'label', 'modelValue', 'fallback', 'disabled'
+ ],
+ emits: ['update:modelValue'],
+ computed: {
+ present () {
+ return typeof this.modelValue !== 'undefined'
+ }
+ }
+}
+</script>
diff --git a/src/components/scope_selector/scope_selector.js b/src/components/scope_selector/scope_selector.js
@@ -44,10 +44,10 @@ const ScopeSelector = {
},
css () {
return {
- public: { selected: this.currentScope === 'public' },
- unlisted: { selected: this.currentScope === 'unlisted' },
- private: { selected: this.currentScope === 'private' },
- direct: { selected: this.currentScope === 'direct' }
+ public: { toggled: this.currentScope === 'public' },
+ unlisted: { toggled: this.currentScope === 'unlisted' },
+ private: { toggled: this.currentScope === 'private' },
+ direct: { toggled: this.currentScope === 'direct' }
}
}
},
diff --git a/src/components/scope_selector/scope_selector.vue b/src/components/scope_selector/scope_selector.vue
@@ -64,21 +64,13 @@
<script src="./scope_selector.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.ScopeSelector {
-
.scope {
display: inline-block;
cursor: pointer;
min-width: 1.3em;
min-height: 1.3em;
text-align: center;
-
- &.selected svg {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
- }
}
}
</style>
diff --git a/src/components/screen_reader_notice/screen_reader_notice.js b/src/components/screen_reader_notice/screen_reader_notice.js
@@ -0,0 +1,21 @@
+const ScreenReaderNotice = {
+ props: {
+ ariaLive: {
+ type: String,
+ default: 'assertive'
+ }
+ },
+ data () {
+ return {
+ currentText: ''
+ }
+ },
+ methods: {
+ announce (text) {
+ this.currentText = text
+ setTimeout(() => { this.currentText = '' }, 1000)
+ }
+ }
+}
+
+export default ScreenReaderNotice
diff --git a/src/components/screen_reader_notice/screen_reader_notice.vue b/src/components/screen_reader_notice/screen_reader_notice.vue
@@ -0,0 +1,10 @@
+<template>
+ <div
+ class="visible-for-screenreader-only"
+ :aria-live="ariaLive"
+ >
+ {{ currentText }}
+ </div>
+</template>
+
+<script src="./screen_reader_notice.js"></script>
diff --git a/src/components/scrollbar.style.js b/src/components/scrollbar.style.js
@@ -0,0 +1,12 @@
+export default {
+ name: 'Scrollbar',
+ selector: ['::-webkit-scrollbar-button', '::-webkit-scrollbar-thumb', '::-webkit-resizer'],
+ notEditable: true, // for now
+ defaultRules: [
+ {
+ directives: {
+ background: '--wallpaper'
+ }
+ }
+ ]
+}
diff --git a/src/components/scrollbar_element.style.js b/src/components/scrollbar_element.style.js
@@ -0,0 +1,102 @@
+const border = (top, shadow) => ({
+ x: 0,
+ y: top ? 1 : -1,
+ blur: 0,
+ spread: 0,
+ color: shadow ? '#000000' : '#FFFFFF',
+ alpha: 0.2,
+ inset: true
+})
+
+const buttonInsetFakeBorders = [border(true, false), border(false, true)]
+const inputInsetFakeBorders = [border(true, true), border(false, false)]
+const buttonOuterShadow = {
+ x: 0,
+ y: 0,
+ blur: 2,
+ spread: 0,
+ color: '#000000',
+ alpha: 1
+}
+
+const hoverGlow = {
+ x: 0,
+ y: 0,
+ blur: 4,
+ spread: 0,
+ color: '--text',
+ alpha: 1
+}
+
+export default {
+ name: 'ScrollbarElement',
+ selector: '::-webkit-scrollbar-button',
+ notEditable: true, // for now
+ states: {
+ pressed: ':active',
+ hover: ':hover:not(:disabled)',
+ disabled: ':disabled'
+ },
+ validInnerComponents: [
+ 'Text'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--fg',
+ shadow: [buttonOuterShadow, ...buttonInsetFakeBorders],
+ roundness: 3
+ }
+ },
+ {
+ state: ['hover'],
+ directives: {
+ shadow: [hoverGlow, ...buttonInsetFakeBorders]
+ }
+ },
+ {
+ state: ['pressed'],
+ directives: {
+ shadow: [buttonOuterShadow, ...inputInsetFakeBorders]
+ }
+ },
+ {
+ state: ['hover', 'pressed'],
+ directives: {
+ shadow: [hoverGlow, ...inputInsetFakeBorders]
+ }
+ },
+ {
+ state: ['toggled'],
+ directives: {
+ background: '--accent,-24.2',
+ shadow: [buttonOuterShadow, ...inputInsetFakeBorders]
+ }
+ },
+ {
+ state: ['toggled', 'hover'],
+ directives: {
+ background: '--accent,-24.2',
+ shadow: [hoverGlow, ...inputInsetFakeBorders]
+ }
+ },
+ {
+ state: ['disabled'],
+ directives: {
+ background: '$blend(--inheritedBackground 0.25 --parent)',
+ shadow: [...buttonInsetFakeBorders]
+ }
+ },
+ {
+ component: 'Text',
+ parent: {
+ component: 'Button',
+ state: ['disabled']
+ },
+ directives: {
+ textOpacity: 0.25,
+ textOpacityMode: 'blend'
+ }
+ }
+ ]
+}
diff --git a/src/components/search/search.js b/src/components/search/search.js
@@ -8,6 +8,7 @@ import {
faCircleNotch,
faSearch
} from '@fortawesome/free-solid-svg-icons'
+import { uniqBy } from 'lodash'
library.add(
faCircleNotch,
@@ -32,7 +33,11 @@ const Search = {
userIds: [],
statuses: [],
hashtags: [],
- currenResultTab: 'statuses'
+ currenResultTab: 'statuses',
+
+ statusesOffset: 0,
+ lastStatusFetchCount: 0,
+ lastQuery: ''
}
},
computed: {
@@ -61,26 +66,42 @@ const Search = {
this.$router.push({ name: 'search', query: { query } })
this.$refs.searchInput.focus()
},
- search (query) {
+ search (query, searchType = null) {
if (!query) {
this.loading = false
return
}
this.loading = true
- this.userIds = []
- this.statuses = []
- this.hashtags = []
this.$refs.searchInput.blur()
+ if (this.lastQuery !== query) {
+ this.userIds = []
+ this.hashtags = []
+ this.statuses = []
+
+ this.statusesOffset = 0
+ this.lastStatusFetchCount = 0
+ }
- this.$store.dispatch('search', { q: query, resolve: true })
+ this.$store.dispatch('search', { q: query, resolve: true, offset: this.statusesOffset, type: searchType })
.then(data => {
this.loading = false
- this.userIds = map(data.accounts, 'id')
- this.statuses = data.statuses
- this.hashtags = data.hashtags
+
+ const oldLength = this.statuses.length
+
+ // Always append to old results. If new results are empty, this doesn't change anything
+ this.userIds = this.userIds.concat(map(data.accounts, 'id'))
+ this.statuses = uniqBy(this.statuses.concat(data.statuses), 'id')
+ this.hashtags = this.hashtags.concat(data.hashtags)
+
this.currenResultTab = this.getActiveTab()
this.loaded = true
+
+ // Offset from whatever we already have
+ this.statusesOffset = this.statuses.length
+ // Because the amount of new statuses can actually be zero, compare to old lenght instead
+ this.lastStatusFetchCount = this.statuses.length - oldLength
+ this.lastQuery = query
})
},
resultCount (tabName) {
diff --git a/src/components/search/search.vue b/src/components/search/search.vue
@@ -1,15 +1,15 @@
<template>
- <div class="panel panel-default">
+ <div class="Search panel panel-default">
<div class="panel-heading">
- <div class="title">
+ <h1 class="title">
{{ $t('nav.search') }}
- </div>
+ </h1>
</div>
- <div class="search-input-container">
+ <div class="panel-body search-input-container">
<input
ref="searchInput"
v-model="searchTerm"
- class="search-input"
+ class="input search-input"
:placeholder="$t('nav.search')"
@keyup.enter="newQuery(searchTerm)"
>
@@ -22,8 +22,8 @@
</button>
</div>
<div
- v-if="loading"
- class="text-center loading-icon"
+ v-if="loading && statusesOffset == 0"
+ class="panel-body text-center loading-icon"
>
<FAIcon
icon="circle-notch"
@@ -55,12 +55,6 @@
</div>
<div class="panel-body">
<div v-if="currenResultTab === 'statuses'">
- <div
- v-if="visibleStatuses.length === 0 && !loading && loaded"
- class="search-result-heading"
- >
- <h4>{{ $t('search.no_results') }}</h4>
- </div>
<Status
v-for="status in visibleStatuses"
:key="status.id"
@@ -71,6 +65,33 @@
:statusoid="status"
:no-heading="false"
/>
+ <button
+ v-if="!loading && loaded && lastStatusFetchCount > 0"
+ class="more-statuses-button button-unstyled -link"
+ @click.prevent="search(searchTerm, 'statuses')"
+ >
+ <div class="new-status-notification text-center">
+ {{ $t('search.load_more') }}
+ </div>
+ </button>
+ <div
+ v-else-if="loading && statusesOffset > 0"
+ class="text-center loading-icon"
+ >
+ <FAIcon
+ icon="circle-notch"
+ spin
+ size="lg"
+ />
+ </div>
+ <div
+ v-if="(visibleStatuses.length === 0 || lastStatusFetchCount === 0) && !loading && loaded"
+ class="search-result-heading"
+ >
+ <h4>
+ {{ visibleStatuses.length === 0 ? $t('search.no_results') : $t('search.no_more_results') }}
+ </h4>
+ </div>
</div>
<div v-else-if="currenResultTab === 'people'">
<div
@@ -127,11 +148,8 @@
<script src="./search.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.search-result-heading {
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+ color: var(--faint);
padding: 0.75rem;
text-align: center;
}
@@ -150,17 +168,7 @@
.search-result {
box-sizing: border-box;
border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
-}
-
-.search-result-footer {
- border-width: 1px 0 0 0;
- border-style: solid;
- border-color: var(--border, $fallback--border);
- padding: 10px;
- background-color: $fallback--fg;
- background-color: var(--panel, $fallback--fg);
+ border-color: var(--border);
}
.search-input-container {
@@ -191,8 +199,7 @@
.hashtag {
flex: 1 1 auto;
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -205,9 +212,14 @@
line-height: 2.25rem;
font-weight: 500;
text-align: center;
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
}
+.more-statuses-button {
+ height: 3.5em;
+ line-height: 3.5em;
+ width: 100%;
+}
+
</style>
diff --git a/src/components/search_bar/search_bar.js b/src/components/search_bar/search_bar.js
@@ -16,7 +16,7 @@ const SearchBar = {
error: false
}),
watch: {
- '$route': function (route) {
+ $route: function (route) {
if (route.name === 'search') {
this.searchTerm = route.query.query
}
diff --git a/src/components/search_bar/search_bar.vue b/src/components/search_bar/search_bar.vue
@@ -8,6 +8,7 @@
class="button-unstyled nav-icon"
:title="$t('nav.search')"
type="button"
+ :aria-expanded="!hidden"
@click.prevent.stop="toggleHidden"
>
<FAIcon
@@ -21,7 +22,7 @@
id="search-bar-input"
ref="searchInput"
v-model="searchTerm"
- class="search-bar-input"
+ class="input search-bar-input"
:placeholder="$t('nav.search')"
type="text"
@keyup.enter="find(searchTerm)"
@@ -29,6 +30,7 @@
<button
class="button-default search-button"
type="submit"
+ :title="$t('nav.search')"
@click="find(searchTerm)"
>
<FAIcon
@@ -39,6 +41,8 @@
<button
class="button-unstyled cancel-search"
type="button"
+ :title="$t('nav.search_close')"
+ :aria-expanded="!hidden"
@click.prevent.stop="toggleHidden"
>
<FAIcon
@@ -47,6 +51,8 @@
class="cancel-icon fa-scale-110 fa-old-padding"
/>
</button>
+ <span class="spacer" />
+ <span class="spacer" />
</template>
</div>
</template>
@@ -54,8 +60,6 @@
<script src="./search_bar.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.SearchBar {
display: inline-flex;
align-items: baseline;
@@ -80,8 +84,7 @@
}
.cancel-icon {
- color: $fallback--text;
- color: var(--btnTopBarText, $fallback--text);
+ color: var(--text);
}
}
diff --git a/src/components/select/select.js b/src/components/select/select.js
@@ -13,6 +13,7 @@ export default {
'modelValue',
'disabled',
'unstyled',
- 'kind'
+ 'kind',
+ 'attrs'
]
}
diff --git a/src/components/select/select.vue b/src/components/select/select.vue
@@ -6,12 +6,14 @@
<select
:disabled="disabled"
:value="modelValue"
+ v-bind="$attrs"
@change="$emit('update:modelValue', $event.target.value)"
>
<slot />
</select>
{{ ' ' }}
<FAIcon
+ v-if="!$attrs.size && !$attrs.multiple"
class="select-down-icon"
icon="chevron-down"
/>
@@ -21,29 +23,56 @@
<script src="./select.js"> </script>
<style lang="scss">
-@import '../../_variables.scss';
-
/* TODO fix order of styles */
label.Select {
padding: 0;
select {
- -webkit-appearance: none;
- -moz-appearance: none;
appearance: none;
background: transparent;
border: none;
- color: $fallback--text;
- color: var(--inputText, --text, $fallback--text);
+ color: var(--text);
margin: 0;
- padding: 0 2em 0 .2em;
- font-family: sans-serif;
- font-family: var(--inputFont, sans-serif);
+ padding: 0 2em 0 0.2em;
+ font-family: var(--font);
font-size: 1em;
width: 100%;
z-index: 1;
height: 2em;
line-height: 16px;
+
+ &[multiple],
+ &[size] {
+ height: 100%;
+ padding: 0.2em;
+
+ option {
+ background-color: transparent;
+
+ &:checked,
+ &.-active {
+ color: var(--selectionText);
+ background-color: var(--selectionBackground);
+ }
+ }
+ }
+ }
+
+ &.disabled,
+ &:disabled {
+ background-color: var(--background);
+ opacity: 1; /* override browser */
+ color: var(--faint);
+
+ select {
+ &[multiple],
+ &[size] {
+ option.-active {
+ color: var(--faint);
+ background: transparent;
+ }
+ }
+ }
}
.select-down-icon {
@@ -53,12 +82,10 @@ label.Select {
right: 5px;
height: 100%;
width: 0.875em;
- color: $fallback--text;
- color: var(--inputText, $fallback--text);
+ font-family: var(--font);
line-height: 2;
- z-index: 0;
+ z-index: 1;
pointer-events: none;
}
-
}
</style>
diff --git a/src/components/select/select_motion.vue b/src/components/select/select_motion.vue
@@ -0,0 +1,136 @@
+<template>
+ <div
+ class="SelectMotion btn-group"
+ >
+ <button
+ class="btn button-default"
+ :disabled="disabled"
+ @click="add"
+ >
+ <FAIcon
+ fixed-width
+ icon="plus"
+ />
+ </button>
+ <button
+ class="btn button-default"
+ :disabled="disabled || !moveUpValid"
+ :class="{ disabled: disabled || !moveUpValid }"
+ @click="moveUp"
+ >
+ <FAIcon
+ fixed-width
+ icon="chevron-up"
+ />
+ </button>
+ <button
+ class="btn button-default"
+ :disabled="disabled || !moveDnValid"
+ :class="{ disabled: disabled || !moveDnValid }"
+ @click="moveDn"
+ >
+ <FAIcon
+ fixed-width
+ icon="chevron-down"
+ />
+ </button>
+ <button
+ class="btn button-default"
+ :disabled="disabled || !present"
+ :class="{ disabled: disabled || !present }"
+ @click="del"
+ >
+ <FAIcon
+ fixed-width
+ icon="times"
+ />
+ </button>
+ </div>
+</template>
+
+<script setup>
+import { computed, defineEmits, defineProps, nextTick } from 'vue'
+
+const props = defineProps({
+ modelValue: {
+ type: Array,
+ required: true
+ },
+ selectedId: {
+ type: Number,
+ required: true
+ },
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ getAddValue: {
+ type: Function,
+ required: true
+ }
+})
+
+const emit = defineEmits(['update:modelValue', 'update:selectedId'])
+
+const moveUpValid = computed(() => {
+ return props.selectedId > 0
+})
+
+const present = computed(() => props.modelValue[props.selectedId] != null)
+
+const moveUp = async () => {
+ const newModel = [...props.modelValue]
+ const movable = newModel.splice(props.selectedId, 1)[0]
+ newModel.splice(props.selectedId - 1, 0, movable)
+
+ emit('update:modelValue', newModel)
+ await nextTick()
+ emit('update:selectedId', props.selectedId - 1)
+}
+
+const moveDnValid = computed(() => {
+ return props.selectedId < props.modelValue.length - 1
+})
+
+const moveDn = async () => {
+ const newModel = [...props.modelValue]
+ const movable = newModel.splice(props.selectedId.value, 1)[0]
+ newModel.splice(props.selectedId + 1, 0, movable)
+
+ emit('update:modelValue', newModel)
+ await nextTick()
+ emit('update:selectedId', props.selectedId + 1)
+}
+
+const add = async () => {
+ const newModel = [...props.modelValue, props.getAddValue()]
+
+ emit('update:modelValue', newModel)
+ await nextTick()
+ emit('update:selectedId', Math.max(newModel.length - 1, 0))
+}
+
+const del = async () => {
+ const newModel = [...props.modelValue]
+ newModel.splice(props.selectedId, 1)
+
+ emit('update:modelValue', newModel)
+ await nextTick()
+ emit('update:selectedId', newModel.length === 0 ? undefined : Math.max(props.selectedId - 1, 0))
+}
+</script>
+
+<style lang="scss">
+.SelectMotion {
+ flex: 0 0 auto;
+ display: grid;
+ grid-auto-columns: 1fr;
+ grid-auto-flow: column;
+ margin-top: 0.25em;
+
+ .button-default {
+ margin: 0;
+ padding: 0;
+ }
+}
+</style>
diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue
@@ -23,16 +23,19 @@
<List
:items="items"
:get-key="getKey"
+ :get-class="item => isSelected(item) ? '-active' : ''"
>
- <template v-slot:item="{item}">
+ <template #item="{item}">
<div
class="selectable-list-item-inner"
:class="{ 'selectable-list-item-selected-inner': isSelected(item) }"
+ @click.stop="toggle(!isSelected(item), item)"
>
<div class="selectable-list-checkbox-wrapper">
<Checkbox
:model-value="isSelected(item)"
@update:model-value="checked => toggle(checked, item)"
+ @click.stop
/>
</div>
<slot
@@ -41,7 +44,7 @@
/>
</div>
</template>
- <template v-slot:empty>
+ <template #empty>
<slot name="empty" />
</template>
</List>
@@ -51,9 +54,11 @@
<script src="./selectable_list.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.selectable-list {
+ --__line-height: 1.5em;
+ --__horizontal-gap: 0.75em;
+ --__vertical-gap: 0.5em;
+
&-item-inner {
display: flex;
align-items: center;
@@ -63,23 +68,12 @@
}
}
- &-item-selected-inner {
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- color: var(--selectedMenuText, $fallback--text);
- --faint: var(--selectedMenuFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuLightText, $fallback--lightText);
- --icon: var(--selectedMenuIcon, $fallback--icon);
- }
-
&-header {
display: flex;
align-items: center;
- padding: 0.6em 0;
- border-bottom: 2px solid;
- border-bottom-color: $fallback--border;
- border-bottom-color: var(--border, $fallback--border);
+ padding: var(--__vertical-gap) var(--__horizontal-gap);
+ border-bottom: 1px solid;
+ border-bottom-color: var(--border);
&-actions {
flex: 1;
@@ -87,7 +81,7 @@
}
&-checkbox-wrapper {
- padding: 0 10px;
+ padding-right: var(--__horizontal-gap);
flex: none;
}
}
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js
@@ -0,0 +1,257 @@
+import { clone, assign } from 'lodash'
+import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
+import StringSetting from '../helpers/string_setting.vue'
+import Checkbox from 'components/checkbox/checkbox.vue'
+import StillImage from 'components/still-image/still-image.vue'
+import Select from 'components/select/select.vue'
+import Popover from 'components/popover/popover.vue'
+import ConfirmModal from 'components/confirm_modal/confirm_modal.vue'
+import ModifiedIndicator from '../helpers/modified_indicator.vue'
+import EmojiEditingPopover from '../helpers/emoji_editing_popover.vue'
+
+const EmojiTab = {
+ components: {
+ TabSwitcher,
+ StringSetting,
+ Checkbox,
+ StillImage,
+ Select,
+ Popover,
+ ConfirmModal,
+ ModifiedIndicator,
+ EmojiEditingPopover
+ },
+
+ data () {
+ return {
+ knownLocalPacks: { },
+ knownRemotePacks: { },
+ editedMetadata: { },
+ packName: '',
+ newPackName: '',
+ deleteModalVisible: false,
+ remotePackInstance: '',
+ remotePackDownloadAs: ''
+ }
+ },
+
+ provide () {
+ return { emojiAddr: this.emojiAddr }
+ },
+
+ computed: {
+ pack () {
+ return this.packName !== '' ? this.knownPacks[this.packName] : undefined
+ },
+ packMeta () {
+ if (this.editedMetadata[this.packName] === undefined) {
+ this.editedMetadata[this.packName] = clone(this.pack.pack)
+ }
+
+ return this.editedMetadata[this.packName]
+ },
+ knownPacks () {
+ // Copy the object itself but not the children, so they are still passed by reference and modified
+ const result = clone(this.knownLocalPacks)
+ for (const instName in this.knownRemotePacks) {
+ for (const instPack in this.knownRemotePacks[instName]) {
+ result[`${instPack}@${instName}`] = this.knownRemotePacks[instName][instPack]
+ }
+ }
+
+ return result
+ },
+ downloadWillReplaceLocal () {
+ return (this.remotePackDownloadAs.trim() === '' && this.pack.remote && this.pack.remote.baseName in this.knownLocalPacks) ||
+ (this.remotePackDownloadAs in this.knownLocalPacks)
+ }
+ },
+
+ methods: {
+ reloadEmoji () {
+ this.$store.state.api.backendInteractor.reloadEmoji()
+ },
+ importFromFS () {
+ this.$store.state.api.backendInteractor.importEmojiFromFS()
+ },
+ emojiAddr (name) {
+ if (this.pack.remote !== undefined) {
+ // Remote pack
+ return `${this.pack.remote.instance}/emoji/${encodeURIComponent(this.pack.remote.baseName)}/${name}`
+ } else {
+ return `${this.$store.state.instance.server}/emoji/${encodeURIComponent(this.packName)}/${name}`
+ }
+ },
+
+ createEmojiPack () {
+ this.$store.state.api.backendInteractor.createEmojiPack(
+ { name: this.newPackName }
+ ).then(resp => resp.json()).then(resp => {
+ if (resp === 'ok') {
+ return this.refreshPackList()
+ } else {
+ this.displayError(resp.error)
+ return Promise.reject(resp)
+ }
+ }).then(done => {
+ this.$refs.createPackPopover.hidePopover()
+
+ this.packName = this.newPackName
+ this.newPackName = ''
+ })
+ },
+ deleteEmojiPack () {
+ this.$store.state.api.backendInteractor.deleteEmojiPack(
+ { name: this.packName }
+ ).then(resp => resp.json()).then(resp => {
+ if (resp === 'ok') {
+ return this.refreshPackList()
+ } else {
+ this.displayError(resp.error)
+ return Promise.reject(resp)
+ }
+ }).then(done => {
+ delete this.editedMetadata[this.packName]
+
+ this.deleteModalVisible = false
+ this.packName = ''
+ })
+ },
+
+ metaEdited (prop) {
+ if (!this.pack) return
+
+ const def = this.pack.pack[prop] || ''
+ const edited = this.packMeta[prop] || ''
+ return edited !== def
+ },
+ savePackMetadata () {
+ this.$store.state.api.backendInteractor.saveEmojiPackMetadata({ name: this.packName, newData: this.packMeta }).then(
+ resp => resp.json()
+ ).then(resp => {
+ if (resp.error !== undefined) {
+ this.displayError(resp.error)
+ return
+ }
+
+ // Update actual pack data
+ this.pack.pack = resp
+ // Delete edited pack data, should auto-update itself
+ delete this.editedMetadata[this.packName]
+ })
+ },
+
+ updatePackFiles (newFiles) {
+ this.pack.files = newFiles
+ this.sortPackFiles(this.packName)
+ },
+
+ loadPacksPaginated (listFunction) {
+ const pageSize = 25
+ const allPacks = {}
+
+ return listFunction({ instance: this.remotePackInstance, page: 1, pageSize: 0 })
+ .then(data => data.json())
+ .then(data => {
+ if (data.error !== undefined) { return Promise.reject(data.error) }
+
+ let resultingPromise = Promise.resolve({})
+ for (let i = 0; i < Math.ceil(data.count / pageSize); i++) {
+ resultingPromise = resultingPromise.then(() => listFunction({ instance: this.remotePackInstance, page: i, pageSize })
+ ).then(data => data.json()).then(pageData => {
+ if (pageData.error !== undefined) { return Promise.reject(pageData.error) }
+
+ assign(allPacks, pageData.packs)
+ })
+ }
+
+ return resultingPromise
+ })
+ .then(finished => allPacks)
+ .catch(data => {
+ this.displayError(data)
+ })
+ },
+
+ refreshPackList () {
+ this.loadPacksPaginated(this.$store.state.api.backendInteractor.listEmojiPacks)
+ .then(allPacks => {
+ this.knownLocalPacks = allPacks
+ for (const name of Object.keys(this.knownLocalPacks)) {
+ this.sortPackFiles(name)
+ }
+ })
+ },
+ listRemotePacks () {
+ this.loadPacksPaginated(this.$store.state.api.backendInteractor.listRemoteEmojiPacks)
+ .then(allPacks => {
+ let inst = this.remotePackInstance
+ if (!inst.startsWith('http')) { inst = 'https://' + inst }
+ const instUrl = new URL(inst)
+ inst = instUrl.host
+
+ for (const packName in allPacks) {
+ allPacks[packName].remote = {
+ baseName: packName,
+ instance: instUrl.origin
+ }
+ }
+
+ this.knownRemotePacks[inst] = allPacks
+ for (const pack in this.knownRemotePacks[inst]) {
+ this.sortPackFiles(`${pack}@${inst}`)
+ }
+
+ this.$refs.remotePackPopover.hidePopover()
+ })
+ .catch(data => {
+ this.displayError(data)
+ })
+ },
+ downloadRemotePack () {
+ if (this.remotePackDownloadAs.trim() === '') {
+ this.remotePackDownloadAs = this.pack.remote.baseName
+ }
+
+ this.$store.state.api.backendInteractor.downloadRemoteEmojiPack({
+ instance: this.pack.remote.instance, packName: this.pack.remote.baseName, as: this.remotePackDownloadAs
+ })
+ .then(data => data.json())
+ .then(resp => {
+ if (resp === 'ok') {
+ this.$refs.dlPackPopover.hidePopover()
+
+ return this.refreshPackList()
+ } else {
+ this.displayError(resp.error)
+ return Promise.reject(resp)
+ }
+ }).then(done => {
+ this.packName = this.remotePackDownloadAs
+ this.remotePackDownloadAs = ''
+ })
+ },
+ displayError (msg) {
+ this.$store.dispatch('pushGlobalNotice', {
+ messageKey: 'admin_dash.emoji.error',
+ messageArgs: [msg],
+ level: 'error'
+ })
+ },
+ sortPackFiles (nameOfPack) {
+ // Sort by key
+ const sorted = Object.keys(this.knownPacks[nameOfPack].files).sort().reduce((acc, key) => {
+ if (key.length === 0) return acc
+ acc[key] = this.knownPacks[nameOfPack].files[key]
+ return acc
+ }, {})
+ this.knownPacks[nameOfPack].files = sorted
+ }
+ },
+
+ mounted () {
+ this.refreshPackList()
+ }
+}
+
+export default EmojiTab
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.scss b/src/components/settings_modal/admin_tabs/emoji_tab.scss
@@ -0,0 +1,59 @@
+.emoji-tab {
+ .btn-group .btn:not(:first-child) {
+ margin-left: 0.5em;
+ }
+
+ .pack-info-wrapper {
+ margin-top: 1em;
+ }
+
+ .emoji-info-input {
+ width: 100%;
+ }
+
+ .emoji-data-input {
+ width: 40%;
+ margin-left: 0.5em;
+ margin-right: 0.5em;
+ }
+
+ .emoji {
+ width: 32px;
+ height: 32px;
+ }
+
+ .emoji-unsaved {
+ box-shadow: 0 3px 5px var(--cBlue);
+ }
+
+ .emoji-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1em 1em;
+ }
+}
+
+.emoji-tab-popover-button:not(:first-child) {
+ margin-left: 0.5em;
+}
+
+.emoji-tab-popover-input {
+ margin-bottom: 0.5em;
+
+ label {
+ display: block;
+ margin-bottom: 0.5em;
+ }
+
+ input {
+ width: 20em;
+ }
+
+ .emoji-tab-popover-file {
+ padding-top: 3px;
+ }
+
+ .warning {
+ color: var(--cOrange);
+ }
+}
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -0,0 +1,358 @@
+<template>
+ <div
+ class="emoji-tab"
+ :label="$t('admin_dash.tabs.emoji')"
+ >
+ <div class="setting-item">
+ <h2>{{ $t('admin_dash.tabs.emoji') }}</h2>
+
+ <ul class="setting-list">
+ <h3>{{ $t('admin_dash.emoji.global_actions') }}</h3>
+
+ <li class="btn-group setting-item">
+ <button
+ class="button button-default btn"
+ type="button"
+ @click="reloadEmoji"
+ >
+ {{ $t('admin_dash.emoji.reload') }}
+ </button>
+ <button
+ class="button button-default btn"
+ type="button"
+ @click="importFromFS"
+ >
+ {{ $t('admin_dash.emoji.importFS') }}
+ </button>
+ </li>
+
+ <li class="btn-group setting-item">
+ <button
+ class="button button-default btn"
+ type="button"
+ @click="$refs.remotePackPopover.showPopover"
+ >
+ {{ $t('admin_dash.emoji.remote_packs') }}
+
+ <Popover
+ ref="remotePackPopover"
+ popover-class="emoji-tab-edit-popover popover-default"
+ trigger="click"
+ placement="bottom"
+ bound-to-selector=".emoji-tab"
+ :bound-to="{ x: 'container' }"
+ :offset="{ y: 5 }"
+ >
+ <template #content>
+ <div class="emoji-tab-popover-input">
+ <h3>{{ $t('admin_dash.emoji.remote_pack_instance') }}</h3>
+ <input
+ v-model="remotePackInstance"
+ class="input"
+ :placeholder="$t('admin_dash.emoji.remote_pack_instance')"
+ >
+ <button
+ class="button button-default btn emoji-tab-popover-button"
+ type="button"
+ @click="listRemotePacks"
+ >
+ {{ $t('admin_dash.emoji.do_list') }}
+ </button>
+ </div>
+ </template>
+ </Popover>
+ </button>
+ </li>
+
+ <h3>{{ $t('admin_dash.emoji.emoji_packs') }}</h3>
+
+ <li>
+ <h4>{{ $t('admin_dash.emoji.edit_pack') }}</h4>
+
+ <Select
+ v-model="packName"
+ class="form-control"
+ >
+ <option
+ value=""
+ disabled
+ hidden
+ >
+ {{ $t('admin_dash.emoji.emoji_pack') }}
+ </option>
+ <option
+ v-for="(pack, listPackName) in knownPacks"
+ :key="listPackName"
+ :label="listPackName"
+ >
+ {{ listPackName }}
+ </option>
+ </Select>
+
+ <button
+ class="button button-default btn emoji-tab-popover-button"
+ type="button"
+ @click="$refs.createPackPopover.showPopover"
+ >
+ {{ $t('admin_dash.emoji.create_pack') }}
+ </button>
+ <Popover
+ ref="createPackPopover"
+ popover-class="emoji-tab-edit-popover popover-default"
+ trigger="click"
+ placement="bottom"
+ bound-to-selector=".emoji-tab"
+ :bound-to="{ x: 'container' }"
+ :offset="{ y: 5 }"
+ >
+ <template #content>
+ <div class="emoji-tab-popover-input">
+ <h3>{{ $t('admin_dash.emoji.new_pack_name') }}</h3>
+ <input
+ v-model="newPackName"
+ :placeholder="$t('admin_dash.emoji.new_pack_name')"
+ class="input"
+ >
+ <button
+ class="button button-default btn emoji-tab-popover-button"
+ type="button"
+ @click="createEmojiPack"
+ >
+ {{ $t('admin_dash.emoji.create') }}
+ </button>
+ </div>
+ </template>
+ </Popover>
+ </li>
+ </ul>
+
+ <div v-if="pack">
+ <div class="pack-info-wrapper">
+ <ul class="setting-list">
+ <li>
+ <label>
+ {{ $t('admin_dash.emoji.description') }}
+ <ModifiedIndicator
+ :changed="metaEdited('description')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
+
+ <textarea
+ v-model="packMeta.description"
+ :disabled="pack.remote !== undefined"
+ class="bio resize-height input"
+ />
+ </label>
+ </li>
+ <li>
+ <label>
+ {{ $t('admin_dash.emoji.homepage') }}
+ <ModifiedIndicator
+ :changed="metaEdited('homepage')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
+
+ <input
+ v-model="packMeta.homepage"
+ class="emoji-info-input input"
+ :disabled="pack.remote !== undefined"
+ >
+ </label>
+ </li>
+ <li>
+ <label>
+ {{ $t('admin_dash.emoji.fallback_src') }}
+ <ModifiedIndicator
+ :changed="metaEdited('fallback-src')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
+
+ <input
+ v-model="packMeta['fallback-src']"
+ class="emoji-info-input input"
+ :disabled="pack.remote !== undefined"
+ >
+ </label>
+ </li>
+ <li>
+ <label>
+ {{ $t('admin_dash.emoji.fallback_sha256') }}
+
+ <input
+ v-model="packMeta['fallback-src-sha256']"
+ :disabled="true"
+ class="emoji-info-input input"
+ >
+ </label>
+ </li>
+ <li>
+ <Checkbox
+ v-model="packMeta['share-files']"
+ :disabled="pack.remote !== undefined"
+ >
+ {{ $t('admin_dash.emoji.share') }}
+ </Checkbox>
+
+ <ModifiedIndicator
+ :changed="metaEdited('share-files')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
+ </li>
+ <li class="btn-group">
+ <button
+ v-if="pack.remote === undefined"
+ class="button button-default btn"
+ type="button"
+ @click="savePackMetadata"
+ >
+ {{ $t('admin_dash.emoji.save_meta') }}
+ </button>
+ <button
+ v-if="pack.remote === undefined"
+ class="button button-default btn"
+ type="button"
+ @click="savePackMetadata"
+ >
+ {{ $t('admin_dash.emoji.revert_meta') }}
+ </button>
+
+ <button
+ v-if="pack.remote === undefined"
+ class="button button-default btn"
+ type="button"
+ @click="deleteModalVisible = true"
+ >
+ {{ $t('admin_dash.emoji.delete_pack') }}
+
+ <ConfirmModal
+ v-if="deleteModalVisible"
+ :title="$t('admin_dash.emoji.delete_title')"
+ :cancel-text="$t('status.delete_confirm_cancel_button')"
+ :confirm-text="$t('status.delete_confirm_accept_button')"
+ @cancelled="deleteModalVisible = false"
+ @accepted="deleteEmojiPack"
+ >
+ {{ $t('admin_dash.emoji.delete_confirm', [packName]) }}
+ </ConfirmModal>
+ </button>
+
+ <button
+ v-if="pack.remote !== undefined"
+ class="button button-default btn"
+ type="button"
+ @click="$refs.dlPackPopover.showPopover"
+ >
+ {{ $t('admin_dash.emoji.download_pack') }}
+
+ <Popover
+ ref="dlPackPopover"
+ trigger="click"
+ placement="bottom"
+ bound-to-selector=".emoji-tab"
+ popover-class="emoji-tab-edit-popover popover-default"
+ :bound-to="{ x: 'container' }"
+ :offset="{ y: 5 }"
+ >
+ <template #content>
+ <h3>{{ $t('admin_dash.emoji.downloading_pack', [packName]) }}</h3>
+ <div>
+ <div>
+ <div class="emoji-tab-popover-input">
+ <label>
+ {{ $t('admin_dash.emoji.download_as_name') }}
+ <input
+ v-model="remotePackDownloadAs"
+ class="emoji-data-input input"
+ :placeholder="$t('admin_dash.emoji.download_as_name_full')"
+ >
+ </label>
+
+ <div
+ v-if="downloadWillReplaceLocal"
+ class="warning"
+ >
+ <em>{{ $t('admin_dash.emoji.replace_warning') }}</em>
+ </div>
+ </div>
+
+ <button
+ class="button button-default btn"
+ type="button"
+ @click="downloadRemotePack"
+ >
+ {{ $t('admin_dash.emoji.download') }}
+ </button>
+ </div>
+ </div>
+ </template>
+ </Popover>
+ </button>
+ </li>
+ </ul>
+ </div>
+
+ <ul class="setting-list">
+ <h4>
+ {{ $t('admin_dash.emoji.files') }}
+
+ <ModifiedIndicator
+ v-if="pack"
+ :changed="$refs.emojiPopovers && $refs.emojiPopovers.some(p => p.isEdited)"
+ message-key="admin_dash.emoji.emoji_changed"
+ />
+ </h4>
+
+ <div
+ v-if="pack"
+ class="emoji-list"
+ >
+ <EmojiEditingPopover
+ v-if="pack.remote === undefined"
+ placement="bottom"
+ new-upload
+ :title="$t('admin_dash.emoji.adding_new')"
+ :pack-name="packName"
+ @updatePackFiles="updatePackFiles"
+ @displayError="displayError"
+ >
+ <template #trigger>
+ <FAIcon
+ icon="plus"
+ size="2x"
+ :title="$t('admin_dash.emoji.add_file')"
+ />
+ </template>
+ </EmojiEditingPopover>
+
+ <EmojiEditingPopover
+ v-for="(file, shortcode) in pack.files"
+ ref="emojiPopovers"
+ :key="shortcode"
+ placement="top"
+ :title="$t('admin_dash.emoji.editing', [shortcode])"
+ :disabled="pack.remote !== undefined"
+ :shortcode="shortcode"
+ :file="file"
+ :pack-name="packName"
+ @updatePackFiles="updatePackFiles"
+ @displayError="displayError"
+ >
+ <template #trigger>
+ <StillImage
+ class="emoji"
+ :src="emojiAddr(file)"
+ :title="`:${shortcode}:`"
+ :alt="`:${shortcode}:`"
+ />
+ </template>
+ </EmojiEditingPopover>
+ </div>
+ </ul>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script src="./emoji_tab.js"></script>
+
+<style lang="scss" src="./emoji_tab.scss"></style>
diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.js b/src/components/settings_modal/admin_tabs/frontends_tab.js
@@ -0,0 +1,113 @@
+import BooleanSetting from '../helpers/boolean_setting.vue'
+import ChoiceSetting from '../helpers/choice_setting.vue'
+import IntegerSetting from '../helpers/integer_setting.vue'
+import StringSetting from '../helpers/string_setting.vue'
+import GroupSetting from '../helpers/group_setting.vue'
+import Popover from 'src/components/popover/popover.vue'
+import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
+
+import SharedComputedObject from '../helpers/shared_computed_object.js'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faGlobe
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faGlobe
+)
+
+const FrontendsTab = {
+ provide () {
+ return {
+ defaultDraftMode: true,
+ defaultSource: 'admin'
+ }
+ },
+ data () {
+ return {
+ working: false
+ }
+ },
+ components: {
+ BooleanSetting,
+ ChoiceSetting,
+ IntegerSetting,
+ StringSetting,
+ GroupSetting,
+ PanelLoading,
+ Popover
+ },
+ created () {
+ if (this.user.rights.admin) {
+ this.$store.dispatch('loadFrontendsStuff')
+ }
+ },
+ computed: {
+ frontends () {
+ return this.$store.state.adminSettings.frontends
+ },
+ ...SharedComputedObject()
+ },
+ methods: {
+ canInstall (frontend) {
+ const fe = this.frontends.find(f => f.name === frontend.name)
+ if (!fe) return false
+ return fe.refs.includes(frontend.ref)
+ },
+ getSuggestedRef (frontend) {
+ if (this.adminDraft) {
+ const defaultFe = this.adminDraft[':pleroma'][':frontends'][':primary']
+ if (defaultFe?.name === frontend.name && this.canInstall(defaultFe)) {
+ return defaultFe.ref
+ } else {
+ return frontend.refs[0]
+ }
+ } else {
+ return frontend.refs[0]
+ }
+ },
+ update (frontend, suggestRef) {
+ const ref = suggestRef || this.getSuggestedRef(frontend)
+ const { name } = frontend
+ const payload = { name, ref }
+
+ this.working = true
+ this.$store.state.api.backendInteractor.installFrontend({ payload })
+ .finally(() => {
+ this.working = false
+ })
+ .then(async (response) => {
+ this.$store.dispatch('loadFrontendsStuff')
+ if (response.error) {
+ const reason = await response.error.json()
+ this.$store.dispatch('pushGlobalNotice', {
+ level: 'error',
+ messageKey: 'admin_dash.frontend.failure_installing_frontend',
+ messageArgs: {
+ version: name + '/' + ref,
+ reason: reason.error
+ },
+ timeout: 5000
+ })
+ } else {
+ this.$store.dispatch('pushGlobalNotice', {
+ level: 'success',
+ messageKey: 'admin_dash.frontend.success_installing_frontend',
+ messageArgs: {
+ version: name + '/' + ref
+ },
+ timeout: 2000
+ })
+ }
+ })
+ },
+ setDefault (frontend, suggestRef) {
+ const ref = suggestRef || this.getSuggestedRef(frontend)
+ const { name } = frontend
+
+ this.$store.commit('updateAdminDraft', { path: [':pleroma', ':frontends', ':primary'], value: { name, ref } })
+ }
+ }
+}
+
+export default FrontendsTab
diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.scss b/src/components/settings_modal/admin_tabs/frontends_tab.scss
@@ -0,0 +1,29 @@
+.frontends-tab {
+ .cards-list {
+ padding: 0;
+ }
+
+ .relative {
+ position: relative;
+ }
+
+ .overlay {
+ position: absolute;
+ background: var(--bg);
+ // fix buttons showing through
+ z-index: 2;
+ opacity: 0.9;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ }
+
+ dd {
+ text-overflow: ellipsis;
+ word-wrap: nowrap;
+ white-space: nowrap;
+ overflow-x: hidden;
+ max-width: 10em;
+ }
+}
diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.vue b/src/components/settings_modal/admin_tabs/frontends_tab.vue
@@ -0,0 +1,217 @@
+<template>
+ <div
+ class="frontends-tab"
+ :label="$t('admin_dash.tabs.frontends')"
+ >
+ <div class="setting-item">
+ <h2>{{ $t('admin_dash.tabs.frontends') }}</h2>
+ <p>{{ $t('admin_dash.frontend.wip_notice') }}</p>
+ <ul
+ v-if="adminDraft"
+ class="setting-list"
+ >
+ <li>
+ <h3>{{ $t('admin_dash.frontend.default_frontend') }}</h3>
+ <p>{{ $t('admin_dash.frontend.default_frontend_tip') }}</p>
+ <ul class="setting-list">
+ <li>
+ <StringSetting path=":pleroma.:frontends.:primary.name" />
+ </li>
+ <li>
+ <StringSetting path=":pleroma.:frontends.:primary.ref" />
+ </li>
+ <li>
+ <GroupSetting path=":pleroma.:frontends.:primary" />
+ </li>
+ </ul>
+ </li>
+ </ul>
+ <div
+ v-else
+ class="setting-list"
+ >
+ {{ $t('admin_dash.frontend.default_frontend_unavail') }}
+ </div>
+
+ <div class="setting-list relative">
+ <PanelLoading
+ v-if="working"
+ class="overlay"
+ />
+ <h3>{{ $t('admin_dash.frontend.available_frontends') }}</h3>
+ <ul class="cards-list">
+ <li
+ v-for="frontend in frontends"
+ :key="frontend.name"
+ >
+ <strong>{{ frontend.name }}</strong>
+ {{ ' ' }}
+ <span v-if="adminDraft && adminDraft[':pleroma'][':frontends'][':primary']?.name === frontend.name">
+ <i18n-t
+ v-if="adminDraft && adminDraft[':pleroma'][':frontends'][':primary']?.ref === frontend.refs[0]"
+ scope="global"
+ keypath="admin_dash.frontend.is_default"
+ />
+ <i18n-t
+ v-else
+ keypath="admin_dash.frontend.is_default_custom"
+ scope="global"
+ >
+ <template #version>
+ <code>{{ adminDraft && adminDraft[':pleroma'][':frontends'][':primary'].ref }}</code>
+ </template>
+ </i18n-t>
+ </span>
+ <dl>
+ <dt>{{ $t('admin_dash.frontend.repository') }}</dt>
+ <dd>
+ <a
+ :href="frontend.git"
+ target="_blank"
+ >{{ frontend.git }}</a>
+ </dd>
+ <template v-if="expertLevel">
+ <dt>{{ $t('admin_dash.frontend.versions') }}</dt>
+ <dd
+ v-for="ref in frontend.refs"
+ :key="ref"
+ >
+ <code>{{ ref }}</code>
+ </dd>
+ </template>
+ <dt v-if="expertLevel">
+ {{ $t('admin_dash.frontend.build_url') }}
+ </dt>
+ <dd v-if="expertLevel">
+ <a
+ :href="frontend.build_url"
+ target="_blank"
+ >{{ frontend.build_url }}</a>
+ </dd>
+ </dl>
+ <div>
+ <span class="btn-group">
+ <button
+ class="button button-default btn"
+ type="button"
+ @click="update(frontend)"
+ >
+ {{
+ frontend.installed
+ ? $t('admin_dash.frontend.reinstall')
+ : $t('admin_dash.frontend.install')
+ }}
+ <code>
+ {{
+ getSuggestedRef(frontend)
+ }}
+ </code>
+ </button>
+ <Popover
+ v-if="frontend.refs.length > 1"
+ trigger="click"
+ class="button-dropdown"
+ placement="bottom"
+ >
+ <template #content="{close}">
+ <div class="dropdown-menu">
+ <button
+ v-for="ref in frontend.refs"
+ :key="ref"
+ class="menu-item dropdown-item"
+ @click.prevent="update(frontend, ref)"
+ @click="close"
+ >
+ <i18n-t
+ keypath="admin_dash.frontend.install_version"
+ scope="global"
+ >
+ <template #version>
+ <code>{{ ref }}</code>
+ </template>
+ </i18n-t>
+ </button>
+ </div>
+ </template>
+ <template #trigger>
+ <button
+ class="button button-default btn dropdown-button"
+ type="button"
+ :title="$t('admin_dash.frontend.more_install_options')"
+ >
+ <FAIcon icon="chevron-down" />
+ </button>
+ </template>
+ </Popover>
+ </span>
+ <span
+ v-if="frontend.installed && frontend.name !== 'admin-fe'"
+ class="btn-group"
+ >
+ <button
+ class="button button-default btn"
+ type="button"
+ :disabled="
+ !adminDraft || adminDraft[':pleroma'][':frontends'][':primary']?.name === frontend.name &&
+ adminDraft[':pleroma'][':frontends'][':primary']?.ref === frontend.refs[0]
+ "
+ @click="setDefault(frontend)"
+ >
+ {{
+ $t('admin_dash.frontend.set_default')
+ }}
+ <code>
+ {{
+ getSuggestedRef(frontend)
+ }}
+ </code>
+ </button>
+ {{ ' ' }}
+ <Popover
+ v-if="frontend.refs.length > 1"
+ trigger="click"
+ class="button-dropdown"
+ placement="bottom"
+ >
+ <template #content="{close}">
+ <div class="dropdown-menu">
+ <button
+ v-for="ref in frontend.installedRefs || frontend.refs"
+ :key="ref"
+ class="menu-item dropdown-item"
+ @click.prevent="setDefault(frontend, ref)"
+ @click="close"
+ >
+ <i18n-t
+ keypath="admin_dash.frontend.set_default_version"
+ scope="global"
+ >
+ <template #version>
+ <code>{{ ref }}</code>
+ </template>
+ </i18n-t>
+ </button>
+ </div>
+ </template>
+ <template #trigger>
+ <button
+ class="button button-default btn dropdown-button"
+ type="button"
+ :title="$t('admin_dash.frontend.more_default_options')"
+ >
+ <FAIcon icon="chevron-down" />
+ </button>
+ </template>
+ </Popover>
+ </span>
+ </div>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script src="./frontends_tab.js"></script>
+
+<style lang="scss" src="./frontends_tab.scss"></style>
diff --git a/src/components/settings_modal/admin_tabs/instance_tab.js b/src/components/settings_modal/admin_tabs/instance_tab.js
@@ -0,0 +1,38 @@
+import BooleanSetting from '../helpers/boolean_setting.vue'
+import ChoiceSetting from '../helpers/choice_setting.vue'
+import IntegerSetting from '../helpers/integer_setting.vue'
+import StringSetting from '../helpers/string_setting.vue'
+import GroupSetting from '../helpers/group_setting.vue'
+import AttachmentSetting from '../helpers/attachment_setting.vue'
+
+import SharedComputedObject from '../helpers/shared_computed_object.js'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faGlobe
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faGlobe
+)
+
+const InstanceTab = {
+ provide () {
+ return {
+ defaultDraftMode: true,
+ defaultSource: 'admin'
+ }
+ },
+ components: {
+ BooleanSetting,
+ ChoiceSetting,
+ IntegerSetting,
+ StringSetting,
+ AttachmentSetting,
+ GroupSetting
+ },
+ computed: {
+ ...SharedComputedObject()
+ }
+}
+
+export default InstanceTab
diff --git a/src/components/settings_modal/admin_tabs/instance_tab.vue b/src/components/settings_modal/admin_tabs/instance_tab.vue
@@ -0,0 +1,206 @@
+<template>
+ <div :label="$t('admin_dash.tabs.instance')">
+ <div class="setting-item">
+ <h2>{{ $t('admin_dash.instance.instance') }}</h2>
+ <ul class="setting-list">
+ <li>
+ <StringSetting path=":pleroma.:instance.:name" />
+ </li>
+ <!-- See https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3963 -->
+ <li v-if="adminDraft[':pleroma'][':instance'][':favicon'] !== undefined">
+ <AttachmentSetting
+ compact
+ path=":pleroma.:instance.:favicon"
+ />
+ </li>
+ <li>
+ <StringSetting path=":pleroma.:instance.:email" />
+ </li>
+ <li>
+ <StringSetting path=":pleroma.:instance.:description" />
+ </li>
+ <li>
+ <StringSetting path=":pleroma.:instance.:short_description" />
+ </li>
+ <li>
+ <AttachmentSetting
+ compact
+ path=":pleroma.:instance.:instance_thumbnail"
+ />
+ </li>
+ <li>
+ <AttachmentSetting path=":pleroma.:instance.:background_image" />
+ </li>
+ </ul>
+ </div>
+ <div class="setting-item">
+ <h2>{{ $t('admin_dash.instance.registrations') }}</h2>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path=":pleroma.:instance.:registrations_open" />
+ <ul class="setting-list suboptions">
+ <li>
+ <BooleanSetting
+ path=":pleroma.:instance.:invites_enabled"
+ parent-path=":pleroma.:instance.:registrations_open"
+ parent-invert
+ />
+ </li>
+ </ul>
+ </li>
+ <li>
+ <BooleanSetting path=":pleroma.:instance.:birthday_required" />
+ <ul class="setting-list suboptions">
+ <li>
+ <IntegerSetting
+ path=":pleroma.:instance.:birthday_min_age"
+ parent-path=":pleroma.:instance.:birthday_required"
+ />
+ </li>
+ </ul>
+ </li>
+ <li>
+ <BooleanSetting path=":pleroma.:instance.:account_activation_required" />
+ </li>
+ <li>
+ <BooleanSetting path=":pleroma.:instance.:account_approval_required" />
+ </li>
+ <li>
+ <h3>{{ $t('admin_dash.instance.captcha_header') }}</h3>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting :path="[':pleroma', 'Pleroma.Captcha', ':enabled']" />
+ <ul class="setting-list suboptions">
+ <li>
+ <ChoiceSetting
+ :path="[':pleroma', 'Pleroma.Captcha', ':method']"
+ :parent-path="[':pleroma', 'Pleroma.Captcha', ':enabled']"
+ :option-label-map="{
+ 'Pleroma.Captcha.Native': $t('admin_dash.captcha.native'),
+ 'Pleroma.Captcha.Kocaptcha': $t('admin_dash.captcha.kocaptcha')
+ }"
+ />
+ <IntegerSetting
+ :path="[':pleroma', 'Pleroma.Captcha', ':seconds_valid']"
+ :parent-path="[':pleroma', 'Pleroma.Captcha', ':enabled']"
+ />
+ </li>
+ <li
+ v-if="adminDraft[':pleroma']['Pleroma.Captcha'][':enabled'] && adminDraft[':pleroma']['Pleroma.Captcha'][':method'] === 'Pleroma.Captcha.Kocaptcha'"
+ >
+ <h4>{{ $t('admin_dash.instance.kocaptcha') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <StringSetting :path="[':pleroma', 'Pleroma.Captcha.Kocaptcha', ':endpoint']" />
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ <div class="setting-item">
+ <h2>{{ $t('admin_dash.instance.access') }}</h2>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting
+ override-backend-description
+ override-backend-description-label
+ path=":pleroma.:instance.:public"
+ />
+ </li>
+ <li>
+ <ChoiceSetting
+ override-backend-description
+ override-backend-description-label
+ path=":pleroma.:instance.:limit_to_local_content"
+ />
+ </li>
+ <li v-if="expertLevel">
+ <h3>{{ $t('admin_dash.instance.restrict.header') }}</h3>
+ <p>
+ {{ $t('admin_dash.instance.restrict.description') }}
+ </p>
+ <ul class="setting-list">
+ <li>
+ <h4>{{ $t('admin_dash.instance.restrict.timelines') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting
+ path=":pleroma.:restrict_unauthenticated.:timelines.:local"
+ indeterminate-state=":if_instance_is_private"
+ swap-description-and-label
+ hide-description
+ />
+ </li>
+ <li>
+ <BooleanSetting
+ path=":pleroma.:restrict_unauthenticated.:timelines.:federated"
+ indeterminate-state=":if_instance_is_private"
+ swap-description-and-label
+ hide-description
+ />
+ </li>
+ <li>
+ <GroupSetting path=":pleroma.:restrict_unauthenticated.:timelines" />
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4>{{ $t('admin_dash.instance.restrict.profiles') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting
+ path=":pleroma.:restrict_unauthenticated.:profiles.:local"
+ indeterminate-state=":if_instance_is_private"
+ swap-description-and-label
+ hide-description
+ />
+ </li>
+ <li>
+ <BooleanSetting
+ path=":pleroma.:restrict_unauthenticated.:profiles.:remote"
+ indeterminate-state=":if_instance_is_private"
+ swap-description-and-label
+ hide-description
+ />
+ </li>
+ <li>
+ <GroupSetting path=":pleroma.:restrict_unauthenticated.:profiles" />
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4>{{ $t('admin_dash.instance.restrict.activities') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting
+ path=":pleroma.:restrict_unauthenticated.:activities.:local"
+ indeterminate-state=":if_instance_is_private"
+ swap-description-and-label
+ hide-description
+ />
+ </li>
+ <li>
+ <BooleanSetting
+ path=":pleroma.:restrict_unauthenticated.:activities.:remote"
+ indeterminate-state=":if_instance_is_private"
+ swap-description-and-label
+ hide-description
+ />
+ </li>
+ <li>
+ <GroupSetting path=":pleroma.:restrict_unauthenticated.:activities" />
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ </div>
+</template>
+
+<script src="./instance_tab.js"></script>
diff --git a/src/components/settings_modal/admin_tabs/limits_tab.js b/src/components/settings_modal/admin_tabs/limits_tab.js
@@ -0,0 +1,28 @@
+import BooleanSetting from '../helpers/boolean_setting.vue'
+import ChoiceSetting from '../helpers/choice_setting.vue'
+import IntegerSetting from '../helpers/integer_setting.vue'
+import StringSetting from '../helpers/string_setting.vue'
+
+import SharedComputedObject from '../helpers/shared_computed_object.js'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faGlobe
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faGlobe
+)
+
+const LimitsTab = {
+ components: {
+ BooleanSetting,
+ ChoiceSetting,
+ IntegerSetting,
+ StringSetting
+ },
+ computed: {
+ ...SharedComputedObject()
+ }
+}
+
+export default LimitsTab
diff --git a/src/components/settings_modal/admin_tabs/limits_tab.vue b/src/components/settings_modal/admin_tabs/limits_tab.vue
@@ -0,0 +1,136 @@
+<template>
+ <div :label="$t('admin_dash.tabs.limits')">
+ <div class="setting-item">
+ <h2>{{ $t('admin_dash.limits.arbitrary_limits') }}</h2>
+ <ul class="setting-list">
+ <li>
+ <h3>{{ $t('admin_dash.limits.posts') }}</h3>
+ <ul class="setting-list">
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:limit"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:remote_limit"
+ expert="1"
+ draft-mode
+ />
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h3>{{ $t('admin_dash.limits.uploads') }}</h3>
+ <ul class="setting-list">
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:description_limit"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:upload_limit"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:max_media_attachments"
+ draft-mode
+ />
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h3>{{ $t('admin_dash.limits.users') }}</h3>
+ <ul class="setting-list">
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:max_pinned_statuses"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:user_bio_length"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:user_name_length"
+ draft-mode
+ />
+ </li>
+ <li>
+ <h4>{{ $t('admin_dash.limits.profile_fields') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:max_account_fields"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:max_remote_account_fields"
+ draft-mode
+ expert="1"
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:account_field_name_length"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:account_field_value_length"
+ draft-mode
+ />
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4>{{ $t('admin_dash.limits.user_uploads') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:avatar_upload_limit"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:banner_upload_limit"
+ draft-mode
+ />
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ </div>
+</template>
+
+<script src="./limits_tab.js"></script>
diff --git a/src/components/settings_modal/helpers/attachment_setting.js b/src/components/settings_modal/helpers/attachment_setting.js
@@ -0,0 +1,44 @@
+import Setting from './setting.js'
+import { fileTypeExt } from 'src/services/file_type/file_type.service.js'
+import MediaUpload from 'src/components/media_upload/media_upload.vue'
+import Attachment from 'src/components/attachment/attachment.vue'
+
+export default {
+ ...Setting,
+ props: {
+ ...Setting.props,
+ compact: Boolean,
+ acceptTypes: {
+ type: String,
+ required: false,
+ default: 'image/*'
+ }
+ },
+ components: {
+ ...Setting.components,
+ MediaUpload,
+ Attachment
+ },
+ computed: {
+ ...Setting.computed,
+ attachment () {
+ const path = this.realDraftMode ? this.draft : this.state
+ // The "server" part is primarily for local dev, but could be useful for alt-domain or multiuser usage.
+ const url = path.includes('://') ? path : this.$store.state.instance.server + path
+ return {
+ mimetype: fileTypeExt(url),
+ url
+ }
+ }
+ },
+ methods: {
+ ...Setting.methods,
+ setMediaFile (fileInfo) {
+ if (this.realDraftMode) {
+ this.draft = fileInfo.url
+ } else {
+ this.configSink(this.path, fileInfo.url)
+ }
+ }
+ }
+}
diff --git a/src/components/settings_modal/helpers/attachment_setting.vue b/src/components/settings_modal/helpers/attachment_setting.vue
@@ -0,0 +1,122 @@
+<template>
+ <span
+ v-if="matchesExpertLevel"
+ class="AttachmentSetting"
+ :class="{ '-compact': compact }"
+ >
+ <label
+ :for="path"
+ :class="{ 'faint': shouldBeDisabled }"
+ >
+ <template v-if="backendDescriptionLabel">
+ {{ backendDescriptionLabel + ' ' }}
+ </template>
+ <template v-else-if="source === 'admin'">
+ MISSING LABEL FOR {{ path }}
+ </template>
+ <slot v-else />
+
+ </label>
+ <p
+ v-if="backendDescriptionDescription"
+ class="setting-description"
+ :class="{ 'faint': shouldBeDisabled }"
+ >
+ {{ backendDescriptionDescription + ' ' }}
+ </p>
+ <div class="attachment-input">
+ <div class="controls control-field">
+ <label for="path">{{ $t('settings.url') }}</label>
+ <input
+ :id="path"
+ class="input string-input"
+ :disabled="shouldBeDisabled"
+ :value="realDraftMode ? draft : state"
+ @change="update"
+ >
+ {{ ' ' }}
+ <ModifiedIndicator
+ :changed="isChanged"
+ :onclick="reset"
+ />
+ <ProfileSettingIndicator :is-profile="isProfileSetting" />
+ </div>
+ <div v-if="!compact">{{ $t('settings.preview') }}</div>
+ <Attachment
+ class="attachment"
+ :compact="compact"
+ :attachment="attachment"
+ size="small"
+ hide-description
+ />
+ <div class="controls control-upload">
+ <MediaUpload
+ ref="mediaUpload"
+ class="media-upload-icon"
+ normal-button
+ :accept-types="acceptTypes"
+ @uploaded="setMediaFile"
+ />
+ </div>
+ </div>
+ <DraftButtons />
+ </span>
+</template>
+
+<script src="./attachment_setting.js"></script>
+
+<style lang="scss">
+.AttachmentSetting {
+ .attachment {
+ display: block;
+ width: 100%;
+ height: 15em;
+ margin-bottom: 0.5em;
+ }
+
+ .attachment-input {
+ margin-left: 1em;
+ display: flex;
+ flex-direction: column;
+ width: 20em;
+ }
+
+ &.-compact {
+ .attachment-input {
+ flex-direction: row;
+ align-items: flex-end;
+ }
+
+ .attachment {
+ flex: 0;
+ order: 0;
+ display: block;
+ min-width: 4em;
+ height: 4em;
+ align-self: center;
+ margin-bottom: 0;
+ }
+
+ .control-field {
+ order: 1;
+ min-width: 12em;
+ margin-left: 0.5em;
+ }
+
+ .control-upload {
+ order: 2;
+ min-width: 12em;
+ padding: 0 0.5em;
+ }
+ }
+
+ .controls {
+ margin-bottom: 0.5em;
+
+ input,
+ button {
+ width: 100%;
+ }
+ }
+}
+</style>
diff --git a/src/components/settings_modal/helpers/boolean_setting.js b/src/components/settings_modal/helpers/boolean_setting.js
@@ -1,47 +1,31 @@
-import { get, set } from 'lodash'
import Checkbox from 'src/components/checkbox/checkbox.vue'
-import ModifiedIndicator from './modified_indicator.vue'
-import ServerSideIndicator from './server_side_indicator.vue'
+import Setting from './setting.js'
+
export default {
+ ...Setting,
+ props: {
+ ...Setting.props,
+ indeterminateState: [String, Object]
+ },
components: {
- Checkbox,
- ModifiedIndicator,
- ServerSideIndicator
+ ...Setting.components,
+ Checkbox
},
- props: [
- 'path',
- 'disabled',
- 'expert'
- ],
computed: {
- pathDefault () {
- const [firstSegment, ...rest] = this.path.split('.')
- return [firstSegment + 'DefaultValue', ...rest].join('.')
- },
- state () {
- const value = get(this.$parent, this.path)
- if (value === undefined) {
- return this.defaultState
- } else {
- return value
- }
- },
- defaultState () {
- return get(this.$parent, this.pathDefault)
- },
- isServerSide () {
- return this.path.startsWith('serverSide_')
- },
- isChanged () {
- return !this.path.startsWith('serverSide_') && this.state !== this.defaultState
- },
- matchesExpertLevel () {
- return (this.expert || 0) <= this.$parent.expertLevel
+ ...Setting.computed,
+ isIndeterminate () {
+ return this.visibleState === this.indeterminateState
}
},
methods: {
- update (e) {
- set(this.$parent, this.path, e)
+ ...Setting.methods,
+ getValue (e) {
+ // Basic tri-state toggle implementation
+ if (!!this.indeterminateState && !e && this.visibleState === true) {
+ // If we have indeterminate state, switching from true to false first goes through indeterminate
+ return this.indeterminateState
+ }
+ return e
}
}
}
diff --git a/src/components/settings_modal/helpers/boolean_setting.vue b/src/components/settings_modal/helpers/boolean_setting.vue
@@ -4,18 +4,37 @@
class="BooleanSetting"
>
<Checkbox
- :model-value="state"
- :disabled="disabled"
+ :model-value="visibleState"
+ :disabled="shouldBeDisabled"
+ :indeterminate="isIndeterminate"
@update:modelValue="update"
>
<span
- v-if="!!$slots.default"
class="label"
+ :class="{ 'faint': shouldBeDisabled }"
>
- <slot />
+ <template v-if="backendDescriptionLabel">
+ {{ backendDescriptionLabel }}
+ </template>
+ <template v-else-if="source === 'admin'">
+ MISSING LABEL FOR {{ path }}
+ </template>
+ <slot v-else />
</span>
- {{ ' ' }}
- <ModifiedIndicator :changed="isChanged" /><ServerSideIndicator :server-side="isServerSide" /> </Checkbox>
+ </Checkbox>
+ <ModifiedIndicator
+ :changed="isChanged"
+ :onclick="reset"
+ />
+ <ProfileSettingIndicator :is-profile="isProfileSetting" />
+ <DraftButtons />
+ <p
+ v-if="backendDescriptionDescription"
+ class="setting-description"
+ :class="{ 'faint': shouldBeDisabled }"
+ >
+ {{ backendDescriptionDescription + ' ' }}
+ </p>
</label>
</template>
diff --git a/src/components/settings_modal/helpers/choice_setting.js b/src/components/settings_modal/helpers/choice_setting.js
@@ -1,48 +1,41 @@
-import { get, set } from 'lodash'
import Select from 'src/components/select/select.vue'
-import ModifiedIndicator from './modified_indicator.vue'
-import ServerSideIndicator from './server_side_indicator.vue'
+import Setting from './setting.js'
+
export default {
+ ...Setting,
components: {
- Select,
- ModifiedIndicator,
- ServerSideIndicator
+ ...Setting.components,
+ Select
},
- props: [
- 'path',
- 'disabled',
- 'options',
- 'expert'
- ],
- computed: {
- pathDefault () {
- const [firstSegment, ...rest] = this.path.split('.')
- return [firstSegment + 'DefaultValue', ...rest].join('.')
+ props: {
+ ...Setting.props,
+ options: {
+ type: Array,
+ required: false
},
- state () {
- const value = get(this.$parent, this.path)
- if (value === undefined) {
- return this.defaultState
- } else {
- return value
+ optionLabelMap: {
+ type: Object,
+ required: false,
+ default: {}
+ }
+ },
+ computed: {
+ ...Setting.computed,
+ realOptions () {
+ if (this.realSource === 'admin') {
+ return this.backendDescriptionSuggestions.map(x => ({
+ key: x,
+ value: x,
+ label: this.optionLabelMap[x] || x
+ }))
}
- },
- defaultState () {
- return get(this.$parent, this.pathDefault)
- },
- isServerSide () {
- return this.path.startsWith('serverSide_')
- },
- isChanged () {
- return !this.path.startsWith('serverSide_') && this.state !== this.defaultState
- },
- matchesExpertLevel () {
- return (this.expert || 0) <= this.$parent.expertLevel
+ return this.options
}
},
methods: {
- update (e) {
- set(this.$parent, this.path, e)
+ ...Setting.methods,
+ getValue (e) {
+ return e
}
}
}
diff --git a/src/components/settings_modal/helpers/choice_setting.vue b/src/components/settings_modal/helpers/choice_setting.vue
@@ -3,15 +3,20 @@
v-if="matchesExpertLevel"
class="ChoiceSetting"
>
- <slot />
+ <template v-if="backendDescriptionLabel">
+ {{ backendDescriptionLabel }}
+ </template>
+ <template v-else>
+ <slot />
+ </template>
{{ ' ' }}
<Select
- :model-value="state"
+ :model-value="realDraftMode ? draft :state"
:disabled="disabled"
@update:modelValue="update"
>
<option
- v-for="option in options"
+ v-for="option in realOptions"
:key="option.key"
:value="option.value"
>
@@ -19,14 +24,19 @@
{{ option.value === defaultState ? $t('settings.instance_default_simple') : '' }}
</option>
</Select>
- <ModifiedIndicator :changed="isChanged" />
- <ServerSideIndicator :server-side="isServerSide" />
+ <ModifiedIndicator
+ :changed="isChanged"
+ :onclick="reset"
+ />
+ <ProfileSettingIndicator :is-profile="isProfileSetting" />
+ <DraftButtons />
+ <p
+ v-if="backendDescriptionDescription"
+ class="setting-description"
+ >
+ {{ backendDescriptionDescription + ' ' }}
+ </p>
</label>
</template>
<script src="./choice_setting.js"></script>
-
-<style lang="scss">
-.ChoiceSetting {
-}
-</style>
diff --git a/src/components/settings_modal/helpers/draft_buttons.vue b/src/components/settings_modal/helpers/draft_buttons.vue
@@ -0,0 +1,88 @@
+<!-- this is a helper exclusive to Setting components -->
+<!-- TODO make it reusable -->
+<template>
+ <span
+ class="DraftButtons"
+ >
+ <Popover
+ v-if="$parent.isDirty"
+ trigger="hover"
+ normal-button
+ :trigger-attrs="{ 'aria-label': $t('settings.commit_value_tooltip') }"
+ @click="$parent.commitDraft"
+ >
+ <template #trigger>
+ {{ $t('settings.commit_value') }}
+ </template>
+ <template #content>
+ <div class="modified-tooltip">
+ {{ $t('settings.commit_value_tooltip') }}
+ </div>
+ </template>
+ </Popover>
+ <Popover
+ v-if="$parent.isDirty"
+ trigger="hover"
+ normal-button
+ :trigger-attrs="{ 'aria-label': $t('settings.reset_value_tooltip') }"
+ @click="$parent.reset"
+ >
+ <template #trigger>
+ {{ $t('settings.reset_value') }}
+ </template>
+ <template #content>
+ <div class="modified-tooltip">
+ {{ $t('settings.reset_value_tooltip') }}
+ </div>
+ </template>
+ </Popover>
+ <Popover
+ v-if="$parent.canHardReset"
+ trigger="hover"
+ normal-button
+ :trigger-attrs="{ 'aria-label': $t('settings.hard_reset_value_tooltip') }"
+ @click="$parent.hardReset"
+ >
+ <template #trigger>
+ {{ $t('settings.hard_reset_value') }}
+ </template>
+ <template #content>
+ <div class="modified-tooltip">
+ {{ $t('settings.hard_reset_value_tooltip') }}
+ </div>
+ </template>
+ </Popover>
+ </span>
+</template>
+
+<script>
+import Popover from 'src/components/popover/popover.vue'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faWrench } from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faWrench
+)
+
+export default {
+ components: { Popover },
+ props: ['changed']
+}
+</script>
+
+<style lang="scss">
+.DraftButtons {
+ display: inline-block;
+ position: relative;
+
+ .button-default {
+ margin-left: 0.5em;
+ }
+}
+
+.draft-tooltip {
+ margin: 0.5em 1em;
+ min-width: 10em;
+ text-align: center;
+}
+</style>
diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue
@@ -0,0 +1,236 @@
+<template>
+ <Popover
+ ref="emojiPopover"
+ trigger="click"
+ :placement="placement"
+ bound-to-selector=".emoji-list"
+ popover-class="emoji-tab-edit-popover popover-default"
+ :bound-to="{ x: 'container' }"
+ :offset="{ y: 5 }"
+ :disabled="disabled"
+ :class="{'emoji-unsaved': isEdited}"
+ >
+ <template #trigger>
+ <slot name="trigger" />
+ </template>
+ <template #content>
+ <h3>
+ {{ title }}
+ </h3>
+
+ <StillImage
+ v-if="emojiPreview"
+ class="emoji"
+ :src="emojiPreview"
+ />
+ <div
+ v-else
+ class="emoji"
+ />
+
+ <div
+ v-if="newUpload"
+ class="emoji-tab-popover-input"
+ >
+ <input
+ type="file"
+ accept="image/*"
+ class="emoji-tab-popover-file input"
+ @change="uploadFile = $event.target.files"
+ >
+ </div>
+ <div>
+ <div class="emoji-tab-popover-input">
+ <label>
+ {{ $t('admin_dash.emoji.shortcode') }}
+ <input
+ v-model="editedShortcode"
+ class="emoji-data-input input"
+ :placeholder="$t('admin_dash.emoji.new_shortcode')"
+ >
+ </label>
+ </div>
+
+ <div class="emoji-tab-popover-input">
+ <label>
+ {{ $t('admin_dash.emoji.filename') }}
+
+ <input
+ v-model="editedFile"
+ class="emoji-data-input input"
+ :placeholder="$t('admin_dash.emoji.new_filename')"
+ >
+ </label>
+ </div>
+
+ <button
+ class="button button-default btn"
+ type="button"
+ :disabled="newUpload ? uploadFile.length == 0 : !isEdited"
+ @click="newUpload ? uploadEmoji() : saveEditedEmoji()"
+ >
+ {{ $t('admin_dash.emoji.save') }}
+ </button>
+
+ <template v-if="!newUpload">
+ <button
+ class="button button-default btn emoji-tab-popover-button"
+ type="button"
+ @click="deleteModalVisible = true"
+ >
+ {{ $t('admin_dash.emoji.delete') }}
+ </button>
+ <button
+ class="button button-default btn emoji-tab-popover-button"
+ type="button"
+ @click="revertEmoji"
+ >
+ {{ $t('admin_dash.emoji.revert') }}
+ </button>
+ <ConfirmModal
+ v-if="deleteModalVisible"
+ :title="$t('admin_dash.emoji.delete_title')"
+ :cancel-text="$t('status.delete_confirm_cancel_button')"
+ :confirm-text="$t('status.delete_confirm_accept_button')"
+ @cancelled="deleteModalVisible = false"
+ @accepted="deleteEmoji"
+ >
+ {{ $t('admin_dash.emoji.delete_confirm', [shortcode]) }}
+ </ConfirmModal>
+ </template>
+ </div>
+ </template>
+ </Popover>
+</template>
+
+<script>
+import Popover from 'components/popover/popover.vue'
+import ConfirmModal from 'components/confirm_modal/confirm_modal.vue'
+import StillImage from 'components/still-image/still-image.vue'
+
+export default {
+ components: { Popover, ConfirmModal, StillImage },
+ inject: ['emojiAddr'],
+ props: {
+ placement: {
+ type: String,
+ required: true
+ },
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+
+ newUpload: Boolean,
+
+ title: {
+ type: String,
+ required: true
+ },
+ packName: {
+ type: String,
+ required: true
+ },
+ shortcode: {
+ type: String,
+ // Only exists when this is not a new upload
+ default: ''
+ },
+ file: {
+ type: String,
+ // Only exists when this is not a new upload
+ default: ''
+ }
+ },
+ emits: ['updatePackFiles', 'displayError'],
+ data () {
+ return {
+ uploadFile: [],
+ editedShortcode: this.shortcode,
+ editedFile: this.file,
+ deleteModalVisible: false
+ }
+ },
+ computed: {
+ emojiPreview () {
+ if (this.newUpload && this.uploadFile.length > 0) {
+ return URL.createObjectURL(this.uploadFile[0])
+ } else if (!this.newUpload) {
+ return this.emojiAddr(this.file)
+ }
+
+ return null
+ },
+ isEdited () {
+ return !this.newUpload && (this.editedShortcode !== this.shortcode || this.editedFile !== this.file)
+ }
+ },
+ methods: {
+ saveEditedEmoji () {
+ if (!this.isEdited) return
+
+ this.$store.state.api.backendInteractor.updateEmojiFile(
+ { packName: this.packName, shortcode: this.shortcode, newShortcode: this.editedShortcode, newFilename: this.editedFile, force: false }
+ ).then(resp => {
+ if (resp.error !== undefined) {
+ this.$emit('displayError', resp.error)
+ return Promise.reject(resp.error)
+ }
+
+ return resp.json()
+ }).then(resp => this.$emit('updatePackFiles', resp))
+ },
+ uploadEmoji () {
+ this.$store.state.api.backendInteractor.addNewEmojiFile({
+ packName: this.packName,
+ file: this.uploadFile[0],
+ shortcode: this.editedShortcode,
+ filename: this.editedFile
+ }).then(resp => resp.json()).then(resp => {
+ if (resp.error !== undefined) {
+ this.$emit('displayError', resp.error)
+ return
+ }
+
+ this.$emit('updatePackFiles', resp)
+ this.$refs.emojiPopover.hidePopover()
+
+ this.editedFile = ''
+ this.editedShortcode = ''
+ this.uploadFile = []
+ })
+ },
+ revertEmoji () {
+ this.editedFile = this.file
+ this.editedShortcode = this.shortcode
+ },
+ deleteEmoji () {
+ this.deleteModalVisible = false
+
+ this.$store.state.api.backendInteractor.deleteEmojiFile(
+ { packName: this.packName, shortcode: this.shortcode }
+ ).then(resp => resp.json()).then(resp => {
+ if (resp.error !== undefined) {
+ this.$emit('displayError', resp.error)
+ return
+ }
+
+ this.$emit('updatePackFiles', resp)
+ })
+ }
+ }
+}
+</script>
+
+<style lang="scss">
+ .emoji-tab-edit-popover {
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ padding-bottom: 0.5em;
+
+ .emoji {
+ width: 32px;
+ height: 32px;
+ }
+ }
+</style>
diff --git a/src/components/settings_modal/helpers/float_setting.vue b/src/components/settings_modal/helpers/float_setting.vue
@@ -0,0 +1,16 @@
+<template>
+ <NumberSetting
+ v-bind="$attrs"
+ >
+ <slot />
+ </NumberSetting>
+</template>
+
+<script>
+import NumberSetting from './number_setting.vue'
+export default {
+ components: {
+ NumberSetting
+ }
+}
+</script>
diff --git a/src/components/settings_modal/helpers/group_setting.js b/src/components/settings_modal/helpers/group_setting.js
@@ -0,0 +1,13 @@
+import { isEqual } from 'lodash'
+
+import Setting from './setting.js'
+
+export default {
+ ...Setting,
+ computed: {
+ ...Setting.computed,
+ isDirty () {
+ return !isEqual(this.state, this.draft)
+ }
+ }
+}
diff --git a/src/components/settings_modal/helpers/group_setting.vue b/src/components/settings_modal/helpers/group_setting.vue
@@ -0,0 +1,15 @@
+<template>
+ <span
+ v-if="matchesExpertLevel"
+ class="GroupSetting"
+ >
+ <ModifiedIndicator
+ :changed="isChanged"
+ :onclick="reset"
+ />
+ <ProfileSettingIndicator :is-profile="isProfileSetting" />
+ <DraftButtons />
+ </span>
+</template>
+
+<script src="./group_setting.js"></script>
diff --git a/src/components/settings_modal/helpers/integer_setting.js b/src/components/settings_modal/helpers/integer_setting.js
@@ -1,41 +0,0 @@
-import { get, set } from 'lodash'
-import ModifiedIndicator from './modified_indicator.vue'
-export default {
- components: {
- ModifiedIndicator
- },
- props: {
- path: String,
- disabled: Boolean,
- min: Number,
- expert: [Number, String]
- },
- computed: {
- pathDefault () {
- const [firstSegment, ...rest] = this.path.split('.')
- return [firstSegment + 'DefaultValue', ...rest].join('.')
- },
- state () {
- const value = get(this.$parent, this.path)
- if (value === undefined) {
- return this.defaultState
- } else {
- return value
- }
- },
- defaultState () {
- return get(this.$parent, this.pathDefault)
- },
- isChanged () {
- return this.state !== this.defaultState
- },
- matchesExpertLevel () {
- return (this.expert || 0) <= this.$parent.expertLevel
- }
- },
- methods: {
- update (e) {
- set(this.$parent, this.path, parseInt(e.target.value))
- }
- }
-}
diff --git a/src/components/settings_modal/helpers/integer_setting.vue b/src/components/settings_modal/helpers/integer_setting.vue
@@ -1,24 +1,17 @@
<template>
- <span
- v-if="matchesExpertLevel"
- class="IntegerSetting"
+ <NumberSetting
+ v-bind="$attrs"
+ :truncate="1"
>
- <label :for="path">
- <slot />
- </label>
- <input
- :id="path"
- class="number-input"
- type="number"
- step="1"
- :disabled="disabled"
- :min="min || 0"
- :value="state"
- @change="update"
- >
- {{ ' ' }}
- <ModifiedIndicator :changed="isChanged" />
- </span>
+ <slot />
+ </NumberSetting>
</template>
-<script src="./integer_setting.js"></script>
+<script>
+import NumberSetting from './number_setting.vue'
+export default {
+ components: {
+ NumberSetting
+ }
+}
+</script>
diff --git a/src/components/settings_modal/helpers/modified_indicator.vue b/src/components/settings_modal/helpers/modified_indicator.vue
@@ -5,17 +5,17 @@
>
<Popover
trigger="hover"
+ :trigger-attrs="{ 'aria-label': $t('settings.setting_changed') }"
>
- <template v-slot:trigger>
+ <template #trigger>
<FAIcon
icon="wrench"
- :aria-label="$t('settings.setting_changed')"
/>
</template>
- <template v-slot:content>
+ <template #content>
<div class="modified-tooltip">
- {{ $t('settings.setting_changed') }}
+ {{ $t(messageKey) }}
</div>
</template>
</Popover>
@@ -33,7 +33,13 @@ library.add(
export default {
components: { Popover },
- props: ['changed']
+ props: {
+ changed: Boolean,
+ messageKey: {
+ type: String,
+ default: 'settings.setting_changed'
+ }
+ }
}
</script>
@@ -41,11 +47,11 @@ export default {
.ModifiedIndicator {
display: inline-block;
position: relative;
+}
- .modified-tooltip {
- margin: 0.5em 1em;
- min-width: 10em;
- text-align: center;
- }
+.modified-tooltip {
+ margin: 0.5em 1em;
+ min-width: 10em;
+ text-align: center;
}
</style>
diff --git a/src/components/settings_modal/helpers/number_setting.js b/src/components/settings_modal/helpers/number_setting.js
@@ -0,0 +1,39 @@
+import Setting from './setting.js'
+
+export default {
+ ...Setting,
+ props: {
+ ...Setting.props,
+ min: {
+ type: Number,
+ required: false,
+ default: 1
+ },
+ max: {
+ type: Number,
+ required: false,
+ default: 1
+ },
+ step: {
+ type: Number,
+ required: false,
+ default: 1
+ },
+ truncate: {
+ type: Number,
+ required: false,
+ default: 1
+ }
+ },
+ methods: {
+ ...Setting.methods,
+ getValue (e) {
+ if (!this.truncate === 1) {
+ return parseInt(e.target.value)
+ } else if (this.truncate > 1) {
+ return Math.trunc(e.target.value / this.truncate) * this.truncate
+ }
+ return parseFloat(e.target.value)
+ }
+ }
+}
diff --git a/src/components/settings_modal/helpers/number_setting.vue b/src/components/settings_modal/helpers/number_setting.vue
@@ -0,0 +1,46 @@
+<template>
+ <span
+ v-if="matchesExpertLevel"
+ class="NumberSetting"
+ >
+ <label
+ :for="path"
+ :class="{ 'faint': shouldBeDisabled }"
+ >
+ <template v-if="backendDescriptionLabel">
+ {{ backendDescriptionLabel + ' ' }}
+ </template>
+ <template v-else-if="source === 'admin'">
+ MISSING LABEL FOR {{ path }}
+ </template>
+ <slot v-else />
+ </label>
+ {{ ' ' }}
+ <input
+ :id="path"
+ class="input number-input"
+ type="number"
+ :step="step || 1"
+ :disabled="shouldBeDisabled"
+ :min="min || 0"
+ :value="realDraftMode ? draft :state"
+ @change="update"
+ >
+ {{ ' ' }}
+ <ModifiedIndicator
+ :changed="isChanged"
+ :onclick="reset"
+ />
+ <ProfileSettingIndicator :is-profile="isProfileSetting" />
+ <DraftButtons />
+ <p
+ v-if="backendDescriptionDescription"
+ class="setting-description"
+ :class="{ 'faint': shouldBeDisabled }"
+ >
+ {{ backendDescriptionDescription + ' ' }}
+ </p>
+ </span>
+</template>
+
+<script src="./number_setting.js"></script>
diff --git a/src/components/settings_modal/helpers/profile_setting_indicator.vue b/src/components/settings_modal/helpers/profile_setting_indicator.vue
@@ -0,0 +1,51 @@
+<template>
+ <span
+ v-if="isProfile"
+ class="ProfileSettingIndicator"
+ >
+ <Popover
+ trigger="hover"
+ >
+ <template #trigger>
+
+ <FAIcon
+ icon="server"
+ :aria-label="$t('settings.setting_server_side')"
+ />
+ </template>
+ <template #content>
+ <div class="profilesetting-tooltip">
+ {{ $t('settings.setting_server_side') }}
+ </div>
+ </template>
+ </Popover>
+ </span>
+</template>
+
+<script>
+import Popover from 'src/components/popover/popover.vue'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faServer } from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faServer
+)
+
+export default {
+ components: { Popover },
+ props: ['isProfile']
+}
+</script>
+
+<style lang="scss">
+.ProfileSettingIndicator {
+ display: inline-block;
+ position: relative;
+}
+
+.profilesetting-tooltip {
+ margin: 0.5em 1em;
+ min-width: 10em;
+ text-align: center;
+}
+</style>
diff --git a/src/components/settings_modal/helpers/server_side_indicator.vue b/src/components/settings_modal/helpers/server_side_indicator.vue
@@ -1,51 +0,0 @@
-<template>
- <span
- v-if="serverSide"
- class="ServerSideIndicator"
- >
- <Popover
- trigger="hover"
- >
- <template v-slot:trigger>
-
- <FAIcon
- icon="server"
- :aria-label="$t('settings.setting_server_side')"
- />
- </template>
- <template v-slot:content>
- <div class="serverside-tooltip">
- {{ $t('settings.setting_server_side') }}
- </div>
- </template>
- </Popover>
- </span>
-</template>
-
-<script>
-import Popover from 'src/components/popover/popover.vue'
-import { library } from '@fortawesome/fontawesome-svg-core'
-import { faServer } from '@fortawesome/free-solid-svg-icons'
-
-library.add(
- faServer
-)
-
-export default {
- components: { Popover },
- props: ['serverSide']
-}
-</script>
-
-<style lang="scss">
-.ServerSideIndicator {
- display: inline-block;
- position: relative;
-
- .serverside-tooltip {
- margin: 0.5em 1em;
- min-width: 10em;
- text-align: center;
- }
-}
-</style>
diff --git a/src/components/settings_modal/helpers/setting.js b/src/components/settings_modal/helpers/setting.js
@@ -0,0 +1,262 @@
+import ModifiedIndicator from './modified_indicator.vue'
+import ProfileSettingIndicator from './profile_setting_indicator.vue'
+import DraftButtons from './draft_buttons.vue'
+import { get, set, cloneDeep } from 'lodash'
+
+export default {
+ components: {
+ ModifiedIndicator,
+ DraftButtons,
+ ProfileSettingIndicator
+ },
+ props: {
+ modelValue: {
+ type: String,
+ default: null
+ },
+ path: {
+ type: [String, Array],
+ required: false
+ },
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ parentPath: {
+ type: [String, Array]
+ },
+ parentInvert: {
+ type: Boolean,
+ default: false
+ },
+ expert: {
+ type: [Number, String],
+ default: 0
+ },
+ source: {
+ type: String,
+ default: undefined
+ },
+ hideDescription: {
+ type: Boolean
+ },
+ swapDescriptionAndLabel: {
+ type: Boolean
+ },
+ overrideBackendDescription: {
+ type: Boolean
+ },
+ overrideBackendDescriptionLabel: {
+ type: Boolean
+ },
+ draftMode: {
+ type: Boolean,
+ default: undefined
+ },
+ timedApplyMode: {
+ type: Boolean,
+ default: false
+ }
+ },
+ inject: {
+ defaultSource: {
+ default: 'default'
+ },
+ defaultDraftMode: {
+ default: false
+ }
+ },
+ data () {
+ return {
+ localDraft: null
+ }
+ },
+ created () {
+ if (this.realDraftMode && (this.realSource !== 'admin' || this.path == null)) {
+ this.draft = this.state
+ }
+ },
+ computed: {
+ draft: {
+ // TODO allow passing shared draft object?
+ get () {
+ if (this.realSource === 'admin' || this.path == null) {
+ return get(this.$store.state.adminSettings.draft, this.canonPath)
+ } else {
+ return this.localDraft
+ }
+ },
+ set (value) {
+ if (this.realSource === 'admin' || this.path == null) {
+ this.$store.commit('updateAdminDraft', { path: this.canonPath, value })
+ } else {
+ this.localDraft = value
+ }
+ }
+ },
+ state () {
+ if (this.path == null) {
+ return this.modelValue
+ }
+ const value = get(this.configSource, this.canonPath)
+ if (value === undefined) {
+ return this.defaultState
+ } else {
+ return value
+ }
+ },
+ visibleState () {
+ return this.realDraftMode ? this.draft : this.state
+ },
+ realSource () {
+ return this.source || this.defaultSource
+ },
+ realDraftMode () {
+ return typeof this.draftMode === 'undefined' ? this.defaultDraftMode : this.draftMode
+ },
+ backendDescription () {
+ return get(this.$store.state.adminSettings.descriptions, this.path)
+ },
+ backendDescriptionLabel () {
+ if (this.realSource !== 'admin') return ''
+ if (!this.backendDescription || this.overrideBackendDescriptionLabel) {
+ return this.$t([
+ 'admin_dash',
+ 'temp_overrides',
+ ...this.canonPath.map(p => p.replace(/\./g, '_DOT_')),
+ 'label'
+ ].join('.'))
+ } else {
+ return this.swapDescriptionAndLabel
+ ? this.backendDescription?.description
+ : this.backendDescription?.label
+ }
+ },
+ backendDescriptionDescription () {
+ if (this.realSource !== 'admin') return ''
+ if (this.hideDescription) return null
+ if (!this.backendDescription || this.overrideBackendDescription) {
+ return this.$t([
+ 'admin_dash',
+ 'temp_overrides',
+ ...this.canonPath.map(p => p.replace(/\./g, '_DOT_')),
+ 'description'
+ ].join('.'))
+ } else {
+ return this.swapDescriptionAndLabel
+ ? this.backendDescription?.label
+ : this.backendDescription?.description
+ }
+ },
+ backendDescriptionSuggestions () {
+ return this.backendDescription?.suggestions
+ },
+ shouldBeDisabled () {
+ if (this.path == null) {
+ return this.disabled
+ }
+ const parentValue = this.parentPath !== undefined ? get(this.configSource, this.parentPath) : null
+ return this.disabled || (parentValue !== null ? (this.parentInvert ? parentValue : !parentValue) : false)
+ },
+ configSource () {
+ switch (this.realSource) {
+ case 'profile':
+ return this.$store.state.profileConfig
+ case 'admin':
+ return this.$store.state.adminSettings.config
+ default:
+ return this.$store.getters.mergedConfig
+ }
+ },
+ configSink () {
+ if (this.path == null) {
+ return (k, v) => this.$emit('update:modelValue', v)
+ }
+ switch (this.realSource) {
+ case 'profile':
+ return (k, v) => this.$store.dispatch('setProfileOption', { name: k, value: v })
+ case 'admin':
+ return (k, v) => this.$store.dispatch('pushAdminSetting', { path: k, value: v })
+ default:
+ if (this.timedApplyMode) {
+ return (k, v) => this.$store.dispatch('setOptionTemporarily', { name: k, value: v })
+ } else {
+ return (k, v) => this.$store.dispatch('setOption', { name: k, value: v })
+ }
+ }
+ },
+ defaultState () {
+ switch (this.realSource) {
+ case 'profile':
+ return {}
+ default:
+ return get(this.$store.getters.defaultConfig, this.path)
+ }
+ },
+ isProfileSetting () {
+ return this.realSource === 'profile'
+ },
+ isChanged () {
+ if (this.path == null) return false
+ switch (this.realSource) {
+ case 'profile':
+ case 'admin':
+ return false
+ default:
+ return this.state !== this.defaultState
+ }
+ },
+ canonPath () {
+ if (this.path == null) return null
+ return Array.isArray(this.path) ? this.path : this.path.split('.')
+ },
+ isDirty () {
+ if (this.path == null) return false
+ if (this.realSource === 'admin' && this.canonPath.length > 3) {
+ return false // should not show draft buttons for "grouped" values
+ } else {
+ return this.realDraftMode && this.draft !== this.state
+ }
+ },
+ canHardReset () {
+ return this.realSource === 'admin' && this.$store.state.adminSettings.modifiedPaths &&
+ this.$store.state.adminSettings.modifiedPaths.has(this.canonPath.join(' -> '))
+ },
+ matchesExpertLevel () {
+ return (this.expert || 0) <= this.$store.state.config.expertLevel > 0
+ }
+ },
+ methods: {
+ getValue (e) {
+ return e.target.value
+ },
+ update (e) {
+ if (this.realDraftMode) {
+ this.draft = this.getValue(e)
+ } else {
+ this.configSink(this.path, this.getValue(e))
+ }
+ },
+ commitDraft () {
+ if (this.realDraftMode) {
+ this.configSink(this.path, this.draft)
+ }
+ },
+ reset () {
+ if (this.realDraftMode) {
+ this.draft = cloneDeep(this.state)
+ } else {
+ set(this.$store.getters.mergedConfig, this.path, cloneDeep(this.defaultState))
+ }
+ },
+ hardReset () {
+ switch (this.realSource) {
+ case 'admin':
+ return this.$store.dispatch('resetAdminSetting', { path: this.path })
+ .then(() => { this.draft = this.state })
+ default:
+ console.warn('Hard reset not implemented yet!')
+ }
+ }
+ }
+}
diff --git a/src/components/settings_modal/helpers/shared_computed_object.js b/src/components/settings_modal/helpers/shared_computed_object.js
@@ -1,52 +1,18 @@
-import { defaultState as configDefaultState } from 'src/modules/config.js'
-import { defaultState as serverSideConfigDefaultState } from 'src/modules/serverSideConfig.js'
-
const SharedComputedObject = () => ({
user () {
return this.$store.state.users.currentUser
},
- // Getting values for default properties
- ...Object.keys(configDefaultState)
- .map(key => [
- key + 'DefaultValue',
- function () {
- return this.$store.getters.defaultConfig[key]
- }
- ])
- .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
- // Generating computed values for vuex properties
- ...Object.keys(configDefaultState)
- .map(key => [key, {
- get () { return this.$store.getters.mergedConfig[key] },
- set (value) {
- this.$store.dispatch('setOption', { name: key, value })
- }
- }])
- .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
- ...Object.keys(serverSideConfigDefaultState)
- .map(key => ['serverSide_' + key, {
- get () { return this.$store.state.serverSideConfig[key] },
- set (value) {
- this.$store.dispatch('setServerSideOption', { name: key, value })
- }
- }])
- .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
- // Special cases (need to transform values or perform actions first)
- useStreamingApi: {
- get () { return this.$store.getters.mergedConfig.useStreamingApi },
- set (value) {
- const promise = value
- ? this.$store.dispatch('enableMastoSockets')
- : this.$store.dispatch('disableMastoSockets')
-
- promise.then(() => {
- this.$store.dispatch('setOption', { name: 'useStreamingApi', value })
- }).catch((e) => {
- console.error('Failed starting MastoAPI Streaming socket', e)
- this.$store.dispatch('disableMastoSockets')
- this.$store.dispatch('setOption', { name: 'useStreamingApi', value: false })
- })
- }
+ expertLevel () {
+ return this.$store.getters.mergedConfig.expertLevel > 0
+ },
+ mergedConfig () {
+ return this.$store.getters.mergedConfig
+ },
+ adminConfig () {
+ return this.$store.state.adminSettings.config
+ },
+ adminDraft () {
+ return this.$store.state.adminSettings.draft
}
})
diff --git a/src/components/settings_modal/helpers/string_setting.js b/src/components/settings_modal/helpers/string_setting.js
@@ -0,0 +1,5 @@
+import Setting from './setting.js'
+
+export default {
+ ...Setting
+}
diff --git a/src/components/settings_modal/helpers/string_setting.vue b/src/components/settings_modal/helpers/string_setting.vue
@@ -0,0 +1,44 @@
+<template>
+ <label
+ v-if="matchesExpertLevel"
+ class="StringSetting"
+ >
+ <label
+ :for="path"
+ class="setting-label"
+ :class="{ 'faint': shouldBeDisabled }"
+ >
+ <template v-if="backendDescriptionLabel">
+ {{ backendDescriptionLabel + ' ' }}
+ </template>
+ <template v-else-if="source === 'admin'">
+ MISSING LABEL FOR {{ path }}
+ </template>
+ <slot v-else />
+ </label>
+ {{ ' ' }}
+ <input
+ :id="path"
+ class="input string-input"
+ :disabled="shouldBeDisabled"
+ :value="realDraftMode ? draft : state"
+ @change="update"
+ >
+ {{ ' ' }}
+ <ModifiedIndicator
+ :changed="isChanged"
+ :onclick="reset"
+ />
+ <ProfileSettingIndicator :is-profile="isProfileSetting" />
+ <DraftButtons />
+ <p
+ v-if="backendDescriptionDescription"
+ class="setting-description"
+ :class="{ 'faint': shouldBeDisabled }"
+ >
+ {{ backendDescriptionDescription + ' ' }}
+ </p>
+ </label>
+</template>
+
+<script src="./string_setting.js"></script>
diff --git a/src/components/settings_modal/helpers/unit_setting.js b/src/components/settings_modal/helpers/unit_setting.js
@@ -0,0 +1,64 @@
+import Select from 'src/components/select/select.vue'
+import Setting from './setting.js'
+
+export const allCssUnits = ['cm', 'mm', 'in', 'px', 'pt', 'pc', 'em', 'ex', 'ch', 'rem', 'vw', 'vh', 'vmin', 'vmax', '%']
+export const defaultHorizontalUnits = ['px', 'rem', 'vw']
+export const defaultVerticalUnits = ['px', 'rem', 'vh']
+
+export default {
+ ...Setting,
+ components: {
+ ...Setting.components,
+ Select
+ },
+ props: {
+ ...Setting.props,
+ min: Number,
+ units: {
+ type: Array,
+ default: () => allCssUnits
+ },
+ unitSet: {
+ type: String,
+ default: 'none'
+ },
+ step: {
+ type: Number,
+ default: 1
+ },
+ resetDefault: {
+ type: Object,
+ default: null
+ }
+ },
+ computed: {
+ ...Setting.computed,
+ stateUnit () {
+ return typeof this.state === 'string' ? this.state.replace(/[0-9,.]+/, '') : ''
+ },
+ stateValue () {
+ return typeof this.state === 'string' ? this.state.replace(/[^0-9,.]+/, '') : ''
+ }
+ },
+ methods: {
+ ...Setting.methods,
+ getUnitString (value) {
+ if (this.unitSet === 'none') return value
+ return this.$t(['settings', 'units', this.unitSet, value].join('.'))
+ },
+ updateValue (e) {
+ this.configSink(this.path, parseFloat(e.target.value) + this.stateUnit)
+ },
+ updateUnit (e) {
+ let value = this.stateValue
+ const newUnit = e.target.value
+ if (this.resetDefault) {
+ const replaceValue = this.resetDefault[newUnit]
+ if (replaceValue != null) {
+ value = replaceValue
+ }
+ }
+ this.configSink(this.path, value + newUnit)
+ }
+ }
+}
diff --git a/src/components/settings_modal/helpers/unit_setting.vue b/src/components/settings_modal/helpers/unit_setting.vue
@@ -0,0 +1,68 @@
+<template>
+ <span
+ v-if="matchesExpertLevel"
+ class="UnitSetting"
+ >
+ <label
+ :for="path"
+ class="size-label"
+ >
+ <slot />
+ </label>
+ {{ ' ' }}
+ <span class="no-break">
+ <input
+ :id="path"
+ class="input number-input"
+ type="number"
+ :step="step"
+ :disabled="disabled"
+ :min="min || 0"
+ :value="stateValue"
+ @change="updateValue"
+ >
+ <Select
+ :id="path"
+ :model-value="stateUnit"
+ :disabled="disabled"
+ class="unit-input unstyled"
+ @change="updateUnit"
+ >
+ <option
+ v-for="option in units"
+ :key="option"
+ :value="option"
+ >
+ {{ getUnitString(option) }}
+ </option>
+ </Select>
+ </span>
+ {{ ' ' }}
+ <ModifiedIndicator
+ :changed="isChanged"
+ :onclick="reset"
+ />
+ </span>
+</template>
+
+<script src="./unit_setting.js"></script>
+
+<style lang="scss">
+.UnitSetting {
+ .no-break {
+ display: inline-block;
+ }
+
+ .number-input {
+ max-width: 6.5em;
+ text-align: right;
+ }
+
+ .unit-input,
+ .unit-input select {
+ min-width: 4em;
+ width: auto;
+ }
+}
+
+</style>
diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
@@ -4,8 +4,9 @@ import AsyncComponentError from 'src/components/async_component_error/async_comp
import getResettableAsyncComponent from 'src/services/resettable_async_component.js'
import Popover from '../popover/popover.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
+import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { cloneDeep } from 'lodash'
+import { cloneDeep, isEqual } from 'lodash'
import {
newImporter,
newExporter
@@ -53,8 +54,17 @@ const SettingsModal = {
Modal,
Popover,
Checkbox,
- SettingsModalContent: getResettableAsyncComponent(
- () => import('./settings_modal_content.vue'),
+ ConfirmModal,
+ SettingsModalUserContent: getResettableAsyncComponent(
+ () => import('./settings_modal_user_content.vue'),
+ {
+ loadingComponent: PanelLoading,
+ errorComponent: AsyncComponentError,
+ delay: 0
+ }
+ ),
+ SettingsModalAdminContent: getResettableAsyncComponent(
+ () => import('./settings_modal_admin_content.vue'),
{
loadingComponent: PanelLoading,
errorComponent: AsyncComponentError,
@@ -147,6 +157,12 @@ const SettingsModal = {
PLEROMAFE_SETTINGS_MINOR_VERSION
]
return clone
+ },
+ resetAdminDraft () {
+ this.$store.commit('resetAdminDraft')
+ },
+ pushAdminDraft () {
+ this.$store.dispatch('pushAdminDraft')
}
},
computed: {
@@ -156,8 +172,14 @@ const SettingsModal = {
modalActivated () {
return this.$store.state.interface.settingsModalState !== 'hidden'
},
- modalOpenedOnce () {
- return this.$store.state.interface.settingsModalLoaded
+ modalMode () {
+ return this.$store.state.interface.settingsModalMode
+ },
+ modalOpenedOnceUser () {
+ return this.$store.state.interface.settingsModalLoadedUser
+ },
+ modalOpenedOnceAdmin () {
+ return this.$store.state.interface.settingsModalLoadedAdmin
},
modalPeeked () {
return this.$store.state.interface.settingsModalState === 'minimized'
@@ -167,9 +189,14 @@ const SettingsModal = {
return this.$store.state.config.expertLevel > 0
},
set (value) {
- console.log(value)
this.$store.dispatch('setOption', { name: 'expertLevel', value: value ? 1 : 0 })
}
+ },
+ adminDraftAny () {
+ return !isEqual(
+ this.$store.state.adminSettings.config,
+ this.$store.state.adminSettings.draft
+ )
}
}
}
diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss
@@ -1,40 +1,42 @@
-@import 'src/_variables.scss';
.settings-modal {
overflow: hidden;
+ h4 {
+ margin-bottom: 0.5em;
+ }
+
.setting-list,
.option-list {
list-style-type: none;
padding-left: 2em;
+
+ .btn:not(.dropdown-button) {
+ padding: 0 2em;
+ }
+
li {
margin-bottom: 0.5em;
}
+
.suboptions {
- margin-top: 0.3em
+ margin-top: 0.3em;
}
- }
- &.peek {
- .settings-modal-panel {
- /* Explanation:
- * Modal is positioned vertically centered.
- * 100vh - 100% = Distance between modal's top+bottom boundaries and screen
- * (100vh - 100%) / 2 = Distance between bottom (or top) boundary and screen
- * + 100% - we move modal completely off-screen, it's top boundary touches
- * bottom of the screen
- * - 50px - leaving tiny amount of space so that titlebar + tiny amount of modal is visible
- */
- transform: translateY(calc(((100vh - 100%) / 2 + 100%) - 50px));
+ &.two-column {
+ column-count: 2;
- @media all and (max-width: 800px) {
- /* For mobile, the modal takes 100% of the available screen.
- This ensures the minimized modal is always 50px above the browser bottom bar regardless of whether or not it is visible.
- */
- transform: translateY(calc(100% - 50px));
+ > li {
+ break-inside: avoid;
}
}
}
+ .setting-description {
+ margin-top: 0.2em;
+ margin-bottom: 2em;
+ font-size: 70%;
+ }
+
.settings-modal-panel {
overflow: hidden;
transition: transform;
@@ -55,14 +57,15 @@
.btn {
min-height: 2em;
- min-width: 10em;
- padding: 0 2em;
}
}
}
.settings-footer {
display: flex;
+ flex-wrap: wrap;
+ line-height: 2;
+
>* {
margin-right: 0.5em;
}
@@ -72,4 +75,43 @@
flex-grow: 1;
}
}
+
+ &.-mobile {
+ .setting-list,
+ .option-list {
+ padding-left: 0.25em;
+
+ > li {
+ margin: 1em 0;
+ line-height: 1.5em;
+ vertical-align: center;
+ }
+
+ &.two-column {
+ column-count: 1;
+ }
+ }
+ }
+
+ &.peek {
+ .settings-modal-panel {
+ /* Explanation:
+ * Modal is positioned vertically centered.
+ * 100vh - 100% = Distance between modal's top+bottom boundaries and screen
+ * (100vh - 100%) / 2 = Distance between bottom (or top) boundary and screen
+ * + 100% - we move modal completely off-screen, it's top boundary touches
+ * bottom of the screen
+ * - 50px - leaving tiny amount of space so that titlebar + tiny amount of modal is visible
+ */
+ transform: translateY(calc(((100vh - 100%) / 2 + 100%) - 50px));
+
+ @media all and (max-width: 800px) {
+ /* For mobile, the modal takes 100% of the available screen.
+ This ensures the minimized modal is always 50px above the browser bottom
+ bar regardless of whether or not it is visible.
+ */
+ transform: translateY(calc(100% - 50px));
+ }
+ }
+ }
}
diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue
@@ -7,14 +7,14 @@
>
<div class="settings-modal-panel panel">
<div class="panel-heading">
- <span class="title">
- {{ $t('settings.settings') }}
- </span>
+ <h1 class="title">
+ {{ modalMode === 'user' ? $t('settings.settings') : $t('admin_dash.window_title') }}
+ </h1>
<transition name="fade">
<div
v-if="currentSaveStateNotice"
class="alert"
- :class="{ transparent: !currentSaveStateNotice.error, error: currentSaveStateNotice.error}"
+ :class="{ success: !currentSaveStateNotice.error, error: currentSaveStateNotice.error}"
@click.prevent
>
{{ currentSaveStateNotice.error ? $t('settings.saving_err') : $t('settings.saving_ok') }}
@@ -42,10 +42,12 @@
</button>
</div>
<div class="panel-body">
- <SettingsModalContent v-if="modalOpenedOnce" />
+ <SettingsModalUserContent v-if="modalMode === 'user' && modalOpenedOnceUser" />
+ <SettingsModalAdminContent v-if="modalMode === 'admin' && modalOpenedOnceAdmin" />
</div>
- <div class="panel-footer settings-footer">
+ <div class="panel-footer settings-footer -flexible-height">
<Popover
+ v-if="modalMode === 'user'"
class="export"
trigger="click"
placement="top"
@@ -53,7 +55,7 @@
:bound-to="{ x: 'container' }"
remove-padding
>
- <template v-slot:trigger>
+ <template #trigger>
<button
class="btn button-default"
:title="$t('general.close')"
@@ -65,10 +67,10 @@
/>
</button>
</template>
- <template v-slot:content="{close}">
+ <template #content="{close}">
<div class="dropdown-menu">
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
@click.prevent="backup"
@click="close"
>
@@ -78,7 +80,7 @@
/><span>{{ $t("settings.file_export_import.backup_settings") }}</span>
</button>
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
@click.prevent="backupWithTheme"
@click="close"
>
@@ -88,7 +90,7 @@
/><span>{{ $t("settings.file_export_import.backup_settings_theme") }}</span>
</button>
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
@click.prevent="restore"
@click="close"
>
@@ -107,12 +109,59 @@
>
{{ $t("settings.expert_mode") }}
</Checkbox>
+ <span v-if="modalMode === 'admin'">
+ <i18n-t
+ scope="global"
+ keypath="admin_dash.wip_notice"
+ >
+ <template #adminFeLink>
+ <a
+ href="/pleroma/admin/#/login-pleroma"
+ target="_blank"
+ >
+ {{ $t("admin_dash.old_ui_link") }}
+ </a>
+ </template>
+ </i18n-t>
+ </span>
<span
id="unscrolled-content"
class="extra-content"
/>
+ <span
+ v-if="modalMode === 'admin'"
+ class="admin-buttons"
+ >
+ <button
+ class="button-default btn"
+ :disabled="!adminDraftAny"
+ @click="resetAdminDraft"
+ >
+ {{ $t("admin_dash.reset_all") }}
+ </button>
+ {{ ' ' }}
+ <button
+ class="button-default btn"
+ :disabled="!adminDraftAny"
+ @click="pushAdminDraft"
+ >
+ {{ $t("admin_dash.commit_all") }}
+ </button>
+ </span>
</div>
</div>
+ <teleport to="#modal">
+ <ConfirmModal
+ v-if="$store.state.interface.temporaryChangesTimeoutId"
+ :title="$t('settings.confirm_new_setting')"
+ :cancel-text="$t('settings.revert')"
+ :confirm-text="$t('settings.confirm')"
+ @cancelled="$store.state.interface.temporaryChangesRevert"
+ @accepted="$store.state.interface.temporaryChangesConfirm"
+ >
+ {{ $t('settings.confirm_new_question') }}
+ </ConfirmModal>
+ </teleport>
</Modal>
</template>
diff --git a/src/components/settings_modal/settings_modal_admin_content.js b/src/components/settings_modal/settings_modal_admin_content.js
@@ -0,0 +1,95 @@
+import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
+
+import InstanceTab from './admin_tabs/instance_tab.vue'
+import LimitsTab from './admin_tabs/limits_tab.vue'
+import FrontendsTab from './admin_tabs/frontends_tab.vue'
+import EmojiTab from './admin_tabs/emoji_tab.vue'
+
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faWrench,
+ faHand,
+ faLaptopCode,
+ faPaintBrush,
+ faBell,
+ faDownload,
+ faEyeSlash,
+ faInfo
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faWrench,
+ faHand,
+ faLaptopCode,
+ faPaintBrush,
+ faBell,
+ faDownload,
+ faEyeSlash,
+ faInfo
+)
+
+const SettingsModalAdminContent = {
+ components: {
+ TabSwitcher,
+
+ InstanceTab,
+ LimitsTab,
+ FrontendsTab,
+ EmojiTab
+ },
+ computed: {
+ user () {
+ return this.$store.state.users.currentUser
+ },
+ isLoggedIn () {
+ return !!this.$store.state.users.currentUser
+ },
+ open () {
+ return this.$store.state.interface.settingsModalState !== 'hidden'
+ },
+ bodyLock () {
+ return this.$store.state.interface.settingsModalState === 'visible'
+ },
+ adminDbLoaded () {
+ return this.$store.state.adminSettings.loaded
+ },
+ adminDescriptionsLoaded () {
+ return this.$store.state.adminSettings.descriptions !== null
+ },
+ noDb () {
+ return this.$store.state.adminSettings.dbConfigEnabled === false
+ }
+ },
+ created () {
+ if (this.user.rights.admin) {
+ this.$store.dispatch('loadAdminStuff')
+ }
+ },
+ methods: {
+ onOpen () {
+ const targetTab = this.$store.state.interface.settingsModalTargetTab
+ // We're being told to open in specific tab
+ if (targetTab) {
+ const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => {
+ return elm.props && elm.props['data-tab-name'] === targetTab
+ })
+ if (tabIndex >= 0) {
+ this.$refs.tabSwitcher.setTab(tabIndex)
+ }
+ }
+ // Clear the state of target tab, so that next time settings is opened
+ // it doesn't force it.
+ this.$store.dispatch('clearSettingsModalTargetTab')
+ }
+ },
+ mounted () {
+ this.onOpen()
+ },
+ watch: {
+ open: function (value) {
+ if (value) this.onOpen()
+ }
+ }
+}
+
+export default SettingsModalAdminContent
diff --git a/src/components/settings_modal/settings_modal_admin_content.scss b/src/components/settings_modal/settings_modal_admin_content.scss
@@ -0,0 +1,48 @@
+.settings_tab-switcher {
+ height: 100%;
+
+ .setting-item {
+ border-bottom: 2px solid var(--border);
+ margin: 1em 1em 1.4em;
+ padding-bottom: 1.4em;
+
+ > div,
+ > label {
+ display: block;
+ margin-bottom: 0.5em;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ .select-multiple {
+ margin-top: 0.5em;
+ display: flex;
+ flex-direction: column;
+
+ .option-list {
+ margin: 0;
+ margin-top: 0.5em;
+ padding-left: 0.5em;
+ }
+ }
+
+ &:last-child {
+ border-bottom: none;
+ padding-bottom: 0;
+ margin-bottom: 1em;
+ }
+
+ textarea {
+ width: 100%;
+ max-width: 100%;
+ height: 100px;
+ }
+
+ .unavailable,
+ .unavailable svg {
+ color: var(--cRed);
+ }
+ }
+}
diff --git a/src/components/settings_modal/settings_modal_admin_content.vue b/src/components/settings_modal/settings_modal_admin_content.vue
@@ -0,0 +1,79 @@
+<template>
+ <tab-switcher
+ v-if="adminDescriptionsLoaded && (noDb || adminDbLoaded)"
+ ref="tabSwitcher"
+ class="settings_tab-switcher"
+ :side-tab-bar="true"
+ :scrollable-tabs="true"
+ :render-only-focused="true"
+ :body-scroll-lock="bodyLock"
+ >
+ <div
+ v-if="noDb"
+ :label="$t('admin_dash.tabs.nodb')"
+ icon="exclamation-triangle"
+ data-tab-name="nodb-notice"
+ >
+ <div :label="$t('admin_dash.tabs.nodb')">
+ <div class="setting-item">
+ <h2>{{ $t('admin_dash.nodb.heading') }}</h2>
+ <i18n-t
+ scope="global"
+ keypath="admin_dash.nodb.text"
+ >
+ <template #documentation>
+ <a
+ href="https://docs-develop.pleroma.social/backend/configuration/howto_database_config/"
+ target="_blank"
+ >
+ {{ $t("admin_dash.nodb.documentation") }}
+ </a>
+ </template>
+ <template #property>
+ <code>config :pleroma, configurable_from_database</code>
+ </template>
+ <template #value>
+ <code>true</code>
+ </template>
+ </i18n-t>
+ <p>{{ $t('admin_dash.nodb.text2') }}</p>
+ </div>
+ </div>
+ </div>
+ <div
+ v-if="adminDbLoaded"
+ :label="$t('admin_dash.tabs.instance')"
+ icon="wrench"
+ data-tab-name="general"
+ >
+ <InstanceTab />
+ </div>
+ <div
+ v-if="adminDbLoaded"
+ :label="$t('admin_dash.tabs.limits')"
+ icon="hand"
+ data-tab-name="limits"
+ >
+ <LimitsTab />
+ </div>
+ <div
+ :label="$t('admin_dash.tabs.frontends')"
+ icon="laptop-code"
+ data-tab-name="frontends"
+ >
+ <FrontendsTab />
+ </div>
+
+ <div
+ :label="$t('admin_dash.tabs.emoji')"
+ icon="face-smile-beam"
+ data-tab-name="emoji"
+ >
+ <EmojiTab />
+ </div>
+ </tab-switcher>
+</template>
+
+<script src="./settings_modal_admin_content.js"></script>
+
+<style src="./settings_modal_admin_content.scss" lang="scss"></style>
diff --git a/src/components/settings_modal/settings_modal_content.js b/src/components/settings_modal/settings_modal_content.js
@@ -1,88 +0,0 @@
-import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
-
-import DataImportExportTab from './tabs/data_import_export_tab.vue'
-import MutesAndBlocksTab from './tabs/mutes_and_blocks_tab.vue'
-import NotificationsTab from './tabs/notifications_tab.vue'
-import FilteringTab from './tabs/filtering_tab.vue'
-import SecurityTab from './tabs/security_tab/security_tab.vue'
-import ProfileTab from './tabs/profile_tab.vue'
-import GeneralTab from './tabs/general_tab.vue'
-import VersionTab from './tabs/version_tab.vue'
-import ThemeTab from './tabs/theme_tab/theme_tab.vue'
-
-import { library } from '@fortawesome/fontawesome-svg-core'
-import {
- faWrench,
- faUser,
- faFilter,
- faPaintBrush,
- faBell,
- faDownload,
- faEyeSlash,
- faInfo
-} from '@fortawesome/free-solid-svg-icons'
-
-library.add(
- faWrench,
- faUser,
- faFilter,
- faPaintBrush,
- faBell,
- faDownload,
- faEyeSlash,
- faInfo
-)
-
-const SettingsModalContent = {
- components: {
- TabSwitcher,
-
- DataImportExportTab,
- MutesAndBlocksTab,
- NotificationsTab,
- FilteringTab,
- SecurityTab,
- ProfileTab,
- GeneralTab,
- VersionTab,
- ThemeTab
- },
- computed: {
- isLoggedIn () {
- return !!this.$store.state.users.currentUser
- },
- open () {
- return this.$store.state.interface.settingsModalState !== 'hidden'
- },
- bodyLock () {
- return this.$store.state.interface.settingsModalState === 'visible'
- }
- },
- methods: {
- onOpen () {
- const targetTab = this.$store.state.interface.settingsModalTargetTab
- // We're being told to open in specific tab
- if (targetTab) {
- const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => {
- return elm.props && elm.props['data-tab-name'] === targetTab
- })
- if (tabIndex >= 0) {
- this.$refs.tabSwitcher.setTab(tabIndex)
- }
- }
- // Clear the state of target tab, so that next time settings is opened
- // it doesn't force it.
- this.$store.dispatch('clearSettingsModalTargetTab')
- }
- },
- mounted () {
- this.onOpen()
- },
- watch: {
- open: function (value) {
- if (value) this.onOpen()
- }
- }
-}
-
-export default SettingsModalContent
diff --git a/src/components/settings_modal/settings_modal_content.scss b/src/components/settings_modal/settings_modal_content.scss
@@ -1,54 +0,0 @@
-@import 'src/_variables.scss';
-.settings_tab-switcher {
- height: 100%;
-
- .setting-item {
- border-bottom: 2px solid var(--fg, $fallback--fg);
- margin: 1em 1em 1.4em;
- padding-bottom: 1.4em;
-
- > div,
- > label {
- display: block;
- margin-bottom: .5em;
- &:last-child {
- margin-bottom: 0;
- }
- }
-
- .select-multiple {
- display: flex;
-
- .option-list {
- margin: 0;
- padding-left: .5em;
- }
- }
-
- &:last-child {
- border-bottom: none;
- padding-bottom: 0;
- margin-bottom: 1em;
- }
-
- select {
- min-width: 10em;
- }
-
- textarea {
- width: 100%;
- max-width: 100%;
- height: 100px;
- }
-
- .unavailable,
- .unavailable svg {
- color: var(--cRed, $fallback--cRed);
- color: $fallback--cRed;
- }
-
- .number-input {
- max-width: 6em;
- }
- }
-}
diff --git a/src/components/settings_modal/settings_modal_content.vue b/src/components/settings_modal/settings_modal_content.vue
@@ -1,83 +0,0 @@
-<template>
- <tab-switcher
- ref="tabSwitcher"
- class="settings_tab-switcher"
- :side-tab-bar="true"
- :scrollable-tabs="true"
- :body-scroll-lock="bodyLock"
- >
- <div
- :label="$t('settings.general')"
- icon="wrench"
- data-tab-name="general"
- >
- <GeneralTab />
- </div>
- <div
- v-if="isLoggedIn"
- :label="$t('settings.profile_tab')"
- icon="user"
- data-tab-name="profile"
- >
- <ProfileTab />
- </div>
- <div
- v-if="isLoggedIn"
- :label="$t('settings.security_tab')"
- icon="lock"
- data-tab-name="security"
- >
- <SecurityTab />
- </div>
- <div
- :label="$t('settings.filtering')"
- icon="filter"
- data-tab-name="filtering"
- >
- <FilteringTab />
- </div>
- <div
- :label="$t('settings.theme')"
- icon="paint-brush"
- data-tab-name="theme"
- >
- <ThemeTab />
- </div>
- <div
- v-if="isLoggedIn"
- :label="$t('settings.notifications')"
- icon="bell"
- data-tab-name="notifications"
- >
- <NotificationsTab />
- </div>
- <div
- v-if="isLoggedIn"
- :label="$t('settings.data_import_export_tab')"
- icon="download"
- data-tab-name="dataImportExport"
- >
- <DataImportExportTab />
- </div>
- <div
- v-if="isLoggedIn"
- :label="$t('settings.mutes_and_blocks')"
- :fullHeight="true"
- icon="eye-slash"
- data-tab-name="mutesAndBlocks"
- >
- <MutesAndBlocksTab />
- </div>
- <div
- :label="$t('settings.version.title')"
- icon="info"
- data-tab-name="version"
- >
- <VersionTab />
- </div>
- </tab-switcher>
-</template>
-
-<script src="./settings_modal_content.js"></script>
-
-<style src="./settings_modal_content.scss" lang="scss"></style>
diff --git a/src/components/settings_modal/settings_modal_user_content.js b/src/components/settings_modal/settings_modal_user_content.js
@@ -0,0 +1,102 @@
+import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
+
+import DataImportExportTab from './tabs/data_import_export_tab.vue'
+import MutesAndBlocksTab from './tabs/mutes_and_blocks_tab.vue'
+import NotificationsTab from './tabs/notifications_tab.vue'
+import FilteringTab from './tabs/filtering_tab.vue'
+import SecurityTab from './tabs/security_tab/security_tab.vue'
+import ProfileTab from './tabs/profile_tab.vue'
+import GeneralTab from './tabs/general_tab.vue'
+import AppearanceTab from './tabs/appearance_tab.vue'
+import VersionTab from './tabs/version_tab.vue'
+import ThemeTab from './tabs/theme_tab/theme_tab.vue'
+import StyleTab from './tabs/style_tab/style_tab.vue'
+
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faWrench,
+ faUser,
+ faFilter,
+ faPaintBrush,
+ faPalette,
+ faBell,
+ faDownload,
+ faEyeSlash,
+ faInfo,
+ faWindowRestore
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faWrench,
+ faUser,
+ faFilter,
+ faPaintBrush,
+ faPalette,
+ faBell,
+ faDownload,
+ faEyeSlash,
+ faInfo,
+ faWindowRestore
+)
+
+const SettingsModalContent = {
+ components: {
+ TabSwitcher,
+
+ DataImportExportTab,
+ MutesAndBlocksTab,
+ NotificationsTab,
+ FilteringTab,
+ SecurityTab,
+ ProfileTab,
+ GeneralTab,
+ AppearanceTab,
+ StyleTab,
+ VersionTab,
+ ThemeTab
+ },
+ computed: {
+ isLoggedIn () {
+ return !!this.$store.state.users.currentUser
+ },
+ open () {
+ return this.$store.state.interface.settingsModalState !== 'hidden'
+ },
+ bodyLock () {
+ return this.$store.state.interface.settingsModalState === 'visible'
+ },
+ expertLevel () {
+ return this.$store.state.config.expertLevel
+ },
+ isMobileLayout () {
+ return this.$store.state.interface.layoutType === 'mobile'
+ }
+ },
+ methods: {
+ onOpen () {
+ const targetTab = this.$store.state.interface.settingsModalTargetTab
+ // We're being told to open in specific tab
+ if (targetTab) {
+ const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => {
+ return elm.props && elm.props['data-tab-name'] === targetTab
+ })
+ if (tabIndex >= 0) {
+ this.$refs.tabSwitcher.setTab(tabIndex)
+ }
+ }
+ // Clear the state of target tab, so that next time settings is opened
+ // it doesn't force it.
+ this.$store.dispatch('clearSettingsModalTargetTab')
+ }
+ },
+ mounted () {
+ this.onOpen()
+ },
+ watch: {
+ open: function (value) {
+ if (value) this.onOpen()
+ }
+ }
+}
+
+export default SettingsModalContent
diff --git a/src/components/settings_modal/settings_modal_user_content.scss b/src/components/settings_modal/settings_modal_user_content.scss
@@ -0,0 +1,62 @@
+.settings_tab-switcher {
+ height: 100%;
+
+ h1 {
+ margin-bottom: 0.5em;
+ margin-top: 0.5em;
+ }
+
+ h4 {
+ margin-bottom: 0;
+ margin-top: 0.25em;
+ }
+
+ h5 {
+ margin-bottom: 0;
+ margin-top: 0.25em;
+ }
+
+ .setting-item {
+ border-bottom: 2px solid var(--border);
+ margin: 1em 1em 1.4em;
+ padding-bottom: 1.4em;
+
+ > div,
+ > label {
+ margin-bottom: 0.5em;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ .select-multiple {
+ margin-top: 1em;
+ display: flex;
+ flex-direction: column;
+
+ .option-list {
+ margin: 0;
+ margin-top: 0.5em;
+ padding-left: 0.5em;
+ }
+ }
+
+ &:last-child {
+ border-bottom: none;
+ padding-bottom: 0;
+ margin-bottom: 1em;
+ }
+
+ textarea {
+ width: 100%;
+ max-width: 100%;
+ height: 100px;
+ }
+
+ .unavailable,
+ .unavailable svg {
+ color: var(--cRed);
+ }
+ }
+}
diff --git a/src/components/settings_modal/settings_modal_user_content.vue b/src/components/settings_modal/settings_modal_user_content.vue
@@ -0,0 +1,102 @@
+<template>
+ <tab-switcher
+ ref="tabSwitcher"
+ class="settings_tab-switcher"
+ :side-tab-bar="true"
+ :scrollable-tabs="true"
+ :body-scroll-lock="bodyLock"
+ >
+ <div
+ :label="$t('settings.general')"
+ icon="wrench"
+ data-tab-name="general"
+ >
+ <GeneralTab />
+ </div>
+ <div
+ :label="$t('settings.appearance')"
+ icon="window-restore"
+ data-tab-name="appearance"
+ :delay-render="true"
+ >
+ <AppearanceTab />
+ </div>
+ <div
+ v-if="expertLevel > 0 && !isMobileLayout"
+ :label="$t('settings.style.themes3.editor.title')"
+ icon="palette"
+ data-tab-name="style"
+ :delay-render="true"
+ >
+ <StyleTab />
+ </div>
+ <div
+ v-if="expertLevel > 0 && !isMobileLayout"
+ :label="$t('settings.theme_old')"
+ icon="paint-brush"
+ data-tab-name="theme"
+ :delay-render="true"
+ >
+ <ThemeTab />
+ </div>
+ <div
+ v-if="isLoggedIn"
+ :label="$t('settings.profile_tab')"
+ icon="user"
+ data-tab-name="profile"
+ >
+ <ProfileTab />
+ </div>
+ <div
+ v-if="isLoggedIn"
+ :label="$t('settings.notifications')"
+ icon="bell"
+ data-tab-name="notifications"
+ >
+ <NotificationsTab />
+ </div>
+ <div
+ v-if="isLoggedIn"
+ :label="$t('settings.security_tab')"
+ icon="lock"
+ data-tab-name="security"
+ >
+ <SecurityTab />
+ </div>
+ <div
+ :label="$t('settings.filtering')"
+ icon="filter"
+ data-tab-name="filtering"
+ >
+ <FilteringTab />
+ </div>
+ <div
+ v-if="isLoggedIn"
+ :label="$t('settings.mutes_and_blocks')"
+ :fullHeight="true"
+ icon="eye-slash"
+ data-tab-name="mutesAndBlocks"
+ >
+ <MutesAndBlocksTab />
+ </div>
+ <div
+ v-if="isLoggedIn"
+ :label="$t('settings.data_import_export_tab')"
+ icon="download"
+ data-tab-name="dataImportExport"
+ >
+ <DataImportExportTab />
+ </div>
+ <div
+ :label="$t('settings.version.title')"
+ icon="info"
+ data-tab-name="version"
+ >
+ <VersionTab />
+ </div>
+ </tab-switcher>
+</template>
+
+<script src="./settings_modal_user_content.js"></script>
+
+<style src="./settings_modal_user_content.scss" lang="scss"></style>
diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js
@@ -0,0 +1,422 @@
+import BooleanSetting from '../helpers/boolean_setting.vue'
+import ChoiceSetting from '../helpers/choice_setting.vue'
+import IntegerSetting from '../helpers/integer_setting.vue'
+import FloatSetting from '../helpers/float_setting.vue'
+import UnitSetting, { defaultHorizontalUnits } from '../helpers/unit_setting.vue'
+import PaletteEditor from 'src/components/palette_editor/palette_editor.vue'
+
+import FontControl from 'src/components/font_control/font_control.vue'
+
+import { normalizeThemeData } from 'src/modules/interface'
+
+import { newImporter } from 'src/services/export_import/export_import.js'
+import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
+import { init } from 'src/services/theme_data/theme_data_3.service.js'
+import {
+ getCssRules,
+ getScopedVersion
+} from 'src/services/theme_data/css_utils.js'
+import { deserialize } from 'src/services/theme_data/iss_deserializer.js'
+
+import SharedComputedObject from '../helpers/shared_computed_object.js'
+import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faGlobe
+} from '@fortawesome/free-solid-svg-icons'
+
+import Preview from './theme_tab/theme_preview.vue'
+
+// helper for debugging
+// eslint-disable-next-line no-unused-vars
+const toValue = (x) => JSON.parse(JSON.stringify(x === undefined ? 'null' : x))
+
+library.add(
+ faGlobe
+)
+
+const AppearanceTab = {
+ data () {
+ return {
+ availableThemesV3: [],
+ availableThemesV2: [],
+ bundledPalettes: [],
+ compilationCache: {},
+ fileImporter: newImporter({
+ accept: '.json, .piss',
+ validator: this.importValidator,
+ onImport: this.onImport,
+ parser: this.importParser,
+ onImportFailure: this.onImportFailure
+ }),
+ palettesKeys: [
+ 'bg',
+ 'fg',
+ 'link',
+ 'text',
+ 'cRed',
+ 'cGreen',
+ 'cBlue',
+ 'cOrange'
+ ],
+ userPalette: {},
+ intersectionObserver: null,
+ thirdColumnModeOptions: ['none', 'notifications', 'postform'].map(mode => ({
+ key: mode,
+ value: mode,
+ label: this.$t(`settings.third_column_mode_${mode}`)
+ })),
+ forcedRoundnessOptions: ['disabled', 'sharp', 'nonsharp', 'round'].map((mode, i) => ({
+ key: mode,
+ value: i - 1,
+ label: this.$t(`settings.style.themes3.hacks.forced_roundness_mode_${mode}`)
+ })),
+ underlayOverrideModes: ['none', 'opaque', 'transparent'].map((mode, i) => ({
+ key: mode,
+ value: mode,
+ label: this.$t(`settings.style.themes3.hacks.underlay_override_mode_${mode}`)
+ }))
+ }
+ },
+ components: {
+ BooleanSetting,
+ ChoiceSetting,
+ IntegerSetting,
+ FloatSetting,
+ UnitSetting,
+ ProfileSettingIndicator,
+ FontControl,
+ Preview,
+ PaletteEditor
+ },
+ mounted () {
+ this.$store.dispatch('getThemeData')
+
+ const updateIndex = (resource) => {
+ const capitalizedResource = resource[0].toUpperCase() + resource.slice(1)
+ const currentIndex = this.$store.state.instance[`${resource}sIndex`]
+
+ let promise
+ if (currentIndex) {
+ promise = Promise.resolve(currentIndex)
+ } else {
+ promise = this.$store.dispatch(`fetch${capitalizedResource}sIndex`)
+ }
+
+ return promise.then(index => {
+ return Object
+ .entries(index)
+ .map(([k, func]) => [k, func()])
+ })
+ }
+
+ updateIndex('style').then(styles => {
+ styles.forEach(([key, stylePromise]) => stylePromise.then(data => {
+ const meta = data.find(x => x.component === '@meta')
+ this.availableThemesV3.push({ key, data, name: meta.directives.name, version: 'v3' })
+ }))
+ })
+
+ updateIndex('theme').then(themes => {
+ themes.forEach(([key, themePromise]) => themePromise.then(data => {
+ if (!data) {
+ console.warn(`Theme with key ${key} is empty or malformed`)
+ } else if (Array.isArray(data)) {
+ console.warn(`Theme with key ${key} is a v1 theme and should be moved to static/palettes/index.json`)
+ } else if (!data.source && !data.theme) {
+ console.warn(`Theme with key ${key} is malformed`)
+ } else {
+ this.availableThemesV2.push({ key, data, name: data.name, version: 'v2' })
+ }
+ }))
+ })
+
+ this.userPalette = this.$store.state.interface.paletteDataUsed || {}
+
+ updateIndex('palette').then(bundledPalettes => {
+ bundledPalettes.forEach(([key, palettePromise]) => palettePromise.then(v => {
+ let palette
+ if (Array.isArray(v)) {
+ const [
+ name,
+ bg,
+ fg,
+ text,
+ link,
+ cRed = '#FF0000',
+ cGreen = '#00FF00',
+ cBlue = '#0000FF',
+ cOrange = '#E3FF00'
+ ] = v
+ palette = { key, name, bg, fg, text, link, cRed, cBlue, cGreen, cOrange }
+ } else {
+ palette = { key, ...v }
+ }
+ if (!palette.key.startsWith('style.')) {
+ this.bundledPalettes.push(palette)
+ }
+ }))
+ })
+
+ if (window.IntersectionObserver) {
+ this.intersectionObserver = new IntersectionObserver((entries, observer) => {
+ entries.forEach(({ target, isIntersecting }) => {
+ if (!isIntersecting) return
+ const theme = this.availableStyles.find(x => x.key === target.dataset.themeKey)
+ this.$nextTick(() => {
+ if (theme) theme.ready = true
+ })
+ observer.unobserve(target)
+ })
+ }, {
+ root: this.$refs.themeList
+ })
+ }
+ },
+ updated () {
+ this.$nextTick(() => {
+ this.$refs.themeList.querySelectorAll('.theme-preview').forEach(node => {
+ this.intersectionObserver.observe(node)
+ })
+ })
+ },
+ watch: {
+ paletteDataUsed () {
+ this.userPalette = this.paletteDataUsed || {}
+ }
+ },
+ computed: {
+ paletteDataUsed () {
+ return this.$store.state.interface.paletteDataUsed
+ },
+ availableStyles () {
+ return [
+ ...this.availableThemesV3,
+ ...this.availableThemesV2
+ ]
+ },
+ availablePalettes () {
+ return [
+ ...this.bundledPalettes,
+ ...this.stylePalettes
+ ]
+ },
+ stylePalettes () {
+ const ruleset = this.$store.state.interface.styleDataUsed || []
+ if (!ruleset && ruleset.length === 0) return
+ const meta = ruleset.find(x => x.component === '@meta')
+ const result = ruleset.filter(x => x.component.startsWith('@palette'))
+ .map(x => {
+ const { variant, directives } = x
+ const {
+ bg,
+ fg,
+ text,
+ link,
+ accent,
+ cRed,
+ cBlue,
+ cGreen,
+ cOrange,
+ wallpaper
+ } = directives
+
+ const result = {
+ name: `${meta.directives.name || this.$t('settings.style.themes3.palette.imported')}: ${variant}`,
+ key: `style.${variant.toLowerCase().replace(/ /g, '_')}`,
+ bg,
+ fg,
+ text,
+ link,
+ accent,
+ cRed,
+ cBlue,
+ cGreen,
+ cOrange,
+ wallpaper
+ }
+ return Object.fromEntries(Object.entries(result).filter(([k, v]) => v))
+ })
+ return result
+ },
+ noIntersectionObserver () {
+ return !window.IntersectionObserver
+ },
+ horizontalUnits () {
+ return defaultHorizontalUnits
+ },
+ fontsOverride () {
+ return this.$store.getters.mergedConfig.fontsOverride
+ },
+ columns () {
+ const mode = this.$store.getters.mergedConfig.thirdColumnMode
+
+ const notif = mode === 'none' ? [] : ['notifs']
+
+ if (this.$store.getters.mergedConfig.sidebarRight || mode === 'postform') {
+ return [...notif, 'content', 'sidebar']
+ } else {
+ return ['sidebar', 'content', ...notif]
+ }
+ },
+ instanceWallpaperUsed () {
+ return this.$store.state.instance.background &&
+ !this.$store.state.users.currentUser.background_image
+ },
+ language: {
+ get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
+ set: function (val) {
+ this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
+ }
+ },
+ customThemeVersion () {
+ const { themeVersion } = this.$store.state.interface
+ return themeVersion
+ },
+ isCustomThemeUsed () {
+ const { customTheme, customThemeSource } = this.mergedConfig
+ return customTheme != null || customThemeSource != null
+ },
+ isCustomStyleUsed (name) {
+ const { styleCustomData } = this.mergedConfig
+ return styleCustomData != null
+ },
+ ...SharedComputedObject()
+ },
+ methods: {
+ updateFont (key, value) {
+ this.$store.dispatch('setOption', {
+ name: 'theme3hacks',
+ value: {
+ ...this.mergedConfig.theme3hacks,
+ fonts: {
+ ...this.mergedConfig.theme3hacks.fonts,
+ [key]: value
+ }
+ }
+ })
+ },
+ importFile () {
+ this.fileImporter.importData()
+ },
+ importParser (file, filename) {
+ if (filename.endsWith('.json')) {
+ return JSON.parse(file)
+ } else if (filename.endsWith('.piss')) {
+ return deserialize(file)
+ }
+ },
+ onImport (parsed, filename) {
+ if (filename.endsWith('.json')) {
+ this.$store.dispatch('setThemeCustom', parsed.source || parsed.theme)
+ } else if (filename.endsWith('.piss')) {
+ this.$store.dispatch('setStyleCustom', parsed)
+ }
+ },
+ onImportFailure (result) {
+ console.error('Failure importing theme:', result)
+ this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_theme_imported', level: 'error' })
+ },
+ importValidator (parsed, filename) {
+ if (filename.endsWith('.json')) {
+ const version = parsed._pleroma_theme_version
+ return version >= 1 || version <= 2
+ } else if (filename.endsWith('.piss')) {
+ if (!Array.isArray(parsed)) return false
+ if (parsed.length < 1) return false
+ if (parsed.find(x => x.component === '@meta') == null) return false
+ return true
+ }
+ },
+ isThemeActive (key) {
+ return key === (this.mergedConfig.theme || this.$store.state.instance.theme)
+ },
+ isStyleActive (key) {
+ return key === (this.mergedConfig.style || this.$store.state.instance.style)
+ },
+ isPaletteActive (key) {
+ return key === (this.mergedConfig.palette || this.$store.state.instance.palette)
+ },
+ setStyle (name) {
+ this.$store.dispatch('setStyle', name)
+ },
+ setTheme (name) {
+ this.$store.dispatch('setTheme', name)
+ },
+ setPalette (name, data) {
+ this.$store.dispatch('setPalette', name)
+ this.userPalette = data
+ },
+ setPaletteCustom (data) {
+ this.$store.dispatch('setPaletteCustom', data)
+ this.userPalette = data
+ },
+ resetTheming (name) {
+ this.$store.dispatch('setStyle', 'stock')
+ },
+ previewTheme (key, version, input) {
+ let theme3
+ if (this.compilationCache[key]) {
+ theme3 = this.compilationCache[key]
+ } else if (input) {
+ if (version === 'v2') {
+ const style = normalizeThemeData(input)
+ const theme2 = convertTheme2To3(style)
+ theme3 = init({
+ inputRuleset: theme2,
+ ultimateBackgroundColor: '#000000',
+ liteMode: true,
+ debug: true,
+ onlyNormalState: true
+ })
+ } else if (version === 'v3') {
+ const palette = input.find(x => x.component === '@palette')
+ let paletteRule
+ if (palette) {
+ const { directives } = palette
+ directives.link = directives.link || directives.accent
+ directives.accent = directives.accent || directives.link
+ paletteRule = {
+ component: 'Root',
+ directives: Object.fromEntries(
+ Object
+ .entries(directives)
+ .filter(([k, v]) => k && k !== 'name')
+ .map(([k, v]) => ['--' + k, 'color | ' + v])
+ )
+ }
+ } else {
+ paletteRule = null
+ }
+
+ theme3 = init({
+ inputRuleset: [...input, paletteRule].filter(x => x),
+ ultimateBackgroundColor: '#000000',
+ liteMode: true,
+ debug: true,
+ onlyNormalState: true
+ })
+ }
+ } else {
+ theme3 = init({
+ inputRuleset: [],
+ ultimateBackgroundColor: '#000000',
+ liteMode: true,
+ debug: true,
+ onlyNormalState: true
+ })
+ }
+
+ if (!this.compilationCache[key]) {
+ this.compilationCache[key] = theme3
+ }
+
+ return getScopedVersion(
+ getCssRules(theme3.eager),
+ '#theme-preview-' + key
+ ).join('\n')
+ }
+ }
+}
+
+export default AppearanceTab
diff --git a/src/components/settings_modal/tabs/appearance_tab.scss b/src/components/settings_modal/tabs/appearance_tab.scss
@@ -0,0 +1,120 @@
+.appearance-tab {
+ .palette,
+ .theme-notice {
+ padding: 0.5em;
+ margin: 1em;
+ }
+
+ .setting-item {
+ padding-bottom: 0;
+
+ &.heading {
+ display: grid;
+ align-items: baseline;
+ grid-template-columns: 1fr auto auto auto;
+ grid-gap: 0.5em;
+
+ h2 {
+ flex: 1 0 auto;
+ }
+ }
+ }
+
+ .palettes {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-gap: 0.5em;
+
+ h4,
+ .unsupported-theme-v2,
+ .userPalette {
+ grid-column: 1 / span 2;
+ }
+ }
+
+ .palette-entry {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 0.5em;
+
+ .palette-label label {
+ text-align: center;
+ }
+
+ .palette-square {
+ flex: 0 0 auto;
+ display: inline-block;
+ min-width: 1em;
+ min-height: 1em;
+ }
+ }
+
+ .column-settings {
+ display: flex;
+ justify-content: space-evenly;
+ flex-wrap: wrap;
+ }
+
+ .column-settings .size-label {
+ display: block;
+ margin-bottom: 0.5em;
+ margin-top: 0.5em;
+ }
+
+ .modal-view.-mobile & {
+ .palette-entry {
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+
+ .palette-label {
+ line-height: 1.5em;
+ margin-top: 0.5em;
+ width: 100%;
+ }
+
+ .palette-preview {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ grid-template-rows: 1em 1em;
+ margin-bottom: 0.5em;
+ }
+ }
+
+ .theme-list {
+ list-style: none;
+ display: flex;
+ flex-wrap: wrap;
+ margin: -0.5em 0;
+ height: 25em;
+ overflow-x: hidden;
+ overflow-y: auto;
+ scrollbar-gutter: stable;
+ border-radius: var(--roundness);
+ border: 1px solid var(--border);
+ padding: 0;
+ margin-bottom: 1em;
+
+ .theme-preview {
+ font-size: 1rem; // fix for firefox
+ width: 19rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin: 0.5em;
+
+ &.placeholder {
+ opacity: 0.2;
+ }
+
+ .theme-preview-container {
+ pointer-events: none;
+ zoom: 0.5;
+ border: none;
+ border-radius: var(--roundness);
+ text-align: left;
+ }
+ }
+ }
+}
diff --git a/src/components/settings_modal/tabs/appearance_tab.vue b/src/components/settings_modal/tabs/appearance_tab.vue
@@ -0,0 +1,380 @@
+<template>
+ <div
+ class="appearance-tab"
+ :label="$t('settings.general')"
+ >
+ <div class="setting-item">
+ <h2>{{ $t('settings.theme') }}</h2>
+ <ul
+ ref="themeList"
+ class="theme-list"
+ >
+ <button
+ class="button-default theme-preview"
+ data-theme-key="stock"
+ :class="{ toggled: isStyleActive('stock') }"
+ @click="resetTheming"
+ >
+ <!-- eslint-disable vue/no-v-text-v-html-on-component -->
+ <!-- eslint-disable vue/no-v-html -->
+ <component
+ :is="'style'"
+ v-html="previewTheme('stock', 'v3')"
+ />
+ <!-- eslint-enable vue/no-v-html -->
+ <!-- eslint-enable vue/no-v-text-v-html-on-component -->
+ <preview id="theme-preview-stock" />
+ <h4 class="theme-name">
+ {{ $t('settings.style.stock_theme_used') }}
+ <span class="alert neutral version">v3</span>
+ </h4>
+ </button>
+ <button
+ v-if="isCustomThemeUsed"
+ disabled
+ class="button-default theme-preview toggled"
+ >
+ <preview />
+ <h4 class="theme-name">
+ {{ $t('settings.style.custom_theme_used') }}
+ <span class="alert neutral version">v2</span>
+ </h4>
+ </button>
+ <button
+ v-if="isCustomStyleUsed"
+ disabled
+ class="button-default theme-preview toggled"
+ >
+ <preview />
+ <h4 class="theme-name">
+ {{ $t('settings.style.custom_style_used') }}
+ <span class="alert neutral version">v3</span>
+ </h4>
+ </button>
+ <button
+ v-for="style in availableStyles"
+ :key="style.key"
+ :data-theme-key="style.key"
+ class="button-default theme-preview"
+ :class="{ toggled: isStyleActive(style.key) }"
+ @click="style.version === 'v2' ? setTheme(style.key) : setStyle(style.key)"
+ >
+ <!-- eslint-disable vue/no-v-text-v-html-on-component -->
+ <!-- eslint-disable vue/no-v-html -->
+ <div v-if="style.ready || noIntersectionObserver">
+ <component
+ :is="'style'"
+ v-html="previewTheme(style.key, style.version, style.data)"
+ />
+ </div>
+ <!-- eslint-enable vue/no-v-html -->
+ <!-- eslint-enable vue/no-v-text-v-html-on-component -->
+ <preview :id="'theme-preview-' + style.key" />
+ <h4 class="theme-name">
+ {{ style.name }}
+ <span class="alert neutral version">{{ style.version }}</span>
+ </h4>
+ </button>
+ </ul>
+ <div class="import-file-container">
+ <button
+ class="btn button-default"
+ @click="importFile"
+ >
+ <FAIcon icon="folder-open" />
+ {{ $t('settings.style.themes3.editor.load_style') }}
+ </button>
+ </div>
+ <div class="setting-item">
+ <h2>{{ $t('settings.style.themes3.palette.label') }}</h2>
+ <div class="palettes">
+ <template v-if="customThemeVersion === 'v3'">
+ <h4>{{ $t('settings.style.themes3.palette.bundled') }}</h4>
+ <button
+ v-for="p in bundledPalettes"
+ :key="p.name"
+ class="btn button-default palette-entry"
+ :class="{ toggled: isPaletteActive(p.key) }"
+ @click="() => setPalette(p.key, p)"
+ >
+ <div class="palette-label">
+ <label>
+ {{ p.name }}
+ </label>
+ </div>
+ <div class="palette-preview">
+ <span
+ v-for="c in palettesKeys"
+ :key="c"
+ class="palette-square"
+ :style="{ backgroundColor: p[c], border: '1px solid ' + (p[c] ?? 'var(--text)') }"
+ />
+ </div>
+ </button>
+ <h4 v-if="stylePalettes?.length > 0">
+ {{ $t('settings.style.themes3.palette.style') }}
+ </h4>
+ <button
+ v-for="p in stylePalettes || []"
+ :key="p.name"
+ class="btn button-default palette-entry"
+ :class="{ toggled: isPaletteActive(p.key) }"
+ @click="() => setPalette(p.key, p)"
+ >
+ <div class="palette-label">
+ <label>
+ {{ p.name ?? $t('settings.style.themes3.palette.user') }}
+ </label>
+ </div>
+ <div class="palette-preview">
+ <span
+ v-for="c in palettesKeys"
+ :key="c"
+ class="palette-square"
+ :style="{ backgroundColor: p[c], border: '1px solid ' + (p[c] ?? 'var(--text)') }"
+ />
+ </div>
+ </button>
+ <h4 v-if="expertLevel > 0">
+ {{ $t('settings.style.themes3.palette.user') }}
+ </h4>
+ <PaletteEditor
+ v-if="expertLevel > 0"
+ v-model="userPalette"
+ class="userPalette"
+ :compact="true"
+ :apply="true"
+ @applyPalette="data => setPaletteCustom(data)"
+ />
+ </template>
+ <template v-else-if="customThemeVersion === 'v2'">
+ <div class="alert neutral theme-notice unsupported-theme-v2">
+ {{ $t('settings.style.themes3.palette.v2_unsupported') }}
+ </div>
+ </template>
+ </div>
+ </div>
+ </div>
+ <div class="setting-item">
+ <h2>{{ $t('settings.scale_and_layout') }}</h2>
+ <div class="alert neutral theme-notice">
+ {{ $t("settings.style.appearance_tab_note") }}
+ </div>
+ <ul class="setting-list">
+ <li>
+ <UnitSetting
+ path="textSize"
+ :step="0.1"
+ :units="['px', 'rem']"
+ :reset-default="{ 'px': 14, 'rem': 1 }"
+ timed-apply-mode
+ >
+ {{ $t('settings.text_size') }}
+ </UnitSetting>
+ <div>
+ <small>
+ <i18n-t
+ scope="global"
+ keypath="settings.text_size_tip"
+ tag="span"
+ >
+ <code>px</code>
+ <code>rem</code>
+ </i18n-t>
+ <br>
+ <i18n-t
+ scope="global"
+ keypath="settings.text_size_tip2"
+ tag="span"
+ >
+ <code>14px</code>
+ </i18n-t>
+ </small>
+ </div>
+ </li>
+ <li>
+ <h3>{{ $t('settings.style.interface_font_user_override') }}</h3>
+ <ul class="setting-list">
+ <li>
+ <FontControl
+ :model-value="mergedConfig.theme3hacks.fonts.interface"
+ name="ui"
+ :label="$t('settings.style.fonts.components.interface')"
+ :fallback="{ family: 'sans-serif' }"
+ no-inherit="1"
+ @update:modelValue="v => updateFont('interface', v)"
+ />
+ </li>
+ <li>
+ <FontControl
+ v-if="expertLevel > 0"
+ :model-value="mergedConfig.theme3hacks.fonts.input"
+ name="input"
+ :fallback="{ family: 'inherit' }"
+ :label="$t('settings.style.fonts.components.input')"
+ @update:modelValue="v => updateFont('input', v)"
+ />
+ </li>
+ <li>
+ <FontControl
+ v-if="expertLevel > 0"
+ :model-value="mergedConfig.theme3hacks.fonts.post"
+ name="post"
+ :fallback="{ family: 'inherit' }"
+ :label="$t('settings.style.fonts.components.post')"
+ @update:modelValue="v => updateFont('post', v)"
+ />
+ </li>
+ <li>
+ <FontControl
+ v-if="expertLevel > 0"
+ :model-value="mergedConfig.theme3hacks.fonts.monospace"
+ name="postCode"
+ :fallback="{ family: 'monospace' }"
+ :label="$t('settings.style.fonts.components.monospace')"
+ @update:modelValue="v => updateFont('monospace', v)"
+ />
+ </li>
+ </ul>
+ </li>
+ <li>
+ <UnitSetting
+ path="emojiSize"
+ :step="0.1"
+ :units="['px', 'rem']"
+ :reset-default="{ 'px': 32, 'rem': 2.2 }"
+ >
+ {{ $t('settings.emoji_size') }}
+ </UnitSetting>
+ <ul
+ class="setting-list suboptions"
+ >
+ <li>
+ <FloatSetting
+ v-if="user"
+ path="emojiReactionsScale"
+ expert="1"
+ >
+ {{ $t('settings.emoji_reactions_scale') }}
+ </FloatSetting>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <UnitSetting
+ path="navbarSize"
+ :step="0.1"
+ :units="['px', 'rem']"
+ :reset-default="{ 'px': 55, 'rem': 3.5 }"
+ >
+ {{ $t('settings.navbar_size') }}
+ </UnitSetting>
+ </li>
+ <h3>{{ $t('settings.columns') }}</h3>
+ <li>
+ <UnitSetting
+ path="panelHeaderSize"
+ :step="0.1"
+ :units="['px', 'rem']"
+ :reset-default="{ 'px': 52, 'rem': 3.2 }"
+ timed-apply-mode
+ >
+ {{ $t('settings.panel_header_size') }}
+ </UnitSetting>
+ </li>
+ <li>
+ <BooleanSetting path="sidebarRight">
+ {{ $t('settings.right_sidebar') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="navbarColumnStretch">
+ {{ $t('settings.navbar_column_stretch') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <ChoiceSetting
+ v-if="user"
+ id="thirdColumnMode"
+ path="thirdColumnMode"
+ :options="thirdColumnModeOptions"
+ >
+ {{ $t('settings.third_column_mode') }}
+ </ChoiceSetting>
+ </li>
+ <li v-if="expertLevel > 0">
+ {{ $t('settings.column_sizes') }}
+ <div class="column-settings">
+ <UnitSetting
+ v-for="column in columns"
+ :key="column"
+ :path="column + 'ColumnWidth'"
+ :units="horizontalUnits"
+ expert="1"
+ >
+ {{ $t('settings.column_sizes_' + column) }}
+ </UnitSetting>
+ </div>
+ </li>
+ <li>
+ <BooleanSetting path="disableStickyHeaders">
+ {{ $t('settings.disable_sticky_headers') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="showScrollbars">
+ {{ $t('settings.show_scrollbars') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </div>
+ <div class="setting-item">
+ <h2>{{ $t('settings.visual_tweaks') }}</h2>
+ <ul class="setting-list">
+ <li>
+ <ChoiceSetting
+ id="forcedRoundness"
+ path="forcedRoundness"
+ :options="forcedRoundnessOptions"
+ >
+ {{ $t('settings.style.themes3.hacks.force_interface_roundness') }}
+ </ChoiceSetting>
+ </li>
+ <li>
+ <ChoiceSetting
+ id="underlayOverride"
+ path="theme3hacks.underlay"
+ :options="underlayOverrideModes"
+ >
+ {{ $t('settings.style.themes3.hacks.underlay_overrides') }}
+ </ChoiceSetting>
+ </li>
+ <li v-if="instanceWallpaperUsed">
+ <BooleanSetting path="hideInstanceWallpaper">
+ {{ $t('settings.hide_wallpaper') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting
+ path="forceThemeRecompilation"
+ :expert="1"
+ >
+ {{ $t('settings.force_theme_recompilation_debug') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting
+ path="themeDebug"
+ :expert="1"
+ >
+ {{ $t('settings.theme_debug') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </div>
+ </div>
+</template>
+
+<script src="./appearance_tab.js"></script>
+
+<style lang="scss" src="./appearance_tab.scss"></style>
diff --git a/src/components/settings_modal/tabs/data_import_export_tab.vue b/src/components/settings_modal/tabs/data_import_export_tab.vue
@@ -78,6 +78,16 @@
{{ $t('settings.download_backup') }}
</a>
<span
+ v-else-if="backup.state === 'running'"
+ >
+ {{ $tc('settings.backup_running', backup.processed_number, { number: backup.processed_number }) }}
+ </span>
+ <span
+ v-else-if="backup.state === 'failed'"
+ >
+ {{ $t('settings.backup_failed') }}
+ </span>
+ <span
v-else
>
{{ $t('settings.backup_not_ready') }}
diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js
@@ -1,6 +1,7 @@
-import { filter, trim } from 'lodash'
+import { filter, trim, debounce } from 'lodash'
import BooleanSetting from '../helpers/boolean_setting.vue'
import ChoiceSetting from '../helpers/choice_setting.vue'
+import UnitSetting from '../helpers/unit_setting.vue'
import IntegerSetting from '../helpers/integer_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
@@ -19,6 +20,7 @@ const FilteringTab = {
components: {
BooleanSetting,
ChoiceSetting,
+ UnitSetting,
IntegerSetting
},
computed: {
@@ -29,24 +31,20 @@ const FilteringTab = {
},
set (value) {
this.muteWordsStringLocal = value
+ this.debouncedSetMuteWords(value)
+ }
+ },
+ debouncedSetMuteWords () {
+ return debounce((value) => {
this.$store.dispatch('setOption', {
name: 'muteWords',
value: filter(value.split('\n'), (word) => trim(word).length > 0)
})
- }
+ }, 1000)
}
},
// Updating nested properties
watch: {
- notificationVisibility: {
- handler (value) {
- this.$store.dispatch('setOption', {
- name: 'notificationVisibility',
- value: this.$store.getters.mergedConfig.notificationVisibility
- })
- },
- deep: true
- },
replyVisibility () {
this.$store.dispatch('queueFlushAll')
}
diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue
@@ -7,13 +7,11 @@
<BooleanSetting path="hideFilteredStatuses">
{{ $t('settings.hide_filtered_statuses') }}
</BooleanSetting>
- <ul
- class="setting-list suboptions"
- :class="[{disabled: !streaming}]"
- >
+ <ul class="setting-list suboptions">
<li>
<BooleanSetting
- :disabled="hideFilteredStatuses"
+ parent-path="hideFilteredStatuses"
+ :parent-invert="true"
path="hideWordFilteredPosts"
>
{{ $t('settings.hide_wordfiltered_statuses') }}
@@ -22,7 +20,8 @@
<li>
<BooleanSetting
v-if="user"
- :disabled="hideFilteredStatuses"
+ parent-path="hideFilteredStatuses"
+ :parent-invert="true"
path="hideMutedThreads"
>
{{ $t('settings.hide_muted_threads') }}
@@ -31,7 +30,8 @@
<li>
<BooleanSetting
v-if="user"
- :disabled="hideFilteredStatuses"
+ parent-path="hideFilteredStatuses"
+ :parent-invert="true"
path="hideMutedPosts"
>
{{ $t('settings.hide_muted_posts') }}
@@ -45,13 +45,18 @@
</BooleanSetting>
</li>
<li>
+ <BooleanSetting path="muteSensitiveStatuses">
+ {{ $t('settings.mute_sensitive_posts') }}
+ </BooleanSetting>
+ </li>
+ <li>
<BooleanSetting path="hidePostStats">
{{ $t('settings.hide_post_stats') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="hideBotIndication">
- {{ $t('settings.hide_bot_indication') }}
+ {{ $t('settings.hide_actor_type_indication') }}
</BooleanSetting>
</li>
<ChoiceSetting
@@ -67,7 +72,7 @@
<textarea
id="muteWords"
v-model="muteWordsString"
- class="resize-height"
+ class="input resize-height"
/>
<div>{{ $t('settings.filtering_explanation') }}</div>
</li>
@@ -91,6 +96,22 @@
{{ $t('settings.hide_attachments_in_convo') }}
</BooleanSetting>
</li>
+ <li>
+ <BooleanSetting path="hideScrobbles">
+ {{ $t('settings.hide_scrobbles') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <UnitSetting
+ key="hideScrobblesAfter"
+ path="hideScrobblesAfter"
+ :units="['m', 'h', 'd']"
+ unit-set="time"
+ expert="1"
+ >
+ {{ $t('settings.hide_scrobbles_after') }}
+ </UnitSetting>
+ </li>
</ul>
</div>
<div
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
@@ -2,10 +2,12 @@ import BooleanSetting from '../helpers/boolean_setting.vue'
import ChoiceSetting from '../helpers/choice_setting.vue'
import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
import IntegerSetting from '../helpers/integer_setting.vue'
+import FloatSetting from '../helpers/float_setting.vue'
+import UnitSetting from '../helpers/unit_setting.vue'
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
-import ServerSideIndicator from '../helpers/server_side_indicator.vue'
+import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faGlobe
@@ -38,10 +40,10 @@ const GeneralTab = {
value: mode,
label: this.$t(`settings.mention_link_display_${mode}`)
})),
- thirdColumnModeOptions: ['none', 'notifications', 'postform'].map(mode => ({
+ userPopoverAvatarActionOptions: ['close', 'zoom', 'open'].map(mode => ({
key: mode,
value: mode,
- label: this.$t(`settings.third_column_mode_${mode}`)
+ label: this.$t(`settings.user_popover_avatar_action_${mode}`)
})),
loopSilentAvailable:
// Firefox
@@ -56,9 +58,11 @@ const GeneralTab = {
BooleanSetting,
ChoiceSetting,
IntegerSetting,
+ FloatSetting,
+ UnitSetting,
InterfaceLanguageSwitcher,
ScopeSelector,
- ServerSideIndicator
+ ProfileSettingIndicator
},
computed: {
postFormats () {
@@ -71,23 +75,19 @@ const GeneralTab = {
label: this.$t(`post_status.content_type["${format}"]`)
}))
},
- instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
- instanceWallpaperUsed () {
- return this.$store.state.instance.background &&
- !this.$store.state.users.currentUser.background_image
- },
- instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable },
language: {
get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
set: function (val) {
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
}
},
+ instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable },
+ instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
...SharedComputedObject()
},
methods: {
changeDefaultScope (value) {
- this.$store.dispatch('setServerSideOption', { name: 'defaultScope', value })
+ this.$store.dispatch('setProfileOption', { name: 'defaultScope', value })
}
}
}
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
@@ -16,16 +16,6 @@
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="sidebarRight">
- {{ $t('settings.right_sidebar') }}
- </BooleanSetting>
- </li>
- <li v-if="instanceWallpaperUsed">
- <BooleanSetting path="hideInstanceWallpaper">
- {{ $t('settings.hide_wallpaper') }}
- </BooleanSetting>
- </li>
- <li>
<BooleanSetting path="stopGifs">
{{ $t('settings.stop_gifs') }}
</BooleanSetting>
@@ -34,14 +24,11 @@
<BooleanSetting path="streaming">
{{ $t('settings.streaming') }}
</BooleanSetting>
- <ul
- class="setting-list suboptions"
- :class="[{disabled: !streaming}]"
- >
+ <ul class="setting-list suboptions">
<li>
<BooleanSetting
path="pauseOnUnfocused"
- :disabled="!streaming"
+ parent-path="streaming"
>
{{ $t('settings.pause_on_unfocused') }}
</BooleanSetting>
@@ -65,27 +52,25 @@
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="disableStickyHeaders">
- {{ $t('settings.disable_sticky_headers') }}
- </BooleanSetting>
- </li>
- <li>
- <BooleanSetting path="showScrollbars">
- {{ $t('settings.show_scrollbars') }}
- </BooleanSetting>
- </li>
- <li>
<ChoiceSetting
- v-if="user"
- id="thirdColumnMode"
- path="thirdColumnMode"
- :options="thirdColumnModeOptions"
+ id="userPopoverAvatarAction"
+ path="userPopoverAvatarAction"
+ :options="userPopoverAvatarActionOptions"
+ expert="1"
>
- {{ $t('settings.third_column_mode') }}
+ {{ $t('settings.user_popover_avatar_action') }}
</ChoiceSetting>
</li>
<li>
<BooleanSetting
+ path="userPopoverOverlay"
+ expert="1"
+ >
+ {{ $t('settings.user_popover_avatar_overlay') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting
path="alwaysShowNewPostButton"
expert="1"
>
@@ -108,6 +93,56 @@
{{ $t('settings.hide_shoutbox') }}
</BooleanSetting>
</li>
+ <li class="select-multiple">
+ <span class="label">{{ $t('settings.confirm_dialogs') }}</span>
+ <ul class="option-list">
+ <li>
+ <BooleanSetting path="modalOnRepeat">
+ {{ $t('settings.confirm_dialogs_repeat') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="modalOnUnfollow">
+ {{ $t('settings.confirm_dialogs_unfollow') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="modalOnBlock">
+ {{ $t('settings.confirm_dialogs_block') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="modalOnMute">
+ {{ $t('settings.confirm_dialogs_mute') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="modalOnDelete">
+ {{ $t('settings.confirm_dialogs_delete') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="modalOnLogout">
+ {{ $t('settings.confirm_dialogs_logout') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="modalOnApproveFollow">
+ {{ $t('settings.confirm_dialogs_approve_follow') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="modalOnDenyFollow">
+ {{ $t('settings.confirm_dialogs_deny_follow') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="modalOnRemoveUserFromFollowers">
+ {{ $t('settings.confirm_dialogs_remove_follower') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
</ul>
</div>
<div class="setting-item">
@@ -123,7 +158,7 @@
</ChoiceSetting>
</li>
<ul
- v-if="conversationDisplay !== 'linear'"
+ v-if="mergedConfig.conversationDisplay !== 'linear'"
class="setting-list suboptions"
>
<li>
@@ -175,12 +210,36 @@
<li>
<BooleanSetting
v-if="user"
- path="serverSide_stripRichContent"
+ source="profile"
+ path="stripRichContent"
expert="1"
>
{{ $t('settings.no_rich_text_description') }}
</BooleanSetting>
</li>
+ <li>
+ <BooleanSetting
+ path="useAbsoluteTimeFormat"
+ expert="1"
+ >
+ {{ $t('settings.absolute_time_format') }}
+ </BooleanSetting>
+ </li>
+ <ul
+ v-if="mergedConfig.useAbsoluteTimeFormat"
+ class="setting-list suboptions"
+ >
+ <li>
+ <UnitSetting
+ path="absoluteTimeFormatMinAge"
+ unit-set="time"
+ :units="['s', 'm', 'h', 'd']"
+ :min="0"
+ >
+ {{ $t('settings.absolute_time_format_min_age') }}
+ </UnitSetting>
+ </li>
+ </ul>
<h3>{{ $t('settings.attachments') }}</h3>
<li>
<BooleanSetting
@@ -200,7 +259,7 @@
<BooleanSetting
path="preloadImage"
expert="1"
- :disabled="!hideNsfw"
+ parent-path="hideNsfw"
>
{{ $t('settings.preload_images') }}
</BooleanSetting>
@@ -209,7 +268,7 @@
<BooleanSetting
path="useOneClickNsfw"
expert="1"
- :disabled="!hideNsfw"
+ parent-path="hideNsfw"
>
{{ $t('settings.use_one_click_nsfw') }}
</BooleanSetting>
@@ -222,15 +281,13 @@
>
{{ $t('settings.loop_video') }}
</BooleanSetting>
- <ul
- class="setting-list suboptions"
- :class="[{disabled: !streaming}]"
- >
+ <ul class="setting-list suboptions">
<li>
<BooleanSetting
path="loopVideoSilentOnly"
expert="1"
- :disabled="!loopVideo || !loopSilentAvailable"
+ parent-path="loopVideo"
+ :disabled="!loopSilentAvailable"
>
{{ $t('settings.loop_video_silent_only') }}
</BooleanSetting>
@@ -261,18 +318,14 @@
{{ $t('settings.mention_link_display') }}
</ChoiceSetting>
</li>
- <ul
- class="setting-list suboptions"
- >
- <li v-if="mentionLinkDisplay === 'short'">
- <BooleanSetting
- path="mentionLinkShowTooltip"
- expert="1"
- >
- {{ $t('settings.mention_link_show_tooltip') }}
- </BooleanSetting>
- </li>
- </ul>
+ <li>
+ <BooleanSetting
+ path="mentionLinkShowTooltip"
+ expert="1"
+ >
+ {{ $t('settings.mention_link_use_tooltip') }}
+ </BooleanSetting>
+ </li>
<li>
<BooleanSetting
path="useAtIcon"
@@ -332,18 +385,18 @@
<ul class="setting-list">
<li>
<label for="default-vis">
- {{ $t('settings.default_vis') }} <ServerSideIndicator :server-side="true" />
+ {{ $t('settings.default_vis') }} <ProfileSettingIndicator :is-profile="true" />
<ScopeSelector
class="scope-selector"
:show-all="true"
- :user-default="serverSide_defaultScope"
- :initial-scope="serverSide_defaultScope"
+ :user-default="$store.state.profileConfig.defaultScope"
+ :initial-scope="$store.state.profileConfig.defaultScope"
:on-scope-change="changeDefaultScope"
/>
</label>
</li>
<li>
- <!-- <BooleanSetting path="serverSide_defaultNSFW"> -->
+ <!-- <BooleanSetting source="profile" path="defaultNSFW"> -->
<BooleanSetting path="sensitiveByDefault">
{{ $t('settings.sensitive_by_default') }}
</BooleanSetting>
@@ -415,6 +468,14 @@
{{ $t('settings.pad_emoji') }}
</BooleanSetting>
</li>
+ <li>
+ <BooleanSetting
+ path="autocompleteSelect"
+ expert="1"
+ >
+ {{ $t('settings.autocomplete_select_first') }}
+ </BooleanSetting>
+ </li>
</ul>
</div>
</div>
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
@@ -9,17 +9,20 @@ import DomainMuteCard from 'src/components/domain_mute_card/domain_mute_card.vue
import SelectableList from 'src/components/selectable_list/selectable_list.vue'
import ProgressButton from 'src/components/progress_button/progress_button.vue'
import withSubscription from 'src/components/../hocs/with_subscription/with_subscription'
+import withLoadMore from 'src/components/../hocs/with_load_more/with_load_more'
import Checkbox from 'src/components/checkbox/checkbox.vue'
-const BlockList = withSubscription({
+const BlockList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchBlocks'),
select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
+ destroy: () => {},
childPropName: 'items'
})(SelectableList)
-const MuteList = withSubscription({
+const MuteList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchMutes'),
select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
+ destroy: () => {},
childPropName: 'items'
})(SelectableList)
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss b/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss
@@ -1,29 +1,29 @@
.mutes-and-blocks-tab {
- height: 100%;
+ height: 100%;
- .usersearch-wrapper {
- padding: 1em;
- }
+ .usersearch-wrapper {
+ padding: 1em;
+ }
- .bulk-actions {
- text-align: right;
- padding: 0 1em;
- min-height: 2em;
- }
+ .bulk-actions {
+ text-align: right;
+ padding: 0 1em;
+ min-height: 2em;
+ }
- .bulk-action-button {
- width: 10em
- }
+ .bulk-action-button {
+ width: 10em;
+ }
- .domain-mute-form {
- padding: 1em;
- display: flex;
- flex-direction: column
- }
+ .domain-mute-form {
+ padding: 1em;
+ display: flex;
+ flex-direction: column;
+ }
- .domain-mute-button {
- align-self: flex-end;
- margin-top: 1em;
- width: 10em
- }
+ .domain-mute-button {
+ align-self: flex-end;
+ margin-top: 1em;
+ width: 10em;
+ }
}
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
@@ -10,7 +10,7 @@
:query="queryUserIds"
:placeholder="$t('settings.search_user_to_block')"
>
- <template v-slot="row">
+ <template #default="row">
<BlockCard
:user-id="row.item"
/>
@@ -21,7 +21,7 @@
:refresh="true"
:get-key="i => i"
>
- <template v-slot:header="{selected}">
+ <template #header="{selected}">
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@@ -29,7 +29,7 @@
:click="() => blockUsers(selected)"
>
{{ $t('user_card.block') }}
- <template v-slot:progress>
+ <template #progress>
{{ $t('user_card.block_progress') }}
</template>
</ProgressButton>
@@ -39,16 +39,16 @@
:click="() => unblockUsers(selected)"
>
{{ $t('user_card.unblock') }}
- <template v-slot:progress>
+ <template #progress>
{{ $t('user_card.unblock_progress') }}
</template>
</ProgressButton>
</div>
</template>
- <template v-slot:item="{item}">
+ <template #item="{item}">
<BlockCard :user-id="item" />
</template>
- <template v-slot:empty>
+ <template #empty>
{{ $t('settings.no_blocks') }}
</template>
</BlockList>
@@ -56,14 +56,14 @@
<div :label="$t('settings.mutes_tab')">
<tab-switcher>
- <div label="Users">
+ <div :label="$t('settings.user_mutes')">
<div class="usersearch-wrapper">
<Autosuggest
:filter="filterUnMutedUsers"
:query="queryUserIds"
:placeholder="$t('settings.search_user_to_mute')"
>
- <template v-slot="row">
+ <template #default="row">
<MuteCard
:user-id="row.item"
/>
@@ -74,7 +74,7 @@
:refresh="true"
:get-key="i => i"
>
- <template v-slot:header="{selected}">
+ <template #header="{selected}">
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@@ -82,7 +82,7 @@
:click="() => muteUsers(selected)"
>
{{ $t('user_card.mute') }}
- <template v-slot:progress>
+ <template #progress>
{{ $t('user_card.mute_progress') }}
</template>
</ProgressButton>
@@ -92,16 +92,16 @@
:click="() => unmuteUsers(selected)"
>
{{ $t('user_card.unmute') }}
- <template v-slot:progress>
+ <template #progress>
{{ $t('user_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
- <template v-slot:item="{item}">
+ <template #item="{item}">
<MuteCard :user-id="item" />
</template>
- <template v-slot:empty>
+ <template #empty>
{{ $t('settings.no_mutes') }}
</template>
</MuteList>
@@ -114,7 +114,7 @@
:query="queryKnownDomains"
:placeholder="$t('settings.type_domains_to_mute')"
>
- <template v-slot="row">
+ <template #default="row">
<DomainMuteCard
:domain="row.item"
/>
@@ -125,7 +125,7 @@
:refresh="true"
:get-key="i => i"
>
- <template v-slot:header="{selected}">
+ <template #header="{selected}">
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@@ -133,16 +133,16 @@
:click="() => unmuteDomains(selected)"
>
{{ $t('domain_mute_card.unmute') }}
- <template v-slot:progress>
+ <template #progress>
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
- <template v-slot:item="{item}">
+ <template #item="{item}">
<DomainMuteCard :domain="item" />
</template>
- <template v-slot:empty>
+ <template #empty>
{{ $t('settings.no_mutes') }}
</template>
</DomainMuteList>
diff --git a/src/components/settings_modal/tabs/notifications_tab.js b/src/components/settings_modal/tabs/notifications_tab.js
@@ -16,6 +16,10 @@ const NotificationsTab = {
user () {
return this.$store.state.users.currentUser
},
+ canReceiveReports () {
+ if (!this.user) { return false }
+ return this.user.privileges.includes('reports_manage_reports')
+ },
...SharedComputedObject()
},
methods: {
diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue
@@ -1,49 +1,239 @@
<template>
<div :label="$t('settings.notifications')">
<div class="setting-item">
+ <h2>{{ $t('settings.notification_setting_annoyance') }}</h2>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="closingDrawerMarksAsSeen">
+ {{ $t('settings.notification_setting_drawer_marks_as_seen') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="ignoreInactionableSeen">
+ {{ $t('settings.notification_setting_ignore_inactionable_seen') }}
+ </BooleanSetting>
+ <div>
+ <small>
+ {{ $t('settings.notification_setting_ignore_inactionable_seen_tip') }}
+ </small>
+ </div>
+ </li>
+ <li>
+ <BooleanSetting
+ path="unseenAtTop"
+ expert="1"
+ >
+ {{ $t('settings.notification_setting_unseen_at_top') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </div>
+ <div class="setting-item">
<h2>{{ $t('settings.notification_setting_filters') }}</h2>
<ul class="setting-list">
<li>
- <BooleanSetting path="serverSide_blockNotificationsFromStrangers">
+ <BooleanSetting
+ source="profile"
+ path="blockNotificationsFromStrangers"
+ >
{{ $t('settings.notification_setting_block_from_strangers') }}
</BooleanSetting>
</li>
- <li class="select-multiple">
- <span class="label">{{ $t('settings.notification_visibility') }}</span>
- <ul class="option-list">
+ <li>
+ <h3> {{ $t('settings.notification_visibility') }}</h3>
+ <p v-if="expertLevel > 0">
+ {{ $t('settings.notification_setting_filters_chrome_push') }}
+ </p>
+ <ul class="setting-list two-column">
<li>
- <BooleanSetting path="notificationVisibility.likes">
- {{ $t('settings.notification_visibility_likes') }}
- </BooleanSetting>
+ <h4> {{ $t('settings.notification_visibility_mentions') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.mentions">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.mentions">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
</li>
<li>
- <BooleanSetting path="notificationVisibility.repeats">
- {{ $t('settings.notification_visibility_repeats') }}
- </BooleanSetting>
+ <h4> {{ $t('settings.notification_visibility_statuses') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.statuses">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.statuses">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
</li>
<li>
- <BooleanSetting path="notificationVisibility.follows">
- {{ $t('settings.notification_visibility_follows') }}
- </BooleanSetting>
+ <h4> {{ $t('settings.notification_visibility_likes') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.likes">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.likes">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
</li>
<li>
- <BooleanSetting path="notificationVisibility.mentions">
- {{ $t('settings.notification_visibility_mentions') }}
+ <h4> {{ $t('settings.notification_visibility_repeats') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.repeats">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.repeats">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4> {{ $t('settings.notification_visibility_emoji_reactions') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.emojiReactions">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.emojiReactions">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4> {{ $t('settings.notification_visibility_follows') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.follows">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.follows">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4> {{ $t('settings.notification_visibility_follow_requests') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.followRequest">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.followRequest">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4> {{ $t('settings.notification_visibility_moves') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.moves">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.moves">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4> {{ $t('settings.notification_visibility_polls') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.polls">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.polls">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li v-if="canReceiveReports">
+ <h4> {{ $t('settings.notification_visibility_reports') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.reports">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.reports">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <BooleanSetting path="showExtraNotifications">
+ {{ $t('settings.notification_show_extra') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <ul class="setting-list suboptions">
+ <li>
+ <BooleanSetting
+ path="showChatsInExtraNotifications"
+ :disabled="!mergedConfig.showExtraNotifications"
+ >
+ {{ $t('settings.notification_extra_chats') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="notificationVisibility.moves">
- {{ $t('settings.notification_visibility_moves') }}
+ <BooleanSetting
+ path="showAnnouncementsInExtraNotifications"
+ :disabled="!mergedConfig.showExtraNotifications"
+ >
+ {{ $t('settings.notification_extra_announcements') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="notificationVisibility.emojiReactions">
- {{ $t('settings.notification_visibility_emoji_reactions') }}
+ <BooleanSetting
+ path="showFollowRequestsInExtraNotifications"
+ :disabled="!mergedConfig.showExtraNotifications"
+ >
+ {{ $t('settings.notification_extra_follow_requests') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="notificationVisibility.polls">
- {{ $t('settings.notification_visibility_polls') }}
+ <BooleanSetting
+ path="showExtraNotificationsTip"
+ :disabled="!mergedConfig.showExtraNotifications"
+ >
+ {{ $t('settings.notification_extra_tip') }}
</BooleanSetting>
</li>
</ul>
@@ -64,10 +254,26 @@
>
{{ $t('settings.enable_web_push_notifications') }}
</BooleanSetting>
+ <ul class="setting-list suboptions">
+ <li>
+ <BooleanSetting
+ path="webPushAlwaysShowNotifications"
+ :disabled="!mergedConfig.webPushNotifications"
+ >
+ {{ $t('settings.enable_web_push_always_show') }}
+ </BooleanSetting>
+ <div :class="{ faint: !mergedConfig.webPushNotifications }">
+ <small>
+ {{ $t('settings.enable_web_push_always_show_tip') }}
+ </small>
+ </div>
+ </li>
+ </ul>
</li>
<li>
<BooleanSetting
- path="serverSide_webPushHideContents"
+ source="profile"
+ path="webPushHideContents"
expert="1"
>
{{ $t('settings.notification_setting_hide_notification_contents') }}
diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js
@@ -9,9 +9,11 @@ import suggestor from 'src/components/emoji_input/suggestor.js'
import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
+import Select from 'src/components/select/select.vue'
import BooleanSetting from '../helpers/boolean_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import localeService from 'src/services/locale/locale.service.js'
+import { propsToNative } from 'src/services/attributes_helper/attributes_helper.service.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -32,10 +34,13 @@ const ProfileTab = {
newName: this.$store.state.users.currentUser.name_unescaped,
newBio: unescape(this.$store.state.users.currentUser.description),
newLocked: this.$store.state.users.currentUser.locked,
+ newBirthday: this.$store.state.users.currentUser.birthday,
+ showBirthday: this.$store.state.users.currentUser.show_birthday,
newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })),
showRole: this.$store.state.users.currentUser.show_role,
role: this.$store.state.users.currentUser.role,
bot: this.$store.state.users.currentUser.bot,
+ actorType: this.$store.state.users.currentUser.actor_type,
pickAvatarBtnVisible: true,
bannerUploading: false,
backgroundUploading: false,
@@ -43,7 +48,7 @@ const ProfileTab = {
bannerPreview: null,
background: null,
backgroundPreview: null,
- emailLanguage: this.$store.state.users.currentUser.language || ''
+ emailLanguage: this.$store.state.users.currentUser.language || ['']
}
},
components: {
@@ -54,7 +59,8 @@ const ProfileTab = {
ProgressButton,
Checkbox,
BooleanSetting,
- InterfaceLanguageSwitcher
+ InterfaceLanguageSwitcher,
+ Select
},
computed: {
user () {
@@ -64,17 +70,19 @@ const ProfileTab = {
emojiUserSuggestor () {
return suggestor({
emoji: [
- ...this.$store.state.instance.emoji,
+ ...this.$store.getters.standardEmojiList,
...this.$store.state.instance.customEmoji
],
store: this.$store
})
},
emojiSuggestor () {
- return suggestor({ emoji: [
- ...this.$store.state.instance.emoji,
- ...this.$store.state.instance.customEmoji
- ] })
+ return suggestor({
+ emoji: [
+ ...this.$store.getters.standardEmojiList,
+ ...this.$store.state.instance.customEmoji
+ ]
+ })
},
userSuggestor () {
return suggestor({ store: this.$store })
@@ -111,6 +119,12 @@ const ProfileTab = {
bannerImgSrc () {
const src = this.$store.state.users.currentUser.cover_photo
return (!src) ? this.defaultBanner : src
+ },
+ groupActorAvailable () {
+ return this.$store.state.instance.groupActorAvailable
+ },
+ availableActorTypes () {
+ return this.groupActorAvailable ? ['Person', 'Service', 'Group'] : ['Person', 'Service']
}
},
methods: {
@@ -122,13 +136,15 @@ const ProfileTab = {
/* eslint-disable camelcase */
display_name: this.newName,
fields_attributes: this.newFields.filter(el => el != null),
- bot: this.bot,
- show_role: this.showRole
+ actor_type: this.actorType,
+ show_role: this.showRole,
+ birthday: this.newBirthday || '',
+ show_birthday: this.showBirthday
/* eslint-enable camelcase */
}
if (this.emailLanguage) {
- params.language = localeService.internalToBackendLocale(this.emailLanguage)
+ params.language = localeService.internalToBackendLocaleMulti(this.emailLanguage)
}
this.$store.state.api.backendInteractor
@@ -151,7 +167,7 @@ const ProfileTab = {
return false
},
deleteField (index, event) {
- this.$delete(this.newFields, index)
+ this.newFields.splice(index, 1)
},
uploadFile (slot, e) {
const file = e.target.files[0]
@@ -255,6 +271,9 @@ const ProfileTab = {
messageArgs: [error.message],
level: 'error'
})
+ },
+ propsToNative (props) {
+ return propsToNative(props)
}
}
}
diff --git a/src/components/settings_modal/tabs/profile_tab.scss b/src/components/settings_modal/tabs/profile_tab.scss
@@ -1,4 +1,3 @@
-@import '../../../_variables.scss';
.profile-tab {
.bio {
margin: 0;
@@ -8,7 +7,7 @@
padding-top: 5px;
}
- input[type=file] {
+ input[type="file"] {
padding: 5px;
height: auto;
}
@@ -42,17 +41,15 @@
display: block;
width: 100%;
height: 100%;
- border-radius: $fallback--avatarRadius;
- border-radius: var(--avatarRadius, $fallback--avatarRadius);
+ border-radius: var(--roundness);
}
.reset-button {
position: absolute;
top: 0.2em;
right: 0.2em;
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
- background-color: rgba(0, 0, 0, 0.6);
+ border-radius: var(--roundness);
+ background-color: rgb(0 0 0 / 60%);
opacity: 0.7;
width: 1.5em;
height: 1.5em;
@@ -128,4 +125,9 @@
padding: 0 0.5em;
}
}
+
+ .birthday-input {
+ display: block;
+ margin-bottom: 1em;
+ }
}
diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue
@@ -8,11 +8,14 @@
enable-emoji-picker
:suggest="emojiSuggestor"
>
- <input
- id="username"
- v-model="newName"
- class="name-changer"
- >
+ <template #default="inputProps">
+ <input
+ id="username"
+ v-model="newName"
+ class="input name-changer"
+ v-bind="propsToNative(inputProps)"
+ >
+ </template>
</EmojiInput>
<p>{{ $t('settings.bio') }}</p>
<EmojiInput
@@ -20,10 +23,13 @@
enable-emoji-picker
:suggest="emojiUserSuggestor"
>
- <textarea
- v-model="newBio"
- class="bio resize-height"
- />
+ <template #default="inputProps">
+ <textarea
+ v-model="newBio"
+ class="input bio resize-height"
+ v-bind="propsToNative(inputProps)"
+ />
+ </template>
</EmojiInput>
<p v-if="role === 'admin' || role === 'moderator'">
<Checkbox v-model="showRole">
@@ -35,6 +41,18 @@
</template>
</Checkbox>
</p>
+ <div>
+ <p>{{ $t('settings.birthday.label') }}</p>
+ <input
+ id="birthday"
+ v-model="newBirthday"
+ type="date"
+ class="input birthday-input"
+ >
+ <Checkbox v-model="showBirthday">
+ {{ $t('settings.birthday.show_birthday') }}
+ </Checkbox>
+ </div>
<div v-if="maxFields > 0">
<p>{{ $t('settings.profile_fields.label') }}</p>
<div
@@ -48,10 +66,14 @@
hide-emoji-button
:suggest="userSuggestor"
>
- <input
- v-model="newFields[i].name"
- :placeholder="$t('settings.profile_fields.name')"
- >
+ <template #default="inputProps">
+ <input
+ v-model="newFields[i].name"
+ :placeholder="$t('settings.profile_fields.name')"
+ v-bind="propsToNative(inputProps)"
+ class="input"
+ >
+ </template>
</EmojiInput>
<EmojiInput
v-model="newFields[i].value"
@@ -59,10 +81,14 @@
hide-emoji-button
:suggest="userSuggestor"
>
- <input
- v-model="newFields[i].value"
- :placeholder="$t('settings.profile_fields.value')"
- >
+ <template #default="inputProps">
+ <input
+ v-model="newFields[i].value"
+ :placeholder="$t('settings.profile_fields.value')"
+ v-bind="propsToNative(inputProps)"
+ class="input"
+ >
+ </template>
</EmojiInput>
<button
class="delete-field button-unstyled -hover-highlight"
@@ -85,10 +111,24 @@
</button>
</div>
<p>
- <Checkbox v-model="bot">
- {{ $t('settings.bot') }}
- </Checkbox>
+ <label>
+ {{ $t('settings.actor_type') }}
+ <Select v-model="actorType">
+ <option
+ v-for="option in availableActorTypes"
+ :key="option"
+ :value="option"
+ >
+ {{ $t('settings.actor_type_' + option) }}
+ </option>
+ </Select>
+ </label>
</p>
+ <div v-if="groupActorAvailable">
+ <small>
+ {{ $t('settings.actor_type_description') }}
+ </small>
+ </div>
<p>
<interface-language-switcher
:prompt-text="$t('settings.email_language')"
@@ -117,8 +157,8 @@
<button
v-if="!isDefaultAvatar && pickAvatarBtnVisible"
:title="$t('settings.reset_avatar')"
- @click="resetAvatar"
class="button-unstyled reset-button"
+ @click="resetAvatar"
>
<FAIcon
icon="times"
@@ -167,6 +207,7 @@
<div>
<input
type="file"
+ class="input"
@change="uploadFile('banner', $event)"
>
</div>
@@ -209,6 +250,7 @@
<div>
<input
type="file"
+ class="input"
@change="uploadFile('background', $event)"
>
</div>
@@ -230,37 +272,50 @@
<h2>{{ $t('settings.account_privacy') }}</h2>
<ul class="setting-list">
<li>
- <BooleanSetting path="serverSide_locked">
+ <BooleanSetting
+ source="profile"
+ path="locked"
+ >
{{ $t('settings.lock_account_description') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="serverSide_discoverable">
+ <BooleanSetting
+ source="profile"
+ path="discoverable"
+ >
{{ $t('settings.discoverable') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="serverSide_allowFollowingMove">
+ <BooleanSetting
+ source="profile"
+ path="allowFollowingMove"
+ >
{{ $t('settings.allow_following_move') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="serverSide_hideFavorites">
+ <BooleanSetting
+ source="profile"
+ path="hideFavorites"
+ >
{{ $t('settings.hide_favorites_description') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="serverSide_hideFollowers">
+ <BooleanSetting
+ source="profile"
+ path="hideFollowers"
+ >
{{ $t('settings.hide_followers_description') }}
</BooleanSetting>
- <ul
- class="setting-list suboptions"
- :class="[{disabled: !serverSide_hideFollowers}]"
- >
+ <ul class="setting-list suboptions">
<li>
<BooleanSetting
- path="serverSide_hideFollowersCount"
- :disabled="!serverSide_hideFollowers"
+ source="profile"
+ path="hideFollowersCount"
+ parent-path="hideFollowers"
>
{{ $t('settings.hide_followers_count_description') }}
</BooleanSetting>
@@ -268,17 +323,18 @@
</ul>
</li>
<li>
- <BooleanSetting path="serverSide_hideFollows">
+ <BooleanSetting
+ source="profile"
+ path="hideFollows"
+ >
{{ $t('settings.hide_follows_description') }}
</BooleanSetting>
- <ul
- class="setting-list suboptions"
- :class="[{disabled: !serverSide_hideFollows}]"
- >
+ <ul class="setting-list suboptions">
<li>
<BooleanSetting
- path="serverSide_hideFollowsCount"
- :disabled="!serverSide_hideFollows"
+ source="profile"
+ path="hideFollowsCount"
+ parent-path="hideFollows"
>
{{ $t('settings.hide_follows_count_description') }}
</BooleanSetting>
diff --git a/src/components/settings_modal/tabs/security_tab/mfa.js b/src/components/settings_modal/tabs/security_tab/mfa.js
@@ -32,8 +32,8 @@ const Mfa = {
components: {
'recovery-codes': RecoveryCodes,
'totp-item': TOTP,
- 'qrcode': VueQrcode,
- 'confirm': Confirm
+ qrcode: VueQrcode,
+ confirm: Confirm
},
computed: {
canSetupOTP () {
@@ -139,7 +139,7 @@ const Mfa = {
// fetch settings from server
async fetchSettings () {
- let result = await this.backendInteractor.settingsMFA()
+ const result = await this.backendInteractor.settingsMFA()
if (result.error) return
this.settings = result.settings
this.settings.available = true
diff --git a/src/components/settings_modal/tabs/security_tab/mfa.vue b/src/components/settings_modal/tabs/security_tab/mfa.vue
@@ -99,12 +99,14 @@
<input
v-model="otpConfirmToken"
type="text"
+ class="input"
>
<p>{{ $t('settings.enter_current_password_to_confirm') }}:</p>
<input
v-model="currentPassword"
type="password"
+ class="input"
>
<div class="confirm-otp-actions">
<button
@@ -137,9 +139,9 @@
<script src="./mfa.js"></script>
<style lang="scss">
-@import '../../../../_variables.scss';
.mfa-settings {
- .mfa-heading, .method-item {
+ .mfa-heading,
+ .method-item {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
@@ -147,26 +149,26 @@
}
.warning {
- color: $fallback--cOrange;
- color: var(--cOrange, $fallback--cOrange);
+ color: var(--cOrange);
}
.setup-otp {
display: flex;
justify-content: center;
flex-wrap: wrap;
+
.qr-code {
flex: 1;
padding-right: 10px;
}
.verify { flex: 1; }
- .error { margin: 4px 0 0 0; }
+ .error { margin: 4px 0 0; }
+
.confirm-otp-actions {
button {
width: 15em;
margin-top: 5px;
}
-
}
}
}
diff --git a/src/components/settings_modal/tabs/security_tab/mfa_backup_codes.vue b/src/components/settings_modal/tabs/security_tab/mfa_backup_codes.vue
@@ -21,15 +21,13 @@
</template>
<script src="./mfa_backup_codes.js"></script>
<style lang="scss">
-@import '../../../../_variables.scss';
-
.mfa-backup-codes {
.warning {
- color: $fallback--cOrange;
- color: var(--cOrange, $fallback--cOrange);
+ color: var(--cOrange);
}
+
.backup-codes {
- font-family: var(--postCodeFont, monospace);
+ font-family: var(--monoFont);
}
}
</style>
diff --git a/src/components/settings_modal/tabs/security_tab/mfa_totp.js b/src/components/settings_modal/tabs/security_tab/mfa_totp.js
@@ -10,7 +10,7 @@ export default {
inProgress: false // progress peform request to disable otp method
}),
components: {
- 'confirm': Confirm
+ confirm: Confirm
},
computed: {
isActivated () {
diff --git a/src/components/settings_modal/tabs/security_tab/mfa_totp.vue b/src/components/settings_modal/tabs/security_tab/mfa_totp.vue
@@ -30,6 +30,7 @@
<input
v-model="currentPassword"
type="password"
+ class="input"
>
</confirm>
<div
diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.js b/src/components/settings_modal/tabs/security_tab/security_tab.js
@@ -13,7 +13,7 @@ const SecurityTab = {
deletingAccount: false,
deleteAccountConfirmPasswordInput: '',
deleteAccountError: false,
- changePasswordInputs: [ '', '', '' ],
+ changePasswordInputs: ['', '', ''],
changedPassword: false,
changePasswordError: false,
moveAccountTarget: '',
diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.vue b/src/components/settings_modal/tabs/security_tab/security_tab.vue
@@ -8,6 +8,7 @@
v-model="newEmail"
type="email"
autocomplete="email"
+ class="input"
>
</div>
<div>
@@ -16,6 +17,7 @@
v-model="changeEmailPassword"
type="password"
autocomplete="current-password"
+ class="input"
>
</div>
<button
@@ -40,6 +42,7 @@
<input
v-model="changePasswordInputs[0]"
type="password"
+ class="input"
>
</div>
<div>
@@ -47,6 +50,7 @@
<input
v-model="changePasswordInputs[1]"
type="password"
+ class="input"
>
</div>
<div>
@@ -54,6 +58,7 @@
<input
v-model="changePasswordInputs[2]"
type="password"
+ class="input"
>
</div>
<button
@@ -143,8 +148,9 @@
/>
</div>
<div>
- <i18n
- path="settings.new_alias_target"
+ <i18n-t
+ scope="global"
+ keypath="settings.new_alias_target"
tag="p"
>
<code
@@ -152,9 +158,10 @@
>
foo@example.org
</code>
- </i18n>
+ </i18n-t>
<input
v-model="addAliasTarget"
+ class="input"
>
</div>
<button
@@ -175,18 +182,20 @@
<h2>{{ $t('settings.move_account') }}</h2>
<p>{{ $t('settings.move_account_notes') }}</p>
<div>
- <i18n
- path="settings.move_account_target"
+ <i18n-t
+ keypath="settings.move_account_target"
tag="p"
+ scope="global"
>
- <code
- place="example"
- >
- foo@example.org
- </code>
- </i18n>
+ <template #example>
+ <code>
+ foo@example.org
+ </code>
+ </template>
+ </i18n-t>
<input
v-model="moveAccountTarget"
+ class="input"
>
</div>
<div>
@@ -195,6 +204,7 @@
v-model="moveAccountPassword"
type="password"
autocomplete="current-password"
+ class="input"
>
</div>
<button
@@ -222,6 +232,7 @@
<input
v-model="deleteAccountConfirmPasswordInput"
type="password"
+ class="input"
>
<button
class="btn button-default"
@@ -241,7 +252,7 @@
class="btn button-default"
@click="confirmDelete"
>
- {{ $t('settings.save') }}
+ {{ $t('settings.delete_account') }}
</button>
</div>
</div>
diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.js b/src/components/settings_modal/tabs/style_tab/style_tab.js
@@ -0,0 +1,835 @@
+import { ref, reactive, computed, watch, watchEffect, provide, getCurrentInstance } from 'vue'
+import { useStore } from 'vuex'
+import { get, set, unset, throttle } from 'lodash'
+
+import Select from 'src/components/select/select.vue'
+import SelectMotion from 'src/components/select/select_motion.vue'
+import Checkbox from 'src/components/checkbox/checkbox.vue'
+import ComponentPreview from 'src/components/component_preview/component_preview.vue'
+import StringSetting from '../../helpers/string_setting.vue'
+import ShadowControl from 'src/components/shadow_control/shadow_control.vue'
+import ColorInput from 'src/components/color_input/color_input.vue'
+import PaletteEditor from 'src/components/palette_editor/palette_editor.vue'
+import OpacityInput from 'src/components/opacity_input/opacity_input.vue'
+import RoundnessInput from 'src/components/roundness_input/roundness_input.vue'
+import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
+import Tooltip from 'src/components/tooltip/tooltip.vue'
+import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue'
+import Preview from '../theme_tab/theme_preview.vue'
+
+import VirtualDirectivesTab from './virtual_directives_tab.vue'
+
+import { init, findColor } from 'src/services/theme_data/theme_data_3.service.js'
+import {
+ getCssRules,
+ getScopedVersion
+} from 'src/services/theme_data/css_utils.js'
+import { serialize } from 'src/services/theme_data/iss_serializer.js'
+import { deserializeShadow, deserialize } from 'src/services/theme_data/iss_deserializer.js'
+import {
+ rgb2hex,
+ hex2rgb,
+ getContrastRatio
+} from 'src/services/color_convert/color_convert.js'
+import {
+ newImporter,
+ newExporter
+} from 'src/services/export_import/export_import.js'
+
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faFloppyDisk,
+ faFolderOpen,
+ faFile,
+ faArrowsRotate,
+ faCheck
+} from '@fortawesome/free-solid-svg-icons'
+
+// helper for debugging
+// eslint-disable-next-line no-unused-vars
+const toValue = (x) => JSON.parse(JSON.stringify(x === undefined ? 'null' : x))
+
+// helper to make states comparable
+const normalizeStates = (states) => ['normal', ...(states?.filter(x => x !== 'normal') || [])].join(':')
+
+library.add(
+ faFile,
+ faFloppyDisk,
+ faFolderOpen,
+ faArrowsRotate,
+ faCheck
+)
+
+export default {
+ components: {
+ Select,
+ SelectMotion,
+ Checkbox,
+ Tooltip,
+ StringSetting,
+ ComponentPreview,
+ TabSwitcher,
+ ShadowControl,
+ ColorInput,
+ PaletteEditor,
+ OpacityInput,
+ RoundnessInput,
+ ContrastRatio,
+ Preview,
+ VirtualDirectivesTab
+ },
+ setup (props, context) {
+ const exports = {}
+ const store = useStore()
+ // All rules that are made by editor
+ const allEditedRules = ref(store.state.interface.styleDataUsed || {})
+ const styleDataUsed = computed(() => store.state.interface.styleDataUsed)
+
+ watch([styleDataUsed], (value) => {
+ onImport(store.state.interface.styleDataUsed)
+ }, { once: true })
+
+ exports.isActive = computed(() => {
+ const tabSwitcher = getCurrentInstance().parent.ctx
+ return tabSwitcher ? tabSwitcher.isActive('style') : false
+ })
+
+ // ## Meta stuff
+ exports.name = ref('')
+ exports.author = ref('')
+ exports.license = ref('')
+ exports.website = ref('')
+
+ const metaOut = computed(() => {
+ return [
+ '@meta {',
+ ` name: ${exports.name.value};`,
+ ` author: ${exports.author.value};`,
+ ` license: ${exports.license.value};`,
+ ` website: ${exports.website.value};`,
+ '}'
+ ].join('\n')
+ })
+
+ const metaRule = computed(() => ({
+ component: '@meta',
+ directives: {
+ name: exports.name.value,
+ author: exports.author.value,
+ license: exports.license.value,
+ website: exports.website.value
+ }
+ }))
+
+ // ## Palette stuff
+ const palettes = reactive([
+ {
+ name: 'default',
+ bg: '#121a24',
+ fg: '#182230',
+ text: '#b9b9ba',
+ link: '#d8a070',
+ accent: '#d8a070',
+ cRed: '#FF0000',
+ cBlue: '#0095ff',
+ cGreen: '#0fa00f',
+ cOrange: '#ffa500'
+ },
+ {
+ name: 'light',
+ bg: '#f2f6f9',
+ fg: '#d6dfed',
+ text: '#304055',
+ underlay: '#5d6086',
+ accent: '#f55b1b',
+ cBlue: '#0095ff',
+ cRed: '#d31014',
+ cGreen: '#0fa00f',
+ cOrange: '#ffa500',
+ border: '#d8e6f9'
+ }
+ ])
+ exports.palettes = palettes
+
+ // This is kinda dumb but you cannot "replace" reactive() object
+ // and so v-model simply fails when you try to chage (increase only?)
+ // length of the array. Since linter complains about mutating modelValue
+ // inside SelectMotion, the next best thing is to just wipe existing array
+ // and replace it with new one.
+
+ const onPalettesUpdate = (e) => {
+ palettes.splice(0, palettes.length)
+ palettes.push(...e)
+ }
+ exports.onPalettesUpdate = onPalettesUpdate
+
+ const selectedPaletteId = ref(0)
+ const selectedPalette = computed({
+ get () {
+ return palettes[selectedPaletteId.value]
+ },
+ set (newPalette) {
+ palettes[selectedPaletteId.value] = newPalette
+ }
+ })
+ exports.selectedPaletteId = selectedPaletteId
+ exports.selectedPalette = selectedPalette
+ provide('selectedPalette', selectedPalette)
+
+ watch([selectedPalette], () => updateOverallPreview())
+
+ exports.getNewPalette = () => ({
+ name: 'new palette',
+ bg: '#121a24',
+ fg: '#182230',
+ text: '#b9b9ba',
+ link: '#d8a070',
+ accent: '#d8a070',
+ cRed: '#FF0000',
+ cBlue: '#0095ff',
+ cGreen: '#0fa00f',
+ cOrange: '#ffa500'
+ })
+
+ // Raw format
+ const palettesRule = computed(() => {
+ return palettes.map(palette => {
+ const { name, ...rest } = palette
+ return {
+ component: '@palette',
+ variant: name,
+ directives: Object
+ .entries(rest)
+ .filter(([k, v]) => v && k)
+ .reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
+ }
+ })
+ })
+
+ // Text format
+ const palettesOut = computed(() => {
+ return palettes.map(({ name, ...palette }) => {
+ const entries = Object
+ .entries(palette)
+ .filter(([k, v]) => v && k)
+ .map(([slot, data]) => ` ${slot}: ${data};`)
+ .join('\n')
+
+ return `@palette.${name} {\n${entries}\n}`
+ }).join('\n\n')
+ })
+
+ // ## Components stuff
+ // Getting existing components
+ const componentsContext = require.context('src', true, /\.style.js(on)?$/)
+ const componentKeysAll = componentsContext.keys()
+ const componentsMap = new Map(
+ componentKeysAll
+ .map(
+ key => [key, componentsContext(key).default]
+ ).filter(([key, component]) => !component.virtual && !component.notEditable)
+ )
+ exports.componentsMap = componentsMap
+ const componentKeys = [...componentsMap.keys()]
+ exports.componentKeys = componentKeys
+
+ // Component list and selection
+ const selectedComponentKey = ref(componentsMap.keys().next().value)
+ exports.selectedComponentKey = selectedComponentKey
+
+ const selectedComponent = computed(() => componentsMap.get(selectedComponentKey.value))
+ const selectedComponentName = computed(() => selectedComponent.value.name)
+
+ // Selection basis
+ exports.selectedComponentVariants = computed(() => {
+ return Object.keys({ normal: null, ...(selectedComponent.value.variants || {}) })
+ })
+ exports.selectedComponentStates = computed(() => {
+ const all = Object.keys({ normal: null, ...(selectedComponent.value.states || {}) })
+ return all.filter(x => x !== 'normal')
+ })
+
+ // selection
+ const selectedVariant = ref('normal')
+ exports.selectedVariant = selectedVariant
+ const selectedState = reactive(new Set())
+ exports.selectedState = selectedState
+ exports.updateSelectedStates = (state, v) => {
+ if (v) {
+ selectedState.add(state)
+ } else {
+ selectedState.delete(state)
+ }
+ }
+
+ // Reset variant and state on component change
+ const updateSelectedComponent = () => {
+ selectedVariant.value = 'normal'
+ selectedState.clear()
+ }
+
+ watch(
+ selectedComponentName,
+ updateSelectedComponent
+ )
+
+ // ### Rules stuff aka meat and potatoes
+ // The native structure of separate rules and the child -> parent
+ // relation isn't very convenient for editor, we replace the array
+ // and child -> parent structure with map and parent -> child structure
+ const rulesToEditorFriendly = (rules, root = {}) => rules.reduce((acc, rule) => {
+ const { parent: rParent, component: rComponent } = rule
+ const parent = rParent ?? rule
+ const hasChildren = !!rParent
+ const child = hasChildren ? rule : null
+
+ const {
+ component: pComponent,
+ variant: pVariant = 'normal',
+ state: pState = [] // no relation to Intel CPUs whatsoever
+ } = parent
+
+ const pPath = `${hasChildren ? pComponent : rComponent}.${pVariant}.${normalizeStates(pState)}`
+
+ let output = get(acc, pPath)
+ if (!output) {
+ set(acc, pPath, {})
+ output = get(acc, pPath)
+ }
+
+ if (hasChildren) {
+ output._children = output._children ?? {}
+ const {
+ component: cComponent,
+ variant: cVariant = 'normal',
+ state: cState = [],
+ directives
+ } = child
+
+ const cPath = `${cComponent}.${cVariant}.${normalizeStates(cState)}`
+ set(output._children, cPath, { directives })
+ } else {
+ output.directives = parent.directives
+ }
+ return acc
+ }, root)
+
+ const editorFriendlyFallbackStructure = computed(() => {
+ const root = {}
+
+ componentKeys.forEach((componentKey) => {
+ const componentValue = componentsMap.get(componentKey)
+ const { defaultRules, name } = componentValue
+ rulesToEditorFriendly(
+ defaultRules.map((rule) => ({ ...rule, component: name })),
+ root
+ )
+ })
+
+ return root
+ })
+
+ // Checking whether component can support some "directives" which
+ // are actually virtual subcomponents, i.e. Text, Link etc
+ exports.componentHas = (subComponent) => {
+ return !!selectedComponent.value.validInnerComponents?.find(x => x === subComponent)
+ }
+
+ // Path for lodash's get and set
+ const getPath = (component, directive) => {
+ const pathSuffix = component ? `._children.${component}.normal.normal` : ''
+ const path = `${selectedComponentName.value}.${selectedVariant.value}.${normalizeStates([...selectedState])}${pathSuffix}.directives.${directive}`
+ return path
+ }
+
+ // Templates for directives
+ const isElementPresent = (component, directive, defaultValue = '') => computed({
+ get () {
+ return get(allEditedRules.value, getPath(component, directive)) != null
+ },
+ set (value) {
+ if (value) {
+ const fallback = get(
+ editorFriendlyFallbackStructure.value,
+ getPath(component, directive)
+ )
+ set(allEditedRules.value, getPath(component, directive), fallback ?? defaultValue)
+ } else {
+ unset(allEditedRules.value, getPath(component, directive))
+ }
+ exports.updateOverallPreview()
+ }
+ })
+
+ const getEditedElement = (component, directive, postProcess = x => x) => computed({
+ get () {
+ let usedRule
+ const fallback = editorFriendlyFallbackStructure.value
+ const real = allEditedRules.value
+ const path = getPath(component, directive)
+
+ usedRule = get(real, path) // get real
+ if (!usedRule) {
+ usedRule = get(fallback, path)
+ }
+
+ return postProcess(usedRule)
+ },
+ set (value) {
+ if (value) {
+ set(allEditedRules.value, getPath(component, directive), value)
+ } else {
+ unset(allEditedRules.value, getPath(component, directive))
+ }
+ exports.updateOverallPreview()
+ }
+ })
+
+ // All the editable stuff for the component
+ exports.editedBackgroundColor = getEditedElement(null, 'background')
+ exports.isBackgroundColorPresent = isElementPresent(null, 'background', '#FFFFFF')
+ exports.editedOpacity = getEditedElement(null, 'opacity')
+ exports.isOpacityPresent = isElementPresent(null, 'opacity', 1)
+ exports.editedRoundness = getEditedElement(null, 'roundness')
+ exports.isRoundnessPresent = isElementPresent(null, 'roundness', '0')
+ exports.editedTextColor = getEditedElement('Text', 'textColor')
+ exports.isTextColorPresent = isElementPresent('Text', 'textColor', '#000000')
+ exports.editedTextAuto = getEditedElement('Text', 'textAuto')
+ exports.isTextAutoPresent = isElementPresent('Text', 'textAuto', '#000000')
+ exports.editedLinkColor = getEditedElement('Link', 'textColor')
+ exports.isLinkColorPresent = isElementPresent('Link', 'textColor', '#000080')
+ exports.editedIconColor = getEditedElement('Icon', 'textColor')
+ exports.isIconColorPresent = isElementPresent('Icon', 'textColor', '#909090')
+ exports.editedBorderColor = getEditedElement('Border', 'textColor')
+ exports.isBorderColorPresent = isElementPresent('Border', 'textColor', '#909090')
+
+ const getContrast = (bg, text) => {
+ try {
+ const bgRgb = hex2rgb(bg)
+ const textRgb = hex2rgb(text)
+
+ const ratio = getContrastRatio(bgRgb, textRgb)
+ return {
+ // TODO this ideally should be part of <ContractRatio />
+ ratio,
+ text: ratio.toPrecision(3) + ':1',
+ // AA level, AAA level
+ aa: ratio >= 4.5,
+ aaa: ratio >= 7,
+ // same but for 18pt+ texts
+ laa: ratio >= 3,
+ laaa: ratio >= 4.5
+ }
+ } catch (e) {
+ console.warn('Failure computing contrast', e)
+ return { error: e }
+ }
+ }
+
+ const normalizeShadows = (shadows) => {
+ return shadows?.map(shadow => {
+ if (typeof shadow === 'object') {
+ return shadow
+ }
+ if (typeof shadow === 'string') {
+ try {
+ return deserializeShadow(shadow)
+ } catch (e) {
+ console.warn(e)
+ return shadow
+ }
+ }
+ return null
+ })
+ }
+ provide('normalizeShadows', normalizeShadows)
+
+ // Shadow is partially edited outside the ShadowControl
+ // for better space utilization
+ const editedShadow = getEditedElement(null, 'shadow', normalizeShadows)
+ exports.editedShadow = editedShadow
+ const editedSubShadowId = ref(null)
+ exports.editedSubShadowId = editedSubShadowId
+ const editedSubShadow = computed(() => {
+ if (editedShadow.value == null || editedSubShadowId.value == null) return null
+ return editedShadow.value[editedSubShadowId.value]
+ })
+ exports.editedSubShadow = editedSubShadow
+ exports.isShadowPresent = isElementPresent(null, 'shadow', [])
+ exports.onSubShadow = (id) => {
+ if (id != null) {
+ editedSubShadowId.value = id
+ } else {
+ editedSubShadow.value = null
+ }
+ }
+ exports.updateSubShadow = (axis, value) => {
+ if (!editedSubShadow.value || editedSubShadowId.value == null) return
+ const newEditedShadow = [...editedShadow.value]
+
+ newEditedShadow[editedSubShadowId.value] = {
+ ...newEditedShadow[editedSubShadowId.value],
+ [axis]: value
+ }
+
+ editedShadow.value = newEditedShadow
+ }
+ exports.isShadowTabOpen = ref(false)
+ exports.onTabSwitch = (tab) => {
+ exports.isShadowTabOpen.value = tab === 'shadow'
+ }
+
+ // component preview
+ exports.editorHintStyle = computed(() => {
+ const editorHint = selectedComponent.value.editor
+ const styles = []
+ if (editorHint && Object.keys(editorHint).length > 0) {
+ if (editorHint.aspect != null) {
+ styles.push(`aspect-ratio: ${editorHint.aspect} !important;`)
+ }
+ if (editorHint.border != null) {
+ styles.push(`border-width: ${editorHint.border}px !important;`)
+ }
+ }
+ return styles.join('; ')
+ })
+
+ const editorFriendlyToOriginal = computed(() => {
+ const resultRules = []
+
+ const convert = (component, data = {}, parent) => {
+ const variants = Object.entries(data || {})
+
+ variants.forEach(([variant, variantData]) => {
+ const states = Object.entries(variantData)
+
+ states.forEach(([jointState, stateData]) => {
+ const state = jointState.split(/:/g)
+ const result = {
+ component,
+ variant,
+ state,
+ directives: stateData.directives || {}
+ }
+
+ if (parent) {
+ result.parent = {
+ component: parent
+ }
+ }
+
+ resultRules.push(result)
+
+ // Currently we only support single depth for simplicity's sake
+ if (!parent) {
+ Object.entries(stateData._children || {}).forEach(([cName, child]) => convert(cName, child, component))
+ }
+ })
+ })
+ }
+
+ [...componentsMap.values()].forEach(({ name }) => {
+ convert(name, allEditedRules.value[name])
+ })
+
+ return resultRules
+ })
+
+ const allCustomVirtualDirectives = [...componentsMap.values()]
+ .map(c => {
+ return c
+ .defaultRules
+ .filter(c => c.component === 'Root')
+ .map(x => Object.entries(x.directives))
+ .flat()
+ })
+ .filter(x => x)
+ .flat()
+ .map(([name, value]) => {
+ const [valType, valVal] = value.split('|')
+ return {
+ name: name.substring(2),
+ valType: valType?.trim(),
+ value: valVal?.trim()
+ }
+ })
+
+ const virtualDirectives = ref(allCustomVirtualDirectives)
+ exports.virtualDirectives = virtualDirectives
+ exports.updateVirtualDirectives = (value) => {
+ virtualDirectives.value = value
+ }
+
+ // Raw format
+ const virtualDirectivesRule = computed(() => ({
+ component: 'Root',
+ directives: Object.fromEntries(
+ virtualDirectives.value.map(vd => [`--${vd.name}`, `${vd.valType} | ${vd.value}`])
+ )
+ }))
+
+ // Text format
+ const virtualDirectivesOut = computed(() => {
+ return [
+ 'Root {',
+ ...virtualDirectives.value
+ .filter(vd => vd.name && vd.valType && vd.value)
+ .map(vd => ` --${vd.name}: ${vd.valType} | ${vd.value};`),
+ '}'
+ ].join('\n')
+ })
+
+ exports.computeColor = (color) => {
+ let computedColor
+ try {
+ computedColor = findColor(color, { dynamicVars: dynamicVars.value, staticVars: staticVars.value })
+ if (computedColor) {
+ return rgb2hex(computedColor)
+ }
+ } catch (e) {
+ console.warn(e)
+ }
+ return null
+ }
+ provide('computeColor', exports.computeColor)
+
+ exports.contrast = computed(() => {
+ return getContrast(
+ exports.computeColor(previewColors.value.background),
+ exports.computeColor(previewColors.value.text)
+ )
+ })
+
+ // ## Export and Import
+ const styleExporter = newExporter({
+ filename: () => exports.name.value ?? 'pleroma_theme',
+ mime: 'text/plain',
+ extension: 'piss',
+ getExportedObject: () => exportStyleData.value
+ })
+
+ const onImport = parsed => {
+ const editorComponents = parsed.filter(x => x.component.startsWith('@'))
+ const rootComponent = parsed.find(x => x.component === 'Root')
+ const rules = parsed.filter(x => !x.component.startsWith('@') && x.component !== 'Root')
+ const metaIn = editorComponents.find(x => x.component === '@meta').directives
+ const palettesIn = editorComponents.filter(x => x.component === '@palette')
+
+ exports.name.value = metaIn.name
+ exports.license.value = metaIn.license
+ exports.author.value = metaIn.author
+ exports.website.value = metaIn.website
+
+ const newVirtualDirectives = Object
+ .entries(rootComponent.directives)
+ .map(([name, value]) => {
+ const [valType, valVal] = value.split('|').map(x => x.trim())
+ return { name: name.substring(2), valType, value: valVal }
+ })
+ virtualDirectives.value = newVirtualDirectives
+
+ onPalettesUpdate(palettesIn.map(x => ({ name: x.variant, ...x.directives })))
+
+ allEditedRules.value = rulesToEditorFriendly(rules)
+
+ exports.updateOverallPreview()
+ }
+
+ const styleImporter = newImporter({
+ accept: '.piss',
+ parser (string) { return deserialize(string) },
+ onImportFailure (result) {
+ console.error('Failure importing style:', result)
+ this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_theme_imported', level: 'error' })
+ },
+ onImport
+ })
+
+ // Raw format
+ const exportRules = computed(() => [
+ metaRule.value,
+ ...palettesRule.value,
+ virtualDirectivesRule.value,
+ ...editorFriendlyToOriginal.value
+ ])
+
+ // Text format
+ const exportStyleData = computed(() => {
+ return [
+ metaOut.value,
+ palettesOut.value,
+ virtualDirectivesOut.value,
+ serialize(editorFriendlyToOriginal.value)
+ ].join('\n\n')
+ })
+
+ exports.clearStyle = () => {
+ onImport(store.state.interface.styleDataUsed)
+ }
+
+ exports.exportStyle = () => {
+ styleExporter.exportData()
+ }
+
+ exports.importStyle = () => {
+ styleImporter.importData()
+ }
+
+ exports.applyStyle = () => {
+ store.dispatch('setStyleCustom', exportRules.value)
+ }
+
+ const overallPreviewRules = ref([])
+ exports.overallPreviewRules = overallPreviewRules
+
+ const overallPreviewCssRules = ref([])
+ watchEffect(throttle(() => {
+ try {
+ overallPreviewCssRules.value = getScopedVersion(
+ getCssRules(overallPreviewRules.value),
+ '#edited-style-preview'
+ ).join('\n')
+ } catch (e) {
+ console.error(e)
+ }
+ }, 500))
+
+ exports.overallPreviewCssRules = overallPreviewCssRules
+
+ const updateOverallPreview = throttle(() => {
+ try {
+ overallPreviewRules.value = init({
+ inputRuleset: [
+ ...exportRules.value,
+ {
+ component: 'Root',
+ directives: Object.fromEntries(
+ Object
+ .entries(selectedPalette.value)
+ .filter(([k, v]) => k && v && k !== 'name')
+ .map(([k, v]) => [`--${k}`, `color | ${v}`])
+ )
+ }
+ ],
+ ultimateBackgroundColor: '#000000',
+ debug: true
+ }).eager
+ } catch (e) {
+ console.error('Could not compile preview theme', e)
+ return null
+ }
+ }, 5000)
+ //
+ // Apart from "hover" we can't really show how component looks like in
+ // certain states, so we have to fake them.
+ const simulatePseudoSelectors = (css, prefix) => css
+ .replace(prefix, '.component-preview .preview-block')
+ .replace(':active', '.preview-active')
+ .replace(':hover', '.preview-hover')
+ .replace(':active', '.preview-active')
+ .replace(':focus', '.preview-focus')
+ .replace(':focus-within', '.preview-focus-within')
+ .replace(':disabled', '.preview-disabled')
+
+ const previewRules = computed(() => {
+ const filtered = overallPreviewRules.value.filter(r => {
+ const componentMatch = r.component === selectedComponentName.value
+ const parentComponentMatch = r.parent?.component === selectedComponentName.value
+ if (!componentMatch && !parentComponentMatch) return false
+ const rule = parentComponentMatch ? r.parent : r
+ if (rule.component !== selectedComponentName.value) return false
+ if (rule.variant !== selectedVariant.value) return false
+ const ruleState = new Set(rule.state.filter(x => x !== 'normal'))
+ const differenceA = [...ruleState].filter(x => !selectedState.has(x))
+ const differenceB = [...selectedState].filter(x => !ruleState.has(x))
+ return (differenceA.length + differenceB.length) === 0
+ })
+ const sorted = [...filtered]
+ .filter(x => x.component === selectedComponentName.value)
+ .sort((a, b) => {
+ const aSelectorLength = a.selector.split(/ /g).length
+ const bSelectorLength = b.selector.split(/ /g).length
+ return aSelectorLength - bSelectorLength
+ })
+
+ const prefix = sorted[0].selector
+
+ return filtered.filter(x => x.selector.startsWith(prefix))
+ })
+
+ exports.previewClass = computed(() => {
+ const selectors = []
+ if (!!selectedComponent.value.variants?.normal || selectedVariant.value !== 'normal') {
+ selectors.push(selectedComponent.value.variants[selectedVariant.value])
+ }
+ if (selectedState.size > 0) {
+ selectedState.forEach(state => {
+ const original = selectedComponent.value.states[state]
+ selectors.push(simulatePseudoSelectors(original))
+ })
+ }
+ return selectors.map(x => x.substring(1)).join('')
+ })
+
+ exports.previewCss = computed(() => {
+ try {
+ const prefix = previewRules.value[0].selector
+ const scoped = getCssRules(previewRules.value).map(x => simulatePseudoSelectors(x, prefix))
+ return scoped.join('\n')
+ } catch (e) {
+ console.error('Invalid ruleset', e)
+ return null
+ }
+ })
+
+ const dynamicVars = computed(() => {
+ return previewRules.value[0].dynamicVars
+ })
+
+ const staticVars = computed(() => {
+ const rootComponent = overallPreviewRules.value.find(r => {
+ return r.component === 'Root'
+ })
+ const rootDirectivesEntries = Object.entries(rootComponent.directives)
+ const directives = {}
+ rootDirectivesEntries
+ .filter(([k, v]) => k.startsWith('--') && v.startsWith('color | '))
+ .map(([k, v]) => [k.substring(2), v.substring('color | '.length)])
+ .forEach(([k, v]) => {
+ directives[k] = findColor(v, { dynamicVars: {}, staticVars: directives })
+ })
+ return directives
+ })
+ provide('staticVars', staticVars)
+ exports.staticVars = staticVars
+
+ const previewColors = computed(() => {
+ const stacked = dynamicVars.value.stacked
+ const background = typeof stacked === 'string' ? stacked : rgb2hex(stacked)
+ return {
+ text: previewRules.value.find(r => r.component === 'Text')?.virtualDirectives['--text'],
+ link: previewRules.value.find(r => r.component === 'Link')?.virtualDirectives['--link'],
+ border: previewRules.value.find(r => r.component === 'Border')?.virtualDirectives['--border'],
+ icon: previewRules.value.find(r => r.component === 'Icon')?.virtualDirectives['--icon'],
+ background
+ }
+ })
+ exports.previewColors = previewColors
+ exports.updateOverallPreview = updateOverallPreview
+
+ updateOverallPreview()
+
+ watch(
+ [
+ allEditedRules.value,
+ palettes,
+ selectedPalette,
+ selectedState,
+ selectedVariant
+ ],
+ updateOverallPreview
+ )
+
+ return exports
+ }
+}
diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.scss b/src/components/settings_modal/tabs/style_tab/style_tab.scss
@@ -0,0 +1,264 @@
+.StyleTab {
+ .style-control {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: baseline;
+ margin-bottom: 0.5em;
+
+ .label {
+ margin-right: 0.5em;
+ flex: 1 1 0;
+ line-height: 2;
+ min-height: 2em;
+ }
+
+ &.suboption {
+ margin-left: 1em;
+ }
+
+ .color-input {
+ flex: 0 0 0;
+ }
+
+ input,
+ select {
+ min-width: 3em;
+ margin: 0;
+ flex: 0;
+
+ &[type="number"] {
+ min-width: 9em;
+
+ &.-small {
+ min-width: 5em;
+ }
+ }
+
+ &[type="range"] {
+ flex: 1;
+ min-width: 9em;
+ align-self: center;
+ margin: 0 0.25em;
+ }
+
+ &[type="checkbox"] + i {
+ height: 1.1em;
+ align-self: center;
+ }
+ }
+ }
+
+ .meta-preview {
+ display: grid;
+ grid-template:
+ "meta meta preview preview"
+ "meta meta preview preview"
+ "meta meta preview preview"
+ "meta meta preview preview";
+ grid-gap: 0.5em;
+ grid-template-columns: min-content min-content 6fr max-content;
+
+ ul.setting-list {
+ padding: 0;
+ margin: 0;
+ display: grid;
+ grid-template-rows: subgrid;
+ grid-area: meta;
+
+ > li {
+ margin: 0;
+ }
+
+ .meta-field {
+ margin: 0;
+
+ .setting-label {
+ display: inline-block;
+ margin-bottom: 0.5em;
+ }
+ }
+ }
+
+ #edited-style-preview {
+ grid-area: preview;
+ }
+ }
+
+ .setting-item {
+ padding-bottom: 0;
+
+ .btn {
+ padding: 0 0.5em;
+ }
+
+ &:not(:first-child) {
+ margin-top: 0.5em;
+ }
+
+ &:not(:last-child) {
+ margin-bottom: 0.5em;
+ }
+ }
+
+ .list-editor {
+ display: grid;
+ grid-template-areas:
+ "label editor"
+ "selector editor"
+ "movement editor";
+ grid-template-columns: 10em 1fr;
+ grid-template-rows: auto 1fr auto;
+ grid-gap: 0.5em;
+
+ .list-edit-area {
+ grid-area: editor;
+ }
+
+ .list-select {
+ grid-area: selector;
+ margin: 0;
+
+ &-label {
+ font-weight: bold;
+ grid-area: label;
+ margin: 0;
+ align-self: baseline;
+ }
+
+ &-movement {
+ grid-area: movement;
+ margin: 0;
+ }
+ }
+ }
+
+ .palette-editor {
+ width: min-content;
+
+ .list-edit-area {
+ display: grid;
+ align-self: baseline;
+ grid-template-rows: subgrid;
+ grid-template-columns: 1fr;
+ }
+
+ .palette-editor-single {
+ grid-row: 2 / span 2;
+ }
+ }
+
+ .variables-editor {
+ .variable-selector {
+ display: grid;
+ grid-template-columns: auto 1fr auto 10em;
+ grid-template-rows: subgrid;
+ align-items: baseline;
+ grid-gap: 0 0.5em;
+ }
+
+ .list-edit-area {
+ display: grid;
+ grid-template-rows: subgrid;
+ }
+
+ .shadow-control {
+ grid-row: 2 / span 2;
+ }
+ }
+
+ .component-editor {
+ display: grid;
+ grid-template-columns: 6fr 3fr 4fr;
+ grid-template-rows: auto auto 1fr;
+ grid-gap: 0.5em;
+ grid-template-areas:
+ "component component variant"
+ "state state state"
+ "preview settings settings";
+
+ .component-selector {
+ grid-area: component;
+ align-self: center;
+ }
+
+ .component-selector,
+ .state-selector,
+ .variant-selector {
+ display: grid;
+ grid-template-columns: 1fr minmax(1fr, 10em);
+ grid-template-rows: auto;
+ grid-auto-flow: column;
+ grid-gap: 0.5em;
+ align-items: baseline;
+
+ > label:not(.Select) {
+ font-weight: bold;
+ justify-self: right;
+ }
+ }
+
+ .state-selector {
+ grid-area: state;
+ grid-template-columns: minmax(min-content, 7em) 1fr;
+ }
+
+ .variant-selector {
+ grid-area: variant;
+ }
+
+ .state-selector-list {
+ display: grid;
+ list-style: none;
+ grid-auto-flow: dense;
+ grid-template-columns: repeat(5, minmax(min-content, 1fr));
+ grid-auto-rows: 1fr;
+ grid-gap: 0.5em;
+ padding: 0;
+ margin: 0;
+ }
+
+ .preview-container {
+ --border: none;
+ --shadow: none;
+ --roundness: none;
+
+ grid-area: preview;
+ }
+
+ .component-settings {
+ grid-area: settings;
+ }
+
+ .editor-tab {
+ display: grid;
+ grid-template-columns: 1fr 2em;
+ grid-column-gap: 0.5em;
+ align-items: center;
+ grid-auto-rows: min-content;
+ grid-auto-flow: dense;
+ border-left: 1px solid var(--border);
+ border-right: 1px solid var(--border);
+ border-bottom: 1px solid var(--border);
+ padding: 0.5em;
+ }
+
+ .shadow-tab {
+ grid-template-columns: 1fr;
+ justify-items: center;
+ }
+ }
+}
+
+.extra-content {
+ .style-actions-container {
+ width: 100%;
+ display: flex;
+ justify-content: end;
+
+ .style-actions {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(7em, 1fr));
+ grid-gap: 0.25em;
+ }
+ }
+}
diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.vue b/src/components/settings_modal/tabs/style_tab/style_tab.vue
@@ -0,0 +1,402 @@
+<script src="./style_tab.js">
+</script>
+
+<template>
+ <div class="StyleTab">
+ <div class="setting-item heading">
+ <h2> {{ $t('settings.style.themes3.editor.title') }} </h2>
+ <div class="meta-preview">
+ <!-- eslint-disable vue/no-v-text-v-html-on-component -->
+ <!-- eslint-disable vue/no-v-html -->
+ <component
+ :is="'style'"
+ v-html="overallPreviewCssRules"
+ />
+ <!-- eslint-enable vue/no-v-html -->
+ <!-- eslint-enable vue/no-v-text-v-html-on-component -->
+ <Preview id="edited-style-preview" />
+ <teleport
+ v-if="isActive"
+ to="#unscrolled-content"
+ >
+ <div class="style-actions-container">
+ <div class="style-actions">
+ <button
+ class="btn button-default button-new"
+ @click="clearStyle"
+ >
+ <FAIcon icon="arrows-rotate" />
+ {{ $t('settings.style.themes3.editor.reset_style') }}
+ </button>
+ <button
+ class="btn button-default button-load"
+ @click="importStyle"
+ >
+ <FAIcon icon="folder-open" />
+ {{ $t('settings.style.themes3.editor.load_style') }}
+ </button>
+ <button
+ class="btn button-default button-save"
+ @click="exportStyle"
+ >
+ <FAIcon icon="floppy-disk" />
+ {{ $t('settings.style.themes3.editor.save_style') }}
+ </button>
+ <button
+ class="btn button-default button-apply"
+ @click="applyStyle"
+ >
+ <FAIcon icon="check" />
+ {{ $t('settings.style.themes3.editor.apply_preview') }}
+ </button>
+ </div>
+ </div>
+ </teleport>
+ <ul class="setting-list style-metadata">
+ <li>
+ <StringSetting
+ v-model="name"
+ class="meta-field"
+ >
+ {{ $t('settings.style.themes3.editor.style_name') }}
+ </StringSetting>
+ </li>
+ <li>
+ <StringSetting
+ v-model="author"
+ class="meta-field"
+ >
+ {{ $t('settings.style.themes3.editor.style_author') }}
+ </StringSetting>
+ </li>
+ <li>
+ <StringSetting
+ v-model="license"
+ class="meta-field"
+ >
+ {{ $t('settings.style.themes3.editor.style_license') }}
+ </StringSetting>
+ </li>
+ <li>
+ <StringSetting
+ v-model="website"
+ class="meta-field"
+ >
+ {{ $t('settings.style.themes3.editor.style_website') }}
+ </StringSetting>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <tab-switcher>
+ <div
+ key="component"
+ class="setting-item component-editor"
+ :label="$t('settings.style.themes3.editor.component_tab')"
+ >
+ <div class="component-selector">
+ <label for="component-selector">
+ {{ $t('settings.style.themes3.editor.component_selector') }}
+ {{ ' ' }}
+ </label>
+ <Select
+ id="component-selector"
+ v-model="selectedComponentKey"
+ >
+ <option
+ v-for="key in componentKeys"
+ :key="'component-' + key"
+ :value="key"
+ >
+ {{ componentsMap.get(key).name }}
+ </option>
+ </Select>
+ </div>
+ <div
+ v-if="selectedComponentVariants.length > 1"
+ class="variant-selector"
+ >
+ <label for="variant-selector">
+ {{ $t('settings.style.themes3.editor.variant_selector') }}
+ </label>
+ <Select
+ v-model="selectedVariant"
+ >
+ <option
+ v-for="variant in selectedComponentVariants"
+ :key="'component-variant-' + variant"
+ :value="variant"
+ >
+ {{ variant }}
+ </option>
+ </Select>
+ </div>
+ <div
+ v-if="selectedComponentStates.length > 0"
+ class="state-selector"
+ >
+ <label>
+ {{ $t('settings.style.themes3.editor.states_selector') }}
+ </label>
+ <ul
+ class="state-selector-list"
+ >
+ <li
+ v-for="state in selectedComponentStates"
+ :key="'component-state-' + state"
+ >
+ <Checkbox
+ :value="selectedState.has(state)"
+ @update:modelValue="(v) => updateSelectedStates(state, v)"
+ >
+ {{ state }}
+ </Checkbox>
+ </li>
+ </ul>
+ </div>
+ <div class="preview-container">
+ <!-- eslint-disable vue/no-v-html vue/no-v-text-v-html-on-component -->
+ <component
+ :is="'style'"
+ v-html="previewCss"
+ />
+ <!-- eslint-enable vue/no-v-html vue/no-v-text-v-html-on-component -->
+ <ComponentPreview
+ class="component-preview"
+ :show-text="componentHas('Text')"
+ :shadow-control="isShadowTabOpen"
+ :preview-class="previewClass"
+ :preview-style="editorHintStyle"
+ :preview-css="previewCss"
+ :disabled="!editedSubShadow && typeof editedShadow !== 'string'"
+ :shadow="editedSubShadow"
+ :no-color-control="true"
+ @update:shadow="({ axis, value }) => updateSubShadow(axis, value)"
+ />
+ </div>
+ <tab-switcher
+ ref="tabSwitcher"
+ class="component-settings"
+ :on-switch="onTabSwitch"
+ >
+ <div
+ key="main"
+ class="editor-tab"
+ :label="$t('settings.style.themes3.editor.main_tab')"
+ >
+ <ColorInput
+ v-model="editedBackgroundColor"
+ name="component-background-color"
+ :fallback="computeColor(editedBackgroundColor) ?? previewColors.background"
+ :disabled="!isBackgroundColorPresent"
+ :label="$t('settings.style.themes3.editor.background')"
+ :hide-optional-checkbox="true"
+ />
+ <Tooltip :text="$t('settings.style.themes3.editor.include_in_rule')">
+ <Checkbox v-model="isBackgroundColorPresent" />
+ </Tooltip>
+ <ColorInput
+ v-if="componentHas('Text')"
+ v-model="editedTextColor"
+ name="component-text-color"
+ :fallback="computeColor(editedTextColor) ?? previewColors.text"
+ :label="$t('settings.style.themes3.editor.text_color')"
+ :disabled="!isTextColorPresent"
+ :hide-optional-checkbox="true"
+ />
+ <Tooltip
+ v-if="componentHas('Text')"
+ :text="$t('settings.style.themes3.editor.include_in_rule')"
+ >
+ <Checkbox v-model="isTextColorPresent" />
+ </Tooltip>
+ <div
+ v-if="componentHas('Text')"
+ class="style-control suboption"
+ >
+ <label
+ for="textAuto"
+ class="label"
+ :class="{ faint: !isTextAutoPresent }"
+ >
+ {{ $t('settings.style.themes3.editor.text_auto.label') }}
+ </label>
+ <Select
+ id="textAuto"
+ v-model="editedTextAuto"
+ :disabled="!isTextAutoPresent"
+ >
+ <option value="no-preserve">
+ {{ $t('settings.style.themes3.editor.text_auto.no-preserve') }}
+ </option>
+ <option value="no-auto">
+ {{ $t('settings.style.themes3.editor.text_auto.no-auto') }}
+ </option>
+ <option value="preserve">
+ {{ $t('settings.style.themes3.editor.text_auto.preserve') }}
+ </option>
+ </Select>
+ </div>
+ <Tooltip
+ v-if="componentHas('Text')"
+ :text="$t('settings.style.themes3.editor.include_in_rule')"
+ >
+ <Checkbox v-model="isTextAutoPresent" />
+ </Tooltip>
+ <div
+ v-if="componentHas('Text')"
+ class="style-control suboption"
+ >
+ <label class="label">
+ {{ $t('settings.style.themes3.editor.contrast') }}
+ </label>
+ <ContrastRatio
+ :show-ratio="true"
+ :contrast="contrast"
+ />
+ </div>
+ <div v-if="componentHas('Text')" />
+ <ColorInput
+ v-if="componentHas('Link')"
+ v-model="editedLinkColor"
+ name="component-link-color"
+ :fallback="computeColor(editedLinkColor) ?? previewColors.link"
+ :label="$t('settings.style.themes3.editor.link_color')"
+ :disabled="!isLinkColorPresent"
+ :hide-optional-checkbox="true"
+ />
+ <Tooltip
+ v-if="componentHas('Link')"
+ :text="$t('settings.style.themes3.editor.include_in_rule')"
+ >
+ <Checkbox v-model="isLinkColorPresent" />
+ </Tooltip>
+ <ColorInput
+ v-if="componentHas('Icon')"
+ v-model="editedIconColor"
+ name="component-icon-color"
+ :fallback="computeColor(editedIconColor) ?? previewColors.icon"
+ :label="$t('settings.style.themes3.editor.icon_color')"
+ :disabled="!isIconColorPresent"
+ :hide-optional-checkbox="true"
+ />
+ <Tooltip
+ v-if="componentHas('Icon')"
+ :text="$t('settings.style.themes3.editor.include_in_rule')"
+ >
+ <Checkbox v-model="isIconColorPresent" />
+ </Tooltip>
+ <ColorInput
+ v-if="componentHas('Border')"
+ v-model="editedBorderColor"
+ name="component-border-color"
+ :fallback="computeColor(editedBorderColor) ?? previewColors.border"
+ :label="$t('settings.style.themes3.editor.border_color')"
+ :disabled="!isBorderColorPresent"
+ :hide-optional-checkbox="true"
+ />
+ <Tooltip
+ v-if="componentHas('Border')"
+ :text="$t('settings.style.themes3.editor.include_in_rule')"
+ >
+ <Checkbox v-model="isBorderColorPresent" />
+ </Tooltip>
+ <OpacityInput
+ v-model="editedOpacity"
+ name="component-opacity"
+ :disabled="!isOpacityPresent"
+ :label="$t('settings.style.themes3.editor.opacity')"
+ />
+ <Tooltip :text="$t('settings.style.themes3.editor.include_in_rule')">
+ <Checkbox v-model="isOpacityPresent" />
+ </Tooltip>
+ <RoundnessInput
+ v-model="editedRoundness"
+ name="component-roundness"
+ :disabled="!isRoundnessPresent"
+ :label="$t('settings.style.themes3.editor.roundness')"
+ />
+ <Tooltip :text="$t('settings.style.themes3.editor.include_in_rule')">
+ <Checkbox v-model="isRoundnessPresent" />
+ </Tooltip>
+ </div>
+ <div
+ key="shadow"
+ class="editor-tab shadow-tab"
+ :label="$t('settings.style.themes3.editor.shadows_tab')"
+ >
+ <Checkbox
+ v-model="isShadowPresent"
+ class="style-control"
+ >
+ {{ $t('settings.style.themes3.editor.include_in_rule') }}
+ </checkbox>
+ <ShadowControl
+ v-model="editedShadow"
+ :disabled="!isShadowPresent"
+ :no-preview="true"
+ :compact="true"
+ :static-vars="staticVars"
+ @subShadowSelected="onSubShadow"
+ />
+ </div>
+ </tab-switcher>
+ </div>
+ <div
+ key="palette"
+ :label="$t('settings.style.themes3.editor.palette_tab')"
+ class="setting-item list-editor palette-editor"
+ >
+ <label
+ class="list-select-label"
+ for="palette-selector"
+ >
+ {{ $t('settings.style.themes3.palette.label') }}
+ {{ ' ' }}
+ </label>
+ <Select
+ id="palette-selector"
+ v-model="selectedPaletteId"
+ class="list-select"
+ size="4"
+ >
+ <option
+ v-for="(p, index) in palettes"
+ :key="p.name"
+ :value="index"
+ >
+ {{ p.name }}
+ </option>
+ </Select>
+ <SelectMotion
+ class="list-select-movement"
+ :model-value="palettes"
+ :selected-id="selectedPaletteId"
+ :get-add-value="getNewPalette"
+ @update:modelValue="onPalettesUpdate"
+ @update:selectedId="e => selectedPaletteId = e"
+ />
+ <div class="list-edit-area">
+ <StringSetting
+ v-model="selectedPalette.name"
+ class="palette-name-input"
+ >
+ {{ $t('settings.style.themes3.palette.name_label') }}
+ </StringSetting>
+ <PaletteEditor
+ v-model="selectedPalette"
+ class="palette-editor-single"
+ />
+ </div>
+ </div>
+ <VirtualDirectivesTab
+ key="variables"
+ :label="$t('settings.style.themes3.editor.variables_tab')"
+ :model-value="virtualDirectives"
+ @update:modelValue="updateVirtualDirectives"
+ />
+ </tab-switcher>
+ </div>
+</template>
+
+<style src="./style_tab.scss" lang="scss"></style>
diff --git a/src/components/settings_modal/tabs/style_tab/virtual_directives_tab.js b/src/components/settings_modal/tabs/style_tab/virtual_directives_tab.js
@@ -0,0 +1,132 @@
+import { ref, computed, watch, inject } from 'vue'
+
+import Select from 'src/components/select/select.vue'
+import SelectMotion from 'src/components/select/select_motion.vue'
+import ShadowControl from 'src/components/shadow_control/shadow_control.vue'
+import ColorInput from 'src/components/color_input/color_input.vue'
+
+import { serializeShadow } from 'src/services/theme_data/iss_serializer.js'
+
+// helper for debugging
+// eslint-disable-next-line no-unused-vars
+const toValue = (x) => JSON.parse(JSON.stringify(x === undefined ? 'null' : x))
+
+export default {
+ components: {
+ Select,
+ SelectMotion,
+ ShadowControl,
+ ColorInput
+ },
+ props: ['modelValue'],
+ emits: ['update:modelValue'],
+ setup (props, context) {
+ const exports = {}
+ const emit = context.emit
+
+ exports.emit = emit
+ exports.computeColor = inject('computeColor')
+ exports.staticVars = inject('staticVars')
+
+ const selectedVirtualDirectiveId = ref(0)
+ exports.selectedVirtualDirectiveId = selectedVirtualDirectiveId
+
+ const selectedVirtualDirective = computed({
+ get () {
+ return props.modelValue[selectedVirtualDirectiveId.value]
+ },
+ set (value) {
+ const newVD = [...props.modelValue]
+ newVD[selectedVirtualDirectiveId.value] = value
+
+ emit('update:modelValue', newVD)
+ }
+ })
+ exports.selectedVirtualDirective = selectedVirtualDirective
+
+ exports.selectedVirtualDirectiveValType = computed({
+ get () {
+ return props.modelValue[selectedVirtualDirectiveId.value].valType
+ },
+ set (value) {
+ const newValType = value
+ let newValue
+ switch (value) {
+ case 'shadow':
+ newValue = '0 0 0 #000000 / 1'
+ break
+ case 'color':
+ newValue = '#000000'
+ break
+ default:
+ newValue = 'none'
+ }
+ const newName = props.modelValue[selectedVirtualDirectiveId.value].name
+ props.modelValue[selectedVirtualDirectiveId.value] = {
+ name: newName,
+ value: newValue,
+ valType: newValType
+ }
+ }
+ })
+
+ const draftVirtualDirectiveValid = ref(true)
+ const draftVirtualDirective = ref({})
+ exports.draftVirtualDirective = draftVirtualDirective
+ const normalizeShadows = inject('normalizeShadows')
+
+ watch(
+ selectedVirtualDirective,
+ (directive) => {
+ switch (directive.valType) {
+ case 'shadow': {
+ if (Array.isArray(directive.value)) {
+ draftVirtualDirective.value = normalizeShadows(directive.value)
+ } else {
+ const splitShadow = directive.value.split(/,/g).map(x => x.trim())
+ draftVirtualDirective.value = normalizeShadows(splitShadow)
+ }
+ break
+ }
+ case 'color':
+ draftVirtualDirective.value = directive.value
+ break
+ default:
+ draftVirtualDirective.value = directive.value
+ break
+ }
+ },
+ { immediate: true }
+ )
+
+ watch(
+ draftVirtualDirective,
+ (directive) => {
+ try {
+ switch (selectedVirtualDirective.value.valType) {
+ case 'shadow': {
+ props.modelValue[selectedVirtualDirectiveId.value].value =
+ directive.map(x => serializeShadow(x)).join(', ')
+ break
+ }
+ default:
+ props.modelValue[selectedVirtualDirectiveId.value].value = directive
+ }
+ draftVirtualDirectiveValid.value = true
+ } catch (e) {
+ console.error('Invalid virtual directive value', e)
+ draftVirtualDirectiveValid.value = false
+ }
+ },
+ { immediate: true }
+ )
+
+ exports.getNewVirtualDirective = () => ({
+ name: 'newDirective',
+ valType: 'generic',
+ value: 'foobar'
+ })
+
+ return exports
+ }
+}
diff --git a/src/components/settings_modal/tabs/style_tab/virtual_directives_tab.vue b/src/components/settings_modal/tabs/style_tab/virtual_directives_tab.vue
@@ -0,0 +1,84 @@
+<script src="./virtual_directives_tab.js"></script>
+
+<template>
+ <div class="setting-item list-editor variables-editor">
+ <label
+ class="list-select-label"
+ for="variables-selector"
+ >
+ {{ $t('settings.style.themes3.editor.variables.label') }}
+ {{ ' ' }}
+ </label>
+ <Select
+ id="variables-selector"
+ v-model="selectedVirtualDirectiveId"
+ class="list-select"
+ size="20"
+ >
+ <option
+ v-for="(p, index) in modelValue"
+ :key="p.name"
+ :value="index"
+ >
+ {{ p.name }}
+ </option>
+ </Select>
+ <SelectMotion
+ class="list-select-movement"
+ :model-value="modelValue"
+ :selected-id="selectedVirtualDirectiveId"
+ :get-add-value="getNewVirtualDirective"
+ @update:modelValue="e => emit('update:modelValue', e)"
+ @update:selectedId="e => selectedVirtualDirectiveId = e"
+ />
+ <div class="list-edit-area">
+ <div class="variable-selector">
+ <label
+ class="variable-name-label"
+ for="variables-selector"
+ >
+ {{ $t('settings.style.themes3.editor.variables.name_label') }}
+ {{ ' ' }}
+ </label>
+ <input
+ v-model="selectedVirtualDirective.name"
+ class="input"
+ >
+ <label
+ class="variable-type-label"
+ for="variables-selector"
+ >
+ {{ $t('settings.style.themes3.editor.variables.type_label') }}
+ {{ ' ' }}
+ </label>
+ <Select
+ v-model="selectedVirtualDirectiveValType"
+ >
+ <option value="shadow">
+ {{ $t('settings.style.themes3.editor.variables.type_shadow') }}
+ </option>
+ <option value="color">
+ {{ $t('settings.style.themes3.editor.variables.type_color') }}
+ </option>
+ <option value="generic">
+ {{ $t('settings.style.themes3.editor.variables.type_generic') }}
+ </option>
+ </Select>
+ </div>
+ <ShadowControl
+ v-if="selectedVirtualDirectiveValType === 'shadow'"
+ v-model="draftVirtualDirective"
+ :static-vars="staticVars"
+ :compact="true"
+ />
+ <ColorInput
+ name="virtual-directive-color"
+ v-if="selectedVirtualDirectiveValType === 'color'"
+ v-model="draftVirtualDirective"
+ :fallback="computeColor(draftVirtualDirective)"
+ :label="$t('settings.style.themes3.editor.variables.virtual_color')"
+ :hide-optional-checkbox="true"
+ />
+ </div>
+ </div>
+</template>
diff --git a/src/components/settings_modal/tabs/theme_tab/preview.vue b/src/components/settings_modal/tabs/theme_tab/preview.vue
@@ -1,145 +0,0 @@
-<template>
- <div class="preview-container">
- <div class="underlay underlay-preview" />
- <div class="panel dummy">
- <div class="panel-heading">
- <div class="title">
- {{ $t('settings.style.preview.header') }}
- <span class="badge badge-notification">
- 99
- </span>
- </div>
- <span class="faint">
- {{ $t('settings.style.preview.header_faint') }}
- </span>
- <span class="alert error">
- {{ $t('settings.style.preview.error') }}
- </span>
- <button class="btn button-default">
- {{ $t('settings.style.preview.button') }}
- </button>
- </div>
- <div class="panel-body theme-preview-content">
- <div class="post">
- <div class="avatar still-image">
- ( ͡° ͜ʖ ͡°)
- </div>
- <div class="content">
- <h4>
- {{ $t('settings.style.preview.content') }}
- </h4>
-
- <i18n-t scope="global" keypath="settings.style.preview.text">
- <code style="font-family: var(--postCodeFont)">
- {{ $t('settings.style.preview.mono') }}
- </code>
- <a style="color: var(--link)">
- {{ $t('settings.style.preview.link') }}
- </a>
- </i18n-t>
-
- <div class="icons">
- <FAIcon
- fixed-width
- style="color: var(--cBlue)"
- class="fa-scale-110 fa-old-padding"
- icon="reply"
- />
- <FAIcon
- fixed-width
- style="color: var(--cGreen)"
- class="fa-scale-110 fa-old-padding"
- icon="retweet"
- />
- <FAIcon
- fixed-width
- style="color: var(--cOrange)"
- class="fa-scale-110 fa-old-padding"
- icon="star"
- />
- <FAIcon
- fixed-width
- style="color: var(--cRed)"
- class="fa-scale-110 fa-old-padding"
- icon="times"
- />
- </div>
- </div>
- </div>
-
- <div class="after-post">
- <div class="avatar-alt">
- :^)
- </div>
- <div class="content">
- <i18n-t
- keypath="settings.style.preview.fine_print"
- tag="span"
- class="faint"
- scope="global"
- >
- <a style="color: var(--faintLink)">
- {{ $t('settings.style.preview.faint_link') }}
- </a>
- </i18n-t>
- </div>
- </div>
- <div class="separator" />
-
- <span class="alert error">
- {{ $t('settings.style.preview.error') }}
- </span>
- <input
- :value="$t('settings.style.preview.input')"
- type="text"
- >
-
- <div class="actions">
- <span class="checkbox">
- <input
- id="preview_checkbox"
- checked="very yes"
- type="checkbox"
- >
- <label for="preview_checkbox">{{ $t('settings.style.preview.checkbox') }}</label>
- </span>
- <button class="btn button-default">
- {{ $t('settings.style.preview.button') }}
- </button>
- </div>
- </div>
- </div>
- </div>
-</template>
-
-<script>
-import { library } from '@fortawesome/fontawesome-svg-core'
-import {
- faTimes,
- faStar,
- faRetweet,
- faReply
-} from '@fortawesome/free-solid-svg-icons'
-
-library.add(
- faTimes,
- faStar,
- faRetweet,
- faReply
-)
-
-export default {}
-</script>
-
-<style lang="scss">
-.preview-container {
- position: relative;
-}
-.underlay-preview {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 10px;
- right: 10px;
-}
-</style>
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_preview.vue b/src/components/settings_modal/tabs/theme_tab/theme_preview.vue
@@ -0,0 +1,250 @@
+<template>
+ <div class="theme-preview-container">
+ <div class="underlay underlay-preview" />
+ <div class="panel dummy">
+ <div class="panel-heading">
+ <h1 class="title">
+ {{ $t('settings.style.preview.header') }}
+ <span class="badge -notification">
+ 99
+ </span>
+ </h1>
+ <span class="faint">
+ {{ $t('settings.style.preview.header_faint') }}
+ </span>
+ <span class="alert error">
+ {{ $t('settings.style.preview.error') }}
+ </span>
+ <button class="btn button-default">
+ {{ $t('settings.style.preview.button') }}
+ </button>
+ </div>
+ <div class="panel-body theme-preview-content">
+ <div class="post">
+ <div class="avatar still-image">
+ ( ͡° ͜ʖ ͡°)
+ </div>
+ <div class="content">
+ <h4>
+ {{ $t('settings.style.preview.content') }}
+ </h4>
+
+ <i18n-t
+ scope="global"
+ keypath="settings.style.preview.text"
+ >
+ <code style="font-family: var(--postCodeFont);">
+ {{ $t('settings.style.preview.mono') }}
+ </code>
+ <a style="color: var(--link);">
+ {{ $t('settings.style.preview.link') }}
+ </a>
+ </i18n-t>
+
+ <div class="icons">
+ <FAIcon
+ fixed-width
+ style="color: var(--cBlue);"
+ class="fa-scale-110 fa-old-padding"
+ icon="reply"
+ />
+ <FAIcon
+ fixed-width
+ style="color: var(--cGreen);"
+ class="fa-scale-110 fa-old-padding"
+ icon="retweet"
+ />
+ <FAIcon
+ fixed-width
+ style="color: var(--cOrange);"
+ class="fa-scale-110 fa-old-padding"
+ icon="star"
+ />
+ <FAIcon
+ fixed-width
+ style="color: var(--cRed);"
+ class="fa-scale-110 fa-old-padding"
+ icon="times"
+ />
+ </div>
+ </div>
+ </div>
+
+ <div class="after-post">
+ <div class="avatar-alt">
+ :^)
+ </div>
+ <div class="content">
+ <i18n-t
+ keypath="settings.style.preview.fine_print"
+ tag="span"
+ class="faint"
+ scope="global"
+ >
+ <a style="color: var(--linkFaint);">
+ {{ $t('settings.style.preview.faint_link') }}
+ </a>
+ </i18n-t>
+ </div>
+ </div>
+ <div class="separator" />
+
+ <span class="alert error">
+ {{ $t('settings.style.preview.error') }}
+ </span>
+ <input
+ :value="$t('settings.style.preview.input')"
+ type="text"
+ class="input"
+ >
+
+ <div class="actions">
+ <Checkbox>
+ {{ $t('settings.style.preview.checkbox') }}
+ </Checkbox>
+ <button class="btn button-default">
+ {{ $t('settings.style.preview.button') }}
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script>
+import Checkbox from 'src/components/checkbox/checkbox.vue'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faTimes,
+ faStar,
+ faRetweet,
+ faReply
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faTimes,
+ faStar,
+ faRetweet,
+ faReply
+)
+
+export default {
+ components: {
+ Checkbox
+ }
+}
+</script>
+
+<style lang="scss">
+.theme-preview-container {
+ position: relative;
+ border-top: 1px dashed;
+ border-bottom: 1px dashed;
+ border-color: var(--border);
+ margin: 1em 0;
+ padding: 1em;
+ background-color: var(--wallpaper);
+ background-image: var(--body-background-image);
+ background-size: cover;
+ background-position: 50% 50%;
+
+ .theme-preview-content {
+ padding: 20px;
+ }
+
+ .dummy {
+ .post {
+ font-family: var(--postFont);
+ display: flex;
+
+ .content {
+ flex: 1;
+
+ h4 {
+ margin-bottom: 0.25em;
+ }
+
+ .icons {
+ margin-top: 0.5em;
+ display: flex;
+
+ i {
+ margin-right: 1em;
+ }
+ }
+ }
+ }
+
+ .after-post {
+ margin-top: 1em;
+ display: flex;
+ align-items: center;
+ }
+
+ .avatar,
+ .avatar-alt {
+ background:
+ linear-gradient(
+ 135deg,
+ #b8e1fc 0%,
+ #a9d2f3 10%,
+ #90bae4 25%,
+ #90bcea 37%,
+ #90bff0 50%,
+ #6ba8e5 51%,
+ #a2daf5 83%,
+ #bdf3fd 100%
+ );
+ color: black;
+ font-family: sans-serif;
+ text-align: center;
+ margin-right: 1em;
+ }
+
+ .avatar-alt {
+ flex: 0 auto;
+ margin-left: 28px;
+ font-size: 12px;
+ min-width: 20px;
+ min-height: 20px;
+ line-height: 20px;
+ }
+
+ .avatar {
+ flex: 0 auto;
+ width: 48px;
+ height: 48px;
+ font-size: 14px;
+ line-height: 48px;
+ }
+
+ .actions {
+ display: flex;
+ align-items: baseline;
+
+ .checkbox {
+ margin-right: 1em;
+ flex: 1;
+ }
+ }
+
+ .separator {
+ margin: 1em;
+ border-bottom: 1px solid;
+ border-color: var(--border);
+ }
+
+ .btn {
+ min-width: 3em;
+ }
+ }
+
+ .underlay-preview {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 10px;
+ right: 10px;
+ }
+}
+ </style>
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js
@@ -1,20 +1,10 @@
import {
rgb2hex,
hex2rgb,
- getContrastRatioLayers
+ getContrastRatioLayers,
+ relativeLuminance
} from 'src/services/color_convert/color_convert.js'
import {
- DEFAULT_SHADOWS,
- generateColors,
- generateShadows,
- generateRadii,
- generateFonts,
- composePreset,
- getThemes,
- shadows2to3,
- colors2to3
-} from 'src/services/style_setter/style_setter.js'
-import {
newImporter,
newExporter
} from 'src/services/export_import/export_import.js'
@@ -25,8 +15,23 @@ import {
CURRENT_VERSION,
OPACITIES,
getLayers,
- getOpacitySlot
+ getOpacitySlot,
+ DEFAULT_SHADOWS,
+ generateColors,
+ generateShadows,
+ generateRadii,
+ generateFonts,
+ shadows2to3,
+ colors2to3
} from 'src/services/theme_data/theme_data.service.js'
+
+import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
+import { init } from 'src/services/theme_data/theme_data_3.service.js'
+import {
+ getCssRules,
+ getScopedVersion
+} from 'src/services/theme_data/css_utils.js'
+
import ColorInput from 'src/components/color_input/color_input.vue'
import RangeInput from 'src/components/range_input/range_input.vue'
import OpacityInput from 'src/components/opacity_input/opacity_input.vue'
@@ -37,7 +42,7 @@ import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import Select from 'src/components/select/select.vue'
-import Preview from './preview.vue'
+import Preview from './theme_preview.vue'
// List of color values used in v1
const v1OnlyNames = [
@@ -62,6 +67,7 @@ const colorConvert = (color) => {
export default {
data () {
return {
+ themeV3Preview: [],
themeImporter: newImporter({
validator: this.importValidator,
onImport: this.onImport,
@@ -78,10 +84,7 @@ export default {
tempImportFile: undefined,
engineVersion: 0,
- previewShadows: {},
- previewColors: {},
- previewRadii: {},
- previewFonts: {},
+ previewTheme: {},
shadowsInvalid: true,
colorsInvalid: true,
@@ -95,11 +98,11 @@ export default {
...Object.keys(SLOT_INHERITANCE)
.map(key => [key, ''])
- .reduce((acc, [key, val]) => ({ ...acc, [ key + 'ColorLocal' ]: val }), {}),
+ .reduce((acc, [key, val]) => ({ ...acc, [key + 'ColorLocal']: val }), {}),
...Object.keys(OPACITIES)
.map(key => [key, ''])
- .reduce((acc, [key, val]) => ({ ...acc, [ key + 'OpacityLocal' ]: val }), {}),
+ .reduce((acc, [key, val]) => ({ ...acc, [key + 'OpacityLocal']: val }), {}),
shadowSelected: undefined,
shadowsLocal: {},
@@ -117,31 +120,24 @@ export default {
}
},
created () {
- const self = this
+ const currentIndex = this.$store.state.instance.themesIndex
- getThemes()
- .then((promises) => {
- return Promise.all(
- Object.entries(promises)
- .map(([k, v]) => v.then(res => [k, res]))
- )
- })
- .then(themes => themes.reduce((acc, [k, v]) => {
- if (v) {
- return {
- ...acc,
- [k]: v
- }
- } else {
- return acc
- }
- }, {}))
- .then((themesComplete) => {
- self.availableStyles = themesComplete
- })
+ let promise
+ if (currentIndex) {
+ promise = Promise.resolve(currentIndex)
+ } else {
+ promise = this.$store.dispatch('fetchThemesIndex')
+ }
+
+ promise.then(themesIndex => {
+ Object
+ .values(themesIndex)
+ .forEach(themeFunc => {
+ themeFunc().then(themeData => themeData && this.availableStyles.push(themeData))
+ })
+ })
},
mounted () {
- this.loadThemeFromLocalStorage()
if (typeof this.shadowSelected === 'undefined') {
this.shadowSelected = this.shadowsAvailable[0]
}
@@ -212,12 +208,12 @@ export default {
currentColors () {
return Object.keys(SLOT_INHERITANCE)
.map(key => [key, this[key + 'ColorLocal']])
- .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {})
+ .reduce((acc, [key, val]) => ({ ...acc, [key]: val }), {})
},
currentOpacity () {
return Object.keys(OPACITIES)
.map(key => [key, this[key + 'OpacityLocal']])
- .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {})
+ .reduce((acc, [key, val]) => ({ ...acc, [key]: val }), {})
},
currentRadii () {
return {
@@ -232,13 +228,6 @@ export default {
chatMessage: this.chatMessageRadiusLocal
}
},
- preview () {
- return composePreset(this.previewColors, this.previewRadii, this.previewShadows, this.previewFonts)
- },
- previewTheme () {
- if (!this.preview.theme.colors) return { colors: {}, opacity: {}, radii: {}, shadows: {}, fonts: {} }
- return this.preview.theme
- },
// This needs optimization maybe
previewContrast () {
try {
@@ -279,6 +268,9 @@ export default {
opacity
)
+ // Temporary patch for null-y value errors
+ if (layers.flat().some(v => v == null)) return acc
+
return {
...acc,
...textColors.reduce((acc, textColorKey) => {
@@ -300,15 +292,11 @@ export default {
return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
} catch (e) {
console.warn('Failure computing contrasts', e)
+ return {}
}
},
- previewRules () {
- if (!this.preview.rules) return ''
- return [
- ...Object.values(this.preview.rules),
- 'color: var(--text)',
- 'font-family: var(--interfaceFont, sans-serif)'
- ].join(';')
+ themeDataUsed () {
+ return this.$store.state.interface.themeDataUsed
},
shadowsAvailable () {
return Object.keys(DEFAULT_SHADOWS).sort()
@@ -319,7 +307,18 @@ export default {
},
set (val) {
if (val) {
- this.shadowsLocal[this.shadowSelected] = this.currentShadowFallback.map(_ => Object.assign({}, _))
+ this.shadowsLocal[this.shadowSelected] = (this.currentShadowFallback || [])
+ .map(s => ({
+ name: null,
+ x: 0,
+ y: 0,
+ blur: 0,
+ spread: 0,
+ inset: false,
+ color: '#000000',
+ alpha: 1,
+ ...s
+ }))
} else {
delete this.shadowsLocal[this.shadowSelected]
}
@@ -406,9 +405,6 @@ export default {
forceUseSource = false
) {
this.dismissWarning()
- if (!source && !theme) {
- throw new Error('Can\'t load theme: empty')
- }
const version = (origin === 'localStorage' && !theme.colors)
? 'l1'
: fileVersion
@@ -484,22 +480,11 @@ export default {
this.dismissWarning()
},
loadThemeFromLocalStorage (confirmLoadSource = false, forceSnapshot = false) {
- const {
- customTheme: theme,
- customThemeSource: source
- } = this.$store.getters.mergedConfig
- if (!theme && !source) {
- // Anon user or never touched themes
- this.loadTheme(
- this.$store.state.instance.themeData,
- 'defaults',
- confirmLoadSource
- )
- } else {
+ const theme = this.themeDataUsed?.source
+ if (theme) {
this.loadTheme(
{
- theme,
- source: forceSnapshot ? theme : source
+ theme
},
'localStorage',
confirmLoadSource
@@ -507,16 +492,15 @@ export default {
}
},
setCustomTheme () {
- this.$store.dispatch('setOption', {
- name: 'customTheme',
- value: {
+ this.$store.dispatch('setThemeV2', {
+ customTheme: {
+ ignore: true,
+ themeFileVersion: this.selectedVersion,
themeEngineVersion: CURRENT_VERSION,
...this.previewTheme
- }
- })
- this.$store.dispatch('setOption', {
- name: 'customThemeSource',
- value: {
+ },
+ customThemeSource: {
+ themeFileVersion: this.selectedVersion,
themeEngineVersion: CURRENT_VERSION,
shadows: this.shadowsLocal,
fonts: this.fontsLocal,
@@ -526,16 +510,24 @@ export default {
}
})
},
- updatePreviewColorsAndShadows () {
- this.previewColors = generateColors({
+ updatePreviewColors () {
+ const result = generateColors({
opacity: this.currentOpacity,
colors: this.currentColors
})
- this.previewShadows = generateShadows(
- { shadows: this.shadowsLocal, opacity: this.previewTheme.opacity, themeEngineVersion: this.engineVersion },
- this.previewColors.theme.colors,
- this.previewColors.mod
- )
+ this.previewTheme.colors = result.theme.colors
+ this.previewTheme.opacity = result.theme.opacity
+ },
+ updatePreviewShadows () {
+ this.previewTheme.shadows = generateShadows(
+ {
+ shadows: this.shadowsLocal,
+ opacity: this.previewTheme.opacity,
+ themeEngineVersion: this.engineVersion
+ },
+ this.previewTheme.colors,
+ relativeLuminance(this.previewTheme.colors.bg) < 0.5 ? 1 : -1
+ ).theme.shadows
},
importTheme () { this.themeImporter.importData() },
exportTheme () { this.themeExporter.exportData() },
@@ -604,7 +596,7 @@ export default {
normalizeLocalState (theme, version = 0, source, forceSource = false) {
let input
if (typeof source !== 'undefined') {
- if (forceSource || source.themeEngineVersion === CURRENT_VERSION) {
+ if (forceSource || source?.themeEngineVersion === CURRENT_VERSION) {
input = source
version = source.themeEngineVersion
} else {
@@ -686,6 +678,8 @@ export default {
} else {
this.shadowsLocal = shadows
}
+ this.updatePreviewColors()
+ this.updatePreviewShadows()
this.shadowSelected = this.shadowsAvailable[0]
}
@@ -693,12 +687,28 @@ export default {
this.clearFonts()
this.fontsLocal = fonts
}
+ },
+ updateTheme3Preview () {
+ const theme2 = convertTheme2To3(this.previewTheme)
+ const theme3 = init({
+ inputRuleset: theme2,
+ ultimateBackgroundColor: '#000000',
+ liteMode: true
+ })
+
+ this.themeV3Preview = getScopedVersion(
+ getCssRules(theme3.eager),
+ '#theme-preview'
+ ).join('\n')
}
},
watch: {
+ themeDataUsed () {
+ this.loadThemeFromLocalStorage()
+ },
currentRadii () {
try {
- this.previewRadii = generateRadii({ radii: this.currentRadii })
+ this.previewTheme.radii = generateRadii({ radii: this.currentRadii }).theme.radii
this.radiiInvalid = false
} catch (e) {
this.radiiInvalid = true
@@ -707,9 +717,8 @@ export default {
},
shadowsLocal: {
handler () {
- if (Object.getOwnPropertyNames(this.previewColors).length === 1) return
try {
- this.updatePreviewColorsAndShadows()
+ this.updatePreviewShadows()
this.shadowsInvalid = false
} catch (e) {
this.shadowsInvalid = true
@@ -721,7 +730,7 @@ export default {
fontsLocal: {
handler () {
try {
- this.previewFonts = generateFonts({ fonts: this.fontsLocal })
+ this.previewTheme.fonts = generateFonts({ fonts: this.fontsLocal }).theme.fonts
this.fontsInvalid = false
} catch (e) {
this.fontsInvalid = true
@@ -732,18 +741,16 @@ export default {
},
currentColors () {
try {
- this.updatePreviewColorsAndShadows()
+ this.updatePreviewColors()
this.colorsInvalid = false
- this.shadowsInvalid = false
} catch (e) {
this.colorsInvalid = true
- this.shadowsInvalid = true
console.warn(e)
}
},
currentOpacity () {
try {
- this.updatePreviewColorsAndShadows()
+ this.updatePreviewColors()
} catch (e) {
console.warn(e)
}
@@ -751,7 +758,6 @@ export default {
selected () {
this.selectedTheme = Object.entries(this.availableStyles).find(([k, s]) => {
if (Array.isArray(s)) {
- console.log(s[0] === this.selected, this.selected)
return s[0] === this.selected
} else {
return s.name === this.selected
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
@@ -1,56 +1,67 @@
-@import 'src/_variables.scss';
.theme-tab {
- padding-bottom: 2em;
- .theme-warning {
- display: flex;
- align-items: baseline;
- margin-bottom: .5em;
- .buttons {
- .btn {
- margin-bottom: .5em;
- }
- }
+ .deprecation-warning {
+ padding: 0.5em;
+ margin: 2em;
}
+
+ padding-bottom: 2em;
+
.preset-switcher {
margin-right: 1em;
}
+ .btn {
+ margin-left: 0.25em;
+ margin-right: 0.25em;
+ }
+
+ .btn-group .btn {
+ margin: 0;
+ }
+
.style-control {
display: flex;
align-items: baseline;
margin-bottom: 5px;
.label {
+ margin-right: 1em;
flex: 1;
- }
-
- &.disabled {
- input, select {
- opacity: .5
- }
+ line-height: 2;
}
.opt {
- margin: .5em;
+ margin: 0.5em;
}
.color-input {
flex: 0 0 0;
}
- input, select {
+ input,
+ select {
min-width: 3em;
margin: 0;
flex: 0;
- &[type=number] {
- min-width: 5em;
+ &[type="number"] {
+ min-width: 9em;
+
+ &.-small {
+ min-width: 5em;
+ }
}
- &[type=range] {
+ &[type="range"] {
flex: 1;
- min-width: 3em;
- align-self: flex-start;
+ min-width: 9em;
+ align-self: center;
+ margin: 0 0.5em;
+ }
+
+ &[type="checkbox"] + i {
+ height: 1.1em;
+ align-self: center;
}
}
}
@@ -63,8 +74,7 @@
.reset-container,
.apply-container,
.radius-container,
- .color-container,
- {
+ .color-container, {
display: flex;
}
@@ -73,10 +83,11 @@
flex-direction: column;
}
- .color-container{
+ .color-container {
> h4 {
width: 99%;
}
+
flex-wrap: wrap;
justify-content: space-between;
}
@@ -100,7 +111,7 @@
p {
flex: 1;
margin: 0;
- margin-right: .5em;
+ margin-right: 0.5em;
}
}
@@ -112,15 +123,16 @@
min-width: 1px;
flex: 0 auto;
padding: 0 1em;
- margin-bottom: .5em;
+ margin-bottom: 0.5em;
}
}
.shadow-selector {
.override {
flex: 1;
- margin-left: .5em;
+ margin-left: 0.5em;
}
+
.select-container {
margin-top: -4px;
margin-bottom: -3px;
@@ -136,7 +148,7 @@
.presets,
.import-export {
- margin-bottom: .5em;
+ margin-bottom: 0.5em;
}
.import-export {
@@ -144,146 +156,54 @@
}
.override {
- margin-left: .5em;
+ margin-left: 0.5em;
}
}
.save-load-options {
flex-wrap: wrap;
- margin-top: .5em;
+ margin-top: 0.5em;
justify-content: center;
+
.keep-option {
- margin: 0 .5em .5em;
+ margin: 0 0.5em 0.5em;
min-width: 25%;
}
}
- .preview-container {
- border-top: 1px dashed;
- border-bottom: 1px dashed;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- margin: 1em 0;
- padding: 1em;
- background-color: var(--wallpaper);
- background-image: var(--body-background-image);
- background-size: cover;
- background-position: 50% 50%;
-
- .dummy {
- .post {
- font-family: var(--postFont);
- display: flex;
-
- .content {
- flex: 1;
-
- h4 {
- margin-bottom: .25em;
- }
-
- .icons {
- margin-top: .5em;
- display: flex;
-
- i {
- margin-right: 1em;
- }
- }
- }
- }
-
- .after-post {
- margin-top: 1em;
- display: flex;
- align-items: center;
- }
-
- .avatar, .avatar-alt{
- background: linear-gradient(135deg, #b8e1fc 0%,#a9d2f3 10%,#90bae4 25%,#90bcea 37%,#90bff0 50%,#6ba8e5 51%,#a2daf5 83%,#bdf3fd 100%);
- color: black;
- font-family: sans-serif;
- text-align: center;
- margin-right: 1em;
- }
-
- .avatar-alt {
- flex: 0 auto;
- margin-left: 28px;
- font-size: 12px;
- min-width: 20px;
- min-height: 20px;
- line-height: 20px;
- border-radius: $fallback--avatarAltRadius;
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
- }
-
- .avatar {
- flex: 0 auto;
- width: 48px;
- height: 48px;
- font-size: 14px;
- line-height: 48px;
- }
-
- .actions {
- display: flex;
- align-items: baseline;
-
- .checkbox {
- display: inline-flex;
- align-items: baseline;
- margin-right: 1em;
- flex: 1;
- }
- }
-
- .separator {
- margin: 1em;
- border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- }
-
- .btn {
- min-width: 3em;
- }
- }
+ .radius-item {
+ flex-basis: auto;
}
.radius-item,
.color-item {
min-width: 20em;
margin: 5px 6px 0 0;
- display:flex;
+ display: flex;
flex-direction: column;
flex: 1 1 0;
&.wide {
- min-width: 60%
+ min-width: 60%;
}
&:not(.wide):nth-child(2n+1) {
margin-right: 7px;
-
}
- .color, .opacity {
- display:flex;
+ .color,
+ .opacity {
+ display: flex;
align-items: baseline;
}
}
- .radius-item {
- flex-basis: auto;
- }
-
.theme-radius-rn,
.theme-color-cl {
border: 0;
box-shadow: none;
background: transparent;
- color: var(--faint, $fallback--faint);
+ color: var(--textFaint);
align-self: stretch;
}
@@ -295,24 +215,24 @@
.theme-radius-in {
min-width: 1em;
- }
-
- .theme-radius-in {
max-width: 7em;
flex: 1;
}
- .theme-radius-lb{
+ .theme-radius-lb {
max-width: 50em;
}
- .theme-preview-content {
- padding: 20px;
- }
+ .theme-warning {
+ display: flex;
+ align-items: baseline;
+ margin-bottom: 0.5em;
- .btn {
- margin-left: .25em;
- margin-right: .25em;
+ .buttons {
+ .btn {
+ margin-bottom: 0.5em;
+ }
+ }
}
}
@@ -323,6 +243,7 @@
justify-content: space-around;
flex-grow: 1;
+ /* stylelint-disable-next-line no-descending-specificity */
.btn {
flex-grow: 1;
min-height: 2em;
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
@@ -1,5 +1,8 @@
<template>
<div class="theme-tab">
+ <div class="alert warning deprecation-warning">
+ {{ $t("settings.style.themes2_outdated") }}
+ </div>
<div class="presets-container">
<div class="save-load">
<div
@@ -120,7 +123,22 @@
</div>
</div>
- <preview :style="previewRules" />
+ <!-- eslint-disable vue/no-v-html vue/no-v-text-v-html-on-component -->
+ <component
+ :is="'style'"
+ v-html="themeV3Preview"
+ />
+ <!-- eslint-enable vue/no-v-html vue/no-v-text-v-html-on-component -->
+ <preview id="theme-preview" />
+
+ <div>
+ <button
+ class="btn button-default"
+ @click="updateTheme3Preview"
+ >
+ {{ $t("settings.style.update_preview") }}
+ </button>
+ </div>
<keep-alive>
<tab-switcher key="style-tweak">
@@ -156,7 +174,7 @@
<OpacityInput
v-model="bgOpacityLocal"
name="bgOpacity"
- :fallback="previewTheme.opacity.bg"
+ :fallback="previewTheme.opacity?.bg"
/>
<ColorInput
v-model="textColorLocal"
@@ -167,16 +185,16 @@
<ColorInput
v-model="accentColorLocal"
name="accentColor"
- :fallback="previewTheme.colors.link"
+ :fallback="previewTheme.colors?.link"
:label="$t('settings.accent')"
- :show-optional-tickbox="typeof linkColorLocal !== 'undefined'"
+ :show-optional-checkbox="typeof linkColorLocal !== 'undefined'"
/>
<ColorInput
v-model="linkColorLocal"
name="linkColor"
- :fallback="previewTheme.colors.accent"
+ :fallback="previewTheme.colors?.accent"
:label="$t('settings.links')"
- :show-optional-tickbox="typeof accentColorLocal !== 'undefined'"
+ :show-optional-checkbox="typeof accentColorLocal !== 'undefined'"
/>
<ContrastRatio :contrast="previewContrast.bgLink" />
</div>
@@ -190,13 +208,13 @@
v-model="fgTextColorLocal"
name="fgTextColor"
:label="$t('settings.text')"
- :fallback="previewTheme.colors.fgText"
+ :fallback="previewTheme.colors?.fgText"
/>
<ColorInput
v-model="fgLinkColorLocal"
name="fgLinkColor"
:label="$t('settings.links')"
- :fallback="previewTheme.colors.fgLink"
+ :fallback="previewTheme.colors?.fgLink"
/>
<p>{{ $t('settings.style.common_colors.foreground_hint') }}</p>
</div>
@@ -256,14 +274,14 @@
<ColorInput
v-model="postLinkColorLocal"
name="postLinkColor"
- :fallback="previewTheme.colors.accent"
+ :fallback="previewTheme.colors?.accent"
:label="$t('settings.links')"
/>
<ContrastRatio :contrast="previewContrast.postLink" />
<ColorInput
v-model="postGreentextColorLocal"
name="postGreentextColor"
- :fallback="previewTheme.colors.cGreen"
+ :fallback="previewTheme.colors?.cGreen"
:label="$t('settings.greentext')"
/>
<ContrastRatio :contrast="previewContrast.postGreentext" />
@@ -272,13 +290,13 @@
v-model="alertErrorColorLocal"
name="alertError"
:label="$t('settings.style.advanced_colors.alert_error')"
- :fallback="previewTheme.colors.alertError"
+ :fallback="previewTheme.colors?.alertError"
/>
<ColorInput
v-model="alertErrorTextColorLocal"
name="alertErrorText"
:label="$t('settings.text')"
- :fallback="previewTheme.colors.alertErrorText"
+ :fallback="previewTheme.colors?.alertErrorText"
/>
<ContrastRatio
:contrast="previewContrast.alertErrorText"
@@ -288,13 +306,13 @@
v-model="alertWarningColorLocal"
name="alertWarning"
:label="$t('settings.style.advanced_colors.alert_warning')"
- :fallback="previewTheme.colors.alertWarning"
+ :fallback="previewTheme.colors?.alertWarning"
/>
<ColorInput
v-model="alertWarningTextColorLocal"
name="alertWarningText"
:label="$t('settings.text')"
- :fallback="previewTheme.colors.alertWarningText"
+ :fallback="previewTheme.colors?.alertWarningText"
/>
<ContrastRatio
:contrast="previewContrast.alertWarningText"
@@ -304,13 +322,13 @@
v-model="alertNeutralColorLocal"
name="alertNeutral"
:label="$t('settings.style.advanced_colors.alert_neutral')"
- :fallback="previewTheme.colors.alertNeutral"
+ :fallback="previewTheme.colors?.alertNeutral"
/>
<ColorInput
v-model="alertNeutralTextColorLocal"
name="alertNeutralText"
:label="$t('settings.text')"
- :fallback="previewTheme.colors.alertNeutralText"
+ :fallback="previewTheme.colors?.alertNeutralText"
/>
<ContrastRatio
:contrast="previewContrast.alertNeutralText"
@@ -319,7 +337,7 @@
<OpacityInput
v-model="alertOpacityLocal"
name="alertOpacity"
- :fallback="previewTheme.opacity.alert"
+ :fallback="previewTheme.opacity?.alert"
/>
</div>
<div class="color-item">
@@ -328,13 +346,13 @@
v-model="badgeNotificationColorLocal"
name="badgeNotification"
:label="$t('settings.style.advanced_colors.badge_notification')"
- :fallback="previewTheme.colors.badgeNotification"
+ :fallback="previewTheme.colors?.badgeNotification"
/>
<ColorInput
v-model="badgeNotificationTextColorLocal"
name="badgeNotificationText"
:label="$t('settings.text')"
- :fallback="previewTheme.colors.badgeNotificationText"
+ :fallback="previewTheme.colors?.badgeNotificationText"
/>
<ContrastRatio
:contrast="previewContrast.badgeNotificationText"
@@ -346,19 +364,19 @@
<ColorInput
v-model="panelColorLocal"
name="panelColor"
- :fallback="previewTheme.colors.panel"
+ :fallback="previewTheme.colors?.panel"
:label="$t('settings.background')"
/>
<OpacityInput
v-model="panelOpacityLocal"
name="panelOpacity"
- :fallback="previewTheme.opacity.panel"
+ :fallback="previewTheme.opacity?.panel"
:disabled="panelColorLocal === 'transparent'"
/>
<ColorInput
v-model="panelTextColorLocal"
name="panelTextColor"
- :fallback="previewTheme.colors.panelText"
+ :fallback="previewTheme.colors?.panelText"
:label="$t('settings.text')"
/>
<ContrastRatio
@@ -368,7 +386,7 @@
<ColorInput
v-model="panelLinkColorLocal"
name="panelLinkColor"
- :fallback="previewTheme.colors.panelLink"
+ :fallback="previewTheme.colors?.panelLink"
:label="$t('settings.links')"
/>
<ContrastRatio
@@ -381,20 +399,20 @@
<ColorInput
v-model="topBarColorLocal"
name="topBarColor"
- :fallback="previewTheme.colors.topBar"
+ :fallback="previewTheme.colors?.topBar"
:label="$t('settings.background')"
/>
<ColorInput
v-model="topBarTextColorLocal"
name="topBarTextColor"
- :fallback="previewTheme.colors.topBarText"
+ :fallback="previewTheme.colors?.topBarText"
:label="$t('settings.text')"
/>
<ContrastRatio :contrast="previewContrast.topBarText" />
<ColorInput
v-model="topBarLinkColorLocal"
name="topBarLinkColor"
- :fallback="previewTheme.colors.topBarLink"
+ :fallback="previewTheme.colors?.topBarLink"
:label="$t('settings.links')"
/>
<ContrastRatio :contrast="previewContrast.topBarLink" />
@@ -404,19 +422,19 @@
<ColorInput
v-model="inputColorLocal"
name="inputColor"
- :fallback="previewTheme.colors.input"
+ :fallback="previewTheme.colors?.input"
:label="$t('settings.background')"
/>
<OpacityInput
v-model="inputOpacityLocal"
name="inputOpacity"
- :fallback="previewTheme.opacity.input"
+ :fallback="previewTheme.opacity?.input"
:disabled="inputColorLocal === 'transparent'"
/>
<ColorInput
v-model="inputTextColorLocal"
name="inputTextColor"
- :fallback="previewTheme.colors.inputText"
+ :fallback="previewTheme.colors?.inputText"
:label="$t('settings.text')"
/>
<ContrastRatio :contrast="previewContrast.inputText" />
@@ -426,33 +444,33 @@
<ColorInput
v-model="btnColorLocal"
name="btnColor"
- :fallback="previewTheme.colors.btn"
+ :fallback="previewTheme.colors?.btn"
:label="$t('settings.background')"
/>
<OpacityInput
v-model="btnOpacityLocal"
name="btnOpacity"
- :fallback="previewTheme.opacity.btn"
+ :fallback="previewTheme.opacity?.btn"
:disabled="btnColorLocal === 'transparent'"
/>
<ColorInput
v-model="btnTextColorLocal"
name="btnTextColor"
- :fallback="previewTheme.colors.btnText"
+ :fallback="previewTheme.colors?.btnText"
:label="$t('settings.text')"
/>
<ContrastRatio :contrast="previewContrast.btnText" />
<ColorInput
v-model="btnPanelTextColorLocal"
name="btnPanelTextColor"
- :fallback="previewTheme.colors.btnPanelText"
+ :fallback="previewTheme.colors?.btnPanelText"
:label="$t('settings.style.advanced_colors.panel_header')"
/>
<ContrastRatio :contrast="previewContrast.btnPanelText" />
<ColorInput
v-model="btnTopBarTextColorLocal"
name="btnTopBarTextColor"
- :fallback="previewTheme.colors.btnTopBarText"
+ :fallback="previewTheme.colors?.btnTopBarText"
:label="$t('settings.style.advanced_colors.top_bar')"
/>
<ContrastRatio :contrast="previewContrast.btnTopBarText" />
@@ -460,27 +478,27 @@
<ColorInput
v-model="btnPressedColorLocal"
name="btnPressedColor"
- :fallback="previewTheme.colors.btnPressed"
+ :fallback="previewTheme.colors?.btnPressed"
:label="$t('settings.background')"
/>
<ColorInput
v-model="btnPressedTextColorLocal"
name="btnPressedTextColor"
- :fallback="previewTheme.colors.btnPressedText"
+ :fallback="previewTheme.colors?.btnPressedText"
:label="$t('settings.text')"
/>
<ContrastRatio :contrast="previewContrast.btnPressedText" />
<ColorInput
v-model="btnPressedPanelTextColorLocal"
name="btnPressedPanelTextColor"
- :fallback="previewTheme.colors.btnPressedPanelText"
+ :fallback="previewTheme.colors?.btnPressedPanelText"
:label="$t('settings.style.advanced_colors.panel_header')"
/>
<ContrastRatio :contrast="previewContrast.btnPressedPanelText" />
<ColorInput
v-model="btnPressedTopBarTextColorLocal"
name="btnPressedTopBarTextColor"
- :fallback="previewTheme.colors.btnPressedTopBarText"
+ :fallback="previewTheme.colors?.btnPressedTopBarText"
:label="$t('settings.style.advanced_colors.top_bar')"
/>
<ContrastRatio :contrast="previewContrast.btnPressedTopBarText" />
@@ -488,52 +506,52 @@
<ColorInput
v-model="btnDisabledColorLocal"
name="btnDisabledColor"
- :fallback="previewTheme.colors.btnDisabled"
+ :fallback="previewTheme.colors?.btnDisabled"
:label="$t('settings.background')"
/>
<ColorInput
v-model="btnDisabledTextColorLocal"
name="btnDisabledTextColor"
- :fallback="previewTheme.colors.btnDisabledText"
+ :fallback="previewTheme.colors?.btnDisabledText"
:label="$t('settings.text')"
/>
<ColorInput
v-model="btnDisabledPanelTextColorLocal"
name="btnDisabledPanelTextColor"
- :fallback="previewTheme.colors.btnDisabledPanelText"
+ :fallback="previewTheme.colors?.btnDisabledPanelText"
:label="$t('settings.style.advanced_colors.panel_header')"
/>
<ColorInput
v-model="btnDisabledTopBarTextColorLocal"
name="btnDisabledTopBarTextColor"
- :fallback="previewTheme.colors.btnDisabledTopBarText"
+ :fallback="previewTheme.colors?.btnDisabledTopBarText"
:label="$t('settings.style.advanced_colors.top_bar')"
/>
<h5>{{ $t('settings.style.advanced_colors.toggled') }}</h5>
<ColorInput
v-model="btnToggledColorLocal"
name="btnToggledColor"
- :fallback="previewTheme.colors.btnToggled"
+ :fallback="previewTheme.colors?.btnToggled"
:label="$t('settings.background')"
/>
<ColorInput
v-model="btnToggledTextColorLocal"
name="btnToggledTextColor"
- :fallback="previewTheme.colors.btnToggledText"
+ :fallback="previewTheme.colors?.btnToggledText"
:label="$t('settings.text')"
/>
<ContrastRatio :contrast="previewContrast.btnToggledText" />
<ColorInput
v-model="btnToggledPanelTextColorLocal"
name="btnToggledPanelTextColor"
- :fallback="previewTheme.colors.btnToggledPanelText"
+ :fallback="previewTheme.colors?.btnToggledPanelText"
:label="$t('settings.style.advanced_colors.panel_header')"
/>
<ContrastRatio :contrast="previewContrast.btnToggledPanelText" />
<ColorInput
v-model="btnToggledTopBarTextColorLocal"
name="btnToggledTopBarTextColor"
- :fallback="previewTheme.colors.btnToggledTopBarText"
+ :fallback="previewTheme.colors?.btnToggledTopBarText"
:label="$t('settings.style.advanced_colors.top_bar')"
/>
<ContrastRatio :contrast="previewContrast.btnToggledTopBarText" />
@@ -543,20 +561,20 @@
<ColorInput
v-model="tabColorLocal"
name="tabColor"
- :fallback="previewTheme.colors.tab"
+ :fallback="previewTheme.colors?.tab"
:label="$t('settings.background')"
/>
<ColorInput
v-model="tabTextColorLocal"
name="tabTextColor"
- :fallback="previewTheme.colors.tabText"
+ :fallback="previewTheme.colors?.tabText"
:label="$t('settings.text')"
/>
<ContrastRatio :contrast="previewContrast.tabText" />
<ColorInput
v-model="tabActiveTextColorLocal"
name="tabActiveTextColor"
- :fallback="previewTheme.colors.tabActiveText"
+ :fallback="previewTheme.colors?.tabActiveText"
:label="$t('settings.text')"
/>
<ContrastRatio :contrast="previewContrast.tabActiveText" />
@@ -566,13 +584,13 @@
<ColorInput
v-model="borderColorLocal"
name="borderColor"
- :fallback="previewTheme.colors.border"
+ :fallback="previewTheme.colors?.border"
:label="$t('settings.style.common.color')"
/>
<OpacityInput
v-model="borderOpacityLocal"
name="borderOpacity"
- :fallback="previewTheme.opacity.border"
+ :fallback="previewTheme.opacity?.border"
:disabled="borderColorLocal === 'transparent'"
/>
</div>
@@ -581,25 +599,25 @@
<ColorInput
v-model="faintColorLocal"
name="faintColor"
- :fallback="previewTheme.colors.faint"
+ :fallback="previewTheme.colors?.faint"
:label="$t('settings.text')"
/>
<ColorInput
v-model="faintLinkColorLocal"
name="faintLinkColor"
- :fallback="previewTheme.colors.faintLink"
+ :fallback="previewTheme.colors?.faintLink"
:label="$t('settings.links')"
/>
<ColorInput
v-model="panelFaintColorLocal"
name="panelFaintColor"
- :fallback="previewTheme.colors.panelFaint"
+ :fallback="previewTheme.colors?.panelFaint"
:label="$t('settings.style.advanced_colors.panel_header')"
/>
<OpacityInput
v-model="faintOpacityLocal"
name="faintOpacity"
- :fallback="previewTheme.opacity.faint"
+ :fallback="previewTheme.opacity?.faint"
/>
</div>
<div class="color-item">
@@ -608,12 +626,12 @@
v-model="underlayColorLocal"
name="underlay"
:label="$t('settings.style.advanced_colors.underlay')"
- :fallback="previewTheme.colors.underlay"
+ :fallback="previewTheme.colors?.underlay"
/>
<OpacityInput
v-model="underlayOpacityLocal"
name="underlayOpacity"
- :fallback="previewTheme.opacity.underlay"
+ :fallback="previewTheme.opacity?.underlay"
:disabled="underlayOpacityLocal === 'transparent'"
/>
</div>
@@ -623,7 +641,7 @@
v-model="wallpaperColorLocal"
name="wallpaper"
:label="$t('settings.style.advanced_colors.wallpaper')"
- :fallback="previewTheme.colors.wallpaper"
+ :fallback="previewTheme.colors?.wallpaper"
/>
</div>
<div class="color-item">
@@ -632,13 +650,13 @@
v-model="pollColorLocal"
name="poll"
:label="$t('settings.background')"
- :fallback="previewTheme.colors.poll"
+ :fallback="previewTheme.colors?.poll"
/>
<ColorInput
v-model="pollTextColorLocal"
name="pollText"
:label="$t('settings.text')"
- :fallback="previewTheme.colors.pollText"
+ :fallback="previewTheme.colors?.pollText"
/>
</div>
<div class="color-item">
@@ -647,7 +665,7 @@
v-model="iconColorLocal"
name="icon"
:label="$t('settings.style.advanced_colors.icons')"
- :fallback="previewTheme.colors.icon"
+ :fallback="previewTheme.colors?.icon"
/>
</div>
<div class="color-item">
@@ -656,20 +674,20 @@
v-model="highlightColorLocal"
name="highlight"
:label="$t('settings.background')"
- :fallback="previewTheme.colors.highlight"
+ :fallback="previewTheme.colors?.highlight"
/>
<ColorInput
v-model="highlightTextColorLocal"
name="highlightText"
:label="$t('settings.text')"
- :fallback="previewTheme.colors.highlightText"
+ :fallback="previewTheme.colors?.highlightText"
/>
<ContrastRatio :contrast="previewContrast.highlightText" />
<ColorInput
v-model="highlightLinkColorLocal"
name="highlightLink"
:label="$t('settings.links')"
- :fallback="previewTheme.colors.highlightLink"
+ :fallback="previewTheme.colors?.highlightLink"
/>
<ContrastRatio :contrast="previewContrast.highlightLink" />
</div>
@@ -679,26 +697,26 @@
v-model="popoverColorLocal"
name="popover"
:label="$t('settings.background')"
- :fallback="previewTheme.colors.popover"
+ :fallback="previewTheme.colors?.popover"
/>
<OpacityInput
v-model="popoverOpacityLocal"
name="popoverOpacity"
- :fallback="previewTheme.opacity.popover"
+ :fallback="previewTheme.opacity?.popover"
:disabled="popoverOpacityLocal === 'transparent'"
/>
<ColorInput
v-model="popoverTextColorLocal"
name="popoverText"
:label="$t('settings.text')"
- :fallback="previewTheme.colors.popoverText"
+ :fallback="previewTheme.colors?.popoverText"
/>
<ContrastRatio :contrast="previewContrast.popoverText" />
<ColorInput
v-model="popoverLinkColorLocal"
name="popoverLink"
:label="$t('settings.links')"
- :fallback="previewTheme.colors.popoverLink"
+ :fallback="previewTheme.colors?.popoverLink"
/>
<ContrastRatio :contrast="previewContrast.popoverLink" />
</div>
@@ -708,20 +726,20 @@
v-model="selectedPostColorLocal"
name="selectedPost"
:label="$t('settings.background')"
- :fallback="previewTheme.colors.selectedPost"
+ :fallback="previewTheme.colors?.selectedPost"
/>
<ColorInput
v-model="selectedPostTextColorLocal"
name="selectedPostText"
:label="$t('settings.text')"
- :fallback="previewTheme.colors.selectedPostText"
+ :fallback="previewTheme.colors?.selectedPostText"
/>
<ContrastRatio :contrast="previewContrast.selectedPostText" />
<ColorInput
v-model="selectedPostLinkColorLocal"
name="selectedPostLink"
:label="$t('settings.links')"
- :fallback="previewTheme.colors.selectedPostLink"
+ :fallback="previewTheme.colors?.selectedPostLink"
/>
<ContrastRatio :contrast="previewContrast.selectedPostLink" />
</div>
@@ -731,20 +749,20 @@
v-model="selectedMenuColorLocal"
name="selectedMenu"
:label="$t('settings.background')"
- :fallback="previewTheme.colors.selectedMenu"
+ :fallback="previewTheme.colors?.selectedMenu"
/>
<ColorInput
v-model="selectedMenuTextColorLocal"
name="selectedMenuText"
:label="$t('settings.text')"
- :fallback="previewTheme.colors.selectedMenuText"
+ :fallback="previewTheme.colors?.selectedMenuText"
/>
<ContrastRatio :contrast="previewContrast.selectedMenuText" />
<ColorInput
v-model="selectedMenuLinkColorLocal"
name="selectedMenuLink"
:label="$t('settings.links')"
- :fallback="previewTheme.colors.selectedMenuLink"
+ :fallback="previewTheme.colors?.selectedMenuLink"
/>
<ContrastRatio :contrast="previewContrast.selectedMenuLink" />
</div>
@@ -753,57 +771,57 @@
<ColorInput
v-model="chatBgColorLocal"
name="chatBgColor"
- :fallback="previewTheme.colors.bg"
+ :fallback="previewTheme.colors?.bg"
:label="$t('settings.background')"
/>
<h5>{{ $t('settings.style.advanced_colors.chat.incoming') }}</h5>
<ColorInput
v-model="chatMessageIncomingBgColorLocal"
name="chatMessageIncomingBgColor"
- :fallback="previewTheme.colors.bg"
+ :fallback="previewTheme.colors?.bg"
:label="$t('settings.background')"
/>
<ColorInput
v-model="chatMessageIncomingTextColorLocal"
name="chatMessageIncomingTextColor"
- :fallback="previewTheme.colors.text"
+ :fallback="previewTheme.colors?.text"
:label="$t('settings.text')"
/>
<ColorInput
v-model="chatMessageIncomingLinkColorLocal"
name="chatMessageIncomingLinkColor"
- :fallback="previewTheme.colors.link"
+ :fallback="previewTheme.colors?.link"
:label="$t('settings.links')"
/>
<ColorInput
v-model="chatMessageIncomingBorderColorLocal"
name="chatMessageIncomingBorderLinkColor"
- :fallback="previewTheme.colors.fg"
+ :fallback="previewTheme.colors?.fg"
:label="$t('settings.style.advanced_colors.chat.border')"
/>
<h5>{{ $t('settings.style.advanced_colors.chat.outgoing') }}</h5>
<ColorInput
v-model="chatMessageOutgoingBgColorLocal"
name="chatMessageOutgoingBgColor"
- :fallback="previewTheme.colors.bg"
+ :fallback="previewTheme.colors?.bg"
:label="$t('settings.background')"
/>
<ColorInput
v-model="chatMessageOutgoingTextColorLocal"
name="chatMessageOutgoingTextColor"
- :fallback="previewTheme.colors.text"
+ :fallback="previewTheme.colors?.text"
:label="$t('settings.text')"
/>
<ColorInput
v-model="chatMessageOutgoingLinkColorLocal"
name="chatMessageOutgoingLinkColor"
- :fallback="previewTheme.colors.link"
+ :fallback="previewTheme.colors?.link"
:label="$t('settings.links')"
/>
<ColorInput
v-model="chatMessageOutgoingBorderColorLocal"
name="chatMessageOutgoingBorderLinkColor"
- :fallback="previewTheme.colors.bg"
+ :fallback="previewTheme.colors?.bg"
:label="$t('settings.style.advanced_colors.chat.border')"
/>
</div>
@@ -826,7 +844,7 @@
v-model="btnRadiusLocal"
name="btnRadius"
:label="$t('settings.btnRadius')"
- :fallback="previewTheme.radii.btn"
+ :fallback="previewTheme.radii?.btn"
max="16"
hard-min="0"
/>
@@ -834,7 +852,7 @@
v-model="inputRadiusLocal"
name="inputRadius"
:label="$t('settings.inputRadius')"
- :fallback="previewTheme.radii.input"
+ :fallback="previewTheme.radii?.input"
max="9"
hard-min="0"
/>
@@ -842,7 +860,7 @@
v-model="checkboxRadiusLocal"
name="checkboxRadius"
:label="$t('settings.checkboxRadius')"
- :fallback="previewTheme.radii.checkbox"
+ :fallback="previewTheme.radii?.checkbox"
max="16"
hard-min="0"
/>
@@ -850,7 +868,7 @@
v-model="panelRadiusLocal"
name="panelRadius"
:label="$t('settings.panelRadius')"
- :fallback="previewTheme.radii.panel"
+ :fallback="previewTheme.radii?.panel"
max="50"
hard-min="0"
/>
@@ -858,7 +876,7 @@
v-model="avatarRadiusLocal"
name="avatarRadius"
:label="$t('settings.avatarRadius')"
- :fallback="previewTheme.radii.avatar"
+ :fallback="previewTheme.radii?.avatar"
max="28"
hard-min="0"
/>
@@ -866,7 +884,7 @@
v-model="avatarAltRadiusLocal"
name="avatarAltRadius"
:label="$t('settings.avatarAltRadius')"
- :fallback="previewTheme.radii.avatarAlt"
+ :fallback="previewTheme.radii?.avatarAlt"
max="28"
hard-min="0"
/>
@@ -874,7 +892,7 @@
v-model="attachmentRadiusLocal"
name="attachmentRadius"
:label="$t('settings.attachmentRadius')"
- :fallback="previewTheme.radii.attachment"
+ :fallback="previewTheme.radii?.attachment"
max="50"
hard-min="0"
/>
@@ -882,7 +900,7 @@
v-model="tooltipRadiusLocal"
name="tooltipRadius"
:label="$t('settings.tooltipRadius')"
- :fallback="previewTheme.radii.tooltip"
+ :fallback="previewTheme.radii?.tooltip"
max="50"
hard-min="0"
/>
@@ -890,7 +908,7 @@
v-model="chatMessageRadiusLocal"
name="chatMessageRadius"
:label="$t('settings.chatMessageRadius')"
- :fallback="previewTheme.radii.chatMessage || 2"
+ :fallback="previewTheme.radii?.chatMessage || 2"
max="50"
hard-min="0"
/>
@@ -919,24 +937,14 @@
</Select>
</div>
<div class="override">
- <label
- for="override"
- class="label"
- >
- {{ $t('settings.style.shadows.override') }}
- </label>
- {{ ' ' }}
- <input
+ <Checkbox
id="override"
v-model="currentShadowOverriden"
name="override"
class="input-override"
- type="checkbox"
>
- <label
- class="checkbox-label"
- for="override"
- />
+ {{ $t('settings.style.shadows.override') }}
+ </Checkbox>
</div>
<button
class="btn button-default"
@@ -947,38 +955,12 @@
</div>
<ShadowControl
v-model="currentShadow"
- :ready="!!currentShadowFallback"
+ :separate-inset="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'"
:fallback="currentShadowFallback"
+ :static-vars="previewTheme.colors"
+ :compact="true"
/>
- <div v-if="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'">
- <i18n-t
- scope="global"
- keypath="settings.style.shadows.filter_hint.always_drop_shadow"
- tag="p"
- >
- <code>filter: drop-shadow()</code>
- </i18n-t>
- <p>{{ $t('settings.style.shadows.filter_hint.avatar_inset') }}</p>
- <i18n-t
- scope="global"
- keypath="settings.style.shadows.filter_hint.drop_shadow_syntax"
- tag="p"
- >
- <code>drop-shadow</code>
- <code>spread-radius</code>
- <code>inset</code>
- </i18n-t>
- <i18n-t
- scope="global"
- keypath="settings.style.shadows.filter_hint.inset_classic"
- tag="p"
- >
- <code>box-shadow</code>
- </i18n-t>
- <p>{{ $t('settings.style.shadows.filter_hint.spread_zero') }}</p>
- </div>
</div>
-
<div
:label="$t('settings.style.fonts._tab_label')"
class="fonts-container"
@@ -996,26 +978,26 @@
v-model="fontsLocal.interface"
name="ui"
:label="$t('settings.style.fonts.components.interface')"
- :fallback="previewTheme.fonts.interface"
+ :fallback="previewTheme.fonts?.interface"
no-inherit="1"
/>
<FontControl
v-model="fontsLocal.input"
name="input"
:label="$t('settings.style.fonts.components.input')"
- :fallback="previewTheme.fonts.input"
+ :fallback="previewTheme.fonts?.input"
/>
<FontControl
v-model="fontsLocal.post"
name="post"
:label="$t('settings.style.fonts.components.post')"
- :fallback="previewTheme.fonts.post"
+ :fallback="previewTheme.fonts?.post"
/>
<FontControl
v-model="fontsLocal.postCode"
name="postCode"
:label="$t('settings.style.fonts.components.postCode')"
- :fallback="previewTheme.fonts.postCode"
+ :fallback="previewTheme.fonts?.postCode"
/>
</div>
</tab-switcher>
diff --git a/src/components/settings_modal/tabs/version_tab.js b/src/components/settings_modal/tabs/version_tab.js
@@ -1,22 +1,17 @@
-import { extractCommit } from 'src/services/version/version.service'
-
const pleromaFeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma-fe/commit/'
-const pleromaBeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma/commit/'
const VersionTab = {
data () {
const instance = this.$store.state.instance
return {
backendVersion: instance.backendVersion,
+ backendRepository: instance.backendRepository,
frontendVersion: instance.frontendVersion
}
},
computed: {
frontendVersionLink () {
return pleromaFeCommitUrl + this.frontendVersion
- },
- backendVersionLink () {
- return pleromaBeCommitUrl + extractCommit(this.backendVersion)
}
}
}
diff --git a/src/components/settings_modal/tabs/version_tab.vue b/src/components/settings_modal/tabs/version_tab.vue
@@ -7,7 +7,7 @@
<ul class="option-list">
<li>
<a
- :href="backendVersionLink"
+ :href="backendRepository"
target="_blank"
>{{ backendVersion }}</a>
</li>
diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js
@@ -1,9 +1,17 @@
-import ColorInput from '../color_input/color_input.vue'
-import OpacityInput from '../opacity_input/opacity_input.vue'
-import Select from '../select/select.vue'
-import { getCssShadow } from '../../services/style_setter/style_setter.js'
-import { hex2rgb } from '../../services/color_convert/color_convert.js'
+import ColorInput from 'src/components/color_input/color_input.vue'
+import OpacityInput from 'src/components/opacity_input/opacity_input.vue'
+import Select from 'src/components/select/select.vue'
+import SelectMotion from 'src/components/select/select_motion.vue'
+import Checkbox from 'src/components/checkbox/checkbox.vue'
+import Popover from 'src/components/popover/popover.vue'
+import ComponentPreview from 'src/components/component_preview/component_preview.vue'
+import { rgb2hex } from 'src/services/color_convert/color_convert.js'
+import { serializeShadow } from 'src/services/theme_data/iss_serializer.js'
+import { deserializeShadow } from 'src/services/theme_data/iss_deserializer.js'
+import { getCssShadow, getCssShadowFilter } from 'src/services/theme_data/css_utils.js'
+import { findShadow, findColor } from 'src/services/theme_data/theme_data_3.service.js'
import { library } from '@fortawesome/fontawesome-svg-core'
+import { throttle, flattenDeep } from 'lodash'
import {
faTimes,
faChevronDown,
@@ -18,103 +26,155 @@ library.add(
faPlus
)
-const toModel = (object = {}) => ({
- x: 0,
- y: 0,
- blur: 0,
- spread: 0,
- inset: false,
- color: '#000000',
- alpha: 1,
- ...object
-})
+const toModel = (input) => {
+ if (typeof input === 'object') {
+ return {
+ x: 0,
+ y: 0,
+ blur: 0,
+ spread: 0,
+ inset: false,
+ color: '#000000',
+ alpha: 1,
+ ...input
+ }
+ } else if (typeof input === 'string') {
+ return input
+ }
+}
export default {
- // 'modelValue' and 'Fallback' can be undefined, but if they are
- // initially vue won't detect it when they become something else
- // therefore i'm using "ready" which should be passed as true when
- // data becomes available
props: [
- 'modelValue', 'fallback', 'ready'
+ 'modelValue',
+ 'fallback',
+ 'separateInset',
+ 'noPreview',
+ 'disabled',
+ 'staticVars',
+ 'compact'
],
- emits: ['update:modelValue'],
+ emits: ['update:modelValue', 'subShadowSelected'],
data () {
return {
selectedId: 0,
- // TODO there are some bugs regarding display of array (it's not getting updated when deleting for some reason)
- cValue: (this.modelValue || this.fallback || []).map(toModel)
+ invalid: false
}
},
components: {
ColorInput,
OpacityInput,
- Select
- },
- methods: {
- add () {
- this.cValue.push(toModel(this.selected))
- this.selectedId = this.cValue.length - 1
- },
- del () {
- this.cValue.splice(this.selectedId, 1)
- this.selectedId = this.cValue.length === 0 ? undefined : Math.max(this.selectedId - 1, 0)
- },
- moveUp () {
- const movable = this.cValue.splice(this.selectedId, 1)[0]
- this.cValue.splice(this.selectedId - 1, 0, movable)
- this.selectedId -= 1
- },
- moveDn () {
- const movable = this.cValue.splice(this.selectedId, 1)[0]
- this.cValue.splice(this.selectedId + 1, 0, movable)
- this.selectedId += 1
- }
- },
- beforeUpdate () {
- this.cValue = this.modelValue || this.fallback
+ Select,
+ SelectMotion,
+ Checkbox,
+ Popover,
+ ComponentPreview
},
computed: {
- anyShadows () {
- return this.cValue.length > 0
- },
- anyShadowsFallback () {
- return this.fallback.length > 0
- },
- selected () {
- if (this.ready && this.anyShadows) {
- return this.cValue[this.selectedId]
- } else {
- return toModel({})
+ cValue: {
+ get () {
+ return (this.modelValue ?? this.fallback ?? []).map(toModel)
+ },
+ set (newVal) {
+ this.$emit('update:modelValue', newVal)
}
},
- currentFallback () {
- if (this.ready && this.anyShadowsFallback) {
- return this.fallback[this.selectedId]
- } else {
- return toModel({})
+ selectedType: {
+ get () {
+ return typeof this.selected
+ },
+ set (newType) {
+ this.selected = toModel(newType === 'object' ? {} : '')
}
},
- moveUpValid () {
- return this.ready && this.selectedId > 0
- },
- moveDnValid () {
- return this.ready && this.selectedId < this.cValue.length - 1
+ selected: {
+ get () {
+ const selected = this.cValue[this.selectedId]
+ if (selected && typeof selected === 'object') {
+ return { ...selected }
+ } else if (typeof selected === 'string') {
+ return selected
+ }
+ return null
+ },
+ set (value) {
+ this.cValue[this.selectedId] = toModel(value)
+ this.$emit('update:modelValue', this.cValue)
+ }
},
present () {
- return this.ready &&
- typeof this.cValue[this.selectedId] !== 'undefined' &&
- !this.usingFallback
+ return this.selected != null && this.modelValue != null
+ },
+ shadowsAreNull () {
+ return this.modelValue == null
},
- usingFallback () {
- return typeof this.modelValue === 'undefined'
+ currentFallback () {
+ return this.fallback?.[this.selectedId]
},
- rgb () {
- return hex2rgb(this.selected.color)
+ getColorFallback () {
+ if (this.staticVars && this.selected?.color) {
+ try {
+ const computedColor = findColor(this.selected.color, { dynamicVars: {}, staticVars: this.staticVars }, true)
+ if (computedColor) return rgb2hex(computedColor)
+ return null
+ } catch (e) {
+ console.warn(e)
+ return null
+ }
+ } else {
+ return this.currentFallback?.color
+ }
},
style () {
- return this.ready ? {
- boxShadow: getCssShadow(this.fallback)
- } : {}
+ try {
+ let result
+ const serialized = this.cValue.map(x => serializeShadow(x)).join(',')
+ serialized.split(/,/).map(deserializeShadow) // validate
+ const expandedShadow = flattenDeep(findShadow(this.cValue, { dynamicVars: {}, staticVars: this.staticVars }))
+ const fixedShadows = expandedShadow.map(x => ({ ...x, color: console.log(x) || rgb2hex(x.color) }))
+
+ if (this.separateInset) {
+ result = {
+ filter: getCssShadowFilter(fixedShadows),
+ boxShadow: getCssShadow(fixedShadows, true)
+ }
+ } else {
+ result = {
+ boxShadow: getCssShadow(fixedShadows)
+ }
+ }
+ this.invalid = false
+ return result
+ } catch (e) {
+ console.error('Invalid shadow', e)
+ this.invalid = true
+ }
+ }
+ },
+ watch: {
+ selected (value) {
+ this.$emit('subShadowSelected', this.selectedId)
}
+ },
+ methods: {
+ getNewSubshadow () {
+ return toModel(this.selected)
+ },
+ onSelectChange (id) {
+ this.selectedId = id
+ },
+ getSubshadowLabel (shadow, index) {
+ if (typeof shadow === 'object') {
+ return shadow?.name ?? this.$t('settings.style.shadows.shadow_id', { value: index })
+ } else if (typeof shadow === 'string') {
+ return shadow || this.$t('settings.style.shadows.empty_expression')
+ }
+ },
+ updateProperty: throttle(function (prop, value) {
+ this.cValue[this.selectedId][prop] = value
+ if (prop === 'inset' && value === false && this.separateInset) {
+ this.cValue[this.selectedId].spread = 0
+ }
+ this.$emit('update:modelValue', this.cValue)
+ }, 100)
}
}
diff --git a/src/components/shadow_control/shadow_control.scss b/src/components/shadow_control/shadow_control.scss
@@ -0,0 +1,122 @@
+.ShadowControl {
+ display: grid;
+ grid-template-columns: 10em 1fr 1fr;
+ grid-template-rows: 1fr;
+ grid-template-areas: "selector preview tweak";
+ grid-gap: 0.5em;
+ justify-content: stretch;
+
+ &.-compact {
+ grid-template-columns: 10em 1fr;
+ grid-template-rows: auto auto;
+ grid-template-areas:
+ "selector preview"
+ "tweak tweak";
+
+ &.-no-preview {
+ grid-template-columns: 1fr;
+ grid-template-rows: 10em 1fr;
+ grid-template-areas:
+ "selector"
+ "tweak";
+ }
+ }
+
+ .shadow-switcher {
+ grid-area: selector;
+ order: 1;
+ flex: 1 0 6em;
+ min-width: 6em;
+ margin-right: 0.125em;
+ display: flex;
+ flex-direction: column;
+
+ .shadow-list {
+ flex: 1 0 auto;
+ }
+ }
+
+ .shadow-tweak {
+ grid-area: tweak;
+ order: 3;
+ flex: 2 0 10em;
+ min-width: 10em;
+ margin-left: 0.125em;
+ margin-right: 0.125em;
+ display: grid;
+ grid-template-rows: auto 1fr;
+ grid-gap: 0.25em;
+
+ /* hack */
+ .input-boolean {
+ flex: 1;
+ display: flex;
+
+ .label {
+ flex: 1;
+ }
+ }
+
+ .input-string {
+ flex: 1 0 5em;
+ }
+
+ .shadow-expression {
+ width: 100%;
+ height: 100%;
+ }
+
+ .id-control {
+ align-items: stretch;
+
+ .shadow-switcher,
+ .btn {
+ min-width: 1px;
+ margin-right: 5px;
+ }
+
+ .btn {
+ padding: 0 0.4em;
+ margin: 0 0.1em;
+ }
+ }
+ }
+
+ &.-no-preview {
+ grid-template-columns: 10em 1fr;
+ grid-template-rows: 1fr;
+ grid-template-areas: "selector tweak";
+
+ .shadow-tweak {
+ order: 0;
+ flex: 2 0 8em;
+ max-width: 100%;
+ }
+
+ .input-range {
+ min-width: 5em;
+ }
+ }
+
+ .inset-alert {
+ padding: 0.25em 0.5em;
+ }
+
+ &.disabled {
+ .inset-alert {
+ opacity: 0.2;
+ }
+ }
+
+ .shadow-preview {
+ grid-area: preview;
+ min-width: 25em;
+ margin-left: 0.125em;
+ align-self: start;
+ justify-self: center;
+ }
+}
+
+.inset-tooltip {
+ max-width: 30em;
+}
diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue
@@ -1,327 +1,238 @@
<template>
<div
- class="shadow-control"
- :class="{ disabled: !present }"
+ class="ShadowControl label shadow-control"
+ :class="{ disabled: disabled || !present, '-no-preview': noPreview, '-compact': compact }"
>
- <div class="shadow-preview-container">
- <div
- :disabled="!present"
- class="y-shift-control"
+ <ComponentPreview
+ v-if="!noPreview"
+ :invalid="invalid"
+ class="shadow-preview"
+ :shadow-control="true"
+ :shadow="selected"
+ :preview-style="style"
+ :disabled="disabled || !present"
+ @update:shadow="({ axis, value }) => updateProperty(axis, value)"
+ />
+ <div class="shadow-switcher">
+ <Select
+ id="shadow-list"
+ v-model="selectedId"
+ class="shadow-list"
+ size="4"
+ :disabled="disabled || shadowsAreNull"
>
- <input
- v-model="selected.y"
- :disabled="!present"
- class="input-number"
- type="number"
+ <option
+ v-for="(shadow, index) in cValue"
+ :key="index"
+ :value="index"
+ :class="{ '-active': index === Number(selectedId) }"
>
- <div class="wrap">
+ {{ getSubshadowLabel(shadow, index) }}
+ </option>
+ </Select>
+ <SelectMotion
+ v-model="cValue"
+ :selected-id="selectedId"
+ :get-add-value="getNewSubshadow"
+ :disabled="disabled"
+ @update:selectedId="onSelectChange"
+ />
+ </div>
+ <div class="shadow-tweak">
+ <Select
+ v-model="selectedType"
+ :disabled="disabled || !present"
+ >
+ <option value="object">
+ {{ $t('settings.style.shadows.raw') }}
+ </option>
+ <option value="string">
+ {{ $t('settings.style.shadows.expression') }}
+ </option>
+ </Select>
+ <template v-if="selectedType === 'string'">
+ <textarea
+ v-model="selected"
+ class="input shadow-expression"
+ :disabled="disabled || shadowsAreNull"
+ :class="{disabled: disabled || shadowsAreNull}"
+ />
+ </template>
+ <template v-else-if="selectedType === 'object'">
+ <div
+ :class="{ disabled: disabled || !present }"
+ class="name-control style-control"
+ >
+ <label
+ for="name"
+ class="label"
+ :class="{ faint: disabled || !present }"
+ >
+ {{ $t('settings.style.shadows.name') }}
+ </label>
+ <input
+ id="name"
+ :value="selected?.name"
+ :disabled="disabled || !present"
+ :class="{ disabled: disabled || !present }"
+ name="name"
+ class="input input-string"
+ @input="e => updateProperty('name', e.target.value)"
+ >
+ </div>
+ <div
+ :disabled="disabled || !present"
+ class="inset-control style-control"
+ >
+ <Checkbox
+ id="inset"
+ :value="selected?.inset"
+ :disabled="disabled || !present"
+ name="inset"
+ class="input-inset input-boolean"
+ @input="e => updateProperty('inset', e.target.checked)"
+ >
+ <template #before>
+ {{ $t('settings.style.shadows.inset') }}
+ </template>
+ </Checkbox>
+ </div>
+ <div
+ :disabled="disabled || !present"
+ :class="{ disabled: disabled || !present }"
+ class="blur-control style-control"
+ >
+ <label
+ for="blur"
+ class="label"
+ :class="{ faint: disabled || !present }"
+ >
+ {{ $t('settings.style.shadows.blur') }}
+ </label>
<input
- v-model="selected.y"
- :disabled="!present"
- class="input-range"
+ id="blur"
+ :value="selected?.blur"
+ :disabled="disabled || !present"
+ :class="{ disabled: disabled || !present }"
+ name="blur"
+ class="input input-range"
type="range"
max="20"
- min="-20"
+ min="0"
+ @input="e => updateProperty('blur', e.target.value)"
+ >
+ <input
+ :value="selected?.blur"
+ class="input input-number -small"
+ :disabled="disabled || !present"
+ :class="{ disabled: disabled || !present }"
+ type="number"
+ min="0"
+ @input="e => updateProperty('blur', e.target.value)"
>
</div>
- </div>
- <div class="preview-window">
<div
- class="preview-block"
- :style="style"
- />
- </div>
- <div
- :disabled="!present"
- class="x-shift-control"
- >
- <input
- v-model="selected.x"
- :disabled="!present"
- class="input-number"
- type="number"
+ class="spread-control style-control"
+ :class="{ disabled: disabled || !present || (separateInset && !selected?.inset) }"
>
- <div class="wrap">
+ <label
+ for="spread"
+ class="label"
+ :class="{ faint: disabled || !present || (separateInset && !selected?.inset) }"
+ >
+ {{ $t('settings.style.shadows.spread') }}
+ </label>
<input
- v-model="selected.x"
- :disabled="!present"
- class="input-range"
+ id="spread"
+ :value="selected?.spread"
+ :class="{ disabled: disabled || !present || (separateInset && !selected?.inset) }"
+ :disabled="disabled || !present || (separateInset && !selected?.inset)"
+ name="spread"
+ class="input input-range"
type="range"
max="20"
min="-20"
+ @input="e => updateProperty('spread', e.target.value)"
>
- </div>
- </div>
- </div>
-
- <div class="shadow-tweak">
- <div
- :disabled="usingFallback"
- class="id-control style-control"
- >
- <Select
- id="shadow-switcher"
- v-model="selectedId"
- class="shadow-switcher"
- :disabled="!ready || usingFallback"
- >
- <option
- v-for="(shadow, index) in cValue"
- :key="index"
- :value="index"
+ <input
+ :value="selected?.spread"
+ class="input input-number -small"
+ :class="{ disabled: disabled || !present || (separateInset && !selected?.inset) }"
+ :disabled="disabled || !present || (separateInset && !selected?.inset)"
+ type="number"
+ @input="e => updateProperty('spread', e.target.value)"
>
- {{ $t('settings.style.shadows.shadow_id', { value: index }) }}
- </option>
- </Select>
- <button
- class="btn button-default"
- :disabled="!ready || !present"
- @click="del"
- >
- <FAIcon
- fixed-width
- icon="times"
- />
- </button>
- <button
- class="btn button-default"
- :disabled="!moveUpValid"
- @click="moveUp"
- >
- <FAIcon
- fixed-width
- icon="chevron-up"
- />
- </button>
- <button
- class="btn button-default"
- :disabled="!moveDnValid"
- @click="moveDn"
- >
- <FAIcon
- fixed-width
- icon="chevron-down"
- />
- </button>
- <button
- class="btn button-default"
- :disabled="usingFallback"
- @click="add"
- >
- <FAIcon
- fixed-width
- icon="plus"
- />
- </button>
- </div>
- <div
- :disabled="!present"
- class="inset-control style-control"
- >
- <label
- for="inset"
- class="label"
- >
- {{ $t('settings.style.shadows.inset') }}
- </label>
- <input
- id="inset"
- v-model="selected.inset"
- :disabled="!present"
- name="inset"
- class="input-inset"
- type="checkbox"
- >
- <label
- class="checkbox-label"
- for="inset"
+ </div>
+ <ColorInput
+ :model-value="selected?.color"
+ :disabled="disabled || !present"
+ :label="$t('settings.style.common.color')"
+ :fallback="getColorFallback"
+ :show-optional-checkbox="false"
+ name="shadow"
+ @update:modelValue="e => updateProperty('color', e)"
/>
- </div>
- <div
- :disabled="!present"
- class="blur-control style-control"
- >
- <label
- for="spread"
- class="label"
- >
- {{ $t('settings.style.shadows.blur') }}
- </label>
- <input
- id="blur"
- v-model="selected.blur"
- :disabled="!present"
- name="blur"
- class="input-range"
- type="range"
- max="20"
- min="0"
- >
- <input
- v-model="selected.blur"
- :disabled="!present"
- class="input-number"
- type="number"
- min="0"
- >
- </div>
- <div
- :disabled="!present"
- class="spread-control style-control"
- >
- <label
- for="spread"
- class="label"
- >
- {{ $t('settings.style.shadows.spread') }}
- </label>
- <input
- id="spread"
- v-model="selected.spread"
- :disabled="!present"
- name="spread"
- class="input-range"
- type="range"
- max="20"
- min="-20"
+ <OpacityInput
+ :model-value="selected?.alpha"
+ :disabled="disabled || !present"
+ @update:modelValue="e => updateProperty('alpha', e)"
+ />
+ <i18n-t
+ scope="global"
+ keypath="settings.style.shadows.hintV3"
+ :class="{ faint: disabled || !present }"
+ tag="p"
>
- <input
- v-model="selected.spread"
- :disabled="!present"
- class="input-number"
- type="number"
+ <code>--variable,mod</code>
+ </i18n-t>
+ <Popover
+ v-if="separateInset"
+ trigger="hover"
>
- </div>
- <ColorInput
- v-model="selected.color"
- :disabled="!present"
- :label="$t('settings.style.common.color')"
- :fallback="currentFallback.color"
- :show-optional-tickbox="false"
- name="shadow"
- />
- <OpacityInput
- v-model="selected.alpha"
- :disabled="!present"
- />
- <i18n-t
- scope="global"
- keypath="settings.style.shadows.hintV3"
- tag="p"
- >
- <code>--variable,mod</code>
- </i18n-t>
+ <template #trigger>
+ <div
+ class="inset-alert alert warning"
+ >
+ <FAIcon icon="exclamation-triangle" />
+
+ {{ $t('settings.style.shadows.filter_hint.avatar_inset_short') }}
+ </div>
+ </template>
+ <template #content>
+ <div class="inset-tooltip tooltip">
+ <i18n-t
+ scope="global"
+ keypath="settings.style.shadows.filter_hint.always_drop_shadow"
+ tag="p"
+ >
+ <code>filter: drop-shadow()</code>
+ </i18n-t>
+ <p>{{ $t('settings.style.shadows.filter_hint.avatar_inset') }}</p>
+ <i18n-t
+ scope="global"
+ keypath="settings.style.shadows.filter_hint.drop_shadow_syntax"
+ tag="p"
+ >
+ <code>drop-shadow</code>
+ <code>spread-radius</code>
+ <code>inset</code>
+ </i18n-t>
+ <i18n-t
+ scope="global"
+ keypath="settings.style.shadows.filter_hint.inset_classic"
+ tag="p"
+ >
+ <code>box-shadow</code>
+ </i18n-t>
+ <p>{{ $t('settings.style.shadows.filter_hint.spread_zero') }}</p>
+ </div>
+ </template>
+ </Popover>
+ </template>
</div>
</div>
</template>
-<script src="./shadow_control.js" ></script>
-
-<style lang="scss">
-@import '../../_variables.scss';
-.shadow-control {
- display: flex;
- flex-wrap: wrap;
- justify-content: center;
- margin-bottom: 1em;
-
- .shadow-preview-container,
- .shadow-tweak {
- margin: 5px 6px 0 0;
- }
- .shadow-preview-container {
- flex: 0;
- display: flex;
- flex-wrap: wrap;
-
- $side: 15em;
-
- input[type=number] {
- width: 5em;
- min-width: 2em;
- }
- .x-shift-control,
- .y-shift-control {
- display: flex;
- flex: 0;
-
- &[disabled=disabled] *{
- opacity: .5
- }
-
- }
-
- .x-shift-control {
- align-items: flex-start;
- }
-
- .x-shift-control .wrap,
- input[type=range] {
- margin: 0;
- width: $side;
- height: 2em;
- }
- .y-shift-control {
- flex-direction: column;
- align-items: flex-end;
- .wrap {
- width: 2em;
- height: $side;
- }
- input[type=range] {
- transform-origin: 1em 1em;
- transform: rotate(90deg);
- }
- }
- .preview-window {
- flex: 1;
- background-color: #999999;
- display: flex;
- align-items: center;
- justify-content: center;
- background-image:
- linear-gradient(45deg, #666666 25%, transparent 25%),
- linear-gradient(-45deg, #666666 25%, transparent 25%),
- linear-gradient(45deg, transparent 75%, #666666 75%),
- linear-gradient(-45deg, transparent 75%, #666666 75%);
- background-size: 20px 20px;
- background-position:0 0, 0 10px, 10px -10px, -10px 0;
-
- border-radius: $fallback--inputRadius;
- border-radius: var(--inputRadius, $fallback--inputRadius);
-
- .preview-block {
- width: 33%;
- height: 33%;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
- border-radius: $fallback--panelRadius;
- border-radius: var(--panelRadius, $fallback--panelRadius);
- }
- }
- }
-
- .shadow-tweak {
- flex: 1;
- min-width: 280px;
-
- .id-control {
- align-items: stretch;
-
- .shadow-switcher {
- flex: 1;
- }
-
- .shadow-switcher, .btn {
- min-width: 1px;
- margin-right: 5px;
- }
+<script src="./shadow_control.js"></script>
- .btn {
- padding: 0 .4em;
- margin: 0 .1em;
- }
- }
- }
-}
-</style>
+<style src="./shadow_control.scss" lang="scss"></style>
diff --git a/src/components/shout_panel/shout_panel.js b/src/components/shout_panel/shout_panel.js
@@ -11,7 +11,7 @@ library.add(
)
const shoutPanel = {
- props: [ 'floating' ],
+ props: ['floating'],
data () {
return {
currentMessage: '',
diff --git a/src/components/shout_panel/shout_panel.vue b/src/components/shout_panel/shout_panel.vue
@@ -5,20 +5,20 @@
>
<div class="panel panel-default">
<div
- class="panel-heading timeline-heading"
+ class="panel-heading"
:class="{ 'shout-heading': floating }"
@click.stop.prevent="togglePanel"
>
- <div class="title">
+ <h1 class="title">
{{ $t('shoutbox.title') }}
<FAIcon
v-if="floating"
icon="times"
class="close-icon"
/>
- </div>
+ </h1>
</div>
- <div class="shout-window">
+ <div class="panel-body shout-window">
<div
v-for="message in messages"
:key="message.id"
@@ -41,10 +41,10 @@
</div>
</div>
</div>
- <div class="shout-input">
+ <div class="panel-body shout-input">
<textarea
v-model="currentMessage"
- class="shout-input-textarea"
+ class="shout-input-textarea input"
rows="1"
@keyup.enter="submit(currentMessage)"
/>
@@ -75,12 +75,10 @@
<script src="./shout_panel.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.floating-shout {
position: fixed;
bottom: 0.5em;
- z-index: 1000;
+ z-index: var(--ZI_popovers);
max-width: 25em;
&.-left {
@@ -97,8 +95,7 @@
cursor: pointer;
.icon {
- color: $fallback--text;
- color: var(--panelText, $fallback--text);
+ color: var(--text);
margin-right: 0.5em;
}
@@ -128,8 +125,7 @@
img {
height: 24px;
width: 24px;
- border-radius: $fallback--avatarRadius;
- border-radius: var(--avatarRadius, $fallback--avatarRadius);
+ border-radius: var(--roundness);
margin-right: 0.5em;
margin-top: 0.25em;
}
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
@@ -2,6 +2,7 @@ import { mapState, mapGetters } from 'vuex'
import UserCard from '../user_card/user_card.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service'
+import { USERNAME_ROUTES } from 'src/components/navigation/navigation.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faSignInAlt,
@@ -14,7 +15,9 @@ import {
faSearch,
faTachometerAlt,
faCog,
- faInfoCircle
+ faInfoCircle,
+ faCompass,
+ faList
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -28,11 +31,13 @@ library.add(
faSearch,
faTachometerAlt,
faCog,
- faInfoCircle
+ faInfoCircle,
+ faCompass,
+ faList
)
const SideDrawer = {
- props: [ 'logout' ],
+ props: ['logout'],
data: () => ({
closed: true,
closeGesture: undefined
@@ -78,15 +83,22 @@ const SideDrawer = {
return this.$store.state.instance.federating
},
timelinesRoute () {
+ let name
if (this.$store.state.interface.lastTimeline) {
- return this.$store.state.interface.lastTimeline
+ name = this.$store.state.interface.lastTimeline
+ }
+ name = this.currentUser ? 'friends' : 'public-timeline'
+ if (USERNAME_ROUTES.has(name)) {
+ return { name, params: { username: this.currentUser.screen_name } }
+ } else {
+ return { name }
}
- return this.currentUser ? 'friends' : 'public-timeline'
},
...mapState({
- pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
+ pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
+ supportsAnnouncements: state => state.announcements.supportsAnnouncements
}),
- ...mapGetters(['unreadChatCount'])
+ ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
},
methods: {
toggleDrawer () {
@@ -103,7 +115,10 @@ const SideDrawer = {
GestureService.updateSwipe(e, this.closeGesture)
},
openSettingsModal () {
- this.$store.dispatch('openSettingsModal')
+ this.$store.dispatch('openSettingsModal', 'user')
+ },
+ openAdminModal () {
+ this.$store.dispatch('openSettingsModal', 'admin')
}
}
}
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
@@ -1,6 +1,6 @@
<template>
<div
- class="side-drawer-container"
+ class="side-drawer-container mobile-drawer"
:class="{ 'side-drawer-container-closed': closed, 'side-drawer-container-open': !closed }"
>
<div
@@ -35,7 +35,10 @@
v-if="!currentUser"
@click="toggleDrawer"
>
- <router-link :to="{ name: 'login' }">
+ <router-link
+ :to="{ name: 'login' }"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -47,7 +50,10 @@
v-if="currentUser || !privateMode"
@click="toggleDrawer"
>
- <router-link :to="{ name: timelinesRoute }">
+ <router-link
+ :to="timelinesRoute"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -56,12 +62,43 @@
</router-link>
</li>
<li
+ v-if="currentUser"
+ @click="toggleDrawer"
+ >
+ <router-link
+ :to="{ name: 'lists' }"
+ class="menu-item"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="list"
+ /> {{ $t("nav.lists") }}
+ </router-link>
+ </li>
+ <li
+ v-if="currentUser"
+ @click="toggleDrawer"
+ >
+ <router-link
+ :to="{ name: 'bookmarks' }"
+ class="menu-item"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="bookmark"
+ /> {{ $t("nav.bookmarks") }}
+ </router-link>
+ </li>
+ <li
v-if="currentUser && pleromaChatMessagesAvailable"
@click="toggleDrawer"
>
<router-link
:to="{ name: 'chats', params: { username: currentUser.screen_name } }"
- style="position: relative"
+ style="position: relative;"
+ class="menu-item"
>
<FAIcon
fixed-width
@@ -70,7 +107,7 @@
/> {{ $t("nav.chats") }}
<span
v-if="unreadChatCount"
- class="badge badge-notification"
+ class="badge -notification"
>
{{ unreadChatCount }}
</span>
@@ -79,7 +116,10 @@
</ul>
<ul v-if="currentUser">
<li @click="toggleDrawer">
- <router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
+ <router-link
+ :to="{ name: 'interactions', params: { username: currentUser.screen_name } }"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -91,7 +131,10 @@
v-if="currentUser.locked"
@click="toggleDrawer"
>
- <router-link to="/friend-requests">
+ <router-link
+ to="/friend-requests"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -99,7 +142,7 @@
/> {{ $t("nav.friend_requests") }}
<span
v-if="followRequestCount > 0"
- class="badge badge-notification"
+ class="badge -notification"
>
{{ followRequestCount }}
</span>
@@ -109,7 +152,10 @@
v-if="shout"
@click="toggleDrawer"
>
- <router-link :to="{ name: 'shout-panel' }">
+ <router-link
+ :to="{ name: 'shout-panel' }"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -123,7 +169,10 @@
v-if="currentUser || !privateMode"
@click="toggleDrawer"
>
- <router-link :to="{ name: 'search' }">
+ <router-link
+ :to="{ name: 'search' }"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -135,7 +184,10 @@
v-if="currentUser && suggestionsEnabled"
@click="toggleDrawer"
>
- <router-link :to="{ name: 'who-to-follow' }">
+ <router-link
+ :to="{ name: 'who-to-follow' }"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -145,7 +197,7 @@
</li>
<li @click="toggleDrawer">
<button
- class="button-unstyled -link -fullwidth"
+ class="menu-item"
@click="openSettingsModal"
>
<FAIcon
@@ -156,7 +208,10 @@
</button>
</li>
<li @click="toggleDrawer">
- <router-link :to="{ name: 'about'}">
+ <router-link
+ :to="{ name: 'about'}"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -168,23 +223,59 @@
v-if="currentUser && currentUser.role === 'admin'"
@click="toggleDrawer"
>
- <a
- href="/pleroma/admin/#/login-pleroma"
- target="_blank"
+ <button
+ class="menu-item"
+ @click.stop="openAdminModal"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="tachometer-alt"
/> {{ $t("nav.administration") }}
- </a>
+ </button>
+ </li>
+ <li
+ v-if="currentUser && supportsAnnouncements"
+ @click="toggleDrawer"
+ >
+ <router-link
+ :to="{ name: 'announcements' }"
+ class="menu-item"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="bullhorn"
+ /> {{ $t("nav.announcements") }}
+ <span
+ v-if="unreadAnnouncementCount"
+ class="badge -notification"
+ >
+ {{ unreadAnnouncementCount }}
+ </span>
+ </router-link>
+ </li>
+ <li
+ v-if="currentUser"
+ @click="toggleDrawer"
+ >
+ <router-link
+ :to="{ name: 'edit-navigation' }"
+ class="menu-item"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="compass"
+ /> {{ $t("nav.edit_nav_mobile") }}
+ </router-link>
</li>
<li
v-if="currentUser"
@click="toggleDrawer"
>
<button
- class="button-unstyled -link -fullwidth"
+ class="menu-item"
@click="doLogout"
>
<FAIcon
@@ -204,14 +295,12 @@
</div>
</template>
-<script src="./side_drawer.js" ></script>
+<script src="./side_drawer.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.side-drawer-container {
position: fixed;
- z-index: 1000;
+ z-index: var(--ZI_navbar);
top: 0;
left: 0;
width: 100%;
@@ -240,11 +329,11 @@
z-index: -1;
transition: 0.35s;
transition-property: background-color;
- background-color: rgba(0, 0, 0, 0.5);
+ background-color: rgb(0 0 0 / 50%);
}
.side-drawer-darken-closed {
- background-color: rgba(0, 0, 0, 0);
+ background-color: rgb(0 0 0 / 0%);
}
.side-drawer-click-outside {
@@ -253,24 +342,16 @@
.side-drawer {
overflow-x: hidden;
- transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
transition: 0.35s;
+ transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
transition-property: transform;
margin: 0 0 0 -100px;
padding: 0 0 1em 100px;
width: 80%;
max-width: 20em;
flex: 0 0 80%;
- box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
- box-shadow: var(--panelShadow);
- background-color: $fallback--bg;
- background-color: var(--popover, $fallback--bg);
- color: $fallback--link;
- color: var(--popoverText, $fallback--link);
- --faint: var(--popoverFaintText, $fallback--faint);
- --faintLink: var(--popoverFaintLink, $fallback--faint);
- --lightText: var(--popoverLightText, $fallback--lightText);
- --icon: var(--popoverIcon, $fallback--icon);
+ box-shadow: var(--shadow);
+ background-color: var(--background);
.badge {
margin-left: 10px;
@@ -316,10 +397,8 @@
list-style: none;
margin: 0;
padding: 0;
-
border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
}
.side-drawer ul:last-child {
@@ -329,23 +408,13 @@
.side-drawer li {
padding: 0;
- a, button {
+ a,
+ button {
box-sizing: border-box;
display: block;
height: 3em;
line-height: 3em;
padding: 0 0.7em;
-
- &:hover {
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenuPopover, $fallback--lightBg);
- color: $fallback--text;
- color: var(--selectedMenuPopoverText, $fallback--text);
- --faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
- --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
- }
}
}
</style>
diff --git a/src/components/staff_panel/staff_panel.js b/src/components/staff_panel/staff_panel.js
@@ -13,16 +13,16 @@ const StaffPanel = {
},
computed: {
groupedStaffAccounts () {
- const staffAccounts = map(this.staffAccounts, this.findUser).filter(_ => _)
+ const staffAccounts = map(this.staffAccounts, this.findUserByName).filter(_ => _)
const groupedStaffAccounts = groupBy(staffAccounts, 'role')
return [
- { role: 'admin', users: groupedStaffAccounts['admin'] },
- { role: 'moderator', users: groupedStaffAccounts['moderator'] }
+ { role: 'admin', users: groupedStaffAccounts.admin },
+ { role: 'moderator', users: groupedStaffAccounts.moderator }
].filter(group => group.users)
},
...mapGetters([
- 'findUser'
+ 'findUserByName'
]),
...mapState({
staffAccounts: state => state.instance.staffAccounts
diff --git a/src/components/staff_panel/staff_panel.vue b/src/components/staff_panel/staff_panel.vue
@@ -24,10 +24,9 @@
</div>
</template>
-<script src="./staff_panel.js" ></script>
+<script src="./staff_panel.js"></script>
<style lang="scss">
-
.staff-group {
padding-left: 1em;
padding-top: 1em;
diff --git a/src/components/status/post.style.js b/src/components/status/post.style.js
@@ -0,0 +1,42 @@
+export default {
+ name: 'Post',
+ selector: '.Status',
+ states: {
+ selected: '.-focused'
+ },
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'ButtonUnstyled',
+ 'RichContent',
+ 'Input',
+ 'Avatar',
+ 'Attachment',
+ 'PollGraph'
+ ],
+ validInnerComponentsLite: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Border',
+ 'ButtonUnstyled',
+ 'RichContent',
+ 'Avatar'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg'
+ }
+ },
+ {
+ state: ['selected'],
+ directives: {
+ background: '--inheritedBackground, 10'
+ }
+ }
+ ]
+}
diff --git a/src/components/status/status.js b/src/components/status/status.js
@@ -4,15 +4,16 @@ import ReactButton from '../react_button/react_button.vue'
import RetweetButton from '../retweet_button/retweet_button.vue'
import ExtraButtons from '../extra_buttons/extra_buttons.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue'
-import UserCard from '../user_card/user_card.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import AvatarList from '../avatar_list/avatar_list.vue'
import Timeago from '../timeago/timeago.vue'
import StatusContent from '../status_content/status_content.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import StatusPopover from '../status_popover/status_popover.vue'
+import UserPopover from '../user_popover/user_popover.vue'
import UserListPopover from '../user_list_popover/user_list_popover.vue'
import EmojiReactions from '../emoji_reactions/emoji_reactions.vue'
+import UserLink from '../user_link/user_link.vue'
import MentionsLine from 'src/components/mentions_line/mentions_line.vue'
import MentionLink from 'src/components/mention_link/mention_link.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@@ -38,7 +39,8 @@ import {
faThumbtack,
faChevronUp,
faChevronDown,
- faAngleDoubleRight
+ faAngleDoubleRight,
+ faPlay
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -58,7 +60,8 @@ library.add(
faThumbtack,
faChevronUp,
faChevronDown,
- faAngleDoubleRight
+ faAngleDoubleRight,
+ faPlay
)
const camelCase = name => name.charAt(0).toUpperCase() + name.slice(1)
@@ -105,7 +108,6 @@ const Status = {
RetweetButton,
ExtraButtons,
PostStatusForm,
- UserCard,
UserAvatar,
AvatarList,
Timeago,
@@ -115,7 +117,9 @@ const Status = {
StatusContent,
RichContent,
MentionLink,
- MentionsLine
+ MentionsLine,
+ UserPopover,
+ UserLink
},
props: [
'statusoid',
@@ -131,6 +135,7 @@ const Status = {
'showPinned',
'inProfile',
'profileUserId',
+ 'inQuote',
'simpleTree',
'controlledThreadDisplayStatus',
@@ -149,6 +154,7 @@ const Status = {
'controlledSetMediaPlaying',
'dive'
],
+ emits: ['interacted'],
data () {
return {
uncontrolledReplying: false,
@@ -157,7 +163,8 @@ const Status = {
uncontrolledMediaPlaying: [],
suspendable: true,
error: null,
- headTailLinks: null
+ headTailLinks: null,
+ displayQuote: !this.inQuote
}
},
computed: {
@@ -225,17 +232,14 @@ const Status = {
muteWordHits () {
return muteWordHits(this.status, this.muteWords)
},
- rtBotStatus () {
- return this.statusoid.user.bot
- },
botStatus () {
- return this.status.user.bot
+ return this.status.user.actor_type === 'Service'
},
- botIndicator () {
- return this.botStatus && !this.hideBotIndication
+ showActorTypeIndicator () {
+ return !this.hideBotIndication
},
- rtBotIndicator () {
- return this.rtBotStatus && !this.hideBotIndication
+ sensitiveStatus () {
+ return this.status.nsfw
},
mentionsLine () {
if (!this.headTailLinks) return []
@@ -264,7 +268,9 @@ const Status = {
// Wordfiltered
this.muteWordHits.length > 0 ||
// bot status
- (this.muteBotStatuses && this.botStatus && !this.compact)
+ (this.muteBotStatuses && this.botStatus && !this.compact) ||
+ // sensitive status
+ (this.muteSensitiveStatuses && this.sensitiveStatus && !this.compact)
return !this.unmuted && !this.shouldNotMute && reasonsToMute
},
userIsMuted () {
@@ -361,14 +367,21 @@ const Status = {
return uniqBy(combinedUsers, 'id')
},
tags () {
+ // eslint-disable-next-line no-prototype-builtins
return this.status.tags.filter(tagObj => tagObj.hasOwnProperty('name')).map(tagObj => tagObj.name).join(' ')
},
hidePostStats () {
return this.mergedConfig.hidePostStats
},
+ shouldDisplayFavsAndRepeats () {
+ return !this.hidePostStats && this.isFocused && (this.combinedFavsAndRepeatsUsers.length > 0 || this.statusFromGlobalRepository.quotes_count)
+ },
muteBotStatuses () {
return this.mergedConfig.muteBotStatuses
},
+ muteSensitiveStatuses () {
+ return this.mergedConfig.muteSensitiveStatuses
+ },
hideBotIndication () {
return this.mergedConfig.hideBotIndication
},
@@ -392,6 +405,50 @@ const Status = {
},
visibilityLocalized () {
return this.$i18n.t('general.scope_in_timeline.' + this.status.visibility)
+ },
+ isEdited () {
+ return this.status.edited_at !== null
+ },
+ editingAvailable () {
+ return this.$store.state.instance.editingAvailable
+ },
+ hasVisibleQuote () {
+ return this.status.quote_url && this.status.quote_visible
+ },
+ hasInvisibleQuote () {
+ return this.status.quote_url && !this.status.quote_visible
+ },
+ quotedStatus () {
+ return this.status.quote_id ? this.$store.state.statuses.allStatusesObject[this.status.quote_id] : undefined
+ },
+ shouldDisplayQuote () {
+ return this.quotedStatus && this.displayQuote
+ },
+ scrobblePresent () {
+ if (this.mergedConfig.hideScrobbles) return false
+ if (!this.status.user.latestScrobble) return false
+ const value = this.mergedConfig.hideScrobblesAfter.match(/\d+/gs)[0]
+ const unit = this.mergedConfig.hideScrobblesAfter.match(/\D+/gs)[0]
+ let multiplier = 60 * 1000 // minutes is smallest unit
+ switch (unit) {
+ case 'm':
+ break
+ case 'h':
+ multiplier *= 60 // hour
+ break
+ case 'd':
+ multiplier *= 60 // hour
+ multiplier *= 24 // day
+ break
+ }
+ const maxAge = Number(value) * multiplier
+ const createdAt = Date.parse(this.status.user.latestScrobble.created_at)
+ const age = Date.now() - createdAt
+ if (age > maxAge) return false
+ return this.status.user.latestScrobble.artist
+ },
+ scrobble () {
+ return this.status.user.latestScrobble
}
},
methods: {
@@ -411,9 +468,11 @@ const Status = {
this.error = error
},
clearError () {
+ this.$emit('interacted')
this.error = undefined
},
toggleReplying () {
+ this.$emit('interacted')
controlledOrUncontrolledToggle(this, 'replying')
},
gotoOriginal (id) {
@@ -448,7 +507,7 @@ const Status = {
scrollIfHighlighted (highlightId) {
const id = highlightId
if (this.status.id === id) {
- let rect = this.$el.getBoundingClientRect()
+ const rect = this.$el.getBoundingClientRect()
if (rect.top < 100) {
// Post is above screen, match its top to screen top
window.scrollBy(0, rect.top - 100)
@@ -460,10 +519,22 @@ const Status = {
window.scrollBy(0, rect.bottom - window.innerHeight + 50)
}
}
+ },
+ toggleDisplayQuote () {
+ if (this.shouldDisplayQuote) {
+ this.displayQuote = false
+ } else if (!this.quotedStatus) {
+ this.$store.dispatch('fetchStatus', this.status.quote_id)
+ .then(() => {
+ this.displayQuote = true
+ })
+ } else {
+ this.displayQuote = true
+ }
}
},
watch: {
- 'highlight': function (id) {
+ highlight: function (id) {
this.scrollIfHighlighted(id)
},
'status.repeat_num': function (num) {
@@ -478,7 +549,7 @@ const Status = {
this.$store.dispatch('fetchFavs', this.status.id)
}
},
- 'isSuspendable': function (val) {
+ isSuspendable: function (val) {
this.suspendable = val
}
}
diff --git a/src/components/status/status.scss b/src/components/status/status.scss
@@ -1,5 +1,3 @@
-@import '../../_variables.scss';
-
.Status {
min-width: 0;
white-space: normal;
@@ -12,24 +10,8 @@
--_still-image-label-visibility: hidden;
}
- &.-focused {
- background-color: $fallback--lightBg;
- background-color: var(--selectedPost, $fallback--lightBg);
- color: $fallback--text;
- color: var(--selectedPostText, $fallback--text);
-
- --lightText: var(--selectedPostLightText, $fallback--light);
- --faint: var(--selectedPostFaintText, $fallback--faint);
- --faintLink: var(--selectedPostFaintLink, $fallback--faint);
- --postLink: var(--selectedPostPostLink, $fallback--faint);
- --postFaintLink: var(--selectedPostFaintPostLink, $fallback--faint);
- --icon: var(--selectedPostIcon, $fallback--icon);
- }
-
.gravestone {
- padding: var(--status-margin, $status-margin);
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+ padding: var(--status-margin);
display: flex;
.deleted-text {
@@ -40,7 +22,7 @@
.status-container {
display: flex;
- padding: var(--status-margin, $status-margin);
+ padding: var(--status-margin);
> * {
min-width: 0;
@@ -52,7 +34,7 @@
}
.pin {
- padding: var(--status-margin, $status-margin) var(--status-margin, $status-margin) 0;
+ padding: var(--status-margin) var(--status-margin) 0;
display: flex;
align-items: center;
justify-content: flex-end;
@@ -68,7 +50,7 @@
}
.left-side {
- margin-right: var(--status-margin, $status-margin);
+ margin-right: var(--status-margin);
}
.right-side {
@@ -77,7 +59,7 @@
}
.usercard {
- margin-bottom: var(--status-margin, $status-margin);
+ margin-bottom: var(--status-margin);
}
.status-username {
@@ -90,7 +72,7 @@
text-overflow: ellipsis;
--_still_image-label-scale: 0.25;
- --emoji-size: 14px;
+ --emoji-size: 1em;
}
.status-favicon {
@@ -135,11 +117,6 @@
.button-unstyled {
padding: 5px;
margin: -5px;
-
- &:hover svg {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
- }
}
.svg-inline--fa {
@@ -156,7 +133,8 @@
margin-right: 0.2em;
}
- & .heading-reply-row {
+ & .heading-reply-row,
+ & .heading-edited-row {
position: relative;
align-content: baseline;
font-size: 0.85em;
@@ -180,7 +158,7 @@
.reply-to-popover {
.reply-to:hover::before {
- content: '';
+ content: "";
display: block;
position: absolute;
bottom: 0;
@@ -196,7 +174,7 @@
&.-strikethrough {
.reply-to::after {
- content: '';
+ content: "";
display: block;
position: absolute;
top: 50%;
@@ -242,16 +220,15 @@
}
.repeat-info {
- padding: 0.4em var(--status-margin, $status-margin);
+ padding: 0.4em var(--status-margin);
.repeat-icon {
- color: $fallback--cGreen;
- color: var(--cGreen, $fallback--cGreen);
+ color: var(--cGreen);
}
}
.repeater-avatar {
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
+ border-radius: var(--roundness);
margin-left: 28px;
width: 20px;
height: 20px;
@@ -288,7 +265,7 @@
position: relative;
width: 100%;
display: flex;
- margin-top: var(--status-margin, $status-margin);
+ margin-top: var(--status-margin);
> * {
max-width: 4em;
@@ -304,6 +281,7 @@
overflow: hidden;
display: flex;
flex-wrap: nowrap;
+ gap: 1ex;
& .status-username,
& .mute-thread,
@@ -335,7 +313,7 @@
margin-left: 0.2em;
&::before {
- content: ' ';
+ content: " ";
}
}
@@ -356,7 +334,7 @@
}
.favs-repeated-users {
- margin-top: var(--status-margin, $status-margin);
+ margin-top: var(--status-margin);
}
.stats {
@@ -367,27 +345,27 @@
.avatar-row {
flex: 1;
- overflow: hidden;
position: relative;
display: flex;
align-items: center;
+ overflow: hidden;
&::before {
- content: '';
+ content: "";
position: absolute;
height: 100%;
width: 1px;
left: 0;
- background-color: var(--faint, $fallback--faint);
+ background-color: var(--textFaint);
}
}
.stat-count {
- margin-right: var(--status-margin, $status-margin);
+ margin-right: var(--status-margin);
user-select: none;
.stat-title {
- color: var(--faint, $fallback--faint);
+ color: var(--textFaint);
font-size: 0.85em;
text-transform: uppercase;
position: relative;
@@ -397,6 +375,7 @@
font-weight: bolder;
font-size: 1.1em;
line-height: 1em;
+ color: var(--text);
}
&:hover .stat-title {
@@ -421,4 +400,22 @@
}
}
}
+
+ .quoted-status {
+ margin-top: 0.5em;
+ border: 1px solid var(--border);
+ border-radius: var(--roundness);
+
+ &.-unavailable-prompt {
+ padding: 0.5em;
+ }
+ }
+
+ .display-quoted-status-button {
+ margin: 0.5em;
+
+ &-icon {
+ color: inherit;
+ }
+ }
}
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
@@ -25,21 +25,35 @@
class="fa-scale-110 fa-old-padding repeat-icon"
icon="retweet"
/>
- <router-link :to="userProfileLink">
- {{ status.user.screen_name_ui }}
- </router-link>
+ <user-link
+ :user="status.user"
+ :at="false"
+ />
</small>
<small
- v-if="showReasonMutedThread"
+ v-if="muteSensitiveStatuses && status.nsfw"
+ class="mute-thread"
+ >
+ {{ $t('status.sensitive_muted') }}
+ </small>
+ <small
+ v-if="muteBotStatuses && botStatus"
class="mute-thread"
>
- {{ $t('status.thread_muted') }}
+ {{ $t('status.bot_muted') }}
</small>
<small
- v-if="showReasonMutedThread && muteWordHits.length > 0"
+ v-if="showReasonMutedThread"
class="mute-thread"
>
- {{ $t('status.thread_muted_and_words') }}
+ <span>
+ {{ $t('status.thread_muted') }}
+ </span>
+ <span
+ v-if="muteWordHits.length > 0"
+ >
+ {{ $t('status.thread_muted_and_words') }}
+ </span>
</small>
<small
class="mute-words"
@@ -78,12 +92,12 @@
<UserAvatar
v-if="retweet"
class="left-side repeater-avatar"
- :bot="rtBotIndicator"
+ :show-actor-type-indicator="showActorTypeIndicator"
:better-shadow="betterShadow"
:user="statusoid.user"
/>
<div class="right-side faint">
- <span
+ <bdi
class="status-username repeater-name"
:title="retweeter"
>
@@ -100,7 +114,7 @@
v-else
:to="retweeterProfileLink"
>{{ retweeter }}</router-link>
- </span>
+ </bdi>
{{ ' ' }}
<FAIcon
icon="retweet"
@@ -124,25 +138,23 @@
>
<a
:href="$router.resolve(userProfileLink).href"
- @click.stop.prevent.capture="toggleUserExpanded"
+ @click.prevent
>
- <UserAvatar
- class="post-avatar"
- :bot="botIndicator"
- :compact="compact"
- :better-shadow="betterShadow"
- :user="status.user"
- />
+ <UserPopover
+ :user-id="status.user.id"
+ :overlay-centers="true"
+ >
+ <UserAvatar
+ class="post-avatar"
+ :show-actor-type-indicator="showActorTypeIndicator"
+ :compact="compact"
+ :better-shadow="betterShadow"
+ :user="status.user"
+ />
+ </UserPopover>
</a>
</div>
<div class="right-side">
- <UserCard
- v-if="userExpanded"
- :user-id="status.user.id"
- :rounded="true"
- :bordered="true"
- class="usercard"
- />
<div
v-if="!noHeading"
class="status-heading"
@@ -166,13 +178,12 @@
>
{{ status.user.name }}
</h4>
- <router-link
+ <user-link
class="account-name"
:title="status.user.screen_name_ui"
- :to="userProfileLink"
- >
- {{ status.user.screen_name_ui }}
- </router-link>
+ :user="status.user"
+ :at="false"
+ />
<img
v-if="!!(status.user && status.user.favicon)"
class="status-favicon"
@@ -182,7 +193,7 @@
<span class="heading-right">
<router-link
- class="timeago faint-link"
+ class="timeago faint"
:to="{ name: 'conversation', params: { id: status.id } }"
>
<Timeago
@@ -252,6 +263,47 @@
</span>
</div>
<div
+ v-if="scrobblePresent"
+ class="status-rich-presence"
+ >
+ <a
+ v-if="scrobble.externalLink"
+ :href="scrobble.externalLink"
+ target="_blank"
+ >
+ {{ scrobble.artist }} — {{ scrobble.title }}
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="play"
+ />
+ <span class="status-rich-presence-time">
+ <Timeago
+ template-key="time.in_past"
+ :time="scrobble.created_at"
+ :auto-update="60"
+ />
+ </span>
+ </a>
+ <span v-if="!scrobble.externalLink">
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="music"
+ />
+ {{ scrobble.artist }} — {{ scrobble.title }}
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="play"
+ />
+ <span class="status-rich-presence-time">
+ <Timeago
+ template-key="time.in_past"
+ :time="scrobble.created_at"
+ :auto-update="60"
+ />
+ </span>
+ </span>
+ </div>
+ <div
v-if="isReply || hasMentionsLine"
class="heading-reply-row"
>
@@ -263,7 +315,7 @@
v-if="!isPreview"
:status-id="status.parent_visible && status.in_reply_to_status_id"
class="reply-to-popover"
- style="min-width: 0"
+ style="min-width: 0;"
:class="{ '-strikethrough': !status.parent_visible }"
>
<button
@@ -322,12 +374,32 @@
class="mentions-line-first"
/>
</span>
+ {{ ' ' }}
<MentionsLine
v-if="hasMentionsLine"
:mentions="mentionsLine.slice(1)"
class="mentions-line"
/>
</div>
+ <div
+ v-if="isEdited && editingAvailable && !isPreview"
+ class="heading-edited-row"
+ >
+ <i18n-t
+ scope="global"
+ keypath="status.edited_at"
+ tag="span"
+ >
+ <template #time>
+ <Timeago
+ template-key="time.in_past"
+ :time="status.edited_at"
+ :auto-update="60"
+ :long-format="true"
+ />
+ </template>
+ </i18n-t>
+ </div>
</div>
<StatusContent
@@ -347,13 +419,55 @@
@parseReady="setHeadTailLinks"
/>
+ <article
+ v-if="hasVisibleQuote"
+ class="quoted-status"
+ >
+ <button
+ class="button-unstyled -link display-quoted-status-button"
+ :aria-expanded="shouldDisplayQuote"
+ @click="toggleDisplayQuote"
+ >
+ {{ shouldDisplayQuote ? $t('status.hide_quote') : $t('status.display_quote') }}
+ <FAIcon
+ class="display-quoted-status-button-icon"
+ :icon="shouldDisplayQuote ? 'chevron-up' : 'chevron-down'"
+ />
+ </button>
+ <Status
+ v-if="shouldDisplayQuote"
+ :statusoid="quotedStatus"
+ :in-quote="true"
+ />
+ </article>
+ <p
+ v-else-if="hasInvisibleQuote"
+ class="quoted-status -unavailable-prompt"
+ >
+ <i18n-t
+ scope="global"
+ keypath="status.invisible_quote"
+ >
+ <template #link>
+ <bdi>
+ <a
+ :href="status.quote_url"
+ target="_blank"
+ >
+ {{ status.quote_url }}
+ </a>
+ </bdi>
+ </template>
+ </i18n-t>
+ </p>
+
<div
v-if="inConversation && !isPreview && replies && replies.length"
class="replies"
>
<button
v-if="showOtherRepliesAsButton && replies.length > 1"
- class="button-unstyled -link faint"
+ class="button-unstyled -link"
:title="$tc('status.ancestor_follow', replies.length - 1, { numReplies: replies.length - 1 })"
@click.prevent="dive"
>
@@ -381,7 +495,7 @@
<transition name="fade">
<div
- v-if="!hidePostStats && isFocused && combinedFavsAndRepeatsUsers.length > 0"
+ v-if="shouldDisplayFavsAndRepeats"
class="favs-repeated-users"
>
<div class="stats">
@@ -409,6 +523,19 @@
</div>
</div>
</UserListPopover>
+ <router-link
+ v-if="statusFromGlobalRepository.quotes_count > 0"
+ :to="{ name: 'quotes', params: { id: status.id } }"
+ >
+ <div
+ class="stat-count"
+ >
+ <a class="stat-title">{{ $t('status.quotes') }}</a>
+ <div class="stat-number">
+ {{ statusFromGlobalRepository.quotes_count }}
+ </div>
+ </div>
+ </router-link>
<div class="avatar-row">
<AvatarList :users="combinedFavsAndRepeatsUsers" />
</div>
@@ -434,14 +561,17 @@
:visibility="status.visibility"
:logged-in="loggedIn"
:status="status"
+ @click="$emit('interacted')"
/>
<favorite-button
:logged-in="loggedIn"
:status="status"
+ @click="$emit('interacted')"
/>
<ReactButton
v-if="loggedIn"
:status="status"
+ @click="$emit('interacted')"
/>
<extra-buttons
:status="status"
@@ -459,7 +589,7 @@
<UserAvatar
class="post-avatar"
:compact="compact"
- :bot="botIndicator"
+ :show-actor-type-indicator="showActorTypeIndicator"
/>
</div>
<div class="right-side">
@@ -492,6 +622,6 @@
</div>
</template>
-<script src="./status.js" ></script>
+<script src="./status.js"></script>
<style src="./status.scss" lang="scss"></style>
diff --git a/src/components/status_body/status_body.scss b/src/components/status_body/status_body.scss
@@ -1,5 +1,3 @@
-@import '../../_variables.scss';
-
.StatusBody {
display: flex;
flex-direction: column;
@@ -14,7 +12,6 @@
& .text,
& .summary {
- font-family: var(--postFont, sans-serif);
white-space: pre-wrap;
overflow-wrap: break-word;
word-wrap: break-word;
@@ -40,8 +37,8 @@
.summary-wrapper {
margin-bottom: 0.5em;
border-style: solid;
- border-width: 0 0 1px 0;
- border-color: var(--border, $fallback--border);
+ border-width: 0 0 1px;
+ border-color: var(--border);
flex-grow: 0;
&.-tall {
@@ -58,8 +55,7 @@
.text-wrapper {
display: flex;
- flex-direction: column;
- flex-wrap: nowrap;
+ flex-flow: column nowrap;
&.-tall-status {
position: relative;
@@ -75,7 +71,7 @@
linear-gradient(to top, white, white);
/* Autoprefixed seem to ignore this one, and also syntax is different */
- -webkit-mask-composite: xor;
+ mask-composite: xor;
mask-composite: exclude;
}
}
@@ -113,20 +109,11 @@
}
}
- .greentext {
- color: $fallback--cGreen;
- color: var(--postGreentext, $fallback--cGreen);
- }
-
- .cyantext {
- color: var(--postCyantext, $fallback--cBlue);
- }
-
&.-compact {
align-items: top;
flex-direction: row;
- --emoji-size: 16px;
+ --emoji-size: calc(var(--emojiSize, 32px) / 2);
& .body,
& .attachments {
@@ -144,7 +131,7 @@
mask-image: linear-gradient(to bottom, white 2em, transparent 3em);
/* Autoprefixed seem to ignore this one, and also syntax is different */
- -webkit-mask-composite: xor;
+ mask-composite: xor;
mask-composite: exclude;
}
@@ -158,7 +145,7 @@
.summary-wrapper {
.summary::after {
- content: ': ';
+ content: ": ";
}
line-height: inherit;
diff --git a/src/components/status_body/status_body.vue b/src/components/status_body/status_body.vue
@@ -11,6 +11,7 @@
>
<RichContent
class="media-body summary"
+ :faint="compact"
:html="status.summary_raw_html"
:emoji="status.emojis"
/>
@@ -48,6 +49,7 @@
:html="status.raw_html"
:emoji="status.emojis"
:handle-links="true"
+ :faint="compact"
:greentext="mergedConfig.greentext"
:attentions="status.attentions"
@parseReady="onParseReady"
@@ -96,5 +98,5 @@
<slot v-if="!hideSubjectStatus" />
</div>
</template>
-<script src="./status_body.js" ></script>
+<script src="./status_body.js"></script>
<style lang="scss" src="./status_body.scss" />
diff --git a/src/components/status_bookmark_folder_menu/status_bookmark_folder_menu.js b/src/components/status_bookmark_folder_menu/status_bookmark_folder_menu.js
@@ -0,0 +1,38 @@
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faChevronRight, faFolder } from '@fortawesome/free-solid-svg-icons'
+import { mapState } from 'vuex'
+
+import Popover from '../popover/popover.vue'
+
+library.add(faChevronRight, faFolder)
+
+const StatusBookmarkFolderMenu = {
+ props: [
+ 'status'
+ ],
+ data () {
+ return {}
+ },
+ components: {
+ Popover
+ },
+ computed: {
+ ...mapState({
+ folders: state => state.bookmarkFolders.allFolders
+ }),
+ folderId () {
+ return this.status.bookmark_folder_id
+ }
+ },
+ methods: {
+ toggleFolder (id) {
+ const value = id === this.folderId ? null : id
+
+ this.$store.dispatch('bookmark', { id: this.status.id, bookmark_folder_id: value })
+ .then(() => this.$emit('onSuccess'))
+ .catch(err => this.$emit('onError', err.error.error))
+ }
+ }
+}
+
+export default StatusBookmarkFolderMenu
diff --git a/src/components/status_bookmark_folder_menu/status_bookmark_folder_menu.vue b/src/components/status_bookmark_folder_menu/status_bookmark_folder_menu.vue
@@ -0,0 +1,40 @@
+<template>
+ <div class="StatusBookmarkFolderMenu">
+ <Popover
+ trigger="hover"
+ placement="left"
+ remove-padding
+ >
+ <template #content>
+ <div class="dropdown-menu">
+ <button
+ v-for="folder in folders"
+ :key="folder.id"
+ class="menu-item dropdown-item"
+ @click="toggleFolder(folder.id)"
+ >
+ <span
+ class="input menu-checkbox -radio"
+ :class="{ 'menu-checkbox-checked': status.bookmark_folder_id == folder.id }"
+ />
+ {{ folder.name }}
+ </button>
+ </div>
+ </template>
+ <template #trigger>
+ <button class="menu-item dropdown-item dropdown-item-icon -has-submenu">
+ <FAIcon
+ fixed-width
+ icon="folder"
+ />{{ $t('bookmark_folders.select_folder') }}<FAIcon
+ class="chevron-icon"
+ size="lg"
+ icon="chevron-right"
+ />
+ </button>
+ </template>
+ </Popover>
+ </div>
+</template>
+
+<script src="./status_bookmark_folder_menu.js"></script>
diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js
@@ -73,6 +73,10 @@ const StatusContent = {
},
computed: {
...controlledOrUncontrolledGetters(['showingTall', 'expandingSubject', 'showingLongSubject']),
+ statusCard () {
+ if (!this.status.card) return null
+ return this.status.card.url === this.status.quote_url ? null : this.status.card
+ },
hideAttachments () {
return (this.mergedConfig.hideAttachments && !this.inConversation) ||
(this.mergedConfig.hideAttachmentsInConv && this.inConversation)
@@ -86,6 +90,9 @@ const StatusContent = {
}
return true
},
+ localCollapseSubjectDefault () {
+ return this.mergedConfig.collapseMessageWithSubject
+ },
attachmentSize () {
if (this.compact) {
return 'small'
diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue
@@ -33,6 +33,7 @@
<gallery
v-if="status.attachments.length !== 0"
class="attachments media-body"
+ :compact="compact"
:nsfw="nsfwClickthrough"
:attachments="status.attachments"
:limit="compact ? 1 : 0"
@@ -42,7 +43,7 @@
/>
<div
- v-if="status.card && !noHeading && !compact"
+ v-if="statusCard && !noHeading && !compact"
class="link-preview media-body"
>
<link-preview
@@ -56,7 +57,7 @@
</div>
</template>
-<script src="./status_content.js" ></script>
+<script src="./status_content.js"></script>
<style lang="scss">
.StatusContent {
flex: 1;
diff --git a/src/components/status_history_modal/status_history_modal.js b/src/components/status_history_modal/status_history_modal.js
@@ -0,0 +1,60 @@
+import { get } from 'lodash'
+import Modal from '../modal/modal.vue'
+import Status from '../status/status.vue'
+
+const StatusHistoryModal = {
+ components: {
+ Modal,
+ Status
+ },
+ data () {
+ return {
+ statuses: []
+ }
+ },
+ computed: {
+ modalActivated () {
+ return this.$store.state.statusHistory.modalActivated
+ },
+ params () {
+ return this.$store.state.statusHistory.params
+ },
+ statusId () {
+ return this.params.id
+ },
+ historyCount () {
+ return this.statuses.length
+ },
+ history () {
+ return this.statuses
+ }
+ },
+ watch: {
+ params (newVal, oldVal) {
+ const newStatusId = get(newVal, 'id') !== get(oldVal, 'id')
+ if (newStatusId) {
+ this.resetHistory()
+ }
+
+ if (newStatusId || get(newVal, 'edited_at') !== get(oldVal, 'edited_at')) {
+ this.fetchStatusHistory()
+ }
+ }
+ },
+ methods: {
+ resetHistory () {
+ this.statuses = []
+ },
+ fetchStatusHistory () {
+ this.$store.dispatch('fetchStatusHistory', this.params)
+ .then(data => {
+ this.statuses = data
+ })
+ },
+ closeModal () {
+ this.$store.dispatch('closeStatusHistoryModal')
+ }
+ }
+}
+
+export default StatusHistoryModal
diff --git a/src/components/status_history_modal/status_history_modal.vue b/src/components/status_history_modal/status_history_modal.vue
@@ -0,0 +1,49 @@
+<template>
+ <Modal
+ v-if="modalActivated"
+ class="status-history-modal-view"
+ @backdropClicked="closeModal"
+ >
+ <div class="status-history-modal-panel panel">
+ <div class="panel-heading">
+ <h1 class="title">
+ {{ $t('status.status_history') }} ({{ historyCount }})
+ </h1>
+ </div>
+ <div class="panel-body">
+ <div
+ v-if="historyCount > 0"
+ class="history-body"
+ >
+ <status
+ v-for="status in history"
+ :key="status.id"
+ :statusoid="status"
+ :is-preview="true"
+ class="conversation-status status-fadein panel-body"
+ />
+ </div>
+ </div>
+ </div>
+ </Modal>
+</template>
+
+<script src="./status_history_modal.js"></script>
+
+<style lang="scss">
+.modal-view.status-history-modal-view {
+ align-items: flex-start;
+}
+
+.status-history-modal-panel {
+ flex-shrink: 0;
+ margin-top: 25%;
+ margin-bottom: 2em;
+ width: 100%;
+ max-width: 700px;
+
+ @media (orientation: landscape) {
+ margin-top: 8%;
+ }
+}
+</style>
diff --git a/src/components/status_popover/status_popover.js b/src/components/status_popover/status_popover.js
@@ -38,6 +38,13 @@ const StatusPopover = {
.catch(e => (this.error = true))
}
}
+ },
+ watch: {
+ status (newStatus, oldStatus) {
+ if (newStatus !== oldStatus) {
+ this.$nextTick(() => this.$refs.popover.updateStyles())
+ }
+ }
}
}
diff --git a/src/components/status_popover/status_popover.vue b/src/components/status_popover/status_popover.vue
@@ -1,14 +1,16 @@
<template>
<Popover
+ ref="popover"
trigger="hover"
+ :stay-on-click="true"
popover-class="popover-default status-popover"
:bound-to="{ x: 'container' }"
@show="enter"
>
- <template v-slot:trigger>
+ <template #trigger>
<slot />
</template>
- <template v-slot:content>
+ <template #content>
<Status
v-if="status"
:is-preview="true"
@@ -35,25 +37,17 @@
</Popover>
</template>
-<script src="./status_popover.js" ></script>
+<script src="./status_popover.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
/* popover styles load on-demand, so we need to override */
.status-popover.popover {
font-size: 1rem;
min-width: 15em;
max-width: 95%;
-
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
border-style: solid;
border-width: 1px;
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
- box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
- box-shadow: var(--popupShadow);
/* TODO cleanup this */
.Status.Status {
diff --git a/src/components/sticker_picker/sticker_picker.js b/src/components/sticker_picker/sticker_picker.js
@@ -31,8 +31,8 @@ const StickerPicker = {
fetch(sticker)
.then((res) => {
res.blob().then((blob) => {
- var file = new File([blob], name, { mimetype: 'image/png' })
- var formData = new FormData()
+ const file = new File([blob], name, { mimetype: 'image/png' })
+ const formData = new FormData()
formData.append('file', file)
statusPosterService.uploadMedia({ store, formData })
.then((fileData) => {
diff --git a/src/components/sticker_picker/sticker_picker.vue b/src/components/sticker_picker/sticker_picker.vue
@@ -32,26 +32,29 @@
<script src="./sticker_picker.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.sticker-picker {
width: 100%;
+
.contents {
min-height: 250px;
+
.sticker-picker-content {
display: flex;
flex-wrap: wrap;
padding: 0 4px;
+
.sticker {
display: flex;
flex: 1 1 auto;
margin: 4px;
width: 56px;
height: 56px;
+
img {
height: 100%;
+
&:hover {
- filter: drop-shadow(0 0 5px var(--accent, $fallback--link));
+ filter: drop-shadow(0 0 5px var(--accent));
}
}
}
diff --git a/src/components/still-image/still-image.js b/src/components/still-image/still-image.js
@@ -7,16 +7,24 @@ const StillImage = {
'imageLoadHandler',
'alt',
'height',
- 'width'
+ 'width',
+ 'dataSrc',
+ 'loading'
],
data () {
return {
+ // for lazy loading, see loadLazy()
+ realSrc: this.src,
stopGifs: this.$store.getters.mergedConfig.stopGifs
}
},
computed: {
animated () {
- return this.stopGifs && (this.mimetype === 'image/gif' || this.src.endsWith('.gif'))
+ if (!this.realSrc) {
+ return false
+ }
+
+ return this.stopGifs && (this.mimetype === 'image/gif' || this.realSrc.endsWith('.gif'))
},
style () {
const appendPx = (str) => /\d$/.test(str) ? str + 'px' : str
@@ -27,7 +35,15 @@ const StillImage = {
}
},
methods: {
+ loadLazy () {
+ if (this.dataSrc) {
+ this.realSrc = this.dataSrc
+ }
+ },
onLoad () {
+ if (!this.realSrc) {
+ return
+ }
const image = this.$refs.src
if (!image) return
this.imageLoadHandler && this.imageLoadHandler(image)
@@ -42,6 +58,14 @@ const StillImage = {
onError () {
this.imageLoadError && this.imageLoadError()
}
+ },
+ watch: {
+ src () {
+ this.realSrc = this.src
+ },
+ dataSrc () {
+ this.$el.removeAttribute('data-loaded')
+ }
}
}
diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue
@@ -11,11 +11,13 @@
<!-- NOTE: key is required to force to re-render img tag when src is changed -->
<img
ref="src"
- :key="src"
+ :key="realSrc"
:alt="alt"
:title="alt"
- :src="src"
+ :data-src="dataSrc"
+ :src="realSrc"
:referrerpolicy="referrerpolicy"
+ :loading="loading"
@load="onLoad"
@error="onError"
>
@@ -26,8 +28,6 @@
<script src="./still-image.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.still-image {
position: relative;
line-height: 0;
@@ -56,18 +56,17 @@
&.animated {
&::before {
zoom: var(--_still_image-label-scale, 1);
- content: 'gif';
+ content: "gif";
position: absolute;
line-height: 1;
font-size: 0.7em;
top: 0.5em;
left: 0.5em;
- background: rgba(127, 127, 127, 0.5);
+ background: rgb(127 127 127 / 50%);
color: #fff;
display: block;
padding: 2px 4px;
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
+ border-radius: var(--roundness);
z-index: 2;
visibility: var(--_still-image-label-visibility, visible);
}
diff --git a/src/components/swipe_click/swipe_click.js b/src/components/swipe_click/swipe_click.js
@@ -5,6 +5,8 @@ import GestureService from '../../services/gesture_service/gesture_service'
* direction: a vector that indicates the direction of the intended swipe
* threshold: the minimum distance in pixels the swipe has moved on `direction'
* for swipe-finished() to have a non-zero sign
+ * disableClickThreshold: the minimum distance in pixels for the swipe to
+ * not trigger a click
* perpendicularTolerance: see gesture_service
*
* Events:
@@ -34,6 +36,10 @@ const SwipeClick = {
type: Function,
default: () => 30
},
+ disableClickThreshold: {
+ type: Function,
+ default: () => 1
+ },
perpendicularTolerance: {
type: Number,
default: 1.0
@@ -72,6 +78,7 @@ const SwipeClick = {
this.$gesture = new GestureService.SwipeAndClickGesture({
direction: this.direction,
threshold: this.threshold,
+ disableClickThreshold: this.disableClickThreshold,
perpendicularTolerance: this.perpendicularTolerance,
swipePreviewCallback: this.preview,
swipeEndCallback: this.end,
diff --git a/src/components/tab_switcher/tab.style.js b/src/components/tab_switcher/tab.style.js
@@ -0,0 +1,78 @@
+export default {
+ name: 'Tab', // Name of the component
+ selector: '.tab', // CSS selector/prefix
+ states: {
+ active: '.active',
+ hover: ':hover:not(.disabled)',
+ disabled: '.disabled'
+ },
+ validInnerComponents: [
+ 'Text',
+ 'Icon'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--fg',
+ shadow: ['--buttonDefaultShadow', '--buttonDefaultBevel'],
+ roundness: 3
+ }
+ },
+ {
+ state: ['hover'],
+ directives: {
+ shadow: ['--buttonDefaultHoverGlow', '--buttonDefaultBevel']
+ }
+ },
+ {
+ state: ['active'],
+ directives: {
+ opacity: 0
+ }
+ },
+ {
+ state: ['hover', 'active'],
+ directives: {
+ shadow: ['--buttonDefaultShadow', '--buttonDefaultBevel']
+ }
+ },
+ {
+ state: ['disabled'],
+ directives: {
+ background: '$blend(--inheritedBackground 0.25 --parent)',
+ shadow: ['--buttonDefaultBevel']
+ }
+ },
+ {
+ component: 'Text',
+ parent: {
+ component: 'Tab',
+ state: ['disabled']
+ },
+ directives: {
+ textOpacity: 0.25,
+ textOpacityMode: 'blend'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'Tab',
+ state: ['active']
+ },
+ directives: {
+ textColor: '--text'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'Tab',
+ state: ['active', 'hover']
+ },
+ directives: {
+ textColor: '--text'
+ }
+ }
+ ]
+}
diff --git a/src/components/tab_switcher/tab_switcher.jsx b/src/components/tab_switcher/tab_switcher.jsx
@@ -60,13 +60,7 @@ export default {
const isWanted = slot => slot.props && slot.props['data-tab-name'] === tabName
return this.$slots.default().findIndex(isWanted) === this.activeIndex
}
- },
- settingsModalVisible () {
- return this.settingsModalState === 'visible'
- },
- ...mapState({
- settingsModalState: state => state.interface.settingsModalState
- })
+ }
},
beforeUpdate () {
const currentSlot = this.slots()[this.active]
@@ -103,7 +97,7 @@ export default {
.map((slot, index) => {
const props = slot.props
if (!props) return
- const classesTab = ['tab', 'button-default']
+ const classesTab = ['tab']
const classesWrapper = ['tab-wrapper']
if (this.activeIndex === index) {
classesTab.push('active')
@@ -117,6 +111,7 @@ export default {
onClick={this.clickTab(index)}
class={classesTab.join(' ')}
type="button"
+ role="tab"
>
<img src={props.image} title={props['image-tooltip']}/>
{props.label ? '' : props.label}
@@ -131,6 +126,7 @@ export default {
onClick={this.clickTab(index)}
class={classesTab.join(' ')}
type="button"
+ role="tab"
>
{!props.icon ? '' : (<FAIcon class="tab-icon" size="2x" fixed-width icon={props.icon}/>)}
<span class="text">
@@ -149,7 +145,12 @@ export default {
if (props.fullHeight) {
classes.push('full-height')
}
- const renderSlot = (!this.renderOnlyFocused || active)
+ let delayRender = slot.props['delay-render']
+ if (delayRender && active) {
+ slot.props['delay-render'] = false
+ delayRender = false
+ }
+ const renderSlot = (!delayRender && (!this.renderOnlyFocused || active))
? slot
: ''
@@ -167,11 +168,15 @@ export default {
return (
<div class={'tab-switcher ' + (this.sideTabBar ? 'side-tabs' : 'top-tabs')}>
- <div class="tabs">
+ <div
+ class="tabs"
+ role="tablist"
+ >
{tabs}
</div>
<div
ref="contents"
+ role="tabpanel"
class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')}
v-body-scroll-lock={this.bodyScrollLock}
>
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
@@ -1,5 +1,4 @@
-@import '../../_variables.scss';
-
+/* stylelint-disable no-descending-specificity */
.tab-switcher {
display: flex;
@@ -17,13 +16,14 @@
overflow-x: auto;
padding-top: 5px;
flex-direction: row;
+ flex: 0 0 auto;
- &::after, &::before {
- content: '';
+ &::after,
+ &::before {
+ content: "";
flex: 1 1 auto;
border-bottom: 1px solid;
- border-bottom-color: $fallback--border;
- border-bottom-color: var(--border, $fallback--border);
+ border-bottom-color: var(--border);
}
.tab-wrapper {
@@ -34,10 +34,10 @@
right: 0;
bottom: 0;
border-bottom: 1px solid;
- border-bottom-color: $fallback--border;
- border-bottom-color: var(--border, $fallback--border);
+ border-bottom-color: var(--border);
}
}
+
.tab {
width: 100%;
min-width: 1px;
@@ -47,6 +47,7 @@
margin-bottom: 6px - 99px;
}
}
+
.contents.scrollable-tabs {
flex-basis: 0;
}
@@ -69,13 +70,13 @@
overflow-x: hidden;
flex-direction: column;
- &::after, &::before {
+ &::after,
+ &::before {
flex-shrink: 0;
- flex-basis: .5em;
- content: '';
+ flex-basis: 0.5em;
+ content: "";
border-right: 1px solid;
- border-right-color: $fallback--border;
- border-right-color: var(--border, $fallback--border);
+ border-right-color: var(--border);
}
&::after {
@@ -100,16 +101,14 @@
right: 0;
bottom: 0;
border-right: 1px solid;
- border-right-color: $fallback--border;
- border-right-color: var(--border, $fallback--border);
+ border-right-color: var(--border);
}
&::before {
flex: 0 0 6px;
- content: '';
+ content: "";
border-right: 1px solid;
- border-right-color: $fallback--border;
- border-right-color: var(--border, $fallback--border);
+ border-right-color: var(--border);
}
&:last-child .tab {
@@ -120,7 +119,7 @@
.tab {
flex: 1;
box-sizing: content-box;
- min-width: 10em;
+ max-width: 9em;
min-width: 1px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
@@ -129,13 +128,24 @@
margin-right: -200px;
margin-left: 1em;
+ &:not(.active) {
+ margin-top: 0;
+ margin-left: 1.5em;
+ }
+
@media all and (max-width: 800px) {
- padding-left: .25em;
- padding-right: calc(.25em + 200px);
- margin-right: calc(.25em - 200px);
- margin-left: .25em;
+ padding-left: 0.25em;
+ padding-right: calc(0.25em + 200px);
+ margin-right: calc(0.25em - 200px);
+ margin-left: 0.25em;
+
+ &:not(.active) {
+ margin-top: 0;
+ margin-left: 0.5em;
+ }
+
.text {
- display: none
+ display: none;
}
}
}
@@ -144,15 +154,17 @@
.contents {
flex: 1 0 auto;
- min-height: 0px;
+ min-height: 0;
.hidden {
display: none;
}
+
.full-height:not(.hidden) {
height: 100%;
display: flex;
flex-direction: column;
+
> *:not(.mobile-label) {
flex: 1;
}
@@ -164,12 +176,22 @@
}
.tab {
+ user-select: none;
+ color: var(--text);
+ border: none;
+ cursor: pointer;
+ box-shadow: var(--shadow);
+ font-size: 1em;
+ font-family: var(--font);
+ border-radius: var(--roundness);
+ background-color: var(--background);
position: relative;
white-space: nowrap;
padding: 6px 1em;
&:not(.active) {
z-index: 4;
+ margin-top: 0.25em;
&:hover {
z-index: 6;
@@ -179,8 +201,6 @@
&.active {
background: transparent;
z-index: 5;
- color: $fallback--text;
- color: var(--tabActiveText, $fallback--text);
}
img {
@@ -195,7 +215,8 @@
position: relative;
box-sizing: border-box;
- &::after, &::before {
+ &::after,
+ &::before {
display: block;
flex: 1 1 auto;
}
@@ -208,7 +229,7 @@
&:not(.active) {
&::after {
- content: '';
+ content: "";
position: absolute;
z-index: 7;
}
@@ -216,15 +237,16 @@
}
.mobile-label {
- padding-left: .3em;
- padding-bottom: .25em;
- margin-top: .5em;
- margin-left: .2em;
- margin-bottom: .25em;
- border-bottom: 1px solid var(--border, $fallback--border);
+ padding-left: 0.3em;
+ padding-bottom: 0.25em;
+ margin-top: 0.5em;
+ margin-left: 0.2em;
+ margin-bottom: 0.25em;
+ border-bottom: 1px solid var(--border);
@media all and (min-width: 800px) {
display: none;
}
}
}
+/* stylelint-enable no-descending-specificity */
diff --git a/src/components/terms_of_service_panel/terms_of_service_panel.vue b/src/components/terms_of_service_panel/terms_of_service_panel.vue
@@ -13,10 +13,10 @@
</div>
</template>
-<script src="./terms_of_service_panel.js" ></script>
+<script src="./terms_of_service_panel.js"></script>
<style lang="scss">
.tos-content {
- margin: 1em
+ margin: 1em;
}
</style>
diff --git a/src/components/text.style.js b/src/components/text.style.js
@@ -0,0 +1,22 @@
+export default {
+ name: 'Text',
+ selector: '/*text*/',
+ virtual: true,
+ states: {
+ faint: '.faint'
+ },
+ defaultRules: [
+ {
+ directives: {
+ textColor: '--text',
+ textAuto: 'no-preserve'
+ }
+ },
+ {
+ state: ['faint'],
+ directives: {
+ textOpacity: 0.5
+ }
+ }
+ ]
+}
diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue
@@ -1,5 +1,5 @@
<template>
- <div class="thread-tree">
+ <article class="thread-tree">
<status
:key="status.id"
ref="statusComponent"
@@ -113,20 +113,20 @@
</template>
</i18n-t>
</div>
- </div>
+ </article>
</template>
<script src="./thread_tree.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
.thread-tree-replies {
- margin-left: var(--status-margin, $status-margin);
- border-left: 2px solid var(--border, $fallback--border);
+ margin-left: var(--status-margin);
+ border-left: 2px solid var(--border);
}
.thread-tree-replies-hidden {
- padding: var(--status-margin, $status-margin);
+ padding: var(--status-margin);
+
/* Make the button stretch along the whole row */
display: flex;
align-items: stretch;
diff --git a/src/components/timeago/timeago.vue b/src/components/timeago/timeago.vue
@@ -3,7 +3,7 @@
:datetime="time"
:title="localeDateString"
>
- {{ $tc(relativeTime.key, relativeTime.num, [relativeTime.num]) }}
+ {{ relativeOrAbsoluteTimeString }}
</time>
</template>
@@ -13,19 +13,82 @@ import localeService from 'src/services/locale/locale.service.js'
export default {
name: 'Timeago',
- props: ['time', 'autoUpdate', 'longFormat', 'nowThreshold'],
+ props: ['time', 'autoUpdate', 'longFormat', 'nowThreshold', 'templateKey'],
data () {
return {
+ relativeTimeMs: 0,
relativeTime: { key: 'time.now', num: 0 },
interval: null
}
},
computed: {
- localeDateString () {
- const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)
+ shouldUseAbsoluteTimeFormat () {
+ if (!this.$store.getters.mergedConfig.useAbsoluteTimeFormat) {
+ return false
+ }
+ return DateUtils.durationStrToMs(this.$store.getters.mergedConfig.absoluteTimeFormatMinAge) <= this.relativeTimeMs
+ },
+ browserLocale () {
+ return localeService.internalToBrowserLocale(this.$i18n.locale)
+ },
+ timeAsDate () {
return typeof this.time === 'string'
- ? new Date(Date.parse(this.time)).toLocaleString(browserLocale)
- : this.time.toLocaleString(browserLocale)
+ ? new Date(Date.parse(this.time))
+ : this.time
+ },
+ localeDateString () {
+ return this.timeAsDate.toLocaleString(this.browserLocale)
+ },
+ relativeTimeString () {
+ const timeString = this.$i18n.tc(this.relativeTime.key, this.relativeTime.num, [this.relativeTime.num])
+
+ if (typeof this.templateKey === 'string' && this.relativeTime.key !== 'time.now') {
+ return this.$i18n.t(this.templateKey, [timeString])
+ }
+
+ return timeString
+ },
+ absoluteTimeString () {
+ if (this.longFormat) {
+ return this.localeDateString
+ }
+ const now = new Date()
+ const formatter = (() => {
+ if (DateUtils.isSameDay(this.timeAsDate, now)) {
+ return new Intl.DateTimeFormat(this.browserLocale, {
+ minute: 'numeric',
+ hour: 'numeric'
+ })
+ } else if (DateUtils.isSameMonth(this.timeAsDate, now)) {
+ return new Intl.DateTimeFormat(this.browserLocale, {
+ month: 'short',
+ day: 'numeric'
+ })
+ } else if (DateUtils.isSameYear(this.timeAsDate, now)) {
+ return new Intl.DateTimeFormat(this.browserLocale, {
+ month: 'short',
+ day: 'numeric'
+ })
+ } else {
+ return new Intl.DateTimeFormat(this.browserLocale, {
+ year: 'numeric',
+ month: 'short'
+ })
+ }
+ })()
+
+ return formatter.format(this.timeAsDate)
+ },
+ relativeOrAbsoluteTimeString () {
+ return this.shouldUseAbsoluteTimeFormat ? this.absoluteTimeString : this.relativeTimeString
+ }
+ },
+ watch: {
+ time (newVal, oldVal) {
+ if (oldVal !== newVal) {
+ clearTimeout(this.interval)
+ this.refreshRelativeTimeObject()
+ }
}
},
created () {
@@ -37,6 +100,7 @@ export default {
methods: {
refreshRelativeTimeObject () {
const nowThreshold = typeof this.nowThreshold === 'number' ? this.nowThreshold : 1
+ this.relativeTimeMs = DateUtils.relativeTimeMs(this.time)
this.relativeTime = this.longFormat
? DateUtils.relativeTime(this.time, nowThreshold)
: DateUtils.relativeTimeShort(this.time, nowThreshold)
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
@@ -1,15 +1,21 @@
import Status from '../status/status.vue'
+import { mapState } from 'vuex'
import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js'
import Conversation from '../conversation/conversation.vue'
import TimelineMenu from '../timeline_menu/timeline_menu.vue'
-import TimelineQuickSettings from './timeline_quick_settings.vue'
+import QuickFilterSettings from '../quick_filter_settings/quick_filter_settings.vue'
+import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue'
import { debounce, throttle, keyBy } from 'lodash'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faCircleNotch, faCog } from '@fortawesome/free-solid-svg-icons'
+import { faCircleNotch, faCirclePlus, faCog, faMinus, faArrowUp, faCheck } from '@fortawesome/free-solid-svg-icons'
library.add(
faCircleNotch,
- faCog
+ faCog,
+ faMinus,
+ faArrowUp,
+ faCirclePlus,
+ faCheck
)
const Timeline = {
@@ -18,6 +24,9 @@ const Timeline = {
'timelineName',
'title',
'userId',
+ 'listId',
+ 'statusId',
+ 'bookmarkFolderId',
'tag',
'embedded',
'count',
@@ -27,6 +36,7 @@ const Timeline = {
],
data () {
return {
+ showScrollTop: false,
paused: false,
unfocused: false,
bottomedOut: false,
@@ -38,7 +48,8 @@ const Timeline = {
Status,
Conversation,
TimelineMenu,
- TimelineQuickSettings
+ QuickFilterSettings,
+ QuickViewSettings
},
computed: {
filteredVisibleStatuses () {
@@ -60,14 +71,21 @@ const Timeline = {
return `${this.$t('timeline.show_new')} (${this.newStatusCount})`
}
},
+ mobileLoadButtonString () {
+ if (this.timeline.flushMarker !== 0) {
+ return '+'
+ } else {
+ return this.newStatusCount > 99 ? '∞' : this.newStatusCount
+ }
+ },
classes () {
- let rootClasses = !this.embedded ? ['panel', 'panel-default'] : ['-nonpanel']
+ let rootClasses = !this.embedded ? ['panel', 'panel-default'] : ['-embedded']
if (this.blockingClicks) rootClasses = rootClasses.concat(['-blocked', '_misclick-prevention'])
return {
root: rootClasses,
- header: ['timeline-heading'].concat(!this.embedded ? ['panel-heading', '-sticky'] : []),
- body: ['timeline-body'].concat(!this.embedded ? ['panel-body'] : []),
- footer: ['timeline-footer'].concat(!this.embedded ? ['panel-footer'] : [])
+ header: ['timeline-heading'].concat(!this.embedded ? ['panel-heading', '-sticky'] : ['panel-body']),
+ body: ['timeline-body'].concat(!this.embedded ? ['panel-body'] : ['panel-body']),
+ footer: ['timeline-footer'].concat(!this.embedded ? ['panel-footer'] : ['panel-body'])
}
},
// id map of statuses which need to be hidden in the main list due to pinning logic
@@ -84,7 +102,10 @@ const Timeline = {
},
virtualScrollingEnabled () {
return this.$store.getters.mergedConfig.virtualScrolling
- }
+ },
+ ...mapState({
+ mobileLayout: state => state.interface.layoutType === 'mobile'
+ })
},
created () {
const store = this.$store
@@ -101,6 +122,9 @@ const Timeline = {
timeline: this.timelineName,
showImmediately,
userId: this.userId,
+ listId: this.listId,
+ statusId: this.statusId,
+ bookmarkFolderId: this.bookmarkFolderId,
tag: this.tag
})
},
@@ -119,6 +143,9 @@ const Timeline = {
this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
},
methods: {
+ scrollToTop () {
+ window.scrollTo({ top: this.$el.offsetTop })
+ },
stopBlockingClicks: debounce(function () {
this.blockingClicks = false
}, 1000),
@@ -137,6 +164,9 @@ const Timeline = {
if (this.timeline.flushMarker !== 0) {
this.$store.commit('clearTimeline', { timeline: this.timelineName, excludeUserId: true })
this.$store.commit('queueFlush', { timeline: this.timelineName, id: 0 })
+ if (this.timelineName === 'user') {
+ this.$store.dispatch('fetchPinnedStatuses', this.userId)
+ }
this.fetchOlderStatuses()
} else {
this.blockClicksTemporarily()
@@ -156,6 +186,9 @@ const Timeline = {
older: true,
showImmediately: true,
userId: this.userId,
+ listId: this.listId,
+ statusId: this.statusId,
+ bookmarkFolderId: this.bookmarkFolderId,
tag: this.tag
}).then(({ statuses }) => {
if (statuses && statuses.length === 0) {
@@ -217,6 +250,7 @@ const Timeline = {
}
},
handleScroll: throttle(function (e) {
+ this.showScrollTop = this.$el.offsetTop < window.scrollY
this.determineVisibleStatuses()
this.scrollLoad(e)
}, 200),
diff --git a/src/components/timeline/timeline.scss b/src/components/timeline/timeline.scss
@@ -1,8 +1,24 @@
-@import '../../_variables.scss';
-
.Timeline {
- .loadmore-text {
- opacity: 1;
+ .timeline-body {
+ background: none;
+ backdrop-filter: none;
+ }
+
+ .alert-badge {
+ font-size: 0.75em;
+ line-height: 1;
+ text-align: right;
+ border-radius: var(--roundness);
+ position: absolute;
+ left: calc(50% - 0.5em);
+ top: calc(50% - 0.4em);
+ padding: 0.2em;
+ margin-left: 0.7em;
+ margin-top: -1em;
+ }
+
+ .loadmore-button {
+ position: relative;
}
&.-blocked {
@@ -10,19 +26,23 @@
}
.conversation-heading {
- top: calc(var(--__panel-heading-height) * var(--currentPanelStack, 2));
+ top: calc(var(--__panel-heading-height) * var(--currentPanelStack, 1) + var(--navbar-height));
z-index: 2;
}
- &.-nonpanel {
+ &.-embedded {
.timeline-heading {
text-align: center;
line-height: 2.75em;
padding: 0 0.5em;
- }
- .timeline-heading {
- .button-default, .alert {
+ // Override the shrug empty filler
+ &:empty::before {
+ content: initial;
+ }
+
+ .button-default,
+ .alert {
line-height: 2em;
width: 100%;
}
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
@@ -1,31 +1,97 @@
<template>
<div :class="['Timeline', classes.root]">
- <div :class="classes.header">
- <TimelineMenu v-if="!embedded" />
- <button
- v-if="showLoadButton"
- class="button-default loadmore-button"
- @click.prevent="showNewStatuses"
- >
- {{ loadButtonString }}
- </button>
+ <div
+ v-if="!embedded"
+ :class="classes.header"
+ >
+ <TimelineMenu
+ v-if="!embedded"
+ :timeline-name="timelineName"
+ />
<div
- v-else-if="!embedded"
- class="loadmore-text faint"
- @click.prevent
+ v-if="showScrollTop"
+ class="rightside-button"
>
- {{ $t('timeline.up_to_date') }}
+ <button
+ class="button-unstyled scroll-to-top-button"
+ type="button"
+ :title="$t('general.scroll_to_top')"
+ @click="scrollToTop"
+ >
+ <FALayers class="fa-scale-110 fa-old-padding-layer">
+ <FAIcon icon="arrow-up" />
+ <FAIcon
+ icon="minus"
+ transform="up-7"
+ />
+ </FALayers>
+ </button>
</div>
- <TimelineQuickSettings v-if="!embedded" />
+ <template v-if="mobileLayout">
+ <div
+ v-if="showLoadButton"
+ class="rightside-button"
+ >
+ <button
+ class="button-unstyled loadmore-button"
+ :title="loadButtonString"
+ @click.prevent="showNewStatuses"
+ >
+ <FAIcon
+ fixed-width
+ icon="circle-plus"
+ />
+ <div class="badge -counter">
+ {{ mobileLoadButtonString }}
+ </div>
+ </button>
+ </div>
+ <div
+ v-else
+ class="loadmore-text faint veryfaint rightside-icon"
+ :title="$t('timeline.up_to_date')"
+ :aria-disabled="true"
+ @click.prevent
+ >
+ <FAIcon
+ fixed-width
+ icon="check"
+ />
+ </div>
+ </template>
+ <template v-else>
+ <button
+ v-if="showLoadButton"
+ class="button-default loadmore-button"
+ @click.prevent="showNewStatuses"
+ >
+ {{ loadButtonString }}
+ </button>
+ <div
+ v-else
+ class="loadmore-text faint"
+ @click.prevent
+ >
+ {{ $t('timeline.up_to_date') }}
+ </div>
+ </template>
+ <QuickFilterSettings
+ class="rightside-button"
+ />
+ <QuickViewSettings
+ class="rightside-button"
+ />
</div>
<div :class="classes.body">
<div
ref="timeline"
class="timeline"
+ role="feed"
>
<conversation
v-for="statusId in filteredPinnedStatusIds"
:key="statusId + '-pinned'"
+ role="listitem"
class="status-fadein"
:status-id="statusId"
:collapsable="true"
@@ -36,6 +102,7 @@
<conversation
v-for="status in filteredVisibleStatuses"
:key="status.id"
+ role="listitem"
class="status-fadein"
:status-id="status.id"
:collapsable="true"
@@ -46,7 +113,10 @@
</div>
</div>
<div :class="classes.footer">
- <teleport :to="footerSlipgate" :disabled="!embedded || !footerSlipgate">
+ <teleport
+ :to="footerSlipgate"
+ :disabled="!embedded || !footerSlipgate"
+ >
<div
v-if="count===0"
class="new-status-notification text-center faint"
@@ -79,6 +149,8 @@
/>
</div>
</teleport>
+ <!-- spacer to avoid having empty shrug -->
+ <span v-if="embedded && footerSlipgate" />
</div>
</div>
</template>
diff --git a/src/components/timeline/timeline_quick_settings.js b/src/components/timeline/timeline_quick_settings.js
@@ -1,67 +0,0 @@
-import Popover from '../popover/popover.vue'
-import { mapGetters } from 'vuex'
-import { library } from '@fortawesome/fontawesome-svg-core'
-import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons'
-
-library.add(
- faFilter,
- faFont,
- faWrench
-)
-
-const TimelineQuickSettings = {
- components: {
- Popover
- },
- methods: {
- setReplyVisibility (visibility) {
- this.$store.dispatch('setOption', { name: 'replyVisibility', value: visibility })
- this.$store.dispatch('queueFlushAll')
- },
- openTab (tab) {
- this.$store.dispatch('openSettingsModalTab', tab)
- }
- },
- computed: {
- ...mapGetters(['mergedConfig']),
- loggedIn () {
- return !!this.$store.state.users.currentUser
- },
- replyVisibilitySelf: {
- get () { return this.mergedConfig.replyVisibility === 'self' },
- set () { this.setReplyVisibility('self') }
- },
- replyVisibilityFollowing: {
- get () { return this.mergedConfig.replyVisibility === 'following' },
- set () { this.setReplyVisibility('following') }
- },
- replyVisibilityAll: {
- get () { return this.mergedConfig.replyVisibility === 'all' },
- set () { this.setReplyVisibility('all') }
- },
- hideMedia: {
- get () { return this.mergedConfig.hideAttachments || this.mergedConfig.hideAttachmentsInConv },
- set () {
- const value = !this.hideMedia
- this.$store.dispatch('setOption', { name: 'hideAttachments', value })
- this.$store.dispatch('setOption', { name: 'hideAttachmentsInConv', value })
- }
- },
- hideMutedPosts: {
- get () { return this.mergedConfig.hideFilteredStatuses },
- set () {
- const value = !this.hideMutedPosts
- this.$store.dispatch('setOption', { name: 'hideFilteredStatuses', value })
- }
- },
- muteBotStatuses: {
- get () { return this.mergedConfig.muteBotStatuses },
- set () {
- const value = !this.muteBotStatuses
- this.$store.dispatch('setOption', { name: 'muteBotStatuses', value })
- }
- }
- }
-}
-
-export default TimelineQuickSettings
diff --git a/src/components/timeline/timeline_quick_settings.vue b/src/components/timeline/timeline_quick_settings.vue
@@ -1,109 +0,0 @@
-<template>
- <Popover
- trigger="click"
- class="TimelineQuickSettings"
- :bound-to="{ x: 'container' }"
- >
- <template v-slot:content>
- <div class="dropdown-menu">
- <div v-if="loggedIn">
- <button
- class="button-default dropdown-item"
- @click="replyVisibilityAll = true"
- >
- <span
- class="menu-checkbox -radio"
- :class="{ 'menu-checkbox-checked': replyVisibilityAll }"
- />{{ $t('settings.reply_visibility_all') }}
- </button>
- <button
- class="button-default dropdown-item"
- @click="replyVisibilityFollowing = true"
- >
- <span
- class="menu-checkbox -radio"
- :class="{ 'menu-checkbox-checked': replyVisibilityFollowing }"
- />{{ $t('settings.reply_visibility_following_short') }}
- </button>
- <button
- class="button-default dropdown-item"
- @click="replyVisibilitySelf = true"
- >
- <span
- class="menu-checkbox -radio"
- :class="{ 'menu-checkbox-checked': replyVisibilitySelf }"
- />{{ $t('settings.reply_visibility_self_short') }}
- </button>
- <div
- role="separator"
- class="dropdown-divider"
- />
- </div>
- <button
- class="button-default dropdown-item"
- @click="muteBotStatuses = !muteBotStatuses"
- >
- <span
- class="menu-checkbox"
- :class="{ 'menu-checkbox-checked': muteBotStatuses }"
- />{{ $t('settings.mute_bot_posts') }}
- </button>
- <button
- class="button-default dropdown-item"
- @click="hideMedia = !hideMedia"
- >
- <span
- class="menu-checkbox"
- :class="{ 'menu-checkbox-checked': hideMedia }"
- />{{ $t('settings.hide_media_previews') }}
- </button>
- <button
- class="button-default dropdown-item"
- @click="hideMutedPosts = !hideMutedPosts"
- >
- <span
- class="menu-checkbox"
- :class="{ 'menu-checkbox-checked': hideMutedPosts }"
- />{{ $t('settings.hide_all_muted_posts') }}
- </button>
- <button
- class="button-default dropdown-item dropdown-item-icon"
- @click="openTab('filtering')"
- >
- <FAIcon icon="font" />{{ $t('settings.word_filter') }}
- </button>
- <button
- class="button-default dropdown-item dropdown-item-icon"
- @click="openTab('general')"
- >
- <FAIcon icon="wrench" />{{ $t('settings.more_settings') }}
- </button>
- </div>
- </template>
- <template v-slot:trigger>
- <button class="button-unstyled">
- <FAIcon icon="filter" />
- </button>
- </template>
- </Popover>
-</template>
-
-<script src="./timeline_quick_settings.js"></script>
-
-<style lang="scss">
-
-.TimelineQuickSettings {
-
- > button {
- line-height: 100%;
- height: 100%;
- width: var(--__panel-heading-height-inner);
- text-align: center;
-
- svg {
- font-size: 1.2em;
- }
- }
-}
-
-</style>
diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js
@@ -1,6 +1,11 @@
import Popover from '../popover/popover.vue'
-import TimelineMenuContent from './timeline_menu_content.vue'
+import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
+import { mapState } from 'vuex'
+import { ListsMenuContent } from '../lists_menu/lists_menu_content.vue'
+import { BookmarkFoldersMenuContent } from '../bookmark_folders_menu/bookmark_folders_menu_content.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
+import { TIMELINES } from 'src/components/navigation/navigation.js'
+import { filterNavigation } from 'src/components/navigation/filter.js'
import {
faChevronDown
} from '@fortawesome/free-solid-svg-icons'
@@ -9,20 +14,23 @@ library.add(faChevronDown)
// Route -> i18n key mapping, exported and not in the computed
// because nav panel benefits from the same information.
-export const timelineNames = () => {
+export const timelineNames = (supportsBookmarkFolders) => {
return {
- 'friends': 'nav.home_timeline',
- 'bookmarks': 'nav.bookmarks',
- 'dms': 'nav.dms',
+ friends: 'nav.home_timeline',
+ bookmarks: supportsBookmarkFolders ? 'nav.all_bookmarks' : 'nav.bookmarks',
+ dms: 'nav.dms',
'public-timeline': 'nav.public_tl',
- 'public-external-timeline': 'nav.twkn'
+ 'public-external-timeline': 'nav.twkn',
+ quotes: 'nav.quotes'
}
}
const TimelineMenu = {
components: {
Popover,
- TimelineMenuContent
+ NavigationEntry,
+ ListsMenuContent,
+ BookmarkFoldersMenuContent
},
data () {
return {
@@ -30,10 +38,38 @@ const TimelineMenu = {
}
},
created () {
- if (timelineNames()[this.$route.name]) {
+ if (timelineNames(this.bookmarkFolders)[this.$route.name]) {
this.$store.dispatch('setLastTimeline', this.$route.name)
}
},
+ computed: {
+ useListsMenu () {
+ const route = this.$route.name
+ return route === 'lists-timeline'
+ },
+ useBookmarkFoldersMenu () {
+ const route = this.$route.name
+ return this.bookmarkFolders && (route === 'bookmark-folder' || route === 'bookmarks')
+ },
+ ...mapState({
+ currentUser: state => state.users.currentUser,
+ privateMode: state => state.instance.private,
+ federating: state => state.instance.federating,
+ bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable
+ }),
+ timelinesList () {
+ return filterNavigation(
+ Object.entries(TIMELINES).map(([k, v]) => ({ ...v, name: k })),
+ {
+ hasChats: this.pleromaChatMessagesAvailable,
+ isFederating: this.federating,
+ isPrivate: this.privateMode,
+ currentUser: this.currentUser,
+ supportsBookmarkFolders: this.bookmarkFolders
+ }
+ )
+ }
+ },
methods: {
openMenu () {
// $nextTick is too fast, animation won't play back but
@@ -58,7 +94,13 @@ const TimelineMenu = {
if (route === 'tag-timeline') {
return '#' + this.$route.params.tag
}
- const i18nkey = timelineNames()[this.$route.name]
+ if (route === 'lists-timeline') {
+ return this.$store.getters.findListTitle(this.$route.params.id)
+ }
+ if (route === 'bookmark-folder') {
+ return this.$store.getters.findBookmarkFolderName(this.$route.params.id)
+ }
+ const i18nkey = timelineNames(this.bookmarkFolders)[this.$route.name]
return i18nkey ? this.$t(i18nkey) : route
}
}
diff --git a/src/components/timeline_menu/timeline_menu.vue b/src/components/timeline_menu/timeline_menu.vue
@@ -3,20 +3,34 @@
trigger="click"
class="TimelineMenu"
:class="{ 'open': isOpen }"
- :margin="{ left: -15, right: -200 }"
:bound-to="{ x: 'container' }"
- popover-class="timeline-menu-popover-wrap"
+ bound-to-selector=".Timeline"
+ popover-class="timeline-menu-popover popover-default"
@show="openMenu"
@close="() => isOpen = false"
>
- <template v-slot:content>
- <div class="timeline-menu-popover popover-default">
- <TimelineMenuContent />
- </div>
+ <template #content>
+ <ListsMenuContent
+ v-if="useListsMenu"
+ :show-pin="false"
+ class="timelines"
+ />
+ <BookmarkFoldersMenuContent
+ v-else-if="useBookmarkFoldersMenu"
+ class="timelines"
+ />
+ <ul v-else>
+ <NavigationEntry
+ v-for="item in timelinesList"
+ :key="item.name"
+ :show-pin="false"
+ :item="item"
+ />
+ </ul>
</template>
- <template v-slot:trigger>
- <button class="button-unstyled title timeline-menu-title">
- <span class="timeline-title">{{ timelineName() }}</span>
+ <template #trigger>
+ <span class="button-unstyled timeline-menu-title">
+ <h1 class="title timeline-title">{{ timelineName() }}</h1>
<span>
<FAIcon
size="sm"
@@ -27,53 +41,42 @@
class="click-blocker"
@click="blockOpen"
/>
- </button>
+ </span>
</template>
</Popover>
</template>
-<script src="./timeline_menu.js" ></script>
+<script src="./timeline_menu.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
+.timeline-menu-popover {
+ min-width: 24rem;
+ max-width: 100vw;
+ margin-top: 0.6rem;
+ font-size: 1rem;
+ border-top-right-radius: 0;
+ border-top-left-radius: 0;
+
+ ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ }
+}
.TimelineMenu {
- flex-shrink: 1;
margin-right: auto;
min-width: 0;
- width: 24rem;
.popover-trigger-button {
vertical-align: bottom;
}
- .timeline-menu-popover-wrap {
- overflow: hidden;
- // Match panel heading padding to line up menu with bottom of heading
- margin-top: 0.6rem;
- padding: 0 15px 15px 15px;
- }
-
- .timeline-menu-popover {
- width: 24rem;
- max-width: 100vw;
- margin: 0;
- font-size: 1rem;
- border-top-right-radius: 0;
- border-top-left-radius: 0;
- transform: translateY(-100%);
- transition: transform 100ms;
- }
-
.panel::after {
border-top-right-radius: 0;
border-top-left-radius: 0;
}
- &.open .timeline-menu-popover {
- transform: translateY(0);
- }
-
.timeline-menu-title {
margin: 0;
cursor: pointer;
@@ -99,75 +102,11 @@
}
&.open .timeline-menu-title svg {
- color: $fallback--text;
- color: var(--panelText, $fallback--text);
transform: rotate(180deg);
}
.panel {
box-shadow: var(--popoverShadow);
}
-
- ul {
- list-style: none;
- margin: 0;
- padding: 0;
- }
-
- li {
- border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- padding: 0;
-
- &:last-child a {
- border-bottom-right-radius: $fallback--panelRadius;
- border-bottom-right-radius: var(--panelRadius, $fallback--panelRadius);
- border-bottom-left-radius: $fallback--panelRadius;
- border-bottom-left-radius: var(--panelRadius, $fallback--panelRadius);
- }
-
- &:last-child {
- border: none;
- }
- }
-
- a {
- display: block;
- padding: 0.6em 0.65em;
-
- &:hover {
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- color: $fallback--link;
- color: var(--selectedMenuText, $fallback--link);
- --faint: var(--selectedMenuFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuLightText, $fallback--lightText);
- --icon: var(--selectedMenuIcon, $fallback--icon);
- }
-
- &.router-link-active {
- font-weight: bolder;
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- color: $fallback--text;
- color: var(--selectedMenuText, $fallback--text);
- --faint: var(--selectedMenuFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuLightText, $fallback--lightText);
- --icon: var(--selectedMenuIcon, $fallback--icon);
-
- &:hover {
- text-decoration: underline;
- }
- }
-
- svg {
- margin-right: 0.4em;
- margin-left: -0.2em;
- }
- }
}
-
</style>
diff --git a/src/components/timeline_menu/timeline_menu_content.js b/src/components/timeline_menu/timeline_menu_content.js
@@ -1,29 +0,0 @@
-import { mapState } from 'vuex'
-import { library } from '@fortawesome/fontawesome-svg-core'
-import {
- faUsers,
- faGlobe,
- faBookmark,
- faEnvelope,
- faHome
-} from '@fortawesome/free-solid-svg-icons'
-
-library.add(
- faUsers,
- faGlobe,
- faBookmark,
- faEnvelope,
- faHome
-)
-
-const TimelineMenuContent = {
- computed: {
- ...mapState({
- currentUser: state => state.users.currentUser,
- privateMode: state => state.instance.private,
- federating: state => state.instance.federating
- })
- }
-}
-
-export default TimelineMenuContent
diff --git a/src/components/timeline_menu/timeline_menu_content.vue b/src/components/timeline_menu/timeline_menu_content.vue
@@ -1,66 +0,0 @@
-<template>
- <ul>
- <li v-if="currentUser">
- <router-link
- class="menu-item"
- :to="{ name: 'friends' }"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110 fa-old-padding "
- icon="home"
- />{{ $t("nav.home_timeline") }}
- </router-link>
- </li>
- <li v-if="currentUser || !privateMode">
- <router-link
- class="menu-item"
- :to="{ name: 'public-timeline' }"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110 fa-old-padding "
- icon="users"
- />{{ $t("nav.public_tl") }}
- </router-link>
- </li>
- <li v-if="federating && (currentUser || !privateMode)">
- <router-link
- class="menu-item"
- :to="{ name: 'public-external-timeline' }"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110 fa-old-padding "
- icon="globe"
- />{{ $t("nav.twkn") }}
- </router-link>
- </li>
- <li v-if="currentUser">
- <router-link
- class="menu-item"
- :to="{ name: 'bookmarks'}"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110 fa-old-padding "
- icon="bookmark"
- />{{ $t("nav.bookmarks") }}
- </router-link>
- </li>
- <li v-if="currentUser">
- <router-link
- class="menu-item"
- :to="{ name: 'dms', params: { username: currentUser.screen_name } }"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110 fa-old-padding "
- icon="envelope"
- />{{ $t("nav.dms") }}
- </router-link>
- </li>
- </ul>
-</template>
-
-<script src="./timeline_menu_content.js" ></script>
diff --git a/src/components/tooltip/tooltip.vue b/src/components/tooltip/tooltip.vue
@@ -0,0 +1,24 @@
+<template>
+ <Popover trigger="hover">
+ <template #trigger>
+ <slot />
+ </template>
+ <template #content>
+ <div class="tooltip">
+ {{ props.text }}
+ </div>
+ </template>
+ </Popover>
+</template>
+
+<script setup>
+import Popover from 'src/components/popover/popover.vue'
+
+const props = defineProps(['text'])
+</script>
+
+<style lang="scss">
+.tooltip {
+ margin: 0.5em 1em;
+}
+</style>
diff --git a/src/components/top_bar.style.js b/src/components/top_bar.style.js
@@ -0,0 +1,28 @@
+export default {
+ name: 'TopBar',
+ selector: 'nav',
+ validInnerComponents: [
+ 'Link',
+ 'Text',
+ 'Icon',
+ 'Button',
+ 'ButtonUnstyled',
+ 'Input',
+ 'Badge'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--fg',
+ shadow: [{
+ x: 0,
+ y: 0,
+ blur: 4,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.6
+ }]
+ }
+ }
+ ]
+}
diff --git a/src/components/underlay.style.js b/src/components/underlay.style.js
@@ -0,0 +1,19 @@
+export default {
+ name: 'Underlay',
+ selector: '#content',
+ // Out of tree selector: Most components are laid over underlay, but underlay itself is not part of the DOM tree,
+ // i.e. it's a separate absolutely-positioned component, so we need to treat it differently depending on whether
+ // we are searching for underlay specifically or for whatever is laid on top of it.
+ outOfTreeSelector: '.underlay',
+ validInnerComponents: [
+ 'Panel'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '#000000',
+ opacity: 0.2
+ }
+ }
+ ]
+}
diff --git a/src/components/unicode_domain_indicator/unicode_domain_indicator.vue b/src/components/unicode_domain_indicator/unicode_domain_indicator.vue
@@ -0,0 +1,26 @@
+<template>
+ <FAIcon
+ v-if="user && user.screen_name_ui_contains_non_ascii"
+ icon="code"
+ :title="$t('unicode_domain_indicator.tooltip')"
+ />
+</template>
+
+<script>
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faCode
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faCode
+)
+
+const UnicodeDomainIndicator = {
+ props: {
+ user: Object
+ }
+}
+
+export default UnicodeDomainIndicator
+</script>
diff --git a/src/components/update_notification/update_notification.js b/src/components/update_notification/update_notification.js
@@ -0,0 +1,69 @@
+import Modal from 'src/components/modal/modal.vue'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import pleromaTan from 'src/assets/pleromatan_apology.png'
+import pleromaTanFox from 'src/assets/pleromatan_apology_fox.png'
+import pleromaTanMask from 'src/assets/pleromatan_apology_mask.png'
+import pleromaTanFoxMask from 'src/assets/pleromatan_apology_fox_mask.png'
+
+import {
+ faTimes
+} from '@fortawesome/free-solid-svg-icons'
+library.add(
+ faTimes
+)
+
+export const CURRENT_UPDATE_COUNTER = 1
+
+const UpdateNotification = {
+ data () {
+ return {
+ showingImage: false,
+ pleromaTanVariant: Math.random() > 0.5 ? pleromaTan : pleromaTanFox,
+ showingMore: false
+ }
+ },
+ components: {
+ Modal
+ },
+ computed: {
+ pleromaTanStyles () {
+ const mask = this.pleromaTanVariant === pleromaTan ? pleromaTanMask : pleromaTanFoxMask
+ return {
+ 'shape-outside': 'url(' + mask + ')'
+ }
+ },
+ shouldShow () {
+ return !this.$store.state.instance.disableUpdateNotification &&
+ this.$store.state.users.currentUser &&
+ this.$store.state.serverSideStorage.flagStorage.updateCounter < CURRENT_UPDATE_COUNTER &&
+ !this.$store.state.serverSideStorage.prefsStorage.simple.dontShowUpdateNotifs
+ }
+ },
+ methods: {
+ toggleShow () {
+ this.showingMore = !this.showingMore
+ },
+ neverShowAgain () {
+ this.toggleShow()
+ this.$store.commit('setFlag', { flag: 'updateCounter', value: CURRENT_UPDATE_COUNTER })
+ this.$store.commit('setPreference', { path: 'simple.dontShowUpdateNotifs', value: true })
+ this.$store.dispatch('pushServerSideStorage')
+ },
+ dismiss () {
+ this.$store.commit('setFlag', { flag: 'updateCounter', value: CURRENT_UPDATE_COUNTER })
+ this.$store.dispatch('pushServerSideStorage')
+ }
+ },
+ mounted () {
+ this.contentHeightNoImage = this.$refs.animatedText.scrollHeight
+
+ // Workaround to get the text height only after mask loaded. A bit hacky.
+ const newImg = new Image()
+ newImg.onload = () => {
+ setTimeout(() => { this.showingImage = true }, 100)
+ }
+ newImg.src = this.pleromaTanVariant === pleromaTan ? pleromaTanMask : pleromaTanFoxMask
+ }
+}
+
+export default UpdateNotification
diff --git a/src/components/update_notification/update_notification.scss b/src/components/update_notification/update_notification.scss
@@ -0,0 +1,113 @@
+.UpdateNotification {
+ overflow: hidden;
+}
+
+.UpdateNotificationModal {
+ --__top-fringe: 15em; // how much pleroma-tan should stick her head above
+ --__bottom-fringe: 80em; // just reserving as much as we can, number is mostly irrelevant
+ --__right-fringe: 8em;
+
+ font-size: 15px;
+ position: relative;
+ transition: transform;
+ transition-timing-function: ease-in-out;
+ transition-duration: 500ms;
+
+ .text {
+ max-width: 40em;
+ padding-left: 1em;
+ }
+
+ @media all and (max-width: 800px) {
+ /* For mobile, the modal takes 100% of the available screen.
+ This ensures the minimized modal is always 50px above the browser
+ bottom bar regardless of whether or not it is visible.
+ */
+ width: 100vw;
+ }
+
+ @media all and (max-height: 600px) {
+ display: none;
+ }
+
+ .content {
+ overflow: hidden;
+ margin-top: calc(-1 * var(--__top-fringe));
+ margin-bottom: calc(-1 * var(--__bottom-fringe));
+ margin-right: calc(-1 * var(--__right-fringe));
+
+ &.-noImage {
+ .text {
+ padding-right: var(--__right-fringe);
+ }
+ }
+ }
+
+ .panel-body {
+ border-width: 0 0 1px;
+ border-style: solid;
+ border-color: var(--border);
+ }
+
+ .panel-footer {
+ z-index: 22;
+ position: relative;
+ border-width: 0;
+ grid-template-columns: auto;
+ }
+
+ .pleroma-tan {
+ object-fit: cover;
+ object-position: top;
+ transition: position, left, right, top, bottom, max-width, max-height;
+ transition-timing-function: ease-in-out;
+ transition-duration: 500ms;
+ width: 25em;
+ float: right;
+ z-index: 20;
+ position: relative;
+ shape-margin: 0.5em;
+ filter: drop-shadow(5px 5px 10px rgb(0 0 0 / 50%));
+ pointer-events: none;
+ }
+
+ .spacer-top {
+ min-height: var(--__top-fringe);
+ }
+
+ .spacer-bottom {
+ min-height: var(--__bottom-fringe);
+ }
+
+ .extra-info-group {
+ transition: max-height, padding, height;
+ transition-timing-function: ease-in;
+ transition-duration: 700ms;
+ max-height: 70vh;
+ mask:
+ linear-gradient(to top, white, transparent) bottom/100% 2px no-repeat,
+ linear-gradient(to top, white, white);
+ }
+
+ .art-credit {
+ text-align: right;
+ }
+
+ &.-peek {
+ /* Explanation:
+ * 100vh - 100% = Distance between modal's top+bottom boundaries and screen
+ * (100vh - 100%) / 2 = Distance between bottom (or top) boundary and screen
+ */
+ transform: translateY(calc(((100vh - 100%) / 2)));
+
+ .pleroma-tan {
+ float: right;
+ z-index: 10;
+ shape-image-threshold: 70%;
+ }
+
+ .extra-info-group {
+ max-height: 0;
+ }
+ }
+}
diff --git a/src/components/update_notification/update_notification.vue b/src/components/update_notification/update_notification.vue
@@ -0,0 +1,106 @@
+<template>
+ <Modal
+ :is-open="!!shouldShow"
+ class="UpdateNotification"
+ :no-background="true"
+ >
+ <div
+ class="UpdateNotificationModal panel"
+ :class="{ '-peek': !showingMore }"
+ >
+ <div class="panel-heading">
+ <h1 class="title">
+ {{ $t('update.big_update_title') }}
+ </h1>
+ </div>
+ <div class="panel-body">
+ <div
+ class="content"
+ :class="{ '-noImage': !showingImage }"
+ >
+ <img
+ v-if="showingImage"
+ class="pleroma-tan"
+ :src="pleromaTanVariant"
+ :style="pleromaTanStyles"
+ >
+ <div class="spacer-top" />
+ <div class="text">
+ <p>
+ {{ $t('update.big_update_content') }}
+ </p>
+ <div
+ ref="animatedText"
+ class="extra-info-group"
+ >
+ <i18n-t
+ scope="global"
+ keypath="update.update_bugs"
+ tag="p"
+ >
+ <template #pleromaGitlab>
+ <a
+ target="_blank"
+ href="https://git.pleroma.social/"
+ >{{ $t('update.update_bugs_gitlab') }}</a>
+ </template>
+ </i18n-t>
+ <i18n-t
+ scope="global"
+ keypath="update.update_changelog"
+ tag="p"
+ >
+ <template #theFullChangelog>
+ <a
+ target="_blank"
+ href="https://pleroma.social/announcements/"
+ >{{ $t('update.update_changelog_here') }}</a>
+ </template>
+ </i18n-t>
+ <p class="art-credit">
+ <i18n-t
+ scope="global"
+ keypath="update.art_by"
+ tag="small"
+ >
+ <template #linkToArtist>
+ <a
+ target="_blank"
+ href="https://post.ebin.club/users/pipivovott"
+ >pipivovott</a>
+ </template>
+ </i18n-t>
+ </p>
+ </div>
+ </div>
+ <div class="spacer-bottom" />
+ </div>
+ </div>
+ <div class="panel-footer">
+ <button
+ class="button-default"
+ @click.prevent="neverShowAgain"
+ >
+ {{ $t("general.never_show_again") }}
+ </button>
+ <button
+ v-if="!showingMore"
+ class="button-default"
+ @click.prevent="toggleShow"
+ >
+ {{ $t("general.show_more") }}
+ </button>
+ <button
+ class="button-default"
+ @click.prevent="dismiss"
+ >
+ {{ $t("general.dismiss") }}
+ </button>
+ </div>
+ </div>
+ </Modal>
+</template>
+
+<script src="./update_notification.js"></script>
+
+<style src="./update_notification.scss" lang="scss"></style>
diff --git a/src/components/user_avatar/avatar.style.js b/src/components/user_avatar/avatar.style.js
@@ -0,0 +1,22 @@
+export default {
+ name: 'Avatar',
+ selector: '.Avatar',
+ variants: {
+ compact: '.-compact'
+ },
+ defaultRules: [
+ {
+ directives: {
+ roundness: 3,
+ shadow: [{
+ x: 0,
+ y: 1,
+ blur: 8,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.7
+ }]
+ }
+ }
+ ]
+}
diff --git a/src/components/user_avatar/user_avatar.js b/src/components/user_avatar/user_avatar.js
@@ -3,11 +3,13 @@ import StillImage from '../still-image/still-image.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
- faRobot
+ faRobot,
+ faPeopleGroup
} from '@fortawesome/free-solid-svg-icons'
library.add(
- faRobot
+ faRobot,
+ faPeopleGroup
)
const UserAvatar = {
@@ -15,7 +17,7 @@ const UserAvatar = {
'user',
'betterShadow',
'compact',
- 'bot'
+ 'showActorTypeIndicator'
],
data () {
return {
diff --git a/src/components/user_avatar/user_avatar.vue b/src/components/user_avatar/user_avatar.vue
@@ -18,21 +18,24 @@
:class="{ '-compact': compact }"
/>
<FAIcon
- v-if="bot"
+ v-if="showActorTypeIndicator && user?.actor_type === 'Service'"
icon="robot"
- class="bot-indicator"
+ class="actor-type-indicator"
+ />
+ <FAIcon
+ v-if="showActorTypeIndicator && user?.actor_type === 'Group'"
+ icon="people-group"
+ class="actor-type-indicator"
/>
</span>
</template>
<script src="./user_avatar.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.Avatar {
- --_avatarShadowBox: var(--avatarStatusShadow);
- --_avatarShadowFilter: var(--avatarStatusShadowFilter);
- --_avatarShadowInset: var(--avatarStatusShadowInset);
+ --_avatarShadowBox: var(--shadow);
+ --_avatarShadowFilter: var(--shadowFilter);
+ --_avatarShadowInset: var(--shadowInset);
--_still-image-label-visibility: hidden;
display: inline-block;
@@ -43,16 +46,14 @@
&.-compact {
width: 32px;
height: 32px;
- border-radius: $fallback--avatarAltRadius;
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
+ border-radius: var(--roundness);
}
.avatar {
width: 100%;
height: 100%;
box-shadow: var(--_avatarShadowBox);
- border-radius: $fallback--avatarRadius;
- border-radius: var(--avatarRadius, $fallback--avatarRadius);
+ border-radius: var(--roundness);
&.-better-shadow {
box-shadow: var(--_avatarShadowInset);
@@ -64,13 +65,11 @@
}
&.-compact {
- border-radius: $fallback--avatarAltRadius;
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
+ border-radius: var(--roundness);
}
&.-placeholder {
- background-color: $fallback--fg;
- background-color: var(--fg, $fallback--fg);
+ background-color: var(--background);
}
}
@@ -79,16 +78,15 @@
height: 100%;
}
- .bot-indicator {
+ .actor-type-indicator {
position: absolute;
bottom: 0;
right: 0;
margin: -0.2em;
padding: 0.2em;
- background: rgba(127, 127, 127, 0.5);
+ background: rgb(127 127 127 / 50%);
color: #fff;
- border-radius: var(--tooltipRadius);
+ border-radius: var(--roundness);
}
-
}
</style>
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
@@ -1,11 +1,15 @@
+import { unitToSeconds } from 'src/services/date_utils/date_utils.js'
import UserAvatar from '../user_avatar/user_avatar.vue'
import RemoteFollow from '../remote_follow/remote_follow.vue'
import ProgressButton from '../progress_button/progress_button.vue'
import FollowButton from '../follow_button/follow_button.vue'
import ModerationTools from '../moderation_tools/moderation_tools.vue'
import AccountActions from '../account_actions/account_actions.vue'
+import UserNote from '../user_note/user_note.vue'
import Select from '../select/select.vue'
+import UserLink from '../user_link/user_link.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
+import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { mapGetters } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
@@ -14,7 +18,9 @@ import {
faRss,
faSearchPlus,
faExternalLinkAlt,
- faEdit
+ faEdit,
+ faTimes,
+ faExpandAlt
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -22,17 +28,30 @@ library.add(
faBell,
faSearchPlus,
faExternalLinkAlt,
- faEdit
+ faEdit,
+ faTimes,
+ faExpandAlt
)
export default {
props: [
- 'userId', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar'
+ 'userId',
+ 'switcher',
+ 'selected',
+ 'hideBio',
+ 'rounded',
+ 'bordered',
+ 'avatarAction', // default - open profile, 'zoom' - zoom, function - call function
+ 'onClose',
+ 'hasNoteEditor'
],
data () {
return {
followRequestInProgress: false,
- betterShadow: this.$store.state.interface.browserSupport.cssFilter
+ betterShadow: this.$store.state.interface.browserSupport.cssFilter,
+ showingConfirmMute: false,
+ muteExpiryAmount: 0,
+ muteExpiryUnit: 'minutes'
}
},
created () {
@@ -47,15 +66,16 @@ export default {
},
classes () {
return [{
- 'user-card-rounded-t': this.rounded === 'top', // set border-top-left-radius and border-top-right-radius
- 'user-card-rounded': this.rounded === true, // set border-radius for all sides
- 'user-card-bordered': this.bordered === true // set border for all sides
+ '-rounded-t': this.rounded === 'top', // set border-top-left-radius and border-top-right-radius
+ '-rounded': this.rounded === true, // set border-radius for all sides
+ '-bordered': this.bordered === true, // set border for all sides
+ '-popover': !!this.onClose // set popover rounding
}]
},
style () {
return {
backgroundImage: [
- `linear-gradient(to bottom, var(--profileTint), var(--profileTint))`,
+ 'linear-gradient(to bottom, var(--profileTint), var(--profileTint))',
`url(${this.user.cover_photo})`
].join(', ')
}
@@ -112,6 +132,22 @@ export default {
hideFollowersCount () {
return this.isOtherUser && this.user.hide_followers_count
},
+ showModerationMenu () {
+ const privileges = this.loggedIn.privileges
+ return this.loggedIn.role === 'admin' || privileges.includes('users_manage_activation_state') || privileges.includes('users_delete') || privileges.includes('users_manage_tags')
+ },
+ hasNote () {
+ return this.relationship.note
+ },
+ supportsNote () {
+ return 'note' in this.relationship
+ },
+ shouldConfirmMute () {
+ return this.mergedConfig.modalOnMute
+ },
+ muteExpiryUnits () {
+ return ['minutes', 'hours', 'days']
+ },
...mapGetters(['mergedConfig'])
},
components: {
@@ -122,11 +158,31 @@ export default {
ProgressButton,
FollowButton,
Select,
- RichContent
+ RichContent,
+ UserLink,
+ UserNote,
+ ConfirmModal
},
methods: {
+ showConfirmMute () {
+ this.showingConfirmMute = true
+ },
+ hideConfirmMute () {
+ this.showingConfirmMute = false
+ },
muteUser () {
- this.$store.dispatch('muteUser', this.user.id)
+ if (!this.shouldConfirmMute) {
+ this.doMuteUser()
+ } else {
+ this.showConfirmMute()
+ }
+ },
+ doMuteUser () {
+ this.$store.dispatch('muteUser', {
+ id: this.user.id,
+ expiresIn: this.shouldConfirmMute ? unitToSeconds(this.muteExpiryUnit, this.muteExpiryAmount) : 0
+ })
+ this.hideConfirmMute()
},
unmuteUser () {
this.$store.dispatch('unmuteUser', this.user.id)
@@ -169,7 +225,13 @@ export default {
this.$store.dispatch('setCurrentMedia', attachment)
},
mentionUser () {
- this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user })
+ this.$store.dispatch('openPostStatusModal', { profileMention: true, repliedUser: this.user })
+ },
+ onAvatarClickHandler (e) {
+ if (this.onAvatarClick) {
+ e.preventDefault()
+ this.onAvatarClick()
+ }
}
}
}
diff --git a/src/components/user_card/user_card.scss b/src/components/user_card/user_card.scss
@@ -1,5 +1,3 @@
-@import '../../_variables.scss';
-
.user-card {
position: relative;
z-index: 1;
@@ -11,7 +9,7 @@
}
.panel-heading {
- padding: .5em 0;
+ padding: 0.5em 0;
text-align: center;
box-shadow: none;
background: transparent;
@@ -21,29 +19,24 @@
position: relative;
}
- .panel-body {
- word-wrap: break-word;
- border-bottom-right-radius: inherit;
- border-bottom-left-radius: inherit;
- // create new stacking context
- position: relative;
- }
-
.background-image {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
- mask: linear-gradient(to top, white, transparent) bottom no-repeat,
- linear-gradient(to top, white, white);
+ mask:
+ linear-gradient(to top, white, transparent) bottom no-repeat,
+ linear-gradient(to top, white, white);
// Autoprefixer seem to ignore this one, and also syntax is different
- -webkit-mask-composite: xor;
+ mask-composite: xor;
mask-composite: exclude;
background-size: cover;
mask-size: 100% 60%;
- border-top-left-radius: calc(var(--panelRadius) - 1px);
- border-top-right-radius: calc(var(--panelRadius) - 1px);
+ border-top-left-radius: calc(var(--__roundnessTop, --panelRadius) - 1px);
+ border-top-right-radius: calc(var(--__roundnessTop, --panelRadius) - 1px);
+ border-bottom-left-radius: calc(var(--__roundnessBottom, --panelRadius) - 1px);
+ border-bottom-right-radius: calc(var(--__roundnessBottom, --panelRadius) - 1px);
background-color: var(--profileBg);
z-index: -2;
@@ -59,11 +52,6 @@
padding: 1em;
margin: 0;
- a {
- color: $fallback--link;
- color: var(--postLink, $fallback--link);
- }
-
img {
object-fit: contain;
vertical-align: middle;
@@ -72,31 +60,36 @@
}
}
- // Modifiers
+ &.-rounded-t {
+ border-top-left-radius: var(--roundness);
+ border-top-right-radius: var(--roundness);
- &-rounded-t {
- border-top-left-radius: $fallback--panelRadius;
- border-top-left-radius: var(--panelRadius, $fallback--panelRadius);
- border-top-right-radius: $fallback--panelRadius;
- border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
+ --__roundnessTop: var(--roundness);
+ --__roundnessBottom: 0;
}
- &-rounded {
- border-radius: $fallback--panelRadius;
- border-radius: var(--panelRadius, $fallback--panelRadius);
+ &.-rounded {
+ border-radius: var(--roundness);
+
+ --__roundnessTop: var(--roundness);
+ --__roundnessBottom: var(--roundness);
}
- &-bordered {
+ &.-popover {
+ border-radius: var(--roundness);
+
+ --__roundnessTop: var(--roundness);
+ --__roundnessBottom: var(--roundness);
+ }
+
+ &.-bordered {
border-width: 1px;
border-style: solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
}
}
.user-info {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
padding: 0 26px;
.container {
@@ -110,48 +103,52 @@
min-width: 0;
}
+ > a {
+ vertical-align: middle;
+ display: flex;
+ }
+
.Avatar {
--_avatarShadowBox: var(--avatarShadow);
--_avatarShadowFilter: var(--avatarShadowFilter);
--_avatarShadowInset: var(--avatarShadowInset);
- flex: 1 0 100%;
width: 56px;
height: 56px;
object-fit: cover;
}
}
- &-avatar-link {
+ &-avatar {
position: relative;
cursor: pointer;
- &-overlay {
+ &.-overlay {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
- background-color: rgba(0, 0, 0, 0.3);
+ background-color: rgb(0 0 0 / 30%);
display: flex;
justify-content: center;
align-items: center;
- border-radius: $fallback--avatarRadius;
- border-radius: var(--avatarRadius, $fallback--avatarRadius);
+ border-radius: var(--roundness);
opacity: 0;
- transition: opacity .2s ease;
+ transition: opacity 0.2s ease;
svg {
- color: #FFF;
+ color: #fff;
}
}
- &:hover &-overlay {
+ &:hover &.-overlay {
opacity: 1;
}
}
- .external-link-button, .edit-profile-button {
+ .external-link-button,
+ .edit-profile-button {
cursor: pointer;
width: 2.5em;
text-align: center;
@@ -159,39 +156,10 @@
padding: 0.5em 0;
&:not(:hover) .icon {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
+ color: var(--lightText);
}
}
- .user-summary {
- display: block;
- margin-left: 0.6em;
- text-align: left;
- text-overflow: ellipsis;
- white-space: nowrap;
- flex: 1 1 0;
- // This is so that text doesn't get overlapped by avatar's shadow if it has
- // big one
- z-index: 1;
- line-height: 2em;
-
- --emoji-size: 1.7em;
-
- .top-line,
- .bottom-line {
- display: flex;
- }
- }
-
- .user-name {
- text-overflow: ellipsis;
- overflow: hidden;
- flex: 1 1 auto;
- margin-right: 1em;
- font-size: 1.1em;
- }
-
.bottom-line {
font-weight: light;
font-size: 1.1em;
@@ -202,12 +170,11 @@
}
.user-screen-name {
+ color: var(--text);
min-width: 1px;
flex: 0 1 auto;
text-overflow: ellipsis;
overflow: hidden;
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
}
.dailyAvg {
@@ -215,21 +182,49 @@
flex: 0 0 auto;
margin-left: 1em;
font-size: 0.7em;
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
.user-role {
flex: none;
- color: $fallback--text;
- color: var(--alertNeutralText, $fallback--text);
- background-color: $fallback--fg;
- background-color: var(--alertNeutral, $fallback--fg);
}
}
+ .user-summary {
+ display: block;
+ margin-left: 0.6em;
+ text-align: left;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ flex: 1 1 0;
+ // This is so that text doesn't get overlapped by avatar's shadow if it has
+ // big one
+ z-index: 1;
+ line-height: 2em;
+
+ --emoji-size: 1.7em;
+
+ .RichContent {
+ /* stylelint-disable-next-line declaration-no-important */
+ --link: var(--text) !important;
+ }
+
+ .top-line,
+ .bottom-line {
+ display: flex;
+ }
+ }
+
+ .user-name {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ flex: 1 1 auto;
+ margin-right: 1em;
+ font-size: 1.1em;
+ }
+
.user-meta {
- margin-bottom: .15em;
+ margin-bottom: 0.15em;
display: flex;
align-items: baseline;
line-height: 22px;
@@ -238,7 +233,7 @@
.following {
flex: 1 0 auto;
margin: 0;
- margin-bottom: .25em;
+ margin-bottom: 0.25em;
text-align: left;
}
@@ -246,7 +241,7 @@
flex: 0 1 auto;
display: flex;
flex-wrap: wrap;
- margin-right: -.5em;
+ margin-right: -0.5em;
align-self: start;
.userHighlightCl {
@@ -269,19 +264,20 @@
.userHighlightText,
.userHighlightSel {
vertical-align: top;
- margin-right: .5em;
- margin-bottom: .25em;
+ margin-right: 0.5em;
+ margin-bottom: 0.25em;
}
}
}
+
.user-interactions {
position: relative;
display: flex;
flex-flow: row wrap;
- margin-right: -.75em;
+ margin-right: -0.75em;
> * {
- margin: 0 .75em .6em 0;
+ margin: 0 0.75em 0.6em 0;
white-space: nowrap;
min-width: 95px;
}
@@ -290,6 +286,10 @@
margin: 0;
}
}
+
+ .user-note {
+ margin: 0 0.75em 0.6em 0;
+ }
}
.sidebar .edit-profile-button {
@@ -298,26 +298,31 @@
.user-counts {
display: flex;
- line-height:16px;
- padding: .5em 1.5em 0em 1.5em;
+ line-height: 16px;
+ padding: 0.5em 1.5em 0;
text-align: center;
justify-content: space-between;
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
flex-wrap: wrap;
}
.user-count {
flex: 1 0 auto;
- padding: .5em 0 .5em 0;
- margin: 0 .5em;
+ padding: 0.5em 0;
+ margin: 0 0.5em;
h5 {
- font-size:1em;
+ font-size: 1em;
font-weight: bolder;
margin: 0 0 0.25em;
}
+
+ /* stylelint-disable-next-line no-descending-specificity */
a {
text-decoration: none;
}
}
+
+.mute-expiry {
+ display: flex;
+ flex-direction: row;
+}
diff --git a/src/components/user_card/user_card.style.js b/src/components/user_card/user_card.style.js
@@ -0,0 +1,42 @@
+export default {
+ name: 'UserCard',
+ selector: '.user-card',
+ notEditable: true,
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Button',
+ 'ButtonUnstyled',
+ 'Input',
+ 'RichContent',
+ 'Alert'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg',
+ opacity: 0,
+ roundness: 3,
+ shadow: [{
+ x: 1,
+ y: 1,
+ blur: 4,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.6
+ }],
+ '--profileTint': 'color | $alpha(--background 0.5)'
+ }
+ },
+ {
+ parent: {
+ component: 'UserCard'
+ },
+ component: 'RichContent',
+ directives: {
+ opacity: 0
+ }
+ }
+ ]
+}
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
@@ -8,25 +8,32 @@
:style="style"
class="background-image"
/>
- <div class="panel-heading -flexible-height">
+ <div :class="onClose ? '' : 'panel-heading -flexible-height'">
<div class="user-info">
<div class="container">
<a
- v-if="allowZoomingAvatar"
- class="user-info-avatar-link"
+ v-if="avatarAction === 'zoom'"
+ class="user-info-avatar -link"
@click="zoomAvatar"
>
<UserAvatar
:better-shadow="betterShadow"
:user="user"
/>
- <div class="user-info-avatar-link-overlay">
+ <div class="user-info-avatar -link -overlay">
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="search-plus"
/>
</div>
</a>
+ <UserAvatar
+ v-else-if="typeof avatarAction === 'function'"
+ class="user-info-avatar"
+ :better-shadow="betterShadow"
+ :user="user"
+ @click="avatarAction"
+ />
<router-link
v-else
:to="userProfileLink(user)"
@@ -38,12 +45,16 @@
</router-link>
<div class="user-summary">
<div class="top-line">
- <RichContent
- :title="user.name"
+ <router-link
+ :to="userProfileLink(user)"
class="user-name"
- :html="user.name"
- :emoji="user.emoji"
- />
+ >
+ <RichContent
+ :title="user.name"
+ :html="user.name"
+ :emoji="user.emoji"
+ />
+ </router-link>
<button
v-if="!isOtherUser && user.is_local"
class="button-unstyled edit-profile-button"
@@ -72,34 +83,58 @@
:user="user"
:relationship="relationship"
/>
- </div>
- <div class="bottom-line">
<router-link
- class="user-screen-name"
- :title="user.screen_name_ui"
+ v-if="onClose"
:to="userProfileLink(user)"
+ class="button-unstyled external-link-button"
+ @click="onClose"
>
- @{{ user.screen_name_ui }}
+ <FAIcon
+ class="icon"
+ icon="expand-alt"
+ />
</router-link>
+ <button
+ v-if="onClose"
+ class="button-unstyled external-link-button"
+ @click="onClose"
+ >
+ <FAIcon
+ class="icon"
+ icon="times"
+ />
+ </button>
+ </div>
+ <div class="bottom-line">
+ <user-link
+ class="user-screen-name"
+ :user="user"
+ />
<template v-if="!hideBio">
<span
v-if="user.deactivated"
- class="alert user-role"
+ class="alert neutral user-role"
>
{{ $t('user_card.deactivated') }}
</span>
<span
v-if="!!visibleRole"
- class="alert user-role"
+ class="alert neutral user-role"
>
{{ $t(`general.role.${visibleRole}`) }}
</span>
<span
- v-if="user.bot"
- class="alert user-role"
+ v-if="user.actor_type === 'Service'"
+ class="alert neutral user-role"
>
{{ $t('user_card.bot') }}
</span>
+ <span
+ v-if="user.actor_type === 'Group'"
+ class="alert user-role"
+ >
+ {{ $t('user_card.group') }}
+ </span>
</template>
<span v-if="user.locked">
<FAIcon
@@ -131,14 +166,14 @@
v-if="userHighlightType !== 'disabled'"
:id="'userHighlightColorTx'+user.id"
v-model="userHighlightColor"
- class="userHighlightText"
+ class="input userHighlightText"
type="text"
>
<input
v-if="userHighlightType !== 'disabled'"
:id="'userHighlightColor'+user.id"
v-model="userHighlightColor"
- class="userHighlightCl"
+ class="input userHighlightCl"
type="color"
>
{{ ' ' }}
@@ -173,7 +208,7 @@
/>
<template v-if="relationship.following">
<ProgressButton
- v-if="!relationship.subscribing"
+ v-if="!relationship.notifying"
class="btn button-default"
:click="subscribeUser"
:title="$t('user_card.subscribe')"
@@ -229,7 +264,7 @@
</button>
</div>
<ModerationTools
- v-if="loggedIn.role === "admin""
+ v-if="showModerationMenu"
:user="user"
/>
</div>
@@ -239,12 +274,15 @@
>
<RemoteFollow :user="user" />
</div>
+ <UserNote
+ v-if="loggedIn && isOtherUser && (hasNote || (hasNoteEditor && supportsNote))"
+ :user="user"
+ :relationship="relationship"
+ :editable="hasNoteEditor"
+ />
</div>
</div>
- <div
- v-if="!hideBio"
- class="panel-body"
- >
+ <div v-if="!hideBio">
<div
v-if="!mergedConfig.hideUserStats && switcher"
class="user-counts"
@@ -279,6 +317,53 @@
:handle-links="true"
/>
</div>
+ <teleport to="#modal">
+ <confirm-modal
+ v-if="showingConfirmMute"
+ :title="$t('user_card.mute_confirm_title')"
+ :confirm-text="$t('user_card.mute_confirm_accept_button')"
+ :cancel-text="$t('user_card.mute_confirm_cancel_button')"
+ @accepted="doMuteUser"
+ @cancelled="hideConfirmMute"
+ >
+ <i18n-t
+ keypath="user_card.mute_confirm"
+ tag="div"
+ >
+ <template #user>
+ <span
+ v-text="user.screen_name_ui"
+ />
+ </template>
+ </i18n-t>
+ <div
+ class="mute-expiry"
+ >
+ <label>
+ {{ $t('user_card.mute_duration_prompt') }}
+ </label>
+ <input
+ v-model="muteExpiryAmount"
+ type="number"
+ class="expiry-amount hide-number-spinner"
+ :min="0"
+ >
+ <Select
+ v-model="muteExpiryUnit"
+ unstyled="true"
+ class="expiry-unit"
+ >
+ <option
+ v-for="unit in muteExpiryUnits"
+ :key="unit"
+ :value="unit"
+ >
+ {{ $t(`time.${unit}_short`, ['']) }}
+ </option>
+ </Select>
+ </div>
+ </confirm-modal>
+ </teleport>
</div>
</template>
diff --git a/src/components/user_link/user_link.vue b/src/components/user_link/user_link.vue
@@ -0,0 +1,42 @@
+<template>
+ <div class="user-profile-link">
+ <router-link
+ :title="user.screen_name_ui"
+ :to="userProfileLink(user)"
+ >
+ <slot>
+ {{ at ? '@' : '' }}{{ user.screen_name_ui }}<UnicodeDomainIndicator
+ :user="user"
+ />
+ </slot>
+ </router-link>
+ </div>
+</template>
+
+<script>
+import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
+import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
+
+const UserLink = {
+ props: {
+ user: Object,
+ at: {
+ type: Boolean,
+ default: true
+ }
+ },
+ components: {
+ UnicodeDomainIndicator
+ },
+ methods: {
+ userProfileLink (user) {
+ return generateProfileLink(
+ user.id, user.screen_name,
+ this.$store.state.instance.restrictedNicknames
+ )
+ }
+ }
+}
+
+export default UserLink
+</script>
diff --git a/src/components/user_list_menu/user_list_menu.js b/src/components/user_list_menu/user_list_menu.js
@@ -0,0 +1,93 @@
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faChevronRight } from '@fortawesome/free-solid-svg-icons'
+import { mapState } from 'vuex'
+
+import DialogModal from '../dialog_modal/dialog_modal.vue'
+import Popover from '../popover/popover.vue'
+
+library.add(faChevronRight)
+
+const UserListMenu = {
+ props: [
+ 'user'
+ ],
+ data () {
+ return {}
+ },
+ components: {
+ DialogModal,
+ Popover
+ },
+ created () {
+ this.$store.dispatch('fetchUserInLists', this.user.id)
+ },
+ computed: {
+ ...mapState({
+ allLists: state => state.lists.allLists
+ }),
+ inListsSet () {
+ return new Set(this.user.inLists.map(x => x.id))
+ },
+ lists () {
+ if (!this.user.inLists) return []
+ return this.allLists.map(list => ({
+ ...list,
+ inList: this.inListsSet.has(list.id)
+ }))
+ }
+ },
+ methods: {
+ toggleList (listId) {
+ if (this.inListsSet.has(listId)) {
+ this.$store.dispatch('removeListAccount', { accountId: this.user.id, listId }).then((response) => {
+ if (!response.ok) { return }
+ this.$store.dispatch('fetchUserInLists', this.user.id)
+ })
+ } else {
+ this.$store.dispatch('addListAccount', { accountId: this.user.id, listId }).then((response) => {
+ if (!response.ok) { return }
+ this.$store.dispatch('fetchUserInLists', this.user.id)
+ })
+ }
+ },
+ toggleRight (right) {
+ const store = this.$store
+ if (this.user.rights[right]) {
+ store.state.api.backendInteractor.deleteRight({ user: this.user, right }).then(response => {
+ if (!response.ok) { return }
+ store.commit('updateRight', { user: this.user, right, value: false })
+ })
+ } else {
+ store.state.api.backendInteractor.addRight({ user: this.user, right }).then(response => {
+ if (!response.ok) { return }
+ store.commit('updateRight', { user: this.user, right, value: true })
+ })
+ }
+ },
+ toggleActivationStatus () {
+ this.$store.dispatch('toggleActivationStatus', { user: this.user })
+ },
+ deleteUserDialog (show) {
+ this.showDeleteUserDialog = show
+ },
+ deleteUser () {
+ const store = this.$store
+ const user = this.user
+ const { id, name } = user
+ store.state.api.backendInteractor.deleteUser({ user })
+ .then(e => {
+ 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) {
+ this.toggled = value
+ }
+ }
+}
+
+export default UserListMenu
diff --git a/src/components/user_list_menu/user_list_menu.vue b/src/components/user_list_menu/user_list_menu.vue
@@ -0,0 +1,38 @@
+<template>
+ <div class="UserListMenu">
+ <Popover
+ trigger="hover"
+ placement="left"
+ remove-padding
+ >
+ <template #content>
+ <div class="dropdown-menu">
+ <button
+ v-for="list in lists"
+ :key="list.id"
+ class="menu-item dropdown-item"
+ @click="toggleList(list.id)"
+ >
+ <span
+ class="input menu-checkbox"
+ :class="{ 'menu-checkbox-checked': list.inList }"
+ />
+ {{ list.title }}
+ </button>
+ </div>
+ </template>
+ <template #trigger>
+ <button class="menu-item dropdown-item -has-submenu">
+ {{ $t('lists.manage_lists') }}
+ <FAIcon
+ class="chevron-icon"
+ size="lg"
+ icon="chevron-right"
+ />
+ </button>
+ </template>
+ </Popover>
+ </div>
+</template>
+
+<script src="./user_list_menu.js"></script>
diff --git a/src/components/user_list_popover/user_list_popover.js b/src/components/user_list_popover/user_list_popover.js
@@ -1,5 +1,6 @@
import { defineAsyncComponent } from 'vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
+import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
@@ -15,6 +16,7 @@ const UserListPopover = {
],
components: {
RichContent,
+ UnicodeDomainIndicator,
Popover: defineAsyncComponent(() => import('../popover/popover.vue')),
UserAvatar: defineAsyncComponent(() => import('../user_avatar/user_avatar.vue'))
},
diff --git a/src/components/user_list_popover/user_list_popover.vue b/src/components/user_list_popover/user_list_popover.vue
@@ -4,10 +4,10 @@
placement="top"
:offset="{ y: 5 }"
>
- <template v-slot:trigger>
+ <template #trigger>
<slot />
</template>
- <template v-slot:content>
+ <template #content>
<div class="user-list-popover">
<template v-if="users.length">
<div
@@ -29,7 +29,7 @@
:emoji="user.emoji"
/>
<!-- eslint-enable vue/no-v-html -->
- <span class="user-list-screen-name">{{ user.screen_name_ui }}</span>
+ <span class="user-list-screen-name">{{ user.screen_name_ui }}</span><UnicodeDomainIndicator :user="user" />
</div>
</div>
</template>
@@ -45,15 +45,13 @@
</Popover>
</template>
-<script src="./user_list_popover.js" ></script>
+<script src="./user_list_popover.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.user-list-popover {
padding: 0.5em;
- --emoji-size: 16px;
+ --emoji-size: calc(var(--emojiSize, 32px) / 2);
.user-list-row {
padding: 0.25em;
diff --git a/src/components/user_note/user_note.js b/src/components/user_note/user_note.js
@@ -0,0 +1,45 @@
+const UserNote = {
+ props: {
+ user: Object,
+ relationship: Object,
+ editable: Boolean
+ },
+ data () {
+ return {
+ localNote: '',
+ editing: false,
+ frozen: false
+ }
+ },
+ computed: {
+ shouldShow () {
+ return this.relationship.note || this.editing
+ }
+ },
+ methods: {
+ startEditing () {
+ this.localNote = this.relationship.note
+ this.editing = true
+ },
+ cancelEditing () {
+ this.editing = false
+ },
+ finalizeEditing () {
+ this.frozen = true
+
+ this.$store.dispatch('editUserNote', {
+ id: this.user.id,
+ comment: this.localNote
+ })
+ .then(() => {
+ this.frozen = false
+ this.editing = false
+ })
+ .catch(() => {
+ this.frozen = false
+ })
+ }
+ }
+}
+
+export default UserNote
diff --git a/src/components/user_note/user_note.vue b/src/components/user_note/user_note.vue
@@ -0,0 +1,86 @@
+<template>
+ <div
+ class="user-note"
+ >
+ <div class="heading">
+ <span>{{ $t('user_card.note') }}</span>
+ <div class="buttons">
+ <button
+ v-show="!editing && editable"
+ class="button-default btn"
+ @click="startEditing"
+ >
+ {{ $t('user_card.edit_note') }}
+ </button>
+ <button
+ v-show="editing"
+ class="button-default btn"
+ :disabled="frozen"
+ @click="finalizeEditing"
+ >
+ {{ $t('user_card.edit_note_apply') }}
+ </button>
+ <button
+ v-show="editing"
+ class="button-default btn"
+ :disabled="frozen"
+ @click="cancelEditing"
+ >
+ {{ $t('user_card.edit_note_cancel') }}
+ </button>
+ </div>
+ </div>
+ <textarea
+ v-show="editing"
+ v-model="localNote"
+ class="input note-text"
+ />
+ <span
+ v-show="!editing"
+ class="note-text"
+ :class="{ '-blank': !relationship.note }"
+ >
+ {{ relationship.note || $t('user_card.note_blank') }}
+ </span>
+ </div>
+</template>
+
+<script src="./user_note.js"></script>
+
+<style lang="scss">
+.user-note {
+ display: flex;
+ flex-direction: column;
+
+ .heading {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 0.75em;
+
+ .btn {
+ min-width: 95px;
+ }
+
+ .buttons {
+ display: flex;
+ flex-direction: row;
+ justify-content: right;
+
+ .btn {
+ margin-left: 0.5em;
+ }
+ }
+ }
+
+ .note-text {
+ align-self: stretch;
+ }
+
+ .note-text.-blank {
+ font-style: italic;
+ color: var(--textFaint);
+ }
+}
+</style>
diff --git a/src/components/user_panel/user_panel.vue b/src/components/user_panel/user_panel.vue
@@ -1,5 +1,5 @@
<template>
- <div class="user-panel">
+ <aside class="user-panel">
<div
v-if="signedIn"
key="user-panel-signed"
@@ -16,14 +16,21 @@
v-else
key="user-panel"
/>
- </div>
+ </aside>
</template>
<script src="./user_panel.js"></script>
<style lang="scss">
-.user-panel .signed-in {
- overflow: visible;
- z-index: 10;
+.user-panel {
+ .panel {
+ background: var(--background);
+ backdrop-filter: var(--backdrop-filter);
+ }
+
+ .signed-in {
+ overflow: visible;
+ z-index: 10;
+ }
}
</style>
diff --git a/src/components/user_popover/user_popover.js b/src/components/user_popover/user_popover.js
@@ -0,0 +1,23 @@
+import UserCard from '../user_card/user_card.vue'
+import { defineAsyncComponent } from 'vue'
+
+const UserPopover = {
+ name: 'UserPopover',
+ props: [
+ 'userId', 'overlayCenters', 'disabled', 'overlayCentersSelector'
+ ],
+ components: {
+ UserCard,
+ Popover: defineAsyncComponent(() => import('../popover/popover.vue'))
+ },
+ computed: {
+ userPopoverAvatarAction () {
+ return this.$store.getters.mergedConfig.userPopoverAvatarAction
+ },
+ userPopoverOverlay () {
+ return this.$store.getters.mergedConfig.userPopoverOverlay
+ }
+ }
+}
+
+export default UserPopover
diff --git a/src/components/user_popover/user_popover.vue b/src/components/user_popover/user_popover.vue
@@ -0,0 +1,33 @@
+<template>
+ <Popover
+ trigger="click"
+ popover-class="popover-default user-popover"
+ :overlay-centers-selector="overlayCentersSelector || '.user-info .Avatar'"
+ :overlay-centers="overlayCenters && userPopoverOverlay"
+ :disabled="disabled"
+ >
+ <template #trigger>
+ <slot />
+ </template>
+ <template #content="{close}">
+ <UserCard
+ class="user-popover"
+ :user-id="userId"
+ :hide-bio="true"
+ :avatar-action="userPopoverAvatarAction == 'close' ? close : userPopoverAvatarAction"
+ :on-close="close"
+ />
+ </template>
+ </Popover>
+</template>
+
+<script src="./user_popover.js"></script>
+
+<style lang="scss">
+/* popover styles load on-demand, so we need to override */
+/* stylelint-disable block-no-empty */
+.user-popover.popover {
+}
+/* stylelint-enable block-no-empty */
+
+</style>
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
@@ -7,13 +7,16 @@ import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import List from '../list/list.vue'
import withLoadMore from '../../hocs/with_load_more/with_load_more'
+import localeService from 'src/services/locale/locale.service.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
- faCircleNotch
+ faCircleNotch,
+ faBirthdayCake
} from '@fortawesome/free-solid-svg-icons'
library.add(
- faCircleNotch
+ faCircleNotch,
+ faBirthdayCake
)
const FollowerList = withLoadMore({
@@ -45,7 +48,7 @@ const UserProfile = {
},
created () {
const routeParams = this.$route.params
- this.load(routeParams.name || routeParams.id)
+ this.load({ name: routeParams.name, id: routeParams.id })
this.tab = get(this.$route, 'query.tab', defaultTabKey)
},
unmounted () {
@@ -76,6 +79,13 @@ const UserProfile = {
},
followersTabVisible () {
return this.isUs || !this.user.hide_followers
+ },
+ favoritesTabVisible () {
+ return this.isUs || !this.user.hide_favorites
+ },
+ formattedBirthday () {
+ const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)
+ return this.user.birthday && new Date(Date.parse(this.user.birthday)).toLocaleDateString(browserLocale, { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' })
}
},
methods: {
@@ -96,6 +106,8 @@ const UserProfile = {
startFetchingTimeline('user', userId)
startFetchingTimeline('media', userId)
if (this.isUs) {
+ startFetchingTimeline('favorites')
+ } else if (!this.user.hide_favorites) {
startFetchingTimeline('favorites', userId)
}
// Fetch all pinned statuses immediately
@@ -106,12 +118,17 @@ const UserProfile = {
this.userId = null
this.error = false
+ const maybeId = userNameOrId.id
+ const maybeName = userNameOrId.name
+
// Check if user data is already loaded in store
- const user = this.$store.getters.findUser(userNameOrId)
+ const user = maybeId ? this.$store.getters.findUser(maybeId) : this.$store.getters.findUserByName(maybeName)
if (user) {
loadById(user.id)
} else {
- this.$store.dispatch('fetchUser', userNameOrId)
+ (maybeId
+ ? this.$store.dispatch('fetchUser', maybeId)
+ : this.$store.dispatch('fetchUserByName', maybeName))
.then(({ id }) => loadById(id))
.catch((reason) => {
const errorMessage = get(reason, 'error.error')
@@ -150,12 +167,12 @@ const UserProfile = {
watch: {
'$route.params.id': function (newVal) {
if (newVal) {
- this.switchUser(newVal)
+ this.switchUser({ id: newVal })
}
},
'$route.params.name': function (newVal) {
if (newVal) {
- this.switchUser(newVal)
+ this.switchUser({ name: newVal })
}
},
'$route.query': function (newVal) {
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
@@ -4,41 +4,54 @@
v-if="user"
class="user-profile panel panel-default"
>
- <UserCard
- :user-id="userId"
- :switcher="true"
- :selected="timeline.viewing"
- :allow-zooming-avatar="true"
- rounded="top"
- />
- <div
- v-if="user.fields_html && user.fields_html.length > 0"
- class="user-profile-fields"
- >
- <dl
- v-for="(field, index) in user.fields_html"
- :key="index"
- class="user-profile-field"
+ <div class="panel-body">
+ <UserCard
+ :user-id="userId"
+ :switcher="true"
+ :selected="timeline.viewing"
+ avatar-action="zoom"
+ rounded="top"
+ :has-note-editor="true"
+ />
+ <span
+ v-if="!!user.birthday"
+ class="user-birthday"
>
- <dt
- :title="user.fields_text[index].name"
- class="user-profile-field-name"
- >
- <RichContent
- :html="field.name"
- :emoji="user.emoji"
- />
- </dt>
- <dd
- :title="user.fields_text[index].value"
- class="user-profile-field-value"
+ <FAIcon
+ class="fa-old-padding"
+ icon="birthday-cake"
+ />
+ {{ $t('user_card.birthday', { birthday: formattedBirthday }) }}
+ </span>
+ <div
+ v-if="user.fields_html && user.fields_html.length > 0"
+ class="user-profile-fields"
+ >
+ <dl
+ v-for="(field, index) in user.fields_html"
+ :key="index"
+ class="user-profile-field"
>
- <RichContent
- :html="field.value"
- :emoji="user.emoji"
- />
- </dd>
- </dl>
+ <dt
+ :title="user.fields_text[index].name"
+ class="user-profile-field-name"
+ >
+ <RichContent
+ :html="field.name"
+ :emoji="user.emoji"
+ />
+ </dt>
+ <dd
+ :title="user.fields_text[index].value"
+ class="user-profile-field-value"
+ >
+ <RichContent
+ :html="field.value"
+ :emoji="user.emoji"
+ />
+ </dd>
+ </dl>
+ </div>
</div>
<tab-switcher
:active-tab="tab"
@@ -56,16 +69,20 @@
:user-id="userId"
:pinned-status-ids="user.pinnedStatusIds"
:in-profile="true"
- :footerSlipgate="footerRef"
+ :footer-slipgate="footerRef"
/>
<div
v-if="followsTabVisible"
key="followees"
+ class="panel-body"
:label="$t('user_card.followees')"
:disabled="!user.friends_count"
>
- <FriendList :user-id="userId">
- <template v-slot:item="{item}">
+ <FriendList
+ :user-id="userId"
+ :non-interactive="true"
+ >
+ <template #item="{item}">
<FollowCard :user="item" />
</template>
</FriendList>
@@ -73,11 +90,15 @@
<div
v-if="followersTabVisible"
key="followers"
+ class="panel-body"
:label="$t('user_card.followers')"
:disabled="!user.followers_count"
>
- <FollowerList :user-id="userId">
- <template v-slot:item="{item}">
+ <FollowerList
+ :user-id="userId"
+ :non-interactive="true"
+ >
+ <template #item="{item}">
<FollowCard
:user="item"
:no-follows-you="isUs"
@@ -95,10 +116,10 @@
:timeline="media"
:user-id="userId"
:in-profile="true"
- :footerSlipgate="footerRef"
+ :footer-slipgate="footerRef"
/>
<Timeline
- v-if="isUs"
+ v-if="favoritesTabVisible"
key="favorites"
:label="$t('user_card.favorites')"
:disabled="!favorites.visibleStatuses.length"
@@ -106,22 +127,26 @@
:title="$t('user_card.favorites')"
timeline-name="favorites"
:timeline="favorites"
+ :user-id="isUs ? undefined : userId"
:in-profile="true"
- :footerSlipgate="footerRef"
+ :footer-slipgate="footerRef"
/>
</tab-switcher>
- <div class="panel-footer" :ref="setFooterRef"></div>
+ <div
+ :ref="setFooterRef"
+ class="panel-footer"
+ />
</div>
<div
v-else
class="panel user-profile-placeholder"
>
<div class="panel-heading">
- <div class="title">
+ <h1 class="title">
{{ $t('settings.profile_tab') }}
- </div>
+ </h1>
</div>
- <div class="panel-body">
+ <div>
<span v-if="error">{{ error }}</span>
<FAIcon
v-else
@@ -136,14 +161,16 @@
<script src="./user_profile.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.user-profile {
flex: 2;
flex-basis: 500px;
// No sticky header on user profile
- --currentPanelStack: 1;
+ --currentPanelStack: 0;
+
+ .user-birthday {
+ margin: 0 0.75em 0.5em;
+ }
.user-profile-fields {
margin: 0 0.5em;
@@ -163,9 +190,8 @@
.user-profile-field {
display: flex;
margin: 0.25em;
- border: 1px solid var(--border, $fallback--border);
- border-radius: $fallback--inputRadius;
- border-radius: var(--inputRadius, $fallback--inputRadius);
+ border: 1px solid var(--border);
+ border-radius: var(--roundness);
.user-profile-field-name {
flex: 0 1 30%;
@@ -173,7 +199,7 @@
text-align: right;
color: var(--lightText);
min-width: 120px;
- border-right: 1px solid var(--border, $fallback--border);
+ border-right: 1px solid var(--border);
}
.user-profile-field-value {
@@ -182,7 +208,8 @@
margin: 0 0 0 0.25em;
}
- .user-profile-field-name, .user-profile-field-value {
+ .user-profile-field-name,
+ .user-profile-field-value {
line-height: 1.3;
text-overflow: ellipsis;
white-space: nowrap;
@@ -200,6 +227,7 @@
padding: 2em;
}
}
+
.user-profile-placeholder {
.panel-body {
display: flex;
@@ -208,4 +236,5 @@
padding: 7em;
}
}
+
</style>
diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js
@@ -1,15 +1,16 @@
-
import Status from '../status/status.vue'
import List from '../list/list.vue'
import Checkbox from '../checkbox/checkbox.vue'
import Modal from '../modal/modal.vue'
+import UserLink from '../user_link/user_link.vue'
const UserReportingModal = {
components: {
Status,
List,
Checkbox,
- Modal
+ Modal,
+ UserLink
},
data () {
return {
@@ -21,14 +22,17 @@ const UserReportingModal = {
}
},
computed: {
+ reportModal () {
+ return this.$store.state.reports.reportModal
+ },
isLoggedIn () {
return !!this.$store.state.users.currentUser
},
isOpen () {
- return this.isLoggedIn && this.$store.state.reports.modalActivated
+ return this.isLoggedIn && this.reportModal.activated
},
userId () {
- return this.$store.state.reports.userId
+ return this.reportModal.userId
},
user () {
return this.$store.getters.findUser(this.userId)
@@ -37,10 +41,10 @@ const UserReportingModal = {
return !this.user.is_local && this.user.screen_name.substr(this.user.screen_name.indexOf('@') + 1)
},
statuses () {
- return this.$store.state.reports.statuses
+ return this.reportModal.statuses
},
preTickedIds () {
- return this.$store.state.reports.preTickedIds
+ return this.reportModal.preTickedIds
}
},
watch: {
diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue
@@ -5,9 +5,13 @@
>
<div class="user-reporting-panel panel">
<div class="panel-heading">
- <div class="title">
- {{ $t('user_reporting.title', [user.screen_name_ui]) }}
- </div>
+ <i18n-t
+ tag="h1"
+ keypath="user_reporting.title"
+ class="title"
+ >
+ <UserLink :user="user" />
+ </i18n-t>
</div>
<div class="panel-body">
<div class="user-reporting-panel-left">
@@ -15,7 +19,7 @@
<p>{{ $t('user_reporting.add_comment_description') }}</p>
<textarea
v-model="comment"
- class="form-control"
+ class="input form-control"
:placeholder="$t('user_reporting.additional_comments')"
rows="1"
@input="resize"
@@ -45,7 +49,7 @@
</div>
<div class="user-reporting-panel-right">
<List :items="statuses">
- <template v-slot:item="{item}">
+ <template #item="{item}">
<div class="status-fadein user-reporting-panel-sitem">
<Status
:in-conversation="false"
@@ -68,8 +72,6 @@
<script src="./user_reporting_modal.js"></script>
<style lang="scss">
-@import '../../_variables.scss';
-
.user-reporting-panel {
width: 90vw;
max-width: 700px;
@@ -80,8 +82,7 @@
display: flex;
flex-direction: column-reverse;
border-top: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
overflow: hidden;
}
@@ -117,7 +118,7 @@
}
.alert {
- margin: 1em 0 0 0;
+ margin: 1em 0 0;
line-height: 1.3em;
}
}
@@ -151,8 +152,7 @@
width: 50%;
max-width: 320px;
border-right: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
padding: 1.1em;
> div {
diff --git a/src/components/video_attachment/video_attachment.vue b/src/components/video_attachment/video_attachment.vue
@@ -2,7 +2,7 @@
<video
class="video"
preload="metadata"
- :src="attachment.url"
+ :src="attachment.url + '#t=0.00000000000001'"
:loop="loopVideo"
:controls="controls"
:alt="attachment.description"
diff --git a/src/components/who_to_follow/who_to_follow.js b/src/components/who_to_follow/who_to_follow.js
@@ -28,7 +28,7 @@ const WhoToFollow = {
getWhoToFollow () {
const credentials = this.$store.state.users.currentUser.credentials
if (credentials) {
- apiService.suggestions({ credentials: credentials })
+ apiService.suggestions({ credentials })
.then((reply) => {
this.showWhoToFollow(reply)
})
diff --git a/src/components/who_to_follow/who_to_follow.vue b/src/components/who_to_follow/who_to_follow.vue
@@ -1,7 +1,9 @@
<template>
<div class="panel panel-default">
<div class="panel-heading">
- {{ $t('who_to_follow.who_to_follow') }}
+ <h1 class="title">
+ {{ $t('who_to_follow.who_to_follow') }}
+ </h1>
</div>
<div class="panel-body">
<FollowCard
@@ -15,6 +17,3 @@
</template>
<script src="./who_to_follow.js"></script>
-
-<style lang="scss">
-</style>
diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js
@@ -6,9 +6,9 @@ function showWhoToFollow (panel, reply) {
const shuffled = shuffle(reply)
panel.usersToFollow.forEach((toFollow, index) => {
- let user = shuffled[index]
- let img = user.avatar || this.$store.state.instance.defaultAvatar
- let name = user.acct
+ const user = shuffled[index]
+ const img = user.avatar || this.$store.state.instance.defaultAvatar
+ const name = user.acct
toFollow.img = img
toFollow.name = name
@@ -24,12 +24,12 @@ function showWhoToFollow (panel, reply) {
}
function getWhoToFollow (panel) {
- var credentials = panel.$store.state.users.currentUser.credentials
+ const credentials = panel.$store.state.users.currentUser.credentials
if (credentials) {
panel.usersToFollow.forEach(toFollow => {
toFollow.name = 'Loading...'
})
- apiService.suggestions({ credentials: credentials })
+ apiService.suggestions({ credentials })
.then((reply) => {
showWhoToFollow(panel, reply)
})
diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.vue b/src/components/who_to_follow_panel/who_to_follow_panel.vue
@@ -2,9 +2,9 @@
<div class="who-to-follow-panel">
<div class="panel panel-default base01-background">
<div class="panel-heading timeline-heading base02-background base04">
- <div class="title">
+ <h1 class="title">
{{ $t('who_to_follow.who_to_follow') }}
- </div>
+ </h1>
</div>
<div class="who-to-follow">
<p
@@ -27,30 +27,34 @@
</div>
</template>
-<script src="./who_to_follow_panel.js" ></script>
+<script src="./who_to_follow_panel.js"></script>
<style lang="scss">
.who-to-follow * {
vertical-align: middle;
}
+
.who-to-follow img {
width: 32px;
height: 32px;
}
+
.who-to-follow {
- padding: 0em 1em;
- margin: 0px;
+ padding: 0 1em;
+ margin: 0;
}
+
.who-to-follow-items {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
- padding: 0px;
- margin: 1em 0em;
+ padding: 0;
+ margin: 1em 0;
}
+
.who-to-follow-more {
- padding: 0px;
- margin: 1em 0em;
+ padding: 0;
+ margin: 1em 0;
text-align: center;
}
</style>
diff --git a/src/hocs/with_load_more/with_load_more.jsx b/src/hocs/with_load_more/with_load_more.jsx
@@ -98,7 +98,7 @@ const withLoadMore = ({
</button>
}
{!this.error && this.loading && <FAIcon spin icon="circle-notch"/>}
- {!this.error && !this.loading && !this.bottomedOut && <a onClick={this.fetchEntries}>{this.$t('general.more')}</a>}
+ {!this.error && !this.loading && !this.bottomedOut && <a onClick={this.fetchEntries} role="button" tabindex="0">{this.$t('general.more')}</a>}
</div>
</div>
)
diff --git a/src/hocs/with_load_more/with_load_more.scss b/src/hocs/with_load_more/with_load_more.scss
@@ -1,13 +1,9 @@
-
-@import '../../_variables.scss';
-
.with-load-more {
&-footer {
padding: 10px;
text-align: center;
border-top: 1px solid;
- border-top-color: $fallback--border;
- border-top-color: var(--border, $fallback--border);
+ border-top-color: var(--border);
.error {
font-size: 1rem;
diff --git a/src/hocs/with_subscription/with_subscription.scss b/src/hocs/with_subscription/with_subscription.scss
@@ -7,4 +7,4 @@
font-size: 1rem;
}
}
-}
-\ No newline at end of file
+}
diff --git a/src/i18n/ar.json b/src/i18n/ar.json
@@ -9,7 +9,8 @@
"scope_options": "",
"text_limit": "الحد الأقصى للنص",
"title": "الميّزات",
- "who_to_follow": "للمتابعة"
+ "who_to_follow": "للمتابعة",
+ "upload_limit": "حد الرفع"
},
"finder": {
"error_fetching_user": "خطأ أثناء جلب صفحة المستخدم",
@@ -17,7 +18,37 @@
},
"general": {
"apply": "تطبيق",
- "submit": "إرسال"
+ "submit": "إرسال",
+ "error_retry": "حاول مجددًا",
+ "retry": "حاول مجدداً",
+ "optional": "اختياري",
+ "show_more": "اعرض المزيد",
+ "show_less": "اعرض أقل",
+ "cancel": "ألغ",
+ "disable": "عطّل",
+ "enable": "فعّل",
+ "confirm": "تأكيد",
+ "close": "أغلق",
+ "role": {
+ "admin": "مدير",
+ "moderator": "مشرف"
+ },
+ "generic_error_message": "حدث خطأ: {0}",
+ "never_show_again": "لا تظهره مجددًا",
+ "yes": "نعم",
+ "no": "لا",
+ "unpin": "ألغ تثبيت العنصر",
+ "undo": "تراجع",
+ "more": "المزيد",
+ "loading": "يحمل…",
+ "generic_error": "حدث خطأ",
+ "scope_in_timeline": {
+ "private": "المتابِعون فقط",
+ "public": "علني",
+ "unlisted": "غير مدرج"
+ },
+ "scroll_to_top": "مرر لأعلى",
+ "pin": "ثبت العنصر"
},
"login": {
"login": "تسجيل الدخول",
@@ -25,7 +56,21 @@
"password": "الكلمة السرية",
"placeholder": "مثال lain",
"register": "انشاء حساب",
- "username": "إسم المستخدم"
+ "username": "إسم المستخدم",
+ "logout_confirm_title": "تأكيد الخروج",
+ "logout_confirm": "أتريد الخروج؟",
+ "logout_confirm_accept_button": "خروج",
+ "logout_confirm_cancel_button": "لا تخرج",
+ "hint": "لِج للانضمام للمناقشة",
+ "authentication_code": "رمز الاستيثاق",
+ "enter_recovery_code": "أدخل رمز التأكيد",
+ "enter_two_factor_code": "أدخل رمز الاستيثاق بعاملين",
+ "recovery_code": "رمز الاستعادة",
+ "heading": {
+ "totp": "الاستيثاق بعاملين",
+ "recovery": "الاستيثاق بعاملين"
+ },
+ "description": "لج باستخدام OAuth"
},
"nav": {
"chat": "الدردشة المحلية",
@@ -33,42 +78,108 @@
"mentions": "الإشارات",
"public_tl": "الخيط الزمني العام",
"timeline": "الخيط الزمني",
- "twkn": "كافة الشبكة المعروفة"
+ "twkn": "كافة الشبكة المعروفة",
+ "search_close": "أغلق شربط البحث",
+ "back": "للخلف",
+ "administration": "الإدارة",
+ "preferences": "التفضيلات",
+ "chats": "المحادثات",
+ "lists": "القوائم",
+ "edit_nav_mobile": "خصص شريط التنقل",
+ "edit_pinned": "حرر العناصر المثبتة",
+ "mobile_notifications_close": "أغلق الاشعارات",
+ "announcements": "إعلانات",
+ "home_timeline": "الخط الزمني الرئيس",
+ "search": "بحث",
+ "who_to_follow": "للمتابعة",
+ "dms": "رسالة شخصية",
+ "edit_finish": "تم التحرير",
+ "timelines": "الخيوط الزمنية",
+ "mobile_notifications": "افتح الإشعارات (تتواجد اشعارات غير مقروءة)",
+ "about": "حول",
+ "user_search": "بحث عن مستخدم"
},
"notifications": {
"broken_favorite": "منشور مجهول، جارٍ البحث عنه…",
"favorited_you": "أعجِب بمنشورك",
"followed_you": "يُتابعك",
"load_older": "تحميل الإشعارات الأقدم",
- "notifications": "الإخطارات",
+ "notifications": "الاشعارات",
"read": "مقروء!",
- "repeated_you": "شارَك منشورك"
+ "repeated_you": "شارَك منشورك",
+ "error": "خطأ أثناء جلب الاشعارات: {0}",
+ "follow_request": "يريد متابعتك",
+ "poll_ended": "انتهى الاستطلاع",
+ "no_more_notifications": "لا مزيد من الإشعارات",
+ "reacted_with": "تفاعل بـ{0}",
+ "submitted_report": "أرسل بلاغًا",
+ "migrated_to": "انتقلَ إلى"
},
"post_status": {
- "account_not_locked_warning": "",
+ "account_not_locked_warning": "حسابك ليس {0}. يمكن للجميع مشاهدة مشاركاتك المحصورة على المتابِعين.",
"account_not_locked_warning_link": "مقفل",
"attachments_sensitive": "اعتبر المرفقات كلها كمحتوى حساس",
"content_type": {
- "text/plain": "نص صافٍ"
+ "text/plain": "نص صِرف",
+ "text/html": "HTML",
+ "text/markdown": "ماركداون"
},
"content_warning": "الموضوع (اختياري)",
"default": "وصلت للتوّ إلى لوس أنجلس.",
"direct_warning": "",
"posting": "النشر",
"scope": {
- "direct": "",
- "private": "",
+ "direct": "مباشر - شارك مع المستخدمين المذكورين فقط",
+ "private": "للمتابِعين فقط - شارك حصرًا مع المتابِعين",
"public": "علني - يُنشر على الخيوط الزمنية العمومية",
"unlisted": "غير مُدرَج - لا يُنشَر على الخيوط الزمنية العمومية"
- }
+ },
+ "media_description": "وصف الوسائط",
+ "direct_warning_to_all": "سيكون عذا المنشور مرئيًا لكل المستخدمين المذكورين.",
+ "post": "انشر",
+ "preview": "معاينة",
+ "preview_empty": "فارغ",
+ "scope_notice": {
+ "public": "سيكون هذا المنشور مرئيًا للجميع",
+ "private": "سيكون هذا المنشور مرئيا لمتابِعيك فقط",
+ "unlisted": "لن تظهر هته المشاركة في الخط الزمني العلني والشبكات العلنية"
+ },
+ "direct_warning_to_first_only": "سيكون عذا المنشور مرئيًا للمستخدمين المذكورين في أول الرسالة.",
+ "edit_unsupported_warning": "بليروما لا يدعم تعديل الذكر والاستطلاع.",
+ "empty_status_error": "يتعذر نشر منشور فارغ دون ملفات",
+ "edit_status": "حرر الحالة",
+ "new_status": "انشر حالة جديدة",
+ "content_type_selection": "نسق المشاركة",
+ "scope_notice_dismiss": "أغلق هذا التنبيه",
+ "media_description_error": "فشل تحديث الوسائط، حاول مجددًا"
},
"registration": {
"bio": "السيرة الذاتية",
"email": "عنوان البريد الإلكتروني",
- "fullname": "الإسم المعروض",
+ "fullname": "الاسم العلني",
"password_confirm": "تأكيد الكلمة السرية",
"registration": "التسجيل",
- "token": "رمز الدعوة"
+ "token": "رمز الدعوة",
+ "bio_optional": "سيرة (اختيارية)",
+ "email_optional": "بيرد إلكتروني (اختياري)",
+ "username_placeholder": "مثل lain",
+ "reason": "سبب التسجيل",
+ "register": "سجل",
+ "validations": {
+ "username_required": "لايمكن تركه فارغًا",
+ "email_required": "لايمكن تركه فارغًا",
+ "password_required": "لايمكن تركه فارغًا",
+ "password_confirmation_required": "لايمكن تركه فارغًا",
+ "fullname_required": "لايمكن تركه فارغًا",
+ "password_confirmation_match": "يلزم أن يطابق كلمة السر",
+ "birthday_required": "لايمكن تركه فارغًا",
+ "birthday_min_age": "يلزم أن يكون في {date} أو قبله"
+ },
+ "fullname_placeholder": "مثل Lain Iwakura",
+ "reason_placeholder": "قبول التسجيل في هذا المثيل يستلزم موافقة المدير\nلهذا يجب عليك إعلامه بسبب التسجيل.",
+ "birthday_optional": "تاريخ الميلاد (اختياري):",
+ "email_language": "بأي لغة تريد استلام رسائل البريد الإلكتروني؟",
+ "birthday": "تاريخ الميلاد:"
},
"settings": {
"attachmentRadius": "المُرفَقات",
@@ -83,9 +194,9 @@
"cGreen": "أخضر (إعادة النشر)",
"cOrange": "برتقالي (مفضلة)",
"cRed": "أحمر (إلغاء)",
- "change_password": "تغيير كلمة السر",
- "change_password_error": "وقع هناك خلل أثناء تعديل كلمتك السرية.",
- "changed_password": "تم تغيير كلمة المرور بنجاح!",
+ "change_password": "غيّر كلمة السر",
+ "change_password_error": "حدث خلل أثناء تعديل كلمتك السرية.",
+ "changed_password": "نجح تغيير كلمة السر!",
"collapse_subject": "",
"confirm_new_password": "تأكيد كلمة السر الجديدة",
"current_avatar": "صورتك الرمزية الحالية",
@@ -94,111 +205,813 @@
"data_import_export_tab": "تصدير واستيراد البيانات",
"default_vis": "أسلوب العرض الافتراضي",
"delete_account": "حذف الحساب",
- "delete_account_description": "حذف حسابك و كافة منشوراتك نهائيًا.",
- "delete_account_error": "",
+ "delete_account_description": "حذف حسابك و كافة بياناتك نهائيًا.",
+ "delete_account_error": "حدثة مشكلة اثناء حذف حسابك، إذا استمرت تواصل مع مدير المثيل.",
"delete_account_instructions": "يُرجى إدخال كلمتك السرية أدناه لتأكيد عملية حذف الحساب.",
"export_theme": "حفظ النموذج",
- "filtering": "التصفية",
+ "filtering": "الترشيح",
"filtering_explanation": "سيتم إخفاء كافة المنشورات التي تحتوي على هذه الكلمات، كلمة واحدة في كل سطر",
"follow_export": "تصدير الاشتراكات",
"follow_export_button": "تصدير الاشتراكات كملف csv",
"follow_export_processing": "التصدير جارٍ، سوف يُطلَب منك تنزيل ملفك بعد حين",
"follow_import": "استيراد الاشتراكات",
"follow_import_error": "خطأ أثناء استيراد المتابِعين",
- "follows_imported": "",
+ "follows_imported": "أُستورد المتابِعون! معالجتهم ستستغرق بعض الوقت.",
"foreground": "الأمامية",
"general": "الإعدادات العامة",
- "hide_attachments_in_convo": "إخفاء المرفقات على المحادثات",
- "hide_attachments_in_tl": "إخفاء المرفقات على الخيط الزمني",
- "hide_post_stats": "",
- "hide_user_stats": "",
- "import_followers_from_a_csv_file": "",
+ "hide_attachments_in_convo": "اخف المرفقات من المحادثات",
+ "hide_attachments_in_tl": "اخف المرفقات من الخيط الزمني",
+ "hide_post_stats": "اخف احصائيات المنشور (مثل عدد التفضيلات)",
+ "hide_user_stats": "اخف احصائيات المستخدم (مثل عدد المتابِعين)",
+ "import_followers_from_a_csv_file": "استورد المتابِعين من ملف csv",
"import_theme": "تحميل نموذج",
"inputRadius": "",
- "instance_default": "",
+ "instance_default": "(الافتراضي: {value})",
"interfaceLanguage": "لغة الواجهة",
- "invalid_theme_imported": "",
+ "invalid_theme_imported": "الملف المختار ليس سمة تدعمها بليروما.لن تطرأ تغييرات على سمتك.",
"limited_availability": "غير متوفر على متصفحك",
"links": "الروابط",
"lock_account_description": "",
- "loop_video": "",
- "loop_video_silent_only": "",
+ "loop_video": "كرر تشغيل الفيديوهات",
+ "loop_video_silent_only": "كرر فيديوهات بدون صوت (مثل gif في ماستودون)",
"name": "الاسم",
"name_bio": "الاسم والسيرة الذاتية",
"new_password": "كلمة السر الجديدة",
"no_rich_text_description": "",
"notification_visibility": "نوع الإشعارات التي تريد عرضها",
"notification_visibility_follows": "يتابع",
- "notification_visibility_likes": "الإعجابات",
- "notification_visibility_mentions": "الإشارات",
- "notification_visibility_repeats": "",
+ "notification_visibility_likes": "المفضلة",
+ "notification_visibility_mentions": "ذِكر",
+ "notification_visibility_repeats": "مشاركات",
"nsfw_clickthrough": "",
"oauth_tokens": "رموز OAuth",
"token": "رمز",
"refresh_token": "رمز التحديث",
"valid_until": "صالح حتى",
"revoke_token": "سحب",
- "panelRadius": "",
+ "panelRadius": "لوحات",
"pause_on_unfocused": "",
"presets": "النماذج",
- "profile_background": "خلفية الصفحة الشخصية",
+ "profile_background": "خلفية الملف التعريفي",
"profile_banner": "رأسية الصفحة الشخصية",
- "profile_tab": "الملف الشخصي",
+ "profile_tab": "الملف التعريفي",
"radii_help": "",
- "replies_in_timeline": "الردود على الخيط الزمني",
- "reply_visibility_all": "عرض كافة الردود",
- "reply_visibility_following": "",
- "reply_visibility_self": "",
+ "replies_in_timeline": "المشاركات في الخيط الزمني",
+ "reply_visibility_all": "أظهر كل المشاركات",
+ "reply_visibility_following": "أظهر الردود الموجهة إلي أو لمتابَعي فقط",
+ "reply_visibility_self": "أظهر الردود الموجهة إلي فقط",
"saving_err": "خطأ أثناء حفظ الإعدادات",
- "saving_ok": "تم حفظ الإعدادات",
+ "saving_ok": "حُفظت الإعدادات",
"security_tab": "الأمان",
"set_new_avatar": "اختيار صورة رمزية جديدة",
"set_new_profile_background": "اختيار خلفية جديدة للملف الشخصي",
"set_new_profile_banner": "اختيار رأسية جديدة للصفحة الشخصية",
"settings": "الإعدادات",
- "stop_gifs": "",
- "streaming": "",
- "text": "النص",
- "theme": "المظهر",
+ "stop_gifs": "إيقاف الصور المتحركة مالم يُمرر فوقها",
+ "streaming": "إظهار المنشورات الجديدة عند التمرير لأعلى",
+ "text": "نص",
+ "theme": "السمة",
"theme_help": "",
"tooltipRadius": "",
"user_settings": "إعدادات المستخدم",
"values": {
"false": "لا",
"true": "نعم"
- }
+ },
+ "emoji_reactions_scale": "معامل تحجيم التفاعلات",
+ "app_name": "اسم تطبيق",
+ "security": "الأمن",
+ "enter_current_password_to_confirm": "أدخل كلمة السر الحالية لتيقن من هويتك",
+ "mfa": {
+ "title": "الاستيثاق بعاملين",
+ "generate_new_recovery_codes": "ولّد رموز استعادة جديدة",
+ "warning_of_generate_new_codes": "عند توليد رموز استعادة جديدة ستزال القديمة.",
+ "recovery_codes": "رموز الاستعادة.",
+ "recovery_codes_warning": "خزن هذه الرموز في مكان آمن. إذا فقدت هذه الرموز وتعذر عليك الوصول إلى تطبيق الاستيثاق بعاملين، لن تتمكن من الوصول لحسابك.",
+ "authentication_methods": "طرق الاستيثاق",
+ "scan": {
+ "title": "مسح",
+ "desc": "امسح رمز الاستجابة السريعة QR من تطبيق الاستيثاق أو أدخل المفتاح:",
+ "secret_code": "مفتاح"
+ },
+ "verify": {
+ "desc": "لتفعيل الاستيثاق بعاملين أدخل الرمز من تطبيق الاستيثاق:"
+ }
+ },
+ "block_import": "استيراد المحجوبين",
+ "import_mutes_from_a_csv_file": "استورد قائمة الخُرس من ملف csv",
+ "account_backup": "نسخ احتياطي للحساب",
+ "download_backup": "نزّل",
+ "account_backup_table_head": "نسخ احتياطي",
+ "backup_not_ready": "هذا النسخ الاحتياطي ليس جاهزًا.",
+ "backup_failed": "فشل النسخ الاحتياطي.",
+ "remove_backup": "أزل",
+ "list_backups_error": "خطأ أثناء حلب قائمة النُسخ الاحتياطية: {error}",
+ "added_backup": "أُضيفت نسخة احتياطية جديدة.",
+ "blocks_tab": "المحجوبون",
+ "confirm_dialogs_block": "حجب مستخدم",
+ "confirm_dialogs_mute": "إخراس مستخدم",
+ "confirm_dialogs_delete": "حذف حالة",
+ "confirm_dialogs_logout": "خروج",
+ "confirm_dialogs_approve_follow": "قبول متابِع",
+ "confirm_dialogs_deny_follow": "رفض متابِع",
+ "list_aliases_error": "خطأ أثناء جلب الكنيات: {error}",
+ "hide_list_aliases_error_action": "أغلق",
+ "remove_alias": "أزل هذه الكنية",
+ "add_alias_error": "حدث خطأ أثناء إضافة الكنية: {error}",
+ "confirm_dialogs": "أطلب تأكيدًا عند",
+ "confirm_dialogs_repeat": "مشاركة حالة",
+ "mutes_and_blocks": "الخُرس والمحجوبون",
+ "move_account_target": "الحساب المستهدف (مثل {example})",
+ "wordfilter": "ترشيح الكلمات",
+ "always_show_post_button": "أظهر الزر العائم لإنشاء منشور جديد دائمًا",
+ "hide_wallpaper": "اخف خلفية المثيل",
+ "save": "احفظ التعديلات",
+ "lists_navigation": "أظهر القوائم في شريط التنقل",
+ "mute_export_button": "صدّر قائمة الخرس إلى ملف csv",
+ "blocks_imported": "اُستورد المحجوبون! معالجة القائمة ستستغرق وقتًا.",
+ "mute_export": "تصدير الخُرس",
+ "mute_import": "استيراد الخُرس",
+ "mute_import_error": "خطأ أثناء استيراد الخُرس",
+ "change_email_error": "حدثت خلل أثناء تغيير بريدك الإلكتروني.",
+ "change_email": "غيّر البريد الإلكتروني",
+ "changed_email": "نجح تغيير البريد الإلكتروني!",
+ "account_alias_table_head": "الكنية",
+ "account_alias": "كنيات الحساب",
+ "move_account": "أنقل الحساب",
+ "moved_account": "نُقل الحساب.",
+ "hide_media_previews": "اخف معاينات الوسائط",
+ "hide_muted_posts": "اخف منشورات المستخدمين الخُرس",
+ "confirm_dialogs_unfollow": "الغاء متابعة مستخدم",
+ "confirm_dialogs_remove_follower": "إزالة متابع",
+ "new_alias_target": "أضف كنية جديدة (مثل {example})",
+ "added_alias": "أُضيفت الكنية.",
+ "move_account_error": "خطأ أثناء نقل الحساب: {error}",
+ "emoji_reactions_on_timeline": "أظهر التفاعلات في الخط الزمني",
+ "mutes_imported": "اُستورد الخُرس! معالجة القائمة ستستغرق وقتًا.",
+ "remove_language": "أزل",
+ "primary_language": "اللغة الرئيسية:",
+ "expert_mode": "أظهر الإعدادات المتقدمة",
+ "block_import_error": "خطأ أثناء استيراد قائمة المحجوبين",
+ "add_backup": "أنشئ نسخة احتياطية جديدة",
+ "add_backup_error": "خطأ أثناء إضافة نسخ احتياطي جديد: {error}",
+ "move_account_notes": "إذا أردت نقل حسابك عليك إضافة كنية تشير إلى هنا في الحساب المستهدف.",
+ "avatar_size_instruction": "أدنى حجم مستحسن للصورة الرمزية هو 150x150 بيكسل.",
+ "word_filter_and_more": "مرشح الكلمات والمزيد...",
+ "hide_all_muted_posts": "اخف المنشورات المكتومة",
+ "max_thumbnails": "أقصى عدد للصور المصغرة لكل منشور (فارغ = غير محدود)",
+ "block_export_button": "صدّر قائمة المحجوبين إلى ملف csv",
+ "block_export": "تصدير المحجوبين",
+ "use_one_click_nsfw": "افتح المرفقات ذات المحتوى الحساس NSFW بنقرة واحدة",
+ "account_privacy": "خصوصية",
+ "use_contain_fit": "لا تقتص الصور المصغرة للمرفقات",
+ "import_blocks_from_a_csv_file": "استورد المحجوبين من ملف csv",
+ "instance_default_simple": "(افتراضي)",
+ "interface": "واجهة",
+ "birthday": {
+ "label": "تاريخ الميلاد",
+ "show_birthday": "اظهر تاريخ ميلادي"
+ },
+ "profile_fields": {
+ "add_field": "أضف حقل",
+ "value": "محتوى",
+ "label": "البيانات الوصفية للملف الشخصي",
+ "name": "لصيقة"
+ },
+ "posts": "منشورات",
+ "user_profiles": "ملفات المستخدمين الشخصية",
+ "notification_visibility_emoji_reactions": "تفاعلات",
+ "notification_visibility_polls": "انتهاء استطلاعات اشتركت بها",
+ "file_export_import": {
+ "restore_settings": "استرجع الإعدادات من ملف",
+ "backup_restore": "نسخ احتياطي للإعدادات",
+ "backup_settings_theme": "احفظ النسخ الاحتياطي للإعدادات والسمة في ملف",
+ "backup_settings": "احفظ النسخ الاحتياطي للإعدادات في ملف"
+ },
+ "mutes_tab": "خُرس",
+ "no_mutes": "لا يوجد خُرس",
+ "hide_followers_count_description": "لا تظهر عدد المتابِعين",
+ "show_moderator_badge": "أظهر شارة \"مشرف\" في ملفي التعريفي",
+ "hide_follows_count_description": "لا تظهر عدد المتابَعين",
+ "hide_muted_threads": "اخف النقاشات المكتومة",
+ "no_blocks": "لا يوجد محجوبون",
+ "show_admin_badge": "أظهر شارة \"مدير\" في ملفي التعريفي",
+ "conversation_display_tree": "تفرعات",
+ "notification_setting_block_from_strangers": "احجب اشعارات من لا تتابعهم",
+ "style": {
+ "switcher": {
+ "clear_all": "امسح الكل",
+ "keep_as_is": "أبقه على حاله",
+ "use_snapshot": "النسخة القديمة",
+ "use_source": "النسخة الحديثة",
+ "load_theme": "حمِّل سمة",
+ "help": {
+ "upgraded_from_v2": "PleromaFE حُدث، وعليه ربما ستجد اختلافًا في السمة."
+ },
+ "keep_color": "أبق الألوان",
+ "keep_opacity": "أبق الشفافية",
+ "keep_fonts": "أبق الخطوط",
+ "keep_shadows": "أبق الظلال",
+ "clear_opacity": "امسح الشفافية"
+ },
+ "common": {
+ "color": "اللون",
+ "opacity": "الشافافية"
+ },
+ "advanced_colors": {
+ "top_bar": "شريط العلوي",
+ "icons": "أيقونات",
+ "poll": "منحنى الاستطلاع",
+ "_tab_label": "متقدم",
+ "badge_notification": "الإشعارات",
+ "selectedPost": "منشور محدد",
+ "selectedMenu": "عنصر محدد من قائمة",
+ "highlight": "عناصر بارزة",
+ "disabled": "معطل",
+ "tabs": "ألسنة",
+ "chat": {
+ "border": "حدود",
+ "incoming": "وارد",
+ "outgoing": "صادر"
+ },
+ "alert_warning": "تحذير",
+ "alert_error": "خطأ",
+ "buttons": "أزرار",
+ "borders": "الحدود",
+ "wallpaper": "خلفية",
+ "pressed": "مضغوط",
+ "inputs": "حقول إدخال"
+ },
+ "shadows": {
+ "components": {
+ "button": "زر",
+ "input": "حقل إدخال",
+ "topBar": "شريط العلوي",
+ "avatar": "الصورة الرمزية لمستخدم (في الملف الشخصي)",
+ "avatarStatus": "الصورة الرمزية لمستخدم (في منشور)"
+ },
+ "_tab_label": "الظلال والإضاءة",
+ "shadow_id": "ظل #{value}",
+ "blur": "طمس",
+ "spread": "توزع"
+ },
+ "fonts": {
+ "size": "حجم (بالبكسل)",
+ "_tab_label": "خطوط",
+ "components": {
+ "interface": "واجهة",
+ "input": "حقول الإدخال",
+ "post": "نص المنشور"
+ },
+ "family": "اسم الخط",
+ "custom": "مخصص"
+ },
+ "preview": {
+ "header": "معاينة",
+ "content": "محتوى",
+ "header_faint": "جيد",
+ "mono": "محتوى",
+ "button": "زر",
+ "input": "وصلت للتوّ إلى لوس أنجلس.",
+ "fine_print": "طالع {0} لتعلّم ما لا ينفعك!",
+ "error": "مثال خطأ",
+ "faint_link": "دليل للمساعدة"
+ },
+ "radii": {
+ "_tab_label": "الانحناء"
+ }
+ },
+ "notification_setting_privacy": "الخصوصية",
+ "notification_mutes": "لوقف استلام إشعارات من مستخدم، أخرسه.",
+ "search_user_to_mute": "جِد من تريد إخراسه",
+ "subject_input_always_show": "أظهر حقل الموضوع دائمًا",
+ "subject_line_noop": "لا تنسخ",
+ "auto_update": "أظهر المنشورات الجديدة تلقائيًا",
+ "mention_link_display": "اعرض روابط الذكر",
+ "more_settings": "إعدادات إضافية",
+ "user_mutes": "مستخدمون",
+ "mention_link_show_avatar": "أظهر الصورة الرمزية للمستخدم بجانب الرابط",
+ "preview": "معاينة",
+ "show_scrollbars": "أظهر شريط التمرير للعمود الجانبي",
+ "third_column_mode": "أظهر محتوى العمود الثالث إذا توفرت المساحة",
+ "third_column_mode_none": "لا تظهر العمود الثالث",
+ "third_column_mode_notifications": "عمود الإشعارات",
+ "columns": "الأعمدة",
+ "column_sizes": "حجم الأعمدة",
+ "column_sizes_sidebar": "الشريط الجانبي",
+ "type_domains_to_mute": "جِد نطاقًا لكتمه",
+ "upload_a_photo": "ارفع صورة",
+ "virtual_scrolling": "حسن تصيير الخيط الزمني",
+ "user_popover_avatar_action_zoom": "كبر صورة الرمزية",
+ "fun": "متعة",
+ "column_sizes_content": "المحتوى",
+ "column_sizes_notifs": "الإشعارات",
+ "search_user_to_block": "جِد من تريد حجبه",
+ "url": "رابط",
+ "subject_line_behavior": "انسخ الموضوع عند الرد",
+ "conversation_display": "اسلوب عرض المحادثة",
+ "mention_link_show_avatar_quick": "أظهر الصورة الرمزية للمستخدم عند ذكره",
+ "user_popover_avatar_action_open": "افتح الملف الشخصي",
+ "notifications": "الإشعارات",
+ "notification_setting_filters": "مرشح",
+ "notification_setting_hide_notification_contents": "اخف محتوى الإشعارات ومرسليها",
+ "mention_link_display_short": "اسماء قصيرة (مثل {'@'}foo)",
+ "mention_link_display_full_for_remote": "اسماء كاملة للمستخدمين من الخوادم البعاد ({'@'}foo{'@'}example.org)",
+ "version": {
+ "title": "نسخة"
+ },
+ "commit_value": "احفظ",
+ "mention_link_display_full": "اسماء كاملة دايمًا (مثل {'@'}foo{'@'}example.org)",
+ "mute_bot_posts": "اكتم مشاركات الحسابات الآلية",
+ "mention_links": "روابط الذِكر",
+ "email_language": "لغة رسائل البريد الإلكتروني المرسلة إلي من الخادم",
+ "bot": "هذا الحساب آلي",
+ "discoverable": "اسمح بالعثور على هذا الحساب من خلال البحث وخِدمات أخرى",
+ "right_sidebar": "عكس ترتيب الأعمدة",
+ "setting_changed": "الإعدادات مغيّرة",
+ "setting_server_side": "هذا الإعداد مرتبط بحسابك وسيأثر على كل الجلسات والعملاء",
+ "allow_following_move": "اسمح بالمتابعة التلقائية عند انتقال حساب متابَع",
+ "chatMessageRadius": "رسائل",
+ "domain_mutes": "نطاقات",
+ "new_email": "البريد إلكتروني الجديد",
+ "notification_visibility_moves": "هجرة مستخدم",
+ "subject_line_mastodon": "مثل ماستودون: انسخ الأصلي",
+ "hide_follows_description": "لا تظهر متابَعي",
+ "conversation_other_replies_button_inside": "داخل الحالات",
+ "autohide_floating_post_button": "اخفاء زر النشر تلقائيا (هاتف)",
+ "conversation_other_replies_button_below": "تحت الحالات",
+ "reply_visibility_following_short": "أظهر الردود الموجهة إلى متابَعي",
+ "conversation_display_linear": "خطي",
+ "conversation_other_replies_button": "أظهر زر \"ردود أخرى\"",
+ "hide_followers_description": "لا تظهر متابِعي"
},
"timeline": {
- "collapse": "",
+ "collapse": "طوي",
"conversation": "محادثة",
"error_fetching": "خطأ أثناء جلب التحديثات",
- "load_older": "تحميل المنشورات القديمة",
+ "load_older": "حمل الحالات القديمة",
"no_retweet_hint": "",
- "repeated": "",
- "show_new": "عرض الجديد",
- "up_to_date": "تم تحديثه"
+ "repeated": "شورِك",
+ "show_new": "اعرض الجديد",
+ "up_to_date": "محدث",
+ "no_more_statuses": "لا مزيد من الحالات",
+ "error": "خطأ أثناء جلب الخيط الزمني: {0}",
+ "reload": "أعد التحميل",
+ "no_statuses": "لا توجد حالات"
},
"user_card": {
"approve": "قبول",
"block": "حظر",
- "blocked": "تم حظره!",
+ "blocked": "حُظر!",
"deny": "رفض",
- "follow": "اتبع",
- "followees": "",
+ "follow": "تابع",
+ "followees": "متابَعون",
"followers": "مُتابِعون",
- "following": "",
+ "following": "متابَع!",
"follows_you": "يتابعك!",
- "mute": "كتم",
- "muted": "تم كتمه",
+ "mute": "أخرِس",
+ "muted": "أخرَس",
"per_day": "في اليوم",
"remote_follow": "مُتابَعة عن بُعد",
- "statuses": "المنشورات"
+ "statuses": "المنشورات",
+ "approve_confirm_accept_button": "قبول",
+ "approve_confirm_title": "تأكيد القبول",
+ "edit_profile": "عدّل الملف الشخصي",
+ "deny_confirm": "أتريد رفض طلب المتابعة من {user} ؟",
+ "unfollow_confirm_title": "تأكيد إلغاء المتابعة",
+ "follow_progress": "الطلب جارٍ…",
+ "hidden": "مخفي",
+ "its_you": "أنت!",
+ "approve_confirm_cancel_button": "لا تقبل",
+ "approve_confirm": "أتريد قبول طلب المتابعة من {user} ؟",
+ "block_confirm_title": "تأكيد الحظر",
+ "block_confirm_accept_button": "حظر",
+ "block_confirm_cancel_button": "لا تحظر",
+ "deactivated": "عُطل",
+ "deny_confirm_title": "تأكيد الرفض",
+ "deny_confirm_accept_button": "رفض",
+ "deny_confirm_cancel_button": "لا ترفض",
+ "favorites": "المفضلة",
+ "follow_cancel": "ألغ الطلب",
+ "follow_sent": "أُرسل الطلب!",
+ "follow_unfollow": "ألغ المتابعة",
+ "unfollow_confirm": "أتريد إلغاء متابعة {user}؟",
+ "unfollow_confirm_accept_button": "ألغ المتابعة",
+ "unfollow_confirm_cancel_button": "لا تلغ المتابعة",
+ "media": "وسائط",
+ "block_confirm": "أتريد حظر {user} ؟",
+ "mute_confirm_cancel_button": "لا تخرِس",
+ "mute_confirm_title": "تأكيد الإخراس",
+ "message": "راسل",
+ "mute_confirm": "أتريد إخراس {user}؟",
+ "mute_confirm_accept_button": "أخرِس",
+ "mention": "أذكر",
+ "mute_duration_prompt": "أخرِس هذا الشخص لـ (ضع 0 لكتمه دائمًا):",
+ "admin_menu": {
+ "moderation": "الإشراف",
+ "grant_admin": "امنحه الإدارة",
+ "revoke_admin": "اخلعه من الإدارة",
+ "delete_user": "احذف مستخدم",
+ "deactivate_account": "عطِّل الحساب",
+ "grant_moderator": "امنحه الإشراف",
+ "revoke_moderator": "اخلعه من الإشراف",
+ "activate_account": "فعُّل الحساب",
+ "delete_account": "احذف الحساب",
+ "strip_media": "أزل الوسائط من المشاركات",
+ "delete_user_data_and_deactivate_confirmation": "هذا الإجراء سيحذف بيانات الحساب وسيعطله، هل أنت متيقن؟"
+ },
+ "note": "ملاحظة",
+ "note_blank": "(لاشيء)",
+ "edit_note": "حرر الملاحظة",
+ "edit_note_apply": "طبِّق",
+ "edit_note_cancel": "ألغِ",
+ "report": "بلّغ",
+ "subscribe": "اشترك",
+ "unsubscribe": "ألغِ الاشتراك",
+ "unblock_progress": "يرفع الحجب…",
+ "block_progress": "يحجب…",
+ "unblock": "ارفع الحجب",
+ "remove_follower": "أزل متابِع",
+ "remove_follower_confirm_title": "تأكيد إزالة متابِع",
+ "remove_follower_confirm_accept_button": "أزِل",
+ "remove_follower_confirm_cancel_button": "أبق",
+ "hide_repeats": "اخف المشاركات",
+ "show_repeats": "أظهر المشاركات",
+ "bot": "آلي",
+ "unmute": "ارفع عنه الخرَس",
+ "unmute_progress": "يرفع الخرَس…",
+ "mute_progress": "يُخرِس…",
+ "remove_follower_confirm": "متيقن من إزالة {user} من متابِعيك؟",
+ "birthday": "وُلد في {birthday}"
},
"user_profile": {
- "timeline_title": "الخيط الزمني للمستخدم"
+ "timeline_title": "الخيط الزمني للمستخدم",
+ "profile_loading_error": "عذرًا، حدث خطأ أثناء تحميل هذا الملف الشخصي.",
+ "profile_does_not_exist": "عذرًا، هذا الملف الشخصي ليس موجودًا."
},
"who_to_follow": {
"more": "المزيد",
"who_to_follow": "للمتابعة"
+ },
+ "about": {
+ "mrf": {
+ "keyword": {
+ "ftl_removal": "إزالة من الخط الزمني الخاص بجميع الشبكات المعروفة",
+ "reject": "رفض",
+ "replace": "إستبدال",
+ "is_replaced_by": "←",
+ "keyword_policies": "سياسة الكلمات الدلالية"
+ },
+ "simple": {
+ "simple_policies": "سياسات الخادم",
+ "instance": "مثيل",
+ "reason": "السبب",
+ "accept": "قبول",
+ "reject": "رفض",
+ "ftl_removal": "أُزيل من الخط الزمني «الشبكات المعروفة»"
+ },
+ "federation": "الاتحاد",
+ "mrf_policies": "تفعيل سياسات إعادة كتابة المنشور",
+ "mrf_policies_desc": "خاصية إعادة كتابة المناشير تقوم بتعديل تفاعل الاتحاد مع هذا الخادم. السياسات التالية مفعّلة:"
+ }
+ },
+ "announcements": {
+ "page_header": "إعلانات",
+ "title": "إعلان",
+ "mark_as_read_action": "علّمه كمقروء",
+ "post_form_header": "انشر إعلانًا",
+ "post_placeholder": "اكتب محتوى الاعلان هنا...",
+ "post_action": "انشر",
+ "post_error": "خطأ: {error}",
+ "close_error": "أغلاق",
+ "delete_action": "احذف",
+ "start_time_prompt": "وقت البدأ: ",
+ "end_time_prompt": "وقت النهاية: ",
+ "all_day_prompt": "هذا حدث يوم كامل",
+ "start_time_display": "يبدأ في {time}",
+ "end_time_display": "ينتهي في {time}",
+ "edit_action": "حرر",
+ "submit_edit_action": "أرسل",
+ "cancel_edit_action": "ألغِ",
+ "inactive_message": "هذا الاعلان غير نشط",
+ "published_time_display": "نُشر في {time}"
+ },
+ "polls": {
+ "votes": "أصوات",
+ "vote": "صوّت",
+ "type": "نوع الاستطلاع",
+ "single_choice": "خيار واحد",
+ "multiple_choices": "متعدد الخيارات",
+ "expiry": "عمر الاستطلاع",
+ "expires_in": "ينتهي الاستطلاع في {0}",
+ "expired": "انتهى الاستطلاع منذ {0}",
+ "add_poll": "أضف استطلاعًا",
+ "add_option": "أضف خيارًا",
+ "option": "خيار",
+ "people_voted_count": "{count} شخص صوّت| {count} شخص صوّت",
+ "votes_count": "{count} صوت | {count} صوت"
+ },
+ "emoji": {
+ "stickers": "ملصقات",
+ "emoji": "إيموجي",
+ "search_emoji": "ابحث عن إيموجي",
+ "unicode_groups": {
+ "animals-and-nature": "حيوانات وطبيعة",
+ "food-and-drink": "أطعمة ومشروبات",
+ "symbols": "رموز",
+ "activities": "نشاطات",
+ "flags": "أعلام",
+ "smileys-and-emotion": "ابتسامات وانفعالات",
+ "travel-and-places": "سفر وأماكن"
+ },
+ "add_emoji": "أدخل إيموجي",
+ "custom": "إيموجي مخصص",
+ "keep_open": "أبق المنتقي مفتوحًا"
+ },
+ "interactions": {
+ "emoji_reactions": "تفاعلات بالإيموجي",
+ "reports": "البلاغات",
+ "follows": "المتابعات الجديدة"
+ },
+ "report": {
+ "state_closed": "مغلق",
+ "state_resolved": "عولج",
+ "reported_statuses": "الحالة المبلغة عنها:",
+ "state_open": "مفتوح",
+ "notes": "ملاحظة:",
+ "state": "الحالة:",
+ "reporter": "المبلِّغ:",
+ "reported_user": "المُبلغ عنه:"
+ },
+ "selectable_list": {
+ "select_all": "اختر الكل"
+ },
+ "image_cropper": {
+ "save": "احفظ",
+ "cancel": "ألغ",
+ "crop_picture": "اقتصاص الصورة",
+ "save_without_cropping": "احفظ دون اقتصاص"
+ },
+ "importer": {
+ "submit": "أرسل",
+ "success": "نجح الاستيراد.",
+ "error": "حدث خطأ أثناء الاستيراد."
+ },
+ "domain_mute_card": {
+ "mute": "أخرِس",
+ "mute_progress": "يُخرس…",
+ "unmute": "ارفع عنه الخرس",
+ "unmute_progress": "يرفع الخرس…"
+ },
+ "exporter": {
+ "export": "صدر",
+ "processing": "يُعالج. سيُطلب منك تنزيل الملف قريباً"
+ },
+ "media_modal": {
+ "previous": "السابق",
+ "next": "التالي",
+ "hide": "أغلق عارض الوسائط",
+ "counter": "{current}\\{total}"
+ },
+ "remote_user_resolver": {
+ "searching_for": "يبحث عن",
+ "error": "لم يُعثر عليه."
+ },
+ "admin_dash": {
+ "nodb": {
+ "documentation": "التوثيق",
+ "text2": "اغلب خيارات الضبط لن تتوفر."
+ },
+ "window_title": "الإدارة",
+ "wip_notice": "لوحة المدير لا زالت تجريبية ولا تزال قيد للتطوير، {adminFeLink}.",
+ "old_ui_link": "واجهة المدير القديمة هنا",
+ "commit_all": "احفظ الكل",
+ "tabs": {
+ "instance": "مثيل"
+ },
+ "instance": {
+ "instance": "معلومات المثيل",
+ "registrations": "تسجيل المستخدمين",
+ "restrict": {
+ "header": "قيّد وصول الزواروالمجهولين",
+ "timelines": "وصول الخط الزمني",
+ "profiles": "وصول الملفات الشخصية",
+ "activities": "وصول النشاطات/الحالات"
+ }
+ },
+ "limits": {
+ "posts": "حد النشر",
+ "uploads": "حد المرفقات",
+ "profile_fields": "حد حقول الملف الشخصي",
+ "user_uploads": "حد وسائط الملف الشخصي"
+ },
+ "frontend": {
+ "repository": "رابط المستودع",
+ "versions": "النسخ المتوفرة",
+ "build_url": "رابط البناء",
+ "reinstall": "أعد التثبيت",
+ "is_default": "(افتراضي)",
+ "is_default_custom": "(افتراضي، النسخة: {version})",
+ "install": "ثبّت",
+ "install_version": "ثبت النسخة {version}",
+ "more_install_options": "مزيد من خيارات التثبيت",
+ "set_default": "عينه كافتراضي",
+ "set_default_version": "عين النسخة {version} كافتراضية",
+ "available_frontends": "متوفر للتثبيت"
+ },
+ "temp_overrides": {
+ ":pleroma": {
+ ":instance": {
+ ":public": {
+ "label": "المثيل علني",
+ "description": "تعطيله سيحصر الوصول إلى API للمستخدمين الوالجين، ولن يقدر الزوار على الوصول إلى الخط الزمني العلني والموحد."
+ },
+ ":description_limit": {
+ "description": "حد عدد المحارف لوصف المرفق"
+ },
+ ":background_image": {
+ "label": "صورة الخلفية"
+ },
+ ":limit_to_local_content": {
+ "label": "اقتصار البحث على المحتوى المحلي"
+ }
+ }
+ }
+ }
+ },
+ "time": {
+ "in_past": "منذ {0}",
+ "unit": {
+ "hours_short": "{0}سا",
+ "minutes": "{0} دقيقة | {0} دقائق",
+ "days_short": "{0}ي",
+ "minutes_short": "{0}د",
+ "hours": "{0} ساعة | {0} ساعات",
+ "weeks": "{0} أسبوع | {0} أسابيع",
+ "months_short": "{0}ش",
+ "seconds": "{0} ثانية | {0} ثانية",
+ "seconds_short": "{0}ثا",
+ "years": "{0} سنة | {0} سنوات",
+ "years_short": "{0}سن",
+ "days": "{0} يوم | {0} أيام",
+ "months": "{0} شهر | {0} أشهر",
+ "weeks_short": "{0}أس"
+ },
+ "in_future": "في {0}",
+ "now": "هذه اللحظة",
+ "now_short": "الآن"
+ },
+ "status": {
+ "delete_confirm": "أتريد حذف هذه الحالة؟",
+ "delete_error": "خطأ أثناء حذف الحالة: {0}",
+ "plus_more": "+{number} أخرون",
+ "many_attachments": "المنشور يحوي {number} مرفقات",
+ "repeat_confirm": "أتريد مشاركة هذه الحالة؟",
+ "edited_at": "(آخر تعديل {time})",
+ "repeat_confirm_title": "تأكيد المشاركة",
+ "repeat_confirm_accept_button": "شارك",
+ "repeat_confirm_cancel_button": "لا تشارك",
+ "edit": "حرر الحالة",
+ "pin": "ثبته على الملف الشخصي",
+ "unpin": "ألغ تثبيته من الملف الشخصي",
+ "delete_confirm_cancel_button": "أبقه",
+ "replies_list": "الردود:",
+ "status_deleted": "هذا المنشور محذوف",
+ "favorites": "المفضلة",
+ "pinned": "مثبت",
+ "hide_full_subject": "اخف كامل الموضوع",
+ "repeats": "المشاركات",
+ "delete": "اخذف الحالة",
+ "delete_confirm_title": "تأكيد الحذف",
+ "reply_to": "رد على",
+ "mentions": "ذكرَ",
+ "unmute_conversation": "ارفع الكتم عن المحادثة",
+ "status_unavailable": "الحالة غير متوفرة",
+ "copy_link": "انسخ رابط الحالة",
+ "show_full_subject": "أظهر الموضوع كاملا",
+ "show_content": "أظهر المحتوى",
+ "hide_content": "اخف المحتوى",
+ "you": "(أنت)",
+ "show_all_attachments": "أظهر كل المرفقات",
+ "hide_attachment": "اخف المرفق",
+ "move_down": "حرك المرفق لليمين",
+ "thread_hide": "اخف هذا النقاش",
+ "thread_muted": "النقاش مكتوم",
+ "delete_confirm_accept_button": "احذف",
+ "mute_conversation": "اكتم المحادثة",
+ "external_source": "مصدر خارجي",
+ "expand": "وسّع",
+ "collapse_attachments": "طوي المرفقات",
+ "remove_attachment": "أزل المرفق",
+ "move_up": "حرك المرفق لليسار",
+ "open_gallery": "افتح المعرض",
+ "thread_show": "أظهر هذا النقاس",
+ "nsfw": "محتوى حساس NSFW",
+ "status_history": "تأريخ الحالة",
+ "thread_show_full_with_icon": "{icon} {text}",
+ "thread_follow_with_icon": "{icon} {text}",
+ "show_all_conversation_with_icon": "{icon} {text}",
+ "ancestor_follow_with_icon": "{icon} {text}",
+ "show_only_conversation_under_this": "أظهر الردود على هذه الحالة فقط",
+ "reaction_count_label": "تفاعل {num} شخص | تفاعل {num} أشخاص",
+ "replies_list_with_others": "رد (+ {numReplies} آخر): | رد (+ {numReplies} آخرون):",
+ "show_attachment_in_modal": "أظهر الوسائط في منبثقات",
+ "show_attachment_description": "معاينة الوصف ( افتح المرفق لقراءة الوصف الكامل)"
+ },
+ "lists": {
+ "creating_list": "إنشاء قائمة جديدة",
+ "update_title": "احفظ العنوان",
+ "add_members": "ابحث عن مزيد من المستخدمين",
+ "really_delete": "أمتيقن من حذف القائمة؟",
+ "lists": "قوائم",
+ "new": "قائمة جديدة",
+ "title": "عنوان القائمة",
+ "search": "ابحث عن مستخدم",
+ "remove_from_list": "أزل من القائمة",
+ "add_to_list": "أضف للقائمة",
+ "editing_list": "تحرير القائمة {listTitle}",
+ "create": "أنشئ",
+ "save": "احفظ التعديلات",
+ "delete": "احذف القائمة",
+ "manage_lists": "أدِر القوائم",
+ "manage_members": "أدِر أعضاء القائمة",
+ "is_in_list": "موجود في القائمة سلفًا"
+ },
+ "file_type": {
+ "audio": "صوت",
+ "image": "صورة",
+ "file": "ملف",
+ "video": "فيديو"
+ },
+ "user_reporting": {
+ "add_comment_description": "سيرسل البلاغ إلى مشرف المثيل، يمكنك شرح سبب البلاغ أدناه:",
+ "title": "بلاغ عن {0}",
+ "additional_comments": "تعليقات إضافية",
+ "forward_description": "هذا المستخدم من خادم آخر. هل تريد إرسال نسخة منه إلى مشرفه؟",
+ "forward_to": "وجّهه إلى {0}",
+ "submit": "أرسل",
+ "generic_error": "حدث خطأ أثناء معالجة طلبك."
+ },
+ "tool_tip": {
+ "media_upload": "ارفع وسائط",
+ "favorite": "فضّل",
+ "add_reaction": "أضف تفاعل",
+ "user_settings": "إعدادات المستخدم",
+ "accept_follow_request": "اقبل طلب المتابعة",
+ "reject_follow_request": "ارفض طلب المتابعة",
+ "repeat": "شارك",
+ "reply": "ردّ"
+ },
+ "upload": {
+ "error": {
+ "base": "فشل الرفع.",
+ "message": "فشل الرفع: {0}",
+ "default": "حاو لاحقًا",
+ "file_too_big": "حجم الملف كبير [{filesize}{filesizeunit}\\{allowedsize}{allowedsizeunit}]"
+ },
+ "file_size_units": {
+ "B": "بايت",
+ "MiB": "مب",
+ "TiB": "تب",
+ "GiB": "غب",
+ "KiB": "كب"
+ }
+ },
+ "search": {
+ "person_talking": "{count} شخص يتكلم",
+ "people_talking": "{count} شخص يتكلم",
+ "no_results": "لا نتائج",
+ "no_more_results": "لا مزيد من النتائج",
+ "people": "أشخاص",
+ "hashtags": "وسوم",
+ "load_more": "حمّل مزيدًا من النتائج"
+ },
+ "password_reset": {
+ "forgot_password": "أنسيت كلمة السر؟",
+ "placeholder": "البريد الإلكتروني أو اسم المستخدم",
+ "return_home": "عُد للصفحة الرئيسية",
+ "too_many_requests": "وصلت سقف المحاولات، حاول لاحقًا."
+ },
+ "chats": {
+ "chats": "محادثات",
+ "delete_confirm": "أتريد حذف هذه الرسالة؟",
+ "you": "أنت:",
+ "message_user": "راسل {nickname}",
+ "delete": "احذف",
+ "new": "محادثة جديدة",
+ "empty_message_error": "يستحيل إرسال رسالة فارغة",
+ "more": "مزيد",
+ "empty_chat_list_placeholder": "ليس لديك محادثات. ابدأ واحدة جديدة!"
+ },
+ "display_date": {
+ "today": "اليوم"
+ },
+ "update": {
+ "big_update_content": "نظرًا لطول المدة التي استغرقها تطوير هذا الاصدار فسترى اختلافات كبيرة عن ما اعتدت عليه.",
+ "update_bugs": "نظرًا لهذا لكبر هذا التحديث فقد نكون قد سهينى عن بعض الاخطاء لذا يرجى التبليغ عن أي علّة أو مشكلة. نحن نرحب بقتراحاتك وتعليقاتكم لتحسين بليروما وواجهها الأمامية وطرح المشاكل المتعلقة بهما.",
+ "update_changelog": "لمزيد من المعلومات، راجع {theFullChangelog}.",
+ "update_changelog_here": "سجل التغييرات الكامل",
+ "art_by": "رَسمُ {linkToArtist}",
+ "big_update_title": "رجاءً تعاون معنا"
}
}
diff --git a/src/i18n/cs.json b/src/i18n/cs.json
@@ -9,7 +9,10 @@
"scope_options": "Možnosti rozsahů",
"text_limit": "Textový limit",
"title": "Vlastnosti",
- "who_to_follow": "Koho sledovat"
+ "who_to_follow": "Koho sledovat",
+ "shout": "Shoutbox",
+ "pleroma_chat_messages": "Pleroma Chat",
+ "upload_limit": "Limit pro velikost souborů"
},
"finder": {
"error_fetching_user": "Chyba při načítání uživatele",
@@ -20,12 +23,47 @@
"submit": "Odeslat",
"more": "Více",
"generic_error": "Vyskytla se chyba",
- "optional": "volitelné"
+ "optional": "volitelné",
+ "retry": "Zkuste to znovu",
+ "show_more": "Zobrazit více",
+ "show_less": "Zobrazit méně",
+ "never_show_again": "Znovu již nezobrazovat",
+ "dismiss": "Zahodit",
+ "cancel": "Zrušit",
+ "disable": "Vypnout",
+ "enable": "Zapnout",
+ "close": "Zavřít",
+ "peek": "Nahlédnout",
+ "generic_error_message": "Došlo k chybě: {0}",
+ "error_retry": "Zkuste to prosím znovu",
+ "confirm": "Potvrdit",
+ "verify": "Ověřit",
+ "scope_in_timeline": {
+ "public": "Veřejné",
+ "direct": "Přímá",
+ "unlisted": "Neuvedené",
+ "private": "Pouze pro sledující"
+ },
+ "scroll_to_top": "Přejít na začátek",
+ "role": {
+ "admin": "Správce",
+ "moderator": "Moderátor"
+ },
+ "pin": "Připnout položku",
+ "flash_content": "Klikněte pro zobrazení Flash obsahu pomocí Ruffle (Experimentální, nemusí fungovat).",
+ "flash_security": "Flash obsah může být nebezpečný, protože se jedná o libovolný spustitelný kód.",
+ "flash_fail": "Nepodařilo se načíst Flash obsah. Podrobnosti naleznete v konzoli.",
+ "undo": "Vrátit zpět",
+ "yes": "Ano",
+ "no": "Ne",
+ "unpin": "Odepnout položku",
+ "loading": "Načítání…"
},
"image_cropper": {
"crop_picture": "Oříznout obrázek",
"save": "Uložit",
- "cancel": "Zrušit"
+ "cancel": "Zrušit",
+ "save_without_cropping": "Uložit bez ořezávání"
},
"login": {
"login": "Přihlásit",
@@ -35,17 +73,31 @@
"placeholder": "např. lain",
"register": "Registrovat",
"username": "Uživatelské jméno",
- "hint": "Chcete-li se přidat do diskuze, přihlaste se"
+ "hint": "Chcete-li se přidat do diskuze, přihlaste se",
+ "logout_confirm": "Opravdu se chcete odhlásit?",
+ "logout_confirm_accept_button": "Odhlásit se",
+ "logout_confirm_cancel_button": "Neodhlašovat",
+ "logout_confirm_title": "Potvrzení odhlášení",
+ "authentication_code": "Ověřovací kód",
+ "enter_recovery_code": "Zadejte záložní kód",
+ "enter_two_factor_code": "Zadejte dvoufaktorový ověřovací kód",
+ "recovery_code": "Záložní kód",
+ "heading": {
+ "totp": "Dvoufaktorové ověřování",
+ "recovery": "Dvoufaktorové obnovení"
+ }
},
"media_modal": {
"previous": "Předchozí",
- "next": "Další"
+ "next": "Další",
+ "counter": "{current} / {total}",
+ "hide": "Zavřít prohlížeč médií"
},
"nav": {
"about": "O instanci",
"back": "Zpět",
"chat": "Místní chat",
- "friend_requests": "Požadavky o sledování",
+ "friend_requests": "Požadavky na sledování",
"mentions": "Zmínky",
"dms": "Přímé zprávy",
"public_tl": "Veřejná časová osa",
@@ -53,7 +105,24 @@
"twkn": "Celá známá síť",
"user_search": "Hledání uživatelů",
"who_to_follow": "Koho sledovat",
- "preferences": "Předvolby"
+ "preferences": "Předvolby",
+ "home_timeline": "Domovská časová osa",
+ "timelines": "Časové osy",
+ "search_close": "Zavřít vyhledávací panel",
+ "chats": "Chaty",
+ "lists": "Seznamy",
+ "edit_nav_mobile": "Upravit navigační panel",
+ "mobile_sidebar": "Přepnout mobilní postranní panel",
+ "announcements": "Oznámení",
+ "mobile_notifications_close": "Uzavřít oznámení",
+ "mobile_notifications": "Otevřít oznámení (máte nějaké nepřečtené)",
+ "administration": "Správa",
+ "bookmarks": "Záložky",
+ "search": "Hledat",
+ "edit_pinned": "Upravit připnuté položky",
+ "edit_finish": "Dokončit úpravu",
+ "mobile_notifications_mark_as_seen": "Označit vše jako přečtené",
+ "interactions": "Interakce"
},
"notifications": {
"broken_favorite": "Neznámý příspěvek, hledám jej…",
@@ -61,9 +130,21 @@
"followed_you": "vás nyní sleduje",
"load_older": "Načíst starší oznámení",
"notifications": "Oznámení",
- "read": "Číst!",
+ "read": "Přečíst!",
"repeated_you": "zopakoval/a váš příspěvek",
- "no_more_notifications": "Žádná další oznámení"
+ "no_more_notifications": "Žádná další oznámení",
+ "error": "Nastala chyba při načítání oznámení: {0}",
+ "unread_announcements": "{num} nepřečtené oznámení | {num} nepřečtených oznámení",
+ "unread_chats": "{num} nepřečtených zpráv | {num} nepřečtených zpráv",
+ "unread_follow_requests": "{num} nový požadavek o sledování | {num} nových požadavků o sledování",
+ "configuration_tip": "Může upravit co zde zobrazovat v {theSettings}. {dismiss}",
+ "follow_request": "vás chce sledovat",
+ "migrated_to": "migroval na",
+ "poll_ended": "anketa skončila",
+ "reacted_with": "reagoval/a s {0}",
+ "submitted_report": "Odeslal/a stížnost",
+ "configuration_tip_settings": "nastavení",
+ "configuration_tip_dismiss": "Již nezobrazovat"
},
"post_status": {
"new_status": "Napsat nový příspěvek",
@@ -85,7 +166,27 @@
"private": "Pouze pro sledující - Poslat pouze sledujícím",
"public": "Veřejný - Poslat na veřejné časové osy",
"unlisted": "Neuvedený - Neposlat na veřejné časové osy"
- }
+ },
+ "edit_unsupported_warning": "Pleroma nepodporuje upravování zmínek a anket.",
+ "edit_status": "Upravit příspěvek",
+ "media_description": "Popis médií",
+ "reply_option": "Odpovědět na tento příspěvek",
+ "content_type_selection": "Formát příspěvku",
+ "post": "Odeslat",
+ "empty_status_error": "Nemůžete odeslat prázdný příspěvek bez žádných souborů",
+ "preview_empty": "Prázdné",
+ "media_description_error": "Selhání při aktualizaci médií, zkuste to znovu",
+ "scope_notice": {
+ "public": "Tento příspěvek bude viditelný pro všechny",
+ "private": "Tento příspěvek bude viditelný pouze pro vaše sledující",
+ "unlisted": "Tento příspěvek nebude viditelný ve Veřejné časové ose a časové ose Celá známá síť"
+ },
+ "scope_notice_dismiss": "Zavřít tuto zprávu",
+ "quote_option": "Citovat tento příspěvek",
+ "direct_warning_to_all": "Tento příspěvek budou vidět pouze zmínění uživatelé.",
+ "direct_warning_to_first_only": "Tento příspěvek bude viditelný pouze pro zmíněné uživatele na začátku příspěvku.",
+ "edit_remote_warning": "Jiné vzdálené instance nemusí podporovat úpravy a nemusí přijmout nejnovější verzi vašeho příspěvku.",
+ "preview": "Náhled"
},
"registration": {
"bio": "O vás",
@@ -105,8 +206,18 @@
"email_required": "nemůže být prázdný",
"password_required": "nemůže být prázdné",
"password_confirmation_required": "nemůže být prázdné",
- "password_confirmation_match": "musí být stejné jako heslo"
- }
+ "password_confirmation_match": "musí být stejné jako heslo",
+ "birthday_min_age": "musí být před nebo v {date}",
+ "birthday_required": "nemůže být ponecháno prázdné"
+ },
+ "birthday_optional": "Datum narození (volitelné):",
+ "register": "Registrovat",
+ "reason": "Důvod pro registraci",
+ "reason_placeholder": "Tato instance schvaluje registrace manuálně,\nZdůvodněte administraci důvod registrace.",
+ "birthday": "Datum narození:",
+ "email_language": "V jakém jazyce chcete přijímat emaily z tohoto serveru?",
+ "email_optional": "Email (volitelný)",
+ "bio_optional": "Bio (volitelné)"
},
"settings": {
"app_name": "Název aplikace",
@@ -136,7 +247,7 @@
"default_vis": "Výchozí rozsah viditelnosti",
"delete_account": "Smazat účet",
"delete_account_description": "Trvale smaže váš účet a všechny vaše příspěvky.",
- "delete_account_error": "Při mazání vašeho účtu nastala chyba. Pokud tato chyba bude trvat, kontaktujte prosím admministrátora vaší instance.",
+ "delete_account_error": "Při mazání vašeho účtu nastala chyba. Pokud tato chyba přetrvává, kontaktujte prosím administrátora vaší instance.",
"delete_account_instructions": "Pro potvrzení smazání účtu napište své heslo do pole níže.",
"avatar_size_instruction": "Doporučená minimální velikost pro avatarové obrázky je 150x150 pixelů.",
"export_theme": "Uložit přednastavení",
@@ -152,7 +263,7 @@
"general": "Obecné",
"hide_attachments_in_convo": "Skrývat přílohy v konverzacích",
"hide_attachments_in_tl": "Skrývat přílohy v časové ose",
- "max_thumbnails": "Maximální počet miniatur na příspěvek",
+ "max_thumbnails": "Maximální počet miniatur na příspěvek (prázdné = žádný limit)",
"hide_isp": "Skrýt panel specifický pro instanci",
"preload_images": "Přednačítat obrázky",
"use_one_click_nsfw": "Otevírat citlivé přílohy pouze jedním kliknutím",
@@ -181,7 +292,7 @@
"new_password": "Nové heslo",
"notification_visibility": "Typy oznámení k zobrazení",
"notification_visibility_follows": "Sledující",
- "notification_visibility_likes": "Oblíbení",
+ "notification_visibility_likes": "Oblíbené",
"notification_visibility_mentions": "Zmínky",
"notification_visibility_repeats": "Zopakování",
"no_rich_text_description": "Odstranit ze všech příspěvků formátování textu",
@@ -237,18 +348,34 @@
"true": "ano"
},
"notifications": "Oznámení",
- "enable_web_push_notifications": "Povolit webová push oznámení",
+ "enable_web_push_notifications": "Povolit web push oznámení",
"style": {
"switcher": {
"keep_color": "Ponechat barvy",
"keep_shadows": "Ponechat stíny",
"keep_opacity": "Ponechat průhlednost",
"keep_roundness": "Ponechat kulatost",
- "keep_fonts": "Keep fonts",
+ "keep_fonts": "Ponechat písma",
"save_load_hint": "Možnosti „Ponechat“ dočasně ponechávají aktuálně nastavené možností při volení či nahrávání motivů, také tyto možnosti ukládají při exportování motivu. Pokud není žádné pole zaškrtnuto, uloží export motivu všechno.",
"reset": "Resetovat",
"clear_all": "Vymazat vše",
- "clear_opacity": "Vymazat průhlednost"
+ "clear_opacity": "Vymazat průhlednost",
+ "keep_as_is": "Ponechat jak je",
+ "use_snapshot": "Stará verze",
+ "help": {
+ "migration_napshot_gone": "Z nějakého důvodu chyběl snímek, některé věci můžou vypadat jinak, než si pamatujete.",
+ "fe_upgraded": "Motiv engine PleromaFE byl aktualizován po aktualizaci verze.",
+ "future_version_imported": "Soubor, který jste importoval/a byl vytvořen pro novější verzi FE.",
+ "older_version_imported": "Soubor, který jste importoval/a byl vytvořen pro starší verzi FE.",
+ "v2_imported": "Soubor, který jste importoval/a byl vytvořen pro starší verzi FE. Snažíme se zachovat maximální kompatibilitu, ale může dojít k nekonzistenci.",
+ "snapshot_present": "Snímek motivu byl načten, takže všechny hodnoty byly přepsány. Místo toho můžete načíst skutečná data motivu.",
+ "upgraded_from_v2": "PleromaFE bylo aktualizováno, motiv může vypadat trochu jinak, než si pamatujete.",
+ "snapshot_missing": "V souboru nebyl žádný snímek motivu, takže může vypadat jinak, než bylo původně zamýšleno.",
+ "fe_downgraded": "Verze PleromaFE byla vrácena zpět.",
+ "migration_snapshot_ok": "Pro jistotu byl načten snímek motivu. Můžete zkusit načíst data motivu."
+ },
+ "load_theme": "Načíst motiv",
+ "use_source": "Nová verze"
},
"common": {
"color": "Barva",
@@ -283,7 +410,27 @@
"borders": "Okraje",
"buttons": "Tlačítka",
"inputs": "Vstupní pole",
- "faint_text": "Vybledlý text"
+ "faint_text": "Vybledlý text",
+ "popover": "Popisy, menu, popovery",
+ "underlay": "Podklad",
+ "pressed": "Zmáčknuté",
+ "selectedPost": "Vybraný příspěvek",
+ "selectedMenu": "Vybraná položka menu",
+ "alert_warning": "Varování",
+ "alert_neutral": "Neutrální",
+ "toggled": "Přepnuto",
+ "disabled": "Vypnuto",
+ "tabs": "Karty",
+ "chat": {
+ "incoming": "Příchozí",
+ "border": "Okraj",
+ "outgoing": "Odchozí"
+ },
+ "post": "Příspěvky/Bio uživatelů",
+ "wallpaper": "Tapeta",
+ "poll": "Graf ankety",
+ "icons": "Ikony",
+ "highlight": "Zvýrazněné prvky"
},
"radii": {
"_tab_label": "Kulatost"
@@ -346,7 +493,252 @@
"checkbox": "Pročetl/a jsem podmínky používání",
"link": "hezký malý odkaz"
}
- }
+ },
+ "added_alias": "Přezdívka přidána.",
+ "emoji_reactions_scale": "Měřítko zvětšení reakcí",
+ "file_export_import": {
+ "backup_restore": "Záloha nastavení",
+ "errors": {
+ "file_too_new": "Nekompatibilní hlavní verze: {fileMajor}, tato verze PleromaFE (verze {feMajor}) je příliš stará",
+ "invalid_file": "Vybraný soubor není podporovaná záloha Pleroma nastavení. Žádné změny nebyli provedeny.",
+ "file_too_old": "Nekompatibilní verze: {fileMajor}, verze souboru je příliš stará a nepodporovaná (min. verze {feMajor})",
+ "file_slightly_new": "Menší verze je rozdílná, některé nastavení se nemusí načíst"
+ },
+ "backup_settings": "Zálohovat nastavení do souboru",
+ "backup_settings_theme": "Zálohovat nastavení a motiv do souboru",
+ "restore_settings": "Obnovit nastavení ze souboru"
+ },
+ "backup_failed": "Záloha selhala.",
+ "tree_fade_ancestors": "Zobrazit autory aktuálního příspěvku ve slabém textu",
+ "mention_link_display_full_for_remote": "jako celá jména pouze pro vzdálené uživatele (např. {'@'}foo{'@'}example.org)",
+ "autocomplete_select_first": "Automaticky vybrat prvního kandidáta, když výsledky automatického doplnění jsou dostupné",
+ "import_blocks_from_a_csv_file": "Importovat blokace z csv souboru",
+ "backup_running": "Tato záloha právě probíhá, zpracován záznam {number}. |Tato záloha právě probíhá, zpracováno {number} záznamů.",
+ "changed_email": "Email byl úspěšně změněn!",
+ "chatMessageRadius": "Zpráva chatu",
+ "confirm_dialogs_delete": "mazání příspěvku",
+ "disable_sticky_headers": "Nezanechávat záhlaví sloupců na horní část obrazovky",
+ "third_column_mode_postform": "Editor hlavního příspěvku a navigaci",
+ "columns": "Sloupce",
+ "sensitive_by_default": "Označit příspěvky jako citlivé ve výchozím stavu",
+ "domain_mutes": "Domény",
+ "fallback_language": "Záložní jazyk {index}:",
+ "primary_language": "Hlavní jazyk:",
+ "security": "Zabezpečení",
+ "enter_current_password_to_confirm": "Zadejte vaše současné heslo pro potvrzení vaší identity",
+ "post_look_feel": "Vzhled příspěvků",
+ "mention_links": "Odkazy zmínek",
+ "mfa": {
+ "confirm_and_enable": "Potvrdit a zapnout OTP",
+ "title": "Dvoufázová autentizace",
+ "scan": {
+ "title": "Skenovat",
+ "desc": "Pomocí vaší 2FA aplikace oskenujte QR kód, nebo zadejte klíč:",
+ "secret_code": "Klíč"
+ },
+ "otp": "OTP",
+ "generate_new_recovery_codes": "Vygenerovat nové záložní kódy",
+ "setup_otp": "Nastavit OTP",
+ "wait_pre_setup_otp": "přednastavení OTP",
+ "waiting_a_recovery_codes": "Přijímám záložní kódy…",
+ "recovery_codes_warning": "Zapište nebo uložte si záložní kódy jelikož je znovu již neuvidíte. Pokud ztratíte přístup k vaší 2FA aplikace a záložním kódům nebudete mít možnost se přihlásit k vašemu účtu.",
+ "recovery_codes": "Záložní kódy.",
+ "warning_of_generate_new_codes": "Když vygenerujete nové záložní kódy, tak staré přestanou fungovat.",
+ "authentication_methods": "Autentizační metody",
+ "verify": {
+ "desc": "Pro zapnutí dvoufázové autentizace zadejte kód z vaší 2FA aplikace:"
+ }
+ },
+ "remove_backup": "Odstranit",
+ "email_language": "Jazyk pro přijímání emailů ze serveru",
+ "block_export": "Export blokací",
+ "block_import": "Import blokací",
+ "block_import_error": "Chyba při importování blokací",
+ "mute_export": "Exportovat ztlumení",
+ "mute_export_button": "Exportovat vaše ztlumení jako csv soubor",
+ "wordfilter": "Filtr slov",
+ "user_profiles": "Profily uživatelů",
+ "use_at_icon": "Zobrazovat {'@'} jako ikonu namísto textu",
+ "notification_visibility_moves": "Migrace uživatelů",
+ "hide_followers_count_description": "Nezobrazovat počet sledujících uživatelů",
+ "reply_visibility_self_short": "Zobrazit odpovědi pouze sobě",
+ "third_column_mode_notifications": "Sloupec oznámení",
+ "useStreamingApi": "Přijímat příspěvky a oznámení v reálném čase",
+ "use_websockets": "Používat websockety (Aktualizace v reálném čase)",
+ "user_mutes": "Uživatelé",
+ "mention_link_display": "Zobrazit odkazy na zmínky",
+ "add_language": "Přidat záložní jazyk",
+ "account_backup": "Zálohování účtu",
+ "account_alias": "Přezdívky účtu",
+ "setting_server_side": "Toto nastavení je vázané na váš profil a ovlivňuje všechny vaše sezení a klienty",
+ "block_export_button": "Exportovat vaše blokace jako csv soubor",
+ "blocks_imported": "Blokace importovány! Jejich zpracování může chvíli trvat.",
+ "mute_import": "Importovat ztlumení",
+ "mute_import_error": "Chyba při importování ztlumení",
+ "mutes_imported": "Ztlumení importovány! Jejich zpracování může chvíli trvat.",
+ "account_backup_table_head": "Záloha",
+ "download_backup": "Stáhnout",
+ "import_mutes_from_a_csv_file": "Importovat ztlumení z csv souboru",
+ "account_backup_description": "Toto umožňuje stáhnout archiv vašeho účtu a vašich příspěvků, ale nemůžou být zpětně importovány do Pleroma účtu.",
+ "backup_not_ready": "Tato záloha není zatím připravená.",
+ "list_backups_error": "Chyba při získávání seznamu záloh: {error}",
+ "add_backup": "Vytvořit novou zálohu",
+ "bot": "Toto je účet robota",
+ "change_email": "Změnit email",
+ "change_email_error": "Nastala chyba při změně vašeho emailu.",
+ "confirm_dialogs": "Požádat o potvrzení při",
+ "confirm_dialogs_mute": "ztlumení uživatele",
+ "confirm_dialogs_logout": "odhlašování",
+ "confirm_dialogs_approve_follow": "schvalování sledujícího",
+ "confirm_dialogs_deny_follow": "odmítání sledujícího",
+ "confirm_dialogs_remove_follower": "odstraňování sledujícího",
+ "mutes_and_blocks": "Ztlumení a Blokace",
+ "account_alias_table_head": "Přezdívka",
+ "move_account": "Přesunout účet",
+ "birthday": {
+ "show_birthday": "Zobrazit moje datum narození",
+ "label": "Datum narození"
+ },
+ "account_privacy": "Soukromí",
+ "notification_visibility_in_column": "Zobrazit ve sloupci/zásuvce oznámení",
+ "notification_visibility_reports": "Nahlášení",
+ "notification_visibility_emoji_reactions": "Reakce",
+ "notification_visibility_polls": "Konce anket, ve kterých jste hlasovali",
+ "notification_extra_tip": "Zobrazení tipu přizpůsobení pro další oznámení",
+ "notification_visibility_native_notifications": "Zobrazit nativní oznámení",
+ "notification_visibility_follow_requests": "Požadavky na sledování",
+ "mute_bot_posts": "Skrýt příspěvky od robotů",
+ "hide_bot_indication": "Skrýt indikaci účtů robotů v příspěvcích",
+ "auto_update": "Zobrazovat nové příspěvky automaticky",
+ "url": "URL",
+ "preview": "Náhled",
+ "profile_fields": {
+ "label": "Metadata profilu",
+ "name": "Štítek",
+ "value": "Obsah",
+ "add_field": "Přidat pole"
+ },
+ "hide_favorites_description": "Nezobrazovat seznam oblíbených příspěvků (uživatelé stále budou notifikování)",
+ "right_sidebar": "Prohodit pořadí sloupců",
+ "hide_scrobbles": "Skrýt scrobbles",
+ "hide_shoutbox": "Skrýt shoutbox instance",
+ "new_email": "Nový email",
+ "notification_show_extra": "Zobrazit další oznámení ve sloupci oznámení",
+ "reply_visibility_following_short": "Zobrazit odpovědi mým sledujícím",
+ "search_user_to_block": "Hledat koho chcete zablokovat",
+ "search_user_to_mute": "Hledat koho chcete ztlumit",
+ "reset_avatar_confirm": "Opravdu chcete resetovat avatar?",
+ "tree_advanced": "Umožnit více flexibilní navigaci ve stromovém zobrazení",
+ "conversation_display_linear_quick": "Lineární zobrazení",
+ "max_depth_in_thread": "Maximální počet zobrazených úrovní ve vlákně ve výchozím stavu",
+ "add_backup_error": "Chyba při přidávání nové zálohy: {error}",
+ "added_backup": "Přidána nová záloha.",
+ "word_filter_and_more": "Filtr slov a další...",
+ "posts": "Příspěvky",
+ "reset_banner_confirm": "Opravdu chcete resetovat banner?",
+ "reset_background_confirm": "Opravdu chcete resetovat pozadí?",
+ "reset_avatar": "Resetovat avatar",
+ "reset_profile_background": "Resetovat pozadí profilu",
+ "reset_profile_banner": "Resetovat banner profilu",
+ "type_domains_to_mute": "Hledat domény ke ztlumení",
+ "virtual_scrolling": "Optimalizovat vykreslování časové osy",
+ "remove_language": "Odstranit",
+ "expert_mode": "Zobrazit pokročilé nastavení",
+ "save": "Uložit změny",
+ "setting_changed": "Nastavení je rozdílné od výchozího",
+ "lists_navigation": "Zobrazovat seznamy v navigaci",
+ "allow_following_move": "Povolit automatické sledování pokud se sledovaný účet přesune",
+ "confirm_dialogs_repeat": "opakování příspěvku",
+ "confirm_dialogs_unfollow": "zrušení sledování uživatele",
+ "confirm_dialogs_block": "blokování uživatele",
+ "list_aliases_error": "Chyba při zjišťování přezdívek: {error}",
+ "remove_alias": "Odstranit tuto přezdívku",
+ "new_alias_target": "Přidat novou přezdívku (např. {example})",
+ "add_alias_error": "Chyba při přidávání přezdívky: {error}",
+ "hide_list_aliases_error_action": "Zavřít",
+ "move_account_notes": "Pokud chcete přesunut účet jinam, musíte jít na cílový účet a přidat přezdívku ukazující na tento účet.",
+ "move_account_target": "Cílový účet (např. {example})",
+ "moved_account": "Účet přesunut.",
+ "move_account_error": "Chyba při přesouvání účtu: {error}",
+ "discoverable": "Umožnit objevení tohoto účtu ve výsledcích vyhledávání a v jiných službách",
+ "pad_emoji": "Přidat mezeru okolo emoji při přidávání emoji z výběru",
+ "emoji_reactions_on_timeline": "Zobrazit emoji reakce u příspěvků",
+ "hide_media_previews": "Schovat náhledy médií",
+ "hide_muted_posts": "Skrýt příspěvky od ztlumených uživatelů",
+ "hide_all_muted_posts": "Skrýt ztlumené uživatele",
+ "navbar_column_stretch": "Protáhnout navbar na šířku sloupců",
+ "always_show_post_button": "Vždy zobrazovat plovoucí tlačítko pro nový příspěvek",
+ "hide_wallpaper": "Skrýt pozadí instance",
+ "hide_wordfiltered_statuses": "Skrýt slovně filtrované příspěvky",
+ "hide_muted_threads": "Skrýt ztlumené vlákna",
+ "notification_extra_chats": "Zobrazit nepřečtené chaty",
+ "notification_extra_announcements": "Zobrazit nepřečtené oznámení",
+ "notification_extra_follow_requests": "Zobrazit nové požadavky na sledování",
+ "hide_follows_count_description": "Nezobrazovat počet sledovaných uživatelů",
+ "autohide_floating_post_button": "Automaticky skrýt tlačítko nového příspěvku (mobilní zařízení)",
+ "minimal_scopes_mode": "Minimalizovat možnosti rozsahu příspěvků",
+ "conversation_display": "Styl zobrazení konverzací",
+ "conversation_display_tree": "Stromové zobrazení",
+ "conversation_display_tree_quick": "Stromový styl",
+ "show_scrollbars": "Zobrazit posuvníky bočních sloupců",
+ "third_column_mode": "Pokud je volné místo, zobrazit třetí sloupec obsahující",
+ "third_column_mode_none": "Nikdy nezobrazovat třetí sloupec",
+ "column_sizes": "Velikost sloupců",
+ "column_sizes_sidebar": "Postranní panel",
+ "column_sizes_content": "Obsah",
+ "column_sizes_notifs": "Oznámení",
+ "conversation_display_linear": "Lineární styl",
+ "conversation_other_replies_button": "Zobrazit tlačítko ostatních odpovědí",
+ "conversation_other_replies_button_below": "Pod příspěvky",
+ "conversation_other_replies_button_inside": "Uvnitř příspěvků",
+ "mention_link_display_short": "vždy jako zkrácená jména (např. {'@'}foo)",
+ "mention_link_display_full": "vždy jako celá jména (např. {'@'}foo{'@'}example.org)",
+ "enable_web_push_always_show_tip": "Některé prohlížeče (Chromium, Chrome) vyžadují aby push zprávy vždy vytvořili oznámení, jinak obecné oznámení 'Tento web byl aktualizován na pozadí' je zobrazeno, povolte abyste zabránili tomuto oznámení, protože Chrome nejspíš skrývá push oznámení, pokud je panel zobrazen. Může mít za následek duplicitní oznámení v ostatních prohlížečích.",
+ "actor_type": "Tento účet je:",
+ "actor_type_description": "Když svůj účet označíte jako skupinu, bude automaticky opakovat všechny příspěvky, které ho zmíní.",
+ "actor_type_Person": "normální uživatel",
+ "actor_type_Service": "bot",
+ "actor_type_Group": "skupina",
+ "hide_actor_type_indication": "Skrýt označení typu účtu (bot, skupina atd.) v příspěvcích",
+ "mention_link_show_avatar": "Zobrazit avatar uživatele vedle odkazu",
+ "mention_link_show_avatar_quick": "Zobrazit avatar uživatele vedle zmínky",
+ "mention_link_fade_domain": "Zeslabit doménu (např {'@'}example.org v {'@'}foo{'@'}example.org)",
+ "fun": "Zábava",
+ "notification_mutes": "Pokud nechcete dostávat oznámení od specifický uživatelů, použijte funkci ztlumení.",
+ "more_settings": "Víc nastavení",
+ "user_popover_avatar_action_zoom": "Zvětšit avatar",
+ "user_popover_avatar_action_close": "Zavřít popover",
+ "notification_setting_annoyance": "Nepříjemnost",
+ "user_popover_avatar_action_open": "Otevřit profil",
+ "notification_setting_drawer_marks_as_seen": "Zavření zásuvky (na mobilu) označí všechny oznámení jako přečtené",
+ "notification_setting_ignore_inactionable_seen": "Ignorovat stav přečtení pro oznámení bez akce (oblíbené, opakování atd.)",
+ "notification_setting_unseen_at_top": "Zobrazovat nepřečtené oznámení nad ostatními",
+ "enable_web_push_always_show": "Vždy zobrazovat web push oznámení",
+ "notification_setting_privacy": "Soukromí",
+ "notification_setting_block_from_strangers": "Blokovat oznámení od uživatelů které nesledujete",
+ "notification_setting_hide_notification_contents": "Schovávat odesílatele a obsah push oznámení",
+ "notification_blocks": "Blokování uživatele zastaví všechny notifikace a také je odhlásí.",
+ "mention_link_use_tooltip": "Zobrazit kartu uživatele při kliknutí na zmínku",
+ "user_popover_avatar_overlay": "Zobrazit popover uživatele přes jeho avatar",
+ "greentext": "Vtipné šipky",
+ "mention_link_bolden_you": "Zvýraznit vaši zmínku",
+ "user_popover_avatar_action": "Popover akce při kliknutí na avatar",
+ "show_yous": "Zobrazit (Vy)",
+ "notification_setting_filters": "Filtry",
+ "notification_setting_ignore_inactionable_seen_tip": "Toto ve skutečnosti neoznačí tyto oznámení jako přečtené a stále o nich budete dostávat oznámení na počítači, pokud si tak vyberete",
+ "notification_setting_filters_chrome_push": "V některých prohlížečích (Chrome) nemusí být možné kompletně vyfiltrovat oznámení, pokud přijdou jako push oznámení",
+ "commit_value": "Uložit",
+ "reset_value": "Resetovat",
+ "reset_value_tooltip": "Resetovat koncept",
+ "hard_reset_value": "Tvrdý reset",
+ "version": {
+ "title": "Verze",
+ "backend_version": "Backend verze",
+ "frontend_version": "Frontend verze"
+ },
+ "commit_value_tooltip": "Hodnota není uložena, stiskněte toto tlačítko pro potvrzení změn",
+ "hard_reset_value_tooltip": "Odstranit nastavení z úložiště a vynutit výchozí hodnotu",
+ "accent": "Akcentní barva"
},
"time": {
"day": "{0} day",
@@ -357,8 +749,8 @@
"hours": "{0} hours",
"hour_short": "{0}h",
"hours_short": "{0}h",
- "in_future": "in {0}",
- "in_past": "{0} ago",
+ "in_future": "za {0}",
+ "in_past": "před {0}",
"minute": "{0} minute",
"minutes": "{0} minutes",
"minute_short": "{0}min",
@@ -367,8 +759,8 @@
"months": "{0} měs",
"month_short": "{0} měs",
"months_short": "{0} měs",
- "now": "teď",
- "now_short": "teď",
+ "now": "právě teď",
+ "now_short": "nyní",
"second": "{0} second",
"seconds": "{0} seconds",
"second_short": "{0}s",
@@ -380,7 +772,23 @@
"year": "{0} r",
"years": "{0} l",
"year_short": "{0}r",
- "years_short": "{0}l"
+ "years_short": "{0}l",
+ "unit": {
+ "seconds_short": "{0}s",
+ "days": "{0} den | {0} dnů",
+ "days_short": "{0}d",
+ "hours": "{0} hodina | {0} hodin",
+ "hours_short": "{0}h",
+ "minutes": "{0} minuta | {0} minut",
+ "months": "{0} měsíc | {0} měsíců",
+ "months_short": "{0}mo",
+ "minutes_short": "{0}min",
+ "seconds": "{0} sekunda | {0} sekund",
+ "weeks": "{0} týden | {0} týdnů",
+ "weeks_short": "{0}w",
+ "years": "{0} rok | {0} roky",
+ "years_short": "{0}y"
+ }
},
"timeline": {
"collapse": "Zabalit",
@@ -392,11 +800,60 @@
"show_new": "Zobrazit nové",
"up_to_date": "Aktuální",
"no_more_statuses": "Žádné další příspěvky",
- "no_statuses": "Žádné příspěvky"
+ "no_statuses": "Žádné příspěvky",
+ "socket_reconnected": "Navázáno spojení v reálném čase",
+ "error": "Chyba při načítání časové osy: {0}",
+ "reload": "Načíst znovu",
+ "socket_broke": "Spojení v reálném čase ztraceno: CloseEvent code {0}"
},
"status": {
"reply_to": "Odpověď uživateli",
- "replies_list": "Odpovědi:"
+ "replies_list": "Odpovědi:",
+ "many_attachments": "Příspěvek má {number} příloh(u)",
+ "collapse_attachments": "Sbalit přílohy",
+ "unpin": "Odepnout z profilu",
+ "thread_muted": "Vlákno ztlumeno",
+ "show_attachment_description": "Popis náhledu (otevřete přílohu pro celý popis)",
+ "move_down": "Posunout přílohu doprava",
+ "thread_show": "Zobrazit toto vlákno",
+ "pin": "Připnout na profil",
+ "mute_conversation": "Ztlumit konverzaci",
+ "thread_hide": "Skrýt toto vlákno",
+ "show_full_subject": "Zobrazit celý předmět",
+ "edited_at": "(naposledy upraveno {time})",
+ "repeat_confirm_accept_button": "Zopakovat",
+ "repeat_confirm_title": "Potvrzení zopakování",
+ "delete_error": "Chyba při mazání příspěvku: {0}",
+ "delete_confirm": "Opravdu chcete smazat tento příspěvek?",
+ "delete_confirm_title": "Potvrzení smazání",
+ "delete_confirm_accept_button": "Smazat",
+ "delete_confirm_cancel_button": "Ponechat",
+ "you": "(Vy)",
+ "hide_attachment": "Skrýt přílohu",
+ "remove_attachment": "Odstranit přílohu",
+ "attachment_stop_flash": "Zastavit Flash player",
+ "nsfw": "NSFW",
+ "repeat_confirm_cancel_button": "Neopakovat",
+ "favorites": "Oblíbené",
+ "repeats": "Opakovaní",
+ "repeat_confirm": "Opravdu chcete zopakovat tento příspěvek?",
+ "delete": "Smazat příspěvek",
+ "copy_link": "Kopírovat odkaz k příspěvku",
+ "external_source": "Externí zdroj",
+ "edit": "Upravit příspěvek",
+ "bookmark": "Přidat do záložek",
+ "unbookmark": "Odebrat ze záložek",
+ "mentions": "Zmínky",
+ "hide_full_subject": "Skrýt celý předmět",
+ "show_content": "Zobrazit obsah",
+ "hide_content": "Skrýt obsah",
+ "unmute_conversation": "Zrušit ztlumení konverzace",
+ "status_unavailable": "Příspěvek je nedostupný",
+ "status_deleted": "Tento příspěvek byl smazán",
+ "expand": "Rozbalit",
+ "show_all_attachments": "Zobrazit všechny přílohy",
+ "move_up": "Posunout přílohu doleva",
+ "open_gallery": "Otevřít galerii"
},
"user_card": {
"approve": "Schválit",
@@ -406,7 +863,7 @@
"favorites": "Oblíbené",
"follow": "Sledovat",
"follow_sent": "Požadavek odeslán!",
- "follow_progress": "Odeslílám požadavek…",
+ "follow_progress": "Odesílám požadavek…",
"follow_unfollow": "Přestat sledovat",
"followees": "Sledovaní",
"followers": "Sledující",
@@ -455,5 +912,270 @@
"GiB": "GiB",
"TiB": "TiB"
}
+ },
+ "about": {
+ "mrf": {
+ "federation": "Federace",
+ "keyword": {
+ "ftl_removal": "Odstranění z časové osy \"Celá známá síť\"",
+ "reject": "Odmítnout",
+ "replace": "Nahradit",
+ "is_replaced_by": "→",
+ "keyword_policies": "Zásady klíčových slov"
+ },
+ "mrf_policies": "Povolené MRF zásady",
+ "simple": {
+ "instance": "Instance",
+ "reason": "Důvod",
+ "not_applicable": "N/A",
+ "accept": "Přijmout",
+ "accept_desc": "Tato instance přijímá zprávy pouze z následujících instancí:",
+ "reject": "Odmítnout",
+ "quarantine": "Karanténa",
+ "quarantine_desc": "Tato instance bude posílat pouze veřejné zprávy na tyto instance:",
+ "media_removal": "Odstranění médií",
+ "media_nsfw_desc": "Tato instance vynucuje média nastavené jako citlivé v příspěvcích z následujících instancí:",
+ "simple_policies": "Zásady specifické pro danou instanci",
+ "ftl_removal": "Odstranění z časové osy \"Celá známá síť\"",
+ "media_nsfw": "Vynutit média jako citlivé",
+ "reject_desc": "Tato instance nebude přijímat zprávy z následujících instancí:",
+ "media_removal_desc": "Tato instance odstraňuje média v příspěvcích z následujících instancí:",
+ "ftl_removal_desc": "Tato instance odstraňuje tyto instance z časové osy \"Celá známá síť\":"
+ },
+ "mrf_policies_desc": "Zásady MRF mění chování federace této instance. Následující MRF zásady jsou povoleny:"
+ },
+ "staff": "Personál"
+ },
+ "exporter": {
+ "processing": "Zpracovávám, zanedlouho budete vyzváni ke stažení vašeho souboru",
+ "export": "Exportovat"
+ },
+ "remote_user_resolver": {
+ "searching_for": "Hledám",
+ "error": "Nenalezeno."
+ },
+ "polls": {
+ "multiple_choices": "Výběr více možností",
+ "expiry": "Doba ankety",
+ "add_poll": "Přidat anketu",
+ "add_option": "Přidat možnost",
+ "single_choice": "Výběr jediné možnosti",
+ "option": "Možnost",
+ "votes": "hlasy",
+ "people_voted_count": "{count} hlasoval/a | {count} voličů",
+ "votes_count": "{count} hlasovat | {count} hlasů",
+ "vote": "Hlasovat",
+ "type": "Typ ankety",
+ "expires_in": "Anketa končí za {0}",
+ "expired": "Anketa skončila před {0}",
+ "not_enough_options": "Příliš málo jedinečných možností v anketě"
+ },
+ "interactions": {
+ "follows": "Nových sledujících",
+ "moves": "Uživatel migroval",
+ "load_older": "Načíst starší interakce",
+ "emoji_reactions": "Emoji reakce",
+ "reports": "Stížnosti",
+ "favs_repeats": "Opakované a oblíbené"
+ },
+ "emoji": {
+ "unicode_groups": {
+ "animals-and-nature": "Zvířata a příroda",
+ "flags": "Vlajky",
+ "activities": "Aktivity",
+ "people-and-body": "Lidé a těla",
+ "food-and-drink": "Jídlo a pití",
+ "objects": "Objekty",
+ "smileys-and-emotion": "Smajlíky a emoce",
+ "symbols": "Symboly",
+ "travel-and-places": "Cestování a místa"
+ },
+ "unicode": "Unicode emoji",
+ "load_all": "Načítání všech {emojiAmount} emoji",
+ "stickers": "Nálepky",
+ "emoji": "Emoji",
+ "keep_open": "Ponechat okno výběru otevřené",
+ "search_emoji": "Hledat emoji",
+ "add_emoji": "Vložit emoji",
+ "custom": "Vlastní emoji",
+ "load_all_hint": "Načteno prvních {saneAmount} emoji, načítání všech emoji může způsobit problémy s výkonem.",
+ "unpacked": "Rozbalené emoji",
+ "regional_indicator": "Regionální indikátor {letter}",
+ "hide_custom_emoji": "Skrýt vlastní emoji"
+ },
+ "importer": {
+ "submit": "Odeslat",
+ "success": "Úspěšně importováno.",
+ "error": "Nastala chyba při importování ze souboru."
+ },
+ "report": {
+ "reporter": "Nahlašující:",
+ "reported_user": "Nahlášený uživatel:",
+ "reported_statuses": "Nahlášené příspěvky:",
+ "notes": "Poznámky:",
+ "state": "Stav:",
+ "state_open": "Otevřeno",
+ "state_closed": "Uzavřeno",
+ "state_resolved": "Vyřešeno"
+ },
+ "announcements": {
+ "mark_as_read_action": "Označit jako přečtené",
+ "page_header": "Oznámení",
+ "title": "Oznámení",
+ "post_form_header": "Vyvěsit oznámení",
+ "post_placeholder": "Zde napište obsah vašeho oznámení…",
+ "post_action": "Odeslat",
+ "post_error": "Chyba: {error}",
+ "close_error": "Zavřít",
+ "delete_action": "Smazat",
+ "start_time_prompt": "Čas začátku: ",
+ "end_time_prompt": "Čas ukončení: ",
+ "all_day_prompt": "Toto je celodenní akce",
+ "published_time_display": "Zveřejněno v {time}",
+ "start_time_display": "Začíná v {time}",
+ "end_time_display": "Končí v {time}",
+ "edit_action": "Upravit",
+ "submit_edit_action": "Odeslat",
+ "cancel_edit_action": "Zrušit",
+ "inactive_message": "Toto oznámení není aktivní"
+ },
+ "shoutbox": {
+ "title": "Shoutbox"
+ },
+ "domain_mute_card": {
+ "mute": "Ztlumit",
+ "mute_progress": "Ztlumuji…",
+ "unmute": "Zrušit ztlumení",
+ "unmute_progress": "Ruším ztlumení…"
+ },
+ "errors": {
+ "storage_unavailable": "Pleroma nemohla získat přístup k úložišti prohlížeče. Vaše přihlášení nebo lokální nastavení se neuloží a můžete narazit na neočekávané problémy. Zkuste povolit soubory cookies."
+ },
+ "selectable_list": {
+ "select_all": "Vybrat vše"
+ },
+ "admin_dash": {
+ "window_title": "Administrace",
+ "commit_all": "Uložit vše",
+ "tabs": {
+ "nodb": "Žádné nastavení v databázi",
+ "frontends": "Frontendy",
+ "instance": "Instance",
+ "limits": "Limity",
+ "emoji": "Emoji"
+ },
+ "nodb": {
+ "heading": "Nastavení v databázi je vypnuto",
+ "documentation": "dokumentace",
+ "text2": "Většina konfiguračních možností nebude dostupná."
+ },
+ "wip_notice": "Tento administrační panel je experimentální a v aktivní vývoji, {adminFeLink}.",
+ "old_ui_link": "staré administrační rozhraní je dostupné zde",
+ "reset_all": "Resetovat vše",
+ "frontend": {
+ "failure_installing_frontend": "Nepodařilo se nainstalovat frontend {version}: {reason}",
+ "reinstall": "Přeinstalovat",
+ "available_frontends": "Dostupné k instalaci",
+ "is_default": "(Výchozí)",
+ "versions": "Dostupné verze",
+ "build_url": "URL sestavení",
+ "install": "Instalovat",
+ "install_version": "Instalovat verzi {version}",
+ "more_install_options": "Více instalačních možností",
+ "more_default_options": "Více výchozích nastavení pro možnosti",
+ "set_default": "Nastavit výchozí",
+ "default_frontend": "Výchozí frontend",
+ "set_default_version": "Nastavit verzi {version} jako výchozí",
+ "repository": "Odkaz k repozitáři",
+ "is_default_custom": "(Výchozí, verze: {version})",
+ "success_installing_frontend": "Frontend {version} byl úspěšně nainstalován"
+ },
+ "captcha": {
+ "native": "Nativní",
+ "kocaptcha": "KoCaptcha"
+ },
+ "instance": {
+ "instance": "Informace o instanci",
+ "captcha_header": "CAPTCHA",
+ "restrict": {
+ "activities": "Přístup k příspěvkům a aktivitám",
+ "timelines": "Přístup k časovým osám",
+ "profiles": "Přístup k uživatelským profilům",
+ "header": "Omezit přístup pro anonymní návštěvníky"
+ },
+ "registrations": "Registrace uživatelů",
+ "kocaptcha": "KoCaptcha nastavení"
+ },
+ "limits": {
+ "posts": "Limity příspěvků",
+ "uploads": "Limity příloh",
+ "users": "Limity uživatelských profilů",
+ "arbitrary_limits": "Libovolné limity",
+ "profile_fields": "Limity profilových polí",
+ "user_uploads": "Limity médií profilů"
+ },
+ "emoji": {
+ "global_actions": "Globální akce",
+ "reload": "Znovu načíst emoji",
+ "importFS": "Importovat emoji ze souborového systému",
+ "error": "Chyba: {0}",
+ "create_pack": "Vytvořit balíček",
+ "delete_pack": "Smazat balíček",
+ "new_pack_name": "Nový název balíčku",
+ "create": "Vytvořit",
+ "emoji_packs": "Emoji balíčky",
+ "remote_packs": "Vzdálené balíčky",
+ "do_list": "List",
+ "emoji_pack": "Emoji balíček",
+ "edit_pack": "Upravit balíček",
+ "description": "Popis",
+ "homepage": "Domovská stránka",
+ "fallback_src": "Záložní zdroj",
+ "fallback_sha256": "Záložní SHA256",
+ "share": "Sdílet",
+ "save": "Uložit",
+ "save_meta": "Uložit metadata",
+ "revert_meta": "Vrátit zpět metadata",
+ "delete": "Smazat",
+ "add_file": "Přidat soubor",
+ "adding_new": "Přidávání nových emoji",
+ "shortcode": "Zkratka",
+ "filename": "Jméno souboru",
+ "new_shortcode": "Zkrat, ponechte prázdné pro odvození",
+ "delete_confirm": "Opravdu chcete smazat {0}?",
+ "download_pack": "Stáhnout balíček",
+ "downloading_pack": "Stahování {0}",
+ "download": "Stáhnout",
+ "download_as_name": "Nové jméno",
+ "download_as_name_full": "Nové jméno, pro opakované použití ponechte prázdné",
+ "files": "Soubory",
+ "editing": "Upravování {0}",
+ "delete_title": "Smazat?",
+ "emoji_changed": "Neuložené změny emoji souborů, zkontrolujte zvýrazněné emoji",
+ "replace_warning": "Tímto se NAHRADÍ místní balíček se stejným jménem",
+ "metadata_changed": "Metadata jsou rozdílné od uložených",
+ "revert": "Vrátit zpět",
+ "new_filename": "Jméno souboru, ponechte prázdné pro odvození"
+ },
+ "temp_overrides": {
+ ":pleroma": {
+ ":instance": {
+ ":background_image": {
+ "label": "Obrázek na pozadí",
+ "description": "Obrázek na pozadí (především používáno PleromaFE)"
+ },
+ ":description_limit": {
+ "label": "Limit",
+ "description": "Limit počtu znaků pro popisy příloh"
+ },
+ ":public": {
+ "label": "Instance je veřejná"
+ },
+ ":limit_to_local_content": {
+ "label": "Limitovat vyhledávání pouze na místní obsah"
+ }
+ }
+ }
+ }
}
}
diff --git a/src/i18n/en.json b/src/i18n/en.json
@@ -32,6 +32,27 @@
},
"staff": "Staff"
},
+ "announcements": {
+ "page_header": "Announcements",
+ "title": "Announcement",
+ "mark_as_read_action": "Mark as read",
+ "post_form_header": "Post announcement",
+ "post_placeholder": "Type your announcement content here...",
+ "post_action": "Post",
+ "post_error": "Error: {error}",
+ "close_error": "Close",
+ "delete_action": "Delete",
+ "start_time_prompt": "Start time: ",
+ "end_time_prompt": "End time: ",
+ "all_day_prompt": "This is an all-day event",
+ "published_time_display": "Published at {time}",
+ "start_time_display": "Starts at {time}",
+ "end_time_display": "Ends at {time}",
+ "edit_action": "Edit",
+ "submit_edit_action": "Submit",
+ "cancel_edit_action": "Cancel",
+ "inactive_message": "This announcement is inactive"
+ },
"shoutbox": {
"title": "Shoutbox"
},
@@ -66,11 +87,13 @@
"more": "More",
"loading": "Loading…",
"generic_error": "An error occured",
+ "generic_error_message": "An error occured: {0}",
"error_retry": "Please try again",
"retry": "Try again",
"optional": "optional",
"show_more": "Show more",
"show_less": "Show less",
+ "never_show_again": "Never show again",
"dismiss": "Dismiss",
"cancel": "Cancel",
"disable": "Disable",
@@ -78,11 +101,17 @@
"confirm": "Confirm",
"verify": "Verify",
"close": "Close",
+ "undo": "Undo",
+ "yes": "Yes",
+ "no": "No",
"peek": "Peek",
+ "scroll_to_top": "Scroll to top",
"role": {
"admin": "Admin",
"moderator": "Moderator"
},
+ "unpin": "Unpin item",
+ "pin": "Pin item",
"flash_content": "Click to show Flash content using Ruffle (Experimental, may not work).",
"flash_security": "Note that this can be potentially dangerous since Flash content is still arbitrary code.",
"flash_fail": "Failed to load flash content, see console for details.",
@@ -108,6 +137,10 @@
"login": "Log in",
"description": "Log in with OAuth",
"logout": "Log out",
+ "logout_confirm_title": "Logout confirmation",
+ "logout_confirm": "Do you really want to logout?",
+ "logout_confirm_accept_button": "Logout",
+ "logout_confirm_cancel_button": "Do not logout",
"password": "Password",
"placeholder": "e.g. lain",
"register": "Register",
@@ -141,12 +174,25 @@
"home_timeline": "Home timeline",
"twkn": "Known Network",
"bookmarks": "Bookmarks",
+ "all_bookmarks": "All bookmarks",
+ "bookmark_folders": "Bookmark folders",
"user_search": "User Search",
"search": "Search",
+ "search_close": "Close search bar",
"who_to_follow": "Who to follow",
"preferences": "Preferences",
"timelines": "Timelines",
- "chats": "Chats"
+ "chats": "Chats",
+ "lists": "Lists",
+ "edit_nav_mobile": "Customize navigation bar",
+ "edit_pinned": "Edit pinned items",
+ "edit_finish": "Done editing",
+ "mobile_sidebar": "Toggle mobile sidebar",
+ "mobile_notifications": "Open notifications (there are unread ones)",
+ "mobile_notifications_close": "Close notifications",
+ "mobile_notifications_mark_as_seen": "Mark all as seen",
+ "announcements": "Announcements",
+ "quotes": "Quotes"
},
"notifications": {
"broken_favorite": "Unknown status, searching for it…",
@@ -161,7 +207,15 @@
"no_more_notifications": "No more notifications",
"migrated_to": "migrated to",
"reacted_with": "reacted with {0}",
- "poll_ended": "poll has ended"
+ "submitted_report": "submitted a report",
+ "poll_ended": "poll has ended",
+ "unread_announcements": "{num} unread announcement | {num} unread announcements",
+ "unread_chats": "{num} unread chat | {num} unread chats",
+ "unread_follow_requests": "{num} new follow request | {num} new follow requests",
+ "configuration_tip": "You can customize what to display here in {theSettings}. {dismiss}",
+ "configuration_tip_settings": "the settings",
+ "configuration_tip_dismiss": "Do not show again",
+ "subscribed_status": "posted"
},
"polls": {
"add_poll": "Add poll",
@@ -177,7 +231,9 @@
"expiry": "Poll age",
"expires_in": "Poll ends in {0}",
"expired": "Poll ended {0} ago",
- "not_enough_options": "Too few unique options in poll"
+ "not_enough_options": "Too few unique options in poll",
+ "non_anonymous": "Public poll",
+ "non_anonymous_title": "Other instances may display the options you voted for"
},
"emoji": {
"stickers": "Stickers",
@@ -186,9 +242,23 @@
"search_emoji": "Search for an emoji",
"add_emoji": "Insert emoji",
"custom": "Custom emoji",
+ "hide_custom_emoji": "Hide custom emojis",
+ "unpacked": "Unpacked emoji",
"unicode": "Unicode emoji",
+ "unicode_groups": {
+ "activities": "Activities",
+ "animals-and-nature": "Animals & Nature",
+ "flags": "Flags",
+ "food-and-drink": "Food & Drink",
+ "objects": "Objects",
+ "people-and-body": "People & Body",
+ "smileys-and-emotion": "Smileys & Emotion",
+ "symbols": "Symbols",
+ "travel-and-places": "Travel & Places"
+ },
"load_all_hint": "Loaded first {saneAmount} emoji, loading all emoji may cause performance issues.",
- "load_all": "Loading all {emojiAmount} emoji"
+ "load_all": "Loading all {emojiAmount} emoji",
+ "regional_indicator": "Regional indicator {letter}"
},
"errors": {
"storage_unavailable": "Pleroma could not access browser storage. Your login or your local settings won't be saved and you might encounter unexpected issues. Try enabling cookies."
@@ -196,11 +266,17 @@
"interactions": {
"favs_repeats": "Repeats and favorites",
"follows": "New follows",
+ "emoji_reactions": "Emoji Reactions",
+ "reports": "Reports",
"moves": "User migrates",
- "load_older": "Load older interactions"
+ "load_older": "Load older interactions",
+ "statuses": "Subscriptions"
},
"post_status": {
+ "edit_status": "Edit status",
"new_status": "Post new status",
+ "reply_option": "Reply to this status",
+ "quote_option": "Quote this status",
"account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
"account_not_locked_warning_link": "locked",
"attachments_sensitive": "Mark attachments as sensitive",
@@ -211,10 +287,13 @@
"text/markdown": "Markdown",
"text/bbcode": "BBCode"
},
+ "content_type_selection": "Post format",
"content_warning": "Subject (optional)",
"default": "Just landed in L.A.",
"direct_warning_to_all": "This post will be visible to all the mentioned users.",
"direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
+ "edit_remote_warning": "Other remote instances may not support editing and unable to receive the latest version of your post.",
+ "edit_unsupported_warning": "Pleroma does not support editing mentions or polls.",
"posting": "Posting",
"post": "Post",
"preview": "Preview",
@@ -226,6 +305,7 @@
"private": "This post will be visible to your followers only",
"unlisted": "This post will not be visible in Public Timeline and The Whole Known Network"
},
+ "scope_notice_dismiss": "Close this notice",
"scope": {
"direct": "Direct - post to mentioned users only",
"private": "Followers-only - post to followers only",
@@ -234,8 +314,9 @@
}
},
"registration": {
- "bio": "Bio",
+ "bio_optional": "Bio (optional)",
"email": "Email",
+ "email_optional": "Email (optional)",
"fullname": "Display name",
"password_confirm": "Password confirmation",
"registration": "Registration",
@@ -254,19 +335,42 @@
"email_required": "cannot be left blank",
"password_required": "cannot be left blank",
"password_confirmation_required": "cannot be left blank",
- "password_confirmation_match": "should be the same as password"
+ "password_confirmation_match": "should be the same as password",
+ "birthday_required": "cannot be left blank",
+ "birthday_min_age": "must be on or before {date}"
},
- "email_language": "In which language do you want to receive emails from the server?"
+ "email_language": "In which language do you want to receive emails from the server?",
+ "birthday": "Birthday:",
+ "birthday_optional": "Birthday (optional):"
},
"remote_user_resolver": {
"remote_user_resolver": "Remote user resolver",
"searching_for": "Searching for",
"error": "Not found."
},
+ "report": {
+ "reporter": "Reporter:",
+ "reported_user": "Reported user:",
+ "reported_statuses": "Reported statuses:",
+ "notes": "Notes:",
+ "state": "State:",
+ "state_open": "Open",
+ "state_closed": "Closed",
+ "state_resolved": "Resolved"
+ },
"selectable_list": {
"select_all": "Select all"
},
"settings": {
+ "add_language": "Add fallback language",
+ "remove_language": "Remove",
+ "primary_language": "Primary language:",
+ "fallback_language": "Fallback language {index}:",
+ "actor_type": "This account is:",
+ "actor_type_description": "Marking your account as a group will make it automatically repeat statuses that mention it.",
+ "actor_type_Person": "a normal user",
+ "actor_type_Service": "a bot",
+ "actor_type_Group": "a group",
"app_name": "App name",
"expert_mode": "Show advanced",
"save": "Save changes",
@@ -276,6 +380,20 @@
"enter_current_password_to_confirm": "Enter your current password to confirm your identity",
"post_look_feel": "Posts Look & Feel",
"mention_links": "Mention links",
+ "appearance": "Appearance",
+ "confirm_new_setting": "Confirm new setting?",
+ "confirm_new_question": "Does this look ok? Setting will be reverted in 10 seconds.",
+ "revert": "Revert",
+ "confirm": "Confirm",
+ "text_size": "Text and interface size",
+ "text_size_tip": "Use {0} for absolute values, {1} will scale with browser default text size.",
+ "text_size_tip2": "Values other than {0} might break some things and themes",
+ "emoji_size": "Emoji size",
+ "navbar_size": "Top bar size",
+ "panel_header_size": "Panel header size",
+ "visual_tweaks": "Minor visual tweaks",
+ "theme_debug": "Show what background theme engine assumes when dealing with transparancy (DEBUG)",
+ "scale_and_layout": "Interface scale and layout",
"mfa": {
"otp": "OTP",
"setup_otp": "Setup OTP",
@@ -297,6 +415,15 @@
"desc": "To enable two-factor authentication, enter the code from your two-factor app:"
}
},
+ "units": {
+ "time": {
+ "m": "minutes",
+ "s": "seconds",
+ "h": "hours",
+ "d": "days"
+ }
+ },
+ "lists_navigation": "Show lists in navigation",
"allow_following_move": "Allow auto-follow when following account moves",
"attachmentRadius": "Attachments",
"attachments": "Attachments",
@@ -322,13 +449,14 @@
"account_backup_table_head": "Backup",
"download_backup": "Download",
"backup_not_ready": "This backup is not ready yet.",
+ "backup_running": "This backup is in progress, processed {number} record. | This backup is in progress, processed {number} records.",
+ "backup_failed": "This backup has failed.",
"remove_backup": "Remove",
"list_backups_error": "Error fetching backup list: {error}",
"add_backup": "Create a new backup",
"added_backup": "Added a new backup.",
"add_backup_error": "Error adding a new backup: {error}",
"blocks_tab": "Blocks",
- "bot": "This is a bot account",
"btnRadius": "Buttons",
"cBlue": "Blue (Reply, follow)",
"cGreen": "Green (Retweet)",
@@ -345,6 +473,16 @@
"composing": "Composing",
"confirm_new_password": "Confirm new password",
"current_password": "Current password",
+ "confirm_dialogs": "Ask for confirmation when",
+ "confirm_dialogs_repeat": "repeating a status",
+ "confirm_dialogs_unfollow": "unfollowing a user",
+ "confirm_dialogs_block": "blocking a user",
+ "confirm_dialogs_mute": "muting a user",
+ "confirm_dialogs_delete": "deleting a status",
+ "confirm_dialogs_logout": "logging out",
+ "confirm_dialogs_approve_follow": "approving a follower",
+ "confirm_dialogs_deny_follow": "denying a follower",
+ "confirm_dialogs_remove_follower": "removing a follower",
"mutes_and_blocks": "Mutes and Blocks",
"data_import_export_tab": "Data import / export",
"default_vis": "Default visibility scope",
@@ -369,12 +507,16 @@
"domain_mutes": "Domains",
"avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
"pad_emoji": "Pad emoji with spaces when adding from picker",
+ "autocomplete_select_first": "Automatically select the first candidate when autocomplete results are available",
"emoji_reactions_on_timeline": "Show emoji reactions on timeline",
+ "emoji_reactions_scale": "Reactions scale factor",
+ "absolute_time_format": "Use absolute time format",
+ "absolute_time_format_min_age": "Only use for time older than this amount of time",
"export_theme": "Save preset",
"filtering": "Filtering",
"wordfilter": "Wordfilter",
"filtering_explanation": "All statuses containing these words will be muted, one per line",
- "word_filter": "Word filter",
+ "word_filter_and_more": "Word filter and more...",
"follow_export": "Follow export",
"follow_export_button": "Export your follows to a csv file",
"follow_import": "Follow import",
@@ -388,12 +530,16 @@
"hide_media_previews": "Hide media previews",
"hide_muted_posts": "Hide posts of muted users",
"mute_bot_posts": "Mute bot posts",
- "hide_bot_indication": "Hide bot indication in posts",
+ "hide_actor_type_indication": "Hide actor type (bots, groups, etc.) indication in posts",
+ "hide_scrobbles": "Hide scrobbles",
+ "hide_scrobbles_after": "Hide scrobbles older than",
+ "mute_sensitive_posts": "Mute sensitive posts",
"hide_all_muted_posts": "Hide muted posts",
"max_thumbnails": "Maximum amount of thumbnails per post (empty = no limit)",
"hide_isp": "Hide instance-specific panel",
"hide_shoutbox": "Hide instance shoutbox",
"right_sidebar": "Reverse order of columns",
+ "navbar_column_stretch": "Stretch navbar to columns width",
"always_show_post_button": "Always show floating New Post button",
"hide_wallpaper": "Hide instance wallpaper",
"preload_images": "Preload images",
@@ -420,6 +566,8 @@
"loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
"mutes_tab": "Mutes",
"play_videos_in_modal": "Play videos in a popup frame",
+ "url": "URL",
+ "preview": "Preview",
"file_export_import": {
"backup_restore": "Settings backup",
"backup_settings": "Backup settings to file",
@@ -438,6 +586,10 @@
"name": "Label",
"value": "Content"
},
+ "birthday": {
+ "label": "Birthday",
+ "show_birthday": "Show my birthday"
+ },
"account_privacy": "Privacy",
"use_contain_fit": "Don't crop the attachment in thumbnails",
"name": "Name",
@@ -447,13 +599,23 @@
"posts": "Posts",
"user_profiles": "User Profiles",
"notification_visibility": "Types of notifications to show",
+ "notification_visibility_in_column": "Show in notifications column/drawer",
+ "notification_visibility_native_notifications": "Show a native notification",
"notification_visibility_follows": "Follows",
+ "notification_visibility_follow_requests": "Follow requests",
"notification_visibility_likes": "Favorites",
"notification_visibility_mentions": "Mentions",
"notification_visibility_repeats": "Repeats",
+ "notification_visibility_reports": "Reports",
"notification_visibility_moves": "User Migrates",
"notification_visibility_emoji_reactions": "Reactions",
"notification_visibility_polls": "Ends of polls you voted in",
+ "notification_visibility_statuses": "Subscriptions",
+ "notification_show_extra": "Show extra notifications in the notifications column",
+ "notification_extra_chats": "Show unread chats",
+ "notification_extra_announcements": "Show unread announcements",
+ "notification_extra_follow_requests": "Show new follow requests",
+ "notification_extra_tip": "Show the customization tip for extra notifications",
"no_rich_text_description": "Strip rich text formatting from all posts",
"no_blocks": "No blocks",
"no_mutes": "No mutes",
@@ -506,17 +668,25 @@
"subject_line_email": "Like email: \"re: subject\"",
"subject_line_mastodon": "Like mastodon: copy as is",
"subject_line_noop": "Do not copy",
+ "force_theme_recompilation_debug": "Disable theme cahe, force recompile on each boot (DEBUG)",
"conversation_display": "Conversation display style",
"conversation_display_tree": "Tree-style",
+ "conversation_display_tree_quick": "Tree view",
"disable_sticky_headers": "Don't stick column headers to top of the screen",
"show_scrollbars": "Show side column's scrollbars",
"third_column_mode": "When there's enough space, show third column containing",
"third_column_mode_none": "Don't show third column at all",
"third_column_mode_notifications": "Notifications column",
"third_column_mode_postform": "Main post form and navigation",
+ "columns": "Columns",
+ "column_sizes": "Column sizes",
+ "column_sizes_sidebar": "Sidebar",
+ "column_sizes_content": "Content",
+ "column_sizes_notifs": "Notifications",
"tree_advanced": "Allow more flexible navigation in tree view",
"tree_fade_ancestors": "Display ancestors of the current status in faint text",
"conversation_display_linear": "Linear-style",
+ "conversation_display_linear_quick": "Linear view",
"conversation_other_replies_button": "Show the \"other replies\" button",
"conversation_other_replies_button_below": "Below statuses",
"conversation_other_replies_button_inside": "Inside statuses",
@@ -525,10 +695,13 @@
"sensitive_by_default": "Mark posts as sensitive by default",
"stop_gifs": "Pause animated images until you hover on them",
"streaming": "Automatically show new posts when scrolled to the top",
+ "auto_update": "Show new posts automatically",
"user_mutes": "Users",
"useStreamingApi": "Receive posts and notifications real-time",
+ "use_websockets": "Use websockets (Realtime updates)",
"text": "Text",
"theme": "Theme",
+ "theme_old": "Theme editor (old)",
"theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
"theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
"theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
@@ -546,23 +719,143 @@
"mention_link_display_short": "always as short names (e.g. {'@'}foo)",
"mention_link_display_full_for_remote": "as full names only for remote users (e.g. {'@'}foo{'@'}example.org)",
"mention_link_display_full": "always as full names (e.g. {'@'}foo{'@'}example.org)",
- "mention_link_show_tooltip": "Show full user names as tooltip for remote users",
+ "mention_link_use_tooltip": "Show user card when clicking mention links",
"mention_link_show_avatar": "Show user avatar beside the link",
+ "mention_link_show_avatar_quick": "Show user avatar next to mentions",
"mention_link_fade_domain": "Fade domains (e.g. {'@'}example.org in {'@'}foo{'@'}example.org)",
"mention_link_bolden_you": "Highlight mention of you when you are mentioned",
+ "user_popover_avatar_action": "Popover avatar click action",
+ "user_popover_avatar_action_zoom": "Zoom the avatar",
+ "user_popover_avatar_action_close": "Close the popover",
+ "user_popover_avatar_action_open": "Open profile",
+ "user_popover_avatar_overlay": "Show user popover over user avatar",
"fun": "Fun",
"greentext": "Meme arrows",
"show_yous": "Show (You)s",
"notifications": "Notifications",
+ "notification_setting_annoyance": "Annoyance",
+ "notification_setting_drawer_marks_as_seen": "Closing drawer (mobile) marks all notifications as read",
+ "notification_setting_ignore_inactionable_seen": "Ignore read state of inactionable notifications (likes, repeats etc)",
+ "notification_setting_ignore_inactionable_seen_tip": "This will not actually mark those notifications as read, and you'll still get desktop notifications about them if you chose so",
+ "notification_setting_unseen_at_top": "Show unread notifications above others",
"notification_setting_filters": "Filters",
+ "notification_setting_filters_chrome_push": "On some browsers (chrome) it might be impossible to completely filter out notifications by type when they arrive by Push",
"notification_setting_block_from_strangers": "Block notifications from users who you do not follow",
"notification_setting_privacy": "Privacy",
"notification_setting_hide_notification_contents": "Hide the sender and contents of push notifications",
"notification_mutes": "To stop receiving notifications from a specific user, use a mute.",
"notification_blocks": "Blocking a user stops all notifications as well as unsubscribes them.",
"enable_web_push_notifications": "Enable web push notifications",
+ "enable_web_push_always_show": "Always show web push notifications",
+ "enable_web_push_always_show_tip": "Some browsers (Chromium, Chrome) require that push messages always result in a notification, otherwise generic 'Website was updated in background' is shown, enable this to prevent this notification from showing, as Chrome seem to hide push notifications if tab is in focus. Can result in showing duplicate notifications on other browsers.",
"more_settings": "More settings",
"style": {
+ "custom_theme_used": "(Custom theme)",
+ "custom_style_used": "(Custom style)",
+ "stock_theme_used": "(Stock theme)",
+ "themes2_outdated": "Editor for Themes V2 is being phased out and will eventually be replaced with a new one that takes advantage of new Themes V3 engine. It should still work but experience might be degraded and inconsistent.",
+ "appearance_tab_note": "Changes on this tab do not affect the theme used, so exported theme will be different from what seen in the UI",
+ "update_preview": "Update preview",
+ "themes3": {
+ "define": "Override",
+ "palette": {
+ "label": "Color schemes",
+ "name_label": "Color scheme name",
+ "import": "Import palette",
+ "export": "Export palette",
+ "apply": "Apply palette",
+ "bg": "Panel background",
+ "fg": "Buttons etc.",
+ "text": "Text",
+ "link": "Links",
+ "accent": "Accent color",
+ "cRed": "Red color",
+ "cBlue": "Blue color",
+ "cGreen": "Green color",
+ "cOrange": "Orange color",
+ "wallpaper": "Wallpaper",
+ "v2_unsupported": "Older v2 themes don't support palettes. Switch to v3 theme to make use of palettes",
+ "bundled": "Bundled palettes",
+ "style": "Palettes provided by selected style",
+ "user": "Custom palette",
+ "imported": "Imported"
+ },
+ "editor": {
+ "title": "Style editor",
+ "reset_style": "Reset",
+ "load_style": "Open from file",
+ "save_style": "Save",
+ "style_name": "Stylesheet name",
+ "style_author": "Made by",
+ "style_license": "License",
+ "style_website": "Website",
+ "component_selector": "Component",
+ "variant_selector": "Variant",
+ "states_selector": "States",
+ "main_tab": "Main",
+ "shadows_tab": "Shadows",
+ "background": "Background color",
+ "text_color": "Text color",
+ "icon_color": "Icon color",
+ "link_color": "Link color",
+ "contrast": "Text contrast",
+ "roundness": "Roundness",
+ "opacity": "Opacity",
+ "border_color": "Border color",
+ "include_in_rule": "Add to rule",
+ "test_string": "TEST",
+ "invalid": "Invalid",
+ "refresh_preview": "Refresh preview",
+ "apply_preview": "Apply",
+ "text_auto": {
+ "label": "Auto-contrast",
+ "no-preserve": "Black or White",
+ "preserve": "Keep color",
+ "no-auto": "Disabled"
+ },
+ "component_tab": "Components style",
+ "palette_tab": "Color schemes",
+ "variables_tab": "Variables (Advanced)",
+ "variables": {
+ "label": "Variables",
+ "name_label": "Name:",
+ "type_label": "Type:",
+ "type_shadow": "Shadow",
+ "type_color": "Color",
+ "type_generic": "Generic",
+ "virtual_color": "Variable color value"
+ }
+ },
+ "hacks": {
+ "underlay_overrides": "Change underlay",
+ "underlay_override_mode_none": "Theme default",
+ "underlay_override_mode_opaque": "Replace with solid color",
+ "underlay_override_mode_transparent": "Remove entirely (might break some themes)",
+ "force_interface_roundness": "Override interface roundness/sharpness",
+ "forced_roundness_mode_disabled": "Use theme defaults",
+ "forced_roundness_mode_sharp": "Force sharp edges",
+ "forced_roundness_mode_nonsharp": "Force not-so-sharp (1px roundness) edges",
+ "forced_roundness_mode_round": "Force round edges"
+ },
+ "font": {
+ "group-builtin": "Browser default fonts",
+ "builtin" : {
+ "serif": "Serif",
+ "sans-serif": "Sans-serif",
+ "monospace": "Monospace",
+ "inherit": "Unchanged"
+ },
+ "group-local": "Locally installed fonts",
+ "local-unavailable1": "List of locally installed fonts unavailalbe",
+ "local-unavailable2": "Use manual entry to specify custom font",
+ "font_list_unavailable": "Couldn't get locally installed fonts: {error}",
+ "lookup_local_fonts": "Load list of fonts installed on this computer",
+ "enter_manually": "Enter font name family manually",
+ "entry": "Enter {fontFamily}",
+ "select": "Select font"
+ }
+ },
+ "interface_font_user_override": "Override theme/browser font used",
"switcher": {
"keep_color": "Keep colors",
"keep_shadows": "Keep shadows",
@@ -654,13 +947,24 @@
"component": "Component",
"override": "Override",
"shadow_id": "Shadow #{value}",
+ "offset": "Shadow offset",
+ "zoom": "Zoom",
+ "offset-x": "x:",
+ "offset-y": "y:",
+ "light_grid": "Use light checkerboard",
+ "color_override": "Use different color",
+ "name": "Name",
"blur": "Blur",
"spread": "Spread",
"inset": "Inset",
+ "raw": "Plain shadow",
+ "expression": "Expression (advanced)",
+ "empty_expression": "Empty expression",
"hintV3": "For shadows you can also use the {0} notation to use other color slot.",
"filter_hint": {
"always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.",
"drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.",
+ "avatar_inset_short": "Separate inset shadow",
"avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.",
"spread_zero": "Shadows with spread > 0 will appear as if it was set to zero",
"inset_classic": "Inset shadows will be using {0}"
@@ -686,7 +990,7 @@
"interface": "Interface",
"input": "Input fields",
"post": "Post text",
- "postCode": "Monospaced text in a post (rich text)"
+ "monospace": "Monospaced text"
},
"family": "Font name",
"size": "Size (in px)",
@@ -712,6 +1016,145 @@
"title": "Version",
"backend_version": "Backend version",
"frontend_version": "Frontend version"
+ },
+ "commit_value": "Save",
+ "commit_value_tooltip": "Value is not saved, press this button to commit your changes",
+ "reset_value": "Reset",
+ "reset_value_tooltip": "Reset draft",
+ "hard_reset_value": "Hard reset",
+ "hard_reset_value_tooltip": "Remove setting from storage, forcing use of default value"
+ },
+ "admin_dash": {
+ "window_title": "Administration",
+ "wip_notice": "This admin dashboard is experimental and WIP, {adminFeLink}.",
+ "old_ui_link": "old admin UI available here",
+ "reset_all": "Reset all",
+ "commit_all": "Save all",
+ "tabs": {
+ "nodb": "No DB Config",
+ "instance": "Instance",
+ "limits": "Limits",
+ "frontends": "Front-ends",
+ "emoji": "Emoji"
+ },
+ "nodb": {
+ "heading": "Database config is disabled",
+ "text": "You need to change backend config files so that {property} is set to {value}, see more in {documentation}.",
+ "documentation": "documentation",
+ "text2": "Most configuration options will be unavailable."
+ },
+ "captcha": {
+ "native": "Native",
+ "kocaptcha": "KoCaptcha"
+ },
+ "instance": {
+ "instance": "Instance information",
+ "registrations": "User sign-ups",
+ "captcha_header": "CAPTCHA",
+ "kocaptcha": "KoCaptcha settings",
+ "access": "Instance access",
+ "restrict": {
+ "header": "Restrict access for anonymous visitors",
+ "description": "Detailed setting for allowing/disallowing access to certain aspects of API. By default (indeterminate state) it will disallow if instance is not public, ticked checkbox means disallow access even if instance is public, unticked means allow access even if instance is private. Please note that unexpected behavior might happen if some settings are set, i.e. if profile access is disabled posts will show without profile information.",
+ "timelines": "Timelines access",
+ "profiles": "User profiles access",
+ "activities": "Statuses/activities access"
+ }
+ },
+ "limits": {
+ "arbitrary_limits": "Arbitrary limits",
+ "posts": "Post limits",
+ "uploads": "Attachments limits",
+ "users": "User profile limits",
+ "profile_fields": "Profile fields limits",
+ "user_uploads": "Profile media limits"
+ },
+ "frontend": {
+ "repository": "Repository link",
+ "versions": "Available versions",
+ "build_url": "Build URL",
+ "reinstall": "Reinstall",
+ "is_default": "(Default)",
+ "is_default_custom": "(Default, version: {version})",
+ "install": "Install",
+ "install_version": "Install version {version}",
+ "more_install_options": "More install options",
+ "more_default_options": "More default setting options",
+ "set_default": "Set default",
+ "set_default_version": "Set version {version} as default",
+ "wip_notice": "Please note that this section is a WIP and lacks certain features as backend implementation of front-end management is incomplete.",
+ "default_frontend": "Default frontend",
+ "default_frontend_tip": "Default frontend will be shown to all users. Currently there's no way to for a user to select personal frontend. If you switch away from PleromaFE you'll most likely have to use old and buggy AdminFE to do instance configuration until we replace it.",
+ "default_frontend_unavail": "Default frontend settings are not available, as this requires configuration in the database",
+ "available_frontends": "Available for install",
+ "failure_installing_frontend": "Failed to install frontend {version}: {reason}",
+ "success_installing_frontend": "Frontend {version} successfully installed"
+ },
+ "emoji": {
+ "global_actions": "Global actions",
+ "reload": "Reload emoji",
+ "importFS": "Import emoji from filesystem",
+ "error": "Error: {0}",
+ "create_pack": "Create pack",
+ "delete_pack": "Delete pack",
+ "new_pack_name": "New pack name",
+ "create": "Create",
+ "emoji_packs": "Emoji packs",
+ "remote_packs": "Remote packs",
+ "do_list": "List",
+ "remote_pack_instance": "Remote pack instance",
+ "emoji_pack": "Emoji pack",
+ "edit_pack": "Edit pack",
+ "description": "Description",
+ "homepage": "Homepage",
+ "fallback_src": "Fallback source",
+ "fallback_sha256": "Fallback SHA256",
+ "share": "Share",
+ "save": "Save",
+ "save_meta": "Save metadata",
+ "revert_meta": "Revert metadata",
+ "delete": "Delete",
+ "revert": "Revert",
+ "add_file": "Add file",
+ "adding_new": "Adding new emoji",
+ "shortcode": "Shortcode",
+ "filename": "Filename",
+ "new_shortcode": "Shortcode, leave blank to infer",
+ "new_filename": "Filename, leave blank to infer",
+ "delete_confirm": "Are you sure you want to delete {0}?",
+ "download_pack": "Download pack",
+ "downloading_pack": "Downloading {0}",
+ "download": "Download",
+ "download_as_name": "New name",
+ "download_as_name_full": "New name, leave blank to reuse",
+ "files": "Files",
+ "editing": "Editing {0}",
+ "delete_title": "Delete?",
+ "metadata_changed": "Metadata different from saved",
+ "emoji_changed": "Unsaved emoji file changes, check highlighted emoji",
+ "replace_warning": "This will REPLACE the local pack of the same name"
+ },
+ "temp_overrides": {
+ ":pleroma": {
+ ":instance": {
+ ":public": {
+ "label": "Instance is public",
+ "description": "Disabling this will make all API accessible only for logged-in users, this will make Public and Federated timelines inaccessible to anonymous visitors."
+ },
+ ":limit_to_local_content": {
+ "label": "Limit search to local content",
+ "description": "Disables global network search for unauthenticated (default), all users or none"
+ },
+ ":description_limit": {
+ "label": "Limit",
+ "description": "Character limit for attachment descriptions"
+ },
+ ":background_image": {
+ "label": "Background image",
+ "description": "Background image (primarily used by PleromaFE)"
+ }
+ }
+ }
}
},
"time": {
@@ -749,18 +1192,31 @@
"no_more_statuses": "No more statuses",
"no_statuses": "No statuses",
"socket_reconnected": "Realtime connection established",
- "socket_broke": "Realtime connection lost: CloseEvent code {0}"
+ "socket_broke": "Realtime connection lost: CloseEvent code {0}",
+ "quick_view_settings": "Quick view settings",
+ "quick_filter_settings": "Quick filter settings"
},
"status": {
"favorites": "Favorites",
"repeats": "Repeats",
+ "quotes": "Quotes",
+ "repeat_confirm": "Do you really want to repeat this status?",
+ "repeat_confirm_title": "Repeat confirmation",
+ "repeat_confirm_accept_button": "Repeat",
+ "repeat_confirm_cancel_button": "Do not repeat",
"delete": "Delete status",
+ "delete_error": "Error deleting status: {0}",
+ "edit": "Edit status",
+ "edited_at": "(last edited {time})",
"pin": "Pin on profile",
"unpin": "Unpin from profile",
"pinned": "Pinned",
"bookmark": "Bookmark",
"unbookmark": "Unbookmark",
"delete_confirm": "Do you really want to delete this status?",
+ "delete_confirm_title": "Delete confirmation",
+ "delete_confirm_accept_button": "Delete",
+ "delete_confirm_cancel_button": "Keep",
"reply_to": "Reply to",
"mentions": "Mentions",
"replies_list": "Replies:",
@@ -772,6 +1228,8 @@
"external_source": "External source",
"thread_muted": "Thread muted",
"thread_muted_and_words": ", has words:",
+ "sensitive_muted": "Muting sensitive content",
+ "bot_muted": "Muting bot content",
"show_full_subject": "Show full subject",
"hide_full_subject": "Hide full subject",
"show_content": "Show content",
@@ -802,14 +1260,34 @@
"ancestor_follow_with_icon": "{icon} {text}",
"show_all_conversation_with_icon": "{icon} {text}",
"show_all_conversation": "Show full conversation ({numStatus} other status) | Show full conversation ({numStatus} other statuses)",
- "show_only_conversation_under_this": "Only show replies to this status"
+ "show_only_conversation_under_this": "Only show replies to this status",
+ "status_history": "Status history",
+ "reaction_count_label": "{num} person reacted | {num} people reacted",
+ "hide_quote": "Hide the quoted status",
+ "display_quote": "Display the quoted status",
+ "invisible_quote": "Quoted status unavailable: {link}",
+ "more_actions": "More actions on this status",
+ "loading": "Loading...",
+ "load_error": "Unable to load status: {error}"
},
"user_card": {
"approve": "Approve",
+ "approve_confirm_title": "Approve confirmation",
+ "approve_confirm_accept_button": "Approve",
+ "approve_confirm_cancel_button": "Do not approve",
+ "approve_confirm": "Do you want to approve {user}'s follow request?",
"block": "Block",
"blocked": "Blocked!",
+ "block_confirm_title": "Block confirmation",
+ "block_confirm": "Do you really want to block {user}?",
+ "block_confirm_accept_button": "Block",
+ "block_confirm_cancel_button": "Do not block",
"deactivated": "Deactivated",
"deny": "Deny",
+ "deny_confirm_title": "Deny confirmation",
+ "deny_confirm_accept_button": "Deny",
+ "deny_confirm_cancel_button": "Do not deny",
+ "deny_confirm": "Do you want to deny {user}'s follow request?",
"edit_profile": "Edit profile",
"favorites": "Favorites",
"follow": "Follow",
@@ -817,6 +1295,10 @@
"follow_sent": "Request sent!",
"follow_progress": "Requesting…",
"follow_unfollow": "Unfollow",
+ "unfollow_confirm_title": "Unfollow confirmation",
+ "unfollow_confirm": "Do you really want to unfollow {user}?",
+ "unfollow_confirm_accept_button": "Unfollow",
+ "unfollow_confirm_cancel_button": "Do not unfollow",
"followees": "Following",
"followers": "Followers",
"following": "Following!",
@@ -828,8 +1310,18 @@
"message": "Message",
"mute": "Mute",
"muted": "Muted",
+ "mute_confirm_title": "Mute confirmation",
+ "mute_confirm": "Do you really want to mute {user}?",
+ "mute_confirm_accept_button": "Mute",
+ "mute_confirm_cancel_button": "Do not mute",
+ "mute_duration_prompt": "Mute this user for (0 for indefinite time):",
"per_day": "per day",
"remote_follow": "Remote follow",
+ "remove_follower": "Remove follower",
+ "remove_follower_confirm_title": "Remove follower confirmation",
+ "remove_follower_confirm_accept_button": "Remove",
+ "remove_follower_confirm_cancel_button": "Keep",
+ "remove_follower_confirm": "Do you really want to remove {user} from your followers?",
"report": "Report",
"statuses": "Statuses",
"subscribe": "Subscribe",
@@ -843,6 +1335,8 @@
"hide_repeats": "Hide repeats",
"show_repeats": "Show repeats",
"bot": "Bot",
+ "group": "Group",
+ "birthday": "Born {birthday}",
"admin_menu": {
"moderation": "Moderation",
"grant_admin": "Grant Admin",
@@ -867,7 +1361,12 @@
"solid": "Solid bg",
"striped": "Striped bg",
"side": "Side stripe"
- }
+ },
+ "note": "Note",
+ "note_blank": "(None)",
+ "edit_note": "Edit note",
+ "edit_note_apply": "Apply",
+ "edit_note_cancel": "Cancel"
},
"user_profile": {
"timeline_title": "User timeline",
@@ -896,7 +1395,10 @@
"user_settings": "User Settings",
"accept_follow_request": "Accept follow request",
"reject_follow_request": "Reject follow request",
- "bookmark": "Bookmark"
+ "bookmark": "Bookmark",
+ "toggle_expand": "Expand or collapse notification to show post in full",
+ "toggle_mute": "Expand or collapse notification to reveal muted content",
+ "autocomplete_available": "{number} result is available. Use up and down keys to navigate through them. | {number} results are available. Use up and down keys to navigate through them."
},
"upload": {
"error": {
@@ -918,7 +1420,9 @@
"hashtags": "Hashtags",
"person_talking": "{count} person talking",
"people_talking": "{count} people talking",
- "no_results": "No results"
+ "no_results": "No results",
+ "no_more_results": "No more results",
+ "load_more": "Load more results"
},
"password_reset": {
"forgot_password": "Forgot password?",
@@ -945,6 +1449,30 @@
"error_sending_message": "Something went wrong when sending the message.",
"empty_chat_list_placeholder": "You don't have any chats yet. Start a new chat!"
},
+ "bookmarks": {
+ "manage_bookmark_folders": "Manage bookmark folders"
+ },
+ "lists": {
+ "lists": "Lists",
+ "new": "New List",
+ "title": "List title",
+ "search": "Search users",
+ "create": "Create",
+ "save": "Save changes",
+ "delete": "Delete list",
+ "following_only": "Limit to Following",
+ "manage_lists": "Manage lists",
+ "manage_members": "Manage list members",
+ "add_members": "Search for more users",
+ "remove_from_list": "Remove from list",
+ "add_to_list": "Add to list",
+ "is_in_list": "Already in list",
+ "editing_list": "Editing list {listTitle}",
+ "creating_list": "Creating new list",
+ "update_title": "Save Title",
+ "really_delete": "Really delete list?",
+ "error": "Error manipulating lists: {0}"
+ },
"file_type": {
"audio": "Audio",
"video": "Video",
@@ -953,5 +1481,39 @@
},
"display_date": {
"today": "Today"
+ },
+ "update": {
+ "big_update_title": "Please bear with us",
+ "big_update_content": "We haven't had a release in a while, so things might look and feel different than what you're used to.",
+ "update_bugs": "Please report any issues and bugs on {pleromaGitlab}, as we have changed a lot, and although we test thoroughly and use development versions ourselves, we may have missed some things. We welcome your feedback and suggestions on issues you might encounter, or how to improve Pleroma and Pleroma-FE.",
+ "update_bugs_gitlab": "Pleroma GitLab",
+ "update_changelog": "For more details on what's changed, see {theFullChangelog}.",
+ "update_changelog_here": "the full changelog",
+ "art_by": "Art by {linkToArtist}"
+ },
+ "unicode_domain_indicator": {
+ "tooltip": "This domain contains non-ascii characters."
+ },
+ "splash": {
+ "loading": "Loading...",
+ "theme": "Applying theme, please wait warmly...",
+ "fun_1": "Drink more water",
+ "fun_2": "Take it easy!",
+ "fun_3": "Suya...",
+ "fun_4": "My Pleroma machine is full power!",
+ "error": "Something went wrong"
+ },
+ "bookmark_folders": {
+ "select_folder": "Select bookmark folder",
+ "creating_folder": "Creating bookmark folder",
+ "editing_folder": "Editing folder {folderName}",
+ "emoji": "Emoji",
+ "name": "Folder name",
+ "new": "New Folder",
+ "create": "Create folder",
+ "delete": "Delete folder",
+ "update_folder": "Save changes",
+ "really_delete": "Do you really want to delete the folder?",
+ "error": "Error manipulating bookmark folders: {0}"
}
}
diff --git a/src/i18n/eo.json b/src/i18n/eo.json
@@ -11,7 +11,8 @@
"title": "Funkcioj",
"who_to_follow": "Kiun aboni",
"pleroma_chat_messages": "Babilejo de Pleroma",
- "upload_limit": "Limo de alŝutoj"
+ "upload_limit": "Limo de alŝutoj",
+ "shout": "Kriujo"
},
"finder": {
"error_fetching_user": "Eraris alporto de uzanto",
@@ -42,7 +43,21 @@
},
"flash_content": "Klaku por montri enhavon de Flash per Ruffle. (Eksperimente, eble ne funkcios.)",
"flash_security": "Sciu, ke tio povas esti danĝera, ĉar la enhavo de Flash ja estas arbitra programo.",
- "flash_fail": "Malsukcesis enlegi enhavon de Flash; vidu detalojn en konzolo."
+ "flash_fail": "Malsukcesis enlegi enhavon de Flash; vidu detalojn en konzolo.",
+ "scope_in_timeline": {
+ "direct": "Persona",
+ "private": "Nur abonantoj",
+ "public": "Publika",
+ "unlisted": "Nelistigita"
+ },
+ "generic_error_message": "Eraris: {0}",
+ "never_show_again": "Neniam remontri",
+ "undo": "Malfari",
+ "yes": "Jes",
+ "no": "Ne",
+ "unpin": "Malfiksi",
+ "pin": "Fiksi",
+ "scroll_to_top": "Rulumi supren"
},
"image_cropper": {
"crop_picture": "Tondi bildon",
@@ -66,22 +81,28 @@
"recovery_code": "Rehava kodo",
"enter_two_factor_code": "Enigu kodon de duobla aŭtentikigo",
"enter_recovery_code": "Enigu rehavan kodon",
- "authentication_code": "Aŭtentikiga kodo"
+ "authentication_code": "Aŭtentikiga kodo",
+ "logout_confirm_title": "Konfirmo de adiaŭo",
+ "logout_confirm": "Ĉu vi certe volas adiaŭi?",
+ "logout_confirm_accept_button": "Adiaŭi",
+ "logout_confirm_cancel_button": "Ne adiaŭi"
},
"media_modal": {
"previous": "Antaŭa",
- "next": "Sekva"
+ "next": "Sekva",
+ "counter": "{current} / {total}",
+ "hide": "Fermi vidilon de vidaŭdaĵoj"
},
"nav": {
- "about": "Pri",
+ "about": "Prio",
"back": "Reen",
"chat": "Loka babilejo",
"friend_requests": "Petoj pri abono",
"mentions": "Mencioj",
"dms": "Rektaj mesaĝoj",
- "public_tl": "Publika historio",
+ "public_tl": "Noda historio",
"timeline": "Historio",
- "twkn": "Konata reto",
+ "twkn": "Federa historio",
"user_search": "Serĉi uzantojn",
"who_to_follow": "Kiun aboni",
"preferences": "Agordoj",
@@ -91,24 +112,43 @@
"administration": "Administrado",
"bookmarks": "Legosignoj",
"timelines": "Historioj",
- "home_timeline": "Hejma historio"
+ "home_timeline": "Hejma historio",
+ "edit_pinned": "Redakti fiksitajn erojn",
+ "lists": "Listoj",
+ "edit_nav_mobile": "Adapti navigan breton",
+ "edit_finish": "Fini redakton",
+ "mobile_notifications": "Malfermi sciigojn (estas nelegitaj)",
+ "mobile_notifications_close": "Fermi sciigojn",
+ "announcements": "Anoncoj",
+ "search_close": "Fermi serĉujon",
+ "mobile_sidebar": "(Mal)ŝalti flankan breton por telefonoj",
+ "mobile_notifications_mark_as_seen": "Marki ĉion vidita",
+ "quotes": "Citoj"
},
"notifications": {
- "broken_favorite": "Nekonata stato, serĉante ĝin…",
- "favorited_you": "ŝatis vian staton",
+ "broken_favorite": "Nekonata afiŝo, serĉante ĝin…",
+ "favorited_you": "ŝatis vian afiŝon",
"followed_you": "ekabonis vin",
"load_older": "Enlegi pli malnovajn sciigojn",
"notifications": "Sciigoj",
"read": "Legite!",
- "repeated_you": "ripetis vian staton",
+ "repeated_you": "ripetis vian afiŝon",
"no_more_notifications": "Neniuj pliaj sciigoj",
"reacted_with": "reagis per {0}",
"migrated_to": "migris al",
"follow_request": "volas vin aboni",
- "error": "Eraris akirado de sciigoj: {0}"
+ "error": "Eraris akirado de sciigoj: {0}",
+ "submitted_report": "sendis raporton",
+ "poll_ended": "enketo finiĝis",
+ "unread_chats": "{num} nelegita babilo | {num} nelegitaj babiloj",
+ "unread_follow_requests": "{num} nova abonpeto | {num} novaj abonpetoj",
+ "configuration_tip": "Vi povas ŝanĝi, kio montriĝos ĉi tie en {theSettings}. {dismiss}",
+ "configuration_tip_settings": "la agordoj",
+ "unread_announcements": "{num} nelegita anonco | {num} nelegitaj anoncoj",
+ "configuration_tip_dismiss": "Ne remontri plu"
},
"post_status": {
- "new_status": "Afiŝi novan staton",
+ "new_status": "Afiŝi",
"account_not_locked_warning": "Via konto ne estas {0}. Iu ajn povas vin aboni por vidi eĉ viajn afiŝoj nur por abonantoj.",
"account_not_locked_warning_link": "ŝlosita",
"attachments_sensitive": "Marki kunsendaĵojn konsternaj",
@@ -129,18 +169,25 @@
"unlisted": "Nelistigita – ne afiŝi al publikaj historioj"
},
"scope_notice": {
- "unlisted": "Ĉi tiu afiŝo ne estos videbla en la Publika historio kaj La tuta konata reto",
+ "unlisted": "Ĉi tiu afiŝo ne estos videbla en la Noda kaj la Federa historioj",
"private": "Ĉi tiu afiŝo estos videbla nur al viaj abonantoj",
"public": "Ĉi tiu afiŝo estos videbla al ĉiuj"
},
"media_description_error": "Malsukcesis afiŝo de vidaŭdaĵoj; reprovu",
- "empty_status_error": "Ne povas afiŝi malplenan staton sen dosieroj",
+ "empty_status_error": "Ne povas fari malplenan afiŝon sen dosieroj",
"preview_empty": "Malplena",
"preview": "Antaŭrigardo",
"direct_warning_to_first_only": "Ĉi tiu afiŝo estas nur videbla al uzantoj menciitaj je la komenco de la mesaĝo.",
"direct_warning_to_all": "Ĉi tiu afiŝo estos videbla al ĉiuj menciitaj uzantoj.",
"media_description": "Priskribo de vidaŭdaĵo",
- "post": "Afiŝo"
+ "post": "Afiŝi",
+ "edit_remote_warning": "Aliaj foraj nodoj eble ne subtenas redaktadon, kaj ne povos ricevi pli novan version de via afiŝo.",
+ "edit_unsupported_warning": "Pleroma ne subtenas redaktadon de mencioj aŭ enketoj.",
+ "edit_status": "Redakti afiŝon",
+ "content_type_selection": "Formo de afiŝo",
+ "scope_notice_dismiss": "Fermi ĉi tiun avizon",
+ "reply_option": "Respondi al ĉi tiu afiŝo",
+ "quote_option": "Citi ĉi tiun afiŝon"
},
"registration": {
"bio": "Priskribo",
@@ -160,11 +207,18 @@
"email_required": "ne povas resti malplena",
"password_required": "ne povas resti malplena",
"password_confirmation_required": "ne povas resti malplena",
- "password_confirmation_match": "samu la pasvorton"
+ "password_confirmation_match": "samu la pasvorton",
+ "birthday_min_age": "ne povas esti post {date}",
+ "birthday_required": "ne povas resti malplena"
},
"reason_placeholder": "Ĉi-node oni aprobas registriĝojn permane.\nSciigu la administrantojn kial vi volas registriĝi.",
"reason": "Kialo registriĝi",
- "register": "Registriĝi"
+ "register": "Registriĝi",
+ "bio_optional": "Prio (malnepra)",
+ "email_optional": "Retpoŝtadreso (malnepra)",
+ "email_language": "En kiu lingvo vi volus ricevi retleterojn de la servilo?",
+ "birthday": "Naskiĝtago:",
+ "birthday_optional": "Naskiĝtago (malnepra):"
},
"settings": {
"app_name": "Nomo de aplikaĵo",
@@ -199,7 +253,7 @@
"avatar_size_instruction": "La rekomendata minimuma grando de profilbildoj estas 150×150 bilderoj.",
"export_theme": "Konservi antaŭagordon",
"filtering": "Filtrado",
- "filtering_explanation": "Ĉiuj statoj kun tiuj ĉi vortoj silentiĝos; skribu po unu linie",
+ "filtering_explanation": "Ĉiuj afiŝoj kun tiuj ĉi vortoj silentiĝos; skribu po unu linie",
"follow_export": "Elporto de abonoj",
"follow_export_button": "Elporti viajn abonojn al CSV-dosiero",
"follow_export_processing": "Traktante; baldaŭ vi ricevos peton elŝuti la dosieron",
@@ -216,7 +270,7 @@
"use_one_click_nsfw": "Malfermi konsternajn kunsendaĵojn per nur unu klako",
"hide_post_stats": "Kaŝi statistikon de afiŝoj (ekz. nombron de ŝatoj)",
"hide_user_stats": "Kaŝi statistikon de uzantoj (ekz. nombron de abonantoj)",
- "hide_filtered_statuses": "Kaŝi filtritajn statojn",
+ "hide_filtered_statuses": "Kaŝi ĉiujn filtritajn afiŝojn",
"import_followers_from_a_csv_file": "Enporti abonojn el CSV-dosiero",
"import_theme": "Enlegi antaŭagordojn",
"inputRadius": "Enigaj kampoj",
@@ -249,7 +303,7 @@
"hide_followers_description": "Ne montri kiu min sekvas",
"show_admin_badge": "Montri la insignon de administranto en mia profilo",
"show_moderator_badge": "Montri la insignon de reguligisto en mia profilo",
- "nsfw_clickthrough": "Ŝalti traklakan kaŝadon de kunsendaĵoj kaj antaŭmontroj de ligiloj por konsternaj statoj",
+ "nsfw_clickthrough": "Ŝalti traklakan kaŝadon de kunsendaĵoj kaj antaŭmontroj de ligiloj por konsternaj afiŝoj",
"oauth_tokens": "Pecoj de OAuth",
"token": "Peco",
"refresh_token": "Aktualiga peco",
@@ -421,7 +475,8 @@
"interface": "Fasado",
"input": "Enigaj kampoj",
"post": "Teksto de afiŝo",
- "postCode": "Egallarĝa teksto en afiŝo (riĉteksto)"
+ "postCode": "Egallarĝa teksto en afiŝo (riĉteksto)",
+ "monospace": "Egallarĝa teksto"
},
"family": "Nomo de tiparo",
"size": "Grando (en bilderoj)",
@@ -441,6 +496,27 @@
"header_faint": "Tio estas en ordo",
"checkbox": "Mi legetis la kondiĉojn de uzado",
"link": "bela eta ligil’"
+ },
+ "custom_theme_used": "(Propra haŭto)",
+ "themes3": {
+ "hacks": {
+ "underlay_override_mode_transparent": "Tute forigi (povus rompi iujn haŭtojn)",
+ "forced_roundness_mode_disabled": "Uzi implicitajn valorojn de haŭto",
+ "forced_roundness_mode_sharp": "Devigi akrajn randojn",
+ "forced_roundness_mode_nonsharp": "Devigi ne tiom akrajn randojn (rondigo je 1 bildero)",
+ "forced_roundness_mode_round": "Devigi rondajn randojn"
+ },
+ "font": {
+ "builtin": {
+ "serif": "Kalkana",
+ "sans-serif": "Senkalkana",
+ "monospace": "Egallarĝa",
+ "inherit": "Senŝanĝe"
+ },
+ "group-local": "Loke instalitaj signoformoj",
+ "local-unavailable1": "Listo de loke instalitaj signoformoj ne estas disponebla",
+ "font_list_unavailable": "Ne povis akiri loke instalitajn signoformojn: {error}"
+ }
}
},
"discoverable": "Permesi trovon de ĉi tiu konto en serĉrezultoj kaj aliaj servoj",
@@ -556,19 +632,161 @@
"hide_shoutbox": "Kaŝi kriujon de nodo",
"always_show_post_button": "Ĉiam montri ŝvebantan butonon por nova afiŝo",
"mentions_new_style": "Pli mojosaj menciligiloj",
- "mentions_new_place": "Meti menciojn sur apartan linion"
+ "mentions_new_place": "Meti menciojn sur apartan linion",
+ "lists_navigation": "Montri listojn en navigiloj",
+ "account_backup": "Savkopio de konto",
+ "account_backup_description": "Ĉi tio povigas vin elŝuti arĥivon de viaj afiŝoj kaj ĉiuj informoj pri via konto, sed ili ne jam povas enportiĝi en konton de Pleroma.",
+ "list_aliases_error": "Eraris akirado de kromnomoj: {error}",
+ "move_account_notes": "Se vi volas movi la konton aliloken, vi devas iri al via celata konto, kaj aldoni kromnomon ligitan al tie ĉi.",
+ "navbar_column_stretch": "Etendi navigan breton laŭ larĝeco de kolumnoj",
+ "posts": "Afiŝoj",
+ "notification_visibility_polls": "Finoj de enketoj kun via voĉo",
+ "conversation_display": "Aspekto de interparoloj",
+ "disable_sticky_headers": "Ne alglui kapojn de kolumnoj al supro de la ekrano",
+ "conversation_display_linear_quick": "Linia vido",
+ "use_websockets": "Uzi teĥnikaron «websockets» (tuja ĝisdatigo)",
+ "mention_link_display_full_for_remote": "plene nur je uzantoj foraj (ekz. {'@'}zozo{'@'}ekzemplo.org)",
+ "expert_mode": "Montri altnivelajn",
+ "setting_server_side": "Ĉi tiu agordo estas ligita al via profilo, kaj efektiviĝos en ĉiuj viaj salutoj kaj klientoj",
+ "post_look_feel": "Aspekto de afiŝoj",
+ "mention_links": "Menciaj ligiloj",
+ "email_language": "Lingvo de leteroj ricevotaj de la servilo",
+ "account_backup_table_head": "Savkopio",
+ "download_backup": "Elŝuti",
+ "backup_not_ready": "Ĉi tiu savkopio ne jam pretas.",
+ "remove_backup": "Forigi",
+ "list_backups_error": "Eraris akirado de listo de savkopioj: {error}",
+ "add_backup": "Fari novan savkopion",
+ "added_backup": "Aldonis novan savkopion.",
+ "add_backup_error": "Eraris aldono de nova savkopio: {error}",
+ "account_alias": "Kromnomoj de konto",
+ "account_alias_table_head": "Kromnomo",
+ "hide_list_aliases_error_action": "Fermi",
+ "remove_alias": "Forigi ĉi tiun kromnomon",
+ "new_alias_target": "Aldoni novan kromnomon (ekz. {example})",
+ "added_alias": "Kromnomo estas aldonita.",
+ "add_alias_error": "Eraris aldono de kromnomo: {error}",
+ "move_account": "Movi konton",
+ "move_account_target": "Celata konto (ekz. {example})",
+ "moved_account": "Konto moviĝis.",
+ "move_account_error": "Eraris movado de konto: {error}",
+ "wordfilter": "Vortofiltrado",
+ "word_filter_and_more": "Vortofiltrado kaj pli…",
+ "mute_bot_posts": "Silentigi afiŝojn de robotoj",
+ "hide_bot_indication": "Kaŝi markon de roboteco en afiŝoj",
+ "hide_wordfiltered_statuses": "Kaŝi vorte filtritajn afiŝojn",
+ "hide_muted_threads": "Kaŝi silentigitajn fadenojn",
+ "account_privacy": "Privateco",
+ "user_profiles": "Profiloj de uzantoj",
+ "hide_favorites_description": "Ne montri liston de miaj ŝatatoj (oni tamen sciiĝas)",
+ "conversation_display_tree": "Arba stilo",
+ "conversation_display_tree_quick": "Arba vido",
+ "show_scrollbars": "Montri rulumskalojn de flankaj kolumnoj",
+ "third_column_mode_none": "Neniam montri trian kolumnon",
+ "third_column_mode_notifications": "Kolumno de sciigoj",
+ "columns": "Kolumnoj",
+ "column_sizes": "Grandeco de kolumnoj",
+ "column_sizes_sidebar": "Flanka breto",
+ "column_sizes_content": "Enhavo",
+ "column_sizes_notifs": "Sciigoj",
+ "tree_advanced": "Permesi pli flekseblan navigadon en arba vido",
+ "conversation_display_linear": "Linia stilo",
+ "conversation_other_replies_button": "Montri la butonon pri «aliaj respondoj»",
+ "conversation_other_replies_button_below": "Sub afiŝoj",
+ "conversation_other_replies_button_inside": "En afiŝoj",
+ "max_depth_in_thread": "Maksimuma nombro de niveloj implicite montrataj en fadeno",
+ "auto_update": "Montri novajn afiŝojn memage",
+ "use_at_icon": "Montri simbolon {'@'} kiel bildon anstataŭ teksto",
+ "mention_link_display": "Montri menciajn ligilojn",
+ "mention_link_display_short": "ĉiam mallonge (ekz. {'@'}zozo)",
+ "mention_link_display_full": "ĉiam plene (ekz. {'@'}zozo{'@'}ekzemplo.org)",
+ "mention_link_show_avatar": "Montri profilbildon de uzanto apud la ligilo",
+ "mention_link_show_avatar_quick": "Montri profilbildon de uzanto apud mencioj",
+ "mention_link_fade_domain": "Malvigligi retnomojn (ekz. {'@'}ekzemplo.org en {'@'}zozo{'@'}ekzemplo.org)",
+ "mention_link_bolden_you": "Emfazi vian mencion, se vi estas menciita",
+ "mention_link_use_tooltip": "Montri karton de uzanto per klako al mencia ligilo",
+ "user_popover_avatar_action_close": "Fermi la ŝprucaĵon",
+ "user_popover_avatar_action_open": "Malfermi la profilon",
+ "user_popover_avatar_overlay": "Aperigi ŝprucaĵon pri uzanto sur profilbildo",
+ "show_yous": "Montri la markon «(Vi)»",
+ "user_popover_avatar_action_zoom": "Zomi la profilbildon",
+ "third_column_mode": "Kun sufiĉo da spaco, montri trian kolumnon kun",
+ "birthday": {
+ "show_birthday": "Montri mian naskiĝtagon",
+ "label": "Naskiĝtago"
+ },
+ "confirm_dialogs_delete": "forigo de afiŝo",
+ "backup_running": "Ĉi tiu savkopiado progresas, traktis {number} datumon. | Ĉi tiu savkopiado progresas, traktis {number} datumojn.",
+ "backup_failed": "Ĉi tiu savkopiado malsukcesis.",
+ "autocomplete_select_first": "Memage elekti unuan kandidaton kiam rezultoj de memaga konjektado disponeblas",
+ "confirm_dialogs_logout": "adiaŭo",
+ "user_popover_avatar_action": "Post klako sur profilbildon en ŝprucaĵo",
+ "remove_language": "Forigi",
+ "primary_language": "Ĉefa lingvo:",
+ "confirm_dialogs": "Peti konfirmon je",
+ "confirm_dialogs_repeat": "ripeto de afiŝo",
+ "confirm_dialogs_unfollow": "malabono de uzanto",
+ "confirm_dialogs_block": "blokado de uzanto",
+ "confirm_dialogs_mute": "silentigo de uzanto",
+ "confirm_dialogs_approve_follow": "aprobo de abonanto",
+ "confirm_dialogs_deny_follow": "malaprobo de abonanto",
+ "confirm_dialogs_remove_follower": "forigo de abonanto",
+ "tree_fade_ancestors": "Montri responditojn de la nuna afiŝo per teksto malvigla",
+ "units": {
+ "time": {
+ "m": "minutoj",
+ "s": "sekundoj",
+ "h": "horoj",
+ "d": "tagoj"
+ }
+ },
+ "url": "URL",
+ "emoji_reactions_scale": "Grandeco de reagoj",
+ "actor_type_Person": "ordinara uzanto",
+ "actor_type": "Ĉi tiu konto estas:",
+ "actor_type_description": "Se vi markos vian konton grupo, ĝi memage ripetos afiŝojn, kiuj mencios ĝin.",
+ "actor_type_Service": "roboto",
+ "actor_type_Group": "grupo",
+ "hide_actor_type_indication": "Kaŝi specon de aganto (roboto, grupo, ktp.) en afiŝoj",
+ "commit_value_tooltip": "Valoro ne estas konservita; premu ĉi tiun butonon por konfirmi viajn ŝanĝojn",
+ "add_language": "Aldoni rezervan lingvon",
+ "commit_value": "Konservi",
+ "force_theme_recompilation_debug": "Malŝalti haŭtan kaŝmemoron, devigi retradukon post ĉiu enlego (POR ERARSERĈADO)",
+ "fallback_language": "Rezerva lingvo {index}:",
+ "notification_extra_follow_requests": "Montri novajn abonpetojn",
+ "notification_extra_tip": "Montri agordan konsileton por ekstraj sciigoj",
+ "notification_show_extra": "Montri ekstrajn sciigojn en la sciiga kolumno",
+ "notification_extra_chats": "Montri nelegitajn babilojn",
+ "notification_extra_announcements": "Montri nelegitajn anoncojn",
+ "notification_setting_annoyance": "Ĝeno",
+ "mute_sensitive_posts": "Silentigi konsternajn afiŝojn",
+ "preview": "Antaŭrigardo",
+ "notification_visibility_native_notifications": "Montri indiĝenan sciigon",
+ "notification_visibility_follow_requests": "Abonpetoj",
+ "notification_visibility_reports": "Raportoj",
+ "notification_setting_ignore_inactionable_seen": "Malatenti legitecon de nereageblaj sciigoj (ŝatoj, ripetoj, ktp.)",
+ "notification_setting_ignore_inactionable_seen_tip": "Ĉi tio ne markos la sciigojn legitaj, kaj vi ankoraŭ ricevos labortablajn sciigojn pri ili, se vi elektis ricevi tiujn",
+ "notification_setting_unseen_at_top": "Montri nelegitajn sciigojn super aliaj",
+ "appearance": "Aspekto",
+ "confirm_new_setting": "Ĉu konfirmi novan agordon?",
+ "confirm_new_question": "Ĉu tio ĉi aspektas ĝuste? La ŝanĝo malfariĝos post 10 sekundoj.",
+ "revert": "Malfari",
+ "confirm": "Konfirmi",
+ "text_size": "Grandeco de teksto kaj fasado",
+ "emoji_size": "Grandeco de bildosignoj",
+ "navbar_size": "Grandeco de supra breto"
},
"timeline": {
"collapse": "Maletendi",
"conversation": "Interparolo",
"error_fetching": "Eraris ĝisdatigo",
- "load_older": "Montri pli malnovajn statojn",
+ "load_older": "Montri pli malnovajn afiŝojn",
"no_retweet_hint": "Afiŝo estas markita kiel rekta aŭ nur por abonantoj, kaj ne eblas ĝin ripeti",
"repeated": "ripetis",
"show_new": "Montri novajn",
"up_to_date": "Ĝisdata",
- "no_more_statuses": "Neniuj pliaj statoj",
- "no_statuses": "Neniuj statoj",
+ "no_more_statuses": "Neniuj pliaj afiŝoj",
+ "no_statuses": "Neniuj afiŝoj",
"reload": "Enlegi ree",
"error": "Eraris akirado de historio: {0}",
"socket_reconnected": "Realtempa konekto fariĝis",
@@ -594,7 +812,7 @@
"muted": "Silentigita",
"per_day": "tage",
"remote_follow": "Fore aboni",
- "statuses": "Statoj",
+ "statuses": "Afiŝoj",
"unblock": "Malbloki",
"unblock_progress": "Malblokante…",
"block_progress": "Blokante…",
@@ -621,7 +839,8 @@
"grant_moderator": "Nomumi reguligiston",
"revoke_admin": "Malnomumi administranton",
"grant_admin": "Nomumi administranton",
- "moderation": "Reguligado"
+ "moderation": "Reguligado",
+ "delete_user_data_and_deactivate_confirmation": "Tio ĉi por ĉiam forigos datumojn de tiu ĉi konto, kaj malaktivigos ĝin. Ĉu vi plene certas?"
},
"show_repeats": "Montri ripetojn",
"hide_repeats": "Kaŝi ripetojn",
@@ -634,7 +853,41 @@
"solid": "Unueca fono",
"disabled": "Senemfaze"
},
- "edit_profile": "Redakti profilon"
+ "edit_profile": "Redakti profilon",
+ "deactivated": "Malaktiva",
+ "follow_cancel": "Nuligi peton",
+ "remove_follower": "Forigi abonanton",
+ "note": "Noto",
+ "note_blank": "(Neniu)",
+ "edit_note_apply": "Apliki",
+ "edit_note_cancel": "Nuligi",
+ "edit_note": "Redakti noton",
+ "block_confirm": "Ĉu vi certe volas bloki uzanton {user}?",
+ "block_confirm_accept_button": "Bloki",
+ "remove_follower_confirm": "Ĉu vi certe volas forigi uzanton {user} de viaj abonantoj?",
+ "approve_confirm_accept_button": "Aprobi",
+ "approve_confirm_cancel_button": "Ne aprobi",
+ "approve_confirm": "Ĉu vi certe volas aprobi abonan peton de {user}?",
+ "block_confirm_title": "Konfirmo de blokado",
+ "approve_confirm_title": "Konfirmo de aprobo",
+ "block_confirm_cancel_button": "Ne bloki",
+ "deny_confirm_accept_button": "Malaprobi",
+ "deny_confirm_cancel_button": "Ne malaprobi",
+ "mute_confirm_title": "Silentigi konfirmon",
+ "deny_confirm_title": "Konfirmo de malaprobo",
+ "mute_confirm": "Ĉu vi certe volas silentigi uzanton {user}?",
+ "mute_confirm_accept_button": "Silentigi",
+ "mute_confirm_cancel_button": "Ne silentigi",
+ "mute_duration_prompt": "Silentigi ĉi tiun uzanton por (0 signifas senliman silentigon):",
+ "remove_follower_confirm_accept_button": "Forigi",
+ "remove_follower_confirm_title": "Konfirmo de forigo de abonanto",
+ "birthday": "Naskita je {birthday}",
+ "deny_confirm": "Ĉu vi certe volas malaprobi abonan peton de {user}?",
+ "unfollow_confirm_cancel_button": "Ne malaboni",
+ "unfollow_confirm_title": "Konfirmo de malabono",
+ "unfollow_confirm": "Ĉu vi certe volas malaboni uzanton {user}?",
+ "unfollow_confirm_accept_button": "Malaboni",
+ "remove_follower_confirm_cancel_button": "Ne forigi"
},
"user_profile": {
"timeline_title": "Historio de uzanto",
@@ -654,7 +907,10 @@
"bookmark": "Legosigno",
"reject_follow_request": "Rifuzi abonpeton",
"accept_follow_request": "Akcepti abonpeton",
- "add_reaction": "Aldoni reagon"
+ "add_reaction": "Aldoni reagon",
+ "toggle_expand": "Etendi aŭ maletendi sciigon por montri plenan afiŝon",
+ "toggle_mute": "Etendi aŭ maletendi afiŝon por montri silentigitan enhavon",
+ "autocomplete_available": "{number} rezulto disponeblas. Uzu la sagajn klavojn supren kaj suben por foliumi ilin. | {number} rezulto disponeblas. Uzu la sagajn klavojn supren kaj suben por foliumi ilin."
},
"upload": {
"error": {
@@ -680,7 +936,21 @@
"load_all": "Enlegante ĉiujn {emojiAmount} bildosignojn",
"load_all_hint": "Enlegis la {saneAmount} unuajn bildosignojn; enlego de ĉiuj povus kaŭzi problemojn pri efikeco.",
"unicode": "Unikoda bildosigno",
- "custom": "Propra bildosigno"
+ "custom": "Propra bildosigno",
+ "unicode_groups": {
+ "activities": "Agado",
+ "animals-and-nature": "Bestoj kaj naturo",
+ "flags": "Flagoj",
+ "food-and-drink": "Manĝaĵoj kaj trinkaĵoj",
+ "objects": "Aĵoj",
+ "people-and-body": "Homoj kaj korpo",
+ "smileys-and-emotion": "Mienbildoj kaj sentoj",
+ "symbols": "Simboloj",
+ "travel-and-places": "Vojaĝoj kaj lokoj"
+ },
+ "regional_indicator": "Regiona marko {letter}",
+ "unpacked": "Malpakitaj bildosignoj",
+ "hide_custom_emoji": "Kaŝi proprajn bildosignojn"
},
"polls": {
"not_enough_options": "Tro malmultaj unikaj elektebloj en la enketo",
@@ -721,7 +991,7 @@
"media_nsfw": "Devige marki vidaŭdaĵojn konsternaj",
"media_removal_desc": "Ĉi tiu nodo forigas vidaŭdaĵojn de afiŝoj el la jenaj nodoj:",
"media_removal": "Forigo de vidaŭdaĵoj",
- "ftl_removal": "Forigo el la historio de «Konata reto»",
+ "ftl_removal": "Forigo el la «Federa historio»",
"quarantine_desc": "Ĉi tiu nodo sendos nur publikajn afiŝojn al la jenaj nodoj:",
"quarantine": "Kvaranteno",
"reject_desc": "Ĉi tiu nodo ne akceptos mesaĝojn de la jenaj nodoj:",
@@ -729,14 +999,16 @@
"accept_desc": "Ĉi tiu nodo nur akceptas mesaĝojn de la jenaj nodoj:",
"accept": "Akcepti",
"simple_policies": "Specialaj politikoj de la nodo",
- "ftl_removal_desc": "Ĉi tiu nodo forigas la jenajn nodojn el la historio de «Konata reto»:"
+ "ftl_removal_desc": "Ĉi tiu nodo forigas la jenajn nodojn el la «Federa historio»:",
+ "instance": "Nodo",
+ "reason": "Kialo"
},
"mrf_policies": "Ŝaltis politikon de Mesaĝa ŝanĝilaro (MRF)",
"keyword": {
"is_replaced_by": "→",
"replace": "Anstataŭigi",
"reject": "Rifuzi",
- "ftl_removal": "Forigo el la historio de «La tuta konata reto»",
+ "ftl_removal": "Forigo el la historio de la «Federa historio»",
"keyword_policies": "Politiko pri ĉefvortoj"
},
"federation": "Federado",
@@ -755,7 +1027,10 @@
"load_older": "Enlegi pli malnovajn interagojn",
"moves": "Migrado de uzantoj",
"follows": "Novaj abonoj",
- "favs_repeats": "Ripetoj kaj ŝatoj"
+ "favs_repeats": "Ripetoj kaj ŝatoj",
+ "emoji_reactions": "Bildosignaj reagoj",
+ "reports": "Raportoj",
+ "statuses": "Abonoj"
},
"errors": {
"storage_unavailable": "Pleroma ne povis aliri deponejon de la foliumilo. Via saluto kaj viaj lokaj agordoj ne estos konservitaj, kaj vi eble renkontos neatenditajn problemojn. Provu permesi kuketojn."
@@ -767,19 +1042,19 @@
"show_full_subject": "Montri plenan temon",
"thread_muted_and_words": ", enhavas vortojn:",
"thread_muted": "Fadeno silentigita",
- "copy_link": "Kopii ligilon al stato",
- "status_unavailable": "Stato ne estas disponebla",
+ "copy_link": "Kopii ligilon al afiŝo",
+ "status_unavailable": "Afiŝo ne estas disponebla",
"unmute_conversation": "Malsilentigi interparolon",
"mute_conversation": "Silentigi interparolon",
"replies_list": "Respondoj:",
"reply_to": "Responde al",
- "delete_confirm": "Ĉu vi certe volas forigi ĉi tiun staton?",
+ "delete_confirm": "Ĉu vi certe volas forigi ĉi tiun afiŝon?",
"unbookmark": "Senlegosigni",
"bookmark": "Legosigni",
"pinned": "Fiksita",
"unpin": "Malfiksi de profilo",
"pin": "Fiksi al profilo",
- "delete": "Forigi staton",
+ "delete": "Forigi afiŝon",
"repeats": "Ripetoj",
"favorites": "Ŝatoj",
"status_deleted": "Ĉi tiu afiŝo foriĝis",
@@ -788,7 +1063,46 @@
"external_source": "Ekstera fonto",
"mentions": "Mencioj",
"you": "(Vi)",
- "plus_more": "+{number} pli"
+ "plus_more": "+{number} pli",
+ "show_all_attachments": "Montri ĉiujn kunsendaĵojn",
+ "collapse_attachments": "Kaŝi iujn kunsendaĵojn",
+ "many_attachments": "Afiŝo havas {number} kunsendaĵo(j)n",
+ "show_attachment_in_modal": "Montri en vidilo de vidaŭdaĵoj",
+ "edit": "Redakti afiŝon",
+ "replies_list_with_others": "Respondoj (+{numReplies} alia): | Respondoj (+{numReplies} aliaj):",
+ "thread_show": "Malkaŝi ĉi tiun fadenon",
+ "thread_show_full": "Montri ĉion en ĉi tiu fadeno ({numStatus} afiŝon sume, maksimume en profundeco {depth}) | Montri ĉion en ĉi tiu fadeno ({numStatus} afiŝojn sume, maksimume en profundeco {depth})",
+ "show_all_conversation": "Montri plenan interparolon ({numStatus} alian afiŝon) | Montri plenan interparolon ({numStatus} aliajn afiŝojn)",
+ "edited_at": "(lastafoje redaktita je {time})",
+ "remove_attachment": "Forigi kunsendaĵon",
+ "show_attachment_description": "Antaŭvidi priskribon (malfermu kunsendaĵon por vidi plenan priskribon)",
+ "hide_attachment": "Kaŝi kunsendaĵon",
+ "attachment_stop_flash": "Ĉesigi ludilon de [Flash]",
+ "move_up": "Ŝovi kunsendaĵon antaŭen",
+ "move_down": "Ŝovi kunsendaĵon posten",
+ "thread_hide": "Kaŝi ĉi tiun fadenon",
+ "thread_show_full_with_icon": "{icon} {text}",
+ "thread_follow": "Montri ceteron de ĉi tiu fadeno ({numStatus} afiŝon sume) | Montri ceteron de ĉi tiu fadeno ({numStatus} afiŝojn sume)",
+ "thread_follow_with_icon": "{icon} {text}",
+ "ancestor_follow": "Vidi {numReplies} alian respondon sub ĉi tiu afiŝo | Vidi {numReplies} aliajn respondojn sub ĉi tiu afiŝo",
+ "ancestor_follow_with_icon": "{icon} {text}",
+ "show_all_conversation_with_icon": "{icon} {text}",
+ "show_only_conversation_under_this": "Montri nur respondojn al ĉi tiu afiŝo",
+ "status_history": "Historio de afiŝo",
+ "open_gallery": "Malfermi galerion",
+ "delete_confirm_title": "Konfirmo de forigo",
+ "delete_confirm_accept_button": "Forigi",
+ "repeat_confirm": "Ĉu vi certe volas ripeti ĉi tiun afiŝon?",
+ "repeat_confirm_title": "Konfirmo de ripeto",
+ "repeat_confirm_accept_button": "Ripeti",
+ "repeat_confirm_cancel_button": "Ne ripeti",
+ "delete_confirm_cancel_button": "Ne forigi",
+ "delete_error": "Eraris forigo de afiŝo: {0}",
+ "hide_quote": "Kaŝi la cititan afiŝon",
+ "display_quote": "Montri la cititan afiŝon",
+ "reaction_count_label": "{num} persono reagis | {num} personoj reagis",
+ "invisible_quote": "Citita afiŝo ne disponeblas: {link}",
+ "quotes": "Citaĵoj"
},
"time": {
"years_short": "{0}j",
@@ -822,14 +1136,32 @@
"days_short": "{0}t",
"day_short": "{0}t",
"days": "{0} tagoj",
- "day": "{0} tago"
+ "day": "{0} tago",
+ "unit": {
+ "days": "{0} tago | {0} tagoj",
+ "minutes": "{0} minuto | {0} minutoj",
+ "days_short": "{0}t",
+ "hours": "{0} horo | {0} horoj",
+ "hours_short": "{0}h",
+ "minutes_short": "{0}min",
+ "months": "{0} monato | {0} monatoj",
+ "months_short": "{0}mo",
+ "seconds": "{0} sekundo | {0} sekundoj",
+ "seconds_short": "{0}sek",
+ "weeks": "{0} semajno | {0} semajnoj",
+ "weeks_short": "{0}sem",
+ "years": "{0} jaro | {0} jaroj",
+ "years_short": "{0}j"
+ }
},
"search": {
"people": "Personoj",
"no_results": "Neniuj rezultoj",
"people_talking": "{count} personoj parolas",
"person_talking": "{count} persono parolas",
- "hashtags": "Kradvortoj"
+ "hashtags": "Kradvortoj",
+ "no_more_results": "Neniuj pliaj rezultoj",
+ "load_more": "Enlegi pliajn rezultojn"
},
"display_date": {
"today": "Hodiaŭ"
@@ -876,5 +1208,157 @@
},
"shoutbox": {
"title": "Kriujo"
+ },
+ "report": {
+ "reporter": "Raportinto:",
+ "reported_user": "Raportito:",
+ "reported_statuses": "Raportitaj afiŝoj:",
+ "notes": "Notoj:",
+ "state": "Afiŝo:",
+ "state_open": "Malfermita",
+ "state_closed": "Fermita",
+ "state_resolved": "Solvita"
+ },
+ "lists": {
+ "editing_list": "Redaktado de listo {listTitle}",
+ "lists": "Listoj",
+ "new": "Nova listo",
+ "title": "Nomo de listo",
+ "search": "Serĉi uzantojn",
+ "create": "Krei",
+ "save": "Konservi ŝanĝojn",
+ "delete": "Forigi liston",
+ "following_only": "Limigi al abonatoj",
+ "manage_lists": "Mastrumi listojn",
+ "manage_members": "Mastrumi listanojn",
+ "add_members": "Serĉi pliajn uzantojn",
+ "remove_from_list": "Forigi de listo",
+ "add_to_list": "Aldoni al listo",
+ "is_in_list": "Jam en listo",
+ "creating_list": "Kreado de nova listo",
+ "update_title": "Konservi nomon",
+ "really_delete": "Ĉu vi certe volas forigi la liston?",
+ "error": "Eraris umado je listoj: {0}"
+ },
+ "update": {
+ "big_update_content": "Ni longe ne eldonis novan version, kaj tial aferoj eble aspektos iom malsame, ol antaŭe.",
+ "update_bugs": "Bonvolu raporti problemojn kaj erarojn ĉe {pleromaGitlab}, ĉar ni ŝanĝis multon, kaj kvankam ni zorge testas kaj mem uzas la prilaboratajn versiojn, ni tamen povas preteratenti ion. Ni bonvenigas viajn rimarkojn kaj proponojn pri renkontitaj eraroj aŭ proponoj plibonigi Pleromon.",
+ "big_update_title": "Bonvolu pacienci",
+ "update_bugs_gitlab": "GitLab de Pleroma",
+ "update_changelog": "Por legi detalojn pri ŝanĝoj, vidu {theFullChangelog}.",
+ "update_changelog_here": "la plenan ŝanĝaron",
+ "art_by": "Arto de {linkToArtist}"
+ },
+ "unicode_domain_indicator": {
+ "tooltip": "Ĉi tiu retnomo enhavas signojn ekster ASCII."
+ },
+ "announcements": {
+ "all_day_prompt": "Ĉi tio estas tuttaga okazo",
+ "page_header": "Anoncoj",
+ "title": "Anonco",
+ "mark_as_read_action": "Marki legita",
+ "post_placeholder": "Entajpu vian anoncon tie ĉi…",
+ "post_action": "Afiŝi",
+ "post_error": "Eraro: {error}",
+ "close_error": "Fermi",
+ "delete_action": "Forigi",
+ "start_time_prompt": "Komenca tempo: ",
+ "end_time_prompt": "Fina tempo: ",
+ "published_time_display": "Publikigita je {time}",
+ "start_time_display": "Komenciĝas je {time}",
+ "end_time_display": "Finiĝas je {time}",
+ "edit_action": "Redakti",
+ "submit_edit_action": "Afiŝi",
+ "cancel_edit_action": "Nuligi",
+ "inactive_message": "Ĉi tiu anonco estas neaktiva",
+ "post_form_header": "Afiŝi anoncon"
+ },
+ "admin_dash": {
+ "frontend": {
+ "default_frontend": "Implicita fasado",
+ "install": "Instali",
+ "versions": "Disponeblaj versioj",
+ "install_version": "Instali version {version}",
+ "more_install_options": "Pli da elektebloj je instalo",
+ "more_default_options": "Pli da elektebloj je implicitaj agordoj",
+ "set_default": "Agordi implicita",
+ "reinstall": "Reinstali",
+ "default_frontend_tip": "Implicita fasado montriĝos al ĉiuj uzantoj. Ankoraŭ ne ekzistas maniero, kiel uzanto povas elekti propran fasadon. Se vi ŝaltos ion alian, ol [PleromaFE], vi verŝajne devos uzadi la malnovan kaj erareman [AdminFE] por agordi la nodon, ĝis ni anstataŭigos ĝin.",
+ "repository": "Ligilo al deponejo",
+ "is_default": "(Implicita)",
+ "is_default_custom": "(Implicita, versio: {version})",
+ "set_default_version": "Agordi version {version} implicita"
+ },
+ "emoji": {
+ "download_as_name_full": "Nova nomo; lasu malplena por reuzi",
+ "download_as_name": "Nova nomo",
+ "reload": "Re-enlegi bildosignojn",
+ "importFS": "Enporti bildosignojn de dosiersistemo",
+ "error": "Eraro: {0}",
+ "create": "Krei",
+ "do_list": "Listo",
+ "delete": "Forigi",
+ "add_file": "Aldoni dosieron",
+ "filename": "Dosiernomo",
+ "files": "Dosieroj",
+ "save_meta": "Konservi pridatumojn",
+ "description": "Priskribo",
+ "homepage": "Hejmpaĝo",
+ "save": "Konservi",
+ "revert": "Malfari",
+ "share": "Kunhavigi"
+ },
+ "tabs": {
+ "emoji": "Bildosignoj",
+ "frontends": "Fasadoj",
+ "instance": "Nodo",
+ "limits": "Limoj"
+ },
+ "instance": {
+ "registrations": "Registriĝoj de uzantoj",
+ "instance": "Informoj pri nodo",
+ "restrict": {
+ "profiles": "Aliro al profiloj de uzantoj",
+ "header": "Limigi aliron por sennomaj vizitantoj",
+ "timelines": "Aliro al historioj"
+ },
+ "access": "Aliro al nodo",
+ "kocaptcha": "Agordo de KoCaptcha"
+ },
+ "limits": {
+ "users": "Limoj de profiloj de uzantoj",
+ "profile_fields": "Limoj de kampoj de profiloj",
+ "user_uploads": "Limoj de vidaŭdaĵoj de profiloj",
+ "posts": "Limoj de afiŝoj",
+ "uploads": "Limoj de kunsendaĵoj",
+ "arbitrary_limits": "Arbitraj limoj"
+ },
+ "nodb": {
+ "documentation": "dokumentaĵo"
+ },
+ "window_title": "Administrado",
+ "wip_notice": "Ĉi tiu administra fasado estas eksperimenta kaj ankoraŭ prilaborata, {adminFeLink}.",
+ "old_ui_link": "malnova administra fasado disponeblas tie ĉi",
+ "temp_overrides": {
+ ":pleroma": {
+ ":instance": {
+ ":public": {
+ "label": "Nodo estas publika"
+ },
+ ":background_image": {
+ "label": "Fonbildo",
+ "description": "Fonbildo (uzota ĉefe de PleromaFE)"
+ },
+ ":description_limit": {
+ "description": "Limo de signoj por priskriboj de kunsendaĵoj",
+ "label": "Limo"
+ }
+ }
+ }
+ },
+ "commit_all": "Konservi ĉion",
+ "captcha": {
+ "kocaptcha": "KoCaptcha"
+ }
}
}
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
@@ -15,7 +15,8 @@
"title": "Fonctionnalités",
"who_to_follow": "Suggestions de suivis",
"pleroma_chat_messages": "Chat Pleroma",
- "upload_limit": "Limite de téléversement"
+ "upload_limit": "Limite de téléversement",
+ "shout": "Shoutbox"
},
"finder": {
"error_fetching_user": "Erreur lors de la recherche du compte",
@@ -44,9 +45,23 @@
"moderator": "Modo'",
"admin": "Admin"
},
- "flash_content": "Clique pour afficher le contenu Flash avec Ruffle (Expérimental, peut ne pas fonctionner).",
+ "flash_content": "Cliquer pour afficher le contenu Flash avec Ruffle (Expérimental, peut ne pas fonctionner).",
"flash_security": "Cela reste potentiellement dangereux, Flash restant du code arbitraire.",
- "flash_fail": "Échec de chargement du contenu Flash, voir la console pour les détails."
+ "flash_fail": "Échec de chargement du contenu Flash, voir la console pour les détails.",
+ "scope_in_timeline": {
+ "direct": "Direct",
+ "public": "Publique",
+ "private": "Abonné⋅e⋅s seulement",
+ "unlisted": "Non-listé"
+ },
+ "undo": "Défaire",
+ "yes": "Oui",
+ "no": "Non",
+ "unpin": "Dégrafer l'élément",
+ "scroll_to_top": "Défiler au début",
+ "pin": "Agrafer l'élément",
+ "generic_error_message": "Une erreur est apparue : {0}",
+ "never_show_again": "Ne plus afficher"
},
"image_cropper": {
"crop_picture": "Rogner l'image",
@@ -75,11 +90,17 @@
"heading": {
"totp": "Authentification à double-facteur",
"recovery": "Récupération de l'authentification à double-facteur"
- }
+ },
+ "logout_confirm_title": "Confirmation de déconnexion",
+ "logout_confirm": "Souhaitez-vous vous déconnecter ?",
+ "logout_confirm_accept_button": "Déconnexion",
+ "logout_confirm_cancel_button": "Ne pas se déconnecter"
},
"media_modal": {
"previous": "Précédent",
- "next": "Suivant"
+ "next": "Suivant",
+ "counter": "{current} / {total}",
+ "hide": "Fermer le visualiseur multimédia"
},
"nav": {
"about": "À propos",
@@ -93,14 +114,25 @@
"timeline": "Flux personnel",
"twkn": "Réseau connu",
"user_search": "Recherche de comptes",
- "who_to_follow": "Suggestion de suivit",
+ "who_to_follow": "Suggestion de suivi",
"preferences": "Préférences",
"search": "Recherche",
"administration": "Administration",
"chats": "Chats",
"bookmarks": "Marques-Pages",
"timelines": "Flux",
- "home_timeline": "Flux personnel"
+ "home_timeline": "Flux personnel",
+ "edit_nav_mobile": "Personnaliser la barre de navigation",
+ "mobile_notifications": "Ouvrir les notifications (il y en a de nouvelles)",
+ "lists": "Listes",
+ "edit_pinned": "Éditer les éléments agrafés",
+ "edit_finish": "Édition terminée",
+ "mobile_sidebar": "(Dés)activer le panneau latéral",
+ "mobile_notifications_close": "Fermer les notifications",
+ "search_close": "Fermer la barre de recherche",
+ "announcements": "Annonces",
+ "mobile_notifications_mark_as_seen": "Marquer tout comme vu",
+ "quotes": "Citations"
},
"notifications": {
"broken_favorite": "Message inconnu, recherche en cours…",
@@ -114,19 +146,29 @@
"migrated_to": "a migré à",
"reacted_with": "a réagi avec {0}",
"follow_request": "veut vous suivre",
- "error": "Erreur de chargement des notifications : {0}"
+ "error": "Erreur de chargement des notifications : {0}",
+ "poll_ended": "Sondage terminé",
+ "submitted_report": "Rapport envoyé",
+ "unread_announcements": "{num} annonce non lue | {num} annonces non lues",
+ "unread_chats": "{num} message non lu | {num} messages non lus",
+ "configuration_tip_settings": "les préférences",
+ "unread_follow_requests": "{num} nouvelle demande de suivi | {num} nouvelles demandes de suivi",
+ "configuration_tip": "Vous pouvez personnaliser ce qui est affiché ici dans {theSettings}. {dismiss}",
+ "configuration_tip_dismiss": "Ne plus montrer"
},
"interactions": {
"favs_repeats": "Partages et favoris",
"follows": "Nouveaux suivis",
"load_older": "Chargez d'anciennes interactions",
- "moves": "Migrations de comptes"
+ "moves": "Migrations de comptes",
+ "emoji_reactions": "Émoticônes de réaction",
+ "reports": "Rapports"
},
"post_status": {
"new_status": "Poster un nouveau statut",
"account_not_locked_warning": "Votre compte n'est pas {0}. N'importe qui peut vous suivre pour voir vos billets en Abonné·e·s uniquement.",
"account_not_locked_warning_link": "verrouillé",
- "attachments_sensitive": "Marquer les pièce-jointes comme sensible",
+ "attachments_sensitive": "Marquer les pièces jointes comme sensible",
"content_type": {
"text/plain": "Texte brut",
"text/html": "HTML",
@@ -154,7 +196,14 @@
"preview_empty": "Vide",
"preview": "Prévisualisation",
"media_description": "Description de la pièce-jointe",
- "post": "Post"
+ "post": "Post",
+ "edit_status": "Éditer le statut",
+ "edit_remote_warning": "Des instances distantes pourraient ne pas supporter l'édition et seront incapables de recevoir la nouvelle version de votre post.",
+ "edit_unsupported_warning": "Pleroma ne supporte pas l'édition de mentions ni de sondages.",
+ "reply_option": "Répondre à ce statut",
+ "quote_option": "Citer ce statut",
+ "scope_notice_dismiss": "Fermer ce message",
+ "content_type_selection": "Format du statut"
},
"registration": {
"bio": "Biographie",
@@ -174,11 +223,18 @@
"email_required": "ne peut pas être laissé vide",
"password_required": "ne peut pas être laissé vide",
"password_confirmation_required": "ne peut pas être laissé vide",
- "password_confirmation_match": "doit être identique au mot de passe"
+ "password_confirmation_match": "doit être identique au mot de passe",
+ "birthday_min_age": "doit être le ou avant le {date}",
+ "birthday_required": "ne peut pas être vide"
},
"reason_placeholder": "Cette instance modère les inscriptions manuellement.\nExpliquer ce qui motive votre inscription à l'administration.",
"reason": "Motivation d'inscription",
- "register": "Enregistrer"
+ "register": "Enregistrer",
+ "email_language": "Dans quelle langue voulez-vous recevoir les emails du server ?",
+ "bio_optional": "Biographie (optionnelle)",
+ "email_optional": "Courriel (optionnel)",
+ "birthday": "Anniversaire :",
+ "birthday_optional": "Anniversaire (optionnel) :"
},
"selectable_list": {
"select_all": "Tout selectionner"
@@ -267,8 +323,8 @@
"import_theme": "Charger le thème",
"inputRadius": "Champs de texte",
"checkboxRadius": "Cases à cocher",
- "instance_default": "(default : {value})",
- "instance_default_simple": "(default)",
+ "instance_default": "(défaut : {value})",
+ "instance_default_simple": "(défaut)",
"interface": "Interface",
"interfaceLanguage": "Langue de l'interface",
"invalid_theme_imported": "Le fichier sélectionné n'est pas un thème Pleroma pris en charge. Aucun changement n'a été apporté à votre thème.",
@@ -570,7 +626,144 @@
"restore_settings": "Restaurer les paramètres depuis un fichier"
},
"hide_shoutbox": "Cacher la shoutbox de l'instance",
- "right_sidebar": "Afficher le paneau latéral à droite"
+ "right_sidebar": "Afficher le paneau latéral à droite",
+ "expert_mode": "Préférences Avancées",
+ "post_look_feel": "Affichage des messages",
+ "mention_links": "Liens des mentions",
+ "email_language": "Langue pour recevoir les emails du server",
+ "account_backup_table_head": "Sauvegarde",
+ "download_backup": "Télécharger",
+ "backup_not_ready": "La sauvegarde n'est pas encore prête.",
+ "remove_backup": "Supprimer",
+ "list_backups_error": "Erreur d'obtention de la liste des sauvegardes : {error}",
+ "add_backup": "Créer une nouvelle sauvegarde",
+ "added_backup": "Ajouter une nouvelle sauvegarde.",
+ "account_alias": "Alias du compte",
+ "account_alias_table_head": "Alias",
+ "list_aliases_error": "Erreur à l'obtention des alias : {error}",
+ "hide_list_aliases_error_action": "Fermer",
+ "remove_alias": "Supprimer cet alias",
+ "new_alias_target": "Ajouter un nouvel alias (ex. {example})",
+ "added_alias": "L'alias à été ajouté.",
+ "add_alias_error": "Erreur à l'ajout de l'alias : {error}",
+ "move_account_target": "Compte cible (ex. {example})",
+ "moved_account": "Compte déplacé.",
+ "move_account_error": "Erreur au déplacement du compte : {error}",
+ "wordfilter": "Filtrage de mots",
+ "mute_bot_posts": "Masquer les messages des robots",
+ "hide_bot_indication": "Cacher l'indication d'un robot avec les messages",
+ "always_show_post_button": "Toujours montrer le bouton flottant Nouveau Message",
+ "hide_muted_threads": "Cacher les fils masqués",
+ "account_privacy": "Intimité",
+ "posts": "Messages",
+ "disable_sticky_headers": "Ne pas coller les en-têtes des colonnes en haut de l'écran",
+ "show_scrollbars": "Montrer les ascenseurs des colonnes",
+ "third_column_mode_none": "Jamais afficher la troisième colonne",
+ "third_column_mode_notifications": "Colonne de notifications",
+ "third_column_mode_postform": "Édition de messages et navigation",
+ "tree_advanced": "Permettre une navigation plus flexible dans l'arborescence",
+ "conversation_display_linear": "Style linéaire",
+ "conversation_other_replies_button": "Montrer le bouton \"autres réponses\"",
+ "conversation_other_replies_button_below": "En-dessous des messages",
+ "conversation_other_replies_button_inside": "Dans les messages",
+ "max_depth_in_thread": "Profondeur maximum à afficher par défaut dans un fil",
+ "mention_link_display": "Afficher les mentions",
+ "mention_link_display_full_for_remote": "complet pour les comptes distants (ex. {'@'}foo{'@'}example.org)",
+ "mention_link_display_full": "toujours complet (ex. {'@'}foo{'@'}example.org)",
+ "mention_link_show_avatar": "Afficher les avatars à coté du lien",
+ "mention_link_fade_domain": "Estomper les domaines (ex. {'@'}example.org en {'@'}foo{'@'}example.org)",
+ "mention_link_bolden_you": "Surligner les mentions qui vous sont destinées",
+ "show_yous": "Afficher (Vous)",
+ "setting_server_side": "Cette préférence est liée au profile et affecte toutes les sessions et clients",
+ "account_backup": "Sauvegarde de compte",
+ "account_backup_description": "Ceci permet de télécharger une archive des informations du compte et vos messages, mais ils ne peuvent pas actuellement être importé dans un compte Pleroma.",
+ "add_backup_error": "Erreur à l'ajout d'une nouvelle sauvegarde : {error}",
+ "move_account": "Déplacer le compte",
+ "move_account_notes": "Si vous voulez déplacer le compte ailleurs, vous devez aller sur votre compte cible et y créer un alias pointant ici.",
+ "hide_wordfiltered_statuses": "Cacher les messages filtré par un mot",
+ "user_profiles": "Profils des utilisateur⋅ice⋅s",
+ "notification_visibility_polls": "Fins de sondage auquel vous avez voté·e",
+ "hide_favorites_description": "Ne pas montrer ma liste de favoris (les personnes sont quand même notifiés)",
+ "conversation_display": "Style d'affichage des conversations",
+ "conversation_display_tree": "Arborescence",
+ "third_column_mode": "Quand il-y-a assez d'espace, afficher une troisième colonne avec",
+ "tree_fade_ancestors": "Montrer les parents du message courant en texte léger",
+ "use_at_icon": "Montrer le symbole {'@'} comme une icône au lieu de textuelle",
+ "mention_link_display_short": "toujours raccourcies (ex. {'@'}foo)",
+ "mention_link_show_tooltip": "Montrer le nom complet pour les comptes distants dans une info-bulle",
+ "lists_navigation": "Afficher les listes dans la navigation",
+ "word_filter_and_more": "Filtrer par mots et plus ...",
+ "columns": "Colonnes",
+ "auto_update": "Afficher automatiquement les nouveaux posts",
+ "mention_link_use_tooltip": "Montrer le profil utilisateur en cliquant sur les liens de mentions",
+ "mention_link_show_avatar_quick": "Afficher l'avatar de l'utilisateur à côté des mentions",
+ "navbar_column_stretch": "Élargir la barre de navigation à la taille des colonnes",
+ "column_sizes": "Taille des colonnes",
+ "column_sizes_sidebar": "Panneau latéral",
+ "column_sizes_content": "Contenu",
+ "column_sizes_notifs": "Notifications",
+ "conversation_display_linear_quick": "Vue linéaire",
+ "use_websockets": "Utiliser les websockets (mises à jour en temps réel)",
+ "user_popover_avatar_action_zoom": "Zoomer sur l'avatar",
+ "user_popover_avatar_action_open": "Ouvrir le profil",
+ "conversation_display_tree_quick": "Vue arborescente",
+ "emoji_reactions_scale": "Taille des réactions",
+ "backup_running": "Cette sauvegarde est en cours, {number} enregistrement effectué. | Cette sauvegarde est en cours, {number} enregistrements effectués.",
+ "backup_failed": "Cette sauvegarde a échoué.",
+ "autocomplete_select_first": "Sélectionner automatiquement la première occurrence lorsque les résultats de l'autocomplétion sont disponibles",
+ "confirm_dialogs_unfollow": "arrête de suivre un utilisateur",
+ "confirm_dialogs_repeat": "reposte un statut",
+ "actor_type": "Ce compte est :",
+ "actor_type_Person": "un utilisateur normal",
+ "actor_type_Service": "un robot",
+ "actor_type_Group": "un groupe",
+ "confirm_dialogs_logout": "à la déconnexion",
+ "confirm_dialogs_approve_follow": "accepte un nouvel abonné",
+ "confirm_dialogs_deny_follow": "refuse un nouvel abonné",
+ "confirm_dialogs_remove_follower": "supprime un abonné",
+ "actor_type_description": "En marquant votre compte comme un groupe, vous répétez automatiquement les statuts qui le mentionnent.",
+ "add_language": "Ajouter une langue de remplacement",
+ "remove_language": "Supprimer",
+ "primary_language": "Langue principale :",
+ "fallback_language": "Langue de remplacement {index} :",
+ "confirm_dialogs": "Demande de confirmation quand",
+ "confirm_dialogs_block": "bloque un utilisateur",
+ "confirm_dialogs_mute": "mute un utilisateur",
+ "confirm_dialogs_delete": "supprime un statut",
+ "url": "URL",
+ "preview": "Aperçu",
+ "reset_value": "Réinitialiser",
+ "hard_reset_value_tooltip": "Supprime le réglage du stockage, force l'utilisation de la valeur par défaut",
+ "reset_value_tooltip": "Réinitialiser le brouillon",
+ "hard_reset_value": "Remise à zéro",
+ "hide_actor_type_indication": "Cacher le type (robots, groupes, etc.) dans les status",
+ "notification_extra_follow_requests": "Afficher les nouvelles demandes de suivi",
+ "user_popover_avatar_action": "Action du clic sur l'avatar",
+ "user_popover_avatar_action_close": "Fermer la fenêtre contextuelle",
+ "notification_setting_ignore_inactionable_seen": "Ignorer les status de lecture des notifications non actionnables (favoris, répétitions, etc)",
+ "notification_setting_ignore_inactionable_seen_tip": "Ceci ne marquera pas ces notifications comme lues, et vous recevrez encore les notifications de bureau si vous le décidez",
+ "notification_setting_unseen_at_top": "Afficher les notifications non lues au-dessus des autres",
+ "notification_setting_filters_chrome_push": "Sur certains navigateurs (chrome), il peut être impossible de filtrer complètement les notifications par type lorsqu'elles arrivent",
+ "enable_web_push_always_show": "Toujours afficher les notifications web",
+ "commit_value": "Sauvegarder",
+ "hide_scrobbles": "Masquer les scrobbles",
+ "notification_setting_annoyance": "Agacement",
+ "notification_setting_drawer_marks_as_seen": "Fermer le tiroir marque toutes les notifications comme lues (mobile)",
+ "commit_value_tooltip": "Les valeurs ne sont pas sauvegardées, appuyez sur ce bouton pour soumettre vos changements",
+ "birthday": {
+ "show_birthday": "Afficher mon anniversaire",
+ "label": "Anniversaire"
+ },
+ "notification_visibility_native_notifications": "Afficher une notification native",
+ "notification_visibility_follow_requests": "Demandes de suivi",
+ "notification_visibility_reports": "Rapports",
+ "notification_extra_chats": "Afficher les discussions non lues",
+ "notification_extra_announcements": "Afficher les annonces non lues",
+ "notification_extra_tip": "Afficher les astuces de personnalisation pour les notifications extras",
+ "enable_web_push_always_show_tip": "Certains navigateurs (Chromium, Chrome) exigent que les messages push donnent toujours lieu à une notification, sinon le message générique \"Le site web a été mis à jour en arrière-plan\" s'affiche ; activez cette option pour empêcher l'affichage de cette notification, car Chrome semble masquer les notifications push si l'onglet est au centre de l'attention. Cela peut entraîner l'affichage de notifications en double sur d'autres navigateurs.",
+ "user_popover_avatar_overlay": "Afficher la fenêtre contextuelle sur l'avatar de l'utilisateur",
+ "notification_visibility_in_column": "Afficher la colonne / le tiroir de notifications",
+ "notification_show_extra": "Afficher les extras dans la colonne de notifications"
},
"timeline": {
"collapse": "Fermer",
@@ -586,7 +779,9 @@
"reload": "Recharger",
"error": "Erreur lors de l'affichage du flux : {0}",
"socket_broke": "Connexion temps-réel perdue : CloseEvent code {0}",
- "socket_reconnected": "Connexion temps-réel établie"
+ "socket_reconnected": "Connexion temps-réel établie",
+ "quick_view_settings": "Afficher les réglages rapides",
+ "quick_filter_settings": "Afficher les filtres rapides"
},
"status": {
"favorites": "Favoris",
@@ -613,7 +808,49 @@
"thread_muted": "Fil de discussion masqué",
"external_source": "Source externe",
"unbookmark": "Supprimer des favoris",
- "bookmark": "Ajouter aux favoris"
+ "bookmark": "Ajouter aux favoris",
+ "plus_more": "plus +{number}",
+ "many_attachments": "Message avec {number} pièce(s)-jointe(s)",
+ "collapse_attachments": "Réduire les pièces jointes",
+ "show_attachment_in_modal": "Montrer dans le visionneur de médias",
+ "hide_attachment": "Cacher la pièce jointe",
+ "you": "(Vous)",
+ "attachment_stop_flash": "Arrêter Flash Player",
+ "move_down": "Décaler la pièce-jointe à droite",
+ "thread_hide": "Cacher ce fil",
+ "thread_show": "Montrer ce fil",
+ "thread_show_full_with_icon": "{icon} {text}",
+ "thread_follow": "Montrer le reste du fil ({numStatus} message) | Montrer le reste du fil ({numStatus} messages)",
+ "thread_follow_with_icon": "{icon} {text}",
+ "ancestor_follow": "Monter les {numReplies} autres réponses après ce message | Monter les {numReplies} autres réponses après ce message",
+ "ancestor_follow_with_icon": "{icon} {text}",
+ "show_all_conversation_with_icon": "{icon} {text}",
+ "show_only_conversation_under_this": "Montrer uniquement les réponses à ce message",
+ "mentions": "Mentions",
+ "replies_list_with_others": "Réponses (+{numReplies} autres) : | Réponses (+{numReplies} autres) :",
+ "show_all_attachments": "Montrer toutes les pièces jointes",
+ "show_attachment_description": "Prévisualiser la description (ouvrir la pièce-jointe pour la description complète)",
+ "remove_attachment": "Enlever la pièce jointe",
+ "move_up": "Décaler la pièce-jointe à gauche",
+ "open_gallery": "Ouvrir la galerie",
+ "thread_show_full": "Montrer tout le fil ({numStatus} message, {depth} niveaux maximum) | Montrer tout le fil ({numStatus} messages, {depth} niveaux maximum)",
+ "show_all_conversation": "Montrer tout le fil ({numStatus} autre message) | Montrer tout le fil ({numStatus} autre messages)",
+ "edit": "Éditer le status",
+ "edited_at": "(dernière édition {time})",
+ "status_history": "Historique du status",
+ "delete_error": "Erreur de suppression du statut : {0}",
+ "repeat_confirm": "Voulez-vous réellement reposter ce statut ?",
+ "reaction_count_label": "{num} personne a réagi | {num} personnes ont réagi",
+ "repeat_confirm_cancel_button": "Ne pas reposter",
+ "hide_quote": "Masquer les status cités",
+ "display_quote": "Afficher les status cités",
+ "invisible_quote": "Citation de statut non disponible : {link}",
+ "delete_confirm_title": "Confirmer la suppression",
+ "more_actions": "Plus d'action sur ce statut",
+ "delete_confirm_cancel_button": "Conserver",
+ "repeat_confirm_title": "Confirmer reposte",
+ "repeat_confirm_accept_button": "Reposter",
+ "delete_confirm_accept_button": "Supprimer"
},
"user_card": {
"approve": "Accepter",
@@ -644,11 +881,11 @@
"unmute_progress": "Démasquage…",
"mute_progress": "Masquage…",
"admin_menu": {
- "moderation": "Moderation",
+ "moderation": "Modération",
"grant_admin": "Promouvoir Administrateur⋅ice",
- "revoke_admin": "Dégrader Administrateur⋅ice",
+ "revoke_admin": "Dégrader L'administrateur⋅ice",
"grant_moderator": "Promouvoir Modérateur⋅ice",
- "revoke_moderator": "Dégrader Modérateur⋅ice",
+ "revoke_moderator": "Dégrader la·e modérateur⋅ice",
"activate_account": "Activer le compte",
"deactivate_account": "Désactiver le compte",
"delete_account": "Supprimer le compte",
@@ -659,7 +896,8 @@
"disable_remote_subscription": "Interdir de s'abonner a l'utilisateur depuis l'instance distante",
"disable_any_subscription": "Interdir de s'abonner à l'utilisateur tout court",
"quarantine": "Interdir les statuts de l'utilisateur à fédérer",
- "delete_user": "Supprimer l'utilisateur"
+ "delete_user": "Supprimer l'utilisateur",
+ "delete_user_data_and_deactivate_confirmation": "Ceci va supprimer les données du compte de manière permanente et le désactivé. Êtes-vous vraiment sûr ?"
},
"mention": "Mention",
"hidden": "Caché",
@@ -679,7 +917,42 @@
"striped": "Fond rayé"
},
"bot": "Robot",
- "edit_profile": "Éditer le profil"
+ "edit_profile": "Éditer le profil",
+ "deactivated": "Désactivé",
+ "follow_cancel": "Annuler la requête",
+ "remove_follower": "Retirer l'abonné·e",
+ "remove_follower_confirm_accept_button": "Supprimer",
+ "approve_confirm_cancel_button": "Ne pas approuver",
+ "block_confirm_accept_button": "Bloquer",
+ "mute_confirm_title": "Confirmation de mise en sourdine",
+ "block_confirm_cancel_button": "Ne pas bloquer",
+ "unfollow_confirm": "Voulez-vous vraiment arrêter de suivre {user} ?",
+ "unfollow_confirm_accept_button": "Ne plus suivre",
+ "birthday": "Né(e) le {birthday}",
+ "edit_note": "Éditer note",
+ "edit_note_apply": "Appliquer",
+ "edit_note_cancel": "Abandonner",
+ "note": "Note",
+ "group": "Groupe",
+ "unfollow_confirm_title": "Confirmer l'arrêt de suivi",
+ "block_confirm_title": "Confirmer le blocage",
+ "deny_confirm_accept_button": "Refuser",
+ "deny_confirm_cancel_button": "Ne pas refuser",
+ "deny_confirm": "Voulez-vous refuser la demande de suivi de {user} ?",
+ "deny_confirm_title": "Refuser la confirmation",
+ "remove_follower_confirm_cancel_button": "Conserver",
+ "mute_duration_prompt": "Mettre cet utilisateur en sourdine pour (0 pour une durée indéterminée) :",
+ "remove_follower_confirm_title": "Confirmation de suppression d'utilisateur",
+ "note_blank": "(Aucun)",
+ "mute_confirm": "Voulez-vous vraiment mettre {user} en sourdine ?",
+ "mute_confirm_accept_button": "Mettre en sourdine",
+ "mute_confirm_cancel_button": "Ne pas mettre en sourdine",
+ "remove_follower_confirm": "Voulez-vous vraiment supprimer {user} de vos abonnés ?",
+ "approve_confirm_accept_button": "Approuver",
+ "approve_confirm": "Voulez-vous approuver la demande de suivi de {user} ?",
+ "block_confirm": "Voulez-vous vraiment bloquer {user} ?",
+ "approve_confirm_title": "Approuver confirmation",
+ "unfollow_confirm_cancel_button": "Ne pas arrêter le suivi"
},
"user_profile": {
"timeline_title": "Flux du compte",
@@ -708,7 +981,10 @@
"add_reaction": "Ajouter une réaction",
"accept_follow_request": "Accepter la demande de suivit",
"reject_follow_request": "Rejeter la demande de suivit",
- "bookmark": "Favori"
+ "bookmark": "Favori",
+ "autocomplete_available": "{number} résultat est disponible. Utilisez les touches haut et bas pour naviguer à l'intérieur. | {number} résultats sont disponibles. Utilisez les touches haut et bas pour naviguer à l'intérieur.",
+ "toggle_expand": "Développer ou réduire la notification pour afficher le message dans son intégralité",
+ "toggle_mute": "Développer ou réduire la notification pour révéler le contenu en sourdine"
},
"upload": {
"error": {
@@ -747,13 +1023,16 @@
"media_removal_desc": "Cette instance supprime le contenu multimédia des instances suivantes :",
"media_nsfw": "Force le contenu multimédia comme sensible",
"ftl_removal": "Supprimées du flux fédéré",
- "media_nsfw_desc": "Cette instance force les pièce-jointes comme sensible pour les messages des instances suivantes :"
+ "media_nsfw_desc": "Cette instance force les pièce-jointes comme sensible pour les messages des instances suivantes :",
+ "reason": "Raison",
+ "not_applicable": "N/A",
+ "instance": "Instance"
},
"federation": "Fédération",
"mrf_policies": "Politiques MRF actives",
"mrf_policies_desc": "Les politiques MRF modifient la fédération entre les instances. Les politiques suivantes sont activées :"
},
- "staff": "Staff"
+ "staff": "Équipe"
},
"domain_mute_card": {
"mute": "Masqué",
@@ -786,7 +1065,21 @@
"load_all": "Charger tout les {emojiAmount} émojis",
"load_all_hint": "{saneAmount} émojis chargé, charger tout les émojis peuvent causer des problèmes de performances.",
"stickers": "Stickers",
- "keep_open": "Garder ouvert"
+ "keep_open": "Garder ouvert",
+ "unicode_groups": {
+ "activities": "Activités",
+ "animals-and-nature": "Animaux & nature",
+ "flags": "Drapeaux",
+ "food-and-drink": "Nourriture & boissons",
+ "objects": "Objets",
+ "people-and-body": "Personnes & Corps",
+ "smileys-and-emotion": "Emoticônes",
+ "symbols": "Symboles",
+ "travel-and-places": "Voyages & lieux"
+ },
+ "regional_indicator": "Indicateur régional {letter}",
+ "unpacked": "Émojis non catégorisés",
+ "hide_custom_emoji": "Masquer les émojis personnalisés"
},
"remote_user_resolver": {
"error": "Non trouvé.",
@@ -825,14 +1118,32 @@
"year": "{0} année",
"years": "{0} années",
"year_short": "{0}a",
- "years_short": "{0}a"
+ "years_short": "{0}a",
+ "unit": {
+ "years": "{0} année | {0} années",
+ "years_short": "{0}ans",
+ "days_short": "{0}j",
+ "hours": "{0} heure | {0} heures",
+ "hours_short": "{0}h",
+ "minutes": "{0} minute | {0} minutes",
+ "minutes_short": "{0}min",
+ "months_short": "{0}mois",
+ "seconds": "{0} seconde | {0} secondes",
+ "seconds_short": "{0}s",
+ "weeks": "{0} semaine | {0} semaines",
+ "days": "{0} jour | {0} jours",
+ "months": "{0} mois | {0} mois",
+ "weeks_short": "{0}semaine"
+ }
},
"search": {
"people": "Comptes",
"person_talking": "{count} personnes discutant",
"hashtags": "Mot-dièses",
"people_talking": "{count} personnes discutant",
- "no_results": "Aucun résultats"
+ "no_results": "Aucun résultat",
+ "no_more_results": "Pas de résultats supplémentaires",
+ "load_more": "Charger plus de résultats"
},
"password_reset": {
"forgot_password": "Mot de passe oublié ?",
@@ -873,5 +1184,201 @@
"delete": "Effacer",
"message_user": "Message à {nickname}",
"you": "Vous :"
+ },
+ "lists": {
+ "new": "Nouvelle liste",
+ "title": "Titre de la liste",
+ "create": "Créer",
+ "save": "Sauvegarder les changements",
+ "delete": "Supprimer la liste",
+ "following_only": "Limiter aux abonné·e·s",
+ "manage_lists": "Gérer les listes",
+ "add_members": "Rechercher plus d'utilisateurs",
+ "remove_from_list": "Retirer de la liste",
+ "add_to_list": "Ajouter à la liste",
+ "is_in_list": "Déjà dans la liste",
+ "editing_list": "Édition de la liste {listTitle}",
+ "creating_list": "Création d'une nouvelle liste",
+ "really_delete": "Êtes-vous sûr·e de vouloir supprimer la liste ?",
+ "error": "Erreur en manipulant les listes : {0}",
+ "lists": "Listes",
+ "search": "Rechercher des utilisateurs",
+ "manage_members": "Gérer les membres des listes",
+ "update_title": "Sauvegarder le titre"
+ },
+ "update": {
+ "update_bugs_gitlab": "GitLab du projet Pleroma",
+ "update_changelog": "Pour plus de détails sur les changements, consultez {theFullChangelog}.",
+ "update_changelog_here": "Liste compète des changements",
+ "art_by": "Œuvre par {linkToArtist}",
+ "big_update_content": "Nous n'avons pas fait de nouvelle version depuis un moment, les choses peuvent vous paraitre différentes de vos habitudes.",
+ "update_bugs": "Veuillez rapporter les problèmes sur {pleromaGitlab}, comme beaucoup de changements on été fait, même si nous testons entièrement et utilisons la version de dévelopement nous-même, nous avons pu en louper. Les retours et suggestions sont bienvenues sur ce que vous avez pu rencontrer, ou sur comment améliorer Pleroma (BE) et Pleroma-FE.",
+ "big_update_title": "Soyez indulgent avec nous"
+ },
+ "unicode_domain_indicator": {
+ "tooltip": "Ce domaine contient des caractères non ascii."
+ },
+ "report": {
+ "reporter": "Rapporteur·euse :",
+ "reported_user": "Compte rapporté :",
+ "reported_statuses": "Status rapportés :",
+ "notes": "Notes :",
+ "state": "Status :",
+ "state_open": "Ouvert",
+ "state_closed": "Fermé",
+ "state_resolved": "Résolut"
+ },
+ "announcements": {
+ "page_header": "Annonces",
+ "title": "Annonce",
+ "mark_as_read_action": "Marquer comme lu",
+ "post_form_header": "Faire une annonce",
+ "post_placeholder": "Écrivez le contenu de l'annonce ici...",
+ "post_action": "Envoyer",
+ "post_error": "Erreur : {error}",
+ "close_error": "Fermer",
+ "delete_action": "Supprimer",
+ "start_time_prompt": "Heure de début : ",
+ "end_time_prompt": "Heure de fin : ",
+ "all_day_prompt": "L'événement dure toute la journée",
+ "inactive_message": "Cette annonce n'est pas active",
+ "published_time_display": "Publié le {time}",
+ "start_time_display": "Démarre à {time}",
+ "end_time_display": "Se termine à {time}",
+ "edit_action": "Modifier",
+ "submit_edit_action": "Envoyer",
+ "cancel_edit_action": "Annuler"
+ },
+ "admin_dash": {
+ "frontend": {
+ "success_installing_frontend": "Installation réussie de l'interface {version}",
+ "failure_installing_frontend": "Échec de l'installation de l'interface {version} : {reason}",
+ "default_frontend_unavail": "Les paramètres de l'interface ne sont pas disponibles, ils doivent être configurés dans la base de données",
+ "build_url": "Construction URL",
+ "reinstall": "Réinstaller",
+ "repository": "Lien du dépôt",
+ "versions": "Versions disponibles",
+ "default_frontend_tip": "L'interface par défaut sera affichée à tous les utilisateurs. Si vous décidez de quitter PleromaFE, vous devrez utiliser l'ancienne AdminFE buguée pour configurer votre instance jusqu'à ce que nous la remplacions.",
+ "is_default": "(Défaut)",
+ "is_default_custom": "(Défaut, version : {version})",
+ "install": "Installation",
+ "install_version": "Installation de la version {version}",
+ "more_install_options": "Plus d'options d'installation",
+ "more_default_options": "Plus d'options de paramétrages par défaut",
+ "set_default": "Définir la valeur par défaut",
+ "set_default_version": "Définir la version {version} comme version par défaut",
+ "wip_notice": "Veuillez noter que cette section est en cours de développement et que certaines fonctionnalités de l'interface ne sont pas implémentées côté serveur.",
+ "default_frontend": "Interface par défaut",
+ "available_frontends": "Disponible pour installation"
+ },
+ "temp_overrides": {
+ ":pleroma": {
+ ":instance": {
+ ":public": {
+ "label": "Cette instance est publique",
+ "description": "En désactivant cette option, toutes les API ne seront accessibles qu'aux utilisateurs connectés, ce qui rendra les chronologies publiques et fédérées inaccessibles aux visiteurs anonymes."
+ },
+ ":limit_to_local_content": {
+ "label": "Limitez la recherche au contenu local",
+ "description": "Désactive la recherche globale sur le réseau pour les utilisateurs non authentifiés (par défaut), tous les utilisateurs ou aucun"
+ },
+ ":description_limit": {
+ "label": "Limite",
+ "description": "Limite de nombre de caractères pour la description des fichiers joints"
+ },
+ ":background_image": {
+ "description": "Image de fond (principalement utilisé par PleromaFE)",
+ "label": "Image de fond d'écran"
+ }
+ }
+ }
+ },
+ "tabs": {
+ "emoji": "Émoji",
+ "limits": "Limites",
+ "frontends": "Interfaces",
+ "instance": "Instance",
+ "nodb": "Pas de configuration de base de données"
+ },
+ "instance": {
+ "kocaptcha": "Réglages KoCaptcha",
+ "access": "Accès à l'instance",
+ "restrict": {
+ "header": "Restreindre l'accès aux visiteurs anonymes",
+ "profiles": "Accès aux profils d'utilisateur",
+ "activities": "Accès aux status/activités",
+ "description": "Paramètre détaillé permettant d'autoriser/interdire l'accès à certains aspects de l'API. Par défaut (état indéterminé), l'accès est interdit si l'instance n'est pas publique ; si la case est cochée, l'accès est interdit même si l'instance est publique ; si la case n'est pas cochée, l'accès est autorisé même si l'instance est privée. Veuillez noter qu'un comportement inattendu peut se produire si certains paramètres sont définis, par exemple si l'accès au profil est désactivé, les messages s'afficheront sans les informations relatives au profil.",
+ "timelines": "Accès aux flux"
+ },
+ "registrations": "Inscription des utilisateurs",
+ "captcha_header": "CAPTCHA",
+ "instance": "Informations sur l'instance"
+ },
+ "emoji": {
+ "global_actions": "Actions globales",
+ "reload": "Recharger les émojis",
+ "importFS": "Importer les émojis depuis le système de fichiers",
+ "error": "Erreur : {0}",
+ "create_pack": "Créer un pack",
+ "delete_pack": "Supprimer un paquet",
+ "new_pack_name": "Renommer le pack",
+ "create": "Créer",
+ "emoji_packs": "Pack d'émojis",
+ "remote_packs": "Packs d'autres instances",
+ "do_list": "Liste",
+ "remote_pack_instance": "Instance du pack",
+ "emoji_pack": "Pack d'émoji",
+ "edit_pack": "Modifier le pack",
+ "description": "Description",
+ "homepage": "Page d'accueil",
+ "fallback_src": "Source de remplacement",
+ "fallback_sha256": "Remplacement SHA256",
+ "share": "Partager",
+ "save": "Sauvegarder",
+ "save_meta": "Sauvegarder les métadonnées",
+ "revert_meta": "Annuler métadonnées",
+ "delete": "Supprimer",
+ "revert": "Revenir en arrière",
+ "add_file": "Ajouter un fichier",
+ "adding_new": "Ajouter un nouvel émoji",
+ "shortcode": "Shortcode",
+ "filename": "Nom du fichier",
+ "new_filename": "Nom de fichier, laisser blanc pour inférer",
+ "delete_confirm": "Êtes-vous sûr de vouloir supprimer {0} ?",
+ "download_pack": "Télécharger pack",
+ "downloading_pack": "Télécharge {0}",
+ "download": "Téléchargement",
+ "download_as_name": "Nouveau nom",
+ "download_as_name_full": "Nouveau nom, laissez blanc pour réutiliser le précédent",
+ "files": "Fichiers",
+ "editing": "Édition de {0}",
+ "delete_title": "Supprimer ?",
+ "metadata_changed": "Métadonnées différentes de celles sauvegardées",
+ "emoji_changed": "Modifications du fichier émoji non sauvegardées, vérifier l'émoji surligné",
+ "replace_warning": "Vous allez REMPLACER le pack local qui porte ce nom"
+ },
+ "window_title": "Administration",
+ "nodb": {
+ "heading": "La configuration de base de données est désactivée",
+ "documentation": "documentation",
+ "text2": "La majorité des options de configuration ne seront pas disponibles.",
+ "text": "Vous devez modifier les fichiers de configuration du serveur pour que {property} soit définie à {value}, plus de détails dans la {documentation}."
+ },
+ "limits": {
+ "arbitrary_limits": "Limites arbitraires",
+ "posts": "Limites des statuts",
+ "uploads": "Limites des pièces jointes",
+ "users": "Limites du profil d'utilisateur",
+ "profile_fields": "Limites des champs du profile",
+ "user_uploads": "Limites des médias du profil"
+ },
+ "captcha": {
+ "native": "Natif",
+ "kocaptcha": "KoCaptcha"
+ },
+ "wip_notice": "Ce tableau de bord d'administration est expérimental et en cours de développement, {adminFeLink}.",
+ "old_ui_link": "L'ancien espace d'administration est disponible ici",
+ "reset_all": "Tout réinitialiser",
+ "commit_all": "Tout sauvegarder"
}
}
diff --git a/src/i18n/id.json b/src/i18n/id.json
@@ -214,7 +214,8 @@
"domain_mutes": "Domain",
"composing": "Menulis",
"no_blocks": "Tidak ada yang diblokir",
- "no_mutes": "Tidak ada yang dibisukan"
+ "no_mutes": "Tidak ada yang dibisukan",
+ "remove_language": "Hapus"
},
"about": {
"mrf": {
@@ -230,7 +231,9 @@
"accept_desc": "Instansi ini hanya menerima pesan dari instansi-instansi berikut:",
"accept": "Terima",
"media_removal": "Penghapusan Media",
- "media_removal_desc": "Instansi ini menghapus media dari postingan yang berasal dari instansi-instansi berikut:"
+ "media_removal_desc": "Instansi ini menghapus media dari postingan yang berasal dari instansi-instansi berikut:",
+ "instance": "Instance",
+ "reason": "Alasan"
},
"federation": "Federasi",
"mrf_policies": "Kebijakan MRF yang diaktifkan"
@@ -437,7 +440,10 @@
"password_required": "tidak boleh kosong",
"email_required": "tidak boleh kosong",
"fullname_required": "tidak boleh kosong",
- "username_required": "tidak boleh kosong"
+ "username_required": "tidak boleh kosong",
+ "password_confirmation_match": "wajib sama dengan sandi",
+ "birthday_required": "tidak boleh kosong",
+ "birthday_min_age": "wajib sama dengan atau sebelum {date}"
},
"register": "Daftar",
"fullname_placeholder": "contoh. Lain Iwakura",
@@ -450,7 +456,12 @@
"bio": "Bio",
"reason_placeholder": "Instansi ini menerima pendaftaran secara manual.\nBeritahu administrasinya mengapa Anda ingin mendaftar.",
"reason": "Alasan mendaftar",
- "registration": "Pendaftaran"
+ "registration": "Pendaftaran",
+ "email_language": "Dalam bahasa apa kamu ingin menerima surel dari server ini?",
+ "email_optional": "Surel (opsional)",
+ "birthday": "Ulang tahun:",
+ "birthday_optional": "Ulang tahun (opsional):",
+ "bio_optional": "Bio (opsional)"
},
"post_status": {
"preview_empty": "Kosong",
@@ -482,7 +493,8 @@
"empty_status_error": "Tidak dapat memposting status kosong tanpa berkas",
"account_not_locked_warning_link": "terkunci",
"account_not_locked_warning": "Akun Anda tidak {0}. Siapapun dapat mengikuti Anda untuk melihat postingan hanya-pengikut Anda.",
- "new_status": "Posting status baru"
+ "new_status": "Posting status baru",
+ "edit_status": "Sunting status"
},
"general": {
"apply": "Terapkan",
@@ -508,7 +520,15 @@
"generic_error": "Terjadi kesalahan",
"loading": "Memuat…",
"more": "Lebih banyak",
- "submit": "Kirim"
+ "submit": "Kirim",
+ "yes": "Ya",
+ "no": "Tidak",
+ "scope_in_timeline": {
+ "direct": "Langsung",
+ "private": "Hanya pengikut",
+ "public": "Publik"
+ },
+ "generic_error_message": "Terjadi kesalahan: {0}"
},
"remote_user_resolver": {
"error": "Tidak ditemukan."
@@ -522,7 +542,18 @@
"emoji": "Emoji",
"stickers": "Stiker",
"keep_open": "Tetap buka pemilih",
- "custom": "Emoji kustom"
+ "custom": "Emoji kustom",
+ "unicode_groups": {
+ "activities": "Aktivitas",
+ "animals-and-nature": "Hewan & Alam",
+ "flags": "Bendera",
+ "food-and-drink": "Makanan & Minuman",
+ "objects": "Objek",
+ "people-and-body": "Orang & Tubuh",
+ "smileys-and-emotion": "Emosi",
+ "symbols": "Simbol",
+ "travel-and-places": "Perjalanan & Tempat-tempat"
+ }
},
"polls": {
"expired": "Japat berakhir {0} yang lalu",
@@ -553,11 +584,17 @@
"timelines": "Linimasa",
"chats": "Obrolan",
"dms": "Pesan langsung",
- "friend_requests": "Ingin mengikuti"
+ "friend_requests": "Ingin mengikuti",
+ "twkn": "Jaringan Dikenal",
+ "mobile_notifications_close": "Tutup notifikasi",
+ "announcements": "Pengumuman",
+ "mobile_notifications": "Buka notifikasi (ada yang belum dibaca)"
},
"media_modal": {
"next": "Selanjutnya",
- "previous": "Sebelum"
+ "previous": "Sebelum",
+ "counter": "{current} / {total}",
+ "hide": "Tutup penampil media"
},
"login": {
"recovery_code": "Kode pemulihan",
@@ -574,7 +611,10 @@
"heading": {
"totp": "Otentikasi dua-faktor"
},
- "enter_two_factor_code": "Masukkan kode dua-faktor"
+ "enter_two_factor_code": "Masukkan kode dua-faktor",
+ "logout_confirm": "Apa kamu yakin ingin keluar?",
+ "logout_confirm_accept_button": "Keluar",
+ "logout_confirm_cancel_button": "Jangan keluar"
},
"importer": {
"error": "Terjadi kesalahan ketika mnengimpor berkas ini.",
@@ -597,7 +637,8 @@
"gopher": "Gopher",
"pleroma_chat_messages": "Pleroma Obrolan",
"chat": "Obrolan",
- "upload_limit": "Batas unggahan"
+ "upload_limit": "Batas unggahan",
+ "media_proxy": "Proxy media"
},
"exporter": {
"processing": "Memproses, Anda akan segera diminta untuk mengunduh berkas Anda",
@@ -619,12 +660,41 @@
"moves": "Pengguna yang bermigrasi",
"follows": "Pengikut baru",
"favs_repeats": "Ulangan dan favorit",
- "load_older": "Muat interaksi yang lebih tua"
+ "load_older": "Muat interaksi yang lebih tua",
+ "emoji_reactions": "Reaksi Emoji",
+ "reports": "Laporan"
},
"errors": {
"storage_unavailable": "Pleroma tidak dapat mengakses penyimpanan browser. Login Anda atau pengaturan lokal Anda tidak akan tersimpan dan masalah yang tidak terduga dapat terjadi. Coba mengaktifkan kuki."
},
"shoutbox": {
"title": "Kotak Suara"
+ },
+ "report": {
+ "state_closed": "Ditutup",
+ "reporter": "Pelapor:",
+ "reported_statuses": "Status yang dilaporkan:",
+ "reported_user": "Pengguna yang dilaporkan:",
+ "notes": "Catatan:",
+ "state": "Status:",
+ "state_open": "Terbuka",
+ "state_resolved": "Selesai"
+ },
+ "announcements": {
+ "end_time_prompt": "Waktu berakhir: ",
+ "published_time_display": "Diterbitkan pada {time}",
+ "page_header": "Pengumuman",
+ "title": "Pengumuman",
+ "mark_as_read_action": "Tandai telah dibaca",
+ "post_placeholder": "Ketik isi pengumumanmu di sini...",
+ "close_error": "Tutup",
+ "delete_action": "Hapus",
+ "start_time_prompt": "Waktu mulai: ",
+ "post_error": "Kesalahan: {error}",
+ "start_time_display": "Dimulai pada {time}",
+ "end_time_display": "Berakhir pada {time}",
+ "edit_action": "Sunting",
+ "submit_edit_action": "Kirim",
+ "cancel_edit_action": "Batal"
}
}
diff --git a/src/i18n/ja_easy.json b/src/i18n/ja_easy.json
@@ -17,7 +17,17 @@
"media_removal": "メディアをのぞく",
"media_removal_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、とりのぞきます:",
"media_nsfw": "メディアをすべてセンシティブにする",
- "media_nsfw_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、すべて、センシティブにマークします:"
+ "media_nsfw_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、すべて、センシティブにマークします:",
+ "reason": "りゆう",
+ "instance": "インスタンス",
+ "not_applicable": "なし"
+ },
+ "keyword": {
+ "keyword_policies": "キーワードポリシー",
+ "reject": "おことわり",
+ "replace": "おきかえ",
+ "ftl_removal": "「つながっているすべてのネットワーク」タイムラインからのぞく",
+ "is_replaced_by": "→"
}
},
"staff": "スタッフ"
@@ -36,7 +46,10 @@
"scope_options": "こうかいはんいせんたく",
"text_limit": "もじのかず",
"title": "ゆうこうなきのう",
- "who_to_follow": "おすすめユーザー"
+ "who_to_follow": "おすすめユーザー",
+ "pleroma_chat_messages": "Pleroma チャット",
+ "upload_limit": "アップロードできるファイルのおおきさ",
+ "shout": "Shoutbox"
},
"finder": {
"error_fetching_user": "ユーザーけんさくがエラーになりました",
@@ -54,7 +67,34 @@
"disable": "なし",
"enable": "あり",
"confirm": "たしかめる",
- "verify": "たしかめる"
+ "verify": "たしかめる",
+ "retry": "もういちど、ためしてください",
+ "loading": "よみこんでいます…",
+ "undo": "もとにもどす",
+ "yes": "はい",
+ "no": "いいえ",
+ "unpin": "ピンどめするのをやめる",
+ "scroll_to_top": "いちばんうえにもどる",
+ "role": {
+ "moderator": "モデレーター",
+ "admin": "かんりするひと"
+ },
+ "flash_security": "Flash コンテンツはどんなコードでもじっこうできるので、あぶないかもしれません。",
+ "flash_fail": "Flash コンテンツをよみこむことに、しっぱいしました。コンソールで、くわしいないようを、よむことができます。",
+ "scope_in_timeline": {
+ "private": "フォロワーげんてい",
+ "public": "パブリック",
+ "unlisted": "アンリステッド",
+ "direct": "ダイレクト"
+ },
+ "pin": "ピンどめする",
+ "flash_content": "Flash コンテンツを、 Ruffle をつかってひょうじする (うごかないかもしれません)。",
+ "generic_error_message": "エラーになりました: {0}",
+ "error_retry": "もういちど、ためしてください",
+ "never_show_again": "にどとひょうじしない",
+ "close": "とじる",
+ "dismiss": "むしする",
+ "peek": "かくす"
},
"image_cropper": {
"crop_picture": "がぞうをきりぬく",
@@ -83,11 +123,17 @@
"heading": {
"totp": "2-ファクターにんしょう",
"recovery": "2-ファクターリカバリー"
- }
+ },
+ "logout_confirm_title": "ログアウトのかくにん",
+ "logout_confirm": "ほんとうに、ログアウトしますか?",
+ "logout_confirm_accept_button": "ログアウトする",
+ "logout_confirm_cancel_button": "ログアウトしない"
},
"media_modal": {
"previous": "まえ",
- "next": "つぎ"
+ "next": "つぎ",
+ "counter": "{current} / {total}",
+ "hide": "メディアビューアーをとじる"
},
"nav": {
"about": "これはなに?",
@@ -104,7 +150,21 @@
"user_search": "ユーザーをさがす",
"search": "さがす",
"who_to_follow": "おすすめユーザー",
- "preferences": "せってい"
+ "preferences": "せってい",
+ "home_timeline": "ホームタイムライン",
+ "bookmarks": "ブックマーク",
+ "timelines": "タイムライン",
+ "chats": "チャット",
+ "lists": "リスト",
+ "mobile_notifications": "つうちをひらく (よんでないものがあります)",
+ "mobile_notifications_close": "つうちをとじる",
+ "announcements": "おしらせ",
+ "edit_pinned": "ピンどめをへんしゅう",
+ "search_close": "けんさくバーをとじる",
+ "edit_nav_mobile": "ナビゲーションバーのせっていをかえる",
+ "mobile_sidebar": "モバイルのサイドバーをきりかえる",
+ "edit_finish": "へんしゅうをおわりにする",
+ "mobile_notifications_mark_as_seen": "ぜんぶ みたことにする"
},
"notifications": {
"broken_favorite": "ステータスがみつかりません。さがしています…",
@@ -114,21 +174,35 @@
"notifications": "つうち",
"read": "よんだ!",
"repeated_you": "あなたのステータスがリピートされました",
- "no_more_notifications": "つうちはありません"
+ "no_more_notifications": "つうちはありません",
+ "error": "つうちをとりにいくことに、しっぱいしました: {0}",
+ "follow_request": "あなたをフォローしたいです",
+ "migrated_to": "インスタンスを、ひっこしました",
+ "reacted_with": "{0} でリアクションしました",
+ "poll_ended": "とうひょうが、おわりました",
+ "submitted_report": "つうほうしました",
+ "unread_announcements": "まだ よんでいない おしらせが {num}こ あります",
+ "configuration_tip_settings": "せってい",
+ "configuration_tip_dismiss": "つぎは ひょうじしない",
+ "unread_chats": "よんでいない チャットが {num}こ あります",
+ "unread_follow_requests": "フォローリクエストが {num}こ あります",
+ "configuration_tip": "ここに ひょうじする ものを {theSettings}で へんこうできます。 {dismiss}"
},
"polls": {
- "add_poll": "いれふだをはじめる",
+ "add_poll": "とうひょうをはじめる",
"add_option": "オプションをふやす",
"option": "オプション",
- "votes": "いれふだ",
- "vote": "ふだをいれる",
- "type": "いれふだのかた",
+ "votes": "ひょう",
+ "vote": "とうひょうする",
+ "type": "とうひょうのけいしき",
"single_choice": "ひとつえらぶ",
"multiple_choices": "いくつでもえらべる",
- "expiry": "いれふだのながさ",
- "expires_in": "いれふだは {0} で、おわります",
- "expired": "いれふだは {0} まえに、おわりました",
- "not_enough_options": "ユニークなオプションが、たりません"
+ "expiry": "とうひょうのながさ",
+ "expires_in": "とうひょうは {0} で、おわります",
+ "expired": "とうひょうは {0} まえに、おわりました",
+ "not_enough_options": "ユニークなオプションが、たりません",
+ "people_voted_count": "{count} にんが、とうひょうしました",
+ "votes_count": "{count} ひょう"
},
"emoji": {
"stickers": "ステッカー",
@@ -139,7 +213,20 @@
"custom": "カスタムえもじ",
"unicode": "ユニコードえもじ",
"load_all_hint": "はじめの {saneAmount} このえもじだけがロードされています。すべてのえもじをロードすると、パフォーマンスがわるくなるかもしれません。",
- "load_all": "すべてのえもじをロード ({emojiAmount} こあります)"
+ "load_all": "すべてのえもじをロード ({emojiAmount} こあります)",
+ "unicode_groups": {
+ "flags": "はた",
+ "activities": "かつどう",
+ "animals-and-nature": "どうぶつ・しぜん",
+ "food-and-drink": "たべもの・のみもの",
+ "objects": "もの",
+ "people-and-body": "ひと・からだ",
+ "smileys-and-emotion": "えがお・きもち",
+ "symbols": "きごう",
+ "travel-and-places": "りょこう・ばしょ"
+ },
+ "regional_indicator": "ばしょをしめすきごう {letter}",
+ "unpacked": "アンパックされた えもじ"
},
"stickers": {
"add_sticker": "ステッカーをふやす"
@@ -147,7 +234,10 @@
"interactions": {
"favs_repeats": "リピートとおきにいり",
"follows": "あたらしいフォロー",
- "load_older": "ふるいやりとりをみる"
+ "load_older": "ふるいやりとりをみる",
+ "emoji_reactions": "えもじリアクション",
+ "moves": "ユーザーのひっこし",
+ "reports": "つうほう"
},
"post_status": {
"new_status": "とうこうする",
@@ -176,7 +266,20 @@
"private": "フォロワーげんてい: フォロワーのみにとどきます",
"public": "パブリック: パブリックタイムラインにとどきます",
"unlisted": "アンリステッド: パブリックタイムラインにとどきません"
- }
+ },
+ "media_description_error": "メディアのアップロードにしっぱいしました。もういちどためしてください",
+ "edit_status": "ステータスをへんしゅうする",
+ "media_description": "メディアのせつめい",
+ "content_type_selection": "とうこうのけいしき",
+ "edit_remote_warning": "ほかのリモートインスタンスは、へんしゅうをサポートしていないかもしれません。そして、へんしゅうされたとうこうをうけとることができないかもしれません。",
+ "post": "とうこう",
+ "edit_unsupported_warning": "Pleroma は、メンションやとうひょうのへんしゅうを、サポートしていません。",
+ "preview": "プレビュー",
+ "preview_empty": "なにもありません",
+ "empty_status_error": "とうこうないようを、にゅうりょくしてください",
+ "scope_notice_dismiss": "このつうちをとじる",
+ "reply_option": "この ステータスに へんしんする",
+ "quote_option": "この ステータスを いんようする"
},
"registration": {
"bio": "プロフィール",
@@ -196,8 +299,18 @@
"email_required": "なにかかいてください",
"password_required": "なにかかいてください",
"password_confirmation_required": "なにかかいてください",
- "password_confirmation_match": "パスワードがちがいます"
- }
+ "password_confirmation_match": "パスワードがちがいます",
+ "birthday_required": "なにかかいてください",
+ "birthday_min_age": "{date} か、それよりまえにしてください"
+ },
+ "reason_placeholder": "このインスタンスでは、ひとがかくにんして、とうろくをうけいれています。\nなぜあなたがとうろくしたいのかを、かんりしているひとに、おしえてください。",
+ "bio_optional": "プロフィール (かかなくてもよい)",
+ "reason": "とうろくするりゆう",
+ "email_optional": "Eメール (かかなくてもよい)",
+ "register": "とうろくする",
+ "email_language": "サーバーからのメールは、なにご(どのことば)がいいですか?",
+ "birthday": "たんじょうび:",
+ "birthday_optional": "たんじょうび (かかなくてもよい):"
},
"remote_user_resolver": {
"remote_user_resolver": "リモートユーザーリゾルバー",
@@ -221,7 +334,7 @@
"warning_of_generate_new_codes": "あたらしいリカバリーコードをつくったら、ふるいコードはつかえなくなります。",
"recovery_codes": "リカバリーコード。",
"waiting_a_recovery_codes": "バックアップコードをうけとっています…",
- "recovery_codes_warning": "コードをかきうつすか、ひとにみられないところにセーブしてください。そうでなければ、あなたはこのコードをふたたびみることはできません。もしあなたが、2FAアプリのアクセスをうしなって、なおかつ、リカバリーコードもおもいだせないならば、あなたはあなたのアカウントから、しめだされます。",
+ "recovery_codes_warning": "コードを かきうつすか、 ほかのひとが みれないところに ほぞんしてください。 そうしないと、 あなたは このコードを にどと みることができません。 もし あなたが 2FAアプリに アクセスできなくなり、 リカバリーコードも おもいだせないなら、 あなたは あなたの アカウントに はいれなくなります。",
"authentication_methods": "にんしょうメソッド",
"scan": {
"title": "スキャン",
@@ -393,7 +506,24 @@
"save_load_hint": "「のこす」オプションをONにすると、テーマをえらんだときとロードしたとき、いまのせっていをのこします。また、テーマをエクスポートするとき、これらのオプションをストアします。すべてのチェックボックスをOFFにすると、テーマをエクスポートしたとき、すべてのせっていをセーブします。",
"reset": "リセット",
"clear_all": "すべてクリア",
- "clear_opacity": "とうめいどをクリア"
+ "clear_opacity": "とうめいどをクリア",
+ "help": {
+ "older_version_imported": "ふるいバージョンのフロントエンドでつくられたファイルをインポートしました。",
+ "snapshot_missing": "ファイルにはテーマのスナップショットがありません。おもっていたみためと、ちがうかもしれません。",
+ "migration_snapshot_ok": "あんぜんのため、テーマのスナップショットがよみこまれました。テーマのデータをよみこむことができます。",
+ "snapshot_source_mismatch": "バージョンがただしくないです。フロントエンドのバージョンをもとにもどしたあと、あたらしくしたことが、りゆうかもしれません。ふるいフロントエンドでテーマをへんこうしていたばあい、ふるいバージョンをつかうのがいいです。そうでないばあい、あたらしいバージョンをつかってください。",
+ "snapshot_present": "テーマのスナップショットをよみこみました。せっていはうわがきされました。かわりに、テーマのじっさいのデータをよみこむことができます。",
+ "fe_upgraded": "フロントエンドといっしょに、テーマエンジンもあたらしくなりました。",
+ "fe_downgraded": "フロントエンドが、まえのバージョンにもどりました。",
+ "migration_napshot_gone": "スナップショットがありません。おぼえているみためと、ちがうかもしれません。",
+ "upgraded_from_v2": "PleromaFEがあたらしくなったので、いままでのみためとすこしちがうかもしれません。",
+ "v2_imported": "ふるいフロントエンドのためのファイルをインポートしました。せっていしたのとは、すこしちがうかもしれません。",
+ "future_version_imported": "あたらしいフロントエンドでつくられたファイルをインポートしました。"
+ },
+ "load_theme": "テーマをよみこむ",
+ "keep_as_is": "そのままにする",
+ "use_snapshot": "ふるいバージョン",
+ "use_source": "あたらしいバージョン"
},
"common": {
"color": "いろ",
@@ -429,7 +559,26 @@
"borders": "さかいめ",
"buttons": "ボタン",
"inputs": "インプットフィールド",
- "faint_text": "うすいテキスト"
+ "faint_text": "うすいテキスト",
+ "post": "とうこう / プロフィール",
+ "wallpaper": "かべがみ",
+ "icons": "アイコン",
+ "highlight": "よくみえるようにした、ようそ",
+ "pressed": "おしたとき",
+ "chat": {
+ "border": "さかいめ",
+ "incoming": "うけとったもの",
+ "outgoing": "おくったもの"
+ },
+ "underlay": "アンダーレイ",
+ "alert_neutral": "それいがい",
+ "popover": "ツールチップ、メニュー、ポップオーバー",
+ "poll": "とうひょうのグラフ",
+ "selectedPost": "えらんだとうこう",
+ "selectedMenu": "えらんだメニューアイテム",
+ "disabled": "つかえないとき",
+ "toggled": "きりかえたとき",
+ "tabs": "タブ"
},
"radii": {
"_tab_label": "まるさ"
@@ -462,7 +611,8 @@
"buttonPressed": "ボタン (おされているとき)",
"buttonPressedHover": "ボタン (ホバー、かつ、おされているとき)",
"input": "インプットフィールド"
- }
+ },
+ "hintV3": "かげのばあいは、 {0} というかきかたをつかうことができます。そうすると、ほかのいろのスロットをつかうことができます。"
},
"fonts": {
"_tab_label": "フォント",
@@ -497,7 +647,171 @@
"title": "バージョン",
"backend_version": "バックエンドのバージョン",
"frontend_version": "フロントエンドのバージョン"
- }
+ },
+ "notification_visibility_polls": "あなたがさんかしたとうひょうが、おわりました",
+ "setting_server_side": "このせっていは、あなたのプロフィールについてのものです。へんこうすると、すべてのセッションとクライアントにえいきょうします",
+ "mute_import_error": "ミュートのインポートが、エラーになりました",
+ "account_backup_description": "あなたのアカウントじょうほうや、とうこうのアーカイブを、ダウンロードすることができます。しかし、 Pleroma アカウントにインポートすることはまだできません。",
+ "list_backups_error": "バックアップリストをとりにいくことが、エラーになりました: {error}",
+ "list_aliases_error": "エイリアスをとりにいくときに、エラーになりました: {error}",
+ "added_alias": "エイリアスをつくりました。",
+ "move_account_notes": "もしあなたがアカウントをほかのインスタンスにひっこしたいのなら、ひっこすさきのアカウントからここへのエイリアスをつくってください。",
+ "file_export_import": {
+ "backup_settings_theme": "せっていとテーマをファイルにバックアップする",
+ "restore_settings": "ファイルからせっていをもとにもどす",
+ "errors": {
+ "file_too_new": "メジャーバージョン({fileMajor})がちがいます。この PleromaFE (せっていのバージョン {feMajor}) はふるいので、つかうことができません",
+ "file_slightly_new": "ファイルのマイナーバージョンがちがっています。いくつかのせっていは、よみこまれないかもしれません",
+ "invalid_file": "これは Pleroma のせっていをバックアップしたファイルではありません。",
+ "file_too_old": "メジャーバージョン({fileMajor})がちがいます。ファイルのバージョンが古いので、使うことができません(バージョン {feMajor} いじょうのせっていバージョンをつかってください)"
+ },
+ "backup_settings": "せっていをファイルにバックアップする",
+ "backup_restore": "せっていのバックアップ"
+ },
+ "hide_wallpaper": "このインスタンスのバックグラウンドをかくす",
+ "reply_visibility_following_short": "わたしのフォローしているひとにあてられたリプライをみる",
+ "reply_visibility_self_short": "じぶんにあてられたリプライだけをみる",
+ "save": "へんこうをほぞんする",
+ "reset_banner_confirm": "ほんとうに、バナーをリセットしますか?",
+ "tree_advanced": "ツリービューで、ナビゲーションをもっとじゅうなんにする",
+ "third_column_mode": "じゅうぶんなくうかんがあれば、3ばんめのれつをひょうじする",
+ "conversation_other_replies_button": "「ほかのリプライ」ボタンをひょうじするばしょ",
+ "user_popover_avatar_action_open": "プロフィールをひらく",
+ "notification_setting_filters": "フィルター",
+ "notification_setting_hide_notification_contents": "おくったひとと、ないようを、プッシュつうちにひょうじしない",
+ "backup_running": "バックアップしています。{number}このデータをしょりしました。",
+ "word_filter_and_more": "ことばのフィルターと、そのほか…",
+ "account_privacy": "プライバシー",
+ "posts": "とうこう",
+ "move_account": "アカウントをひっこす",
+ "move_account_target": "ひっこしさきのアカウント (れい: {example})",
+ "mute_bot_posts": "Bot のとうこうをミュートする",
+ "hide_bot_indication": "Bot によるとうこうであることを、とうこうにひょうじしない",
+ "hide_all_muted_posts": "ミュートしたとうこうをかくす",
+ "hide_shoutbox": "Shoutbox をかくす",
+ "conversation_display_tree": "ツリーけいしき",
+ "mention_link_display_full_for_remote": "リモートユーザーだけ、ながいなまえでひょうじする (れい: {'@'}hoge{'@'}example.org)",
+ "mention_link_bolden_you": "あなたがメンションされたとき、あなたへのメンションを、よくみえるようにする",
+ "user_popover_avatar_action": "ポップオーバーのアバターをクリックしたとき",
+ "user_popover_avatar_action_zoom": "アバターをおおきくする",
+ "user_popover_avatar_action_close": "ポップオーバーをとじる",
+ "always_show_post_button": "とうこうボタンをいつもひょうじする",
+ "auto_update": "あたらしいとうこうを、じどうてきにみせる",
+ "user_mutes": "ユーザー",
+ "useStreamingApi": "とうこうとつうちを、リアルタイムにうけとる",
+ "use_websockets": "Websockets をつかう (リアルタイムアップデート)",
+ "mutes_and_blocks": "ミュートとブロック",
+ "emoji_reactions_on_timeline": "えもじリアクションをタイムラインにひょうじする",
+ "accent": "アクセント",
+ "domain_mutes": "ドメイン",
+ "import_mutes_from_a_csv_file": "CSVファイルからミュートをインポートする",
+ "reset_avatar": "アバターをリセットする",
+ "remove_language": "とりのぞく",
+ "primary_language": "さいしょに つかう ことば:",
+ "add_language": "よびとしてつかうことばを、ついかする",
+ "fallback_language": "よびとして つかう ことば {index}:",
+ "lists_navigation": "ナビゲーションにリストをひょうじする",
+ "account_alias": "アカウントのエイリアス",
+ "mention_link_display_full": "いつも、ながいなまえをひょうじする (れい: {'@'}hoge{'@'}example.org)",
+ "setting_changed": "せっていは、デフォルトとちがっています",
+ "email_language": "サーバーからうけとるEメールのことば",
+ "mute_export": "ミュートのエクスポート",
+ "mute_export_button": "あなたのミュートを、 CSV ファイルにエクスポートします",
+ "mute_import": "ミュートのインポート",
+ "mutes_imported": "ミュートをインポートしました!すこしじかんがかかるかもしれません。",
+ "account_backup": "アカウントのバックアップ",
+ "account_backup_table_head": "バックアップ",
+ "download_backup": "ダウンロード",
+ "backup_not_ready": "バックアップのじゅんびが、まだできていません。",
+ "backup_failed": "バックアップにしっぱいしました。",
+ "remove_backup": "とりのぞく",
+ "add_backup": "あたらしいバックアップをつくる",
+ "added_backup": "あたらしいバックアップをつくりました。",
+ "add_backup_error": "あたらしいバックアップをつくるときに、エラーになりました: {error}",
+ "bot": "これは bot アカウントです",
+ "account_alias_table_head": "エイリアス",
+ "hide_list_aliases_error_action": "とじる",
+ "remove_alias": "このエイリアスをけす",
+ "add_alias_error": "エイリアスをつくるときに、エラーになりました: {error}",
+ "new_alias_target": "あたらしいエイリアスをつくる (れい: {example})",
+ "moved_account": "アカウントをひっこしました。",
+ "move_account_error": "アカウントをひっこしているときに、エラーになりました: {error}",
+ "wordfilter": "ことばのフィルター",
+ "hide_media_previews": "メディアのプレビューをかくす",
+ "right_sidebar": "サイドバーをみぎにひょうじする",
+ "hide_wordfiltered_statuses": "ことばのフィルターでフィルターされたステータスをかくす",
+ "hide_muted_threads": "ミュートされたスレッドをかくす",
+ "navbar_column_stretch": "ナビゲーションバーをれつのはばまでのばす",
+ "birthday": {
+ "label": "たんじょうび",
+ "show_birthday": "たんじょうびを、ひょうじする"
+ },
+ "profile_fields": {
+ "label": "プロフィールのメタデータ",
+ "add_field": "フィールドをふやす",
+ "name": "ラベル",
+ "value": "ないよう"
+ },
+ "user_profiles": "ユーザープロフィール",
+ "notification_visibility_moves": "ユーザーのひっこし",
+ "notification_visibility_emoji_reactions": "リアクション",
+ "hide_favorites_description": "おきにいりのリストをみせない (つうちはおくられます)",
+ "reset_profile_background": "プロフィールバックグラウンドをリセットする",
+ "reset_profile_banner": "プロフィールバナーをリセットする",
+ "reset_avatar_confirm": "ほんとうに、アバターをリセットしますか?",
+ "reset_background_confirm": "ほんとうに、バックグラウンドをリセットしますか?",
+ "column_sizes_sidebar": "サイドバー",
+ "column_sizes_notifs": "つうち",
+ "columns": "れつ",
+ "column_sizes": "れつのおおきさ",
+ "column_sizes_content": "コンテンツ",
+ "conversation_display": "スレッドのひょうじけいしき",
+ "conversation_display_linear": "リニアけいしき",
+ "conversation_display_linear_quick": "リニアビュー",
+ "show_scrollbars": "よこのれつにスクロールバーをひょうじする",
+ "third_column_mode_none": "3ばんめのれつをひょうじしない",
+ "third_column_mode_postform": "とうこうフォームとナビゲーション",
+ "third_column_mode_notifications": "つうちのれつをひょうじする",
+ "tree_fade_ancestors": "げんざいのステータスのおやを、うすいいろのもじでひょうじする",
+ "conversation_other_replies_button_below": "ステータスのした",
+ "conversation_other_replies_button_inside": "ステータスのなか",
+ "max_depth_in_thread": "デフォルトでひょうじするスレッドのふかさ",
+ "sensitive_by_default": "デフォルトで、とうこうをNSFWにする",
+ "type_domains_to_mute": "ミュートしたいドメインを、ここでけんさくできます",
+ "mention_link_use_tooltip": "メンションのリンクをクリックしたとき、ユーザーカードをみせる",
+ "mention_link_show_avatar": "ユーザーのアバターをリンクのよこにひょうじする",
+ "mention_link_show_avatar_quick": "ユーザーのアバターをメンションのとなりにひょうじする",
+ "mention_link_fade_domain": "ドメイン(れい: {'@'}hoge{'@'}example.org のなかの {'@'}example.org)を、うすいいろにする",
+ "user_popover_avatar_overlay": "ユーザーのポップオーバーを、ユーザーのアバターのうえにひょうじする",
+ "show_yous": "(あなた)をひょうじする",
+ "notification_setting_block_from_strangers": "フォローしていないユーザーからのつうちをブロックする",
+ "notification_setting_privacy": "プライバシー",
+ "more_settings": "そのたのせってい",
+ "expert_mode": "くわしいせっていを、ひょうじする",
+ "mention_links": "メンションのリンク",
+ "post_look_feel": "とうこうのみためとかんかく",
+ "allow_following_move": "フォローしているアカウントがインスタンスをひっこしたばあい、じどうでフォローしてもよい",
+ "chatMessageRadius": "チャットメッセージ",
+ "confirm_dialogs": "つぎのばあいに、かくにんをする",
+ "confirm_dialogs_repeat": "ステータスをリピートするとき",
+ "confirm_dialogs_unfollow": "ユーザーのフォローをはずすとき",
+ "confirm_dialogs_block": "ユーザーをブロックするとき",
+ "confirm_dialogs_mute": "ユーザーをミュートするとき",
+ "confirm_dialogs_delete": "ステータスをけすとき",
+ "confirm_dialogs_logout": "ログアウトするとき",
+ "confirm_dialogs_approve_follow": "フォローをうけいれるとき",
+ "confirm_dialogs_deny_follow": "フォローをことわるとき",
+ "confirm_dialogs_remove_follower": "フォロワーをとりのぞくとき",
+ "conversation_display_tree_quick": "ツリービュー",
+ "disable_sticky_headers": "れつのヘッダーを、がめんのいちばんうえにこていしない",
+ "virtual_scrolling": "タイムラインのレンダリングをよくする",
+ "use_at_icon": "{'@'} きごうを、もじのかわりに、アイコンでひょうじする",
+ "mention_link_display_short": "いつも、みじかいなまえにする (れい: {'@'}hoge)",
+ "mention_link_display": "メンションのリンクをひょうじするけいしき",
+ "url": "URL",
+ "preview": "プレビュー",
+ "emoji_reactions_scale": "リアクションを なんばいの おおきさで ひょうじするか",
+ "autocomplete_select_first": "じどうほかんが あれば、 さいしょの ものを じどうで えらぶ"
},
"time": {
"day": "{0}日",
@@ -531,7 +845,23 @@
"year": "{0}年",
"years": "{0}年",
"year_short": "{0}年",
- "years_short": "{0}年"
+ "years_short": "{0}年",
+ "unit": {
+ "minutes": "{0}ふん",
+ "seconds_short": "{0}びょう",
+ "weeks": "{0}しゅうかん",
+ "weeks_short": "{0}しゅう",
+ "years": "{0}ねん",
+ "years_short": "{0}ねん",
+ "days": "{0}にち",
+ "days_short": "{0}にち",
+ "hours": "{0}じかん",
+ "hours_short": "{0}じかん",
+ "minutes_short": "{0}ふん",
+ "months": "{0}かげつ",
+ "months_short": "{0}かげつ",
+ "seconds": "{0}びょう"
+ }
},
"timeline": {
"collapse": "たたむ",
@@ -543,7 +873,11 @@
"show_new": "よみこみ",
"up_to_date": "さいしん",
"no_more_statuses": "これでおわりです",
- "no_statuses": "ありません"
+ "no_statuses": "ありません",
+ "socket_broke": "コード{0}により、リアルタイムでつながることがなくなりました",
+ "socket_reconnected": "リアルタイムでつながることを、つくりました",
+ "reload": "もういちど、よみこむ",
+ "error": "タイムラインをとりにいくときに、エラーになりました: {0}"
},
"status": {
"favorites": "おきにいり",
@@ -556,7 +890,57 @@
"reply_to": "へんしん:",
"replies_list": "へんしん:",
"mute_conversation": "スレッドをミュートする",
- "unmute_conversation": "スレッドをミュートするのをやめる"
+ "unmute_conversation": "スレッドをミュートするのをやめる",
+ "repeat_confirm_title": "リピートのかくにん",
+ "mentions": "メンション",
+ "thread_muted": "ミュートされたスレッド",
+ "collapse_attachments": "ファイルをかくす",
+ "remove_attachment": "ファイルをとりのぞく",
+ "thread_show_full": "このスレッドのすべてのとうこうをみる (ぜんぶで{numStatus}このステータス、ふかさ{depth})",
+ "show_all_attachments": "すべてのファイルをみる",
+ "hide_full_subject": "かくす",
+ "nsfw": "NSFW",
+ "hide_content": "かくす",
+ "status_deleted": "このとうこうは、けされました",
+ "you": "(あなた)",
+ "expand": "ひろげる",
+ "repeat_confirm_accept_button": "リピートする",
+ "repeat_confirm_cancel_button": "リピートしない",
+ "edited_at": "({time} まえにへんしゅう)",
+ "delete_confirm_title": "けすことのかくにん",
+ "delete_confirm_accept_button": "けす",
+ "delete_confirm_cancel_button": "のこす",
+ "edit": "ステータスをへんしゅうする",
+ "bookmark": "ブックマークする",
+ "unbookmark": "ブックマークをはずす",
+ "replies_list_with_others": "へんしん (ほかに +{numReplies}こ):",
+ "status_unavailable": "ステータスがありません",
+ "copy_link": "リンクをコピー",
+ "external_source": "そとにあるソース",
+ "thread_muted_and_words": "つぎのことばをふくむので:",
+ "show_content": "みる",
+ "plus_more": "あと {number}こ",
+ "many_attachments": "とうこうには、{number}このファイルがついています",
+ "show_attachment_in_modal": "メディアモーダルでみる",
+ "show_attachment_description": "せつめいのプレビュー (ぜんぶみるには、ファイルをひらいてください)",
+ "hide_attachment": "ファイルをかくす",
+ "attachment_stop_flash": "Flash プレーヤーをとめる",
+ "move_up": "ファイルをひだりにうごかす",
+ "move_down": "ファイルをみぎにうごかす",
+ "open_gallery": "ギャラリーをひらく",
+ "thread_hide": "スレッドをかくす",
+ "thread_show": "スレッドをみる",
+ "show_full_subject": "すべてをみる",
+ "repeat_confirm": "ほんとうに、このステータスをリピートしますか?",
+ "show_all_conversation": "このスレッドをぜんぶみる (あと {numStatus}このステータス)",
+ "show_only_conversation_under_this": "このステータスへのへんしんだけをみる",
+ "status_history": "ステータスのれきし",
+ "thread_show_full_with_icon": "{icon} {text}",
+ "thread_follow": "のこりのとうこうをみる (ぜんぶで {numStatus}このステータス)",
+ "thread_follow_with_icon": "{icon} {text}",
+ "ancestor_follow": "このステータスよりしたの、{numReplies}このへんしんをみる",
+ "ancestor_follow_with_icon": "{icon} {text}",
+ "show_all_conversation_with_icon": "{icon} {text}"
},
"user_card": {
"approve": "うけいれ",
@@ -577,7 +961,7 @@
"media": "メディア",
"mention": "メンション",
"mute": "ミュート",
- "muted": "ミュートしています!",
+ "muted": "ミュートしています",
"per_day": "/日",
"remote_follow": "リモートフォロー",
"report": "つうほう",
@@ -608,8 +992,52 @@
"disable_remote_subscription": "ほかのインスタンスからフォローされないようにする",
"disable_any_subscription": "フォローされないようにする",
"quarantine": "ほかのインスタンスのユーザーのとうこうをとめる",
- "delete_user": "ユーザーをけす"
- }
+ "delete_user": "ユーザーをけす",
+ "delete_user_data_and_deactivate_confirmation": "これをすると、このアカウントのデータがきえて、にどとつかえなくなります。ほんとうに、していいですか?"
+ },
+ "mute_confirm_accept_button": "ミュートする",
+ "unfollow_confirm_title": "フォローをやめることのかくにん",
+ "mute_confirm": "ほんとうに、 {user} をミュートしますか?",
+ "mute_duration_prompt": "このユーザーをつぎのじかんだけミュートする (0にすると、おわりがありません):",
+ "edit_note_apply": "てきよう",
+ "block_confirm": "ほんとうに、 {user} をブロックしますか?",
+ "deactivated": "つかえない",
+ "remove_follower": "フォロワーをとりのぞく",
+ "highlight": {
+ "solid": "バッググラウンドをひとつのいろにする",
+ "striped": "しまもようのバックグラウンドにする",
+ "side": "はじにせんをつける",
+ "disabled": "めだたせない"
+ },
+ "mute_confirm_cancel_button": "ミュートしない",
+ "unfollow_confirm_accept_button": "フォローをやめる",
+ "unfollow_confirm": "ほんとうに、 {user} のフォローをやめますか?",
+ "unfollow_confirm_cancel_button": "フォローしたままにする",
+ "mute_confirm_title": "ミュートのかくにん",
+ "block_confirm_accept_button": "ブロックする",
+ "block_confirm_cancel_button": "ブロックしない",
+ "deny_confirm_title": "おことわりのかくにん",
+ "deny_confirm_accept_button": "ことわる",
+ "deny_confirm_cancel_button": "ことわらない",
+ "deny_confirm": "{user} のフォローリクエストをことわりますか?",
+ "follow_cancel": "リクエストをキャンセル",
+ "birthday": "{birthday} に、うまれました",
+ "remove_follower_confirm_title": "フォロワーをとりのぞくことのかくにん",
+ "remove_follower_confirm_accept_button": "とりのぞく",
+ "remove_follower_confirm_cancel_button": "のこす",
+ "remove_follower_confirm": "ほんとうに、 {user} をあなたのフォロワーからとりのぞきますか?",
+ "edit_note": "メモをへんしゅうする",
+ "edit_note_cancel": "キャンセル",
+ "message": "メッセージ",
+ "bot": "bot",
+ "approve_confirm_title": "うけいれのかくにん",
+ "approve_confirm_accept_button": "うけいれる",
+ "approve_confirm_cancel_button": "うけいれない",
+ "approve_confirm": "{user} のフォローリクエストをうけいれますか?",
+ "edit_profile": "プロフィールをへんしゅう",
+ "block_confirm_title": "ブロックのかくにん",
+ "note_blank": "(なし)",
+ "note": "メモ"
},
"user_profile": {
"timeline_title": "ユーザータイムライン",
@@ -634,13 +1062,21 @@
"repeat": "リピート",
"reply": "リプライ",
"favorite": "おきにいり",
- "user_settings": "ユーザーせってい"
+ "user_settings": "ユーザーせってい",
+ "accept_follow_request": "フォローのおねがいを、うけいれる",
+ "toggle_mute": "ミュートされたないようをみるために、つうちをひらくか、とじる",
+ "autocomplete_available": "{number}このけっかが、あります。うえとしたのキーをつかって、けっかをみることができます。",
+ "add_reaction": "リアクションをつける",
+ "reject_follow_request": "フォローのおねがいを、ことわる",
+ "bookmark": "ブックマーク",
+ "toggle_expand": "とうこうをすべてみるために、つうちをひらくか、とじる"
},
"upload": {
"error": {
"base": "アップロードにしっぱいしました。",
"file_too_big": "ファイルがおおきすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
- "default": "しばらくしてから、ためしてください"
+ "default": "しばらくしてから、ためしてください",
+ "message": "アップロードにしっぱいしました: {0}"
},
"file_size_units": {
"B": "B",
@@ -655,7 +1091,9 @@
"hashtags": "ハッシュタグ",
"person_talking": "{count} にんが、はなしています",
"people_talking": "{count} にんが、はなしています",
- "no_results": "みつかりませんでした"
+ "no_results": "みつかりませんでした",
+ "no_more_results": "これでおわりです",
+ "load_more": "もっとみる"
},
"password_reset": {
"forgot_password": "パスワードを、わすれましたか?",
@@ -668,5 +1106,103 @@
"password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。",
"password_reset_required": "ログインするには、パスワードをリセットしてください。",
"password_reset_required_but_mailer_is_disabled": "あなたはパスワードのリセットがひつようです。しかし、まずいことに、このインスタンスでは、パスワードのリセットができなくなっています。このインスタンスのアドミニストレーターに、おといあわせください。"
+ },
+ "announcements": {
+ "post_placeholder": "おしらせのないようを、にゅうりょくしてください。",
+ "end_time_prompt": "おわるじかん: ",
+ "inactive_message": "このおしらせは、つかわれていません",
+ "page_header": "おしらせ",
+ "title": "おしらせ",
+ "post_action": "とうこう",
+ "post_form_header": "おしらせをとうこう",
+ "mark_as_read_action": "よんだことにする",
+ "post_error": "エラー: {error}",
+ "close_error": "とじる",
+ "delete_action": "けす",
+ "start_time_display": "{time}にはじまります",
+ "end_time_display": "{time}におわります",
+ "edit_action": "へんしゅう",
+ "start_time_prompt": "はじまるじかん: ",
+ "all_day_prompt": "このイベントはいちにちじゅうやります",
+ "published_time_display": "{time}にこうかいされました",
+ "submit_edit_action": "そうしん",
+ "cancel_edit_action": "キャンセル"
+ },
+ "report": {
+ "reported_statuses": "つうほうされたステータス:",
+ "reporter": "つうほうしたひと:",
+ "state_closed": "クローズ",
+ "state_resolved": "かいけつしました",
+ "reported_user": "つうほうされたユーザー:",
+ "notes": "メモ:",
+ "state": "じょうたい:",
+ "state_open": "オープン"
+ },
+ "update": {
+ "update_bugs": "もんだいや、バグがあれば、 {pleromaGitlab} でおしえてください。ちゃんとテストはしているのですが、たくさんのことをかえているので、そしてかいはつバージョンをつかっているので、もんだいやバグに、きづかないことがあります。あなたがきづいたもんだいについての、フィードバックやていあんを、まっています。 Pleroma や Pleroma-FE をよくするやりかたについても、おしえてください。",
+ "update_changelog_here": "すべてのかわったことのきろく",
+ "art_by": "{linkToArtist}によるさくひん",
+ "big_update_title": "すこし、まってください",
+ "big_update_content": "しばらくリリースがありませんでした。おもっていたみためと、ちがうかもしれません。",
+ "update_bugs_gitlab": "Pleroma GitLab",
+ "update_changelog": "かわったことをすべてみるには、{theFullChangelog}をみてください。"
+ },
+ "chats": {
+ "new": "あたらしいチャット",
+ "chats": "チャット",
+ "you": "あなた:",
+ "message_user": "{nickname} にメッセージ",
+ "delete": "けす",
+ "empty_message_error": "なにかかいてください",
+ "more": "もっとみる",
+ "delete_confirm": "ほんとうに、このメッセージをけしますか?",
+ "error_loading_chat": "チャットをよみこむことに、しっぱいしました。",
+ "error_sending_message": "メッセージをおくることに、しっぱいしました。",
+ "empty_chat_list_placeholder": "チャットがありません。あたらしいチャットボタンをおして、はじめてください!"
+ },
+ "shoutbox": {
+ "title": "Shoutbox"
+ },
+ "errors": {
+ "storage_unavailable": "Pleroma はブラウザーのストレージにアクセスすることができません。あなたがログインしたことと、あなたのローカルのせっていは、ほぞんされません。ほかにももんだいがおきるかもしれません。 Cookie をゆうこうにしてください。"
+ },
+ "lists": {
+ "lists": "リスト",
+ "new": "あたらしいリスト",
+ "search": "ユーザーをさがす",
+ "title": "リストのなまえ",
+ "create": "つくる",
+ "save": "へんこうをほぞんする",
+ "delete": "リストをけす",
+ "following_only": "フォローしているひとげんていにする",
+ "manage_lists": "リストをかんりする",
+ "manage_members": "リストにふくまれるひとを、かんりする",
+ "add_members": "もっとユーザーをさがす",
+ "remove_from_list": "リストからとりのぞく",
+ "add_to_list": "リストにいれる",
+ "editing_list": "リスト {listTitle} をへんしゅうしています",
+ "creating_list": "あたらしいリストをつくっています",
+ "update_title": "なまえをほぞんする",
+ "really_delete": "ほんとうに、リストをけしますか?",
+ "is_in_list": "すでにリストのなかにあります",
+ "error": "リストをへんしゅうするときに、エラーになりました: {0}"
+ },
+ "file_type": {
+ "audio": "オーディオ",
+ "video": "ビデオ",
+ "image": "がぞう",
+ "file": "ファイル"
+ },
+ "display_date": {
+ "today": "きょう"
+ },
+ "unicode_domain_indicator": {
+ "tooltip": "このドメインは、ASCIIいがいのもじをふくんでいます。"
+ },
+ "domain_mute_card": {
+ "mute": "ミュート",
+ "mute_progress": "ミュートしています…",
+ "unmute": "ミュートをやめる",
+ "unmute_progress": "ミュートをやめています…"
}
}
diff --git a/src/i18n/ja_pedantic.json b/src/i18n/ja_pedantic.json
@@ -4,36 +4,37 @@
},
"exporter": {
"export": "エクスポート",
- "processing": "処理中です。処理が完了すると、ファイルをダウンロードするよう指示があります"
+ "processing": "処理中です。処理が完了すると、ファイルのダウンロードが開始します"
},
"features_panel": {
"chat": "チャット",
"gopher": "Gopher",
- "media_proxy": "メディアプロクシ",
+ "media_proxy": "メディアプロキシ",
"scope_options": "公開範囲選択",
- "text_limit": "文字の数",
+ "text_limit": "文字数制限",
"title": "有効な機能",
"who_to_follow": "おすすめユーザー",
"upload_limit": "ファイルサイズの上限",
- "pleroma_chat_messages": "Pleroma チャット"
+ "pleroma_chat_messages": "Pleroma チャット",
+ "shout": "Shoutbox"
},
"finder": {
- "error_fetching_user": "ユーザー検索がエラーになりました",
+ "error_fetching_user": "ユーザーの取得に失敗しました",
"find_user": "ユーザーを探す"
},
"general": {
"apply": "適用",
"submit": "送信",
- "more": "続き",
- "generic_error": "エラーになりました",
- "optional": "省略可",
+ "more": "もっと",
+ "generic_error": "エラーが発生しました",
+ "optional": "任意",
"show_more": "もっと見る",
"show_less": "たたむ",
"cancel": "キャンセル",
"disable": "無効",
"enable": "有効",
"confirm": "確認",
- "verify": "検査",
+ "verify": "検証",
"peek": "隠す",
"close": "閉じる",
"dismiss": "無視",
@@ -46,7 +47,21 @@
},
"flash_security": "Flashコンテンツが任意の命令を実行させることにより、コンピューターが危険にさらされることがあります。",
"flash_fail": "Flashコンテンツの読み込みに失敗しました。コンソールで詳細を確認できます。",
- "flash_content": "(試験的機能)クリックしてFlashコンテンツを再生します。"
+ "flash_content": "(試験的機能) クリックしてFlashコンテンツを再生します。",
+ "yes": "はい",
+ "no": "いいえ",
+ "scroll_to_top": "最上部へスクロール",
+ "unpin": "ピン留めを外す",
+ "pin": "ピン留めする",
+ "scope_in_timeline": {
+ "direct": "ダイレクト",
+ "private": "フォロワー限定",
+ "public": "パブリック",
+ "unlisted": "アンリステッド"
+ },
+ "generic_error_message": "エラーが発生しました: {0}",
+ "never_show_again": "二度と表示しない",
+ "undo": "取り消す"
},
"image_cropper": {
"crop_picture": "画像を切り抜く",
@@ -57,7 +72,7 @@
"importer": {
"submit": "送信",
"success": "正常にインポートされました。",
- "error": "このファイルをインポートするとき、エラーが発生しました。"
+ "error": "ファイルのインポート中にエラーが発生しました。"
},
"login": {
"login": "ログイン",
@@ -69,30 +84,36 @@
"username": "ユーザー名",
"hint": "会話に加わるには、ログインしてください",
"authentication_code": "認証コード",
- "enter_recovery_code": "リカバリーコードを入力してください",
- "enter_two_factor_code": "2段階認証コードを入力してください",
+ "enter_recovery_code": "リカバリーコードを入力",
+ "enter_two_factor_code": "二段階認証コードを入力",
"recovery_code": "リカバリーコード",
"heading": {
- "totp": "2段階認証",
- "recovery": "2段階リカバリー"
- }
+ "totp": "二段階認証",
+ "recovery": "二段階認証リカバリー"
+ },
+ "logout_confirm": "本当にログアウトしますか?",
+ "logout_confirm_accept_button": "ログアウト",
+ "logout_confirm_cancel_button": "ログアウトしない",
+ "logout_confirm_title": "ログアウトの確認"
},
"media_modal": {
- "previous": "前",
- "next": "次"
+ "previous": "前へ",
+ "next": "次へ",
+ "hide": "メディアビューアを閉じる",
+ "counter": "{current} / {total}"
},
"nav": {
"about": "このインスタンスについて",
"back": "戻る",
"chat": "ローカルチャット",
"friend_requests": "フォローリクエスト",
- "mentions": "通知",
- "interactions": "インタラクション",
+ "mentions": "メンション",
+ "interactions": "通知",
"dms": "ダイレクトメッセージ",
"public_tl": "公開タイムライン",
"timeline": "タイムライン",
"twkn": "すべてのネットワーク",
- "user_search": "ユーザーを探す",
+ "user_search": "ユーザー検索",
"search": "検索",
"who_to_follow": "おすすめユーザー",
"preferences": "設定",
@@ -100,21 +121,38 @@
"bookmarks": "ブックマーク",
"timelines": "タイムライン",
"chats": "チャット",
- "home_timeline": "ホームタイムライン"
+ "home_timeline": "ホームタイムライン",
+ "mobile_notifications_mark_as_seen": "すべて既読にする",
+ "search_close": "検索バーを閉じる",
+ "lists": "リスト",
+ "edit_nav_mobile": "ナビゲーションバーを編集",
+ "edit_pinned": "ピン留めを編集",
+ "edit_finish": "完了",
+ "mobile_notifications": "通知を開く (未読あり)",
+ "mobile_notifications_close": "通知を閉じる",
+ "announcements": "お知らせ"
},
"notifications": {
"broken_favorite": "ステータスが見つかりません。探しています…",
- "favorited_you": "あなたのステータスがお気に入りされました",
+ "favorited_you": "ステータスがお気に入りされました",
"followed_you": "フォローされました",
- "load_older": "古い通知をみる",
+ "load_older": "古い通知を読み込む",
"notifications": "通知",
"read": "読んだ!",
- "repeated_you": "あなたのステータスがリピートされました",
+ "repeated_you": "ステータスがリピートされました",
"no_more_notifications": "通知はありません",
"reacted_with": "{0} でリアクションしました",
"migrated_to": "インスタンスを引っ越しました",
- "follow_request": "あなたをフォローしたいです",
- "error": "通知の取得に失敗しました: {0}"
+ "follow_request": "あなたをフォローしたがっています",
+ "error": "通知の取得に失敗しました: {0}",
+ "poll_ended": "投票結果が確定しました",
+ "configuration_tip_dismiss": "二度と表示しない",
+ "unread_announcements": "未読のお知らせが{num}件あります | 未読のお知らせが{num}件あります",
+ "unread_chats": "未読のチャットが{num}件あります | 未読のチャットが{num}件あります",
+ "unread_follow_requests": "フォローリクエストが{num}件来ています | フォローリクエストが{num}件来ています",
+ "configuration_tip": "ここに表示する通知の種類は{theSettings}にて変更することができます。 {dismiss}",
+ "submitted_report": "通報が送信されました",
+ "configuration_tip_settings": "設定"
},
"polls": {
"add_poll": "投票を追加",
@@ -128,20 +166,22 @@
"expiry": "投票期間",
"expires_in": "投票は {0} で終了します",
"expired": "投票は {0} 前に終了しました",
- "not_enough_options": "相異なる選択肢が不足しています",
+ "not_enough_options": "選択肢が少なすぎます",
"votes_count": "{count} 票 | {count} 票",
- "people_voted_count": "{count} 人投票 | {count} 人投票"
+ "people_voted_count": "{count}人が投票しました | {count}人が投票しました"
},
"emoji": {
"stickers": "ステッカー",
"emoji": "絵文字",
- "keep_open": "ピッカーを開いたままにする",
+ "keep_open": "絵文字ピッカーを開いたままにする",
"search_emoji": "絵文字を検索",
"add_emoji": "絵文字を挿入",
"custom": "カスタム絵文字",
"unicode": "Unicode絵文字",
"load_all": "全 {emojiAmount} 絵文字を読み込む",
- "load_all_hint": "最初の {saneAmount} 絵文字を読み込みました、全て読み込むと重くなる可能性があります。"
+ "load_all_hint": "最初の {saneAmount} 件の絵文字を読み込みました。すべて読み込むとパフォーマンスに影響を与える可能性があります。",
+ "unpacked": "パック外の絵文字",
+ "hide_custom_emoji": "カスタム絵文字を表示しない"
},
"stickers": {
"add_sticker": "ステッカーを追加"
@@ -149,30 +189,32 @@
"interactions": {
"favs_repeats": "リピートとお気に入り",
"follows": "新しいフォロワー",
- "load_older": "古いインタラクションを見る",
- "moves": "ユーザーの引っ越し"
+ "load_older": "古い通知を読み込む",
+ "moves": "ユーザーの引っ越し",
+ "emoji_reactions": "絵文字リアクション",
+ "reports": "通報"
},
"post_status": {
"new_status": "投稿する",
- "account_not_locked_warning": "あなたのアカウントは {0} ではありません。あなたをフォローすれば、誰でも、フォロワー限定のステータスを読むことができます。",
- "account_not_locked_warning_link": "ロックされたアカウント",
- "attachments_sensitive": "ファイルをNSFWにする",
+ "account_not_locked_warning": "あなたのアカウントは {0} ではありません。あなたをフォローすれば、誰でもフォロワー限定のステータスを読むことができます。",
+ "account_not_locked_warning_link": "鍵アカウント",
+ "attachments_sensitive": "ファイルを閲覧注意に設定する",
"content_type": {
"text/plain": "プレーンテキスト",
"text/html": "HTML",
"text/markdown": "Markdown",
"text/bbcode": "BBCode"
},
- "content_warning": "説明 (省略可)",
+ "content_warning": "注釈 (任意)",
"default": "羽田空港に着きました。",
- "direct_warning_to_all": "この投稿は、メンションされたすべてのユーザーが、見ることができます。",
- "direct_warning_to_first_only": "この投稿は、メッセージの冒頭でメンションされたユーザーだけが、見ることができます。",
+ "direct_warning_to_all": "この投稿は、メンションされたすべてのユーザーが閲覧できます。",
+ "direct_warning_to_first_only": "この投稿は、メッセージの冒頭でメンションされたユーザーだけが閲覧できます。",
"direct_warning": "このステータスは、メンションされたユーザーだけが、読むことができます。",
"posting": "投稿",
"scope_notice": {
- "public": "この投稿は、誰でも見ることができます",
- "private": "この投稿は、あなたのフォロワーだけが、見ることができます",
- "unlisted": "この投稿は、パブリックタイムラインと、接続しているすべてのネットワークには、表示されません"
+ "public": "この投稿は誰でも閲覧できます",
+ "private": "この投稿はフォロワーのみ閲覧できます",
+ "unlisted": "この投稿は、公開タイムラインとすべてのネットワークには表示されません"
},
"scope": {
"direct": "ダイレクト: メンションされたユーザーのみに届きます",
@@ -180,22 +222,29 @@
"public": "パブリック: 公開タイムラインに届きます",
"unlisted": "アンリステッド: 公開タイムラインに届きません"
},
- "media_description_error": "メディアのアップロードに失敗しました。もう一度お試しください",
+ "media_description_error": "メディアのアップデートに失敗しました。もう一度お試しください",
"empty_status_error": "投稿内容を入力してください",
"preview_empty": "何もありません",
"preview": "プレビュー",
"media_description": "メディアの説明",
- "post": "投稿"
+ "post": "投稿",
+ "edit_status": "ステータスを編集",
+ "reply_option": "このステータスに返信する",
+ "quote_option": "このステータスを引用する",
+ "edit_remote_warning": "他のインスタンスは投稿の編集に対応していないかもしれません。その場合、編集した内容は伝わりません。",
+ "edit_unsupported_warning": "Pleromaは、メンションと投票の編集に対応していません。",
+ "scope_notice_dismiss": "このメッセージを閉じる",
+ "content_type_selection": "投稿形式"
},
"registration": {
"bio": "プロフィール",
- "email": "Eメール",
- "fullname": "スクリーンネーム",
+ "email": "メールアドレス",
+ "fullname": "表示名",
"password_confirm": "パスワードの確認",
"registration": "登録",
- "token": "招待トークン",
+ "token": "招待コード",
"captcha": "CAPTCHA",
- "new_captcha": "文字が読めないときは、画像をクリックすると、新しい画像になります",
+ "new_captcha": "文字が読めない場合、画像をクリックすると新しい画像が表示されます",
"username_placeholder": "例: lain",
"fullname_placeholder": "例: 岩倉玲音",
"bio_placeholder": "例:\nこんにちは。私は玲音。\n私はアニメのキャラクターで、日本の郊外に住んでいます。私をWiredで見たことがあるかもしれません。",
@@ -205,11 +254,18 @@
"email_required": "必須",
"password_required": "必須",
"password_confirmation_required": "必須",
- "password_confirmation_match": "パスワードが違います"
+ "password_confirmation_match": "パスワードが一致しません",
+ "birthday_required": "必須",
+ "birthday_min_age": "{date} 以降のユーザーは登録できません"
},
- "reason_placeholder": "このインスタンスは、新規登録を手動で受け付けています。\n登録したい理由を、インスタンスの管理者に教えてください。",
- "reason": "登録するための目的",
- "register": "登録"
+ "reason_placeholder": "このインスタンスは、新規登録を手動で承認しています。\n登録したい理由をインスタンスの管理者に教えてください。",
+ "reason": "登録を希望する理由",
+ "register": "登録",
+ "email_language": "このサーバーからのメールをどの言語で受け取りますか?",
+ "bio_optional": "プロフィール (任意)",
+ "email_optional": "メールアドレス (任意)",
+ "birthday": "誕生日:",
+ "birthday_optional": "誕生日 (任意):"
},
"selectable_list": {
"select_all": "すべて選択"
@@ -223,44 +279,44 @@
"setup_otp": "OTPのセットアップ",
"wait_pre_setup_otp": "OTPのプリセット",
"confirm_and_enable": "OTPの確認と有効化",
- "title": "2段階認証",
+ "title": "二段階認証",
"generate_new_recovery_codes": "新しいリカバリーコードを生成",
"warning_of_generate_new_codes": "新しいリカバリーコードを生成すると、古いコードは使用できなくなります。",
"recovery_codes": "リカバリーコード。",
"waiting_a_recovery_codes": "バックアップコードを受信しています…",
- "recovery_codes_warning": "コードを紙に書くか、安全な場所に保存してください。そうでなければ、あなたはコードを再び見ることはできません。もし2段階認証アプリのアクセスを喪失し、なおかつ、リカバリーコードもないならば、あなたは自分のアカウントから閉め出されます。",
+ "recovery_codes_warning": "リカバリーコードをどこか安全な場所に書き留めてください。このコードは二度と表示されません。二段階認証アプリへのアクセスを失い、リカバリーコードも紛失した場合、二度とアカウントにログインできなくなります。",
"authentication_methods": "認証方法",
"scan": {
"title": "スキャン",
- "desc": "あなたの2段階認証アプリを使って、このQRコードをスキャンするか、テキストキーを入力してください:",
+ "desc": "二段階認証アプリでQRコードを読み取るか、テキストキーを入力してください:",
"secret_code": "キー"
},
"verify": {
- "desc": "2段階認証を有効にするには、あなたの2段階認証アプリのコードを入力してください:"
+ "desc": "二段階認証を有効にするには、二段階認証アプリに表示されたコードを入力してください:"
}
},
"attachmentRadius": "ファイル",
"attachments": "ファイル",
- "avatar": "アバター",
- "avatarAltRadius": "通知のアバター",
- "avatarRadius": "アバター",
+ "avatar": "アイコン",
+ "avatarAltRadius": "通知内のアイコン",
+ "avatarRadius": "アイコン",
"background": "バックグラウンド",
"bio": "プロフィール",
"block_export": "ブロックのエクスポート",
"block_export_button": "ブロックをCSVファイルにエクスポートする",
"block_import": "ブロックのインポート",
"block_import_error": "ブロックのインポートに失敗しました",
- "blocks_imported": "ブロックをインポートしました! 実際に処理されるまでに、しばらく時間がかかります。",
+ "blocks_imported": "ブロックがインポートされました。処理には時間がかかる場合があります。",
"blocks_tab": "ブロック",
"btnRadius": "ボタン",
"cBlue": "返信とフォロー",
"cGreen": "リピート",
"cOrange": "お気に入り",
"cRed": "キャンセル",
- "change_password": "パスワードを変える",
- "change_password_error": "パスワードを変えることが、できなかったかもしれません。",
- "changed_password": "パスワードが、変わりました!",
- "collapse_subject": "説明のある投稿をたたむ",
+ "change_password": "パスワードを変更",
+ "change_password_error": "パスワードの変更中にエラーが発生しました。",
+ "changed_password": "パスワードが変更されました!",
+ "collapse_subject": "注釈のついた投稿をたたむ",
"composing": "投稿",
"confirm_new_password": "新しいパスワードの確認",
"current_avatar": "現在のアバター",
@@ -268,52 +324,52 @@
"current_profile_banner": "現在のプロフィールバナー",
"data_import_export_tab": "インポートとエクスポート",
"default_vis": "デフォルトの公開範囲",
- "delete_account": "アカウントを消す",
- "delete_account_description": "あなたのデータが消えて、アカウントが使えなくなります。",
- "delete_account_error": "アカウントを消すことが、できなかったかもしれません。インスタンスの管理者に、連絡してください。",
- "delete_account_instructions": "本当にアカウントを消してもいいなら、パスワードを入力してください。",
+ "delete_account": "アカウントの削除",
+ "delete_account_description": "アカウントのデータを永久的に削除し、アカウントを無効化します。",
+ "delete_account_error": "アカウントの削除中にエラーが発生しました。継続して発生する場合、管理者に問い合せてください。",
+ "delete_account_instructions": "アカウント削除の確認のため、パスワードを入力してください。",
"discoverable": "検索などのサービスでこのアカウントを見つけることを許可する",
- "avatar_size_instruction": "アバターの大きさは、150×150ピクセルか、それよりも大きくするといいです。",
- "pad_emoji": "ピッカーから絵文字を挿入するとき、絵文字の両側にスペースを入れる",
- "export_theme": "保存",
+ "avatar_size_instruction": "アイコン画像のサイズは150x150以上を推奨します。",
+ "pad_emoji": "絵文字ピッカーから絵文字を挿入するとき、絵文字の前後に空白を挿入する",
+ "export_theme": "ファイルにテーマを出力",
"filtering": "フィルタリング",
- "filtering_explanation": "これらの言葉を含むすべてのものがミュートされます。1行に1つの言葉を書いてください",
+ "filtering_explanation": "これらの単語を含むステータスはミュートされます。(1行に1単語)",
"follow_export": "フォローのエクスポート",
"follow_export_button": "エクスポート",
"follow_export_processing": "お待ちください。まもなくファイルをダウンロードできます。",
"follow_import": "フォローのインポート",
- "follow_import_error": "フォローのインポートがエラーになりました",
- "follows_imported": "フォローがインポートされました! 少し時間がかかるかもしれません。",
+ "follow_import_error": "フォローのインポートに失敗しました",
+ "follows_imported": "フォローがインポートされました。処理には時間がかかる場合があります。",
"foreground": "フォアグラウンド",
"general": "全般",
- "hide_attachments_in_convo": "スレッドのファイルを隠す",
- "hide_attachments_in_tl": "タイムラインのファイルを隠す",
- "hide_muted_posts": "ミュートしているユーザーの投稿を隠す",
- "max_thumbnails": "投稿に含まれるサムネイルの最大数",
- "hide_isp": "インスタンス固有パネルを隠す",
+ "hide_attachments_in_convo": "スレッド内のファイルを表示しない",
+ "hide_attachments_in_tl": "タイムラインのファイルを表示しない",
+ "hide_muted_posts": "ミュートしているユーザーの投稿を表示しない",
+ "max_thumbnails": "投稿に表示するサムネイルの最大数 (空にすると無制限)",
+ "hide_isp": "インスタンス固有パネルを表示しない",
"preload_images": "画像を先読みする",
- "use_one_click_nsfw": "NSFWなファイルを1クリックで開く",
- "hide_post_stats": "投稿の統計を隠す (例: お気に入りの数)",
- "hide_user_stats": "ユーザーの統計を隠す (例: フォロワーの数)",
- "hide_filtered_statuses": "フィルターされた投稿を隠す",
+ "use_one_click_nsfw": "閲覧注意なファイルを1クリックで開く",
+ "hide_post_stats": "投稿の統計を表示しない (例: お気に入りの数)",
+ "hide_user_stats": "ユーザーの統計を表示しない (例: フォロワーの数)",
+ "hide_filtered_statuses": "フィルタリングされた投稿を表示しない",
"import_blocks_from_a_csv_file": "CSVファイルからブロックをインポートする",
"import_followers_from_a_csv_file": "CSVファイルからフォローをインポートする",
- "import_theme": "ロード",
- "inputRadius": "インプットフィールド",
+ "import_theme": "ファイルからテーマを読み込む",
+ "inputRadius": "入力欄",
"checkboxRadius": "チェックボックス",
"instance_default": "(デフォルト: {value})",
"instance_default_simple": "(デフォルト)",
"interface": "インターフェース",
"interfaceLanguage": "インターフェースの言語",
- "invalid_theme_imported": "このファイルはPleromaのテーマではありません。テーマは変更されませんでした。",
- "limited_availability": "あなたのブラウザではできません",
+ "invalid_theme_imported": "非対応の形式のテーマファイルです。テーマは変更されませんでした。",
+ "limited_availability": "非対応のブラウザです",
"links": "リンク",
- "lock_account_description": "あなたが認めた人だけ、あなたのアカウントをフォローできる",
- "loop_video": "ビデオを繰り返す",
- "loop_video_silent_only": "音のないビデオだけ繰り返す",
+ "lock_account_description": "フォローを承認制にする",
+ "loop_video": "動画をループ再生する",
+ "loop_video_silent_only": "音声のない動画のみループ再生する",
"mutes_tab": "ミュート",
- "play_videos_in_modal": "ビデオをメディアビューアーで見る",
- "use_contain_fit": "画像のサムネイルを、切り抜かない",
+ "play_videos_in_modal": "動画をメディアビューアで再生する",
+ "use_contain_fit": "画像のサムネイルを切り抜かない",
"name": "名前",
"name_bio": "名前とプロフィール",
"new_password": "新しいパスワード",
@@ -322,55 +378,55 @@
"notification_visibility_likes": "お気に入り",
"notification_visibility_mentions": "メンション",
"notification_visibility_repeats": "リピート",
- "no_rich_text_description": "リッチテキストを使わない",
- "no_blocks": "ブロックはありません",
- "no_mutes": "ミュートはありません",
- "hide_follows_description": "フォローしている人を見せない",
- "hide_followers_description": "フォロワーを見せない",
- "hide_follows_count_description": "フォローしている人の数を見せない",
- "hide_followers_count_description": "フォロワーの数を見せない",
- "show_admin_badge": "\"管理者\"のバッジを見せる",
- "show_moderator_badge": "\"モデレーター\"のバッジを見せる",
- "nsfw_clickthrough": "NSFWなファイルを隠す",
+ "no_rich_text_description": "投稿のテキスト装飾を無効化する",
+ "no_blocks": "ブロックしたユーザーはいません",
+ "no_mutes": "ミュートしたユーザーはいません",
+ "hide_follows_description": "フォロー欄を非公開にする",
+ "hide_followers_description": "フォロワー欄を非公開にする",
+ "hide_follows_count_description": "フォロー数を非公開にする",
+ "hide_followers_count_description": "フォロワー数を非公開にする",
+ "show_admin_badge": "プロフィールに「管理者」バッジを表示する",
+ "show_moderator_badge": "プロフィールに「モデレーター」バッジを表示する",
+ "nsfw_clickthrough": "閲覧注意なファイルを隠す",
"oauth_tokens": "OAuthトークン",
"token": "トークン",
"refresh_token": "トークンを更新",
- "valid_until": "まで有効",
+ "valid_until": "有効期限",
"revoke_token": "取り消す",
"panelRadius": "パネル",
- "pause_on_unfocused": "タブにフォーカスがないときストリーミングを止める",
+ "pause_on_unfocused": "タブにフォーカスがないとき、タイムラインの自動更新を止める",
"presets": "プリセット",
"profile_background": "プロフィールの背景",
"profile_banner": "プロフィールのバナー",
"profile_tab": "プロフィール",
- "radii_help": "インターフェースの丸さを設定する",
- "replies_in_timeline": "タイムラインのリプライ",
- "reply_visibility_all": "すべてのリプライを見る",
- "reply_visibility_following": "私に宛てられたリプライと、フォローしている人からのリプライを見る",
- "reply_visibility_self": "私に宛てられたリプライを見る",
- "autohide_floating_post_button": "新しい投稿ボタンを自動的に隠す (モバイル)",
+ "radii_help": "インターフェースの角丸を設定する (ピクセル単位)",
+ "replies_in_timeline": "タイムライン上の返信",
+ "reply_visibility_all": "すべての返信を表示する",
+ "reply_visibility_following": "自分、もしくはフォローしているユーザー宛ての返信のみを表示する",
+ "reply_visibility_self": "自分に宛てられた返信のみを表示する",
+ "autohide_floating_post_button": "投稿ボタンを自動的に隠す (モバイル)",
"saving_err": "設定を保存できませんでした",
"saving_ok": "設定を保存しました",
"search_user_to_block": "ブロックしたいユーザーを検索",
"search_user_to_mute": "ミュートしたいユーザーを検索",
"security_tab": "セキュリティ",
- "scope_copy": "返信するとき、公開範囲をコピーする (DMの公開範囲は、常にコピーされます)",
- "minimal_scopes_mode": "公開範囲選択オプションを最小にする",
- "set_new_avatar": "新しいアバターを設定する",
- "set_new_profile_background": "新しいプロフィールのバックグラウンドを設定する",
- "set_new_profile_banner": "新しいプロフィールバナーを設定する",
+ "scope_copy": "返信の公開範囲を返信先に合わせる",
+ "minimal_scopes_mode": "公開範囲選択オプションを最小化する",
+ "set_new_avatar": "アイコンを設定する",
+ "set_new_profile_background": "プロフィールの背景を設定する",
+ "set_new_profile_banner": "プロフィールのバナーを設定する",
"settings": "設定",
- "subject_input_always_show": "サブジェクトフィールドをいつでも表示する",
- "subject_line_behavior": "返信するときサブジェクトをコピーする",
- "subject_line_email": "メール風: \"re: サブジェクト\"",
- "subject_line_mastodon": "マストドン風: そのままコピー",
+ "subject_input_always_show": "注釈欄を常に表示する",
+ "subject_line_behavior": "返信するとき、返信先の注釈をコピーする",
+ "subject_line_email": "メール風: \"re: 注釈\"",
+ "subject_line_mastodon": "Mastodon風: そのままコピー",
"subject_line_noop": "コピーしない",
- "post_status_content_type": "投稿のコンテントタイプ",
- "stop_gifs": "カーソルを重ねたとき、GIFを動かす",
- "streaming": "上までスクロールしたとき、自動的にストリーミングする",
+ "post_status_content_type": "デフォルトの投稿形式",
+ "stop_gifs": "GIFを自動再生しない",
+ "streaming": "上までスクロールしたとき、自動でタイムラインを更新する",
"text": "文字",
"theme": "テーマ",
- "theme_help": "カラーテーマをカスタマイズできます。",
+ "theme_help": "カラーコード(#rrggbb)を使用してカラーテーマをカスタマイズできます。",
"theme_help_v2_1": "チェックボックスをONにすると、コンポーネントごとに、色と透明度をオーバーライドできます。「すべてクリア」ボタンを押すと、すべてのオーバーライドをやめます。",
"theme_help_v2_2": "バックグラウンドとテキストのコントラストを表すアイコンがあります。マウスをホバーすると、詳しい説明が出ます。透明な色を使っているときは、最悪の場合のコントラストが示されます。",
"tooltipRadius": "ツールチップとアラート",
@@ -381,9 +437,9 @@
"true": "はい"
},
"notifications": "通知",
- "notification_mutes": "特定のユーザーからの通知を止めるには、ミュートしてください。",
- "notification_blocks": "ブロックしているユーザーからの通知は、すべて止まります。",
- "enable_web_push_notifications": "ウェブプッシュ通知を許可する",
+ "notification_mutes": "特定のユーザーからの通知を止めるには、ミュートを使用してください。",
+ "notification_blocks": "ユーザーをブロックすると、そのユーザーからの通知はすべて停止されます。",
+ "enable_web_push_notifications": "プッシュ通知を有効にする",
"style": {
"switcher": {
"keep_color": "色を残す",
@@ -398,12 +454,12 @@
"help": {
"snapshot_missing": "テーマのスナップショットがありません。思っていた見た目と違うかもしれません。",
"migration_snapshot_ok": "念のために、テーマのスナップショットが読み込まれました。テーマのデータを読み込むことができます。",
- "fe_downgraded": "フロントエンドが前のバージョンに戻りました。",
- "fe_upgraded": "フロントエンドと一緒に、テーマエンジンが新しくなりました。",
- "older_version_imported": "古いフロントエンドで作られたファイルをインポートしました。",
- "future_version_imported": "新しいフロントエンドで作られたファイルをインポートしました。",
- "v2_imported": "古いフロントエンドのためのファイルをインポートしました。設定した通りにならないかもしれません。",
- "upgraded_from_v2": "フロントエンドが新しくなったので、今までの見た目と少し違うかもしれません。",
+ "fe_downgraded": "PleromaFEが前のバージョンに戻りました。",
+ "fe_upgraded": "PleromaFEのテーマエンジンが更新されました。",
+ "older_version_imported": "古いバージョンで作成されたファイルをインポートしました。",
+ "future_version_imported": "新しいバージョンで作成されたファイルをインポートしました。",
+ "v2_imported": "古いバージョンで作成されたファイルをインポートしました。設定した通りにならないかもしれません。",
+ "upgraded_from_v2": "PleromaFEが更新されました。テーマの表示が以前と異なる場合があります。",
"snapshot_source_mismatch": "フロントエンドがロールバックと更新を繰り返したため、バージョンが競合しています。",
"migration_napshot_gone": "スナップショットがありません、覚えているものと見た目が違うかもしれません。",
"snapshot_present": "テーマのスナップショットが読み込まれました。設定は上書きされました。代わりとして実データを読み込むことができます。"
@@ -432,7 +488,7 @@
"common_colors": {
"_tab_label": "共通",
"main": "共通の色",
- "foreground_hint": "「詳細」タブで、もっと細かく設定できます",
+ "foreground_hint": "「詳細」タブで、より細かく設定できます",
"rgbo": "アイコンとアクセントとバッジ"
},
"advanced_colors": {
@@ -445,7 +501,7 @@
"top_bar": "トップバー",
"borders": "境界",
"buttons": "ボタン",
- "inputs": "インプットフィールド",
+ "inputs": "入力欄",
"faint_text": "薄いテキスト",
"alert_neutral": "それ以外",
"chat": {
@@ -483,7 +539,7 @@
"filter_hint": {
"always_drop_shadow": "ブラウザーがサポートしていれば、常に {0} が使われます。",
"drop_shadow_syntax": "{0} は、{1} パラメーターと {2} キーワードをサポートしていません。",
- "avatar_inset": "内側の影と外側の影を同時に使うと、透明なアバターの表示が乱れます。",
+ "avatar_inset": "内側の影と外側の影を同時に使うと、透明なアイコンの表示がおかしくなることがあります。",
"spread_zero": "広がりが 0 よりも大きな影は、0 と同じです",
"inset_classic": "内側の影は {0} を使います"
},
@@ -491,14 +547,14 @@
"panel": "パネル",
"panelHeader": "パネルヘッダー",
"topBar": "トップバー",
- "avatar": "ユーザーアバター (プロフィール)",
- "avatarStatus": "ユーザーアバター (投稿)",
+ "avatar": "ユーザーアイコン (プロフィール)",
+ "avatarStatus": "ユーザーアイコン (投稿)",
"popup": "ポップアップとツールチップ",
"button": "ボタン",
"buttonHover": "ボタン (ホバー)",
"buttonPressed": "ボタン (押されているとき)",
"buttonPressedHover": "ボタン (ホバー、かつ、押されているとき)",
- "input": "インプットフィールド"
+ "input": "入力欄"
},
"hintV3": "影の場合は、 {0} 表記を使って他の色スロットを使うこともできます。"
},
@@ -507,9 +563,10 @@
"help": "「カスタム」を選んだときは、システムにあるフォントの名前を、正しく入力してください。",
"components": {
"interface": "インターフェース",
- "input": "インプットフィールド",
+ "input": "入力欄",
"post": "投稿",
- "postCode": "等幅 (投稿がリッチテキストであるとき)"
+ "postCode": "等幅 (投稿がリッチテキストであるとき)",
+ "monospace": "等幅テキスト"
},
"family": "フォント名",
"size": "大きさ (px)",
@@ -529,14 +586,50 @@
"header_faint": "エラーではありません",
"checkbox": "利用規約を読みました",
"link": "ハイパーリンク"
- }
+ },
+ "themes2_outdated": "V2テーマのエディタは徐々に廃止され、最終的には新しいV3テーマのものに置き換えられる予定です。現状はまだ動作するはずですが、正しく動作する保証はありません。",
+ "themes3": {
+ "font": {
+ "group-local": "端末上にインストールされたフォント",
+ "local-unavailable2": "フォント名を直接指定してください",
+ "lookup_local_fonts": "端末上のフォントの一覧から選ぶ",
+ "group-builtin": "ブラウザのデフォルトフォント",
+ "builtin": {
+ "serif": "明朝体 (Serif)",
+ "sans-serif": "ゴシック体 (Sans-serif)",
+ "monospace": "等幅 (Monospace)",
+ "inherit": "変更しない"
+ },
+ "local-unavailable1": "端末上のフォントの一覧が取得できません",
+ "font_list_unavailable": "端末上のフォントの一覧が取得できません: {error}",
+ "enter_manually": "フォント名を直接入力する",
+ "entry": "{fontFamily}を入力",
+ "select": "フォントを選択"
+ },
+ "hacks": {
+ "underlay_overrides": "背景表示",
+ "underlay_override_mode_none": "テーマのデフォルトを使用する",
+ "underlay_override_mode_opaque": "単色に置き換える",
+ "underlay_override_mode_transparent": "非表示にする (テーマによっては表示が壊れる可能性があります)",
+ "force_interface_roundness": "インターフェースの角丸設定",
+ "forced_roundness_mode_disabled": "テーマのデフォルトを使用する",
+ "forced_roundness_mode_sharp": "角ばったデザインを強制する",
+ "forced_roundness_mode_nonsharp": "若干の角丸(1px分丸める)デザインを強制する",
+ "forced_roundness_mode_round": "角丸デザインを強制する"
+ },
+ "define": "上書き"
+ },
+ "custom_theme_used": "(カスタムテーマ)",
+ "appearance_tab_note": "以下の設定はテーマには反映されないため、エクスポートしたテーマの見た目は今見えているものと異なる可能性があります",
+ "update_preview": "プレビューを更新",
+ "interface_font_user_override": "フォント設定の上書き"
},
"version": {
"title": "バージョン",
"backend_version": "バックエンドのバージョン",
"frontend_version": "フロントエンドのバージョン"
},
- "notification_setting_hide_notification_contents": "送った人と内容を、プッシュ通知に表示しない",
+ "notification_setting_hide_notification_contents": "送った人と通知の内容をプッシュ通知に表示しない",
"notification_setting_privacy": "プライバシー",
"notification_setting_block_from_strangers": "フォローしていないユーザーからの通知を拒否する",
"notification_setting_filters": "フィルター",
@@ -544,53 +637,55 @@
"virtual_scrolling": "タイムラインの描画を最適化する",
"type_domains_to_mute": "ミュートしたいドメインを検索",
"useStreamingApiWarning": "(実験中で、投稿を取りこぼすかもしれないので、おすすめしません)",
- "useStreamingApi": "投稿と通知を、すぐに受け取る",
+ "useStreamingApi": "投稿と通知をリアルタイムで受信する",
"user_mutes": "ユーザー",
- "reset_background_confirm": "本当にバックグラウンドを初期化しますか?",
- "reset_banner_confirm": "本当にバナーを初期化しますか?",
- "reset_avatar_confirm": "本当にアバターを初期化しますか?",
- "hide_wallpaper": "インスタンスのバックグラウンドを隠す",
- "reset_profile_background": "プロフィールのバックグラウンドを初期化",
- "reset_profile_banner": "プロフィールのバナーを初期化",
- "reset_avatar": "アバターを初期化",
+ "reset_background_confirm": "本当に背景をリセットしますか?",
+ "reset_banner_confirm": "本当にバナーをリセットしますか?",
+ "reset_avatar_confirm": "本当にアイコンをリセットしますか?",
+ "hide_wallpaper": "インスタンスデフォルトの壁紙を表示しない",
+ "reset_profile_background": "プロフィールの背景をリセット",
+ "reset_profile_banner": "プロフィールのバナーをリセット",
+ "reset_avatar": "アイコンをリセット",
"notification_visibility_emoji_reactions": "リアクション",
"notification_visibility_moves": "ユーザーの引っ越し",
"new_email": "新しいメールアドレス",
"profile_fields": {
"value": "内容",
"name": "ラベル",
- "add_field": "枠を追加",
- "label": "プロフィール補足情報"
+ "add_field": "入力欄を追加",
+ "label": "追加情報"
},
"accent": "アクセント",
- "mutes_imported": "ミュートをインポートしました!少し時間がかかるかもしれません。",
- "emoji_reactions_on_timeline": "絵文字リアクションをタイムラインに表示",
+ "mutes_imported": "ミュートがインポートされました。処理には時間がかかる場合があります。",
+ "emoji_reactions_on_timeline": "絵文字リアクションをタイムラインに表示する",
"domain_mutes": "ドメイン",
"mutes_and_blocks": "ミュートとブロック",
"chatMessageRadius": "チャットメッセージ",
- "change_email_error": "メールアドレスを変えることが、できなかったかもしれません。",
- "changed_email": "メールアドレスが、変わりました!",
- "change_email": "メールアドレスを変える",
+ "change_email_error": "メールアドレスの変更中にエラーが発生しました。",
+ "changed_email": "メールアドレスが変更されました!",
+ "change_email": "メールアドレスを変更",
"bot": "これは bot アカウントです",
"mute_export_button": "ミュートをCSVファイルにエクスポートする",
"import_mutes_from_a_csv_file": "CSVファイルからミュートをインポートする",
"mute_import_error": "ミュートのインポートに失敗しました",
"mute_import": "ミュートのインポート",
"mute_export": "ミュートのエクスポート",
- "allow_following_move": "フォロー中のアカウントが引っ越したとき、自動フォローを許可する",
- "setting_changed": "規定の設定と異なっています",
- "greentext": "引用を緑色で表示",
- "sensitive_by_default": "はじめから投稿をセンシティブとして設定",
+ "allow_following_move": "フォローしているアカウントが引っ越したとき、引っ越し先を自動でフォローする",
+ "setting_changed": "デフォルトから変更された設定",
+ "greentext": "Meme arrows",
+ "sensitive_by_default": "デフォルトで投稿を閲覧注意として設定",
"more_settings": "その他の設定",
- "reply_visibility_self_short": "自分宛のリプライを見る",
- "reply_visibility_following_short": "フォローしている人に宛てられたリプライを見る",
- "hide_all_muted_posts": "ミュートした投稿を隠す",
- "hide_media_previews": "メディアのプレビューを隠す",
+ "reply_visibility_self_short": "自分宛ての返信のみ表示",
+ "reply_visibility_following_short": "フォローしているユーザー宛ての返信のみ表示",
+ "hide_all_muted_posts": "ミュートした投稿を表示しない",
+ "hide_media_previews": "メディアのプレビューを表示しない",
"word_filter": "単語フィルタ",
"file_export_import": {
"errors": {
- "invalid_file": "これはPleromaの設定をバックアップしたファイルではありません。",
- "file_slightly_new": "ファイルのマイナーバージョンが異なり、一部の設定が読み込まれないことがあります"
+ "invalid_file": "非対応の形式の設定ファイルです。設定は変更されませんでした。",
+ "file_slightly_new": "設定ファイルのバージョンが異なります。一部の設定は読み込まれないかもしれません",
+ "file_too_new": "互換性エラー: PleromaFEが古すぎます。設定ファイルのバージョン{fileMajor}はこのPleromaFE (バージョン{feMajor})と互換性がありません",
+ "file_too_old": "互換性エラー: 設定ファイルが古すぎます。設定ファイルのバージョン{fileMajor}はこのPleromaFE (バージョン{feMajor})と互換性がありません"
},
"restore_settings": "設定をファイルから復元する",
"backup_settings_theme": "テーマを含む設定をファイルにバックアップする",
@@ -599,8 +694,164 @@
},
"save": "変更を保存",
"hide_shoutbox": "Shoutboxを表示しない",
- "always_show_post_button": "投稿ボタンを常に表示",
- "right_sidebar": "サイドバーを右に表示"
+ "always_show_post_button": "投稿ボタンを常に表示する",
+ "right_sidebar": "サイドバーを右側に表示する",
+ "email_language": "このサーバーから受け取るメールの言語",
+ "confirm_dialogs": "以下のとき確認ダイアログを表示する:",
+ "confirm_dialogs_repeat": "ステータスをリピートするとき",
+ "confirm_dialogs_unfollow": "ユーザーのフォローを解除するとき",
+ "confirm_dialogs_block": "ユーザーをブロックするとき",
+ "confirm_dialogs_mute": "ユーザーをミュートするとき",
+ "confirm_dialogs_delete": "投稿を削除するとき",
+ "confirm_dialogs_logout": "ログアウトするとき",
+ "confirm_dialogs_deny_follow": "フォローリクエストを却下するとき",
+ "confirm_dialogs_remove_follower": "フォロワーを解除するとき",
+ "move_account_target": "引っ越し先のアカウント (例: {example})",
+ "move_account_error": "引っ越し中にエラーが発生しました: {error}",
+ "autocomplete_select_first": "オートコンプリートで最初の結果を自動的に選択する",
+ "hide_bot_indication": "bot アカウントであることを示すマークを表示しない",
+ "navbar_column_stretch": "ナビゲーションバーの幅を画面幅に合わせる",
+ "notification_visibility_follow_requests": "フォローリクエスト",
+ "notification_visibility_reports": "通報",
+ "notification_extra_chats": "未読のチャットを表示する",
+ "hide_favorites_description": "お気に入り欄を非公開にする (通知は送信されます)",
+ "conversation_display_tree": "ツリー形式",
+ "max_depth_in_thread": "デフォルトで表示するスレッドの深さ",
+ "mention_link_display": "メンションリンクを",
+ "mention_link_display_short": "常に短く表示する (例: {'@'}hoge)",
+ "mention_link_use_tooltip": "メンションリンクをクリックした時ユーザーカードを表示する",
+ "mention_link_show_avatar": "メンションリンクの横にユーザーのアイコンを表示する",
+ "mention_link_display_full_for_remote": "リモートのユーザーのみすべて表示する (例: {'@'}hoge{'@'}example.org)",
+ "mention_link_display_full": "常にすべて表示する (例: {'@'}hoge{'@'}example.org)",
+ "notification_setting_filters_chrome_push": "Chromeなどのブラウザでは、種類に応じた通知の無効化がプッシュ通知に反映されない場合があります",
+ "hard_reset_value_tooltip": "データベースから設定値を削除し、デフォルト値に戻します",
+ "disable_sticky_headers": "カラムヘッダーを画面上部に固定しない",
+ "column_sizes_notifs": "通知カラム",
+ "conversation_other_replies_button": "「その他の返信」ボタンの位置",
+ "use_websockets": "Websocketを利用してリアルタイムで更新を行う",
+ "mention_link_fade_domain": "メンションのドメイン部分を薄く表示する (例: {'@'}foo{'@'}example.org の {'@'}example.org の部分)",
+ "mention_link_show_avatar_quick": "メンションの横にユーザーアイコンを表示する",
+ "mention_link_bolden_you": "自分宛てのメンションを強調表示する",
+ "user_popover_avatar_action": "ユーザーカード内のユーザーアイコンをクリックした際の挙動",
+ "user_popover_avatar_overlay": "ユーザーカードをユーザーアイコンに被せて表示する",
+ "show_yous": "自分宛てのメンションの横に「(あなた)」と表示する",
+ "preview": "プレビュー",
+ "url": "URL",
+ "conversation_display": "スレッドの表示形式",
+ "column_sizes": "カラム幅",
+ "third_column_mode_none": "表示しない",
+ "column_sizes_content": "コンテンツ",
+ "third_column_mode_notifications": "通知カラムにする",
+ "third_column_mode_postform": "投稿フォームとナビゲーションにする",
+ "conversation_display_linear_quick": "時系列表示",
+ "conversation_display_linear": "時系列形式",
+ "conversation_display_tree_quick": "ツリー表示",
+ "user_popover_avatar_action_open": "プロフィールを表示する",
+ "account_backup": "アカウントのバックアップ",
+ "wordfilter": "ワードフィルター",
+ "column_sizes_sidebar": "サイドバー",
+ "emoji_reactions_scale": "絵文字リアクションの表示倍率",
+ "hide_wordfiltered_statuses": "ワードフィルターによってフィルタリングされたステータスを表示しない",
+ "hide_muted_threads": "ミュートしたスレッドを表示しない",
+ "notification_visibility_polls": "投票結果の確定",
+ "user_popover_avatar_action_zoom": "アイコンを拡大する",
+ "post_look_feel": "投稿の表示形式",
+ "mention_links": "メンションのリンク",
+ "setting_server_side": "この設定はサーバー側に保存され、すべてのセッションとクライアントに影響します",
+ "word_filter_and_more": "ワードフィルターとその他の設定",
+ "notification_extra_announcements": "未読のお知らせを表示する",
+ "notification_extra_follow_requests": "新着のフォローリクエストを表示する",
+ "show_scrollbars": "サイドカラムにスクロールバーを表示する",
+ "third_column_mode": "十分に幅があるとき、三つ目のカラムを",
+ "columns": "カラム",
+ "commit_value": "保存",
+ "commit_value_tooltip": "値は保存されていません。反映するにはこのボタンを押してください",
+ "remove_backup": "削除",
+ "add_backup": "新規バックアップを作成",
+ "account_backup_description": "アカウント情報と投稿のアーカイブをダウンロードできます。開発段階の機能であり、現状、ダウンロードしたデータをインポートすることはできません。",
+ "mute_bot_posts": "BOTアカウントの投稿をミュートする",
+ "auto_update": "自動でタイムラインを更新する",
+ "enable_web_push_always_show_tip": "この設定は、Chromeなどのブラウザで「このサイトはバックグラウンドで更新されました」という通知が表示されることを防止します。その他のブラウザでこの設定を有効化すると、通知が二重で表示されることがあります。",
+ "backup_failed": "バックアップに失敗しました。",
+ "confirm_dialogs_approve_follow": "フォローリクエストを承認するとき",
+ "moved_account": "アカウントの引っ越しが完了しました。",
+ "reset_value": "リセット",
+ "reset_value_tooltip": "編集中の値を破棄します",
+ "hard_reset_value": "デフォルトに戻す",
+ "conversation_other_replies_button_below": "投稿の下",
+ "conversation_other_replies_button_inside": "投稿の中",
+ "add_language": "代替言語を追加",
+ "remove_language": "削除",
+ "account_alias_table_head": "エイリアス",
+ "account_alias": "アカウントエイリアス",
+ "list_aliases_error": "エイリアスの取得中にエラーが発生しました: {error}",
+ "hide_list_aliases_error_action": "閉じる",
+ "remove_alias": "削除",
+ "new_alias_target": "エイリアスを追加 (例: {example})",
+ "added_alias": "エイリアスが追加されました。",
+ "add_alias_error": "エイリアスの追加中にエラーが発生しました: {error}",
+ "move_account": "アカウントの引っ越し",
+ "move_account_notes": "アカウントを引っ越すためには、まず引っ越し先のアカウントにこのアカウントへのエイリアスを追加する必要があります。",
+ "birthday": {
+ "label": "誕生日",
+ "show_birthday": "誕生日を公開する"
+ },
+ "account_privacy": "プライバシー",
+ "posts": "投稿",
+ "user_profiles": "ユーザープロフィール",
+ "primary_language": "第一言語:",
+ "fallback_language": "代替言語 {index}:",
+ "expert_mode": "高度な設定を表示",
+ "account_backup_table_head": "バックアップ",
+ "download_backup": "ダウンロード",
+ "backup_not_ready": "まだ準備中です。",
+ "backup_running": "処理中…{number}件のデータが処理されました。 | 処理中… {number}件のデータが処理されました。",
+ "list_backups_error": "バックアップ一覧の取得に失敗しました: {error}",
+ "added_backup": "バックアップがキューに追加されました。",
+ "add_backup_error": "バックアップの追加に失敗しました: {error}",
+ "user_popover_avatar_action_close": "ユーザーカードを閉じる",
+ "tree_advanced": "高度なナビゲーションボタンを表示する",
+ "tree_fade_ancestors": "スレッド上で祖先にあたるステータスを薄いテキストで表示する",
+ "actor_type_description": "グループとして設定されたアカウントは、メンションのついたステータスを自動的にリピートします。",
+ "actor_type_Person": "通常アカウント",
+ "actor_type_Service": "BOTアカウント",
+ "actor_type_Group": "グループアカウント",
+ "notification_visibility_in_column": "通知カラム(PC)、通知サイドバー(モバイル)に表示する",
+ "notification_setting_annoyance": "通知のカスタマイズ",
+ "notification_setting_unseen_at_top": "未読の通知を最上部に表示する",
+ "enable_web_push_always_show": "プッシュ通知を常に表示する",
+ "hide_scrobbles": "Scrobbleを表示しない",
+ "actor_type": "アカウントタイプ:",
+ "hide_actor_type_indication": "投稿にアカウントタイプ(BOTアカウント、グループアカウントなど)を示すアイコンを表示しない",
+ "notification_show_extra": "その他の通知を通知カラムに表示する",
+ "notification_setting_drawer_marks_as_seen": "モバイルUIで、通知サイドバーを閉じた時すべての通知を既読にする",
+ "notification_setting_ignore_inactionable_seen_tip": "この設定は通知を自動的に既読にするわけではなく、この設定を有効にしてもプッシュ通知などは届きます",
+ "notification_setting_ignore_inactionable_seen": "お気に入りやリピートの通知など、アクション不可な通知を未読として扱わない",
+ "notification_extra_tip": "通知カラムをカスマイズするためのヒントを表示する",
+ "use_at_icon": "メンションリンク内の{'@'}記号を画像にする",
+ "mute_sensitive_posts": "閲覧注意な投稿をミュートする",
+ "units": {
+ "time": {
+ "m": "分",
+ "s": "秒",
+ "h": "時間",
+ "d": "日"
+ }
+ },
+ "hide_scrobbles_after": "これより古いScrobbleを表示しない:",
+ "force_theme_recompilation_debug": "テーマのキャッシュを無効化し、起動の度にコンパイルし直す (デバッグ用)",
+ "scale_and_layout": "インターフェースの表示サイズとレイアウト",
+ "appearance": "見た目",
+ "confirm_new_setting": "設定を適用しますか?",
+ "confirm_new_question": "これで問題ありませんか?10秒間操作がない場合、元の設定に戻ります。",
+ "revert": "元に戻す",
+ "confirm": "適用",
+ "text_size": "フォントサイズ",
+ "text_size_tip2": "{0}以外に設定すると見た目が壊れてしまう場合があります",
+ "emoji_size": "絵文字のサイズ",
+ "navbar_size": "トップバーのサイズ",
+ "panel_header_size": "パネルヘッダーのサイズ",
+ "notification_visibility_statuses": "購読"
},
"time": {
"day": "{0}日",
@@ -622,7 +873,7 @@
"month_short": "{0}ヶ月前",
"months_short": "{0}ヶ月前",
"now": "たった今",
- "now_short": "たった今",
+ "now_short": "今",
"second": "{0}秒",
"seconds": "{0}秒",
"second_short": "{0}秒",
@@ -634,23 +885,41 @@
"year": "{0}年",
"years": "{0}年",
"year_short": "{0}年",
- "years_short": "{0}年"
+ "years_short": "{0}年",
+ "unit": {
+ "seconds_short": "{0}秒",
+ "weeks": "{0} 週間 | {0} 週間",
+ "weeks_short": "{0}週",
+ "years": "{0} 年 | {0} 年",
+ "years_short": "{0}年",
+ "days": "{0} 日 | {0} 日",
+ "hours": "{0} 時間 | {0} 時間",
+ "hours_short": "{0}時間",
+ "minutes": "{0} 分 | {0} 分",
+ "minutes_short": "{0}分",
+ "months": "{0} ヶ月 | {0} ヶ月",
+ "months_short": "{0}ヶ月",
+ "seconds": "{0} 秒 | {0} 秒",
+ "days_short": "{0}日"
+ }
},
"timeline": {
"collapse": "たたむ",
"conversation": "スレッド",
"error_fetching": "読み込みがエラーになりました",
- "load_older": "古いステータス",
- "no_retweet_hint": "投稿を「フォロワーのみ」または「ダイレクト」にすると、リピートできなくなります",
+ "load_older": "古いステータスを読み込む",
+ "no_retweet_hint": "公開範囲が「フォロワーのみ」または「ダイレクト」の投稿はリピートできません",
"repeated": "リピート",
"show_new": "読み込み",
"up_to_date": "最新",
"no_more_statuses": "これで終わりです",
"no_statuses": "ステータスはありません",
"reload": "再読み込み",
- "error": "タイムラインの読み込みに失敗しました: {0}",
+ "error": "タイムラインの読み込み中にエラーが発生しました: {0}",
"socket_reconnected": "リアルタイム接続が確立されました",
- "socket_broke": "コード{0}によりリアルタイム接続が切断されました"
+ "socket_broke": "リアルタイム接続が切断されました: コード{0}",
+ "quick_view_settings": "表示の簡易設定",
+ "quick_filter_settings": "フィルターの簡易設定"
},
"status": {
"favorites": "お気に入り",
@@ -659,8 +928,8 @@
"pin": "プロフィールにピン留め",
"unpin": "プロフィールのピン留めを外す",
"pinned": "ピン留め",
- "delete_confirm": "本当にこのステータスを削除してもよろしいですか?",
- "reply_to": "返信",
+ "delete_confirm": "本当に削除しますか?",
+ "reply_to": "返信先:",
"replies_list": "返信:",
"mute_conversation": "スレッドをミュート",
"unmute_conversation": "スレッドのミュートを解除",
@@ -679,19 +948,62 @@
"unbookmark": "ブックマーク解除",
"bookmark": "ブックマーク",
"mentions": "メンション",
- "you": "(あなた)",
- "plus_more": "ほか{number}件"
+ "you": "(あなた)",
+ "plus_more": "ほか{number}件",
+ "delete_confirm_title": "削除の確認",
+ "ancestor_follow": "このステータスについた{numReplies}件の返信をすべて表示 | このステータスについた{numReplies}件の返信をすべて表示",
+ "invisible_quote": "引用先のステータスが存在しません: {link}",
+ "ancestor_follow_with_icon": "{icon} {text}",
+ "show_all_conversation_with_icon": "{icon} {text}",
+ "delete_error": "ステータスの削除中にエラーが発生しました: {0}",
+ "delete_confirm_accept_button": "削除する",
+ "delete_confirm_cancel_button": "削除しない",
+ "collapse_attachments": "ファイルをたたむ",
+ "show_all_attachments": "すべてのファイルを表示",
+ "hide_attachment": "ファイルを隠す",
+ "reaction_count_label": "{num}人がリアクションしました | {num}人がリアクションしました",
+ "repeat_confirm_accept_button": "リピートする",
+ "repeat_confirm_cancel_button": "リピートしない",
+ "repeat_confirm": "本当にリピートしますか?",
+ "edit": "ステータスを編集",
+ "edited_at": "(最終編集: {time})",
+ "repeat_confirm_title": "リピートの確認",
+ "many_attachments": "この投稿には{number}件のファイルが添付されています",
+ "remove_attachment": "ファイルを削除",
+ "attachment_stop_flash": "Flashプレーヤーを停止",
+ "move_up": "ファイルを左へ移動",
+ "move_down": "ファイルを右へ移動",
+ "thread_follow": "このスレッドの残りを表示 (全部で{numStatus}件の投稿があります) | このスレッドの残りを表示 (全部で{numStatus}件の投稿があります)",
+ "thread_follow_with_icon": "{icon} {text}",
+ "hide_quote": "引用先を隠す",
+ "display_quote": "引用先を表示",
+ "show_only_conversation_under_this": "このステータスへの返信のみを表示",
+ "show_all_conversation": "スレッドの全体を表示 ({numStatus}件のステータス) | スレッドの全体を表示 ({numStatus}件のステータス)",
+ "replies_list_with_others": "返信 (+{numReplies}人): | 返信 (+{numReplies}人):",
+ "more_actions": "その他のアクション",
+ "thread_show_full": "このスレッドをすべて表示 (全部で{depth}層、{numStatus}件の投稿があります) | このスレッドを全て表示 (全部で{depth}層、{numStatus}件の投稿があります)",
+ "thread_show_full_with_icon": "{icon} {text}",
+ "show_attachment_in_modal": "メディアビューアで開く",
+ "show_attachment_description": "メディアの説明文をポップアップで表示 (全文を読むにはメディアを開いてください)",
+ "thread_hide": "このスレッドをたたむ",
+ "thread_show": "このスレッドを開く",
+ "open_gallery": "メディアビューアで開く",
+ "status_history": "編集履歴",
+ "sensitive_muted": "閲覧注意な投稿のためミュートされています",
+ "load_error": "投稿の読み込みに失敗しました: {error}",
+ "loading": "読み込み中…",
+ "quotes": "引用"
},
"user_card": {
- "approve": "受け入れ",
+ "approve": "承認",
"block": "ブロック",
"blocked": "ブロックしています!",
- "deny": "お断り",
+ "deny": "拒否",
"favorites": "お気に入り",
"follow": "フォロー",
- "follow_sent": "リクエストを送りました!",
+ "follow_sent": "リクエストを送信しました!",
"follow_progress": "リクエストしています…",
- "follow_unfollow": "フォローをやめる",
+ "follow_unfollow": "フォロー解除",
"followees": "フォロー",
"followers": "フォロワー",
"following": "フォローしています!",
@@ -700,7 +1012,7 @@
"media": "メディア",
"mention": "メンション",
"mute": "ミュート",
- "muted": "ミュートしています",
+ "muted": "ミュート済み",
"per_day": "/日",
"remote_follow": "リモートフォロー",
"report": "通報",
@@ -720,16 +1032,17 @@
"grant_moderator": "モデレーター権限を付与",
"revoke_moderator": "モデレーター権限を解除",
"activate_account": "アカウントをアクティブにする",
- "deactivate_account": "アカウントをアクティブでなくする",
+ "deactivate_account": "アカウントを無効化する",
"delete_account": "アカウントを削除",
- "force_nsfw": "すべての投稿をNSFWにする",
- "strip_media": "投稿からメディアを除去する",
- "force_unlisted": "投稿を未収載にする",
- "sandbox": "投稿をフォロワーのみにする",
- "disable_remote_subscription": "他のインスタンスからフォローされないようにする",
- "disable_any_subscription": "フォローされないようにする",
- "quarantine": "他のインスタンスからの投稿を止める",
- "delete_user": "ユーザーを削除"
+ "force_nsfw": "すべての投稿を閲覧注意にする",
+ "strip_media": "すべての投稿からメディアを除去する",
+ "force_unlisted": "すべての投稿をアンリステッドにする",
+ "sandbox": "すべての投稿をフォロワー限定にする",
+ "disable_remote_subscription": "他のインスタンスからフォローできないようにする",
+ "disable_any_subscription": "フォローできないようにする",
+ "quarantine": "投稿を連合しないようにする",
+ "delete_user": "ユーザーを削除",
+ "delete_user_data_and_deactivate_confirmation": "このアカウントのデータを永久に削除し、アカウントを無効化します。本当によろしいですね?"
},
"roles": {
"moderator": "モデレーター",
@@ -738,7 +1051,7 @@
"show_repeats": "リピートを見る",
"hide_repeats": "リピートを隠す",
"message": "メッセージ",
- "hidden": "隠す",
+ "hidden": "非公開",
"bot": "bot",
"highlight": {
"solid": "背景を単色にする",
@@ -746,21 +1059,55 @@
"side": "端に線を付ける",
"disabled": "強調しない"
},
- "edit_profile": "プロフィールを編集"
+ "edit_profile": "プロフィールを編集",
+ "deny_confirm_accept_button": "拒否する",
+ "note_blank": "(なし)",
+ "edit_note_cancel": "キャンセル",
+ "remove_follower_confirm_cancel_button": "解除しない",
+ "block_confirm_title": "ブロックの確認",
+ "block_confirm": "本当に{user}をブロックしますか?",
+ "birthday": "誕生日: {birthday}",
+ "edit_note": "メモを編集",
+ "edit_note_apply": "適用",
+ "note": "メモ",
+ "remove_follower_confirm": "本当に{user}からのフォローを解除しますか?",
+ "follow_cancel": "リクエストを取り消す",
+ "approve_confirm_title": "承認の確認",
+ "remove_follower": "フォロワーを解除",
+ "remove_follower_confirm_title": "フォロワー解除の確認",
+ "approve_confirm_accept_button": "承認する",
+ "deny_confirm": "本当に{user}からのフォローリクエストを拒否しますか?",
+ "deny_confirm_cancel_button": "拒否しない",
+ "mute_confirm_cancel_button": "ミュートしない",
+ "approve_confirm_cancel_button": "承認しない",
+ "approve_confirm": "本当に{user}からのフォローリクエストを承認しますか?",
+ "unfollow_confirm_title": "フォロー解除の確認",
+ "unfollow_confirm_cancel_button": "解除しない",
+ "mute_confirm_accept_button": "ミュートする",
+ "mute_confirm": "本当に{user}をミュートしますか?",
+ "block_confirm_accept_button": "ブロックする",
+ "block_confirm_cancel_button": "ブロックしない",
+ "deny_confirm_title": "拒否の確認",
+ "unfollow_confirm": "本当に{user}のフォローを解除しますか?",
+ "unfollow_confirm_accept_button": "解除する",
+ "remove_follower_confirm_accept_button": "解除する",
+ "mute_confirm_title": "ミュートの確認",
+ "deactivated": "無効化済み",
+ "group": "グループ"
},
"user_profile": {
"timeline_title": "ユーザータイムライン",
- "profile_does_not_exist": "申し訳ない。このプロフィールは存在しません。",
- "profile_loading_error": "申し訳ない。プロフィールの読み込みがエラーになりました。"
+ "profile_does_not_exist": "このプロフィールは存在しません。",
+ "profile_loading_error": "プロフィールの読み込み中にエラーが発生しました。"
},
"user_reporting": {
- "title": "通報する: {0}",
+ "title": "{0}を通報する",
"add_comment_description": "この通報は、あなたのインスタンスのモデレーターに送られます。このアカウントを通報する理由を説明することができます:",
"additional_comments": "追加のコメント",
- "forward_description": "このアカウントは他のサーバーに置かれています。この通報のコピーをリモートのサーバーに送りますか?",
- "forward_to": "転送する: {0}",
+ "forward_description": "これは他のインスタンスのアカウントです。この通報のコピーをリモートのインスタンスに送りますか?",
+ "forward_to": "{0}に転送する",
"submit": "送信",
- "generic_error": "あなたのリクエストを処理しようとしましたが、エラーになりました。"
+ "generic_error": "リクエストの処理中にエラーが発生しました。"
},
"who_to_follow": {
"more": "詳細",
@@ -774,15 +1121,17 @@
"user_settings": "ユーザー設定",
"bookmark": "ブックマーク",
"reject_follow_request": "フォローリクエストを拒否",
- "accept_follow_request": "フォローリクエストを許可",
- "add_reaction": "リアクションを追加"
+ "accept_follow_request": "フォローリクエストを承認",
+ "add_reaction": "リアクションを追加",
+ "toggle_mute": "ミュートされた通知を開く/閉じる",
+ "toggle_expand": "この投稿を開く/閉じる"
},
"upload": {
"error": {
"base": "アップロードに失敗しました。",
- "file_too_big": "ファイルが大きすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
- "default": "しばらくしてから試してください",
- "message": "アップロードに失敗: {0}"
+ "file_too_big": "ファイルが大きすぎます [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+ "default": "時間を置いて再試行してください",
+ "message": "アップロードに失敗しました: {0}"
},
"file_size_units": {
"B": "B",
@@ -793,11 +1142,13 @@
}
},
"search": {
- "people": "人々",
+ "people": "ユーザー",
"hashtags": "ハッシュタグ",
"person_talking": "{count} 人が話しています",
"people_talking": "{count} 人が話しています",
- "no_results": "見つかりませんでした"
+ "no_results": "見つかりませんでした",
+ "load_more": "さらに読み込む",
+ "no_more_results": "結果は以上です"
},
"password_reset": {
"forgot_password": "パスワードを忘れましたか?",
@@ -817,23 +1168,25 @@
"federation": "連合",
"simple": {
"media_nsfw_desc": "このインスタンスでは、以下のインスタンスからの投稿に対して、メディアを閲覧注意に設定します:",
- "media_nsfw": "メディアを閲覧注意に設定",
+ "media_nsfw": "強制閲覧注意",
"media_removal_desc": "このインスタンスでは、以下のインスタンスからの投稿に対して、メディアを除去します:",
"media_removal": "メディア除去",
- "ftl_removal": "「既知のネットワーク」タイムラインから除外",
- "ftl_removal_desc": "このインスタンスでは、以下のインスタンスを「既知のネットワーク」タイムラインから除外します:",
+ "ftl_removal": "「すべてのネットワーク」タイムラインから除外",
+ "ftl_removal_desc": "このインスタンスでは、以下のインスタンスを「すべてのネットワーク」タイムラインから除外します:",
"quarantine_desc": "このインスタンスでは、以下のインスタンスに対して公開投稿のみを送信します:",
"quarantine": "検疫",
"reject_desc": "このインスタンスでは、以下のインスタンスからのメッセージを受け付けません:",
"accept_desc": "このインスタンスでは、以下のインスタンスからのメッセージのみを受け付けます:",
"accept": "許可",
- "simple_policies": "インスタンス固有のポリシー",
- "reject": "拒否"
+ "simple_policies": "インスタンスに対するポリシー",
+ "reject": "拒否",
+ "instance": "インスタンス",
+ "reason": "理由"
},
"mrf_policies": "有効なMRFポリシー",
"keyword": {
- "replace": "置き換え",
- "ftl_removal": "「接続しているすべてのネットワーク」タイムラインから除外",
+ "replace": "置換",
+ "ftl_removal": "「すべてのネットワーク」タイムラインから除外",
"keyword_policies": "キーワードポリシー",
"is_replaced_by": "→",
"reject": "拒否"
@@ -847,8 +1200,8 @@
"file_type": {
"file": "ファイル",
"image": "画像",
- "video": "ビデオ",
- "audio": "オーディオ"
+ "video": "動画",
+ "audio": "音声"
},
"remote_user_resolver": {
"error": "見つかりませんでした。",
@@ -865,7 +1218,7 @@
"empty_chat_list_placeholder": "チャットはありません。新規チャットのボタンを押して始めましょう!",
"error_sending_message": "メッセージの送信に失敗しました。",
"error_loading_chat": "チャットの読み込みに失敗しました。",
- "delete_confirm": "このメッセージを本当に消してもいいですか?",
+ "delete_confirm": "本当にこのメッセージを削除しますか?",
"more": "もっと見る",
"empty_message_error": "メッセージを入力して下さい",
"new": "新規チャット",
@@ -879,5 +1232,191 @@
"unmute": "ミュート解除",
"mute_progress": "ミュート中…",
"mute": "ミュート"
+ },
+ "admin_dash": {
+ "window_title": "管理者設定",
+ "nodb": {
+ "text": "{property}が{value}に設定されるよう、設定ファイルを編集する必要があります。詳しくは{documentation}を確認してください。",
+ "documentation": "ドキュメント",
+ "text2": "ほとんどの設定項目は利用できません。",
+ "heading": "データベースへの設定の保存は無効化されています"
+ },
+ "captcha": {
+ "native": "ネイティブ",
+ "kocaptcha": "KoCaptcha"
+ },
+ "instance": {
+ "restrict": {
+ "header": "匿名ユーザーへのアクセス制限",
+ "profiles": "ユーザープロフィールへのアクセス",
+ "timelines": "タイムラインへのアクセス",
+ "activities": "ステータスへのアクセス",
+ "description": "この設定は特定のAPIへのアクセスを制御します。デフォルトでは、インスタンスの公開設定を反映します。一部の設定は、変更すると予期しない動作を引き起こすことがあります(例: ユーザープロフィールへのアクセスを禁止すると、投稿にユーザーの情報が表示されなくなります)。"
+ },
+ "instance": "インスタンス情報",
+ "registrations": "ユーザー登録",
+ "access": "インスタンスへのアクセス",
+ "captcha_header": "CAPTCHA",
+ "kocaptcha": "KoCaptchaの設定"
+ },
+ "frontend": {
+ "available_frontends": "インストール可能なフロントエンド",
+ "success_installing_frontend": "{version} は正常にインストールされました",
+ "failure_installing_frontend": "{version} のインストールに失敗しました: {reason}",
+ "repository": "リポジトリのURL",
+ "versions": "利用可能なバージョン",
+ "build_url": "ダウンロードURL",
+ "reinstall": "再インストール",
+ "install": "インストール",
+ "install_version": "バージョン {version} をインストール",
+ "is_default": "(デフォルト)",
+ "is_default_custom": "(デフォルト、バージョン: {version})",
+ "default_frontend": "デフォルトのフロントエンド",
+ "more_install_options": "その他のインストールオプション",
+ "wip_notice": "このセクションは開発段階です。バックエンド側の実装が未完成であるため、一部の機能は欠けています。",
+ "set_default": "デフォルトに設定",
+ "set_default_version": "バージョン {version} をデフォルトに設定",
+ "default_frontend_tip": "デフォルトのフロントエンドはすべてのユーザーに表示されます。現時点で、ユーザーがフロントエンドを選択する方法はありません。デフォルトのフロントエンドをPleromaFE以外に設定した場合、インスタンスの設定を変更するには古いAdminFEを使用する必要があります。"
+ },
+ "temp_overrides": {
+ ":pleroma": {
+ ":instance": {
+ ":public": {
+ "label": "インスタンスを公開する",
+ "description": "この設定を無効化すると、すべてのAPIの使用にログインが必要になります。これにより、匿名ユーザーは公開タイムラインとすべてのネットワークにアクセスできなくなります。"
+ },
+ ":background_image": {
+ "description": "(主にPleromaFEで使用される)背景画像",
+ "label": "背景画像"
+ },
+ ":limit_to_local_content": {
+ "description": "他インスタンスの情報の検索を、未ログインのユーザー(デフォルト)もしくはすべてのユーザーに対して制限します",
+ "label": "検索をローカルのみに制限する"
+ },
+ ":description_limit": {
+ "description": "ファイルの説明欄に対する文字数制限",
+ "label": "制限"
+ }
+ }
+ }
+ },
+ "wip_notice": "この管理者用設定画面は試験段階であり、未完成です。{adminFeLink}。",
+ "reset_all": "すべてリセット",
+ "commit_all": "すべて保存",
+ "old_ui_link": "これまでの管理者画面にはここからアクセスできます",
+ "tabs": {
+ "limits": "制限",
+ "instance": "インスタンス",
+ "frontends": "フロントエンド",
+ "emoji": "絵文字"
+ },
+ "limits": {
+ "arbitrary_limits": "任意の制限",
+ "posts": "投稿の制限",
+ "uploads": "ファイルの制限",
+ "profile_fields": "追加情報欄の制限",
+ "user_uploads": "プロフィール画像の制限",
+ "users": "ユーザープロフィールの設定"
+ },
+ "emoji": {
+ "create_pack": "パックを作成",
+ "delete_pack": "パックを削除",
+ "create": "作成",
+ "emoji_packs": "絵文字パック",
+ "remote_packs": "リモートのパック",
+ "emoji_pack": "絵文字パック",
+ "edit_pack": "パックを編集",
+ "homepage": "ホームページ",
+ "save": "保存",
+ "save_meta": "メタデータを保存",
+ "shortcode": "ショートコード",
+ "filename": "ファイル名",
+ "delete_confirm": "{0}を削除してもよろしいですか?",
+ "download_pack": "パックをダウンロード",
+ "downloading_pack": "{0}をダウンロード中",
+ "download": "ダウンロード",
+ "editing": "{0}を編集中",
+ "error": "エラー: {0}",
+ "delete": "削除",
+ "global_actions": "グローバルアクション",
+ "reload": "絵文字を再読み込み",
+ "new_pack_name": "新規パック名",
+ "fallback_sha256": "代替ソースのSHA256ハッシュ",
+ "description": "説明",
+ "fallback_src": "代替ソース",
+ "share": "共有",
+ "add_file": "ファイルを追加",
+ "adding_new": "新規絵文字を追加",
+ "revert_meta": "メタデータを元に戻す",
+ "revert": "元に戻す",
+ "new_shortcode": "ショートコード (任意)",
+ "new_filename": "ファイル名 (任意)",
+ "files": "ファイル",
+ "delete_title": "削除しますか?",
+ "metadata_changed": "変更されたメタデータ",
+ "emoji_changed": "保存されていない変更点があります。ハイライトされた絵文字を確認してください"
+ }
+ },
+ "lists": {
+ "search": "ユーザーを検索",
+ "update_title": "リスト名を保存",
+ "really_delete": "本当に削除しますか?",
+ "error": "リストの処理中にエラーが発生しました: {0}",
+ "lists": "リスト",
+ "new": "新規リスト",
+ "save": "変更を保存",
+ "delete": "リストを削除",
+ "editing_list": "{listTitle}の編集",
+ "creating_list": "新規リストの作成",
+ "create": "作成",
+ "title": "リスト名",
+ "following_only": "フォローしているユーザーのみ表示",
+ "manage_lists": "リストの管理",
+ "manage_members": "メンバーの管理",
+ "add_members": "メンバーの追加",
+ "remove_from_list": "リストから削除",
+ "add_to_list": "リストに追加",
+ "is_in_list": "追加済み"
+ },
+ "update": {
+ "update_bugs": "何か問題を見つけたら{pleromaGitlab}にて報告してください。開発中のバージョンにて念入りに確認はしましたが、様々なものが変更されているため、我々が見逃したものがあるかもしれません。バグの報告や、Pleroma/PleromaFEを改善するための提案やフィードバックは大歓迎です。",
+ "update_changelog_here": "変更履歴",
+ "update_changelog": "全ての変更点は{theFullChangelog}を参照してください。",
+ "big_update_content": "久しぶりのリリースですので、今までと異なるところがあるかもしれません。",
+ "update_bugs_gitlab": "Pleroma GitLab",
+ "big_update_title": ""
+ },
+ "report": {
+ "reported_statuses": "通報されたステータス:",
+ "notes": "メモ:",
+ "state": "状態:",
+ "state_open": "未解決",
+ "reporter": "通報者:",
+ "state_resolved": "解決済み",
+ "reported_user": "被通報者:",
+ "state_closed": "問題なし"
+ },
+ "unicode_domain_indicator": {
+ "tooltip": "このドメインには非ASCII文字が含まれています。"
+ },
+ "announcements": {
+ "page_header": "お知らせ",
+ "title": "お知らせ",
+ "mark_as_read_action": "既読にする",
+ "post_form_header": "お知らせを投稿",
+ "post_placeholder": "お知らせの内容を入力してください…",
+ "post_action": "投稿",
+ "post_error": "エラー: {error}",
+ "close_error": "閉じる",
+ "delete_action": "削除",
+ "submit_edit_action": "完了",
+ "cancel_edit_action": "キャンセル",
+ "published_time_display": "{time} に公開",
+ "start_time_display": "{time}から開始",
+ "end_time_display": "{time}に終了",
+ "edit_action": "編集",
+ "start_time_prompt": "開始日時: ",
+ "end_time_prompt": "終了日時: ",
+ "all_day_prompt": "終日"
}
}
diff --git a/src/i18n/ko.json b/src/i18n/ko.json
@@ -4,14 +4,15 @@
},
"features_panel": {
"chat": "챗",
- "gopher": "고퍼",
+ "gopher": "Gopher",
"media_proxy": "미디어 프록시",
"scope_options": "범위 옵션",
"text_limit": "텍스트 제한",
"title": "기능",
"who_to_follow": "팔로우 추천",
"upload_limit": "최대 파일용량",
- "pleroma_chat_messages": "Pleroma 채트"
+ "pleroma_chat_messages": "Pleroma 채팅",
+ "shout": "외치기"
},
"finder": {
"error_fetching_user": "사용자 정보 불러오기 실패",
@@ -21,12 +22,12 @@
"apply": "적용",
"submit": "보내기",
"loading": "로딩중…",
- "peek": "숨기기",
+ "peek": "살짝 보기",
"close": "닫기",
"verify": "검사",
"confirm": "확인",
- "enable": "유효",
- "disable": "무효",
+ "enable": "활성화",
+ "disable": "비활성화",
"cancel": "취소",
"dismiss": "무시",
"show_less": "접기",
@@ -34,18 +35,35 @@
"optional": "필수 아님",
"retry": "다시 시도하십시오",
"error_retry": "다시 시도하십시오",
- "generic_error": "잘못되었습니다",
+ "generic_error": "에러가 발생했습니다",
"more": "더 보기",
"role": {
"moderator": "중재자",
"admin": "관리자"
- }
+ },
+ "undo": "취소",
+ "yes": "네",
+ "no": "아니오",
+ "unpin": "고정 해제",
+ "pin": "고정",
+ "scope_in_timeline": {
+ "private": "팔로워 전용",
+ "public": "공개",
+ "unlisted": "비표시",
+ "direct": "다이렉트"
+ },
+ "flash_content": "클릭해서 플래시 컨텐츠 보기 (Ruffle 사용, 작동하지 않을 수 있습니다).",
+ "flash_security": "플래시 컨텐츠는 임의 코드 실행이 아직도 가능할 수 있기에 위험할 수 있습니다.",
+ "flash_fail": "플래시를 로드하지 못했습니다, 콘솔로 자세한 내용을 확인하세요.",
+ "scroll_to_top": "맨 위로 올라가기",
+ "generic_error_message": "에러가 발생했습니다: {0}",
+ "never_show_again": "다시 보지 않기"
},
"login": {
"login": "로그인",
"description": "OAuth로 로그인",
"logout": "로그아웃",
- "password": "암호",
+ "password": "패스워드",
"placeholder": "예시: lain",
"register": "가입",
"username": "사용자 이름",
@@ -57,7 +75,11 @@
"enter_two_factor_code": "2단계인증 코드를 입력하십시오",
"enter_recovery_code": "복구 코드를 입력하십시오",
"authentication_code": "인증 코드",
- "hint": "로그인하여 대화에 참가합시다"
+ "hint": "로그인해서 대화에 참여",
+ "logout_confirm_title": "로그아웃 확인",
+ "logout_confirm": "정말 로그아웃 하시겠습니까?",
+ "logout_confirm_accept_button": "로그아웃",
+ "logout_confirm_cancel_button": "로그아웃 안 함"
},
"nav": {
"about": "인스턴스 소개",
@@ -71,96 +93,137 @@
"twkn": "알려진 네트워크",
"user_search": "사용자 검색",
"preferences": "환경설정",
- "chats": "채트",
+ "chats": "채팅",
"timelines": "타임라인",
"who_to_follow": "추천된 사용자",
"search": "검색",
"bookmarks": "북마크",
- "interactions": "대화",
+ "interactions": "알림",
"administration": "관리",
- "home_timeline": "홈 타임라인"
+ "home_timeline": "홈 타임라인",
+ "mobile_notifications": "알림 열기 (읽지 않은 알림이 있습니다)",
+ "lists": "리스트",
+ "edit_nav_mobile": "네비게이션 바 커스텀하기",
+ "edit_pinned": "상단 고정 편집",
+ "edit_finish": "편집 종료",
+ "mobile_notifications_close": "알림 닫기",
+ "mobile_sidebar": "모바일 사이드바 토글",
+ "announcements": "공지사항",
+ "search_close": "검색 바 닫기",
+ "mobile_notifications_mark_as_seen": "모두 읽음으로 표시"
},
"notifications": {
"broken_favorite": "알 수 없는 게시물입니다, 검색합니다…",
- "favorited_you": "당신의 게시물을 즐겨찾기",
- "followed_you": "당신을 팔로우",
- "load_older": "오래 된 알림 불러오기",
+ "favorited_you": "관심을 가짐",
+ "followed_you": "팔로우함",
+ "load_older": "이전 알림 불러오기",
"notifications": "알림",
"read": "읽음!",
- "repeated_you": "당신의 게시물을 리핏",
+ "repeated_you": "리핏함",
"no_more_notifications": "알림이 없습니다",
"migrated_to": "이사했습니다",
"reacted_with": "{0} 로 반응했습니다",
"error": "알림 불러오기 실패: {0}",
- "follow_request": "당신에게 팔로우 신청"
+ "follow_request": "팔로우 요청",
+ "submitted_report": "신고 내용을 전송함",
+ "poll_ended": "투표가 끝남",
+ "unread_follow_requests": "{num}개의 새 팔로우 요청 | {num}개의 새 팔로우 요청",
+ "configuration_tip": "{theSettings}에서 어떻게 보이는지 바꿀 수 있습니다. {dismiss}",
+ "configuration_tip_settings": "설정",
+ "configuration_tip_dismiss": "다시 보지 않기",
+ "unread_announcements": "{num}개의 읽지 않은 공지사항 | {num}개의 읽지 않은 공지사항",
+ "unread_chats": "{num}개의 읽지 않은 채팅 | {num}개의 읽지 않은 채팅"
},
"post_status": {
"new_status": "새 게시물 게시",
- "account_not_locked_warning": "당신의 계정은 {0} 상태가 아닙니다. 누구나 당신을 팔로우 하고 팔로워 전용 게시물을 볼 수 있습니다.",
+ "account_not_locked_warning": "계정이 {0} 상태가 아닙니다. 누구나 당신을 팔로우 하고 팔로워 전용 게시물을 볼 수 있습니다.",
"account_not_locked_warning_link": "잠김",
"attachments_sensitive": "첨부물을 민감함으로 설정",
"content_type": {
"text/plain": "평문",
"text/bbcode": "BBCode",
- "text/markdown": "Markdown",
+ "text/markdown": "마크다운",
"text/html": "HTML"
},
- "content_warning": "주제 (필수 아님)",
+ "content_warning": "제목 (선택)",
"default": "인천공항에 도착했습니다.",
"direct_warning": "이 게시물을 멘션 된 사용자들에게만 보여집니다",
- "posting": "게시",
+ "posting": "게시 중",
"scope": {
"direct": "다이렉트 - 멘션 된 사용자들에게만",
"private": "팔로워 전용 - 팔로워들에게만",
"public": "공개 - 공개 타임라인으로",
- "unlisted": "비공개 - 공개 타임라인에 게시 안 함"
+ "unlisted": "비표시 - 공개 타임라인에는 안 보이게"
},
- "preview_empty": "아무것도 없습니다",
+ "preview_empty": "비어있음",
"preview": "미리보기",
"scope_notice": {
- "public": "이 글은 누구나 볼 수 있습니다"
+ "public": "누구나 볼 수 있습니다",
+ "private": "팔로워에게만 보여집니다",
+ "unlisted": "공개 타임라인이나 알려진 네트워크에는 보여지지 않습니다"
},
- "media_description_error": "파일을 올리지 못하였습니다. 다시한번 시도하여 주십시오",
- "empty_status_error": "글을 입력하십시오",
- "media_description": "첨부파일 설명"
+ "media_description_error": "파일을 올리지 못했습니다, 다시 시도해 보세요",
+ "empty_status_error": "게시물이 비어 있습니다",
+ "media_description": "첨부파일 설명",
+ "direct_warning_to_all": "멘션한 모든 사용자에게 보여집니다.",
+ "edit_unsupported_warning": "Pleroma는 멘션이나 투표를 수정하는 기능을 지원하지 않습니다.",
+ "edit_status": "수정",
+ "edit_remote_warning": "수정 기능이 없는 다른 인스턴스에서는 수정한 사항이 반영되지 않을 수 있습니다.",
+ "post": "게시",
+ "direct_warning_to_first_only": "맨 앞에 멘션한 사용자들에게만 보여집니다.",
+ "content_type_selection": "게시물 형태",
+ "scope_notice_dismiss": "알림 닫기",
+ "reply_option": "이 게시물에 답글",
+ "quote_option": "이 게시물을 인용"
},
"registration": {
"bio": "소개",
"email": "이메일",
- "fullname": "표시 되는 이름",
- "password_confirm": "암호 확인",
+ "fullname": "표시될 이름",
+ "password_confirm": "패스워드 확인",
"registration": "가입하기",
"token": "초대 토큰",
"captcha": "캡차",
- "new_captcha": "이미지를 클릭해서 새로운 캡차",
+ "new_captcha": "이미지를 클릭해서 새로운 캡차 가져오기",
"validations": {
"username_required": "공백으로 둘 수 없습니다",
"fullname_required": "공백으로 둘 수 없습니다",
"email_required": "공백으로 둘 수 없습니다",
"password_required": "공백으로 둘 수 없습니다",
"password_confirmation_required": "공백으로 둘 수 없습니다",
- "password_confirmation_match": "패스워드와 일치해야 합니다"
+ "password_confirmation_match": "패스워드와 일치해야 합니다",
+ "birthday_required": "공백으로 둘 수 없습니다",
+ "birthday_min_age": "{date} 또는 그 이전 출생만 가능합니다"
},
"fullname_placeholder": "예: 김례인",
- "username_placeholder": "예: lain"
+ "username_placeholder": "예: lain",
+ "bio_placeholder": "예시\n안녕하세요, 례인입니다.\n일본 시외에서 애니메이션 아이돌을 하고 있습니다. Wired에서 절 보셨을 거예요.",
+ "bio_optional": "소개 (선택)",
+ "email_optional": "이메일 (선택)",
+ "reason": "가입하려는 이유",
+ "reason_placeholder": "이 인스턴스는 수동으로 가입을 승인하고 있습니다.\n왜 가입하고 싶은지 관리자에게 알려주세요.",
+ "register": "가입",
+ "email_language": "무슨 언어로 이메일을 받길 원하시나요?",
+ "birthday": "생일:",
+ "birthday_optional": "생일 (선택):"
},
"settings": {
"attachmentRadius": "첨부물",
"attachments": "첨부물",
- "avatar": "아바타",
- "avatarAltRadius": "아바타 (알림)",
- "avatarRadius": "아바타",
+ "avatar": "프로필 사진",
+ "avatarAltRadius": "프로필 사진 (알림창)",
+ "avatarRadius": "프로필 사진",
"background": "배경",
"bio": "소개",
"btnRadius": "버튼",
"cBlue": "파랑 (답글, 팔로우)",
"cGreen": "초록 (리트윗)",
- "cOrange": "주황 (즐겨찾기)",
+ "cOrange": "주황 (관심글)",
"cRed": "빨강 (취소)",
- "change_password": "암호 바꾸기",
- "change_password_error": "암호를 바꾸는 데 몇 가지 문제가 있습니다.",
- "changed_password": "암호를 바꾸었습니다!",
- "collapse_subject": "주제를 가진 게시물 접기",
+ "change_password": "패스워드 바꾸기",
+ "change_password_error": "패스워드를 바꾸는 데 문제가 있습니다.",
+ "changed_password": "패스워드가 바뀌었습니다!",
+ "collapse_subject": "제목이 있는 게시물 접기",
"composing": "작성",
"confirm_new_password": "새 패스워드 확인",
"current_avatar": "현재 아바타",
@@ -169,27 +232,27 @@
"data_import_export_tab": "데이터 불러오기 / 내보내기",
"default_vis": "기본 공개 범위",
"delete_account": "계정 삭제",
- "delete_account_description": "데이터가 영구히 삭제되고 계정이 불활성화됩니다.",
+ "delete_account_description": "데이터가 영구히 삭제되고 계정이 비활성화됩니다.",
"delete_account_error": "계정을 삭제하는데 문제가 있습니다. 계속 발생한다면 인스턴스 관리자에게 문의하세요.",
- "delete_account_instructions": "계정 삭제를 확인하기 위해 아래에 패스워드 입력.",
+ "delete_account_instructions": "아래 패스워드를 입력하시면 계정이 삭제됩니다.",
"export_theme": "프리셋 저장",
"filtering": "필터링",
- "filtering_explanation": "아래의 단어를 가진 게시물들은 뮤트 됩니다, 한 줄에 하나씩 적으세요",
+ "filtering_explanation": "이 단어를 가진 게시물들은 뮤트됩니다, 한 줄에 하나씩 적으세요",
"follow_export": "팔로우 내보내기",
- "follow_export_button": "팔로우 목록을 csv로 내보내기",
+ "follow_export_button": "팔로우 목록을 CSV 파일로 내보내기",
"follow_export_processing": "진행 중입니다, 곧 다운로드 가능해 질 것입니다",
"follow_import": "팔로우 불러오기",
"follow_import_error": "팔로우 불러오기 실패",
"follows_imported": "팔로우 목록을 불러왔습니다! 처리에는 시간이 걸립니다.",
- "foreground": "전경",
+ "foreground": "표면",
"general": "일반",
"hide_attachments_in_convo": "대화의 첨부물 숨기기",
"hide_attachments_in_tl": "타임라인의 첨부물 숨기기",
"hide_isp": "인스턴스 전용 패널 숨기기",
"preload_images": "이미지 미리 불러오기",
- "hide_post_stats": "게시물 통계 숨기기 (즐겨찾기 수 등)",
+ "hide_post_stats": "게시물 통계 숨기기 (관심글 수 등)",
"hide_user_stats": "사용자 통계 숨기기 (팔로워 수 등)",
- "import_followers_from_a_csv_file": "csv 파일에서 팔로우 목록 불러오기",
+ "import_followers_from_a_csv_file": "CSV 파일에서 팔로우 목록 불러오기",
"import_theme": "프리셋 불러오기",
"inputRadius": "입력 칸",
"checkboxRadius": "체크박스",
@@ -197,58 +260,58 @@
"instance_default_simple": "(기본)",
"interface": "인터페이스",
"interfaceLanguage": "인터페이스 언어",
- "invalid_theme_imported": "선택한 파일은 지원하는 플레로마 테마가 아닙니다. 아무런 변경도 일어나지 않았습니다.",
+ "invalid_theme_imported": "해당 파일은 지원되지 않는 Pleroma 테마입니다. 아무 일도 일어나지 않았습니다.",
"limited_availability": "이 브라우저에서 사용 불가",
"links": "링크",
- "lock_account_description": "계정을 승인 된 팔로워들로 제한",
+ "lock_account_description": "팔로워를 승인해서 받도록 제한",
"loop_video": "비디오 반복재생",
- "loop_video_silent_only": "소리가 없는 비디오만 반복 재생 (마스토돈의 \"gifs\" 같은 것들)",
+ "loop_video_silent_only": "소리가 없는 비디오만 반복 재생 (마스토돈의 \"GIF\" 같은 것들)",
"name": "이름",
"name_bio": "이름 & 소개",
- "new_password": "새 암호",
- "notification_visibility": "보여 줄 알림 종류",
+ "new_password": "새 패스워드",
+ "notification_visibility": "보여질 알림 종류",
"notification_visibility_follows": "팔로우",
- "notification_visibility_likes": "좋아함",
+ "notification_visibility_likes": "관심글",
"notification_visibility_mentions": "멘션",
- "notification_visibility_repeats": "반복",
+ "notification_visibility_repeats": "리핏",
"no_rich_text_description": "모든 게시물의 서식을 지우기",
- "hide_follows_description": "내가 팔로우하는 사람을 표시하지 않음",
- "hide_followers_description": "나를 따르는 사람을 숨기기",
- "nsfw_clickthrough": "NSFW 이미지 \"클릭해서 보이기\"를 활성화",
+ "hide_follows_description": "팔로우 중인 사람 숨기기",
+ "hide_followers_description": "팔로워 숨기기",
+ "nsfw_clickthrough": "민감한 이미지를 숨기기",
"oauth_tokens": "OAuth 토큰",
"token": "토큰",
"refresh_token": "토큰 새로 고침",
- "valid_until": "까지 유효하다",
+ "valid_until": "만료일",
"revoke_token": "취소",
"panelRadius": "패널",
- "pause_on_unfocused": "탭이 활성 상태가 아닐 때 스트리밍 멈추기",
+ "pause_on_unfocused": "탭이 포커스되지 않았을 땐 멈추기",
"presets": "프리셋",
"profile_background": "프로필 배경",
"profile_banner": "프로필 배너",
"profile_tab": "프로필",
"radii_help": "인터페이스 모서리 둥글기 (픽셀 단위)",
- "replies_in_timeline": "답글을 타임라인에",
+ "replies_in_timeline": "타임라인의 답글",
"reply_visibility_all": "모든 답글 보기",
- "reply_visibility_following": "나에게 직접 오는 답글이나 내가 팔로우 중인 사람에게서 오는 답글만 표시",
- "reply_visibility_self": "나에게 직접 전송 된 답글만 보이기",
- "saving_err": "설정 저장 실패",
+ "reply_visibility_following": "나에게 직접 오거나 내가 팔로우 중인 사람이 보낸 답글만 보기",
+ "reply_visibility_self": "나에게 직접 온 답글만 보기",
+ "saving_err": "설정을 저장하는 데 에러가 발생했습니다",
"saving_ok": "설정 저장 됨",
"security_tab": "보안",
- "scope_copy": "답글을 달 때 공개 범위 따라가리 (다이렉트 메시지는 언제나 따라감)",
- "set_new_avatar": "새 아바타 설정",
+ "scope_copy": "답글을 달 때 공개 범위 따라가기 (다이렉트 메시지는 언제나 따라감)",
+ "set_new_avatar": "새 프로필 사진 설정",
"set_new_profile_background": "새 프로필 배경 설정",
"set_new_profile_banner": "새 프로필 배너 설정",
"settings": "설정",
- "subject_input_always_show": "항상 주제 칸 보이기",
- "subject_line_behavior": "답글을 달 때 주제 복사하기",
- "subject_line_email": "이메일처럼: \"re: 주제\"",
+ "subject_input_always_show": "항상 제목 입력창 보이기",
+ "subject_line_behavior": "답글을 달 때 제목 복사하기",
+ "subject_line_email": "이메일처럼: \"re: 제목\"",
"subject_line_mastodon": "마스토돈처럼: 그대로 복사",
"subject_line_noop": "복사 안 함",
- "stop_gifs": "GIF파일에 마우스를 올려서 재생",
- "streaming": "최상단에 도달하면 자동으로 새 게시물 스트리밍",
+ "stop_gifs": "마우스를 올려서 GIF 재생",
+ "streaming": "최상단에 도달하면 알아서 새 게시물 가져오기",
"text": "텍스트",
"theme": "테마",
- "theme_help": "16진수 색상코드(#rrggbb)를 사용해 색상 테마를 커스터마이즈.",
+ "theme_help": "16진수 색상코드(#rrggbb)를 사용해 색상을 조정하세요.",
"theme_help_v2_1": "체크박스를 통해 몇몇 컴포넌트의 색상과 불투명도를 조절 가능, \"모두 지우기\" 버튼으로 덮어 씌운 것을 모두 취소.",
"theme_help_v2_2": "몇몇 입력칸 밑의 아이콘은 전경/배경 대비 관련 표시등입니다, 마우스를 올려 자세한 정보를 볼 수 있습니다. 투명도 대비 표시등이 가장 최악의 경우를 나타낸다는 것을 유의하세요.",
"tooltipRadius": "툴팁/경고",
@@ -265,25 +328,42 @@
"keep_shadows": "그림자 유지",
"keep_opacity": "불투명도 유지",
"keep_roundness": "둥글기 유지",
- "keep_fonts": "글자체 유지",
+ "keep_fonts": "글꼴 유지",
"save_load_hint": "\"유지\" 옵션들은 다른 테마를 고르거나 불러 올 때 현재 설정 된 옵션들을 건드리지 않게 합니다, 테마를 내보내기 할 때도 이 옵션에 따라 저장합니다. 아무 것도 체크 되지 않았다면 모든 설정을 내보냅니다.",
"reset": "초기화",
"clear_all": "모두 지우기",
- "clear_opacity": "불투명도 지우기"
+ "clear_opacity": "불투명도 지우기",
+ "help": {
+ "upgraded_from_v2": "PleromaFE가 업그레이드 되었기에, 테마가 기억하시던 것과 조금 다를 수 있습니다.",
+ "v2_imported": "불러온 파일은 이곳보다 이전 버전의 FE에서 만들어졌습니다. 호환성을 유지하겠지만 깨진 부분이 있을 수 있습니다.",
+ "migration_snapshot_ok": "혹시나 싶어서, 테마 스냅샷을 불러왔습니다. 테마 데이터를 불러와도 됩니다.",
+ "snapshot_source_mismatch": "버전이 충돌됩니다: 아마 FE가 롤백되고 다시 업데이트 되어서일 건데, 이전 버전 FE로 테마를 수정했다면 이전 버전 FE를 써보시는 게 좋고, 아니면 새 버전을 쓰세요.",
+ "future_version_imported": "불러온 파일은 이곳보다 새 버전의 FE에서 만들어졌습니다.",
+ "older_version_imported": "불러온 파일은 이곳보다 이전 버전의 FE에서 만들어졌습니다.",
+ "snapshot_present": "테마 스냅샷이 있어서, 모든 값이 덮어 씌워졌습니다. 직접 테마의 실제 데이터를 대신 불러와도 됩니다.",
+ "snapshot_missing": "파일에 스냅샷이 없어서 원래 보였던 것보다 다르게 보일 수 있습니다.",
+ "fe_upgraded": "버전 업데이트로 PleromaFE의 테마 엔진이 업그레이드 되었습니다.",
+ "fe_downgraded": "PleromaFE의 버전이 롤백되었습니다.",
+ "migration_napshot_gone": "뭔 일인진 모르겠지만 스냅샷이 없어서, 몇몇 개가 기억하신 것과 달리 보일 수 있습니다."
+ },
+ "load_theme": "테마 불러오기",
+ "keep_as_is": "그대로 두기",
+ "use_snapshot": "이전 버전",
+ "use_source": "새 버전"
},
"common": {
"color": "색상",
"opacity": "불투명도",
"contrast": {
- "hint": "대비율이 {ratio}입니다, 이것은 {context} {level}",
+ "hint": "색상 대비율이 {ratio}입니다, {context} {level}",
"level": {
- "aa": "AA등급 가이드라인에 부합합니다 (최소한도)",
- "aaa": "AAA등급 가이드라인에 부합합니다 (권장)",
- "bad": "아무런 가이드라인 등급에도 미치지 못합니다"
+ "aa": "접근성 가이드라인 AA등급을 충족합니다 (최소)",
+ "aaa": "접근성 가이드라인 AAA등급을 충족합니다 (권장)",
+ "bad": "접근성 가이드라인을 충족하지 못합니다"
},
"context": {
"18pt": "큰 (18pt 이상) 텍스트에 대해",
- "text": "텍스트에 대해"
+ "text": "일반 텍스트에 대해"
}
}
},
@@ -307,13 +387,24 @@
"faint_text": "흐려진 텍스트",
"chat": {
"border": "경계선",
- "outgoing": "송신",
- "incoming": "수신"
+ "outgoing": "보냄",
+ "incoming": "받음"
},
"selectedMenu": "선택된 메뉴 요소",
"selectedPost": "선택된 글",
"icons": "아이콘",
- "alert_warning": "경고"
+ "alert_warning": "경고",
+ "alert_neutral": "중립적",
+ "post": "게시물 / 유저 소개",
+ "popover": "툴팁, 메뉴, 프로필 카드",
+ "disabled": "비활성화",
+ "wallpaper": "배경사진",
+ "poll": "투표 그래프",
+ "highlight": "강조 요소",
+ "pressed": "눌렸을 때",
+ "toggled": "토글됨",
+ "tabs": "탭",
+ "underlay": "밑배경"
},
"radii": {
"_tab_label": "둥글기"
@@ -344,23 +435,24 @@
"button": "버튼",
"buttonHover": "버튼 (마우스 올렸을 때)",
"buttonPressed": "버튼 (눌렸을 때)",
- "buttonPressedHover": "Button (마우스 올림 + 눌림)",
+ "buttonPressedHover": "버튼 (마우스 올림 + 눌림)",
"input": "입력칸"
- }
+ },
+ "hintV3": "그림자의 경우 {0} 표기법으로 다른 컬러 슬롯을 사용할 수 있습니다."
},
"fonts": {
- "_tab_label": "글자체",
- "help": "인터페이스의 요소에 사용 될 글자체를 고르세요. \"커스텀\"은 시스템에 있는 폰트 이름을 정확히 입력해야 합니다.",
+ "_tab_label": "글꼴",
+ "help": "화면에 적용할 글꼴을 고르세요. \"직접 입력\"은 시스템에 있는 글꼴 이름을 정확히 입력해야 합니다.",
"components": {
"interface": "인터페이스",
"input": "입력칸",
"post": "게시물 텍스트",
"postCode": "게시물의 고정폭 텍스트 (서식 있는 텍스트)"
},
- "family": "글자체 이름",
+ "family": "글꼴 이름",
"size": "크기 (px 단위)",
"weight": "굵기",
- "custom": "커스텀"
+ "custom": "직접 입력"
},
"preview": {
"header": "미리보기",
@@ -371,8 +463,8 @@
"mono": "내용",
"input": "인천공항에 도착했습니다.",
"faint_link": "도움 되는 설명서",
- "fine_print": "우리의 {0} 를 읽고 도움 되지 않는 것들을 배우자!",
- "header_faint": "이건 괜찮아",
+ "fine_print": "우리의 {0}를 읽고 도움 되지 않는 것들을 배우자!",
+ "header_faint": "괜찮은 텍스트",
"checkbox": "나는 약관을 대충 훑어보았습니다",
"link": "작고 귀여운 링크"
}
@@ -381,44 +473,255 @@
"mfa": {
"scan": {
"secret_code": "키",
- "title": "스캔"
+ "title": "스캔",
+ "desc": "2단계 인증 앱을 통해 QR 코드를 찍거나 키를 입력하세요:"
},
"authentication_methods": "인증 방법",
- "waiting_a_recovery_codes": "예비 코드를 수신하고 있습니다…",
+ "waiting_a_recovery_codes": "복구 코드를 가져오고 있습니다…",
"recovery_codes": "복구 코드.",
- "generate_new_recovery_codes": "새로운 복구 코드를 작성",
- "title": "2단계인증",
- "confirm_and_enable": "OTP 확인과 활성화",
- "setup_otp": "OTP 설치",
- "otp": "OTP"
+ "generate_new_recovery_codes": "새 복구 코드 생성",
+ "title": "2단계 인증",
+ "confirm_and_enable": "확인 & OTP 활성화",
+ "setup_otp": "OTP 설정",
+ "otp": "OTP",
+ "warning_of_generate_new_codes": "새 복구 코드를 생성하면, 이전 코드는 작동하지 않게 됩니다.",
+ "recovery_codes_warning": "복구 코드를 어딘가 안전한 곳에 적어 놓으세요 - 더 이상 이 코드를 보실 순 없습니다. 만약 2단계 인증 앱과 복구 코드 둘 다 접근할 수 없게 된다면 계정에 로그인할 수 없게 됩니다.",
+ "verify": {
+ "desc": "활성화하려면 2단계 인증 앱에서 받은 코드를 입력하세요:"
+ }
},
"security": "보안",
- "emoji_reactions_on_timeline": "이모지 반응을 타임라인으로 표시",
- "avatar_size_instruction": "크기를 150x150 이상으로 설정할 것을 추장합니다.",
+ "emoji_reactions_on_timeline": "에모지 반응을 타임라인에 표시",
+ "avatar_size_instruction": "최소 150x150 픽셀보다 큰 사진을 업로드하시면 좋습니다.",
"blocks_tab": "차단",
"notification_setting_privacy": "보안",
"user_mutes": "사용자",
"notification_visibility_emoji_reactions": "반응",
"profile_fields": {
- "value": "내용"
+ "value": "내용",
+ "label": "프로필 추가정보",
+ "add_field": "필드 추가",
+ "name": "라벨"
+ },
+ "mutes_and_blocks": "뮤트와 차단",
+ "chatMessageRadius": "채팅 메시지",
+ "change_email": "메일 주소 바꾸기",
+ "changed_email": "메일 주소가 바뀌었습니다!",
+ "bot": "이 계정은 자동 봇입니다",
+ "mutes_tab": "뮤트",
+ "app_name": "앱 이름",
+ "notification_setting_block_from_strangers": "팔로하지 않은 계정에서 보내는 알림 차단",
+ "autohide_floating_post_button": "알아서 새 게시물 버튼 숨기기 (모바일)",
+ "blocks_imported": "차단 목록을 가져왔습니다! 처리하는 데에 시간이 걸릴 수 있습니다.",
+ "mutes_imported": "뮤트 목록을 가져왔습니다! 처리하는 데에 시간이 걸릴 수 있습니다.",
+ "account_backup_description": "내 계정 정보와 게시물이 담긴 아카이브를 다운로드 받을 수 있지만, 아직 Pleroma로 다시 불러오는 기능은 지원하지 않습니다.",
+ "move_account_notes": "계정을 다른 곳으로 이사하려면, 이사 갈 계정으로 가셔서 별칭이 이 계정을 가리키도록 하세요.",
+ "hide_bot_indication": "게시물에서 봇 알림 숨기기",
+ "navbar_column_stretch": "상단 바를 컬럼 너비만큼 늘리기",
+ "show_admin_badge": "내 프로필에 \"관리자\" 배지 달기",
+ "sensitive_by_default": "게시물을 민감함으로 기본 설정",
+ "notification_mutes": "특정 사용자의 알림을 받지 않으려면, 뮤트를 사용하세요.",
+ "mention_link_fade_domain": "흐려진 도메인 (예: {'@'}foo{'@'}example.org 에서의 {'@'}example.org)",
+ "notification_blocks": "사용자를 차단하면 알림을 받지 않는데다 구독까지 취소하게 됩니다.",
+ "conversation_display_tree": "트리",
+ "save": "변경 사항을 저장",
+ "allow_following_move": "팔로우 중인 계정이 이사를 하면 자동으로 팔로우하기",
+ "expert_mode": "고급 설정 보기",
+ "setting_changed": "기본 설정과 다릅니다",
+ "setting_server_side": "이 설정은 계정과 묶여 있으며 연결된 모든 세션과 클라이언트에 영향을 줍니다",
+ "enter_current_password_to_confirm": "본인 확인을 위해 현재 패스워드를 입력하세요",
+ "post_look_feel": "게시물 모양새",
+ "mention_links": "멘션 링크",
+ "lists_navigation": "메뉴에 리스트 보이기",
+ "email_language": "서버로부터 이메일을 받을 언어",
+ "block_import": "차단 목록 가져오기",
+ "block_export_button": "차단 목록을 CSV 파일로 내보내기",
+ "block_import_error": "차단 목록을 가져오는 데에 문제가 발생했습니다",
+ "mute_export": "뮤트 목록 내보내기",
+ "mute_export_button": "뮤트 목록을 CSV 파일로 내보내기",
+ "mute_import": "뮤트 목록 가져오기",
+ "mute_import_error": "뮤트 목록을 가져오는 데에 문제가 발생했습니다",
+ "import_mutes_from_a_csv_file": "뮤트 목록을 CSV 파일에서 가져오기",
+ "account_backup": "계정 백업",
+ "account_backup_table_head": "백업",
+ "download_backup": "다운로드",
+ "backup_not_ready": "백업이 아직 준비되지 않았습니다.",
+ "remove_backup": "삭제",
+ "list_backups_error": "백업 리스트를 가져오는 데 에러가 발생했습니다: {error}",
+ "add_backup": "새 백업 만들기",
+ "added_backup": "새 백업 추가됨.",
+ "add_backup_error": "새 백업을 추가하는 데 에러가 발생했습니다: {error}",
+ "change_email_error": "메일 주소를 바꾸는 데 문제가 있습니다.",
+ "account_alias": "계정 별칭",
+ "always_show_post_button": "항상 떠다니는 게시물 작성 버튼 보기",
+ "mute_bot_posts": "봇 게시물 뮤트하기",
+ "hide_all_muted_posts": "뮤트한 게시물 숨기기",
+ "account_alias_table_head": "별칭",
+ "hide_list_aliases_error_action": "닫기",
+ "remove_alias": "이 별칭 삭제",
+ "new_alias_target": "새 별칭 추가 (예시. {example})",
+ "added_alias": "별칭이 추가되었습니다.",
+ "move_account": "계정 이사",
+ "move_account_target": "이사 갈 계정 (예시. {example})",
+ "moved_account": "계정을 이사했습니다.",
+ "discoverable": "검색 결과나 다른 서비스들에서 이 계정을 찾을 수 있도록 허용",
+ "pad_emoji": "에모지를 선택창에서 고를 때 띄어쓰기를 집어넣기",
+ "wordfilter": "단어 필터",
+ "word_filter_and_more": "단어 필터 및 기타 설정...",
+ "accent": "강조",
+ "hide_media_previews": "미디어 미리보기 숨기기",
+ "max_thumbnails": "게시물 하나 당 최대로 보여질 섬네일 개수 (비워두면 제한을 두지 않습니다)",
+ "hide_shoutbox": "인스턴스 외치기 숨기기",
+ "right_sidebar": "컬럼 순서 뒤집기",
+ "hide_wallpaper": "인스턴스 배경화면 가리기",
+ "use_one_click_nsfw": "민감한 첨부물을 클릭 한 번으로 열기",
+ "move_account_error": "계정을 이사하는 데 에러가 발생했습니다: {error}",
+ "hide_muted_posts": "뮤트한 사용자의 게시물 숨기기",
+ "hide_filtered_statuses": "필터된 모든 게시물 숨기기",
+ "hide_wordfiltered_statuses": "단어 필터된 게시물 숨기기",
+ "use_contain_fit": "첨부파일의 섬네일을 자르지 않음",
+ "hide_muted_threads": "뮤트한 스레드 숨기기",
+ "import_blocks_from_a_csv_file": "CSV 파일에서 차단 목록 불러오기",
+ "play_videos_in_modal": "팝업 프레임에서 비디오를 재생",
+ "file_export_import": {
+ "backup_restore": "설정 백업",
+ "backup_settings": "설정을 파일로 백업",
+ "backup_settings_theme": "설정과 테마를 파일로 백업",
+ "restore_settings": "파일에서 설정 복구하기",
+ "errors": {
+ "invalid_file": "해당 파일은 지원되지 않는 Pleroma 백업입니다. 아무 일도 일어나지 않았습니다.",
+ "file_too_new": "호환되지 않는 버전: {fileMajor}, 이 PleromaFE (설정 버전 {feMajor}) 가 너무 낡아서 처리할 수 없습니다",
+ "file_too_old": "호환되지 않는 버전: {fileMajor}, 파일 버전이 너무 낡아서 처리할 수 없습니다 (지원되는 최소 설정 버전 {feMajor})",
+ "file_slightly_new": "파일 마이너 버전이 달라서, 몇몇 설정들이 적용되지 않았을 수 있습니다"
+ }
+ },
+ "account_privacy": "사생활 보안",
+ "new_email": "새 메일 주소",
+ "hide_favorites_description": "내 관심글을 보이지 않음 (알림은 갑니다)",
+ "hide_follows_count_description": "팔로우 중 숫자 숨기기",
+ "hide_followers_count_description": "팔로워 숫자 숨기기",
+ "no_mutes": "뮤트 없음",
+ "search_user_to_block": "차단할 사람 검색하기",
+ "search_user_to_mute": "뮤트할 사람 검색하기",
+ "posts": "게시물",
+ "notification_visibility_moves": "계정 이사",
+ "notification_visibility_polls": "참여한 투표가 끝남",
+ "no_blocks": "차단 없음",
+ "reply_visibility_self_short": "내 답글만 보기",
+ "reply_visibility_following_short": "팔로우 중인 사람들끼리의 답글 보기",
+ "user_profiles": "사용자 프로필",
+ "show_moderator_badge": "내 프로필에 \"중재자\" 배지 달기",
+ "type_domains_to_mute": "뮤트할 도메인 검색하기",
+ "disable_sticky_headers": "컬럼 헤더를 화면 상단에 고정하지 않음",
+ "auto_update": "알아서 새 게시물 가져오기",
+ "minimal_scopes_mode": "공개 범위 선택지 줄이기",
+ "reset_avatar": "프로필 사진 초기화",
+ "reset_avatar_confirm": "정말 프로필 사진을 초기화할까요?",
+ "reset_profile_background": "프로필 배경 초기화",
+ "reset_profile_banner": "프로필 배너 초기화",
+ "reset_banner_confirm": "정말 프로필 배너를 초기화할까요?",
+ "reset_background_confirm": "정말 프로필 배경을 초기화할까요?",
+ "useStreamingApi": "실시간으로 게시물과 알림 받기",
+ "use_websockets": "웹소켓 사용 (실시간 업데이트)",
+ "upload_a_photo": "사진 업로드",
+ "conversation_display": "대화 표시 모양",
+ "conversation_display_tree_quick": "트리 뷰",
+ "show_scrollbars": "측면 컬럼의 스크롤바 보기",
+ "conversation_other_replies_button_inside": "게시물 안에 놓기",
+ "notification_setting_hide_notification_contents": "푸시 알림에서 보낸 사람과 내용을 숨김",
+ "virtual_scrolling": "타임라인 렌더링 최적화",
+ "use_at_icon": "{'@'} 문자를 텍스트 대신 아이콘으로 표시",
+ "mention_link_display": "멘션에 링크 표시",
+ "mention_link_display_short": "항상 짧은 이름 사용 (예: {'@'}foo)",
+ "mention_link_display_full_for_remote": "다른 인스턴스 사용자만 이름 전부 보기 (예: {'@'}foo{'@'}example.org)",
+ "mention_link_display_full": "항상 이름 전부 보기 (예: {'@'}foo{'@'}example.org)",
+ "mention_link_use_tooltip": "멘션 링크를 누르면 사용자 카드 보기",
+ "mention_link_show_avatar": "링크 옆에 프로필 사진 보기",
+ "mention_link_bolden_you": "누가 날 멘션했을 때 멘션을 강조 표시",
+ "user_popover_avatar_action_zoom": "사진 키우기",
+ "greentext": "밈 화살표",
+ "show_yous": "\"(당신)\" 보이기",
+ "notification_setting_filters": "필터",
+ "more_settings": "추가 설정",
+ "user_popover_avatar_action_open": "프로필 열기",
+ "version": {
+ "frontend_version": "프론트엔드 버전",
+ "title": "버전",
+ "backend_version": "백엔드 버전"
+ },
+ "fun": "즐겁다",
+ "domain_mutes": "도메인",
+ "third_column_mode": "공간이 충분하면, 세 번째 컬럼 채우기",
+ "third_column_mode_none": "세 번째 컬럼 안 보기",
+ "third_column_mode_notifications": "알림 컬럼",
+ "third_column_mode_postform": "게시물 편집창과 내비게이션",
+ "columns": "컬럼",
+ "column_sizes": "컬럼 크기",
+ "column_sizes_sidebar": "사이드바",
+ "column_sizes_content": "내용",
+ "column_sizes_notifs": "알림",
+ "tree_advanced": "트리 뷰에서 더 유연한 탐색을 허용",
+ "tree_fade_ancestors": "현재 게시물보다 상단의 게시물들을 흐린 텍스트로 표시",
+ "conversation_display_linear": "선형",
+ "conversation_display_linear_quick": "선형 뷰",
+ "conversation_other_replies_button": "\"답글 더 보기\" 버튼을",
+ "conversation_other_replies_button_below": "게시물 아래에 놓기",
+ "max_depth_in_thread": "기본적으로 보일 최대 깊이",
+ "user_popover_avatar_action": "프로필 카드의 사진 클릭 시",
+ "user_popover_avatar_action_close": "카드 닫기",
+ "user_popover_avatar_overlay": "프로필 카드를 프로필 사진 위에 띄우기",
+ "post_status_content_type": "게시물 내용 형식",
+ "list_aliases_error": "별칭을 가져오는 중 에러 발생: {error}",
+ "add_alias_error": "별칭을 추가하는 중 에러 발생: {error}",
+ "mention_link_show_avatar_quick": "멘션 옆에 유저 프로필 사진을 보임",
+ "backup_running": "백업 중입니다, {number}개 처리 완료. | 백업 중입니다, {number}개 처리 완료.",
+ "confirm_dialogs": "하기 전에 다시 물어보기",
+ "autocomplete_select_first": "자동완성이 가능하면 자동으로 첫 번째 후보를 선택",
+ "backup_failed": "백업에 실패했습니다.",
+ "emoji_reactions_scale": "리액션 크기",
+ "birthday": {
+ "label": "생일",
+ "show_birthday": "내 생일 보여주기"
},
- "mutes_and_blocks": "침묵과 차단",
- "chatMessageRadius": "챗 메시지",
- "change_email": "메일주소 바꾸기",
- "changed_email": "메일주소가 갱신되었습니다!",
- "bot": "이 계정은 bot입니다",
- "mutes_tab": "침묵",
- "app_name": "앱 이름"
+ "add_language": "보조 언어 추가",
+ "confirm_dialogs_repeat": "리핏",
+ "confirm_dialogs_unfollow": "언팔로우",
+ "confirm_dialogs_block": "차단",
+ "confirm_dialogs_mute": "뮤트",
+ "confirm_dialogs_delete": "게시물 삭제",
+ "confirm_dialogs_approve_follow": "팔로워 승인",
+ "confirm_dialogs_deny_follow": "팔로워 거절",
+ "confirm_dialogs_remove_follower": "팔로워 제거",
+ "remove_language": "삭제",
+ "primary_language": "주 언어:",
+ "fallback_language": "보조 언어 {index}:",
+ "confirm_dialogs_logout": "로그아웃",
+ "url": "URL",
+ "preview": "미리보기",
+ "commit_value": "저장",
+ "commit_value_tooltip": "값이 저장되지 않았습니다, 버튼을 눌러 변경사항을 반영하세요",
+ "reset_value": "초기화",
+ "reset_value_tooltip": "변경사항 초기화",
+ "hard_reset_value": "완전 초기화",
+ "hard_reset_value_tooltip": "스토리지에서 설정을 제거하고, 기본값을 사용하도록 강제합니다"
},
"timeline": {
"collapse": "접기",
"conversation": "대화",
"error_fetching": "업데이트 불러오기 실패",
- "load_older": "더 오래 된 게시물 불러오기",
- "no_retweet_hint": "팔로워 전용, 다이렉트 메시지는 반복할 수 없습니다",
- "repeated": "반복 됨",
- "show_new": "새로운 것 보기",
- "up_to_date": "최신 상태"
+ "load_older": "이전 게시물 불러오기",
+ "no_retweet_hint": "팔로워 전용 게시물과 다이렉트 메시지는 리핏할 수 없습니다",
+ "repeated": "리핏함",
+ "show_new": "새 게시물 보기",
+ "up_to_date": "최신",
+ "error": "타임라인을 가져오지 못했습니다: {0}",
+ "reload": "새로고침",
+ "no_statuses": "게시물 없음",
+ "no_more_statuses": "새 게시물 없음",
+ "socket_reconnected": "실시간 연결 됨",
+ "socket_broke": "실시간 연결이 끊어짐: CloseEvent 코드 {0}",
+ "quick_filter_settings": "빠른 필터 설정",
+ "quick_view_settings": "빠른 뷰 설정"
},
"user_card": {
"approve": "승인",
@@ -426,22 +729,101 @@
"blocked": "차단 됨!",
"deny": "거부",
"follow": "팔로우",
- "follow_sent": "요청 보내짐!",
+ "follow_sent": "요청 보냄!",
"follow_progress": "요청 중…",
- "follow_unfollow": "팔로우 중지",
+ "follow_unfollow": "언팔로우",
"followees": "팔로우 중",
"followers": "팔로워",
"following": "팔로우 중!",
- "follows_you": "당신을 팔로우 합니다!",
+ "follows_you": "나를 팔로우 합니다!",
"its_you": "당신입니다!",
- "mute": "침묵",
- "muted": "침묵 됨",
- "per_day": "/ 하루",
- "remote_follow": "원격 팔로우",
- "statuses": "게시물"
+ "mute": "뮤트",
+ "muted": "뮤트 됨",
+ "per_day": "개 / 일",
+ "remote_follow": "다른 인스턴스에서 팔로우",
+ "statuses": "게시물",
+ "unmute_progress": "뮤트 해제 중…",
+ "unblock_progress": "차단 해제 중…",
+ "admin_menu": {
+ "revoke_moderator": "중재자 탄핵",
+ "sandbox": "게시물 공개 범위를 팔로워 전용으로 강제",
+ "disable_any_subscription": "누구도 팔로우를 못하도록 막기",
+ "delete_user_data_and_deactivate_confirmation": "영구적으로 이 계정의 데이터가 삭제되고 비활성화 됩니다. 정말로 괜찮겠습니까?",
+ "moderation": "관리",
+ "grant_admin": "관리자로 임명",
+ "grant_moderator": "중재자로 임명",
+ "disable_remote_subscription": "다른 인스턴스에서 팔로우하지 못하도록 막기",
+ "activate_account": "계정 활성화",
+ "deactivate_account": "계정 비활성화",
+ "delete_account": "계정 삭제",
+ "force_nsfw": "모든 게시물을 민감한 내용으로 표시",
+ "strip_media": "게시물에서 미디어 제거",
+ "revoke_admin": "관리자 탄핵",
+ "force_unlisted": "게시물 공개 범위를 비표시로 강제",
+ "quarantine": "연합 타임라인에서 사용자 게시물 비허용",
+ "delete_user": "사용자 삭제"
+ },
+ "deactivated": "비활성화됨",
+ "edit_profile": "프로필 편집",
+ "favorites": "관심글",
+ "follow_cancel": "팔로우 요청 취소",
+ "unmute": "뮤트 해제",
+ "mute_progress": "뮤트 중…",
+ "hidden": "숨겨짐",
+ "media": "미디어",
+ "mention": "멘션",
+ "message": "메시지",
+ "remove_follower": "팔로워 삭제",
+ "report": "신고",
+ "subscribe": "구독",
+ "unsubscribe": "구독 해제",
+ "unblock": "차단 해제",
+ "block_progress": "차단 중…",
+ "hide_repeats": "리핏 숨기기",
+ "show_repeats": "리핏 보기",
+ "bot": "봇",
+ "highlight": {
+ "disabled": "강조 표시 없음",
+ "striped": "줄무늬 배경",
+ "solid": "단색 배경",
+ "side": "옆트임"
+ },
+ "approve_confirm_title": "승인 확인",
+ "approve_confirm_accept_button": "승인",
+ "approve_confirm_cancel_button": "승인 안 함",
+ "approve_confirm": "{user}의 팔로우 요청을 승인할까요?",
+ "block_confirm_title": "차단 확인",
+ "note": "노트",
+ "unfollow_confirm": "정말 {user}를 팔로우 해제하시겠습니까?",
+ "unfollow_confirm_accept_button": "팔로우 해제",
+ "unfollow_confirm_cancel_button": "취소",
+ "remove_follower_confirm_title": "팔로워 삭제 확인",
+ "remove_follower_confirm_cancel_button": "냅두기",
+ "remove_follower_confirm_accept_button": "치우기",
+ "edit_note_cancel": "취소",
+ "birthday": "{birthday}에 태어남",
+ "edit_note": "노트 수정",
+ "edit_note_apply": "적용",
+ "deny_confirm_cancel_button": "취소",
+ "unfollow_confirm_title": "팔로우 해제 확인",
+ "mute_confirm_accept_button": "뮤트",
+ "remove_follower_confirm": "정말 {user}를 팔로워에서 치울까요?",
+ "deny_confirm_accept_button": "거절",
+ "mute_confirm_title": "뮤트 확인",
+ "mute_confirm": "정말 {user}를 뮤트할까요?",
+ "block_confirm_cancel_button": "취소",
+ "deny_confirm_title": "거절 확인",
+ "block_confirm": "정말 {user}를 차단할까요?",
+ "block_confirm_accept_button": "차단",
+ "mute_confirm_cancel_button": "취소",
+ "mute_duration_prompt": "이 사용자를 뮤트할 시간 (0으로 두면 무한히):",
+ "deny_confirm": "{user}의 팔로 요청을 거절할까요?",
+ "note_blank": "(없음)"
},
"user_profile": {
- "timeline_title": "사용자 타임라인"
+ "timeline_title": "사용자 타임라인",
+ "profile_does_not_exist": "죄송하지만, 이 프로필은 존재하지 않습니다.",
+ "profile_loading_error": "죄송하지만, 프로필을 불러오는 데 에러가 발생했습니다."
},
"who_to_follow": {
"more": "더 보기",
@@ -449,38 +831,64 @@
},
"tool_tip": {
"media_upload": "미디어 업로드",
- "repeat": "반복",
+ "repeat": "리핏",
"reply": "답글",
- "favorite": "즐겨찾기",
- "user_settings": "사용자 설정"
+ "favorite": "관심글",
+ "user_settings": "사용자 설정",
+ "add_reaction": "반응 추가",
+ "accept_follow_request": "팔로우 요청 승인",
+ "reject_follow_request": "팔로우 요청 거절",
+ "bookmark": "북마크",
+ "autocomplete_available": "{number}개의 결과가 있습니다. 위 또는 아래 화살표 키로 탐색할 수 있습니다. | {number}개의 결과가 있습니다. 위 또는 아래 화살표 키로 탐색할 수 있습니다.",
+ "toggle_expand": "알림을 펼치거나 접어서 전체 게시물을 보기",
+ "toggle_mute": "알림을 펼치거나 접어서 뮤트한 내용 보기"
},
"upload": {
"error": {
"base": "업로드 실패.",
"file_too_big": "파일이 너무 커요 [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
- "default": "잠시 후에 다시 시도해 보세요"
+ "default": "잠시 후에 다시 시도해 보세요",
+ "message": "업로드 실패: {0}"
},
"file_size_units": {
- "B": "바이트",
- "KiB": "키비바이트",
- "MiB": "메비바이트",
- "GiB": "기비바이트",
- "TiB": "테비바이트"
+ "B": "B",
+ "KiB": "KiB",
+ "MiB": "MiB",
+ "GiB": "GiB",
+ "TiB": "TiB"
}
},
"interactions": {
"follows": "새 팔로워",
- "favs_repeats": "반복과 즐겨찾기",
- "moves": "계정 통합"
+ "favs_repeats": "리핏과 관심",
+ "moves": "계정 이동",
+ "emoji_reactions": "에모지 반응",
+ "reports": "신고",
+ "load_older": "이전 반응 불러오기"
},
"emoji": {
- "load_all": "전체 {emojiAmount} 이모지 불러오기",
- "unicode": "Unicode 이모지",
- "custom": "전용 이모지",
- "add_emoji": "이모지 넣기",
- "search_emoji": "이모지 검색",
- "emoji": "이모지",
- "stickers": "스티커"
+ "load_all": "전체 {emojiAmount}개의 에모지 불러오기",
+ "unicode": "Unicode 에모지",
+ "custom": "전용 에모지",
+ "add_emoji": "에모지 넣기",
+ "search_emoji": "에모지 검색",
+ "emoji": "에모지",
+ "stickers": "스티커",
+ "load_all_hint": "첫 {saneAmount}개의 에모지를 불러왔습니다, 에모지를 전부 불러오면 성능 저하가 있을 수 있습니다.",
+ "unicode_groups": {
+ "people-and-body": "사람 & 몸",
+ "smileys-and-emotion": "웃는 얼굴 & 감정",
+ "travel-and-places": "여행 & 장소",
+ "activities": "활동",
+ "animals-and-nature": "동물 & 자연",
+ "flags": "깃발",
+ "food-and-drink": "음식 & 음료",
+ "objects": "사물",
+ "symbols": "기호"
+ },
+ "keep_open": "열린 채로 두기",
+ "regional_indicator": "지역 표시기 {letter}",
+ "unpacked": "미분류 에모지"
},
"polls": {
"add_poll": "투표를 추가",
@@ -493,11 +901,16 @@
"option": "선택지",
"add_option": "선택지 추가",
"expired": "투표는 {0} 전에 마감되었습니다",
- "expires_in": "투표는 {0}에 마감됩니다"
+ "expires_in": "투표는 {0}에 마감됩니다",
+ "single_choice": "하나만 선택",
+ "multiple_choices": "여러 개 선택",
+ "not_enough_options": "선택지가 너무 적습니다"
},
"media_modal": {
"next": "다음",
- "previous": "이전"
+ "previous": "이전",
+ "counter": "{current} / {total}",
+ "hide": "미디어 뷰어 닫기"
},
"importer": {
"error": "이 파일을 가져올 때 오류가 발생하였습니다.",
@@ -511,14 +924,14 @@
"crop_picture": "사진 자르기"
},
"exporter": {
- "processing": "처리중입니다, 처리가 끝나면 파일을 다운로드하라는 지시가 있겠습니다",
+ "processing": "처리중입니다, 곧 파일을 다운로드할 수 있습니다",
"export": "내보내기"
},
"domain_mute_card": {
- "unmute_progress": "침묵을 해제중…",
- "unmute": "침묵 해제",
- "mute_progress": "침묵으로 설정중…",
- "mute": "침묵"
+ "unmute_progress": "뮤트 해제 중…",
+ "unmute": "뮤트 해제",
+ "mute_progress": "뮤트 중…",
+ "mute": "뮤트"
},
"about": {
"staff": "운영자",
@@ -536,21 +949,25 @@
"accept_desc": "이 인스턴스에서는 아래의 인스턴스로부터 보내온 투고만이 접수됩니다:",
"reject": "거부",
"accept": "허가",
- "simple_policies": "인스턴스 특유의 폴리시"
+ "simple_policies": "인스턴스 특유의 정책",
+ "instance": "인스턴스",
+ "reason": "사유",
+ "not_applicable": "없음"
},
- "mrf_policies": "사용되는 MRF 폴리시",
+ "mrf_policies": "사용되는 MRF 정책",
"keyword": {
"is_replaced_by": "→",
"replace": "바꾸기",
"reject": "거부",
"ftl_removal": "\"알려진 모든 네트워크\" 타임라인에서 제외",
- "keyword_policies": "단어 폴리시"
+ "keyword_policies": "단어 정책"
},
- "federation": "연합"
+ "federation": "연합",
+ "mrf_policies_desc": "MRF 정책은 이 인스턴스의 페더레이션 동작을 제어하고 있습니다. 적용되고 있는 정책은 다음과 같습니다:"
}
},
"shoutbox": {
- "title": "Shoutbox"
+ "title": "외치기"
},
"time": {
"years_short": "{0} 년",
@@ -565,8 +982,8 @@
"second_short": "{0} 초",
"seconds": "{0} 초",
"second": "{0} 초",
- "now_short": "방금",
- "now": "방끔",
+ "now_short": "지금",
+ "now": "방금",
"months_short": "{0} 달 전",
"month_short": "{0} 달 전",
"months": "{0} 달 전",
@@ -583,13 +1000,301 @@
"days_short": "{0} 일",
"day_short": "{0} 일",
"days": "{0} 일",
- "day": "{0} 일"
+ "day": "{0} 일",
+ "unit": {
+ "weeks": "{0}주 | {0}주",
+ "minutes": "{0}분 | {0}분",
+ "seconds": "{0}초 | {0}초",
+ "seconds_short": "{0}초",
+ "weeks_short": "{0}주",
+ "years": "{0}년 | {0}년",
+ "years_short": "{0}년",
+ "days": "{0}일 | {0}일",
+ "days_short": "{0}일",
+ "hours": "{0}시간 | {0}시간",
+ "hours_short": "{0}시간",
+ "minutes_short": "{0}분",
+ "months": "{0}달 | {0}달",
+ "months_short": "{0}달"
+ },
+ "in_future": "{0} 후"
},
"remote_user_resolver": {
"error": "찾을 수 없습니다.",
- "searching_for": "검색중"
+ "searching_for": "검색:",
+ "remote_user_resolver": "다른 인스턴스 사용자 안내기"
},
"selectable_list": {
"select_all": "모두 선택"
+ },
+ "lists": {
+ "title": "리스트 제목",
+ "search": "사용자 검색하기",
+ "lists": "리스트",
+ "new": "리스트 만들기",
+ "create": "만들기",
+ "delete": "리스트 삭제",
+ "following_only": "팔로우 중인 사람들만",
+ "manage_lists": "리스트 관리",
+ "manage_members": "멤버 관리",
+ "remove_from_list": "리스트에서 제거",
+ "add_to_list": "리스트에 추가",
+ "is_in_list": "리스트에 이미 있음",
+ "editing_list": "{listTitle} 리스트 편집",
+ "update_title": "제목 저장",
+ "really_delete": "리스트를 삭제하시겠어요?",
+ "save": "변경 사항을 저장",
+ "creating_list": "새 리스트 만들기",
+ "add_members": "사용자 추가",
+ "error": "리스트를 조작하는 데 오류가 발생했습니다: {0}"
+ },
+ "search": {
+ "no_more_results": "결과 더 없음",
+ "load_more": "결과 더 불러오기",
+ "people": "사람",
+ "hashtags": "해시태그",
+ "person_talking": "{count}명이 말하는 중",
+ "people_talking": "{count}명이 말하는 중",
+ "no_results": "결과 없음"
+ },
+ "password_reset": {
+ "forgot_password": "패스워드를 잊으셨나요?",
+ "password_reset": "패스워드 재설정",
+ "placeholder": "이메일 주소 또는 사용자 이름",
+ "password_reset_required_but_mailer_is_disabled": "패스워드 초기화를 하셔야 하지만, 못 하게 막혀 있습니다. 인스턴스 관리자에게 문의해주세요.",
+ "check_email": "패스워드 초기화를 위해 이메일을 확인해주세요.",
+ "return_home": "홈으로 돌아가기",
+ "password_reset_required": "로그인하려면 패스워드를 초기화해야 합니다.",
+ "password_reset_disabled": "패스워드 초기화를 못 하게 되어 있습니다. 인스턴스 관리자에게 문의해주세요.",
+ "instruction": "이메일 주소 또는 사용자 이름을 입력하세요. 패스워드 초기화 링크를 메일로 보내드립니다.",
+ "too_many_requests": "너무 많은 시도를 했습니다, 나중에 다시 해주세요."
+ },
+ "chats": {
+ "you": "당신:",
+ "delete": "삭제",
+ "new": "새 채팅",
+ "chats": "채팅",
+ "empty_message_error": "메시지가 비어 있습니다",
+ "more": "더 보기",
+ "error_loading_chat": "왜인진 모르겠는데 채팅을 불러오지 못했습니다.",
+ "error_sending_message": "왜인진 모르겠는데 메시지를 전송하지 못했습니다.",
+ "delete_confirm": "이 메시지를 정말 지울까요?",
+ "empty_chat_list_placeholder": "채팅이 없네요. 새 채팅을 시작해보세요!",
+ "message_user": "{nickname}에게 메시지"
+ },
+ "file_type": {
+ "audio": "오디오",
+ "video": "영상",
+ "image": "사진",
+ "file": "파일"
+ },
+ "display_date": {
+ "today": "오늘"
+ },
+ "update": {
+ "big_update_title": "양해해주세요",
+ "update_bugs_gitlab": "Pleroma GitLab",
+ "update_changelog_here": "변경 내역",
+ "update_changelog": "무엇이 바뀌었는지 자세히 알아보시려면, {theFullChangelog}을 참조하세요.",
+ "big_update_content": "저희가 한동안 릴리즈를 안 해서, 익숙하셨던 생김새나 경험과 많이 달라졌을 수 있습니다.",
+ "update_bugs": "저희가 비록 테스트를 많이 하고 직접 개발 버전을 쓰기도 하지만, 많이 바꾸기도 했고, 몇몇 가지 놓친 점들이 있을 터이니, 사용하면서 불편한 점이나 문제는 {pleromaGitlab}에 제보해주시면 감사하겠습니다. 저희는 겪으신 문제점이나 Pleroma와 Pleroma-FE에 대한 피드백과 제안을 환영합니다.",
+ "art_by": "{linkToArtist} 그림"
+ },
+ "unicode_domain_indicator": {
+ "tooltip": "이 도메인은 아스키 문자가 아닌 문자를 포함하고 있습니다."
+ },
+ "status": {
+ "mute_conversation": "대화 뮤트",
+ "thread_muted_and_words": ", 단어 포함:",
+ "unpin": "프로필에서 고정 해제",
+ "replies_list_with_others": "답글 (+{numReplies}개): | 답글 (+{numReplies}개):",
+ "show_attachment_in_modal": "미디어 모달에서 보기",
+ "thread_hide": "이 스레드 숨기기",
+ "show_attachment_description": "설명 미리보기 (첨부물을 열어서 전체 설명 보기)",
+ "thread_show_full": "이 스레드를 전부 들춰보기 (총 {numStatus}개 있음, 최대 깊이 {depth}) | 이 스레드를 전부 들춰보기 (총 {numStatus}개 있음, 최대 깊이 {depth})",
+ "thread_follow": "이 스레드의 나머지 부분 보기 (총 {numStatus}개) | 이 스레드의 나머지 부분 보기 (총 {numStatus}개)",
+ "status_history": "게시물 이력",
+ "show_all_conversation": "전체 대화 보기 ({numStatus}개 더 있음) | 전체 대화 보기 ({numStatus}개 더 있음)",
+ "repeats": "리핏",
+ "delete": "삭제",
+ "edit": "수정",
+ "favorites": "관심글",
+ "edited_at": "({time}에 마지막으로 수정됨)",
+ "pin": "프로필에 고정",
+ "pinned": "고정됨",
+ "bookmark": "북마크",
+ "unbookmark": "북마크 해제",
+ "delete_confirm": "정말 지우시겠어요?",
+ "reply_to": "답글",
+ "mentions": "멘션",
+ "replies_list": "답글:",
+ "unmute_conversation": "대화 뮤트 해제",
+ "thread_muted": "스레드 뮤트됨",
+ "status_unavailable": "게시물 접근 불가",
+ "copy_link": "게시물 링크 복사",
+ "external_source": "원본 페이지",
+ "show_full_subject": "전체 제목 보기",
+ "hide_full_subject": "전체 제목 숨기기",
+ "show_content": "내용 보기",
+ "hide_content": "내용 숨기기",
+ "status_deleted": "지워진 게시물입니다",
+ "nsfw": "민감한 내용",
+ "expand": "펼치기",
+ "you": "(당신)",
+ "plus_more": "+{number}개 더 있음",
+ "many_attachments": "{number}개의 첨부물을 가짐",
+ "show_all_attachments": "첨부물 전부 보이기",
+ "hide_attachment": "첨부물 숨기기",
+ "collapse_attachments": "첨부물 접기",
+ "remove_attachment": "첨부물 지우기",
+ "attachment_stop_flash": "플래시 플레이어 정지",
+ "move_up": "첨부물 왼쪽으로 밀기",
+ "move_down": "첨부물 오른쪽으로 밀기",
+ "open_gallery": "갤러리 열기",
+ "thread_show": "이 스레드 보이기",
+ "thread_show_full_with_icon": "{icon} {text}",
+ "thread_follow_with_icon": "{icon} {text}",
+ "ancestor_follow_with_icon": "{icon} {text}",
+ "show_all_conversation_with_icon": "{icon} {text}",
+ "ancestor_follow": "이 게시물 아래 {numReplies}개 답글 더 보기 | 이 게시물 아래 {numReplies}개 답글 더 보기",
+ "show_only_conversation_under_this": "이 게시물의 답글만 보기",
+ "repeat_confirm": "리핏할까요?",
+ "repeat_confirm_title": "리핏 확인",
+ "repeat_confirm_accept_button": "리핏",
+ "repeat_confirm_cancel_button": "리핏 안 함",
+ "delete_confirm_title": "삭제 확인",
+ "delete_confirm_accept_button": "삭제",
+ "delete_confirm_cancel_button": "냅두기",
+ "delete_error": "게시물 삭제 에러: {0}",
+ "reaction_count_label": "{num}명이 반응함 | {num}명이 반응함"
+ },
+ "errors": {
+ "storage_unavailable": "Pleroma가 브라우저 저장소에 접근할 수 없습니다. 로그인이 풀리거나 로컬 설정이 초기화 되는 등 예상치 못한 문제를 겪을 수 있습니다. 쿠키를 활성화 해보세요."
+ },
+ "report": {
+ "reporter": "신고자:",
+ "reported_statuses": "신고된 게시물:",
+ "notes": "기타:",
+ "state": "상태:",
+ "state_open": "열림",
+ "state_closed": "닫힘",
+ "reported_user": "신고된 사용자:",
+ "state_resolved": "해결됨"
+ },
+ "user_reporting": {
+ "title": "{0} 신고",
+ "add_comment_description": "이 신고서는 내 인스턴스의 중재자에게 전달됩니다. 왜 이 계정을 신고하려는지 좀 더 자세히 알려주세요:",
+ "additional_comments": "추가 설명",
+ "forward_description": "이 계정은 다른 서버에 있는 계정입니다. 그쪽으로도 신고를 보낼까요?",
+ "forward_to": "{0}로 전달하기",
+ "submit": "전송",
+ "generic_error": "요청을 처리하는 중 오류가 발생했습니다."
+ },
+ "announcements": {
+ "end_time_prompt": "끝나는 시각: ",
+ "page_header": "공지사항",
+ "title": "공지사항",
+ "mark_as_read_action": "읽음으로 표시",
+ "post_form_header": "공지사항 작성",
+ "post_placeholder": "공지사항 내용을 작성하세요...",
+ "post_error": "오류: {error}",
+ "close_error": "닫기",
+ "delete_action": "삭제",
+ "post_action": "게시",
+ "start_time_prompt": "시작 시각: ",
+ "all_day_prompt": "온종일 있는 이벤트입니다",
+ "published_time_display": "{time}에 게시함",
+ "start_time_display": "{time}에 시작함",
+ "end_time_display": "{time}에 끝남",
+ "edit_action": "편집",
+ "submit_edit_action": "수정본 반영",
+ "cancel_edit_action": "취소",
+ "inactive_message": "이 공지사항은 비활성화 되었습니다"
+ },
+ "admin_dash": {
+ "window_title": "관리",
+ "wip_notice": "이 관리자 대시보드는 실험적이며 개발 중에 있습니다, {adminFeLink}.",
+ "old_ui_link": "대신 구 관리자 UI를 사용할 수 있습니다",
+ "reset_all": "전부 초기화",
+ "commit_all": "전부 저장",
+ "tabs": {
+ "nodb": "DB 설정 불가",
+ "instance": "인스턴스",
+ "frontends": "프론트엔드",
+ "limits": "제한"
+ },
+ "nodb": {
+ "heading": "데이터베이스 설정 기능이 비활성화 되어 있습니다",
+ "documentation": "관련 문서",
+ "text2": "대부분의 설정을 건드릴 수 없습니다.",
+ "text": "백엔드 설정 파일에서 {property}를 {value}로 바꿔야 합니다, {documentation}를 참고하세요."
+ },
+ "captcha": {
+ "kocaptcha": "KoCaptcha",
+ "native": "내장"
+ },
+ "instance": {
+ "registrations": "유저 가입",
+ "captcha_header": "캡차",
+ "kocaptcha": "KoCaptcha 설정",
+ "access": "인스턴스 접근",
+ "restrict": {
+ "timelines": "타임라인 접근",
+ "profiles": "사용자 프로필 접근",
+ "activities": "게시물/활동 접근",
+ "header": "로그인하지 않은 방문자의 접근을 제한",
+ "description": "특정 API의 접근을 허용할지 말지에 대한 세부 설정입니다. 기본적으로(애매한 체크 표시) 인스턴스가 비공개이면 접근을 차단합니다, 체크 표시는 인스턴스가 공개여도 차단함을 의미합니다, 체크 해제는 인스턴스가 비공개여도 접근을 허용함을 의미합니다. 설정을 바꾸면 예기치 않은 동작이 일어날 수 있음을 유의하세요, 예로 프로필 접근이 차단되면 프로필 정보 없이 게시물이 보여집니다."
+ },
+ "instance": "인스턴스 정보"
+ },
+ "limits": {
+ "arbitrary_limits": "임의 제한",
+ "posts": "게시물 제한",
+ "uploads": "첨부파일 제한",
+ "users": "사용자 프로필 제한",
+ "profile_fields": "프로필 필드 제한",
+ "user_uploads": "프로필 미디어 제한"
+ },
+ "frontend": {
+ "repository": "리포지토리 링크",
+ "versions": "사용 가능한 버전",
+ "build_url": "빌드 URL",
+ "reinstall": "재설치",
+ "is_default": "(기본)",
+ "is_default_custom": "(기본, 버전: {version})",
+ "install": "설치",
+ "install_version": "설치된 버전 {version}",
+ "more_install_options": "설치 옵션 더 보기",
+ "more_default_options": "기본 설정 옵션 더 보기",
+ "set_default": "기본으로 설정",
+ "set_default_version": "버전 {version}을 기본으로 설정",
+ "wip_notice": "이 부분은 프론트엔드 관리에 대한 백엔드 구현이 미완성이기 때문에 개발 중이고 몇몇 기능이 빠져 있습니다.",
+ "default_frontend": "기본 프론트엔드",
+ "default_frontend_tip2": "개발 중: 아직 Pleroma 백엔드가 모든 설치된 프론트엔드 목록을 알려주지 않기 때문에 이름과 ref을 직접 입력해야 합니다. 아래에 있는 목록은 여기 값을 입력하기 위한 단축 버튼입니다.",
+ "available_frontends": "설치 가능",
+ "default_frontend_tip": "기본 프론트엔드는 모든 유저에게 보입니다. 현재로썬 유저가 개인적으로 프론트엔드를 선택할 수 있진 않습니다. PleromaFE에서 벗어난다면 저희가 완전히 대체할 때까지는 인스턴스 설정을 위해서 아마도 낡고 버그투성이인 AdminFE를 쓰셔야 할 겁니다."
+ },
+ "temp_overrides": {
+ ":pleroma": {
+ ":instance": {
+ ":public": {
+ "label": "인스턴스를 공개",
+ "description": "이것을 끄면 모든 API가 로그인한 유저만 사용 가능하게 되며, 로그인하지 않은 사용자에겐 공개와 연합 타임라인이 보이지 않게 됩니다."
+ },
+ ":limit_to_local_content": {
+ "label": "로컬 컨텐츠만 검색하도록 제한",
+ "description": "로그인하지 않은 사람 (기본값), 모두 또는 없음에게 전역 검색을 비활성화합니다"
+ },
+ ":description_limit": {
+ "label": "글자수 제한",
+ "description": "첨부파일 설명문의 글자수 제한"
+ },
+ ":background_image": {
+ "label": "배경 이미지",
+ "description": "배경 이미지 (주로 PleromaFE에서 쓰임)"
+ }
+ }
+ }
+ }
}
}
diff --git a/src/i18n/languages.js b/src/i18n/languages.js
@@ -0,0 +1,54 @@
+const languages = [
+ 'ar',
+ 'ca',
+ 'cs',
+ 'de',
+ 'eo',
+ 'en',
+ 'es',
+ 'et',
+ 'eu',
+ 'fi',
+ 'fr',
+ 'ga',
+ 'he',
+ 'hu',
+ 'it',
+ 'ja',
+ 'ja_easy',
+ 'ko',
+ 'nan-TW',
+ 'nb',
+ 'nl',
+ 'oc',
+ 'pl',
+ 'pdc',
+ 'pt',
+ 'ro',
+ 'ru',
+ 'sk',
+ 'te',
+ 'uk',
+ 'zh',
+ 'zh_Hant'
+]
+
+const specialJsonName = {
+ ja: 'ja_pedantic'
+}
+
+const langCodeToJsonName = (code) => specialJsonName[code] || code
+
+const langCodeToCldrName = (code) => code
+
+const ensureFinalFallback = codes => {
+ const codeList = Array.isArray(codes) ? codes : [codes]
+ return codeList.includes('en') ? codeList : codeList.concat(['en'])
+}
+
+module.exports = {
+ languages,
+ langCodeToJsonName,
+ langCodeToCldrName,
+ ensureFinalFallback
+}
diff --git a/src/i18n/messages.js b/src/i18n/messages.js
@@ -7,49 +7,47 @@
// sed -i -e "s/'//gm" -e 's/"/\\"/gm' -re 's/^( +)(.+?): ((.+?))?(,?)(\{?)$/\1"\2": "\4"/gm' -e 's/\"\{\"/{/g' -e 's/,"$/",/g' file.json
// There's only problem that apostrophe character ' gets replaced by \\ so you have to fix it manually, sorry.
-const loaders = {
- ar: () => import('./ar.json'),
- ca: () => import('./ca.json'),
- cs: () => import('./cs.json'),
- de: () => import('./de.json'),
- eo: () => import('./eo.json'),
- es: () => import('./es.json'),
- et: () => import('./et.json'),
- eu: () => import('./eu.json'),
- fi: () => import('./fi.json'),
- fr: () => import('./fr.json'),
- ga: () => import('./ga.json'),
- he: () => import('./he.json'),
- hu: () => import('./hu.json'),
- it: () => import('./it.json'),
- ja: () => import('./ja_pedantic.json'),
- ja_easy: () => import('./ja_easy.json'),
- ko: () => import('./ko.json'),
- nb: () => import('./nb.json'),
- nl: () => import('./nl.json'),
- oc: () => import('./oc.json'),
- pl: () => import('./pl.json'),
- pt: () => import('./pt.json'),
- ro: () => import('./ro.json'),
- ru: () => import('./ru.json'),
- sk: () => import('./sk.json'),
- te: () => import('./te.json'),
- uk: () => import('./uk.json'),
- zh: () => import('./zh.json'),
- zh_Hant: () => import('./zh_Hant.json')
+import { isEqual } from 'lodash'
+import { languages, langCodeToJsonName } from './languages.js'
+
+const ULTIMATE_FALLBACK_LOCALE = 'en'
+
+const hasLanguageFile = (code) => languages.includes(code)
+
+const loadLanguageFile = (code) => {
+ return import(
+ /* webpackInclude: /\.json$/ */
+ /* webpackChunkName: "i18n/[request]" */
+ `./${langCodeToJsonName(code)}.json`
+ )
}
const messages = {
- languages: ['en', ...Object.keys(loaders)],
+ languages,
default: {
en: require('./en.json').default
},
setLanguage: async (i18n, language) => {
- if (loaders[language]) {
- let messages = await loaders[language]()
- i18n.setLocaleMessage(language, messages.default)
+ const languages = (Array.isArray(language) ? language : [language]).filter(k => k)
+
+ if (!languages.includes(ULTIMATE_FALLBACK_LOCALE)) {
+ languages.push(ULTIMATE_FALLBACK_LOCALE)
}
- i18n.locale = language
+ const [first, ...rest] = languages
+
+ if (first === i18n.locale && isEqual(rest, i18n.fallbackLocale)) {
+ return
+ }
+
+ for (const lang of languages) {
+ if (hasLanguageFile(lang)) {
+ const messages = await loadLanguageFile(lang)
+ i18n.setLocaleMessage(lang, messages.default)
+ }
+ }
+
+ i18n.fallbackLocale = rest
+ i18n.locale = first
}
}
diff --git a/src/i18n/nan-TW.json b/src/i18n/nan-TW.json
@@ -0,0 +1,1385 @@
+{
+ "about": {
+ "mrf": {
+ "federation": "聯邦",
+ "keyword": {
+ "keyword_policies": "關鍵字政策",
+ "ftl_removal": "Tuì「知影 ê 網路」時間線除掉",
+ "reject": "拒絕",
+ "replace": "取代",
+ "is_replaced_by": "→"
+ },
+ "mrf_policies": "啟用 ê MRF 政策",
+ "mrf_policies_desc": "MRF 政策操作本站 ê 對外通信行為。以下ê政策啟用 ah:",
+ "simple": {
+ "simple_policies": "站臺特有 ê 政策",
+ "instance": "站",
+ "reason": "理由",
+ "accept": "接受",
+ "accept_desc": "本站干焦接受下跤 ê 站 ê 短 phue:",
+ "reject": "拒絕",
+ "reject_desc": "本站 buē 接受 tuì 以下 ê 站 ê 短 phue:",
+ "quarantine": "隔離",
+ "quarantine_desc": "針對下跤 ê 站,本站干焦送出公開ê PO文:",
+ "ftl_removal": "Tuì「知影 ê 網路」時間線thâi掉",
+ "ftl_removal_desc": "本站buē 佇「知影 ê 網路」刊下跤 ê 站 ê PO文:",
+ "media_removal": "Thâi除媒體",
+ "media_removal_desc": "本站 kā 下跤 ê 站臺送 ê PO文 ê 媒體 lóng thâi 除:",
+ "media_nsfw": "媒體 lóng 標做「敏感內容」",
+ "media_nsfw_desc": "本站 kā 下跤 ê 站 ê 媒體,lóng 標做敏感內容:",
+ "not_applicable": "N/A"
+ }
+ },
+ "staff": "工作人員"
+ },
+ "announcements": {
+ "page_header": "公告",
+ "title": "公告",
+ "mark_as_read_action": "標做讀過",
+ "post_form_header": "貼公告",
+ "post_placeholder": "佇 tsia 拍你 ê 公告……",
+ "post_action": "貼",
+ "post_error": "錯誤:{error}",
+ "close_error": "關",
+ "start_time_prompt": "開始時間: ",
+ "end_time_prompt": "結束時間: ",
+ "all_day_prompt": "Tse 是 kui 工 ê 事件",
+ "published_time_display": "公告佇 {time}",
+ "start_time_display": "有效 tuì:{time}",
+ "end_time_display": "中止佇:{time}",
+ "edit_action": "編輯",
+ "submit_edit_action": "送出",
+ "cancel_edit_action": "取消",
+ "inactive_message": "這个公告 tsit-má 無效力",
+ "delete_action": "Thâi掉"
+ },
+ "shoutbox": {
+ "title": "留話枋"
+ },
+ "domain_mute_card": {
+ "mute": "消音",
+ "mute_progress": "Teh 消音……",
+ "unmute": "予有聲",
+ "unmute_progress": "Teh 予有聲……"
+ },
+ "exporter": {
+ "export": "匯出",
+ "processing": "Teh 處理,較停仔指示你下載檔案"
+ },
+ "features_panel": {
+ "shout": "留話枋",
+ "pleroma_chat_messages": "Pleroma 開講",
+ "media_proxy": "媒體代理伺侯器",
+ "scope_options": "公開範圍選項",
+ "text_limit": "字數限制",
+ "title": "有效 ê 功能",
+ "who_to_follow": "啥儂通綴",
+ "upload_limit": "檔案 sài-suh 限制",
+ "gopher": "Gopher"
+ },
+ "finder": {
+ "error_fetching_user": "Tshuē 用者 ê 時起錯誤",
+ "find_user": "Tshuē 用者"
+ },
+ "general": {
+ "apply": "應用",
+ "submit": "送出",
+ "more": "Koh 較 tsē",
+ "loading": "Leh 載入……",
+ "generic_error": "起錯誤 ah",
+ "generic_error_message": "起錯誤:{0}",
+ "error_retry": "請 koh 試一 kái",
+ "retry": "Koh 試",
+ "optional": "非必要",
+ "show_more": "展示較 tsē",
+ "show_less": "展示較少",
+ "never_show_again": "Mài koh 展示",
+ "dismiss": "無視",
+ "cancel": "取消",
+ "disable": "無愛用",
+ "enable": "啟用",
+ "confirm": "確認",
+ "verify": "驗證",
+ "close": "關掉",
+ "undo": "復原",
+ "yes": "是",
+ "no": "毋是",
+ "peek": "先看 māi",
+ "scroll_to_top": "捲 kàu 頂懸",
+ "role": {
+ "admin": "行政員",
+ "moderator": "管理員"
+ },
+ "unpin": "無愛 kā 釘",
+ "pin": "Kā釘起來",
+ "flash_content": "Ji̍h tsia,用 Ruffle(iáu teh 試驗,可能 buē 紡)看 Flash ê 內容。",
+ "flash_sepcurity": "注意 tse 可能有危險,因為 Flash 內容猶原是任意 ê 程式碼。",
+ "flash_fail": "載入 flash 內容失敗,詳細ē當看控制臺。",
+ "scope_in_timeline": {
+ "direct": "私人 phue",
+ "private": "干焦 hōo 綴 lí ê 看",
+ "public": "公開佇公共時間線",
+ "unlisted": "無愛公開佇公共時間線"
+ },
+ "flash_security": "Flash內容通藏任何ê指令,所以可能有危險。"
+ },
+ "image_cropper": {
+ "crop_picture": "裁相片",
+ "save": "儲存",
+ "save_without_cropping": "無裁就儲存",
+ "cancel": "取消"
+ },
+ "importer": {
+ "submit": "送出",
+ "success": "匯入成功。",
+ "error": "佇匯入 ê 時起錯誤。"
+ },
+ "login": {
+ "login": "登入",
+ "description": "用 OAuth 登入",
+ "logout": "登出",
+ "logout_confirm_title": "登出確認",
+ "logout_confirm": "Lí 敢真正 beh 登出?",
+ "logout_confirm_accept_button": "登出",
+ "logout_confirm_cancel_button": "mài 登出",
+ "password": "密碼",
+ "placeholder": "例:lain",
+ "register": "註冊",
+ "username": "用者 ê 名",
+ "hint": "登入,參與討論",
+ "authentication_code": "認證碼",
+ "enter_recovery_code": "輸入恢復碼",
+ "enter_two_factor_code": "輸入兩階段認證碼",
+ "recovery_code": "恢復碼",
+ "heading": {
+ "totp": "兩階段認證",
+ "recovery": "兩階段恢復"
+ }
+ },
+ "media_modal": {
+ "previous": "頂一 ê",
+ "next": "後一个",
+ "counter": "{current} / {total}",
+ "hide": "關掉媒體瀏覽"
+ },
+ "nav": {
+ "about": "關係本站",
+ "administration": "管理",
+ "back": "轉去",
+ "friend_requests": "跟綴請求",
+ "mentions": "The̍h起",
+ "interactions": "互動",
+ "dms": "私人 phue",
+ "public_tl": "公共時間線",
+ "timeline": "時間線",
+ "home_timeline": "Tshù ê 時間線",
+ "twkn": "知影 ê 網路",
+ "bookmarks": "冊籤",
+ "user_search": "Tshuē 用者",
+ "search_close": "關掉 tshiau-tshuē liâu",
+ "who_to_follow": "Siáng ē當綴",
+ "preferences": "個人 ê 設定",
+ "timelines": "時間線",
+ "chats": "開講",
+ "lists": "列單",
+ "edit_nav_mobile": "自訂導覽條",
+ "edit_pinned": "編輯釘起來 ê 項目",
+ "edit_finish": "編輯 suah",
+ "mobile_sidebar": "切換行動版 ê 邊 á liâu",
+ "mobile_notifications": "拍開通知(有無讀ê)",
+ "mobile_notifications_close": "關掉通知",
+ "announcements": "公告",
+ "search": "Tshuē",
+ "mobile_notifications_mark_as_seen": "Lóng 標做有讀",
+ "quotes": "引用"
+ },
+ "notifications": {
+ "broken_favorite": "狀態毋知影,leh tshiau-tshuē……",
+ "error": "佇取得通知 ê 時起錯誤:{0}",
+ "favorited_you": "kah 意 lí ê 狀態",
+ "followed_you": "綴 lí",
+ "follow_request": "想 beh 綴 lí",
+ "load_older": "載入 khah 早 ê 通知",
+ "notifications": "通知",
+ "read": "有讀ah!",
+ "repeated_you": "轉送 lí ê 狀態",
+ "no_more_notifications": "無別 ê 通知",
+ "migrated_to": "移民到",
+ "reacted_with": "顯出{0} ê 反應",
+ "submitted_report": "送出檢舉",
+ "poll_ended": "投票結束",
+ "unread_announcements": "{num} 篇公告iáu bē 讀",
+ "unread_chats": "{num} ê開講iáu bē讀",
+ "unread_follow_requests": "{num}ê新ê跟tuè請求",
+ "configuration_tip": "用{theSettings},lí通自訂siánn物佇tsia顯示。{dismiss}",
+ "configuration_tip_settings": "設定",
+ "configuration_tip_dismiss": "Mài koh顯示",
+ "subscribed_status": "有發送ê"
+ },
+ "polls": {
+ "add_poll": "開投票",
+ "add_option": "加選項",
+ "option": "選項",
+ "votes": "票",
+ "people_voted_count": "{count} 位有投",
+ "votes_count": "{count} 票",
+ "vote": "投票",
+ "type": "投票 ê 形式",
+ "single_choice": "孤選",
+ "multiple_choices": "Tsē 選",
+ "expiry": "投票期限",
+ "expires_in": "投票 tī {0} 以後結束",
+ "expired": "投票佇 {0} 以前結束",
+ "not_enough_options": "投票 ê 選項傷少"
+ },
+ "emoji": {
+ "stickers": "貼圖",
+ "emoji": "繪文字",
+ "keep_open": "Hōo 揀選仔開 leh",
+ "search_emoji": "Tshuē 繪文字",
+ "add_emoji": "插繪文字",
+ "custom": "定製 ê 繪文字",
+ "unpacked": "拍開 ê 繪文字",
+ "unicode": "Unicode 繪文字",
+ "unicode_groups": {
+ "activities": "活動",
+ "animals-and-nature": "動物 kap 自然",
+ "flags": "旗 á",
+ "food-and-drink": "食物 kap 飲料",
+ "objects": "物體",
+ "people-and-body": "Lâng kap 身軀",
+ "smileys-and-emotion": "笑面 kap 情緒",
+ "symbols": "符號",
+ "travel-and-places": "旅遊 kap 所在"
+ },
+ "load_all_hint": "載入頭前 {saneAmount} ê 繪文字,規个攏載入效能可能 ē khah 食力。",
+ "load_all": "Kā {emojiAmount} ê 繪文字攏載入",
+ "regional_indicator": "地區指引 {letter}",
+ "hide_custom_emoji": "Khàm掉自訂ê繪文字"
+ },
+ "errors": {
+ "storage_unavailable": "Pleroma buē-tàng the̍h 著瀏覽器儲存 ê。Lí ê 登入狀態抑是局部設定 buē 儲存,mā 凡勢 tú 著意料外 ê 問題。拍開 cookie 看māi。"
+ },
+ "interactions": {
+ "favs_repeats": "轉送 kap kah 意",
+ "follows": "最近綴 lí ê",
+ "emoji_reactions": "繪文字 ê 反應",
+ "reports": "檢舉",
+ "moves": "用者 ê 移民",
+ "load_older": "載入 koh khah 早 ê 互動",
+ "statuses": "訂ê"
+ },
+ "post_status": {
+ "edit_status": "編輯狀態",
+ "new_status": "PO 新 ê 狀態",
+ "account_not_locked_warning": "Lín 口座毋是 {0} ê。見 nā 有 lâng 綴--lí,ē-tàng 看著 lí ê 限定跟綴者 ê PO 文。.",
+ "account_not_locked_warning_link": "鎖起來 ê 口座",
+ "attachments_sensitive": "Kā 附件標做敏感內容",
+ "media_description": "媒體說明",
+ "content_type": {
+ "text/plain": "純 ê 文字",
+ "text/html": "HTML",
+ "text/markdown": "Markdown",
+ "text/bbcode": "BBCode"
+ },
+ "content_type_selection": "貼 ê 形式",
+ "content_warning": "主旨(毋是必要)",
+ "default": "Tú正kàu高雄ah。",
+ "direct_warning_to_all": "Tsit ê PO 文通 hōo 逐 ê 提起 ê 用者看見。",
+ "direct_warning_to_first_only": "Tsit ê PO 文,kan-ta佇短phue tú開始提起ê用者,tsiah通看見。",
+ "edit_remote_warning": "別 ê 站臺可能無支援編輯,無法度收著 PO 文上新 ê 版本。",
+ "edit_unsupported_warning": "Pleroma 無支持編輯 the̍h 起 hām 投票。",
+ "posting": "PO 文",
+ "preview": "Sing看māi",
+ "preview_empty": "空 ê",
+ "empty_status_error": "無法度 PO 無檔案 koh 空 ê 狀態",
+ "media_description_error": "更新媒體失敗,請 koh 試一 kái",
+ "scope_notice": {
+ "public": "Tsit ê PO 文通予逐 ê 儂看著",
+ "private": "Tsit ê PO 文 kan-ta 予綴 lí ê 看著",
+ "unlisted": "Tsit ê PO 文 buē 公開 tī 公共時間線 kap 知影 ê 網路"
+ },
+ "scope_notice_dismiss": "關掉 tsit ê 通知",
+ "scope": {
+ "direct": "私人 phue - PO 文干焦予提起 ê 用者看著",
+ "private": "限定綴 ê 儂 - PO 文干焦予綴 lí ê 儂看著",
+ "public": "公開 - PO kàu 公開時間線",
+ "unlisted": "Mài 列出來 - Mài PO tī 公開時間線"
+ },
+ "post": "PO 上去",
+ "reply_option": "應tsit ê狀態",
+ "quote_option": "引用tsit ê狀態"
+ },
+ "registration": {
+ "bio_optional": "介紹(毋是必要)",
+ "email_optional": "Email(毋是必要)",
+ "fullname": "顯示 ê 名",
+ "password_confirm": "確認密碼",
+ "registration": "註冊",
+ "token": "邀請碼",
+ "captcha": "驗證碼",
+ "new_captcha": "Ji̍h 圖片,the̍h 新 ê 驗證碼",
+ "fullname_placeholder": "e.g. 岩倉 Lain",
+ "bio_placeholder": "e.g.\nLí 好,我是 Lain。\n我是日本動畫 ê 角色,tuà tī 日本 ê 郊區。Lí 凡勢 bat tī Wired 知影我。",
+ "reason": "註冊 ê 理由",
+ "reason_placeholder": "本站靠人工審核註冊。\n介紹管理者 lí beh tī tsia 註冊 ê 理由。",
+ "register": "註冊",
+ "validations": {
+ "username_required": "著愛添",
+ "fullname_required": "著愛添",
+ "email_required": "著愛添",
+ "password_required": "著愛添",
+ "password_confirmation_required": "著愛添",
+ "password_confirmation_match": "密碼著相 kâng",
+ "birthday_required": "著愛添",
+ "birthday_min_age": "Buē-tàng tī {date} 以後"
+ },
+ "email_language": "Lí想 beh 服侍器用 siánn 物語言寄批 hōo lí?",
+ "birthday": "生日:",
+ "birthday_optional": "生日(毋是必要):",
+ "email": "電子 phue 箱",
+ "username_placeholder": "比如:lain"
+ },
+ "remote_user_resolver": {
+ "remote_user_resolver": "別站用者 ê 解析器",
+ "error": "Tshuē無。",
+ "searching_for": "Tshuē:"
+ },
+ "report": {
+ "reporter": "檢舉人:",
+ "reported_user": "Beh 檢舉 ê 用者:",
+ "reported_statuses": "Beh 檢舉 ê 狀態:",
+ "state_open": "開 ê",
+ "state_closed": "關 ê",
+ "state_resolved": "解決了 ê",
+ "notes": "註:",
+ "state": "狀態:"
+ },
+ "selectable_list": {
+ "select_all": "攏總揀"
+ },
+ "settings": {
+ "add_language": "加一 ê 備用 ê 語言",
+ "remove_language": "Ni 掉",
+ "primary_language": "主要語言:",
+ "fallback_language": "備用語言 {index}:",
+ "app_name": "App ê 名",
+ "expert_mode": "進階模式",
+ "save": "保存改變",
+ "security": "安全",
+ "setting_changed": "設定 kap 預先 ê 有 tsing 差",
+ "style": {
+ "common": {
+ "color": "色彩",
+ "opacity": "無透明度",
+ "contrast": {
+ "hint": "色彩ê對比率:{ratio}。{level}、 {context}",
+ "level": {
+ "aa": "合AA級ê準則(上kē ê)",
+ "aaa": "合AAA級ê準則(建議ê)",
+ "bad": "無合半ê無障礙準則"
+ },
+ "context": {
+ "18pt": "大(18pt 以上)ê文字",
+ "text": "文字"
+ }
+ }
+ },
+ "switcher": {
+ "keep_shadows": "保持陰影",
+ "keep_color": "保持色彩",
+ "keep_opacity": "保持無透明度",
+ "keep_roundness": "保留邊á角ê khà-buh",
+ "keep_fonts": "保持字型",
+ "reset": "重頭設定",
+ "clear_all": "Lóng清掉",
+ "clear_opacity": "清掉無透明度",
+ "load_theme": "載入主題",
+ "keep_as_is": "Mài振動",
+ "use_snapshot": "舊ê版本",
+ "use_source": "新ê版本",
+ "help": {
+ "upgraded_from_v2": "PleromaFE升級ah,主題huân-sè kap lí知影ê無kâng。",
+ "v2_imported": "Lí輸入ê檔案是舊版本ê前端用ê。Guán盡量予版本相通,毋過可能有所在buē-tàng。",
+ "older_version_imported": "Lí輸入ê檔案是予舊ê前端用ê。",
+ "future_version_imported": "Lí輸入ê檔案是新ê前端所用ê。",
+ "snapshot_missing": "無主題ê快相佇檔案內,所以,伊看起來凡勢kap原來預料ê無kâng。",
+ "snapshot_present": "主題ê快相有載入,所以逐ê值lóng khàm過去ah。Lí 通改載入主題實際ê資料。",
+ "fe_upgraded": "版本更新了後,Pleroma前端ê ia̋n-jín 升級ah。",
+ "fe_downgraded": "Pleroma ê前端滾tńg去ah。",
+ "migration_snapshot_ok": "為著保險,主題快相載入去ah。Lí ē當試載入主題資料。",
+ "migration_napshot_gone": "快相因故無去ah,tsi̍t-kuá所在看起來可能hām lí所想ê無kâng。",
+ "snapshot_source_mismatch": "版本tshia̋ng-póng:上可能因為前端滾轉去koh更新ah,若因為用舊版本ê前端,主題tsiah改變,lí有可能beh用舊ê版本。無,著用新ê。"
+ },
+ "save_load_hint": "佇揀iah是載入主題ê時,「保存」選項保留現tsú時設定ê選項;mā佇輸出主題ê時tsūn,儲存頭拄á講ê選項。若是逐ê選擇框á無設定,逐項設定就ē khǹg佇輸出ê主題。"
+ },
+ "common_colors": {
+ "_tab_label": "一般",
+ "main": "一般ê色彩",
+ "foreground_hint": "請看「進階」分頁,來調整khah幼ê所在",
+ "rgbo": "標頭、強調、徽章"
+ },
+ "advanced_colors": {
+ "_tab_label": "進階",
+ "alert": "警告ê背景",
+ "alert_error": "錯誤",
+ "alert_warning": "警告",
+ "alert_neutral": "其他ê",
+ "post": "PO文/用者紹介",
+ "badge": "徽章ê背景",
+ "popover": "提示、目錄、跳出來ê",
+ "badge_notification": "通知",
+ "panel_header": "面枋ê標題",
+ "top_bar": "頂 liâu-á",
+ "borders": "框á邊",
+ "buttons": "鈕仔",
+ "inputs": "輸入框á",
+ "faint_text": "淺ê文字",
+ "underlay": "Tshū-á",
+ "wallpaper": "壁紙",
+ "poll": "投票數ê圖",
+ "icons": "標á",
+ "highlight": "強調ê要素",
+ "pressed": "Tshi̍h ê 時",
+ "selectedPost": "選擇ê PO文",
+ "selectedMenu": "選擇ê目錄項目",
+ "disabled": "關ê",
+ "toggled": "切換ê時",
+ "tabs": "分頁",
+ "chat": {
+ "incoming": "收著ê",
+ "outgoing": "送出ê",
+ "border": "框á邊"
+ }
+ },
+ "radii": {
+ "_tab_label": "邊á角ê khà-buh"
+ },
+ "shadows": {
+ "_tab_label": "影kap光",
+ "override": "Khàm掉",
+ "shadow_id": "影 #{value}",
+ "blur": "予n̄g-n̄g",
+ "spread": "Hōo 闊",
+ "inset": "內pîng",
+ "filter_hint": {
+ "always_drop_shadow": "警告,tsit ê 影一直用 {0},若是瀏覽器支援tsē。",
+ "drop_shadow_syntax": "{0} 無支援參數 {1} kap 關鍵字 {2}。",
+ "avatar_inset": "請注意,結合內pîng kap外pîng ê影佇標頭,可能佇透明ê標頭現無預料ê結果。",
+ "spread_zero": "若是「hōo 闊」ê值比0較大,影ê顯示ē kap hōo 闊設做0 kâng款",
+ "inset_classic": "內pîng ê影ē用{0}"
+ },
+ "component": "部件",
+ "hintV3": "針對影,lí mā ē當用 {0} 標示法,來用其他ê色彩 khang (slot)。",
+ "components": {
+ "panelHeader": "面枋ê標題",
+ "topBar": "頂 liâu-á",
+ "avatar": "用者ê標頭(佇個人資料欄位)",
+ "popup": "跳出來ê kap提醒",
+ "button": "鈕仔",
+ "buttonHover": "鈕仔(滑鼠ê指標khǹg佇面頂)",
+ "panel": "面枋",
+ "avatarStatus": "用者ê標頭(佇PO文ê顯示)",
+ "buttonPressedHover": "鈕仔(滑鼠指標leh khǹg 佇頂懸,koh tshi̍h ê時)",
+ "buttonPressed": "鈕仔(leh tshi̍h ê時)",
+ "input": "輸入框á"
+ }
+ },
+ "fonts": {
+ "_tab_label": "字型",
+ "components": {
+ "interface": "界面",
+ "input": "輸入框á",
+ "post": "PO文",
+ "postCode": "RTF ê PO文ê平闊文字"
+ },
+ "family": "字型ê名",
+ "help": "揀界面元件所用ê字型。若是揀「家己指定」,lí著輸入系統內ê字型正確ê名。",
+ "size": "Sài-suh(單位:畫素)",
+ "weight": "字ê重(粗度)",
+ "custom": "家己指定"
+ },
+ "preview": {
+ "header": "先看māi",
+ "content": "內容",
+ "error": "錯誤ê例",
+ "button": "鈕á",
+ "text": "Tsē是{0}kap{1} ê例",
+ "mono": "內容",
+ "input": "Tú正kàu高雄ah。",
+ "faint_link": "有幫tsān ê手冊",
+ "fine_print": "讀guán ê {0},毋過學無有路用ê!",
+ "header_faint": "Tsē OK",
+ "checkbox": "我有讀過使用條款",
+ "link": "好ê細ê連結"
+ }
+ },
+ "upload": {
+ "error": {
+ "base": "上傳 ê 時失敗。",
+ "message": "傳 buē 起去:{0}",
+ "file_too_big": "檔案 sài-suh 傷大 [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+ "default": "Koh 試一 kái。"
+ }
+ },
+ "search": {
+ "people": "用戶",
+ "hashtags": "主題標籤",
+ "person_talking": "{count} ê leh 論",
+ "people_talking": "{count} ê leh 論",
+ "no_results": "無半 ê 結果",
+ "no_more_results": "無其他 ê 結果",
+ "load_more": "載入 koh 較 tsē 結果"
+ },
+ "password_reset": {
+ "forgot_password": "Buē 記得密碼?",
+ "password_reset": "重頭設密碼",
+ "instruction": "拍 lí ê email 地址 iah 是用者 ê 名。Guán 會送 lí 連結,重頭設定密碼。",
+ "placeholder": "Lí ê email 地址 iah 是用者 ê 名。",
+ "check_email": "檢查電子 phue 箱,看有重頭設密碼 ê 連結無。",
+ "return_home": "轉來頭頁",
+ "too_many_requests": "Lí kā 請求 ê khòo-tah 用了 ah。等一時仔,閣試一 pái。",
+ "password_reset_disabled": "密碼重頭設定無開放。請聯絡本站 ê 行政員。",
+ "password_reset_required": "Beh 登入,著重頭設 lí ê 密碼。",
+ "password_reset_required_but_mailer_is_disabled": "Lí 需要重頭設密碼,毋 koh tsia 無開放密碼 koh 再設定。請聯絡本站 ê 行政員。"
+ },
+ "chats": {
+ "message_user": "傳私人 phue:{nickname}",
+ "delete": "Thâi 掉",
+ "chats": "開講",
+ "new": "發起開講",
+ "empty_message_error": "無法度 PO 空 ê phue",
+ "more": "Koh較濟……",
+ "delete_confirm": "Lí 敢真 ê beh thâi tsit 張 phue?",
+ "error_loading_chat": "載入開講 ê 時,出箠 ah。",
+ "error_sending_message": "送 phue ê 時,出箠 ah。",
+ "empty_chat_list_placeholder": "Lí 猶無佇 tsia 開講過,來開講 lah!"
+ },
+ "lists": {
+ "lists": "列單",
+ "new": "新 ê 列單",
+ "title": "列單標題",
+ "search": "Tshuē 用者",
+ "create": "開新 ê",
+ "save": "保存改變",
+ "delete": "刣列單",
+ "following_only": "限定 lí 所關注 ê",
+ "manage_lists": "管理列單",
+ "manage_members": "管理列單成員",
+ "add_members": "Tshiau 閣較 tsē ê 用者",
+ "remove_from_list": "對列單刣掉",
+ "add_to_list": "加入去列單",
+ "is_in_list": "列單已經有 ah ",
+ "editing_list": "編輯列單 {listTitle}",
+ "creating_list": "開新 ê 列單",
+ "update_title": "保存標題",
+ "really_delete": "敢真正 beh 刣掉列單?",
+ "error": "操作列單 ê 時陣出重耽:{0}"
+ },
+ "file_type": {
+ "audio": "音訊",
+ "video": "影片",
+ "image": "影像",
+ "file": "檔案"
+ },
+ "display_date": {
+ "today": "今 á 日"
+ },
+ "update": {
+ "big_update_title": "敬請體諒",
+ "big_update_content": "因為 guán 有一站 á 無發行新版本,所以這个版本會 kap lí 以早慣 sì ê 無仝。",
+ "update_bugs": "請佇 {pleromaGitlab} 報告任何問題 kap bug,因為 Pleroma 改變真 tsē。雖罔 guán 徹底 leh 試,mā 家 kī 用開發版,伊凡勢有一寡重耽。Guán 歡迎 lín 提供關係所拄著 ê 問題 ê 意見、建議,或者是改進 Pleroma kap Pleroma-FE ê 法度。",
+ "update_changelog": "Nā beh 知影改變 ê 詳細,請看:{theFullChangelog}.",
+ "update_changelog_here": "Kui ê 改變日誌",
+ "art_by": "美編:{linkToArtist}"
+ },
+ "unicode_domain_indicator": {
+ "tooltip": "這 ê 域名包含毋是 ascii ê 字元。"
+ },
+ "setting_server_side": "Tsit-ê設定縛佇lí ê個人資料,mā 影響逐ê連線階段kap用者端",
+ "post_look_feel": "PO 文ê外貌kap感受",
+ "mention_links": "提起 ê 連結",
+ "mfa": {
+ "otp": "OTP",
+ "setup_otp": "設 OTP",
+ "wait_pre_setup_otp": "kā OTP 預設",
+ "title": "兩階段認證",
+ "generate_new_recovery_codes": "產生新ê恢復碼",
+ "warning_of_generate_new_codes": "產生新 ê 恢復碼ê時,舊 ê tio̍h 變無效。",
+ "recovery_codes": "恢復碼。",
+ "waiting_a_recovery_codes": "當leh收備份碼……",
+ "authentication_methods": "認證方法",
+ "scan": {
+ "title": "掃一 ē",
+ "secret_code": "鎖匙",
+ "desc": "The̍h lí个兩階段app,掃 tsit ê QR code,抑是拍文字鎖匙:"
+ },
+ "verify": {
+ "desc": "Nā beh開兩階段認證,請拍兩階段認證app內底ê碼:"
+ },
+ "confirm_and_enable": "確定,拍開 OTP",
+ "recovery_codes_warning": "著 kā tsiah ê 號碼抄落來,抑是儲存佇安全ê所在,因為號碼 buē koh 再出現。若是 lí 袂當用 lí 个兩階段認證app,而且恢復碼拍 ka-la̍uh,lí就永永buē當登入lí个口座。"
+ },
+ "lists_navigation": "佇導覽中顯示列單",
+ "allow_following_move": "若是綴ê口座徙位ê時,允准自動綴新ê",
+ "attachmentRadius": "附件",
+ "avatar": "標頭",
+ "avatarAltRadius": "標頭(通知)",
+ "avatarRadius": "標頭",
+ "background": "背景",
+ "bio": "紹介",
+ "block_export": "輸出封鎖名單",
+ "block_export_button": "封鎖名單輸出kàu csv檔",
+ "block_import_error": "佇輸入封鎖名單ê時出tshê",
+ "block_import": "輸入封鎖名單",
+ "mute_export": "輸出消音名單",
+ "mute_export_button": "輸出消音名單kàu csv檔",
+ "mute_import": "輸入消音名單",
+ "blocks_imported": "成功輸入封鎖名單!較停仔tsiah ē處理suah。",
+ "mutes_imported": "成功輸入消音名單!較停仔tsiah ē處理suah。",
+ "import_mutes_from_a_csv_file": "輸入封鎖名單ê csv檔",
+ "account_backup": "備份口座",
+ "mutes_and_blocks": "消音kap封鎖",
+ "delete_account": "Thâi口座",
+ "delete_account_error": "佇刣掉lí ê 口座ê時出問題。若是問題一直佇leh,請聯絡 lín 站臺 ê 行政員。",
+ "account_alias": "口座 ê 別名",
+ "account_alias_table_head": "別名",
+ "list_aliases_error": "佇the̍h別名ê時出tshê:{error}",
+ "hide_list_aliases_error_action": "關掉",
+ "remove_alias": "Thâi 掉tsit ê別名",
+ "new_alias_target": "加新ê別名(比如: {example}))",
+ "added_alias": "別名加入去ah。",
+ "add_alias_error": "佇加別名ê時出tshê:{error}",
+ "move_account": "徙口座",
+ "move_account_target": "目標口座(比如:{example})",
+ "moved_account": "口座徙過去ah。",
+ "move_account_error": "佇徙口座ê時出tshê:{error}",
+ "attachments": "附件",
+ "email_language": "服侍器送ê email 所用 ê 語言",
+ "enter_current_password_to_confirm": "輸入lí tsit-má ê 密碼,確認lí ê身份",
+ "mute_import_error": "佇輸入消音名單ê時出tshê",
+ "delete_account_description": "Ē 永永刣掉lí个資料,hōo lí 个口座bē當用。",
+ "delete_account_instructions": "佇佇下跤拍lí个密碼,確認 kā 口座 thâi掉。",
+ "move_account_notes": "若是欲徙tsit ê口座,著去lí ê目標口座hia,加一ê指tsia ê別名。",
+ "account_backup_table_head": "備份",
+ "download_backup": "下載",
+ "backup_not_ready": "備份猶 buē tshuân 予好勢。",
+ "backup_running": "備份leh處理,其中 {number} 筆記錄處理 suah--ah。",
+ "backup_failed": "備份失敗。",
+ "remove_backup": "Thâi 掉",
+ "list_backups_error": "佇 the̍h 備份列單ê時出tshê: {error}",
+ "add_backup": "開新ê備份",
+ "added_backup": "新ê備份開好 ah。",
+ "add_backup_error": "佇開新ê備份ê時出tshê:{error}",
+ "blocks_tab": "封鎖",
+ "bot": "Tse 是機器 lâng ê 口座",
+ "btnRadius": "鈕仔",
+ "cBlue": "藍色(回應,跟綴)",
+ "cGreen": "綠色(轉送)",
+ "cOrange": "柑仔色(kah 意)",
+ "cRed": "紅色(取消)",
+ "change_email": "換電子 phue 箱",
+ "changed_email": "電子 phue 箱變換成功!",
+ "change_password": "改密碼",
+ "change_password_error": "佇改密碼ê時出問題。",
+ "changed_password": "改密碼成功!",
+ "chatMessageRadius": "開講ê訊息",
+ "composing": "編寫ê設定",
+ "confirm_new_password": "確認新ê密碼",
+ "current_password": "Tann ê 密碼",
+ "confirm_dialogs": "問確認佇",
+ "confirm_dialogs_repeat": "轉送狀態",
+ "confirm_dialogs_unfollow": "無愛綴用者",
+ "confirm_dialogs_block": "封鎖用者",
+ "confirm_dialogs_mute": "kā用者消音",
+ "confirm_dialogs_delete": "thâi掉狀態",
+ "confirm_dialogs_logout": "登出",
+ "confirm_dialogs_approve_follow": "允准跟綴",
+ "confirm_dialogs_deny_follow": "無允准跟綴",
+ "confirm_dialogs_remove_follower": "徙走綴 lí ê",
+ "data_import_export_tab": "資料輸入/出",
+ "default_vis": "預設ê公開範圍",
+ "discoverable": "允准用tshiau-tshuē kap 其他ê服務tshuē著 tsit ê口座",
+ "domain_mutes": "域名",
+ "avatar_size_instruction": "建議ê標頭影像sài-suh 是150x150畫素。",
+ "pad_emoji": "Tuì 揀選器揀繪文字以後,佇繪文字雙 pîng 邊加空白",
+ "emoji_reactions_on_timeline": "佇時間線頂,顯示繪文字ê反應",
+ "emoji_reactions_scale": "反應ê規模係數",
+ "export_theme": "保存主題",
+ "filtering": "過濾",
+ "wordfilter": "詞語過濾器",
+ "word_filter_and_more": "詞語過濾器 kap 其他……",
+ "follow_export": "輸出 lí 所綴ê",
+ "follow_export_button": "輸出lí所綴ê kàu csv 檔",
+ "follow_import": "輸入lí所綴ê",
+ "follow_import_error": "佇輸入跟綴 ê 資料 ê 時出tshê",
+ "accent": "強調",
+ "foreground": "前景",
+ "general": "一般",
+ "hide_attachments_in_convo": "佇對話ê時,khàm附件",
+ "hide_attachments_in_tl": "Khàm掉時間線內ê附件",
+ "hide_media_previews": "Khàm掉媒體ê預展",
+ "hide_muted_posts": "Khàm掉消音ê用者ê PO文",
+ "hide_bot_indication": "Khàm 掉PO文內底ê機器lâng ê指示",
+ "hide_all_muted_posts": "Khàm掉消音êPO文",
+ "max_thumbnails": "PO文ê縮小圖ê khòo-tah(無寫=無限制)",
+ "hide_isp": "Khàm 站臺特有ê面 pang",
+ "right_sidebar": "Kā 邊á ê欄位徙kah正手pîng",
+ "navbar_column_stretch": "伸導覽liâu,kah 欄位平闊",
+ "always_show_post_button": "一直顯示「新ê PO文」ê鈕仔",
+ "hide_wallpaper": "Khàm站臺ê壁紙",
+ "use_one_click_nsfw": "Tshi̍h chi̍t 下就ē當拍開敏感內容",
+ "hide_post_stats": "Khàm PO文ê統計數據(比如:kah 意ê額數)",
+ "hide_filtered_statuses": "Khàm 逐ê過濾掉êPO文",
+ "hide_wordfiltered_statuses": "Khàm詞語過濾掉ê狀態",
+ "hide_muted_threads": "Khàm消音ê討論線",
+ "import_blocks_from_a_csv_file": "Tuì csv 檔輸入封鎖名單",
+ "import_followers_from_a_csv_file": "Uì csv 檔輸入跟綴ê資料",
+ "import_theme": "載入主題",
+ "inputRadius": "輸入ê格仔",
+ "checkboxRadius": "選擇框仔",
+ "instance_default": "(預設:{value})",
+ "instance_default_simple": "(預設)",
+ "interface": "界面",
+ "column_sizes_sidebar": "邊 á liâu",
+ "auto_update": "自動顯示新ê PO文",
+ "user_mutes": "用者",
+ "useStreamingApi": "連鞭收著PO文kap通知",
+ "use_websockets": "用websockets(實ê時間ê更新)",
+ "text": "文字",
+ "theme": "主題",
+ "theme_help": "用16進位ê碼(#rrggbb)來訂做家己ê色彩主題。",
+ "change_email_error": "佇換電子phue箱ê時出問題。",
+ "collapse_subject": "Kā 有主旨ê PO 文 khàm 起來",
+ "autocomplete_select_first": "若是有自動完成ê結果,自動揀頭一ê侯選ê",
+ "filtering_explanation": "見若有下跤ê詞語ê狀態,會hőng消音。一tsuā寫一ê",
+ "follows_imported": "Lí所綴ê輸入去ah!較停仔tsiah ē處理suah。",
+ "mute_bot_posts": "Kā 機器lâng ê PO文消音",
+ "hide_shoutbox": "Khàm 站臺ê留話pang",
+ "account_backup_description": "Tse 予 lí ē當 kā lín 口座 ê 資訊 kap PO 文載落來,毋過 in 猶無法度輸入kàu Pleroma口座 ê 內底。",
+ "theme_help_v2_1": "拍開選擇框á就 ē 當改掉一寡組件ê色彩kap無透明度。Ji̍h「Lóng清掉」,ē 恢復原來ê款。",
+ "preload_images": "Kā 圖片先載入",
+ "hide_user_stats": "Khàm 掉用者ê統計數據(比如:綴ê lâng額)",
+ "interfaceLanguage": "界面ê語言",
+ "invalid_theme_imported": "Lí 所揀ê主題檔案,Pleroma 無支援,所以主題無改。",
+ "limited_availability": "你ê瀏覽器內底buē當用",
+ "links": "連結",
+ "lock_account_description": "Kan-ta lí 同意,別儂tsiah通綴lí",
+ "loop_video": "循環播出ê影片",
+ "loop_video_silent_only": "Kan-ta無聲ê影片tsiah通循環播出(比如:Mastodon ê \"gif\")",
+ "mutes_tab": "消音",
+ "play_videos_in_modal": "佇跳出來ê框仔播出影片",
+ "url": "URL",
+ "preview": "預展",
+ "file_export_import": {
+ "backup_restore": "備份設定",
+ "backup_settings": "Kā 設定備份kàu檔案",
+ "backup_settings_theme": "Kā設定kap主題備份kàu檔案",
+ "restore_settings": "對檔案回復設定",
+ "errors": {
+ "file_too_old": "無接受ê主要版本:{fileMajor},檔案ê版本siūnn舊,buē當處理({feMajor} 版以後ê tsiah支援)",
+ "file_slightly_new": "檔案ê次版本無仝,一寡設定可能buē當載入去",
+ "invalid_file": "選擇ê檔案毋是Pleroma支援ê設定備份,設定無振動。",
+ "file_too_new": "無接受ê主要版本:{fileMajor},本 PleromaFE(設定版本 {feMajor})siūnn舊,buē當處理"
+ }
+ },
+ "profile_fields": {
+ "label": "個人資料ê meta資料",
+ "add_field": "加格仔",
+ "name": "標簽",
+ "value": "內容"
+ },
+ "birthday": {
+ "label": "生日",
+ "show_birthday": "顯示我ê生日"
+ },
+ "account_privacy": "隱私",
+ "use_contain_fit": "Mài裁附件ê縮小圖",
+ "name_bio": "名kah介紹",
+ "new_password": "新ê密碼",
+ "posts": "PO文",
+ "name": "名",
+ "new_email": "新ê電子phue箱",
+ "notification_visibility_likes": "收藏",
+ "hide_favorites_description": "Mài 顯示阮收藏ê列單(別儂uân-á ē收著通知)",
+ "user_profiles": "用者ê資料",
+ "notification_visibility": "Beh顯示啥款ê通知",
+ "notification_visibility_follows": "綴ê儂",
+ "notification_visibility_mentions": "提起",
+ "notification_visibility_repeats": "轉送",
+ "notification_visibility_moves": "用者suá位",
+ "notification_visibility_emoji_reactions": "反應",
+ "notification_visibility_polls": "Lí參與ê選舉辦suah佇",
+ "no_rich_text_description": "Po文mài用RTF格式",
+ "no_blocks": "無封鎖",
+ "no_mutes": "無消音",
+ "hide_follows_description": "Mài顯示我綴ê儂",
+ "hide_followers_description": "Mài顯示綴我ê儂",
+ "hide_follows_count_description": "Mài顯示我跟綴ê儂額",
+ "hide_followers_count_description": "Mài顯示綴我ê儂額",
+ "show_moderator_badge": "佇我ê個人資料顯示「管理員」證章",
+ "nsfw_clickthrough": "Khàm掉敏感ê媒體內容",
+ "oauth_tokens": "OAuth token",
+ "refresh_token": "重頭the̍h token",
+ "valid_until": "到期佇",
+ "revoke_token": "撤回",
+ "panelRadius": "面pang",
+ "presets": "代先ê設定",
+ "profile_background": "個人資料ê背景",
+ "profile_banner": "個人資料ê條á",
+ "profile_tab": "個人資料",
+ "radii_help": "設定界面邊á ê khà-buh (curve) ê 半徑(單位:畫素)",
+ "replies_in_timeline": "佇時間線內底ê回應",
+ "reply_visibility_all": "顯示所有ê回應",
+ "reply_visibility_following": "Kan-ta顯示送予我抑是我綴ê儂ê回應",
+ "reply_visibility_self": "Kan-ta顯示送予我ê回應",
+ "reply_visibility_following_short": "顯示予我所綴ê儂ê回應",
+ "reply_visibility_self_short": "Kan-ta顯示予我ka-kī ê回應",
+ "autohide_floating_post_button": "自動khàm掉「新êPO文」ê鈕仔(行動版)",
+ "saving_err": "佇保存設定ê時出tshê",
+ "saving_ok": "設定保存好ah",
+ "search_user_to_block": "Tshuē lí beh封鎖ê",
+ "search_user_to_mute": "Tshuē lí beh 消音ê",
+ "security_tab": "安全",
+ "scope_copy": "回應ê時ē khóo-pih ê範圍(私人phue 定著ē hőng khóo-pih)",
+ "minimal_scopes_mode": "Kā PO文ê公開範圍ê選項,kiu kah上細",
+ "set_new_avatar": "設定新ê標頭",
+ "set_new_profile_background": "設定新ê個人資料ê背景",
+ "set_new_profile_banner": "設定新ê個人資料ê條á",
+ "reset_avatar": "重頭設定標頭",
+ "reset_profile_background": "重頭設個人資料ê背景",
+ "reset_profile_banner": "重頭設個人資料ê條á",
+ "reset_avatar_confirm": "Lí敢確實beh 重頭設定標頭?",
+ "reset_banner_confirm": "Lí敢確實beh 重頭設定條á?",
+ "reset_background_confirm": "Lí敢確實beh 重頭設定背景?",
+ "settings": "設定",
+ "subject_input_always_show": "一直顯示主旨ê格á",
+ "subject_line_behavior": "回應ê時,khóo-pih主旨",
+ "subject_line_email": "電子phue風格:「re: 主旨」",
+ "subject_line_mastodon": "Mastodon風格:主旨無變",
+ "subject_line_noop": "Mài khóo-pih",
+ "conversation_display": "顯示對話ê風格",
+ "conversation_display_tree": "樹á ê形",
+ "disable_sticky_headers": "Mài 予欄位ê頭牢佇螢幕頂懸",
+ "show_scrollbars": "展示邊á ê欄位 ê giú-á",
+ "third_column_mode": "空間夠額ê時,展示第三ê欄位",
+ "third_column_mode_none": "不管時mài顯示第三ê欄位",
+ "third_column_mode_notifications": "通知ê欄位",
+ "third_column_mode_postform": "主要ê PO文表kah導覽",
+ "show_admin_badge": "佇我ê個人資料顯示「行政員」證章",
+ "pause_on_unfocused": "若是 Pleroma ê分頁無點開,tiō 暫停更新",
+ "conversation_display_tree_quick": "樹á形ê展示",
+ "columns": "欄位",
+ "column_sizes": "欄位sài-suh",
+ "column_sizes_content": "內容",
+ "column_sizes_notifs": "通知",
+ "tree_advanced": "允准用較活動ê方式導覽佇樹á形ê展示",
+ "tree_fade_ancestors": "用較淺ê色水顯示目前狀態ê前文",
+ "conversation_display_linear": "線á形ê風格",
+ "conversation_display_linear_quick": "線á形ê展示",
+ "conversation_other_replies_button": "顯示「其他ê回應」鈕仔",
+ "conversation_other_replies_button_below": "佇狀態下kha",
+ "conversation_other_replies_button_inside": "佇狀態內底",
+ "max_depth_in_thread": "預設ê討論線顯示層數ê上限",
+ "post_status_content_type": "Po文狀態ê內容類型",
+ "sensitive_by_default": "預設內,kā po文標做敏感內容",
+ "stop_gifs": "Kā滑鼠ê指標khǹg佇面頂ê時,動畫圖片tsiah振動",
+ "streaming": "Giú kàu頂懸ê時,自動展示新ê po文",
+ "theme_help_v2_2": "一寡圖片下kha ê標á,是背景/圖片ê對比指示,滑鼠指標khǹg佇面頂ê時,ē當看詳細。請記lit,若是用透明ê,對比指示顯示上bái ê情況。",
+ "tooltipRadius": "提醒",
+ "type_domains_to_mute": "揣beh愛消音ê域名",
+ "upload_a_photo": "Kā相片傳上去",
+ "user_settings": "用者ê設定",
+ "values": {
+ "false": "無",
+ "true": "是"
+ },
+ "mention_link_display_short": "一直顯示短ê名(比如: {'@'}foo)",
+ "mention_link_display_full": "一直用全名顯示(比如:{'@'}foo{'@'}example.org)",
+ "virtual_scrolling": "Kā時間線ê算畫最佳化",
+ "mention_link_display_full_for_remote": "Kan-ta kā其他域名ê用者,用全名顯示(比如:{'@'}foo{'@'}example.org)",
+ "token": "Token",
+ "use_at_icon": "用標á顯示 {'@'} 符號,mài用文字",
+ "mention_link_display": "顯示提起ê連結",
+ "mention_link_use_tooltip": "佇tshi̍h提起ê連結ê時,顯示用者ê卡片",
+ "mention_link_show_avatar": "佇連結邊á顯示用者ê標頭",
+ "mention_link_show_avatar_quick": "佇提起ê隔壁,顯示用者ê標頭",
+ "mention_link_fade_domain": "用較淺ê色水顯示域名(比如:{'@'}foo{'@'}example.org ê {'@'}example.org)",
+ "mention_link_bolden_you": "佇lí hőng提起ê時,強調對lí ê提起文字",
+ "user_popover_avatar_action": "Tshi̍h跳出來ê標頭ê動作",
+ "user_popover_avatar_action_zoom": "放大/縮小標頭",
+ "user_popover_avatar_action_close": "關掉跳出來ê框á",
+ "user_popover_avatar_action_open": "拍開個人資料",
+ "user_popover_avatar_overlay": "佇用者ê跳出來ê框仔面頂,顯示用者ê標頭",
+ "fun": "趣味ê",
+ "greentext": "Meme ê箭頭",
+ "show_yous": "顯示(Lí)",
+ "notifications": "通知",
+ "notification_setting_filters": "過濾ê",
+ "notification_setting_block_from_strangers": "關lí bô綴ê lâng 送ê通知",
+ "notification_setting_privacy": "隱私",
+ "notification_setting_hide_notification_contents": "Kā sak通知ê lâng kap伊ê內容khàm掉",
+ "notification_mutes": "若tsún無愛收tuì指定用者來ê通知,著用消音。",
+ "notification_blocks": "封鎖用者ē停止所有i hia來ê通知,mā取消訂伊。",
+ "enable_web_push_notifications": "拍開網頁sak通知ê功能",
+ "more_settings": "Koh較tsē ê設定",
+ "version": {
+ "title": "版本",
+ "backend_version": "後端ê版本",
+ "frontend_version": "前端ê版本"
+ },
+ "commit_value": "儲存",
+ "commit_value_tooltip": "值無儲存,tshi̍h tsit ê 鈕仔來送出你改變ê",
+ "hard_reset_value": "硬ê重頭設",
+ "hard_reset_value_tooltip": "Suá掉儲存內底ê設定,強制用預設ê值",
+ "reset_value": "重頭設",
+ "reset_value_tooltip": "重頭設草稿",
+ "hide_scrobbles": "Tshàng scrobble(記錄)",
+ "notification_show_extra": "顯示koh khah tsē ê通知佇通知ê欄位",
+ "notification_extra_chats": "顯示bô讀ê開講",
+ "notification_extra_announcements": "顯示bô讀ê公告",
+ "notification_extra_follow_requests": "顯示新ê跟tuè請求",
+ "notification_extra_tip": "顯示自訂其他通知ê撇步",
+ "confirm_new_setting": "Lí敢確認新ê設定?",
+ "text_size_tip": "用 {0} 做絕對值,{1} ē根據瀏覽器ê標準文字sài-suh放大縮小。",
+ "theme_debug": "佇處理透明ê時,顯示背景主題ia̋n-jín 所假使ê(DEBUG)",
+ "units": {
+ "time": {
+ "s": "秒鐘",
+ "m": "分鐘",
+ "h": "點鐘",
+ "d": "工"
+ }
+ },
+ "actor_type": "Tsit ê口座是:",
+ "actor_type_Person": "一般ê用者",
+ "actor_type_description": "標記lí ê口座做群組,ē hōo自動轉送提起伊ê狀態。",
+ "actor_type_Group": "群組",
+ "actor_type_Service": "機器lâng",
+ "appearance": "外觀",
+ "confirm_new_question": "Tse看起來kám好?設定ē佇10秒鐘後改轉去。",
+ "revert": "改轉去",
+ "confirm": "確認",
+ "text_size": "文字kap界面ê sài-suh",
+ "text_size_tip2": "毋是 {0} ê值可能ē破壞一寡物件kap主題",
+ "emoji_size": "繪文字ê sài-suh",
+ "navbar_size": "頂 liâu-á êsài-suh",
+ "panel_header_size": "面pang標題ê sài-suh",
+ "visual_tweaks": "細細ê外觀調整",
+ "scale_and_layout": "界面ê sài-suh kap排列"
+ },
+ "status": {
+ "favorites": "收藏",
+ "repeat_confirm_cancel_button": "Mài轉送",
+ "delete_confirm_title": "Thâi掉ê確認",
+ "edit": "編輯狀態",
+ "edited_at": "(頂kái編輯佇:{time})",
+ "pin": "釘佇個人資料",
+ "unpin": "Tuì個人資料拆掉",
+ "pinned": "釘入去ê",
+ "bookmark": "加入冊籤",
+ "unbookmark": "Tuì冊籤the̍h掉",
+ "delete_confirm": "Lí kám真ê beh thâi掉tsit ê狀態?",
+ "delete_confirm_accept_button": "Thâi掉",
+ "delete_confirm_cancel_button": "保留",
+ "reply_to": "回應",
+ "replies_list": "回應:",
+ "repeats": "轉送",
+ "repeat_confirm_accept_button": "轉送",
+ "repeat_confirm_title": "轉送ê確認",
+ "repeat_confirm": "Lí kám真ê beh轉送tsit ê狀態?",
+ "delete": "Thâi掉身份",
+ "delete_error": "Thâi狀態ê時出tshê:{0}",
+ "mentions": "提起",
+ "move_down": "Kā附件suá kàu正pîng",
+ "thread_show_full": "展示tsit 條討論線ê所有(lóng總有{numStatus}ê狀態,深度上限:{depth})",
+ "thread_follow": "看討論線tshun ê部份(lóng總有{numStatus}ê狀態)",
+ "replies_list_with_others": "回應(+其他{numReplies}ê):",
+ "mute_conversation": "Kā會話消音",
+ "unmute_conversation": "Kā會話取消消音",
+ "status_unavailable": "狀態bē當用",
+ "copy_link": "Khóo-pih 狀態ê連結",
+ "external_source": "外口ê來源",
+ "thread_muted": "討論線消音ah",
+ "thread_muted_and_words": ",有詞語:",
+ "hide_full_subject": "Khàm掉主題ê全文",
+ "show_full_subject": "顯示標題ê全文",
+ "show_content": "顯示內容",
+ "hide_content": "Khàm掉內容",
+ "status_deleted": "Tsit篇PO文thâi掉ah",
+ "nsfw": "敏感ê內容",
+ "expand": "Thián開",
+ "you": "(Lí)",
+ "plus_more": "Koh有{number}ê",
+ "many_attachments": "PO文有{number}ê附件",
+ "collapse_attachments": "Kā附件tshàng起來",
+ "show_all_attachments": "顯示逐ê附件",
+ "show_attachment_in_modal": "佇媒體模式顯示",
+ "show_attachment_description": "Kā敘述先看māi(拍開附件會當看kui ê敘述)",
+ "hide_attachment": "Khàm掉附件",
+ "attachment_stop_flash": "停止Flash ê播放器",
+ "remove_attachment": "Kā附件suá走",
+ "move_up": "Kā附件suá kàu倒pîng",
+ "open_gallery": "拍開畫廊",
+ "thread_hide": "Khàm掉討論線",
+ "thread_show": "顯示討論線",
+ "thread_show_full_with_icon": "{icon} {text}",
+ "thread_follow_with_icon": "{icon} {text}",
+ "ancestor_follow": "看其他{numReplies}ê佇tsit ê狀態ê回應",
+ "ancestor_follow_with_icon": "{icon} {text}",
+ "show_all_conversation_with_icon": "{icon} {text}",
+ "show_all_conversation": "看kui ê會話(有其他{numStatus}ê狀態)",
+ "show_only_conversation_under_this": "Kan-ta顯示tsit ê狀態ê回應",
+ "status_history": "狀態ê歷史",
+ "reaction_count_label": "{num}ê lâng用表情反應",
+ "hide_quote": "Khàm掉引用ê狀態",
+ "display_quote": "顯示引用ê狀態",
+ "invisible_quote": "引用ê狀態bē當用:{link}",
+ "more_actions": "佇tsit ê狀態ê其他動作"
+ },
+ "user_card": {
+ "favorites": "收藏",
+ "show_repeats": "顯示轉送",
+ "hide_repeats": "Khàm掉轉送",
+ "remove_follower_confirm": "Lí kám真正想beh kā {user} tuì lí所跟綴ê suá走?",
+ "statuses": "狀態",
+ "admin_menu": {
+ "activate_account": "啟動口座",
+ "deactivate_account": "予口座失效",
+ "delete_account": "Thâi掉口座",
+ "force_nsfw": "Kā逐ê PO文標做敏感內容",
+ "strip_media": "Tuì PO文thâi掉媒體",
+ "force_unlisted": "強制PO文mài列佇公共時間線",
+ "disable_remote_subscription": "Mài允准tuì其他站臺跟tuè用者",
+ "sandbox": "強制PO文kan-ta予跟tuè ê看",
+ "disable_any_subscription": "Mài允准跟tuè任何用者",
+ "quarantine": "Tuì聯邦禁止用者ê PO文",
+ "delete_user": "Thâi掉用者ê口座",
+ "delete_user_data_and_deactivate_confirmation": "Án-ne ē永永thâi掉tsit ê口座ê資料兼hōo失效。Lí kám完全確定?",
+ "grant_admin": "授與行政員ê權",
+ "revoke_admin": "撤掉行政員ê權",
+ "moderation": "仲裁",
+ "grant_moderator": "授與仲裁員ê權",
+ "revoke_moderator": "撤掉仲裁員ê權"
+ },
+ "highlight": {
+ "disabled": "Mài強調",
+ "side": "邊á ê花tsuā",
+ "solid": "孤色ê背景",
+ "striped": "花tsuā ê背景"
+ },
+ "note": "筆記",
+ "note_blank": "(無)",
+ "edit_note": "編輯筆記",
+ "edit_note_apply": "適用",
+ "approve": "核准",
+ "approve_confirm_title": "核准ê確認",
+ "approve_confirm_accept_button": "核准",
+ "approve_confirm_cancel_button": "Mài核准",
+ "block": "封鎖",
+ "blocked": "封鎖ah!",
+ "block_confirm_title": "封鎖ê確認",
+ "approve_confirm": "Lí kám想beh核准{user}ê跟tuè請求?",
+ "block_confirm": "Lí kám 真正想beh封鎖{user}?",
+ "block_confirm_accept_button": "封鎖",
+ "block_confirm_cancel_button": "Mài封鎖",
+ "deactivated": "停止使用ah",
+ "deny": "拒絕",
+ "deny_confirm_title": "拒絕ê確認",
+ "deny_confirm_accept_button": "拒絕",
+ "deny_confirm_cancel_button": "Mài拒絕",
+ "deny_confirm": "Lí kám想beh拒絕{user}ê跟tuè請求?",
+ "edit_profile": "編輯個人資料",
+ "follow": "跟tuè",
+ "follow_cancel": "取消請求",
+ "follow_sent": "請求送ah!",
+ "follow_progress": "Teh請求……",
+ "follow_unfollow": "無愛跟tuè",
+ "unfollow_confirm_title": "無愛跟tuè ê確認",
+ "unfollow_confirm": "lí kám真正無beh跟tuè {user}?",
+ "unfollow_confirm_accept_button": "無愛跟綴",
+ "unfollow_confirm_cancel_button": "繼續跟tuè",
+ "followees": "Teh跟綴",
+ "followers": "跟綴ê",
+ "following": "Teh跟tuè!",
+ "follows_you": "跟tuè lí!",
+ "hidden": "Tshàng起來ê",
+ "its_you": "Tse是lí!",
+ "media": "媒體",
+ "mention": "提起",
+ "message": "短phue",
+ "mute": "消音",
+ "muted": "消音ê",
+ "mute_confirm_title": "消音ê確認",
+ "mute_confirm": "Lí確定想beh kā {user}消音?",
+ "mute_confirm_accept_button": "消音",
+ "mute_confirm_cancel_button": "Mài消音",
+ "mute_duration_prompt": "消音tsit ê用戶ê期限(0表示永遠):",
+ "per_day": "/kang",
+ "remote_follow": "遠距離ê關注",
+ "remove_follower": "Suá走跟綴ê",
+ "remove_follower_confirm_title": "Suá走跟tuè者ê確認",
+ "remove_follower_confirm_accept_button": "Suá走",
+ "remove_follower_confirm_cancel_button": "保留",
+ "report": "檢舉",
+ "subscribe": "注文",
+ "unsubscribe": "取消注文",
+ "unblock": "Mài封鎖",
+ "unblock_progress": "Teh取消封鎖……",
+ "block_progress": "Leh封鎖……",
+ "unmute": "Mài消音",
+ "mute_progress": "Leh消音……",
+ "unmute_progress": "Leh取消消音……",
+ "bot": "機器lâng",
+ "birthday": "出世佇{birthday}",
+ "edit_note_cancel": "取消"
+ },
+ "tool_tip": {
+ "favorite": "收藏",
+ "repeat": "轉送",
+ "media_upload": "Kā媒體傳起去",
+ "reply": "回應",
+ "add_reaction": "加反應",
+ "user_settings": "用者ê設定",
+ "accept_follow_request": "允准跟tuè ê請求",
+ "reject_follow_request": "拒絕跟tuè ê請求",
+ "bookmark": "冊籤",
+ "toggle_expand": "Thián開á是tshàng通知,顯示kui篇PO文",
+ "toggle_mute": "Thián開á是tshàng通知,顯露消音ê內容",
+ "autocomplete_available": "{number} ê結果通用。用頂kap下ê key來看結果。"
+ },
+ "password_reset": {
+ "instruction": "輸入你ê email地址iah是用者ê名。阮ē寄予lí連結,通重頭設你ê密碼。",
+ "password_reset_disabled": "密碼重頭設ê功能無開放。請聯絡lín站臺ê行政員。",
+ "password_reset_required_but_mailer_is_disabled": "Lí著重設密碼,M̄-koh重頭設密碼ê功能無開放。請聯絡lín站臺ê行政員。",
+ "forgot_password": "Buē記得密碼?",
+ "password_reset": "密碼重頭設",
+ "placeholder": "你ê email iah是用者ê名",
+ "check_email": "檢查你ê電子phue箱,有重頭設密碼ê連結ê phue無。",
+ "return_home": "Tńg去頭頁",
+ "too_many_requests": "Lí已經kàu 試ê回數限制 ah,小等leh koh試。",
+ "password_reset_required": "Lí著重設密碼,tsiah通登入。"
+ },
+ "admin_dash": {
+ "window_title": "行政員",
+ "reset_all": "Kui ê重頭設",
+ "wip_notice": "Tsit ê 管理 la-jí-báng (dashboard) 是試驗ê,koh teh 起做,{adminFeLink}.",
+ "old_ui_link": "舊ê管理界面佇tsia",
+ "commit_all": "Lóng總儲存",
+ "tabs": {
+ "nodb": "無資料庫ê設置",
+ "instance": "站臺",
+ "limits": "限制",
+ "frontends": "前端"
+ },
+ "nodb": {
+ "heading": "資料庫設置無開放",
+ "text": "Lí需要改後端ê設置檔案,tsiah ē當kā{property}設做{value},請佇{documentation}了解詳細。",
+ "documentation": "文件",
+ "text2": "大部份ê設定ē無開放。"
+ },
+ "limits": {
+ "user_uploads": "個人資料ê媒體限制",
+ "arbitrary_limits": "任何限制",
+ "posts": "PO文ê限制",
+ "uploads": "附件ê限制",
+ "users": "用者個人資料ê限制",
+ "profile_fields": "個人資料欄位ê限制"
+ },
+ "captcha": {
+ "native": "在來ê",
+ "kocaptcha": "KoCaptcha"
+ },
+ "instance": {
+ "instance": "站臺ê資訊",
+ "registrations": "用者ê註冊",
+ "kocaptcha": "KoCaptcha ê設定",
+ "restrict": {
+ "header": "管制無落名ê訪客使用",
+ "timelines": "讀取時間線",
+ "profiles": "讀取用者ê個人資料",
+ "activities": "讀取狀態/活動",
+ "description": "(無)允准一kuá方面ê API the̍h取資源ê詳細設定。預設(無定ê狀態),若是站臺毋是公開ê,ē無允准the̍h取;選擇框á若勾,就算站臺是公開ê,iáu是無允准the̍h取;若無勾,就算站臺是私人ê,mā是允准the̍h取。請注意,若是設一kuá設定,無預料ê行為可能產生。比如講,若是the̍h取個人資料無開放,PO文buē顯示個人資料。"
+ },
+ "access": "讀取實體",
+ "captcha_header": "CAPTCHA"
+ },
+ "frontend": {
+ "repository": "原始碼庫ê連結",
+ "versions": "通用ê版本",
+ "build_url": "起做URL",
+ "reinstall": "重頭安裝",
+ "is_default": "(預設)",
+ "is_default_custom": "(預設,版本:{version})",
+ "install": "安裝",
+ "install_version": "安裝ê版本:{version}",
+ "more_install_options": "其他ê安裝選項",
+ "more_default_options": "其他ê預設設定ê選項",
+ "set_default": "設做預設ê",
+ "set_default_version": "Kā版本{version}設做預設ê",
+ "default_frontend_tip": "預設ê前端ē展示予逐ê用者。現在,用者無法度揀個人ê前端。若是lí變換,無beh用PleromaFE,上有可能ē用舊koh問題tsē ê AdminFE 做站臺ê設置,佇阮iáu-bē kā伊取代以前。",
+ "wip_notice": "請注意,tsit ê段落iáu teh起做,欠缺一寡特點,因為後端tuì前端管理ê實做無齊備。",
+ "default_frontend": "預設ê前端",
+ "default_frontend_tip2": "Teh起做:因為Pleroma後端無適當列出逐ê安裝ê前端,lí著手動輸入名字kap引用。下kha ê列單提供寫tsiah-ê 值ê近路。",
+ "available_frontends": "Ē當安裝"
+ },
+ "temp_overrides": {
+ ":pleroma": {
+ ":instance": {
+ ":public": {
+ "description": "無開放tse,ē 控制逐êAPI,干焦予登入ê用者用,mā ē予公開kap聯邦ê時間線,buē當予無落名ê訪客the̍h著。",
+ "label": "站臺是公開ê"
+ },
+ ":limit_to_local_content": {
+ "label": "Kan-ta會當tshuē在地ê內容",
+ "description": "無開放無認證ê用者、逐儂,猶是lóng總開放tshuē全球ê網路"
+ },
+ ":description_limit": {
+ "label": "限制",
+ "description": "附件說明ê字元限制"
+ },
+ ":background_image": {
+ "label": "背景ê影像",
+ "description": "背景ê影像(主要予PleromaFE用)"
+ }
+ }
+ }
+ }
+ },
+ "timeline": {
+ "up_to_date": "是上新ê",
+ "collapse": "疊起來",
+ "conversation": "會話",
+ "error": "佇the̍h時間線ê時出tshê:{0}",
+ "load_older": "載入舊ê狀態",
+ "repeated": "轉送ah",
+ "no_retweet_hint": "PO文hőng標做限定跟綴ê,á是私人phue,無法度轉送",
+ "show_new": "看新ê",
+ "reload": "重新載入",
+ "no_more_statuses": "無其他ê狀態",
+ "no_statuses": "無狀態",
+ "socket_reconnected": "實時ê連結成立ah",
+ "socket_broke": "實時連結拍m̄見ah:CloseEvent代碼{0}",
+ "quick_view_settings": "快速 view ê設定",
+ "quick_filter_settings": "快速過濾器ê設定"
+ },
+ "time": {
+ "unit": {
+ "days": "{0}工",
+ "days_short": "{0}工",
+ "hours": "{0}點鐘",
+ "hours_short": "{0}點鐘",
+ "minutes": "{0}分鐘",
+ "minutes_short": "{0}分",
+ "months": "{0}個月",
+ "months_short": "{0}個月",
+ "seconds": "{0}秒鐘",
+ "seconds_short": "{0}秒",
+ "weeks": "{0}禮拜",
+ "weeks_short": "{0}週",
+ "years": "{0}年",
+ "years_short": "{0}年"
+ },
+ "in_future": "koh有{0}",
+ "in_past": "{0}進前",
+ "now": "tú正",
+ "now_short": "tsit-má"
+ },
+ "user_reporting": {
+ "title": "檢舉 {0}",
+ "forward_description": "Tsit ê口座是別ê站臺ê。Mā kám beh寄報告ê khóo-pih kàu hit ê站?",
+ "add_comment_description": "本檢舉ē 寄kàu你ê站臺ê仲裁員。Lí會當佇下kha解說檢舉tsit ê口座ê原因:",
+ "additional_comments": "其他ê意見",
+ "forward_to": "轉送kàu{0}",
+ "submit": "送出",
+ "generic_error": "佇處理lí ê請求ê時出tshê。"
+ },
+ "lists": {
+ "really_delete": "Kám真正 beh thâi列單?",
+ "search": "Tshiau-tshuē用者",
+ "create": "建立",
+ "save": "保存改變",
+ "delete": "Thâi列單",
+ "lists": "列單",
+ "new": "新ê列單",
+ "title": "列單ê標題",
+ "following_only": "限制佇跟tuè ê",
+ "manage_lists": "管理列單",
+ "manage_members": "管理列單ê成員",
+ "add_members": "Tshiau-tshuē其他ê用者",
+ "remove_from_list": "Tuì列單suá走",
+ "add_to_list": "Ke-thinn kàu列單",
+ "is_in_list": "已經佇列單內底",
+ "editing_list": "編輯列單 {listTitle}",
+ "creating_list": "開新ê列單",
+ "update_title": "保存標題",
+ "error": "佇操作列單ê時出tshê:{0}"
+ },
+ "update": {
+ "update_bugs": "請報告任何問題kap錯誤佇 {pleromaGitlab},因為已經改變真tsē。雖bóng guán徹底試過,ka-kī mā用開發版,iáu是有可能有無注意ê所在。Guán歡迎lí tuì所tú tio̍h ê問題,提出意見kap建議,或者是改進Pleroma kap Pleroma-FE ê方法。",
+ "big_update_title": "請sió等tsi̍t ê",
+ "update_bugs_gitlab": "Pleroma GitLab",
+ "big_update_content": "Guán已經有tsi̍t段時間無推出發行,所以外觀kap感覺kap lí所慣勢ê,凡勢無kâng。",
+ "update_changelog": "Beh知影改變ê詳細,請看{theFullChangelog}。",
+ "update_changelog_here": "Changelog全文",
+ "art_by": "美術製作:{linkToArtist}"
+ },
+ "user_profile": {
+ "timeline_title": "用者ê時間線",
+ "profile_does_not_exist": "Pháinn勢,tsit ê個人資料無佇leh。",
+ "profile_loading_error": "Pháinn勢,佇載入tsit ê個人資料ê時出tshê。"
+ },
+ "who_to_follow": {
+ "more": "詳情",
+ "who_to_follow": "Siáng通tuè"
+ },
+ "upload": {
+ "error": {
+ "base": "傳起去ê時失敗。",
+ "message": "傳起去ê時失敗:{0}",
+ "file_too_big": "檔案siūnn大[{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+ "default": "Koh試tsi̍t kái"
+ },
+ "file_size_units": {
+ "B": "B",
+ "KiB": "KiB",
+ "MiB": "MiB",
+ "GiB": "GiB",
+ "TiB": "TiB"
+ }
+ },
+ "search": {
+ "people": "Lâng",
+ "hashtags": "井字ê標籤",
+ "person_talking": "{count}ê lâng teh開講",
+ "people_talking": "{count}ê lâng teh開講",
+ "no_results": "無結果",
+ "no_more_results": "無其他結果",
+ "load_more": "載入其他結果"
+ },
+ "chats": {
+ "you": "Lí:",
+ "message_user": "送短phue予:{nickname}",
+ "delete": "Thâi掉",
+ "chats": "開講",
+ "new": "新ê開講",
+ "empty_message_error": "Bē當PO空ê短phue",
+ "more": "其他",
+ "delete_confirm": "Lí kám真正beh thâi tsit ê短phue?",
+ "error_loading_chat": "佇載入開講ê時出問題。",
+ "error_sending_message": "佇送短phue ê時出問題。",
+ "empty_chat_list_placeholder": "Lí iáu buē開講過。開始開講!"
+ },
+ "file_type": {
+ "audio": "聲音",
+ "video": "影片",
+ "image": "影像",
+ "file": "檔案"
+ },
+ "display_date": {
+ "today": "今á日"
+ },
+ "unicode_domain_indicator": {
+ "tooltip": "Tsit ê域名含m̄是ascii ê字元。"
+ }
+}
diff --git a/src/i18n/nl.json b/src/i18n/nl.json
@@ -8,10 +8,11 @@
"media_proxy": "Mediaproxy",
"scope_options": "Zichtbaarheidsopties",
"text_limit": "Tekstlimiet",
- "title": "Kenmerken",
+ "title": "Functies",
"who_to_follow": "Wie te volgen",
"upload_limit": "Upload limiet",
- "pleroma_chat_messages": "Pleroma Chat"
+ "pleroma_chat_messages": "Pleroma Chat",
+ "shout": "Shoutbox"
},
"finder": {
"error_fetching_user": "Fout tijdens ophalen gebruiker",
@@ -39,6 +40,15 @@
"role": {
"moderator": "Moderator",
"admin": "Beheerder"
+ },
+ "flash_content": "Klik om Flash-content te laten zien met Ruffle (Experimenteel, werkt mogelijk niet).",
+ "flash_security": "Let op: Flash-inhoud is niet gescreend en kan malware bevatten.",
+ "flash_fail": "Laden van Flash-content is mislukt, zie console voor details.",
+ "scope_in_timeline": {
+ "direct": "Privé",
+ "private": "Alleen-volgers",
+ "public": "Openbaar",
+ "unlisted": "Niet-openbaar"
}
},
"login": {
@@ -60,7 +70,7 @@
}
},
"nav": {
- "about": "Over",
+ "about": "Over ons",
"back": "Terug",
"chat": "Lokale Chat",
"friend_requests": "Volgverzoeken",
@@ -68,7 +78,7 @@
"dms": "Privéberichten",
"public_tl": "Openbare tijdlijn",
"timeline": "Tijdlijn",
- "twkn": "Bekende Netwerk",
+ "twkn": "Globale Netwerk",
"user_search": "Gebruiker Zoeken",
"who_to_follow": "Wie te volgen",
"preferences": "Voorkeuren",
@@ -81,22 +91,23 @@
"bookmarks": "Bladwijzers"
},
"notifications": {
- "broken_favorite": "Onbekende status, aan het zoeken…",
- "favorited_you": "vond je status leuk",
+ "broken_favorite": "Onbekend bericht, aan het zoeken…",
+ "favorited_you": "vond je bericht leuk",
"followed_you": "volgt jou",
"load_older": "Oudere meldingen laden",
"notifications": "Meldingen",
"read": "Gelezen!",
- "repeated_you": "herhaalde je status",
+ "repeated_you": "herhaalde je bericht",
"no_more_notifications": "Geen meldingen meer",
"migrated_to": "is gemigreerd naar",
"follow_request": "wil je volgen",
"reacted_with": "reageerde met {0}",
- "error": "Fout bij ophalen van meldingen: {0}"
+ "error": "Fout bij ophalen van meldingen: {0}",
+ "poll_ended": "peiling is beëindigd"
},
"post_status": {
- "new_status": "Nieuwe status plaatsen",
- "account_not_locked_warning": "Je account is niet {0}. Iedereen kan je volgen om je alleen-volgers-berichten te lezen.",
+ "new_status": "Nieuw bericht plaatsen",
+ "account_not_locked_warning": "Je account is niet {0}. Iedereen kan je volgen om je alleen-volgersberichten te lezen.",
"account_not_locked_warning_link": "gesloten",
"attachments_sensitive": "Bijlagen als gevoelig markeren",
"content_type": {
@@ -108,10 +119,10 @@
"content_warning": "Onderwerp (optioneel)",
"default": "Tijd voor anime!",
"direct_warning": "Deze post zal enkel zichtbaar zijn voor de personen die genoemd zijn.",
- "posting": "Plaatsen",
+ "posting": "Aan het plaatsen",
"scope": {
"direct": "Privé - bericht enkel naar vermelde gebruikers sturen",
- "private": "Enkel volgers - bericht enkel naar volgers sturen",
+ "private": "Alleen-volgers - bericht is enkel leesbaar voor volgers",
"public": "Openbaar - bericht op openbare tijdlijnen plaatsen",
"unlisted": "Niet vermelden - niet tonen op openbare tijdlijnen"
},
@@ -119,11 +130,11 @@
"direct_warning_to_first_only": "Dit bericht zal alleen zichtbaar zijn voor de vermelde gebruikers aan het begin van het bericht.",
"scope_notice": {
"public": "Dit bericht zal voor iedereen zichtbaar zijn",
- "unlisted": "Dit bericht zal niet zichtbaar zijn in de Openbare Tijdlijn en Het Geheel Bekende Netwerk",
+ "unlisted": "Dit bericht zal niet zichtbaar zijn in de Openbare Tijdlijn en Het Globale Netwerk",
"private": "Dit bericht zal voor alleen je volgers zichtbaar zijn"
},
- "post": "Bericht",
- "empty_status_error": "Kan geen lege status zonder bijlagen plaatsen",
+ "post": "Plaatsen",
+ "empty_status_error": "Kan geen leeg bericht zonder bijlagen plaatsen",
"preview_empty": "Leeg",
"preview": "Voorbeeld",
"media_description": "Mediaomschrijving",
@@ -149,13 +160,14 @@
"username_placeholder": "bijv. lain",
"fullname_placeholder": "bijv. Lain Iwakura",
"bio_placeholder": "bijv.\nHallo, ik ben Lain.\nIk ben een animemeisje woonachtig in een buitenwijk in Japan. Je kent me misschien van the Wired.",
- "reason_placeholder": "Deze instantie keurt registraties handmatig goed.\nLaat de beheerder weten waarom je wilt registreren.",
+ "reason_placeholder": "Deze instantie keurt registraties handmatig goed.\nLaat de beheerder weten waarom je je wilt registreren.",
"reason": "Reden voor registratie",
- "register": "Registreren"
+ "register": "Registreren",
+ "email_language": "In welke taal wil je e-mails ontvangen van de server?"
},
"settings": {
- "attachmentRadius": "Bijlages",
- "attachments": "Bijlages",
+ "attachmentRadius": "Bijlagen",
+ "attachments": "Bijlagen",
"avatar": "Avatar",
"avatarAltRadius": "Avatars (meldingen)",
"avatarRadius": "Avatars",
@@ -169,7 +181,7 @@
"change_password": "Wachtwoord wijzigen",
"change_password_error": "Er is een fout opgetreden bij het wijzigen van je wachtwoord.",
"changed_password": "Wachtwoord succesvol gewijzigd!",
- "collapse_subject": "Klap berichten met een onderwerp in",
+ "collapse_subject": "Berichten met een onderwerp inklappen",
"composing": "Opstellen",
"confirm_new_password": "Nieuw wachtwoord bevestigen",
"current_avatar": "Je huidige avatar",
@@ -181,9 +193,9 @@
"delete_account_description": "Permanent je gegevens verwijderen en account deactiveren.",
"delete_account_error": "Er is een fout opgetreden bij het verwijderen van je account. Indien dit probleem zich voor blijft doen, neem dan contact op met de beheerder van deze instantie.",
"delete_account_instructions": "Voer je wachtwoord in het onderstaande invoerveld in om het verwijderen van je account te bevestigen.",
- "export_theme": "Voorinstelling opslaan",
+ "export_theme": "Preset opslaan",
"filtering": "Filtering",
- "filtering_explanation": "Alle statussen die deze woorden bevatten worden genegeerd, één filter per regel",
+ "filtering_explanation": "Alle berichten die deze woorden bevatten worden genegeerd, één filter per regel",
"follow_export": "Volgers exporteren",
"follow_export_button": "Exporteer je volgers naar een csv-bestand",
"follow_export_processing": "Aan het verwerken, binnen enkele ogenblikken wordt je gevraagd je bestand te downloaden",
@@ -192,13 +204,13 @@
"follows_imported": "Volgers geïmporteerd! Het kan even duren voordat deze verwerkt zijn.",
"foreground": "Voorgrond",
"general": "Algemeen",
- "hide_attachments_in_convo": "Bijlagen in conversaties verbergen",
+ "hide_attachments_in_convo": "Bijlagen in gesprekken verbergen",
"hide_attachments_in_tl": "Bijlagen in tijdlijn verbergen",
"hide_isp": "Instantie-specifiek paneel verbergen",
"preload_images": "Afbeeldingen vooraf laden",
- "hide_post_stats": "Bericht statistieken verbergen (bijv. het aantal favorieten)",
+ "hide_post_stats": "Bericht-statistieken verbergen (bijv. het aantal favorieten)",
"hide_user_stats": "Gebruikers-statistieken verbergen (bijv. het aantal volgers)",
- "import_followers_from_a_csv_file": "Gevolgden uit een csv bestand importeren",
+ "import_followers_from_a_csv_file": "Gevolgde gebruikers uit een csv bestand importeren",
"import_theme": "Preset laden",
"inputRadius": "Invoervelden",
"checkboxRadius": "Checkboxen",
@@ -216,13 +228,13 @@
"name_bio": "Naam & bio",
"new_password": "Nieuw wachtwoord",
"notification_visibility": "Type meldingen die getoond worden",
- "notification_visibility_follows": "Gevolgden",
+ "notification_visibility_follows": "Gevolgde gebruikers",
"notification_visibility_likes": "Favorieten",
"notification_visibility_mentions": "Vermeldingen",
"notification_visibility_repeats": "Herhalingen",
"no_rich_text_description": "Verwijder rich text formattering van alle berichten",
"hide_network_description": "Toon niet wie mij volgt en wie ik volg.",
- "nsfw_clickthrough": "Doorklikbaar verbergen van gevoelige bijlages en link voorbeelden inschakelen",
+ "nsfw_clickthrough": "Gevoelige media verbergen",
"oauth_tokens": "OAuth-tokens",
"token": "Token",
"refresh_token": "Token vernieuwen",
@@ -249,15 +261,15 @@
"settings": "Instellingen",
"subject_input_always_show": "Altijd onderwerpveld tonen",
"subject_line_behavior": "Onderwerp kopiëren bij beantwoorden",
- "subject_line_email": "Zoals email: \"re: onderwerp\"",
- "subject_line_mastodon": "Zoals mastodon: kopieer zoals het is",
+ "subject_line_email": "Zoals e-mail: \"re: onderwerp\"",
+ "subject_line_mastodon": "Zoals mastodon: kopiëren zoals het is",
"subject_line_noop": "Niet kopiëren",
- "stop_gifs": "GIFs afspelen bij zweven",
+ "stop_gifs": "Geanimeerde afbeeldingen afspelen bij zweven",
"streaming": "Automatisch streamen van nieuwe berichten inschakelen wanneer tot boven gescrold is",
"text": "Tekst",
"theme": "Thema",
"theme_help": "Hex kleur codes (#rrggbb) gebruiken om je kleur thema te wijzigen.",
- "theme_help_v2_1": "Je kan ook de kleur en transparantie van bepaalde componenten overschrijven door de checkbox aan te vinken, gebruik de \"Alles wissen\" knop om alle overschrijvingen te annuleren.",
+ "theme_help_v2_1": "Je kan ook de kleur en transparantie van bepaalde componenten overschrijven door de checkbox aan te vinken, gebruik de \"Alles wissen\" knop om alle overschrijvingen te herstellen.",
"theme_help_v2_2": "Iconen onder sommige onderdelen zijn achtergrond/tekst contrast indicatoren, zweef er over voor gedetailleerde info. Hou er rekening mee dat bij doorzichtigheid de ergst mogelijke situatie wordt weer gegeven.",
"tooltipRadius": "Tooltips/alarmen",
"user_settings": "Gebruikersinstellingen",
@@ -275,10 +287,10 @@
"keep_roundness": "Rondingen behouden",
"keep_fonts": "Lettertypes behouden",
"save_load_hint": "\"Behoud\" opties behouden de momenteel ingestelde opties bij het selecteren of laden van thema's, maar slaan ook de genoemde opties op bij het exporteren van een thema. Wanneer alle selectievakjes zijn uitgeschakeld, zal het exporteren van thema's alles opslaan.",
- "reset": "Reset",
+ "reset": "Herstellen",
"clear_all": "Alles wissen",
"clear_opacity": "Transparantie wissen",
- "keep_as_is": "Hou zoals het is",
+ "keep_as_is": "Houden zoals het is",
"use_snapshot": "Oude versie",
"use_source": "Nieuwe versie",
"help": {
@@ -289,7 +301,7 @@
"snapshot_source_mismatch": "Versie conflict: waarschijnlijk was FE terug gerold en opnieuw bijgewerkt, indien je het thema aangepast hebt met de oudere versie van FE wil je waarschijnlijk de oude versie gebruiken, gebruik anders de nieuwe versie.",
"migration_napshot_gone": "Voor een onduidelijke reden mist de momentopname, dus sommige dingen kunnen anders uitzien dan je gewend bent.",
"migration_snapshot_ok": "Voor de zekerheid is een momentopname van het thema geladen. Je kunt proberen om de thema gegevens te laden.",
- "fe_downgraded": "PleromaFE's versie is terug gerold.",
+ "fe_downgraded": "PleromaFE's versie is terug gezet.",
"fe_upgraded": "De thema-engine van PleromaFE is bijgewerkt na de versie update.",
"snapshot_missing": "Het bestand bevat geen thema momentopname, dus het thema kan anders uitzien dan je oorspronkelijk bedacht had.",
"snapshot_present": "Thema momentopname is geladen, alle waarden zijn overschreven. Je kunt in plaats daarvan ook de daadwerkelijke data van het thema laden."
@@ -315,7 +327,7 @@
"common_colors": {
"_tab_label": "Algemeen",
"main": "Algemene kleuren",
- "foreground_hint": "Zie \"Geavanceerd\" tab voor meer gedetailleerde controle",
+ "foreground_hint": "Zie \"Geavanceerd\" tab voor meer gedetailleerde opties",
"rgbo": "Iconen, accenten, badges"
},
"advanced_colors": {
@@ -336,9 +348,9 @@
"selectedMenu": "Geselecteerd menu item",
"selectedPost": "Geselecteerd bericht",
"pressed": "Ingedrukt",
- "highlight": "Gemarkeerde elementen",
+ "highlight": "Uitgelichte elementen",
"icons": "Iconen",
- "poll": "Poll grafiek",
+ "poll": "Peiling grafiek",
"underlay": "Onderlaag",
"popover": "Tooltips, menu's, popovers",
"post": "Berichten / Gebruiker bios",
@@ -352,7 +364,7 @@
"wallpaper": "Achtergrond"
},
"radii": {
- "_tab_label": "Rondheid"
+ "_tab_label": "Rondingen"
},
"shadows": {
"_tab_label": "Schaduw en belichting",
@@ -374,8 +386,8 @@
"panel": "Paneel",
"panelHeader": "Paneel koptekst",
"topBar": "Top balk",
- "avatar": "Gebruikers avatar (in profiel weergave)",
- "avatarStatus": "Gebruikers avatar (in bericht weergave)",
+ "avatar": "Gebruikers-avatar (in profiel weergave)",
+ "avatarStatus": "Gebruikers-avatar (in bericht weergave)",
"popup": "Popups en tooltips",
"button": "Knop",
"buttonHover": "Knop (zweven)",
@@ -386,7 +398,7 @@
"hintV3": "Voor schaduwen kun je ook de {0} notatie gebruiken om de andere kleur invoer te gebruiken."
},
"fonts": {
- "_tab_label": "Lettertypes",
+ "_tab_label": "Lettertypen",
"help": "Selecteer het lettertype om te gebruiken voor elementen van de UI. Voor \"aangepast\" dien je de exacte naam van het lettertype in te voeren zoals die in het systeem wordt weergegeven.",
"components": {
"interface": "Interface",
@@ -426,10 +438,10 @@
"wait_pre_setup_otp": "OTP voorinstellen",
"confirm_and_enable": "Bevestig en schakel OTP in",
"title": "Twee-factorauthenticatie",
- "generate_new_recovery_codes": "Genereer nieuwe herstelcodes",
+ "generate_new_recovery_codes": "Nieuwe herstelcodes genereren",
"recovery_codes": "Herstelcodes.",
"waiting_a_recovery_codes": "Back-upcodes ontvangen…",
- "authentication_methods": "Authenticatiemethodes",
+ "authentication_methods": "Authenticatiemethoden",
"scan": {
"title": "Scannen",
"desc": "Scan de QR-code of voer een sleutel in met je twee-factorapplicatie:",
@@ -441,39 +453,39 @@
"warning_of_generate_new_codes": "Wanneer je nieuwe herstelcodes genereert, zullen je oude codes niet langer werken.",
"recovery_codes_warning": "Schrijf de codes op of sla ze op een veilige locatie op - anders kun je ze niet meer inzien. Als je toegang tot je 2FA-app en herstelcodes verliest, zal je buitengesloten zijn van je account."
},
- "allow_following_move": "Automatisch volgen toestaan wanneer een gevolgd account migreert",
- "block_export": "Blokkades exporteren",
- "block_import": "Blokkades importeren",
- "blocks_imported": "Blokkades geïmporteerd! Het kan even duren voordat deze verwerkt zijn.",
- "blocks_tab": "Blokkades",
+ "allow_following_move": "Automatisch volgen toestaan wanneer een gevolgd account verhuist",
+ "block_export": "Geblokkeerde gebruikers exporteren",
+ "block_import": "Geblokkeerde gebruikers importeren",
+ "blocks_imported": "Geblokkeerde gebruikers geïmporteerd! Het kan even duren voordat deze verwerkt zijn.",
+ "blocks_tab": "Geblokkeerde gebruikers",
"change_email": "E-mail wijzigen",
"change_email_error": "Er is een fout opgetreden tijdens het wijzigen van je e-mailadres.",
"changed_email": "E-mailadres succesvol gewijzigd!",
"domain_mutes": "Domeinen",
"avatar_size_instruction": "De aangeraden minimale afmeting voor avatar-afbeeldingen is 150x150 pixels.",
- "pad_emoji": "Vul emoji aan met spaties wanneer deze met de picker ingevoegd worden",
+ "pad_emoji": "Emoji aan met spaties aanvullen wanneer deze met de picker ingevoegd worden",
"emoji_reactions_on_timeline": "Toon emoji-reacties op de tijdlijn",
"accent": "Accent",
"hide_muted_posts": "Berichten van genegeerde gebruikers verbergen",
"max_thumbnails": "Maximaal aantal miniaturen per bericht",
"use_one_click_nsfw": "Gevoelige bijlagen met slechts één klik openen",
- "hide_filtered_statuses": "Gefilterde statussen verbergen",
- "import_blocks_from_a_csv_file": "Blokkades van een csv bestand importeren",
- "mutes_tab": "Genegeerden",
- "play_videos_in_modal": "Video's in een popup frame afspelen",
+ "hide_filtered_statuses": "Gefilterde berichten verbergen",
+ "import_blocks_from_a_csv_file": "Geblokkeerde gebruikers van een csv bestand importeren",
+ "mutes_tab": "Genegeerde gebruikers",
+ "play_videos_in_modal": "Video's in een popup venster afspelen",
"new_email": "Nieuwe e-mail",
"notification_visibility_emoji_reactions": "Reacties",
- "no_blocks": "Geen blokkades",
- "no_mutes": "Geen genegeerden",
+ "no_blocks": "Geen geblokkeerde gebruikers",
+ "no_mutes": "Geen genegeerde gebruikers",
"hide_followers_description": "Niet tonen wie mij volgt",
"hide_followers_count_description": "Niet mijn volgers aantal tonen",
- "hide_follows_count_description": "Niet mijn gevolgde aantal tonen",
+ "hide_follows_count_description": "Niet mijn gevolgden aantal tonen",
"show_admin_badge": "\"Beheerder\" badge in mijn profiel tonen",
- "autohide_floating_post_button": "Nieuw Bericht knop automatisch verbergen (mobiel)",
+ "autohide_floating_post_button": "\"Bericht opstellen\"-knop automatisch verbergen (mobiel)",
"search_user_to_block": "Zoek wie je wilt blokkeren",
"search_user_to_mute": "Zoek wie je wilt negeren",
"minimal_scopes_mode": "Bericht bereik-opties minimaliseren",
- "post_status_content_type": "Bericht status content type",
+ "post_status_content_type": "Standaard bericht content type",
"user_mutes": "Gebruikers",
"useStreamingApi": "Berichten en meldingen in real-time ontvangen",
"useStreamingApiWarning": "(Afgeraden, experimenteel, kan berichten overslaan)",
@@ -482,7 +494,7 @@
"fun": "Plezier",
"greentext": "Meme pijlen",
"block_export_button": "Exporteer je geblokkeerde gebruikers naar een csv-bestand",
- "block_import_error": "Fout bij importeren blokkades",
+ "block_import_error": "Fout bij importeren geblokkeerde gebruikers",
"discoverable": "Sta toe dat dit account ontdekt kan worden in zoekresultaten en andere diensten",
"use_contain_fit": "Bijlage in miniaturen niet bijsnijden",
"notification_visibility_moves": "Gebruiker Migraties",
@@ -495,7 +507,7 @@
"backend_version": "Backend versie",
"title": "Versie"
},
- "mutes_and_blocks": "Negeringen en Blokkades",
+ "mutes_and_blocks": "Negeren en Blokkeren",
"profile_fields": {
"value": "Inhoud",
"name": "Label",
@@ -508,15 +520,15 @@
"hide_media_previews": "Media voorbeelden verbergen",
"word_filter": "Woord filter",
"chatMessageRadius": "Chatbericht",
- "mute_export": "Genegeerden export",
- "mute_export_button": "Exporteer je genegeerden naar een csv-bestand",
- "mute_import_error": "Fout tijdens het importeren van genegeerden",
- "mute_import": "Genegeerden import",
- "mutes_imported": "Genegeerden geïmporteerd! Het kan even duren voordat deze verwerkt zijn.",
+ "mute_export": "Genegeerde gebruikers export",
+ "mute_export_button": "Genegeerde gebruikers naar een csv-bestand exporteren",
+ "mute_import_error": "Fout tijdens het importeren van genegeerde gebruikers",
+ "mute_import": "Genegeerde gebruikers import",
+ "mutes_imported": "Genegeerde gebruikers geïmporteerd! Het kan even duren voordat deze verwerkt zijn.",
"more_settings": "Meer instellingen",
- "notification_setting_hide_notification_contents": "Afzender en inhoud van push meldingen verbergen",
+ "notification_setting_hide_notification_contents": "Afzender en inhoud van push-meldingen verbergen",
"notification_setting_block_from_strangers": "Meldingen van gebruikers die je niet volgt blokkeren",
- "virtual_scrolling": "Tijdlijn rendering optimaliseren",
+ "virtual_scrolling": "Tijdlijn weergave optimaliseren",
"sensitive_by_default": "Berichten standaard als gevoelig markeren",
"reset_avatar_confirm": "Wil je echt de avatar herstellen?",
"reset_banner_confirm": "Wil je echt de banner herstellen?",
@@ -528,7 +540,7 @@
"reply_visibility_following_short": "Antwoorden naar mijn gevolgden tonen",
"file_export_import": {
"errors": {
- "file_slightly_new": "Bestand minor versie is verschillend, sommige instellingen kunnen mogelijk niet worden geladen",
+ "file_slightly_new": "Minor versie van bestand is verschillend, sommige instellingen kunnen mogelijk niet worden geladen",
"file_too_old": "Incompatibele hoofdversie: {fileMajor}, bestandsversie is te oud en wordt niet ondersteund (minimale versie {feMajor})",
"file_too_new": "Incompatibele hoofdversie: {fileMajor}, deze PleromaFE (instellingen versie {feMajor}) is te oud om deze te ondersteunen",
"invalid_file": "Het geselecteerde bestand is niet een door Pleroma ondersteunde instellingen back-up. Er zijn geen wijzigingen gemaakt."
@@ -536,27 +548,95 @@
"restore_settings": "Instellingen uit bestand herstellen",
"backup_settings_theme": "Instellingen en thema naar bestand back-uppen",
"backup_settings": "Instellingen naar bestand back-uppen",
- "backup_restore": "Instellingen backup"
+ "backup_restore": "Instellingen back-up"
},
- "hide_wallpaper": "Instantie achtergrond verbergen",
+ "hide_wallpaper": "Achtergrond-afbeelding verbergen",
"hide_all_muted_posts": "Genegeerde berichten verbergen",
- "import_mutes_from_a_csv_file": "Importeer genegeerden van een csv bestand"
+ "import_mutes_from_a_csv_file": "Genegeerde gebruikers uit een csv bestand importeren",
+ "added_alias": "Alias is toegevoegd.",
+ "add_alias_error": "Fout bij het toevoegen van alias: {error}",
+ "move_account": "Account verhuizen",
+ "move_account_notes": "Indien je het account ergens anders heen wilt verplaatsen, dien je eerst een alias naar dit account te maken in het nieuwe account.",
+ "move_account_target": "Doelwit account (b.v. {example})",
+ "moved_account": "Het account is verhuisd.",
+ "move_account_error": "Fout tijdens account verhuizen: {error}",
+ "wordfilter": "Woordfilter",
+ "third_column_mode": "Indien er genoeg plaats is, derde kolom tonen met",
+ "third_column_mode_none": "Géén derde kolom tonen",
+ "third_column_mode_notifications": "Meldingen",
+ "third_column_mode_postform": "Berichtformulier en navigatie",
+ "tree_advanced": "Flexibelere navigatie toestaan in boom weergave",
+ "tree_fade_ancestors": "Ouders van huidige bericht met gedempte tekst tonen",
+ "conversation_display_linear": "Lineaire weergave",
+ "mention_link_display_full_for_remote": "als volledige namen alleen voor externe gebruikers (b.v. {'@'}foo{'@'}example.org)",
+ "mention_link_display_full": "altijd als volledige namen (b.v. {'@'}foo{'@'}example.org)",
+ "mention_link_show_avatar": "Profielfoto naast link tonen",
+ "mention_link_fade_domain": "Domeinen vervagen (b.v. {'@'}example.org in {'@'}foo{'@'}example.org)",
+ "mention_link_bolden_you": "Vermeldingen naar jezelf uitlichten",
+ "expert_mode": "Geavanceerde opties tonen",
+ "setting_server_side": "Deze instelling is gebonden aan je profiel en beïnvloed alle sessies en clients",
+ "post_look_feel": "Berichten Look & Feel",
+ "mention_links": "Vermelding-links",
+ "email_language": "Taal voor e-mails van de server",
+ "account_backup": "Account back-up",
+ "account_backup_description": "Hiermee kun je een archief van je account gegevens en berichten downloaden, maar deze kunnen nog niet geïmporteerd worden in een Pleroma account.",
+ "account_backup_table_head": "Back-up",
+ "download_backup": "Downloaden",
+ "backup_not_ready": "Deze back-up is nog niet gereed.",
+ "remove_backup": "Verwijderen",
+ "list_backups_error": "Fout bij het ophalen van back-ups: {error}",
+ "add_backup": "Nieuwe back-up aanmaken",
+ "added_backup": "Nieuwe back-up is toegevoegd.",
+ "add_backup_error": "Fout bij het maken van back-up: {error}",
+ "account_alias": "Account aliassen",
+ "account_alias_table_head": "Alias",
+ "list_aliases_error": "Fout bij het ophalen van aliassen: {error}",
+ "hide_list_aliases_error_action": "Sluiten",
+ "remove_alias": "Deze alias verwijderen",
+ "new_alias_target": "Nieuwe alias toevoegen (b.v. {example})",
+ "mute_bot_posts": "Bot-berichten negeren",
+ "hide_bot_indication": "Bot-indicatie in berichten verbergen",
+ "hide_shoutbox": "Shoutbox verbergen",
+ "right_sidebar": "Kolom-volgorde omdraaien",
+ "always_show_post_button": "Altijd de zwevende \"Bericht opstellen\"-knop tonen",
+ "hide_wordfiltered_statuses": "Berichten met gefilterde woorden verbergen",
+ "hide_muted_threads": "Genegeerde gesprekken verbergen",
+ "account_privacy": "Privacy",
+ "posts": "Berichten",
+ "user_profiles": "Gebruikersprofielen",
+ "notification_visibility_polls": "Einde van peilingen waar je in gestemd hebt",
+ "hide_favorites_description": "Lijst van favorieten verbergen (mensen krijgen wel nog meldingen)",
+ "conversation_display": "Gespreksweergave stijl",
+ "conversation_display_tree": "Boom weergave",
+ "disable_sticky_headers": "Kolomkopteksten niet bovenaan het scherm plakken",
+ "show_scrollbars": "Scrollbalk tonen in zijkolommen",
+ "conversation_other_replies_button": "\"Andere antwoorden\"-knop tonen",
+ "conversation_other_replies_button_below": "Onder berichten",
+ "conversation_other_replies_button_inside": "Binnen in berichten",
+ "max_depth_in_thread": "Maximum lagen van een gesprek welke standaard getoond dienen te worden",
+ "use_at_icon": "{'@'} symbool als icoon tonen in plaats van tekst",
+ "mention_link_display": "Vermelding-links tonen",
+ "mention_link_display_short": "altijd als korte namen (b.v. {'@'}foo)",
+ "mention_link_use_tooltip": "Volledige namen in tooltip tonen voor externe gebruikers",
+ "show_yous": "(Jij)'s tonen",
+ "user_popover_avatar_zoom": "Gebruikers-avatar inzoomen wanneer hier op geklikt wordt in een popover in plaats van de popover te sluiten",
+ "user_popover_avatar_overlay": "Gebruikers-popover tonen over gebruikers-avatar"
},
"timeline": {
- "collapse": "Inklappen",
- "conversation": "Conversatie",
+ "collapse": "Invouwen",
+ "conversation": "Gesprek",
"error_fetching": "Fout bij ophalen van updates",
- "load_older": "Oudere statussen laden",
- "no_retweet_hint": "Bericht is gemarkeerd als enkel volgers of direct en kan niet worden herhaald",
+ "load_older": "Oudere berichten laden",
+ "no_retweet_hint": "Bericht is gemarkeerd als enkel-volgers of privé en kan niet worden herhaald of geciteerd",
"repeated": "herhaalde",
"show_new": "Nieuwe tonen",
"up_to_date": "Up-to-date",
- "no_statuses": "Geen statussen",
- "no_more_statuses": "Geen statussen meer",
+ "no_statuses": "Geen berichten",
+ "no_more_statuses": "Geen verdere berichten",
"socket_broke": "Realtime verbinding verloren: CloseEvent code {0}",
"socket_reconnected": "Realtime verbinding opgezet",
"reload": "Verversen",
- "error": "Fout tijdens het ophalen van tijdlijn: {0}"
+ "error": "Fout bij het ophalen van tijdlijn: {0}"
},
"user_card": {
"approve": "Goedkeuren",
@@ -565,27 +645,27 @@
"deny": "Weigeren",
"favorites": "Favorieten",
"follow": "Volgen",
- "follow_cancel": "Aanvraag annuleren",
- "follow_sent": "Aanvraag verzonden!",
+ "follow_cancel": "Verzoek annuleren",
+ "follow_sent": "Verzoek verzonden!",
"follow_progress": "Aanvragen…",
- "follow_unfollow": "Stop volgen",
- "followees": "Aan het volgen",
+ "follow_unfollow": "Ontvolgen",
+ "followees": "Volgen",
"followers": "Volgers",
- "following": "Aan het volgen!",
+ "following": "Gevolgd!",
"follows_you": "Volgt jou!",
"its_you": "'t is jij!",
"mute": "Negeren",
"muted": "Genegeerd",
"per_day": "per dag",
- "remote_follow": "Volg vanop afstand",
- "statuses": "Statussen",
+ "remote_follow": "Van afstand volgen",
+ "statuses": "Berichten",
"admin_menu": {
"delete_user": "Gebruiker verwijderen",
- "quarantine": "Federeren van gebruikers berichten verbieden",
+ "quarantine": "Federeren van berichten verbieden",
"disable_any_subscription": "Volgen van gebruiker in zijn geheel verbieden",
"disable_remote_subscription": "Volgen van gebruiker vanaf andere instanties verbieden",
"sandbox": "Berichten forceren om alleen voor volgers zichtbaar te zijn",
- "force_unlisted": "Berichten forceren om niet publiekelijk getoond te worden",
+ "force_unlisted": "Berichten forceren om niet openbaar getoond te worden",
"strip_media": "Media van berichten verwijderen",
"force_nsfw": "Alle berichten als gevoelig markeren",
"delete_account": "Account verwijderen",
@@ -595,30 +675,33 @@
"grant_moderator": "Moderatorsrechten toekennen",
"revoke_admin": "Beheerdersrechten intrekken",
"grant_admin": "Beheerdersrechten toekennen",
- "moderation": "Moderatie"
+ "moderation": "Moderatie",
+ "delete_user_data_and_deactivate_confirmation": "Dit zal permanent alle data van dit account verwijderen en het account deactiveren. Weet je het zeker?"
},
"show_repeats": "Herhalingen tonen",
"hide_repeats": "Herhalingen verbergen",
"mute_progress": "Negeren…",
- "unmute_progress": "Negering opheffen…",
- "unmute": "Negering opheffen",
+ "unmute_progress": "Negeren opheffen…",
+ "unmute": "Negeren opheffen",
"block_progress": "Blokkeren…",
- "unblock_progress": "Blokkade opheffen…",
- "unblock": "Blokkade opheffen",
+ "unblock_progress": "Blokkeren opheffen…",
+ "unblock": "Blokkeren opheffen",
"unsubscribe": "Abonnement opzeggen",
"subscribe": "Abonneren",
- "report": "Aangeven",
- "mention": "Vermelding",
+ "report": "Rapporteren",
+ "mention": "Vermelden",
"media": "Media",
"hidden": "Verborgen",
"highlight": {
"side": "Zijstreep",
"striped": "Gestreepte achtergrond",
"solid": "Effen achtergrond",
- "disabled": "Geen highlight"
+ "disabled": "Geen uitlichting"
},
"bot": "Bot",
- "message": "Bericht"
+ "message": "Bericht",
+ "edit_profile": "Profiel wijzigen",
+ "deactivated": "Gedeactiveerd"
},
"user_profile": {
"timeline_title": "Gebruikerstijdlijn",
@@ -634,11 +717,11 @@
"repeat": "Herhalen",
"reply": "Beantwoorden",
"favorite": "Favoriet maken",
- "user_settings": "Gebruikers Instellingen",
- "reject_follow_request": "Volg-verzoek afwijzen",
- "accept_follow_request": "Volg-aanvraag accepteren",
+ "user_settings": "Gebruikersinstellingen",
+ "reject_follow_request": "Volgverzoek afwijzen",
+ "accept_follow_request": "Volgverzoek accepteren",
"add_reaction": "Reactie toevoegen",
- "bookmark": "Bladwijzer"
+ "bookmark": "Bladwijzer maken"
},
"upload": {
"error": {
@@ -663,27 +746,27 @@
"replace": "Vervangen",
"is_replaced_by": "→",
"keyword_policies": "Zoekwoordbeleid",
- "ftl_removal": "Verwijdering van \"Het Geheel Bekende Netwerk\" Tijdlijn"
+ "ftl_removal": "Verwijderen van \"Het Globale Netwerk\" Tijdlijn"
},
"mrf_policies_desc": "MRF-regels beïnvloeden het federatiegedrag van de instantie. De volgende regels zijn ingeschakeld:",
"mrf_policies": "Ingeschakelde MRF-regels",
"simple": {
- "simple_policies": "Instantiespecifieke regels",
+ "simple_policies": "Instantie-specifieke regels",
"instance": "Instantie",
"reason": "Reden",
"not_applicable": "n.v.t.",
"accept": "Accepteren",
"accept_desc": "Deze instantie accepteert alleen berichten van de volgende instanties:",
"reject": "Afwijzen",
- "reject_desc": "Deze instantie zal geen berichten accepteren van de volgende instanties:",
+ "reject_desc": "Deze instantie zal géén berichten accepteren van de volgende instanties:",
"quarantine": "Quarantaine",
- "quarantine_desc": "Deze instantie zal alleen openbare berichten sturen naar de volgende instanties:",
- "ftl_removal_desc": "Deze instantie verwijdert de volgende instanties van \"Bekende Netwerk\" tijdlijn:",
+ "quarantine_desc": "Deze instantie zal géén berichten sturen naar de volgende instanties:",
+ "ftl_removal_desc": "Deze instantie verwijdert de volgende instanties van \"Globale Netwerk\" tijdlijn:",
"media_removal_desc": "Deze instantie verwijdert media van berichten van de volgende instanties:",
- "media_nsfw_desc": "Deze instantie stelt media in als gevoelig in berichten van de volgende instanties:",
- "ftl_removal": "Verwijderen van \"Bekende Netwerk\" Tijdlijn",
- "media_removal": "Mediaverwijdering",
- "media_nsfw": "Forceer media als gevoelig"
+ "media_nsfw_desc": "Deze instantie markeert media als gevoelig in berichten van de volgende instanties:",
+ "ftl_removal": "Verwijderen van \"Globale Netwerk\" Tijdlijn",
+ "media_removal": "Verwijderen van media",
+ "media_nsfw": "Media als gevoelig markeren"
}
},
"staff": "Personeel"
@@ -691,8 +774,8 @@
"domain_mute_card": {
"mute": "Negeren",
"mute_progress": "Negeren…",
- "unmute": "Negering opheffen",
- "unmute_progress": "Negering wordt opgeheven…"
+ "unmute": "Negeren opheffen",
+ "unmute_progress": "Negeren wordt opgeheven…"
},
"exporter": {
"export": "Exporteren",
@@ -711,21 +794,23 @@
},
"media_modal": {
"previous": "Vorige",
- "next": "Volgende"
+ "next": "Volgende",
+ "counter": "{current} / {total}",
+ "hide": "Media venster sluiten"
},
"polls": {
- "add_poll": "Poll toevoegen",
+ "add_poll": "Peiling toevoegen",
"add_option": "Optie toevoegen",
"option": "Optie",
"votes": "stemmen",
- "vote": "Stem",
+ "vote": "Stemmen",
"single_choice": "Enkele keuze",
"multiple_choices": "Meerkeuze",
- "expiry": "Poll leeftijd",
- "expires_in": "Poll eindigt in {0}",
- "expired": "Poll is {0} geleden beëindigd",
- "not_enough_options": "Te weinig opties in poll",
- "type": "Poll-type",
+ "expiry": "Peiling tijdsduur",
+ "expires_in": "Peiling eindigt in {0}",
+ "expired": "Peiling is {0} geleden beëindigd",
+ "not_enough_options": "Te weinig opties in peiling",
+ "type": "Peiling-type",
"votes_count": "{count} stem | {count} stemmen",
"people_voted_count": "{count} persoon heeft gestemd | {count} personen hebben gestemd"
},
@@ -742,28 +827,41 @@
},
"interactions": {
"favs_repeats": "Herhalingen en favorieten",
- "follows": "Nieuwe gevolgden",
+ "follows": "Nieuwe volgs",
"moves": "Gebruikermigraties",
+ "emoji_reactions": "Emoji Reacties",
+ "reports": "Rapportages",
"load_older": "Oudere interacties laden"
},
"remote_user_resolver": {
"searching_for": "Zoeken naar",
"error": "Niet gevonden.",
- "remote_user_resolver": "Externe gebruikers-zoeker"
- },
+ "remote_user_resolver": "Externe gebruiker zoeker"
+ },
+ "report": {
+ "reporter": "Reporteerder:",
+ "reported_user": "Gerapporteerde gebruiker:",
+ "reported_statuses": "Gerapporteerde statussen:",
+ "notes": "Notas:",
+ "state": "Status:",
+ "state_open": "Open",
+ "state_closed": "Gesloten",
+ "state_resolved": "Opgelost"
+ },
+
"selectable_list": {
"select_all": "Alles selecteren"
},
"password_reset": {
- "password_reset_required_but_mailer_is_disabled": "Je dient je wachtwoord opnieuw in te stellen, maar wachtwoord reset is uitgeschakeld. Neem contact op met de beheerder van deze instantie.",
+ "password_reset_required_but_mailer_is_disabled": "Je dient je wachtwoord opnieuw in te stellen, maar wachtwoordherstel is uitgeschakeld. Neem contact op met de beheerder van deze instantie.",
"password_reset_required": "Je dient je wachtwoord opnieuw in te stellen om in te kunnen loggen.",
- "password_reset_disabled": "Wachtwoord reset is uitgeschakeld. Neem contact op met de beheerder van deze instantie.",
+ "password_reset_disabled": "Wachtwoordherstel is uitgeschakeld. Neem contact op met de beheerder van deze instantie.",
"too_many_requests": "Je hebt het maximaal aantal pogingen bereikt, probeer het later opnieuw.",
"return_home": "Terugkeren naar de home pagina",
"check_email": "Controleer je email inbox voor een link om je wachtwoord opnieuw in te stellen.",
"placeholder": "Je email of gebruikersnaam",
"instruction": "Voer je email adres of gebruikersnaam in. We sturen je een link om je wachtwoord opnieuw in te stellen.",
- "password_reset": "Wachtwoord opnieuw instellen",
+ "password_reset": "Wachtwoord herstellen",
"forgot_password": "Wachtwoord vergeten?"
},
"search": {
@@ -779,26 +877,26 @@
"forward_to": "Doorsturen naar {0}",
"forward_description": "Dit account hoort bij een andere server. Wil je een kopie van het rapport ook daarheen sturen?",
"additional_comments": "Aanvullende opmerkingen",
- "add_comment_description": "Het rapport zal naar de moderators van de instantie worden verstuurd. Je kunt hieronder uitleg bijvoegen waarom je dit account wilt aangeven:",
- "title": "{0} aangeven"
+ "add_comment_description": "Het rapport zal naar de moderators van de instantie worden verstuurd. Je kunt hieronder uitleg bijvoegen waarom je dit account wilt rapporteren:",
+ "title": "{0} rapporteren"
},
"status": {
- "copy_link": "Link naar status kopiëren",
- "status_unavailable": "Status niet beschikbaar",
- "unmute_conversation": "Conversatie niet meer negeren",
- "mute_conversation": "Conversatie negeren",
+ "copy_link": "Link naar bericht kopiëren",
+ "status_unavailable": "Bericht niet beschikbaar",
+ "unmute_conversation": "Gesprek niet meer negeren",
+ "mute_conversation": "Gesprek negeren",
"replies_list": "Antwoorden:",
"reply_to": "Antwoorden aan",
- "delete_confirm": "Wil je echt deze status verwijderen?",
+ "delete_confirm": "Wil je echt dit bericht verwijderen?",
"pin": "Aan profiel vastmaken",
"pinned": "Vastgezet",
"unpin": "Van profiel losmaken",
- "delete": "Status verwijderen",
+ "delete": "Bericht verwijderen",
"repeats": "Herhalingen",
"favorites": "Favorieten",
"thread_muted_and_words": ", heeft woorden:",
- "thread_muted": "Thread genegeerd",
- "expand": "Uitklappen",
+ "thread_muted": "Gesprek genegeerd",
+ "expand": "Uitvouwen",
"nsfw": "Gevoelig",
"status_deleted": "Dit bericht is verwijderd",
"hide_content": "Inhoud verbergen",
@@ -807,7 +905,33 @@
"show_full_subject": "Volledig onderwerp tonen",
"external_source": "Externe bron",
"unbookmark": "Bladwijzer verwijderen",
- "bookmark": "Bladwijzer toevoegen"
+ "bookmark": "Bladwijzer toevoegen",
+ "show_attachment_description": "Voorbeeld beschrijving (open bijlage om de volledige beschrijving te zien)",
+ "remove_attachment": "Bijlage verwijderen",
+ "attachment_stop_flash": "Flash speler stoppen",
+ "move_up": "Bijlage naar links schuiven",
+ "move_down": "Bijlage naar rechts schuiven",
+ "open_gallery": "Gallerij openen",
+ "thread_hide": "Gesprek verbergen",
+ "thread_show": "Gesprek tonen",
+ "show_all_conversation": "Volledig gesprek tonen ({numStatus} ander bericht) | Volledig gesprek tonen ({numStatus} andere berichten)",
+ "show_only_conversation_under_this": "Alleen antwoorden op dit bericht tonen",
+ "mentions": "Vermeldingen",
+ "replies_list_with_others": "Antwoorden (+{numReplies} andere): | Antwoorden (+{numReplies} anderen):",
+ "you": "(Jij)",
+ "plus_more": "+{number} meer",
+ "many_attachments": "Bericht heeft {number} bijlage | Bericht heeft {number} bijlagen",
+ "collapse_attachments": "Bijlagen invouwen",
+ "show_all_attachments": "Alle bijlagen tonen",
+ "show_attachment_in_modal": "In media venster tonen",
+ "hide_attachment": "Bijlage verbergen",
+ "thread_show_full": "Alle berichten in dit gesprek tonen ({numStatus} bericht in totaal, max. diepte {depth}) | Alle berichten in dit gesprek tonen ({numStatus} berichten in totaal, max. diepte {depth})",
+ "thread_show_full_with_icon": "{icon} {text}",
+ "thread_follow": "Rest van gesprek tonen ({numStatus} bericht in totaal) | Rest van gesprek tonen ({numStatus} berichten in totaal)",
+ "thread_follow_with_icon": "{icon} {text}",
+ "ancestor_follow": "{numReplies} ander antwoord onder dit bericht tonen | {numReplies} andere antwoorden onder dit bericht tonen",
+ "ancestor_follow_with_icon": "{icon} {text}",
+ "show_all_conversation_with_icon": "{icon} {text}"
},
"time": {
"years_short": "{0}j",
@@ -841,13 +965,29 @@
"days_short": "{0}d",
"day_short": "{0}d",
"days": "{0} dagen",
- "day": "{0} dag"
+ "day": "{0} dag",
+ "unit": {
+ "months": "{0} maand | {0} maanden",
+ "months_short": "{0}ma",
+ "seconds": "{0} seconde | {0} seconden",
+ "seconds_short": "{0}s",
+ "weeks": "{0} week | {0} weken",
+ "weeks_short": "{0}w",
+ "years": "{0} jaar | {0} jaren",
+ "years_short": "{0}j",
+ "days": "{0} dag | {0} dagen",
+ "days_short": "{0}d",
+ "hours": "{0} uur | {0} uren",
+ "hours_short": "{0}u",
+ "minutes": "{0} minuut | {0} minuten",
+ "minutes_short": "{0}min"
+ }
},
"shoutbox": {
"title": "Shoutbox"
},
"errors": {
- "storage_unavailable": "Pleroma kon browseropslag niet benaderen. Je login of lokale instellingen worden niet opgeslagen en je kunt onverwachte problemen ondervinden. Probeer cookies te accepteren."
+ "storage_unavailable": "Pleroma kan de browseropslag niet benaderen. Je login of lokale instellingen worden niet opgeslagen en je kunt onverwachte problemen ondervinden. Probeer cookies te accepteren."
},
"display_date": {
"today": "Vandaag"
diff --git a/src/i18n/pdc.json b/src/i18n/pdc.json
@@ -0,0 +1 @@
+{}
diff --git a/src/i18n/pl.json b/src/i18n/pl.json
@@ -24,7 +24,10 @@
"media_removal": "Usuwanie multimediów",
"media_removal_desc": "Ta instancja usuwa multimedia z postów od wymienionych instancji:",
"media_nsfw": "Multimedia ustawione jako wrażliwe",
- "media_nsfw_desc": "Ta instancja wymusza, by multimedia z wymienionych instancji były ustawione jako wrażliwe:"
+ "media_nsfw_desc": "Ta instancja wymusza, by multimedia z wymienionych instancji były ustawione jako wrażliwe:",
+ "instance": "Instancja",
+ "reason": "Powód",
+ "not_applicable": "Nie dotyczy"
}
},
"staff": "Administracja"
@@ -861,5 +864,13 @@
},
"errors": {
"storage_unavailable": "Pleroma nie mogła uzyskać dostępu do pamięci masowej przeglądarki. Twój login lub lokalne ustawienia nie zostaną zapisane i możesz napotkać problemy. Spróbuj włączyć ciasteczka."
+ },
+ "announcements": {
+ "page_header": "Ogłoszenia",
+ "title": "Ogłoszenie",
+ "mark_as_read_action": "Oznacz jako przeczytane",
+ "post_placeholder": "Wprowadź treść ogłoszenia…",
+ "close_error": "Zamknij",
+ "delete_action": "Usuń"
}
}
diff --git a/src/i18n/pt.json b/src/i18n/pt.json
@@ -11,7 +11,8 @@
"title": "Características",
"who_to_follow": "Quem seguir",
"upload_limit": "Limite de carregamento",
- "pleroma_chat_messages": "Chat do Pleroma"
+ "pleroma_chat_messages": "Chat do Pleroma",
+ "shout": "Shoutbox"
},
"finder": {
"error_fetching_user": "Erro ao pesquisar utilizador",
@@ -36,11 +37,27 @@
"error_retry": "Por favor, tenta novamente",
"loading": "A carregar…",
"dismiss": "Ignorar",
- "role":
- {
+ "role": {
"moderator": "Moderador",
"admin": "Admin"
- }
+ },
+ "undo": "Refazer",
+ "yes": "Sim",
+ "no": "Não",
+ "unpin": "Desafixar o item",
+ "scroll_to_top": "Rolar para o topo",
+ "flash_content": "Clique para mostrar conteúdo Flash usando o Ruffle (Experimental, talvez não funcione).",
+ "flash_security": "Note que isso pode ser potencialmente perigoso dado que o conteúdo Flash ainda é código arbitrário.",
+ "flash_fail": "Falha ao carregar conteúdo flash, veja o console para detalhes.",
+ "scope_in_timeline": {
+ "direct": "Direct",
+ "private": "Apenas-seguidores",
+ "public": "Público",
+ "unlisted": "Não-listado"
+ },
+ "pin": "Fixar o item",
+ "generic_error_message": "Um erro ocorreu: {0}",
+ "never_show_again": "Não mostrar mais"
},
"image_cropper": {
"crop_picture": "Cortar imagem",
@@ -64,11 +81,17 @@
"recovery_code": "Código de recuperação",
"authentication_code": "Código de autenticação",
"enter_two_factor_code": "Introduza o código de dois fatores",
- "enter_recovery_code": "Introduza um código de recuperação"
+ "enter_recovery_code": "Introduza um código de recuperação",
+ "logout_confirm_title": "Confirmação de logoff",
+ "logout_confirm": "Você realmente quer sair?",
+ "logout_confirm_accept_button": "Sair",
+ "logout_confirm_cancel_button": "Não sair"
},
"media_modal": {
"previous": "Anterior",
- "next": "Próximo"
+ "next": "Próximo",
+ "counter": "{current} / {total}",
+ "hide": "Fechar visualizador de mídia"
},
"nav": {
"about": "Sobre",
@@ -88,7 +111,18 @@
"administration": "Administração",
"chats": "Salas de Chat",
"timelines": "Cronologias",
- "bookmarks": "Itens Guardados"
+ "bookmarks": "Itens Guardados",
+ "home_timeline": "Timeline da home",
+ "lists": "Listas",
+ "edit_pinned": "Editar itens fixados",
+ "edit_nav_mobile": "Customizar barra de navegação",
+ "mobile_notifications_mark_as_seen": "Marcar todas como vistas",
+ "search_close": "Fechar barra de busca",
+ "mobile_notifications_close": "Fechar notificações",
+ "announcements": "Anúncios",
+ "edit_finish": "Edição finalizada",
+ "mobile_sidebar": "Alternar barra lateral móvel",
+ "mobile_notifications": "Abrir notificações (há notificações não lidas)"
},
"notifications": {
"broken_favorite": "Publicação desconhecida, a procurar…",
@@ -102,7 +136,15 @@
"reacted_with": "reagiu com {0}",
"migrated_to": "migrou para",
"follow_request": "quer seguir-te",
- "error": "Erro ao obter notificações: {0}"
+ "error": "Erro ao obter notificações: {0}",
+ "unread_announcements": "{num} anúncio não lido | {num} anúncios não lidos",
+ "unread_chats": "{num} mensagem não lida | {num} mensagens não lidas",
+ "configuration_tip": "Você pode customizar o que você deseja mostrar aqui em {theSettings}. {dismiss}",
+ "unread_follow_requests": "{num} novo pedido de seguidor | {num} novos pedidos de seguidores",
+ "configuration_tip_settings": "as configurações",
+ "configuration_tip_dismiss": "Não mostrar novamente",
+ "poll_ended": "enquete finalizada",
+ "submitted_report": "enviado um relatório"
},
"post_status": {
"new_status": "Publicar nova publicação",
@@ -136,7 +178,14 @@
"media_description": "Descrição da multimédia",
"media_description_error": "Falha ao atualizar ficheiro, tente novamente",
"direct_warning_to_first_only": "Esta publicação só será visível para os utilizadores mencionados no início da mensagem.",
- "direct_warning_to_all": "Esta publicação será visível para todos os utilizadores mencionados."
+ "direct_warning_to_all": "Esta publicação será visível para todos os utilizadores mencionados.",
+ "edit_status": "Editar status",
+ "reply_option": "Responder a esse status",
+ "quote_option": "Citar esse status",
+ "edit_remote_warning": "Outras instâncias remotas talvez não suportem edição e sejam incapazes de receber a última versão do seu post.",
+ "content_type_selection": "Formato do post",
+ "scope_notice_dismiss": "Fechar essa notificação",
+ "edit_unsupported_warning": "Pleroma não suporta editar menções ou enquetes."
},
"registration": {
"bio": "Biografia",
@@ -156,8 +205,18 @@
"email_required": "não pode ser deixado em branco",
"password_required": "não pode ser deixado em branco",
"password_confirmation_required": "não pode ser deixado em branco",
- "password_confirmation_match": "deve corresponder à palavra-passe"
- }
+ "password_confirmation_match": "deve corresponder à palavra-passe",
+ "birthday_required": "não pode ser deixado em branco",
+ "birthday_min_age": "deve ser em ou antes de {date}"
+ },
+ "birthday": "Data de nascimento:",
+ "reason": "Razão para registrar",
+ "register": "Registrar",
+ "reason_placeholder": "Essa instância aprova os registros manualmente.\nPermita ao administrador saber o porquê do seu registro.",
+ "birthday_optional": "Data de nascimento (opcional):",
+ "bio_optional": "Bio (opcional)",
+ "email_optional": "Email (opcional)",
+ "email_language": "Em qual linguagem você deseja receber emails do servidor?"
},
"settings": {
"app_name": "Nome da aplicação",
@@ -523,7 +582,56 @@
"autohide_floating_post_button": "Automaticamente ocultar o botão 'Nova Publicação' (telemóvel)",
"notification_visibility_moves": "Utilizador Migrado",
"accent": "Destaque",
- "pad_emoji": "Preencher espaços ao adicionar emojis do seletor"
+ "pad_emoji": "Preencher espaços ao adicionar emojis do seletor",
+ "confirm_dialogs_logout": "saindo",
+ "move_account_error": "Erro ao mover conta: {error}",
+ "confirm_dialogs_delete": "excluindo um status",
+ "save": "Salvar mudanças",
+ "lists_navigation": "Mostrar listas na navegação",
+ "email_language": "Linguagem para receber emails do servidor",
+ "account_backup_description": "Isso permite a você baixar um arquivo das informações da sua conta e os seus posts, mas eles ainda não podem ser importados para uma conta do Pleroma.",
+ "add_backup_error": "Erro ao adicionar um novo backup: {error}",
+ "confirm_dialogs": "Pedir por confirmação quando",
+ "confirm_dialogs_repeat": "repetindo um status",
+ "account_alias": "Apelidos de conta",
+ "account_alias_table_head": "Apelido",
+ "list_aliases_error": "Erro ao buscar por apelidos: {error}",
+ "hide_list_aliases_error_action": "Fechar",
+ "confirm_dialogs_deny_follow": "negando um seguidor",
+ "confirm_dialogs_approve_follow": "aprovando um seguidor",
+ "backup_running": "Esse backup está em andamento, {number} registro processado. | Esse backup está em progresso, {number} registros processados.",
+ "add_backup": "Criar um novo backup",
+ "added_backup": "Adicionado um novo backup.",
+ "backup_failed": "Esse backup falhou.",
+ "list_backups_error": "Erro ao buscar a lista de backup: {error}",
+ "move_account_notes": "Se você deseja mover a conta para outro lugar, você deve ir para sua conta de destino e adicionar um apelido apontando para cá.",
+ "add_alias_error": "Erro ao adicionar apelido: {error}",
+ "move_account": "Mover conta",
+ "actor_type": "Essa conta é:",
+ "actor_type_description": "Marcando a sua conta como um grupo irá fazer com que ela automaticamente repita os status que a mencionam.",
+ "actor_type_Person": "um usuário normal",
+ "actor_type_Service": "um bot",
+ "actor_type_Group": "um grupo",
+ "account_backup": "Backup da conta",
+ "confirm_dialogs_unfollow": "deixando de seguir usuário",
+ "confirm_dialogs_block": "bloqueando um usuário",
+ "confirm_dialogs_remove_follower": "removendo um seguidor",
+ "remove_alias": "Remover esse apelido",
+ "new_alias_target": "Adicionar um novo apelido (e.g. {example})",
+ "added_alias": "Apelido adicionado.",
+ "move_account_target": "Conta de destino (e.g. {example})",
+ "moved_account": "Conta movida.",
+ "remove_language": "Remover",
+ "primary_language": "Linguagem primária:",
+ "fallback_language": "Linguagem de reserva {index}:",
+ "add_language": "Adicionar linguagem de reserva",
+ "expert_mode": "Mostrar avançados",
+ "setting_changed": "As configurações são diferentes do padrão",
+ "setting_server_side": "Essas configurações estão atreladas ao seu perfil e afetarão todas as sessões e clientes",
+ "mention_links": "Links de menção",
+ "confirm_dialogs_mute": "mutando um usuário",
+ "backup_not_ready": "Esse backup não está pronto ainda.",
+ "remove_backup": "Remover"
},
"timeline": {
"collapse": "Esconder",
@@ -699,7 +807,20 @@
"load_all": "A carregar todos os {emojiAmount} emojis",
"load_all_hint": "Carregado o primeiro emoji {saneAmount}, carregar todos os emojis pode causar problemas de desempenho.",
"keep_open": "Manter o seletor aberto",
- "stickers": "Autocolantes"
+ "stickers": "Autocolantes",
+ "hide_custom_emoji": "Ocultar emojis customizados",
+ "unicode_groups": {
+ "symbols": "Símbolos",
+ "activities": "Atividades",
+ "animals-and-nature": "Animais & Natureza",
+ "people-and-body": "Pessoas & Corpo",
+ "smileys-and-emotion": "Sorriso & Emoção",
+ "travel-and-places": "Viagem & Lugares",
+ "food-and-drink": "Comida & Bebidas",
+ "objects": "Objetos"
+ },
+ "regional_indicator": "Indicador regional {letter}",
+ "unpacked": "Emoji desempacotado"
},
"polls": {
"single_choice": "Escolha única",
@@ -713,7 +834,9 @@
"expiry": "Tempo para finalizar sondagem",
"multiple_choices": "Escolha múltipla",
"type": "Tipo de sondagem",
- "add_poll": "Adicionar Sondagem"
+ "add_poll": "Adicionar Sondagem",
+ "votes_count": "{count} voto | {count} votos",
+ "people_voted_count": "{count} pessoa votou | {count} pessoas votaram"
},
"importer": {
"error": "Ocorreu um erro ao importar este ficheiro.",
@@ -737,7 +860,9 @@
"load_older": "Carregar interações mais antigas",
"follows": "Novos seguidores",
"favs_repeats": "Gostos e Partilhas",
- "moves": "O utilizador migra"
+ "moves": "O utilizador migra",
+ "emoji_reactions": "Reações de Emoji",
+ "reports": "Relatórios"
},
"errors": {
"storage_unavailable": "O Pleroma não conseguiu aceder ao armazenamento do navegador. A sua sessão ou definições locais não serão armazenadas e poderá encontrar problemas inesperados. Tente ativar as cookies."
@@ -828,5 +953,35 @@
"day_short": "{0}d",
"days": "{0} dias",
"day": "{0} dia"
+ },
+ "report": {
+ "state_closed": "Fechar",
+ "reported_statuses": "Estado das denúncias:",
+ "reported_user": "Usuário denunciado:",
+ "state_resolved": "Resolvido",
+ "state": "Estado:",
+ "state_open": "Abrir",
+ "notes": "Notas:"
+ },
+ "announcements": {
+ "start_time_display": "Inicia às {time}",
+ "post_form_header": "Enviar anúncio",
+ "post_placeholder": "Digite o conteúdo do seu anúncio aqui...",
+ "page_header": "Anúncios",
+ "title": "Anúncio",
+ "mark_as_read_action": "Marcar como lido",
+ "post_action": "Postar",
+ "post_error": "Erro: {error}",
+ "close_error": "Fechar",
+ "delete_action": "Apagar",
+ "start_time_prompt": "Tempo de início: ",
+ "end_time_prompt": "Tempo de término: ",
+ "all_day_prompt": "Esse é um evento para o dia todo",
+ "published_time_display": "Publicado às {time}",
+ "end_time_display": "Finaliza às {time}",
+ "edit_action": "Editar",
+ "submit_edit_action": "Enviar",
+ "cancel_edit_action": "Cancelar",
+ "inactive_message": "Esse anúncio está inativo"
}
}
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
@@ -456,6 +456,15 @@
"subject_line_mastodon": "Как в Mastodon: скопировать как есть",
"subject_line_email": "Как в электронной почте: \"re: тема\"",
"subject_line_behavior": "Копировать тему в ответах",
+ "third_column_mode": "Когда недостаточно места, показывать третью колонку содержащую",
+ "third_column_mode_none": "Не показывать третью колонку совсем",
+ "third_column_mode_notifications": "Колонку уведомлений",
+ "third_column_mode_postform": "Форму отправки сообщения и навигацию",
+ "columns": "Колонки",
+ "column_sizes": "Размеры колонок",
+ "column_sizes_sidebar": "Боковой",
+ "column_sizes_content": "Содержимого",
+ "column_sizes_notifs": "Уведомлений",
"no_mutes": "Нет игнорируемых",
"no_blocks": "Нет блокировок",
"notification_visibility_emoji_reactions": "Реакции",
diff --git a/src/i18n/service_worker_messages.js b/src/i18n/service_worker_messages.js
@@ -25,6 +25,7 @@ const messages = {
oc: require('../lib/notification-i18n-loader.js!./oc.json'),
pl: require('../lib/notification-i18n-loader.js!./pl.json'),
pt: require('../lib/notification-i18n-loader.js!./pt.json'),
+ pdc: require('../lib/notification-i18n-loader.js!./pdc.json'),
ro: require('../lib/notification-i18n-loader.js!./ro.json'),
ru: require('../lib/notification-i18n-loader.js!./ru.json'),
sk: require('../lib/notification-i18n-loader.js!./sk.json'),
diff --git a/src/i18n/uk.json b/src/i18n/uk.json
@@ -24,7 +24,21 @@
},
"flash_content": "Натисніть для перегляду змісту Flash за допомогою Ruffle (експериментально, може не працювати).",
"flash_security": "Ця функція може становити ризик, оскільки Flash-вміст все ще є потенційно небезпечним.",
- "flash_fail": "Не вдалося завантажити Flash-вміст, докладнішу інформацію дивись у консолі."
+ "flash_fail": "Не вдалося завантажити Flash-вміст, докладнішу інформацію дивись у консолі.",
+ "generic_error_message": "Виникла помилка: {0}",
+ "never_show_again": "Ніколи не показувати знову",
+ "scope_in_timeline": {
+ "direct": "Приватне",
+ "private": "Лише читачі",
+ "public": "Публічне",
+ "unlisted": "Непублічне"
+ },
+ "undo": "Скасувати",
+ "yes": "Так",
+ "no": "Ні",
+ "unpin": "Відкріпити",
+ "scroll_to_top": "Піднятися вгору",
+ "pin": "Прикріпити"
},
"finder": {
"error_fetching_user": "Користувача не знайдено",
@@ -39,7 +53,8 @@
"scope_options": "Параметри обсягу",
"media_proxy": "Посередник медіа-даних",
"text_limit": "Ліміт символів",
- "upload_limit": "Обмеження завантажень"
+ "upload_limit": "Обмеження завантажень",
+ "shout": "Гучномовець"
},
"exporter": {
"processing": "Опрацьовую, скоро ви зможете завантажити файл",
@@ -52,7 +67,7 @@
"mute": "Ігнорувати"
},
"shoutbox": {
- "title": "Оголошення"
+ "title": "Гучномовець"
},
"about": {
"staff": "Адміністрація",
@@ -70,7 +85,10 @@
"accept": "Прийняти",
"reject": "Відхилити",
"accept_desc": "Поточний інстанс приймає повідомлення тільки з перелічених інстансів:",
- "simple_policies": "Правила поточного інстансу"
+ "simple_policies": "Правила поточного інстансу",
+ "reason": "Причина",
+ "not_applicable": "н/в",
+ "instance": "Сервер"
},
"mrf_policies_desc": "Правила MRF розповсюджуються на даний інстанс. Наступні правила активні:",
"mrf_policies": "Активувати правила MRF (модуль переписування повідомлень)",
@@ -100,7 +118,11 @@
"totp": "Двофакторна автентифікація"
},
"enter_two_factor_code": "Введіть двофакторний код автентифікації",
- "placeholder": "напр. stepan"
+ "placeholder": "напр. stepan",
+ "logout_confirm": "Ви дійсно хочете вийти?",
+ "logout_confirm_accept_button": "Вийти",
+ "logout_confirm_cancel_button": "Ні, хочу назад!",
+ "logout_confirm_title": "Вихід"
},
"importer": {
"error": "Під час імпортування файлу сталася помилка.",
@@ -141,7 +163,15 @@
"followed_you": "підписався(-лась) на вас",
"favorited_you": "вподобав(-ла) ваш допис",
"broken_favorite": "Невідомий допис, шукаю його…",
- "error": "Помилка при оновленні сповіщень: {0}"
+ "error": "Помилка при оновленні сповіщень: {0}",
+ "poll_ended": "опитування закінчено",
+ "submitted_report": "подав скаргу",
+ "unread_announcements": "{num} непрочитане оголошення | {num} непрочитаних оголошень",
+ "unread_chats": "{num} непрочитаний чат | {num} непрочитаних чатів",
+ "unread_follow_requests": "{num} новий запит на підписку | {num} нових запитів на підписку",
+ "configuration_tip": "Ви можете налаштувати, що відображати тут у {theSettings}. {dismiss}",
+ "configuration_tip_settings": "налаштування",
+ "configuration_tip_dismiss": "Не показувати знову"
},
"nav": {
"chats": "Чати",
@@ -161,11 +191,24 @@
"mentions": "Згадування",
"back": "Назад",
"administration": "Адміністрування",
- "home_timeline": "Домашня стрічка"
+ "home_timeline": "Домашня стрічка",
+ "lists": "Списки",
+ "edit_pinned": "Редагувати прикріплене",
+ "edit_finish": "Завершити редагування",
+ "mobile_sidebar": "Ввімкнути бокову панель",
+ "mobile_notifications": "Відкрити сповіщення (є непрочитані)",
+ "mobile_notifications_close": "Закрити сповіщення",
+ "edit_nav_mobile": "Редагувати панель навігації",
+ "announcements": "Анонси",
+ "search_close": "Закрити панель пошуку",
+ "mobile_notifications_mark_as_seen": "Позначити все прочитаним",
+ "quotes": "Цитування"
},
"media_modal": {
"next": "Наступна",
- "previous": "Попередня"
+ "previous": "Попередня",
+ "counter": "{current} / {total}",
+ "hide": "Закрити медіапереглядач"
},
"password_reset": {
"instruction": "Введіть свою адресу електронної пошти або ім’я користувача. Ми надішлемо вам посилання для скидання пароля.",
@@ -205,7 +248,9 @@
"load_older": "Завантажити давніші взаємодії",
"follows": "Нові підписки",
"favs_repeats": "Поширення та вподобайки",
- "moves": "Міграції користувачів"
+ "moves": "Міграції користувачів",
+ "emoji_reactions": "Емоджі реакції",
+ "reports": "Скарги"
},
"errors": {
"storage_unavailable": "Pleroma не змогла отримати доступ до сховища браузеру. Ваша сесія та налаштування не будуть збережені, це може спричинити непередбачувані проблеми. Спробуйте увімкнути cookie."
@@ -219,7 +264,21 @@
"emoji": "Емодзі",
"load_all": "Всі {emojiAmount} эмодзі завантажуються",
"load_all_hint": "Завантажені перші {saneAmount} емодзі, завантаження всіх емодзі може призвести до проблем з продуктивністю.",
- "unicode": "Стандартні емодзі"
+ "unicode": "Стандартні емодзі",
+ "regional_indicator": "Регіональний індикатор {letter}",
+ "unicode_groups": {
+ "animals-and-nature": "Тварини і Рослини",
+ "flags": "Прапори",
+ "food-and-drink": "Їжа та Напої",
+ "objects": "Об'єкти",
+ "people-and-body": "Люди та Тіло",
+ "smileys-and-emotion": "Смайлики та Емотікони",
+ "activities": "Активності",
+ "symbols": "Символи",
+ "travel-and-places": "Подорожі та Місця"
+ },
+ "unpacked": "Розпаковані емоджі",
+ "hide_custom_emoji": "Приховати кастомні емодзі"
},
"post_status": {
"content_type": {
@@ -234,7 +293,7 @@
"new_status": "Створити допис",
"direct_warning_to_first_only": "Цей допис побачать лише користувачі, що були згадані на початку повідомлення.",
"direct_warning_to_all": "Цей допис побачать всі згадані користувачі.",
- "default": "Що нового?",
+ "default": "Щойно приземлились у Борисполі.",
"content_warning": "Тема (необов'язково)",
"preview": "Попередній перегляд",
"posting": "Відправляється",
@@ -253,7 +312,14 @@
"preview_empty": "Пустий",
"media_description_error": "Не вдалось оновити медіа, спробуйте ще раз",
"media_description": "Опис медіа",
- "post": "Опублікувати"
+ "post": "Опублікувати",
+ "edit_unsupported_warning": "Pleroma не підтримує редагування згадувань чи голосувань.",
+ "edit_status": "Редагувати допис",
+ "edit_remote_warning": "Інші віддалені інстанси можуть не підтримувати редагування та вони можуть не отримати актуальну версію допису.",
+ "content_type_selection": "Форматування допису",
+ "scope_notice_dismiss": "Закрити це сповіщення",
+ "reply_option": "Відповісти на цей допис",
+ "quote_option": "Процитувати допис"
},
"settings": {
"blocks_imported": "Блокування імпортовані! Їх обробка триватиме певний час.",
@@ -478,7 +544,7 @@
"header": "Попередній перегляд",
"link": "невеличке посилання",
"header_faint": "Це нормально",
- "input": "Що нового?",
+ "input": "Щойно приземлився у Борисполі.",
"checkbox": "Я переглянув умови використання",
"fine_print": "Прочитайте наш {0} аби нічого нового не дізнатись!",
"faint_link": "корисний підручник"
@@ -638,7 +704,154 @@
"backup_restore": "Резервне копіювання налаштувань"
},
"right_sidebar": "Показувати бокову панель справа",
- "hide_shoutbox": "Приховати оголошення інстансу"
+ "hide_shoutbox": "Приховати гучномовець",
+ "setting_server_side": "Цей параметр прив’язаний до вашого профілю та впливає на всі сеанси та клієнти",
+ "lists_navigation": "Показувати списки в навігації",
+ "account_backup": "Резервне копіювання облікового запису",
+ "account_backup_description": "Це дозволяє завантажити архів даних вашого облікового запису та ваших дописів, але їх ще не можна імпортувати в обліковий запис Pleroma.",
+ "add_backup_error": "Не вдалося додати нову резервну копію: {error}",
+ "account_alias": "Псевдоніми облікового запису",
+ "new_alias_target": "Додати новий псевдонім (напр. {example})",
+ "move_account_notes": "Якщо ви хочете перемістити обліковий запис на інший інстанс, вам потрібно перейти до свого цільового облікового запису та додати псевдонім, що вказує цей обліковий запис.",
+ "added_backup": "Додано нову резервну копію.",
+ "expert_mode": "Показати додаткові параметри",
+ "post_look_feel": "Відображення дописів",
+ "email_language": "Мова для отримання електронних листів від сервера",
+ "account_backup_table_head": "Резервне копіювання",
+ "download_backup": "Завантажити",
+ "backup_not_ready": "Резервна копія ще не готова.",
+ "remove_backup": "Видалити",
+ "list_backups_error": "Помилка під час отримання списку резервних копій: {error}",
+ "add_backup": "Створити нову резервну копію",
+ "account_alias_table_head": "Псевдонім",
+ "list_aliases_error": "Помилка під час отримання псевдонімів: {error}",
+ "hide_list_aliases_error_action": "Закрити",
+ "remove_alias": "Видалити цей псевдонім",
+ "added_alias": "Псевдонім додано.",
+ "add_alias_error": "Помилка під час додавання псевдоніма: {error}",
+ "move_account": "Перемістити обліковий запис",
+ "move_account_target": "Цільовий обліковий запис (напр. {example})",
+ "moved_account": "Обліковий запис переміщено.",
+ "move_account_error": "Помилка під час переміщення облікового запису: {error}",
+ "word_filter_and_more": "Фільтр слів та більше...",
+ "hide_bot_indication": "Сховати позначку бот у дописах",
+ "navbar_column_stretch": "Розтягнути панель навігації на ширину колонок",
+ "hide_wordfiltered_statuses": "Ховати фільтровані статуси",
+ "hide_muted_threads": "Ховати приглушені треди",
+ "posts": "Дописи",
+ "account_privacy": "Безпека",
+ "conversation_display": "Стиль відображення розмови",
+ "conversation_display_tree": "Деревоподібне",
+ "conversation_display_tree_quick": "Вигляд дерева",
+ "disable_sticky_headers": "Не закріплювати заголовок колонки зверху на сторінці",
+ "third_column_mode_none": "Не показувати третю колонку взагалі",
+ "third_column_mode_notifications": "Колонку сповіщень",
+ "columns": "Колонки",
+ "auto_update": "Автоматично показувати нові дописи",
+ "use_websockets": "Використовувати вебсокети (Оновлення в реальному часі)",
+ "use_at_icon": "Показувати {'@'} символ як іконку замість тексту",
+ "mute_bot_posts": "Приховувати дописи ботів",
+ "always_show_post_button": "Завжди показувати плаваючу кнопку «Новий Допис»",
+ "hide_favorites_description": "Не показувати список моїх вподобань (люди все одно отримують сповіщення)",
+ "third_column_mode": "Коли достатньо місця, показувати третю колонку, що містить",
+ "user_popover_avatar_action_open": "Відкрити профіль",
+ "wordfilter": "Фільтр слів",
+ "mention_links": "Посилання для згадування",
+ "user_profiles": "Профілі користувачів",
+ "notification_visibility_polls": "Закінчення опитувань, в яких ви проголосували",
+ "remove_language": "Вилучити",
+ "primary_language": "Основна мова:",
+ "fallback_language": "Резервна мова {index}:",
+ "confirm_dialogs_deny_follow": "тим, як відмовити у запиті на підписку",
+ "confirm_dialogs_remove_follower": "видаленням підписника",
+ "notification_show_extra": "Показувати додаткові сповіщення в панелі сповіщень",
+ "notification_extra_chats": "Показувати непрочитані чати",
+ "notification_extra_announcements": "Показувати непрочитані оголошення",
+ "notification_extra_follow_requests": "Показувати нові запити на підписку",
+ "third_column_mode_postform": "Форму відправки повідомлень та панель навігації",
+ "notification_extra_tip": "Показати пораду з налаштувань для додаткових сповіщень",
+ "backup_running": "Резервне копіювання триває, оброблено {number} записи. | Резервне копіювання триває, оброблено {number} записів.",
+ "backup_failed": "Резервне копіювання не вдалося.",
+ "preview": "Попередній перегляд",
+ "url": "URL",
+ "birthday": {
+ "label": "День народження",
+ "show_birthday": "Показувати мій день народження"
+ },
+ "confirm_dialogs": "Запитувати підтвердження перед",
+ "confirm_dialogs_repeat": "поширенням допису",
+ "confirm_dialogs_unfollow": "скасуванням підписки",
+ "confirm_dialogs_block": "блокуванням користувача",
+ "confirm_dialogs_mute": "тим, як заглушити користувача",
+ "show_scrollbars": "Показувати смугу прокрутки на бічних панелях",
+ "column_sizes": "Розміри панелей",
+ "column_sizes_sidebar": "Бічна панель",
+ "add_language": "Додати резервну мову",
+ "confirm_dialogs_delete": "видаленням допису",
+ "confirm_dialogs_logout": "виходом із системи",
+ "confirm_dialogs_approve_follow": "схваленням запиту на підписку",
+ "mute_sensitive_posts": "Не стежити за чутливими постами",
+ "notification_visibility_follow_requests": "Запити на стеження",
+ "notification_visibility_reports": "Скарги",
+ "conversation_display_linear": "Линійний стиль",
+ "conversation_display_linear_quick": "Линійний вигляд",
+ "conversation_other_replies_button": "Показувати кнопку \"інші відповіді\"",
+ "conversation_other_replies_button_below": "Нижче статусів",
+ "mention_link_bolden_you": "Підсвічувати згадки в яких вас згадано",
+ "notification_setting_ignore_inactionable_seen_tip": "Це насправді не позначить ці сповіщення прочитанними, і ви все одно отримаєте сповіщення на робочому столі",
+ "notification_setting_unseen_at_top": "Показувати непрочитані сповіщення згори",
+ "mention_link_show_avatar": "Показувати світлину користувача поруч з посиланням",
+ "column_sizes_notifs": "Сповіщення",
+ "commit_value": "Зберегти",
+ "commit_value_tooltip": "Значення не збережено, натисніть цю кнопку щоб зберегти зміни",
+ "units": {
+ "time": {
+ "m": "хвилин",
+ "s": "секунд",
+ "h": "годин",
+ "d": "днів"
+ }
+ },
+ "hide_scrobbles_after": "Приховати прослуховування старіші чим",
+ "conversation_other_replies_button_inside": "Всередині статусів",
+ "mention_link_display": "Показувати посилання на згадки",
+ "user_popover_avatar_action": "Дія при натисканні на світлину",
+ "notification_setting_ignore_inactionable_seen": "Ігнорувати прочитаний статус сповіщень, на які неможливо відреагувати (вподобання, репости і тд)",
+ "user_popover_avatar_action_close": "Закрити панель",
+ "reset_value": "Скинути",
+ "enable_web_push_always_show_tip": "Деякі браузери (Chromium, Chrome) потребують щоб push повідомлення завжди були сповіщенням, інакше ви побачите загальне повідомлення \"Сайт було оновлено у фоні\". Увімкніть це налаштування щоб запобігти цьому повідомленню. Може призвести до подвійних сповіщень у інших браузерах.",
+ "autocomplete_select_first": "Автоматично обирати перше значення коли доступні результати автозаповнення",
+ "hide_scrobbles": "Приховати прослуховування",
+ "notification_visibility_in_column": "Показувати в панелі сповіщень",
+ "tree_advanced": "Дозволити більш гнучку навігацію при розгорнутому перегляді",
+ "tree_fade_ancestors": "Показувати похідні статуси більш блідим текстом",
+ "notification_setting_drawer_marks_as_seen": "Закриття панелі в мобільній версії позначає всі сповіщення прочитанними",
+ "user_popover_avatar_overlay": "Показувати картку користувача над світлиною",
+ "show_yous": "Показати (Вас)",
+ "notification_setting_annoyance": "Роздратування",
+ "notification_setting_filters_chrome_push": "У деяких браузерах (Google Chrome) може бути неможливо повністю відфільтрувати сповіщення за типом, коли вони надходять через Push",
+ "enable_web_push_always_show": "Завжди показувати web push сповіщення",
+ "user_popover_avatar_action_zoom": "Збільшити світлину",
+ "actor_type_description": "Позначення вашого акаунту як групового змусить його автоматично повторювати статуси, які вас згадують.",
+ "actor_type_Person": "звичайний користувач",
+ "actor_type_Service": "бот",
+ "actor_type_Group": "група",
+ "actor_type": "Цей акаунт:",
+ "notification_visibility_native_notifications": "Показувати нативне сповіщення",
+ "column_sizes_content": "Зміст",
+ "mention_link_display_full": "завжди повні імена (наприклад {'@'}petro{'@'}poroshenko.org)",
+ "force_theme_recompilation_debug": "Вимкнути кеш теми, увімкнути перекомпіляцію при кожному старті (ВІДЛАДКА)",
+ "mention_link_use_tooltip": "Показувати картку користувача при натисканні згадки",
+ "mention_link_show_avatar_quick": "Показувати світлину користувача поруч зі згадками",
+ "mention_link_fade_domain": "Скорочувати домени (наприклад {'@'}poroshenko.org в {'@'}petro{'@'}poroshenko.org)",
+ "hard_reset_value": "Скинути всі налаштування",
+ "reset_value_tooltip": "Відкинути чернетку",
+ "hard_reset_value_tooltip": "Прибрати налаштування зі сховища, буде використовуватись значення за замовчуванням",
+ "emoji_reactions_scale": "Масштабування реакцій",
+ "max_depth_in_thread": "Максимальна кількість рівнів треду для відображення за замовчуванням",
+ "mention_link_display_full_for_remote": "як повні імена тільки для користувачів з інших серверів (наприклад {'@'}petro{'@'}poroshenko.org)",
+ "mention_link_display_short": "завжди як короткі імена (наприклад {'@'}petro)",
+ "hide_actor_type_indication": "Приховати позначення типу акаунту (бот, група і тд) в постах"
},
"selectable_list": {
"select_all": "Вибрати все"
@@ -655,7 +868,9 @@
"password_required": "не може бути порожнім",
"email_required": "не може бути порожнім",
"fullname_required": "не може бути порожнім",
- "username_required": "не може бути порожнім"
+ "username_required": "не може бути порожнім",
+ "birthday_required": "не може бути пустим",
+ "birthday_min_age": "має бути в або перед {date}"
},
"bio_placeholder": "напр.\nНаш народ завжди прагне волі для себе і бажає її для інших народів. Він боровся і бореться за правду і справедливість. Ми хочемо жити у згоді і взаємному шануванні з усіми народами доброї волі. Такі самі права визнаємо за іншими народами, за які боремося для себе.",
"fullname_placeholder": "напр. Степан Бандера",
@@ -670,7 +885,12 @@
"captcha": "CAPTCHA",
"register": "Зареєструватися",
"reason_placeholder": "Цей інстанс обробляє запити на реєстрацію вручну.\nРозкажіть адміністрації чому ви хочете зареєструватися.",
- "reason": "Причина реєстрації"
+ "reason": "Причина реєстрації",
+ "bio_optional": "Біографія (необов'язково)",
+ "email_language": "Якою мовою ви бажаєте отримувати електронні листи від сервера?",
+ "email_optional": "Ел. пошта (необов'язково)",
+ "birthday": "День народження:",
+ "birthday_optional": "День народження (необов'язково):"
},
"who_to_follow": {
"who_to_follow": "На кого підписатися",
@@ -685,7 +905,10 @@
"reject_follow_request": "Відхилити запит на підписку",
"accept_follow_request": "Прийняти запит на підписку",
"media_upload": "Завантажити медіа",
- "bookmark": "Додати до закладок"
+ "bookmark": "Додати до закладок",
+ "toggle_expand": "Розгорнути або згорнути сповіщення щоб показати допис повністю",
+ "toggle_mute": "Розгорнути або згорнути сповіщення щоб відкрити заглушений контент",
+ "autocomplete_available": "{number} результат. Використовуйте клавіши зі стрілками для навігації. | {number} результатів доступно. Використовуйте клавіши зі стрілками для навігації."
},
"upload": {
"error": {
@@ -734,14 +957,32 @@
"day": "{0} день",
"seconds_short": "{0}с",
"seconds": "{0} секунди",
- "in_future": "через {0}"
+ "in_future": "через {0}",
+ "unit": {
+ "months": "{0} місяць | {0} місяців",
+ "minutes": "{0} хвилина | {0} хвилин",
+ "hours_short": "{0}год",
+ "minutes_short": "{0}хв",
+ "months_short": "{0}міс",
+ "seconds": "{0} секунда | {0} секунд",
+ "seconds_short": "{0}с",
+ "weeks_short": "{0}тижд",
+ "years": "{0} рік | {0} років",
+ "years_short": "{0}р.",
+ "days": "{0} день | {0} днів",
+ "days_short": "{0}д",
+ "hours": "{0} година | {0} годин",
+ "weeks": "{0} тиждень | {0} тижнів"
+ }
},
"search": {
"no_results": "Немає результатів",
"hashtags": "Хештеги",
"people": "Люди",
"people_talking": "{count} людей говорять про це",
- "person_talking": "{count} особа говорить про це"
+ "person_talking": "{count} особа говорить про це",
+ "no_more_results": "Більше немає",
+ "load_more": "Завантажити ще"
},
"user_card": {
"statuses": "Дописи",
@@ -766,7 +1007,8 @@
"grant_moderator": "Надати права модератора",
"revoke_admin": "Позбавити прав адміністратора",
"grant_admin": "Надати права адміністратора",
- "quarantine": "Не розповсюджувати дописи на інших інстансах"
+ "quarantine": "Не розповсюджувати дописи на інших інстансах",
+ "delete_user_data_and_deactivate_confirmation": "Це назовсім видалить дані обліковки й вимкне її. Точно продовжити?"
},
"deny": "Відмовити",
"block": "Заблокувати",
@@ -803,7 +1045,42 @@
"disabled": "Не виділяти"
},
"bot": "Бот",
- "edit_profile": "Редагувати профіль"
+ "edit_profile": "Редагувати профіль",
+ "deactivated": "Деактивований",
+ "follow_cancel": "Скасувати запит",
+ "block_confirm_title": "Блокування",
+ "block_confirm": "Точно заблокувати {user}?",
+ "mute_confirm_cancel_button": "Ні, не приглушувати",
+ "note_blank": "(Пусто)",
+ "edit_note_apply": "Застосувати",
+ "edit_note_cancel": "Скасувати",
+ "block_confirm_accept_button": "Так, заблокувати",
+ "block_confirm_cancel_button": "Ні, не блокувати",
+ "deny_confirm_title": "Відхилити запит на підписку",
+ "mute_confirm_accept_button": "Так, приглушити",
+ "mute_confirm": "Точно приглушити {user}?",
+ "edit_note": "Редагувати нотатку",
+ "mute_confirm_title": "Приглушення",
+ "mute_duration_prompt": "Приглушити користувача на (0 якщо назавжди):",
+ "approve_confirm_title": "Дозвіл підписатись",
+ "approve_confirm_accept_button": "Так, дозволити",
+ "approve_confirm_cancel_button": "Ні, скасувати",
+ "deny_confirm_accept_button": "Так, відхилити",
+ "deny_confirm_cancel_button": "Ні, скасувати",
+ "deny_confirm": "Ви точно хочете відхилити запит на підписку від {user}?",
+ "unfollow_confirm_title": "Відписка",
+ "unfollow_confirm": "Точно відписатись від {user}?",
+ "unfollow_confirm_accept_button": "Так, відписатись",
+ "unfollow_confirm_cancel_button": "Ні, не відписуватись",
+ "note": "Приватна нотатка",
+ "group": "Група",
+ "remove_follower_confirm": "Ви дійсно хочете прибрати користувача {user} з ваших фоловерів?",
+ "remove_follower_confirm_title": "Підтверджувати відписку",
+ "remove_follower": "Відписка",
+ "remove_follower_confirm_accept_button": "Прибрати",
+ "remove_follower_confirm_cancel_button": "Зберегти",
+ "birthday": "День народження: {birthday}",
+ "approve_confirm": "Прийняти запит на стеження від {user}?"
},
"status": {
"copy_link": "Скопіювати посилання на допис",
@@ -830,7 +1107,53 @@
"thread_muted": "Нитка заглушена",
"unmute_conversation": "Припинити глушити розмову",
"external_source": "Зовнішнє джерело",
- "expand": "Розгорнути"
+ "expand": "Розгорнути",
+ "edit": "Редагувати допис",
+ "edited_at": "(змінено: {time})",
+ "thread_follow_with_icon": "{icon} {text}",
+ "ancestor_follow_with_icon": "{icon} {text}",
+ "show_all_conversation_with_icon": "{icon} {text}",
+ "plus_more": "+{number} більше",
+ "thread_show_full_with_icon": "{icon} {text}",
+ "show_only_conversation_under_this": "Показати всі відповіді на цей допис",
+ "status_history": "Історія змін",
+ "thread_hide": "Сховати гілку",
+ "open_gallery": "Відкрити галерею",
+ "repeat_confirm": "Точно поширити допис?",
+ "repeat_confirm_title": "Підтвердьте поширення",
+ "repeat_confirm_accept_button": "Так, поширити",
+ "repeat_confirm_cancel_button": "Ні, не поширювати",
+ "delete_error": "Помилка при видаленні допису: {0}",
+ "delete_confirm_accept_button": "Так, видалити",
+ "delete_confirm_cancel_button": "Ні, лишити",
+ "delete_confirm_title": "Підтвердьте видалення",
+ "you": "(ви)",
+ "collapse_attachments": "Згорнути вкладення",
+ "show_all_attachments": "Показати всі вкладення",
+ "hide_attachment": "Сховати вкладення",
+ "many_attachments": "Вкладень: {number} | Вкладень: {number}",
+ "attachment_stop_flash": "Зупинити Flash-плеєр",
+ "thread_follow": "Ще відповідей: {numStatus} | Ще відповідей: {numStatus}",
+ "remove_attachment": "Видалити вкладення",
+ "ancestor_follow": "Переглянути ще {numReplies} під цим дописом | Переглянути ще {numReplies} під цим дописом",
+ "show_all_conversation": "Показати всю розмову (ще дописів: {numStatus}) | Показати всю розмову (ще дописів: {numStatus})",
+ "move_up": "Посунути вкладення ліворуч",
+ "move_down": "Посунути вкладення праворуч",
+ "thread_show": "Показати гілку",
+ "mentions": "Згадки",
+ "thread_show_full": "Показати відповіді: ({numStatus}/{depth}) | Показати відповіді: ({numStatus}/{depth})",
+ "hide_quote": "Сховати процитований допис",
+ "display_quote": "Показати процитований допис",
+ "invisible_quote": "Процитований допис недоступний: {link}",
+ "replies_list_with_others": "Ще відповідей: {numReplies} | Ще відповідей: {numReplies}:",
+ "show_attachment_in_modal": "Показати вкладення у вікні",
+ "show_attachment_description": "Переглянути опис (натисніть саме вкладення, якщо опис не вміщається)",
+ "quotes": "Цитування",
+ "load_error": "Неможливо завантажити статус: {error}",
+ "loading": "Завантаження...",
+ "sensitive_muted": "Заглушення чутливого контенту",
+ "reaction_count_label": "{num} людина відреагувала | {num} людей відреагували",
+ "more_actions": "Більше дій для цього статусу"
},
"timeline": {
"no_more_statuses": "Більше немає дописів",
@@ -845,7 +1168,9 @@
"repeated": "поширив(-ла)",
"no_retweet_hint": "Запис, позначено як \"тільки для підписників\" або \"особисте\" і тому не може бути поширений",
"socket_broke": "Втрачено з'єднання у реальному часі: код {0}",
- "socket_reconnected": "Встановлено з'єднання у реальному часі"
+ "socket_reconnected": "Встановлено з'єднання у реальному часі",
+ "quick_view_settings": "Налаштування швидкого перегляду",
+ "quick_filter_settings": "Налаштування швидкого фільтру"
},
"user_reporting": {
"submit": "Відправити",
@@ -860,5 +1185,202 @@
"profile_loading_error": "Вибачте, під час завантаження цього профілю виникла помилка.",
"profile_does_not_exist": "Вибачте, цей профіль більше не існує.",
"timeline_title": "Стрічка користувача"
+ },
+ "report": {
+ "notes": "Примітки:",
+ "state": "Статус:",
+ "state_open": "відкритий",
+ "state_closed": "закритий",
+ "state_resolved": "вирішений",
+ "reported_statuses": "Дописи, на які подано скаргу:",
+ "reporter": "Позивач:",
+ "reported_user": "Відповідач:"
+ },
+ "announcements": {
+ "delete_action": "Видалити",
+ "page_header": "Анонси",
+ "title": "Анонси",
+ "mark_as_read_action": "Позначити як прочитане",
+ "post_form_header": "Розмістити оголошення",
+ "post_placeholder": "Введіть текст вашого оголошення тут...",
+ "post_action": "Пост",
+ "post_error": "Помилка: {error}",
+ "close_error": "Закрити",
+ "start_time_prompt": "Початок: ",
+ "end_time_prompt": "Кінець: ",
+ "all_day_prompt": "Це захід на цілий день",
+ "published_time_display": "Опубліковано в {time}",
+ "start_time_display": "Початок о {time}",
+ "end_time_display": "Кінець о {time}",
+ "edit_action": "Редагувати",
+ "submit_edit_action": "Надіслати",
+ "cancel_edit_action": "Скасувати",
+ "inactive_message": "Це оголошення неактивне"
+ },
+ "lists": {
+ "really_delete": "Дійсно видалити список?",
+ "error": "Помилка при роботі зі списками: {0}",
+ "is_in_list": "Вже є у списку",
+ "editing_list": "Редагування списку {listTitle}",
+ "creating_list": "Створення нового списку",
+ "search": "Знайти користувачів",
+ "create": "Створити",
+ "save": "Зберегти зміни",
+ "manage_members": "Керувати учасниками списку",
+ "new": "Новий список",
+ "title": "Назва списку",
+ "delete": "Видалити список",
+ "following_only": "Лише за ким ви стежите",
+ "lists": "Списки",
+ "manage_lists": "Керувати списками",
+ "remove_from_list": "Видалити зі списку",
+ "add_to_list": "Додати до списку",
+ "update_title": "Зберегти назву",
+ "add_members": "Шукати більше користувачів"
+ },
+ "update": {
+ "update_changelog": "Щоб дізнатись більше інформації, дивіться {theFullChangelog}.",
+ "update_bugs": "Будь ласка, повідомляйте про будь-які проблеми та помилки на {pleromaGitlab}, оскільки ми внесли багато змін, і навіть після ретельно проведених перевірок, ми можемо щось пропустити. Ми заздалегідь вдячні за ваші відгуки щодо проблем, з якими ви можете зіткнутися, а також пропозиції щодо вдосконалення Pleroma та Pleroma-FE.",
+ "update_changelog_here": "повний список змін",
+ "big_update_title": "Хвилинку уваги",
+ "update_bugs_gitlab": "Pleroma GitLab",
+ "big_update_content": "У нас не було оновлень протягом тривалого часу, тому речі можуть мати інакший вигляд, аніж ви звикли.",
+ "art_by": "Арт від {linkToArtist}"
+ },
+ "unicode_domain_indicator": {
+ "tooltip": "Цей домен містить не-ASCII символи."
+ },
+ "admin_dash": {
+ "window_title": "Адміністрування",
+ "tabs": {
+ "instance": "Сервер (Instance)",
+ "frontends": "Фронтенди",
+ "nodb": "Немає конфігурації бази даних",
+ "emoji": "Емодзі",
+ "limits": "Ліміти"
+ },
+ "nodb": {
+ "heading": "Конфіг бази даних вимкнено",
+ "text": "Вам потрібно змінити налаштування бекенду таким чином, щоб {property} дорівнювало {value}, детальніше у {documentation}.",
+ "text2": "Більшість налаштувань будуть недоступні.",
+ "documentation": "документація"
+ },
+ "frontend": {
+ "install": "Встановити",
+ "install_version": "Встановити версію {version}",
+ "success_installing_frontend": "Фронтенд версії {version} успішно встановлено",
+ "failure_installing_frontend": "Не вдалось встановити версію {version}: {reason}",
+ "repository": "Посилання на репозиторій",
+ "versions": "Доступні версії",
+ "is_default_custom": "(За замовчуванням, версія: {version})",
+ "build_url": "URL збірки",
+ "reinstall": "Перевстановити",
+ "default_frontend_unavail": "Налаштування фронтенду недоступні, адже вони потребують конфігурації бази даних",
+ "default_frontend_tip": "Фронтенд за замовчуванням будуть бачити всі користувачі. На сьогоднішній день немає можливості обирати персональний фронтенд під кожного користувача. Якщо ви не користуватиметесь PleromaFE, то, скоріш за все, вам доведеться користуватись старим та забагованим AdminFE для налаштування свого серверу, допоки ми не придумаємо нічого кращого.",
+ "set_default": "Призначити за замовчуванням",
+ "set_default_version": "Призначити версію {version} за замовчуванням",
+ "wip_notice": "Будь ласка майте на увазі що цей розділ знаходиться у процесі розробки та певні функції можуть не працювати.",
+ "default_frontend": "Фронтенд за замовчуванням",
+ "available_frontends": "Доступно для встановлення",
+ "is_default": "(За замовчуванням)",
+ "more_install_options": "Більше варіантів встановлення",
+ "more_default_options": "Більше налаштувань за замовчуванням"
+ },
+ "emoji": {
+ "adding_new": "Додати новий емодзі",
+ "shortcode": "Шорткод",
+ "filename": "Назва файлу",
+ "add_file": "Додати файл",
+ "importFS": "Імпортувати емодзі з файлової системи",
+ "global_actions": "Глобальні дії",
+ "reload": "Перезавантажити емодзі",
+ "error": "Помилка: {0}",
+ "delete_pack": "Видалити набір",
+ "create_pack": "Створити набір",
+ "create": "Створити",
+ "new_pack_name": "Нова назва набору",
+ "emoji_packs": "Набори емодзі",
+ "remote_packs": "Віддалені набори",
+ "do_list": "Список",
+ "remote_pack_instance": "Сервер з віддаленими наборами",
+ "homepage": "Домашня сторінка",
+ "edit_pack": "Редагувати набір",
+ "description": "Опис",
+ "fallback_src": "Джерело заміни",
+ "share": "Поділитись",
+ "fallback_sha256": "Заміна SHA256",
+ "delete_confirm": "Ви впевнені, що хочете видалити {0}?",
+ "download_pack": "Завантажити набір",
+ "downloading_pack": "Завантаження {0}",
+ "download": "Завантажити",
+ "new_filename": "Назва файлу, залиште порожнім для автозаповнення",
+ "download_as_name": "Нове ім'я",
+ "editing": "Редагування {0}",
+ "delete_title": "Видалити?",
+ "download_as_name_full": "Нове ім'я, залиште порожнім для перевикористання",
+ "files": "Файли",
+ "metadata_changed": "Метадані відрізняються від збережених",
+ "replace_warning": "Це ЗАМІНИТЬ локальний набір з такою самою назвою",
+ "emoji_changed": "Незбережені зміни файлу емодзі, перевірте підсвічений емодзі",
+ "emoji_pack": "Набір емодзі",
+ "revert_meta": "Відновити метадані",
+ "save": "Зберегти",
+ "delete": "Видалити",
+ "revert": "Відновити",
+ "save_meta": "Зберегти метадані",
+ "new_shortcode": "Шорткод, залиште порожнім для автозаповнення"
+ },
+ "instance": {
+ "restrict": {
+ "activities": "Доступ до статусів/активностей",
+ "header": "Обмежити доступ для анонімних відвідувачів",
+ "timelines": "Доступ до стрічок",
+ "profiles": "Доступ до профілів користувачів",
+ "description": "Детальне налаштування для контролю доступу до певних розділів API. За замовчуванням (невизначений стан) доступ буде заборонений якщо сервер не публічний, увімкнене налаштування забороняє доступ навіть до публічного серверу, вимкнене налаштування дозволяє доступ навіть до приватного серверу. Неправильні налаштування можуть призвести до небажаних наслідків: наприклад, якщо доступ до профілю обмежений, то пости будуть відображатись без інформації про профіль."
+ },
+ "registrations": "Заявки на реєстрацію",
+ "instance": "Інформація про сервер",
+ "access": "Доступ до серверу",
+ "captcha_header": "CAPTCHA",
+ "kocaptcha": "налаштування KoCaptcha"
+ },
+ "reset_all": "Скинути все",
+ "commit_all": "Зберегти все",
+ "captcha": {
+ "kocaptcha": "KoCaptchа",
+ "native": "Нативний"
+ },
+ "limits": {
+ "uploads": "Ліміти вкладень",
+ "users": "Ліміти користувацьких профілей",
+ "profile_fields": "Ліміти полів у профілі",
+ "arbitrary_limits": "Довільні ліміти",
+ "user_uploads": "Ліміти медіа у профілі",
+ "posts": "Ліміти дописів"
+ },
+ "temp_overrides": {
+ ":pleroma": {
+ ":instance": {
+ ":limit_to_local_content": {
+ "label": "Обмежити пошуки локальним контентом",
+ "description": "Вимикає глобальних пошук по мережі для неавторизованих (за замовчуванням), всіх користувачів або нікого"
+ },
+ ":description_limit": {
+ "description": "Максимальна довжина поля опису вкладень",
+ "label": "Обмеження"
+ },
+ ":public": {
+ "description": "Вимкнення цього зробить API доступним тільки залогіненим користувачам, таким чином Публічна стрічка та стрічка Федерації будуть недоступні неавторизованим користувачам.",
+ "label": "Публічний сервер"
+ },
+ ":background_image": {
+ "label": "Тло",
+ "description": "Тло (використовується PleromaFE)"
+ }
+ }
+ }
+ },
+ "wip_notice": "Ця адмінська панель експериментальна, {adminFeLink}.",
+ "old_ui_link": "старий інтерфейс адмінки доступний тут"
}
}
diff --git a/src/i18n/zh.json b/src/i18n/zh.json
@@ -15,7 +15,8 @@
"title": "功能",
"who_to_follow": "推荐关注",
"pleroma_chat_messages": "Pleroma 聊天",
- "upload_limit": "上传限制"
+ "upload_limit": "上传限制",
+ "shout": "留言板"
},
"finder": {
"error_fetching_user": "获取用户时发生错误",
@@ -46,7 +47,21 @@
},
"flash_content": "点击以使用 Ruffle 显示 Flash 内容(实验性,可能无效)。",
"flash_security": "注意这可能有潜在的危险,因为 Flash 内容仍然是任意的代码。",
- "flash_fail": "Flash 内容加载失败,请在控制台查看详情。"
+ "flash_fail": "Flash 内容加载失败,请在控制台查看详情。",
+ "scope_in_timeline": {
+ "public": "公开",
+ "direct": "私讯",
+ "private": "仅关注者",
+ "unlisted": "列外"
+ },
+ "scroll_to_top": "滚动至顶",
+ "generic_error_message": "发生一个错误:{0}",
+ "never_show_again": "不再显示",
+ "undo": "撤销",
+ "yes": "是",
+ "no": "否",
+ "unpin": "取消固定该项",
+ "pin": "固定该项"
},
"image_cropper": {
"crop_picture": "裁剪图片",
@@ -75,11 +90,17 @@
"heading": {
"totp": "双重因素验证",
"recovery": "双重因素恢复"
- }
+ },
+ "logout_confirm_cancel_button": "不要登出",
+ "logout_confirm_title": "确认登出",
+ "logout_confirm_accept_button": "登出",
+ "logout_confirm": "您确定要登出吗?"
},
"media_modal": {
"previous": "往前",
- "next": "往后"
+ "next": "往后",
+ "hide": "关闭媒体查看器",
+ "counter": "{current} / {total}"
},
"nav": {
"about": "关于",
@@ -100,21 +121,41 @@
"chats": "聊天",
"timelines": "时间线",
"bookmarks": "书签",
- "home_timeline": "主页时间线"
+ "home_timeline": "主页时间线",
+ "lists": "列表",
+ "edit_finish": "完成编辑",
+ "mobile_notifications": "打开通知(有未读的)",
+ "mobile_notifications_close": "关闭通知",
+ "announcements": "公告",
+ "edit_nav_mobile": "自定义导航栏",
+ "edit_pinned": "编辑固定的项目",
+ "mobile_sidebar": "切换移动设备侧栏",
+ "search_close": "关闭搜索栏",
+ "mobile_notifications_mark_as_seen": "全部已阅",
+ "quotes": "引用"
},
"notifications": {
"broken_favorite": "未知的状态,正在搜索中…",
- "favorited_you": "喜欢了你的状态",
- "followed_you": "关注了你",
+ "favorited_you": "喜欢了您的状态",
+ "followed_you": "关注了您",
"load_older": "加载更早的通知",
"notifications": "通知",
"read": "已阅!",
- "repeated_you": "转发了你的状态",
+ "repeated_you": "转发了您的状态",
"no_more_notifications": "没有更多的通知",
- "reacted_with": "作出了 {0} 的反应",
+ "reacted_with": "作出了 {0} 的回应",
"migrated_to": "迁移到了",
- "follow_request": "想要关注你",
- "error": "取得通知时发生错误:{0}"
+ "follow_request": "想要关注您",
+ "error": "取得通知时发生错误:{0}",
+ "poll_ended": "投票结束了",
+ "submitted_report": "提交举报",
+ "unread_announcements": "{num} 条未读公告",
+ "unread_chats": "{num} 条未读聊天讯息",
+ "unread_follow_requests": "{num} 个新关注请求",
+ "configuration_tip": "可以在 {theSettings} 里定制什么会显示在这里。{dismiss}",
+ "configuration_tip_settings": "设置",
+ "configuration_tip_dismiss": "不再显示",
+ "subscribed_status": "已发送"
},
"polls": {
"add_poll": "增加投票",
@@ -139,11 +180,14 @@
"favs_repeats": "转发和喜欢",
"follows": "新的关注者",
"load_older": "加载更早的互动",
- "moves": "用户迁移"
+ "moves": "用户迁移",
+ "reports": "举报",
+ "emoji_reactions": "表情回应",
+ "statuses": "订阅"
},
"post_status": {
"new_status": "发布新状态",
- "account_not_locked_warning": "你的帐号没有 {0}。任何人都可以关注你并浏览你的上锁内容。",
+ "account_not_locked_warning": "您的帐号没有 {0}。任何人都可以关注您并浏览您的上锁内容。",
"account_not_locked_warning_link": "上锁",
"attachments_sensitive": "标记附件为敏感内容",
"content_type": {
@@ -159,12 +203,12 @@
"posting": "发送中",
"scope_notice": {
"public": "本条内容可以被所有人看到",
- "private": "关注你的人才能看到本条内容",
+ "private": "关注您的人才能看到本条内容",
"unlisted": "本条内容既不在公共时间线,也不会在所有已知网络上可见"
},
"scope": {
"direct": "私信 - 只发送给被提及的用户",
- "private": "仅关注者 - 只有关注了你的人能看到",
+ "private": "仅关注者 - 只有关注了您的人能看到",
"public": "公共 - 发送到公共时间轴",
"unlisted": "不公开 - 不会发送到公共时间轴"
},
@@ -172,8 +216,15 @@
"preview": "预览",
"media_description": "媒体描述",
"media_description_error": "更新媒体失败,请重试",
- "empty_status_error": "不能发布没有内容、没有附件的发文",
- "post": "发送"
+ "empty_status_error": "不能发布没有内容、没有附件的帖子",
+ "post": "发送",
+ "edit_remote_warning": "其它远程实例可能不支持编辑并且无法接收您的帖子的最新版本。",
+ "edit_unsupported_warning": "Pleroma 不支持对提及或投票进行编辑。",
+ "edit_status": "编辑状态",
+ "content_type_selection": "发帖格式",
+ "scope_notice_dismiss": "关闭此提示",
+ "reply_option": "回复这条状态",
+ "quote_option": "引用这条状态"
},
"registration": {
"bio": "简介",
@@ -186,18 +237,25 @@
"new_captcha": "点击图片获取新的验证码",
"username_placeholder": "例如:lain",
"fullname_placeholder": "例如:岩仓玲音",
- "bio_placeholder": "例如:\n你好,我是玲音。\n我是一个住在日本郊区的动画少女。你可能在 Wired 见过我。",
+ "bio_placeholder": "例如:\n你好,我是玲音。\n我是一个住在日本郊区的动画少女。您可能在 Wired 见过我。",
"validations": {
"username_required": "不能留空",
"fullname_required": "不能留空",
"email_required": "不能留空",
"password_required": "不能留空",
"password_confirmation_required": "不能留空",
- "password_confirmation_match": "密码不一致"
+ "password_confirmation_match": "密码不一致",
+ "birthday_required": "不能为空",
+ "birthday_min_age": "必须在 {date} 或之前"
},
"reason_placeholder": "此实例的注册需要手动批准。\n请让管理员知道您为什么想要注册。",
"reason": "注册理由",
- "register": "注册"
+ "register": "注册",
+ "email_language": "您想从服务器收到什么语言的邮件?",
+ "bio_optional": "介绍(可选)",
+ "email_optional": "电子邮件(可选)",
+ "birthday": "生日:",
+ "birthday_optional": "生日(可选):"
},
"selectable_list": {
"select_all": "选择全部"
@@ -235,7 +293,7 @@
"background": "背景",
"bio": "简介",
"block_export": "屏蔽名单导出",
- "block_export_button": "导出你的屏蔽名单到一个 csv 文件",
+ "block_export_button": "导出您的屏蔽名单到一个 csv 文件",
"block_import": "屏蔽名单导入",
"block_import_error": "导入屏蔽名单出错",
"blocks_imported": "屏蔽名单导入成功!需要一点时间来处理。",
@@ -249,17 +307,17 @@
"change_password_error": "修改密码的时候出了点问题。",
"changed_password": "成功修改了密码!",
"collapse_subject": "折叠带主题的内容",
- "composing": "写作",
+ "composing": "撰写",
"confirm_new_password": "确认新密码",
"current_avatar": "当前头像",
"current_password": "当前密码",
"current_profile_banner": "您当前的横幅图片",
"data_import_export_tab": "数据导入/导出",
"default_vis": "默认可见范围",
- "delete_account": "删除账户",
- "delete_account_description": "永久删除你的帐号和所有数据。",
- "delete_account_error": "删除账户时发生错误,如果一直删除不了,请联系实例管理员。",
- "delete_account_instructions": "在下面输入您的密码来确认删除账户。",
+ "delete_account": "删除账号",
+ "delete_account_description": "永久删除您的帐号和所有数据。",
+ "delete_account_error": "删除账号时发生错误。如果一直删除不了,请联系实例管理员。",
+ "delete_account_instructions": "在下面输入您的密码来确认删除账号。",
"avatar_size_instruction": "推荐的头像图片最小尺寸为 150x150 像素。",
"export_theme": "导出预置主题",
"filtering": "过滤器",
@@ -334,11 +392,11 @@
"autohide_floating_post_button": "自动隐藏新帖子的按钮(移动设备)",
"saving_err": "保存设置时发生错误",
"saving_ok": "设置已保存",
- "search_user_to_block": "搜索你想屏蔽的用户",
- "search_user_to_mute": "搜索你想要隐藏的用户",
+ "search_user_to_block": "搜索您想屏蔽的用户",
+ "search_user_to_mute": "搜索您想要隐藏的用户",
"security_tab": "安全",
"scope_copy": "回复时复制可见范围(私信中永远会复制)",
- "minimal_scopes_mode": "使发文可见范围的选项最少化",
+ "minimal_scopes_mode": "使帖子可见范围的选项最小化",
"set_new_avatar": "设置新头像",
"set_new_profile_background": "设置新的个人资料背景",
"set_new_profile_banner": "设置新的横幅图片",
@@ -348,7 +406,7 @@
"subject_line_email": "类似电子邮件: \"re: 主题\"",
"subject_line_mastodon": "类似 mastodon: 与原主题相同",
"subject_line_noop": "不要复制",
- "post_status_content_type": "发文状态内容类型",
+ "post_status_content_type": "帖子状态内容类型",
"stop_gifs": "鼠标悬停时播放GIF",
"streaming": "滚动到顶部时自动推送新内容",
"text": "文本",
@@ -492,7 +550,8 @@
"interface": "界面",
"input": "输入框",
"post": "发帖文字",
- "postCode": "帖子中使用等间距文字(富文本)"
+ "postCode": "帖子中使用等间距文字(富文本)",
+ "monospace": "等宽文本"
},
"family": "字体名称",
"size": "大小 (in px)",
@@ -512,7 +571,43 @@
"header_faint": "这很正常",
"checkbox": "我已经浏览了条款及细则",
"link": "一个棒棒的小小链接"
- }
+ },
+ "custom_theme_used": "(自定义主题)",
+ "themes2_outdated": "V2 主题的编辑器正在被淘汰并且最终会被新的利用 V3 主题引擎的编辑器取代。但是体验有可能会被降级并且不稳定。",
+ "appearance_tab_note": "在这个标签页的更改不会影响使用的主题,所以导出的主题会和界面显示的主题不同",
+ "update_preview": "更新预览",
+ "themes3": {
+ "define": "覆盖",
+ "hacks": {
+ "underlay_overrides": "更改底色",
+ "underlay_override_mode_none": "主题默认",
+ "underlay_override_mode_opaque": "使用单色更改",
+ "underlay_override_mode_transparent": "完全移除(有可能破外一些主题)",
+ "force_interface_roundness": "覆盖界面圆角/锐度",
+ "forced_roundness_mode_disabled": "使用主题默认",
+ "forced_roundness_mode_sharp": "强制使用锐利边角",
+ "forced_roundness_mode_nonsharp": "强制使用不太锋利(1px 圆角)的边角",
+ "forced_roundness_mode_round": "强制使用圆角"
+ },
+ "font": {
+ "group-builtin": "浏览器默认字体",
+ "builtin": {
+ "serif": "衬线字体",
+ "sans-serif": "无衬线字体",
+ "monospace": "等宽字体",
+ "inherit": "未更改"
+ },
+ "group-local": "本地字体",
+ "local-unavailable1": "不可用的本地字体列表",
+ "local-unavailable2": "使用手动输入来指定自定义字体",
+ "font_list_unavailable": "无法找到本地字体:{error}",
+ "lookup_local_fonts": "加载这台电脑的本地字体列表",
+ "enter_manually": "手动输入字体名称",
+ "entry": "输入 {fontFamily}",
+ "select": "选择字体"
+ }
+ },
+ "interface_font_user_override": "覆盖使用的主题/浏览器字体"
},
"version": {
"title": "版本",
@@ -528,12 +623,12 @@
"notification_setting_privacy_option": "在通知推送中隐藏发送者和内容",
"notification_setting_privacy": "隐私",
"hide_follows_count_description": "不显示关注数",
- "notification_visibility_emoji_reactions": "互动",
+ "notification_visibility_emoji_reactions": "回应",
"notification_visibility_moves": "用户迁移",
"new_email": "新邮箱",
- "emoji_reactions_on_timeline": "在时间线上显示表情符号互动",
+ "emoji_reactions_on_timeline": "在时间线上显示表情符号回应",
"notification_setting_hide_notification_contents": "隐藏推送通知中的发送者与内容信息",
- "notification_setting_block_from_strangers": "屏蔽来自你没有关注的用户的通知",
+ "notification_setting_block_from_strangers": "屏蔽来自您没有关注的用户的通知",
"type_domains_to_mute": "搜索需要隐藏的域名",
"useStreamingApi": "实时接收帖子和通知",
"user_mutes": "用户",
@@ -564,15 +659,15 @@
"mutes_imported": "隐藏名单导入成功!处理它们将需要一段时间。",
"mute_import_error": "导入隐藏名单出错",
"mute_import": "隐藏名单导入",
- "mute_export_button": "导出你的隐藏名单到一个 csv 文件",
+ "mute_export_button": "导出您的隐藏名单到一个 csv 文件",
"mute_export": "隐藏名单导出",
"hide_wallpaper": "隐藏实例壁纸",
"setting_changed": "与默认设置不同",
"more_settings": "更多设置",
- "sensitive_by_default": "默认标记发文为敏感内容",
+ "sensitive_by_default": "默认标记帖子为敏感内容",
"reply_visibility_self_short": "只显示对我本人的回复",
"reply_visibility_following_short": "显示对我关注的人的回复",
- "hide_all_muted_posts": "不显示已隐藏的发文",
+ "hide_all_muted_posts": "不显示已隐藏的帖子",
"hide_media_previews": "隐藏媒体预览",
"word_filter": "词语过滤",
"save": "保存更改",
@@ -588,8 +683,170 @@
"backup_settings": "备份设置到文件",
"backup_restore": "设置备份"
},
- "right_sidebar": "在右侧显示侧边栏",
- "hide_shoutbox": "隐藏实例留言板"
+ "right_sidebar": "反转分栏的顺序",
+ "hide_shoutbox": "隐藏实例留言板",
+ "expert_mode": "显示高级",
+ "download_backup": "下载",
+ "mention_links": "提及链接",
+ "account_backup": "账号备份",
+ "account_backup_table_head": "备份",
+ "remove_backup": "移除",
+ "list_backups_error": "获取备份列表出错:{error}",
+ "add_backup": "创建一个新备份",
+ "added_backup": "创建了一个新备份。",
+ "account_alias": "账号别名",
+ "account_alias_table_head": "别名",
+ "list_aliases_error": "获取别名时出错:{error}",
+ "hide_list_aliases_error_action": "关闭",
+ "remove_alias": "移除这个别名",
+ "new_alias_target": "添加一个新别名(例如 {example})",
+ "added_alias": "别名添加好了。",
+ "move_account": "移动账号",
+ "move_account_target": "目标账号(例如 {example})",
+ "moved_account": "账号移动好了。",
+ "move_account_error": "移动账号时出错:{error}",
+ "setting_server_side": "这个设置是捆绑到您的个人资料的,能影响所有会话和客户端",
+ "post_look_feel": "文章的样子跟感受",
+ "email_language": "从服务器收邮件的语言",
+ "account_backup_description": "这个允许您下载一份账号信息和文章的存档,但是现在还不能导入到 Pleroma 账号里。",
+ "backup_not_ready": "备份还没准备好。",
+ "add_backup_error": "添加新备份时出错:{error}",
+ "add_alias_error": "添加别名时出错:{error}",
+ "move_account_notes": "如果您想把账号移动到别的地方,您必须去目标账号,然后加一个指向这里的别名。",
+ "wordfilter": "词语过滤器",
+ "user_profiles": "用户资料",
+ "third_column_mode_notifications": "通知栏",
+ "backup_running": "此备份正在进行,已处理 {number} 条记录。 |此备份正在进行,已处理 {number} 条记录。",
+ "lists_navigation": "在导航中显示列表",
+ "word_filter_and_more": "词过滤器及其它...",
+ "backup_failed": "此备份已失败。",
+ "birthday": {
+ "label": "生日",
+ "show_birthday": "展示我的生日"
+ },
+ "hide_favorites_description": "不显示我的喜欢列表(人们仍然会收到通知)",
+ "third_column_mode": "当有足够的空间时,显示第三栏包含",
+ "third_column_mode_postform": "主要的帖子形式和导航",
+ "columns": "分栏",
+ "user_popover_avatar_overlay": "在用户头像上显示用户弹出窗口",
+ "navbar_column_stretch": "延伸导航栏至分栏宽度",
+ "posts": "帖子",
+ "conversation_display_linear_quick": "线性视图",
+ "conversation_other_replies_button": "显示 “其它回复” 按钮",
+ "confirm_dialogs_delete": "删除状态",
+ "confirm_dialogs_mute": "隐藏用户",
+ "column_sizes": "分栏大小",
+ "column_sizes_sidebar": "侧栏",
+ "column_sizes_content": "內容",
+ "column_sizes_notifs": "通知",
+ "conversation_other_replies_button_below": "在状态下方",
+ "conversation_other_replies_button_inside": "在状态中",
+ "auto_update": "自动显示新的帖子",
+ "use_websockets": "使用 websockets(实时更新)",
+ "max_depth_in_thread": "默认显示同主题帖子中的最大层数",
+ "hide_wordfiltered_statuses": "隐藏经过词语过滤的状态",
+ "hide_muted_threads": "不显示已隐藏的同主题帖子",
+ "notification_visibility_polls": "您所投的投票的结束于",
+ "tree_advanced": "允许在树状视图中进行更灵活的导航",
+ "tree_fade_ancestors": "以模糊的文字显示当前状态的上级",
+ "conversation_display_linear": "线性样式",
+ "mention_link_fade_domain": "淡化域名(例如:{'@'}example.org 中的 {'@'}foo{'@'}example.org)",
+ "mention_link_bolden_you": "当您被提及时突出显示提及您",
+ "user_popover_avatar_action": "弹出式头像点击动作",
+ "user_popover_avatar_action_zoom": "缩放头像",
+ "user_popover_avatar_action_close": "关闭弹出窗口",
+ "show_yous": "显示 (You)s",
+ "add_language": "添加备用语言",
+ "remove_language": "移除",
+ "primary_language": "主要语言:",
+ "fallback_language": "备用语言 {index}:",
+ "account_privacy": "隐私",
+ "conversation_display": "对话显示样式",
+ "conversation_display_tree": "树状样式",
+ "conversation_display_tree_quick": "树状视图",
+ "disable_sticky_headers": "不要把分栏的顶栏固定在屏幕的顶部",
+ "confirm_dialogs": "请求确认于",
+ "confirm_dialogs_logout": "登出",
+ "confirm_dialogs_deny_follow": "拒绝关注请求",
+ "confirm_dialogs_approve_follow": "批准关注请求",
+ "confirm_dialogs_block": "屏蔽用户",
+ "confirm_dialogs_unfollow": "取消关注用户",
+ "confirm_dialogs_repeat": "转发状态",
+ "confirm_dialogs_remove_follower": "移除关注者",
+ "mute_bot_posts": "隐藏机器人的帖子",
+ "hide_bot_indication": "隐藏帖子中的机器人提示",
+ "always_show_post_button": "始终显示浮动的新帖子按钮",
+ "show_scrollbars": "显示侧栏的滚动条",
+ "third_column_mode_none": "完全不显示第三栏",
+ "use_at_icon": "将 {'@'} 符号显示为图标而不是文本",
+ "mention_link_display": "显示提及链接",
+ "mention_link_display_short": "始终以简称的形式出现(例如:{'@'}foo)",
+ "mention_link_display_full_for_remote": "仅远程实例用户以全名的形式出现(例如:{'@'}foo{'@'}example.org)",
+ "mention_link_display_full": "始终以全名的形式出现(例如:{'@'}foo{'@'}example.org)",
+ "mention_link_use_tooltip": "点击提及链接时显示用户卡片",
+ "mention_link_show_avatar": "在链接旁边显示用户头像",
+ "mention_link_show_avatar_quick": "在提及内容旁边显示用户头像",
+ "user_popover_avatar_action_open": "打开个人资料",
+ "autocomplete_select_first": "当有自动完成的结果时,自动选择第一个候选项",
+ "url": "URL",
+ "preview": "预览",
+ "commit_value": "保存",
+ "commit_value_tooltip": "当前值未保存,请按此按钮以提交您的修改",
+ "reset_value": "重置",
+ "reset_value_tooltip": "重置草稿",
+ "hard_reset_value": "硬重置",
+ "hard_reset_value_tooltip": "从存储中移除设置,强制使用默认值",
+ "emoji_reactions_scale": "表情回应比例系数",
+ "notification_show_extra": "在通知栏里显示额外通知",
+ "notification_extra_chats": "显示未读聊天",
+ "notification_extra_announcements": "显示未读公告",
+ "notification_extra_follow_requests": "显示新的关注请求",
+ "notification_extra_tip": "显示额外通知的定制提示",
+ "notification_visibility_follow_requests": "关注请求",
+ "notification_visibility_reports": "举报",
+ "mute_sensitive_posts": "隐藏敏感帖子",
+ "notification_visibility_in_column": "在侧栏/菜单显示通知菜单",
+ "notification_visibility_native_notifications": "显示本地通知",
+ "units": {
+ "time": {
+ "m": "分钟",
+ "s": "秒",
+ "h": "小时",
+ "d": "天"
+ }
+ },
+ "hide_scrobbles_after": "隐藏比这个时间更早的 scrobble",
+ "notification_setting_ignore_inactionable_seen": "忽略无法回复通知(喜欢,转发等)的已阅状态",
+ "notification_setting_unseen_at_top": "将未读通知置顶",
+ "notification_setting_ignore_inactionable_seen_tip": "如果您继续,这将不会标记这些通知为已读,并且您仍会接收到桌面推送通知",
+ "actor_type": "账号:",
+ "actor_type_description": "将您的账号标记为组会使其转发所有提及它的状态。",
+ "actor_type_Person": "正常用户",
+ "actor_type_Service": "机器人",
+ "actor_type_Group": "组",
+ "hide_actor_type_indication": "隐藏帖子中账号类型(机器人,组等)的表示",
+ "notification_setting_annoyance": "烦扰",
+ "notification_setting_drawer_marks_as_seen": "关闭菜单(移动端)来标记全部通知为已阅",
+ "enable_web_push_always_show_tip": "一些浏览器(Chromium,Chrome)需要推送信息才能显示通知,否则会显示“网页在背景发生了更改”的通知,勾选这个选项可以防止这种通知显示,因为 Chrome 在标签页激活时会隐藏网页推送通知。可能会在其他浏览器中显示双重通知。",
+ "enable_web_push_always_show": "总是显示网页推送通知",
+ "force_theme_recompilation_debug": "禁用主题缓存,强制在每次启动时重新编译(调试)",
+ "notification_setting_filters_chrome_push": "在一些浏览器中(Chrome),有可能无法完全按照类型过滤通过推送传递的通知",
+ "hide_scrobbles": "隐藏 scrobble",
+ "appearance": "外观",
+ "confirm_new_setting": "确认新的设置?",
+ "confirm_new_question": "是否保留这些设置?设置将在 10 秒后还原。",
+ "revert": "恢复",
+ "confirm": "确定",
+ "text_size": "文字与界面大小",
+ "text_size_tip": "用 {0} 作为绝对值,{1} 会根据浏览器默认文字大小进行缩放。",
+ "text_size_tip2": "{0} 之外的值可能会破坏一些功能和主题",
+ "emoji_size": "表情符号大小",
+ "navbar_size": "顶栏大小",
+ "panel_header_size": "面板标题大小",
+ "visual_tweaks": "细微外观调整",
+ "theme_debug": "显示当遇到透明背景时背景主题引擎的假设(调试)",
+ "scale_and_layout": "界面大小与布局",
+ "notification_visibility_statuses": "订阅"
},
"time": {
"day": "{0} 天",
@@ -623,7 +880,23 @@
"year": "{0} 年",
"years": "{0} 年",
"year_short": "{0}y",
- "years_short": "{0}y"
+ "years_short": "{0}y",
+ "unit": {
+ "days_short": "{0} 天",
+ "hours": "{0} 小时",
+ "hours_short": "{0} 时",
+ "minutes": "{0} 分",
+ "minutes_short": "{0} 分",
+ "months": "{0} 个月",
+ "months_short": "{0} 月",
+ "seconds": "{0} 秒",
+ "seconds_short": "{0} 秒",
+ "weeks_short": "{0} 周",
+ "years": "{0} 年",
+ "years_short": "{0} 年",
+ "weeks": "{0} 周",
+ "days": "{0} 天"
+ }
},
"timeline": {
"collapse": "折叠",
@@ -639,7 +912,9 @@
"reload": "重新载入",
"error": "取得时间轴时发生错误:{0}",
"socket_broke": "丢失实时连接:CloseEvent code {0}",
- "socket_reconnected": "已建立实时连接"
+ "socket_reconnected": "已建立实时连接",
+ "quick_view_settings": "快速视图设置",
+ "quick_filter_settings": "快速过滤设置"
},
"status": {
"favorites": "喜欢",
@@ -648,7 +923,7 @@
"pin": "在个人资料置顶",
"unpin": "取消在个人资料置顶",
"pinned": "置顶",
- "delete_confirm": "你真的想要删除这条状态吗?",
+ "delete_confirm": "您确定要删除这条状态吗?",
"reply_to": "回复",
"replies_list": "回复:",
"mute_conversation": "隐藏对话",
@@ -657,7 +932,7 @@
"show_content": "显示内容",
"hide_full_subject": "隐藏此部分标题",
"show_full_subject": "显示全部标题",
- "thread_muted": "此系列消息已被隐藏",
+ "thread_muted": "同主题帖子已被隐藏",
"copy_link": "复制状态链接",
"status_unavailable": "状态不可取得",
"unbookmark": "取消书签",
@@ -666,7 +941,53 @@
"status_deleted": "该状态已被删除",
"nsfw": "NSFW",
"external_source": "外部来源",
- "expand": "展开"
+ "expand": "展开",
+ "you": "(您)",
+ "plus_more": "还有 {number} 个",
+ "many_attachments": "文章有 {number} 个附件",
+ "collapse_attachments": "折起附件",
+ "show_all_attachments": "显示所有附件",
+ "show_attachment_description": "预览描述(打开附件能看完整描述)",
+ "hide_attachment": "隐藏附件",
+ "remove_attachment": "移除附件",
+ "attachment_stop_flash": "停止 Flash 播放器",
+ "move_up": "把附件左移",
+ "open_gallery": "打开图库",
+ "thread_hide": "隐藏这个同主题帖子",
+ "thread_show": "显示这个同主题帖子",
+ "thread_show_full_with_icon": "{icon} {text}",
+ "thread_follow": "查看这个同主题帖子的剩余部分(一共有 {numStatus} 个状态)",
+ "thread_follow_with_icon": "{icon} {text}",
+ "ancestor_follow": "查看这个状态下的别的 {numReplies} 个回复",
+ "ancestor_follow_with_icon": "{icon} {text}",
+ "show_all_conversation_with_icon": "{icon} {text}",
+ "show_all_conversation": "显示完整对话(还有 {numStatus} 个状态)",
+ "mentions": "提及",
+ "replies_list_with_others": "回复(另外 +{numReplies} 个):",
+ "move_down": "把附件右移",
+ "thread_show_full": "显示这个同主题帖子下的所有东西(一共有 {numStatus} 个状态,最大深度 {depth})",
+ "show_only_conversation_under_this": "只显示这个状态的回复",
+ "repeat_confirm": "您确定要转发这条状态吗?",
+ "repeat_confirm_title": "确认转发",
+ "repeat_confirm_accept_button": "转发",
+ "repeat_confirm_cancel_button": "不要转发",
+ "edit": "编辑状态",
+ "edited_at": "(最后编辑于 {time})",
+ "delete_confirm_title": "确认删除",
+ "delete_confirm_accept_button": "删除",
+ "delete_confirm_cancel_button": "保留",
+ "show_attachment_in_modal": "在媒体模式中显示",
+ "status_history": "状态历史",
+ "delete_error": "删除状态时出错:{0}",
+ "reaction_count_label": "{num} 人作出了表情回应",
+ "invisible_quote": "引用的状态不可用:{link}",
+ "hide_quote": "隐藏引用的状态",
+ "display_quote": "显示引用的状态",
+ "quotes": "引用",
+ "sensitive_muted": "正在隐藏敏感内容",
+ "loading": "加载中...",
+ "load_error": "无法加载动态:{error}",
+ "more_actions": "状态的更多动作"
},
"user_card": {
"approve": "核准",
@@ -681,14 +1002,14 @@
"followees": "正在关注",
"followers": "关注者",
"following": "正在关注!",
- "follows_you": "关注了你!",
- "its_you": "就是你!",
+ "follows_you": "关注了您!",
+ "its_you": "就是您!",
"media": "媒体",
"mute": "隐藏",
"muted": "已隐藏",
"per_day": "每天",
"remote_follow": "跨站关注",
- "report": "报告",
+ "report": "举报",
"statuses": "状态",
"subscribe": "订阅",
"unsubscribe": "退订",
@@ -713,8 +1034,9 @@
"sandbox": "强制帖子为只有关注者可看",
"disable_remote_subscription": "禁止从远程实例关注用户",
"disable_any_subscription": "完全禁止关注用户",
- "quarantine": "从联合实例中禁止用户帖子",
- "delete_user": "删除用户"
+ "quarantine": "不许帖子传入别站",
+ "delete_user": "删除用户",
+ "delete_user_data_and_deactivate_confirmation": "这将永久删除该账号的数据并停用该账号。您完全确定吗?"
},
"hidden": "已隐藏",
"show_repeats": "显示转发",
@@ -728,7 +1050,42 @@
"solid": "单一颜色背景",
"disabled": "不突出显示"
},
- "edit_profile": "编辑个人资料"
+ "edit_profile": "编辑个人资料",
+ "approve_confirm_title": "确认批准",
+ "approve_confirm_accept_button": "批准",
+ "block_confirm_accept_button": "屏蔽",
+ "block_confirm_cancel_button": "不要屏蔽",
+ "deactivated": "已停用",
+ "deny_confirm_title": "确认拒绝",
+ "deny_confirm_accept_button": "拒绝",
+ "deny_confirm_cancel_button": "不要拒绝",
+ "deny_confirm": "您是否要拒绝 {user} 的关注请求?",
+ "follow_cancel": "取消请求",
+ "unfollow_confirm_title": "确认取消关注",
+ "unfollow_confirm": "您确定要取消关注 {user} 吗?",
+ "unfollow_confirm_accept_button": "取消关注",
+ "unfollow_confirm_cancel_button": "不要取消关注",
+ "mute_confirm_title": "确认隐藏",
+ "mute_confirm_accept_button": "隐藏",
+ "mute_confirm_cancel_button": "不要隐藏",
+ "mute_duration_prompt": "让这个用户隐藏(0表示无限期):",
+ "remove_follower": "移除关注者",
+ "remove_follower_confirm_title": "确认移除关注者",
+ "remove_follower_confirm_cancel_button": "保留",
+ "remove_follower_confirm": "您确定要将 {user} 从您的关注者里移除吗?",
+ "birthday": "生于 {birthday}",
+ "note": "备注",
+ "approve_confirm_cancel_button": "不要批准",
+ "approve_confirm": "您是否要批准 {user} 的关注请求?",
+ "block_confirm_title": "确认屏蔽",
+ "block_confirm": "您确定要屏蔽 {user} 吗?",
+ "mute_confirm": "您确定要隐藏 {user} 吗?",
+ "remove_follower_confirm_accept_button": "移除",
+ "note_blank": "(空)",
+ "edit_note": "编辑备注",
+ "edit_note_apply": "应用",
+ "edit_note_cancel": "取消",
+ "group": "组"
},
"user_profile": {
"timeline_title": "用户时间线",
@@ -736,10 +1093,10 @@
"profile_loading_error": "抱歉,载入个人资料时出错。"
},
"user_reporting": {
- "title": "报告 {0}",
- "add_comment_description": "此报告会发送给您的实例监察员。您可以在下面提供更多详细信息解释报告的缘由:",
+ "title": "举报 {0}",
+ "add_comment_description": "此举报会发送给您的实例监察员。您可以在下面提供更多详细信息解释举报的缘由:",
"additional_comments": "其它信息",
- "forward_description": "这个账号来自另一个服务器。是否同时发送一份报告副本到那里?",
+ "forward_description": "这个账号来自另一个服务器。是否同时发送一份举报副本到那里?",
"forward_to": "转发 {0}",
"submit": "提交",
"generic_error": "当处理您的请求时,发生了一个错误。"
@@ -755,9 +1112,12 @@
"favorite": "喜欢",
"user_settings": "用户设置",
"reject_follow_request": "拒绝关注请求",
- "add_reaction": "添加互动",
+ "add_reaction": "添加回应",
"bookmark": "书签",
- "accept_follow_request": "接受关注请求"
+ "accept_follow_request": "接受关注请求",
+ "toggle_expand": "展开或折叠通知以显示帖子全文",
+ "toggle_mute": "展开或折叠通知以显示已隐藏的内容",
+ "autocomplete_available": "共有 {number} 个结果可用。使用向上和向下键浏览它们。"
},
"upload": {
"error": {
@@ -779,7 +1139,9 @@
"hashtags": "话题标签",
"person_talking": "{count} 人正在讨论",
"people_talking": "{count} 人正在讨论",
- "no_results": "没有搜索结果"
+ "no_results": "没有搜索结果",
+ "no_more_results": "没有更多结果",
+ "load_more": "加载更多结果"
},
"password_reset": {
"forgot_password": "忘记密码了?",
@@ -807,7 +1169,21 @@
"search_emoji": "搜索表情符号",
"emoji": "表情符号",
"load_all": "加载所有表情符号(共 {emojiAmount} 个)",
- "load_all_hint": "最先加载的 {saneAmount} 表情符号,加载全部表情符号可能会带来性能问题。"
+ "load_all_hint": "最先加载的 {saneAmount} 表情符号,加载全部表情符号可能会带来性能问题。",
+ "unicode_groups": {
+ "flags": "旗帜",
+ "food-and-drink": "饮食",
+ "objects": "物件",
+ "people-and-body": "人和身体",
+ "symbols": "符号",
+ "travel-and-places": "旅行和地点",
+ "activities": "活动",
+ "animals-and-nature": "动物和自然",
+ "smileys-and-emotion": "表情与情感"
+ },
+ "regional_indicator": "地区指示符 {letter}",
+ "unpacked": "未分组的表情符号",
+ "hide_custom_emoji": "隐藏自定义表情符号"
},
"about": {
"mrf": {
@@ -824,7 +1200,10 @@
"media_nsfw": "强制设置媒体为敏感内容",
"media_removal_desc": "本实例移除来自以下实例的媒体内容:",
"ftl_removal_desc": "该实例在从“已知网络”时间线上移除了下列实例:",
- "ftl_removal": "从“已知网络”时间线上移除"
+ "ftl_removal": "从“已知网络”时间线上移除",
+ "reason": "理由",
+ "not_applicable": "无",
+ "instance": "实例"
},
"mrf_policies_desc": "MRF 策略会影响本实例的互通行为。以下策略已启用:",
"mrf_policies": "已启用的 MRF 策略",
@@ -864,13 +1243,210 @@
"empty_chat_list_placeholder": "您还没有任何聊天记录。开始聊天吧!",
"error_sending_message": "发送消息时出了点问题。",
"error_loading_chat": "加载聊天时出了点问题。",
- "delete_confirm": "您确实要删除此消息吗?",
+ "delete_confirm": "您确定要删除此消息吗?",
"more": "更多",
"empty_message_error": "无法发布空消息",
"new": "新聊天",
"chats": "聊天",
"delete": "删除",
"message_user": "发消息给 {nickname}",
- "you": "你:"
+ "you": "您:"
+ },
+ "announcements": {
+ "page_header": "公告",
+ "title": "公告",
+ "mark_as_read_action": "标为已读",
+ "post_form_header": "发布公告",
+ "post_placeholder": "在这里输入公告内容...",
+ "post_action": "发布",
+ "post_error": "错误:{error}",
+ "close_error": "关闭",
+ "delete_action": "删除",
+ "start_time_prompt": "起始时间: ",
+ "end_time_prompt": "终止时间: ",
+ "all_day_prompt": "这是全天的事件",
+ "published_time_display": "发表于 {time}",
+ "start_time_display": "开始于 {time}",
+ "end_time_display": "结束于 {time}",
+ "edit_action": "编辑",
+ "submit_edit_action": "提交",
+ "cancel_edit_action": "取消",
+ "inactive_message": "这个公告不活跃"
+ },
+ "report": {
+ "reported_user": "被举报者:",
+ "state_closed": "已关闭",
+ "state_resolved": "已解决",
+ "reporter": "举报者:",
+ "state_open": "开启",
+ "reported_statuses": "已举报的状态:",
+ "notes": "备注:",
+ "state": "状态:"
+ },
+ "unicode_domain_indicator": {
+ "tooltip": "此域名包含非 ascii 字符。"
+ },
+ "update": {
+ "update_bugs_gitlab": "Pleroma GitLab",
+ "update_changelog": "关于变化的更多细节,请参见 {theFullChangelog} 。",
+ "update_changelog_here": "完整的更新日志",
+ "big_update_title": "请忍耐一下",
+ "big_update_content": "我们已经有一段时间没有发布发行版,所以事情的外观和感觉可能与您习惯的不一样。",
+ "update_bugs": "请在 {pleromaGitlab} 上报告任何问题和 bug,因为我们改变了软件中的很多东西,虽然我们进行了彻底的测试,并且我们自己使用开发版本,但我们可能错过了一些东西。我们欢迎您对您可能遇到的问题或如何改进 Pleroma 和 Pleroma-FE 提出反馈和建议。",
+ "art_by": "{linkToArtist} 的作品"
+ },
+ "lists": {
+ "search": "搜索用户",
+ "create": "创建",
+ "save": "保存更改",
+ "delete": "删除列表",
+ "following_only": "限制于正在关注",
+ "manage_lists": "管理列表",
+ "manage_members": "管理列表成员",
+ "add_members": "搜索更多用户",
+ "remove_from_list": "从列表中移除",
+ "add_to_list": "添加到列表",
+ "is_in_list": "已在列表中",
+ "editing_list": "正在编辑列表 {listTitle}",
+ "creating_list": "正在创建新的列表",
+ "update_title": "保存标题",
+ "really_delete": "真的要删除列表吗?",
+ "error": "操作列表时出错:{0}",
+ "lists": "列表",
+ "new": "新的列表",
+ "title": "列表标题"
+ },
+ "admin_dash": {
+ "window_title": "管理员",
+ "old_ui_link": "旧的管理界面在此处",
+ "reset_all": "重置全部",
+ "commit_all": "保存全部",
+ "tabs": {
+ "nodb": "无数据库配置",
+ "instance": "实例",
+ "limits": "限制",
+ "frontends": "前端",
+ "emoji": "表情符号"
+ },
+ "nodb": {
+ "heading": "数据库配置已禁用",
+ "documentation": "文档",
+ "text2": "大多数配置选项将不可用。",
+ "text": "您需要修改后端配置文件,以便将 {property} 设置为 {value},更多内容请参见 {documentation}。"
+ },
+ "captcha": {
+ "native": "本地",
+ "kocaptcha": "KoCaptcha"
+ },
+ "instance": {
+ "instance": "实例信息",
+ "registrations": "用户注册",
+ "captcha_header": "验证码",
+ "kocaptcha": "KoCaptcha 设置",
+ "access": "实例访问",
+ "restrict": {
+ "header": "限制匿名访客的访问",
+ "timelines": "时间线访问",
+ "profiles": "用户个人资料访问",
+ "activities": "状态/活动访问",
+ "description": "允许/不允许访问特定 API 的详细设置。默认情况下(不确定状态),如果实例不是公开的,它将拒绝访问;勾选复选框意味着即使实例是公开的,也拒绝访问;不勾选意味着即使实例是私有的,也允许访问。请注意,如果某些设置被设定,可能会发生意想不到的行为,例如,如果个人资料访问被禁用,显示的帖文将不包含个人资料信息。"
+ }
+ },
+ "limits": {
+ "arbitrary_limits": "任意限制",
+ "posts": "帖文限制",
+ "uploads": "附件限制",
+ "users": "用户个人资料限制",
+ "profile_fields": "个人资料字段限制",
+ "user_uploads": "个人资料媒体限制"
+ },
+ "frontend": {
+ "repository": "存储库链接",
+ "versions": "可用版本",
+ "build_url": "构建产物 URL",
+ "reinstall": "重新安装",
+ "is_default": "(默认)",
+ "is_default_custom": "(默认,版本:{version})",
+ "install": "安装",
+ "install_version": "安装版本 {version}",
+ "more_install_options": "更多安装选项",
+ "more_default_options": "更多默认设置选项",
+ "set_default": "设为默认",
+ "set_default_version": "将版本 {version} 设为默认",
+ "wip_notice": "请注意,此部分是一个WIP,缺乏某些功能,因为前端管理的后台实现并不完整。",
+ "default_frontend": "默认前端",
+ "default_frontend_tip": "默认的前端将显示给所有用户。目前还没有办法让用户选择自己的前端。如果您不使用 PleromaFE,您很可能不得不使用旧的和有问题的 AdminFE 来进行实例配置,直到我们替换它。",
+ "available_frontends": "可供安装",
+ "failure_installing_frontend": "无法安装前端 {version}:{reason}",
+ "success_installing_frontend": "前端 {version} 成功安装",
+ "default_frontend_unavail": "默认前端设置不可以,因为这需要数据库中的配置"
+ },
+ "temp_overrides": {
+ ":pleroma": {
+ ":instance": {
+ ":public": {
+ "label": "实例是公开的",
+ "description": "禁用此功能将使所有的 API 只能被已登录用户访问,这将使公共和联邦时间线无法被匿名访客访问。"
+ },
+ ":limit_to_local_content": {
+ "label": "将搜索限于本地内容",
+ "description": "禁用未认证用户(默认)、所有用户或无人的全局网络搜索"
+ },
+ ":description_limit": {
+ "label": "限制",
+ "description": "附件描述的字数限制"
+ },
+ ":background_image": {
+ "label": "背景图片",
+ "description": "背景图片(主要使用于 PleromaFE)"
+ }
+ }
+ }
+ },
+ "wip_notice": "此管理仪表板是实验性和 WIP 的,{adminFeLink}。",
+ "emoji": {
+ "remote_pack_instance": "远程表情包实例",
+ "fallback_src": "回退源",
+ "fallback_sha256": "回退 SHA256",
+ "delete_confirm": "您确定要删除 {0} 吗?",
+ "download_pack": "下载表情包",
+ "files": "文件",
+ "downloading_pack": "正在下载 {0}",
+ "download": "下载",
+ "download_as_name": "新名称",
+ "download_as_name_full": "新名称,留空来使用旧的名称",
+ "emoji_changed": "未保存的表情符号文件更改,检查突出显示的的表情符号",
+ "replace_warning": "这将替换本地同名的表情包",
+ "reload": "重新加载表情符号",
+ "create_pack": "创建表情包",
+ "emoji_pack": "表情包",
+ "save_meta": "保存元数据",
+ "delete": "删除",
+ "revert": "恢复",
+ "add_file": "添加文件",
+ "adding_new": "添加新的表情符号",
+ "shortcode": "简码",
+ "filename": "文件名",
+ "new_shortcode": "简码,留空来自动推断",
+ "emoji_packs": "表情包",
+ "remote_packs": "远程表情包",
+ "do_list": "列表",
+ "edit_pack": "编辑表情包",
+ "description": "描述",
+ "global_actions": "全局动作",
+ "importFS": "从文件系统导入表情符号",
+ "error": "错误:{0}",
+ "delete_pack": "删除表情包",
+ "new_pack_name": "新的表情包名称",
+ "create": "创建",
+ "homepage": "主页",
+ "share": "分享",
+ "save": "保存",
+ "revert_meta": "回复元数据",
+ "new_filename": "文件名,留空来自动推断",
+ "editing": "正在编辑 {0}",
+ "delete_title": "确定删除?",
+ "metadata_changed": "元数据和保存的不同"
+ }
}
}
diff --git a/src/i18n/zh_Hant.json b/src/i18n/zh_Hant.json
@@ -113,12 +113,23 @@
"submit": "提交",
"apply": "應用",
"role": {
- "moderator": "主持人",
+ "moderator": "審查者",
"admin": "管理員"
},
"flash_content": "點擊以使用 Ruffle 顯示 Flash 內容(實驗性,可能無效)。",
"flash_security": "請注意,這可能有潜在的危險,因為Flash內容仍然是武斷的程式碼。",
- "flash_fail": "無法加載flash內容,請參閱控制台瞭解詳細資訊。"
+ "flash_fail": "無法加載flash內容,請參閱控制台瞭解詳細資訊。",
+ "no": "否",
+ "generic_error_message": "發生了一個錯誤: {0}",
+ "never_show_again": "不再顯示",
+ "yes": "是",
+ "undo": "復原",
+ "scroll_to_top": "滾動至頂部",
+ "pin": "置頂",
+ "scope_in_timeline": {
+ "private": "僅關注者"
+ },
+ "unpin": "停止置頂"
},
"finder": {
"find_user": "尋找用戶",
@@ -133,7 +144,8 @@
"pleroma_chat_messages": "Pleroma 聊天",
"chat": "聊天",
"gopher": "Gopher",
- "upload_limit": "上傳限制"
+ "upload_limit": "上傳限制",
+ "shout": "留言板"
},
"exporter": {
"processing": "正在處理,稍後會提示您下載文件",
@@ -164,11 +176,14 @@
"reject": "拒絕",
"accept_desc": "本實例只接收來自下列實例的消息:",
"simple_policies": "站規",
- "accept": "接受"
+ "accept": "接受",
+ "instance": "實例",
+ "reason": "原因",
+ "not_applicable": "N/A"
},
"mrf_policies_desc": "MRF 策略會影響本實例的互通行為。以下策略已啟用:",
"keyword": {
- "ftl_removal": "從“全部已知網絡”時間線上移除",
+ "ftl_removal": "從「全部已知網絡」時間線上移除",
"replace": "取代",
"reject": "拒絕",
"is_replaced_by": "→",
@@ -531,7 +546,7 @@
"backend_version": "後端版本",
"frontend_version": "前端版本"
},
- "virtual_scrolling": "優化時間線渲染",
+ "virtual_scrolling": "最佳化時間軸算繪",
"import_mutes_from_a_csv_file": "從CSV文件導入靜音",
"mutes_imported": "靜音導入了!處理它們將需要一段時間。",
"mute_import": "靜音導入",
@@ -561,7 +576,10 @@
},
"sensitive_by_default": "默認標記發文為敏感內容",
"right_sidebar": "在右側顯示側邊欄",
- "hide_shoutbox": "隱藏實例留言框"
+ "hide_shoutbox": "隱藏實例留言框",
+ "mention_link_display_short": "總是使用短名(如: {'@'}foo)",
+ "mention_link_display": "顯式提及連結",
+ "use_at_icon": "將{'@'}改用圖標顯示,不用文字"
},
"chats": {
"more": "更多",
@@ -865,5 +883,26 @@
"password_reset_disabled": "密碼重置已經被禁用。請聯繫你的實例管理員。",
"password_reset_required": "您必須重置密碼才能登陸。",
"password_reset_required_but_mailer_is_disabled": "您必須重置密碼,但是密碼重置被禁用了。請聯繫您所在實例的管理員。"
+ },
+ "announcements": {
+ "post_error": "錯誤: {error}",
+ "close_error": "關閉",
+ "delete_action": "刪除",
+ "start_time_prompt": "開始時間: ",
+ "end_time_prompt": "結束時間: ",
+ "all_day_prompt": "這是全日活動",
+ "start_time_display": "{time} 開始",
+ "end_time_display": "{time} 結束",
+ "published_time_display": "{time} 發布",
+ "edit_action": "編輯",
+ "submit_edit_action": "送出",
+ "cancel_edit_action": "取消",
+ "inactive_message": "此公告無效",
+ "page_header": "公告",
+ "title": "公告",
+ "mark_as_read_action": "標示為以閱讀",
+ "post_placeholder": "在此輸入您的公告內容……",
+ "post_form_header": "發布公告",
+ "post_action": "發布"
}
}
diff --git a/src/lib/notification-i18n-loader.js b/src/lib/notification-i18n-loader.js
@@ -3,8 +3,8 @@
// meant to be used to load the partial i18n we need for
// the service worker.
module.exports = function (source) {
- var object = JSON.parse(source)
- var smol = {
+ const object = JSON.parse(source)
+ const smol = {
notifications: object.notifications || {}
}
diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js
@@ -5,16 +5,19 @@ import { each, get, set, cloneDeep } from 'lodash'
let loaded = false
const defaultReducer = (state, paths) => (
- paths.length === 0 ? state : paths.reduce((substate, path) => {
- set(substate, path, get(state, path))
- return substate
- }, {})
+ paths.length === 0
+ ? state
+ : paths.reduce((substate, path) => {
+ set(substate, path, get(state, path))
+ return substate
+ }, {})
)
const saveImmedeatelyActions = [
'markNotificationsAsSeen',
'clearCurrentUser',
'setCurrentUser',
+ 'setServerSideStorage',
'setHighlight',
'setOption',
'setClientData',
@@ -30,12 +33,12 @@ export default function createPersistedState ({
key = 'vuex-lz',
paths = [],
getState = (key, storage) => {
- let value = storage.getItem(key)
+ const value = storage.getItem(key)
return value
},
setState = (key, state, storage) => {
if (!loaded) {
- console.log('waiting for old state to be loaded...')
+ console.info('waiting for old state to be loaded...')
return Promise.resolve()
} else {
return storage.setItem(key, state)
@@ -62,7 +65,7 @@ export default function createPersistedState ({
}
loaded = true
} catch (e) {
- console.log("Couldn't load state")
+ console.error("Couldn't load state")
console.error(e)
loaded = true
}
@@ -83,8 +86,8 @@ export default function createPersistedState ({
})
}
} catch (e) {
- console.log("Couldn't persist state:")
- console.log(e)
+ console.error("Couldn't persist state:")
+ console.error(e)
}
})
}
diff --git a/src/main.js b/src/main.js
@@ -6,10 +6,14 @@ import './lib/event_target_polyfill.js'
import interfaceModule from './modules/interface.js'
import instanceModule from './modules/instance.js'
import statusesModule from './modules/statuses.js'
+import notificationsModule from './modules/notifications.js'
+import listsModule from './modules/lists.js'
import usersModule from './modules/users.js'
import apiModule from './modules/api.js'
import configModule from './modules/config.js'
-import serverSideConfigModule from './modules/serverSideConfig.js'
+import profileConfigModule from './modules/profileConfig.js'
+import serverSideStorageModule from './modules/serverSideStorage.js'
+import adminSettingsModule from './modules/adminSettings.js'
import shoutModule from './modules/shout.js'
import oauthModule from './modules/oauth.js'
import authFlowModule from './modules/auth_flow.js'
@@ -18,7 +22,11 @@ import oauthTokensModule from './modules/oauth_tokens.js'
import reportsModule from './modules/reports.js'
import pollsModule from './modules/polls.js'
import postStatusModule from './modules/postStatus.js'
+import editStatusModule from './modules/editStatus.js'
+import statusHistoryModule from './modules/statusHistory.js'
import chatsModule from './modules/chats.js'
+import announcementsModule from './modules/announcements.js'
+import bookmarkFoldersModule from './modules/bookmark_folders.js'
import { createI18n } from 'vue-i18n'
@@ -38,10 +46,11 @@ const i18n = createI18n({
messages: messages.default
})
-messages.setLanguage(i18n, currentLocale)
+messages.setLanguage(i18n.global, currentLocale)
const persistedStateOptions = {
paths: [
+ 'serverSideStorage.cache',
'config',
'users.lastLoginName',
'oauth'
@@ -49,48 +58,87 @@ const persistedStateOptions = {
};
(async () => {
- let storageError = false
- const plugins = [pushNotifications]
- try {
- const persistedState = await createPersistedState(persistedStateOptions)
- plugins.push(persistedState)
- } catch (e) {
- console.error(e)
- storageError = true
+ const isFox = Math.floor(Math.random() * 2) > 0 ? '_fox' : ''
+
+ const splashError = (i18n, e) => {
+ const throbber = document.querySelector('#throbber')
+ throbber.addEventListener('animationend', () => {
+ document.querySelector('#mascot').src = `/static/pleromatan_orz${isFox}.png`
+ })
+ throbber.classList.add('dead')
+ document.querySelector('#status').textContent = i18n.global.t('splash.error')
+ console.error('PleromaFE failed to initialize: ', e)
+ document.querySelector('#statusError').textContent = e
+ document.querySelector('#statusStack').textContent = e.stack
+ document.querySelector('#statusError').style = 'display: block'
+ document.querySelector('#statusStack').style = 'display: block'
+ }
+
+ window.splashError = e => splashError(i18n, e)
+ window.splashUpdate = key => {
+ if (document.querySelector('#status')) {
+ document.querySelector('#status').textContent = i18n.global.t(key)
+ }
}
- const store = createStore({
- modules: {
- i18n: {
- getters: {
- i18n: () => i18n.global
- }
+
+ try {
+ let storageError
+ const plugins = [pushNotifications]
+ try {
+ const persistedState = await createPersistedState(persistedStateOptions)
+ plugins.push(persistedState)
+ } catch (e) {
+ console.error('Storage error', e)
+ storageError = e
+ }
+ document.querySelector('#mascot').src = `/static/pleromatan_apology${isFox}.png`
+ document.querySelector('#status').removeAttribute('class')
+ document.querySelector('#status').textContent = i18n.global.t('splash.loading')
+ document.querySelector('#splash-credit').textContent = i18n.global.t('update.art_by', { linkToArtist: 'pipivovott' })
+ const store = createStore({
+ modules: {
+ i18n: {
+ getters: {
+ i18n: () => i18n.global
+ }
+ },
+ interface: interfaceModule,
+ instance: instanceModule,
+ // TODO refactor users/statuses modules, they depend on each other
+ users: usersModule,
+ statuses: statusesModule,
+ notifications: notificationsModule,
+ lists: listsModule,
+ api: apiModule,
+ config: configModule,
+ profileConfig: profileConfigModule,
+ serverSideStorage: serverSideStorageModule,
+ adminSettings: adminSettingsModule,
+ shout: shoutModule,
+ oauth: oauthModule,
+ authFlow: authFlowModule,
+ mediaViewer: mediaViewerModule,
+ oauthTokens: oauthTokensModule,
+ reports: reportsModule,
+ polls: pollsModule,
+ postStatus: postStatusModule,
+ editStatus: editStatusModule,
+ statusHistory: statusHistoryModule,
+ chats: chatsModule,
+ announcements: announcementsModule,
+ bookmarkFolders: bookmarkFoldersModule
},
- interface: interfaceModule,
- instance: instanceModule,
- // TODO refactor users/statuses modules, they depend on each other
- users: usersModule,
- statuses: statusesModule,
- api: apiModule,
- config: configModule,
- serverSideConfig: serverSideConfigModule,
- shout: shoutModule,
- oauth: oauthModule,
- authFlow: authFlowModule,
- mediaViewer: mediaViewerModule,
- oauthTokens: oauthTokensModule,
- reports: reportsModule,
- polls: pollsModule,
- postStatus: postStatusModule,
- chats: chatsModule
- },
- plugins,
- strict: false // Socket modifies itself, let's ignore this for now.
- // strict: process.env.NODE_ENV !== 'production'
- })
- if (storageError) {
- store.dispatch('pushGlobalNotice', { messageKey: 'errors.storage_unavailable', level: 'error' })
+ plugins,
+ strict: false // Socket modifies itself, let's ignore this for now.
+ // strict: process.env.NODE_ENV !== 'production'
+ })
+ if (storageError) {
+ store.dispatch('pushGlobalNotice', { messageKey: 'errors.storage_unavailable', level: 'error' })
+ }
+ return await afterStoreSetup({ store, i18n })
+ } catch (e) {
+ splashError(i18n, e)
}
- afterStoreSetup({ store, i18n })
})()
// These are inlined by webpack's DefinePlugin
diff --git a/src/modules/adminSettings.js b/src/modules/adminSettings.js
@@ -0,0 +1,229 @@
+import { set, get, cloneDeep, differenceWith, isEqual, flatten } from 'lodash'
+
+export const defaultState = {
+ frontends: [],
+ loaded: false,
+ needsReboot: null,
+ config: null,
+ modifiedPaths: null,
+ descriptions: null,
+ draft: null,
+ dbConfigEnabled: null
+}
+
+export const newUserFlags = {
+ ...defaultState.flagStorage
+}
+
+const adminSettingsStorage = {
+ state: {
+ ...cloneDeep(defaultState)
+ },
+ mutations: {
+ setInstanceAdminNoDbConfig (state) {
+ state.loaded = false
+ state.dbConfigEnabled = false
+ },
+ setAvailableFrontends (state, { frontends }) {
+ state.frontends = frontends.map(f => {
+ f.installedRefs = f.installed_refs
+ if (f.name === 'pleroma-fe') {
+ f.refs = ['master', 'develop']
+ } else {
+ f.refs = [f.ref]
+ }
+ return f
+ })
+ },
+ updateAdminSettings (state, { config, modifiedPaths }) {
+ state.loaded = true
+ state.dbConfigEnabled = true
+ state.config = config
+ state.modifiedPaths = modifiedPaths
+ },
+ updateAdminDescriptions (state, { descriptions }) {
+ state.descriptions = descriptions
+ },
+ updateAdminDraft (state, { path, value }) {
+ const [group, key, subkey] = path
+ const parent = [group, key, subkey]
+
+ set(state.draft, path, value)
+
+ // force-updating grouped draft to trigger refresh of group settings
+ if (path.length > parent.length) {
+ set(state.draft, parent, cloneDeep(get(state.draft, parent)))
+ }
+ },
+ resetAdminDraft (state) {
+ state.draft = cloneDeep(state.config)
+ }
+ },
+ actions: {
+ loadFrontendsStuff ({ state, rootState, dispatch, commit }) {
+ rootState.api.backendInteractor.fetchAvailableFrontends()
+ .then(frontends => commit('setAvailableFrontends', { frontends }))
+ },
+ loadAdminStuff ({ state, rootState, dispatch, commit }) {
+ rootState.api.backendInteractor.fetchInstanceDBConfig()
+ .then(backendDbConfig => {
+ if (backendDbConfig.error) {
+ if (backendDbConfig.error.status === 400) {
+ backendDbConfig.error.json().then(errorJson => {
+ if (/configurable_from_database/.test(errorJson.error)) {
+ commit('setInstanceAdminNoDbConfig')
+ }
+ })
+ }
+ } else {
+ dispatch('setInstanceAdminSettings', { backendDbConfig })
+ }
+ })
+ if (state.descriptions === null) {
+ rootState.api.backendInteractor.fetchInstanceConfigDescriptions()
+ .then(backendDescriptions => dispatch('setInstanceAdminDescriptions', { backendDescriptions }))
+ }
+ },
+ setInstanceAdminSettings ({ state, commit, dispatch }, { backendDbConfig }) {
+ const config = state.config || {}
+ const modifiedPaths = new Set()
+ backendDbConfig.configs.forEach(c => {
+ const path = [c.group, c.key]
+ if (c.db) {
+ // Path elements can contain dot, therefore we use ' -> ' as a separator instead
+ // Using strings for modified paths for easier searching
+ c.db.forEach(x => modifiedPaths.add([...path, x].join(' -> ')))
+ }
+ const convert = (value) => {
+ if (Array.isArray(value) && value.length > 0 && value[0].tuple) {
+ return value.reduce((acc, c) => {
+ return { ...acc, [c.tuple[0]]: convert(c.tuple[1]) }
+ }, {})
+ } else {
+ return value
+ }
+ }
+ set(config, path, convert(c.value))
+ })
+ commit('updateAdminSettings', { config, modifiedPaths })
+ commit('resetAdminDraft')
+ },
+ setInstanceAdminDescriptions ({ state, commit, dispatch }, { backendDescriptions }) {
+ const convert = ({ children, description, label, key = '<ROOT>', group, suggestions }, path, acc) => {
+ const newPath = group ? [group, key] : [key]
+ const obj = { description, label, suggestions }
+ if (Array.isArray(children)) {
+ children.forEach(c => {
+ convert(c, newPath, obj)
+ })
+ }
+ set(acc, newPath, obj)
+ }
+
+ const descriptions = {}
+ backendDescriptions.forEach(d => convert(d, '', descriptions))
+ commit('updateAdminDescriptions', { descriptions })
+ },
+
+ // This action takes draft state, diffs it with live config state and then pushes
+ // only differences between the two. Difference detection only work up to subkey (third) level.
+ pushAdminDraft ({ rootState, state, commit, dispatch }) {
+ // TODO cleanup paths in modifiedPaths
+ const convert = (value) => {
+ if (typeof value !== 'object') {
+ return value
+ } else if (Array.isArray(value)) {
+ return value.map(convert)
+ } else {
+ return Object.entries(value).map(([k, v]) => ({ tuple: [k, v] }))
+ }
+ }
+
+ // Getting all group-keys used in config
+ const allGroupKeys = flatten(
+ Object
+ .entries(state.config)
+ .map(
+ ([group, lv1data]) => Object
+ .keys(lv1data)
+ .map((key) => ({ group, key }))
+ )
+ )
+
+ // Only using group-keys where there are changes detected
+ const changedGroupKeys = allGroupKeys.filter(({ group, key }) => {
+ return !isEqual(state.config[group][key], state.draft[group][key])
+ })
+
+ // Here we take all changed group-keys and get all changed subkeys
+ const changed = changedGroupKeys.map(({ group, key }) => {
+ const config = state.config[group][key]
+ const draft = state.draft[group][key]
+
+ // We convert group-key value into entries arrays
+ const eConfig = Object.entries(config)
+ const eDraft = Object.entries(draft)
+
+ // Then those entries array we diff so only changed subkey entries remain
+ // We use the diffed array to reconstruct the object and then shove it into convert()
+ return ({ group, key, value: convert(Object.fromEntries(differenceWith(eDraft, eConfig, isEqual))) })
+ })
+
+ rootState.api.backendInteractor.pushInstanceDBConfig({
+ payload: {
+ configs: changed
+ }
+ })
+ .then(() => rootState.api.backendInteractor.fetchInstanceDBConfig())
+ .then(backendDbConfig => dispatch('setInstanceAdminSettings', { backendDbConfig }))
+ },
+ pushAdminSetting ({ rootState, state, commit, dispatch }, { path, value }) {
+ const [group, key, ...rest] = Array.isArray(path) ? path : path.split(/\./g)
+ const clone = {} // not actually cloning the entire thing to avoid excessive writes
+ set(clone, rest, value)
+
+ // TODO cleanup paths in modifiedPaths
+ const convert = (value) => {
+ if (typeof value !== 'object') {
+ return value
+ } else if (Array.isArray(value)) {
+ return value.map(convert)
+ } else {
+ return Object.entries(value).map(([k, v]) => ({ tuple: [k, v] }))
+ }
+ }
+
+ rootState.api.backendInteractor.pushInstanceDBConfig({
+ payload: {
+ configs: [{
+ group,
+ key,
+ value: convert(clone)
+ }]
+ }
+ })
+ .then(() => rootState.api.backendInteractor.fetchInstanceDBConfig())
+ .then(backendDbConfig => dispatch('setInstanceAdminSettings', { backendDbConfig }))
+ },
+ resetAdminSetting ({ rootState, state, commit, dispatch }, { path }) {
+ const [group, key, subkey] = path.split(/\./g)
+
+ state.modifiedPaths.delete(path)
+
+ return rootState.api.backendInteractor.pushInstanceDBConfig({
+ payload: {
+ configs: [{
+ group,
+ key,
+ delete: true,
+ subkeys: [subkey]
+ }]
+ }
+ })
+ .then(() => rootState.api.backendInteractor.fetchInstanceDBConfig())
+ .then(backendDbConfig => dispatch('setInstanceAdminSettings', { backendDbConfig }))
+ }
+ }
+}
+
+export default adminSettingsStorage
diff --git a/src/modules/announcements.js b/src/modules/announcements.js
@@ -0,0 +1,135 @@
+const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5
+
+export const defaultState = {
+ announcements: [],
+ supportsAnnouncements: true,
+ fetchAnnouncementsTimer: undefined
+}
+
+export const mutations = {
+ setAnnouncements (state, announcements) {
+ state.announcements = announcements
+ },
+ setAnnouncementRead (state, { id, read }) {
+ const index = state.announcements.findIndex(a => a.id === id)
+
+ if (index < 0) {
+ return
+ }
+
+ state.announcements[index].read = read
+ },
+ setFetchAnnouncementsTimer (state, timer) {
+ state.fetchAnnouncementsTimer = timer
+ },
+ setSupportsAnnouncements (state, supportsAnnouncements) {
+ state.supportsAnnouncements = supportsAnnouncements
+ }
+}
+
+export const getters = {
+ unreadAnnouncementCount (state, _getters, rootState) {
+ if (!rootState.users.currentUser) {
+ return 0
+ }
+
+ const unread = state.announcements.filter(announcement => !(announcement.inactive || announcement.read))
+ return unread.length
+ }
+}
+
+const announcements = {
+ state: defaultState,
+ mutations,
+ getters,
+ actions: {
+ fetchAnnouncements (store) {
+ if (!store.state.supportsAnnouncements) {
+ return Promise.resolve()
+ }
+
+ const currentUser = store.rootState.users.currentUser
+ const isAdmin = currentUser && currentUser.privileges.includes('announcements_manage_announcements')
+
+ const getAnnouncements = async () => {
+ if (!isAdmin) {
+ return store.rootState.api.backendInteractor.fetchAnnouncements()
+ }
+
+ const all = await store.rootState.api.backendInteractor.adminFetchAnnouncements()
+ const visible = await store.rootState.api.backendInteractor.fetchAnnouncements()
+ const visibleObject = visible.reduce((a, c) => {
+ a[c.id] = c
+ return a
+ }, {})
+ const getWithinVisible = announcement => visibleObject[announcement.id]
+
+ all.forEach(announcement => {
+ const visibleAnnouncement = getWithinVisible(announcement)
+ if (!visibleAnnouncement) {
+ announcement.inactive = true
+ } else {
+ announcement.read = visibleAnnouncement.read
+ }
+ })
+
+ return all
+ }
+
+ return getAnnouncements()
+ .then(announcements => {
+ store.commit('setAnnouncements', announcements)
+ })
+ .catch(error => {
+ // If and only if backend does not support announcements, it would return 404.
+ // In this case, silently ignores it.
+ if (error && error.statusCode === 404) {
+ store.commit('setSupportsAnnouncements', false)
+ } else {
+ throw error
+ }
+ })
+ },
+ markAnnouncementAsRead (store, id) {
+ return store.rootState.api.backendInteractor.dismissAnnouncement({ id })
+ .then(() => {
+ store.commit('setAnnouncementRead', { id, read: true })
+ })
+ },
+ startFetchingAnnouncements (store) {
+ if (store.state.fetchAnnouncementsTimer) {
+ return
+ }
+
+ const interval = setInterval(() => store.dispatch('fetchAnnouncements'), FETCH_ANNOUNCEMENT_INTERVAL_MS)
+ store.commit('setFetchAnnouncementsTimer', interval)
+
+ return store.dispatch('fetchAnnouncements')
+ },
+ stopFetchingAnnouncements (store) {
+ const interval = store.state.fetchAnnouncementsTimer
+ store.commit('setFetchAnnouncementsTimer', undefined)
+ clearInterval(interval)
+ },
+ postAnnouncement (store, { content, startsAt, endsAt, allDay }) {
+ return store.rootState.api.backendInteractor.postAnnouncement({ content, startsAt, endsAt, allDay })
+ .then(() => {
+ return store.dispatch('fetchAnnouncements')
+ })
+ },
+ editAnnouncement (store, { id, content, startsAt, endsAt, allDay }) {
+ return store.rootState.api.backendInteractor.editAnnouncement({ id, content, startsAt, endsAt, allDay })
+ .then(() => {
+ return store.dispatch('fetchAnnouncements')
+ })
+ },
+ deleteAnnouncement (store, id) {
+ return store.rootState.api.backendInteractor.deleteAnnouncement({ id })
+ .then(() => {
+ return store.dispatch('fetchAnnouncements')
+ })
+ }
+ }
+}
+
+export default announcements
diff --git a/src/modules/api.js b/src/modules/api.js
@@ -15,6 +15,9 @@ const api = {
mastoUserSocketStatus: null,
followRequests: []
},
+ getters: {
+ followRequestCount: state => state.followRequests.length
+ },
mutations: {
setBackendInteractor (state, backendInteractor) {
state.backendInteractor = backendInteractor
@@ -100,6 +103,13 @@ const api = {
showImmediately: timelineData.visibleStatuses.length === 0,
timeline: 'friends'
})
+ } else if (message.event === 'status.update') {
+ dispatch('addNewStatuses', {
+ statuses: [message.status],
+ userId: false,
+ showImmediately: message.status.id in timelineData.visibleStatusesObject,
+ timeline: 'friends'
+ })
} else if (message.event === 'delete') {
dispatch('deleteStatusById', message.id)
} else if (message.event === 'pleroma:chat_update') {
@@ -191,12 +201,15 @@ const api = {
startFetchingTimeline (store, {
timeline = 'friends',
tag = false,
- userId = false
+ userId = false,
+ listId = false,
+ statusId = false,
+ bookmarkFolderId = false
}) {
if (store.state.fetchers[timeline]) return
const fetcher = store.state.backendInteractor.startFetchingTimeline({
- timeline, store, userId, tag
+ timeline, store, userId, listId, statusId, bookmarkFolderId, tag
})
store.commit('addFetcher', { fetcherName: timeline, fetcher })
},
@@ -205,7 +218,7 @@ const api = {
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: timeline, fetcher })
},
- fetchTimeline (store, timeline, { ...rest }) {
+ fetchTimeline (store, { timeline, ...rest }) {
store.state.backendInteractor.fetchTimeline({
store,
timeline,
@@ -233,7 +246,7 @@ const api = {
// Follow requests
startFetchingFollowRequests (store) {
- if (store.state.fetchers['followRequests']) return
+ if (store.state.fetchers.followRequests) return
const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store })
store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
@@ -244,10 +257,34 @@ const api = {
store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher })
},
removeFollowRequest (store, request) {
- let requests = store.state.followRequests.filter((it) => it !== request)
+ const requests = store.state.followRequests.filter((it) => it !== request)
store.commit('setFollowRequests', requests)
},
+ // Lists
+ startFetchingLists (store) {
+ if (store.state.fetchers.lists) return
+ const fetcher = store.state.backendInteractor.startFetchingLists({ store })
+ store.commit('addFetcher', { fetcherName: 'lists', fetcher })
+ },
+ stopFetchingLists (store) {
+ const fetcher = store.state.fetchers.lists
+ if (!fetcher) return
+ store.commit('removeFetcher', { fetcherName: 'lists', fetcher })
+ },
+
+ // Bookmark folders
+ startFetchingBookmarkFolders (store) {
+ if (store.state.fetchers.bookmarkFolders) return
+ const fetcher = store.state.backendInteractor.startFetchingBookmarkFolders({ store })
+ store.commit('addFetcher', { fetcherName: 'bookmarkFolders', fetcher })
+ },
+ stopFetchingBookmarkFolders (store) {
+ const fetcher = store.state.fetchers.bookmarkFolders
+ if (!fetcher) return
+ store.commit('removeFetcher', { fetcherName: 'bookmarkFolders', fetcher })
+ },
+
// Pleroma websocket
setWsToken (store, token) {
store.commit('setWsToken', token)
diff --git a/src/modules/bookmark_folders.js b/src/modules/bookmark_folders.js
@@ -0,0 +1,66 @@
+import { remove, find } from 'lodash'
+
+export const defaultState = {
+ allFolders: []
+}
+
+export const mutations = {
+ setBookmarkFolders (state, value) {
+ state.allFolders = value
+ },
+ setBookmarkFolder (state, { id, name, emoji, emoji_url: emojiUrl }) {
+ const entry = find(state.allFolders, { id })
+ if (!entry) {
+ state.allFolders.push({ id, name, emoji, emoji_url: emojiUrl })
+ } else {
+ entry.name = name
+ entry.emoji = emoji
+ entry.emoji_url = emojiUrl
+ }
+ },
+ deleteBookmarkFolder (state, { folderId }) {
+ remove(state.allFolders, folder => folder.id === folderId)
+ }
+}
+
+const actions = {
+ setBookmarkFolders ({ commit }, value) {
+ commit('setBookmarkFolders', value)
+ },
+ createBookmarkFolder ({ rootState, commit }, { name, emoji }) {
+ return rootState.api.backendInteractor.createBookmarkFolder({ name, emoji })
+ .then((folder) => {
+ commit('setBookmarkFolder', folder)
+ return folder
+ })
+ },
+ setBookmarkFolder ({ rootState, commit }, { folderId, name, emoji }) {
+ return rootState.api.backendInteractor.updateBookmarkFolder({ folderId, name, emoji })
+ .then((folder) => {
+ commit('setBookmarkFolder', folder)
+ return folder
+ })
+ },
+ deleteBookmarkFolder ({ rootState, commit }, { folderId }) {
+ rootState.api.backendInteractor.deleteBookmarkFolder({ folderId })
+ commit('deleteBookmarkFolder', { folderId })
+ }
+}
+
+export const getters = {
+ findBookmarkFolderName: state => id => {
+ const folder = state.allFolders.find(folder => folder.id === id)
+
+ if (!folder) return
+ return folder.name
+ }
+}
+
+const bookmarkFolders = {
+ state: defaultState,
+ mutations,
+ actions,
+ getters
+}
+
+export default bookmarkFolders
diff --git a/src/modules/chats.js b/src/modules/chats.js
@@ -65,6 +65,7 @@ const chats = {
const newChatMessageSideEffects = (chat) => {
maybeShowChatNotification(store, chat)
}
+ commit('addNewUsers', chats.map(k => k.account).filter(k => k))
commit('addNewChats', { dispatch, chats, rootGetters, newChatMessageSideEffects })
},
updateChat ({ commit }, { chat }) {
diff --git a/src/modules/config.js b/src/modules/config.js
@@ -1,9 +1,21 @@
import Cookies from 'js-cookie'
-import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
+import { applyConfig } from '../services/style_setter/style_setter.js'
import messages from '../i18n/messages'
+import { set } from 'lodash'
import localeService from '../services/locale/locale.service.js'
const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage'
+const APPEARANCE_SETTINGS_KEYS = new Set([
+ 'sidebarColumnWidth',
+ 'contentColumnWidth',
+ 'notifsColumnWidth',
+ 'textSize',
+ 'navbarSize',
+ 'panelHeaderSize',
+ 'forcedRoundness',
+ 'emojiSize',
+ 'emojiReactionsScale'
+])
const browserLocale = (window.navigator.language || 'en').split('-')[0]
@@ -17,15 +29,40 @@ export const multiChoiceProperties = [
'subjectLineBehavior',
'conversationDisplay', // tree | linear
'conversationOtherRepliesButton', // below | inside
- 'mentionLinkDisplay' // short | full_for_remote | full
+ 'mentionLinkDisplay', // short | full_for_remote | full
+ 'userPopoverAvatarAction' // close | zoom | open
]
export const defaultState = {
expertLevel: 0, // used to track which settings to show and hide
- colors: {},
- theme: undefined,
- customTheme: undefined,
- customThemeSource: undefined,
+
+ // Theme stuff
+ theme: undefined, // Very old theme store, stores preset name, still in use
+
+ // V1
+ colors: {}, // VERY old theme store, just colors of V1, probably not even used anymore
+
+ // V2
+ customTheme: undefined, // "snapshot", previously was used as actual theme store for V2 so it's still used in case of PleromaFE downgrade event.
+ customThemeSource: undefined, // "source", stores original theme data
+
+ // V3
+ style: null,
+ styleCustomData: null,
+ palette: null,
+ paletteCustomData: null,
+ themeDebug: false, // debug mode that uses computed backgrounds instead of real ones to debug contrast functions
+ forceThemeRecompilation: false, // flag that forces recompilation on boot even if cache exists
+ theme3hacks: { // Hacks, user overrides that are independent of theme used
+ underlay: 'none',
+ fonts: {
+ interface: undefined,
+ input: undefined,
+ post: undefined,
+ monospace: undefined
+ }
+ },
+
hideISP: false,
hideInstanceWallpaper: false,
hideShoutbox: false,
@@ -34,10 +71,13 @@ export const defaultState = {
hideMutedThreads: undefined, // instance default
hideWordFilteredPosts: undefined, // instance default
muteBotStatuses: undefined, // instance default
+ muteSensitiveStatuses: undefined, // instance default
collapseMessageWithSubject: undefined, // instance default
padEmoji: true,
hideAttachments: false,
hideAttachmentsInConv: false,
+ hideScrobbles: false,
+ hideScrobblesAfter: '2d',
maxThumbnails: 16,
hideNsfw: true,
preloadImage: true,
@@ -54,15 +94,31 @@ export const defaultState = {
notificationVisibility: {
follows: true,
mentions: true,
+ statuses: true,
likes: true,
repeats: true,
moves: true,
emojiReactions: true,
followRequest: true,
+ reports: true,
+ chatMention: true,
+ polls: true
+ },
+ notificationNative: {
+ follows: true,
+ mentions: true,
+ statuses: true,
+ likes: false,
+ repeats: false,
+ moves: false,
+ emojiReactions: false,
+ followRequest: true,
+ reports: true,
chatMention: true,
polls: true
},
webPushNotifications: false,
+ webPushAlwaysShowNotifications: false,
muteWords: [],
highlight: {},
interfaceLanguage: browserLocale,
@@ -76,11 +132,32 @@ export const defaultState = {
minimalScopesMode: undefined, // instance default
// This hides statuses filtered via a word filter
hideFilteredStatuses: undefined, // instance default
+ modalOnRepeat: undefined, // instance default
+ modalOnUnfollow: undefined, // instance default
+ modalOnBlock: undefined, // instance default
+ modalOnMute: undefined, // instance default
+ modalOnDelete: undefined, // instance default
+ modalOnLogout: undefined, // instance default
+ modalOnApproveFollow: undefined, // instance default
+ modalOnDenyFollow: undefined, // instance default
+ modalOnRemoveUserFromFollowers: undefined, // instance default
playVideosInModal: false,
useOneClickNsfw: false,
useContainFit: true,
disableStickyHeaders: false,
showScrollbars: false,
+ userPopoverAvatarAction: 'open',
+ userPopoverOverlay: false,
+ sidebarColumnWidth: '25rem',
+ contentColumnWidth: '45rem',
+ notifsColumnWidth: '25rem',
+ emojiReactionsScale: undefined,
+ textSize: undefined, // instance default
+ emojiSize: undefined, // instance default
+ navbarSize: undefined, // instance default
+ panelHeaderSize: undefined, // instance default
+ forcedRoundness: undefined, // instance default
+ navbarColumnStretch: false,
greentext: undefined, // instance default
useAtIcon: undefined, // instance default
mentionLinkDisplay: undefined, // instance default
@@ -98,7 +175,18 @@ export const defaultState = {
conversationTreeAdvanced: undefined, // instance default
conversationOtherRepliesButton: undefined, // instance default
conversationTreeFadeAncestors: undefined, // instance default
- maxDepthInThread: undefined // instance default
+ showExtraNotifications: undefined, // instance default
+ showExtraNotificationsTip: undefined, // instance default
+ showChatsInExtraNotifications: undefined, // instance default
+ showAnnouncementsInExtraNotifications: undefined, // instance default
+ showFollowRequestsInExtraNotifications: undefined, // instance default
+ maxDepthInThread: undefined, // instance default
+ autocompleteSelect: undefined, // instance default
+ closingDrawerMarksAsSeen: undefined, // instance default
+ unseenAtTop: undefined, // instance default
+ ignoreInactionableSeen: undefined, // instance default
+ useAbsoluteTimeFormat: undefined, // instance default
+ absoluteTimeFormatMinAge: undefined // instance default
}
// caching the instance default properties
@@ -128,8 +216,12 @@ const config = {
}
},
mutations: {
+ setOptionTemporarily (state, { name, value }) {
+ set(state, name, value)
+ applyConfig(state)
+ },
setOption (state, { name, value }) {
- state[name] = value
+ set(state, name, value)
},
setHighlight (state, { user, color, type }) {
const data = this.state.config.highlight[user]
@@ -145,7 +237,7 @@ const config = {
const knownKeys = new Set(Object.keys(defaultState))
const presentKeys = new Set(Object.keys(data))
const intersection = new Set()
- for (let elem of presentKeys) {
+ for (const elem of presentKeys) {
if (knownKeys.has(elem)) {
intersection.add(elem)
}
@@ -158,23 +250,86 @@ const config = {
setHighlight ({ commit, dispatch }, { user, color, type }) {
commit('setHighlight', { user, color, type })
},
- setOption ({ commit, dispatch }, { name, value }) {
- commit('setOption', { name, value })
- switch (name) {
- case 'theme':
- setPreset(value)
- break
- case 'customTheme':
- case 'customThemeSource':
- applyTheme(value)
- break
- case 'interfaceLanguage':
- messages.setLanguage(this.getters.i18n, value)
- Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value))
- break
- case 'thirdColumnMode':
- dispatch('setLayoutWidth', undefined)
- break
+ setOptionTemporarily ({ commit, dispatch, state, rootState }, { name, value }) {
+ if (rootState.interface.temporaryChangesTimeoutId !== null) {
+ console.warn('Can\'t track more than one temporary change')
+ return
+ }
+ const oldValue = state[name]
+
+ commit('setOptionTemporarily', { name, value })
+
+ const confirm = () => {
+ dispatch('setOption', { name, value })
+ commit('clearTemporaryChanges')
+ }
+
+ const revert = () => {
+ commit('setOptionTemporarily', { name, value: oldValue })
+ commit('clearTemporaryChanges')
+ }
+
+ commit('setTemporaryChanges', {
+ timeoutId: setTimeout(revert, 10000),
+ confirm,
+ revert
+ })
+ },
+ setThemeV2 ({ commit, dispatch }, { customTheme, customThemeSource }) {
+ commit('setOption', { name: 'theme', value: 'custom' })
+ commit('setOption', { name: 'customTheme', value: customTheme })
+ commit('setOption', { name: 'customThemeSource', value: customThemeSource })
+ dispatch('setTheme', { themeData: customThemeSource, recompile: true })
+ },
+ setOption ({ commit, dispatch, state }, { name, value }) {
+ const exceptions = new Set([
+ 'useStreamingApi'
+ ])
+
+ if (exceptions.has(name)) {
+ switch (name) {
+ case 'useStreamingApi': {
+ const action = value ? 'enableMastoSockets' : 'disableMastoSockets'
+
+ dispatch(action).then(() => {
+ commit('setOption', { name: 'useStreamingApi', value })
+ }).catch((e) => {
+ console.error('Failed starting MastoAPI Streaming socket', e)
+ dispatch('disableMastoSockets')
+ dispatch('setOption', { name: 'useStreamingApi', value: false })
+ })
+ break
+ }
+ }
+ } else {
+ commit('setOption', { name, value })
+ if (APPEARANCE_SETTINGS_KEYS.has(name)) {
+ applyConfig(state)
+ }
+ if (name.startsWith('theme3hacks')) {
+ dispatch('applyTheme', { recompile: true })
+ }
+ switch (name) {
+ case 'theme':
+ if (value === 'custom') break
+ dispatch('setTheme', { themeName: value, recompile: true, saveData: true })
+ break
+ case 'themeDebug': {
+ dispatch('setTheme', { recompile: true })
+ break
+ }
+ case 'interfaceLanguage':
+ messages.setLanguage(this.getters.i18n, value)
+ dispatch('loadUnicodeEmojiData', value)
+ Cookies.set(
+ BACKEND_LANGUAGE_COOKIE_NAME,
+ localeService.internalToBackendLocaleMulti(value)
+ )
+ break
+ case 'thirdColumnMode':
+ dispatch('setLayoutWidth', undefined)
+ break
+ }
}
}
}
diff --git a/src/modules/editStatus.js b/src/modules/editStatus.js
@@ -0,0 +1,25 @@
+const editStatus = {
+ state: {
+ params: null,
+ modalActivated: false
+ },
+ mutations: {
+ openEditStatusModal (state, params) {
+ state.params = params
+ state.modalActivated = true
+ },
+ closeEditStatusModal (state) {
+ state.modalActivated = false
+ }
+ },
+ actions: {
+ openEditStatusModal ({ commit }, params) {
+ commit('openEditStatusModal', params)
+ },
+ closeEditStatusModal ({ commit }) {
+ commit('closeEditStatusModal')
+ }
+ }
+}
+
+export default editStatus
diff --git a/src/modules/errors.js b/src/modules/errors.js
@@ -2,8 +2,8 @@ import { capitalize } from 'lodash'
export function humanizeErrors (errors) {
return Object.entries(errors).reduce((errs, [k, val]) => {
- let message = val.reduce((acc, message) => {
- let key = capitalize(k.replace(/_/g, ' '))
+ const message = val.reduce((acc, message) => {
+ const key = capitalize(k.replace(/_/g, ' '))
return acc + [key, message].join(' ') + '. '
}, '')
return [...errs, message]
diff --git a/src/modules/instance.js b/src/modules/instance.js
@@ -1,7 +1,40 @@
-import { getPreset, applyTheme } from '../services/style_setter/style_setter.js'
-import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
import apiService from '../services/api/api.service.js'
import { instanceDefaultProperties } from './config.js'
+import { langCodeToCldrName, ensureFinalFallback } from '../i18n/languages.js'
+
+const SORTED_EMOJI_GROUP_IDS = [
+ 'smileys-and-emotion',
+ 'people-and-body',
+ 'animals-and-nature',
+ 'food-and-drink',
+ 'travel-and-places',
+ 'activities',
+ 'objects',
+ 'symbols',
+ 'flags'
+]
+
+const REGIONAL_INDICATORS = (() => {
+ const start = 0x1F1E6
+ const end = 0x1F1FF
+ const A = 'A'.codePointAt(0)
+ const res = new Array(end - start + 1)
+ for (let i = start; i <= end; ++i) {
+ const letter = String.fromCodePoint(A + i - start)
+ res[i - start] = {
+ replacement: String.fromCodePoint(i),
+ imageUrl: false,
+ displayText: 'regional_indicator_' + letter,
+ displayTextI18n: {
+ key: 'emoji.regional_indicator',
+ args: { letter }
+ }
+ }
+ }
+ return res
+})()
+
+const REMOTE_INTERACTION_URL = '/main/ostatus'
const defaultState = {
// Stuff from apiConfig
@@ -9,7 +42,10 @@ const defaultState = {
registrationOpen: true,
server: 'http://localhost:4040/',
textlimit: 5000,
- themeData: undefined,
+ themesIndex: undefined,
+ stylesIndex: undefined,
+ palettesIndex: undefined,
+ themeData: undefined, // used for theme editor v2
vapidPublicKey: undefined,
// Stuff from static/config.json
@@ -36,11 +72,22 @@ const defaultState = {
hideSitename: false,
hideUserStats: false,
muteBotStatuses: false,
+ muteSensitiveStatuses: false,
+ modalOnRepeat: false,
+ modalOnUnfollow: false,
+ modalOnBlock: true,
+ modalOnMute: false,
+ modalOnDelete: true,
+ modalOnLogout: true,
+ modalOnApproveFollow: false,
+ modalOnDenyFollow: false,
+ modalOnRemoveUserFromFollowers: false,
loginMethod: 'password',
logo: '/static/logo.svg',
logoMargin: '.2em',
logoMask: true,
logoLeft: false,
+ disableUpdateNotification: false,
minimalScopesMode: false,
nsfwCensorImage: undefined,
postContentType: 'text/plain',
@@ -52,32 +99,59 @@ const defaultState = {
sidebarRight: false,
subjectLineBehavior: 'email',
theme: 'pleroma-dark',
+ palette: null,
+ style: null,
+ emojiReactionsScale: 0.5,
+ textSize: '14px',
+ emojiSize: '2.2rem',
+ navbarSize: '3.5rem',
+ panelHeaderSize: '3.2rem',
+ forcedRoundness: -1,
+ fontsOverride: {},
virtualScrolling: true,
sensitiveByDefault: false,
conversationDisplay: 'linear',
conversationTreeAdvanced: false,
conversationOtherRepliesButton: 'below',
conversationTreeFadeAncestors: false,
+ showExtraNotifications: true,
+ showExtraNotificationsTip: true,
+ showChatsInExtraNotifications: true,
+ showAnnouncementsInExtraNotifications: true,
+ showFollowRequestsInExtraNotifications: true,
maxDepthInThread: 6,
+ autocompleteSelect: false,
+ closingDrawerMarksAsSeen: true,
+ unseenAtTop: false,
+ ignoreInactionableSeen: false,
+ useAbsoluteTimeFormat: false,
+ absoluteTimeFormatMinAge: '0d',
// Nasty stuff
customEmoji: [],
customEmojiFetched: false,
- emoji: [],
+ emoji: {},
emojiFetched: false,
+ unicodeEmojiAnnotations: {},
pleromaBackend: true,
postFormats: [],
restrictedNicknames: [],
safeDM: true,
knownDomains: [],
+ birthdayRequired: false,
+ birthdayMinAge: 0,
// Feature-set, apparently, not everything here is reported...
shoutAvailable: false,
pleromaChatMessagesAvailable: false,
+ pleromaCustomEmojiReactionsAvailable: false,
+ pleromaBookmarkFoldersAvailable: false,
gopherAvailable: false,
mediaProxyAvailable: false,
suggestionsEnabled: false,
suggestionsWeb: '',
+ quotingAvailable: false,
+ groupActorAvailable: false,
// Html stuff
instanceSpecificPanelContent: '',
@@ -85,6 +159,7 @@ const defaultState = {
// Version Information
backendVersion: '',
+ backendRepository: '',
frontendVersion: '',
pollsAvailable: false,
@@ -96,6 +171,31 @@ const defaultState = {
}
}
+const loadAnnotations = (lang) => {
+ return import(
+ /* webpackChunkName: "emoji-annotations/[request]" */
+ `@kazvmoe-infra/unicode-emoji-json/annotations/${langCodeToCldrName(lang)}.json`
+ )
+ .then(k => k.default)
+}
+
+const injectAnnotations = (emoji, annotations) => {
+ const availableLangs = Object.keys(annotations)
+
+ return {
+ ...emoji,
+ annotations: availableLangs.reduce((acc, cur) => {
+ acc[cur] = annotations[cur][emoji.replacement]
+ return acc
+ }, {})
+ }
+}
+
+const injectRegionalIndicators = groups => {
+ groups.symbols.push(...REGIONAL_INDICATORS)
+ return groups
+}
+
const instance = {
state: defaultState,
mutations: {
@@ -106,6 +206,9 @@ const instance = {
},
setKnownDomains (state, domains) {
state.knownDomains = domains
+ },
+ setUnicodeEmojiAnnotations (state, { lang, annotations }) {
+ state.unicodeEmojiAnnotations[lang] = annotations
}
},
getters: {
@@ -114,8 +217,68 @@ const instance = {
.map(key => [key, state[key]])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
},
+ groupedCustomEmojis (state) {
+ const packsOf = emoji => {
+ const packs = emoji.tags
+ .filter(k => k.startsWith('pack:'))
+ .map(k => {
+ const packName = k.slice(5) // remove 'pack:' prefix
+ return {
+ id: `custom-${packName}`,
+ text: packName
+ }
+ })
+
+ if (!packs.length) {
+ return [{
+ id: 'unpacked'
+ }]
+ } else {
+ return packs
+ }
+ }
+
+ return state.customEmoji
+ .reduce((res, emoji) => {
+ packsOf(emoji).forEach(({ id: packId, text: packName }) => {
+ if (!res[packId]) {
+ res[packId] = ({
+ id: packId,
+ text: packName,
+ image: emoji.imageUrl,
+ emojis: []
+ })
+ }
+ res[packId].emojis.push(emoji)
+ })
+ return res
+ }, {})
+ },
+ standardEmojiList (state) {
+ return SORTED_EMOJI_GROUP_IDS
+ .map(groupId => (state.emoji[groupId] || []).map(k => injectAnnotations(k, state.unicodeEmojiAnnotations)))
+ .reduce((a, b) => a.concat(b), [])
+ },
+ standardEmojiGroupList (state) {
+ return SORTED_EMOJI_GROUP_IDS.map(groupId => ({
+ id: groupId,
+ emojis: (state.emoji[groupId] || []).map(k => injectAnnotations(k, state.unicodeEmojiAnnotations))
+ }))
+ },
instanceDomain (state) {
return new URL(state.server).hostname
+ },
+ remoteInteractionLink (state) {
+ const server = state.server.endsWith('/') ? state.server.slice(0, -1) : state.server
+ const link = server + REMOTE_INTERACTION_URL
+
+ return ({ statusId, nickname }) => {
+ if (statusId) {
+ return `${link}?status_id=${statusId}`
+ } else {
+ return `${link}?nickname=${nickname}`
+ }
+ }
}
},
actions: {
@@ -130,39 +293,74 @@ const instance = {
dispatch('initializeSocket')
}
break
- case 'theme':
- dispatch('setTheme', value)
- break
}
},
async getStaticEmoji ({ commit }) {
try {
- const res = await window.fetch('/static/emoji.json')
- if (res.ok) {
- const values = await res.json()
- const emoji = Object.keys(values).map((key) => {
- return {
- displayText: key,
- imageUrl: false,
- replacement: values[key]
- }
- }).sort((a, b) => a.name > b.name ? 1 : -1)
- commit('setInstanceOption', { name: 'emoji', value: emoji })
- } else {
- throw (res)
- }
+ const values = (await import(/* webpackChunkName: 'emoji' */ '../../static/emoji.json')).default
+
+ const emoji = Object.keys(values).reduce((res, groupId) => {
+ res[groupId] = values[groupId].map(e => ({
+ displayText: e.slug,
+ imageUrl: false,
+ replacement: e.emoji
+ }))
+ return res
+ }, {})
+ commit('setInstanceOption', { name: 'emoji', value: injectRegionalIndicators(emoji) })
} catch (e) {
console.warn("Can't load static emoji")
console.warn(e)
}
},
+ loadUnicodeEmojiData ({ commit, state }, language) {
+ const langList = ensureFinalFallback(language)
+
+ return Promise.all(
+ langList
+ .map(async lang => {
+ if (!state.unicodeEmojiAnnotations[lang]) {
+ try {
+ const annotations = await loadAnnotations(lang)
+ commit('setUnicodeEmojiAnnotations', { lang, annotations })
+ } catch (e) {
+ console.warn(`Error loading unicode emoji annotations for ${lang}: `, e)
+ // ignore
+ }
+ }
+ }))
+ },
+
async getCustomEmoji ({ commit, state }) {
try {
const res = await window.fetch('/api/pleroma/emoji.json')
if (res.ok) {
const result = await res.json()
const values = Array.isArray(result) ? Object.assign({}, ...result) : result
+ const caseInsensitiveStrCmp = (a, b) => {
+ const la = a.toLowerCase()
+ const lb = b.toLowerCase()
+ return la > lb ? 1 : (la < lb ? -1 : 0)
+ }
+ const noPackLast = (a, b) => {
+ const aNull = a === ''
+ const bNull = b === ''
+ if (aNull === bNull) {
+ return 0
+ } else if (aNull && !bNull) {
+ return 1
+ } else {
+ return -1
+ }
+ }
+ const byPackThenByName = (a, b) => {
+ const packOf = emoji => (emoji.tags.filter(k => k.startsWith('pack:'))[0] || '').slice(5)
+ const packOfA = packOf(a)
+ const packOfB = packOf(b)
+ return noPackLast(packOfA, packOfB) || caseInsensitiveStrCmp(packOfA, packOfB) || caseInsensitiveStrCmp(a.displayText, b.displayText)
+ }
+
const emoji = Object.entries(values).map(([key, value]) => {
const imageUrl = value.image_url
return {
@@ -173,7 +371,7 @@ const instance = {
}
// Technically could use tags but those are kinda useless right now,
// should have been "pack" field, that would be more useful
- }).sort((a, b) => a.displayText.toLowerCase() > b.displayText.toLowerCase() ? 1 : -1)
+ }).sort(byPackThenByName)
commit('setInstanceOption', { name: 'customEmoji', value: emoji })
} else {
throw (res)
@@ -183,25 +381,6 @@ const instance = {
console.warn(e)
}
},
-
- setTheme ({ commit, rootState }, themeName) {
- commit('setInstanceOption', { name: 'theme', value: themeName })
- getPreset(themeName)
- .then(themeData => {
- commit('setInstanceOption', { name: 'themeData', value: themeData })
- // No need to apply theme if there's user theme already
- const { customTheme } = rootState.config
- if (customTheme) return
-
- // New theme presets don't have 'theme' property, they use 'source'
- const themeSource = themeData.source
- if (!themeData.theme || (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION)) {
- applyTheme(themeSource)
- } else {
- applyTheme(themeData.theme)
- }
- })
- },
fetchEmoji ({ dispatch, state }) {
if (!state.customEmojiFetched) {
state.customEmojiFetched = true
diff --git a/src/modules/interface.js b/src/modules/interface.js
@@ -1,7 +1,31 @@
+import { getResourcesIndex, applyTheme, tryLoadCache } from '../services/style_setter/style_setter.js'
+import { CURRENT_VERSION, generatePreset } from 'src/services/theme_data/theme_data.service.js'
+import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
+import { deserialize } from '../services/theme_data/iss_deserializer.js'
+
+// helper for debugging
+// eslint-disable-next-line no-unused-vars
+const toValue = (x) => JSON.parse(JSON.stringify(x === undefined ? 'null' : x))
+
const defaultState = {
+ localFonts: null,
+ themeApplied: false,
+ themeVersion: 'v3',
+ styleNameUsed: null,
+ styleDataUsed: null,
+ useStylePalette: false, // hack for applying styles from appearance tab
+ paletteNameUsed: null,
+ paletteDataUsed: null,
+ themeNameUsed: null,
+ themeDataUsed: null,
+ temporaryChangesTimeoutId: null, // used for temporary options that revert after a timeout
+ temporaryChangesConfirm: () => {}, // used for applying temporary options
+ temporaryChangesRevert: () => {}, // used for reverting temporary options
settingsModalState: 'hidden',
- settingsModalLoaded: false,
+ settingsModalLoadedUser: false,
+ settingsModalLoadedAdmin: false,
settingsModalTargetTab: null,
+ settingsModalMode: 'user',
settings: {
currentSaveStateNotice: null,
noticeClearTimeout: null,
@@ -11,7 +35,8 @@ const defaultState = {
cssFilter: window.CSS && window.CSS.supports && (
window.CSS.supports('filter', 'drop-shadow(0 0)') ||
window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')
- )
+ ),
+ localFonts: typeof window.queryLocalFonts === 'function'
},
layoutType: 'normal',
globalNotices: [],
@@ -33,6 +58,20 @@ const interfaceMod = {
state.settings.currentSaveStateNotice = { error: true, errorData: error }
}
},
+ setTemporaryChanges (state, { timeoutId, confirm, revert }) {
+ state.temporaryChangesTimeoutId = timeoutId
+ state.temporaryChangesConfirm = confirm
+ state.temporaryChangesRevert = revert
+ },
+ clearTemporaryChanges (state) {
+ clearTimeout(state.temporaryChangesTimeoutId)
+ state.temporaryChangesTimeoutId = null
+ state.temporaryChangesConfirm = () => {}
+ state.temporaryChangesRevert = () => {}
+ },
+ setThemeApplied (state) {
+ state.themeApplied = true
+ },
setNotificationPermission (state, permission) {
state.notificationPermission = permission
},
@@ -54,10 +93,17 @@ const interfaceMod = {
throw new Error('Illegal minimization state of settings modal')
}
},
- openSettingsModal (state) {
+ openSettingsModal (state, value) {
+ state.settingsModalMode = value
state.settingsModalState = 'visible'
- if (!state.settingsModalLoaded) {
- state.settingsModalLoaded = true
+ if (value === 'user') {
+ if (!state.settingsModalLoadedUser) {
+ state.settingsModalLoadedUser = true
+ }
+ } else if (value === 'admin') {
+ if (!state.settingsModalLoadedAdmin) {
+ state.settingsModalLoadedAdmin = true
+ }
}
},
setSettingsModalTargetTab (state, value) {
@@ -77,6 +123,10 @@ const interfaceMod = {
},
setLastTimeline (state, value) {
state.lastTimeline = value
+ },
+ setFontsList (state, value) {
+ // Set is used here so that we filter out duplicate fonts (possibly same font but with different weight)
+ state.localFonts = [...(new Set(value.map(font => font.family))).values()]
}
},
actions: {
@@ -92,8 +142,8 @@ const interfaceMod = {
closeSettingsModal ({ commit }) {
commit('closeSettingsModal')
},
- openSettingsModal ({ commit }) {
- commit('openSettingsModal')
+ openSettingsModal ({ commit }, value = 'user') {
+ commit('openSettingsModal', value)
},
togglePeekSettingsModal ({ commit }) {
commit('togglePeekSettingsModal')
@@ -103,7 +153,7 @@ const interfaceMod = {
},
openSettingsModalTab ({ commit }, value) {
commit('setSettingsModalTargetTab', value)
- commit('openSettingsModal')
+ commit('openSettingsModal', 'user')
},
pushGlobalNotice (
{ commit, dispatch, state },
@@ -151,10 +201,519 @@ const interfaceMod = {
commit('setLayoutType', wideLayout ? 'wide' : normalOrMobile)
}
},
+ queryLocalFonts ({ commit, dispatch, state }) {
+ if (state.localFonts !== null) return
+ commit('setFontsList', [])
+ if (!state.browserSupport.localFonts) {
+ return
+ }
+ window
+ .queryLocalFonts()
+ .then((fonts) => {
+ commit('setFontsList', fonts)
+ })
+ .catch((e) => {
+ dispatch('pushGlobalNotice', {
+ messageKey: 'settings.style.themes3.font.font_list_unavailable',
+ messageArgs: {
+ error: e
+ },
+ level: 'error'
+ })
+ })
+ },
setLastTimeline ({ commit }, value) {
commit('setLastTimeline', value)
+ },
+ async fetchPalettesIndex ({ commit, state }) {
+ try {
+ const value = await getResourcesIndex('/static/palettes/index.json')
+ commit('setInstanceOption', { name: 'palettesIndex', value })
+ return value
+ } catch (e) {
+ console.error('Could not fetch palettes index', e)
+ commit('setInstanceOption', { name: 'palettesIndex', value: { _error: e } })
+ return Promise.resolve({})
+ }
+ },
+ setPalette ({ dispatch, commit }, value) {
+ dispatch('resetThemeV3Palette')
+ dispatch('resetThemeV2')
+
+ commit('setOption', { name: 'palette', value })
+
+ dispatch('applyTheme', { recompile: true })
+ },
+ setPaletteCustom ({ dispatch, commit }, value) {
+ dispatch('resetThemeV3Palette')
+ dispatch('resetThemeV2')
+
+ commit('setOption', { name: 'paletteCustomData', value })
+
+ dispatch('applyTheme', { recompile: true })
+ },
+ async fetchStylesIndex ({ commit, state }) {
+ try {
+ const value = await getResourcesIndex(
+ '/static/styles/index.json',
+ deserialize
+ )
+ commit('setInstanceOption', { name: 'stylesIndex', value })
+ return value
+ } catch (e) {
+ console.error('Could not fetch styles index', e)
+ commit('setInstanceOption', { name: 'stylesIndex', value: { _error: e } })
+ return Promise.resolve({})
+ }
+ },
+ setStyle ({ dispatch, commit, state }, value) {
+ dispatch('resetThemeV3')
+ dispatch('resetThemeV2')
+ dispatch('resetThemeV3Palette')
+
+ commit('setOption', { name: 'style', value })
+ state.useStylePalette = true
+
+ dispatch('applyTheme', { recompile: true }).then(() => {
+ state.useStylePalette = false
+ })
+ },
+ setStyleCustom ({ dispatch, commit, state }, value) {
+ dispatch('resetThemeV3')
+ dispatch('resetThemeV2')
+ dispatch('resetThemeV3Palette')
+
+ commit('setOption', { name: 'styleCustomData', value })
+
+ state.useStylePalette = true
+ dispatch('applyTheme', { recompile: true }).then(() => {
+ state.useStylePalette = false
+ })
+ },
+ async fetchThemesIndex ({ commit, state }) {
+ try {
+ const value = await getResourcesIndex('/static/styles.json')
+ commit('setInstanceOption', { name: 'themesIndex', value })
+ return value
+ } catch (e) {
+ console.error('Could not fetch themes index', e)
+ commit('setInstanceOption', { name: 'themesIndex', value: { _error: e } })
+ return Promise.resolve({})
+ }
+ },
+ setTheme ({ dispatch, commit }, value) {
+ dispatch('resetThemeV3')
+ dispatch('resetThemeV3Palette')
+ dispatch('resetThemeV2')
+
+ commit('setOption', { name: 'theme', value })
+
+ dispatch('applyTheme', { recompile: true })
+ },
+ setThemeCustom ({ dispatch, commit }, value) {
+ dispatch('resetThemeV3')
+ dispatch('resetThemeV3Palette')
+ dispatch('resetThemeV2')
+
+ commit('setOption', { name: 'customTheme', value })
+ commit('setOption', { name: 'customThemeSource', value })
+
+ dispatch('applyTheme', { recompile: true })
+ },
+ resetThemeV3 ({ dispatch, commit }) {
+ commit('setOption', { name: 'style', value: null })
+ commit('setOption', { name: 'styleCustomData', value: null })
+ },
+ resetThemeV3Palette ({ dispatch, commit }) {
+ commit('setOption', { name: 'palette', value: null })
+ commit('setOption', { name: 'paletteCustomData', value: null })
+ },
+ resetThemeV2 ({ dispatch, commit }) {
+ commit('setOption', { name: 'theme', value: null })
+ commit('setOption', { name: 'customTheme', value: null })
+ commit('setOption', { name: 'customThemeSource', value: null })
+ },
+ async getThemeData ({ dispatch, commit, rootState, state }) {
+ const getData = async (resource, index, customData, name) => {
+ const capitalizedResource = resource[0].toUpperCase() + resource.slice(1)
+ const result = {}
+
+ if (customData) {
+ result.nameUsed = 'custom' // custom data overrides name
+ result.dataUsed = customData
+ } else {
+ result.nameUsed = name
+
+ if (result.nameUsed == null) {
+ result.dataUsed = null
+ return result
+ }
+
+ let fetchFunc = index[result.nameUsed]
+ // Fallbacks
+ if (!fetchFunc) {
+ if (resource === 'style' || resource === 'palette') {
+ return result
+ }
+ const newName = Object.keys(index)[0]
+ fetchFunc = index[newName]
+ console.warn(`${capitalizedResource} with id '${state.styleNameUsed}' not found, trying back to '${newName}'`)
+ if (!fetchFunc) {
+ console.warn(`${capitalizedResource} doesn't have a fallback, defaulting to stock.`)
+ fetchFunc = () => Promise.resolve(null)
+ }
+ }
+ result.dataUsed = await fetchFunc()
+ }
+ return result
+ }
+
+ const {
+ style: instanceStyleName,
+ palette: instancePaletteName
+ } = rootState.instance
+
+ let {
+ theme: instanceThemeV2Name,
+ themesIndex,
+ stylesIndex,
+ palettesIndex
+ } = rootState.instance
+
+ const {
+ style: userStyleName,
+ styleCustomData: userStyleCustomData,
+ palette: userPaletteName,
+ paletteCustomData: userPaletteCustomData
+ } = rootState.config
+
+ let {
+ theme: userThemeV2Name,
+ customTheme: userThemeV2Snapshot,
+ customThemeSource: userThemeV2Source
+ } = rootState.config
+
+ let majorVersionUsed
+
+ console.debug(
+ `User V3 palette: ${userPaletteName}, style: ${userStyleName} , custom: ${!!userStyleCustomData}`
+ )
+ console.debug(
+ `User V2 name: ${userThemeV2Name}, source: ${!!userThemeV2Source}, snapshot: ${!!userThemeV2Snapshot}`
+ )
+
+ console.debug(`Instance V3 palette: ${instancePaletteName}, style: ${instanceStyleName}`)
+ console.debug('Instance V2 theme: ' + instanceThemeV2Name)
+
+ if (userPaletteName || userPaletteCustomData ||
+ userStyleName || userStyleCustomData ||
+ (
+ // User V2 overrides instance V3
+ (instancePaletteName ||
+ instanceStyleName) &&
+ instanceThemeV2Name == null &&
+ userThemeV2Name == null
+ )
+ ) {
+ // Palette and/or style overrides V2 themes
+ instanceThemeV2Name = null
+ userThemeV2Name = null
+ userThemeV2Source = null
+ userThemeV2Snapshot = null
+
+ majorVersionUsed = 'v3'
+ } else if (
+ (userThemeV2Name ||
+ userThemeV2Snapshot ||
+ userThemeV2Source ||
+ instanceThemeV2Name)
+ ) {
+ majorVersionUsed = 'v2'
+ } else {
+ // if all fails fallback to v3
+ majorVersionUsed = 'v3'
+ }
+
+ if (majorVersionUsed === 'v3') {
+ const result = await Promise.all([
+ dispatch('fetchPalettesIndex'),
+ dispatch('fetchStylesIndex')
+ ])
+
+ palettesIndex = result[0]
+ stylesIndex = result[1]
+ } else {
+ // Promise.all just to be uniform with v3
+ const result = await Promise.all([
+ dispatch('fetchThemesIndex')
+ ])
+
+ themesIndex = result[0]
+ }
+
+ state.themeVersion = majorVersionUsed
+
+ console.debug('Version used', majorVersionUsed)
+
+ if (majorVersionUsed === 'v3') {
+ state.themeDataUsed = null
+ state.themeNameUsed = null
+
+ const style = await getData(
+ 'style',
+ stylesIndex,
+ userStyleCustomData,
+ userStyleName || instanceStyleName
+ )
+ state.styleNameUsed = style.nameUsed
+ state.styleDataUsed = style.dataUsed
+
+ let firstStylePaletteName = null
+ style
+ .dataUsed
+ ?.filter(x => x.component === '@palette')
+ .map(x => {
+ const cleanDirectives = Object.fromEntries(
+ Object
+ .entries(x.directives)
+ .filter(([k, v]) => k)
+ )
+
+ return { name: x.variant, ...cleanDirectives }
+ })
+ .forEach(palette => {
+ const key = 'style.' + palette.name.toLowerCase().replace(/ /g, '_')
+ if (!firstStylePaletteName) firstStylePaletteName = key
+ palettesIndex[key] = () => Promise.resolve(palette)
+ })
+
+ const palette = await getData(
+ 'palette',
+ palettesIndex,
+ userPaletteCustomData,
+ state.useStylePalette ? firstStylePaletteName : (userPaletteName || instancePaletteName)
+ )
+
+ if (state.useStylePalette) {
+ commit('setOption', { name: 'palette', value: firstStylePaletteName })
+ }
+
+ state.paletteNameUsed = palette.nameUsed
+ state.paletteDataUsed = palette.dataUsed
+
+ if (state.paletteDataUsed) {
+ state.paletteDataUsed.link = state.paletteDataUsed.link || state.paletteDataUsed.accent
+ state.paletteDataUsed.accent = state.paletteDataUsed.accent || state.paletteDataUsed.link
+ }
+ if (Array.isArray(state.paletteDataUsed)) {
+ const [
+ name,
+ bg,
+ fg,
+ text,
+ link,
+ cRed = '#FF0000',
+ cGreen = '#00FF00',
+ cBlue = '#0000FF',
+ cOrange = '#E3FF00'
+ ] = palette.dataUsed
+ state.paletteDataUsed = {
+ name,
+ bg,
+ fg,
+ text,
+ link,
+ accent: link,
+ cRed,
+ cBlue,
+ cGreen,
+ cOrange
+ }
+ }
+ console.debug('Palette data used', palette.dataUsed)
+ } else {
+ state.styleNameUsed = null
+ state.styleDataUsed = null
+ state.paletteNameUsed = null
+ state.paletteDataUsed = null
+
+ const theme = await getData(
+ 'theme',
+ themesIndex,
+ userThemeV2Source || userThemeV2Snapshot,
+ userThemeV2Name || instanceThemeV2Name
+ )
+ state.themeNameUsed = theme.nameUsed
+ state.themeDataUsed = theme.dataUsed
+ }
+ },
+ async applyTheme (
+ { dispatch, commit, rootState, state },
+ { recompile = false } = {}
+ ) {
+ const {
+ forceThemeRecompilation,
+ themeDebug,
+ theme3hacks
+ } = rootState.config
+ // If we're not not forced to recompile try using
+ // cache (tryLoadCache return true if load successful)
+
+ const forceRecompile = forceThemeRecompilation || recompile
+ if (!forceRecompile && !themeDebug && await tryLoadCache()) {
+ return commit('setThemeApplied')
+ }
+ window.splashUpdate('splash.theme')
+ await dispatch('getThemeData')
+
+ try {
+ const paletteIss = (() => {
+ if (!state.paletteDataUsed) return null
+ const result = {
+ component: 'Root',
+ directives: {}
+ }
+
+ Object
+ .entries(state.paletteDataUsed)
+ .filter(([k]) => k !== 'name')
+ .forEach(([k, v]) => {
+ let issRootDirectiveName
+ switch (k) {
+ case 'background':
+ issRootDirectiveName = 'bg'
+ break
+ case 'foreground':
+ issRootDirectiveName = 'fg'
+ break
+ default:
+ issRootDirectiveName = k
+ }
+ result.directives['--' + issRootDirectiveName] = 'color | ' + v
+ })
+ return result
+ })()
+
+ const theme2ruleset = state.themeDataUsed && convertTheme2To3(normalizeThemeData(state.themeDataUsed))
+ const hacks = []
+
+ Object.entries(theme3hacks).forEach(([key, value]) => {
+ switch (key) {
+ case 'fonts': {
+ Object.entries(theme3hacks.fonts).forEach(([fontKey, font]) => {
+ if (!font?.family) return
+ switch (fontKey) {
+ case 'interface':
+ hacks.push({
+ component: 'Root',
+ directives: {
+ '--font': 'generic | ' + font.family
+ }
+ })
+ break
+ case 'input':
+ hacks.push({
+ component: 'Input',
+ directives: {
+ '--font': 'generic | ' + font.family
+ }
+ })
+ break
+ case 'post':
+ hacks.push({
+ component: 'RichContent',
+ directives: {
+ '--font': 'generic | ' + font.family
+ }
+ })
+ break
+ case 'monospace':
+ hacks.push({
+ component: 'Root',
+ directives: {
+ '--monoFont': 'generic | ' + font.family
+ }
+ })
+ break
+ }
+ })
+ break
+ }
+ case 'underlay': {
+ if (value !== 'none') {
+ const newRule = {
+ component: 'Underlay',
+ directives: {}
+ }
+ if (value === 'opaque') {
+ newRule.directives.opacity = 1
+ newRule.directives.background = '--wallpaper'
+ }
+ if (value === 'transparent') {
+ newRule.directives.opacity = 0
+ }
+ hacks.push(newRule)
+ }
+ break
+ }
+ }
+ })
+
+ const rulesetArray = [
+ theme2ruleset,
+ state.styleDataUsed,
+ paletteIss,
+ hacks
+ ].filter(x => x)
+
+ return applyTheme(
+ rulesetArray.flat(),
+ () => commit('setThemeApplied'),
+ () => {},
+ themeDebug
+ )
+ } catch (e) {
+ window.splashError(e)
+ }
}
}
}
export default interfaceMod
+
+export const normalizeThemeData = (input) => {
+ let themeData, themeSource
+
+ if (input.themeFileVerison === 1) {
+ // this might not be even used at all, some leftover of unimplemented code in V2 editor
+ return generatePreset(input).theme
+ } else if (
+ Object.prototype.hasOwnProperty.call(input, '_pleroma_theme_version') ||
+ Object.prototype.hasOwnProperty.call(input, 'source') ||
+ Object.prototype.hasOwnProperty.call(input, 'theme')
+ ) {
+ // We got passed a full theme file
+ themeData = input.theme
+ themeSource = input.source
+ } else if (
+ Object.prototype.hasOwnProperty.call(input, 'themeEngineVersion') ||
+ Object.prototype.hasOwnProperty.call(input, 'colors')
+ ) {
+ // We got passed a source/snapshot
+ themeData = input
+ themeSource = input
+ }
+ // New theme presets don't have 'theme' property, they use 'source'
+
+ let out // shout, shout let it all out
+ if (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION) {
+ // There are some themes in wild that have completely broken source
+ out = { ...(themeData || {}), ...themeSource }
+ } else {
+ out = themeData
+ }
+
+ // generatePreset here basically creates/updates "snapshot",
+ // while also fixing the 2.2 -> 2.3 colors/shadows/etc
+ return generatePreset(out).theme
+}
diff --git a/src/modules/lists.js b/src/modules/lists.js
@@ -0,0 +1,130 @@
+import { remove, find } from 'lodash'
+
+export const defaultState = {
+ allLists: [],
+ allListsObject: {}
+}
+
+export const mutations = {
+ setLists (state, value) {
+ state.allLists = value
+ },
+ setList (state, { listId, title }) {
+ if (!state.allListsObject[listId]) {
+ state.allListsObject[listId] = { accountIds: [] }
+ }
+ state.allListsObject[listId].title = title
+
+ const entry = find(state.allLists, { id: listId })
+ if (!entry) {
+ state.allLists.push({ id: listId, title })
+ } else {
+ entry.title = title
+ }
+ },
+ setListAccounts (state, { listId, accountIds }) {
+ if (!state.allListsObject[listId]) {
+ state.allListsObject[listId] = { accountIds: [] }
+ }
+ state.allListsObject[listId].accountIds = accountIds
+ },
+ addListAccount (state, { listId, accountId }) {
+ if (!state.allListsObject[listId]) {
+ state.allListsObject[listId] = { accountIds: [] }
+ }
+ state.allListsObject[listId].accountIds.push(accountId)
+ },
+ removeListAccount (state, { listId, accountId }) {
+ if (!state.allListsObject[listId]) {
+ state.allListsObject[listId] = { accountIds: [] }
+ }
+ const { accountIds } = state.allListsObject[listId]
+ const set = new Set(accountIds)
+ set.delete(accountId)
+ state.allListsObject[listId].accountIds = [...set]
+ },
+ deleteList (state, { listId }) {
+ delete state.allListsObject[listId]
+ remove(state.allLists, list => list.id === listId)
+ }
+}
+
+const actions = {
+ setLists ({ commit }, value) {
+ commit('setLists', value)
+ },
+ createList ({ rootState, commit }, { title }) {
+ return rootState.api.backendInteractor.createList({ title })
+ .then((list) => {
+ commit('setList', { listId: list.id, title })
+ return list
+ })
+ },
+ fetchList ({ rootState, commit }, { listId }) {
+ return rootState.api.backendInteractor.getList({ listId })
+ .then((list) => commit('setList', { listId: list.id, title: list.title }))
+ },
+ fetchListAccounts ({ rootState, commit }, { listId }) {
+ return rootState.api.backendInteractor.getListAccounts({ listId })
+ .then((accountIds) => commit('setListAccounts', { listId, accountIds }))
+ },
+ setList ({ rootState, commit }, { listId, title }) {
+ rootState.api.backendInteractor.updateList({ listId, title })
+ commit('setList', { listId, title })
+ },
+ setListAccounts ({ rootState, commit }, { listId, accountIds }) {
+ const saved = rootState.lists.allListsObject[listId].accountIds || []
+ const added = accountIds.filter(id => !saved.includes(id))
+ const removed = saved.filter(id => !accountIds.includes(id))
+ commit('setListAccounts', { listId, accountIds })
+ if (added.length > 0) {
+ rootState.api.backendInteractor.addAccountsToList({ listId, accountIds: added })
+ }
+ if (removed.length > 0) {
+ rootState.api.backendInteractor.removeAccountsFromList({ listId, accountIds: removed })
+ }
+ },
+ addListAccount ({ rootState, commit }, { listId, accountId }) {
+ return rootState
+ .api
+ .backendInteractor
+ .addAccountsToList({ listId, accountIds: [accountId] })
+ .then((result) => {
+ commit('addListAccount', { listId, accountId })
+ return result
+ })
+ },
+ removeListAccount ({ rootState, commit }, { listId, accountId }) {
+ return rootState
+ .api
+ .backendInteractor
+ .removeAccountsFromList({ listId, accountIds: [accountId] })
+ .then((result) => {
+ commit('removeListAccount', { listId, accountId })
+ return result
+ })
+ },
+ deleteList ({ rootState, commit }, { listId }) {
+ rootState.api.backendInteractor.deleteList({ listId })
+ commit('deleteList', { listId })
+ }
+}
+
+export const getters = {
+ findListTitle: state => id => {
+ if (!state.allListsObject[id]) return
+ return state.allListsObject[id].title
+ },
+ findListAccounts: state => id => {
+ return [...state.allListsObject[id].accountIds]
+ }
+}
+
+const lists = {
+ state: defaultState,
+ mutations,
+ actions,
+ getters
+}
+
+export default lists
diff --git a/src/modules/notifications.js b/src/modules/notifications.js
@@ -0,0 +1,169 @@
+import apiService from '../services/api/api.service.js'
+
+import {
+ isStatusNotification,
+ isValidNotification,
+ maybeShowNotification
+} from '../services/notification_utils/notification_utils.js'
+
+import {
+ closeDesktopNotification,
+ closeAllDesktopNotifications
+} from '../services/desktop_notification_utils/desktop_notification_utils.js'
+
+const emptyNotifications = () => ({
+ desktopNotificationSilence: true,
+ maxId: 0,
+ minId: Number.POSITIVE_INFINITY,
+ data: [],
+ idStore: {},
+ loading: false
+})
+
+export const defaultState = () => ({
+ ...emptyNotifications()
+})
+
+export const notifications = {
+ state: defaultState(),
+ mutations: {
+ addNewNotifications (state, { notifications }) {
+ notifications.forEach(notification => {
+ state.data.push(notification)
+ state.idStore[notification.id] = notification
+ })
+ },
+ clearNotifications (state) {
+ state = emptyNotifications()
+ },
+ updateNotificationsMinMaxId (state, id) {
+ state.maxId = id > state.maxId ? id : state.maxId
+ state.minId = id < state.minId ? id : state.minId
+ },
+ setNotificationsLoading (state, { value }) {
+ state.loading = value
+ },
+ setNotificationsSilence (state, { value }) {
+ state.desktopNotificationSilence = value
+ },
+ markNotificationsAsSeen (state) {
+ state.data.forEach((notification) => {
+ notification.seen = true
+ })
+ },
+ markSingleNotificationAsSeen (state, { id }) {
+ const notification = state.idStore[id]
+ if (notification) notification.seen = true
+ },
+ dismissNotification (state, { id }) {
+ state.data = state.data.filter(n => n.id !== id)
+ delete state.idStore[id]
+ },
+ updateNotification (state, { id, updater }) {
+ const notification = state.idStore[id]
+ notification && updater(notification)
+ }
+ },
+ actions: {
+ addNewNotifications (store, { notifications, older }) {
+ const { commit, dispatch, state, rootState } = store
+ const validNotifications = notifications.filter((notification) => {
+ // If invalid notification, update ids but don't add it to store
+ if (!isValidNotification(notification)) {
+ console.error('Invalid notification:', notification)
+ commit('updateNotificationsMinMaxId', notification.id)
+ return false
+ }
+ return true
+ })
+
+ const statusNotifications = validNotifications.filter(notification => isStatusNotification(notification.type) && notification.status)
+
+ // Synchronous commit to add all the statuses
+ commit('addNewStatuses', { statuses: statusNotifications.map(notification => notification.status) })
+
+ // Update references to statuses in notifications to ones in the store
+ statusNotifications.forEach(notification => {
+ const id = notification.status.id
+ const referenceStatus = rootState.statuses.allStatusesObject[id]
+
+ if (referenceStatus) {
+ notification.status = referenceStatus
+ }
+ })
+
+ validNotifications.forEach(notification => {
+ if (notification.type === 'pleroma:report') {
+ dispatch('addReport', notification.report)
+ }
+
+ if (notification.type === 'pleroma:emoji_reaction') {
+ dispatch('fetchEmojiReactionsBy', notification.status.id)
+ }
+
+ // Only add a new notification if we don't have one for the same action
+ // eslint-disable-next-line no-prototype-builtins
+ if (!state.idStore.hasOwnProperty(notification.id)) {
+ commit('updateNotificationsMinMaxId', notification.id)
+ commit('addNewNotifications', { notifications: [notification] })
+
+ maybeShowNotification(store, notification)
+ } else if (notification.seen) {
+ state.idStore[notification.id].seen = true
+ }
+ })
+ },
+ notificationClicked ({ state, dispatch }, { id }) {
+ const notification = state.idStore[id]
+ const { type, seen } = notification
+
+ if (!seen) {
+ switch (type) {
+ case 'mention':
+ case 'pleroma:report':
+ case 'follow_request':
+ break
+ default:
+ dispatch('markSingleNotificationAsSeen', { id })
+ }
+ }
+ },
+ setNotificationsLoading ({ rootState, commit }, { value }) {
+ commit('setNotificationsLoading', { value })
+ },
+ setNotificationsSilence ({ rootState, commit }, { value }) {
+ commit('setNotificationsSilence', { value })
+ },
+ markNotificationsAsSeen ({ rootState, state, commit }) {
+ commit('markNotificationsAsSeen')
+ apiService.markNotificationsAsSeen({
+ id: state.maxId,
+ credentials: rootState.users.currentUser.credentials
+ }).then(() => {
+ closeAllDesktopNotifications(rootState)
+ })
+ },
+ markSingleNotificationAsSeen ({ rootState, commit }, { id }) {
+ commit('markSingleNotificationAsSeen', { id })
+ apiService.markNotificationsAsSeen({
+ single: true,
+ id,
+ credentials: rootState.users.currentUser.credentials
+ }).then(() => {
+ closeDesktopNotification(rootState, { id })
+ })
+ },
+ dismissNotificationLocal ({ rootState, commit }, { id }) {
+ commit('dismissNotification', { id })
+ },
+ dismissNotification ({ rootState, commit }, { id }) {
+ commit('dismissNotification', { id })
+ rootState.api.backendInteractor.dismissNotification({ id })
+ },
+ updateNotification ({ rootState, commit }, { id, updater }) {
+ commit('updateNotification', { id, updater })
+ }
+ }
+}
+
+export default notifications
diff --git a/src/modules/postStatus.js b/src/modules/postStatus.js
@@ -10,6 +10,9 @@ const postStatus = {
},
closePostStatusModal (state) {
state.modalActivated = false
+ },
+ resetPostStatusModal (state) {
+ state.params = null
}
},
actions: {
@@ -18,6 +21,9 @@ const postStatus = {
},
closePostStatusModal ({ commit }) {
commit('closePostStatusModal')
+ },
+ resetPostStatusModal ({ commit }) {
+ commit('resetPostStatusModal')
}
}
}
diff --git a/src/modules/profileConfig.js b/src/modules/profileConfig.js
@@ -0,0 +1,140 @@
+import { get, set } from 'lodash'
+
+const defaultApi = ({ rootState, commit }, { path, value }) => {
+ const params = {}
+ set(params, path, value)
+ return rootState
+ .api
+ .backendInteractor
+ .updateProfile({ params })
+ .then(result => {
+ commit('addNewUsers', [result])
+ commit('setCurrentUser', result)
+ })
+}
+
+const notificationsApi = ({ rootState, commit }, { path, value, oldValue }) => {
+ const settings = {}
+ set(settings, path, value)
+ return rootState
+ .api
+ .backendInteractor
+ .updateNotificationSettings({ settings })
+ .then(result => {
+ if (result.status === 'success') {
+ commit('confirmProfileOption', { name, value })
+ } else {
+ commit('confirmProfileOption', { name, value: oldValue })
+ }
+ })
+}
+
+/**
+ * Map that stores relation between path for reading (from user profile),
+ * for writing (into API) an what API to use.
+ *
+ * Shorthand - instead of { get, set, api? } object it's possible to use string
+ * in case default api is used and get = set
+ *
+ * If no api is specified, defaultApi is used (see above)
+ */
+export const settingsMap = {
+ defaultScope: 'source.privacy',
+ defaultNSFW: 'source.sensitive', // BROKEN: pleroma/pleroma#2837
+ stripRichContent: {
+ get: 'source.pleroma.no_rich_text',
+ set: 'no_rich_text'
+ },
+ // Privacy
+ locked: 'locked',
+ acceptChatMessages: {
+ get: 'pleroma.accepts_chat_messages',
+ set: 'accepts_chat_messages'
+ },
+ allowFollowingMove: {
+ get: 'pleroma.allow_following_move',
+ set: 'allow_following_move'
+ },
+ discoverable: {
+ get: 'source.pleroma.discoverable',
+ set: 'discoverable'
+ },
+ hideFavorites: {
+ get: 'pleroma.hide_favorites',
+ set: 'hide_favorites'
+ },
+ hideFollowers: {
+ get: 'pleroma.hide_followers',
+ set: 'hide_followers'
+ },
+ hideFollows: {
+ get: 'pleroma.hide_follows',
+ set: 'hide_follows'
+ },
+ hideFollowersCount: {
+ get: 'pleroma.hide_followers_count',
+ set: 'hide_followers_count'
+ },
+ hideFollowsCount: {
+ get: 'pleroma.hide_follows_count',
+ set: 'hide_follows_count'
+ },
+ // NotificationSettingsAPIs
+ webPushHideContents: {
+ get: 'pleroma.notification_settings.hide_notification_contents',
+ set: 'hide_notification_contents',
+ api: notificationsApi
+ },
+ blockNotificationsFromStrangers: {
+ get: 'pleroma.notification_settings.block_from_strangers',
+ set: 'block_from_strangers',
+ api: notificationsApi
+ }
+}
+
+export const defaultState = Object.fromEntries(Object.keys(settingsMap).map(key => [key, null]))
+
+const profileConfig = {
+ state: { ...defaultState },
+ mutations: {
+ confirmProfileOption (state, { name, value }) {
+ set(state, name, value)
+ },
+ wipeProfileOption (state, { name }) {
+ set(state, name, null)
+ },
+ wipeAllProfileOptions (state) {
+ Object.keys(settingsMap).forEach(key => {
+ set(state, key, null)
+ })
+ },
+ // Set the settings based on their path location
+ setCurrentUser (state, user) {
+ Object.entries(settingsMap).forEach((map) => {
+ const [name, value] = map
+ const { get: path = value } = value
+ set(state, name, get(user._original, path))
+ })
+ }
+ },
+ actions: {
+ setProfileOption ({ rootState, state, commit, dispatch }, { name, value }) {
+ const oldValue = get(state, name)
+ const map = settingsMap[name]
+ if (!map) throw new Error('Invalid server-side setting')
+ const { set: path = map, api = defaultApi } = map
+ commit('wipeProfileOption', { name })
+
+ api({ rootState, commit }, { path, value, oldValue })
+ .catch((e) => {
+ console.warn('Error setting server-side option:', e)
+ commit('confirmProfileOption', { name, value: oldValue })
+ })
+ },
+ logout ({ commit }) {
+ commit('wipeAllProfileOptions')
+ }
+ }
+}
+
+export default profileConfig
diff --git a/src/modules/reports.js b/src/modules/reports.js
@@ -2,20 +2,29 @@ import filter from 'lodash/filter'
const reports = {
state: {
- userId: null,
- statuses: [],
- preTickedIds: [],
- modalActivated: false
+ reportModal: {
+ userId: null,
+ statuses: [],
+ preTickedIds: [],
+ activated: false
+ },
+ reports: {}
},
mutations: {
openUserReportingModal (state, { userId, statuses, preTickedIds }) {
- state.userId = userId
- state.statuses = statuses
- state.preTickedIds = preTickedIds
- state.modalActivated = true
+ state.reportModal.userId = userId
+ state.reportModal.statuses = statuses
+ state.reportModal.preTickedIds = preTickedIds
+ state.reportModal.activated = true
},
closeUserReportingModal (state) {
- state.modalActivated = false
+ state.reportModal.activated = false
+ },
+ setReportState (reportsState, { id, state }) {
+ reportsState.reports[id].state = state
+ },
+ addReport (state, report) {
+ state.reports[report.id] = report
}
},
actions: {
@@ -31,6 +40,23 @@ const reports = {
},
closeUserReportingModal ({ commit }) {
commit('closeUserReportingModal')
+ },
+ setReportState ({ commit, dispatch, rootState }, { id, state }) {
+ const oldState = rootState.reports.reports[id].state
+ commit('setReportState', { id, state })
+ rootState.api.backendInteractor.setReportState({ id, state }).catch(e => {
+ console.error('Failed to set report state', e)
+ dispatch('pushGlobalNotice', {
+ level: 'error',
+ messageKey: 'general.generic_error_message',
+ messageArgs: [e.message],
+ timeout: 5000
+ })
+ commit('setReportState', { id, state: oldState })
+ })
+ },
+ addReport ({ commit }, report) {
+ commit('addReport', report)
}
}
}
diff --git a/src/modules/serverSideConfig.js b/src/modules/serverSideConfig.js
@@ -1,140 +0,0 @@
-import { get, set } from 'lodash'
-
-const defaultApi = ({ rootState, commit }, { path, value }) => {
- const params = {}
- set(params, path, value)
- return rootState
- .api
- .backendInteractor
- .updateProfile({ params })
- .then(result => {
- commit('addNewUsers', [result])
- commit('setCurrentUser', result)
- })
-}
-
-const notificationsApi = ({ rootState, commit }, { path, value, oldValue }) => {
- const settings = {}
- set(settings, path, value)
- return rootState
- .api
- .backendInteractor
- .updateNotificationSettings({ settings })
- .then(result => {
- if (result.status === 'success') {
- commit('confirmServerSideOption', { name, value })
- } else {
- commit('confirmServerSideOption', { name, value: oldValue })
- }
- })
-}
-
-/**
- * Map that stores relation between path for reading (from user profile),
- * for writing (into API) an what API to use.
- *
- * Shorthand - instead of { get, set, api? } object it's possible to use string
- * in case default api is used and get = set
- *
- * If no api is specified, defaultApi is used (see above)
- */
-export const settingsMap = {
- 'defaultScope': 'source.privacy',
- 'defaultNSFW': 'source.sensitive', // BROKEN: pleroma/pleroma#2837
- 'stripRichContent': {
- get: 'source.pleroma.no_rich_text',
- set: 'no_rich_text'
- },
- // Privacy
- 'locked': 'locked',
- 'acceptChatMessages': {
- get: 'pleroma.accepts_chat_messages',
- set: 'accepts_chat_messages'
- },
- 'allowFollowingMove': {
- get: 'pleroma.allow_following_move',
- set: 'allow_following_move'
- },
- 'discoverable': {
- get: 'source.pleroma.discoverable',
- set: 'discoverable'
- },
- 'hideFavorites': {
- get: 'pleroma.hide_favorites',
- set: 'hide_favorites'
- },
- 'hideFollowers': {
- get: 'pleroma.hide_followers',
- set: 'hide_followers'
- },
- 'hideFollows': {
- get: 'pleroma.hide_follows',
- set: 'hide_follows'
- },
- 'hideFollowersCount': {
- get: 'pleroma.hide_followers_count',
- set: 'hide_followers_count'
- },
- 'hideFollowsCount': {
- get: 'pleroma.hide_follows_count',
- set: 'hide_follows_count'
- },
- // NotificationSettingsAPIs
- 'webPushHideContents': {
- get: 'pleroma.notification_settings.hide_notification_contents',
- set: 'hide_notification_contents',
- api: notificationsApi
- },
- 'blockNotificationsFromStrangers': {
- get: 'pleroma.notification_settings.block_from_strangers',
- set: 'block_from_strangers',
- api: notificationsApi
- }
-}
-
-export const defaultState = Object.fromEntries(Object.keys(settingsMap).map(key => [key, null]))
-
-const serverSideConfig = {
- state: { ...defaultState },
- mutations: {
- confirmServerSideOption (state, { name, value }) {
- set(state, name, value)
- },
- wipeServerSideOption (state, { name }) {
- set(state, name, null)
- },
- wipeAllServerSideOptions (state) {
- Object.keys(settingsMap).forEach(key => {
- set(state, key, null)
- })
- },
- // Set the settings based on their path location
- setCurrentUser (state, user) {
- Object.entries(settingsMap).forEach((map) => {
- const [name, value] = map
- const { get: path = value } = value
- set(state, name, get(user._original, path))
- })
- }
- },
- actions: {
- setServerSideOption ({ rootState, state, commit, dispatch }, { name, value }) {
- const oldValue = get(state, name)
- const map = settingsMap[name]
- if (!map) throw new Error('Invalid server-side setting')
- const { set: path = map, api = defaultApi } = map
- commit('wipeServerSideOption', { name })
-
- api({ rootState, commit }, { path, value, oldValue })
- .catch((e) => {
- console.warn('Error setting server-side option:', e)
- commit('confirmServerSideOption', { name, value: oldValue })
- })
- },
- logout ({ commit }) {
- commit('wipeAllServerSideOptions')
- }
- }
-}
-
-export default serverSideConfig
diff --git a/src/modules/serverSideStorage.js b/src/modules/serverSideStorage.js
@@ -0,0 +1,435 @@
+import { toRaw } from 'vue'
+import { isEqual, cloneDeep, set, get, clamp, flatten, groupBy, findLastIndex, takeRight, uniqWith } from 'lodash'
+import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js'
+
+export const VERSION = 1
+export const NEW_USER_DATE = new Date('2022-08-04') // date of writing this, basically
+
+export const COMMAND_TRIM_FLAGS = 1000
+export const COMMAND_TRIM_FLAGS_AND_RESET = 1001
+
+export const defaultState = {
+ // do we need to update data on server?
+ dirty: false,
+ // storage of flags - stuff that can only be set and incremented
+ flagStorage: {
+ updateCounter: 0, // Counter for most recent update notification seen
+ reset: 0 // special flag that can be used to force-reset all flags, debug purposes only
+ // special reset codes:
+ // 1000: trim keys to those known by currently running FE
+ // 1001: same as above + reset everything to 0
+ },
+ prefsStorage: {
+ _journal: [],
+ simple: {
+ dontShowUpdateNotifs: false,
+ collapseNav: false
+ },
+ collections: {
+ pinnedNavItems: ['home', 'dms', 'chats']
+ }
+ },
+ // raw data
+ raw: null,
+ // local cache
+ cache: null
+}
+
+export const newUserFlags = {
+ ...defaultState.flagStorage,
+ updateCounter: CURRENT_UPDATE_COUNTER // new users don't need to see update notification
+}
+
+export const _moveItemInArray = (array, value, movement) => {
+ const oldIndex = array.indexOf(value)
+ const newIndex = oldIndex + movement
+ const newArray = [...array]
+ // remove old
+ newArray.splice(oldIndex, 1)
+ // add new
+ newArray.splice(clamp(newIndex, 0, newArray.length + 1), 0, value)
+ return newArray
+}
+
+const _wrapData = (data, userName) => ({
+ ...data,
+ _user: userName,
+ _timestamp: Date.now(),
+ _version: VERSION
+})
+
+const _checkValidity = (data) => data._timestamp > 0 && data._version > 0
+
+const _verifyPrefs = (state) => {
+ state.prefsStorage = state.prefsStorage || {
+ simple: {},
+ collections: {}
+ }
+ Object.entries(defaultState.prefsStorage.simple).forEach(([k, v]) => {
+ if (typeof v === 'number' || typeof v === 'boolean') return
+ console.warn(`Preference simple.${k} as invalid type, reinitializing`)
+ set(state.prefsStorage.simple, k, defaultState.prefsStorage.simple[k])
+ })
+ Object.entries(defaultState.prefsStorage.collections).forEach(([k, v]) => {
+ if (Array.isArray(v)) return
+ console.warn(`Preference collections.${k} as invalid type, reinitializing`)
+ set(state.prefsStorage.collections, k, defaultState.prefsStorage.collections[k])
+ })
+}
+
+export const _getRecentData = (cache, live) => {
+ const result = { recent: null, stale: null, needUpload: false }
+ const cacheValid = _checkValidity(cache || {})
+ const liveValid = _checkValidity(live || {})
+ if (!liveValid && cacheValid) {
+ result.needUpload = true
+ console.debug('Nothing valid stored on server, assuming cache to be source of truth')
+ result.recent = cache
+ result.stale = live
+ } else if (!cacheValid && liveValid) {
+ console.debug('Valid storage on server found, no local cache found, using live as source of truth')
+ result.recent = live
+ result.stale = cache
+ } else if (cacheValid && liveValid) {
+ console.debug('Both sources have valid data, figuring things out...')
+ if (live._timestamp === cache._timestamp && live._version === cache._version) {
+ console.debug('Same version/timestamp on both source, source of truth irrelevant')
+ result.recent = cache
+ result.stale = live
+ } else {
+ console.debug('Different timestamp, figuring out which one is more recent')
+ if (live._timestamp < cache._timestamp) {
+ result.recent = cache
+ result.stale = live
+ } else {
+ result.recent = live
+ result.stale = cache
+ }
+ }
+ } else {
+ console.debug('Both sources are invalid, start from scratch')
+ result.needUpload = true
+ }
+ return result
+}
+
+export const _getAllFlags = (recent, stale) => {
+ return Array.from(new Set([
+ ...Object.keys(toRaw((recent || {}).flagStorage || {})),
+ ...Object.keys(toRaw((stale || {}).flagStorage || {}))
+ ]))
+}
+
+export const _mergeFlags = (recent, stale, allFlagKeys) => {
+ if (!stale.flagStorage) return recent.flagStorage
+ if (!recent.flagStorage) return stale.flagStorage
+ return Object.fromEntries(allFlagKeys.map(flag => {
+ const recentFlag = recent.flagStorage[flag]
+ const staleFlag = stale.flagStorage[flag]
+ // use flag that is of higher value
+ return [flag, Number((recentFlag > staleFlag ? recentFlag : staleFlag) || 0)]
+ }))
+}
+
+const _mergeJournal = (...journals) => {
+ // Ignore invalid journal entries
+ const allJournals = flatten(
+ journals.map(j => Array.isArray(j) ? j : [])
+ ).filter(entry =>
+ Object.prototype.hasOwnProperty.call(entry, 'path') &&
+ Object.prototype.hasOwnProperty.call(entry, 'operation') &&
+ Object.prototype.hasOwnProperty.call(entry, 'args') &&
+ Object.prototype.hasOwnProperty.call(entry, 'timestamp')
+ )
+ const grouped = groupBy(allJournals, 'path')
+ const trimmedGrouped = Object.entries(grouped).map(([path, journal]) => {
+ // side effect
+ journal.sort((a, b) => a.timestamp > b.timestamp ? 1 : -1)
+
+ if (path.startsWith('collections')) {
+ const lastRemoveIndex = findLastIndex(journal, ({ operation }) => operation === 'removeFromCollection')
+ // everything before last remove is unimportant
+ let remainder
+ if (lastRemoveIndex > 0) {
+ remainder = journal.slice(lastRemoveIndex)
+ } else {
+ // everything else doesn't need trimming
+ remainder = journal
+ }
+ return uniqWith(remainder, (a, b) => {
+ if (a.path !== b.path) { return false }
+ if (a.operation !== b.operation) { return false }
+ if (a.operation === 'addToCollection') {
+ return a.args[0] === b.args[0]
+ }
+ return false
+ })
+ } else if (path.startsWith('simple')) {
+ // Only the last record is important
+ return takeRight(journal)
+ } else {
+ return journal
+ }
+ })
+ return flatten(trimmedGrouped)
+ .sort((a, b) => a.timestamp > b.timestamp ? 1 : -1)
+}
+
+export const _mergePrefs = (recent, stale, allFlagKeys) => {
+ if (!stale) return recent
+ if (!recent) return stale
+ const { _journal: recentJournal, ...recentData } = recent
+ const { _journal: staleJournal } = stale
+ /** Journal entry format:
+ * path: path to entry in prefsStorage
+ * timestamp: timestamp of the change
+ * operation: operation type
+ * arguments: array of arguments, depends on operation type
+ *
+ * currently only supported operation type is "set" which just sets the value
+ * to requested one. Intended only to be used with simple preferences (boolean, number)
+ * shouldn't be used with collections!
+ */
+ const resultOutput = { ...recentData }
+ const totalJournal = _mergeJournal(staleJournal, recentJournal)
+ totalJournal.forEach(({ path, timestamp, operation, command, args }) => {
+ if (path.startsWith('_')) {
+ console.error(`journal contains entry to edit internal (starts with _) field '${path}', something is incorrect here, ignoring.`)
+ return
+ }
+ switch (operation) {
+ case 'set':
+ set(resultOutput, path, args[0])
+ break
+ case 'addToCollection':
+ set(resultOutput, path, Array.from(new Set(get(resultOutput, path)).add(args[0])))
+ break
+ case 'removeFromCollection': {
+ const newSet = new Set(get(resultOutput, path))
+ newSet.delete(args[0])
+ set(resultOutput, path, Array.from(newSet))
+ break
+ }
+ case 'reorderCollection': {
+ const [value, movement] = args
+ set(resultOutput, path, _moveItemInArray(get(resultOutput, path), value, movement))
+ break
+ }
+ default:
+ console.error(`Unknown journal operation: '${operation}', did we forget to run reverse migrations beforehand?`)
+ }
+ })
+ return { ...resultOutput, _journal: totalJournal }
+}
+
+export const _resetFlags = (totalFlags, knownKeys = defaultState.flagStorage) => {
+ let result = { ...totalFlags }
+ const allFlagKeys = Object.keys(totalFlags)
+ // flag reset functionality
+ if (totalFlags.reset >= COMMAND_TRIM_FLAGS && totalFlags.reset <= COMMAND_TRIM_FLAGS_AND_RESET) {
+ console.debug('Received command to trim the flags')
+ const knownKeysSet = new Set(Object.keys(knownKeys))
+
+ // Trim
+ result = {}
+ allFlagKeys.forEach(flag => {
+ if (knownKeysSet.has(flag)) {
+ result[flag] = totalFlags[flag]
+ }
+ })
+
+ // Reset
+ if (totalFlags.reset === COMMAND_TRIM_FLAGS_AND_RESET) {
+ // 1001 - and reset everything to 0
+ console.debug('Received command to reset the flags')
+ Object.keys(knownKeys).forEach(flag => { result[flag] = 0 })
+ }
+ } else if (totalFlags.reset > 0 && totalFlags.reset < 9000) {
+ console.debug('Received command to reset the flags')
+ allFlagKeys.forEach(flag => { result[flag] = 0 })
+ }
+ result.reset = 0
+ return result
+}
+
+export const _doMigrations = (cache) => {
+ if (!cache) return cache
+
+ if (cache._version < VERSION) {
+ console.debug('Local cached data has older version, seeing if there any migrations that can be applied')
+
+ // no migrations right now since we only have one version
+ console.debug('No migrations found')
+ }
+
+ if (cache._version > VERSION) {
+ console.debug('Local cached data has newer version, seeing if there any reverse migrations that can be applied')
+
+ // no reverse migrations right now but we leave a possibility of loading a hotpatch if need be
+ if (window._PLEROMA_HOTPATCH) {
+ if (window._PLEROMA_HOTPATCH.reverseMigrations) {
+ console.debug('Found hotpatch migration, applying')
+ return window._PLEROMA_HOTPATCH.reverseMigrations.call({}, 'serverSideStorage', { from: cache._version, to: VERSION }, cache)
+ }
+ }
+ }
+
+ return cache
+}
+
+export const mutations = {
+ clearServerSideStorage (state, userData) {
+ state = { ...cloneDeep(defaultState) }
+ },
+ setServerSideStorage (state, userData) {
+ const live = userData.storage
+ state.raw = live
+ let cache = state.cache
+ if (cache && cache._user !== userData.fqn) {
+ console.warn('cache belongs to another user! reinitializing local cache!')
+ cache = null
+ }
+
+ cache = _doMigrations(cache)
+
+ let { recent, stale, needsUpload } = _getRecentData(cache, live)
+
+ const userNew = userData.created_at > NEW_USER_DATE
+ const flagsTemplate = userNew ? newUserFlags : defaultState.flagStorage
+ let dirty = false
+
+ if (recent === null) {
+ console.debug(`Data is empty, initializing for ${userNew ? 'new' : 'existing'} user`)
+ recent = _wrapData({
+ flagStorage: { ...flagsTemplate },
+ prefsStorage: { ...defaultState.prefsStorage }
+ })
+ }
+
+ if (!needsUpload && recent && stale) {
+ console.debug('Checking if data needs merging...')
+ // discarding timestamps and versions
+ const { _timestamp: _0, _version: _1, ...recentData } = recent
+ const { _timestamp: _2, _version: _3, ...staleData } = stale
+ dirty = !isEqual(recentData, staleData)
+ console.debug(`Data ${dirty ? 'needs' : 'doesn\'t need'} merging`)
+ }
+
+ const allFlagKeys = _getAllFlags(recent, stale)
+ let totalFlags
+ let totalPrefs
+ if (dirty) {
+ // Merge the flags
+ console.debug('Merging the data...')
+ totalFlags = _mergeFlags(recent, stale, allFlagKeys)
+ _verifyPrefs(recent)
+ _verifyPrefs(stale)
+ totalPrefs = _mergePrefs(recent.prefsStorage, stale.prefsStorage)
+ } else {
+ totalFlags = recent.flagStorage
+ totalPrefs = recent.prefsStorage
+ }
+
+ totalFlags = _resetFlags(totalFlags)
+
+ recent.flagStorage = { ...flagsTemplate, ...totalFlags }
+ recent.prefsStorage = { ...defaultState.prefsStorage, ...totalPrefs }
+
+ state.dirty = dirty || needsUpload
+ state.cache = recent
+ // set local timestamp to smaller one if we don't have any changes
+ if (stale && recent && !state.dirty) {
+ state.cache._timestamp = Math.min(stale._timestamp, recent._timestamp)
+ }
+ state.flagStorage = state.cache.flagStorage
+ state.prefsStorage = state.cache.prefsStorage
+ },
+ setFlag (state, { flag, value }) {
+ state.flagStorage[flag] = value
+ state.dirty = true
+ },
+ setPreference (state, { path, value }) {
+ if (path.startsWith('_')) {
+ console.error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
+ return
+ }
+ set(state.prefsStorage, path, value)
+ state.prefsStorage._journal = [
+ ...state.prefsStorage._journal,
+ { operation: 'set', path, args: [value], timestamp: Date.now() }
+ ]
+ state.dirty = true
+ },
+ addCollectionPreference (state, { path, value }) {
+ if (path.startsWith('_')) {
+ console.error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
+ return
+ }
+ const collection = new Set(get(state.prefsStorage, path))
+ collection.add(value)
+ set(state.prefsStorage, path, [...collection])
+ state.prefsStorage._journal = [
+ ...state.prefsStorage._journal,
+ { operation: 'addToCollection', path, args: [value], timestamp: Date.now() }
+ ]
+ state.dirty = true
+ },
+ removeCollectionPreference (state, { path, value }) {
+ if (path.startsWith('_')) {
+ console.error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
+ return
+ }
+ const collection = new Set(get(state.prefsStorage, path))
+ collection.delete(value)
+ set(state.prefsStorage, path, [...collection])
+ state.prefsStorage._journal = [
+ ...state.prefsStorage._journal,
+ { operation: 'removeFromCollection', path, args: [value], timestamp: Date.now() }
+ ]
+ state.dirty = true
+ },
+ reorderCollectionPreference (state, { path, value, movement }) {
+ if (path.startsWith('_')) {
+ console.error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
+ return
+ }
+ const collection = get(state.prefsStorage, path)
+ const newCollection = _moveItemInArray(collection, value, movement)
+ set(state.prefsStorage, path, newCollection)
+ state.prefsStorage._journal = [
+ ...state.prefsStorage._journal,
+ { operation: 'arrangeCollection', path, args: [value], timestamp: Date.now() }
+ ]
+ state.dirty = true
+ },
+ updateCache (state, { username }) {
+ state.prefsStorage._journal = _mergeJournal(state.prefsStorage._journal)
+ state.cache = _wrapData({
+ flagStorage: toRaw(state.flagStorage),
+ prefsStorage: toRaw(state.prefsStorage)
+ }, username)
+ }
+}
+
+const serverSideStorage = {
+ state: {
+ ...cloneDeep(defaultState)
+ },
+ mutations,
+ actions: {
+ pushServerSideStorage ({ state, rootState, commit }, { force = false } = {}) {
+ const needPush = state.dirty || force
+ if (!needPush) return
+ commit('updateCache', { username: rootState.users.currentUser.fqn })
+ const params = { pleroma_settings_store: { 'pleroma-fe': state.cache } }
+ rootState.api.backendInteractor
+ .updateProfile({ params })
+ .then((user) => {
+ commit('setServerSideStorage', user)
+ state.dirty = false
+ })
+ }
+ }
+}
+
+export default serverSideStorage
diff --git a/src/modules/statusHistory.js b/src/modules/statusHistory.js
@@ -0,0 +1,25 @@
+const statusHistory = {
+ state: {
+ params: {},
+ modalActivated: false
+ },
+ mutations: {
+ openStatusHistoryModal (state, params) {
+ state.params = params
+ state.modalActivated = true
+ },
+ closeStatusHistoryModal (state) {
+ state.modalActivated = false
+ }
+ },
+ actions: {
+ openStatusHistoryModal ({ commit }, params) {
+ commit('openStatusHistoryModal', params)
+ },
+ closeStatusHistoryModal ({ commit }) {
+ commit('closeStatusHistoryModal')
+ }
+ }
+}
+
+export default statusHistory
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
@@ -12,11 +12,6 @@ import {
isArray,
omitBy
} from 'lodash'
-import {
- isStatusNotification,
- isValidNotification,
- maybeShowNotification
-} from '../services/notification_utils/notification_utils.js'
import apiService from '../services/api/api.service.js'
const emptyTl = (userId = 0) => ({
@@ -36,21 +31,12 @@ const emptyTl = (userId = 0) => ({
flushMarker: 0
})
-const emptyNotifications = () => ({
- desktopNotificationSilence: true,
- maxId: 0,
- minId: Number.POSITIVE_INFINITY,
- data: [],
- idStore: {},
- loading: false
-})
-
export const defaultState = () => ({
allStatuses: [],
+ scrobblesNextFetch: {},
allStatusesObject: {},
conversationsObject: {},
maxId: 0,
- notifications: emptyNotifications(),
favorites: new Set(),
timelines: {
mentions: emptyTl(),
@@ -62,7 +48,8 @@ export const defaultState = () => ({
friends: emptyTl(),
tag: emptyTl(),
dms: emptyTl(),
- bookmarks: emptyTl()
+ bookmarks: emptyTl(),
+ list: emptyTl()
}
})
@@ -119,8 +106,24 @@ const sortTimeline = (timeline) => {
return timeline
}
+const getLatestScrobble = (state, user) => {
+ if (state.scrobblesNextFetch[user.id] && state.scrobblesNextFetch[user.id] > Date.now()) {
+ return
+ }
+
+ state.scrobblesNextFetch[user.id] = Date.now() + 24 * 60 * 60 * 1000
+ apiService.fetchScrobbles({ accountId: user.id }).then((scrobbles) => {
+ if (scrobbles.length > 0) {
+ user.latestScrobble = scrobbles[0]
+
+ state.scrobblesNextFetch[user.id] = Date.now() + 60 * 1000
+ }
+ })
+}
+
// Add status to the global storages (arrays and objects maintaining statuses) except timelines
const addStatusToGlobalStorage = (state, data) => {
+ getLatestScrobble(state, data.user)
const result = mergeOrAdd(state.allStatuses, state.allStatusesObject, data)
if (result.new) {
// Add to conversation
@@ -136,22 +139,6 @@ const addStatusToGlobalStorage = (state, data) => {
return result
}
-// Remove status from the global storages (arrays and objects maintaining statuses) except timelines
-const removeStatusFromGlobalStorage = (state, status) => {
- remove(state.allStatuses, { id: status.id })
-
- // TODO: Need to remove from allStatusesObject?
-
- // Remove possible notification
- remove(state.notifications.data, ({ action: { id } }) => id === status.id)
-
- // Remove from conversation
- const conversationId = status.statusnet_conversation_id
- if (state.conversationsObject[conversationId]) {
- remove(state.conversationsObject[conversationId], { id: status.id })
- }
-}
-
const addNewStatuses = (state, { statuses, showImmediately = false, timeline, user = {}, noIdUpdate = false, userId, pagination = {} }) => {
// Sanity check
if (!isArray(statuses)) {
@@ -228,6 +215,10 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
timelineObject.newStatusCount += 1
}
+ if (status.quote) {
+ addStatus(status.quote, /* showImmediately = */ false, /* addToTimeline = */ false)
+ }
+
return status
}
@@ -245,10 +236,13 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
}
const processors = {
- 'status': (status) => {
+ status: (status) => {
+ addStatus(status, showImmediately)
+ },
+ edit: (status) => {
addStatus(status, showImmediately)
},
- 'retweet': (status) => {
+ retweet: (status) => {
// RetweetedStatuses are never shown immediately
const retweetedStatus = addStatus(status.retweeted_status, false, false)
@@ -270,7 +264,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
retweet.retweeted_status = retweetedStatus
},
- 'favorite': (favorite) => {
+ favorite: (favorite) => {
// Only update if this is a new favorite.
// Ignore our own favorites because we get info about likes as response to like request
if (!state.favorites.has(favorite.id)) {
@@ -278,24 +272,10 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
favoriteStatus(favorite)
}
},
- 'deletion': (deletion) => {
- const uri = deletion.uri
- const status = find(allStatuses, { uri })
- if (!status) {
- return
- }
-
- removeStatusFromGlobalStorage(state, status)
-
- if (timeline) {
- remove(timelineObject.statuses, { uri })
- remove(timelineObject.visibleStatuses, { uri })
- }
- },
- 'follow': (follow) => {
+ follow: (follow) => {
// NOOP, it is known status but we don't do anything about it for now
},
- 'default': (unknown) => {
+ default: (unknown) => {
console.log('unknown status type')
console.log(unknown)
}
@@ -303,7 +283,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
each(statuses, (status) => {
const type = status.type
- const processor = processors[type] || processors['default']
+ const processor = processors[type] || processors.default
processor(status)
})
@@ -313,47 +293,6 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
}
}
-const updateNotificationsMinMaxId = (state, notification) => {
- state.notifications.maxId = notification.id > state.notifications.maxId
- ? notification.id
- : state.notifications.maxId
- state.notifications.minId = notification.id < state.notifications.minId
- ? notification.id
- : state.notifications.minId
-}
-
-const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters, newNotificationSideEffects }) => {
- each(notifications, (notification) => {
- // If invalid notification, update ids but don't add it to store
- if (!isValidNotification(notification)) {
- console.error('Invalid notification:', notification)
- updateNotificationsMinMaxId(state, notification)
- return
- }
-
- if (isStatusNotification(notification.type)) {
- notification.action = addStatusToGlobalStorage(state, notification.action).item
- notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
- }
-
- if (notification.type === 'pleroma:emoji_reaction') {
- dispatch('fetchEmojiReactionsBy', notification.status.id)
- }
-
- // Only add a new notification if we don't have one for the same action
- if (!state.notifications.idStore.hasOwnProperty(notification.id)) {
- updateNotificationsMinMaxId(state, notification)
-
- state.notifications.data.push(notification)
- state.notifications.idStore[notification.id] = notification
-
- newNotificationSideEffects(notification)
- } else if (notification.seen) {
- state.notifications.idStore[notification.id].seen = true
- }
- })
-}
-
const removeStatus = (state, { timeline, userId }) => {
const timelineObject = state.timelines[timeline]
if (userId) {
@@ -366,7 +305,6 @@ const removeStatus = (state, { timeline, userId }) => {
export const mutations = {
addNewStatuses,
- addNewNotifications,
removeStatus,
showNewStatuses (state, { timeline }) {
const oldTimeline = (state.timelines[timeline])
@@ -388,9 +326,6 @@ export const mutations = {
const userId = excludeUserId ? state.timelines[timeline].userId : undefined
state.timelines[timeline] = emptyTl(userId)
},
- clearNotifications (state) {
- state.notifications = emptyNotifications()
- },
setFavorited (state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
@@ -450,10 +385,12 @@ export const mutations = {
setBookmarked (state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.bookmarked = value
+ newStatus.bookmark_folder_id = status.bookmark_folder_id
},
setBookmarkedConfirm (state, { status }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.bookmarked = status.bookmarked
+ if (status.pleroma) newStatus.bookmark_folder_id = status.pleroma.bookmark_folder
},
setDeleted (state, { status }) {
const newStatus = state.allStatusesObject[status.id]
@@ -473,31 +410,6 @@ export const mutations = {
const newStatus = state.allStatusesObject[id]
newStatus.nsfw = nsfw
},
- setNotificationsLoading (state, { value }) {
- state.notifications.loading = value
- },
- setNotificationsSilence (state, { value }) {
- state.notifications.desktopNotificationSilence = value
- },
- markNotificationsAsSeen (state) {
- each(state.notifications.data, (notification) => {
- notification.seen = true
- })
- },
- markSingleNotificationAsSeen (state, { id }) {
- const notification = find(state.notifications.data, n => n.id === id)
- if (notification) notification.seen = true
- },
- dismissNotification (state, { id }) {
- state.notifications.data = state.notifications.data.filter(n => n.id !== id)
- },
- dismissNotifications (state, { finder }) {
- state.notifications.data = state.notifications.data.filter(n => finder)
- },
- updateNotification (state, { id, updater }) {
- const notification = find(state.notifications.data, n => n.id === id)
- notification && updater(notification)
- },
queueFlush (state, { timeline, id }) {
state.timelines[timeline].flushMarker = id
},
@@ -522,7 +434,7 @@ export const mutations = {
},
addEmojiReactionsBy (state, { id, emojiReactions, currentUser }) {
const status = state.allStatusesObject[id]
- status['emoji_reactions'] = emojiReactions
+ status.emoji_reactions = emojiReactions
},
addOwnReaction (state, { id, emoji, currentUser }) {
const status = state.allStatusesObject[id]
@@ -543,7 +455,7 @@ export const mutations = {
if (reactionIndex >= 0) {
status.emoji_reactions[reactionIndex] = newReaction
} else {
- status['emoji_reactions'] = [...status.emoji_reactions, newReaction]
+ status.emoji_reactions = [...status.emoji_reactions, newReaction]
}
},
removeOwnReaction (state, { id, emoji, currentUser }) {
@@ -564,7 +476,7 @@ export const mutations = {
if (newReaction.count > 0) {
status.emoji_reactions[reactionIndex] = newReaction
} else {
- status['emoji_reactions'] = status.emoji_reactions.filter(r => r.name !== emoji)
+ status.emoji_reactions = status.emoji_reactions.filter(r => r.name !== emoji)
}
},
updateStatusWithPoll (state, { id, poll }) {
@@ -579,30 +491,32 @@ export const mutations = {
const statuses = {
state: defaultState(),
actions: {
- addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId, pagination }) {
+ addNewStatuses ({ rootState, commit, dispatch, state }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId, pagination }) {
commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId, pagination })
},
- addNewNotifications (store, { notifications, older }) {
- const { commit, dispatch, rootGetters } = store
-
- const newNotificationSideEffects = (notification) => {
- maybeShowNotification(store, notification)
- }
- commit('addNewNotifications', { dispatch, notifications, older, rootGetters, newNotificationSideEffects })
- },
- setNotificationsLoading ({ rootState, commit }, { value }) {
- commit('setNotificationsLoading', { value })
- },
- setNotificationsSilence ({ rootState, commit }, { value }) {
- commit('setNotificationsSilence', { value })
- },
fetchStatus ({ rootState, dispatch }, id) {
return rootState.api.backendInteractor.fetchStatus({ id })
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
},
- deleteStatus ({ rootState, commit }, status) {
- commit('setDeleted', { status })
+ fetchStatusSource ({ rootState, dispatch }, status) {
+ return apiService.fetchStatusSource({ id: status.id, credentials: rootState.users.currentUser.credentials })
+ },
+ fetchStatusHistory ({ rootState, dispatch }, status) {
+ return apiService.fetchStatusHistory({ status })
+ },
+ deleteStatus ({ rootState, commit, dispatch }, status) {
apiService.deleteStatus({ id: status.id, credentials: rootState.users.currentUser.credentials })
+ .then((_) => {
+ commit('setDeleted', { status })
+ })
+ .catch((e) => {
+ dispatch('pushGlobalNotice', {
+ level: 'error',
+ messageKey: 'status.delete_error',
+ messageArgs: [e.message],
+ timeout: 5000
+ })
+ })
},
deleteStatusById ({ rootState, commit }, id) {
const status = rootState.statuses.allStatusesObject[id]
@@ -657,7 +571,7 @@ const statuses = {
},
bookmark ({ rootState, commit }, status) {
commit('setBookmarked', { status, value: true })
- rootState.api.backendInteractor.bookmarkStatus({ id: status.id })
+ rootState.api.backendInteractor.bookmarkStatus({ id: status.id, folder_id: status.bookmark_folder_id })
.then(status => {
commit('setBookmarkedConfirm', { status })
})
@@ -675,31 +589,6 @@ const statuses = {
queueFlushAll ({ rootState, commit }) {
commit('queueFlushAll')
},
- markNotificationsAsSeen ({ rootState, commit }) {
- commit('markNotificationsAsSeen')
- apiService.markNotificationsAsSeen({
- id: rootState.statuses.notifications.maxId,
- credentials: rootState.users.currentUser.credentials
- })
- },
- markSingleNotificationAsSeen ({ rootState, commit }, { id }) {
- commit('markSingleNotificationAsSeen', { id })
- apiService.markNotificationsAsSeen({
- single: true,
- id,
- credentials: rootState.users.currentUser.credentials
- })
- },
- dismissNotificationLocal ({ rootState, commit }, { id }) {
- commit('dismissNotification', { id })
- },
- dismissNotification ({ rootState, commit }, { id }) {
- commit('dismissNotification', { id })
- rootState.api.backendInteractor.dismissNotification({ id })
- },
- updateNotification ({ rootState, commit }, { id, updater }) {
- commit('updateNotification', { id, updater })
- },
fetchFavsAndRepeats ({ rootState, commit }, id) {
Promise.all([
rootState.api.backendInteractor.fetchFavoritedByUsers({ id }),
@@ -732,7 +621,7 @@ const statuses = {
)
},
fetchEmojiReactionsBy ({ rootState, commit }, id) {
- rootState.api.backendInteractor.fetchEmojiReactions({ id }).then(
+ return rootState.api.backendInteractor.fetchEmojiReactions({ id }).then(
emojiReactions => {
commit('addEmojiReactionsBy', { id, emojiReactions, currentUser: rootState.users.currentUser })
}
@@ -746,10 +635,11 @@ const statuses = {
rootState.api.backendInteractor.fetchRebloggedByUsers({ id })
.then(rebloggedByUsers => commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser }))
},
- search (store, { q, resolve, limit, offset, following }) {
- return store.rootState.api.backendInteractor.search2({ q, resolve, limit, offset, following })
+ search (store, { q, resolve, limit, offset, following, type }) {
+ return store.rootState.api.backendInteractor.search2({ q, resolve, limit, offset, following, type })
.then((data) => {
store.commit('addNewUsers', data.accounts)
+ store.commit('addNewUsers', data.statuses.map(s => s.user).filter(u => u))
store.commit('addNewStatuses', { statuses: data.statuses })
return data
})
diff --git a/src/modules/users.js b/src/modules/users.js
@@ -2,7 +2,7 @@ import backendInteractorService from '../services/backend_interactor_service/bac
import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
import oauthApi from '../services/new_api/oauth.js'
import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash'
-import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
+import { registerPushNotifications, unregisterPushNotifications } from '../services/sw/sw.js'
// TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => {
@@ -16,9 +16,6 @@ export const mergeOrAdd = (arr, obj, item) => {
// This is a new item, prepare it
arr.push(item)
obj[item.id] = item
- if (item.screen_name && !item.screen_name.includes('@')) {
- obj[item.screen_name.toLowerCase()] = item
- }
return { item, new: true }
}
}
@@ -54,13 +51,26 @@ const unblockUser = (store, id) => {
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
}
-const muteUser = (store, id) => {
+const removeUserFromFollowers = (store, id) => {
+ return store.rootState.api.backendInteractor.removeUserFromFollowers({ id })
+ .then((relationship) => store.commit('updateUserRelationship', [relationship]))
+}
+
+const editUserNote = (store, { id, comment }) => {
+ return store.rootState.api.backendInteractor.editUserNote({ id, comment })
+ .then((relationship) => store.commit('updateUserRelationship', [relationship]))
+}
+
+const muteUser = (store, args) => {
+ const id = typeof args === 'object' ? args.id : args
+ const expiresIn = typeof args === 'object' ? args.expiresIn : 0
+
const predictedRelationship = store.state.relationships[id] || { id }
predictedRelationship.muting = true
store.commit('updateUserRelationship', [predictedRelationship])
store.commit('addMuteId', id)
- return store.rootState.api.backendInteractor.muteUser({ id })
+ return store.rootState.api.backendInteractor.muteUser({ id, expiresIn })
.then((relationship) => {
store.commit('updateUserRelationship', [relationship])
store.commit('addMuteId', id)
@@ -103,23 +113,23 @@ export const mutations = {
const user = state.usersObject[id]
const tags = user.tags || []
const newTags = tags.concat([tag])
- user['tags'] = newTags
+ user.tags = newTags
},
untagUser (state, { user: { id }, tag }) {
const user = state.usersObject[id]
const tags = user.tags || []
const newTags = tags.filter(t => t !== tag)
- user['tags'] = newTags
+ user.tags = newTags
},
updateRight (state, { user: { id }, right, value }) {
const user = state.usersObject[id]
- let newRights = user.rights
+ const newRights = user.rights
newRights[right] = value
- user['rights'] = newRights
+ user.rights = newRights
},
updateActivationStatus (state, { user: { id }, deactivated }) {
const user = state.usersObject[id]
- user['deactivated'] = deactivated
+ user.deactivated = deactivated
},
setCurrentUser (state, user) {
state.lastLoginName = user.screen_name
@@ -148,13 +158,13 @@ export const mutations = {
clearFriends (state, userId) {
const user = state.usersObject[userId]
if (user) {
- user['friendIds'] = []
+ user.friendIds = []
}
},
clearFollowers (state, userId) {
const user = state.usersObject[userId]
if (user) {
- user['followerIds'] = []
+ user.followerIds = []
}
},
addNewUsers (state, users) {
@@ -162,7 +172,11 @@ export const mutations = {
if (user.relationship) {
state.relationships[user.relationship.id] = user.relationship
}
- mergeOrAdd(state.users, state.usersObject, user)
+ const res = mergeOrAdd(state.users, state.usersObject, user)
+ const item = res.item
+ if (res.new && item.screen_name && !item.screen_name.includes('@')) {
+ state.usersByNameObject[item.screen_name.toLowerCase()] = item
+ }
})
},
updateUserRelationship (state, relationships) {
@@ -170,6 +184,9 @@ export const mutations = {
state.relationships[relationship.id] = relationship
})
},
+ updateUserInLists (state, { id, inLists }) {
+ state.usersObject[id].inLists = inLists
+ },
saveBlockIds (state, blockIds) {
state.currentUser.blockIds = blockIds
},
@@ -178,9 +195,15 @@ export const mutations = {
state.currentUser.blockIds.push(blockId)
}
},
+ setBlockIdsMaxId (state, blockIdsMaxId) {
+ state.currentUser.blockIdsMaxId = blockIdsMaxId
+ },
saveMuteIds (state, muteIds) {
state.currentUser.muteIds = muteIds
},
+ setMuteIdsMaxId (state, muteIdsMaxId) {
+ state.currentUser.muteIdsMaxId = muteIdsMaxId
+ },
addMuteId (state, muteId) {
if (state.currentUser.muteIds.indexOf(muteId) === -1) {
state.currentUser.muteIds.push(muteId)
@@ -222,11 +245,12 @@ export const mutations = {
},
setColor (state, { user: { id }, highlighted }) {
const user = state.usersObject[id]
- user['highlight'] = highlighted
+ user.highlight = highlighted
},
signUpPending (state) {
state.signUpPending = true
state.signUpErrors = []
+ state.signUpNotice = {}
},
signUpSuccess (state) {
state.signUpPending = false
@@ -234,17 +258,21 @@ export const mutations = {
signUpFailure (state, errors) {
state.signUpPending = false
state.signUpErrors = errors
+ state.signUpNotice = {}
+ },
+ signUpNotice (state, notice) {
+ state.signUpPending = false
+ state.signUpErrors = []
+ state.signUpNotice = notice
}
}
export const getters = {
findUser: state => query => {
- const result = state.usersObject[query]
- // In case it's a screen_name, we can try searching case-insensitive
- if (!result && typeof query === 'string') {
- return state.usersObject[query.toLowerCase()]
- }
- return result
+ return state.usersObject[query]
+ },
+ findUserByName: state => query => {
+ return state.usersByNameObject[query.toLowerCase()]
},
findUserByUrl: state => query => {
return state.users
@@ -263,8 +291,10 @@ export const defaultState = {
currentUser: false,
users: [],
usersObject: {},
+ usersByNameObject: {},
signUpPending: false,
signUpErrors: [],
+ signUpNotice: {},
relationships: {}
}
@@ -285,16 +315,39 @@ const users = {
return user
})
},
+ fetchUserByName (store, name) {
+ return store.rootState.api.backendInteractor.fetchUserByName({ name })
+ .then((user) => {
+ store.commit('addNewUsers', [user])
+ return user
+ })
+ },
fetchUserRelationship (store, id) {
if (store.state.currentUser) {
store.rootState.api.backendInteractor.fetchUserRelationship({ id })
.then((relationships) => store.commit('updateUserRelationship', relationships))
}
},
- fetchBlocks (store) {
- return store.rootState.api.backendInteractor.fetchBlocks()
+ fetchUserInLists (store, id) {
+ if (store.state.currentUser) {
+ store.rootState.api.backendInteractor.fetchUserInLists({ id })
+ .then((inLists) => store.commit('updateUserInLists', { id, inLists }))
+ }
+ },
+ fetchBlocks (store, args) {
+ const { reset } = args || {}
+
+ const maxId = store.state.currentUser.blockIdsMaxId
+ return store.rootState.api.backendInteractor.fetchBlocks({ maxId })
.then((blocks) => {
- store.commit('saveBlockIds', map(blocks, 'id'))
+ if (reset) {
+ store.commit('saveBlockIds', map(blocks, 'id'))
+ } else {
+ map(blocks, 'id').map(id => store.commit('addBlockId', id))
+ }
+ if (blocks.length) {
+ store.commit('setBlockIdsMaxId', last(blocks).id)
+ }
store.commit('addNewUsers', blocks)
return blocks
})
@@ -305,16 +358,32 @@ const users = {
unblockUser (store, id) {
return unblockUser(store, id)
},
+ removeUserFromFollowers (store, id) {
+ return removeUserFromFollowers(store, id)
+ },
blockUsers (store, ids = []) {
return Promise.all(ids.map(id => blockUser(store, id)))
},
unblockUsers (store, ids = []) {
return Promise.all(ids.map(id => unblockUser(store, id)))
},
- fetchMutes (store) {
- return store.rootState.api.backendInteractor.fetchMutes()
+ editUserNote (store, args) {
+ return editUserNote(store, args)
+ },
+ fetchMutes (store, args) {
+ const { reset } = args || {}
+
+ const maxId = store.state.currentUser.muteIdsMaxId
+ return store.rootState.api.backendInteractor.fetchMutes({ maxId })
.then((mutes) => {
- store.commit('saveMuteIds', map(mutes, 'id'))
+ if (reset) {
+ store.commit('saveMuteIds', map(mutes, 'id'))
+ } else {
+ map(mutes, 'id').map(id => store.commit('addMuteId', id))
+ }
+ if (mutes.length) {
+ store.commit('setMuteIdsMaxId', last(mutes).id)
+ }
store.commit('addNewUsers', mutes)
return mutes
})
@@ -383,17 +452,17 @@ const users = {
commit('clearFollowers', userId)
},
subscribeUser ({ rootState, commit }, id) {
- return rootState.api.backendInteractor.subscribeUser({ id })
+ return rootState.api.backendInteractor.followUser({ id, notify: true })
.then((relationship) => commit('updateUserRelationship', [relationship]))
},
unsubscribeUser ({ rootState, commit }, id) {
- return rootState.api.backendInteractor.unsubscribeUser({ id })
+ return rootState.api.backendInteractor.followUser({ id, notify: false })
.then((relationship) => commit('updateUserRelationship', [relationship]))
},
toggleActivationStatus ({ rootState, commit }, { user }) {
const api = user.deactivated ? rootState.api.backendInteractor.activateUser : rootState.api.backendInteractor.deactivateUser
api({ user })
- .then((user) => { let deactivated = !user.is_active; commit('updateActivationStatus', { user, deactivated }) })
+ .then((user) => { const deactivated = !user.is_active; commit('updateActivationStatus', { user, deactivated }) })
},
registerPushNotifications (store) {
const token = store.state.currentUser.credentials
@@ -437,7 +506,7 @@ const users = {
store.commit('addNewUsers', users)
store.commit('addNewUsers', targetUsers)
- const notificationsObject = store.rootState.statuses.notifications.idStore
+ const notificationsObject = store.rootState.notifications.idStore
const relevantNotifications = Object.entries(notificationsObject)
.filter(([k, val]) => notificationIds.includes(k))
.map(([k, val]) => val)
@@ -457,17 +526,24 @@ const users = {
async signUp (store, userInfo) {
store.commit('signUpPending')
- let rootState = store.rootState
+ const rootState = store.rootState
try {
- let data = await rootState.api.backendInteractor.register(
+ const data = await rootState.api.backendInteractor.register(
{ params: { ...userInfo } }
)
- store.commit('signUpSuccess')
- store.commit('setToken', data.access_token)
- store.dispatch('loginUser', data.access_token)
+
+ if (data.access_token) {
+ store.commit('signUpSuccess')
+ store.commit('setToken', data.access_token)
+ store.dispatch('loginUser', data.access_token)
+ return 'ok'
+ } else { // Request succeeded, but user cannot login yet.
+ store.commit('signUpNotice', data)
+ return 'request_sent'
+ }
} catch (e) {
- let errors = e.message
+ const errors = e.message
store.commit('signUpFailure', errors)
throw e
}
@@ -502,6 +578,8 @@ const users = {
store.dispatch('stopFetchingTimeline', 'friends')
store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
store.dispatch('stopFetchingNotifications')
+ store.dispatch('stopFetchingLists')
+ store.dispatch('stopFetchingBookmarkFolders')
store.dispatch('stopFetchingFollowRequests')
store.commit('clearNotifications')
store.commit('resetStatuses')
@@ -509,11 +587,13 @@ const users = {
store.dispatch('setLastTimeline', 'public-timeline')
store.dispatch('setLayoutWidth', windowWidth())
store.dispatch('setLayoutHeight', windowHeight())
+ store.commit('clearServerSideStorage')
})
},
loginUser (store, accessToken) {
return new Promise((resolve, reject) => {
const commit = store.commit
+ const dispatch = store.dispatch
commit('beginLogin')
store.rootState.api.backendInteractor.verifyCredentials(accessToken)
.then((data) => {
@@ -525,52 +605,61 @@ const users = {
user.muteIds = []
user.domainMutes = []
commit('setCurrentUser', user)
+ commit('setServerSideStorage', user)
commit('addNewUsers', [user])
- store.dispatch('fetchEmoji')
+ dispatch('fetchEmoji')
getNotificationPermission()
.then(permission => commit('setNotificationPermission', permission))
// Set our new backend interactor
commit('setBackendInteractor', backendInteractorService(accessToken))
+ dispatch('pushServerSideStorage')
if (user.token) {
- store.dispatch('setWsToken', user.token)
+ dispatch('setWsToken', user.token)
// Initialize the shout socket.
- store.dispatch('initializeSocket')
+ dispatch('initializeSocket')
}
const startPolling = () => {
// Start getting fresh posts.
- store.dispatch('startFetchingTimeline', { timeline: 'friends' })
+ dispatch('startFetchingTimeline', { timeline: 'friends' })
// Start fetching notifications
- store.dispatch('startFetchingNotifications')
+ dispatch('startFetchingNotifications')
// Start fetching chats
- store.dispatch('startFetchingChats')
+ dispatch('startFetchingChats')
+ }
+
+ dispatch('startFetchingLists')
+ dispatch('startFetchingBookmarkFolders')
+
+ if (user.locked) {
+ dispatch('startFetchingFollowRequests')
}
if (store.getters.mergedConfig.useStreamingApi) {
- store.dispatch('fetchTimeline', 'friends', { since: null })
- store.dispatch('fetchNotifications', { since: null })
- store.dispatch('enableMastoSockets', true).catch((error) => {
+ dispatch('fetchTimeline', { timeline: 'friends', since: null })
+ dispatch('fetchNotifications', { since: null })
+ dispatch('enableMastoSockets', true).catch((error) => {
console.error('Failed initializing MastoAPI Streaming socket', error)
}).then(() => {
- store.dispatch('fetchChats', { latest: true })
- setTimeout(() => store.dispatch('setNotificationsSilence', false), 10000)
+ dispatch('fetchChats', { latest: true })
+ setTimeout(() => dispatch('setNotificationsSilence', false), 10000)
})
} else {
startPolling()
}
// Get user mutes
- store.dispatch('fetchMutes')
+ dispatch('fetchMutes')
- store.dispatch('setLayoutWidth', windowWidth())
- store.dispatch('setLayoutHeight', windowHeight())
+ dispatch('setLayoutWidth', windowWidth())
+ dispatch('setLayoutHeight', windowHeight())
// Fetch our friends
store.rootState.api.backendInteractor.fetchFriends({ id: user.id })
@@ -579,6 +668,12 @@ const users = {
const response = data.error
// Authentication failed
commit('endLogin')
+
+ // remove authentication token on client/authentication errors
+ if ([400, 401, 403, 422].includes(response.status)) {
+ commit('clearToken')
+ }
+
if (response.status === 401) {
reject(new Error('Wrong username or password'))
} else {
@@ -589,7 +684,7 @@ const users = {
resolve()
})
.catch((error) => {
- console.log(error)
+ console.error(error)
commit('endLogin')
reject(new Error('Failed to connect to server, try again'))
})
diff --git a/src/panel.scss b/src/panel.scss
@@ -1,37 +1,53 @@
+/* stylelint-disable no-descending-specificity */
.panel {
+ --__panel-background: var(--background);
+ --__panel-backdrop-filter: var(--backdrop-filter);
+
+ .tab-switcher .tabs {
+ background: var(--__panel-background);
+ backdrop-filter: var(--__panel-backdrop-filter);
+ }
+
position: relative;
display: flex;
flex-direction: column;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
+
+ .panel-heading {
+ background-color: inherit;
+ }
&::after,
& {
- border-radius: $fallback--panelRadius;
- border-radius: var(--panelRadius, $fallback--panelRadius);
+ border-radius: var(--roundness);
}
&::after {
- content: '';
+ content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 5;
- box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
- box-shadow: var(--panelShadow);
+ box-shadow: var(--shadow);
pointer-events: none;
}
}
.panel-body {
padding: var(--panel-body-padding, 0);
+ background: var(--background);
+ backdrop-filter: var(--__panel-backdrop-filter);
+
+ .tab-switcher .tabs {
+ background: none;
+ backdrop-filter: none;
+ }
&:empty::before {
content: "¯\\_(ツ)_/¯"; // Could use words but it'd require translations
display: block;
- margin: 1em;
+ padding: 1em;
text-align: center;
}
@@ -44,20 +60,23 @@
.panel-heading,
.panel-footer {
- --panel-heading-height-padding: 0.6em;
- --__panel-heading-height: 3.2em;
- --__panel-heading-height-inner: calc(var(--__panel-heading-height) - 2 * var(--panel-heading-height-padding));
+ --panel-heading-height-padding: calc(var(--panel-header-height) * 0.2);
+ --__panel-heading-gap: calc(var(--panel-header-height) * 0.1565);
+ --__panel-heading-height: var(--panel-header-height);
+ --__panel-heading-height-inner: calc(var(--__panel-heading-height) - 2 * var(--panel-heading-height-padding, 0));
+ font-size: calc(var(--panelHeaderSize) / 3.2);
+ backdrop-filter: var(--__panel-backdrop-filter);
position: relative;
box-sizing: border-box;
display: grid;
grid-auto-flow: column;
grid-template-columns: minmax(50%, 1fr);
grid-auto-columns: auto;
- grid-column-gap: 0.5em;
+ grid-column-gap: var(--__panel-heading-gap);
flex: none;
background-size: cover;
- padding: 0.6em;
+ padding: var(--panel-heading-height-padding);
height: var(--__panel-heading-height);
line-height: var(--__panel-heading-height-inner);
z-index: 4;
@@ -74,8 +93,7 @@
&.-stub {
&,
&::after {
- border-radius: $fallback--panelRadius;
- border-radius: var(--panelRadius, $fallback--panelRadius);
+ border-radius: var(--roundness);
}
}
@@ -86,7 +104,7 @@
&::after,
&::before {
- content: '';
+ content: "";
position: absolute;
top: 0;
bottom: 0;
@@ -97,6 +115,8 @@
.title {
font-size: 1.3em;
+ margin: 0;
+ font-weight: normal;
}
.alert {
@@ -117,82 +137,76 @@
padding-bottom: 0;
align-self: stretch;
}
+
+ > .alert {
+ line-height: calc(var(--__panel-heading-height-inner) - 2px);
+ }
}
}
// TODO Should refactor panels into separate component and utilize slots
.panel-heading {
- border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
- border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
- border-width: 0 0 1px 0;
+ border-radius: var(--roundness) var(--roundness) 0 0;
+ border-width: 0 0 1px;
align-items: start;
- // panel theme
- color: var(--panelText);
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
+ background-image:
+ linear-gradient(to bottom, var(--background), var(--background)),
+ linear-gradient(to bottom, var(--__panel-background), var(--__panel-background));
&::after {
- background-color: $fallback--fg;
- background-color: var(--panel, $fallback--fg);
+ background-color: var(--background);
z-index: -2;
- border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
- border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
- box-shadow: var(--panelHeaderShadow);
- }
-
- a,
- .-link {
- color: $fallback--link;
- color: var(--panelLink, $fallback--link);
- }
-
- .faint {
- background-color: transparent;
- color: $fallback--faint;
- color: var(--panelFaint, $fallback--faint);
- }
-
- .faint-link {
- color: $fallback--faint;
- color: var(--faintLink, $fallback--faint);
+ border-radius: var(--roundness) var(--roundness) 0 0;
+ box-shadow: var(--shadow);
}
&:not(.-flexible-height) {
> .button-default {
flex-shrink: 0;
+ }
+ }
- &,
- i[class*=icon-] {
- color: $fallback--text;
- color: var(--btnPanelText, $fallback--text);
- }
+ .rightside-button {
+ align-self: stretch;
+ text-align: center;
+ width: var(--__panel-heading-height);
+ height: var(--__panel-heading-height);
+ margin: calc(-1 * var(--panel-heading-height-padding)) 0;
+ margin-right: calc(-1 * var(--__panel-heading-gap));
- &:active {
- background-color: $fallback--fg;
- background-color: var(--btnPressedPanel, $fallback--fg);
- color: $fallback--text;
- color: var(--btnPressedPanelText, $fallback--text);
- }
+ > button {
+ box-sizing: border-box;
+ padding: calc(1 * var(--panel-heading-height-padding)) 0;
+ height: 100%;
+ width: 100%;
+ text-align: center;
- &:disabled {
- color: $fallback--text;
- color: var(--btnDisabledPanelText, $fallback--text);
+ svg {
+ font-size: 1.2em;
}
+ }
+ }
- &.toggled {
- color: $fallback--text;
- color: var(--btnToggledPanelText, $fallback--text);
- }
+ .rightside-icon {
+ align-self: stretch;
+ text-align: center;
+ width: var(--__panel-heading-height);
+ margin-right: calc(-1 * var(--__panel-heading-gap));
+
+ svg {
+ font-size: 1.2em;
}
}
}
.panel-footer {
- border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
- border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
align-items: center;
- border-width: 1px 0 0 0;
+ border-width: 1px 0 0;
border-style: solid;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
+ background-color: var(--__panel-background);
}
+/* stylelint-enable no-descending-specificity */
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
@@ -1,5 +1,5 @@
import { each, map, concat, last, get } from 'lodash'
-import { parseStatus, parseUser, parseNotification, parseAttachment, parseChat, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js'
+import { parseStatus, parseSource, parseUser, parseNotification, parseAttachment, parseChat, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js'
import { RegistrationError, StatusCodeError } from '../errors/errors'
/* eslint-env browser */
@@ -49,9 +49,16 @@ const MASTODON_PUBLIC_TIMELINE = '/api/v1/timelines/public'
const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home'
const MASTODON_STATUS_URL = id => `/api/v1/statuses/${id}`
const MASTODON_STATUS_CONTEXT_URL = id => `/api/v1/statuses/${id}/context`
+const MASTODON_STATUS_SOURCE_URL = id => `/api/v1/statuses/${id}/source`
+const MASTODON_STATUS_HISTORY_URL = id => `/api/v1/statuses/${id}/history`
const MASTODON_USER_URL = '/api/v1/accounts'
+const MASTODON_USER_LOOKUP_URL = '/api/v1/accounts/lookup'
const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships'
const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses`
+const MASTODON_USER_IN_LISTS = id => `/api/v1/accounts/${id}/lists`
+const MASTODON_LIST_URL = id => `/api/v1/lists/${id}`
+const MASTODON_LIST_TIMELINE_URL = id => `/api/v1/timelines/list/${id}`
+const MASTODON_LIST_ACCOUNTS_URL = id => `/api/v1/lists/${id}/accounts`
const MASTODON_TAG_TIMELINE_URL = tag => `/api/v1/timelines/tag/${tag}`
const MASTODON_BOOKMARK_TIMELINE_URL = '/api/v1/bookmarks'
const MASTODON_USER_BLOCKS_URL = '/api/v1/blocks/'
@@ -60,8 +67,8 @@ const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block`
const MASTODON_UNBLOCK_USER_URL = id => `/api/v1/accounts/${id}/unblock`
const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute`
const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute`
-const MASTODON_SUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/subscribe`
-const MASTODON_UNSUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/unsubscribe`
+const MASTODON_REMOVE_USER_FROM_FOLLOWERS = id => `/api/v1/accounts/${id}/remove_from_followers`
+const MASTODON_USER_NOTE_URL = id => `/api/v1/accounts/${id}/note`
const MASTODON_BOOKMARK_STATUS_URL = id => `/api/v1/statuses/${id}/bookmark`
const MASTODON_UNBOOKMARK_STATUS_URL = id => `/api/v1/statuses/${id}/unbookmark`
const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
@@ -76,24 +83,51 @@ const MASTODON_PIN_OWN_STATUS = id => `/api/v1/statuses/${id}/pin`
const MASTODON_UNPIN_OWN_STATUS = id => `/api/v1/statuses/${id}/unpin`
const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
-const MASTODON_SEARCH_2 = `/api/v2/search`
+const MASTODON_SEARCH_2 = '/api/v2/search'
const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks'
+const MASTODON_LISTS_URL = '/api/v1/lists'
const MASTODON_STREAMING = '/api/v1/streaming'
const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers'
+const MASTODON_ANNOUNCEMENTS_URL = '/api/v1/announcements'
+const MASTODON_ANNOUNCEMENTS_DISMISS_URL = id => `/api/v1/announcements/${id}/dismiss`
const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/reactions`
const PLEROMA_EMOJI_REACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
-const PLEROMA_CHATS_URL = `/api/v1/pleroma/chats`
+const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats'
const PLEROMA_CHAT_URL = id => `/api/v1/pleroma/chats/by-account-id/${id}`
const PLEROMA_CHAT_MESSAGES_URL = id => `/api/v1/pleroma/chats/${id}/messages`
const PLEROMA_CHAT_READ_URL = id => `/api/v1/pleroma/chats/${id}/read`
const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}`
+const PLEROMA_ADMIN_REPORTS = '/api/pleroma/admin/reports'
const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
+const PLEROMA_ANNOUNCEMENTS_URL = '/api/v1/pleroma/admin/announcements'
+const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements'
+const PLEROMA_EDIT_ANNOUNCEMENT_URL = id => `/api/v1/pleroma/admin/announcements/${id}`
+const PLEROMA_DELETE_ANNOUNCEMENT_URL = id => `/api/v1/pleroma/admin/announcements/${id}`
+const PLEROMA_SCROBBLES_URL = id => `/api/v1/pleroma/accounts/${id}/scrobbles`
+const PLEROMA_STATUS_QUOTES_URL = id => `/api/v1/pleroma/statuses/${id}/quotes`
+const PLEROMA_USER_FAVORITES_TIMELINE_URL = id => `/api/v1/pleroma/accounts/${id}/favourites`
+const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders'
+const PLEROMA_BOOKMARK_FOLDER_URL = id => `/api/v1/pleroma/bookmark_folders/${id}`
+
+const PLEROMA_ADMIN_CONFIG_URL = '/api/pleroma/admin/config'
+const PLEROMA_ADMIN_DESCRIPTIONS_URL = '/api/pleroma/admin/config/descriptions'
+const PLEROMA_ADMIN_FRONTENDS_URL = '/api/pleroma/admin/frontends'
+const PLEROMA_ADMIN_FRONTENDS_INSTALL_URL = '/api/pleroma/admin/frontends/install'
+
+const PLEROMA_EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji'
+const PLEROMA_EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import'
+const PLEROMA_EMOJI_PACKS_URL = (page, pageSize) => `/api/v1/pleroma/emoji/packs?page=${page}&page_size=${pageSize}`
+const PLEROMA_EMOJI_PACK_URL = (name) => `/api/v1/pleroma/emoji/pack?name=${name}`
+const PLEROMA_EMOJI_PACKS_DL_REMOTE_URL = '/api/v1/pleroma/emoji/packs/download'
+const PLEROMA_EMOJI_PACKS_LS_REMOTE_URL =
+ (url, page, pageSize) => `/api/v1/pleroma/emoji/packs/remote?url=${url}&page=${page}&page_size=${pageSize}`
+const PLEROMA_EMOJI_UPDATE_FILE_URL = (name) => `/api/v1/pleroma/emoji/packs/files?name=${name}`
const oldfetch = window.fetch
-let fetch = (url, options) => {
+const fetch = (url, options) => {
options = options || {}
const baseUrl = ''
const fullUrl = baseUrl + url
@@ -105,7 +139,7 @@ const promisedRequest = ({ method, url, params, payload, credentials, headers =
const options = {
method,
headers: {
- 'Accept': 'application/json',
+ Accept: 'application/json',
'Content-Type': 'application/json',
...headers
}
@@ -147,7 +181,7 @@ const updateNotificationSettings = ({ credentials, settings }) => {
form.append(key, value)
})
- return fetch(NOTIFICATION_SETTINGS_URL, {
+ return fetch(`${NOTIFICATION_SETTINGS_URL}?${new URLSearchParams(settings)}`, {
headers: authHeaders(credentials),
method: 'PUT',
body: form
@@ -229,16 +263,17 @@ const getCaptcha = () => fetch('/api/pleroma/captcha').then(resp => resp.json())
const authHeaders = (accessToken) => {
if (accessToken) {
- return { 'Authorization': `Bearer ${accessToken}` }
+ return { Authorization: `Bearer ${accessToken}` }
} else {
return { }
}
}
const followUser = ({ id, credentials, ...options }) => {
- let url = MASTODON_FOLLOW_URL(id)
+ const url = MASTODON_FOLLOW_URL(id)
const form = {}
- if (options.reblogs !== undefined) { form['reblogs'] = options.reblogs }
+ if (options.reblogs !== undefined) { form.reblogs = options.reblogs }
+ if (options.notify !== undefined) { form.notify = options.notify }
return fetch(url, {
body: JSON.stringify(form),
headers: {
@@ -250,13 +285,20 @@ const followUser = ({ id, credentials, ...options }) => {
}
const unfollowUser = ({ id, credentials }) => {
- let url = MASTODON_UNFOLLOW_URL(id)
+ const url = MASTODON_UNFOLLOW_URL(id)
return fetch(url, {
headers: authHeaders(credentials),
method: 'POST'
}).then((data) => data.json())
}
+const fetchUserInLists = ({ id, credentials }) => {
+ const url = MASTODON_USER_IN_LISTS(id)
+ return fetch(url, {
+ headers: authHeaders(credentials)
+ }).then((data) => data.json())
+}
+
const pinOwnStatus = ({ id, credentials }) => {
return promisedRequest({ url: MASTODON_PIN_OWN_STATUS(id), credentials, method: 'POST' })
.then((data) => parseStatus(data))
@@ -291,8 +333,26 @@ const unblockUser = ({ id, credentials }) => {
}).then((data) => data.json())
}
+const removeUserFromFollowers = ({ id, credentials }) => {
+ return fetch(MASTODON_REMOVE_USER_FROM_FOLLOWERS(id), {
+ headers: authHeaders(credentials),
+ method: 'POST'
+ }).then((data) => data.json())
+}
+
+const editUserNote = ({ id, credentials, comment }) => {
+ return promisedRequest({
+ url: MASTODON_USER_NOTE_URL(id),
+ credentials,
+ payload: {
+ comment
+ },
+ method: 'POST'
+ })
+}
+
const approveUser = ({ id, credentials }) => {
- let url = MASTODON_APPROVE_USER_URL(id)
+ const url = MASTODON_APPROVE_USER_URL(id)
return fetch(url, {
headers: authHeaders(credentials),
method: 'POST'
@@ -300,7 +360,7 @@ const approveUser = ({ id, credentials }) => {
}
const denyUser = ({ id, credentials }) => {
- let url = MASTODON_DENY_USER_URL(id)
+ const url = MASTODON_DENY_USER_URL(id)
return fetch(url, {
headers: authHeaders(credentials),
method: 'POST'
@@ -308,13 +368,32 @@ const denyUser = ({ id, credentials }) => {
}
const fetchUser = ({ id, credentials }) => {
- let url = `${MASTODON_USER_URL}/${id}`
+ const url = `${MASTODON_USER_URL}/${id}`
return promisedRequest({ url, credentials })
.then((data) => parseUser(data))
}
+const fetchUserByName = ({ name, credentials }) => {
+ return promisedRequest({
+ url: MASTODON_USER_LOOKUP_URL,
+ credentials,
+ params: { acct: name }
+ })
+ .then(data => data.id)
+ .catch(error => {
+ if (error && error.statusCode === 404) {
+ // Either the backend does not support lookup endpoint,
+ // or there is no user with such name. Fallback and treat name as id.
+ return name
+ } else {
+ throw error
+ }
+ })
+ .then(id => fetchUser({ id, credentials }))
+}
+
const fetchUserRelationship = ({ id, credentials }) => {
- let url = `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}`
+ const url = `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}`
return fetch(url, { headers: authHeaders(credentials) })
.then((response) => {
return new Promise((resolve, reject) => response.json()
@@ -333,7 +412,7 @@ const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => {
maxId && `max_id=${maxId}`,
sinceId && `since_id=${sinceId}`,
limit && `limit=${limit}`,
- `with_relationships=true`
+ 'with_relationships=true'
].filter(_ => _).join('&')
url = url + (args ? '?' + args : '')
@@ -343,6 +422,7 @@ const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => {
}
const exportFriends = ({ id, credentials }) => {
+ // eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
try {
let friends = []
@@ -368,7 +448,7 @@ const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => {
maxId && `max_id=${maxId}`,
sinceId && `since_id=${sinceId}`,
limit && `limit=${limit}`,
- `with_relationships=true`
+ 'with_relationships=true'
].filter(_ => _).join('&')
url += args ? '?' + args : ''
@@ -384,8 +464,83 @@ const fetchFollowRequests = ({ credentials }) => {
.then((data) => data.map(parseUser))
}
+const fetchLists = ({ credentials }) => {
+ const url = MASTODON_LISTS_URL
+ return fetch(url, { headers: authHeaders(credentials) })
+ .then((data) => data.json())
+}
+
+const createList = ({ title, credentials }) => {
+ const url = MASTODON_LISTS_URL
+ const headers = authHeaders(credentials)
+ headers['Content-Type'] = 'application/json'
+
+ return fetch(url, {
+ headers,
+ method: 'POST',
+ body: JSON.stringify({ title })
+ }).then((data) => data.json())
+}
+
+const getList = ({ listId, credentials }) => {
+ const url = MASTODON_LIST_URL(listId)
+ return fetch(url, { headers: authHeaders(credentials) })
+ .then((data) => data.json())
+}
+
+const updateList = ({ listId, title, credentials }) => {
+ const url = MASTODON_LIST_URL(listId)
+ const headers = authHeaders(credentials)
+ headers['Content-Type'] = 'application/json'
+
+ return fetch(url, {
+ headers,
+ method: 'PUT',
+ body: JSON.stringify({ title })
+ })
+}
+
+const getListAccounts = ({ listId, credentials }) => {
+ const url = MASTODON_LIST_ACCOUNTS_URL(listId)
+ return fetch(url, { headers: authHeaders(credentials) })
+ .then((data) => data.json())
+ .then((data) => data.map(({ id }) => id))
+}
+
+const addAccountsToList = ({ listId, accountIds, credentials }) => {
+ const url = MASTODON_LIST_ACCOUNTS_URL(listId)
+ const headers = authHeaders(credentials)
+ headers['Content-Type'] = 'application/json'
+
+ return fetch(url, {
+ headers,
+ method: 'POST',
+ body: JSON.stringify({ account_ids: accountIds })
+ })
+}
+
+const removeAccountsFromList = ({ listId, accountIds, credentials }) => {
+ const url = MASTODON_LIST_ACCOUNTS_URL(listId)
+ const headers = authHeaders(credentials)
+ headers['Content-Type'] = 'application/json'
+
+ return fetch(url, {
+ headers,
+ method: 'DELETE',
+ body: JSON.stringify({ account_ids: accountIds })
+ })
+}
+
+const deleteList = ({ listId, credentials }) => {
+ const url = MASTODON_LIST_URL(listId)
+ return fetch(url, {
+ method: 'DELETE',
+ headers: authHeaders(credentials)
+ })
+}
+
const fetchConversation = ({ id, credentials }) => {
- let urlContext = MASTODON_STATUS_CONTEXT_URL(id)
+ const urlContext = MASTODON_STATUS_CONTEXT_URL(id)
return fetch(urlContext, { headers: authHeaders(credentials) })
.then((data) => {
if (data.ok) {
@@ -401,7 +556,7 @@ const fetchConversation = ({ id, credentials }) => {
}
const fetchStatus = ({ id, credentials }) => {
- let url = MASTODON_STATUS_URL(id)
+ const url = MASTODON_STATUS_URL(id)
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => {
if (data.ok) {
@@ -413,6 +568,31 @@ const fetchStatus = ({ id, credentials }) => {
.then((data) => parseStatus(data))
}
+const fetchStatusSource = ({ id, credentials }) => {
+ const url = MASTODON_STATUS_SOURCE_URL(id)
+ return fetch(url, { headers: authHeaders(credentials) })
+ .then((data) => {
+ if (data.ok) {
+ return data
+ }
+ throw new Error('Error fetching source', data)
+ })
+ .then((data) => data.json())
+ .then((data) => parseSource(data))
+}
+
+const fetchStatusHistory = ({ status, credentials }) => {
+ const url = MASTODON_STATUS_HISTORY_URL(status.id)
+ return promisedRequest({ url, credentials })
+ .then((data) => {
+ data.reverse()
+ return data.map((item) => {
+ item.originalStatus = status
+ return parseStatus(item)
+ })
+ })
+}
+
const tagUser = ({ tag, credentials, user }) => {
const screenName = user.screen_name
const form = {
@@ -425,7 +605,7 @@ const tagUser = ({ tag, credentials, user }) => {
return fetch(TAG_USER_URL, {
method: 'PUT',
- headers: headers,
+ headers,
body: JSON.stringify(form)
})
}
@@ -442,7 +622,7 @@ const untagUser = ({ tag, credentials, user }) => {
return fetch(TAG_USER_URL, {
method: 'DELETE',
- headers: headers,
+ headers,
body: JSON.stringify(body)
})
}
@@ -495,7 +675,7 @@ const deleteUser = ({ credentials, user }) => {
return fetch(`${ADMIN_USERS_URL}?nickname=${screenName}`, {
method: 'DELETE',
- headers: headers
+ headers
})
}
@@ -503,33 +683,56 @@ const fetchTimeline = ({
timeline,
credentials,
since = false,
+ minId = false,
until = false,
userId = false,
+ listId = false,
+ statusId = false,
tag = false,
withMuted = false,
- replyVisibility = 'all'
+ replyVisibility = 'all',
+ includeTypes = [],
+ bookmarkFolderId = false
}) => {
const timelineUrls = {
public: MASTODON_PUBLIC_TIMELINE,
friends: MASTODON_USER_HOME_TIMELINE_URL,
dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL,
notifications: MASTODON_USER_NOTIFICATIONS_URL,
- 'publicAndExternal': MASTODON_PUBLIC_TIMELINE,
+ publicAndExternal: MASTODON_PUBLIC_TIMELINE,
user: MASTODON_USER_TIMELINE_URL,
media: MASTODON_USER_TIMELINE_URL,
+ list: MASTODON_LIST_TIMELINE_URL,
favorites: MASTODON_USER_FAVORITES_TIMELINE_URL,
+ publicFavorites: PLEROMA_USER_FAVORITES_TIMELINE_URL,
tag: MASTODON_TAG_TIMELINE_URL,
- bookmarks: MASTODON_BOOKMARK_TIMELINE_URL
+ bookmarks: MASTODON_BOOKMARK_TIMELINE_URL,
+ quotes: PLEROMA_STATUS_QUOTES_URL
}
const isNotifications = timeline === 'notifications'
const params = []
let url = timelineUrls[timeline]
+ if (timeline === 'favorites' && userId) {
+ url = timelineUrls.publicFavorites(userId)
+ }
+
if (timeline === 'user' || timeline === 'media') {
url = url(userId)
}
+ if (timeline === 'list') {
+ url = url(listId)
+ }
+
+ if (timeline === 'quotes') {
+ url = url(statusId)
+ }
+
+ if (minId) {
+ params.push(['min_id', minId])
+ }
if (since) {
params.push(['since_id', since])
}
@@ -554,32 +757,36 @@ const fetchTimeline = ({
if (replyVisibility !== 'all') {
params.push(['reply_visibility', replyVisibility])
}
+ if (includeTypes.length > 0) {
+ includeTypes.forEach(type => {
+ params.push(['include_types[]', type])
+ })
+ }
+ if (timeline === 'bookmarks' && bookmarkFolderId) {
+ params.push(['folder_id', bookmarkFolderId])
+ }
params.push(['limit', 20])
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
url += `?${queryString}`
- let status = ''
- let statusText = ''
-
- let pagination = {}
return fetch(url, { headers: authHeaders(credentials) })
- .then((data) => {
- status = data.status
- statusText = data.statusText
- pagination = parseLinkHeaderPagination(data.headers.get('Link'), {
- flakeId: timeline !== 'bookmarks' && timeline !== 'notifications'
- })
- return data
- })
- .then((data) => data.json())
- .then((data) => {
- if (!data.errors) {
+ .then(async (response) => {
+ const success = response.ok
+
+ const data = await response.json()
+
+ if (success && !data.errors) {
+ const pagination = parseLinkHeaderPagination(response.headers.get('Link'), {
+ flakeId: timeline !== 'bookmarks' && timeline !== 'notifications'
+ })
+
return { data: data.map(isNotifications ? parseNotification : parseStatus), pagination }
} else {
- data.status = status
- data.statusText = statusText
+ data.errors ||= []
+ data.status = response.status
+ data.statusText = response.statusText
return data
}
})
@@ -627,11 +834,14 @@ const unretweet = ({ id, credentials }) => {
.then((data) => parseStatus(data))
}
-const bookmarkStatus = ({ id, credentials }) => {
+const bookmarkStatus = ({ id, credentials, ...options }) => {
return promisedRequest({
url: MASTODON_BOOKMARK_STATUS_URL(id),
headers: authHeaders(credentials),
- method: 'POST'
+ method: 'POST',
+ payload: {
+ folder_id: options.folder_id
+ }
})
}
@@ -652,6 +862,7 @@ const postStatus = ({
poll,
mediaIds = [],
inReplyToStatusId,
+ quoteId,
contentType,
preview,
idempotencyKey
@@ -670,7 +881,7 @@ const postStatus = ({
})
if (pollOptions.some(option => option !== '')) {
const normalizedPoll = {
- expires_in: poll.expiresIn,
+ expires_in: parseInt(poll.expiresIn, 10),
multiple: poll.multiple
}
Object.keys(normalizedPoll).forEach(key => {
@@ -684,11 +895,14 @@ const postStatus = ({
if (inReplyToStatusId) {
form.append('in_reply_to_id', inReplyToStatusId)
}
+ if (quoteId) {
+ form.append('quote_id', quoteId)
+ }
if (preview) {
form.append('preview', 'true')
}
- let postHeaders = authHeaders(credentials)
+ const postHeaders = authHeaders(credentials)
if (idempotencyKey) {
postHeaders['idempotency-key'] = idempotencyKey
}
@@ -704,9 +918,58 @@ const postStatus = ({
.then((data) => data.error ? data : parseStatus(data))
}
+const editStatus = ({
+ id,
+ credentials,
+ status,
+ spoilerText,
+ sensitive,
+ poll,
+ mediaIds = [],
+ contentType
+}) => {
+ const form = new FormData()
+ const pollOptions = poll.options || []
+
+ form.append('status', status)
+ if (spoilerText) form.append('spoiler_text', spoilerText)
+ if (sensitive) form.append('sensitive', sensitive)
+ if (contentType) form.append('content_type', contentType)
+ mediaIds.forEach(val => {
+ form.append('media_ids[]', val)
+ })
+
+ if (pollOptions.some(option => option !== '')) {
+ const normalizedPoll = {
+ expires_in: parseInt(poll.expiresIn, 10),
+ multiple: poll.multiple
+ }
+ Object.keys(normalizedPoll).forEach(key => {
+ form.append(`poll[${key}]`, normalizedPoll[key])
+ })
+
+ pollOptions.forEach(option => {
+ form.append('poll[options][]', option)
+ })
+ }
+
+ const putHeaders = authHeaders(credentials)
+
+ return fetch(MASTODON_STATUS_URL(id), {
+ body: form,
+ method: 'PUT',
+ headers: putHeaders
+ })
+ .then((response) => {
+ return response.json()
+ })
+ .then((data) => data.error ? data : parseStatus(data))
+}
+
const deleteStatus = ({ id, credentials }) => {
- return fetch(MASTODON_DELETE_URL(id), {
- headers: authHeaders(credentials),
+ return promisedRequest({
+ url: MASTODON_DELETE_URL(id),
+ credentials,
method: 'DELETE'
})
}
@@ -895,29 +1158,33 @@ const generateMfaBackupCodes = ({ credentials }) => {
}).then((data) => data.json())
}
-const fetchMutes = ({ credentials }) => {
- return promisedRequest({ url: MASTODON_USER_MUTES_URL, credentials })
+const fetchMutes = ({ maxId, credentials }) => {
+ const query = new URLSearchParams({ with_relationships: true })
+ if (maxId) {
+ query.append('max_id', maxId)
+ }
+ return promisedRequest({ url: `${MASTODON_USER_MUTES_URL}?${query.toString()}`, credentials })
.then((users) => users.map(parseUser))
}
-const muteUser = ({ id, credentials }) => {
- return promisedRequest({ url: MASTODON_MUTE_USER_URL(id), credentials, method: 'POST' })
+const muteUser = ({ id, expiresIn, credentials }) => {
+ const payload = {}
+ if (expiresIn) {
+ payload.expires_in = expiresIn
+ }
+ return promisedRequest({ url: MASTODON_MUTE_USER_URL(id), credentials, method: 'POST', payload })
}
const unmuteUser = ({ id, credentials }) => {
return promisedRequest({ url: MASTODON_UNMUTE_USER_URL(id), credentials, method: 'POST' })
}
-const subscribeUser = ({ id, credentials }) => {
- return promisedRequest({ url: MASTODON_SUBSCRIBE_USER(id), credentials, method: 'POST' })
-}
-
-const unsubscribeUser = ({ id, credentials }) => {
- return promisedRequest({ url: MASTODON_UNSUBSCRIBE_USER(id), credentials, method: 'POST' })
-}
-
-const fetchBlocks = ({ credentials }) => {
- return promisedRequest({ url: MASTODON_USER_BLOCKS_URL, credentials })
+const fetchBlocks = ({ maxId, credentials }) => {
+ const query = new URLSearchParams({ with_relationships: true })
+ if (maxId) {
+ query.append('max_id', maxId)
+ }
+ return promisedRequest({ url: `${MASTODON_USER_BLOCKS_URL}?${query.toString()}`, credentials })
.then((users) => users.map(parseUser))
}
@@ -993,7 +1260,7 @@ const vote = ({ pollId, choices, credentials }) => {
method: 'POST',
credentials,
payload: {
- choices: choices
+ choices
}
})
}
@@ -1053,8 +1320,8 @@ const reportUser = ({ credentials, userId, statusIds, comment, forward }) => {
url: MASTODON_REPORT_USER_URL,
method: 'POST',
payload: {
- 'account_id': userId,
- 'status_ids': statusIds,
+ account_id: userId,
+ status_ids: statusIds,
comment,
forward
},
@@ -1074,9 +1341,9 @@ const searchUsers = ({ credentials, query }) => {
.then((data) => data.map(parseUser))
}
-const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
+const search2 = ({ credentials, q, resolve, limit, offset, following, type }) => {
let url = MASTODON_SEARCH_2
- let params = []
+ const params = []
if (q) {
params.push(['q', encodeURIComponent(q)])
@@ -1098,9 +1365,13 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
params.push(['following', true])
}
+ if (type) {
+ params.push(['following', type])
+ }
+
params.push(['with_relationships', true])
- let queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
+ const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
url += `?${queryString}`
return fetch(url, { headers: authHeaders(credentials) })
@@ -1153,6 +1424,66 @@ const dismissNotification = ({ credentials, id }) => {
})
}
+const adminFetchAnnouncements = ({ credentials }) => {
+ return promisedRequest({ url: PLEROMA_ANNOUNCEMENTS_URL, credentials })
+}
+
+const fetchAnnouncements = ({ credentials }) => {
+ return promisedRequest({ url: MASTODON_ANNOUNCEMENTS_URL, credentials })
+}
+
+const dismissAnnouncement = ({ id, credentials }) => {
+ return promisedRequest({
+ url: MASTODON_ANNOUNCEMENTS_DISMISS_URL(id),
+ credentials,
+ method: 'POST'
+ })
+}
+
+const announcementToPayload = ({ content, startsAt, endsAt, allDay }) => {
+ const payload = { content }
+
+ if (typeof startsAt !== 'undefined') {
+ payload.starts_at = startsAt ? new Date(startsAt).toISOString() : null
+ }
+
+ if (typeof endsAt !== 'undefined') {
+ payload.ends_at = endsAt ? new Date(endsAt).toISOString() : null
+ }
+
+ if (typeof allDay !== 'undefined') {
+ payload.all_day = allDay
+ }
+
+ return payload
+}
+
+const postAnnouncement = ({ credentials, content, startsAt, endsAt, allDay }) => {
+ return promisedRequest({
+ url: PLEROMA_POST_ANNOUNCEMENT_URL,
+ credentials,
+ method: 'POST',
+ payload: announcementToPayload({ content, startsAt, endsAt, allDay })
+ })
+}
+
+const editAnnouncement = ({ id, credentials, content, startsAt, endsAt, allDay }) => {
+ return promisedRequest({
+ url: PLEROMA_EDIT_ANNOUNCEMENT_URL(id),
+ credentials,
+ method: 'PATCH',
+ payload: announcementToPayload({ content, startsAt, endsAt, allDay })
+ })
+}
+
+const deleteAnnouncement = ({ id, credentials }) => {
+ return promisedRequest({
+ url: PLEROMA_DELETE_ANNOUNCEMENT_URL(id),
+ credentials,
+ method: 'DELETE'
+ })
+}
+
export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
return Object.entries({
...(credentials
@@ -1170,7 +1501,8 @@ const MASTODON_STREAMING_EVENTS = new Set([
'update',
'notification',
'delete',
- 'filters_changed'
+ 'filters_changed',
+ 'status.update'
])
const PLEROMA_STREAMING_EVENTS = new Set([
@@ -1242,6 +1574,8 @@ export const handleMastoWS = (wsEvent) => {
const data = payload ? JSON.parse(payload) : null
if (event === 'update') {
return { event, status: parseStatus(data) }
+ } else if (event === 'status.update') {
+ return { event, status: parseStatus(data) }
} else if (event === 'notification') {
return { event, notification: parseNotification(data) }
} else if (event === 'pleroma:chat_update') {
@@ -1254,12 +1588,12 @@ export const handleMastoWS = (wsEvent) => {
}
export const WSConnectionStatus = Object.freeze({
- 'JOINED': 1,
- 'CLOSED': 2,
- 'ERROR': 3,
- 'DISABLED': 4,
- 'STARTING': 5,
- 'STARTING_INITIAL': 6
+ JOINED: 1,
+ CLOSED: 2,
+ ERROR: 3,
+ DISABLED: 4,
+ STARTING: 5,
+ STARTING_INITIAL: 6
})
const chats = ({ credentials }) => {
@@ -1297,11 +1631,11 @@ const chatMessages = ({ id, credentials, maxId, sinceId, limit = 20 }) => {
const sendChatMessage = ({ id, content, mediaId = null, idempotencyKey, credentials }) => {
const payload = {
- 'content': content
+ content
}
if (mediaId) {
- payload['media_id'] = mediaId
+ payload.media_id = mediaId
}
const headers = {}
@@ -1313,7 +1647,7 @@ const sendChatMessage = ({ id, content, mediaId = null, idempotencyKey, credenti
return promisedRequest({
url: PLEROMA_CHAT_MESSAGES_URL(id),
method: 'POST',
- payload: payload,
+ payload,
credentials,
headers
})
@@ -1324,7 +1658,7 @@ const readChat = ({ id, lastReadId, credentials }) => {
url: PLEROMA_CHAT_READ_URL(id),
method: 'POST',
payload: {
- 'last_read_id': lastReadId
+ last_read_id: lastReadId
},
credentials
})
@@ -1338,12 +1672,273 @@ const deleteChatMessage = ({ chatId, messageId, credentials }) => {
})
}
+const setReportState = ({ id, state, credentials }) => {
+ // TODO: Can't use promisedRequest because on OK this does not return json
+ // See https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1322
+ return fetch(PLEROMA_ADMIN_REPORTS, {
+ headers: {
+ ...authHeaders(credentials),
+ Accept: 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ method: 'PATCH',
+ body: JSON.stringify({
+ reports: [{
+ id,
+ state
+ }]
+ })
+ })
+ .then(data => {
+ if (data.status >= 500) {
+ throw Error(data.statusText)
+ } else if (data.status >= 400) {
+ return data.json()
+ }
+ return data
+ })
+ .then(data => {
+ if (data.errors) {
+ throw Error(data.errors[0].message)
+ }
+ })
+}
+
+// ADMIN STUFF // EXPERIMENTAL
+const fetchInstanceDBConfig = ({ credentials }) => {
+ return fetch(PLEROMA_ADMIN_CONFIG_URL, {
+ headers: authHeaders(credentials)
+ })
+ .then((response) => {
+ if (response.ok) {
+ return response.json()
+ } else {
+ return {
+ error: response
+ }
+ }
+ })
+}
+
+const fetchInstanceConfigDescriptions = ({ credentials }) => {
+ return fetch(PLEROMA_ADMIN_DESCRIPTIONS_URL, {
+ headers: authHeaders(credentials)
+ })
+ .then((response) => {
+ if (response.ok) {
+ return response.json()
+ } else {
+ return {
+ error: response
+ }
+ }
+ })
+}
+
+const fetchAvailableFrontends = ({ credentials }) => {
+ return fetch(PLEROMA_ADMIN_FRONTENDS_URL, {
+ headers: authHeaders(credentials)
+ })
+ .then((response) => {
+ if (response.ok) {
+ return response.json()
+ } else {
+ return {
+ error: response
+ }
+ }
+ })
+}
+
+const pushInstanceDBConfig = ({ credentials, payload }) => {
+ return fetch(PLEROMA_ADMIN_CONFIG_URL, {
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ ...authHeaders(credentials)
+ },
+ method: 'POST',
+ body: JSON.stringify(payload)
+ })
+ .then((response) => {
+ if (response.ok) {
+ return response.json()
+ } else {
+ return {
+ error: response
+ }
+ }
+ })
+}
+
+const installFrontend = ({ credentials, payload }) => {
+ return fetch(PLEROMA_ADMIN_FRONTENDS_INSTALL_URL, {
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ ...authHeaders(credentials)
+ },
+ method: 'POST',
+ body: JSON.stringify(payload)
+ })
+ .then((response) => {
+ if (response.ok) {
+ return response.json()
+ } else {
+ return {
+ error: response
+ }
+ }
+ })
+}
+
+const fetchScrobbles = ({ accountId, limit = 1 }) => {
+ let url = PLEROMA_SCROBBLES_URL(accountId)
+ const params = [['limit', limit]]
+ const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
+ url += `?${queryString}`
+ return fetch(url, {})
+ .then((response) => {
+ if (response.ok) {
+ return response.json()
+ } else {
+ return {
+ error: response
+ }
+ }
+ })
+}
+
+const deleteEmojiPack = ({ name }) => {
+ return fetch(PLEROMA_EMOJI_PACK_URL(name), { method: 'DELETE' })
+}
+
+const reloadEmoji = () => {
+ return fetch(PLEROMA_EMOJI_RELOAD_URL, { method: 'POST' })
+}
+
+const importEmojiFromFS = () => {
+ return fetch(PLEROMA_EMOJI_IMPORT_FS_URL)
+}
+
+const createEmojiPack = ({ name }) => {
+ return fetch(PLEROMA_EMOJI_PACK_URL(name), { method: 'POST' })
+}
+
+const listEmojiPacks = ({ page, pageSize }) => {
+ return fetch(PLEROMA_EMOJI_PACKS_URL(page, pageSize))
+}
+
+const listRemoteEmojiPacks = ({ instance, page, pageSize }) => {
+ if (!instance.startsWith('http')) {
+ instance = 'https://' + instance
+ }
+
+ return fetch(
+ PLEROMA_EMOJI_PACKS_LS_REMOTE_URL(instance, page, pageSize),
+ {
+ headers: { 'Content-Type': 'application/json' }
+ }
+ )
+}
+
+const downloadRemoteEmojiPack = ({ instance, packName, as }) => {
+ return fetch(
+ PLEROMA_EMOJI_PACKS_DL_REMOTE_URL,
+ {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ url: instance, name: packName, as
+ })
+ }
+ )
+}
+
+const saveEmojiPackMetadata = ({ name, newData }) => {
+ return fetch(
+ PLEROMA_EMOJI_PACK_URL(name),
+ {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ metadata: newData })
+ }
+ )
+}
+
+const addNewEmojiFile = ({ packName, file, shortcode, filename }) => {
+ const data = new FormData()
+ if (filename.trim() !== '') { data.set('filename', filename) }
+ if (shortcode.trim() !== '') { data.set('shortcode', shortcode) }
+ data.set('file', file)
+
+ return fetch(
+ PLEROMA_EMOJI_UPDATE_FILE_URL(packName),
+ { method: 'POST', body: data }
+ )
+}
+
+const updateEmojiFile = ({ packName, shortcode, newShortcode, newFilename, force }) => {
+ return fetch(
+ PLEROMA_EMOJI_UPDATE_FILE_URL(packName),
+ {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ shortcode, new_shortcode: newShortcode, new_filename: newFilename, force })
+ }
+ )
+}
+
+const deleteEmojiFile = ({ packName, shortcode }) => {
+ return fetch(`${PLEROMA_EMOJI_UPDATE_FILE_URL(packName)}&shortcode=${shortcode}`, { method: 'DELETE' })
+}
+
+const fetchBookmarkFolders = ({ credentials }) => {
+ const url = PLEROMA_BOOKMARK_FOLDERS_URL
+ return fetch(url, { headers: authHeaders(credentials) })
+ .then((data) => data.json())
+}
+
+const createBookmarkFolder = ({ name, emoji, credentials }) => {
+ const url = PLEROMA_BOOKMARK_FOLDERS_URL
+ const headers = authHeaders(credentials)
+ headers['Content-Type'] = 'application/json'
+
+ return fetch(url, {
+ headers,
+ method: 'POST',
+ body: JSON.stringify({ name, emoji })
+ }).then((data) => data.json())
+}
+
+const updateBookmarkFolder = ({ folderId, name, emoji, credentials }) => {
+ const url = PLEROMA_BOOKMARK_FOLDER_URL(folderId)
+ const headers = authHeaders(credentials)
+ headers['Content-Type'] = 'application/json'
+
+ return fetch(url, {
+ headers,
+ method: 'PATCH',
+ body: JSON.stringify({ name, emoji })
+ }).then((data) => data.json())
+}
+
+const deleteBookmarkFolder = ({ folderId, credentials }) => {
+ const url = PLEROMA_BOOKMARK_FOLDER_URL(folderId)
+ return fetch(url, {
+ method: 'DELETE',
+ headers: authHeaders(credentials)
+ })
+}
+
const apiService = {
verifyCredentials,
fetchTimeline,
fetchPinnedStatuses,
fetchConversation,
fetchStatus,
+ fetchStatusSource,
+ fetchStatusHistory,
fetchFriends,
exportFriends,
fetchFollowers,
@@ -1355,7 +1950,10 @@ const apiService = {
unmuteConversation,
blockUser,
unblockUser,
+ removeUserFromFollowers,
+ editUserNote,
fetchUser,
+ fetchUserByName,
fetchUserRelationship,
favorite,
unfavorite,
@@ -1364,14 +1962,13 @@ const apiService = {
bookmarkStatus,
unbookmarkStatus,
postStatus,
+ editStatus,
deleteStatus,
uploadMedia,
setMediaDescription,
fetchMutes,
muteUser,
unmuteUser,
- subscribeUser,
- unsubscribeUser,
fetchBlocks,
fetchOAuthTokens,
revokeOAuthToken,
@@ -1404,6 +2001,14 @@ const apiService = {
addBackup,
listBackups,
fetchFollowRequests,
+ fetchLists,
+ createList,
+ getList,
+ updateList,
+ getListAccounts,
+ addAccountsToList,
+ removeAccountsFromList,
+ deleteList,
approveUser,
denyUser,
suggestions,
@@ -1429,7 +2034,36 @@ const apiService = {
chatMessages,
sendChatMessage,
readChat,
- deleteChatMessage
+ deleteChatMessage,
+ setReportState,
+ fetchUserInLists,
+ fetchAnnouncements,
+ dismissAnnouncement,
+ postAnnouncement,
+ editAnnouncement,
+ deleteAnnouncement,
+ fetchScrobbles,
+ adminFetchAnnouncements,
+ fetchInstanceDBConfig,
+ fetchInstanceConfigDescriptions,
+ fetchAvailableFrontends,
+ pushInstanceDBConfig,
+ installFrontend,
+ importEmojiFromFS,
+ reloadEmoji,
+ listEmojiPacks,
+ createEmojiPack,
+ deleteEmojiPack,
+ saveEmojiPackMetadata,
+ addNewEmojiFile,
+ updateEmojiFile,
+ deleteEmojiFile,
+ listRemoteEmojiPacks,
+ downloadRemoteEmojiPack,
+ fetchBookmarkFolders,
+ createBookmarkFolder,
+ updateBookmarkFolder,
+ deleteBookmarkFolder
}
export default apiService
diff --git a/src/services/attributes_helper/attributes_helper.service.js b/src/services/attributes_helper/attributes_helper.service.js
@@ -0,0 +1,8 @@
+import { kebabCase } from 'lodash'
+
+const propsToNative = props => Object.keys(props).reduce((acc, cur) => {
+ acc[kebabCase(cur)] = props[cur]
+ return acc
+}, {})
+
+export { propsToNative }
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -2,10 +2,12 @@ import apiService, { getMastodonSocketURI, ProcessedWS } from '../api/api.servic
import timelineFetcher from '../timeline_fetcher/timeline_fetcher.service.js'
import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
+import listsFetcher from '../../services/lists_fetcher/lists_fetcher.service.js'
+import bookmarkFoldersFetcher from '../../services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js'
const backendInteractorService = credentials => ({
- startFetchingTimeline ({ timeline, store, userId = false, tag }) {
- return timelineFetcher.startFetching({ timeline, store, credentials, userId, tag })
+ startFetchingTimeline ({ timeline, store, userId = false, listId = false, statusId = false, bookmarkFolderId = false, tag }) {
+ return timelineFetcher.startFetching({ timeline, store, credentials, userId, listId, statusId, bookmarkFolderId, tag })
},
fetchTimeline (args) {
@@ -24,6 +26,14 @@ const backendInteractorService = credentials => ({
return followRequestFetcher.startFetching({ store, credentials })
},
+ startFetchingLists ({ store }) {
+ return listsFetcher.startFetching({ store, credentials })
+ },
+
+ startFetchingBookmarkFolders ({ store }) {
+ return bookmarkFoldersFetcher.startFetching({ store, credentials })
+ },
+
startUserSocket ({ store }) {
const serv = store.rootState.instance.server.replace('http', 'ws')
const url = serv + getMastodonSocketURI({ credentials, stream: 'user' })
diff --git a/src/services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js b/src/services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js
@@ -0,0 +1,22 @@
+import apiService from '../api/api.service.js'
+import { promiseInterval } from '../promise_interval/promise_interval.js'
+
+const fetchAndUpdate = ({ store, credentials }) => {
+ return apiService.fetchBookmarkFolders({ credentials })
+ .then(bookmarkFolders => {
+ store.commit('setBookmarkFolders', bookmarkFolders)
+ }, () => {})
+ .catch(() => {})
+}
+
+const startFetching = ({ credentials, store }) => {
+ const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store })
+ boundFetchAndUpdate()
+ return promiseInterval(boundFetchAndUpdate, 240000)
+}
+
+const bookmarkFoldersFetcher = {
+ startFetching
+}
+
+export default bookmarkFoldersFetcher
diff --git a/src/services/chat_service/chat_service.js b/src/services/chat_service/chat_service.js
@@ -7,7 +7,7 @@ const empty = (chatId) => {
messages: [],
newMessageCount: 0,
lastSeenMessageId: '0',
- chatId: chatId,
+ chatId,
minId: undefined,
maxId: undefined
}
@@ -101,7 +101,7 @@ const add = (storage, { messages: newMessages, updateMaxId = true }) => {
storage.messages = storage.messages.filter(msg => msg.id !== message.id)
}
Object.assign(fakeMessage, message, { error: false })
- delete fakeMessage['fakeId']
+ delete fakeMessage.fakeId
storage.idIndex[fakeMessage.id] = fakeMessage
delete storage.idIndex[message.fakeId]
@@ -178,7 +178,7 @@ const getView = (storage) => {
id: date.getTime().toString()
})
- previousMessage['isTail'] = true
+ previousMessage.isTail = true
currentMessageChainId = undefined
afterDate = true
}
@@ -193,15 +193,15 @@ const getView = (storage) => {
// end a message chian
if ((nextMessage && nextMessage.account_id) !== message.account_id) {
- object['isTail'] = true
+ object.isTail = true
currentMessageChainId = undefined
}
// start a new message chain
if ((previousMessage && previousMessage.data && previousMessage.data.account_id) !== message.account_id || afterDate) {
currentMessageChainId = _.uniqueId()
- object['isHead'] = true
- object['messageChainId'] = currentMessageChainId
+ object.isHead = true
+ object.messageChainId = currentMessageChainId
}
result.push(object)
diff --git a/src/services/chat_utils/chat_utils.js b/src/services/chat_utils/chat_utils.js
@@ -25,7 +25,7 @@ export const buildFakeMessage = ({ content, chatId, attachments, userId, idempot
chat_id: chatId,
created_at: new Date(),
id: `${new Date().getTime()}`,
- attachments: attachments,
+ attachments,
account_id: userId,
idempotency_key: idempotencyKey,
emojis: [],
diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
@@ -53,15 +53,6 @@ const c2linear = (bit) => {
}
/**
- * Converts sRGB into linear RGB
- * @param {Object} srgb - sRGB color
- * @returns {Object} linear rgb color
- */
-const srgbToLinear = (srgb) => {
- return 'rgb'.split('').reduce((acc, c) => { acc[c] = c2linear(srgb[c]); return acc }, {})
-}
-
-/**
* Calculates relative luminance for given color
* https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
* https://www.w3.org/TR/2008/REC-WCAG20-20081211/relative-luminance.xml
@@ -70,7 +61,10 @@ const srgbToLinear = (srgb) => {
* @returns {Number} relative luminance
*/
export const relativeLuminance = (srgb) => {
- const { r, g, b } = srgbToLinear(srgb)
+ const r = c2linear(srgb.r)
+ const g = c2linear(srgb.g)
+ const b = c2linear(srgb.b)
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b
}
@@ -110,13 +104,17 @@ export const getContrastRatioLayers = (text, layers, bedrock) => {
* @returns {Object} sRGB of resulting color
*/
export const alphaBlend = (fg, fga, bg) => {
- if (fga === 1 || typeof fga === 'undefined') return fg
- return 'rgb'.split('').reduce((acc, c) => {
- // Simplified https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
- // for opaque bg and transparent fg
- acc[c] = (fg[c] * fga + bg[c] * (1 - fga))
- return acc
- }, {})
+ if (fga === 1 || typeof fga === 'undefined') {
+ return fg
+ }
+
+ // Simplified https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
+ // for opaque bg and transparent fg
+ return {
+ r: (fg.r * fga + bg.r * (1 - fga)),
+ g: (fg.g * fga + bg.g * (1 - fga)),
+ b: (fg.b * fga + bg.b * (1 - fga))
+ }
}
/**
@@ -130,10 +128,11 @@ export const alphaBlendLayers = (bedrock, layers) => layers.reduce((acc, [color,
}, bedrock)
export const invert = (rgb) => {
- return 'rgb'.split('').reduce((acc, c) => {
- acc[c] = 255 - rgb[c]
- return acc
- }, {})
+ return {
+ r: 255 - rgb.r,
+ g: 255 - rgb.g,
+ b: 255 - rgb.b
+ }
}
/**
@@ -144,11 +143,14 @@ export const invert = (rgb) => {
*/
export const hex2rgb = (hex) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
- return result ? {
- r: parseInt(result[1], 16),
- g: parseInt(result[2], 16),
- b: parseInt(result[3], 16)
- } : null
+
+ return result
+ ? {
+ r: parseInt(result[1], 16),
+ g: parseInt(result[2], 16),
+ b: parseInt(result[3], 16)
+ }
+ : null
}
/**
@@ -159,11 +161,13 @@ export const hex2rgb = (hex) => {
* @returns {Object} result
*/
export const mixrgb = (a, b) => {
- return 'rgb'.split('').reduce((acc, k) => {
- acc[k] = (a[k] + b[k]) / 2
- return acc
- }, {})
+ return {
+ r: (a.r + b.r) / 2,
+ g: (a.g + b.g) / 2,
+ b: (a.b + b.b) / 2
+ }
}
+
/**
* Converts rgb object into a CSS rgba() color
*
@@ -171,7 +175,33 @@ export const mixrgb = (a, b) => {
* @returns {String} CSS rgba() color
*/
export const rgba2css = function (rgba) {
- return `rgba(${Math.floor(rgba.r)}, ${Math.floor(rgba.g)}, ${Math.floor(rgba.b)}, ${rgba.a})`
+ const base = {
+ r: 0,
+ g: 0,
+ b: 0,
+ a: 1
+ }
+
+ if (rgba !== null) {
+ if (rgba.r !== undefined && !isNaN(rgba.r)) {
+ base.r = rgba.r
+ }
+ if (rgba.g !== undefined && !isNaN(rgba.g)) {
+ base.g = rgba.g
+ }
+ if (rgba.b !== undefined && !isNaN(rgba.b)) {
+ base.b = rgba.b
+ }
+ if (rgba.a !== undefined && !isNaN(rgba.a)) {
+ base.a = rgba.a
+ }
+ } else {
+ base.r = 255
+ base.g = 255
+ base.b = 255
+ }
+
+ return `rgba(${Math.floor(base.r)}, ${Math.floor(base.g)}, ${Math.floor(base.b)}, ${base.a})`
}
/**
diff --git a/src/services/completion/completion.js b/src/services/completion/completion.js
@@ -35,7 +35,7 @@ export const addPositionToWords = (words) => {
}
export const splitByWhitespaceBoundary = (str) => {
- let result = []
+ const result = []
let currentWord = ''
for (let i = 0; i < str.length; i++) {
const currentChar = str[i]
diff --git a/src/services/date_utils/date_utils.js b/src/services/date_utils/date_utils.js
@@ -6,11 +6,14 @@ export const WEEK = 7 * DAY
export const MONTH = 30 * DAY
export const YEAR = 365.25 * DAY
-export const relativeTime = (date, nowThreshold = 1) => {
+export const relativeTimeMs = (date) => {
if (typeof date === 'string') date = Date.parse(date)
+ return Math.abs(Date.now() - date)
+}
+export const relativeTime = (date, nowThreshold = 1) => {
const round = Date.now() > date ? Math.floor : Math.ceil
- const d = Math.abs(Date.now() - date)
- let r = { num: round(d / YEAR), key: 'time.unit.years' }
+ const d = relativeTimeMs(date)
+ const r = { num: round(d / YEAR), key: 'time.unit.years' }
if (d < nowThreshold * SECOND) {
r.num = 0
r.key = 'time.now'
@@ -41,3 +44,55 @@ export const relativeTimeShort = (date, nowThreshold = 1) => {
r.key += '_short'
return r
}
+
+export const unitToSeconds = (unit, amount) => {
+ switch (unit) {
+ case 'minutes': return 0.001 * amount * MINUTE
+ case 'hours': return 0.001 * amount * HOUR
+ case 'days': return 0.001 * amount * DAY
+ }
+}
+
+export const secondsToUnit = (unit, amount) => {
+ switch (unit) {
+ case 'minutes': return (1000 * amount) / MINUTE
+ case 'hours': return (1000 * amount) / HOUR
+ case 'days': return (1000 * amount) / DAY
+ }
+}
+
+export const isSameYear = (a, b) => {
+ return a.getFullYear() === b.getFullYear()
+}
+
+export const isSameMonth = (a, b) => {
+ return a.getFullYear() === b.getFullYear() &&
+ a.getMonth() === b.getMonth()
+}
+
+export const isSameDay = (a, b) => {
+ return a.getFullYear() === b.getFullYear() &&
+ a.getMonth() === b.getMonth() &&
+ a.getDate() === b.getDate()
+}
+
+export const durationStrToMs = (str) => {
+ if (typeof str !== 'string') {
+ return 0
+ }
+
+ const unit = str.replace(/[0-9,.]+/, '')
+ const value = str.replace(/[^0-9,.]+/, '')
+ switch (unit) {
+ case 'd':
+ return value * DAY
+ case 'h':
+ return value * HOUR
+ case 'm':
+ return value * MINUTE
+ case 's':
+ return value * SECOND
+ default:
+ return 0
+ }
+}
diff --git a/src/services/desktop_notification_utils/desktop_notification_utils.js b/src/services/desktop_notification_utils/desktop_notification_utils.js
@@ -1,9 +1,38 @@
+import {
+ showDesktopNotification as swDesktopNotification,
+ closeDesktopNotification as swCloseDesktopNotification,
+ isSWSupported
+} from '../sw/sw.js'
+const state = { failCreateNotif: false }
+
export const showDesktopNotification = (rootState, desktopNotificationOpts) => {
if (!('Notification' in window && window.Notification.permission === 'granted')) return
- if (rootState.statuses.notifications.desktopNotificationSilence) { return }
+ if (rootState.notifications.desktopNotificationSilence) { return }
+
+ if (isSWSupported()) {
+ swDesktopNotification(desktopNotificationOpts)
+ } else if (!state.failCreateNotif) {
+ try {
+ const desktopNotification = new window.Notification(desktopNotificationOpts.title, desktopNotificationOpts)
+ setTimeout(desktopNotification.close.bind(desktopNotification), 5000)
+ } catch {
+ state.failCreateNotif = true
+ }
+ }
+}
+
+export const closeDesktopNotification = (rootState, { id }) => {
+ if (!('Notification' in window && window.Notification.permission === 'granted')) return
+
+ if (isSWSupported()) {
+ swCloseDesktopNotification({ id })
+ }
+}
+
+export const closeAllDesktopNotifications = (rootState) => {
+ if (!('Notification' in window && window.Notification.permission === 'granted')) return
- const desktopNotification = new window.Notification(desktopNotificationOpts.title, desktopNotificationOpts)
- // Chrome is known for not closing notifications automatically
- // according to MDN, anyway.
- setTimeout(desktopNotification.close.bind(desktopNotification), 5000)
+ if (isSWSupported()) {
+ swCloseDesktopNotification({})
+ }
}
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -39,15 +39,17 @@ const qvitterStatusType = (status) => {
export const parseUser = (data) => {
const output = {}
- const masto = data.hasOwnProperty('acct')
+ const masto = Object.prototype.hasOwnProperty.call(data, 'acct')
// case for users in "mentions" property for statuses in MastoAPI
- const mastoShort = masto && !data.hasOwnProperty('avatar')
+ const mastoShort = masto && !Object.prototype.hasOwnProperty.call(data, 'avatar')
+ output.inLists = null
output.id = String(data.id)
output._original = data // used for server-side settings
if (masto) {
output.screen_name = data.acct
+ output.fqn = data.fqn
output.statusnet_profile_url = data.url
// There's nothing else to get
@@ -90,6 +92,9 @@ export const parseUser = (data) => {
output.bot = data.bot
if (data.pleroma) {
+ if (data.pleroma.settings_store) {
+ output.storage = data.pleroma.settings_store['pleroma-fe']
+ }
const relationship = data.pleroma.relationship
output.background_image = data.pleroma.background_image
@@ -102,6 +107,7 @@ export const parseUser = (data) => {
output.allow_following_move = data.pleroma.allow_following_move
+ output.hide_favorites = data.pleroma.hide_favorites
output.hide_follows = data.pleroma.hide_follows
output.hide_followers = data.pleroma.hide_followers
output.hide_follows_count = data.pleroma.hide_follows_count
@@ -119,6 +125,36 @@ export const parseUser = (data) => {
} else {
output.role = 'member'
}
+
+ output.birthday = data.pleroma.birthday
+
+ if (data.pleroma.privileges) {
+ output.privileges = data.pleroma.privileges
+ } else if (data.pleroma.is_admin) {
+ output.privileges = [
+ 'users_read',
+ 'users_manage_invites',
+ 'users_manage_activation_state',
+ 'users_manage_tags',
+ 'users_manage_credentials',
+ 'users_delete',
+ 'messages_read',
+ 'messages_delete',
+ 'instances_delete',
+ 'reports_manage_reports',
+ 'moderation_log_read',
+ 'announcements_manage_announcements',
+ 'emoji_manage_emoji',
+ 'statistics_read'
+ ]
+ } else if (data.pleroma.is_moderator) {
+ output.privileges = [
+ 'messages_delete',
+ 'reports_manage_reports'
+ ]
+ } else {
+ output.privileges = []
+ }
}
if (data.source) {
@@ -129,6 +165,8 @@ export const parseUser = (data) => {
output.no_rich_text = data.source.pleroma.no_rich_text
output.show_role = data.source.pleroma.show_role
output.discoverable = data.source.pleroma.discoverable
+ output.show_birthday = data.pleroma.show_birthday
+ output.actor_type = data.source.pleroma.actor_type
}
}
@@ -211,12 +249,14 @@ export const parseUser = (data) => {
output.screen_name_ui = output.screen_name
if (output.screen_name && output.screen_name.includes('@')) {
const parts = output.screen_name.split('@')
- let unicodeDomain = punycode.toUnicode(parts[1])
+ const unicodeDomain = punycode.toUnicode(parts[1])
if (unicodeDomain !== parts[1]) {
// Add some identifier so users can potentially spot spoofing attempts:
// lain.com and xn--lin-6cd.com would appear identical otherwise.
- unicodeDomain = '🌏' + unicodeDomain
+ output.screen_name_ui_contains_non_ascii = true
output.screen_name_ui = [parts[0], unicodeDomain].join('@')
+ } else {
+ output.screen_name_ui_contains_non_ascii = false
}
}
@@ -225,7 +265,7 @@ export const parseUser = (data) => {
export const parseAttachment = (data) => {
const output = {}
- const masto = !data.hasOwnProperty('oembed')
+ const masto = !Object.prototype.hasOwnProperty.call(data, 'oembed')
if (masto) {
// Not exactly same...
@@ -244,9 +284,19 @@ export const parseAttachment = (data) => {
return output
}
+export const parseSource = (data) => {
+ const output = {}
+
+ output.text = data.text
+ output.spoiler_text = data.spoiler_text
+ output.content_type = data.content_type
+
+ return output
+}
+
export const parseStatus = (data) => {
const output = {}
- const masto = data.hasOwnProperty('account')
+ const masto = Object.prototype.hasOwnProperty.call(data, 'account')
if (masto) {
output.favorited = data.favourited
@@ -265,6 +315,8 @@ export const parseStatus = (data) => {
output.tags = data.tags
+ output.edited_at = data.edited_at
+
if (data.pleroma) {
const { pleroma } = data
output.text = pleroma.content ? data.pleroma.content['text/plain'] : data.content
@@ -275,6 +327,12 @@ export const parseStatus = (data) => {
output.thread_muted = pleroma.thread_muted
output.emoji_reactions = pleroma.emoji_reactions
output.parent_visible = pleroma.parent_visible === undefined ? true : pleroma.parent_visible
+ output.quote = pleroma.quote ? parseStatus(pleroma.quote) : undefined
+ output.quote_id = pleroma.quote_id ? pleroma.quote_id : (output.quote ? output.quote.id : undefined)
+ output.quote_url = pleroma.quote_url
+ output.quote_visible = pleroma.quote_visible
+ output.quotes_count = pleroma.quotes_count
+ output.bookmark_folder_id = pleroma.bookmark_folder
} else {
output.text = data.content
output.summary = data.spoiler_text
@@ -366,15 +424,19 @@ export const parseStatus = (data) => {
output.favoritedBy = []
output.rebloggedBy = []
+ if (Object.prototype.hasOwnProperty.call(data, 'originalStatus')) {
+ Object.assign(output, data.originalStatus)
+ }
+
return output
}
export const parseNotification = (data) => {
const mastoDict = {
- 'favourite': 'like',
- 'reblog': 'repeat'
+ favourite: 'like',
+ reblog: 'repeat'
}
- const masto = !data.hasOwnProperty('ntype')
+ const masto = !Object.prototype.hasOwnProperty.call(data, 'ntype')
const output = {}
if (masto) {
@@ -383,12 +445,19 @@ export const parseNotification = (data) => {
// TODO: null check should be a temporary fix, I guess.
// Investigate why backend does this.
output.status = isStatusNotification(output.type) && data.status !== null ? parseStatus(data.status) : null
- output.action = output.status // TODO: Refactor, this is unneeded
output.target = output.type !== 'move'
? null
: parseUser(data.target)
output.from_profile = parseUser(data.account)
output.emoji = data.emoji
+ output.emoji_url = data.emoji_url
+ if (data.report) {
+ output.report = data.report
+ output.report.content = data.report.content
+ output.report.acct = parseUser(data.report.account)
+ output.report.actor = parseUser(data.report.actor)
+ output.report.statuses = data.report.statuses.map(parseStatus)
+ }
} else {
const parsedNotice = parseStatus(data.notice)
output.type = data.ntype
diff --git a/src/services/errors/errors.js b/src/services/errors/errors.js
@@ -26,6 +26,7 @@ export class RegistrationError extends Error {
// the error is probably a JSON object with a single key, "errors", whose value is another JSON object containing the real errors
if (typeof error === 'string') {
error = JSON.parse(error)
+ // eslint-disable-next-line
if (error.hasOwnProperty('error')) {
error = JSON.parse(error.error)
}
diff --git a/src/services/export_import/export_import.js b/src/services/export_import/export_import.js
@@ -2,15 +2,23 @@ import utf8 from 'utf8'
export const newExporter = ({
filename = 'data',
+ mime = 'application/json',
+ extension = '.json',
getExportedObject
}) => ({
exportData () {
- const stringified = utf8.encode(JSON.stringify(getExportedObject(), null, 2)) // Pretty-print and indent with 2 spaces
+ let stringified
+ if (mime === 'application/json') {
+ stringified = utf8.encode(JSON.stringify(getExportedObject(), null, 2)) // Pretty-print and indent with 2 spaces
+ } else {
+ stringified = utf8.encode(getExportedObject()) // Pretty-print and indent with 2 spaces
+ }
// Create an invisible link with a data url and simulate a click
const e = document.createElement('a')
- e.setAttribute('download', `${filename}.json`)
- e.setAttribute('href', 'data:application/json;base64,' + window.btoa(stringified))
+ const realFilename = typeof filename === 'function' ? filename() : filename
+ e.setAttribute('download', `${realFilename}.${extension}`)
+ e.setAttribute('href', `data:${mime};base64, ${window.btoa(stringified)}`)
e.style.display = 'none'
document.body.appendChild(e)
@@ -20,6 +28,8 @@ export const newExporter = ({
})
export const newImporter = ({
+ accept = '.json',
+ parser = (string) => JSON.parse(string),
onImport,
onImportFailure,
validator = () => true
@@ -27,18 +37,19 @@ export const newImporter = ({
importData () {
const filePicker = document.createElement('input')
filePicker.setAttribute('type', 'file')
- filePicker.setAttribute('accept', '.json')
+ filePicker.setAttribute('accept', accept)
filePicker.addEventListener('change', event => {
if (event.target.files[0]) {
+ const filename = event.target.files[0].name
// eslint-disable-next-line no-undef
const reader = new FileReader()
reader.onload = ({ target }) => {
try {
- const parsed = JSON.parse(target.result)
- const validationResult = validator(parsed)
+ const parsed = parser(target.result, filename)
+ const validationResult = validator(parsed, filename)
if (validationResult === true) {
- onImport(parsed)
+ onImport(parsed, filename)
} else {
onImportFailure({ validationResult })
}
diff --git a/src/services/favicon_service/favicon_service.js b/src/services/favicon_service/favicon_service.js
@@ -55,10 +55,13 @@ const createFaviconService = () => {
})
}
+ const getOriginalFavicons = () => [...favicons]
+
return {
initFaviconService,
clearFaviconBadge,
- drawFaviconBadge
+ drawFaviconBadge,
+ getOriginalFavicons
}
}
diff --git a/src/services/file_size_format/file_size_format.js b/src/services/file_size_format/file_size_format.js
@@ -1,15 +1,14 @@
-const fileSizeFormat = (num) => {
- var exponent
- var unit
- var units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']
+const fileSizeFormat = (numArg) => {
+ const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']
+ let num = numArg
if (num < 1) {
return num + ' ' + units[0]
}
- exponent = Math.min(Math.floor(Math.log(num) / Math.log(1024)), units.length - 1)
+ const exponent = Math.min(Math.floor(Math.log(num) / Math.log(1024)), units.length - 1)
num = (num / Math.pow(1024, exponent)).toFixed(2) * 1
- unit = units[exponent]
- return { num: num, unit: unit }
+ const unit = units[exponent]
+ return { num, unit }
}
const fileSizeFormatService = {
fileSizeFormat
diff --git a/src/services/file_type/file_type.service.js b/src/services/file_type/file_type.service.js
@@ -1,7 +1,7 @@
// TODO this func might as well take the entire file and use its mimetype
// or the entire service could be just mimetype service that only operates
// on mimetypes and not files. Currently the naming is confusing.
-const fileType = mimetype => {
+export const fileType = mimetype => {
if (mimetype.match(/flash/)) {
return 'flash'
}
@@ -25,11 +25,25 @@ const fileType = mimetype => {
return 'unknown'
}
-const fileMatchesSomeType = (types, file) =>
+export const fileTypeExt = url => {
+ if (url.match(/\.(a?png|jpe?g|gif|webp|avif)$/)) {
+ return 'image'
+ }
+ if (url.match(/\.(ogv|mp4|webm|mov)$/)) {
+ return 'video'
+ }
+ if (url.match(/\.(it|s3m|mod|umx|mp3|aac|m4a|flac|alac|ogg|oga|opus|wav|ape|midi?)$/)) {
+ return 'audio'
+ }
+ return 'unknown'
+}
+
+export const fileMatchesSomeType = (types, file) =>
types.some(type => fileType(file.mimetype) === type)
const fileTypeService = {
fileType,
+ fileTypeExt,
fileMatchesSomeType
}
diff --git a/src/services/html_converter/html_line_converter.service.js b/src/services/html_converter/html_line_converter.service.js
@@ -46,7 +46,7 @@ export const convertHtmlToLines = (html = '') => {
// All block-level elements that aren't empty elements, i.e. not <hr>
const nonEmptyElements = new Set(visualLineElements)
// Difference
- for (let elem of emptyElements) {
+ for (const elem of emptyElements) {
nonEmptyElements.delete(elem)
}
@@ -56,7 +56,7 @@ export const convertHtmlToLines = (html = '') => {
...emptyElements.values()
])
- let buffer = [] // Current output buffer
+ const buffer = [] // Current output buffer
const level = [] // How deep we are in tags and which tags were there
let textBuffer = '' // Current line content
let tagBuffer = null // Current tag buffer, if null = we are not currently reading a tag
diff --git a/src/services/html_converter/utility.service.js b/src/services/html_converter/utility.service.js
@@ -5,7 +5,7 @@
* @return {String} - tagname, i.e. "div"
*/
export const getTagName = (tag) => {
- const result = /(?:<\/(\w+)>|<(\w+)\s?.*?\/?>)/gi.exec(tag)
+ const result = /(?:<\/(\w+)>|<(\w+)\s?.*?\/?>)/gis.exec(tag)
return result && (result[1] || result[2])
}
@@ -16,19 +16,27 @@ export const getTagName = (tag) => {
* @return {Object} - map of attributes key = attribute name, value = attribute value
* attributes without values represented as boolean true
*/
-export const getAttrs = tag => {
+export const getAttrs = (tag, filter) => {
const innertag = tag
.substring(1, tag.length - 1)
.replace(new RegExp('^' + getTagName(tag)), '')
.replace(/\/?$/, '')
.trim()
- const attrs = Array.from(innertag.matchAll(/([a-z0-9-]+)(?:=("[^"]+?"|'[^']+?'))?/gi))
+ const attrs = Array.from(innertag.matchAll(/([a-z]+[a-z0-9-]*)(?:=("[^"]+?"|'[^']+?'))?/gi))
.map(([trash, key, value]) => [key, value])
.map(([k, v]) => {
if (!v) return [k, true]
return [k, v.substring(1, v.length - 1)]
})
- return Object.fromEntries(attrs)
+ const defaultFilter = ([k, v]) => {
+ const attrKey = k.toLowerCase()
+ if (attrKey === 'style') return false
+ if (attrKey === 'class') {
+ return v === 'greentext' || v === 'cyantext'
+ }
+ return true
+ }
+ return Object.fromEntries(attrs.filter(filter || defaultFilter))
}
/**
@@ -50,7 +58,7 @@ export const processTextForEmoji = (text, emojis, processor) => {
if (char === ':') {
const next = text.slice(i + 1)
let found = false
- for (let emoji of emojis) {
+ for (const emoji of emojis) {
if (next.slice(0, emoji.shortcode.length + 1) === (emoji.shortcode + ':')) {
found = emoji
break
diff --git a/src/services/lists_fetcher/lists_fetcher.service.js b/src/services/lists_fetcher/lists_fetcher.service.js
@@ -0,0 +1,22 @@
+import apiService from '../api/api.service.js'
+import { promiseInterval } from '../promise_interval/promise_interval.js'
+
+const fetchAndUpdate = ({ store, credentials }) => {
+ return apiService.fetchLists({ credentials })
+ .then(lists => {
+ store.commit('setLists', lists)
+ }, () => {})
+ .catch(() => {})
+}
+
+const startFetching = ({ credentials, store }) => {
+ const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store })
+ boundFetchAndUpdate()
+ return promiseInterval(boundFetchAndUpdate, 240000)
+}
+
+const listsFetcher = {
+ startFetching
+}
+
+export default listsFetcher
diff --git a/src/services/locale/locale.service.js b/src/services/locale/locale.service.js
@@ -3,31 +3,39 @@ import ISO6391 from 'iso-639-1'
import _ from 'lodash'
const specialLanguageCodes = {
- 'ja_easy': 'ja',
- 'zh_Hant': 'zh-HANT',
- 'zh': 'zh-Hans'
+ pdc: 'en',
+ ja_easy: 'ja',
+ zh_Hant: 'zh-HANT',
+ zh: 'zh-Hans'
}
const internalToBrowserLocale = code => specialLanguageCodes[code] || code
const internalToBackendLocale = code => internalToBrowserLocale(code).replace('_', '-')
+const internalToBackendLocaleMulti = codes => {
+ const langs = Array.isArray(codes) ? codes : [codes]
+ return langs.map(internalToBackendLocale).join(',')
+}
const getLanguageName = (code) => {
const specialLanguageNames = {
- 'ja_easy': 'やさしいにほんご',
- 'zh': '简体中文',
- 'zh_Hant': '繁體中文'
+ pdc: 'Pennsilfaanisch-Deitsch',
+ ja_easy: 'やさしいにほんご',
+ 'nan-TW': '臺語(閩南語)',
+ zh: '简体中文',
+ zh_Hant: '繁體中文'
}
const languageName = specialLanguageNames[code] || ISO6391.getNativeName(code)
const browserLocale = internalToBrowserLocale(code)
return languageName.charAt(0).toLocaleUpperCase(browserLocale) + languageName.slice(1)
}
-const languages = _.map(languagesObject.languages, (code) => ({ code: code, name: getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name))
+const languages = _.map(languagesObject.languages, (code) => ({ code, name: getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name))
const localeService = {
internalToBrowserLocale,
internalToBackendLocale,
+ internalToBackendLocaleMulti,
languages,
getLanguageName
}
diff --git a/src/services/matcher/matcher.service.js b/src/services/matcher/matcher.service.js
@@ -14,8 +14,11 @@ export const mentionMatchesUrl = (attention, url) => {
* @param {string} url
*/
export const extractTagFromUrl = (url) => {
- const regex = /tag[s]*\/(\w+)$/g
- const result = regex.exec(url)
+ const decoded = decodeURI(url)
+ // https://git.pleroma.social/pleroma/elixir-libraries/linkify/-/blob/master/lib/linkify/parser.ex
+ // https://www.pcre.org/original/doc/html/pcrepattern.html
+ const regex = /tag[s]*\/([\p{L}\p{N}_]*[\p{Alphabetic}_·\u{200c}][\p{L}\p{N}_·\p{M}\u{200c}]*)$/ug
+ const result = regex.exec(decoded)
if (!result) {
return false
}
diff --git a/src/services/new_api/oauth.js b/src/services/new_api/oauth.js
@@ -10,7 +10,8 @@ export const getOrCreateApp = ({ clientId, clientSecret, instance, commit }) =>
const url = `${instance}/api/v1/apps`
const form = new window.FormData()
- form.append('client_name', `PleromaFE_${window.___pleromafe_commit_hash}_${(new Date()).toISOString()}`)
+ form.append('client_name', 'PleromaFE')
+ form.append('website', 'https://pleroma.social')
form.append('redirect_uris', REDIRECT_URI)
form.append('scopes', 'read write follow push admin')
diff --git a/src/services/new_api/password_reset.js b/src/services/new_api/password_reset.js
@@ -1,6 +1,6 @@
import { reduce } from 'lodash'
-const MASTODON_PASSWORD_RESET_URL = `/auth/password`
+const MASTODON_PASSWORD_RESET_URL = '/auth/password'
const resetPassword = ({ instance, email }) => {
const params = { email }
diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js
@@ -1,27 +1,37 @@
-import { filter, sortBy, includes } from 'lodash'
import { muteWordHits } from '../status_parser/status_parser.js'
import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js'
-export const notificationsFromStore = store => store.state.statuses.notifications.data
+import FaviconService from 'src/services/favicon_service/favicon_service.js'
+
+export const ACTIONABLE_NOTIFICATION_TYPES = new Set(['mention', 'pleroma:report', 'follow_request'])
+
+let cachedBadgeUrl = null
+
+export const notificationsFromStore = store => store.state.notifications.data
export const visibleTypes = store => {
- const rootState = store.rootState || store.state
+ // When called from within a module we need rootGetters to access wider scope
+ // however when called from a component (i.e. this.$store) we already have wider scope
+ const rootGetters = store.rootGetters || store.getters
+ const { notificationVisibility } = rootGetters.mergedConfig
return ([
- rootState.config.notificationVisibility.likes && 'like',
- rootState.config.notificationVisibility.mentions && 'mention',
- rootState.config.notificationVisibility.repeats && 'repeat',
- rootState.config.notificationVisibility.follows && 'follow',
- rootState.config.notificationVisibility.followRequest && 'follow_request',
- rootState.config.notificationVisibility.moves && 'move',
- rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction',
- rootState.config.notificationVisibility.polls && 'poll'
+ notificationVisibility.likes && 'like',
+ notificationVisibility.mentions && 'mention',
+ notificationVisibility.statuses && 'status',
+ notificationVisibility.repeats && 'repeat',
+ notificationVisibility.follows && 'follow',
+ notificationVisibility.followRequest && 'follow_request',
+ notificationVisibility.moves && 'move',
+ notificationVisibility.emojiReactions && 'pleroma:emoji_reaction',
+ notificationVisibility.reports && 'pleroma:report',
+ notificationVisibility.polls && 'poll'
].filter(_ => _))
}
-const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reaction', 'poll']
+const statusNotifications = new Set(['like', 'mention', 'status', 'repeat', 'pleroma:emoji_reaction', 'poll'])
-export const isStatusNotification = (type) => includes(statusNotifications, type)
+export const isStatusNotification = (type) => statusNotifications.has(type)
export const isValidNotification = (notification) => {
if (isStatusNotification(notification.type) && !notification.status) {
@@ -48,35 +58,57 @@ const sortById = (a, b) => {
const isMutedNotification = (store, notification) => {
if (!notification.status) return
- return notification.status.muted || muteWordHits(notification.status, store.rootGetters.mergedConfig.muteWords).length > 0
+ const rootGetters = store.rootGetters || store.getters
+ return notification.status.muted || muteWordHits(notification.status, rootGetters.mergedConfig.muteWords).length > 0
}
export const maybeShowNotification = (store, notification) => {
const rootState = store.rootState || store.state
+ const rootGetters = store.rootGetters || store.getters
if (notification.seen) return
if (!visibleTypes(store).includes(notification.type)) return
if (notification.type === 'mention' && isMutedNotification(store, notification)) return
- const notificationObject = prepareNotificationObject(notification, store.rootGetters.i18n)
+ const notificationObject = prepareNotificationObject(notification, rootGetters.i18n)
showDesktopNotification(rootState, notificationObject)
}
export const filteredNotificationsFromStore = (store, types) => {
// map is just to clone the array since sort mutates it and it causes some issues
- let sortedNotifications = notificationsFromStore(store).map(_ => _).sort(sortById)
- sortedNotifications = sortBy(sortedNotifications, 'seen')
+ const sortedNotifications = notificationsFromStore(store).map(_ => _).sort(sortById)
+ // TODO implement sorting elsewhere and make it optional
return sortedNotifications.filter(
(notification) => (types || visibleTypes(store)).includes(notification.type)
)
}
-export const unseenNotificationsFromStore = store =>
- filter(filteredNotificationsFromStore(store), ({ seen }) => !seen)
+export const unseenNotificationsFromStore = store => {
+ const rootGetters = store.rootGetters || store.getters
+ const ignoreInactionableSeen = rootGetters.mergedConfig.ignoreInactionableSeen
+
+ return filteredNotificationsFromStore(store).filter(({ seen, type }) => {
+ if (!ignoreInactionableSeen) return !seen
+ if (seen) return false
+ return ACTIONABLE_NOTIFICATION_TYPES.has(type)
+ })
+}
export const prepareNotificationObject = (notification, i18n) => {
+ if (cachedBadgeUrl === null) {
+ const favicons = FaviconService.getOriginalFavicons()
+ const favicon = favicons[favicons.length - 1]
+ if (!favicon) {
+ cachedBadgeUrl = 'about:blank'
+ } else {
+ cachedBadgeUrl = favicon.favimg.src
+ }
+ }
+
const notifObj = {
- tag: notification.id
+ tag: notification.id,
+ type: notification.type,
+ badge: cachedBadgeUrl
}
const status = notification.status
const title = notification.from_profile.name
@@ -87,6 +119,9 @@ export const prepareNotificationObject = (notification, i18n) => {
case 'like':
i18nString = 'favorited_you'
break
+ case 'status':
+ i18nString = 'subscribed_status'
+ break
case 'repeat':
i18nString = 'repeated_you'
break
@@ -99,6 +134,9 @@ export const prepareNotificationObject = (notification, i18n) => {
case 'follow_request':
i18nString = 'follow_request'
break
+ case 'pleroma:report':
+ i18nString = 'submitted_report'
+ break
case 'poll':
i18nString = 'poll_ended'
break
@@ -120,3 +158,18 @@ export const prepareNotificationObject = (notification, i18n) => {
return notifObj
}
+
+export const countExtraNotifications = (store) => {
+ const rootGetters = store.rootGetters || store.getters
+ const mergedConfig = rootGetters.mergedConfig
+
+ if (!mergedConfig.showExtraNotifications) {
+ return 0
+ }
+
+ return [
+ mergedConfig.showChatsInExtraNotifications ? rootGetters.unreadChatCount : 0,
+ mergedConfig.showAnnouncementsInExtraNotifications ? rootGetters.unreadAnnouncementCount : 0,
+ mergedConfig.showFollowRequestsInExtraNotifications ? rootGetters.followRequestCount : 0
+ ].reduce((a, c) => a + c, 0)
+}
diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -1,6 +1,22 @@
import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js'
+// For using include_types when fetching notifications.
+// Note: chat_mention excluded as pleroma-fe polls them separately
+const mastoApiNotificationTypes = [
+ 'mention',
+ 'status',
+ 'favourite',
+ 'reblog',
+ 'follow',
+ 'follow_request',
+ 'move',
+ 'poll',
+ 'pleroma:emoji_reaction',
+ 'pleroma:chat_mention',
+ 'pleroma:report'
+]
+
const update = ({ store, notifications, older }) => {
store.dispatch('addNewNotifications', { notifications, older })
}
@@ -9,23 +25,24 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => {
const args = { credentials }
const { getters } = store
const rootState = store.rootState || store.state
- const timelineData = rootState.statuses.notifications
+ const timelineData = rootState.notifications
const hideMutedPosts = getters.mergedConfig.hideMutedPosts
- args['withMuted'] = !hideMutedPosts
+ args.includeTypes = mastoApiNotificationTypes
+ args.withMuted = !hideMutedPosts
- args['timeline'] = 'notifications'
+ args.timeline = 'notifications'
if (older) {
if (timelineData.minId !== Number.POSITIVE_INFINITY) {
- args['until'] = timelineData.minId
+ args.until = timelineData.minId
}
return fetchNotifications({ store, args, older })
} else {
// fetch new notifications
if (since === undefined && timelineData.maxId !== Number.POSITIVE_INFINITY) {
- args['since'] = timelineData.maxId
+ args.since = timelineData.maxId
} else if (since !== null) {
- args['since'] = since
+ args.since = since
}
const result = fetchNotifications({ store, args, older })
@@ -36,10 +53,14 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => {
// The normal maxId-check does not tell if older notifications have changed
const notifications = timelineData.data
const readNotifsIds = notifications.filter(n => n.seen).map(n => n.id)
- const numUnseenNotifs = notifications.length - readNotifsIds.length
- if (numUnseenNotifs > 0 && readNotifsIds.length > 0) {
- args['since'] = Math.max(...readNotifsIds)
- fetchNotifications({ store, args, older })
+ const unreadNotifsIds = notifications.filter(n => !n.seen).map(n => n.id)
+ if (readNotifsIds.length > 0 && readNotifsIds.length > 0) {
+ const minId = Math.min(...unreadNotifsIds) // Oldest known unread notification
+ if (minId !== Infinity) {
+ args.since = false // Don't use since_id since it sorta conflicts with min_id
+ args.minId = minId - 1 // go beyond
+ fetchNotifications({ store, args, older })
+ }
}
return result
@@ -63,6 +84,7 @@ const fetchNotifications = ({ store, args, older }) => {
messageArgs: [error.message],
timeout: 5000
})
+ console.error(error)
})
}
diff --git a/src/services/push/push.js b/src/services/push/push.js
@@ -1,111 +0,0 @@
-import runtime from 'serviceworker-webpack-plugin/lib/runtime'
-
-function urlBase64ToUint8Array (base64String) {
- const padding = '='.repeat((4 - base64String.length % 4) % 4)
- const base64 = (base64String + padding)
- .replace(/-/g, '+')
- .replace(/_/g, '/')
-
- const rawData = window.atob(base64)
- return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)))
-}
-
-function isPushSupported () {
- return 'serviceWorker' in navigator && 'PushManager' in window
-}
-
-function getOrCreateServiceWorker () {
- return runtime.register()
- .catch((err) => console.error('Unable to get or create a service worker.', err))
-}
-
-function subscribePush (registration, isEnabled, vapidPublicKey) {
- if (!isEnabled) return Promise.reject(new Error('Web Push is disabled in config'))
- if (!vapidPublicKey) return Promise.reject(new Error('VAPID public key is not found'))
-
- const subscribeOptions = {
- userVisibleOnly: true,
- applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
- }
- return registration.pushManager.subscribe(subscribeOptions)
-}
-
-function unsubscribePush (registration) {
- return registration.pushManager.getSubscription()
- .then((subscribtion) => {
- if (subscribtion === null) { return }
- return subscribtion.unsubscribe()
- })
-}
-
-function deleteSubscriptionFromBackEnd (token) {
- return window.fetch('/api/v1/push/subscription/', {
- method: 'DELETE',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- }
- }).then((response) => {
- if (!response.ok) throw new Error('Bad status code from server.')
- return response
- })
-}
-
-function sendSubscriptionToBackEnd (subscription, token, notificationVisibility) {
- return window.fetch('/api/v1/push/subscription/', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify({
- subscription,
- data: {
- alerts: {
- follow: notificationVisibility.follows,
- favourite: notificationVisibility.likes,
- mention: notificationVisibility.mentions,
- reblog: notificationVisibility.repeats,
- move: notificationVisibility.moves
- }
- }
- })
- }).then((response) => {
- if (!response.ok) throw new Error('Bad status code from server.')
- return response.json()
- }).then((responseData) => {
- if (!responseData.id) throw new Error('Bad response from server.')
- return responseData
- })
-}
-
-export function registerPushNotifications (isEnabled, vapidPublicKey, token, notificationVisibility) {
- if (isPushSupported()) {
- getOrCreateServiceWorker()
- .then((registration) => subscribePush(registration, isEnabled, vapidPublicKey))
- .then((subscription) => sendSubscriptionToBackEnd(subscription, token, notificationVisibility))
- .catch((e) => console.warn(`Failed to setup Web Push Notifications: ${e.message}`))
- }
-}
-
-export function unregisterPushNotifications (token) {
- if (isPushSupported()) {
- Promise.all([
- deleteSubscriptionFromBackEnd(token),
- getOrCreateServiceWorker()
- .then((registration) => {
- return unsubscribePush(registration).then((result) => [registration, result])
- })
- .then(([registration, unsubResult]) => {
- if (!unsubResult) {
- console.warn('Push subscription cancellation wasn\'t successful, killing SW anyway...')
- }
- return registration.unregister().then((result) => {
- if (!result) {
- console.warn('Failed to kill SW')
- }
- })
- })
- ]).catch((e) => console.warn(`Failed to disable Web Push Notifications: ${e.message}`))
- }
-}
diff --git a/src/services/random_seed/random_seed.service.js b/src/services/random_seed/random_seed.service.js
@@ -0,0 +1,3 @@
+const genRandomSeed = () => `${Math.random()}`.replace('.', '-')
+
+export default genRandomSeed
diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js
@@ -10,6 +10,7 @@ const postStatus = ({
poll,
media = [],
inReplyToStatusId = undefined,
+ quoteId = undefined,
contentType = 'text/plain',
preview = false,
idempotencyKey = ''
@@ -24,6 +25,7 @@ const postStatus = ({
sensitive,
mediaIds,
inReplyToStatusId,
+ quoteId,
contentType,
poll,
preview,
@@ -47,6 +49,47 @@ const postStatus = ({
})
}
+const editStatus = ({
+ store,
+ statusId,
+ status,
+ spoilerText,
+ sensitive,
+ poll,
+ media = [],
+ contentType = 'text/plain'
+}) => {
+ const mediaIds = map(media, 'id')
+
+ return apiService.editStatus({
+ id: statusId,
+ credentials: store.state.users.currentUser.credentials,
+ status,
+ spoilerText,
+ sensitive,
+ poll,
+ mediaIds,
+ contentType
+ })
+ .then((data) => {
+ if (!data.error) {
+ store.dispatch('addNewStatuses', {
+ statuses: [data],
+ timeline: 'friends',
+ showImmediately: true,
+ noIdUpdate: true // To prevent missing notices on next pull.
+ })
+ }
+ return data
+ })
+ .catch((err) => {
+ console.error('Error editing status', err)
+ return {
+ error: err.message
+ }
+ })
+}
+
const uploadMedia = ({ store, formData }) => {
const credentials = store.state.users.currentUser.credentials
return apiService.uploadMedia({ credentials, formData })
@@ -59,6 +102,7 @@ const setMediaDescription = ({ store, id, description }) => {
const statusPosterService = {
postStatus,
+ editStatus,
uploadMedia,
setMediaDescription
}
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
@@ -1,421 +1,301 @@
-import { convert } from 'chromatism'
-import { rgb2hex, hex2rgb, rgba2css, getCssColor, relativeLuminance } from '../color_convert/color_convert.js'
-import { getColors, computeDynamicColor, getOpacitySlot } from '../theme_data/theme_data.service.js'
+import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js'
+import { getCssRules } from '../theme_data/css_utils.js'
+import { defaultState } from '../../modules/config.js'
+import { chunk } from 'lodash'
+import pako from 'pako'
+import localforage from 'localforage'
-export const applyTheme = (input) => {
- const { rules } = generatePreset(input)
- const head = document.head
- const body = document.body
- body.classList.add('hidden')
-
- const styleEl = document.createElement('style')
- head.appendChild(styleEl)
- const styleSheet = styleEl.sheet
+// On platforms where this is not supported, it will return undefined
+// Otherwise it will return an array
+const supportsAdoptedStyleSheets = !!document.adoptedStyleSheets
- styleSheet.toString()
- styleSheet.insertRule(`:root { ${rules.radii} }`, 'index-max')
- styleSheet.insertRule(`:root { ${rules.colors} }`, 'index-max')
- styleSheet.insertRule(`:root { ${rules.shadows} }`, 'index-max')
- styleSheet.insertRule(`:root { ${rules.fonts} }`, 'index-max')
- body.classList.remove('hidden')
-}
+const createStyleSheet = (id) => {
+ if (supportsAdoptedStyleSheets) {
+ return {
+ el: null,
+ sheet: new CSSStyleSheet(),
+ rules: []
+ }
+ }
-export const getCssShadow = (input, usesDropShadow) => {
- if (input.length === 0) {
- return 'none'
+ const el = document.getElementById(id)
+ // Clear all rules in it
+ for (let i = el.sheet.cssRules.length - 1; i >= 0; --i) {
+ el.sheet.deleteRule(i)
}
- return input
- .filter(_ => usesDropShadow ? _.inset : _)
- .map((shad) => [
- shad.x,
- shad.y,
- shad.blur,
- shad.spread
- ].map(_ => _ + 'px').concat([
- getCssColor(shad.color, shad.alpha),
- shad.inset ? 'inset' : ''
- ]).join(' ')).join(', ')
+ return {
+ el,
+ sheet: el.sheet,
+ rules: []
+ }
}
-const getCssShadowFilter = (input) => {
- if (input.length === 0) {
- return 'none'
- }
+const EAGER_STYLE_ID = 'pleroma-eager-styles'
+const LAZY_STYLE_ID = 'pleroma-lazy-styles'
- return input
- // drop-shadow doesn't support inset or spread
- .filter((shad) => !shad.inset && Number(shad.spread) === 0)
- .map((shad) => [
- shad.x,
- shad.y,
- // drop-shadow's blur is twice as strong compared to box-shadow
- shad.blur / 2
- ].map(_ => _ + 'px').concat([
- getCssColor(shad.color, shad.alpha)
- ]).join(' '))
- .map(_ => `drop-shadow(${_})`)
- .join(' ')
+const adoptStyleSheets = (styles) => {
+ if (supportsAdoptedStyleSheets) {
+ document.adoptedStyleSheets = styles.map(s => s.sheet)
+ }
+ // Some older browsers do not support document.adoptedStyleSheets.
+ // In this case, we use the <style> elements.
+ // Since the <style> elements we need are already in the DOM, there
+ // is nothing to do here.
}
-export const generateColors = (themeData) => {
- const sourceColors = !themeData.themeEngineVersion
- ? colors2to3(themeData.colors || themeData)
- : themeData.colors || themeData
+export const generateTheme = (inputRuleset, callbacks, debug) => {
+ const {
+ onNewRule = (rule, isLazy) => {},
+ onLazyFinished = () => {},
+ onEagerFinished = () => {}
+ } = callbacks
- const { colors, opacity } = getColors(sourceColors, themeData.opacity || {})
+ const themes3 = init({
+ inputRuleset,
+ debug
+ })
- const htmlColors = Object.entries(colors)
- .reduce((acc, [k, v]) => {
- if (!v) return acc
- acc.solid[k] = rgb2hex(v)
- acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgba2css(v)
- return acc
- }, { complete: {}, solid: {} })
- return {
- rules: {
- colors: Object.entries(htmlColors.complete)
- .filter(([k, v]) => v)
- .map(([k, v]) => `--${k}: ${v}`)
- .join(';')
- },
- theme: {
- colors: htmlColors.solid,
- opacity
- }
+ getCssRules(themes3.eager, debug).forEach(rule => {
+ // Hacks to support multiple selectors on same component
+ onNewRule(rule, false)
+ })
+ onEagerFinished()
+
+ // Optimization - instead of processing all lazy rules in one go, process them in small chunks
+ // so that UI can do other things and be somewhat responsive while less important rules are being
+ // processed
+ let counter = 0
+ const chunks = chunk(themes3.lazy, 200)
+ // let t0 = performance.now()
+ const processChunk = () => {
+ const chunk = chunks[counter]
+ Promise.all(chunk.map(x => x())).then(result => {
+ getCssRules(result.filter(x => x), debug).forEach(rule => {
+ onNewRule(rule, true)
+ })
+ // const t1 = performance.now()
+ // console.debug('Chunk ' + counter + ' took ' + (t1 - t0) + 'ms')
+ // t0 = t1
+ counter += 1
+ if (counter < chunks.length) {
+ setTimeout(processChunk, 0)
+ } else {
+ onLazyFinished()
+ }
+ })
}
+
+ return { lazyProcessFunc: processChunk }
}
-export const generateRadii = (input) => {
- let inputRadii = input.radii || {}
- // v1 -> v2
- if (typeof input.btnRadius !== 'undefined') {
- inputRadii = Object
- .entries(input)
- .filter(([k, v]) => k.endsWith('Radius'))
- .reduce((acc, e) => { acc[e[0].split('Radius')[0]] = e[1]; return acc }, {})
+export const tryLoadCache = async () => {
+ console.info('Trying to load compiled theme data from cache')
+ const data = await localforage.getItem('pleromafe-theme-cache')
+ if (!data) return null
+ let cache
+ try {
+ const decoded = new TextDecoder().decode(pako.inflate(data))
+ cache = JSON.parse(decoded)
+ console.info(`Loaded theme from cache, size=${cache}`)
+ } catch (e) {
+ console.error('Failed to decode theme cache:', e)
+ return false
}
- const radii = Object.entries(inputRadii).filter(([k, v]) => v).reduce((acc, [k, v]) => {
- acc[k] = v
- return acc
- }, {
- btn: 4,
- input: 4,
- checkbox: 2,
- panel: 10,
- avatar: 5,
- avatarAlt: 50,
- tooltip: 2,
- attachment: 5,
- chatMessage: inputRadii.panel
- })
+ if (cache.engineChecksum === getEngineChecksum()) {
+ const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
+ const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
- return {
- rules: {
- radii: Object.entries(radii).filter(([k, v]) => v).map(([k, v]) => `--${k}Radius: ${v}px`).join(';')
- },
- theme: {
- radii
- }
+ cache.data[0].forEach(rule => eagerStyles.sheet.insertRule(rule, 'index-max'))
+ cache.data[1].forEach(rule => lazyStyles.sheet.insertRule(rule, 'index-max'))
+
+ adoptStyleSheets([eagerStyles, lazyStyles])
+
+ return true
+ } else {
+ console.warn('Engine checksum doesn\'t match, cache not usable, clearing')
+ localStorage.removeItem('pleroma-fe-theme-cache')
}
}
-export const generateFonts = (input) => {
- const fonts = Object.entries(input.fonts || {}).filter(([k, v]) => v).reduce((acc, [k, v]) => {
- acc[k] = Object.entries(v).filter(([k, v]) => v).reduce((acc, [k, v]) => {
- acc[k] = v
- return acc
- }, acc[k])
- return acc
- }, {
- interface: {
- family: 'sans-serif'
- },
- input: {
- family: 'inherit'
- },
- post: {
- family: 'inherit'
- },
- postCode: {
- family: 'monospace'
- }
- })
+export const applyTheme = (
+ input,
+ onEagerFinish = data => {},
+ onFinish = data => {},
+ debug
+) => {
+ const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
+ const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
- return {
- rules: {
- fonts: Object
- .entries(fonts)
- .filter(([k, v]) => v)
- .map(([k, v]) => `--${k}Font: ${v.family}`).join(';')
- },
- theme: {
- fonts
+ const insertRule = (styles, rule) => {
+ if (rule.indexOf('webkit') >= 0) {
+ try {
+ styles.sheet.insertRule(rule, 'index-max')
+ styles.rules.push(rule)
+ } catch (e) {
+ console.warn('Can\'t insert rule due to lack of support', e)
+ }
+ } else {
+ styles.sheet.insertRule(rule, 'index-max')
+ styles.rules.push(rule)
}
}
-}
-const border = (top, shadow) => ({
- x: 0,
- y: top ? 1 : -1,
- blur: 0,
- spread: 0,
- color: shadow ? '#000000' : '#FFFFFF',
- alpha: 0.2,
- inset: true
-})
-const buttonInsetFakeBorders = [border(true, false), border(false, true)]
-const inputInsetFakeBorders = [border(true, true), border(false, false)]
-const hoverGlow = {
- x: 0,
- y: 0,
- blur: 4,
- spread: 0,
- color: '--faint',
- alpha: 1
-}
+ const { lazyProcessFunc } = generateTheme(
+ input,
+ {
+ onNewRule (rule, isLazy) {
+ if (isLazy) {
+ insertRule(lazyStyles, rule)
+ } else {
+ insertRule(eagerStyles, rule)
+ }
+ },
+ onEagerFinished () {
+ adoptStyleSheets([eagerStyles])
+ onEagerFinish()
+ },
+ onLazyFinished () {
+ adoptStyleSheets([eagerStyles, lazyStyles])
+ const cache = { engineChecksum: getEngineChecksum(), data: [eagerStyles.rules, lazyStyles.rules] }
+ onFinish(cache)
+ const compress = (js) => {
+ return pako.deflate(JSON.stringify(js))
+ }
+ localforage.setItem('pleromafe-theme-cache', compress(cache))
+ }
+ },
+ debug
+ )
-export const DEFAULT_SHADOWS = {
- panel: [{
- x: 1,
- y: 1,
- blur: 4,
- spread: 0,
- color: '#000000',
- alpha: 0.6
- }],
- topBar: [{
- x: 0,
- y: 0,
- blur: 4,
- spread: 0,
- color: '#000000',
- alpha: 0.6
- }],
- popup: [{
- x: 2,
- y: 2,
- blur: 3,
- spread: 0,
- color: '#000000',
- alpha: 0.5
- }],
- avatar: [{
- x: 0,
- y: 1,
- blur: 8,
- spread: 0,
- color: '#000000',
- alpha: 0.7
- }],
- avatarStatus: [],
- panelHeader: [],
- button: [{
- x: 0,
- y: 0,
- blur: 2,
- spread: 0,
- color: '#000000',
- alpha: 1
- }, ...buttonInsetFakeBorders],
- buttonHover: [hoverGlow, ...buttonInsetFakeBorders],
- buttonPressed: [hoverGlow, ...inputInsetFakeBorders],
- input: [...inputInsetFakeBorders, {
- x: 0,
- y: 0,
- blur: 2,
- inset: true,
- spread: 0,
- color: '#000000',
- alpha: 1
- }]
+ setTimeout(lazyProcessFunc, 0)
}
-export const generateShadows = (input, colors) => {
- // TODO this is a small hack for `mod` to work with shadows
- // this is used to get the "context" of shadow, i.e. for `mod` properly depend on background color of element
- const hackContextDict = {
- button: 'btn',
- panel: 'bg',
- top: 'topBar',
- popup: 'popover',
- avatar: 'bg',
- panelHeader: 'panel',
- input: 'input'
- }
- const cleanInputShadows = Object.fromEntries(
- Object.entries(input.shadows || {})
- .map(([name, shadowSlot]) => [
- name,
- // defaulting color to black to avoid potential problems
- shadowSlot.map(shadowDef => ({ color: '#000000', ...shadowDef }))
- ])
- )
- const inputShadows = cleanInputShadows && !input.themeEngineVersion
- ? shadows2to3(cleanInputShadows, input.opacity)
- : cleanInputShadows || {}
- const shadows = Object.entries({
- ...DEFAULT_SHADOWS,
- ...inputShadows
- }).reduce((shadowsAcc, [slotName, shadowDefs]) => {
- const slotFirstWord = slotName.replace(/[A-Z].*$/, '')
- const colorSlotName = hackContextDict[slotFirstWord]
- const isLightOnDark = relativeLuminance(convert(colors[colorSlotName]).rgb) < 0.5
- const mod = isLightOnDark ? 1 : -1
- const newShadow = shadowDefs.reduce((shadowAcc, def) => [
- ...shadowAcc,
- {
- ...def,
- color: rgb2hex(computeDynamicColor(
- def.color,
- (variableSlot) => convert(colors[variableSlot]).rgb,
- mod
- ))
- }
- ], [])
- return { ...shadowsAcc, [slotName]: newShadow }
- }, {})
+const extractStyleConfig = ({
+ sidebarColumnWidth,
+ contentColumnWidth,
+ notifsColumnWidth,
+ emojiReactionsScale,
+ emojiSize,
+ navbarSize,
+ panelHeaderSize,
+ textSize,
+ forcedRoundness
+}) => {
+ const result = {
+ sidebarColumnWidth,
+ contentColumnWidth,
+ notifsColumnWidth,
+ emojiReactionsScale,
+ emojiSize,
+ navbarSize,
+ panelHeaderSize,
+ textSize
+ }
- return {
- rules: {
- shadows: Object
- .entries(shadows)
- // TODO for v2.2: if shadow doesn't have non-inset shadows with spread > 0 - optionally
- // convert all non-inset shadows into filter: drop-shadow() to boost performance
- .map(([k, v]) => [
- `--${k}Shadow: ${getCssShadow(v)}`,
- `--${k}ShadowFilter: ${getCssShadowFilter(v)}`,
- `--${k}ShadowInset: ${getCssShadow(v, true)}`
- ].join(';'))
- .join(';')
- },
- theme: {
- shadows
- }
+ switch (forcedRoundness) {
+ case 'disable':
+ break
+ case '0':
+ result.forcedRoundness = '0'
+ break
+ case '1':
+ result.forcedRoundness = '1px'
+ break
+ case '2':
+ result.forcedRoundness = '0.4rem'
+ break
+ default:
}
+
+ return result
}
-export const composePreset = (colors, radii, shadows, fonts) => {
- return {
- rules: {
- ...shadows.rules,
- ...colors.rules,
- ...radii.rules,
- ...fonts.rules
- },
- theme: {
- ...shadows.theme,
- ...colors.theme,
- ...radii.theme,
- ...fonts.theme
- }
+const defaultStyleConfig = extractStyleConfig(defaultState)
+
+export const applyConfig = (input, i18n) => {
+ const config = extractStyleConfig(input)
+
+ if (config === defaultStyleConfig) {
+ return
}
-}
-export const generatePreset = (input) => {
- const colors = generateColors(input)
- return composePreset(
- colors,
- generateRadii(input),
- generateShadows(input, colors.theme.colors, colors.mod),
- generateFonts(input)
- )
+ const head = document.head
+
+ const rules = Object
+ .entries(config)
+ .filter(([k, v]) => v)
+ .map(([k, v]) => `--${k}: ${v}`).join(';')
+
+ document.getElementById('style-config')?.remove()
+ const styleEl = document.createElement('style')
+ styleEl.id = 'style-config'
+ head.appendChild(styleEl)
+ const styleSheet = styleEl.sheet
+
+ styleSheet.toString()
+ styleSheet.insertRule(`:root { ${rules} }`, 'index-max')
+
+ // TODO find a way to make this not apply to theme previews
+ if (Object.prototype.hasOwnProperty.call(config, 'forcedRoundness')) {
+ styleSheet.insertRule(` *:not(.preview-block) {
+ --roundness: var(--forcedRoundness) !important;
+ }`, 'index-max')
+ }
}
-export const getThemes = () => {
+export const getResourcesIndex = async (url, parser = JSON.parse) => {
const cache = 'no-store'
+ const customUrl = url.replace(/\.(\w+)$/, '.custom.$1')
+ let builtin
+ let custom
- return window.fetch('/static/styles.json', { cache })
- .then((data) => data.json())
- .then((themes) => {
- return Object.entries(themes).map(([k, v]) => {
- let promise = null
+ const resourceTransform = (resources) => {
+ return Object
+ .entries(resources)
+ .map(([k, v]) => {
if (typeof v === 'object') {
- promise = Promise.resolve(v)
+ return [k, () => Promise.resolve(v)]
} else if (typeof v === 'string') {
- promise = window.fetch(v, { cache })
- .then((data) => data.json())
- .catch((e) => {
- console.error(e)
- return null
- })
+ return [
+ k,
+ () => window
+ .fetch(v, { cache })
+ .then(data => data.text())
+ .then(text => parser(text))
+ .catch(e => {
+ console.error(e)
+ return null
+ })
+ ]
+ } else {
+ console.error(`Unknown resource format - ${k} is a ${typeof v}`)
+ return [k, null]
}
- return [k, promise]
})
- })
- .then((promises) => {
- return promises
- .reduce((acc, [k, v]) => {
- acc[k] = v
- return acc
- }, {})
- })
-}
-export const colors2to3 = (colors) => {
- return Object.entries(colors).reduce((acc, [slotName, color]) => {
- const btnPositions = ['', 'Panel', 'TopBar']
- switch (slotName) {
- case 'lightBg':
- return { ...acc, highlight: color }
- case 'btnText':
- return {
- ...acc,
- ...btnPositions
- .reduce(
- (statePositionAcc, position) =>
- ({ ...statePositionAcc, ['btn' + position + 'Text']: color })
- , {}
- )
- }
- default:
- return { ...acc, [slotName]: color }
- }
- }, {})
-}
+ }
-/**
- * This handles compatibility issues when importing v2 theme's shadows to current format
- *
- * Back in v2 shadows allowed you to use dynamic colors however those used pure CSS3 variables
- */
-export const shadows2to3 = (shadows, opacity) => {
- return Object.entries(shadows).reduce((shadowsAcc, [slotName, shadowDefs]) => {
- const isDynamic = ({ color = '#000000' }) => color.startsWith('--')
- const getOpacity = ({ color }) => opacity[getOpacitySlot(color.substring(2).split(',')[0])]
- const newShadow = shadowDefs.reduce((shadowAcc, def) => [
- ...shadowAcc,
- {
- ...def,
- alpha: isDynamic(def) ? getOpacity(def) || 1 : def.alpha
- }
- ], [])
- return { ...shadowsAcc, [slotName]: newShadow }
- }, {})
-}
+ try {
+ const builtinData = await window.fetch(url, { cache })
+ const builtinResources = await builtinData.json()
+ builtin = resourceTransform(builtinResources)
+ } catch (e) {
+ builtin = []
+ console.warn(`Builtin resources at ${url} unavailable`)
+ }
-export const getPreset = (val) => {
- return getThemes()
- .then((themes) => themes[val] ? themes[val] : themes['pleroma-dark'])
- .then((theme) => {
- const isV1 = Array.isArray(theme)
- const data = isV1 ? {} : theme.theme
-
- if (isV1) {
- const bg = hex2rgb(theme[1])
- const fg = hex2rgb(theme[2])
- const text = hex2rgb(theme[3])
- const link = hex2rgb(theme[4])
-
- const cRed = hex2rgb(theme[5] || '#FF0000')
- const cGreen = hex2rgb(theme[6] || '#00FF00')
- const cBlue = hex2rgb(theme[7] || '#0000FF')
- const cOrange = hex2rgb(theme[8] || '#E3FF00')
-
- data.colors = { bg, fg, text, link, cRed, cBlue, cGreen, cOrange }
- }
+ try {
+ const customData = await window.fetch(customUrl, { cache })
+ const customResources = await customData.json()
+ custom = resourceTransform(customResources)
+ } catch (e) {
+ custom = []
+ console.warn(`Custom resources at ${customUrl} unavailable`)
+ }
- return { theme: data, source: theme.source }
- })
+ const total = [...custom, ...builtin]
+ if (total.length === 0) {
+ return Promise.reject(new Error(`Resource at ${url} and ${customUrl} completely unavailable. Panicking`))
+ }
+ return Promise.resolve(Object.fromEntries(total))
}
-
-export const setPreset = (val) => getPreset(val).then(data => applyTheme(data.theme))
diff --git a/src/services/sw/sw.js b/src/services/sw/sw.js
@@ -0,0 +1,148 @@
+import runtime from 'serviceworker-webpack5-plugin/lib/runtime'
+
+function urlBase64ToUint8Array (base64String) {
+ const padding = '='.repeat((4 - base64String.length % 4) % 4)
+ const base64 = (base64String + padding)
+ .replace(/-/g, '+')
+ .replace(/_/g, '/')
+
+ const rawData = window.atob(base64)
+ return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)))
+}
+
+export function isSWSupported () {
+ return 'serviceWorker' in navigator
+}
+
+function isPushSupported () {
+ return 'PushManager' in window
+}
+
+function getOrCreateServiceWorker () {
+ return runtime.register()
+ .catch((err) => console.error('Unable to get or create a service worker.', err))
+}
+
+function subscribePush (registration, isEnabled, vapidPublicKey) {
+ if (!isEnabled) return Promise.reject(new Error('Web Push is disabled in config'))
+ if (!vapidPublicKey) return Promise.reject(new Error('VAPID public key is not found'))
+
+ const subscribeOptions = {
+ userVisibleOnly: false,
+ applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
+ }
+ return registration.pushManager.subscribe(subscribeOptions)
+}
+
+function unsubscribePush (registration) {
+ return registration.pushManager.getSubscription()
+ .then((subscription) => {
+ if (subscription === null) { return }
+ return subscription.unsubscribe()
+ })
+}
+
+function deleteSubscriptionFromBackEnd (token) {
+ return fetch('/api/v1/push/subscription/', {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`
+ }
+ }).then((response) => {
+ if (!response.ok) throw new Error('Bad status code from server.')
+ return response
+ })
+}
+
+function sendSubscriptionToBackEnd (subscription, token, notificationVisibility) {
+ return window.fetch('/api/v1/push/subscription/', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`
+ },
+ body: JSON.stringify({
+ subscription,
+ data: {
+ alerts: {
+ follow: notificationVisibility.follows,
+ favourite: notificationVisibility.likes,
+ mention: notificationVisibility.mentions,
+ reblog: notificationVisibility.repeats,
+ move: notificationVisibility.moves
+ }
+ }
+ })
+ }).then((response) => {
+ if (!response.ok) throw new Error('Bad status code from server.')
+ return response.json()
+ }).then((responseData) => {
+ if (!responseData.id) throw new Error('Bad response from server.')
+ return responseData
+ })
+}
+export async function initServiceWorker (store) {
+ if (!isSWSupported()) return
+ await getOrCreateServiceWorker()
+ navigator.serviceWorker.addEventListener('message', (event) => {
+ const { dispatch } = store
+ const { type, ...rest } = event.data
+
+ switch (type) {
+ case 'notificationClicked':
+ dispatch('notificationClicked', { id: rest.id })
+ }
+ })
+}
+
+export async function showDesktopNotification (content) {
+ if (!isSWSupported) return
+ const { active: sw } = await window.navigator.serviceWorker.getRegistration()
+ if (!sw) return console.error('No serviceworker found!')
+ sw.postMessage({ type: 'desktopNotification', content })
+}
+
+export async function closeDesktopNotification ({ id }) {
+ if (!isSWSupported) return
+ const { active: sw } = await window.navigator.serviceWorker.getRegistration()
+ if (!sw) return console.error('No serviceworker found!')
+ if (id >= 0) {
+ sw.postMessage({ type: 'desktopNotificationClose', content: { id } })
+ } else {
+ sw.postMessage({ type: 'desktopNotificationClose', content: { all: true } })
+ }
+}
+
+export async function updateFocus () {
+ if (!isSWSupported) return
+ const { active: sw } = await window.navigator.serviceWorker.getRegistration()
+ if (!sw) return console.error('No serviceworker found!')
+ sw.postMessage({ type: 'updateFocus' })
+}
+
+export function registerPushNotifications (isEnabled, vapidPublicKey, token, notificationVisibility) {
+ if (isPushSupported()) {
+ getOrCreateServiceWorker()
+ .then((registration) => subscribePush(registration, isEnabled, vapidPublicKey))
+ .then((subscription) => sendSubscriptionToBackEnd(subscription, token, notificationVisibility))
+ .catch((e) => console.warn(`Failed to setup Web Push Notifications: ${e.message}`))
+ }
+}
+
+export function unregisterPushNotifications (token) {
+ if (isPushSupported()) {
+ Promise.all([
+ deleteSubscriptionFromBackEnd(token),
+ getOrCreateServiceWorker()
+ .then((registration) => {
+ return unsubscribePush(registration).then((result) => [registration, result])
+ })
+ .then(([registration, unsubResult]) => {
+ if (!unsubResult) {
+ console.warn('Push subscription cancellation wasn\'t successful')
+ }
+ })
+ ]).catch((e) => console.warn(`Failed to disable Web Push Notifications: ${e.message}`))
+ }
+}
diff --git a/src/services/theme_data/css_utils.js b/src/services/theme_data/css_utils.js
@@ -0,0 +1,156 @@
+import { convert } from 'chromatism'
+
+import { hex2rgb, rgba2css } from '../color_convert/color_convert.js'
+
+export const getCssColorString = (color, alpha = 1) => rgba2css({ ...convert(color).rgb, a: alpha })
+
+export const getCssShadow = (input, usesDropShadow) => {
+ if (input.length === 0) {
+ return 'none'
+ }
+
+ return input
+ .filter(_ => usesDropShadow ? _.inset : _)
+ .map((shad) => [
+ shad.x,
+ shad.y,
+ shad.blur,
+ shad.spread
+ ].map(_ => _ + 'px ').concat([
+ getCssColorString(shad.color, shad.alpha),
+ shad.inset ? 'inset' : ''
+ ]).join(' ')).join(', ')
+}
+
+export const getCssShadowFilter = (input) => {
+ if (input.length === 0) {
+ return 'none'
+ }
+
+ return input
+ // drop-shadow doesn't support inset or spread
+ .filter((shad) => !shad.inset && Number(shad.spread) === 0)
+ .map((shad) => [
+ shad.x,
+ shad.y,
+ // drop-shadow's blur is twice as strong compared to box-shadow
+ shad.blur / 2
+ ].map(_ => _ + 'px').concat([
+ getCssColorString(shad.color, shad.alpha)
+ ]).join(' '))
+ .map(_ => `drop-shadow(${_})`)
+ .join(' ')
+}
+
+// `debug` changes what backgrounds are used to "stacked" solid colors so you can see
+// what theme engine "thinks" is actual background color is for purposes of text color
+// generation and for when --stacked variable is used
+export const getCssRules = (rules, debug) => rules.map(rule => {
+ let selector = rule.selector
+ if (!selector) {
+ selector = 'html'
+ }
+ const header = selector + ' {'
+ const footer = '}'
+
+ const virtualDirectives = Object.entries(rule.virtualDirectives || {}).map(([k, v]) => {
+ return ' ' + k + ': ' + v
+ }).join(';\n')
+
+ const directives = Object.entries(rule.directives).map(([k, v]) => {
+ switch (k) {
+ case 'roundness': {
+ return ' ' + [
+ '--roundness: ' + v + 'px'
+ ].join(';\n ')
+ }
+ case 'shadow': {
+ if (!rule.dynamicVars.shadow) {
+ return ''
+ }
+ return ' ' + [
+ '--shadow: ' + getCssShadow(rule.dynamicVars.shadow),
+ '--shadowFilter: ' + getCssShadowFilter(rule.dynamicVars.shadow),
+ '--shadowInset: ' + getCssShadow(rule.dynamicVars.shadow, true)
+ ].join(';\n ')
+ }
+ case 'background': {
+ if (debug) {
+ return `
+ --background: ${getCssColorString(rule.dynamicVars.stacked)};
+ background-color: ${getCssColorString(rule.dynamicVars.stacked)};
+ `
+ }
+ if (v === 'transparent') {
+ if (rule.component === 'Root') return null
+ return [
+ rule.directives.backgroundNoCssColor !== 'yes' ? ('background-color: ' + v) : '',
+ ' --background: ' + v
+ ].filter(x => x).join(';\n')
+ }
+ const color = getCssColorString(rule.dynamicVars.background, rule.directives.opacity)
+ const cssDirectives = ['--background: ' + color]
+ if (rule.directives.backgroundNoCssColor !== 'yes') {
+ cssDirectives.push('background-color: ' + color)
+ }
+ return cssDirectives.filter(x => x).join(';\n')
+ }
+ case 'blur': {
+ const cssDirectives = []
+ if (rule.directives.opacity < 1) {
+ cssDirectives.push(`--backdrop-filter: blur(${v}) `)
+ if (rule.directives.backgroundNoCssColor !== 'yes') {
+ cssDirectives.push(`backdrop-filter: blur(${v}) `)
+ }
+ }
+ return cssDirectives.join(';\n')
+ }
+ case 'font': {
+ return 'font-family: ' + v
+ }
+ case 'textColor': {
+ if (rule.directives.textNoCssColor === 'yes') { return '' }
+ return 'color: ' + v
+ }
+ default:
+ if (k.startsWith('--')) {
+ const [type, value] = v.split('|').map(x => x.trim())
+ switch (type) {
+ case 'color': {
+ const color = rule.dynamicVars[k]
+ if (typeof color === 'string') {
+ return k + ': ' + rgba2css(hex2rgb(color))
+ } else {
+ return k + ': ' + rgba2css(color)
+ }
+ }
+ case 'generic':
+ return k + ': ' + value
+ default:
+ return null
+ }
+ }
+ return null
+ }
+ }).filter(x => x).map(x => ' ' + x + ';').join('\n')
+
+ return [
+ header,
+ directives,
+ (rule.component === 'Text' && rule.state.indexOf('faint') < 0 && rule.directives.textNoCssColor !== 'yes') ? ' color: var(--text);' : '',
+ virtualDirectives,
+ footer
+ ].filter(x => x).join('\n')
+}).filter(x => x)
+
+export const getScopedVersion = (rules, newScope) => {
+ return rules.map(x => {
+ if (x.startsWith('html')) {
+ return x.replace('html', newScope)
+ } else if (x.startsWith('#content')) {
+ return x.replace('#content', newScope)
+ } else {
+ return newScope + ' > ' + x
+ }
+ })
+}
diff --git a/src/services/theme_data/iss_deserializer.js b/src/services/theme_data/iss_deserializer.js
@@ -0,0 +1,170 @@
+import { flattenDeep } from 'lodash'
+
+export const deserializeShadow = string => {
+ const modes = ['_full', 'inset', 'x', 'y', 'blur', 'spread', 'color', 'alpha', 'name']
+ const regexPrep = [
+ // inset keyword (optional)
+ '^',
+ '(?:(inset)\\s+)?',
+ // x
+ '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)',
+ // y
+ '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)',
+ // blur (optional)
+ '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)?',
+ // spread (optional)
+ '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)?',
+ // either hex, variable or function
+ '(#[0-9a-f]{6}|--[a-z0-9\\-_]+|\\$[a-z0-9\\-()_ ]+)',
+ // opacity (optional)
+ '(?:\\s+\\/\\s+([0-9]+(?:\\.[0-9]+)?)\\s*)?',
+ // name
+ '(?:\\s+#(\\w+)\\s*)?',
+ '$'
+ ].join('')
+ const regex = new RegExp(regexPrep, 'gis') // global, (stable) indices, single-string
+ const result = regex.exec(string)
+ if (result == null) {
+ if (string.startsWith('$') || string.startsWith('--')) {
+ return string
+ } else {
+ throw new Error(`Invalid shadow definition: '${string}'`)
+ }
+ } else {
+ const numeric = new Set(['x', 'y', 'blur', 'spread', 'alpha'])
+ const { x, y, blur, spread, alpha, inset, color, name } = Object.fromEntries(modes.map((mode, i) => {
+ if (numeric.has(mode)) {
+ const number = Number(result[i])
+ if (Number.isNaN(number)) {
+ if (mode === 'alpha') return [mode, 1]
+ return [mode, 0]
+ }
+ return [mode, number]
+ } else if (mode === 'inset') {
+ return [mode, !!result[i]]
+ } else {
+ return [mode, result[i]]
+ }
+ }).filter(([k, v]) => v !== false).slice(1))
+
+ return { x, y, blur, spread, color, alpha, inset, name }
+ }
+}
+// this works nearly the same as HTML tree converter
+const parseIss = (input) => {
+ const buffer = [{ selector: null, content: [] }]
+ let textBuffer = ''
+
+ const getCurrentBuffer = () => {
+ let current = buffer[buffer.length - 1]
+ if (current == null) {
+ current = { selector: null, content: [] }
+ }
+ return current
+ }
+
+ // Processes current line buffer, adds it to output buffer and clears line buffer
+ const flushText = (kind) => {
+ if (textBuffer === '') return
+ if (kind === 'content') {
+ getCurrentBuffer().content.push(textBuffer.trim())
+ } else {
+ getCurrentBuffer().selector = textBuffer.trim()
+ }
+ textBuffer = ''
+ }
+
+ for (let i = 0; i < input.length; i++) {
+ const char = input[i]
+
+ if (char === ';') {
+ flushText('content')
+ } else if (char === '{') {
+ flushText('header')
+ } else if (char === '}') {
+ flushText('content')
+ buffer.push({ selector: null, content: [] })
+ textBuffer = ''
+ } else {
+ textBuffer += char
+ }
+ }
+
+ return buffer
+}
+export const deserialize = (input) => {
+ const ast = parseIss(input)
+ const finalResult = ast.filter(i => i.selector != null).map(item => {
+ const { selector, content } = item
+ let stateCount = 0
+ const selectors = selector.split(/,/g)
+ const result = selectors.map(selector => {
+ const output = { component: '' }
+ let currentDepth = null
+
+ selector.split(/ /g).reverse().forEach((fragment, index, arr) => {
+ const fragmentObject = { component: '' }
+
+ let mode = 'component'
+ for (let i = 0; i < fragment.length; i++) {
+ const char = fragment[i]
+ switch (char) {
+ case '.': {
+ mode = 'variant'
+ fragmentObject.variant = ''
+ break
+ }
+ case ':': {
+ mode = 'state'
+ fragmentObject.state = fragmentObject.state || []
+ stateCount++
+ break
+ }
+ default: {
+ if (mode === 'state') {
+ const currentState = fragmentObject.state[stateCount - 1]
+ if (currentState == null) {
+ fragmentObject.state.push('')
+ }
+ fragmentObject.state[stateCount - 1] += char
+ } else {
+ fragmentObject[mode] += char
+ }
+ }
+ }
+ }
+ if (currentDepth !== null) {
+ currentDepth.parent = { ...fragmentObject }
+ currentDepth = currentDepth.parent
+ } else {
+ Object.keys(fragmentObject).forEach(key => {
+ output[key] = fragmentObject[key]
+ })
+ if (index !== (arr.length - 1)) {
+ output.parent = { component: '' }
+ }
+ currentDepth = output
+ }
+ })
+
+ output.directives = Object.fromEntries(content.map(d => {
+ const [property, value] = d.split(':')
+ let realValue = (value || '').trim()
+ if (property === 'shadow') {
+ if (realValue === 'none') {
+ realValue = []
+ } else {
+ realValue = value.split(',').map(v => deserializeShadow(v.trim()))
+ }
+ } if (!Number.isNaN(Number(value))) {
+ realValue = Number(value)
+ }
+ return [property, realValue]
+ }))
+
+ return output
+ })
+ return result
+ })
+ return flattenDeep(finalResult)
+}
diff --git a/src/services/theme_data/iss_serializer.js b/src/services/theme_data/iss_serializer.js
@@ -0,0 +1,53 @@
+import { unroll } from './iss_utils.js'
+import { deserializeShadow } from './iss_deserializer.js'
+
+export const serializeShadow = (s, throwOnInvalid) => {
+ if (typeof s === 'object') {
+ const inset = s.inset ? 'inset ' : ''
+ const name = s.name ? ` #${s.name} ` : ''
+ const result = `${inset}${s.x} ${s.y} ${s.blur} ${s.spread} ${s.color} / ${s.alpha}${name}`
+ deserializeShadow(result) // Verify that output is valid and parseable
+ return result
+ } else {
+ return s
+ }
+}
+
+export const serialize = (ruleset) => {
+ return ruleset.map((rule) => {
+ if (Object.keys(rule.directives || {}).length === 0) return false
+
+ const header = unroll(rule).reverse().map(rule => {
+ const { component } = rule
+ const newVariant = (rule.variant == null || rule.variant === 'normal') ? '' : ('.' + rule.variant)
+ const newState = (rule.state || []).filter(st => st !== 'normal')
+
+ return `${component}${newVariant}${newState.map(st => ':' + st).join('')}`
+ }).join(' ')
+
+ const content = Object.entries(rule.directives).map(([directive, value]) => {
+ if (directive.startsWith('--')) {
+ const [valType, newValue] = value.split('|') // only first one! intentional!
+ switch (valType) {
+ case 'shadow':
+ return ` ${directive}: ${valType.trim()} | ${newValue.map(serializeShadow).map(s => s.trim()).join(', ')}`
+ default:
+ return ` ${directive}: ${valType.trim()} | ${newValue.trim()}`
+ }
+ } else {
+ switch (directive) {
+ case 'shadow':
+ if (value.length > 0) {
+ return ` ${directive}: ${value.map(serializeShadow).join(', ')}`
+ } else {
+ return ` ${directive}: none`
+ }
+ default:
+ return ` ${directive}: ${value}`
+ }
+ }
+ })
+
+ return `${header} {\n${content.join(';\n')}\n}`
+ }).filter(x => x).join('\n\n')
+}
diff --git a/src/services/theme_data/iss_utils.js b/src/services/theme_data/iss_utils.js
@@ -0,0 +1,199 @@
+import { sortBy } from 'lodash'
+
+// "Unrolls" a tree structure of item: { parent: { ...item2, parent: { ...item3, parent: {...} } }}
+// into an array [item2, item3] for iterating
+export const unroll = (item) => {
+ const out = []
+ let currentParent = item
+ while (currentParent) {
+ out.push(currentParent)
+ currentParent = currentParent.parent
+ }
+ return out
+}
+
+// This gives you an array of arrays of all possible unique (i.e. order-insensitive) combinations
+// Can only accept primitives. Duplicates are not supported and can cause unexpected behavior
+export const getAllPossibleCombinations = (array) => {
+ const combos = [array.map(x => [x])]
+ for (let comboSize = 2; comboSize <= array.length; comboSize++) {
+ const previous = combos[combos.length - 1]
+ const newCombos = previous.map(self => {
+ const selfSet = new Set()
+ self.forEach(x => selfSet.add(x))
+ const nonSelf = array.filter(x => !selfSet.has(x))
+ return nonSelf.map(x => [...self, x])
+ })
+ const flatCombos = newCombos.reduce((acc, x) => [...acc, ...x], [])
+ const uniqueComboStrings = new Set()
+ const uniqueCombos = flatCombos.map(sortBy).filter(x => {
+ if (uniqueComboStrings.has(x.join())) {
+ return false
+ } else {
+ uniqueComboStrings.add(x.join())
+ return true
+ }
+ })
+ combos.push(uniqueCombos)
+ }
+ return combos.reduce((acc, x) => [...acc, ...x], [])
+}
+
+/**
+ * Converts rule, parents and their criteria into a CSS (or path if ignoreOutOfTreeSelector == true)
+ * selector.
+ *
+ * "path" here refers to "fake" selector that cannot be actually used in UI but is used for internal
+ * purposes
+ *
+ * @param {Object} components - object containing all components definitions
+ *
+ * @returns {Function}
+ * @param {Object} rule - rule in question to convert to CSS selector
+ * @param {boolean} ignoreOutOfTreeSelector - wthether to ignore aformentioned field in
+ * component definition and use selector
+ * @param {boolean} isParent - (mostly) internal argument used when recursing
+ *
+ * @returns {String} CSS selector (or path)
+ */
+export const genericRuleToSelector = components => (rule, ignoreOutOfTreeSelector, liteMode, children) => {
+ const isParent = !!children
+ if (!rule && !isParent) return null
+ const component = components[rule.component]
+ const { states = {}, variants = {}, outOfTreeSelector } = component
+
+ const expand = (array = [], subArray = []) => {
+ if (array.length === 0) return subArray.map(x => [x])
+ if (subArray.length === 0) return array.map(x => [x])
+ return array.map(a => {
+ return subArray.map(b => [a, b])
+ }).flat()
+ }
+
+ let componentSelectors = Array.isArray(component.selector) ? component.selector : [component.selector]
+ if (ignoreOutOfTreeSelector || liteMode) componentSelectors = [componentSelectors[0]]
+ componentSelectors = componentSelectors.map(selector => {
+ if (selector === ':root') {
+ return ''
+ } else if (isParent) {
+ return selector
+ } else {
+ if (outOfTreeSelector && !ignoreOutOfTreeSelector) return outOfTreeSelector
+ return selector
+ }
+ })
+
+ const applicableVariantName = (rule.variant || 'normal')
+ let variantSelectors = null
+ if (applicableVariantName !== 'normal') {
+ variantSelectors = variants[applicableVariantName]
+ } else {
+ variantSelectors = variants?.normal ?? ''
+ }
+ variantSelectors = Array.isArray(variantSelectors) ? variantSelectors : [variantSelectors]
+ if (ignoreOutOfTreeSelector || liteMode) variantSelectors = [variantSelectors[0]]
+
+ const applicableStates = (rule.state || []).filter(x => x !== 'normal')
+ // const applicableStates = (rule.state || [])
+ const statesSelectors = applicableStates.map(state => {
+ const selector = states[state] || ''
+ let arraySelector = Array.isArray(selector) ? selector : [selector]
+ if (ignoreOutOfTreeSelector || liteMode) arraySelector = [arraySelector[0]]
+ arraySelector
+ .sort((a, b) => {
+ if (a.startsWith(':')) return 1
+ if (/^[a-z]/.exec(a)) return -1
+ else return 0
+ })
+ .join('')
+ return arraySelector
+ })
+
+ const statesSelectorsFlat = statesSelectors.reduce((acc, s) => {
+ return expand(acc, s).map(st => st.join(''))
+ }, [])
+
+ const componentVariant = expand(componentSelectors, variantSelectors).map(cv => cv.join(''))
+ const componentVariantStates = expand(componentVariant, statesSelectorsFlat).map(cvs => cvs.join(''))
+ const selectors = expand(componentVariantStates, children).map(cvsc => cvsc.join(' '))
+ /*
+ */
+
+ if (rule.parent) {
+ return genericRuleToSelector(components)(rule.parent, ignoreOutOfTreeSelector, liteMode, selectors)
+ }
+
+ return selectors.join(', ').trim()
+}
+
+/**
+ * Check if combination matches
+ *
+ * @param {Object} criteria - criteria to match against
+ * @param {Object} subject - rule/combination to check match
+ * @param {boolean} strict - strict checking:
+ * By default every variant and state inherits from "normal" state/variant
+ * so when checking if combination matches, it WILL match against "normal"
+ * state/variant. In strict mode inheritance is ignored an "normal" does
+ * not match
+ */
+export const combinationsMatch = (criteria, subject, strict) => {
+ if (criteria.component !== subject.component) return false
+
+ // All variants inherit from normal
+ if (subject.variant !== 'normal' || strict) {
+ if (criteria.variant !== subject.variant) return false
+ }
+
+ // Subject states > 1 essentially means state is "normal" and therefore matches
+ if (subject.state.length > 1 || strict) {
+ const subjectStatesSet = new Set(subject.state)
+ const criteriaStatesSet = new Set(criteria.state)
+
+ const setsAreEqual =
+ [...criteriaStatesSet].every(state => subjectStatesSet.has(state)) &&
+ [...subjectStatesSet].every(state => criteriaStatesSet.has(state))
+
+ if (!setsAreEqual) return false
+ }
+ return true
+}
+
+/**
+ * Search for rule that matches `criteria` in set of rules
+ * meant to be used in a ruleset.filter() function
+ *
+ * @param {Object} criteria - criteria to search for
+ * @param {boolean} strict - whether search strictly or not (see combinationsMatch)
+ *
+ * @return function that returns true/false if subject matches
+ */
+export const findRules = (criteria, strict) => subject => {
+ // If we searching for "general" rules - ignore "specific" ones
+ if (criteria.parent === null && !!subject.parent) return false
+ if (!combinationsMatch(criteria, subject, strict)) return false
+
+ if (criteria.parent !== undefined && criteria.parent !== null) {
+ if (!subject.parent && !strict) return true
+ const pathCriteria = unroll(criteria)
+ const pathSubject = unroll(subject)
+ if (pathCriteria.length < pathSubject.length) return false
+
+ // Search: .a .b .c
+ // Matches: .a .b .c; .b .c; .c; .z .a .b .c
+ // Does not match .a .b .c .d, .a .b .e
+ for (let i = 0; i < pathCriteria.length; i++) {
+ const criteriaParent = pathCriteria[i]
+ const subjectParent = pathSubject[i]
+ if (!subjectParent) return true
+ if (!combinationsMatch(criteriaParent, subjectParent, strict)) return false
+ }
+ }
+ return true
+}
+
+// Pre-fills 'normal' state/variant if missing
+export const normalizeCombination = rule => {
+ rule.variant = rule.variant ?? 'normal'
+ rule.state = [...new Set(['normal', ...(rule.state || [])])]
+}
diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js
@@ -709,6 +709,14 @@ export const SLOT_INHERITANCE = {
textColor: 'bw'
},
+ badgeNeutral: '--cGreen',
+ badgeNeutralText: {
+ depends: ['text', 'badgeNeutral'],
+ layer: 'badge',
+ variant: 'badgeNeutral',
+ textColor: 'bw'
+ },
+
chatBg: {
depends: ['bg']
},
diff --git a/src/services/theme_data/pleromafe.t3.js b/src/services/theme_data/pleromafe.t3.js
@@ -0,0 +1,2 @@
+export const sampleRules = [
+]
diff --git a/src/services/theme_data/theme2_keys.js b/src/services/theme_data/theme2_keys.js
@@ -0,0 +1,177 @@
+export default [
+ 'bg',
+ 'wallpaper',
+ 'fg',
+ 'text',
+ 'underlay',
+ 'link',
+ 'accent',
+ 'faint',
+ 'faintLink',
+ 'postFaintLink',
+
+ 'cBlue',
+ 'cRed',
+ 'cGreen',
+ 'cOrange',
+
+ 'profileBg',
+ 'profileTint',
+
+ 'highlight',
+ 'highlightLightText',
+ 'highlightPostLink',
+ 'highlightFaintText',
+ 'highlightFaintLink',
+ 'highlightPostFaintLink',
+ 'highlightText',
+ 'highlightLink',
+ 'highlightIcon',
+
+ 'popover',
+ 'popoverLightText',
+ 'popoverPostLink',
+ 'popoverFaintText',
+ 'popoverFaintLink',
+ 'popoverPostFaintLink',
+ 'popoverText',
+ 'popoverLink',
+ 'popoverIcon',
+
+ 'selectedPost',
+ 'selectedPostFaintText',
+ 'selectedPostLightText',
+ 'selectedPostPostLink',
+ 'selectedPostFaintLink',
+ 'selectedPostText',
+ 'selectedPostLink',
+ 'selectedPostIcon',
+
+ 'selectedMenu',
+ 'selectedMenuLightText',
+ 'selectedMenuFaintText',
+ 'selectedMenuFaintLink',
+ 'selectedMenuText',
+ 'selectedMenuLink',
+ 'selectedMenuIcon',
+
+ 'selectedMenuPopover',
+ 'selectedMenuPopoverLightText',
+ 'selectedMenuPopoverFaintText',
+ 'selectedMenuPopoverFaintLink',
+ 'selectedMenuPopoverText',
+ 'selectedMenuPopoverLink',
+ 'selectedMenuPopoverIcon',
+
+ 'lightText',
+
+ 'postLink',
+
+ 'postGreentext',
+
+ 'postCyantext',
+
+ 'border',
+
+ 'poll',
+ 'pollText',
+
+ 'icon',
+
+ // Foreground,
+ 'fgText',
+ 'fgLink',
+
+ // Panel header,
+ 'panel',
+ 'panelText',
+ 'panelFaint',
+ 'panelLink',
+
+ // Top bar,
+ 'topBar',
+ 'topBarText',
+ 'topBarLink',
+
+ // Tabs,
+ 'tab',
+ 'tabText',
+ 'tabActiveText',
+
+ // Buttons,
+ 'btn',
+ 'btnText',
+ 'btnPanelText',
+ 'btnTopBarText',
+
+ // Buttons: pressed,
+ 'btnPressed',
+ 'btnPressedText',
+ 'btnPressedPanel',
+ 'btnPressedPanelText',
+ 'btnPressedTopBar',
+ 'btnPressedTopBarText',
+
+ // Buttons: toggled,
+ 'btnToggled',
+ 'btnToggledText',
+ 'btnToggledPanelText',
+ 'btnToggledTopBarText',
+
+ // Buttons: disabled,
+ 'btnDisabled',
+ 'btnDisabledText',
+ 'btnDisabledPanelText',
+ 'btnDisabledTopBarText',
+
+ // Input fields,
+ 'input',
+ 'inputText',
+ 'inputPanelText',
+ 'inputTopbarText',
+
+ 'alertError',
+ 'alertErrorText',
+ 'alertErrorPanelText',
+
+ 'alertWarning',
+ 'alertWarningText',
+ 'alertWarningPanelText',
+
+ 'alertSuccess',
+ 'alertSuccessText',
+ 'alertSuccessPanelText',
+
+ 'alertNeutral',
+ 'alertNeutralText',
+ 'alertNeutralPanelText',
+
+ 'alertPopupError',
+ 'alertPopupErrorText',
+
+ 'alertPopupWarning',
+ 'alertPopupWarningText',
+
+ 'alertPopupSuccess',
+ 'alertPopupSuccessText',
+
+ 'alertPopupNeutral',
+ 'alertPopupNeutralText',
+
+ 'badgeNeutral',
+ 'badgeNeutralText',
+
+ 'badgeNotification',
+ 'badgeNotificationText',
+
+ 'chatBg',
+
+ 'chatMessageIncomingBg',
+ 'chatMessageIncomingText',
+ 'chatMessageIncomingLink',
+ 'chatMessageIncomingBorder',
+ 'chatMessageOutgoingBg',
+ 'chatMessageOutgoingText',
+ 'chatMessageOutgoingLink',
+ 'chatMessageOutgoingBorder'
+]
diff --git a/src/services/theme_data/theme2_to_theme3.js b/src/services/theme_data/theme2_to_theme3.js
@@ -0,0 +1,534 @@
+import { convert } from 'chromatism'
+import allKeys from './theme2_keys'
+
+// keys that are meant to be used globally, i.e. what's the rest of the theme is based upon.
+export const basePaletteKeys = new Set([
+ 'bg',
+ 'fg',
+ 'text',
+ 'link',
+ 'accent',
+
+ 'cBlue',
+ 'cRed',
+ 'cGreen',
+ 'cOrange',
+
+ 'wallpaper'
+])
+
+export const fontsKeys = new Set([
+ 'interface',
+ 'input',
+ 'post',
+ 'postCode'
+])
+
+export const opacityKeys = new Set([
+ 'alert',
+ 'alertPopup',
+ 'bg',
+ 'border',
+ 'btn',
+ 'faint',
+ 'input',
+ 'panel',
+ 'popover',
+ 'profileTint',
+ 'underlay'
+])
+
+export const shadowsKeys = new Set([
+ 'panel',
+ 'topBar',
+ 'popup',
+ 'avatar',
+ 'avatarStatus',
+ 'panelHeader',
+ 'button',
+ 'buttonHover',
+ 'buttonPressed',
+ 'input'
+])
+
+export const radiiKeys = new Set([
+ 'btn',
+ 'input',
+ 'checkbox',
+ 'panel',
+ 'avatar',
+ 'avatarAlt',
+ 'tooltip',
+ 'attachment',
+ 'chatMessage'
+])
+
+// Keys that are not available in editor and never meant to be edited
+export const hiddenKeys = new Set([
+ 'profileBg',
+ 'profileTint'
+])
+
+export const extendedBasePrefixes = [
+ 'border',
+ 'icon',
+ 'highlight',
+ 'lightText',
+
+ 'popover',
+
+ 'panel',
+ 'topBar',
+ 'tab',
+ 'btn',
+ 'input',
+ 'selectedMenu',
+
+ 'alert',
+ 'alertPopup',
+ 'badge',
+
+ 'post',
+ 'selectedPost', // wrong nomenclature
+ 'poll',
+
+ 'chatBg',
+ 'chatMessage'
+]
+export const nonComponentPrefixes = new Set([
+ 'border',
+ 'icon',
+ 'highlight',
+ 'lightText',
+ 'chatBg'
+])
+
+export const extendedBaseKeys = Object.fromEntries(
+ extendedBasePrefixes.map(prefix => [
+ prefix,
+ allKeys.filter(k => {
+ if (prefix === 'alert') {
+ return k.startsWith(prefix) && !k.startsWith('alertPopup')
+ }
+ return k.startsWith(prefix)
+ })
+ ])
+)
+
+// Keysets that are only really used intermideately, i.e. to generate other colors
+export const temporary = new Set([
+ '',
+ 'highlight'
+])
+
+export const temporaryColors = {}
+
+export const convertTheme2To3 = (data) => {
+ data.colors.accent = data.colors.accent || data.colors.link
+ data.colors.link = data.colors.link || data.colors.accent
+ const generateRoot = () => {
+ const directives = {}
+ basePaletteKeys.forEach(key => { directives['--' + key] = 'color | ' + convert(data.colors[key]).hex })
+ return {
+ component: 'Root',
+ directives
+ }
+ }
+
+ const convertOpacity = () => {
+ const newRules = []
+ Object.keys(data.opacity || {}).forEach(key => {
+ if (!opacityKeys.has(key) || data.opacity[key] === undefined) return null
+ const originalOpacity = data.opacity[key]
+ const rule = { source: '2to3' }
+
+ switch (key) {
+ case 'alert':
+ rule.component = 'Alert'
+ break
+ case 'alertPopup':
+ rule.component = 'Alert'
+ rule.parent = { component: 'Popover' }
+ break
+ case 'bg':
+ rule.component = 'Panel'
+ break
+ case 'border':
+ rule.component = 'Border'
+ break
+ case 'btn':
+ rule.component = 'Button'
+ break
+ case 'faint':
+ rule.component = 'Text'
+ rule.state = ['faint']
+ break
+ case 'input':
+ rule.component = 'Input'
+ break
+ case 'panel':
+ rule.component = 'PanelHeader'
+ break
+ case 'popover':
+ rule.component = 'Popover'
+ break
+ case 'profileTint':
+ return null
+ case 'underlay':
+ rule.component = 'Underlay'
+ break
+ }
+
+ switch (key) {
+ case 'alert':
+ case 'alertPopup':
+ case 'bg':
+ case 'btn':
+ case 'input':
+ case 'panel':
+ case 'popover':
+ case 'underlay':
+ rule.directives = { opacity: originalOpacity }
+ break
+ case 'faint':
+ case 'border':
+ rule.directives = { textOpacity: originalOpacity }
+ break
+ }
+
+ newRules.push(rule)
+
+ if (rule.component === 'Button') {
+ newRules.push({ ...rule, component: 'ScrollbarElement' })
+ newRules.push({ ...rule, component: 'Tab' })
+ newRules.push({ ...rule, component: 'Tab', state: ['active'], directives: { opacity: 0 } })
+ }
+ if (rule.component === 'Panel') {
+ newRules.push({ ...rule, component: 'Post' })
+ }
+ })
+ return newRules
+ }
+
+ const convertRadii = () => {
+ const newRules = []
+ Object.keys(data.radii || {}).forEach(key => {
+ if (!radiiKeys.has(key) || data.radii[key] === undefined) return null
+ const originalRadius = data.radii[key]
+ const rule = { source: '2to3' }
+
+ switch (key) {
+ case 'btn':
+ rule.component = 'Button'
+ break
+ case 'tab':
+ rule.component = 'Tab'
+ break
+ case 'input':
+ rule.component = 'Input'
+ break
+ case 'checkbox':
+ rule.component = 'Input'
+ rule.variant = 'checkbox'
+ break
+ case 'panel':
+ rule.component = 'Panel'
+ break
+ case 'avatar':
+ rule.component = 'Avatar'
+ break
+ case 'avatarAlt':
+ rule.component = 'Avatar'
+ rule.variant = 'compact'
+ break
+ case 'tooltip':
+ rule.component = 'Popover'
+ break
+ case 'attachment':
+ rule.component = 'Attachment'
+ break
+ case 'ChatMessage':
+ rule.component = 'Button'
+ break
+ }
+ rule.directives = {
+ roundness: originalRadius
+ }
+ newRules.push(rule)
+ if (rule.component === 'Button') {
+ newRules.push({ ...rule, component: 'ScrollbarElement' })
+ newRules.push({ ...rule, component: 'Tab' })
+ }
+ })
+ return newRules
+ }
+
+ const convertFonts = () => {
+ const newRules = []
+ Object.keys(data.fonts || {}).forEach(key => {
+ if (!fontsKeys.has(key)) return
+ if (!data.fonts[key]) return
+ const originalFont = data.fonts[key].family
+ const rule = { source: '2to3' }
+
+ switch (key) {
+ case 'interface':
+ case 'postCode':
+ rule.component = 'Root'
+ break
+ case 'input':
+ rule.component = 'Input'
+ break
+ case 'post':
+ rule.component = 'RichContent'
+ break
+ }
+ switch (key) {
+ case 'interface':
+ case 'input':
+ case 'post':
+ rule.directives = { '--font': 'generic | ' + originalFont }
+ break
+ case 'postCode':
+ rule.directives = { '--monoFont': 'generic | ' + originalFont }
+ newRules.push({ ...rule, component: 'RichContent' })
+ break
+ }
+ newRules.push(rule)
+ })
+ return newRules
+ }
+ const convertShadows = () => {
+ const newRules = []
+ Object.keys(data.shadows || {}).forEach(key => {
+ if (!shadowsKeys.has(key)) return
+ const originalShadow = data.shadows[key]
+ const rule = { source: '2to3' }
+
+ switch (key) {
+ case 'panel':
+ rule.component = 'Panel'
+ break
+ case 'topBar':
+ rule.component = 'TopBar'
+ break
+ case 'popup':
+ rule.component = 'Popover'
+ break
+ case 'avatar':
+ rule.component = 'Avatar'
+ break
+ case 'avatarStatus':
+ rule.component = 'Avatar'
+ rule.parent = { component: 'Post' }
+ break
+ case 'panelHeader':
+ rule.component = 'PanelHeader'
+ break
+ case 'button':
+ rule.component = 'Button'
+ break
+ case 'buttonHover':
+ rule.component = 'Button'
+ rule.state = ['hover']
+ break
+ case 'buttonPressed':
+ rule.component = 'Button'
+ rule.state = ['pressed']
+ break
+ case 'input':
+ rule.component = 'Input'
+ break
+ }
+ rule.directives = {
+ shadow: originalShadow
+ }
+ newRules.push(rule)
+ if (key === 'topBar') {
+ newRules.push({ ...rule, component: 'PanelHeader', parent: { component: 'MobileDrawer' } })
+ }
+ if (key === 'avatarStatus') {
+ newRules.push({ ...rule, parent: { component: 'Notification' } })
+ }
+ if (key === 'buttonPressed') {
+ newRules.push({ ...rule, state: ['toggled'] })
+ newRules.push({ ...rule, state: ['toggled', 'focus'] })
+ newRules.push({ ...rule, state: ['pressed', 'focus'] })
+ newRules.push({ ...rule, state: ['toggled', 'focus', 'hover'] })
+ newRules.push({ ...rule, state: ['pressed', 'focus', 'hover'] })
+ }
+
+ if (rule.component === 'Button') {
+ newRules.push({ ...rule, component: 'ScrollbarElement' })
+ newRules.push({ ...rule, component: 'Tab' })
+ }
+ })
+ return newRules
+ }
+
+ const extendedRules = Object.entries(extendedBaseKeys).map(([prefix, keys]) => {
+ if (nonComponentPrefixes.has(prefix)) return null
+ const rule = { source: '2to3' }
+ if (prefix === 'alertPopup') {
+ rule.component = 'Alert'
+ rule.parent = { component: 'Popover' }
+ } else if (prefix === 'selectedPost') {
+ rule.component = 'Post'
+ rule.state = ['selected']
+ } else if (prefix === 'selectedMenu') {
+ rule.component = 'MenuItem'
+ rule.state = ['hover']
+ } else if (prefix === 'chatMessageIncoming') {
+ rule.component = 'ChatMessage'
+ } else if (prefix === 'chatMessageOutgoing') {
+ rule.component = 'ChatMessage'
+ rule.variant = 'outgoing'
+ } else if (prefix === 'panel') {
+ rule.component = 'PanelHeader'
+ } else if (prefix === 'topBar') {
+ rule.component = 'TopBar'
+ } else if (prefix === 'chatMessage') {
+ rule.component = 'ChatMessage'
+ } else if (prefix === 'poll') {
+ rule.component = 'PollGraph'
+ } else if (prefix === 'btn') {
+ rule.component = 'Button'
+ } else {
+ rule.component = prefix[0].toUpperCase() + prefix.slice(1).toLowerCase()
+ }
+ return keys.map((key) => {
+ if (!data.colors[key]) return null
+ const leftoverKey = key.replace(prefix, '')
+ const parts = (leftoverKey || 'Bg').match(/[A-Z][a-z]*/g)
+ const last = parts.slice(-1)[0]
+ let newRule = { source: '2to3', directives: {} }
+ let variantArray = []
+
+ switch (last) {
+ case 'Text':
+ case 'Faint': // typo
+ case 'Link':
+ case 'Icon':
+ case 'Greentext':
+ case 'Cyantext':
+ case 'Border':
+ newRule.parent = rule
+ newRule.directives.textColor = data.colors[key]
+ variantArray = parts.slice(0, -1)
+ break
+ default:
+ newRule = { ...rule, directives: {} }
+ newRule.directives.background = data.colors[key]
+ variantArray = parts
+ break
+ }
+
+ if (last === 'Text' || last === 'Link') {
+ const secondLast = parts.slice(-2)[0]
+ if (secondLast === 'Light') {
+ return null // unsupported
+ } else if (secondLast === 'Faint') {
+ newRule.state = ['faint']
+ variantArray = parts.slice(0, -2)
+ }
+ }
+
+ switch (last) {
+ case 'Text':
+ case 'Link':
+ case 'Icon':
+ case 'Border':
+ newRule.component = last
+ break
+ case 'Greentext':
+ case 'Cyantext':
+ newRule.component = 'FunText'
+ newRule.variant = last.toLowerCase()
+ break
+ case 'Faint':
+ newRule.component = 'Text'
+ newRule.state = ['faint']
+ break
+ }
+
+ variantArray = variantArray.filter(x => x !== 'Bg')
+
+ if (last === 'Link' && prefix === 'selectedPost') {
+ // selectedPost has typo - duplicate 'Post'
+ variantArray = variantArray.filter(x => x !== 'Post')
+ }
+
+ if (prefix === 'popover' && variantArray[0] === 'Post') {
+ newRule.component = 'Post'
+ newRule.parent = { source: '2to3hack', component: 'Popover' }
+ variantArray = variantArray.filter(x => x !== 'Post')
+ }
+
+ if (prefix === 'selectedMenu' && variantArray[0] === 'Popover') {
+ newRule.parent = { source: '2to3hack', component: 'Popover' }
+ variantArray = variantArray.filter(x => x !== 'Popover')
+ }
+
+ switch (prefix) {
+ case 'btn':
+ case 'input':
+ case 'alert': {
+ const hasPanel = variantArray.find(x => x === 'Panel')
+ if (hasPanel) {
+ newRule.parent = { source: '2to3hack', component: 'PanelHeader', parent: newRule.parent }
+ variantArray = variantArray.filter(x => x !== 'Panel')
+ }
+ const hasTop = variantArray.find(x => x === 'Top') // TopBar
+ if (hasTop) {
+ newRule.parent = { source: '2to3hack', component: 'TopBar', parent: newRule.parent }
+ variantArray = variantArray.filter(x => x !== 'Top' && x !== 'Bar')
+ }
+ break
+ }
+ }
+
+ if (variantArray.length > 0) {
+ if (prefix === 'btn') {
+ newRule.state = variantArray.map(x => x.toLowerCase())
+ } else {
+ newRule.variant = variantArray[0].toLowerCase()
+ }
+ }
+
+ if (newRule.component === 'Panel') {
+ return [newRule, { ...newRule, component: 'MobileDrawer' }]
+ } else if (newRule.component === 'Button') {
+ const rules = [
+ newRule,
+ { ...newRule, component: 'Tab' },
+ { ...newRule, component: 'ScrollbarElement' }
+ ]
+ if (newRule.state?.indexOf('toggled') >= 0) {
+ rules.push({ ...newRule, state: [...newRule.state, 'focused'] })
+ rules.push({ ...newRule, state: [...newRule.state, 'hover'] })
+ rules.push({ ...newRule, state: [...newRule.state, 'hover', 'focused'] })
+ }
+ if (newRule.state?.indexOf('hover') >= 0) {
+ rules.push({ ...newRule, state: [...newRule.state, 'focused'] })
+ }
+ return rules
+ } else if (newRule.component === 'Badge') {
+ if (newRule.variant === 'notification') {
+ return [newRule, { component: 'Root', directives: { '--badgeNotification': 'color | ' + newRule.directives.background } }]
+ } else if (newRule.variant === 'neutral') {
+ return [{ ...newRule, variant: 'normal' }]
+ } else {
+ return [newRule]
+ }
+ } else if (newRule.component === 'TopBar') {
+ return [newRule, { ...newRule, parent: { component: 'MobileDrawer' }, component: 'PanelHeader' }]
+ } else {
+ return [newRule]
+ }
+ })
+ })
+
+ const flatExtRules = extendedRules.filter(x => x).reduce((acc, x) => [...acc, ...x], []).filter(x => x).reduce((acc, x) => [...acc, ...x], [])
+
+ return [generateRoot(), ...convertShadows(), ...convertRadii(), ...convertOpacity(), ...convertFonts(), ...flatExtRules]
+}
diff --git a/src/services/theme_data/theme3_slot_functions.js b/src/services/theme_data/theme3_slot_functions.js
@@ -0,0 +1,150 @@
+import { convert, brightness } from 'chromatism'
+import { alphaBlend, getTextColor, relativeLuminance } from '../color_convert/color_convert.js'
+
+export const process = (text, functions, { findColor, findShadow }, { dynamicVars, staticVars }) => {
+ const { funcName, argsString } = /\$(?<funcName>\w+)\((?<argsString>[#a-zA-Z0-9-,.'"\s]*)\)/.exec(text).groups
+ const args = argsString.split(/ /g).map(a => a.trim())
+
+ const func = functions[funcName]
+ if (args.length < func.argsNeeded) {
+ throw new Error(`$${funcName} requires at least ${func.argsNeeded} arguments, but ${args.length} were provided`)
+ }
+ return func.exec(args, { findColor, findShadow }, { dynamicVars, staticVars })
+}
+
+export const colorFunctions = {
+ alpha: {
+ argsNeeded: 2,
+ documentation: 'Changes alpha value of the color only to be used for CSS variables',
+ args: [
+ 'color: source color used',
+ 'amount: alpha value'
+ ],
+ exec: (args, { findColor }, { dynamicVars, staticVars }) => {
+ const [color, amountArg] = args
+
+ const colorArg = convert(findColor(color, { dynamicVars, staticVars })).rgb
+ const amount = Number(amountArg)
+ return { ...colorArg, a: amount }
+ }
+ },
+ brightness: {
+ argsNeeded: 2,
+ document: 'Changes brightness/lightness of color in HSL colorspace',
+ args: [
+ 'color: source color used',
+ 'amount: lightness value'
+ ],
+ exec: (args, { findColor }, { dynamicVars, staticVars }) => {
+ const [color, amountArg] = args
+
+ const colorArg = convert(findColor(color, { dynamicVars, staticVars })).hsl
+ colorArg.l += Number(amountArg)
+ return { ...convert(colorArg).rgb }
+ }
+ },
+ textColor: {
+ argsNeeded: 2,
+ documentation: 'Get text color with adequate contrast for given background and intended text color. Same function is used internally',
+ args: [
+ 'background: color of backdrop where text will be shown',
+ 'foreground: intended text color',
+ `[preserve]: (optional) intended color preservation:
+'preserve' - try to preserve the color
+'no-preserve' - if can't get adequate color - fall back to black or white
+'no-auto' - don't do anything (useless as a color function)`
+ ],
+ exec: (args, { findColor }, { dynamicVars, staticVars }) => {
+ const [backgroundArg, foregroundArg, preserve = 'preserve'] = args
+
+ const background = convert(findColor(backgroundArg, { dynamicVars, staticVars })).rgb
+ const foreground = convert(findColor(foregroundArg, { dynamicVars, staticVars })).rgb
+
+ return getTextColor(background, foreground, preserve === 'preserve')
+ }
+ },
+ blend: {
+ argsNeeded: 3,
+ documentation: 'Alpha blending between two colors',
+ args: [
+ 'background: bottom layer color',
+ 'amount: opacity of top layer',
+ 'foreground: upper layer color'
+ ],
+ exec: (args, { findColor }, { dynamicVars, staticVars }) => {
+ const [backgroundArg, amountArg, foregroundArg] = args
+
+ const background = convert(findColor(backgroundArg, { dynamicVars, staticVars })).rgb
+ const foreground = convert(findColor(foregroundArg, { dynamicVars, staticVars })).rgb
+ const amount = Number(amountArg)
+
+ return alphaBlend(background, amount, foreground)
+ }
+ },
+ mod: {
+ argsNeeded: 2,
+ documentation: 'Old function that increases or decreases brightness depending if color is dark or light. Advised against using it as it might give unexpected results.',
+ args: [
+ 'color: source color',
+ 'amount: how much darken/brighten the color'
+ ],
+ exec: (args, { findColor }, { dynamicVars, staticVars }) => {
+ const [colorArg, amountArg] = args
+
+ const color = convert(findColor(colorArg, { dynamicVars, staticVars })).rgb
+ const amount = Number(amountArg)
+
+ const effectiveBackground = dynamicVars.lowerLevelBackground
+ const isLightOnDark = relativeLuminance(convert(effectiveBackground).rgb) < 0.5
+ const mod = isLightOnDark ? 1 : -1
+ return brightness(amount * mod, color).rgb
+ }
+ }
+}
+
+export const shadowFunctions = {
+ borderSide: {
+ argsNeeded: 3,
+ documentation: 'Simulate a border on a side with a shadow, best works on inset border',
+ args: [
+ 'color: border color',
+ 'side: string indicating on which side border should be, takes either one word or two words joined by dash (i.e. "left" or "bottom-right")',
+ '[alpha]: (Optional) border opacity, defaults to 1 (fully opaque)',
+ '[inset]: (Optional) whether border should be on the inside or outside, defaults to inside'
+ ],
+ exec: (args, { findColor }) => {
+ const [color, side, alpha = '1', widthArg = '1', inset = 'inset'] = args
+
+ const width = Number(widthArg)
+ const isInset = inset === 'inset'
+
+ const targetShadow = {
+ x: 0,
+ y: 0,
+ blur: 0,
+ spread: 0,
+ color,
+ alpha: Number(alpha),
+ inset: isInset
+ }
+
+ side.split('-').forEach((position) => {
+ switch (position) {
+ case 'left':
+ targetShadow.x = width * (inset ? 1 : -1)
+ break
+ case 'right':
+ targetShadow.x = -1 * width * (inset ? 1 : -1)
+ break
+ case 'top':
+ targetShadow.y = width * (inset ? 1 : -1)
+ break
+ case 'bottom':
+ targetShadow.y = -1 * width * (inset ? 1 : -1)
+ break
+ }
+ })
+ return [targetShadow]
+ }
+ }
+}
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
@@ -1,5 +1,5 @@
import { convert, brightness, contrastRatio } from 'chromatism'
-import { alphaBlendLayers, getTextColor, relativeLuminance } from '../color_convert/color_convert.js'
+import { rgb2hex, rgba2css, alphaBlendLayers, getTextColor, relativeLuminance, getCssColor } from '../color_convert/color_convert.js'
import { LAYERS, DEFAULT_OPACITY, SLOT_INHERITANCE } from './pleromafe.js'
/*
@@ -39,7 +39,7 @@ import { LAYERS, DEFAULT_OPACITY, SLOT_INHERITANCE } from './pleromafe.js'
export const CURRENT_VERSION = 3
export const getLayersArray = (layer, data = LAYERS) => {
- let array = [layer]
+ const array = [layer]
let parent = data[layer]
while (parent) {
array.unshift(parent)
@@ -117,7 +117,6 @@ export const topoSort = (
// Put it into the output list
output.push(node)
} else if (grays.has(node)) {
- console.debug('Cyclic depenency in topoSort, ignoring')
output.push(node)
} else if (blacks.has(node)) {
// do nothing
@@ -138,6 +137,7 @@ export const topoSort = (
if (depsA === depsB || (depsB !== 0 && depsA !== 0)) return ai - bi
if (depsA === 0 && depsB !== 0) return -1
if (depsB === 0 && depsA !== 0) return 1
+ return 0 // failsafe, shouldn't happen?
}).map(({ data }) => data)
}
@@ -406,3 +406,347 @@ export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({
}
}
}, { colors: {}, opacity: {} })
+
+export const composePreset = (colors, radii, shadows, fonts) => {
+ return {
+ rules: {
+ ...shadows.rules,
+ ...colors.rules,
+ ...radii.rules,
+ ...fonts.rules
+ },
+ theme: {
+ ...shadows.theme,
+ ...colors.theme,
+ ...radii.theme,
+ ...fonts.theme
+ }
+ }
+}
+
+export const generatePreset = (input) => {
+ const colors = generateColors(input)
+ return composePreset(
+ colors,
+ generateRadii(input),
+ generateShadows(input, colors.theme.colors, colors.mod),
+ generateFonts(input)
+ )
+}
+
+export const getCssShadow = (input, usesDropShadow) => {
+ if (input.length === 0) {
+ return 'none'
+ }
+
+ return input
+ .filter(_ => usesDropShadow ? _.inset : _)
+ .map((shad) => [
+ shad.x,
+ shad.y,
+ shad.blur,
+ shad.spread
+ ].map(_ => _ + 'px').concat([
+ getCssColor(shad.color, shad.alpha),
+ shad.inset ? 'inset' : ''
+ ]).join(' ')).join(', ')
+}
+
+export const getCssShadowFilter = (input) => {
+ if (input.length === 0) {
+ return 'none'
+ }
+
+ return input
+ // drop-shadow doesn't support inset or spread
+ .filter((shad) => !shad.inset && Number(shad.spread) === 0)
+ .map((shad) => [
+ shad.x,
+ shad.y,
+ // drop-shadow's blur is twice as strong compared to box-shadow
+ shad.blur / 2
+ ].map(_ => _ + 'px').concat([
+ getCssColor(shad.color, shad.alpha)
+ ]).join(' '))
+ .map(_ => `drop-shadow(${_})`)
+ .join(' ')
+}
+
+export const generateColors = (themeData) => {
+ const sourceColors = !themeData.themeEngineVersion
+ ? colors2to3(themeData.colors || themeData)
+ : themeData.colors || themeData
+
+ const { colors, opacity } = getColors(sourceColors, themeData.opacity || {})
+
+ const htmlColors = Object.entries(colors)
+ .reduce((acc, [k, v]) => {
+ if (!v) return acc
+ acc.solid[k] = rgb2hex(v)
+ acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgba2css(v)
+ return acc
+ }, { complete: {}, solid: {} })
+ return {
+ rules: {
+ colors: Object.entries(htmlColors.complete)
+ .filter(([k, v]) => v)
+ .map(([k, v]) => `--${k}: ${v}`)
+ .join(';')
+ },
+ theme: {
+ colors: htmlColors.solid,
+ opacity
+ }
+ }
+}
+
+export const generateRadii = (input) => {
+ let inputRadii = input.radii || {}
+ // v1 -> v2
+ if (typeof input.btnRadius !== 'undefined') {
+ inputRadii = Object
+ .entries(input)
+ .filter(([k, v]) => k.endsWith('Radius'))
+ .reduce((acc, e) => { acc[e[0].split('Radius')[0]] = e[1]; return acc }, {})
+ }
+ const radii = Object.entries(inputRadii).filter(([k, v]) => v).reduce((acc, [k, v]) => {
+ acc[k] = v
+ return acc
+ }, {
+ btn: 4,
+ input: 4,
+ checkbox: 2,
+ panel: 10,
+ avatar: 5,
+ avatarAlt: 50,
+ tooltip: 2,
+ attachment: 5,
+ chatMessage: inputRadii.panel
+ })
+
+ return {
+ rules: {
+ radii: Object.entries(radii).filter(([k, v]) => v).map(([k, v]) => `--${k}Radius: ${v}px`).join(';')
+ },
+ theme: {
+ radii
+ }
+ }
+}
+
+export const generateFonts = (input) => {
+ const fonts = Object.entries(input.fonts || {}).filter(([k, v]) => v).reduce((acc, [k, v]) => {
+ acc[k] = Object.entries(v).filter(([k, v]) => v).reduce((acc, [k, v]) => {
+ acc[k] = v
+ return acc
+ }, acc[k])
+ return acc
+ }, {
+ interface: {
+ family: 'sans-serif'
+ },
+ input: {
+ family: 'inherit'
+ },
+ post: {
+ family: 'inherit'
+ },
+ postCode: {
+ family: 'monospace'
+ }
+ })
+
+ return {
+ rules: {
+ fonts: Object
+ .entries(fonts)
+ .filter(([k, v]) => v)
+ .map(([k, v]) => `--${k}Font: ${v.family}`).join(';')
+ },
+ theme: {
+ fonts
+ }
+ }
+}
+
+const border = (top, shadow) => ({
+ x: 0,
+ y: top ? 1 : -1,
+ blur: 0,
+ spread: 0,
+ color: shadow ? '#000000' : '#FFFFFF',
+ alpha: 0.2,
+ inset: true
+})
+const buttonInsetFakeBorders = [border(true, false), border(false, true)]
+const inputInsetFakeBorders = [border(true, true), border(false, false)]
+const hoverGlow = {
+ x: 0,
+ y: 0,
+ blur: 4,
+ spread: 0,
+ color: '--faint',
+ alpha: 1
+}
+
+export const DEFAULT_SHADOWS = {
+ panel: [{
+ x: 1,
+ y: 1,
+ blur: 4,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.6
+ }],
+ topBar: [{
+ x: 0,
+ y: 0,
+ blur: 4,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.6
+ }],
+ popup: [{
+ x: 2,
+ y: 2,
+ blur: 3,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.5
+ }],
+ avatar: [{
+ x: 0,
+ y: 1,
+ blur: 8,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.7
+ }],
+ avatarStatus: [],
+ panelHeader: [],
+ button: [{
+ x: 0,
+ y: 0,
+ blur: 2,
+ spread: 0,
+ color: '#000000',
+ alpha: 1
+ }, ...buttonInsetFakeBorders],
+ buttonHover: [hoverGlow, ...buttonInsetFakeBorders],
+ buttonPressed: [hoverGlow, ...inputInsetFakeBorders],
+ input: [...inputInsetFakeBorders, {
+ x: 0,
+ y: 0,
+ blur: 2,
+ inset: true,
+ spread: 0,
+ color: '#000000',
+ alpha: 1
+ }]
+}
+export const generateShadows = (input, colors) => {
+ // TODO this is a small hack for `mod` to work with shadows
+ // this is used to get the "context" of shadow, i.e. for `mod` properly depend on background color of element
+ const hackContextDict = {
+ button: 'btn',
+ panel: 'bg',
+ top: 'topBar',
+ popup: 'popover',
+ avatar: 'bg',
+ panelHeader: 'panel',
+ input: 'input'
+ }
+
+ const cleanInputShadows = Object.fromEntries(
+ Object.entries(input.shadows || {})
+ .map(([name, shadowSlot]) => [
+ name,
+ // defaulting color to black to avoid potential problems
+ shadowSlot.map(shadowDef => ({ color: '#000000', ...shadowDef }))
+ ])
+ )
+ const inputShadows = cleanInputShadows && !input.themeEngineVersion
+ ? shadows2to3(cleanInputShadows, input.opacity)
+ : cleanInputShadows || {}
+ const shadows = Object.entries({
+ ...DEFAULT_SHADOWS,
+ ...inputShadows
+ }).reduce((shadowsAcc, [slotName, shadowDefs]) => {
+ const slotFirstWord = slotName.replace(/[A-Z].*$/, '')
+ const colorSlotName = hackContextDict[slotFirstWord]
+ const isLightOnDark = relativeLuminance(convert(colors[colorSlotName]).rgb) < 0.5
+ const mod = isLightOnDark ? 1 : -1
+ const newShadow = shadowDefs.reduce((shadowAcc, def) => [
+ ...shadowAcc,
+ {
+ ...def,
+ color: rgb2hex(computeDynamicColor(
+ def.color,
+ (variableSlot) => convert(colors[variableSlot]).rgb,
+ mod
+ ))
+ }
+ ], [])
+ return { ...shadowsAcc, [slotName]: newShadow }
+ }, {})
+
+ return {
+ rules: {
+ shadows: Object
+ .entries(shadows)
+ // TODO for v2.2: if shadow doesn't have non-inset shadows with spread > 0 - optionally
+ // convert all non-inset shadows into filter: drop-shadow() to boost performance
+ .map(([k, v]) => [
+ `--${k}Shadow: ${getCssShadow(v)}`,
+ `--${k}ShadowFilter: ${getCssShadowFilter(v)}`,
+ `--${k}ShadowInset: ${getCssShadow(v, true)}`
+ ].join(';'))
+ .join(';')
+ },
+ theme: {
+ shadows
+ }
+ }
+}
+
+/**
+ * This handles compatibility issues when importing v2 theme's shadows to current format
+ *
+ * Back in v2 shadows allowed you to use dynamic colors however those used pure CSS3 variables
+ */
+export const shadows2to3 = (shadows, opacity) => {
+ return Object.entries(shadows).reduce((shadowsAcc, [slotName, shadowDefs]) => {
+ const isDynamic = ({ color = '#000000' }) => color.startsWith('--')
+ const getOpacity = ({ color }) => opacity[getOpacitySlot(color.substring(2).split(',')[0])]
+ const newShadow = shadowDefs.reduce((shadowAcc, def) => [
+ ...shadowAcc,
+ {
+ ...def,
+ alpha: isDynamic(def) ? getOpacity(def) || 1 : def.alpha
+ }
+ ], [])
+ return { ...shadowsAcc, [slotName]: newShadow }
+ }, {})
+}
+
+export const colors2to3 = (colors) => {
+ return Object.entries(colors).reduce((acc, [slotName, color]) => {
+ const btnPositions = ['', 'Panel', 'TopBar']
+ switch (slotName) {
+ case 'lightBg':
+ return { ...acc, highlight: color }
+ case 'btnText':
+ return {
+ ...acc,
+ ...btnPositions
+ .reduce(
+ (statePositionAcc, position) =>
+ ({ ...statePositionAcc, ['btn' + position + 'Text']: color })
+ , {}
+ )
+ }
+ default:
+ return { ...acc, [slotName]: color }
+ }
+ }, {})
+}
diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js
@@ -0,0 +1,573 @@
+import { convert, brightness } from 'chromatism'
+import sum from 'hash-sum'
+import { flattenDeep, sortBy } from 'lodash'
+import {
+ alphaBlend,
+ getTextColor,
+ rgba2css,
+ mixrgb,
+ relativeLuminance
+} from '../color_convert/color_convert.js'
+
+import {
+ colorFunctions,
+ shadowFunctions,
+ process
+} from './theme3_slot_functions.js'
+
+import {
+ unroll,
+ getAllPossibleCombinations,
+ genericRuleToSelector,
+ normalizeCombination,
+ findRules
+} from './iss_utils.js'
+import { deserializeShadow } from './iss_deserializer.js'
+
+// Ensuring the order of components
+const components = {
+ Root: null,
+ Text: null,
+ FunText: null,
+ Link: null,
+ Icon: null,
+ Border: null,
+ Panel: null,
+ Chat: null,
+ ChatMessage: null
+}
+
+export const findShadow = (shadows, { dynamicVars, staticVars }) => {
+ return (shadows || []).map(shadow => {
+ let targetShadow
+ if (typeof shadow === 'string') {
+ if (shadow.startsWith('$')) {
+ targetShadow = process(shadow, shadowFunctions, { findColor, findShadow }, { dynamicVars, staticVars })
+ } else if (shadow.startsWith('--')) {
+ // modifiers are completely unsupported here
+ const variableSlot = shadow.substring(2)
+ return findShadow(staticVars[variableSlot], { dynamicVars, staticVars })
+ } else {
+ targetShadow = deserializeShadow(shadow)
+ }
+ } else {
+ targetShadow = shadow
+ }
+
+ const shadowArray = Array.isArray(targetShadow) ? targetShadow : [targetShadow]
+ return shadowArray.map(s => ({
+ ...s,
+ color: findColor(s.color, { dynamicVars, staticVars })
+ }))
+ })
+}
+
+export const findColor = (color, { dynamicVars, staticVars }) => {
+ try {
+ if (typeof color !== 'string' || (!color.startsWith('--') && !color.startsWith('$'))) return color
+ let targetColor = null
+ if (color.startsWith('--')) {
+ // Modifier support is pretty much for v2 themes only
+ const [variable, modifier] = color.split(/,/g).map(str => str.trim())
+ const variableSlot = variable.substring(2)
+ if (variableSlot === 'stack') {
+ const { r, g, b } = dynamicVars.stacked
+ targetColor = { r, g, b }
+ } else if (variableSlot.startsWith('parent')) {
+ if (variableSlot === 'parent') {
+ const { r, g, b } = dynamicVars.lowerLevelBackground
+ targetColor = { r, g, b }
+ } else {
+ const virtualSlot = variableSlot.replace(/^parent/, '')
+ targetColor = convert(dynamicVars.lowerLevelVirtualDirectivesRaw[virtualSlot]).rgb
+ }
+ } else {
+ switch (variableSlot) {
+ case 'inheritedBackground':
+ targetColor = convert(dynamicVars.inheritedBackground).rgb
+ break
+ case 'background':
+ targetColor = convert(dynamicVars.background).rgb
+ break
+ default:
+ targetColor = convert(staticVars[variableSlot]).rgb
+ }
+ }
+
+ if (modifier) {
+ const effectiveBackground = dynamicVars.lowerLevelBackground ?? targetColor
+ const isLightOnDark = relativeLuminance(convert(effectiveBackground).rgb) < 0.5
+ const mod = isLightOnDark ? 1 : -1
+ targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb
+ }
+ }
+
+ if (color.startsWith('$')) {
+ try {
+ targetColor = process(color, colorFunctions, { findColor }, { dynamicVars, staticVars })
+ } catch (e) {
+ console.error('Failure executing color function', e)
+ targetColor = '#FF00FF'
+ }
+ }
+ // Color references other color
+ return targetColor
+ } catch (e) {
+ throw new Error(`Couldn't find color "${color}", variables are:
+Static:
+${JSON.stringify(staticVars, null, 2)}
+Dynamic:
+${JSON.stringify(dynamicVars, null, 2)}`)
+ }
+}
+
+const getTextColorAlpha = (directives, intendedTextColor, dynamicVars, staticVars) => {
+ const opacity = directives.textOpacity
+ const backgroundColor = convert(dynamicVars.lowerLevelBackground).rgb
+ const textColor = convert(findColor(intendedTextColor, { dynamicVars, staticVars })).rgb
+ if (opacity === null || opacity === undefined || opacity >= 1) {
+ return convert(textColor).hex
+ }
+ if (opacity === 0) {
+ return convert(backgroundColor).hex
+ }
+ const opacityMode = directives.textOpacityMode
+ switch (opacityMode) {
+ case 'fake':
+ return convert(alphaBlend(textColor, opacity, backgroundColor)).hex
+ case 'mixrgb':
+ return convert(mixrgb(backgroundColor, textColor)).hex
+ default:
+ return rgba2css({ a: opacity, ...textColor })
+ }
+}
+
+// Loading all style.js[on] files dynamically
+const componentsContext = require.context('src', true, /\.style.js(on)?$/)
+componentsContext.keys().forEach(key => {
+ const component = componentsContext(key).default
+ if (components[component.name] != null) {
+ console.warn(`Component in file ${key} is trying to override existing component ${component.name}! You have collisions/duplicates!`)
+ }
+ components[component.name] = component
+})
+
+const engineChecksum = sum(components)
+
+const ruleToSelector = genericRuleToSelector(components)
+
+export const getEngineChecksum = () => engineChecksum
+
+/**
+ * Initializes and compiles the theme according to the ruleset
+ *
+ * @param {Object[]} inputRuleset - set of rules to compile theme against. Acts as an override to
+ * component default rulesets
+ * @param {string} ultimateBackgroundColor - Color that will be the "final" background for
+ * calculating contrast ratios and making text automatically accessible. Really used for cases when
+ * stuff is transparent.
+ * @param {boolean} debug - print out debug information in console, mostly just performance stuff
+ * @param {boolean} liteMode - use validInnerComponentsLite instead of validInnerComponents, meant to
+ * generatate theme previews and such that need to be compiled faster and don't require a lot of other
+ * components present in "normal" mode
+ * @param {boolean} onlyNormalState - only use components 'normal' states, meant for generating theme
+ * previews since states are the biggest factor for compilation time and are completely unnecessary
+ * when previewing multiple themes at same time
+ */
+export const init = ({
+ inputRuleset,
+ ultimateBackgroundColor,
+ debug = false,
+ liteMode = false,
+ editMode = false,
+ onlyNormalState = false,
+ initialStaticVars = {}
+}) => {
+ const rootComponentName = 'Root'
+ if (!inputRuleset) throw new Error('Ruleset is null or undefined!')
+ const staticVars = { ...initialStaticVars }
+ const stacked = {}
+ const computed = {}
+
+ const rulesetUnsorted = [
+ ...Object.values(components)
+ .map(c => (c.defaultRules || []).map(r => ({ source: 'Built-in', component: c.name, ...r })))
+ .reduce((acc, arr) => [...acc, ...arr], []),
+ ...inputRuleset
+ ].map(rule => {
+ normalizeCombination(rule)
+ let currentParent = rule.parent
+ while (currentParent) {
+ normalizeCombination(currentParent)
+ currentParent = currentParent.parent
+ }
+
+ return rule
+ })
+
+ const ruleset = rulesetUnsorted
+ .map((data, index) => ({ data, index }))
+ .toSorted(({ data: a, index: ai }, { data: b, index: bi }) => {
+ const parentsA = unroll(a).length
+ const parentsB = unroll(b).length
+
+ let aScore = 0
+ let bScore = 0
+
+ aScore += parentsA * 1000
+ bScore += parentsB * 1000
+
+ aScore += a.variant !== 'normal' ? 100 : 0
+ bScore += b.variant !== 'normal' ? 100 : 0
+
+ aScore += a.state.filter(x => x !== 'normal').length * 1000
+ bScore += b.state.filter(x => x !== 'normal').length * 1000
+
+ aScore += a.component === 'Text' ? 1 : 0
+ bScore += b.component === 'Text' ? 1 : 0
+
+ // Debug
+ a._specificityScore = aScore
+ b._specificityScore = bScore
+
+ if (aScore === bScore) {
+ return ai - bi
+ }
+ return aScore - bScore
+ })
+ .map(({ data }) => data)
+
+ if (!ultimateBackgroundColor) {
+ console.warn('No ultimate background color provided, falling back to panel color')
+ const rootRule = ruleset.findLast((x) => (x.component === 'Root' && x.directives?.['--bg']))
+ ultimateBackgroundColor = rootRule.directives['--bg'].split('|')[1].trim()
+ }
+
+ const virtualComponents = new Set(Object.values(components).filter(c => c.virtual).map(c => c.name))
+ const nonEditableComponents = new Set(Object.values(components).filter(c => c.notEditable).map(c => c.name))
+
+ const processCombination = (combination) => {
+ try {
+ const selector = ruleToSelector(combination, true)
+ const cssSelector = ruleToSelector(combination)
+
+ const parentSelector = selector.split(/ /g).slice(0, -1).join(' ')
+ const soloSelector = selector.split(/ /g).slice(-1)[0]
+
+ const lowerLevelSelector = parentSelector
+ let lowerLevelBackground = computed[lowerLevelSelector]?.background
+ if (editMode && !lowerLevelBackground) {
+ // FIXME hack for editor until it supports handling component backgrounds
+ lowerLevelBackground = '#00FFFF'
+ }
+ const lowerLevelVirtualDirectives = computed[lowerLevelSelector]?.virtualDirectives
+ const lowerLevelVirtualDirectivesRaw = computed[lowerLevelSelector]?.virtualDirectivesRaw
+
+ const dynamicVars = computed[selector] || {
+ lowerLevelBackground,
+ lowerLevelVirtualDirectives,
+ lowerLevelVirtualDirectivesRaw
+ }
+
+ // Inheriting all of the applicable rules
+ const existingRules = ruleset.filter(findRules(combination))
+ const computedDirectives =
+ existingRules
+ .map(r => r.directives)
+ .reduce((acc, directives) => ({ ...acc, ...directives }), {})
+ const computedRule = {
+ ...combination,
+ directives: computedDirectives
+ }
+
+ computed[selector] = computed[selector] || {}
+ computed[selector].computedRule = computedRule
+ computed[selector].dynamicVars = dynamicVars
+
+ if (virtualComponents.has(combination.component)) {
+ const virtualName = [
+ '--',
+ combination.component.toLowerCase(),
+ combination.variant === 'normal'
+ ? ''
+ : combination.variant[0].toUpperCase() + combination.variant.slice(1).toLowerCase(),
+ ...sortBy(combination.state.filter(x => x !== 'normal')).map(state => state[0].toUpperCase() + state.slice(1).toLowerCase())
+ ].join('')
+
+ let inheritedTextColor = computedDirectives.textColor
+ let inheritedTextAuto = computedDirectives.textAuto
+ let inheritedTextOpacity = computedDirectives.textOpacity
+ let inheritedTextOpacityMode = computedDirectives.textOpacityMode
+ const lowerLevelTextSelector = [...selector.split(/ /g).slice(0, -1), soloSelector].join(' ')
+ const lowerLevelTextRule = computed[lowerLevelTextSelector]
+
+ if (inheritedTextColor == null || inheritedTextOpacity == null || inheritedTextOpacityMode == null) {
+ inheritedTextColor = computedDirectives.textColor ?? lowerLevelTextRule.textColor
+ inheritedTextAuto = computedDirectives.textAuto ?? lowerLevelTextRule.textAuto
+ inheritedTextOpacity = computedDirectives.textOpacity ?? lowerLevelTextRule.textOpacity
+ inheritedTextOpacityMode = computedDirectives.textOpacityMode ?? lowerLevelTextRule.textOpacityMode
+ }
+
+ const newTextRule = {
+ ...computedRule,
+ directives: {
+ ...computedRule.directives,
+ textColor: inheritedTextColor,
+ textAuto: inheritedTextAuto ?? 'preserve',
+ textOpacity: inheritedTextOpacity,
+ textOpacityMode: inheritedTextOpacityMode
+ }
+ }
+
+ dynamicVars.inheritedBackground = lowerLevelBackground
+ dynamicVars.stacked = convert(stacked[lowerLevelSelector]).rgb
+
+ const intendedTextColor = convert(findColor(inheritedTextColor, { dynamicVars, staticVars })).rgb
+ const textColor = newTextRule.directives.textAuto === 'no-auto'
+ ? intendedTextColor
+ : getTextColor(
+ convert(stacked[lowerLevelSelector]).rgb,
+ intendedTextColor,
+ newTextRule.directives.textAuto === 'preserve'
+ )
+ const virtualDirectives = computed[lowerLevelSelector].virtualDirectives || {}
+ const virtualDirectivesRaw = computed[lowerLevelSelector].virtualDirectivesRaw || {}
+
+ // Storing color data in lower layer to use as custom css properties
+ virtualDirectives[virtualName] = getTextColorAlpha(newTextRule.directives, textColor, dynamicVars)
+ virtualDirectivesRaw[virtualName] = textColor
+
+ computed[lowerLevelSelector].virtualDirectives = virtualDirectives
+ computed[lowerLevelSelector].virtualDirectivesRaw = virtualDirectivesRaw
+
+ return {
+ dynamicVars,
+ selector: cssSelector.split(/ /g).slice(0, -1).join(' '),
+ ...combination,
+ directives: {},
+ virtualDirectives: {
+ [virtualName]: getTextColorAlpha(newTextRule.directives, textColor, dynamicVars)
+ },
+ virtualDirectivesRaw: {
+ [virtualName]: textColor
+ }
+ }
+ } else {
+ computed[selector] = computed[selector] || {}
+
+ // TODO: DEFAULT TEXT COLOR
+ const lowerLevelStackedBackground = stacked[lowerLevelSelector] || convert(ultimateBackgroundColor).rgb
+
+ if (computedDirectives.background) {
+ let inheritRule = null
+ const variantRules = ruleset.filter(
+ findRules({
+ component: combination.component,
+ variant: combination.variant,
+ parent: combination.parent
+ })
+ )
+ const lastVariantRule = variantRules[variantRules.length - 1]
+ if (lastVariantRule) {
+ inheritRule = lastVariantRule
+ } else {
+ const normalRules = ruleset.filter(findRules({
+ component: combination.component,
+ parent: combination.parent
+ }))
+ const lastNormalRule = normalRules[normalRules.length - 1]
+ inheritRule = lastNormalRule
+ }
+
+ const inheritSelector = ruleToSelector({ ...inheritRule, parent: combination.parent }, true)
+ const inheritedBackground = computed[inheritSelector].background
+
+ dynamicVars.inheritedBackground = inheritedBackground
+
+ const rgb = convert(findColor(computedDirectives.background, { dynamicVars, staticVars })).rgb
+
+ if (!stacked[selector]) {
+ let blend
+ const alpha = computedDirectives.opacity ?? 1
+ if (alpha >= 1) {
+ blend = rgb
+ } else if (alpha <= 0) {
+ blend = lowerLevelStackedBackground
+ } else {
+ blend = alphaBlend(rgb, computedDirectives.opacity, lowerLevelStackedBackground)
+ }
+ stacked[selector] = blend
+ computed[selector].background = { ...rgb, a: computedDirectives.opacity ?? 1 }
+ }
+ }
+
+ if (computedDirectives.shadow) {
+ dynamicVars.shadow = flattenDeep(findShadow(flattenDeep(computedDirectives.shadow), { dynamicVars, staticVars }))
+ }
+
+ if (!stacked[selector]) {
+ computedDirectives.background = 'transparent'
+ computedDirectives.opacity = 0
+ stacked[selector] = lowerLevelStackedBackground
+ computed[selector].background = { ...lowerLevelStackedBackground, a: 0 }
+ }
+
+ dynamicVars.stacked = stacked[selector]
+ dynamicVars.background = computed[selector].background
+
+ const dynamicSlots = Object.entries(computedDirectives).filter(([k, v]) => k.startsWith('--'))
+
+ dynamicSlots.forEach(([k, v]) => {
+ const [type, value] = v.split('|').map(x => x.trim()) // woah, Extreme!
+ switch (type) {
+ case 'color': {
+ const color = findColor(value, { dynamicVars, staticVars })
+ dynamicVars[k] = color
+ if (combination.component === rootComponentName) {
+ staticVars[k.substring(2)] = color
+ }
+ break
+ }
+ case 'shadow': {
+ const shadow = value.split(/,/g).map(s => s.trim()).filter(x => x)
+ dynamicVars[k] = shadow
+ if (combination.component === rootComponentName) {
+ staticVars[k.substring(2)] = shadow
+ }
+ break
+ }
+ case 'generic': {
+ dynamicVars[k] = value
+ if (combination.component === rootComponentName) {
+ staticVars[k.substring(2)] = value
+ }
+ break
+ }
+ }
+ })
+
+ const rule = {
+ dynamicVars,
+ selector: cssSelector,
+ ...combination,
+ directives: computedDirectives
+ }
+
+ return rule
+ }
+ } catch (e) {
+ const { component, variant, state } = combination
+ throw new Error(`Error processing combination ${component}.${variant}:${state.join(':')}: ${e}`)
+ }
+ }
+
+ const processInnerComponent = (component, parent) => {
+ const combinations = []
+ const {
+ states: originalStates = {},
+ variants: originalVariants = {}
+ } = component
+
+ let validInnerComponents
+ if (editMode) {
+ const temp = (component.validInnerComponentsLite || component.validInnerComponents || [])
+ validInnerComponents = temp.filter(c => virtualComponents.has(c) && !nonEditableComponents.has(c))
+ } else if (liteMode) {
+ validInnerComponents = (component.validInnerComponentsLite || component.validInnerComponents || [])
+ } else {
+ validInnerComponents = component.validInnerComponents || []
+ }
+
+ // Normalizing states and variants to always include "normal"
+ const states = { normal: '', ...originalStates }
+ const variants = { normal: '', ...originalVariants }
+ const innerComponents = (validInnerComponents).map(name => {
+ const result = components[name]
+ if (result === undefined) console.error(`Component ${component.name} references a component ${name} which does not exist!`)
+ return result
+ })
+
+ // Optimization: we only really need combinations without "normal" because all states implicitly have it
+ const permutationStateKeys = Object.keys(states).filter(s => s !== 'normal')
+ const stateCombinations = onlyNormalState
+ ? [
+ ['normal']
+ ]
+ : [
+ ['normal'],
+ ...getAllPossibleCombinations(permutationStateKeys)
+ .map(combination => ['normal', ...combination])
+ .filter(combo => {
+ // Optimization: filter out some hard-coded combinations that don't make sense
+ if (combo.indexOf('disabled') >= 0) {
+ return !(
+ combo.indexOf('hover') >= 0 ||
+ combo.indexOf('focused') >= 0 ||
+ combo.indexOf('pressed') >= 0
+ )
+ }
+ return true
+ })
+ ]
+
+ const stateVariantCombination = Object.keys(variants).map(variant => {
+ return stateCombinations.map(state => ({ variant, state }))
+ }).reduce((acc, x) => [...acc, ...x], [])
+
+ stateVariantCombination.forEach(combination => {
+ combination.component = component.name
+ combination.lazy = component.lazy || parent?.lazy
+ combination.parent = parent
+ if (!liteMode && combination.state.indexOf('hover') >= 0) {
+ combination.lazy = true
+ }
+
+ combinations.push(combination)
+
+ innerComponents.forEach(innerComponent => {
+ combinations.push(...processInnerComponent(innerComponent, combination))
+ })
+ })
+
+ return combinations
+ }
+
+ const t0 = performance.now()
+ const combinations = processInnerComponent(components[rootComponentName] ?? components.Root)
+ const t1 = performance.now()
+ if (debug) {
+ console.debug('Tree traveral took ' + (t1 - t0) + ' ms')
+ }
+
+ const result = combinations.map((combination) => {
+ if (combination.lazy) {
+ return async () => processCombination(combination)
+ } else {
+ return processCombination(combination)
+ }
+ }).filter(x => x)
+ const t2 = performance.now()
+ if (debug) {
+ console.debug('Eager processing took ' + (t2 - t1) + ' ms')
+ }
+
+ // optimization to traverse big-ass array only once instead of twice
+ const eager = []
+ const lazy = []
+
+ result.forEach(x => {
+ if (typeof x === 'function') {
+ lazy.push(x)
+ } else {
+ eager.push(x)
+ }
+ })
+
+ return {
+ lazy,
+ eager,
+ staticVars,
+ engineChecksum,
+ themeChecksum: sum([lazy, eager])
+ }
+}
diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js
@@ -3,12 +3,13 @@ import { camelCase } from 'lodash'
import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js'
-const update = ({ store, statuses, timeline, showImmediately, userId, pagination }) => {
+const update = ({ store, statuses, timeline, showImmediately, userId, listId, pagination }) => {
const ccTimeline = camelCase(timeline)
store.dispatch('addNewStatuses', {
timeline: ccTimeline,
userId,
+ listId,
statuses,
showImmediately,
pagination
@@ -22,6 +23,9 @@ const fetchAndUpdate = ({
older = false,
showImmediately = false,
userId = false,
+ listId = false,
+ statusId = false,
+ bookmarkFolderId = false,
tag = false,
until,
since
@@ -34,20 +38,23 @@ const fetchAndUpdate = ({
const loggedIn = !!rootState.users.currentUser
if (older) {
- args['until'] = until || timelineData.minId
+ args.until = until || timelineData.minId
} else {
if (since === undefined) {
- args['since'] = timelineData.maxId
+ args.since = timelineData.maxId
} else if (since !== null) {
- args['since'] = since
+ args.since = since
}
}
- args['userId'] = userId
- args['tag'] = tag
- args['withMuted'] = !hideMutedPosts
+ args.userId = userId
+ args.listId = listId
+ args.statusId = statusId
+ args.bookmarkFolderId = bookmarkFolderId
+ args.tag = tag
+ args.withMuted = !hideMutedPosts
if (loggedIn && ['friends', 'public', 'publicAndExternal'].includes(timeline)) {
- args['replyVisibility'] = replyVisibility
+ args.replyVisibility = replyVisibility
}
const numStatusesBeforeFetch = timelineData.statuses.length
@@ -60,9 +67,9 @@ const fetchAndUpdate = ({
const { data: statuses, pagination } = response
if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) {
- store.dispatch('queueFlush', { timeline: timeline, id: timelineData.maxId })
+ store.dispatch('queueFlush', { timeline, id: timelineData.maxId })
}
- update({ store, statuses, timeline, showImmediately, userId, pagination })
+ update({ store, statuses, timeline, showImmediately, userId, listId, pagination })
return { statuses, pagination }
})
.catch((error) => {
@@ -75,14 +82,16 @@ const fetchAndUpdate = ({
})
}
-const startFetching = ({ timeline = 'friends', credentials, store, userId = false, tag = false }) => {
+const startFetching = ({ timeline = 'friends', credentials, store, userId = false, listId = false, statusId = false, bookmarkFolderId = false, tag = false }) => {
const rootState = store.rootState || store.state
const timelineData = rootState.statuses.timelines[camelCase(timeline)]
const showImmediately = timelineData.visibleStatuses.length === 0
timelineData.userId = userId
- fetchAndUpdate({ timeline, credentials, store, showImmediately, userId, tag })
+ timelineData.listId = listId
+ timelineData.bookmarkFolderId = bookmarkFolderId
+ fetchAndUpdate({ timeline, credentials, store, showImmediately, userId, listId, statusId, bookmarkFolderId, tag })
const boundFetchAndUpdate = () =>
- fetchAndUpdate({ timeline, credentials, store, userId, tag })
+ fetchAndUpdate({ timeline, credentials, store, userId, listId, statusId, bookmarkFolderId, tag })
return promiseInterval(boundFetchAndUpdate, 10000)
}
const timelineFetcher = {
diff --git a/src/services/user_highlighter/user_highlighter.js b/src/services/user_highlighter/user_highlighter.js
@@ -36,7 +36,7 @@ const highlightStyle = (prefs) => {
'linear-gradient(to right,',
`${solidColor} ,`,
`${solidColor} 2px,`,
- `transparent 6px`
+ 'transparent 6px'
].join(' '),
backgroundPosition: '0 0',
...customProps
diff --git a/src/services/version/version.service.js b/src/services/version/version.service.js
@@ -1,6 +0,0 @@
-
-export const extractCommit = versionString => {
- const regex = /-g(\w+)/i
- const matches = versionString.match(regex)
- return matches ? matches[1] : ''
-}
diff --git a/src/sw.js b/src/sw.js
@@ -13,9 +13,10 @@ const i18n = createI18n({
messages
})
-function isEnabled () {
- return localForage.getItem('vuex-lz')
- .then(data => data.config.webPushNotifications)
+const state = {
+ lastFocused: null,
+ notificationIds: new Set(),
+ allowedNotificationTypes: null
}
function getWindowClients () {
@@ -23,17 +24,48 @@ function getWindowClients () {
.then((clientList) => clientList.filter(({ type }) => type === 'window'))
}
-const setLocale = async () => {
- const state = await localForage.getItem('vuex-lz')
- const locale = state.config.interfaceLanguage || 'en'
+const setSettings = async () => {
+ const vuexState = await localForage.getItem('vuex-lz')
+ const locale = vuexState.config.interfaceLanguage || 'en'
i18n.locale = locale
+ const notificationsNativeArray = Object.entries(vuexState.config.notificationNative)
+ state.webPushAlwaysShowNotifications = vuexState.config.webPushAlwaysShowNotifications
+
+ state.allowedNotificationTypes = new Set(
+ notificationsNativeArray
+ .filter(([k, v]) => v)
+ .map(([k]) => {
+ switch (k) {
+ case 'mentions':
+ return 'mention'
+ case 'statuses':
+ return 'status'
+ case 'likes':
+ return 'like'
+ case 'repeats':
+ return 'repeat'
+ case 'emojiReactions':
+ return 'pleroma:emoji_reaction'
+ case 'reports':
+ return 'pleroma:report'
+ case 'followRequest':
+ return 'follow_request'
+ case 'follows':
+ return 'follow'
+ case 'polls':
+ return 'poll'
+ default:
+ return k
+ }
+ })
+ )
}
-const maybeShowNotification = async (event) => {
- const enabled = await isEnabled()
+const showPushNotification = async (event) => {
const activeClients = await getWindowClients()
- await setLocale()
- if (enabled && (activeClients.length === 0)) {
+ await setSettings()
+ // Only show push notifications if all tabs/windows are closed
+ if (state.webPushAlwaysShowNotifications || activeClients.length === 0) {
const data = event.data.json()
const url = `${self.registration.scope}api/v1/notifications/${data.notification_id}`
@@ -43,13 +75,48 @@ const maybeShowNotification = async (event) => {
const res = prepareNotificationObject(parsedNotification, i18n)
- self.registration.showNotification(res.title, res)
+ if (state.webPushAlwaysShowNotifications || state.allowedNotificationTypes.has(parsedNotification.type)) {
+ return self.registration.showNotification(res.title, res)
+ }
}
+ return Promise.resolve()
}
self.addEventListener('push', async (event) => {
if (event.data) {
- event.waitUntil(maybeShowNotification(event))
+ // Supposedly, we HAVE to return a promise inside waitUntil otherwise it will
+ // show (extra) notification that website is updated in background
+ event.waitUntil(showPushNotification(event))
+ }
+})
+
+self.addEventListener('message', async (event) => {
+ await setSettings()
+ const { type, content } = event.data
+
+ if (type === 'desktopNotification') {
+ const { title, ...rest } = content
+ const { tag, type } = rest
+ if (state.notificationIds.has(tag)) return
+ state.notificationIds.add(tag)
+ setTimeout(() => state.notificationIds.delete(tag), 10000)
+ if (state.allowedNotificationTypes.has(type)) {
+ self.registration.showNotification(title, rest)
+ }
+ }
+
+ if (type === 'desktopNotificationClose') {
+ const { id, all } = content
+ const search = all ? null : { tag: id }
+ const notifications = await self.registration.getNotifications(search)
+ notifications.forEach(n => n.close())
+ }
+
+ if (type === 'updateFocus') {
+ state.lastFocused = event.source.id
+
+ const notifications = await self.registration.getNotifications()
+ notifications.forEach(n => n.close())
}
})
@@ -57,9 +124,16 @@ self.addEventListener('notificationclick', (event) => {
event.notification.close()
event.waitUntil(getWindowClients().then((list) => {
- for (var i = 0; i < list.length; i++) {
- var client = list[i]
- if (client.url === '/' && 'focus' in client) { return client.focus() }
+ for (let i = 0; i < list.length; i++) {
+ const client = list[i]
+ client.postMessage({ type: 'notificationClicked', id: event.notification.tag })
+ }
+
+ for (let i = 0; i < list.length; i++) {
+ const client = list[i]
+ if (state.lastFocused === null || client.id === state.lastFocused) {
+ if ('focus' in client) return client.focus()
+ }
}
if (clients.openWindow) return clients.openWindow('/')
diff --git a/static/.gitignore b/static/.gitignore
@@ -0,0 +1 @@
+*.custom.*
diff --git a/static/config.json b/static/config.json
@@ -14,6 +14,7 @@
"logoMask": true,
"logoLeft": false,
"minimalScopesMode": false,
+ "disableUpdateNotification": false,
"nsfwCensorImage": "",
"postContentType": "text/plain",
"redirectRootLogin": "/main/friends",
@@ -23,6 +24,8 @@
"showInstanceSpecificPanel": false,
"sidebarRight": false,
"subjectLineBehavior": "email",
- "theme": "pleroma-dark",
+ "theme": null,
+ "style": null,
+ "palette": null,
"webPushNotifications": false
}
diff --git a/static/emoji.json b/static/emoji.json
@@ -1,1431 +0,0 @@
-{
- "100": "💯",
- "1234": "🔢",
- "1st_place_medal": "🥇",
- "2nd_place_medal": "🥈",
- "3rd_place_medal": "🥉",
- "8ball": "🎱",
- "a_button_blood_type": "🅰",
- "ab": "🆎",
- "abacus": "🧮",
- "abc": "🔤",
- "abcd": "🔡",
- "accept": "🉑",
- "adhesive_bandage": "🩹",
- "admission_tickets": "🎟",
- "adult": "🧑",
- "aerial_tramway": "🚡",
- "airplane": "✈",
- "airplane_arriving": "🛬",
- "airplane_departure": "🛫",
- "alarm_clock": "⏰",
- "alembic": "⚗️",
- "alien": "👽",
- "ambulance": "🚑",
- "amphora": "🏺",
- "anchor": "⚓",
- "angel": "👼",
- "anger": "💢",
- "anger_right": "🗯",
- "angry": "😠",
- "anguished": "😧",
- "ant": "🐜",
- "apple": "🍎",
- "aquarius": "♒",
- "aries": "♈",
- "arrow_backward": "◀️",
- "arrow_double_down": "⏬",
- "arrow_double_up": "⏫",
- "arrow_down": "⬇️",
- "arrow_down_small": "🔽",
- "arrow_forward": "▶️",
- "arrow_heading_down": "⤵️",
- "arrow_heading_up": "⤴️",
- "arrow_left": "⬅️",
- "arrow_lower_left": "↙️",
- "arrow_lower_right": "↘️",
- "arrow_right": "➡",
- "arrow_right_hook": "↪️",
- "arrow_up": "⬆️",
- "arrow_up_down": "↕",
- "arrow_up_small": "🔼",
- "arrow_upper_left": "↖",
- "arrow_upper_right": "↗️",
- "arrows_clockwise": "🔃",
- "arrows_counterclockwise": "🔄",
- "art": "🎨",
- "articulated_lorry": "🚛",
- "artist_palette": "🎨",
- "asterisk": "*⃣",
- "astonished": "😲",
- "athletic_shoe": "👟",
- "atm": "🏧",
- "atom": "⚛",
- "atom_symbol": "⚛️",
- "auto_rickshaw": "🛺",
- "automobile": "🚗",
- "avocado": "🥑",
- "axe": "🪓",
- "b_button_blood_type": "🅱",
- "baby": "👶",
- "baby_bottle": "🍼",
- "baby_chick": "🐤",
- "baby_symbol": "🚼",
- "back": "🔙",
- "bacon": "🥓",
- "badger": "🦡",
- "badminton": "🏸",
- "bagel": "🥯",
- "baggage_claim": "🛄",
- "baguette_bread": "🥖",
- "balance_scale": "⚖️",
- "bald": "🦲",
- "ballet_shoes": "🩰",
- "balloon": "🎈",
- "ballot_box": "🗳",
- "ballot_box_with_check": "☑️",
- "bamboo": "🎍",
- "banana": "🍌",
- "bangbang": "‼️",
- "banjo": "🪕",
- "bank": "🏦",
- "bar_chart": "📊",
- "barber": "💈",
- "baseball": "⚾",
- "basket": "🧺",
- "basketball": "🏀",
- "basketballer": "⛹",
- "bat": "🦇",
- "bath": "🛀",
- "bathtub": "🛁",
- "battery": "🔋",
- "beach_umbrella": "⛱",
- "beach_with_umbrella": "🏖",
- "bear": "🐻",
- "beard": "🧔",
- "bearded_person": "🧔",
- "bed": "🛏",
- "bee": "🐝",
- "beer": "🍺",
- "beers": "🍻",
- "beetle": "🐞",
- "beginner": "🔰",
- "bell": "🔔",
- "bellhop_bell": "🛎",
- "bento": "🍱",
- "beverage_box": "🧃",
- "bicyclist": "🚴",
- "bike": "🚲",
- "bikini": "👙",
- "billed_cap": "🧢",
- "biohazard": "☣️",
- "bird": "🐦",
- "birthday": "🎂",
- "black_circle": "⚫",
- "black_heart": "🖤",
- "black_joker": "🃏",
- "black_large_square": "⬛",
- "black_medium_small_square": "◾",
- "black_medium_square": "◼",
- "black_nib": "✒️",
- "black_small_square": "▪",
- "black_square_button": "🔲",
- "blond_haired_person": "👱",
- "blossom": "🌼",
- "blowfish": "🐡",
- "blue_book": "📘",
- "blue_car": "🚙",
- "blue_circle": "🔵",
- "blue_heart": "💙",
- "blue_square": "🟦",
- "blush": "😊",
- "boar": "🐗",
- "bomb": "💣",
- "bone": "🦴",
- "book": "📖",
- "bookmark": "🔖",
- "bookmark_tabs": "📑",
- "books": "📚",
- "boom": "💥",
- "boot": "👢",
- "bouquet": "💐",
- "bow": "🙇",
- "bow_and_arrow": "🏹",
- "bowl_with_spoon": "🥣",
- "bowling": "🎳",
- "boxing_glove": "🥊",
- "boy": "👦",
- "brain": "🧠",
- "bread": "🍞",
- "breast_feeding": "🤱",
- "breastfeeding": "🤱",
- "brick": "🧱",
- "bride_with_veil": "👰",
- "bridge_at_night": "🌉",
- "briefcase": "💼",
- "briefs": "🩲",
- "broccoli": "🥦",
- "broken_heart": "💔",
- "broom": "🧹",
- "brown_circle": "🟤",
- "brown_heart": "🤎",
- "bug": "🐛",
- "building_construction": "🏗",
- "bulb": "💡",
- "bullettrain_front": "🚅",
- "bullettrain_side": "🚄",
- "burrito": "🌯",
- "bus": "🚌",
- "busstop": "🚏",
- "bust_in_silhouette": "👤",
- "busts_in_silhouette": "👥",
- "butter": "🧈",
- "butterfly": "🦋",
- "cactus": "🌵",
- "cake": "🍰",
- "calendar": "📆",
- "call_me": "🤙",
- "call_me_hand": "🤙",
- "calling": "📲",
- "camel": "🐫",
- "camera": "📷",
- "camera_with_flash": "📸",
- "camping": "🏕",
- "cancer": "♋",
- "candle": "🕯",
- "candy": "🍬",
- "canned_food": "🥫",
- "canoe": "🛶",
- "capital_abcd": "🔠",
- "capricorn": "♑",
- "card_file_box": "🗃",
- "card_index": "📇",
- "card_index_dividers": "🗂",
- "carousel_horse": "🎠",
- "carrot": "🥕",
- "cat": "🐱",
- "cat2": "🐈",
- "cd": "💿",
- "chains": "⛓️",
- "chair": "🪑",
- "champagne": "🍾",
- "champagne_glass": "🥂",
- "chart": "💹",
- "chart_with_downwards_trend": "📉",
- "chart_with_upwards_trend": "📈",
- "check_box_with_check": "☑",
- "check_mark": "✔",
- "checkered_flag": "🏁",
- "cheese": "🧀",
- "cheese_wedge": "🧀",
- "cherries": "🍒",
- "cherry_blossom": "🌸",
- "chess_pawn": "♟",
- "chestnut": "🌰",
- "chicken": "🐔",
- "child": "🧒",
- "children_crossing": "🚸",
- "chipmunk": "🐿",
- "chocolate_bar": "🍫",
- "chopsticks": "🥢",
- "christmas_tree": "🎄",
- "church": "⛪",
- "cinema": "🎦",
- "circled_m": "Ⓜ",
- "circus_tent": "🎪",
- "city_dusk": "🌆",
- "city_sunset": "🌇",
- "cityscape": "🏙",
- "cityscape_at_dusk": "🌆",
- "cl": "🆑",
- "clap": "👏",
- "clapper": "🎬",
- "classical_building": "🏛",
- "clinking_glasses": "🥂",
- "clipboard": "📋",
- "clock1": "🕐",
- "clock10": "🕙",
- "clock1030": "🕥",
- "clock11": "🕚",
- "clock1130": "🕦",
- "clock12": "🕛",
- "clock1230": "🕧",
- "clock130": "🕜",
- "clock2": "🕑",
- "clock230": "🕝",
- "clock3": "🕒",
- "clock330": "🕞",
- "clock4": "🕓",
- "clock430": "🕟",
- "clock5": "🕔",
- "clock530": "🕠",
- "clock6": "🕕",
- "clock630": "🕡",
- "clock7": "🕖",
- "clock730": "🕢",
- "clock8": "🕗",
- "clock830": "🕣",
- "clock9": "🕘",
- "clock930": "🕤",
- "closed_book": "📕",
- "closed_lock_with_key": "🔐",
- "closed_umbrella": "🌂",
- "cloud": "☁️",
- "cloud_with_lightning": "🌩",
- "cloud_with_lightning_and_rain": "⛈️",
- "cloud_with_rain": "🌧",
- "cloud_with_snow": "🌨",
- "clown": "🤡",
- "clown_face": "🤡",
- "club_suit": "♣️",
- "clubs": "♣",
- "coat": "🧥",
- "cocktail": "🍸",
- "coconut": "🥥",
- "coffee": "☕",
- "coffin": "⚰️",
- "cold_face": "🥶",
- "cold_sweat": "😰",
- "comet": "☄️",
- "compass": "🧭",
- "compression": "🗜",
- "computer": "💻",
- "computer_mouse": "🖱",
- "confetti_ball": "🎊",
- "confounded": "😖",
- "confused": "😕",
- "congratulations": "㊗",
- "construction": "🚧",
- "construction_worker": "👷",
- "control_knobs": "🎛",
- "convenience_store": "🏪",
- "cookie": "🍪",
- "cooking": "🍳",
- "cool": "🆒",
- "cop": "👮",
- "copyright": "©",
- "corn": "🌽",
- "couch_and_lamp": "🛋",
- "couple": "👫",
- "couple_with_heart": "💑",
- "couplekiss": "💏",
- "cow": "🐮",
- "cow2": "🐄",
- "cowboy": "🤠",
- "cowboy_hat_face": "🤠",
- "crab": "🦀",
- "crayon": "🖍",
- "crazy_face": "🤪",
- "credit_card": "💳",
- "crescent_moon": "🌙",
- "cricket": "🦗",
- "cricket_game": "🏏",
- "crocodile": "🐊",
- "croissant": "🥐",
- "cross": "✝️",
- "crossed_fingers": "🤞",
- "crossed_flags": "🎌",
- "crossed_swords": "⚔️",
- "crown": "👑",
- "cry": "😢",
- "crying_cat_face": "😿",
- "crystal_ball": "🔮",
- "cucumber": "🥒",
- "cup_with_straw": "🥤",
- "cupcake": "🧁",
- "cupid": "💘",
- "curling_stone": "🥌",
- "curly_hair": "🦱",
- "curly_loop": "➰",
- "currency_exchange": "💱",
- "curry": "🍛",
- "custard": "🍮",
- "customs": "🛃",
- "cut_of_meat": "🥩",
- "cyclone": "🌀",
- "dagger": "🗡",
- "dancer": "💃",
- "dancers": "👯",
- "dango": "🍡",
- "dark_skin_tone": "🏿",
- "dark_sunglasses": "🕶",
- "dart": "🎯",
- "dash": "💨",
- "date": "📅",
- "deaf_person": "🧏",
- "deciduous_tree": "🌳",
- "deer": "🦌",
- "department_store": "🏬",
- "derelict_house": "🏚",
- "desert": "🏜",
- "desert_island": "🏝",
- "desktop_computer": "🖥",
- "detective": "🕵",
- "diamond_shape_with_a_dot_inside": "💠",
- "diamond_suit": "♦️",
- "diamonds": "♦",
- "disappointed": "😞",
- "disappointed_relieved": "😥",
- "diving_mask": "🤿",
- "diya_lamp": "🪔",
- "dizzy": "💫",
- "dizzy_face": "😵",
- "dna": "🧬",
- "do_not_litter": "🚯",
- "dog": "🐶",
- "dog2": "🐕",
- "dollar": "💵",
- "dolls": "🎎",
- "dolphin": "🐬",
- "door": "🚪",
- "double_exclamation_mark": "‼",
- "doughnut": "🍩",
- "dove": "🕊",
- "down_arrow": "⬇",
- "downleft_arrow": "↙",
- "downright_arrow": "↘",
- "dragon": "🐉",
- "dragon_face": "🐲",
- "dress": "👗",
- "dromedary_camel": "🐪",
- "drooling_face": "🤤",
- "drop_of_blood": "🩸",
- "droplet": "💧",
- "drum": "🥁",
- "duck": "🦆",
- "dumpling": "🥟",
- "dvd": "📀",
- "e-mail": "📧",
- "eagle": "🦅",
- "ear": "👂",
- "ear_of_rice": "🌾",
- "ear_with_hearing_aid": "🦻",
- "earth_africa": "🌍",
- "earth_americas": "🌎",
- "earth_asia": "🌏",
- "egg": "🥚",
- "eggplant": "🍆",
- "eight": "8⃣",
- "eight_pointed_black_star": "✴️",
- "eight_spoked_asterisk": "✳️",
- "eightpointed_star": "✴",
- "eightspoked_asterisk": "✳",
- "eject_button": "⏏",
- "electric_plug": "🔌",
- "elephant": "🐘",
- "elf": "🧝",
- "end": "🔚",
- "envelope": "✉",
- "envelope_with_arrow": "📩",
- "euro": "💶",
- "european_castle": "🏰",
- "european_post_office": "🏤",
- "evergreen_tree": "🌲",
- "exclamation": "❗",
- "exclamation_question_mark": "⁉",
- "exploding_head": "🤯",
- "expressionless": "😑",
- "eye": "👁",
- "eyeglasses": "👓",
- "eyes": "👀",
- "face_vomiting": "🤮",
- "face_with_hand_over_mouth": "🤭",
- "face_with_headbandage": "🤕",
- "face_with_monocle": "🧐",
- "face_with_raised_eyebrow": "🤨",
- "face_with_symbols_on_mouth": "🤬",
- "face_with_symbols_over_mouth": "🤬",
- "face_with_thermometer": "🤒",
- "factory": "🏭",
- "fairy": "🧚",
- "falafel": "🧆",
- "fallen_leaf": "🍂",
- "family": "👪",
- "fast_forward": "⏩",
- "fax": "📠",
- "fearful": "😨",
- "feet": "🐾",
- "female_sign": "♀",
- "ferris_wheel": "🎡",
- "ferry": "⛴️",
- "field_hockey": "🏑",
- "file_cabinet": "🗄",
- "file_folder": "📁",
- "film_frames": "🎞",
- "film_projector": "📽",
- "fingers_crossed": "🤞",
- "fire": "🔥",
- "fire_engine": "🚒",
- "fire_extinguisher": "🧯",
- "firecracker": "🧨",
- "fireworks": "🎆",
- "first_place": "🥇",
- "first_quarter_moon": "🌓",
- "first_quarter_moon_with_face": "🌛",
- "fish": "🐟",
- "fish_cake": "🍥",
- "fishing_pole_and_fish": "🎣",
- "fist": "✊",
- "five": "5⃣",
- "flag_black": "🏴",
- "flag_white": "🏳",
- "flags": "🎏",
- "flamingo": "🦩",
- "flashlight": "🔦",
- "flat_shoe": "🥿",
- "fleur-de-lis": "⚜",
- "fleurde-lis": "⚜️",
- "floppy_disk": "💾",
- "flower_playing_cards": "🎴",
- "flushed": "😳",
- "flying_disc": "🥏",
- "flying_saucer": "🛸",
- "fog": "🌫",
- "foggy": "🌁",
- "foot": "🦶",
- "football": "🏈",
- "footprints": "👣",
- "fork_and_knife": "🍴",
- "fork_and_knife_with_plate": "🍽",
- "fortune_cookie": "🥠",
- "fountain": "⛲",
- "fountain_pen": "🖋",
- "four": "4⃣",
- "four_leaf_clover": "🍀",
- "fox": "🦊",
- "framed_picture": "🖼",
- "free": "🆓",
- "french_bread": "🥖",
- "fried_shrimp": "🍤",
- "fries": "🍟",
- "frog": "🐸",
- "frowning": "😦",
- "frowning_face": "☹️",
- "fuelpump": "⛽",
- "full_moon": "🌕",
- "full_moon_with_face": "🌝",
- "funeral_urn": "⚱️",
- "game_die": "🎲",
- "garlic": "🧄",
- "gear": "⚙️",
- "gem": "💎",
- "gemini": "♊",
- "genie": "🧞",
- "ghost": "👻",
- "gift": "🎁",
- "gift_heart": "💝",
- "giraffe": "🦒",
- "girl": "👧",
- "glass_of_milk": "🥛",
- "globe_with_meridians": "🌐",
- "gloves": "🧤",
- "goal": "🥅",
- "goal_net": "🥅",
- "goat": "🐐",
- "goggles": "🥽",
- "golf": "⛳",
- "golfer": "🏌",
- "gorilla": "🦍",
- "grapes": "🍇",
- "green_apple": "🍏",
- "green_book": "📗",
- "green_circle": "🟢",
- "green_heart": "💚",
- "green_salad": "🥗",
- "green_square": "🟩",
- "grey_exclamation": "❕",
- "grey_question": "❔",
- "grimacing": "😬",
- "grin": "😁",
- "grinning": "😀",
- "guard": "💂",
- "guardsman": "💂",
- "guide_dog": "🦮",
- "guitar": "🎸",
- "gun": "🔫",
- "haircut": "💇",
- "hamburger": "🍔",
- "hammer": "🔨",
- "hammer_and_pick": "⚒️",
- "hammer_and_wrench": "🛠",
- "hamster": "🐹",
- "hand_with_fingers_splayed": "🖐",
- "handbag": "👜",
- "handshake": "🤝",
- "hash": "#⃣",
- "hatched_chick": "🐥",
- "hatching_chick": "🐣",
- "head_bandage": "🤕",
- "headphones": "🎧",
- "hear_no_evil": "🙉",
- "heart": "❤️",
- "heart_decoration": "💟",
- "heart_exclamation": "❣",
- "heart_eyes": "😍",
- "heart_eyes_cat": "😻",
- "heart_suit": "♥️",
- "heartbeat": "💓",
- "heartpulse": "💗",
- "hearts": "♥",
- "heavy_check_mark": "✔️",
- "heavy_division_sign": "➗",
- "heavy_dollar_sign": "💲",
- "heavy_minus_sign": "➖",
- "heavy_multiplication_x": "✖️",
- "heavy_plus_sign": "➕",
- "hedgehog": "🦔",
- "helicopter": "🚁",
- "herb": "🌿",
- "hibiscus": "🌺",
- "high_brightness": "🔆",
- "high_heel": "👠",
- "hiking_boot": "🥾",
- "hindu_temple": "🛕",
- "hippopotamus": "🦛",
- "hockey": "🏒",
- "hole": "🕳",
- "honey_pot": "🍯",
- "horse": "🐴",
- "horse_racing": "🏇",
- "hospital": "🏥",
- "hot_face": "🥵",
- "hot_pepper": "🌶",
- "hot_springs": "♨",
- "hotdog": "🌭",
- "hotel": "🏨",
- "hotsprings": "♨️",
- "hourglass": "⌛",
- "hourglass_flowing_sand": "⏳",
- "house": "🏠",
- "house_with_garden": "🏡",
- "houses": "🏘",
- "hugging": "🤗",
- "hundred_points": "💯",
- "hushed": "😯",
- "ice": "🧊",
- "ice_cream": "🍨",
- "ice_hockey": "🏒",
- "ice_skate": "⛸️",
- "icecream": "🍦",
- "id": "🆔",
- "ideograph_advantage": "🉐",
- "imp": "👿",
- "inbox_tray": "📥",
- "incoming_envelope": "📨",
- "index_pointing_up": "☝",
- "infinity": "♾",
- "information": "ℹ️",
- "information_desk_person": "💁",
- "information_source": "ℹ",
- "innocent": "😇",
- "input_numbers": "🔢",
- "interrobang": "⁉️",
- "iphone": "📱",
- "izakaya_lantern": "🏮",
- "jack_o_lantern": "🎃",
- "japan": "🗾",
- "japanese_castle": "🏯",
- "japanese_congratulations_button": "㊗️",
- "japanese_free_of_charge_button": "🈚",
- "japanese_goblin": "👺",
- "japanese_ogre": "👹",
- "japanese_reserved_button": "🈯",
- "japanese_secret_button": "㊙️",
- "japanese_service_charge_button": "🈂",
- "jeans": "👖",
- "joy": "😂",
- "joy_cat": "😹",
- "joystick": "🕹",
- "kaaba": "🕋",
- "kangaroo": "🦘",
- "key": "🔑",
- "keyboard": "⌨️",
- "keycap_ten": "🔟",
- "kick_scooter": "🛴",
- "kimono": "👘",
- "kiss": "💋",
- "kissing": "😗",
- "kissing_cat": "😽",
- "kissing_closed_eyes": "😚",
- "kissing_heart": "😘",
- "kissing_smiling_eyes": "😙",
- "kitchen_knife": "🔪",
- "kite": "🪁",
- "kiwi": "🥝",
- "kiwi_fruit": "🥝",
- "knife": "🔪",
- "koala": "🐨",
- "koko": "🈁",
- "lab_coat": "🥼",
- "label": "🏷",
- "lacrosse": "🥍",
- "large_blue_diamond": "🔷",
- "large_orange_diamond": "🔶",
- "last_quarter_moon": "🌗",
- "last_quarter_moon_with_face": "🌜",
- "last_track_button": "⏮️",
- "latin_cross": "✝",
- "laughing": "😆",
- "leafy_green": "🥬",
- "leaves": "🍃",
- "ledger": "📒",
- "left_arrow": "⬅",
- "left_arrow_curving_right": "↪",
- "left_facing_fist": "🤛",
- "left_luggage": "🛅",
- "left_right_arrow": "↔",
- "leftfacing_fist": "🤛",
- "leftright_arrow": "↔️",
- "leftwards_arrow_with_hook": "↩️",
- "leg": "🦵",
- "lemon": "🍋",
- "leo": "♌",
- "leopard": "🐆",
- "level_slider": "🎚",
- "libra": "♎",
- "light_rail": "🚈",
- "light_skin_tone": "🏻",
- "link": "🔗",
- "linked_paperclips": "🖇",
- "lion_face": "🦁",
- "lips": "👄",
- "lipstick": "💄",
- "lizard": "🦎",
- "llama": "🦙",
- "lobster": "🦞",
- "lock": "🔒",
- "lock_with_ink_pen": "🔏",
- "lollipop": "🍭",
- "loop": "➿",
- "lotion_bottle": "🧴",
- "loud_sound": "🔊",
- "loudspeaker": "📢",
- "love_hotel": "🏩",
- "love_letter": "💌",
- "love_you_gesture": "🤟",
- "loveyou_gesture": "🤟",
- "low_brightness": "🔅",
- "luggage": "🧳",
- "lying_face": "🤥",
- "m": "Ⓜ️",
- "mag": "🔍",
- "mag_right": "🔎",
- "mage": "🧙",
- "magnet": "🧲",
- "mahjong": "🀄",
- "mailbox": "📫",
- "mailbox_closed": "📪",
- "mailbox_with_mail": "📬",
- "mailbox_with_no_mail": "📭",
- "male_sign": "♂",
- "man": "👨",
- "man_dancing": "🕺",
- "man_in_suit": "🕴",
- "man_in_tuxedo": "🤵",
- "man_with_chinese_cap": "👲",
- "man_with_gua_pi_mao": "👲",
- "man_with_turban": "👳",
- "mango": "🥭",
- "mans_shoe": "👞",
- "mantelpiece_clock": "🕰",
- "manual_wheelchair": "🦽",
- "maple_leaf": "🍁",
- "martial_arts_uniform": "🥋",
- "mask": "😷",
- "massage": "💆",
- "mate": "🧉",
- "meat_on_bone": "🍖",
- "mechanical_arm": "🦾",
- "mechanical_leg": "🦿",
- "medal": "🏅",
- "medical_symbol": "⚕",
- "medium_skin_tone": "🏽",
- "mediumdark_skin_tone": "🏾",
- "mediumlight_skin_tone": "🏼",
- "mega": "📣",
- "melon": "🍈",
- "memo": "📝",
- "menorah": "🕎",
- "mens": "🚹",
- "merperson": "🧜",
- "metal": "🤘",
- "metro": "🚇",
- "microbe": "🦠",
- "microphone": "🎤",
- "microscope": "🔬",
- "middle_finger": "🖕",
- "military_medal": "🎖",
- "milk": "🥛",
- "milky_way": "🌌",
- "minibus": "🚐",
- "minidisc": "💽",
- "mobile_phone_off": "📴",
- "money_mouth": "🤑",
- "money_with_wings": "💸",
- "moneybag": "💰",
- "moneymouth_face": "🤑",
- "monkey": "🐒",
- "monkey_face": "🐵",
- "monorail": "🚝",
- "moon_cake": "🥮",
- "mortar_board": "🎓",
- "mosque": "🕌",
- "mosquito": "🦟",
- "motor_boat": "🛥",
- "motor_scooter": "🛵",
- "motorcycle": "🏍",
- "motorized_wheelchair": "🦼",
- "motorway": "🛣",
- "mount_fuji": "🗻",
- "mountain": "⛰️",
- "mountain_bicyclist": "🚵",
- "mountain_cableway": "🚠",
- "mountain_railway": "🚞",
- "mouse": "🐭",
- "mouse2": "🐁",
- "movie_camera": "🎥",
- "moyai": "🗿",
- "mrs_claus": "🤶",
- "multiplication_sign": "✖",
- "muscle": "💪",
- "mushroom": "🍄",
- "musical_keyboard": "🎹",
- "musical_note": "🎵",
- "musical_score": "🎼",
- "mute": "🔇",
- "nail_care": "💅",
- "name_badge": "📛",
- "national_park": "🏞",
- "nauseated_face": "🤢",
- "nazar_amulet": "🧿",
- "necktie": "👔",
- "negative_squared_cross_mark": "❎",
- "nerd": "🤓",
- "neutral_face": "😐",
- "new": "🆕",
- "new_moon": "🌑",
- "new_moon_with_face": "🌚",
- "newspaper": "📰",
- "next_track_button": "⏭️",
- "ng": "🆖",
- "night_with_stars": "🌃",
- "nine": "9⃣",
- "no_bell": "🔕",
- "no_bicycles": "🚳",
- "no_entry": "⛔",
- "no_entry_sign": "🚫",
- "no_good": "🙅",
- "no_mobile_phones": "📵",
- "no_mouth": "😶",
- "no_pedestrians": "🚷",
- "no_smoking": "🚭",
- "non-potable_water": "🚱",
- "nose": "👃",
- "notebook": "📓",
- "notebook_with_decorative_cover": "📔",
- "notes": "🎶",
- "nut_and_bolt": "🔩",
- "o": "⭕",
- "o_button_blood_type": "🅾",
- "ocean": "🌊",
- "octagonal_sign": "🛑",
- "octopus": "🐙",
- "oden": "🍢",
- "office": "🏢",
- "oil_drum": "🛢",
- "ok": "🆗",
- "ok_hand": "👌",
- "ok_woman": "🙆",
- "old_key": "🗝",
- "older_adult": "🧓",
- "older_man": "👴",
- "older_person": "🧓",
- "older_woman": "👵",
- "om_symbol": "🕉",
- "on": "🔛",
- "oncoming_automobile": "🚘",
- "oncoming_bus": "🚍",
- "oncoming_fist": "👊",
- "oncoming_police_car": "🚔",
- "oncoming_taxi": "🚖",
- "one": "1⃣",
- "onepiece_swimsuit": "🩱",
- "onion": "🧅",
- "open_file_folder": "📂",
- "open_hands": "👐",
- "open_mouth": "😮",
- "ophiuchus": "⛎",
- "orange_book": "📙",
- "orange_circle": "🟠",
- "orange_heart": "🧡",
- "orange_square": "🟧",
- "orangutan": "🦧",
- "orthodox_cross": "☦️",
- "otter": "🦦",
- "outbox_tray": "📤",
- "owl": "🦉",
- "ox": "🐂",
- "oyster": "🦪",
- "p_button": "🅿",
- "package": "📦",
- "page_facing_up": "📄",
- "page_with_curl": "📃",
- "pager": "📟",
- "paintbrush": "🖌",
- "palm_tree": "🌴",
- "palms_up_together": "🤲",
- "pancakes": "🥞",
- "panda_face": "🐼",
- "paperclip": "📎",
- "parachute": "🪂",
- "parrot": "🦜",
- "part_alternation_mark": "〽",
- "partly_sunny": "⛅",
- "partying_face": "🥳",
- "passenger_ship": "🛳",
- "passport_control": "🛂",
- "pause_button": "⏸️",
- "peace": "☮",
- "peace_symbol": "☮️",
- "peach": "🍑",
- "peacock": "🦚",
- "peanuts": "🥜",
- "pear": "🍐",
- "pen": "🖊",
- "pencil": "📝",
- "pencil2": "✏",
- "penguin": "🐧",
- "pensive": "😔",
- "people_with_bunny_ears_partying": "👯",
- "people_wrestling": "🤼",
- "performing_arts": "🎭",
- "persevere": "😣",
- "person": "🧑",
- "person_biking": "🚴",
- "person_bouncing_ball": "⛹️",
- "person_bowing": "🙇",
- "person_cartwheeling": "🤸",
- "person_climbing": "🧗",
- "person_doing_cartwheel": "🤸",
- "person_facepalming": "🤦",
- "person_fencing": "🤺",
- "person_frowning": "🙍",
- "person_gesturing_no": "🙅",
- "person_gesturing_ok": "🙆",
- "person_getting_haircut": "💇",
- "person_getting_massage": "💆",
- "person_in_lotus_position": "🧘",
- "person_in_steamy_room": "🧖",
- "person_juggling": "🤹",
- "person_kneeling": "🧎",
- "person_mountain_biking": "🚵",
- "person_playing_handball": "🤾",
- "person_playing_water_polo": "🤽",
- "person_pouting": "🙎",
- "person_raising_hand": "🙋",
- "person_rowing_boat": "🚣",
- "person_running": "🏃",
- "person_shrugging": "🤷",
- "person_standing": "🧍",
- "person_surfing": "🏄",
- "person_swimming": "🏊",
- "person_tipping_hand": "💁",
- "person_walking": "🚶",
- "person_wearing_turban": "👳",
- "person_with_blond_hair": "👱",
- "person_with_pouting_face": "🙎",
- "petri_dish": "🧫",
- "pick": "⛏️",
- "pie": "🥧",
- "pig": "🐷",
- "pig2": "🐖",
- "pig_nose": "🐽",
- "pill": "💊",
- "pinching_hand": "🤏",
- "pineapple": "🍍",
- "ping_pong": "🏓",
- "pisces": "♓",
- "pizza": "🍕",
- "place_of_worship": "🛐",
- "play_button": "▶",
- "play_or_pause_button": "⏯️",
- "play_pause": "⏯",
- "pleading_face": "🥺",
- "point_down": "👇",
- "point_left": "👈",
- "point_right": "👉",
- "point_up": "☝️",
- "point_up_2": "👆",
- "police_car": "🚓",
- "police_officer": "👮",
- "poodle": "🐩",
- "poop": "💩",
- "popcorn": "🍿",
- "post_office": "🏣",
- "postal_horn": "📯",
- "postbox": "📮",
- "potable_water": "🚰",
- "potato": "🥔",
- "pouch": "👝",
- "poultry_leg": "🍗",
- "pound": "💷",
- "pouting_cat": "😾",
- "pray": "🙏",
- "prayer_beads": "📿",
- "pregnant_woman": "🤰",
- "pretzel": "🥨",
- "prince": "🤴",
- "princess": "👸",
- "printer": "🖨",
- "probing_cane": "🦯",
- "punch": "👊",
- "purple_circle": "🟣",
- "purple_heart": "💜",
- "purse": "👛",
- "pushpin": "📌",
- "put_litter_in_its_place": "🚮",
- "puzzle_piece": "🧩",
- "question": "❓",
- "rabbit": "🐰",
- "rabbit2": "🐇",
- "raccoon": "🦝",
- "racehorse": "🐎",
- "racing_car": "🏎",
- "radio": "📻",
- "radio_button": "🔘",
- "radioactive": "☢️",
- "rage": "😡",
- "railway_car": "🚃",
- "railway_track": "🛤",
- "rainbow": "🌈",
- "raised_back_of_hand": "🤚",
- "raised_hand": "✋",
- "raised_hands": "🙌",
- "raising_hand": "🙋",
- "ram": "🐏",
- "ramen": "🍜",
- "rat": "🐀",
- "razor": "🪒",
- "receipt": "🧾",
- "record_button": "⏺️",
- "recycle": "♻",
- "recycling_symbol": "♻️",
- "red_car": "🚗",
- "red_circle": "🔴",
- "red_envelope": "🧧",
- "red_hair": "🦰",
- "red_heart": "❤",
- "red_square": "🟥",
- "regional_indicator_a": "🇦",
- "regional_indicator_b": "🇧",
- "regional_indicator_c": "🇨",
- "regional_indicator_d": "🇩",
- "regional_indicator_e": "🇪",
- "regional_indicator_f": "🇫",
- "regional_indicator_g": "🇬",
- "regional_indicator_h": "🇭",
- "regional_indicator_i": "🇮",
- "regional_indicator_j": "🇯",
- "regional_indicator_k": "🇰",
- "regional_indicator_l": "🇱",
- "regional_indicator_m": "🇲",
- "regional_indicator_n": "🇳",
- "regional_indicator_o": "🇴",
- "regional_indicator_p": "🇵",
- "regional_indicator_q": "🇶",
- "regional_indicator_r": "🇷",
- "regional_indicator_s": "🇸",
- "regional_indicator_t": "🇹",
- "regional_indicator_u": "🇺",
- "regional_indicator_v": "🇻",
- "regional_indicator_w": "🇼",
- "regional_indicator_x": "🇽",
- "regional_indicator_y": "🇾",
- "regional_indicator_z": "🇿",
- "registered": "®",
- "relieved": "😌",
- "reminder_ribbon": "🎗",
- "repeat": "🔁",
- "repeat_one": "🔂",
- "rescue_worker’s_helmet": "⛑️",
- "restroom": "🚻",
- "reverse_button": "◀",
- "revolving_hearts": "💞",
- "rewind": "⏪",
- "rhino": "🦏",
- "rhinoceros": "🦏",
- "ribbon": "🎀",
- "rice": "🍚",
- "rice_ball": "🍙",
- "rice_cracker": "🍘",
- "rice_scene": "🎑",
- "right_arrow": "➡️",
- "right_arrow_curving_down": "⤵",
- "right_arrow_curving_left": "↩",
- "right_arrow_curving_up": "⤴",
- "right_facing_fist": "🤜",
- "rightfacing_fist": "🤜",
- "ring": "💍",
- "ringed_planet": "🪐",
- "robot": "🤖",
- "rocket": "🚀",
- "rofl": "🤣",
- "roll_of_paper": "🧻",
- "rolledup_newspaper": "🗞",
- "roller_coaster": "🎢",
- "rolling_eyes": "🙄",
- "rolling_on_the_floor_laughing": "🤣",
- "rooster": "🐓",
- "rose": "🌹",
- "rosette": "🏵",
- "rotating_light": "🚨",
- "round_pushpin": "📍",
- "rowboat": "🚣",
- "rugby_football": "🏉",
- "runner": "🏃",
- "running_shirt_with_sash": "🎽",
- "safety_pin": "🧷",
- "safety_vest": "🦺",
- "sagittarius": "♐",
- "sailboat": "⛵",
- "sake": "🍶",
- "salad": "🥗",
- "salt": "🧂",
- "sandal": "👡",
- "sandwich": "🥪",
- "santa": "🎅",
- "sari": "🥻",
- "satellite": "📡",
- "sauropod": "🦕",
- "saxophone": "🎷",
- "scales": "⚖",
- "scarf": "🧣",
- "school": "🏫",
- "school_satchel": "🎒",
- "scissors": "✂",
- "scooter": "🛴",
- "scorpion": "🦂",
- "scorpius": "♏",
- "scream": "😱",
- "scream_cat": "🙀",
- "scroll": "📜",
- "seat": "💺",
- "second_place": "🥈",
- "secret": "㊙",
- "see_no_evil": "🙈",
- "seedling": "🌱",
- "selfie": "🤳",
- "seven": "7⃣",
- "shallow_pan_of_food": "🥘",
- "shamrock": "☘️",
- "shark": "🦈",
- "shaved_ice": "🍧",
- "sheep": "🐑",
- "shell": "🐚",
- "shield": "🛡",
- "shinto_shrine": "⛩️",
- "ship": "🚢",
- "shirt": "👕",
- "shopping_bags": "🛍",
- "shopping_cart": "🛒",
- "shorts": "🩳",
- "shower": "🚿",
- "shrimp": "🦐",
- "shushing_face": "🤫",
- "sign_of_the_horns": "🤘",
- "signal_strength": "📶",
- "six": "6⃣",
- "six_pointed_star": "🔯",
- "skateboard": "🛹",
- "ski": "🎿",
- "skier": "⛷️",
- "skull": "💀",
- "skull_and_crossbones": "☠️",
- "skull_crossbones": "☠",
- "skunk": "🦨",
- "sled": "🛷",
- "sleeping": "😴",
- "sleeping_accommodation": "🛌",
- "sleepy": "😪",
- "slight_frown": "🙁",
- "slight_smile": "🙂",
- "slightly_frowning_face": "🙁",
- "slot_machine": "🎰",
- "sloth": "🦥",
- "small_airplane": "🛩",
- "small_blue_diamond": "🔹",
- "small_orange_diamond": "🔸",
- "small_red_triangle": "🔺",
- "small_red_triangle_down": "🔻",
- "smile": "😄",
- "smile_cat": "😸",
- "smiley": "😃",
- "smiley_cat": "😺",
- "smiling": "☺️",
- "smiling_face": "☺",
- "smiling_face_with_hearts": "🥰",
- "smiling_imp": "😈",
- "smirk": "😏",
- "smirk_cat": "😼",
- "smoking": "🚬",
- "snail": "🐌",
- "snake": "🐍",
- "sneezing_face": "🤧",
- "snowboarder": "🏂",
- "snowcapped_mountain": "🏔",
- "snowflake": "❄",
- "snowman": "⛄",
- "soap": "🧼",
- "sob": "😭",
- "soccer": "⚽",
- "socks": "🧦",
- "softball": "🥎",
- "soon": "🔜",
- "sos": "🆘",
- "sound": "🔉",
- "space_invader": "👾",
- "spade_suit": "♠️",
- "spades": "♠",
- "spaghetti": "🍝",
- "sparkle": "❇",
- "sparkler": "🎇",
- "sparkles": "✨",
- "sparkling_heart": "💖",
- "speak_no_evil": "🙊",
- "speaker": "🔈",
- "speaking_head": "🗣",
- "speech_balloon": "💬",
- "speech_left": "🗨",
- "speedboat": "🚤",
- "spider": "🕷",
- "spider_web": "🕸",
- "spiral_calendar": "🗓",
- "spiral_notepad": "🗒",
- "sponge": "🧽",
- "spoon": "🥄",
- "squid": "🦑",
- "stadium": "🏟",
- "star": "⭐",
- "star2": "🌟",
- "star_and_crescent": "☪️",
- "star_of_david": "✡",
- "star_struck": "🤩",
- "stars": "🌠",
- "starstruck": "🤩",
- "station": "🚉",
- "statue_of_liberty": "🗽",
- "steam_locomotive": "🚂",
- "stethoscope": "🩺",
- "stew": "🍲",
- "stop_button": "⏹️",
- "stopwatch": "⏱️",
- "straight_ruler": "📏",
- "strawberry": "🍓",
- "stuck_out_tongue": "😛",
- "stuck_out_tongue_closed_eyes": "😝",
- "stuck_out_tongue_winking_eye": "😜",
- "studio_microphone": "🎙",
- "stuffed_flatbread": "🥙",
- "sun": "☀",
- "sun_behind_large_cloud": "🌥",
- "sun_behind_rain_cloud": "🌦",
- "sun_behind_small_cloud": "🌤",
- "sun_with_face": "🌞",
- "sunflower": "🌻",
- "sunglasses": "😎",
- "sunny": "☀️",
- "sunrise": "🌅",
- "sunrise_over_mountains": "🌄",
- "superhero": "🦸",
- "supervillain": "🦹",
- "surfer": "🏄",
- "sushi": "🍣",
- "suspension_railway": "🚟",
- "swan": "🦢",
- "sweat": "😓",
- "sweat_drops": "💦",
- "sweat_smile": "😅",
- "sweet_potato": "🍠",
- "swimmer": "🏊",
- "symbols": "🔣",
- "synagogue": "🕍",
- "syringe": "💉",
- "t_rex": "🦖",
- "taco": "🌮",
- "tada": "🎉",
- "takeout_box": "🥡",
- "tanabata_tree": "🎋",
- "tangerine": "🍊",
- "taurus": "♉",
- "taxi": "🚕",
- "tea": "🍵",
- "teddy_bear": "🧸",
- "telephone": "☎",
- "telephone_receiver": "📞",
- "telescope": "🔭",
- "tennis": "🎾",
- "tent": "⛺",
- "test_tube": "🧪",
- "thermometer": "🌡",
- "thermometer_face": "🤒",
- "thinking": "🤔",
- "third_place": "🥉",
- "thought_balloon": "💭",
- "thread": "🧵",
- "three": "3⃣",
- "thumbsdown": "👎",
- "thumbsup": "👍",
- "ticket": "🎫",
- "tiger": "🐯",
- "tiger2": "🐅",
- "timer_clock": "⏲️",
- "tired_face": "😫",
- "tm": "™",
- "toilet": "🚽",
- "tokyo_tower": "🗼",
- "tomato": "🍅",
- "tone1": "🏻",
- "tone2": "🏼",
- "tone3": "🏽",
- "tone4": "🏾",
- "tone5": "🏿",
- "tongue": "👅",
- "toolbox": "🧰",
- "tooth": "🦷",
- "top": "🔝",
- "tophat": "🎩",
- "tornado": "🌪",
- "track_next": "⏭",
- "track_previous": "⏮",
- "trackball": "🖲",
- "tractor": "🚜",
- "trade_mark": "™️",
- "traffic_light": "🚥",
- "train": "🚋",
- "train2": "🚆",
- "tram": "🚊",
- "trex": "🦖",
- "triangular_flag_on_post": "🚩",
- "triangular_ruler": "📐",
- "trident": "🔱",
- "triumph": "😤",
- "trolleybus": "🚎",
- "trophy": "🏆",
- "tropical_drink": "🍹",
- "tropical_fish": "🐠",
- "truck": "🚚",
- "trumpet": "🎺",
- "tulip": "🌷",
- "tumbler_glass": "🥃",
- "turkey": "🦃",
- "turtle": "🐢",
- "tv": "📺",
- "twisted_rightwards_arrows": "🔀",
- "two": "2⃣",
- "two_hearts": "💕",
- "two_men_holding_hands": "👬",
- "two_women_holding_hands": "👭",
- "u5272": "🈹",
- "u5408": "🈴",
- "u55b6": "🈺",
- "u6307": "🈯",
- "u6708": "🈷",
- "u6709": "🈶",
- "u6e80": "🈵",
- "u7121": "🈚",
- "u7533": "🈸",
- "u7981": "🈲",
- "u7a7a": "🈳",
- "umbrella": "☔",
- "umbrella_on_ground": "⛱️",
- "unamused": "😒",
- "underage": "🔞",
- "unicorn": "🦄",
- "unlock": "🔓",
- "up": "🆙",
- "up_arrow": "⬆",
- "updown_arrow": "↕️",
- "upleft_arrow": "↖️",
- "upright_arrow": "↗",
- "upside_down": "🙃",
- "v": "✌️",
- "vampire": "🧛",
- "vertical_traffic_light": "🚦",
- "vhs": "📼",
- "vibration_mode": "📳",
- "victory_hand": "✌",
- "video_camera": "📹",
- "video_game": "🎮",
- "violin": "🎻",
- "virgo": "♍",
- "volcano": "🌋",
- "volleyball": "🏐",
- "vs": "🆚",
- "vulcan": "🖖",
- "vulcan_salute": "🖖",
- "waffle": "🧇",
- "walking": "🚶",
- "waning_crescent_moon": "🌘",
- "waning_gibbous_moon": "🌖",
- "warning": "⚠",
- "wastebasket": "🗑",
- "watch": "⌚",
- "water_buffalo": "🐃",
- "watermelon": "🍉",
- "wave": "👋",
- "wavy_dash": "〰️",
- "waxing_crescent_moon": "🌒",
- "waxing_gibbous_moon": "🌔",
- "wc": "🚾",
- "weary": "😩",
- "wedding": "💒",
- "weightlifter": "🏋",
- "whale": "🐳",
- "whale2": "🐋",
- "wheel_of_dharma": "☸️",
- "wheelchair": "♿",
- "white_check_mark": "✅",
- "white_circle": "⚪",
- "white_flower": "💮",
- "white_hair": "🦳",
- "white_heart": "🤍",
- "white_large_square": "⬜",
- "white_medium_small_square": "◽",
- "white_medium_square": "◻️",
- "white_small_square": "▫️",
- "white_square_button": "🔳",
- "wilted_flower": "🥀",
- "wilted_rose": "🥀",
- "wind_blowing_face": "🌬",
- "wind_chime": "🎐",
- "wine_glass": "🍷",
- "wink": "😉",
- "wolf": "🐺",
- "woman": "👩",
- "woman_with_headscarf": "🧕",
- "womans_clothes": "👚",
- "womans_hat": "👒",
- "womens": "🚺",
- "woozy_face": "🥴",
- "world_map": "🗺",
- "worried": "😟",
- "wrench": "🔧",
- "writing_hand": "✍️",
- "x": "❌",
- "yarn": "🧶",
- "yawning_face": "🥱",
- "yellow_circle": "🟡",
- "yellow_heart": "💛",
- "yellow_square": "🟨",
- "yen": "💴",
- "yin_yang": "☯️",
- "yoyo": "🪀",
- "yum": "😋",
- "zany_face": "🤪",
- "zap": "⚡",
- "zebra": "🦓",
- "zero": "0⃣",
- "zipper_mouth": "🤐",
- "zombie": "🧟",
- "zzz": "💤"
-}
-\ No newline at end of file
diff --git a/static/palettes/index.json b/static/palettes/index.json
@@ -0,0 +1,32 @@
+{
+ "pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
+ "pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
+ "classic-dark": {
+ "name": "Classic Dark",
+ "bg": "#161c20",
+ "fg": "#282e32",
+ "text": "#b9b9b9",
+ "link": "#baaa9c",
+ "cRed": "#d31014",
+ "cGreen": "#0fa00f",
+ "cBlue": "#0095ff",
+ "cOrange": "#ffa500"
+ },
+ "bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
+ "pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"],
+ "tomorrow-night": {
+ "name": "Tomorrow Night",
+ "bg": "#1d1f21",
+ "fg": "#373b41",
+ "link": "#81a2be",
+ "text": "#c5c8c6",
+ "cRed": "#cc6666",
+ "cBlue": "#8abeb7",
+ "cGreen": "#b5bd68",
+ "cOrange": "#de935f",
+ "_cYellow": "#f0c674",
+ "_cPurple": "#b294bb"
+ },
+ "ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
+ "monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ]
+}
diff --git a/static/pleromatan_apology.png b/static/pleromatan_apology.png
Binary files differ.
diff --git a/static/pleromatan_apology_fox.png b/static/pleromatan_apology_fox.png
Binary files differ.
diff --git a/static/pleromatan_orz.png b/static/pleromatan_orz.png
Binary files differ.
diff --git a/static/pleromatan_orz_fox.png b/static/pleromatan_orz_fox.png
Binary files differ.
diff --git a/static/styles.json b/static/styles.json
@@ -1,12 +1,6 @@
{
"pleroma-dark": "/static/themes/pleroma-dark.json",
"pleroma-light": "/static/themes/pleroma-light.json",
- "pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"],
- "classic-dark": [ "Classic Dark", "#161c20", "#282e32", "#b9b9b9", "#baaa9c", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
- "bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
- "ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
- "monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ],
-
"redmond-xx": "/static/themes/redmond-xx.json",
"redmond-xx-se": "/static/themes/redmond-xx-se.json",
"redmond-xxi": "/static/themes/redmond-xxi.json",
diff --git a/static/styles/Breezy DX.piss b/static/styles/Breezy DX.piss
@@ -0,0 +1,80 @@
+@meta {
+ name: Breezy DX;
+ author: HJ;
+ license: WTFPL;
+ website: ebin.club;
+}
+
+@palette.Dark {
+ bg: #292C32;
+ fg: #292C32;
+ text: #ffffff;
+ link: #1CA4F3;
+ accent: #1CA4F3;
+ cRed: #f41a51;
+ cBlue: #1CA4F3;
+ cGreen: #1af46e;
+ cOrange: #f4af1a;
+}
+
+@palette.Light {
+ bg: #EFF0F2;
+ fg: #EFF0F2;
+ text: #1B1F22;
+ underlay: #5d6086;
+ accent: #1CA4F3;
+ cBlue: #1CA4F3;
+ cRed: #f41a51;
+ cGreen: #1af46e;
+ cOrange: #f4af1a;
+ border: #d8e6f9;
+ link: #1CA4F3;
+}
+
+Root {
+ --badgeNotification: color | --cRed;
+ --buttonDefaultHoverGlow: shadow | inset 0 0 0 1 --accent / 1;
+ --buttonDefaultFocusGlow: shadow | inset 0 0 0 1 --accent / 1;
+ --buttonDefaultShadow: shadow | inset 0 0 0 1 --text / 0.35, 0 5 5 -5 #000000 / 0.35;
+ --buttonDefaultBevel: shadow | inset 0 14 14 -14 #FFFFFF / 0.1;
+ --buttonPressedBevel: shadow | inset 0 -20 20 -20 #000000 / 0.05;
+ --defaultInputBevel: shadow | inset 0 0 0 1 --text / 0.35;
+ --defaultInputHoverGlow: shadow | 0 0 0 1 --accent / 1;
+ --defaultInputFocusGlow: shadow | 0 0 0 1 --link / 1;
+}
+
+Button:disabled {
+ shadow: --buttonDefaultBevel, --buttonDefaultShadow
+}
+
+Button:hover {
+ shadow: --buttonDefaultHoverGlow, --buttonDefaultBevel, --buttonDefaultShadow
+}
+
+Button:toggled {
+ background: $blend(--bg 0.3 --accent)
+}
+
+Button:pressed {
+ background: $blend(--bg 0.8 --accent)
+}
+
+Button:pressed:toggled {
+ background: $blend(--bg 0.2 --accent)
+}
+
+Button:toggled:hover {
+ background: $blend(--bg 0.3 --accent)
+}
+
+Input {
+ shadow: --defaultInputBevel
+}
+
+PanelHeader {
+ shadow: inset 0 30 30 -30 #ffffff / 0.25
+}
+
+Tab:hover {
+ shadow: --buttonDefaultHoverGlow, --buttonDefaultBevel, --buttonDefaultShadow
+}
diff --git a/static/styles/Redmond DX.piss b/static/styles/Redmond DX.piss
@@ -0,0 +1,169 @@
+@meta {
+ name: Redmond DX;
+ author: HJ;
+ license: WTFPL;
+ website: ebin.club;
+}
+
+@palette.Modern {
+ bg: #D3CFC7;
+ fg: #092369;
+ text: #000000;
+ link: #0000FF;
+ accent: #A5C9F0;
+ cRed: #FF3000;
+ cBlue: #009EFF;
+ cGreen: #309E00;
+ cOrange: #FFCE00;
+}
+
+@palette.Classic {
+ bg: #BFBFBF;
+ fg: #000180;
+ text: #000000;
+ link: #0000FF;
+ accent: #A5C9F0;
+ cRed: #FF0000;
+ cBlue: #2E2ECE;
+ cGreen: #007E00;
+ cOrange: #CE8F5F;
+}
+
+@palette.Vapor {
+ bg: #F0ADCD;
+ fg: #bca4ee;
+ text: #602040;
+ link: #064745;
+ accent: #9DF7C8;
+ cRed: #86004a;
+ cBlue: #0e5663;
+ cGreen: #0a8b51;
+ cOrange: #787424;
+}
+
+Root {
+ --gradientColor: color | --accent;
+ --inputColor: color | #FFFFFF;
+ --bevelLight: color | $brightness(--bg 50);
+ --bevelDark: color | $brightness(--bg -20);
+ --bevelExtraDark: color | #404040;
+ --buttonDefaultBevel: shadow | $borderSide(--bevelExtraDark bottom-right 1 1), $borderSide(--bevelLight top-left 1 1), $borderSide(--bevelDark bottom-right 1 2);
+ --buttonPressedFocusedBevel: shadow | inset 0 0 0 1 #000000 / 1 #Outer , inset 0 0 0 2 --bevelExtraDark / 1 #inner;
+ --buttonPressedBevel: shadow | $borderSide(--bevelDark top-left 1 1), $borderSide(--bevelLight bottom-right 1 1), $borderSide(--bevelExtraDark top-left 1 2);
+ --defaultInputBevel: shadow | $borderSide(--bevelDark top-left 1 1), $borderSide(--bevelLight bottom-right 1 1), $borderSide(--bevelExtraDark top-left 1 2), $borderSide(--bg bottom-right 1 2);
+}
+
+Button:toggled {
+ background: --bg;
+ shadow: --buttonPressedBevel
+}
+
+Button:focused {
+ shadow: --buttonDefaultBevel, 0 0 0 1 #000000 / 1
+}
+
+Button:pressed {
+ shadow: --buttonPressedBevel
+}
+
+Button:hover {
+ shadow: --buttonDefaultBevel;
+ background: --bg
+}
+
+Button {
+ shadow: --buttonDefaultBevel;
+ background: --bg;
+ roundness: 0
+}
+
+Button:pressed:hover {
+ shadow: --buttonPressedBevel
+}
+
+Button:hover:pressed:focused {
+ shadow: --buttonPressedFocusedBevel
+}
+
+Button:pressed:focused {
+ shadow: --buttonPressedFocusedBevel
+}
+
+Button:toggled:pressed {
+ shadow: --buttonPressedFocusedBevel
+}
+
+Input {
+ background: $mod(--bg -80);
+ shadow: --defaultInputBevel;
+ roundness: 0
+}
+
+Input:focused {
+ shadow: inset 0 0 0 1 #000000 / 1, --defaultInputBevel
+}
+
+Input:focused:hover {
+ shadow: --defaultInputBevel
+}
+
+Input:focused:hover:disabled {
+ shadow: --defaultInputBevel
+}
+
+Input:hover {
+ shadow: --defaultInputBevel
+}
+
+Input:disabled {
+ shadow: --defaultInputBevel
+}
+
+Panel {
+ shadow: --buttonDefaultBevel;
+ roundness: 0
+}
+
+PanelHeader {
+ shadow: inset -1100 0 1000 -1000 --gradientColor / 1 #Gradient ;
+ background: --fg
+}
+
+Tab:hover {
+ background: --bg;
+ shadow: --buttonDefaultBevel
+}
+
+Tab:active {
+ background: --bg
+}
+
+Tab:active:hover {
+ background: --bg;
+ shadow: --defaultButtonBevel
+}
+
+Tab:active:hover:disabled {
+ background: --bg
+}
+
+Tab:hover:disabled {
+ background: --bg
+}
+
+Tab:disabled {
+ background: --bg
+}
+
+Tab {
+ background: --bg;
+ shadow: --buttonDefaultBevel
+}
+
+Tab:hover:active {
+ shadow: --buttonDefaultBevel
+}
+
+TopBar Link {
+ textColor: #ffffff
+}
diff --git a/static/styles/index.json b/static/styles/index.json
@@ -0,0 +1,4 @@
+{
+ "RedmondDX": "/static/styles/Redmond DX.piss",
+ "BreezyDX": "/static/styles/Breezy DX.piss"
+}
diff --git a/test/e2e/custom-assertions/elementCount.js b/test/e2e/custom-assertions/elementCount.js
@@ -16,7 +16,7 @@ exports.assertion = function (selector, count) {
return res.value
}
this.command = function (cb) {
- var self = this
+ const self = this
return this.api.execute(function (selector) {
return document.querySelectorAll(selector).length
}, [selector], function (res) {
diff --git a/test/e2e/nightwatch.conf.js b/test/e2e/nightwatch.conf.js
@@ -1,45 +1,45 @@
require('@babel/register')
-var config = require('../../config')
+const config = require('../../config')
// http://nightwatchjs.org/guide#settings-file
module.exports = {
- 'src_folders': ['test/e2e/specs'],
- 'output_folder': 'test/e2e/reports',
- 'custom_assertions_path': ['test/e2e/custom-assertions'],
+ src_folders: ['test/e2e/specs'],
+ output_folder: 'test/e2e/reports',
+ custom_assertions_path: ['test/e2e/custom-assertions'],
- 'selenium': {
- 'start_process': true,
- 'server_path': 'node_modules/selenium-server/lib/runner/selenium-server-standalone-2.53.1.jar',
- 'host': '127.0.0.1',
- 'port': 4444,
- 'cli_args': {
+ selenium: {
+ start_process: true,
+ server_path: require('selenium-server').path,
+ host: '127.0.0.1',
+ port: 4444,
+ cli_args: {
'webdriver.chrome.driver': require('chromedriver').path
}
},
- 'test_settings': {
- 'default': {
- 'selenium_port': 4444,
- 'selenium_host': 'localhost',
- 'silent': true,
- 'globals': {
- 'devServerURL': 'http://localhost:' + (process.env.PORT || config.dev.port)
+ test_settings: {
+ default: {
+ selenium_port: 4444,
+ selenium_host: 'localhost',
+ silent: true,
+ globals: {
+ devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
}
},
- 'chrome': {
- 'desiredCapabilities': {
- 'browserName': 'chrome',
- 'javascriptEnabled': true,
- 'acceptSslCerts': true
+ chrome: {
+ desiredCapabilities: {
+ browserName: 'chrome',
+ javascriptEnabled: true,
+ acceptSslCerts: true
}
},
- 'firefox': {
- 'desiredCapabilities': {
- 'browserName': 'firefox',
- 'javascriptEnabled': true,
- 'acceptSslCerts': true
+ firefox: {
+ desiredCapabilities: {
+ browserName: 'firefox',
+ javascriptEnabled: true,
+ acceptSslCerts: true
}
}
}
diff --git a/test/e2e/runner.js b/test/e2e/runner.js
@@ -1,6 +1,6 @@
// 1. start the dev server using production config
process.env.NODE_ENV = 'testing'
-var server = require('../../build/dev-server.js')
+const server = require('../../build/dev-server.js')
// 2. run the nightwatch test suite against it
// to run in additional browsers:
@@ -9,7 +9,7 @@ var server = require('../../build/dev-server.js')
// or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
// For more information on Nightwatch's config file, see
// http://nightwatchjs.org/guide#settings-file
-var opts = process.argv.slice(2)
+let opts = process.argv.slice(2)
if (opts.indexOf('--config') === -1) {
opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])
}
@@ -17,8 +17,8 @@ if (opts.indexOf('--env') === -1) {
opts = opts.concat(['--env', 'chrome'])
}
-var spawn = require('cross-spawn')
-var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
+const spawn = require('cross-spawn')
+const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
runner.on('exit', function (code) {
server.close()
diff --git a/test/unit/karma.conf.js b/test/unit/karma.conf.js
@@ -4,24 +4,19 @@
// https://github.com/webpack/karma-webpack
// var path = require('path')
-var merge = require('webpack-merge')
-var HtmlWebpackPlugin = require('html-webpack-plugin')
-var baseConfig = require('../../build/webpack.base.conf')
-var utils = require('../../build/utils')
-var webpack = require('webpack')
+const merge = require('webpack-merge')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const baseConfig = require('../../build/webpack.base.conf')
+const utils = require('../../build/utils')
+const webpack = require('webpack')
// var projectRoot = path.resolve(__dirname, '../../')
-var webpackConfig = merge(baseConfig, {
+const webpackConfig = merge(baseConfig, {
// use inline sourcemap for karma-sourcemap-loader
module: {
rules: utils.styleLoaders()
},
- devtool: '#inline-source-map',
- // vue: {
- // loaders: {
- // js: 'isparta'
- // }
- // },
+ devtool: 'inline-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': require('../../config/test.env')
@@ -37,22 +32,6 @@ var webpackConfig = merge(baseConfig, {
// no need for app entry during tests
delete webpackConfig.entry
-// make sure isparta loader is applied before eslint
-// webpackConfig.module.preLoaders = webpackConfig.module.preLoaders || []
-// webpackConfig.module.preLoaders.unshift({
-// test: /\.js$/,
-// loader: 'isparta',
-// include: path.resolve(projectRoot, 'src')
-// })
-
-// // only apply babel for test files when using isparta
-// webpackConfig.module.loaders.some(function (loader, i) {
-// if (loader.loader === 'babel') {
-// loader.include = path.resolve(projectRoot, 'test/unit')
-// return true
-// }
-// })
-
module.exports = function (config) {
config.set({
// to run in additional browsers:
@@ -63,7 +42,7 @@ module.exports = function (config) {
frameworks: ['mocha', 'sinon-chai'],
reporters: ['mocha'],
customLaunchers: {
- 'FirefoxHeadless': {
+ FirefoxHeadless: {
base: 'Firefox',
flags: [
'-headless'
diff --git a/test/unit/specs/boot/routes.spec.js b/test/unit/specs/boot/routes.spec.js
@@ -19,6 +19,7 @@ describe('routes', () => {
const matchedComponents = router.currentRoute.value.matched
+ // eslint-disable-next-line no-prototype-builtins
expect(matchedComponents[0].components.default.components.hasOwnProperty('Timeline')).to.eql(true)
})
@@ -27,6 +28,7 @@ describe('routes', () => {
const matchedComponents = router.currentRoute.value.matched
+ // eslint-disable-next-line no-prototype-builtins
expect(matchedComponents[0].components.default.components.hasOwnProperty('UserCard')).to.eql(true)
})
@@ -35,6 +37,31 @@ describe('routes', () => {
const matchedComponents = router.currentRoute.value.matched
+ // eslint-disable-next-line no-prototype-builtins
expect(matchedComponents[0].components.default.components.hasOwnProperty('UserCard')).to.eql(true)
})
+
+ it('list view', async () => {
+ await router.push('/lists')
+
+ const matchedComponents = router.currentRoute.value.matched
+
+ expect(Object.prototype.hasOwnProperty.call(matchedComponents[0].components.default.components, 'ListsCard')).to.eql(true)
+ })
+
+ it('list timeline', async () => {
+ await router.push('/lists/1')
+
+ const matchedComponents = router.currentRoute.value.matched
+
+ expect(Object.prototype.hasOwnProperty.call(matchedComponents[0].components.default.components, 'Timeline')).to.eql(true)
+ })
+
+ it('list edit', async () => {
+ await router.push('/lists/1/edit')
+
+ const matchedComponents = router.currentRoute.value.matched
+
+ expect(Object.prototype.hasOwnProperty.call(matchedComponents[0].components.default.components, 'BasicUserCard')).to.eql(true)
+ })
})
diff --git a/test/unit/specs/components/emoji_input.spec.js b/test/unit/specs/components/emoji_input.spec.js
@@ -14,7 +14,8 @@ const generateInput = (value, padEmoji = true) => {
padEmoji
}
}
- }
+ },
+ $t: (msg) => msg
},
stubs: {
FAIcon: true
@@ -29,7 +30,7 @@ const generateInput = (value, padEmoji = true) => {
modelValue: value
},
slots: {
- 'default': () => h('input', '')
+ default: () => h('input', '')
}
})
return wrapper
diff --git a/test/unit/specs/components/gallery.spec.js b/test/unit/specs/components/gallery.spec.js
@@ -0,0 +1,276 @@
+import Gallery from 'src/components/gallery/gallery.vue'
+
+describe('Gallery', () => {
+ let local
+
+ it('attachments is falsey', () => {
+ local = { attachments: false }
+ expect(Gallery.computed.rows.call(local)).to.eql([])
+
+ local = { attachments: null }
+ expect(Gallery.computed.rows.call(local)).to.eql([])
+
+ local = { attachments: undefined }
+ expect(Gallery.computed.rows.call(local)).to.eql([])
+ })
+
+ it('no attachments', () => {
+ local = { attachments: [] }
+ expect(Gallery.computed.rows.call(local)).to.eql([])
+ })
+
+ it('one audio attachment', () => {
+ local = {
+ attachments: [
+ { mimetype: 'audio/mpeg' }
+ ]
+ }
+
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { audio: true, items: [{ mimetype: 'audio/mpeg' }] }
+ ])
+ })
+
+ it('one image attachment', () => {
+ local = {
+ attachments: [
+ { mimetype: 'image/png' }
+ ]
+ }
+
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { items: [{ mimetype: 'image/png' }] }
+ ])
+ })
+
+ it('one audio attachment and one image attachment', () => {
+ local = {
+ attachments: [
+ { mimetype: 'audio/mpeg' },
+ { mimetype: 'image/png' }
+ ]
+ }
+
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { audio: true, items: [{ mimetype: 'audio/mpeg' }] },
+ { items: [{ mimetype: 'image/png' }] }
+ ])
+ })
+
+ it('has "size" key set to "hide"', () => {
+ let local
+ local = {
+ attachments: [
+ { mimetype: 'audio/mpeg' }
+ ],
+ size: 'hide'
+ }
+
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { minimal: true, items: [{ mimetype: 'audio/mpeg' }] }
+ ])
+
+ local = {
+ attachments: [
+ { mimetype: 'image/jpg' },
+ { mimetype: 'image/png' },
+ { mimetype: 'image/jpg' },
+ { mimetype: 'audio/mpeg' },
+ { mimetype: 'image/png' },
+ { mimetype: 'audio/mpeg' },
+ { mimetype: 'image/jpg' },
+ { mimetype: 'image/png' },
+ { mimetype: 'image/jpg' }
+ ],
+ size: 'hide'
+ }
+
+ // When defining `size: hide`, the `items` aren't
+ // grouped and `audio` isn't set
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { minimal: true, items: [{ mimetype: 'image/jpg' }] },
+ { minimal: true, items: [{ mimetype: 'image/png' }] },
+ { minimal: true, items: [{ mimetype: 'image/jpg' }] },
+ { minimal: true, items: [{ mimetype: 'audio/mpeg' }] },
+ { minimal: true, items: [{ mimetype: 'image/png' }] },
+ { minimal: true, items: [{ mimetype: 'audio/mpeg' }] },
+ { minimal: true, items: [{ mimetype: 'image/jpg' }] },
+ { minimal: true, items: [{ mimetype: 'image/png' }] },
+ { minimal: true, items: [{ mimetype: 'image/jpg' }] }
+ ])
+ })
+
+ // types other than image or audio should be `minimal`
+ it('non-image/audio', () => {
+ let local
+ local = {
+ attachments: [
+ { mimetype: 'plain/text' }
+ ]
+ }
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { minimal: true, items: [{ mimetype: 'plain/text' }] }
+ ])
+
+ // No grouping of non-image/audio items
+ local = {
+ attachments: [
+ { mimetype: 'plain/text' },
+ { mimetype: 'plain/text' },
+ { mimetype: 'plain/text' }
+ ]
+ }
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { minimal: true, items: [{ mimetype: 'plain/text' }] },
+ { minimal: true, items: [{ mimetype: 'plain/text' }] },
+ { minimal: true, items: [{ mimetype: 'plain/text' }] }
+ ])
+
+ local = {
+ attachments: [
+ { mimetype: 'image/png' },
+ { mimetype: 'plain/text' },
+ { mimetype: 'image/jpg' },
+ { mimetype: 'audio/mpeg' }
+ ]
+ }
+ // NOTE / TODO: When defining `size: hide`, the `items` aren't
+ // grouped and `audio` isn't set
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { items: [{ mimetype: 'image/png' }] },
+ { minimal: true, items: [{ mimetype: 'plain/text' }] },
+ { items: [{ mimetype: 'image/jpg' }] },
+ { audio: true, items: [{ mimetype: 'audio/mpeg' }] }
+ ])
+ })
+
+ it('mixed attachments', () => {
+ local = {
+ attachments: [
+ { mimetype: 'audio/mpeg' },
+ { mimetype: 'image/png' },
+ { mimetype: 'audio/mpeg' },
+ { mimetype: 'image/jpg' },
+ { mimetype: 'image/png' },
+ { mimetype: 'image/jpg' },
+ { mimetype: 'image/jpg' }
+ ]
+ }
+
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { audio: true, items: [{ mimetype: 'audio/mpeg' }] },
+ { items: [{ mimetype: 'image/png' }] },
+ { audio: true, items: [{ mimetype: 'audio/mpeg' }] },
+ { items: [{ mimetype: 'image/jpg' }, { mimetype: 'image/png' }, { mimetype: 'image/jpg' }, { mimetype: 'image/jpg' }] }
+ ])
+
+ local = {
+ attachments: [
+ { mimetype: 'image/jpg' },
+ { mimetype: 'image/png' },
+ { mimetype: 'image/jpg' },
+ { mimetype: 'image/jpg' },
+ { mimetype: 'audio/mpeg' },
+ { mimetype: 'image/png' },
+ { mimetype: 'audio/mpeg' }
+ ]
+ }
+
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { items: [{ mimetype: 'image/jpg' }, { mimetype: 'image/png' }, { mimetype: 'image/jpg' }] },
+ { items: [{ mimetype: 'image/jpg' }] },
+ { audio: true, items: [{ mimetype: 'audio/mpeg' }] },
+ { items: [{ mimetype: 'image/png' }] },
+ { audio: true, items: [{ mimetype: 'audio/mpeg' }] }
+ ])
+
+ local = {
+ attachments: [
+ { mimetype: 'image/jpg' },
+ { mimetype: 'image/png' },
+ { mimetype: 'image/jpg' },
+ { mimetype: 'image/jpg' },
+ { mimetype: 'image/png' },
+ { mimetype: 'image/png' },
+ { mimetype: 'image/jpg' }
+ ]
+ }
+
+ // Group by three-per-row, unless there's one dangling, then stick it on the end of the last row
+ // https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1785#note_98514
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { items: [{ mimetype: 'image/jpg' }, { mimetype: 'image/png' }, { mimetype: 'image/jpg' }] },
+ { items: [{ mimetype: 'image/jpg' }, { mimetype: 'image/png' }, { mimetype: 'image/png' }, { mimetype: 'image/jpg' }] }
+ ])
+
+ local = {
+ attachments: [
+ { mimetype: 'image/jpg' },
+ { mimetype: 'image/png' },
+ { mimetype: 'image/jpg' },
+ { mimetype: 'image/jpg' },
+ { mimetype: 'image/png' },
+ { mimetype: 'image/png' },
+ { mimetype: 'image/jpg' },
+ { mimetype: 'image/png' }
+ ]
+ }
+
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { items: [{ mimetype: 'image/jpg' }, { mimetype: 'image/png' }, { mimetype: 'image/jpg' }] },
+ { items: [{ mimetype: 'image/jpg' }, { mimetype: 'image/png' }, { mimetype: 'image/png' }] },
+ { items: [{ mimetype: 'image/jpg' }, { mimetype: 'image/png' }] }
+ ])
+ })
+
+ it('does not do grouping when grid is set', () => {
+ const attachments = [
+ { mimetype: 'audio/mpeg' },
+ { mimetype: 'image/png' },
+ { mimetype: 'audio/mpeg' },
+ { mimetype: 'image/jpg' },
+ { mimetype: 'image/png' },
+ { mimetype: 'image/jpg' },
+ { mimetype: 'image/jpg' }
+ ]
+
+ local = { grid: true, attachments }
+
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { grid: true, items: attachments }
+ ])
+ })
+
+ it('limit is set', () => {
+ const attachments = [
+ { mimetype: 'audio/mpeg' },
+ { mimetype: 'image/png' },
+ { mimetype: 'image/jpg' },
+ { mimetype: 'audio/mpeg' },
+ { mimetype: 'image/jpg' }
+ ]
+
+ let local
+ local = { attachments, limit: 2 }
+
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { audio: true, items: [{ mimetype: 'audio/mpeg' }] },
+ { items: [{ mimetype: 'image/png' }] }
+ ])
+
+ local = { attachments, limit: 3 }
+
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { audio: true, items: [{ mimetype: 'audio/mpeg' }] },
+ { items: [{ mimetype: 'image/png' }, { mimetype: 'image/jpg' }] }
+ ])
+
+ local = { attachments, limit: 4 }
+
+ expect(Gallery.computed.rows.call(local)).to.eql([
+ { audio: true, items: [{ mimetype: 'audio/mpeg' }] },
+ { items: [{ mimetype: 'image/png' }, { mimetype: 'image/jpg' }] },
+ { audio: true, items: [{ mimetype: 'audio/mpeg' }] }
+ ])
+ })
+})
diff --git a/test/unit/specs/components/rich_content.spec.js b/test/unit/specs/components/rich_content.spec.js
@@ -4,16 +4,26 @@ import RichContent from 'src/components/rich_content/rich_content.jsx'
const attentions = []
const global = {
mocks: {
- '$store': null
+ $store: {
+ state: {},
+ getters: {
+ mergedConfig: () => ({
+ mentionLinkShowTooltip: true
+ }),
+ findUserByUrl: () => null
+ }
+ }
},
stubs: {
FAIcon: true
}
}
-const makeMention = (who) => {
+const makeMention = (who, noClass) => {
attentions.push({ statusnet_profile_url: `https://fake.tld/@${who}` })
- return `<span class="h-card"><a class="u-url mention" href="https://fake.tld/@${who}">@<span>${who}</span></a></span>`
+ return noClass
+ ? `<span><a href="https://fake.tld/@${who}">@<span>${who}</span></a></span>`
+ : `<span class="h-card"><a class="u-url mention" href="https://fake.tld/@${who}">@<span>${who}</span></a></span>`
}
const p = (...data) => `<p>${data.join('')}</p>`
const compwrap = (...data) => `<span class="RichContent">${data.join('')}</span>`
@@ -131,8 +141,18 @@ describe('RichContent', () => {
].join(''),
[
makeMention('John'),
- makeMention('Josh'),
- makeMention('Jeremy')
+ makeMention('Josh'), makeMention('Jeremy')
+ ].join('')
+ ].join('\n')
+ const strippedHtml = [
+ [
+ makeMention('Jack', true),
+ 'let\'s meet up with ',
+ makeMention('Janet', true)
+ ].join(''),
+ [
+ makeMention('John', true),
+ makeMention('Josh', true), makeMention('Jeremy', true)
].join('')
].join('\n')
@@ -147,7 +167,7 @@ describe('RichContent', () => {
}
})
- expect(wrapper.html()).to.eql(compwrap(html))
+ expect(wrapper.html()).to.eql(compwrap(strippedHtml))
})
it('Adds greentext and cyantext to the post', () => {
@@ -349,7 +369,6 @@ describe('RichContent', () => {
p(
'<span class="MentionsLine">',
'<span class="MentionLink mention-link">',
- '<!-- eslint-disable vue/no-v-html -->',
'<a href="lol" class="original" target="_blank">',
'<span>',
'https://</span>',
@@ -358,10 +377,7 @@ describe('RichContent', () => {
'<span>',
'</span>',
'</a>',
- '<!-- eslint-enable vue/no-v-html -->',
- '<!--v-if-->', // v-if placeholder, mentionlink's "new" (i.e. rich) display
'</span>',
- '<!--v-if-->', // v-if placeholder, mentionsline's extra mentions and stuff
'</span>'
),
p(
@@ -380,7 +396,7 @@ describe('RichContent', () => {
}
})
- expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(expected))
+ expect(wrapper.html().replace(/\n/g, '').replace(/<!--.*?-->/g, '')).to.eql(compwrap(expected))
})
it('rich contents of nested mentions are handled properly', () => {
@@ -409,10 +425,9 @@ describe('RichContent', () => {
'Testing'
].join('')
const expected = [
- '<span class="poast-style">',
+ '<span>',
'<span class="MentionsLine">',
'<span class="MentionLink mention-link">',
- '<!-- eslint-disable vue/no-v-html -->',
'<a href="lol" class="original" target="_blank">',
'<span>',
'https://</span>',
@@ -421,11 +436,8 @@ describe('RichContent', () => {
'<span>',
'</span>',
'</a>',
- '<!-- eslint-enable vue/no-v-html -->',
- '<!--v-if-->', // v-if placeholder, mentionlink's "new" (i.e. rich) display
'</span>',
'<span class="MentionLink mention-link">',
- '<!-- eslint-disable vue/no-v-html -->',
'<a href="lol" class="original" target="_blank">',
'<span>',
'https://</span>',
@@ -434,10 +446,7 @@ describe('RichContent', () => {
'<span>',
'</span>',
'</a>',
- '<!-- eslint-enable vue/no-v-html -->',
- '<!--v-if-->', // v-if placeholder, mentionlink's "new" (i.e. rich) display
'</span>',
- '<!--v-if-->', // v-if placeholder, mentionsline's extra mentions and stuff
'</span>',
' ',
'</span>',
@@ -455,7 +464,7 @@ describe('RichContent', () => {
}
})
- expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(expected))
+ expect(wrapper.html().replace(/\n/g, '').replace(/<!--.*?-->/g, '')).to.eql(compwrap(expected))
})
it('rich contents of a link are handled properly', () => {
diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js
@@ -15,6 +15,7 @@ const actions = {
const testGetters = {
findUser: state => getters.findUser(state.users),
+ findUserByName: state => getters.findUserByName(state.users),
relationship: state => getters.relationship(state.users),
mergedConfig: state => ({
colors: '',
@@ -95,6 +96,7 @@ const externalProfileStore = createStore({
credentials: ''
},
usersObject: { 100: extUser },
+ usersByNameObject: {},
users: [extUser],
relationships: {}
}
@@ -163,7 +165,8 @@ const localProfileStore = createStore({
currentUser: {
credentials: ''
},
- usersObject: { 100: localUser, 'testuser': localUser },
+ usersObject: { 100: localUser },
+ usersByNameObject: { testuser: localUser },
users: [localUser],
relationships: {}
}
@@ -175,7 +178,7 @@ describe.skip('UserProfile', () => {
it('renders external profile', () => {
const wrapper = mount(UserProfile, {
global: {
- plugins: [ externalProfileStore ],
+ plugins: [externalProfileStore],
mocks: {
$route: {
params: { id: 100 },
@@ -192,7 +195,7 @@ describe.skip('UserProfile', () => {
it('renders local profile', () => {
const wrapper = mount(UserProfile, {
global: {
- plugins: [ localProfileStore ],
+ plugins: [localProfileStore],
mocks: {
$route: {
params: { name: 'testUser' },
diff --git a/test/unit/specs/modules/lists.spec.js b/test/unit/specs/modules/lists.spec.js
@@ -0,0 +1,83 @@
+import { cloneDeep } from 'lodash'
+import { defaultState, mutations, getters } from '../../../../src/modules/lists.js'
+
+describe('The lists module', () => {
+ describe('mutations', () => {
+ it('updates array of all lists', () => {
+ const state = cloneDeep(defaultState)
+ const list = { id: '1', title: 'testList' }
+
+ mutations.setLists(state, [list])
+ expect(state.allLists).to.have.length(1)
+ expect(state.allLists).to.eql([list])
+ })
+
+ it('adds a new list with a title, updating the title for existing lists', () => {
+ const state = cloneDeep(defaultState)
+ const list = { id: '1', title: 'testList' }
+ const modList = { id: '1', title: 'anotherTestTitle' }
+
+ mutations.setList(state, { listId: list.id, title: list.title })
+ expect(state.allListsObject[list.id]).to.eql({ title: list.title, accountIds: [] })
+ expect(state.allLists).to.have.length(1)
+ expect(state.allLists[0]).to.eql(list)
+
+ mutations.setList(state, { listId: modList.id, title: modList.title })
+ expect(state.allListsObject[modList.id]).to.eql({ title: modList.title, accountIds: [] })
+ expect(state.allLists).to.have.length(1)
+ expect(state.allLists[0]).to.eql(modList)
+ })
+
+ it('adds a new list with an array of IDs, updating the IDs for existing lists', () => {
+ const state = cloneDeep(defaultState)
+ const list = { id: '1', accountIds: ['1', '2', '3'] }
+ const modList = { id: '1', accountIds: ['3', '4', '5'] }
+
+ mutations.setListAccounts(state, { listId: list.id, accountIds: list.accountIds })
+ expect(state.allListsObject[list.id]).to.eql({ accountIds: list.accountIds })
+
+ mutations.setListAccounts(state, { listId: modList.id, accountIds: modList.accountIds })
+ expect(state.allListsObject[modList.id]).to.eql({ accountIds: modList.accountIds })
+ })
+
+ it('deletes a list', () => {
+ const state = {
+ allLists: [{ id: '1', title: 'testList' }],
+ allListsObject: {
+ 1: { title: 'testList', accountIds: ['1', '2', '3'] }
+ }
+ }
+ const listId = '1'
+
+ mutations.deleteList(state, { listId })
+ expect(state.allLists).to.have.length(0)
+ expect(state.allListsObject).to.eql({})
+ })
+ })
+
+ describe('getters', () => {
+ it('returns list title', () => {
+ const state = {
+ allLists: [{ id: '1', title: 'testList' }],
+ allListsObject: {
+ 1: { title: 'testList', accountIds: ['1', '2', '3'] }
+ }
+ }
+ const id = '1'
+
+ expect(getters.findListTitle(state)(id)).to.eql('testList')
+ })
+
+ it('returns list accounts', () => {
+ const state = {
+ allLists: [{ id: '1', title: 'testList' }],
+ allListsObject: {
+ 1: { title: 'testList', accountIds: ['1', '2', '3'] }
+ }
+ }
+ const id = '1'
+
+ expect(getters.findListAccounts(state)(id)).to.eql(['1', '2', '3'])
+ })
+ })
+})
diff --git a/test/unit/specs/modules/serverSideStorage.spec.js b/test/unit/specs/modules/serverSideStorage.spec.js
@@ -0,0 +1,338 @@
+import { cloneDeep } from 'lodash'
+
+import {
+ VERSION,
+ COMMAND_TRIM_FLAGS,
+ COMMAND_TRIM_FLAGS_AND_RESET,
+ _moveItemInArray,
+ _getRecentData,
+ _getAllFlags,
+ _mergeFlags,
+ _mergePrefs,
+ _resetFlags,
+ mutations,
+ defaultState,
+ newUserFlags
+} from 'src/modules/serverSideStorage.js'
+
+describe('The serverSideStorage module', () => {
+ describe('mutations', () => {
+ describe('setServerSideStorage', () => {
+ const { setServerSideStorage } = mutations
+ const user = {
+ created_at: new Date('1999-02-09'),
+ storage: {}
+ }
+
+ it('should initialize storage if none present', () => {
+ const state = cloneDeep(defaultState)
+ setServerSideStorage(state, user)
+ expect(state.cache._version).to.eql(VERSION)
+ expect(state.cache._timestamp).to.be.a('number')
+ expect(state.cache.flagStorage).to.eql(defaultState.flagStorage)
+ expect(state.cache.prefsStorage).to.eql(defaultState.prefsStorage)
+ })
+
+ it('should initialize storage with proper flags for new users if none present', () => {
+ const state = cloneDeep(defaultState)
+ setServerSideStorage(state, { ...user, created_at: new Date() })
+ expect(state.cache._version).to.eql(VERSION)
+ expect(state.cache._timestamp).to.be.a('number')
+ expect(state.cache.flagStorage).to.eql(newUserFlags)
+ expect(state.cache.prefsStorage).to.eql(defaultState.prefsStorage)
+ })
+
+ it('should merge flags even if remote timestamp is older', () => {
+ const state = {
+ ...cloneDeep(defaultState),
+ cache: {
+ _timestamp: Date.now(),
+ _version: VERSION,
+ ...cloneDeep(defaultState)
+ }
+ }
+ setServerSideStorage(
+ state,
+ {
+ ...user,
+ storage: {
+ _timestamp: 123,
+ _version: VERSION,
+ flagStorage: {
+ ...defaultState.flagStorage,
+ updateCounter: 1
+ },
+ prefsStorage: {
+ ...defaultState.prefsStorage
+ }
+ }
+ }
+ )
+ expect(state.cache.flagStorage).to.eql({
+ ...defaultState.flagStorage,
+ updateCounter: 1
+ })
+ })
+
+ it('should reset local timestamp to remote if contents are the same', () => {
+ const state = {
+ ...cloneDeep(defaultState),
+ cache: null
+ }
+ setServerSideStorage(
+ state,
+ {
+ ...user,
+ storage: {
+ _timestamp: 123,
+ _version: VERSION,
+ flagStorage: {
+ ...defaultState.flagStorage,
+ updateCounter: 999
+ }
+ }
+ }
+ )
+ expect(state.cache._timestamp).to.eql(123)
+ expect(state.flagStorage.updateCounter).to.eql(999)
+ expect(state.cache.flagStorage.updateCounter).to.eql(999)
+ })
+
+ it('should remote version if local missing', () => {
+ const state = cloneDeep(defaultState)
+ setServerSideStorage(state, user)
+ expect(state.cache._version).to.eql(VERSION)
+ expect(state.cache._timestamp).to.be.a('number')
+ expect(state.cache.flagStorage).to.eql(defaultState.flagStorage)
+ })
+ })
+ describe('setPreference', () => {
+ const { setPreference, updateCache, addCollectionPreference, removeCollectionPreference } = mutations
+
+ it('should set preference and update journal log accordingly', () => {
+ const state = cloneDeep(defaultState)
+ setPreference(state, { path: 'simple.testing', value: 1 })
+ expect(state.prefsStorage.simple.testing).to.eql(1)
+ expect(state.prefsStorage._journal.length).to.eql(1)
+ expect(state.prefsStorage._journal[0]).to.eql({
+ path: 'simple.testing',
+ operation: 'set',
+ args: [1],
+ // should have A timestamp, we don't really care what it is
+ timestamp: state.prefsStorage._journal[0].timestamp
+ })
+ })
+
+ it('should keep journal to a minimum', () => {
+ const state = cloneDeep(defaultState)
+ setPreference(state, { path: 'simple.testing', value: 1 })
+ setPreference(state, { path: 'simple.testing', value: 2 })
+ addCollectionPreference(state, { path: 'collections.testing', value: 2 })
+ removeCollectionPreference(state, { path: 'collections.testing', value: 2 })
+ updateCache(state, { username: 'test' })
+ expect(state.prefsStorage.simple.testing).to.eql(2)
+ expect(state.prefsStorage.collections.testing).to.eql([])
+ expect(state.prefsStorage._journal.length).to.eql(2)
+ expect(state.prefsStorage._journal[0]).to.eql({
+ path: 'simple.testing',
+ operation: 'set',
+ args: [2],
+ // should have A timestamp, we don't really care what it is
+ timestamp: state.prefsStorage._journal[0].timestamp
+ })
+ expect(state.prefsStorage._journal[1]).to.eql({
+ path: 'collections.testing',
+ operation: 'removeFromCollection',
+ args: [2],
+ // should have A timestamp, we don't really care what it is
+ timestamp: state.prefsStorage._journal[1].timestamp
+ })
+ })
+
+ it('should remove duplicate entries from journal', () => {
+ const state = cloneDeep(defaultState)
+ setPreference(state, { path: 'simple.testing', value: 1 })
+ setPreference(state, { path: 'simple.testing', value: 1 })
+ addCollectionPreference(state, { path: 'collections.testing', value: 2 })
+ addCollectionPreference(state, { path: 'collections.testing', value: 2 })
+ updateCache(state, { username: 'test' })
+ expect(state.prefsStorage.simple.testing).to.eql(1)
+ expect(state.prefsStorage.collections.testing).to.eql([2])
+ expect(state.prefsStorage._journal.length).to.eql(2)
+ })
+ })
+ })
+
+ describe('helper functions', () => {
+ describe('_moveItemInArray', () => {
+ it('should move item according to movement value', () => {
+ expect(_moveItemInArray([1, 2, 3, 4], 4, -1)).to.eql([1, 2, 4, 3])
+ expect(_moveItemInArray([1, 2, 3, 4], 1, 2)).to.eql([2, 3, 1, 4])
+ })
+ it('should clamp movement to within array', () => {
+ expect(_moveItemInArray([1, 2, 3, 4], 4, -10)).to.eql([4, 1, 2, 3])
+ expect(_moveItemInArray([1, 2, 3, 4], 3, 99)).to.eql([1, 2, 4, 3])
+ })
+ })
+ describe('_getRecentData', () => {
+ it('should handle nulls correctly', () => {
+ expect(_getRecentData(null, null)).to.eql({ recent: null, stale: null, needUpload: true })
+ })
+
+ it('doesn\'t choke on invalid data', () => {
+ expect(_getRecentData({ a: 1 }, { b: 2 })).to.eql({ recent: null, stale: null, needUpload: true })
+ })
+
+ it('should prefer the valid non-null correctly, needUpload works properly', () => {
+ const nonNull = { _version: VERSION, _timestamp: 1 }
+ expect(_getRecentData(nonNull, null)).to.eql({ recent: nonNull, stale: null, needUpload: true })
+ expect(_getRecentData(null, nonNull)).to.eql({ recent: nonNull, stale: null, needUpload: false })
+ })
+
+ it('should prefer the one with higher timestamp', () => {
+ const a = { _version: VERSION, _timestamp: 1 }
+ const b = { _version: VERSION, _timestamp: 2 }
+
+ expect(_getRecentData(a, b)).to.eql({ recent: b, stale: a, needUpload: false })
+ expect(_getRecentData(b, a)).to.eql({ recent: b, stale: a, needUpload: false })
+ })
+
+ it('case where both are same', () => {
+ const a = { _version: VERSION, _timestamp: 3 }
+ const b = { _version: VERSION, _timestamp: 3 }
+
+ expect(_getRecentData(a, b)).to.eql({ recent: b, stale: a, needUpload: false })
+ expect(_getRecentData(b, a)).to.eql({ recent: b, stale: a, needUpload: false })
+ })
+ })
+
+ describe('_getAllFlags', () => {
+ it('should handle nulls properly', () => {
+ expect(_getAllFlags(null, null)).to.eql([])
+ })
+ it('should output list of keys if passed single object', () => {
+ expect(_getAllFlags({ flagStorage: { a: 1, b: 1, c: 1 } }, null)).to.eql(['a', 'b', 'c'])
+ })
+ it('should union keys of both objects', () => {
+ expect(_getAllFlags({ flagStorage: { a: 1, b: 1, c: 1 } }, { flagStorage: { c: 1, d: 1 } })).to.eql(['a', 'b', 'c', 'd'])
+ })
+ })
+
+ describe('_mergeFlags', () => {
+ it('should handle merge two flag sets correctly picking higher numbers', () => {
+ expect(
+ _mergeFlags(
+ { flagStorage: { a: 0, b: 3 } },
+ { flagStorage: { b: 1, c: 4, d: 9 } },
+ ['a', 'b', 'c', 'd'])
+ ).to.eql({ a: 0, b: 3, c: 4, d: 9 })
+ })
+ })
+
+ describe('_mergePrefs', () => {
+ it('should prefer recent and apply journal to it', () => {
+ expect(
+ _mergePrefs(
+ // RECENT
+ {
+ simple: { a: 1, b: 0, c: true },
+ _journal: [
+ { path: 'simple.b', operation: 'set', args: [0], timestamp: 2 },
+ { path: 'simple.c', operation: 'set', args: [true], timestamp: 4 }
+ ]
+ },
+ // STALE
+ {
+ simple: { a: 1, b: 1, c: false },
+ _journal: [
+ { path: 'simple.a', operation: 'set', args: [1], timestamp: 1 },
+ { path: 'simple.b', operation: 'set', args: [1], timestamp: 3 }
+ ]
+ }
+ )
+ ).to.eql({
+ simple: { a: 1, b: 1, c: true },
+ _journal: [
+ { path: 'simple.a', operation: 'set', args: [1], timestamp: 1 },
+ { path: 'simple.b', operation: 'set', args: [1], timestamp: 3 },
+ { path: 'simple.c', operation: 'set', args: [true], timestamp: 4 }
+ ]
+ })
+ })
+
+ it('should allow setting falsy values', () => {
+ expect(
+ _mergePrefs(
+ // RECENT
+ {
+ simple: { a: 1, b: 0, c: false },
+ _journal: [
+ { path: 'simple.b', operation: 'set', args: [0], timestamp: 2 },
+ { path: 'simple.c', operation: 'set', args: [false], timestamp: 4 }
+ ]
+ },
+ // STALE
+ {
+ simple: { a: 0, b: 0, c: true },
+ _journal: [
+ { path: 'simple.a', operation: 'set', args: [0], timestamp: 1 },
+ { path: 'simple.b', operation: 'set', args: [0], timestamp: 3 }
+ ]
+ }
+ )
+ ).to.eql({
+ simple: { a: 0, b: 0, c: false },
+ _journal: [
+ { path: 'simple.a', operation: 'set', args: [0], timestamp: 1 },
+ { path: 'simple.b', operation: 'set', args: [0], timestamp: 3 },
+ { path: 'simple.c', operation: 'set', args: [false], timestamp: 4 }
+ ]
+ })
+ })
+
+ it('should work with strings', () => {
+ expect(
+ _mergePrefs(
+ // RECENT
+ {
+ simple: { a: 'foo' },
+ _journal: [
+ { path: 'simple.a', operation: 'set', args: ['foo'], timestamp: 2 }
+ ]
+ },
+ // STALE
+ {
+ simple: { a: 'bar' },
+ _journal: [
+ { path: 'simple.a', operation: 'set', args: ['bar'], timestamp: 4 }
+ ]
+ }
+ )
+ ).to.eql({
+ simple: { a: 'bar' },
+ _journal: [
+ { path: 'simple.a', operation: 'set', args: ['bar'], timestamp: 4 }
+ ]
+ })
+ })
+ })
+
+ describe('_resetFlags', () => {
+ it('should reset all known flags to 0 when reset flag is set to > 0 and < 9000', () => {
+ const totalFlags = { a: 0, b: 3, reset: 1 }
+
+ expect(_resetFlags(totalFlags)).to.eql({ a: 0, b: 0, reset: 0 })
+ })
+ it('should trim all flags to known when reset is set to 1000', () => {
+ const totalFlags = { a: 0, b: 3, c: 33, reset: COMMAND_TRIM_FLAGS }
+
+ expect(_resetFlags(totalFlags, { a: 0, b: 0, reset: 0 })).to.eql({ a: 0, b: 3, reset: 0 })
+ })
+ it('should trim all flags to known and reset when reset is set to 1001', () => {
+ const totalFlags = { a: 0, b: 3, c: 33, reset: COMMAND_TRIM_FLAGS_AND_RESET }
+
+ expect(_resetFlags(totalFlags, { a: 0, b: 0, reset: 0 })).to.eql({ a: 0, b: 0, reset: 0 })
+ })
+ })
+ })
+})
diff --git a/test/unit/specs/modules/statuses.spec.js b/test/unit/specs/modules/statuses.spec.js
@@ -77,24 +77,6 @@ describe('Statuses module', () => {
expect(state.timelines.public.newStatusCount).to.equal(0)
})
- it('removes statuses by tag on deletion', () => {
- const state = defaultState()
- const status = makeMockStatus({ id: '1' })
- const otherStatus = makeMockStatus({ id: '3' })
- status.uri = 'xxx'
- const deletion = makeMockStatus({ id: '2', type: 'deletion' })
- deletion.text = 'Dolus deleted notice {{tag:gs.smuglo.li,2016-11-18:noticeId=1038007:objectType=note}}.'
- deletion.uri = 'xxx'
-
- mutations.addNewStatuses(state, { statuses: [status, otherStatus], showImmediately: true, timeline: 'public' })
- mutations.addNewStatuses(state, { statuses: [deletion], showImmediately: true, timeline: 'public' })
-
- expect(state.allStatuses).to.eql([otherStatus])
- expect(state.timelines.public.statuses).to.eql([otherStatus])
- expect(state.timelines.public.visibleStatuses).to.eql([otherStatus])
- expect(state.timelines.public.maxId).to.eql('3')
- })
-
it('does not update the maxId when the noIdUpdate flag is set', () => {
const state = defaultState()
const status = makeMockStatus({ id: '1' })
@@ -245,7 +227,7 @@ describe('Statuses module', () => {
it('increments count in existing reaction', () => {
const state = defaultState()
const status = makeMockStatus({ id: '1' })
- status.emoji_reactions = [ { name: '😂', count: 1, accounts: [] } ]
+ status.emoji_reactions = [{ name: '😂', count: 1, accounts: [] }]
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
mutations.addOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } })
@@ -269,7 +251,7 @@ describe('Statuses module', () => {
it('decreases count in existing reaction', () => {
const state = defaultState()
const status = makeMockStatus({ id: '1' })
- status.emoji_reactions = [ { name: '😂', count: 2, accounts: [{ id: 'me' }] } ]
+ status.emoji_reactions = [{ name: '😂', count: 2, accounts: [{ id: 'me' }] }]
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } })
@@ -315,62 +297,4 @@ describe('Statuses module', () => {
expect(state.timelines.user.userId).to.eql(123)
})
})
-
- describe('notifications', () => {
- it('removes a notification when the notice gets removed', () => {
- const user = { id: '1' }
- const state = defaultState()
- const status = makeMockStatus({ id: '1' })
- const otherStatus = makeMockStatus({ id: '3' })
- const mentionedStatus = makeMockStatus({ id: '2' })
- mentionedStatus.attentions = [user]
- mentionedStatus.uri = 'xxx'
- otherStatus.attentions = [user]
-
- const deletion = makeMockStatus({ id: '4', type: 'deletion' })
- deletion.text = 'Dolus deleted notice {{tag:gs.smuglo.li,2016-11-18:noticeId=1038007:objectType=note}}.'
- deletion.uri = 'xxx'
- const newNotificationSideEffects = () => {}
- mutations.addNewStatuses(state, { statuses: [status, otherStatus], user })
- mutations.addNewNotifications(
- state,
- {
- notifications: [{
- from_profile: { id: '2' },
- id: '998',
- type: 'mention',
- status: otherStatus,
- action: otherStatus,
- seen: false
- }],
- newNotificationSideEffects
- })
-
- expect(state.notifications.data.length).to.eql(1)
- mutations.addNewNotifications(
- state,
- {
- notifications: [{
- from_profile: { id: '2' },
- id: '999',
- type: 'mention',
- status: mentionedStatus,
- action: mentionedStatus,
- seen: false
- }],
- newNotificationSideEffects
- })
-
- mutations.addNewStatuses(state, { statuses: [mentionedStatus], user })
- expect(state.allStatuses.length).to.eql(3)
- expect(state.notifications.data.length).to.eql(2)
- expect(state.notifications.data[1].status).to.eql(mentionedStatus)
- expect(state.notifications.data[1].action).to.eql(mentionedStatus)
- expect(state.notifications.data[1].type).to.eql('mention')
-
- mutations.addNewStatuses(state, { statuses: [deletion], user })
- expect(state.allStatuses.length).to.eql(2)
- expect(state.notifications.data.length).to.eql(1)
- })
- })
})
diff --git a/test/unit/specs/modules/users.spec.js b/test/unit/specs/modules/users.spec.js
@@ -57,24 +57,27 @@ describe('The users module', () => {
})
describe('findUser', () => {
- it('returns user with matching screen_name', () => {
+ it('does not return user with matching screen_name', () => {
const user = { screen_name: 'Guy', id: '1' }
const state = {
usersObject: {
- 1: user,
+ 1: user
+ },
+ usersByNameObject: {
guy: user
}
}
const name = 'Guy'
- const expected = { screen_name: 'Guy', id: '1' }
- expect(getters.findUser(state)(name)).to.eql(expected)
+ expect(getters.findUser(state)(name)).to.eql(undefined)
})
it('returns user with matching id', () => {
const user = { screen_name: 'Guy', id: '1' }
const state = {
usersObject: {
- 1: user,
+ 1: user
+ },
+ usersByNameObject: {
guy: user
}
}
@@ -83,4 +86,35 @@ describe('The users module', () => {
expect(getters.findUser(state)(id)).to.eql(expected)
})
})
+
+ describe('findUserByName', () => {
+ it('returns user with matching screen_name', () => {
+ const user = { screen_name: 'Guy', id: '1' }
+ const state = {
+ usersObject: {
+ 1: user
+ },
+ usersByNameObject: {
+ guy: user
+ }
+ }
+ const name = 'Guy'
+ const expected = { screen_name: 'Guy', id: '1' }
+ expect(getters.findUserByName(state)(name)).to.eql(expected)
+ })
+
+ it('does not return user with matching id', () => {
+ const user = { screen_name: 'Guy', id: '1' }
+ const state = {
+ usersObject: {
+ 1: user
+ },
+ usersByNameObject: {
+ guy: user
+ }
+ }
+ const id = '1'
+ expect(getters.findUserByName(state)(id)).to.eql(undefined)
+ })
+ })
})
diff --git a/test/unit/specs/services/chat_service/chat_service.spec.js b/test/unit/specs/services/chat_service/chat_service.spec.js
@@ -24,23 +24,23 @@ describe('chatService', () => {
describe('.add', () => {
it("Doesn't add duplicates", () => {
const chat = chatService.empty()
- chatService.add(chat, { messages: [ message1 ] })
- chatService.add(chat, { messages: [ message1 ] })
+ chatService.add(chat, { messages: [message1] })
+ chatService.add(chat, { messages: [message1] })
expect(chat.messages.length).to.eql(1)
- chatService.add(chat, { messages: [ message2 ] })
+ chatService.add(chat, { messages: [message2] })
expect(chat.messages.length).to.eql(2)
})
it('Updates minId and lastMessage and newMessageCount', () => {
const chat = chatService.empty()
- chatService.add(chat, { messages: [ message1 ] })
+ chatService.add(chat, { messages: [message1] })
expect(chat.maxId).to.eql(message1.id)
expect(chat.minId).to.eql(message1.id)
expect(chat.newMessageCount).to.eql(1)
- chatService.add(chat, { messages: [ message2 ] })
+ chatService.add(chat, { messages: [message2] })
expect(chat.maxId).to.eql(message2.id)
expect(chat.minId).to.eql(message1.id)
expect(chat.newMessageCount).to.eql(2)
@@ -50,7 +50,7 @@ describe('chatService', () => {
expect(chat.lastSeenMessageId).to.eql(message2.id)
// Add message with higher id
- chatService.add(chat, { messages: [ message3 ] })
+ chatService.add(chat, { messages: [message3] })
expect(chat.newMessageCount).to.eql(1)
})
})
@@ -59,9 +59,9 @@ describe('chatService', () => {
it('Updates minId and lastMessage', () => {
const chat = chatService.empty()
- chatService.add(chat, { messages: [ message1 ] })
- chatService.add(chat, { messages: [ message2 ] })
- chatService.add(chat, { messages: [ message3 ] })
+ chatService.add(chat, { messages: [message1] })
+ chatService.add(chat, { messages: [message2] })
+ chatService.add(chat, { messages: [message3] })
expect(chat.maxId).to.eql(message3.id)
expect(chat.minId).to.eql(message1.id)
@@ -80,9 +80,9 @@ describe('chatService', () => {
it('Inserts date separators', () => {
const chat = chatService.empty()
- chatService.add(chat, { messages: [ message1 ] })
- chatService.add(chat, { messages: [ message2 ] })
- chatService.add(chat, { messages: [ message3 ] })
+ chatService.add(chat, { messages: [message1] })
+ chatService.add(chat, { messages: [message2] })
+ chatService.add(chat, { messages: [message3] })
const view = chatService.getView(chat)
expect(view.map(i => i.type)).to.eql(['date', 'message', 'message', 'date', 'message'])
diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
@@ -195,7 +195,7 @@ describe('API Entities normalizer', () => {
expect(parsedPost).to.have.property('type', 'status')
expect(parsedRepeat).to.have.property('type', 'retweet')
expect(parsedRepeat).to.have.property('retweeted_status')
- expect(parsedRepeat).to.have.deep.property('retweeted_status.id', 'deadbeef')
+ expect(parsedRepeat).to.have.nested.property('retweeted_status.id', 'deadbeef')
})
it('sets nsfw for statuses with the #nsfw tag', () => {
@@ -229,7 +229,7 @@ describe('API Entities normalizer', () => {
expect(parsedPost).to.have.property('type', 'status')
expect(parsedRepeat).to.have.property('type', 'retweet')
expect(parsedRepeat).to.have.property('retweeted_status')
- expect(parsedRepeat).to.have.deep.property('retweeted_status.id', 'deadbeef')
+ expect(parsedRepeat).to.have.nested.property('retweeted_status.id', 'deadbeef')
})
})
})
@@ -269,7 +269,8 @@ describe('API Entities normalizer', () => {
it('converts IDN to unicode and marks it as internatonal', () => {
const user = makeMockUserMasto({ acct: 'lain@xn--lin-6cd.com' })
- expect(parseUser(user)).to.have.property('screen_name_ui').that.equal('lain@🌏lаin.com')
+ expect(parseUser(user)).to.have.property('screen_name_ui').that.equal('lain@lаin.com')
+ expect(parseUser(user)).to.have.property('screen_name_ui_contains_non_ascii').that.equal(true)
})
})
@@ -284,9 +285,9 @@ describe('API Entities normalizer', () => {
})
expect(parseNotification(notif)).to.have.property('id', 123)
expect(parseNotification(notif)).to.have.property('seen', false)
- expect(parseNotification(notif)).to.have.deep.property('status.id', '444')
- expect(parseNotification(notif)).to.have.deep.property('action.id', '444')
- expect(parseNotification(notif)).to.have.deep.property('from_profile.id', 'spurdo')
+ expect(parseNotification(notif)).to.have.nested.property('status.id', '444')
+ expect(parseNotification(notif)).to.have.nested.property('action.id', '444')
+ expect(parseNotification(notif)).to.have.nested.property('from_profile.id', 'spurdo')
})
it('correctly normalizes favorite notifications', () => {
@@ -303,9 +304,9 @@ describe('API Entities normalizer', () => {
expect(parseNotification(notif)).to.have.property('id', 123)
expect(parseNotification(notif)).to.have.property('type', 'like')
expect(parseNotification(notif)).to.have.property('seen', true)
- expect(parseNotification(notif)).to.have.deep.property('status.id', '4412')
- expect(parseNotification(notif)).to.have.deep.property('action.id', '444')
- expect(parseNotification(notif)).to.have.deep.property('from_profile.id', 'spurdo')
+ expect(parseNotification(notif)).to.have.nested.property('status.id', '4412')
+ expect(parseNotification(notif)).to.have.nested.property('action.id', '444')
+ expect(parseNotification(notif)).to.have.nested.property('from_profile.id', 'spurdo')
})
})
@@ -314,8 +315,8 @@ describe('API Entities normalizer', () => {
const linkHeader = '<https://example.com/api/v1/notifications?max_id=861676>; rel="next", <https://example.com/api/v1/notifications?min_id=861741>; rel="prev"'
const result = parseLinkHeaderPagination(linkHeader)
expect(result).to.eql({
- 'maxId': 861676,
- 'minId': 861741
+ maxId: 861676,
+ minId: 861741
})
})
@@ -323,8 +324,8 @@ describe('API Entities normalizer', () => {
const linkHeader = '<http://example.com/api/v1/timelines/home?max_id=9waQx5IIS48qVue2Ai>; rel="next", <http://example.com/api/v1/timelines/home?min_id=9wi61nIPnfn674xgie>; rel="prev"'
const result = parseLinkHeaderPagination(linkHeader, { flakeId: true })
expect(result).to.eql({
- 'maxId': '9waQx5IIS48qVue2Ai',
- 'minId': '9wi61nIPnfn674xgie'
+ maxId: '9waQx5IIS48qVue2Ai',
+ minId: '9wi61nIPnfn674xgie'
})
})
})
diff --git a/test/unit/specs/services/file_size_format/file_size_format.spec.js b/test/unit/specs/services/file_size_format/file_size_format.spec.js
@@ -25,8 +25,8 @@ describe('fileSizeFormat', () => {
}
]
- var res = []
- for (var value in values) {
+ const res = []
+ for (const value in values) {
res.push(fileSizeFormatService.fileSizeFormat(values[value]))
}
expect(res).to.eql(expected)
diff --git a/test/unit/specs/services/matcher/matcher.spec.js b/test/unit/specs/services/matcher/matcher.spec.js
@@ -78,5 +78,11 @@ describe('MatcherService', () => {
expect(MatcherService.extractTagFromUrl(url)).to.eql(false)
})
+
+ it('should return tag name from non-ascii tags', () => {
+ const url = encodeURI('https://website.com/tag/喵喵喵')
+
+ expect(MatcherService.extractTagFromUrl(url)).to.eql('喵喵喵')
+ })
})
})
diff --git a/test/unit/specs/services/notification_utils/notification_utils.spec.js b/test/unit/specs/services/notification_utils/notification_utils.spec.js
@@ -5,28 +5,28 @@ describe('NotificationUtils', () => {
it('should return sorted notifications with configured types', () => {
const store = {
state: {
- statuses: {
- notifications: {
- data: [
- {
- id: 1,
- action: { id: '1' },
- type: 'like'
- },
- {
- id: 2,
- action: { id: '2' },
- type: 'mention'
- },
- {
- id: 3,
- action: { id: '3' },
- type: 'repeat'
- }
- ]
- }
- },
- config: {
+ notifications: {
+ data: [
+ {
+ id: 1,
+ action: { id: '1' },
+ type: 'like'
+ },
+ {
+ id: 2,
+ action: { id: '2' },
+ type: 'mention'
+ },
+ {
+ id: 3,
+ action: { id: '3' },
+ type: 'repeat'
+ }
+ ]
+ }
+ },
+ getters: {
+ mergedConfig: {
notificationVisibility: {
likes: true,
repeats: true,
@@ -55,23 +55,23 @@ describe('NotificationUtils', () => {
it('should return only notifications not marked as seen', () => {
const store = {
state: {
- statuses: {
- notifications: {
- data: [
- {
- action: { id: '1' },
- type: 'like',
- seen: false
- },
- {
- action: { id: '2' },
- type: 'mention',
- seen: true
- }
- ]
- }
- },
- config: {
+ notifications: {
+ data: [
+ {
+ action: { id: '1' },
+ type: 'like',
+ seen: false
+ },
+ {
+ action: { id: '2' },
+ type: 'mention',
+ seen: true
+ }
+ ]
+ }
+ },
+ getters: {
+ mergedConfig: {
notificationVisibility: {
likes: true,
repeats: true,
diff --git a/test/unit/specs/services/theme_data/iss_deserializer.spec.js b/test/unit/specs/services/theme_data/iss_deserializer.spec.js
@@ -0,0 +1,40 @@
+import { deserialize } from 'src/services/theme_data/iss_deserializer.js'
+import { serialize } from 'src/services/theme_data/iss_serializer.js'
+const componentsContext = require.context('src', true, /\.style.js(on)?$/)
+
+describe('ISS (de)serialization', () => {
+ componentsContext.keys().forEach(key => {
+ const component = componentsContext(key).default
+
+ it(`(De)serialization of component ${component.name} works`, () => {
+ const normalized = component.defaultRules.map(x => ({ component: component.name, ...x }))
+ const serialized = serialize(normalized)
+ const deserialized = deserialize(serialized)
+
+ // for some reason comparing objects directly fails the assert
+ expect(JSON.stringify(deserialized, null, 2)).to.equal(JSON.stringify(normalized, null, 2))
+ })
+ })
+
+ /*
+ // Debug snippet
+ const onlyComponent = componentsContext('./components/panel_header.style.js').default
+ it.only(`(De)serialization of component ${onlyComponent.name} works`, () => {
+ const normalized = onlyComponent.defaultRules.map(x => ({ component: onlyComponent.name, ...x }))
+ console.log('BEGIN INPUT ================')
+ console.log(normalized)
+ console.log('END INPUT ==================')
+ const serialized = serialize(normalized)
+ console.log('BEGIN SERIAL ===============')
+ console.log(serialized)
+ console.log('END SERIAL =================')
+ const deserialized = deserialize(serialized)
+ console.log('BEGIN DESERIALIZED =========')
+ console.log(serialized)
+ console.log('END DESERIALIZED ===========')
+
+ // for some reason comparing objects directly fails the assert
+ expect(JSON.stringify(deserialized, null, 2)).to.equal(JSON.stringify(normalized, null, 2))
+ })
+ /* */
+})
diff --git a/test/unit/specs/services/theme_data/sanity_checks.spec.js b/test/unit/specs/services/theme_data/sanity_checks.spec.js
@@ -6,7 +6,7 @@ const checkColors = (output) => {
expect(v, key).to.be.an('object')
expect(v, key).to.include.all.keys('r', 'g', 'b')
'rgba'.split('').forEach(k => {
- if ((k === 'a' && v.hasOwnProperty('a')) || k !== 'a') {
+ if ((k === 'a' && Object.prototype.hasOwnProperty.call(v, 'a')) || k !== 'a') {
expect(v[k], key + '.' + k).to.be.a('number')
expect(v[k], key + '.' + k).to.be.least(0)
expect(v[k], key + '.' + k).to.be.most(k === 'a' ? 1 : 255)
diff --git a/test/unit/specs/services/theme_data/theme_data3.spec.js b/test/unit/specs/services/theme_data/theme_data3.spec.js
@@ -0,0 +1,150 @@
+// import { topoSort } from 'src/services/theme_data/theme_data.service.js'
+import {
+ getAllPossibleCombinations
+} from 'src/services/theme_data/iss_utils.js'
+import {
+ init
+} from 'src/services/theme_data/theme_data_3.service.js'
+import {
+ basePaletteKeys
+} from 'src/services/theme_data/theme2_to_theme3.js'
+
+describe('Theme Data 3', () => {
+ describe('getAllPossibleCombinations', () => {
+ it('test simple 3 values case', () => {
+ const out = getAllPossibleCombinations([1, 2, 3]).map(x => x.sort((a, b) => a - b))
+ expect(out).to.eql([
+ [1], [2], [3],
+ [1, 2], [1, 3], [2, 3],
+ [1, 2, 3]
+ ])
+ })
+
+ it('test simple 4 values case', () => {
+ const out = getAllPossibleCombinations([1, 2, 3, 4]).map(x => x.sort((a, b) => a - b))
+ expect(out).to.eql([
+ [1], [2], [3], [4],
+ [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4],
+ [1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4],
+ [1, 2, 3, 4]
+ ])
+ })
+
+ it('test massive 5 values case, using strings', () => {
+ const out = getAllPossibleCombinations(['a', 'b', 'c', 'd', 'e']).map(x => x.sort((a, b) => a - b))
+ expect(out).to.eql([
+ // 1
+ ['a'], ['b'], ['c'], ['d'], ['e'],
+ // 2
+ ['a', 'b'], ['a', 'c'], ['a', 'd'], ['a', 'e'],
+ ['b', 'c'], ['b', 'd'], ['b', 'e'],
+ ['c', 'd'], ['c', 'e'],
+ ['d', 'e'],
+ // 3
+ ['a', 'b', 'c'], ['a', 'b', 'd'], ['a', 'b', 'e'],
+ ['a', 'c', 'd'], ['a', 'c', 'e'],
+ ['a', 'd', 'e'],
+
+ ['b', 'c', 'd'], ['b', 'c', 'e'],
+ ['b', 'd', 'e'],
+
+ ['c', 'd', 'e'],
+ // 4
+ ['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'e'],
+ ['a', 'b', 'd', 'e'],
+
+ ['a', 'c', 'd', 'e'],
+
+ ['b', 'c', 'd', 'e'],
+ // 5
+ ['a', 'b', 'c', 'd', 'e']
+ ])
+ })
+ })
+
+ describe('init', function () {
+ this.timeout(5000)
+
+ it('Test initialization without anything', () => {
+ const out = init({ inputRuleset: [], ultimateBackgroundColor: '#DEADAF' })
+
+ expect(out).to.have.property('eager')
+ expect(out).to.have.property('lazy')
+ expect(out).to.have.property('staticVars')
+
+ expect(out.lazy).to.be.an('array')
+ expect(out.lazy).to.have.lengthOf.above(1)
+ expect(out.eager).to.be.an('array')
+ expect(out.eager).to.have.lengthOf.above(1)
+ expect(out.staticVars).to.be.an('object')
+
+ // check backwards compat/generic stuff
+ basePaletteKeys.forEach(key => {
+ expect(out.staticVars).to.have.property(key)
+ })
+ })
+
+ it('Test initialization with a basic palette', () => {
+ const out = init({
+ inputRuleset: [{
+ component: 'Root',
+ directives: {
+ '--bg': 'color | #008080',
+ '--fg': 'color | #00C0A0'
+ }
+ }],
+ ultimateBackgroundColor: '#DEADAF'
+ })
+
+ expect(out.staticVars).to.have.property('bg').equal('#008080')
+ expect(out.staticVars).to.have.property('fg').equal('#00C0A0')
+
+ const panelRule = out.eager.filter(x => {
+ if (x.component !== 'Panel') return false
+ return true
+ })[0]
+
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked', { r: 0, g: 128, b: 128 })
+ })
+
+ it('Test initialization with opacity', () => {
+ const out = init({
+ inputRuleset: [{
+ component: 'Root',
+ directives: {
+ '--bg': 'color | #008080'
+ }
+ }, {
+ component: 'Panel',
+ directives: {
+ opacity: 0.5
+ }
+ }],
+ ultimateBackgroundColor: '#DEADAF'
+ })
+
+ expect(out.staticVars).to.have.property('bg').equal('#008080')
+
+ const panelRule = out.eager.filter(x => {
+ if (x.component !== 'Panel') return false
+ return true
+ })[0]
+
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.background', { r: 0, g: 128, b: 128, a: 0.5 })
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked')
+ // Somewhat incorrect since we don't do gamma correction
+ // real expectancy should be this:
+ /*
+
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.r').that.is.closeTo(147.0, 0.01)
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.g').that.is.closeTo(143.2, 0.01)
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.b').that.is.closeTo(144.0, 0.01)
+
+ */
+
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.r').that.is.closeTo(88.8, 0.01)
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.g').that.is.closeTo(133.2, 0.01)
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.b').that.is.closeTo(134, 0.01)
+ })
+ })
+})
diff --git a/test/unit/specs/services/version/version.service.spec.js b/test/unit/specs/services/version/version.service.spec.js
@@ -1,11 +0,0 @@
-import { extractCommit } from 'src/services/version/version.service.js'
-
-describe('extractCommit', () => {
- it('return short commit hash following "-g" characters', () => {
- expect(extractCommit('1.0.0-45-g5e7aeebc')).to.eql('5e7aeebc')
- })
-
- it('return short commit hash without branch name', () => {
- expect(extractCommit('1.0.0-45-g5e7aeebc-branch')).to.eql('5e7aeebc')
- })
-})
diff --git a/tools/check-changelog b/tools/check-changelog
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+echo "looking for change log"
+
+git remote add upstream https://git.pleroma.social/pleroma/pleroma-fe.git
+git fetch upstream ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}:refs/remotes/upstream/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME
+
+git diff --raw --no-renames upstream/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME HEAD -- changelog.d | \
+ grep ' A\t' | grep '\.\(skip\|add\|remove\|change\|fix\|security\)$'
+ret=$?
+
+if [ $ret -eq 0 ]; then
+ echo "found a changelog entry"
+ exit 0
+else
+ echo "changelog entry not found"
+ exit 1
+fi
diff --git a/tools/collect-changelog b/tools/collect-changelog
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+collectType() {
+ local suffix="$1"
+ local header="$2"
+ local printed=0
+ for file in changelog.d/*."$suffix"; do
+ if [ '!' -f "$file" ]; then
+ continue
+ fi
+ if [ "$printed" = 0 ]; then
+ echo
+ echo "### $header"
+ printed=1
+ fi
+ # Normalize any trailing newlines/spaces, etc.
+ echo "- $(cat "$file")"
+ done
+}
+
+collectType security Security
+collectType change Changed
+collectType add Added
+collectType fix Fixed
+collectType remove Removed
+
+rm changelog.d/*
diff --git a/yarn.lock b/yarn.lock
@@ -9,19 +9,21 @@
dependencies:
"@jridgewell/trace-mapping" "^0.3.0"
-"@babel/code-frame@^7.0.0":
+"@ampproject/remapping@^2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d"
+ integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.1.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@babel/code-frame@7.0.0", "@babel/code-frame@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
+ integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==
dependencies:
"@babel/highlight" "^7.0.0"
-"@babel/code-frame@^7.16.7":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789"
- integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==
- dependencies:
- "@babel/highlight" "^7.16.7"
-
"@babel/code-frame@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a"
@@ -29,68 +31,110 @@
dependencies:
"@babel/highlight" "^7.18.6"
-"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.17.7":
+"@babel/code-frame@^7.21.4":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39"
+ integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==
+ dependencies:
+ "@babel/highlight" "^7.18.6"
+
+"@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5":
+ version "7.23.5"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244"
+ integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==
+ dependencies:
+ "@babel/highlight" "^7.23.4"
+ chalk "^2.4.2"
+
+"@babel/code-frame@^7.24.1":
+ version "7.24.2"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae"
+ integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==
+ dependencies:
+ "@babel/highlight" "^7.24.2"
+ picocolors "^1.0.0"
+
+"@babel/compat-data@^7.17.7":
version "7.17.7"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.7.tgz#078d8b833fbbcc95286613be8c716cef2b519fa2"
integrity sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==
-"@babel/compat-data@^7.18.6", "@babel/compat-data@^7.18.8":
+"@babel/compat-data@^7.18.8":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d"
integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==
-"@babel/core@7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.9.tgz#805461f967c77ff46c74ca0460ccf4fe933ddd59"
- integrity sha512-1LIb1eL8APMy91/IMW+31ckrfBM4yCoLaVzoDhZUKSM4cu1L1nIidyxkCgzPAgrC5WEz36IPEr/eSeSF9pIn+g==
+"@babel/compat-data@^7.20.5":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.5.tgz#86f172690b093373a933223b4745deeb6049e733"
+ integrity sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==
+
+"@babel/compat-data@^7.21.5":
+ version "7.21.7"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.7.tgz#61caffb60776e49a57ba61a88f02bedd8714f6bc"
+ integrity sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==
+
+"@babel/core@7.21.8":
+ version "7.21.8"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.8.tgz#2a8c7f0f53d60100ba4c32470ba0281c92aa9aa4"
+ integrity sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==
+ dependencies:
+ "@ampproject/remapping" "^2.2.0"
+ "@babel/code-frame" "^7.21.4"
+ "@babel/generator" "^7.21.5"
+ "@babel/helper-compilation-targets" "^7.21.5"
+ "@babel/helper-module-transforms" "^7.21.5"
+ "@babel/helpers" "^7.21.5"
+ "@babel/parser" "^7.21.8"
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.21.5"
+ "@babel/types" "^7.21.5"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.2"
+ semver "^6.3.0"
+
+"@babel/core@^7.12.3":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.10.tgz#39ad504991d77f1f3da91be0b8b949a5bc466fb8"
+ integrity sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==
dependencies:
"@ampproject/remapping" "^2.1.0"
"@babel/code-frame" "^7.18.6"
- "@babel/generator" "^7.18.9"
+ "@babel/generator" "^7.18.10"
"@babel/helper-compilation-targets" "^7.18.9"
"@babel/helper-module-transforms" "^7.18.9"
"@babel/helpers" "^7.18.9"
- "@babel/parser" "^7.18.9"
- "@babel/template" "^7.18.6"
- "@babel/traverse" "^7.18.9"
- "@babel/types" "^7.18.9"
+ "@babel/parser" "^7.18.10"
+ "@babel/template" "^7.18.10"
+ "@babel/traverse" "^7.18.10"
+ "@babel/types" "^7.18.10"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.1"
semver "^6.3.0"
-"@babel/core@^7.17.9":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.6.tgz#54a107a3c298aee3fe5e1947a6464b9b6faca03d"
- integrity sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==
+"@babel/eslint-parser@7.21.8":
+ version "7.21.8"
+ resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.21.8.tgz#59fb6fc4f3b017ab86987c076226ceef7b2b2ef2"
+ integrity sha512-HLhI+2q+BP3sf78mFUZNCGc10KEmoUqtUT1OCdMZsN+qr4qFeLUod62/zAnF3jNQstwyasDkZnVXwfK2Bml7MQ==
dependencies:
- "@ampproject/remapping" "^2.1.0"
- "@babel/code-frame" "^7.18.6"
- "@babel/generator" "^7.18.6"
- "@babel/helper-compilation-targets" "^7.18.6"
- "@babel/helper-module-transforms" "^7.18.6"
- "@babel/helpers" "^7.18.6"
- "@babel/parser" "^7.18.6"
- "@babel/template" "^7.18.6"
- "@babel/traverse" "^7.18.6"
- "@babel/types" "^7.18.6"
- convert-source-map "^1.7.0"
- debug "^4.1.0"
- gensync "^1.0.0-beta.2"
- json5 "^2.2.1"
+ "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1"
+ eslint-visitor-keys "^2.1.0"
semver "^6.3.0"
-"@babel/generator@^7.17.3":
- version "7.17.7"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.7.tgz#8da2599beb4a86194a3b24df6c085931d9ee45ad"
- integrity sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==
+"@babel/generator@^7.18.10":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.10.tgz#794f328bfabdcbaf0ebf9bf91b5b57b61fa77a2a"
+ integrity sha512-0+sW7e3HjQbiHbj1NeU/vN8ornohYlacAfZIaXhdoGweQqgcNy69COVciYYqEXJ/v+9OBA7Frxm4CVAuNqKeNA==
dependencies:
- "@babel/types" "^7.17.0"
+ "@babel/types" "^7.18.10"
+ "@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
- source-map "^0.5.0"
-"@babel/generator@^7.18.6", "@babel/generator@^7.18.7":
+"@babel/generator@^7.18.7":
version "7.18.7"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.7.tgz#2aa78da3c05aadfc82dbac16c99552fc802284bd"
integrity sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A==
@@ -108,6 +152,45 @@
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
+"@babel/generator@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.7.tgz#f8ef57c8242665c5929fe2e8d82ba75460187b4a"
+ integrity sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==
+ dependencies:
+ "@babel/types" "^7.20.7"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ jsesc "^2.5.1"
+
+"@babel/generator@^7.21.4":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.4.tgz#64a94b7448989f421f919d5239ef553b37bb26bc"
+ integrity sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==
+ dependencies:
+ "@babel/types" "^7.21.4"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jsesc "^2.5.1"
+
+"@babel/generator@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.5.tgz#c0c0e5449504c7b7de8236d99338c3e2a340745f"
+ integrity sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==
+ dependencies:
+ "@babel/types" "^7.21.5"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jsesc "^2.5.1"
+
+"@babel/generator@^7.24.1":
+ version "7.24.1"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.1.tgz#e67e06f68568a4ebf194d1c6014235344f0476d0"
+ integrity sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==
+ dependencies:
+ "@babel/types" "^7.24.0"
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.25"
+ jsesc "^2.5.1"
+
"@babel/helper-annotate-as-pure@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862"
@@ -130,34 +213,36 @@
"@babel/helper-explode-assignable-expression" "^7.18.6"
"@babel/types" "^7.18.6"
-"@babel/helper-compilation-targets@^7.13.0":
- version "7.17.7"
- resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz#a3c2924f5e5f0379b356d4cfb313d1414dc30e46"
- integrity sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==
+"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz#69e64f57b524cde3e5ff6cc5a9f4a387ee5563bf"
+ integrity sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==
dependencies:
- "@babel/compat-data" "^7.17.7"
- "@babel/helper-validator-option" "^7.16.7"
- browserslist "^4.17.5"
+ "@babel/compat-data" "^7.18.8"
+ "@babel/helper-validator-option" "^7.18.6"
+ browserslist "^4.20.2"
semver "^6.3.0"
-"@babel/helper-compilation-targets@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.6.tgz#18d35bfb9f83b1293c22c55b3d576c1315b6ed96"
- integrity sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg==
+"@babel/helper-compilation-targets@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb"
+ integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==
dependencies:
- "@babel/compat-data" "^7.18.6"
+ "@babel/compat-data" "^7.20.5"
"@babel/helper-validator-option" "^7.18.6"
- browserslist "^4.20.2"
+ browserslist "^4.21.3"
+ lru-cache "^5.1.1"
semver "^6.3.0"
-"@babel/helper-compilation-targets@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz#69e64f57b524cde3e5ff6cc5a9f4a387ee5563bf"
- integrity sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==
+"@babel/helper-compilation-targets@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz#631e6cc784c7b660417421349aac304c94115366"
+ integrity sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==
dependencies:
- "@babel/compat-data" "^7.18.8"
- "@babel/helper-validator-option" "^7.18.6"
- browserslist "^4.20.2"
+ "@babel/compat-data" "^7.21.5"
+ "@babel/helper-validator-option" "^7.21.0"
+ browserslist "^4.21.3"
+ lru-cache "^5.1.1"
semver "^6.3.0"
"@babel/helper-create-class-features-plugin@^7.18.6":
@@ -173,6 +258,20 @@
"@babel/helper-replace-supers" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
+"@babel/helper-create-class-features-plugin@^7.21.0":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.4.tgz#3a017163dc3c2ba7deb9a7950849a9586ea24c18"
+ integrity sha512-46QrX2CQlaFRF4TkwfTt6nJD7IHq8539cCL7SDpqWSDeJKY1xylKKY5F/33mJhLZ3mFvKv2gGrVS6NkyF6qs+Q==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-function-name" "^7.21.0"
+ "@babel/helper-member-expression-to-functions" "^7.21.0"
+ "@babel/helper-optimise-call-expression" "^7.18.6"
+ "@babel/helper-replace-supers" "^7.20.7"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+
"@babel/helper-create-regexp-features-plugin@^7.16.7":
version "7.17.0"
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz#1dcc7d40ba0c6b6b25618997c5dbfd310f186fe1"
@@ -189,27 +288,26 @@
"@babel/helper-annotate-as-pure" "^7.18.6"
regexpu-core "^5.1.0"
-"@babel/helper-define-polyfill-provider@^0.3.1":
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz#52411b445bdb2e676869e5a74960d2d3826d2665"
- integrity sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==
+"@babel/helper-create-regexp-features-plugin@^7.20.5":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.4.tgz#40411a8ab134258ad2cf3a3d987ec6aa0723cee5"
+ integrity sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA==
dependencies:
- "@babel/helper-compilation-targets" "^7.13.0"
- "@babel/helper-module-imports" "^7.12.13"
- "@babel/helper-plugin-utils" "^7.13.0"
- "@babel/traverse" "^7.13.0"
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ regexpu-core "^5.3.1"
+
+"@babel/helper-define-polyfill-provider@^0.3.3":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a"
+ integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.17.7"
+ "@babel/helper-plugin-utils" "^7.16.7"
debug "^4.1.1"
lodash.debounce "^4.0.8"
resolve "^1.14.2"
semver "^6.1.2"
-"@babel/helper-environment-visitor@^7.16.7":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7"
- integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==
- dependencies:
- "@babel/types" "^7.16.7"
-
"@babel/helper-environment-visitor@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz#b7eee2b5b9d70602e59d1a6cad7dd24de7ca6cd7"
@@ -220,6 +318,16 @@
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
+"@babel/helper-environment-visitor@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz#c769afefd41d171836f7cb63e295bedf689d48ba"
+ integrity sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==
+
+"@babel/helper-environment-visitor@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
+ integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
+
"@babel/helper-explode-assignable-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096"
@@ -227,15 +335,6 @@
dependencies:
"@babel/types" "^7.18.6"
-"@babel/helper-function-name@^7.16.7":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz#f1ec51551fb1c8956bc8dd95f38523b6cf375f8f"
- integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==
- dependencies:
- "@babel/helper-get-function-arity" "^7.16.7"
- "@babel/template" "^7.16.7"
- "@babel/types" "^7.16.7"
-
"@babel/helper-function-name@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz#8334fecb0afba66e6d87a7e8c6bb7fed79926b83"
@@ -252,19 +351,29 @@
"@babel/template" "^7.18.6"
"@babel/types" "^7.18.9"
-"@babel/helper-get-function-arity@^7.16.7":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419"
- integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==
+"@babel/helper-function-name@^7.19.0":
+ version "7.19.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c"
+ integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==
dependencies:
- "@babel/types" "^7.16.7"
+ "@babel/template" "^7.18.10"
+ "@babel/types" "^7.19.0"
-"@babel/helper-hoist-variables@^7.16.7":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246"
- integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==
+"@babel/helper-function-name@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4"
+ integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==
dependencies:
- "@babel/types" "^7.16.7"
+ "@babel/template" "^7.20.7"
+ "@babel/types" "^7.21.0"
+
+"@babel/helper-function-name@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
+ integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
+ dependencies:
+ "@babel/template" "^7.22.15"
+ "@babel/types" "^7.23.0"
"@babel/helper-hoist-variables@^7.18.6":
version "7.18.6"
@@ -273,6 +382,13 @@
dependencies:
"@babel/types" "^7.18.6"
+"@babel/helper-hoist-variables@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
+ integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
"@babel/helper-member-expression-to-functions@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.6.tgz#44802d7d602c285e1692db0bad9396d007be2afc"
@@ -280,19 +396,19 @@
dependencies:
"@babel/types" "^7.18.6"
-"@babel/helper-member-expression-to-functions@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815"
- integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==
+"@babel/helper-member-expression-to-functions@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz#a6f26e919582275a93c3aa6594756d71b0bb7f05"
+ integrity sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==
dependencies:
- "@babel/types" "^7.18.9"
+ "@babel/types" "^7.20.7"
-"@babel/helper-module-imports@^7.0.0":
- version "7.7.4"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz#e5a92529f8888bf319a6376abfbd1cebc491ad91"
- integrity sha512-dGcrX6K9l8258WFjyDLJwuVKxR4XZfU0/vTUgOQYWEnRD8mgr+p4d6fCUMq/ys0h4CCt/S5JhbvtyErjWouAUQ==
+"@babel/helper-member-expression-to-functions@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz#319c6a940431a133897148515877d2f3269c3ba5"
+ integrity sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==
dependencies:
- "@babel/types" "^7.7.4"
+ "@babel/types" "^7.21.0"
"@babel/helper-module-imports@^7.0.0-beta.49":
version "7.0.0"
@@ -300,13 +416,6 @@
dependencies:
"@babel/types" "^7.0.0"
-"@babel/helper-module-imports@^7.12.13":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437"
- integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==
- dependencies:
- "@babel/types" "^7.16.7"
-
"@babel/helper-module-imports@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e"
@@ -314,6 +423,20 @@
dependencies:
"@babel/types" "^7.18.6"
+"@babel/helper-module-imports@^7.21.4":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz#ac88b2f76093637489e718a90cec6cf8a9b029af"
+ integrity sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==
+ dependencies:
+ "@babel/types" "^7.21.4"
+
+"@babel/helper-module-imports@~7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0"
+ integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==
+ dependencies:
+ "@babel/types" "^7.22.15"
+
"@babel/helper-module-transforms@^7.18.6":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.8.tgz#4f8408afead0188cfa48672f9d0e5787b61778c8"
@@ -342,6 +465,34 @@
"@babel/traverse" "^7.18.9"
"@babel/types" "^7.18.9"
+"@babel/helper-module-transforms@^7.20.11":
+ version "7.21.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2"
+ integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-module-imports" "^7.18.6"
+ "@babel/helper-simple-access" "^7.20.2"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.21.2"
+ "@babel/types" "^7.21.2"
+
+"@babel/helper-module-transforms@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz#d937c82e9af68d31ab49039136a222b17ac0b420"
+ integrity sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.21.5"
+ "@babel/helper-module-imports" "^7.21.4"
+ "@babel/helper-simple-access" "^7.21.5"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.21.5"
+ "@babel/types" "^7.21.5"
+
"@babel/helper-optimise-call-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe"
@@ -354,7 +505,7 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250"
integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==
-"@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
+"@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5"
integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==
@@ -369,15 +520,35 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz#4b8aea3b069d8cb8a72cdfe28ddf5ceca695ef2f"
integrity sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==
-"@babel/helper-remap-async-to-generator@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.6.tgz#fa1f81acd19daee9d73de297c0308783cd3cfc23"
- integrity sha512-z5wbmV55TveUPZlCLZvxWHtrjuJd+8inFhk7DG0WW87/oJuGDcjDiu7HIvGcpf5464L6xKCg3vNkmlVVz9hwyQ==
+"@babel/helper-plugin-utils@^7.19.0":
+ version "7.19.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf"
+ integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==
+
+"@babel/helper-plugin-utils@^7.20.2":
+ version "7.20.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629"
+ integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==
+
+"@babel/helper-plugin-utils@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz#345f2377d05a720a4e5ecfa39cbf4474a4daed56"
+ integrity sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==
+
+"@babel/helper-plugin-utils@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295"
+ integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==
+
+"@babel/helper-remap-async-to-generator@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519"
+ integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==
dependencies:
"@babel/helper-annotate-as-pure" "^7.18.6"
- "@babel/helper-environment-visitor" "^7.18.6"
- "@babel/helper-wrap-function" "^7.18.6"
- "@babel/types" "^7.18.6"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-wrap-function" "^7.18.9"
+ "@babel/types" "^7.18.9"
"@babel/helper-replace-supers@^7.18.6":
version "7.18.6"
@@ -390,16 +561,17 @@
"@babel/traverse" "^7.18.6"
"@babel/types" "^7.18.6"
-"@babel/helper-replace-supers@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz#1092e002feca980fbbb0bd4d51b74a65c6a500e6"
- integrity sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ==
+"@babel/helper-replace-supers@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz#243ecd2724d2071532b2c8ad2f0f9f083bcae331"
+ integrity sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==
dependencies:
"@babel/helper-environment-visitor" "^7.18.9"
- "@babel/helper-member-expression-to-functions" "^7.18.9"
+ "@babel/helper-member-expression-to-functions" "^7.20.7"
"@babel/helper-optimise-call-expression" "^7.18.6"
- "@babel/traverse" "^7.18.9"
- "@babel/types" "^7.18.9"
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.20.7"
+ "@babel/types" "^7.20.7"
"@babel/helper-simple-access@^7.18.6":
version "7.18.6"
@@ -408,19 +580,26 @@
dependencies:
"@babel/types" "^7.18.6"
-"@babel/helper-skip-transparent-expression-wrappers@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz#778d87b3a758d90b471e7b9918f34a9a02eb5818"
- integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==
+"@babel/helper-simple-access@^7.20.2":
+ version "7.20.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9"
+ integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==
dependencies:
- "@babel/types" "^7.18.9"
+ "@babel/types" "^7.20.2"
-"@babel/helper-split-export-declaration@^7.16.7":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b"
- integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==
+"@babel/helper-simple-access@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz#d697a7971a5c39eac32c7e63c0921c06c8a249ee"
+ integrity sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==
dependencies:
- "@babel/types" "^7.16.7"
+ "@babel/types" "^7.21.5"
+
+"@babel/helper-skip-transparent-expression-wrappers@^7.20.0":
+ version "7.20.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684"
+ integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==
+ dependencies:
+ "@babel/types" "^7.20.0"
"@babel/helper-split-export-declaration@^7.18.6":
version "7.18.6"
@@ -429,6 +608,33 @@
dependencies:
"@babel/types" "^7.18.6"
+"@babel/helper-split-export-declaration@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
+ integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-string-parser@^7.18.10":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56"
+ integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==
+
+"@babel/helper-string-parser@^7.19.4":
+ version "7.19.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63"
+ integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==
+
+"@babel/helper-string-parser@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd"
+ integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==
+
+"@babel/helper-string-parser@^7.23.4":
+ version "7.23.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83"
+ integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==
+
"@babel/helper-validator-identifier@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad"
@@ -439,34 +645,35 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
-"@babel/helper-validator-option@^7.16.7":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23"
- integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==
+"@babel/helper-validator-identifier@^7.19.1":
+ version "7.19.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
+ integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==
+
+"@babel/helper-validator-identifier@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
+ integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
"@babel/helper-validator-option@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8"
integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==
-"@babel/helper-wrap-function@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.6.tgz#ec44ea4ad9d8988b90c3e465ba2382f4de81a073"
- integrity sha512-I5/LZfozwMNbwr/b1vhhuYD+J/mU+gfGAj5td7l5Rv9WYmH6i3Om69WGKNmlIpsVW/mF6O5bvTKbvDQZVgjqOw==
- dependencies:
- "@babel/helper-function-name" "^7.18.6"
- "@babel/template" "^7.18.6"
- "@babel/traverse" "^7.18.6"
- "@babel/types" "^7.18.6"
+"@babel/helper-validator-option@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180"
+ integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==
-"@babel/helpers@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.6.tgz#4c966140eaa1fcaa3d5a8c09d7db61077d4debfd"
- integrity sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ==
+"@babel/helper-wrap-function@^7.18.9":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.10.tgz#a7fcd3ab9b1be4c9b52cf7d7fdc1e88c2ce93396"
+ integrity sha512-95NLBP59VWdfK2lyLKe6eTMq9xg+yWKzxzxbJ1wcYNi1Auz200+83fMDADjRxBvc2QQor5zja2yTQzXGhk2GtQ==
dependencies:
- "@babel/template" "^7.18.6"
- "@babel/traverse" "^7.18.6"
- "@babel/types" "^7.18.6"
+ "@babel/helper-function-name" "^7.18.9"
+ "@babel/template" "^7.18.10"
+ "@babel/traverse" "^7.18.10"
+ "@babel/types" "^7.18.10"
"@babel/helpers@^7.18.9":
version "7.18.9"
@@ -477,6 +684,15 @@
"@babel/traverse" "^7.18.9"
"@babel/types" "^7.18.9"
+"@babel/helpers@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.5.tgz#5bac66e084d7a4d2d9696bdf0175a93f7fb63c08"
+ integrity sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==
+ dependencies:
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.21.5"
+ "@babel/types" "^7.21.5"
+
"@babel/highlight@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4"
@@ -485,15 +701,6 @@
esutils "^2.0.2"
js-tokens "^4.0.0"
-"@babel/highlight@^7.16.7":
- version "7.16.10"
- resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88"
- integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==
- dependencies:
- "@babel/helper-validator-identifier" "^7.16.7"
- chalk "^2.0.0"
- js-tokens "^4.0.0"
-
"@babel/highlight@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
@@ -503,15 +710,39 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
+"@babel/highlight@^7.23.4":
+ version "7.23.4"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b"
+ integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.22.20"
+ chalk "^2.4.2"
+ js-tokens "^4.0.0"
+
+"@babel/highlight@^7.24.2":
+ version "7.24.2"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26"
+ integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.22.20"
+ chalk "^2.4.2"
+ js-tokens "^4.0.0"
+ picocolors "^1.0.0"
+
+"@babel/parser@^7.14.7":
+ version "7.18.11"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9"
+ integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==
+
"@babel/parser@^7.16.4":
version "7.17.8"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.8.tgz#2817fb9d885dd8132ea0f8eb615a6388cca1c240"
integrity sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==
-"@babel/parser@^7.16.7", "@babel/parser@^7.17.3":
- version "7.17.7"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.7.tgz#fc19b645a5456c8d6fdb6cecd3c66c0173902800"
- integrity sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA==
+"@babel/parser@^7.18.10":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.10.tgz#94b5f8522356e69e8277276adf67ed280c90ecc1"
+ integrity sha512-TYk3OA0HKL6qNryUayb5UUEhM/rkOQozIBEA5ITXh5DWrSp0TlUQXMyZmnWxG/DizSWBeeQ0Zbc5z8UGaaqoeg==
"@babel/parser@^7.18.6", "@babel/parser@^7.18.8":
version "7.18.8"
@@ -523,6 +754,31 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.9.tgz#f2dde0c682ccc264a9a8595efd030a5cc8fd2539"
integrity sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg==
+"@babel/parser@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.7.tgz#66fe23b3c8569220817d5feb8b9dcdc95bb4f71b"
+ integrity sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==
+
+"@babel/parser@^7.21.4":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17"
+ integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==
+
+"@babel/parser@^7.21.5", "@babel/parser@^7.21.8":
+ version "7.21.8"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8"
+ integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==
+
+"@babel/parser@^7.22.15", "@babel/parser@^7.23.6":
+ version "7.23.6"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b"
+ integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==
+
+"@babel/parser@^7.23.9", "@babel/parser@^7.24.0", "@babel/parser@^7.24.1":
+ version "7.24.1"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a"
+ integrity sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==
+
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
@@ -530,23 +786,23 @@
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz#a11af19aa373d68d561f08e0a57242350ed0ec50"
- integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz#d9c85589258539a22a901033853101a6198d4ef1"
+ integrity sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.9"
- "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9"
- "@babel/plugin-proposal-optional-chaining" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
+ "@babel/plugin-proposal-optional-chaining" "^7.20.7"
-"@babel/plugin-proposal-async-generator-functions@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.6.tgz#aedac81e6fc12bb643374656dd5f2605bf743d17"
- integrity sha512-WAz4R9bvozx4qwf74M+sfqPMKfSqwM0phxPTR6iJIi8robgzXwkEgmeJG1gEKhm6sDqT/U9aV3lfcqybIpev8w==
+"@babel/plugin-proposal-async-generator-functions@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz#bfb7276d2d573cb67ba379984a2334e262ba5326"
+ integrity sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==
dependencies:
- "@babel/helper-environment-visitor" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
- "@babel/helper-remap-async-to-generator" "^7.18.6"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-remap-async-to-generator" "^7.18.9"
"@babel/plugin-syntax-async-generators" "^7.8.4"
"@babel/plugin-proposal-class-properties@^7.18.6":
@@ -557,13 +813,13 @@
"@babel/helper-create-class-features-plugin" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-proposal-class-static-block@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz#8aa81d403ab72d3962fc06c26e222dacfc9b9020"
- integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==
+"@babel/plugin-proposal-class-static-block@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz#77bdd66fb7b605f3a61302d224bdfacf5547977d"
+ integrity sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==
dependencies:
- "@babel/helper-create-class-features-plugin" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-create-class-features-plugin" "^7.21.0"
+ "@babel/helper-plugin-utils" "^7.20.2"
"@babel/plugin-syntax-class-static-block" "^7.14.5"
"@babel/plugin-proposal-dynamic-import@^7.18.6":
@@ -590,12 +846,12 @@
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-syntax-json-strings" "^7.8.3"
-"@babel/plugin-proposal-logical-assignment-operators@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz#8148cbb350483bf6220af06fa6db3690e14b2e23"
- integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==
+"@babel/plugin-proposal-logical-assignment-operators@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz#dfbcaa8f7b4d37b51e8bfb46d94a5aea2bb89d83"
+ integrity sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.20.2"
"@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6":
@@ -614,16 +870,16 @@
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-syntax-numeric-separator" "^7.10.4"
-"@babel/plugin-proposal-object-rest-spread@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz#f9434f6beb2c8cae9dfcf97d2a5941bbbf9ad4e7"
- integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==
+"@babel/plugin-proposal-object-rest-spread@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a"
+ integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==
dependencies:
- "@babel/compat-data" "^7.18.8"
- "@babel/helper-compilation-targets" "^7.18.9"
- "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/compat-data" "^7.20.5"
+ "@babel/helper-compilation-targets" "^7.20.7"
+ "@babel/helper-plugin-utils" "^7.20.2"
"@babel/plugin-syntax-object-rest-spread" "^7.8.3"
- "@babel/plugin-transform-parameters" "^7.18.8"
+ "@babel/plugin-transform-parameters" "^7.20.7"
"@babel/plugin-proposal-optional-catch-binding@^7.18.6":
version "7.18.6"
@@ -633,13 +889,13 @@
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
-"@babel/plugin-proposal-optional-chaining@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz#e8e8fe0723f2563960e4bf5e9690933691915993"
- integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==
+"@babel/plugin-proposal-optional-chaining@^7.20.7", "@babel/plugin-proposal-optional-chaining@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea"
+ integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.9"
- "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
"@babel/plugin-syntax-optional-chaining" "^7.8.3"
"@babel/plugin-proposal-private-methods@^7.18.6":
@@ -650,14 +906,14 @@
"@babel/helper-create-class-features-plugin" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-proposal-private-property-in-object@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz#a64137b232f0aca3733a67eb1a144c192389c503"
- integrity sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==
+"@babel/plugin-proposal-private-property-in-object@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz#19496bd9883dd83c23c7d7fc45dcd9ad02dfa1dc"
+ integrity sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==
dependencies:
"@babel/helper-annotate-as-pure" "^7.18.6"
- "@babel/helper-create-class-features-plugin" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-create-class-features-plugin" "^7.21.0"
+ "@babel/helper-plugin-utils" "^7.20.2"
"@babel/plugin-syntax-private-property-in-object" "^7.14.5"
"@babel/plugin-proposal-unicode-property-regex@^7.18.6":
@@ -711,12 +967,19 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
-"@babel/plugin-syntax-import-assertions@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz#cd6190500a4fa2fe31990a963ffab4b63e4505e4"
- integrity sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==
+"@babel/plugin-syntax-import-assertions@^7.20.0":
+ version "7.20.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4"
+ integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.19.0"
+
+"@babel/plugin-syntax-import-meta@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51"
+ integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
"@babel/plugin-syntax-json-strings@^7.8.3":
version "7.8.3"
@@ -725,12 +988,12 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
-"@babel/plugin-syntax-jsx@^7.0.0":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665"
- integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==
+"@babel/plugin-syntax-jsx@^7.23.3":
+ version "7.23.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz#8f2e4f8a9b5f9aa16067e142c1ac9cd9f810f473"
+ integrity sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==
dependencies:
- "@babel/helper-plugin-utils" "^7.16.7"
+ "@babel/helper-plugin-utils" "^7.22.5"
"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
version "7.10.4"
@@ -788,21 +1051,21 @@
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"
-"@babel/plugin-transform-arrow-functions@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe"
- integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==
+"@babel/plugin-transform-arrow-functions@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz#9bb42a53de447936a57ba256fbf537fc312b6929"
+ integrity sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.21.5"
-"@babel/plugin-transform-async-to-generator@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz#ccda3d1ab9d5ced5265fdb13f1882d5476c71615"
- integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==
+"@babel/plugin-transform-async-to-generator@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354"
+ integrity sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==
dependencies:
"@babel/helper-module-imports" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
- "@babel/helper-remap-async-to-generator" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-remap-async-to-generator" "^7.18.9"
"@babel/plugin-transform-block-scoped-functions@^7.18.6":
version "7.18.6"
@@ -811,40 +1074,42 @@
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-transform-block-scoping@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz#f9b7e018ac3f373c81452d6ada8bd5a18928926d"
- integrity sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==
+"@babel/plugin-transform-block-scoping@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz#e737b91037e5186ee16b76e7ae093358a5634f02"
+ integrity sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.20.2"
-"@babel/plugin-transform-classes@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz#90818efc5b9746879b869d5ce83eb2aa48bbc3da"
- integrity sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g==
+"@babel/plugin-transform-classes@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz#f469d0b07a4c5a7dbb21afad9e27e57b47031665"
+ integrity sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==
dependencies:
"@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-compilation-targets" "^7.20.7"
"@babel/helper-environment-visitor" "^7.18.9"
- "@babel/helper-function-name" "^7.18.9"
+ "@babel/helper-function-name" "^7.21.0"
"@babel/helper-optimise-call-expression" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.9"
- "@babel/helper-replace-supers" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-replace-supers" "^7.20.7"
"@babel/helper-split-export-declaration" "^7.18.6"
globals "^11.1.0"
-"@babel/plugin-transform-computed-properties@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e"
- integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==
+"@babel/plugin-transform-computed-properties@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz#3a2d8bb771cd2ef1cd736435f6552fe502e11b44"
+ integrity sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/template" "^7.20.7"
-"@babel/plugin-transform-destructuring@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz#68906549c021cb231bee1db21d3b5b095f8ee292"
- integrity sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA==
+"@babel/plugin-transform-destructuring@^7.21.3":
+ version "7.21.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz#73b46d0fd11cd6ef57dea8a381b1215f4959d401"
+ integrity sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.20.2"
"@babel/plugin-transform-dotall-regex@^7.18.6":
version "7.18.6"
@@ -877,12 +1142,12 @@
"@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-transform-for-of@^7.18.8":
- version "7.18.8"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1"
- integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==
+"@babel/plugin-transform-for-of@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc"
+ integrity sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.21.5"
"@babel/plugin-transform-function-name@^7.18.9":
version "7.18.9"
@@ -907,35 +1172,32 @@
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-transform-modules-amd@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz#8c91f8c5115d2202f277549848874027d7172d21"
- integrity sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==
+"@babel/plugin-transform-modules-amd@^7.20.11":
+ version "7.20.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz#3daccca8e4cc309f03c3a0c4b41dc4b26f55214a"
+ integrity sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==
dependencies:
- "@babel/helper-module-transforms" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
- babel-plugin-dynamic-import-node "^2.3.3"
+ "@babel/helper-module-transforms" "^7.20.11"
+ "@babel/helper-plugin-utils" "^7.20.2"
-"@babel/plugin-transform-modules-commonjs@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz#afd243afba166cca69892e24a8fd8c9f2ca87883"
- integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==
+"@babel/plugin-transform-modules-commonjs@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz#d69fb947eed51af91de82e4708f676864e5e47bc"
+ integrity sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ==
dependencies:
- "@babel/helper-module-transforms" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
- "@babel/helper-simple-access" "^7.18.6"
- babel-plugin-dynamic-import-node "^2.3.3"
+ "@babel/helper-module-transforms" "^7.21.5"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-simple-access" "^7.21.5"
-"@babel/plugin-transform-modules-systemjs@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.9.tgz#545df284a7ac6a05125e3e405e536c5853099a06"
- integrity sha512-zY/VSIbbqtoRoJKo2cDTewL364jSlZGvn0LKOf9ntbfxOvjfmyrdtEEOAdswOswhZEb8UH3jDkCKHd1sPgsS0A==
+"@babel/plugin-transform-modules-systemjs@^7.20.11":
+ version "7.20.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz#467ec6bba6b6a50634eea61c9c232654d8a4696e"
+ integrity sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==
dependencies:
"@babel/helper-hoist-variables" "^7.18.6"
- "@babel/helper-module-transforms" "^7.18.9"
- "@babel/helper-plugin-utils" "^7.18.9"
- "@babel/helper-validator-identifier" "^7.18.6"
- babel-plugin-dynamic-import-node "^2.3.3"
+ "@babel/helper-module-transforms" "^7.20.11"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-validator-identifier" "^7.19.1"
"@babel/plugin-transform-modules-umd@^7.18.6":
version "7.18.6"
@@ -945,13 +1207,13 @@
"@babel/helper-module-transforms" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz#c89bfbc7cc6805d692f3a49bc5fc1b630007246d"
- integrity sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==
+"@babel/plugin-transform-named-capturing-groups-regex@^7.20.5":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz#626298dd62ea51d452c3be58b285d23195ba69a8"
+ integrity sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==
dependencies:
- "@babel/helper-create-regexp-features-plugin" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-create-regexp-features-plugin" "^7.20.5"
+ "@babel/helper-plugin-utils" "^7.20.2"
"@babel/plugin-transform-new-target@^7.18.6":
version "7.18.6"
@@ -968,12 +1230,19 @@
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/helper-replace-supers" "^7.18.6"
-"@babel/plugin-transform-parameters@^7.18.8":
- version "7.18.8"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz#ee9f1a0ce6d78af58d0956a9378ea3427cccb48a"
- integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==
+"@babel/plugin-transform-parameters@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz#0ee349e9d1bc96e78e3b37a7af423a4078a7083f"
+ integrity sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.20.2"
+
+"@babel/plugin-transform-parameters@^7.21.3":
+ version "7.21.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz#18fc4e797cf6d6d972cb8c411dbe8a809fa157db"
+ integrity sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.20.2"
"@babel/plugin-transform-property-literals@^7.18.6":
version "7.18.6"
@@ -982,13 +1251,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-transform-regenerator@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz#585c66cb84d4b4bf72519a34cfce761b8676ca73"
- integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==
+"@babel/plugin-transform-regenerator@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz#576c62f9923f94bcb1c855adc53561fd7913724e"
+ integrity sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
- regenerator-transform "^0.15.0"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ regenerator-transform "^0.15.1"
"@babel/plugin-transform-reserved-words@^7.18.6":
version "7.18.6"
@@ -997,16 +1266,16 @@
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-transform-runtime@7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.9.tgz#d9e4b1b25719307bfafbf43065ed7fb3a83adb8f"
- integrity sha512-wS8uJwBt7/b/mzE13ktsJdmS4JP/j7PQSaADtnb4I2wL0zK51MQ0pmF8/Jy0wUIS96fr+fXT6S/ifiPXnvrlSg==
+"@babel/plugin-transform-runtime@7.21.4":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.4.tgz#2e1da21ca597a7d01fc96b699b21d8d2023191aa"
+ integrity sha512-1J4dhrw1h1PqnNNpzwxQ2UBymJUF8KuPjAAnlLwZcGhHAIqUigFW7cdK6GHoB64ubY4qXQNYknoUeks4Wz7CUA==
dependencies:
- "@babel/helper-module-imports" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.9"
- babel-plugin-polyfill-corejs2 "^0.3.1"
- babel-plugin-polyfill-corejs3 "^0.5.2"
- babel-plugin-polyfill-regenerator "^0.3.1"
+ "@babel/helper-module-imports" "^7.21.4"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ babel-plugin-polyfill-corejs2 "^0.3.3"
+ babel-plugin-polyfill-corejs3 "^0.6.0"
+ babel-plugin-polyfill-regenerator "^0.4.1"
semver "^6.3.0"
"@babel/plugin-transform-shorthand-properties@^7.18.6":
@@ -1016,13 +1285,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-transform-spread@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz#6ea7a6297740f381c540ac56caf75b05b74fb664"
- integrity sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA==
+"@babel/plugin-transform-spread@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz#c2d83e0b99d3bf83e07b11995ee24bf7ca09401e"
+ integrity sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.9"
- "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
"@babel/plugin-transform-sticky-regex@^7.18.6":
version "7.18.6"
@@ -1045,12 +1314,12 @@
dependencies:
"@babel/helper-plugin-utils" "^7.18.9"
-"@babel/plugin-transform-unicode-escapes@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.6.tgz#0d01fb7fb2243ae1c033f65f6e3b4be78db75f27"
- integrity sha512-XNRwQUXYMP7VLuy54cr/KS/WeL3AZeORhrmeZ7iewgu+X2eBqmpaLI/hzqr9ZxCeUoq0ASK4GUzSM0BDhZkLFw==
+"@babel/plugin-transform-unicode-escapes@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz#1e55ed6195259b0e9061d81f5ef45a9b009fb7f2"
+ integrity sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.21.5"
"@babel/plugin-transform-unicode-regex@^7.18.6":
version "7.18.6"
@@ -1060,38 +1329,39 @@
"@babel/helper-create-regexp-features-plugin" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/preset-env@7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.9.tgz#9b3425140d724fbe590322017466580844c7eaff"
- integrity sha512-75pt/q95cMIHWssYtyfjVlvI+QEZQThQbKvR9xH+F/Agtw/s4Wfc2V9Bwd/P39VtixB7oWxGdH4GteTTwYJWMg==
+"@babel/preset-env@7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.21.5.tgz#db2089d99efd2297716f018aeead815ac3decffb"
+ integrity sha512-wH00QnTTldTbf/IefEVyChtRdw5RJvODT/Vb4Vcxq1AZvtXj6T0YeX0cAcXhI6/BdGuiP3GcNIL4OQbI2DVNxg==
dependencies:
- "@babel/compat-data" "^7.18.8"
- "@babel/helper-compilation-targets" "^7.18.9"
- "@babel/helper-plugin-utils" "^7.18.9"
- "@babel/helper-validator-option" "^7.18.6"
+ "@babel/compat-data" "^7.21.5"
+ "@babel/helper-compilation-targets" "^7.21.5"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-validator-option" "^7.21.0"
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6"
- "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9"
- "@babel/plugin-proposal-async-generator-functions" "^7.18.6"
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.20.7"
+ "@babel/plugin-proposal-async-generator-functions" "^7.20.7"
"@babel/plugin-proposal-class-properties" "^7.18.6"
- "@babel/plugin-proposal-class-static-block" "^7.18.6"
+ "@babel/plugin-proposal-class-static-block" "^7.21.0"
"@babel/plugin-proposal-dynamic-import" "^7.18.6"
"@babel/plugin-proposal-export-namespace-from" "^7.18.9"
"@babel/plugin-proposal-json-strings" "^7.18.6"
- "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9"
+ "@babel/plugin-proposal-logical-assignment-operators" "^7.20.7"
"@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6"
"@babel/plugin-proposal-numeric-separator" "^7.18.6"
- "@babel/plugin-proposal-object-rest-spread" "^7.18.9"
+ "@babel/plugin-proposal-object-rest-spread" "^7.20.7"
"@babel/plugin-proposal-optional-catch-binding" "^7.18.6"
- "@babel/plugin-proposal-optional-chaining" "^7.18.9"
+ "@babel/plugin-proposal-optional-chaining" "^7.21.0"
"@babel/plugin-proposal-private-methods" "^7.18.6"
- "@babel/plugin-proposal-private-property-in-object" "^7.18.6"
+ "@babel/plugin-proposal-private-property-in-object" "^7.21.0"
"@babel/plugin-proposal-unicode-property-regex" "^7.18.6"
"@babel/plugin-syntax-async-generators" "^7.8.4"
"@babel/plugin-syntax-class-properties" "^7.12.13"
"@babel/plugin-syntax-class-static-block" "^7.14.5"
"@babel/plugin-syntax-dynamic-import" "^7.8.3"
"@babel/plugin-syntax-export-namespace-from" "^7.8.3"
- "@babel/plugin-syntax-import-assertions" "^7.18.6"
+ "@babel/plugin-syntax-import-assertions" "^7.20.0"
+ "@babel/plugin-syntax-import-meta" "^7.10.4"
"@babel/plugin-syntax-json-strings" "^7.8.3"
"@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
"@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
@@ -1101,44 +1371,44 @@
"@babel/plugin-syntax-optional-chaining" "^7.8.3"
"@babel/plugin-syntax-private-property-in-object" "^7.14.5"
"@babel/plugin-syntax-top-level-await" "^7.14.5"
- "@babel/plugin-transform-arrow-functions" "^7.18.6"
- "@babel/plugin-transform-async-to-generator" "^7.18.6"
+ "@babel/plugin-transform-arrow-functions" "^7.21.5"
+ "@babel/plugin-transform-async-to-generator" "^7.20.7"
"@babel/plugin-transform-block-scoped-functions" "^7.18.6"
- "@babel/plugin-transform-block-scoping" "^7.18.9"
- "@babel/plugin-transform-classes" "^7.18.9"
- "@babel/plugin-transform-computed-properties" "^7.18.9"
- "@babel/plugin-transform-destructuring" "^7.18.9"
+ "@babel/plugin-transform-block-scoping" "^7.21.0"
+ "@babel/plugin-transform-classes" "^7.21.0"
+ "@babel/plugin-transform-computed-properties" "^7.21.5"
+ "@babel/plugin-transform-destructuring" "^7.21.3"
"@babel/plugin-transform-dotall-regex" "^7.18.6"
"@babel/plugin-transform-duplicate-keys" "^7.18.9"
"@babel/plugin-transform-exponentiation-operator" "^7.18.6"
- "@babel/plugin-transform-for-of" "^7.18.8"
+ "@babel/plugin-transform-for-of" "^7.21.5"
"@babel/plugin-transform-function-name" "^7.18.9"
"@babel/plugin-transform-literals" "^7.18.9"
"@babel/plugin-transform-member-expression-literals" "^7.18.6"
- "@babel/plugin-transform-modules-amd" "^7.18.6"
- "@babel/plugin-transform-modules-commonjs" "^7.18.6"
- "@babel/plugin-transform-modules-systemjs" "^7.18.9"
+ "@babel/plugin-transform-modules-amd" "^7.20.11"
+ "@babel/plugin-transform-modules-commonjs" "^7.21.5"
+ "@babel/plugin-transform-modules-systemjs" "^7.20.11"
"@babel/plugin-transform-modules-umd" "^7.18.6"
- "@babel/plugin-transform-named-capturing-groups-regex" "^7.18.6"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.20.5"
"@babel/plugin-transform-new-target" "^7.18.6"
"@babel/plugin-transform-object-super" "^7.18.6"
- "@babel/plugin-transform-parameters" "^7.18.8"
+ "@babel/plugin-transform-parameters" "^7.21.3"
"@babel/plugin-transform-property-literals" "^7.18.6"
- "@babel/plugin-transform-regenerator" "^7.18.6"
+ "@babel/plugin-transform-regenerator" "^7.21.5"
"@babel/plugin-transform-reserved-words" "^7.18.6"
"@babel/plugin-transform-shorthand-properties" "^7.18.6"
- "@babel/plugin-transform-spread" "^7.18.9"
+ "@babel/plugin-transform-spread" "^7.20.7"
"@babel/plugin-transform-sticky-regex" "^7.18.6"
"@babel/plugin-transform-template-literals" "^7.18.9"
"@babel/plugin-transform-typeof-symbol" "^7.18.9"
- "@babel/plugin-transform-unicode-escapes" "^7.18.6"
+ "@babel/plugin-transform-unicode-escapes" "^7.21.5"
"@babel/plugin-transform-unicode-regex" "^7.18.6"
"@babel/preset-modules" "^0.1.5"
- "@babel/types" "^7.18.9"
- babel-plugin-polyfill-corejs2 "^0.3.1"
- babel-plugin-polyfill-corejs3 "^0.5.2"
- babel-plugin-polyfill-regenerator "^0.3.1"
- core-js-compat "^3.22.1"
+ "@babel/types" "^7.21.5"
+ babel-plugin-polyfill-corejs2 "^0.3.3"
+ babel-plugin-polyfill-corejs3 "^0.6.0"
+ babel-plugin-polyfill-regenerator "^0.4.1"
+ core-js-compat "^3.25.1"
semver "^6.3.0"
"@babel/preset-modules@^0.1.5":
@@ -1152,10 +1422,10 @@
"@babel/types" "^7.4.4"
esutils "^2.0.2"
-"@babel/register@7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.18.9.tgz#1888b24bc28d5cc41c412feb015e9ff6b96e439c"
- integrity sha512-ZlbnXDcNYHMR25ITwwNKT88JiaukkdVj/nG7r3wnuXkOTHc60Uy05PwMCPre0hSkY68E6zK3xz+vUJSP2jWmcw==
+"@babel/register@7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.21.0.tgz#c97bf56c2472e063774f31d344c592ebdcefa132"
+ integrity sha512-9nKsPmYDi5DidAqJaQooxIhsLJiNMkGr8ypQ8Uic7cIox7UCDsM7HuUGxdGT7mSDTYbqzIdsOWzfBton/YJrMw==
dependencies:
clone-deep "^4.0.1"
find-cache-dir "^2.0.0"
@@ -1163,12 +1433,17 @@
pirates "^4.0.5"
source-map-support "^0.5.16"
-"@babel/runtime@7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
- integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
+"@babel/regjsgen@^0.8.0":
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
+ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
+
+"@babel/runtime@7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
+ integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==
dependencies:
- regenerator-runtime "^0.13.4"
+ regenerator-runtime "^0.13.11"
"@babel/runtime@^7.8.4":
version "7.17.7"
@@ -1177,14 +1452,14 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@babel/template@^7.0.0", "@babel/template@^7.16.7":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
- integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==
+"@babel/template@^7.18.10":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71"
+ integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==
dependencies:
- "@babel/code-frame" "^7.16.7"
- "@babel/parser" "^7.16.7"
- "@babel/types" "^7.16.7"
+ "@babel/code-frame" "^7.18.6"
+ "@babel/parser" "^7.18.10"
+ "@babel/types" "^7.18.10"
"@babel/template@^7.18.6":
version "7.18.6"
@@ -1195,19 +1470,46 @@
"@babel/parser" "^7.18.6"
"@babel/types" "^7.18.6"
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.13.0":
- version "7.17.3"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57"
- integrity sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==
- dependencies:
- "@babel/code-frame" "^7.16.7"
- "@babel/generator" "^7.17.3"
- "@babel/helper-environment-visitor" "^7.16.7"
- "@babel/helper-function-name" "^7.16.7"
- "@babel/helper-hoist-variables" "^7.16.7"
- "@babel/helper-split-export-declaration" "^7.16.7"
- "@babel/parser" "^7.17.3"
- "@babel/types" "^7.17.0"
+"@babel/template@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8"
+ integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==
+ dependencies:
+ "@babel/code-frame" "^7.18.6"
+ "@babel/parser" "^7.20.7"
+ "@babel/types" "^7.20.7"
+
+"@babel/template@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
+ integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
+ dependencies:
+ "@babel/code-frame" "^7.22.13"
+ "@babel/parser" "^7.22.15"
+ "@babel/types" "^7.22.15"
+
+"@babel/template@^7.23.9":
+ version "7.24.0"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50"
+ integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==
+ dependencies:
+ "@babel/code-frame" "^7.23.5"
+ "@babel/parser" "^7.24.0"
+ "@babel/types" "^7.24.0"
+
+"@babel/traverse@^7.18.10":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.10.tgz#37ad97d1cb00efa869b91dd5d1950f8a6cf0cb08"
+ integrity sha512-J7ycxg0/K9XCtLyHf0cz2DqDihonJeIo+z+HEdRe9YuT8TY4A66i+Ab2/xZCEW7Ro60bPCBBfqqboHSamoV3+g==
+ dependencies:
+ "@babel/code-frame" "^7.18.6"
+ "@babel/generator" "^7.18.10"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-function-name" "^7.18.9"
+ "@babel/helper-hoist-variables" "^7.18.6"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/parser" "^7.18.10"
+ "@babel/types" "^7.18.10"
debug "^4.1.0"
globals "^11.1.0"
@@ -1243,6 +1545,70 @@
debug "^4.1.0"
globals "^11.1.0"
+"@babel/traverse@^7.20.7":
+ version "7.20.8"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.8.tgz#e3a23eb04af24f8bbe8a8ba3eef6155b77df0b08"
+ integrity sha512-/RNkaYDeCy4MjyV70+QkSHhxbvj2JO/5Ft2Pa880qJOG8tWrqcT/wXUuCCv43yogfqPzHL77Xu101KQPf4clnQ==
+ dependencies:
+ "@babel/code-frame" "^7.18.6"
+ "@babel/generator" "^7.20.7"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-function-name" "^7.19.0"
+ "@babel/helper-hoist-variables" "^7.18.6"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/parser" "^7.20.7"
+ "@babel/types" "^7.20.7"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
+"@babel/traverse@^7.21.2":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.4.tgz#a836aca7b116634e97a6ed99976236b3282c9d36"
+ integrity sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==
+ dependencies:
+ "@babel/code-frame" "^7.21.4"
+ "@babel/generator" "^7.21.4"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-function-name" "^7.21.0"
+ "@babel/helper-hoist-variables" "^7.18.6"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/parser" "^7.21.4"
+ "@babel/types" "^7.21.4"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
+"@babel/traverse@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.5.tgz#ad22361d352a5154b498299d523cf72998a4b133"
+ integrity sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==
+ dependencies:
+ "@babel/code-frame" "^7.21.4"
+ "@babel/generator" "^7.21.5"
+ "@babel/helper-environment-visitor" "^7.21.5"
+ "@babel/helper-function-name" "^7.21.0"
+ "@babel/helper-hoist-variables" "^7.18.6"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/parser" "^7.21.5"
+ "@babel/types" "^7.21.5"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
+"@babel/traverse@^7.23.9":
+ version "7.24.1"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.1.tgz#d65c36ac9dd17282175d1e4a3c49d5b7988f530c"
+ integrity sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==
+ dependencies:
+ "@babel/code-frame" "^7.24.1"
+ "@babel/generator" "^7.24.1"
+ "@babel/helper-environment-visitor" "^7.22.20"
+ "@babel/helper-function-name" "^7.23.0"
+ "@babel/helper-hoist-variables" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ "@babel/parser" "^7.24.1"
+ "@babel/types" "^7.24.0"
+ debug "^4.3.1"
+ globals "^11.1.0"
+
"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49":
version "7.2.2"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.2.2.tgz#44e10fc24e33af524488b716cdaee5360ea8ed1e"
@@ -1251,7 +1617,7 @@
lodash "^4.17.10"
to-fast-properties "^2.0.0"
-"@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.4.4":
+"@babel/types@^7.16.7", "@babel/types@^7.4.4":
version "7.17.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b"
integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==
@@ -1259,6 +1625,15 @@
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
+"@babel/types@^7.18.10":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.10.tgz#4908e81b6b339ca7c6b7a555a5fc29446f26dde6"
+ integrity sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==
+ dependencies:
+ "@babel/helper-string-parser" "^7.18.10"
+ "@babel/helper-validator-identifier" "^7.18.6"
+ to-fast-properties "^2.0.0"
+
"@babel/types@^7.18.6", "@babel/types@^7.18.7", "@babel/types@^7.18.8":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.8.tgz#c5af199951bf41ba4a6a9a6d0d8ad722b30cd42f"
@@ -1275,13 +1650,67 @@
"@babel/helper-validator-identifier" "^7.18.6"
to-fast-properties "^2.0.0"
-"@babel/types@^7.7.4":
- version "7.7.4"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.7.4.tgz#516570d539e44ddf308c07569c258ff94fde9193"
- integrity sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==
+"@babel/types@^7.19.0", "@babel/types@^7.20.0":
+ version "7.20.0"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.0.tgz#52c94cf8a7e24e89d2a194c25c35b17a64871479"
+ integrity sha512-Jlgt3H0TajCW164wkTOTzHkZb075tMQMULzrLUoUeKmO7eFL96GgDxf7/Axhc5CAuKE3KFyVW1p6ysKsi2oXAg==
dependencies:
- esutils "^2.0.2"
- lodash "^4.17.13"
+ "@babel/helper-string-parser" "^7.19.4"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ to-fast-properties "^2.0.0"
+
+"@babel/types@^7.20.2", "@babel/types@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f"
+ integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==
+ dependencies:
+ "@babel/helper-string-parser" "^7.19.4"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ to-fast-properties "^2.0.0"
+
+"@babel/types@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.0.tgz#1da00d89c2f18b226c9207d96edbeb79316a1819"
+ integrity sha512-uR7NWq2VNFnDi7EYqiRz2Jv/VQIu38tu64Zy8TX2nQFQ6etJ9V/Rr2msW8BS132mum2rL645qpDrLtAJtVpuow==
+ dependencies:
+ "@babel/helper-string-parser" "^7.19.4"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ to-fast-properties "^2.0.0"
+
+"@babel/types@^7.21.2", "@babel/types@^7.21.4":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.4.tgz#2d5d6bb7908699b3b416409ffd3b5daa25b030d4"
+ integrity sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==
+ dependencies:
+ "@babel/helper-string-parser" "^7.19.4"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ to-fast-properties "^2.0.0"
+
+"@babel/types@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6"
+ integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==
+ dependencies:
+ "@babel/helper-string-parser" "^7.21.5"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ to-fast-properties "^2.0.0"
+
+"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0":
+ version "7.23.6"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd"
+ integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==
+ dependencies:
+ "@babel/helper-string-parser" "^7.23.4"
+ "@babel/helper-validator-identifier" "^7.22.20"
+ to-fast-properties "^2.0.0"
+
+"@babel/types@^7.23.9", "@babel/types@^7.24.0":
+ version "7.24.0"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf"
+ integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==
+ dependencies:
+ "@babel/helper-string-parser" "^7.23.4"
+ "@babel/helper-validator-identifier" "^7.22.20"
to-fast-properties "^2.0.0"
"@chenfengyuan/vue-qrcode@2.0.0":
@@ -1294,71 +1723,110 @@
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
-"@fortawesome/fontawesome-common-types@6.1.1":
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.1.tgz#7dc996042d21fc1ae850e3173b5c67b0549f9105"
- integrity sha512-wVn5WJPirFTnzN6tR95abCx+ocH+3IFLXAgyavnf9hUmN0CfWoDjPT/BAWsUVwSlYYVBeCLJxaqi7ZGe4uSjBA==
+"@csstools/selector-specificity@^2.0.2":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36"
+ integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==
-"@fortawesome/fontawesome-svg-core@6.1.1":
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.1.1.tgz#3424ec6182515951816be9b11665d67efdce5b5f"
- integrity sha512-NCg0w2YIp81f4V6cMGD9iomfsIj7GWrqmsa0ZsPh59G7PKiGN1KymZNxmF00ssuAlo/VZmpK6xazsGOwzKYUMg==
+"@eslint/eslintrc@^1.4.1":
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e"
+ integrity sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==
dependencies:
- "@fortawesome/fontawesome-common-types" "6.1.1"
+ ajv "^6.12.4"
+ debug "^4.3.2"
+ espree "^9.4.0"
+ globals "^13.19.0"
+ ignore "^5.2.0"
+ import-fresh "^3.2.1"
+ js-yaml "^4.1.0"
+ minimatch "^3.1.2"
+ strip-json-comments "^3.1.1"
-"@fortawesome/free-regular-svg-icons@6.1.1":
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.1.1.tgz#3f2f58262a839edf0643cbacee7a8a8230061c98"
- integrity sha512-xXiW7hcpgwmWtndKPOzG+43fPH7ZjxOaoeyooptSztGmJxCAflHZxXNK0GcT0uEsR4jTGQAfGklDZE5NHoBhKg==
+"@fortawesome/fontawesome-common-types@6.4.0":
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz#88da2b70d6ca18aaa6ed3687832e11f39e80624b"
+ integrity sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==
+
+"@fortawesome/fontawesome-svg-core@6.4.0":
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz#3727552eff9179506e9203d72feb5b1063c11a21"
+ integrity sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==
dependencies:
- "@fortawesome/fontawesome-common-types" "6.1.1"
+ "@fortawesome/fontawesome-common-types" "6.4.0"
-"@fortawesome/free-solid-svg-icons@6.1.1":
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.1.1.tgz#3369e673f8fe8be2fba30b1ec274d47490a830a6"
- integrity sha512-0/5exxavOhI/D4Ovm2r3vxNojGZioPwmFrKg0ZUH69Q68uFhFPs6+dhAToh6VEQBntxPRYPuT5Cg1tpNa9JUPg==
+"@fortawesome/free-regular-svg-icons@6.4.0":
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz#cacc53bd8d832d46feead412d9ea9ce80a55e13a"
+ integrity sha512-ZfycI7D0KWPZtf7wtMFnQxs8qjBXArRzczABuMQqecA/nXohquJ5J/RCR77PmY5qGWkxAZDxpnUFVXKwtY/jPw==
dependencies:
- "@fortawesome/fontawesome-common-types" "6.1.1"
+ "@fortawesome/fontawesome-common-types" "6.4.0"
-"@fortawesome/vue-fontawesome@3.0.1":
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.1.tgz#ced35cefc52b364f7db973f2fe9f50c3dd160715"
- integrity sha512-CdXZJoCS+aEPec26ZP7hWWU3SaJlQPZSCGdgpQ2qGl2HUmtUUNrI3zC4XWdn1JUmh3t5OuDeRG1qB4eGRNSD4A==
+"@fortawesome/free-solid-svg-icons@6.4.0":
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz#48c0e790847fa56299e2f26b82b39663b8ad7119"
+ integrity sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==
+ dependencies:
+ "@fortawesome/fontawesome-common-types" "6.4.0"
-"@intlify/bundle-utils@next":
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/@intlify/bundle-utils/-/bundle-utils-3.0.0.tgz#d7667b3e6c5889988d9fd27acc2c7c068a2bfbc3"
- integrity sha512-y43Z5Q3ZJvxqtD8xUH6U3yrlZeay7ZTqkzv1GQ4b0mGQtk5uptOT9Ra4qvGuUv8QyPQsortrA/OHWUD5ax5ZNQ==
+"@fortawesome/vue-fontawesome@3.0.3":
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.3.tgz#633e2998d11f7d4ed41f0d5ea461a22ec9b9d034"
+ integrity sha512-KCPHi9QemVXGMrfuwf3nNnNo129resAIQWut9QTAMXmXqL2ErABC6ohd2yY5Ipq0CLWNbKHk8TMdTXL/Zf3ZhA==
+
+"@humanwhocodes/config-array@^0.11.8":
+ version "0.11.8"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9"
+ integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==
+ dependencies:
+ "@humanwhocodes/object-schema" "^1.2.1"
+ debug "^4.1.1"
+ minimatch "^3.0.5"
+
+"@humanwhocodes/module-importer@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
+ integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
+
+"@humanwhocodes/object-schema@^1.2.1":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
+ integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
+
+"@intlify/bundle-utils@3.4.0":
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/@intlify/bundle-utils/-/bundle-utils-3.4.0.tgz#72558611f4b223a6791f591363dc48a4bcacdf70"
+ integrity sha512-2UQkqiSAOSPEHMGWlybqWm4G2K0X+FyYho5AwXz6QklSX1EY5EDmOSxZmwscn2qmKBnp6OYsme5kUrnN9xrWzQ==
dependencies:
"@intlify/message-compiler" next
"@intlify/shared" next
jsonc-eslint-parser "^1.0.1"
- source-map "^0.6.1"
+ source-map "0.6.1"
yaml-eslint-parser "^0.3.2"
-"@intlify/core-base@9.2.0-beta.40":
- version "9.2.0-beta.40"
- resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.2.0-beta.40.tgz#85df2e183b2102716c5d40795848fc2359354580"
- integrity sha512-vOR0lHgtJ3IkzvXLeMQeNeYreFSKG9v3SU8QOD//WKHdBy4QPISs9CZJkYzBeBVCJVZ/eM6OTSbXF8M2k53iCw==
+"@intlify/core-base@9.2.2":
+ version "9.2.2"
+ resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.2.2.tgz#5353369b05cc9fe35cab95fe20afeb8a4481f939"
+ integrity sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==
dependencies:
- "@intlify/devtools-if" "9.2.0-beta.40"
- "@intlify/message-compiler" "9.2.0-beta.40"
- "@intlify/shared" "9.2.0-beta.40"
- "@intlify/vue-devtools" "9.2.0-beta.40"
+ "@intlify/devtools-if" "9.2.2"
+ "@intlify/message-compiler" "9.2.2"
+ "@intlify/shared" "9.2.2"
+ "@intlify/vue-devtools" "9.2.2"
-"@intlify/devtools-if@9.2.0-beta.40":
- version "9.2.0-beta.40"
- resolved "https://registry.yarnpkg.com/@intlify/devtools-if/-/devtools-if-9.2.0-beta.40.tgz#bee42fefaaaa590aa5ac7fe2a98777fb84bfaf5e"
- integrity sha512-EUiuLxlgortD1dhT0btm3YYIs2vk9kMdcGXiYYbHWRTylc8Iv7Yz47y5Y+IlbZzk51h/nYvuqXE1h9diZZWAvQ==
+"@intlify/devtools-if@9.2.2":
+ version "9.2.2"
+ resolved "https://registry.yarnpkg.com/@intlify/devtools-if/-/devtools-if-9.2.2.tgz#b13d9ac4b4e2fe6d2e7daa556517a8061fe8bd39"
+ integrity sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==
dependencies:
- "@intlify/shared" "9.2.0-beta.40"
+ "@intlify/shared" "9.2.2"
-"@intlify/message-compiler@9.2.0-beta.40":
- version "9.2.0-beta.40"
- resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.2.0-beta.40.tgz#d5d0c5652b9e74e0b4da07a2b8731e1f0e729029"
- integrity sha512-6QWTSYewmkew4nsRqgkwTVuGFKzxVCOK8EXsPt15N+tN1g+OYjC3PfGA2dPB6cVkNxqA9mV/hNK02uHPWU9t0A==
+"@intlify/message-compiler@9.2.2":
+ version "9.2.2"
+ resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.2.2.tgz#e42ab6939b8ae5b3d21faf6a44045667a18bba1c"
+ integrity sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==
dependencies:
- "@intlify/shared" "9.2.0-beta.40"
+ "@intlify/shared" "9.2.2"
source-map "0.6.1"
"@intlify/message-compiler@next":
@@ -1374,31 +1842,63 @@
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.2.0-beta.34.tgz#e8e9a93455eadcc9785fe2e2437fe037fc267f7d"
integrity sha512-hbUKcVbTOkLVpnlSeZE1OPgEI7FpvhuZF/gb84xECTjXEImIa3u0fIcJKUUffv3dlAx8fMOE5xKgDzngidm0tw==
-"@intlify/shared@9.2.0-beta.40":
- version "9.2.0-beta.40"
- resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.2.0-beta.40.tgz#a850936008e6e865310b2a49136d494dd326faab"
- integrity sha512-xWz+SFjgt/LfaSbbHVn+V7gmvX4ZNP3cIFta790GWZ/tEgwJeC3tkV7i45iUbZ4ZimOerFgKH05b7qvJlKb6RQ==
+"@intlify/shared@9.2.2", "@intlify/shared@^9.2.2":
+ version "9.2.2"
+ resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.2.2.tgz#5011be9ca2b4ab86f8660739286e2707f9abb4a5"
+ integrity sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==
-"@intlify/vue-devtools@9.2.0-beta.40":
- version "9.2.0-beta.40"
- resolved "https://registry.yarnpkg.com/@intlify/vue-devtools/-/vue-devtools-9.2.0-beta.40.tgz#37457fd719b0b6afb0679c33ceb47b0ac77f457c"
- integrity sha512-3A0D/E9quf+KWonzXUDk3xNP0+d1DMdtAwyXNTjzFcQPvjugC2Xn6fmsd0kNn7nHjgpB+vwIuamGiuE+S+OULw==
+"@intlify/vue-devtools@9.2.2":
+ version "9.2.2"
+ resolved "https://registry.yarnpkg.com/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz#b95701556daf7ebb3a2d45aa3ae9e6415aed8317"
+ integrity sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==
dependencies:
- "@intlify/core-base" "9.2.0-beta.40"
- "@intlify/shared" "9.2.0-beta.40"
+ "@intlify/core-base" "9.2.2"
+ "@intlify/shared" "9.2.2"
-"@intlify/vue-i18n-loader@^5.0.0":
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/@intlify/vue-i18n-loader/-/vue-i18n-loader-5.0.0.tgz#26f7b9d55b3feb5d50cdbbd537c7ed4b2396b3fb"
- integrity sha512-rlqWLHrXdchvI9jsI5XA7/3UqE+4pgBD40d+9DWdyRkKeXfMMO9lmkp21jOKC8afWcK0NW5qzYTjp+JEJ6ymZA==
+"@intlify/vue-i18n-loader@5.0.1":
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/@intlify/vue-i18n-loader/-/vue-i18n-loader-5.0.1.tgz#af7d32059e32138e91495e5240f7ce2adb71c738"
+ integrity sha512-z1dFLsR5YsEbA7+zqd8C3lvBOr6DWMMyUdX3Y42e+6Y5cL8uE55uQfdjUDbhQe7R6YlZT7ZDaIXoIGyoFaJDNg==
dependencies:
- "@intlify/bundle-utils" next
- "@intlify/shared" next
+ "@intlify/bundle-utils" "3.4.0"
+ "@intlify/shared" "^9.2.2"
js-yaml "^4.1.0"
json5 "^2.2.0"
loader-utils "^2.0.0"
-"@jridgewell/gen-mapping@^0.3.2":
+"@istanbuljs/schema@^0.1.2":
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
+ integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
+
+"@jest/schemas@^29.0.0":
+ version "29.0.0"
+ resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.0.0.tgz#5f47f5994dd4ef067fb7b4188ceac45f77fe952a"
+ integrity sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==
+ dependencies:
+ "@sinclair/typebox" "^0.24.1"
+
+"@jest/types@^29.2.1":
+ version "29.2.1"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.2.1.tgz#ec9c683094d4eb754e41e2119d8bdaef01cf6da0"
+ integrity sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==
+ dependencies:
+ "@jest/schemas" "^29.0.0"
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ "@types/istanbul-reports" "^3.0.0"
+ "@types/node" "*"
+ "@types/yargs" "^17.0.8"
+ chalk "^4.0.0"
+
+"@jridgewell/gen-mapping@^0.1.0":
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996"
+ integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.0"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+
+"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
@@ -1407,21 +1907,63 @@
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
+"@jridgewell/gen-mapping@^0.3.5":
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36"
+ integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==
+ dependencies:
+ "@jridgewell/set-array" "^1.2.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
+"@jridgewell/resolve-uri@3.1.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
+ integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
+
"@jridgewell/resolve-uri@^3.0.3":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c"
integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==
-"@jridgewell/set-array@^1.0.1":
+"@jridgewell/resolve-uri@^3.1.0":
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
+ integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
+
+"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
+"@jridgewell/set-array@^1.2.1":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280"
+ integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
+
+"@jridgewell/source-map@^0.3.2":
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb"
+ integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/sourcemap-codec@1.4.14":
+ version "1.4.14"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
+ integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
+
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.11"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec"
integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==
+"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15":
+ version "1.4.15"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+ integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
+
"@jridgewell/trace-mapping@^0.3.0":
version "0.3.4"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3"
@@ -1430,6 +1972,30 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
+"@jridgewell/trace-mapping@^0.3.14":
+ version "0.3.15"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774"
+ integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.0.3"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+
+"@jridgewell/trace-mapping@^0.3.17":
+ version "0.3.17"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985"
+ integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==
+ dependencies:
+ "@jridgewell/resolve-uri" "3.1.0"
+ "@jridgewell/sourcemap-codec" "1.4.14"
+
+"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
+ version "0.3.25"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0"
+ integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
+
"@jridgewell/trace-mapping@^0.3.9":
version "0.3.14"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
@@ -1445,6 +2011,35 @@
dependencies:
pointer-tracker "^2.0.3"
+"@kazvmoe-infra/unicode-emoji-json@0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@kazvmoe-infra/unicode-emoji-json/-/unicode-emoji-json-0.4.0.tgz#555bab2f8d11db74820ef0a2fbe2805b17c22587"
+ integrity sha512-22OffREdHzD0U6A/W4RaFPV8NR73za6euibtAxNxO/fu5A6TwxRO2lAdbDWKJH9COv/vYs8zqfEiSalXH2nXJA==
+
+"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
+ version "5.1.1-v1"
+ resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
+ integrity sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==
+ dependencies:
+ eslint-scope "5.1.1"
+
+"@nightwatch/chai@5.0.2":
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/@nightwatch/chai/-/chai-5.0.2.tgz#86b20908fc090dffd5c9567c0392bc6a494cc2e6"
+ integrity sha512-yzILJFCcE75OPoRfBlJ80Y3Ky06ljsdrK4Ld92yhmM477vxO2GEguwnd+ldl7pdSYTcg1gSJ1bPPQrA+/Hrn+A==
+ dependencies:
+ assertion-error "1.1.0"
+ check-error "1.0.2"
+ deep-eql "4.0.1"
+ loupe "2.3.4"
+ pathval "1.1.1"
+ type-detect "4.0.8"
+
+"@nightwatch/html-reporter-template@0.2.1":
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/@nightwatch/html-reporter-template/-/html-reporter-template-0.2.1.tgz#9fa86e8cab6ee703d2e55b47abac92613f97a298"
+ integrity sha512-GEBeGoXVmTYPtNC4Yq34vfgxf6mlFyEagxpsfH18Qe5BvctF2rprX+wI5dKBm9p5IqHo6ZOcXHCufOeP3cjuOw==
+
"@nodelib/fs.scandir@2.1.3":
version "2.1.3"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
@@ -1453,11 +2048,24 @@
"@nodelib/fs.stat" "2.0.3"
run-parallel "^1.1.9"
+"@nodelib/fs.scandir@2.1.5":
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
+ integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
+ dependencies:
+ "@nodelib/fs.stat" "2.0.5"
+ run-parallel "^1.1.9"
+
"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3"
integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==
+"@nodelib/fs.stat@2.0.5":
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
+ integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
+
"@nodelib/fs.walk@^1.2.3":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976"
@@ -1466,54 +2074,84 @@
"@nodelib/fs.scandir" "2.1.3"
fastq "^1.6.0"
-"@npmcli/move-file@^1.0.1":
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674"
- integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==
+"@nodelib/fs.walk@^1.2.8":
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
+ integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
dependencies:
- mkdirp "^1.0.4"
- rimraf "^3.0.2"
+ "@nodelib/fs.scandir" "2.1.5"
+ fastq "^1.6.0"
-"@ruffle-rs/ruffle@^0.1.0-nightly.2022.7.12":
- version "0.1.0-nightly.2022.7.12"
- resolved "https://registry.yarnpkg.com/@ruffle-rs/ruffle/-/ruffle-0.1.0-nightly.2022.7.12.tgz#c2d77fce7a0e98d51a6535371550e0bff019d0ea"
- integrity sha512-DFsiT4kdUuSHsYXzHV97e9Ui3FkcsHEg1GyHJipt/lCpCoZ2uRtP41uEz9eNc9ug8jWd7UyXxJmdkkRvs9UHgQ==
+"@ruffle-rs/ruffle@0.1.0-nightly.2024.8.21":
+ version "0.1.0-nightly.2024.8.21"
+ resolved "https://registry.yarnpkg.com/@ruffle-rs/ruffle/-/ruffle-0.1.0-nightly.2024.8.21.tgz#e4bdb6386b487dc12471681c7265f565d813e1cf"
+ integrity sha512-nfTPJEPJPo4MrUACuLoW19wNKgF1rrbxCO5if1MZfsWcUxZ6+pwlQWq1JxXalxEjYg8VwJtWzWEchWJQkMckwA==
-"@socket.io/base64-arraybuffer@~1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#568d9beae00b0d835f4f8c53fd55714986492e61"
- integrity sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==
+"@sinclair/typebox@^0.24.1":
+ version "0.24.51"
+ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f"
+ integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==
-"@stylelint/postcss-css-in-js@^0.37.2":
- version "0.37.3"
- resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.3.tgz#d149a385e07ae365b0107314c084cb6c11adbf49"
- integrity sha512-scLk3cSH1H9KggSniseb2KNAU5D9FWc3H7BxCSAIdtU9OWIyw0zkEZ9qEKHryRM+SExYXRKNb7tOOVNAsQ3iwg==
+"@sinonjs/commons@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3"
+ integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==
dependencies:
- "@babel/core" "^7.17.9"
+ type-detect "4.0.8"
-"@stylelint/postcss-markdown@^0.36.2":
- version "0.36.2"
- resolved "https://registry.yarnpkg.com/@stylelint/postcss-markdown/-/postcss-markdown-0.36.2.tgz#0a540c4692f8dcdfc13c8e352c17e7bfee2bb391"
- integrity sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ==
+"@sinonjs/commons@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72"
+ integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==
dependencies:
- remark "^13.0.0"
- unist-util-find-all-after "^3.0.2"
+ type-detect "4.0.8"
-"@testim/chrome-version@^1.0.7":
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/@testim/chrome-version/-/chrome-version-1.0.7.tgz#0cd915785ec4190f08a3a6acc9b61fc38fb5f1a9"
- integrity sha512-8UT/J+xqCYfn3fKtOznAibsHpiuDshCb0fwgWxRazTT19Igp9ovoXMPhXyLD6m3CKQGTMHgqoxaFfMWaL40Rnw==
+"@sinonjs/fake-timers@^10.0.2":
+ version "10.0.2"
+ resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz#d10549ed1f423d80639c528b6c7f5a1017747d0c"
+ integrity sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==
+ dependencies:
+ "@sinonjs/commons" "^2.0.0"
+
+"@sinonjs/samsam@^8.0.0":
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.0.tgz#0d488c91efb3fa1442e26abea81759dfc8b5ac60"
+ integrity sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==
+ dependencies:
+ "@sinonjs/commons" "^2.0.0"
+ lodash.get "^4.4.2"
+ type-detect "^4.0.8"
+
+"@sinonjs/text-encoding@^0.7.1":
+ version "0.7.2"
+ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz#5981a8db18b56ba38ef0efb7d995b12aa7b51918"
+ integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==
+
+"@socket.io/component-emitter@~3.1.0":
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2"
+ integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==
+
+"@testim/chrome-version@^1.1.3":
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/@testim/chrome-version/-/chrome-version-1.1.3.tgz#fbb68696899d7b8c1b9b891eded9c04fe2cd5529"
+ integrity sha512-g697J3WxV/Zytemz8aTuKjTGYtta9+02kva3C1xc7KXB8GdbfE1akGJIsZLyY/FSh2QrnE+fiB7vmWU3XNcb6A==
+
+"@tootallnate/once@2":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
+ integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
+
+"@trysound/sax@0.2.0":
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
+ integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
-"@types/component-emitter@^1.2.10":
- version "1.2.11"
- resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.11.tgz#50d47d42b347253817a39709fef03ce66a108506"
- integrity sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==
-
"@types/cookie@^0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d"
@@ -1524,35 +2162,73 @@
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080"
integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==
-"@types/http-proxy@^1.17.3":
- version "1.17.8"
- resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.8.tgz#968c66903e7e42b483608030ee85800f22d03f55"
- integrity sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA==
+"@types/eslint-scope@^3.7.3":
+ version "3.7.4"
+ resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
+ integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==
+ dependencies:
+ "@types/eslint" "*"
+ "@types/estree" "*"
+
+"@types/eslint@*", "@types/eslint@^7.29.0 || ^8.4.1":
+ version "8.4.5"
+ resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.5.tgz#acdfb7dd36b91cc5d812d7c093811a8f3d9b31e4"
+ integrity sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==
+ dependencies:
+ "@types/estree" "*"
+ "@types/json-schema" "*"
+
+"@types/estree@*":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2"
+ integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==
+
+"@types/estree@^0.0.51":
+ version "0.0.51"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
+ integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==
+
+"@types/html-minifier-terser@^6.0.0":
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
+ integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==
+
+"@types/http-proxy@^1.17.8":
+ version "1.17.9"
+ resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a"
+ integrity sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==
dependencies:
"@types/node" "*"
-"@types/json-schema@^7.0.5":
- version "7.0.10"
- resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.10.tgz#9b05b7896166cd00e9cbd59864853abf65d9ac23"
- integrity sha512-BLO9bBq59vW3fxCpD4o0N4U+DXsvwvIcl+jofw0frQo/GrBFC+/jRZj1E7kgp6dvTyNmA4y6JCV5Id/r3mNP5A==
+"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44"
+ integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==
-"@types/json-schema@^7.0.6":
- version "7.0.7"
- resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
- integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
+"@types/istanbul-lib-report@*":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686"
+ integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==
+ dependencies:
+ "@types/istanbul-lib-coverage" "*"
+
+"@types/istanbul-reports@^3.0.0":
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff"
+ integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==
+ dependencies:
+ "@types/istanbul-lib-report" "*"
+
+"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
+ version "7.0.11"
+ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
+ integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
-"@types/mdast@^3.0.0":
- version "3.0.10"
- resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af"
- integrity sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==
- dependencies:
- "@types/unist" "*"
-
"@types/minimist@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6"
@@ -1578,15 +2254,17 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
-"@types/unist@*":
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
- integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
+"@types/yargs-parser@*":
+ version "21.0.0"
+ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
+ integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==
-"@types/unist@^2.0.0", "@types/unist@^2.0.2":
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
- integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
+"@types/yargs@^17.0.8":
+ version "17.0.13"
+ resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76"
+ integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==
+ dependencies:
+ "@types/yargs-parser" "*"
"@types/yauzl@^2.9.1":
version "2.9.1"
@@ -1595,298 +2273,346 @@
dependencies:
"@types/node" "*"
-"@ungap/event-target@0.2.3":
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/@ungap/event-target/-/event-target-0.2.3.tgz#be682c681126dca2371c4e1a1721f8e8bb400905"
- integrity sha512-7Bz0qdvxNGV9n0f+xcMKU7wsEfK6PNzo8IdAcOiBgMNyCuU0Mk9dv0Hbd/Kgr+MFFfn4xLHFbuOt820egT5qEA==
+"@ungap/event-target@0.2.4":
+ version "0.2.4"
+ resolved "https://registry.yarnpkg.com/@ungap/event-target/-/event-target-0.2.4.tgz#8b083a62ee665228bac08013fa516a3488528bb8"
+ integrity sha512-u9Fd3k2qfMtn+0dxbCn/y0pzQ9Ucw6lWR984CrHcbxc+WzcMkJE4VjWHWSb9At40MjwMyHCkJNXroS55Osshhw==
-"@vue/babel-helper-vue-jsx-merge-props@1.2.1":
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz#31624a7a505fb14da1d58023725a4c5f270e6a81"
- integrity sha512-QOi5OW45e2R20VygMSNhyQHvpdUwQZqGPc748JLGCYEy+yp8fNFNdbNIGAgZmi9e+2JHPd6i6idRuqivyicIkA==
+"@ungap/promise-all-settled@1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
+ integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
-"@vue/babel-helper-vue-transform-on@^1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.0.2.tgz#9b9c691cd06fc855221a2475c3cc831d774bc7dc"
- integrity sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==
+"@vue/babel-helper-vue-jsx-merge-props@1.4.0":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.4.0.tgz#8d53a1e21347db8edbe54d339902583176de09f2"
+ integrity sha512-JkqXfCkUDp4PIlFdDQ0TdXoIejMtTHP67/pvxlgeY+u5k3LEdKuWZ3LK6xkxo52uDoABIVyRwqVkfLQJhk7VBA==
-"@vue/babel-plugin-jsx@1.1.1":
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.1.1.tgz#0c5bac27880d23f89894cd036a37b55ef61ddfc1"
- integrity sha512-j2uVfZjnB5+zkcbc/zsOc0fSNGCMMjaEXP52wdwdIfn0qjFfEYpYZBFKFg+HHnQeJCVrjOeO0YxgaL7DMrym9w==
- dependencies:
- "@babel/helper-module-imports" "^7.0.0"
- "@babel/plugin-syntax-jsx" "^7.0.0"
- "@babel/template" "^7.0.0"
- "@babel/traverse" "^7.0.0"
- "@babel/types" "^7.0.0"
- "@vue/babel-helper-vue-transform-on" "^1.0.2"
- camelcase "^6.0.0"
- html-tags "^3.1.0"
+"@vue/babel-helper-vue-transform-on@1.2.2":
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.2.tgz#7f1f817a4f00ad531651a8d1d22e22d9e42807ef"
+ integrity sha512-nOttamHUR3YzdEqdM/XXDyCSdxMA9VizUKoroLX6yTyRtggzQMHXcmwh8a7ZErcJttIBIc9s68a1B8GZ+Dmvsw==
+
+"@vue/babel-plugin-jsx@1.2.2":
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.2.tgz#eb426fb4660aa510bb8d188ff0ec140405a97d8a"
+ integrity sha512-nYTkZUVTu4nhP199UoORePsql0l+wj7v/oyQjtThUVhJl1U+6qHuoVhIvR3bf7eVKjbCK+Cs2AWd7mi9Mpz9rA==
+ dependencies:
+ "@babel/helper-module-imports" "~7.22.15"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-jsx" "^7.23.3"
+ "@babel/template" "^7.23.9"
+ "@babel/traverse" "^7.23.9"
+ "@babel/types" "^7.23.9"
+ "@vue/babel-helper-vue-transform-on" "1.2.2"
+ "@vue/babel-plugin-resolve-type" "1.2.2"
+ camelcase "^6.3.0"
+ html-tags "^3.3.1"
svg-tags "^1.0.0"
-"@vue/compiler-core@3.2.31":
- version "3.2.31"
- resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.31.tgz#d38f06c2cf845742403b523ab4596a3fda152e89"
- integrity sha512-aKno00qoA4o+V/kR6i/pE+aP+esng5siNAVQ422TkBNM6qA4veXiZbSe8OTXHXquEi/f6Akc+nLfB4JGfe4/WQ==
+"@vue/babel-plugin-resolve-type@1.2.2":
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.2.tgz#66844898561da6449e0f4a261b0c875118e0707b"
+ integrity sha512-EntyroPwNg5IPVdUJupqs0CFzuf6lUrVvCspmv2J1FITLeGnUCuoGNNk78dgCusxEiYj6RMkTJflGSxk5aIC4A==
+ dependencies:
+ "@babel/code-frame" "^7.23.5"
+ "@babel/helper-module-imports" "~7.22.15"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/parser" "^7.23.9"
+ "@vue/compiler-sfc" "^3.4.15"
+
+"@vue/compiler-core@3.2.45":
+ version "3.2.45"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.45.tgz#d9311207d96f6ebd5f4660be129fb99f01ddb41b"
+ integrity sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==
dependencies:
"@babel/parser" "^7.16.4"
- "@vue/shared" "3.2.31"
+ "@vue/shared" "3.2.45"
estree-walker "^2.0.2"
source-map "^0.6.1"
-"@vue/compiler-dom@3.2.31":
- version "3.2.31"
- resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.31.tgz#b1b7dfad55c96c8cc2b919cd7eb5fd7e4ddbf00e"
- integrity sha512-60zIlFfzIDf3u91cqfqy9KhCKIJgPeqxgveH2L+87RcGU/alT6BRrk5JtUso0OibH3O7NXuNOQ0cDc9beT0wrg==
+"@vue/compiler-core@3.4.15":
+ version "3.4.15"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.15.tgz#be20d1bbe19626052500b48969302cb6f396d36e"
+ integrity sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==
dependencies:
- "@vue/compiler-core" "3.2.31"
- "@vue/shared" "3.2.31"
+ "@babel/parser" "^7.23.6"
+ "@vue/shared" "3.4.15"
+ entities "^4.5.0"
+ estree-walker "^2.0.2"
+ source-map-js "^1.0.2"
+
+"@vue/compiler-dom@3.2.45":
+ version "3.2.45"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz#c43cc15e50da62ecc16a42f2622d25dc5fd97dce"
+ integrity sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==
+ dependencies:
+ "@vue/compiler-core" "3.2.45"
+ "@vue/shared" "3.2.45"
-"@vue/compiler-sfc@3.2.31", "@vue/compiler-sfc@^3.1.0":
- version "3.2.31"
- resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.31.tgz#d02b29c3fe34d599a52c5ae1c6937b4d69f11c2f"
- integrity sha512-748adc9msSPGzXgibHiO6T7RWgfnDcVQD+VVwYgSsyyY8Ans64tALHZANrKtOzvkwznV/F4H7OAod/jIlp/dkQ==
+"@vue/compiler-dom@3.4.15":
+ version "3.4.15"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz#753f5ed55f78d33dff04701fad4d76ff0cf81ee5"
+ integrity sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==
+ dependencies:
+ "@vue/compiler-core" "3.4.15"
+ "@vue/shared" "3.4.15"
+
+"@vue/compiler-sfc@3.2.45":
+ version "3.2.45"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz#7f7989cc04ec9e7c55acd406827a2c4e96872c70"
+ integrity sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==
dependencies:
"@babel/parser" "^7.16.4"
- "@vue/compiler-core" "3.2.31"
- "@vue/compiler-dom" "3.2.31"
- "@vue/compiler-ssr" "3.2.31"
- "@vue/reactivity-transform" "3.2.31"
- "@vue/shared" "3.2.31"
+ "@vue/compiler-core" "3.2.45"
+ "@vue/compiler-dom" "3.2.45"
+ "@vue/compiler-ssr" "3.2.45"
+ "@vue/reactivity-transform" "3.2.45"
+ "@vue/shared" "3.2.45"
estree-walker "^2.0.2"
magic-string "^0.25.7"
postcss "^8.1.10"
source-map "^0.6.1"
-"@vue/compiler-ssr@3.2.31":
- version "3.2.31"
- resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.31.tgz#4fa00f486c9c4580b40a4177871ebbd650ecb99c"
- integrity sha512-mjN0rqig+A8TVDnsGPYJM5dpbjlXeHUm2oZHZwGyMYiGT/F4fhJf/cXy8QpjnLQK4Y9Et4GWzHn9PS8AHUnSkw==
+"@vue/compiler-sfc@^3.4.15":
+ version "3.4.15"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz#4e5811e681955fcec886cebbec483f6ae463a64b"
+ integrity sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==
+ dependencies:
+ "@babel/parser" "^7.23.6"
+ "@vue/compiler-core" "3.4.15"
+ "@vue/compiler-dom" "3.4.15"
+ "@vue/compiler-ssr" "3.4.15"
+ "@vue/shared" "3.4.15"
+ estree-walker "^2.0.2"
+ magic-string "^0.30.5"
+ postcss "^8.4.33"
+ source-map-js "^1.0.2"
+
+"@vue/compiler-ssr@3.2.45":
+ version "3.2.45"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz#bd20604b6e64ea15344d5b6278c4141191c983b2"
+ integrity sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==
dependencies:
- "@vue/compiler-dom" "3.2.31"
- "@vue/shared" "3.2.31"
+ "@vue/compiler-dom" "3.2.45"
+ "@vue/shared" "3.2.45"
+
+"@vue/compiler-ssr@3.4.15":
+ version "3.4.15"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz#a910a5b89ba4f0a776e40b63d69bdae2f50616cf"
+ integrity sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==
+ dependencies:
+ "@vue/compiler-dom" "3.4.15"
+ "@vue/shared" "3.4.15"
"@vue/devtools-api@^6.0.0-beta.11":
version "6.1.3"
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.1.3.tgz#a44c52e8fa6d22f84db3abdcdd0be5135b7dd7cf"
integrity sha512-79InfO2xHv+WHIrH1bHXQUiQD/wMls9qBk6WVwGCbdwP7/3zINtvqPNMtmSHXsIKjvUAHc8L0ouOj6ZQQRmcXg==
-"@vue/devtools-api@^6.1.4", "@vue/devtools-api@^6.2.1":
+"@vue/devtools-api@^6.2.1":
version "6.2.1"
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.2.1.tgz#6f2948ff002ec46df01420dfeff91de16c5b4092"
integrity sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ==
-"@vue/reactivity-transform@3.2.31":
- version "3.2.31"
- resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.31.tgz#0f5b25c24e70edab2b613d5305c465b50fc00911"
- integrity sha512-uS4l4z/W7wXdI+Va5pgVxBJ345wyGFKvpPYtdSgvfJfX/x2Ymm6ophQlXXB6acqGHtXuBqNyyO3zVp9b1r0MOA==
+"@vue/devtools-api@^6.4.5":
+ version "6.4.5"
+ resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.4.5.tgz#d54e844c1adbb1e677c81c665ecef1a2b4bb8380"
+ integrity sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ==
+
+"@vue/reactivity-transform@3.2.45":
+ version "3.2.45"
+ resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz#07ac83b8138550c83dfb50db43cde1e0e5e8124d"
+ integrity sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==
dependencies:
"@babel/parser" "^7.16.4"
- "@vue/compiler-core" "3.2.31"
- "@vue/shared" "3.2.31"
+ "@vue/compiler-core" "3.2.45"
+ "@vue/shared" "3.2.45"
estree-walker "^2.0.2"
magic-string "^0.25.7"
-"@vue/reactivity@3.2.31":
- version "3.2.31"
- resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.31.tgz#fc90aa2cdf695418b79e534783aca90d63a46bbd"
- integrity sha512-HVr0l211gbhpEKYr2hYe7hRsV91uIVGFYNHj73njbARVGHQvIojkImKMaZNDdoDZOIkMsBc9a1sMqR+WZwfSCw==
+"@vue/reactivity@3.2.45":
+ version "3.2.45"
+ resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.45.tgz#412a45b574de601be5a4a5d9a8cbd4dee4662ff0"
+ integrity sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==
dependencies:
- "@vue/shared" "3.2.31"
+ "@vue/shared" "3.2.45"
-"@vue/runtime-core@3.2.31":
- version "3.2.31"
- resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.31.tgz#9d284c382f5f981b7a7b5971052a1dc4ef39ac7a"
- integrity sha512-Kcog5XmSY7VHFEMuk4+Gap8gUssYMZ2+w+cmGI6OpZWYOEIcbE0TPzzPHi+8XTzAgx1w/ZxDFcXhZeXN5eKWsA==
+"@vue/runtime-core@3.2.45":
+ version "3.2.45"
+ resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.45.tgz#7ad7ef9b2519d41062a30c6fa001ec43ac549c7f"
+ integrity sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==
dependencies:
- "@vue/reactivity" "3.2.31"
- "@vue/shared" "3.2.31"
+ "@vue/reactivity" "3.2.45"
+ "@vue/shared" "3.2.45"
-"@vue/runtime-dom@3.2.31":
- version "3.2.31"
- resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.31.tgz#79ce01817cb3caf2c9d923f669b738d2d7953eff"
- integrity sha512-N+o0sICVLScUjfLG7u9u5XCjvmsexAiPt17GNnaWHJUfsKed5e85/A3SWgKxzlxx2SW/Hw7RQxzxbXez9PtY3g==
+"@vue/runtime-dom@3.2.45":
+ version "3.2.45"
+ resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz#1a2ef6ee2ad876206fbbe2a884554bba2d0faf59"
+ integrity sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==
dependencies:
- "@vue/runtime-core" "3.2.31"
- "@vue/shared" "3.2.31"
+ "@vue/runtime-core" "3.2.45"
+ "@vue/shared" "3.2.45"
csstype "^2.6.8"
-"@vue/server-renderer@3.2.31":
- version "3.2.31"
- resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.31.tgz#201e9d6ce735847d5989403af81ef80960da7141"
- integrity sha512-8CN3Zj2HyR2LQQBHZ61HexF5NReqngLT3oahyiVRfSSvak+oAvVmu8iNLSu6XR77Ili2AOpnAt1y8ywjjqtmkg==
+"@vue/server-renderer@3.2.45":
+ version "3.2.45"
+ resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.45.tgz#ca9306a0c12b0530a1a250e44f4a0abac6b81f3f"
+ integrity sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==
dependencies:
- "@vue/compiler-ssr" "3.2.31"
- "@vue/shared" "3.2.31"
+ "@vue/compiler-ssr" "3.2.45"
+ "@vue/shared" "3.2.45"
-"@vue/shared@3.2.31":
- version "3.2.31"
- resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.31.tgz#c90de7126d833dcd3a4c7534d534be2fb41faa4e"
- integrity sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ==
+"@vue/shared@3.2.45":
+ version "3.2.45"
+ resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.45.tgz#a3fffa7489eafff38d984e23d0236e230c818bc2"
+ integrity sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==
-"@vue/test-utils@2.0.2":
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-2.0.2.tgz#0b5edd683366153d5bc5a91edc62f292118710eb"
- integrity sha512-E2P4oXSaWDqTZNbmKZFVLrNN/siVN78YkEqs7pHryWerrlZR9bBFLWdJwRoguX45Ru6HxIflzKl4vQvwRMwm5g==
-
-"@vuelidate/core@2.0.0-alpha.43":
- version "2.0.0-alpha.43"
- resolved "https://registry.yarnpkg.com/@vuelidate/core/-/core-2.0.0-alpha.43.tgz#f7eac52504c8948d6b2038f1b5b16ea976d96cc7"
- integrity sha512-0/IoC8+A/wgNqmTIQc2eFYYnBlwIkvfM+riVDG1HAG3amdH7iNrFxNRkiHH/XJ810ZpOYPfYVslqYLbgq6E5HQ==
- dependencies:
- vue-demi "^0.13.4"
+"@vue/shared@3.4.15":
+ version "3.4.15"
+ resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.15.tgz#e7d2ea050c667480cb5e1a6df2ac13bcd03a8f30"
+ integrity sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==
-"@vuelidate/validators@2.0.0-alpha.31":
- version "2.0.0-alpha.31"
- resolved "https://registry.yarnpkg.com/@vuelidate/validators/-/validators-2.0.0-alpha.31.tgz#04d63307bc0a12db9f7ad94243350b83aacee998"
- integrity sha512-+MFA9nZ7Y9zCpq383/voPDk/hiAmu6KqiJJhLOYB/FmrUPVoyKnuKnI9Bwiq8ok9GZlVkI8BnIrKPKGj9QpwiQ==
+"@vue/test-utils@2.2.8":
+ version "2.2.8"
+ resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-2.2.8.tgz#2002a2b2c90309f66c5c175b735621438832a610"
+ integrity sha512-/R8DKzp41Ip/RqTt1jvOVi5gxby3EwNWiYHNYsG9FAjEvt0gzDvYN55lCKzX7IdnI5zVIOo5tHtts0SLT+JrWw==
dependencies:
- vue-demi "^0.13.4"
+ js-beautify "1.14.6"
-"@webassemblyjs/ast@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"
- integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==
+"@vuelidate/core@2.0.3":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@vuelidate/core/-/core-2.0.3.tgz#40468c5ed15b72bde880a026b0699c2f0f1ecede"
+ integrity sha512-AN6l7KF7+mEfyWG0doT96z+47ljwPpZfi9/JrNMkOGLFv27XVZvKzRLXlmDPQjPl/wOB1GNnHuc54jlCLRNqGA==
dependencies:
- "@webassemblyjs/helper-module-context" "1.9.0"
- "@webassemblyjs/helper-wasm-bytecode" "1.9.0"
- "@webassemblyjs/wast-parser" "1.9.0"
-
-"@webassemblyjs/floating-point-hex-parser@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4"
- integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==
-
-"@webassemblyjs/helper-api-error@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2"
- integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==
-
-"@webassemblyjs/helper-buffer@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00"
- integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==
+ vue-demi "^0.13.11"
-"@webassemblyjs/helper-code-frame@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27"
- integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==
+"@vuelidate/validators@2.0.4":
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/@vuelidate/validators/-/validators-2.0.4.tgz#0a88a7b2b18f15fd9c384095593f369a6f7384e9"
+ integrity sha512-odTxtUZ2JpwwiQ10t0QWYJkkYrfd0SyFYhdHH44QQ1jDatlZgTh/KRzrWVmn/ib9Gq7H4hFD4e8ahoo5YlUlDw==
dependencies:
- "@webassemblyjs/wast-printer" "1.9.0"
-
-"@webassemblyjs/helper-fsm@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8"
- integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==
+ vue-demi "^0.13.11"
-"@webassemblyjs/helper-module-context@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07"
- integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==
+"@webassemblyjs/ast@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
+ integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==
dependencies:
- "@webassemblyjs/ast" "1.9.0"
+ "@webassemblyjs/helper-numbers" "1.11.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
-"@webassemblyjs/helper-wasm-bytecode@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790"
- integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==
+"@webassemblyjs/floating-point-hex-parser@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f"
+ integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==
-"@webassemblyjs/helper-wasm-section@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346"
- integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==
- dependencies:
- "@webassemblyjs/ast" "1.9.0"
- "@webassemblyjs/helper-buffer" "1.9.0"
- "@webassemblyjs/helper-wasm-bytecode" "1.9.0"
- "@webassemblyjs/wasm-gen" "1.9.0"
+"@webassemblyjs/helper-api-error@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16"
+ integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==
-"@webassemblyjs/ieee754@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4"
- integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==
- dependencies:
- "@xtuc/ieee754" "^1.2.0"
+"@webassemblyjs/helper-buffer@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5"
+ integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==
-"@webassemblyjs/leb128@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95"
- integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==
+"@webassemblyjs/helper-numbers@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae"
+ integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==
dependencies:
+ "@webassemblyjs/floating-point-hex-parser" "1.11.1"
+ "@webassemblyjs/helper-api-error" "1.11.1"
"@xtuc/long" "4.2.2"
-"@webassemblyjs/utf8@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab"
- integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==
+"@webassemblyjs/helper-wasm-bytecode@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1"
+ integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==
-"@webassemblyjs/wasm-edit@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf"
- integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==
- dependencies:
- "@webassemblyjs/ast" "1.9.0"
- "@webassemblyjs/helper-buffer" "1.9.0"
- "@webassemblyjs/helper-wasm-bytecode" "1.9.0"
- "@webassemblyjs/helper-wasm-section" "1.9.0"
- "@webassemblyjs/wasm-gen" "1.9.0"
- "@webassemblyjs/wasm-opt" "1.9.0"
- "@webassemblyjs/wasm-parser" "1.9.0"
- "@webassemblyjs/wast-printer" "1.9.0"
-
-"@webassemblyjs/wasm-gen@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c"
- integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==
+"@webassemblyjs/helper-wasm-section@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a"
+ integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==
dependencies:
- "@webassemblyjs/ast" "1.9.0"
- "@webassemblyjs/helper-wasm-bytecode" "1.9.0"
- "@webassemblyjs/ieee754" "1.9.0"
- "@webassemblyjs/leb128" "1.9.0"
- "@webassemblyjs/utf8" "1.9.0"
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/helper-buffer" "1.11.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
+ "@webassemblyjs/wasm-gen" "1.11.1"
-"@webassemblyjs/wasm-opt@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61"
- integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==
+"@webassemblyjs/ieee754@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614"
+ integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==
dependencies:
- "@webassemblyjs/ast" "1.9.0"
- "@webassemblyjs/helper-buffer" "1.9.0"
- "@webassemblyjs/wasm-gen" "1.9.0"
- "@webassemblyjs/wasm-parser" "1.9.0"
+ "@xtuc/ieee754" "^1.2.0"
-"@webassemblyjs/wasm-parser@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e"
- integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==
+"@webassemblyjs/leb128@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5"
+ integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==
dependencies:
- "@webassemblyjs/ast" "1.9.0"
- "@webassemblyjs/helper-api-error" "1.9.0"
- "@webassemblyjs/helper-wasm-bytecode" "1.9.0"
- "@webassemblyjs/ieee754" "1.9.0"
- "@webassemblyjs/leb128" "1.9.0"
- "@webassemblyjs/utf8" "1.9.0"
-
-"@webassemblyjs/wast-parser@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914"
- integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==
- dependencies:
- "@webassemblyjs/ast" "1.9.0"
- "@webassemblyjs/floating-point-hex-parser" "1.9.0"
- "@webassemblyjs/helper-api-error" "1.9.0"
- "@webassemblyjs/helper-code-frame" "1.9.0"
- "@webassemblyjs/helper-fsm" "1.9.0"
"@xtuc/long" "4.2.2"
-"@webassemblyjs/wast-printer@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899"
- integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==
- dependencies:
- "@webassemblyjs/ast" "1.9.0"
- "@webassemblyjs/wast-parser" "1.9.0"
+"@webassemblyjs/utf8@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff"
+ integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==
+
+"@webassemblyjs/wasm-edit@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6"
+ integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/helper-buffer" "1.11.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
+ "@webassemblyjs/helper-wasm-section" "1.11.1"
+ "@webassemblyjs/wasm-gen" "1.11.1"
+ "@webassemblyjs/wasm-opt" "1.11.1"
+ "@webassemblyjs/wasm-parser" "1.11.1"
+ "@webassemblyjs/wast-printer" "1.11.1"
+
+"@webassemblyjs/wasm-gen@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76"
+ integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
+ "@webassemblyjs/ieee754" "1.11.1"
+ "@webassemblyjs/leb128" "1.11.1"
+ "@webassemblyjs/utf8" "1.11.1"
+
+"@webassemblyjs/wasm-opt@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2"
+ integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/helper-buffer" "1.11.1"
+ "@webassemblyjs/wasm-gen" "1.11.1"
+ "@webassemblyjs/wasm-parser" "1.11.1"
+
+"@webassemblyjs/wasm-parser@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199"
+ integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/helper-api-error" "1.11.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
+ "@webassemblyjs/ieee754" "1.11.1"
+ "@webassemblyjs/leb128" "1.11.1"
+ "@webassemblyjs/utf8" "1.11.1"
+
+"@webassemblyjs/wast-printer@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0"
+ integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
"@xtuc/long" "4.2.2"
"@xtuc/ieee754@^1.2.0":
@@ -1897,9 +2623,15 @@
version "4.2.2"
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
-abbrev@1, abbrev@1.0.x:
- version "1.0.9"
- resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
+abab@^2.0.5, abab@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
+ integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
+
+abbrev@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
+ integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
accepts@~1.3.4:
version "1.3.7"
@@ -1916,35 +2648,38 @@ accepts@~1.3.8:
mime-types "~2.1.34"
negotiator "0.6.3"
-acorn-jsx@^5.0.0:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e"
+acorn-globals@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45"
+ integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==
+ dependencies:
+ acorn "^7.1.1"
+ acorn-walk "^7.1.1"
+
+acorn-import-assertions@^1.7.6:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9"
+ integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==
-acorn-jsx@^5.2.0:
+acorn-jsx@^5.2.0, acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
- integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
-
-acorn@^6.0.2, acorn@^6.0.7:
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f"
+ integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
-acorn@^6.4.1:
- version "6.4.2"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6"
- integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==
+acorn-walk@^7.1.1:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
+ integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
acorn@^7.1.1, acorn@^7.4.1:
version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
-agent-base@2:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-2.1.1.tgz#d6de10d5af6132d5bd692427d46fc538539094c7"
- dependencies:
- extend "~3.0.0"
- semver "~5.0.1"
+acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0:
+ version "8.8.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
+ integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
agent-base@6:
version "6.0.2"
@@ -1953,47 +2688,26 @@ agent-base@6:
dependencies:
debug "4"
-aggregate-error@^3.0.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
- integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==
+ajv-formats@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520"
+ integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==
dependencies:
- clean-stack "^2.0.0"
- indent-string "^4.0.0"
-
-ajv-errors@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
-
-ajv-keywords@^3.1.0:
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.0.tgz#4b831e7b531415a7cc518cd404e73f6193c6349d"
+ ajv "^8.0.0"
-ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
+ajv-keywords@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
-ajv@^6.1.0, ajv@^6.9.1:
- version "6.10.0"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1"
- dependencies:
- fast-deep-equal "^2.0.1"
- fast-json-stable-stringify "^2.0.0"
- json-schema-traverse "^0.4.1"
- uri-js "^4.2.2"
-
-ajv@^6.10.2:
- version "6.12.3"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706"
- integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==
+ajv-keywords@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16"
+ integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==
dependencies:
- fast-deep-equal "^3.1.1"
- fast-json-stable-stringify "^2.0.0"
- json-schema-traverse "^0.4.1"
- uri-js "^4.2.2"
+ fast-deep-equal "^3.1.3"
-ajv@^6.12.4, ajv@^6.12.5:
+ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -2003,7 +2717,7 @@ ajv@^6.12.4, ajv@^6.12.5:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
-ajv@^8.0.1:
+ajv@^8.0.0, ajv@^8.0.1, ajv@^8.8.0:
version "8.11.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
@@ -2013,22 +2727,22 @@ ajv@^8.0.1:
require-from-string "^2.0.2"
uri-js "^4.2.2"
-alphanum-sort@^1.0.1, alphanum-sort@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
+ansi-align@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59"
+ integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==
+ dependencies:
+ string-width "^4.1.0"
-amdefine@>=0.0.4:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
+ansi-colors@4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
+ integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
ansi-colors@^3.0.0:
version "3.2.4"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
-ansi-escapes@^3.2.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
-
ansi-html-community@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41"
@@ -2060,7 +2774,7 @@ ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
-ansi-styles@^3.2.0, ansi-styles@^3.2.1:
+ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
dependencies:
@@ -2081,18 +2795,14 @@ ansi-styles@^4.1.0:
"@types/color-name" "^1.1.1"
color-convert "^2.0.1"
-ansi-styles@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178"
-
-anymatch@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
+ansi-to-html@0.7.2:
+ version "0.7.2"
+ resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.7.2.tgz#a92c149e4184b571eb29a0135ca001a8e2d710cb"
+ integrity sha512-v6MqmEpNlxF+POuyhKkidusCHWWkaLcGRURzivcU3I9tv7k4JVhFcnukrM5Rlk2rUywdZuzYAZ+kbZqWCnfN3g==
dependencies:
- micromatch "^3.1.4"
- normalize-path "^2.1.1"
+ entities "^2.2.0"
-anymatch@~3.1.1, anymatch@~3.1.2:
+anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
@@ -2100,57 +2810,24 @@ anymatch@~3.1.1, anymatch@~3.1.2:
normalize-path "^3.0.0"
picomatch "^2.0.4"
-aproba@^1.0.3, aproba@^1.1.1:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
-
-are-we-there-yet@~1.1.2:
- version "1.1.5"
- resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
- dependencies:
- delegates "^1.0.0"
- readable-stream "^2.0.6"
-
-argparse@^1.0.7:
- version "1.0.10"
- resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
- dependencies:
- sprintf-js "~1.0.2"
-
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
-arr-diff@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
-
-arr-flatten@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
-
-arr-union@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
-
-array-find-index@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
-
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
-array-includes@^3.1.4:
- version "3.1.4"
- resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9"
- integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==
+array-includes@^3.1.6:
+ version "3.1.6"
+ resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f"
+ integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==
dependencies:
call-bind "^1.0.2"
- define-properties "^1.1.3"
- es-abstract "^1.19.1"
- get-intrinsic "^1.1.1"
+ define-properties "^1.1.4"
+ es-abstract "^1.20.4"
+ get-intrinsic "^1.1.3"
is-string "^1.0.7"
array-union@^2.1.0:
@@ -2158,197 +2835,89 @@ array-union@^2.1.0:
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
-array-unique@^0.3.2:
- version "0.3.2"
- resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
+array.prototype.flat@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2"
+ integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ es-abstract "^1.20.4"
+ es-shim-unscopables "^1.0.0"
-array.prototype.flat@^1.2.5:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13"
- integrity sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==
+array.prototype.flatmap@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183"
+ integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==
dependencies:
call-bind "^1.0.2"
- define-properties "^1.1.3"
- es-abstract "^1.19.0"
+ define-properties "^1.1.4"
+ es-abstract "^1.20.4"
+ es-shim-unscopables "^1.0.0"
arrify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
-asn1.js@^4.0.0:
- version "4.10.1"
- resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
- dependencies:
- bn.js "^4.0.0"
- inherits "^2.0.1"
- minimalistic-assert "^1.0.0"
-
-assert@^1.1.1:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91"
- dependencies:
- util "0.10.3"
-
-assertion-error@1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.0.tgz#c7f85438fdd466bc7ca16ab90c81513797a5d23b"
-
-assertion-error@^1.0.1:
+assertion-error@1.1.0, assertion-error@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
-
-assign-symbols@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
-
-ast-types@0.x.x:
- version "0.11.7"
- resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.7.tgz#f318bf44e339db6a320be0009ded64ec1471f46c"
-
-astral-regex@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
+ integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
astral-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
-async-each@^1.0.1:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
-
-async@1.x:
- version "1.5.2"
- resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
-
-async@^2.5.0:
- version "2.6.1"
- resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
- dependencies:
- lodash "^4.17.10"
-
-atob@^2.1.1:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
+async@^3.2.3:
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c"
+ integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==
-autoprefixer@6.7.7, autoprefixer@^6.3.1:
- version "6.7.7"
- resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014"
- integrity sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=
- dependencies:
- browserslist "^1.7.6"
- caniuse-db "^1.0.30000634"
- normalize-range "^0.1.2"
- num2fraction "^1.2.2"
- postcss "^5.2.16"
- postcss-value-parser "^3.2.3"
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+ integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
-autoprefixer@^9.8.6:
- version "9.8.8"
- resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.8.tgz#fd4bd4595385fa6f06599de749a4d5f7a474957a"
- integrity sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==
+autoprefixer@10.4.19:
+ version "10.4.19"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.19.tgz#ad25a856e82ee9d7898c59583c1afeb3fa65f89f"
+ integrity sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==
dependencies:
- browserslist "^4.12.0"
- caniuse-lite "^1.0.30001109"
+ browserslist "^4.23.0"
+ caniuse-lite "^1.0.30001599"
+ fraction.js "^4.3.7"
normalize-range "^0.1.2"
- num2fraction "^1.2.2"
- picocolors "^0.2.1"
- postcss "^7.0.32"
- postcss-value-parser "^4.1.0"
-
-axios@^0.21.1:
- version "0.21.4"
- resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
- integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
- dependencies:
- follow-redirects "^1.14.0"
+ picocolors "^1.0.0"
+ postcss-value-parser "^4.2.0"
-babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
- dependencies:
- chalk "^1.1.3"
- esutils "^2.0.2"
- js-tokens "^3.0.2"
-
-babel-core@^6.1.4, babel-core@^6.26.0:
- version "6.26.3"
- resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207"
- dependencies:
- babel-code-frame "^6.26.0"
- babel-generator "^6.26.0"
- babel-helpers "^6.24.1"
- babel-messages "^6.23.0"
- babel-register "^6.26.0"
- babel-runtime "^6.26.0"
- babel-template "^6.26.0"
- babel-traverse "^6.26.0"
- babel-types "^6.26.0"
- babylon "^6.18.0"
- convert-source-map "^1.5.1"
- debug "^2.6.9"
- json5 "^0.5.1"
- lodash "^4.17.4"
- minimatch "^3.0.4"
- path-is-absolute "^1.0.1"
- private "^0.1.8"
- slash "^1.0.0"
- source-map "^0.5.7"
+available-typed-arrays@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
+ integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
-babel-eslint@7.2.3:
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.3.tgz#b2fe2d80126470f5c19442dc757253a897710827"
- integrity sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=
- dependencies:
- babel-code-frame "^6.22.0"
- babel-traverse "^6.23.1"
- babel-types "^6.23.0"
- babylon "^6.17.0"
-
-babel-generator@^6.26.0:
- version "6.26.1"
- resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
- dependencies:
- babel-messages "^6.23.0"
- babel-runtime "^6.26.0"
- babel-types "^6.26.0"
- detect-indent "^4.0.0"
- jsesc "^1.3.0"
- lodash "^4.17.4"
- source-map "^0.5.7"
- trim-right "^1.0.1"
-
-babel-helpers@^6.24.1:
- version "6.24.1"
- resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
- dependencies:
- babel-runtime "^6.22.0"
- babel-template "^6.24.1"
-
-babel-loader@8.2.5:
- version "8.2.5"
- resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.5.tgz#d45f585e654d5a5d90f5350a779d7647c5ed512e"
- integrity sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==
- dependencies:
- find-cache-dir "^3.3.1"
- loader-utils "^2.0.0"
- make-dir "^3.1.0"
- schema-utils "^2.6.5"
+axe-core@^4.6.1:
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.6.2.tgz#6e566ab2a3d29e415f5115bc0fd2597a5eb3e5e3"
+ integrity sha512-b1WlTV8+XKLj9gZy2DZXgQiyDp9xkkoe2a6U6UbYccScq2wgH/YwCeI2/Jq2mgo0HzQxqJOjWZBLeA/mqsk5Mg==
-babel-messages@^6.23.0:
- version "6.23.0"
- resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
+axios@^1.1.3:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.2.tgz#72681724c6e6a43a9fea860fc558127dbe32f9f1"
+ integrity sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==
dependencies:
- babel-runtime "^6.22.0"
+ follow-redirects "^1.15.0"
+ form-data "^4.0.0"
+ proxy-from-env "^1.1.0"
-babel-plugin-dynamic-import-node@^2.3.3:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
- integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==
+babel-loader@9.1.3:
+ version "9.1.3"
+ resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-9.1.3.tgz#3d0e01b4e69760cc694ee306fe16d358aa1c6f9a"
+ integrity sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==
dependencies:
- object.assign "^4.1.0"
+ find-cache-dir "^4.0.0"
+ schema-utils "^4.0.0"
babel-plugin-lodash@3.3.4:
version "3.3.4"
@@ -2361,94 +2930,29 @@ babel-plugin-lodash@3.3.4:
lodash "^4.17.10"
require-package-name "^2.0.1"
-babel-plugin-polyfill-corejs2@^0.3.1:
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz#440f1b70ccfaabc6b676d196239b138f8a2cfba5"
- integrity sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==
+babel-plugin-polyfill-corejs2@^0.3.3:
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122"
+ integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==
dependencies:
- "@babel/compat-data" "^7.13.11"
- "@babel/helper-define-polyfill-provider" "^0.3.1"
+ "@babel/compat-data" "^7.17.7"
+ "@babel/helper-define-polyfill-provider" "^0.3.3"
semver "^6.1.1"
-babel-plugin-polyfill-corejs3@^0.5.2:
- version "0.5.2"
- resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz#aabe4b2fa04a6e038b688c5e55d44e78cd3a5f72"
- integrity sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==
- dependencies:
- "@babel/helper-define-polyfill-provider" "^0.3.1"
- core-js-compat "^3.21.0"
-
-babel-plugin-polyfill-regenerator@^0.3.1:
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz#2c0678ea47c75c8cc2fbb1852278d8fb68233990"
- integrity sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==
+babel-plugin-polyfill-corejs3@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a"
+ integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==
dependencies:
- "@babel/helper-define-polyfill-provider" "^0.3.1"
+ "@babel/helper-define-polyfill-provider" "^0.3.3"
+ core-js-compat "^3.25.1"
-babel-register@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
+babel-plugin-polyfill-regenerator@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747"
+ integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==
dependencies:
- babel-core "^6.26.0"
- babel-runtime "^6.26.0"
- core-js "^2.5.0"
- home-or-tmp "^2.0.0"
- lodash "^4.17.4"
- mkdirp "^0.5.1"
- source-map-support "^0.4.15"
-
-babel-runtime@^6.22.0, babel-runtime@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
- dependencies:
- core-js "^2.4.0"
- regenerator-runtime "^0.11.0"
-
-babel-template@^6.24.1, babel-template@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
- dependencies:
- babel-runtime "^6.26.0"
- babel-traverse "^6.26.0"
- babel-types "^6.26.0"
- babylon "^6.18.0"
- lodash "^4.17.4"
-
-babel-traverse@^6.23.1, babel-traverse@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
- dependencies:
- babel-code-frame "^6.26.0"
- babel-messages "^6.23.0"
- babel-runtime "^6.26.0"
- babel-types "^6.26.0"
- babylon "^6.18.0"
- debug "^2.6.8"
- globals "^9.18.0"
- invariant "^2.2.2"
- lodash "^4.17.4"
-
-babel-types@^6.23.0, babel-types@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
- dependencies:
- babel-runtime "^6.26.0"
- esutils "^2.0.2"
- lodash "^4.17.4"
- to-fast-properties "^1.0.3"
-
-babylon@^6.17.0, babylon@^6.18.0:
- version "6.18.0"
- resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
-
-bail@^1.0.0:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776"
- integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==
-
-balanced-match@^0.4.2:
- version "0.4.2"
- resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
+ "@babel/helper-define-polyfill-provider" "^0.3.3"
balanced-match@^1.0.0:
version "1.0.0"
@@ -2459,57 +2963,38 @@ balanced-match@^2.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9"
integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==
-base64-js@^1.0.2:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
+base64-js@^1.3.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
+ integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
base64id@2.0.0, base64id@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==
-base@^0.11.1:
- version "0.11.2"
- resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
- dependencies:
- cache-base "^1.0.1"
- class-utils "^0.3.5"
- component-emitter "^1.2.1"
- define-property "^1.0.0"
- isobject "^3.0.1"
- mixin-deep "^1.2.0"
- pascalcase "^0.1.1"
-
-big.js@^3.1.3:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
-
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
-binary-extensions@^1.0.0:
- version "1.12.0"
- resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14"
-
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
-bluebird@^3.5.5:
- version "3.7.2"
- resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
- integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
-
-bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
- version "4.11.8"
- resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
+bl@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
+ integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
+ dependencies:
+ buffer "^5.5.0"
+ inherits "^2.0.4"
+ readable-stream "^3.4.0"
-body-parser@1.20.0:
- version "1.20.0"
- resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5"
- integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==
+body-parser@1.20.1:
+ version "1.20.1"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
+ integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
dependencies:
bytes "3.1.2"
content-type "~1.0.4"
@@ -2519,7 +3004,7 @@ body-parser@1.20.0:
http-errors "2.0.0"
iconv-lite "0.4.24"
on-finished "2.4.1"
- qs "6.10.3"
+ qs "6.11.0"
raw-body "2.5.1"
type-is "~1.6.18"
unpipe "1.0.0"
@@ -2540,36 +3025,43 @@ body-parser@^1.19.0:
raw-body "2.4.3"
type-is "~1.6.18"
-body-scroll-lock@2.7.1:
- version "2.7.1"
- resolved "https://registry.yarnpkg.com/body-scroll-lock/-/body-scroll-lock-2.7.1.tgz#caf3f9c91773af1ffb684cd66ed9137b5b737014"
- integrity sha512-hS53SQ8RhM0e4DsQ3PKz6Gr2O7Kpdh59TWU98GHjaQznL7y4dFycEPk7pFQAikqBaUSCArkc5E3pe7CWIt2fZA==
+body-scroll-lock@3.1.5:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/body-scroll-lock/-/body-scroll-lock-3.1.5.tgz#c1392d9217ed2c3e237fee1e910f6cdd80b7aaec"
+ integrity sha512-Yi1Xaml0EvNA0OYWxXiYNqY24AfWkbA6w5vxE7GWxtKfzIbZM+Qw+aSmkgsbWzbHiy/RCSkUZBplVxTA+E4jJg==
-boolbase@~1.0.0:
+boolbase@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
+ integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
+
+boxen@5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50"
+ integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==
+ dependencies:
+ ansi-align "^3.0.0"
+ camelcase "^6.2.0"
+ chalk "^4.1.0"
+ cli-boxes "^2.2.1"
+ string-width "^4.2.2"
+ type-fest "^0.20.2"
+ widest-line "^3.1.0"
+ wrap-ansi "^7.0.0"
-brace-expansion@^1.0.0, brace-expansion@^1.1.7:
+brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
-braces@^2.3.1, braces@^2.3.2:
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
+brace-expansion@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
+ integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
dependencies:
- arr-flatten "^1.1.0"
- array-unique "^0.3.2"
- extend-shallow "^2.0.1"
- fill-range "^4.0.0"
- isobject "^3.0.1"
- repeat-element "^1.1.2"
- snapdragon "^0.8.1"
- snapdragon-node "^2.0.1"
- split-string "^3.0.2"
- to-regex "^3.0.1"
+ balanced-match "^1.0.0"
braces@^3.0.1, braces@^3.0.2, braces@~3.0.2:
version "3.0.2"
@@ -2578,94 +3070,25 @@ braces@^3.0.1, braces@^3.0.2, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
-brorand@^1.0.1:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
-
-browser-stdout@1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f"
-
-browserify-aes@^1.0.0, browserify-aes@^1.0.4:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48"
- dependencies:
- buffer-xor "^1.0.3"
- cipher-base "^1.0.0"
- create-hash "^1.1.0"
- evp_bytestokey "^1.0.3"
- inherits "^2.0.1"
- safe-buffer "^5.0.1"
-
-browserify-cipher@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0"
- dependencies:
- browserify-aes "^1.0.4"
- browserify-des "^1.0.0"
- evp_bytestokey "^1.0.0"
-
-browserify-des@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c"
- dependencies:
- cipher-base "^1.0.1"
- des.js "^1.0.0"
- inherits "^2.0.1"
- safe-buffer "^5.1.2"
-
-browserify-rsa@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
- dependencies:
- bn.js "^4.1.0"
- randombytes "^2.0.1"
-
-browserify-sign@^4.0.0:
- version "4.0.4"
- resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298"
- dependencies:
- bn.js "^4.1.1"
- browserify-rsa "^4.0.0"
- create-hash "^1.1.0"
- create-hmac "^1.1.2"
- elliptic "^6.0.0"
- inherits "^2.0.1"
- parse-asn1 "^5.0.0"
-
-browserify-zlib@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f"
- dependencies:
- pako "~1.0.5"
-
-browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
- version "1.7.7"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9"
- dependencies:
- caniuse-db "^1.0.30000639"
- electron-to-chromium "^1.2.7"
+browser-process-hrtime@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
+ integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
-browserslist@^4.12.0:
- version "4.13.0"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.13.0.tgz#42556cba011e1b0a2775b611cba6a8eca18e940d"
- integrity sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==
- dependencies:
- caniuse-lite "^1.0.30001093"
- electron-to-chromium "^1.3.488"
- escalade "^3.0.1"
- node-releases "^1.1.58"
+browser-stdout@1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
+ integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
-browserslist@^4.17.5, browserslist@^4.19.1:
- version "4.20.2"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88"
- integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==
+browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.20.3:
+ version "4.21.3"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a"
+ integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==
dependencies:
- caniuse-lite "^1.0.30001317"
- electron-to-chromium "^1.4.84"
- escalade "^3.1.1"
- node-releases "^2.0.2"
- picocolors "^1.0.0"
+ caniuse-lite "^1.0.30001370"
+ electron-to-chromium "^1.4.202"
+ node-releases "^2.0.6"
+ update-browserslist-db "^1.0.5"
browserslist@^4.20.2:
version "4.21.1"
@@ -2677,15 +3100,25 @@ browserslist@^4.20.2:
node-releases "^2.0.5"
update-browserslist-db "^1.0.4"
-browserslist@^4.21.1:
- version "4.21.2"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.2.tgz#59a400757465535954946a400b841ed37e2b4ecf"
- integrity sha512-MonuOgAtUB46uP5CezYbRaYKBNt2LxP0yX+Pmj4LkcDFGkn9Cbpi83d9sCjwQDErXsIJSzY5oKGDbgOlF/LPAA==
+browserslist@^4.21.3, browserslist@^4.21.4:
+ version "4.21.4"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987"
+ integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==
dependencies:
- caniuse-lite "^1.0.30001366"
- electron-to-chromium "^1.4.188"
+ caniuse-lite "^1.0.30001400"
+ electron-to-chromium "^1.4.251"
node-releases "^2.0.6"
- update-browserslist-db "^1.0.4"
+ update-browserslist-db "^1.0.9"
+
+browserslist@^4.23.0:
+ version "4.23.0"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab"
+ integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==
+ dependencies:
+ caniuse-lite "^1.0.30001587"
+ electron-to-chromium "^1.4.668"
+ node-releases "^2.0.14"
+ update-browserslist-db "^1.0.13"
buffer-crc32@~0.2.3:
version "0.2.13"
@@ -2696,93 +3129,26 @@ buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
-buffer-xor@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
-
-buffer@^4.3.0:
- version "4.9.1"
- resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
+buffer@^5.5.0:
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
+ integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
dependencies:
- base64-js "^1.0.2"
- ieee754 "^1.1.4"
- isarray "^1.0.0"
-
-builtin-modules@^1.0.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
-
-builtin-status-codes@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
+ base64-js "^1.3.1"
+ ieee754 "^1.1.13"
-bytes@3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
+builtins@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9"
+ integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==
+ dependencies:
+ semver "^7.0.0"
bytes@3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
-cacache@^12.0.2:
- version "12.0.4"
- resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c"
- integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==
- dependencies:
- bluebird "^3.5.5"
- chownr "^1.1.1"
- figgy-pudding "^3.5.1"
- glob "^7.1.4"
- graceful-fs "^4.1.15"
- infer-owner "^1.0.3"
- lru-cache "^5.1.1"
- mississippi "^3.0.0"
- mkdirp "^0.5.1"
- move-concurrently "^1.0.1"
- promise-inflight "^1.0.1"
- rimraf "^2.6.3"
- ssri "^6.0.1"
- unique-filename "^1.1.1"
- y18n "^4.0.0"
-
-cacache@^15.0.5:
- version "15.0.6"
- resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.6.tgz#65a8c580fda15b59150fb76bf3f3a8e45d583099"
- integrity sha512-g1WYDMct/jzW+JdWEyjaX2zoBkZ6ZT9VpOyp2I/VMtDsNLffNat3kqPFfi1eDRSK9/SuKGyORDHcQMcPF8sQ/w==
- dependencies:
- "@npmcli/move-file" "^1.0.1"
- chownr "^2.0.0"
- fs-minipass "^2.0.0"
- glob "^7.1.4"
- infer-owner "^1.0.4"
- lru-cache "^6.0.0"
- minipass "^3.1.1"
- minipass-collect "^1.0.2"
- minipass-flush "^1.0.5"
- minipass-pipeline "^1.2.2"
- mkdirp "^1.0.3"
- p-map "^4.0.0"
- promise-inflight "^1.0.1"
- rimraf "^3.0.2"
- ssri "^8.0.1"
- tar "^6.0.2"
- unique-filename "^1.1.1"
-
-cache-base@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
- dependencies:
- collection-visit "^1.0.0"
- component-emitter "^1.2.1"
- get-value "^2.0.6"
- has-value "^1.0.0"
- isobject "^3.0.1"
- set-value "^2.0.0"
- to-object-path "^0.3.0"
- union-value "^1.0.0"
- unset-value "^1.0.0"
-
call-bind@^1.0.0, call-bind@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
@@ -2791,42 +3157,17 @@ call-bind@^1.0.0, call-bind@^1.0.2:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
-caller-callsite@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
- integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=
- dependencies:
- callsites "^2.0.0"
-
-caller-path@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4"
- integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=
- dependencies:
- caller-callsite "^2.0.0"
-
-callsites@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
- integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=
-
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
-camel-case@3.0.x:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73"
- dependencies:
- no-case "^2.2.0"
- upper-case "^1.1.1"
-
-camelcase-keys@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
+camel-case@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a"
+ integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==
dependencies:
- camelcase "^2.0.0"
- map-obj "^1.0.0"
+ pascal-case "^3.1.2"
+ tslib "^2.0.3"
camelcase-keys@^6.2.2:
version "6.2.2"
@@ -2837,10 +3178,6 @@ camelcase-keys@^6.2.2:
map-obj "^4.0.0"
quick-lru "^4.0.1"
-camelcase@^2.0.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
-
camelcase@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
@@ -2855,56 +3192,47 @@ camelcase@^6.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e"
integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
-caniuse-api@^1.5.2:
- version "1.6.1"
- resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c"
+camelcase@^6.2.0, camelcase@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
+ integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
+
+caniuse-api@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"
+ integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==
dependencies:
- browserslist "^1.3.6"
- caniuse-db "^1.0.30000529"
+ browserslist "^4.0.0"
+ caniuse-lite "^1.0.0"
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
- version "1.0.30000928"
- resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000928.tgz#2e83d2b14276442da239511615eb7c62fed0cfa7"
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001359, caniuse-lite@^1.0.30001370, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599:
+ version "1.0.30001662"
+ resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz"
+ integrity sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==
-caniuse-lite@^1.0.30001093:
- version "1.0.30001107"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001107.tgz#809360df7a5b3458f627aa46b0f6ed6d5239da9a"
- integrity sha512-86rCH+G8onCmdN4VZzJet5uPELII59cUzDphko3thQFgAQG1RNa+sVLDoALIhRYmflo5iSIzWY3vu1XTWtNMQQ==
-
-caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001359:
- version "1.0.30001366"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001366.tgz#c73352c83830a9eaf2dea0ff71fb4b9a4bbaa89c"
- integrity sha512-yy7XLWCubDobokgzudpkKux8e0UOOnLHE6mlNJBzT3lZJz6s5atSEzjoL+fsCPkI0G8MP5uVdDx1ur/fXEWkZA==
-
-caniuse-lite@^1.0.30001317:
- version "1.0.30001317"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001317.tgz#0548fb28fd5bc259a70b8c1ffdbe598037666a1b"
- integrity sha512-xIZLh8gBm4dqNX0gkzrBeyI86J2eCjWzYAs40q88smG844YIrN4tVQl/RhquHvKEKImWWFIVh1Lxe5n1G/N+GQ==
-
-caniuse-lite@^1.0.30001366:
- version "1.0.30001367"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001367.tgz#2b97fe472e8fa29c78c5970615d7cd2ee414108a"
- integrity sha512-XDgbeOHfifWV3GEES2B8rtsrADx4Jf+juKX2SICJcaUhjYBO3bR96kvEIHa15VU6ohtOhBZuPGGYGbXMRn0NCw==
-
-chai-nightwatch@~0.1.x:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/chai-nightwatch/-/chai-nightwatch-0.1.1.tgz#1ca56de768d3c0868fe7fc2f4d32c2fe894e6be9"
+chai-nightwatch@0.5.3:
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/chai-nightwatch/-/chai-nightwatch-0.5.3.tgz#980ecf63dde5a04e7f3524370682c7ff01178ffb"
+ integrity sha512-38ixH/mqpY6IwnZkz6xPqx8aB5/KVR+j6VPugcir3EGOsphnWXrPH/mUt8Jp+ninL6ghY0AaJDQ10hSfCPGy/g==
dependencies:
- assertion-error "1.0.0"
- deep-eql "0.1.3"
+ assertion-error "1.1.0"
-chai@3.5.0:
- version "3.5.0"
- resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247"
- integrity sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=
+chai@4.3.7:
+ version "4.3.7"
+ resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.7.tgz#ec63f6df01829088e8bf55fca839bcd464a8ec51"
+ integrity sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==
dependencies:
- assertion-error "^1.0.1"
- deep-eql "^0.1.3"
- type-detect "^1.0.0"
+ assertion-error "^1.1.0"
+ check-error "^1.0.2"
+ deep-eql "^4.1.2"
+ get-func-name "^2.0.0"
+ loupe "^2.3.1"
+ pathval "^1.1.1"
+ type-detect "^4.0.5"
-chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
+chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
dependencies:
@@ -2914,7 +3242,7 @@ chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
-chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2:
+chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
dependencies:
@@ -2922,15 +3250,7 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
-chalk@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
- integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
- dependencies:
- ansi-styles "^4.1.0"
- supports-color "^7.1.0"
-
-chalk@^4.1.1:
+chalk@^4.0.0, chalk@^4.0.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -2938,90 +3258,33 @@ chalk@^4.1.1:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
-chalk@~0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f"
- dependencies:
- ansi-styles "~1.0.0"
- has-color "~0.1.0"
- strip-ansi "~0.1.0"
-
-character-entities-legacy@^1.0.0:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1"
- integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==
-
-character-entities@^1.0.0:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b"
- integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==
-
-character-reference-invalid@^1.0.0:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560"
- integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==
-
-chardet@^0.7.0:
- version "0.7.0"
- resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
-
-"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.1:
- version "3.5.3"
- resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
- integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
- dependencies:
- anymatch "~3.1.2"
- braces "~3.0.2"
- glob-parent "~5.1.2"
- is-binary-path "~2.1.0"
- is-glob "~4.0.1"
- normalize-path "~3.0.0"
- readdirp "~3.6.0"
- optionalDependencies:
- fsevents "~2.3.2"
-
-chokidar@^2.1.8:
- version "2.1.8"
- resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
- integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==
- dependencies:
- anymatch "^2.0.0"
- async-each "^1.0.1"
- braces "^2.3.2"
- glob-parent "^3.1.0"
- inherits "^2.0.3"
- is-binary-path "^1.0.0"
- is-glob "^4.0.0"
- normalize-path "^3.0.0"
- path-is-absolute "^1.0.0"
- readdirp "^2.2.1"
- upath "^1.1.1"
- optionalDependencies:
- fsevents "^1.2.7"
+chalk@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
+ integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+check-error@1.0.2, check-error@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
+ integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==
-chokidar@^3.4.1:
- version "3.5.1"
- resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
- integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==
+chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.1:
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
+ integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
dependencies:
- anymatch "~3.1.1"
+ anymatch "~3.1.2"
braces "~3.0.2"
- glob-parent "~5.1.0"
+ glob-parent "~5.1.2"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
- readdirp "~3.5.0"
+ readdirp "~3.6.0"
optionalDependencies:
- fsevents "~2.3.1"
-
-chownr@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494"
-
-chownr@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
- integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
+ fsevents "~2.3.2"
chromatism@3.0.0:
version "3.0.0"
@@ -3033,52 +3296,40 @@ chrome-trace-event@^1.0.2:
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
-chromedriver@87.0.7:
- version "87.0.7"
- resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-87.0.7.tgz#74041e02ff7f633e91b98eb707e2476f713dc4ca"
- integrity sha512-7J7iN2rJuSDsKb9BUUMewJt07PuTlZYd809D10dUCT1rjMD3i2jUw7dum9RxdC1xO3aFwMd8TwZ5NR82T+S+Dg==
+chromedriver@108.0.0:
+ version "108.0.0"
+ resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-108.0.0.tgz#7994013f423d8b95a513bb9553a55088de81b252"
+ integrity sha512-/kb0rb0dlC4RfXh2BOT7RV87K6d+It3VV5YXebLzO5a8t2knNffiTE23XPJQCH+l1xmgoW8/sOX/NB9irskvOQ==
dependencies:
- "@testim/chrome-version" "^1.0.7"
- axios "^0.21.1"
- del "^6.0.0"
+ "@testim/chrome-version" "^1.1.3"
+ axios "^1.1.3"
+ compare-versions "^5.0.1"
extract-zip "^2.0.1"
- https-proxy-agent "^5.0.0"
- mkdirp "^1.0.4"
+ https-proxy-agent "^5.0.1"
proxy-from-env "^1.1.0"
tcp-port-used "^1.0.1"
-cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
- dependencies:
- inherits "^2.0.1"
- safe-buffer "^5.0.1"
-
-clap@^1.0.9:
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51"
- dependencies:
- chalk "^1.1.3"
+ci-info@3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2"
+ integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==
-class-utils@^0.3.5:
- version "0.3.6"
- resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
- dependencies:
- arr-union "^3.1.0"
- define-property "^0.2.5"
- isobject "^3.0.0"
- static-extend "^0.1.1"
+ci-info@^3.2.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.5.0.tgz#bfac2a29263de4c829d806b1ab478e35091e171f"
+ integrity sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==
-clean-css@4.2.x:
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17"
+clean-css@^5.2.2:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.1.tgz#d0610b0b90d125196a2894d35366f734e5d7aa32"
+ integrity sha512-lCr8OHhiWCTw4v8POJovCoh4T7I9U11yVsPjMWWnnMmp9ZowCxyad1Pathle/9HjaDp+fdQKjO9fQydE6RHTZg==
dependencies:
source-map "~0.6.0"
-clean-stack@^2.0.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
- integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
+cli-boxes@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
+ integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==
cli-cursor@^2.1.0:
version "2.1.0"
@@ -3086,14 +3337,31 @@ cli-cursor@^2.1.0:
dependencies:
restore-cursor "^2.0.0"
+cli-cursor@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
+ integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
+ dependencies:
+ restore-cursor "^3.1.0"
+
cli-spinners@^1.0.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a"
integrity sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==
-cli-width@^2.0.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
+cli-spinners@^2.5.0:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a"
+ integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==
+
+cli-table3@^0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2"
+ integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==
+ dependencies:
+ string-width "^4.2.0"
+ optionalDependencies:
+ "@colors/colors" "1.5.0"
click-outside-vue3@4.0.1:
version "4.0.1"
@@ -3126,39 +3394,12 @@ clone-deep@^4.0.1:
kind-of "^6.0.2"
shallow-clone "^3.0.0"
-clone-regexp@^2.1.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f"
- integrity sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==
- dependencies:
- is-regexp "^2.0.0"
-
clone@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
+ integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
-co@~3.0.6:
- version "3.0.6"
- resolved "https://registry.yarnpkg.com/co/-/co-3.0.6.tgz#1445f226c5eb956138e68c9ac30167ea7d2e6bda"
-
-coa@~1.0.1:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.4.tgz#a9ef153660d6a86a8bdec0289a5c684d217432fd"
- dependencies:
- q "^1.1.2"
-
-code-point-at@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
-
-collection-visit@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
- dependencies:
- map-visit "^1.0.0"
- object-visit "^1.0.0"
-
-color-convert@^1.3.0, color-convert@^1.9.0:
+color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
dependencies:
@@ -3171,7 +3412,7 @@ color-convert@^2.0.1:
dependencies:
color-name "~1.1.4"
-color-name@1.1.3, color-name@^1.0.0:
+color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
@@ -3180,82 +3421,68 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-color-string@^0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991"
- dependencies:
- color-name "^1.0.0"
-
-color@^0.11.0:
- version "0.11.4"
- resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764"
- dependencies:
- clone "^1.0.2"
- color-convert "^1.3.0"
- color-string "^0.3.0"
-
-colormin@^1.0.5:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133"
- dependencies:
- color "^0.11.0"
- css-color-names "0.0.4"
- has "^1.0.1"
+colord@^2.9.1, colord@^2.9.3:
+ version "2.9.3"
+ resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43"
+ integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
colors@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
-colors@~1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
-
-commander@2.17.x, commander@~2.17.1:
- version "2.17.1"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
-
-commander@2.9.0:
- version "2.9.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
+combined-stream@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
+ integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
- graceful-readlink ">= 1.0.0"
+ delayed-stream "~1.0.0"
-commander@^2.20.0:
+commander@^2.19.0, commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+commander@^7.2.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
+ integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
+
+commander@^8.3.0:
+ version "8.3.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
+ integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
+
+common-path-prefix@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0"
+ integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==
+
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
-component-emitter@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
-
-component-emitter@~1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
- integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
+compare-versions@^5.0.1:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-5.0.3.tgz#a9b34fea217472650ef4a2651d905f42c28ebfd7"
+ integrity sha512-4UZlZP8Z99MGEY+Ovg/uJxJuvoXuN4M6B3hKaiackiHrgzQFEe3diJi1mf1PNHbFujM7FvLrK2bpgIaImbtZ1A==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
-concat-stream@^1.5.0:
- version "1.6.2"
- resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
+config-chain@^1.1.13:
+ version "1.1.13"
+ resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
+ integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==
dependencies:
- buffer-from "^1.0.0"
- inherits "^2.0.3"
- readable-stream "^2.2.2"
- typedarray "^0.0.6"
+ ini "^1.3.4"
+ proto-list "~1.2.1"
-connect-history-api-fallback@1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
- integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
+connect-history-api-fallback@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8"
+ integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==
connect@^3.7.0:
version "3.7.0"
@@ -3267,20 +3494,6 @@ connect@^3.7.0:
parseurl "~1.3.3"
utils-merge "1.0.1"
-console-browserify@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
- dependencies:
- date-now "^0.1.4"
-
-console-control-strings@^1.0.0, console-control-strings@~1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
-
-constants-browserify@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
-
content-disposition@0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
@@ -3292,12 +3505,6 @@ content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
-convert-source-map@^1.5.1:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
- dependencies:
- safe-buffer "~5.1.1"
-
convert-source-map@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
@@ -3319,57 +3526,24 @@ cookie@~0.4.1:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
-copy-concurrently@^1.0.0:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
+copy-webpack-plugin@11.0.0:
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a"
+ integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==
dependencies:
- aproba "^1.1.1"
- fs-write-stream-atomic "^1.0.8"
- iferr "^0.1.5"
- mkdirp "^0.5.1"
- rimraf "^2.5.4"
- run-queue "^1.0.0"
-
-copy-descriptor@^0.1.0:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
-
-copy-webpack-plugin@6.4.1:
- version "6.4.1"
- resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-6.4.1.tgz#138cd9b436dbca0a6d071720d5414848992ec47e"
- integrity sha512-MXyPCjdPVx5iiWyl40Va3JGh27bKzOTNY3NjUTrosD2q7dR/cLD0013uqJ3BpFbUjyONINjb6qI7nDIJujrMbA==
- dependencies:
- cacache "^15.0.5"
- fast-glob "^3.2.4"
- find-cache-dir "^3.3.1"
- glob-parent "^5.1.1"
- globby "^11.0.1"
- loader-utils "^2.0.0"
+ fast-glob "^3.2.11"
+ glob-parent "^6.0.1"
+ globby "^13.1.1"
normalize-path "^3.0.0"
- p-limit "^3.0.2"
- schema-utils "^3.0.0"
- serialize-javascript "^5.0.1"
- webpack-sources "^1.4.3"
-
-core-js-compat@^3.21.0:
- version "3.21.1"
- resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.21.1.tgz#cac369f67c8d134ff8f9bd1623e3bc2c42068c82"
- integrity sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==
- dependencies:
- browserslist "^4.19.1"
- semver "7.0.0"
+ schema-utils "^4.0.0"
+ serialize-javascript "^6.0.0"
-core-js-compat@^3.22.1:
- version "3.23.4"
- resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.23.4.tgz#56ad4a352884317a15f6b04548ff7139d23b917f"
- integrity sha512-RkSRPe+JYEoflcsuxJWaiMPhnZoFS51FcIxm53k4KzhISCBTmaGlto9dTIrYuk0hnJc3G6pKufAKepHnBq6B6Q==
+core-js-compat@^3.25.1:
+ version "3.26.0"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.26.0.tgz#94e2cf8ba3e63800c4956ea298a6473bc9d62b44"
+ integrity sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A==
dependencies:
- browserslist "^4.21.1"
- semver "7.0.0"
-
-core-js@^2.4.0, core-js@^2.5.0:
- version "2.6.2"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.2.tgz#267988d7268323b349e20b4588211655f0e83944"
+ browserslist "^4.21.4"
core-util-is@~1.0.0:
version "1.0.2"
@@ -3383,16 +3557,6 @@ cors@~2.8.5:
object-assign "^4"
vary "^1"
-cosmiconfig@^5.0.0:
- version "5.2.1"
- resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a"
- integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==
- dependencies:
- import-fresh "^2.0.0"
- is-directory "^0.3.1"
- js-yaml "^3.13.1"
- parse-json "^4.0.0"
-
cosmiconfig@^7.0.0:
version "7.0.1"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d"
@@ -3404,182 +3568,174 @@ cosmiconfig@^7.0.0:
path-type "^4.0.0"
yaml "^1.10.0"
-create-ecdh@^4.0.0:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff"
+cosmiconfig@^7.1.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
+ integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==
dependencies:
- bn.js "^4.1.0"
- elliptic "^6.0.0"
+ "@types/parse-json" "^4.0.0"
+ import-fresh "^3.2.1"
+ parse-json "^5.0.0"
+ path-type "^4.0.0"
+ yaml "^1.10.0"
-create-hash@^1.1.0, create-hash@^1.1.2:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
- dependencies:
- cipher-base "^1.0.1"
- inherits "^2.0.1"
- md5.js "^1.3.4"
- ripemd160 "^2.0.1"
- sha.js "^2.4.0"
+cropperjs@1.5.13:
+ version "1.5.13"
+ resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.5.13.tgz#eb1682f01d17c70ed5244317091d745c9a249ef8"
+ integrity sha512-by7jKAo73y5/Do0K6sxdTKHgndY0NMjG2bEdgeJxycbcmHuCiMXqw8sxy5C5Y5WTOTcDGmbT7Sr5CgKOXR06OA==
-create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
- version "1.1.7"
- resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff"
+cross-spawn@7.0.3, cross-spawn@^7.0.2:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
+ integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
dependencies:
- cipher-base "^1.0.3"
- create-hash "^1.1.0"
- inherits "^2.0.1"
- ripemd160 "^2.0.0"
- safe-buffer "^5.0.1"
- sha.js "^2.4.8"
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
-cropperjs@1.5.12:
- version "1.5.12"
- resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.5.12.tgz#d9c0db2bfb8c0d769d51739e8f916bbc44e10f50"
- integrity sha512-re7UdjE5UnwdrovyhNzZ6gathI4Rs3KGCBSc8HCIjUo5hO42CtzyblmWLj6QWVw7huHyDMfpKxhiO2II77nhDw==
+css-declaration-sorter@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz#72ebd995c8f4532ff0036631f7365cce9759df14"
+ integrity sha512-OGT677UGHJTAVMRhPO+HJ4oKln3wkBTwtDFH0ojbqm+MJm6xuDMHp2nkhh/ThaBqq20IbraBQSWKfSLNHQO9Og==
-cross-spawn@4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41"
- integrity sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=
- dependencies:
- lru-cache "^4.0.1"
- which "^1.2.9"
-
-cross-spawn@^6.0.5:
- version "6.0.5"
- resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
- dependencies:
- nice-try "^1.0.4"
- path-key "^2.0.1"
- semver "^5.5.0"
- shebang-command "^1.2.0"
- which "^1.2.9"
-
-crypto-browserify@^3.11.0:
- version "3.12.0"
- resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
- dependencies:
- browserify-cipher "^1.0.0"
- browserify-sign "^4.0.0"
- create-ecdh "^4.0.0"
- create-hash "^1.1.0"
- create-hmac "^1.1.0"
- diffie-hellman "^5.0.0"
- inherits "^2.0.1"
- pbkdf2 "^3.0.3"
- public-encrypt "^4.0.0"
- randombytes "^2.0.0"
- randomfill "^1.0.3"
-
-css-color-names@0.0.4:
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
-
-css-loader@0.28.11:
- version "0.28.11"
- resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.11.tgz#c3f9864a700be2711bb5a2462b2389b1a392dab7"
- integrity sha512-wovHgjAx8ZIMGSL8pTys7edA1ClmzxHeY6n/d97gg5odgsxEgKjULPR0viqyC+FWMCL9sfqoC/QCUBo62tLvPg==
- dependencies:
- babel-code-frame "^6.26.0"
- css-selector-tokenizer "^0.7.0"
- cssnano "^3.10.0"
- icss-utils "^2.1.0"
- loader-utils "^1.0.2"
- lodash.camelcase "^4.3.0"
- object-assign "^4.1.1"
- postcss "^5.0.6"
- postcss-modules-extract-imports "^1.2.0"
- postcss-modules-local-by-default "^1.2.0"
- postcss-modules-scope "^1.1.0"
- postcss-modules-values "^1.3.0"
- postcss-value-parser "^3.3.0"
- source-list-map "^2.0.0"
-
-css-select@^1.1.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
- dependencies:
- boolbase "~1.0.0"
- css-what "2.1"
- domutils "1.5.1"
- nth-check "~1.0.1"
+css-functions-list@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.1.0.tgz#cf5b09f835ad91a00e5959bcfc627cd498e1321b"
+ integrity sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==
-css-selector-tokenizer@^0.7.0:
- version "0.7.1"
- resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz#a177271a8bca5019172f4f891fc6eed9cbf68d5d"
+css-loader@6.10.0:
+ version "6.10.0"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.10.0.tgz#7c172b270ec7b833951b52c348861206b184a4b7"
+ integrity sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==
+ dependencies:
+ icss-utils "^5.1.0"
+ postcss "^8.4.33"
+ postcss-modules-extract-imports "^3.0.0"
+ postcss-modules-local-by-default "^4.0.4"
+ postcss-modules-scope "^3.1.1"
+ postcss-modules-values "^4.0.0"
+ postcss-value-parser "^4.2.0"
+ semver "^7.5.4"
+
+css-minimizer-webpack-plugin@4.2.2:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-4.2.2.tgz#79f6199eb5adf1ff7ba57f105e3752d15211eb35"
+ integrity sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA==
+ dependencies:
+ cssnano "^5.1.8"
+ jest-worker "^29.1.2"
+ postcss "^8.4.17"
+ schema-utils "^4.0.0"
+ serialize-javascript "^6.0.0"
+ source-map "^0.6.1"
+
+css-select@^4.1.3:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b"
+ integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==
dependencies:
- cssesc "^0.1.0"
- fastparse "^1.1.1"
- regexpu-core "^1.0.0"
+ boolbase "^1.0.0"
+ css-what "^6.0.1"
+ domhandler "^4.3.1"
+ domutils "^2.8.0"
+ nth-check "^2.0.1"
-css-what@2.1:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.2.tgz#c0876d9d0480927d7d4920dcd72af3595649554d"
+css-tree@^1.1.2, css-tree@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d"
+ integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==
+ dependencies:
+ mdn-data "2.0.14"
+ source-map "^0.6.1"
-cssesc@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
+css-what@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
+ integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
-cssnano@^3.10.0:
- version "3.10.0"
- resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38"
- dependencies:
- autoprefixer "^6.3.1"
- decamelize "^1.1.2"
- defined "^1.0.0"
- has "^1.0.1"
- object-assign "^4.0.1"
- postcss "^5.0.14"
- postcss-calc "^5.2.0"
- postcss-colormin "^2.1.8"
- postcss-convert-values "^2.3.4"
- postcss-discard-comments "^2.0.4"
- postcss-discard-duplicates "^2.0.1"
- postcss-discard-empty "^2.0.1"
- postcss-discard-overridden "^0.1.1"
- postcss-discard-unused "^2.2.1"
- postcss-filter-plugins "^2.0.0"
- postcss-merge-idents "^2.1.5"
- postcss-merge-longhand "^2.0.1"
- postcss-merge-rules "^2.0.3"
- postcss-minify-font-values "^1.0.2"
- postcss-minify-gradients "^1.0.1"
- postcss-minify-params "^1.0.4"
- postcss-minify-selectors "^2.0.4"
- postcss-normalize-charset "^1.1.0"
- postcss-normalize-url "^3.0.7"
- postcss-ordered-values "^2.1.0"
- postcss-reduce-idents "^2.2.2"
- postcss-reduce-initial "^1.0.0"
- postcss-reduce-transforms "^1.0.3"
- postcss-svgo "^2.1.1"
- postcss-unique-selectors "^2.0.2"
- postcss-value-parser "^3.2.3"
- postcss-zindex "^2.0.1"
-
-csso@~2.3.1:
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85"
+cssnano-preset-default@^5.2.12:
+ version "5.2.12"
+ resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.12.tgz#ebe6596ec7030e62c3eb2b3c09f533c0644a9a97"
+ integrity sha512-OyCBTZi+PXgylz9HAA5kHyoYhfGcYdwFmyaJzWnzxuGRtnMw/kR6ilW9XzlzlRAtB6PLT/r+prYgkef7hngFew==
+ dependencies:
+ css-declaration-sorter "^6.3.0"
+ cssnano-utils "^3.1.0"
+ postcss-calc "^8.2.3"
+ postcss-colormin "^5.3.0"
+ postcss-convert-values "^5.1.2"
+ postcss-discard-comments "^5.1.2"
+ postcss-discard-duplicates "^5.1.0"
+ postcss-discard-empty "^5.1.1"
+ postcss-discard-overridden "^5.1.0"
+ postcss-merge-longhand "^5.1.6"
+ postcss-merge-rules "^5.1.2"
+ postcss-minify-font-values "^5.1.0"
+ postcss-minify-gradients "^5.1.1"
+ postcss-minify-params "^5.1.3"
+ postcss-minify-selectors "^5.2.1"
+ postcss-normalize-charset "^5.1.0"
+ postcss-normalize-display-values "^5.1.0"
+ postcss-normalize-positions "^5.1.1"
+ postcss-normalize-repeat-style "^5.1.1"
+ postcss-normalize-string "^5.1.0"
+ postcss-normalize-timing-functions "^5.1.0"
+ postcss-normalize-unicode "^5.1.0"
+ postcss-normalize-url "^5.1.0"
+ postcss-normalize-whitespace "^5.1.1"
+ postcss-ordered-values "^5.1.3"
+ postcss-reduce-initial "^5.1.0"
+ postcss-reduce-transforms "^5.1.0"
+ postcss-svgo "^5.1.0"
+ postcss-unique-selectors "^5.1.1"
+
+cssnano-utils@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861"
+ integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==
+
+cssnano@^5.1.8:
+ version "5.1.13"
+ resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.13.tgz#83d0926e72955332dc4802a7070296e6258efc0a"
+ integrity sha512-S2SL2ekdEz6w6a2epXn4CmMKU4K3KpcyXLKfAYc9UQQqJRkD/2eLUG0vJ3Db/9OvO5GuAdgXw3pFbR6abqghDQ==
+ dependencies:
+ cssnano-preset-default "^5.2.12"
+ lilconfig "^2.0.3"
+ yaml "^1.10.2"
+
+csso@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529"
+ integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==
dependencies:
- clap "^1.0.9"
- source-map "^0.5.3"
+ css-tree "^1.1.2"
+
+cssom@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36"
+ integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==
+
+cssom@~0.3.6:
+ version "0.3.8"
+ resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
+ integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
+
+cssstyle@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852"
+ integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==
+ dependencies:
+ cssom "~0.3.6"
csstype@^2.6.8:
version "2.6.20"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.20.tgz#9229c65ea0b260cf4d3d997cb06288e36a8d6dda"
integrity sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==
-currently-unhandled@^0.4.1:
- version "0.4.1"
- resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
- dependencies:
- array-find-index "^1.0.1"
-
custom-event-polyfill@1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz#9bc993ddda937c1a30ccd335614c6c58c4f87aee"
@@ -3589,53 +3745,31 @@ custom-event@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
-cyclist@~0.2.2:
- version "0.2.2"
- resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
-
-data-uri-to-buffer@1:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz#77163ea9c20d8641b4707e8f18abdf9a78f34835"
+data-urls@^3.0.1:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143"
+ integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==
+ dependencies:
+ abab "^2.0.6"
+ whatwg-mimetype "^3.0.0"
+ whatwg-url "^11.0.0"
date-format@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.6.tgz#f6138b8f17968df9815b3d101fc06b0523f066c5"
integrity sha512-B9vvg5rHuQ8cbUXE/RMWMyX2YA5TecT3jKF5fLtGNlzPlU7zblSPmAm2OImDbWL+LDOQ6pUm+4LOFz+ywS41Zw==
-date-now@^0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
-
-dateformat@^1.0.6:
- version "1.0.12"
- resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9"
- dependencies:
- get-stdin "^4.0.1"
- meow "^3.3.0"
-
de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
-debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
+debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
dependencies:
ms "2.0.0"
-debug@2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
- dependencies:
- ms "0.7.1"
-
-debug@2.6.8:
- version "2.6.8"
- resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
- dependencies:
- ms "2.0.0"
-
debug@4:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
@@ -3649,6 +3783,20 @@ debug@4.1.0:
dependencies:
ms "^2.1.1"
+debug@4.3.3:
+ version "4.3.3"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
+ integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
+ dependencies:
+ ms "2.1.2"
+
+debug@4.3.4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
debug@=3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
@@ -3662,19 +3810,19 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"
-debug@^4.0.0, debug@^4.3.1, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
- version "4.3.4"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
- integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
- dependencies:
- ms "2.1.2"
-
-debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
+debug@^4.1.0, debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
dependencies:
ms "^2.1.1"
+debug@~4.3.4:
+ version "4.3.6"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b"
+ integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==
+ dependencies:
+ ms "2.1.2"
+
decamelize-keys@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9"
@@ -3683,82 +3831,73 @@ decamelize-keys@^1.1.0:
decamelize "^1.1.0"
map-obj "^1.0.0"
-decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0:
+decamelize@^1.1.0, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
-decode-uri-component@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
+decamelize@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
+ integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
-deep-eql@0.1.3, deep-eql@^0.1.3:
- version "0.1.3"
- resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2"
+decimal.js@^10.3.1:
+ version "10.4.3"
+ resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
+ integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
+
+deep-eql@4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.0.1.tgz#2b65bc89491d193780c452edee2144a91bb0a445"
+ integrity sha512-D/Oxqobjr+kxaHsgiQBZq9b6iAWdEj5W/JdJm8deNduAPc9CwXQ3BJJCuEqlrPXcy45iOMkGPZ0T81Dnz7UDCA==
dependencies:
- type-detect "0.1.1"
+ type-detect "^4.0.0"
-deep-extend@^0.6.0:
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+deep-eql@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.2.tgz#270ceb902f87724077e6f6449aed81463f42fc1c"
+ integrity sha512-gT18+YW4CcW/DBNTwAmqTtkJh7f9qqScu2qFVlx7kCoeY9tlBu9cUcr7+I+Z/noG8INehS3xQgLpTtd/QUTn4w==
+ dependencies:
+ type-detect "^4.0.0"
-deep-is@^0.1.3, deep-is@~0.1.3:
+deep-is@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
-define-properties@^1.1.2, define-properties@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
- dependencies:
- object-keys "^1.0.12"
-
-define-property@^0.2.5:
- version "0.2.5"
- resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
- dependencies:
- is-descriptor "^0.1.0"
-
-define-property@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6"
- dependencies:
- is-descriptor "^1.0.0"
+deep-is@~0.1.3:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
+ integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
-define-property@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d"
+defaults@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
+ integrity sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==
dependencies:
- is-descriptor "^1.0.2"
- isobject "^3.0.1"
+ clone "^1.0.2"
-defined@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
+define-lazy-prop@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
+ integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
-degenerator@~1.0.2:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095"
+define-properties@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
dependencies:
- ast-types "0.x.x"
- escodegen "1.x.x"
- esprima "3.x.x"
+ object-keys "^1.0.12"
-del@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/del/-/del-6.0.0.tgz#0b40d0332cea743f1614f818be4feb717714c952"
- integrity sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==
+define-properties@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1"
+ integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==
dependencies:
- globby "^11.0.1"
- graceful-fs "^4.2.4"
- is-glob "^4.0.1"
- is-path-cwd "^2.2.0"
- is-path-inside "^3.0.2"
- p-map "^4.0.0"
- rimraf "^3.0.2"
- slash "^3.0.0"
+ has-property-descriptors "^1.0.0"
+ object-keys "^1.1.1"
-delegates@^1.0.0:
+delayed-stream@~1.0.0:
version "1.0.0"
- resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
+ resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+ integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
depd@2.0.0:
version "2.0.0"
@@ -3769,52 +3908,29 @@ depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
-des.js@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
- dependencies:
- inherits "^2.0.1"
- minimalistic-assert "^1.0.0"
-
destroy@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
-detect-indent@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
- dependencies:
- repeating "^2.0.0"
-
-detect-libc@^1.0.2:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
-
di@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
-diff@1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf"
-
-diff@3.2.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
+didyoumean@1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
+ integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
-diff@3.5.0, diff@^3.1.0:
- version "3.5.0"
- resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
- integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
+diff@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
+ integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
-diffie-hellman@^5.0.0:
- version "5.0.3"
- resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
- dependencies:
- bn.js "^4.1.0"
- miller-rabin "^4.0.0"
- randombytes "^2.0.0"
+diff@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40"
+ integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==
dijkstrajs@^1.0.1:
version "1.0.2"
@@ -3841,9 +3957,10 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
-dom-converter@~0.2:
+dom-converter@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
+ integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==
dependencies:
utila "~0.4"
@@ -3857,115 +3974,121 @@ dom-serialize@^2.2.1:
extend "^3.0.0"
void-elements "^2.0.0"
-dom-serializer@0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
+dom-serializer@^1.0.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30"
+ integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==
dependencies:
- domelementtype "~1.1.1"
- entities "~1.1.1"
+ domelementtype "^2.0.1"
+ domhandler "^4.2.0"
+ entities "^2.0.0"
-domain-browser@^1.1.1:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
+dom-serializer@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
+ integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
+ dependencies:
+ domelementtype "^2.3.0"
+ domhandler "^5.0.2"
+ entities "^4.2.0"
-domelementtype@1, domelementtype@^1.3.1:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
+domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
+ integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
-domelementtype@~1.1.1:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
+domexception@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673"
+ integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==
+ dependencies:
+ webidl-conversions "^7.0.0"
-domhandler@2.1:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594"
+domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c"
+ integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==
dependencies:
- domelementtype "1"
+ domelementtype "^2.2.0"
-domhandler@^2.3.0:
- version "2.4.2"
- resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
- integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==
+domhandler@^5.0.1, domhandler@^5.0.2:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
+ integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
dependencies:
- domelementtype "1"
+ domelementtype "^2.3.0"
-domutils@1.1:
- version "1.1.6"
- resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485"
+domutils@^2.5.2, domutils@^2.8.0:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
+ integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
dependencies:
- domelementtype "1"
+ dom-serializer "^1.0.1"
+ domelementtype "^2.2.0"
+ domhandler "^4.2.0"
-domutils@1.5.1:
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
+domutils@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c"
+ integrity sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==
dependencies:
- dom-serializer "0"
- domelementtype "1"
+ dom-serializer "^2.0.0"
+ domelementtype "^2.3.0"
+ domhandler "^5.0.1"
-domutils@^1.5.1:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
- integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
+dot-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
+ integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==
dependencies:
- dom-serializer "0"
- domelementtype "1"
+ no-case "^3.0.4"
+ tslib "^2.0.3"
+
+dotenv@10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
+ integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
-duplexify@^3.4.2, duplexify@^3.6.0:
- version "3.7.1"
- resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
+editorconfig@^0.15.3:
+ version "0.15.3"
+ resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5"
+ integrity sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==
dependencies:
- end-of-stream "^1.0.0"
- inherits "^2.0.1"
- readable-stream "^2.0.0"
- stream-shift "^1.0.0"
+ commander "^2.19.0"
+ lru-cache "^4.1.5"
+ semver "^5.6.0"
+ sigmund "^1.0.1"
ee-first@1.1.1:
version "1.1.1"
- resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
-
-ejs@2.5.7:
- version "2.5.7"
- resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a"
-
-electron-to-chromium@^1.2.7:
- version "1.3.100"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.100.tgz#899fb088def210aee6b838a47655bbb299190e13"
+ resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
-electron-to-chromium@^1.3.488:
- version "1.3.509"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.509.tgz#830fcb89cd66dc2984d18d794973b99e3f00584c"
- integrity sha512-cN4lkjNRuTG8rtAqTOVgwpecEC2kbKA04PG6YijcKGHK/kD0xLjiqExcAOmLUwtXZRF8cBeam2I0VZcih919Ug==
+ejs@3.1.8:
+ version "3.1.8"
+ resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b"
+ integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==
+ dependencies:
+ jake "^10.8.5"
electron-to-chromium@^1.4.172:
version "1.4.187"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.187.tgz#b884493df00816dc2ce928958c4f6a51a93fe1a8"
integrity sha512-t3iFLHVIMhB8jGZ+8ui951nz6Bna5qKfhxezG3wzXdBJ79qFKPsE2chjjVFNqC1ewhfrPQrw9pmVeo4FFpZeQA==
-electron-to-chromium@^1.4.188:
- version "1.4.191"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.191.tgz#01dd4bf32502a48ce24bf3890b5553a1c5f93539"
- integrity sha512-MeEaiuoSFh4G+rrN+Ilm1KJr8pTTZloeLurcZ+PRcthvdK1gWThje+E6baL7/7LoNctrzCncavAG/j/vpES9jg==
-
-electron-to-chromium@^1.4.84:
- version "1.4.86"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.86.tgz#90fe4a9787f48d6522957213408e08a8126b2ebc"
- integrity sha512-EVTZ+igi8x63pK4bPuA95PXIs2b2Cowi3WQwI9f9qManLiZJOD1Lash1J3W4TvvcUCcIR4o/rgi9o8UicXSO+w==
-
-elliptic@^6.0.0:
- version "6.4.1"
- resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a"
- dependencies:
- bn.js "^4.4.0"
- brorand "^1.0.1"
- hash.js "^1.0.0"
- hmac-drbg "^1.0.0"
- inherits "^2.0.1"
- minimalistic-assert "^1.0.0"
- minimalistic-crypto-utils "^1.0.0"
-
-emoji-regex@^7.0.1:
- version "7.0.3"
- resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
+electron-to-chromium@^1.4.202:
+ version "1.4.219"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.219.tgz#a7a672304b6aa4f376918d3f63a47f2c3906009a"
+ integrity sha512-zoQJsXOUw0ZA0YxbjkmzBumAJRtr6je5JySuL/bAoFs0DuLiLJ+5FzRF7/ZayihxR2QcewlRZVm5QZdUhwjOgA==
+
+electron-to-chromium@^1.4.251:
+ version "1.4.276"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.276.tgz#17837b19dafcc43aba885c4689358b298c19b520"
+ integrity sha512-EpuHPqu8YhonqLBXHoU6hDJCD98FCe6KDoet3/gY1qsQ6usjJoHqBH2YIVs8FXaAtHwVL8Uqa/fsYao/vq9VWQ==
+
+electron-to-chromium@^1.4.668:
+ version "1.4.690"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.690.tgz#dd5145d45c49c08a9a6f7454127e660bdf9a3fa7"
+ integrity sha512-+2OAGjUx68xElQhydpcbqH50hE8Vs2K6TkAeLhICYfndb67CVH0UsZaijmRUE3rHlIxU1u0jxwhgVe6fK3YANA==
emoji-regex@^8.0.0:
version "8.0.0"
@@ -3990,23 +4113,21 @@ encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
-end-of-stream@^1.0.0, end-of-stream@^1.1.0:
+end-of-stream@^1.1.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
dependencies:
once "^1.4.0"
-engine.io-parser@~5.0.3:
- version "5.0.3"
- resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.3.tgz#ca1f0d7b11e290b4bfda251803baea765ed89c09"
- integrity sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==
- dependencies:
- "@socket.io/base64-arraybuffer" "~1.0.2"
+engine.io-parser@~5.2.1:
+ version "5.2.3"
+ resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f"
+ integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==
-engine.io@~6.2.0:
- version "6.2.0"
- resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.2.0.tgz#003bec48f6815926f2b1b17873e576acd54f41d0"
- integrity sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==
+engine.io@~6.5.2:
+ version "6.5.5"
+ resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.5.tgz#430b80d8840caab91a50e9e23cb551455195fc93"
+ integrity sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==
dependencies:
"@types/cookie" "^0.4.1"
"@types/cors" "^2.8.12"
@@ -4016,39 +4137,54 @@ engine.io@~6.2.0:
cookie "~0.4.1"
cors "~2.8.5"
debug "~4.3.1"
- engine.io-parser "~5.0.3"
- ws "~8.2.3"
+ engine.io-parser "~5.2.1"
+ ws "~8.17.1"
-enhanced-resolve@^4.5.0:
- version "4.5.0"
- resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec"
- integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==
+enhanced-resolve@^5.10.0:
+ version "5.10.0"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6"
+ integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==
dependencies:
- graceful-fs "^4.1.2"
- memory-fs "^0.5.0"
- tapable "^1.0.0"
+ graceful-fs "^4.2.4"
+ tapable "^2.2.0"
ent@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
-entities@^1.1.1, entities@~1.1.1:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
+entities@^2.0.0, entities@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
+ integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
+
+entities@^4.2.0, entities@^4.3.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174"
+ integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==
+
+entities@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
+ integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
+
+envinfo@7.8.1:
+ version "7.8.1"
+ resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475"
+ integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==
-errno@^0.1.3, errno@~0.1.7:
+errno@^0.1.3:
version "0.1.7"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
dependencies:
prr "~1.0.1"
-error-ex@^1.2.0, error-ex@^1.3.1:
+error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
dependencies:
is-arrayish "^0.2.1"
-es-abstract@^1.19.0, es-abstract@^1.19.1:
+es-abstract@^1.19.0:
version "1.19.1"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3"
integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==
@@ -4074,24 +4210,65 @@ es-abstract@^1.19.0, es-abstract@^1.19.1:
string.prototype.trimstart "^1.0.4"
unbox-primitive "^1.0.1"
-es-abstract@^1.5.1:
- version "1.13.0"
- resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
+es-abstract@^1.20.4:
+ version "1.21.1"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.1.tgz#e6105a099967c08377830a0c9cb589d570dd86c6"
+ integrity sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==
dependencies:
- es-to-primitive "^1.2.0"
+ available-typed-arrays "^1.0.5"
+ call-bind "^1.0.2"
+ es-set-tostringtag "^2.0.1"
+ es-to-primitive "^1.2.1"
function-bind "^1.1.1"
+ function.prototype.name "^1.1.5"
+ get-intrinsic "^1.1.3"
+ get-symbol-description "^1.0.0"
+ globalthis "^1.0.3"
+ gopd "^1.0.1"
has "^1.0.3"
- is-callable "^1.1.4"
- is-regex "^1.0.4"
- object-keys "^1.0.12"
+ has-property-descriptors "^1.0.0"
+ has-proto "^1.0.1"
+ has-symbols "^1.0.3"
+ internal-slot "^1.0.4"
+ is-array-buffer "^3.0.1"
+ is-callable "^1.2.7"
+ is-negative-zero "^2.0.2"
+ is-regex "^1.1.4"
+ is-shared-array-buffer "^1.0.2"
+ is-string "^1.0.7"
+ is-typed-array "^1.1.10"
+ is-weakref "^1.0.2"
+ object-inspect "^1.12.2"
+ object-keys "^1.1.1"
+ object.assign "^4.1.4"
+ regexp.prototype.flags "^1.4.3"
+ safe-regex-test "^1.0.0"
+ string.prototype.trimend "^1.0.6"
+ string.prototype.trimstart "^1.0.6"
+ typed-array-length "^1.0.4"
+ unbox-primitive "^1.0.2"
+ which-typed-array "^1.1.9"
+
+es-module-lexer@^0.9.0:
+ version "0.9.3"
+ resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19"
+ integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==
+
+es-set-tostringtag@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8"
+ integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==
+ dependencies:
+ get-intrinsic "^1.1.3"
+ has "^1.0.3"
+ has-tostringtag "^1.0.0"
-es-to-primitive@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
+es-shim-unscopables@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241"
+ integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==
dependencies:
- is-callable "^1.1.4"
- is-date-object "^1.0.1"
- is-symbol "^1.0.2"
+ has "^1.0.3"
es-to-primitive@^1.2.1:
version "1.2.1"
@@ -4102,11 +4279,6 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
-escalade@^3.0.1:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4"
- integrity sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==
-
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
@@ -4117,214 +4289,221 @@ escape-html@1.0.3, escape-html@~1.0.3:
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
-escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
-escodegen@1.8.x:
- version "1.8.1"
- resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018"
- dependencies:
- esprima "^2.7.1"
- estraverse "^1.9.1"
- esutils "^2.0.2"
- optionator "^0.8.1"
- optionalDependencies:
- source-map "~0.2.0"
-
-escodegen@1.x.x, escodegen@^1.6.1:
- version "1.11.0"
- resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589"
+escodegen@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd"
+ integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==
dependencies:
- esprima "^3.1.3"
- estraverse "^4.2.0"
+ esprima "^4.0.1"
+ estraverse "^5.2.0"
esutils "^2.0.2"
optionator "^0.8.1"
optionalDependencies:
source-map "~0.6.1"
-eslint-config-standard@12.0.0:
- version "12.0.0"
- resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-12.0.0.tgz#638b4c65db0bd5a41319f96bba1f15ddad2107d9"
- integrity sha512-COUz8FnXhqFitYj4DTqHzidjIL/t4mumGZto5c7DrBpvWoie+Sn3P4sLEzUGeYhRElWuFEf8K1S1EfvD1vixCQ==
+eslint-config-standard@17.0.0:
+ version "17.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz#fd5b6cf1dcf6ba8d29f200c461de2e19069888cf"
+ integrity sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==
-eslint-friendly-formatter@2.0.7:
- version "2.0.7"
- resolved "https://registry.yarnpkg.com/eslint-friendly-formatter/-/eslint-friendly-formatter-2.0.7.tgz#657f95a19af4989636afebb1cc9de6cebbd088ee"
- integrity sha1-ZX+VoZr0mJY2r+uxzJ3mzrvQiO4=
+eslint-formatter-friendly@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-formatter-friendly/-/eslint-formatter-friendly-7.0.0.tgz#32a4998ababa0a39994aed629b831fda7dabc864"
+ integrity sha512-WXg2D5kMHcRxIZA3ulxdevi8/BGTXu72pfOO5vXHqcAfClfIWDSlOljROjCSOCcKvilgmHz1jDWbvFCZHjMQ5w==
dependencies:
- chalk "^1.0.0"
- extend "^3.0.0"
- minimist "^1.2.0"
- text-table "^0.2.0"
+ "@babel/code-frame" "7.0.0"
+ chalk "2.4.2"
+ extend "3.0.2"
+ strip-ansi "5.2.0"
+ text-table "0.2.0"
-eslint-import-resolver-node@^0.3.6:
- version "0.3.6"
- resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd"
- integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==
+eslint-import-resolver-node@^0.3.7:
+ version "0.3.7"
+ resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7"
+ integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==
dependencies:
debug "^3.2.7"
- resolve "^1.20.0"
-
-eslint-loader@2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-2.2.1.tgz#28b9c12da54057af0845e2a6112701a2f6bf8337"
- integrity sha512-RLgV9hoCVsMLvOxCuNjdqOrUqIj9oJg8hF44vzJaYqsAHuY9G2YAeN3joQ9nxP0p5Th9iFSIpKo+SD8KISxXRg==
- dependencies:
- loader-fs-cache "^1.0.0"
- loader-utils "^1.0.2"
- object-assign "^4.0.1"
- object-hash "^1.1.4"
- rimraf "^2.6.1"
+ is-core-module "^2.11.0"
+ resolve "^1.22.1"
-eslint-module-utils@^2.7.3:
- version "2.7.3"
- resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee"
- integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==
+eslint-module-utils@^2.7.4:
+ version "2.7.4"
+ resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974"
+ integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==
dependencies:
debug "^3.2.7"
- find-up "^2.1.0"
-eslint-plugin-es@^1.3.1:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.0.tgz#475f65bb20c993fc10e8c8fe77d1d60068072da6"
+eslint-plugin-es@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz#f0822f0c18a535a97c3e714e89f88586a7641ec9"
+ integrity sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==
dependencies:
- eslint-utils "^1.3.0"
- regexpp "^2.0.1"
+ eslint-utils "^2.0.0"
+ regexpp "^3.0.0"
-eslint-plugin-import@2.26.0:
- version "2.26.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b"
- integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==
+eslint-plugin-import@2.27.5:
+ version "2.27.5"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz#876a6d03f52608a3e5bb439c2550588e51dd6c65"
+ integrity sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==
dependencies:
- array-includes "^3.1.4"
- array.prototype.flat "^1.2.5"
- debug "^2.6.9"
+ array-includes "^3.1.6"
+ array.prototype.flat "^1.3.1"
+ array.prototype.flatmap "^1.3.1"
+ debug "^3.2.7"
doctrine "^2.1.0"
- eslint-import-resolver-node "^0.3.6"
- eslint-module-utils "^2.7.3"
+ eslint-import-resolver-node "^0.3.7"
+ eslint-module-utils "^2.7.4"
has "^1.0.3"
- is-core-module "^2.8.1"
+ is-core-module "^2.11.0"
is-glob "^4.0.3"
minimatch "^3.1.2"
- object.values "^1.1.5"
- resolve "^1.22.0"
+ object.values "^1.1.6"
+ resolve "^1.22.1"
+ semver "^6.3.0"
tsconfig-paths "^3.14.1"
-eslint-plugin-node@7.0.1:
- version "7.0.1"
- resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-7.0.1.tgz#a6e054e50199b2edd85518b89b4e7b323c9f36db"
- integrity sha512-lfVw3TEqThwq0j2Ba/Ckn2ABdwmL5dkOgAux1rvOk6CO7A6yGyPI2+zIxN6FyNkp1X1X/BSvKOceD6mBWSj4Yw==
+eslint-plugin-n@15.6.1:
+ version "15.6.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-n/-/eslint-plugin-n-15.6.1.tgz#f7e77f24abb92a550115cf11e29695da122c398c"
+ integrity sha512-R9xw9OtCRxxaxaszTQmQAlPgM+RdGjaL1akWuY/Fv9fRAi8Wj4CUKc6iYVG8QNRjRuo8/BqVYIpfqberJUEacA==
dependencies:
- eslint-plugin-es "^1.3.1"
- eslint-utils "^1.3.1"
- ignore "^4.0.2"
- minimatch "^3.0.4"
- resolve "^1.8.1"
- semver "^5.5.0"
-
-eslint-plugin-promise@4.3.1:
- version "4.3.1"
- resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz#61485df2a359e03149fdafc0a68b0e030ad2ac45"
- integrity sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==
+ builtins "^5.0.1"
+ eslint-plugin-es "^4.1.0"
+ eslint-utils "^3.0.0"
+ ignore "^5.1.1"
+ is-core-module "^2.11.0"
+ minimatch "^3.1.2"
+ resolve "^1.22.1"
+ semver "^7.3.8"
-eslint-plugin-standard@4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz#0c3bf3a67e853f8bbbc580fb4945fbf16f41b7c5"
- integrity sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ==
+eslint-plugin-promise@6.1.1:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz#269a3e2772f62875661220631bd4dafcb4083816"
+ integrity sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==
-eslint-plugin-vue@5.2.3:
- version "5.2.3"
- resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-5.2.3.tgz#3ee7597d823b5478804b2feba9863b1b74273961"
- integrity sha512-mGwMqbbJf0+VvpGR5Lllq0PMxvTdrZ/ZPjmhkacrCHbubJeJOt+T6E3HUzAifa2Mxi7RSdJfC9HFpOeSYVMMIw==
+eslint-plugin-vue@9.9.0:
+ version "9.9.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.9.0.tgz#ac788ebccd2eb94d846a507df55da50693b80c91"
+ integrity sha512-YbubS7eK0J7DCf0U2LxvVP7LMfs6rC6UltihIgval3azO3gyDwEGVgsCMe1TmDiEkl6GdMKfRpaME6QxIYtzDQ==
dependencies:
- vue-eslint-parser "^5.0.0"
+ eslint-utils "^3.0.0"
+ natural-compare "^1.4.0"
+ nth-check "^2.0.1"
+ postcss-selector-parser "^6.0.9"
+ semver "^7.3.5"
+ vue-eslint-parser "^9.0.1"
+ xml-name-validator "^4.0.0"
-eslint-scope@^4.0.0, eslint-scope@^4.0.3:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
+eslint-scope@5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
+ integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
dependencies:
- esrecurse "^4.1.0"
+ esrecurse "^4.3.0"
estraverse "^4.1.1"
-eslint-utils@^1.3.0, eslint-utils@^1.3.1:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512"
+eslint-scope@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642"
+ integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^5.2.0"
-eslint-utils@^2.1.0:
+eslint-utils@^2.0.0, eslint-utils@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==
dependencies:
eslint-visitor-keys "^1.1.0"
-eslint-visitor-keys@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
+eslint-utils@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672"
+ integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==
+ dependencies:
+ eslint-visitor-keys "^2.0.0"
eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
-eslint@5.16.0:
- version "5.16.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea"
- integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==
+eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
+ integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
+
+eslint-visitor-keys@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
+ integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
+
+eslint-webpack-plugin@3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz#1978cdb9edc461e4b0195a20da950cf57988347c"
+ integrity sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==
dependencies:
- "@babel/code-frame" "^7.0.0"
- ajv "^6.9.1"
- chalk "^2.1.0"
- cross-spawn "^6.0.5"
- debug "^4.0.1"
+ "@types/eslint" "^7.29.0 || ^8.4.1"
+ jest-worker "^28.0.2"
+ micromatch "^4.0.5"
+ normalize-path "^3.0.0"
+ schema-utils "^4.0.0"
+
+eslint@8.33.0:
+ version "8.33.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.33.0.tgz#02f110f32998cb598c6461f24f4d306e41ca33d7"
+ integrity sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==
+ dependencies:
+ "@eslint/eslintrc" "^1.4.1"
+ "@humanwhocodes/config-array" "^0.11.8"
+ "@humanwhocodes/module-importer" "^1.0.1"
+ "@nodelib/fs.walk" "^1.2.8"
+ ajv "^6.10.0"
+ chalk "^4.0.0"
+ cross-spawn "^7.0.2"
+ debug "^4.3.2"
doctrine "^3.0.0"
- eslint-scope "^4.0.3"
- eslint-utils "^1.3.1"
- eslint-visitor-keys "^1.0.0"
- espree "^5.0.1"
- esquery "^1.0.1"
+ escape-string-regexp "^4.0.0"
+ eslint-scope "^7.1.1"
+ eslint-utils "^3.0.0"
+ eslint-visitor-keys "^3.3.0"
+ espree "^9.4.0"
+ esquery "^1.4.0"
esutils "^2.0.2"
- file-entry-cache "^5.0.1"
- functional-red-black-tree "^1.0.1"
- glob "^7.1.2"
- globals "^11.7.0"
- ignore "^4.0.6"
+ fast-deep-equal "^3.1.3"
+ file-entry-cache "^6.0.1"
+ find-up "^5.0.0"
+ glob-parent "^6.0.2"
+ globals "^13.19.0"
+ grapheme-splitter "^1.0.4"
+ ignore "^5.2.0"
import-fresh "^3.0.0"
imurmurhash "^0.1.4"
- inquirer "^6.2.2"
- js-yaml "^3.13.0"
+ is-glob "^4.0.0"
+ is-path-inside "^3.0.3"
+ js-sdsl "^4.1.4"
+ js-yaml "^4.1.0"
json-stable-stringify-without-jsonify "^1.0.1"
- levn "^0.3.0"
- lodash "^4.17.11"
- minimatch "^3.0.4"
- mkdirp "^0.5.1"
+ levn "^0.4.1"
+ lodash.merge "^4.6.2"
+ minimatch "^3.1.2"
natural-compare "^1.4.0"
- optionator "^0.8.2"
- path-is-inside "^1.0.2"
- progress "^2.0.0"
- regexpp "^2.0.1"
- semver "^5.5.1"
- strip-ansi "^4.0.0"
- strip-json-comments "^2.0.1"
- table "^5.2.3"
+ optionator "^0.9.1"
+ regexpp "^3.2.0"
+ strip-ansi "^6.0.1"
+ strip-json-comments "^3.1.0"
text-table "^0.2.0"
-espree@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/espree/-/espree-4.1.0.tgz#728d5451e0fd156c04384a7ad89ed51ff54eb25f"
- dependencies:
- acorn "^6.0.2"
- acorn-jsx "^5.0.0"
- eslint-visitor-keys "^1.0.0"
-
-espree@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a"
- dependencies:
- acorn "^6.0.7"
- acorn-jsx "^5.0.0"
- eslint-visitor-keys "^1.0.0"
-
espree@^6.0.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"
@@ -4334,38 +4513,52 @@ espree@^6.0.0:
acorn-jsx "^5.2.0"
eslint-visitor-keys "^1.1.0"
-esprima@2.7.x, esprima@^2.6.0, esprima@^2.7.1:
- version "2.7.3"
- resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
+espree@^9.3.1:
+ version "9.3.2"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596"
+ integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==
+ dependencies:
+ acorn "^8.7.1"
+ acorn-jsx "^5.3.2"
+ eslint-visitor-keys "^3.3.0"
-esprima@3.x.x, esprima@^3.1.3:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
+espree@^9.4.0:
+ version "9.4.0"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.0.tgz#cd4bc3d6e9336c433265fc0aa016fc1aaf182f8a"
+ integrity sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==
+ dependencies:
+ acorn "^8.8.0"
+ acorn-jsx "^5.3.2"
+ eslint-visitor-keys "^3.3.0"
-esprima@^4.0.0:
+esprima@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
+ integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
-esquery@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708"
+esquery@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5"
+ integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==
dependencies:
- estraverse "^4.0.0"
+ estraverse "^5.1.0"
-esrecurse@^4.1.0:
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf"
+esrecurse@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+ integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
dependencies:
- estraverse "^4.1.0"
-
-estraverse@^1.9.1:
- version "1.9.3"
- resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44"
+ estraverse "^5.2.0"
-estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
+estraverse@^4.1.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
+estraverse@^5.1.0, estraverse@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
+ integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
estree-walker@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
@@ -4384,49 +4577,24 @@ eventemitter3@^4.0.0:
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
-events@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88"
+events@^3.2.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
+ integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
eventsource-polyfill@0.9.6:
version "0.9.6"
resolved "https://registry.yarnpkg.com/eventsource-polyfill/-/eventsource-polyfill-0.9.6.tgz#10e0d187f111b167f28fdab918843ce7d818f13c"
integrity sha1-EODRh/ERsWfyj9q5GIQ859gY8Tw=
-evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"
- dependencies:
- md5.js "^1.3.4"
- safe-buffer "^5.1.1"
-
-execall@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/execall/-/execall-2.0.0.tgz#16a06b5fe5099df7d00be5d9c06eecded1663b45"
- integrity sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==
- dependencies:
- clone-regexp "^2.1.0"
-
-expand-brackets@^2.1.4:
- version "2.1.4"
- resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
- dependencies:
- debug "^2.3.3"
- define-property "^0.2.5"
- extend-shallow "^2.0.1"
- posix-character-classes "^0.1.0"
- regex-not "^1.0.0"
- snapdragon "^0.8.1"
- to-regex "^3.0.1"
-
-express@4.18.1:
- version "4.18.1"
- resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf"
- integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==
+express@4.18.2:
+ version "4.18.2"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
+ integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
- body-parser "1.20.0"
+ body-parser "1.20.1"
content-disposition "0.5.4"
content-type "~1.0.4"
cookie "0.5.0"
@@ -4445,7 +4613,7 @@ express@4.18.1:
parseurl "~1.3.3"
path-to-regexp "0.1.7"
proxy-addr "~2.0.7"
- qs "6.10.3"
+ qs "6.11.0"
range-parser "~1.2.1"
safe-buffer "5.2.1"
send "0.18.0"
@@ -4456,43 +4624,10 @@ express@4.18.1:
utils-merge "1.0.1"
vary "~1.1.2"
-extend-shallow@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
- dependencies:
- is-extendable "^0.1.0"
-
-extend-shallow@^3.0.0, extend-shallow@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8"
- dependencies:
- assign-symbols "^1.0.0"
- is-extendable "^1.0.1"
-
-extend@3, extend@^3.0.0, extend@~3.0.0:
+extend@3.0.2, extend@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
-
-external-editor@^3.0.3:
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27"
- dependencies:
- chardet "^0.7.0"
- iconv-lite "^0.4.24"
- tmp "^0.0.33"
-
-extglob@^2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
- dependencies:
- array-unique "^0.3.2"
- define-property "^1.0.0"
- expand-brackets "^2.1.4"
- extend-shallow "^2.0.1"
- fragment-cache "^0.2.1"
- regex-not "^1.0.0"
- snapdragon "^0.8.1"
- to-regex "^3.0.1"
+ integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
extract-zip@^2.0.1:
version "2.0.1"
@@ -4505,43 +4640,26 @@ extract-zip@^2.0.1:
optionalDependencies:
"@types/yauzl" "^2.9.1"
-fast-deep-equal@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
-
-fast-deep-equal@^3.1.1:
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
-fast-glob@^3.1.1:
- version "3.2.4"
- resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3"
- integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==
- dependencies:
- "@nodelib/fs.stat" "^2.0.2"
- "@nodelib/fs.walk" "^1.2.3"
- glob-parent "^5.1.0"
- merge2 "^1.3.0"
- micromatch "^4.0.2"
- picomatch "^2.2.1"
-
-fast-glob@^3.2.4:
- version "3.2.5"
- resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661"
- integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==
+fast-glob@^3.2.11, fast-glob@^3.2.9:
+ version "3.2.11"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
+ integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
- glob-parent "^5.1.0"
+ glob-parent "^5.1.2"
merge2 "^1.3.0"
- micromatch "^4.0.2"
- picomatch "^2.2.1"
+ micromatch "^4.0.4"
-fast-glob@^3.2.5, fast-glob@^3.2.9:
- version "3.2.11"
- resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
- integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
+fast-glob@^3.2.12:
+ version "3.2.12"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
+ integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
@@ -4553,18 +4671,15 @@ fast-json-stable-stringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
-fast-levenshtein@~2.0.4:
+fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
+ integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
-fastest-levenshtein@^1.0.12:
- version "1.0.12"
- resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2"
- integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==
-
-fastparse@^1.1.1:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9"
+fastest-levenshtein@^1.0.16:
+ version "1.0.16"
+ resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
+ integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==
fastq@^1.6.0:
version "1.8.0"
@@ -4580,22 +4695,6 @@ fd-slicer@~1.1.0:
dependencies:
pend "~1.2.0"
-figgy-pudding@^3.5.1:
- version "3.5.1"
- resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
-
-figures@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
- dependencies:
- escape-string-regexp "^1.0.5"
-
-file-entry-cache@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c"
- dependencies:
- flat-cache "^2.0.1"
-
file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@@ -4603,26 +4702,12 @@ file-entry-cache@^6.0.1:
dependencies:
flat-cache "^3.0.4"
-file-loader@3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-3.0.1.tgz#f8e0ba0b599918b51adfe45d66d1e771ad560faa"
- integrity sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw==
- dependencies:
- loader-utils "^1.0.2"
- schema-utils "^1.0.0"
-
-file-uri-to-path@1:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
-
-fill-range@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
+filelist@^1.0.1:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5"
+ integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==
dependencies:
- extend-shallow "^2.0.1"
- is-number "^3.0.0"
- repeat-string "^1.6.1"
- to-regex-range "^2.1.0"
+ minimatch "^5.0.1"
fill-range@^7.0.1:
version "7.0.1"
@@ -4657,15 +4742,7 @@ finalhandler@1.2.0:
statuses "2.0.1"
unpipe "~1.0.0"
-find-cache-dir@^0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9"
- dependencies:
- commondir "^1.0.1"
- mkdirp "^0.5.1"
- pkg-dir "^1.0.0"
-
-find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
+find-cache-dir@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
dependencies:
@@ -4673,27 +4750,21 @@ find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
make-dir "^2.0.0"
pkg-dir "^3.0.0"
-find-cache-dir@^3.3.1:
- version "3.3.1"
- resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880"
- integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==
- dependencies:
- commondir "^1.0.1"
- make-dir "^3.0.2"
- pkg-dir "^4.1.0"
-
-find-up@^1.0.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
+find-cache-dir@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-4.0.0.tgz#a30ee0448f81a3990708f6453633c733e2f6eec2"
+ integrity sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==
dependencies:
- path-exists "^2.0.0"
- pinkie-promise "^2.0.0"
+ common-path-prefix "^3.0.0"
+ pkg-dir "^7.0.0"
-find-up@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
+find-up@5.0.0, find-up@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
dependencies:
- locate-path "^2.0.0"
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
find-up@^3.0.0:
version "3.0.0"
@@ -4701,7 +4772,7 @@ find-up@^3.0.0:
dependencies:
locate-path "^3.0.0"
-find-up@^4.0.0, find-up@^4.1.0:
+find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
@@ -4709,13 +4780,13 @@ find-up@^4.0.0, find-up@^4.1.0:
locate-path "^5.0.0"
path-exists "^4.0.0"
-flat-cache@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
+find-up@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-6.3.0.tgz#2abab3d3280b2dc7ac10199ef324c4e002c8c790"
+ integrity sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==
dependencies:
- flatted "^2.0.0"
- rimraf "2.6.3"
- write "1.0.3"
+ locate-path "^7.1.0"
+ path-exists "^5.0.0"
flat-cache@^3.0.4:
version "3.0.4"
@@ -4725,9 +4796,10 @@ flat-cache@^3.0.4:
flatted "^3.1.0"
rimraf "^3.0.2"
-flatted@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916"
+flat@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
+ integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
flatted@^3.1.0:
version "3.2.6"
@@ -4743,56 +4815,47 @@ flatten@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
-flush-write-stream@^1.0.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
- dependencies:
- inherits "^2.0.3"
- readable-stream "^2.3.6"
-
follow-redirects@^1.0.0:
version "1.6.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.6.1.tgz#514973c44b5757368bad8bddfe52f81f015c94cb"
dependencies:
debug "=3.1.0"
-follow-redirects@^1.14.0:
- version "1.14.9"
- resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
- integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
+follow-redirects@^1.15.0:
+ version "1.15.2"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
+ integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
-for-in@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
+for-each@^0.3.3:
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
+ integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==
+ dependencies:
+ is-callable "^1.1.3"
-formatio@1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb"
+form-data@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
+ integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
- samsam "1.x"
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ mime-types "^2.1.12"
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
-fragment-cache@^0.2.1:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
- dependencies:
- map-cache "^0.2.2"
+fraction.js@^4.3.7:
+ version "4.3.7"
+ resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
+ integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
-from2@^2.1.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
- dependencies:
- inherits "^2.0.1"
- readable-stream "^2.0.0"
-
fs-extra@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.1.tgz#27de43b4320e833f6867cc044bfce29fdf0ef3b8"
@@ -4802,71 +4865,42 @@ fs-extra@^10.0.1:
jsonfile "^6.0.1"
universalify "^2.0.0"
-fs-minipass@^1.2.5:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d"
- dependencies:
- minipass "^2.2.1"
-
-fs-minipass@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
- integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
- dependencies:
- minipass "^3.0.0"
-
-fs-write-stream-atomic@^1.0.8:
- version "1.0.10"
- resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
+fs-extra@^10.1.0:
+ version "10.1.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
+ integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
dependencies:
- graceful-fs "^4.1.2"
- iferr "^0.1.5"
- imurmurhash "^0.1.4"
- readable-stream "1 || 2"
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
-fsevents@^1.2.7:
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f"
- dependencies:
- nan "^2.12.1"
- node-pre-gyp "^0.12.0"
-
-fsevents@~2.3.1, fsevents@~2.3.2:
+fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
-ftp@~0.3.10:
- version "0.3.10"
- resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d"
- dependencies:
- readable-stream "1.1.x"
- xregexp "2.0.0"
-
function-bind@1.1.1, function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
-functional-red-black-tree@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
-
-gauge@~2.7.3:
- version "2.7.4"
- resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
+function.prototype.name@^1.1.5:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621"
+ integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==
dependencies:
- aproba "^1.0.3"
- console-control-strings "^1.0.0"
- has-unicode "^2.0.0"
- object-assign "^4.1.0"
- signal-exit "^3.0.0"
- string-width "^1.0.1"
- strip-ansi "^3.0.1"
- wide-align "^1.1.0"
+ call-bind "^1.0.2"
+ define-properties "^1.1.3"
+ es-abstract "^1.19.0"
+ functions-have-names "^1.2.2"
+
+functions-have-names@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
+ integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
@@ -4878,6 +4912,11 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+get-func-name@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
+ integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==
+
get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
@@ -4887,95 +4926,58 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
has "^1.0.3"
has-symbols "^1.0.1"
-get-stdin@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
-
-get-stdin@^8.0.0:
- version "8.0.0"
- resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53"
- integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==
+get-intrinsic@^1.1.3:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f"
+ integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==
+ dependencies:
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-symbols "^1.0.3"
get-stream@^5.1.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
dependencies:
- pump "^3.0.0"
-
-get-symbol-description@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6"
- integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==
- dependencies:
- call-bind "^1.0.2"
- get-intrinsic "^1.1.1"
-
-get-uri@2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.2.tgz#5c795e71326f6ca1286f2fc82575cd2bab2af578"
- dependencies:
- data-uri-to-buffer "1"
- debug "2"
- extend "3"
- file-uri-to-path "1"
- ftp "~0.3.10"
- readable-stream "2"
-
-get-value@^2.0.3, get-value@^2.0.6:
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
-
-glob-parent@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
- dependencies:
- is-glob "^3.1.0"
- path-dirname "^1.0.0"
+ pump "^3.0.0"
-glob-parent@^5.1.0:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
- integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
+get-symbol-description@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6"
+ integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==
dependencies:
- is-glob "^4.0.1"
+ call-bind "^1.0.2"
+ get-intrinsic "^1.1.1"
-glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2:
+glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
-glob@7.0.5:
- version "7.0.5"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.5.tgz#b4202a69099bbb4d292a7c1b95b6682b67ebdc95"
+glob-parent@^6.0.1, glob-parent@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
+ integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
dependencies:
- fs.realpath "^1.0.0"
- inflight "^1.0.4"
- inherits "2"
- minimatch "^3.0.2"
- once "^1.3.0"
- path-is-absolute "^1.0.0"
+ is-glob "^4.0.3"
-glob@7.1.1:
- version "7.1.1"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
- dependencies:
- fs.realpath "^1.0.0"
- inflight "^1.0.4"
- inherits "2"
- minimatch "^3.0.2"
- once "^1.3.0"
- path-is-absolute "^1.0.0"
+glob-to-regexp@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
+ integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
-glob@^5.0.15:
- version "5.0.15"
- resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
+glob@7.2.0, glob@^7.1.7:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
+ integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
dependencies:
+ fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
- minimatch "2 || 3"
+ minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
@@ -4990,40 +4992,28 @@ glob@^7.0.0, glob@^7.1.1, glob@^7.1.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
-glob@^7.1.2:
- version "7.1.4"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
- dependencies:
- fs.realpath "^1.0.0"
- inflight "^1.0.4"
- inherits "2"
- minimatch "^3.0.4"
- once "^1.3.0"
- path-is-absolute "^1.0.0"
-
-glob@^7.1.4:
- version "7.1.6"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
- integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
+glob@^7.2.3:
+ version "7.2.3"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
+ integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
- minimatch "^3.0.4"
+ minimatch "^3.1.1"
once "^1.3.0"
path-is-absolute "^1.0.0"
-glob@^7.1.7:
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
- integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
+glob@^8.0.3:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
+ integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
- minimatch "^3.0.4"
+ minimatch "^5.0.1"
once "^1.3.0"
- path-is-absolute "^1.0.0"
global-modules@^2.0.0:
version "2.0.0"
@@ -5041,27 +5031,25 @@ global-prefix@^3.0.0:
kind-of "^6.0.2"
which "^1.3.1"
-globals@^11.1.0, globals@^11.7.0:
+globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
-globals@^9.18.0:
- version "9.18.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
+globals@^13.19.0:
+ version "13.19.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-13.19.0.tgz#7a42de8e6ad4f7242fbcca27ea5b23aca367b5c8"
+ integrity sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==
+ dependencies:
+ type-fest "^0.20.2"
-globby@^11.0.1:
- version "11.0.1"
- resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357"
- integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==
+globalthis@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf"
+ integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==
dependencies:
- array-union "^2.1.0"
- dir-glob "^3.0.1"
- fast-glob "^3.1.1"
- ignore "^5.1.4"
- merge2 "^1.3.0"
- slash "^3.0.0"
+ define-properties "^1.1.3"
-globby@^11.0.3:
+globby@^11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
@@ -5073,19 +5061,30 @@ globby@^11.0.3:
merge2 "^1.4.1"
slash "^3.0.0"
+globby@^13.1.1:
+ version "13.1.2"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.2.tgz#29047105582427ab6eca4f905200667b056da515"
+ integrity sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==
+ dependencies:
+ dir-glob "^3.0.1"
+ fast-glob "^3.2.11"
+ ignore "^5.2.0"
+ merge2 "^1.4.1"
+ slash "^4.0.0"
+
globjoin@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43"
integrity sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM=
-gonzales-pe@^4.3.0:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3"
- integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==
+gopd@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
+ integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
dependencies:
- minimist "^1.2.5"
+ get-intrinsic "^1.1.3"
-graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
+graceful-fs@^4.1.2:
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
@@ -5099,23 +5098,20 @@ graceful-fs@^4.2.4:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
-"graceful-readlink@>= 1.0.0":
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
+graceful-fs@^4.2.9:
+ version "4.2.10"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
+ integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
-growl@1.9.2:
- version "1.9.2"
- resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f"
+grapheme-splitter@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
+ integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
-handlebars@^4.0.1:
- version "4.0.12"
- resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.12.tgz#2c15c8a96d46da5e266700518ba8cb8d919d5bc5"
- dependencies:
- async "^2.5.0"
- optimist "^0.6.1"
- source-map "^0.6.1"
- optionalDependencies:
- uglify-js "^3.1.4"
+growl@1.10.5:
+ version "1.10.5"
+ resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
+ integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
hard-rejection@^2.1.0:
version "2.1.0"
@@ -5133,13 +5129,10 @@ has-bigints@^1.0.1:
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
-has-color@~0.1.0:
- version "0.1.7"
- resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f"
-
-has-flag@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
+has-bigints@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
+ integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
has-flag@^3.0.0:
version "3.0.0"
@@ -5150,11 +5143,23 @@ has-flag@^4.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+has-property-descriptors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861"
+ integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==
+ dependencies:
+ get-intrinsic "^1.1.1"
+
+has-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
+ integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
+
has-symbols@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
-has-symbols@^1.0.1, has-symbols@^1.0.2:
+has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
@@ -5166,50 +5171,12 @@ has-tostringtag@^1.0.0:
dependencies:
has-symbols "^1.0.2"
-has-unicode@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
-
-has-value@^0.3.1:
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
- dependencies:
- get-value "^2.0.3"
- has-values "^0.1.4"
- isobject "^2.0.0"
-
-has-value@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177"
- dependencies:
- get-value "^2.0.6"
- has-values "^1.0.0"
- isobject "^3.0.0"
-
-has-values@^0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771"
-
-has-values@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f"
- dependencies:
- is-number "^3.0.0"
- kind-of "^4.0.0"
-
-has@^1.0.1, has@^1.0.3:
+has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
dependencies:
function-bind "^1.1.1"
-hash-base@^3.0.0:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918"
- dependencies:
- inherits "^2.0.1"
- safe-buffer "^5.0.1"
-
hash-sum@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04"
@@ -5219,37 +5186,11 @@ hash-sum@^2.0.0:
resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-2.0.0.tgz#81d01bb5de8ea4a214ad5d6ead1b523460b0b45a"
integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==
-hash.js@^1.0.0, hash.js@^1.0.3:
- version "1.1.7"
- resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
- dependencies:
- inherits "^2.0.3"
- minimalistic-assert "^1.0.1"
-
-he@1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
-
-he@1.2.x, he@^1.1.0:
+he@1.2.0, he@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
-hmac-drbg@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
- dependencies:
- hash.js "^1.0.3"
- minimalistic-assert "^1.0.0"
- minimalistic-crypto-utils "^1.0.1"
-
-home-or-tmp@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
- dependencies:
- os-homedir "^1.0.0"
- os-tmpdir "^1.0.1"
-
hosted-git-info@^2.1.4:
version "2.7.1"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047"
@@ -5261,74 +5202,76 @@ hosted-git-info@^4.0.1:
dependencies:
lru-cache "^6.0.0"
-html-comment-regex@^1.1.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7"
+html-encoding-sniffer@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9"
+ integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==
+ dependencies:
+ whatwg-encoding "^2.0.0"
html-entities@^2.1.0:
version "2.3.3"
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46"
integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==
-html-minifier@^3.2.3:
- version "3.5.21"
- resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.21.tgz#d0040e054730e354db008463593194015212d20c"
- dependencies:
- camel-case "3.0.x"
- clean-css "4.2.x"
- commander "2.17.x"
- he "1.2.x"
- param-case "2.1.x"
- relateurl "0.2.x"
- uglify-js "3.4.x"
-
-html-tags@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140"
- integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==
+html-escaper@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
+ integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
-html-webpack-plugin@3.2.0:
+html-minifier-terser@^6.0.2:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab"
+ integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==
+ dependencies:
+ camel-case "^4.1.2"
+ clean-css "^5.2.2"
+ commander "^8.3.0"
+ he "^1.2.0"
+ param-case "^3.0.4"
+ relateurl "^0.2.7"
+ terser "^5.10.0"
+
+html-tags@^3.2.0:
version "3.2.0"
- resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b"
- integrity sha1-sBq71yOsqqeze2r0SS69oD2d03s=
- dependencies:
- html-minifier "^3.2.3"
- loader-utils "^0.2.16"
- lodash "^4.17.3"
- pretty-error "^2.0.2"
- tapable "^1.0.0"
- toposort "^1.0.0"
- util.promisify "1.0.0"
-
-htmlparser2@^3.10.0:
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
- integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
+ resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.2.0.tgz#dbb3518d20b726524e4dd43de397eb0a95726961"
+ integrity sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==
+
+html-tags@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce"
+ integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==
+
+html-webpack-plugin@5.5.1:
+ version "5.5.1"
+ resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.1.tgz#826838e31b427f5f7f30971f8d8fa2422dfa6763"
+ integrity sha512-cTUzZ1+NqjGEKjmVgZKLMdiFg3m9MdRXkZW2OEe69WYVi5ONLMmlnSZdXzGGMOq0C8jGDrL6EWyEDDUioHO/pA==
dependencies:
- domelementtype "^1.3.1"
- domhandler "^2.3.0"
- domutils "^1.5.1"
- entities "^1.1.1"
- inherits "^2.0.1"
- readable-stream "^3.1.1"
+ "@types/html-minifier-terser" "^6.0.0"
+ html-minifier-terser "^6.0.2"
+ lodash "^4.17.21"
+ pretty-error "^4.0.0"
+ tapable "^2.0.0"
-htmlparser2@~3.3.0:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe"
+htmlparser2@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
+ integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
dependencies:
- domelementtype "1"
- domhandler "2.1"
- domutils "1.1"
- readable-stream "1.0"
+ domelementtype "^2.0.1"
+ domhandler "^4.0.0"
+ domutils "^2.5.2"
+ entities "^2.0.0"
-http-errors@1.6.3:
- version "1.6.3"
- resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
+htmlparser2@^8.0.0:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.1.tgz#abaa985474fcefe269bc761a779b544d7196d010"
+ integrity sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==
dependencies:
- depd "~1.1.2"
- inherits "2.0.3"
- setprototypeof "1.1.0"
- statuses ">= 1.4.0 < 2"
+ domelementtype "^2.3.0"
+ domhandler "^5.0.2"
+ domutils "^3.0.1"
+ entities "^4.3.0"
http-errors@1.8.1:
version "1.8.1"
@@ -5352,26 +5295,27 @@ http-errors@2.0.0:
statuses "2.0.1"
toidentifier "1.0.1"
-http-proxy-agent@1:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-1.0.0.tgz#cc1ce38e453bf984a0f7702d2dd59c73d081284a"
+http-proxy-agent@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
+ integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==
dependencies:
- agent-base "2"
- debug "2"
- extend "3"
+ "@tootallnate/once" "2"
+ agent-base "6"
+ debug "4"
-http-proxy-middleware@0.21.0:
- version "0.21.0"
- resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.21.0.tgz#c6b1ca05174b5fbc57bee9485ffa0fa2f0dabeb0"
- integrity sha512-4Arcl5QQ6pRMRJmtM1WVHKHkFAQn5uvw83XuNeqnMTOikDiCoTxv5/vdudhKQsF+1mtaAawrK2SEB1v2tYecdQ==
+http-proxy-middleware@2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f"
+ integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==
dependencies:
- "@types/http-proxy" "^1.17.3"
- http-proxy "^1.18.0"
+ "@types/http-proxy" "^1.17.8"
+ http-proxy "^1.18.1"
is-glob "^4.0.1"
- lodash "^4.17.15"
+ is-plain-obj "^3.0.0"
micromatch "^4.0.2"
-http-proxy@^1.18.0, http-proxy@^1.18.1:
+http-proxy@^1.18.1:
version "1.18.1"
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
@@ -5380,18 +5324,6 @@ http-proxy@^1.18.0, http-proxy@^1.18.1:
follow-redirects "^1.0.0"
requires-port "^1.0.0"
-https-browserify@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
-
-https-proxy-agent@1:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz#35f7da6c48ce4ddbfa264891ac593ee5ff8671e6"
- dependencies:
- agent-base "2"
- debug "2"
- extend "3"
-
https-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
@@ -5400,56 +5332,47 @@ https-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
-iconv-lite@0.4.23, iconv-lite@^0.4.4:
- version "0.4.23"
- resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
+https-proxy-agent@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
+ integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
dependencies:
- safer-buffer ">= 2.1.2 < 3"
+ agent-base "6"
+ debug "4"
-iconv-lite@0.4.24, iconv-lite@^0.4.24:
+iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
dependencies:
safer-buffer ">= 2.1.2 < 3"
-icss-replace-symbols@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
-
-icss-utils@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-2.1.0.tgz#83f0a0ec378bf3246178b6c2ad9136f135b1c962"
- dependencies:
- postcss "^6.0.1"
-
-ieee754@^1.1.4:
- version "1.1.12"
- resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
-
-iferr@^0.1.5:
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
-
-ignore-walk@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8"
+iconv-lite@0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
+ integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
dependencies:
- minimatch "^3.0.4"
+ safer-buffer ">= 2.1.2 < 3.0.0"
-ignore@^4.0.2, ignore@^4.0.6:
- version "4.0.6"
- resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
+icss-utils@^5.0.0, icss-utils@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
+ integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
-ignore@^5.1.4, ignore@^5.1.8:
- version "5.1.8"
- resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
- integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
+ieee754@^1.1.13:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
+ integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
-ignore@^5.2.0:
+ignore@^5.1.1, ignore@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
+ignore@^5.2.1:
+ version "5.2.4"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
+ integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
+
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
@@ -5459,21 +5382,6 @@ immutable@^4.0.0:
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef"
integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==
-import-cwd@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
- integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=
- dependencies:
- import-from "^2.1.0"
-
-import-fresh@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
- integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY=
- dependencies:
- caller-path "^2.0.0"
- resolve-from "^3.0.0"
-
import-fresh@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.0.0.tgz#a3d897f420cab0e671236897f75bc14b4885c390"
@@ -5489,13 +5397,6 @@ import-fresh@^3.2.1:
parent-module "^1.0.0"
resolve-from "^4.0.0"
-import-from@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1"
- integrity sha1-M1238qev/VOqpHHUuAId7ja387E=
- dependencies:
- resolve-from "^3.0.0"
-
import-lazy@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153"
@@ -5505,12 +5406,6 @@ imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
-indent-string@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
- dependencies:
- repeating "^2.0.0"
-
indent-string@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
@@ -5520,11 +5415,6 @@ indexes-of@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
-infer-owner@^1.0.3, infer-owner@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
- integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==
-
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@@ -5532,48 +5422,24 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@^2.0.3, inherits@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
-inherits@2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
-
-inherits@2.0.4:
+inherits@2.0.4, inherits@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
-ini@^1.3.5, ini@~1.3.0:
+ini@^1.3.4:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
+ integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
+
+ini@^1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
-inject-loader@2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/inject-loader/-/inject-loader-2.0.1.tgz#1a7b45d60a81610459ac76079c3ce2a654d0dfc7"
- integrity sha1-GntF1gqBYQRZrHYHnDziplTQ38c=
- dependencies:
- loader-utils "^0.2.3"
-
-inquirer@^6.2.2:
- version "6.3.1"
- resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.3.1.tgz#7a413b5e7950811013a3db491c61d1f3b776e8e7"
- dependencies:
- ansi-escapes "^3.2.0"
- chalk "^2.4.2"
- cli-cursor "^2.1.0"
- cli-width "^2.0.0"
- external-editor "^3.0.3"
- figures "^2.0.0"
- lodash "^4.17.11"
- mute-stream "0.0.7"
- run-async "^2.2.0"
- rxjs "^6.4.0"
- string-width "^2.1.0"
- strip-ansi "^5.1.0"
- through "^2.3.6"
-
internal-slot@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
@@ -5583,61 +5449,36 @@ internal-slot@^1.0.3:
has "^1.0.3"
side-channel "^1.0.4"
+internal-slot@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.4.tgz#8551e7baf74a7a6ba5f749cfb16aa60722f0d6f3"
+ integrity sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==
+ dependencies:
+ get-intrinsic "^1.1.3"
+ has "^1.0.3"
+ side-channel "^1.0.4"
+
interpret@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
-invariant@^2.2.2:
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
- dependencies:
- loose-envify "^1.0.0"
-
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
-ip@1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/ip/-/ip-1.0.1.tgz#c7e356cdea225ae71b36d70f2e71a92ba4e42590"
-
-ip@^1.1.4:
- version "1.1.5"
- resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
-
ipaddr.js@1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
-is-absolute-url@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
-
-is-accessor-descriptor@^0.1.6:
- version "0.1.6"
- resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"
- dependencies:
- kind-of "^3.0.2"
-
-is-accessor-descriptor@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656"
- dependencies:
- kind-of "^6.0.0"
-
-is-alphabetical@^1.0.0:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d"
- integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==
-
-is-alphanumerical@^1.0.0:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf"
- integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==
+is-array-buffer@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.1.tgz#deb1db4fcae48308d54ef2442706c0393997052a"
+ integrity sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==
dependencies:
- is-alphabetical "^1.0.0"
- is-decimal "^1.0.0"
+ call-bind "^1.0.2"
+ get-intrinsic "^1.1.3"
+ is-typed-array "^1.1.10"
is-arrayish@^0.2.1:
version "0.2.1"
@@ -5650,12 +5491,6 @@ is-bigint@^1.0.1:
dependencies:
has-bigints "^1.0.1"
-is-binary-path@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
- dependencies:
- binary-extensions "^1.0.0"
-
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
@@ -5671,20 +5506,10 @@ is-boolean-object@^1.1.0:
call-bind "^1.0.2"
has-tostringtag "^1.0.0"
-is-buffer@^1.1.5:
- version "1.1.6"
- resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
-
-is-buffer@^2.0.0:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623"
- integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==
-
-is-builtin-module@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
- dependencies:
- builtin-modules "^1.0.0"
+is-callable@^1.1.3, is-callable@^1.2.7:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
+ integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
is-callable@^1.1.4:
version "1.1.4"
@@ -5695,6 +5520,13 @@ is-callable@^1.2.4:
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945"
integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==
+is-core-module@^2.11.0:
+ version "2.11.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
+ integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
+ dependencies:
+ has "^1.0.3"
+
is-core-module@^2.5.0, is-core-module@^2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
@@ -5709,93 +5541,24 @@ is-core-module@^2.8.1:
dependencies:
has "^1.0.3"
-is-data-descriptor@^0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
- dependencies:
- kind-of "^3.0.2"
-
-is-data-descriptor@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7"
- dependencies:
- kind-of "^6.0.0"
-
is-date-object@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
-is-decimal@^1.0.0:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5"
- integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==
-
-is-descriptor@^0.1.0:
- version "0.1.6"
- resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
- dependencies:
- is-accessor-descriptor "^0.1.6"
- is-data-descriptor "^0.1.4"
- kind-of "^5.0.0"
-
-is-descriptor@^1.0.0, is-descriptor@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec"
- dependencies:
- is-accessor-descriptor "^1.0.0"
- is-data-descriptor "^1.0.0"
- kind-of "^6.0.2"
-
-is-directory@^0.3.1:
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
-
-is-docker@^2.0.0:
+is-docker@^2.0.0, is-docker@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
-is-extendable@^0.1.0, is-extendable@^0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
-
-is-extendable@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4"
- dependencies:
- is-plain-object "^2.0.4"
-
-is-extglob@^2.1.0, is-extglob@^2.1.1:
+is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
-is-finite@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
- dependencies:
- number-is-nan "^1.0.0"
-
-is-fullwidth-code-point@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
- dependencies:
- number-is-nan "^1.0.0"
-
-is-fullwidth-code-point@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
-
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
-is-glob@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
- dependencies:
- is-extglob "^2.1.0"
-
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
@@ -5809,12 +5572,12 @@ is-glob@^4.0.3:
dependencies:
is-extglob "^2.1.1"
-is-hexadecimal@^1.0.0:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7"
- integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==
+is-interactive@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
+ integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
-is-negative-zero@^2.0.1:
+is-negative-zero@^2.0.1, is-negative-zero@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150"
integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==
@@ -5826,51 +5589,45 @@ is-number-object@^1.0.4:
dependencies:
has-tostringtag "^1.0.0"
-is-number@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
- dependencies:
- kind-of "^3.0.2"
-
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
-is-path-cwd@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb"
- integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==
-
-is-path-inside@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017"
- integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==
+is-path-inside@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
+ integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
-is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
+is-plain-obj@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
-is-plain-obj@^2.0.0:
+is-plain-obj@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
-is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
+is-plain-obj@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7"
+ integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==
+
+is-plain-object@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
dependencies:
isobject "^3.0.1"
-is-promise@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
+is-plain-object@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
+ integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
-is-regex@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
- dependencies:
- has "^1.0.1"
+is-potential-custom-element-name@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
+ integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
is-regex@^1.1.4:
version "1.1.4"
@@ -5880,16 +5637,18 @@ is-regex@^1.1.4:
call-bind "^1.0.2"
has-tostringtag "^1.0.0"
-is-regexp@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-2.1.0.tgz#cd734a56864e23b956bf4e7c66c396a4c0b22c2d"
- integrity sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==
-
is-shared-array-buffer@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6"
integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==
+is-shared-array-buffer@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79"
+ integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==
+ dependencies:
+ call-bind "^1.0.2"
+
is-string@^1.0.5, is-string@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd"
@@ -5897,12 +5656,6 @@ is-string@^1.0.5, is-string@^1.0.7:
dependencies:
has-tostringtag "^1.0.0"
-is-svg@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9"
- dependencies:
- html-comment-regex "^1.1.0"
-
is-symbol@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38"
@@ -5916,9 +5669,16 @@ is-symbol@^1.0.3:
dependencies:
has-symbols "^1.0.2"
-is-typedarray@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+is-typed-array@^1.1.10, is-typed-array@^1.1.9:
+ version "1.1.10"
+ resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f"
+ integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==
+ dependencies:
+ available-typed-arrays "^1.0.5"
+ call-bind "^1.0.2"
+ for-each "^0.3.3"
+ gopd "^1.0.1"
+ has-tostringtag "^1.0.0"
is-unicode-supported@^0.1.0:
version "0.1.0"
@@ -5929,26 +5689,19 @@ is-url@^1.2.2:
version "1.2.4"
resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
-is-utf8@^0.2.0:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
-
-is-weakref@^1.0.1:
+is-weakref@^1.0.1, is-weakref@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2"
integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==
dependencies:
call-bind "^1.0.2"
-is-windows@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
-
is-wsl@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
+ integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==
-is-wsl@^2.1.0:
+is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
@@ -5967,7 +5720,7 @@ isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
-isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
+isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -5985,104 +5738,170 @@ iso-639-1@2.1.15:
resolved "https://registry.yarnpkg.com/iso-639-1/-/iso-639-1-2.1.15.tgz#20cf78a4f691aeb802c16f17a6bad7d99271e85d"
integrity sha512-7c7mBznZu2ktfvyT582E2msM+Udc1EjOyhVRE/0ZsjD9LBtWSm23h3PtiRh2a35XoUsTQQjJXaJzuLjXsOdFDg==
-isobject@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
- dependencies:
- isarray "1.0.0"
-
-isobject@^3.0.0, isobject@^3.0.1:
+isobject@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
-isparta-loader@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/isparta-loader/-/isparta-loader-2.0.0.tgz#4425f496c93f765bbceb4dd938576da307566ed1"
- integrity sha1-RCX0lsk/dlu8603ZOFdtowdWbtE=
+istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
+ integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==
+
+istanbul-lib-instrument@^5.1.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f"
+ integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==
dependencies:
- isparta "4.x.x"
+ "@babel/core" "^7.12.3"
+ "@babel/parser" "^7.14.7"
+ "@istanbuljs/schema" "^0.1.2"
+ istanbul-lib-coverage "^3.2.0"
+ semver "^6.3.0"
-isparta@4.x.x:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/isparta/-/isparta-4.1.1.tgz#c92e49672946914ec5407c801160f3374e0b7cb4"
- dependencies:
- babel-core "^6.1.4"
- escodegen "^1.6.1"
- esprima "^4.0.0"
- istanbul "0.4.5"
- mkdirp "^0.5.0"
- nomnomnomnom "^2.0.0"
- object-assign "^4.0.1"
- source-map "^0.5.0"
- which "^1.0.9"
-
-istanbul@0.4.5, istanbul@^0.4.0:
- version "0.4.5"
- resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b"
- dependencies:
- abbrev "1.0.x"
- async "1.x"
- escodegen "1.8.x"
- esprima "2.7.x"
- glob "^5.0.15"
- handlebars "^4.0.1"
- js-yaml "3.x"
- mkdirp "0.5.x"
- nopt "3.x"
- once "1.x"
- resolve "1.1.x"
- supports-color "^3.1.0"
- which "^1.1.1"
- wordwrap "^1.0.0"
-
-js-base64@^2.1.9:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.0.tgz#42255ba183ab67ce59a0dee640afdc00ab5ae93e"
+istanbul-lib-report@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6"
+ integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==
+ dependencies:
+ istanbul-lib-coverage "^3.0.0"
+ make-dir "^3.0.0"
+ supports-color "^7.1.0"
-js-cookie@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414"
- integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==
+istanbul-lib-source-maps@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551"
+ integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==
+ dependencies:
+ debug "^4.1.1"
+ istanbul-lib-coverage "^3.0.0"
+ source-map "^0.6.1"
-"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
+istanbul-reports@^3.0.5:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae"
+ integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==
+ dependencies:
+ html-escaper "^2.0.0"
+ istanbul-lib-report "^3.0.0"
-js-tokens@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+jake@^10.8.5:
+ version "10.8.5"
+ resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
+ integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==
+ dependencies:
+ async "^3.2.3"
+ chalk "^4.0.2"
+ filelist "^1.0.1"
+ minimatch "^3.0.4"
+
+jest-util@^29.2.1:
+ version "29.2.1"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.2.1.tgz#f26872ba0dc8cbefaba32c34f98935f6cf5fc747"
+ integrity sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==
+ dependencies:
+ "@jest/types" "^29.2.1"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ ci-info "^3.2.0"
+ graceful-fs "^4.2.9"
+ picomatch "^2.2.3"
-js-yaml@3.x:
- version "3.12.1"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600"
+jest-worker@^27.4.5:
+ version "27.5.1"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0"
+ integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==
dependencies:
- argparse "^1.0.7"
- esprima "^4.0.0"
+ "@types/node" "*"
+ merge-stream "^2.0.0"
+ supports-color "^8.0.0"
+
+jest-worker@^28.0.2, jest-worker@^28.1.0:
+ version "28.1.3"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.3.tgz#7e3c4ce3fa23d1bb6accb169e7f396f98ed4bb98"
+ integrity sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==
+ dependencies:
+ "@types/node" "*"
+ merge-stream "^2.0.0"
+ supports-color "^8.0.0"
-js-yaml@^3.13.0, js-yaml@^3.13.1:
- version "3.13.1"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
+jest-worker@^29.1.2:
+ version "29.2.1"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.2.1.tgz#8ba68255438252e1674f990f0180c54dfa26a3b1"
+ integrity sha512-ROHTZ+oj7sBrgtv46zZ84uWky71AoYi0vEV9CdEtc1FQunsoAGe5HbQmW76nI5QWdvECVPrSi1MCVUmizSavMg==
dependencies:
- argparse "^1.0.7"
- esprima "^4.0.0"
+ "@types/node" "*"
+ jest-util "^29.2.1"
+ merge-stream "^2.0.0"
+ supports-color "^8.0.0"
+
+js-beautify@1.14.6:
+ version "1.14.6"
+ resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.14.6.tgz#b23ca5d74a462c282c7711bb51150bcc97f2b507"
+ integrity sha512-GfofQY5zDp+cuHc+gsEXKPpNw2KbPddreEo35O6jT6i0RVK6LhsoYBhq5TvK4/n74wnA0QbK8gGd+jUZwTMKJw==
+ dependencies:
+ config-chain "^1.1.13"
+ editorconfig "^0.15.3"
+ glob "^8.0.3"
+ nopt "^6.0.0"
+
+js-cookie@3.0.5:
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc"
+ integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==
+
+js-sdsl@^4.1.4:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.1.5.tgz#1ff1645e6b4d1b028cd3f862db88c9d887f26e2a"
+ integrity sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==
+
+js-tokens@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+
+js-tokens@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-8.0.0.tgz#5dbe2cdfa9afc93251d3a77bf18c3ad6fa8a4de4"
+ integrity sha512-PC7MzqInq9OqKyTXfIvQNcjMkODJYC8A17kAaQgeW79yfhqTWSOfjHYQ2mDDcwJ96Iibtwkfh0C7R/OvqPlgVA==
-js-yaml@^4.1.0:
+js-yaml@4.1.0, js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
dependencies:
argparse "^2.0.1"
-js-yaml@~3.7.0:
- version "3.7.0"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80"
- dependencies:
- argparse "^1.0.7"
- esprima "^2.6.0"
-
-jsesc@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
+jsdom@19.0.0:
+ version "19.0.0"
+ resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-19.0.0.tgz#93e67c149fe26816d38a849ea30ac93677e16b6a"
+ integrity sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==
+ dependencies:
+ abab "^2.0.5"
+ acorn "^8.5.0"
+ acorn-globals "^6.0.0"
+ cssom "^0.5.0"
+ cssstyle "^2.3.0"
+ data-urls "^3.0.1"
+ decimal.js "^10.3.1"
+ domexception "^4.0.0"
+ escodegen "^2.0.0"
+ form-data "^4.0.0"
+ html-encoding-sniffer "^3.0.0"
+ http-proxy-agent "^5.0.0"
+ https-proxy-agent "^5.0.0"
+ is-potential-custom-element-name "^1.0.1"
+ nwsapi "^2.2.0"
+ parse5 "6.0.1"
+ saxes "^5.0.1"
+ symbol-tree "^3.2.4"
+ tough-cookie "^4.0.0"
+ w3c-hr-time "^1.0.2"
+ w3c-xmlserializer "^3.0.0"
+ webidl-conversions "^7.0.0"
+ whatwg-encoding "^2.0.0"
+ whatwg-mimetype "^3.0.0"
+ whatwg-url "^10.0.0"
+ ws "^8.2.3"
+ xml-name-validator "^4.0.0"
jsesc@^2.5.1:
version "2.5.2"
@@ -6098,10 +5917,15 @@ json-loader@0.5.7:
resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d"
integrity sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==
-json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
+json-parse-better-errors@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
+json-parse-even-better-errors@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
+ integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@@ -6115,14 +5939,6 @@ json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
-json3@3.3.2:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
-
-json5@^0.5.0, json5@^0.5.1:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
-
json5@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
@@ -6141,6 +5957,11 @@ json5@^2.2.0, json5@^2.2.1:
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
+json5@^2.2.2:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
+ integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
+
jsonc-eslint-parser@^1.0.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsonc-eslint-parser/-/jsonc-eslint-parser-1.4.1.tgz#8cbe99f6f5199acbc5a823c4c0b6135411027fa6"
@@ -6161,23 +5982,40 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
-karma-coverage@1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/karma-coverage/-/karma-coverage-1.1.2.tgz#cc09dceb589a83101aca5fe70c287645ef387689"
- integrity sha512-eQawj4Cl3z/CjxslYy9ariU4uDh7cCNFZHNWXWRpl0pNeblY/4wHR7M7boTYXWrn9bY0z2pZmr11eKje/S/hIw==
+jszip@^3.10.0:
+ version "3.10.1"
+ resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
+ integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
dependencies:
- dateformat "^1.0.6"
- istanbul "^0.4.0"
- lodash "^4.17.0"
- minimatch "^3.0.0"
- source-map "^0.5.1"
+ lie "~3.3.0"
+ pako "~1.0.2"
+ readable-stream "~2.3.6"
+ setimmediate "^1.0.5"
-karma-firefox-launcher@1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/karma-firefox-launcher/-/karma-firefox-launcher-1.3.0.tgz#ebcbb1d1ddfada6be900eb8fae25bcf2dcdc8171"
- integrity sha512-Fi7xPhwrRgr+94BnHX0F5dCl1miIW4RHnzjIGxF8GaIEp7rNqX7LSi7ok63VXs3PS/5MQaQMhGxw+bvD+pibBQ==
+just-extend@^4.0.2:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744"
+ integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==
+
+karma-coverage@2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/karma-coverage/-/karma-coverage-2.2.0.tgz#64f838b66b71327802e7f6f6c39d569b7024e40c"
+ integrity sha512-gPVdoZBNDZ08UCzdMHHhEImKrw1+PAOQOIiffv1YsvxFhBjqvo/SVXNk4tqn1SYqX0BJZT6S/59zgxiBe+9OuA==
+ dependencies:
+ istanbul-lib-coverage "^3.2.0"
+ istanbul-lib-instrument "^5.1.0"
+ istanbul-lib-report "^3.0.0"
+ istanbul-lib-source-maps "^4.0.1"
+ istanbul-reports "^3.0.5"
+ minimatch "^3.0.4"
+
+karma-firefox-launcher@2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/karma-firefox-launcher/-/karma-firefox-launcher-2.1.3.tgz#b278a4cbffa92ab81394b1a398813847b0624a85"
+ integrity sha512-LMM2bseebLbYjODBOVt7TCPP9OI2vZIXCavIXhkO9m+10Uj5l7u/SKoeRmYx8FYHTVGZSpk6peX+3BMHC1WwNw==
dependencies:
- is-wsl "^2.1.0"
+ is-wsl "^2.2.0"
+ which "^3.0.0"
karma-mocha-reporter@2.2.5:
version "2.2.5"
@@ -6207,29 +6045,26 @@ karma-sourcemap-loader@0.3.8:
dependencies:
graceful-fs "^4.1.2"
-karma-spec-reporter@0.0.34:
- version "0.0.34"
- resolved "https://registry.yarnpkg.com/karma-spec-reporter/-/karma-spec-reporter-0.0.34.tgz#7dc79cdc76b0e37f17006921439600ae3c648669"
- integrity sha512-l5H/Nh9q4g2Ysx2CDU2m+NIPyLQpCVbk9c4V02BTZHw3NM6RO1dq3eRpKXCSSdPt4RGfhHk8jDt3XYkGp+5PWg==
+karma-spec-reporter@0.0.36:
+ version "0.0.36"
+ resolved "https://registry.yarnpkg.com/karma-spec-reporter/-/karma-spec-reporter-0.0.36.tgz#c54dc155dec2ded1f92ea68dbbdd67fcedbef350"
+ integrity sha512-11bvOl1x6ryKZph7kmbmMpbi8vsngEGxGOoeTlIcDaH3ab3j8aPJnZ+r+K/SS0sBSGy5VGkGYO2+hLct7hw/6w==
dependencies:
colors "1.4.0"
-karma-webpack@4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-4.0.2.tgz#23219bd95bdda853e3073d3874d34447c77bced0"
- integrity sha512-970/okAsdUOmiMOCY8sb17A2I8neS25Ad9uhyK3GHgmRSIFJbDcNEFE8dqqUhNe9OHiCC9k3DMrSmtd/0ymP1A==
+karma-webpack@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-5.0.0.tgz#2a2c7b80163fe7ffd1010f83f5507f95ef39f840"
+ integrity sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA==
dependencies:
- clone-deep "^4.0.1"
- loader-utils "^1.1.0"
- neo-async "^2.6.1"
- schema-utils "^1.0.0"
- source-map "^0.7.3"
- webpack-dev-middleware "^3.7.0"
+ glob "^7.1.3"
+ minimatch "^3.0.4"
+ webpack-merge "^4.1.5"
-karma@6.4.0:
- version "6.4.0"
- resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.0.tgz#82652dfecdd853ec227b74ed718a997028a99508"
- integrity sha512-s8m7z0IF5g/bS5ONT7wsOavhW4i4aFkzD4u4wgzAQWT4HGUeWI3i21cK2Yz6jndMAeHETp5XuNsRoyGJZXVd4w==
+karma@6.4.4:
+ version "6.4.4"
+ resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.4.tgz#dfa5a426cf5a8b53b43cd54ef0d0d09742351492"
+ integrity sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==
dependencies:
"@colors/colors" "1.5.0"
body-parser "^1.19.0"
@@ -6250,29 +6085,13 @@ karma@6.4.0:
qjobs "^1.2.0"
range-parser "^1.2.1"
rimraf "^3.0.2"
- socket.io "^4.4.1"
+ socket.io "^4.7.2"
source-map "^0.6.1"
tmp "^0.2.1"
ua-parser-js "^0.7.30"
yargs "^16.1.1"
-kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
- version "3.2.2"
- resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
- dependencies:
- is-buffer "^1.1.5"
-
-kind-of@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57"
- dependencies:
- is-buffer "^1.1.5"
-
-kind-of@^5.0.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
-
-kind-of@^6.0.0, kind-of@^6.0.2:
+kind-of@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
@@ -6281,14 +6100,33 @@ kind-of@^6.0.3:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
-known-css-properties@^0.21.0:
- version "0.21.0"
- resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.21.0.tgz#15fbd0bbb83447f3ce09d8af247ed47c68ede80d"
- integrity sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw==
+klona@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc"
+ integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==
+
+klona@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22"
+ integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==
+
+known-css-properties@^0.26.0:
+ version "0.26.0"
+ resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.26.0.tgz#008295115abddc045a9f4ed7e2a84dc8b3a77649"
+ integrity sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==
+
+levn@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
+ integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+ dependencies:
+ prelude-ls "^1.2.1"
+ type-check "~0.4.0"
-levn@^0.3.0, levn@~0.3.0:
+levn@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
+ integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==
dependencies:
prelude-ls "~1.1.2"
type-check "~0.3.2"
@@ -6299,43 +6137,29 @@ lie@3.1.1:
dependencies:
immediate "~3.0.5"
+lie@~3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
+ integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
+ dependencies:
+ immediate "~3.0.5"
+
+lilconfig@^2.0.3:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4"
+ integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==
+
lines-and-columns@^1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
-load-json-file@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
- dependencies:
- graceful-fs "^4.1.2"
- parse-json "^2.2.0"
- pify "^2.0.0"
- pinkie-promise "^2.0.0"
- strip-bom "^2.0.0"
-
-loader-fs-cache@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.1.tgz#56e0bf08bd9708b26a765b68509840c8dec9fdbc"
- dependencies:
- find-cache-dir "^0.1.1"
- mkdirp "0.5.1"
-
-loader-runner@^2.4.0:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
- integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
-
-loader-utils@^0.2.16, loader-utils@^0.2.3:
- version "0.2.17"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348"
- dependencies:
- big.js "^3.1.3"
- emojis-list "^2.0.0"
- json5 "^0.5.0"
- object-assign "^4.0.1"
+loader-runner@^4.2.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1"
+ integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
-loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0:
+loader-utils@^1.0.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
dependencies:
@@ -6343,15 +6167,6 @@ loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0:
emojis-list "^2.0.0"
json5 "^1.0.1"
-loader-utils@^1.2.3:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
- integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
- dependencies:
- big.js "^5.2.2"
- emojis-list "^3.0.0"
- json5 "^1.0.1"
-
loader-utils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
@@ -6368,13 +6183,6 @@ localforage@1.10.0:
dependencies:
lie "3.1.1"
-locate-path@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
- dependencies:
- p-locate "^2.0.0"
- path-exists "^3.0.0"
-
locate-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
@@ -6389,6 +6197,20 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
+locate-path@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-7.2.0.tgz#69cb1779bd90b35ab1e771e1f2f89a202c2a8a8a"
+ integrity sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==
+ dependencies:
+ p-locate "^6.0.0"
+
lodash._arraycopy@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz#76e7b7c1f1fb92547374878a562ed06a3e50f6e1"
@@ -6424,18 +6246,10 @@ lodash._baseclone@^3.0.0:
lodash.isarray "^3.0.0"
lodash.keys "^3.0.0"
-lodash._baseclone@^4.0.0:
- version "4.5.7"
- resolved "https://registry.yarnpkg.com/lodash._baseclone/-/lodash._baseclone-4.5.7.tgz#ce42ade08384ef5d62fa77c30f61a46e686f8434"
-
lodash._basecopy@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
-lodash._basecreate@^3.0.0:
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821"
-
lodash._baseeach@^3.0.0:
version "3.0.4"
resolved "https://registry.yarnpkg.com/lodash._baseeach/-/lodash._baseeach-3.0.4.tgz#cf8706572ca144e8d9d75227c990da982f932af3"
@@ -6482,14 +6296,6 @@ lodash._isiterateecall@^3.0.0:
version "3.0.9"
resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c"
-lodash._stack@^4.0.0:
- version "4.1.3"
- resolved "https://registry.yarnpkg.com/lodash._stack/-/lodash._stack-4.1.3.tgz#751aa76c1b964b047e76d14fc72a093fcb5e2dd0"
-
-lodash.camelcase@^4.3.0:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
-
lodash.clone@3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-3.0.3.tgz#84688c73d32b5a90ca25616963f189252a997043"
@@ -6498,29 +6304,20 @@ lodash.clone@3.0.3:
lodash._bindcallback "^3.0.0"
lodash._isiterateecall "^3.0.0"
-lodash.create@3.1.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7"
- dependencies:
- lodash._baseassign "^3.0.0"
- lodash._basecreate "^3.0.0"
- lodash._isiterateecall "^3.0.0"
-
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
-lodash.defaultsdeep@4.3.2:
- version "4.3.2"
- resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.3.2.tgz#6c1a586e6c5647b0e64e2d798141b8836158be8a"
- dependencies:
- lodash._baseclone "^4.0.0"
- lodash._stack "^4.0.0"
- lodash.isplainobject "^4.0.0"
- lodash.keysin "^4.0.0"
- lodash.mergewith "^4.0.0"
- lodash.rest "^4.0.0"
+lodash.defaultsdeep@4.6.1:
+ version "4.6.1"
+ resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6"
+ integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==
+
+lodash.escape@4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
+ integrity sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==
lodash.find@^3.2.1:
version "3.2.1"
@@ -6533,6 +6330,11 @@ lodash.find@^3.2.1:
lodash.isarray "^3.0.0"
lodash.keys "^3.0.0"
+lodash.get@^4.4.2:
+ version "4.4.2"
+ resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
+ integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
+
lodash.isarguments@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
@@ -6558,10 +6360,6 @@ lodash.isplainobject@^3.0.0, lodash.isplainobject@^3.2.0:
lodash.isarguments "^3.0.0"
lodash.keysin "^3.0.0"
-lodash.isplainobject@^4.0.0:
- version "4.0.6"
- resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
-
lodash.istypedarray@^3.0.0:
version "3.0.6"
resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62"
@@ -6581,13 +6379,15 @@ lodash.keysin@^3.0.0:
lodash.isarguments "^3.0.0"
lodash.isarray "^3.0.0"
-lodash.keysin@^4.0.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/lodash.keysin/-/lodash.keysin-4.2.0.tgz#8cc3fb35c2d94acc443a1863e02fa40799ea6f28"
-
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
+ integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==
+
+lodash.merge@4.6.2, lodash.merge@^4.6.2:
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
+ integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash.merge@^3.3.2:
version "3.3.2"
@@ -6605,19 +6405,16 @@ lodash.merge@^3.3.2:
lodash.keysin "^3.0.0"
lodash.toplainobject "^3.0.0"
-lodash.mergewith@^4.0.0:
- version "4.6.1"
- resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
-
lodash.pairs@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash.pairs/-/lodash.pairs-3.0.1.tgz#bbe08d5786eeeaa09a15c91ebf0dcb7d2be326a9"
dependencies:
lodash.keys "^3.0.0"
-lodash.rest@^4.0.0:
- version "4.0.5"
- resolved "https://registry.yarnpkg.com/lodash.rest/-/lodash.rest-4.0.5.tgz#954ef75049262038c96d1fc98b28fdaf9f0772aa"
+lodash.pick@4.4.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
+ integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==
lodash.restparam@^3.0.0:
version "3.6.1"
@@ -6638,25 +6435,24 @@ lodash.truncate@^4.4.2:
lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
+ integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
-lodash@4.17.21, lodash@^4.17.20, lodash@^4.17.21:
+lodash@4.17.21, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
-lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.3, lodash@^4.17.4:
+lodash@^4.17.10:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
-lodash@^4.17.13:
- version "4.17.15"
- resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
- integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
-
-lodash@^4.17.15:
- version "4.17.19"
- resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
- integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
+log-symbols@4.1.0, log-symbols@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
+ integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
+ dependencies:
+ chalk "^4.1.0"
+ is-unicode-supported "^0.1.0"
log-symbols@^1.0.2:
version "1.0.2"
@@ -6670,14 +6466,6 @@ log-symbols@^2.1.0:
dependencies:
chalk "^2.0.1"
-log-symbols@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
- integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
- dependencies:
- chalk "^4.1.0"
- is-unicode-supported "^0.1.0"
-
log4js@^6.4.1:
version "6.4.4"
resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.4.4.tgz#c9bc75569f3f40bba22fe1bd0677afa7a6a13bac"
@@ -6689,36 +6477,24 @@ log4js@^6.4.1:
rfdc "^1.3.0"
streamroller "^3.0.6"
-lolex@1.6.0, lolex@^1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6"
- integrity sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=
-
-longest-streak@^2.0.0:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4"
- integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==
-
-loose-envify@^1.0.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+loupe@2.3.4, loupe@^2.3.1:
+ version "2.3.4"
+ resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3"
+ integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==
dependencies:
- js-tokens "^3.0.0 || ^4.0.0"
+ get-func-name "^2.0.0"
-loud-rejection@^1.0.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
+lower-case@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
+ integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
dependencies:
- currently-unhandled "^0.4.1"
- signal-exit "^3.0.0"
-
-lower-case@^1.1.1:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
+ tslib "^2.0.3"
-lru-cache@^4.0.1:
+lru-cache@^4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
+ integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
dependencies:
pseudomap "^1.0.2"
yallist "^2.1.2"
@@ -6726,6 +6502,7 @@ lru-cache@^4.0.1:
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
+ integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
dependencies:
yallist "^3.0.2"
@@ -6736,10 +6513,6 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
-lru-cache@~2.6.5:
- version "2.6.5"
- resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5"
-
magic-string@^0.25.7:
version "0.25.9"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
@@ -6747,6 +6520,13 @@ magic-string@^0.25.7:
dependencies:
sourcemap-codec "^1.4.8"
+magic-string@^0.30.5:
+ version "0.30.5"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9"
+ integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==
+ dependencies:
+ "@jridgewell/sourcemap-codec" "^1.4.15"
+
make-dir@^2.0.0, make-dir@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
@@ -6754,18 +6534,14 @@ make-dir@^2.0.0, make-dir@^2.1.0:
pify "^4.0.1"
semver "^5.6.0"
-make-dir@^3.0.2, make-dir@^3.1.0:
+make-dir@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
dependencies:
semver "^6.0.0"
-map-cache@^0.2.2:
- version "0.2.2"
- resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
-
-map-obj@^1.0.0, map-obj@^1.0.1:
+map-obj@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
@@ -6774,56 +6550,15 @@ map-obj@^4.0.0:
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.1.0.tgz#b91221b542734b9f14256c0132c897c5d7256fd5"
integrity sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==
-map-visit@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
- dependencies:
- object-visit "^1.0.0"
-
-math-expression-evaluator@^1.2.14:
- version "1.2.17"
- resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
-
mathml-tag-names@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
-md5.js@^1.3.4:
- version "1.3.5"
- resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
- dependencies:
- hash-base "^3.0.0"
- inherits "^2.0.1"
- safe-buffer "^5.1.2"
-
-mdast-util-from-markdown@^0.8.0:
- version "0.8.5"
- resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz#d1ef2ca42bc377ecb0463a987910dae89bd9a28c"
- integrity sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==
- dependencies:
- "@types/mdast" "^3.0.0"
- mdast-util-to-string "^2.0.0"
- micromark "~2.11.0"
- parse-entities "^2.0.0"
- unist-util-stringify-position "^2.0.0"
-
-mdast-util-to-markdown@^0.6.0:
- version "0.6.5"
- resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz#b33f67ca820d69e6cc527a93d4039249b504bebe"
- integrity sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==
- dependencies:
- "@types/unist" "^2.0.0"
- longest-streak "^2.0.0"
- mdast-util-to-string "^2.0.0"
- parse-entities "^2.0.0"
- repeat-string "^1.0.0"
- zwitch "^1.0.0"
-
-mdast-util-to-string@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b"
- integrity sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==
+mdn-data@2.0.14:
+ version "2.0.14"
+ resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
+ integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
media-typer@0.3.0:
version "0.3.0"
@@ -6836,29 +6571,6 @@ memory-fs@^0.4.1:
errno "^0.1.3"
readable-stream "^2.0.1"
-memory-fs@^0.5.0:
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c"
- integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==
- dependencies:
- errno "^0.1.3"
- readable-stream "^2.0.1"
-
-meow@^3.3.0:
- version "3.7.0"
- resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
- dependencies:
- camelcase-keys "^2.0.0"
- decamelize "^1.1.2"
- loud-rejection "^1.0.0"
- map-obj "^1.0.1"
- minimist "^1.1.3"
- normalize-package-data "^2.3.4"
- object-assign "^4.0.1"
- read-pkg-up "^1.0.1"
- redent "^1.0.0"
- trim-newlines "^1.0.0"
-
meow@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364"
@@ -6881,6 +6593,11 @@ merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+merge-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
+ integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+
merge2@^1.3.0, merge2@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
@@ -6890,32 +6607,6 @@ methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
-micromark@~2.11.0:
- version "2.11.4"
- resolved "https://registry.yarnpkg.com/micromark/-/micromark-2.11.4.tgz#d13436138eea826383e822449c9a5c50ee44665a"
- integrity sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==
- dependencies:
- debug "^4.0.0"
- parse-entities "^2.0.0"
-
-micromatch@^3.1.10, micromatch@^3.1.4:
- version "3.1.10"
- resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
- dependencies:
- arr-diff "^4.0.0"
- array-unique "^0.3.2"
- braces "^2.3.1"
- define-property "^2.0.2"
- extend-shallow "^3.0.2"
- extglob "^2.0.4"
- fragment-cache "^0.2.1"
- kind-of "^6.0.2"
- nanomatch "^1.2.9"
- object.pick "^1.3.0"
- regex-not "^1.0.0"
- snapdragon "^0.8.1"
- to-regex "^3.0.2"
-
micromatch@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
@@ -6924,7 +6615,7 @@ micromatch@^4.0.2:
braces "^3.0.1"
picomatch "^2.0.5"
-micromatch@^4.0.4:
+micromatch@^4.0.4, micromatch@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
@@ -6932,13 +6623,6 @@ micromatch@^4.0.4:
braces "^3.0.2"
picomatch "^2.3.1"
-miller-rabin@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
- dependencies:
- bn.js "^4.0.0"
- brorand "^1.0.1"
-
mime-db@1.40.0:
version "1.40.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
@@ -6948,28 +6632,24 @@ mime-db@1.52.0:
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
-mime-types@~2.1.24:
- version "2.1.24"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
- dependencies:
- mime-db "1.40.0"
-
-mime-types@~2.1.34:
+mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.34:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
+mime-types@~2.1.24:
+ version "2.1.24"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
+ dependencies:
+ mime-db "1.40.0"
+
mime@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
-mime@^2.0.3:
- version "2.4.3"
- resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.3.tgz#229687331e86f68924e6cb59e1cdd937f18275fe"
-
mime@^2.4.4, mime@^2.5.2:
version "2.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
@@ -6979,48 +6659,57 @@ mimic-fn@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
+mimic-fn@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
+ integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+
min-indent@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
-mini-css-extract-plugin@0.12.0:
- version "0.12.0"
- resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.12.0.tgz#ddeb74fd6304ca9f99c1db74acc7d5b507705454"
- integrity sha512-z6PQCe9rd1XUwZ8gMaEVwwRyZlrYy8Ba1gRjFP5HcV51HkXX+XlwZ+a1iAYTjSYwgNBXoNR7mhx79mDpOn5fdw==
+mini-css-extract-plugin@2.7.6:
+ version "2.7.6"
+ resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz#282a3d38863fddcd2e0c220aaed5b90bc156564d"
+ integrity sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==
dependencies:
- loader-utils "^1.1.0"
- normalize-url "1.9.1"
- schema-utils "^1.0.0"
- webpack-sources "^1.1.0"
-
-minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
+ schema-utils "^4.0.0"
-minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
+minimatch@3.1.2, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ dependencies:
+ brace-expansion "^1.1.7"
-"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+minimatch@4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4"
+ integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==
dependencies:
brace-expansion "^1.1.7"
-minimatch@3.0.3:
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774"
+minimatch@5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
+ integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
dependencies:
- brace-expansion "^1.0.0"
+ brace-expansion "^2.0.1"
-minimatch@^3.1.2:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
- integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+minimatch@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
dependencies:
brace-expansion "^1.1.7"
+minimatch@^5.0.1:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7"
+ integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==
+ dependencies:
+ brace-expansion "^2.0.1"
+
minimist-options@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619"
@@ -7030,165 +6719,98 @@ minimist-options@4.1.0:
is-plain-obj "^1.1.0"
kind-of "^6.0.3"
-minimist@0.0.8, minimist@~0.0.1:
+minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
-minimist@^1.1.3, minimist@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
-
-minimist@^1.2.3, minimist@^1.2.6:
+minimist@1.2.6, minimist@^1.2.3, minimist@^1.2.6:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
+minimist@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
+
minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
-minipass-collect@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617"
- integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==
- dependencies:
- minipass "^3.0.0"
-
-minipass-flush@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373"
- integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==
- dependencies:
- minipass "^3.0.0"
-
-minipass-pipeline@^1.2.2:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c"
- integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==
- dependencies:
- minipass "^3.0.0"
-
-minipass@^2.2.1, minipass@^2.3.4:
- version "2.3.5"
- resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
- dependencies:
- safe-buffer "^5.1.2"
- yallist "^3.0.0"
-
-minipass@^3.0.0, minipass@^3.1.1:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd"
- integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==
- dependencies:
- yallist "^4.0.0"
-
-minizlib@^1.1.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614"
- dependencies:
- minipass "^2.2.1"
-
-minizlib@^2.1.1:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
- integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
- dependencies:
- minipass "^3.0.0"
- yallist "^4.0.0"
-
-mississippi@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022"
- dependencies:
- concat-stream "^1.5.0"
- duplexify "^3.4.2"
- end-of-stream "^1.1.0"
- flush-write-stream "^1.0.0"
- from2 "^2.1.0"
- parallel-transform "^1.1.0"
- pump "^3.0.0"
- pumpify "^1.3.3"
- stream-each "^1.1.0"
- through2 "^2.0.0"
-
-mixin-deep@^1.2.0:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
- dependencies:
- for-in "^1.0.2"
- is-extendable "^1.0.1"
+mitt@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/mitt/-/mitt-2.1.0.tgz#f740577c23176c6205b121b2973514eade1b2230"
+ integrity sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==
-mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
+mkdirp@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
dependencies:
minimist "0.0.8"
-mkdirp@^0.5.3, mkdirp@^0.5.5:
+mkdirp@^0.5.5:
version "0.5.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
dependencies:
minimist "^1.2.5"
-mkdirp@^1.0.3, mkdirp@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
- integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
-
-mkpath@1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/mkpath/-/mkpath-1.0.0.tgz#ebb3a977e7af1c683ae6fda12b545a6ba6c5853d"
-
-mocha-nightwatch@3.2.2:
- version "3.2.2"
- resolved "https://registry.yarnpkg.com/mocha-nightwatch/-/mocha-nightwatch-3.2.2.tgz#91bcb9b3bde057dd7677c78125e491e58d66647c"
- dependencies:
- browser-stdout "1.3.0"
- commander "2.9.0"
- debug "2.2.0"
- diff "1.4.0"
- escape-string-regexp "1.0.5"
- glob "7.0.5"
- growl "1.9.2"
- json3 "3.3.2"
- lodash.create "3.1.1"
- mkdirp "0.5.1"
- supports-color "3.1.2"
-
-mocha@3.5.3:
- version "3.5.3"
- resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.5.3.tgz#1e0480fe36d2da5858d1eb6acc38418b26eaa20d"
- integrity sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==
- dependencies:
- browser-stdout "1.3.0"
- commander "2.9.0"
- debug "2.6.8"
- diff "3.2.0"
- escape-string-regexp "1.0.5"
- glob "7.1.1"
- growl "1.9.2"
- he "1.1.1"
- json3 "3.3.2"
- lodash.create "3.1.1"
- mkdirp "0.5.1"
- supports-color "3.1.2"
-
-move-concurrently@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
- dependencies:
- aproba "^1.1.1"
- copy-concurrently "^1.0.0"
- fs-write-stream-atomic "^1.0.8"
- mkdirp "^0.5.1"
- rimraf "^2.5.4"
- run-queue "^1.0.3"
-
-ms@0.7.1:
- version "0.7.1"
- resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
+mocha@10.2.0:
+ version "10.2.0"
+ resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8"
+ integrity sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==
+ dependencies:
+ ansi-colors "4.1.1"
+ browser-stdout "1.3.1"
+ chokidar "3.5.3"
+ debug "4.3.4"
+ diff "5.0.0"
+ escape-string-regexp "4.0.0"
+ find-up "5.0.0"
+ glob "7.2.0"
+ he "1.2.0"
+ js-yaml "4.1.0"
+ log-symbols "4.1.0"
+ minimatch "5.0.1"
+ ms "2.1.3"
+ nanoid "3.3.3"
+ serialize-javascript "6.0.0"
+ strip-json-comments "3.1.1"
+ supports-color "8.1.1"
+ workerpool "6.2.1"
+ yargs "16.2.0"
+ yargs-parser "20.2.4"
+ yargs-unparser "2.0.0"
+
+mocha@9.2.2:
+ version "9.2.2"
+ resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.2.tgz#d70db46bdb93ca57402c809333e5a84977a88fb9"
+ integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==
+ dependencies:
+ "@ungap/promise-all-settled" "1.1.2"
+ ansi-colors "4.1.1"
+ browser-stdout "1.3.1"
+ chokidar "3.5.3"
+ debug "4.3.3"
+ diff "5.0.0"
+ escape-string-regexp "4.0.0"
+ find-up "5.0.0"
+ glob "7.2.0"
+ growl "1.10.5"
+ he "1.2.0"
+ js-yaml "4.1.0"
+ log-symbols "4.1.0"
+ minimatch "4.2.1"
+ ms "2.1.3"
+ nanoid "3.3.1"
+ serialize-javascript "6.0.0"
+ strip-json-comments "3.1.1"
+ supports-color "8.1.1"
+ which "2.0.2"
+ workerpool "6.2.0"
+ yargs "16.2.0"
+ yargs-parser "20.2.4"
+ yargs-unparser "2.0.0"
ms@2.0.0:
version "2.0.0"
@@ -7208,51 +6830,35 @@ ms@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
-mute-stream@0.0.7:
- version "0.0.7"
- resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
-
-nan@^2.12.1:
- version "2.14.0"
- resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
-
-nanoid@^3.3.1:
+nanoid@3.3.1, nanoid@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
-nanomatch@^1.2.9:
- version "1.2.13"
- resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
- dependencies:
- arr-diff "^4.0.0"
- array-unique "^0.3.2"
- define-property "^2.0.2"
- extend-shallow "^3.0.2"
- fragment-cache "^0.2.1"
- is-windows "^1.0.2"
- kind-of "^6.0.2"
- object.pick "^1.3.0"
- regex-not "^1.0.0"
- snapdragon "^0.8.1"
- to-regex "^3.0.1"
+nanoid@3.3.3:
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
+ integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==
-native-promise-only@^0.8.1:
- version "0.8.1"
- resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11"
+nanoid@^3.3.4:
+ version "3.3.4"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
+ integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
+
+nanoid@^3.3.6:
+ version "3.3.6"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
+ integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
+
+nanoid@^3.3.7:
+ version "3.3.7"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
+ integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
-needle@^2.2.1:
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e"
- dependencies:
- debug "^2.1.2"
- iconv-lite "^0.4.4"
- sax "^1.2.4"
-
negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@@ -7262,132 +6868,91 @@ negotiator@0.6.3:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
-neo-async@^2.5.0:
- version "2.6.1"
- resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
-
-neo-async@^2.6.1:
+neo-async@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
-netmask@~1.0.4:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35"
-
-nice-try@^1.0.4:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
-
-nightwatch@0.9.21:
- version "0.9.21"
- resolved "https://registry.yarnpkg.com/nightwatch/-/nightwatch-0.9.21.tgz#9e794a7514b4fd5f46602d368e50515232ab9e90"
- integrity sha1-nnlKdRS0/V9GYC02jlBRUjKrnpA=
- dependencies:
- chai-nightwatch "~0.1.x"
- ejs "2.5.7"
+nightwatch-axe-verbose@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.1.0.tgz#3a03f70dd9b78739a5178ee096a24e0658a8ee53"
+ integrity sha512-j31VB0wdv/HXoQWWAJsvNc9UenXzXf1u/QsvExCUDuFOMR4GRg3963wlPIxd2ME47egXsnkXPd1dl8Ozdk7XHA==
+ dependencies:
+ axe-core "^4.6.1"
+
+nightwatch@2.6.25:
+ version "2.6.25"
+ resolved "https://registry.yarnpkg.com/nightwatch/-/nightwatch-2.6.25.tgz#99c2abdd8a7f1ce8be2882ac5458a776caf1b5ab"
+ integrity sha512-aYc5eA6M/iADdbKbD6dMHlhUsaJm/Y4/VOtSHSC23nimGTXMUKbe1Bb14Iz3/SNyz2joHOkpxaDIPIAZCSlOiQ==
+ dependencies:
+ "@nightwatch/chai" "5.0.2"
+ "@nightwatch/html-reporter-template" "0.2.1"
+ ansi-to-html "0.7.2"
+ assertion-error "1.1.0"
+ boxen "5.1.2"
+ chai-nightwatch "0.5.3"
+ ci-info "3.3.0"
+ cli-table3 "^0.6.3"
+ didyoumean "1.2.2"
+ dotenv "10.0.0"
+ ejs "3.1.8"
+ envinfo "7.8.1"
+ fs-extra "^10.1.0"
+ glob "^7.2.3"
+ jsdom "19.0.0"
lodash.clone "3.0.3"
- lodash.defaultsdeep "4.3.2"
- minimatch "3.0.3"
- mkpath "1.0.0"
- mocha-nightwatch "3.2.2"
- optimist "0.6.1"
- proxy-agent "2.0.0"
- q "1.4.1"
-
-no-case@^2.2.0:
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
+ lodash.defaultsdeep "4.6.1"
+ lodash.escape "4.0.1"
+ lodash.merge "4.6.2"
+ lodash.pick "4.4.0"
+ minimatch "3.1.2"
+ minimist "1.2.6"
+ mocha "9.2.2"
+ nightwatch-axe-verbose "^2.1.0"
+ open "8.4.0"
+ ora "5.4.1"
+ selenium-webdriver "4.6.1"
+ semver "7.3.5"
+ stacktrace-parser "0.1.10"
+ strip-ansi "6.0.1"
+ untildify "^4.0.0"
+ uuid "8.3.2"
+
+nise@^5.1.4:
+ version "5.1.4"
+ resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.4.tgz#491ce7e7307d4ec546f5a659b2efe94a18b4bbc0"
+ integrity sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==
dependencies:
- lower-case "^1.1.1"
+ "@sinonjs/commons" "^2.0.0"
+ "@sinonjs/fake-timers" "^10.0.2"
+ "@sinonjs/text-encoding" "^0.7.1"
+ just-extend "^4.0.2"
+ path-to-regexp "^1.7.0"
-node-libs-browser@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
- integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==
- dependencies:
- assert "^1.1.1"
- browserify-zlib "^0.2.0"
- buffer "^4.3.0"
- console-browserify "^1.1.0"
- constants-browserify "^1.0.0"
- crypto-browserify "^3.11.0"
- domain-browser "^1.1.1"
- events "^3.0.0"
- https-browserify "^1.0.0"
- os-browserify "^0.3.0"
- path-browserify "0.0.1"
- process "^0.11.10"
- punycode "^1.2.4"
- querystring-es3 "^0.2.0"
- readable-stream "^2.3.3"
- stream-browserify "^2.0.1"
- stream-http "^2.7.2"
- string_decoder "^1.0.0"
- timers-browserify "^2.0.4"
- tty-browserify "0.0.0"
- url "^0.11.0"
- util "^0.11.0"
- vm-browserify "^1.0.1"
-
-node-pre-gyp@^0.12.0:
- version "0.12.0"
- resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149"
- dependencies:
- detect-libc "^1.0.2"
- mkdirp "^0.5.1"
- needle "^2.2.1"
- nopt "^4.0.1"
- npm-packlist "^1.1.6"
- npmlog "^4.0.2"
- rc "^1.2.7"
- rimraf "^2.6.1"
- semver "^5.3.0"
- tar "^4"
-
-node-releases@^1.1.58:
- version "1.1.60"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084"
- integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==
-
-node-releases@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01"
- integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==
+no-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
+ integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
+ dependencies:
+ lower-case "^2.0.2"
+ tslib "^2.0.3"
+
+node-releases@^2.0.14:
+ version "2.0.14"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
+ integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==
node-releases@^2.0.5, node-releases@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==
-nomnomnomnom@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/nomnomnomnom/-/nomnomnomnom-2.0.1.tgz#b2239f031c8d04da67e32836e1e3199e12f7a8e2"
- dependencies:
- chalk "~0.4.0"
- underscore "~1.6.0"
-
-nopt@3.x:
- version "3.0.6"
- resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
- dependencies:
- abbrev "1"
-
-nopt@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
- dependencies:
- abbrev "1"
- osenv "^0.1.4"
-
-normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
+nopt@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d"
+ integrity sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==
dependencies:
- hosted-git-info "^2.1.4"
- is-builtin-module "^1.0.0"
- semver "2 || 3 || 4 || 5"
- validate-npm-package-license "^3.0.1"
+ abbrev "^1.0.0"
normalize-package-data@^2.5.0:
version "2.5.0"
@@ -7409,12 +6974,6 @@ normalize-package-data@^3.0.0:
semver "^7.3.4"
validate-npm-package-license "^3.0.1"
-normalize-path@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
- dependencies:
- remove-trailing-separator "^1.0.1"
-
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
@@ -7423,95 +6982,41 @@ normalize-range@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
-normalize-selector@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/normalize-selector/-/normalize-selector-0.2.0.tgz#d0b145eb691189c63a78d201dc4fdb1293ef0c03"
- integrity sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=
-
-normalize-url@1.9.1, normalize-url@^1.4.0:
- version "1.9.1"
- resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c"
- dependencies:
- object-assign "^4.0.1"
- prepend-http "^1.0.0"
- query-string "^4.1.0"
- sort-keys "^1.0.0"
-
-npm-bundled@^1.0.1:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979"
-
-npm-packlist@^1.1.6:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.2.0.tgz#55a60e793e272f00862c7089274439a4cc31fc7f"
- dependencies:
- ignore-walk "^3.0.1"
- npm-bundled "^1.0.1"
-
-npmlog@^4.0.2:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
- dependencies:
- are-we-there-yet "~1.1.2"
- console-control-strings "~1.1.0"
- gauge "~2.7.3"
- set-blocking "~2.0.0"
+normalize-url@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
+ integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
-nth-check@~1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
+nth-check@^2.0.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
+ integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==
dependencies:
- boolbase "~1.0.0"
-
-num2fraction@^1.2.2:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
+ boolbase "^1.0.0"
-number-is-nan@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
+nwsapi@^2.2.0:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0"
+ integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==
-object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
+object-assign@^4:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
-object-copy@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
- dependencies:
- copy-descriptor "^0.1.0"
- define-property "^0.2.5"
- kind-of "^3.0.3"
-
-object-hash@^1.1.4:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df"
-
object-inspect@^1.11.0, object-inspect@^1.9.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0"
integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==
-object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
+object-inspect@^1.12.2:
+ version "1.12.3"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9"
+ integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==
+
+object-keys@^1.0.12, object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
-object-visit@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
- dependencies:
- isobject "^3.0.0"
-
-object.assign@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
- integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
- dependencies:
- define-properties "^1.1.2"
- function-bind "^1.1.1"
- has-symbols "^1.0.0"
- object-keys "^1.0.11"
-
object.assign@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
@@ -7522,27 +7027,24 @@ object.assign@^4.1.2:
has-symbols "^1.0.1"
object-keys "^1.1.1"
-object.getownpropertydescriptors@^2.0.3:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16"
- dependencies:
- define-properties "^1.1.2"
- es-abstract "^1.5.1"
-
-object.pick@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
+object.assign@^4.1.4:
+ version "4.1.4"
+ resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f"
+ integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==
dependencies:
- isobject "^3.0.1"
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ has-symbols "^1.0.3"
+ object-keys "^1.1.1"
-object.values@^1.1.5:
- version "1.1.5"
- resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac"
- integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==
+object.values@^1.1.6:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d"
+ integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==
dependencies:
call-bind "^1.0.2"
- define-properties "^1.1.3"
- es-abstract "^1.19.1"
+ define-properties "^1.1.4"
+ es-abstract "^1.20.4"
on-finished@2.4.1:
version "2.4.1"
@@ -7557,7 +7059,7 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"
-once@1.x, once@^1.3.0, once@^1.3.1, once@^1.4.0:
+once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
dependencies:
@@ -7569,31 +7071,52 @@ onetime@^2.0.0:
dependencies:
mimic-fn "^1.0.0"
-opn@4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/opn/-/opn-4.0.2.tgz#7abc22e644dff63b0a96d5ab7f2790c0f01abc95"
- integrity sha1-erwi5kTf9jsKltWrfyeQwPAavJU=
+onetime@^5.1.0:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
+ integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
dependencies:
- object-assign "^4.0.1"
- pinkie-promise "^2.0.0"
+ mimic-fn "^2.1.0"
-optimist@0.6.1, optimist@^0.6.1:
- version "0.6.1"
- resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
+open@8.4.0:
+ version "8.4.0"
+ resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8"
+ integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==
+ dependencies:
+ define-lazy-prop "^2.0.0"
+ is-docker "^2.1.1"
+ is-wsl "^2.2.0"
+
+opn@5.5.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
+ integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==
dependencies:
- minimist "~0.0.1"
- wordwrap "~0.0.2"
+ is-wsl "^1.1.0"
-optionator@^0.8.1, optionator@^0.8.2:
- version "0.8.2"
- resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
+optionator@^0.8.1:
+ version "0.8.3"
+ resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
+ integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==
dependencies:
deep-is "~0.1.3"
- fast-levenshtein "~2.0.4"
+ fast-levenshtein "~2.0.6"
levn "~0.3.0"
prelude-ls "~1.1.2"
type-check "~0.3.2"
- wordwrap "~1.0.0"
+ word-wrap "~1.2.3"
+
+optionator@^0.9.1:
+ version "0.9.1"
+ resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
+ integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==
+ dependencies:
+ deep-is "^0.1.3"
+ fast-levenshtein "^2.0.6"
+ levn "^0.4.1"
+ prelude-ls "^1.2.1"
+ type-check "^0.4.0"
+ word-wrap "^1.2.3"
ora@0.4.1:
version "0.4.1"
@@ -7605,30 +7128,20 @@ ora@0.4.1:
cli-spinners "^1.0.0"
log-symbols "^1.0.2"
-os-browserify@^0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
-
-os-homedir@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
-
-os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
-
-osenv@^0.1.4:
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
- dependencies:
- os-homedir "^1.0.0"
- os-tmpdir "^1.0.0"
-
-p-limit@^1.1.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
+ora@5.4.1:
+ version "5.4.1"
+ resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18"
+ integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==
dependencies:
- p-try "^1.0.0"
+ bl "^4.1.0"
+ chalk "^4.1.0"
+ cli-cursor "^3.1.0"
+ cli-spinners "^2.5.0"
+ is-interactive "^1.0.0"
+ is-unicode-supported "^0.1.0"
+ log-symbols "^4.1.0"
+ strip-ansi "^6.0.0"
+ wcwidth "^1.0.1"
p-limit@^2.0.0:
version "2.1.0"
@@ -7650,11 +7163,12 @@ p-limit@^3.0.2:
dependencies:
yocto-queue "^0.1.0"
-p-locate@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
+p-limit@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-4.0.0.tgz#914af6544ed32bfa54670b061cafcbd04984b644"
+ integrity sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==
dependencies:
- p-limit "^1.1.0"
+ yocto-queue "^1.0.0"
p-locate@^3.0.0:
version "3.0.0"
@@ -7669,62 +7183,41 @@ p-locate@^4.1.0:
dependencies:
p-limit "^2.2.0"
-p-map@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
- integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
+p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
dependencies:
- aggregate-error "^3.0.0"
+ p-limit "^3.0.2"
-p-try@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
+p-locate@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-6.0.0.tgz#3da9a49d4934b901089dca3302fa65dc5a05c04f"
+ integrity sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==
+ dependencies:
+ p-limit "^4.0.0"
p-try@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1"
-pac-proxy-agent@1:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-1.1.0.tgz#34a385dfdf61d2f0ecace08858c745d3e791fd4d"
- dependencies:
- agent-base "2"
- debug "2"
- extend "3"
- get-uri "2"
- http-proxy-agent "1"
- https-proxy-agent "1"
- pac-resolver "~2.0.0"
- raw-body "2"
- socks-proxy-agent "2"
-
-pac-resolver@~2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-2.0.0.tgz#99b88d2f193fbdeefc1c9a529c1f3260ab5277cd"
- dependencies:
- co "~3.0.6"
- degenerator "~1.0.2"
- ip "1.0.1"
- netmask "~1.0.4"
- thunkify "~2.1.1"
-
-pako@~1.0.5:
- version "1.0.10"
- resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732"
+pako@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86"
+ integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
-parallel-transform@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"
- dependencies:
- cyclist "~0.2.2"
- inherits "^2.0.3"
- readable-stream "^2.1.5"
+pako@~1.0.2:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
+ integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
-param-case@2.1.x:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247"
+param-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
+ integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==
dependencies:
- no-case "^2.2.0"
+ dot-case "^3.0.4"
+ tslib "^2.0.3"
parent-module@^1.0.0:
version "1.0.1"
@@ -7732,43 +7225,6 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
-parse-asn1@^5.0.0:
- version "5.1.4"
- resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.4.tgz#37f6628f823fbdeb2273b4d540434a22f3ef1fcc"
- dependencies:
- asn1.js "^4.0.0"
- browserify-aes "^1.0.0"
- create-hash "^1.1.0"
- evp_bytestokey "^1.0.0"
- pbkdf2 "^3.0.3"
- safe-buffer "^5.1.1"
-
-parse-entities@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8"
- integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==
- dependencies:
- character-entities "^1.0.0"
- character-entities-legacy "^1.0.0"
- character-reference-invalid "^1.0.0"
- is-alphanumerical "^1.0.0"
- is-decimal "^1.0.0"
- is-hexadecimal "^1.0.0"
-
-parse-json@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
- dependencies:
- error-ex "^1.2.0"
-
-parse-json@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
- integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=
- dependencies:
- error-ex "^1.3.1"
- json-parse-better-errors "^1.0.1"
-
parse-json@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.1.tgz#7cfe35c1ccd641bce3981467e6c2ece61b3b3878"
@@ -7779,36 +7235,30 @@ parse-json@^5.0.0:
json-parse-better-errors "^1.0.1"
lines-and-columns "^1.1.6"
-parse-link-header@1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/parse-link-header/-/parse-link-header-1.0.1.tgz#bedfe0d2118aeb84be75e7b025419ec8a61140a7"
- integrity sha1-vt/g0hGK64S+deewJUGeyKYRQKc=
+parse-link-header@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/parse-link-header/-/parse-link-header-2.0.0.tgz#949353e284f8aa01f2ac857a98f692b57733f6b7"
+ integrity sha512-xjU87V0VyHZybn2RrCX5TIFGxTVZE6zqqZWMPlIKiSKuWh/X5WZdt+w1Ki1nXB+8L/KtL+nZ4iq+sfI6MrhhMw==
dependencies:
xtend "~4.0.1"
+parse5@6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
+ integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
+
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
-pascalcase@^0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
-
-path-browserify@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
- integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==
-
-path-dirname@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
-
-path-exists@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
+pascal-case@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb"
+ integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==
dependencies:
- pinkie-promise "^2.0.0"
+ no-case "^3.0.4"
+ tslib "^2.0.3"
path-exists@^3.0.0:
version "3.0.0"
@@ -7819,17 +7269,19 @@ path-exists@^4.0.0:
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
-path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
+path-exists@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-5.0.0.tgz#a6aad9489200b21fab31e49cf09277e5116fb9e7"
+ integrity sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==
+
+path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
-path-is-inside@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
-
-path-key@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
path-parse@^1.0.6:
version "1.0.6"
@@ -7850,42 +7302,24 @@ path-to-regexp@^1.7.0:
dependencies:
isarray "0.0.1"
-path-type@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
- dependencies:
- graceful-fs "^4.1.2"
- pify "^2.0.0"
- pinkie-promise "^2.0.0"
-
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
-pbkdf2@^3.0.3:
- version "3.0.17"
- resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6"
- dependencies:
- create-hash "^1.1.2"
- create-hmac "^1.1.4"
- ripemd160 "^2.0.1"
- safe-buffer "^5.0.1"
- sha.js "^2.4.8"
+pathval@1.1.1, pathval@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d"
+ integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
-phoenix@1.6.2:
- version "1.6.2"
- resolved "https://registry.yarnpkg.com/phoenix/-/phoenix-1.6.2.tgz#8d1d9f06e51cb893d08059e80488cd0de328e01a"
- integrity sha512-VjR27NETvrLSj8rI6DlpVAfo7pCYth/9+1OCoTof4LKEbq0141ze/tdxFHHZzVQSok3gqJUo2h/tqbxR3r8eyw==
-
-picocolors@^0.2.1:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f"
- integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==
+phoenix@1.7.7:
+ version "1.7.7"
+ resolved "https://registry.yarnpkg.com/phoenix/-/phoenix-1.7.7.tgz#829817ea65a83ef78a3a88e3e074125f502a034f"
+ integrity sha512-moAN6e4Z16x/x1nswUpnTR2v5gm7HsI7eluZ2YnYUUsBNzi3cY/5frmiJfXIEi877IQAafzTfp8hd6vEUMme+w==
picocolors@^1.0.0:
version "1.0.0"
@@ -7902,52 +7336,32 @@ picomatch@^2.0.5, picomatch@^2.2.1:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
-picomatch@^2.3.1:
+picomatch@^2.2.3, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
-pify@^2.0.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
-
pify@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
-pinkie-promise@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
- dependencies:
- pinkie "^2.0.0"
-
-pinkie@^2.0.0:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
-
pirates@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==
-pkg-dir@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
- dependencies:
- find-up "^1.0.0"
-
pkg-dir@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
dependencies:
find-up "^3.0.0"
-pkg-dir@^4.1.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
- integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
+pkg-dir@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-7.0.0.tgz#8f0c08d6df4476756c5ff29b3282d0bab7517d11"
+ integrity sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==
dependencies:
- find-up "^4.0.0"
+ find-up "^6.3.0"
pngjs@^5.0.0:
version "5.0.0"
@@ -7959,264 +7373,254 @@ pointer-tracker@^2.0.3:
resolved "https://registry.yarnpkg.com/pointer-tracker/-/pointer-tracker-2.4.0.tgz#78721c2d2201486db11ec1094377f03023b621b3"
integrity sha512-pWI2tpaM/XNtc9mUTv42Rmjf6mkHvE8LT5DDEq0G7baPNhxNM9E3CepubPplSoSLk9E5bwQrAMyDcPVmJyTW4g==
-posix-character-classes@^0.1.0:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
-
-postcss-calc@^5.2.0:
- version "5.3.1"
- resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e"
- dependencies:
- postcss "^5.0.2"
- postcss-message-helpers "^2.0.0"
- reduce-css-calc "^1.2.6"
-
-postcss-colormin@^2.1.8:
- version "2.2.2"
- resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b"
- dependencies:
- colormin "^1.0.5"
- postcss "^5.0.13"
- postcss-value-parser "^3.2.3"
-
-postcss-convert-values@^2.3.4:
- version "2.6.1"
- resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz#bbd8593c5c1fd2e3d1c322bb925dcae8dae4d62d"
- dependencies:
- postcss "^5.0.11"
- postcss-value-parser "^3.1.2"
-
-postcss-discard-comments@^2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d"
- dependencies:
- postcss "^5.0.14"
-
-postcss-discard-duplicates@^2.0.1:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz#b9abf27b88ac188158a5eb12abcae20263b91932"
- dependencies:
- postcss "^5.0.4"
-
-postcss-discard-empty@^2.0.1:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz#d2b4bd9d5ced5ebd8dcade7640c7d7cd7f4f92b5"
- dependencies:
- postcss "^5.0.14"
-
-postcss-discard-overridden@^0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz#8b1eaf554f686fb288cd874c55667b0aa3668d58"
- dependencies:
- postcss "^5.0.16"
-
-postcss-discard-unused@^2.2.1:
- version "2.2.3"
- resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz#bce30b2cc591ffc634322b5fb3464b6d934f4433"
- dependencies:
- postcss "^5.0.14"
- uniqs "^2.0.0"
-
-postcss-filter-plugins@^2.0.0:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz#82245fdf82337041645e477114d8e593aa18b8ec"
+postcss-calc@^8.2.3:
+ version "8.2.4"
+ resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5"
+ integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==
dependencies:
- postcss "^5.0.4"
+ postcss-selector-parser "^6.0.9"
+ postcss-value-parser "^4.2.0"
-postcss-html@^0.36.0:
- version "0.36.0"
- resolved "https://registry.yarnpkg.com/postcss-html/-/postcss-html-0.36.0.tgz#b40913f94eaacc2453fd30a1327ad6ee1f88b204"
- integrity sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==
+postcss-colormin@^5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.0.tgz#3cee9e5ca62b2c27e84fce63affc0cfb5901956a"
+ integrity sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==
dependencies:
- htmlparser2 "^3.10.0"
+ browserslist "^4.16.6"
+ caniuse-api "^3.0.0"
+ colord "^2.9.1"
+ postcss-value-parser "^4.2.0"
-postcss-less@^3.1.4:
- version "3.1.4"
- resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-3.1.4.tgz#369f58642b5928ef898ffbc1a6e93c958304c5ad"
- integrity sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==
+postcss-convert-values@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz#31586df4e184c2e8890e8b34a0b9355313f503ab"
+ integrity sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g==
dependencies:
- postcss "^7.0.14"
+ browserslist "^4.20.3"
+ postcss-value-parser "^4.2.0"
-postcss-load-config@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003"
- integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==
+postcss-discard-comments@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696"
+ integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==
+
+postcss-discard-duplicates@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848"
+ integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==
+
+postcss-discard-empty@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c"
+ integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==
+
+postcss-discard-overridden@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e"
+ integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==
+
+postcss-html@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/postcss-html/-/postcss-html-1.5.0.tgz#57a43bc9e336f516ecc448a37d2e8c2290170a6f"
+ integrity sha512-kCMRWJRHKicpA166kc2lAVUGxDZL324bkj/pVOb6RhjB0Z5Krl7mN0AsVkBhVIRZZirY0lyQXG38HCVaoKVNoA==
dependencies:
- cosmiconfig "^5.0.0"
- import-cwd "^2.0.0"
+ htmlparser2 "^8.0.0"
+ js-tokens "^8.0.0"
+ postcss "^8.4.0"
+ postcss-safe-parser "^6.0.0"
-postcss-loader@3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d"
- integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==
+postcss-loader@7.0.2:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.0.2.tgz#b53ff44a26fba3688eee92a048c7f2d4802e23bb"
+ integrity sha512-fUJzV/QH7NXUAqV8dWJ9Lg4aTkDCezpTS5HgJ2DvqznexTbSTxgi/dTECvTZ15BwKTtk8G/bqI/QTu2HPd3ZCg==
dependencies:
- loader-utils "^1.1.0"
- postcss "^7.0.0"
- postcss-load-config "^2.0.0"
- schema-utils "^1.0.0"
+ cosmiconfig "^7.0.0"
+ klona "^2.0.5"
+ semver "^7.3.8"
postcss-media-query-parser@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244"
integrity sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=
-postcss-merge-idents@^2.1.5:
- version "2.1.7"
- resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270"
+postcss-merge-longhand@^5.1.6:
+ version "5.1.6"
+ resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.6.tgz#f378a8a7e55766b7b644f48e5d8c789ed7ed51ce"
+ integrity sha512-6C/UGF/3T5OE2CEbOuX7iNO63dnvqhGZeUnKkDeifebY0XqkkvrctYSZurpNE902LDf2yKwwPFgotnfSoPhQiw==
dependencies:
- has "^1.0.1"
- postcss "^5.0.10"
- postcss-value-parser "^3.1.1"
+ postcss-value-parser "^4.2.0"
+ stylehacks "^5.1.0"
-postcss-merge-longhand@^2.0.1:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz#23d90cd127b0a77994915332739034a1a4f3d658"
+postcss-merge-rules@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz#7049a14d4211045412116d79b751def4484473a5"
+ integrity sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ==
dependencies:
- postcss "^5.0.4"
+ browserslist "^4.16.6"
+ caniuse-api "^3.0.0"
+ cssnano-utils "^3.1.0"
+ postcss-selector-parser "^6.0.5"
-postcss-merge-rules@^2.0.3:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz#d1df5dfaa7b1acc3be553f0e9e10e87c61b5f721"
+postcss-minify-font-values@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b"
+ integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==
dependencies:
- browserslist "^1.5.2"
- caniuse-api "^1.5.2"
- postcss "^5.0.4"
- postcss-selector-parser "^2.2.2"
- vendors "^1.0.0"
+ postcss-value-parser "^4.2.0"
-postcss-message-helpers@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e"
+postcss-minify-gradients@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c"
+ integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==
+ dependencies:
+ colord "^2.9.1"
+ cssnano-utils "^3.1.0"
+ postcss-value-parser "^4.2.0"
-postcss-minify-font-values@^1.0.2:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz#4b58edb56641eba7c8474ab3526cafd7bbdecb69"
+postcss-minify-params@^5.1.3:
+ version "5.1.3"
+ resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz#ac41a6465be2db735099bbd1798d85079a6dc1f9"
+ integrity sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==
dependencies:
- object-assign "^4.0.1"
- postcss "^5.0.4"
- postcss-value-parser "^3.0.2"
+ browserslist "^4.16.6"
+ cssnano-utils "^3.1.0"
+ postcss-value-parser "^4.2.0"
-postcss-minify-gradients@^1.0.1:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz#5dbda11373703f83cfb4a3ea3881d8d75ff5e6e1"
+postcss-minify-selectors@^5.2.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6"
+ integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==
dependencies:
- postcss "^5.0.12"
- postcss-value-parser "^3.3.0"
+ postcss-selector-parser "^6.0.5"
-postcss-minify-params@^1.0.4:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3"
+postcss-modules-extract-imports@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
+ integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==
+
+postcss-modules-local-by-default@^4.0.4:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz#7cbed92abd312b94aaea85b68226d3dec39a14e6"
+ integrity sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==
dependencies:
- alphanum-sort "^1.0.1"
- postcss "^5.0.2"
- postcss-value-parser "^3.0.2"
- uniqs "^2.0.0"
+ icss-utils "^5.0.0"
+ postcss-selector-parser "^6.0.2"
+ postcss-value-parser "^4.1.0"
-postcss-minify-selectors@^2.0.4:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf"
+postcss-modules-scope@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz#32cfab55e84887c079a19bbb215e721d683ef134"
+ integrity sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==
dependencies:
- alphanum-sort "^1.0.2"
- has "^1.0.1"
- postcss "^5.0.14"
- postcss-selector-parser "^2.0.0"
+ postcss-selector-parser "^6.0.4"
-postcss-modules-extract-imports@^1.2.0:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz#dc87e34148ec7eab5f791f7cd5849833375b741a"
+postcss-modules-values@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
+ integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
dependencies:
- postcss "^6.0.1"
+ icss-utils "^5.0.0"
-postcss-modules-local-by-default@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069"
+postcss-normalize-charset@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed"
+ integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==
+
+postcss-normalize-display-values@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8"
+ integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==
dependencies:
- css-selector-tokenizer "^0.7.0"
- postcss "^6.0.1"
+ postcss-value-parser "^4.2.0"
-postcss-modules-scope@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90"
+postcss-normalize-positions@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz#ef97279d894087b59325b45c47f1e863daefbb92"
+ integrity sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==
dependencies:
- css-selector-tokenizer "^0.7.0"
- postcss "^6.0.1"
+ postcss-value-parser "^4.2.0"
-postcss-modules-values@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20"
+postcss-normalize-repeat-style@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz#e9eb96805204f4766df66fd09ed2e13545420fb2"
+ integrity sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==
dependencies:
- icss-replace-symbols "^1.1.0"
- postcss "^6.0.1"
+ postcss-value-parser "^4.2.0"
-postcss-normalize-charset@^1.1.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1"
+postcss-normalize-string@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228"
+ integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==
dependencies:
- postcss "^5.0.5"
+ postcss-value-parser "^4.2.0"
-postcss-normalize-url@^3.0.7:
- version "3.0.8"
- resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz#108f74b3f2fcdaf891a2ffa3ea4592279fc78222"
+postcss-normalize-timing-functions@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb"
+ integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==
dependencies:
- is-absolute-url "^2.0.0"
- normalize-url "^1.4.0"
- postcss "^5.0.14"
- postcss-value-parser "^3.2.3"
+ postcss-value-parser "^4.2.0"
-postcss-ordered-values@^2.1.0:
- version "2.2.3"
- resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz#eec6c2a67b6c412a8db2042e77fe8da43f95c11d"
+postcss-normalize-unicode@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz#3d23aede35e160089a285e27bf715de11dc9db75"
+ integrity sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==
dependencies:
- postcss "^5.0.4"
- postcss-value-parser "^3.0.1"
+ browserslist "^4.16.6"
+ postcss-value-parser "^4.2.0"
-postcss-reduce-idents@^2.2.2:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3"
+postcss-normalize-url@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc"
+ integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==
dependencies:
- postcss "^5.0.4"
- postcss-value-parser "^3.0.2"
+ normalize-url "^6.0.1"
+ postcss-value-parser "^4.2.0"
-postcss-reduce-initial@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz#68f80695f045d08263a879ad240df8dd64f644ea"
+postcss-normalize-whitespace@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa"
+ integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==
dependencies:
- postcss "^5.0.4"
+ postcss-value-parser "^4.2.0"
-postcss-reduce-transforms@^1.0.3:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz#ff76f4d8212437b31c298a42d2e1444025771ae1"
+postcss-ordered-values@^5.1.3:
+ version "5.1.3"
+ resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz#b6fd2bd10f937b23d86bc829c69e7732ce76ea38"
+ integrity sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==
+ dependencies:
+ cssnano-utils "^3.1.0"
+ postcss-value-parser "^4.2.0"
+
+postcss-reduce-initial@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz#fc31659ea6e85c492fb2a7b545370c215822c5d6"
+ integrity sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==
dependencies:
- has "^1.0.1"
- postcss "^5.0.8"
- postcss-value-parser "^3.0.1"
+ browserslist "^4.16.6"
+ caniuse-api "^3.0.0"
+
+postcss-reduce-transforms@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9"
+ integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==
+ dependencies:
+ postcss-value-parser "^4.2.0"
postcss-resolve-nested-selector@0.1.1, postcss-resolve-nested-selector@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz#29ccbc7c37dedfac304e9fff0bf1596b3f6a0e4e"
integrity sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4=
-postcss-safe-parser@^4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz#a6d4e48f0f37d9f7c11b2a581bf00f8ba4870b96"
- integrity sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==
- dependencies:
- postcss "^7.0.26"
-
-postcss-sass@^0.4.4:
- version "0.4.4"
- resolved "https://registry.yarnpkg.com/postcss-sass/-/postcss-sass-0.4.4.tgz#91f0f3447b45ce373227a98b61f8d8f0785285a3"
- integrity sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg==
- dependencies:
- gonzales-pe "^4.3.0"
- postcss "^7.0.21"
+postcss-safe-parser@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz#bb4c29894171a94bc5c996b9a30317ef402adaa1"
+ integrity sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==
-postcss-scss@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-2.1.1.tgz#ec3a75fa29a55e016b90bf3269026c53c1d2b383"
- integrity sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==
- dependencies:
- postcss "^7.0.6"
+postcss-scss@^4.0.2, postcss-scss@^4.0.6:
+ version "4.0.6"
+ resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.6.tgz#5d62a574b950a6ae12f2aa89b60d63d9e4432bfd"
+ integrity sha512-rLDPhJY4z/i4nVFZ27j9GqLxj1pwxE80eAzUNRMXtcpipFYIeowerzBgG3yJhMtObGEXidtIgbUpQ3eLDsf5OQ==
postcss-selector-parser@2.2.1:
version "2.2.1"
@@ -8227,15 +7631,15 @@ postcss-selector-parser@2.2.1:
indexes-of "^1.0.1"
uniq "^1.0.1"
-postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2:
- version "2.2.3"
- resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90"
+postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.6:
+ version "6.0.11"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc"
+ integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==
dependencies:
- flatten "^1.0.2"
- indexes-of "^1.0.1"
- uniq "^1.0.1"
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
-postcss-selector-parser@^6.0.5:
+postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9:
version "6.0.10"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
@@ -8243,131 +7647,111 @@ postcss-selector-parser@^6.0.5:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
-postcss-svgo@^2.1.1:
- version "2.1.6"
- resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d"
+postcss-svgo@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d"
+ integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==
dependencies:
- is-svg "^2.0.0"
- postcss "^5.0.14"
- postcss-value-parser "^3.2.3"
- svgo "^0.7.0"
+ postcss-value-parser "^4.2.0"
+ svgo "^2.7.0"
-postcss-syntax@^0.36.2:
- version "0.36.2"
- resolved "https://registry.yarnpkg.com/postcss-syntax/-/postcss-syntax-0.36.2.tgz#f08578c7d95834574e5593a82dfbfa8afae3b51c"
- integrity sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==
-
-postcss-unique-selectors@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d"
+postcss-unique-selectors@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6"
+ integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==
dependencies:
- alphanum-sort "^1.0.1"
- postcss "^5.0.4"
- uniqs "^2.0.0"
-
-postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0:
- version "3.3.1"
- resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
+ postcss-selector-parser "^6.0.5"
postcss-value-parser@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
-postcss-zindex@^2.0.1:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22"
- dependencies:
- has "^1.0.1"
- postcss "^5.0.4"
- uniqs "^2.0.0"
+postcss-value-parser@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
+ integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
-postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16:
- version "5.2.18"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5"
+postcss@8.4.23:
+ version "8.4.23"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab"
+ integrity sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==
dependencies:
- chalk "^1.1.3"
- js-base64 "^2.1.9"
- source-map "^0.5.6"
- supports-color "^3.2.3"
+ nanoid "^3.3.6"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
-postcss@^6.0.1:
- version "6.0.23"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
+postcss@^8.1.10:
+ version "8.4.12"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905"
+ integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==
dependencies:
- chalk "^2.4.1"
- source-map "^0.6.1"
- supports-color "^5.4.0"
+ nanoid "^3.3.1"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
-postcss@^7.0.0:
- version "7.0.17"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f"
- integrity sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==
+postcss@^8.4.0:
+ version "8.4.21"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4"
+ integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==
dependencies:
- chalk "^2.4.2"
- source-map "^0.6.1"
- supports-color "^6.1.0"
+ nanoid "^3.3.4"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
-postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.6:
- version "7.0.32"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d"
- integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==
+postcss@^8.4.17:
+ version "8.4.18"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.18.tgz#6d50046ea7d3d66a85e0e782074e7203bc7fbca2"
+ integrity sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==
dependencies:
- chalk "^2.4.2"
- source-map "^0.6.1"
- supports-color "^6.1.0"
+ nanoid "^3.3.4"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
-postcss@^7.0.35:
- version "7.0.39"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309"
- integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==
+postcss@^8.4.19:
+ version "8.4.20"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.20.tgz#64c52f509644cecad8567e949f4081d98349dc56"
+ integrity sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==
dependencies:
- picocolors "^0.2.1"
- source-map "^0.6.1"
+ nanoid "^3.3.4"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
-postcss@^8.1.10:
- version "8.4.12"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905"
- integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==
+postcss@^8.4.33:
+ version "8.4.33"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742"
+ integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==
dependencies:
- nanoid "^3.3.1"
+ nanoid "^3.3.7"
picocolors "^1.0.0"
source-map-js "^1.0.2"
+prelude-ls@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
+ integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
+ integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==
-prepend-http@^1.0.0:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
-
-pretty-error@^2.0.2:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3"
+pretty-error@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6"
+ integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==
dependencies:
- renderkid "^2.0.1"
- utila "~0.4"
-
-private@^0.1.8:
- version "0.1.8"
- resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
+ lodash "^4.17.20"
+ renderkid "^3.0.0"
process-nextick-args@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
-process@^0.11.10:
- version "0.11.10"
- resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
-
-progress@^2.0.0:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
-
-promise-inflight@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
+proto-list@~1.2.1:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
+ integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==
proxy-addr@~2.0.7:
version "2.0.7"
@@ -8377,19 +7761,6 @@ proxy-addr@~2.0.7:
forwarded "0.2.0"
ipaddr.js "1.9.1"
-proxy-agent@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-2.0.0.tgz#57eb5347aa805d74ec681cb25649dba39c933499"
- dependencies:
- agent-base "2"
- debug "2"
- extend "3"
- http-proxy-agent "1"
- https-proxy-agent "1"
- lru-cache "~2.6.5"
- pac-proxy-agent "1"
- socks-proxy-agent "2"
-
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
@@ -8402,24 +7773,12 @@ prr@~1.0.1:
pseudomap@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
+ integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==
-public-encrypt@^4.0.0:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
- dependencies:
- bn.js "^4.1.0"
- browserify-rsa "^4.0.0"
- create-hash "^1.1.0"
- parse-asn1 "^5.0.0"
- randombytes "^2.0.1"
- safe-buffer "^5.1.2"
-
-pump@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
- dependencies:
- end-of-stream "^1.1.0"
- once "^1.3.1"
+psl@^1.1.33:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
+ integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
pump@^3.0.0:
version "3.0.0"
@@ -8428,54 +7787,39 @@ pump@^3.0.0:
end-of-stream "^1.1.0"
once "^1.3.1"
-pumpify@^1.3.3:
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce"
- dependencies:
- duplexify "^3.6.0"
- inherits "^2.0.3"
- pump "^2.0.0"
-
-punycode.js@2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.1.0.tgz#f3937f7a914152c2dc17e9c280a2cf86a26b7cda"
- integrity sha512-LvGUJ9QHiESLM4yn8JuJWicstRcJKRmP46psQw1HvCZ9puLFwYMKJWvkAkP3OHBVzNzZGx/D53EYJrIaKd9gZQ==
+punycode.js@2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.3.0.tgz#6aaa35964ffecc676545995ecb65980bd8302f61"
+ integrity sha512-AM9kSplQQCRlRkRZzx2EcqW2AQ9HuYoUzzl/tjJDNJEUeYHFGJ/rGE0a9cE1b41iuFz94pAwcEekC137Dd9Eyw==
punycode@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
+ integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==
-punycode@^1.2.4:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
-
-punycode@^2.1.0:
+punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
-q@1.4.1, q@^1.1.2:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e"
-
qjobs@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071"
integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==
-qrcode@1:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.0.tgz#95abb8a91fdafd86f8190f2836abbfc500c72d1b"
- integrity sha512-9MgRpgVc+/+47dFvQeD6U2s0Z92EsKzcHogtum4QB+UNd025WOJSHvn/hjk9xmzj7Stj95CyUAs31mrjxliEsQ==
+qrcode@1.5.3:
+ version "1.5.3"
+ resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.3.tgz#03afa80912c0dccf12bc93f615a535aad1066170"
+ integrity sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==
dependencies:
dijkstrajs "^1.0.1"
encode-utf8 "^1.0.3"
pngjs "^5.0.0"
yargs "^15.3.1"
-qs@6.10.3:
- version "6.10.3"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
- integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
+qs@6.11.0:
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
+ integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
dependencies:
side-channel "^1.0.4"
@@ -8484,54 +7828,38 @@ qs@6.9.7:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==
-query-string@^4.1.0:
- version "4.3.4"
- resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
- dependencies:
- object-assign "^4.1.0"
- strict-uri-encode "^1.0.0"
-
-querystring-es3@^0.2.0:
+querystring-es3@0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
+ integrity sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==
-querystring@0.2.0, querystring@^0.2.0:
+querystring@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
+ integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==
+
+querystringify@^2.1.1:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
+ integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
quick-lru@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
-randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
+randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
dependencies:
safe-buffer "^5.1.0"
-randomfill@^1.0.3:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458"
- dependencies:
- randombytes "^2.0.5"
- safe-buffer "^5.1.0"
-
range-parser@^1.2.1, range-parser@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
-raw-body@2:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3"
- dependencies:
- bytes "3.0.0"
- http-errors "1.6.3"
- iconv-lite "0.4.23"
- unpipe "1.0.0"
-
raw-body@2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c"
@@ -8552,27 +7880,6 @@ raw-body@2.5.1:
iconv-lite "0.4.24"
unpipe "1.0.0"
-raw-loader@0.5.1:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa"
- integrity sha1-DD0L6u2KAclm2Xh793goElKpeao=
-
-rc@^1.2.7:
- version "1.2.8"
- resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
- dependencies:
- deep-extend "^0.6.0"
- ini "~1.3.0"
- minimist "^1.2.0"
- strip-json-comments "~2.0.1"
-
-read-pkg-up@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
- dependencies:
- find-up "^1.0.0"
- read-pkg "^1.0.0"
-
read-pkg-up@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507"
@@ -8582,14 +7889,6 @@ read-pkg-up@^7.0.1:
read-pkg "^5.2.0"
type-fest "^0.8.1"
-read-pkg@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
- dependencies:
- load-json-file "^1.0.0"
- normalize-package-data "^2.3.2"
- path-type "^1.0.0"
-
read-pkg@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc"
@@ -8600,7 +7899,7 @@ read-pkg@^5.2.0:
parse-json "^5.0.0"
type-fest "^0.6.0"
-"readable-stream@1 || 2", readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6:
+readable-stream@^2.0.1:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
dependencies:
@@ -8612,25 +7911,7 @@ read-pkg@^5.2.0:
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
-readable-stream@1.0:
- version "1.0.34"
- resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
- dependencies:
- core-util-is "~1.0.0"
- inherits "~2.0.1"
- isarray "0.0.1"
- string_decoder "~0.10.x"
-
-readable-stream@1.1.x:
- version "1.1.14"
- resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
- dependencies:
- core-util-is "~1.0.0"
- inherits "~2.0.1"
- isarray "0.0.1"
- string_decoder "~0.10.x"
-
-readable-stream@^3.1.1:
+readable-stream@^3.4.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@@ -8639,20 +7920,18 @@ readable-stream@^3.1.1:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
-readdirp@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
- dependencies:
- graceful-fs "^4.1.11"
- micromatch "^3.1.10"
- readable-stream "^2.0.2"
-
-readdirp@~3.5.0:
- version "3.5.0"
- resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
- integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
+readable-stream@~2.3.6:
+ version "2.3.7"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
+ integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
dependencies:
- picomatch "^2.2.1"
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.1.1"
+ util-deprecate "~1.0.1"
readdirp@~3.6.0:
version "3.6.0"
@@ -8667,13 +7946,6 @@ rechoir@^0.6.2:
dependencies:
resolve "^1.1.6"
-redent@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
- dependencies:
- indent-string "^2.1.0"
- strip-indent "^1.0.1"
-
redent@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
@@ -8682,20 +7954,6 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
-reduce-css-calc@^1.2.6:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716"
- dependencies:
- balanced-match "^0.4.2"
- math-expression-evaluator "^1.2.14"
- reduce-function-call "^1.0.1"
-
-reduce-function-call@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99"
- dependencies:
- balanced-match "^0.4.2"
-
regenerate-unicode-properties@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56"
@@ -8703,49 +7961,48 @@ regenerate-unicode-properties@^10.0.1:
dependencies:
regenerate "^1.4.2"
-regenerate@^1.2.1:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
+regenerate-unicode-properties@^10.1.0:
+ version "10.1.0"
+ resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c"
+ integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==
+ dependencies:
+ regenerate "^1.4.2"
regenerate@^1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
-regenerator-runtime@^0.11.0:
- version "0.11.1"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
+regenerator-runtime@^0.13.11:
+ version "0.13.11"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
+ integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
regenerator-runtime@^0.13.4:
version "0.13.9"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
-regenerator-transform@^0.15.0:
- version "0.15.0"
- resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537"
- integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==
+regenerator-transform@^0.15.1:
+ version "0.15.1"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56"
+ integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==
dependencies:
"@babel/runtime" "^7.8.4"
-regex-not@^1.0.0, regex-not@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
+regexp.prototype.flags@^1.4.3:
+ version "1.4.3"
+ resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"
+ integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==
dependencies:
- extend-shallow "^3.0.2"
- safe-regex "^1.1.0"
-
-regexpp@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
+ call-bind "^1.0.2"
+ define-properties "^1.1.3"
+ functions-have-names "^1.2.2"
-regexpu-core@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b"
- dependencies:
- regenerate "^1.2.1"
- regjsgen "^0.2.0"
- regjsparser "^0.1.4"
+regexpp@^3.0.0, regexpp@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
+ integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
regexpu-core@^5.0.1:
version "5.0.1"
@@ -8771,21 +8028,23 @@ regexpu-core@^5.1.0:
unicode-match-property-ecmascript "^2.0.0"
unicode-match-property-value-ecmascript "^2.0.0"
-regjsgen@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"
+regexpu-core@^5.3.1:
+ version "5.3.2"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b"
+ integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==
+ dependencies:
+ "@babel/regjsgen" "^0.8.0"
+ regenerate "^1.4.2"
+ regenerate-unicode-properties "^10.1.0"
+ regjsparser "^0.9.1"
+ unicode-match-property-ecmascript "^2.0.0"
+ unicode-match-property-value-ecmascript "^2.1.0"
regjsgen@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d"
integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==
-regjsparser@^0.1.4:
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c"
- dependencies:
- jsesc "~0.5.0"
-
regjsparser@^0.8.2:
version "0.8.4"
resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f"
@@ -8793,65 +8052,28 @@ regjsparser@^0.8.2:
dependencies:
jsesc "~0.5.0"
-relateurl@0.2.x:
- version "0.2.7"
- resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
-
-remark-parse@^9.0.0:
- version "9.0.0"
- resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-9.0.0.tgz#4d20a299665880e4f4af5d90b7c7b8a935853640"
- integrity sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==
- dependencies:
- mdast-util-from-markdown "^0.8.0"
-
-remark-stringify@^9.0.0:
- version "9.0.1"
- resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-9.0.1.tgz#576d06e910548b0a7191a71f27b33f1218862894"
- integrity sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==
- dependencies:
- mdast-util-to-markdown "^0.6.0"
-
-remark@^13.0.0:
- version "13.0.0"
- resolved "https://registry.yarnpkg.com/remark/-/remark-13.0.0.tgz#d15d9bf71a402f40287ebe36067b66d54868e425"
- integrity sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==
- dependencies:
- remark-parse "^9.0.0"
- remark-stringify "^9.0.0"
- unified "^9.1.0"
-
-remove-trailing-separator@^1.0.1:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
-
-renderkid@^2.0.1:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.2.tgz#12d310f255360c07ad8fde253f6c9e9de372d2aa"
+regjsparser@^0.9.1:
+ version "0.9.1"
+ resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709"
+ integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==
dependencies:
- css-select "^1.1.0"
- dom-converter "~0.2"
- htmlparser2 "~3.3.0"
- strip-ansi "^3.0.0"
- utila "^0.4.0"
-
-repeat-element@^1.1.2:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
+ jsesc "~0.5.0"
-repeat-string@^1.0.0, repeat-string@^1.6.1:
- version "1.6.1"
- resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
+relateurl@^0.2.7:
+ version "0.2.7"
+ resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
+ integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==
-repeating@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
+renderkid@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a"
+ integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==
dependencies:
- is-finite "^1.0.0"
-
-replace-ext@1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
- integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=
+ css-select "^4.1.3"
+ dom-converter "^0.2.0"
+ htmlparser2 "^6.1.0"
+ lodash "^4.17.21"
+ strip-ansi "^6.0.1"
require-directory@^2.1.1:
version "2.1.1"
@@ -8875,11 +8097,6 @@ requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
-resolve-from@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
- integrity sha1-six699nWiBvItuZTM17rywoYh0g=
-
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@@ -8889,21 +8106,17 @@ resolve-from@^5.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
-resolve-url@^0.2.1:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
-
-resolve@1.1.x, resolve@^1.1.6:
+resolve@^1.1.6:
version "1.1.7"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
-resolve@^1.10.0, resolve@^1.8.1:
+resolve@^1.10.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.0.tgz#4014870ba296176b86343d50b60f3b50609ce232"
dependencies:
path-parse "^1.0.6"
-resolve@^1.14.2, resolve@^1.20.0:
+resolve@^1.14.2:
version "1.22.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
@@ -8912,7 +8125,7 @@ resolve@^1.14.2, resolve@^1.20.0:
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
-resolve@^1.22.0:
+resolve@^1.22.1:
version "1.22.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
@@ -8925,13 +8138,17 @@ restore-cursor@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
dependencies:
- onetime "^2.0.0"
+ onetime "^2.0.0"
+ signal-exit "^3.0.2"
+
+restore-cursor@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
+ integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
+ dependencies:
+ onetime "^5.1.0"
signal-exit "^3.0.2"
-ret@~0.1.10:
- version "0.1.15"
- resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
-
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
@@ -8942,19 +8159,6 @@ rfdc@^1.3.0:
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
-rimraf@2.6.3, rimraf@^2.5.4, rimraf@^2.6.1:
- version "2.6.3"
- resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
- dependencies:
- glob "^7.1.3"
-
-rimraf@^2.6.3:
- version "2.7.1"
- resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
- integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
- dependencies:
- glob "^7.1.3"
-
rimraf@^3.0.0, rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
@@ -8962,146 +8166,125 @@ rimraf@^3.0.0, rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
-ripemd160@^2.0.0, ripemd160@^2.0.1:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
- dependencies:
- hash-base "^3.0.0"
- inherits "^2.0.1"
-
-run-async@^2.2.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
- dependencies:
- is-promise "^2.1.0"
-
run-parallel@^1.1.9:
version "1.1.9"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679"
integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==
-run-queue@^1.0.0, run-queue@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
- dependencies:
- aproba "^1.1.1"
-
-rxjs@^6.4.0:
- version "6.5.2"
- resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7"
- dependencies:
- tslib "^1.9.0"
-
safe-buffer@5.2.1, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
-safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
-safe-regex@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
+safe-regex-test@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295"
+ integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==
dependencies:
- ret "~0.1.10"
+ call-bind "^1.0.2"
+ get-intrinsic "^1.1.3"
+ is-regex "^1.1.4"
-"safer-buffer@>= 2.1.2 < 3":
+"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
-samsam@1.x, samsam@^1.1.3:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50"
-
-sass-loader@7.3.1:
- version "7.3.1"
- resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.3.1.tgz#a5bf68a04bcea1c13ff842d747150f7ab7d0d23f"
- integrity sha512-tuU7+zm0pTCynKYHpdqaPpe+MMTQ76I9TPZ7i4/5dZsigE350shQWe5EZNl5dBidM49TPET75tNqRbcsUZWeNA==
+sass-loader@13.2.2:
+ version "13.2.2"
+ resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-13.2.2.tgz#f97e803993b24012c10d7ba9676548bf7a6b18b9"
+ integrity sha512-nrIdVAAte3B9icfBiGWvmMhT/D+eCDwnk+yA7VE/76dp/WkHX+i44Q/pfo71NYbwj0Ap+PGsn0ekOuU1WFJ2AA==
dependencies:
- clone-deep "^4.0.1"
- loader-utils "^1.0.1"
- neo-async "^2.5.0"
- pify "^4.0.1"
- semver "^6.3.0"
+ klona "^2.0.6"
+ neo-async "^2.6.2"
-sass@1.54.0:
- version "1.54.0"
- resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.0.tgz#24873673265e2a4fe3d3a997f714971db2fba1f4"
- integrity sha512-C4zp79GCXZfK0yoHZg+GxF818/aclhp9F48XBu/+bm9vXEVAYov9iU3FBVRMq3Hx3OA4jfKL+p2K9180mEh0xQ==
+sass@1.60.0:
+ version "1.60.0"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.60.0.tgz#657f0c23a302ac494b09a5ba8497b739fb5b5a81"
+ integrity sha512-updbwW6fNb5gGm8qMXzVO7V4sWf7LMXnMly/JEyfbfERbVH46Fn6q02BX7/eHTdKpE7d+oTkMMQpFWNUMfFbgQ==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
-sax@^1.2.4, sax@~1.2.1:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
-
-schema-utils@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
+saxes@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d"
+ integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==
dependencies:
- ajv "^6.1.0"
- ajv-errors "^1.0.0"
- ajv-keywords "^3.1.0"
+ xmlchars "^2.2.0"
-schema-utils@^2.6.5:
- version "2.7.1"
- resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7"
- integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==
+schema-utils@^3.1.0, schema-utils@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281"
+ integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==
dependencies:
- "@types/json-schema" "^7.0.5"
- ajv "^6.12.4"
+ "@types/json-schema" "^7.0.8"
+ ajv "^6.12.5"
ajv-keywords "^3.5.2"
-schema-utils@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef"
- integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==
+schema-utils@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7"
+ integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==
dependencies:
- "@types/json-schema" "^7.0.6"
- ajv "^6.12.5"
- ajv-keywords "^3.5.2"
+ "@types/json-schema" "^7.0.9"
+ ajv "^8.8.0"
+ ajv-formats "^2.1.1"
+ ajv-keywords "^5.0.0"
selenium-server@2.53.1:
version "2.53.1"
resolved "https://registry.yarnpkg.com/selenium-server/-/selenium-server-2.53.1.tgz#d681528812f3c2e0531a6b7e613e23bb02cce8a6"
-"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.6.0:
+selenium-webdriver@4.6.1:
+ version "4.6.1"
+ resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.6.1.tgz#ac66867206542a40c24b5a44f4ccbae992e962dc"
+ integrity sha512-FT8Dw0tbzaTp8YYLuwhaCnve/nw03HKrOJrA3aUmTKmxaIFSP4kT2R5fN3K0RpV5kbR0ZnM4FGVI2vANBvekaA==
+ dependencies:
+ jszip "^3.10.0"
+ tmp "^0.2.1"
+ ws ">=8.7.0"
+
+"semver@2 || 3 || 4 || 5", semver@^5.6.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
-semver@5.7.1:
- version "5.7.1"
- resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
- integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
-
-semver@7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
- integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
+semver@7.3.5:
+ version "7.3.5"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
+ integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
+ dependencies:
+ lru-cache "^6.0.0"
-semver@^5.5.1:
- version "5.7.0"
- resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
+semver@7.3.8, semver@^7.3.8:
+ version "7.3.8"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
+ integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
+ dependencies:
+ lru-cache "^6.0.0"
semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
-semver@^7.3.4:
+semver@^7.0.0, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6:
version "7.3.7"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
dependencies:
lru-cache "^6.0.0"
-semver@~5.0.1:
- version "5.0.3"
- resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a"
+semver@^7.5.4:
+ version "7.5.4"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
+ integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
+ dependencies:
+ lru-cache "^6.0.0"
send@0.18.0:
version "0.18.0"
@@ -9122,17 +8305,10 @@ send@0.18.0:
range-parser "~1.2.1"
statuses "2.0.1"
-serialize-javascript@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
- integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
- dependencies:
- randombytes "^2.1.0"
-
-serialize-javascript@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
- integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==
+serialize-javascript@6.0.0, serialize-javascript@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
+ integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==
dependencies:
randombytes "^2.1.0"
@@ -9146,71 +8322,44 @@ serve-static@1.15.0:
parseurl "~1.3.3"
send "0.18.0"
-serviceworker-webpack-plugin@1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/serviceworker-webpack-plugin/-/serviceworker-webpack-plugin-1.0.1.tgz#481863288487e92da01d49745336c72ef8a6136b"
- integrity sha512-VgDEkZ3pA0HajsRaWtl5w6bLxAXx0Y+4dm7YeTcIxVmvC9YXvstex38HOBDuYETeDS5fUlBy/47gC0QYBrG0nw==
+serviceworker-webpack5-plugin@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/serviceworker-webpack5-plugin/-/serviceworker-webpack5-plugin-2.0.0.tgz#6d3516b11fcf3cfadee60f04d607e7d042e3e7fc"
+ integrity sha512-lMypbP8MQiWeUa2Za1BfV9M18aXQCoB71XaeL6v32qxvdTCqILv2cxDNW1Mlq/tHJ8hgKlP3INQ2eo2BLaHyZQ==
dependencies:
minimatch "^3.0.4"
-set-blocking@^2.0.0, set-blocking@~2.0.0:
+set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
-set-value@^0.4.3:
- version "0.4.3"
- resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1"
- dependencies:
- extend-shallow "^2.0.1"
- is-extendable "^0.1.1"
- is-plain-object "^2.0.1"
- to-object-path "^0.3.0"
-
-set-value@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274"
- dependencies:
- extend-shallow "^2.0.1"
- is-extendable "^0.1.1"
- is-plain-object "^2.0.3"
- split-string "^3.0.1"
-
-setimmediate@^1.0.4:
+setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
- integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
-
-setprototypeof@1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
+ integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
setprototypeof@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
-sha.js@^2.4.0, sha.js@^2.4.8:
- version "2.4.11"
- resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
- dependencies:
- inherits "^2.0.1"
- safe-buffer "^5.0.1"
-
shallow-clone@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
dependencies:
kind-of "^6.0.2"
-shebang-command@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
dependencies:
- shebang-regex "^1.0.0"
+ shebang-regex "^3.0.0"
-shebang-regex@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shelljs@0.8.5:
version "0.8.5"
@@ -9230,45 +8379,46 @@ side-channel@^1.0.4:
get-intrinsic "^1.0.2"
object-inspect "^1.9.0"
-signal-exit@^3.0.0, signal-exit@^3.0.2:
+sigmund@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
+ integrity sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==
+
+signal-exit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
-sinon-chai@2.14.0:
- version "2.14.0"
- resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-2.14.0.tgz#da7dd4cc83cd6a260b67cca0f7a9fdae26a1205d"
- integrity sha512-9stIF1utB0ywNHNT7RgiXbdmen8QDCRsrTjw+G9TgKt1Yexjiv8TOWZ6WHsTPz57Yky3DIswZvEqX8fpuHNDtQ==
+signal-exit@^3.0.7:
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
+ integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
-sinon@2.4.1:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.4.1.tgz#021fd64b54cb77d9d2fb0d43cdedfae7629c3a36"
- integrity sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==
- dependencies:
- diff "^3.1.0"
- formatio "1.2.0"
- lolex "^1.6.0"
- native-promise-only "^0.8.1"
- path-to-regexp "^1.7.0"
- samsam "^1.1.3"
- text-encoding "0.6.4"
- type-detect "^4.0.0"
+sinon-chai@3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.7.0.tgz#cfb7dec1c50990ed18c153f1840721cf13139783"
+ integrity sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==
-slash@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
+sinon@15.0.4:
+ version "15.0.4"
+ resolved "https://registry.yarnpkg.com/sinon/-/sinon-15.0.4.tgz#bcca6fef19b14feccc96473f0d7adc81e0bc5268"
+ integrity sha512-uzmfN6zx3GQaria1kwgWGeKiXSSbShBbue6Dcj0SI8fiCNFbiUDqKl57WFlY5lyhxZVUKmXvzgG2pilRQCBwWg==
+ dependencies:
+ "@sinonjs/commons" "^3.0.0"
+ "@sinonjs/fake-timers" "^10.0.2"
+ "@sinonjs/samsam" "^8.0.0"
+ diff "^5.1.0"
+ nise "^5.1.4"
+ supports-color "^7.2.0"
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
-slice-ansi@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
- dependencies:
- ansi-styles "^3.2.0"
- astral-regex "^1.0.0"
- is-fullwidth-code-point "^2.0.0"
+slash@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
+ integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
slice-ansi@^4.0.0:
version "4.0.0"
@@ -9279,109 +8429,40 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
-smart-buffer@^1.0.13:
- version "1.1.15"
- resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-1.1.15.tgz#7f114b5b65fab3e2a35aa775bb12f0d1c649bf16"
-
-snapdragon-node@^2.0.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
- dependencies:
- define-property "^1.0.0"
- isobject "^3.0.0"
- snapdragon-util "^3.0.1"
-
-snapdragon-util@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2"
- dependencies:
- kind-of "^3.2.0"
-
-snapdragon@^0.8.1:
- version "0.8.2"
- resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d"
+socket.io-adapter@~2.5.2:
+ version "2.5.5"
+ resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz#c7a1f9c703d7756844751b6ff9abfc1780664082"
+ integrity sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==
dependencies:
- base "^0.11.1"
- debug "^2.2.0"
- define-property "^0.2.5"
- extend-shallow "^2.0.1"
- map-cache "^0.2.2"
- source-map "^0.5.6"
- source-map-resolve "^0.5.0"
- use "^3.1.0"
+ debug "~4.3.4"
+ ws "~8.17.1"
-socket.io-adapter@~2.4.0:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz#b50a4a9ecdd00c34d4c8c808224daa1a786152a6"
- integrity sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==
-
-socket.io-parser@~4.0.4:
- version "4.0.4"
- resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.4.tgz#9ea21b0d61508d18196ef04a2c6b9ab630f4c2b0"
- integrity sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==
+socket.io-parser@~4.2.4:
+ version "4.2.4"
+ resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83"
+ integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==
dependencies:
- "@types/component-emitter" "^1.2.10"
- component-emitter "~1.3.0"
+ "@socket.io/component-emitter" "~3.1.0"
debug "~4.3.1"
-socket.io@^4.4.1:
- version "4.5.1"
- resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.5.1.tgz#aa7e73f8a6ce20ee3c54b2446d321bbb6b1a9029"
- integrity sha512-0y9pnIso5a9i+lJmsCdtmTTgJFFSvNQKDnPQRz28mGNnxbmqYg2QPtJTLFxhymFZhAIn50eHAKzJeiNaKr+yUQ==
+socket.io@^4.7.2:
+ version "4.7.5"
+ resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.5.tgz#56eb2d976aef9d1445f373a62d781a41c7add8f8"
+ integrity sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==
dependencies:
accepts "~1.3.4"
base64id "~2.0.0"
+ cors "~2.8.5"
debug "~4.3.2"
- engine.io "~6.2.0"
- socket.io-adapter "~2.4.0"
- socket.io-parser "~4.0.4"
-
-socks-proxy-agent@2:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-2.1.1.tgz#86ebb07193258637870e13b7bd99f26c663df3d3"
- dependencies:
- agent-base "2"
- extend "3"
- socks "~1.1.5"
-
-socks@~1.1.5:
- version "1.1.10"
- resolved "https://registry.yarnpkg.com/socks/-/socks-1.1.10.tgz#5b8b7fc7c8f341c53ed056e929b7bf4de8ba7b5a"
- dependencies:
- ip "^1.1.4"
- smart-buffer "^1.0.13"
-
-sort-keys@^1.0.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
- dependencies:
- is-plain-obj "^1.0.0"
-
-source-list-map@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
+ engine.io "~6.5.2"
+ socket.io-adapter "~2.5.2"
+ socket.io-parser "~4.2.4"
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
-source-map-resolve@^0.5.0:
- version "0.5.2"
- resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259"
- dependencies:
- atob "^2.1.1"
- decode-uri-component "^0.2.0"
- resolve-url "^0.2.1"
- source-map-url "^0.4.0"
- urix "^0.1.0"
-
-source-map-support@^0.4.15:
- version "0.4.18"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"
- dependencies:
- source-map "^0.5.6"
-
source-map-support@^0.5.16:
version "0.5.16"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
@@ -9390,37 +8471,18 @@ source-map-support@^0.5.16:
buffer-from "^1.0.0"
source-map "^0.6.0"
-source-map-support@~0.5.12:
- version "0.5.19"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
- integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
+source-map-support@~0.5.20:
+ version "0.5.21"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
+ integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
-source-map-url@^0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
-
source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
-source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7:
- version "0.5.7"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
-
-source-map@^0.7.3:
- version "0.7.3"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
- integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
-
-source-map@~0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d"
- dependencies:
- amdefine ">=0.0.4"
-
sourcemap-codec@^1.4.8:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
@@ -9448,83 +8510,28 @@ spdx-license-ids@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e"
-specificity@^0.4.1:
- version "0.4.1"
- resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019"
- integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==
-
-split-string@^3.0.1, split-string@^3.0.2:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
- dependencies:
- extend-shallow "^3.0.0"
-
-sprintf-js@~1.0.2:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
-
-ssri@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
- dependencies:
- figgy-pudding "^3.5.1"
-
-ssri@^8.0.1:
- version "8.0.1"
- resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af"
- integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==
- dependencies:
- minipass "^3.1.1"
+stable@^0.1.8:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
+ integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
-static-extend@^0.1.1:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
+stacktrace-parser@0.1.10:
+ version "0.1.10"
+ resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a"
+ integrity sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==
dependencies:
- define-property "^0.2.5"
- object-copy "^0.1.0"
+ type-fest "^0.7.1"
statuses@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
-"statuses@>= 1.4.0 < 2":
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
-
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
-stream-browserify@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
- dependencies:
- inherits "~2.0.1"
- readable-stream "^2.0.2"
-
-stream-each@^1.1.0:
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae"
- dependencies:
- end-of-stream "^1.1.0"
- stream-shift "^1.0.0"
-
-stream-http@^2.7.2:
- version "2.8.3"
- resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc"
- dependencies:
- builtin-status-codes "^3.0.0"
- inherits "^2.0.1"
- readable-stream "^2.3.6"
- to-arraybuffer "^1.0.0"
- xtend "^4.0.0"
-
-stream-shift@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
-
streamroller@^3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.0.6.tgz#52823415800ded79a49aa3f7712f50a422b97493"
@@ -9534,34 +8541,7 @@ streamroller@^3.0.6:
debug "^4.3.4"
fs-extra "^10.0.1"
-strict-uri-encode@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
-
-string-width@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
- dependencies:
- code-point-at "^1.0.0"
- is-fullwidth-code-point "^1.0.0"
- strip-ansi "^3.0.0"
-
-"string-width@^1.0.2 || 2", string-width@^2.1.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
- dependencies:
- is-fullwidth-code-point "^2.0.0"
- strip-ansi "^4.0.0"
-
-string-width@^3.0.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
- dependencies:
- emoji-regex "^7.0.1"
- is-fullwidth-code-point "^2.0.0"
- strip-ansi "^5.1.0"
-
-string-width@^4.1.0, string-width@^4.2.2, string-width@^4.2.3:
+string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.2, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -9587,6 +8567,15 @@ string.prototype.trimend@^1.0.4:
call-bind "^1.0.2"
define-properties "^1.1.3"
+string.prototype.trimend@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533"
+ integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ es-abstract "^1.20.4"
+
string.prototype.trimstart@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed"
@@ -9595,11 +8584,14 @@ string.prototype.trimstart@^1.0.4:
call-bind "^1.0.2"
define-properties "^1.1.3"
-string_decoder@^1.0.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
+string.prototype.trimstart@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4"
+ integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==
dependencies:
- safe-buffer "~5.1.0"
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ es-abstract "^1.20.4"
string_decoder@^1.1.1:
version "1.3.0"
@@ -9608,17 +8600,27 @@ string_decoder@^1.1.1:
dependencies:
safe-buffer "~5.2.0"
-string_decoder@~0.10.x:
- version "0.10.31"
- resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
-
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
dependencies:
safe-buffer "~5.1.0"
-strip-ansi@^3.0.0, strip-ansi@^3.0.1:
+strip-ansi@5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
+ integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
+ dependencies:
+ ansi-regex "^4.1.0"
+
+strip-ansi@6.0.1, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
dependencies:
@@ -9630,12 +8632,6 @@ strip-ansi@^4.0.0:
dependencies:
ansi-regex "^3.0.0"
-strip-ansi@^5.1.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
- dependencies:
- ansi-regex "^4.1.0"
-
strip-ansi@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
@@ -9643,33 +8639,10 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"
-strip-ansi@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
-strip-ansi@~0.1.0:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991"
-
-strip-bom@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
- dependencies:
- is-utf8 "^0.2.0"
-
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
-strip-indent@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
- dependencies:
- get-stdin "^4.0.1"
-
strip-indent@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
@@ -9677,26 +8650,58 @@ strip-indent@^3.0.0:
dependencies:
min-indent "^1.0.0"
-strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+ integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
style-search@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=
-stylelint-config-recommended@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz#e0e547434016c5539fe2650afd58049a2fd1d657"
- integrity sha512-F6yTRuc06xr1h5Qw/ykb2LuFynJ2IxkKfCMf+1xqPffkxh0S09Zc902XCffcsw/XMFq/OzQ1w54fLIDtmRNHnQ==
+stylehacks@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.0.tgz#a40066490ca0caca04e96c6b02153ddc39913520"
+ integrity sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==
+ dependencies:
+ browserslist "^4.16.6"
+ postcss-selector-parser "^6.0.4"
+
+stylelint-config-html@>=1.0.0, stylelint-config-html@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/stylelint-config-html/-/stylelint-config-html-1.1.0.tgz#999db19aea713b7ff6dde92ada76e4c1bd812b66"
+ integrity sha512-IZv4IVESjKLumUGi+HWeb7skgO6/g4VMuAYrJdlqQFndgbj6WJAXPhaysvBiXefX79upBdQVumgYcdd17gCpjQ==
+
+stylelint-config-recommended-scss@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-8.0.0.tgz#1c1e93e619fe2275d4c1067928d92e0614f7d64f"
+ integrity sha512-BxjxEzRaZoQb7Iinc3p92GS6zRdRAkIuEu2ZFLTxJK2e1AIcCb5B5MXY9KOXdGTnYFZ+KKx6R4Fv9zU6CtMYPQ==
+ dependencies:
+ postcss-scss "^4.0.2"
+ stylelint-config-recommended "^9.0.0"
+ stylelint-scss "^4.0.0"
+
+stylelint-config-recommended-vue@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/stylelint-config-recommended-vue/-/stylelint-config-recommended-vue-1.4.0.tgz#0a182da17dc9e846e4bec65a7676ccf882b35964"
+ integrity sha512-DVJqyX2KvMCn9U0+keL12r7xlsH26K4Vg8NrIZuq5MoF7g82DpMp326Om4E0Q+Il1o+bTHuUyejf2XAI0iD04Q==
+ dependencies:
+ semver "^7.3.5"
+ stylelint-config-html ">=1.0.0"
+ stylelint-config-recommended ">=6.0.0"
+
+stylelint-config-recommended@>=6.0.0, stylelint-config-recommended@^9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-9.0.0.tgz#1c9e07536a8cd875405f8ecef7314916d94e7e40"
+ integrity sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==
-stylelint-config-standard@20.0.0:
- version "20.0.0"
- resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-20.0.0.tgz#06135090c9e064befee3d594289f50e295b5e20d"
- integrity sha512-IB2iFdzOTA/zS4jSVav6z+wGtin08qfj+YyExHB3LF9lnouQht//YyB0KZq9gGz5HNPkddHOzcY8HsUey6ZUlA==
+stylelint-config-standard@29.0.0:
+ version "29.0.0"
+ resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-29.0.0.tgz#4cc0e0f05512a39bb8b8e97853247d3a95d66fa2"
+ integrity sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg==
dependencies:
- stylelint-config-recommended "^3.0.0"
+ stylelint-config-recommended "^9.0.0"
stylelint-rscss@0.4.0:
version "0.4.0"
@@ -9706,94 +8711,95 @@ stylelint-rscss@0.4.0:
postcss-resolve-nested-selector "0.1.1"
postcss-selector-parser "2.2.1"
-stylelint@13.13.1:
- version "13.13.1"
- resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.13.1.tgz#fca9c9f5de7990ab26a00f167b8978f083a18f3c"
- integrity sha512-Mv+BQr5XTUrKqAXmpqm6Ddli6Ief+AiPZkRsIrAoUKFuq/ElkUh9ZMYxXD0iQNZ5ADghZKLOWz1h7hTClB7zgQ==
+stylelint-scss@^4.0.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.3.0.tgz#638800faf823db11fff60d537c81051fe74c90fa"
+ integrity sha512-GvSaKCA3tipzZHoz+nNO7S02ZqOsdBzMiCx9poSmLlb3tdJlGddEX/8QzCOD8O7GQan9bjsvLMsO5xiw6IhhIQ==
+ dependencies:
+ lodash "^4.17.21"
+ postcss-media-query-parser "^0.2.3"
+ postcss-resolve-nested-selector "^0.1.1"
+ postcss-selector-parser "^6.0.6"
+ postcss-value-parser "^4.1.0"
+
+stylelint-webpack-plugin@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/stylelint-webpack-plugin/-/stylelint-webpack-plugin-3.3.0.tgz#3ba40e2b2b2b7d1802fa53618e01fc28bc21ffb3"
+ integrity sha512-F53bapIZ9zI16ero8IWm6TrUE6SSibZBphJE9b5rR2FxtvmGmm1YmS+a5xjQzn63+cv71GVSCu4byX66fBLpEw==
+ dependencies:
+ globby "^11.1.0"
+ jest-worker "^28.1.0"
+ micromatch "^4.0.5"
+ normalize-path "^3.0.0"
+ schema-utils "^4.0.0"
+
+stylelint@14.16.1:
+ version "14.16.1"
+ resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-14.16.1.tgz#b911063530619a1bbe44c2b875fd8181ebdc742d"
+ integrity sha512-ErlzR/T3hhbV+a925/gbfc3f3Fep9/bnspMiJPorfGEmcBbXdS+oo6LrVtoUZ/w9fqD6o6k7PtUlCOsCRdjX/A==
dependencies:
- "@stylelint/postcss-css-in-js" "^0.37.2"
- "@stylelint/postcss-markdown" "^0.36.2"
- autoprefixer "^9.8.6"
+ "@csstools/selector-specificity" "^2.0.2"
balanced-match "^2.0.0"
- chalk "^4.1.1"
- cosmiconfig "^7.0.0"
- debug "^4.3.1"
- execall "^2.0.0"
- fast-glob "^3.2.5"
- fastest-levenshtein "^1.0.12"
+ colord "^2.9.3"
+ cosmiconfig "^7.1.0"
+ css-functions-list "^3.1.0"
+ debug "^4.3.4"
+ fast-glob "^3.2.12"
+ fastest-levenshtein "^1.0.16"
file-entry-cache "^6.0.1"
- get-stdin "^8.0.0"
global-modules "^2.0.0"
- globby "^11.0.3"
+ globby "^11.1.0"
globjoin "^0.1.4"
- html-tags "^3.1.0"
- ignore "^5.1.8"
+ html-tags "^3.2.0"
+ ignore "^5.2.1"
import-lazy "^4.0.0"
imurmurhash "^0.1.4"
- known-css-properties "^0.21.0"
- lodash "^4.17.21"
- log-symbols "^4.1.0"
+ is-plain-object "^5.0.0"
+ known-css-properties "^0.26.0"
mathml-tag-names "^2.1.3"
meow "^9.0.0"
- micromatch "^4.0.4"
- normalize-selector "^0.2.0"
- postcss "^7.0.35"
- postcss-html "^0.36.0"
- postcss-less "^3.1.4"
+ micromatch "^4.0.5"
+ normalize-path "^3.0.0"
+ picocolors "^1.0.0"
+ postcss "^8.4.19"
postcss-media-query-parser "^0.2.3"
postcss-resolve-nested-selector "^0.1.1"
- postcss-safe-parser "^4.0.2"
- postcss-sass "^0.4.4"
- postcss-scss "^2.1.1"
- postcss-selector-parser "^6.0.5"
- postcss-syntax "^0.36.2"
- postcss-value-parser "^4.1.0"
+ postcss-safe-parser "^6.0.0"
+ postcss-selector-parser "^6.0.11"
+ postcss-value-parser "^4.2.0"
resolve-from "^5.0.0"
- slash "^3.0.0"
- specificity "^0.4.1"
- string-width "^4.2.2"
- strip-ansi "^6.0.0"
+ string-width "^4.2.3"
+ strip-ansi "^6.0.1"
style-search "^0.1.0"
- sugarss "^2.0.0"
+ supports-hyperlinks "^2.3.0"
svg-tags "^1.0.0"
- table "^6.6.0"
+ table "^6.8.1"
v8-compile-cache "^2.3.0"
- write-file-atomic "^3.0.3"
-
-sugarss@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-2.0.0.tgz#ddd76e0124b297d40bf3cca31c8b22ecb43bc61d"
- integrity sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==
- dependencies:
- postcss "^7.0.2"
+ write-file-atomic "^4.0.2"
-supports-color@3.1.2, supports-color@^3.1.0:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
+supports-color@8.1.1, supports-color@^8.0.0:
+ version "8.1.1"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
+ integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
dependencies:
- has-flag "^1.0.0"
+ has-flag "^4.0.0"
supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
-supports-color@^3.2.3:
- version "3.2.3"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
- dependencies:
- has-flag "^1.0.0"
-
-supports-color@^5.3.0, supports-color@^5.4.0:
+supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
dependencies:
has-flag "^3.0.0"
-supports-color@^6.1.0:
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
+supports-color@^7.0.0, supports-color@^7.2.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
- has-flag "^3.0.0"
+ has-flag "^4.0.0"
supports-color@^7.1.0:
version "7.1.0"
@@ -9802,6 +8808,14 @@ supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
+supports-hyperlinks@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624"
+ integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==
+ dependencies:
+ has-flag "^4.0.0"
+ supports-color "^7.0.0"
+
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
@@ -9812,31 +8826,28 @@ svg-tags@^1.0.0:
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=
-svgo@^0.7.0:
- version "0.7.2"
- resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
- dependencies:
- coa "~1.0.1"
- colors "~1.1.2"
- csso "~2.3.1"
- js-yaml "~3.7.0"
- mkdirp "~0.5.1"
- sax "~1.2.1"
- whet.extend "~0.9.9"
-
-table@^5.2.3:
- version "5.3.3"
- resolved "https://registry.yarnpkg.com/table/-/table-5.3.3.tgz#eae560c90437331b74200e011487a33442bd28b4"
- dependencies:
- ajv "^6.9.1"
- lodash "^4.17.11"
- slice-ansi "^2.1.0"
- string-width "^3.0.0"
-
-table@^6.6.0:
- version "6.8.0"
- resolved "https://registry.yarnpkg.com/table/-/table-6.8.0.tgz#87e28f14fa4321c3377ba286f07b79b281a3b3ca"
- integrity sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==
+svgo@^2.7.0:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24"
+ integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==
+ dependencies:
+ "@trysound/sax" "0.2.0"
+ commander "^7.2.0"
+ css-select "^4.1.3"
+ css-tree "^1.1.3"
+ csso "^4.2.0"
+ picocolors "^1.0.0"
+ stable "^0.1.8"
+
+symbol-tree@^3.2.4:
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
+ integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
+
+table@^6.8.1:
+ version "6.8.1"
+ resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf"
+ integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==
dependencies:
ajv "^8.0.1"
lodash.truncate "^4.4.2"
@@ -9844,33 +8855,10 @@ table@^6.6.0:
string-width "^4.2.3"
strip-ansi "^6.0.1"
-tapable@^1.0.0, tapable@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
-
-tar@^4:
- version "4.4.8"
- resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d"
- dependencies:
- chownr "^1.1.1"
- fs-minipass "^1.2.5"
- minipass "^2.3.4"
- minizlib "^1.1.1"
- mkdirp "^0.5.0"
- safe-buffer "^5.1.2"
- yallist "^3.0.2"
-
-tar@^6.0.2:
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83"
- integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==
- dependencies:
- chownr "^2.0.0"
- fs-minipass "^2.0.0"
- minipass "^3.0.0"
- minizlib "^2.1.1"
- mkdirp "^1.0.3"
- yallist "^4.0.0"
+tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
+ integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
tcp-port-used@^1.0.1:
version "1.0.1"
@@ -9879,65 +8867,31 @@ tcp-port-used@^1.0.1:
debug "4.1.0"
is2 "2.0.1"
-terser-webpack-plugin@^1.4.3:
- version "1.4.5"
- resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b"
- integrity sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==
+terser-webpack-plugin@^5.1.3:
+ version "5.3.4"
+ resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.4.tgz#f4d31e265883d20fda3ca9c0fc6a53f173ae62e3"
+ integrity sha512-SmnkUhBxLDcBfTIeaq+ZqJXLVEyXxSaNcCeSezECdKjfkMrTTnPvapBILylYwyEvHFZAn2cJ8dtiXel5XnfOfQ==
dependencies:
- cacache "^12.0.2"
- find-cache-dir "^2.1.0"
- is-wsl "^1.1.0"
- schema-utils "^1.0.0"
- serialize-javascript "^4.0.0"
- source-map "^0.6.1"
- terser "^4.1.2"
- webpack-sources "^1.4.0"
- worker-farm "^1.7.0"
+ "@jridgewell/trace-mapping" "^0.3.14"
+ jest-worker "^27.4.5"
+ schema-utils "^3.1.1"
+ serialize-javascript "^6.0.0"
+ terser "^5.14.1"
-terser@^4.1.2:
- version "4.8.0"
- resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17"
- integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==
+terser@^5.10.0, terser@^5.14.1:
+ version "5.14.2"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10"
+ integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==
dependencies:
+ "@jridgewell/source-map" "^0.3.2"
+ acorn "^8.5.0"
commander "^2.20.0"
- source-map "~0.6.1"
- source-map-support "~0.5.12"
+ source-map-support "~0.5.20"
-text-encoding@0.6.4:
- version "0.6.4"
- resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
-
-text-table@^0.2.0:
+text-table@0.2.0, text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
-through2@^2.0.0:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
- dependencies:
- readable-stream "~2.3.6"
- xtend "~4.0.1"
-
-through@^2.3.6:
- version "2.3.8"
- resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
-
-thunkify@~2.1.1:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d"
-
-timers-browserify@^2.0.4:
- version "2.0.10"
- resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae"
- dependencies:
- setimmediate "^1.0.4"
-
-tmp@^0.0.33:
- version "0.0.33"
- resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
- dependencies:
- os-tmpdir "~1.0.2"
-
tmp@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
@@ -9945,31 +8899,10 @@ tmp@^0.2.1:
dependencies:
rimraf "^3.0.0"
-to-arraybuffer@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
-
-to-fast-properties@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
-
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
-to-object-path@^0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
- dependencies:
- kind-of "^3.0.2"
-
-to-regex-range@^2.1.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38"
- dependencies:
- is-number "^3.0.0"
- repeat-string "^1.6.1"
-
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@@ -9977,42 +8910,33 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
-to-regex@^3.0.1, to-regex@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"
- dependencies:
- define-property "^2.0.2"
- extend-shallow "^3.0.2"
- regex-not "^1.0.2"
- safe-regex "^1.1.0"
-
toidentifier@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
-toposort@^1.0.0:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
+tough-cookie@^4.0.0:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874"
+ integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==
+ dependencies:
+ psl "^1.1.33"
+ punycode "^2.1.1"
+ universalify "^0.2.0"
+ url-parse "^1.5.3"
-trim-newlines@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
+tr46@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9"
+ integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==
+ dependencies:
+ punycode "^2.1.1"
trim-newlines@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30"
integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==
-trim-right@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
-
-trough@^1.0.0:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
- integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
-
tsconfig-paths@^3.14.1:
version "3.14.1"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a"
@@ -10023,29 +8947,26 @@ tsconfig-paths@^3.14.1:
minimist "^1.2.6"
strip-bom "^3.0.0"
-tslib@^1.9.0:
- version "1.9.3"
- resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
+tslib@^2.0.3:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
+ integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
-tty-browserify@0.0.0:
- version "0.0.0"
- resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
+type-check@^0.4.0, type-check@~0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
+ integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+ dependencies:
+ prelude-ls "^1.2.1"
type-check@~0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
+ integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==
dependencies:
prelude-ls "~1.1.2"
-type-detect@0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822"
-
-type-detect@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2"
-
-type-detect@^4.0.0:
+type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
@@ -10054,11 +8975,21 @@ type-fest@^0.18.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f"
integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==
+type-fest@^0.20.2:
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
+ integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
+
type-fest@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==
+type-fest@^0.7.1:
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48"
+ integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==
+
type-fest@^0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
@@ -10072,29 +9003,20 @@ type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"
-typedarray-to-buffer@^3.1.5:
- version "3.1.5"
- resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
- integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
+typed-array-length@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb"
+ integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==
dependencies:
- is-typedarray "^1.0.0"
-
-typedarray@^0.0.6:
- version "0.0.6"
- resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
+ call-bind "^1.0.2"
+ for-each "^0.3.3"
+ is-typed-array "^1.1.9"
ua-parser-js@^0.7.30:
version "0.7.31"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6"
integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==
-uglify-js@3.4.x, uglify-js@^3.1.4:
- version "3.4.9"
- resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
- dependencies:
- commander "~2.17.1"
- source-map "~0.6.1"
-
unbox-primitive@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
@@ -10105,9 +9027,15 @@ unbox-primitive@^1.0.1:
has-symbols "^1.0.2"
which-boxed-primitive "^1.0.2"
-underscore@~1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
+unbox-primitive@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
+ integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==
+ dependencies:
+ call-bind "^1.0.2"
+ has-bigints "^1.0.2"
+ has-symbols "^1.0.3"
+ which-boxed-primitive "^1.0.2"
unicode-canonical-property-names-ecmascript@^2.0.0:
version "2.0.0"
@@ -10127,70 +9055,24 @@ unicode-match-property-value-ecmascript@^2.0.0:
resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714"
integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==
+unicode-match-property-value-ecmascript@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0"
+ integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==
+
unicode-property-aliases-ecmascript@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8"
integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==
-unified@^9.1.0:
- version "9.2.2"
- resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.2.tgz#67649a1abfc3ab85d2969502902775eb03146975"
- integrity sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==
- dependencies:
- bail "^1.0.0"
- extend "^3.0.0"
- is-buffer "^2.0.0"
- is-plain-obj "^2.0.0"
- trough "^1.0.0"
- vfile "^4.0.0"
-
-union-value@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
- dependencies:
- arr-union "^3.1.0"
- get-value "^2.0.6"
- is-extendable "^0.1.1"
- set-value "^0.4.3"
-
uniq@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
-uniqs@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
-
-unique-filename@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
- dependencies:
- unique-slug "^2.0.0"
-
-unique-slug@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.1.tgz#5e9edc6d1ce8fb264db18a507ef9bd8544451ca6"
- dependencies:
- imurmurhash "^0.1.4"
-
-unist-util-find-all-after@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/unist-util-find-all-after/-/unist-util-find-all-after-3.0.2.tgz#fdfecd14c5b7aea5e9ef38d5e0d5f774eeb561f6"
- integrity sha512-xaTC/AGZ0rIM2gM28YVRAFPIZpzbpDtU3dRmp7EXlNVA8ziQc4hY3H7BHXM1J49nEmiqc3svnqMReW+PGqbZKQ==
- dependencies:
- unist-util-is "^4.0.0"
-
-unist-util-is@^4.0.0:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.0.2.tgz#c7d1341188aa9ce5b3cff538958de9895f14a5de"
- integrity sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==
-
-unist-util-stringify-position@^2.0.0:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da"
- integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==
- dependencies:
- "@types/unist" "^2.0.2"
+universalify@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
+ integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
universalify@^2.0.0:
version "2.0.0"
@@ -10201,16 +9083,18 @@ unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
-unset-value@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559"
- dependencies:
- has-value "^0.3.1"
- isobject "^3.0.0"
+untildify@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
+ integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
-upath@^1.1.1:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068"
+update-browserslist-db@^1.0.13:
+ version "1.0.13"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4"
+ integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==
+ dependencies:
+ escalade "^3.1.1"
+ picocolors "^1.0.0"
update-browserslist-db@^1.0.4:
version "1.0.4"
@@ -10220,9 +9104,21 @@ update-browserslist-db@^1.0.4:
escalade "^3.1.1"
picocolors "^1.0.0"
-upper-case@^1.1.1:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
+update-browserslist-db@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz#be06a5eedd62f107b7c19eb5bcefb194411abf38"
+ integrity sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==
+ dependencies:
+ escalade "^3.1.1"
+ picocolors "^1.0.0"
+
+update-browserslist-db@^1.0.9:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3"
+ integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==
+ dependencies:
+ escalade "^3.1.1"
+ picocolors "^1.0.0"
uri-js@^4.2.2:
version "4.2.2"
@@ -10230,31 +9126,23 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
-urix@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
-
-url-loader@1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-1.1.2.tgz#b971d191b83af693c5e3fea4064be9e1f2d7f8d8"
- integrity sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==
+url-parse@^1.5.3:
+ version "1.5.10"
+ resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
+ integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
dependencies:
- loader-utils "^1.1.0"
- mime "^2.0.3"
- schema-utils "^1.0.0"
+ querystringify "^2.1.1"
+ requires-port "^1.0.0"
-url@^0.11.0:
+url@0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
+ integrity sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==
dependencies:
punycode "1.3.2"
querystring "0.2.0"
-use@^3.1.0:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
-
-utf8@^3.0.0:
+utf8@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1"
integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==
@@ -10263,26 +9151,7 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
-util.promisify@1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030"
- dependencies:
- define-properties "^1.1.2"
- object.getownpropertydescriptors "^2.0.3"
-
-util@0.10.3:
- version "0.10.3"
- resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
- dependencies:
- inherits "2.0.1"
-
-util@^0.11.0:
- version "0.11.1"
- resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61"
- dependencies:
- inherits "2.0.3"
-
-utila@^0.4.0, utila@~0.4:
+utila@~0.4:
version "0.4.0"
resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
@@ -10290,6 +9159,11 @@ utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+uuid@8.3.2:
+ version "8.3.2"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
+ integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
+
uuid@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
@@ -10310,132 +9184,142 @@ vary@^1, vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
-vendors@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.2.tgz#7fcb5eef9f5623b156bcea89ec37d63676f21801"
-
-vfile-message@^2.0.0:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a"
- integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==
- dependencies:
- "@types/unist" "^2.0.0"
- unist-util-stringify-position "^2.0.0"
-
-vfile@^4.0.0:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.1.1.tgz#282d28cebb609183ac51703001bc18b3e3f17de9"
- integrity sha512-lRjkpyDGjVlBA7cDQhQ+gNcvB1BGaTHYuSOcY3S7OhDmBtnzX95FhtZZDecSTDm6aajFymyve6S5DN4ZHGezdQ==
- dependencies:
- "@types/unist" "^2.0.0"
- is-buffer "^2.0.0"
- replace-ext "1.0.0"
- unist-util-stringify-position "^2.0.0"
- vfile-message "^2.0.0"
-
-vm-browserify@^1.0.1:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
- integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
-
void-elements@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
-vue-demi@^0.13.4:
- version "0.13.5"
- resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.5.tgz#d5eddbc9eaefb89ce5995269d1fa6b0486312092"
- integrity sha512-tO3K2bML3AwiHmVHeKCq6HLef2st4zBXIV5aEkoJl6HZ+gJWxWv2O8wLH8qrA3SX3lDoTDHNghLX1xZg83MXvw==
+vue-demi@^0.13.11:
+ version "0.13.11"
+ resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99"
+ integrity sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==
-vue-eslint-parser@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-5.0.0.tgz#00f4e4da94ec974b821a26ff0ed0f7a78402b8a1"
+vue-eslint-parser@^9.0.1:
+ version "9.0.3"
+ resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.0.3.tgz#0c17a89e0932cc94fa6a79f0726697e13bfe3c96"
+ integrity sha512-yL+ZDb+9T0ELG4VIFo/2anAOz8SvBdlqEnQnvJ3M7Scq56DvtjY0VY88bByRZB0D4J0u8olBcfrXTVONXsh4og==
dependencies:
- debug "^4.1.0"
- eslint-scope "^4.0.0"
- eslint-visitor-keys "^1.0.0"
- espree "^4.1.0"
- esquery "^1.0.1"
- lodash "^4.17.11"
-
-vue-i18n@9.2.0-beta.40:
- version "9.2.0-beta.40"
- resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.2.0-beta.40.tgz#8088b19d619f47bf0e0f529f9bd1413460f2ff56"
- integrity sha512-UwcGsbTTaDJry6BbFFzt115EVHN/bXi07DyUIZ4zrYeGMBPp2QAptMwVaGUQid1gaMmUreAKarGIqw46oCQEvg==
- dependencies:
- "@intlify/core-base" "9.2.0-beta.40"
- "@intlify/shared" "9.2.0-beta.40"
- "@intlify/vue-devtools" "9.2.0-beta.40"
+ debug "^4.3.4"
+ eslint-scope "^7.1.1"
+ eslint-visitor-keys "^3.3.0"
+ espree "^9.3.1"
+ esquery "^1.4.0"
+ lodash "^4.17.21"
+ semver "^7.3.6"
+
+vue-i18n@9.2.2:
+ version "9.2.2"
+ resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.2.2.tgz#aeb49d9424923c77e0d6441e3f21dafcecd0e666"
+ integrity sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==
+ dependencies:
+ "@intlify/core-base" "9.2.2"
+ "@intlify/shared" "9.2.2"
+ "@intlify/vue-devtools" "9.2.2"
"@vue/devtools-api" "^6.2.1"
-vue-loader@^16.0.0:
- version "16.8.3"
- resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-16.8.3.tgz#d43e675def5ba9345d6c7f05914c13d861997087"
- integrity sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==
+vue-loader@17.0.1:
+ version "17.0.1"
+ resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-17.0.1.tgz#c0ee8875e0610a0c2d13ba9b4d50a9c8442e7a3a"
+ integrity sha512-/OOyugJnImKCkAKrAvdsWMuwoCqGxWT5USLsjohzWbMgOwpA5wQmzQiLMzZd7DjhIfunzAGIApTOgIylz/kwcg==
dependencies:
chalk "^4.1.0"
hash-sum "^2.0.0"
loader-utils "^2.0.0"
-vue-router@4.1.2:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.1.2.tgz#ae08f63c9610afa6bff6743e8f128b7054d4c9f5"
- integrity sha512-5BP1qXFncVRwgV/XnqzsKApdMjQPqWIpoUBdL1ynz8HyLxIX/UDAx7Ql2BjmA5CXT/p61JfZvkpiFWFpaqcfag==
+vue-observe-visibility@^2.0.0-alpha.1:
+ version "2.0.0-alpha.1"
+ resolved "https://registry.yarnpkg.com/vue-observe-visibility/-/vue-observe-visibility-2.0.0-alpha.1.tgz#1e4eda7b12562161d58984b7e0dea676d83bdb13"
+ integrity sha512-flFbp/gs9pZniXR6fans8smv1kDScJ8RS7rEpMjhVabiKeq7Qz3D9+eGsypncjfIyyU84saU88XZ0zjbD6Gq/g==
+
+vue-resize@^2.0.0-alpha.1:
+ version "2.0.0-alpha.1"
+ resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz#43eeb79e74febe932b9b20c5c57e0ebc14e2df3a"
+ integrity sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==
+
+vue-router@4.1.6:
+ version "4.1.6"
+ resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.1.6.tgz#b70303737e12b4814578d21d68d21618469375a1"
+ integrity sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==
dependencies:
- "@vue/devtools-api" "^6.1.4"
+ "@vue/devtools-api" "^6.4.5"
-vue-style-loader@4.1.2:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"
+vue-style-loader@4.1.3:
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz#6d55863a51fa757ab24e89d9371465072aa7bc35"
+ integrity sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==
dependencies:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
-vue-template-compiler@2.6.11:
- version "2.6.11"
- resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz#c04704ef8f498b153130018993e56309d4698080"
- integrity sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==
+vue-template-compiler@2.7.14:
+ version "2.7.14"
+ resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz#4545b7dfb88090744c1577ae5ac3f964e61634b1"
+ integrity sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==
dependencies:
de-indent "^1.0.2"
- he "^1.1.0"
+ he "^1.2.0"
-vue@^3.2.31:
- version "3.2.31"
- resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.31.tgz#e0c49924335e9f188352816788a4cca10f817ce6"
- integrity sha512-odT3W2tcffTiQCy57nOT93INw1auq5lYLLYtWpPYQQYQOOdHiqFct9Xhna6GJ+pJQaF67yZABraH47oywkJgFw==
+vue-virtual-scroller@^2.0.0-beta.7:
+ version "2.0.0-beta.8"
+ resolved "https://registry.yarnpkg.com/vue-virtual-scroller/-/vue-virtual-scroller-2.0.0-beta.8.tgz#eeceda57e4faa5ba1763994c873923e2a956898b"
+ integrity sha512-b8/f5NQ5nIEBRTNi6GcPItE4s7kxNHw2AIHLtDp+2QvqdTjVN0FgONwX9cr53jWRgnu+HRLPaWDOR2JPI5MTfQ==
dependencies:
- "@vue/compiler-dom" "3.2.31"
- "@vue/compiler-sfc" "3.2.31"
- "@vue/runtime-dom" "3.2.31"
- "@vue/server-renderer" "3.2.31"
- "@vue/shared" "3.2.31"
+ mitt "^2.1.0"
+ vue-observe-visibility "^2.0.0-alpha.1"
+ vue-resize "^2.0.0-alpha.1"
-vuex@4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/vuex/-/vuex-4.0.2.tgz#f896dbd5bf2a0e963f00c67e9b610de749ccacc9"
- integrity sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==
+vue@3.2.45:
+ version "3.2.45"
+ resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.45.tgz#94a116784447eb7dbd892167784619fef379b3c8"
+ integrity sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==
+ dependencies:
+ "@vue/compiler-dom" "3.2.45"
+ "@vue/compiler-sfc" "3.2.45"
+ "@vue/runtime-dom" "3.2.45"
+ "@vue/server-renderer" "3.2.45"
+ "@vue/shared" "3.2.45"
+
+vuex@4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/vuex/-/vuex-4.1.0.tgz#aa1b3ea5c7385812b074c86faeeec2217872e36c"
+ integrity sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==
dependencies:
"@vue/devtools-api" "^6.0.0-beta.11"
-watchpack-chokidar2@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957"
- integrity sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==
+w3c-hr-time@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
+ integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==
dependencies:
- chokidar "^2.1.8"
+ browser-process-hrtime "^1.0.0"
-watchpack@^1.7.4:
- version "1.7.5"
- resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453"
- integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==
+w3c-xmlserializer@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz#06cdc3eefb7e4d0b20a560a5a3aeb0d2d9a65923"
+ integrity sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==
+ dependencies:
+ xml-name-validator "^4.0.0"
+
+watchpack@^2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
+ integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
dependencies:
+ glob-to-regexp "^0.4.1"
graceful-fs "^4.1.2"
- neo-async "^2.5.0"
- optionalDependencies:
- chokidar "^3.4.1"
- watchpack-chokidar2 "^2.0.1"
-webpack-dev-middleware@3.7.3, webpack-dev-middleware@^3.7.0:
+wcwidth@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
+ integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==
+ dependencies:
+ defaults "^1.0.3"
+
+webidl-conversions@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
+ integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
+
+webpack-dev-middleware@3.7.3:
version "3.7.3"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5"
integrity sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==
@@ -10446,14 +9330,13 @@ webpack-dev-middleware@3.7.3, webpack-dev-middleware@^3.7.0:
range-parser "^1.2.1"
webpack-log "^2.0.0"
-webpack-hot-middleware@2.25.1:
- version "2.25.1"
- resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.25.1.tgz#581f59edf0781743f4ca4c200fd32c9266c6cf7c"
- integrity sha512-Koh0KyU/RPYwel/khxbsDz9ibDivmUbrRuKSSQvW42KSDdO4w23WI3SkHpSUKHE76LrFnnM/L7JCrpBwu8AXYw==
+webpack-hot-middleware@2.25.3:
+ version "2.25.3"
+ resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.25.3.tgz#be343ce2848022cfd854dd82820cd730998c6794"
+ integrity sha512-IK/0WAHs7MTu1tzLTjio73LjS3Ov+VvBKQmE8WPlJutgG5zT6Urgq/BbAdRrHTRpyzK0dvAvFh1Qg98akxgZpA==
dependencies:
ansi-html-community "0.0.8"
html-entities "^2.1.0"
- querystring "^0.2.0"
strip-ansi "^6.0.0"
webpack-log@^2.0.0:
@@ -10474,53 +9357,75 @@ webpack-merge@0.20.0:
lodash.isplainobject "^3.2.0"
lodash.merge "^3.3.2"
-webpack-sources@^1.1.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85"
+webpack-merge@^4.1.5:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d"
+ integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==
dependencies:
- source-list-map "^2.0.0"
- source-map "~0.6.1"
+ lodash "^4.17.15"
-webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
- version "1.4.3"
- resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
- integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
+webpack-sources@^3.2.3:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
+ integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
+
+webpack@5.75.0:
+ version "5.75.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152"
+ integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==
+ dependencies:
+ "@types/eslint-scope" "^3.7.3"
+ "@types/estree" "^0.0.51"
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/wasm-edit" "1.11.1"
+ "@webassemblyjs/wasm-parser" "1.11.1"
+ acorn "^8.7.1"
+ acorn-import-assertions "^1.7.6"
+ browserslist "^4.14.5"
+ chrome-trace-event "^1.0.2"
+ enhanced-resolve "^5.10.0"
+ es-module-lexer "^0.9.0"
+ eslint-scope "5.1.1"
+ events "^3.2.0"
+ glob-to-regexp "^0.4.1"
+ graceful-fs "^4.2.9"
+ json-parse-even-better-errors "^2.3.1"
+ loader-runner "^4.2.0"
+ mime-types "^2.1.27"
+ neo-async "^2.6.2"
+ schema-utils "^3.1.0"
+ tapable "^2.1.1"
+ terser-webpack-plugin "^5.1.3"
+ watchpack "^2.4.0"
+ webpack-sources "^3.2.3"
+
+whatwg-encoding@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53"
+ integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==
dependencies:
- source-list-map "^2.0.0"
- source-map "~0.6.1"
+ iconv-lite "0.6.3"
-webpack@4.46.0:
- version "4.46.0"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542"
- integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==
- dependencies:
- "@webassemblyjs/ast" "1.9.0"
- "@webassemblyjs/helper-module-context" "1.9.0"
- "@webassemblyjs/wasm-edit" "1.9.0"
- "@webassemblyjs/wasm-parser" "1.9.0"
- acorn "^6.4.1"
- ajv "^6.10.2"
- ajv-keywords "^3.4.1"
- chrome-trace-event "^1.0.2"
- enhanced-resolve "^4.5.0"
- eslint-scope "^4.0.3"
- json-parse-better-errors "^1.0.2"
- loader-runner "^2.4.0"
- loader-utils "^1.2.3"
- memory-fs "^0.4.1"
- micromatch "^3.1.10"
- mkdirp "^0.5.3"
- neo-async "^2.6.1"
- node-libs-browser "^2.2.1"
- schema-utils "^1.0.0"
- tapable "^1.1.3"
- terser-webpack-plugin "^1.4.3"
- watchpack "^1.7.4"
- webpack-sources "^1.4.1"
-
-whet.extend@~0.9.9:
- version "0.9.9"
- resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"
+whatwg-mimetype@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7"
+ integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==
+
+whatwg-url@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-10.0.0.tgz#37264f720b575b4a311bd4094ed8c760caaa05da"
+ integrity sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==
+ dependencies:
+ tr46 "^3.0.0"
+ webidl-conversions "^7.0.0"
+
+whatwg-url@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018"
+ integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==
+ dependencies:
+ tr46 "^3.0.0"
+ webidl-conversions "^7.0.0"
which-boxed-primitive@^1.0.2:
version "1.0.2"
@@ -10538,31 +9443,59 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
-which@^1.0.9, which@^1.1.1, which@^1.2.9, which@^1.3.1:
+which-typed-array@^1.1.9:
+ version "1.1.9"
+ resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6"
+ integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==
+ dependencies:
+ available-typed-arrays "^1.0.5"
+ call-bind "^1.0.2"
+ for-each "^0.3.3"
+ gopd "^1.0.1"
+ has-tostringtag "^1.0.0"
+ is-typed-array "^1.1.10"
+
+which@2.0.2, which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+which@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
dependencies:
isexe "^2.0.0"
-wide-align@^1.1.0:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
+which@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/which/-/which-3.0.1.tgz#89f1cd0c23f629a8105ffe69b8172791c87b4be1"
+ integrity sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==
dependencies:
- string-width "^1.0.2 || 2"
+ isexe "^2.0.0"
-wordwrap@^1.0.0, wordwrap@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
+widest-line@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"
+ integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==
+ dependencies:
+ string-width "^4.0.0"
-wordwrap@~0.0.2:
- version "0.0.3"
- resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
+word-wrap@^1.2.3, word-wrap@~1.2.3:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
+ integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
-worker-farm@^1.7.0:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"
- dependencies:
- errno "~0.1.7"
+workerpool@6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b"
+ integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==
+
+workerpool@6.2.1:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
+ integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
wrap-ansi@^6.2.0:
version "6.2.0"
@@ -10586,32 +9519,40 @@ wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
-write-file-atomic@^3.0.3:
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
- integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==
+write-file-atomic@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd"
+ integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==
dependencies:
imurmurhash "^0.1.4"
- is-typedarray "^1.0.0"
- signal-exit "^3.0.2"
- typedarray-to-buffer "^3.1.5"
+ signal-exit "^3.0.7"
-write@1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"
- dependencies:
- mkdirp "^0.5.1"
+ws@>=8.7.0:
+ version "8.8.1"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.1.tgz#5dbad0feb7ade8ecc99b830c1d77c913d4955ff0"
+ integrity sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==
+
+ws@^8.2.3:
+ version "8.11.0"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
+ integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
-ws@~8.2.3:
- version "8.2.3"
- resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba"
- integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==
+ws@~8.17.1:
+ version "8.17.1"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b"
+ integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==
-xregexp@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
+xml-name-validator@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
+ integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==
+
+xmlchars@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
+ integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
-xtend@^4.0.0, xtend@~4.0.1:
+xtend@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
@@ -10627,10 +9568,12 @@ y18n@^5.0.5:
yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
+ integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==
-yallist@^3.0.0, yallist@^3.0.2:
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
+yallist@^3.0.2:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
+ integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yallist@^4.0.0:
version "4.0.0"
@@ -10646,11 +9589,16 @@ yaml-eslint-parser@^0.3.2:
lodash "^4.17.20"
yaml "^1.10.0"
-yaml@^1.10.0:
+yaml@^1.10.0, yaml@^1.10.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
+yargs-parser@20.2.4:
+ version "20.2.4"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
+ integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==
+
yargs-parser@^18.1.2:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
@@ -10664,6 +9612,29 @@ yargs-parser@^20.2.2, yargs-parser@^20.2.3:
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
+yargs-unparser@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb"
+ integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==
+ dependencies:
+ camelcase "^6.0.0"
+ decamelize "^4.0.0"
+ flat "^5.0.2"
+ is-plain-obj "^2.1.0"
+
+yargs@16.2.0, yargs@^16.1.1:
+ version "16.2.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
+ integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
+ dependencies:
+ cliui "^7.0.2"
+ escalade "^3.1.1"
+ get-caller-file "^2.0.5"
+ require-directory "^2.1.1"
+ string-width "^4.2.0"
+ y18n "^5.0.5"
+ yargs-parser "^20.2.2"
+
yargs@^15.3.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
@@ -10681,19 +9652,6 @@ yargs@^15.3.1:
y18n "^4.0.0"
yargs-parser "^18.1.2"
-yargs@^16.1.1:
- version "16.2.0"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
- integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
- dependencies:
- cliui "^7.0.2"
- escalade "^3.1.1"
- get-caller-file "^2.0.5"
- require-directory "^2.1.1"
- string-width "^4.2.0"
- y18n "^5.0.5"
- yargs-parser "^20.2.2"
-
yauzl@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
@@ -10707,7 +9665,7 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
-zwitch@^1.0.0:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"
- integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==
+yocto-queue@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251"
+ integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==