commit: 0885f5cac20f0741022311735c344401336d54d6
parent 139f8dd91eab93243161c81dcf4435a2fe22ab32
Author: Henry Jameson <me@hjkos.com>
Date: Mon, 20 Jan 2025 03:01:54 +0200
Merge remote-tracking branch 'origin/develop' into renovate/karma-webpack-5.x
Diffstat:
427 files changed, 19410 insertions(+), 6773 deletions(-)
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/.gitignore b/.gitignore
@@ -8,3 +8,4 @@ selenium-debug.log
.idea/
config/local.json
static/emoji.json
+logs/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
@@ -38,11 +38,14 @@ lint:
stage: lint
script:
- yarn
- - npm run lint
- - npm run stylelint
+ - yarn lint
+ - yarn stylelint
test:
stage: test
+ tags:
+ - amd64
+ - himem
variables:
APT_CACHE_DIR: apt-cache
script:
@@ -54,9 +57,12 @@ test:
build:
stage: build
+ tags:
+ - amd64
+ - himem
script:
- yarn
- - npm run build
+ - yarn build
artifacts:
paths:
- dist/
diff --git a/CHANGELOG.md b/CHANGELOG.md
@@ -3,6 +3,74 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+## 2.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
+- 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
diff --git a/build/check-versions.js b/build/check-versions.js
@@ -11,11 +11,6 @@ var versionRequirements = [
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
- },
- {
- name: 'npm',
- currentVersion: exec('npm --version'),
- versionRequirement: packageConfig.engines.npm
}
]
diff --git a/changelog.d/add-apng.add b/changelog.d/add-apng.add
@@ -1 +0,0 @@
-Make Pleroma FE to also view apng (Animated PNG) attachment.
diff --git a/changelog.d/admin-emoji-packs.add b/changelog.d/admin-emoji-packs.add
@@ -1 +0,0 @@
-Added emoji pack management to the admin panel
diff --git a/changelog.d/backend-repo-url.skip b/changelog.d/backend-repo-url.skip
diff --git a/changelog.d/batch2.skip b/changelog.d/batch2.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/color-schemes.add b/changelog.d/color-schemes.add
@@ -0,0 +1 @@
+Some new default color schemes
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/create-link-when-url-present.add b/changelog.d/create-link-when-url-present.add
@@ -1 +0,0 @@
-Create a link to the URL of the scrobble when it's present
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/denpmify-gitlab-ci.skip b/changelog.d/denpmify-gitlab-ci.skip
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/double-notifications.fix b/changelog.d/double-notifications.fix
@@ -1 +0,0 @@
-Fix native notifications appearing as many times as there are open tabs. Clicking on notification will focus last focused tab.
diff --git a/changelog.d/drafts-imp.skip b/changelog.d/drafts-imp.skip
diff --git a/changelog.d/drafts.add b/changelog.d/drafts.add
@@ -0,0 +1 @@
+Add draft management system
diff --git a/changelog.d/emoji-picker.add b/changelog.d/emoji-picker.add
@@ -0,0 +1 @@
+fixed occasional overflows in emoji picker and made header scrollable
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/extra-notifications.add b/changelog.d/extra-notifications.add
@@ -1 +0,0 @@
-Support showing extra notifications in the notifications column
diff --git a/changelog.d/focus-clear.add b/changelog.d/focus-clear.add
@@ -1 +0,0 @@
-Focusing into a tab clears all current desktop notifications
diff --git a/changelog.d/group-actor.add b/changelog.d/group-actor.add
@@ -1 +0,0 @@
-Support group actors
diff --git a/changelog.d/hide-custom-emojis-in-picker.add b/changelog.d/hide-custom-emojis-in-picker.add
@@ -1 +0,0 @@
-Allow hiding custom emojis in picker.
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/mobile-chrome-notifs.fix b/changelog.d/mobile-chrome-notifs.fix
@@ -1 +0,0 @@
-Fixed error that appeared on mobile Chrome(ium) (and derivatives) when native notifications are allowed
diff --git a/changelog.d/mobile-drawer-notifications.change b/changelog.d/mobile-drawer-notifications.change
@@ -1 +0,0 @@
-Added option to not mark all notifications when closing notifications drawer on mobile, this creates a new button to mark all as seen.
diff --git a/changelog.d/modals-mobile.change b/changelog.d/modals-mobile.change
@@ -0,0 +1 @@
+modal layout for mobile has new layout to make it easy to use
diff --git a/changelog.d/modals.fix b/changelog.d/modals.fix
@@ -0,0 +1 @@
+fixed modals buttons overflow
diff --git a/changelog.d/more-notification-types-setting.fix b/changelog.d/more-notification-types-setting.fix
@@ -1 +0,0 @@
-Fixed being unable to set notification visibility for reports and follow requests
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/muted_user_en_translation.skip b/changelog.d/muted_user_en_translation.skip
@@ -0,0 +1 @@
+Added missing EN translation key for status.muted_user
diff --git a/changelog.d/mutes.change b/changelog.d/mutes.change
@@ -0,0 +1 @@
+better display of mute reason on posts
diff --git a/changelog.d/native-filtering.add b/changelog.d/native-filtering.add
@@ -1 +0,0 @@
-Added 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.
diff --git a/changelog.d/native-notifications.add b/changelog.d/native-notifications.add
@@ -1 +0,0 @@
-Native notifications now also have "badge" property that matches instance's favicon (visible in Android Chromium at least)
diff --git a/changelog.d/no-check-npm.skip b/changelog.d/no-check-npm.skip
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/non-expiring-polls-indication.fix b/changelog.d/non-expiring-polls-indication.fix
@@ -1 +0,0 @@
-The expiry date indication won't be shown if the poll never expires
diff --git a/changelog.d/noninteractive-ignore-read.add b/changelog.d/noninteractive-ignore-read.add
@@ -1 +0,0 @@
-Added 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)
diff --git a/changelog.d/notification-read.add b/changelog.d/notification-read.add
@@ -1 +0,0 @@
-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.
diff --git a/changelog.d/notifications-sorting.change b/changelog.d/notifications-sorting.change
@@ -1 +0,0 @@
-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.
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/public-favorites.add b/changelog.d/public-favorites.add
@@ -1 +0,0 @@
-Display public favorites on user profiles
-\ No newline at end of file
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/registration-notice.add b/changelog.d/registration-notice.add
@@ -1 +0,0 @@
-Show a dedicated registration notice page when further action is required after registering
diff --git a/changelog.d/serviceworkers.change b/changelog.d/serviceworkers.change
@@ -1 +0,0 @@
-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.
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/show-recent-scrobble.skip b/changelog.d/show-recent-scrobble.skip
@@ -1 +0,0 @@
-Shows the most recent scrobble under each post when available
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/streaming-op-after-conn.change b/changelog.d/streaming-op-after-conn.change
@@ -0,0 +1 @@
+Authenticate and subscribe to streaming after connection
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/unreads-sync.fix b/changelog.d/unreads-sync.fix
@@ -1 +0,0 @@
-unread notifications should now properly catch up (eventually) in polling mode
diff --git a/changelog.d/upload-resizing.add b/changelog.d/upload-resizing.add
@@ -0,0 +1 @@
+Resize most kinds of images on upload.
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/video-poster.fix b/changelog.d/video-poster.fix
@@ -1 +0,0 @@
-Video posters on Safari
diff --git a/changelog.d/video-poster.update.skip b/changelog.d/video-poster.update.skip
@@ -1 +0,0 @@
-nothing
diff --git a/changelog.d/vue.change b/changelog.d/vue.change
@@ -0,0 +1 @@
+Upgraded Vue to version 3.5
diff --git a/changelog.d/vuex-devtools.skip b/changelog.d/vuex-devtools.skip
diff --git a/changelog.d/web-push-always.add b/changelog.d/web-push-always.add
@@ -1 +0,0 @@
-Added option to always "show" notifications when using web push for better compatibility with some browsers (chrome, edge, safari)
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/index.html b/index.html
@@ -3,12 +3,163 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
- <link rel="icon" type="image/png" href="/favicon.png">
+ <!-- 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-->
</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" />
diff --git a/package.json b/package.json
@@ -1,6 +1,6 @@
{
"name": "pleroma_fe",
- "version": "2.6.1",
+ "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,
@@ -10,13 +10,13 @@
"unit": "karma start test/unit/karma.conf.js --single-run",
"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 '**/*.scss' '**/*.vue'",
+ "test": "yarn run unit && yarn run e2e",
+ "stylelint": "yarn exec 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.21.5",
+ "@babel/runtime": "7.26.0",
"@chenfengyuan/vue-qrcode": "2.0.0",
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-regular-svg-icons": "6.4.0",
@@ -24,55 +24,56 @@
"@fortawesome/vue-fontawesome": "3.0.3",
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
- "@ruffle-rs/ruffle": "0.1.0-nightly.2024.3.17",
+ "@ruffle-rs/ruffle": "0.1.0-nightly.2025.1.13",
"@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.13",
+ "cropperjs": "1.6.2",
"escape-html": "1.0.3",
+ "hash-sum": "^2.0.0",
"js-cookie": "3.0.5",
"localforage": "1.10.0",
+ "pako": "^2.1.0",
"parse-link-header": "2.0.0",
- "phoenix": "1.7.7",
- "punycode.js": "2.3.0",
- "qrcode": "1.5.3",
+ "phoenix": "1.7.18",
+ "punycode.js": "2.3.1",
+ "qrcode": "1.5.4",
"querystring-es3": "0.2.1",
- "url": "0.11.0",
+ "url": "0.11.4",
"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": "3.5.13",
+ "vue-i18n": "10",
+ "vue-router": "4.5.0",
"vue-virtual-scroller": "^2.0.0-beta.7",
"vuex": "4.1.0"
},
"devDependencies": {
- "@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",
+ "@babel/core": "7.26.0",
+ "@babel/eslint-parser": "7.26.5",
+ "@babel/plugin-transform-runtime": "7.25.9",
+ "@babel/preset-env": "7.26.0",
+ "@babel/register": "7.25.9",
"@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.1",
- "@vue/compiler-sfc": "3.2.45",
- "@vue/test-utils": "2.2.8",
- "autoprefixer": "10.4.19",
- "babel-loader": "9.1.3",
+ "@vue/babel-plugin-jsx": "1.2.5",
+ "@vue/compiler-sfc": "3.5.13",
+ "@vue/test-utils": "2.4.6",
+ "autoprefixer": "10.4.20",
+ "babel-loader": "9.2.1",
"babel-plugin-lodash": "3.3.4",
- "chai": "4.3.7",
+ "chai": "4.5.0",
"chalk": "1.1.3",
"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",
+ "cross-spawn": "7.0.6",
+ "css-loader": "6.11.0",
"css-minimizer-webpack-plugin": "4.2.2",
"custom-event-polyfill": "1.0.7",
- "eslint": "8.33.0",
+ "eslint": "8.57.1",
"eslint-config-standard": "17.0.0",
"eslint-formatter-friendly": "7.0.0",
"eslint-plugin-import": "2.27.5",
@@ -81,15 +82,15 @@
"eslint-plugin-vue": "9.9.0",
"eslint-webpack-plugin": "3.2.0",
"eventsource-polyfill": "0.9.6",
- "express": "4.18.2",
- "function-bind": "1.1.1",
+ "express": "4.21.2",
+ "function-bind": "1.1.2",
"html-webpack-plugin": "5.5.1",
- "http-proxy-middleware": "2.0.6",
+ "http-proxy-middleware": "2.0.7",
"iso-639-1": "2.1.15",
"json-loader": "0.5.7",
- "karma": "6.4.2",
- "karma-coverage": "2.2.0",
- "karma-firefox-launcher": "2.1.2",
+ "karma": "6.4.4",
+ "karma-coverage": "2.2.1",
+ "karma-firefox-launcher": "2.1.3",
"karma-mocha": "2.0.1",
"karma-mocha-reporter": "2.2.5",
"karma-sinon-chai": "2.0.2",
@@ -108,7 +109,7 @@
"postcss-scss": "^4.0.6",
"sass": "1.60.0",
"sass-loader": "13.2.2",
- "selenium-server": "2.53.1",
+ "selenium-server": "3.141.59",
"semver": "7.3.8",
"serviceworker-webpack5-plugin": "2.0.0",
"shelljs": "0.8.5",
@@ -129,7 +130,7 @@
"webpack-merge": "0.20.0"
},
"engines": {
- "node": ">= 16.0.0",
- "npm": ">= 3.0.0"
- }
+ "node": ">= 16.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
@@ -44,16 +44,36 @@ export default {
data: () => ({
mobileActivePanel: 'timeline'
}),
+ watch: {
+ themeApplied (value) {
+ this.removeSplash()
+ },
+ layoutType (value) {
+ document.getElementById('modal').classList = ['-' + this.layoutType]
+ }
+ },
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)
+ document.getElementById('modal').classList = ['-' + this.layoutType]
+ },
+ 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 [
{
@@ -130,6 +150,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,10 +1,9 @@
// stylelint-disable rscss/class-format
/* stylelint-disable no-descending-specificity */
-@import "./variables";
@import "./panel";
:root {
- --navbar-height: 3.5rem;
+ --status-margin: 0.75em;
--post-line-height: 1.4;
// Z-Index stuff
--ZI_media_modal: 9000;
@@ -13,19 +12,25 @@
--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;
@@ -42,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
@@ -61,7 +84,7 @@ body {
&::-webkit-scrollbar-button {
--___bgPadding: 2px;
- color: var(--btnText);
+ color: var(--text);
background-repeat: no-repeat, no-repeat;
&:horizontal {
@@ -69,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);
}
}
@@ -87,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%;
}
}
@@ -104,15 +127,14 @@ 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 {
@@ -128,29 +150,15 @@ h4 {
i[class*="icon-"],
.svg-inline--fa,
.iconLetter {
- color: $fallback--icon;
- color: var(--icon, $fallback--icon);
-}
-
-.button-unstyled:hover,
-a:hover {
- > i[class*="icon-"],
- > .svg-inline--fa,
- > .iconLetter {
- color: var(--text);
- }
+ color: var(--icon);
}
nav {
z-index: var(--ZI_navbar);
- background-color: $fallback--fg;
- background-color: var(--topBar, $fallback--fg);
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
- box-shadow: 0 0 4px rgb(0 0 0 / 60%);
- box-shadow: var(--topBarShadow);
+ box-shadow: var(--shadow);
box-sizing: border-box;
height: var(--navbar-height);
+ font-size: calc(var(--navbar-height) / 3.5);
position: fixed;
}
@@ -195,16 +203,14 @@ nav {
grid-column: 1 / span 3;
grid-row: 1 / 1;
pointer-events: none;
- background-color: rgb(0 0 0 / 15%);
- background-color: var(--underlay, rgb(0 0 0 / 15%));
+ background-color: var(--underlay);
z-index: -1000;
}
.app-layout {
--miniColumn: 25rem;
--maxiColumn: 45rem;
- --columnGap: 1em;
- --status-margin: 0.75em;
+ --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)));
@@ -366,106 +372,123 @@ 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 {
+ line-height: var(--__line-height);
+ font-family: inherit;
+ font-weight: 400;
+ font-size: 100%;
+ cursor: pointer;
+
+ a,
+ button:not(.button-default) {
+ color: var(--text);
+ font-size: 100%;
+ }
+
+ &.disabled {
+ cursor: not-allowed;
+ }
+}
+
+.menu-item,
+.list-item {
+ display: block;
+ box-sizing: border-box;
+ border: none;
+ outline: none;
+ text-align: initial;
+ 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%;
+ 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 rgb(255 255 255 / 30%);
- box-shadow: var(--buttonHoverShadow);
- }
-
- &:active {
- box-shadow:
- 0 0 4px 0 rgb(255 255 255 / 30%),
- 0 1px 0 0 rgb(0 0 0 / 20%) inset,
- 0 -1px 0 0 rgb(255 255 255 / 20%) 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 rgb(255 255 255 / 30%),
- 0 1px 0 0 rgb(0 0 0 / 20%) inset,
- 0 -1px 0 0 rgb(255 255 255 / 20%) inset;
- box-shadow: var(--buttonPressedShadow);
-
- svg,
- i {
- color: $fallback--text;
- color: var(--btnToggledText, $fallback--text);
- }
+ &:hover + .menu-item-collapsible:not(.-expanded) + &,
+ &.-active + .menu-item-collapsible:not(.-expanded) + & {
+ border-top-width: 0;
+ }
+
+ &[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-family: inherit;
+ line-height: unset;
}
- &.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;
@@ -473,28 +496,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;
}
@@ -502,19 +520,10 @@ textarea,
--_padding: 0.5em;
border: none;
- border-radius: $fallback--inputRadius;
- border-radius: var(--inputRadius, $fallback--inputRadius);
- box-shadow:
- 0 1px 0 0 rgb(0 0 0 / 20%) inset,
- 0 -1px 0 0 rgb(255 255 255 / 20%) inset,
- 0 0 2px 0 rgb(0 0 0 / 100%) 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;
@@ -528,7 +537,6 @@ textarea,
&[disabled="disabled"],
&.disabled {
cursor: not-allowed;
- opacity: 0.5;
}
&[type="range"] {
@@ -543,9 +551,9 @@ textarea,
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 {
@@ -559,16 +567,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;
@@ -581,8 +587,9 @@ textarea,
&[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 {
@@ -600,13 +607,9 @@ textarea,
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;
@@ -622,17 +625,26 @@ 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 {
@@ -653,7 +665,7 @@ option {
li {
border: 1px solid var(--border);
- border-radius: var(--inputRadius);
+ border-radius: var(--roundness);
padding: 0.5em;
margin: 0.25em;
}
@@ -669,22 +681,23 @@ option {
display: inline-flex;
vertical-align: middle;
- button,
- .button-dropdown {
+ > *,
+ > * .button-default {
+ --_roundness-left: 0;
+ --_roundness-right: 0;
+
position: relative;
flex: 1 1 auto;
+ }
- &:not(:last-child),
- &:not(:last-child) .button-default {
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
- }
+ > *:first-child,
+ > *:first-child .button-default {
+ --_roundness-left: var(--roundness);
+ }
- &:not(:first-child),
- &:not(:first-child) .button-default {
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
- }
+ > *:last-child,
+ > *:last-child .button-default {
+ --_roundness-right: var(--roundness);
}
}
@@ -714,74 +727,64 @@ option {
overflow: hidden;
text-overflow: ellipsis;
- &.badge-notification {
- background-color: $fallback--cRed;
- background-color: var(--badgeNotification, $fallback--cRed);
- color: white;
- color: var(--badgeNotificationText, white);
+ &.-dot,
+ &.-counter {
+ margin: 0;
+ position: absolute;
}
-}
-.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 {
+ 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;
}
- &.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);
- }
+ &.-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;
}
- &.success {
- background-color: var(--alertSuccess, $fallback--alertWarning);
- color: var(--alertSuccessText, $fallback--text);
-
- .panel-heading & {
- color: var(--alertSuccessPanelText, $fallback--text);
- }
+ &.-neutral {
+ background-color: var(--badgeNeutral);
+ color: white;
+ color: var(--badgeNeutralText, white);
}
}
-.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 {
@@ -802,6 +805,10 @@ option {
&.iconLetter {
font-size: 1.1em;
}
+
+ &.svg-inline--fa {
+ vertical-align: -0.15em;
+ }
}
.fa-old-padding {
@@ -816,6 +823,11 @@ option {
opacity: 0.25;
}
+.timeago {
+ --link: var(--text);
+ --linkFaint: var(--textFaint);
+}
+
.login-hint {
text-align: center;
@@ -914,3 +926,174 @@ option {
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"
>
@@ -69,7 +70,7 @@
<PostStatusModal />
<EditStatusModal v-if="editingAvailable" />
<StatusHistoryModal v-if="editingAvailable" />
- <SettingsModal />
+ <SettingsModal :class="layoutModalClass" />
<UpdateNotification />
<GlobalNoticeList />
</div>
diff --git a/src/_variables.scss b/src/_variables.scss
@@ -1,36 +0,0 @@
-$main-color: #f58d2c;
-$main-background: white;
-$darkened-background: whitesmoke;
-
-$fallback--bg: #121a24;
-$fallback--fg: #182230;
-$fallback--faint: rgb(185 185 186 / 50%);
-$fallback--text: #b9b9ba;
-$fallback--link: #d8a070;
-$fallback--icon: #666;
-$fallback--lightBg: rgb(21 30 42);
-$fallback--lightText: #b9b9ba;
-$fallback--border: #222;
-$fallback--cRed: #f00;
-$fallback--cBlue: #0095ff;
-$fallback--cGreen: #0fa00f;
-$fallback--cOrange: orange;
-
-$fallback--alertError: rgb(211 16 20 / 50%);
-$fallback--alertWarning: rgb(111 111 20 / 50%);
-
-$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: 0 0 2px 0 rgb(0 0 0 / 100%),
- 0 1px 0 0 rgb(255 255 255 / 20%) inset,
- 0 -1px 0 0 rgb(0 0 0 / 20%) 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/boot/after_store.js b/src/boot/after_store.js
@@ -13,8 +13,7 @@ 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, applyConfig } 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'
@@ -123,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')
@@ -160,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 }) => {
@@ -243,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
@@ -255,6 +255,7 @@ const getNodeInfo = async ({ store }) => {
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') })
@@ -279,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
@@ -328,17 +330,10 @@ const setConfig = async ({ store }) => {
}
const checkOAuthToken = async ({ store }) => {
- // eslint-disable-next-line no-async-promise-executor
- 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 }) => {
@@ -355,24 +350,14 @@ const afterStoreSetup = async ({ store, i18n }) => {
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)
+ 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
@@ -381,7 +366,9 @@ const afterStoreSetup = async ({ store, i18n }) => {
getInstancePanel({ store }),
getNodeInfo({ store }),
getInstanceConfig({ store })
- ])
+ ]).catch(e => Promise.reject(e))
+
+ await store.dispatch('loadDrafts')
// Start fetching things that don't need to block the UI
store.dispatch('fetchMutes')
@@ -406,6 +393,13 @@ 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)
@@ -417,7 +411,6 @@ const afterStoreSetup = async ({ store, i18n }) => {
app.config.unwrapInjectedRef = true
app.mount('#app')
-
return app
}
diff --git a/src/boot/routes.js b/src/boot/routes.js
@@ -25,6 +25,10 @@ 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 Drafts from 'components/drafts/drafts.vue'
+import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue'
+import BookmarkFolderEdit from '../components/bookmark_folder_edit/bookmark_folder_edit.vue'
export default (store) => {
const validateAuthenticatedRoute = (to, from, next) => {
@@ -51,6 +55,7 @@ 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: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline },
{
name: 'remote-user-profile-acct',
path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',
@@ -78,13 +83,18 @@ export default (store) => {
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
{ name: 'about', path: '/about', component: About },
{ name: 'announcements', path: '/announcements', component: AnnouncementsPage },
+ { name: 'drafts', path: '/drafts', component: Drafts },
{ name: 'user-profile', path: '/users/:name', component: UserProfile },
{ name: 'legacy-user-profile', path: '/:name', component: UserProfile },
{ name: 'lists', path: '/lists', component: Lists },
{ name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline },
{ name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit },
{ name: 'lists-new', path: '/lists/new', component: ListsEdit },
- { name: 'edit-navigation', path: '/nav-edit', component: NavPanel, props: () => ({ forceExpand: true, forceEditMode: true }), beforeEnter: validateAuthenticatedRoute }
+ { name: '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/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue
@@ -11,14 +11,14 @@
<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') }}
@@ -31,34 +31,34 @@
<UserListMenu :user="user" />
<button
v-if="relationship.followed_by"
- class="btn button-default btn-block dropdown-item"
+ 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') }}
@@ -86,6 +86,7 @@
<i18n-t
keypath="user_card.block_confirm"
tag="span"
+ scope="global"
>
<template #user>
<span
@@ -107,6 +108,7 @@
<i18n-t
keypath="user_card.remove_follower_confirm"
tag="span"
+ scope="global"
>
<template #user>
<span
@@ -122,19 +124,12 @@
<script src="./account_actions.js"></script>
<style lang="scss">
-@import "../../variables";
-
.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.vue b/src/components/announcement/announcement.vue
@@ -99,16 +99,14 @@
<script src="./announcement.js"></script>
<style lang="scss">
-@import "../../variables";
-
.announcement {
- border-bottom: 1px solid var(--border, $fallback--border);
+ border-bottom: 1px solid var(--border);
border-radius: 0;
- padding: var(--status-margin, $status-margin);
+ padding: var(--status-margin);
.heading,
.body {
- margin-bottom: var(--status-margin, $status-margin);
+ margin-bottom: var(--status-margin);
}
.footer {
diff --git a/src/components/announcement_editor/announcement_editor.vue b/src/components/announcement_editor/announcement_editor.vue
@@ -3,7 +3,7 @@
<textarea
ref="textarea"
v-model="announcement.content"
- class="post-textarea"
+ class="input post-textarea"
rows="1"
cols="1"
:placeholder="$t('announcements.post_placeholder')"
@@ -14,6 +14,7 @@
<input
id="announcement-start-time"
v-model="announcement.startsAt"
+ class="input"
:type="announcement.allDay ? 'date' : 'datetime-local'"
:disabled="disabled"
>
@@ -23,6 +24,7 @@
<input
id="announcement-end-time"
v-model="announcement.endsAt"
+ class="input"
:type="announcement.allDay ? 'date' : 'datetime-local'"
:disabled="disabled"
>
@@ -32,8 +34,9 @@
id="announcement-all-day"
v-model="announcement.allDay"
:disabled="disabled"
- />
- <label for="announcement-all-day">{{ $t('announcements.all_day_prompt') }}</label>
+ >
+ {{ $t('announcements.all_day_prompt') }}
+ </Checkbox>
</span>
</div>
</template>
diff --git a/src/components/announcements_page/announcements_page.vue b/src/components/announcements_page/announcements_page.vue
@@ -1,9 +1,9 @@
<template>
<div class="panel panel-default announcements-page">
<div class="panel-heading">
- <span>
+ <h1 class="title">
{{ $t('announcements.page_header') }}
- </span>
+ </h1>
</div>
<div class="panel-body">
<section
@@ -61,15 +61,13 @@
<script src="./announcements_page.js"></script>
<style lang="scss">
-@import "../../variables";
-
.announcements-page {
.post-form {
- padding: var(--status-margin, $status-margin);
+ padding: var(--status-margin);
.heading,
.body {
- margin-bottom: var(--status-margin, $status-margin);
+ margin-bottom: var(--status-margin);
}
.post-button {
diff --git a/src/components/attachment/attachment.scss b/src/components/attachment/attachment.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.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;
@@ -126,23 +129,12 @@
.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: rgb(230 230 230 / 70%);
-
- .svg-inline--fa {
- color: rgb(0 0 0 / 60%);
- }
-
- &:hover .svg-inline--fa {
- color: rgb(0 0 0 / 90%);
- }
}
}
@@ -217,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;
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=""
>
@@ -175,7 +175,6 @@
:is="videoTag"
v-if="type === 'video' && !hidden"
class="video-container"
- :class="{ 'button-unstyled': 'isModal' }"
:href="attachment.url"
@click.stop.prevent="openModal"
>
@@ -253,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";
-
.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 rgb(0 0 0 / 60%);
- box-shadow: var(--panelShadow);
+ 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
@@ -17,8 +17,6 @@
<script src="./avatar_list.js"></script>
<style lang="scss">
-@import "../../variables";
-
.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.vue b/src/components/basic_user_card/basic_user_card.vue
@@ -47,9 +47,8 @@
display: flex;
flex: 1 0;
margin: 0;
- padding: 0.6em 1em;
- --emoji-size: 14px;
+ --emoji-size: 1em;
&-collapsed-content {
margin-left: 0.7em;
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 1 2 --text / 0.4',
+ '--buttonDefaultFocusGlow': 'shadow | 0 0 1 2 --link / 0.5',
+ '--buttonDefaultShadow': 'shadow | 0 0 2 #000000',
+ '--buttonDefaultBevel': 'shadow | $borderSide(#FFFFFF top 0.2 1), $borderSide(#000000 bottom 0.2 1)',
+ '--buttonPressedBevel': 'shadow | inset 0 0 4 #000000, $borderSide(#FFFFFF bottom 0.2 1), $borderSide(#000000 top 0.2 1)'
+ }
+ },
+ {
+ // 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: '--accent,-24.2',
+ shadow: ['--buttonDefaultShadow', '--buttonPressedBevel']
+ }
+ },
+ {
+ state: ['toggled', 'hover'],
+ directives: {
+ background: '--accent,-24.2',
+ shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel']
+ }
+ },
+ {
+ state: ['toggled', 'disabled'],
+ directives: {
+ background: '$blend(--accent 0.25 --parent)',
+ shadow: ['--buttonPressedBevel']
+ }
+ },
+ {
+ state: ['disabled'],
+ directives: {
+ background: '$blend(--accent 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,96 @@
+export default {
+ name: 'ButtonUnstyled',
+ selector: '.button-unstyled',
+ notEditable: true,
+ states: {
+ toggled: '.toggled',
+ disabled: ':disabled',
+ hover: ':hover:not(:disabled)',
+ focused: ':focus-within'
+ },
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Badge'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ 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.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;
- border-radius: 10px 10px 0 0;
- border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 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,8 +59,6 @@
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;
@@ -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>
@@ -76,6 +76,7 @@
:disable-sensitivity-checkbox="true"
:disable-submit="errorLoadingChat || !currentChat"
:disable-preview="true"
+ :disable-draft="true"
:optimistic-posting="true"
:post-handler="sendMessage"
:submit-on-enter="!mobileLayout"
@@ -95,6 +96,5 @@
<script src="./chat.js"></script>
<style lang="scss">
-@import "../../variables";
@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"
@@ -45,8 +45,6 @@
<script src="./chat_list.js"></script>
<style lang="scss">
-@import "../../variables";
-
.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 rgb(0 0 0 / 10%);
- }
-
.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;
}
@@ -73,11 +65,6 @@
}
}
- .Avatar {
- border-radius: $fallback--avatarAltRadius;
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
- }
-
.chat-preview-body {
--emoji-size: 1.4em;
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";
@import "./chat_list_item";
</style>
diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js
@@ -66,7 +66,6 @@ const ChatMessage = {
return this.message.attachments.length > 0
},
...mapState({
- betterShadow: state => state.interface.browserSupport.cssFilter,
currentUser: state => state.users.currentUser,
restrictedNicknames: state => state.instance.restrictedNicknames
}),
diff --git a/src/components/chat_message/chat_message.scss b/src/components/chat_message/chat_message.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.chat-message-wrapper {
&.hovered-message-chain {
.animated.Avatar {
@@ -27,12 +25,6 @@
.menu-icon {
cursor: pointer;
-
- &:hover,
- .extra-button-popover.open & {
- color: $fallback--text;
- color: var(--text, $fallback--text);
- }
}
.popover {
@@ -61,10 +53,12 @@
}
.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 {
@@ -97,8 +91,7 @@
.error {
.status-content.media-body,
.created-at {
- color: $fallback--cRed;
- color: var(--badgeNotification, $fallback--cRed);
+ color: var(--badgeNotification);
}
}
@@ -117,16 +110,6 @@
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;
}
@@ -137,22 +120,6 @@
}
.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);
- }
-
- .created-at {
- a {
- color: var(--chatMessageIncomingText, $fallback--text);
- }
- }
-
.chat-message-menu {
left: 0.4rem;
}
@@ -176,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
@@ -20,7 +20,6 @@
>
<UserAvatar
:compact="true"
- :better-shadow="betterShadow"
:user="author"
/>
</UserPopover>
@@ -53,7 +52,7 @@
<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") }}
diff --git a/src/components/chat_new/chat_new.scss b/src/components/chat_new/chat_new.scss
@@ -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";
@import "./chat_new";
</style>
diff --git a/src/components/chat_title/chat_title.vue b/src/components/chat_title/chat_title.vue
@@ -26,15 +26,13 @@
<script src="./chat_title.js"></script>
<style lang="scss">
-@import "../../variables";
-
.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
@@ -3,6 +3,13 @@
class="checkbox"
: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"
@@ -12,13 +19,15 @@
@change="$emit('update:modelValue', $event.target.checked)"
>
<i
- class="checkbox-indicator"
+ 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>
@@ -54,7 +63,6 @@ export default {
</script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.checkbox {
@@ -62,26 +70,34 @@ export default {
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: "✓";
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);
- 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;
@@ -91,28 +107,20 @@ export default {
box-sizing: border-box;
}
- &.disabled {
- .checkbox-indicator::before,
- .label {
- opacity: 0.5;
- }
-
- .label {
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+ .disabled {
+ .checkbox-indicator::before {
+ background-color: var(--background);
}
}
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);
+ color: var(--text);
}
}
@@ -122,8 +130,14 @@ export default {
}
}
- & > span {
- margin-left: 0.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";
-
.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: 0.2em 8px;
input {
+ color: var(--text);
background: none;
- color: $fallback--lightText;
- color: var(--inputText, $fallback--lightText);
border: none;
padding: 0;
margin: 0;
@@ -23,21 +27,39 @@
min-width: 3em;
padding: 0;
}
+ }
- &.nativeColor {
- flex: 0 0 2em;
- min-width: 2em;
- align-self: stretch;
- min-height: 100%;
+ .nativeColor {
+ cursor: pointer;
+ flex: 0 0 auto;
+ padding: 0;
+
+ 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: stretch;
- min-height: 100%;
+ min-height: 1.1em;
+ border-radius: var(--roundness);
+ }
+
+ .invalidIndicator {
+ background: transparent;
+ box-sizing: border-box;
+ border: 2px solid var(--cRed);
}
.transparentIndicator {
@@ -58,16 +80,26 @@
&::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,49 +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>
<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
@@ -84,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'],
@@ -102,18 +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" src="./color_input.scss"></style>
-
-<style lang="scss">
-.color-control {
- input.text-input {
- max-width: 7em;
- flex: 1;
- }
-}
-</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/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,8 +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;
@@ -96,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
@@ -56,7 +56,8 @@ const conversation = {
expanded: false,
threadDisplayStatusObject: {}, // id => 'showing' | 'hidden'
statusContentPropertiesObject: {},
- inlineDivePosition: null
+ inlineDivePosition: null,
+ loadStatusError: null
}
},
props: [
@@ -392,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) {
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"
@@ -28,7 +30,27 @@
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"
@@ -51,7 +73,7 @@
</template>
<template #text>
<span>
- {{ $tc('status.show_all_conversation', otherTopLevelCount, { numStatus: otherTopLevelCount }) }}
+ {{ $t('status.show_all_conversation', { numStatus: otherTopLevelCount }, otherTopLevelCount) }}
</span>
</template>
</i18n-t>
@@ -124,7 +146,7 @@
</template>
<template #text>
<span>
- {{ $tc('status.ancestor_follow', getReplies(status.id).length - 1, { numReplies: getReplies(status.id).length - 1 }) }}
+ {{ $t('status.ancestor_follow', { numReplies: getReplies(status.id, getReplies(status.id).length - 1).length - 1 }) }}
</span>
</template>
</i18n-t>
@@ -203,6 +225,7 @@
</div>
<div
v-else
+ class="Conversation -hidden"
:style="hiddenStyle"
/>
</template>
@@ -210,14 +233,17 @@
<script src="./conversation.js"></script>
<style lang="scss">
-@import "../../variables";
-
.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: 1px solid 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 */
@@ -227,20 +253,22 @@
}
.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: 1px solid 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 */
@@ -253,16 +281,17 @@
}
.thread-ancestor-dive-box-inner {
- padding: var(--status-margin, $status-margin);
+ padding: var(--status-margin);
}
.conversation-status {
- border-bottom: 1px solid var(--border, $fallback--border);
+ border-bottom: 1px solid var(--border);
border-radius: 0;
}
.thread-ancestor-has-other-replies .conversation-status,
- &:last-child .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,
&.-expanded .thread-tree .conversation-status {
@@ -270,20 +299,36 @@
}
.thread-ancestors + .thread-tree > .conversation-status {
- border-top: 1px solid var(--border, $fallback--border);
+ border-top: 1px solid var(--border);
}
/* expanded conversation in timeline */
&.status-fadein.-expanded .thread-body {
- border-left: 4px solid $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.scss b/src/components/desktop_nav/desktop_nav.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.DesktopNav {
width: 100%;
z-index: var(--ZI_navbar);
@@ -8,10 +6,6 @@
color: var(--inputTopbarText, var(--inputText));
}
- a {
- color: var(--topBarLink, $fallback--link);
- }
-
.inner-nav {
display: grid;
grid-template-rows: var(--navbar-height);
@@ -54,27 +48,7 @@
.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);
- }
-
- &.toggled {
- color: $fallback--text;
- color: var(--btnToggledTopBarText, $fallback--text);
- background-color: $fallback--fg;
- background-color: var(--btnToggledTopBar, $fallback--fg);
+ color: var(--text);
}
}
@@ -94,8 +68,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;
@@ -114,11 +87,6 @@
width: 2em;
height: 100%;
text-align: center;
-
- .svg-inline--fa {
- color: $fallback--link;
- color: var(--topBarLink, $fallback--link);
- }
}
.sitename {
diff --git a/src/components/dialog_modal/dialog_modal.js b/src/components/dialog_modal/dialog_modal.js
@@ -8,6 +8,11 @@ const DialogModal = {
default: () => {},
type: Function
}
+ },
+ computed: {
+ mobileCenter () {
+ return this.$store.getters.mergedConfig.modalMobileCenter
+ }
}
}
diff --git a/src/components/dialog_modal/dialog_modal.vue b/src/components/dialog_modal/dialog_modal.vue
@@ -1,6 +1,7 @@
<template>
<span
- :class="{ 'dark-overlay': darkOverlay }"
+ class="dialog-container"
+ :class="{ 'dark-overlay': darkOverlay, '-center-mobile': mobileCenter }"
@click.self.stop="onCancel()"
>
<div
@@ -8,11 +9,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 +26,6 @@
<script src="./dialog_modal.js"></script>
<style lang="scss">
-@import "../../variables";
-
// TODO: unify with other modals.
.dark-overlay {
&::before {
@@ -43,19 +42,24 @@
}
}
-.dialog-modal.panel {
+.dialog-container {
+ display: grid;
+ position: fixed;
top: 0;
- left: 50%;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ justify-content: center;
+ align-items: center;
+ justify-items: center;
+}
+
+.dialog-modal.panel {
max-height: 80vh;
max-width: 90vw;
- margin: 15vh auto;
- position: fixed;
- transform: translateX(-50%);
z-index: 2001;
cursor: default;
display: block;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
.dialog-modal-heading {
.title {
@@ -66,24 +70,57 @@
.dialog-modal-content {
margin: 0;
padding: 1rem;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
white-space: normal;
+ text-align: center;
}
.dialog-modal-footer {
margin: 0;
padding: 0.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);
- display: flex;
- justify-content: flex-end;
+ border-top: 1px solid var(--border);
+ display: grid;
+ justify-content: end;
+ grid-gap: 0.5em;
+ grid-template-columns: min-content;
+ grid-auto-columns: min-content;
+ grid-auto-flow: column dense;
+ height: auto;
button {
width: auto;
- margin-left: 0.5rem;
+ white-space: nowrap;
+ padding-left: 2em;
+ padding-right: 2em;
+ }
+ }
+}
+
+#modal.-mobile {
+ .dialog-container {
+ justify-content: stretch;
+ align-items: end;
+ justify-items: stretch;
+
+ &.-center-mobile {
+ align-items: center;
+ }
+ }
+
+ .dialog-modal.panel {
+ min-width: 100vw;
+ }
+
+ .dialog-modal-footer {
+ flex-direction: column;
+ justify-content: flex-end;
+ grid-template-columns: 1fr;
+ grid-auto-columns: none;
+ grid-auto-rows: auto;
+ grid-auto-flow: row dense;
+
+ button {
+ grid-column: 1;
+ height: 3em;
}
}
}
diff --git a/src/components/draft/draft.js b/src/components/draft/draft.js
@@ -0,0 +1,104 @@
+import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
+import EditStatusForm from 'src/components/edit_status_form/edit_status_form.vue'
+import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
+import StatusContent from 'src/components/status_content/status_content.vue'
+import Gallery from 'src/components/gallery/gallery.vue'
+import { cloneDeep } from 'lodash'
+
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faPollH
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faPollH
+)
+
+const Draft = {
+ components: {
+ PostStatusForm,
+ EditStatusForm,
+ ConfirmModal,
+ StatusContent,
+ Gallery
+ },
+ props: {
+ draft: {
+ type: Object,
+ required: true
+ }
+ },
+ data () {
+ return {
+ referenceDraft: cloneDeep(this.draft),
+ editing: false,
+ showingConfirmDialog: false
+ }
+ },
+ computed: {
+ relAttrs () {
+ if (this.draft.type === 'edit') {
+ return { statusId: this.draft.refId }
+ } else if (this.draft.type === 'reply') {
+ return { replyTo: this.draft.refId }
+ } else {
+ return {}
+ }
+ },
+ safeToSave () {
+ return this.draft.status ||
+ this.draft.files?.length ||
+ this.draft.hasPoll
+ },
+ postStatusFormProps () {
+ return {
+ draftId: this.draft.id,
+ ...this.relAttrs
+ }
+ },
+ refStatus () {
+ return this.draft.refId ? this.$store.state.statuses.allStatusesObject[this.draft.refId] : undefined
+ },
+ localCollapseSubjectDefault () {
+ return this.$store.getters.mergedConfig.collapseMessageWithSubject
+ },
+ nsfwClickthrough () {
+ if (!this.draft.nsfw) {
+ return false
+ }
+ if (this.draft.summary && this.localCollapseSubjectDefault) {
+ return false
+ }
+ return true
+ }
+ },
+ watch: {
+ editing (newVal) {
+ if (newVal) return
+ if (this.safeToSave) {
+ this.$store.dispatch('addOrSaveDraft', { draft: this.draft })
+ } else {
+ this.$store.dispatch('addOrSaveDraft', { draft: this.referenceDraft })
+ }
+ }
+ },
+ methods: {
+ toggleEditing () {
+ this.editing = !this.editing
+ },
+ abandon () {
+ this.showingConfirmDialog = true
+ },
+ doAbandon () {
+ this.$store.dispatch('abandonDraft', { id: this.draft.id })
+ .then(() => {
+ this.hideConfirmDialog()
+ })
+ },
+ hideConfirmDialog () {
+ this.showingConfirmDialog = false
+ }
+ }
+}
+
+export default Draft
diff --git a/src/components/draft/draft.vue b/src/components/draft/draft.vue
@@ -0,0 +1,171 @@
+<template>
+ <article class="Draft">
+ <div
+ v-if="!editing"
+ class="status-content"
+ >
+ <div>
+ <i18n-t
+ v-if="draft.type === 'reply' || draft.type === 'edit'"
+ tag="span"
+ :keypath="draft.type === 'reply' ? 'drafts.replying' : 'drafts.editing'"
+ >
+ <template #statusLink>
+ <router-link
+ class="faint-link"
+ :to="{ name: 'conversation', params: { id: draft.refId } }"
+ >
+ {{ refStatus ? refStatus.external_url : $t('drafts.unavailable') }}
+ </router-link>
+ </template>
+ </i18n-t>
+ <StatusContent
+ v-if="draft.refId && refStatus"
+ class="status-content"
+ :status="refStatus"
+ :compact="true"
+ />
+ </div>
+ <div class="status-preview">
+ <span class="status_content">
+ <p v-if="draft.spoilerText">
+ <i>
+ {{ draft.spoilerText }}:
+ </i>
+ </p>
+ <p v-if="draft.status">{{ draft.status }}</p>
+ <p v-else class="faint">{{ $t('drafts.empty') }}</p>
+ </span>
+ <gallery
+ v-if="draft.files?.length !== 0"
+ class="attachments media-body"
+ :compact="true"
+ :nsfw="nsfwClickthrough"
+ :attachments="draft.files"
+ :limit="1"
+ size="small"
+ @play="$emit('mediaplay', attachment.id)"
+ @pause="$emit('mediapause', attachment.id)"
+ />
+ <div
+ v-if="draft.poll.options"
+ class="poll-indicator-container"
+ :title="$t('drafts.poll_tooltip')"
+ >
+ <div class="poll-indicator">
+ <FAIcon
+ icon="poll-h"
+ size="3x"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ <div v-if="editing">
+ <PostStatusForm
+ v-if="draft.type !== 'edit'"
+ :hide-draft="true"
+ v-bind="postStatusFormProps"
+ />
+ <EditStatusForm
+ v-else
+ :hide-draft="true"
+ :params="postStatusFormProps"
+ />
+ </div>
+ <teleport to="#modal">
+ <confirm-modal
+ v-if="showingConfirmDialog"
+ :title="$t('drafts.abandon_confirm_title')"
+ :confirm-text="$t('drafts.abandon_confirm_accept_button')"
+ :cancel-text="$t('drafts.abandon_confirm_cancel_button')"
+ @accepted="doAbandon"
+ @cancelled="hideConfirmDialog"
+ >
+ {{ $t('drafts.abandon_confirm') }}
+ </confirm-modal>
+ </teleport>
+ <div class="actions">
+ <button
+ class="btn button-default"
+ :aria-expanded="editing"
+ @click.prevent.stop="toggleEditing"
+ >
+ {{ editing ? $t('drafts.save') : $t('drafts.continue') }}
+ </button>
+ <button
+ class="btn button-default"
+ @click.prevent.stop="abandon"
+ >
+ {{ $t('drafts.abandon') }}
+ </button>
+ </div>
+ </article>
+</template>
+
+<script src="./draft.js"></script>
+
+<style lang="scss">
+.Draft {
+ position: relative;
+
+ .status-content {
+ padding: 0.5em;
+ margin: 0.5em 0;
+ }
+
+ .status-preview {
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-auto-columns: 10em;
+ grid-auto-flow: column;
+ grid-gap: 0.5em;
+ align-items: start;
+ max-width: 100%;
+
+ p {
+ word-wrap: break-word;
+ white-space: normal;
+ overflow-x: hidden;
+ }
+
+ .poll-indicator-container {
+ border-radius: var(--roundness);
+ display: grid;
+ justify-items: center;
+ align-items: center;
+ align-self: start;
+ height: 0;
+ padding-bottom: 62.5%;
+ position: relative;
+ }
+
+ .poll-indicator {
+ box-sizing: border-box;
+ border: 1px solid var(--border);
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ display: grid;
+ justify-items: center;
+ align-items: center;
+ width: 100%;
+ height: 100%;
+ }
+ }
+
+ .actions {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-evenly;
+
+ .btn {
+ flex: 1;
+ margin-left: 1em;
+ margin-right: 1em;
+ }
+ }
+}
+</style>
diff --git a/src/components/draft_closer/draft_closer.js b/src/components/draft_closer/draft_closer.js
@@ -0,0 +1,52 @@
+import DialogModal from 'src/components/dialog_modal/dialog_modal.vue'
+
+const DraftCloser = {
+ data () {
+ return {
+ showing: false
+ }
+ },
+ components: {
+ DialogModal
+ },
+ emits: [
+ 'save',
+ 'discard'
+ ],
+ computed: {
+ action () {
+ if (this.$store.getters.mergedConfig.autoSaveDraft) {
+ return 'save'
+ } else {
+ return this.$store.getters.mergedConfig.unsavedPostAction
+ }
+ },
+ shouldConfirm () {
+ return this.action === 'confirm'
+ }
+ },
+ methods: {
+ requestClose () {
+ if (this.shouldConfirm) {
+ this.showing = true
+ } else if (this.action === 'save') {
+ this.save()
+ } else {
+ this.discard()
+ }
+ },
+ save () {
+ this.$emit('save')
+ this.showing = false
+ },
+ discard () {
+ this.$emit('discard')
+ this.showing = false
+ },
+ cancel () {
+ this.showing = false
+ }
+ }
+}
+
+export default DraftCloser
diff --git a/src/components/draft_closer/draft_closer.vue b/src/components/draft_closer/draft_closer.vue
@@ -0,0 +1,43 @@
+<template>
+ <teleport to="#modal">
+ <dialog-modal
+ v-if="showing"
+ v-body-scroll-lock="true"
+ class="confirm-modal"
+ :on-cancel="cancel"
+ >
+ <template #header>
+ <span>
+ {{ $t('post_status.close_confirm_title') }}
+ </span>
+ </template>
+
+ {{ $t('post_status.close_confirm') }}
+
+ <template #footer>
+ <button
+ class="btn button-default"
+ @click.prevent="save"
+ >
+ {{ $t('post_status.close_confirm_save_button') }}
+ </button>
+
+ <button
+ class="btn button-default"
+ @click.prevent="discard"
+ >
+ {{ $t('post_status.close_confirm_discard_button') }}
+ </button>
+
+ <button
+ class="btn button-default"
+ @click.prevent="cancel"
+ >
+ {{ $t('post_status.close_confirm_continue_composing_button') }}
+ </button>
+ </template>
+ </dialog-modal>
+ </teleport>
+</template>
+
+<script src="./draft_closer.js"></script>
diff --git a/src/components/drafts/drafts.js b/src/components/drafts/drafts.js
@@ -0,0 +1,16 @@
+import Draft from 'src/components/draft/draft.vue'
+import List from 'src/components/list/list.vue'
+
+const Drafts = {
+ components: {
+ Draft,
+ List
+ },
+ computed: {
+ drafts () {
+ return this.$store.getters.draftsArray
+ }
+ }
+}
+
+export default Drafts
diff --git a/src/components/drafts/drafts.vue b/src/components/drafts/drafts.vue
@@ -0,0 +1,36 @@
+<template>
+ <div class="Drafts">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <div class="title">
+ {{ $t('drafts.drafts') }}
+ </div>
+ </div>
+ <div class="panel-body">
+ <p v-if="drafts.length === 0">
+ {{ $t('drafts.no_drafts') }}
+ </p>
+ <List
+ v-else
+ :items="drafts"
+ :non-interactive="true"
+ >
+ <template #item="{ item: draft }">
+ <Draft
+ class="draft"
+ :draft="draft"
+ />
+ </template>
+ </List>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script src="./drafts.js"></script>
+
+<style lang="scss">
+.draft {
+ margin: 1em 0;
+}
+</style>
diff --git a/src/components/edit_status_form/edit_status_form.js b/src/components/edit_status_form/edit_status_form.js
@@ -0,0 +1,44 @@
+import PostStatusForm from '../post_status_form/post_status_form.vue'
+import statusPosterService from '../../services/status_poster/status_poster.service.js'
+
+const EditStatusForm = {
+ components: {
+ PostStatusForm
+ },
+ props: {
+ params: {
+ type: Object,
+ required: true
+ }
+ },
+ methods: {
+ requestClose () {
+ this.$refs.postStatusForm.requestClose()
+ },
+ doEditStatus ({ status, spoilerText, sensitive, media, contentType, poll }) {
+ const params = {
+ store: this.$store,
+ statusId: this.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
+ }
+ })
+ }
+ }
+}
+
+export default EditStatusForm
diff --git a/src/components/edit_status_form/edit_status_form.vue b/src/components/edit_status_form/edit_status_form.vue
@@ -0,0 +1,11 @@
+<template>
+ <PostStatusForm
+ ref="postStatusForm"
+ v-bind="params"
+ :post-handler="doEditStatus"
+ :disable-polls="true"
+ :disable-visibility-selector="true"
+ />
+</template>
+
+<script src="./edit_status_form.js"></script>
diff --git a/src/components/edit_status_modal/edit_status_modal.js b/src/components/edit_status_modal/edit_status_modal.js
@@ -1,11 +1,10 @@
-import PostStatusForm from '../post_status_form/post_status_form.vue'
+import EditStatusForm from '../edit_status_form/edit_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,
+ EditStatusForm,
Modal
},
data () {
@@ -43,30 +42,10 @@ const EditStatusModal = {
}
},
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.$refs.editStatusForm.requestClose()
+ },
+ doCloseModal () {
this.$store.dispatch('closeEditStatusModal')
}
}
diff --git a/src/components/edit_status_modal/edit_status_modal.vue b/src/components/edit_status_modal/edit_status_modal.vue
@@ -6,15 +6,17 @@
>
<div class="edit-form-modal-panel panel">
<div class="panel-heading">
- {{ $t('post_status.edit_status') }}
+ <h1 class="title">
+ {{ $t('post_status.edit_status') }}
+ </h1>
</div>
- <PostStatusForm
+ <EditStatusForm
+ ref="editStatusForm"
class="panel-body"
- v-bind="params"
- :post-handler="doEditStatus"
- :disable-polls="true"
- :disable-visibility-selector="true"
- @posted="closeModal"
+ :params="params"
+ @posted="doCloseModal"
+ @draft-done="doCloseModal"
+ @can-close="doCloseModal"
/>
</div>
</Modal>
diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
@@ -293,9 +293,12 @@ const EmojiInput = {
}))
this.highlighted = this.defaultCandidateIndex
this.$refs.screenReaderNotice.announce(
- this.$tc('tool_tip.autocomplete_available',
- this.suggestions.length,
- { number: this.suggestions.length }))
+ this.$t(
+ 'tool_tip.autocomplete_available',
+ { number: this.suggestions.length },
+ this.suggestions.length
+ )
+ )
}
},
methods: {
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
@@ -1,7 +1,7 @@
<template>
<div
ref="root"
- class="emoji-input"
+ class="input emoji-input"
:class="{ 'with-picker': !hideEmojiButton }"
>
<slot
@@ -68,9 +68,9 @@
v-for="(suggestion, index) in suggestions"
:id="suggestionItemId(index)"
:key="index"
- class="autocomplete-item"
+ class="menu-item autocomplete-item"
role="option"
- :class="{ highlighted: index === highlighted }"
+ :class="{ '-active': index === highlighted }"
:aria-label="autoCompleteItemLabel(suggestion)"
:aria-selected="index === highlighted"
@click.stop.prevent="onClick($event, suggestion)"
@@ -110,9 +110,8 @@
<script src="./emoji_input.js"></script>
<style lang="scss">
-@import "../../variables";
-
-.emoji-input {
+.input.emoji-input {
+ padding: 0;
display: flex;
flex-direction: column;
position: relative;
@@ -127,8 +126,7 @@
line-height: 24px;
&:hover i {
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
}
@@ -145,6 +143,12 @@
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;
}
&.with-picker input {
@@ -179,26 +183,28 @@
position: absolute;
}
- &-item {
+ &-item.menu-item {
display: flex;
- cursor: pointer;
- padding: 0.2em 0.4em;
- border-bottom: 1px solid rgb(0 0 0 / 40%);
- height: 32px;
+ padding-top: 0;
+ padding-bottom: 0;
.image {
- width: 32px;
- height: 32px;
- line-height: 32px;
+ 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;
- font-size: 32px;
- margin-right: 4px;
+ margin-right: var(--__horizontal-gap);
img {
- width: 32px;
- height: 32px;
+ width: calc(var(--__line-height) + var(--__vertical-gap) * 2);
+ height: calc(var(--__line-height) + var(--__vertical-gap) * 2);
object-fit: contain;
}
+
+ span {
+ font-size: var(--__line-height);
+ line-height: var(--__line-height);
+ }
}
.label {
@@ -216,17 +222,6 @@
line-height: 9px;
}
}
-
- &.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);
- }
}
}
</style>
diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js
@@ -97,7 +97,7 @@ const EmojiPicker = {
enableStickerPicker: {
required: false,
type: Boolean,
- default: false
+ default: true
},
hideCustomEmoji: {
required: false,
@@ -105,7 +105,11 @@ const EmojiPicker = {
default: false
}
},
- inject: ['popoversZLayer'],
+ inject: {
+ popoversZLayer: {
+ default: ''
+ }
+ },
data () {
return {
keyword: '',
@@ -120,6 +124,7 @@ const EmojiPicker = {
groupRefs: {},
emojiRefs: {},
filteredEmojiGroups: [],
+ emojiSize: 0,
width: 0
}
},
@@ -130,9 +135,43 @@ const EmojiPicker = {
Popover
},
methods: {
+ groupScroll (e) {
+ e.currentTarget.scrollLeft += e.deltaY + e.deltaX
+ },
+ updateEmojiSize () {
+ const css = window.getComputedStyle(this.$refs.popover.$el)
+ const fontSize = css.getPropertyValue('font-size') || '14px'
+ const emojiSize = css.getPropertyValue('--emojiSize') || '2.2rem'
+
+ const fontSizeUnit = fontSize.replace(/[0-9,.]+/, '')
+ const fontSizeValue = Number(fontSize.replace(/[^0-9,.]+/, ''))
+
+ const emojiSizeUnit = emojiSize.replace(/[0-9,.]+/, '')
+ const emojiSizeValue = Number(emojiSize.replace(/[^0-9,.]+/, ''))
+
+ let fontSizeMultiplier
+ if (fontSizeUnit.endsWith('em')) {
+ fontSizeMultiplier = fontSizeValue
+ } else {
+ fontSizeMultiplier = fontSizeValue / 14
+ }
+
+ let emojiSizeReal
+ if (emojiSizeUnit.endsWith('em')) {
+ emojiSizeReal = emojiSizeValue * fontSizeMultiplier * 14
+ } else {
+ emojiSizeReal = emojiSizeValue
+ }
+ console.log(emojiSizeReal)
+
+ const fullEmojiSize = emojiSizeReal + (2 * 0.2 * fontSizeMultiplier * 14)
+ this.emojiSize = fullEmojiSize
+ },
showPicker () {
this.$refs.popover.showPopover()
- this.onShowing()
+ this.$nextTick(() => {
+ this.onShowing()
+ })
},
hidePicker () {
this.$refs.popover.hidePopover()
@@ -160,7 +199,7 @@ const EmojiPicker = {
if (!this.keepOpen) {
this.$refs.popover.hidePopover()
}
- this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
+ this.$emit('emoji', { insertion: value, insertionUrl: emoji.imageUrl, keepOpen: this.keepOpen })
},
onScroll (startIndex, endIndex, visibleStartIndex, visibleEndIndex) {
const target = this.$refs['emoji-groups'].$el
@@ -224,6 +263,7 @@ const EmojiPicker = {
},
onShowing () {
const oldContentLoaded = this.contentLoaded
+ this.updateEmojiSize()
this.recalculateItemPerRow()
this.$nextTick(() => {
this.$refs.search.focus()
@@ -266,16 +306,21 @@ const EmojiPicker = {
},
computed: {
minItemSize () {
- return this.emojiHeight
+ return this.emojiSize
},
- emojiHeight () {
- return 32 + 4
+ // used to watch it
+ fontSize () {
+ this.$nextTick(() => {
+ this.updateEmojiSize()
+ })
+ return this.$store.getters.mergedConfig.fontSize
},
- emojiWidth () {
- return 32 + 4
+ emojiHeight () {
+ return this.emojiSize
},
itemPerRow () {
- return this.width ? Math.floor(this.width / this.emojiWidth - 1) : 6
+ console.log('CALC', this.emojiSize, this.width)
+ return this.width ? Math.floor(this.width / this.emojiSize) : 6
},
activeGroupView () {
return this.showingStickers ? '' : this.activeGroup
diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
@@ -1,47 +1,35 @@
-@import "../../variables";
-
-$emoji-picker-header-height: 36px;
-$emoji-picker-header-picture-width: 32px;
-$emoji-picker-header-picture-height: 32px;
-$emoji-picker-emoji-size: 32px;
-
.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;
- 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);
&-header-image {
display: inline-flex;
justify-content: center;
align-items: center;
- width: $emoji-picker-header-picture-width;
- max-width: $emoji-picker-header-picture-width;
- height: $emoji-picker-header-picture-height;
- max-height: $emoji-picker-header-picture-height;
+ width: var(--__emoji-picker-header);
+ max-width: var(--__emoji-picker-header);
+ height: var(--__emoji-picker-header);
+ max-height: var(--__emoji-picker-header);
.still-image {
- max-width: 100%;
- max-height: 100%;
- height: 100%;
- width: 100%;
+ 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,
.hide-custom-emoji {
- padding: 7px;
+ padding: 0.5em;
line-height: normal;
}
@@ -55,13 +43,14 @@ $emoji-picker-emoji-size: 32px;
}
.keep-open-label {
- padding: 0 7px;
+ padding: 0 0.5em;
display: flex;
}
.heading {
display: flex;
- padding: 10px 7px 5px;
+ flex-direction: column;
+ padding: 0.7em 0.5em 0;
}
.content {
@@ -76,14 +65,14 @@ $emoji-picker-emoji-size: 32px;
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;
}
@@ -92,30 +81,29 @@ $emoji-picker-emoji-size: 32px;
flex-basis: auto;
display: flex;
align-content: center;
+ scrollbar-width: thin;
&-item {
- padding: 0 7px;
+ padding: 0 0.5em;
cursor: pointer;
- font-size: 1.85em;
- width: $emoji-picker-header-picture-width;
- max-width: $emoji-picker-header-picture-width;
- height: $emoji-picker-header-picture-height;
- max-height: $emoji-picker-header-picture-height;
+ 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;
}
}
}
@@ -142,7 +130,7 @@ $emoji-picker-emoji-size: 32px;
.emoji {
&-search {
- padding: 5px;
+ padding-bottom: 0.5em;
flex: 0 0 auto;
input {
@@ -156,6 +144,7 @@ $emoji-picker-emoji-size: 32px;
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,
@@ -178,45 +167,60 @@ $emoji-picker-emoji-size: 32px;
}
}
- &-group {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- padding-left: 5px;
- justify-content: left;
-
- &-title {
- font-size: 0.85em;
- width: 100%;
- margin: 0;
-
- &.disabled {
- display: none;
- }
- }
- }
-
&-item {
- width: $emoji-picker-emoji-size;
- height: $emoji-picker-emoji-size;
+ width: var(--emoji-size);
+ height: var(--emoji-size);
box-sizing: border-box;
display: flex;
- line-height: $emoji-picker-emoji-size;
+ line-height: var(--emoji-size);
align-items: center;
justify-content: center;
- margin: 4px;
+ margin: 0.2em;
cursor: pointer;
.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: 24px;
+ font-size: calc(var(--emoji-size) * 0.8);
overflow: hidden;
}
}
+
+ &-group {
+ display: grid;
+ grid-template-columns: repeat(var(--__amount), 1fr);
+ align-items: center;
+ justify-items: center;
+ justify-content: center;
+ grid-template-rows: repeat(1, auto);
+
+ &.first-row {
+ grid-template-rows: repeat(2, auto);
+
+ .emoji-item {
+ grid-row: 2;
+ }
+ }
+
+ &-title {
+ font-size: 0.85em;
+ grid-column: span var(--__amount);
+ width: 100%;
+ margin: 0;
+ padding-left: 0.3em;
+
+ &.disabled {
+ display: none;
+ }
+ }
+ }
}
}
diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue
@@ -8,7 +8,19 @@
@close="onPopoverClosed"
>
<template #content>
- <div class="heading">
+ <div
+ class="heading"
+ >
+ <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>
<!--
Body scroll lock needs to be on every scrollable element on safari iOS.
Here we tell it to enable scrolling for this element.
@@ -18,14 +30,15 @@
ref="header"
v-body-scroll-lock="isInModal"
class="emoji-tabs"
+ @wheel.prevent="groupScroll"
>
<span
v-for="group in filteredEmojiGroups"
:ref="setGroupRef('group-header-' + group.id)"
:key="group.id"
- class="emoji-tabs-item"
+ class="button-unstyled emoji-tabs-item"
:class="{
- active: activeGroupView === group.id
+ toggled: activeGroupView === group.id
}"
:title="group.text"
role="button"
@@ -52,8 +65,8 @@
class="additional-tabs"
>
<span
- class="stickers-tab-icon additional-tabs-item"
- :class="{active: showingStickers}"
+ class="button-unstyled stickers-tab-icon additional-tabs-item"
+ :class="{toggled: showingStickers}"
:title="$t('emoji.stickers')"
@click.prevent="toggleStickers"
>
@@ -72,16 +85,6 @@
class="emoji-content"
:class="{hidden: showingStickers}"
>
- <div class="emoji-search">
- <input
- ref="search"
- v-model="keyword"
- type="text"
- class="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"
@@ -89,6 +92,7 @@
class="emoji-groups"
:class="groupsScrolledClass"
:min-item-size="minItemSize"
+ :buffer="minItemSize"
:items="emojiItems"
:emit-update="true"
@update="onScroll"
@@ -105,6 +109,8 @@
>
<div
class="emoji-group"
+ :class="{ ['first-row']: group.isFirstRow }"
+ :style="{ '--__amount': itemPerRow }"
>
<h6
v-if="group.isFirstRow"
diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
@@ -1,5 +1,6 @@
import UserAvatar from '../user_avatar/user_avatar.vue'
import UserListPopover from '../user_list_popover/user_list_popover.vue'
+import StillImage from 'src/components/still-image/still-image.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faPlus,
@@ -19,7 +20,8 @@ const EmojiReactions = {
name: 'EmojiReactions',
components: {
UserAvatar,
- UserListPopover
+ UserListPopover,
+ StillImage
},
props: ['status'],
data: () => ({
@@ -85,9 +87,12 @@ const EmojiReactions = {
'btn',
'button-default',
'emoji-reaction-count-button',
- { '-picked-reaction': this.reactedWith(reaction.name) }
+ {
+ '-picked-reaction': this.reactedWith(reaction.name),
+ toggled: this.reactedWith(reaction.name)
+ }
],
- 'aria-label': this.$tc('status.reaction_count_label', reaction.count, { num: reaction.count })
+ 'aria-label': this.$t('status.reaction_count_label', { num: reaction.count }, reaction.count)
}
}
}
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
@@ -10,7 +10,7 @@
v-bind="!loggedIn ? { href: remoteInteractionLink } : {}"
role="button"
class="emoji-reaction btn button-default"
- :class="{ '-picked-reaction': reactedWith(reaction.name) }"
+ :class="{ '-picked-reaction': reactedWith(reaction.name), toggled: reactedWith(reaction.name) }"
:title="reaction.url ? reaction.name : undefined"
:aria-pressed="reactedWith(reaction.name)"
@click="emojiOnClick(reaction.name, $event)"
@@ -18,12 +18,11 @@
<span
class="reaction-emoji"
>
- <img
+ <StillImage
v-if="reaction.url"
:src="reaction.url"
class="reaction-emoji-content"
- width="1em"
- >
+ />
<span
v-else
class="reaction-emoji reaction-emoji-content"
@@ -72,7 +71,6 @@
<script src="./emoji_reactions.js"></script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.EmojiReactions {
@@ -80,7 +78,7 @@
margin-top: 0.25em;
flex-wrap: wrap;
- --emoji-size: calc(1.25em * var(--emojiReactionsScale, 1));
+ --emoji-size: calc(var(--emojiSize, 1.25em) * var(--emojiReactionsScale, 1));
.emoji-reaction-container {
display: flex;
@@ -92,7 +90,6 @@
padding: 0;
.emoji-reaction-count-button {
- background-color: var(--btn);
margin: 0;
height: 100%;
border-top-left-radius: 0;
@@ -102,13 +99,6 @@
display: inline-flex;
justify-content: center;
align-items: center;
- color: $fallback--text;
- color: var(--btnText, $fallback--text);
-
- &.-picked-reaction {
- border: 1px solid var(--accent, $fallback--link);
- margin-right: -1px;
- }
}
}
}
@@ -131,17 +121,23 @@
display: flex;
justify-content: center;
align-items: center;
+
+ --_still_image-label-scale: 0.3;
}
.reaction-emoji-content {
max-width: 100%;
max-height: 100%;
- width: auto;
- height: auto;
+ width: var(--emoji-size);
+ height: var(--emoji-size);
line-height: inherit;
overflow: hidden;
font-size: calc(var(--emoji-size) * 0.8);
margin: 0;
+
+ img {
+ object-fit: contain;
+ }
}
&:focus {
@@ -149,18 +145,12 @@
}
.svg-inline--fa {
- color: $fallback--text;
- color: var(--btnText, $fallback--text);
+ color: var(--text);
}
&.-picked-reaction {
- border: 1px solid var(--accent, $fallback--link);
- margin-left: -1px; // offset the border, can't use inset shadows either
- margin-right: -1px;
-
.svg-inline--fa {
- color: $fallback--link;
- color: var(--accent, $fallback--link);
+ color: var(--accent);
}
}
@@ -176,8 +166,7 @@
@include focused-style {
.svg-inline--fa {
- color: $fallback--link;
- color: var(--accent, $fallback--link);
+ color: var(--accent);
}
.focus-marker {
diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js
@@ -1,6 +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,
@@ -36,7 +37,8 @@ const ExtraButtons = {
props: ['status'],
components: {
Popover,
- ConfirmModal
+ ConfirmModal,
+ StatusBookmarkFolderMenu
},
data () {
return {
@@ -145,6 +147,9 @@ const ExtraButtons = {
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}`
},
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
@@ -12,13 +12,13 @@
>
<template #content="{close}">
<div
+ :id="`popup-menu-${randomSeed}`"
class="dropdown-menu"
role="menu"
- :id="`popup-menu-${randomSeed}`"
>
<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"
>
@@ -29,7 +29,7 @@
</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"
>
@@ -40,7 +40,7 @@
</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"
@@ -52,7 +52,7 @@
</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"
@@ -65,7 +65,7 @@
<template v-if="canBookmark">
<button
v-if="!status.bookmarked"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
role="menuitem"
@click.prevent="bookmarkStatus"
@click="close"
@@ -77,7 +77,7 @@
</button>
<button
v-if="status.bookmarked"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
role="menuitem"
@click.prevent="unbookmarkStatus"
@click="close"
@@ -87,10 +87,14 @@
icon="bookmark"
/><span>{{ $t("status.unbookmark") }}</span>
</button>
+ <StatusBookmarkFolderMenu
+ v-if="status.bookmarked && bookmarkFolders"
+ :status="status"
+ />
</template>
<button
v-if="ownStatus && editingAvailable"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
role="menuitem"
@click.prevent="editStatus"
@click="close"
@@ -102,7 +106,7 @@
</button>
<button
v-if="isEdited && editingAvailable"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
role="menuitem"
@click.prevent="showStatusHistory"
@click="close"
@@ -114,7 +118,7 @@
</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"
@@ -125,7 +129,7 @@
/><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"
@@ -137,7 +141,7 @@
</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"
@@ -149,7 +153,7 @@
/><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"
@@ -201,7 +205,6 @@
<script src="./extra_buttons.js"></script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.ExtraButtons {
@@ -211,8 +214,7 @@
margin: -10px;
&:hover .svg-inline--fa {
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
}
diff --git a/src/components/extra_notifications/extra_notifications.vue b/src/components/extra_notifications/extra_notifications.vue
@@ -14,7 +14,7 @@
class="fa-scale-110 icon"
icon="comments"
/>
- {{ $tc('notifications.unread_chats', unreadChatCount, { num: unreadChatCount }) }}
+ {{ $t('notifications.unread_chats', { num: unreadChatCount }, unreadChatCount) }}
</router-link>
</div>
<div
@@ -31,7 +31,7 @@
class="fa-scale-110 icon"
icon="bullhorn"
/>
- {{ $tc('notifications.unread_announcements', unreadAnnouncementCount, { num: unreadAnnouncementCount }) }}
+ {{ $t('notifications.unread_announcements', { num: unreadAnnouncementCount }, unreadAnnouncementCount) }}
</router-link>
</div>
<div
@@ -48,7 +48,7 @@
class="fa-scale-110 icon"
icon="user-plus"
/>
- {{ $tc('notifications.unread_follow_requests', followRequestCount, { num: followRequestCount }) }}
+ {{ $t('notifications.unread_follow_requests', { num: followRequestCount }, followRequestCount) }}
</router-link>
</div>
<i18n-t
@@ -56,6 +56,7 @@
tag="span"
class="notification tip extra-notification"
keypath="notifications.configuration_tip"
+ scope="global"
>
<template #theSettings>
<button
@@ -80,8 +81,6 @@
<script src="./extra_notifications.js" />
<style lang="scss">
-@import "../../variables";
-
.ExtraNotifications {
width: 100%;
display: flex;
@@ -91,8 +90,7 @@
.notification {
width: 100%;
border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
display: flex;
flex-direction: column;
align-items: stretch;
diff --git a/src/components/favorite_button/favorite_button.vue b/src/components/favorite_button/favorite_button.vue
@@ -65,7 +65,6 @@
<script src="./favorite_button.js"></script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.FavoriteButton {
@@ -88,8 +87,7 @@
&:hover .svg-inline--fa,
&.-favorited .svg-inline--fa {
- color: $fallback--cOrange;
- color: var(--cOrange, $fallback--cOrange);
+ color: var(--cOrange);
}
@include unfocused-style {
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>
diff --git a/src/components/flash/flash.vue b/src/components/flash/flash.vue
@@ -42,8 +42,6 @@
<script src="./flash.js"></script>
<style lang="scss">
-@import "../../variables";
-
.Flash {
display: inline-block;
width: 100%;
diff --git a/src/components/follow_button/follow_button.vue b/src/components/follow_button/follow_button.vue
@@ -17,6 +17,7 @@
@cancelled="hideConfirmUnfollow"
>
<i18n-t
+ scope="global"
keypath="user_card.unfollow_confirm"
tag="span"
>
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,76 +1,157 @@
<template>
- <div
- class="font-control style-control"
- :class="{ custom: isCustom }"
- >
+ <div class="font-control">
<label
:id="name + '-label'"
- :for="preset === 'custom' ? name : name + '-font-switcher'"
+ :for="manualEntry ? name : name + '-font-switcher'"
class="label"
>
- {{ label }}
+ {{ $t('settings.style.themes3.font.label', { label }) }}
</label>
- <input
+ {{ ' ' }}
+ <Checkbox
v-if="typeof fallback !== 'undefined'"
+ class="font-checkbox"
:id="name + '-o'"
- :aria-labelledby="name + '-label'"
- class="opt exlcude-disabled visible-for-screenreader-only"
- 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'"
- :aria-hidden="true"
- />
- {{ ' ' }}
- <Select
- :id="name + '-font-switcher'"
- v-model="preset"
- :disabled="!present"
- class="font-switcher"
+ {{ $t('settings.style.themes3.define') }}
+ </Checkbox>
+ <div
+ v-if="modelValue?.family"
+ class="font-input"
>
- <option
- v-for="option in availableOptions"
- :key="option"
- :value="option"
+ <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>
+ </div>
</div>
</template>
<script src="./font_control.js"></script>
<style lang="scss">
-@import "../../variables";
-
.font-control {
- input.custom-font {
- min-width: 10em;
+ .custom-font {
+ min-width: 20em;
+ max-width: 20em;
}
- &.custom {
- /* TODO Should make proper joiners... */
- .font-switcher {
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
- }
+ .font-input {
+ margin-left: 2em;
+ margin-top: 0.5em;
+ }
- .custom-font {
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
- }
+ .font-checkbox {
+ margin-left: 1em;
}
}
+
+.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.vue b/src/components/gallery/gallery.vue
@@ -87,8 +87,6 @@
<script src='./gallery.js'></script>
<style lang="scss">
-@import "../../variables";
-
.Gallery {
.gallery-rows {
display: flex;
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,8 +25,6 @@
<script src="./global_notice_list.js"></script>
<style lang="scss">
-@import "../../variables";
-
.global-notice-list {
position: fixed;
top: calc(var(--navbar-height) + 0.5em);
@@ -52,48 +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/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.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), inset 0 0 2 #000000 / 0.15',
+ '--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/interactions/interactions.js b/src/components/interactions/interactions.js
@@ -3,6 +3,7 @@ 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'],
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')"
/>
diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
@@ -9,7 +9,7 @@
:key="index"
>
<label>
- {{ index === 0 ? $t('settings.primary_language') : $tc('settings.fallback_language', index, { index }) }}
+ {{ index === 0 ? $t('settings.primary_language') : $t('settings.fallback_language', { index }, index) }}
<Select
class="language-select"
:model-value="controlledLanguage[index]"
@@ -104,8 +104,6 @@ export default {
</script>
<style lang="scss">
-@import "../../variables";
-
.interface-language-switcher {
.language-select {
margin-right: 1em;
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";
-
.link-preview-card {
display: flex;
flex-direction: row;
@@ -51,8 +49,7 @@
width: 100%;
height: 100%;
object-fit: cover;
- border-radius: $fallback--attachmentRadius;
- border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
+ border-radius: var(--roundness);
}
}
@@ -82,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
@@ -7,6 +7,7 @@
v-for="item in items"
:key="getKey(item)"
class="list-item"
+ :class="[getClass(item), nonInteractive ? '-non-interactive' : '']"
role="listitem"
>
<slot
@@ -33,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";
-
-.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.vue b/src/components/lists/lists.vue
@@ -2,7 +2,9 @@
<div class="Lists panel panel-default">
<div class="panel-heading">
<div class="title">
- {{ $t('lists.lists') }}
+ <h1 class="title">
+ {{ $t('lists.lists') }}
+ </h1>
</div>
<router-link
:to="{ name: 'lists-new' }"
diff --git a/src/components/lists_card/lists_card.vue b/src/components/lists_card/lists_card.vue
@@ -21,8 +21,6 @@
<script src="./lists_card.js"></script>
<style lang="scss">
-@import "../../variables";
-
.list-card {
display: flex;
}
@@ -35,18 +33,6 @@
.button-list-edit {
margin: 0;
padding: 1em;
- 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);
- }
+ color: var(--link);
}
</style>
diff --git a/src/components/lists_edit/lists_edit.vue b/src/components/lists_edit/lists_edit.vue
@@ -17,6 +17,7 @@
<i18n-t
v-if="id"
keypath="lists.editing_list"
+ scope="global"
>
<template #listTitle>
{{ title }}
@@ -25,6 +26,7 @@
<i18n-t
v-else
keypath="lists.creating_list"
+ scope="global"
/>
</div>
</div>
@@ -36,6 +38,7 @@
id="list-edit-title"
ref="title"
v-model="titleDraft"
+ class="input"
>
<button
v-if="id"
@@ -164,8 +167,6 @@
<script src="./lists_edit.js"></script>
<style lang="scss">
-@import "../../variables";
-
.ListEdit {
--panel-body-padding: 0.5em;
diff --git a/src/components/lists_user_search/lists_user_search.vue b/src/components/lists_user_search/lists_user_search.vue
@@ -10,6 +10,7 @@
<input
ref="search"
v-model="query"
+ class="input"
:placeholder="$t('lists.search')"
@input="onInput"
>
@@ -27,8 +28,6 @@
<script src="./lists_user_search.js"></script>
<style lang="scss">
-@import "../../variables";
-
.ListsUserSearch {
.input-wrap {
display: flex;
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>
@@ -93,8 +95,6 @@
<script src="./login_form.js"></script>
<style lang="scss">
-@import "../../variables";
-
.login-form {
display: flex;
flex-direction: column;
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
@@ -98,7 +98,7 @@
<span
class="counter"
>
- {{ $tc('media_modal.counter', currentIndex + 1, { current: currentIndex + 1, total: media.length }) }}
+ {{ $t('media_modal.counter', { current: currentIndex + 1, total: media.length }, currentIndex + 1) }}
</span>
<span
v-if="loading"
diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js
@@ -28,7 +28,81 @@ const mediaUpload = {
this.$refs.input.click()
}
},
- uploadFile (file) {
+ async resizeImage (file) {
+ // Skip if not an image or if it's a GIF
+ if (!file.type.startsWith('image/') || file.type === 'image/gif') {
+ return file
+ }
+
+ // For PNGs, check if animated
+ if (file.type === 'image/png') {
+ const isAnimated = await this.isAnimatedPng(file)
+ if (isAnimated) {
+ return file
+ }
+ }
+
+ return new Promise((resolve) => {
+ const img = new Image()
+ img.onload = () => {
+ // Calculate new dimensions
+ let width = img.width
+ let height = img.height
+ const maxSize = 2048
+
+ if (width > maxSize || height > maxSize) {
+ if (width > height) {
+ height = Math.round((height * maxSize) / width)
+ width = maxSize
+ } else {
+ width = Math.round((width * maxSize) / height)
+ height = maxSize
+ }
+ }
+
+ // Create canvas and resize
+ const canvas = document.createElement('canvas')
+ canvas.width = width
+ canvas.height = height
+ const ctx = canvas.getContext('2d')
+ ctx.drawImage(img, 0, 0, width, height)
+
+ // Check WebP support by trying to create a WebP canvas
+ const testCanvas = document.createElement('canvas')
+ const supportsWebP = testCanvas.toDataURL('image/webp').startsWith('data:image/webp')
+
+ // Convert to WebP if supported, otherwise JPEG
+ const type = supportsWebP ? 'image/webp' : 'image/jpeg'
+ const extension = supportsWebP ? '.webp' : '.jpg'
+
+ // Remove the original extension and add new one
+ const newFileName = file.name.replace(/\.[^/.]+$/, '') + extension
+
+ canvas.toBlob((blob) => {
+ resolve(new File([blob], newFileName, {
+ type: type,
+ lastModified: Date.now()
+ }))
+ }, type, 0.85)
+ }
+ img.src = URL.createObjectURL(file)
+ })
+ },
+ async isAnimatedPng (file) {
+ const buffer = await file.arrayBuffer()
+ const view = new Uint8Array(buffer)
+ // Look for animated PNG chunks (acTL)
+ for (let i = 0; i < view.length - 8; i++) {
+ if (view[i] === 0x61 && // a
+ view[i + 1] === 0x63 && // c
+ view[i + 2] === 0x54 && // T
+ view[i + 3] === 0x4C) { // L
+ return true
+ }
+ }
+ return false
+ },
+ async uploadFile (file) {
const self = this
const store = this.$store
if (file.size > store.state.instance.uploadlimit) {
@@ -37,8 +111,11 @@ const mediaUpload = {
self.$emit('upload-failed', 'file_too_big', { filesize: filesize.num, filesizeunit: filesize.unit, allowedsize: allowedsize.num, allowedsizeunit: allowedsize.unit })
return
}
+
+ // Resize image if needed
+ const processedFile = await this.resizeImage(file)
const formData = new FormData()
- formData.append('file', file)
+ formData.append('file', processedFile)
self.$emit('uploading')
self.uploadCount++
diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue
@@ -36,8 +36,6 @@
<script src="./media_upload.js"></script>
<style lang="scss">
-@import "../../variables";
-
.media-upload {
.hidden-input-file {
display: none;
diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js
@@ -53,7 +53,9 @@ const MentionLink = {
this.$router.push(link)
},
handleSelection () {
- this.hasSelection = document.getSelection().containsNode(this.$refs.full, true)
+ if (this.$refs.full) {
+ this.hasSelection = document.getSelection().containsNode(this.$refs.full, true)
+ }
}
},
mounted () {
diff --git a/src/components/mention_link/mention_link.scss b/src/components/mention_link/mention_link.scss
@@ -1,10 +1,7 @@
-@import "../../variables";
-
.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;
@@ -61,8 +58,10 @@
}
&.-has-selection {
- color: var(--alertNeutralText, $fallback--text);
- background-color: var(--alertNeutral, $fallback--fg);
+ --color: var(--selectionText);
+ --link: var(--selectionText);
+
+ background-color: var(--selectionBackground);
}
.at {
@@ -102,7 +101,7 @@
}
.serverName.-faded {
- color: var(--faintLink, $fallback--link);
+ color: var(--linkFaint);
}
}
diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue
@@ -22,7 +22,7 @@
:class="classnames"
>
<a
- class="short button-unstyled"
+ class="short"
:class="{ '-with-tooltip': shouldShowTooltip }"
:href="url"
@click.prevent="onClick"
diff --git a/src/components/mentions_line/mentions_line.vue b/src/components/mentions_line/mentions_line.vue
@@ -22,13 +22,13 @@
/>
</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') }}
diff --git a/src/components/menu_item.style.js b/src/components/menu_item.style.js
@@ -0,0 +1,113 @@
+export default {
+ name: 'MenuItem',
+ selector: '.menu-item',
+ validInnerComponents: [
+ 'Text',
+ 'Icon',
+ 'Input',
+ 'Border',
+ 'ButtonUnstyled',
+ 'Badge',
+ 'Avatar'
+ ],
+ states: {
+ hover: ':hover:not(.disabled)',
+ active: '.-active',
+ disabled: '.disabled'
+ },
+ 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'
+ }
+ },
+ {
+ component: 'Text',
+ parent: {
+ component: 'MenuItem',
+ state: ['disabled']
+ },
+ directives: {
+ textOpacity: 0.25,
+ textOpacityMode: 'blend'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'MenuItem',
+ state: ['disabled']
+ },
+ directives: {
+ textOpacity: 0.25,
+ textOpacityMode: 'blend'
+ }
+ }
+ ]
+}
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>
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.vue b/src/components/mobile_nav/mobile_nav.vue
@@ -20,7 +20,7 @@
/>
<div
v-if="(unreadChatCount && !chatsPinned) || unreadAnnouncementCount"
- class="alert-dot"
+ class="badge -dot -notification"
/>
</button>
<NavigationPins class="pins" />
@@ -37,26 +37,26 @@
/>
<div
v-if="unseenNotificationsCount"
- class="alert-dot"
+ class="badge -dot -notification"
/>
</button>
</div>
</nav>
<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">
+ <div class="panel-heading mobile-notifications-header">
+ <h1 class="title">
{{ $t('notifications.notifications') }}
<span
v-if="unseenCountBadgeText"
- class="badge badge-notification unseen-count"
+ class="badge -notification unseen-count"
>{{ unseenCountBadgeText }}</span>
- </span>
+ </h1>
<span class="spacer" />
<button
v-if="notificationsAtTop"
@@ -123,21 +123,19 @@
<script src="./mobile_nav.js"></script>
<style lang="scss">
-@import "../../variables";
-
.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);
}
}
@@ -165,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;
@@ -185,13 +170,13 @@
position: fixed;
top: 0;
left: 0;
- box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
- box-shadow: var(--panelShadow);
+ box-shadow: var(--shadow);
transition-property: transform;
transition-duration: 0.25s;
transform: translateX(0);
z-index: var(--ZI_navbar);
-webkit-overflow-scrolling: touch;
+ background: var(--background);
&.-closed {
transform: translateX(100%);
@@ -205,14 +190,10 @@
justify-content: space-between;
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: 0 0 4px rgb(0 0 0 / 60%);
- box-shadow: var(--topBarShadow);
+ box-shadow: var(--shadow);
.spacer {
flex: 1;
@@ -221,6 +202,9 @@
.title {
font-size: 1.3em;
margin-left: 0.6em;
+ white-space: nowrap;
+ overflow-x: hidden;
+ text-overflow: ellipsis;
}
}
@@ -233,15 +217,11 @@
}
.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;
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
@@ -13,8 +13,6 @@
<script src="./mobile_post_status_button.js"></script>
<style lang="scss">
-@import "../../variables";
-
.MobilePostButton {
&.button-default {
width: 5em;
@@ -25,8 +23,6 @@
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;
@@ -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/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.vue b/src/components/moderation_tools/moderation_tools.vue
@@ -12,13 +12,13 @@
<div class="dropdown-menu">
<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') }}
@@ -31,14 +31,14 @@
</span>
<button
v-if="canChangeActivationState"
- class="button-default dropdown-item"
+ 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
v-if="canDeleteAccount"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="deleteUserDialog(true)"
>
{{ $t('user_card.admin_menu.delete_account') }}
@@ -50,74 +50,74 @@
/>
<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') }}
@@ -166,8 +166,6 @@
<script src="./moderation_tools.js"></script>
<style lang="scss">
-@import "../../variables";
-
.moderation-tools-popover {
height: 100%;
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";
@import "./mrf_transparency_panel";
</style>
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
@@ -1,3 +1,4 @@
+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'
@@ -19,7 +20,8 @@ import {
faInfoCircle,
faStream,
faList,
- faBullhorn
+ faBullhorn,
+ faFilePen
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -34,13 +36,15 @@ library.add(
faInfoCircle,
faStream,
faList,
- faBullhorn
+ faBullhorn,
+ faFilePen
)
const NavPanel = {
props: ['forceExpand', 'forceEditMode'],
created () {
},
components: {
+ BookmarkFoldersMenuContent,
ListsMenuContent,
NavigationEntry,
NavigationPins,
@@ -51,6 +55,7 @@ const NavPanel = {
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 }))
}
@@ -62,6 +67,9 @@ const NavPanel = {
toggleLists () {
this.showLists = !this.showLists
},
+ toggleBookmarkFolders () {
+ this.showBookmarkFolders = !this.showBookmarkFolders
+ },
toggleEditMode () {
this.editMode = !this.editMode
},
@@ -90,7 +98,8 @@ const NavPanel = {
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
+ collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav,
+ bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable
}),
timelinesItems () {
return filterNavigation(
@@ -102,7 +111,8 @@ const NavPanel = {
hasAnnouncements: this.supportsAnnouncements,
isFederating: this.federating,
isPrivate: this.privateMode,
- currentUser: this.currentUser
+ currentUser: this.currentUser,
+ supportsBookmarkFolders: this.bookmarkFolders
}
)
},
@@ -116,7 +126,8 @@ const NavPanel = {
hasAnnouncements: this.supportsAnnouncements,
isFederating: this.federating,
isPrivate: this.privateMode,
- currentUser: this.currentUser
+ currentUser: this.currentUser,
+ supportsBookmarkFolders: this.bookmarkFolders
}
)
},
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
@@ -37,7 +37,8 @@
</NavigationEntry>
<div
v-show="showTimelines"
- class="timelines-background"
+ class="timelines-background menu-item-collapsible"
+ :class="{ '-expanded': showTimelines }"
>
<div class="timelines">
<NavigationEntry
@@ -57,12 +58,11 @@
>
<router-link
:title="$t('lists.manage_lists')"
- class="extra-button"
+ class="button-unstyled extra-button"
:to="{ name: 'lists' }"
@click.stop
>
<FAIcon
- class="extra-button"
fixed-width
icon="wrench"
/>
@@ -75,7 +75,8 @@
</NavigationEntry>
<div
v-show="showLists"
- class="timelines-background"
+ class="timelines-background menu-item-collapsible"
+ :class="{ '-expanded': showLists }"
>
<ListsMenuContent
:show-pin="editMode || forceEditMode"
@@ -83,6 +84,39 @@
/>
</div>
<NavigationEntry
+ v-if="currentUser && bookmarkFolders"
+ :show-pin="false"
+ :item="{ icon: 'bookmark', label: 'nav.bookmarks' }"
+ :aria-expanded="showBookmarkFolders ? 'true' : 'false'"
+ @click="toggleBookmarkFolders"
+ >
+ <router-link
+ :title="$t('bookmarks.manage_bookmark_folders')"
+ class="button-unstyled extra-button"
+ :to="{ name: 'bookmark-folders' }"
+ @click.stop
+ >
+ <FAIcon
+ fixed-width
+ icon="wrench"
+ />
+ </router-link>
+ <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"
@@ -91,7 +125,7 @@
<NavigationEntry
v-if="!forceEditMode && currentUser"
:show-pin="false"
- :item="{ label: editMode ? $t('nav.edit_finish') : $t('nav.edit_pinned'), icon: editMode ? 'check' : 'wrench' }"
+ :item="{ labelRaw: editMode ? $t('nav.edit_finish') : $t('nav.edit_pinned'), icon: editMode ? 'check' : 'wrench' }"
@click="toggleEditMode"
/>
</ul>
@@ -102,12 +136,10 @@
<script src="./nav_panel.js"></script>
<style lang="scss">
-@import "../../variables";
-
.NavPanel {
.panel {
overflow: hidden;
- box-shadow: var(--panelShadow);
+ box-shadow: var(--shadow);
}
ul {
@@ -116,56 +148,14 @@
padding: 0;
}
- li {
- position: relative;
- border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- }
-
- > li {
- &: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;
- }
-
.navigation-chevron {
margin-left: 0.8em;
margin-right: 0.8em;
font-size: 1.1em;
}
- .timelines-chevron {
- margin-left: 0.8em;
- font-size: 1.1em;
- }
-
.timelines-background {
padding: 0 0 0 0.6em;
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- }
-
- .timelines {
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
}
.nav-panel-heading {
diff --git a/src/components/navigation/filter.js b/src/components/navigation/filter.js
@@ -1,4 +1,4 @@
-export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFederating, isPrivate, currentUser }) => {
+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
@@ -7,6 +7,7 @@ export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFede
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
})
}
@@ -17,3 +18,12 @@ export const getListEntries = state => state.lists.allLists.map(list => ({
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
@@ -1,11 +1,16 @@
+// routes that take :username property
export const USERNAME_ROUTES = new Set([
- 'bookmarks',
'dms',
'interactions',
'notifications',
'chat',
- 'chats',
- 'user-profile'
+ 'chats'
+])
+
+// routes that take :name property
+export const NAME_ROUTES = new Set([
+ 'user-profile',
+ 'legacy-user-profile'
])
export const TIMELINES = {
@@ -32,7 +37,8 @@ export const TIMELINES = {
bookmarks: {
route: 'bookmarks',
icon: 'bookmark',
- label: 'nav.bookmarks'
+ label: 'nav.bookmarks',
+ criteria: ['!supportsBookmarkFolders']
},
favorites: {
routeObject: { name: 'user-profile', query: { tab: 'favorites' } },
@@ -56,6 +62,7 @@ export const ROOT_ITEMS = {
route: 'chats',
icon: 'comments',
label: 'nav.chats',
+ badgeStyle: 'notification',
badgeGetter: 'unreadChatCount',
criteria: ['chats']
},
@@ -63,6 +70,7 @@ export const ROOT_ITEMS = {
route: 'friend-requests',
icon: 'user-plus',
label: 'nav.friend_requests',
+ badgeStyle: 'notification',
criteria: ['lockedUser'],
badgeGetter: 'followRequestCount'
},
@@ -76,8 +84,16 @@ export const ROOT_ITEMS = {
route: 'announcements',
icon: 'bullhorn',
label: 'nav.announcements',
+ badgeStyle: 'notification',
badgeGetter: 'unreadAnnouncementCount',
criteria: ['announcements']
+ },
+ drafts: {
+ route: 'drafts',
+ icon: 'file-pen',
+ label: 'nav.drafts',
+ badgeStyle: 'neutral',
+ badgeGetter: 'draftCount'
}
}
@@ -93,7 +109,9 @@ export function routeTo (item, currentUser) {
}
if (USERNAME_ROUTES.has(route.name)) {
- route.params = { username: currentUser.screen_name, name: currentUser.screen_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.vue b/src/components/navigation/navigation_entry.vue
@@ -1,7 +1,6 @@
<template>
<OptionalRouterLink
v-slot="{ isActive, href, navigate } = {}"
- ass="ass"
:to="routeTo"
>
<li
@@ -11,7 +10,7 @@
>
<component
:is="routeTo ? 'a' : 'button'"
- class="main-link button-unstyled"
+ class="main-link"
:href="href"
@click="navigate"
>
@@ -23,11 +22,25 @@
: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-if="item.iconLetter"
- class="icon iconLetter fa-scale-110 menu-icon"
- >{{ item.iconLetter }}
+ 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>
@@ -35,7 +48,8 @@
<slot />
<div
v-if="item.badgeGetter && getters[item.badgeGetter]"
- class="badge badge-notification"
+ class="badge"
+ :class="[`-${item.badgeStyle}`]"
>
{{ getters[item.badgeGetter] }}
</div>
@@ -63,73 +77,63 @@
<script src="./navigation_entry.js"></script>
<style lang="scss">
-@import "../../variables";
+.NavigationEntry.menu-item {
+ --__line-height: 2.5em;
+ --__horizontal-gap: 0.5em;
+ --__vertical-gap: 0.4em;
-.NavigationEntry {
- display: flex;
- box-sizing: border-box;
+ padding: var(--__vertical-gap) var(--__horizontal-gap);
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-auto-columns: var(--__line-height);
+ grid-auto-flow: column;
+ grid-gap: var(--__horizontal-gap);
align-items: baseline;
- height: 3.5em;
- line-height: 3.5em;
- padding: 0 1em;
- width: 100%;
- color: $fallback--link;
- color: var(--link, $fallback--link);
- .timelines-chevron {
- margin-right: 0;
+ &[aria-expanded] {
+ padding-right: var(--__horizontal-gap);
}
.main-link {
- flex: 1;
+ line-height: var(--__line-height);
+ box-sizing: border-box;
}
.menu-icon {
- margin-right: 0.8em;
+ line-height: var(--__line-height);
+ padding: 0;
+ width: var(--__line-height);
+ margin-right: var(--__horizontal-gap);
}
+ .timelines-chevron,
.extra-button {
- width: 3em;
+ line-height: var(--__line-height);
+ width: 100%;
+ padding: 0;
text-align: center;
-
- &:last-child {
- margin-right: -0.8em;
- }
}
- &: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);
-
- .menu-icon {
- --icon: var(--text, $fallback--icon);
- }
+ .badge {
+ justify-self: center;
}
- &.-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);
+ .iconEmoji {
+ display: inline-block;
+ text-align: center;
+ object-fit: contain;
+ vertical-align: middle;
+ height: var(--__line-height);
+ width: var(--__line-height);
- .menu-icon {
- --icon: var(--text, $fallback--icon);
+ > span {
+ font-size: 1.5rem;
}
+ }
- &:hover {
- text-decoration: underline;
- }
+ img.iconEmoji {
+ padding: 0.25rem;
+ box-sizing: border-box;
}
}
</style>
diff --git a/src/components/navigation/navigation_pins.vue b/src/components/navigation/navigation_pins.vue
@@ -3,7 +3,8 @@
<router-link
v-for="item in pinnedList"
:key="item.name"
- class="pinned-item"
+ class="button-unstyled pinned-item"
+ active-class="toggled"
:to="getRouteTo(item)"
:title="item.labelRaw || $t(item.label)"
>
@@ -18,7 +19,8 @@
>{{ item.iconLetter }}</span>
<div
v-if="item.badgeGetter && getters[item.badgeGetter]"
- class="alert-dot"
+ class="badge -dot"
+ :class="[`-${item.badgeStyle}`]"
/>
</router-link>
</span>
@@ -27,23 +29,18 @@
<script src="./navigation_pins.js"></script>
<style lang="scss">
-@import "../../variables";
-
.NavigationPins {
display: flex;
flex-wrap: wrap;
overflow: hidden;
height: 100%;
- .alert-dot {
- border-radius: 100%;
- height: 0.5em;
- width: 0.5em;
- position: absolute;
- right: calc(50% - 0.75em);
- top: calc(50% - 0.5em);
- background-color: $fallback--cRed;
- background-color: var(--badgeNotification, $fallback--cRed);
+ &.alert-dot-notification {
+ background-color: var(--badgeNotification);
+ }
+
+ &.alert-dot-neutral {
+ background-color: var(--badgeNeutral);
}
.pinned-item {
@@ -60,15 +57,9 @@
margin: 0;
}
- &.router-link-active {
- color: $fallback--text;
- color: var(--panelText, $fallback--text);
+ &.toggled {
+ margin-bottom: -4px;
border-bottom: 4px solid;
-
- & .svg-inline--fa,
- & .iconLetter {
- color: inherit;
- }
}
}
}
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
@@ -43,7 +43,6 @@ const Notification = {
data () {
return {
statusExpanded: false,
- betterShadow: this.$store.state.interface.browserSupport.cssFilter,
unmuted: false,
showingApproveConfirmDialog: false,
showingDenyConfirmDialog: false
diff --git a/src/components/notification/notification.scss b/src/components/notification/notification.scss
@@ -1,14 +1,16 @@
-@import "../../variables";
-
// TODO Copypaste from Status, should unify it somehow
.Notification {
border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
word-wrap: break-word;
word-break: break-word;
- --emoji-size: 14px;
+ &.Status {
+ /* stylelint-disable-next-line declaration-no-important */
+ background-color: transparent !important;
+ }
+
+ --emoji-size: 1em;
&:hover {
--_still-image-img-visibility: visible;
@@ -24,6 +26,7 @@
overflow: hidden;
display: flex;
flex-wrap: nowrap;
+ gap: 1ex;
& .status-username,
& .mute-thread,
@@ -71,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,6 +1,6 @@
<template>
<article
- v-if="notification.type === 'mention'"
+ v-if="notification.type === 'mention' || notification.type === 'status'"
>
<Status
class="Notification"
@@ -47,9 +47,7 @@
>
<UserAvatar
class="post-avatar"
- :bot="botIndicator"
:compact="true"
- :better-shadow="betterShadow"
:user="notification.from_profile"
/>
</UserPopover>
@@ -155,7 +153,7 @@
<router-link
v-if="notification.status"
:to="{ name: 'conversation', params: { id: notification.status.id } }"
- class="timeago-link faint-link"
+ class="timeago-link faint"
>
<Timeago
:time="notification.created_at"
@@ -247,7 +245,6 @@
/>
<template v-else>
<StatusContent
- :class="{ faint: !statusExpanded }"
:compact="!statusExpanded"
:status="notification.status"
/>
diff --git a/src/components/notifications/notification_filters.vue b/src/components/notifications/notification_filters.vue
@@ -8,65 +8,74 @@
<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>
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
@@ -33,7 +33,7 @@ const Notifications = {
// 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: {
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.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,7 +22,7 @@
&.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);
}
}
}
@@ -35,6 +32,11 @@
.notification {
box-sizing: border-box;
+ /* TODO cleanup this */
+ .Status {
+ flex: 1;
+ }
+
&:hover .animated.Avatar {
canvas {
display: none;
@@ -60,24 +62,17 @@
width: 32px;
height: 32px;
}
-
- .faint {
- --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);
}
}
@@ -97,11 +92,6 @@
}
}
- /* TODO cleanup this */
- .Status {
- flex: 1;
- }
-
time {
white-space: nowrap;
}
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
@@ -14,13 +14,13 @@
v-if="!noHeading"
class="notifications-heading panel-heading -sticky"
>
- <div class="title">
+ <h1 class="title">
{{ $t('notifications.notifications') }}
<span
v-if="unseenCountBadgeText"
- class="badge badge-notification unseen-count"
+ class="badge -notification unseen-count"
>{{ unseenCountBadgeText }}</span>
- </div>
+ </h1>
<div
v-if="showScrollTop"
class="rightside-button"
@@ -85,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">
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/palette_editor/palette_editor.vue b/src/components/palette_editor/palette_editor.vue
@@ -0,0 +1,195 @@
+<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"
+ :disabled="disabled"
+ :class="{ disabled }"
+ >
+ {{ $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', 'disabled'])
+const emit = defineEmits(['update:modelValue', 'applyPalette'])
+const getExportedObject = () => paletteKeys.reduce((acc, key) => {
+ const value = props.modelValue[key]
+ if (value == null) {
+ return acc
+ } else {
+ return { ...acc, [key]: props.modelValue[key] }
+ }
+}, {})
+
+const 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,59 @@
+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: 0,
+ y: 0,
+ blur: 3,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.5
+ },
+ {
+ x: 0,
+ y: 4,
+ blur: 6,
+ spread: 3,
+ color: '#000000',
+ alpha: 0.3
+ }]
+ }
+ }
+ ]
+}
diff --git a/src/components/panel_header.style.js b/src/components/panel_header.style.js
@@ -0,0 +1,40 @@
+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: [{
+ x: 0,
+ y: 1,
+ blur: 3,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.4
+ },
+ {
+ x: 0,
+ y: 1,
+ blur: 0,
+ spread: 0,
+ color: '#ffffff',
+ alpha: 0.2,
+ inset: true
+ }]
+ }
+ }
+ ]
+}
diff --git a/src/components/panel_loading/panel_loading.vue b/src/components/panel_loading/panel_loading.vue
@@ -23,22 +23,18 @@ export default {}
</script>
<style lang="scss">
-@import "src/variables";
-
.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";
-
.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/pinch_zoom/pinch_zoom.vue b/src/components/pinch_zoom/pinch_zoom.vue
@@ -2,7 +2,6 @@
<pinch-zoom
class="pinch-zoom-parent"
v-bind="$attrs"
- v-on="$listeners"
>
<slot />
</pinch-zoom>
diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue
@@ -37,12 +37,14 @@
:role="poll.multiple ? 'checkbox' : 'radio'"
:aria-labelledby="`option-vote-${randomSeed}-${index}`"
:aria-checked="choices[index]"
+ class="input unstyled"
@click="activateOption(index)"
>
+ <!-- TODO: USE CHECKBOX -->
<input
v-if="poll.multiple"
type="checkbox"
- class="poll-checkbox"
+ class="input -checkbox poll-checkbox"
:disabled="loading"
:value="index"
>
@@ -51,6 +53,7 @@
type="radio"
:disabled="loading"
:value="index"
+ class="input -radio"
>
<label class="option-vote">
<RichContent
@@ -73,12 +76,19 @@
>
{{ $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 }) }}
+ {{ $t("polls.people_voted_count", { count: poll.voters_count }, poll.voters_count) }}
</template>
<template v-else>
- {{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }}
+ {{ $t("polls.votes_count", { count: poll.votes_count }, poll.votes_count) }}
</template>
<span v-if="expiresAt !== null">
·
@@ -103,8 +113,6 @@
<script src="./poll.js"></script>
<style lang="scss">
-@import "../../variables";
-
.poll {
.votes {
display: flex;
@@ -114,6 +122,10 @@
.poll-option {
margin: 0.75em 0.5em;
+
+ .input {
+ line-height: inherit;
+ }
}
.option-result {
@@ -121,8 +133,7 @@
display: flex;
flex-direction: row;
position: relative;
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
+ color: var(--textLight);
}
.option-result-label {
@@ -141,12 +152,7 @@
.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;
diff --git a/src/components/poll/poll_form.js b/src/components/poll/poll_form.js
@@ -1,5 +1,5 @@
import * as DateUtils from 'src/services/date_utils/date_utils.js'
-import { uniq } from 'lodash'
+import { pollFallback } from 'src/services/poll/poll.service.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import Select from '../select/select.vue'
import {
@@ -17,14 +17,33 @@ export default {
Select
},
name: 'PollForm',
- props: ['visible'],
- data: () => ({
- pollType: 'single',
- options: ['', ''],
- expiryAmount: 10,
- expiryUnit: 'minutes'
- }),
+ props: {
+ visible: {},
+ params: {
+ type: Object,
+ required: true
+ }
+ },
computed: {
+ pollType: {
+ get () { return pollFallback(this.params, 'pollType') },
+ set (newVal) { this.params.pollType = newVal }
+ },
+ options () {
+ const hasOptions = !!this.params.options
+ if (!hasOptions) {
+ this.params.options = pollFallback(this.params, 'options')
+ }
+ return this.params.options
+ },
+ expiryAmount: {
+ get () { return pollFallback(this.params, 'expiryAmount') },
+ set (newVal) { this.params.expiryAmount = newVal }
+ },
+ expiryUnit: {
+ get () { return pollFallback(this.params, 'expiryUnit') },
+ set (newVal) { this.params.expiryUnit = newVal }
+ },
pollLimits () {
return this.$store.state.instance.pollLimits
},
@@ -89,7 +108,6 @@ export default {
deleteOption (index, event) {
if (this.options.length > 2) {
this.options.splice(index, 1)
- this.updatePollToParent()
}
},
convertExpiryToUnit (unit, amount) {
@@ -104,24 +122,6 @@ export default {
Math.max(this.minExpirationInCurrentUnit, this.expiryAmount)
this.expiryAmount =
Math.min(this.maxExpirationInCurrentUnit, this.expiryAmount)
- this.updatePollToParent()
- },
- updatePollToParent () {
- const expiresIn = this.convertExpiryFromUnit(
- this.expiryUnit,
- this.expiryAmount
- )
-
- const options = uniq(this.options.filter(option => option !== ''))
- if (options.length < 2) {
- this.$emit('update-poll', { error: this.$t('polls.not_enough_options') })
- return
- }
- this.$emit('update-poll', {
- options,
- multiple: this.pollType === 'multiple',
- expiresIn
- })
}
}
}
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"
>
- {{ $tc(`time.unit.${unit}_short`, expiryAmount, ['']) }}
+ {{ $t(`time.unit.${unit}_short`, [''], expiryAmount) }}
</option>
</Select>
</div>
@@ -95,8 +95,6 @@
<script src="./poll_form.js"></script>
<style lang="scss">
-@import "../../variables";
-
.poll-form {
display: flex;
flex-direction: column;
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
@@ -53,7 +53,11 @@ const Popover = {
default: {}
}
},
- inject: ['popoversZLayer'], // override popover z layer
+ 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
diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue
@@ -42,8 +42,6 @@
<script src="./popover.js" />
<style lang="scss">
-@import "../../variables";
-
.popover-trigger-button {
display: inline-block;
}
@@ -53,81 +51,54 @@
position: fixed;
min-width: 0;
max-width: calc(100vw - 20px);
- box-shadow: 2px 2px 3px rgb(0 0 0 / 50%);
- box-shadow: var(--popupShadow);
+ box-shadow: var(--shadow);
}
.popover-default {
&::after {
content: "";
position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- z-index: 3;
- box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
- 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: 0.5rem 0;
+ padding: 0;
font-size: 1em;
text-align: left;
list-style: none;
max-width: 100vw;
z-index: var(--ZI_popover_override, var(--ZI_popovers));
white-space: nowrap;
+ background-color: var(--background);
.dropdown-divider {
height: 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: 0;
- 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);
}
}
@@ -138,40 +109,18 @@
}
}
- &: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);
- }
- }
-
.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: 0;
- background-color: $fallback--fg;
- background-color: var(--input, $fallback--fg);
- box-shadow: 0 0 2px black inset;
- box-shadow: var(--inputShadow);
- margin-right: 0.75em;
+ box-shadow: var(--shadow);
+ margin-right: var(--__horizontal-gap);
&.menu-checkbox-checked::after {
font-size: 1.25em;
@@ -188,30 +137,5 @@
}
}
}
-
- .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
@@ -7,14 +7,17 @@ import PollForm from '../poll/poll_form.vue'
import Attachment from '../attachment/attachment.vue'
import Gallery from 'src/components/gallery/gallery.vue'
import StatusContent from '../status_content/status_content.vue'
+import Popover from 'src/components/popover/popover.vue'
import fileTypeService from '../../services/file_type/file_type.service.js'
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
import { propsToNative } from '../../services/attributes_helper/attributes_helper.service.js'
+import { pollFormToMasto } from 'src/services/poll/poll.service.js'
import { reject, map, uniqBy, debounce } from 'lodash'
import suggestor from '../emoji_input/suggestor.js'
import { mapGetters, mapState } from 'vuex'
import Checkbox from '../checkbox/checkbox.vue'
import Select from '../select/select.vue'
+import DraftCloser from 'src/components/draft_closer/draft_closer.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -23,7 +26,10 @@ import {
faUpload,
faBan,
faTimes,
- faCircleNotch
+ faCircleNotch,
+ faChevronDown,
+ faChevronLeft,
+ faChevronRight
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -32,7 +38,10 @@ library.add(
faUpload,
faBan,
faTimes,
- faCircleNotch
+ faCircleNotch,
+ faChevronDown,
+ faChevronLeft,
+ faChevronRight
)
const buildMentionsString = ({ user, attentions = [] }, currentUser) => {
@@ -55,6 +64,18 @@ const pxStringToNumber = (str) => {
return Number(str.substring(0, str.length - 2))
}
+const typeAndRefId = ({ replyTo, profileMention, statusId }) => {
+ if (replyTo) {
+ return ['reply', replyTo]
+ } else if (profileMention) {
+ return ['mention', profileMention]
+ } else if (statusId) {
+ return ['edit', statusId]
+ } else {
+ return ['new', '']
+ }
+}
+
const PostStatusForm = {
props: [
'statusId',
@@ -79,6 +100,9 @@ const PostStatusForm = {
'disableSensitivityCheckbox',
'disableSubmit',
'disablePreview',
+ 'disableDraft',
+ 'hideDraft',
+ 'closeable',
'placeholder',
'maxHeight',
'postHandler',
@@ -87,13 +111,18 @@ const PostStatusForm = {
'fileLimit',
'submitOnEnter',
'emojiPickerPlacement',
- 'optimisticPosting'
+ 'optimisticPosting',
+ 'profileMention',
+ 'draftId'
],
emits: [
'posted',
+ 'draft-done',
'resize',
'mediaplay',
- 'mediapause'
+ 'mediapause',
+ 'can-close',
+ 'update'
],
components: {
MediaUpload,
@@ -104,7 +133,9 @@ const PostStatusForm = {
Select,
Attachment,
StatusContent,
- Gallery
+ Gallery,
+ DraftCloser,
+ Popover
},
mounted () {
this.updateIdempotencyKey()
@@ -125,41 +156,54 @@ const PostStatusForm = {
const { scopeCopy } = this.$store.getters.mergedConfig
- if (this.replyTo) {
- const currentUser = this.$store.state.users.currentUser
- statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
- }
+ const [statusType, refId] = typeAndRefId({ replyTo: this.replyTo, profileMention: this.profileMention && this.repliedUser?.id, statusId: this.statusId })
- const scope = ((this.copyMessageScope && scopeCopy) || this.copyMessageScope === 'direct')
- ? this.copyMessageScope
- : this.$store.state.users.currentUser.default_scope
-
- const { postContentType: contentType, sensitiveByDefault } = this.$store.getters.mergedConfig
-
- let statusParams = {
- spoilerText: this.subject || '',
- status: statusText,
- nsfw: !!sensitiveByDefault,
- files: [],
- poll: {},
- mediaDescriptions: {},
- visibility: scope,
- contentType
- }
+ // If we are starting a new post, do not associate it with old drafts
+ let statusParams = !this.disableDraft && (this.draftId || statusType !== 'new') ? this.getDraft(statusType, refId) : null
+
+ if (!statusParams) {
+ if (statusType === 'reply' || statusType === 'mention') {
+ const currentUser = this.$store.state.users.currentUser
+ statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
+ }
+
+ const scope = ((this.copyMessageScope && scopeCopy) || this.copyMessageScope === 'direct')
+ ? this.copyMessageScope
+ : this.$store.state.users.currentUser.default_scope
+
+ const { postContentType: contentType, sensitiveByDefault } = this.$store.getters.mergedConfig
- if (this.statusId) {
- const statusContentType = this.statusContentType || contentType
statusParams = {
+ type: statusType,
+ refId,
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,
+ status: statusText,
+ nsfw: !!sensitiveByDefault,
+ files: [],
+ poll: {},
+ hasPoll: false,
+ mediaDescriptions: {},
+ visibility: scope,
+ contentType,
quoting: false
}
+
+ if (statusType === 'edit') {
+ const statusContentType = this.statusContentType || contentType
+ statusParams = {
+ type: statusType,
+ refId,
+ spoilerText: this.subject || '',
+ status: this.statusText || '',
+ nsfw: this.statusIsSensitive || !!sensitiveByDefault,
+ files: this.statusFiles || [],
+ poll: this.statusPoll || {},
+ hasPoll: false,
+ mediaDescriptions: this.statusMediaDescriptions || {},
+ visibility: this.statusScope || scope,
+ contentType: statusContentType
+ }
+ }
}
return {
@@ -171,13 +215,14 @@ const PostStatusForm = {
highlighted: 0,
newStatus: statusParams,
caret: 0,
- pollFormVisible: false,
showDropIcon: 'hide',
dropStopTimeout: null,
preview: null,
previewLoading: false,
emojiInputShown: false,
- idempotencyKey: ''
+ idempotencyKey: '',
+ saveInhibited: true,
+ saveable: false
}
},
computed: {
@@ -190,6 +235,9 @@ const PostStatusForm = {
showAllScopes () {
return !this.mergedConfig.minimalScopesMode
},
+ hideExtraActions () {
+ return this.disableDraft || this.hideDraft
+ },
emojiUserSuggestor () {
return suggestor({
emoji: [
@@ -292,6 +340,32 @@ const PostStatusForm = {
return false
},
+ debouncedMaybeAutoSaveDraft () {
+ return debounce(this.maybeAutoSaveDraft, 3000)
+ },
+ pollFormVisible () {
+ return this.newStatus.hasPoll
+ },
+ shouldAutoSaveDraft () {
+ return this.$store.getters.mergedConfig.autoSaveDraft
+ },
+ autoSaveState () {
+ if (this.saveable) {
+ return this.$t('post_status.auto_save_saving')
+ } else if (this.newStatus.id) {
+ return this.$t('post_status.auto_save_saved')
+ } else {
+ return this.$t('post_status.auto_save_nothing_new')
+ }
+ },
+ safeToSaveDraft () {
+ return (
+ this.newStatus.status ||
+ this.newStatus.spoilerText ||
+ this.newStatus.files?.length ||
+ this.newStatus.hasPoll
+ ) && this.saveable
+ },
...mapGetters(['mergedConfig']),
...mapState({
mobileLayout: state => state.interface.mobileLayout
@@ -303,15 +377,32 @@ const PostStatusForm = {
handler () {
this.statusChanged()
}
+ },
+ saveable (val) {
+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event#usage_notes
+ // MDN says we'd better add the beforeunload event listener only when needed, and remove it when it's no longer needed
+ if (val) {
+ this.addBeforeUnloadListener()
+ } else {
+ this.removeBeforeUnloadListener()
+ }
}
},
+ beforeUnmount () {
+ this.maybeAutoSaveDraft()
+ this.removeBeforeUnloadListener()
+ },
methods: {
statusChanged () {
this.autoPreview()
this.updateIdempotencyKey()
+ this.debouncedMaybeAutoSaveDraft()
+ this.saveable = true
+ this.saveInhibited = false
},
clearStatus () {
const newStatus = this.newStatus
+ this.saveInhibited = true
this.newStatus = {
status: '',
spoilerText: '',
@@ -319,10 +410,10 @@ const PostStatusForm = {
visibility: newStatus.visibility,
contentType: newStatus.contentType,
poll: {},
+ hasPoll: false,
mediaDescriptions: {},
quoting: false
}
- this.pollFormVisible = false
this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
this.clearPollForm()
if (this.preserveFocus) {
@@ -335,6 +426,7 @@ const PostStatusForm = {
el.style.height = undefined
this.error = null
if (this.preview) this.previewStatus()
+ this.saveable = false
},
async postStatus (event, newStatus, opts = {}) {
if (this.posting && !this.optimisticPosting) { return }
@@ -352,7 +444,7 @@ const PostStatusForm = {
return
}
- const poll = this.pollFormVisible ? this.newStatus.poll : {}
+ const poll = this.newStatus.hasPoll ? pollFormToMasto(this.newStatus.poll) : {}
if (this.pollContentError) {
this.error = this.pollContentError
return
@@ -387,6 +479,7 @@ const PostStatusForm = {
postHandler(postingOptions).then((data) => {
if (!data.error) {
+ this.abandonDraft()
this.clearStatus()
this.$emit('posted', data)
} else {
@@ -631,7 +724,7 @@ const PostStatusForm = {
this.newStatus.visibility = visibility
},
togglePollForm () {
- this.pollFormVisible = !this.pollFormVisible
+ this.newStatus.hasPoll = !this.newStatus.hasPoll
},
setPoll (poll) {
this.newStatus.poll = poll
@@ -664,6 +757,84 @@ const PostStatusForm = {
},
propsToNative (props) {
return propsToNative(props)
+ },
+ saveDraft () {
+ if (!this.disableDraft &&
+ !this.saveInhibited) {
+ if (this.safeToSaveDraft) {
+ return this.$store.dispatch('addOrSaveDraft', { draft: this.newStatus })
+ .then(id => {
+ if (this.newStatus.id !== id) {
+ this.newStatus.id = id
+ }
+ this.saveable = false
+ if (!this.shouldAutoSaveDraft) {
+ this.clearStatus()
+ this.$emit('draft-done')
+ }
+ })
+ } else if (this.newStatus.id) {
+ // There is a draft, but there is nothing in it, clear it
+ return this.abandonDraft()
+ .then(() => {
+ this.saveable = false
+ if (!this.shouldAutoSaveDraft) {
+ this.clearStatus()
+ this.$emit('draft-done')
+ }
+ })
+ }
+ }
+ return Promise.resolve()
+ },
+ maybeAutoSaveDraft () {
+ if (this.shouldAutoSaveDraft) {
+ this.saveDraft(false)
+ }
+ },
+ abandonDraft () {
+ return this.$store.dispatch('abandonDraft', { id: this.newStatus.id })
+ },
+ getDraft (statusType, refId) {
+ const maybeDraft = this.$store.state.drafts.drafts[this.draftId]
+ if (this.draftId && maybeDraft) {
+ return maybeDraft
+ } else {
+ const existingDrafts = this.$store.getters.draftsByTypeAndRefId(statusType, refId)
+
+ if (existingDrafts.length) {
+ return existingDrafts[0]
+ }
+ }
+ // No draft available, fall back
+ },
+ requestClose () {
+ if (!this.saveable) {
+ this.$emit('can-close')
+ } else {
+ this.$refs.draftCloser.requestClose()
+ }
+ },
+ saveAndCloseDraft () {
+ this.saveDraft().then(() => {
+ this.$emit('can-close')
+ })
+ },
+ discardAndCloseDraft () {
+ this.abandonDraft().then(() => {
+ this.$emit('can-close')
+ })
+ },
+ addBeforeUnloadListener () {
+ this._beforeUnloadListener ||= () => {
+ this.saveDraft()
+ }
+ window.addEventListener('beforeunload', this._beforeUnloadListener)
+ },
+ removeBeforeUnloadListener () {
+ if (this._beforeUnloadListener) {
+ window.removeEventListener('beforeunload', this._beforeUnloadListener)
+ }
}
}
}
diff --git a/src/components/post_status_form/post_status_form.scss b/src/components/post_status_form/post_status_form.scss
@@ -0,0 +1,258 @@
+.post-status-form {
+ position: relative;
+
+ .attachments {
+ margin-bottom: 0.5em;
+ }
+
+ .more-post-actions {
+ height: 100%;
+
+ .btn {
+ height: 100%;
+ }
+ }
+
+ .form-bottom {
+ display: flex;
+ justify-content: space-between;
+ padding: 0.5em;
+ height: 2.5em;
+
+ .post-button-group {
+ width: 10em;
+ display: flex;
+
+ .post-button {
+ flex: 1 0 auto;
+ }
+
+ .more-post-actions {
+ flex: 0 0 auto;
+ }
+ }
+
+ p {
+ margin: 0.35em;
+ padding: 0.35em;
+ display: flex;
+ }
+ }
+
+ .form-bottom-left {
+ display: flex;
+ flex: 1;
+ padding-right: 7px;
+ margin-right: 7px;
+ max-width: 10em;
+ }
+
+ .preview-heading {
+ display: flex;
+ flex-wrap: wrap;
+ }
+
+ .preview-toggle {
+ flex: 10 0 auto;
+ cursor: pointer;
+ user-select: none;
+ padding-left: 0.5em;
+
+ &:hover {
+ text-decoration: underline;
+ }
+
+ svg,
+ i {
+ margin-left: 0.2em;
+ font-size: 0.8em;
+ transform: rotate(90deg);
+ }
+ }
+
+ .preview-container {
+ margin-bottom: 1em;
+ }
+
+ .preview-error {
+ font-style: italic;
+ color: var(--textFaint);
+ }
+
+ .preview-status {
+ 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: var(--textFaint);
+ }
+ }
+
+ .visibility-tray {
+ display: flex;
+ justify-content: space-between;
+ padding-top: 5px;
+ align-items: baseline;
+ }
+
+ .visibility-notice.edit-warning {
+ > :first-child {
+ margin-top: 0;
+ }
+
+ > :last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ // Order is not necessary but a good indicator
+ .media-upload-icon {
+ order: 1;
+ justify-content: left;
+ }
+
+ .emoji-icon {
+ order: 2;
+ justify-content: center;
+ }
+
+ .poll-icon {
+ order: 3;
+ 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: 0.2em;
+ margin-bottom: 0.5em;
+ width: 18em;
+
+ img,
+ video {
+ object-fit: contain;
+ max-height: 10em;
+ }
+
+ .video {
+ max-height: 10em;
+ }
+
+ input {
+ flex: 1;
+ width: 100%;
+ }
+ }
+
+ .status-input-wrapper {
+ display: flex;
+ position: relative;
+ width: 100%;
+ flex-direction: column;
+ }
+
+ .btn[disabled] {
+ cursor: not-allowed;
+ }
+
+ form {
+ display: flex;
+ flex-direction: column;
+ margin: 0.6em;
+ position: relative;
+ }
+
+ .form-group {
+ display: flex;
+ flex-direction: column;
+ padding: 0.25em 0.5em 0.5em;
+ line-height: 1.85;
+ }
+
+ .input.form-post-body {
+ // TODO: make a resizable textarea component?
+ box-sizing: content-box; // needed for easier computation of dynamic size
+ overflow: hidden;
+ transition: min-height 200ms 100ms;
+ // stock padding + 1 line of text (for counter)
+ padding-bottom: calc(var(--_padding) + var(--post-line-height) * 1em);
+ // two lines of text
+ height: calc(var(--post-line-height) * 1em);
+ min-height: calc(var(--post-line-height) * 1em);
+ resize: none;
+ background: transparent;
+
+ &.scrollable-form {
+ overflow-y: auto;
+ }
+ }
+
+ .main-input {
+ position: relative;
+ }
+
+ .character-counter {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ padding: 0;
+ margin: 0 0.5em;
+
+ &.error {
+ color: var(--cRed);
+ }
+ }
+
+ @keyframes fade-in {
+ from { opacity: 0; }
+ to { opacity: 0.6; }
+ }
+
+ @keyframes fade-out {
+ from { opacity: 0.6; }
+ to { opacity: 0; }
+ }
+
+ .drop-indicator {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ font-size: 5em;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0.6;
+ color: var(--text);
+ background-color: var(--bg);
+ border-radius: var(--roundness);
+ border: 2px dashed var(--text);
+ }
+
+ .auto-save-status {
+ align-self: center;
+ }
+}
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
@@ -103,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"
@@ -126,42 +156,12 @@
class="preview-status"
/>
</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>
<EmojiInput
v-if="!disableSubject && (newStatus.spoilerText || alwaysShowSubject)"
v-model="newStatus.spoilerText"
enable-emoji-picker
:suggest="emojiSuggestor"
- class="form-control"
+ class="input form-control"
>
<template #default="inputProps">
<input
@@ -171,7 +171,7 @@
:disabled="posting && !optimisticPosting"
v-bind="propsToNative(inputProps)"
size="1"
- class="form-post-subject"
+ class="input form-post-subject"
>
</template>
</EmojiInput>
@@ -180,11 +180,11 @@
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"
@@ -198,7 +198,7 @@
rows="1"
cols="1"
:disabled="posting && !optimisticPosting"
- class="form-post-body"
+ class="input form-post-body"
:class="{ 'scrollable-form': !!maxHeight }"
v-bind="propsToNative(inputProps)"
@keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
@@ -235,9 +235,8 @@
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
@@ -263,8 +262,14 @@
v-if="pollsAvailable"
ref="pollForm"
:visible="pollFormVisible"
- @update-poll="setPoll"
+ :params="newStatus.poll"
/>
+ <span
+ v-if="!disableDraft && shouldAutoSaveDraft"
+ class="auto-save-status"
+ >
+ {{ autoSaveState }}
+ </span>
<div
ref="bottom"
class="form-bottom"
@@ -297,28 +302,58 @@
<FAIcon icon="poll-h" />
</button>
</div>
- <button
- v-if="posting"
- disabled
- class="btn button-default"
- >
- {{ $t('post_status.posting') }}
- </button>
- <button
- v-else-if="isOverLengthLimit"
- disabled
- class="btn button-default"
- >
- {{ $t('post_status.post') }}
- </button>
- <button
- v-else
- :disabled="uploadingFiles || disableSubmit"
- class="btn button-default"
- @click.stop.prevent="postStatus($event, newStatus)"
- >
- {{ $t('post_status.post') }}
- </button>
+ <div class="btn-group post-button-group">
+ <button
+ class="btn button-default post-button"
+ :disabled="isOverLengthLimit || posting || uploadingFiles || disableSubmit"
+ @click.stop.prevent="postStatus($event, newStatus)"
+ >
+ <template v-if="posting">
+ {{ $t('post_status.posting') }}
+ </template>
+ <template v-else>
+ {{ $t('post_status.post') }}
+ </template>
+ </button>
+ <Popover
+ v-if="!hideExtraActions"
+ class="more-post-actions"
+ :normal-button="true"
+ trigger="click"
+ placement="bottom"
+ :offset="{ y: 5 }"
+ >
+ <template #trigger>
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="chevron-down"
+ />
+ </template>
+ <template #content="{close}">
+ <div
+ class="dropdown-menu"
+ role="menu"
+ >
+ <button
+ v-if="!hideDraft || !disableDraft"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menu"
+ :disabled="!safeToSaveDraft && saveable"
+ :class="{ disabled: !safeToSaveDraft }"
+ @click.prevent="saveDraft"
+ @click="close"
+ >
+ <template v-if="closeable">
+ {{ $t('post_status.save_to_drafts_and_close_button') }}
+ </template>
+ <template v-else>
+ {{ $t('post_status.save_to_drafts_button') }}
+ </template>
+ </button>
+ </div>
+ </template>
+ </Popover>
+ </div>
</div>
<div
v-show="showDropIcon !== 'hide'"
@@ -369,278 +404,14 @@
</Checkbox>
</div>
</form>
+ <DraftCloser
+ ref="draftCloser"
+ @save="saveAndCloseDraft"
+ @discard="discardAndCloseDraft"
+ />
</div>
</template>
<script src="./post_status_form.js"></script>
-<style lang="scss">
-@import "../../variables";
-
-.post-status-form {
- position: relative;
-
- .attachments {
- margin-bottom: 0.5em;
- }
-
- .form-bottom {
- display: flex;
- justify-content: space-between;
- padding: 0.5em;
- height: 2.5em;
-
- button {
- width: 10em;
- }
-
- p {
- margin: 0.35em;
- padding: 0.35em;
- display: flex;
- }
- }
-
- .form-bottom-left {
- display: flex;
- flex: 1;
- padding-right: 7px;
- margin-right: 7px;
- max-width: 10em;
- }
-
- .preview-heading {
- display: flex;
- padding-left: 0.5em;
- }
-
- .preview-toggle {
- flex: 1;
- cursor: pointer;
- user-select: none;
-
- &:hover {
- text-decoration: underline;
- }
-
- svg,
- i {
- margin-left: 0.2em;
- font-size: 0.8em;
- transform: rotate(90deg);
- }
- }
-
- .preview-container {
- margin-bottom: 1em;
- }
-
- .preview-error {
- font-style: italic;
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
- }
-
- .preview-status {
- border: 1px solid $fallback--border;
- border: 1px solid var(--border, $fallback--border);
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
- padding: 0.5em;
- margin: 0;
- }
-
- .reply-or-quote-selector {
- margin-bottom: 0.5em;
- }
-
- .text-format {
- .only-format {
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
- }
- }
-
- .visibility-tray {
- display: flex;
- justify-content: space-between;
- padding-top: 5px;
- align-items: baseline;
- }
-
- .visibility-notice.edit-warning {
- > :first-child {
- margin-top: 0;
- }
-
- > :last-child {
- margin-bottom: 0;
- }
- }
-
- // Order is not necessary but a good indicator
- .media-upload-icon {
- order: 1;
- justify-content: left;
- }
-
- .emoji-icon {
- order: 2;
- justify-content: center;
- }
-
- .poll-icon {
- order: 3;
- 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;
-
- &.selected,
- &:hover {
- // needs to be specific to override icon default color
- svg,
- i,
- label {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
- }
- }
-
- &.disabled {
- svg,
- i {
- cursor: not-allowed;
- color: $fallback--icon;
- color: var(--btnDisabledText, $fallback--icon);
-
- &:hover {
- color: $fallback--icon;
- color: var(--btnDisabledText, $fallback--icon);
- }
- }
- }
- }
-
- .error {
- text-align: center;
- }
-
- .media-upload-wrapper {
- margin-right: 0.2em;
- margin-bottom: 0.5em;
- width: 18em;
-
- img,
- video {
- object-fit: contain;
- max-height: 10em;
- }
-
- .video {
- max-height: 10em;
- }
-
- input {
- flex: 1;
- width: 100%;
- }
- }
-
- .status-input-wrapper {
- display: flex;
- position: relative;
- width: 100%;
- flex-direction: column;
- }
-
- .btn[disabled] {
- cursor: not-allowed;
- }
-
- form {
- display: flex;
- flex-direction: column;
- margin: 0.6em;
- position: relative;
- }
-
- .form-group {
- display: flex;
- flex-direction: column;
- padding: 0.25em 0.5em 0.5em;
- line-height: 1.85;
- }
-
- .form-post-body {
- // TODO: make a resizable textarea component?
- box-sizing: content-box; // needed for easier computation of dynamic size
- overflow: hidden;
- transition: min-height 200ms 100ms;
- // stock padding + 1 line of text (for counter)
- padding-bottom: calc(var(--_padding) + var(--post-line-height) * 1em);
- // two lines of text
- height: calc(var(--post-line-height) * 1em);
- min-height: calc(var(--post-line-height) * 1em);
- resize: none;
-
- &.scrollable-form {
- overflow-y: auto;
- }
- }
-
- .main-input {
- position: relative;
- }
-
- .character-counter {
- position: absolute;
- bottom: 0;
- right: 0;
- padding: 0;
- margin: 0 0.5em;
-
- &.error {
- color: $fallback--cRed;
- color: var(--cRed, $fallback--cRed);
- }
- }
-
- @keyframes fade-in {
- from { opacity: 0; }
- to { opacity: 0.6; }
- }
-
- @keyframes fade-out {
- from { opacity: 0.6; }
- to { opacity: 0; }
- }
-
- .drop-indicator {
- position: absolute;
- width: 100%;
- height: 100%;
- font-size: 5em;
- display: flex;
- 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);
- }
-}
-</style>
+<style src="./post_status_form.scss" lang="scss"></style>
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,16 @@
>
<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"
+ :closeable="true"
@posted="resetAndClose"
+ @draft-done="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
@@ -63,6 +63,13 @@ const QuickFilterSettings = {
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 })
+ }
}
}
}
diff --git a/src/components/quick_filter_settings/quick_filter_settings.vue b/src/components/quick_filter_settings/quick_filter_settings.vue
@@ -16,39 +16,39 @@
>
<button
v-if="!conversation"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
:aria-checked="replyVisibilityAll"
role="menuitemradio"
@click="replyVisibilityAll = true"
>
<span
- class="menu-checkbox -radio"
+ class="input menu-checkbox -radio"
:class="{ 'menu-checkbox-checked': replyVisibilityAll }"
:aria-hidden="true"
/>{{ $t('settings.reply_visibility_all') }}
</button>
<button
v-if="!conversation"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
:aria-checked="replyVisibilityFollowing"
role="menuitemradio"
@click="replyVisibilityFollowing = true"
>
<span
- class="menu-checkbox -radio"
+ 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="button-default dropdown-item"
+ class="menu-item dropdown-item"
:aria-checked="replyVisibilitySelf"
role="menuitemradio"
@click="replyVisibilitySelf = true"
>
<span
- class="menu-checkbox -radio"
+ class="input menu-checkbox -radio"
:class="{ 'menu-checkbox-checked': replyVisibilitySelf }"
:aria-hidden="true"
/>{{ $t('settings.reply_visibility_self_short') }}
@@ -60,43 +60,55 @@
/>
</div>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
role="menuitemcheckbox"
:aria-checked="muteBotStatuses"
@click="muteBotStatuses = !muteBotStatuses"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': muteBotStatuses }"
:aria-hidden="true"
/>{{ $t('settings.mute_bot_posts') }}
</button>
<button
- class="button-default dropdown-item"
+ 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="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hideMedia }"
:aria-hidden="true"
/>{{ $t('settings.hide_media_previews') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
role="menuitemcheckbox"
:aria-checked="hideMutedPosts"
@click="hideMutedPosts = !hideMutedPosts"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hideMutedPosts }"
:aria-hidden="true"
/>{{ $t('settings.hide_all_muted_posts') }}
</button>
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
role="menuitem"
@click="openTab('filtering')"
>
diff --git a/src/components/quick_view_settings/quick_view_settings.js b/src/components/quick_view_settings/quick_view_settings.js
@@ -61,6 +61,13 @@ const QuickViewSettings = {
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 })
+ }
}
}
}
diff --git a/src/components/quick_view_settings/quick_view_settings.vue b/src/components/quick_view_settings/quick_view_settings.vue
@@ -12,13 +12,13 @@
>
<div role="group">
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
:aria-checked="conversationDisplay === 'tree'"
role="menuitemradio"
@click="conversationDisplay = 'tree'"
>
<span
- class="menu-checkbox -radio"
+ class="input menu-checkbox -radio"
:aria-hidden="true"
:class="{ 'menu-checkbox-checked': conversationDisplay === 'tree' }"
/><FAIcon
@@ -27,13 +27,13 @@
/> {{ $t('settings.conversation_display_tree_quick') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
:aria-checked="conversationDisplay === 'linear'"
role="menuitemradio"
@click="conversationDisplay = 'linear'"
>
<span
- class="menu-checkbox -radio"
+ class="input menu-checkbox -radio"
:class="{ 'menu-checkbox-checked': conversationDisplay === 'linear' }"
:aria-hidden="true"
/><FAIcon
@@ -47,45 +47,45 @@
class="dropdown-divider"
/>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
role="menuitemcheckbox"
:aria-checked="showUserAvatars"
@click="showUserAvatars = !showUserAvatars"
>
<span
- class="menu-checkbox"
+ 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="button-default dropdown-item"
+ class="menu-item dropdown-item"
role="menuitemcheckbox"
:aria-checked="autoUpdate"
@click="autoUpdate = !autoUpdate"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': autoUpdate }"
:aria-hidden="true"
/>{{ $t('settings.auto_update') }}
</button>
<button
v-if="!conversation"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
role="menuitemcheckbox"
:aria-checked="collapseWithSubjects"
@click="collapseWithSubjects = !collapseWithSubjects"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': collapseWithSubjects }"
:aria-hidden="true"
/>{{ $t('settings.collapse_subject') }}
</button>
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
role="menuitem"
@click="openTab('general')"
>
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
@@ -14,7 +14,7 @@
v-if="typeof fallback !== 'undefined'"
:id="name + '-o'"
:aria-labelledby="name + '-label'"
- class="opt visible-for-screenreader-only"
+ class="input -checkbox opt visible-for-screenreader-only"
type="checkbox"
:checked="present"
@change="$emit('update:modelValue', !present ? fallback : undefined)"
@@ -27,7 +27,7 @@
/>
<input
:id="name"
- class="input-number"
+ class="input input-number"
type="range"
:value="modelValue || fallback"
:disabled="!present || disabled"
@@ -38,7 +38,7 @@
>
<input
:id="name + '-numeric'"
- class="input-number"
+ class="input input-number"
type="number"
:aria-labelledby="name + '-label'"
:value="modelValue || fallback"
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
@@ -2,7 +2,7 @@
<span class="ReactButton">
<EmojiPicker
ref="picker"
- :enable-sticker-picker="enableStickerPicker"
+ :enable-sticker-picker="false"
:hide-custom-emoji="hideCustomEmoji"
class="emoji-picker-panel"
@emoji="addReaction"
@@ -41,7 +41,6 @@
<script src="./react_button.js"></script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.ReactButton {
@@ -58,7 +57,7 @@
height: 1px;
width: 100%;
margin: 0.5em;
- background-color: var(--border, $fallback--border);
+ background-color: var(--border);
}
.reaction-picker {
@@ -99,11 +98,6 @@
padding: 10px;
margin: -10px;
- &:hover .svg-inline--fa {
- color: $fallback--text;
- color: var(--text, $fallback--text);
- }
-
@include unfocused-style {
.focus-marker {
visibility: hidden;
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
@@ -1,7 +1,9 @@
<template>
<div class="settings panel panel-default">
<div class="panel-heading">
- {{ $t('registration.registration') }}
+ <h1 class="title">
+ {{ $t('registration.registration') }}
+ </h1>
</div>
<div
v-if="!hasSignUpNotice"
@@ -25,7 +27,7 @@
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')"
>
@@ -53,7 +55,7 @@
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')"
>
@@ -81,7 +83,7 @@
id="email"
v-model="v$.user.email.$model"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
type="email"
:aria-required="accountActivationRequired"
>
@@ -106,7 +108,7 @@
id="bio"
v-model="user.bio"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
:placeholder="bioPlaceholder"
/>
</div>
@@ -123,7 +125,7 @@
id="sign-up-password"
v-model="user.password"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
type="password"
:aria-required="true"
>
@@ -151,7 +153,7 @@
id="sign-up-password-confirmation"
v-model="user.confirm"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
type="password"
:aria-required="true"
>
@@ -184,7 +186,7 @@
id="sign-up-birthday"
v-model="user.birthday"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
type="date"
:max="birthdayRequired ? birthdayMinAttr : undefined"
:aria-required="birthdayRequired"
@@ -199,7 +201,7 @@
<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>
+ <span>{{ $t('registration.validations.birthday_min_age', { date: birthdayMinFormatted }) }}</span>
</li>
</ul>
</div>
@@ -229,7 +231,7 @@
id="reason"
v-model="user.reason"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
:placeholder="reasonPlaceholder"
/>
</div>
@@ -256,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"
@@ -275,7 +277,7 @@
id="token"
v-model="token"
disabled="true"
- class="form-control"
+ class="input form-control"
type="text"
>
</div>
@@ -320,9 +322,6 @@
<script src="./registration.js"></script>
<style lang="scss">
-@import "../../variables";
-$validations-cRed: #f04124;
-
.registration-form {
display: flex;
flex-direction: column;
@@ -369,8 +368,7 @@ $validations-cRed: #f04124;
}
.form-group--error .form--label {
- color: $validations-cRed;
- color: var(--cRed, $validations-cRed);
+ color: var(--cRed);
}
.form-error {
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>
diff --git a/src/components/remove_follower_button/remove_follower_button.vue b/src/components/remove_follower_button/remove_follower_button.vue
@@ -17,6 +17,7 @@
@cancelled="hideConfirmRemoveUserFromFollowers"
>
<i18n-t
+ scope="global"
keypath="user_card.remove_follower_confirm"
tag="span"
>
diff --git a/src/components/reply_button/reply_button.vue b/src/components/reply_button/reply_button.vue
@@ -59,7 +59,6 @@
<script src="./reply_button.js"></script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.ReplyButton {
@@ -78,8 +77,7 @@
.interactive {
&:hover .svg-inline--fa,
&.-active .svg-inline--fa {
- color: $fallback--cBlue;
- color: var(--cBlue, $fallback--cBlue);
+ color: var(--cBlue);
}
@include unfocused-style {
diff --git a/src/components/report/report.scss b/src/components/report/report.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.Report {
.report-content {
margin: 0.5em 0 1em;
@@ -10,12 +8,8 @@
}
.reported-status {
- border: 1px solid $fallback--faint;
- border-color: var(--faint, $fallback--faint);
- border-radius: $fallback--inputRadius;
- border-radius: var(--inputRadius, $fallback--inputRadius);
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ border: 1px solid var(--border);
+ border-radius: var(--roundness);
display: block;
padding: 0.5em;
margin: 0.5em 0;
diff --git a/src/components/report/report.vue b/src/components/report/report.vue
@@ -17,7 +17,7 @@
<Select
:id="report-state"
v-model="state"
- class="form-control"
+ class="input form-control"
>
<option
v-for="state in ['open', 'closed', 'resolved']"
diff --git a/src/components/retweet_button/retweet_button.vue b/src/components/retweet_button/retweet_button.vue
@@ -84,7 +84,6 @@
<script src="./retweet_button.js"></script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.RetweetButton {
@@ -107,8 +106,7 @@
&:hover .svg-inline--fa,
&.-repeated .svg-inline--fa {
- color: $fallback--cGreen;
- color: var(--cGreen, $fallback--cGreen);
+ color: var(--cGreen);
}
@include unfocused-style {
diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx
@@ -79,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
@@ -277,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,10 +1,19 @@
-@import "../../variables";
-
.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 0.2em;
font-style: italic;
- border-left: 0.2em solid var(--faint, $fallback--faint);
+ border-left: 0.2em solid var(--textFaint);
padding-left: 1em;
}
@@ -17,7 +26,7 @@
kbd,
var,
pre {
- font-family: var(--postCodeFont, monospace);
+ font-family: var(--monoFont);
}
p {
@@ -65,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,55 @@
+export default {
+ name: 'Root',
+ selector: ':root',
+ notEditable: true,
+ validInnerComponents: [
+ // These are purely for --parent--text et such to work
+ 'Text',
+ 'Link',
+ 'Border',
+
+ '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,8 +64,6 @@
<script src="./scope_selector.js"></script>
<style lang="scss">
-@import "../../variables";
-
.ScopeSelector {
.scope {
display: inline-block;
@@ -73,11 +71,6 @@
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
@@ -2,7 +2,7 @@ const ScreenReaderNotice = {
props: {
ariaLive: {
type: String,
- defualt: 'assertive'
+ default: 'assertive'
}
},
data () {
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.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)"
>
@@ -23,7 +23,7 @@
</div>
<div
v-if="loading && statusesOffset == 0"
- class="text-center loading-icon"
+ class="panel-body text-center loading-icon"
>
<FAIcon
icon="circle-notch"
@@ -67,7 +67,7 @@
/>
<button
v-if="!loading && loaded && lastStatusFetchCount > 0"
- class="more-statuses-button button-unstyled -link -fullwidth"
+ class="more-statuses-button button-unstyled -link"
@click.prevent="search(searchTerm, 'statuses')"
>
<div class="new-status-notification text-center">
@@ -148,11 +148,8 @@
<script src="./search.js"></script>
<style lang="scss">
-@import "../../variables";
-
.search-result-heading {
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+ color: var(--faint);
padding: 0.75rem;
text-align: center;
}
@@ -171,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;
- 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 {
@@ -212,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;
@@ -226,14 +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.vue b/src/components/search_bar/search_bar.vue
@@ -22,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)"
@@ -60,8 +60,6 @@
<script src="./search_bar.js"></script>
<style lang="scss">
-@import "../../variables";
-
.SearchBar {
display: inline-flex;
align-items: baseline;
@@ -86,8 +84,7 @@
}
.cancel-icon {
- color: $fallback--text;
- color: var(--btnTopBarText, $fallback--text);
+ color: var(--text);
}
}
diff --git a/src/components/select/select.vue b/src/components/select/select.vue
@@ -6,13 +6,14 @@
<select
:disabled="disabled"
:value="modelValue"
- v-bind="attrs"
+ 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"
/>
@@ -22,8 +23,6 @@
<script src="./select.js"> </script>
<style lang="scss">
-@import "../../variables";
-
/* TODO fix order of styles */
label.Select {
padding: 0;
@@ -32,17 +31,48 @@ label.Select {
appearance: none;
background: transparent;
border: none;
- color: $fallback--text;
- color: var(--inputText, --text, $fallback--text);
+ color: var(--text);
margin: 0;
padding: 0 2em 0 0.2em;
- font-family: sans-serif;
- font-family: var(--inputFont, sans-serif);
+ 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 {
@@ -52,10 +82,9 @@ 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;
}
}
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 #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
@@ -51,9 +54,11 @@
<script src="./selectable_list.js"></script>
<style lang="scss">
-@import "../../variables";
-
.selectable-list {
+ --__line-height: 1.5em;
+ --__horizontal-gap: 0.75em;
+ --__vertical-gap: 0.5em;
+
&-item-inner {
display: flex;
align-items: center;
@@ -63,24 +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;
@@ -88,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.scss b/src/components/settings_modal/admin_tabs/emoji_tab.scss
@@ -1,5 +1,3 @@
-@import "src/variables";
-
.emoji-tab {
.btn-group .btn:not(:first-child) {
margin-left: 0.5em;
@@ -25,7 +23,7 @@
}
.emoji-unsaved {
- box-shadow: 0 3px 5px var(--cBlue, $fallback--cBlue);
+ box-shadow: 0 3px 5px var(--cBlue);
}
.emoji-list {
@@ -56,6 +54,6 @@
}
.warning {
- color: var(--cOrange, $fallback--cOrange);
+ 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
@@ -13,13 +13,15 @@
<button
class="button button-default btn"
type="button"
- @click="reloadEmoji">
+ @click="reloadEmoji"
+ >
{{ $t('admin_dash.emoji.reload') }}
</button>
<button
class="button button-default btn"
type="button"
- @click="importFromFS">
+ @click="importFromFS"
+ >
{{ $t('admin_dash.emoji.importFS') }}
</button>
</li>
@@ -28,7 +30,8 @@
<button
class="button button-default btn"
type="button"
- @click="$refs.remotePackPopover.showPopover">
+ @click="$refs.remotePackPopover.showPopover"
+ >
{{ $t('admin_dash.emoji.remote_packs') }}
<Popover
@@ -43,11 +46,16 @@
<template #content>
<div class="emoji-tab-popover-input">
<h3>{{ $t('admin_dash.emoji.remote_pack_instance') }}</h3>
- <input v-model="remotePackInstance" :placeholder="$t('admin_dash.emoji.remote_pack_instance')">
+ <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">
+ @click="listRemotePacks"
+ >
{{ $t('admin_dash.emoji.do_list') }}
</button>
</div>
@@ -61,9 +69,22 @@
<li>
<h4>{{ $t('admin_dash.emoji.edit_pack') }}</h4>
- <Select class="form-control" v-model="packName">
- <option value="" disabled hidden>{{ $t('admin_dash.emoji.emoji_pack') }}</option>
- <option v-for="(pack, listPackName) in knownPacks" :label="listPackName" :key="listPackName">
+ <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>
@@ -71,7 +92,8 @@
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
- @click="$refs.createPackPopover.showPopover">
+ @click="$refs.createPackPopover.showPopover"
+ >
{{ $t('admin_dash.emoji.create_pack') }}
</button>
<Popover
@@ -86,11 +108,16 @@
<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')">
+ <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">
+ @click="createEmojiPack"
+ >
{{ $t('admin_dash.emoji.create') }}
</button>
</div>
@@ -105,67 +132,96 @@
<li>
<label>
{{ $t('admin_dash.emoji.description') }}
- <ModifiedIndicator :changed="metaEdited('description')" message-key="admin_dash.emoji.metadata_changed" />
+ <ModifiedIndicator
+ :changed="metaEdited('description')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
<textarea
v-model="packMeta.description"
:disabled="pack.remote !== undefined"
- class="bio resize-height" />
+ 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" />
+ <ModifiedIndicator
+ :changed="metaEdited('homepage')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
- <input
- class="emoji-info-input" v-model="packMeta.homepage"
- :disabled="pack.remote !== undefined">
+ <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" />
+ <ModifiedIndicator
+ :changed="metaEdited('fallback-src')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
- <input class="emoji-info-input" v-model="packMeta['fallback-src']" :disabled="pack.remote !== undefined">
+ <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 :disabled="true" class="emoji-info-input" v-model="packMeta['fallback-src-sha256']">
+ <input
+ v-model="packMeta['fallback-src-sha256']"
+ :disabled="true"
+ class="emoji-info-input input"
+ >
</label>
</li>
<li>
- <Checkbox :disabled="pack.remote !== undefined" v-model="packMeta['share-files']">
+ <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" />
+ <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"
- v-if="pack.remote === undefined"
- @click="savePackMetadata">
+ @click="savePackMetadata"
+ >
{{ $t('admin_dash.emoji.save_meta') }}
</button>
<button
+ v-if="pack.remote === undefined"
class="button button-default btn"
type="button"
- v-if="pack.remote === undefined"
- @click="savePackMetadata">
+ @click="savePackMetadata"
+ >
{{ $t('admin_dash.emoji.revert_meta') }}
</button>
<button
- class="button button-default btn"
v-if="pack.remote === undefined"
+ class="button button-default btn"
type="button"
- @click="deleteModalVisible = true">
+ @click="deleteModalVisible = true"
+ >
{{ $t('admin_dash.emoji.delete_pack') }}
<ConfirmModal
@@ -174,16 +230,18 @@
:cancel-text="$t('status.delete_confirm_cancel_button')"
:confirm-text="$t('status.delete_confirm_accept_button')"
@cancelled="deleteModalVisible = false"
- @accepted="deleteEmojiPack" >
+ @accepted="deleteEmojiPack"
+ >
{{ $t('admin_dash.emoji.delete_confirm', [packName]) }}
</ConfirmModal>
</button>
<button
+ v-if="pack.remote !== undefined"
class="button button-default btn"
type="button"
- v-if="pack.remote !== undefined"
- @click="$refs.dlPackPopover.showPopover">
+ @click="$refs.dlPackPopover.showPopover"
+ >
{{ $t('admin_dash.emoji.download_pack') }}
<Popover
@@ -202,12 +260,17 @@
<div class="emoji-tab-popover-input">
<label>
{{ $t('admin_dash.emoji.download_as_name') }}
- <input class="emoji-data-input"
+ <input
v-model="remotePackDownloadAs"
- :placeholder="$t('admin_dash.emoji.download_as_name_full')">
+ class="emoji-data-input input"
+ :placeholder="$t('admin_dash.emoji.download_as_name_full')"
+ >
</label>
- <div v-if="downloadWillReplaceLocal" class="warning">
+ <div
+ v-if="downloadWillReplaceLocal"
+ class="warning"
+ >
<em>{{ $t('admin_dash.emoji.replace_warning') }}</em>
</div>
</div>
@@ -215,7 +278,8 @@
<button
class="button button-default btn"
type="button"
- @click="downloadRemotePack">
+ @click="downloadRemotePack"
+ >
{{ $t('admin_dash.emoji.download') }}
</button>
</div>
@@ -231,31 +295,47 @@
<h4>
{{ $t('admin_dash.emoji.files') }}
- <ModifiedIndicator v-if="pack"
+ <ModifiedIndicator
+ v-if="pack"
:changed="$refs.emojiPopovers && $refs.emojiPopovers.some(p => p.isEdited)"
- message-key="admin_dash.emoji.emoji_changed"/>
+ message-key="admin_dash.emoji.emoji_changed"
+ />
</h4>
- <div class="emoji-list" v-if="pack">
+ <div
+ v-if="pack"
+ class="emoji-list"
+ >
<EmojiEditingPopover
v-if="pack.remote === undefined"
- placement="bottom" new-upload
+ placement="bottom"
+ new-upload
:title="$t('admin_dash.emoji.adding_new')"
- :packName="packName"
- @updatePackFiles="updatePackFiles" @displayError="displayError"
+ :pack-name="packName"
+ @updatePackFiles="updatePackFiles"
+ @displayError="displayError"
>
<template #trigger>
- <FAIcon icon="plus" size="2x" :title="$t('admin_dash.emoji.add_file')" />
+ <FAIcon
+ icon="plus"
+ size="2x"
+ :title="$t('admin_dash.emoji.add_file')"
+ />
</template>
</EmojiEditingPopover>
<EmojiEditingPopover
- placement="top" ref="emojiPopovers"
- v-for="(file, shortcode) in pack.files" :key="shortcode"
+ 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" :packName="packName"
- @updatePackFiles="updatePackFiles" @displayError="displayError"
+ :shortcode="shortcode"
+ :file="file"
+ :pack-name="packName"
+ @updatePackFiles="updatePackFiles"
+ @displayError="displayError"
>
<template #trigger>
<StillImage
diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.vue b/src/components/settings_modal/admin_tabs/frontends_tab.vue
@@ -6,7 +6,10 @@
<div class="setting-item">
<h2>{{ $t('admin_dash.tabs.frontends') }}</h2>
<p>{{ $t('admin_dash.frontend.wip_notice') }}</p>
- <ul class="setting-list" v-if="adminDraft">
+ <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>
@@ -23,12 +26,18 @@
</ul>
</li>
</ul>
- <div v-else class="setting-list">
+ <div
+ v-else
+ class="setting-list"
+ >
{{ $t('admin_dash.frontend.default_frontend_unavail') }}
</div>
<div class="setting-list relative">
- <PanelLoading class="overlay" v-if="working"/>
+ <PanelLoading
+ v-if="working"
+ class="overlay"
+ />
<h3>{{ $t('admin_dash.frontend.available_frontends') }}</h3>
<ul class="cards-list">
<li
@@ -40,11 +49,13 @@
<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>
@@ -107,11 +118,14 @@
<button
v-for="ref in frontend.refs"
:key="ref"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click.prevent="update(frontend, ref)"
@click="close"
>
- <i18n-t keypath="admin_dash.frontend.install_version">
+ <i18n-t
+ keypath="admin_dash.frontend.install_version"
+ scope="global"
+ >
<template #version>
<code>{{ ref }}</code>
</template>
@@ -164,11 +178,14 @@
<button
v-for="ref in frontend.installedRefs || frontend.refs"
:key="ref"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click.prevent="setDefault(frontend, ref)"
@click="close"
>
- <i18n-t keypath="admin_dash.frontend.set_default_version">
+ <i18n-t
+ keypath="admin_dash.frontend.set_default_version"
+ scope="global"
+ >
<template #version>
<code>{{ ref }}</code>
</template>
diff --git a/src/components/settings_modal/admin_tabs/instance_tab.vue b/src/components/settings_modal/admin_tabs/instance_tab.vue
@@ -8,7 +8,10 @@
</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" />
+ <AttachmentSetting
+ compact
+ path=":pleroma.:instance.:favicon"
+ />
</li>
<li>
<StringSetting path=":pleroma.:instance.:email" />
@@ -20,7 +23,10 @@
<StringSetting path=":pleroma.:instance.:short_description" />
</li>
<li>
- <AttachmentSetting compact path=":pleroma.:instance.:instance_thumbnail" />
+ <AttachmentSetting
+ compact
+ path=":pleroma.:instance.:instance_thumbnail"
+ />
</li>
<li>
<AttachmentSetting path=":pleroma.:instance.:background_image" />
diff --git a/src/components/settings_modal/admin_tabs/limits_tab.js b/src/components/settings_modal/admin_tabs/limits_tab.js
@@ -14,7 +14,6 @@ library.add(
)
const LimitsTab = {
- data () {},
components: {
BooleanSetting,
ChoiceSetting,
diff --git a/src/components/settings_modal/helpers/attachment_setting.vue b/src/components/settings_modal/helpers/attachment_setting.vue
@@ -29,7 +29,7 @@
<label for="path">{{ $t('settings.url') }}</label>
<input
:id="path"
- class="string-input"
+ class="input string-input"
:disabled="shouldBeDisabled"
:value="realDraftMode ? draft : state"
@change="update"
@@ -48,18 +48,14 @@
:attachment="attachment"
size="small"
hide-description
- @setMedia="onMedia"
- @naturalSizeLoad="onNaturalSizeLoad"
/>
<div class="controls control-upload">
<MediaUpload
ref="mediaUpload"
class="media-upload-icon"
- :drop-files="dropFiles"
normal-button
:accept-types="acceptTypes"
@uploaded="setMediaFile"
- @upload-failed="uploadFailed"
/>
</div>
</div>
diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue
@@ -1,10 +1,10 @@
<template>
<Popover
+ ref="emojiPopover"
trigger="click"
:placement="placement"
bound-to-selector=".emoji-list"
popover-class="emoji-tab-edit-popover popover-default"
- ref="emojiPopover"
:bound-to="{ x: 'container' }"
:offset="{ y: 5 }"
:disabled="disabled"
@@ -18,23 +18,36 @@
{{ title }}
</h3>
- <StillImage class="emoji" v-if="emojiPreview" :src="emojiPreview" />
- <div v-else class="emoji"></div>
-
- <div class="emoji-tab-popover-input" v-if="newUpload">
+ <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"
- @change="uploadFile = $event.target.files">
+ 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 class="emoji-data-input"
+ <input
v-model="editedShortcode"
- :placeholder="$t('admin_dash.emoji.new_shortcode')">
+ class="emoji-data-input input"
+ :placeholder="$t('admin_dash.emoji.new_shortcode')"
+ >
</label>
</div>
@@ -42,9 +55,11 @@
<label>
{{ $t('admin_dash.emoji.filename') }}
- <input class="emoji-data-input"
+ <input
v-model="editedFile"
- :placeholder="$t('admin_dash.emoji.new_filename')">
+ class="emoji-data-input input"
+ :placeholder="$t('admin_dash.emoji.new_filename')"
+ >
</label>
</div>
@@ -52,7 +67,8 @@
class="button button-default btn"
type="button"
:disabled="newUpload ? uploadFile.length == 0 : !isEdited"
- @click="newUpload ? uploadEmoji() : saveEditedEmoji()">
+ @click="newUpload ? uploadEmoji() : saveEditedEmoji()"
+ >
{{ $t('admin_dash.emoji.save') }}
</button>
@@ -60,13 +76,15 @@
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
- @click="deleteModalVisible = true">
+ @click="deleteModalVisible = true"
+ >
{{ $t('admin_dash.emoji.delete') }}
</button>
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
- @click="revertEmoji">
+ @click="revertEmoji"
+ >
{{ $t('admin_dash.emoji.revert') }}
</button>
<ConfirmModal
@@ -75,7 +93,8 @@
:cancel-text="$t('status.delete_confirm_cancel_button')"
:confirm-text="$t('status.delete_confirm_accept_button')"
@cancelled="deleteModalVisible = false"
- @accepted="deleteEmoji" >
+ @accepted="deleteEmoji"
+ >
{{ $t('admin_dash.emoji.delete_confirm', [shortcode]) }}
</ConfirmModal>
</template>
@@ -91,6 +110,39 @@ 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: [],
@@ -113,7 +165,6 @@ export default {
return !this.newUpload && (this.editedShortcode !== this.shortcode || this.editedFile !== this.file)
}
},
- inject: ['emojiAddr'],
methods: {
saveEditedEmoji () {
if (!this.isEdited) return
@@ -167,29 +218,6 @@ export default {
this.$emit('updatePackFiles', resp)
})
}
- },
- emits: ['updatePackFiles', 'displayError'],
- props: {
- placement: String,
- disabled: {
- type: Boolean,
- default: false
- },
-
- newUpload: Boolean,
-
- title: String,
- packName: String,
- 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: ''
- }
}
}
</script>
diff --git a/src/components/settings_modal/helpers/integer_setting.vue b/src/components/settings_modal/helpers/integer_setting.vue
@@ -1,7 +1,7 @@
<template>
<NumberSetting
v-bind="$attrs"
- truncate="1"
+ :truncate="1"
>
<slot />
</NumberSetting>
diff --git a/src/components/settings_modal/helpers/number_setting.js b/src/components/settings_modal/helpers/number_setting.js
@@ -4,6 +4,21 @@ 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,
diff --git a/src/components/settings_modal/helpers/number_setting.vue b/src/components/settings_modal/helpers/number_setting.vue
@@ -15,9 +15,10 @@
</template>
<slot v-else />
</label>
+ {{ ' ' }}
<input
:id="path"
- class="number-input"
+ class="input number-input"
type="number"
:step="step || 1"
:disabled="shouldBeDisabled"
diff --git a/src/components/settings_modal/helpers/setting.js b/src/components/settings_modal/helpers/setting.js
@@ -10,9 +10,13 @@ export default {
ProfileSettingIndicator
},
props: {
+ modelValue: {
+ type: String,
+ default: null
+ },
path: {
type: [String, Array],
- required: true
+ required: false
},
disabled: {
type: Boolean,
@@ -48,6 +52,10 @@ export default {
draftMode: {
type: Boolean,
default: undefined
+ },
+ timedApplyMode: {
+ type: Boolean,
+ default: false
}
},
inject: {
@@ -64,7 +72,7 @@ export default {
}
},
created () {
- if (this.realDraftMode && this.realSource !== 'admin') {
+ if (this.realDraftMode && (this.realSource !== 'admin' || this.path == null)) {
this.draft = this.state
}
},
@@ -72,14 +80,14 @@ export default {
draft: {
// TODO allow passing shared draft object?
get () {
- if (this.realSource === 'admin') {
+ 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') {
+ if (this.realSource === 'admin' || this.path == null) {
this.$store.commit('updateAdminDraft', { path: this.canonPath, value })
} else {
this.localDraft = value
@@ -87,6 +95,9 @@ export default {
}
},
state () {
+ if (this.path == null) {
+ return this.modelValue
+ }
const value = get(this.configSource, this.canonPath)
if (value === undefined) {
return this.defaultState
@@ -141,6 +152,9 @@ export default {
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)
},
@@ -155,13 +169,20 @@ export default {
}
},
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:
- return (k, v) => this.$store.dispatch('setOption', { name: k, value: v })
+ 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 () {
@@ -176,6 +197,7 @@ export default {
return this.realSource === 'profile'
},
isChanged () {
+ if (this.path == null) return false
switch (this.realSource) {
case 'profile':
case 'admin':
@@ -185,9 +207,11 @@ export default {
}
},
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 {
diff --git a/src/components/settings_modal/helpers/size_setting.js b/src/components/settings_modal/helpers/size_setting.js
@@ -1,40 +0,0 @@
-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
- }
- },
- computed: {
- ...Setting.computed,
- stateUnit () {
- return this.state.replace(/\d+/, '')
- },
- stateValue () {
- return this.state.replace(/\D+/, '')
- }
- },
- methods: {
- ...Setting.methods,
- updateValue (e) {
- this.configSink(this.path, parseInt(e.target.value) + this.stateUnit)
- },
- updateUnit (e) {
- this.configSink(this.path, this.stateValue + e.target.value)
- }
- }
-}
diff --git a/src/components/settings_modal/helpers/size_setting.vue b/src/components/settings_modal/helpers/size_setting.vue
@@ -1,62 +0,0 @@
-<template>
- <span
- v-if="matchesExpertLevel"
- class="SizeSetting"
- >
- <label
- :for="path"
- class="size-label"
- >
- <slot />
- </label>
- <input
- :id="path"
- class="number-input"
- type="number"
- step="1"
- :disabled="disabled"
- :min="min || 0"
- :value="stateValue"
- @change="updateValue"
- >
- <Select
- :id="path"
- :model-value="stateUnit"
- :disabled="disabled"
- class="css-unit-input"
- @change="updateUnit"
- >
- <option
- v-for="option in units"
- :key="option"
- :value="option"
- >
- {{ option }}
- </option>
- </Select>
- {{ ' ' }}
- <ModifiedIndicator
- :changed="isChanged"
- :onclick="reset"
- />
- </span>
-</template>
-
-<script src="./size_setting.js"></script>
-
-<style lang="scss">
-.SizeSetting {
- .number-input {
- max-width: 6.5em;
- }
-
- .css-unit-input,
- .css-unit-input select {
- margin-left: 0.5em;
- width: 4em;
- max-width: 4em;
- min-width: 4em;
- }
-}
-
-</style>
diff --git a/src/components/settings_modal/helpers/string_setting.vue b/src/components/settings_modal/helpers/string_setting.vue
@@ -5,6 +5,7 @@
>
<label
:for="path"
+ class="setting-label"
:class="{ 'faint': shouldBeDisabled }"
>
<template v-if="backendDescriptionLabel">
@@ -15,9 +16,10 @@
</template>
<slot v-else />
</label>
+ {{ ' ' }}
<input
:id="path"
- class="string-input"
+ class="input string-input"
:disabled="shouldBeDisabled"
:value="realDraftMode ? draft : state"
@change="update"
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,6 +4,7 @@ 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, isEqual } from 'lodash'
import {
@@ -53,6 +54,7 @@ const SettingsModal = {
Modal,
Popover,
Checkbox,
+ ConfirmModal,
SettingsModalUserContent: getResettableAsyncComponent(
() => import('./settings_modal_user_content.vue'),
{
diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss
@@ -1,5 +1,3 @@
-@import "src/variables";
-
.settings-modal {
overflow: hidden;
@@ -12,6 +10,10 @@
list-style-type: none;
padding-left: 2em;
+ .btn:not(.dropdown-button) {
+ padding: 0 2em;
+ }
+
li {
margin-bottom: 0.5em;
}
@@ -56,10 +58,6 @@
.btn {
min-height: 2em;
}
-
- .btn:not(.dropdown-button) {
- padding: 0 2em;
- }
}
}
@@ -78,6 +76,23 @@
}
}
+ &.-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:
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">
+ <h1 class="title">
{{ modalMode === 'user' ? $t('settings.settings') : $t('admin_dash.window_title') }}
- </span>
+ </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') }}
@@ -70,7 +70,7 @@
<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"
>
@@ -80,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"
>
@@ -90,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"
>
@@ -110,7 +110,10 @@
{{ $t("settings.expert_mode") }}
</Checkbox>
<span v-if="modalMode === 'admin'">
- <i18n-t keypath="admin_dash.wip_notice">
+ <i18n-t
+ scope="global"
+ keypath="admin_dash.wip_notice"
+ >
<template #adminFeLink>
<a
href="/pleroma/admin/#/login-pleroma"
@@ -147,6 +150,18 @@
</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.scss b/src/components/settings_modal/settings_modal_admin_content.scss
@@ -1,10 +1,8 @@
-@import "src/variables";
-
.settings_tab-switcher {
height: 100%;
.setting-item {
- border-bottom: 2px solid var(--fg, $fallback--fg);
+ border-bottom: 2px solid var(--border);
margin: 1em 1em 1.4em;
padding-bottom: 1.4em;
@@ -19,10 +17,13 @@
}
.select-multiple {
+ margin-top: 0.5em;
display: flex;
+ flex-direction: column;
.option-list {
margin: 0;
+ margin-top: 0.5em;
padding-left: 0.5em;
}
}
@@ -33,10 +34,6 @@
margin-bottom: 1em;
}
- select {
- min-width: 10em;
- }
-
textarea {
width: 100%;
max-width: 100%;
@@ -45,8 +42,7 @@
.unavailable,
.unavailable svg {
- color: var(--cRed, $fallback--cRed);
- color: $fallback--cRed;
+ 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
@@ -17,7 +17,10 @@
<div :label="$t('admin_dash.tabs.nodb')">
<div class="setting-item">
<h2>{{ $t('admin_dash.nodb.heading') }}</h2>
- <i18n-t keypath="admin_dash.nodb.text">
+ <i18n-t
+ scope="global"
+ keypath="admin_dash.nodb.text"
+ >
<template #documentation>
<a
href="https://docs-develop.pleroma.social/backend/configuration/howto_database_config/"
diff --git a/src/components/settings_modal/settings_modal_user_content.js b/src/components/settings_modal/settings_modal_user_content.js
@@ -7,8 +7,10 @@ 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 {
@@ -16,10 +18,12 @@ import {
faUser,
faFilter,
faPaintBrush,
+ faPalette,
faBell,
faDownload,
faEyeSlash,
- faInfo
+ faInfo,
+ faWindowRestore
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -27,10 +31,12 @@ library.add(
faUser,
faFilter,
faPaintBrush,
+ faPalette,
faBell,
faDownload,
faEyeSlash,
- faInfo
+ faInfo,
+ faWindowRestore
)
const SettingsModalContent = {
@@ -44,6 +50,8 @@ const SettingsModalContent = {
SecurityTab,
ProfileTab,
GeneralTab,
+ AppearanceTab,
+ StyleTab,
VersionTab,
ThemeTab
},
@@ -56,6 +64,12 @@ const SettingsModalContent = {
},
bodyLock () {
return this.$store.state.interface.settingsModalState === 'visible'
+ },
+ expertLevel () {
+ return this.$store.state.config.expertLevel
+ },
+ isMobileLayout () {
+ return this.$store.state.interface.layoutType === 'mobile'
}
},
methods: {
diff --git a/src/components/settings_modal/settings_modal_user_content.scss b/src/components/settings_modal/settings_modal_user_content.scss
@@ -1,16 +1,28 @@
-@import "src/variables";
-
.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(--fg, $fallback--fg);
+ 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 {
@@ -19,10 +31,13 @@
}
.select-multiple {
+ margin-top: 1em;
display: flex;
+ flex-direction: column;
.option-list {
margin: 0;
+ margin-top: 0.5em;
padding-left: 0.5em;
}
}
@@ -33,10 +48,6 @@
margin-bottom: 1em;
}
- select {
- min-width: 10em;
- }
-
textarea {
width: 100%;
max-width: 100%;
@@ -45,8 +56,7 @@
.unavailable,
.unavailable svg {
- color: var(--cRed, $fallback--cRed);
- color: $fallback--cRed;
+ 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
@@ -14,6 +14,32 @@
<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"
@@ -23,6 +49,14 @@
</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"
@@ -37,19 +71,13 @@
<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"
+ :label="$t('settings.mutes_and_blocks')"
+ :fullHeight="true"
+ icon="eye-slash"
+ data-tab-name="mutesAndBlocks"
>
- <NotificationsTab />
+ <MutesAndBlocksTab />
</div>
<div
v-if="isLoggedIn"
@@ -60,15 +88,6 @@
<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"
diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js
@@ -0,0 +1,425 @@
+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, .iss',
+ 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: {
+ switchInProgress () {
+ return this.$store.state.interface.themeChangeInProgress
+ },
+ 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('.iss')) {
+ return deserialize(file)
+ }
+ },
+ onImport (parsed, filename) {
+ if (filename.endsWith('.json')) {
+ this.$store.dispatch('setThemeCustom', parsed.source || parsed.theme)
+ } else if (filename.endsWith('.iss')) {
+ 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('.iss')) {
+ 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,144 @@
+.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;
+ }
+ }
+ }
+
+ h4 {
+ margin: 0.5em 0;
+ }
+
+ .palettes-container {
+ height: 15em;
+ overflow-y: auto;
+ overflow-x: hidden;
+ scrollbar-gutter: stable;
+ border-radius: var(--roundness);
+ border: 1px solid var(--border);
+ margin: -0.5em;
+ }
+
+ .palettes {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-gap: 0.5em;
+ padding: 0.5em;
+ width: 100%;
+
+ h4 {
+ margin: 0;
+ grid-column: 1 / span 2;
+ }
+ }
+
+ .palette-entry {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 0.5em;
+ height: max-content;
+
+ .palette-label {
+ height: auto;
+
+ 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 & {
+ .palettes {
+ grid-template-columns: 1fr;
+ }
+
+ .palette-entry {
+ grid-column: 1;
+ justify-content: center;
+ }
+
+ .palette-label {
+ line-height: 1.5em;
+ margin-top: 0.5em;
+ }
+ }
+
+ .palette-preview {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ grid-template-rows: 1em 1em;
+ margin: 0.5em 0;
+ }
+
+ .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,395 @@
+<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"
+ @click="resetTheming"
+ :class="{ toggled: isStyleActive('stock'), disabled: switchInProgress }"
+ :disabled="switchInProgress"
+ >
+ <!-- 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: isThemeActive(style.key), disabled: switchInProgress }"
+ @click="style.version === 'v2' ? setTheme(style.key) : setStyle(style.key)"
+ :disabled="switchInProgress"
+ >
+ <!-- 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"
+ :class="{ disabled: switchInProgress }"
+ :disabled="switchInProgress"
+ >
+ <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
+ v-if="customThemeVersion === 'v3'"
+ class="palettes-container"
+ >
+ <h4 v-if="stylePalettes?.length > 0">
+ {{ $t('settings.style.themes3.palette.style') }}
+ </h4>
+ <div class="palettes">
+ <button
+ v-for="p in stylePalettes || []"
+ :key="p.name"
+ class="btn button-default palette-entry"
+ :class="{ toggled: isPaletteActive(p.key), disabled: switchInProgress }"
+ :disabled="switchInProgress"
+ @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>{{ $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), disabled: switchInProgress }"
+ :disabled="switchInProgress"
+ @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>
+ </div>
+ </div>
+ <div>
+ <template v-if="customThemeVersion === 'v3'">
+ <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)"
+ :disabled="switchInProgress"
+ />
+ </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>
+ <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.style.interface_font_user_override') }}</h3>
+ <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>
+ <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>
+ <BooleanSetting path="modalMobileCenter">
+ {{ $t('settings.mobile_center_dialog') }}
+ </BooleanSetting>
+ </li>
+ <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
@@ -80,7 +80,7 @@
<span
v-else-if="backup.state === 'running'"
>
- {{ $tc('settings.backup_running', backup.processed_number, { number: backup.processed_number }) }}
+ {{ $t('settings.backup_running', { number: backup.processed_number }, backup.processed_number) }}
</span>
<span
v-else-if="backup.state === 'failed'"
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, 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: {
diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue
@@ -45,6 +45,11 @@
</BooleanSetting>
</li>
<li>
+ <BooleanSetting path="muteSensitiveStatuses">
+ {{ $t('settings.mute_sensitive_posts') }}
+ </BooleanSetting>
+ </li>
+ <li>
<BooleanSetting path="hidePostStats">
{{ $t('settings.hide_post_stats') }}
</BooleanSetting>
@@ -67,7 +72,7 @@
<textarea
id="muteWords"
v-model="muteWordsString"
- class="resize-height"
+ class="input resize-height"
/>
<div>{{ $t('settings.filtering_explanation') }}</div>
</li>
@@ -96,6 +101,17 @@
{{ $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
@@ -3,7 +3,7 @@ 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 SizeSetting, { defaultHorizontalUnits } from '../helpers/size_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'
@@ -40,15 +40,15 @@ 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}`)
})),
- userPopoverAvatarActionOptions: ['close', 'zoom', 'open'].map(mode => ({
+ unsavedPostActionOptions: ['save', 'discard', 'confirm'].map(mode => ({
key: mode,
value: mode,
- label: this.$t(`settings.user_popover_avatar_action_${mode}`)
+ label: this.$t(`settings.unsaved_post_action_${mode}`)
})),
loopSilentAvailable:
// Firefox
@@ -64,15 +64,12 @@ const GeneralTab = {
ChoiceSetting,
IntegerSetting,
FloatSetting,
- SizeSetting,
+ UnitSetting,
InterfaceLanguageSwitcher,
ScopeSelector,
ProfileSettingIndicator
},
computed: {
- horizontalUnits () {
- return defaultHorizontalUnits
- },
postFormats () {
return this.$store.state.instance.postFormats || []
},
@@ -83,29 +80,14 @@ const GeneralTab = {
label: this.$t(`post_status.content_type["${format}"]`)
}))
},
- 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]
- }
- },
- 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: {
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
@@ -15,11 +15,6 @@
{{ $t('settings.hide_isp') }}
</BooleanSetting>
</li>
- <li v-if="instanceWallpaperUsed">
- <BooleanSetting path="hideInstanceWallpaper">
- {{ $t('settings.hide_wallpaper') }}
- </BooleanSetting>
- </li>
<li>
<BooleanSetting path="stopGifs">
{{ $t('settings.stop_gifs') }}
@@ -98,53 +93,6 @@
{{ $t('settings.hide_shoutbox') }}
</BooleanSetting>
</li>
- <li>
- <h3>{{ $t('settings.columns') }}</h3>
- </li>
- <li>
- <BooleanSetting path="disableStickyHeaders">
- {{ $t('settings.disable_sticky_headers') }}
- </BooleanSetting>
- </li>
- <li>
- <BooleanSetting path="showScrollbars">
- {{ $t('settings.show_scrollbars') }}
- </BooleanSetting>
- </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">
- <SizeSetting
- v-for="column in columns"
- :key="column"
- :path="column + 'ColumnWidth'"
- :units="horizontalUnits"
- expert="1"
- >
- {{ $t('settings.column_sizes_' + column) }}
- </SizeSetting>
- </div>
- </li>
<li class="select-multiple">
<span class="label">{{ $t('settings.confirm_dialogs') }}</span>
<ul class="option-list">
@@ -270,14 +218,28 @@
</BooleanSetting>
</li>
<li>
- <FloatSetting
- v-if="user"
- path="emojiReactionsScale"
+ <BooleanSetting
+ path="useAbsoluteTimeFormat"
expert="1"
>
- {{ $t('settings.emoji_reactions_scale') }}
- </FloatSetting>
+ {{ $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
@@ -514,23 +476,25 @@
{{ $t('settings.autocomplete_select_first') }}
</BooleanSetting>
</li>
+ <li>
+ <BooleanSetting
+ path="autoSaveDraft"
+ >
+ {{ $t('settings.auto_save_draft') }}
+ </BooleanSetting>
+ </li>
+ <li v-if="!autoSaveDraft">
+ <ChoiceSetting
+ id="unsavedPostAction"
+ path="unsavedPostAction"
+ :options="unsavedPostActionOptions"
+ >
+ {{ $t('settings.unsaved_post_action') }}
+ </ChoiceSetting>
+ </li>
</ul>
</div>
</div>
</template>
<script src="./general_tab.js"></script>
-
-<style lang="scss">
-.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;
-}
-</style>
diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue
@@ -19,7 +19,10 @@
</div>
</li>
<li>
- <BooleanSetting path="unseenAtTop" expert="1">
+ <BooleanSetting
+ path="unseenAtTop"
+ expert="1"
+ >
{{ $t('settings.notification_setting_unseen_at_top') }}
</BooleanSetting>
</li>
@@ -38,7 +41,9 @@
</li>
<li>
<h3> {{ $t('settings.notification_visibility') }}</h3>
- <p v-if="expertLevel > 0">{{ $t('settings.notification_setting_filters_chrome_push') }}</p>
+ <p v-if="expertLevel > 0">
+ {{ $t('settings.notification_setting_filters_chrome_push') }}
+ </p>
<ul class="setting-list two-column">
<li>
<h4> {{ $t('settings.notification_visibility_mentions') }}</h4>
@@ -56,6 +61,21 @@
</ul>
</li>
<li>
+ <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>
<h4> {{ $t('settings.notification_visibility_likes') }}</h4>
<ul class="setting-list">
<li>
diff --git a/src/components/settings_modal/tabs/profile_tab.scss b/src/components/settings_modal/tabs/profile_tab.scss
@@ -1,5 +1,3 @@
-@import "../../../variables";
-
.profile-tab {
.bio {
margin: 0;
@@ -43,16 +41,14 @@
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);
+ border-radius: var(--roundness);
background-color: rgb(0 0 0 / 60%);
opacity: 0.7;
width: 1.5em;
diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue
@@ -12,7 +12,7 @@
<input
id="username"
v-model="newName"
- class="name-changer"
+ class="input name-changer"
v-bind="propsToNative(inputProps)"
>
</template>
@@ -26,7 +26,7 @@
<template #default="inputProps">
<textarea
v-model="newBio"
- class="bio resize-height"
+ class="input bio resize-height"
v-bind="propsToNative(inputProps)"
/>
</template>
@@ -47,7 +47,7 @@
id="birthday"
v-model="newBirthday"
type="date"
- class="birthday-input"
+ class="input birthday-input"
>
<Checkbox v-model="showBirthday">
{{ $t('settings.birthday.show_birthday') }}
@@ -71,6 +71,7 @@
v-model="newFields[i].name"
:placeholder="$t('settings.profile_fields.name')"
v-bind="propsToNative(inputProps)"
+ class="input"
>
</template>
</EmojiInput>
@@ -85,6 +86,7 @@
v-model="newFields[i].value"
:placeholder="$t('settings.profile_fields.value')"
v-bind="propsToNative(inputProps)"
+ class="input"
>
</template>
</EmojiInput>
@@ -205,6 +207,7 @@
<div>
<input
type="file"
+ class="input"
@change="uploadFile('banner', $event)"
>
</div>
@@ -247,6 +250,7 @@
<div>
<input
type="file"
+ class="input"
@change="uploadFile('background', $event)"
>
</div>
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,8 +139,6 @@
<script src="./mfa.js"></script>
<style lang="scss">
-@import "../../../../variables";
-
.mfa-settings {
.mfa-heading,
.method-item {
@@ -149,8 +149,7 @@
}
.warning {
- color: $fallback--cOrange;
- color: var(--cOrange, $fallback--cOrange);
+ color: var(--cOrange);
}
.setup-otp {
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,16 +21,13 @@
</template>
<script src="./mfa_backup_codes.js"></script>
<style lang="scss">
-@import "../../../../variables";
-
.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.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.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
@@ -144,6 +149,7 @@
</div>
<div>
<i18n-t
+ scope="global"
keypath="settings.new_alias_target"
tag="p"
>
@@ -155,6 +161,7 @@
</i18n-t>
<input
v-model="addAliasTarget"
+ class="input"
>
</div>
<button
@@ -178,6 +185,7 @@
<i18n-t
keypath="settings.move_account_target"
tag="p"
+ scope="global"
>
<template #example>
<code>
@@ -187,6 +195,7 @@
</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"
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: 'iss',
+ 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: '.iss',
+ 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
+ v-if="selectedVirtualDirectiveValType === 'color'"
+ v-model="draftVirtualDirective"
+ name="virtual-directive-color"
+ :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,149 +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,
@@ -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]
}
@@ -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 {
@@ -306,13 +295,8 @@ export default {
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()
@@ -323,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]
}
@@ -410,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
@@ -488,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
@@ -511,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,
@@ -530,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() },
@@ -608,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 {
@@ -690,6 +678,8 @@ export default {
} else {
this.shadowsLocal = shadows
}
+ this.updatePreviewColors()
+ this.updatePreviewShadows()
this.shadowSelected = this.shadowsAvailable[0]
}
@@ -697,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
@@ -711,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
@@ -725,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
@@ -736,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)
}
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,6 +1,9 @@
-@import "src/variables";
-
.theme-tab {
+ .deprecation-warning {
+ padding: 0.5em;
+ margin: 2em;
+ }
+
padding-bottom: 2em;
.preset-switcher {
@@ -12,13 +15,19 @@
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;
+ line-height: 2;
}
.opt {
@@ -36,20 +45,23 @@
flex: 0;
&[type="number"] {
- min-width: 5em;
+ min-width: 9em;
+
+ &.-small {
+ min-width: 5em;
+ }
}
&[type="range"] {
flex: 1;
- min-width: 3em;
- align-self: flex-start;
+ min-width: 9em;
+ align-self: center;
+ margin: 0 0.5em;
}
- }
- &.disabled {
- input,
- select {
- opacity: 0.5;
+ &[type="checkbox"] + i {
+ height: 1.1em;
+ align-self: center;
}
}
}
@@ -159,111 +171,6 @@
}
}
- .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: 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;
- 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;
}
@@ -296,7 +203,7 @@
border: 0;
box-shadow: none;
background: transparent;
- color: var(--faint, $fallback--faint);
+ color: var(--textFaint);
align-self: stretch;
}
@@ -316,10 +223,6 @@
max-width: 50em;
}
- .theme-preview-content {
- padding: 20px;
- }
-
.theme-warning {
display: flex;
align-items: baseline;
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,105 +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,334 +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 visible-for-screenreader-only"
- type="checkbox"
- >
- <label
- class="checkbox-label"
- for="inset"
- :aria-hidden="true"
+ </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";
-
-.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: 0.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: #999;
- display: flex;
- align-items: center;
- justify-content: center;
- background-image:
- linear-gradient(45deg, #666 25%, transparent 25%),
- linear-gradient(-45deg, #666 25%, transparent 25%),
- linear-gradient(45deg, transparent 75%, #666 75%),
- linear-gradient(-45deg, transparent 75%, #666 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;
- }
-
- .btn {
- padding: 0 0.4em;
- margin: 0 0.1em;
- }
- }
- }
-}
-</style>
+<style src="./shadow_control.scss" lang="scss"></style>
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,8 +75,6 @@
<script src="./shout_panel.js"></script>
<style lang="scss">
-@import "../../variables";
-
.floating-shout {
position: fixed;
bottom: 0.5em;
@@ -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
@@ -17,7 +17,8 @@ import {
faCog,
faInfoCircle,
faCompass,
- faList
+ faList,
+ faFilePen
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -33,7 +34,8 @@ library.add(
faCog,
faInfoCircle,
faCompass,
- faList
+ faList,
+ faFilePen
)
const SideDrawer = {
@@ -98,7 +100,7 @@ const SideDrawer = {
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
supportsAnnouncements: state => state.announcements.supportsAnnouncements
}),
- ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
+ ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount', 'draftCount'])
},
methods: {
toggleDrawer () {
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="timelinesRoute">
+ <router-link
+ :to="timelinesRoute"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -59,7 +65,10 @@
v-if="currentUser"
@click="toggleDrawer"
>
- <router-link :to="{ name: 'lists' }">
+ <router-link
+ :to="{ name: 'lists' }"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -68,12 +77,28 @@
</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;"
+ class="menu-item"
>
<FAIcon
fixed-width
@@ -82,7 +107,7 @@
/> {{ $t("nav.chats") }}
<span
v-if="unreadChatCount"
- class="badge badge-notification"
+ class="badge -notification"
>
{{ unreadChatCount }}
</span>
@@ -91,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"
@@ -103,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"
@@ -111,7 +142,7 @@
/> {{ $t("nav.friend_requests") }}
<span
v-if="followRequestCount > 0"
- class="badge badge-notification"
+ class="badge -notification"
>
{{ followRequestCount }}
</span>
@@ -121,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"
@@ -135,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"
@@ -147,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"
@@ -157,7 +197,7 @@
</li>
<li @click="toggleDrawer">
<button
- class="button-unstyled -link -fullwidth"
+ class="menu-item"
@click="openSettingsModal"
>
<FAIcon
@@ -168,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"
@@ -181,7 +224,7 @@
@click="toggleDrawer"
>
<button
- class="button-unstyled -link -fullwidth"
+ class="menu-item"
@click.stop="openAdminModal"
>
<FAIcon
@@ -197,6 +240,7 @@
>
<router-link
:to="{ name: 'announcements' }"
+ class="menu-item"
>
<FAIcon
fixed-width
@@ -205,7 +249,7 @@
/> {{ $t("nav.announcements") }}
<span
v-if="unreadAnnouncementCount"
- class="badge badge-notification"
+ class="badge -notification"
>
{{ unreadAnnouncementCount }}
</span>
@@ -215,7 +259,31 @@
v-if="currentUser"
@click="toggleDrawer"
>
- <router-link :to="{ name: 'edit-navigation' }">
+ <router-link
+ :to="{ name: 'drafts' }"
+ class="menu-item"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="file-pen"
+ /> {{ $t('nav.drafts') }}
+ <span
+ v-if="draftCount"
+ class="badge -neutral"
+ >
+ {{ draftCount }}
+ </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"
@@ -228,7 +296,7 @@
@click="toggleDrawer"
>
<button
- class="button-unstyled -link -fullwidth"
+ class="menu-item"
@click="doLogout"
>
<FAIcon
@@ -251,8 +319,6 @@
<script src="./side_drawer.js"></script>
<style lang="scss">
-@import "../../variables";
-
.side-drawer-container {
position: fixed;
z-index: var(--ZI_navbar);
@@ -305,17 +371,8 @@
width: 80%;
max-width: 20em;
flex: 0 0 80%;
- box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
- 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;
@@ -362,8 +419,7 @@
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 {
@@ -380,18 +436,6 @@
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/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
@@ -238,6 +238,9 @@ const Status = {
showActorTypeIndicator () {
return !this.hideBotIndication
},
+ sensitiveStatus () {
+ return this.status.nsfw
+ },
mentionsLine () {
if (!this.headTailLinks) return []
const writtenSet = new Set(this.headTailLinks.writtenMentions.map(_ => _.url))
@@ -257,16 +260,46 @@ const Status = {
hasMentionsLine () {
return this.mentionsLine.length > 0
},
+ muteReasons () {
+ return [
+ this.userIsMuted ? 'user' : null,
+ status.thread_muted ? 'thread' : null,
+ (this.muteWordHits.length > 0) ? 'wordfilter' : null,
+ (this.muteBotStatuses && this.botStatus) ? 'bot' : null,
+ (this.muteSensitiveStatuses && this.sensitiveStatus) ? 'nsfw' : null
+ ].filter(_ => _)
+ },
+ muteLocalized () {
+ if (this.muteReasons.length === 0) return null
+ const mainReason = () => {
+ switch (this.muteReasons[0]) {
+ case 'user': return this.$t('status.muted_user')
+ case 'thread': return this.$t('status.thread_muted')
+ case 'wordfilter':
+ return this.$t(
+ 'status.muted_words',
+ {
+ word: this.muteWordHits[0],
+ numWordsMore: this.muteWordHits.length - 1
+ },
+ this.muteWordHits.length
+ )
+ case 'bot': return this.$t('status.bot_muted')
+ case 'nsfw': return this.$t('status.sensitive_muted')
+ }
+ }
+ return this.$t(
+ 'status.multi_reason_mute',
+ {
+ main: mainReason(),
+ numReasonsMore: this.muteReasons.length - 1
+ },
+ this.muteReasons.length - 1
+ )
+ },
muted () {
if (this.statusoid.user.id === this.currentUser.id) return false
- const reasonsToMute = this.userIsMuted ||
- // Thread is muted
- status.thread_muted ||
- // Wordfiltered
- this.muteWordHits.length > 0 ||
- // bot status
- (this.muteBotStatuses && this.botStatus && !this.compact)
- return !this.unmuted && !this.shouldNotMute && reasonsToMute
+ return !this.unmuted && !this.shouldNotMute && this.muteReasons.length > 0
},
userIsMuted () {
if (this.statusoid.user.id === this.currentUser.id) return false
@@ -368,18 +401,21 @@ const Status = {
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
},
currentUser () {
return this.$store.state.users.currentUser
},
- betterShadow () {
- return this.$store.state.interface.browserSupport.cssFilter
- },
mergedConfig () {
return this.$store.getters.mergedConfig
},
@@ -414,7 +450,27 @@ const Status = {
return this.quotedStatus && this.displayQuote
},
scrobblePresent () {
- return !this.mergedConfig.hideScrobbles && this.status.user.latestScrobble && this.status.user.latestScrobble.artist
+ 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
@@ -442,6 +498,13 @@ const Status = {
},
toggleReplying () {
this.$emit('interacted')
+ if (this.replying) {
+ this.$refs.postStatusForm.requestClose()
+ } else {
+ this.doToggleReplying()
+ }
+ },
+ doToggleReplying () {
controlledOrUncontrolledToggle(this, 'replying')
},
gotoOriginal (id) {
diff --git a/src/components/status/status.scss b/src/components/status/status.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.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 {
@@ -243,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;
@@ -289,7 +265,7 @@
position: relative;
width: 100%;
display: flex;
- margin-top: var(--status-margin, $status-margin);
+ margin-top: var(--status-margin);
> * {
max-width: 4em;
@@ -305,17 +281,13 @@
overflow: hidden;
display: flex;
flex-wrap: nowrap;
+ gap: 1ex;
& .status-username,
- & .mute-thread,
- & .mute-words {
+ & .mute-reason {
word-wrap: normal;
word-break: normal;
white-space: nowrap;
- }
-
- & .status-username,
- & .mute-words {
text-overflow: ellipsis;
overflow: hidden;
}
@@ -325,19 +297,7 @@
flex: 0 1 auto;
margin-right: 0.2em;
font-size: smaller;
- }
-
- .mute-thread {
- flex: 0 0 auto;
- }
-
- .mute-words {
- flex: 1 0 5em;
- margin-left: 0.2em;
-
- &::before {
- content: " ";
- }
+ display: flex;
}
.unmute {
@@ -357,7 +317,7 @@
}
.favs-repeated-users {
- margin-top: var(--status-margin, $status-margin);
+ margin-top: var(--status-margin);
}
.stats {
@@ -368,10 +328,10 @@
.avatar-row {
flex: 1;
- overflow: hidden;
position: relative;
display: flex;
align-items: center;
+ overflow: hidden;
&::before {
content: "";
@@ -379,16 +339,16 @@
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;
@@ -398,6 +358,7 @@
font-weight: bolder;
font-size: 1.1em;
line-height: 1em;
+ color: var(--text);
}
&:hover .stat-title {
@@ -425,8 +386,8 @@
.quoted-status {
margin-top: 0.5em;
- border: 1px solid var(--border, $fallback--border);
- border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
+ border: 1px solid var(--border);
+ border-radius: var(--roundness);
&.-unavailable-prompt {
padding: 0.5em;
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
@@ -30,23 +30,8 @@
:at="false"
/>
</small>
- <small
- v-if="showReasonMutedThread"
- class="mute-thread"
- >
- {{ $t('status.thread_muted') }}
- </small>
- <small
- v-if="showReasonMutedThread && muteWordHits.length > 0"
- class="mute-thread"
- >
- {{ $t('status.thread_muted_and_words') }}
- </small>
- <small
- class="mute-words"
- :title="muteWordHits.join(', ')"
- >
- {{ muteWordHits.join(', ') }}
+ <small class="mute-reason">
+ {{ muteLocalized }}
</small>
<button
class="unmute button-unstyled"
@@ -80,7 +65,6 @@
v-if="retweet"
class="left-side repeater-avatar"
:show-actor-type-indicator="showActorTypeIndicator"
- :better-shadow="betterShadow"
:user="statusoid.user"
/>
<div class="right-side faint">
@@ -135,7 +119,6 @@
class="post-avatar"
:show-actor-type-indicator="showActorTypeIndicator"
:compact="compact"
- :better-shadow="betterShadow"
:user="status.user"
/>
</UserPopover>
@@ -180,7 +163,7 @@
<span class="heading-right">
<router-link
- class="timeago faint-link"
+ class="timeago faint"
:to="{ name: 'conversation', params: { id: status.id } }"
>
<Timeago
@@ -298,44 +281,61 @@
v-if="isReply"
class="glued-label reply-glued-label"
>
- <StatusPopover
- v-if="!isPreview"
- :status-id="status.parent_visible && status.in_reply_to_status_id"
- class="reply-to-popover"
- style="min-width: 0;"
- :class="{ '-strikethrough': !status.parent_visible }"
+ <i18n-t
+ keypath="status.reply_to_with_arg"
+ scope="global"
>
- <button
- class="button-unstyled reply-to"
- :aria-label="$t('tool_tip.reply')"
- @click.prevent="gotoOriginal(status.in_reply_to_status_id)"
- >
- <FAIcon
- class="fa-scale-110 fa-old-padding"
- icon="reply"
- flip="horizontal"
- />
- {{ ' ' }}
+ <template #replyToWithIcon>
+ <StatusPopover
+ v-if="!isPreview"
+ :status-id="status.parent_visible && status.in_reply_to_status_id"
+ class="reply-to-popover"
+ style="min-width: 0;"
+ :class="{ '-strikethrough': !status.parent_visible }"
+ >
+ <button
+ class="button-unstyled reply-to"
+ :aria-label="$t('tool_tip.reply')"
+ @click.prevent="gotoOriginal(status.in_reply_to_status_id)"
+ >
+ <i18n-t
+ keypath="status.reply_to_with_icon"
+ scope="global"
+ >
+ <template #icon>
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="reply"
+ flip="horizontal"
+ />
+ </template>
+ <template #replyTo>
+ <span
+ class="reply-to-text"
+ >
+ {{ $t('status.reply_to') }}
+ </span>
+ </template>
+ </i18n-t>
+ </button>
+ </StatusPopover>
+
<span
- class="reply-to-text"
+ v-else
+ class="reply-to-no-popover"
>
- {{ $t('status.reply_to') }}
+ <span class="reply-to-text">{{ $t('status.reply_to') }}</span>
</span>
- </button>
- </StatusPopover>
-
- <span
- v-else
- class="reply-to-no-popover"
- >
- <span class="reply-to-text">{{ $t('status.reply_to') }}</span>
- </span>
- <MentionLink
- :content="replyToName"
- :url="replyProfileLink"
- :user-id="status.in_reply_to_user_id"
- :user-screen-name="status.in_reply_to_screen_name"
- />
+ </template>
+ <template #user>
+ <MentionLink
+ :content="replyToName"
+ :url="replyProfileLink"
+ :user-id="status.in_reply_to_user_id"
+ :user-screen-name="status.in_reply_to_screen_name"
+ />
+ </template>
+ </i18n-t>
</span>
<!-- This little wrapper is made for sole purpose of "gluing" -->
@@ -373,6 +373,7 @@
class="heading-edited-row"
>
<i18n-t
+ scope="global"
keypath="status.edited_at"
tag="span"
>
@@ -430,7 +431,10 @@
v-else-if="hasInvisibleQuote"
class="quoted-status -unavailable-prompt"
>
- <i18n-t keypath="status.invisible_quote">
+ <i18n-t
+ scope="global"
+ keypath="status.invisible_quote"
+ >
<template #link>
<bdi>
<a
@@ -450,11 +454,11 @@
>
<button
v-if="showOtherRepliesAsButton && replies.length > 1"
- class="button-unstyled -link faint"
- :title="$tc('status.ancestor_follow', replies.length - 1, { numReplies: replies.length - 1 })"
+ class="button-unstyled -link"
+ :title="$t('status.ancestor_follow', { numReplies: replies.length - 1 }, replies.length - 1)"
@click.prevent="dive"
>
- {{ $tc('status.replies_list_with_others', replies.length - 1, { numReplies: replies.length - 1 }) }}
+ {{ $t('status.replies_list_with_others', { numReplies: replies.length - 1 }, replies.length - 1) }}
</button>
<span
v-else
@@ -478,7 +482,7 @@
<transition name="fade">
<div
- v-if="!hidePostStats && isFocused && combinedFavsAndRepeatsUsers.length > 0"
+ v-if="shouldDisplayFavsAndRepeats"
class="favs-repeated-users"
>
<div class="stats">
@@ -506,6 +510,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>
@@ -579,13 +596,17 @@
class="status-container reply-form"
>
<PostStatusForm
+ ref="postStatusForm"
class="reply-body"
+ :closeable="true"
:reply-to="status.id"
:attentions="status.attentions"
:replied-user="status.user"
:copy-message-scope="status.visibility"
:subject="replySubject"
- @posted="toggleReplying"
+ @posted="doToggleReplying"
+ @draft-done="doToggleReplying"
+ @can-close="doToggleReplying"
/>
</div>
</template>
diff --git a/src/components/status_body/status_body.scss b/src/components/status_body/status_body.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.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;
@@ -41,7 +38,7 @@
margin-bottom: 0.5em;
border-style: solid;
border-width: 0 0 1px;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
flex-grow: 0;
&.-tall {
@@ -112,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 {
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"
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
@@ -90,6 +90,9 @@ const StatusContent = {
}
return true
},
+ localCollapseSubjectDefault () {
+ return this.mergedConfig.collapseMessageWithSubject
+ },
attachmentSize () {
if (this.compact) {
return 'small'
diff --git a/src/components/status_history_modal/status_history_modal.vue b/src/components/status_history_modal/status_history_modal.vue
@@ -6,7 +6,9 @@
>
<div class="status-history-modal-panel panel">
<div class="panel-heading">
- {{ $t('status.status_history') }} ({{ historyCount }})
+ <h1 class="title">
+ {{ $t('status.status_history') }} ({{ historyCount }})
+ </h1>
</div>
<div class="panel-body">
<div
diff --git a/src/components/status_popover/status_popover.vue b/src/components/status_popover/status_popover.vue
@@ -40,19 +40,14 @@
<script src="./status_popover.js"></script>
<style lang="scss">
-@import "../../variables";
-
/* 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);
/* TODO cleanup this */
.Status.Status {
diff --git a/src/components/sticker_picker/sticker_picker.vue b/src/components/sticker_picker/sticker_picker.vue
@@ -32,8 +32,6 @@
<script src="./sticker_picker.js"></script>
<style lang="scss">
-@import "../../variables";
-
.sticker-picker {
width: 100%;
@@ -56,7 +54,7 @@
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.vue b/src/components/still-image/still-image.vue
@@ -28,8 +28,6 @@
<script src="./still-image.js"></script>
<style lang="scss">
-@import "../../variables";
-
.still-image {
position: relative;
line-height: 0;
@@ -68,8 +66,7 @@
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/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
@@ -97,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')
@@ -145,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
: ''
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
/* stylelint-disable no-descending-specificity */
.tab-switcher {
display: flex;
@@ -25,8 +23,7 @@
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 {
@@ -37,8 +34,7 @@
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);
}
}
@@ -80,8 +76,7 @@
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 {
@@ -106,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: "";
border-right: 1px solid;
- border-right-color: $fallback--border;
- border-right-color: var(--border, $fallback--border);
+ border-right-color: var(--border);
}
&:last-child .tab {
@@ -126,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;
@@ -135,12 +128,22 @@
margin-right: -200px;
margin-left: 1em;
+ &:not(.active) {
+ margin-top: 0;
+ margin-left: 1.5em;
+ }
+
@media all and (max-width: 800px) {
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;
}
@@ -173,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;
@@ -188,8 +201,6 @@
&.active {
background: transparent;
z-index: 5;
- color: $fallback--text;
- color: var(--tabActiveText, $fallback--text);
}
img {
@@ -231,7 +242,7 @@
margin-top: 0.5em;
margin-left: 0.2em;
margin-bottom: 0.25em;
- border-bottom: 1px solid var(--border, $fallback--border);
+ border-bottom: 1px solid var(--border);
@media all and (min-width: 800px) {
display: none;
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
@@ -89,7 +89,7 @@
</template>
<template #text>
<span>
- {{ $tc('status.thread_follow', totalReplyCount[status.id], { numStatus: totalReplyCount[status.id] }) }}
+ {{ $t('status.thread_follow', { numStatus: totalReplyCount[status.id] }, totalReplyCount[status.id]) }}
</span>
</template>
</i18n-t>
@@ -108,7 +108,7 @@
</template>
<template #text>
<span>
- {{ $tc('status.thread_show_full', totalReplyCount[status.id], { numStatus: totalReplyCount[status.id], depth: totalReplyDepth[status.id] }) }}
+ {{ $t('status.thread_show_full', { numStatus: totalReplyCount[status.id], depth: totalReplyDepth[status.id] }, totalReplyCount[status.id]) }}
</span>
</template>
</i18n-t>
@@ -119,15 +119,13 @@
<script src="./thread_tree.js"></script>
<style lang="scss">
-@import "../../variables";
-
.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;
diff --git a/src/components/timeago/timeago.vue b/src/components/timeago/timeago.vue
@@ -3,7 +3,7 @@
:datetime="time"
:title="localeDateString"
>
- {{ relativeTimeString }}
+ {{ relativeOrAbsoluteTimeString }}
</time>
</template>
@@ -16,25 +16,71 @@ export default {
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])
+ const timeString = this.$i18n.t(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: {
@@ -54,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
@@ -25,6 +25,8 @@ const Timeline = {
'title',
'userId',
'listId',
+ 'statusId',
+ 'bookmarkFolderId',
'tag',
'embedded',
'count',
@@ -77,13 +79,13 @@ const Timeline = {
}
},
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
@@ -121,6 +123,8 @@ const Timeline = {
showImmediately,
userId: this.userId,
listId: this.listId,
+ statusId: this.statusId,
+ bookmarkFolderId: this.bookmarkFolderId,
tag: this.tag
})
},
@@ -183,6 +187,8 @@ const Timeline = {
showImmediately: true,
userId: this.userId,
listId: this.listId,
+ statusId: this.statusId,
+ bookmarkFolderId: this.bookmarkFolderId,
tag: this.tag
}).then(({ statuses }) => {
if (statuses && statuses.length === 0) {
diff --git a/src/components/timeline/timeline.scss b/src/components/timeline/timeline.scss
@@ -1,31 +1,20 @@
-@import "../../variables";
-
.Timeline {
- .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: var(--badgeNeutral);
+ .timeline-body {
+ background: none;
+ backdrop-filter: none;
}
.alert-badge {
font-size: 0.75em;
line-height: 1;
text-align: right;
- border-radius: var(--tooltipRadius);
+ 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;
- background-color: var(--badgeNeutral);
- color: var(--badgeNeutralText);
}
.loadmore-button {
@@ -37,16 +26,21 @@
}
.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;
+ // Override the shrug empty filler
+ &:empty::before {
+ content: initial;
+ }
+
.button-default,
.alert {
line-height: 2em;
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
@@ -1,12 +1,15 @@
<template>
<div :class="['Timeline', classes.root]">
- <div :class="classes.header">
+ <div
+ v-if="!embedded"
+ :class="classes.header"
+ >
<TimelineMenu
v-if="!embedded"
:timeline-name="timelineName"
/>
<div
- v-if="showScrollTop && !embedded"
+ v-if="showScrollTop"
class="rightside-button"
>
<button
@@ -24,7 +27,7 @@
</FALayers>
</button>
</div>
- <template v-if="mobileLayout && !embedded">
+ <template v-if="mobileLayout">
<div
v-if="showLoadButton"
class="rightside-button"
@@ -38,13 +41,13 @@
fixed-width
icon="circle-plus"
/>
- <div class="alert-badge">
+ <div class="badge -counter">
{{ mobileLoadButtonString }}
</div>
</button>
</div>
<div
- v-else-if="!embedded"
+ v-else
class="loadmore-text faint veryfaint rightside-icon"
:title="$t('timeline.up_to_date')"
:aria-disabled="true"
@@ -65,7 +68,7 @@
{{ loadButtonString }}
</button>
<div
- v-else-if="!embedded"
+ v-else
class="loadmore-text faint"
@click.prevent
>
@@ -73,11 +76,9 @@
</div>
</template>
<QuickFilterSettings
- v-if="!embedded"
class="rightside-button"
/>
<QuickViewSettings
- v-if="!embedded"
class="rightside-button"
/>
</div>
@@ -148,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_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js
@@ -2,6 +2,7 @@ import Popover from '../popover/popover.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'
@@ -13,13 +14,14 @@ 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',
+ 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'
}
}
@@ -27,7 +29,8 @@ const TimelineMenu = {
components: {
Popover,
NavigationEntry,
- ListsMenuContent
+ ListsMenuContent,
+ BookmarkFoldersMenuContent
},
data () {
return {
@@ -35,7 +38,7 @@ const TimelineMenu = {
}
},
created () {
- if (timelineNames()[this.$route.name]) {
+ if (timelineNames(this.bookmarkFolders)[this.$route.name]) {
this.$store.dispatch('setLastTimeline', this.$route.name)
}
},
@@ -44,10 +47,15 @@ const TimelineMenu = {
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
+ federating: state => state.instance.federating,
+ bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable
}),
timelinesList () {
return filterNavigation(
@@ -56,7 +64,8 @@ const TimelineMenu = {
hasChats: this.pleromaChatMessagesAvailable,
isFederating: this.federating,
isPrivate: this.privateMode,
- currentUser: this.currentUser
+ currentUser: this.currentUser,
+ supportsBookmarkFolders: this.bookmarkFolders
}
)
}
@@ -88,7 +97,10 @@ const TimelineMenu = {
if (route === 'lists-timeline') {
return this.$store.getters.findListTitle(this.$route.params.id)
}
- const i18nkey = timelineNames()[this.$route.name]
+ 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
@@ -15,6 +15,10 @@
:show-pin="false"
class="timelines"
/>
+ <BookmarkFoldersMenuContent
+ v-else-if="useBookmarkFoldersMenu"
+ class="timelines"
+ />
<ul v-else>
<NavigationEntry
v-for="item in timelinesList"
@@ -25,8 +29,8 @@
</ul>
</template>
<template #trigger>
- <span class="button-unstyled title timeline-menu-title">
- <span class="timeline-title">{{ timelineName() }}</span>
+ <span class="button-unstyled timeline-menu-title">
+ <h1 class="title timeline-title">{{ timelineName() }}</h1>
<span>
<FAIcon
size="sm"
@@ -45,8 +49,6 @@
<script src="./timeline_menu.js"></script>
<style lang="scss">
-@import "../../variables";
-
.timeline-menu-popover {
min-width: 24rem;
max-width: 100vw;
@@ -60,65 +62,6 @@
margin: 0;
padding: 0;
}
-
- a {
- display: block;
- padding: 0 0.65em;
- height: 3.5em;
- line-height: 3.5em;
-
- &: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;
- }
- }
-
- 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;
- }
- }
}
.TimelineMenu {
@@ -159,8 +102,6 @@
}
&.open .timeline-menu-title svg {
- color: $fallback--text;
- color: var(--panelText, $fallback--text);
transform: rotate(180deg);
}
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,57 @@
+export default {
+ name: 'TopBar',
+ selector: 'nav',
+ validInnerComponents: [
+ 'Link',
+ 'Text',
+ 'Icon',
+ 'Button',
+ 'ButtonUnstyled',
+ 'Input',
+ 'Badge'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--fg',
+ shadow: [{
+ x: 0,
+ y: 1,
+ blur: 4,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.4
+ },
+ {
+ x: 0,
+ y: 2,
+ blur: 7,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.3
+ }]
+ }
+ },
+ {
+ component: 'Link',
+ parent: {
+ component: 'TopBar'
+ },
+ directives: {
+ textColor: '--text'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'ButtonUnstyled',
+ parent: {
+ component: 'TopBar'
+ }
+ },
+ directives: {
+ textColor: '--parent--text'
+ }
+ }
+ ]
+}
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/update_notification/update_notification.scss b/src/components/update_notification/update_notification.scss
@@ -1,5 +1,3 @@
-@import "src/variables";
-
.UpdateNotification {
overflow: hidden;
}
@@ -48,7 +46,7 @@
.panel-body {
border-width: 0 0 1px;
border-style: solid;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
}
.panel-footer {
diff --git a/src/components/update_notification/update_notification.vue b/src/components/update_notification/update_notification.vue
@@ -9,9 +9,9 @@
:class="{ '-peek': !showingMore }"
>
<div class="panel-heading">
- <span class="title">
+ <h1 class="title">
{{ $t('update.big_update_title') }}
- </span>
+ </h1>
</div>
<div class="panel-body">
<div
@@ -34,6 +34,7 @@
class="extra-info-group"
>
<i18n-t
+ scope="global"
keypath="update.update_bugs"
tag="p"
>
@@ -45,6 +46,7 @@
</template>
</i18n-t>
<i18n-t
+ scope="global"
keypath="update.update_changelog"
tag="p"
>
@@ -57,6 +59,7 @@
</i18n-t>
<p class="art-credit">
<i18n-t
+ scope="global"
keypath="update.art_by"
tag="small"
>
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: 4,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.2
+ }]
+ }
+ }
+ ]
+}
diff --git a/src/components/user_avatar/user_avatar.js b/src/components/user_avatar/user_avatar.js
@@ -15,14 +15,14 @@ library.add(
const UserAvatar = {
props: [
'user',
- 'betterShadow',
'compact',
'showActorTypeIndicator'
],
data () {
return {
showPlaceholder: false,
- defaultAvatar: `${this.$store.state.instance.server + this.$store.state.instance.defaultAvatar}`
+ defaultAvatar: `${this.$store.state.instance.server + this.$store.state.instance.defaultAvatar}`,
+ betterShadow: this.$store.state.interface.browserSupport.cssFilter
}
},
components: {
diff --git a/src/components/user_avatar/user_avatar.vue b/src/components/user_avatar/user_avatar.vue
@@ -32,12 +32,10 @@
<script src="./user_avatar.js"></script>
<style lang="scss">
-@import "../../variables";
-
.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;
@@ -48,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);
@@ -69,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);
}
}
@@ -92,7 +86,7 @@
padding: 0.2em;
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
@@ -48,7 +48,6 @@ export default {
data () {
return {
followRequestInProgress: false,
- betterShadow: this.$store.state.interface.browserSupport.cssFilter,
showingConfirmMute: false,
muteExpiryAmount: 0,
muteExpiryUnit: 'minutes'
@@ -225,7 +224,7 @@ 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) {
diff --git a/src/components/user_card/user_card.scss b/src/components/user_card/user_card.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.user-card {
position: relative;
z-index: 1;
@@ -21,14 +19,6 @@
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;
@@ -62,11 +52,6 @@
padding: 1em;
margin: 0;
- a {
- color: $fallback--link;
- color: var(--postLink, $fallback--link);
- }
-
img {
object-fit: contain;
vertical-align: middle;
@@ -76,53 +61,37 @@
}
&.-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);
+ border-top-left-radius: var(--roundness);
+ border-top-right-radius: var(--roundness);
- --__roundnessTop: var(--panelRadius);
+ --__roundnessTop: var(--roundness);
--__roundnessBottom: 0;
}
&.-rounded {
- border-radius: $fallback--panelRadius;
- border-radius: var(--panelRadius, $fallback--panelRadius);
+ border-radius: var(--roundness);
- --__roundnessTop: var(--panelRadius);
- --__roundnessBottom: var(--panelRadius);
+ --__roundnessTop: var(--roundness);
+ --__roundnessBottom: var(--roundness);
}
&.-popover {
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
+ border-radius: var(--roundness);
- --__roundnessTop: var(--tooltipRadius);
- --__roundnessBottom: var(--tooltipRadius);
+ --__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;
- a {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
-
- &:hover {
- color: var(--icon);
- }
- }
-
.container {
min-width: 0;
padding: 16px 0 6px;
@@ -164,8 +133,7 @@
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 0.2s ease;
@@ -188,8 +156,7 @@
padding: 0.5em 0;
&:not(:hover) .icon {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
+ color: var(--lightText);
}
}
@@ -203,6 +170,7 @@
}
.user-screen-name {
+ color: var(--text);
min-width: 1px;
flex: 0 1 auto;
text-overflow: ellipsis;
@@ -214,16 +182,11 @@
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);
}
}
@@ -241,6 +204,11 @@
--emoji-size: 1.7em;
+ .RichContent {
+ /* stylelint-disable-next-line declaration-no-important */
+ --link: var(--text) !important;
+ }
+
.top-line,
.bottom-line {
display: flex;
@@ -334,8 +302,6 @@
padding: 0.5em 1.5em 0;
text-align: center;
justify-content: space-between;
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
flex-wrap: wrap;
}
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,7 +8,7 @@
:style="style"
class="background-image"
/>
- <div :class="onClose ? '' : panel-heading -flexible-height">
+ <div :class="onClose ? '' : 'panel-heading -flexible-height'">
<div class="user-info">
<div class="container">
<a
@@ -16,10 +16,7 @@
class="user-info-avatar -link"
@click="zoomAvatar"
>
- <UserAvatar
- :better-shadow="betterShadow"
- :user="user"
- />
+ <UserAvatar :user="user" />
<div class="user-info-avatar -link -overlay">
<FAIcon
class="fa-scale-110 fa-old-padding"
@@ -30,7 +27,6 @@
<UserAvatar
v-else-if="typeof avatarAction === 'function'"
class="user-info-avatar"
- :better-shadow="betterShadow"
:user="user"
@click="avatarAction"
/>
@@ -38,10 +34,7 @@
v-else
:to="userProfileLink(user)"
>
- <UserAvatar
- :better-shadow="betterShadow"
- :user="user"
- />
+ <UserAvatar :user="user" />
</router-link>
<div class="user-summary">
<div class="top-line">
@@ -113,19 +106,19 @@
<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.actor_type === 'Service'"
- class="alert user-role"
+ class="alert neutral user-role"
>
{{ $t('user_card.bot') }}
</span>
@@ -166,14 +159,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"
>
{{ ' ' }}
@@ -208,7 +201,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')"
@@ -282,10 +275,7 @@
/>
</div>
</div>
- <div
- v-if="!hideBio"
- class="panel-body"
- >
+ <div v-if="!hideBio">
<div
v-if="!mergedConfig.hideUserStats && switcher"
class="user-counts"
diff --git a/src/components/user_link/user_link.vue b/src/components/user_link/user_link.vue
@@ -1,12 +1,16 @@
<template>
- <router-link
- :title="user.screen_name_ui"
- :to="userProfileLink(user)"
- >
- {{ at ? '@' : '' }}{{ user.screen_name_ui }}<UnicodeDomainIndicator
- :user="user"
- />
- </router-link>
+ <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>
diff --git a/src/components/user_list_menu/user_list_menu.vue b/src/components/user_list_menu/user_list_menu.vue
@@ -10,11 +10,11 @@
<button
v-for="list in lists"
:key="list.id"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click="toggleList(list.id)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': list.inList }"
/>
{{ list.title }}
@@ -22,7 +22,7 @@
</div>
</template>
<template #trigger>
- <button class="btn button-default dropdown-item -has-submenu">
+ <button class="menu-item dropdown-item -has-submenu">
{{ $t('lists.manage_lists') }}
<FAIcon
class="chevron-icon"
diff --git a/src/components/user_list_popover/user_list_popover.vue b/src/components/user_list_popover/user_list_popover.vue
@@ -48,12 +48,10 @@
<script src="./user_list_popover.js"></script>
<style lang="scss">
-@import "../../variables";
-
.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.vue b/src/components/user_note/user_note.vue
@@ -33,7 +33,7 @@
<textarea
v-show="editing"
v-model="localNote"
- class="note-text"
+ class="input note-text"
/>
<span
v-show="!editing"
@@ -48,8 +48,6 @@
<script src="./user_note.js"></script>
<style lang="scss">
-@import "../../variables";
-
.user-note {
display: flex;
flex-direction: column;
@@ -82,7 +80,7 @@
.note-text.-blank {
font-style: italic;
- color: var(--faint, $fallback--faint);
+ color: var(--textFaint);
}
}
</style>
diff --git a/src/components/user_panel/user_panel.vue b/src/components/user_panel/user_panel.vue
@@ -22,8 +22,15 @@
<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.vue b/src/components/user_popover/user_popover.vue
@@ -24,8 +24,6 @@
<script src="./user_popover.js"></script>
<style lang="scss">
-@import "../../variables";
-
/* popover styles load on-demand, so we need to override */
/* stylelint-disable block-no-empty */
.user-popover.popover {
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
@@ -4,52 +4,54 @@
v-if="user"
class="user-profile panel panel-default"
>
- <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"
- >
- <FAIcon
- class="fa-old-padding"
- icon="birthday-cake"
+ <div class="panel-body">
+ <UserCard
+ :user-id="userId"
+ :switcher="true"
+ :selected="timeline.viewing"
+ avatar-action="zoom"
+ rounded="top"
+ :has-note-editor="true"
/>
- {{ $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"
+ <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"
@@ -72,10 +74,14 @@
<div
v-if="followsTabVisible"
key="followees"
+ class="panel-body"
:label="$t('user_card.followees')"
:disabled="!user.friends_count"
>
- <FriendList :user-id="userId">
+ <FriendList
+ :user-id="userId"
+ :non-interactive="true"
+ >
<template #item="{item}">
<FollowCard :user="item" />
</template>
@@ -84,10 +90,14 @@
<div
v-if="followersTabVisible"
key="followers"
+ class="panel-body"
:label="$t('user_card.followers')"
:disabled="!user.followers_count"
>
- <FollowerList :user-id="userId">
+ <FollowerList
+ :user-id="userId"
+ :non-interactive="true"
+ >
<template #item="{item}">
<FollowCard
:user="item"
@@ -117,7 +127,7 @@
:title="$t('user_card.favorites')"
timeline-name="favorites"
:timeline="favorites"
- :user-id="userId"
+ :user-id="isUs ? undefined : userId"
:in-profile="true"
:footer-slipgate="footerRef"
/>
@@ -132,11 +142,11 @@
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
@@ -151,14 +161,12 @@
<script src="./user_profile.js"></script>
<style lang="scss">
-@import "../../variables";
-
.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;
@@ -182,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%;
@@ -192,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 {
@@ -229,4 +236,5 @@
padding: 7em;
}
}
+
</style>
diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue
@@ -6,7 +6,7 @@
<div class="user-reporting-panel panel">
<div class="panel-heading">
<i18n-t
- tag="div"
+ tag="h1"
keypath="user_reporting.title"
class="title"
>
@@ -19,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"
@@ -72,8 +72,6 @@
<script src="./user_reporting_modal.js"></script>
<style lang="scss">
-@import "../../variables";
-
.user-reporting-panel {
width: 90vw;
max-width: 700px;
@@ -84,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;
}
@@ -155,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/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
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
diff --git a/src/hocs/with_load_more/with_load_more.scss b/src/hocs/with_load_more/with_load_more.scss
@@ -1,12 +1,9 @@
-@import "../../variables";
-
.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/i18n/cs.json b/src/i18n/cs.json
@@ -678,7 +678,7 @@
"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ý styl",
+ "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í",
@@ -737,7 +737,8 @@
"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"
+ "hard_reset_value_tooltip": "Odstranit nastavení z úložiště a vynutit výchozí hodnotu",
+ "accent": "Akcentní barva"
},
"time": {
"day": "{0} day",
@@ -748,7 +749,7 @@
"hours": "{0} hours",
"hour_short": "{0}h",
"hours_short": "{0}h",
- "in_future": "in {0}",
+ "in_future": "za {0}",
"in_past": "před {0}",
"minute": "{0} minute",
"minutes": "{0} minutes",
@@ -758,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",
@@ -771,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",
@@ -783,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",
@@ -797,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í",
@@ -995,13 +1061,121 @@
"nodb": "Žádné nastavení v databázi",
"frontends": "Frontendy",
"instance": "Instance",
- "limits": "Limity"
+ "limits": "Limity",
+ "emoji": "Emoji"
},
"nodb": {
- "heading": "Nastavení v databázi je vypnuto"
+ "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"
+ "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
@@ -174,6 +174,8 @@
"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",
@@ -186,11 +188,12 @@
"edit_pinned": "Edit pinned items",
"edit_finish": "Done editing",
"mobile_sidebar": "Toggle mobile sidebar",
- "mobile_notifications": "Open notifications",
"mobile_notifications": "Open notifications (there are unread ones)",
"mobile_notifications_close": "Close notifications",
"mobile_notifications_mark_as_seen": "Mark all as seen",
- "announcements": "Announcements"
+ "announcements": "Announcements",
+ "quotes": "Quotes",
+ "drafts": "Drafts"
},
"notifications": {
"broken_favorite": "Unknown status, searching for it…",
@@ -212,7 +215,8 @@
"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"
+ "configuration_tip_dismiss": "Do not show again",
+ "subscribed_status": "posted"
},
"polls": {
"add_poll": "Add poll",
@@ -228,7 +232,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",
@@ -264,7 +270,8 @@
"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",
@@ -305,7 +312,17 @@
"private": "Followers-only - post to followers only",
"public": "Public - post to public timelines",
"unlisted": "Unlisted - do not post to public timelines"
- }
+ },
+ "close_confirm_title": "Closing post form",
+ "close_confirm": "What do you want to do with your current writing?",
+ "close_confirm_save_button": "Save",
+ "close_confirm_discard_button": "Discard",
+ "close_confirm_continue_composing_button": "Continue composing",
+ "auto_save_nothing_new": "Nothing new to save.",
+ "auto_save_saved": "Saved.",
+ "auto_save_saving": "Saving...",
+ "save_to_drafts_button": "Save to drafts",
+ "save_to_drafts_and_close_button": "Save to drafts and close"
},
"registration": {
"bio_optional": "Bio (optional)",
@@ -365,6 +382,7 @@
"actor_type_Person": "a normal user",
"actor_type_Service": "a bot",
"actor_type_Group": "a group",
+ "mobile_center_dialog": "Vertically center dialogs on mobile",
"app_name": "App name",
"expert_mode": "Show advanced",
"save": "Save changes",
@@ -374,6 +392,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",
@@ -395,6 +427,14 @@
"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",
@@ -480,8 +520,15 @@
"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",
+ "unsaved_post_action": "When you try to close an unsaved posting form",
+ "unsaved_post_action_save": "Save it to drafts",
+ "unsaved_post_action_discard": "Discard it",
+ "unsaved_post_action_confirm": "Ask every time",
+ "auto_save_draft": "Save drafts as you compose",
"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",
@@ -502,6 +549,8 @@
"mute_bot_posts": "Mute bot 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",
@@ -578,6 +627,7 @@
"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",
@@ -635,6 +685,7 @@
"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",
@@ -667,6 +718,7 @@
"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.",
@@ -715,6 +767,113 @@
"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",
+ "label": "{label} font"
+ }
+ },
+ "interface_font_user_override": "Override theme/browser font used",
"switcher": {
"keep_color": "Keep colors",
"keep_shadows": "Keep shadows",
@@ -806,13 +965,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}"
@@ -838,7 +1008,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)",
@@ -906,7 +1076,7 @@
"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": "Statues/activities access"
+ "activities": "Statuses/activities access"
}
},
"limits": {
@@ -931,8 +1101,8 @@
"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 front-end",
- "default_frontend_tip": "Default front-end will be shown to all users. Currently there's no way to for a user to select personal front-end. 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": "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}",
@@ -1047,6 +1217,7 @@
"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",
@@ -1065,6 +1236,8 @@
"delete_confirm_accept_button": "Delete",
"delete_confirm_cancel_button": "Keep",
"reply_to": "Reply to",
+ "reply_to_with_icon": "{icon} {replyTo}",
+ "reply_to_with_arg": "{replyToWithIcon} {user}",
"mentions": "Mentions",
"replies_list": "Replies:",
"replies_list_with_others": "Replies (+{numReplies} other): | Replies (+{numReplies} others):",
@@ -1073,8 +1246,13 @@
"status_unavailable": "Status unavailable",
"copy_link": "Copy link to status",
"external_source": "External source",
+ "muted_words": "Wordfiltered: {word} | Wordfiltered: {word} and {numWordsMore} more words",
+ "multi_reason_mute": "{main} | {main} + one more reason | {main} + {numReasonsMore} more reasons",
+ "muted_user": "User muted",
"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",
@@ -1111,7 +1289,9 @@
"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"
+ "more_actions": "More actions on this status",
+ "loading": "Loading...",
+ "load_error": "Unable to load status: {error}"
},
"user_card": {
"approve": "Approve",
@@ -1231,6 +1411,7 @@
},
"tool_tip": {
"media_upload": "Upload media",
+ "mentions": "Mentions",
"repeat": "Repeat",
"reply": "Reply",
"favorite": "Favorite",
@@ -1292,6 +1473,9 @@
"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",
@@ -1333,5 +1517,43 @@
},
"unicode_domain_indicator": {
"tooltip": "This domain contains non-ascii characters."
+ },
+ "drafts": {
+ "drafts": "Drafts",
+ "no_drafts": "You have no drafts",
+ "empty": "(No content)",
+ "poll_tooltip": "Draft contains a poll",
+ "continue": "Continue composing",
+ "save": "Save without posting",
+ "abandon": "Abandon draft",
+ "abandon_confirm_title": "Abandon confirmation",
+ "abandon_confirm": "Do you really want to abandon this draft?",
+ "abandon_confirm_accept_button": "Abandon",
+ "abandon_confirm_cancel_button": "Keep",
+ "replying": "Replying to {statusLink}",
+ "editing": "Editing {statusLink}",
+ "unavailable": "(unavailable)"
+ },
+ "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
@@ -121,7 +121,9 @@
"mobile_notifications_close": "Fermi sciigojn",
"announcements": "Anoncoj",
"search_close": "Fermi serĉujon",
- "mobile_sidebar": "(Mal)ŝalti flankan breton por telefonoj"
+ "mobile_sidebar": "(Mal)ŝalti flankan breton por telefonoj",
+ "mobile_notifications_mark_as_seen": "Marki ĉion vidita",
+ "quotes": "Citoj"
},
"notifications": {
"broken_favorite": "Nekonata afiŝo, serĉante ĝin…",
@@ -137,7 +139,13 @@
"follow_request": "volas vin aboni",
"error": "Eraris akirado de sciigoj: {0}",
"submitted_report": "sendis raporton",
- "poll_ended": "enketo finiĝis"
+ "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",
@@ -467,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)",
@@ -487,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",
@@ -701,7 +731,50 @@
"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"
+ "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",
@@ -876,7 +949,8 @@
"travel-and-places": "Vojaĝoj kaj lokoj"
},
"regional_indicator": "Regiona marko {letter}",
- "unpacked": "Malpakitaj bildosignoj"
+ "unpacked": "Malpakitaj bildosignoj",
+ "hide_custom_emoji": "Kaŝi proprajn bildosignojn"
},
"polls": {
"not_enough_options": "Tro malmultaj unikaj elektebloj en la enketo",
@@ -955,7 +1029,8 @@
"follows": "Novaj abonoj",
"favs_repeats": "Ripetoj kaj ŝatoj",
"emoji_reactions": "Bildosignaj reagoj",
- "reports": "Raportoj"
+ "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."
@@ -1021,7 +1096,13 @@
"repeat_confirm_title": "Konfirmo de ripeto",
"repeat_confirm_accept_button": "Ripeti",
"repeat_confirm_cancel_button": "Ne ripeti",
- "delete_confirm_cancel_button": "Ne forigi"
+ "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",
@@ -1191,5 +1272,93 @@
"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
@@ -58,7 +58,7 @@
"yes": "Oui",
"no": "Non",
"unpin": "Dégrafer l'élément",
- "scroll_to_top": "Défiler au début",
+ "scroll_to_top": "Défiler vers le haut",
"pin": "Agrafer l'élément",
"generic_error_message": "Une erreur est apparue : {0}",
"never_show_again": "Ne plus afficher"
@@ -90,7 +90,11 @@
"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",
@@ -106,16 +110,16 @@
"mentions": "Mentions",
"interactions": "Interactions",
"dms": "Messages directs",
- "public_tl": "Flux publique",
+ "public_tl": "Flux public",
"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",
+ "bookmarks": "Marque-Pages",
"timelines": "Flux",
"home_timeline": "Flux personnel",
"edit_nav_mobile": "Personnaliser la barre de navigation",
@@ -123,8 +127,14 @@
"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"
+ "mobile_sidebar": "Basculer la barre latérale",
+ "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",
+ "all_bookmarks": "Tous les marque-pages",
+ "bookmark_folders": "Dossiers des marque-pages"
},
"notifications": {
"broken_favorite": "Message inconnu, recherche en cours…",
@@ -140,7 +150,14 @@
"follow_request": "veut vous suivre",
"error": "Erreur de chargement des notifications : {0}",
"poll_ended": "Sondage terminé",
- "submitted_report": "Rapport envoyé"
+ "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",
+ "subscribed_status": "posté"
},
"interactions": {
"favs_repeats": "Partages et favoris",
@@ -148,13 +165,14 @@
"load_older": "Chargez d'anciennes interactions",
"moves": "Migrations de comptes",
"emoji_reactions": "Émoticônes de réaction",
- "reports": "Rapports"
+ "reports": "Rapports",
+ "statuses": "Abonnements"
},
"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",
@@ -173,8 +191,8 @@
},
"scope": {
"direct": "Direct - N'envoyer qu'aux personnes mentionnées",
- "private": "Abonné·e·s uniquement - Seul·e·s vos abonné·e·s verront vos status",
- "public": "Publique - Afficher dans les flux publics",
+ "private": "Abonné·e·s uniquement - Seul·e·s vos abonné·e·s verront vos statuts",
+ "public": "Public - Afficher dans les flux publics",
"unlisted": "Non-Listé - Ne pas afficher dans les flux publics"
},
"media_description_error": "Échec de téléversement du media, essayez encore",
@@ -183,9 +201,13 @@
"preview": "Prévisualisation",
"media_description": "Description de la pièce-jointe",
"post": "Post",
- "edit_status": "Éditer le status",
+ "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."
+ "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",
@@ -205,14 +227,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",
"email_language": "Dans quelle langue voulez-vous recevoir les emails du server ?",
"bio_optional": "Biographie (optionnelle)",
- "email_optional": "Courriel (optionnel)"
+ "email_optional": "Courriel (optionnel)",
+ "birthday": "Anniversaire :",
+ "birthday_optional": "Anniversaire (optionnel) :"
},
"selectable_list": {
"select_all": "Tout selectionner"
@@ -323,8 +349,8 @@
"notification_visibility_mentions": "Mentionnés",
"notification_visibility_repeats": "Partages",
"no_rich_text_description": "Ne formatez pas le texte",
- "no_blocks": "Aucun bloqués",
- "no_mutes": "Aucun masqués",
+ "no_blocks": "Aucun bloqué",
+ "no_mutes": "Aucun masqué",
"hide_follows_description": "Ne pas afficher à qui je suis abonné",
"hide_followers_description": "Ne pas afficher qui est abonné à moi",
"show_admin_badge": "Afficher le badge d'Admin sur mon profil",
@@ -379,7 +405,7 @@
"true": "oui"
},
"notifications": "Notifications",
- "notification_mutes": "Pour stopper la récéption de notifications d'un utilisateur particulier, utilisez un masquage.",
+ "notification_mutes": "Pour stopper la réception de notifications d’un utilisateur particulier, utilisez la fonction « masquer ».",
"notification_blocks": "Bloquer un utilisateur stoppe toute notification et se désabonne de lui.",
"enable_web_push_notifications": "Activer les notifications de push web",
"style": {
@@ -483,7 +509,8 @@
"drop_shadow_syntax": "{0} ne supporte pas le paramètre {1} et mot-clé {2}.",
"avatar_inset": "Veuillez noter que combiner à la fois les ombres internes et non-internes sur les avatars peut fournir des résultats inattendus avec la transparence des avatars.",
"spread_zero": "Les ombres avec une dispersion > 0 apparaitrons comme si ils étaient à zéro",
- "inset_classic": "L'ombre interne utilisera toujours {0}"
+ "inset_classic": "L'ombre interne utilisera toujours {0}",
+ "avatar_inset_short": "Séparer l'ombre incrustée"
},
"components": {
"panel": "Panneau",
@@ -498,7 +525,10 @@
"buttonPressedHover": "Bouton (cliqué+survol)",
"input": "Champ de saisie"
},
- "hintV3": "Pour les ombres vous pouvez aussi utiliser la notation {0} pour utiliser un autre emplacement de couleur."
+ "hintV3": "Pour les ombres vous pouvez aussi utiliser la notation {0} pour utiliser un autre emplacement de couleur.",
+ "name": "Nom",
+ "offset": "Décalage d'ombre",
+ "light_grid": "Utiliser un damier clair"
},
"fonts": {
"_tab_label": "Polices",
@@ -507,7 +537,8 @@
"interface": "Interface",
"input": "Champs de saisie",
"post": "Post text",
- "postCode": "Texte à taille fixe dans un article (texte enrichi)"
+ "postCode": "Texte à taille fixe dans un article (texte enrichi)",
+ "monospace": "Texte à espacement fixe"
},
"family": "Nom de la police",
"size": "Taille (en px)",
@@ -527,7 +558,43 @@
"header_faint": "Tout va bien",
"checkbox": "J'ai survolé les conditions d'utilisation",
"link": "un petit lien sympa"
- }
+ },
+ "themes2_outdated": "L'éditeur pour les thèmes V2 est en train d'être supprimé et sera remplacé par un nouvel éditeur qui tirera parti du nouveau moteur pour les thèmes V3. Il devrait toujours fonctionner, mais l'expérience pourrait être dégradée et incohérente.",
+ "appearance_tab_note": "Dans cet onglet, les changements n'affectent pas le thème utilisé, donc le thème exporté sera différent de ce que vous voyez à l'écran",
+ "themes3": {
+ "define": "Écraser",
+ "hacks": {
+ "underlay_override_mode_none": "Valeurs par défaut du thème",
+ "underlay_overrides": "Changer le fond",
+ "underlay_override_mode_opaque": "Remplacer par une couleur uniforme",
+ "force_interface_roundness": "Écraser les arrondis de l'interface",
+ "underlay_override_mode_transparent": "Retirer complètement (peut casser certains thèmes)",
+ "forced_roundness_mode_disabled": "Utiliser les valeurs par défaut du thème",
+ "forced_roundness_mode_sharp": "Forcer les bords carrés",
+ "forced_roundness_mode_round": "Forcer les bords ronds",
+ "forced_roundness_mode_nonsharp": "Forcer les bords \"pas-vraiment-carrés\" (1 px d'arrondi)"
+ },
+ "font": {
+ "builtin": {
+ "serif": "Serif",
+ "sans-serif": "Sans-serif",
+ "monospace": "Monospace",
+ "inherit": "Inchangé"
+ },
+ "group-builtin": "Polices par défaut du navigateur",
+ "local-unavailable2": "Entrer manuellement une police spécifique",
+ "lookup_local_fonts": "Charger la liste des polices installées sur cet ordinateur",
+ "enter_manually": "Entrer le nom d'une famille de police manuellement",
+ "entry": "Entrer {fontFamily}",
+ "select": "Choisir une police",
+ "local-unavailable1": "Listes des polices indisponibles installées localement",
+ "font_list_unavailable": "Impossible de trouver la police locale : {error}",
+ "group-local": "Polices installées localement"
+ }
+ },
+ "interface_font_user_override": "Écraser le thème ou les polices utilisées",
+ "custom_theme_used": "(Thème personnalisé)",
+ "update_preview": "Mise à jour de l'aperçu"
},
"version": {
"title": "Version",
@@ -554,7 +621,7 @@
"useStreamingApi": "Recevoir les messages et notifications en temps réel",
"notification_setting_filters": "Filtres",
"notification_setting_privacy_option": "Masquer l'expéditeur et le contenu des notifications push",
- "notification_setting_privacy": "Intimité",
+ "notification_setting_privacy": "Vie privée",
"hide_followers_count_description": "Masquer le nombre d'abonnés",
"accent": "Accent",
"chatMessageRadius": "Message de chat",
@@ -565,7 +632,7 @@
"mute_import": "Import des masquages",
"mute_export_button": "Exporter vos masquages dans un fichier CSV",
"mute_export": "Export des masquages",
- "notification_setting_hide_notification_contents": "Cacher l'expéditeur et le contenu des notifications push",
+ "notification_setting_hide_notification_contents": "Masquer l’expéditeur et le contenu des notifications « push »",
"notification_setting_block_from_strangers": "Bloquer les notifications des utilisateur⋅ice⋅s que vous ne suivez pas",
"virtual_scrolling": "Optimiser le rendu des flux",
"reset_background_confirm": "Voulez-vraiment réinitialiser l'arrière-plan ?",
@@ -632,7 +699,7 @@
"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é",
+ "account_privacy": "Confidentialité",
"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",
@@ -652,7 +719,7 @@
"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",
+ "setting_server_side": "Cette préférence est liée au profil 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}",
@@ -677,20 +744,105 @@
"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_sidebar": "Barre latérale",
"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"
+ "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 statuts",
+ "notification_extra_follow_requests": "Afficher les nouvelles demandes de suivi",
+ "user_popover_avatar_action": "Action lors du clic sur la fenêtre Avatar",
+ "user_popover_avatar_action_close": "Fermer la fenêtre contextuelle",
+ "notification_setting_ignore_inactionable_seen": "Ignorer les statuts 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. Ceci peut entraîner l’affichage de notifications en double sur d’autres navigateurs.",
+ "user_popover_avatar_overlay": "Afficher la fiche de l’utilisateur au survol de l’avatar",
+ "notification_visibility_in_column": "Afficher la colonne / le tiroir de notifications",
+ "notification_show_extra": "Afficher les extras dans la colonne de notifications",
+ "units": {
+ "time": {
+ "m": "minutes",
+ "s": "secondes",
+ "h": "heures",
+ "d": "jours"
+ }
+ },
+ "force_theme_recompilation_debug": "Désactiver le cache du thème, forcer la recompilation à chaque démarrage (DEBUG)",
+ "absolute_time_format": "Utiliser le format UTC",
+ "absolute_time_format_min_age": "À n'utiliser que pour les périodes plus anciennes que cette durée",
+ "notification_visibility_statuses": "Abonnements",
+ "mute_sensitive_posts": "Masquer les postes sensibles",
+ "appearance": "Apparence",
+ "confirm_new_setting": "Confirmer le nouveau réglage ?",
+ "confirm_new_question": "Est-ce que tout est ok ? Le réglage sera rétabli dans dix secondes.",
+ "revert": "Rétablir",
+ "confirm": "Confirmer",
+ "text_size": "Taille du texte et de l'interface",
+ "text_size_tip": "Utiliser {0} pour des valeurs absolues, {1} mettra l'échelle du texte à la valeur par défaut du navigateur.",
+ "text_size_tip2": "Les valeurs différentes de {0} pourraient casser les thèmes ou d'autres trucs",
+ "emoji_size": "Taille des émojis",
+ "navbar_size": "Taille de la barre supérieure",
+ "panel_header_size": "Taille du panneau d'en-tête",
+ "visual_tweaks": "Ajustements visuels mineurs",
+ "theme_debug": "Montrer ce que le moteur de thème de l'arrière-plan bricole lorsqu'il travaille avec la transparence (DEBUG)",
+ "scale_and_layout": "Taille et disposition de l'interface",
+ "hide_scrobbles_after": "Masquer les scrobbles plus vieux que"
},
"timeline": {
"collapse": "Fermer",
"conversation": "Conversation",
"error_fetching": "Erreur en cherchant les mises à jour",
- "load_older": "Afficher des status plus ancien",
+ "load_older": "Afficher des statuts plus anciens",
"no_retweet_hint": "Le message est marqué en abonnés-seulement ou direct et ne peut pas être partagé",
"repeated": "a partagé",
"show_new": "Afficher plus",
@@ -716,8 +868,8 @@
"replies_list": "Réponses :",
"mute_conversation": "Masquer la conversation",
"unmute_conversation": "Démasquer la conversation",
- "status_unavailable": "Status indisponible",
- "copy_link": "Copier le lien au status",
+ "status_unavailable": "Statut indisponible",
+ "copy_link": "Copier le lien au statut",
"expand": "Développer",
"nsfw": "Contenu sensible",
"status_deleted": "Ce post a été effacé",
@@ -755,10 +907,28 @@
"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",
+ "show_all_conversation": "Montrer tout le fil ({numStatus} autre message) | Montrer tout le fil ({numStatus} autres messages)",
+ "edit": "Éditer le statuts",
"edited_at": "(dernière édition {time})",
- "status_history": "Historique du status"
+ "status_history": "Historique du statut",
+ "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 statuts cités",
+ "display_quote": "Afficher les statuts 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",
+ "sensitive_muted": "Silencer le contenu sensible",
+ "quotes": "Citations",
+ "load_error": "Impossible de charger la publication : {error}",
+ "loading": "En cours de chargement…",
+ "bot_muted": "Silencer le contenu de robots"
},
"user_card": {
"approve": "Accepter",
@@ -828,7 +998,39 @@
"edit_profile": "Éditer le profil",
"deactivated": "Désactivé",
"follow_cancel": "Annuler la requête",
- "remove_follower": "Retirer l'abonné·e"
+ "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": "Modifier la note",
+ "edit_note_apply": "Appliquer",
+ "edit_note_cancel": "Annuler",
+ "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": "(vide)",
+ "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",
@@ -857,7 +1059,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": "Étendre ou réduire la notification pour montrer les messages en entier",
+ "toggle_mute": "Étendre ou réduire les notifications pour révéler le contenu masqué"
},
"upload": {
"error": {
@@ -886,11 +1091,11 @@
"simple": {
"simple_policies": "Politiques par instances",
"accept": "Acceptées",
- "accept_desc": "Cette instance accepte les messages seulement depuis ces instances :",
+ "accept_desc": "Cette instance accepte les messages seulement depuis les instances suivantes :",
"reject": "Rejetées",
"reject_desc": "Cette instance n'acceptera pas de message de ces instances :",
"quarantine": "Quarantaine",
- "quarantine_desc": "Cette instance enverra seulement des messages publics à ces instances :",
+ "quarantine_desc": "Cette instance enverra uniquement des messages publics aux instances suivantes :",
"ftl_removal_desc": "Cette instance supprime les instance suivantes du flux fédéré :",
"media_removal": "Suppression des pièce-jointes",
"media_removal_desc": "Cette instance supprime le contenu multimédia des instances suivantes :",
@@ -927,7 +1132,9 @@
"vote": "Voter",
"expired": "Sondage terminé il y a {0}",
"people_voted_count": "{count} voteur | {count} voteurs",
- "votes_count": "{count} vote | {count} votes"
+ "votes_count": "{count} vote | {count} votes",
+ "non_anonymous": "Sondage public",
+ "non_anonymous_title": "Certaines instances pourraient afficher ce pour quoi vous avez voté"
},
"emoji": {
"emoji": "Émoji",
@@ -937,7 +1144,7 @@
"unicode": "émoji unicode",
"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",
+ "stickers": "Autocollants",
"keep_open": "Garder ouvert",
"unicode_groups": {
"activities": "Activités",
@@ -945,12 +1152,14 @@
"flags": "Drapeaux",
"food-and-drink": "Nourriture & boissons",
"objects": "Objets",
- "people-and-body": "Personnes & Corps",
+ "people-and-body": "Humain·e·s & Corps",
"smileys-and-emotion": "Emoticônes",
"symbols": "Symboles",
"travel-and-places": "Voyages & lieux"
},
- "regional_indicator": "Indicateur régional {letter}"
+ "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é.",
@@ -1012,7 +1221,7 @@
"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"
},
@@ -1047,7 +1256,7 @@
"empty_chat_list_placeholder": "Vous n'avez pas encore de discussions. Démarrez-en une nouvelle !",
"error_sending_message": "Quelque chose s'est mal passé pendant l'envoi du message.",
"error_loading_chat": "Quelque chose s'est mal passé au chargement de la discussion.",
- "delete_confirm": "Voulez-vous vraiment effacer ce message ?",
+ "delete_confirm": "Souhaitez-vous vraiment effacer ce message ?",
"more": "Plus",
"empty_message_error": "Impossible d'envoyer un message vide",
"new": "Nouvelle discussion",
@@ -1083,7 +1292,8 @@
"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."
+ "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 patients avec nous"
},
"unicode_domain_indicator": {
"tooltip": "Ce domaine contient des caractères non ascii."
@@ -1091,11 +1301,190 @@
"report": {
"reporter": "Rapporteur·euse :",
"reported_user": "Compte rapporté :",
- "reported_statuses": "Status rapportés :",
- "notes": "Notes :",
- "state": "Status :",
+ "reported_statuses": "Statuts rapportés :",
+ "notes": "Notes :",
+ "state": "Statut :",
"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": "Envoyer l'annonce",
+ "post_placeholder": "Rédiger ici le contenu de l'annonce...",
+ "post_action": "Publier",
+ "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": "URL de référence",
+ "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 statuts/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": "Code court",
+ "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",
+ "new_shortcode": "Shortcode, laissez vide pour déduire"
+ },
+ "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"
+ },
+ "splash": {
+ "settings": "Application des paramètres…",
+ "almost": "Réticulation des splines…",
+ "fun_2": "Allez-y doucement !",
+ "fun_3": "Suya…",
+ "fun_4": "Ma machine Pleroma est à pleine puissance !",
+ "loading": "En cours de chargement…",
+ "theme": "Application du thème, veuillez patienter chaleureusement…",
+ "instance": "Récupération des infos de l'instance…",
+ "fun_1": "Buvez plus d'eau",
+ "error": "Quelque chose s'est mal passé"
+ },
+ "bookmark_folders": {
+ "select_folder": "Sélectionnez le dossier des favoris",
+ "create": "Créer le dossier",
+ "emoji": "Emoji",
+ "name": "Nom du dossier",
+ "new": "Nouveau dossier",
+ "delete": "Supprimer le dossier",
+ "update_folder": "Enregistrer les changements",
+ "really_delete": "Voulez-vous vraiment supprimer le dossier ?",
+ "error": "Erreur lors de la manipulation des dossiers de favoris : {0}",
+ "creating_folder": "Création du dossier des favoris",
+ "editing_folder": "Édition du dossier {folderName}"
}
}
diff --git a/src/i18n/ja_pedantic.json b/src/i18n/ja_pedantic.json
@@ -130,7 +130,11 @@
"edit_finish": "完了",
"mobile_notifications": "通知を開く (未読あり)",
"mobile_notifications_close": "通知を閉じる",
- "announcements": "お知らせ"
+ "announcements": "お知らせ",
+ "all_bookmarks": "全てのブックマーク",
+ "bookmark_folders": "ブックマークフォルダ",
+ "mobile_sidebar": "モバイルのサイドバーを開く",
+ "quotes": "引用"
},
"notifications": {
"broken_favorite": "ステータスが見つかりません。探しています…",
@@ -152,7 +156,8 @@
"unread_follow_requests": "フォローリクエストが{num}件来ています | フォローリクエストが{num}件来ています",
"configuration_tip": "ここに表示する通知の種類は{theSettings}にて変更することができます。 {dismiss}",
"submitted_report": "通報が送信されました",
- "configuration_tip_settings": "設定"
+ "configuration_tip_settings": "設定",
+ "subscribed_status": "投稿しました"
},
"polls": {
"add_poll": "投票を追加",
@@ -192,7 +197,8 @@
"load_older": "古い通知を読み込む",
"moves": "ユーザーの引っ越し",
"emoji_reactions": "絵文字リアクション",
- "reports": "通報"
+ "reports": "通報",
+ "statuses": "購読"
},
"post_status": {
"new_status": "投稿する",
@@ -401,9 +407,9 @@
"profile_tab": "プロフィール",
"radii_help": "インターフェースの角丸を設定する (ピクセル単位)",
"replies_in_timeline": "タイムライン上の返信",
- "reply_visibility_all": "すべての返信を表示",
- "reply_visibility_following": "自分、もしくはフォローしているユーザー宛ての返信のみを表示",
- "reply_visibility_self": "自分に宛てられた返信のみを表示",
+ "reply_visibility_all": "すべての返信を表示する",
+ "reply_visibility_following": "自分、もしくはフォローしているユーザー宛ての返信のみを表示する",
+ "reply_visibility_self": "自分に宛てられた返信のみを表示する",
"autohide_floating_post_button": "投稿ボタンを自動的に隠す (モバイル)",
"saving_err": "設定を保存できませんでした",
"saving_ok": "設定を保存しました",
@@ -416,7 +422,7 @@
"set_new_profile_background": "プロフィールの背景を設定する",
"set_new_profile_banner": "プロフィールのバナーを設定する",
"settings": "設定",
- "subject_input_always_show": "注釈欄をいつでも表示する",
+ "subject_input_always_show": "注釈欄を常に表示する",
"subject_line_behavior": "返信するとき、返信先の注釈をコピーする",
"subject_line_email": "メール風: \"re: 注釈\"",
"subject_line_mastodon": "Mastodon風: そのままコピー",
@@ -537,18 +543,18 @@
"inset": "内側",
"hint": "影の設定では、色の値として --variable を使うことができます。これはCSS3変数です。ただし、透明度の設定は、効かなくなります。",
"filter_hint": {
- "always_drop_shadow": "ブラウザーがサポートしていれば、常に {0} が使われます。",
+ "always_drop_shadow": "注意: この影には、ブラウザーがサポートしている場合、常に {0} が使われます。",
"drop_shadow_syntax": "{0} は、{1} パラメーターと {2} キーワードをサポートしていません。",
- "avatar_inset": "内側の影と外側の影を同時に使うと、透明なアバターの表示が乱れます。",
- "spread_zero": "広がりが 0 よりも大きな影は、0 と同じです",
- "inset_classic": "内側の影は {0} を使います"
+ "avatar_inset": "内側の影と外側の影を同時に使うと、透明なアイコンの表示が変になる場合があることに注意してください。",
+ "spread_zero": "広がりを 0 より大きくしても、表示は 0 と変わりません",
+ "inset_classic": "内側の影には {0} が使われます"
},
"components": {
"panel": "パネル",
"panelHeader": "パネルヘッダー",
"topBar": "トップバー",
- "avatar": "ユーザーアバター (プロフィール)",
- "avatarStatus": "ユーザーアバター (投稿)",
+ "avatar": "ユーザーアイコン (プロフィール)",
+ "avatarStatus": "ユーザーアイコン (投稿)",
"popup": "ポップアップとツールチップ",
"button": "ボタン",
"buttonHover": "ボタン (ホバー)",
@@ -556,7 +562,10 @@
"buttonPressedHover": "ボタン (ホバー、かつ、押されているとき)",
"input": "入力欄"
},
- "hintV3": "影の場合は、 {0} 表記を使って他の色スロットを使うこともできます。"
+ "hintV3": "影の色には、 {0} 表記を使って他の色スロットに指定した色を再利用することもできます。",
+ "offset": "影のオフセット",
+ "name": "名前",
+ "light_grid": "背景のチェック模様を明るくする"
},
"fonts": {
"_tab_label": "フォント",
@@ -565,7 +574,8 @@
"interface": "インターフェース",
"input": "入力欄",
"post": "投稿",
- "postCode": "等幅 (投稿がリッチテキストであるとき)"
+ "postCode": "等幅 (投稿がリッチテキストであるとき)",
+ "monospace": "等幅テキスト"
},
"family": "フォント名",
"size": "大きさ (px)",
@@ -585,7 +595,43 @@
"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": "バージョン",
@@ -620,7 +666,7 @@
},
"accent": "アクセント",
"mutes_imported": "ミュートがインポートされました。処理には時間がかかる場合があります。",
- "emoji_reactions_on_timeline": "絵文字リアクションをタイムラインに表示",
+ "emoji_reactions_on_timeline": "絵文字リアクションをタイムラインに表示する",
"domain_mutes": "ドメイン",
"mutes_and_blocks": "ミュートとブロック",
"chatMessageRadius": "チャットメッセージ",
@@ -635,7 +681,7 @@
"mute_export": "ミュートのエクスポート",
"allow_following_move": "フォローしているアカウントが引っ越したとき、引っ越し先を自動でフォローする",
"setting_changed": "デフォルトから変更された設定",
- "greentext": ">記号から始まる行を「リピート」の色で表示 (Meme Arrows)",
+ "greentext": "Meme arrows",
"sensitive_by_default": "デフォルトで投稿を閲覧注意として設定",
"more_settings": "その他の設定",
"reply_visibility_self_short": "自分宛ての返信のみ表示",
@@ -657,8 +703,8 @@
},
"save": "変更を保存",
"hide_shoutbox": "Shoutboxを表示しない",
- "always_show_post_button": "投稿ボタンを常に表示",
- "right_sidebar": "サイドバーを右に表示",
+ "always_show_post_button": "投稿ボタンを常に表示する",
+ "right_sidebar": "サイドバーを右側に表示する",
"email_language": "このサーバーから受け取るメールの言語",
"confirm_dialogs": "以下のとき確認ダイアログを表示する:",
"confirm_dialogs_repeat": "ステータスをリピートするとき",
@@ -697,7 +743,7 @@
"mention_link_bolden_you": "自分宛てのメンションを強調表示する",
"user_popover_avatar_action": "ユーザーカード内のユーザーアイコンをクリックした際の挙動",
"user_popover_avatar_overlay": "ユーザーカードをユーザーアイコンに被せて表示する",
- "show_yous": "自分宛てのメンションの横に「(あなた)」を表示",
+ "show_yous": "自分宛てのメンションの横に「(あなた)」と表示する",
"preview": "プレビュー",
"url": "URL",
"conversation_display": "スレッドの表示形式",
@@ -791,7 +837,35 @@
"notification_setting_ignore_inactionable_seen_tip": "この設定は通知を自動的に既読にするわけではなく、この設定を有効にしてもプッシュ通知などは届きます",
"notification_setting_ignore_inactionable_seen": "お気に入りやリピートの通知など、アクション不可な通知を未読として扱わない",
"notification_extra_tip": "通知カラムをカスマイズするためのヒントを表示する",
- "use_at_icon": "メンションリンク内の{'@'}記号を画像にする"
+ "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": "購読",
+ "text_size_tip": "絶対的な値には{0}を使用してください。{1}はブラウザのデフォルトのフォントサイズを基準にスケールされます。",
+ "visual_tweaks": "細かい外観の変更",
+ "absolute_time_format": "時間の表示に絶対表記を使用する",
+ "absolute_time_format_min_age": "これより古い日時に対してのみ使用する:",
+ "theme_debug": "テーマエンジンが透明度を処理する際に見ているものを表示する (デバッグ用)"
},
"time": {
"day": "{0}日",
@@ -868,7 +942,7 @@
"pin": "プロフィールにピン留め",
"unpin": "プロフィールのピン留めを外す",
"pinned": "ピン留め",
- "delete_confirm": "本当にこの削除しますか?",
+ "delete_confirm": "本当に削除しますか?",
"reply_to": "返信先:",
"replies_list": "返信:",
"mute_conversation": "スレッドをミュート",
@@ -928,7 +1002,12 @@
"thread_hide": "このスレッドをたたむ",
"thread_show": "このスレッドを開く",
"open_gallery": "メディアビューアで開く",
- "status_history": "編集履歴"
+ "status_history": "編集履歴",
+ "sensitive_muted": "閲覧注意な投稿のためミュートされています",
+ "load_error": "投稿の読み込みに失敗しました: {error}",
+ "loading": "読み込み中…",
+ "quotes": "引用",
+ "bot_muted": "BOTによる投稿のためミュートされています"
},
"user_card": {
"approve": "承認",
@@ -937,8 +1016,8 @@
"deny": "拒否",
"favorites": "お気に入り",
"follow": "フォロー",
- "follow_sent": "リクエストを送信しました!",
- "follow_progress": "リクエストしています…",
+ "follow_sent": "リクエスト完了!",
+ "follow_progress": "リクエスト中…",
"follow_unfollow": "フォロー解除",
"followees": "フォロー",
"followers": "フォロワー",
@@ -987,7 +1066,7 @@
"show_repeats": "リピートを見る",
"hide_repeats": "リピートを隠す",
"message": "メッセージ",
- "hidden": "隠す",
+ "hidden": "非公開",
"bot": "bot",
"highlight": {
"solid": "背景を単色にする",
@@ -1029,7 +1108,8 @@
"remove_follower_confirm_accept_button": "解除する",
"mute_confirm_title": "ミュートの確認",
"deactivated": "無効化済み",
- "group": "グループ"
+ "group": "グループ",
+ "mute_duration_prompt": "ミュート期間 (0で無期限):"
},
"user_profile": {
"timeline_title": "ユーザータイムライン",
@@ -1243,15 +1323,60 @@
"tabs": {
"limits": "制限",
"instance": "インスタンス",
- "frontends": "フロントエンド"
+ "frontends": "フロントエンド",
+ "emoji": "絵文字"
},
"limits": {
- "arbitrary_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": "保存されていない変更点があります。ハイライトされた絵文字を確認してください",
+ "importFS": "ファイルシステムから絵文字をインポート",
+ "do_list": "一覧を取得",
+ "remote_pack_instance": "リモートパックを取得するインスタンス",
+ "download_as_name": "パック名",
+ "download_as_name_full": "名前を入力 (空にするとリモートの名前を使用)",
+ "replace_warning": "続行するとローカルにある同名のパックが上書きされます"
}
},
"lists": {
@@ -1314,6 +1439,30 @@
"edit_action": "編集",
"start_time_prompt": "開始日時: ",
"end_time_prompt": "終了日時: ",
- "all_day_prompt": "終日"
+ "all_day_prompt": "終日",
+ "inactive_message": "このお知らせは無効です"
+ },
+ "splash": {
+ "loading": "読み込み中…",
+ "theme": "テーマを適用中です。お待ちください…",
+ "instance": "インスタンス情報を取得中…",
+ "settings": "設定を適用中…",
+ "fun_2": "ゆっくりしていってね!",
+ "error": "問題が発生しました",
+ "fun_3": "( ˘ω˘)スヤァ…",
+ "almost": "少女祈祷中…",
+ "fun_4": "俺のPleromaが火を吹くぜ!"
+ },
+ "bookmark_folders": {
+ "emoji": "絵文字",
+ "update_folder": "変更を保存",
+ "really_delete": "本当にフォルダを削除しますか?",
+ "name": "フォルダ名",
+ "editing_folder": "{folderName}フォルダの編集",
+ "creating_folder": "新規ブックマークフォルダの作成",
+ "create": "フォルダを作成",
+ "delete": "フォルダを削除",
+ "error": "ブックマークフォルダの処理中にエラーが発生しました: {0}",
+ "new": "新規フォルダ"
}
}
diff --git a/src/i18n/languages.js b/src/i18n/languages.js
@@ -22,6 +22,7 @@ const languages = [
'nl',
'oc',
'pl',
+ 'pdc',
'pt',
'ro',
'ru',
diff --git a/src/i18n/nan-TW.json b/src/i18n/nan-TW.json
@@ -190,7 +190,8 @@
"mobile_notifications_close": "關掉通知",
"announcements": "公告",
"search": "Tshuē",
- "mobile_notifications_mark_as_seen": "Lóng 標做有讀"
+ "mobile_notifications_mark_as_seen": "Lóng 標做有讀",
+ "quotes": "引用"
},
"notifications": {
"broken_favorite": "狀態毋知影,leh tshiau-tshuē……",
@@ -212,7 +213,8 @@
"unread_follow_requests": "{num}ê新ê跟tuè請求",
"configuration_tip": "用{theSettings},lí通自訂siánn物佇tsia顯示。{dismiss}",
"configuration_tip_settings": "設定",
- "configuration_tip_dismiss": "Mài koh顯示"
+ "configuration_tip_dismiss": "Mài koh顯示",
+ "subscribed_status": "有發送ê"
},
"polls": {
"add_poll": "開投票",
@@ -233,7 +235,7 @@
"emoji": {
"stickers": "貼圖",
"emoji": "繪文字",
- "keep_open": "Hōo 揀選仔開 leh",
+ "keep_open": "Hōo 揀選器開 leh",
"search_emoji": "Tshuē 繪文字",
"add_emoji": "插繪文字",
"custom": "定製 ê 繪文字",
@@ -252,7 +254,8 @@
},
"load_all_hint": "載入頭前 {saneAmount} ê 繪文字,規个攏載入效能可能 ē khah 食力。",
"load_all": "Kā {emojiAmount} ê 繪文字攏載入",
- "regional_indicator": "地區指引 {letter}"
+ "regional_indicator": "地區指引 {letter}",
+ "hide_custom_emoji": "Khàm掉自訂ê繪文字"
},
"errors": {
"storage_unavailable": "Pleroma buē-tàng the̍h 著瀏覽器儲存 ê。Lí ê 登入狀態抑是局部設定 buē 儲存,mā 凡勢 tú 著意料外 ê 問題。拍開 cookie 看māi。"
@@ -263,7 +266,8 @@
"emoji_reactions": "繪文字 ê 反應",
"reports": "檢舉",
"moves": "用者 ê 移民",
- "load_older": "載入 koh khah 早 ê 互動"
+ "load_older": "載入 koh khah 早 ê 互動",
+ "statuses": "訂ê"
},
"post_status": {
"edit_status": "編輯狀態",
@@ -935,7 +939,34 @@
"notification_extra_chats": "顯示bô讀ê開講",
"notification_extra_announcements": "顯示bô讀ê公告",
"notification_extra_follow_requests": "顯示新ê跟tuè請求",
- "notification_extra_tip": "顯示自訂其他通知ê撇步"
+ "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": "收藏",
@@ -957,7 +988,7 @@
"repeat_confirm_accept_button": "轉送",
"repeat_confirm_title": "轉送ê確認",
"repeat_confirm": "Lí kám真ê beh轉送tsit ê狀態?",
- "delete": "Thâi掉身份",
+ "delete": "Thâi掉狀態",
"delete_error": "Thâi狀態ê時出tshê:{0}",
"mentions": "提起",
"move_down": "Kā附件suá kàu正pîng",
@@ -1001,7 +1032,7 @@
"show_only_conversation_under_this": "Kan-ta顯示tsit ê狀態ê回應",
"status_history": "狀態ê歷史",
"reaction_count_label": "{num}ê lâng用表情反應",
- "hide_quote": "Khàm條引用ê狀態",
+ "hide_quote": "Khàm掉引用ê狀態",
"display_quote": "顯示引用ê狀態",
"invisible_quote": "引用ê狀態bē當用:{link}",
"more_actions": "佇tsit ê狀態ê其他動作"
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/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
@@ -33,11 +33,11 @@
"public": "Публічне",
"unlisted": "Непублічне"
},
- "undo": "Відмінити",
+ "undo": "Скасувати",
"yes": "Так",
"no": "Ні",
"unpin": "Відкріпити",
- "scroll_to_top": "Вгору",
+ "scroll_to_top": "Піднятися вгору",
"pin": "Прикріпити"
},
"finder": {
@@ -88,7 +88,7 @@
"simple_policies": "Правила поточного інстансу",
"reason": "Причина",
"not_applicable": "н/в",
- "instance": "Інстанс"
+ "instance": "Сервер"
},
"mrf_policies_desc": "Правила MRF розповсюджуються на даний інстанс. Наступні правила активні:",
"mrf_policies": "Активувати правила MRF (модуль переписування повідомлень)",
@@ -200,7 +200,9 @@
"mobile_notifications_close": "Закрити сповіщення",
"edit_nav_mobile": "Редагувати панель навігації",
"announcements": "Анонси",
- "search_close": "Закрити панель пошуку"
+ "search_close": "Закрити панель пошуку",
+ "mobile_notifications_mark_as_seen": "Позначити все прочитаним",
+ "quotes": "Цитування"
},
"media_modal": {
"next": "Наступна",
@@ -275,7 +277,8 @@
"symbols": "Символи",
"travel-and-places": "Подорожі та Місця"
},
- "unpacked": "Розпаковані емоджі"
+ "unpacked": "Розпаковані емоджі",
+ "hide_custom_emoji": "Приховати кастомні емодзі"
},
"post_status": {
"content_type": {
@@ -290,7 +293,7 @@
"new_status": "Створити допис",
"direct_warning_to_first_only": "Цей допис побачать лише користувачі, що були згадані на початку повідомлення.",
"direct_warning_to_all": "Цей допис побачать всі згадані користувачі.",
- "default": "Що нового?",
+ "default": "Щойно приземлились у Борисполі.",
"content_warning": "Тема (необов'язково)",
"preview": "Попередній перегляд",
"posting": "Відправляється",
@@ -541,7 +544,7 @@
"header": "Попередній перегляд",
"link": "невеличке посилання",
"header_faint": "Це нормально",
- "input": "Що нового?",
+ "input": "Щойно приземлився у Борисполі.",
"checkbox": "Я переглянув умови використання",
"fine_print": "Прочитайте наш {0} аби нічого нового не дізнатись!",
"faint_link": "корисний підручник"
@@ -747,7 +750,7 @@
"auto_update": "Автоматично показувати нові дописи",
"use_websockets": "Використовувати вебсокети (Оновлення в реальному часі)",
"use_at_icon": "Показувати {'@'} символ як іконку замість тексту",
- "mute_bot_posts": "Приховати дописи ботів",
+ "mute_bot_posts": "Приховувати дописи ботів",
"always_show_post_button": "Завжди показувати плаваючу кнопку «Новий Допис»",
"hide_favorites_description": "Не показувати список моїх вподобань (люди все одно отримують сповіщення)",
"third_column_mode": "Коли достатньо місця, показувати третю колонку, що містить",
@@ -786,7 +789,69 @@
"add_language": "Додати резервну мову",
"confirm_dialogs_delete": "видаленням допису",
"confirm_dialogs_logout": "виходом із системи",
- "confirm_dialogs_approve_follow": "схваленням запиту на підписку"
+ "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": "Вибрати все"
@@ -804,7 +869,8 @@
"email_required": "не може бути порожнім",
"fullname_required": "не може бути порожнім",
"username_required": "не може бути порожнім",
- "birthday_required": "не може бути пустим"
+ "birthday_required": "не може бути пустим",
+ "birthday_min_age": "має бути в або перед {date}"
},
"bio_placeholder": "напр.\nНаш народ завжди прагне волі для себе і бажає її для інших народів. Він боровся і бореться за правду і справедливість. Ми хочемо жити у згоді і взаємному шануванні з усіми народами доброї волі. Такі самі права визнаємо за іншими народами, за які боремося для себе.",
"fullname_placeholder": "напр. Степан Бандера",
@@ -839,7 +905,10 @@
"reject_follow_request": "Відхилити запит на підписку",
"accept_follow_request": "Прийняти запит на підписку",
"media_upload": "Завантажити медіа",
- "bookmark": "Додати до закладок"
+ "bookmark": "Додати до закладок",
+ "toggle_expand": "Розгорнути або згорнути сповіщення щоб показати допис повністю",
+ "toggle_mute": "Розгорнути або згорнути сповіщення щоб відкрити заглушений контент",
+ "autocomplete_available": "{number} результат. Використовуйте клавіши зі стрілками для навігації. | {number} результатів доступно. Використовуйте клавіши зі стрілками для навігації."
},
"upload": {
"error": {
@@ -911,7 +980,9 @@
"hashtags": "Хештеги",
"people": "Люди",
"people_talking": "{count} людей говорять про це",
- "person_talking": "{count} особа говорить про це"
+ "person_talking": "{count} особа говорить про це",
+ "no_more_results": "Більше немає",
+ "load_more": "Завантажити ще"
},
"user_card": {
"statuses": "Дописи",
@@ -1001,7 +1072,15 @@
"unfollow_confirm": "Точно відписатись від {user}?",
"unfollow_confirm_accept_button": "Так, відписатись",
"unfollow_confirm_cancel_button": "Ні, не відписуватись",
- "note": "Приватна нотатка"
+ "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": "Скопіювати посилання на допис",
@@ -1062,13 +1141,19 @@
"move_down": "Посунути вкладення праворуч",
"thread_show": "Показати гілку",
"mentions": "Згадки",
- "thread_show_full": "Показати відповіді: {numStatus} | Показати відповіді: {numStatus}",
+ "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": "Переглянути опис (натисніть саме вкладення, якщо опис не вміщається)"
+ "show_attachment_description": "Переглянути опис (натисніть саме вкладення, якщо опис не вміщається)",
+ "quotes": "Цитування",
+ "load_error": "Неможливо завантажити статус: {error}",
+ "loading": "Завантаження...",
+ "sensitive_muted": "Заглушення чутливого контенту",
+ "reaction_count_label": "{num} людина відреагувала | {num} людей відреагували",
+ "more_actions": "Більше дій для цього статусу"
},
"timeline": {
"no_more_statuses": "Більше немає дописів",
@@ -1084,7 +1169,8 @@
"no_retweet_hint": "Запис, позначено як \"тільки для підписників\" або \"особисте\" і тому не може бути поширений",
"socket_broke": "Втрачено з'єднання у реальному часі: код {0}",
"socket_reconnected": "Встановлено з'єднання у реальному часі",
- "quick_view_settings": "Налаштування швидкого перегляду"
+ "quick_view_settings": "Налаштування швидкого перегляду",
+ "quick_filter_settings": "Налаштування швидкого фільтру"
},
"user_reporting": {
"submit": "Відправити",
@@ -1148,7 +1234,9 @@
"lists": "Списки",
"manage_lists": "Керувати списками",
"remove_from_list": "Видалити зі списку",
- "add_to_list": "Додати до списку"
+ "add_to_list": "Додати до списку",
+ "update_title": "Зберегти назву",
+ "add_members": "Шукати більше користувачів"
},
"update": {
"update_changelog": "Щоб дізнатись більше інформації, дивіться {theFullChangelog}.",
@@ -1156,9 +1244,143 @@
"update_changelog_here": "повний список змін",
"big_update_title": "Хвилинку уваги",
"update_bugs_gitlab": "Pleroma GitLab",
- "big_update_content": "У нас не було оновлень протягом тривалого часу, тому речі можуть мати інакший вигляд, аніж ви звикли."
+ "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
@@ -130,20 +130,22 @@
"edit_nav_mobile": "自定义导航栏",
"edit_pinned": "编辑固定的项目",
"mobile_sidebar": "切换移动设备侧栏",
- "search_close": "关闭搜索栏"
+ "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": "想要关注你",
+ "follow_request": "想要关注您",
"error": "取得通知时发生错误:{0}",
"poll_ended": "投票结束了",
"submitted_report": "提交举报",
@@ -152,7 +154,8 @@
"unread_follow_requests": "{num} 个新关注请求",
"configuration_tip": "可以在 {theSettings} 里定制什么会显示在这里。{dismiss}",
"configuration_tip_settings": "设置",
- "configuration_tip_dismiss": "不再显示"
+ "configuration_tip_dismiss": "不再显示",
+ "subscribed_status": "已发送"
},
"polls": {
"add_poll": "增加投票",
@@ -179,11 +182,12 @@
"load_older": "加载更早的互动",
"moves": "用户迁移",
"reports": "举报",
- "emoji_reactions": "表情回应"
+ "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": {
@@ -199,12 +203,12 @@
"posting": "发送中",
"scope_notice": {
"public": "本条内容可以被所有人看到",
- "private": "关注你的人才能看到本条内容",
+ "private": "关注您的人才能看到本条内容",
"unlisted": "本条内容既不在公共时间线,也不会在所有已知网络上可见"
},
"scope": {
"direct": "私信 - 只发送给被提及的用户",
- "private": "仅关注者 - 只有关注了你的人能看到",
+ "private": "仅关注者 - 只有关注了您的人能看到",
"public": "公共 - 发送到公共时间轴",
"unlisted": "不公开 - 不会发送到公共时间轴"
},
@@ -212,7 +216,7 @@
"preview": "预览",
"media_description": "媒体描述",
"media_description_error": "更新媒体失败,请重试",
- "empty_status_error": "不能发布没有内容、没有附件的发文",
+ "empty_status_error": "不能发布没有内容、没有附件的帖子",
"post": "发送",
"edit_remote_warning": "其它远程实例可能不支持编辑并且无法接收您的帖子的最新版本。",
"edit_unsupported_warning": "Pleroma 不支持对提及或投票进行编辑。",
@@ -233,7 +237,7 @@
"new_captcha": "点击图片获取新的验证码",
"username_placeholder": "例如:lain",
"fullname_placeholder": "例如:岩仓玲音",
- "bio_placeholder": "例如:\n你好,我是玲音。\n我是一个住在日本郊区的动画少女。你可能在 Wired 见过我。",
+ "bio_placeholder": "例如:\n你好,我是玲音。\n我是一个住在日本郊区的动画少女。您可能在 Wired 见过我。",
"validations": {
"username_required": "不能留空",
"fullname_required": "不能留空",
@@ -247,7 +251,7 @@
"reason_placeholder": "此实例的注册需要手动批准。\n请让管理员知道您为什么想要注册。",
"reason": "注册理由",
"register": "注册",
- "email_language": "你想从服务器收到什么语言的邮件?",
+ "email_language": "您想从服务器收到什么语言的邮件?",
"bio_optional": "介绍(可选)",
"email_optional": "电子邮件(可选)",
"birthday": "生日:",
@@ -289,7 +293,7 @@
"background": "背景",
"bio": "简介",
"block_export": "屏蔽名单导出",
- "block_export_button": "导出你的屏蔽名单到一个 csv 文件",
+ "block_export_button": "导出您的屏蔽名单到一个 csv 文件",
"block_import": "屏蔽名单导入",
"block_import_error": "导入屏蔽名单出错",
"blocks_imported": "屏蔽名单导入成功!需要一点时间来处理。",
@@ -310,10 +314,10 @@
"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": "过滤器",
@@ -388,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": "设置新的横幅图片",
@@ -402,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": "文本",
@@ -546,7 +550,8 @@
"interface": "界面",
"input": "输入框",
"post": "发帖文字",
- "postCode": "帖子中使用等间距文字(富文本)"
+ "postCode": "帖子中使用等间距文字(富文本)",
+ "monospace": "等宽文本"
},
"family": "字体名称",
"size": "大小 (in px)",
@@ -566,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": "版本",
@@ -582,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": "用户",
@@ -618,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": "保存更改",
@@ -664,14 +705,14 @@
"move_account_target": "目标账号(例如 {example})",
"moved_account": "账号移动好了。",
"move_account_error": "移动账号时出错:{error}",
- "setting_server_side": "这个设置是捆绑到你的个人资料的,能影响所有会话和客户端",
+ "setting_server_side": "这个设置是捆绑到您的个人资料的,能影响所有会话和客户端",
"post_look_feel": "文章的样子跟感受",
"email_language": "从服务器收邮件的语言",
- "account_backup_description": "这个允许你下载一份账号信息和文章的存档,但是现在还不能导入到 Pleroma 账号里。",
+ "account_backup_description": "这个允许您下载一份账号信息和文章的存档,但是现在还不能导入到 Pleroma 账号里。",
"backup_not_ready": "备份还没准备好。",
"add_backup_error": "添加新备份时出错:{error}",
"add_alias_error": "添加别名时出错:{error}",
- "move_account_notes": "如果你想把账号移动到别的地方,你必须去目标账号,然后加一个指向这里的别名。",
+ "move_account_notes": "如果您想把账号移动到别的地方,您必须去目标账号,然后加一个指向这里的别名。",
"wordfilter": "词语过滤器",
"user_profiles": "用户资料",
"third_column_mode_notifications": "通知栏",
@@ -685,7 +726,7 @@
},
"hide_favorites_description": "不显示我的喜欢列表(人们仍然会收到通知)",
"third_column_mode": "当有足够的空间时,显示第三栏包含",
- "third_column_mode_postform": "主要的发文形式和导航",
+ "third_column_mode_postform": "主要的帖子形式和导航",
"columns": "分栏",
"user_popover_avatar_overlay": "在用户头像上显示用户弹出窗口",
"navbar_column_stretch": "延伸导航栏至分栏宽度",
@@ -705,12 +746,12 @@
"max_depth_in_thread": "默认显示同主题帖子中的最大层数",
"hide_wordfiltered_statuses": "隐藏经过词语过滤的状态",
"hide_muted_threads": "不显示已隐藏的同主题帖子",
- "notification_visibility_polls": "你所投的投票的结束于",
+ "notification_visibility_polls": "您所投的投票的结束于",
"tree_advanced": "允许在树状视图中进行更灵活的导航",
"tree_fade_ancestors": "以模糊的文字显示当前状态的上级",
"conversation_display_linear": "线性样式",
"mention_link_fade_domain": "淡化域名(例如:{'@'}example.org 中的 {'@'}foo{'@'}example.org)",
- "mention_link_bolden_you": "当你被提及时突出显示提及你",
+ "mention_link_bolden_you": "当您被提及时突出显示提及您",
"user_popover_avatar_action": "弹出式头像点击动作",
"user_popover_avatar_action_zoom": "缩放头像",
"user_popover_avatar_action_close": "关闭弹出窗口",
@@ -750,7 +791,7 @@
"url": "URL",
"preview": "预览",
"commit_value": "保存",
- "commit_value_tooltip": "当前值未保存,请按此按钮以提交你的修改",
+ "commit_value_tooltip": "当前值未保存,请按此按钮以提交您的修改",
"reset_value": "重置",
"reset_value_tooltip": "重置草稿",
"hard_reset_value": "硬重置",
@@ -760,7 +801,52 @@
"notification_extra_chats": "显示未读聊天",
"notification_extra_announcements": "显示未读公告",
"notification_extra_follow_requests": "显示新的关注请求",
- "notification_extra_tip": "显示额外通知的定制提示"
+ "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} 天",
@@ -856,7 +942,7 @@
"nsfw": "NSFW",
"external_source": "外部来源",
"expand": "展开",
- "you": "(你)",
+ "you": "(您)",
"plus_more": "还有 {number} 个",
"many_attachments": "文章有 {number} 个附件",
"collapse_attachments": "折起附件",
@@ -896,7 +982,12 @@
"reaction_count_label": "{num} 人作出了表情回应",
"invisible_quote": "引用的状态不可用:{link}",
"hide_quote": "隐藏引用的状态",
- "display_quote": "显示引用的状态"
+ "display_quote": "显示引用的状态",
+ "quotes": "引用",
+ "sensitive_muted": "正在隐藏敏感内容",
+ "loading": "加载中...",
+ "load_error": "无法加载动态:{error}",
+ "more_actions": "状态的更多动作"
},
"user_card": {
"approve": "核准",
@@ -911,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": "退订",
@@ -945,7 +1036,7 @@
"disable_any_subscription": "完全禁止关注用户",
"quarantine": "不许帖子传入别站",
"delete_user": "删除用户",
- "delete_user_data_and_deactivate_confirmation": "这将永久删除该账户的数据并停用该账户。你完全确定吗?"
+ "delete_user_data_and_deactivate_confirmation": "这将永久删除该账号的数据并停用该账号。您完全确定吗?"
},
"hidden": "已隐藏",
"show_repeats": "显示转发",
@@ -993,7 +1084,8 @@
"note_blank": "(空)",
"edit_note": "编辑备注",
"edit_note_apply": "应用",
- "edit_note_cancel": "取消"
+ "edit_note_cancel": "取消",
+ "group": "组"
},
"user_profile": {
"timeline_title": "用户时间线",
@@ -1001,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": "当处理您的请求时,发生了一个错误。"
@@ -1020,7 +1112,7 @@
"favorite": "喜欢",
"user_settings": "用户设置",
"reject_follow_request": "拒绝关注请求",
- "add_reaction": "添加互动",
+ "add_reaction": "添加回应",
"bookmark": "书签",
"accept_follow_request": "接受关注请求",
"toggle_expand": "展开或折叠通知以显示帖子全文",
@@ -1090,7 +1182,8 @@
"smileys-and-emotion": "表情与情感"
},
"regional_indicator": "地区指示符 {letter}",
- "unpacked": "未分组的表情符号"
+ "unpacked": "未分组的表情符号",
+ "hide_custom_emoji": "隐藏自定义表情符号"
},
"about": {
"mrf": {
@@ -1157,7 +1250,7 @@
"chats": "聊天",
"delete": "删除",
"message_user": "发消息给 {nickname}",
- "you": "你:"
+ "you": "您:"
},
"announcements": {
"page_header": "公告",
@@ -1198,8 +1291,8 @@
"update_changelog": "关于变化的更多细节,请参见 {theFullChangelog} 。",
"update_changelog_here": "完整的更新日志",
"big_update_title": "请忍耐一下",
- "big_update_content": "我们已经有一段时间没有发布发行版,所以事情的外观和感觉可能与你习惯的不一样。",
- "update_bugs": "请在 {pleromaGitlab} 上报告任何问题和bug,因为我们已经改变了很多,虽然我们进行了彻底的测试,并且自己使用了开发版本,但我们可能错过了一些东西。我们欢迎你对你可能遇到的问题或如何改进Pleroma和Pleroma-FE提出反馈和建议。",
+ "big_update_content": "我们已经有一段时间没有发布发行版,所以事情的外观和感觉可能与您习惯的不一样。",
+ "update_bugs": "请在 {pleromaGitlab} 上报告任何问题和 bug,因为我们改变了软件中的很多东西,虽然我们进行了彻底的测试,并且我们自己使用开发版本,但我们可能错过了一些东西。我们欢迎您对您可能遇到的问题或如何改进 Pleroma 和 Pleroma-FE 提出反馈和建议。",
"art_by": "{linkToArtist} 的作品"
},
"lists": {
@@ -1232,13 +1325,14 @@
"nodb": "无数据库配置",
"instance": "实例",
"limits": "限制",
- "frontends": "前端"
+ "frontends": "前端",
+ "emoji": "表情符号"
},
"nodb": {
"heading": "数据库配置已禁用",
"documentation": "文档",
"text2": "大多数配置选项将不可用。",
- "text": "你需要修改后端配置文件,以便将 {property} 设置为 {value},更多内容请参见 {documentation}。"
+ "text": "您需要修改后端配置文件,以便将 {property} 设置为 {value},更多内容请参见 {documentation}。"
},
"captcha": {
"native": "本地",
@@ -1281,8 +1375,11 @@
"set_default_version": "将版本 {version} 设为默认",
"wip_notice": "请注意,此部分是一个WIP,缺乏某些功能,因为前端管理的后台实现并不完整。",
"default_frontend": "默认前端",
- "default_frontend_tip": "默认的前端将显示给所有用户。目前还没有办法让用户选择个人的前端。如果你不使用 PleromaFE,你很可能不得不使用旧的和有问题的 AdminFE 来进行实例配置,直到我们替换它。",
- "available_frontends": "可供安装"
+ "default_frontend_tip": "默认的前端将显示给所有用户。目前还没有办法让用户选择自己的前端。如果您不使用 PleromaFE,您很可能不得不使用旧的和有问题的 AdminFE 来进行实例配置,直到我们替换它。",
+ "available_frontends": "可供安装",
+ "failure_installing_frontend": "无法安装前端 {version}:{reason}",
+ "success_installing_frontend": "前端 {version} 成功安装",
+ "default_frontend_unavail": "默认前端设置不可以,因为这需要数据库中的配置"
},
"temp_overrides": {
":pleroma": {
@@ -1306,6 +1403,50 @@
}
}
},
- "wip_notice": "此管理仪表板是实验性和 WIP 的,{adminFeLink}。"
+ "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/lib/persisted_state.js b/src/lib/persisted_state.js
@@ -1,6 +1,6 @@
import merge from 'lodash.merge'
-import localforage from 'localforage'
import { each, get, set, cloneDeep } from 'lodash'
+import { storage } from './storage.js'
let loaded = false
@@ -26,7 +26,7 @@ const saveImmedeatelyActions = [
]
const defaultStorage = (() => {
- return localforage
+ return storage
})()
export default function createPersistedState ({
diff --git a/src/lib/storage.js b/src/lib/storage.js
@@ -0,0 +1,3 @@
+import localforage from 'localforage'
+
+export const storage = localforage
diff --git a/src/main.js b/src/main.js
@@ -24,9 +24,10 @@ 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 draftsModule from './modules/drafts.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'
@@ -58,55 +59,91 @@ 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,
+ drafts: draftsModule,
+ chats: chatsModule,
+ announcements: announcementsModule,
+ bookmarkFolders: bookmarkFoldersModule
},
- 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
- },
- 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,
+ options: {
+ devtools: process.env.NODE_ENV !== 'production'
+ },
+ 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/api.js b/src/modules/api.js
@@ -87,6 +87,9 @@ const api = {
const { state, commit, dispatch, rootState } = store
const timelineData = rootState.statuses.timelines.friends
state.mastoUserSocket = state.backendInteractor.startUserSocket({ store })
+ state.mastoUserSocket.addEventListener('pleroma:authenticated', () => {
+ state.mastoUserSocket.subscribe('user')
+ })
state.mastoUserSocket.addEventListener(
'message',
({ detail: message }) => {
@@ -202,12 +205,14 @@ const api = {
timeline = 'friends',
tag = false,
userId = false,
- listId = false
+ listId = false,
+ statusId = false,
+ bookmarkFolderId = false
}) {
if (store.state.fetchers[timeline]) return
const fetcher = store.state.backendInteractor.startFetchingTimeline({
- timeline, store, userId, listId, tag
+ timeline, store, userId, listId, statusId, bookmarkFolderId, tag
})
store.commit('addFetcher', { fetcherName: timeline, fetcher })
},
@@ -271,6 +276,18 @@ const api = {
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/config.js b/src/modules/config.js
@@ -1,10 +1,21 @@
import Cookies from 'js-cookie'
-import { setPreset, applyTheme, applyConfig } 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]
@@ -19,15 +30,40 @@ export const multiChoiceProperties = [
'conversationDisplay', // tree | linear
'conversationOtherRepliesButton', // below | inside
'mentionLinkDisplay', // short | full_for_remote | full
- 'userPopoverAvatarAction' // close | zoom | open
+ 'userPopoverAvatarAction', // close | zoom | open
+ 'unsavedPostAction' // save | discard | confirm
]
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,
@@ -36,11 +72,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,
@@ -57,6 +95,7 @@ export const defaultState = {
notificationVisibility: {
follows: true,
mentions: true,
+ statuses: true,
likes: true,
repeats: true,
moves: true,
@@ -69,6 +108,7 @@ export const defaultState = {
notificationNative: {
follows: true,
mentions: true,
+ statuses: true,
likes: false,
repeats: false,
moves: false,
@@ -102,6 +142,7 @@ export const defaultState = {
modalOnApproveFollow: undefined, // instance default
modalOnDenyFollow: undefined, // instance default
modalOnRemoveUserFromFollowers: undefined, // instance default
+ modalMobileCenter: undefined,
playVideosInModal: false,
useOneClickNsfw: false,
useContainFit: true,
@@ -112,7 +153,12 @@ export const defaultState = {
sidebarColumnWidth: '25rem',
contentColumnWidth: '45rem',
notifsColumnWidth: '25rem',
- emojiReactionsScale: 1.0,
+ 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
@@ -140,7 +186,11 @@ export const defaultState = {
autocompleteSelect: undefined, // instance default
closingDrawerMarksAsSeen: undefined, // instance default
unseenAtTop: undefined, // instance default
- ignoreInactionableSeen: undefined // instance default
+ ignoreInactionableSeen: undefined, // instance default
+ unsavedPostAction: undefined, // instance default
+ autoSaveDraft: undefined, // instance default
+ useAbsoluteTimeFormat: undefined, // instance default
+ absoluteTimeFormatMinAge: undefined // instance default
}
// caching the instance default properties
@@ -170,6 +220,10 @@ const config = {
}
},
mutations: {
+ setOptionTemporarily (state, { name, value }) {
+ set(state, name, value)
+ applyConfig(state)
+ },
setOption (state, { name, value }) {
set(state, name, value)
},
@@ -200,6 +254,37 @@ const config = {
setHighlight ({ commit, dispatch }, { user, color, type }) {
commit('setHighlight', { user, color, type })
},
+ 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'
@@ -217,24 +302,26 @@ const config = {
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':
- setPreset(value)
+ if (value === 'custom') break
+ dispatch('setTheme', { themeName: value, recompile: true, saveData: true })
break
- case 'sidebarColumnWidth':
- case 'contentColumnWidth':
- case 'notifsColumnWidth':
- case 'emojiReactionsScale':
- applyConfig(state)
- break
- case 'customTheme':
- case 'customThemeSource':
- applyTheme(value)
+ case 'themeDebug': {
+ dispatch('setTheme', { recompile: true })
break
+ }
case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value)
dispatch('loadUnicodeEmojiData', value)
diff --git a/src/modules/drafts.js b/src/modules/drafts.js
@@ -0,0 +1,86 @@
+import { storage } from 'src/lib/storage.js'
+
+export const defaultState = {
+ drafts: {}
+}
+
+export const mutations = {
+ addOrSaveDraft (state, { draft }) {
+ state.drafts[draft.id] = draft
+ },
+ abandonDraft (state, { id }) {
+ delete state.drafts[id]
+ },
+ loadDrafts (state, data) {
+ state.drafts = data
+ }
+}
+
+const storageKey = 'pleroma-fe-drafts'
+
+/*
+ * Note: we do not use the persist state plugin because
+ * it is not impossible for a user to have two windows at
+ * the same time. The persist state plugin is just overriding
+ * everything with the current state. This isn't good because
+ * if a draft is created in one window and another draft is
+ * created in another, the draft in the first window will just
+ * be overriden.
+ * Here, we can't guarantee 100% atomicity unless one uses
+ * different keys, which will just pollute the whole storage.
+ * It is indeed best to have backend support for this.
+ */
+const getStorageData = async () => ((await storage.getItem(storageKey)) || {})
+
+const saveDraftToStorage = async (draft) => {
+ const currentData = await getStorageData()
+ currentData[draft.id] = JSON.parse(JSON.stringify(draft))
+ await storage.setItem(storageKey, currentData)
+}
+
+const deleteDraftFromStorage = async (id) => {
+ const currentData = await getStorageData()
+ delete currentData[id]
+ await storage.setItem(storageKey, currentData)
+}
+
+export const actions = {
+ async addOrSaveDraft (store, { draft }) {
+ const id = draft.id || (new Date().getTime()).toString()
+ const draftWithId = { ...draft, id }
+ store.commit('addOrSaveDraft', { draft: draftWithId })
+ await saveDraftToStorage(draftWithId)
+ return id
+ },
+ async abandonDraft (store, { id }) {
+ store.commit('abandonDraft', { id })
+ await deleteDraftFromStorage(id)
+ },
+ async loadDrafts (store) {
+ const currentData = await getStorageData()
+ store.commit('loadDrafts', currentData)
+ }
+}
+
+export const getters = {
+ draftsByTypeAndRefId (state) {
+ return (type, refId) => {
+ return Object.values(state.drafts).filter(draft => draft.type === type && draft.refId === refId)
+ }
+ },
+ draftsArray (state) {
+ return Object.values(state.drafts)
+ },
+ draftCount (state) {
+ return Object.values(state.drafts).length
+ }
+}
+
+const drafts = {
+ state: defaultState,
+ mutations,
+ getters,
+ actions
+}
+
+export default drafts
diff --git a/src/modules/instance.js b/src/modules/instance.js
@@ -1,5 +1,3 @@
-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'
@@ -44,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
@@ -71,6 +72,7 @@ const defaultState = {
hideSitename: false,
hideUserStats: false,
muteBotStatuses: false,
+ muteSensitiveStatuses: false,
modalOnRepeat: false,
modalOnUnfollow: false,
modalOnBlock: true,
@@ -80,6 +82,7 @@ const defaultState = {
modalOnApproveFollow: false,
modalOnDenyFollow: false,
modalOnRemoveUserFromFollowers: false,
+ modalMobileCenter: false,
loginMethod: 'password',
logo: '/static/logo.svg',
logoMargin: '.2em',
@@ -97,6 +100,15 @@ 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',
@@ -113,6 +125,10 @@ const defaultState = {
closingDrawerMarksAsSeen: true,
unseenAtTop: false,
ignoreInactionableSeen: false,
+ unsavedPostAction: 'confirm',
+ autoSaveDraft: false,
+ useAbsoluteTimeFormat: false,
+ absoluteTimeFormatMinAge: '0d',
// Nasty stuff
customEmoji: [],
@@ -132,6 +148,7 @@ const defaultState = {
shoutAvailable: false,
pleromaChatMessagesAvailable: false,
pleromaCustomEmojiReactionsAvailable: false,
+ pleromaBookmarkFoldersAvailable: false,
gopherAvailable: false,
mediaProxyAvailable: false,
suggestionsEnabled: false,
@@ -145,6 +162,7 @@ const defaultState = {
// Version Information
backendVersion: '',
+ backendRepository: '',
frontendVersion: '',
pollsAvailable: false,
@@ -278,9 +296,6 @@ const instance = {
dispatch('initializeSocket')
}
break
- case 'theme':
- dispatch('setTheme', value)
- break
}
},
async getStaticEmoji ({ commit }) {
@@ -369,25 +384,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,4 +1,27 @@
+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,
+ themeChangeInProgress: 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',
settingsModalLoadedUser: false,
settingsModalLoadedAdmin: false,
@@ -13,7 +36,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: [],
@@ -35,6 +59,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
},
@@ -86,6 +124,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: {
@@ -160,10 +202,523 @@ 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
+ state.themeChangeInProgress = true
+ // 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()) {
+ state.themeChangeInProgress = false
+ 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'),
+ () => {
+ state.themeChangeInProgress = false
+ },
+ 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/statuses.js b/src/modules/statuses.js
@@ -385,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]
@@ -569,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 })
})
diff --git a/src/modules/users.js b/src/modules/users.js
@@ -452,11 +452,11 @@ 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 }) {
@@ -579,6 +579,7 @@ const users = {
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')
@@ -635,6 +636,7 @@ const users = {
}
dispatch('startFetchingLists')
+ dispatch('startFetchingBookmarkFolders')
if (user.locked) {
dispatch('startFetchingFollowRequests')
diff --git a/src/panel.scss b/src/panel.scss
@@ -1,15 +1,24 @@
/* 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 {
@@ -20,19 +29,25 @@
left: 0;
right: 0;
z-index: 5;
- box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
- 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;
}
@@ -45,11 +60,13 @@
.panel-heading,
.panel-footer {
- --panel-heading-height-padding: 0.6em;
- --__panel-heading-gap: 0.5em;
- --__panel-heading-height: 3.2em;
+ --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;
@@ -76,8 +93,7 @@
&.-stub {
&,
&::after {
- border-radius: $fallback--panelRadius;
- border-radius: var(--panelRadius, $fallback--panelRadius);
+ border-radius: var(--roundness);
}
}
@@ -99,6 +115,8 @@
.title {
font-size: 1.3em;
+ margin: 0;
+ font-weight: normal;
}
.alert {
@@ -119,82 +137,33 @@
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-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);
- }
-
- .button-unstyled:hover,
- a:hover {
- i[class*="icon-"],
- .svg-inline--fa,
- .iconLetter {
- color: var(--panelText);
- }
- }
-
- .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);
- }
-
- &:active {
- background-color: $fallback--fg;
- background-color: var(--btnPressedPanel, $fallback--fg);
- color: $fallback--text;
- color: var(--btnPressedPanelText, $fallback--text);
- }
-
- &:disabled {
- color: $fallback--text;
- color: var(--btnDisabledPanelText, $fallback--text);
- }
-
- &.toggled {
- color: $fallback--text;
- color: var(--btnToggledPanelText, $fallback--text);
- }
}
}
@@ -232,11 +201,12 @@
}
.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;
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
@@ -68,8 +68,6 @@ const MASTODON_UNBLOCK_USER_URL = id => `/api/v1/accounts/${id}/unblock`
const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute`
const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute`
const MASTODON_REMOVE_USER_FROM_FOLLOWERS = id => `/api/v1/accounts/${id}/remove_from_followers`
-const MASTODON_SUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/subscribe`
-const MASTODON_UNSUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/unsubscribe`
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`
@@ -108,7 +106,10 @@ 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'
@@ -272,6 +273,7 @@ const followUser = ({ id, credentials, ...options }) => {
const url = MASTODON_FOLLOW_URL(id)
const form = {}
if (options.reblogs !== undefined) { form.reblogs = options.reblogs }
+ if (options.notify !== undefined) { form.notify = options.notify }
return fetch(url, {
body: JSON.stringify(form),
headers: {
@@ -685,10 +687,12 @@ const fetchTimeline = ({
until = false,
userId = false,
listId = false,
+ statusId = false,
tag = false,
withMuted = false,
replyVisibility = 'all',
- includeTypes = []
+ includeTypes = [],
+ bookmarkFolderId = false
}) => {
const timelineUrls = {
public: MASTODON_PUBLIC_TIMELINE,
@@ -702,7 +706,8 @@ const fetchTimeline = ({
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 = []
@@ -721,6 +726,10 @@ const fetchTimeline = ({
url = url(listId)
}
+ if (timeline === 'quotes') {
+ url = url(statusId)
+ }
+
if (minId) {
params.push(['min_id', minId])
}
@@ -753,6 +762,9 @@ const fetchTimeline = ({
params.push(['include_types[]', type])
})
}
+ if (timeline === 'bookmarks' && bookmarkFolderId) {
+ params.push(['folder_id', bookmarkFolderId])
+ }
params.push(['limit', 20])
@@ -822,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
+ }
})
}
@@ -1164,14 +1179,6 @@ 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 = ({ maxId, credentials }) => {
const query = new URLSearchParams({ with_relationships: true })
if (maxId) {
@@ -1477,17 +1484,18 @@ const deleteAnnouncement = ({ id, credentials }) => {
})
}
-export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
- return Object.entries({
- ...(credentials
- ? { access_token: credentials }
- : {}
- ),
- stream,
- ...args
- }).reduce((acc, [key, val]) => {
- return acc + `${key}=${val}&`
- }, MASTODON_STREAMING + '?')
+export const getMastodonSocketURI = ({ credentials, stream, args = {} }, base) => {
+ const url = new URL(MASTODON_STREAMING, base)
+ if (credentials) {
+ url.searchParams.append('access_token', credentials)
+ }
+ if (stream) {
+ url.searchParams.append('stream', stream)
+ }
+ Object.entries(args).forEach(([key, val]) => {
+ url.searchParams.append(key, val)
+ })
+ return url
}
const MASTODON_STREAMING_EVENTS = new Set([
@@ -1499,7 +1507,8 @@ const MASTODON_STREAMING_EVENTS = new Set([
])
const PLEROMA_STREAMING_EVENTS = new Set([
- 'pleroma:chat_update'
+ 'pleroma:chat_update',
+ 'pleroma:respond'
])
// A thin wrapper around WebSocket API that allows adding a pre-processor to it
@@ -1507,7 +1516,8 @@ const PLEROMA_STREAMING_EVENTS = new Set([
export const ProcessedWS = ({
url,
preprocessor = handleMastoWS,
- id = 'Unknown'
+ id = 'Unknown',
+ credentials
}) => {
const eventTarget = new EventTarget()
const socket = new WebSocket(url)
@@ -1522,6 +1532,12 @@ export const ProcessedWS = ({
}
socket.addEventListener('open', (wsEvent) => {
console.debug(`[WS][${id}] Socket connected`, wsEvent)
+ if (credentials) {
+ socket.send(JSON.stringify({
+ type: 'pleroma:authenticate',
+ token: credentials
+ }))
+ }
})
socket.addEventListener('error', (wsEvent) => {
console.debug(`[WS][${id}] Socket errored`, wsEvent)
@@ -1542,19 +1558,47 @@ export const ProcessedWS = ({
})
/**/
+ const onAuthenticated = () => {
+ eventTarget.dispatchEvent(new CustomEvent('pleroma:authenticated'))
+ }
+
proxy(socket, 'open')
proxy(socket, 'close')
- proxy(socket, 'message', preprocessor)
+ proxy(socket, 'message', (event) => preprocessor(event, { onAuthenticated }))
proxy(socket, 'error')
// 1000 = Normal Closure
eventTarget.close = () => { socket.close(1000, 'Shutting down socket') }
eventTarget.getState = () => socket.readyState
+ eventTarget.subscribe = (stream, args = {}) => {
+ console.debug(
+ `[WS][${id}] Subscribing to stream ${stream} with args`,
+ args
+ )
+ socket.send(JSON.stringify({
+ type: 'subscribe',
+ stream,
+ ...args
+ }))
+ }
+ eventTarget.unsubscribe = (stream, args = {}) => {
+ console.debug(
+ `[WS][${id}] Unsubscribing from stream ${stream} with args`,
+ args
+ )
+ socket.send(JSON.stringify({
+ type: 'unsubscribe',
+ stream,
+ ...args
+ }))
+ }
return eventTarget
}
-export const handleMastoWS = (wsEvent) => {
+export const handleMastoWS = (wsEvent, {
+ onAuthenticated = () => {}
+} = {}) => {
const { data } = wsEvent
if (!data) return
const parsedEvent = JSON.parse(data)
@@ -1565,7 +1609,18 @@ export const handleMastoWS = (wsEvent) => {
return { event, id: payload }
}
const data = payload ? JSON.parse(payload) : null
- if (event === 'update') {
+ if (event === 'pleroma:respond') {
+ if (data.type === 'pleroma:authenticate') {
+ if (data.result === 'success') {
+ console.debug('[WS] Successfully authenticated')
+ onAuthenticated()
+ } else {
+ console.error('[WS] Unable to authenticate:', data.error)
+ wsEvent.target.close()
+ }
+ }
+ return null
+ } else if (event === 'update') {
return { event, status: parseStatus(data) }
} else if (event === 'status.update') {
return { event, status: parseStatus(data) }
@@ -1886,6 +1941,44 @@ 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,
@@ -1924,8 +2017,6 @@ const apiService = {
fetchMutes,
muteUser,
unmuteUser,
- subscribeUser,
- unsubscribeUser,
fetchBlocks,
fetchOAuthTokens,
revokeOAuthToken,
@@ -2016,7 +2107,11 @@ const apiService = {
updateEmojiFile,
deleteEmojiFile,
listRemoteEmojiPacks,
- downloadRemoteEmojiPack
+ downloadRemoteEmojiPack,
+ fetchBookmarkFolders,
+ createBookmarkFolder,
+ updateBookmarkFolder,
+ deleteBookmarkFolder
}
export default apiService
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -3,10 +3,11 @@ 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, listId = false, tag }) {
- return timelineFetcher.startFetching({ timeline, store, credentials, userId, listId, 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) {
@@ -29,10 +30,14 @@ const backendInteractorService = credentials => ({
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' })
- return ProcessedWS({ url, id: 'User' })
+ const url = getMastodonSocketURI({}, serv)
+ return ProcessedWS({ url, id: 'Unified', credentials })
},
...Object.entries(apiService).reduce((acc, [key, func]) => {
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/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
@@ -1,4 +1,4 @@
-import { invertLightness, contrastRatio } from 'chromatism'
+import { invertLightness, contrastRatio, convert } from 'chromatism'
// useful for visualizing color when debugging
export const consoleColor = (color) => console.log('%c##########', 'background: ' + color + '; color: ' + color)
@@ -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,6 +143,7 @@ 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),
@@ -161,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
*
@@ -173,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})`
}
/**
@@ -187,19 +215,37 @@ export const rgba2css = function (rgba) {
* @param {Boolean} preserve - try to preserve intended text color's hue/saturation (i.e. no BW)
*/
export const getTextColor = function (bg, text, preserve) {
- const contrast = getContrastRatio(bg, text)
-
- if (contrast < 4.5) {
- const base = typeof text.a !== 'undefined' ? { a: text.a } : {}
- const result = Object.assign(base, invertLightness(text).rgb)
- if (!preserve && getContrastRatio(bg, result) < 4.5) {
+ const originalContrast = getContrastRatio(bg, text)
+ if (!preserve) {
+ if (originalContrast < 4.5) {
// B&W
return contrastRatio(bg, text).rgb
}
- // Inverted color
- return result
}
- return text
+
+ const originalColor = convert(text).hex
+ const invertedColor = invertLightness(originalColor).hex
+ const invertedContrast = getContrastRatio(bg, convert(invertedColor).rgb)
+ let workColor
+
+ if (invertedContrast > originalContrast) {
+ workColor = invertedColor
+ } else {
+ workColor = originalColor
+ }
+
+ let contrast = getContrastRatio(bg, text)
+ const result = convert(rgb2hex(workColor)).hsl
+ const delta = result.l > 50 ? 1 : -1
+ const multiplier = 10
+ while (contrast < 4.5 && result.l > 20 && result.l < 80) {
+ result.l += delta * multiplier
+ result.l = Math.min(100, Math.max(0, result.l))
+ contrast = getContrastRatio(bg, convert(result).rgb)
+ }
+
+ const base = typeof text.a !== 'undefined' ? { a: text.a } : {}
+ return Object.assign(convert(result).rgb, base)
}
/**
diff --git a/src/services/date_utils/date_utils.js b/src/services/date_utils/date_utils.js
@@ -6,10 +6,13 @@ 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)
+ const d = relativeTimeMs(date)
const r = { num: round(d / YEAR), key: 'time.unit.years' }
if (d < nowThreshold * SECOND) {
r.num = 0
@@ -57,3 +60,39 @@ export const secondsToUnit = (unit, amount) => {
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/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -331,6 +331,8 @@ export const parseStatus = (data) => {
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
@@ -440,7 +442,9 @@ export const parseNotification = (data) => {
if (masto) {
output.type = mastoDict[data.type] || data.type
output.seen = data.pleroma.is_seen
- output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null
+ // 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.target = output.type !== 'move'
? null
: parseUser(data.target)
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/locale/locale.service.js b/src/services/locale/locale.service.js
@@ -3,6 +3,7 @@ import ISO6391 from 'iso-639-1'
import _ from 'lodash'
const specialLanguageCodes = {
+ pdc: 'en',
ja_easy: 'ja',
zh_Hant: 'zh-HANT',
zh: 'zh-Hans'
@@ -18,6 +19,7 @@ const internalToBackendLocaleMulti = codes => {
const getLanguageName = (code) => {
const specialLanguageNames = {
+ pdc: 'Pennsilfaanisch-Deitsch',
ja_easy: 'やさしいにほんご',
'nan-TW': '臺語(閩南語)',
zh: '简体中文',
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/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js
@@ -18,6 +18,7 @@ export const visibleTypes = store => {
return ([
notificationVisibility.likes && 'like',
notificationVisibility.mentions && 'mention',
+ notificationVisibility.statuses && 'status',
notificationVisibility.repeats && 'repeat',
notificationVisibility.follows && 'follow',
notificationVisibility.followRequest && 'follow_request',
@@ -28,7 +29,7 @@ export const visibleTypes = store => {
].filter(_ => _))
}
-const statusNotifications = new Set(['like', 'mention', 'repeat', 'pleroma:emoji_reaction', 'poll'])
+const statusNotifications = new Set(['like', 'mention', 'status', 'repeat', 'pleroma:emoji_reaction', 'poll'])
export const isStatusNotification = (type) => statusNotifications.has(type)
@@ -118,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
diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -5,11 +5,15 @@ import { promiseInterval } from '../promise_interval/promise_interval.js'
// 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'
]
diff --git a/src/services/poll/poll.service.js b/src/services/poll/poll.service.js
@@ -0,0 +1,36 @@
+import * as DateUtils from 'src/services/date_utils/date_utils.js'
+import { uniq } from 'lodash'
+
+const pollFallbackValues = {
+ pollType: 'single',
+ options: ['', ''],
+ expiryAmount: 10,
+ expiryUnit: 'minutes'
+}
+
+const pollFallback = (object, attr) => {
+ return object[attr] !== undefined ? object[attr] : pollFallbackValues[attr]
+}
+
+const pollFormToMasto = (poll) => {
+ const expiresIn = DateUtils.unitToSeconds(
+ pollFallback(poll, 'expiryUnit'),
+ pollFallback(poll, 'expiryAmount')
+ )
+
+ const options = uniq(pollFallback(poll, 'options').filter(option => option !== ''))
+ if (options.length < 2) {
+ return { errorKey: 'polls.not_enough_options' }
+ }
+
+ return {
+ options,
+ multiple: pollFallback(poll, 'pollType') === 'multiple',
+ expiresIn
+ }
+}
+
+export {
+ pollFallback,
+ pollFormToMasto
+}
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
@@ -1,452 +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'
+
+// On platforms where this is not supported, it will return undefined
+// Otherwise it will return an array
+const supportsAdoptedStyleSheets = !!document.adoptedStyleSheets
+
+const createStyleSheet = (id) => {
+ if (supportsAdoptedStyleSheets) {
+ return {
+ el: null,
+ sheet: new CSSStyleSheet(),
+ rules: []
+ }
+ }
-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
+ 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)
+ }
- 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')
+ return {
+ el,
+ sheet: el.sheet,
+ rules: []
+ }
}
-const configColumns = ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth, emojiReactionsScale }) =>
- ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth, emojiReactionsScale })
-
-const defaultConfigColumns = configColumns(defaultState)
-
-export const applyConfig = (config) => {
- const columns = configColumns(config)
+const EAGER_STYLE_ID = 'pleroma-eager-styles'
+const LAZY_STYLE_ID = 'pleroma-lazy-styles'
- if (columns === defaultConfigColumns) {
- return
+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.
+}
- const head = document.head
- const body = document.body
- body.classList.add('hidden')
-
- const rules = Object
- .entries(columns)
- .filter(([k, v]) => v)
- .map(([k, v]) => `--${k}: ${v}`).join(';')
-
- const styleEl = document.createElement('style')
- head.appendChild(styleEl)
- const styleSheet = styleEl.sheet
+export const generateTheme = (inputRuleset, callbacks, debug) => {
+ const {
+ onNewRule = (rule, isLazy) => {},
+ onLazyFinished = () => {},
+ onEagerFinished = () => {}
+ } = callbacks
- styleSheet.toString()
- styleSheet.insertRule(`:root { ${rules} }`, 'index-max')
- body.classList.remove('hidden')
-}
+ const themes3 = init({
+ inputRuleset,
+ debug
+ })
-export const getCssShadow = (input, usesDropShadow) => {
- if (input.length === 0) {
- return 'none'
+ 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 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 { lazyProcessFunc: processChunk }
}
-const getCssShadowFilter = (input) => {
- if (input.length === 0) {
- return 'none'
+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
}
+ if (cache.engineChecksum === getEngineChecksum()) {
+ const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
+ const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
- 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(' ')
-}
+ cache.data[0].forEach(rule => eagerStyles.sheet.insertRule(rule, 'index-max'))
+ cache.data[1].forEach(rule => lazyStyles.sheet.insertRule(rule, 'index-max'))
-export const generateColors = (themeData) => {
- const sourceColors = !themeData.themeEngineVersion
- ? colors2to3(themeData.colors || themeData)
- : themeData.colors || themeData
+ adoptStyleSheets([eagerStyles, lazyStyles])
- 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
- }
+ return true
+ } else {
+ console.warn('Engine checksum doesn\'t match, cache not usable, clearing')
+ localStorage.removeItem('pleroma-fe-theme-cache')
}
}
-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 applyTheme = (
+ input,
+ onEagerFinish = data => {},
+ onFinish = data => {},
+ debug
+) => {
+ const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
+ const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
+
+ 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 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(';')
+ 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))
+ }
},
- theme: {
- radii
- }
- }
+ debug
+ )
+
+ setTimeout(lazyProcessFunc, 0)
}
-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'
- }
- })
+const extractStyleConfig = ({
+ sidebarColumnWidth,
+ contentColumnWidth,
+ notifsColumnWidth,
+ emojiReactionsScale,
+ emojiSize,
+ navbarSize,
+ panelHeaderSize,
+ textSize,
+ forcedRoundness
+}) => {
+ const result = {
+ sidebarColumnWidth,
+ contentColumnWidth,
+ notifsColumnWidth,
+ emojiReactionsScale,
+ emojiSize,
+ navbarSize,
+ panelHeaderSize,
+ textSize
+ }
- return {
- rules: {
- fonts: Object
- .entries(fonts)
- .filter(([k, v]) => v)
- .map(([k, v]) => `--${k}Font: ${v.family}`).join(';')
- },
- theme: {
- fonts
- }
+ 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:
}
-}
-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
+ return result
}
-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 defaultStyleConfig = extractStyleConfig(defaultState)
- 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 }
- }, {})
+export const applyConfig = (input, i18n) => {
+ const config = extractStyleConfig(input)
- 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
- }
+ if (config === defaultStyleConfig) {
+ return
}
-}
-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 head = document.head
-export const generatePreset = (input) => {
- const colors = generateColors(input)
- return composePreset(
- colors,
- generateRadii(input),
- generateShadows(input, colors.theme.colors, colors.mod),
- generateFonts(input)
- )
+ 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'
-
- return window.fetch('/static/styles.json', { cache })
- .then((data) => data.json())
- .then((themes) => {
- return Object.entries(themes).map(([k, v]) => {
- let promise = null
+ const customUrl = url.replace(/\.(\w+)$/, '.custom.$1')
+ let builtin
+ let custom
+
+ 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
@@ -36,9 +36,9 @@ function subscribePush (registration, isEnabled, vapidPublicKey) {
function unsubscribePush (registration) {
return registration.pushManager.getSubscription()
- .then((subscribtion) => {
- if (subscribtion === null) { return }
- return subscribtion.unsubscribe()
+ .then((subscription) => {
+ if (subscription === null) { return }
+ return subscription.unsubscribe()
})
}
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.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,169 @@
+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)
+ }
+ },
+ boost: {
+ argsNeeded: 2,
+ documentation: 'If given color is dark makes it darker, if color is light - makes it lighter',
+ 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 isLight = relativeLuminance(color) < 0.5
+ const mod = isLight ? -1 : 1
+ return brightness(amount * mod, color).rgb
+ }
+ },
+ mod: {
+ argsNeeded: 2,
+ documentation: 'Old function that increases or decreases brightness depending if background 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")',
+ 'width: border width (thickness)',
+ '[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'
/*
@@ -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
@@ -407,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,575 @@
+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] || {
+ lowerLevelSelector,
+ 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
+
+ // avoid putting more stuff into actual CSS
+ computed[selector].virtualDirectives = {}
+ // but still be able to access it i.e. from --parent
+ computed[selector].virtualDirectivesRaw = computed[lowerLevelSelector]?.virtualDirectivesRaw || {}
+
+ 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,
+ virtualDirectivesRaw
+ }
+ } 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
@@ -24,6 +24,8 @@ const fetchAndUpdate = ({
showImmediately = false,
userId = false,
listId = false,
+ statusId = false,
+ bookmarkFolderId = false,
tag = false,
until,
since
@@ -47,6 +49,8 @@ const fetchAndUpdate = ({
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)) {
@@ -78,15 +82,16 @@ const fetchAndUpdate = ({
})
}
-const startFetching = ({ timeline = 'friends', credentials, store, userId = false, listId = 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
timelineData.listId = listId
- fetchAndUpdate({ timeline, credentials, store, showImmediately, userId, listId, tag })
+ timelineData.bookmarkFolderId = bookmarkFolderId
+ fetchAndUpdate({ timeline, credentials, store, showImmediately, userId, listId, statusId, bookmarkFolderId, tag })
const boundFetchAndUpdate = () =>
- fetchAndUpdate({ timeline, credentials, store, userId, listId, tag })
+ fetchAndUpdate({ timeline, credentials, store, userId, listId, statusId, bookmarkFolderId, tag })
return promiseInterval(boundFetchAndUpdate, 10000)
}
const timelineFetcher = {
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
@@ -1,6 +1,6 @@
/* eslint-env serviceworker */
-import localForage from 'localforage'
+import { storage } from 'src/lib/storage.js'
import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js'
import { prepareNotificationObject } from './services/notification_utils/notification_utils.js'
import { createI18n } from 'vue-i18n'
@@ -25,7 +25,7 @@ function getWindowClients () {
}
const setSettings = async () => {
- const vuexState = await localForage.getItem('vuex-lz')
+ const vuexState = await storage.getItem('vuex-lz')
const locale = vuexState.config.interfaceLanguage || 'en'
i18n.locale = locale
const notificationsNativeArray = Object.entries(vuexState.config.notificationNative)
@@ -38,6 +38,8 @@ const setSettings = async () => {
switch (k) {
case 'mentions':
return 'mention'
+ case 'statuses':
+ return 'status'
case 'likes':
return 'like'
case 'repeats':
diff --git a/static/.gitignore b/static/.gitignore
@@ -0,0 +1 @@
+*.custom.*
diff --git a/static/config.json b/static/config.json
@@ -24,6 +24,8 @@
"showInstanceSpecificPanel": false,
"sidebarRight": false,
"subjectLineBehavior": "email",
- "theme": "pleroma-dark",
+ "theme": null,
+ "style": null,
+ "palette": null,
"webPushNotifications": false
}
diff --git a/static/palettes/index.json b/static/palettes/index.json
@@ -0,0 +1,75 @@
+{
+ "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"
+ },
+ "dracula": {
+ "name": "Dracula",
+ "bg": "#282A36",
+ "fg": "#44475A",
+ "link": "#BC92F9",
+ "text": "#f8f8f2",
+ "cRed": "#FF5555",
+ "cBlue": "#8BE9FD",
+ "cGreen": "#50FA7B",
+ "cOrange": "#FFB86C"
+ },
+ "ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
+ "monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ],
+ "purple-stream": {
+ "name": "Purple stream",
+ "bg": "#17171A",
+ "fg": "#450F92",
+ "link": "#8769B4",
+ "text": "#C0C0C5",
+ "cRed": "#EB0300",
+ "cBlue": "#4656FF",
+ "cGreen": "#B0E020",
+ "cOrange": "#FF9046"
+ },
+ "feud": {
+ "name": "Feud",
+ "bg": "#323337",
+ "fg": "#1D1E21",
+ "link": "#18A0E3",
+ "accent": "#6671E2",
+ "text": "#DBDDE0",
+ "cRed": "#E05053",
+ "cBlue": "#6671E2",
+ "cGreen": "#3A8D5D",
+ "cOrange": "#DCAA45"
+ },
+ "constabulary": {
+ "name": "Constabulary",
+ "bg": "#FFFFFF",
+ "fg": "#3B5897",
+ "link": "#28487C",
+ "text": "#333333",
+ "cRed": "#FA3C4C",
+ "cBlue": "#0083FF",
+ "cGreen": "#44BDC6",
+ "cOrange": "#FFC200"
+ }
+}
diff --git a/src/assets/pleromatan_apology.png b/static/pleromatan_apology.png
Binary files differ.
diff --git a/src/assets/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.iss b/static/styles/Breezy DX.iss
@@ -0,0 +1,101 @@
+@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;
+}
+
+@palette.Panda {
+ bg: #EFF0F2;
+ fg: #292C32;
+ text: #1B1F22;
+ link: #1CA4F3;
+ accent: #1CA4F3;
+ cRed: #f41a51;
+ cBlue: #1CA4F3;
+ cGreen: #1af46e;
+ cOrange: #f4af1a;
+}
+
+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 --parent--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 --parent--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)
+}
+
+Button {
+ background: --parent;
+}
+
+Input {
+ shadow: --defaultInputBevel;
+ background: --parent;
+}
+
+PanelHeader {
+ shadow: inset 0 30 30 -30 #ffffff / 0.25
+}
+
+Tab:hover {
+ shadow: --buttonDefaultHoverGlow, --buttonDefaultBevel, --buttonDefaultShadow
+}
+
+Tab {
+ background: --parent;
+}
diff --git a/static/styles/Redmond DX.iss b/static/styles/Redmond DX.iss
@@ -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: $boost(--bg 20);
+ 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.iss",
+ "BreezyDX": "/static/styles/Breezy DX.iss"
+}
diff --git a/test/e2e/nightwatch.conf.js b/test/e2e/nightwatch.conf.js
@@ -9,7 +9,7 @@ module.exports = {
selenium: {
start_process: true,
- server_path: 'node_modules/selenium-server/lib/runner/selenium-server-standalone-2.53.1.jar',
+ server_path: require('selenium-server').path,
host: '127.0.0.1',
port: 4444,
cli_args: {
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/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/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
@@ -6,7 +6,7 @@ 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\|fix\|security\)$'
+ grep ' A\t' | grep '\.\(skip\|add\|remove\|change\|fix\|security\)$'
ret=$?
if [ $ret -eq 0 ]; then
diff --git a/yarn.lock b/yarn.lock
@@ -31,61 +31,45 @@
dependencies:
"@babel/highlight" "^7.18.6"
-"@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==
+"@babel/code-frame@^7.24.7", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2":
+ version "7.26.2"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
+ integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
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/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/helper-validator-identifier" "^7.25.9"
+ js-tokens "^4.0.0"
+ picocolors "^1.0.0"
"@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/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.22.6", "@babel/compat-data@^7.26.0", "@babel/compat-data@^7.26.5":
+ version "7.26.5"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.5.tgz#df93ac37f4417854130e21d72c66ff3d4b897fc7"
+ integrity sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==
-"@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==
+"@babel/core@7.26.0":
+ version "7.26.0"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40"
+ integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==
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"
+ "@babel/code-frame" "^7.26.0"
+ "@babel/generator" "^7.26.0"
+ "@babel/helper-compilation-targets" "^7.25.9"
+ "@babel/helper-module-transforms" "^7.26.0"
+ "@babel/helpers" "^7.26.0"
+ "@babel/parser" "^7.26.0"
+ "@babel/template" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
+ "@babel/types" "^7.26.0"
+ convert-source-map "^2.0.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
- json5 "^2.2.2"
- semver "^6.3.0"
+ json5 "^2.2.3"
+ semver "^6.3.1"
"@babel/core@^7.12.3":
version "7.18.10"
@@ -108,14 +92,14 @@
json5 "^2.2.1"
semver "^6.3.0"
-"@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==
+"@babel/eslint-parser@7.26.5":
+ version "7.26.5"
+ resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.26.5.tgz#aa669f4d873f9cd617050cf3c40c19cd96307efb"
+ integrity sha512-Kkm8C8uxI842AwQADxl0GbcG1rupELYLShazYEZO/2DYjhyWXJIOUVOE3tBYm6JXzUCNJOZEzqc4rCW/jsEQYQ==
dependencies:
"@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1"
eslint-visitor-keys "^2.1.0"
- semver "^6.3.0"
+ semver "^6.3.1"
"@babel/generator@^7.18.10":
version "7.18.10"
@@ -126,15 +110,6 @@
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
-"@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==
- dependencies:
- "@babel/types" "^7.18.7"
- "@jridgewell/gen-mapping" "^0.3.2"
- jsesc "^2.5.1"
-
"@babel/generator@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.9.tgz#68337e9ea8044d6ddc690fb29acae39359cca0a5"
@@ -144,51 +119,16 @@
"@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.23.6":
- version "7.23.6"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e"
- integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==
+"@babel/generator@^7.26.0", "@babel/generator@^7.26.5":
+ version "7.26.5"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.5.tgz#e44d4ab3176bbcaf78a5725da5f1dc28802a9458"
+ integrity sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==
dependencies:
- "@babel/types" "^7.23.6"
- "@jridgewell/gen-mapping" "^0.3.2"
- "@jridgewell/trace-mapping" "^0.3.17"
- 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"
- integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==
- dependencies:
- "@babel/types" "^7.16.7"
+ "@babel/parser" "^7.26.5"
+ "@babel/types" "^7.26.5"
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.25"
+ jsesc "^3.0.2"
"@babel/helper-annotate-as-pure@^7.18.6":
version "7.18.6"
@@ -197,15 +137,14 @@
dependencies:
"@babel/types" "^7.18.6"
-"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.6.tgz#f14d640ed1ee9246fb33b8255f08353acfe70e6a"
- integrity sha512-KT10c1oWEpmrIRYnthbzHgoOf6B+Xd6a5yhdbNtdhtG7aO1or5HViuf1TQR36xY/QprXA5nvxO6nAjhJ4y38jw==
+"@babel/helper-annotate-as-pure@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4"
+ integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==
dependencies:
- "@babel/helper-explode-assignable-expression" "^7.18.6"
- "@babel/types" "^7.18.6"
+ "@babel/types" "^7.25.9"
-"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9":
+"@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==
@@ -215,62 +154,29 @@
browserslist "^4.20.2"
semver "^6.3.0"
-"@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==
+"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.25.9":
+ version "7.26.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8"
+ integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==
dependencies:
- "@babel/compat-data" "^7.20.5"
- "@babel/helper-validator-option" "^7.18.6"
- browserslist "^4.21.3"
+ "@babel/compat-data" "^7.26.5"
+ "@babel/helper-validator-option" "^7.25.9"
+ browserslist "^4.24.0"
lru-cache "^5.1.1"
- semver "^6.3.0"
+ semver "^6.3.1"
-"@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==
+"@babel/helper-create-class-features-plugin@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz#7644147706bb90ff613297d49ed5266bde729f83"
+ integrity sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==
dependencies:
- "@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":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.6.tgz#6f15f8459f3b523b39e00a99982e2c040871ed72"
- integrity sha512-YfDzdnoxHGV8CzqHGyCbFvXg5QESPFkXlHtvdCkesLjjVMT2Adxe4FGUR5ChIb3DxSaXO12iIOCWoXdsUVwnqw==
- dependencies:
- "@babel/helper-annotate-as-pure" "^7.18.6"
- "@babel/helper-environment-visitor" "^7.18.6"
- "@babel/helper-function-name" "^7.18.6"
- "@babel/helper-member-expression-to-functions" "^7.18.6"
- "@babel/helper-optimise-call-expression" "^7.18.6"
- "@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"
- integrity sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==
- dependencies:
- "@babel/helper-annotate-as-pure" "^7.16.7"
- regexpu-core "^5.0.1"
+ "@babel/helper-annotate-as-pure" "^7.25.9"
+ "@babel/helper-member-expression-to-functions" "^7.25.9"
+ "@babel/helper-optimise-call-expression" "^7.25.9"
+ "@babel/helper-replace-supers" "^7.25.9"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
+ semver "^6.3.1"
"@babel/helper-create-regexp-features-plugin@^7.18.6":
version "7.18.6"
@@ -280,61 +186,31 @@
"@babel/helper-annotate-as-pure" "^7.18.6"
regexpu-core "^5.1.0"
-"@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==
+"@babel/helper-create-regexp-features-plugin@^7.25.9":
+ version "7.26.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz#5169756ecbe1d95f7866b90bb555b022595302a0"
+ integrity sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==
dependencies:
- "@babel/helper-annotate-as-pure" "^7.18.6"
- regexpu-core "^5.3.1"
+ "@babel/helper-annotate-as-pure" "^7.25.9"
+ regexpu-core "^6.2.0"
+ semver "^6.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==
+"@babel/helper-define-polyfill-provider@^0.6.2", "@babel/helper-define-polyfill-provider@^0.6.3":
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz#f4f2792fae2ef382074bc2d713522cf24e6ddb21"
+ integrity sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==
dependencies:
- "@babel/helper-compilation-targets" "^7.17.7"
- "@babel/helper-plugin-utils" "^7.16.7"
+ "@babel/helper-compilation-targets" "^7.22.6"
+ "@babel/helper-plugin-utils" "^7.22.5"
debug "^4.1.1"
lodash.debounce "^4.0.8"
resolve "^1.14.2"
- semver "^6.1.2"
-
-"@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"
- integrity sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q==
"@babel/helper-environment-visitor@^7.18.9":
version "7.18.9"
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"
- integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==
- dependencies:
- "@babel/types" "^7.18.6"
-
-"@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"
- integrity sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw==
- dependencies:
- "@babel/template" "^7.18.6"
- "@babel/types" "^7.18.6"
-
"@babel/helper-function-name@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz#940e6084a55dee867d33b4e487da2676365e86b0"
@@ -343,30 +219,6 @@
"@babel/template" "^7.18.6"
"@babel/types" "^7.18.9"
-"@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/template" "^7.18.10"
- "@babel/types" "^7.19.0"
-
-"@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/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"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
@@ -374,33 +226,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"
- integrity sha512-CeHxqwwipekotzPDUuJOfIMtcIHBuc7WAzLmTYWctVigqS5RktNMQ5bEwQSuGewzYnCtTWa3BARXeiLxDTv+Ng==
- dependencies:
- "@babel/types" "^7.18.6"
-
-"@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==
+"@babel/helper-member-expression-to-functions@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3"
+ integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==
dependencies:
- "@babel/types" "^7.20.7"
-
-"@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.21.0"
+ "@babel/traverse" "^7.25.9"
+ "@babel/types" "^7.25.9"
"@babel/helper-module-imports@^7.0.0-beta.49":
version "7.0.0"
@@ -415,33 +247,13 @@
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==
+"@babel/helper-module-imports@^7.24.7", "@babel/helper-module-imports@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715"
+ integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==
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"
- integrity sha512-che3jvZwIcZxrwh63VfnFTUzcAM9v/lznYkkRxIBGMPt1SudOKHAEec0SIRCfiuIzTcF7VGj/CaTT6gY4eWxvA==
- dependencies:
- "@babel/helper-environment-visitor" "^7.18.6"
- "@babel/helper-module-imports" "^7.18.6"
- "@babel/helper-simple-access" "^7.18.6"
- "@babel/helper-split-export-declaration" "^7.18.6"
- "@babel/helper-validator-identifier" "^7.18.6"
- "@babel/template" "^7.18.6"
- "@babel/traverse" "^7.18.8"
- "@babel/types" "^7.18.8"
+ "@babel/traverse" "^7.25.9"
+ "@babel/types" "^7.25.9"
"@babel/helper-module-transforms@^7.18.9":
version "7.18.9"
@@ -457,113 +269,54 @@
"@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==
+"@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.26.0":
+ version "7.26.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae"
+ integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==
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-module-imports" "^7.25.9"
+ "@babel/helper-validator-identifier" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
-"@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"
- integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==
+"@babel/helper-optimise-call-expression@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e"
+ integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==
dependencies:
- "@babel/types" "^7.18.6"
+ "@babel/types" "^7.25.9"
"@babel/helper-plugin-utils@^7.0.0":
version "7.0.0"
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.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==
-
"@babel/helper-plugin-utils@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz#9448974dd4fb1d80fefe72e8a0af37809cd30d6d"
integrity sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg==
-"@babel/helper-plugin-utils@^7.18.9":
- version "7.18.9"
- 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-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.9"
- "@babel/helper-wrap-function" "^7.18.9"
- "@babel/types" "^7.18.9"
+"@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.8", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.26.5":
+ version "7.26.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35"
+ integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==
-"@babel/helper-replace-supers@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.6.tgz#efedf51cfccea7b7b8c0f00002ab317e7abfe420"
- integrity sha512-fTf7zoXnUGl9gF25fXCWE26t7Tvtyn6H4hkLSYhATwJvw2uYxd3aoXplMSe0g9XbwK7bmxNes7+FGO0rB/xC0g==
+"@babel/helper-remap-async-to-generator@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz#e53956ab3d5b9fb88be04b3e2f31b523afd34b92"
+ integrity sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==
dependencies:
- "@babel/helper-environment-visitor" "^7.18.6"
- "@babel/helper-member-expression-to-functions" "^7.18.6"
- "@babel/helper-optimise-call-expression" "^7.18.6"
- "@babel/traverse" "^7.18.6"
- "@babel/types" "^7.18.6"
+ "@babel/helper-annotate-as-pure" "^7.25.9"
+ "@babel/helper-wrap-function" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
-"@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==
+"@babel/helper-replace-supers@^7.25.9":
+ version "7.26.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d"
+ integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==
dependencies:
- "@babel/helper-environment-visitor" "^7.18.9"
- "@babel/helper-member-expression-to-functions" "^7.20.7"
- "@babel/helper-optimise-call-expression" "^7.18.6"
- "@babel/template" "^7.20.7"
- "@babel/traverse" "^7.20.7"
- "@babel/types" "^7.20.7"
+ "@babel/helper-member-expression-to-functions" "^7.25.9"
+ "@babel/helper-optimise-call-expression" "^7.25.9"
+ "@babel/traverse" "^7.26.5"
"@babel/helper-simple-access@^7.18.6":
version "7.18.6"
@@ -572,26 +325,13 @@
dependencies:
"@babel/types" "^7.18.6"
-"@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.20.2"
-
-"@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.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==
+"@babel/helper-skip-transparent-expression-wrappers@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9"
+ integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==
dependencies:
- "@babel/types" "^7.20.0"
+ "@babel/traverse" "^7.25.9"
+ "@babel/types" "^7.25.9"
"@babel/helper-split-export-declaration@^7.18.6":
version "7.18.6"
@@ -600,32 +340,15 @@
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-string-parser@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c"
+ integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
"@babel/helper-validator-identifier@^7.16.7":
version "7.16.7"
@@ -637,35 +360,29 @@
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-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-identifier@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
+ integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
"@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-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/helper-validator-option@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72"
+ integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==
-"@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==
+"@babel/helper-wrap-function@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz#d99dfd595312e6c894bd7d237470025c85eea9d0"
+ integrity sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==
dependencies:
- "@babel/helper-function-name" "^7.18.9"
- "@babel/template" "^7.18.10"
- "@babel/traverse" "^7.18.10"
- "@babel/types" "^7.18.10"
+ "@babel/template" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
+ "@babel/types" "^7.25.9"
"@babel/helpers@^7.18.9":
version "7.18.9"
@@ -676,14 +393,13 @@
"@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==
+"@babel/helpers@^7.26.0":
+ version "7.26.0"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4"
+ integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==
dependencies:
- "@babel/template" "^7.20.7"
- "@babel/traverse" "^7.21.5"
- "@babel/types" "^7.21.5"
+ "@babel/template" "^7.25.9"
+ "@babel/types" "^7.26.0"
"@babel/highlight@^7.0.0":
version "7.0.0"
@@ -702,31 +418,17 @@
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/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.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":
+"@babel/parser@^7.18.6":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.8.tgz#822146080ac9c62dac0823bb3489622e0bc1cbdf"
integrity sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA==
@@ -736,691 +438,586 @@
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/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"
- integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==
- dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
-
-"@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==
+"@babel/parser@^7.25.3", "@babel/parser@^7.25.6", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.5":
+ version "7.26.5"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.5.tgz#6fec9aebddef25ca57a935c86dbb915ae2da3e1f"
+ integrity sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==
dependencies:
- "@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/types" "^7.26.5"
-"@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==
+"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe"
+ integrity sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==
dependencies:
- "@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/helper-plugin-utils" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
-"@babel/plugin-proposal-class-properties@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3"
- integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==
+"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz#af9e4fb63ccb8abcb92375b2fcfe36b60c774d30"
+ integrity sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==
dependencies:
- "@babel/helper-create-class-features-plugin" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@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==
+"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.9":
+ version "7.25.9"
+ 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.25.9.tgz#e8dc26fcd616e6c5bf2bd0d5a2c151d4f92a9137"
+ integrity sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==
dependencies:
- "@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/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-proposal-dynamic-import@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz#72bcf8d408799f547d759298c3c27c7e7faa4d94"
- integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz#807a667f9158acac6f6164b4beb85ad9ebc9e1d1"
+ integrity sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
- "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
+ "@babel/plugin-transform-optional-chaining" "^7.25.9"
-"@babel/plugin-proposal-export-namespace-from@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203"
- integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==
+"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz#de7093f1e7deaf68eadd7cc6b07f2ab82543269e"
+ integrity sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.9"
- "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
-"@babel/plugin-proposal-json-strings@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b"
- integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==
- dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
- "@babel/plugin-syntax-json-strings" "^7.8.3"
+"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2":
+ version "7.21.0-placeholder-for-preset-env.2"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703"
+ integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==
-"@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==
+"@babel/plugin-syntax-import-assertions@^7.26.0":
+ version "7.26.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz#620412405058efa56e4a564903b79355020f445f"
+ integrity sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==
dependencies:
- "@babel/helper-plugin-utils" "^7.20.2"
- "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1"
- integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==
+"@babel/plugin-syntax-import-attributes@^7.26.0":
+ version "7.26.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7"
+ integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
- "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-proposal-numeric-separator@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75"
- integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==
+"@babel/plugin-syntax-jsx@^7.24.7":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290"
+ integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
- "@babel/plugin-syntax-numeric-separator" "^7.10.4"
-
-"@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.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.20.7"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-proposal-optional-catch-binding@^7.18.6":
+"@babel/plugin-syntax-unicode-sets-regex@^7.18.6":
version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb"
- integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==
- dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
- "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
-
-"@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.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":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea"
- integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==
- dependencies:
- "@babel/helper-create-class-features-plugin" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
-
-"@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.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":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e"
- integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357"
+ integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==
dependencies:
"@babel/helper-create-regexp-features-plugin" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-proposal-unicode-property-regex@^7.4.4":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz#635d18eb10c6214210ffc5ff4932552de08188a2"
- integrity sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==
- dependencies:
- "@babel/helper-create-regexp-features-plugin" "^7.16.7"
- "@babel/helper-plugin-utils" "^7.16.7"
-
-"@babel/plugin-syntax-async-generators@^7.8.4":
- version "7.8.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
- integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
- dependencies:
- "@babel/helper-plugin-utils" "^7.8.0"
-
-"@babel/plugin-syntax-class-properties@^7.12.13":
- version "7.12.13"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10"
- integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==
- dependencies:
- "@babel/helper-plugin-utils" "^7.12.13"
-
-"@babel/plugin-syntax-class-static-block@^7.14.5":
- version "7.14.5"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406"
- integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==
- dependencies:
- "@babel/helper-plugin-utils" "^7.14.5"
-
-"@babel/plugin-syntax-dynamic-import@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3"
- integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==
- dependencies:
- "@babel/helper-plugin-utils" "^7.8.0"
-
-"@babel/plugin-syntax-export-namespace-from@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a"
- integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==
- dependencies:
- "@babel/helper-plugin-utils" "^7.8.3"
-
-"@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.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"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a"
- integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==
- dependencies:
- "@babel/helper-plugin-utils" "^7.8.0"
-
-"@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.22.5"
-
-"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
- version "7.10.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
- integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==
- dependencies:
- "@babel/helper-plugin-utils" "^7.10.4"
-
-"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9"
- integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==
+"@babel/plugin-transform-arrow-functions@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz#7821d4410bee5daaadbb4cdd9a6649704e176845"
+ integrity sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==
dependencies:
- "@babel/helper-plugin-utils" "^7.8.0"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-syntax-numeric-separator@^7.10.4":
- version "7.10.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97"
- integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==
+"@babel/plugin-transform-async-generator-functions@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz#1b18530b077d18a407c494eb3d1d72da505283a2"
+ integrity sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==
dependencies:
- "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-remap-async-to-generator" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
-"@babel/plugin-syntax-object-rest-spread@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
- integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
+"@babel/plugin-transform-async-to-generator@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz#c80008dacae51482793e5a9c08b39a5be7e12d71"
+ integrity sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==
dependencies:
- "@babel/helper-plugin-utils" "^7.8.0"
+ "@babel/helper-module-imports" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-remap-async-to-generator" "^7.25.9"
-"@babel/plugin-syntax-optional-catch-binding@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1"
- integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==
+"@babel/plugin-transform-block-scoped-functions@^7.25.9":
+ version "7.26.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz#3dc4405d31ad1cbe45293aa57205a6e3b009d53e"
+ integrity sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==
dependencies:
- "@babel/helper-plugin-utils" "^7.8.0"
+ "@babel/helper-plugin-utils" "^7.26.5"
-"@babel/plugin-syntax-optional-chaining@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
- integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
+"@babel/plugin-transform-block-scoping@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz#c33665e46b06759c93687ca0f84395b80c0473a1"
+ integrity sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==
dependencies:
- "@babel/helper-plugin-utils" "^7.8.0"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-syntax-private-property-in-object@^7.14.5":
- version "7.14.5"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad"
- integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==
+"@babel/plugin-transform-class-properties@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz#a8ce84fedb9ad512549984101fa84080a9f5f51f"
+ integrity sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==
dependencies:
- "@babel/helper-plugin-utils" "^7.14.5"
+ "@babel/helper-create-class-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-syntax-top-level-await@^7.14.5":
- version "7.14.5"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c"
- integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==
+"@babel/plugin-transform-class-static-block@^7.26.0":
+ version "7.26.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz#6c8da219f4eb15cae9834ec4348ff8e9e09664a0"
+ integrity sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==
dependencies:
- "@babel/helper-plugin-utils" "^7.14.5"
+ "@babel/helper-create-class-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@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==
+"@babel/plugin-transform-classes@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz#7152457f7880b593a63ade8a861e6e26a4469f52"
+ integrity sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==
dependencies:
- "@babel/helper-plugin-utils" "^7.21.5"
-
-"@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.20.2"
- "@babel/helper-remap-async-to-generator" "^7.18.9"
-
-"@babel/plugin-transform-block-scoped-functions@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8"
- integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==
- dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-annotate-as-pure" "^7.25.9"
+ "@babel/helper-compilation-targets" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-replace-supers" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
+ globals "^11.1.0"
-"@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==
+"@babel/plugin-transform-computed-properties@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz#db36492c78460e534b8852b1d5befe3c923ef10b"
+ integrity sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==
dependencies:
- "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/template" "^7.25.9"
-"@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==
+"@babel/plugin-transform-destructuring@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz#966ea2595c498224340883602d3cfd7a0c79cea1"
+ integrity sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==
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.21.0"
- "@babel/helper-optimise-call-expression" "^7.18.6"
- "@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/helper-plugin-utils" "^7.25.9"
-"@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==
+"@babel/plugin-transform-dotall-regex@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz#bad7945dd07734ca52fe3ad4e872b40ed09bb09a"
+ integrity sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==
dependencies:
- "@babel/helper-plugin-utils" "^7.21.5"
- "@babel/template" "^7.20.7"
+ "@babel/helper-create-regexp-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@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==
+"@babel/plugin-transform-duplicate-keys@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz#8850ddf57dce2aebb4394bb434a7598031059e6d"
+ integrity sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==
dependencies:
- "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-transform-dotall-regex@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8"
- integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==
+"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz#6f7259b4de127721a08f1e5165b852fcaa696d31"
+ integrity sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==
dependencies:
- "@babel/helper-create-regexp-features-plugin" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-create-regexp-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-transform-dotall-regex@^7.4.4":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz#6b2d67686fab15fb6a7fd4bd895d5982cfc81241"
- integrity sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==
+"@babel/plugin-transform-dynamic-import@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz#23e917de63ed23c6600c5dd06d94669dce79f7b8"
+ integrity sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==
dependencies:
- "@babel/helper-create-regexp-features-plugin" "^7.16.7"
- "@babel/helper-plugin-utils" "^7.16.7"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-transform-duplicate-keys@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e"
- integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==
+"@babel/plugin-transform-exponentiation-operator@^7.25.9":
+ version "7.26.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz#e29f01b6de302c7c2c794277a48f04a9ca7f03bc"
+ integrity sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-transform-exponentiation-operator@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd"
- integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==
+"@babel/plugin-transform-export-namespace-from@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz#90745fe55053394f554e40584cda81f2c8a402a2"
+ integrity sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==
dependencies:
- "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@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==
+"@babel/plugin-transform-for-of@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz#4bdc7d42a213397905d89f02350c5267866d5755"
+ integrity sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==
dependencies:
- "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
-"@babel/plugin-transform-function-name@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0"
- integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==
+"@babel/plugin-transform-function-name@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz#939d956e68a606661005bfd550c4fc2ef95f7b97"
+ integrity sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==
dependencies:
- "@babel/helper-compilation-targets" "^7.18.9"
- "@babel/helper-function-name" "^7.18.9"
- "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-compilation-targets" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
-"@babel/plugin-transform-literals@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc"
- integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==
+"@babel/plugin-transform-json-strings@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz#c86db407cb827cded902a90c707d2781aaa89660"
+ integrity sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-transform-member-expression-literals@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e"
- integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==
+"@babel/plugin-transform-literals@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz#1a1c6b4d4aa59bc4cad5b6b3a223a0abd685c9de"
+ integrity sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@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==
+"@babel/plugin-transform-logical-assignment-operators@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz#b19441a8c39a2fda0902900b306ea05ae1055db7"
+ integrity sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==
dependencies:
- "@babel/helper-module-transforms" "^7.20.11"
- "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@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==
+"@babel/plugin-transform-member-expression-literals@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz#63dff19763ea64a31f5e6c20957e6a25e41ed5de"
+ integrity sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==
dependencies:
- "@babel/helper-module-transforms" "^7.21.5"
- "@babel/helper-plugin-utils" "^7.21.5"
- "@babel/helper-simple-access" "^7.21.5"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@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==
+"@babel/plugin-transform-modules-amd@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz#49ba478f2295101544abd794486cd3088dddb6c5"
+ integrity sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==
dependencies:
- "@babel/helper-hoist-variables" "^7.18.6"
- "@babel/helper-module-transforms" "^7.20.11"
- "@babel/helper-plugin-utils" "^7.20.2"
- "@babel/helper-validator-identifier" "^7.19.1"
+ "@babel/helper-module-transforms" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-transform-modules-umd@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9"
- integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==
+"@babel/plugin-transform-modules-commonjs@^7.25.9":
+ version "7.26.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz#8f011d44b20d02c3de44d8850d971d8497f981fb"
+ integrity sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==
dependencies:
- "@babel/helper-module-transforms" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-module-transforms" "^7.26.0"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@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==
+"@babel/plugin-transform-modules-systemjs@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz#8bd1b43836269e3d33307151a114bcf3ba6793f8"
+ integrity sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==
dependencies:
- "@babel/helper-create-regexp-features-plugin" "^7.20.5"
- "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-module-transforms" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-validator-identifier" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
-"@babel/plugin-transform-new-target@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8"
- integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==
+"@babel/plugin-transform-modules-umd@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz#6710079cdd7c694db36529a1e8411e49fcbf14c9"
+ integrity sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-module-transforms" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-transform-object-super@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c"
- integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==
+"@babel/plugin-transform-named-capturing-groups-regex@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz#454990ae6cc22fd2a0fa60b3a2c6f63a38064e6a"
+ integrity sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
- "@babel/helper-replace-supers" "^7.18.6"
+ "@babel/helper-create-regexp-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@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==
+"@babel/plugin-transform-new-target@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz#42e61711294b105c248336dcb04b77054ea8becd"
+ integrity sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==
dependencies:
- "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@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==
+"@babel/plugin-transform-nullish-coalescing-operator@^7.25.9":
+ version "7.26.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz#fbf6b3c92cb509e7b319ee46e3da89c5bedd31fe"
+ integrity sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==
dependencies:
- "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-plugin-utils" "^7.26.5"
-"@babel/plugin-transform-property-literals@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3"
- integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==
+"@babel/plugin-transform-numeric-separator@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz#bfed75866261a8b643468b0ccfd275f2033214a1"
+ integrity sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@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==
+"@babel/plugin-transform-object-rest-spread@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz#0203725025074164808bcf1a2cfa90c652c99f18"
+ integrity sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==
dependencies:
- "@babel/helper-plugin-utils" "^7.21.5"
- regenerator-transform "^0.15.1"
+ "@babel/helper-compilation-targets" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/plugin-transform-parameters" "^7.25.9"
-"@babel/plugin-transform-reserved-words@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a"
- integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==
+"@babel/plugin-transform-object-super@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz#385d5de135162933beb4a3d227a2b7e52bb4cf03"
+ integrity sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-replace-supers" "^7.25.9"
-"@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==
+"@babel/plugin-transform-optional-catch-binding@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz#10e70d96d52bb1f10c5caaac59ac545ea2ba7ff3"
+ integrity sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==
dependencies:
- "@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/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-transform-shorthand-properties@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9"
- integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==
+"@babel/plugin-transform-optional-chaining@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz#e142eb899d26ef715435f201ab6e139541eee7dd"
+ integrity sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
-"@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==
+"@babel/plugin-transform-parameters@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz#b856842205b3e77e18b7a7a1b94958069c7ba257"
+ integrity sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==
dependencies:
- "@babel/helper-plugin-utils" "^7.20.2"
- "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-transform-sticky-regex@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc"
- integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==
+"@babel/plugin-transform-private-methods@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57"
+ integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-create-class-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-transform-template-literals@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e"
- integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==
+"@babel/plugin-transform-private-property-in-object@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz#9c8b73e64e6cc3cbb2743633885a7dd2c385fe33"
+ integrity sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-annotate-as-pure" "^7.25.9"
+ "@babel/helper-create-class-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@babel/plugin-transform-typeof-symbol@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0"
- integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==
+"@babel/plugin-transform-property-literals@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz#d72d588bd88b0dec8b62e36f6fda91cedfe28e3f"
+ integrity sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@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==
+"@babel/plugin-transform-regenerator@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz#03a8a4670d6cebae95305ac6defac81ece77740b"
+ integrity sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==
dependencies:
- "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ regenerator-transform "^0.15.2"
-"@babel/plugin-transform-unicode-regex@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca"
- integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==
+"@babel/plugin-transform-regexp-modifiers@^7.26.0":
+ version "7.26.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz#2f5837a5b5cd3842a919d8147e9903cc7455b850"
+ integrity sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==
dependencies:
- "@babel/helper-create-regexp-features-plugin" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-create-regexp-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
-"@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.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.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.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.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.20.7"
- "@babel/plugin-proposal-optional-catch-binding" "^7.18.6"
- "@babel/plugin-proposal-optional-chaining" "^7.21.0"
- "@babel/plugin-proposal-private-methods" "^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.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"
- "@babel/plugin-syntax-numeric-separator" "^7.10.4"
- "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
- "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
- "@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.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.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.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.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.20.5"
- "@babel/plugin-transform-new-target" "^7.18.6"
- "@babel/plugin-transform-object-super" "^7.18.6"
- "@babel/plugin-transform-parameters" "^7.21.3"
- "@babel/plugin-transform-property-literals" "^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.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.21.5"
- "@babel/plugin-transform-unicode-regex" "^7.18.6"
- "@babel/preset-modules" "^0.1.5"
- "@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":
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9"
- integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==
+"@babel/plugin-transform-reserved-words@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz#0398aed2f1f10ba3f78a93db219b27ef417fb9ce"
+ integrity sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-runtime@7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz#62723ea3f5b31ffbe676da9d6dae17138ae580ea"
+ integrity sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==
+ dependencies:
+ "@babel/helper-module-imports" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ babel-plugin-polyfill-corejs2 "^0.4.10"
+ babel-plugin-polyfill-corejs3 "^0.10.6"
+ babel-plugin-polyfill-regenerator "^0.6.1"
+ semver "^6.3.1"
+
+"@babel/plugin-transform-shorthand-properties@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz#bb785e6091f99f826a95f9894fc16fde61c163f2"
+ integrity sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-spread@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz#24a35153931b4ba3d13cec4a7748c21ab5514ef9"
+ integrity sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
+
+"@babel/plugin-transform-sticky-regex@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz#c7f02b944e986a417817b20ba2c504dfc1453d32"
+ integrity sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-template-literals@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz#6dbd4a24e8fad024df76d1fac6a03cf413f60fe1"
+ integrity sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-typeof-symbol@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz#224ba48a92869ddbf81f9b4a5f1204bbf5a2bc4b"
+ integrity sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-unicode-escapes@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz#a75ef3947ce15363fccaa38e2dd9bc70b2788b82"
+ integrity sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-unicode-property-regex@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz#a901e96f2c1d071b0d1bb5dc0d3c880ce8f53dd3"
+ integrity sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-unicode-regex@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz#5eae747fe39eacf13a8bd006a4fb0b5d1fa5e9b1"
+ integrity sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-unicode-sets-regex@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz#65114c17b4ffc20fa5b163c63c70c0d25621fabe"
+ integrity sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/preset-env@7.26.0":
+ version "7.26.0"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.26.0.tgz#30e5c6bc1bcc54865bff0c5a30f6d4ccdc7fa8b1"
+ integrity sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==
+ dependencies:
+ "@babel/compat-data" "^7.26.0"
+ "@babel/helper-compilation-targets" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-validator-option" "^7.25.9"
+ "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.9"
+ "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.9"
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.9"
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.25.9"
+ "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.9"
+ "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2"
+ "@babel/plugin-syntax-import-assertions" "^7.26.0"
+ "@babel/plugin-syntax-import-attributes" "^7.26.0"
+ "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6"
+ "@babel/plugin-transform-arrow-functions" "^7.25.9"
+ "@babel/plugin-transform-async-generator-functions" "^7.25.9"
+ "@babel/plugin-transform-async-to-generator" "^7.25.9"
+ "@babel/plugin-transform-block-scoped-functions" "^7.25.9"
+ "@babel/plugin-transform-block-scoping" "^7.25.9"
+ "@babel/plugin-transform-class-properties" "^7.25.9"
+ "@babel/plugin-transform-class-static-block" "^7.26.0"
+ "@babel/plugin-transform-classes" "^7.25.9"
+ "@babel/plugin-transform-computed-properties" "^7.25.9"
+ "@babel/plugin-transform-destructuring" "^7.25.9"
+ "@babel/plugin-transform-dotall-regex" "^7.25.9"
+ "@babel/plugin-transform-duplicate-keys" "^7.25.9"
+ "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.9"
+ "@babel/plugin-transform-dynamic-import" "^7.25.9"
+ "@babel/plugin-transform-exponentiation-operator" "^7.25.9"
+ "@babel/plugin-transform-export-namespace-from" "^7.25.9"
+ "@babel/plugin-transform-for-of" "^7.25.9"
+ "@babel/plugin-transform-function-name" "^7.25.9"
+ "@babel/plugin-transform-json-strings" "^7.25.9"
+ "@babel/plugin-transform-literals" "^7.25.9"
+ "@babel/plugin-transform-logical-assignment-operators" "^7.25.9"
+ "@babel/plugin-transform-member-expression-literals" "^7.25.9"
+ "@babel/plugin-transform-modules-amd" "^7.25.9"
+ "@babel/plugin-transform-modules-commonjs" "^7.25.9"
+ "@babel/plugin-transform-modules-systemjs" "^7.25.9"
+ "@babel/plugin-transform-modules-umd" "^7.25.9"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.25.9"
+ "@babel/plugin-transform-new-target" "^7.25.9"
+ "@babel/plugin-transform-nullish-coalescing-operator" "^7.25.9"
+ "@babel/plugin-transform-numeric-separator" "^7.25.9"
+ "@babel/plugin-transform-object-rest-spread" "^7.25.9"
+ "@babel/plugin-transform-object-super" "^7.25.9"
+ "@babel/plugin-transform-optional-catch-binding" "^7.25.9"
+ "@babel/plugin-transform-optional-chaining" "^7.25.9"
+ "@babel/plugin-transform-parameters" "^7.25.9"
+ "@babel/plugin-transform-private-methods" "^7.25.9"
+ "@babel/plugin-transform-private-property-in-object" "^7.25.9"
+ "@babel/plugin-transform-property-literals" "^7.25.9"
+ "@babel/plugin-transform-regenerator" "^7.25.9"
+ "@babel/plugin-transform-regexp-modifiers" "^7.26.0"
+ "@babel/plugin-transform-reserved-words" "^7.25.9"
+ "@babel/plugin-transform-shorthand-properties" "^7.25.9"
+ "@babel/plugin-transform-spread" "^7.25.9"
+ "@babel/plugin-transform-sticky-regex" "^7.25.9"
+ "@babel/plugin-transform-template-literals" "^7.25.9"
+ "@babel/plugin-transform-typeof-symbol" "^7.25.9"
+ "@babel/plugin-transform-unicode-escapes" "^7.25.9"
+ "@babel/plugin-transform-unicode-property-regex" "^7.25.9"
+ "@babel/plugin-transform-unicode-regex" "^7.25.9"
+ "@babel/plugin-transform-unicode-sets-regex" "^7.25.9"
+ "@babel/preset-modules" "0.1.6-no-external-plugins"
+ babel-plugin-polyfill-corejs2 "^0.4.10"
+ babel-plugin-polyfill-corejs3 "^0.10.6"
+ babel-plugin-polyfill-regenerator "^0.6.1"
+ core-js-compat "^3.38.1"
+ semver "^6.3.1"
+
+"@babel/preset-modules@0.1.6-no-external-plugins":
+ version "0.1.6-no-external-plugins"
+ resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a"
+ integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/plugin-proposal-unicode-property-regex" "^7.4.4"
- "@babel/plugin-transform-dotall-regex" "^7.4.4"
"@babel/types" "^7.4.4"
esutils "^2.0.2"
-"@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==
+"@babel/register@7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.25.9.tgz#1c465acf7dc983d70ccc318eb5b887ecb04f021b"
+ integrity sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==
dependencies:
clone-deep "^4.0.1"
find-cache-dir "^2.0.0"
make-dir "^2.1.0"
- pirates "^4.0.5"
+ pirates "^4.0.6"
source-map-support "^0.5.16"
-"@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==
+"@babel/runtime@7.26.0":
+ version "7.26.0"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1"
+ integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==
dependencies:
- regenerator-runtime "^0.13.11"
+ regenerator-runtime "^0.14.0"
"@babel/runtime@^7.8.4":
version "7.17.7"
@@ -1447,23 +1044,14 @@
"@babel/parser" "^7.18.6"
"@babel/types" "^7.18.6"
-"@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==
+"@babel/template@^7.25.0", "@babel/template@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016"
+ integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==
dependencies:
- "@babel/code-frame" "^7.22.13"
- "@babel/parser" "^7.22.15"
- "@babel/types" "^7.22.15"
+ "@babel/code-frame" "^7.25.9"
+ "@babel/parser" "^7.25.9"
+ "@babel/types" "^7.25.9"
"@babel/traverse@^7.18.10":
version "7.18.10"
@@ -1481,22 +1069,6 @@
debug "^4.1.0"
globals "^11.1.0"
-"@babel/traverse@^7.18.6", "@babel/traverse@^7.18.8":
- version "7.18.8"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.8.tgz#f095e62ab46abf1da35e5a2011f43aee72d8d5b0"
- integrity sha512-UNg/AcSySJYR/+mIcJQDCv00T+AqRO7j/ZEJLzpaYtgM48rMg5MnkJgyNqkzo88+p4tfRvZJCEiwwfG6h4jkRg==
- dependencies:
- "@babel/code-frame" "^7.18.6"
- "@babel/generator" "^7.18.7"
- "@babel/helper-environment-visitor" "^7.18.6"
- "@babel/helper-function-name" "^7.18.6"
- "@babel/helper-hoist-variables" "^7.18.6"
- "@babel/helper-split-export-declaration" "^7.18.6"
- "@babel/parser" "^7.18.8"
- "@babel/types" "^7.18.8"
- debug "^4.1.0"
- globals "^11.1.0"
-
"@babel/traverse@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.9.tgz#deeff3e8f1bad9786874cb2feda7a2d77a904f98"
@@ -1513,67 +1085,16 @@
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==
+"@babel/traverse@^7.25.6", "@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5":
+ version "7.26.5"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.5.tgz#6d0be3e772ff786456c1a37538208286f6e79021"
+ integrity sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==
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.7":
- version "7.23.7"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305"
- integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==
- dependencies:
- "@babel/code-frame" "^7.23.5"
- "@babel/generator" "^7.23.6"
- "@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.23.6"
- "@babel/types" "^7.23.6"
+ "@babel/code-frame" "^7.26.2"
+ "@babel/generator" "^7.26.5"
+ "@babel/parser" "^7.26.5"
+ "@babel/template" "^7.25.9"
+ "@babel/types" "^7.26.5"
debug "^4.3.1"
globals "^11.1.0"
@@ -1585,14 +1106,6 @@
lodash "^4.17.10"
to-fast-properties "^2.0.0"
-"@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==
- dependencies:
- "@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"
@@ -1602,7 +1115,7 @@
"@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":
+"@babel/types@^7.18.6":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.8.tgz#c5af199951bf41ba4a6a9a6d0d8ad722b30cd42f"
integrity sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw==
@@ -1618,58 +1131,20 @@
"@babel/helper-validator-identifier" "^7.18.6"
to-fast-properties "^2.0.0"
-"@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==
+"@babel/types@^7.25.6", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.5":
+ version "7.26.5"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.5.tgz#7a1e1c01d28e26d1fe7f8ec9567b3b92b9d07747"
+ integrity sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==
dependencies:
- "@babel/helper-string-parser" "^7.19.4"
- "@babel/helper-validator-identifier" "^7.19.1"
- to-fast-properties "^2.0.0"
+ "@babel/helper-string-parser" "^7.25.9"
+ "@babel/helper-validator-identifier" "^7.25.9"
-"@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", "@babel/types@^7.23.6":
- version "7.23.6"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd"
- integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==
+"@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==
dependencies:
- "@babel/helper-string-parser" "^7.23.4"
- "@babel/helper-validator-identifier" "^7.22.20"
+ "@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
"@chenfengyuan/vue-qrcode@2.0.0":
@@ -1687,14 +1162,26 @@
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36"
integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==
-"@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==
+"@eslint-community/eslint-utils@^4.2.0":
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56"
+ integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==
+ dependencies:
+ eslint-visitor-keys "^3.4.3"
+
+"@eslint-community/regexpp@^4.6.1":
+ version "4.12.1"
+ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0"
+ integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
+
+"@eslint/eslintrc@^2.1.4":
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad"
+ integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
- espree "^9.4.0"
+ espree "^9.6.0"
globals "^13.19.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
@@ -1702,6 +1189,11 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
+"@eslint/js@8.57.1":
+ version "8.57.1"
+ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2"
+ integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
+
"@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"
@@ -1733,13 +1225,13 @@
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==
+"@humanwhocodes/config-array@^0.13.0":
+ version "0.13.0"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748"
+ integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==
dependencies:
- "@humanwhocodes/object-schema" "^1.2.1"
- debug "^4.1.1"
+ "@humanwhocodes/object-schema" "^2.0.3"
+ debug "^4.3.1"
minimatch "^3.0.5"
"@humanwhocodes/module-importer@^1.0.1":
@@ -1747,10 +1239,10 @@
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==
+"@humanwhocodes/object-schema@^2.0.3":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
+ integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
"@intlify/bundle-utils@3.4.0":
version "3.4.0"
@@ -1763,30 +1255,21 @@
source-map "0.6.1"
yaml-eslint-parser "^0.3.2"
-"@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.2"
- "@intlify/message-compiler" "9.2.2"
- "@intlify/shared" "9.2.2"
- "@intlify/vue-devtools" "9.2.2"
-
-"@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==
+"@intlify/core-base@10.0.5":
+ version "10.0.5"
+ resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-10.0.5.tgz#c4d992381f8c3a50c79faf67be3404b399c3be28"
+ integrity sha512-F3snDTQs0MdvnnyzTDTVkOYVAZOE/MHwRvF7mn7Jw1yuih4NrFYLNYIymGlLmq4HU2iIdzYsZ7f47bOcwY73XQ==
dependencies:
- "@intlify/shared" "9.2.2"
+ "@intlify/message-compiler" "10.0.5"
+ "@intlify/shared" "10.0.5"
-"@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==
+"@intlify/message-compiler@10.0.5":
+ version "10.0.5"
+ resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-10.0.5.tgz#4eeace9f4560020d5e5d77f32bed7755e71d8efd"
+ integrity sha512-6GT1BJ852gZ0gItNZN2krX5QAmea+cmdjMvsWohArAZ3GmHdnNANEcF9JjPXAMRtQ6Ux5E269ymamg/+WU6tQA==
dependencies:
- "@intlify/shared" "9.2.2"
- source-map "0.6.1"
+ "@intlify/shared" "10.0.5"
+ source-map-js "^1.0.2"
"@intlify/message-compiler@next":
version "9.2.0-beta.34"
@@ -1796,24 +1279,21 @@
"@intlify/shared" "9.2.0-beta.34"
source-map "0.6.1"
+"@intlify/shared@10.0.5":
+ version "10.0.5"
+ resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-10.0.5.tgz#1b46ca8b541f03508fe28da8f34e4bb85506d6bc"
+ integrity sha512-bmsP4L2HqBF6i6uaMqJMcFBONVjKt+siGluRq4Ca4C0q7W2eMaVZr8iCgF9dKbcVXutftkC7D6z2SaSMmLiDyA==
+
"@intlify/shared@9.2.0-beta.34", "@intlify/shared@next":
version "9.2.0-beta.34"
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.2.0-beta.34.tgz#e8e9a93455eadcc9785fe2e2437fe037fc267f7d"
integrity sha512-hbUKcVbTOkLVpnlSeZE1OPgEI7FpvhuZF/gb84xECTjXEImIa3u0fIcJKUUffv3dlAx8fMOE5xKgDzngidm0tw==
-"@intlify/shared@9.2.2", "@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.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.2"
- "@intlify/shared" "9.2.2"
-
"@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"
@@ -1825,6 +1305,18 @@
json5 "^2.2.0"
loader-utils "^2.0.0"
+"@isaacs/cliui@^8.0.2":
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
+ integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==
+ dependencies:
+ string-width "^5.1.2"
+ string-width-cjs "npm:string-width@^4.2.0"
+ strip-ansi "^7.0.1"
+ strip-ansi-cjs "npm:strip-ansi@^6.0.1"
+ wrap-ansi "^8.1.0"
+ wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
+
"@istanbuljs/schema@^0.1.2":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
@@ -1866,21 +1358,35 @@
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
-"@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/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.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/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"
@@ -1889,21 +1395,21 @@
"@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.15":
+"@jridgewell/sourcemap-codec@^1.4.14":
version "1.4.15"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
+"@jridgewell/sourcemap-codec@^1.5.0":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
+ integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
+
"@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"
@@ -1920,13 +1426,13 @@
"@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==
+"@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/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
"@jridgewell/trace-mapping@^0.3.9":
version "0.3.14"
@@ -2014,10 +1520,20 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
-"@ruffle-rs/ruffle@0.1.0-nightly.2024.3.17":
- version "0.1.0-nightly.2024.3.17"
- resolved "https://registry.yarnpkg.com/@ruffle-rs/ruffle/-/ruffle-0.1.0-nightly.2024.3.17.tgz#df3626f7277ed85742a602508c191d3186b0cabc"
- integrity sha512-Wl/7CDZSmomOcfBeOYOO6xUUrN7upnGRDJEm3fpCtN3j5kU8dGF8xzaziAttjkD8byLYS09InE7PlUTyyAwCiQ==
+"@one-ini/wasm@0.1.1":
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/@one-ini/wasm/-/wasm-0.1.1.tgz#6013659736c9dbfccc96e8a9c2b3de317df39323"
+ integrity sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==
+
+"@pkgjs/parseargs@^0.11.0":
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
+ integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
+
+"@ruffle-rs/ruffle@0.1.0-nightly.2025.1.13":
+ version "0.1.0-nightly.2025.1.13"
+ resolved "https://registry.yarnpkg.com/@ruffle-rs/ruffle/-/ruffle-0.1.0-nightly.2025.1.13.tgz#0d9c796b6b7f28c524f6ebbc356f3697ca0b7488"
+ integrity sha512-deFf2TE6UscMv5fRo3kjxJtpHCrS1kc2L7Hi8w2QSSbnvODJMqEGDYcp7+VI7R85lDOSze0YPJnDS4S71JHzkg==
"@sinclair/typebox@^0.24.1":
version "0.24.51"
@@ -2059,10 +1575,10 @@
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz#5981a8db18b56ba38ef0efb7d995b12aa7b51918"
integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==
-"@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==
+"@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"
@@ -2084,11 +1600,6 @@
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"
@@ -2220,202 +1731,145 @@
resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
+"@ungap/structured-clone@^1.2.0":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.1.tgz#28fa185f67daaf7b7a1a8c1d445132c5d979f8bd"
+ integrity sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==
+
"@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-helper-vue-transform-on@1.2.1":
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.1.tgz#3a48da809025b9a0eb4f4b3030e0d316c40fac0a"
- integrity sha512-jtEXim+pfyHWwvheYwUwSXm43KwQo8nhOBDyjrUITV6X2tB7lJm6n/+4sqR8137UVZZul5hBzWHdZ2uStYpyRQ==
+"@vue/babel-helper-vue-transform-on@1.2.5":
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.5.tgz#b9e195b92bfa8d15d5aa9581ca01cb702dbcc19d"
+ integrity sha512-lOz4t39ZdmU4DJAa2hwPYmKc8EsuGa2U0L9KaZaOJUt0UwQNjNA3AZTq6uEivhOKhhG1Wvy96SvYBoFmCg3uuw==
-"@vue/babel-plugin-jsx@1.2.1":
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.1.tgz#786c5395605a1d2463d6b10d8a7f3abdc01d25ce"
- integrity sha512-Yy9qGktktXhB39QE99So/BO2Uwm/ZG+gpL9vMg51ijRRbINvgbuhyJEi4WYmGRMx/MSTfK0xjgZ3/MyY+iLCEg==
- 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.22.15"
- "@babel/traverse" "^7.23.7"
- "@babel/types" "^7.23.6"
- "@vue/babel-helper-vue-transform-on" "1.2.1"
- "@vue/babel-plugin-resolve-type" "1.2.1"
- camelcase "^6.3.0"
+"@vue/babel-plugin-jsx@1.2.5":
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.5.tgz#77f4f9f189d00c24ebd587ab84ae615dfa1c3abb"
+ integrity sha512-zTrNmOd4939H9KsRIGmmzn3q2zvv1mjxkYZHgqHZgDrXz5B1Q3WyGEjO2f+JrmKghvl1JIRcvo63LgM1kH5zFg==
+ dependencies:
+ "@babel/helper-module-imports" "^7.24.7"
+ "@babel/helper-plugin-utils" "^7.24.8"
+ "@babel/plugin-syntax-jsx" "^7.24.7"
+ "@babel/template" "^7.25.0"
+ "@babel/traverse" "^7.25.6"
+ "@babel/types" "^7.25.6"
+ "@vue/babel-helper-vue-transform-on" "1.2.5"
+ "@vue/babel-plugin-resolve-type" "1.2.5"
html-tags "^3.3.1"
svg-tags "^1.0.0"
-"@vue/babel-plugin-resolve-type@1.2.1":
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.1.tgz#874fb3e02d033b3dd2e0fc883a3d1ceef0bdf39b"
- integrity sha512-IOtnI7pHunUzHS/y+EG/yPABIAp0VN8QhQ0UCS09jeMVxgAnI9qdOzO85RXdQGxq+aWCdv8/+k3W0aYO6j/8fQ==
- 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.6"
- "@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.45"
- estree-walker "^2.0.2"
- source-map "^0.6.1"
-
-"@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:
- "@babel/parser" "^7.23.6"
- "@vue/shared" "3.4.15"
+"@vue/babel-plugin-resolve-type@1.2.5":
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.5.tgz#f6ed0d39987fe0158370659b73156c55e80d17b5"
+ integrity sha512-U/ibkQrf5sx0XXRnUZD1mo5F7PkpKyTbfXM3a3rC4YnUz6crHEz9Jg09jzzL6QYlXNto/9CePdOg/c87O4Nlfg==
+ dependencies:
+ "@babel/code-frame" "^7.24.7"
+ "@babel/helper-module-imports" "^7.24.7"
+ "@babel/helper-plugin-utils" "^7.24.8"
+ "@babel/parser" "^7.25.6"
+ "@vue/compiler-sfc" "^3.5.3"
+
+"@vue/compiler-core@3.5.13":
+ version "3.5.13"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz#b0ae6c4347f60c03e849a05d34e5bf747c9bda05"
+ integrity sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==
+ dependencies:
+ "@babel/parser" "^7.25.3"
+ "@vue/shared" "3.5.13"
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-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.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-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"
+ source-map-js "^1.2.0"
+
+"@vue/compiler-dom@3.5.13":
+ version "3.5.13"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz#bb1b8758dbc542b3658dda973b98a1c9311a8a58"
+ integrity sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==
+ dependencies:
+ "@vue/compiler-core" "3.5.13"
+ "@vue/shared" "3.5.13"
+
+"@vue/compiler-sfc@3.5.13", "@vue/compiler-sfc@^3.5.3":
+ version "3.5.13"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz#461f8bd343b5c06fac4189c4fef8af32dea82b46"
+ integrity sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==
+ dependencies:
+ "@babel/parser" "^7.25.3"
+ "@vue/compiler-core" "3.5.13"
+ "@vue/compiler-dom" "3.5.13"
+ "@vue/compiler-ssr" "3.5.13"
+ "@vue/shared" "3.5.13"
estree-walker "^2.0.2"
- magic-string "^0.30.5"
- postcss "^8.4.33"
- source-map-js "^1.0.2"
+ magic-string "^0.30.11"
+ postcss "^8.4.48"
+ source-map-js "^1.2.0"
-"@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==
+"@vue/compiler-ssr@3.5.13":
+ version "3.5.13"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz#e771adcca6d3d000f91a4277c972a996d07f43ba"
+ integrity sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==
dependencies:
- "@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/compiler-dom" "3.5.13"
+ "@vue/shared" "3.5.13"
"@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.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/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.45"
- "@vue/shared" "3.2.45"
- estree-walker "^2.0.2"
- magic-string "^0.25.7"
+"@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.6.4":
+ version "6.6.4"
+ resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343"
+ integrity sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==
-"@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==
+"@vue/reactivity@3.5.13":
+ version "3.5.13"
+ resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.13.tgz#b41ff2bb865e093899a22219f5b25f97b6fe155f"
+ integrity sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==
dependencies:
- "@vue/shared" "3.2.45"
+ "@vue/shared" "3.5.13"
-"@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==
+"@vue/runtime-core@3.5.13":
+ version "3.5.13"
+ resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz#1fafa4bf0b97af0ebdd9dbfe98cd630da363a455"
+ integrity sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==
dependencies:
- "@vue/reactivity" "3.2.45"
- "@vue/shared" "3.2.45"
+ "@vue/reactivity" "3.5.13"
+ "@vue/shared" "3.5.13"
-"@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==
+"@vue/runtime-dom@3.5.13":
+ version "3.5.13"
+ resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz#610fc795de9246300e8ae8865930d534e1246215"
+ integrity sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==
dependencies:
- "@vue/runtime-core" "3.2.45"
- "@vue/shared" "3.2.45"
- csstype "^2.6.8"
+ "@vue/reactivity" "3.5.13"
+ "@vue/runtime-core" "3.5.13"
+ "@vue/shared" "3.5.13"
+ csstype "^3.1.3"
-"@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==
+"@vue/server-renderer@3.5.13":
+ version "3.5.13"
+ resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz#429ead62ee51de789646c22efe908e489aad46f7"
+ integrity sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==
dependencies:
- "@vue/compiler-ssr" "3.2.45"
- "@vue/shared" "3.2.45"
+ "@vue/compiler-ssr" "3.5.13"
+ "@vue/shared" "3.5.13"
-"@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/shared@3.5.13":
+ version "3.5.13"
+ resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f"
+ integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==
-"@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==
-
-"@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==
+"@vue/test-utils@2.4.6":
+ version "2.4.6"
+ resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-2.4.6.tgz#7d534e70c4319d2a587d6a3b45a39e9695ade03c"
+ integrity sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==
dependencies:
- js-beautify "1.14.6"
+ js-beautify "^1.14.9"
+ vue-component-type-helpers "^2.0.0"
"@vuelidate/core@2.0.3":
version "2.0.3"
@@ -2565,10 +2019,10 @@ abab@^2.0.5, abab@^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==
+abbrev@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf"
+ integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==
accepts@~1.3.4:
version "1.3.7"
@@ -2613,11 +2067,16 @@ acorn@^7.1.1, acorn@^7.4.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
-acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0:
+acorn@^8.5.0, acorn@^8.7.1:
version "8.8.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
+acorn@^8.9.0:
+ version "8.14.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0"
+ integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
+
agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
@@ -2644,7 +2103,7 @@ ajv-keywords@^5.0.0:
dependencies:
fast-deep-equal "^3.1.3"
-ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5:
+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==
@@ -2707,6 +2166,11 @@ ansi-regex@^5.0.1:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+ansi-regex@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654"
+ integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==
+
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -2732,6 +2196,11 @@ ansi-styles@^4.1.0:
"@types/color-name" "^1.1.1"
color-convert "^2.0.1"
+ansi-styles@^6.1.0:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
+ integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
+
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"
@@ -2817,16 +2286,16 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
-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==
+autoprefixer@10.4.20:
+ version "10.4.20"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.20.tgz#5caec14d43976ef42e32dcb4bd62878e96be5b3b"
+ integrity sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==
dependencies:
- browserslist "^4.23.0"
- caniuse-lite "^1.0.30001599"
+ browserslist "^4.23.3"
+ caniuse-lite "^1.0.30001646"
fraction.js "^4.3.7"
normalize-range "^0.1.2"
- picocolors "^1.0.0"
+ picocolors "^1.0.1"
postcss-value-parser "^4.2.0"
available-typed-arrays@^1.0.5:
@@ -2848,10 +2317,10 @@ axios@^1.1.3:
form-data "^4.0.0"
proxy-from-env "^1.1.0"
-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==
+babel-loader@9.2.1:
+ version "9.2.1"
+ resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-9.2.1.tgz#04c7835db16c246dd19ba0914418f3937797587b"
+ integrity sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==
dependencies:
find-cache-dir "^4.0.0"
schema-utils "^4.0.0"
@@ -2867,29 +2336,29 @@ babel-plugin-lodash@3.3.4:
lodash "^4.17.10"
require-package-name "^2.0.1"
-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==
+babel-plugin-polyfill-corejs2@^0.4.10:
+ version "0.4.12"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz#ca55bbec8ab0edeeef3d7b8ffd75322e210879a9"
+ integrity sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==
dependencies:
- "@babel/compat-data" "^7.17.7"
- "@babel/helper-define-polyfill-provider" "^0.3.3"
- semver "^6.1.1"
+ "@babel/compat-data" "^7.22.6"
+ "@babel/helper-define-polyfill-provider" "^0.6.3"
+ semver "^6.3.1"
-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==
+babel-plugin-polyfill-corejs3@^0.10.6:
+ version "0.10.6"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz#2deda57caef50f59c525aeb4964d3b2f867710c7"
+ integrity sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==
dependencies:
- "@babel/helper-define-polyfill-provider" "^0.3.3"
- core-js-compat "^3.25.1"
+ "@babel/helper-define-polyfill-provider" "^0.6.2"
+ core-js-compat "^3.38.0"
-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==
+babel-plugin-polyfill-regenerator@^0.6.1:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz#abeb1f3f1c762eace37587f42548b08b57789bc8"
+ integrity sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==
dependencies:
- "@babel/helper-define-polyfill-provider" "^0.3.3"
+ "@babel/helper-define-polyfill-provider" "^0.6.3"
balanced-match@^1.0.0:
version "1.0.0"
@@ -2928,21 +2397,21 @@ bl@^4.1.0:
inherits "^2.0.4"
readable-stream "^3.4.0"
-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==
+body-parser@1.20.3:
+ version "1.20.3"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6"
+ integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
dependencies:
bytes "3.1.2"
- content-type "~1.0.4"
+ content-type "~1.0.5"
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
http-errors "2.0.0"
iconv-lite "0.4.24"
on-finished "2.4.1"
- qs "6.11.0"
- raw-body "2.5.1"
+ qs "6.13.0"
+ raw-body "2.5.2"
type-is "~1.6.18"
unpipe "1.0.0"
@@ -3037,25 +2506,15 @@ browserslist@^4.20.2:
node-releases "^2.0.5"
update-browserslist-db "^1.0.4"
-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.30001400"
- electron-to-chromium "^1.4.251"
- node-releases "^2.0.6"
- 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==
+browserslist@^4.23.3, browserslist@^4.24.0, browserslist@^4.24.3:
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b"
+ integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==
dependencies:
- caniuse-lite "^1.0.30001587"
- electron-to-chromium "^1.4.668"
- node-releases "^2.0.14"
- update-browserslist-db "^1.0.13"
+ caniuse-lite "^1.0.30001688"
+ electron-to-chromium "^1.5.73"
+ node-releases "^2.0.19"
+ update-browserslist-db "^1.1.1"
buffer-crc32@~0.2.3:
version "0.2.13"
@@ -3086,6 +2545,14 @@ bytes@3.1.2:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
+call-bind-apply-helpers@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840"
+ integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==
+ dependencies:
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+
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"
@@ -3094,6 +2561,14 @@ call-bind@^1.0.0, call-bind@^1.0.2:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
+call-bound@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.3.tgz#41cfd032b593e39176a71533ab4f384aa04fd681"
+ integrity sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==
+ dependencies:
+ call-bind-apply-helpers "^1.0.1"
+ get-intrinsic "^1.2.6"
+
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@@ -3129,7 +2604,7 @@ camelcase@^6.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e"
integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
-camelcase@^6.2.0, camelcase@^6.3.0:
+camelcase@^6.2.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
@@ -3144,30 +2619,15 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001370:
- version "1.0.30001376"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001376.tgz#af2450833e5a06873fbb030a9556ca9461a2736d"
- integrity sha512-I27WhtOQ3X3v3it9gNs/oTpoE5KpwmqKR5oKPA8M0G7uMXh9Ty81Q904HpKUrM30ei7zfcL5jE7AXefgbOfMig==
-
-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.0, caniuse-lite@^1.0.30001359, caniuse-lite@^1.0.30001370:
+ 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.30001400:
- version "1.0.30001418"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001418.tgz#5f459215192a024c99e3e3a53aac310fc7cf24e6"
- integrity sha512-oIs7+JL3K9JRQ3jPZjlH6qyYDp+nBTCais7hjh0s+fuBwufc7uZ7hPYMXrDOJhV360KGMTcczMRObk0/iMqZRg==
-
-caniuse-lite@^1.0.30001587:
- version "1.0.30001591"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz#16745e50263edc9f395895a7cd468b9f3767cf33"
- integrity sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==
-
-caniuse-lite@^1.0.30001599:
- version "1.0.30001599"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz#571cf4f3f1506df9bf41fcbb6d10d5d017817bce"
- integrity sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==
+caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001688:
+ version "1.0.30001692"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz#4585729d95e6b95be5b439da6ab55250cd125bf9"
+ integrity sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==
chai-nightwatch@0.5.3:
version "0.5.3"
@@ -3176,18 +2636,18 @@ chai-nightwatch@0.5.3:
dependencies:
assertion-error "1.1.0"
-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==
+chai@4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8"
+ integrity sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==
dependencies:
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"
+ check-error "^1.0.3"
+ deep-eql "^4.1.3"
+ get-func-name "^2.0.2"
+ loupe "^2.3.6"
pathval "^1.1.1"
- type-detect "^4.0.5"
+ type-detect "^4.1.0"
chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1:
version "1.1.3"
@@ -3199,7 +2659,7 @@ chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
-chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.2:
+chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
dependencies:
@@ -3223,11 +2683,18 @@ chalk@^4.1.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
-check-error@1.0.2, 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==
+check-error@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694"
+ integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==
+ dependencies:
+ get-func-name "^2.0.2"
+
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"
@@ -3395,7 +2862,12 @@ combined-stream@^1.0.8:
dependencies:
delayed-stream "~1.0.0"
-commander@^2.19.0, commander@^2.20.0:
+commander@^10.0.0:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
+ integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
+
+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==
@@ -3424,11 +2896,6 @@ compare-versions@^5.0.1:
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-5.0.3.tgz#a9b34fea217472650ef4a2651d905f42c28ebfd7"
integrity sha512-4UZlZP8Z99MGEY+Ovg/uJxJuvoXuN4M6B3hKaiackiHrgzQFEe3diJi1mf1PNHbFujM7FvLrK2bpgIaImbtZ1A==
-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==
-
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -3467,6 +2934,11 @@ content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
+content-type@~1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
+ integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
+
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"
@@ -3474,14 +2946,19 @@ convert-source-map@^1.7.0:
dependencies:
safe-buffer "~5.1.1"
+convert-source-map@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
+ integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
+
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
-cookie@0.5.0:
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
- integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
+cookie@0.7.1:
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9"
+ integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==
cookie@~0.4.1:
version "0.4.2"
@@ -3500,12 +2977,12 @@ copy-webpack-plugin@11.0.0:
schema-utils "^4.0.0"
serialize-javascript "^6.0.0"
-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==
+core-js-compat@^3.38.0, core-js-compat@^3.38.1:
+ version "3.40.0"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.40.0.tgz#7485912a5a4a4315c2fdb2cbdc623e6881c88b38"
+ integrity sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==
dependencies:
- browserslist "^4.21.4"
+ browserslist "^4.24.3"
core-util-is@~1.0.0:
version "1.0.2"
@@ -3541,12 +3018,21 @@ cosmiconfig@^7.1.0:
path-type "^4.0.0"
yaml "^1.10.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==
+cropperjs@1.6.2:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.6.2.tgz#d1a5d627d880581cca41b7901f06923500e4201b"
+ integrity sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==
+
+cross-spawn@7.0.6, cross-spawn@^7.0.0:
+ version "7.0.6"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
+ integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
-cross-spawn@7.0.3, cross-spawn@^7.0.2:
+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==
@@ -3565,16 +3051,16 @@ css-functions-list@^3.1.0:
resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.1.0.tgz#cf5b09f835ad91a00e5959bcfc627cd498e1321b"
integrity sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==
-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==
+css-loader@6.11.0:
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.11.0.tgz#33bae3bf6363d0a7c2cf9031c96c744ff54d85ba"
+ integrity sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==
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-extract-imports "^3.1.0"
+ postcss-modules-local-by-default "^4.0.5"
+ postcss-modules-scope "^3.2.0"
postcss-modules-values "^4.0.0"
postcss-value-parser "^4.2.0"
semver "^7.5.4"
@@ -3693,10 +3179,10 @@ cssstyle@^2.3.0:
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==
+csstype@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
+ integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
custom-event-polyfill@1.0.7:
version "1.0.7"
@@ -3721,11 +3207,6 @@ date-format@^4.0.6:
resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.6.tgz#f6138b8f17968df9815b3d101fc06b0523f066c5"
integrity sha512-B9vvg5rHuQ8cbUXE/RMWMyX2YA5TecT3jKF5fLtGNlzPlU7zblSPmAm2OImDbWL+LDOQ6pUm+4LOFz+ywS41Zw==
-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.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -3778,6 +3259,13 @@ debug@^4.1.0, debug@^4.1.1:
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"
@@ -3807,10 +3295,10 @@ deep-eql@4.0.1:
dependencies:
type-detect "^4.0.0"
-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==
+deep-eql@^4.1.3:
+ version "4.1.4"
+ resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.4.tgz#d0d3912865911bb8fac5afb4e3acfa6a28dc72b7"
+ integrity sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==
dependencies:
type-detect "^4.0.0"
@@ -4004,15 +3492,29 @@ dotenv@10.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
-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==
+dunder-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
+ integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
dependencies:
- commander "^2.19.0"
- lru-cache "^4.1.5"
- semver "^5.6.0"
- sigmund "^1.0.1"
+ call-bind-apply-helpers "^1.0.1"
+ es-errors "^1.3.0"
+ gopd "^1.2.0"
+
+eastasianwidth@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
+ integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
+
+editorconfig@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-1.0.4.tgz#040c9a8e9a6c5288388b87c2db07028aa89f53a3"
+ integrity sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==
+ dependencies:
+ "@one-ini/wasm" "0.1.1"
+ commander "^10.0.0"
+ minimatch "9.0.1"
+ semver "^7.5.3"
ee-first@1.1.1:
version "1.1.1"
@@ -4035,21 +3537,21 @@ electron-to-chromium@^1.4.202:
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==
+electron-to-chromium@^1.5.73:
+ version "1.5.80"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.80.tgz#ca7a8361d7305f0ec9e203ce4e633cbb8a8ef1b1"
+ integrity sha512-LTrKpW0AqIuHwmlVNV+cjFYTnXtM9K37OGhpe0ZI10ScPSxqVSryZHIY3WnCS5NSYbBODRTZyhRMS2h5FAEqAw==
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+emoji-regex@^9.2.2:
+ version "9.2.2"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
+ integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
+
emojis-list@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
@@ -4059,32 +3561,30 @@ emojis-list@^3.0.0:
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
-encode-utf8@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda"
- integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==
-
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+encodeurl@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
+ integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
+
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"
@@ -4094,8 +3594,8 @@ 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@^5.10.0:
version "5.10.0"
@@ -4206,11 +3706,28 @@ es-abstract@^1.20.4:
unbox-primitive "^1.0.2"
which-typed-array "^1.1.9"
+es-define-property@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
+ integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
+
+es-errors@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
+ integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
+
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-object-atoms@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941"
+ integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==
+ dependencies:
+ es-errors "^1.3.0"
+
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"
@@ -4241,6 +3758,11 @@ escalade@^3.1.1:
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+escalade@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
+ integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
+
escape-html@1.0.3, escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
@@ -4376,6 +3898,14 @@ eslint-scope@^7.1.1:
esrecurse "^4.3.0"
estraverse "^5.2.0"
+eslint-scope@^7.2.2:
+ version "7.2.2"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f"
+ integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^5.2.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"
@@ -4405,6 +3935,11 @@ eslint-visitor-keys@^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-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3:
+ version "3.4.3"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
+ integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
+
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"
@@ -4416,49 +3951,48 @@ eslint-webpack-plugin@3.2.0:
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==
+eslint@8.57.1:
+ version "8.57.1"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9"
+ integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==
dependencies:
- "@eslint/eslintrc" "^1.4.1"
- "@humanwhocodes/config-array" "^0.11.8"
+ "@eslint-community/eslint-utils" "^4.2.0"
+ "@eslint-community/regexpp" "^4.6.1"
+ "@eslint/eslintrc" "^2.1.4"
+ "@eslint/js" "8.57.1"
+ "@humanwhocodes/config-array" "^0.13.0"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"
- ajv "^6.10.0"
+ "@ungap/structured-clone" "^1.2.0"
+ ajv "^6.12.4"
chalk "^4.0.0"
cross-spawn "^7.0.2"
debug "^4.3.2"
doctrine "^3.0.0"
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"
+ eslint-scope "^7.2.2"
+ eslint-visitor-keys "^3.4.3"
+ espree "^9.6.1"
+ esquery "^1.4.2"
esutils "^2.0.2"
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"
+ graphemer "^1.4.0"
ignore "^5.2.0"
- import-fresh "^3.0.0"
imurmurhash "^0.1.4"
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.4.1"
lodash.merge "^4.6.2"
minimatch "^3.1.2"
natural-compare "^1.4.0"
- optionator "^0.9.1"
- regexpp "^3.2.0"
+ optionator "^0.9.3"
strip-ansi "^6.0.1"
- strip-json-comments "^3.1.0"
text-table "^0.2.0"
espree@^6.0.0:
@@ -4479,14 +4013,14 @@ espree@^9.3.1:
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.3.0"
-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==
+espree@^9.6.0, espree@^9.6.1:
+ version "9.6.1"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
+ integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
dependencies:
- acorn "^8.8.0"
+ acorn "^8.9.0"
acorn-jsx "^5.3.2"
- eslint-visitor-keys "^3.3.0"
+ eslint-visitor-keys "^3.4.1"
esprima@^4.0.1:
version "4.0.1"
@@ -4500,6 +4034,13 @@ esquery@^1.4.0:
dependencies:
estraverse "^5.1.0"
+esquery@^1.4.2:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7"
+ integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
+ dependencies:
+ estraverse "^5.1.0"
+
esrecurse@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
@@ -4544,37 +4085,37 @@ eventsource-polyfill@0.9.6:
resolved "https://registry.yarnpkg.com/eventsource-polyfill/-/eventsource-polyfill-0.9.6.tgz#10e0d187f111b167f28fdab918843ce7d818f13c"
integrity sha1-EODRh/ERsWfyj9q5GIQ859gY8Tw=
-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==
+express@4.21.2:
+ version "4.21.2"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32"
+ integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
- body-parser "1.20.1"
+ body-parser "1.20.3"
content-disposition "0.5.4"
content-type "~1.0.4"
- cookie "0.5.0"
+ cookie "0.7.1"
cookie-signature "1.0.6"
debug "2.6.9"
depd "2.0.0"
- encodeurl "~1.0.2"
+ encodeurl "~2.0.0"
escape-html "~1.0.3"
etag "~1.8.1"
- finalhandler "1.2.0"
+ finalhandler "1.3.1"
fresh "0.5.2"
http-errors "2.0.0"
- merge-descriptors "1.0.1"
+ merge-descriptors "1.0.3"
methods "~1.1.2"
on-finished "2.4.1"
parseurl "~1.3.3"
- path-to-regexp "0.1.7"
+ path-to-regexp "0.1.12"
proxy-addr "~2.0.7"
- qs "6.11.0"
+ qs "6.13.0"
range-parser "~1.2.1"
safe-buffer "5.2.1"
- send "0.18.0"
- serve-static "1.15.0"
+ send "0.19.0"
+ serve-static "1.16.2"
setprototypeof "1.2.0"
statuses "2.0.1"
type-is "~1.6.18"
@@ -4686,13 +4227,13 @@ finalhandler@1.1.2:
statuses "~1.5.0"
unpipe "~1.0.0"
-finalhandler@1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32"
- integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==
+finalhandler@1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019"
+ integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==
dependencies:
debug "2.6.9"
- encodeurl "~1.0.2"
+ encodeurl "~2.0.0"
escape-html "~1.0.3"
on-finished "2.4.1"
parseurl "~1.3.3"
@@ -4790,6 +4331,14 @@ for-each@^0.3.3:
dependencies:
is-callable "^1.1.3"
+foreground-child@^3.1.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77"
+ integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==
+ dependencies:
+ cross-spawn "^7.0.0"
+ signal-exit "^4.0.1"
+
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
@@ -4840,7 +4389,12 @@ fsevents@~2.3.2:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
-function-bind@1.1.1, function-bind@^1.1.1:
+function-bind@1.1.2, function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
+function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@@ -4874,6 +4428,11 @@ get-func-name@^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-func-name@^2.0.1, get-func-name@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41"
+ integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==
+
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"
@@ -4892,6 +4451,30 @@ get-intrinsic@^1.1.3:
has "^1.0.3"
has-symbols "^1.0.3"
+get-intrinsic@^1.2.5, get-intrinsic@^1.2.6:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.7.tgz#dcfcb33d3272e15f445d15124bc0a216189b9044"
+ integrity sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==
+ dependencies:
+ call-bind-apply-helpers "^1.0.1"
+ es-define-property "^1.0.1"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.0.0"
+ function-bind "^1.1.2"
+ get-proto "^1.0.0"
+ gopd "^1.2.0"
+ has-symbols "^1.1.0"
+ hasown "^2.0.2"
+ math-intrinsics "^1.1.0"
+
+get-proto@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
+ integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
+ dependencies:
+ dunder-proto "^1.0.1"
+ es-object-atoms "^1.0.0"
+
get-stream@^5.1.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
@@ -4938,6 +4521,18 @@ glob@7.2.0, glob@^7.1.7:
once "^1.3.0"
path-is-absolute "^1.0.0"
+glob@^10.3.3:
+ version "10.4.5"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956"
+ integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==
+ dependencies:
+ foreground-child "^3.1.0"
+ jackspeak "^3.1.2"
+ minimatch "^9.0.4"
+ minipass "^7.1.2"
+ package-json-from-dist "^1.0.0"
+ path-scurry "^1.11.1"
+
glob@^7.0.0, glob@^7.1.1, glob@^7.1.3:
version "7.1.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
@@ -4961,17 +4556,6 @@ glob@^7.2.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
-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 "^5.0.1"
- once "^1.3.0"
-
global-modules@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
@@ -5041,6 +4625,11 @@ gopd@^1.0.1:
dependencies:
get-intrinsic "^1.1.3"
+gopd@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
+ integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
+
graceful-fs@^4.1.2:
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
@@ -5060,10 +4649,10 @@ graceful-fs@^4.2.9:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
-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==
+graphemer@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
+ integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
growl@1.10.5:
version "1.10.5"
@@ -5121,6 +4710,11 @@ has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3:
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+has-symbols@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
+ integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
+
has-tostringtag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
@@ -5143,6 +4737,13 @@ 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==
+hasown@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
+ integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
+ dependencies:
+ function-bind "^1.1.2"
+
he@1.2.0, he@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@@ -5261,10 +4862,10 @@ http-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
-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==
+http-proxy-middleware@2.0.7:
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6"
+ integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==
dependencies:
"@types/http-proxy" "^1.17.8"
http-proxy "^1.18.1"
@@ -5339,13 +4940,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-fresh@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.0.0.tgz#a3d897f420cab0e671236897f75bc14b4885c390"
- dependencies:
- parent-module "^1.0.0"
- resolve-from "^4.0.0"
-
import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@@ -5741,6 +5335,15 @@ istanbul-reports@^3.0.5:
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"
+jackspeak@^3.1.2:
+ version "3.4.3"
+ resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a"
+ integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==
+ dependencies:
+ "@isaacs/cliui" "^8.0.2"
+ optionalDependencies:
+ "@pkgjs/parseargs" "^0.11.0"
+
jake@^10.8.5:
version "10.8.5"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
@@ -5791,26 +5394,22 @@ jest-worker@^29.1.2:
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==
+js-beautify@^1.14.9:
+ version "1.15.1"
+ resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.15.1.tgz#4695afb508c324e1084ee0b952a102023fc65b64"
+ integrity sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==
dependencies:
config-chain "^1.1.13"
- editorconfig "^0.15.3"
- glob "^8.0.3"
- nopt "^6.0.0"
+ editorconfig "^1.0.4"
+ glob "^10.3.3"
+ js-cookie "^3.0.5"
+ nopt "^7.2.0"
-js-cookie@3.0.5:
+js-cookie@3.0.5, 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"
@@ -5865,10 +5464,20 @@ jsesc@^2.5.1:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
+jsesc@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
+ integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
+
jsesc@~0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
+jsesc@~3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e"
+ integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==
+
json-loader@0.5.7:
version "0.5.7"
resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d"
@@ -5914,7 +5523,7 @@ 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:
+json5@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
@@ -5954,10 +5563,10 @@ just-extend@^4.0.2:
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==
+karma-coverage@2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/karma-coverage/-/karma-coverage-2.2.1.tgz#e1cc074f93ace9dc4fb7e7aeca7135879c2e358c"
+ integrity sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==
dependencies:
istanbul-lib-coverage "^3.2.0"
istanbul-lib-instrument "^5.1.0"
@@ -5966,13 +5575,13 @@ karma-coverage@2.2.0:
istanbul-reports "^3.0.5"
minimatch "^3.0.4"
-karma-firefox-launcher@2.1.2:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/karma-firefox-launcher/-/karma-firefox-launcher-2.1.2.tgz#9a38cc783c579a50f3ed2a82b7386186385cfc2d"
- integrity sha512-VV9xDQU1QIboTrjtGVD4NCfzIH7n01ZXqy/qpBhnOeGVOkG5JYPEm8kuSd7psHE6WouZaQ9Ool92g8LFweSNMA==
+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.2.0"
- which "^2.0.1"
+ which "^3.0.0"
karma-mocha-reporter@2.2.5:
version "2.2.5"
@@ -6009,19 +5618,19 @@ karma-spec-reporter@0.0.36:
dependencies:
colors "1.4.0"
-karma-webpack@5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-5.0.1.tgz#4eafd31bbe684a747a6e8f3e4ad373e53979ced4"
- integrity sha512-oo38O+P3W2mSPCSUrQdySSPv1LvPpXP+f+bBimNomS5sW+1V4SuhCuW8TfJzV+rDv921w2fDSDw0xJbPe6U+kQ==
+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:
glob "^7.1.3"
- minimatch "^9.0.3"
+ minimatch "^3.0.4"
webpack-merge "^4.1.5"
-karma@6.4.2:
- version "6.4.2"
- resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.2.tgz#a983f874cee6f35990c4b2dcc3d274653714de8e"
- integrity sha512-C6SU/53LB31BEgRg+omznBEMY4SjHU3ricV6zBcAe1EeILKkeScr+fZXtaI5WyDbkVowJxxAI6h73NcFPmXolQ==
+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"
@@ -6042,7 +5651,7 @@ karma@6.4.2:
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"
@@ -6434,13 +6043,20 @@ log4js@^6.4.1:
rfdc "^1.3.0"
streamroller "^3.0.6"
-loupe@2.3.4, loupe@^2.3.1:
+loupe@2.3.4:
version "2.3.4"
resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3"
integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==
dependencies:
get-func-name "^2.0.0"
+loupe@^2.3.6:
+ version "2.3.7"
+ resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697"
+ integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==
+ dependencies:
+ get-func-name "^2.0.1"
+
lower-case@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
@@ -6448,13 +6064,10 @@ lower-case@^2.0.2:
dependencies:
tslib "^2.0.3"
-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"
+lru-cache@^10.2.0:
+ version "10.4.3"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
+ integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
lru-cache@^5.1.1:
version "5.1.1"
@@ -6470,19 +6083,12 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
-magic-string@^0.25.7:
- version "0.25.9"
- resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
- integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
- 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==
+magic-string@^0.30.11:
+ version "0.30.17"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453"
+ integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==
dependencies:
- "@jridgewell/sourcemap-codec" "^1.4.15"
+ "@jridgewell/sourcemap-codec" "^1.5.0"
make-dir@^2.0.0, make-dir@^2.1.0:
version "2.1.0"
@@ -6507,6 +6113,11 @@ 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==
+math-intrinsics@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
+ integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
+
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"
@@ -6546,9 +6157,10 @@ meow@^9.0.0:
type-fest "^0.18.0"
yargs-parser "^20.2.3"
-merge-descriptors@1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+merge-descriptors@1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
+ integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
merge-stream@^2.0.0:
version "2.0.0"
@@ -6654,6 +6266,13 @@ minimatch@5.0.1:
dependencies:
brace-expansion "^2.0.1"
+minimatch@9.0.1:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253"
+ integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==
+ dependencies:
+ brace-expansion "^2.0.1"
+
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@@ -6667,10 +6286,10 @@ minimatch@^5.0.1:
dependencies:
brace-expansion "^2.0.1"
-minimatch@^9.0.3:
- version "9.0.3"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825"
- integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==
+minimatch@^9.0.4:
+ version "9.0.5"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
+ integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
dependencies:
brace-expansion "^2.0.1"
@@ -6701,6 +6320,11 @@ minimist@^1.2.5:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
+"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
+ integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
+
mitt@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mitt/-/mitt-2.1.0.tgz#f740577c23176c6205b121b2973514eade1b2230"
@@ -6794,7 +6418,7 @@ ms@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
-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==
@@ -6901,22 +6525,22 @@ no-case@^3.0.4:
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.19:
+ version "2.0.19"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314"
+ integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
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==
-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==
+nopt@^7.2.0:
+ version "7.2.1"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.1.tgz#1cac0eab9b8e97c9093338446eddd40b2c8ca1e7"
+ integrity sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==
dependencies:
- abbrev "^1.0.0"
+ abbrev "^2.0.0"
normalize-package-data@^2.5.0:
version "2.5.0"
@@ -6977,6 +6601,11 @@ object-inspect@^1.12.2:
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9"
integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==
+object-inspect@^1.13.3:
+ version "1.13.3"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a"
+ integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==
+
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"
@@ -7070,17 +6699,17 @@ optionator@^0.8.1:
type-check "~0.3.2"
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==
+optionator@^0.9.3:
+ version "0.9.4"
+ resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
+ integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
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"
+ word-wrap "^1.2.5"
ora@0.4.1:
version "0.4.1"
@@ -7165,6 +6794,16 @@ p-try@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1"
+package-json-from-dist@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505"
+ integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
+
+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==
+
pako@~1.0.2:
version "1.0.11"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
@@ -7251,9 +6890,18 @@ path-parse@^1.0.7:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
-path-to-regexp@0.1.7:
- version "0.1.7"
- resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
+path-scurry@^1.11.1:
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2"
+ integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==
+ dependencies:
+ lru-cache "^10.2.0"
+ minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+
+path-to-regexp@0.1.12:
+ version "0.1.12"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7"
+ integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==
path-to-regexp@^1.7.0:
version "1.7.0"
@@ -7275,16 +6923,21 @@ pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
-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==
+phoenix@1.7.18:
+ version "1.7.18"
+ resolved "https://registry.yarnpkg.com/phoenix/-/phoenix-1.7.18.tgz#13b58af5572f9e6ad40d4683b79bee5a0bb84d5b"
+ integrity sha512-Qo+V9+knfEd+R1pzCe+XJlj3GPSxWz4PNwzFl7GgssuTVYPoh/he3mbPQJ+NEDdqulxAbBtWCNYGPB3WplS5Mg==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+picocolors@^1.0.1, picocolors@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
+ integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
+
picomatch@^2.0.4:
version "2.2.3"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d"
@@ -7304,10 +6957,10 @@ pify@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
-pirates@^4.0.5:
- version "4.0.5"
- resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
- integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==
+pirates@^4.0.6:
+ version "4.0.6"
+ resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9"
+ integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==
pkg-dir@^3.0.0:
version "3.0.0"
@@ -7452,26 +7105,26 @@ postcss-minify-selectors@^5.2.1:
dependencies:
postcss-selector-parser "^6.0.5"
-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-extract-imports@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002"
+ integrity sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==
-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==
+postcss-modules-local-by-default@^4.0.5:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz#d150f43837831dae25e4085596e84f6f5d6ec368"
+ integrity sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==
dependencies:
icss-utils "^5.0.0"
- postcss-selector-parser "^6.0.2"
+ postcss-selector-parser "^7.0.0"
postcss-value-parser "^4.1.0"
-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==
+postcss-modules-scope@^3.2.0:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz#1bbccddcb398f1d7a511e0a2d1d047718af4078c"
+ integrity sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==
dependencies:
- postcss-selector-parser "^6.0.4"
+ postcss-selector-parser "^7.0.0"
postcss-modules-values@^4.0.0:
version "4.0.0"
@@ -7577,9 +7230,9 @@ postcss-safe-parser@^6.0.0:
integrity sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==
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==
+ version "4.0.9"
+ resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.9.tgz#a03c773cd4c9623cb04ce142a52afcec74806685"
+ integrity sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==
postcss-selector-parser@2.2.1:
version "2.2.1"
@@ -7598,7 +7251,7 @@ postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.6:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
-postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9:
+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==
@@ -7606,6 +7259,14 @@ postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector
cssesc "^3.0.0"
util-deprecate "^1.0.2"
+postcss-selector-parser@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz#41bd8b56f177c093ca49435f65731befe25d6b9c"
+ integrity sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
postcss-svgo@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d"
@@ -7640,15 +7301,6 @@ postcss@8.4.23:
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==
- dependencies:
- nanoid "^3.3.1"
- picocolors "^1.0.0"
- source-map-js "^1.0.2"
-
postcss@^8.4.0:
version "8.4.21"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4"
@@ -7685,6 +7337,15 @@ postcss@^8.4.33:
picocolors "^1.0.0"
source-map-js "^1.0.2"
+postcss@^8.4.48:
+ version "8.4.49"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19"
+ integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==
+ dependencies:
+ nanoid "^3.3.7"
+ picocolors "^1.1.1"
+ source-map-js "^1.2.1"
+
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -7729,11 +7390,6 @@ prr@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
-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==
-
psl@^1.1.33:
version "1.9.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
@@ -7746,15 +7402,15 @@ pump@^3.0.0:
end-of-stream "^1.1.0"
once "^1.3.1"
-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.js@2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7"
+ integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==
-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.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
+ integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==
punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
@@ -7765,38 +7421,39 @@ qjobs@^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.5.3:
- version "1.5.3"
- resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.3.tgz#03afa80912c0dccf12bc93f615a535aad1066170"
- integrity sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==
+qrcode@1.5.4:
+ version "1.5.4"
+ resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.4.tgz#5cb81d86eb57c675febb08cf007fff963405da88"
+ integrity sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==
dependencies:
dijkstrajs "^1.0.1"
- encode-utf8 "^1.0.3"
pngjs "^5.0.0"
yargs "^15.3.1"
-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==
+qs@6.13.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
+ integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
dependencies:
- side-channel "^1.0.4"
+ side-channel "^1.0.6"
qs@6.9.7:
version "6.9.7"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==
+qs@^6.12.3:
+ version "6.14.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930"
+ integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==
+ dependencies:
+ side-channel "^1.1.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:
- 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"
@@ -7829,10 +7486,10 @@ raw-body@2.4.3:
iconv-lite "0.4.24"
unpipe "1.0.0"
-raw-body@2.5.1:
- version "2.5.1"
- resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
- integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
+raw-body@2.5.2:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
+ integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
dependencies:
bytes "3.1.2"
http-errors "2.0.0"
@@ -7920,10 +7577,10 @@ regenerate-unicode-properties@^10.0.1:
dependencies:
regenerate "^1.4.2"
-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==
+regenerate-unicode-properties@^10.2.0:
+ version "10.2.0"
+ resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0"
+ integrity sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==
dependencies:
regenerate "^1.4.2"
@@ -7932,20 +7589,20 @@ regenerate@^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.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.1:
- version "0.15.1"
- resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56"
- integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==
+regenerator-runtime@^0.14.0:
+ version "0.14.1"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
+ integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
+
+regenerator-transform@^0.15.2:
+ version "0.15.2"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4"
+ integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==
dependencies:
"@babel/runtime" "^7.8.4"
@@ -7958,23 +7615,11 @@ regexp.prototype.flags@^1.4.3:
define-properties "^1.1.3"
functions-have-names "^1.2.2"
-regexpp@^3.0.0, regexpp@^3.2.0:
+regexpp@^3.0.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"
- resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.0.1.tgz#c531122a7840de743dcf9c83e923b5560323ced3"
- integrity sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==
- dependencies:
- regenerate "^1.4.2"
- regenerate-unicode-properties "^10.0.1"
- regjsgen "^0.6.0"
- regjsparser "^0.8.2"
- unicode-match-property-ecmascript "^2.0.0"
- unicode-match-property-value-ecmascript "^2.0.0"
-
regexpu-core@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.1.0.tgz#2f8504c3fd0ebe11215783a41541e21c79942c6d"
@@ -7987,15 +7632,15 @@ regexpu-core@^5.1.0:
unicode-match-property-ecmascript "^2.0.0"
unicode-match-property-value-ecmascript "^2.0.0"
-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==
+regexpu-core@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.2.0.tgz#0e5190d79e542bf294955dccabae04d3c7d53826"
+ integrity sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==
dependencies:
- "@babel/regjsgen" "^0.8.0"
regenerate "^1.4.2"
- regenerate-unicode-properties "^10.1.0"
- regjsparser "^0.9.1"
+ regenerate-unicode-properties "^10.2.0"
+ regjsgen "^0.8.0"
+ regjsparser "^0.12.0"
unicode-match-property-ecmascript "^2.0.0"
unicode-match-property-value-ecmascript "^2.1.0"
@@ -8004,6 +7649,18 @@ regjsgen@^0.6.0:
resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d"
integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==
+regjsgen@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.8.0.tgz#df23ff26e0c5b300a6470cad160a9d090c3a37ab"
+ integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==
+
+regjsparser@^0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.12.0.tgz#0e846df6c6530586429377de56e0475583b088dc"
+ integrity sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==
+ dependencies:
+ jsesc "~3.0.2"
+
regjsparser@^0.8.2:
version "0.8.4"
resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f"
@@ -8011,13 +7668,6 @@ regjsparser@^0.8.2:
dependencies:
jsesc "~0.5.0"
-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:
- jsesc "~0.5.0"
-
relateurl@^0.2.7:
version "0.2.7"
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
@@ -8195,9 +7845,10 @@ schema-utils@^4.0.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"
+selenium-server@3.141.59:
+ version "3.141.59"
+ resolved "https://registry.yarnpkg.com/selenium-server/-/selenium-server-3.141.59.tgz#cbefdf50aae636ee4c67b819532a8233ce3fd6b0"
+ integrity sha512-pL7T1YtAqOEXiBbTx0KdZMkE2U7PYucemd7i0nDLcxcR1APXYZlJfNr5hrvL3mZgwXb7AJEZPINzC6mDU3eP5g==
selenium-webdriver@4.6.1:
version "4.6.1"
@@ -8226,11 +7877,16 @@ semver@7.3.8, semver@^7.3.8:
dependencies:
lru-cache "^6.0.0"
-semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
+semver@^6.0.0, 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@^6.3.1:
+ version "6.3.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
+ integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
+
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"
@@ -8238,6 +7894,11 @@ semver@^7.0.0, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6:
dependencies:
lru-cache "^6.0.0"
+semver@^7.5.3:
+ version "7.6.3"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
+ integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
+
semver@^7.5.4:
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
@@ -8245,10 +7906,10 @@ semver@^7.5.4:
dependencies:
lru-cache "^6.0.0"
-send@0.18.0:
- version "0.18.0"
- resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
- integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==
+send@0.19.0:
+ version "0.19.0"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
+ integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
dependencies:
debug "2.6.9"
depd "2.0.0"
@@ -8271,15 +7932,15 @@ serialize-javascript@6.0.0, serialize-javascript@^6.0.0:
dependencies:
randombytes "^2.1.0"
-serve-static@1.15.0:
- version "1.15.0"
- resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
- integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
+serve-static@1.16.2:
+ version "1.16.2"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296"
+ integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==
dependencies:
- encodeurl "~1.0.2"
+ encodeurl "~2.0.0"
escape-html "~1.0.3"
parseurl "~1.3.3"
- send "0.18.0"
+ send "0.19.0"
serviceworker-webpack5-plugin@2.0.0:
version "2.0.0"
@@ -8329,6 +7990,35 @@ shelljs@0.8.5:
interpret "^1.0.0"
rechoir "^0.6.2"
+side-channel-list@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad"
+ integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==
+ dependencies:
+ es-errors "^1.3.0"
+ object-inspect "^1.13.3"
+
+side-channel-map@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42"
+ integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==
+ dependencies:
+ call-bound "^1.0.2"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.5"
+ object-inspect "^1.13.3"
+
+side-channel-weakmap@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea"
+ integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==
+ dependencies:
+ call-bound "^1.0.2"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.5"
+ object-inspect "^1.13.3"
+ side-channel-map "^1.0.1"
+
side-channel@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
@@ -8338,10 +8028,16 @@ side-channel@^1.0.4:
get-intrinsic "^1.0.2"
object-inspect "^1.9.0"
-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==
+side-channel@^1.0.6, side-channel@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9"
+ integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
+ dependencies:
+ es-errors "^1.3.0"
+ object-inspect "^1.13.3"
+ side-channel-list "^1.0.0"
+ side-channel-map "^1.0.1"
+ side-channel-weakmap "^1.0.2"
signal-exit@^3.0.2:
version "3.0.2"
@@ -8352,6 +8048,11 @@ signal-exit@^3.0.7:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
+signal-exit@^4.0.1:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
+ integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
+
sinon-chai@3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.7.0.tgz#cfb7dec1c50990ed18c153f1840721cf13139783"
@@ -8388,37 +8089,45 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
-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-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:
+ debug "~4.3.4"
+ ws "~8.17.1"
-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"
+ 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-js@^1.2.0, source-map-js@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
+ integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
+
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"
@@ -8439,11 +8148,6 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, sourc
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
-sourcemap-codec@^1.4.8:
- version "1.4.8"
- resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
- integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
-
spdx-correct@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4"
@@ -8497,6 +8201,15 @@ streamroller@^3.0.6:
debug "^4.3.4"
fs-extra "^10.0.1"
+"string-width-cjs@npm:string-width@^4.2.0":
+ 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==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
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"
@@ -8515,6 +8228,15 @@ string-width@^4.2.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"
+string-width@^5.0.1, string-width@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
+ integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
+ dependencies:
+ eastasianwidth "^0.2.0"
+ emoji-regex "^9.2.2"
+ strip-ansi "^7.0.1"
+
string.prototype.trimend@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80"
@@ -8562,6 +8284,13 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
+"strip-ansi-cjs@npm: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@5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
@@ -8595,6 +8324,13 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"
+strip-ansi@^7.0.1:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
+ integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
+ dependencies:
+ ansi-regex "^6.0.1"
+
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
@@ -8606,7 +8342,7 @@ strip-indent@^3.0.0:
dependencies:
min-indent "^1.0.0"
-strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
+strip-json-comments@3.1.1, 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==
@@ -8922,10 +8658,15 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
-type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8:
+type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
+type-detect@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c"
+ integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==
+
type-fest@^0.18.0:
version "0.18.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f"
@@ -9044,14 +8785,6 @@ untildify@^4.0.0:
resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
-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"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz#dbfc5a789caa26b1db8990796c2c8ebbce304824"
@@ -9068,13 +8801,13 @@ update-browserslist-db@^1.0.5:
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==
+update-browserslist-db@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz#97e9c96ab0ae7bcac08e9ae5151d26e6bc6b5580"
+ integrity sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==
dependencies:
- escalade "^3.1.1"
- picocolors "^1.0.0"
+ escalade "^3.2.0"
+ picocolors "^1.1.1"
uri-js@^4.2.2:
version "4.2.2"
@@ -9090,13 +8823,13 @@ url-parse@^1.5.3:
querystringify "^2.1.1"
requires-port "^1.0.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==
+url@0.11.4:
+ version "0.11.4"
+ resolved "https://registry.yarnpkg.com/url/-/url-0.11.4.tgz#adca77b3562d56b72746e76b330b7f27b6721f3c"
+ integrity sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==
dependencies:
- punycode "1.3.2"
- querystring "0.2.0"
+ punycode "^1.4.1"
+ qs "^6.12.3"
utf8@3.0.0:
version "3.0.0"
@@ -9144,6 +8877,11 @@ void-elements@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
+vue-component-type-helpers@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/vue-component-type-helpers/-/vue-component-type-helpers-2.2.0.tgz#de5fa802b6beae7125595ec0d3d5195a22691623"
+ integrity sha512-cYrAnv2me7bPDcg9kIcGwjJiSB6Qyi08+jLDo9yuvoFQjzHiPTzML7RnkJB1+3P6KMsX/KbCD4QE3Tv/knEllw==
+
vue-demi@^0.13.11:
version "0.13.11"
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99"
@@ -9162,15 +8900,14 @@ vue-eslint-parser@^9.0.1:
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==
+vue-i18n@10:
+ version "10.0.5"
+ resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-10.0.5.tgz#fdf4e6c7b669e80cfa3a12ed9625e2b46671cdf0"
+ integrity sha512-9/gmDlCblz3i8ypu/afiIc/SUIfTTE1mr0mZhb9pk70xo2csHAM9mp2gdQ3KD2O0AM3Hz/5ypb+FycTj/lHlPQ==
dependencies:
- "@intlify/core-base" "9.2.2"
- "@intlify/shared" "9.2.2"
- "@intlify/vue-devtools" "9.2.2"
- "@vue/devtools-api" "^6.2.1"
+ "@intlify/core-base" "10.0.5"
+ "@intlify/shared" "10.0.5"
+ "@vue/devtools-api" "^6.5.0"
vue-loader@17.0.1:
version "17.0.1"
@@ -9191,12 +8928,12 @@ vue-resize@^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==
+vue-router@4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.5.0.tgz#58fc5fe374e10b6018f910328f756c3dae081f14"
+ integrity sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==
dependencies:
- "@vue/devtools-api" "^6.4.5"
+ "@vue/devtools-api" "^6.6.4"
vue-style-loader@4.1.3:
version "4.1.3"
@@ -9206,14 +8943,6 @@ vue-style-loader@4.1.3:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
-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.2.0"
-
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"
@@ -9223,16 +8952,16 @@ vue-virtual-scroller@^2.0.0-beta.7:
vue-observe-visibility "^2.0.0-alpha.1"
vue-resize "^2.0.0-alpha.1"
-vue@3.2.45:
- version "3.2.45"
- resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.45.tgz#94a116784447eb7dbd892167784619fef379b3c8"
- integrity sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==
+vue@3.5.13:
+ version "3.5.13"
+ resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.13.tgz#9f760a1a982b09c0c04a867903fc339c9f29ec0a"
+ integrity sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==
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"
+ "@vue/compiler-dom" "3.5.13"
+ "@vue/compiler-sfc" "3.5.13"
+ "@vue/runtime-dom" "3.5.13"
+ "@vue/server-renderer" "3.5.13"
+ "@vue/shared" "3.5.13"
vuex@4.1.0:
version "4.1.0"
@@ -9424,6 +9153,13 @@ which@^1.3.1:
dependencies:
isexe "^2.0.0"
+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:
+ isexe "^2.0.0"
+
widest-line@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"
@@ -9431,7 +9167,12 @@ widest-line@^3.1.0:
dependencies:
string-width "^4.0.0"
-word-wrap@^1.2.3, word-wrap@~1.2.3:
+word-wrap@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
+ integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
+
+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==
@@ -9446,6 +9187,15 @@ workerpool@6.2.1:
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrap-ansi@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
@@ -9464,6 +9214,15 @@ wrap-ansi@^7.0.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
+wrap-ansi@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
+ integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
+ dependencies:
+ ansi-styles "^6.1.0"
+ string-width "^5.0.1"
+ strip-ansi "^7.0.1"
+
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -9486,10 +9245,10 @@ ws@^8.2.3:
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==
xml-name-validator@^4.0.0:
version "4.0.0"
@@ -9514,11 +9273,6 @@ y18n@^5.0.5:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
-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.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"