diff --git a/.babelrc b/.babelrc index bc2b0e31..3c732dd1 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,5 @@ { - "presets": ["es2015", "stage-2", "env"], - "plugins": ["transform-runtime", "lodash", "transform-vue-jsx"], + "presets": ["@babel/preset-env"], + "plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-transform-vue-jsx"], "comments": false } diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9d42288e..7f6d3c92 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,7 +24,7 @@ test: - apt install firefox-esr -y --no-install-recommends - firefox --version - yarn - - npm run unit + - yarn unit build: stage: build diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index ca2ebcfa..00000000 --- a/CHANGELOG +++ /dev/null @@ -1,35 +0,0 @@ -## 2017-02-20 - -- Overall CSS styling fixes -- Current theme is displayed in theme selector -- Theme selector is moved to the settings page -- Oembed attachments will now display correctly -- Styling changes to the user info cards -- Notification count in title -- Better Notification handling (persistance, mark as read) -- Post statuses with ctrl+enter -- Links in statuses open in a new tab -- Optimized mobile view -- Fix crash on persistance failure -- Compress persisted state -- Sync mutes with backend (SEE NOTE BELOW) - -Pleroma will now try to get the current mutes from the backend. Sadly, a bug in -Qvitter will not allow getting the mutes from the endpoint, because it will -ignore HTTP Basic authentication. Mutes will still persist in Pleroma through -localstorage, but the mutes from Qvitter won't be picked up if the call fails. - -The patch for Qvitter: - ---- a/actions/apiqvittermutes.php -+++ b/actions/apiqvittermutes.php -@@ -74,7 +74,7 @@ class ApiQvitterMutesAction extends ApiPrivateAuthAction - { - parent::handle(); - -- $this->target = Profile::current(); -+ $this->target = $this->scoped; - - if(!$this->target instanceof Profile) { - $this->clientError(_('You have to be logged in to view your mutes.'), 403); - diff --git a/CHANGELOG.md b/CHANGELOG.md index 2719edcf..ad03c760 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,94 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - ## [Unreleased] +### Changed +- Removed the use of with_move parameters when fetching notifications + +### Fixed +- Weird bug related to post being sent seemingly after pasting with keyboard (hopefully) +- Multiple issues with muted statuses/notifications + +## [Unreleased patch] +### Add +- Added private notifications option for push notifications +- 'Copy link' button for statuses (in the ellipsis menu) +- Autocomplete domains from list of known instances + +### Changed +- Registration page no longer requires email if the server is configured not to require it +- Change heart to thumbs up in reaction picker +- Close the media modal on navigation events +- Add colons to the emoji alt text, to make them copyable +- Add better visual indication for drag-and-drop for files + +### Fixed +- Status ellipsis menu closes properly when selecting certain options +- Cropped images look correct in Chrome +- Newlines in the muted words settings work again +- Clicking on non-latin hashtags won't open a new window +- Uploading and drag-dropping multiple files works correctly now. + +## [2.0.3] - 2020-05-02 +### Fixed +- Show more/less works correctly with auto-collapsed subjects and long posts +- RTL characters won't look messed up in notifications + +### Changed +- Emoji autocomplete will match any part of the word and not just start, for example :drool will now helpfully suggest :blobcatdrool: and :blobcatdroolreach: + +### Add +- Follow request notification support + +## [2.0.2] - 2020-04-08 +### Fixed +- Favorite/Repeat avatars not showing up on private instances/non-public posts +- Autocorrect getting triggered in the captcha field +- Overflow on long domains in follow/move notifications + +### Changed +- Polish translation updated + +## [2.0.0] - 2020-02-28 +### Added +- Tons of color slots including ones for hover/pressed/toggled buttons +- Experimental `--variable[,mod]` syntax support for color slots in themes. the `mod` makes color brighter/darker depending on background color (makes darker color brighter/darker depending on background color) +- Paper theme by Shpuld +- Icons in nav panel +- Private mode support +- Support for 'Move' type notifications +- Pleroma AMOLED dark theme +- User level domain mutes, under User Settings -> Mutes +- Emoji reactions for statuses +- MRF keyword policy disclosure +### Changed +- Updated Pleroma default themes +- theme engine update to 3 (themes v2.1 introduction) +- massive internal changes in theme engine - slowly away from "generate things separately with spaghetti code" towards "feed all data into single 'generateTheme' function and declare slot inheritance and all in a separate file" +- Breezy theme updates to make it closer to actual Breeze in some aspects +- when using `--variable` in shadows it no longer uses the actual CSS3 variable, instead it generates color from other slots +- theme doesn't get saved to local storage when opening FE anonymously +- Captcha now resets on failed registrations +- Notifications column now cleans itself up to optimize performance when tab is left open for a long time +- 403 messaging +### Fixed +- Fixed loader-spinner not disappearing when a status preview fails to load +- anon viewers won't get theme data saved to local storage, so admin changing default theme will have an effect for users coming back to instance. +- Single notifications left unread when hitting read on another device/tab +- Registration fixed +- Deactivation of remote accounts from frontend +- Fixed NSFW unhiding not working with videos when using one-click unhiding/displaying +- Improved performance of anything that uses popovers (most notably statuses) + +## [1.1.7 and earlier] - 2019-12-14 ### Added - Ability to hide/show repeats from user - User profile button clutter organized into a menu - Emoji picker - Started changelog anew - Ability to change user's email +- About page +- Added remote user redirect ### Changed - changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes ### Fixed diff --git a/README.md b/README.md index 889f0837..b66383ad 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# pleroma_fe +# Pleroma-FE -> A single column frontend for both Pleroma and GS servers. +> A single column frontend designed for Pleroma.  @@ -11,7 +11,6 @@ To translate Pleroma-FE, add your language to [src/i18n/messages.js](https://git # FOR ADMINS You don't need to build Pleroma-FE yourself. Those using the Pleroma backend will be able to use it out of the box. -For the GNU social backend, check out https://git.pleroma.social/pleroma/pleroma-fe/wikis/dual-boot-with-qvitter to see how to run Pleroma-FE and Qvitter at the same time. ## Build Setup diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js index f8968966..dfef37a6 100644 --- a/build/webpack.base.conf.js +++ b/build/webpack.base.conf.js @@ -3,6 +3,7 @@ var config = require('../config') var utils = require('./utils') var projectRoot = path.resolve(__dirname, '../') var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin') +var FontelloPlugin = require("fontello-webpack-plugin") var env = process.env.NODE_ENV // check env & config/index.js to decide weither to enable CSS Sourcemaps for the @@ -11,6 +12,8 @@ var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap) var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap) var useCssSourceMap = cssSourceMapDev || cssSourceMapProd +var now = Date.now() + module.exports = { entry: { app: './src/main.js' @@ -32,6 +35,7 @@ module.exports = { ], alias: { 'vue$': 'vue/dist/vue.runtime.common', + 'static': path.resolve(__dirname, '../static'), 'src': path.resolve(__dirname, '../src'), 'assets': path.resolve(__dirname, '../src/assets'), 'components': path.resolve(__dirname, '../src/components') @@ -90,6 +94,14 @@ module.exports = { new ServiceWorkerWebpackPlugin({ entry: path.join(__dirname, '..', 'src/sw.js'), filename: 'sw-pleroma.js' + }), + new FontelloPlugin({ + config: require('../static/fontello.json'), + name: 'fontello', + output: { + css: 'static/[name].' + now + '.css', // [hash] is not supported. Use the current timestamp instead for versioning. + font: 'static/font/[name].' + now + '.[ext]' + } }) ] } diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 35363537..14b0428f 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -19,32 +19,69 @@ There's currently no mechanism for user-settings synchronization across several ## Options -### `theme` -Default theme used for new users. De-facto instance-default, user can change theme. +### `alwaysShowSubjectInput` +`true` - will always show subject line input, `false` - only show when it's not empty (i.e. replying). To hide subject line input completely, set it to `false` and `subjectLineBehavior` to `"noop"` ### `background` Default image background. Be aware of using too big images as they may take longer to load. Currently image is fitted with `background-size: cover` which means "scaled and cropped", currently left-aligned. De-facto instance default, user can choose their own background, if they remove their own background, instance default will be used instead. +### `collapseMessageWithSubject` +Collapse post content when post has a subject line (content warning). Instance-default. + +### `disableChat` +hides the chat (TODO: even if it's enabled on backend) + +### `greentext` +Changes lines prefixed with the `>` character to have a green text color + +### `hideFilteredStatuses` +Removes filtered statuses from timelines. + +### `hideMutedPosts` +Removes muted statuses from timelines. + +### `hidePostStats` +Hide repeats/favorites counters for posts. + +### `hideSitename` +Hide instance name in header. + +### `hideUserStats` +Hide followers/friends counters for users. + +### `loginMethod` +`"password"` - show simple password field +`"token"` - show button to log in with external method (will redirect to login form, more details in BE documentation) + ### `logo`, `logoMask`, `logoMargin` Instance `logo`, could be any image, including svg. By default it assumes logo used will be monochrome-with-alpha one, this is done to be compatible with both light and dark themes, so that white logo designed with dark theme in mind won't be invisible over light theme, this is done via [CSS3 Masking](https://www.html5rocks.com/en/tutorials/masking/adobe/). Basically - it will take alpha channel of the image and fill non-transparent areas of it with solid color. If you really want colorful logo - it can be done by setting `logoMask` to `false`. `logoMargin` allows you to adjust vertical margins between logo boundary and navbar borders. The idea is that to have logo's image without any extra margins and instead adjust them to your need in layout. +### `minimalScopesMode` +Limit scope selection to *Direct*, *User default* and *Scope of post replying to*. This also makes it impossible to reply to a DM with a non-DM post from PleromaFE. + +### `nsfwCensorImage` +Use custom image for NSFW'd images + +### `postContentType` +Default post formatting option (markdown/bbcode/plaintext/etc...) + ### `redirectRootNoLogin`, `redirectRootLogin` These two settings should point to where FE should redirect visitor when they login/open up website root -### `chatDisabled` -hides the chat (TODO: even if it's enabled on backend) +### `scopeCopy` +Copy post scope (visibility) when replying to a post. Instance-default. + +### `sidebarRight` +Change alignment of sidebar and panels to the right. Defaults to `false`. + +### `showFeaturesPanel` +Show panel showcasing instance features/settings to logged-out visitors ### `showInstanceSpecificPanel` This allows you to include arbitrary HTML content in a panel below navigation menu. PleromaFE looks for an html page `instance/panel.html`, by default it's not provided in FE, but BE bundles some [default one](https://git.pleroma.social/pleroma/pleroma/blob/develop/priv/static/instance/panel.html). De-facto instance-defaults, since user can hide instance-specific panel. -### `collapseMessageWithSubject` -Collapse post content when post has a subject line (content warning). Instance-default. - -### `scopeCopy` -Copy post scope (visibility) when replying to a post. Instance-default. - ### `subjectLineBehavior` How to handle subject line (CW) when replying to a post. * `"email"` - like EMail - prepend `re: ` to subject line if it doesn't already start with it. @@ -52,36 +89,22 @@ How to handle subject line (CW) when replying to a post. * `"noop"` - do not copy Instance-default. -### `postContentType` -Default post formatting option (markdown/bbcode/plaintext/etc...) - -### `alwaysShowSubjectInput` -`true` - will always show subject line input, `false` - only show when it's not empty (i.e. replying). To hide subject line input completely, set it to `false` and `subjectLineBehavior` to `"noop"` - -### `hidePostStats` and `hideUserStats` -Hide counters for posts and users respectively, i.e. hiding repeats/favorites counts for posts, hiding followers/friends counts for users. This is just cosmetic and aimed to ease pressure and bias imposed by stat numbers of people and/or posts. (as an example: so that people care less about how many followers someone has since they can't see that info) - -### `loginMethod` -`"password"` - show simple password field -`"token"` - show button to log in with external method (will redirect to login form, more details in BE documentation) +### `theme` +Default theme used for new users. De-facto instance-default, user can change theme. ### `webPushNotifications` Enables [PushAPI](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) - based notifications for users. Instance-default. -### `noAttachmentLinks` -**TODO Currently doesn't seem to be doing anything code-wise**, but implication is to disable adding links for attachments, which looks nicer but breaks compatibility with old GNU/Social servers. -### `nsfwCensorImage` -Use custom image for NSFW'd images - -### `showFeaturesPanel` -Show panel showcasing instance features/settings to logged-out visitors ## Indirect configuration Some features are configured depending on how backend is configured. In general the approach is "if backend allows it there's no need to hide it, if backend doesn't allow it there's no need to show it. ### Chat -**TODO somewhat broken, see: chatDisabled** chat can be disabled by disabling it in backend +**TODO somewhat broken, see: disableChat** chat can be disabled by disabling it in backend + +### Private Mode +If the `private` instance setting is enabled in the backend, features that are not accessible without authentication, such as the timelines and search will be disabled for unauthenticated users. ### Rich text formatting in post formatting Rich text formatting options are displayed depending on how many formatting options are enabled on backend, if you don't want your users to use rich text at all you can only allow "text/plain" one, frontend then will only display post text format as a label instead of dropdown (just so that users know for example if you only allow Markdown, only BBCode or only Plain text) @@ -89,10 +112,3 @@ Rich text formatting options are displayed depending on how many formatting opti ### Who to follow This is a panel intended for users to find people to follow based on randomness or on post contents. Being potentially privacy unfriendly feature it needs to be enabled and configured in backend to be enabled. -### Safe DM message display - -Setting this will change the warning text that is displayed for direct messages. - -ATTENTION: If you actually want the behavior to change. You will need to set the appropriate option at the backend. See the backend documentation for information about that. - -DO NOT activate this without checking the backend configuration first! diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 076bfb1c..f417f33d 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -33,7 +33,7 @@ will become Note that you can only use emoji defined on your instance, you cannot "copy" someone else's emoji, and will have to ask your administrator to copy emoji from other instance to yours. Lastly, there's two convenience options for emoji: an emoji picker (smiley face to the right of "submit" button) and autocomplete suggestions - when you start typing :shortcode: it will automatically try to suggest you emoj and complete the shortcode for you if you select one. **Note** that if emoji doesn't show up in suggestions nor in emoji picker it means there's no such emoji on your instance, if shortcode doesn't match any defined emoji it will appear as text. * **Attachments** are fairly simple - you can attach any file to a post as long as the file is within maximum size limits. If you're uploading explicit material you can mark all of your attachments as sensitive (or add `#nsfw` tag) - it will hide the images and videos behind a warning so that it won't be displayed instantly. -* **Subject line** also known as **CW** (Content Warning) could be used as a header to the post and/or to warn others about contents of the post having something that might upset somebody or something among those lines. Several applications allow to hide post content leaving only subject line visible. As a side-effect using subject line will also mark your images as sensitive (see above). +* **Subject line** also known as **CW** (Content Warning) could be used as a header to the post and/or to warn others about contents of the post having something that might upset somebody or something among those lines. Several applications allow to hide post content leaving only subject line visible. Using a subject line will not mark your images as sensitive, you will have to do that explicitly (see above). * **Visiblity scope** controls who will be able to see your posts. There are four scopes available: 1. `Public`: This is the default, and some fediverse software like GNU Social only supports this. This means that your post is accessible by anyone and will be shown in the public timelines. diff --git a/index.html b/index.html index fd4e795e..1ff944d9 100644 --- a/index.html +++ b/index.html @@ -6,8 +6,6 @@ <title>Pleroma</title> <!--server-generated-meta--> <link rel="icon" type="image/png" href="/favicon.png"> - <link rel="stylesheet" href="/static/font/css/fontello.css"> - <link rel="stylesheet" href="/static/font/css/animation.css"> </head> <body class="hidden"> <noscript>To use Pleroma, please enable JavaScript.</noscript> diff --git a/package.json b/package.json index f039d412..c0665f6e 100644 --- a/package.json +++ b/package.json @@ -15,51 +15,46 @@ "lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs" }, "dependencies": { + "@babel/runtime": "^7.7.6", "@chenfengyuan/vue-qrcode": "^1.0.0", - "babel-plugin-add-module-exports": "^0.2.1", - "babel-plugin-lodash": "^3.2.11", "body-scroll-lock": "^2.6.4", "chromatism": "^3.0.0", "cropperjs": "^1.4.3", "diff": "^3.0.1", - "karma-mocha-reporter": "^2.2.1", + "escape-html": "^1.0.3", "localforage": "^1.5.0", - "object-path": "^0.11.3", "phoenix": "^1.3.0", "portal-vue": "^2.1.4", - "sanitize-html": "^1.13.0", "v-click-outside": "^2.1.1", - "v-tooltip": "^2.0.2", - "vue": "^2.5.13", + "vue": "^2.6.11", "vue-chat-scroll": "^1.2.1", "vue-i18n": "^7.3.2", "vue-router": "^3.0.1", - "vue-template-compiler": "^2.3.4", + "vue-template-compiler": "^2.6.11", "vuelidate": "^0.7.4", - "vuex": "^3.0.1", - "whatwg-fetch": "^2.0.3" + "vuex": "^3.0.1" }, "devDependencies": { - "@babel/polyfill": "^7.0.0", + "karma-mocha-reporter": "^2.2.1", + "@babel/core": "^7.7.5", + "@babel/plugin-transform-runtime": "^7.7.6", + "@babel/preset-env": "^7.7.6", + "@babel/register": "^7.7.4", + "@ungap/event-target": "^0.1.0", + "@vue/babel-helper-vue-jsx-merge-props": "^1.0.0", + "@vue/babel-plugin-transform-vue-jsx": "^1.1.2", "@vue/test-utils": "^1.0.0-beta.26", "autoprefixer": "^6.4.0", - "babel-core": "^6.0.0", "babel-eslint": "^7.0.0", - "babel-helper-vue-jsx-merge-props": "^2.0.3", - "babel-loader": "^7.0.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "babel-plugin-transform-runtime": "^6.0.0", - "babel-plugin-transform-vue-jsx": "3", - "babel-preset-env": "^1.7.0", - "babel-preset-es2015": "^6.0.0", - "babel-preset-stage-2": "^6.0.0", - "babel-register": "^6.0.0", + "babel-loader": "^8.0.6", + "babel-plugin-lodash": "^3.3.4", "chai": "^3.5.0", "chalk": "^1.1.3", "chromedriver": "^2.21.2", "connect-history-api-fallback": "^1.1.0", "cross-spawn": "^4.0.2", "css-loader": "^0.28.0", + "custom-event-polyfill": "^1.0.7", "eslint": "^5.16.0", "eslint-config-standard": "^12.0.0", "eslint-friendly-formatter": "^2.0.5", @@ -72,6 +67,7 @@ "eventsource-polyfill": "^0.9.6", "express": "^4.13.3", "file-loader": "^3.0.1", + "fontello-webpack-plugin": "https://github.com/w3geo/fontello-webpack-plugin.git#6149eac8f2672ab6da089e8802fbfcac98487186", "function-bind": "^1.0.2", "html-webpack-plugin": "^3.0.0", "http-proxy-middleware": "^0.17.2", diff --git a/src/App.js b/src/App.js index 04a40e30..040138c9 100644 --- a/src/App.js +++ b/src/App.js @@ -6,6 +6,7 @@ import InstanceSpecificPanel from './components/instance_specific_panel/instance import FeaturesPanel from './components/features_panel/features_panel.vue' import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue' import ChatPanel from './components/chat_panel/chat_panel.vue' +import SettingsModal from './components/settings_modal/settings_modal.vue' import MediaModal from './components/media_modal/media_modal.vue' import SideDrawer from './components/side_drawer/side_drawer.vue' import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue' @@ -29,6 +30,7 @@ export default { SideDrawer, MobilePostStatusButton, MobileNav, + SettingsModal, UserReportingModal, PostStatusModal }, @@ -45,7 +47,8 @@ export default { }), created () { // Load the locale from the storage - this.$i18n.locale = this.$store.getters.mergedConfig.interfaceLanguage + const val = this.$store.getters.mergedConfig.interfaceLanguage + this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) window.addEventListener('resize', this.updateMobileState) }, destroyed () { @@ -90,6 +93,7 @@ export default { }, sitename () { return this.$store.state.instance.name }, chat () { return this.$store.state.chat.channel.state === 'joined' }, + hideSitename () { return this.$store.state.instance.hideSitename }, suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled }, showInstanceSpecificPanel () { return this.$store.state.instance.showInstanceSpecificPanel && @@ -97,7 +101,13 @@ export default { this.$store.state.instance.instanceSpecificPanelContent }, showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }, - isMobileLayout () { return this.$store.state.interface.mobileLayout } + isMobileLayout () { return this.$store.state.interface.mobileLayout }, + privateMode () { return this.$store.state.instance.private }, + sidebarAlign () { + return { + 'order': this.$store.state.instance.sidebarRight ? 99 : 0 + } + } }, methods: { scrollToTop () { @@ -110,6 +120,9 @@ export default { onSearchBarToggled (hidden) { this.searchBarHidden = hidden }, + openSettingsModal () { + this.$store.dispatch('openSettingsModal') + }, updateMobileState () { const mobileLayout = windowWidth() <= 800 const changed = mobileLayout !== this.isMobileLayout diff --git a/src/App.scss b/src/App.scss index 310962b8..f2972eda 100644 --- a/src/App.scss +++ b/src/App.scss @@ -31,9 +31,12 @@ h4 { margin: auto; min-height: 100vh; max-width: 980px; - background-color: rgba(0,0,0,0.15); align-content: flex-start; } +.underlay { + background-color: rgba(0,0,0,0.15); + background-color: var(--underlay, rgba(0,0,0,0.15)); +} .text-center { text-align: center; @@ -75,7 +78,7 @@ button { border-radius: $fallback--btnRadius; border-radius: var(--btnRadius, $fallback--btnRadius); cursor: pointer; - box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; + box-shadow: $fallback--buttonShadow; box-shadow: var(--buttonShadow); font-size: 14px; font-family: sans-serif; @@ -98,18 +101,39 @@ button { &:active { box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset; box-shadow: var(--buttonPressedShadow); + color: $fallback--text; + color: var(--btnPressedText, $fallback--text); + background-color: $fallback--fg; + background-color: var(--btnPressed, $fallback--fg); + i { + color: $fallback--text; + color: var(--btnPressedText, $fallback--text); + } } &:disabled { cursor: not-allowed; - opacity: 0.5; + color: $fallback--text; + color: var(--btnDisabledText, $fallback--text); + background-color: $fallback--fg; + background-color: var(--btnDisabled, $fallback--fg); + i { + color: $fallback--text; + color: var(--btnDisabledText, $fallback--text); + } } - &.pressed { - color: $fallback--faint; - color: var(--faint, $fallback--faint); - background-color: $fallback--bg; - background-color: var(--bg, $fallback--bg) + &.toggled { + color: $fallback--text; + color: var(--btnToggledText, $fallback--text); + background-color: $fallback--fg; + background-color: var(--btnToggled, $fallback--fg); + box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset; + box-shadow: var(--buttonPressedShadow); + i { + color: $fallback--text; + color: var(--btnToggledText, $fallback--text); + } } &.danger { @@ -121,12 +145,15 @@ button { } } -label.select { - padding: 0; +input, textarea, .select, .input { -} + &.unstyled { + border-radius: 0; + background: none; + box-shadow: none; + height: unset; + } -input, textarea, .select { border: none; border-radius: $fallback--inputRadius; border-radius: var(--inputRadius, $fallback--inputRadius); @@ -140,13 +167,17 @@ input, textarea, .select { font-family: var(--inputFont, sans-serif); font-size: 14px; margin: 0; - padding: 8px .5em; box-sizing: border-box; display: inline-block; position: relative; height: 28px; line-height: 16px; hyphens: none; + padding: 8px .5em; + + &.select { + padding: 0; + } &:disabled, &[disabled=disabled] { cursor: not-allowed; @@ -160,7 +191,7 @@ input, textarea, .select { right: 5px; height: 100%; color: $fallback--text; - color: var(--text, $fallback--text); + color: var(--inputText, $fallback--text); line-height: 28px; z-index: 0; pointer-events: none; @@ -198,7 +229,7 @@ input, textarea, .select { &:checked + label::before { box-shadow: 0px 0px 2px black inset, 0px 0px 0px 4px $fallback--fg inset; box-shadow: var(--inputShadow), 0px 0px 0px 4px var(--fg, $fallback--fg) inset; - background-color: var(--link, $fallback--link); + background-color: var(--accent, $fallback--link); } &:disabled { &, @@ -235,7 +266,7 @@ input, textarea, .select { display: none; &:checked + label::before { color: $fallback--text; - color: var(--text, $fallback--text); + color: var(--inputText, $fallback--text); } &:disabled { &, @@ -353,6 +384,33 @@ i[class*=icon-] { height: 50px; box-sizing: border-box; + button { + &, i[class*=icon-] { + 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) + } + } + + .logo { display: flex; position: absolute; @@ -487,6 +545,10 @@ main-router { color: $fallback--faint; color: var(--panelFaint, $fallback--faint); } + .faint-link { + color: $fallback--faint; + color: var(--faintLink, $fallback--faint); + } .alert { white-space: nowrap; @@ -504,11 +566,35 @@ main-router { min-height: 0; box-sizing: border-box; margin: 0; - margin-left: .25em; + margin-left: .5em; min-width: 1px; align-self: stretch; } + button { + &, 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); + } + } + a { color: $fallback--link; color: var(--panelLink, $fallback--link) @@ -774,51 +860,6 @@ nav { } } -.setting-item { - border-bottom: 2px solid var(--fg, $fallback--fg); - margin: 1em 1em 1.4em; - padding-bottom: 1.4em; - - > div { - margin-bottom: .5em; - &:last-child { - margin-bottom: 0; - } - } - - &:last-child { - border-bottom: none; - padding-bottom: 0; - margin-bottom: 1em; - } - - select { - min-width: 10em; - } - - - textarea { - width: 100%; - max-width: 100%; - height: 100px; - } - - .unavailable, - .unavailable i { - color: var(--cRed, $fallback--cRed); - color: $fallback--cRed; - } - - .btn { - min-height: 28px; - min-width: 10em; - padding: 0 2em; - } - - .number-input { - max-width: 6em; - } -} .select-multiple { display: flex; .option-list { @@ -855,3 +896,31 @@ nav { .btn.btn-default { min-height: 28px; } + +.animate-spin { + animation: spin 2s infinite linear; + display: inline-block; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(359deg); + } +} + +.new-status-notification { + position:relative; + margin-top: -1px; + font-size: 1.1em; + border-width: 1px 0 0 0; + border-style: solid; + border-color: var(--border, $fallback--border); + padding: 10px; + z-index: 1; + background-color: $fallback--fg; + background-color: var(--panel, $fallback--fg); +} diff --git a/src/App.vue b/src/App.vue index dbe842ec..7b9ad3dc 100644 --- a/src/App.vue +++ b/src/App.vue @@ -31,6 +31,7 @@ </div> <div class="item"> <router-link + v-if="!hideSitename" class="site-name" :to="{ name: 'root' }" active-class="home" @@ -40,19 +41,21 @@ </div> <div class="item right"> <search-bar + v-if="currentUser || !privateMode" class="nav-icon mobile-hidden" @toggled="onSearchBarToggled" @click.stop.native /> - <router-link + <a + href="#" class="mobile-hidden" - :to="{ name: 'settings'}" + @click.stop="openSettingsModal" > <i class="button-icon icon-cog nav-icon" :title="$t('nav.preferences')" /> - </router-link> + </a> <a v-if="currentUser && currentUser.role === 'admin'" href="/pleroma/admin/#/login-pleroma" @@ -76,9 +79,12 @@ </nav> <div id="content" - class="container" + class="container underlay" > - <div class="sidebar-flexer mobile-hidden"> + <div + class="sidebar-flexer mobile-hidden" + :style="sidebarAlign" + > <div class="sidebar-bounds"> <div class="sidebar-scroller"> <div class="sidebar"> @@ -120,6 +126,7 @@ <MobilePostStatusButton /> <UserReportingModal /> <PostStatusModal /> + <SettingsModal /> <portal-target name="modal" /> </div> </template> diff --git a/src/_variables.scss b/src/_variables.scss index e18101f0..30dc3e42 100644 --- a/src/_variables.scss +++ b/src/_variables.scss @@ -27,3 +27,5 @@ $fallback--tooltipRadius: 5px; $fallback--avatarRadius: 4px; $fallback--avatarAltRadius: 10px; $fallback--attachmentRadius: 10px; + +$fallback--buttonShadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 80a55849..0db03547 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -5,6 +5,8 @@ import App from '../App.vue' import { windowWidth } from '../services/window_utils/window_utils' import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js' import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' +import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' +import { applyTheme } from '../services/style_setter/style_setter.js' const getStatusnetConfig = async ({ store }) => { try { @@ -106,8 +108,9 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { copyInstanceOption('subjectLineBehavior') copyInstanceOption('postContentType') copyInstanceOption('alwaysShowSubjectInput') - copyInstanceOption('noAttachmentLinks') copyInstanceOption('showFeaturesPanel') + copyInstanceOption('hideSitename') + copyInstanceOption('sidebarRight') return store.dispatch('setTheme', config['theme']) } @@ -184,6 +187,12 @@ const getAppSecret = async ({ store }) => { }) } +const resolveStaffAccounts = ({ store, accounts }) => { + const nicknames = accounts.map(uri => uri.split('/').pop()) + nicknames.map(nickname => store.dispatch('fetchUser', nickname)) + store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames }) +} + const getNodeInfo = async ({ store }) => { try { const res = await window.fetch('/nodeinfo/2.0.json') @@ -209,9 +218,34 @@ const getNodeInfo = async ({ store }) => { store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version }) store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' }) + const priv = metadata.private + store.dispatch('setInstanceOption', { name: 'private', value: priv }) + const frontendVersion = window.___pleromafe_commit_hash store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion }) - store.dispatch('setInstanceOption', { name: 'tagPolicyAvailable', value: metadata.federation.mrf_policies.includes('TagPolicy') }) + + const federation = metadata.federation + + store.dispatch('setInstanceOption', { + name: 'tagPolicyAvailable', + value: typeof federation.mrf_policies === 'undefined' + ? false + : metadata.federation.mrf_policies.includes('TagPolicy') + }) + + store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation }) + store.dispatch('setInstanceOption', { + name: 'federating', + value: typeof federation.enabled === 'undefined' + ? true + : federation.enabled + }) + + const accountActivationRequired = metadata.accountActivationRequired + store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired }) + + const accounts = metadata.staffAccounts + resolveStaffAccounts({ store, accounts }) } else { throw (res) } @@ -236,7 +270,7 @@ const checkOAuthToken = async ({ store }) => { try { await store.dispatch('loginUser', store.getters.getUserToken()) } catch (e) { - console.log(e) + console.error(e) } } resolve() @@ -244,29 +278,38 @@ const checkOAuthToken = async ({ store }) => { } const afterStoreSetup = async ({ store, i18n }) => { - if (store.state.config.customTheme) { - // This is a hack to deal with async loading of config.json and themes - // See: style_setter.js, setPreset() - window.themeLoaded = true - store.dispatch('setOption', { - name: 'customTheme', - value: store.state.config.customTheme - }) - } - const width = windowWidth() store.dispatch('setMobileLayout', width <= 800) + 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!') + } // Now we can try getting the server settings and logging in await Promise.all([ checkOAuthToken({ store }), - setConfig({ store }), getTOS({ store }), getInstancePanel({ store }), getStickers({ store }), getNodeInfo({ store }) ]) + // Start fetching things that don't need to block the UI + store.dispatch('fetchMutes') + const router = new VueRouter({ mode: 'history', routes: routes(store), diff --git a/src/boot/routes.js b/src/boot/routes.js index 7400a682..d98a3b50 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -7,10 +7,8 @@ import Interactions from 'components/interactions/interactions.vue' import DMs from 'components/dm_timeline/dm_timeline.vue' import UserProfile from 'components/user_profile/user_profile.vue' import Search from 'components/search/search.vue' -import Settings from 'components/settings/settings.vue' import Registration from 'components/registration/registration.vue' import PasswordReset from 'components/password_reset/password_reset.vue' -import UserSettings from 'components/user_settings/user_settings.vue' import FollowRequests from 'components/follow_requests/follow_requests.vue' import OAuthCallback from 'components/oauth_callback/oauth_callback.vue' import Notifications from 'components/notifications/notifications.vue' @@ -56,12 +54,10 @@ export default (store) => { { name: 'external-user-profile', path: '/users/:id', component: UserProfile }, { name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute }, { name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute }, - { name: 'settings', path: '/settings', component: Settings }, { name: 'registration', path: '/registration', component: Registration }, { name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true }, { name: 'registration-token', path: '/registration/:token', component: Registration }, { name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute }, - { name: 'user-settings', path: '/user-settings', component: UserSettings, beforeEnter: validateAuthenticatedRoute }, { name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute }, { name: 'login', path: '/login', component: AuthForm }, { name: 'chat', path: '/chat', component: ChatPanel, props: () => ({ floating: false }) }, diff --git a/src/components/about/about.js b/src/components/about/about.js index ae1cb182..1df25845 100644 --- a/src/components/about/about.js +++ b/src/components/about/about.js @@ -1,15 +1,24 @@ import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_panel.vue' import FeaturesPanel from '../features_panel/features_panel.vue' import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue' +import StaffPanel from '../staff_panel/staff_panel.vue' +import MRFTransparencyPanel from '../mrf_transparency_panel/mrf_transparency_panel.vue' const About = { components: { InstanceSpecificPanel, FeaturesPanel, - TermsOfServicePanel + TermsOfServicePanel, + StaffPanel, + MRFTransparencyPanel }, computed: { - showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel } + showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }, + showInstanceSpecificPanel () { + return this.$store.state.instance.showInstanceSpecificPanel && + !this.$store.getters.mergedConfig.hideISP && + this.$store.state.instance.instanceSpecificPanelContent + } } } diff --git a/src/components/about/about.vue b/src/components/about/about.vue index 62ae16ea..518f6184 100644 --- a/src/components/about/about.vue +++ b/src/components/about/about.vue @@ -1,8 +1,10 @@ <template> <div class="sidebar"> - <instance-specific-panel /> - <features-panel v-if="showFeaturesPanel" /> + <instance-specific-panel v-if="showInstanceSpecificPanel" /> + <staff-panel /> <terms-of-service-panel /> + <MRFTransparencyPanel /> + <features-panel v-if="showFeaturesPanel" /> </div> </template> diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js index 204d506a..0826c275 100644 --- a/src/components/account_actions/account_actions.js +++ b/src/components/account_actions/account_actions.js @@ -1,14 +1,16 @@ import ProgressButton from '../progress_button/progress_button.vue' +import Popover from '../popover/popover.vue' const AccountActions = { props: [ - 'user' + 'user', 'relationship' ], data () { return { } }, components: { - ProgressButton + ProgressButton, + Popover }, methods: { showRepeats () { @@ -25,9 +27,6 @@ const AccountActions = { }, reportUser () { this.$store.dispatch('openUserReportingModal', this.user.id) - }, - mentionUser () { - this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user }) } } } diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue index 046cba93..744b77d5 100644 --- a/src/components/account_actions/account_actions.vue +++ b/src/components/account_actions/account_actions.vue @@ -1,46 +1,36 @@ <template> <div class="account-actions"> - <v-popover + <Popover trigger="click" - class="account-tools-popover" - :container="false" - placement="bottom-end" - :offset="5" + placement="bottom" > - <div slot="popover"> + <div + slot="content" + class="account-tools-popover" + > <div class="dropdown-menu"> - <button - class="btn btn-default btn-block dropdown-item" - @click="mentionUser" - > - {{ $t('user_card.mention') }} - </button> - <template v-if="user.following"> - <div - role="separator" - class="dropdown-divider" - /> + <template v-if="relationship.following"> <button - v-if="user.showing_reblogs" + v-if="relationship.showing_reblogs" class="btn btn-default dropdown-item" @click="hideRepeats" > {{ $t('user_card.hide_repeats') }} </button> <button - v-if="!user.showing_reblogs" + v-if="!relationship.showing_reblogs" class="btn btn-default dropdown-item" @click="showRepeats" > {{ $t('user_card.show_repeats') }} </button> + <div + role="separator" + class="dropdown-divider" + /> </template> - <div - role="separator" - class="dropdown-divider" - /> <button - v-if="user.statusnet_blocking" + v-if="relationship.blocking" class="btn btn-default btn-block dropdown-item" @click="unblockUser" > @@ -61,10 +51,13 @@ </button> </div> </div> - <div class="btn btn-default ellipsis-button"> + <div + slot="trigger" + class="btn btn-default ellipsis-button" + > <i class="icon-ellipsis trigger-button" /> </div> - </v-popover> + </Popover> </div> </template> @@ -72,7 +65,6 @@ <style lang="scss"> @import '../../_variables.scss'; -@import '../popper/popper.scss'; .account-actions { margin: 0 .8em; } @@ -80,6 +72,7 @@ .account-actions button.dropdown-item { margin-left: 0; } + .account-actions .trigger-button { color: $fallback--lightText; color: var(--lightText, $fallback--lightText); diff --git a/src/components/async_component_error/async_component_error.vue b/src/components/async_component_error/async_component_error.vue new file mode 100644 index 00000000..b68b98f9 --- /dev/null +++ b/src/components/async_component_error/async_component_error.vue @@ -0,0 +1,41 @@ +<template> + <div class="async-component-error"> + <div> + <h4> + {{ $t('general.generic_error') }} + </h4> + <p> + {{ $t('general.error_retry') }} + </p> + <button + class="btn" + @click="retry" + > + {{ $t('general.retry') }} + </button> + </div> + </div> +</template> + +<script> +export default { + methods: { + retry () { + this.$emit('resetAsyncComponent') + } + } +} +</script> + +<style lang="scss"> +.async-component-error { + display: flex; + height: 100%; + align-items: center; + justify-content: center; + .btn { + margin: .5em; + padding: .5em 2em; + } +} +</style> diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index 06b496b0..b832e10f 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -2,6 +2,7 @@ import StillImage from '../still-image/still-image.vue' import VideoAttachment from '../video_attachment/video_attachment.vue' import nsfwImage from '../../assets/nsfw.png' import fileTypeService from '../../services/file_type/file_type.service.js' +import { mapGetters } from 'vuex' const Attachment = { props: [ @@ -49,7 +50,8 @@ const Attachment = { }, fullwidth () { return this.type === 'html' || this.type === 'audio' - } + }, + ...mapGetters(['mergedConfig']) }, methods: { linkClicked ({ target }) { @@ -58,7 +60,7 @@ const Attachment = { } }, openModal (event) { - const modalTypes = this.$store.getters.mergedConfig.playVideosInModal + const modalTypes = this.mergedConfig.playVideosInModal ? ['image', 'video'] : ['image'] if (fileTypeService.fileMatchesSomeType(modalTypes, this.attachment) || @@ -71,7 +73,10 @@ const Attachment = { } }, toggleHidden (event) { - if (this.$store.getters.mergedConfig.useOneClickNsfw && !this.showHidden) { + if ( + (this.mergedConfig.useOneClickNsfw && !this.showHidden) && + (this.type !== 'video' || this.mergedConfig.playVideosInModal) + ) { this.openModal(event) return } diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index 0748b2f0..a7e217c1 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -130,6 +130,8 @@ .placeholder { margin-right: 8px; margin-bottom: 4px; + color: $fallback--link; + color: var(--postLink, $fallback--link); } .nsfw-placeholder { diff --git a/src/components/autosuggest/autosuggest.vue b/src/components/autosuggest/autosuggest.vue index 1f86e996..f283ab82 100644 --- a/src/components/autosuggest/autosuggest.vue +++ b/src/components/autosuggest/autosuggest.vue @@ -40,8 +40,8 @@ top: 100%; right: 0; max-height: 400px; - background-color: $fallback--lightBg; - background-color: var(--lightBg, $fallback--lightBg); + background-color: $fallback--bg; + background-color: var(--bg, $fallback--bg); border-style: solid; border-width: 1px; border-color: $fallback--border; diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue index 8a02174e..9e410610 100644 --- a/src/components/basic_user_card/basic_user_card.vue +++ b/src/components/basic_user_card/basic_user_card.vue @@ -12,7 +12,7 @@ class="basic-user-card-expanded-content" > <UserCard - :user="user" + :user-id="user.id" :rounded="true" :bordered="true" /> diff --git a/src/components/block_card/block_card.js b/src/components/block_card/block_card.js index c459ff1b..0bf4e37b 100644 --- a/src/components/block_card/block_card.js +++ b/src/components/block_card/block_card.js @@ -11,8 +11,11 @@ const BlockCard = { user () { return this.$store.getters.findUser(this.userId) }, + relationship () { + return this.$store.getters.relationship(this.userId) + }, blocked () { - return this.user.statusnet_blocking + return this.relationship.blocking } }, components: { diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue index 1113f81d..03375b2f 100644 --- a/src/components/checkbox/checkbox.vue +++ b/src/components/checkbox/checkbox.vue @@ -87,13 +87,13 @@ export default { &:checked + .checkbox-indicator::before { color: $fallback--text; - color: var(--text, $fallback--text); + color: var(--inputText, $fallback--text); } &:indeterminate + .checkbox-indicator::before { content: '–'; color: $fallback--text; - color: var(--text, $fallback--text); + color: var(--inputText, $fallback--text); } } diff --git a/src/components/color_input/color_input.scss b/src/components/color_input/color_input.scss new file mode 100644 index 00000000..8e9923cf --- /dev/null +++ b/src/components/color_input/color_input.scss @@ -0,0 +1,68 @@ +@import '../../_variables.scss'; + +.color-input { + display: inline-flex; + + &-field.input { + display: inline-flex; + flex: 0 0 0; + max-width: 9em; + align-items: stretch; + padding: .2em 8px; + + input { + background: none; + color: $fallback--lightText; + color: var(--inputText, $fallback--lightText); + border: none; + padding: 0; + margin: 0; + + &.textColor { + flex: 1 0 3em; + min-width: 3em; + padding: 0; + } + + &.nativeColor { + flex: 0 0 2em; + min-width: 2em; + align-self: center; + height: 100%; + } + } + .computedIndicator, + .transparentIndicator { + flex: 0 0 2em; + min-width: 2em; + align-self: center; + height: 100%; + } + .transparentIndicator { + // forgot to install counter-strike source, ooops + background-color: #FF00FF; + position: relative; + &::before, &::after { + display: block; + content: ''; + background-color: #000000; + position: absolute; + height: 50%; + width: 50%; + } + &::after { + top: 0; + left: 0; + } + &::before { + bottom: 0; + right: 0; + } + } + } + + .label { + flex: 1 1 auto; + } + +} diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue index 9db62e81..8fb16113 100644 --- a/src/components/color_input/color_input.vue +++ b/src/components/color_input/color_input.vue @@ -1,6 +1,6 @@ <template> <div - class="color-control style-control" + class="color-input style-control" :class="{ disabled: !present || disabled }" > <label @@ -9,46 +9,100 @@ > {{ label }} </label> - <input - v-if="typeof fallback !== 'undefined'" - :id="name + '-o'" - class="opt exlcude-disabled" - type="checkbox" + <Checkbox + v-if="typeof fallback !== 'undefined' && showOptionalTickbox" :checked="present" - @input="$emit('input', typeof value === 'undefined' ? fallback : undefined)" - > - <label - v-if="typeof fallback !== 'undefined'" - class="opt-l" - :for="name + '-o'" + :disabled="disabled" + class="opt" + @change="$emit('input', typeof value === 'undefined' ? fallback : undefined)" /> - <input - :id="name" - class="color-input" - type="color" - :value="value || fallback" - :disabled="!present || disabled" - @input="$emit('input', $event.target.value)" - > - <input - :id="name + '-t'" - class="text-input" - type="text" - :value="value || fallback" - :disabled="!present || disabled" - @input="$emit('input', $event.target.value)" - > + <div class="input color-input-field"> + <input + :id="name + '-t'" + class="textColor unstyled" + type="text" + :value="value || fallback" + :disabled="!present || disabled" + @input="$emit('input', $event.target.value)" + > + <input + v-if="validColor" + :id="name" + class="nativeColor unstyled" + type="color" + :value="value || fallback" + :disabled="!present || disabled" + @input="$emit('input', $event.target.value)" + > + <div + v-if="transparentColor" + class="transparentIndicator" + /> + <div + v-if="computedColor" + class="computedIndicator" + :style="{backgroundColor: fallback}" + /> + </div> </div> </template> - +<style lang="scss" src="./color_input.scss"></style> <script> +import Checkbox from '../checkbox/checkbox.vue' +import { hex2rgb } from '../../services/color_convert/color_convert.js' export default { - props: [ - 'name', 'label', 'value', 'fallback', 'disabled' - ], + components: { + Checkbox + }, + props: { + // Name of color, used for identifying + name: { + required: true, + type: String + }, + // Readable label + label: { + required: true, + type: String + }, + // Color value, should be required but vue cannot tell the difference + // between "property missing" and "property set to undefined" + value: { + required: false, + type: String, + default: undefined + }, + // Color fallback to use when value is not defeind + fallback: { + required: false, + type: String, + default: undefined + }, + // Disable the control + disabled: { + required: false, + type: Boolean, + default: false + }, + // Show "optional" tickbox, for when value might become mandatory + showOptionalTickbox: { + required: false, + type: Boolean, + default: true + } + }, computed: { present () { return typeof this.value !== 'undefined' + }, + validColor () { + return hex2rgb(this.value || this.fallback) + }, + transparentColor () { + return this.value === 'transparent' + }, + computedColor () { + return this.value && this.value.startsWith('--') } } } diff --git a/src/components/contrast_ratio/contrast_ratio.vue b/src/components/contrast_ratio/contrast_ratio.vue index 15a450a2..ba92bc17 100644 --- a/src/components/contrast_ratio/contrast_ratio.vue +++ b/src/components/contrast_ratio/contrast_ratio.vue @@ -37,9 +37,17 @@ <script> export default { - props: [ - 'large', 'contrast' - ], + props: { + large: { + required: false + }, + // TODO: Make theme switcher compute theme initially so that contrast + // component won't be called without contrast data + contrast: { + required: false, + type: Object + } + }, computed: { hint () { const levelVal = this.contrast.aaa ? 'aaa' : (this.contrast.aa ? 'aa' : 'bad') diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 72ee9c39..45fb2bf6 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -43,7 +43,8 @@ const conversation = { 'collapsable', 'isPage', 'pinnedStatusIdsObject', - 'inProfile' + 'inProfile', + 'profileUserId' ], created () { if (this.isPage) { @@ -149,6 +150,7 @@ const conversation = { if (!id) return this.highlight = id this.$store.dispatch('fetchFavsAndRepeats', id) + this.$store.dispatch('fetchEmojiReactionsBy', id) }, getHighlight () { return this.isExpanded ? this.highlight : null diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 0f1de55f..2e48240a 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -27,6 +27,7 @@ :highlight="getHighlight()" :replies="getReplies(status.id)" :in-profile="inProfile" + :profile-user-id="profileUserId" class="status-fadein panel-body" @goto="setHighlight" @toggleExpanded="toggleExpanded" diff --git a/src/components/dialog_modal/dialog_modal.vue b/src/components/dialog_modal/dialog_modal.vue index 55d7a7d2..3241ce3e 100644 --- a/src/components/dialog_modal/dialog_modal.vue +++ b/src/components/dialog_modal/dialog_modal.vue @@ -75,18 +75,18 @@ .dialog-modal-content { margin: 0; padding: 1rem 1rem; - background-color: $fallback--lightBg; - background-color: var(--lightBg, $fallback--lightBg); + background-color: $fallback--bg; + background-color: var(--bg, $fallback--bg); white-space: normal; } .dialog-modal-footer { margin: 0; padding: .5em .5em; - background-color: $fallback--lightBg; - background-color: var(--lightBg, $fallback--lightBg); - border-top: 1px solid $fallback--bg; - border-top: 1px solid var(--bg, $fallback--bg); + 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; diff --git a/src/components/domain_mute_card/domain_mute_card.js b/src/components/domain_mute_card/domain_mute_card.js new file mode 100644 index 00000000..f234dcb0 --- /dev/null +++ b/src/components/domain_mute_card/domain_mute_card.js @@ -0,0 +1,26 @@ +import ProgressButton from '../progress_button/progress_button.vue' + +const DomainMuteCard = { + props: ['domain'], + components: { + ProgressButton + }, + computed: { + user () { + return this.$store.state.users.currentUser + }, + muted () { + return this.user.domainMutes.includes(this.domain) + } + }, + methods: { + unmuteDomain () { + return this.$store.dispatch('unmuteDomain', this.domain) + }, + muteDomain () { + return this.$store.dispatch('muteDomain', this.domain) + } + } +} + +export default DomainMuteCard diff --git a/src/components/domain_mute_card/domain_mute_card.vue b/src/components/domain_mute_card/domain_mute_card.vue new file mode 100644 index 00000000..97aee243 --- /dev/null +++ b/src/components/domain_mute_card/domain_mute_card.vue @@ -0,0 +1,53 @@ +<template> + <div class="domain-mute-card"> + <div class="domain-mute-card-domain"> + {{ domain }} + </div> + <ProgressButton + v-if="muted" + :click="unmuteDomain" + class="btn btn-default" + > + {{ $t('domain_mute_card.unmute') }} + <template slot="progress"> + {{ $t('domain_mute_card.unmute_progress') }} + </template> + </ProgressButton> + <ProgressButton + v-else + :click="muteDomain" + class="btn btn-default" + > + {{ $t('domain_mute_card.mute') }} + <template slot="progress"> + {{ $t('domain_mute_card.mute_progress') }} + </template> + </ProgressButton> + </div> +</template> + +<script src="./domain_mute_card.js"></script> + +<style lang="scss"> +.domain-mute-card { + flex: 1 0; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.6em 1em 0.6em 0; + + &-domain { + margin-right: 1em; + overflow: hidden; + text-overflow: ellipsis; + } + + button { + width: 10em; + } + + .autosuggest-results & { + padding-left: 1em; + } +} +</style> diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js index 001a22e9..f4c3479c 100644 --- a/src/components/emoji_input/emoji_input.js +++ b/src/components/emoji_input/emoji_input.js @@ -147,7 +147,7 @@ const EmojiInput = { input.elm.addEventListener('keydown', this.onKeyDown) input.elm.addEventListener('click', this.onClickInput) input.elm.addEventListener('transitionend', this.onTransition) - input.elm.addEventListener('compositionupdate', this.onCompositionUpdate) + input.elm.addEventListener('input', this.onInput) }, unmounted () { const { input } = this @@ -159,7 +159,7 @@ const EmojiInput = { input.elm.removeEventListener('keydown', this.onKeyDown) input.elm.removeEventListener('click', this.onClickInput) input.elm.removeEventListener('transitionend', this.onTransition) - input.elm.removeEventListener('compositionupdate', this.onCompositionUpdate) + input.elm.removeEventListener('input', this.onInput) } }, methods: { @@ -406,12 +406,6 @@ const EmojiInput = { this.resize() this.$emit('input', e.target.value) }, - onCompositionUpdate (e) { - this.showPicker = false - this.setCaret(e) - this.resize() - this.$emit('input', e.target.value) - }, onClickInput (e) { this.showPicker = false }, diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue index a7215670..e9ac09c3 100644 --- a/src/components/emoji_input/emoji_input.vue +++ b/src/components/emoji_input/emoji_input.vue @@ -109,10 +109,16 @@ box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5); box-shadow: var(--popupShadow); min-width: 75%; - background: $fallback--bg; - background: var(--bg, $fallback--bg); - color: $fallback--lightText; - color: var(--lightText, $fallback--lightText); + background-color: $fallback--bg; + background-color: var(--popover, $fallback--bg); + color: $fallback--link; + color: var(--popoverText, $fallback--link); + --faint: var(--popoverFaintText, $fallback--faint); + --faintLink: var(--popoverFaintLink, $fallback--faint); + --lightText: var(--popoverLightText, $fallback--lightText); + --postLink: var(--popoverPostLink, $fallback--link); + --postFaintLink: var(--popoverPostFaintLink, $fallback--link); + --icon: var(--popoverIcon, $fallback--icon); } } @@ -157,7 +163,12 @@ &.highlighted { background-color: $fallback--fg; - background-color: var(--lightBg, $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); } } } diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js index aec5c39d..15a71eff 100644 --- a/src/components/emoji_input/suggestor.js +++ b/src/components/emoji_input/suggestor.js @@ -29,17 +29,29 @@ export default data => input => { export const suggestEmoji = emojis => input => { const noPrefix = input.toLowerCase().substr(1) return emojis - .filter(({ displayText }) => displayText.toLowerCase().startsWith(noPrefix)) + .filter(({ displayText }) => displayText.toLowerCase().match(noPrefix)) .sort((a, b) => { let aScore = 0 let bScore = 0 - // Make custom emojis a priority - aScore += a.imageUrl ? 10 : 0 - bScore += b.imageUrl ? 10 : 0 + // An exact match always wins + aScore += a.displayText.toLowerCase() === noPrefix ? 200 : 0 + bScore += b.displayText.toLowerCase() === noPrefix ? 200 : 0 - // Sort alphabetically - const alphabetically = a.displayText > b.displayText ? 1 : -1 + // Prioritize custom emoji a lot + aScore += a.imageUrl ? 100 : 0 + bScore += b.imageUrl ? 100 : 0 + + // Prioritize prefix matches somewhat + aScore += a.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0 + bScore += b.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0 + + // Sort by length + aScore -= a.displayText.length + bScore -= b.displayText.length + + // Break ties alphabetically + const alphabetically = a.displayText > b.displayText ? 0.5 : -0.5 return bScore - aScore + alphabetically }) diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss index 6608f393..8bd07e45 100644 --- a/src/components/emoji_picker/emoji_picker.scss +++ b/src/components/emoji_picker/emoji_picker.scss @@ -8,6 +8,15 @@ left: 0; margin: 0 !important; z-index: 1; + background-color: $fallback--bg; + background-color: var(--popover, $fallback--bg); + color: $fallback--link; + color: var(--popoverText, $fallback--link); + --lightText: var(--popoverLightText, $fallback--faint); + --faint: var(--popoverFaintText, $fallback--faint); + --faintLink: var(--popoverFaintLink, $fallback--faint); + --lightText: var(--popoverLightText, $fallback--lightText); + --icon: var(--popoverIcon, $fallback--icon); .keep-open, .too-many-emoji { diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js new file mode 100644 index 00000000..ae7f53be --- /dev/null +++ b/src/components/emoji_reactions/emoji_reactions.js @@ -0,0 +1,69 @@ +import UserAvatar from '../user_avatar/user_avatar.vue' +import Popover from '../popover/popover.vue' + +const EMOJI_REACTION_COUNT_CUTOFF = 12 + +const EmojiReactions = { + name: 'EmojiReactions', + components: { + UserAvatar, + Popover + }, + props: ['status'], + data: () => ({ + showAll: false + }), + computed: { + tooManyReactions () { + return this.status.emoji_reactions.length > EMOJI_REACTION_COUNT_CUTOFF + }, + emojiReactions () { + return this.showAll + ? this.status.emoji_reactions + : this.status.emoji_reactions.slice(0, EMOJI_REACTION_COUNT_CUTOFF) + }, + showMoreString () { + return `+${this.status.emoji_reactions.length - EMOJI_REACTION_COUNT_CUTOFF}` + }, + accountsForEmoji () { + return this.status.emoji_reactions.reduce((acc, reaction) => { + acc[reaction.name] = reaction.accounts || [] + return acc + }, {}) + }, + loggedIn () { + return !!this.$store.state.users.currentUser + } + }, + methods: { + toggleShowAll () { + this.showAll = !this.showAll + }, + reactedWith (emoji) { + return this.status.emoji_reactions.find(r => r.name === emoji).me + }, + fetchEmojiReactionsByIfMissing () { + const hasNoAccounts = this.status.emoji_reactions.find(r => !r.accounts) + if (hasNoAccounts) { + this.$store.dispatch('fetchEmojiReactionsBy', this.status.id) + } + }, + reactWith (emoji) { + this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji }) + }, + unreact (emoji) { + this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji }) + }, + emojiOnClick (emoji, event) { + if (!this.loggedIn) return + + if (this.reactedWith(emoji)) { + this.unreact(emoji) + } else { + this.reactWith(emoji) + } + } + } +} + +export default EmojiReactions diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue new file mode 100644 index 00000000..bac4c605 --- /dev/null +++ b/src/components/emoji_reactions/emoji_reactions.vue @@ -0,0 +1,141 @@ +<template> + <div class="emoji-reactions"> + <Popover + v-for="(reaction) in emojiReactions" + :key="reaction.name" + trigger="hover" + placement="top" + :offset="{ y: 5 }" + > + <div + slot="content" + class="reacted-users" + > + <div v-if="accountsForEmoji[reaction.name].length"> + <div + v-for="(account) in accountsForEmoji[reaction.name]" + :key="account.id" + class="reacted-user" + > + <UserAvatar + :user="account" + class="avatar-small" + :compact="true" + /> + <div class="reacted-user-names"> + <!-- eslint-disable vue/no-v-html --> + <span + class="reacted-user-name" + v-html="account.name_html" + /> + <!-- eslint-enable vue/no-v-html --> + <span class="reacted-user-screen-name">{{ account.screen_name }}</span> + </div> + </div> + </div> + <div v-else> + <i class="icon-spin4 animate-spin" /> + </div> + </div> + <button + slot="trigger" + class="emoji-reaction btn btn-default" + :class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }" + @click="emojiOnClick(reaction.name, $event)" + @mouseenter="fetchEmojiReactionsByIfMissing()" + > + <span class="reaction-emoji">{{ reaction.name }}</span> + <span>{{ reaction.count }}</span> + </button> + </Popover> + <a + v-if="tooManyReactions" + class="emoji-reaction-expand faint" + href="javascript:void(0)" + @click="toggleShowAll" + > + {{ showAll ? $t('general.show_less') : showMoreString }} + </a> + </div> +</template> + +<script src="./emoji_reactions.js" ></script> +<style lang="scss"> +@import '../../_variables.scss'; + +.emoji-reactions { + display: flex; + margin-top: 0.25em; + flex-wrap: wrap; +} + +.reacted-users { + padding: 0.5em; +} + +.reacted-user { + padding: 0.25em; + display: flex; + flex-direction: row; + + .reacted-user-names { + display: flex; + flex-direction: column; + margin-left: 0.5em; + min-width: 5em; + + img { + width: 1em; + height: 1em; + } + } + + .reacted-user-screen-name { + font-size: 9px; + } +} + +.emoji-reaction { + padding: 0 0.5em; + margin-right: 0.5em; + margin-top: 0.5em; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + .reaction-emoji { + width: 1.25em; + margin-right: 0.25em; + } + &:focus { + outline: none; + } + + &.not-clickable { + cursor: default; + &:hover { + box-shadow: $fallback--buttonShadow; + box-shadow: var(--buttonShadow); + } + } +} + +.emoji-reaction-expand { + padding: 0 0.5em; + margin-right: 0.5em; + margin-top: 0.5em; + display: flex; + align-items: center; + justify-content: center; + &:hover { + text-decoration: underline; + } +} + +.picked-reaction { + border: 1px solid var(--accent, $fallback--link); + margin-left: -1px; // offset the border, can't use inset shadows either + margin-right: calc(0.5em - 1px); +} + +</style> diff --git a/src/components/export_import/export_import.vue b/src/components/export_import/export_import.vue index 20c6f569..ae00487f 100644 --- a/src/components/export_import/export_import.vue +++ b/src/components/export_import/export_import.vue @@ -42,7 +42,7 @@ export default { }, methods: { exportData () { - const stringified = JSON.stringify(this.exportObject) // Pretty-print and indent with 2 spaces + const stringified = JSON.stringify(this.exportObject, null, 2) // Pretty-print and indent with 2 spaces // Create an invisible link with a data url and simulate a click const e = document.createElement('a') diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js index 5ac73e97..e4b19d01 100644 --- a/src/components/extra_buttons/extra_buttons.js +++ b/src/components/extra_buttons/extra_buttons.js @@ -1,5 +1,8 @@ +import Popover from '../popover/popover.vue' + const ExtraButtons = { props: [ 'status' ], + components: { Popover }, methods: { deleteStatus () { const confirmed = window.confirm(this.$t('status.delete_confirm')) @@ -26,6 +29,11 @@ const ExtraButtons = { this.$store.dispatch('unmuteConversation', this.status.id) .then(() => this.$emit('onSuccess')) .catch(err => this.$emit('onError', err.error.error)) + }, + copyLink () { + navigator.clipboard.writeText(this.statusLink) + .then(() => this.$emit('onSuccess')) + .catch(err => this.$emit('onError', err.error.error)) } }, computed: { @@ -43,6 +51,9 @@ const ExtraButtons = { }, canMute () { return !!this.currentUser + }, + 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 index 746f1c91..bca93ea7 100644 --- a/src/components/extra_buttons/extra_buttons.vue +++ b/src/components/extra_buttons/extra_buttons.vue @@ -1,11 +1,13 @@ <template> - <v-popover - v-if="canDelete || canMute || canPin" + <Popover trigger="click" placement="top" class="extra-button-popover" > - <div slot="popover"> + <div + slot="content" + slot-scope="{close}" + > <div class="dropdown-menu"> <button v-if="canMute && !status.thread_muted" @@ -23,41 +25,48 @@ </button> <button v-if="!status.pinned && canPin" - v-close-popover class="dropdown-item dropdown-item-icon" @click.prevent="pinStatus" + @click="close" > <i class="icon-pin" /><span>{{ $t("status.pin") }}</span> </button> <button v-if="status.pinned && canPin" - v-close-popover class="dropdown-item dropdown-item-icon" @click.prevent="unpinStatus" + @click="close" > <i class="icon-pin" /><span>{{ $t("status.unpin") }}</span> </button> <button v-if="canDelete" - v-close-popover class="dropdown-item dropdown-item-icon" @click.prevent="deleteStatus" + @click="close" > <i class="icon-cancel" /><span>{{ $t("status.delete") }}</span> </button> + <button + class="dropdown-item dropdown-item-icon" + @click.prevent="copyLink" + @click="close" + > + <i class="icon-share" /><span>{{ $t("status.copy_link") }}</span> + </button> </div> </div> - <div class="button-icon"> - <i class="icon-ellipsis" /> - </div> - </v-popover> + <i + slot="trigger" + class="icon-ellipsis button-icon" + /> + </Popover> </template> <script src="./extra_buttons.js" ></script> <style lang="scss"> @import '../../_variables.scss'; -@import '../popper/popper.scss'; .icon-ellipsis { cursor: pointer; diff --git a/src/components/follow_button/follow_button.js b/src/components/follow_button/follow_button.js index 12da2645..95e7cb6b 100644 --- a/src/components/follow_button/follow_button.js +++ b/src/components/follow_button/follow_button.js @@ -1,6 +1,6 @@ import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate' export default { - props: ['user', 'labelFollowing', 'buttonClass'], + props: ['relationship', 'labelFollowing', 'buttonClass'], data () { return { inProgress: false @@ -8,12 +8,12 @@ export default { }, computed: { isPressed () { - return this.inProgress || this.user.following + return this.inProgress || this.relationship.following }, title () { - if (this.inProgress || this.user.following) { + if (this.inProgress || this.relationship.following) { return this.$t('user_card.follow_unfollow') - } else if (this.user.requested) { + } else if (this.relationship.requested) { return this.$t('user_card.follow_again') } else { return this.$t('user_card.follow') @@ -22,9 +22,9 @@ export default { label () { if (this.inProgress) { return this.$t('user_card.follow_progress') - } else if (this.user.following) { + } else if (this.relationship.following) { return this.labelFollowing || this.$t('user_card.following') - } else if (this.user.requested) { + } else if (this.relationship.requested) { return this.$t('user_card.follow_sent') } else { return this.$t('user_card.follow') @@ -33,20 +33,20 @@ export default { }, methods: { onClick () { - this.user.following ? this.unfollow() : this.follow() + this.relationship.following ? this.unfollow() : this.follow() }, follow () { this.inProgress = true - requestFollow(this.user, this.$store).then(() => { + requestFollow(this.relationship.id, this.$store).then(() => { this.inProgress = false }) }, unfollow () { const store = this.$store this.inProgress = true - requestUnfollow(this.user, store).then(() => { + requestUnfollow(this.relationship.id, store).then(() => { this.inProgress = false - store.commit('removeStatus', { timeline: 'friends', userId: this.user.id }) + store.commit('removeStatus', { timeline: 'friends', userId: this.relationship.id }) }) } } diff --git a/src/components/follow_button/follow_button.vue b/src/components/follow_button/follow_button.vue index f0cbb94b..bfdc137b 100644 --- a/src/components/follow_button/follow_button.vue +++ b/src/components/follow_button/follow_button.vue @@ -1,7 +1,7 @@ <template> <button class="btn btn-default follow-button" - :class="{ pressed: isPressed }" + :class="{ toggled: isPressed }" :disabled="inProgress" :title="title" @click="onClick" diff --git a/src/components/follow_card/follow_card.js b/src/components/follow_card/follow_card.js index aefd609e..6dcb6d47 100644 --- a/src/components/follow_card/follow_card.js +++ b/src/components/follow_card/follow_card.js @@ -18,6 +18,9 @@ const FollowCard = { }, loggedIn () { return this.$store.state.users.currentUser + }, + relationship () { + return this.$store.getters.relationship(this.user.id) } } } diff --git a/src/components/follow_card/follow_card.vue b/src/components/follow_card/follow_card.vue index 81e6e6dc..b503783f 100644 --- a/src/components/follow_card/follow_card.vue +++ b/src/components/follow_card/follow_card.vue @@ -2,24 +2,24 @@ <basic-user-card :user="user"> <div class="follow-card-content-container"> <span - v-if="!noFollowsYou && user.follows_you" + v-if="isMe || (!noFollowsYou && relationship.followed_by)" class="faint" > {{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }} </span> <template v-if="!loggedIn"> <div - v-if="!user.following" + v-if="!relationship.following" class="follow-card-follow-button" > <RemoteFollow :user="user" /> </div> </template> - <template v-else> + <template v-else-if="!isMe"> <FollowButton - :user="user" - class="follow-card-follow-button" + :relationship="relationship" :label-following="$t('user_card.follow_unfollow')" + class="follow-card-follow-button" /> </template> </div> diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js index 1a00a1c1..cbd75311 100644 --- a/src/components/follow_request_card/follow_request_card.js +++ b/src/components/follow_request_card/follow_request_card.js @@ -1,4 +1,5 @@ import BasicUserCard from '../basic_user_card/basic_user_card.vue' +import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js' const FollowRequestCard = { props: ['user'], @@ -6,13 +7,32 @@ const FollowRequestCard = { BasicUserCard }, methods: { + findFollowRequestNotificationId () { + const notif = notificationsFromStore(this.$store).find( + (notif) => notif.from_profile.id === this.user.id && notif.type === 'follow_request' + ) + return notif && notif.id + }, approveUser () { - this.$store.state.api.backendInteractor.approveUser(this.user.id) + this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) this.$store.dispatch('removeFollowRequest', this.user) + + const notifId = this.findFollowRequestNotificationId() + this.$store.dispatch('markSingleNotificationAsSeen', { id: notifId }) + this.$store.dispatch('updateNotification', { + id: notifId, + updater: notification => { + notification.type = 'follow' + } + }) }, denyUser () { - this.$store.state.api.backendInteractor.denyUser(this.user.id) - this.$store.dispatch('removeFollowRequest', this.user) + const notifId = this.findFollowRequestNotificationId() + this.$store.state.api.backendInteractor.denyUser({ id: this.user.id }) + .then(() => { + this.$store.dispatch('dismissNotificationLocal', { id: notifId }) + this.$store.dispatch('removeFollowRequest', this.user) + }) } } } diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue index 7abc2161..1ffa7b3c 100644 --- a/src/components/gallery/gallery.vue +++ b/src/components/gallery/gallery.vue @@ -78,6 +78,7 @@ video, canvas { object-fit: contain; + height: 100%; } } diff --git a/src/components/interactions/interactions.js b/src/components/interactions/interactions.js index 1f8a9de9..7fe5e76d 100644 --- a/src/components/interactions/interactions.js +++ b/src/components/interactions/interactions.js @@ -3,12 +3,14 @@ import Notifications from '../notifications/notifications.vue' const tabModeDict = { mentions: ['mention'], 'likes+repeats': ['repeat', 'like'], - follows: ['follow'] + follows: ['follow'], + moves: ['move'] } const Interactions = { data () { return { + allowFollowingMove: this.$store.state.users.currentUser.allow_following_move, filterMode: tabModeDict['mentions'] } }, diff --git a/src/components/interactions/interactions.vue b/src/components/interactions/interactions.vue index 08cee343..57d5d87c 100644 --- a/src/components/interactions/interactions.vue +++ b/src/components/interactions/interactions.vue @@ -21,6 +21,11 @@ key="follows" :label="$t('interactions.follows')" /> + <span + v-if="!allowFollowingMove" + key="moves" + :label="$t('interactions.moves')" + /> </tab-switcher> <Notifications ref="notifications" diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue index 1ca22001..dd6800a3 100644 --- a/src/components/interface_language_switcher/interface_language_switcher.vue +++ b/src/components/interface_language_switcher/interface_language_switcher.vue @@ -32,7 +32,7 @@ import _ from 'lodash' export default { computed: { languageCodes () { - return Object.keys(languagesObject) + return languagesObject.languages }, languageNames () { @@ -43,7 +43,6 @@ export default { get: function () { return this.$store.getters.mergedConfig.interfaceLanguage }, set: function (val) { this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) - this.$i18n.locale = val } } }, @@ -51,8 +50,8 @@ export default { methods: { getLanguageName (code) { const specialLanguageNames = { - 'ja': 'Japanese (やさしいにほんご)', - 'ja_pedantic': 'Japanese (日本語)', + 'ja': 'Japanese (日本語)', + 'ja_easy': 'Japanese (やさしいにほんご)', 'zh': 'Chinese (简体中文)' } return specialLanguageNames[code] || ISO6391.getName(code) diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index 0b574a04..0d8f1da6 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -58,7 +58,7 @@ const LoginForm = { ).then((result) => { if (result.error) { if (result.error === 'mfa_required') { - this.requireMFA({ app: app, settings: result }) + this.requireMFA({ settings: result }) } else if (result.identifier === 'password_reset_required') { this.$router.push({ name: 'password-reset', params: { passwordResetRequested: true } }) } else { diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index abb18c7d..24764e80 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -84,10 +84,12 @@ const MediaModal = { } }, mounted () { + window.addEventListener('popstate', this.hide) document.addEventListener('keyup', this.handleKeyupEvent) document.addEventListener('keydown', this.handleKeydownEvent) }, destroyed () { + window.removeEventListener('popstate', this.hide) document.removeEventListener('keyup', this.handleKeyupEvent) document.removeEventListener('keydown', this.handleKeydownEvent) } diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 49e3143e..80d2a8b9 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -10,13 +10,13 @@ :src="currentMedia.url" @touchstart.stop="mediaTouchStart" @touchmove.stop="mediaTouchMove" + @click="hide" > <VideoAttachment v-if="type === 'video'" class="modal-image" :attachment="currentMedia" :controls="true" - @click.stop.native="" /> <button v-if="canNavigate" diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js index f457d022..fbb2d03d 100644 --- a/src/components/media_upload/media_upload.js +++ b/src/components/media_upload/media_upload.js @@ -5,10 +5,15 @@ import fileSizeFormatService from '../../services/file_size_format/file_size_for const mediaUpload = { data () { return { - uploading: false, + uploadCount: 0, uploadReady: true } }, + computed: { + uploading () { + return this.uploadCount > 0 + } + }, methods: { uploadFile (file) { const self = this @@ -23,29 +28,21 @@ const mediaUpload = { formData.append('file', file) self.$emit('uploading') - self.uploading = true + self.uploadCount++ statusPosterService.uploadMedia({ store, formData }) .then((fileData) => { self.$emit('uploaded', fileData) - self.uploading = false + self.decreaseUploadCount() }, (error) => { // eslint-disable-line handle-callback-err self.$emit('upload-failed', 'default') - self.uploading = false + self.decreaseUploadCount() }) }, - fileDrop (e) { - if (e.dataTransfer.files.length > 0) { - e.preventDefault() // allow dropping text like before - this.uploadFile(e.dataTransfer.files[0]) - } - }, - fileDrag (e) { - let types = e.dataTransfer.types - if (types.contains('Files')) { - e.dataTransfer.dropEffect = 'copy' - } else { - e.dataTransfer.dropEffect = 'none' + decreaseUploadCount () { + this.uploadCount-- + if (this.uploadCount === 0) { + this.$emit('all-uploaded') } }, clearFile () { @@ -54,11 +51,13 @@ const mediaUpload = { this.uploadReady = true }) }, - change ({ target }) { - for (var i = 0; i < target.files.length; i++) { - let file = target.files[i] + multiUpload (files) { + for (const file of files) { this.uploadFile(file) } + }, + change ({ target }) { + this.multiUpload(target.files) } }, props: [ @@ -67,7 +66,7 @@ const mediaUpload = { watch: { 'dropFiles': function (fileInfos) { if (!this.uploading) { - this.uploadFile(fileInfos[0]) + this.multiUpload(fileInfos) } } } diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue index 1dda7bc1..5e31730b 100644 --- a/src/components/media_upload/media_upload.vue +++ b/src/components/media_upload/media_upload.vue @@ -1,21 +1,16 @@ <template> - <div - class="media-upload" - @drop.prevent - @dragover.prevent="fileDrag" - @drop="fileDrop" - > + <div class="media-upload"> <label - class="btn btn-default" + class="label" :title="$t('tool_tip.media_upload')" > <i v-if="uploading" - class="icon-spin4 animate-spin" + class="progress-icon icon-spin4 animate-spin" /> <i v-if="!uploading" - class="icon-upload" + class="new-icon icon-upload" /> <input v-if="uploadReady" @@ -30,15 +25,24 @@ <script src="./media_upload.js" ></script> -<style> +<style lang="scss"> .media-upload { - .icon-upload { + .label { + display: inline-block; + } + + .new-icon { cursor: pointer; } - label { - display: block; - width: 100%; + .progress-icon { + display: inline-block; + line-height: 0; + &::before { + /* Overriding fontello to achieve the perfect speeeen */ + margin: 0; + line-height: 0; + } } } -</style> + </style> diff --git a/src/components/mfa_form/recovery_form.js b/src/components/mfa_form/recovery_form.js index 7a3cc22d..b25c65dd 100644 --- a/src/components/mfa_form/recovery_form.js +++ b/src/components/mfa_form/recovery_form.js @@ -8,18 +8,23 @@ export default { }), computed: { ...mapGetters({ - authApp: 'authFlow/app', authSettings: 'authFlow/settings' }), - ...mapState({ instance: 'instance' }) + ...mapState({ + instance: 'instance', + oauth: 'oauth' + }) }, methods: { ...mapMutations('authFlow', ['requireTOTP', 'abortMFA']), ...mapActions({ login: 'authFlow/login' }), clearError () { this.error = false }, submit () { + const { clientId, clientSecret } = this.oauth + const data = { - app: this.authApp, + clientId, + clientSecret, instance: this.instance.server, mfaToken: this.authSettings.mfa_token, code: this.code diff --git a/src/components/mfa_form/totp_form.js b/src/components/mfa_form/totp_form.js index 778bf8dc..b774f2d0 100644 --- a/src/components/mfa_form/totp_form.js +++ b/src/components/mfa_form/totp_form.js @@ -7,18 +7,23 @@ export default { }), computed: { ...mapGetters({ - authApp: 'authFlow/app', authSettings: 'authFlow/settings' }), - ...mapState({ instance: 'instance' }) + ...mapState({ + instance: 'instance', + oauth: 'oauth' + }) }, methods: { ...mapMutations('authFlow', ['requireRecovery', 'abortMFA']), ...mapActions({ login: 'authFlow/login' }), clearError () { this.error = false }, submit () { + const { clientId, clientSecret } = this.oauth + const data = { - app: this.authApp, + clientId, + clientSecret, instance: this.instance.server, mfaToken: this.authSettings.mfa_token, code: this.code diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js index 5a90c31f..c1166a0c 100644 --- a/src/components/mobile_nav/mobile_nav.js +++ b/src/components/mobile_nav/mobile_nav.js @@ -29,6 +29,7 @@ const MobileNav = { unseenNotificationsCount () { return this.unseenNotifications.length }, + hideSitename () { return this.$store.state.instance.hideSitename }, sitename () { return this.$store.state.instance.name } }, methods: { diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue index d1c24e56..51f1d636 100644 --- a/src/components/mobile_nav/mobile_nav.vue +++ b/src/components/mobile_nav/mobile_nav.vue @@ -17,6 +17,7 @@ <i class="button-icon icon-menu" /> </a> <router-link + v-if="!hideSitename" class="site-name" :to="{ name: 'root' }" active-class="home" diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue index cee24241..2b58913f 100644 --- a/src/components/modal/modal.vue +++ b/src/components/modal/modal.vue @@ -1,8 +1,9 @@ <template> <div v-show="isOpen" - v-body-scroll-lock="isOpen" + v-body-scroll-lock="isOpen && !noBackground" class="modal-view" + :class="classes" @click.self="$emit('backdropClicked')" > <slot /> @@ -15,6 +16,18 @@ export default { isOpen: { type: Boolean, default: true + }, + noBackground: { + type: Boolean, + default: false + } + }, + computed: { + classes () { + return { + 'modal-background': !this.noBackground, + 'open': this.isOpen + } } } } @@ -32,12 +45,22 @@ export default { justify-content: center; align-items: center; overflow: auto; + pointer-events: none; animation-duration: 0.2s; - background-color: rgba(0, 0, 0, 0.5); animation-name: modal-background-fadein; + opacity: 0; - body:not(.scroll-locked) & { - opacity: 0; + > * { + pointer-events: initial; + } + + &.modal-background { + pointer-events: initial; + background-color: rgba(0, 0, 0, 0.5); + } + + &.open { + opacity: 1; } } diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js index 8aadc8c5..d4fdc53e 100644 --- a/src/components/moderation_tools/moderation_tools.js +++ b/src/components/moderation_tools/moderation_tools.js @@ -1,4 +1,5 @@ import DialogModal from '../dialog_modal/dialog_modal.vue' +import Popover from '../popover/popover.vue' const FORCE_NSFW = 'mrf_tag:media-force-nsfw' const STRIP_MEDIA = 'mrf_tag:media-strip' @@ -14,7 +15,6 @@ const ModerationTools = { ], data () { return { - showDropDown: false, tags: { FORCE_NSFW, STRIP_MEDIA, @@ -24,11 +24,13 @@ const ModerationTools = { SANDBOX, QUARANTINE }, - showDeleteUserDialog: false + showDeleteUserDialog: false, + toggled: false } }, components: { - DialogModal + DialogModal, + Popover }, computed: { tagsSet () { @@ -45,12 +47,12 @@ const ModerationTools = { toggleTag (tag) { const store = this.$store if (this.tagsSet.has(tag)) { - store.state.api.backendInteractor.untagUser(this.user, tag).then(response => { + store.state.api.backendInteractor.untagUser({ user: this.user, tag }).then(response => { if (!response.ok) { return } store.commit('untagUser', { user: this.user, tag }) }) } else { - store.state.api.backendInteractor.tagUser(this.user, tag).then(response => { + store.state.api.backendInteractor.tagUser({ user: this.user, tag }).then(response => { if (!response.ok) { return } store.commit('tagUser', { user: this.user, tag }) }) @@ -59,24 +61,19 @@ const ModerationTools = { toggleRight (right) { const store = this.$store if (this.user.rights[right]) { - store.state.api.backendInteractor.deleteRight(this.user, right).then(response => { + store.state.api.backendInteractor.deleteRight({ user: this.user, right }).then(response => { if (!response.ok) { return } - store.commit('updateRight', { user: this.user, right: right, value: false }) + store.commit('updateRight', { user: this.user, right, value: false }) }) } else { - store.state.api.backendInteractor.addRight(this.user, right).then(response => { + store.state.api.backendInteractor.addRight({ user: this.user, right }).then(response => { if (!response.ok) { return } - store.commit('updateRight', { user: this.user, right: right, value: true }) + store.commit('updateRight', { user: this.user, right, value: true }) }) } }, toggleActivationStatus () { - const store = this.$store - const status = !!this.user.deactivated - store.state.api.backendInteractor.setActivationStatus(this.user, status).then(response => { - if (!response.ok) { return } - store.commit('updateActivationStatus', { user: this.user, status: status }) - }) + this.$store.dispatch('toggleActivationStatus', { user: this.user }) }, deleteUserDialog (show) { this.showDeleteUserDialog = show @@ -85,7 +82,7 @@ const ModerationTools = { const store = this.$store const user = this.user const { id, name } = user - store.state.api.backendInteractor.deleteUser(user) + store.state.api.backendInteractor.deleteUser({ user }) .then(e => { this.$store.dispatch('markStatusesAsDeleted', status => user.id === status.user.id) const isProfile = this.$route.name === 'external-user-profile' || this.$route.name === 'user-profile' @@ -94,6 +91,9 @@ const ModerationTools = { window.history.back() } }) + }, + setToggled (value) { + this.toggled = value } } } diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue index 006d6373..b2d5acc5 100644 --- a/src/components/moderation_tools/moderation_tools.vue +++ b/src/components/moderation_tools/moderation_tools.vue @@ -1,13 +1,14 @@ <template> <div> - <v-popover + <Popover trigger="click" class="moderation-tools-popover" - placement="bottom-end" - @show="showDropDown = true" - @hide="showDropDown = false" + placement="bottom" + :offset="{ y: 5 }" + @show="setToggled(true)" + @close="setToggled(false)" > - <div slot="popover"> + <div slot="content"> <div class="dropdown-menu"> <span v-if="user.is_local"> <button @@ -122,12 +123,13 @@ </div> </div> <button + slot="trigger" class="btn btn-default btn-block" - :class="{ pressed: showDropDown }" + :class="{ toggled }" > {{ $t('user_card.admin_menu.moderation') }} </button> - </v-popover> + </Popover> <portal to="modal"> <DialogModal v-if="showDeleteUserDialog" @@ -160,7 +162,6 @@ <style lang="scss"> @import '../../_variables.scss'; -@import '../popper/popper.scss'; .menu-checkbox { float: right; diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.js b/src/components/mrf_transparency_panel/mrf_transparency_panel.js new file mode 100644 index 00000000..a0b600d2 --- /dev/null +++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.js @@ -0,0 +1,35 @@ +import { mapState } from 'vuex' +import { get } from 'lodash' + +const MRFTransparencyPanel = { + computed: { + ...mapState({ + federationPolicy: state => get(state, 'instance.federationPolicy'), + mrfPolicies: state => get(state, 'instance.federationPolicy.mrf_policies', []), + quarantineInstances: state => get(state, 'instance.federationPolicy.quarantined_instances', []), + acceptInstances: state => get(state, 'instance.federationPolicy.mrf_simple.accept', []), + rejectInstances: state => get(state, 'instance.federationPolicy.mrf_simple.reject', []), + ftlRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.federated_timeline_removal', []), + mediaNsfwInstances: state => get(state, 'instance.federationPolicy.mrf_simple.media_nsfw', []), + mediaRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.media_removal', []), + keywordsFtlRemoval: state => get(state, 'instance.federationPolicy.mrf_keyword.federated_timeline_removal', []), + keywordsReject: state => get(state, 'instance.federationPolicy.mrf_keyword.reject', []), + keywordsReplace: state => get(state, 'instance.federationPolicy.mrf_keyword.replace', []) + }), + hasInstanceSpecificPolicies () { + return this.quarantineInstances.length || + this.acceptInstances.length || + this.rejectInstances.length || + this.ftlRemovalInstances.length || + this.mediaNsfwInstances.length || + this.mediaRemovalInstances.length + }, + hasKeywordPolicies () { + return this.keywordsFtlRemoval.length || + this.keywordsReject.length || + this.keywordsReplace.length + } + } +} + +export default MRFTransparencyPanel diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue new file mode 100644 index 00000000..acdf822e --- /dev/null +++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue @@ -0,0 +1,167 @@ +<template> + <div + v-if="federationPolicy" + class="mrf-transparency-panel" + > + <div class="panel panel-default base01-background"> + <div class="panel-heading timeline-heading base02-background"> + <div class="title"> + {{ $t("about.mrf.federation") }} + </div> + </div> + <div class="panel-body"> + <div class="mrf-section"> + <h2>{{ $t("about.mrf.mrf_policies") }}</h2> + <p>{{ $t("about.mrf.mrf_policies_desc") }}</p> + + <ul> + <li + v-for="policy in mrfPolicies" + :key="policy" + v-text="policy" + /> + </ul> + + <h2 v-if="hasInstanceSpecificPolicies"> + {{ $t("about.mrf.simple.simple_policies") }} + </h2> + + <div v-if="acceptInstances.length"> + <h4>{{ $t("about.mrf.simple.accept") }}</h4> + + <p>{{ $t("about.mrf.simple.accept_desc") }}</p> + + <ul> + <li + v-for="instance in acceptInstances" + :key="instance" + v-text="instance" + /> + </ul> + </div> + + <div v-if="rejectInstances.length"> + <h4>{{ $t("about.mrf.simple.reject") }}</h4> + + <p>{{ $t("about.mrf.simple.reject_desc") }}</p> + + <ul> + <li + v-for="instance in rejectInstances" + :key="instance" + v-text="instance" + /> + </ul> + </div> + + <div v-if="quarantineInstances.length"> + <h4>{{ $t("about.mrf.simple.quarantine") }}</h4> + + <p>{{ $t("about.mrf.simple.quarantine_desc") }}</p> + + <ul> + <li + v-for="instance in quarantineInstances" + :key="instance" + v-text="instance" + /> + </ul> + </div> + + <div v-if="ftlRemovalInstances.length"> + <h4>{{ $t("about.mrf.simple.ftl_removal") }}</h4> + + <p>{{ $t("about.mrf.simple.ftl_removal_desc") }}</p> + + <ul> + <li + v-for="instance in ftlRemovalInstances" + :key="instance" + v-text="instance" + /> + </ul> + </div> + + <div v-if="mediaNsfwInstances.length"> + <h4>{{ $t("about.mrf.simple.media_nsfw") }}</h4> + + <p>{{ $t("about.mrf.simple.media_nsfw_desc") }}</p> + + <ul> + <li + v-for="instance in mediaNsfwInstances" + :key="instance" + v-text="instance" + /> + </ul> + </div> + + <div v-if="mediaRemovalInstances.length"> + <h4>{{ $t("about.mrf.simple.media_removal") }}</h4> + + <p>{{ $t("about.mrf.simple.media_removal_desc") }}</p> + + <ul> + <li + v-for="instance in mediaRemovalInstances" + :key="instance" + v-text="instance" + /> + </ul> + </div> + + <h2 v-if="hasKeywordPolicies"> + {{ $t("about.mrf.keyword.keyword_policies") }} + </h2> + + <div v-if="keywordsFtlRemoval.length"> + <h4>{{ $t("about.mrf.keyword.ftl_removal") }}</h4> + + <ul> + <li + v-for="keyword in keywordsFtlRemoval" + :key="keyword" + v-text="keyword" + /> + </ul> + </div> + + <div v-if="keywordsReject.length"> + <h4>{{ $t("about.mrf.keyword.reject") }}</h4> + + <ul> + <li + v-for="keyword in keywordsReject" + :key="keyword" + v-text="keyword" + /> + </ul> + </div> + + <div v-if="keywordsReplace.length"> + <h4>{{ $t("about.mrf.keyword.replace") }}</h4> + + <ul> + <li + v-for="keyword in keywordsReplace" + :key="keyword" + > + {{ keyword.pattern }} + {{ $t("about.mrf.keyword.is_replaced_by") }} + {{ keyword.replacement }} + </li> + </ul> + </div> + </div> + </div> + </div> + </div> +</template> + +<script src="./mrf_transparency_panel.js"></script> + +<style lang="scss"> +.mrf-section { + margin: 1em; +} +</style> diff --git a/src/components/mute_card/mute_card.js b/src/components/mute_card/mute_card.js index 65c9cfb5..cbec0e9b 100644 --- a/src/components/mute_card/mute_card.js +++ b/src/components/mute_card/mute_card.js @@ -11,8 +11,11 @@ const MuteCard = { user () { return this.$store.getters.findUser(this.userId) }, + relationship () { + return this.$store.getters.relationship(this.userId) + }, muted () { - return this.user.muted + return this.relationship.muting } }, components: { @@ -21,13 +24,13 @@ const MuteCard = { methods: { unmuteUser () { this.progress = true - this.$store.dispatch('unmuteUser', this.user.id).then(() => { + this.$store.dispatch('unmuteUser', this.userId).then(() => { this.progress = false }) }, muteUser () { this.progress = true - this.$store.dispatch('muteUser', this.user.id).then(() => { + this.$store.dispatch('muteUser', this.userId).then(() => { this.progress = false }) } diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index aa3f7605..8f7edb7f 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -1,25 +1,18 @@ -import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service' +import { mapState } from 'vuex' const NavPanel = { created () { if (this.currentUser && this.currentUser.locked) { - const store = this.$store - const credentials = store.state.users.currentUser.credentials - - followRequestFetcher.startFetching({ store, credentials }) + this.$store.dispatch('startFetchingFollowRequests') } }, - computed: { - currentUser () { - return this.$store.state.users.currentUser - }, - chat () { - return this.$store.state.chat.channel - }, - followRequestCount () { - return this.$store.state.api.followRequests.length - } - } + computed: mapState({ + currentUser: state => state.users.currentUser, + chat: state => state.chat.channel, + followRequestCount: state => state.api.followRequests.length, + privateMode: state => state.instance.private, + federating: state => state.instance.federating + }) } export default NavPanel diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue index 614fadf4..8cd04dc7 100644 --- a/src/components/nav_panel/nav_panel.vue +++ b/src/components/nav_panel/nav_panel.vue @@ -4,22 +4,22 @@ <ul> <li v-if="currentUser"> <router-link :to="{ name: 'friends' }"> - {{ $t("nav.timeline") }} + <i class="button-icon icon-home-2" /> {{ $t("nav.timeline") }} </router-link> </li> <li v-if="currentUser"> <router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }"> - {{ $t("nav.interactions") }} + <i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }} </router-link> </li> <li v-if="currentUser"> <router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }"> - {{ $t("nav.dms") }} + <i class="button-icon icon-mail-alt" /> {{ $t("nav.dms") }} </router-link> </li> <li v-if="currentUser && currentUser.locked"> <router-link :to="{ name: 'friend-requests' }"> - {{ $t("nav.friend_requests") }} + <i class="button-icon icon-user-plus" /> {{ $t("nav.friend_requests") }} <span v-if="followRequestCount > 0" class="badge follow-request-count" @@ -28,14 +28,19 @@ </span> </router-link> </li> - <li> + <li v-if="currentUser || !privateMode"> <router-link :to="{ name: 'public-timeline' }"> - {{ $t("nav.public_tl") }} + <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }} + </router-link> + </li> + <li v-if="federating && (currentUser || !privateMode)"> + <router-link :to="{ name: 'public-external-timeline' }"> + <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }} </router-link> </li> <li> - <router-link :to="{ name: 'public-external-timeline' }"> - {{ $t("nav.twkn") }} + <router-link :to="{ name: 'about' }"> + <i class="button-icon icon-info-circled" /> {{ $t("nav.about") }} </router-link> </li> </ul> @@ -95,17 +100,33 @@ &:hover { background-color: $fallback--lightBg; - background-color: var(--lightBg, $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(--lightBg, $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; } } } + +.nav-panel .button-icon:before { + width: 1.1em; +} </style> diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 7d46eb5a..5aa40e98 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -1,7 +1,9 @@ +import StatusContent from '../status_content/status_content.vue' import Status from '../status/status.vue' import UserAvatar from '../user_avatar/user_avatar.vue' import UserCard from '../user_card/user_card.vue' import Timeago from '../timeago/timeago.vue' +import { isStatusNotification } from '../../services/notification_utils/notification_utils.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' @@ -15,10 +17,11 @@ const Notification = { }, props: [ 'notification' ], components: { - Status, + StatusContent, UserAvatar, UserCard, - Timeago + Timeago, + Status }, methods: { toggleUserExpanded () { @@ -32,6 +35,24 @@ const Notification = { }, toggleMute () { this.unmuted = !this.unmuted + }, + approveUser () { + this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) + this.$store.dispatch('removeFollowRequest', this.user) + this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id }) + this.$store.dispatch('updateNotification', { + id: this.notification.id, + updater: notification => { + notification.type = 'follow' + } + }) + }, + denyUser () { + this.$store.state.api.backendInteractor.denyUser({ id: this.user.id }) + .then(() => { + this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id }) + this.$store.dispatch('removeFollowRequest', this.user) + }) } }, computed: { @@ -43,20 +64,23 @@ const Notification = { const user = this.notification.from_profile return highlightStyle(highlight[user.screen_name]) }, - userInStore () { - return this.$store.getters.findUser(this.notification.from_profile.id) - }, user () { - if (this.userInStore) { - return this.userInStore - } - return this.notification.from_profile + return this.$store.getters.findUser(this.notification.from_profile.id) }, userProfileLink () { return this.generateUserProfileLink(this.user) }, + targetUser () { + return this.$store.getters.findUser(this.notification.target.id) + }, + targetUserProfileLink () { + return this.generateUserProfileLink(this.targetUser) + }, needMute () { - return this.user.muted + return this.$store.getters.relationship(this.user.id).muting + }, + isStatusNotification () { + return isStatusNotification(this.notification.type) } } } diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index 1f192c77..044ac871 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -40,14 +40,14 @@ <div class="notification-right"> <UserCard v-if="userExpanded" - :user="getUser(notification)" + :user-id="getUser(notification).id" :rounded="true" :bordered="true" /> <span class="notification-details"> <div class="name-and-action"> <!-- eslint-disable vue/no-v-html --> - <span + <bdi v-if="!!notification.from_profile.name_html" class="username" :title="'@'+notification.from_profile.screen_name" @@ -74,20 +74,24 @@ <i class="fa icon-user-plus lit" /> <small>{{ $t('notifications.followed_you') }}</small> </span> - </div> - <div - v-if="notification.type === 'follow'" - class="timeago" - > - <span class="faint"> - <Timeago - :time="notification.created_at" - :auto-update="240" - /> + <span v-if="notification.type === 'follow_request'"> + <i class="fa icon-user lit" /> + <small>{{ $t('notifications.follow_request') }}</small> + </span> + <span v-if="notification.type === 'move'"> + <i class="fa icon-arrow-curved lit" /> + <small>{{ $t('notifications.migrated_to') }}</small> + </span> + <span v-if="notification.type === 'pleroma:emoji_reaction'"> + <small> + <i18n path="notifications.reacted_with"> + <span class="emoji-reaction-emoji">{{ notification.emoji }}</span> + </i18n> + </small> </span> </div> <div - v-else + v-if="isStatusNotification" class="timeago" > <router-link @@ -101,6 +105,17 @@ /> </router-link> </div> + <div + v-else + class="timeago" + > + <span class="faint"> + <Timeago + :time="notification.created_at" + :auto-update="240" + /> + </span> + </div> <a v-if="needMute" href="#" @@ -108,19 +123,43 @@ ><i class="button-icon icon-eye-off" /></a> </span> <div - v-if="notification.type === 'follow'" + v-if="notification.type === 'follow' || notification.type === 'follow_request'" class="follow-text" > - <router-link :to="userProfileLink"> + <router-link + :to="userProfileLink" + class="follow-name" + > @{{ notification.from_profile.screen_name }} </router-link> + <div + v-if="notification.type === 'follow_request'" + style="white-space: nowrap;" + > + <i + class="icon-ok button-icon follow-request-accept" + :title="$t('tool_tip.accept_follow_request')" + @click="approveUser()" + /> + <i + class="icon-cancel button-icon follow-request-reject" + :title="$t('tool_tip.reject_follow_request')" + @click="denyUser()" + /> + </div> + </div> + <div + v-else-if="notification.type === 'move'" + class="move-text" + > + <router-link :to="targetUserProfileLink"> + @{{ notification.target.screen_name }} + </router-link> </div> <template v-else> - <status + <status-content class="faint" - :compact="true" - :statusoid="notification.action" - :no-heading="true" + :status="notification.action" /> </template> </div> diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 6c4054fd..26ffbab6 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -2,10 +2,12 @@ import Notification from '../notification/notification.vue' import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js' import { notificationsFromStore, - visibleNotificationsFromStore, + filteredNotificationsFromStore, unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils.js' +const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30 + const Notifications = { props: { // Disables display of panel header @@ -18,7 +20,11 @@ const Notifications = { }, data () { return { - bottomedOut: false + bottomedOut: false, + // How many seen notifications to display in the list. The more there are, + // the heavier the page becomes. This count is increased when loading + // older notifications, and cut back to default whenever hitting "Read!". + seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT } }, computed: { @@ -34,19 +40,27 @@ const Notifications = { unseenNotifications () { return unseenNotificationsFromStore(this.$store) }, - visibleNotifications () { - return visibleNotificationsFromStore(this.$store, this.filterMode) + filteredNotifications () { + return filteredNotificationsFromStore(this.$store, this.filterMode) }, unseenCount () { return this.unseenNotifications.length }, loading () { return this.$store.state.statuses.notifications.loading + }, + notificationsToDisplay () { + return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount) } }, components: { Notification }, + created () { + const { dispatch } = this.$store + + dispatch('fetchAndUpdateNotifications') + }, watch: { unseenCount (count) { if (count > 0) { @@ -59,12 +73,21 @@ const Notifications = { methods: { markAsSeen () { this.$store.dispatch('markNotificationsAsSeen') + this.seenToDisplayCount = DEFAULT_SEEN_TO_DISPLAY_COUNT }, fetchOlderNotifications () { if (this.loading) { return } + const seenCount = this.filteredNotifications.length - this.unseenCount + if (this.seenToDisplayCount < seenCount) { + this.seenToDisplayCount = Math.min(this.seenToDisplayCount + 20, seenCount) + return + } else if (this.seenToDisplayCount > seenCount) { + this.seenToDisplayCount = seenCount + } + const store = this.$store const credentials = store.state.users.currentUser.credentials store.commit('setNotificationsLoading', { value: true }) @@ -77,6 +100,7 @@ const Notifications = { if (notifs.length === 0) { this.bottomedOut = true } + this.seenToDisplayCount += notifs.length }) } } diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index 71876b14..b675af5a 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -36,6 +36,8 @@ border-bottom: 1px solid; border-color: $fallback--border; border-color: var(--border, $fallback--border); + word-wrap: break-word; + word-break: break-word; &:hover .animated.avatar { canvas { @@ -46,10 +48,6 @@ } } - .muted { - padding: .25em .6em; - } - .non-mention { display: flex; flex: 1; @@ -68,6 +66,9 @@ a { color: var(--faintLink); } + .status-content a { + color: var(--postFaintLink); + } } padding: 0; .media-body { @@ -76,8 +77,38 @@ } } - .follow-text { + .follow-request-accept { + cursor: pointer; + + &:hover { + color: $fallback--text; + color: var(--text, $fallback--text); + } + } + + .follow-request-reject { + cursor: pointer; + + &:hover { + color: $fallback--cRed; + color: var(--cRed, $fallback--cRed); + } + } + + + .follow-text, .move-text { padding: 0.5em 0; + overflow-wrap: break-word; + display: flex; + justify-content: space-between; + + .follow-name { + display: block; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } .status-el { @@ -94,6 +125,10 @@ min-width: 0; } + .emoji-reaction-emoji { + font-size: 16px; + } + .notification-details { min-width: 0px; word-wrap: break-word; @@ -135,6 +170,11 @@ color: var(--cGreen, $fallback--cGreen); } + .icon-user.lit { + color: $fallback--cBlue; + color: var(--cBlue, $fallback--cBlue); + } + .icon-user-plus.lit { color: $fallback--cBlue; color: var(--cBlue, $fallback--cBlue); @@ -151,6 +191,11 @@ color: var(--cOrange, $fallback--cOrange); } + .icon-arrow-curved.lit { + color: $fallback--cBlue; + color: var(--cBlue, $fallback--cBlue); + } + .status-content { margin: 0; max-height: 300px; diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index c42c35e6..d477a41b 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -32,7 +32,7 @@ </div> <div class="panel-body"> <div - v-for="notification in visibleNotifications" + v-for="notification in notificationsToDisplay" :key="notification.id" class="notification" :class="{"unseen": !minimalMode && !notification.seen}" diff --git a/src/components/opacity_input/opacity_input.vue b/src/components/opacity_input/opacity_input.vue index c677f18c..3cc3942b 100644 --- a/src/components/opacity_input/opacity_input.vue +++ b/src/components/opacity_input/opacity_input.vue @@ -9,18 +9,12 @@ > {{ $t('settings.style.common.opacity') }} </label> - <input + <Checkbox v-if="typeof fallback !== 'undefined'" - :id="name + '-o'" - class="opt exclude-disabled" - type="checkbox" :checked="present" - @input="$emit('input', !present ? fallback : undefined)" - > - <label - v-if="typeof fallback !== 'undefined'" - class="opt-l" - :for="name + '-o'" + :disabled="disabled" + class="opt" + @change="$emit('input', !present ? fallback : undefined)" /> <input :id="name" @@ -37,7 +31,11 @@ </template> <script> +import Checkbox from '../checkbox/checkbox.vue' export default { + components: { + Checkbox + }, props: [ 'name', 'value', 'fallback', 'disabled' ], diff --git a/src/components/panel_loading/panel_loading.vue b/src/components/panel_loading/panel_loading.vue new file mode 100644 index 00000000..4efebb3c --- /dev/null +++ b/src/components/panel_loading/panel_loading.vue @@ -0,0 +1,29 @@ +<template> + <div class="panel-loading"> + <span class="loading-text"> + <i class="icon-spin4 animate-spin" /> + {{ $t('general.loading') }} + </span> + </div> +</template> + +<style lang="scss"> +@import 'src/_variables.scss'; + +.panel-loading { + display: flex; + height: 100%; + align-items: center; + justify-content: center; + font-size: 2em; + color: $fallback--text; + color: var(--text, $fallback--text); + .loading-text i { + font-size: 3em; + line-height: 0; + vertical-align: middle; + color: $fallback--text; + color: var(--text, $fallback--text); + } +} +</style> diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue index db8e33b3..56e91cca 100644 --- a/src/components/poll/poll.vue +++ b/src/components/poll/poll.vue @@ -104,8 +104,10 @@ .result-fill { height: 100%; position: absolute; + color: $fallback--text; + color: var(--pollText, $fallback--text); background-color: $fallback--lightBg; - background-color: var(--linkBg, $fallback--lightBg); + background-color: var(--poll, $fallback--lightBg); border-radius: $fallback--panelRadius; border-radius: var(--panelRadius, $fallback--panelRadius); top: 0; diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js new file mode 100644 index 00000000..5881d266 --- /dev/null +++ b/src/components/popover/popover.js @@ -0,0 +1,156 @@ + +const Popover = { + name: 'Popover', + props: { + // Action to trigger popover: either 'hover' or 'click' + trigger: String, + // Either 'top' or 'bottom' + placement: String, + // Takes object with properties 'x' and 'y', values of these can be + // 'container' for using offsetParent as boundaries for either axis + // or 'viewport' + boundTo: Object, + // Takes a top/bottom/left/right object, how much space to leave + // between boundary and popover element + margin: Object, + // Takes a x/y object and tells how many pixels to offset from + // anchor point on either axis + offset: Object, + // Additional styles you may want for the popover container + popoverClass: String + }, + data () { + return { + hidden: true, + styles: { opacity: 0 }, + oldSize: { width: 0, height: 0 } + } + }, + methods: { + updateStyles () { + if (this.hidden) { + this.styles = { + opacity: 0 + } + return + } + + // Popover will be anchored around this element, trigger ref is the container, so + // its children are what are inside the slot. Expect only one slot="trigger". + const anchorEl = (this.$refs.trigger && this.$refs.trigger.children[0]) || this.$el + const screenBox = anchorEl.getBoundingClientRect() + // Screen position of the origin point for popover + const origin = { x: screenBox.left + screenBox.width * 0.5, y: screenBox.top } + const content = this.$refs.content + // Minor optimization, don't call a slow reflow call if we don't have to + const parentBounds = this.boundTo && + (this.boundTo.x === 'container' || this.boundTo.y === 'container') && + this.$el.offsetParent.getBoundingClientRect() + const margin = this.margin || {} + + // What are the screen bounds for the popover? Viewport vs container + // when using viewport, using default margin values to dodge the navbar + const xBounds = this.boundTo && this.boundTo.x === 'container' ? { + min: parentBounds.left + (margin.left || 0), + max: parentBounds.right - (margin.right || 0) + } : { + min: 0 + (margin.left || 10), + max: window.innerWidth - (margin.right || 10) + } + + const yBounds = this.boundTo && this.boundTo.y === 'container' ? { + min: parentBounds.top + (margin.top || 0), + max: parentBounds.bottom - (margin.bottom || 0) + } : { + min: 0 + (margin.top || 50), + max: window.innerHeight - (margin.bottom || 5) + } + + let horizOffset = 0 + + // If overflowing from left, move it so that it doesn't + if ((origin.x - content.offsetWidth * 0.5) < xBounds.min) { + horizOffset += -(origin.x - content.offsetWidth * 0.5) + xBounds.min + } + + // If overflowing from right, move it so that it doesn't + if ((origin.x + horizOffset + content.offsetWidth * 0.5) > xBounds.max) { + horizOffset -= (origin.x + horizOffset + content.offsetWidth * 0.5) - xBounds.max + } + + // Default to whatever user wished with placement prop + let usingTop = this.placement !== 'bottom' + + // Handle special cases, first force to displaying on top if there's not space on bottom, + // regardless of what placement value was. Then check if there's not space on top, and + // force to bottom, again regardless of what placement value was. + if (origin.y + content.offsetHeight > yBounds.max) usingTop = true + if (origin.y - content.offsetHeight < yBounds.min) usingTop = false + + const yOffset = (this.offset && this.offset.y) || 0 + const translateY = usingTop + ? -anchorEl.offsetHeight - yOffset - content.offsetHeight + : yOffset + + const xOffset = (this.offset && this.offset.x) || 0 + const translateX = (anchorEl.offsetWidth * 0.5) - content.offsetWidth * 0.5 + horizOffset + xOffset + + // Note, separate translateX and translateY avoids blurry text on chromium, + // single translate or translate3d resulted in blurry text. + this.styles = { + opacity: 1, + transform: `translateX(${Math.floor(translateX)}px) translateY(${Math.floor(translateY)}px)` + } + }, + showPopover () { + if (this.hidden) this.$emit('show') + this.hidden = false + this.$nextTick(this.updateStyles) + }, + hidePopover () { + if (!this.hidden) this.$emit('close') + this.hidden = true + this.styles = { opacity: 0 } + }, + onMouseenter (e) { + if (this.trigger === 'hover') this.showPopover() + }, + onMouseleave (e) { + if (this.trigger === 'hover') this.hidePopover() + }, + onClick (e) { + if (this.trigger === 'click') { + if (this.hidden) { + this.showPopover() + } else { + this.hidePopover() + } + } + }, + onClickOutside (e) { + if (this.hidden) return + if (this.$el.contains(e.target)) return + this.hidePopover() + } + }, + updated () { + // Monitor changes to content size, update styles only when content sizes have changed, + // that should be the only time we need to move the popover box if we don't care about scroll + // or resize + const content = this.$refs.content + if (!content) return + if (this.oldSize.width !== content.offsetWidth || this.oldSize.height !== content.offsetHeight) { + this.updateStyles() + this.oldSize = { width: content.offsetWidth, height: content.offsetHeight } + } + }, + created () { + document.addEventListener('click', this.onClickOutside) + }, + destroyed () { + document.removeEventListener('click', this.onClickOutside) + this.hidePopover() + } +} + +export default Popover diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue new file mode 100644 index 00000000..a271cb1b --- /dev/null +++ b/src/components/popover/popover.vue @@ -0,0 +1,118 @@ +<template> + <div + @mouseenter="onMouseenter" + @mouseleave="onMouseleave" + > + <div + ref="trigger" + @click="onClick" + > + <slot name="trigger" /> + </div> + <div + v-if="!hidden" + ref="content" + :style="styles" + class="popover" + :class="popoverClass" + > + <slot + name="content" + class="popover-inner" + :close="hidePopover" + /> + </div> + </div> +</template> + +<script src="./popover.js" /> + +<style lang=scss> +@import '../../_variables.scss'; + +.popover { + z-index: 8; + position: absolute; + min-width: 0; + transition: opacity 0.3s; + + box-shadow: 1px 1px 4px rgba(0,0,0,.6); + box-shadow: var(--panelShadow); + 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); +} + +.dropdown-menu { + display: block; + padding: .5rem 0; + font-size: 1rem; + text-align: left; + list-style: none; + max-width: 100vw; + z-index: 10; + white-space: nowrap; + + .dropdown-divider { + height: 0; + margin: .5rem 0; + overflow: hidden; + border-top: 1px solid $fallback--border; + border-top: 1px solid var(--border, $fallback--border); + } + + .dropdown-item { + line-height: 21px; + margin-right: 5px; + overflow: auto; + display: block; + padding: .25rem 1.0rem .25rem 1.5rem; + clear: both; + font-weight: 400; + text-align: inherit; + white-space: nowrap; + border: none; + border-radius: 0px; + background-color: transparent; + box-shadow: none; + width: 100%; + height: 100%; + + --btnText: var(--popoverText, $fallback--text); + + &-icon { + padding-left: 0.5rem; + + i { + margin-right: 0.25rem; + color: var(--menuPopoverIcon, $fallback--icon) + } + } + + &:active, &:hover { + background-color: $fallback--lightBg; + background-color: var(--selectedMenuPopover, $fallback--lightBg); + color: $fallback--link; + color: var(--selectedMenuPopoverText, $fallback--link); + --faint: var(--selectedMenuPopoverFaintText, $fallback--faint); + --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint); + --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText); + --icon: var(--selectedMenuPopoverIcon, $fallback--icon); + i { + color: var(--selectedMenuPopoverIcon, $fallback--icon); + } + } + + } +} +</style> diff --git a/src/components/popper/popper.scss b/src/components/popper/popper.scss deleted file mode 100644 index 06daa871..00000000 --- a/src/components/popper/popper.scss +++ /dev/null @@ -1,147 +0,0 @@ -@import '../../_variables.scss'; - -.tooltip.popover { - z-index: 8; - - .popover-inner { - box-shadow: 1px 1px 4px rgba(0,0,0,.6); - box-shadow: var(--panelShadow); - border-radius: $fallback--btnRadius; - border-radius: var(--btnRadius, $fallback--btnRadius); - background-color: $fallback--bg; - background-color: var(--bg, $fallback--bg); - } - - .popover-arrow { - width: 0; - height: 0; - border-style: solid; - position: absolute; - margin: 5px; - border-color: $fallback--bg; - border-color: var(--bg, $fallback--bg); - } - - &[x-placement^="top"] { - margin-bottom: 5px; - - .popover-arrow { - border-width: 5px 5px 0 5px; - border-left-color: transparent !important; - border-right-color: transparent !important; - border-bottom-color: transparent !important; - bottom: -4px; - left: calc(50% - 5px); - margin-top: 0; - margin-bottom: 0; - } - } - - &[x-placement^="bottom"] { - margin-top: 5px; - - .popover-arrow { - border-width: 0 5px 5px 5px; - border-left-color: transparent !important; - border-right-color: transparent !important; - border-top-color: transparent !important; - top: -4px; - left: calc(50% - 5px); - margin-top: 0; - margin-bottom: 0; - } - } - - &[x-placement^="right"] { - margin-left: 5px; - - .popover-arrow { - border-width: 5px 5px 5px 0; - border-left-color: transparent !important; - border-top-color: transparent !important; - border-bottom-color: transparent !important; - left: -4px; - top: calc(50% - 5px); - margin-left: 0; - margin-right: 0; - } - } - - &[x-placement^="left"] { - margin-right: 5px; - - .popover-arrow { - border-width: 5px 0 5px 5px; - border-top-color: transparent !important; - border-right-color: transparent !important; - border-bottom-color: transparent !important; - right: -4px; - top: calc(50% - 5px); - margin-left: 0; - margin-right: 0; - } - } - - &[aria-hidden='true'] { - visibility: hidden; - opacity: 0; - transition: opacity .15s, visibility .15s; - } - - &[aria-hidden='false'] { - visibility: visible; - opacity: 1; - transition: opacity .15s; - } -} - -.dropdown-menu { - display: block; - padding: .5rem 0; - font-size: 1rem; - text-align: left; - list-style: none; - max-width: 100vw; - z-index: 10; - - .dropdown-divider { - height: 0; - margin: .5rem 0; - overflow: hidden; - border-top: 1px solid $fallback--border; - border-top: 1px solid var(--border, $fallback--border); - } - - .dropdown-item { - line-height: 21px; - margin-right: 5px; - overflow: auto; - display: block; - padding: .25rem 1.0rem .25rem 1.5rem; - clear: both; - font-weight: 400; - text-align: inherit; - white-space: normal; - border: none; - border-radius: 0px; - background-color: transparent; - box-shadow: none; - width: 100%; - height: 100%; - - &-icon { - padding-left: 0.5rem; - - i { - margin-right: 0.25rem; - } - } - - &:hover { - // TODO: improve the look on breeze themes - background-color: $fallback--fg; - background-color: var(--btn, $fallback--fg); - box-shadow: none; - } - } -} diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index af6299e4..9027566f 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -82,7 +82,9 @@ const PostStatusForm = { contentType }, caret: 0, - pollFormVisible: false + pollFormVisible: false, + showDropIcon: 'hide', + dropStopTimeout: null } }, computed: { @@ -102,7 +104,7 @@ const PostStatusForm = { ...this.$store.state.instance.customEmoji ], users: this.$store.state.users.users, - updateUsersList: (input) => this.$store.dispatch('searchUsers', input) + updateUsersList: (query) => this.$store.dispatch('searchUsers', { query }) }) }, emojiSuggestor () { @@ -169,9 +171,7 @@ const PostStatusForm = { if (this.submitDisabled) { return } if (this.newStatus.status === '') { - if (this.newStatus.files.length > 0) { - this.newStatus.status = '\u200b' // hack - } else { + if (this.newStatus.files.length === 0) { this.error = 'Cannot post an empty status with no files' return } @@ -220,7 +220,6 @@ const PostStatusForm = { }, addMediaFile (fileInfo) { this.newStatus.files.push(fileInfo) - this.enableSubmit() }, removeMediaFile (fileInfo) { let index = this.newStatus.files.indexOf(fileInfo) @@ -229,7 +228,6 @@ const PostStatusForm = { uploadFailed (errString, templateArgs) { templateArgs = templateArgs || {} this.error = this.$t('upload.error.base') + ' ' + this.$t('upload.error.' + errString, templateArgs) - this.enableSubmit() }, disableSubmit () { this.submitDisabled = true @@ -252,13 +250,27 @@ const PostStatusForm = { } }, fileDrop (e) { - if (e.dataTransfer.files.length > 0) { + if (e.dataTransfer && e.dataTransfer.types.includes('Files')) { e.preventDefault() // allow dropping text like before this.dropFiles = e.dataTransfer.files + clearTimeout(this.dropStopTimeout) + this.showDropIcon = 'hide' } }, + fileDragStop (e) { + // The false-setting is done with delay because just using leave-events + // directly caused unwanted flickering, this is not perfect either but + // much less noticable. + clearTimeout(this.dropStopTimeout) + this.showDropIcon = 'fade' + this.dropStopTimeout = setTimeout(() => (this.showDropIcon = 'hide'), 500) + }, fileDrag (e) { e.dataTransfer.dropEffect = 'copy' + if (e.dataTransfer && e.dataTransfer.types.includes('Files')) { + clearTimeout(this.dropStopTimeout) + this.showDropIcon = 'show' + } }, onEmojiInputInput (e) { this.$nextTick(() => { diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index 0094b1aa..c4d7f7e2 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -6,7 +6,15 @@ <form autocomplete="off" @submit.prevent="postStatus(newStatus)" + @dragover.prevent="fileDrag" > + <div + v-show="showDropIcon !== 'hide'" + :style="{ animation: showDropIcon === 'show' ? 'fade-in 0.25s' : 'fade-out 0.5s' }" + class="drop-indicator icon-upload" + @dragleave="fileDragStop" + @drop.stop="fileDrop" + /> <div class="form-group"> <i18n v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'" @@ -96,9 +104,7 @@ :disabled="posting" class="form-post-body" @keydown.meta.enter="postStatus(newStatus)" - @keyup.ctrl.enter="postStatus(newStatus)" - @drop="fileDrop" - @dragover.prevent="fileDrag" + @keydown.ctrl.enter="postStatus(newStatus)" @input="resize" @compositionupdate="resize" @paste="paste" @@ -172,6 +178,7 @@ @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="uploadFailed" + @all-uploaded="enableSubmit" /> <div class="emoji-icon" @@ -339,11 +346,6 @@ font-size: 26px; flex: 1; - i { - display: block; - width: 100%; - } - &.selected, &:hover { // needs to be specific to override icon default color i, label { @@ -451,7 +453,8 @@ form { display: flex; flex-direction: column; - padding: 0.6em; + margin: 0.6em; + position: relative; } .form-group { @@ -509,5 +512,35 @@ cursor: pointer; z-index: 4; } + + @keyframes fade-in { + from { opacity: 0; } + to { opacity: 0.6; } + } + + @keyframes fade-out { + from { opacity: 0.6; } + to { opacity: 0; } + } + + .drop-indicator { + position: absolute; + z-index: 1; + 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> diff --git a/src/components/post_status_modal/post_status_modal.js b/src/components/post_status_modal/post_status_modal.js index b44354db..be945400 100644 --- a/src/components/post_status_modal/post_status_modal.js +++ b/src/components/post_status_modal/post_status_modal.js @@ -13,9 +13,6 @@ const PostStatusModal = { } }, computed: { - isLoggedIn () { - return !!this.$store.state.users.currentUser - }, modalActivated () { return this.$store.state.postStatus.modalActivated }, diff --git a/src/components/post_status_modal/post_status_modal.vue b/src/components/post_status_modal/post_status_modal.vue index dbcd321e..07c58f74 100644 --- a/src/components/post_status_modal/post_status_modal.vue +++ b/src/components/post_status_modal/post_status_modal.vue @@ -1,6 +1,5 @@ <template> <Modal - v-if="isLoggedIn && !resettingForm" :is-open="modalActivated" class="post-form-modal-view" @backdropClicked="closeModal" diff --git a/src/components/public_and_external_timeline/public_and_external_timeline.js b/src/components/public_and_external_timeline/public_and_external_timeline.js index f614c13b..cbd4491b 100644 --- a/src/components/public_and_external_timeline/public_and_external_timeline.js +++ b/src/components/public_and_external_timeline/public_and_external_timeline.js @@ -10,7 +10,7 @@ const PublicAndExternalTimeline = { this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' }) }, destroyed () { - this.$store.dispatch('stopFetching', 'publicAndExternal') + this.$store.dispatch('stopFetchingTimeline', 'publicAndExternal') } } diff --git a/src/components/public_timeline/public_timeline.js b/src/components/public_timeline/public_timeline.js index 8976a99c..66c40d3a 100644 --- a/src/components/public_timeline/public_timeline.js +++ b/src/components/public_timeline/public_timeline.js @@ -10,7 +10,7 @@ const PublicTimeline = { this.$store.dispatch('startFetchingTimeline', { timeline: 'public' }) }, destroyed () { - this.$store.dispatch('stopFetching', 'public') + this.$store.dispatch('stopFetchingTimeline', 'public') } } diff --git a/src/components/range_input/range_input.vue b/src/components/range_input/range_input.vue index aaa2ed26..5857a5c1 100644 --- a/src/components/range_input/range_input.vue +++ b/src/components/range_input/range_input.vue @@ -12,7 +12,7 @@ <input v-if="typeof fallback !== 'undefined'" :id="name + '-o'" - class="opt exclude-disabled" + class="opt" type="checkbox" :checked="present" @input="$emit('input', !present ? fallback : undefined)" diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js new file mode 100644 index 00000000..f0931446 --- /dev/null +++ b/src/components/react_button/react_button.js @@ -0,0 +1,39 @@ +import Popover from '../popover/popover.vue' +import { mapGetters } from 'vuex' + +const ReactButton = { + props: ['status'], + data () { + return { + filterWord: '' + } + }, + components: { + Popover + }, + methods: { + addReaction (event, emoji, close) { + const existingReaction = this.status.emoji_reactions.find(r => r.name === emoji) + if (existingReaction && existingReaction.me) { + this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji }) + } else { + this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji }) + } + close() + } + }, + computed: { + commonEmojis () { + return ['👍', '😠', '👀', '😂', '🔥'] + }, + emojis () { + if (this.filterWord !== '') { + return this.$store.state.instance.emoji.filter(emoji => emoji.displayText.includes(this.filterWord)) + } + return this.$store.state.instance.emoji || [] + }, + ...mapGetters(['mergedConfig']) + } +} + +export default ReactButton diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue new file mode 100644 index 00000000..0b34add1 --- /dev/null +++ b/src/components/react_button/react_button.vue @@ -0,0 +1,110 @@ +<template> + <Popover + trigger="click" + placement="top" + :offset="{ y: 5 }" + class="react-button-popover" + > + <div + slot="content" + slot-scope="{close}" + > + <div class="reaction-picker-filter"> + <input + v-model="filterWord" + :placeholder="$t('emoji.search_emoji')" + > + </div> + <div class="reaction-picker"> + <span + v-for="emoji in commonEmojis" + :key="emoji" + class="emoji-button" + @click="addReaction($event, emoji, close)" + > + {{ emoji }} + </span> + <div class="reaction-picker-divider" /> + <span + v-for="(emoji, key) in emojis" + :key="key" + class="emoji-button" + @click="addReaction($event, emoji.replacement, close)" + > + {{ emoji.replacement }} + </span> + <div class="reaction-bottom-fader" /> + </div> + </div> + <i + slot="trigger" + class="icon-smile button-icon add-reaction-button" + :title="$t('tool_tip.add_reaction')" + /> + </Popover> +</template> + +<script src="./react_button.js" ></script> + +<style lang="scss"> +@import '../../_variables.scss'; + +.reaction-picker-filter { + padding: 0.5em; + display: flex; + input { + flex: 1; + } +} + +.reaction-picker-divider { + height: 1px; + width: 100%; + margin: 0.5em; + background-color: var(--border, $fallback--border); +} + +.reaction-picker { + width: 10em; + height: 9em; + font-size: 1.5em; + overflow-y: scroll; + display: flex; + flex-wrap: wrap; + padding: 0.5em; + text-align: center; + align-content: flex-start; + user-select: none; + + mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat, + linear-gradient(to bottom, white 0, transparent 100%) top no-repeat, + linear-gradient(to top, white, white); + transition: mask-size 150ms; + mask-size: 100% 20px, 100% 20px, auto; + // Autoprefixed seem to ignore this one, and also syntax is different + -webkit-mask-composite: xor; + mask-composite: exclude; + + .emoji-button { + cursor: pointer; + + flex-basis: 20%; + line-height: 1.5em; + align-content: center; + + &:hover { + transform: scale(1.25); + } + } +} + +.add-reaction-button { + cursor: pointer; + + &:hover { + color: $fallback--text; + color: var(--text, $fallback--text); + } +} + +</style> diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js index 57f3caf0..dab06e1e 100644 --- a/src/components/registration/registration.js +++ b/src/components/registration/registration.js @@ -1,5 +1,5 @@ import { validationMixin } from 'vuelidate' -import { required, sameAs } from 'vuelidate/lib/validators' +import { required, requiredIf, sameAs } from 'vuelidate/lib/validators' import { mapActions, mapState } from 'vuex' const registration = { @@ -14,15 +14,17 @@ const registration = { }, captcha: {} }), - validations: { - user: { - email: { required }, - username: { required }, - fullname: { required }, - password: { required }, - confirm: { - required, - sameAsPassword: sameAs('password') + validations () { + return { + user: { + email: { required: requiredIf(() => this.accountActivationRequired) }, + username: { required }, + fullname: { required }, + password: { required }, + confirm: { + required, + sameAsPassword: sameAs('password') + } } } }, @@ -43,7 +45,8 @@ const registration = { signedIn: (state) => !!state.users.currentUser, isPending: (state) => state.users.signUpPending, serverValidationErrors: (state) => state.users.signUpErrors, - termsOfService: (state) => state.instance.tos + termsOfService: (state) => state.instance.tos, + accountActivationRequired: (state) => state.instance.accountActivationRequired }) }, methods: { @@ -63,7 +66,8 @@ const registration = { await this.signUp(this.user) this.$router.push({ name: 'friends' }) } catch (error) { - console.warn('Registration failed: ' + error) + console.warn('Registration failed: ', error) + this.setCaptcha() } } }, diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue index 5bb06a4f..a83ca1e5 100644 --- a/src/components/registration/registration.vue +++ b/src/components/registration/registration.vue @@ -170,9 +170,9 @@ <label class="form--label" for="captcha-label" - >{{ $t('captcha') }}</label> + >{{ $t('registration.captcha') }}</label> - <template v-if="captcha.type == 'kocaptcha'"> + <template v-if="['kocaptcha', 'native'].includes(captcha.type)"> <img :src="captcha.url" @click="setCaptcha" @@ -187,6 +187,9 @@ class="form-control" type="text" autocomplete="off" + autocorrect="off" + autocapitalize="off" + spellcheck="false" > </template> </div> diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue index d9ec7ece..a9bb12a1 100644 --- a/src/components/selectable_list/selectable_list.vue +++ b/src/components/selectable_list/selectable_list.vue @@ -68,7 +68,12 @@ &-item-selected-inner { background-color: $fallback--lightBg; - background-color: var(--lightBg, $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 { diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js deleted file mode 100644 index c49083f9..00000000 --- a/src/components/settings/settings.js +++ /dev/null @@ -1,112 +0,0 @@ -/* eslint-env browser */ -import { filter, trim } from 'lodash' - -import TabSwitcher from '../tab_switcher/tab_switcher.js' -import StyleSwitcher from '../style_switcher/style_switcher.vue' -import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue' -import { extractCommit } from '../../services/version/version.service' -import { instanceDefaultProperties, defaultState as configDefaultState } from '../../modules/config.js' -import Checkbox from '../checkbox/checkbox.vue' - -const pleromaFeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma-fe/commit/' -const pleromaBeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma/commit/' - -const multiChoiceProperties = [ - 'postContentType', - 'subjectLineBehavior' -] - -const settings = { - data () { - const instance = this.$store.state.instance - - return { - loopSilentAvailable: - // Firefox - Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') || - // Chrome-likes - Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') || - // Future spec, still not supported in Nightly 63 as of 08/2018 - Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks'), - - backendVersion: instance.backendVersion, - frontendVersion: instance.frontendVersion - } - }, - components: { - TabSwitcher, - StyleSwitcher, - InterfaceLanguageSwitcher, - Checkbox - }, - computed: { - user () { - return this.$store.state.users.currentUser - }, - currentSaveStateNotice () { - return this.$store.state.interface.settings.currentSaveStateNotice - }, - postFormats () { - return this.$store.state.instance.postFormats || [] - }, - instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel }, - frontendVersionLink () { - return pleromaFeCommitUrl + this.frontendVersion - }, - backendVersionLink () { - return pleromaBeCommitUrl + extractCommit(this.backendVersion) - }, - // Getting localized values for instance-default properties - ...instanceDefaultProperties - .filter(key => multiChoiceProperties.includes(key)) - .map(key => [ - key + 'DefaultValue', - function () { - return this.$store.getters.instanceDefaultConfig[key] - } - ]) - .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), - ...instanceDefaultProperties - .filter(key => !multiChoiceProperties.includes(key)) - .map(key => [ - key + 'LocalizedValue', - function () { - return this.$t('settings.values.' + this.$store.getters.instanceDefaultConfig[key]) - } - ]) - .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), - // Generating computed values for vuex properties - ...Object.keys(configDefaultState) - .map(key => [key, { - get () { return this.$store.getters.mergedConfig[key] }, - set (value) { - this.$store.dispatch('setOption', { name: key, value }) - } - }]) - .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), - // Special cases (need to transform values) - muteWordsString: { - get () { return this.$store.getters.mergedConfig.muteWords.join('\n') }, - set (value) { - this.$store.dispatch('setOption', { - name: 'muteWords', - value: filter(value.split('\n'), (word) => trim(word).length > 0) - }) - } - } - }, - // Updating nested properties - watch: { - notificationVisibility: { - handler (value) { - this.$store.dispatch('setOption', { - name: 'notificationVisibility', - value: this.$store.getters.mergedConfig.notificationVisibility - }) - }, - deep: true - } - } -} - -export default settings diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue deleted file mode 100644 index a83489d2..00000000 --- a/src/components/settings/settings.vue +++ /dev/null @@ -1,389 +0,0 @@ -<template> - <div class="settings panel panel-default"> - <div class="panel-heading"> - <div class="title"> - {{ $t('settings.settings') }} - </div> - - <transition name="fade"> - <template v-if="currentSaveStateNotice"> - <div - v-if="currentSaveStateNotice.error" - class="alert error" - @click.prevent - > - {{ $t('settings.saving_err') }} - </div> - - <div - v-if="!currentSaveStateNotice.error" - class="alert transparent" - @click.prevent - > - {{ $t('settings.saving_ok') }} - </div> - </template> - </transition> - </div> - <div class="panel-body"> - <keep-alive> - <tab-switcher> - <div :label="$t('settings.general')"> - <div class="setting-item"> - <h2>{{ $t('settings.interface') }}</h2> - <ul class="setting-list"> - <li> - <interface-language-switcher /> - </li> - <li v-if="instanceSpecificPanelPresent"> - <Checkbox v-model="hideISP"> - {{ $t('settings.hide_isp') }} - </Checkbox> - </li> - </ul> - </div> - <div class="setting-item"> - <h2>{{ $t('nav.timeline') }}</h2> - <ul class="setting-list"> - <li> - <Checkbox v-model="hideMutedPosts"> - {{ $t('settings.hide_muted_posts') }} {{ $t('settings.instance_default', { value: hideMutedPostsLocalizedValue }) }} - </Checkbox> - </li> - <li> - <Checkbox v-model="collapseMessageWithSubject"> - {{ $t('settings.collapse_subject') }} {{ $t('settings.instance_default', { value: collapseMessageWithSubjectLocalizedValue }) }} - </Checkbox> - </li> - <li> - <Checkbox v-model="streaming"> - {{ $t('settings.streaming') }} - </Checkbox> - <ul - class="setting-list suboptions" - :class="[{disabled: !streaming}]" - > - <li> - <Checkbox - v-model="pauseOnUnfocused" - :disabled="!streaming" - > - {{ $t('settings.pause_on_unfocused') }} - </Checkbox> - </li> - </ul> - </li> - <li> - <Checkbox v-model="autoLoad"> - {{ $t('settings.autoload') }} - </Checkbox> - </li> - <li> - <Checkbox v-model="hoverPreview"> - {{ $t('settings.reply_link_preview') }} - </Checkbox> - </li> - </ul> - </div> - - <div class="setting-item"> - <h2>{{ $t('settings.composing') }}</h2> - <ul class="setting-list"> - <li> - <Checkbox v-model="scopeCopy"> - {{ $t('settings.scope_copy') }} {{ $t('settings.instance_default', { value: scopeCopyLocalizedValue }) }} - </Checkbox> - </li> - <li> - <Checkbox v-model="alwaysShowSubjectInput"> - {{ $t('settings.subject_input_always_show') }} {{ $t('settings.instance_default', { value: alwaysShowSubjectInputLocalizedValue }) }} - </Checkbox> - </li> - <li> - <div> - {{ $t('settings.subject_line_behavior') }} - <label - for="subjectLineBehavior" - class="select" - > - <select - id="subjectLineBehavior" - v-model="subjectLineBehavior" - > - <option value="email"> - {{ $t('settings.subject_line_email') }} - {{ subjectLineBehaviorDefaultValue == 'email' ? $t('settings.instance_default_simple') : '' }} - </option> - <option value="masto"> - {{ $t('settings.subject_line_mastodon') }} - {{ subjectLineBehaviorDefaultValue == 'mastodon' ? $t('settings.instance_default_simple') : '' }} - </option> - <option value="noop"> - {{ $t('settings.subject_line_noop') }} - {{ subjectLineBehaviorDefaultValue == 'noop' ? $t('settings.instance_default_simple') : '' }} - </option> - </select> - <i class="icon-down-open" /> - </label> - </div> - </li> - <li v-if="postFormats.length > 0"> - <div> - {{ $t('settings.post_status_content_type') }} - <label - for="postContentType" - class="select" - > - <select - id="postContentType" - v-model="postContentType" - > - <option - v-for="postFormat in postFormats" - :key="postFormat" - :value="postFormat" - > - {{ $t(`post_status.content_type["${postFormat}"]`) }} - {{ postContentTypeDefaultValue === postFormat ? $t('settings.instance_default_simple') : '' }} - </option> - </select> - <i class="icon-down-open" /> - </label> - </div> - </li> - <li> - <Checkbox v-model="minimalScopesMode"> - {{ $t('settings.minimal_scopes_mode') }} {{ $t('settings.instance_default', { value: minimalScopesModeLocalizedValue }) }} - </Checkbox> - </li> - <li> - <Checkbox v-model="autohideFloatingPostButton"> - {{ $t('settings.autohide_floating_post_button') }} - </Checkbox> - </li> - <li> - <Checkbox v-model="padEmoji"> - {{ $t('settings.pad_emoji') }} - </Checkbox> - </li> - </ul> - </div> - - <div class="setting-item"> - <h2>{{ $t('settings.attachments') }}</h2> - <ul class="setting-list"> - <li> - <Checkbox v-model="hideAttachments"> - {{ $t('settings.hide_attachments_in_tl') }} - </Checkbox> - </li> - <li> - <Checkbox v-model="hideAttachmentsInConv"> - {{ $t('settings.hide_attachments_in_convo') }} - </Checkbox> - </li> - <li> - <label for="maxThumbnails"> - {{ $t('settings.max_thumbnails') }} - </label> - <input - id="maxThumbnails" - v-model.number="maxThumbnails" - class="number-input" - type="number" - min="0" - step="1" - > - </li> - <li> - <Checkbox v-model="hideNsfw"> - {{ $t('settings.nsfw_clickthrough') }} - </Checkbox> - </li> - <ul class="setting-list suboptions"> - <li> - <Checkbox - v-model="preloadImage" - :disabled="!hideNsfw" - > - {{ $t('settings.preload_images') }} - </Checkbox> - </li> - <li> - <Checkbox - v-model="useOneClickNsfw" - :disabled="!hideNsfw" - > - {{ $t('settings.use_one_click_nsfw') }} - </Checkbox> - </li> - </ul> - <li> - <Checkbox v-model="stopGifs"> - {{ $t('settings.stop_gifs') }} - </Checkbox> - </li> - <li> - <Checkbox v-model="loopVideo"> - {{ $t('settings.loop_video') }} - </Checkbox> - <ul - class="setting-list suboptions" - :class="[{disabled: !streaming}]" - > - <li> - <Checkbox - v-model="loopVideoSilentOnly" - :disabled="!loopVideo || !loopSilentAvailable" - > - {{ $t('settings.loop_video_silent_only') }} - </Checkbox> - <div - v-if="!loopSilentAvailable" - class="unavailable" - > - <i class="icon-globe" />! {{ $t('settings.limited_availability') }} - </div> - </li> - </ul> - </li> - <li> - <Checkbox v-model="playVideosInModal"> - {{ $t('settings.play_videos_in_modal') }} - </Checkbox> - </li> - <li> - <Checkbox v-model="useContainFit"> - {{ $t('settings.use_contain_fit') }} - </Checkbox> - </li> - </ul> - </div> - - <div class="setting-item"> - <h2>{{ $t('settings.notifications') }}</h2> - <ul class="setting-list"> - <li> - <Checkbox v-model="webPushNotifications"> - {{ $t('settings.enable_web_push_notifications') }} - </Checkbox> - </li> - </ul> - </div> - </div> - - <div :label="$t('settings.theme')"> - <div class="setting-item"> - <style-switcher /> - </div> - </div> - - <div :label="$t('settings.filtering')"> - <div class="setting-item"> - <div class="select-multiple"> - <span class="label">{{ $t('settings.notification_visibility') }}</span> - <ul class="option-list"> - <li> - <Checkbox v-model="notificationVisibility.likes"> - {{ $t('settings.notification_visibility_likes') }} - </Checkbox> - </li> - <li> - <Checkbox v-model="notificationVisibility.repeats"> - {{ $t('settings.notification_visibility_repeats') }} - </Checkbox> - </li> - <li> - <Checkbox v-model="notificationVisibility.follows"> - {{ $t('settings.notification_visibility_follows') }} - </Checkbox> - </li> - <li> - <Checkbox v-model="notificationVisibility.mentions"> - {{ $t('settings.notification_visibility_mentions') }} - </Checkbox> - </li> - </ul> - </div> - <div> - {{ $t('settings.replies_in_timeline') }} - <label - for="replyVisibility" - class="select" - > - <select - id="replyVisibility" - v-model="replyVisibility" - > - <option - value="all" - selected - >{{ $t('settings.reply_visibility_all') }}</option> - <option value="following">{{ $t('settings.reply_visibility_following') }}</option> - <option value="self">{{ $t('settings.reply_visibility_self') }}</option> - </select> - <i class="icon-down-open" /> - </label> - </div> - <div> - <Checkbox v-model="hidePostStats"> - {{ $t('settings.hide_post_stats') }} {{ $t('settings.instance_default', { value: hidePostStatsLocalizedValue }) }} - </Checkbox> - </div> - <div> - <Checkbox v-model="hideUserStats"> - {{ $t('settings.hide_user_stats') }} {{ $t('settings.instance_default', { value: hideUserStatsLocalizedValue }) }} - </Checkbox> - </div> - </div> - <div class="setting-item"> - <div> - <p>{{ $t('settings.filtering_explanation') }}</p> - <textarea - id="muteWords" - v-model="muteWordsString" - /> - </div> - <div> - <Checkbox v-model="hideFilteredStatuses"> - {{ $t('settings.hide_filtered_statuses') }} {{ $t('settings.instance_default', { value: hideFilteredStatusesLocalizedValue }) }} - </Checkbox> - </div> - </div> - </div> - <div :label="$t('settings.version.title')"> - <div class="setting-item"> - <ul class="setting-list"> - <li> - <p>{{ $t('settings.version.backend_version') }}</p> - <ul class="option-list"> - <li> - <a - :href="backendVersionLink" - target="_blank" - >{{ backendVersion }}</a> - </li> - </ul> - </li> - <li> - <p>{{ $t('settings.version.frontend_version') }}</p> - <ul class="option-list"> - <li> - <a - :href="frontendVersionLink" - target="_blank" - >{{ frontendVersion }}</a> - </li> - </ul> - </li> - </ul> - </div> - </div> - </tab-switcher> - </keep-alive> - </div> - </div> -</template> - -<script src="./settings.js"> -</script> diff --git a/src/components/settings_modal/helpers/shared_computed_object.js b/src/components/settings_modal/helpers/shared_computed_object.js new file mode 100644 index 00000000..86703697 --- /dev/null +++ b/src/components/settings_modal/helpers/shared_computed_object.js @@ -0,0 +1,58 @@ +import { + instanceDefaultProperties, + multiChoiceProperties, + defaultState as configDefaultState +} from 'src/modules/config.js' + +const SharedComputedObject = () => ({ + user () { + return this.$store.state.users.currentUser + }, + // Getting localized values for instance-default properties + ...instanceDefaultProperties + .filter(key => multiChoiceProperties.includes(key)) + .map(key => [ + key + 'DefaultValue', + function () { + return this.$store.getters.instanceDefaultConfig[key] + } + ]) + .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), + ...instanceDefaultProperties + .filter(key => !multiChoiceProperties.includes(key)) + .map(key => [ + key + 'LocalizedValue', + function () { + return this.$t('settings.values.' + this.$store.getters.instanceDefaultConfig[key]) + } + ]) + .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), + // Generating computed values for vuex properties + ...Object.keys(configDefaultState) + .map(key => [key, { + get () { return this.$store.getters.mergedConfig[key] }, + set (value) { + this.$store.dispatch('setOption', { name: key, value }) + } + }]) + .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), + // Special cases (need to transform values or perform actions first) + useStreamingApi: { + get () { return this.$store.getters.mergedConfig.useStreamingApi }, + set (value) { + const promise = value + ? this.$store.dispatch('enableMastoSockets') + : this.$store.dispatch('disableMastoSockets') + + promise.then(() => { + this.$store.dispatch('setOption', { name: 'useStreamingApi', value }) + }).catch((e) => { + console.error('Failed starting MastoAPI Streaming socket', e) + this.$store.dispatch('disableMastoSockets') + this.$store.dispatch('setOption', { name: 'useStreamingApi', value: false }) + }) + } + } +}) + +export default SharedComputedObject diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js new file mode 100644 index 00000000..f0d49c91 --- /dev/null +++ b/src/components/settings_modal/settings_modal.js @@ -0,0 +1,42 @@ +import Modal from 'src/components/modal/modal.vue' +import PanelLoading from 'src/components/panel_loading/panel_loading.vue' +import AsyncComponentError from 'src/components/async_component_error/async_component_error.vue' +import getResettableAsyncComponent from 'src/services/resettable_async_component.js' + +const SettingsModal = { + components: { + Modal, + SettingsModalContent: getResettableAsyncComponent( + () => import('./settings_modal_content.vue'), + { + loading: PanelLoading, + error: AsyncComponentError, + delay: 0 + } + ) + }, + methods: { + closeModal () { + this.$store.dispatch('closeSettingsModal') + }, + peekModal () { + this.$store.dispatch('togglePeekSettingsModal') + } + }, + computed: { + currentSaveStateNotice () { + return this.$store.state.interface.settings.currentSaveStateNotice + }, + modalActivated () { + return this.$store.state.interface.settingsModalState !== 'hidden' + }, + modalOpenedOnce () { + return this.$store.state.interface.settingsModalLoaded + }, + modalPeeked () { + return this.$store.state.interface.settingsModalState === 'minimized' + } + } +} + +export default SettingsModal diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss new file mode 100644 index 00000000..833ff89a --- /dev/null +++ b/src/components/settings_modal/settings_modal.scss @@ -0,0 +1,44 @@ +@import 'src/_variables.scss'; +.settings-modal { + overflow: hidden; + + &.peek { + .settings-modal-panel { + /* Explanation: + * Modal is positioned vertically centered. + * 100vh - 100% = Distance between modal's top+bottom boundaries and screen + * (100vh - 100%) / 2 = Distance between bottom (or top) boundary and screen + * + 100% - we move modal completely off-screen, it's top boundary touches + * bottom of the screen + * - 50px - leaving tiny amount of space so that titlebar + tiny amount of modal is visible + */ + transform: translateY(calc(((100vh - 100%) / 2 + 100%) - 50px)); + } + } + + .settings-modal-panel { + overflow: hidden; + transition: transform; + transition-timing-function: ease-in-out; + transition-duration: 300ms; + width: 1000px; + max-width: 90vw; + height: 90vh; + + @media all and (max-width: 800px) { + max-width: 100vw; + height: 100vh; + } + + .panel-body { + height: 100%; + overflow-y: hidden; + + .btn { + min-height: 28px; + min-width: 10em; + padding: 0 2em; + } + } + } +} diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue new file mode 100644 index 00000000..6bc64ed0 --- /dev/null +++ b/src/components/settings_modal/settings_modal.vue @@ -0,0 +1,54 @@ +<template> + <Modal + :is-open="modalActivated" + class="settings-modal" + :class="{ peek: modalPeeked }" + :no-background="modalPeeked" + > + <div class="settings-modal-panel panel"> + <div class="panel-heading"> + <span class="title"> + {{ $t('settings.settings') }} + </span> + <transition name="fade"> + <template v-if="currentSaveStateNotice"> + <div + v-if="currentSaveStateNotice.error" + class="alert error" + @click.prevent + > + {{ $t('settings.saving_err') }} + </div> + + <div + v-if="!currentSaveStateNotice.error" + class="alert transparent" + @click.prevent + > + {{ $t('settings.saving_ok') }} + </div> + </template> + </transition> + <button + class="btn" + @click="peekModal" + > + {{ $t('general.peek') }} + </button> + <button + class="btn" + @click="closeModal" + > + {{ $t('general.close') }} + </button> + </div> + <div class="panel-body"> + <SettingsModalContent v-if="modalOpenedOnce" /> + </div> + </div> + </Modal> +</template> + +<script src="./settings_modal.js"></script> + +<style src="./settings_modal.scss" lang="scss"></style> diff --git a/src/components/settings_modal/settings_modal_content.js b/src/components/settings_modal/settings_modal_content.js new file mode 100644 index 00000000..48101a90 --- /dev/null +++ b/src/components/settings_modal/settings_modal_content.js @@ -0,0 +1,34 @@ +import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js' + +import DataImportExportTab from './tabs/data_import_export_tab.vue' +import MutesAndBlocksTab from './tabs/mutes_and_blocks_tab.vue' +import NotificationsTab from './tabs/notifications_tab.vue' +import FilteringTab from './tabs/filtering_tab.vue' +import SecurityTab from './tabs/security_tab/security_tab.vue' +import ProfileTab from './tabs/profile_tab.vue' +import GeneralTab from './tabs/general_tab.vue' +import VersionTab from './tabs/version_tab.vue' +import ThemeTab from './tabs/theme_tab/theme_tab.vue' + +const SettingsModalContent = { + components: { + TabSwitcher, + + DataImportExportTab, + MutesAndBlocksTab, + NotificationsTab, + FilteringTab, + SecurityTab, + ProfileTab, + GeneralTab, + VersionTab, + ThemeTab + }, + computed: { + isLoggedIn () { + return !!this.$store.state.users.currentUser + } + } +} + +export default SettingsModalContent diff --git a/src/components/settings_modal/settings_modal_content.scss b/src/components/settings_modal/settings_modal_content.scss new file mode 100644 index 00000000..a3fef1cf --- /dev/null +++ b/src/components/settings_modal/settings_modal_content.scss @@ -0,0 +1,43 @@ +@import 'src/_variables.scss'; +.settings_tab-switcher { + height: 100%; + + .setting-item { + border-bottom: 2px solid var(--fg, $fallback--fg); + margin: 1em 1em 1.4em; + padding-bottom: 1.4em; + + > div { + margin-bottom: .5em; + &:last-child { + margin-bottom: 0; + } + } + + &:last-child { + border-bottom: none; + padding-bottom: 0; + margin-bottom: 1em; + } + + select { + min-width: 10em; + } + + textarea { + width: 100%; + max-width: 100%; + height: 100px; + } + + .unavailable, + .unavailable i { + color: var(--cRed, $fallback--cRed); + color: $fallback--cRed; + } + + .number-input { + max-width: 6em; + } + } +} diff --git a/src/components/settings_modal/settings_modal_content.vue b/src/components/settings_modal/settings_modal_content.vue new file mode 100644 index 00000000..2156844f --- /dev/null +++ b/src/components/settings_modal/settings_modal_content.vue @@ -0,0 +1,73 @@ +<template> + <tab-switcher + ref="tabSwitcher" + class="settings_tab-switcher" + :side-tab-bar="true" + :scrollable-tabs="true" + > + <div + :label="$t('settings.general')" + icon="wrench" + > + <GeneralTab /> + </div> + <div + v-if="isLoggedIn" + :label="$t('settings.profile_tab')" + icon="user" + > + <ProfileTab /> + </div> + <div + v-if="isLoggedIn" + :label="$t('settings.security_tab')" + icon="lock" + > + <SecurityTab /> + </div> + <div + :label="$t('settings.filtering')" + icon="filter" + > + <FilteringTab /> + </div> + <div + :label="$t('settings.theme')" + icon="brush" + > + <ThemeTab /> + </div> + <div + v-if="isLoggedIn" + :label="$t('settings.notifications')" + icon="bell-ringing-o" + > + <NotificationsTab /> + </div> + <div + v-if="isLoggedIn" + :label="$t('settings.data_import_export_tab')" + icon="download" + > + <DataImportExportTab /> + </div> + <div + v-if="isLoggedIn" + :label="$t('settings.mutes_and_blocks')" + :fullHeight="true" + icon="eye-off" + > + <MutesAndBlocksTab /> + </div> + <div + :label="$t('settings.version.title')" + icon="info-circled" + > + <VersionTab /> + </div> + </tab-switcher> +</template> + +<script src="./settings_modal_content.js"></script> + +<style src="./settings_modal_content.scss" lang="scss"></style> diff --git a/src/components/settings_modal/tabs/data_import_export_tab.js b/src/components/settings_modal/tabs/data_import_export_tab.js new file mode 100644 index 00000000..168f89e1 --- /dev/null +++ b/src/components/settings_modal/tabs/data_import_export_tab.js @@ -0,0 +1,65 @@ +import Importer from 'src/components/importer/importer.vue' +import Exporter from 'src/components/exporter/exporter.vue' +import Checkbox from 'src/components/checkbox/checkbox.vue' + +const DataImportExportTab = { + data () { + return { + activeTab: 'profile', + newDomainToMute: '' + } + }, + created () { + this.$store.dispatch('fetchTokens') + }, + components: { + Importer, + Exporter, + Checkbox + }, + computed: { + user () { + return this.$store.state.users.currentUser + } + }, + methods: { + getFollowsContent () { + return this.$store.state.api.backendInteractor.exportFriends({ id: this.$store.state.users.currentUser.id }) + .then(this.generateExportableUsersContent) + }, + getBlocksContent () { + return this.$store.state.api.backendInteractor.fetchBlocks() + .then(this.generateExportableUsersContent) + }, + importFollows (file) { + return this.$store.state.api.backendInteractor.importFollows({ file }) + .then((status) => { + if (!status) { + throw new Error('failed') + } + }) + }, + importBlocks (file) { + return this.$store.state.api.backendInteractor.importBlocks({ file }) + .then((status) => { + if (!status) { + throw new Error('failed') + } + }) + }, + generateExportableUsersContent (users) { + // Get addresses + return users.map((user) => { + // check is it's a local user + if (user && user.is_local) { + // append the instance address + // eslint-disable-next-line no-undef + return user.screen_name + '@' + location.hostname + } + return user.screen_name + }).join('\n') + } + } +} + +export default DataImportExportTab diff --git a/src/components/settings_modal/tabs/data_import_export_tab.vue b/src/components/settings_modal/tabs/data_import_export_tab.vue new file mode 100644 index 00000000..b5d0f5ed --- /dev/null +++ b/src/components/settings_modal/tabs/data_import_export_tab.vue @@ -0,0 +1,43 @@ +<template> + <div + :label="$t('settings.data_import_export_tab')" + > + <div class="setting-item"> + <h2>{{ $t('settings.follow_import') }}</h2> + <p>{{ $t('settings.import_followers_from_a_csv_file') }}</p> + <Importer + :submit-handler="importFollows" + :success-message="$t('settings.follows_imported')" + :error-message="$t('settings.follow_import_error')" + /> + </div> + <div class="setting-item"> + <h2>{{ $t('settings.follow_export') }}</h2> + <Exporter + :get-content="getFollowsContent" + filename="friends.csv" + :export-button-label="$t('settings.follow_export_button')" + /> + </div> + <div class="setting-item"> + <h2>{{ $t('settings.block_import') }}</h2> + <p>{{ $t('settings.import_blocks_from_a_csv_file') }}</p> + <Importer + :submit-handler="importBlocks" + :success-message="$t('settings.blocks_imported')" + :error-message="$t('settings.block_import_error')" + /> + </div> + <div class="setting-item"> + <h2>{{ $t('settings.block_export') }}</h2> + <Exporter + :get-content="getBlocksContent" + filename="blocks.csv" + :export-button-label="$t('settings.block_export_button')" + /> + </div> + </div> +</template> + +<script src="./data_import_export_tab.js"></script> +<!-- <style lang="scss" src="./profile.scss"></style> --> diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js new file mode 100644 index 00000000..224a7f47 --- /dev/null +++ b/src/components/settings_modal/tabs/filtering_tab.js @@ -0,0 +1,44 @@ +import { filter, trim } from 'lodash' +import Checkbox from 'src/components/checkbox/checkbox.vue' + +import SharedComputedObject from '../helpers/shared_computed_object.js' + +const FilteringTab = { + data () { + return { + muteWordsStringLocal: this.$store.getters.mergedConfig.muteWords.join('\n') + } + }, + components: { + Checkbox + }, + computed: { + ...SharedComputedObject(), + muteWordsString: { + get () { + return this.muteWordsStringLocal + }, + set (value) { + this.muteWordsStringLocal = value + this.$store.dispatch('setOption', { + name: 'muteWords', + value: filter(value.split('\n'), (word) => trim(word).length > 0) + }) + } + } + }, + // Updating nested properties + watch: { + notificationVisibility: { + handler (value) { + this.$store.dispatch('setOption', { + name: 'notificationVisibility', + value: this.$store.getters.mergedConfig.notificationVisibility + }) + }, + deep: true + } + } +} + +export default FilteringTab diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue new file mode 100644 index 00000000..eea41514 --- /dev/null +++ b/src/components/settings_modal/tabs/filtering_tab.vue @@ -0,0 +1,86 @@ +<template> + <div :label="$t('settings.filtering')"> + <div class="setting-item"> + <div class="select-multiple"> + <span class="label">{{ $t('settings.notification_visibility') }}</span> + <ul class="option-list"> + <li> + <Checkbox v-model="notificationVisibility.likes"> + {{ $t('settings.notification_visibility_likes') }} + </Checkbox> + </li> + <li> + <Checkbox v-model="notificationVisibility.repeats"> + {{ $t('settings.notification_visibility_repeats') }} + </Checkbox> + </li> + <li> + <Checkbox v-model="notificationVisibility.follows"> + {{ $t('settings.notification_visibility_follows') }} + </Checkbox> + </li> + <li> + <Checkbox v-model="notificationVisibility.mentions"> + {{ $t('settings.notification_visibility_mentions') }} + </Checkbox> + </li> + <li> + <Checkbox v-model="notificationVisibility.moves"> + {{ $t('settings.notification_visibility_moves') }} + </Checkbox> + </li> + <li> + <Checkbox v-model="notificationVisibility.emojiReactions"> + {{ $t('settings.notification_visibility_emoji_reactions') }} + </Checkbox> + </li> + </ul> + </div> + <div> + {{ $t('settings.replies_in_timeline') }} + <label + for="replyVisibility" + class="select" + > + <select + id="replyVisibility" + v-model="replyVisibility" + > + <option + value="all" + selected + >{{ $t('settings.reply_visibility_all') }}</option> + <option value="following">{{ $t('settings.reply_visibility_following') }}</option> + <option value="self">{{ $t('settings.reply_visibility_self') }}</option> + </select> + <i class="icon-down-open" /> + </label> + </div> + <div> + <Checkbox v-model="hidePostStats"> + {{ $t('settings.hide_post_stats') }} {{ $t('settings.instance_default', { value: hidePostStatsLocalizedValue }) }} + </Checkbox> + </div> + <div> + <Checkbox v-model="hideUserStats"> + {{ $t('settings.hide_user_stats') }} {{ $t('settings.instance_default', { value: hideUserStatsLocalizedValue }) }} + </Checkbox> + </div> + </div> + <div class="setting-item"> + <div> + <p>{{ $t('settings.filtering_explanation') }}</p> + <textarea + id="muteWords" + v-model="muteWordsString" + /> + </div> + <div> + <Checkbox v-model="hideFilteredStatuses"> + {{ $t('settings.hide_filtered_statuses') }} {{ $t('settings.instance_default', { value: hideFilteredStatusesLocalizedValue }) }} + </Checkbox> + </div> + </div> + </div> +</template> +<script src="./filtering_tab.js"></script> diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js new file mode 100644 index 00000000..0eb37e44 --- /dev/null +++ b/src/components/settings_modal/tabs/general_tab.js @@ -0,0 +1,31 @@ +import Checkbox from 'src/components/checkbox/checkbox.vue' +import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue' + +import SharedComputedObject from '../helpers/shared_computed_object.js' + +const GeneralTab = { + data () { + return { + loopSilentAvailable: + // Firefox + Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') || + // Chrome-likes + Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') || + // Future spec, still not supported in Nightly 63 as of 08/2018 + Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks') + } + }, + components: { + Checkbox, + InterfaceLanguageSwitcher + }, + computed: { + postFormats () { + return this.$store.state.instance.postFormats || [] + }, + instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel }, + ...SharedComputedObject() + } +} + +export default GeneralTab diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue new file mode 100644 index 00000000..f89c0480 --- /dev/null +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -0,0 +1,272 @@ +<template> + <div :label="$t('settings.general')"> + <div class="setting-item"> + <h2>{{ $t('settings.interface') }}</h2> + <ul class="setting-list"> + <li> + <interface-language-switcher /> + </li> + <li v-if="instanceSpecificPanelPresent"> + <Checkbox v-model="hideISP"> + {{ $t('settings.hide_isp') }} + </Checkbox> + </li> + </ul> + </div> + <div class="setting-item"> + <h2>{{ $t('nav.timeline') }}</h2> + <ul class="setting-list"> + <li> + <Checkbox v-model="hideMutedPosts"> + {{ $t('settings.hide_muted_posts') }} {{ $t('settings.instance_default', { value: hideMutedPostsLocalizedValue }) }} + </Checkbox> + </li> + <li> + <Checkbox v-model="collapseMessageWithSubject"> + {{ $t('settings.collapse_subject') }} {{ $t('settings.instance_default', { value: collapseMessageWithSubjectLocalizedValue }) }} + </Checkbox> + </li> + <li> + <Checkbox v-model="streaming"> + {{ $t('settings.streaming') }} + </Checkbox> + <ul + class="setting-list suboptions" + :class="[{disabled: !streaming}]" + > + <li> + <Checkbox + v-model="pauseOnUnfocused" + :disabled="!streaming" + > + {{ $t('settings.pause_on_unfocused') }} + </Checkbox> + </li> + </ul> + </li> + <li> + <Checkbox v-model="useStreamingApi"> + {{ $t('settings.useStreamingApi') }} + <br> + <small> + {{ $t('settings.useStreamingApiWarning') }} + </small> + </Checkbox> + </li> + <li> + <Checkbox v-model="autoLoad"> + {{ $t('settings.autoload') }} + </Checkbox> + </li> + <li> + <Checkbox v-model="hoverPreview"> + {{ $t('settings.reply_link_preview') }} + </Checkbox> + </li> + <li> + <Checkbox v-model="emojiReactionsOnTimeline"> + {{ $t('settings.emoji_reactions_on_timeline') }} + </Checkbox> + </li> + </ul> + </div> + + <div class="setting-item"> + <h2>{{ $t('settings.composing') }}</h2> + <ul class="setting-list"> + <li> + <Checkbox v-model="scopeCopy"> + {{ $t('settings.scope_copy') }} {{ $t('settings.instance_default', { value: scopeCopyLocalizedValue }) }} + </Checkbox> + </li> + <li> + <Checkbox v-model="alwaysShowSubjectInput"> + {{ $t('settings.subject_input_always_show') }} {{ $t('settings.instance_default', { value: alwaysShowSubjectInputLocalizedValue }) }} + </Checkbox> + </li> + <li> + <div> + {{ $t('settings.subject_line_behavior') }} + <label + for="subjectLineBehavior" + class="select" + > + <select + id="subjectLineBehavior" + v-model="subjectLineBehavior" + > + <option value="email"> + {{ $t('settings.subject_line_email') }} + {{ subjectLineBehaviorDefaultValue == 'email' ? $t('settings.instance_default_simple') : '' }} + </option> + <option value="masto"> + {{ $t('settings.subject_line_mastodon') }} + {{ subjectLineBehaviorDefaultValue == 'mastodon' ? $t('settings.instance_default_simple') : '' }} + </option> + <option value="noop"> + {{ $t('settings.subject_line_noop') }} + {{ subjectLineBehaviorDefaultValue == 'noop' ? $t('settings.instance_default_simple') : '' }} + </option> + </select> + <i class="icon-down-open" /> + </label> + </div> + </li> + <li v-if="postFormats.length > 0"> + <div> + {{ $t('settings.post_status_content_type') }} + <label + for="postContentType" + class="select" + > + <select + id="postContentType" + v-model="postContentType" + > + <option + v-for="postFormat in postFormats" + :key="postFormat" + :value="postFormat" + > + {{ $t(`post_status.content_type["${postFormat}"]`) }} + {{ postContentTypeDefaultValue === postFormat ? $t('settings.instance_default_simple') : '' }} + </option> + </select> + <i class="icon-down-open" /> + </label> + </div> + </li> + <li> + <Checkbox v-model="minimalScopesMode"> + {{ $t('settings.minimal_scopes_mode') }} {{ $t('settings.instance_default', { value: minimalScopesModeLocalizedValue }) }} + </Checkbox> + </li> + <li> + <Checkbox v-model="autohideFloatingPostButton"> + {{ $t('settings.autohide_floating_post_button') }} + </Checkbox> + </li> + <li> + <Checkbox v-model="padEmoji"> + {{ $t('settings.pad_emoji') }} + </Checkbox> + </li> + </ul> + </div> + + <div class="setting-item"> + <h2>{{ $t('settings.attachments') }}</h2> + <ul class="setting-list"> + <li> + <Checkbox v-model="hideAttachments"> + {{ $t('settings.hide_attachments_in_tl') }} + </Checkbox> + </li> + <li> + <Checkbox v-model="hideAttachmentsInConv"> + {{ $t('settings.hide_attachments_in_convo') }} + </Checkbox> + </li> + <li> + <label for="maxThumbnails"> + {{ $t('settings.max_thumbnails') }} + </label> + <input + id="maxThumbnails" + v-model.number="maxThumbnails" + class="number-input" + type="number" + min="0" + step="1" + > + </li> + <li> + <Checkbox v-model="hideNsfw"> + {{ $t('settings.nsfw_clickthrough') }} + </Checkbox> + </li> + <ul class="setting-list suboptions"> + <li> + <Checkbox + v-model="preloadImage" + :disabled="!hideNsfw" + > + {{ $t('settings.preload_images') }} + </Checkbox> + </li> + <li> + <Checkbox + v-model="useOneClickNsfw" + :disabled="!hideNsfw" + > + {{ $t('settings.use_one_click_nsfw') }} + </Checkbox> + </li> + </ul> + <li> + <Checkbox v-model="stopGifs"> + {{ $t('settings.stop_gifs') }} + </Checkbox> + </li> + <li> + <Checkbox v-model="loopVideo"> + {{ $t('settings.loop_video') }} + </Checkbox> + <ul + class="setting-list suboptions" + :class="[{disabled: !streaming}]" + > + <li> + <Checkbox + v-model="loopVideoSilentOnly" + :disabled="!loopVideo || !loopSilentAvailable" + > + {{ $t('settings.loop_video_silent_only') }} + </Checkbox> + <div + v-if="!loopSilentAvailable" + class="unavailable" + > + <i class="icon-globe" />! {{ $t('settings.limited_availability') }} + </div> + </li> + </ul> + </li> + <li> + <Checkbox v-model="playVideosInModal"> + {{ $t('settings.play_videos_in_modal') }} + </Checkbox> + </li> + <li> + <Checkbox v-model="useContainFit"> + {{ $t('settings.use_contain_fit') }} + </Checkbox> + </li> + </ul> + </div> + + <div class="setting-item"> + <h2>{{ $t('settings.notifications') }}</h2> + <ul class="setting-list"> + <li> + <Checkbox v-model="webPushNotifications"> + {{ $t('settings.enable_web_push_notifications') }} + </Checkbox> + </li> + </ul> + </div> + + <div class="setting-item"> + <h2>{{ $t('settings.fun') }}</h2> + <ul class="setting-list"> + <li> + <Checkbox v-model="greentext"> + {{ $t('settings.greentext') }} {{ $t('settings.instance_default', { value: greentextLocalizedValue }) }} + </Checkbox> + </li> + </ul> + </div> + </div> +</template> + +<script src="./general_tab.js"></script> diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js new file mode 100644 index 00000000..40a87b81 --- /dev/null +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js @@ -0,0 +1,136 @@ +import get from 'lodash/get' +import map from 'lodash/map' +import reject from 'lodash/reject' +import Autosuggest from 'src/components/autosuggest/autosuggest.vue' +import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js' +import BlockCard from 'src/components/block_card/block_card.vue' +import MuteCard from 'src/components/mute_card/mute_card.vue' +import DomainMuteCard from 'src/components/domain_mute_card/domain_mute_card.vue' +import SelectableList from 'src/components/selectable_list/selectable_list.vue' +import ProgressButton from 'src/components/progress_button/progress_button.vue' +import withSubscription from 'src/components/../hocs/with_subscription/with_subscription' +import Checkbox from 'src/components/checkbox/checkbox.vue' + +const BlockList = withSubscription({ + fetch: (props, $store) => $store.dispatch('fetchBlocks'), + select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []), + childPropName: 'items' +})(SelectableList) + +const MuteList = withSubscription({ + fetch: (props, $store) => $store.dispatch('fetchMutes'), + select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []), + childPropName: 'items' +})(SelectableList) + +const DomainMuteList = withSubscription({ + fetch: (props, $store) => $store.dispatch('fetchDomainMutes'), + select: (props, $store) => get($store.state.users.currentUser, 'domainMutes', []), + childPropName: 'items' +})(SelectableList) + +const MutesAndBlocks = { + data () { + return { + activeTab: 'profile' + } + }, + created () { + this.$store.dispatch('fetchTokens') + this.$store.dispatch('getKnownDomains') + }, + components: { + TabSwitcher, + BlockList, + MuteList, + DomainMuteList, + BlockCard, + MuteCard, + DomainMuteCard, + ProgressButton, + Autosuggest, + Checkbox + }, + computed: { + knownDomains () { + return this.$store.state.instance.knownDomains + }, + user () { + return this.$store.state.users.currentUser + } + }, + methods: { + importFollows (file) { + return this.$store.state.api.backendInteractor.importFollows({ file }) + .then((status) => { + if (!status) { + throw new Error('failed') + } + }) + }, + importBlocks (file) { + return this.$store.state.api.backendInteractor.importBlocks({ file }) + .then((status) => { + if (!status) { + throw new Error('failed') + } + }) + }, + generateExportableUsersContent (users) { + // Get addresses + return users.map((user) => { + // check is it's a local user + if (user && user.is_local) { + // append the instance address + // eslint-disable-next-line no-undef + return user.screen_name + '@' + location.hostname + } + return user.screen_name + }).join('\n') + }, + activateTab (tabName) { + this.activeTab = tabName + }, + filterUnblockedUsers (userIds) { + return reject(userIds, (userId) => { + const relationship = this.$store.getters.relationship(this.userId) + return relationship.blocking || userId === this.user.id + }) + }, + filterUnMutedUsers (userIds) { + return reject(userIds, (userId) => { + const relationship = this.$store.getters.relationship(this.userId) + return relationship.muting || userId === this.user.id + }) + }, + queryUserIds (query) { + return this.$store.dispatch('searchUsers', { query }) + .then((users) => map(users, 'id')) + }, + blockUsers (ids) { + return this.$store.dispatch('blockUsers', ids) + }, + unblockUsers (ids) { + return this.$store.dispatch('unblockUsers', ids) + }, + muteUsers (ids) { + return this.$store.dispatch('muteUsers', ids) + }, + unmuteUsers (ids) { + return this.$store.dispatch('unmuteUsers', ids) + }, + filterUnMutedDomains (urls) { + return urls.filter(url => !this.user.domainMutes.includes(url)) + }, + queryKnownDomains (query) { + return new Promise((resolve, reject) => { + resolve(this.knownDomains.filter(url => url.toLowerCase().includes(query))) + }) + }, + unmuteDomains (domains) { + return this.$store.dispatch('unmuteDomains', domains) + } + } +} + +export default MutesAndBlocks diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss b/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss new file mode 100644 index 00000000..ceb64efb --- /dev/null +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss @@ -0,0 +1,29 @@ +.mutes-and-blocks-tab { + height: 100%; + + .usersearch-wrapper { + padding: 1em; + } + + .bulk-actions { + text-align: right; + padding: 0 1em; + min-height: 28px; + } + + .bulk-action-button { + width: 10em + } + + .domain-mute-form { + padding: 1em; + display: flex; + flex-direction: column + } + + .domain-mute-button { + align-self: flex-end; + margin-top: 1em; + width: 10em + } +} diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue new file mode 100644 index 00000000..5a1cf2c0 --- /dev/null +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue @@ -0,0 +1,171 @@ +<template> + <tab-switcher + :scrollable-tabs="true" + class="mutes-and-blocks-tab" + > + <div :label="$t('settings.blocks_tab')"> + <div class="usersearch-wrapper"> + <Autosuggest + :filter="filterUnblockedUsers" + :query="queryUserIds" + :placeholder="$t('settings.search_user_to_block')" + > + <BlockCard + slot-scope="row" + :user-id="row.item" + /> + </Autosuggest> + </div> + <BlockList + :refresh="true" + :get-key="i => i" + > + <template + slot="header" + slot-scope="{selected}" + > + <div class="bulk-actions"> + <ProgressButton + v-if="selected.length > 0" + class="btn btn-default bulk-action-button" + :click="() => blockUsers(selected)" + > + {{ $t('user_card.block') }} + <template slot="progress"> + {{ $t('user_card.block_progress') }} + </template> + </ProgressButton> + <ProgressButton + v-if="selected.length > 0" + class="btn btn-default" + :click="() => unblockUsers(selected)" + > + {{ $t('user_card.unblock') }} + <template slot="progress"> + {{ $t('user_card.unblock_progress') }} + </template> + </ProgressButton> + </div> + </template> + <template + slot="item" + slot-scope="{item}" + > + <BlockCard :user-id="item" /> + </template> + <template slot="empty"> + {{ $t('settings.no_blocks') }} + </template> + </BlockList> + </div> + + <div :label="$t('settings.mutes_tab')"> + <tab-switcher> + <div label="Users"> + <div class="usersearch-wrapper"> + <Autosuggest + :filter="filterUnMutedUsers" + :query="queryUserIds" + :placeholder="$t('settings.search_user_to_mute')" + > + <MuteCard + slot-scope="row" + :user-id="row.item" + /> + </Autosuggest> + </div> + <MuteList + :refresh="true" + :get-key="i => i" + > + <template + slot="header" + slot-scope="{selected}" + > + <div class="bulk-actions"> + <ProgressButton + v-if="selected.length > 0" + class="btn btn-default" + :click="() => muteUsers(selected)" + > + {{ $t('user_card.mute') }} + <template slot="progress"> + {{ $t('user_card.mute_progress') }} + </template> + </ProgressButton> + <ProgressButton + v-if="selected.length > 0" + class="btn btn-default" + :click="() => unmuteUsers(selected)" + > + {{ $t('user_card.unmute') }} + <template slot="progress"> + {{ $t('user_card.unmute_progress') }} + </template> + </ProgressButton> + </div> + </template> + <template + slot="item" + slot-scope="{item}" + > + <MuteCard :user-id="item" /> + </template> + <template slot="empty"> + {{ $t('settings.no_mutes') }} + </template> + </MuteList> + </div> + + <div :label="$t('settings.domain_mutes')"> + <div class="domain-mute-form"> + <Autosuggest + :filter="filterUnMutedDomains" + :query="queryKnownDomains" + :placeholder="$t('settings.type_domains_to_mute')" + > + <DomainMuteCard + slot-scope="row" + :domain="row.item" + /> + </Autosuggest> + </div> + <DomainMuteList + :refresh="true" + :get-key="i => i" + > + <template + slot="header" + slot-scope="{selected}" + > + <div class="bulk-actions"> + <ProgressButton + v-if="selected.length > 0" + class="btn btn-default" + :click="() => unmuteDomains(selected)" + > + {{ $t('domain_mute_card.unmute') }} + <template slot="progress"> + {{ $t('domain_mute_card.unmute_progress') }} + </template> + </ProgressButton> + </div> + </template> + <template + slot="item" + slot-scope="{item}" + > + <DomainMuteCard :domain="item" /> + </template> + <template slot="empty"> + {{ $t('settings.no_mutes') }} + </template> + </DomainMuteList> + </div> + </tab-switcher> + </div> + </tab-switcher> +</template> + +<script src="./mutes_and_blocks_tab.js"></script> +<style lang="scss" src="./mutes_and_blocks_tab.scss"></style> diff --git a/src/components/settings_modal/tabs/notifications_tab.js b/src/components/settings_modal/tabs/notifications_tab.js new file mode 100644 index 00000000..3e44c95d --- /dev/null +++ b/src/components/settings_modal/tabs/notifications_tab.js @@ -0,0 +1,27 @@ +import Checkbox from 'src/components/checkbox/checkbox.vue' + +const NotificationsTab = { + data () { + return { + activeTab: 'profile', + notificationSettings: this.$store.state.users.currentUser.notification_settings, + newDomainToMute: '' + } + }, + components: { + Checkbox + }, + computed: { + user () { + return this.$store.state.users.currentUser + } + }, + methods: { + updateNotificationSettings () { + this.$store.state.api.backendInteractor + .updateNotificationSettings({ settings: this.notificationSettings }) + } + } +} + +export default NotificationsTab diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue new file mode 100644 index 00000000..b7a3cb37 --- /dev/null +++ b/src/components/settings_modal/tabs/notifications_tab.vue @@ -0,0 +1,54 @@ +<template> + <div :label="$t('settings.notifications')"> + <div class="setting-item"> + <h2>{{ $t('settings.notification_setting_filters') }}</h2> + <div class="select-multiple"> + <span class="label">{{ $t('settings.notification_setting') }}</span> + <ul class="option-list"> + <li> + <Checkbox v-model="notificationSettings.follows"> + {{ $t('settings.notification_setting_follows') }} + </Checkbox> + </li> + <li> + <Checkbox v-model="notificationSettings.followers"> + {{ $t('settings.notification_setting_followers') }} + </Checkbox> + </li> + <li> + <Checkbox v-model="notificationSettings.non_follows"> + {{ $t('settings.notification_setting_non_follows') }} + </Checkbox> + </li> + <li> + <Checkbox v-model="notificationSettings.non_followers"> + {{ $t('settings.notification_setting_non_followers') }} + </Checkbox> + </li> + </ul> + </div> + </div> + + <div class="setting-item"> + <h2>{{ $t('settings.notification_setting_privacy') }}</h2> + <p> + <Checkbox v-model="notificationSettings.privacy_option"> + {{ $t('settings.notification_setting_privacy_option') }} + </Checkbox> + </p> + </div> + <div class="setting-item"> + <p>{{ $t('settings.notification_mutes') }}</p> + <p>{{ $t('settings.notification_blocks') }}</p> + <button + class="btn btn-default" + @click="updateNotificationSettings" + > + {{ $t('general.submit') }} + </button> + </div> + </div> +</template> + +<script src="./notifications_tab.js"></script> +<!-- <style lang="scss" src="./profile.scss"></style> --> diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js new file mode 100644 index 00000000..8658b097 --- /dev/null +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -0,0 +1,179 @@ +import unescape from 'lodash/unescape' +import ImageCropper from 'src/components/image_cropper/image_cropper.vue' +import ScopeSelector from 'src/components/scope_selector/scope_selector.vue' +import fileSizeFormatService from 'src/components/../services/file_size_format/file_size_format.js' +import ProgressButton from 'src/components/progress_button/progress_button.vue' +import EmojiInput from 'src/components/emoji_input/emoji_input.vue' +import suggestor from 'src/components/emoji_input/suggestor.js' +import Autosuggest from 'src/components/autosuggest/autosuggest.vue' +import Checkbox from 'src/components/checkbox/checkbox.vue' + +const ProfileTab = { + data () { + return { + newName: this.$store.state.users.currentUser.name, + newBio: unescape(this.$store.state.users.currentUser.description), + newLocked: this.$store.state.users.currentUser.locked, + newNoRichText: this.$store.state.users.currentUser.no_rich_text, + newDefaultScope: this.$store.state.users.currentUser.default_scope, + hideFollows: this.$store.state.users.currentUser.hide_follows, + hideFollowers: this.$store.state.users.currentUser.hide_followers, + hideFollowsCount: this.$store.state.users.currentUser.hide_follows_count, + hideFollowersCount: this.$store.state.users.currentUser.hide_followers_count, + showRole: this.$store.state.users.currentUser.show_role, + role: this.$store.state.users.currentUser.role, + discoverable: this.$store.state.users.currentUser.discoverable, + allowFollowingMove: this.$store.state.users.currentUser.allow_following_move, + pickAvatarBtnVisible: true, + bannerUploading: false, + backgroundUploading: false, + banner: null, + bannerPreview: null, + background: null, + backgroundPreview: null, + bannerUploadError: null, + backgroundUploadError: null + } + }, + components: { + ScopeSelector, + ImageCropper, + EmojiInput, + Autosuggest, + ProgressButton, + Checkbox + }, + computed: { + user () { + return this.$store.state.users.currentUser + }, + emojiUserSuggestor () { + return suggestor({ + emoji: [ + ...this.$store.state.instance.emoji, + ...this.$store.state.instance.customEmoji + ], + users: this.$store.state.users.users, + updateUsersList: (query) => this.$store.dispatch('searchUsers', { query }) + }) + }, + emojiSuggestor () { + return suggestor({ emoji: [ + ...this.$store.state.instance.emoji, + ...this.$store.state.instance.customEmoji + ] }) + } + }, + methods: { + updateProfile () { + this.$store.state.api.backendInteractor + .updateProfile({ + params: { + note: this.newBio, + locked: this.newLocked, + // Backend notation. + /* eslint-disable camelcase */ + display_name: this.newName, + default_scope: this.newDefaultScope, + no_rich_text: this.newNoRichText, + hide_follows: this.hideFollows, + hide_followers: this.hideFollowers, + discoverable: this.discoverable, + allow_following_move: this.allowFollowingMove, + hide_follows_count: this.hideFollowsCount, + hide_followers_count: this.hideFollowersCount, + show_role: this.showRole + /* eslint-enable camelcase */ + } }).then((user) => { + this.$store.commit('addNewUsers', [user]) + this.$store.commit('setCurrentUser', user) + }) + }, + changeVis (visibility) { + this.newDefaultScope = visibility + }, + uploadFile (slot, e) { + const file = e.target.files[0] + if (!file) { return } + if (file.size > this.$store.state.instance[slot + 'limit']) { + const filesize = fileSizeFormatService.fileSizeFormat(file.size) + const allowedsize = fileSizeFormatService.fileSizeFormat(this.$store.state.instance[slot + 'limit']) + this[slot + 'UploadError'] = [ + this.$t('upload.error.base'), + this.$t( + 'upload.error.file_too_big', + { + filesize: filesize.num, + filesizeunit: filesize.unit, + allowedsize: allowedsize.num, + allowedsizeunit: allowedsize.unit + } + ) + ].join(' ') + return + } + // eslint-disable-next-line no-undef + const reader = new FileReader() + reader.onload = ({ target }) => { + const img = target.result + this[slot + 'Preview'] = img + this[slot] = file + } + reader.readAsDataURL(file) + }, + submitAvatar (cropper, file) { + const that = this + return new Promise((resolve, reject) => { + function updateAvatar (avatar) { + that.$store.state.api.backendInteractor.updateAvatar({ avatar }) + .then((user) => { + that.$store.commit('addNewUsers', [user]) + that.$store.commit('setCurrentUser', user) + resolve() + }) + .catch((err) => { + reject(new Error(that.$t('upload.error.base') + ' ' + err.message)) + }) + } + + if (cropper) { + cropper.getCroppedCanvas().toBlob(updateAvatar, file.type) + } else { + updateAvatar(file) + } + }) + }, + submitBanner () { + if (!this.bannerPreview) { return } + + this.bannerUploading = true + this.$store.state.api.backendInteractor.updateBanner({ banner: this.banner }) + .then((user) => { + this.$store.commit('addNewUsers', [user]) + this.$store.commit('setCurrentUser', user) + this.bannerPreview = null + }) + .catch((err) => { + this.bannerUploadError = this.$t('upload.error.base') + ' ' + err.message + }) + .then(() => { this.bannerUploading = false }) + }, + submitBg () { + if (!this.backgroundPreview) { return } + let background = this.background + this.backgroundUploading = true + this.$store.state.api.backendInteractor.updateBg({ background }).then((data) => { + if (!data.error) { + this.$store.commit('addNewUsers', [data]) + this.$store.commit('setCurrentUser', data) + this.backgroundPreview = null + } else { + this.backgroundUploadError = this.$t('upload.error.base') + data.error + } + this.backgroundUploading = false + }) + } + } +} + +export default ProfileTab diff --git a/src/components/settings_modal/tabs/profile_tab.scss b/src/components/settings_modal/tabs/profile_tab.scss new file mode 100644 index 00000000..4aab81eb --- /dev/null +++ b/src/components/settings_modal/tabs/profile_tab.scss @@ -0,0 +1,82 @@ +@import '../../../_variables.scss'; +.profile-tab { + .bio { + margin: 0; + } + + .visibility-tray { + padding-top: 5px; + } + + input[type=file] { + padding: 5px; + height: auto; + } + + .banner { + max-width: 100%; + } + + .uploading { + font-size: 1.5em; + margin: 0.25em; + } + + .name-changer { + width: 100%; + } + + .bg { + max-width: 100%; + } + + .current-avatar { + display: block; + width: 150px; + height: 150px; + border-radius: $fallback--avatarRadius; + border-radius: var(--avatarRadius, $fallback--avatarRadius); + } + + .oauth-tokens { + width: 100%; + + th { + text-align: left; + } + + .actions { + text-align: right; + } + } + + &-usersearch-wrapper { + padding: 1em; + } + + &-bulk-actions { + text-align: right; + padding: 0 1em; + min-height: 28px; + + button { + width: 10em; + } + } + + &-domain-mute-form { + padding: 1em; + display: flex; + flex-direction: column; + + button { + align-self: flex-end; + margin-top: 1em; + width: 10em; + } + } + + .setting-subitem { + margin-left: 1.75em; + } +} diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue new file mode 100644 index 00000000..fff4f970 --- /dev/null +++ b/src/components/settings_modal/tabs/profile_tab.vue @@ -0,0 +1,213 @@ +<template> + <div class="profile-tab"> + <div class="setting-item"> + <h2>{{ $t('settings.name_bio') }}</h2> + <p>{{ $t('settings.name') }}</p> + <EmojiInput + v-model="newName" + enable-emoji-picker + :suggest="emojiSuggestor" + > + <input + id="username" + v-model="newName" + classname="name-changer" + > + </EmojiInput> + <p>{{ $t('settings.bio') }}</p> + <EmojiInput + v-model="newBio" + enable-emoji-picker + :suggest="emojiUserSuggestor" + > + <textarea + v-model="newBio" + classname="bio" + /> + </EmojiInput> + <p> + <Checkbox v-model="newLocked"> + {{ $t('settings.lock_account_description') }} + </Checkbox> + </p> + <div> + <label for="default-vis">{{ $t('settings.default_vis') }}</label> + <div + id="default-vis" + class="visibility-tray" + > + <scope-selector + :show-all="true" + :user-default="newDefaultScope" + :initial-scope="newDefaultScope" + :on-scope-change="changeVis" + /> + </div> + </div> + <p> + <Checkbox v-model="newNoRichText"> + {{ $t('settings.no_rich_text_description') }} + </Checkbox> + </p> + <p> + <Checkbox v-model="hideFollows"> + {{ $t('settings.hide_follows_description') }} + </Checkbox> + </p> + <p class="setting-subitem"> + <Checkbox + v-model="hideFollowsCount" + :disabled="!hideFollows" + > + {{ $t('settings.hide_follows_count_description') }} + </Checkbox> + </p> + <p> + <Checkbox v-model="hideFollowers"> + {{ $t('settings.hide_followers_description') }} + </Checkbox> + </p> + <p class="setting-subitem"> + <Checkbox + v-model="hideFollowersCount" + :disabled="!hideFollowers" + > + {{ $t('settings.hide_followers_count_description') }} + </Checkbox> + </p> + <p> + <Checkbox v-model="allowFollowingMove"> + {{ $t('settings.allow_following_move') }} + </Checkbox> + </p> + <p v-if="role === 'admin' || role === 'moderator'"> + <Checkbox v-model="showRole"> + <template v-if="role === 'admin'"> + {{ $t('settings.show_admin_badge') }} + </template> + <template v-if="role === 'moderator'"> + {{ $t('settings.show_moderator_badge') }} + </template> + </Checkbox> + </p> + <p> + <Checkbox v-model="discoverable"> + {{ $t('settings.discoverable') }} + </Checkbox> + </p> + <button + :disabled="newName && newName.length === 0" + class="btn btn-default" + @click="updateProfile" + > + {{ $t('general.submit') }} + </button> + </div> + <div class="setting-item"> + <h2>{{ $t('settings.avatar') }}</h2> + <p class="visibility-notice"> + {{ $t('settings.avatar_size_instruction') }} + </p> + <p>{{ $t('settings.current_avatar') }}</p> + <img + :src="user.profile_image_url_original" + class="current-avatar" + > + <p>{{ $t('settings.set_new_avatar') }}</p> + <button + v-show="pickAvatarBtnVisible" + id="pick-avatar" + class="btn" + type="button" + > + {{ $t('settings.upload_a_photo') }} + </button> + <image-cropper + trigger="#pick-avatar" + :submit-handler="submitAvatar" + @open="pickAvatarBtnVisible=false" + @close="pickAvatarBtnVisible=true" + /> + </div> + <div class="setting-item"> + <h2>{{ $t('settings.profile_banner') }}</h2> + <p>{{ $t('settings.current_profile_banner') }}</p> + <img + :src="user.cover_photo" + class="banner" + > + <p>{{ $t('settings.set_new_profile_banner') }}</p> + <img + v-if="bannerPreview" + class="banner" + :src="bannerPreview" + > + <div> + <input + type="file" + @change="uploadFile('banner', $event)" + > + </div> + <i + v-if="bannerUploading" + class=" icon-spin4 animate-spin uploading" + /> + <button + v-else-if="bannerPreview" + class="btn btn-default" + @click="submitBanner" + > + {{ $t('general.submit') }} + </button> + <div + v-if="bannerUploadError" + class="alert error" + > + Error: {{ bannerUploadError }} + <i + class="button-icon icon-cancel" + @click="clearUploadError('banner')" + /> + </div> + </div> + <div class="setting-item"> + <h2>{{ $t('settings.profile_background') }}</h2> + <p>{{ $t('settings.set_new_profile_background') }}</p> + <img + v-if="backgroundPreview" + class="bg" + :src="backgroundPreview" + > + <div> + <input + type="file" + @change="uploadFile('background', $event)" + > + </div> + <i + v-if="backgroundUploading" + class=" icon-spin4 animate-spin uploading" + /> + <button + v-else-if="backgroundPreview" + class="btn btn-default" + @click="submitBg" + > + {{ $t('general.submit') }} + </button> + <div + v-if="backgroundUploadError" + class="alert error" + > + Error: {{ backgroundUploadError }} + <i + class="button-icon icon-cancel" + @click="clearUploadError('background')" + /> + </div> + </div> + </div> +</template> + +<script src="./profile_tab.js"></script> +<style lang="scss" src="./profile_tab.scss"></style> diff --git a/src/components/user_settings/confirm.js b/src/components/settings_modal/tabs/security_tab/confirm.js similarity index 100% rename from src/components/user_settings/confirm.js rename to src/components/settings_modal/tabs/security_tab/confirm.js diff --git a/src/components/user_settings/confirm.vue b/src/components/settings_modal/tabs/security_tab/confirm.vue similarity index 100% rename from src/components/user_settings/confirm.vue rename to src/components/settings_modal/tabs/security_tab/confirm.vue diff --git a/src/components/user_settings/mfa.js b/src/components/settings_modal/tabs/security_tab/mfa.js similarity index 98% rename from src/components/user_settings/mfa.js rename to src/components/settings_modal/tabs/security_tab/mfa.js index 3090138a..abf37062 100644 --- a/src/components/user_settings/mfa.js +++ b/src/components/settings_modal/tabs/security_tab/mfa.js @@ -139,7 +139,7 @@ const Mfa = { // fetch settings from server async fetchSettings () { - let result = await this.backendInteractor.fetchSettingsMFA() + let result = await this.backendInteractor.settingsMFA() if (result.error) return this.settings = result.settings this.settings.available = true diff --git a/src/components/user_settings/mfa.vue b/src/components/settings_modal/tabs/security_tab/mfa.vue similarity index 96% rename from src/components/user_settings/mfa.vue rename to src/components/settings_modal/tabs/security_tab/mfa.vue index 14ea10a1..7aca3c8d 100644 --- a/src/components/user_settings/mfa.vue +++ b/src/components/settings_modal/tabs/security_tab/mfa.vue @@ -137,20 +137,20 @@ <script src="./mfa.js"></script> <style lang="scss"> -@import '../../_variables.scss'; -.warning { - color: $fallback--cOrange; - color: var(--cOrange, $fallback--cOrange); -} +@import '../../../../_variables.scss'; .mfa-settings { .mfa-heading, .method-item { - overflow: hidden; display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; } + .warning { + color: $fallback--cOrange; + color: var(--cOrange, $fallback--cOrange); + } + .setup-otp { display: flex; justify-content: center; diff --git a/src/components/user_settings/mfa_backup_codes.js b/src/components/settings_modal/tabs/security_tab/mfa_backup_codes.js similarity index 100% rename from src/components/user_settings/mfa_backup_codes.js rename to src/components/settings_modal/tabs/security_tab/mfa_backup_codes.js diff --git a/src/components/user_settings/mfa_backup_codes.vue b/src/components/settings_modal/tabs/security_tab/mfa_backup_codes.vue similarity index 69% rename from src/components/user_settings/mfa_backup_codes.vue rename to src/components/settings_modal/tabs/security_tab/mfa_backup_codes.vue index e6c8ede2..d7e98b3c 100644 --- a/src/components/user_settings/mfa_backup_codes.vue +++ b/src/components/settings_modal/tabs/security_tab/mfa_backup_codes.vue @@ -1,5 +1,5 @@ <template> - <div> + <div class="mfa-backup-codes"> <h4 v-if="displayTitle"> {{ $t('settings.mfa.recovery_codes') }} </h4> @@ -21,13 +21,15 @@ </template> <script src="./mfa_backup_codes.js"></script> <style lang="scss"> -@import '../../_variables.scss'; +@import '../../../../_variables.scss'; -.warning { - color: $fallback--cOrange; - color: var(--cOrange, $fallback--cOrange); -} -.backup-codes { - font-family: var(--postCodeFont, monospace); +.mfa-backup-codes { + .warning { + color: $fallback--cOrange; + color: var(--cOrange, $fallback--cOrange); + } + .backup-codes { + font-family: var(--postCodeFont, monospace); + } } </style> diff --git a/src/components/user_settings/mfa_totp.js b/src/components/settings_modal/tabs/security_tab/mfa_totp.js similarity index 100% rename from src/components/user_settings/mfa_totp.js rename to src/components/settings_modal/tabs/security_tab/mfa_totp.js diff --git a/src/components/user_settings/mfa_totp.vue b/src/components/settings_modal/tabs/security_tab/mfa_totp.vue similarity index 100% rename from src/components/user_settings/mfa_totp.vue rename to src/components/settings_modal/tabs/security_tab/mfa_totp.vue diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.js b/src/components/settings_modal/tabs/security_tab/security_tab.js new file mode 100644 index 00000000..811161a5 --- /dev/null +++ b/src/components/settings_modal/tabs/security_tab/security_tab.js @@ -0,0 +1,106 @@ +import ProgressButton from 'src/components/progress_button/progress_button.vue' +import Checkbox from 'src/components/checkbox/checkbox.vue' +import Mfa from './mfa.vue' + +const SecurityTab = { + data () { + return { + newEmail: '', + changeEmailError: false, + changeEmailPassword: '', + changedEmail: false, + deletingAccount: false, + deleteAccountConfirmPasswordInput: '', + deleteAccountError: false, + changePasswordInputs: [ '', '', '' ], + changedPassword: false, + changePasswordError: false + } + }, + created () { + this.$store.dispatch('fetchTokens') + }, + components: { + ProgressButton, + Mfa, + Checkbox + }, + computed: { + user () { + return this.$store.state.users.currentUser + }, + pleromaBackend () { + return this.$store.state.instance.pleromaBackend + }, + oauthTokens () { + return this.$store.state.oauthTokens.tokens.map(oauthToken => { + return { + id: oauthToken.id, + appName: oauthToken.app_name, + validUntil: new Date(oauthToken.valid_until).toLocaleDateString() + } + }) + } + }, + methods: { + confirmDelete () { + this.deletingAccount = true + }, + deleteAccount () { + this.$store.state.api.backendInteractor.deleteAccount({ password: this.deleteAccountConfirmPasswordInput }) + .then((res) => { + if (res.status === 'success') { + this.$store.dispatch('logout') + this.$router.push({ name: 'root' }) + } else { + this.deleteAccountError = res.error + } + }) + }, + changePassword () { + const params = { + password: this.changePasswordInputs[0], + newPassword: this.changePasswordInputs[1], + newPasswordConfirmation: this.changePasswordInputs[2] + } + this.$store.state.api.backendInteractor.changePassword(params) + .then((res) => { + if (res.status === 'success') { + this.changedPassword = true + this.changePasswordError = false + this.logout() + } else { + this.changedPassword = false + this.changePasswordError = res.error + } + }) + }, + changeEmail () { + const params = { + email: this.newEmail, + password: this.changeEmailPassword + } + this.$store.state.api.backendInteractor.changeEmail(params) + .then((res) => { + if (res.status === 'success') { + this.changedEmail = true + this.changeEmailError = false + } else { + this.changedEmail = false + this.changeEmailError = res.error + } + }) + }, + logout () { + this.$store.dispatch('logout') + this.$router.replace('/') + }, + revokeToken (id) { + if (window.confirm(`${this.$i18n.t('settings.revoke_token')}?`)) { + this.$store.dispatch('revokeToken', id) + } + } + } +} + +export default SecurityTab diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.vue b/src/components/settings_modal/tabs/security_tab/security_tab.vue new file mode 100644 index 00000000..3d32d73d --- /dev/null +++ b/src/components/settings_modal/tabs/security_tab/security_tab.vue @@ -0,0 +1,143 @@ +<template> + <div :label="$t('settings.security_tab')"> + <div class="setting-item"> + <h2>{{ $t('settings.change_email') }}</h2> + <div> + <p>{{ $t('settings.new_email') }}</p> + <input + v-model="newEmail" + type="email" + autocomplete="email" + > + </div> + <div> + <p>{{ $t('settings.current_password') }}</p> + <input + v-model="changeEmailPassword" + type="password" + autocomplete="current-password" + > + </div> + <button + class="btn btn-default" + @click="changeEmail" + > + {{ $t('general.submit') }} + </button> + <p v-if="changedEmail"> + {{ $t('settings.changed_email') }} + </p> + <template v-if="changeEmailError !== false"> + <p>{{ $t('settings.change_email_error') }}</p> + <p>{{ changeEmailError }}</p> + </template> + </div> + + <div class="setting-item"> + <h2>{{ $t('settings.change_password') }}</h2> + <div> + <p>{{ $t('settings.current_password') }}</p> + <input + v-model="changePasswordInputs[0]" + type="password" + > + </div> + <div> + <p>{{ $t('settings.new_password') }}</p> + <input + v-model="changePasswordInputs[1]" + type="password" + > + </div> + <div> + <p>{{ $t('settings.confirm_new_password') }}</p> + <input + v-model="changePasswordInputs[2]" + type="password" + > + </div> + <button + class="btn btn-default" + @click="changePassword" + > + {{ $t('general.submit') }} + </button> + <p v-if="changedPassword"> + {{ $t('settings.changed_password') }} + </p> + <p v-else-if="changePasswordError !== false"> + {{ $t('settings.change_password_error') }} + </p> + <p v-if="changePasswordError"> + {{ changePasswordError }} + </p> + </div> + + <div class="setting-item"> + <h2>{{ $t('settings.oauth_tokens') }}</h2> + <table class="oauth-tokens"> + <thead> + <tr> + <th>{{ $t('settings.app_name') }}</th> + <th>{{ $t('settings.valid_until') }}</th> + <th /> + </tr> + </thead> + <tbody> + <tr + v-for="oauthToken in oauthTokens" + :key="oauthToken.id" + > + <td>{{ oauthToken.appName }}</td> + <td>{{ oauthToken.validUntil }}</td> + <td class="actions"> + <button + class="btn btn-default" + @click="revokeToken(oauthToken.id)" + > + {{ $t('settings.revoke_token') }} + </button> + </td> + </tr> + </tbody> + </table> + </div> + <mfa /> + <div class="setting-item"> + <h2>{{ $t('settings.delete_account') }}</h2> + <p v-if="!deletingAccount"> + {{ $t('settings.delete_account_description') }} + </p> + <div v-if="deletingAccount"> + <p>{{ $t('settings.delete_account_instructions') }}</p> + <p>{{ $t('login.password') }}</p> + <input + v-model="deleteAccountConfirmPasswordInput" + type="password" + > + <button + class="btn btn-default" + @click="deleteAccount" + > + {{ $t('settings.delete_account') }} + </button> + </div> + <p v-if="deleteAccountError !== false"> + {{ $t('settings.delete_account_error') }} + </p> + <p v-if="deleteAccountError"> + {{ deleteAccountError }} + </p> + <button + v-if="!deletingAccount" + class="btn btn-default" + @click="confirmDelete" + > + {{ $t('general.submit') }} + </button> + </div> + </div> +</template> + +<script src="./security_tab.js"></script> +<!-- <style lang="scss" src="./profile.scss"></style> --> diff --git a/src/components/settings_modal/tabs/theme_tab/preview.vue b/src/components/settings_modal/tabs/theme_tab/preview.vue new file mode 100644 index 00000000..9d984659 --- /dev/null +++ b/src/components/settings_modal/tabs/theme_tab/preview.vue @@ -0,0 +1,117 @@ +<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"> + {{ $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 path="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> + + <div class="icons"> + <i + style="color: var(--cBlue)" + class="button-icon icon-reply" + /> + <i + style="color: var(--cGreen)" + class="button-icon icon-retweet" + /> + <i + style="color: var(--cOrange)" + class="button-icon icon-star" + /> + <i + style="color: var(--cRed)" + class="button-icon icon-cancel" + /> + </div> + </div> + </div> + + <div class="after-post"> + <div class="avatar-alt"> + :^) + </div> + <div class="content"> + <i18n + path="settings.style.preview.fine_print" + tag="span" + class="faint" + > + <a style="color: var(--faintLink)"> + {{ $t('settings.style.preview.faint_link') }} + </a> + </i18n> + </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"> + {{ $t('settings.style.preview.button') }} + </button> + </div> + </div> + </div> + </div> +</template> + +<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_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js new file mode 100644 index 00000000..9d61b0c4 --- /dev/null +++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.js @@ -0,0 +1,759 @@ +import { set, delete as del } from 'vue' +import { + rgb2hex, + hex2rgb, + getContrastRatioLayers +} 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 { + SLOT_INHERITANCE +} from 'src/services/theme_data/pleromafe.js' +import { + CURRENT_VERSION, + OPACITIES, + getLayers, + getOpacitySlot +} from 'src/services/theme_data/theme_data.service.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' +import ShadowControl from 'src/components/shadow_control/shadow_control.vue' +import FontControl from 'src/components/font_control/font_control.vue' +import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue' +import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js' +import ExportImport from 'src/components/export_import/export_import.vue' +import Checkbox from 'src/components/checkbox/checkbox.vue' + +import Preview from './preview.vue' + +// List of color values used in v1 +const v1OnlyNames = [ + 'bg', + 'fg', + 'text', + 'link', + 'cRed', + 'cGreen', + 'cBlue', + 'cOrange' +].map(_ => _ + 'ColorLocal') + +const colorConvert = (color) => { + if (color.startsWith('--') || color === 'transparent') { + return color + } else { + return hex2rgb(color) + } +} + +export default { + data () { + return { + availableStyles: [], + selected: this.$store.getters.mergedConfig.theme, + themeWarning: undefined, + tempImportFile: undefined, + engineVersion: 0, + + previewShadows: {}, + previewColors: {}, + previewRadii: {}, + previewFonts: {}, + + shadowsInvalid: true, + colorsInvalid: true, + radiiInvalid: true, + + keepColor: false, + keepShadows: false, + keepOpacity: false, + keepRoundness: false, + keepFonts: false, + + ...Object.keys(SLOT_INHERITANCE) + .map(key => [key, '']) + .reduce((acc, [key, val]) => ({ ...acc, [ key + 'ColorLocal' ]: val }), {}), + + ...Object.keys(OPACITIES) + .map(key => [key, '']) + .reduce((acc, [key, val]) => ({ ...acc, [ key + 'OpacityLocal' ]: val }), {}), + + shadowSelected: undefined, + shadowsLocal: {}, + fontsLocal: {}, + + btnRadiusLocal: '', + inputRadiusLocal: '', + checkboxRadiusLocal: '', + panelRadiusLocal: '', + avatarRadiusLocal: '', + avatarAltRadiusLocal: '', + attachmentRadiusLocal: '', + tooltipRadiusLocal: '' + } + }, + created () { + const self = this + + 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 + }) + }, + mounted () { + this.loadThemeFromLocalStorage() + if (typeof this.shadowSelected === 'undefined') { + this.shadowSelected = this.shadowsAvailable[0] + } + }, + computed: { + themeWarningHelp () { + if (!this.themeWarning) return + const t = this.$t + const pre = 'settings.style.switcher.help.' + const { + origin, + themeEngineVersion, + type, + noActionsPossible + } = this.themeWarning + if (origin === 'file') { + // Loaded v2 theme from file + if (themeEngineVersion === 2 && type === 'wrong_version') { + return t(pre + 'v2_imported') + } + if (themeEngineVersion > CURRENT_VERSION) { + return t(pre + 'future_version_imported') + ' ' + + ( + noActionsPossible + ? t(pre + 'snapshot_missing') + : t(pre + 'snapshot_present') + ) + } + if (themeEngineVersion < CURRENT_VERSION) { + return t(pre + 'future_version_imported') + ' ' + + ( + noActionsPossible + ? t(pre + 'snapshot_missing') + : t(pre + 'snapshot_present') + ) + } + } else if (origin === 'localStorage') { + if (type === 'snapshot_source_mismatch') { + return t(pre + 'snapshot_source_mismatch') + } + // FE upgraded from v2 + if (themeEngineVersion === 2) { + return t(pre + 'upgraded_from_v2') + } + // Admin downgraded FE + if (themeEngineVersion > CURRENT_VERSION) { + return t(pre + 'fe_downgraded') + ' ' + + ( + noActionsPossible + ? t(pre + 'migration_snapshot_ok') + : t(pre + 'migration_snapshot_gone') + ) + } + // Admin upgraded FE + if (themeEngineVersion < CURRENT_VERSION) { + return t(pre + 'fe_upgraded') + ' ' + + ( + noActionsPossible + ? t(pre + 'migration_snapshot_ok') + : t(pre + 'migration_snapshot_gone') + ) + } + } + }, + selectedVersion () { + return Array.isArray(this.selected) ? 1 : 2 + }, + currentColors () { + return Object.keys(SLOT_INHERITANCE) + .map(key => [key, this[key + 'ColorLocal']]) + .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {}) + }, + currentOpacity () { + return Object.keys(OPACITIES) + .map(key => [key, this[key + 'OpacityLocal']]) + .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {}) + }, + currentRadii () { + return { + btn: this.btnRadiusLocal, + input: this.inputRadiusLocal, + checkbox: this.checkboxRadiusLocal, + panel: this.panelRadiusLocal, + avatar: this.avatarRadiusLocal, + avatarAlt: this.avatarAltRadiusLocal, + tooltip: this.tooltipRadiusLocal, + attachment: this.attachmentRadiusLocal + } + }, + 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 { + if (!this.previewTheme.colors.bg) return {} + const colors = this.previewTheme.colors + const opacity = this.previewTheme.opacity + if (!colors.bg) return {} + const hints = (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 + }) + const colorsConverted = Object.entries(colors).reduce((acc, [key, value]) => ({ ...acc, [key]: colorConvert(value) }), {}) + + const ratios = Object.entries(SLOT_INHERITANCE).reduce((acc, [key, value]) => { + const slotIsBaseText = key === 'text' || key === 'link' + const slotIsText = slotIsBaseText || ( + typeof value === 'object' && value !== null && value.textColor + ) + if (!slotIsText) return acc + const { layer, variant } = slotIsBaseText ? { layer: 'bg' } : value + const background = variant || layer + const opacitySlot = getOpacitySlot(background) + const textColors = [ + key, + ...(background === 'bg' ? ['cRed', 'cGreen', 'cBlue', 'cOrange'] : []) + ] + + const layers = getLayers( + layer, + variant || layer, + opacitySlot, + colorsConverted, + opacity + ) + + return { + ...acc, + ...textColors.reduce((acc, textColorKey) => { + const newKey = slotIsBaseText + ? 'bg' + textColorKey[0].toUpperCase() + textColorKey.slice(1) + : textColorKey + return { + ...acc, + [newKey]: getContrastRatioLayers( + colorsConverted[textColorKey], + layers, + colorsConverted[textColorKey] + ) + } + }, {}) + } + }, {}) + + return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {}) + } catch (e) { + console.warn('Failure computing contrasts', e) + } + }, + previewRules () { + if (!this.preview.rules) return '' + return [ + ...Object.values(this.preview.rules), + 'color: var(--text)', + 'font-family: var(--interfaceFont, sans-serif)' + ].join(';') + }, + shadowsAvailable () { + return Object.keys(DEFAULT_SHADOWS).sort() + }, + currentShadowOverriden: { + get () { + return !!this.currentShadow + }, + set (val) { + if (val) { + set(this.shadowsLocal, this.shadowSelected, this.currentShadowFallback.map(_ => Object.assign({}, _))) + } else { + del(this.shadowsLocal, this.shadowSelected) + } + } + }, + currentShadowFallback () { + return (this.previewTheme.shadows || {})[this.shadowSelected] + }, + currentShadow: { + get () { + return this.shadowsLocal[this.shadowSelected] + }, + set (v) { + set(this.shadowsLocal, this.shadowSelected, v) + } + }, + themeValid () { + return !this.shadowsInvalid && !this.colorsInvalid && !this.radiiInvalid + }, + exportedTheme () { + const saveEverything = ( + !this.keepFonts && + !this.keepShadows && + !this.keepOpacity && + !this.keepRoundness && + !this.keepColor + ) + + const source = { + themeEngineVersion: CURRENT_VERSION + } + + if (this.keepFonts || saveEverything) { + source.fonts = this.fontsLocal + } + if (this.keepShadows || saveEverything) { + source.shadows = this.shadowsLocal + } + if (this.keepOpacity || saveEverything) { + source.opacity = this.currentOpacity + } + if (this.keepColor || saveEverything) { + source.colors = this.currentColors + } + if (this.keepRoundness || saveEverything) { + source.radii = this.currentRadii + } + + const theme = { + themeEngineVersion: CURRENT_VERSION, + ...this.previewTheme + } + + return { + // To separate from other random JSON files and possible future source formats + _pleroma_theme_version: 2, theme, source + } + } + }, + components: { + ColorInput, + OpacityInput, + RangeInput, + ContrastRatio, + ShadowControl, + FontControl, + TabSwitcher, + Preview, + ExportImport, + Checkbox + }, + methods: { + loadTheme ( + { + theme, + source, + _pleroma_theme_version: fileVersion + }, + origin, + forceUseSource = false + ) { + this.dismissWarning() + if (!source && !theme) { + throw new Error('Can\'t load theme: empty') + } + const version = (origin === 'localStorage' && !theme.colors) + ? 'l1' + : fileVersion + const snapshotEngineVersion = (theme || {}).themeEngineVersion + const themeEngineVersion = (source || {}).themeEngineVersion || 2 + const versionsMatch = themeEngineVersion === CURRENT_VERSION + const sourceSnapshotMismatch = ( + theme !== undefined && + source !== undefined && + themeEngineVersion !== snapshotEngineVersion + ) + // Force loading of source if user requested it or if snapshot + // is unavailable + const forcedSourceLoad = (source && forceUseSource) || !theme + if (!(versionsMatch && !sourceSnapshotMismatch) && + !forcedSourceLoad && + version !== 'l1' && + origin !== 'defaults' + ) { + if (sourceSnapshotMismatch && origin === 'localStorage') { + this.themeWarning = { + origin, + themeEngineVersion, + type: 'snapshot_source_mismatch' + } + } else if (!theme) { + this.themeWarning = { + origin, + noActionsPossible: true, + themeEngineVersion, + type: 'no_snapshot_old_version' + } + } else if (!versionsMatch) { + this.themeWarning = { + origin, + noActionsPossible: !source, + themeEngineVersion, + type: 'wrong_version' + } + } + } + this.normalizeLocalState(theme, version, source, forcedSourceLoad) + }, + forceLoadLocalStorage () { + this.loadThemeFromLocalStorage(true) + }, + dismissWarning () { + this.themeWarning = undefined + this.tempImportFile = undefined + }, + forceLoad () { + const { origin } = this.themeWarning + switch (origin) { + case 'localStorage': + this.loadThemeFromLocalStorage(true) + break + case 'file': + this.onImport(this.tempImportFile, true) + break + } + this.dismissWarning() + }, + forceSnapshot () { + const { origin } = this.themeWarning + switch (origin) { + case 'localStorage': + this.loadThemeFromLocalStorage(false, true) + break + case 'file': + console.err('Forcing snapshout from file is not supported yet') + break + } + 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 { + this.loadTheme( + { + theme, + source: forceSnapshot ? theme : source + }, + 'localStorage', + confirmLoadSource + ) + } + }, + setCustomTheme () { + this.$store.dispatch('setOption', { + name: 'customTheme', + value: { + themeEngineVersion: CURRENT_VERSION, + ...this.previewTheme + } + }) + this.$store.dispatch('setOption', { + name: 'customThemeSource', + value: { + themeEngineVersion: CURRENT_VERSION, + shadows: this.shadowsLocal, + fonts: this.fontsLocal, + opacity: this.currentOpacity, + colors: this.currentColors, + radii: this.currentRadii + } + }) + }, + updatePreviewColorsAndShadows () { + this.previewColors = 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 + ) + }, + onImport (parsed, forceSource = false) { + this.tempImportFile = parsed + this.loadTheme(parsed, 'file', forceSource) + }, + importValidator (parsed) { + const version = parsed._pleroma_theme_version + return version >= 1 || version <= 2 + }, + clearAll () { + this.loadThemeFromLocalStorage() + }, + + // Clears all the extra stuff when loading V1 theme + clearV1 () { + Object.keys(this.$data) + .filter(_ => _.endsWith('ColorLocal') || _.endsWith('OpacityLocal')) + .filter(_ => !v1OnlyNames.includes(_)) + .forEach(key => { + set(this.$data, key, undefined) + }) + }, + + clearRoundness () { + Object.keys(this.$data) + .filter(_ => _.endsWith('RadiusLocal')) + .forEach(key => { + set(this.$data, key, undefined) + }) + }, + + clearOpacity () { + Object.keys(this.$data) + .filter(_ => _.endsWith('OpacityLocal')) + .forEach(key => { + set(this.$data, key, undefined) + }) + }, + + clearShadows () { + this.shadowsLocal = {} + }, + + clearFonts () { + this.fontsLocal = {} + }, + + /** + * This applies stored theme data onto form. Supports three versions of data: + * v3 (version >= 3) - newest version of themes which supports snapshots for better compatiblity + * v2 (version = 2) - newer version of themes. + * v1 (version = 1) - older version of themes (import from file) + * v1l (version = l1) - older version of theme (load from local storage) + * v1 and v1l differ because of way themes were stored/exported. + * @param {Object} theme - theme data (snapshot) + * @param {Number} version - version of data. 0 means try to guess based on data. "l1" means v1, locastorage type + * @param {Object} source - theme source - this will be used if compatible + * @param {Boolean} source - by default source won't be used if version doesn't match since it might render differently + * this allows importing source anyway + */ + normalizeLocalState (theme, version = 0, source, forceSource = false) { + let input + if (typeof source !== 'undefined') { + if (forceSource || source.themeEngineVersion === CURRENT_VERSION) { + input = source + version = source.themeEngineVersion + } else { + input = theme + } + } else { + input = theme + } + + const radii = input.radii || input + const opacity = input.opacity + const shadows = input.shadows || {} + const fonts = input.fonts || {} + const colors = !input.themeEngineVersion + ? colors2to3(input.colors || input) + : input.colors || input + + if (version === 0) { + if (input.version) version = input.version + // Old v1 naming: fg is text, btn is foreground + if (typeof colors.text === 'undefined' && typeof colors.fg !== 'undefined') { + version = 1 + } + // New v2 naming: text is text, fg is foreground + if (typeof colors.text !== 'undefined' && typeof colors.fg !== 'undefined') { + version = 2 + } + } + + this.engineVersion = version + + // Stuff that differs between V1 and V2 + if (version === 1) { + this.fgColorLocal = rgb2hex(colors.btn) + this.textColorLocal = rgb2hex(colors.fg) + } + + if (!this.keepColor) { + this.clearV1() + const keys = new Set(version !== 1 ? Object.keys(SLOT_INHERITANCE) : []) + if (version === 1 || version === 'l1') { + keys + .add('bg') + .add('link') + .add('cRed') + .add('cBlue') + .add('cGreen') + .add('cOrange') + } + + keys.forEach(key => { + const color = colors[key] + const hex = rgb2hex(colors[key]) + this[key + 'ColorLocal'] = hex === '#aN' ? color : hex + }) + } + + if (opacity && !this.keepOpacity) { + this.clearOpacity() + Object.entries(opacity).forEach(([k, v]) => { + if (typeof v === 'undefined' || v === null || Number.isNaN(v)) return + this[k + 'OpacityLocal'] = v + }) + } + + if (!this.keepRoundness) { + this.clearRoundness() + Object.entries(radii).forEach(([k, v]) => { + // 'Radius' is kept mostly for v1->v2 localstorage transition + const key = k.endsWith('Radius') ? k.split('Radius')[0] : k + this[key + 'RadiusLocal'] = v + }) + } + + if (!this.keepShadows) { + this.clearShadows() + if (version === 2) { + this.shadowsLocal = shadows2to3(shadows, this.previewTheme.opacity) + } else { + this.shadowsLocal = shadows + } + this.shadowSelected = this.shadowsAvailable[0] + } + + if (!this.keepFonts) { + this.clearFonts() + this.fontsLocal = fonts + } + } + }, + watch: { + currentRadii () { + try { + this.previewRadii = generateRadii({ radii: this.currentRadii }) + this.radiiInvalid = false + } catch (e) { + this.radiiInvalid = true + console.warn(e) + } + }, + shadowsLocal: { + handler () { + if (Object.getOwnPropertyNames(this.previewColors).length === 1) return + try { + this.updatePreviewColorsAndShadows() + this.shadowsInvalid = false + } catch (e) { + this.shadowsInvalid = true + console.warn(e) + } + }, + deep: true + }, + fontsLocal: { + handler () { + try { + this.previewFonts = generateFonts({ fonts: this.fontsLocal }) + this.fontsInvalid = false + } catch (e) { + this.fontsInvalid = true + console.warn(e) + } + }, + deep: true + }, + currentColors () { + try { + this.updatePreviewColorsAndShadows() + this.colorsInvalid = false + this.shadowsInvalid = false + } catch (e) { + this.colorsInvalid = true + this.shadowsInvalid = true + console.warn(e) + } + }, + currentOpacity () { + try { + this.updatePreviewColorsAndShadows() + } catch (e) { + console.warn(e) + } + }, + selected () { + this.dismissWarning() + if (this.selectedVersion === 1) { + if (!this.keepRoundness) { + this.clearRoundness() + } + + if (!this.keepShadows) { + this.clearShadows() + } + + if (!this.keepOpacity) { + this.clearOpacity() + } + + if (!this.keepColor) { + this.clearV1() + + this.bgColorLocal = this.selected[1] + this.fgColorLocal = this.selected[2] + this.textColorLocal = this.selected[3] + this.linkColorLocal = this.selected[4] + this.cRedColorLocal = this.selected[5] + this.cGreenColorLocal = this.selected[6] + this.cBlueColorLocal = this.selected[7] + this.cOrangeColorLocal = this.selected[8] + } + } else if (this.selectedVersion >= 2) { + this.normalizeLocalState(this.selected.theme, 2, this.selected.source) + } + } + } +} diff --git a/src/components/style_switcher/style_switcher.scss b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss similarity index 90% rename from src/components/style_switcher/style_switcher.scss rename to src/components/settings_modal/tabs/theme_tab/theme_tab.scss index 135c113a..926eceff 100644 --- a/src/components/style_switcher/style_switcher.scss +++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss @@ -1,5 +1,16 @@ -@import '../../_variables.scss'; -.style-switcher { +@import 'src/_variables.scss'; +.theme-tab { + padding-bottom: 2em; + .theme-warning { + display: flex; + align-items: baseline; + margin-bottom: .5em; + .buttons { + .btn { + margin-bottom: .5em; + } + } + } .preset-switcher { margin-right: 1em; } @@ -15,26 +26,23 @@ &.disabled { input, select { - &:not(.exclude-disabled) { - opacity: .5 - } + opacity: .5 } } + .opt { + margin: .5em; + } + + .color-input { + flex: 0 0 0; + } + input, select { min-width: 3em; margin: 0; flex: 0; - &[type=color] { - padding: 1px; - cursor: pointer; - height: 29px; - min-width: 2em; - border: none; - align-self: stretch; - } - &[type=number] { min-width: 5em; } @@ -42,22 +50,11 @@ &[type=range] { flex: 1; min-width: 3em; - } - - &[type=checkbox] + label { - margin: 6px 0; - } - - &:not([type=number]):not([type=text]) { align-self: flex-start; } } } - .tab-switcher { - margin: 0 -1em; - } - .reset-container { flex-wrap: wrap; } @@ -98,20 +95,25 @@ align-items: baseline; width: 100%; min-height: 30px; - - .btn { - min-width: 1px; - flex: 0 auto; - padding: 0 1em; - } + margin-bottom: 1em; p { flex: 1; margin: 0; margin-right: .5em; } + } - margin-bottom: 1em; + .tab-header-buttons { + display: flex; + flex-direction: column; + + .btn { + min-width: 1px; + flex: 0 auto; + padding: 0 1em; + margin-bottom: .5em; + } } .shadow-selector { @@ -161,7 +163,7 @@ border-bottom: 1px dashed; border-color: $fallback--border; border-color: var(--border, $fallback--border); - margin: 1em -1em 0; + margin: 1em 0; padding: 1em; background: var(--body-background-image); background-size: cover; @@ -328,6 +330,14 @@ padding: 20px; } + .apply-container { + .btn { + min-height: 28px; + min-width: 10em; + padding: 0 2em; + } + } + .btn { margin-left: .25em; margin-right: .25em; diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue similarity index 51% rename from src/components/style_switcher/style_switcher.vue rename to src/components/settings_modal/tabs/theme_tab/theme_tab.vue index ad032041..fcfad23b 100644 --- a/src/components/style_switcher/style_switcher.vue +++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue @@ -1,8 +1,54 @@ <template> - <div class="style-switcher"> + <div class="theme-tab"> <div class="presets-container"> <div class="save-load"> - <export-import + <div + v-if="themeWarning" + class="theme-warning" + > + <div class="alert warning"> + {{ themeWarningHelp }} + </div> + <div class="buttons"> + <template v-if="themeWarning.type === 'snapshot_source_mismatch'"> + <button + class="btn" + @click="forceLoad" + > + {{ $t('settings.style.switcher.use_source') }} + </button> + <button + class="btn" + @click="forceSnapshot" + > + {{ $t('settings.style.switcher.use_snapshot') }} + </button> + </template> + <template v-else-if="themeWarning.noActionsPossible"> + <button + class="btn" + @click="dismissWarning" + > + {{ $t('general.dismiss') }} + </button> + </template> + <template v-else> + <button + class="btn" + @click="forceLoad" + > + {{ $t('settings.style.switcher.load_theme') }} + </button> + <button + class="btn" + @click="dismissWarning" + > + {{ $t('settings.style.switcher.keep_as_is') }} + </button> + </template> + </div> + </div> + <ExportImport :export-object="exportedTheme" :export-label="$t("settings.export_theme")" :import-label="$t("settings.import_theme")" @@ -27,8 +73,8 @@ :key="style.name" :value="style" :style="{ - backgroundColor: style[1] || style.theme.colors.bg, - color: style[3] || style.theme.colors.text + backgroundColor: style[1] || (style.theme || style.source).colors.bg, + color: style[3] || (style.theme || style.source).colors.text }" > {{ style[0] || style.name }} @@ -38,7 +84,7 @@ </label> </div> </template> - </export-import> + </ExportImport> </div> <div class="save-load-options"> <span class="keep-option"> @@ -70,9 +116,7 @@ </div> </div> - <div class="preview-container"> - <preview :style="previewRules" /> - </div> + <preview :style="previewRules" /> <keep-alive> <tab-switcher key="style-tweak"> @@ -82,18 +126,20 @@ > <div class="tab-header"> <p>{{ $t('settings.theme_help') }}</p> - <button - class="btn" - @click="clearOpacity" - > - {{ $t('settings.style.switcher.clear_opacity') }} - </button> - <button - class="btn" - @click="clearV1" - > - {{ $t('settings.style.switcher.clear_all') }} - </button> + <div class="tab-header-buttons"> + <button + class="btn" + @click="clearOpacity" + > + {{ $t('settings.style.switcher.clear_opacity') }} + </button> + <button + class="btn" + @click="clearV1" + > + {{ $t('settings.style.switcher.clear_all') }} + </button> + </div> </div> <p>{{ $t('settings.theme_help_v2_1') }}</p> <h4>{{ $t('settings.style.common_colors.main') }}</h4> @@ -106,7 +152,7 @@ <OpacityInput v-model="bgOpacityLocal" name="bgOpacity" - :fallback="previewTheme.opacity.bg || 1" + :fallback="previewTheme.opacity.bg" /> <ColorInput v-model="textColorLocal" @@ -114,10 +160,19 @@ :label="$t('settings.text')" /> <ContrastRatio :contrast="previewContrast.bgText" /> + <ColorInput + v-model="accentColorLocal" + name="accentColor" + :fallback="previewTheme.colors.link" + :label="$t('settings.accent')" + :show-optional-tickbox="typeof linkColorLocal !== 'undefined'" + /> <ColorInput v-model="linkColorLocal" name="linkColor" + :fallback="previewTheme.colors.accent" :label="$t('settings.links')" + :show-optional-tickbox="typeof accentColorLocal !== 'undefined'" /> <ContrastRatio :contrast="previewContrast.bgLink" /> </div> @@ -148,13 +203,13 @@ name="cRedColor" :label="$t('settings.cRed')" /> - <ContrastRatio :contrast="previewContrast.bgRed" /> + <ContrastRatio :contrast="previewContrast.bgCRed" /> <ColorInput v-model="cBlueColorLocal" name="cBlueColor" :label="$t('settings.cBlue')" /> - <ContrastRatio :contrast="previewContrast.bgBlue" /> + <ContrastRatio :contrast="previewContrast.bgCBlue" /> </div> <div class="color-item"> <ColorInput @@ -162,13 +217,13 @@ name="cGreenColor" :label="$t('settings.cGreen')" /> - <ContrastRatio :contrast="previewContrast.bgGreen" /> + <ContrastRatio :contrast="previewContrast.bgCGreen" /> <ColorInput v-model="cOrangeColorLocal" name="cOrangeColor" :label="$t('settings.cOrange')" /> - <ContrastRatio :contrast="previewContrast.bgOrange" /> + <ContrastRatio :contrast="previewContrast.bgCOrange" /> </div> <p>{{ $t('settings.theme_help_v2_2') }}</p> </div> @@ -193,6 +248,14 @@ </button> </div> <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.post') }}</h4> + <ColorInput + v-model="postLinkColorLocal" + name="postLinkColor" + :fallback="previewTheme.colors.accent" + :label="$t('settings.links')" + /> + <ContrastRatio :contrast="previewContrast.postLink" /> <h4>{{ $t('settings.style.advanced_colors.alert') }}</h4> <ColorInput v-model="alertErrorColorLocal" @@ -200,14 +263,53 @@ :label="$t('settings.style.advanced_colors.alert_error')" :fallback="previewTheme.colors.alertError" /> - <ContrastRatio :contrast="previewContrast.alertError" /> + <ColorInput + v-model="alertErrorTextColorLocal" + name="alertErrorText" + :label="$t('settings.text')" + :fallback="previewTheme.colors.alertErrorText" + /> + <ContrastRatio + :contrast="previewContrast.alertErrorText" + large="true" + /> <ColorInput v-model="alertWarningColorLocal" name="alertWarning" :label="$t('settings.style.advanced_colors.alert_warning')" :fallback="previewTheme.colors.alertWarning" /> - <ContrastRatio :contrast="previewContrast.alertWarning" /> + <ColorInput + v-model="alertWarningTextColorLocal" + name="alertWarningText" + :label="$t('settings.text')" + :fallback="previewTheme.colors.alertWarningText" + /> + <ContrastRatio + :contrast="previewContrast.alertWarningText" + large="true" + /> + <ColorInput + v-model="alertNeutralColorLocal" + name="alertNeutral" + :label="$t('settings.style.advanced_colors.alert_neutral')" + :fallback="previewTheme.colors.alertNeutral" + /> + <ColorInput + v-model="alertNeutralTextColorLocal" + name="alertNeutralText" + :label="$t('settings.text')" + :fallback="previewTheme.colors.alertNeutralText" + /> + <ContrastRatio + :contrast="previewContrast.alertNeutralText" + large="true" + /> + <OpacityInput + v-model="alertOpacityLocal" + name="alertOpacity" + :fallback="previewTheme.opacity.alert" + /> </div> <div class="color-item"> <h4>{{ $t('settings.style.advanced_colors.badge') }}</h4> @@ -217,19 +319,30 @@ :label="$t('settings.style.advanced_colors.badge_notification')" :fallback="previewTheme.colors.badgeNotification" /> + <ColorInput + v-model="badgeNotificationTextColorLocal" + name="badgeNotificationText" + :label="$t('settings.text')" + :fallback="previewTheme.colors.badgeNotificationText" + /> + <ContrastRatio + :contrast="previewContrast.badgeNotificationText" + large="true" + /> </div> <div class="color-item"> <h4>{{ $t('settings.style.advanced_colors.panel_header') }}</h4> <ColorInput v-model="panelColorLocal" name="panelColor" - :fallback="fgColorLocal" + :fallback="previewTheme.colors.panel" :label="$t('settings.background')" /> <OpacityInput v-model="panelOpacityLocal" name="panelOpacity" - :fallback="previewTheme.opacity.panel || 1" + :fallback="previewTheme.opacity.panel" + :disabled="panelColorLocal === 'transparent'" /> <ColorInput v-model="panelTextColorLocal" @@ -239,7 +352,7 @@ /> <ContrastRatio :contrast="previewContrast.panelText" - large="1" + large="true" /> <ColorInput v-model="panelLinkColorLocal" @@ -249,7 +362,7 @@ /> <ContrastRatio :contrast="previewContrast.panelLink" - large="1" + large="true" /> </div> <div class="color-item"> @@ -257,7 +370,7 @@ <ColorInput v-model="topBarColorLocal" name="topBarColor" - :fallback="fgColorLocal" + :fallback="previewTheme.colors.topBar" :label="$t('settings.background')" /> <ColorInput @@ -280,13 +393,14 @@ <ColorInput v-model="inputColorLocal" name="inputColor" - :fallback="fgColorLocal" + :fallback="previewTheme.colors.input" :label="$t('settings.background')" /> <OpacityInput v-model="inputOpacityLocal" name="inputOpacity" - :fallback="previewTheme.opacity.input || 1" + :fallback="previewTheme.opacity.input" + :disabled="inputColorLocal === 'transparent'" /> <ColorInput v-model="inputTextColorLocal" @@ -301,13 +415,14 @@ <ColorInput v-model="btnColorLocal" name="btnColor" - :fallback="fgColorLocal" + :fallback="previewTheme.colors.btn" :label="$t('settings.background')" /> <OpacityInput v-model="btnOpacityLocal" name="btnOpacity" - :fallback="previewTheme.opacity.btn || 1" + :fallback="previewTheme.opacity.btn" + :disabled="btnColorLocal === 'transparent'" /> <ColorInput v-model="btnTextColorLocal" @@ -316,6 +431,124 @@ :label="$t('settings.text')" /> <ContrastRatio :contrast="previewContrast.btnText" /> + <ColorInput + v-model="btnPanelTextColorLocal" + name="btnPanelTextColor" + :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" + :label="$t('settings.style.advanced_colors.top_bar')" + /> + <ContrastRatio :contrast="previewContrast.btnTopBarText" /> + <h5>{{ $t('settings.style.advanced_colors.pressed') }}</h5> + <ColorInput + v-model="btnPressedColorLocal" + name="btnPressedColor" + :fallback="previewTheme.colors.btnPressed" + :label="$t('settings.background')" + /> + <ColorInput + v-model="btnPressedTextColorLocal" + name="btnPressedTextColor" + :fallback="previewTheme.colors.btnPressedText" + :label="$t('settings.text')" + /> + <ContrastRatio :contrast="previewContrast.btnPressedText" /> + <ColorInput + v-model="btnPressedPanelTextColorLocal" + name="btnPressedPanelTextColor" + :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" + :label="$t('settings.style.advanced_colors.top_bar')" + /> + <ContrastRatio :contrast="previewContrast.btnPressedTopBarText" /> + <h5>{{ $t('settings.style.advanced_colors.disabled') }}</h5> + <ColorInput + v-model="btnDisabledColorLocal" + name="btnDisabledColor" + :fallback="previewTheme.colors.btnDisabled" + :label="$t('settings.background')" + /> + <ColorInput + v-model="btnDisabledTextColorLocal" + name="btnDisabledTextColor" + :fallback="previewTheme.colors.btnDisabledText" + :label="$t('settings.text')" + /> + <ColorInput + v-model="btnDisabledPanelTextColorLocal" + name="btnDisabledPanelTextColor" + :fallback="previewTheme.colors.btnDisabledPanelText" + :label="$t('settings.style.advanced_colors.panel_header')" + /> + <ColorInput + v-model="btnDisabledTopBarTextColorLocal" + name="btnDisabledTopBarTextColor" + :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" + :label="$t('settings.background')" + /> + <ColorInput + v-model="btnToggledTextColorLocal" + name="btnToggledTextColor" + :fallback="previewTheme.colors.btnToggledText" + :label="$t('settings.text')" + /> + <ContrastRatio :contrast="previewContrast.btnToggledText" /> + <ColorInput + v-model="btnToggledPanelTextColorLocal" + name="btnToggledPanelTextColor" + :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" + :label="$t('settings.style.advanced_colors.top_bar')" + /> + <ContrastRatio :contrast="previewContrast.btnToggledTopBarText" /> + </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.tabs') }}</h4> + <ColorInput + v-model="tabColorLocal" + name="tabColor" + :fallback="previewTheme.colors.tab" + :label="$t('settings.background')" + /> + <ColorInput + v-model="tabTextColorLocal" + name="tabTextColor" + :fallback="previewTheme.colors.tabText" + :label="$t('settings.text')" + /> + <ContrastRatio :contrast="previewContrast.tabText" /> + <ColorInput + v-model="tabActiveTextColorLocal" + name="tabActiveTextColor" + :fallback="previewTheme.colors.tabActiveText" + :label="$t('settings.text')" + /> + <ContrastRatio :contrast="previewContrast.tabActiveText" /> </div> <div class="color-item"> <h4>{{ $t('settings.style.advanced_colors.borders') }}</h4> @@ -328,7 +561,8 @@ <OpacityInput v-model="borderOpacityLocal" name="borderOpacity" - :fallback="previewTheme.opacity.border || 1" + :fallback="previewTheme.opacity.border" + :disabled="borderColorLocal === 'transparent'" /> </div> <div class="color-item"> @@ -336,7 +570,7 @@ <ColorInput v-model="faintColorLocal" name="faintColor" - :fallback="previewTheme.colors.faint || 1" + :fallback="previewTheme.colors.faint" :label="$t('settings.text')" /> <ColorInput @@ -354,9 +588,146 @@ <OpacityInput v-model="faintOpacityLocal" name="faintOpacity" - :fallback="previewTheme.opacity.faint || 0.5" + :fallback="previewTheme.opacity.faint" /> </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.underlay') }}</h4> + <ColorInput + v-model="underlayColorLocal" + name="underlay" + :label="$t('settings.style.advanced_colors.underlay')" + :fallback="previewTheme.colors.underlay" + /> + <OpacityInput + v-model="underlayOpacityLocal" + name="underlayOpacity" + :fallback="previewTheme.opacity.underlay" + :disabled="underlayOpacityLocal === 'transparent'" + /> + </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.poll') }}</h4> + <ColorInput + v-model="pollColorLocal" + name="poll" + :label="$t('settings.background')" + :fallback="previewTheme.colors.poll" + /> + <ColorInput + v-model="pollTextColorLocal" + name="pollText" + :label="$t('settings.text')" + :fallback="previewTheme.colors.pollText" + /> + </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.icons') }}</h4> + <ColorInput + v-model="iconColorLocal" + name="icon" + :label="$t('settings.style.advanced_colors.icons')" + :fallback="previewTheme.colors.icon" + /> + </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.highlight') }}</h4> + <ColorInput + v-model="highlightColorLocal" + name="highlight" + :label="$t('settings.background')" + :fallback="previewTheme.colors.highlight" + /> + <ColorInput + v-model="highlightTextColorLocal" + name="highlightText" + :label="$t('settings.text')" + :fallback="previewTheme.colors.highlightText" + /> + <ContrastRatio :contrast="previewContrast.highlightText" /> + <ColorInput + v-model="highlightLinkColorLocal" + name="highlightLink" + :label="$t('settings.links')" + :fallback="previewTheme.colors.highlightLink" + /> + <ContrastRatio :contrast="previewContrast.highlightLink" /> + </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.popover') }}</h4> + <ColorInput + v-model="popoverColorLocal" + name="popover" + :label="$t('settings.background')" + :fallback="previewTheme.colors.popover" + /> + <OpacityInput + v-model="popoverOpacityLocal" + name="popoverOpacity" + :fallback="previewTheme.opacity.popover" + :disabled="popoverOpacityLocal === 'transparent'" + /> + <ColorInput + v-model="popoverTextColorLocal" + name="popoverText" + :label="$t('settings.text')" + :fallback="previewTheme.colors.popoverText" + /> + <ContrastRatio :contrast="previewContrast.popoverText" /> + <ColorInput + v-model="popoverLinkColorLocal" + name="popoverLink" + :label="$t('settings.links')" + :fallback="previewTheme.colors.popoverLink" + /> + <ContrastRatio :contrast="previewContrast.popoverLink" /> + </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.selectedPost') }}</h4> + <ColorInput + v-model="selectedPostColorLocal" + name="selectedPost" + :label="$t('settings.background')" + :fallback="previewTheme.colors.selectedPost" + /> + <ColorInput + v-model="selectedPostTextColorLocal" + name="selectedPostText" + :label="$t('settings.text')" + :fallback="previewTheme.colors.selectedPostText" + /> + <ContrastRatio :contrast="previewContrast.selectedPostText" /> + <ColorInput + v-model="selectedPostLinkColorLocal" + name="selectedPostLink" + :label="$t('settings.links')" + :fallback="previewTheme.colors.selectedPostLink" + /> + <ContrastRatio :contrast="previewContrast.selectedPostLink" /> + </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.selectedMenu') }}</h4> + <ColorInput + v-model="selectedMenuColorLocal" + name="selectedMenu" + :label="$t('settings.background')" + :fallback="previewTheme.colors.selectedMenu" + /> + <ColorInput + v-model="selectedMenuTextColorLocal" + name="selectedMenuText" + :label="$t('settings.text')" + :fallback="previewTheme.colors.selectedMenuText" + /> + <ContrastRatio :contrast="previewContrast.selectedMenuText" /> + <ColorInput + v-model="selectedMenuLinkColorLocal" + name="selectedMenuLink" + :label="$t('settings.links')" + :fallback="previewTheme.colors.selectedMenuLink" + /> + <ContrastRatio :contrast="previewContrast.selectedMenuLink" /> + </div> </div> <div @@ -491,7 +862,7 @@ {{ $t('settings.style.switcher.clear_all') }} </button> </div> - <shadow-control + <ShadowControl v-model="currentShadow" :ready="!!currentShadowFallback" :fallback="currentShadowFallback" @@ -582,6 +953,6 @@ </div> </template> -<script src="./style_switcher.js"></script> +<script src="./theme_tab.js"></script> -<style src="./style_switcher.scss" lang="scss"></style> +<style src="./theme_tab.scss" lang="scss"></style> diff --git a/src/components/settings_modal/tabs/version_tab.js b/src/components/settings_modal/tabs/version_tab.js new file mode 100644 index 00000000..616bdadf --- /dev/null +++ b/src/components/settings_modal/tabs/version_tab.js @@ -0,0 +1,24 @@ +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, + frontendVersion: instance.frontendVersion + } + }, + computed: { + frontendVersionLink () { + return pleromaFeCommitUrl + this.frontendVersion + }, + backendVersionLink () { + return pleromaBeCommitUrl + extractCommit(this.backendVersion) + } + } +} + +export default VersionTab diff --git a/src/components/settings_modal/tabs/version_tab.vue b/src/components/settings_modal/tabs/version_tab.vue new file mode 100644 index 00000000..d35ff25e --- /dev/null +++ b/src/components/settings_modal/tabs/version_tab.vue @@ -0,0 +1,31 @@ +<template> + <div :label="$t('settings.version.title')"> + <div class="setting-item"> + <ul class="setting-list"> + <li> + <p>{{ $t('settings.version.backend_version') }}</p> + <ul class="option-list"> + <li> + <a + :href="backendVersionLink" + target="_blank" + >{{ backendVersion }}</a> + </li> + </ul> + </li> + <li> + <p>{{ $t('settings.version.frontend_version') }}</p> + <ul class="option-list"> + <li> + <a + :href="frontendVersionLink" + target="_blank" + >{{ frontendVersion }}</a> + </li> + </ul> + </li> + </ul> + </div> + </div> +</template> +<script src="./version_tab.js"> diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js index 44e4a22f..f9e7b985 100644 --- a/src/components/shadow_control/shadow_control.js +++ b/src/components/shadow_control/shadow_control.js @@ -3,6 +3,17 @@ import OpacityInput from '../opacity_input/opacity_input.vue' import { getCssShadow } from '../../services/style_setter/style_setter.js' import { hex2rgb } from '../../services/color_convert/color_convert.js' +const toModel = (object = {}) => ({ + x: 0, + y: 0, + blur: 0, + spread: 0, + inset: false, + color: '#000000', + alpha: 1, + ...object +}) + export default { // 'Value' and 'Fallback' can be undefined, but if they are // initially vue won't detect it when they become something else @@ -15,7 +26,7 @@ export default { return { selectedId: 0, // TODO there are some bugs regarding display of array (it's not getting updated when deleting for some reason) - cValue: this.value || this.fallback || [] + cValue: (this.value || this.fallback || []).map(toModel) } }, components: { @@ -24,12 +35,12 @@ export default { }, methods: { add () { - this.cValue.push(Object.assign({}, this.selected)) + 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 : 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] @@ -46,19 +57,24 @@ export default { this.cValue = this.value || this.fallback }, computed: { + anyShadows () { + return this.cValue.length > 0 + }, + anyShadowsFallback () { + return this.fallback.length > 0 + }, selected () { - if (this.ready && this.cValue.length > 0) { + if (this.ready && this.anyShadows) { return this.cValue[this.selectedId] } else { - return { - x: 0, - y: 0, - blur: 0, - spread: 0, - inset: false, - color: '#000000', - alpha: 1 - } + return toModel({}) + } + }, + currentFallback () { + if (this.ready && this.anyShadowsFallback) { + return this.fallback[this.selectedId] + } else { + return toModel({}) } }, moveUpValid () { @@ -80,7 +96,7 @@ export default { }, style () { return this.ready ? { - boxShadow: getCssShadow(this.cValue) + boxShadow: getCssShadow(this.fallback) } : {} } } diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue index de8a42d1..815a9e59 100644 --- a/src/components/shadow_control/shadow_control.vue +++ b/src/components/shadow_control/shadow_control.vue @@ -191,15 +191,20 @@ 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" /> - <p> - {{ $t('settings.style.shadows.hint') }} - </p> + <i18n + path="settings.style.shadows.hintV3" + tag="p" + > + <code>--variable,mod</code> + </i18n> </div> </div> </template> diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js index 567d2e5e..d1f044f6 100644 --- a/src/components/side_drawer/side_drawer.js +++ b/src/components/side_drawer/side_drawer.js @@ -10,6 +10,10 @@ const SideDrawer = { }), created () { this.closeGesture = GestureService.swipeGesture(GestureService.DIRECTION_LEFT, this.toggleDrawer) + + if (this.currentUser && this.currentUser.locked) { + this.$store.dispatch('startFetchingFollowRequests') + } }, components: { UserCard }, computed: { @@ -29,11 +33,20 @@ const SideDrawer = { logo () { return this.$store.state.instance.logo }, + hideSitename () { + return this.$store.state.instance.hideSitename + }, sitename () { return this.$store.state.instance.name }, followRequestCount () { return this.$store.state.api.followRequests.length + }, + privateMode () { + return this.$store.state.instance.private + }, + federating () { + return this.$store.state.instance.federating } }, methods: { @@ -49,6 +62,9 @@ const SideDrawer = { }, touchMove (e) { GestureService.updateSwipe(e, this.closeGesture) + }, + openSettingsModal () { + this.$store.dispatch('openSettingsModal') } } } diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue index 214b8e0c..f253742d 100644 --- a/src/components/side_drawer/side_drawer.vue +++ b/src/components/side_drawer/side_drawer.vue @@ -19,7 +19,7 @@ > <UserCard v-if="currentUser" - :user="currentUser" + :user-id="currentUser.id" :hide-bio="true" /> <div @@ -27,7 +27,7 @@ class="side-drawer-logo-wrapper" > <img :src="logo"> - <span>{{ sitename }}</span> + <span v-if="!hideSitename">{{ sitename }}</span> </div> </div> <ul> @@ -36,7 +36,7 @@ @click="toggleDrawer" > <router-link :to="{ name: 'login' }"> - {{ $t("login.login") }} + <i class="button-icon icon-login" /> {{ $t("login.login") }} </router-link> </li> <li @@ -44,7 +44,7 @@ @click="toggleDrawer" > <router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }"> - {{ $t("nav.dms") }} + <i class="button-icon icon-mail-alt" /> {{ $t("nav.dms") }} </router-link> </li> <li @@ -52,7 +52,7 @@ @click="toggleDrawer" > <router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }"> - {{ $t("nav.interactions") }} + <i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }} </router-link> </li> </ul> @@ -62,7 +62,7 @@ @click="toggleDrawer" > <router-link :to="{ name: 'friends' }"> - {{ $t("nav.timeline") }} + <i class="button-icon icon-home-2" /> {{ $t("nav.timeline") }} </router-link> </li> <li @@ -70,7 +70,7 @@ @click="toggleDrawer" > <router-link to="/friend-requests"> - {{ $t("nav.friend_requests") }} + <i class="button-icon icon-user-plus" /> {{ $t("nav.friend_requests") }} <span v-if="followRequestCount > 0" class="badge follow-request-count" @@ -79,14 +79,20 @@ </span> </router-link> </li> - <li @click="toggleDrawer"> + <li + v-if="currentUser || !privateMode" + @click="toggleDrawer" + > <router-link to="/main/public"> - {{ $t("nav.public_tl") }} + <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }} </router-link> </li> - <li @click="toggleDrawer"> + <li + v-if="federating && (currentUser || !privateMode)" + @click="toggleDrawer" + > <router-link to="/main/all"> - {{ $t("nav.twkn") }} + <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }} </router-link> </li> <li @@ -94,14 +100,17 @@ @click="toggleDrawer" > <router-link :to="{ name: 'chat' }"> - {{ $t("nav.chat") }} + <i class="button-icon icon-chat" /> {{ $t("nav.chat") }} </router-link> </li> </ul> <ul> - <li @click="toggleDrawer"> + <li + v-if="currentUser || !privateMode" + @click="toggleDrawer" + > <router-link :to="{ name: 'search' }"> - {{ $t("nav.search") }} + <i class="button-icon icon-search" /> {{ $t("nav.search") }} </router-link> </li> <li @@ -109,17 +118,20 @@ @click="toggleDrawer" > <router-link :to="{ name: 'who-to-follow' }"> - {{ $t("nav.who_to_follow") }} + <i class="button-icon icon-user-plus" /> {{ $t("nav.who_to_follow") }} </router-link> </li> <li @click="toggleDrawer"> - <router-link :to="{ name: 'settings' }"> - {{ $t("settings.settings") }} - </router-link> + <a + href="#" + @click="openSettingsModal" + > + <i class="button-icon icon-cog" /> {{ $t("settings.settings") }} + </a> </li> <li @click="toggleDrawer"> <router-link :to="{ name: 'about'}"> - {{ $t("nav.about") }} + <i class="button-icon icon-info-circled" /> {{ $t("nav.about") }} </router-link> </li> <li @@ -130,7 +142,7 @@ href="/pleroma/admin/#/login-pleroma" target="_blank" > - {{ $t("nav.administration") }} + <i class="button-icon icon-gauge" /> {{ $t("nav.administration") }} </a> </li> <li @@ -141,7 +153,7 @@ href="#" @click="doLogout" > - {{ $t("login.logout") }} + <i class="button-icon icon-logout" /> {{ $t("login.logout") }} </a> </li> </ul> @@ -214,7 +226,17 @@ box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6); box-shadow: var(--panelShadow); background-color: $fallback--bg; - background-color: var(--bg, $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); + + .button-icon:before { + width: 1.1em; + } } .side-drawer-logo-wrapper { @@ -276,7 +298,13 @@ &:hover { background-color: $fallback--lightBg; - background-color: var(--lightBg, $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); } } } diff --git a/src/components/staff_panel/staff_panel.js b/src/components/staff_panel/staff_panel.js new file mode 100644 index 00000000..4f98fff6 --- /dev/null +++ b/src/components/staff_panel/staff_panel.js @@ -0,0 +1,15 @@ +import map from 'lodash/map' +import BasicUserCard from '../basic_user_card/basic_user_card.vue' + +const StaffPanel = { + components: { + BasicUserCard + }, + computed: { + staffAccounts () { + return map(this.$store.state.instance.staffAccounts, nickname => this.$store.getters.findUser(nickname)).filter(_ => _) + } + } +} + +export default StaffPanel diff --git a/src/components/staff_panel/staff_panel.vue b/src/components/staff_panel/staff_panel.vue new file mode 100644 index 00000000..1d13003d --- /dev/null +++ b/src/components/staff_panel/staff_panel.vue @@ -0,0 +1,23 @@ +<template> + <div class="staff-panel"> + <div class="panel panel-default base01-background"> + <div class="panel-heading timeline-heading base02-background"> + <div class="title"> + {{ $t("about.staff") }} + </div> + </div> + <div class="panel-body"> + <basic-user-card + v-for="user in staffAccounts" + :key="user.screen_name" + :user="user" + /> + </div> + </div> + </div> +</template> + +<script src="./staff_panel.js" ></script> + +<style lang="scss"> +</style> diff --git a/src/components/status/status.js b/src/components/status/status.js index 4fbd5ac3..73382521 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -1,22 +1,20 @@ -import Attachment from '../attachment/attachment.vue' import FavoriteButton from '../favorite_button/favorite_button.vue' +import ReactButton from '../react_button/react_button.vue' import RetweetButton from '../retweet_button/retweet_button.vue' -import Poll from '../poll/poll.vue' import ExtraButtons from '../extra_buttons/extra_buttons.vue' import PostStatusForm from '../post_status_form/post_status_form.vue' import UserCard from '../user_card/user_card.vue' import UserAvatar from '../user_avatar/user_avatar.vue' -import Gallery from '../gallery/gallery.vue' -import LinkPreview from '../link-preview/link-preview.vue' import AvatarList from '../avatar_list/avatar_list.vue' import Timeago from '../timeago/timeago.vue' +import StatusContent from '../status_content/status_content.vue' import StatusPopover from '../status_popover/status_popover.vue' +import EmojiReactions from '../emoji_reactions/emoji_reactions.vue' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' -import fileType from 'src/services/file_type/file_type.service' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' -import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js' -import { filter, unescape, uniqBy } from 'lodash' -import { mapGetters } from 'vuex' +import { muteWordHits } from '../../services/status_parser/status_parser.js' +import { unescape, uniqBy } from 'lodash' +import { mapGetters, mapState } from 'vuex' const Status = { name: 'Status', @@ -32,27 +30,27 @@ const Status = { 'noHeading', 'inlineExpanded', 'showPinned', - 'inProfile' + 'inProfile', + 'profileUserId' ], data () { return { replying: false, unmuted: false, userExpanded: false, - showingTall: this.inConversation && this.focused, - showingLongSubject: false, - error: null, - expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject, - betterShadow: this.$store.state.interface.browserSupport.cssFilter + error: null } }, computed: { - localCollapseSubjectDefault () { - return this.mergedConfig.collapseMessageWithSubject - }, muteWords () { return this.mergedConfig.muteWords }, + showReasonMutedThread () { + return ( + this.status.thread_muted || + (this.status.reblog && this.status.reblog.thread_muted) + ) && !this.inConversation + }, repeaterClass () { const user = this.statusoid.user return highlightClass(user) @@ -75,10 +73,6 @@ const Status = { const highlight = this.mergedConfig.highlight return highlightStyle(highlight[user.screen_name]) }, - hideAttachments () { - return (this.mergedConfig.hideAttachments && !this.inConversation) || - (this.mergedConfig.hideAttachmentsInConv && this.inConversation) - }, userProfileLink () { return this.generateUserProfileLink(this.status.user.id, this.status.user.screen_name) }, @@ -103,18 +97,46 @@ const Status = { return this.$store.state.statuses.allStatusesObject[this.status.id] }, loggedIn () { - return !!this.$store.state.users.currentUser + return !!this.currentUser }, muteWordHits () { - const statusText = this.status.text.toLowerCase() - const statusSummary = this.status.summary.toLowerCase() - const hits = filter(this.muteWords, (muteWord) => { - return statusText.includes(muteWord.toLowerCase()) || statusSummary.includes(muteWord.toLowerCase()) - }) - - return hits + return muteWordHits(this.status, this.muteWords) + }, + muted () { + const { status } = this + const { reblog } = status + const relationship = this.$store.getters.relationship(status.user.id) + const relationshipReblog = reblog && this.$store.getters.relationship(reblog.user.id) + const reasonsToMute = ( + // Post is muted according to BE + status.muted || + // Reprööt of a muted post according to BE + (reblog && reblog.muted) || + // Muted user + relationship.muting || + // Muted user of a reprööt + (relationshipReblog && relationshipReblog.muting) || + // Thread is muted + status.thread_muted || + // Wordfiltered + this.muteWordHits.length > 0 + ) + const excusesNotToMute = ( + ( + this.inProfile && ( + // Don't mute user's posts on user timeline (except reblogs) + (!reblog && status.user.id === this.profileUserId) || + // Same as above but also allow self-reblogs + (reblog && reblog.user.id === this.profileUserId) + ) + ) || + // Don't mute statuses in muted conversation when said conversation is opened + (this.inConversation && status.thread_muted) + // No excuses if post has muted words + ) && !this.muteWordHits.length > 0 + + return !this.unmuted && !excusesNotToMute && reasonsToMute }, - muted () { return !this.unmuted && ((!this.inProfile && this.status.user.muted) || (!this.inConversation && this.status.thread_muted) || this.muteWordHits.length > 0) }, hideFilteredStatuses () { return this.mergedConfig.hideFilteredStatuses }, @@ -131,20 +153,6 @@ const Status = { // use conversation highlight only when in conversation return this.status.id === this.highlight }, - // This is a bit hacky, but we want to approximate post height before rendering - // so we count newlines (masto uses <p> for paragraphs, GS uses <br> between them) - // as well as approximate line count by counting characters and approximating ~80 - // per line. - // - // Using max-height + overflow: auto for status components resulted in false positives - // very often with japanese characters, and it was very annoying. - tallStatus () { - const lengthScore = this.status.statusnet_html.split(/<p|<br/).length + this.status.text.length / 80 - return lengthScore > 20 - }, - longSubject () { - return this.status.summary.length > 900 - }, isReply () { return !!(this.status.in_reply_to_status_id && this.status.in_reply_to_user_id) }, @@ -163,7 +171,7 @@ const Status = { if (this.inConversation || !this.isReply) { return false } - if (this.status.user.id === this.$store.state.users.currentUser.id) { + if (this.status.user.id === this.currentUser.id) { return false } if (this.status.type === 'retweet') { @@ -174,43 +182,19 @@ const Status = { if (this.status.user.id === this.status.attentions[i].id) { continue } - const taggedUser = this.$store.getters.findUser(this.status.attentions[i].id) - if (checkFollowing && taggedUser && taggedUser.following) { + // There's zero guarantee of this working. If we happen to have that user and their + // relationship in store then it will work, but there's kinda little chance of having + // them for people you're not following. + const relationship = this.$store.state.users.relationships[this.status.attentions[i].id] + if (checkFollowing && relationship && relationship.following) { return false } - if (this.status.attentions[i].id === this.$store.state.users.currentUser.id) { + if (this.status.attentions[i].id === this.currentUser.id) { return false } } return this.status.attentions.length > 0 }, - hideSubjectStatus () { - if (this.tallStatus && !this.localCollapseSubjectDefault) { - return false - } - return !this.expandingSubject && this.status.summary - }, - hideTallStatus () { - if (this.status.summary && this.localCollapseSubjectDefault) { - return false - } - if (this.showingTall) { - return false - } - return this.tallStatus - }, - showingMore () { - return (this.tallStatus && this.showingTall) || (this.status.summary && this.expandingSubject) - }, - nsfwClickthrough () { - if (!this.status.nsfw) { - return false - } - if (this.status.summary && this.localCollapseSubjectDefault) { - return false - } - return true - }, replySubject () { if (!this.status.summary) return '' const decodedSummary = unescape(this.status.summary) @@ -224,43 +208,6 @@ const Status = { return '' } }, - attachmentSize () { - if ((this.mergedConfig.hideAttachments && !this.inConversation) || - (this.mergedConfig.hideAttachmentsInConv && this.inConversation) || - (this.status.attachments.length > this.maxThumbnails)) { - return 'hide' - } else if (this.compact) { - return 'small' - } - return 'normal' - }, - galleryTypes () { - if (this.attachmentSize === 'hide') { - return [] - } - return this.mergedConfig.playVideosInModal - ? ['image', 'video'] - : ['image'] - }, - galleryAttachments () { - return this.status.attachments.filter( - file => fileType.fileMatchesSomeType(this.galleryTypes, file) - ) - }, - nonGalleryAttachments () { - return this.status.attachments.filter( - file => !fileType.fileMatchesSomeType(this.galleryTypes, file) - ) - }, - maxThumbnails () { - return this.mergedConfig.maxThumbnails - }, - contentHtml () { - if (!this.status.summary_html) { - return this.status.statusnet_html - } - return this.status.summary_html + '<br />' + this.status.statusnet_html - }, combinedFavsAndRepeatsUsers () { // Use the status from the global status repository since favs and repeats are saved in it const combinedUsers = [].concat( @@ -269,31 +216,31 @@ const Status = { ) return uniqBy(combinedUsers, 'id') }, - ownStatus () { - return this.status.user.id === this.$store.state.users.currentUser.id - }, tags () { return this.status.tags.filter(tagObj => tagObj.hasOwnProperty('name')).map(tagObj => tagObj.name).join(' ') }, hidePostStats () { return this.mergedConfig.hidePostStats }, - ...mapGetters(['mergedConfig']) + ...mapGetters(['mergedConfig']), + ...mapState({ + betterShadow: state => state.interface.browserSupport.cssFilter, + currentUser: state => state.users.currentUser + }) }, components: { - Attachment, FavoriteButton, + ReactButton, RetweetButton, ExtraButtons, PostStatusForm, - Poll, UserCard, UserAvatar, - Gallery, - LinkPreview, AvatarList, Timeago, - StatusPopover + StatusPopover, + EmojiReactions, + StatusContent }, methods: { visibilityIcon (visibility) { @@ -314,32 +261,6 @@ const Status = { clearError () { this.error = undefined }, - linkClicked (event) { - const target = event.target.closest('.status-content a') - if (target) { - if (target.className.match(/mention/)) { - const href = target.href - const attn = this.status.attentions.find(attn => mentionMatchesUrl(attn, href)) - if (attn) { - event.stopPropagation() - event.preventDefault() - const link = this.generateUserProfileLink(attn.id, attn.screen_name) - this.$router.push(link) - return - } - } - if (target.rel.match(/(?:^|\s)tag(?:$|\s)/) || target.className.match(/hashtag/)) { - // Extract tag name from link url - const tag = extractTagFromUrl(target.href) - if (tag) { - const link = this.generateTagLink(tag) - this.$router.push(link) - return - } - } - window.open(target.href, '_blank') - } - }, toggleReplying () { this.replying = !this.replying }, @@ -357,26 +278,8 @@ const Status = { toggleUserExpanded () { this.userExpanded = !this.userExpanded }, - toggleShowMore () { - if (this.showingTall) { - this.showingTall = false - } else if (this.expandingSubject && this.status.summary) { - this.expandingSubject = false - } else if (this.hideTallStatus) { - this.showingTall = true - } else if (this.hideSubjectStatus && this.status.summary) { - this.expandingSubject = true - } - }, generateUserProfileLink (id, name) { return generateProfileLink(id, name, this.$store.state.instance.restrictedNicknames) - }, - generateTagLink (tag) { - return `/tag/${tag}` - }, - setMedia () { - const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments - return () => this.$store.dispatch('setMedia', attachments) } }, watch: { diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 65778b2e..336f912a 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -17,12 +17,33 @@ </div> <template v-if="muted && !isPreview"> <div class="media status container muted"> - <small> + <small class="username"> + <i + v-if="muted && retweet" + class="button-icon icon-retweet" + /> <router-link :to="userProfileLink"> {{ status.user.screen_name }} </router-link> </small> - <small class="muteWords">{{ muteWordHits.join(', ') }}</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> <a href="#" class="unmute" @@ -94,7 +115,7 @@ <div class="status-body"> <UserCard v-if="userExpanded" - :user="status.user" + :user-id="status.user.id" :rounded="true" :bordered="true" class="status-usercard" @@ -177,6 +198,8 @@ <StatusPopover v-if="!isPreview" :status-id="status.in_reply_to_status_id" + class="reply-to-popover" + style="min-width: 0" > <a class="reply-to" @@ -224,104 +247,12 @@ </div> </div> - <div - v-if="longSubject" - class="status-content-wrapper" - :class="{ 'tall-status': !showingLongSubject }" - > - <a - v-if="!showingLongSubject" - class="tall-status-hider" - :class="{ 'tall-status-hider_focused': isFocused }" - href="#" - @click.prevent="showingLongSubject=true" - >{{ $t("general.show_more") }}</a> - <div - class="status-content media-body" - @click.prevent="linkClicked" - v-html="contentHtml" - /> - <a - v-if="showingLongSubject" - href="#" - class="status-unhider" - @click.prevent="showingLongSubject=false" - >{{ $t("general.show_less") }}</a> - </div> - <div - v-else - :class="{'tall-status': hideTallStatus}" - class="status-content-wrapper" - > - <a - v-if="hideTallStatus" - class="tall-status-hider" - :class="{ 'tall-status-hider_focused': isFocused }" - href="#" - @click.prevent="toggleShowMore" - >{{ $t("general.show_more") }}</a> - <div - v-if="!hideSubjectStatus" - class="status-content media-body" - @click.prevent="linkClicked" - v-html="contentHtml" - /> - <div - v-else - class="status-content media-body" - @click.prevent="linkClicked" - v-html="status.summary_html" - /> - <a - v-if="hideSubjectStatus" - href="#" - class="cw-status-hider" - @click.prevent="toggleShowMore" - >{{ $t("general.show_more") }}</a> - <a - v-if="showingMore" - href="#" - class="status-unhider" - @click.prevent="toggleShowMore" - >{{ $t("general.show_less") }}</a> - </div> - - <div v-if="status.poll && status.poll.options"> - <poll :base-poll="status.poll" /> - </div> - - <div - v-if="status.attachments && (!hideSubjectStatus || showingLongSubject)" - class="attachments media-body" - > - <attachment - v-for="attachment in nonGalleryAttachments" - :key="attachment.id" - class="non-gallery" - :size="attachmentSize" - :nsfw="nsfwClickthrough" - :attachment="attachment" - :allow-play="true" - :set-media="setMedia()" - /> - <gallery - v-if="galleryAttachments.length > 0" - :nsfw="nsfwClickthrough" - :attachments="galleryAttachments" - :set-media="setMedia()" - /> - </div> - - <div - v-if="status.card && !hideSubjectStatus && !noHeading" - class="link-preview media-body" - > - <link-preview - :card="status.card" - :size="attachmentSize" - :nsfw="nsfwClickthrough" - /> - </div> + <StatusContent + :status="status" + :no-heading="noHeading" + :highlight="highlight" + :focused="isFocused" + /> <transition name="fade"> <div @@ -354,6 +285,11 @@ </div> </transition> + <EmojiReactions + v-if="(mergedConfig.emojiReactionsOnTimeline || isFocused) && (!noHeading && !isPreview)" + :status="status" + /> + <div v-if="!noHeading && !isPreview" class="status-actions media-body" @@ -382,6 +318,10 @@ :logged-in="loggedIn" :status="status" /> + <ReactButton + v-if="loggedIn" + :status="status" + /> <extra-buttons :status="status" @onError="showError" @@ -445,7 +385,15 @@ $status-margin: 0.75em; &_focused { background-color: $fallback--lightBg; - background-color: var(--lightBg, $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); } .timeline & { @@ -541,11 +489,10 @@ $status-margin: 0.75em; align-items: stretch; > .reply-to-and-accountname > a { + overflow: hidden; max-width: 100%; text-overflow: ellipsis; - overflow: hidden; white-space: nowrap; - display: inline-block; word-break: break-all; } } @@ -554,7 +501,6 @@ $status-margin: 0.75em; display: flex; height: 18px; margin-right: 0.5em; - overflow: hidden; max-width: 100%; .icon-reply { transform: scaleX(-1); @@ -565,6 +511,10 @@ $status-margin: 0.75em; display: flex; } + .reply-to-popover { + min-width: 0; + } + .reply-to { display: flex; } @@ -572,9 +522,8 @@ $status-margin: 0.75em; .reply-to-text { overflow: hidden; text-overflow: ellipsis; + white-space: nowrap; margin: 0 0.4em 0 0.2em; - color: $fallback--faint; - color: var(--faint, $fallback--faint); } .replies-separator { @@ -596,100 +545,6 @@ $status-margin: 0.75em; } } - .tall-status { - position: relative; - height: 220px; - overflow-x: hidden; - overflow-y: hidden; - z-index: 1; - .status-content { - height: 100%; - mask: linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat, - linear-gradient(to top, white, white); - // Autoprefixed seem to ignore this one, and also syntax is different - -webkit-mask-composite: xor; - mask-composite: exclude; - } - } - - .tall-status-hider { - display: inline-block; - word-break: break-all; - position: absolute; - height: 70px; - margin-top: 150px; - width: 100%; - text-align: center; - line-height: 110px; - z-index: 2; - } - - .status-unhider, .cw-status-hider { - width: 100%; - text-align: center; - display: inline-block; - word-break: break-all; - } - - .status-content { - font-family: var(--postFont, sans-serif); - line-height: 1.4em; - white-space: pre-wrap; - - img, video { - max-width: 100%; - max-height: 400px; - vertical-align: middle; - object-fit: contain; - - &.emoji { - width: 32px; - height: 32px; - } - } - - blockquote { - margin: 0.2em 0 0.2em 2em; - font-style: italic; - } - - pre { - overflow: auto; - } - - code, samp, kbd, var, pre { - font-family: var(--postCodeFont, monospace); - } - - p { - margin: 0 0 1em 0; - } - - p:last-child { - margin: 0 0 0 0; - } - - h1 { - font-size: 1.1em; - line-height: 1.2em; - margin: 1.4em 0; - } - - h2 { - font-size: 1.1em; - margin: 1.0em 0; - } - - h3 { - font-size: 1em; - margin: 1.2em 0; - } - - h4 { - margin: 1.1em 0; - } - } - .retweet-info { padding: 0.4em $status-margin; margin: 0; @@ -751,10 +606,6 @@ $status-margin: 0.75em; } } -.greentext { - color: green; -} - .status-conversation { border-left-style: solid; } @@ -807,33 +658,54 @@ $status-margin: 0.75em; } .muted { - padding: 0.25em 0.5em; - button { + padding: .25em .6em; + height: 1.2em; + line-height: 1.2em; + text-overflow: ellipsis; + overflow: hidden; + display: flex; + flex-wrap: nowrap; + + .username, .mute-thread, .mute-words { + word-wrap: normal; + word-break: normal; + white-space: nowrap; + } + + .username, .mute-words { + text-overflow: ellipsis; + overflow: hidden; + } + + .username { + flex: 0 1 auto; + margin-right: .2em; + } + + .mute-thread { + flex: 0 0 auto; + } + + .mute-words { + flex: 1 0 5em; + margin-left: .2em; + &::before { + content: ' ' + } + } + + .unmute { + flex: 0 0 auto; + margin-left: auto; + display: block; margin-left: auto; } - - .muteWords { - margin-left: 10px; - } -} - -a.unmute { - display: block; - margin-left: auto; } .reply-body { flex: 1; } -.timeline :not(.panel-disabled) > { - .status-el:last-child { - border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; - border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); - border-bottom: none; - } -} - .favs-repeated-users { margin-top: $status-margin; diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js new file mode 100644 index 00000000..c0a71e8f --- /dev/null +++ b/src/components/status_content/status_content.js @@ -0,0 +1,210 @@ +import Attachment from '../attachment/attachment.vue' +import Poll from '../poll/poll.vue' +import Gallery from '../gallery/gallery.vue' +import LinkPreview from '../link-preview/link-preview.vue' +import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' +import fileType from 'src/services/file_type/file_type.service' +import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js' +import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js' +import { mapGetters, mapState } from 'vuex' + +const StatusContent = { + name: 'StatusContent', + props: [ + 'status', + 'focused', + 'noHeading', + 'fullContent' + ], + data () { + return { + showingTall: this.inConversation && this.focused, + showingLongSubject: false, + // not as computed because it sets the initial state which will be changed later + expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject + } + }, + computed: { + localCollapseSubjectDefault () { + return this.mergedConfig.collapseMessageWithSubject + }, + hideAttachments () { + return (this.mergedConfig.hideAttachments && !this.inConversation) || + (this.mergedConfig.hideAttachmentsInConv && this.inConversation) + }, + // This is a bit hacky, but we want to approximate post height before rendering + // so we count newlines (masto uses <p> for paragraphs, GS uses <br> between them) + // as well as approximate line count by counting characters and approximating ~80 + // per line. + // + // Using max-height + overflow: auto for status components resulted in false positives + // very often with japanese characters, and it was very annoying. + tallStatus () { + const lengthScore = this.status.statusnet_html.split(/<p|<br/).length + this.status.text.length / 80 + return lengthScore > 20 + }, + longSubject () { + return this.status.summary.length > 900 + }, + // When a status has a subject and is also tall, we should only have one show more/less button. If the default is to collapse statuses with subjects, we just treat it like a status with a subject; otherwise, we just treat it like a tall status. + mightHideBecauseSubject () { + return this.status.summary && (!this.tallStatus || this.localCollapseSubjectDefault) + }, + mightHideBecauseTall () { + return this.tallStatus && (!this.status.summary || !this.localCollapseSubjectDefault) + }, + hideSubjectStatus () { + return this.mightHideBecauseSubject && !this.expandingSubject + }, + hideTallStatus () { + return this.mightHideBecauseTall && !this.showingTall + }, + showingMore () { + return (this.mightHideBecauseTall && this.showingTall) || (this.mightHideBecauseSubject && this.expandingSubject) + }, + nsfwClickthrough () { + if (!this.status.nsfw) { + return false + } + if (this.status.summary && this.localCollapseSubjectDefault) { + return false + } + return true + }, + attachmentSize () { + if ((this.mergedConfig.hideAttachments && !this.inConversation) || + (this.mergedConfig.hideAttachmentsInConv && this.inConversation) || + (this.status.attachments.length > this.maxThumbnails)) { + return 'hide' + } else if (this.compact) { + return 'small' + } + return 'normal' + }, + galleryTypes () { + if (this.attachmentSize === 'hide') { + return [] + } + return this.mergedConfig.playVideosInModal + ? ['image', 'video'] + : ['image'] + }, + galleryAttachments () { + return this.status.attachments.filter( + file => fileType.fileMatchesSomeType(this.galleryTypes, file) + ) + }, + nonGalleryAttachments () { + return this.status.attachments.filter( + file => !fileType.fileMatchesSomeType(this.galleryTypes, file) + ) + }, + hasImageAttachments () { + return this.status.attachments.some( + file => fileType.fileType(file.mimetype) === 'image' + ) + }, + hasVideoAttachments () { + return this.status.attachments.some( + file => fileType.fileType(file.mimetype) === 'video' + ) + }, + maxThumbnails () { + return this.mergedConfig.maxThumbnails + }, + postBodyHtml () { + const html = this.status.statusnet_html + + if (this.mergedConfig.greentext) { + try { + if (html.includes('>')) { + // This checks if post has '>' at the beginning, excluding mentions so that @mention >impying works + return processHtml(html, (string) => { + if (string.includes('>') && + string + .replace(/<[^>]+?>/gi, '') // remove all tags + .replace(/@\w+/gi, '') // remove mentions (even failed ones) + .trim() + .startsWith('>')) { + return `<span class='greentext'>${string}</span>` + } else { + return string + } + }) + } else { + return html + } + } catch (e) { + console.err('Failed to process status html', e) + return html + } + } else { + return html + } + }, + contentHtml () { + if (!this.status.summary_html) { + return this.postBodyHtml + } + return this.status.summary_html + '<br />' + this.postBodyHtml + }, + ...mapGetters(['mergedConfig']), + ...mapState({ + betterShadow: state => state.interface.browserSupport.cssFilter, + currentUser: state => state.users.currentUser + }) + }, + components: { + Attachment, + Poll, + Gallery, + LinkPreview + }, + methods: { + linkClicked (event) { + const target = event.target.closest('.status-content a') + if (target) { + if (target.className.match(/mention/)) { + const href = target.href + const attn = this.status.attentions.find(attn => mentionMatchesUrl(attn, href)) + if (attn) { + event.stopPropagation() + event.preventDefault() + const link = this.generateUserProfileLink(attn.id, attn.screen_name) + this.$router.push(link) + return + } + } + if (target.rel.match(/(?:^|\s)tag(?:$|\s)/) || target.className.match(/hashtag/)) { + // Extract tag name from dataset or link url + const tag = target.dataset.tag || extractTagFromUrl(target.href) + if (tag) { + const link = this.generateTagLink(tag) + this.$router.push(link) + return + } + } + window.open(target.href, '_blank') + } + }, + toggleShowMore () { + if (this.mightHideBecauseTall) { + this.showingTall = !this.showingTall + } else if (this.mightHideBecauseSubject) { + this.expandingSubject = !this.expandingSubject + } + }, + generateUserProfileLink (id, name) { + return generateProfileLink(id, name, this.$store.state.instance.restrictedNicknames) + }, + generateTagLink (tag) { + return `/tag/${tag}` + }, + setMedia () { + const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments + return () => this.$store.dispatch('setMedia', attachments) + } + } +} + +export default StatusContent diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue new file mode 100644 index 00000000..8c2e8749 --- /dev/null +++ b/src/components/status_content/status_content.vue @@ -0,0 +1,240 @@ +<template> + <!-- eslint-disable vue/no-v-html --> + <div class="status-body"> + <slot name="header" /> + <div + v-if="longSubject" + class="status-content-wrapper" + :class="{ 'tall-status': !showingLongSubject }" + > + <a + v-if="!showingLongSubject" + class="tall-status-hider" + :class="{ 'tall-status-hider_focused': focused }" + href="#" + @click.prevent="showingLongSubject=true" + > + {{ $t("general.show_more") }} + <span + v-if="hasImageAttachments" + class="icon-picture" + /> + <span + v-if="hasVideoAttachments" + class="icon-video" + /> + <span + v-if="status.card" + class="icon-link" + /> + </a> + <div + class="status-content media-body" + @click.prevent="linkClicked" + v-html="contentHtml" + /> + <a + v-if="showingLongSubject" + href="#" + class="status-unhider" + @click.prevent="showingLongSubject=false" + >{{ $t("general.show_less") }}</a> + </div> + <div + v-else + :class="{'tall-status': hideTallStatus}" + class="status-content-wrapper" + > + <a + v-if="hideTallStatus" + class="tall-status-hider" + :class="{ 'tall-status-hider_focused': focused }" + href="#" + @click.prevent="toggleShowMore" + >{{ $t("general.show_more") }}</a> + <div + v-if="!hideSubjectStatus" + class="status-content media-body" + @click.prevent="linkClicked" + v-html="contentHtml" + /> + <div + v-else + class="status-content media-body" + @click.prevent="linkClicked" + v-html="status.summary_html" + /> + <a + v-if="hideSubjectStatus" + href="#" + class="cw-status-hider" + @click.prevent="toggleShowMore" + >{{ $t("general.show_more") }}</a> + <a + v-if="showingMore" + href="#" + class="status-unhider" + @click.prevent="toggleShowMore" + >{{ $t("general.show_less") }}</a> + </div> + + <div v-if="status.poll && status.poll.options"> + <poll :base-poll="status.poll" /> + </div> + + <div + v-if="status.attachments.length !== 0 && (!hideSubjectStatus || showingLongSubject)" + class="attachments media-body" + > + <attachment + v-for="attachment in nonGalleryAttachments" + :key="attachment.id" + class="non-gallery" + :size="attachmentSize" + :nsfw="nsfwClickthrough" + :attachment="attachment" + :allow-play="true" + :set-media="setMedia()" + /> + <gallery + v-if="galleryAttachments.length > 0" + :nsfw="nsfwClickthrough" + :attachments="galleryAttachments" + :set-media="setMedia()" + /> + </div> + + <div + v-if="status.card && !hideSubjectStatus && !noHeading" + class="link-preview media-body" + > + <link-preview + :card="status.card" + :size="attachmentSize" + :nsfw="nsfwClickthrough" + /> + </div> + <slot name="footer" /> + </div> + <!-- eslint-enable vue/no-v-html --> +</template> + +<script src="./status_content.js" ></script> +<style lang="scss"> +@import '../../_variables.scss'; + +$status-margin: 0.75em; + +.status-body { + flex: 1; + min-width: 0; + + .tall-status { + position: relative; + height: 220px; + overflow-x: hidden; + overflow-y: hidden; + z-index: 1; + .status-content { + height: 100%; + mask: linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat, + linear-gradient(to top, white, white); + /* Autoprefixed seem to ignore this one, and also syntax is different */ + -webkit-mask-composite: xor; + mask-composite: exclude; + } + } + + .tall-status-hider { + display: inline-block; + word-break: break-all; + position: absolute; + height: 70px; + margin-top: 150px; + width: 100%; + text-align: center; + line-height: 110px; + z-index: 2; + } + + .status-unhider, .cw-status-hider { + width: 100%; + text-align: center; + display: inline-block; + word-break: break-all; + } + + .status-content { + font-family: var(--postFont, sans-serif); + line-height: 1.4em; + white-space: pre-wrap; + + img, video { + max-width: 100%; + max-height: 400px; + vertical-align: middle; + object-fit: contain; + + &.emoji { + width: 32px; + height: 32px; + } + } + + blockquote { + margin: 0.2em 0 0.2em 2em; + font-style: italic; + } + + pre { + overflow: auto; + } + + code, samp, kbd, var, pre { + font-family: var(--postCodeFont, monospace); + } + + p { + margin: 0 0 1em 0; + } + + p:last-child { + margin: 0 0 0 0; + } + + h1 { + font-size: 1.1em; + line-height: 1.2em; + margin: 1.4em 0; + } + + h2 { + font-size: 1.1em; + margin: 1.0em 0; + } + + h3 { + font-size: 1em; + margin: 1.2em 0; + } + + h4 { + margin: 1.1em 0; + } + } +} + +.greentext { + color: $fallback--cGreen; + color: var(--cGreen, $fallback--cGreen); +} + +.timeline :not(.panel-disabled) > { + .status-el:last-child { + border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; + border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); + border-bottom: none; + } +} + +</style> diff --git a/src/components/status_popover/status_popover.js b/src/components/status_popover/status_popover.js index 19f16bd9..159132a9 100644 --- a/src/components/status_popover/status_popover.js +++ b/src/components/status_popover/status_popover.js @@ -7,11 +7,7 @@ const StatusPopover = { ], data () { return { - popperOptions: { - modifiers: { - preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' } - } - } + error: false } }, computed: { @@ -20,12 +16,15 @@ const StatusPopover = { } }, components: { - Status: () => import('../status/status.vue') + Status: () => import('../status/status.vue'), + Popover: () => import('../popover/popover.vue') }, methods: { enter () { if (!this.status) { this.$store.dispatch('fetchStatus', this.statusId) + .then(data => (this.error = false)) + .catch(e => (this.error = true)) } } } diff --git a/src/components/status_popover/status_popover.vue b/src/components/status_popover/status_popover.vue index eacf4c06..f5948207 100644 --- a/src/components/status_popover/status_popover.vue +++ b/src/components/status_popover/status_popover.vue @@ -1,27 +1,36 @@ <template> - <v-popover + <Popover + trigger="hover" popover-class="status-popover" - placement="top-start" - :popper-options="popperOptions" - @show="enter()" + :bound-to="{ x: 'container' }" + @show="enter" > - <template slot="popover"> + <template slot="trigger"> + <slot /> + </template> + <div + slot="content" + > <Status v-if="status" :is-preview="true" :statusoid="status" :compact="true" /> + <div + v-else-if="error" + class="status-preview-no-content faint" + > + {{ $t('status.status_unavailable') }} + </div> <div v-else - class="status-preview-loading" + class="status-preview-no-content" > <i class="icon-spin4 animate-spin" /> </div> - </template> - - <slot /> - </v-popover> + </div> + </Popover> </template> <script src="./status_popover.js" ></script> @@ -29,50 +38,25 @@ <style lang="scss"> @import '../../_variables.scss'; -.tooltip.popover.status-popover { +.status-popover { font-size: 1rem; min-width: 15em; max-width: 95%; - margin-left: 0.5em; - .popover-inner { - border-color: $fallback--border; - border-color: var(--border, $fallback--border); - border-style: solid; - border-width: 1px; - border-radius: $fallback--tooltipRadius; - border-radius: var(--tooltipRadius, $fallback--tooltipRadius); - box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5); - box-shadow: var(--popupShadow); - } - - .popover-arrow::before { - position: absolute; - content: ''; - left: -7px; - border: solid 7px transparent; - z-index: -1; - } - - &[x-placement^="bottom-start"] .popover-arrow::before { - top: -2px; - border-top-width: 0; - border-bottom-color: $fallback--border; - border-bottom-color: var(--border, $fallback--border); - } - - &[x-placement^="top-start"] .popover-arrow::before { - bottom: -2px; - border-bottom-width: 0; - border-top-color: $fallback--border; - border-top-color: var(--border, $fallback--border); - } + border-color: $fallback--border; + border-color: var(--border, $fallback--border); + border-style: solid; + border-width: 1px; + border-radius: $fallback--tooltipRadius; + border-radius: var(--tooltipRadius, $fallback--tooltipRadius); + box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5); + box-shadow: var(--popupShadow); .status-el.status-el { border: none; } - .status-preview-loading { + .status-preview-no-content { padding: 1em; text-align: center; diff --git a/src/components/sticker_picker/sticker_picker.vue b/src/components/sticker_picker/sticker_picker.vue index 323855b9..dc449ccb 100644 --- a/src/components/sticker_picker/sticker_picker.vue +++ b/src/components/sticker_picker/sticker_picker.vue @@ -36,23 +36,23 @@ .sticker-picker { width: 100%; - position: relative; - .tab-switcher { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - } - .sticker-picker-content { - .sticker { - display: inline-block; - width: 20%; - height: 20%; - img { - width: 100%; - &:hover { - filter: drop-shadow(0 0 5px var(--link, $fallback--link)); + .contents { + min-height: 250px; + .sticker-picker-content { + display: flex; + flex-wrap: wrap; + padding: 0 4px; + .sticker { + display: flex; + flex: 1 1 auto; + margin: 4px; + width: 56px; + height: 56px; + img { + height: 100%; + &:hover { + filter: drop-shadow(0 0 5px var(--accent, $fallback--link)); + } } } } diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue index 4137bd59..f2ddeb7b 100644 --- a/src/components/still-image/still-image.vue +++ b/src/components/still-image/still-image.vue @@ -23,12 +23,15 @@ <style lang="scss"> @import '../../_variables.scss'; + .still-image { position: relative; line-height: 0; overflow: hidden; width: 100%; height: 100%; + display: flex; + align-items: center; &:hover canvas { display: none; @@ -36,7 +39,7 @@ img { width: 100%; - height: 100%; + min-height: 100%; object-fit: contain; } diff --git a/src/components/style_switcher/preview.vue b/src/components/style_switcher/preview.vue deleted file mode 100644 index 101a32bd..00000000 --- a/src/components/style_switcher/preview.vue +++ /dev/null @@ -1,101 +0,0 @@ -<template> - <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"> - {{ $t('settings.style.preview.button') }} - </button> - </div> - <div class="panel-body theme-preview-content"> - <div class="post"> - <div class="avatar"> - ( ͡° ͜ʖ ͡°) - </div> - <div class="content"> - <h4> - {{ $t('settings.style.preview.content') }} - </h4> - - <i18n path="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> - - <div class="icons"> - <i - style="color: var(--cBlue)" - class="button-icon icon-reply" - /> - <i - style="color: var(--cGreen)" - class="button-icon icon-retweet" - /> - <i - style="color: var(--cOrange)" - class="button-icon icon-star" - /> - <i - style="color: var(--cRed)" - class="button-icon icon-cancel" - /> - </div> - </div> - </div> - - <div class="after-post"> - <div class="avatar-alt"> - :^) - </div> - <div class="content"> - <i18n - path="settings.style.preview.fine_print" - tag="span" - class="faint" - > - <a style="color: var(--faintLink)"> - {{ $t('settings.style.preview.faint_link') }} - </a> - </i18n> - </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"> - {{ $t('settings.style.preview.button') }} - </button> - </div> - </div> - </div> -</template> diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js deleted file mode 100644 index ebde4475..00000000 --- a/src/components/style_switcher/style_switcher.js +++ /dev/null @@ -1,580 +0,0 @@ -import { rgb2hex, hex2rgb, getContrastRatio, alphaBlend } from '../../services/color_convert/color_convert.js' -import { set, delete as del } from 'vue' -import { generateColors, generateShadows, generateRadii, generateFonts, composePreset, getThemes } from '../../services/style_setter/style_setter.js' -import ColorInput from '../color_input/color_input.vue' -import RangeInput from '../range_input/range_input.vue' -import OpacityInput from '../opacity_input/opacity_input.vue' -import ShadowControl from '../shadow_control/shadow_control.vue' -import FontControl from '../font_control/font_control.vue' -import ContrastRatio from '../contrast_ratio/contrast_ratio.vue' -import TabSwitcher from '../tab_switcher/tab_switcher.js' -import Preview from './preview.vue' -import ExportImport from '../export_import/export_import.vue' -import Checkbox from '../checkbox/checkbox.vue' - -// List of color values used in v1 -const v1OnlyNames = [ - 'bg', - 'fg', - 'text', - 'link', - 'cRed', - 'cGreen', - 'cBlue', - 'cOrange' -].map(_ => _ + 'ColorLocal') - -export default { - data () { - return { - availableStyles: [], - selected: this.$store.getters.mergedConfig.theme, - - previewShadows: {}, - previewColors: {}, - previewRadii: {}, - previewFonts: {}, - - shadowsInvalid: true, - colorsInvalid: true, - radiiInvalid: true, - - keepColor: false, - keepShadows: false, - keepOpacity: false, - keepRoundness: false, - keepFonts: false, - - textColorLocal: '', - linkColorLocal: '', - - bgColorLocal: '', - bgOpacityLocal: undefined, - - fgColorLocal: '', - fgTextColorLocal: undefined, - fgLinkColorLocal: undefined, - - btnColorLocal: undefined, - btnTextColorLocal: undefined, - btnOpacityLocal: undefined, - - inputColorLocal: undefined, - inputTextColorLocal: undefined, - inputOpacityLocal: undefined, - - panelColorLocal: undefined, - panelTextColorLocal: undefined, - panelLinkColorLocal: undefined, - panelFaintColorLocal: undefined, - panelOpacityLocal: undefined, - - topBarColorLocal: undefined, - topBarTextColorLocal: undefined, - topBarLinkColorLocal: undefined, - - alertErrorColorLocal: undefined, - alertWarningColorLocal: undefined, - - badgeOpacityLocal: undefined, - badgeNotificationColorLocal: undefined, - - borderColorLocal: undefined, - borderOpacityLocal: undefined, - - faintColorLocal: undefined, - faintOpacityLocal: undefined, - faintLinkColorLocal: undefined, - - cRedColorLocal: '', - cBlueColorLocal: '', - cGreenColorLocal: '', - cOrangeColorLocal: '', - - shadowSelected: undefined, - shadowsLocal: {}, - fontsLocal: {}, - - btnRadiusLocal: '', - inputRadiusLocal: '', - checkboxRadiusLocal: '', - panelRadiusLocal: '', - avatarRadiusLocal: '', - avatarAltRadiusLocal: '', - attachmentRadiusLocal: '', - tooltipRadiusLocal: '' - } - }, - created () { - const self = this - - getThemes().then((themesComplete) => { - self.availableStyles = themesComplete - }) - }, - mounted () { - this.normalizeLocalState(this.$store.getters.mergedConfig.customTheme) - if (typeof this.shadowSelected === 'undefined') { - this.shadowSelected = this.shadowsAvailable[0] - } - }, - computed: { - selectedVersion () { - return Array.isArray(this.selected) ? 1 : 2 - }, - currentColors () { - return { - bg: this.bgColorLocal, - text: this.textColorLocal, - link: this.linkColorLocal, - - fg: this.fgColorLocal, - fgText: this.fgTextColorLocal, - fgLink: this.fgLinkColorLocal, - - panel: this.panelColorLocal, - panelText: this.panelTextColorLocal, - panelLink: this.panelLinkColorLocal, - panelFaint: this.panelFaintColorLocal, - - input: this.inputColorLocal, - inputText: this.inputTextColorLocal, - - topBar: this.topBarColorLocal, - topBarText: this.topBarTextColorLocal, - topBarLink: this.topBarLinkColorLocal, - - btn: this.btnColorLocal, - btnText: this.btnTextColorLocal, - - alertError: this.alertErrorColorLocal, - alertWarning: this.alertWarningColorLocal, - badgeNotification: this.badgeNotificationColorLocal, - - faint: this.faintColorLocal, - faintLink: this.faintLinkColorLocal, - border: this.borderColorLocal, - - cRed: this.cRedColorLocal, - cBlue: this.cBlueColorLocal, - cGreen: this.cGreenColorLocal, - cOrange: this.cOrangeColorLocal - } - }, - currentOpacity () { - return { - bg: this.bgOpacityLocal, - btn: this.btnOpacityLocal, - input: this.inputOpacityLocal, - panel: this.panelOpacityLocal, - topBar: this.topBarOpacityLocal, - border: this.borderOpacityLocal, - faint: this.faintOpacityLocal - } - }, - currentRadii () { - return { - btn: this.btnRadiusLocal, - input: this.inputRadiusLocal, - checkbox: this.checkboxRadiusLocal, - panel: this.panelRadiusLocal, - avatar: this.avatarRadiusLocal, - avatarAlt: this.avatarAltRadiusLocal, - tooltip: this.tooltipRadiusLocal, - attachment: this.attachmentRadiusLocal - } - }, - 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 () { - if (!this.previewTheme.colors.bg) return {} - const colors = this.previewTheme.colors - const opacity = this.previewTheme.opacity - if (!colors.bg) return {} - const hints = (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 - }) - - // fgsfds :DDDD - const fgs = { - text: hex2rgb(colors.text), - panelText: hex2rgb(colors.panelText), - panelLink: hex2rgb(colors.panelLink), - btnText: hex2rgb(colors.btnText), - topBarText: hex2rgb(colors.topBarText), - inputText: hex2rgb(colors.inputText), - - link: hex2rgb(colors.link), - topBarLink: hex2rgb(colors.topBarLink), - - red: hex2rgb(colors.cRed), - green: hex2rgb(colors.cGreen), - blue: hex2rgb(colors.cBlue), - orange: hex2rgb(colors.cOrange) - } - - const bgs = { - bg: hex2rgb(colors.bg), - btn: hex2rgb(colors.btn), - panel: hex2rgb(colors.panel), - topBar: hex2rgb(colors.topBar), - input: hex2rgb(colors.input), - alertError: hex2rgb(colors.alertError), - alertWarning: hex2rgb(colors.alertWarning), - badgeNotification: hex2rgb(colors.badgeNotification) - } - - /* This is a bit confusing because "bottom layer" used is text color - * This is done to get worst case scenario when background below transparent - * layer matches text color, making it harder to read the lower alpha is. - */ - const ratios = { - bgText: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.text), fgs.text), - bgLink: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.link), fgs.link), - bgRed: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.red), fgs.red), - bgGreen: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.green), fgs.green), - bgBlue: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.blue), fgs.blue), - bgOrange: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.orange), fgs.orange), - - tintText: getContrastRatio(alphaBlend(bgs.bg, 0.5, fgs.panelText), fgs.text), - - panelText: getContrastRatio(alphaBlend(bgs.panel, opacity.panel, fgs.panelText), fgs.panelText), - panelLink: getContrastRatio(alphaBlend(bgs.panel, opacity.panel, fgs.panelLink), fgs.panelLink), - - btnText: getContrastRatio(alphaBlend(bgs.btn, opacity.btn, fgs.btnText), fgs.btnText), - - inputText: getContrastRatio(alphaBlend(bgs.input, opacity.input, fgs.inputText), fgs.inputText), - - topBarText: getContrastRatio(alphaBlend(bgs.topBar, opacity.topBar, fgs.topBarText), fgs.topBarText), - topBarLink: getContrastRatio(alphaBlend(bgs.topBar, opacity.topBar, fgs.topBarLink), fgs.topBarLink) - } - - return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {}) - }, - previewRules () { - if (!this.preview.rules) return '' - return [ - ...Object.values(this.preview.rules), - 'color: var(--text)', - 'font-family: var(--interfaceFont, sans-serif)' - ].join(';') - }, - shadowsAvailable () { - return Object.keys(this.previewTheme.shadows).sort() - }, - currentShadowOverriden: { - get () { - return !!this.currentShadow - }, - set (val) { - if (val) { - set(this.shadowsLocal, this.shadowSelected, this.currentShadowFallback.map(_ => Object.assign({}, _))) - } else { - del(this.shadowsLocal, this.shadowSelected) - } - } - }, - currentShadowFallback () { - return this.previewTheme.shadows[this.shadowSelected] - }, - currentShadow: { - get () { - return this.shadowsLocal[this.shadowSelected] - }, - set (v) { - set(this.shadowsLocal, this.shadowSelected, v) - } - }, - themeValid () { - return !this.shadowsInvalid && !this.colorsInvalid && !this.radiiInvalid - }, - exportedTheme () { - const saveEverything = ( - !this.keepFonts && - !this.keepShadows && - !this.keepOpacity && - !this.keepRoundness && - !this.keepColor - ) - - const theme = {} - - if (this.keepFonts || saveEverything) { - theme.fonts = this.fontsLocal - } - if (this.keepShadows || saveEverything) { - theme.shadows = this.shadowsLocal - } - if (this.keepOpacity || saveEverything) { - theme.opacity = this.currentOpacity - } - if (this.keepColor || saveEverything) { - theme.colors = this.currentColors - } - if (this.keepRoundness || saveEverything) { - theme.radii = this.currentRadii - } - - return { - // To separate from other random JSON files and possible future theme formats - _pleroma_theme_version: 2, theme - } - } - }, - components: { - ColorInput, - OpacityInput, - RangeInput, - ContrastRatio, - ShadowControl, - FontControl, - TabSwitcher, - Preview, - ExportImport, - Checkbox - }, - methods: { - setCustomTheme () { - this.$store.dispatch('setOption', { - name: 'customTheme', - value: { - shadows: this.shadowsLocal, - fonts: this.fontsLocal, - opacity: this.currentOpacity, - colors: this.currentColors, - radii: this.currentRadii - } - }) - }, - onImport (parsed) { - if (parsed._pleroma_theme_version === 1) { - this.normalizeLocalState(parsed, 1) - } else if (parsed._pleroma_theme_version === 2) { - this.normalizeLocalState(parsed.theme, 2) - } - }, - importValidator (parsed) { - const version = parsed._pleroma_theme_version - return version >= 1 || version <= 2 - }, - clearAll () { - const state = this.$store.getters.mergedConfig.customTheme - const version = state.colors ? 2 : 'l1' - this.normalizeLocalState(this.$store.getters.mergedConfig.customTheme, version) - }, - - // Clears all the extra stuff when loading V1 theme - clearV1 () { - Object.keys(this.$data) - .filter(_ => _.endsWith('ColorLocal') || _.endsWith('OpacityLocal')) - .filter(_ => !v1OnlyNames.includes(_)) - .forEach(key => { - set(this.$data, key, undefined) - }) - }, - - clearRoundness () { - Object.keys(this.$data) - .filter(_ => _.endsWith('RadiusLocal')) - .forEach(key => { - set(this.$data, key, undefined) - }) - }, - - clearOpacity () { - Object.keys(this.$data) - .filter(_ => _.endsWith('OpacityLocal')) - .forEach(key => { - set(this.$data, key, undefined) - }) - }, - - clearShadows () { - this.shadowsLocal = {} - }, - - clearFonts () { - this.fontsLocal = {} - }, - - /** - * This applies stored theme data onto form. Supports three versions of data: - * v2 (version = 2) - newer version of themes. - * v1 (version = 1) - older version of themes (import from file) - * v1l (version = l1) - older version of theme (load from local storage) - * v1 and v1l differ because of way themes were stored/exported. - * @param {Object} input - input data - * @param {Number} version - version of data. 0 means try to guess based on data. "l1" means v1, locastorage type - */ - normalizeLocalState (input, version = 0) { - const colors = input.colors || input - const radii = input.radii || input - const opacity = input.opacity - const shadows = input.shadows || {} - const fonts = input.fonts || {} - - if (version === 0) { - if (input.version) version = input.version - // Old v1 naming: fg is text, btn is foreground - if (typeof colors.text === 'undefined' && typeof colors.fg !== 'undefined') { - version = 1 - } - // New v2 naming: text is text, fg is foreground - if (typeof colors.text !== 'undefined' && typeof colors.fg !== 'undefined') { - version = 2 - } - } - - // Stuff that differs between V1 and V2 - if (version === 1) { - this.fgColorLocal = rgb2hex(colors.btn) - this.textColorLocal = rgb2hex(colors.fg) - } - - if (!this.keepColor) { - this.clearV1() - const keys = new Set(version !== 1 ? Object.keys(colors) : []) - if (version === 1 || version === 'l1') { - keys - .add('bg') - .add('link') - .add('cRed') - .add('cBlue') - .add('cGreen') - .add('cOrange') - } - - keys.forEach(key => { - this[key + 'ColorLocal'] = rgb2hex(colors[key]) - }) - } - - if (!this.keepRoundness) { - this.clearRoundness() - Object.entries(radii).forEach(([k, v]) => { - // 'Radius' is kept mostly for v1->v2 localstorage transition - const key = k.endsWith('Radius') ? k.split('Radius')[0] : k - this[key + 'RadiusLocal'] = v - }) - } - - if (!this.keepShadows) { - this.clearShadows() - this.shadowsLocal = shadows - this.shadowSelected = this.shadowsAvailable[0] - } - - if (!this.keepFonts) { - this.clearFonts() - this.fontsLocal = fonts - } - - if (opacity && !this.keepOpacity) { - this.clearOpacity() - Object.entries(opacity).forEach(([k, v]) => { - if (typeof v === 'undefined' || v === null || Number.isNaN(v)) return - this[k + 'OpacityLocal'] = v - }) - } - } - }, - watch: { - currentRadii () { - try { - this.previewRadii = generateRadii({ radii: this.currentRadii }) - this.radiiInvalid = false - } catch (e) { - this.radiiInvalid = true - console.warn(e) - } - }, - shadowsLocal: { - handler () { - try { - this.previewShadows = generateShadows({ shadows: this.shadowsLocal }) - this.shadowsInvalid = false - } catch (e) { - this.shadowsInvalid = true - console.warn(e) - } - }, - deep: true - }, - fontsLocal: { - handler () { - try { - this.previewFonts = generateFonts({ fonts: this.fontsLocal }) - this.fontsInvalid = false - } catch (e) { - this.fontsInvalid = true - console.warn(e) - } - }, - deep: true - }, - currentColors () { - try { - this.previewColors = generateColors({ - opacity: this.currentOpacity, - colors: this.currentColors - }) - this.colorsInvalid = false - } catch (e) { - this.colorsInvalid = true - console.warn(e) - } - }, - currentOpacity () { - try { - this.previewColors = generateColors({ - opacity: this.currentOpacity, - colors: this.currentColors - }) - } catch (e) { - console.warn(e) - } - }, - selected () { - if (this.selectedVersion === 1) { - if (!this.keepRoundness) { - this.clearRoundness() - } - - if (!this.keepShadows) { - this.clearShadows() - } - - if (!this.keepOpacity) { - this.clearOpacity() - } - - if (!this.keepColor) { - this.clearV1() - - this.bgColorLocal = this.selected[1] - this.fgColorLocal = this.selected[2] - this.textColorLocal = this.selected[3] - this.linkColorLocal = this.selected[4] - this.cRedColorLocal = this.selected[5] - this.cGreenColorLocal = this.selected[6] - this.cBlueColorLocal = this.selected[7] - this.cOrangeColorLocal = this.selected[8] - } - } else if (this.selectedVersion >= 2) { - this.normalizeLocalState(this.selected.theme, 2) - } - } - } -} diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js index 008e1e95..7891cb78 100644 --- a/src/components/tab_switcher/tab_switcher.js +++ b/src/components/tab_switcher/tab_switcher.js @@ -24,6 +24,11 @@ export default Vue.component('tab-switcher', { required: false, type: Boolean, default: false + }, + sideTabBar: { + required: false, + type: Boolean, + default: false } }, data () { @@ -55,6 +60,9 @@ export default Vue.component('tab-switcher', { this.onSwitch.call(null, this.$slots.default[index].key) } this.active = index + if (this.scrollableTabs) { + this.$refs.contents.scrollTop = 0 + } } } }, @@ -64,7 +72,6 @@ export default Vue.component('tab-switcher', { if (!slot.tag) return const classesTab = ['tab'] const classesWrapper = ['tab-wrapper'] - if (this.activeIndex === index) { classesTab.push('active') classesWrapper.push('active') @@ -87,8 +94,14 @@ export default Vue.component('tab-switcher', { <button disabled={slot.data.attrs.disabled} onClick={this.activateTab(index)} - class={classesTab.join(' ')}> - {slot.data.attrs.label}</button> + class={classesTab.join(' ')} + type="button" + > + {!slot.data.attrs.icon ? '' : (<i class={'tab-icon icon-' + slot.data.attrs.icon}/>)} + <span class="text"> + {slot.data.attrs.label} + </span> + </button> </div> ) }) @@ -96,20 +109,32 @@ export default Vue.component('tab-switcher', { const contents = this.$slots.default.map((slot, index) => { if (!slot.tag) return const active = this.activeIndex === index - if (this.renderOnlyFocused) { - return active - ? <div class="active">{slot}</div> - : <div class="hidden"></div> + const classes = [ active ? 'active' : 'hidden' ] + if (slot.data.attrs.fullHeight) { + classes.push('full-height') } - return <div class={active ? 'active' : 'hidden' }>{slot}</div> + const renderSlot = (!this.renderOnlyFocused || active) + ? slot + : '' + + return ( + <div class={classes}> + { + this.sideTabBar + ? <h1 class="mobile-label">{slot.data.attrs.label}</h1> + : '' + } + {renderSlot} + </div> + ) }) return ( - <div class="tab-switcher"> + <div class={'tab-switcher ' + (this.sideTabBar ? 'side-tabs' : 'top-tabs')}> <div class="tabs"> {tabs} </div> - <div class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')}> + <div ref="contents" class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')}> {contents} </div> </div> diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss index 3e5eacd5..d2ef4857 100644 --- a/src/components/tab_switcher/tab_switcher.scss +++ b/src/components/tab_switcher/tab_switcher.scss @@ -2,7 +2,144 @@ .tab-switcher { display: flex; - flex-direction: column; + + .tab-icon { + font-size: 2em; + display: block; + } + + &.top-tabs { + flex-direction: column; + + > .tabs { + width: 100%; + overflow-y: hidden; + overflow-x: auto; + padding-top: 5px; + flex-direction: row; + + &::after, &::before { + content: ''; + flex: 1 1 auto; + border-bottom: 1px solid; + border-bottom-color: $fallback--border; + border-bottom-color: var(--border, $fallback--border); + } + .tab-wrapper { + height: 28px; + + &:not(.active)::after { + left: 0; + right: 0; + bottom: 0; + border-bottom: 1px solid; + border-bottom-color: $fallback--border; + border-bottom-color: var(--border, $fallback--border); + } + } + .tab { + width: 100%; + min-width: 1px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + padding-bottom: 99px; + margin-bottom: 6px - 99px; + } + } + .contents.scrollable-tabs { + flex-basis: 0; + } + } + + &.side-tabs { + flex-direction: row; + + @media all and (max-width: 800px) { + overflow-x: auto; + } + + > .contents { + flex: 1 1 auto; + } + + > .tabs { + flex: 0 0 auto; + overflow-y: auto; + overflow-x: hidden; + flex-direction: column; + + &::after, &::before { + flex-shrink: 0; + flex-basis: .5em; + content: ''; + border-right: 1px solid; + border-right-color: $fallback--border; + border-right-color: var(--border, $fallback--border); + } + + &::after { + flex-grow: 1; + } + + &::before { + flex-grow: 0; + } + + .tab-wrapper { + min-width: 10em; + display: flex; + flex-direction: column; + + @media all and (max-width: 800px) { + min-width: 1em; + } + + &:not(.active)::after { + top: 0; + right: 0; + bottom: 0; + border-right: 1px solid; + border-right-color: $fallback--border; + border-right-color: var(--border, $fallback--border); + } + + &::before { + flex: 0 0 6px; + content: ''; + border-right: 1px solid; + border-right-color: $fallback--border; + border-right-color: var(--border, $fallback--border); + } + + &:last-child .tab { + margin-bottom: 0; + } + } + + .tab { + flex: 1; + box-sizing: content-box; + min-width: 10em; + min-width: 1px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + padding-left: 1em; + padding-right: calc(1em + 200px); + margin-right: -200px; + margin-left: 1em; + + @media all and (max-width: 800px) { + padding-left: .25em; + padding-right: calc(.25em + 200px); + margin-right: calc(.25em - 200px); + margin-left: .25em; + .text { + display: none + } + } + } + } + } .contents { flex: 1 0 auto; @@ -11,81 +148,89 @@ .hidden { display: none; } + .full-height:not(.hidden) { + height: 100%; + display: flex; + flex-direction: column; + > *:not(.mobile-label) { + flex: 1; + } + } &.scrollable-tabs { - flex-basis: 0; overflow-y: auto; } } + + .tab { + position: relative; + white-space: nowrap; + padding: 6px 1em; + background-color: $fallback--fg; + background-color: var(--tab, $fallback--fg); + + &, &:active .tab-icon { + color: $fallback--text; + color: var(--tabText, $fallback--text); + } + + &:not(.active) { + z-index: 4; + + &:hover { + z-index: 6; + } + } + + &.active { + background: transparent; + z-index: 5; + color: $fallback--text; + color: var(--tabActiveText, $fallback--text); + } + + img { + max-height: 26px; + vertical-align: top; + margin-top: -5px; + } + } + .tabs { display: flex; position: relative; - width: 100%; - overflow-y: hidden; - overflow-x: auto; - padding-top: 5px; box-sizing: border-box; &::after, &::before { display: block; - content: ''; flex: 1 1 auto; - border-bottom: 1px solid; - border-bottom-color: $fallback--border; - border-bottom-color: var(--border, $fallback--border); } + } - .tab-wrapper { - height: 28px; - position: relative; - display: flex; - flex: 0 0 auto; + .tab-wrapper { + position: relative; + display: flex; + flex: 0 0 auto; - .tab { - width: 100%; - min-width: 1px; - position: relative; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - padding: 6px 1em; - padding-bottom: 99px; - margin-bottom: 6px - 99px; - white-space: nowrap; - - &:not(.active) { - z-index: 4; - - &:hover { - z-index: 6; - } - } - - &.active { - background: transparent; - z-index: 5; - } - - img { - max-height: 26px; - vertical-align: top; - margin-top: -5px; - } - } - - &:not(.active) { - &::after { - content: ''; - position: absolute; - left: 0; - right: 0; - bottom: 0; - z-index: 7; - border-bottom: 1px solid; - border-bottom-color: $fallback--border; - border-bottom-color: var(--border, $fallback--border); - } + &:not(.active) { + &::after { + content: ''; + position: absolute; + z-index: 7; } } + } + .mobile-label { + padding-left: .3em; + padding-bottom: .25em; + margin-top: .5em; + margin-left: .2em; + margin-bottom: .25em; + border-bottom: 1px solid var(--border, $fallback--border); + + @media all and (min-width: 800px) { + display: none; + } } } diff --git a/src/components/tag_timeline/tag_timeline.js b/src/components/tag_timeline/tag_timeline.js index 458eb1c5..400c6a4b 100644 --- a/src/components/tag_timeline/tag_timeline.js +++ b/src/components/tag_timeline/tag_timeline.js @@ -19,7 +19,7 @@ const TagTimeline = { } }, destroyed () { - this.$store.dispatch('stopFetching', 'tag') + this.$store.dispatch('stopFetchingTimeline', 'tag') } } diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 27a9a55e..9a53acd6 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -36,7 +36,12 @@ const Timeline = { } }, computed: { - timelineError () { return this.$store.state.statuses.error }, + timelineError () { + return this.$store.state.statuses.error + }, + errorData () { + return this.$store.state.statuses.errorData + }, newStatusCount () { return this.timeline.newStatusCount }, diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index f1d3903a..9777bd0c 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -11,15 +11,22 @@ > {{ $t('timeline.error_fetching') }} </div> + <div + v-else-if="errorData" + class="loadmore-error alert error" + @click.prevent + > + {{ errorData.statusText }} + </div> <button - v-if="timeline.newStatusCount > 0 && !timelineError" + v-if="timeline.newStatusCount > 0 && !timelineError && !errorData" class="loadmore-button" @click.prevent="showNewStatuses" > {{ $t('timeline.show_new') }}{{ newStatusCountStr }} </button> <div - v-if="!timeline.newStatusCount > 0 && !timelineError" + v-if="!timeline.newStatusCount > 0 && !timelineError && !errorData" class="loadmore-text faint" @click.prevent > @@ -37,6 +44,7 @@ :collapsable="true" :pinned-status-ids-object="pinnedStatusIdsObject" :in-profile="inProfile" + :profile-user-id="userId" /> </template> <template v-for="status in timeline.visibleStatuses"> @@ -47,6 +55,7 @@ :status-id="status.id" :collapsable="true" :in-profile="inProfile" + :profile-user-id="userId" /> </template> </div> @@ -65,12 +74,18 @@ {{ $t('timeline.no_more_statuses') }} </div> <a - v-else-if="!timeline.loading" + v-else-if="!timeline.loading && !errorData" href="#" @click.prevent="fetchOlderStatuses()" > <div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div> </a> + <a + v-else-if="errorData" + href="#" + > + <div class="new-status-notification text-center panel-footer">{{ errorData.error }}</div> + </a> <div v-else class="new-status-notification text-center panel-footer" @@ -91,17 +106,4 @@ opacity: 1; } } - -.new-status-notification { - position:relative; - margin-top: -1px; - font-size: 1.1em; - border-width: 1px 0 0 0; - border-style: solid; - border-color: var(--border, $fallback--border); - padding: 10px; - z-index: 1; - background-color: $fallback--fg; - background-color: var(--panel, $fallback--fg); -} </style> diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index cc8a1ed6..8e6b9d7f 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -4,13 +4,12 @@ import ProgressButton from '../progress_button/progress_button.vue' import FollowButton from '../follow_button/follow_button.vue' import ModerationTools from '../moderation_tools/moderation_tools.vue' import AccountActions from '../account_actions/account_actions.vue' -import { hex2rgb } from '../../services/color_convert/color_convert.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import { mapGetters } from 'vuex' export default { props: [ - 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar' + 'userId', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar' ], data () { return { @@ -22,6 +21,12 @@ export default { this.$store.dispatch('fetchUserRelationship', this.user.id) }, computed: { + user () { + return this.$store.getters.findUser(this.userId) + }, + relationship () { + return this.$store.getters.relationship(this.userId) + }, classes () { return [{ 'user-card-rounded-t': this.rounded === 'top', // set border-top-left-radius and border-top-right-radius @@ -30,21 +35,11 @@ export default { }] }, style () { - const color = this.$store.getters.mergedConfig.customTheme.colors - ? this.$store.getters.mergedConfig.customTheme.colors.bg // v2 - : this.$store.getters.mergedConfig.colors.bg // v1 - - if (color) { - const rgb = (typeof color === 'string') ? hex2rgb(color) : color - const tintColor = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .5)` - - return { - backgroundColor: `rgb(${Math.floor(rgb.r * 0.53)}, ${Math.floor(rgb.g * 0.56)}, ${Math.floor(rgb.b * 0.59)})`, - backgroundImage: [ - `linear-gradient(to bottom, ${tintColor}, ${tintColor})`, - `url(${this.user.cover_photo})` - ].join(', ') - } + return { + backgroundImage: [ + `linear-gradient(to bottom, var(--profileTint), var(--profileTint))`, + `url(${this.user.cover_photo})` + ].join(', ') } }, isOtherUser () { @@ -93,6 +88,12 @@ export default { const roleTitle = rights.admin ? 'admin' : 'moderator' return validRole && roleTitle }, + hideFollowsCount () { + return this.isOtherUser && this.user.hide_follows_count + }, + hideFollowersCount () { + return this.isOtherUser && this.user.hide_followers_count + }, ...mapGetters(['mergedConfig']) }, components: { @@ -143,6 +144,9 @@ export default { } this.$store.dispatch('setMedia', [attachment]) this.$store.dispatch('setCurrent', attachment) + }, + mentionUser () { + this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user }) } } } diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index 6f3c958e..c4a5ce9d 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -50,15 +50,6 @@ > {{ user.name }} </div> - <router-link - v-if="!isOtherUser" - :to="{ name: 'user-settings' }" - > - <i - class="button-icon icon-wrench usersettings" - :title="$t('tool_tip.user_settings')" - /> - </router-link> <a v-if="isOtherUser && !user.is_local" :href="user.statusnet_profile_url" @@ -69,6 +60,7 @@ <AccountActions v-if="isOtherUser && loggedIn" :user="user" + :relationship="relationship" /> </div> <div class="bottom-line"> @@ -92,7 +84,7 @@ </div> <div class="user-meta"> <div - v-if="user.follows_you && loggedIn && isOtherUser" + v-if="relationship.followed_by && loggedIn && isOtherUser" class="following" > {{ $t('user_card.follows_you') }} @@ -117,7 +109,7 @@ type="color" > <label - for="style-switcher" + for="theme_tab" class="userHighlightSel select" > <select @@ -139,10 +131,10 @@ class="user-interactions" > <div class="btn-group"> - <FollowButton :user="user" /> - <template v-if="user.following"> + <FollowButton :relationship="relationship" /> + <template v-if="relationship.following"> <ProgressButton - v-if="!user.subscribed" + v-if="!relationship.subscribing" class="btn btn-default" :click="subscribeUser" :title="$t('user_card.subscribe')" @@ -151,7 +143,7 @@ </ProgressButton> <ProgressButton v-else - class="btn btn-default pressed" + class="btn btn-default toggled" :click="unsubscribeUser" :title="$t('user_card.unsubscribe')" > @@ -161,8 +153,8 @@ </div> <div> <button - v-if="user.muted" - class="btn btn-default btn-block pressed" + v-if="relationship.muting" + class="btn btn-default btn-block toggled" @click="unmuteUser" > {{ $t('user_card.muted') }} @@ -175,6 +167,14 @@ {{ $t('user_card.mute') }} </button> </div> + <div> + <button + class="btn btn-default btn-block" + @click="mentionUser" + > + {{ $t('user_card.mention') }} + </button> + </div> <ModerationTools v-if="loggedIn.role === "admin"" :user="user" @@ -208,14 +208,14 @@ @click.prevent="setProfileView('friends')" > <h5>{{ $t('user_card.followees') }}</h5> - <span>{{ user.friends_count }}</span> + <span>{{ hideFollowsCount ? $t('user_card.hidden') : user.friends_count }}</span> </div> <div class="user-count" @click.prevent="setProfileView('followers')" > <h5>{{ $t('user_card.followers') }}</h5> - <span>{{ user.followers_count }}</span> + <span>{{ hideFollowersCount ? $t('user_card.hidden') : user.followers_count }}</span> </div> </div> <!-- eslint-disable vue/no-v-html --> @@ -278,6 +278,7 @@ mask-size: 100% 60%; border-top-left-radius: calc(var(--panelRadius) - 1px); border-top-right-radius: calc(var(--panelRadius) - 1px); + background-color: var(--profileBg); &.hide-bio { mask-size: 100% 40px; @@ -291,6 +292,11 @@ &-bio { text-align: center; + a { + color: $fallback--link; + color: var(--postLink, $fallback--link); + } + img { object-fit: contain; vertical-align: middle; @@ -452,14 +458,13 @@ color: var(--text, $fallback--text); } - // TODO use proper colors .staff { flex: none; text-transform: capitalize; color: $fallback--text; - color: var(--btnText, $fallback--text); + color: var(--alertNeutralText, $fallback--text); background-color: $fallback--fg; - background-color: var(--btn, $fallback--fg); + background-color: var(--alertNeutral, $fallback--fg); } } @@ -530,12 +535,6 @@ button { margin: 0; - - &.pressed { - // TODO: This should be themed. - border-bottom-color: rgba(255, 255, 255, 0.2); - border-top-color: rgba(0, 0, 0, 0.2); - } } } } diff --git a/src/components/user_panel/user_panel.vue b/src/components/user_panel/user_panel.vue index e9f08015..1db4f648 100644 --- a/src/components/user_panel/user_panel.vue +++ b/src/components/user_panel/user_panel.vue @@ -6,7 +6,7 @@ class="panel panel-default signed-in" > <UserCard - :user="user" + :user-id="user.id" :hide-bio="true" rounded="top" /> diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 00055707..95760bf8 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -3,6 +3,7 @@ import UserCard from '../user_card/user_card.vue' import FollowCard from '../follow_card/follow_card.vue' import Timeline from '../timeline/timeline.vue' import Conversation from '../conversation/conversation.vue' +import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js' import List from '../list/list.vue' import withLoadMore from '../../hocs/with_load_more/with_load_more' @@ -112,9 +113,9 @@ const UserProfile = { } }, stopFetching () { - this.$store.dispatch('stopFetching', 'user') - this.$store.dispatch('stopFetching', 'favorites') - this.$store.dispatch('stopFetching', 'media') + this.$store.dispatch('stopFetchingTimeline', 'user') + this.$store.dispatch('stopFetchingTimeline', 'favorites') + this.$store.dispatch('stopFetchingTimeline', 'media') }, switchUser (userNameOrId) { this.stopFetching() @@ -146,6 +147,7 @@ const UserProfile = { FollowerList, FriendList, FollowCard, + TabSwitcher, Conversation } } diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index 14082e83..1871d46c 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -5,7 +5,7 @@ class="user-profile panel panel-default" > <UserCard - :user="user" + :user-id="userId" :switcher="true" :selected="timeline.viewing" :allow-zooming-avatar="true" diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js index 833fa98a..38cf117b 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.js +++ b/src/components/user_reporting_modal/user_reporting_modal.js @@ -64,7 +64,7 @@ const UserReportingModal = { forward: this.forward, statusIds: this.statusIdsToReport } - this.$store.state.api.backendInteractor.reportUser(params) + this.$store.state.api.backendInteractor.reportUser({ ...params }) .then(() => { this.processing = false this.resetState() diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js deleted file mode 100644 index 3fdc5340..00000000 --- a/src/components/user_settings/user_settings.js +++ /dev/null @@ -1,374 +0,0 @@ -import unescape from 'lodash/unescape' -import get from 'lodash/get' -import map from 'lodash/map' -import reject from 'lodash/reject' -import TabSwitcher from '../tab_switcher/tab_switcher.js' -import ImageCropper from '../image_cropper/image_cropper.vue' -import StyleSwitcher from '../style_switcher/style_switcher.vue' -import ScopeSelector from '../scope_selector/scope_selector.vue' -import fileSizeFormatService from '../../services/file_size_format/file_size_format.js' -import BlockCard from '../block_card/block_card.vue' -import MuteCard from '../mute_card/mute_card.vue' -import SelectableList from '../selectable_list/selectable_list.vue' -import ProgressButton from '../progress_button/progress_button.vue' -import EmojiInput from '../emoji_input/emoji_input.vue' -import suggestor from '../emoji_input/suggestor.js' -import Autosuggest from '../autosuggest/autosuggest.vue' -import Importer from '../importer/importer.vue' -import Exporter from '../exporter/exporter.vue' -import withSubscription from '../../hocs/with_subscription/with_subscription' -import Checkbox from '../checkbox/checkbox.vue' -import Mfa from './mfa.vue' - -const BlockList = withSubscription({ - fetch: (props, $store) => $store.dispatch('fetchBlocks'), - select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []), - childPropName: 'items' -})(SelectableList) - -const MuteList = withSubscription({ - fetch: (props, $store) => $store.dispatch('fetchMutes'), - select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []), - childPropName: 'items' -})(SelectableList) - -const UserSettings = { - data () { - return { - newEmail: '', - newName: this.$store.state.users.currentUser.name, - newBio: unescape(this.$store.state.users.currentUser.description), - newLocked: this.$store.state.users.currentUser.locked, - newNoRichText: this.$store.state.users.currentUser.no_rich_text, - newDefaultScope: this.$store.state.users.currentUser.default_scope, - hideFollows: this.$store.state.users.currentUser.hide_follows, - hideFollowers: this.$store.state.users.currentUser.hide_followers, - hideFollowsCount: this.$store.state.users.currentUser.hide_follows_count, - hideFollowersCount: this.$store.state.users.currentUser.hide_followers_count, - showRole: this.$store.state.users.currentUser.show_role, - role: this.$store.state.users.currentUser.role, - discoverable: this.$store.state.users.currentUser.discoverable, - pickAvatarBtnVisible: true, - bannerUploading: false, - backgroundUploading: false, - banner: null, - bannerPreview: null, - background: null, - backgroundPreview: null, - bannerUploadError: null, - backgroundUploadError: null, - changeEmailError: false, - changeEmailPassword: '', - changedEmail: false, - deletingAccount: false, - deleteAccountConfirmPasswordInput: '', - deleteAccountError: false, - changePasswordInputs: [ '', '', '' ], - changedPassword: false, - changePasswordError: false, - activeTab: 'profile', - notificationSettings: this.$store.state.users.currentUser.notification_settings - } - }, - created () { - this.$store.dispatch('fetchTokens') - }, - components: { - StyleSwitcher, - ScopeSelector, - TabSwitcher, - ImageCropper, - BlockList, - MuteList, - EmojiInput, - Autosuggest, - BlockCard, - MuteCard, - ProgressButton, - Importer, - Exporter, - Mfa, - Checkbox - }, - computed: { - user () { - return this.$store.state.users.currentUser - }, - emojiUserSuggestor () { - return suggestor({ - emoji: [ - ...this.$store.state.instance.emoji, - ...this.$store.state.instance.customEmoji - ], - users: this.$store.state.users.users, - updateUsersList: (input) => this.$store.dispatch('searchUsers', input) - }) - }, - emojiSuggestor () { - return suggestor({ emoji: [ - ...this.$store.state.instance.emoji, - ...this.$store.state.instance.customEmoji - ] }) - }, - pleromaBackend () { - return this.$store.state.instance.pleromaBackend - }, - minimalScopesMode () { - return this.$store.state.instance.minimalScopesMode - }, - vis () { - return { - public: { selected: this.newDefaultScope === 'public' }, - unlisted: { selected: this.newDefaultScope === 'unlisted' }, - private: { selected: this.newDefaultScope === 'private' }, - direct: { selected: this.newDefaultScope === 'direct' } - } - }, - currentSaveStateNotice () { - return this.$store.state.interface.settings.currentSaveStateNotice - }, - oauthTokens () { - return this.$store.state.oauthTokens.tokens.map(oauthToken => { - return { - id: oauthToken.id, - appName: oauthToken.app_name, - validUntil: new Date(oauthToken.valid_until).toLocaleDateString() - } - }) - } - }, - methods: { - updateProfile () { - this.$store.state.api.backendInteractor - .updateProfile({ - params: { - note: this.newBio, - locked: this.newLocked, - // Backend notation. - /* eslint-disable camelcase */ - display_name: this.newName, - default_scope: this.newDefaultScope, - no_rich_text: this.newNoRichText, - hide_follows: this.hideFollows, - hide_followers: this.hideFollowers, - discoverable: this.discoverable, - hide_follows_count: this.hideFollowsCount, - hide_followers_count: this.hideFollowersCount, - show_role: this.showRole - /* eslint-enable camelcase */ - } }).then((user) => { - this.$store.commit('addNewUsers', [user]) - this.$store.commit('setCurrentUser', user) - }) - }, - updateNotificationSettings () { - this.$store.state.api.backendInteractor - .updateNotificationSettings({ settings: this.notificationSettings }) - }, - changeVis (visibility) { - this.newDefaultScope = visibility - }, - uploadFile (slot, e) { - const file = e.target.files[0] - if (!file) { return } - if (file.size > this.$store.state.instance[slot + 'limit']) { - const filesize = fileSizeFormatService.fileSizeFormat(file.size) - const allowedsize = fileSizeFormatService.fileSizeFormat(this.$store.state.instance[slot + 'limit']) - this[slot + 'UploadError'] = this.$t('upload.error.base') + ' ' + this.$t('upload.error.file_too_big', { filesize: filesize.num, filesizeunit: filesize.unit, allowedsize: allowedsize.num, allowedsizeunit: allowedsize.unit }) - return - } - // eslint-disable-next-line no-undef - const reader = new FileReader() - reader.onload = ({ target }) => { - const img = target.result - this[slot + 'Preview'] = img - this[slot] = file - } - reader.readAsDataURL(file) - }, - submitAvatar (cropper, file) { - const that = this - return new Promise((resolve, reject) => { - function updateAvatar (avatar) { - that.$store.state.api.backendInteractor.updateAvatar({ avatar }) - .then((user) => { - that.$store.commit('addNewUsers', [user]) - that.$store.commit('setCurrentUser', user) - resolve() - }) - .catch((err) => { - reject(new Error(that.$t('upload.error.base') + ' ' + err.message)) - }) - } - - if (cropper) { - cropper.getCroppedCanvas().toBlob(updateAvatar, file.type) - } else { - updateAvatar(file) - } - }) - }, - clearUploadError (slot) { - this[slot + 'UploadError'] = null - }, - submitBanner () { - if (!this.bannerPreview) { return } - - this.bannerUploading = true - this.$store.state.api.backendInteractor.updateBanner({ banner: this.banner }) - .then((user) => { - this.$store.commit('addNewUsers', [user]) - this.$store.commit('setCurrentUser', user) - this.bannerPreview = null - }) - .catch((err) => { - this.bannerUploadError = this.$t('upload.error.base') + ' ' + err.message - }) - .then(() => { this.bannerUploading = false }) - }, - submitBg () { - if (!this.backgroundPreview) { return } - let background = this.background - this.backgroundUploading = true - this.$store.state.api.backendInteractor.updateBg({ background }).then((data) => { - if (!data.error) { - this.$store.commit('addNewUsers', [data]) - this.$store.commit('setCurrentUser', data) - this.backgroundPreview = null - } else { - this.backgroundUploadError = this.$t('upload.error.base') + data.error - } - this.backgroundUploading = false - }) - }, - importFollows (file) { - return this.$store.state.api.backendInteractor.importFollows(file) - .then((status) => { - if (!status) { - throw new Error('failed') - } - }) - }, - importBlocks (file) { - return this.$store.state.api.backendInteractor.importBlocks(file) - .then((status) => { - if (!status) { - throw new Error('failed') - } - }) - }, - generateExportableUsersContent (users) { - // Get addresses - return users.map((user) => { - // check is it's a local user - if (user && user.is_local) { - // append the instance address - // eslint-disable-next-line no-undef - return user.screen_name + '@' + location.hostname - } - return user.screen_name - }).join('\n') - }, - getFollowsContent () { - return this.$store.state.api.backendInteractor.exportFriends({ id: this.$store.state.users.currentUser.id }) - .then(this.generateExportableUsersContent) - }, - getBlocksContent () { - return this.$store.state.api.backendInteractor.fetchBlocks() - .then(this.generateExportableUsersContent) - }, - confirmDelete () { - this.deletingAccount = true - }, - deleteAccount () { - this.$store.state.api.backendInteractor.deleteAccount({ password: this.deleteAccountConfirmPasswordInput }) - .then((res) => { - if (res.status === 'success') { - this.$store.dispatch('logout') - this.$router.push({ name: 'root' }) - } else { - this.deleteAccountError = res.error - } - }) - }, - changePassword () { - const params = { - password: this.changePasswordInputs[0], - newPassword: this.changePasswordInputs[1], - newPasswordConfirmation: this.changePasswordInputs[2] - } - this.$store.state.api.backendInteractor.changePassword(params) - .then((res) => { - if (res.status === 'success') { - this.changedPassword = true - this.changePasswordError = false - this.logout() - } else { - this.changedPassword = false - this.changePasswordError = res.error - } - }) - }, - changeEmail () { - const params = { - email: this.newEmail, - password: this.changeEmailPassword - } - this.$store.state.api.backendInteractor.changeEmail(params) - .then((res) => { - if (res.status === 'success') { - this.changedEmail = true - this.changeEmailError = false - } else { - this.changedEmail = false - this.changeEmailError = res.error - } - }) - }, - activateTab (tabName) { - this.activeTab = tabName - }, - logout () { - this.$store.dispatch('logout') - this.$router.replace('/') - }, - revokeToken (id) { - if (window.confirm(`${this.$i18n.t('settings.revoke_token')}?`)) { - this.$store.dispatch('revokeToken', id) - } - }, - filterUnblockedUsers (userIds) { - return reject(userIds, (userId) => { - const user = this.$store.getters.findUser(userId) - return !user || user.statusnet_blocking || user.id === this.$store.state.users.currentUser.id - }) - }, - filterUnMutedUsers (userIds) { - return reject(userIds, (userId) => { - const user = this.$store.getters.findUser(userId) - return !user || user.muted || user.id === this.$store.state.users.currentUser.id - }) - }, - queryUserIds (query) { - return this.$store.dispatch('searchUsers', query) - .then((users) => map(users, 'id')) - }, - blockUsers (ids) { - return this.$store.dispatch('blockUsers', ids) - }, - unblockUsers (ids) { - return this.$store.dispatch('unblockUsers', ids) - }, - muteUsers (ids) { - return this.$store.dispatch('muteUsers', ids) - }, - unmuteUsers (ids) { - return this.$store.dispatch('unmuteUsers', ids) - }, - identity (value) { - return value - } - } -} - -export default UserSettings diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue deleted file mode 100644 index 8c18cf49..00000000 --- a/src/components/user_settings/user_settings.vue +++ /dev/null @@ -1,646 +0,0 @@ -<template> - <div class="settings panel panel-default"> - <div class="panel-heading"> - <div class="title"> - {{ $t('settings.user_settings') }} - </div> - <transition name="fade"> - <template v-if="currentSaveStateNotice"> - <div - v-if="currentSaveStateNotice.error" - class="alert error" - @click.prevent - > - {{ $t('settings.saving_err') }} - </div> - - <div - v-if="!currentSaveStateNotice.error" - class="alert transparent" - @click.prevent - > - {{ $t('settings.saving_ok') }} - </div> - </template> - </transition> - </div> - <div class="panel-body profile-edit"> - <tab-switcher> - <div :label="$t('settings.profile_tab')"> - <div class="setting-item"> - <h2>{{ $t('settings.name_bio') }}</h2> - <p>{{ $t('settings.name') }}</p> - <EmojiInput - v-model="newName" - enable-emoji-picker - :suggest="emojiSuggestor" - > - <input - id="username" - v-model="newName" - classname="name-changer" - > - </EmojiInput> - <p>{{ $t('settings.bio') }}</p> - <EmojiInput - v-model="newBio" - enable-emoji-picker - :suggest="emojiUserSuggestor" - > - <textarea - v-model="newBio" - classname="bio" - /> - </EmojiInput> - <p> - <Checkbox v-model="newLocked"> - {{ $t('settings.lock_account_description') }} - </Checkbox> - </p> - <div> - <label for="default-vis">{{ $t('settings.default_vis') }}</label> - <div - id="default-vis" - class="visibility-tray" - > - <scope-selector - :show-all="true" - :user-default="newDefaultScope" - :initial-scope="newDefaultScope" - :on-scope-change="changeVis" - /> - </div> - </div> - <p> - <Checkbox v-model="newNoRichText"> - {{ $t('settings.no_rich_text_description') }} - </Checkbox> - </p> - <p> - <Checkbox v-model="hideFollows"> - {{ $t('settings.hide_follows_description') }} - </Checkbox> - </p> - <p class="setting-subitem"> - <Checkbox - v-model="hideFollowsCount" - :disabled="!hideFollows" - > - {{ $t('settings.hide_follows_count_description') }} - </Checkbox> - </p> - <p> - <Checkbox - v-model="hideFollowers" - > - {{ $t('settings.hide_followers_description') }} - </Checkbox> - </p> - <p class="setting-subitem"> - <Checkbox - v-model="hideFollowersCount" - :disabled="!hideFollowers" - > - {{ $t('settings.hide_followers_count_description') }} - </Checkbox> - </p> - <p> - <Checkbox v-model="showRole"> - <template v-if="role === 'admin'"> - {{ $t('settings.show_admin_badge') }} - </template> - <template v-if="role === 'moderator'"> - {{ $t('settings.show_moderator_badge') }} - </template> - </Checkbox> - </p> - <p> - <Checkbox v-model="discoverable"> - {{ $t('settings.discoverable') }} - </Checkbox> - </p> - <button - :disabled="newName && newName.length === 0" - class="btn btn-default" - @click="updateProfile" - > - {{ $t('general.submit') }} - </button> - </div> - <div class="setting-item"> - <h2>{{ $t('settings.avatar') }}</h2> - <p class="visibility-notice"> - {{ $t('settings.avatar_size_instruction') }} - </p> - <p>{{ $t('settings.current_avatar') }}</p> - <img - :src="user.profile_image_url_original" - class="current-avatar" - > - <p>{{ $t('settings.set_new_avatar') }}</p> - <button - v-show="pickAvatarBtnVisible" - id="pick-avatar" - class="btn" - type="button" - > - {{ $t('settings.upload_a_photo') }} - </button> - <image-cropper - trigger="#pick-avatar" - :submit-handler="submitAvatar" - @open="pickAvatarBtnVisible=false" - @close="pickAvatarBtnVisible=true" - /> - </div> - <div class="setting-item"> - <h2>{{ $t('settings.profile_banner') }}</h2> - <p>{{ $t('settings.current_profile_banner') }}</p> - <img - :src="user.cover_photo" - class="banner" - > - <p>{{ $t('settings.set_new_profile_banner') }}</p> - <img - v-if="bannerPreview" - class="banner" - :src="bannerPreview" - > - <div> - <input - type="file" - @change="uploadFile('banner', $event)" - > - </div> - <i - v-if="bannerUploading" - class=" icon-spin4 animate-spin uploading" - /> - <button - v-else-if="bannerPreview" - class="btn btn-default" - @click="submitBanner" - > - {{ $t('general.submit') }} - </button> - <div - v-if="bannerUploadError" - class="alert error" - > - Error: {{ bannerUploadError }} - <i - class="button-icon icon-cancel" - @click="clearUploadError('banner')" - /> - </div> - </div> - <div class="setting-item"> - <h2>{{ $t('settings.profile_background') }}</h2> - <p>{{ $t('settings.set_new_profile_background') }}</p> - <img - v-if="backgroundPreview" - class="bg" - :src="backgroundPreview" - > - <div> - <input - type="file" - @change="uploadFile('background', $event)" - > - </div> - <i - v-if="backgroundUploading" - class=" icon-spin4 animate-spin uploading" - /> - <button - v-else-if="backgroundPreview" - class="btn btn-default" - @click="submitBg" - > - {{ $t('general.submit') }} - </button> - <div - v-if="backgroundUploadError" - class="alert error" - > - Error: {{ backgroundUploadError }} - <i - class="button-icon icon-cancel" - @click="clearUploadError('background')" - /> - </div> - </div> - </div> - - <div :label="$t('settings.security_tab')"> - <div class="setting-item"> - <h2>{{ $t('settings.change_email') }}</h2> - <div> - <p>{{ $t('settings.new_email') }}</p> - <input - v-model="newEmail" - type="email" - autocomplete="email" - > - </div> - <div> - <p>{{ $t('settings.current_password') }}</p> - <input - v-model="changeEmailPassword" - type="password" - autocomplete="current-password" - > - </div> - <button - class="btn btn-default" - @click="changeEmail" - > - {{ $t('general.submit') }} - </button> - <p v-if="changedEmail"> - {{ $t('settings.changed_email') }} - </p> - <template v-if="changeEmailError !== false"> - <p>{{ $t('settings.change_email_error') }}</p> - <p>{{ changeEmailError }}</p> - </template> - </div> - - <div class="setting-item"> - <h2>{{ $t('settings.change_password') }}</h2> - <div> - <p>{{ $t('settings.current_password') }}</p> - <input - v-model="changePasswordInputs[0]" - type="password" - > - </div> - <div> - <p>{{ $t('settings.new_password') }}</p> - <input - v-model="changePasswordInputs[1]" - type="password" - > - </div> - <div> - <p>{{ $t('settings.confirm_new_password') }}</p> - <input - v-model="changePasswordInputs[2]" - type="password" - > - </div> - <button - class="btn btn-default" - @click="changePassword" - > - {{ $t('general.submit') }} - </button> - <p v-if="changedPassword"> - {{ $t('settings.changed_password') }} - </p> - <p v-else-if="changePasswordError !== false"> - {{ $t('settings.change_password_error') }} - </p> - <p v-if="changePasswordError"> - {{ changePasswordError }} - </p> - </div> - - <div class="setting-item"> - <h2>{{ $t('settings.oauth_tokens') }}</h2> - <table class="oauth-tokens"> - <thead> - <tr> - <th>{{ $t('settings.app_name') }}</th> - <th>{{ $t('settings.valid_until') }}</th> - <th /> - </tr> - </thead> - <tbody> - <tr - v-for="oauthToken in oauthTokens" - :key="oauthToken.id" - > - <td>{{ oauthToken.appName }}</td> - <td>{{ oauthToken.validUntil }}</td> - <td class="actions"> - <button - class="btn btn-default" - @click="revokeToken(oauthToken.id)" - > - {{ $t('settings.revoke_token') }} - </button> - </td> - </tr> - </tbody> - </table> - </div> - <mfa /> - <div class="setting-item"> - <h2>{{ $t('settings.delete_account') }}</h2> - <p v-if="!deletingAccount"> - {{ $t('settings.delete_account_description') }} - </p> - <div v-if="deletingAccount"> - <p>{{ $t('settings.delete_account_instructions') }}</p> - <p>{{ $t('login.password') }}</p> - <input - v-model="deleteAccountConfirmPasswordInput" - type="password" - > - <button - class="btn btn-default" - @click="deleteAccount" - > - {{ $t('settings.delete_account') }} - </button> - </div> - <p v-if="deleteAccountError !== false"> - {{ $t('settings.delete_account_error') }} - </p> - <p v-if="deleteAccountError"> - {{ deleteAccountError }} - </p> - <button - v-if="!deletingAccount" - class="btn btn-default" - @click="confirmDelete" - > - {{ $t('general.submit') }} - </button> - </div> - </div> - - <div - v-if="pleromaBackend" - :label="$t('settings.notifications')" - > - <div class="setting-item"> - <div class="select-multiple"> - <span class="label">{{ $t('settings.notification_setting') }}</span> - <ul class="option-list"> - <li> - <Checkbox v-model="notificationSettings.follows"> - {{ $t('settings.notification_setting_follows') }} - </Checkbox> - </li> - <li> - <Checkbox v-model="notificationSettings.followers"> - {{ $t('settings.notification_setting_followers') }} - </Checkbox> - </li> - <li> - <Checkbox v-model="notificationSettings.non_follows"> - {{ $t('settings.notification_setting_non_follows') }} - </Checkbox> - </li> - <li> - <Checkbox v-model="notificationSettings.non_followers"> - {{ $t('settings.notification_setting_non_followers') }} - </Checkbox> - </li> - </ul> - </div> - <p>{{ $t('settings.notification_mutes') }}</p> - <p>{{ $t('settings.notification_blocks') }}</p> - <button - class="btn btn-default" - @click="updateNotificationSettings" - > - {{ $t('general.submit') }} - </button> - </div> - </div> - - <div - v-if="pleromaBackend" - :label="$t('settings.data_import_export_tab')" - > - <div class="setting-item"> - <h2>{{ $t('settings.follow_import') }}</h2> - <p>{{ $t('settings.import_followers_from_a_csv_file') }}</p> - <Importer - :submit-handler="importFollows" - :success-message="$t('settings.follows_imported')" - :error-message="$t('settings.follow_import_error')" - /> - </div> - <div class="setting-item"> - <h2>{{ $t('settings.follow_export') }}</h2> - <Exporter - :get-content="getFollowsContent" - filename="friends.csv" - :export-button-label="$t('settings.follow_export_button')" - /> - </div> - <div class="setting-item"> - <h2>{{ $t('settings.block_import') }}</h2> - <p>{{ $t('settings.import_blocks_from_a_csv_file') }}</p> - <Importer - :submit-handler="importBlocks" - :success-message="$t('settings.blocks_imported')" - :error-message="$t('settings.block_import_error')" - /> - </div> - <div class="setting-item"> - <h2>{{ $t('settings.block_export') }}</h2> - <Exporter - :get-content="getBlocksContent" - filename="blocks.csv" - :export-button-label="$t('settings.block_export_button')" - /> - </div> - </div> - - <div :label="$t('settings.blocks_tab')"> - <div class="profile-edit-usersearch-wrapper"> - <Autosuggest - :filter="filterUnblockedUsers" - :query="queryUserIds" - :placeholder="$t('settings.search_user_to_block')" - > - <BlockCard - slot-scope="row" - :user-id="row.item" - /> - </Autosuggest> - </div> - <BlockList - :refresh="true" - :get-key="identity" - > - <template - slot="header" - slot-scope="{selected}" - > - <div class="profile-edit-bulk-actions"> - <ProgressButton - v-if="selected.length > 0" - class="btn btn-default" - :click="() => blockUsers(selected)" - > - {{ $t('user_card.block') }} - <template slot="progress"> - {{ $t('user_card.block_progress') }} - </template> - </ProgressButton> - <ProgressButton - v-if="selected.length > 0" - class="btn btn-default" - :click="() => unblockUsers(selected)" - > - {{ $t('user_card.unblock') }} - <template slot="progress"> - {{ $t('user_card.unblock_progress') }} - </template> - </ProgressButton> - </div> - </template> - <template - slot="item" - slot-scope="{item}" - > - <BlockCard :user-id="item" /> - </template> - <template slot="empty"> - {{ $t('settings.no_blocks') }} - </template> - </BlockList> - </div> - - <div :label="$t('settings.mutes_tab')"> - <div class="profile-edit-usersearch-wrapper"> - <Autosuggest - :filter="filterUnMutedUsers" - :query="queryUserIds" - :placeholder="$t('settings.search_user_to_mute')" - > - <MuteCard - slot-scope="row" - :user-id="row.item" - /> - </Autosuggest> - </div> - <MuteList - :refresh="true" - :get-key="identity" - > - <template - slot="header" - slot-scope="{selected}" - > - <div class="profile-edit-bulk-actions"> - <ProgressButton - v-if="selected.length > 0" - class="btn btn-default" - :click="() => muteUsers(selected)" - > - {{ $t('user_card.mute') }} - <template slot="progress"> - {{ $t('user_card.mute_progress') }} - </template> - </ProgressButton> - <ProgressButton - v-if="selected.length > 0" - class="btn btn-default" - :click="() => unmuteUsers(selected)" - > - {{ $t('user_card.unmute') }} - <template slot="progress"> - {{ $t('user_card.unmute_progress') }} - </template> - </ProgressButton> - </div> - </template> - <template - slot="item" - slot-scope="{item}" - > - <MuteCard :user-id="item" /> - </template> - <template slot="empty"> - {{ $t('settings.no_mutes') }} - </template> - </MuteList> - </div> - </tab-switcher> - </div> - </div> -</template> - -<script src="./user_settings.js"> -</script> - -<style lang="scss"> -@import '../../_variables.scss'; - -.profile-edit { - .bio { - margin: 0; - } - - .visibility-tray { - padding-top: 5px; - } - - input[type=file] { - padding: 5px; - height: auto; - } - - .banner { - max-width: 100%; - } - - .uploading { - font-size: 1.5em; - margin: 0.25em; - } - - .name-changer { - width: 100%; - } - - .bg { - max-width: 100%; - } - - .current-avatar { - display: block; - width: 150px; - height: 150px; - border-radius: $fallback--avatarRadius; - border-radius: var(--avatarRadius, $fallback--avatarRadius); - } - - .oauth-tokens { - width: 100%; - - th { - text-align: left; - } - - .actions { - text-align: right; - } - } - - &-usersearch-wrapper { - padding: 1em; - } - - &-bulk-actions { - text-align: right; - padding: 0 1em; - min-height: 28px; - - button { - width: 10em; - } - } - - .setting-subitem { - margin-left: 1.75em; - } -} -</style> diff --git a/src/hocs/with_load_more/with_load_more.js b/src/hocs/with_load_more/with_load_more.js index 1e1b2a74..6142f513 100644 --- a/src/hocs/with_load_more/with_load_more.js +++ b/src/hocs/with_load_more/with_load_more.js @@ -65,7 +65,7 @@ const withLoadMore = ({ } } }, - render (createElement) { + render (h) { const props = { props: { ...this.$props, @@ -74,7 +74,7 @@ const withLoadMore = ({ on: this.$listeners, scopedSlots: this.$scopedSlots } - const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value)) + const children = Object.entries(this.$slots).map(([key, value]) => h('template', { slot: key }, value)) return ( <div class="with-load-more"> <WrappedComponent {...props}> diff --git a/src/hocs/with_subscription/with_subscription.js b/src/hocs/with_subscription/with_subscription.js index 91fc4cca..1775adcb 100644 --- a/src/hocs/with_subscription/with_subscription.js +++ b/src/hocs/with_subscription/with_subscription.js @@ -49,7 +49,7 @@ const withSubscription = ({ } } }, - render (createElement) { + render (h) { if (!this.error && !this.loading) { const props = { props: { @@ -59,7 +59,7 @@ const withSubscription = ({ on: this.$listeners, scopedSlots: this.$scopedSlots } - const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value)) + const children = Object.entries(this.$slots).map(([key, value]) => h('template', { slot: key }, value)) return ( <div class="with-subscription"> <WrappedComponent {...props}> diff --git a/src/i18n/ar.json b/src/i18n/ar.json index 72e3010f..8bba2b97 100644 --- a/src/i18n/ar.json +++ b/src/i18n/ar.json @@ -1,206 +1,206 @@ { - "chat": { - "title": "الدردشة" + "chat": { + "title": "الدردشة" + }, + "features_panel": { + "chat": "الدردشة", + "gopher": "غوفر", + "media_proxy": "بروكسي الوسائط", + "scope_options": "", + "text_limit": "الحد الأقصى للنص", + "title": "الميّزات", + "who_to_follow": "للمتابعة" + }, + "finder": { + "error_fetching_user": "خطأ أثناء جلب صفحة المستخدم", + "find_user": "البحث عن مستخدِم" + }, + "general": { + "apply": "تطبيق", + "submit": "إرسال" + }, + "login": { + "login": "تسجيل الدخول", + "logout": "الخروج", + "password": "الكلمة السرية", + "placeholder": "مثال lain", + "register": "انشاء حساب", + "username": "إسم المستخدم" + }, + "nav": { + "chat": "الدردشة المحلية", + "friend_requests": "طلبات المتابَعة", + "mentions": "الإشارات", + "public_tl": "الخيط الزمني العام", + "timeline": "الخيط الزمني", + "twkn": "كافة الشبكة المعروفة" + }, + "notifications": { + "broken_favorite": "منشور مجهول، جارٍ البحث عنه…", + "favorited_you": "أعجِب بمنشورك", + "followed_you": "يُتابعك", + "load_older": "تحميل الإشعارات الأقدم", + "notifications": "الإخطارات", + "read": "مقروء!", + "repeated_you": "شارَك منشورك" + }, + "post_status": { + "account_not_locked_warning": "", + "account_not_locked_warning_link": "مقفل", + "attachments_sensitive": "اعتبر المرفقات كلها كمحتوى حساس", + "content_type": { + "text/plain": "نص صافٍ" }, - "features_panel": { - "chat": "الدردشة", - "gopher": "غوفر", - "media_proxy": "بروكسي الوسائط", - "scope_options": "", - "text_limit": "الحد الأقصى للنص", - "title": "الميّزات", - "who_to_follow": "للمتابعة" - }, - "finder": { - "error_fetching_user": "خطأ أثناء جلب صفحة المستخدم", - "find_user": "البحث عن مستخدِم" - }, - "general": { - "apply": "تطبيق", - "submit": "إرسال" - }, - "login": { - "login": "تسجيل الدخول", - "logout": "الخروج", - "password": "الكلمة السرية", - "placeholder": "مثال lain", - "register": "انشاء حساب", - "username": "إسم المستخدم" - }, - "nav": { - "chat": "الدردشة المحلية", - "friend_requests": "طلبات المتابَعة", - "mentions": "الإشارات", - "public_tl": "الخيط الزمني العام", - "timeline": "الخيط الزمني", - "twkn": "كافة الشبكة المعروفة" - }, - "notifications": { - "broken_favorite": "منشور مجهول، جارٍ البحث عنه…", - "favorited_you": "أعجِب بمنشورك", - "followed_you": "يُتابعك", - "load_older": "تحميل الإشعارات الأقدم", - "notifications": "الإخطارات", - "read": "مقروء!", - "repeated_you": "شارَك منشورك" - }, - "post_status": { - "account_not_locked_warning": "", - "account_not_locked_warning_link": "مقفل", - "attachments_sensitive": "اعتبر المرفقات كلها كمحتوى حساس", - "content_type": { - "text/plain": "نص صافٍ" - }, - "content_warning": "الموضوع (اختياري)", - "default": "وصلت للتوّ إلى لوس أنجلس.", - "direct_warning": "", - "posting": "النشر", - "scope": { - "direct": "", - "private": "", - "public": "علني - يُنشر على الخيوط الزمنية العمومية", - "unlisted": "غير مُدرَج - لا يُنشَر على الخيوط الزمنية العمومية" - } - }, - "registration": { - "bio": "السيرة الذاتية", - "email": "عنوان البريد الإلكتروني", - "fullname": "الإسم المعروض", - "password_confirm": "تأكيد الكلمة السرية", - "registration": "التسجيل", - "token": "رمز الدعوة" - }, - "settings": { - "attachmentRadius": "المُرفَقات", - "attachments": "المُرفَقات", - "autoload": "", - "avatar": "الصورة الرمزية", - "avatarAltRadius": "الصور الرمزية (الإشعارات)", - "avatarRadius": "الصور الرمزية", - "background": "الخلفية", - "bio": "السيرة الذاتية", - "btnRadius": "الأزرار", - "cBlue": "أزرق (الرد، المتابَعة)", - "cGreen": "أخضر (إعادة النشر)", - "cOrange": "برتقالي (مفضلة)", - "cRed": "أحمر (إلغاء)", - "change_password": "تغيير كلمة السر", - "change_password_error": "وقع هناك خلل أثناء تعديل كلمتك السرية.", - "changed_password": "تم تغيير كلمة المرور بنجاح!", - "collapse_subject": "", - "confirm_new_password": "تأكيد كلمة السر الجديدة", - "current_avatar": "صورتك الرمزية الحالية", - "current_password": "كلمة السر الحالية", - "current_profile_banner": "الرأسية الحالية لصفحتك الشخصية", - "data_import_export_tab": "تصدير واستيراد البيانات", - "default_vis": "أسلوب العرض الافتراضي", - "delete_account": "حذف الحساب", - "delete_account_description": "حذف حسابك و كافة منشوراتك نهائيًا.", - "delete_account_error": "", - "delete_account_instructions": "يُرجى إدخال كلمتك السرية أدناه لتأكيد عملية حذف الحساب.", - "export_theme": "حفظ النموذج", - "filtering": "التصفية", - "filtering_explanation": "سيتم إخفاء كافة المنشورات التي تحتوي على هذه الكلمات، كلمة واحدة في كل سطر", - "follow_export": "تصدير الاشتراكات", - "follow_export_button": "تصدير الاشتراكات كملف csv", - "follow_export_processing": "التصدير جارٍ، سوف يُطلَب منك تنزيل ملفك بعد حين", - "follow_import": "استيراد الاشتراكات", - "follow_import_error": "خطأ أثناء استيراد المتابِعين", - "follows_imported": "", - "foreground": "الأمامية", - "general": "الإعدادات العامة", - "hide_attachments_in_convo": "إخفاء المرفقات على المحادثات", - "hide_attachments_in_tl": "إخفاء المرفقات على الخيط الزمني", - "hide_post_stats": "", - "hide_user_stats": "", - "import_followers_from_a_csv_file": "", - "import_theme": "تحميل نموذج", - "inputRadius": "", - "instance_default": "", - "interfaceLanguage": "لغة الواجهة", - "invalid_theme_imported": "", - "limited_availability": "غير متوفر على متصفحك", - "links": "الروابط", - "lock_account_description": "", - "loop_video": "", - "loop_video_silent_only": "", - "name": "الاسم", - "name_bio": "الاسم والسيرة الذاتية", - "new_password": "كلمة السر الجديدة", - "no_rich_text_description": "", - "notification_visibility": "نوع الإشعارات التي تريد عرضها", - "notification_visibility_follows": "يتابع", - "notification_visibility_likes": "الإعجابات", - "notification_visibility_mentions": "الإشارات", - "notification_visibility_repeats": "", - "nsfw_clickthrough": "", - "oauth_tokens": "رموز OAuth", - "token": "رمز", - "refresh_token": "رمز التحديث", - "valid_until": "صالح حتى", - "revoke_token": "سحب", - "panelRadius": "", - "pause_on_unfocused": "", - "presets": "النماذج", - "profile_background": "خلفية الصفحة الشخصية", - "profile_banner": "رأسية الصفحة الشخصية", - "profile_tab": "الملف الشخصي", - "radii_help": "", - "replies_in_timeline": "الردود على الخيط الزمني", - "reply_link_preview": "", - "reply_visibility_all": "عرض كافة الردود", - "reply_visibility_following": "", - "reply_visibility_self": "", - "saving_err": "خطأ أثناء حفظ الإعدادات", - "saving_ok": "تم حفظ الإعدادات", - "security_tab": "الأمان", - "set_new_avatar": "اختيار صورة رمزية جديدة", - "set_new_profile_background": "اختيار خلفية جديدة للملف الشخصي", - "set_new_profile_banner": "اختيار رأسية جديدة للصفحة الشخصية", - "settings": "الإعدادات", - "stop_gifs": "", - "streaming": "", - "text": "النص", - "theme": "المظهر", - "theme_help": "", - "tooltipRadius": "", - "user_settings": "إعدادات المستخدم", - "values": { - "false": "لا", - "true": "نعم" - } - }, - "timeline": { - "collapse": "", - "conversation": "محادثة", - "error_fetching": "خطأ أثناء جلب التحديثات", - "load_older": "تحميل المنشورات القديمة", - "no_retweet_hint": "", - "repeated": "", - "show_new": "عرض الجديد", - "up_to_date": "تم تحديثه" - }, - "user_card": { - "approve": "قبول", - "block": "حظر", - "blocked": "تم حظره!", - "deny": "رفض", - "follow": "اتبع", - "followees": "", - "followers": "مُتابِعون", - "following": "", - "follows_you": "يتابعك!", - "mute": "كتم", - "muted": "تم كتمه", - "per_day": "في اليوم", - "remote_follow": "مُتابَعة عن بُعد", - "statuses": "المنشورات" - }, - "user_profile": { - "timeline_title": "الخيط الزمني للمستخدم" - }, - "who_to_follow": { - "more": "المزيد", - "who_to_follow": "للمتابعة" + "content_warning": "الموضوع (اختياري)", + "default": "وصلت للتوّ إلى لوس أنجلس.", + "direct_warning": "", + "posting": "النشر", + "scope": { + "direct": "", + "private": "", + "public": "علني - يُنشر على الخيوط الزمنية العمومية", + "unlisted": "غير مُدرَج - لا يُنشَر على الخيوط الزمنية العمومية" } -} \ No newline at end of file + }, + "registration": { + "bio": "السيرة الذاتية", + "email": "عنوان البريد الإلكتروني", + "fullname": "الإسم المعروض", + "password_confirm": "تأكيد الكلمة السرية", + "registration": "التسجيل", + "token": "رمز الدعوة" + }, + "settings": { + "attachmentRadius": "المُرفَقات", + "attachments": "المُرفَقات", + "autoload": "", + "avatar": "الصورة الرمزية", + "avatarAltRadius": "الصور الرمزية (الإشعارات)", + "avatarRadius": "الصور الرمزية", + "background": "الخلفية", + "bio": "السيرة الذاتية", + "btnRadius": "الأزرار", + "cBlue": "أزرق (الرد، المتابَعة)", + "cGreen": "أخضر (إعادة النشر)", + "cOrange": "برتقالي (مفضلة)", + "cRed": "أحمر (إلغاء)", + "change_password": "تغيير كلمة السر", + "change_password_error": "وقع هناك خلل أثناء تعديل كلمتك السرية.", + "changed_password": "تم تغيير كلمة المرور بنجاح!", + "collapse_subject": "", + "confirm_new_password": "تأكيد كلمة السر الجديدة", + "current_avatar": "صورتك الرمزية الحالية", + "current_password": "كلمة السر الحالية", + "current_profile_banner": "الرأسية الحالية لصفحتك الشخصية", + "data_import_export_tab": "تصدير واستيراد البيانات", + "default_vis": "أسلوب العرض الافتراضي", + "delete_account": "حذف الحساب", + "delete_account_description": "حذف حسابك و كافة منشوراتك نهائيًا.", + "delete_account_error": "", + "delete_account_instructions": "يُرجى إدخال كلمتك السرية أدناه لتأكيد عملية حذف الحساب.", + "export_theme": "حفظ النموذج", + "filtering": "التصفية", + "filtering_explanation": "سيتم إخفاء كافة المنشورات التي تحتوي على هذه الكلمات، كلمة واحدة في كل سطر", + "follow_export": "تصدير الاشتراكات", + "follow_export_button": "تصدير الاشتراكات كملف csv", + "follow_export_processing": "التصدير جارٍ، سوف يُطلَب منك تنزيل ملفك بعد حين", + "follow_import": "استيراد الاشتراكات", + "follow_import_error": "خطأ أثناء استيراد المتابِعين", + "follows_imported": "", + "foreground": "الأمامية", + "general": "الإعدادات العامة", + "hide_attachments_in_convo": "إخفاء المرفقات على المحادثات", + "hide_attachments_in_tl": "إخفاء المرفقات على الخيط الزمني", + "hide_post_stats": "", + "hide_user_stats": "", + "import_followers_from_a_csv_file": "", + "import_theme": "تحميل نموذج", + "inputRadius": "", + "instance_default": "", + "interfaceLanguage": "لغة الواجهة", + "invalid_theme_imported": "", + "limited_availability": "غير متوفر على متصفحك", + "links": "الروابط", + "lock_account_description": "", + "loop_video": "", + "loop_video_silent_only": "", + "name": "الاسم", + "name_bio": "الاسم والسيرة الذاتية", + "new_password": "كلمة السر الجديدة", + "no_rich_text_description": "", + "notification_visibility": "نوع الإشعارات التي تريد عرضها", + "notification_visibility_follows": "يتابع", + "notification_visibility_likes": "الإعجابات", + "notification_visibility_mentions": "الإشارات", + "notification_visibility_repeats": "", + "nsfw_clickthrough": "", + "oauth_tokens": "رموز OAuth", + "token": "رمز", + "refresh_token": "رمز التحديث", + "valid_until": "صالح حتى", + "revoke_token": "سحب", + "panelRadius": "", + "pause_on_unfocused": "", + "presets": "النماذج", + "profile_background": "خلفية الصفحة الشخصية", + "profile_banner": "رأسية الصفحة الشخصية", + "profile_tab": "الملف الشخصي", + "radii_help": "", + "replies_in_timeline": "الردود على الخيط الزمني", + "reply_link_preview": "", + "reply_visibility_all": "عرض كافة الردود", + "reply_visibility_following": "", + "reply_visibility_self": "", + "saving_err": "خطأ أثناء حفظ الإعدادات", + "saving_ok": "تم حفظ الإعدادات", + "security_tab": "الأمان", + "set_new_avatar": "اختيار صورة رمزية جديدة", + "set_new_profile_background": "اختيار خلفية جديدة للملف الشخصي", + "set_new_profile_banner": "اختيار رأسية جديدة للصفحة الشخصية", + "settings": "الإعدادات", + "stop_gifs": "", + "streaming": "", + "text": "النص", + "theme": "المظهر", + "theme_help": "", + "tooltipRadius": "", + "user_settings": "إعدادات المستخدم", + "values": { + "false": "لا", + "true": "نعم" + } + }, + "timeline": { + "collapse": "", + "conversation": "محادثة", + "error_fetching": "خطأ أثناء جلب التحديثات", + "load_older": "تحميل المنشورات القديمة", + "no_retweet_hint": "", + "repeated": "", + "show_new": "عرض الجديد", + "up_to_date": "تم تحديثه" + }, + "user_card": { + "approve": "قبول", + "block": "حظر", + "blocked": "تم حظره!", + "deny": "رفض", + "follow": "اتبع", + "followees": "", + "followers": "مُتابِعون", + "following": "", + "follows_you": "يتابعك!", + "mute": "كتم", + "muted": "تم كتمه", + "per_day": "في اليوم", + "remote_follow": "مُتابَعة عن بُعد", + "statuses": "المنشورات" + }, + "user_profile": { + "timeline_title": "الخيط الزمني للمستخدم" + }, + "who_to_follow": { + "more": "المزيد", + "who_to_follow": "للمتابعة" + } +} diff --git a/src/i18n/de.json b/src/i18n/de.json index a4b4c16f..a44e58cb 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -7,8 +7,8 @@ "gopher": "Gopher", "media_proxy": "Medienproxy", "scope_options": "Reichweitenoptionen", - "text_limit": "Textlimit", - "title": "Features", + "text_limit": "Zeichenlimit", + "title": "Funktionen", "who_to_follow": "Wem folgen?" }, "finder": { @@ -17,7 +17,18 @@ }, "general": { "apply": "Anwenden", - "submit": "Absenden" + "submit": "Absenden", + "more": "Mehr", + "generic_error": "Ein Fehler ist aufgetreten", + "optional": "Optional", + "show_more": "Zeige mehr", + "show_less": "Zeige weniger", + "dismiss": "Ablehnen", + "cancel": "Abbrechen", + "disable": "Deaktivieren", + "enable": "Aktivieren", + "confirm": "Bestätigen", + "verify": "Verifizieren" }, "login": { "login": "Anmelden", @@ -26,7 +37,16 @@ "password": "Passwort", "placeholder": "z.B. lain", "register": "Registrieren", - "username": "Benutzername" + "username": "Benutzername", + "authentication_code": "Authentifizierungscode", + "enter_recovery_code": "Gebe einen Wiederherstellungscode ein", + "recovery_code": "Wiederherstellungscode", + "heading": { + "totp": "Zwei-Faktor Authentifizierung", + "recovery": "Zwei-Faktor Wiederherstellung" + }, + "hint": "Anmelden um an der Diskussion teilzunehmen", + "enter_two_factor_code": "Gebe einen Zwei-Faktor-Code ein" }, "nav": { "about": "Über", @@ -41,7 +61,9 @@ "twkn": "Das gesamte bekannte Netzwerk", "user_search": "Benutzersuche", "search": "Suche", - "preferences": "Voreinstellungen" + "preferences": "Voreinstellungen", + "administration": "Administration", + "who_to_follow": "Wem folgen" }, "notifications": { "broken_favorite": "Unbekannte Nachricht, suche danach...", @@ -50,7 +72,11 @@ "load_older": "Ältere Benachrichtigungen laden", "notifications": "Benachrichtigungen", "read": "Gelesen!", - "repeated_you": "wiederholte deine Nachricht" + "repeated_you": "wiederholte deine Nachricht", + "follow_request": "möchte dir folgen", + "migrated_to": "migrierte zu", + "reacted_with": "reagierte mit {0}", + "no_more_notifications": "Keine Benachrichtigungen mehr" }, "post_status": { "new_status": "Neuen Status veröffentlichen", @@ -58,7 +84,10 @@ "account_not_locked_warning_link": "gesperrt", "attachments_sensitive": "Anhänge als heikel markieren", "content_type": { - "text/plain": "Nur Text" + "text/plain": "Nur Text", + "text/bbcode": "BBCode", + "text/markdown": "Markdown", + "text/html": "HTML" }, "content_warning": "Betreff (optional)", "default": "Sitze gerade im Hofbräuhaus.", @@ -69,6 +98,13 @@ "private": "Nur Follower - Beitrag nur für Follower sichtbar", "public": "Öffentlich - Beitrag an öffentliche Zeitleisten", "unlisted": "Nicht gelistet - Nicht in öffentlichen Zeitleisten anzeigen" + }, + "direct_warning_to_all": "Dieser Beitrag wird für alle erwähnten Benutzer sichtbar sein.", + "direct_warning_to_first_only": "Dieser Beitrag wird für alle Benutzer, die am Anfang der Nachricht erwähnt wurden, sichtbar sein.", + "scope_notice": { + "public": "Dieser Beitrag wird für alle sichtbar sein", + "private": "Dieser Beitrag wird nur für deine Follower sichtbar sein", + "unlisted": "Dieser Beitrag wird weder in der öffentlichen Zeitleiste noch im gesamten bekannten Netzwerk sichtbar sein" } }, "registration": { @@ -86,8 +122,11 @@ "email_required": "darf nicht leer sein", "password_required": "darf nicht leer sein", "password_confirmation_required": "darf nicht leer sein", - "password_confirmation_match": "sollte mit dem Passwort identisch sein." - } + "password_confirmation_match": "sollte mit dem Passwort identisch sein" + }, + "bio_placeholder": "z.B.\nHallo, ich bin Lain.\nIch bin ein Anime Mödchen aus dem vorstädtischen Japan. Du kennst mich vielleicht vom Wired.", + "fullname_placeholder": "z.B. Lain Iwakura", + "username_placeholder": "z.B. lain" }, "settings": { "attachmentRadius": "Anhänge", @@ -99,7 +138,7 @@ "background": "Hintergrund", "bio": "Bio", "btnRadius": "Buttons", - "cBlue": "Blau (Antworten, Folgt dir)", + "cBlue": "Blau (Antworten, folgt dir)", "cGreen": "Grün (Retweet)", "cOrange": "Orange (Favorisieren)", "cRed": "Rot (Abbrechen)", @@ -115,21 +154,21 @@ "data_import_export_tab": "Datenimport/-export", "default_vis": "Standard-Sichtbarkeitsumfang", "delete_account": "Account löschen", - "delete_account_description": "Lösche deinen Account und alle deine Nachrichten unwiderruflich.", + "delete_account_description": "Lösche deine Daten und deaktiviere deinen Account unwiderruflich.", "delete_account_error": "Es ist ein Fehler beim Löschen deines Accounts aufgetreten. Tritt dies weiterhin auf, wende dich an den Administrator der Instanz.", "delete_account_instructions": "Tippe dein Passwort unten in das Feld ein, um die Löschung deines Accounts zu bestätigen.", - "discoverable": "Erlaubnis für automatisches Suchen nach diesem Account", + "discoverable": "Erlaube, dass dieser Account in Suchergebnissen auftaucht", "avatar_size_instruction": "Die empfohlene minimale Größe für Avatare ist 150x150 Pixel.", "pad_emoji": "Emojis mit Leerzeichen umrahmen", "export_theme": "Farbschema speichern", "filtering": "Filtern", - "filtering_explanation": "Alle Beiträge die diese Wörter enthalten werden ausgeblendet. Ein Wort pro Zeile.", + "filtering_explanation": "Alle Beiträge, welche diese Wörter enthalten, werden ausgeblendet. Ein Wort pro Zeile.", "follow_export": "Follower exportieren", "follow_export_button": "Exportiere deine Follows in eine csv-Datei", "follow_export_processing": "In Bearbeitung. Die Liste steht gleich zum herunterladen bereit.", - "follow_import": "Followers importieren", - "follow_import_error": "Fehler beim importieren der Follower", - "follows_imported": "Followers importiert! Die Bearbeitung kann eine Zeit lang dauern.", + "follow_import": "Follower importieren", + "follow_import_error": "Fehler beim Importieren der Follower", + "follows_imported": "Follower importiert! Die Bearbeitung kann einen Moment dauern.", "foreground": "Vordergrund", "general": "Allgemein", "hide_attachments_in_convo": "Anhänge in Unterhaltungen ausblenden", @@ -142,7 +181,7 @@ "hide_post_stats": "Beitragsstatistiken verbergen (z.B. die Anzahl der Favoriten)", "hide_user_stats": "Benutzerstatistiken verbergen (z.B. die Anzahl der Follower)", "hide_filtered_statuses": "Gefilterte Beiträge verbergen", - "import_followers_from_a_csv_file": "Importiere Follower, denen du folgen möchtest, aus einer CSV-Datei", + "import_followers_from_a_csv_file": "Importiere Follower aus einer CSV-Datei", "import_theme": "Farbschema laden", "inputRadius": "Eingabefelder", "checkboxRadius": "Auswahlfelder", @@ -156,7 +195,7 @@ "lock_account_description": "Sperre deinen Account, um neue Follower zu genehmigen oder abzulehnen", "loop_video": "Videos wiederholen", "loop_video_silent_only": "Nur Videos ohne Ton wiederholen (z.B. Mastodons \"gifs\")", - "mutes_tab": "Mutes", + "mutes_tab": "Stummschaltungen", "play_videos_in_modal": "Videos in größerem Medienfenster abspielen", "use_contain_fit": "Vorschaubilder nicht zuschneiden", "name": "Name", @@ -221,7 +260,7 @@ }, "notifications": "Benachrichtigungen", "enable_web_push_notifications": "Web-Pushbenachrichtigungen aktivieren", - "style": { + "style": { "switcher": { "keep_color": "Farben beibehalten", "keep_shadows": "Schatten beibehalten", @@ -329,7 +368,43 @@ "checkbox": "Ich habe die Allgemeinen Geschäftsbedingungen überflogen", "link": "ein netter kleiner Link" } - } + }, + "app_name": "Anwendungsname", + "mfa": { + "otp": "OTP", + "recovery_codes_warning": "Schreibe dir die Codes auf oder speichere sie an einem sicheren Ort - ansonsten wirst du sie nicht wiederfinden. Wenn du den Zugriff zu deiner 2FA App und die Wiederherstellungs-Codes verlierst, wirst du aus deinem Account ausgeschlossen sein.", + "recovery_codes": "Wiederherstellungs-Codes.", + "warning_of_generate_new_codes": "Wenn du neue Wiederherstellungs-Codes generierst, werden die alten Codes nicht mehr funktionieren.", + "generate_new_recovery_codes": "Generiere neue Wiederherstellungs-Codes", + "title": "Zwei-Faktor Authentifizierung", + "waiting_a_recovery_codes": "Erhalte Wiederherstellungscodes...", + "authentication_methods": "Authentifizierungsmethoden", + "scan": { + "title": "Scan", + "secret_code": "Schlüssel", + "desc": "Wenn du deine 2FA App verwendest, scanne diesen QR Code oder gebe den Schlüssel ein:" + }, + "verify": { + "desc": "Um 2FA zu aktivieren, gib den Code von deiner 2FA-App ein:" + } + }, + "enter_current_password_to_confirm": "Gib dein aktuelles Passwort ein, um deine Identität zu bestätigen", + "security": "Sicherheit", + "allow_following_move": "Erlaube automatisches Folgen, sobald ein gefolgter Nutzer umzieht", + "blocks_imported": "Blocks importiert! Die Verarbeitung wird einen Moment brauchen.", + "block_import_error": "Fehler beim Importieren der Blocks", + "block_import": "Block Import", + "block_export_button": "Exportiere deine Blocks in eine csv Datei", + "block_export": "Block Export", + "emoji_reactions_on_timeline": "Zeige Emoji-Reaktionen auf der Zeitleiste", + "domain_mutes": "Domains", + "changed_email": "Email Adresse erfolgreich geändert!", + "change_email_error": "Es trat ein Problem auf beim Versuch, deine Email Adresse zu ändern.", + "change_email": "Ändere Email", + "notification_setting_non_followers": "Nutzer, die dir nicht folgen", + "notification_setting_followers": "Nutzer, die dir folgen", + "import_blocks_from_a_csv_file": "Importiere Blocks von einer CSV Datei", + "accent": "Akzent" }, "timeline": { "collapse": "Einklappen", @@ -352,7 +427,7 @@ "follow_again": "Anfrage erneut senden?", "follow_unfollow": "Folgen beenden", "followees": "Folgt", - "followers": "Followers", + "followers": "Folgende", "following": "Folgst du!", "follows_you": "Folgt dir!", "its_you": "Das bist du!", @@ -360,7 +435,10 @@ "muted": "Stummgeschaltet", "per_day": "pro Tag", "remote_follow": "Folgen", - "statuses": "Beiträge" + "statuses": "Beiträge", + "admin_menu": { + "sandbox": "Erzwinge Beiträge nur für Follower sichtbar zu sein" + } }, "user_profile": { "timeline_title": "Beiträge" @@ -376,11 +454,11 @@ "favorite": "Favorisieren", "user_settings": "Benutzereinstellungen" }, - "upload":{ + "upload": { "error": { - "base": "Hochladen fehlgeschlagen.", - "file_too_big": "Datei ist zu groß [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", - "default": "Bitte versuche es später erneut" + "base": "Hochladen fehlgeschlagen.", + "file_too_big": "Datei ist zu groß [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", + "default": "Bitte versuche es später erneut" }, "file_size_units": { "B": "B", @@ -409,5 +487,98 @@ "password_reset_disabled": "Passwortzurücksetzen deaktiviert. Bitte Administrator kontaktieren.", "password_reset_required": "Passwortzurücksetzen erforderlich", "password_reset_required_but_mailer_is_disabled": "Passwortzurücksetzen wäre erforderlich, ist aber deaktiviert. Bitte Administrator kontaktieren." + }, + "about": { + "mrf": { + "federation": "Föderation", + "mrf_policies": "Aktivierte MRF Richtlinien", + "simple": { + "simple_policies": "Instanzspezifische Richtlinien", + "accept": "Akzeptieren", + "reject": "Ablehnen", + "reject_desc": "Diese Instanz akzeptiert keine Nachrichten der folgenden Instanzen:", + "quarantine": "Quarantäne", + "ftl_removal": "Von der Zeitleiste \"Das gesamte bekannte Netzwerk\" entfernen", + "media_removal": "Medienentfernung", + "media_removal_desc": "Diese Instanz entfernt Medien von den Beiträgen der folgenden Instanzen:", + "media_nsfw": "Erzwingen Medien als heikel zu makieren", + "media_nsfw_desc": "Diese Instanz makiert die Medien in Beiträgen der folgenden Instanzen als heikel:", + "accept_desc": "Diese Instanz akzeptiert nur Nachrichten von den folgenden Instanzen:", + "quarantine_desc": "Diese Instanz sendet nur öffentliche Beiträge zu den folgenden Instanzen:", + "ftl_removal_desc": "Dieser Instanz entfernt folgende Instanzen von der \"Das gesamte bekannte Netzwerk\" Zeitleiste:" + }, + "keyword": { + "keyword_policies": "Keyword Richtlinien", + "reject": "Ablehnen", + "replace": "Ersetzen", + "is_replaced_by": "→", + "ftl_removal": "Von der Zeitleiste \"Das gesamte bekannte Netzwerk\" entfernen" + }, + "mrf_policies_desc": "MRF Richtlinien manipulieren das Föderationsverhalten dieser Instanz. Die folgenden Richtlinien sind aktiv:" + }, + "staff": "Mitarbeiter" + }, + "domain_mute_card": { + "mute": "Stummschalten", + "mute_progress": "Wird stummgeschaltet..", + "unmute": "Stummschaltung aufheben", + "unmute_progress": "Stummschaltung wird aufgehoben.." + }, + "exporter": { + "export": "Exportieren", + "processing": "Verarbeitung läuft, bald wird Du dazu aufgefordert, deine Datei herunterzuladen" + }, + "image_cropper": { + "crop_picture": "Bild zuschneiden", + "save": "Speichern", + "cancel": "Abbrechen", + "save_without_cropping": "Ohne Zuschneiden speichern" + }, + "importer": { + "submit": "Absenden", + "success": "Erfolgreich importiert.", + "error": "Ein Fehler ist beim Verabeiten der Datei aufgetreten." + }, + "media_modal": { + "previous": "Zurück", + "next": "Weiter" + }, + "polls": { + "add_poll": "Umfrage hinzufügen", + "add_option": "Option hinzufügen", + "option": "Option", + "votes": "Stimmen", + "vote": "Abstimmen", + "type": "Umfragetyp", + "multiple_choices": "Mehrere Auswahlmöglichkeiten", + "single_choice": "Eine Auswahlmöglichkeit", + "expiry": "Alter der Umfrage", + "expired": "Die Umfrage endete vor {0}", + "not_enough_options": "Zu wenig einzigartige Auswahlmöglichkeiten in der Umfrage", + "expires_in": "Die Umfrage endet in {0}" + }, + "emoji": { + "stickers": "Sticker", + "emoji": "Emoji", + "search_emoji": "Nach einem Emoji suchen", + "custom": "Benutzerdefinierter Emoji", + "keep_open": "Auswahlfenster offen halten", + "add_emoji": "Emoji einfügen", + "load_all": "Lade alle {emojiAmount} Emoji", + "load_all_hint": "Erfolgreich erste {saneAmount} Emoji geladen, alle Emojis zu laden würde Leistungsprobleme hervorrufen.", + "unicode": "Unicode Emoji" + }, + "interactions": { + "load_older": "Lade ältere Interaktionen", + "follows": "Neue Follows", + "favs_repeats": "Wiederholungen und Favoriten", + "moves": "Benutzer migriert zu" + }, + "selectable_list": { + "select_all": "Wähle alle" + }, + "remote_user_resolver": { + "searching_for": "Suche nach", + "error": "Nicht gefunden." } } diff --git a/src/i18n/en.json b/src/i18n/en.json index 483432ff..eefe10e5 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1,7 +1,43 @@ { + "about": { + "mrf": { + "federation": "Federation", + "keyword": { + "keyword_policies": "Keyword Policies", + "ftl_removal": "Removal from \"The Whole Known Network\" Timeline", + "reject": "Reject", + "replace": "Replace", + "is_replaced_by": "→" + }, + "mrf_policies": "Enabled MRF Policies", + "mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance. The following policies are enabled:", + "simple": { + "simple_policies": "Instance-specific Policies", + "accept": "Accept", + "accept_desc": "This instance only accepts messages from the following instances:", + "reject": "Reject", + "reject_desc": "This instance will not accept messages from the following instances:", + "quarantine": "Quarantine", + "quarantine_desc": "This instance will send only public posts to the following instances:", + "ftl_removal": "Removal from \"The Whole Known Network\" Timeline", + "ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:", + "media_removal": "Media Removal", + "media_removal_desc": "This instance removes media from posts on the following instances:", + "media_nsfw": "Media Force-set As Sensitive", + "media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:" + } + }, + "staff": "Staff" + }, "chat": { "title": "Chat" }, + "domain_mute_card": { + "mute": "Mute", + "mute_progress": "Muting…", + "unmute": "Unmute", + "unmute_progress": "Unmuting…" + }, "exporter": { "export": "Export", "processing": "Processing, you'll soon be asked to download your file" @@ -23,15 +59,21 @@ "apply": "Apply", "submit": "Submit", "more": "More", + "loading": "Loading…", "generic_error": "An error occured", + "error_retry": "Please try again", + "retry": "Try again", "optional": "optional", "show_more": "Show more", "show_less": "Show less", + "dismiss": "Dismiss", "cancel": "Cancel", "disable": "Disable", "enable": "Enable", "confirm": "Confirm", - "verify": "Verify" + "verify": "Verify", + "close": "Close", + "peek": "Peek" }, "image_cropper": { "crop_picture": "Crop picture", @@ -57,9 +99,9 @@ "enter_recovery_code": "Enter a recovery code", "enter_two_factor_code": "Enter a two-factor code", "recovery_code": "Recovery code", - "heading" : { - "totp" : "Two-factor authentication", - "recovery" : "Two-factor recovery" + "heading": { + "totp": "Two-factor authentication", + "recovery": "Two-factor recovery" } }, "media_modal": { @@ -84,14 +126,17 @@ "preferences": "Preferences" }, "notifications": { - "broken_favorite": "Unknown status, searching for it...", + "broken_favorite": "Unknown status, searching for it…", "favorited_you": "favorited your status", "followed_you": "followed you", + "follow_request": "wants to follow you", "load_older": "Load older notifications", "notifications": "Notifications", "read": "Read!", "repeated_you": "repeated your status", - "no_more_notifications": "No more notifications" + "no_more_notifications": "No more notifications", + "migrated_to": "migrated to", + "reacted_with": "reacted with {0}" }, "polls": { "add_poll": "Add Poll", @@ -121,6 +166,7 @@ "interactions": { "favs_repeats": "Repeats and Favorites", "follows": "New follows", + "moves": "User migrates", "load_older": "Load older interactions" }, "post_status": { @@ -185,17 +231,17 @@ "security": "Security", "enter_current_password_to_confirm": "Enter your current password to confirm your identity", "mfa": { - "otp" : "OTP", - "setup_otp" : "Setup OTP", - "wait_pre_setup_otp" : "presetting OTP", - "confirm_and_enable" : "Confirm & enable OTP", + "otp": "OTP", + "setup_otp": "Setup OTP", + "wait_pre_setup_otp": "presetting OTP", + "confirm_and_enable": "Confirm & enable OTP", "title": "Two-factor Authentication", - "generate_new_recovery_codes" : "Generate new recovery codes", - "warning_of_generate_new_codes" : "When you generate new recovery codes, your old codes won’t work anymore.", - "recovery_codes" : "Recovery codes.", - "waiting_a_recovery_codes": "Receiving backup codes...", - "recovery_codes_warning" : "Write the codes down or save them somewhere secure - otherwise you won't see them again. If you lose access to your 2FA app and recovery codes you'll be locked out of your account.", - "authentication_methods" : "Authentication methods", + "generate_new_recovery_codes": "Generate new recovery codes", + "warning_of_generate_new_codes": "When you generate new recovery codes, your old codes won’t work anymore.", + "recovery_codes": "Recovery codes.", + "waiting_a_recovery_codes": "Receiving backup codes…", + "recovery_codes_warning": "Write the codes down or save them somewhere secure - otherwise you won't see them again. If you lose access to your 2FA app and recovery codes you'll be locked out of your account.", + "authentication_methods": "Authentication methods", "scan": { "title": "Scan", "desc": "Using your two-factor app, scan this QR code or enter text key:", @@ -205,6 +251,7 @@ "desc": "To enable two-factor authentication, enter the code from your two-factor app:" } }, + "allow_following_move": "Allow auto-follow when following account moves", "attachmentRadius": "Attachments", "attachments": "Attachments", "autoload": "Enable automatic loading when scrolled to the bottom", @@ -236,15 +283,18 @@ "current_avatar": "Your current avatar", "current_password": "Current password", "current_profile_banner": "Your current profile banner", + "mutes_and_blocks": "Mutes and Blocks", "data_import_export_tab": "Data Import / Export", "default_vis": "Default visibility scope", "delete_account": "Delete Account", - "delete_account_description": "Permanently delete your account and all your messages.", + "delete_account_description": "Permanently delete your data and deactivate your account.", "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.", "delete_account_instructions": "Type your password in the input below to confirm account deletion.", "discoverable": "Allow discovery of this account in search results and other services", + "domain_mutes": "Domains", "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.", "pad_emoji": "Pad emoji with spaces when adding from picker", + "emoji_reactions_on_timeline": "Show emoji reactions on timeline", "export_theme": "Save preset", "filtering": "Filtering", "filtering_explanation": "All statuses containing these words will be muted, one per line", @@ -253,6 +303,7 @@ "follow_import": "Follow import", "follow_import_error": "Error importing followers", "follows_imported": "Follows imported! Processing them will take a while.", + "accent": "Accent", "foreground": "Foreground", "general": "General", "hide_attachments_in_convo": "Hide attachments in conversations", @@ -292,6 +343,8 @@ "notification_visibility_likes": "Likes", "notification_visibility_mentions": "Mentions", "notification_visibility_repeats": "Repeats", + "notification_visibility_moves": "User Migrates", + "notification_visibility_emoji_reactions": "Reactions", "no_rich_text_description": "Strip rich text formatting from all posts", "no_blocks": "No blocks", "no_mutes": "No mutes", @@ -339,24 +392,33 @@ "post_status_content_type": "Post status content type", "stop_gifs": "Play-on-hover GIFs", "streaming": "Enable automatic streaming of new posts when scrolled to the top", + "user_mutes": "Users", + "useStreamingApi": "Receive posts and notifications real-time", + "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)", "text": "Text", "theme": "Theme", "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.", "tooltipRadius": "Tooltips/alerts", + "type_domains_to_mute": "Search domains to mute", "upload_a_photo": "Upload a photo", "user_settings": "User Settings", "values": { "false": "no", "true": "yes" }, + "fun": "Fun", + "greentext": "Meme arrows", "notifications": "Notifications", + "notification_setting_filters": "Filters", "notification_setting": "Receive notifications from:", "notification_setting_follows": "Users you follow", "notification_setting_non_follows": "Users you do not follow", "notification_setting_followers": "Users who follow you", "notification_setting_non_followers": "Users who do not follow you", + "notification_setting_privacy": "Privacy", + "notification_setting_privacy_option": "Hide the sender and contents of push notifications", "notification_mutes": "To stop receiving notifications from a specific user, use a mute.", "notification_blocks": "Blocking a user stops all notifications as well as unsubscribes them.", "enable_web_push_notifications": "Enable web push notifications", @@ -370,7 +432,24 @@ "save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.", "reset": "Reset", "clear_all": "Clear all", - "clear_opacity": "Clear opacity" + "clear_opacity": "Clear opacity", + "load_theme": "Load theme", + "keep_as_is": "Keep as is", + "use_snapshot": "Old version", + "use_source": "New version", + "help": { + "upgraded_from_v2": "PleromaFE has been upgraded, theme could look a little bit different than you remember.", + "v2_imported": "File you imported was made for older FE. We try to maximize compatibility but there still could be inconsistencies.", + "future_version_imported": "File you imported was made in newer version of FE.", + "older_version_imported": "File you imported was made in older version of FE.", + "snapshot_present": "Theme snapshot is loaded, so all values are overriden. You can load theme's actual data instead.", + "snapshot_missing": "No theme snapshot was in the file so it could look different than originally envisioned.", + "fe_upgraded": "PleromaFE's theme engine upgraded after version update.", + "fe_downgraded": "PleromaFE's version rolled back.", + "migration_snapshot_ok": "Just to be safe, theme snapshot loaded. You can try loading theme data.", + "migration_napshot_gone": "For whatever reason snapshot was missing, some stuff could look different than you remember.", + "snapshot_source_mismatch": "Versions conflict: most likely FE was rolled back and updated again, if you changed theme using older version of FE you most likely want to use old version, otherwise use new version." + } }, "common": { "color": "Color", @@ -399,14 +478,27 @@ "alert": "Alert background", "alert_error": "Error", "alert_warning": "Warning", + "alert_neutral": "Neutral", + "post": "Posts/User bios", "badge": "Badge background", + "popover": "Tooltips, menus, popovers", "badge_notification": "Notification", "panel_header": "Panel header", "top_bar": "Top bar", "borders": "Borders", "buttons": "Buttons", "inputs": "Input fields", - "faint_text": "Faded text" + "faint_text": "Faded text", + "underlay": "Underlay", + "poll": "Poll graph", + "icons": "Icons", + "highlight": "Highlighted elements", + "pressed": "Pressed", + "selectedPost": "Selected post", + "selectedMenu": "Selected menu item", + "disabled": "Disabled", + "toggled": "Toggled", + "tabs": "Tabs" }, "radii": { "_tab_label": "Roundness" @@ -419,7 +511,7 @@ "blur": "Blur", "spread": "Spread", "inset": "Inset", - "hint": "For shadows you can also use --variable as a color value to use CSS3 variables. Please note that setting opacity won't work in this case.", + "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.", @@ -533,7 +625,11 @@ "reply_to": "Reply to", "replies_list": "Replies:", "mute_conversation": "Mute conversation", - "unmute_conversation": "Unmute conversation" + "unmute_conversation": "Unmute conversation", + "status_unavailable": "Status unavailable", + "copy_link": "Copy link to status", + "thread_muted": "Thread muted", + "thread_muted_and_words": ", has words:" }, "user_card": { "approve": "Approve", @@ -550,6 +646,7 @@ "followers": "Followers", "following": "Following!", "follows_you": "Follows you!", + "hidden": "Hidden", "its_you": "It's you!", "media": "Media", "mention": "Mention", @@ -562,11 +659,11 @@ "subscribe": "Subscribe", "unsubscribe": "Unsubscribe", "unblock": "Unblock", - "unblock_progress": "Unblocking...", - "block_progress": "Blocking...", + "unblock_progress": "Unblocking…", + "block_progress": "Blocking…", "unmute": "Unmute", - "unmute_progress": "Unmuting...", - "mute_progress": "Muting...", + "unmute_progress": "Unmuting…", + "mute_progress": "Muting…", "hide_repeats": "Hide repeats", "show_repeats": "Show repeats", "admin_menu": { @@ -612,9 +709,12 @@ "repeat": "Repeat", "reply": "Reply", "favorite": "Favorite", - "user_settings": "User Settings" + "add_reaction": "Add Reaction", + "user_settings": "User Settings", + "accept_follow_request": "Accept follow request", + "reject_follow_request": "Reject follow request" }, - "upload":{ + "upload": { "error": { "base": "Upload failed.", "file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", diff --git a/src/i18n/es.json b/src/i18n/es.json index 163eb707..931d4c64 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -57,12 +57,12 @@ "enter_recovery_code": "Inserta el código de recuperación", "enter_two_factor_code": "Inserta el código de dos factores", "recovery_code": "Código de recuperación", - "heading" : { - "totp" : "Autenticación de dos factores", - "recovery" : "Recuperación de dos factores" + "heading": { + "totp": "Autenticación de dos factores", + "recovery": "Recuperación de dos factores" } }, - "media_modal": { + "media_modal": { "previous": "Anterior", "next": "Siguiente" }, @@ -103,7 +103,7 @@ "single_choice": "Elección única", "multiple_choices": "Elección múltiple", "expiry": "Tiempo de vida de la encuesta", - "expires_in": "La encuensta termina en {0}", + "expires_in": "La encuesta termina en {0}", "expired": "La encuesta terminó hace {0}", "not_enough_options": "Muy pocas opciones únicas en la encuesta" }, @@ -137,7 +137,7 @@ }, "content_warning": "Tema (opcional)", "default": "Acabo de aterrizar en L.A.", - "direct_warning_to_all": "Esta publicación será visible para todos los usarios mencionados.", + "direct_warning_to_all": "Esta publicación será visible para todos los usuarios mencionados.", "direct_warning_to_first_only": "Esta publicación solo será visible para los usuarios mencionados al comienzo del mensaje.", "posting": "Publicando", "scope_notice": { @@ -146,7 +146,7 @@ "unlisted": "Esta publicación no será visible en la Línea Temporal Pública ni en Toda La Red Conocida" }, "scope": { - "direct": "Directo - Solo para los usuarios mencionados.", + "direct": "Directo - Solo para los usuarios mencionados", "private": "Solo-seguidores - Solo tus seguidores leerán la publicación", "public": "Público - Entradas visibles en las Líneas Temporales Públicas", "unlisted": "Sin listar - Entradas no visibles en las Líneas Temporales Públicas" @@ -173,7 +173,7 @@ "password_confirmation_match": "la contraseña no coincide" } }, - "selectable_list": { + "selectable_list": { "select_all": "Seleccionar todo" }, "settings": { @@ -181,17 +181,17 @@ "security": "Seguridad", "enter_current_password_to_confirm": "Introduce la contraseña actual para confirmar tu identidad", "mfa": { - "otp" : "OTP", - "setup_otp" : "Configurar OTP", - "wait_pre_setup_otp" : "preconfiguración OTP", - "confirm_and_enable" : "Confirmar y habilitar OTP", + "otp": "OTP", + "setup_otp": "Configurar OTP", + "wait_pre_setup_otp": "preconfiguración OTP", + "confirm_and_enable": "Confirmar y habilitar OTP", "title": "Autentificación de dos factores", - "generate_new_recovery_codes" : "Generar códigos de recuperación nuevos", - "warning_of_generate_new_codes" : "Cuando generas nuevos códigos de recuperación, los antiguos dejarán de funcionar.", - "recovery_codes" : "Códigos de recuperación.", + "generate_new_recovery_codes": "Generar códigos de recuperación nuevos", + "warning_of_generate_new_codes": "Cuando generas nuevos códigos de recuperación, los antiguos dejarán de funcionar.", + "recovery_codes": "Códigos de recuperación.", "waiting_a_recovery_codes": "Recibiendo códigos de respaldo", - "recovery_codes_warning" : "Anote los códigos o guárdelos en un lugar seguro, de lo contrario no los volverá a ver. Si pierde el acceso a su aplicación 2FA y los códigos de recuperación, su cuenta quedará bloqueada.", - "authentication_methods" : "Métodos de autentificación", + "recovery_codes_warning": "Anote los códigos o guárdelos en un lugar seguro, de lo contrario no los volverá a ver. Si pierde el acceso a su aplicación 2FA y los códigos de recuperación, su cuenta quedará bloqueada.", + "authentication_methods": "Métodos de autentificación", "scan": { "title": "Escanear", "desc": "Usando su aplicación de dos factores, escanee este código QR o ingrese la clave de texto:", @@ -210,7 +210,7 @@ "background": "Fondo", "bio": "Biografía", "block_export": "Exportar usuarios bloqueados", - "block_export_button": "Exporta la lista de tus usarios bloqueados a un archivo csv", + "block_export_button": "Exporta la lista de tus usuarios bloqueados a un archivo csv", "block_import": "Importar usuarios bloqueados", "block_import_error": "Error importando la lista de usuarios bloqueados", "blocks_imported": "¡Lista de usuarios bloqueados importada! El procesado puede tardar un poco.", @@ -222,7 +222,7 @@ "cRed": "Rojo (Cancelar)", "change_password": "Cambiar contraseña", "change_password_error": "Hubo un problema cambiando la contraseña.", - "changed_password": "Contraseña cambiada correctamente!", + "changed_password": "¡Contraseña cambiada correctamente!", "collapse_subject": "Colapsar entradas con tema", "composing": "Redactando", "confirm_new_password": "Confirmar la nueva contraseña", @@ -286,7 +286,7 @@ "notification_visibility_repeats": "Repeticiones (Repeats)", "no_rich_text_description": "Eliminar el formato de texto enriquecido de todas las entradas", "no_blocks": "No hay usuarios bloqueados", - "no_mutes": "No hay usuarios sinlenciados", + "no_mutes": "No hay usuarios silenciados", "hide_follows_description": "No mostrar a quién sigo", "hide_followers_description": "No mostrar quién me sigue", "hide_follows_count_description": "No mostrar el número de cuentas que sigo", @@ -305,7 +305,7 @@ "profile_background": "Fondo del Perfil", "profile_banner": "Cabecera del Perfil", "profile_tab": "Perfil", - "radii_help": "Estable el redondeo de las esquinas de la interfaz (en píxeles)", + "radii_help": "Establezca el redondeo de las esquinas de la interfaz (en píxeles)", "replies_in_timeline": "Réplicas en la línea temporal", "reply_link_preview": "Activar la previsualización del enlace de responder al pasar el ratón por encima", "reply_visibility_all": "Mostrar todas las réplicas", @@ -337,7 +337,7 @@ "theme_help_v2_1": "También puede invalidar los colores y la opacidad de ciertos componentes si activa la casilla de verificación. Use el botón \"Borrar todo\" para deshacer los cambios.", "theme_help_v2_2": "Los iconos debajo de algunas entradas son indicadores de contraste de fondo/texto, desplace el ratón por encima para obtener información más detallada. Tenga en cuenta que cuando se utilizan indicadores de contraste de transparencia se muestra el peor caso posible.", "tooltipRadius": "Información/alertas", - "upload_a_photo": "Subir una foto", + "upload_a_photo": "Subir una foto", "user_settings": "Ajustes del Usuario", "values": { "false": "no", @@ -583,7 +583,7 @@ "profile_does_not_exist": "Lo sentimos, este perfil no existe.", "profile_loading_error": "Lo sentimos, hubo un error al cargar este perfil." }, - "user_reporting": { + "user_reporting": { "title": "Reportando a {0}", "add_comment_description": "El informe será enviado a los moderadores de su instancia. Puedes proporcionar una explicación de por qué estás reportando esta cuenta a continuación:", "additional_comments": "Comentarios adicionales", @@ -603,7 +603,7 @@ "favorite": "Favorito", "user_settings": "Ajustes de usuario" }, - "upload":{ + "upload": { "error": { "base": "Subida fallida.", "file_too_big": "Archivo demasiado grande [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", @@ -635,4 +635,4 @@ "too_many_requests": "Has alcanzado el límite de intentos, vuelve a intentarlo más tarde.", "password_reset_disabled": "El restablecimiento de contraseñas está deshabilitado. Póngase en contacto con el administrador de su instancia." } -} \ No newline at end of file +} diff --git a/src/i18n/et.json b/src/i18n/et.json index 5262b2a4..b5ae4275 100644 --- a/src/i18n/et.json +++ b/src/i18n/et.json @@ -4,7 +4,19 @@ "find_user": "Otsi kasutajaid" }, "general": { - "submit": "Postita" + "submit": "Postita", + "verify": "Kinnita", + "confirm": "Kinnita", + "enable": "Luba", + "disable": "Keela", + "cancel": "Tühista", + "dismiss": "Olgu", + "show_less": "Kuva vähem", + "show_more": "Kuva rohkem", + "optional": "valikuline", + "generic_error": "Esines viga", + "more": "Rohkem", + "apply": "Rakenda" }, "login": { "login": "Logi sisse", @@ -12,29 +24,95 @@ "password": "Parool", "placeholder": "nt lain", "register": "Registreeru", - "username": "Kasutajanimi" + "username": "Kasutajanimi", + "heading": { + "recovery": "Kaheastmelise autentimise taaste", + "totp": "Kaheastmeline autentimine" + }, + "recovery_code": "Taastekood", + "enter_two_factor_code": "Sisesta kaheastmelise autentimise kood", + "enter_recovery_code": "Sisesta taastekood", + "authentication_code": "Autentimiskood", + "hint": "Logi sisse, et liituda vestlusega", + "description": "Logi sisse OAuthiga" }, "nav": { "mentions": "Mainimised", "public_tl": "Avalik Ajajoon", "timeline": "Ajajoon", - "twkn": "Kogu Teadaolev Võrgustik" + "twkn": "Kogu Teadaolev Võrgustik", + "preferences": "Eelistused", + "who_to_follow": "Keda jälgida", + "search": "Otsing", + "user_search": "Kasutajaotsing", + "dms": "Privaatsõnumid", + "interactions": "Interaktsioonid", + "friend_requests": "Jägimistaotlused", + "chat": "Kohalik vestlus", + "back": "Tagasi", + "administration": "Administreerimine", + "about": "Meist" }, "notifications": { "followed_you": "alustas sinu jälgimist", - "notifications": "Teavitused", - "read": "Loe!" + "notifications": "Teated", + "read": "Loe!", + "reacted_with": "reageeris {0}", + "migrated_to": "kolis", + "no_more_notifications": "Rohkem teateid ei ole", + "repeated_you": "taaspostitas su staatuse", + "load_older": "Laadi vanemad teated", + "follow_request": "soovib Teid jälgida", + "favorited_you": "lisas su staatuse lemmikuks", + "broken_favorite": "Tundmatu staatus, otsin…" }, "post_status": { "default": "Just sõitsin elektrirongiga Tallinnast Pääskülla.", - "posting": "Postitan" + "posting": "Postitan", + "scope": { + "unlisted": "Peidetud - Ära postita avalikele ajajoontele", + "public": "Avalil - Postita avalikele ajajoontele", + "private": "Jälgijatele - Postita ainult jälgijatele", + "direct": "Privaatne - Postita ainult mainitud kasutajatele" + }, + "scope_notice": { + "unlisted": "See postitus ei ole nähtav avalikul ega kogu võrgu ajajoonel", + "private": "See postitus on nähtav ainult Teie jälgijatele", + "public": "See postitus on nähtav kõigile" + }, + "direct_warning_to_first_only": "See postitus on nähtav ainult kirja alguses mainitud kasutajatele.", + "direct_warning_to_all": "See postitus on nähtav kõikidele mainitud kasutajatele.", + "content_warning": "Pealkiri (valikuline)", + "content_type": { + "text/bbcode": "BBCode", + "text/markdown": "Markdown", + "text/html": "HTML", + "text/plain": "Lihttekst" + }, + "attachments_sensitive": "Märgi manused sensitiivseks", + "account_not_locked_warning_link": "lukus", + "account_not_locked_warning": "Teie konto ei ole {0}. Kõik võivad Teid jälgida, et näha Teie ainult-jälgijatele postitusi.", + "new_status": "Postita uus staatus" }, "registration": { "bio": "Bio", "email": "E-post", "fullname": "Kuvatav nimi", "password_confirm": "Parooli kinnitamine", - "registration": "Registreerimine" + "registration": "Registreerimine", + "validations": { + "password_confirmation_match": "peaks olema sama kui salasõna", + "password_confirmation_required": "ei saa jätta tühjaks", + "password_required": "ei saa jätta tühjaks", + "email_required": "ei saa jätta tühjaks", + "fullname_required": "ei saa jätta tühjaks", + "username_required": "ei saa jätta tühjaks" + }, + "fullname_placeholder": "Näiteks Lain Iwakura", + "username_placeholder": "Näiteks lain", + "new_captcha": "Vajuta pildile, et saada uus captcha", + "captcha": "CAPTCHA", + "token": "Kutse võti" }, "settings": { "attachments": "Manused", @@ -44,7 +122,7 @@ "current_avatar": "Sinu praegune profiilipilt", "current_profile_banner": "Praegune profiilibänner", "filtering": "Sisu filtreerimine", - "filtering_explanation": "Kõiki staatuseid, mis sisaldavad neid sõnu, ei kuvata. Üks sõna reale.", + "filtering_explanation": "Kõiki staatuseid, mis sisaldavad neid sõnu, ei kuvata. Üks sõna reale", "hide_attachments_in_convo": "Peida manused vastlustes", "hide_attachments_in_tl": "Peida manused ajajoonel", "name": "Nimi", @@ -58,7 +136,201 @@ "set_new_profile_banner": "Vali uus profiilibänner", "settings": "Sätted", "theme": "Teema", - "user_settings": "Kasutaja sätted" + "user_settings": "Kasutaja sätted", + "subject_line_noop": "Ära kopeeri", + "subject_line_mastodon": "Nagu mastodon: kopeeri nagu on", + "subject_line_email": "Nagu e-post: \"vs: pealkiri\"", + "subject_line_behavior": "Kopeeri pealkiri vastamisel", + "subject_input_always_show": "Alati kuva pealkirja välja", + "minimal_scopes_mode": "Peida postituse nähtavussätted", + "scope_copy": "Kopeeri nähtavussätted vastamisel (Privaatsed on alati kopeeritud)", + "security_tab": "Turvalisus", + "search_user_to_mute": "Otsi, keda soovid vaigistada", + "search_user_to_block": "Otsi, keda soovid blokeerida", + "saving_ok": "Sätted salvestatud", + "saving_err": "Sätete salvestamine ebaõnnestus", + "autohide_floating_post_button": "Automaatselt peida uue postituse nupp (mobiilil)", + "reply_visibility_self": "Näita ainult vastuseid, mis on suunatud mulle", + "reply_visibility_following": "Näita ainult vastuseid, mis on suunatud mulle või kasutajatele, keda jälgin", + "reply_visibility_all": "Näita kõiki vastuseid", + "replies_in_timeline": "Vastused ajajoonel", + "radii_help": "Liidese ümardamine (pikslites)", + "profile_tab": "Profiil", + "presets": "Salvestatud sätted", + "pause_on_unfocused": "Peata reaalajas voog kui leht pole fookuses", + "panelRadius": "Paneelid", + "revoke_token": "Keela", + "valid_until": "Kehtiv kuni", + "refresh_token": "Värskendustoken", + "token": "Token", + "oauth_tokens": "OAuth tokenid", + "show_moderator_badge": "Näita Moderaator silti mu profiilil", + "show_admin_badge": "Näita Admin silti mu profiilil", + "hide_followers_count_description": "Ära näita minu jälgijate arvu", + "hide_follows_count_description": "Ära näita minu jälgimiste arvu", + "hide_followers_description": "Ära näita minu jälgijaid", + "hide_follows_description": "Ära näita minu jälgimisi", + "no_mutes": "Vaigistusi pole", + "no_blocks": "Blokeeringuid pole", + "no_rich_text_description": "Muuda kõik postitused lihttekstiks", + "notification_visibility_emoji_reactions": "Reaktsioonid", + "notification_visibility_moves": "Kasutaja kolimised", + "notification_visibility_repeats": "Taaspostitused", + "notification_visibility_mentions": "Mainimised", + "notification_visibility_likes": "Lemmikud", + "notification_visibility_follows": "Jälgimised", + "notification_visibility": "Milliseid teateid kuvatakse", + "new_password": "Uus salasõna", + "new_email": "Uus e-post", + "use_contain_fit": "Näita eelvaadetes täis suuruses pilte", + "play_videos_in_modal": "Näita videoid eraldi raamis", + "mutes_tab": "Vaigistused", + "loop_video_silent_only": "Loop videod, millel pole heli (nt. Mastodoni \"gifid\")", + "loop_video": "Loop videod", + "lock_account_description": "Piira oma konto ainult lubatud jälgijatele", + "links": "Lingid", + "limited_availability": "Pole Teie veebilehitsejas saadaval", + "invalid_theme_imported": "Valitud fail ei ole Pleroma kujundus. Kujundusele muudatusi ei tehtud.", + "interfaceLanguage": "Liidese keel", + "interface": "Liides", + "instance_default_simple": "(vaikimisi)", + "instance_default": "(vaikimisi: {value})", + "checkboxRadius": "Märkeruudud", + "inputRadius": "Sisestuskastid", + "import_theme": "Lae sätted", + "import_followers_from_a_csv_file": "Impordi jälgimised csv failist", + "import_blocks_from_a_csv_file": "Impordi blokeeringud csv failist", + "hide_filtered_statuses": "Peida filtreeritud staatused", + "hide_user_stats": "Peida kasutaja statistika (nt. jälgijate arv)", + "hide_post_stats": "Peida postituse statistika (nt. lemmikute arv)", + "use_one_click_nsfw": "Ava NSFW manused ühe klikiga", + "preload_images": "Piltide eellaadimine", + "hide_isp": "Peida instantsipõhine paneel", + "max_thumbnails": "Maksimaalne lubatud eelvaadete arv postituste kohta", + "hide_muted_posts": "Peida vaigistatud kasutajate postitused", + "general": "Üldine", + "foreground": "Esiplaan", + "accent": "Rõhk", + "follows_imported": "Jälgimised imporditud! Nende töötlemine võtab natuke aega.", + "follow_import_error": "Jälgimiste importimisel tekkis viga", + "follow_import": "Impordi jälgimised", + "follow_export_button": "Ekspordi oma jälgimised csv failiks", + "follow_export": "Ekspordi jälgimised", + "export_theme": "Salvesta sätted", + "emoji_reactions_on_timeline": "Näita reaktsioone ajajoonel", + "pad_emoji": "Lisa emotikonidele tühikud ette ja järgi neid menüüst valides", + "avatar_size_instruction": "Profiilipildi soovitatud minimaalne suurus on 150x150 pikslit.", + "domain_mutes": "Domeenid", + "discoverable": "Luba selle konto ilmumine otsingutulemustes ning muudes teenustes", + "delete_account_instructions": "Konto kustutamise kinnitamiseks sisestage oma salasõna.", + "delete_account_error": "Teie konto kustutamisel tekkis viga. Kui see jätkub, palun võtke kontakti administraatoriga.", + "delete_account_description": "Jäädavalt kustuta oma andmed ja konto.", + "delete_account": "Kustuta konto", + "default_vis": "Vaikimisi nähtavus", + "data_import_export_tab": "Andmete import / eksport", + "current_password": "Praegune salasõna", + "confirm_new_password": "Kinnita uus salasõna", + "composing": "Koostamine", + "collapse_subject": "Peida postituste pealkirjad", + "changed_password": "Salasõna edukalt muudetud!", + "change_password_error": "Esines viga salasõna muutmisel.", + "change_password": "Muuda salasõna", + "changed_email": "E-post edukalt muudetud!", + "change_email_error": "Esines viga e-posti muutmisel.", + "change_email": "Muuda e-posti", + "cRed": "Punane (Tühista)", + "cOrange": "Oranž (Lisa lemmikuks)", + "cGreen": "Roheline (Taaspostita)", + "cBlue": "Sinine (Vasta, jälgi)", + "btnRadius": "Nupud", + "blocks_tab": "Blokeeringud", + "blocks_imported": "Blokeeringud imporditud! Nende töötlemine võtab natuke aega.", + "block_import_error": "Blokeeringute importimisel esines viga", + "block_import": "Blokeeringute import", + "block_export_button": "Ekspordi oma blokeeringud csv failiks", + "block_export": "Blokeeringute eksport", + "background": "Taust", + "avatarRadius": "Profiilipildid", + "avatarAltRadius": "Profiilipildid (Teated)", + "attachmentRadius": "Manused", + "allow_following_move": "Luba automaatjälgimine kui jälgitav konto kolib", + "mfa": { + "verify": { + "desc": "Et lubada kaheastmelist autentimist, sisestage kood oma äpist:" + }, + "scan": { + "desc": "Kasutades oma kaheastmelise autentimise äppi, skännige see QR kood või sisestage tekstiline võti:", + "secret_code": "Võti", + "title": "Skänni" + }, + "authentication_methods": "Autentimismeetodid", + "recovery_codes_warning": "Kirjutage need koodid üles ning hoidke need kindlas kohas. Kui Te kaotate ligipääsu oma kaheastmelise autentimise äppile ning nendele koodidele, ei ole Teil võimalik oma kontosse sisse logida.", + "waiting_a_recovery_codes": "Laen taastekoode…", + "recovery_codes": "Taastekoodid.", + "warning_of_generate_new_codes": "Kui Te loote uued taastekoodid, Teie vanad koodid ei tööta enam.", + "generate_new_recovery_codes": "Loo uued taastekoodid", + "title": "Kaheastmeline autentimine", + "confirm_and_enable": "Kinnita & luba OTP", + "wait_pre_setup_otp": "sean üles OTP", + "setup_otp": "Sea üles OTP", + "otp": "OTP" + }, + "enter_current_password_to_confirm": "Sisetage isiku tõestamiseks oma salasõna", + "security": "Turvalisus", + "app_name": "Rakenduse nimi", + "style": { + "switcher": { + "help": { + "snapshot_present": "Kujunduse eelvaade on laetud, nii et kõik väärtused on üle kirjutatud. Te saate laadida ka kujunduse päris sisu.", + "older_version_imported": "Teie imporditud fail oli loodud vanemas versioonis.", + "future_version_imported": "Teie imporditud fail oli loodud uuemas versioonis.", + "v2_imported": "Teie imporditud fail oli vanema versiooni jaoks. Me üritame hoida ühilduvust, kuid ikkagi võib esineda erinevusi.", + "upgraded_from_v2": "PleromaFE-d uuendati, teie kujundus võib välja näha natuke erinev, kui mäletate." + }, + "use_source": "Uus versioon", + "use_snapshot": "Vana versioon", + "keep_as_is": "Jäta nii, nagu on", + "load_theme": "Lae kujundus", + "clear_opacity": "Tühista läbipaistvus", + "clear_all": "Tühista kõik", + "reset": "Taasta algne", + "keep_fonts": "Jäta fondid", + "keep_roundness": "Jäta ümarus", + "keep_opacity": "Jäta läbipaistvus", + "keep_shadows": "Jäta varjud", + "keep_color": "Jäta värvid" + } + }, + "enable_web_push_notifications": "Luba veebipõhised push-teated", + "notification_blocks": "Kasutaja blokeerimisel ei tule neilt enam teateid ning nendele teilt ka mitte.", + "notification_setting_privacy_option": "Peida saatja ning sisu push-teadetelt", + "notification_setting": "Saa teateid nendelt:", + "notifications": "Teated", + "notification_mutes": "Kui soovid mõnelt kasutajalt mitte teateid saada, kasuta vaigistust.", + "notification_setting_privacy": "Privaatsus", + "notification_setting_non_followers": "Kasutajatelt, kes sind ei jälgi", + "notification_setting_followers": "Kasutajatelt, kes jälgivad sind", + "notification_setting_non_follows": "Kasutajatelt, keda sa ei jälgi", + "notification_setting_follows": "Kasutajatelt, keda jälgid", + "notification_setting_filters": "Filtrid", + "greentext": "Meemi nooled", + "fun": "Naljad", + "values": { + "true": "jah", + "false": "ei" + }, + "upload_a_photo": "Lae üles foto", + "type_domains_to_mute": "Trüki siia domeene, mida vaigistada", + "tooltipRadius": "Vihjed/hoiatused", + "theme_help_v2_1": "Te saate ka mõndade komponentide värvust ning läbipaistvust üle kirjutada vajutades ruudule. Kasuta \"Tühista kõik\" nuppu, et need tühistada.", + "theme_help": "Kasuta hex värvikoode (#rrggbb) oma kujunduse isikupärastamiseks.", + "text": "Tekst", + "useStreamingApiWarning": "(Pole soovituslik, eksperimentaalne, on teada, et jätab postitusi vahele)", + "useStreamingApi": "Saa postitusi ning teateid reaalajas", + "user_mutes": "Kasutajad", + "streaming": "Luba uute postituste automaatvoog kui oled lehekülje alguses", + "stop_gifs": "Mängi GIFid hiirega ületades", + "post_status_content_type": "Postituse sisutüüp" }, "timeline": { "conversation": "Vestlus", @@ -79,5 +351,111 @@ "muted": "Vaigistatud", "per_day": "päevas", "statuses": "Staatuseid" + }, + "about": { + "mrf": { + "mrf_policies_desc": "MRF poliitikad mõjutavad selle instansi föderatsiooni käitumist. Järgmised poliitikad on lubatud:", + "simple": { + "media_nsfw_desc": "See instants määrab nendest instantsidest postituste meedia sensitiivseks:", + "media_nsfw": "Meedia määratakse sensitiivseks", + "media_removal_desc": "See instants eemaldab meedia postitustelt nendest instantsidest:", + "media_removal": "Meedia eemaldamine", + "ftl_removal_desc": "See instants eemaldab postitused nendelt instantsidest \"Kogu teatud võrgu\" ajajoonelt:", + "ftl_removal": "\"Kogu teatud võrgu\" ajajoonelt eemaldamine", + "quarantine_desc": "See instants saadab ainult avalikke postitusi järgmistele instantsidele:", + "quarantine": "Karantiini", + "reject_desc": "See instants ei luba sõnumeid nendest instantsidest:", + "reject": "Keela", + "accept_desc": "See instants lubab sõnumeid ainult nendest instantsidest:", + "accept": "Luba", + "simple_policies": "Instansi-omased poliitikad" + }, + "mrf_policies": "Lubatud MRF poliitikad", + "keyword": { + "is_replaced_by": "→", + "replace": "Vaheta", + "reject": "Lükka tagasi", + "ftl_removal": "\"Kogu teatud võrgu\" ajajoonelt eemaldamine", + "keyword_policies": "Võtmesõna poliitikad" + }, + "federation": "Föderatsioon" + }, + "staff": "Personal" + }, + "selectable_list": { + "select_all": "Vali kõik" + }, + "remote_user_resolver": { + "error": "Ei leitud.", + "searching_for": "Otsin", + "remote_user_resolver": "Kaugkasutaja leidja" + }, + "interactions": { + "load_older": "Laadi vanemad interaktsioonid", + "moves": "Kasutaja kolimised", + "follows": "Uued jälgimised", + "favs_repeats": "Taaspostitused ja lemmikud" + }, + "emoji": { + "load_all": "Laen kõik {emojiAmount} emotikoni", + "load_all_hint": "Laadisin esimesed {saneAmount} emotikoni, kõike laadides võib esineda probleeme jõudlusega.", + "unicode": "Unicode emotikonid", + "custom": "Kohandatud emotikonid", + "add_emoji": "Lisa emotikon", + "search_emoji": "Otsi emotikone", + "keep_open": "Hoia valija lahti", + "emoji": "Emotikonid", + "stickers": "Kleepsud" + }, + "polls": { + "not_enough_options": "Liiga vähe unikaalseid valikuid hääletuses", + "expired": "Hääletus lõppes {0} tagasi", + "expires_in": "Hääletus lõppeb {0}", + "expiry": "Hääletuse vanus", + "multiple_choices": "Mitu vastust", + "single_choice": "Üks vastus", + "type": "Hääletuse tüüp", + "vote": "Hääleta", + "votes": "häält", + "option": "Valik", + "add_option": "Lisa valik", + "add_poll": "Lisa küsitlus" + }, + "media_modal": { + "next": "Järgmine", + "previous": "Eelmine" + }, + "importer": { + "error": "Faili importimisel tekkis viga.", + "success": "Import õnnestus.", + "submit": "Esita" + }, + "image_cropper": { + "cancel": "Tühista", + "save_without_cropping": "Salvesta muudatusteta", + "save": "Salvesta", + "crop_picture": "Modifitseeri pilti" + }, + "features_panel": { + "who_to_follow": "Keda jälgida", + "title": "Featuurid", + "text_limit": "Tekstilimiit", + "scope_options": "Ulatuse valikud", + "media_proxy": "Meedia proksi", + "gopher": "Gopher", + "chat": "Vestlus" + }, + "exporter": { + "processing": "Töötlemine, Teilt küsitakse varsti faili allalaadimist", + "export": "Ekspordi" + }, + "domain_mute_card": { + "unmute_progress": "Eemaldan vaigistuse…", + "unmute": "Ära vaigista", + "mute_progress": "Vaigistan…", + "mute": "Vaigista" + }, + "chat": { + "title": "Vestlus" } } diff --git a/src/i18n/fi.json b/src/i18n/fi.json index e7ed5408..99a1b53a 100644 --- a/src/i18n/fi.json +++ b/src/i18n/fi.json @@ -19,7 +19,16 @@ "apply": "Aseta", "submit": "Lähetä", "more": "Lisää", - "generic_error": "Virhe tapahtui" + "generic_error": "Virhe tapahtui", + "optional": "valinnainen", + "show_more": "Näytä lisää", + "show_less": "Näytä vähemmän", + "dismiss": "Sulje", + "cancel": "Peruuta", + "disable": "Poista käytöstä", + "confirm": "Hyväksy", + "verify": "Varmenna", + "enable": "Ota käyttöön" }, "login": { "login": "Kirjaudu sisään", @@ -28,7 +37,16 @@ "password": "Salasana", "placeholder": "esim. Seppo", "register": "Rekisteröidy", - "username": "Käyttäjänimi" + "username": "Käyttäjänimi", + "hint": "Kirjaudu sisään liittyäksesi keskusteluun", + "authentication_code": "Todennuskoodi", + "enter_recovery_code": "Syötä palautuskoodi", + "recovery_code": "Palautuskoodi", + "heading": { + "totp": "Monivaihetodennus", + "recovery": "Monivaihepalautus" + }, + "enter_two_factor_code": "Syötä monivaihetodennuskoodi" }, "nav": { "about": "Tietoja", @@ -43,7 +61,9 @@ "twkn": "Koko Tunnettu Verkosto", "user_search": "Käyttäjähaku", "who_to_follow": "Seurausehdotukset", - "preferences": "Asetukset" + "preferences": "Asetukset", + "administration": "Ylläpito", + "search": "Haku" }, "notifications": { "broken_favorite": "Viestiä ei löydetty...", @@ -53,7 +73,10 @@ "notifications": "Ilmoitukset", "read": "Lue!", "repeated_you": "toisti viestisi", - "no_more_notifications": "Ei enempää ilmoituksia" + "no_more_notifications": "Ei enempää ilmoituksia", + "reacted_with": "lisäsi reaktion {0}", + "migrated_to": "siirtyi sivulle", + "follow_request": "haluaa seurata sinua" }, "polls": { "add_poll": "Lisää äänestys", @@ -67,12 +90,14 @@ "expiry": "Äänestyksen kesto", "expires_in": "Päättyy {0} päästä", "expired": "Päättyi {0} sitten", - "not_enough_option": "Liian vähän uniikkeja vaihtoehtoja äänestyksessä" + "not_enough_option": "Liian vähän uniikkeja vaihtoehtoja äänestyksessä", + "not_enough_options": "Liian vähän ainutkertaisia vaihtoehtoja" }, "interactions": { "favs_repeats": "Toistot ja tykkäykset", "follows": "Uudet seuraukset", - "load_older": "Lataa vanhempia interaktioita" + "load_older": "Lataa vanhempia interaktioita", + "moves": "Käyttäjien siirtymiset" }, "post_status": { "new_status": "Uusi viesti", @@ -80,7 +105,10 @@ "account_not_locked_warning_link": "lukittu", "attachments_sensitive": "Merkkaa liitteet arkaluonteisiksi", "content_type": { - "text/plain": "Tavallinen teksti" + "text/plain": "Tavallinen teksti", + "text/html": "HTML", + "text/markdown": "Markdown", + "text/bbcode": "BBCode" }, "content_warning": "Aihe (valinnainen)", "default": "Tulin juuri saunasta.", @@ -91,6 +119,13 @@ "private": "Vain-seuraajille - Näkyy vain seuraajillesi", "public": "Julkinen - Näkyy julkisilla aikajanoilla", "unlisted": "Listaamaton - Ei näy julkisilla aikajanoilla" + }, + "direct_warning_to_all": "Tämä viesti näkyy vain viestissä mainituille käyttäjille.", + "direct_warning_to_first_only": "Tämä viesti näkyy vain viestin alussa mainituille käyttäjille.", + "scope_notice": { + "public": "Tämä viesti näkyy kaikille", + "private": "Tämä viesti näkyy vain sinun seuraajillesi", + "unlisted": "Tämä viesti ei näy Julkisella Aikajanalla tai Koko Tunnettu Verkosto -aikajanalla" } }, "registration": { @@ -109,7 +144,10 @@ "password_required": "ei voi olla tyhjä", "password_confirmation_required": "ei voi olla tyhjä", "password_confirmation_match": "pitää vastata salasanaa" - } + }, + "username_placeholder": "esim. peke", + "fullname_placeholder": "esim. Pekka Postaaja", + "bio_placeholder": "esim.\nHei, olen Pekka.\nOlen esimerkkikäyttäjä tässä verkostossa." }, "settings": { "attachmentRadius": "Liitteet", @@ -140,6 +178,7 @@ "delete_account_description": "Poista tilisi ja viestisi pysyvästi.", "delete_account_error": "Virhe poistaessa tiliäsi. Jos virhe jatkuu, ota yhteyttä palvelimesi ylläpitoon.", "delete_account_instructions": "Syötä salasanasi vahvistaaksesi tilin poiston.", + "emoji_reactions_on_timeline": "Näytä emojireaktiot aikajanalla", "export_theme": "Tallenna teema", "filtering": "Suodatus", "filtering_explanation": "Kaikki viestit, jotka sisältävät näitä sanoja, suodatetaan. Yksi sana per rivi.", @@ -149,7 +188,7 @@ "follow_import": "Seurausten tuonti", "follow_import_error": "Virhe tuodessa seuraksia", "follows_imported": "Seuraukset tuotu! Niiden käsittely vie hetken.", - "foreground": "Korostus", + "foreground": "Etuala", "general": "Yleinen", "hide_attachments_in_convo": "Piilota liitteet keskusteluissa", "hide_attachments_in_tl": "Piilota liitteet aikajanalla", @@ -183,14 +222,15 @@ "notification_visibility_likes": "Tykkäykset", "notification_visibility_mentions": "Maininnat", "notification_visibility_repeats": "Toistot", - "no_rich_text_description": "Älä näytä tekstin muotoilua.", + "notification_visibility_emoji_reactions": "Reaktiot", + "no_rich_text_description": "Älä näytä tekstin muotoilua", "hide_network_description": "Älä näytä seurauksiani tai seuraajiani", "nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse", "oauth_tokens": "OAuth-merkit", "token": "Token", "refresh_token": "Päivitä token", "valid_until": "Voimassa asti", - "revoke_token": "Peruuttaa", + "revoke_token": "Peruuta", "panelRadius": "Ruudut", "pause_on_unfocused": "Pysäytä automaattinen viestien näyttö välilehden ollessa pois fokuksesta", "presets": "Valmiit teemat", @@ -228,6 +268,228 @@ "values": { "false": "pois päältä", "true": "päällä" + }, + "hide_follows_description": "Älä näytä ketä seuraan", + "show_moderator_badge": "Näytä Moderaattori-merkki profiilissani", + "useStreamingApi": "Vastaanota viestiejä ja ilmoituksia reaaliajassa", + "notification_setting_filters": "Suodattimet", + "notification_setting": "Vastaanota ilmoituksia seuraavista:", + "notification_setting_privacy_option": "Piilota lähettäjä ja sisältö sovelluksen ulkopuolisista ilmoituksista", + "enable_web_push_notifications": "Ota käyttöön sovelluksen ulkopuoliset ilmoitukset", + "app_name": "Sovelluksen nimi", + "security": "Turvallisuus", + "mfa": { + "otp": "OTP", + "setup_otp": "OTP-asetukset", + "wait_pre_setup_otp": "esiasetetaan OTP:ta", + "confirm_and_enable": "Hyväksy ja käytä OTP", + "title": "Monivaihetodennus", + "generate_new_recovery_codes": "Luo uudet palautuskoodit", + "authentication_methods": "Todennus", + "warning_of_generate_new_codes": "Luodessasi uudet palautuskoodit, vanhat koodisi lakkaavat toimimasta.", + "recovery_codes": "Palautuskoodit.", + "waiting_a_recovery_codes": "Odotetaan palautuskoodeja...", + "recovery_codes_warning": "Kirjoita koodit ylös tai tallenna ne turvallisesti, muuten et näe niitä uudestaan. Jos et voi käyttää monivaihetodennusta ja sinulla ei ole palautuskoodeja, et voi enää kirjautua sisään tilillesi.", + "scan": { + "title": "Skannaa", + "secret_code": "Avain", + "desc": "Käytä monivaihetodennus-sovellusta skannakksesi tämän QR-kooding, tai syötä avain:" + }, + "verify": { + "desc": "Kytkeäksesi päälle monivaihetodennuksen, syötä koodi monivaihetodennussovellksesta:" + } + }, + "allow_following_move": "Salli automaattinen seuraaminen kun käyttäjä siirtää tilinsä", + "block_export": "Estojen vienti", + "block_export_button": "Vie estosi CSV-tiedostoon", + "block_import": "Estojen tuonti", + "block_import_error": "Virhe tuodessa estoja", + "blocks_imported": "Estot tuotu! Käsittely vie hetken.", + "blocks_tab": "Estot", + "change_email": "Vaihda sähköpostiosoite", + "change_email_error": "Virhe vaihtaessa sähköpostiosoitetta.", + "changed_email": "Sähköpostiosoite vaihdettu!", + "domain_mutes": "Sivut", + "avatar_size_instruction": "Suositeltu vähimmäiskoko profiilikuville on 150x150 pikseliä.", + "accent": "Korostus", + "hide_muted_posts": "Piilota mykistettyjen käyttäjien viestit", + "hide_filtered_statuses": "Piilota mykistetyt viestit", + "import_blocks_from_a_csv_file": "Tuo estot CSV-tiedostosta", + "no_blocks": "Ei estoja", + "no_mutes": "Ei mykistyksiä", + "notification_visibility_moves": "Käyttäjien siirtymiset", + "hide_followers_description": "Älä näytä ketkä seuraavat minua", + "hide_follows_count_description": "Älä näytä seurauksien määrää", + "hide_followers_count_description": "Älä näytä seuraajien määrää", + "show_admin_badge": "Näytä Ylläpitäjä-merkki proofilissani", + "autohide_floating_post_button": "Piilota Uusi Viesti -nappi automaattisesti (mobiili)", + "search_user_to_block": "Hae estettäviä käyttäjiä", + "search_user_to_mute": "Hae mykistettäviä käyttäjiä", + "minimal_scopes_mode": "Yksinkertaista näkyvyydenrajauksen vaihtoehdot", + "post_status_content_type": "Uuden viestin sisällön muoto", + "user_mutes": "Käyttäjät", + "useStreamingApiWarning": "(Kokeellinen)", + "type_domains_to_mute": "Syötä mykistettäviä sivustoja", + "upload_a_photo": "Lataa kuva", + "fun": "Hupi", + "greentext": "Meeminuolet", + "notifications": "Ilmoitukset", + "style": { + "switcher": { + "save_load_hint": "\"Säilytä\" asetukset säilyttävät tällä hetkellä asetetut asetukset valittaessa tai ladatessa teemaa, se myös tallentaa kyseiset asetukset viedessä teemaa. Kun kaikki laatikot ovat tyhjänä, viety teema tallentaa kaiken.", + "help": { + "older_version_imported": "Tuomasi tiedosto on luotu vanhemmalla versiolla.", + "fe_upgraded": "PleromaFE:n teemaus päivitetty versiopäivityksen yhteydessä.", + "migration_snapshot_ok": "Varmuuden vuoksi teeman kaappaus ladattu. Voit koittaa ladata teeman sisällön.", + "migration_napshot_gone": "Jostain syystä teeman kaappaus puuttuu, kaikki asiat eivät välttämättä näytä oikealta.", + "snapshot_source_mismatch": "Versiot eivät täsmää: todennäköisesti versio vaihdettu vanhempaan ja päivitetty uudestaan, jos vaihdoit teemaa vanhalla versiolla, sinun tulisi käyttää vanhaa versiota, muutoin uutta.", + "upgraded_from_v2": "PleromaFE on päivitetty, teemasi saattaa näyttää erilaiselta kuin muistat.", + "v2_imported": "Tuomasi tiedosto on luotu vanhemmalla versiolla. Yhteensopivuus ei välttämättä ole täydellinen.", + "future_version_imported": "Tuomasi tiedosto on luotu uudemmalla versiolla.", + "snapshot_present": "Teeman kaappaus ladattu, joten kaikki arvot ovat ylikirjoitettu. Voit sen sijaan ladata teeman sisällön.", + "snapshot_missing": "Teeman kaappausta ei tiedostossa, joten se voi näyttää erilaiselta kuin suunniteltu.", + "fe_downgraded": "PleromaFE:n versio vaihtunut vanhempaan." + }, + "keep_color": "Säilytä värit", + "keep_shadows": "Säilytä varjot", + "keep_opacity": "Säilytä läpinäkyvyys", + "keep_roundness": "Säilytä pyöristys", + "keep_fonts": "Säilytä fontit", + "reset": "Palauta", + "clear_all": "Tyhjennä kaikki", + "clear_opacity": "Tyhjennä läpinäkyvyys", + "load_theme": "Lataa teema", + "keep_as_is": "Pidä sellaisenaan", + "use_snapshot": "Vanha", + "use_source": "Uusi" + }, + "advanced_colors": { + "selectedPost": "Valittu viesti", + "_tab_label": "Edistynyt", + "alert": "Varoituksen tausta", + "alert_error": "Virhe", + "alert_warning": "Varoitus", + "alert_neutral": "Neutraali", + "post": "Viestit/Käyttäjien kuvaukset", + "badge": "Merkin tausta", + "badge_notification": "Ilmoitus", + "panel_header": "Ruudun otsikko", + "top_bar": "Yläpalkki", + "borders": "Reunat", + "buttons": "Napit", + "inputs": "Syöttökentät", + "faint_text": "Häivytetty teksti", + "underlay": "Taustapeite", + "poll": "Äänestyksen kuvaaja", + "icons": "Ikonit", + "highlight": "Korostetut elementit", + "pressed": "Painettu", + "selectedMenu": "Valikon valinta", + "disabled": "Pois käytöstä", + "toggled": "Kytketty", + "tabs": "Välilehdet", + "popover": "Työkaluvinkit, valikot, ponnahdusviestit" + }, + "common": { + "color": "Väri", + "opacity": "Läpinäkyvyys", + "contrast": { + "level": { + "aaa": "saavuttaa AAA-tason (suositeltu)", + "aa": "saavuttaa AA-tason (minimi)", + "bad": "ei saavuta mitään helppokäyttöisyyssuosituksia" + }, + "hint": "Kontrastisuhde on {ratio}, se {level} {context}", + "context": { + "18pt": "suurella (18pt+) tekstillä", + "text": "tekstillä" + } + } + }, + "common_colors": { + "_tab_label": "Yleinen", + "main": "Yleiset värit", + "foreground_hint": "Löydät \"Edistynyt\"-välilehdeltä tarkemmat asetukset", + "rgbo": "Ikonit, korostukset, merkit" + }, + "shadows": { + "filter_hint": { + "always_drop_shadow": "Varoitus, tämä varjo käyttää aina {0} kun selain tukee sitä.", + "avatar_inset": "Huom. sisennettyjen ja ei-sisennettyjen varjojen yhdistelmät saattavat luoda ei-odotettuja lopputuloksia läpinäkyvillä profiilikuvilla.", + "drop_shadow_syntax": "{0} ei tue {1} parametria ja {2} avainsanaa.", + "spread_zero": "Varjot joiden levitys > 0 näyttävät samalta kuin se olisi nolla", + "inset_classic": "Sisennetyt varjot käyttävät {0}" + }, + "components": { + "buttonPressedHover": "Nappi (painettu ja kohdistettu)", + "panel": "Ruutu", + "panelHeader": "Ruudun otsikko", + "topBar": "Yläpalkki", + "avatar": "Profiilikuva (profiilinäkymässä)", + "avatarStatus": "Profiilikuva (viestin yhtyedessä)", + "popup": "Ponnahdusviestit ja työkaluvinkit", + "button": "Nappi", + "buttonHover": "Nappi (kohdistus)", + "buttonPressed": "Nappi (painettu)", + "input": "Syöttökenttä" + }, + "hintV3": "Voit käyttää {0} merkintää varjoille käyttääksesi väriä toisesta asetuksesta.", + "_tab_label": "Valo ja varjostus", + "component": "Komponentti", + "override": "Ylikirjoita", + "shadow_id": "Varjo #{value}", + "blur": "Sumennus", + "spread": "Levitys", + "inset": "Sisennys" + }, + "fonts": { + "help": "Valitse fontti käyttöliittymälle. \"Oma\"-vaihtohdolle on syötettävä fontin nimi tarkalleen samana kuin se on järjestelmässäsi.", + "_tab_label": "Fontit", + "components": { + "interface": "Käyttöliittymä", + "input": "Syöttökentät", + "post": "Viestin teksti", + "postCode": "Tasavälistetty teksti viestissä" + }, + "family": "Fontin nimi", + "size": "Koko (pikseleissä)", + "weight": "Painostus (paksuus)", + "custom": "Oma" + }, + "preview": { + "input": "Tulin juuri saunasta.", + "header": "Esikatselu", + "content": "Sisältö", + "error": "Esimerkkivirhe", + "button": "Nappi", + "text": "Vähän lisää {0} ja {1}", + "mono": "sisältöä", + "faint_link": "manuaali", + "fine_print": "Lue meidän {0} vaikka huvin vuoksi!", + "header_faint": "Tämä on OK", + "checkbox": "Olen silmäillyt käyttöehdot", + "link": "kiva linkki" + }, + "radii": { + "_tab_label": "Pyöristys" + } + }, + "enter_current_password_to_confirm": "Syötä nykyinen salasanasi todentaaksesi henkilöllisyytesi", + "discoverable": "Salli tilisi näkyvyys hakukoneisiin ja muihin palveluihin", + "pad_emoji": "Välistä emojit välilyönneillä lisätessäsi niitä valitsimesta", + "mutes_tab": "Mykistykset", + "new_email": "Uusi sähköpostiosoite", + "notification_setting_follows": "Käyttäjät joita seuraat", + "notification_setting_non_follows": "Käyttäjät joita et seuraa", + "notification_setting_followers": "Käyttäjät jotka seuraavat sinua", + "notification_setting_non_followers": "Käyttäjät jotka eivät seuraa sinua", + "notification_setting_privacy": "Yksityisyys", + "notification_mutes": "Jos et halua ilmoituksia joltain käyttäjältä, käytä mykistystä.", + "notification_blocks": "Estäminen pysäyttää kaikki ilmoitukset käyttäjältä ja poistaa seurauksen.", + "version": { + "title": "Versio", + "backend_version": "Palvelimen versio", + "frontend_version": "Käyttöliittymän versio" } }, "time": { @@ -249,8 +511,8 @@ "months": "{0} kuukautta", "month_short": "{0}kk", "months_short": "{0}kk", - "now": "nyt", - "now_short": "juuri nyt", + "now": "juuri nyt", + "now_short": "nyt", "second": "{0} sekunti", "seconds": "{0} sekuntia", "second_short": "{0}s", @@ -273,7 +535,8 @@ "repeated": "toisti", "show_new": "Näytä uudet", "up_to_date": "Ajantasalla", - "no_more_statuses": "Ei enempää viestejä" + "no_more_statuses": "Ei enempää viestejä", + "no_statuses": "Ei viestejä" }, "status": { "favorites": "Tykkäykset", @@ -285,8 +548,10 @@ "delete_confirm": "Haluatko varmasti postaa viestin?", "reply_to": "Vastaus", "replies_list": "Vastaukset:", - "mute_conversation": "Hiljennä keskustelu", - "unmute_conversation": "Poista hiljennys" + "mute_conversation": "Mykistä keskustelu", + "unmute_conversation": "Poista mykistys", + "status_unavailable": "Viesti ei saatavissa", + "copy_link": "Kopioi linkki" }, "user_card": { "approve": "Hyväksy", @@ -295,7 +560,7 @@ "deny": "Älä hyväksy", "follow": "Seuraa", "follow_sent": "Pyyntö lähetetty!", - "follow_progress": "Pyydetään...", + "follow_progress": "Pyydetään…", "follow_again": "Lähetä pyyntö uudestaan", "follow_unfollow": "Älä seuraa", "followees": "Seuraa", @@ -303,14 +568,50 @@ "following": "Seuraat!", "follows_you": "Seuraa sinua!", "its_you": "Sinun tili!", - "mute": "Hiljennä", - "muted": "Hiljennetty", + "mute": "Mykistä", + "muted": "Mykistetty", "per_day": "päivässä", "remote_follow": "Seuraa muualta", - "statuses": "Viestit" + "statuses": "Viestit", + "hidden": "Piilotettu", + "media": "Media", + "block_progress": "Estetään...", + "admin_menu": { + "grant_admin": "Anna Ylläpitöoikeudet", + "force_nsfw": "Merkitse kaikki viestit NSFW:nä", + "disable_any_subscription": "Estä käyttäjän seuraaminen", + "moderation": "Moderaatio", + "revoke_admin": "Poista Ylläpitöoikeudet", + "grant_moderator": "Anna Moderaattorioikeudet", + "revoke_moderator": "Poista Moderaattorioikeudet", + "activate_account": "Aktivoi tili", + "deactivate_account": "Deaktivoi tili", + "delete_account": "Poista tili", + "strip_media": "Poista media viesteistä", + "force_unlisted": "Pakota viestit listaamattomiksi", + "sandbox": "Pakota viestit vain seuraajille", + "disable_remote_subscription": "Estä seuraaminen ulkopuolisilta sivuilta", + "quarantine": "Estä käyttäjän viestin federoituminen", + "delete_user": "Poista käyttäjä", + "delete_user_confirmation": "Oletko aivan varma? Tätä ei voi kumota." + }, + "favorites": "Tykkäykset", + "mention": "Mainitse", + "report": "Ilmianna", + "subscribe": "Tilaa", + "unsubscribe": "Poista tilaus", + "unblock": "Poista esto", + "unblock_progress": "Postetaan estoa...", + "unmute": "Poista mykistys", + "unmute_progress": "Poistetaan mykistystä...", + "mute_progress": "Mykistetään...", + "hide_repeats": "Piilota toistot", + "show_repeats": "Näytä toistot" }, "user_profile": { - "timeline_title": "Käyttäjän aikajana" + "timeline_title": "Käyttäjän aikajana", + "profile_does_not_exist": "Tätä profiilia ei ole.", + "profile_loading_error": "Virhe ladatessa profiilia." }, "who_to_follow": { "more": "Lisää", @@ -321,9 +622,12 @@ "repeat": "Toista", "reply": "Vastaa", "favorite": "Tykkää", - "user_settings": "Käyttäjäasetukset" + "user_settings": "Käyttäjäasetukset", + "add_reaction": "Lisää Reaktio", + "accept_follow_request": "Hyväksy seurauspyyntö", + "reject_follow_request": "Hylkää seurauspyyntö" }, - "upload":{ + "upload": { "error": { "base": "Lataus epäonnistui.", "file_too_big": "Tiedosto liian suuri [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", @@ -336,5 +640,108 @@ "GiB": "Gt", "TiB": "Tt" } + }, + "about": { + "mrf": { + "keyword": { + "keyword_policies": "Avainsanasäännöt", + "ftl_removal": "Poistettu \"Koko Tunnettu Verkosto\" -aikajanalta", + "reject": "Hylkää", + "replace": "Korvaa", + "is_replaced_by": "→" + }, + "simple": { + "accept": "Hyväksy", + "reject": "Hylkää", + "quarantine": "Karanteeni", + "ftl_removal": "Poisto \"Koko Tunnettu Verkosto\" -aikajanalta", + "media_removal": "Media-tiedostojen poisto", + "simple_policies": "Palvelinkohtaiset Säännöt", + "accept_desc": "Tämä palvelin hyväksyy viestit vain seuraavilta palvelimilta:", + "reject_desc": "Tämä palvelin ei hyväksy viestejä seuraavilta palvelimilta:", + "quarantine_desc": "Tämä palvelin lähettää vain julkisia viestejä seuraaville palvelimille:", + "ftl_removal_desc": "Tämä palvelin poistaa nämä palvelimet \"Koko Tunnettu Verkosto\"-aikajanalta:", + "media_removal_desc": "Tämä palvelin postaa mediatiedostot viesteistä seuraavilta palvelimilta:", + "media_nsfw": "Pakota Media Arkaluontoiseksi", + "media_nsfw_desc": "Tämä palvelin pakottaa mediatiedostot arkaluonteisiksi seuraavilta palvelimilta:" + }, + "federation": "Federaatio", + "mrf_policies": "Aktivoidut MRF-säännöt", + "mrf_policies_desc": "MRF-säännöt muuttavat federaation toimintaa sivulla. Seuraavat säännöt ovat kytketty päälle:" + }, + "staff": "Henkilökunta" + }, + "domain_mute_card": { + "mute": "Mykistä", + "unmute": "Poista mykistys", + "mute_progress": "Mykistetään...", + "unmute_progress": "Poistetaan mykistyst..." + }, + "exporter": { + "export": "Vie", + "processing": "Käsitellään, hetken päästä voit tallentaa tiedoston" + }, + "image_cropper": { + "crop_picture": "Rajaa kuva", + "save": "Tallenna", + "save_without_cropping": "Tallenna rajaamatta", + "cancel": "Peruuta" + }, + "importer": { + "submit": "Hyväksy", + "error": "Virhe tapahtui tietoja tuodessa.", + "success": "Tuonti onnistui." + }, + "media_modal": { + "previous": "Edellinen", + "next": "Seuraava" + }, + "emoji": { + "stickers": "Tarrat", + "emoji": "Emoji", + "keep_open": "Pidä valitsin auki", + "search_emoji": "Hae emojia", + "add_emoji": "Lisää emoji", + "custom": "Custom-emoji", + "load_all": "Ladataan kaikkia {emojiAmount} emojia", + "unicode": "Unicode-emoji", + "load_all_hint": "Ensimmäiset {saneAmount} emojia ladattu, kaikkien emojien lataaminen voi aiheuttaa hidastelua." + }, + "remote_user_resolver": { + "remote_user_resolver": "Ulkopuolinen käyttäjä", + "searching_for": "Etsitään käyttäjää", + "error": "Ei löytynyt." + }, + "selectable_list": { + "select_all": "Valitse kaikki" + }, + "password_reset": { + "check_email": "Tarkista sähköpostisi salasanannollausta varten.", + "instruction": "Syötä sähköpostiosoite tai käyttäjänimi. Lähetämme linkin salasanan nollausta varten.", + "password_reset_disabled": "Salasanan nollaus ei käytössä. Ota yhteyttä sivun ylläpitäjään.", + "password_reset_required_but_mailer_is_disabled": "Sinun täytyy vaihtaa salasana, mutta salasanan nollaus on pois käytöstä. Ota yhteyttä sivun ylläpitäjään.", + "forgot_password": "Unohditko salasanan?", + "password_reset": "Salasanan nollaus", + "placeholder": "Sähköpostiosoite tai käyttäjänimi", + "return_home": "Palaa etusivulle", + "not_found": "Sähköpostiosoitetta tai käyttäjänimeä ei löytynyt.", + "too_many_requests": "Olet käyttänyt kaikki yritykset, yritä uudelleen myöhemmin.", + "password_reset_required": "Sinun täytyy vaihtaa salasana kirjautuaksesi." + }, + "user_reporting": { + "add_comment_description": "Tämä raportti lähetetään sivun moderaattoreille. Voit antaa selityksen miksi ilmiannoit tilin:", + "title": "Ilmiannetaan {0}", + "additional_comments": "Lisäkommentit", + "forward_description": "Tämä tili on toiselta palvelimelta. Lähetä kopio ilmiannosta sinnekin?", + "forward_to": "Lähetä eteenpäin: {0}", + "submit": "Lähetä", + "generic_error": "Virhe käsitellessä pyyntöä." + }, + "search": { + "people": "Käyttäjät", + "hashtags": "Aihetunnisteet", + "people_talking": "{0} käyttäjää puhuvat", + "person_talking": "{0} käyttäjä puhuu", + "no_results": "Ei tuloksia" } } diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 5f0053d5..31b69a0f 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -1,549 +1,743 @@ { - "chat": { - "title": "Chat" - }, - "exporter": { - "export": "Exporter", - "processing": "En cours de traitement, vous pourrez bientôt télécharger votre fichier" - }, - "features_panel": { - "chat": "Chat", - "gopher": "Gopher", - "media_proxy": "Proxy média", - "scope_options": "Options de visibilité", - "text_limit": "Limite de texte", - "title": "Caractéristiques", - "who_to_follow": "Personnes à suivre" - }, - "finder": { - "error_fetching_user": "Erreur lors de la recherche de l'utilisateur·ice", - "find_user": "Chercher un-e utilisateur·ice" - }, - "general": { - "apply": "Appliquer", - "submit": "Envoyer", - "more": "Plus", - "generic_error": "Une erreur s'est produite", - "optional": "optionnel", - "show_more": "Montrer plus", - "show_less": "Montrer moins", - "cancel": "Annuler", - "disable": "Désactiver", - "enable": "Activer", - "confirm": "Confirmer", - "verify": "Vérifier" - }, - "image_cropper": { - "crop_picture": "Rogner l'image", - "save": "Sauvegarder", - "save_without_cropping": "Sauvegarder sans rogner", - "cancel": "Annuler" - }, - "importer": { - "submit": "Soumettre", - "success": "Importé avec succès.", - "error": "Une erreur est survenue pendant l'import de ce fichier." - }, - "login": { - "login": "Connexion", - "description": "Connexion avec OAuth", - "logout": "Déconnexion", - "password": "Mot de passe", - "placeholder": "p.e. lain", - "register": "S'inscrire", - "username": "Identifiant", - "hint": "Connectez-vous pour rejoindre la discussion", - "authentication_code": "Code d'authentification", - "enter_recovery_code": "Entrez un code de récupération", - "enter_two_factor_code": "Entrez un code à double authentification", - "recovery_code": "Code de récupération", - "heading": { - "totp": "Authentification à double authentification", - "recovery": "Récuperation de la double authentification" - } - }, - "media_modal": { - "previous": "Précédent", - "next": "Suivant" - }, - "nav": { - "about": "À propos", - "back": "Retour", - "chat": "Chat local", - "friend_requests": "Demandes de suivi", - "mentions": "Notifications", - "interactions": "Interactions", - "dms": "Messages directs", - "public_tl": "Fil d'actualité public", - "timeline": "Fil d'actualité", - "twkn": "Ensemble du réseau connu", - "user_search": "Recherche d'utilisateur·ice", - "who_to_follow": "Qui suivre", - "preferences": "Préférences" - }, - "notifications": { - "broken_favorite": "Chargement d'un message inconnu…", - "favorited_you": "a aimé votre statut", - "followed_you": "a commencé à vous suivre", - "load_older": "Charger les notifications précédentes", - "notifications": "Notifications", - "read": "Lu !", - "repeated_you": "a partagé votre statut", - "no_more_notifications": "Aucune notification supplémentaire" - }, - "interactions": { - "favs_repeats": "Partages et favoris", - "follows": "Nouveaux⋅elles abonné⋅e⋅s ?", - "load_older": "Chargez d'anciennes interactions" - }, - "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 le média comme sensible", - "content_type": { - "text/plain": "Texte brut", - "text/html": "HTML", - "text/markdown": "Markdown", - "text/bbcode": "BBCode" - }, - "content_warning": "Sujet (optionnel)", - "default": "Écrivez ici votre prochain statut.", - "direct_warning_to_all": "Ce message sera visible pour toutes les personnes mentionnées.", - "direct_warning_to_first_only": "Ce message sera visible uniquement pour personnes mentionnées au début du message.", - "posting": "Envoi en cours", - "scope_notice": { - "public": "Ce statut sera visible par tout le monde", - "private": "Ce statut sera visible par seulement vos abonné⋅e⋅s", - "unlisted": "Ce statut ne sera pas visible dans le Fil d'actualité public et l'Ensemble du réseau connu" - }, - "scope": { - "direct": "Direct - N'envoyer qu'aux personnes mentionnées", - "private": "Abonné·e·s uniquement - Seul·e·s vos abonné·e·s verront vos billets", - "public": "Publique - Afficher dans les fils publics", - "unlisted": "Non-Listé - Ne pas afficher dans les fils publics" - } - }, - "registration": { - "bio": "Biographie", - "email": "Adresse mail", - "fullname": "Pseudonyme", - "password_confirm": "Confirmation du mot de passe", - "registration": "Inscription", - "token": "Jeton d'invitation", - "captcha": "CAPTCHA", - "new_captcha": "Cliquez sur l'image pour avoir un nouveau captcha", - "username_placeholder": "p.e. lain", - "fullname_placeholder": "p.e. Lain Iwakura", - "bio_placeholder": "p.e.\nSalut, je suis Lain\nJe suis une héroïne d'animé qui vit dans une banlieue japonaise. Vous me connaissez peut-être du Wired.", - "validations": { - "username_required": "ne peut pas être laissé vide", - "fullname_required": "ne peut pas être laissé vide", - "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" - } - }, - "selectable_list": { - "select_all": "Tout selectionner" - }, - "settings": { - "app_name": "Nom de l'application", - "security": "Sécurité", - "enter_current_password_to_confirm": "Entrez votre mot de passe actuel pour confirmer votre identité", - "mfa": { - "otp": "OTP", - "setup_otp": "Configurer OTP", - "wait_pre_setup_otp": "préconfiguration OTP", - "confirm_and_enable": "Confirmer & activer OTP", - "title": "Double authentification", - "generate_new_recovery_codes": "Générer de nouveaux codes de récupération", - "warning_of_generate_new_codes": "Quand vous générez de nouveauc codes de récupération, vos anciens codes ne fonctionnerons plus.", - "recovery_codes": "Codes de récupération.", - "waiting_a_recovery_codes": "Récéption des codes de récupération…", - "recovery_codes_warning": "Écrivez les codes ou sauvez les quelquepart sécurisé - sinon vous ne les verrez plus jamais. Si vous perdez l'accès à votre application de double authentification et codes de récupération vous serez vérouillé en dehors de votre compte.", - "authentication_methods": "Methodes d'authentification", - "scan": { - "title": "Scanner", - "desc": "En utilisant votre application de double authentification, scannez ce QR code ou entrez la clé textuelle :", - "secret_code": "Clé" - }, - "verify": { - "desc": "Pour activer la double authentification, entrez le code depuis votre application:" - } - }, - "attachmentRadius": "Pièces jointes", - "attachments": "Pièces jointes", - "autoload": "Charger la suite automatiquement une fois le bas de la page atteint", - "avatar": "Avatar", - "avatarAltRadius": "Avatars (Notifications)", - "avatarRadius": "Avatars", - "background": "Arrière-plan", - "bio": "Biographie", - "block_export": "Export des comptes bloqués", - "block_export_button": "Export des comptes bloqués vers un fichier csv", - "block_import": "Import des comptes bloqués", - "block_import_error": "Erreur lors de l'import des comptes bloqués", - "blocks_imported": "Blocks importés! Le traitement va prendre un moment.", - "blocks_tab": "Bloqué·e·s", - "btnRadius": "Boutons", - "cBlue": "Bleu (répondre, suivre)", - "cGreen": "Vert (partager)", - "cOrange": "Orange (aimer)", - "cRed": "Rouge (annuler)", - "change_password": "Changez votre mot de passe", - "change_password_error": "Il y a eu un problème pour changer votre mot de passe.", - "changed_password": "Mot de passe modifié avec succès !", - "collapse_subject": "Réduire les messages avec des sujets", - "composing": "Composition", - "confirm_new_password": "Confirmation du nouveau mot de passe", - "current_avatar": "Avatar actuel", - "current_password": "Mot de passe actuel", - "current_profile_banner": "Bannière de profil actuelle", - "data_import_export_tab": "Import / Export des Données", - "default_vis": "Visibilité par défaut", - "delete_account": "Supprimer le compte", - "delete_account_description": "Supprimer définitivement votre compte et tous vos statuts.", - "delete_account_error": "Il y a eu un problème lors de la tentative de suppression de votre compte. Si le problème persiste, contactez l'administrateur⋅ice de cette instance.", - "delete_account_instructions": "Indiquez votre mot de passe ci-dessous pour confirmer la suppression de votre compte.", - "avatar_size_instruction": "La taille minimale recommandée pour l'image de l'avatar est de 150x150 pixels.", - "export_theme": "Enregistrer le thème", - "filtering": "Filtre", - "filtering_explanation": "Tous les statuts contenant ces mots seront masqués. Un mot par ligne", - "follow_export": "Exporter les abonnements", - "follow_export_button": "Exporter les abonnements en csv", - "follow_import": "Importer des abonnements", - "follow_import_error": "Erreur lors de l'importation des abonnements", - "follows_imported": "Abonnements importés ! Le traitement peut prendre un moment.", - "foreground": "Premier plan", - "general": "Général", - "hide_attachments_in_convo": "Masquer les pièces jointes dans les conversations", - "hide_attachments_in_tl": "Masquer les pièces jointes dans le journal", - "hide_muted_posts": "Masquer les statuts des utilisateurs masqués", - "max_thumbnails": "Nombre maximum de miniatures par statuts", - "hide_isp": "Masquer le panneau spécifique a l'instance", - "preload_images": "Précharger les images", - "use_one_click_nsfw": "Ouvrir les pièces-jointes NSFW avec un seul clic", - "hide_post_stats": "Masquer les statistiques de publication (le nombre de favoris)", - "hide_user_stats": "Masquer les statistiques de profil (le nombre d'amis)", - "hide_filtered_statuses": "Masquer les statuts filtrés", - "import_blocks_from_a_csv_file": "Importer les blocages depuis un fichier csv", - "import_followers_from_a_csv_file": "Importer des abonnements depuis un fichier csv", - "import_theme": "Charger le thème", - "inputRadius": "Champs de texte", - "checkboxRadius": "Cases à cocher", - "instance_default": "(default: {value})", - "instance_default_simple": "(default)", - "interface": "Interface", - "interfaceLanguage": "Langue de l'interface", - "invalid_theme_imported": "Le fichier sélectionné n'est pas un thème Pleroma pris en charge. Aucun changement n'a été apporté à votre thème.", - "limited_availability": "Non disponible dans votre navigateur", - "links": "Liens", - "lock_account_description": "Limitez votre compte aux abonnés acceptés uniquement", - "loop_video": "Vidéos en boucle", - "loop_video_silent_only": "Boucle uniquement les vidéos sans le son (les « gifs » de Mastodon)", - "mutes_tab": "Comptes silenciés", - "play_videos_in_modal": "Jouer les vidéos directement dans le visionneur de médias", - "use_contain_fit": "Ne pas rogner les miniatures des pièces-jointes", - "name": "Nom", - "name_bio": "Nom & Bio", - "new_password": "Nouveau mot de passe", - "notification_visibility": "Types de notifications à afficher", - "notification_visibility_follows": "Abonnements", - "notification_visibility_likes": "J'aime", - "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", - "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'Administrateur⋅ice sur mon profil", - "show_moderator_badge": "Afficher le badge de Modérateur⋅ice sur mon profil", - "nsfw_clickthrough": "Masquer les images marquées comme contenu adulte ou sensible", - "oauth_tokens": "Jetons OAuth", - "token": "Jeton", - "refresh_token": "Refresh Token", - "valid_until": "Valable jusque", - "revoke_token": "Révoquer", - "panelRadius": "Fenêtres", - "pause_on_unfocused": "Suspendre le streaming lorsque l'onglet n'est pas actif", - "presets": "Thèmes prédéfinis", - "profile_background": "Image de fond", - "profile_banner": "Bannière de profil", - "profile_tab": "Profil", - "radii_help": "Vous pouvez ici choisir le niveau d'arrondi des angles de l'interface (en pixels)", - "replies_in_timeline": "Réponses au journal", - "reply_link_preview": "Afficher un aperçu lors du survol de liens vers une réponse", - "reply_visibility_all": "Montrer toutes les réponses", - "reply_visibility_following": "Afficher uniquement les réponses adressées à moi ou aux personnes que je suis", - "reply_visibility_self": "Afficher uniquement les réponses adressées à moi", - "autohide_floating_post_button": "Automatiquement cacher le bouton de Nouveau Statut (sur mobile)", - "saving_err": "Erreur lors de l'enregistrement des paramètres", - "saving_ok": "Paramètres enregistrés", - "search_user_to_block": "Rechercher qui vous voulez bloquer", - "search_user_to_mute": "Rechercher qui vous voulez masquer", - "security_tab": "Sécurité", - "scope_copy": "Garder la même visibilité en répondant (les DMs restent toujours des DMs)", - "minimal_scopes_mode": "Rétrécir les options de séléction de la portée", - "set_new_avatar": "Changer d'avatar", - "set_new_profile_background": "Changer d'image de fond", - "set_new_profile_banner": "Changer de bannière", - "settings": "Paramètres", - "subject_input_always_show": "Toujours copier le champ de sujet", - "subject_line_behavior": "Copier le sujet en répondant", - "subject_line_email": "Comme les mails: « re: sujet »", - "subject_line_mastodon": "Comme mastodon: copier tel quel", - "subject_line_noop": "Ne pas copier", - "post_status_content_type": "Type de contenu du statuts", - "stop_gifs": "N'animer les GIFS que lors du survol du curseur de la souris", - "streaming": "Charger automatiquement les nouveaux statuts lorsque vous êtes au haut de la page", - "text": "Texte", - "theme": "Thème", - "theme_help": "Spécifiez des codes couleur hexadécimaux (#rrvvbb) pour personnaliser les couleurs du thème.", - "theme_help_v2_1": "Vous pouvez aussi surcharger certaines couleurs de composants et transparence via la case à cocher, utilisez le bouton « Vider tout » pour effacer toutes les surcharges.", - "theme_help_v2_2": "Les icônes sous certaines des entrées ont un indicateur de contraste du fond/texte, survolez les pour plus d'informations détailles. Veuillez garder a l'esprit que lors de l'utilisation de transparence l'indicateur de contraste indique le pire des cas.", - "tooltipRadius": "Info-bulles/alertes", - "upload_a_photo": "Envoyer une photo", - "user_settings": "Paramètres utilisateur", - "values": { - "false": "non", - "true": "oui" - }, - "notifications": "Notifications", - "notification_setting": "Reçevoir les notifications de:", - "notification_setting_follows": "Utilisateurs que vous suivez", - "notification_setting_non_follows": "Utilisateurs que vous ne suivez pas", - "notification_setting_followers": "Utilisateurs qui vous suivent", - "notification_setting_non_followers": "Utilisateurs qui ne vous suivent pas", - "notification_mutes": "Pour stopper la récéption de notifications d'un utilisateur particulier, utilisez un masquage.", - "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": { - "switcher": { - "keep_color": "Garder les couleurs", - "keep_shadows": "Garder les ombres", - "keep_opacity": "Garder la transparence", - "keep_roundness": "Garder la rondeur", - "keep_fonts": "Garder les polices", - "save_load_hint": "L'option « Garder » préserve les options activés en cours lors de la séléction ou chargement des thèmes, il sauve aussi les dites options lors de l'export d'un thème. Quand toutes les cases sont décochés, exporter un thème sauvera tout.", - "reset": "Remise à zéro", - "clear_all": "Tout vider", - "clear_opacity": "Vider la transparence" - }, - "common": { - "color": "Couleur", - "opacity": "Transparence", - "contrast": { - "hint": "Le ratio de contraste est {ratio}, il {level} {context}", - "level": { - "aa": "répond aux directives de niveau AA (minimum)", - "aaa": "répond aux directives de niveau AAA (recommandé)", - "bad": "ne réponds à aucune directive d'accessibilité" - }, - "context": { - "18pt": "pour texte large (19pt+)", - "text": "pour texte" - } - } - }, - "common_colors": { - "_tab_label": "Commun", - "main": "Couleurs communes", - "foreground_hint": "Voir l'onglet « Avancé » pour plus de contrôle détaillé", - "rgbo": "Icônes, accents, badges" - }, - "advanced_colors": { - "_tab_label": "Avancé", - "alert": "Fond d'alerte", - "alert_error": "Erreur", - "badge": "Fond de badge", - "badge_notification": "Notification", - "panel_header": "Entête de panneau", - "top_bar": "Barre du haut", - "borders": "Bordures", - "buttons": "Boutons", - "inputs": "Champs de saisie", - "faint_text": "Texte en fondu" - }, - "radii": { - "_tab_label": "Rondeur" - }, - "shadows": { - "_tab_label": "Ombres et éclairage", - "component": "Composant", - "override": "Surcharger", - "shadow_id": "Ombre #{value}", - "blur": "Flou", - "spread": "Dispersion", - "inset": "Interne", - "hint": "Pour les ombres, vous pouvez aussi utiliser --variable comme valeur de couleur en CSS3. Veuillez noter que spécifier la transparence ne fonctionnera pas dans ce cas.", - "filter_hint": { - "always_drop_shadow": "Attention, cette ombre utilise toujours {0} quand le navigateur le supporte.", - "drop_shadow_syntax": "{0} ne supporte pas le paramètre {1} et mot-clé {2}.", - "avatar_inset": "Veuillez noter que combiner a la fois les ombres internes et non-internes sur les avatars peut fournir des résultats innatendus 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}" - }, - "components": { - "panel": "Panneau", - "panelHeader": "En-tête de panneau", - "topBar": "Barre du haut", - "avatar": "Avatar utilisateur⋅ice (dans la vue de profil)", - "avatarStatus": "Avatar utilisateur⋅ice (dans la vue de statuts)", - "popup": "Popups et infobulles", - "button": "Bouton", - "buttonHover": "Bouton (survol)", - "buttonPressed": "Bouton (cliqué)", - "buttonPressedHover": "Bouton (cliqué+survol)", - "input": "Champ de saisie" - } - }, - "fonts": { - "_tab_label": "Polices", - "help": "Sélectionnez la police à utiliser pour les éléments de l'UI. Pour « personnalisé » vous avez à entrer le nom exact de la police comme il apparaît dans le système.", - "components": { - "interface": "Interface", - "input": "Champs de saisie", - "post": "Post text", - "postCode": "Texte à taille fixe dans un article (texte enrichi)" - }, - "family": "Nom de la police", - "size": "Taille (en px)", - "weight": "Poid (gras)", - "custom": "Personnalisé" - }, - "preview": { - "header": "Prévisualisation", - "content": "Contenu", - "error": "Exemple d'erreur", - "button": "Bouton", - "text": "Un certain nombre de {0} et {1}", - "mono": "contenu", - "input": "Je viens juste d’atterrir à L.A.", - "faint_link": "manuel utile", - "fine_print": "Lisez notre {0} pour n'apprendre rien d'utile !", - "header_faint": "Tout va bien", - "checkbox": "J'ai survolé les conditions d'utilisation", - "link": "un petit lien sympa" - } - }, - "version": { - "title": "Version", - "backend_version": "Version du Backend", - "frontend_version": "Version du Frontend" - } - }, - "timeline": { - "collapse": "Fermer", - "conversation": "Conversation", - "error_fetching": "Erreur en cherchant les mises à jour", - "load_older": "Afficher plus", - "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", - "up_to_date": "À jour", - "no_more_statuses": "Pas plus de statuts", - "no_statuses": "Aucun statuts" - }, - "status": { - "favorites": "Favoris", - "repeats": "Partages", - "delete": "Supprimer statuts", - "pin": "Agraffer sur le profil", - "unpin": "Dégraffer du profil", - "pinned": "Agraffé", - "delete_confirm": "Voulez-vous vraiment supprimer ce statuts ?", - "reply_to": "Réponse à", - "replies_list": "Réponses:" - }, - "user_card": { - "approve": "Accepter", - "block": "Bloquer", - "blocked": "Bloqué !", - "deny": "Rejeter", - "favorites": "Favoris", - "follow": "Suivre", - "follow_sent": "Demande envoyée !", - "follow_progress": "Demande en cours…", - "follow_again": "Renvoyer la demande ?", - "follow_unfollow": "Désabonner", - "followees": "Suivis", - "followers": "Vous suivent", - "following": "Suivi !", - "follows_you": "Vous suit !", - "its_you": "C'est vous !", - "media": "Media", - "mute": "Masquer", - "muted": "Masqué", - "per_day": "par jour", - "remote_follow": "Suivre d'une autre instance", - "report": "Signalement", - "statuses": "Statuts", - "unblock": "Débloquer", - "unblock_progress": "Déblocage…", - "block_progress": "Blocage…", - "unmute": "Démasquer", - "unmute_progress": "Démasquage…", - "mute_progress": "Masquage…", - "admin_menu": { - "moderation": "Moderation", - "grant_admin": "Promouvoir Administrateur⋅ice", - "revoke_admin": "Dégrader Administrateur⋅ice", - "grant_moderator": "Promouvoir Modérateur⋅ice", - "revoke_moderator": "Dégrader Modérateur⋅ice", - "activate_account": "Activer le compte", - "deactivate_account": "Désactiver le compte", - "delete_account": "Supprimer le compte", - "force_nsfw": "Marquer tous les statuts comme NSFW", - "strip_media": "Supprimer les medias des statuts", - "force_unlisted": "Forcer les statuts à être délistés", - "sandbox": "Forcer les statuts à être visibles seuleument pour les abonné⋅e⋅s", - "disable_remote_subscription": "Interdir de s'abonner a l'utilisateur depuis l'instance distante", - "disable_any_subscription": "Interdir de s'abonner à l'utilisateur tout court", - "quarantine": "Interdir les statuts de l'utilisateur à fédérer", - "delete_user": "Supprimer l'utilisateur", - "delete_user_confirmation": "Êtes-vous absolument-sûr⋅e ? Cette action ne peut être annulée." - } - }, - "user_profile": { - "timeline_title": "Journal de l'utilisateur⋅ice", - "profile_does_not_exist": "Désolé, ce profil n'existe pas.", - "profile_loading_error": "Désolé, il y a eu une erreur au chargement du profil." - }, - "user_reporting": { - "title": "Signaler {0}", - "add_comment_description": "Ce signalement sera envoyé aux modérateur⋅ice⋅s de votre instance. Vous pouvez fournir une explication de pourquoi vous signalez ce compte ci-dessous :", - "additional_comments": "Commentaires additionnels", - "forward_description": "Le compte vient d'un autre serveur. Envoyer une copie du signalement à celui-ci aussi ?", - "forward_to": "Transmettre à {0}", - "submit": "Envoyer", - "generic_error": "Une erreur est survenue lors du traitement de votre requête." - }, - "who_to_follow": { - "more": "Plus", - "who_to_follow": "À qui s'abonner" - }, - "tool_tip": { - "media_upload": "Envoyer un media", - "repeat": "Répéter", - "reply": "Répondre", - "favorite": "Favoriser", - "user_settings": "Paramètres utilisateur" - }, - "upload": { - "error": { - "base": "L'envoi a échoué.", - "file_too_big": "Fichier trop gros [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", - "default": "Réessayez plus tard" - }, - "file_size_units": { - "B": "O", - "KiB": "KiO", - "MiB": "MiO", - "GiB": "GiO", - "TiB": "TiO" - } + "chat": { + "title": "Chat" + }, + "exporter": { + "export": "Exporter", + "processing": "En cours de traitement, vous pourrez bientôt télécharger votre fichier" + }, + "features_panel": { + "chat": "Chat", + "gopher": "Gopher", + "media_proxy": "Proxy média", + "scope_options": "Options de visibilité", + "text_limit": "Limite de texte", + "title": "Caractéristiques", + "who_to_follow": "Personnes à suivre" + }, + "finder": { + "error_fetching_user": "Erreur lors de la recherche de l'utilisateur·ice", + "find_user": "Chercher un-e utilisateur·ice" + }, + "general": { + "apply": "Appliquer", + "submit": "Envoyer", + "more": "Plus", + "generic_error": "Une erreur s'est produite", + "optional": "optionnel", + "show_more": "Montrer plus", + "show_less": "Montrer moins", + "cancel": "Annuler", + "disable": "Désactiver", + "enable": "Activer", + "confirm": "Confirmer", + "verify": "Vérifier", + "dismiss": "Rejeter" + }, + "image_cropper": { + "crop_picture": "Rogner l'image", + "save": "Sauvegarder", + "save_without_cropping": "Sauvegarder sans rogner", + "cancel": "Annuler" + }, + "importer": { + "submit": "Soumettre", + "success": "Importé avec succès.", + "error": "Une erreur est survenue pendant l'import de ce fichier." + }, + "login": { + "login": "Connexion", + "description": "Connexion avec OAuth", + "logout": "Déconnexion", + "password": "Mot de passe", + "placeholder": "p.e. lain", + "register": "S'inscrire", + "username": "Identifiant", + "hint": "Connectez-vous pour rejoindre la discussion", + "authentication_code": "Code d'authentification", + "enter_recovery_code": "Entrez un code de récupération", + "enter_two_factor_code": "Entrez un code à double authentification", + "recovery_code": "Code de récupération", + "heading": { + "totp": "Authentification à double authentification", + "recovery": "Récuperation de la double authentification" } + }, + "media_modal": { + "previous": "Précédent", + "next": "Suivant" + }, + "nav": { + "about": "À propos", + "back": "Retour", + "chat": "Chat local", + "friend_requests": "Demandes de suivi", + "mentions": "Notifications", + "interactions": "Interactions", + "dms": "Messages directs", + "public_tl": "Fil d'actualité public", + "timeline": "Fil d'actualité", + "twkn": "Ensemble du réseau connu", + "user_search": "Recherche d'utilisateur·ice", + "who_to_follow": "Qui suivre", + "preferences": "Préférences", + "search": "Recherche", + "administration": "Administration" + }, + "notifications": { + "broken_favorite": "Chargement d'un message inconnu…", + "favorited_you": "a aimé votre statut", + "followed_you": "a commencé à vous suivre", + "load_older": "Charger les notifications précédentes", + "notifications": "Notifications", + "read": "Lu !", + "repeated_you": "a partagé votre statut", + "no_more_notifications": "Aucune notification supplémentaire", + "migrated_to": "a migré à", + "reacted_with": "a réagi avec {0}", + "follow_request": "veut vous suivre" + }, + "interactions": { + "favs_repeats": "Partages et favoris", + "follows": "Nouveaux suivis", + "load_older": "Chargez d'anciennes interactions", + "moves": "Migrations de comptes" + }, + "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 le média comme sensible", + "content_type": { + "text/plain": "Texte brut", + "text/html": "HTML", + "text/markdown": "Markdown", + "text/bbcode": "BBCode" + }, + "content_warning": "Sujet (optionnel)", + "default": "Écrivez ici votre prochain statut.", + "direct_warning_to_all": "Ce message sera visible pour toutes les personnes mentionnées.", + "direct_warning_to_first_only": "Ce message sera visible uniquement pour personnes mentionnées au début du message.", + "posting": "Envoi en cours", + "scope_notice": { + "public": "Ce statut sera visible par tout le monde", + "private": "Ce statut sera visible par seulement vos abonné⋅e⋅s", + "unlisted": "Ce statut ne sera pas visible dans le Fil d'actualité public et l'Ensemble du réseau connu" + }, + "scope": { + "direct": "Direct - N'envoyer qu'aux personnes mentionnées", + "private": "Abonné·e·s uniquement - Seul·e·s vos abonné·e·s verront vos billets", + "public": "Publique - Afficher dans les fils publics", + "unlisted": "Non-Listé - Ne pas afficher dans les fils publics" + } + }, + "registration": { + "bio": "Biographie", + "email": "Adresse mail", + "fullname": "Pseudonyme", + "password_confirm": "Confirmation du mot de passe", + "registration": "Inscription", + "token": "Jeton d'invitation", + "captcha": "CAPTCHA", + "new_captcha": "Cliquez sur l'image pour avoir un nouveau captcha", + "username_placeholder": "p.e. lain", + "fullname_placeholder": "p.e. Lain Iwakura", + "bio_placeholder": "p.e.\nSalut, je suis Lain\nJe suis une héroïne d'animé qui vit dans une banlieue japonaise. Vous me connaissez peut-être du Wired.", + "validations": { + "username_required": "ne peut pas être laissé vide", + "fullname_required": "ne peut pas être laissé vide", + "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" + } + }, + "selectable_list": { + "select_all": "Tout selectionner" + }, + "settings": { + "app_name": "Nom de l'application", + "security": "Sécurité", + "enter_current_password_to_confirm": "Entrez votre mot de passe actuel pour confirmer votre identité", + "mfa": { + "otp": "OTP", + "setup_otp": "Configurer OTP", + "wait_pre_setup_otp": "préconfiguration OTP", + "confirm_and_enable": "Confirmer & activer OTP", + "title": "Double authentification", + "generate_new_recovery_codes": "Générer de nouveaux codes de récupération", + "warning_of_generate_new_codes": "Quand vous générez de nouveauc codes de récupération, vos anciens codes ne fonctionnerons plus.", + "recovery_codes": "Codes de récupération.", + "waiting_a_recovery_codes": "Réception des codes de récupération…", + "recovery_codes_warning": "Écrivez les codes ou sauvez les quelquepart sécurisé - sinon vous ne les verrez plus jamais. Si vous perdez l'accès à votre application de double authentification et codes de récupération vous serez vérouillé en dehors de votre compte.", + "authentication_methods": "Methodes d'authentification", + "scan": { + "title": "Scanner", + "desc": "En utilisant votre application de double authentification, scannez ce QR code ou entrez la clé textuelle :", + "secret_code": "Clé" + }, + "verify": { + "desc": "Pour activer la double authentification, entrez le code depuis votre application :" + } + }, + "attachmentRadius": "Pièces jointes", + "attachments": "Pièces jointes", + "autoload": "Charger la suite automatiquement une fois le bas de la page atteint", + "avatar": "Avatar", + "avatarAltRadius": "Avatars (Notifications)", + "avatarRadius": "Avatars", + "background": "Arrière-plan", + "bio": "Biographie", + "block_export": "Export des comptes bloqués", + "block_export_button": "Export des comptes bloqués vers un fichier csv", + "block_import": "Import des comptes bloqués", + "block_import_error": "Erreur lors de l'import des comptes bloqués", + "blocks_imported": "Blocks importés ! Le traitement va prendre un moment.", + "blocks_tab": "Bloqué·e·s", + "btnRadius": "Boutons", + "cBlue": "Bleu (répondre, suivre)", + "cGreen": "Vert (partager)", + "cOrange": "Orange (aimer)", + "cRed": "Rouge (annuler)", + "change_password": "Changez votre mot de passe", + "change_password_error": "Il y a eu un problème pour changer votre mot de passe.", + "changed_password": "Mot de passe modifié avec succès !", + "collapse_subject": "Réduire les messages avec des sujets", + "composing": "Composition", + "confirm_new_password": "Confirmation du nouveau mot de passe", + "current_avatar": "Avatar actuel", + "current_password": "Mot de passe actuel", + "current_profile_banner": "Bannière de profil actuelle", + "data_import_export_tab": "Import / Export des Données", + "default_vis": "Visibilité par défaut", + "delete_account": "Supprimer le compte", + "delete_account_description": "Supprimer définitivement vos données et désactiver votre compte.", + "delete_account_error": "Il y a eu un problème lors de la tentative de suppression de votre compte. Si le problème persiste, contactez l'administrateur⋅ice de cette instance.", + "delete_account_instructions": "Indiquez votre mot de passe ci-dessous pour confirmer la suppression de votre compte.", + "avatar_size_instruction": "La taille minimale recommandée pour l'image de l'avatar est de 150x150 pixels.", + "export_theme": "Enregistrer le thème", + "filtering": "Filtre", + "filtering_explanation": "Tous les statuts contenant ces mots seront masqués. Un mot par ligne", + "follow_export": "Exporter les abonnements", + "follow_export_button": "Exporter les abonnements en csv", + "follow_import": "Importer des abonnements", + "follow_import_error": "Erreur lors de l'importation des abonnements", + "follows_imported": "Abonnements importés ! Le traitement peut prendre un moment.", + "foreground": "Premier plan", + "general": "Général", + "hide_attachments_in_convo": "Masquer les pièces jointes dans les conversations", + "hide_attachments_in_tl": "Masquer les pièces jointes dans le journal", + "hide_muted_posts": "Masquer les statuts des utilisateurs masqués", + "max_thumbnails": "Nombre maximum de miniatures par statuts", + "hide_isp": "Masquer le panneau spécifique a l'instance", + "preload_images": "Précharger les images", + "use_one_click_nsfw": "Ouvrir les pièces-jointes NSFW avec un seul clic", + "hide_post_stats": "Masquer les statistiques de publication (le nombre de favoris)", + "hide_user_stats": "Masquer les statistiques de profil (le nombre d'amis)", + "hide_filtered_statuses": "Masquer les statuts filtrés", + "import_blocks_from_a_csv_file": "Importer les blocages depuis un fichier csv", + "import_followers_from_a_csv_file": "Importer des abonnements depuis un fichier csv", + "import_theme": "Charger le thème", + "inputRadius": "Champs de texte", + "checkboxRadius": "Cases à cocher", + "instance_default": "(default : {value})", + "instance_default_simple": "(default)", + "interface": "Interface", + "interfaceLanguage": "Langue de l'interface", + "invalid_theme_imported": "Le fichier sélectionné n'est pas un thème Pleroma pris en charge. Aucun changement n'a été apporté à votre thème.", + "limited_availability": "Non disponible dans votre navigateur", + "links": "Liens", + "lock_account_description": "Limitez votre compte aux abonnés acceptés uniquement", + "loop_video": "Vidéos en boucle", + "loop_video_silent_only": "Boucle uniquement les vidéos sans le son (les « gifs » de Mastodon)", + "mutes_tab": "Comptes silenciés", + "play_videos_in_modal": "Jouer les vidéos directement dans le visionneur de médias", + "use_contain_fit": "Ne pas rogner les miniatures des pièces-jointes", + "name": "Nom", + "name_bio": "Nom & Bio", + "new_password": "Nouveau mot de passe", + "notification_visibility": "Types de notifications à afficher", + "notification_visibility_follows": "Abonnements", + "notification_visibility_likes": "J'aime", + "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", + "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'Administrateur⋅ice sur mon profil", + "show_moderator_badge": "Afficher le badge de Modérateur⋅ice sur mon profil", + "nsfw_clickthrough": "Masquer les images marquées comme contenu adulte ou sensible", + "oauth_tokens": "Jetons OAuth", + "token": "Jeton", + "refresh_token": "Rafraichir le jeton", + "valid_until": "Valable jusque", + "revoke_token": "Révoquer", + "panelRadius": "Fenêtres", + "pause_on_unfocused": "Suspendre le streaming lorsque l'onglet n'est pas actif", + "presets": "Thèmes prédéfinis", + "profile_background": "Image de fond", + "profile_banner": "Bannière de profil", + "profile_tab": "Profil", + "radii_help": "Vous pouvez ici choisir le niveau d'arrondi des angles de l'interface (en pixels)", + "replies_in_timeline": "Réponses au journal", + "reply_link_preview": "Afficher un aperçu lors du survol de liens vers une réponse", + "reply_visibility_all": "Montrer toutes les réponses", + "reply_visibility_following": "Afficher uniquement les réponses adressées à moi ou aux personnes que je suis", + "reply_visibility_self": "Afficher uniquement les réponses adressées à moi", + "autohide_floating_post_button": "Automatiquement cacher le bouton de Nouveau Statut (sur mobile)", + "saving_err": "Erreur lors de l'enregistrement des paramètres", + "saving_ok": "Paramètres enregistrés", + "search_user_to_block": "Rechercher qui vous voulez bloquer", + "search_user_to_mute": "Rechercher qui vous voulez masquer", + "security_tab": "Sécurité", + "scope_copy": "Garder la même visibilité en répondant (les DMs restent toujours des DMs)", + "minimal_scopes_mode": "Rétrécir les options de séléction de la portée", + "set_new_avatar": "Changer d'avatar", + "set_new_profile_background": "Changer d'image de fond", + "set_new_profile_banner": "Changer de bannière", + "settings": "Paramètres", + "subject_input_always_show": "Toujours copier le champ de sujet", + "subject_line_behavior": "Copier le sujet en répondant", + "subject_line_email": "Similaire au courriel : « re : sujet »", + "subject_line_mastodon": "Comme mastodon : copier tel quel", + "subject_line_noop": "Ne pas copier", + "post_status_content_type": "Type de contenu du statuts", + "stop_gifs": "N'animer les GIFS que lors du survol du curseur de la souris", + "streaming": "Charger automatiquement les nouveaux statuts lorsque vous êtes au haut de la page", + "text": "Texte", + "theme": "Thème", + "theme_help": "Spécifiez des codes couleur hexadécimaux (#rrvvbb) pour personnaliser les couleurs du thème.", + "theme_help_v2_1": "Vous pouvez aussi surcharger certaines couleurs de composants et transparence via la case à cocher, utilisez le bouton « Vider tout » pour effacer toutes les surcharges.", + "theme_help_v2_2": "Les icônes sous certaines des entrées ont un indicateur de contraste du fond/texte, survolez les pour plus d'informations détailles. Veuillez garder a l'esprit que lors de l'utilisation de transparence l'indicateur de contraste indique le pire des cas.", + "tooltipRadius": "Info-bulles/alertes", + "upload_a_photo": "Envoyer une photo", + "user_settings": "Paramètres utilisateur", + "values": { + "false": "non", + "true": "oui" + }, + "notifications": "Notifications", + "notification_setting": "Reçevoir les notifications de :", + "notification_setting_follows": "Utilisateurs que vous suivez", + "notification_setting_non_follows": "Utilisateurs que vous ne suivez pas", + "notification_setting_followers": "Utilisateurs qui vous suivent", + "notification_setting_non_followers": "Utilisateurs qui ne vous suivent pas", + "notification_mutes": "Pour stopper la récéption de notifications d'un utilisateur particulier, utilisez un masquage.", + "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": { + "switcher": { + "keep_color": "Garder les couleurs", + "keep_shadows": "Garder les ombres", + "keep_opacity": "Garder la transparence", + "keep_roundness": "Garder la rondeur", + "keep_fonts": "Garder les polices", + "save_load_hint": "L'option « Garder » préserve les options activés en cours lors de la séléction ou chargement des thèmes, il sauve aussi les dites options lors de l'export d'un thème. Quand toutes les cases sont décochés, exporter un thème sauvera tout.", + "reset": "Remise à zéro", + "clear_all": "Tout vider", + "clear_opacity": "Vider la transparence", + "load_theme": "Charger le thème", + "use_snapshot": "Ancienne version", + "help": { + "upgraded_from_v2": "PleromaFE à été mis à jour, le thème peut être un peu différent que dans vos souvenirs.", + "v2_imported": "Le fichier que vous avez importé vient d'un version antérieure. Nous essayons de maximizer la compatibilité mais il peu y avoir quelques incohérences.", + "future_version_imported": "Le fichier importé viens d'une version postérieure de PleromaFE.", + "older_version_imported": "Le fichier importé viens d'une version antérieure de PleromaFE.", + "snapshot_source_mismatch": "Conflict de version : Probablement due à un retour arrière puis remise à jour de la version de PleromaFE, si vous avez charger le thème en utilisant une version antérieure vous voulez probablement utiliser la version antérieure, autrement utiliser la version postérieure.", + "migration_napshot_gone": "Pour une raison inconnue l'instantané est manquant, des parties peuvent rendre différentes que dans vos souvenirs.", + "migration_snapshot_ok": "Pour être sûr un instantanée du thème à été chargé. Vos pouvez essayer de charger ses données.", + "fe_downgraded": "Retour en arrière de la version de PleromaFE.", + "fe_upgraded": "Le moteur de thème PleromaFE à été mis à jour après un changement de version.", + "snapshot_missing": "Aucun instantané du thème à été trouvé dans le fichier, il peut y avoir un rendu différent à la vision originelle." + }, + "keep_as_is": "Garder tel-quel", + "use_source": "Nouvelle version" + }, + "common": { + "color": "Couleur", + "opacity": "Transparence", + "contrast": { + "hint": "Le ratio de contraste est {ratio}, il {level} {context}", + "level": { + "aa": "répond aux directives de niveau AA (minimum)", + "aaa": "répond aux directives de niveau AAA (recommandé)", + "bad": "ne réponds à aucune directive d'accessibilité" + }, + "context": { + "18pt": "pour texte large (19pt+)", + "text": "pour texte" + } + } + }, + "common_colors": { + "_tab_label": "Commun", + "main": "Couleurs communes", + "foreground_hint": "Voir l'onglet « Avancé » pour plus de contrôle détaillé", + "rgbo": "Icônes, accents, badges" + }, + "advanced_colors": { + "_tab_label": "Avancé", + "alert": "Fond d'alerte", + "alert_error": "Erreur", + "badge": "Fond de badge", + "badge_notification": "Notification", + "panel_header": "Entête de panneau", + "top_bar": "Barre du haut", + "borders": "Bordures", + "buttons": "Boutons", + "inputs": "Champs de saisie", + "faint_text": "Texte en fondu", + "underlay": "sous-calque", + "pressed": "Appuyé", + "alert_warning": "Avertissement", + "alert_neutral": "Neutre", + "post": "Messages/Bios des comptes", + "poll": "Graphique de Sondage", + "icons": "Icônes", + "selectedPost": "Message sélectionné", + "selectedMenu": "Objet sélectionné du menu", + "disabled": "Désactivé", + "tabs": "Onglets", + "toggled": "(Dés)activé", + "highlight": "Éléments mis en valeur", + "popover": "Infobulles, menus" + }, + "radii": { + "_tab_label": "Rondeur" + }, + "shadows": { + "_tab_label": "Ombres et éclairage", + "component": "Composant", + "override": "Surcharger", + "shadow_id": "Ombre #{value}", + "blur": "Flou", + "spread": "Dispersion", + "inset": "Interne", + "hint": "Pour les ombres, vous pouvez aussi utiliser --variable comme valeur de couleur en CSS3. Veuillez noter que spécifier la transparence ne fonctionnera pas dans ce cas.", + "filter_hint": { + "always_drop_shadow": "Attention, cette ombre utilise toujours {0} quand le navigateur le supporte.", + "drop_shadow_syntax": "{0} ne supporte pas le paramètre {1} et mot-clé {2}.", + "avatar_inset": "Veuillez noter que combiner a la fois les ombres internes et non-internes sur les avatars peut fournir des résultats innatendus 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}" + }, + "components": { + "panel": "Panneau", + "panelHeader": "En-tête de panneau", + "topBar": "Barre du haut", + "avatar": "Avatar utilisateur⋅ice (dans la vue de profil)", + "avatarStatus": "Avatar utilisateur⋅ice (dans la vue de statuts)", + "popup": "Popups et infobulles", + "button": "Bouton", + "buttonHover": "Bouton (survol)", + "buttonPressed": "Bouton (cliqué)", + "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." + }, + "fonts": { + "_tab_label": "Polices", + "help": "Sélectionnez la police à utiliser pour les éléments de l'UI. Pour « personnalisé » vous avez à entrer le nom exact de la police comme il apparaît dans le système.", + "components": { + "interface": "Interface", + "input": "Champs de saisie", + "post": "Post text", + "postCode": "Texte à taille fixe dans un article (texte enrichi)" + }, + "family": "Nom de la police", + "size": "Taille (en px)", + "weight": "Poid (gras)", + "custom": "Personnalisé" + }, + "preview": { + "header": "Prévisualisation", + "content": "Contenu", + "error": "Exemple d'erreur", + "button": "Bouton", + "text": "Un certain nombre de {0} et {1}", + "mono": "contenu", + "input": "Je viens juste d’atterrir à L.A.", + "faint_link": "manuel utile", + "fine_print": "Lisez notre {0} pour n'apprendre rien d'utile !", + "header_faint": "Tout va bien", + "checkbox": "J'ai survolé les conditions d'utilisation", + "link": "un petit lien sympa" + } + }, + "version": { + "title": "Version", + "backend_version": "Version du Backend", + "frontend_version": "Version du Frontend" + }, + "change_email": "Changer de courriel", + "domain_mutes": "Domaines", + "pad_emoji": "Rajouter un espace autour de l'émoji après l’avoir choisit", + "notification_visibility_emoji_reactions": "Réactions", + "hide_follows_count_description": "Masquer le nombre de suivis", + "useStreamingApiWarning": "(Non recommandé, expérimental, connu pour rater des messages)", + "type_domains_to_mute": "Écrire les domaines à masquer", + "fun": "Rigolo", + "greentext": "greentexting", + "allow_following_move": "Suivre automatiquement quand ce compte migre", + "change_email_error": "Il y a eu un problème pour charger votre courriel.", + "changed_email": "Courriel changé avec succès !", + "discoverable": "Permettre de découvrir ce compte dans les résultats de recherche web et autres services", + "emoji_reactions_on_timeline": "Montrer les émojis-réactions dans le flux", + "new_email": "Nouveau courriel", + "notification_visibility_moves": "Migrations de compte", + "user_mutes": "Comptes", + "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é", + "hide_followers_count_description": "Masquer le nombre d'abonnés", + "accent": "Accent" + }, + "timeline": { + "collapse": "Fermer", + "conversation": "Conversation", + "error_fetching": "Erreur en cherchant les mises à jour", + "load_older": "Afficher plus", + "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", + "up_to_date": "À jour", + "no_more_statuses": "Pas plus de statuts", + "no_statuses": "Aucun statuts" + }, + "status": { + "favorites": "Favoris", + "repeats": "Partages", + "delete": "Supprimer statuts", + "pin": "Agraffer sur le profil", + "unpin": "Dégraffer du profil", + "pinned": "Agraffé", + "delete_confirm": "Voulez-vous vraiment supprimer ce statuts ?", + "reply_to": "Réponse à", + "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" + }, + "user_card": { + "approve": "Accepter", + "block": "Bloquer", + "blocked": "Bloqué !", + "deny": "Rejeter", + "favorites": "Favoris", + "follow": "Suivre", + "follow_sent": "Demande envoyée !", + "follow_progress": "Demande en cours…", + "follow_again": "Renvoyer la demande ?", + "follow_unfollow": "Désabonner", + "followees": "Suivis", + "followers": "Vous suivent", + "following": "Suivi !", + "follows_you": "Vous suit !", + "its_you": "C'est vous !", + "media": "Media", + "mute": "Masquer", + "muted": "Masqué", + "per_day": "par jour", + "remote_follow": "Suivre d'une autre instance", + "report": "Signalement", + "statuses": "Statuts", + "unblock": "Débloquer", + "unblock_progress": "Déblocage…", + "block_progress": "Blocage…", + "unmute": "Démasquer", + "unmute_progress": "Démasquage…", + "mute_progress": "Masquage…", + "admin_menu": { + "moderation": "Moderation", + "grant_admin": "Promouvoir Administrateur⋅ice", + "revoke_admin": "Dégrader Administrateur⋅ice", + "grant_moderator": "Promouvoir Modérateur⋅ice", + "revoke_moderator": "Dégrader Modérateur⋅ice", + "activate_account": "Activer le compte", + "deactivate_account": "Désactiver le compte", + "delete_account": "Supprimer le compte", + "force_nsfw": "Marquer tous les statuts comme NSFW", + "strip_media": "Supprimer les medias des statuts", + "force_unlisted": "Forcer les statuts à être délistés", + "sandbox": "Forcer les statuts à être visibles seuleument pour les abonné⋅e⋅s", + "disable_remote_subscription": "Interdir de s'abonner a l'utilisateur depuis l'instance distante", + "disable_any_subscription": "Interdir de s'abonner à l'utilisateur tout court", + "quarantine": "Interdir les statuts de l'utilisateur à fédérer", + "delete_user": "Supprimer l'utilisateur", + "delete_user_confirmation": "Êtes-vous absolument-sûr⋅e ? Cette action ne peut être annulée." + }, + "mention": "Mention", + "hidden": "Caché", + "subscribe": "Abonner", + "unsubscribe": "Désabonner", + "hide_repeats": "Cacher les partages", + "show_repeats": "Montrer les partages" + }, + "user_profile": { + "timeline_title": "Journal de l'utilisateur⋅ice", + "profile_does_not_exist": "Désolé, ce profil n'existe pas.", + "profile_loading_error": "Désolé, il y a eu une erreur au chargement du profil." + }, + "user_reporting": { + "title": "Signaler {0}", + "add_comment_description": "Ce signalement sera envoyé aux modérateur⋅ice⋅s de votre instance. Vous pouvez fournir une explication de pourquoi vous signalez ce compte ci-dessous :", + "additional_comments": "Commentaires additionnels", + "forward_description": "Le compte vient d'un autre serveur. Envoyer une copie du signalement à celui-ci aussi ?", + "forward_to": "Transmettre à {0}", + "submit": "Envoyer", + "generic_error": "Une erreur est survenue lors du traitement de votre requête." + }, + "who_to_follow": { + "more": "Plus", + "who_to_follow": "À qui s'abonner" + }, + "tool_tip": { + "media_upload": "Envoyer un media", + "repeat": "Répéter", + "reply": "Répondre", + "favorite": "Favoriser", + "user_settings": "Paramètres utilisateur", + "add_reaction": "Ajouter une réaction", + "accept_follow_request": "Accepter la demande de suivit", + "reject_follow_request": "Rejeter la demande de suivit" + }, + "upload": { + "error": { + "base": "L'envoi a échoué.", + "file_too_big": "Fichier trop gros [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", + "default": "Réessayez plus tard" + }, + "file_size_units": { + "B": "O", + "KiB": "KiO", + "MiB": "MiO", + "GiB": "GiO", + "TiB": "TiO" + } + }, + "about": { + "mrf": { + "keyword": { + "reject": "Rejeté", + "replace": "Remplacer", + "keyword_policies": "Politiques par mot-clés", + "ftl_removal": "Suppression du flux \"Ensemble du réseau connu\"", + "is_replaced_by": "→" + }, + "simple": { + "simple_policies": "Politiques par instances", + "accept": "Accepter", + "accept_desc": "Cette instance accepte des messages seulement depuis ces instances :", + "reject": "Rejeter", + "reject_desc": "Cette instance n'acceptera pas de message de ces instances :", + "quarantine": "Quarantaine", + "quarantine_desc": "Cette instance enverras seulement des messages publics à ces instances :", + "ftl_removal_desc": "Cette instance supprime ces instance du flux fédéré :", + "media_removal": "Suppression multimédia", + "media_removal_desc": "Cette instance supprime le contenu multimédia des instances suivantes :", + "media_nsfw": "Force le contenu multimédia comme sensible", + "ftl_removal": "Suppression du flux fédéré", + "media_nsfw_desc": "Cette instance force le contenu multimédia comme sensible pour les messages des instances suivantes :" + }, + "federation": "Fédération", + "mrf_policies": "Politiques MRF activées", + "mrf_policies_desc": "Les politiques MRF modifient la fédération entre les instances. Les politiques suivantes sont activées :" + }, + "staff": "Staff" + }, + "domain_mute_card": { + "mute": "Muet", + "mute_progress": "Masquage…", + "unmute": "Démasquer", + "unmute_progress": "Démasquage…" + }, + "polls": { + "add_poll": "Ajouter un Sondage", + "add_option": "Ajouter une option", + "option": "Option", + "votes": "votes", + "type": "Type de Sondage", + "single_choice": "Choix unique", + "multiple_choices": "Choix multiples", + "expiry": "Age du sondage", + "expires_in": "Fin du sondage dans {0}", + "not_enough_options": "Trop peu d'options unique au sondage", + "vote": "Voter", + "expired": "Sondage terminé il y a {0}" + }, + "emoji": { + "emoji": "Émoji", + "search_emoji": "Rechercher un émoji", + "add_emoji": "Insérer un émoji", + "custom": "émoji personnalisé", + "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", + "keep_open": "Garder le sélecteur ouvert" + }, + "remote_user_resolver": { + "error": "Non trouvé.", + "searching_for": "Rechercher", + "remote_user_resolver": "Résolution de compte distant" + }, + "time": { + "minutes_short": "{0}min", + "second_short": "{0}s", + "day": "{0} jour", + "days": "{0} jours", + "months": "{0} mois", + "month_short": "{0}m", + "months_short": "{0}m", + "now": "tout de suite", + "now_short": "maintenant", + "second": "{0} seconde", + "seconds": "{0} secondes", + "seconds_short": "{0}s", + "day_short": "{0}j", + "days_short": "{0}j", + "hour": "{0} heure", + "hours": "{0} heures", + "hour_short": "{0}h", + "hours_short": "{0}h", + "in_future": "dans {0}", + "in_past": "il y a {0}", + "minute": "{0} minute", + "minutes": "{0} minutes", + "minute_short": "{0}min", + "month": "{0} mois", + "week": "{0} semaine", + "weeks": "{0} semaines", + "week_short": "{0}s", + "weeks_short": "{0}s", + "year": "{0} année", + "years": "{0} années", + "year_short": "{0}a", + "years_short": "{0}a" + }, + "search": { + "people": "Comptes", + "person_talking": "{count} personnes discutant", + "hashtags": "Mot-dièses", + "people_talking": "{count} personnes discutant", + "no_results": "Aucun résultats" + }, + "password_reset": { + "forgot_password": "Mot de passe oublié ?", + "check_email": "Vérifiez vos courriels pour le lien permettant de changer votre mot de passe.", + "password_reset_disabled": "Le changement de mot de passe est désactivé. Veuillez contacter l'administration de votre instance.", + "password_reset_required_but_mailer_is_disabled": "Vous devez changer votre mot de passe mais sont changement est désactivé. Veuillez contacter l’administration de votre instance.", + "password_reset": "Nouveau mot de passe", + "instruction": "Entrer votre address de courriel ou votre nom utilisateur. Nous enverrons un lien pour changer votre mot de passe.", + "placeholder": "Votre email ou nom d'utilisateur", + "return_home": "Retourner à la page d'accueil", + "not_found": "Email ou nom d'utilisateur inconnu.", + "too_many_requests": "Vos avez atteint la limite d'essais, essayez plus tard.", + "password_reset_required": "Vous devez changer votre mot de passe pour vous authentifier." + } } diff --git a/src/i18n/it.json b/src/i18n/it.json index f441292e..6c8be351 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -1,140 +1,357 @@ { "general": { "submit": "Invia", - "apply": "Applica" + "apply": "Applica", + "more": "Altro", + "generic_error": "Errore", + "optional": "facoltativo", + "show_more": "Mostra tutto", + "show_less": "Ripiega", + "dismiss": "Chiudi", + "cancel": "Annulla", + "disable": "Disabilita", + "enable": "Abilita", + "confirm": "Conferma", + "verify": "Verifica", + "peek": "Anteprima", + "close": "Chiudi", + "retry": "Riprova", + "error_retry": "Per favore, riprova", + "loading": "Carico…" }, "nav": { "mentions": "Menzioni", - "public_tl": "Sequenza temporale pubblica", - "timeline": "Sequenza temporale", - "twkn": "L'intera rete conosciuta", - "chat": "Chat Locale", - "friend_requests": "Richieste di Seguirti" + "public_tl": "Sequenza pubblica", + "timeline": "Sequenza personale", + "twkn": "Sequenza globale", + "chat": "Chat della stanza", + "friend_requests": "Vogliono seguirti", + "about": "Informazioni", + "administration": "Amministrazione", + "back": "Indietro", + "interactions": "Interazioni", + "dms": "Messaggi diretti", + "user_search": "Ricerca utenti", + "search": "Ricerca", + "who_to_follow": "Chi seguire", + "preferences": "Preferenze" }, "notifications": { "followed_you": "ti segue", "notifications": "Notifiche", - "read": "Leggi!", - "broken_favorite": "Stato sconosciuto, lo sto cercando...", - "favorited_you": "ha messo mi piace al tuo stato", - "load_older": "Carica notifiche più vecchie", - "repeated_you": "ha condiviso il tuo stato" + "read": "Letto!", + "broken_favorite": "Stato sconosciuto, lo sto cercando…", + "favorited_you": "ha gradito il tuo messaggio", + "load_older": "Carica notifiche precedenti", + "repeated_you": "ha condiviso il tuo messaggio", + "follow_request": "vuole seguirti", + "no_more_notifications": "Fine delle notifiche", + "migrated_to": "è migrato verso", + "reacted_with": "ha reagito con {0}" }, "settings": { "attachments": "Allegati", - "autoload": "Abilita caricamento automatico quando si raggiunge fondo pagina", - "avatar": "Avatar", + "autoload": "Abilita caricamento automatico quando raggiungi il fondo pagina", + "avatar": "Icona utente", "bio": "Introduzione", - "current_avatar": "Il tuo avatar attuale", - "current_profile_banner": "Il tuo banner attuale", + "current_avatar": "La tua icona attuale", + "current_profile_banner": "Il tuo stendardo attuale", "filtering": "Filtri", - "filtering_explanation": "Tutti i post contenenti queste parole saranno silenziati, uno per linea", + "filtering_explanation": "Tutti i post contenenti queste parole saranno silenziati, una per riga", "hide_attachments_in_convo": "Nascondi gli allegati presenti nelle conversazioni", - "hide_attachments_in_tl": "Nascondi gli allegati presenti nella sequenza temporale", + "hide_attachments_in_tl": "Nascondi gli allegati presenti nelle sequenze", "name": "Nome", - "name_bio": "Nome & Introduzione", - "nsfw_clickthrough": "Abilita il click per visualizzare gli allegati segnati come NSFW", + "name_bio": "Nome ed introduzione", + "nsfw_clickthrough": "Fai click per visualizzare gli allegati offuscati", "profile_background": "Sfondo della tua pagina", - "profile_banner": "Banner del tuo profilo", - "reply_link_preview": "Abilita il link per la risposta al passaggio del mouse", - "set_new_avatar": "Scegli un nuovo avatar", + "profile_banner": "Stendardo del tuo profilo", + "reply_link_preview": "Visualizza le risposte al passaggio del cursore", + "set_new_avatar": "Scegli una nuova icona", "set_new_profile_background": "Scegli un nuovo sfondo per la tua pagina", - "set_new_profile_banner": "Scegli un nuovo banner per il tuo profilo", + "set_new_profile_banner": "Scegli un nuovo stendardo per il tuo profilo", "settings": "Impostazioni", "theme": "Tema", "user_settings": "Impostazioni Utente", "attachmentRadius": "Allegati", - "avatarAltRadius": "Avatar (Notifiche)", - "avatarRadius": "Avatar", + "avatarAltRadius": "Icone utente (Notifiche)", + "avatarRadius": "Icone utente", "background": "Sfondo", "btnRadius": "Pulsanti", - "cBlue": "Blu (Rispondere, seguire)", - "cGreen": "Verde (Condividi)", - "cOrange": "Arancio (Mi piace)", - "cRed": "Rosso (Annulla)", - "change_password": "Cambia Password", + "cBlue": "Blu (risposte, seguire)", + "cGreen": "Verde (ripeti)", + "cOrange": "Arancione (gradire)", + "cRed": "Rosso (annulla)", + "change_password": "Cambia password", "change_password_error": "C'è stato un problema durante il cambiamento della password.", "changed_password": "Password cambiata correttamente!", - "collapse_subject": "Riduci post che hanno un oggetto", + "collapse_subject": "Ripiega messaggi con Oggetto", "confirm_new_password": "Conferma la nuova password", - "current_password": "Password attuale", - "data_import_export_tab": "Importa / Esporta Dati", - "default_vis": "Visibilità predefinita dei post", - "delete_account": "Elimina Account", - "delete_account_description": "Elimina definitivamente il tuo account e tutti i tuoi messaggi.", - "delete_account_error": "C'è stato un problema durante l'eliminazione del tuo account. Se il problema persiste contatta l'amministratore della tua istanza.", - "delete_account_instructions": "Digita la tua password nel campo sottostante per confermare l'eliminazione dell'account.", - "export_theme": "Salva settaggi", + "current_password": "La tua password attuale", + "data_import_export_tab": "Importa o esporta dati", + "default_vis": "Visibilità predefinita dei messaggi", + "delete_account": "Elimina profilo", + "delete_account_description": "Elimina definitivamente i tuoi dati e disattiva il tuo profilo.", + "delete_account_error": "C'è stato un problema durante l'eliminazione del tuo profilo. Se il problema persiste contatta l'amministratore della tua stanza.", + "delete_account_instructions": "Digita la tua password nel campo sottostante per confermare l'eliminazione del tuo profilo.", + "export_theme": "Salva impostazioni", "follow_export": "Esporta la lista di chi segui", - "follow_export_button": "Esporta la lista di chi segui in un file csv", + "follow_export_button": "Esporta la lista di chi segui in un file CSV", "follow_export_processing": "Sto elaborando, presto ti sarà chiesto di scaricare il tuo file", "follow_import": "Importa la lista di chi segui", "follow_import_error": "Errore nell'importazione della lista di chi segui", "follows_imported": "Importazione riuscita! L'elaborazione richiederà un po' di tempo.", - "foreground": "In primo piano", + "foreground": "Primo piano", "general": "Generale", - "hide_post_stats": "Nascondi statistiche dei post (es. il numero di mi piace)", - "hide_user_stats": "Nascondi statistiche dell'utente (es. il numero di chi ti segue)", - "import_followers_from_a_csv_file": "Importa una lista di chi segui da un file csv", - "import_theme": "Carica settaggi", + "hide_post_stats": "Nascondi statistiche dei messaggi (es. il numero di preferenze)", + "hide_user_stats": "Nascondi statistiche dell'utente (es. il numero dei tuoi seguaci)", + "import_followers_from_a_csv_file": "Importa una lista di chi segui da un file CSV", + "import_theme": "Carica impostazioni", "inputRadius": "Campi di testo", "instance_default": "(predefinito: {value})", - "interfaceLanguage": "Linguaggio dell'interfaccia", - "invalid_theme_imported": "Il file selezionato non è un file di tema per Pleroma supportato. Il tuo tema non è stato modificato.", + "interfaceLanguage": "Lingua dell'interfaccia", + "invalid_theme_imported": "Il file selezionato non è un tema supportato da Pleroma. Il tuo tema non è stato modificato.", "limited_availability": "Non disponibile nel tuo browser", "links": "Collegamenti", - "lock_account_description": "Limita il tuo account solo per contatti approvati", + "lock_account_description": "Limita il tuo account solo a seguaci approvati", "loop_video": "Riproduci video in ciclo continuo", - "loop_video_silent_only": "Riproduci solo video senza audio in ciclo continuo (es. le gif di Mastodon)", + "loop_video_silent_only": "Riproduci solo video senza audio in ciclo continuo (es. le \"gif\" di Mastodon)", "new_password": "Nuova password", "notification_visibility": "Tipi di notifiche da mostrare", "notification_visibility_follows": "Nuove persone ti seguono", - "notification_visibility_likes": "Mi piace", + "notification_visibility_likes": "Preferiti", "notification_visibility_mentions": "Menzioni", "notification_visibility_repeats": "Condivisioni", - "no_rich_text_description": "Togli la formattazione del testo da tutti i post", + "no_rich_text_description": "Togli la formattazione del testo da tutti i messaggi", "oauth_tokens": "Token OAuth", "token": "Token", "refresh_token": "Aggiorna token", "valid_until": "Valido fino a", - "revoke_token": "Revocare", + "revoke_token": "Revoca", "panelRadius": "Pannelli", - "pause_on_unfocused": "Metti in pausa l'aggiornamento continuo quando la scheda non è in primo piano", + "pause_on_unfocused": "Interrompi l'aggiornamento continuo mentre la scheda è in secondo piano", "presets": "Valori predefiniti", "profile_tab": "Profilo", - "radii_help": "Imposta l'arrotondamento dei bordi (in pixel)", - "replies_in_timeline": "Risposte nella sequenza temporale", + "radii_help": "Imposta il raggio degli angoli (in pixel)", + "replies_in_timeline": "Risposte nella sequenza personale", "reply_visibility_all": "Mostra tutte le risposte", - "reply_visibility_following": "Mostra solo le risposte dirette a me o agli utenti che seguo", - "reply_visibility_self": "Mostra solo risposte dirette a me", + "reply_visibility_following": "Mostra solo le risposte rivolte a me o agli utenti che seguo", + "reply_visibility_self": "Mostra solo risposte rivolte a me", "saving_err": "Errore nel salvataggio delle impostazioni", "saving_ok": "Impostazioni salvate", "security_tab": "Sicurezza", - "stop_gifs": "Riproduci GIF al passaggio del cursore del mouse", - "streaming": "Abilita aggiornamento automatico dei nuovi post quando si è in alto alla pagina", + "stop_gifs": "Riproduci GIF al passaggio del cursore", + "streaming": "Mostra automaticamente i nuovi messaggi quando sei in cima alla pagina", "text": "Testo", "theme_help": "Usa codici colore esadecimali (#rrggbb) per personalizzare il tuo schema di colori.", - "tooltipRadius": "Descrizioni/avvisi", + "tooltipRadius": "Suggerimenti/avvisi", "values": { "false": "no", - "true": "si" - } + "true": "sì" + }, + "avatar_size_instruction": "La taglia minima per l'icona personale è 150x150 pixel.", + "domain_mutes": "Domini", + "discoverable": "Permetti la scoperta di questo profilo da servizi di ricerca ed altro", + "composing": "Composizione", + "changed_email": "Email cambiata con successo!", + "change_email_error": "C'è stato un problema nel cambiare la tua email.", + "change_email": "Cambia email", + "blocks_tab": "Bloccati", + "blocks_imported": "Blocchi importati! Saranno elaborati a breve.", + "block_import_error": "Errore nell'importazione", + "block_import": "Importa blocchi", + "block_export_button": "Esporta i tuoi blocchi in un file CSV", + "block_export": "Esporta blocchi", + "allow_following_move": "Consenti", + "mfa": { + "verify": { + "desc": "Per abilitare l'autenticazione bifattoriale, inserisci il codice fornito dalla tua applicazione:" + }, + "scan": { + "secret_code": "Codice", + "desc": "Con la tua applicazione bifattoriale, acquisisci questo QR o inserisci il codice manualmente:", + "title": "Acquisisci" + }, + "authentication_methods": "Metodi di accesso", + "recovery_codes_warning": "Appuntati i codici o salvali in un posto sicuro, altrimenti rischi di non rivederli mai più. Se perderai l'accesso sia alla tua applicazione bifattoriale che ai codici di recupero non potrai più accedere al tuo profilo.", + "waiting_a_recovery_codes": "Ricevo codici di recupero…", + "recovery_codes": "Codici di recupero.", + "warning_of_generate_new_codes": "Alla generazione di nuovi codici di recupero, quelli vecchi saranno disattivati.", + "generate_new_recovery_codes": "Genera nuovi codici di recupero", + "title": "Accesso bifattoriale", + "confirm_and_enable": "Conferma ed abilita OTP", + "wait_pre_setup_otp": "preimposto OTP", + "setup_otp": "Imposta OTP", + "otp": "OTP" + }, + "enter_current_password_to_confirm": "Inserisci la tua password per identificarti", + "security": "Sicurezza", + "app_name": "Nome applicazione", + "style": { + "switcher": { + "help": { + "older_version_imported": "Il tema importato è stato creato per una versione precedente dell'interfaccia.", + "future_version_imported": "Il tema importato è stato creato per una versione più recente dell'interfaccia.", + "v2_imported": "Il tema importato è stato creato per una vecchia interfaccia. Non tutto potrebbe essere come prima.", + "upgraded_from_v2": "L'interfaccia è stata aggiornata, il tema potrebbe essere diverso da come lo intendevi.", + "migration_snapshot_ok": "Ho caricato l'anteprima del tema. Puoi provare a caricarne i contenuti.", + "fe_downgraded": "L'interfaccia è stata portata ad una versione precedente.", + "fe_upgraded": "Lo schema dei temi è stato aggiornato insieme all'interfaccia.", + "snapshot_missing": "Il tema non è provvisto di anteprima, quindi potrebbe essere diverso da come appare.", + "snapshot_present": "Tutti i valori sono sostituiti dall'anteprima del tema. Puoi invece caricare i suoi contenuti.", + "snapshot_source_mismatch": "Conflitto di versione: probabilmente l'interfaccia è stata portata ad una versione precedente e poi aggiornata di nuovo. Se hai modificato il tema con una versione precedente dell'interfaccia, usa la vecchia versione del tema, altrimenti puoi usare la nuova.", + "migration_napshot_gone": "Anteprima del tema non trovata, non tutto potrebbe essere come ricordi." + }, + "use_source": "Nuova versione", + "use_snapshot": "Versione precedente", + "keep_as_is": "Mantieni tal quale", + "load_theme": "Carica tema", + "clear_opacity": "Rimuovi opacità", + "clear_all": "Azzera tutto", + "reset": "Reimposta", + "save_load_hint": "Le opzioni \"mantieni\" conservano le impostazioni correnti quando selezioni o carichi un tema, e le salvano quando ne esporti uno. Quando nessuna casella è selezionata, tutte le impostazioni correnti saranno salvate nel tema.", + "keep_fonts": "Mantieni font", + "keep_roundness": "Mantieni vertici", + "keep_opacity": "Mantieni opacità", + "keep_shadows": "Mantieni ombre", + "keep_color": "Mantieni colori" + }, + "common": { + "opacity": "Opacità", + "color": "Colore", + "contrast": { + "context": { + "text": "per il testo", + "18pt": "per il testo grande (oltre 17pt)" + }, + "level": { + "bad": "non soddisfa le linee guida di alcun livello", + "aaa": "soddisfa le linee guida di livello AAA (ottimo)", + "aa": "soddisfa le linee guida di livello AA (sufficiente)" + }, + "hint": "Il rapporto di contrasto è {ratio}, e {level} {context}" + } + }, + "advanced_colors": { + "badge": "Sfondo medaglie", + "post": "Messaggi / Biografie", + "alert_neutral": "Neutro", + "alert_warning": "Attenzione", + "alert_error": "Errore", + "alert": "Sfondo degli avvertimenti", + "_tab_label": "Avanzate", + "tabs": "Etichette", + "disabled": "Disabilitato", + "selectedMenu": "Voce menù selezionata", + "selectedPost": "Messaggio selezionato", + "pressed": "Premuto", + "highlight": "Elementi evidenziati", + "icons": "Icone", + "poll": "Grafico sondaggi", + "underlay": "Sottostante", + "faint_text": "Testo sbiadito", + "inputs": "Campi d'immissione", + "buttons": "Pulsanti", + "borders": "Bordi", + "top_bar": "Barra superiore", + "panel_header": "Titolo pannello", + "badge_notification": "Notifica", + "popover": "Suggerimenti, menù, sbalzi" + }, + "common_colors": { + "rgbo": "Icone, accenti, medaglie", + "foreground_hint": "Seleziona l'etichetta \"Avanzate\" per controlli più fini", + "main": "Colori comuni", + "_tab_label": "Comuni" + }, + "shadows": { + "inset": "Includi", + "spread": "Spandi", + "blur": "Sfoca", + "shadow_id": "Ombra numero {value}", + "override": "Sostituisci", + "component": "Componente", + "_tab_label": "Luci ed ombre" + }, + "radii": { + "_tab_label": "Raggio" + } + }, + "enable_web_push_notifications": "Abilita notifiche web push", + "fun": "Divertimento", + "notification_mutes": "Per non ricevere notifiche da uno specifico utente, zittiscilo.", + "notification_setting_privacy_option": "Nascondi mittente e contenuti delle notifiche push", + "notification_setting_privacy": "Privacy", + "notification_setting_followers": "Utenti che ti seguono", + "notification_setting_non_followers": "Utenti che non ti seguono", + "notification_setting_non_follows": "Utenti che non segui", + "notification_setting_follows": "Utenti che segui", + "notification_setting": "Ricevi notifiche da:", + "notification_setting_filters": "Filtri", + "notifications": "Notifiche", + "greentext": "Frecce da meme", + "upload_a_photo": "Carica un'immagine", + "type_domains_to_mute": "Cerca domini da zittire", + "theme_help_v2_2": "Le icone dietro alcuni elementi sono indicatori del contrasto fra testo e sfondo, passaci sopra col puntatore per ulteriori informazioni. Se si usano delle trasparenze, questi indicatori mostrano il peggior caso possibile.", + "theme_help_v2_1": "Puoi anche forzare colore ed opacità di alcuni elementi selezionando la casella. Usa il pulsante \"Azzera\" per azzerare tutte le forzature.", + "useStreamingApiWarning": "(Sconsigliato, sperimentale, può saltare messaggi)", + "useStreamingApi": "Ricevi messaggi e notifiche in tempo reale", + "user_mutes": "Utenti", + "post_status_content_type": "Tipo di contenuto dei messaggi", + "subject_line_noop": "Non copiare", + "subject_line_mastodon": "Come in Mastodon: copia tal quale", + "subject_line_email": "Come nelle email: \"re: oggetto\"", + "subject_line_behavior": "Copia oggetto quando rispondi", + "subject_input_always_show": "Mostra sempre il campo Oggetto", + "minimal_scopes_mode": "Riduci opzioni di visibilità", + "scope_copy": "Risposte ereditano la visibilità (messaggi privati lo fanno sempre)", + "search_user_to_mute": "Cerca utente da zittire", + "search_user_to_block": "Cerca utente da bloccare", + "autohide_floating_post_button": "Nascondi automaticamente il pulsante di composizione (mobile)", + "show_moderator_badge": "Mostra l'insegna di moderatore sulla mia pagina", + "show_admin_badge": "Mostra l'insegna di amministratore sulla mia pagina", + "hide_followers_count_description": "Non mostrare quanti seguaci ho", + "hide_follows_count_description": "Non mostrare quanti utenti seguo", + "hide_followers_description": "Non mostrare i miei seguaci", + "hide_follows_description": "Non mostrare chi seguo", + "no_mutes": "Nessun utente zittito", + "no_blocks": "Nessun utente bloccato", + "notification_visibility_emoji_reactions": "Reazioni", + "notification_visibility_moves": "Migrazioni utenti", + "new_email": "Nuova email", + "use_contain_fit": "Non ritagliare le anteprime degli allegati", + "play_videos_in_modal": "Riproduci video in un riquadro a sbalzo", + "mutes_tab": "Zittiti", + "interface": "Interfaccia", + "instance_default_simple": "(predefinito)", + "checkboxRadius": "Caselle di selezione", + "import_blocks_from_a_csv_file": "Importa blocchi da un file CSV", + "hide_filtered_statuses": "Nascondi messaggi filtrati", + "use_one_click_nsfw": "Apri media offuscati con un solo click", + "preload_images": "Precarica immagini", + "hide_isp": "Nascondi pannello della stanza", + "max_thumbnails": "Numero massimo di anteprime per messaggio", + "hide_muted_posts": "Nascondi messaggi degli utenti zittiti", + "accent": "Accento", + "emoji_reactions_on_timeline": "Mostra emoji di reazione sulle sequenze", + "pad_emoji": "Affianca spazi agli emoji inseriti tramite selettore", + "notification_blocks": "Bloccando un utente non riceverai più le sue notifiche né lo seguirai più.", + "mutes_and_blocks": "Zittiti e bloccati" }, "timeline": { - "error_fetching": "Errore nel prelievo aggiornamenti", + "error_fetching": "Errore nell'aggiornamento", "load_older": "Carica messaggi più vecchi", "show_new": "Mostra nuovi", "up_to_date": "Aggiornato", "collapse": "Riduci", "conversation": "Conversazione", - "no_retweet_hint": "La visibilità del post è impostata solo per chi ti segue o messaggio diretto e non può essere condiviso", + "no_retweet_hint": "Il messaggio è diretto o solo per seguaci e non può essere condiviso", "repeated": "condiviso" }, "user_card": { "follow": "Segui", "followees": "Chi stai seguendo", - "followers": "Chi ti segue", - "following": "Lo stai seguendo!", + "followers": "Seguaci", + "following": "Seguìto!", "follows_you": "Ti segue!", "mute": "Silenzia", "muted": "Silenziato", @@ -152,9 +369,9 @@ "features_panel": { "chat": "Chat", "gopher": "Gopher", - "media_proxy": "Media proxy", - "scope_options": "Opzioni di visibilità", - "text_limit": "Lunghezza limite", + "media_proxy": "Proxy multimedia", + "scope_options": "Opzioni visibilità", + "text_limit": "Lunghezza massima", "title": "Caratteristiche", "who_to_follow": "Chi seguire" }, @@ -166,27 +383,48 @@ "login": "Accedi", "logout": "Disconnettiti", "password": "Password", - "placeholder": "es. lain", + "placeholder": "es. Lupo Lucio", "register": "Registrati", - "username": "Nome utente" + "username": "Nome utente", + "description": "Accedi con OAuth", + "hint": "Accedi per partecipare alla discussione", + "authentication_code": "Codice di autenticazione", + "enter_recovery_code": "Inserisci un codice di recupero", + "enter_two_factor_code": "Inserisci un codice two-factor", + "recovery_code": "Codice di recupero", + "heading": { + "totp": "Autenticazione two-factor", + "recovery": "Recupero two-factor" + } }, "post_status": { - "account_not_locked_warning": "Il tuo account non è {0}. Chiunque può seguirti e vedere i tuoi post riservati a chi ti segue.", - "account_not_locked_warning_link": "bloccato", - "attachments_sensitive": "Segna allegati come sensibili", + "account_not_locked_warning": "Il tuo profilo non è {0}. Chiunque può seguirti e vedere i tuoi messaggi riservati ai tuoi seguaci.", + "account_not_locked_warning_link": "protetto", + "attachments_sensitive": "Nascondi gli allegati", "content_type": { - "text/plain": "Testo normale" + "text/plain": "Testo normale", + "text/bbcode": "BBCode", + "text/markdown": "Markdown", + "text/html": "HTML" }, "content_warning": "Oggetto (facoltativo)", - "default": "Appena atterrato in L.A.", + "default": "Sono appena atterrato a Fiumicino.", "direct_warning": "Questo post sarà visibile solo dagli utenti menzionati.", - "posting": "Pubblica", + "posting": "Sto pubblicando", "scope": { - "direct": "Diretto - Pubblicato solo per gli utenti menzionati", - "private": "Solo per chi ti segue - Visibile solo da chi ti segue", - "public": "Pubblico - Visibile sulla sequenza temporale pubblica", - "unlisted": "Non elencato - Non visibile sulla sequenza temporale pubblica" - } + "direct": "Diretto - Visibile solo agli utenti menzionati", + "private": "Solo per seguaci - Visibile solo dai tuoi seguaci", + "public": "Pubblico - Visibile sulla sequenza pubblica", + "unlisted": "Non elencato - Non visibile sulla sequenza pubblica" + }, + "scope_notice": { + "unlisted": "Questo messaggio non sarà visibile sulla sequenza locale né su quella pubblica", + "private": "Questo messaggio sarà visibile solo ai tuoi seguaci", + "public": "Questo messaggio sarà visibile a tutti" + }, + "direct_warning_to_first_only": "Questo messaggio sarà visibile solo agli utenti menzionati all'inizio.", + "direct_warning_to_all": "Questo messaggio sarà visibile a tutti i menzionati.", + "new_status": "Nuovo messaggio" }, "registration": { "bio": "Introduzione", @@ -194,13 +432,120 @@ "fullname": "Nome visualizzato", "password_confirm": "Conferma password", "registration": "Registrazione", - "token": "Codice d'invito" + "token": "Codice d'invito", + "validations": { + "password_confirmation_match": "dovrebbe essere uguale alla password", + "password_confirmation_required": "non può essere vuoto", + "password_required": "non può essere vuoto", + "email_required": "non può essere vuoto", + "fullname_required": "non può essere vuoto", + "username_required": "non può essere vuoto" + }, + "bio_placeholder": "es.\nCiao, sono Lupo Lucio.\nSono un lupo fantastico che vive nel Fantabosco. Forse mi hai visto alla Melevisione.", + "fullname_placeholder": "es. Lupo Lucio", + "username_placeholder": "es. mister_wolf", + "new_captcha": "Clicca l'immagine per avere un altro captcha", + "captcha": "CAPTCHA" }, "user_profile": { - "timeline_title": "Sequenza Temporale dell'Utente" + "timeline_title": "Sequenza dell'Utente" }, "who_to_follow": { - "more": "Più", + "more": "Altro", "who_to_follow": "Chi seguire" + }, + "about": { + "mrf": { + "federation": "Federazione", + "keyword": { + "reject": "Rifiuta", + "replace": "Sostituisci", + "is_replaced_by": "→", + "keyword_policies": "Regole per parole chiave", + "ftl_removal": "Rimozione dalla sequenza globale" + }, + "simple": { + "reject": "Rifiuta", + "accept": "Accetta", + "simple_policies": "Regole specifiche alla stanza", + "accept_desc": "Questa stanza accetta messaggi solo dalle seguenti stanze:", + "reject_desc": "Questa stanza non accetterà messaggi dalle stanze seguenti:", + "quarantine": "Quarantena", + "quarantine_desc": "Questa stanza inoltrerà solo messaggi pubblici alle seguenti stanze:", + "ftl_removal": "Rimozione dalla sequenza globale", + "ftl_removal_desc": "Questa stanza rimuove le seguenti stanze dalla sequenza globale:", + "media_removal": "Rimozione multimedia", + "media_removal_desc": "Questa istanza rimuove gli allegati dalle seguenti stanze:", + "media_nsfw": "Allegati oscurati forzatamente", + "media_nsfw_desc": "Questa stanza oscura gli allegati dei messaggi provenienti da queste stanze:" + }, + "mrf_policies": "Regole RM abilitate", + "mrf_policies_desc": "Le regole RM cambiano il comportamento federativo della stanza. Vigono le seguenti regole:" + }, + "staff": "Equipaggio" + }, + "domain_mute_card": { + "mute": "Zittisci", + "mute_progress": "Zittisco…", + "unmute": "Ascolta", + "unmute_progress": "Procedo…" + }, + "exporter": { + "export": "Esporta", + "processing": "In elaborazione, il tuo file sarà scaricabile a breve" + }, + "image_cropper": { + "crop_picture": "Ritaglia immagine", + "save": "Salva", + "save_without_cropping": "Salva senza ritagliare", + "cancel": "Annulla" + }, + "importer": { + "submit": "Invia", + "success": "Importato.", + "error": "L'importazione non è andata a buon fine." + }, + "media_modal": { + "previous": "Precedente", + "next": "Prossimo" + }, + "polls": { + "add_poll": "Sondaggio", + "add_option": "Alternativa", + "option": "Opzione", + "votes": "voti", + "vote": "Vota", + "type": "Tipo di sondaggio", + "single_choice": "Scelta singola", + "multiple_choices": "Scelta multipla", + "expiry": "Scadenza", + "expires_in": "Scade fra {0}", + "expired": "Scaduto {0} fa", + "not_enough_options": "Aggiungi altre risposte" + }, + "interactions": { + "favs_repeats": "Condivisi e preferiti", + "load_older": "Carica vecchie interazioni", + "moves": "Utenti migrati", + "follows": "Nuovi seguìti" + }, + "emoji": { + "load_all": "Carico tutti i {emojiAmount} emoji", + "load_all_hint": "Primi {saneAmount} emoji caricati, caricarli tutti potrebbe causare rallentamenti.", + "unicode": "Emoji Unicode", + "custom": "Emoji personale", + "add_emoji": "Inserisci Emoji", + "search_emoji": "Cerca un emoji", + "keep_open": "Tieni aperto il menù", + "emoji": "Emoji", + "stickers": "Adesivi" + }, + "selectable_list": { + "select_all": "Seleziona tutto" + }, + "remote_user_resolver": { + "error": "Non trovato.", + "searching_for": "Cerco", + "remote_user_resolver": "Cerca utenti remoti" } } diff --git a/src/i18n/ja.json b/src/i18n/ja_easy.json similarity index 90% rename from src/i18n/ja.json rename to src/i18n/ja_easy.json index 592a7257..978e43b3 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja_easy.json @@ -1,4 +1,27 @@ { + "about": { + "mrf": { + "federation": "フェデレーション", + "mrf_policies": "ゆうこうなMRFポリシー", + "mrf_policies_desc": "MRFポリシーは、このインスタンスのフェデレーションのふるまいを、いじります。これらのMRFポリシーがゆうこうになっています:", + "simple": { + "simple_policies": "インスタンスのポリシー", + "accept": "うけいれ", + "accept_desc": "このインスンスは、これらのインスタンスからのメッセージのみをうけいれます:", + "reject": "おことわり", + "reject_desc": "このインスタンスは、これらのインスタンスからのメッセージをうけいれません:", + "quarantine": "けんえき", + "quarantine_desc": "このインスタンスは、これらのインスタンスに、パブリックなとうこうのみを、おくります:", + "ftl_removal": "「つながっているすべてのネットワーク」タイムラインからのぞく", + "ftl_removal_desc": "このインスタンスは、つながっているすべてのネットワーク」タイムラインから、これらのインスタンスを、とりのぞきます:", + "media_removal": "メディアをのぞく", + "media_removal_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、とりのぞきます:", + "media_nsfw": "メディアをすべてセンシティブにする", + "media_nsfw_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、すべて、センシティブにマークします:" + } + }, + "staff": "スタッフ" + }, "chat": { "title": "チャット" }, @@ -68,6 +91,7 @@ }, "nav": { "about": "これはなに?", + "administration": "アドミニストレーション", "back": "もどる", "chat": "ローカルチャット", "friend_requests": "フォローリクエスト", @@ -113,7 +137,9 @@ "search_emoji": "えもじをさがす", "add_emoji": "えもじをうちこむ", "custom": "カスタムえもじ", - "unicode": "ユニコードえもじ" + "unicode": "ユニコードえもじ", + "load_all_hint": "はじめの {saneAmount} このえもじだけがロードされています。すべてのえもじをロードすると、パフォーマンスがわるくなるかもしれません。", + "load_all": "すべてのえもじをロード ({emojiAmount} こあります)" }, "stickers": { "add_sticker": "ステッカーをふやす" @@ -173,6 +199,11 @@ "password_confirmation_match": "パスワードがちがいます" } }, + "remote_user_resolver": { + "remote_user_resolver": "リモートユーザーリゾルバー", + "searching_for": "さがしています:", + "error": "みつかりませんでした。" + }, "selectable_list": { "select_all": "すべてえらぶ" }, @@ -220,6 +251,9 @@ "cGreen": "リピート", "cOrange": "おきにいり", "cRed": "キャンセル", + "change_email": "メールアドレスをかえる", + "change_email_error": "メールアドレスをかえようとしましたが、なにかがおかしいです。", + "changed_email": "メールアドレスをかえることができました!", "change_password": "パスワードをかえる", "change_password_error": "パスワードをかえることが、できなかったかもしれません。", "changed_password": "パスワードが、かわりました!", @@ -279,6 +313,7 @@ "use_contain_fit": "がぞうのサムネイルを、きりぬかない", "name": "なまえ", "name_bio": "なまえとプロフィール", + "new_email": "あたらしいメールアドレス", "new_password": "あたらしいパスワード", "notification_visibility": "ひょうじするつうち", "notification_visibility_follows": "フォロー", @@ -344,6 +379,8 @@ "false": "いいえ", "true": "はい" }, + "fun": "おたのしみ", + "greentext": "ミームやじるし", "notifications": "つうち", "notification_setting": "つうちをうけとる:", "notification_setting_follows": "あなたがフォローしているひとから", @@ -391,6 +428,7 @@ "_tab_label": "くわしく", "alert": "アラートのバックグラウンド", "alert_error": "エラー", + "alert_warning": "けいこく", "badge": "バッジのバックグラウンド", "badge_notification": "つうち", "panel_header": "パネルヘッダー", @@ -542,6 +580,7 @@ "followers": "フォロワー", "following": "フォローしています!", "follows_you": "フォローされました!", + "hidden": "かくされています", "its_you": "これはあなたです!", "media": "メディア", "mention": "メンション", @@ -559,6 +598,8 @@ "unmute": "ミュートをやめる", "unmute_progress": "ミュートをとりけしています...", "mute_progress": "ミュートしています...", + "hide_repeats": "リピートをかくす", + "show_repeats": "リピートをみる", "admin_menu": { "moderation": "モデレーション", "grant_admin": "アドミンにする", @@ -634,6 +675,8 @@ "return_home": "ホームページにもどる", "not_found": "そのメールアドレスまたはユーザーめいを、みつけることができませんでした。", "too_many_requests": "パスワードリセットを、ためすことが、おおすぎます。しばらくしてから、ためしてください。", - "password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。" + "password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。", + "password_reset_required": "ログインするには、パスワードをリセットしてください。", + "password_reset_required_but_mailer_is_disabled": "あなたはパスワードのリセットがひつようです。しかし、まずいことに、このインスタンスでは、パスワードのリセットができなくなっています。このインスタンスのアドミニストレーターに、おといあわせください。" } } diff --git a/src/i18n/messages.js b/src/i18n/messages.js index 89c8a8c8..c3195f10 100644 --- a/src/i18n/messages.js +++ b/src/i18n/messages.js @@ -7,34 +7,47 @@ // sed -i -e "s/'//gm" -e 's/"/\\"/gm' -re 's/^( +)(.+?): ((.+?))?(,?)(\{?)$/\1"\2": "\4"/gm' -e 's/\"\{\"/{/g' -e 's/,"$/",/g' file.json // There's only problem that apostrophe character ' gets replaced by \\ so you have to fix it manually, sorry. +const loaders = { + ar: () => import('./ar.json'), + ca: () => import('./ca.json'), + cs: () => import('./cs.json'), + de: () => import('./de.json'), + eo: () => import('./eo.json'), + es: () => import('./es.json'), + et: () => import('./et.json'), + eu: () => import('./eu.json'), + fi: () => import('./fi.json'), + fr: () => import('./fr.json'), + ga: () => import('./ga.json'), + he: () => import('./he.json'), + hu: () => import('./hu.json'), + it: () => import('./it.json'), + ja: () => import('./ja_pedantic.json'), + ja_easy: () => import('./ja_easy.json'), + ko: () => import('./ko.json'), + nb: () => import('./nb.json'), + nl: () => import('./nl.json'), + oc: () => import('./oc.json'), + pl: () => import('./pl.json'), + pt: () => import('./pt.json'), + ro: () => import('./ro.json'), + ru: () => import('./ru.json'), + te: () => import('./te.json'), + zh: () => import('./zh.json') +} + const messages = { - ar: require('./ar.json'), - ca: require('./ca.json'), - cs: require('./cs.json'), - de: require('./de.json'), - en: require('./en.json'), - eo: require('./eo.json'), - es: require('./es.json'), - et: require('./et.json'), - eu: require('./eu.json'), - fi: require('./fi.json'), - fr: require('./fr.json'), - ga: require('./ga.json'), - he: require('./he.json'), - hu: require('./hu.json'), - it: require('./it.json'), - ja: require('./ja.json'), - ja_pedantic: require('./ja_pedantic.json'), - ko: require('./ko.json'), - nb: require('./nb.json'), - nl: require('./nl.json'), - oc: require('./oc.json'), - pl: require('./pl.json'), - pt: require('./pt.json'), - ro: require('./ro.json'), - ru: require('./ru.json'), - te: require('./te.json'), - zh: require('./zh.json') + languages: ['en', ...Object.keys(loaders)], + default: { + en: require('./en.json') + }, + setLanguage: async (i18n, language) => { + if (loaders[language]) { + let messages = await loaders[language]() + i18n.setLocaleMessage(language, messages) + } + i18n.locale = language + } } export default messages diff --git a/src/i18n/nl.json b/src/i18n/nl.json index 7e2f0604..af728b6e 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -8,7 +8,7 @@ "media_proxy": "Media proxy", "scope_options": "Zichtbaarheidsopties", "text_limit": "Tekst limiet", - "title": "Features", + "title": "Kenmerken", "who_to_follow": "Wie te volgen" }, "finder": { @@ -16,58 +16,95 @@ "find_user": "Gebruiker zoeken" }, "general": { - "apply": "toepassen", - "submit": "Verzend" + "apply": "Toepassen", + "submit": "Verzend", + "more": "Meer", + "optional": "optioneel", + "show_more": "Bekijk meer", + "show_less": "Bekijk minder", + "dismiss": "Opheffen", + "cancel": "Annuleren", + "disable": "Uitschakelen", + "enable": "Inschakelen", + "confirm": "Bevestigen", + "verify": "Verifiëren", + "generic_error": "Er is een fout opgetreden" }, "login": { "login": "Log in", "description": "Log in met OAuth", - "logout": "Log uit", + "logout": "Uitloggen", "password": "Wachtwoord", - "placeholder": "bv. lain", - "register": "Registreer", - "username": "Gebruikersnaam" + "placeholder": "bijv. lain", + "register": "Registreren", + "username": "Gebruikersnaam", + "hint": "Log in om deel te nemen aan de discussie", + "authentication_code": "Authenticatie code", + "enter_recovery_code": "Voer een herstelcode in", + "enter_two_factor_code": "Voer een twee-factor code in", + "recovery_code": "Herstelcode", + "heading": { + "totp": "Twee-factor authenticatie", + "recovery": "Twee-factor herstelling" + } }, "nav": { "about": "Over", "back": "Terug", - "chat": "Locale Chat", - "friend_requests": "Volgverzoek", + "chat": "Lokale Chat", + "friend_requests": "Volgverzoeken", "mentions": "Vermeldingen", "dms": "Directe Berichten", "public_tl": "Publieke Tijdlijn", "timeline": "Tijdlijn", - "twkn": "Het Geheel Gekende Netwerk", - "user_search": "Zoek Gebruiker", + "twkn": "Het Geheel Bekende Netwerk", + "user_search": "Gebruiker Zoeken", "who_to_follow": "Wie te volgen", - "preferences": "Voorkeuren" + "preferences": "Voorkeuren", + "administration": "Administratie", + "search": "Zoeken", + "interactions": "Interacties" }, "notifications": { - "broken_favorite": "Onbekende status, aan het zoeken...", + "broken_favorite": "Onbekende status, aan het zoeken…", "favorited_you": "vond je status leuk", "followed_you": "volgt jou", "load_older": "Laad oudere meldingen", "notifications": "Meldingen", "read": "Gelezen!", - "repeated_you": "Herhaalde je status" + "repeated_you": "Herhaalde je status", + "no_more_notifications": "Geen meldingen meer", + "migrated_to": "is gemigreerd naar", + "follow_request": "wil je volgen", + "reacted_with": "reageerde met {0}" }, "post_status": { - "new_status": "Post nieuwe status", - "account_not_locked_warning": "Je account is niet {0}. Iedereen die je volgt kan enkel-volgers posts lezen.", + "new_status": "Nieuwe status plaatsen", + "account_not_locked_warning": "Je account is niet {0}. Iedereen kan je volgen om je alleen-volgers berichten te lezen.", "account_not_locked_warning_link": "gesloten", - "attachments_sensitive": "Markeer bijlage als gevoelig", + "attachments_sensitive": "Markeer bijlagen als gevoelig", "content_type": { - "text/plain": "Gewone tekst" + "text/plain": "Platte tekst", + "text/html": "HTML", + "text/markdown": "Markdown", + "text/bbcode": "BBCode" }, "content_warning": "Onderwerp (optioneel)", - "default": "Tijd voor een pauze!", + "default": "Zojuist geland in L.A.", "direct_warning": "Deze post zal enkel zichtbaar zijn voor de personen die genoemd zijn.", "posting": "Plaatsen", "scope": { - "direct": "Direct - Post enkel naar genoemde gebruikers", + "direct": "Direct - Post enkel naar vermelde gebruikers", "private": "Enkel volgers - Post enkel naar volgers", "public": "Publiek - Post op publieke tijdlijnen", - "unlisted": "Unlisted - Toon niet op publieke tijdlijnen" + "unlisted": "Niet Vermelden - Niet tonen op publieke tijdlijnen" + }, + "direct_warning_to_all": "Dit bericht zal zichtbaar zijn voor alle vermelde gebruikers.", + "direct_warning_to_first_only": "Dit bericht zal alleen zichtbaar zijn voor de vermelde gebruikers aan het begin van het bericht.", + "scope_notice": { + "public": "Dit bericht zal voor iedereen zichtbaar zijn", + "unlisted": "Dit bericht zal niet zichtbaar zijn in de Publieke Tijdlijn en Het Geheel Bekende Netwerk", + "private": "Dit bericht zal voor alleen je volgers zichtbaar zijn" } }, "registration": { @@ -76,7 +113,7 @@ "fullname": "Weergave naam", "password_confirm": "Wachtwoord bevestiging", "registration": "Registratie", - "token": "Uitnodigingstoken", + "token": "Uitnodigings-token", "captcha": "CAPTCHA", "new_captcha": "Klik op de afbeelding voor een nieuwe captcha", "validations": { @@ -86,141 +123,161 @@ "password_required": "moet ingevuld zijn", "password_confirmation_required": "moet ingevuld zijn", "password_confirmation_match": "komt niet overeen met het wachtwoord" - } + }, + "username_placeholder": "bijv. lain", + "fullname_placeholder": "bijv. Lain Iwakura", + "bio_placeholder": "bijv.\nHallo, ik ben Lain.\nIk ben een anime meisje woonachtig in een buitenwijk in Japan. Je kent me misschien van the Wired." }, "settings": { "attachmentRadius": "Bijlages", "attachments": "Bijlages", - "autoload": "Automatisch laden wanneer tot de bodem gescrold inschakelen", + "autoload": "Automatisch laden inschakelen wanneer tot de bodem gescrold wordt", "avatar": "Avatar", "avatarAltRadius": "Avatars (Meldingen)", "avatarRadius": "Avatars", "background": "Achtergrond", "bio": "Bio", "btnRadius": "Knoppen", - "cBlue": "Blauw (Antwoord, volgen)", - "cGreen": "Groen (Herhaal)", - "cOrange": "Oranje (Vind ik leuk)", - "cRed": "Rood (Annuleer)", - "change_password": "Verander Wachtwoord", - "change_password_error": "Er was een probleem bij het aanpassen van je wachtwoord.", - "changed_password": "Wachtwoord succesvol aangepast!", - "collapse_subject": "Klap posts met onderwerp in", - "composing": "Samenstellen", - "confirm_new_password": "Bevestig nieuw wachtwoord", + "cBlue": "Blauw (Beantwoorden, volgen)", + "cGreen": "Groen (Herhalen)", + "cOrange": "Oranje (Favoriet)", + "cRed": "Rood (Annuleren)", + "change_password": "Wachtwoord Wijzigen", + "change_password_error": "Er is een fout opgetreden bij het wijzigen van je wachtwoord.", + "changed_password": "Wachtwoord succesvol gewijzigd!", + "collapse_subject": "Klap berichten met een onderwerp in", + "composing": "Opstellen", + "confirm_new_password": "Nieuw wachtwoord bevestigen", "current_avatar": "Je huidige avatar", "current_password": "Huidig wachtwoord", "current_profile_banner": "Je huidige profiel banner", "data_import_export_tab": "Data Import / Export", - "default_vis": "Standaard zichtbaarheidsscope", - "delete_account": "Verwijder Account", - "delete_account_description": "Verwijder je account en berichten permanent.", - "delete_account_error": "Er was een probleem bij het verwijderen van je account. Indien dit probleem blijft, gelieve de administratie van deze instantie te verwittigen.", - "delete_account_instructions": "Typ je wachtwoord in de input hieronder om het verwijderen van je account te bevestigen.", - "export_theme": "Sla preset op", + "default_vis": "Standaard zichtbaarheidsbereik", + "delete_account": "Account Verwijderen", + "delete_account_description": "Permanent je gegevens verwijderen en account deactiveren.", + "delete_account_error": "Er is een fout opgetreden bij het verwijderen van je account. Indien dit probleem zich voor blijft doen, neem dan contact op met de beheerder van deze instantie.", + "delete_account_instructions": "Voer je wachtwoord in het onderstaande invoerveld in om het verwijderen van je account te bevestigen.", + "export_theme": "Preset opslaan", "filtering": "Filtering", - "filtering_explanation": "Alle statussen die deze woorden bevatten worden genegeerd, één filter per lijn.", - "follow_export": "Volgers export", - "follow_export_button": "Exporteer je volgers naar een csv file", + "filtering_explanation": "Alle statussen die deze woorden bevatten worden genegeerd, één filter per lijn", + "follow_export": "Volgers exporteren", + "follow_export_button": "Exporteer je volgers naar een csv bestand", "follow_export_processing": "Aan het verwerken, binnen enkele ogenblikken wordt je gevraagd je bestand te downloaden", - "follow_import": "Volgers import", + "follow_import": "Volgers importeren", "follow_import_error": "Fout bij importeren volgers", - "follows_imported": "Volgers geïmporteerd! Het kan even duren om ze allemaal te verwerken.", + "follows_imported": "Volgers geïmporteerd! Het kan even duren voordat deze verwerkt zijn.", "foreground": "Voorgrond", "general": "Algemeen", "hide_attachments_in_convo": "Verberg bijlages in conversaties", "hide_attachments_in_tl": "Verberg bijlages in de tijdlijn", "hide_isp": "Verberg instantie-specifiek paneel", - "preload_images": "Afbeeldingen voorladen", - "hide_post_stats": "Verberg post statistieken (bv. het aantal vind-ik-leuks)", - "hide_user_stats": "Verberg post statistieken (bv. het aantal volgers)", - "import_followers_from_a_csv_file": "Importeer volgers uit een csv file", - "import_theme": "Laad preset", - "inputRadius": "Invoer velden", + "preload_images": "Afbeeldingen vooraf laden", + "hide_post_stats": "Verberg bericht statistieken (bijv. het aantal favorieten)", + "hide_user_stats": "Verberg bericht statistieken (bijv. het aantal volgers)", + "import_followers_from_a_csv_file": "Importeer volgers uit een csv bestand", + "import_theme": "Preset laden", + "inputRadius": "Invoervelden", "checkboxRadius": "Checkboxen", "instance_default": "(standaard: {value})", "instance_default_simple": "(standaard)", "interface": "Interface", "interfaceLanguage": "Interface taal", - "invalid_theme_imported": "Het geselecteerde thema is geen door Pleroma ondersteund thema. Er zijn geen aanpassingen gedaan.", - "limited_availability": "Onbeschikbaar in je browser", + "invalid_theme_imported": "Het geselecteerde bestand is geen door Pleroma ondersteund thema. Er zijn geen aanpassingen gedaan.", + "limited_availability": "Niet beschikbaar in je browser", "links": "Links", "lock_account_description": "Laat volgers enkel toe na expliciete toestemming", - "loop_video": "Speel videos af in een lus", - "loop_video_silent_only": "Speel enkel videos zonder geluid af in een lus (bv. Mastodon's \"gifs\")", + "loop_video": "Herhaal video's", + "loop_video_silent_only": "Herhaal enkel video's zonder geluid (bijv. Mastodon's \"gifs\")", "name": "Naam", "name_bio": "Naam & Bio", "new_password": "Nieuw wachtwoord", "notification_visibility": "Type meldingen die getoond worden", - "notification_visibility_follows": "Volgers", + "notification_visibility_follows": "Volgingen", "notification_visibility_likes": "Vind-ik-leuks", "notification_visibility_mentions": "Vermeldingen", "notification_visibility_repeats": "Herhalingen", - "no_rich_text_description": "Strip rich text formattering van alle posts", + "no_rich_text_description": "Verwijder rich text formattering van alle berichten", "hide_network_description": "Toon niet wie mij volgt en wie ik volg.", - "nsfw_clickthrough": "Schakel doorklikbaar verbergen van NSFW bijlages in", + "nsfw_clickthrough": "Doorklikbaar verbergen van gevoelige bijlages inschakelen", "oauth_tokens": "OAuth-tokens", "token": "Token", - "refresh_token": "Token vernieuwen", + "refresh_token": "Token Vernieuwen", "valid_until": "Geldig tot", "revoke_token": "Intrekken", "panelRadius": "Panelen", - "pause_on_unfocused": "Pauzeer streamen wanneer de tab niet gefocused is", + "pause_on_unfocused": "Streamen pauzeren wanneer de tab niet in focus is", "presets": "Presets", "profile_background": "Profiel Achtergrond", "profile_banner": "Profiel Banner", "profile_tab": "Profiel", "radii_help": "Stel afronding van hoeken in de interface in (in pixels)", "replies_in_timeline": "Antwoorden in tijdlijn", - "reply_link_preview": "Schakel antwoordlink preview in bij over zweven met muisaanwijzer", - "reply_visibility_all": "Toon alle antwoorden", - "reply_visibility_following": "Toon enkel antwoorden naar mij of andere gebruikers gericht", - "reply_visibility_self": "Toon enkel antwoorden naar mij gericht", + "reply_link_preview": "Antwoord-link weergave inschakelen bij aanwijzen met muisaanwijzer", + "reply_visibility_all": "Alle antwoorden tonen", + "reply_visibility_following": "Enkel antwoorden tonen die aan mij of gevolgde gebruikers gericht zijn", + "reply_visibility_self": "Enkel antwoorden tonen die aan mij gericht zijn", "saving_err": "Fout tijdens opslaan van instellingen", "saving_ok": "Instellingen opgeslagen", - "security_tab": "Veiligheid", - "scope_copy": "Neem scope over bij antwoorden (Directe Berichten blijven altijd Direct)", - "set_new_avatar": "Zet nieuwe avatar", - "set_new_profile_background": "Zet nieuwe profiel achtergrond", - "set_new_profile_banner": "Zet nieuwe profiel banner", + "security_tab": "Beveiliging", + "scope_copy": "Neem bereik over bij beantwoorden (Directe Berichten blijven altijd Direct)", + "set_new_avatar": "Nieuwe avatar instellen", + "set_new_profile_background": "Nieuwe profiel achtergrond instellen", + "set_new_profile_banner": "Nieuwe profiel banner instellen", "settings": "Instellingen", - "subject_input_always_show": "Maak onderwerpveld altijd zichtbaar", - "subject_line_behavior": "Kopieer onderwerp bij antwoorden", + "subject_input_always_show": "Altijd onderwerpveld tonen", + "subject_line_behavior": "Onderwerp kopiëren bij antwoorden", "subject_line_email": "Zoals email: \"re: onderwerp\"", - "subject_line_mastodon": "Zoals Mastodon: kopieer zoals het is", - "subject_line_noop": "Kopieer niet", - "stop_gifs": "Speel GIFs af bij zweven", - "streaming": "Schakel automatisch streamen van posts in wanneer tot boven gescrold.", + "subject_line_mastodon": "Zoals mastodon: kopieer zoals het is", + "subject_line_noop": "Niet kopiëren", + "stop_gifs": "GIFs afspelen bij zweven", + "streaming": "Automatisch streamen van nieuwe berichten inschakelen wanneer tot boven gescrold is", "text": "Tekst", "theme": "Thema", "theme_help": "Gebruik hex color codes (#rrggbb) om je kleurschema te wijzigen.", - "theme_help_v2_1": "Je kan ook de kleur en transparantie van bepaalde componenten overschrijven door de checkbox aan te vinken, gebruik de \"Wis alles\" knop om alle overschrijvingen te annuleren.", - "theme_help_v2_2": "Iconen onder sommige items zijn achtergrond/tekst contrast indicators, zweef er over voor gedetailleerde info. Hou er rekening mee dat bij doorzichtigheid de ergst mogelijke situatie wordt weer gegeven.", - "tooltipRadius": "Gereedschapstips/alarmen", - "user_settings": "Gebruikers Instellingen", + "theme_help_v2_1": "Je kan ook de kleur en transparantie van bepaalde componenten overschrijven door de checkbox aan te vinken, gebruik de \"Alles wissen\" knop om alle overschrijvingen te annuleren.", + "theme_help_v2_2": "Iconen onder sommige onderdelen zijn achtergrond/tekst contrast indicatoren, zweef er over voor gedetailleerde info. Hou er rekening mee dat bij doorzichtigheid de ergst mogelijke situatie wordt weer gegeven.", + "tooltipRadius": "Tooltips/alarmen", + "user_settings": "Gebruikersinstellingen", "values": { "false": "nee", "true": "ja" }, "notifications": "Meldingen", - "enable_web_push_notifications": "Schakel web push meldingen in", + "enable_web_push_notifications": "Web push meldingen inschakelen", "style": { "switcher": { - "keep_color": "Behoud kleuren", - "keep_shadows": "Behoud schaduwen", - "keep_opacity": "Behoud transparantie", - "keep_roundness": "Behoud afrondingen", - "keep_fonts": "Behoud lettertypes", + "keep_color": "Kleuren behouden", + "keep_shadows": "Schaduwen behouden", + "keep_opacity": "Transparantie behouden", + "keep_roundness": "Rondingen behouden", + "keep_fonts": "Lettertypes behouden", "save_load_hint": "\"Behoud\" opties behouden de momenteel ingestelde opties bij het selecteren of laden van thema's, maar slaan ook de genoemde opties op bij het exporteren van een thema. Wanneer alle selectievakjes zijn uitgeschakeld, zal het exporteren van thema's alles opslaan.", "reset": "Reset", - "clear_all": "Wis alles", - "clear_opacity": "Wis transparantie" + "clear_all": "Alles wissen", + "clear_opacity": "Transparantie wissen", + "keep_as_is": "Hou zoals het is", + "use_snapshot": "Oude versie", + "use_source": "Nieuwe versie", + "help": { + "future_version_imported": "Het geïmporteerde bestand is gemaakt voor een nieuwere versie van FE.", + "older_version_imported": "Het geïmporteerde bestand is gemaakt voor een oudere versie van FE.", + "upgraded_from_v2": "PleromaFE is bijgewerkt, het thema kan iets anders uitzien dan dat je gewend bent.", + "v2_imported": "Het geïmporteerde bestand is gemaakt voor een oudere FE. We proberen compatibiliteit te maximaliseren, maar het kan toch voorkomen dat er inconsistenties zijn.", + "snapshot_source_mismatch": "Versie conflict: waarschijnlijk was FE terug gerold en opnieuw bijgewerkt, indien je het thema aangepast hebt met de oudere versie van FE wil je waarschijnlijk de oude versie gebruiken, gebruik anders de nieuwe versie.", + "migration_napshot_gone": "Voor een onduidelijke reden mist de momentopname, dus sommige dingen kunnen anders uitzien dan je gewend bent.", + "migration_snapshot_ok": "Voor de zekerheid is een momentopname van het thema geladen. Je kunt proberen om de thema gegevens te laden.", + "fe_downgraded": "PleromaFE's versie is terug gerold.", + "fe_upgraded": "De thema-engine van PleromaFE is bijgewerkt na de versie update.", + "snapshot_missing": "Het bestand bevat geen thema momentopname, dus het thema kan anders uitzien dan je oorspronkelijk bedacht had.", + "snapshot_present": "Thema momentopname is geladen, alle waarden zijn overschreven. Je kunt in plaats daarvan ook de daadwerkelijke data van het thema laden." + }, + "load_theme": "Thema laden" }, "common": { "color": "Kleur", "opacity": "Transparantie", "contrast": { - "hint": "Contrast ratio is {ratio}, {level} {context}", + "hint": "Contrast verhouding is {ratio}, {level} {context}", "level": { "aa": "voldoet aan de richtlijn van niveau AA (minimum)", "aaa": "voldoet aan de richtlijn van niveau AAA (aangeraden)", @@ -233,8 +290,8 @@ } }, "common_colors": { - "_tab_label": "Gemeenschappelijk", - "main": "Gemeenschappelijke kleuren", + "_tab_label": "Algemeen", + "main": "Algemene kleuren", "foreground_hint": "Zie \"Geavanceerd\" tab voor meer gedetailleerde controle", "rgbo": "Iconen, accenten, badges" }, @@ -244,58 +301,73 @@ "alert_error": "Fout", "badge": "Badge achtergrond", "badge_notification": "Meldingen", - "panel_header": "Paneel hoofding", - "top_bar": "Top bar", + "panel_header": "Paneel koptekst", + "top_bar": "Top balk", "borders": "Randen", "buttons": "Knoppen", "inputs": "Invoervelden", - "faint_text": "Vervaagde tekst" + "faint_text": "Vervaagde tekst", + "tabs": "Tabbladen", + "toggled": "Geschakeld", + "disabled": "Uitgeschakeld", + "selectedMenu": "Geselecteerd menu item", + "selectedPost": "Geselecteerd bericht", + "pressed": "Ingedrukt", + "highlight": "Gemarkeerde elementen", + "icons": "Iconen", + "poll": "Poll grafiek", + "underlay": "Onderlaag", + "popover": "Tooltips, menu's, popovers", + "post": "Berichten / Gebruiker bios", + "alert_neutral": "Neutraal", + "alert_warning": "Waarschuwing" }, "radii": { "_tab_label": "Rondheid" }, "shadows": { "_tab_label": "Schaduw en belichting", - "component": "Component", + "component": "Onderdeel", "override": "Overschrijven", "shadow_id": "Schaduw #{value}", "blur": "Vervagen", - "spread": "Spreid", + "spread": "Spreiding", "inset": "Inzet", "hint": "Voor schaduw kan je ook --variable gebruiken als een kleur waarde om CSS3 variabelen te gebruiken. Houd er rekening mee dat het instellen van opaciteit in dit geval niet werkt.", "filter_hint": { "always_drop_shadow": "Waarschuwing, deze schaduw gebruikt altijd {0} als de browser dit ondersteund.", "drop_shadow_syntax": "{0} ondersteund niet de {1} parameter en {2} sleutelwoord.", - "avatar_inset": "Houd er rekening mee dat het combineren van zowel inzet and niet-inzet schaduwen op transparante avatars onverwachte resultaten kan opleveren.", + "avatar_inset": "Houdt er rekening mee dat het combineren van zowel inzet and niet-inzet schaduwen op transparante avatars onverwachte resultaten kan opleveren.", "spread_zero": "Schaduw met spreiding > 0 worden weergegeven alsof ze op nul staan", "inset_classic": "Inzet schaduw zal {0} gebruiken" }, "components": { "panel": "Paneel", - "panelHeader": "Paneel hoofding", - "topBar": "Top bar", - "avatar": "Gebruiker avatar (in profiel weergave)", - "avatarStatus": "Gebruiker avatar (in post weergave)", - "popup": "Popups en gereedschapstips", + "panelHeader": "Paneel koptekst", + "topBar": "Top balk", + "avatar": "Gebruikers avatar (in profiel weergave)", + "avatarStatus": "Gebruikers avatar (in bericht weergave)", + "popup": "Popups en tooltips", "button": "Knop", "buttonHover": "Knop (zweven)", "buttonPressed": "Knop (ingedrukt)", "buttonPressedHover": "Knop (ingedrukt+zweven)", "input": "Invoerveld" - } + }, + "hintV3": "Voor schaduwen kun je ook de {0} notatie gebruiken om de andere kleur invoer te gebruiken." }, "fonts": { "_tab_label": "Lettertypes", - "help": "Selecteer het lettertype om te gebruiken voor elementen van de UI.Voor \"aangepast\" moet je de exacte naam van het lettertype invoeren zoals die in het systeem wordt weergegeven.", + "help": "Selecteer het lettertype om te gebruiken voor elementen van de UI. Voor \"aangepast\" dien je de exacte naam van het lettertype in te voeren zoals die in het systeem wordt weergegeven.", "components": { "interface": "Interface", "input": "Invoervelden", - "post": "Post tekst", - "postCode": "Monospaced tekst in een post (rich text)" + "post": "Bericht tekst", + "postCode": "Monospaced tekst in een bericht (rich text)" }, - "family": "Naam lettertype", + "family": "Lettertype naam", "size": "Grootte (in px)", - "weight": "Gewicht (vetheid)", + "weight": "Gewicht (dikgedruktheid)", "custom": "Aangepast" }, "preview": { @@ -305,31 +377,119 @@ "button": "Knop", "text": "Nog een boel andere {0} en {1}", "mono": "inhoud", - "input": "Tijd voor een pauze!", + "input": "Zojuist geland in L.A.", "faint_link": "handige gebruikershandleiding", "fine_print": "Lees onze {0} om niets nuttig te leren!", "header_faint": "Alles komt goed", - "checkbox": "Ik heb de gebruikersvoorwaarden eens van ver bekeken", - "link": "een link" + "checkbox": "Ik heb de gebruikersvoorwaarden gelezen", + "link": "een leuke kleine link" } + }, + "notification_setting_follows": "Gebruikers die je volgt", + "notification_setting_non_follows": "Gebruikers die je niet volgt", + "notification_setting_followers": "Gebruikers die je volgen", + "notification_setting_privacy": "Privacy", + "notification_setting_privacy_option": "Verberg de afzender en inhoud van push meldingen", + "notification_mutes": "Om niet langer meldingen te ontvangen van een specifieke gebruiker, kun je deze negeren.", + "app_name": "App naam", + "security": "Beveiliging", + "enter_current_password_to_confirm": "Voer je huidige wachtwoord in om je identiteit te bevestigen", + "mfa": { + "otp": "OTP", + "setup_otp": "OTP instellen", + "wait_pre_setup_otp": "OTP voorinstellen", + "confirm_and_enable": "Bevestig en schakel OTP in", + "title": "Twee-factor Authenticatie", + "generate_new_recovery_codes": "Genereer nieuwe herstelcodes", + "recovery_codes": "Herstelcodes.", + "waiting_a_recovery_codes": "Backup codes ontvangen…", + "authentication_methods": "Authenticatie methodes", + "scan": { + "title": "Scannen", + "desc": "Scan de QR code of voer een sleutel in met je twee-factor applicatie:", + "secret_code": "Sleutel" + }, + "verify": { + "desc": "Voer de code van je twee-factor applicatie in om twee-factor authenticatie in te schakelen:" + }, + "warning_of_generate_new_codes": "Wanneer je nieuwe herstelcodes genereert, zullen je oude code niet langer werken.", + "recovery_codes_warning": "Schrijf de codes op of sla ze op een veilige locatie op - anders kun je ze niet meer inzien. Als je toegang tot je 2FA app en herstelcodes verliest, zal je buitengesloten zijn uit je account." + }, + "allow_following_move": "Automatisch volgen toestaan wanneer een gevolgd account migreert", + "block_export": "Blokkades exporteren", + "block_import": "Blokkades importeren", + "blocks_imported": "Blokkades geïmporteerd! Het kan even duren voordat deze verwerkt zijn.", + "blocks_tab": "Blokkades", + "change_email": "Email wijzigen", + "change_email_error": "Er is een fout opgetreden tijdens het wijzigen van je email.", + "changed_email": "Email succesvol gewijzigd!", + "domain_mutes": "Domeinen", + "avatar_size_instruction": "De aangeraden minimale afmeting voor avatar afbeeldingen is 150x150 pixels.", + "pad_emoji": "Vul emoji aan met spaties wanneer deze met de picker ingevoegd worden", + "emoji_reactions_on_timeline": "Toon emoji reacties op de tijdlijn", + "accent": "Accent", + "hide_muted_posts": "Verberg berichten van genegeerde gebruikers", + "max_thumbnails": "Maximaal aantal miniaturen per bericht", + "use_one_click_nsfw": "Open gevoelige bijlagen met slechts één klik", + "hide_filtered_statuses": "Gefilterde statussen verbergen", + "import_blocks_from_a_csv_file": "Importeer blokkades van een csv bestand", + "mutes_tab": "Negeringen", + "play_videos_in_modal": "Speel video's af in een popup frame", + "new_email": "Nieuwe Email", + "notification_visibility_emoji_reactions": "Reacties", + "no_blocks": "Geen blokkades", + "no_mutes": "Geen negeringen", + "hide_followers_description": "Niet tonen wie mij volgt", + "hide_followers_count_description": "Niet mijn volgers aantal tonen", + "hide_follows_count_description": "Niet mijn gevolgde aantal tonen", + "show_admin_badge": "Beheerders badge tonen in mijn profiel", + "autohide_floating_post_button": "Nieuw Bericht knop automatisch verbergen (mobiel)", + "search_user_to_block": "Zoek wie je wilt blokkeren", + "search_user_to_mute": "Zoek wie je wilt negeren", + "minimal_scopes_mode": "Bericht bereik-opties minimaliseren", + "post_status_content_type": "Bericht status content type", + "user_mutes": "Gebruikers", + "useStreamingApi": "Berichten en meldingen in real-time ontvangen", + "useStreamingApiWarning": "(Afgeraden, experimenteel, kan berichten overslaan)", + "type_domains_to_mute": "Voer domeinen in om te negeren", + "upload_a_photo": "Upload een foto", + "fun": "Plezier", + "greentext": "Meme pijlen", + "notification_setting": "Ontvang meldingen van:", + "block_export_button": "Exporteer je geblokkeerde gebruikers naar een csv bestand", + "block_import_error": "Fout bij importeren blokkades", + "discoverable": "Sta toe dat dit account ontdekt kan worden in zoekresultaten en andere diensten", + "use_contain_fit": "Snij bijlage in miniaturen niet bij", + "notification_visibility_moves": "Gebruiker Migraties", + "hide_follows_description": "Niet tonen wie ik volg", + "show_moderator_badge": "Moderators badge tonen in mijn profiel", + "notification_setting_filters": "Filters", + "notification_setting_non_followers": "Gebruikers die je niet volgen", + "notification_blocks": "Door een gebruiker te blokkeren, ontvang je geen meldingen meer van de gebruiker en wordt je abonnement op de gebruiker opgeheven.", + "version": { + "frontend_version": "Frontend Versie", + "backend_version": "Backend Versie", + "title": "Versie" } }, "timeline": { "collapse": "Inklappen", "conversation": "Conversatie", "error_fetching": "Fout bij ophalen van updates", - "load_older": "Laad oudere Statussen", - "no_retweet_hint": "Post is gemarkeerd als enkel volgers of direct en kan niet worden herhaald", + "load_older": "Oudere statussen laden", + "no_retweet_hint": "Bericht is gemarkeerd als enkel volgers of direct en kan niet worden herhaald", "repeated": "herhaalde", - "show_new": "Toon nieuwe", - "up_to_date": "Up-to-date" + "show_new": "Nieuwe tonen", + "up_to_date": "Up-to-date", + "no_statuses": "Geen statussen", + "no_more_statuses": "Geen statussen meer" }, "user_card": { "approve": "Goedkeuren", "block": "Blokkeren", "blocked": "Geblokkeerd!", - "deny": "Ontzeggen", - "favorites": "Vind-ik-leuks", + "deny": "Weigeren", + "favorites": "Favorieten", "follow": "Volgen", "follow_sent": "Aanvraag verzonden!", "follow_progress": "Aanvragen…", @@ -340,31 +500,69 @@ "following": "Aan het volgen!", "follows_you": "Volgt jou!", "its_you": "'t is jij!", - "mute": "Dempen", - "muted": "Gedempt", + "mute": "Negeren", + "muted": "Genegeerd", "per_day": "per dag", "remote_follow": "Volg vanop afstand", - "statuses": "Statussen" + "statuses": "Statussen", + "admin_menu": { + "delete_user_confirmation": "Weet je het heel zeker? Deze uitvoering kan niet ongedaan worden gemaakt.", + "delete_user": "Gebruiker verwijderen", + "quarantine": "Federeren van gebruikers berichten verbieden", + "disable_any_subscription": "Volgen van gebruiker in zijn geheel verbieden", + "disable_remote_subscription": "Volgen van gebruiker vanaf andere instanties verbieden", + "sandbox": "Berichten forceren om alleen voor volgers zichtbaar te zijn", + "force_unlisted": "Berichten forceren om niet publiekelijk getoond te worden", + "strip_media": "Media van berichten verwijderen", + "force_nsfw": "Alle berichten als gevoelig markeren", + "delete_account": "Account verwijderen", + "deactivate_account": "Account deactiveren", + "activate_account": "Account activeren", + "revoke_moderator": "Moderatorsrechten intrekken", + "grant_moderator": "Moderatorsrechten toekennen", + "revoke_admin": "Beheerdersrechten intrekken", + "grant_admin": "Beheerdersrechten toekennen", + "moderation": "Moderatie" + }, + "show_repeats": "Herhalingen tonen", + "hide_repeats": "Herhalingen verbergen", + "mute_progress": "Negeren…", + "unmute_progress": "Negering opheffen…", + "unmute": "Negering opheffen", + "block_progress": "Blokkeren…", + "unblock_progress": "Blokkade opheffen…", + "unblock": "Blokkade opheffen", + "unsubscribe": "Abonnement opzeggen", + "subscribe": "Abonneren", + "report": "Aangeven", + "mention": "Vermelding", + "media": "Media", + "hidden": "Verborgen" }, "user_profile": { - "timeline_title": "Gebruikers Tijdlijn" + "timeline_title": "Gebruikers Tijdlijn", + "profile_loading_error": "Sorry, er is een fout opgetreden bij het laden van dit profiel.", + "profile_does_not_exist": "Sorry, dit profiel bestaat niet." }, "who_to_follow": { "more": "Meer", "who_to_follow": "Wie te volgen" }, "tool_tip": { - "media_upload": "Upload Media", - "repeat": "Herhaal", - "reply": "Antwoord", - "favorite": "Vind-ik-leuk", - "user_settings": "Gebruikers Instellingen" + "media_upload": "Media Uploaden", + "repeat": "Herhalen", + "reply": "Beantwoorden", + "favorite": "Favoriet maken", + "user_settings": "Gebruikers Instellingen", + "reject_follow_request": "Volg-verzoek afwijzen", + "accept_follow_request": "Volg-aanvraag accepteren", + "add_reaction": "Reactie toevoegen" }, - "upload":{ + "upload": { "error": { - "base": "Upload gefaald.", - "file_too_big": "Bestand is te groot [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", - "default": "Probeer later opnieuw" + "base": "Upload mislukt.", + "file_too_big": "Bestand is te groot [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", + "default": "Probeer het later opnieuw" }, "file_size_units": { "B": "B", @@ -373,5 +571,177 @@ "GiB": "GiB", "TiB": "TiB" } + }, + "about": { + "mrf": { + "federation": "Federatie", + "keyword": { + "reject": "Afwijzen", + "replace": "Vervangen", + "is_replaced_by": "→", + "keyword_policies": "Zoekwoord Beleid", + "ftl_removal": "Verwijdering van \"Het Geheel Bekende Netwerk\" Tijdlijn" + }, + "mrf_policies_desc": "MRF regels beïnvloeden het federatie gedrag van de instantie. De volgende regels zijn ingeschakeld:", + "mrf_policies": "Ingeschakelde MRF Regels", + "simple": { + "simple_policies": "Instantie-specifieke Regels", + "accept": "Accepteren", + "accept_desc": "Deze instantie accepteert alleen berichten van de volgende instanties:", + "reject": "Afwijzen", + "reject_desc": "Deze instantie zal geen berichten accepteren van de volgende instanties:", + "quarantine": "Quarantaine", + "quarantine_desc": "Deze instantie zal alleen publieke berichten sturen naar de volgende instanties:", + "ftl_removal_desc": "Deze instantie verwijdert de volgende instanties van \"Het Geheel Bekende Netwerk\" tijdlijn:", + "media_removal_desc": "Deze instantie verwijdert media van berichten van de volgende instanties:", + "media_nsfw_desc": "Deze instantie stelt media in als gevoelig in berichten van de volgende instanties:", + "ftl_removal": "Verwijderen van \"Het Geheel Bekende Netwerk\" Tijdlijn", + "media_removal": "Media Verwijdering", + "media_nsfw": "Forceer Media als Gevoelig" + } + }, + "staff": "Personeel" + }, + "domain_mute_card": { + "mute": "Negeren", + "mute_progress": "Negeren…", + "unmute": "Negering opheffen", + "unmute_progress": "Negering wordt opgeheven…" + }, + "exporter": { + "export": "Exporteren", + "processing": "Verwerken, er wordt zo gevraagd om je bestand te downloaden" + }, + "image_cropper": { + "save": "Opslaan", + "save_without_cropping": "Opslaan zonder bijsnijden", + "cancel": "Annuleren", + "crop_picture": "Afbeelding bijsnijden" + }, + "importer": { + "submit": "Verzenden", + "success": "Succesvol geïmporteerd.", + "error": "Er is een fout opgetreden bij het importeren van dit bestand." + }, + "media_modal": { + "previous": "Vorige", + "next": "Volgende" + }, + "polls": { + "add_poll": "Poll Toevoegen", + "add_option": "Optie Toevoegen", + "option": "Optie", + "votes": "stemmen", + "vote": "Stem", + "single_choice": "Enkele keuze", + "multiple_choices": "Meerkeuze", + "expiry": "Poll leeftijd", + "expires_in": "Poll eindigt in {0}", + "expired": "Poll is {0} geleden beëindigd", + "not_enough_options": "Te weinig opties in poll", + "type": "Poll type" + }, + "emoji": { + "emoji": "Emoji", + "keep_open": "Picker openhouden", + "search_emoji": "Zoek voor een emoji", + "add_emoji": "Emoji invoegen", + "unicode": "Unicode emoji", + "load_all": "Alle {emojiAmount} emoji worden geladen", + "stickers": "Stickers", + "load_all_hint": "Eerste {saneAmount} emoji geladen, alle emoji tegelijk laden kan problemen veroorzaken met prestaties.", + "custom": "Gepersonaliseerde emoji" + }, + "interactions": { + "favs_repeats": "Herhalingen en Favorieten", + "follows": "Nieuwe volgingen", + "moves": "Gebruiker migreert", + "load_older": "Oudere interacties laden" + }, + "remote_user_resolver": { + "searching_for": "Zoeken naar", + "error": "Niet gevonden.", + "remote_user_resolver": "Externe gebruikers zoeker" + }, + "selectable_list": { + "select_all": "Alles selecteren" + }, + "password_reset": { + "password_reset_required_but_mailer_is_disabled": "Je dient je wachtwoord opnieuw in te stellen, maar wachtwoord reset is uitgeschakeld. Neem contact op met de beheerder van deze instantie.", + "password_reset_required": "Je dient je wachtwoord opnieuw in te stellen om in te kunnen loggen.", + "password_reset_disabled": "Wachtwoord reset is uitgeschakeld. Neem contact op met de beheerder van deze instantie.", + "too_many_requests": "Je hebt het maximaal aantal pogingen bereikt, probeer het later opnieuw.", + "not_found": "We kunnen die email of gebruikersnaam niet vinden.", + "return_home": "Terugkeren naar de home pagina", + "check_email": "Controleer je email inbox voor een link om je wachtwoord opnieuw in te stellen.", + "placeholder": "Je email of gebruikersnaam", + "instruction": "Voer je email adres of gebruikersnaam in. We sturen je een link om je wachtwoord opnieuw in te stellen.", + "password_reset": "Wachtwoord opnieuw instellen", + "forgot_password": "Wachtwoord vergeten?" + }, + "search": { + "no_results": "Geen resultaten", + "people_talking": "{count} personen aan het praten", + "person_talking": "{count} persoon aan het praten", + "hashtags": "Hashtags", + "people": "Personen" + }, + "user_reporting": { + "generic_error": "Er is een fout opgetreden tijdens het verwerken van je verzoek.", + "submit": "Verzenden", + "forward_to": "Doorsturen naar {0}", + "forward_description": "Dit account hoort bij een andere server. Wil je een kopie van het rapport ook daarheen sturen?", + "additional_comments": "Aanvullende opmerkingen", + "add_comment_description": "Het rapport zal naar de moderators van de instantie worden verstuurd. Je kunt hieronder uitleg bijvoegen waarom je dit account wilt aangeven:", + "title": "{0} aangeven" + }, + "status": { + "copy_link": "Link naar status kopiëren", + "status_unavailable": "Status niet beschikbaar", + "unmute_conversation": "Conversatie niet meer negeren", + "mute_conversation": "Conversatie negeren", + "replies_list": "Antwoorden:", + "reply_to": "Antwoorden aan", + "delete_confirm": "Wil je echt deze status verwijderen?", + "pin": "Aan profiel vastmaken", + "pinned": "Vastgezet", + "unpin": "Van profiel losmaken", + "delete": "Status verwijderen", + "repeats": "Herhalingen", + "favorites": "Favorieten" + }, + "time": { + "years_short": "{0}j", + "year_short": "{0}j", + "years": "{0} jaren", + "year": "{0} jaar", + "weeks_short": "{0}w", + "week_short": "{0}w", + "weeks": "{0} weken", + "week": "{0} week", + "seconds_short": "{0}s", + "second_short": "{0}s", + "seconds": "{0} seconden", + "second": "{0} seconde", + "now_short": "nu", + "now": "zojuist", + "months_short": "{0}ma", + "month_short": "{0}ma", + "months": "{0} maanden", + "month": "{0} maand", + "minutes_short": "{0}min", + "minute_short": "{0}min", + "minutes": "{0} minuten", + "minute": "{0} minuut", + "in_past": "{0} geleden", + "in_future": "over {0}", + "hours_short": "{0}u", + "hour_short": "{0}u", + "hours": "{0} uren", + "hour": "{0} uur", + "days_short": "{0}d", + "day_short": "{0}d", + "days": "{0} dagen", + "day": "{0} dag" } } diff --git a/src/i18n/pl.json b/src/i18n/pl.json index 51cadfb6..61e09318 100644 --- a/src/i18n/pl.json +++ b/src/i18n/pl.json @@ -1,7 +1,47 @@ { + "about": { + "mrf": { + "federation": "Federacja", + "keyword": { + "keyword_policies": "Zasady słów kluczowych", + "ftl_removal": "Usunięcie z \"Całej znanej sieci\"", + "reject": "Odrzucanie", + "replace": "Zastąpienie", + "is_replaced_by": "→" + }, + "mrf_policies": "Włączone zasady MRF", + "mrf_policies_desc": "Zasady MRF zmieniają zachowanie federowania instancji. Następujące zasady są włączone:", + "simple": { + "simple_policies": "Zasady specyficzne dla instancji", + "accept": "Akceptowanie", + "accept_desc": "Ta instancja akceptuje tylko posty z wymienionych instancji:", + "reject": "Odrzucanie", + "reject_desc": "Ta instancja odrzuca posty z wymienionych instancji:", + "quarantine": "Kwarantanna", + "quarantine_desc": "Ta instancja wysyła tylko publiczne posty do wymienionych instancji:", + "ftl_removal": "Usunięcie z \"Całej znanej sieci\"", + "ftl_removal_desc": "Ta instancja usuwa wymienionych instancje z \"Całej znanej sieci\":", + "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:" + } + }, + "staff": "Administracja" + }, "chat": { "title": "Czat" }, + "domain_mute_card": { + "mute": "Wycisz", + "mute_progress": "Wyciszam...", + "unmute": "Odcisz", + "unmute_progress": "Odciszam..." + }, + "exporter": { + "export": "Eksportuj", + "processing": "Przetwarzam, za chwilę zostaniesz zapytany(-na) o ściągnięcie pliku" + }, "features_panel": { "chat": "Czat", "gopher": "Gopher", @@ -20,7 +60,15 @@ "submit": "Wyślij", "more": "Więcej", "generic_error": "Wystąpił błąd", - "optional": "nieobowiązkowe" + "optional": "nieobowiązkowe", + "show_more": "Pokaż więcej", + "show_less": "Pokaż mniej", + "dismiss": "Odrzuć", + "cancel": "Anuluj", + "disable": "Wyłącz", + "enable": "Włącz", + "confirm": "Potwierdź", + "verify": "Zweryfikuj" }, "image_cropper": { "crop_picture": "Przytnij obrazek", @@ -28,6 +76,11 @@ "save_without_cropping": "Zapisz bez przycinania", "cancel": "Anuluj" }, + "importer": { + "submit": "Wyślij", + "success": "Zaimportowano pomyślnie.", + "error": "Wystąpił błąd podczas importowania pliku." + }, "login": { "login": "Zaloguj", "description": "Zaloguj używając OAuth", @@ -36,7 +89,15 @@ "placeholder": "n.p. lain", "register": "Zarejestruj", "username": "Użytkownik", - "hint": "Zaloguj się, aby dołączyć do dyskusji" + "hint": "Zaloguj się, aby dołączyć do dyskusji", + "authentication_code": "Kod weryfikacyjny", + "enter_recovery_code": "Wprowadź kod zapasowy", + "enter_two_factor_code": "Wprowadź kod weryfikacyjny", + "recovery_code": "Kod zapasowy", + "heading": { + "totp": "Weryfikacja dwuetapowa", + "recovery": "Zapasowa weryfikacja dwuetapowa" + } }, "media_modal": { "previous": "Poprzednie", @@ -44,15 +105,18 @@ }, "nav": { "about": "O nas", + "administration": "Administracja", "back": "Wróć", "chat": "Lokalny czat", "friend_requests": "Prośby o możliwość obserwacji", "mentions": "Wzmianki", + "interactions": "Interakcje", "dms": "Wiadomości prywatne", "public_tl": "Publiczna oś czasu", "timeline": "Oś czasu", "twkn": "Cała znana sieć", "user_search": "Wyszukiwanie użytkowników", + "search": "Wyszukiwanie", "who_to_follow": "Sugestie obserwacji", "preferences": "Preferencje" }, @@ -64,7 +128,41 @@ "notifications": "Powiadomienia", "read": "Przeczytane!", "repeated_you": "powtórzył(-a) twój status", - "no_more_notifications": "Nie masz więcej powiadomień" + "no_more_notifications": "Nie masz więcej powiadomień", + "migrated_to": "wyemigrował do", + "reacted_with": "zareagował z {0}", + "follow_request": "chce ciebie obserwować" + }, + "polls": { + "add_poll": "Dodaj ankietę", + "add_option": "Dodaj opcję", + "option": "Opcja", + "votes": "głosów", + "vote": "Głosuj", + "type": "Typ ankiety", + "single_choice": "jednokrotnego wyboru", + "multiple_choices": "wielokrotnego wyboru", + "expiry": "Czas trwania ankiety", + "expires_in": "Ankieta kończy się za {0}", + "expired": "Ankieta skończyła się {0} temu", + "not_enough_options": "Zbyt mało unikalnych opcji w ankiecie" + }, + "emoji": { + "stickers": "Naklejki", + "emoji": "Emoji", + "keep_open": "Zostaw selektor otwarty", + "search_emoji": "Wyszukaj emoji", + "add_emoji": "Wstaw emoji", + "custom": "Niestandardowe emoji", + "unicode": "Emoji unicode", + "load_all_hint": "Załadowano pierwsze {saneAmount} emoji, Załadowanie wszystkich emoji może spowodować problemy z wydajnością.", + "load_all": "Ładuję wszystkie {emojiAmount} emoji" + }, + "interactions": { + "favs_repeats": "Powtórzenia i ulubione", + "follows": "Nowi obserwujący", + "moves": "Użytkownik migruje", + "load_older": "Załaduj starsze interakcje" }, "post_status": { "new_status": "Dodaj nowy status", @@ -79,8 +177,14 @@ }, "content_warning": "Temat (nieobowiązkowy)", "default": "Właśnie wróciłem z kościoła", - "direct_warning": "Ten wpis zobaczą tylko osoby, o których wspomniałeś(-aś).", + "direct_warning_to_all": "Ten wpis zobaczą wszystkie osoby, o których wspomniałeś(-aś).", + "direct_warning_to_first_only": "Ten wpis zobaczą tylko te osoby, o których wspomniałeś(-aś) na początku wiadomości.", "posting": "Wysyłanie", + "scope_notice": { + "public": "Ten post będzie widoczny dla każdego", + "private": "Ten post będzie widoczny tylko dla twoich obserwujących", + "unlisted": "Ten post nie będzie widoczny na publicznej osi czasu i całej znanej sieci" + }, "scope": { "direct": "Bezpośredni – Tylko dla wspomnianych użytkowników", "private": "Tylko dla obserwujących – Umieść dla osób, które cię obserwują", @@ -109,8 +213,40 @@ "password_confirmation_match": "musi być takie jak hasło" } }, + "remote_user_resolver": { + "remote_user_resolver": "Wyszukiwarka użytkowników nietutejszych", + "searching_for": "Szukam", + "error": "Nie znaleziono." + }, + "selectable_list": { + "select_all": "Zaznacz wszystko" + }, "settings": { "app_name": "Nazwa aplikacji", + "security": "Bezpieczeństwo", + "enter_current_password_to_confirm": "Wprowadź obecne hasło, by potwierdzić twoją tożsamość", + "mfa": { + "otp": "OTP", + "setup_otp": "Ustaw OTP", + "wait_pre_setup_otp": "początkowe ustawianie OTP", + "confirm_and_enable": "Potwierdź i włącz OTP", + "title": "Weryfikacja dwuetapowa", + "generate_new_recovery_codes": "Wygeneruj nowe kody zapasowe", + "warning_of_generate_new_codes": "Po tym gdy wygenerujesz nowe kody zapasowe, stare przestaną działać.", + "recovery_codes": "Kody zapasowe.", + "waiting_a_recovery_codes": "Otrzymuję kody zapasowe...", + "recovery_codes_warning": "Spisz kody na kartce papieru, albo zapisz je w bezpiecznym miejscu - inaczej nie zobaczysz ich już nigdy. Jeśli stracisz dostęp do twojej aplikacji 2FA i kodów zapasowych, nie będziesz miał(-a) dostępu do swojego konta.", + "authentication_methods": "Metody weryfikacji", + "scan": { + "title": "Skanuj", + "desc": "Zeskanuj ten kod QR używając twojej aplikacji 2FA albo wpisz ten klucz:", + "secret_code": "Klucz" + }, + "verify": { + "desc": "By włączyć weryfikację dwuetapową, wpisz kod z twojej aplikacji 2FA:" + } + }, + "allow_following_move": "Zezwalaj na automatyczną obserwację gdy obserwowane konto migruje", "attachmentRadius": "Załączniki", "attachments": "Załączniki", "autoload": "Włącz automatyczne ładowanie po przewinięciu do końca strony", @@ -119,12 +255,20 @@ "avatarRadius": "Awatary", "background": "Tło", "bio": "Bio", + "block_export": "Eksport blokad", + "block_export_button": "Eksportuj twoje blokady do pliku .csv", + "block_import": "Import blokad", + "block_import_error": "Wystąpił błąd podczas importowania blokad", + "blocks_imported": "Zaimportowano blokady, przetwarzanie może zająć trochę czasu.", "blocks_tab": "Bloki", "btnRadius": "Przyciski", "cBlue": "Niebieski (odpowiedz, obserwuj)", "cGreen": "Zielony (powtórzenia)", "cOrange": "Pomarańczowy (ulubione)", "cRed": "Czerwony (anuluj)", + "change_email": "Zmień email", + "change_email_error": "Wystąpił problem podczas zmiany emaila.", + "changed_email": "Pomyślnie zmieniono email!", "change_password": "Zmień hasło", "change_password_error": "Podczas zmiany hasła wystąpił problem.", "changed_password": "Pomyślnie zmieniono hasło!", @@ -137,19 +281,23 @@ "data_import_export_tab": "Import/eksport danych", "default_vis": "Domyślny zakres widoczności", "delete_account": "Usuń konto", - "delete_account_description": "Trwale usuń konto i wszystkie posty.", + "delete_account_description": "Trwale usuń dane i zdezaktywuj konto.", "delete_account_error": "Wystąpił problem z usuwaniem twojego konta. Jeżeli problem powtarza się, poinformuj administratora swojej instancji.", "delete_account_instructions": "Wprowadź swoje hasło w poniższe pole aby potwierdzić usunięcie konta.", + "discoverable": "Zezwól na odkrywanie tego konta w wynikach wyszukiwania i innych usługach", + "domain_mutes": "Domeny", "avatar_size_instruction": "Zalecany minimalny rozmiar awatarów to 150x150 pikseli.", + "pad_emoji": "Dodaj odstęp z obu stron emoji podczas dodawania selektorem", + "emoji_reactions_on_timeline": "Pokaż reakcje emoji na osi czasu", "export_theme": "Zapisz motyw", "filtering": "Filtrowanie", "filtering_explanation": "Wszystkie statusy zawierające te słowa będą wyciszone. Jedno słowo na linijkę.", "follow_export": "Eksport obserwowanych", "follow_export_button": "Eksportuj swoją listę obserwowanych do pliku CSV", - "follow_export_processing": "Przetwarzanie, wkrótce twój plik zacznie się ściągać.", "follow_import": "Import obserwowanych", "follow_import_error": "Błąd przy importowaniu obserwowanych", "follows_imported": "Obserwowani zaimportowani! Przetwarzanie może trochę potrwać.", + "accent": "Akcent", "foreground": "Pierwszy plan", "general": "Ogólne", "hide_attachments_in_convo": "Ukrywaj załączniki w rozmowach", @@ -162,18 +310,19 @@ "hide_post_stats": "Ukrywaj statysyki postów (np. liczbę polubień)", "hide_user_stats": "Ukrywaj statysyki użytkowników (np. liczbę obserwujących)", "hide_filtered_statuses": "Ukrywaj filtrowane statusy", + "import_blocks_from_a_csv_file": "Importuj blokady z pliku CSV", "import_followers_from_a_csv_file": "Importuj obserwowanych z pliku CSV", "import_theme": "Załaduj motyw", "inputRadius": "Pola tekstowe", "checkboxRadius": "Pola wyboru", - "instance_default": "(domyślny: {value})", - "instance_default_simple": "(domyślny)", + "instance_default": "(domyślnie: {value})", + "instance_default_simple": "(domyślne)", "interface": "Interfejs", "interfaceLanguage": "Język interfejsu", "invalid_theme_imported": "Wybrany plik nie jest obsługiwanym motywem Pleromy. Nie dokonano zmian w twoim motywie.", "limited_availability": "Niedostępne w twojej przeglądarce", "links": "Łącza", - "lock_account_description": "Ogranicz swoje konto dla zatwierdzonych obserwowanych", + "lock_account_description": "Spraw, by konto mogli wyświetlać tylko zatwierdzeni obserwujący", "loop_video": "Zapętlaj filmy", "loop_video_silent_only": "Zapętlaj tylko filmy bez dźwięku (np. mastodonowe „gify”)", "mutes_tab": "Wyciszenia", @@ -181,17 +330,22 @@ "use_contain_fit": "Nie przycinaj załączników na miniaturach", "name": "Imię", "name_bio": "Imię i bio", + "new_email": "Nowy email", "new_password": "Nowe hasło", "notification_visibility": "Rodzaje powiadomień do wyświetlania", "notification_visibility_follows": "Obserwacje", "notification_visibility_likes": "Ulubione", "notification_visibility_mentions": "Wzmianki", "notification_visibility_repeats": "Powtórzenia", + "notification_visibility_moves": "Użytkownik migruje", + "notification_visibility_emoji_reactions": "Reakcje", "no_rich_text_description": "Usuwaj formatowanie ze wszystkich postów", "no_blocks": "Bez blokad", "no_mutes": "Bez wyciszeń", "hide_follows_description": "Nie pokazuj kogo obserwuję", "hide_followers_description": "Nie pokazuj kto mnie obserwuje", + "hide_follows_count_description": "Nie pokazuj licznika obserwowanych", + "hide_followers_count_description": "Nie pokazuj licznika obserwujących", "show_admin_badge": "Pokazuj odznakę Administrator na moim profilu", "show_moderator_badge": "Pokazuj odznakę Moderator na moim profilu", "nsfw_clickthrough": "Włącz domyślne ukrywanie załączników o treści nieprzyzwoitej (NSFW)", @@ -212,10 +366,14 @@ "reply_visibility_all": "Pokazuj wszystkie odpowiedzi", "reply_visibility_following": "Pokazuj tylko odpowiedzi skierowane do mnie i osób które obserwuję", "reply_visibility_self": "Pokazuj tylko odpowiedzi skierowane do mnie", + "autohide_floating_post_button": "Ukryj automatycznie przycisk \"Nowy post\" (mobile)", "saving_err": "Nie udało się zapisać ustawień", "saving_ok": "Zapisano ustawienia", + "search_user_to_block": "Wyszukaj kogo chcesz zablokować", + "search_user_to_mute": "Wyszukaj kogo chcesz wyciszyć", "security_tab": "Bezpieczeństwo", "scope_copy": "Kopiuj zakres podczas odpowiadania (DM-y zawsze są kopiowane)", + "minimal_scopes_mode": "Zminimalizuj opcje wyboru zakresu postów", "set_new_avatar": "Ustaw nowy awatar", "set_new_profile_background": "Ustaw nowe tło profilu", "set_new_profile_banner": "Ustaw nowy banner profilu", @@ -228,19 +386,32 @@ "post_status_content_type": "Post status content type", "stop_gifs": "Odtwarzaj GIFy po najechaniu kursorem", "streaming": "Włącz automatycznie strumieniowanie nowych postów gdy jesteś na początku strony", + "user_mutes": "Użytkownicy", + "useStreamingApi": "Otrzymuj posty i powiadomienia w czasie rzeczywistym", + "useStreamingApiWarning": "(Niezalecane, eksperymentalne, pomija posty)", "text": "Tekst", "theme": "Motyw", "theme_help": "Użyj kolorów w notacji szesnastkowej (#rrggbb), by stworzyć swój motyw.", "theme_help_v2_1": "Możesz też zastąpić kolory i widoczność poszczególnych komponentów przełączając pola wyboru, użyj „Wyczyść wszystko” aby usunąć wszystkie zastąpienia.", "theme_help_v2_2": "Ikony pod niektórych wpisami są wskaźnikami kontrastu pomiędzy tłem a tekstem, po najechaniu na nie otrzymasz szczegółowe informacje. Zapamiętaj, że jeżeli używasz przezroczystości, wskaźniki pokazują najgorszy możliwy przypadek.", "tooltipRadius": "Etykiety/alerty", + "type_domains_to_mute": "Wpisz domeny, które chcesz wyciszyć", "upload_a_photo": "Wyślij zdjęcie", "user_settings": "Ustawienia użytkownika", "values": { "false": "nie", "true": "tak" }, + "fun": "Zabawa", + "greentext": "Memiczne strzałki", "notifications": "Powiadomienia", + "notification_setting": "Otrzymuj powiadomienia od:", + "notification_setting_follows": "Ludzi których obserwujesz", + "notification_setting_non_follows": "Ludzi których nie obserwujesz", + "notification_setting_followers": "Ludzi którzy obserwują ciebie", + "notification_setting_non_followers": "Ludzi którzy nie obserwują ciebie", + "notification_mutes": "By przestać otrzymywać powiadomienia od jednego użytkownika, wycisz go.", + "notification_blocks": "Blokowanie uzytkownika zatrzymuje wszystkie powiadomienia i odsubskrybowuje go.", "enable_web_push_notifications": "Włącz powiadomienia push", "style": { "switcher": { @@ -249,10 +420,27 @@ "keep_opacity": "Zachowaj widoczność", "keep_roundness": "Zachowaj zaokrąglenie", "keep_fonts": "Zachowaj czcionki", - "save_load_hint": "Opcje „zachowaj” pozwalają na pozostanie przy obecnych opcjach po wybraniu lub załadowaniu motywu, jak i przechowywanie ich podczas eksportowania motywu. Jeżeli wszystkie są odznaczone, eksportowanie motywu spowoduje zapisanie wszystkiego.", + "save_load_hint": "Opcje „zachowaj” pozwalają na pozostanie przy obecnych opcjach po wybraniu lub załadowaniu motywu, jak i przechowywanie ich podczas eksportowania motywu. Jeżeli wszystkie opcje są odznaczone, eksportowanie motywu spowoduje zapisanie wszystkiego.", "reset": "Wyzeruj", "clear_all": "Wyczyść wszystko", - "clear_opacity": "Wyczyść widoczność" + "clear_opacity": "Wyczyść widoczność", + "load_theme": "Załaduj motyw", + "keep_as_is": "Zostaw po staremu", + "use_snapshot": "Stara wersja", + "use_source": "Nowa wersja", + "help": { + "upgraded_from_v2": "PleromaFE zostało zaaktualizowane, motyw może wyglądać nieco inaczej niż zapamiętałeś(-aś).", + "v2_imported": "Plik który zaimportowałeś(-aś) został stworzony dla starszego FE. Próbujemy zwiększyć kompatybilność, lecz wciąż mogą występować rozbieżności.", + "future_version_imported": "Plik który zaimportowałeś(-aś) został stworzony w nowszej wersji FE.", + "older_version_imported": "Plik który zaimportowałeś(-aś) został stworzony w starszej wersji FE.", + "snapshot_present": "Migawka motywu jest załadowana, więc wszystkie wartości zostały nadpisane. Zamiast tego możesz załadować właściwe dane motywu.", + "snapshot_missing": "Nie znaleziono migawki motywu w pliku, więc motyw może wyglądać inaczej niż pierwotnie zaplanowano.", + "fe_upgraded": "Silnik motywów PleromaFE został zaaktualizowany.", + "fe_downgraded": "Wersja PleromaFE została cofnięta.", + "migration_snapshot_ok": "Żeby być bezpiecznym, migawka motywu została załadowana. Możesz spróbować załadować dane motywu.", + "migration_napshot_gone": "Z jakiegoś powodu migawka zniknęła, niektóre rzeczy mogą wyglądać inaczej niż zapamiętałeś(-aś).", + "snapshot_source_mismatch": "Konflikt wersji: najprawdopodobniej FE zostało cofnięte do poprzedniej wersji i zaktualizowane ponownie, jeśli zmieniłeś(-aś) motyw używając starszej wersji FE, najprawdopodobniej chcesz używać starszej wersji, w przeciwnym razie użyj nowej wersji." + } }, "common": { "color": "Kolor", @@ -280,14 +468,28 @@ "_tab_label": "Zaawansowane", "alert": "Tło alertu", "alert_error": "Błąd", + "alert_warning": "Ostrzeżenie", + "alert_neutral": "Neutralne", + "post": "Posty/Bio użytkowników", "badge": "Tło odznaki", + "popover": "Etykiety, menu, popovery", "badge_notification": "Powiadomienie", "panel_header": "Nagłówek panelu", "top_bar": "Górny pasek", "borders": "Granice", "buttons": "Przyciski", "inputs": "Pola wejścia", - "faint_text": "Zanikający tekst" + "faint_text": "Zanikający tekst", + "underlay": "Podkład", + "poll": "Wykres ankiety", + "icons": "Ikony", + "highlight": "Podświetlone elementy", + "pressed": "Naciśnięte", + "selectedPost": "Wybrany post", + "selectedMenu": "Wybrany element menu", + "disabled": "Wyłączone", + "toggled": "Przełączone", + "tabs": "Karty" }, "radii": { "_tab_label": "Zaokrąglenie" @@ -300,11 +502,11 @@ "blur": "Rozmycie", "spread": "Szerokość", "inset": "Inset", - "hint": "Możesz też używać --zmiennych jako kolorów, aby wykorzystać zmienne CSS3. Pamiętaj, że ustawienie widoczności nie będzie wtedy działać.", + "hintV3": "Dla cieni możesz również użyć notacji {0} by użyć inny slot koloru.", "filter_hint": { "always_drop_shadow": "Ostrzeżenie, ten cień zawsze używa {0} jeżeli to obsługiwane przez przeglądarkę.", "drop_shadow_syntax": "{0} nie obsługuje parametru {1} i słowa kluczowego {2}.", - "avatar_inset": "Pamiętaj że użycie jednocześnie cieni inset i nie inset na awatarach może daćnieoczekiwane wyniki z przezroczystymi awatarami.", + "avatar_inset": "Pamiętaj że użycie jednocześnie cieni inset i nie inset na awatarach może dać nieoczekiwane wyniki z przezroczystymi awatarami.", "spread_zero": "Cienie o ujemnej szerokości będą widoczne tak, jakby wynosiła ona zero", "inset_classic": "Cienie inset będą używały {0}" }, @@ -347,7 +549,7 @@ "faint_link": "pomocny podręcznik", "fine_print": "Przeczytaj nasz {0}, aby nie nauczyć się niczego przydatnego!", "header_faint": "W porządku", - "checkbox": "Przeleciałem przez zasady użytkowania", + "checkbox": "Przeleciałem(-am) przez zasady użytkowania", "link": "i fajny mały odnośnik" } }, @@ -355,7 +557,44 @@ "title": "Wersja", "backend_version": "Wersja back-endu", "frontend_version": "Wersja front-endu" - } + }, + "notification_setting_privacy": "Prywatność", + "notification_setting_filters": "Filtry", + "notification_setting_privacy_option": "Ukryj nadawcę i zawartość powiadomień push" + }, + "time": { + "day": "{0} dzień", + "days": "{0} dni", + "day_short": "{0} d", + "days_short": "{0} d", + "hour": "{0} godzina", + "hours": "{0} godzin", + "hour_short": "{0} godz.", + "hours_short": "{0} godz.", + "in_future": "za {0}", + "in_past": "{0} temu", + "minute": "{0} minuta", + "minutes": "{0} minut", + "minute_short": "{0} min", + "minutes_short": "{0} min", + "month": "{0} miesiąc", + "months": "{0} miesięcy", + "month_short": "{0} mies.", + "months_short": "{0} mies.", + "now": "teraz", + "now_short": "teraz", + "second": "{0} sekunda", + "seconds": "{0} sekund", + "second_short": "{0} s", + "seconds_short": "{0} s", + "week": "{0} tydzień", + "weeks": "{0} tygodni", + "week_short": "{0} tydz.", + "weeks_short": "{0} tyg.", + "year": "{0} rok", + "years": "{0} lata", + "year_short": "{0} r.", + "years_short": "{0} lata" }, "timeline": { "collapse": "Zwiń", @@ -370,8 +609,19 @@ "no_statuses": "Brak statusów" }, "status": { + "favorites": "Ulubione", + "repeats": "Powtórzenia", + "delete": "Usuń status", + "pin": "Przypnij na profilu", + "unpin": "Odepnij z profilu", + "pinned": "Przypnięte", + "delete_confirm": "Czy naprawdę chcesz usunąć ten status?", "reply_to": "Odpowiedź dla", - "replies_list": "Odpowiedzi:" + "replies_list": "Odpowiedzi:", + "mute_conversation": "Wycisz konwersację", + "unmute_conversation": "Odcisz konwersację", + "status_unavailable": "Status niedostępny", + "copy_link": "Kopiuj link do statusu" }, "user_card": { "approve": "Przyjmij", @@ -388,25 +638,60 @@ "followers": "Obserwujący", "following": "Obserwowany!", "follows_you": "Obserwuje cię!", + "hidden": "Ukryte", "its_you": "To ty!", "media": "Media", + "mention": "Wspomnienie", "mute": "Wycisz", "muted": "Wyciszony(-a)", "per_day": "dziennie", "remote_follow": "Zdalna obserwacja", + "report": "Zgłoś", "statuses": "Statusy", + "subscribe": "Subskrybuj", + "unsubscribe": "Odsubskrybuj", "unblock": "Odblokuj", "unblock_progress": "Odblokowuję…", "block_progress": "Blokuję…", "unmute": "Cofnij wyciszenie", "unmute_progress": "Cofam wyciszenie…", - "mute_progress": "Wyciszam…" + "mute_progress": "Wyciszam…", + "hide_repeats": "Ukryj powtórzenia", + "show_repeats": "Pokaż powtórzenia", + "admin_menu": { + "moderation": "Moderacja", + "grant_admin": "Przyznaj admina", + "revoke_admin": "Odwołaj admina", + "grant_moderator": "Przyznaj moderatora", + "revoke_moderator": "Odwołaj moderatora", + "activate_account": "Aktywuj konto", + "deactivate_account": "Dezaktywuj konto", + "delete_account": "Usuń konto", + "force_nsfw": "Oznacz wszystkie posty jako NSFW", + "strip_media": "Usuń multimedia z postów", + "force_unlisted": "Wymuś posty na niepubliczne", + "sandbox": "Wymuś by posty były tylko dla obserwujących", + "disable_remote_subscription": "Zakaż obserwowania użytkownika ze zdalnych instancji", + "disable_any_subscription": "Zakaż całkowicie obserwowania użytkownika", + "quarantine": "Zakaż federowania postów od tego użytkownika", + "delete_user": "Usuń użytkownika", + "delete_user_confirmation": "Czy jesteś absolutnie pewny(-a)? Ta operacja nie może być cofnięta." + } }, "user_profile": { "timeline_title": "Oś czasu użytkownika", "profile_does_not_exist": "Przepraszamy, ten profil nie istnieje.", "profile_loading_error": "Przepraszamy, wystąpił błąd podczas ładowania tego profilu." }, + "user_reporting": { + "title": "Raportowanie {0}", + "add_comment_description": "Zgłoszenie zostanie wysłane do moderatorów instancji. Możesz dodać powód dlaczego zgłaszasz owe konto poniżej:", + "additional_comments": "Dodatkowe komentarze", + "forward_description": "To konto jest z innego serwera. Wysłać również tam kopię zgłoszenia?", + "forward_to": "Przekaż do {0}", + "submit": "Wyślij", + "generic_error": "Wystąpił błąd podczas przetwarzania twojej prośby." + }, "who_to_follow": { "more": "Więcej", "who_to_follow": "Propozycje obserwacji" @@ -416,9 +701,12 @@ "repeat": "Powtórz", "reply": "Odpowiedz", "favorite": "Dodaj do ulubionych", - "user_settings": "Ustawienia użytkownika" + "add_reaction": "Dodaj reakcję", + "user_settings": "Ustawienia użytkownika", + "accept_follow_request": "Akceptuj prośbę o możliwość obserwacji", + "reject_follow_request": "Odrzuć prośbę o możliwość obserwacji" }, - "upload":{ + "upload": { "error": { "base": "Wysyłanie nie powiodło się.", "file_too_big": "Zbyt duży plik [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", @@ -431,5 +719,25 @@ "GiB": "GiB", "TiB": "TiB" } + }, + "search": { + "people": "Ludzie", + "hashtags": "Hasztagi", + "person_talking": "{count} osoba rozmawia o tym", + "people_talking": "{count} osób rozmawia o tym", + "no_results": "Brak wyników" + }, + "password_reset": { + "forgot_password": "Zapomniałeś(-aś) hasła?", + "password_reset": "Reset hasła", + "instruction": "Wprowadź swój adres email lub nazwę użytkownika. Wyślemy ci link z którym możesz zresetować hasło.", + "placeholder": "Twój email lub nazwa użytkownika", + "check_email": "Sprawdź pocztę, aby uzyskać link do zresetowania hasła.", + "return_home": "Wróć do strony głównej", + "not_found": "Nie mogliśmy znaleźć tego emaila lub nazwy użytkownika.", + "too_many_requests": "Przekroczyłeś(-aś) limit prób, spróbuj ponownie później.", + "password_reset_disabled": "Resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji.", + "password_reset_required": "Musisz zresetować hasło, by się zalogować.", + "password_reset_required_but_mailer_is_disabled": "Musisz zresetować hasło, ale resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji." } } diff --git a/src/i18n/ru.json b/src/i18n/ru.json index f8bcd996..f9a72954 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -13,7 +13,12 @@ "disable": "Оключить", "enable": "Включить", "confirm": "Подтвердить", - "verify": "Проверить" + "verify": "Проверить", + "more": "Больше", + "generic_error": "Произошла ошибка", + "optional": "не обязательно", + "show_less": "Показать меньше", + "show_more": "Показать больше" }, "login": { "login": "Войти", @@ -26,9 +31,9 @@ "enter_recovery_code": "Ввести код восстановления", "enter_two_factor_code": "Ввести код аутентификации", "recovery_code": "Код восстановления", - "heading" : { - "TotpForm" : "Двухфакторная аутентификация", - "RecoveryForm" : "Two-factor recovery" + "heading": { + "TotpForm": "Двухфакторная аутентификация", + "RecoveryForm": "Two-factor recovery" } }, "nav": { @@ -39,7 +44,8 @@ "public_tl": "Публичная лента", "timeline": "Лента", "twkn": "Федеративная лента", - "search": "Поиск" + "search": "Поиск", + "friend_requests": "Запросы на чтение" }, "notifications": { "broken_favorite": "Неизвестный статус, ищем...", @@ -48,7 +54,8 @@ "load_older": "Загрузить старые уведомления", "notifications": "Уведомления", "read": "Прочесть", - "repeated_you": "повторил(а) ваш статус" + "repeated_you": "повторил(а) ваш статус", + "follow_request": "хочет читать вас" }, "interactions": { "favs_repeats": "Повторы и фавориты", @@ -56,7 +63,7 @@ "load_older": "Загрузить старые взаимодействия" }, "post_status": { - "account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может зафоловить вас чтобы прочитать посты только для подписчиков", + "account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может начать читать вас чтобы видеть посты только для подписчиков.", "account_not_locked_warning_link": "залочен", "attachments_sensitive": "Вложения содержат чувствительный контент", "content_warning": "Тема (не обязательно)", @@ -94,17 +101,17 @@ "settings": { "enter_current_password_to_confirm": "Введите свой текущий пароль", "mfa": { - "otp" : "OTP", - "setup_otp" : "Настройка OTP", - "wait_pre_setup_otp" : "предварительная настройка OTP", - "confirm_and_enable" : "Подтвердить и включить OTP", + "otp": "OTP", + "setup_otp": "Настройка OTP", + "wait_pre_setup_otp": "предварительная настройка OTP", + "confirm_and_enable": "Подтвердить и включить OTP", "title": "Двухфакторная аутентификация", - "generate_new_recovery_codes" : "Получить новые коды востановления", - "warning_of_generate_new_codes" : "После получения новых кодов восстановления, старые больше не будут работать.", - "recovery_codes" : "Коды восстановления.", + "generate_new_recovery_codes": "Получить новые коды востановления", + "warning_of_generate_new_codes": "После получения новых кодов восстановления, старые больше не будут работать.", + "recovery_codes": "Коды восстановления.", "waiting_a_recovery_codes": "Получение кодов восстановления ...", - "recovery_codes_warning" : "Запишите эти коды и держите в безопасном месте - иначе вы их больше не увидите. Если вы потеряете доступ к OTP приложению - без резервных кодов вы больше не сможете залогиниться.", - "authentication_methods" : "Методы аутентификации", + "recovery_codes_warning": "Запишите эти коды и держите в безопасном месте - иначе вы их больше не увидите. Если вы потеряете доступ к OTP приложению - без резервных кодов вы больше не сможете залогиниться.", + "authentication_methods": "Методы аутентификации", "scan": { "title": "Сканирование", "desc": "Используйте приложение для двухэтапной аутентификации для сканирования этого QR-код или введите текстовый ключ:", @@ -129,10 +136,10 @@ "cRed": "Отменить", "change_email": "Сменить email", "change_email_error": "Произошла ошибка при попытке изменить email.", - "changed_email": "Email изменён успешно.", + "changed_email": "Email изменён успешно!", "change_password": "Сменить пароль", "change_password_error": "Произошла ошибка при попытке изменить пароль.", - "changed_password": "Пароль изменён успешно.", + "changed_password": "Пароль изменён успешно!", "collapse_subject": "Сворачивать посты с темой", "confirm_new_password": "Подтверждение нового пароля", "current_avatar": "Текущий аватар", @@ -150,7 +157,7 @@ "follow_export_button": "Экспортировать читаемых в файл .csv", "follow_export_processing": "Ведётся обработка, скоро вам будет предложено загрузить файл", "follow_import": "Импортировать читаемых", - "follow_import_error": "Ошибка при импортировании читаемых.", + "follow_import_error": "Ошибка при импортировании читаемых", "follows_imported": "Список читаемых импортирован. Обработка займёт некоторое время..", "foreground": "Передний план", "general": "Общие", @@ -174,6 +181,8 @@ "name_bio": "Имя и описание", "new_email": "Новый email", "new_password": "Новый пароль", + "fun": "Потешное", + "greentext": "Мемные стрелочки", "notification_visibility": "Показывать уведомления", "notification_visibility_follows": "Подписки", "notification_visibility_likes": "Лайки", @@ -202,7 +211,7 @@ "replies_in_timeline": "Ответы в ленте", "reply_link_preview": "Включить предварительный просмотр ответа при наведении мыши", "reply_visibility_all": "Показывать все ответы", - "reply_visibility_following": "Показывать только ответы мне и тех на кого я подписан", + "reply_visibility_following": "Показывать только ответы мне или тех на кого я подписан", "reply_visibility_self": "Показывать только ответы мне", "autohide_floating_post_button": "Автоматически скрывать кнопку постинга (в мобильной версии)", "saving_err": "Не удалось сохранить настройки", @@ -217,10 +226,12 @@ "subject_input_always_show": "Всегда показывать поле ввода темы", "stop_gifs": "Проигрывать GIF анимации только при наведении", "streaming": "Включить автоматическую загрузку новых сообщений при прокрутке вверх", + "useStreamingApi": "Получать сообщения и уведомления в реальном времени", + "useStreamingApiWarning": "(Не рекомендуется, экспериментально, сообщения могут пропадать)", "text": "Текст", "theme": "Тема", "theme_help": "Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.", - "theme_help_v2_1": "Вы так же можете перепоределить цвета определенных компонентов нажав соотв. галочку. Используйте кнопку \"Очистить всё\" чтобы снять все переопределения", + "theme_help_v2_1": "Вы так же можете перепоределить цвета определенных компонентов нажав соотв. галочку. Используйте кнопку \"Очистить всё\" чтобы снять все переопределения.", "theme_help_v2_2": "Под некоторыми полями ввода это идикаторы контрастности, наведите на них мышью чтобы узнать больше. Приспользовании прозрачности контраст расчитывается для наихудшего варианта.", "tooltipRadius": "Всплывающие подсказки/уведомления", "user_settings": "Настройки пользователя", @@ -288,9 +299,9 @@ "inset": "Внутренняя", "hint": "Для теней вы так же можете использовать --variable в качестве цвета чтобы использовать CSS3-переменные. В таком случае прозрачность работать не будет.", "filter_hint": { - "always_drop_shadow": "Внимание, эта тень всегда использует {0} когда браузер поддерживает это", - "drop_shadow_syntax": "{0} не поддерживает параметр {1} и ключевое слово {2}", - "avatar_inset": "Одновременное использование внутренних и внешних теней на (прозрачных) аватарках может дать не те результаты что вы ожидаете", + "always_drop_shadow": "Внимание, эта тень всегда использует {0} когда браузер поддерживает это.", + "drop_shadow_syntax": "{0} не поддерживает параметр {1} и ключевое слово {2}.", + "avatar_inset": "Одновременное использование внутренних и внешних теней на (прозрачных) аватарках может дать не те результаты что вы ожидаете.", "spread_zero": "Тени с разбросом > 0 будут выглядеть как если бы разброс установлен в 0", "inset_classic": "Внутренние тени будут использовать {0}" }, @@ -336,7 +347,13 @@ "checkbox": "Я подтверждаю что не было ни единого разрыва", "link": "ссылка" } - } + }, + "notification_setting_non_followers": "Не читающие вас", + "allow_following_move": "Разрешить автоматически читать новый аккаунт при перемещении на другой сервер", + "hide_user_stats": "Не показывать статистику пользователей (например количество читателей)", + "notification_setting_followers": "Читающие вас", + "notification_setting_follows": "Читаемые вами", + "notification_setting_non_follows": "Не читаемые вами" }, "timeline": { "collapse": "Свернуть", @@ -355,12 +372,12 @@ "follow": "Читать", "follow_sent": "Запрос отправлен!", "follow_progress": "Запрашиваем…", - "follow_again": "Запросить еще заново?", + "follow_again": "Запросить еще раз?", "follow_unfollow": "Перестать читать", "followees": "Читаемые", "followers": "Читатели", - "following": "Читаю", - "follows_you": "Читает вас", + "following": "Читаю!", + "follows_you": "Читает вас!", "mute": "Игнорировать", "muted": "Игнорирую", "per_day": "в день", @@ -378,9 +395,9 @@ "force_nsfw": "Отмечать посты пользователя как NSFW", "strip_media": "Убирать вложения из постов пользователя", "force_unlisted": "Не добавлять посты в публичные ленты", - "sandbox": "Посты доступны только для подписчиков", - "disable_remote_subscription": "Запретить подписываться с удаленных серверов", - "disable_any_subscription": "Запретить подписываться на пользователя", + "sandbox": "Принудить видимость постов только читателям", + "disable_remote_subscription": "Запретить читать с удаленных серверов", + "disable_any_subscription": "Запретить читать пользователя", "quarantine": "Не федерировать посты пользователя", "delete_user": "Удалить пользователя", "delete_user_confirmation": "Вы уверены? Это действие нельзя отменить." @@ -406,5 +423,56 @@ "not_found": "Мы не смогли найти аккаунт с таким email-ом или именем пользователя.", "too_many_requests": "Вы исчерпали допустимое количество попыток, попробуйте позже.", "password_reset_disabled": "Сброс пароля отключен. Cвяжитесь с администратором вашего сервера." + }, + "about": { + "mrf": { + "federation": "Федерация", + "simple": { + "accept_desc": "Данный сервер принимает сообщения только со следующих серверов:", + "ftl_removal_desc": "Данный сервер скрывает следующие сервера с федеративной ленты:", + "media_nsfw_desc": "Данный сервер принужденно помечает вложения со следущих серверов как NSFW:", + "simple_policies": "Правила для определенных серверов", + "accept": "Принимаемые сообщения", + "reject": "Отклоняемые сообщения", + "reject_desc": "Данный сервер не принимает сообщения со следующих серверов:", + "quarantine": "Зона карантина", + "quarantine_desc": "Данный сервер отправляет только публичные посты следующим серверам:", + "ftl_removal": "Скрытие с федеративной ленты", + "media_removal": "Удаление вложений", + "media_removal_desc": "Данный сервер удаляет вложения со следующих серверов:", + "media_nsfw": "Принужденно помеченно как NSFW" + }, + "keyword": { + "ftl_removal": "Убрать из федеративной ленты", + "reject": "Отклонить", + "keyword_policies": "Действия на ключевые слова", + "replace": "Заменить", + "is_replaced_by": "→" + }, + "mrf_policies": "Активные правила MRF (модуль переписывания сообщений)", + "mrf_policies_desc": "Правила MRF (модуль переписывания сообщений) влияют на федерацию данного сервера. Следующие правила активны:" + }, + "staff": "Администрация" + }, + "domain_mute_card": { + "mute": "Игнорировать", + "mute_progress": "В процессе…", + "unmute": "Прекратить игнорирование", + "unmute_progress": "В процессе…" + }, + "exporter": { + "export": "Экспорт", + "processing": "Запрос в обработке, вам скоро будет предложено загрузить файл" + }, + "features_panel": { + "chat": "Чат", + "media_proxy": "Прокси для внешних вложений", + "text_limit": "Лимит символов", + "title": "Особенности", + "gopher": "Gopher" + }, + "tool_tip": { + "accept_follow_request": "Принять запрос на чтение", + "reject_follow_request": "Отклонить запрос на чтение" } } diff --git a/src/i18n/te.json b/src/i18n/te.json index f0953d97..6022349d 100644 --- a/src/i18n/te.json +++ b/src/i18n/te.json @@ -1,352 +1,352 @@ { - "chat.title": "చాట్", - "features_panel.chat": "చాట్", - "features_panel.gopher": "గోఫర్", - "features_panel.media_proxy": "మీడియా ప్రాక్సీ", - "features_panel.scope_options": "స్కోప్ ఎంపికలు", - "features_panel.text_limit": "వచన పరిమితి", - "features_panel.title": "లక్షణాలు", - "features_panel.who_to_follow": "ఎవరిని అనుసరించాలి", - "finder.error_fetching_user": "వినియోగదారుని పొందడంలో లోపం", - "finder.find_user": "వినియోగదారుని కనుగొనండి", - "general.apply": "వర్తించు", - "general.submit": "సమర్పించు", - "general.more": "మరిన్ని", - "general.generic_error": "ఒక తప్పిదం సంభవించినది", - "general.optional": "ఐచ్చికం", - "image_cropper.crop_picture": "చిత్రాన్ని కత్తిరించండి", - "image_cropper.save": "దాచు", - "image_cropper.save_without_cropping": "కత్తిరించకుండా సేవ్ చేయి", - "image_cropper.cancel": "రద్దుచేయి", - "login.login": "లాగిన్", - "login.description": "OAuth తో లాగిన్ అవ్వండి", - "login.logout": "లాగౌట్", - "login.password": "సంకేతపదము", - "login.placeholder": "ఉదా. lain", - "login.register": "నమోదు చేసుకోండి", - "login.username": "వాడుకరి పేరు", - "login.hint": "చర్చలో చేరడానికి లాగిన్ అవ్వండి", - "media_modal.previous": "ముందరి పుట", - "media_modal.next": "తరువాత", - "nav.about": "గురించి", - "nav.back": "వెనక్కి", - "nav.chat": "స్థానిక చాట్", - "nav.friend_requests": "అనుసరించడానికి అభ్యర్థనలు", - "nav.mentions": "ప్రస్తావనలు", - "nav.dms": "నేరుగా పంపిన సందేశాలు", - "nav.public_tl": "ప్రజా కాలక్రమం", - "nav.timeline": "కాలక్రమం", - "nav.twkn": "మొత్తం తెలిసిన నెట్వర్క్", - "nav.user_search": "వాడుకరి శోధన", - "nav.who_to_follow": "ఎవరిని అనుసరించాలి", - "nav.preferences": "ప్రాధాన్యతలు", - "notifications.broken_favorite": "తెలియని స్థితి, దాని కోసం శోధిస్తోంది...", - "notifications.favorited_you": "మీ స్థితిని ఇష్టపడ్డారు", - "notifications.followed_you": "మిమ్మల్ని అనుసరించారు", - "notifications.load_older": "పాత నోటిఫికేషన్లను లోడ్ చేయండి", - "notifications.notifications": "ప్రకటనలు", - "notifications.read": "చదివాను!", - "notifications.repeated_you": "మీ స్థితిని పునరావృతం చేసారు", - "notifications.no_more_notifications": "ఇక నోటిఫికేషన్లు లేవు", - "post_status.new_status": "క్రొత్త స్థితిని పోస్ట్ చేయండి", - "post_status.account_not_locked_warning": "మీ ఖాతా {౦} కాదు. ఎవరైనా మిమ్మల్ని అనుసరించి అనుచరులకు మాత్రమే ఉద్దేశించిన పోస్టులను చూడవచ్చు.", - "post_status.account_not_locked_warning_link": "తాళం వేయబడినది", - "post_status.attachments_sensitive": "జోడింపులను సున్నితమైనవిగా గుర్తించండి", - "post_status.content_type.text/plain": "సాధారణ అక్షరాలు", - "post_status.content_type.text/html": "హెచ్టిఎమ్ఎల్", - "post_status.content_type.text/markdown": "మార్క్డౌన్", - "post_status.content_warning": "విషయం (ఐచ్ఛికం)", - "post_status.default": "ఇప్పుడే విజయవాడలో దిగాను.", - "post_status.direct_warning": "ఈ పోస్ట్ మాత్రమే పేర్కొన్న వినియోగదారులకు మాత్రమే కనిపిస్తుంది.", - "post_status.posting": "పోస్ట్ చేస్తున్నా", - "post_status.scope.direct": "ప్రత్యక్ష - పేర్కొన్న వినియోగదారులకు మాత్రమే పోస్ట్ చేయబడుతుంది", - "post_status.scope.private": "అనుచరులకు మాత్రమే - అనుచరులకు మాత్రమే పోస్ట్ చేయబడుతుంది", - "post_status.scope.public": "పబ్లిక్ - ప్రజా కాలక్రమాలకు పోస్ట్ చేయబడుతుంది", - "post_status.scope.unlisted": "జాబితా చేయబడనిది - ప్రజా కాలక్రమాలకు పోస్ట్ చేయవద్దు", - "registration.bio": "బయో", - "registration.email": "ఈ మెయిల్", - "registration.fullname": "ప్రదర్శన పేరు", - "registration.password_confirm": "పాస్వర్డ్ నిర్ధారణ", - "registration.registration": "నమోదు", - "registration.token": "ఆహ్వాన టోకెన్", - "registration.captcha": "కాప్చా", - "registration.new_captcha": "కొత్త కాప్చా పొందుటకు చిత్రం మీద క్లిక్ చేయండి", - "registration.username_placeholder": "ఉదా. lain", - "registration.fullname_placeholder": "ఉదా. Lain Iwakura", - "registration.bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.", - "registration.validations.username_required": "ఖాళీగా విడిచిపెట్టరాదు", - "registration.validations.fullname_required": "ఖాళీగా విడిచిపెట్టరాదు", - "registration.validations.email_required": "ఖాళీగా విడిచిపెట్టరాదు", - "registration.validations.password_required": "ఖాళీగా విడిచిపెట్టరాదు", - "registration.validations.password_confirmation_required": "ఖాళీగా విడిచిపెట్టరాదు", - "registration.validations.password_confirmation_match": "సంకేతపదం వలె ఉండాలి", - "settings.app_name": "అనువర్తన పేరు", - "settings.attachmentRadius": "జోడింపులు", - "settings.attachments": "జోడింపులు", - "settings.autoload": "క్రిందికి స్క్రోల్ చేయబడినప్పుడు స్వయంచాలక లోడింగ్ని ప్రారంభించు", - "settings.avatar": "అవతారం", - "settings.avatarAltRadius": "అవతారాలు (ప్రకటనలు)", - "settings.avatarRadius": "అవతారాలు", - "settings.background": "బ్యాక్గ్రౌండు", - "settings.bio": "బయో", - "settings.blocks_tab": "బ్లాక్లు", - "settings.btnRadius": "బటన్లు", - "settings.cBlue": "నీలం (ప్రత్యుత్తరం, అనుసరించండి)", - "settings.cGreen": "Green (Retweet)", - "settings.cOrange": "ఆరెంజ్ (ఇష్టపడు)", - "settings.cRed": "Red (Cancel)", - "settings.change_password": "పాస్వర్డ్ మార్చండి", - "settings.change_password_error": "మీ పాస్వర్డ్ను మార్చడంలో సమస్య ఉంది.", - "settings.changed_password": "పాస్వర్డ్ విజయవంతంగా మార్చబడింది!", - "settings.collapse_subject": "Collapse posts with subjects", - "settings.composing": "Composing", - "settings.confirm_new_password": "కొత్త పాస్వర్డ్ను నిర్ధారించండి", - "settings.current_avatar": "మీ ప్రస్తుత అవతారం", - "settings.current_password": "ప్రస్తుత పాస్వర్డ్", - "settings.current_profile_banner": "మీ ప్రస్తుత ప్రొఫైల్ బ్యానర్", - "settings.data_import_export_tab": "Data Import / Export", - "settings.default_vis": "Default visibility scope", - "settings.delete_account": "Delete Account", - "settings.delete_account_description": "మీ ఖాతా మరియు మీ అన్ని సందేశాలను శాశ్వతంగా తొలగించండి.", - "settings.delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.", - "settings.delete_account_instructions": "ఖాతా తొలగింపును నిర్ధారించడానికి దిగువ ఇన్పుట్లో మీ పాస్వర్డ్ను టైప్ చేయండి.", - "settings.avatar_size_instruction": "అవతార్ చిత్రాలకు సిఫార్సు చేసిన కనీస పరిమాణం 150x150 పిక్సెల్స్.", - "settings.export_theme": "Save preset", - "settings.filtering": "వడపోత", - "settings.filtering_explanation": "All statuses containing these words will be muted, one per line", - "settings.follow_export": "Follow export", - "settings.follow_export_button": "Export your follows to a csv file", - "settings.follow_export_processing": "Processing, you'll soon be asked to download your file", - "settings.follow_import": "Follow import", - "settings.follow_import_error": "అనుచరులను దిగుమతి చేయడంలో లోపం", - "settings.follows_imported": "Follows imported! Processing them will take a while.", - "settings.foreground": "Foreground", - "settings.general": "General", - "settings.hide_attachments_in_convo": "సంభాషణలలో జోడింపులను దాచు", - "settings.hide_attachments_in_tl": "కాలక్రమంలో జోడింపులను దాచు", - "settings.hide_muted_posts": "మ్యూట్ చేసిన వినియోగదారుల యొక్క పోస్ట్లను దాచిపెట్టు", - "settings.max_thumbnails": "Maximum amount of thumbnails per post", - "settings.hide_isp": "Hide instance-specific panel", - "settings.preload_images": "Preload images", - "settings.use_one_click_nsfw": "కేవలం ఒక క్లిక్ తో NSFW జోడింపులను తెరవండి", - "settings.hide_post_stats": "Hide post statistics (e.g. the number of favorites)", - "settings.hide_user_stats": "Hide user statistics (e.g. the number of followers)", - "settings.hide_filtered_statuses": "Hide filtered statuses", - "settings.import_followers_from_a_csv_file": "Import follows from a csv file", - "settings.import_theme": "Load preset", - "settings.inputRadius": "Input fields", - "settings.checkboxRadius": "Checkboxes", - "settings.instance_default": "(default: {value})", - "settings.instance_default_simple": "(default)", - "settings.interface": "Interface", - "settings.interfaceLanguage": "Interface language", - "settings.invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.", - "settings.limited_availability": "మీ బ్రౌజర్లో అందుబాటులో లేదు", - "settings.links": "Links", - "settings.lock_account_description": "మీ ఖాతాను ఆమోదించిన అనుచరులకు మాత్రమే పరిమితం చేయండి", - "settings.loop_video": "Loop videos", - "settings.loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")", - "settings.mutes_tab": "మ్యూట్ చేయబడినవి", - "settings.play_videos_in_modal": "మీడియా వీక్షికలో నేరుగా వీడియోలను ప్లే చేయి", - "settings.use_contain_fit": "అటాచ్మెంట్ సూక్ష్మచిత్రాలను కత్తిరించవద్దు", - "settings.name": "Name", - "settings.name_bio": "పేరు & బయో", - "settings.new_password": "కొత్త సంకేతపదం", - "settings.notification_visibility": "చూపించవలసిన నోటిఫికేషన్ రకాలు", - "settings.notification_visibility_follows": "Follows", - "settings.notification_visibility_likes": "ఇష్టాలు", - "settings.notification_visibility_mentions": "ప్రస్తావనలు", - "settings.notification_visibility_repeats": "పునఃప్రసారాలు", - "settings.no_rich_text_description": "అన్ని పోస్ట్ల నుండి రిచ్ టెక్స్ట్ ఫార్మాటింగ్ను స్ట్రిప్ చేయండి", - "settings.no_blocks": "బ్లాక్స్ లేవు", - "settings.no_mutes": "మ్యూట్లు లేవు", - "settings.hide_follows_description": "నేను ఎవరిని అనుసరిస్తున్నానో చూపించవద్దు", - "settings.hide_followers_description": "నన్ను ఎవరు అనుసరిస్తున్నారో చూపవద్దు", - "settings.show_admin_badge": "నా ప్రొఫైల్ లో అడ్మిన్ బ్యాడ్జ్ చూపించు", - "settings.show_moderator_badge": "నా ప్రొఫైల్లో మోడరేటర్ బ్యాడ్జ్ని చూపించు", - "settings.nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding", - "settings.oauth_tokens": "OAuth tokens", - "settings.token": "Token", - "settings.refresh_token": "Refresh Token", - "settings.valid_until": "Valid Until", - "settings.revoke_token": "Revoke", - "settings.panelRadius": "Panels", - "settings.pause_on_unfocused": "Pause streaming when tab is not focused", - "settings.presets": "Presets", - "settings.profile_background": "Profile Background", - "settings.profile_banner": "Profile Banner", - "settings.profile_tab": "Profile", - "settings.radii_help": "Set up interface edge rounding (in pixels)", - "settings.replies_in_timeline": "Replies in timeline", - "settings.reply_link_preview": "Enable reply-link preview on mouse hover", - "settings.reply_visibility_all": "Show all replies", - "settings.reply_visibility_following": "Only show replies directed at me or users I'm following", - "settings.reply_visibility_self": "Only show replies directed at me", - "settings.saving_err": "Error saving settings", - "settings.saving_ok": "Settings saved", - "settings.security_tab": "Security", - "settings.scope_copy": "Copy scope when replying (DMs are always copied)", - "settings.set_new_avatar": "Set new avatar", - "settings.set_new_profile_background": "Set new profile background", - "settings.set_new_profile_banner": "Set new profile banner", - "settings.settings": "Settings", - "settings.subject_input_always_show": "Always show subject field", - "settings.subject_line_behavior": "Copy subject when replying", - "settings.subject_line_email": "Like email: \"re: subject\"", - "settings.subject_line_mastodon": "Like mastodon: copy as is", - "settings.subject_line_noop": "Do not copy", - "settings.post_status_content_type": "Post status content type", - "settings.stop_gifs": "Play-on-hover GIFs", - "settings.streaming": "Enable automatic streaming of new posts when scrolled to the top", - "settings.text": "Text", - "settings.theme": "Theme", - "settings.theme_help": "Use hex color codes (#rrggbb) to customize your color theme.", - "settings.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.", - "settings.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.", - "settings.tooltipRadius": "Tooltips/alerts", - "settings.upload_a_photo": "Upload a photo", - "settings.user_settings": "User Settings", - "settings.values.false": "no", - "settings.values.true": "yes", - "settings.notifications": "Notifications", - "settings.enable_web_push_notifications": "Enable web push notifications", - "settings.style.switcher.keep_color": "Keep colors", - "settings.style.switcher.keep_shadows": "Keep shadows", - "settings.style.switcher.keep_opacity": "Keep opacity", - "settings.style.switcher.keep_roundness": "Keep roundness", - "settings.style.switcher.keep_fonts": "Keep fonts", - "settings.style.switcher.save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.", - "settings.style.switcher.reset": "Reset", - "settings.style.switcher.clear_all": "Clear all", - "settings.style.switcher.clear_opacity": "Clear opacity", - "settings.style.common.color": "Color", - "settings.style.common.opacity": "Opacity", - "settings.style.common.contrast.hint": "Contrast ratio is {ratio}, it {level} {context}", - "settings.style.common.contrast.level.aa": "meets Level AA guideline (minimal)", - "settings.style.common.contrast.level.aaa": "meets Level AAA guideline (recommended)", - "settings.style.common.contrast.level.bad": "doesn't meet any accessibility guidelines", - "settings.style.common.contrast.context.18pt": "for large (18pt+) text", - "settings.style.common.contrast.context.text": "for text", - "settings.style.common_colors._tab_label": "Common", - "settings.style.common_colors.main": "Common colors", - "settings.style.common_colors.foreground_hint": "See \"Advanced\" tab for more detailed control", - "settings.style.common_colors.rgbo": "Icons, accents, badges", - "settings.style.advanced_colors._tab_label": "Advanced", - "settings.style.advanced_colors.alert": "Alert background", - "settings.style.advanced_colors.alert_error": "Error", - "settings.style.advanced_colors.badge": "Badge background", - "settings.style.advanced_colors.badge_notification": "Notification", - "settings.style.advanced_colors.panel_header": "Panel header", - "settings.style.advanced_colors.top_bar": "Top bar", - "settings.style.advanced_colors.borders": "Borders", - "settings.style.advanced_colors.buttons": "Buttons", - "settings.style.advanced_colors.inputs": "Input fields", - "settings.style.advanced_colors.faint_text": "Faded text", - "settings.style.radii._tab_label": "Roundness", - "settings.style.shadows._tab_label": "Shadow and lighting", - "settings.style.shadows.component": "Component", - "settings.style.shadows.override": "Override", - "settings.style.shadows.shadow_id": "Shadow #{value}", - "settings.style.shadows.blur": "Blur", - "settings.style.shadows.spread": "Spread", - "settings.style.shadows.inset": "Inset", - "settings.style.shadows.hint": "For shadows you can also use --variable as a color value to use CSS3 variables. Please note that setting opacity won't work in this case.", - "settings.style.shadows.filter_hint.always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.", - "settings.style.shadows.filter_hint.drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.", - "settings.style.shadows.filter_hint.avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.", - "settings.style.shadows.filter_hint.spread_zero": "Shadows with spread > 0 will appear as if it was set to zero", - "settings.style.shadows.filter_hint.inset_classic": "Inset shadows will be using {0}", - "settings.style.shadows.components.panel": "Panel", - "settings.style.shadows.components.panelHeader": "Panel header", - "settings.style.shadows.components.topBar": "Top bar", - "settings.style.shadows.components.avatar": "User avatar (in profile view)", - "settings.style.shadows.components.avatarStatus": "User avatar (in post display)", - "settings.style.shadows.components.popup": "Popups and tooltips", - "settings.style.shadows.components.button": "Button", - "settings.style.shadows.components.buttonHover": "Button (hover)", - "settings.style.shadows.components.buttonPressed": "Button (pressed)", - "settings.style.shadows.components.buttonPressedHover": "Button (pressed+hover)", - "settings.style.shadows.components.input": "Input field", - "settings.style.fonts._tab_label": "Fonts", - "settings.style.fonts.help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.", - "settings.style.fonts.components.interface": "Interface", - "settings.style.fonts.components.input": "Input fields", - "settings.style.fonts.components.post": "Post text", - "settings.style.fonts.components.postCode": "Monospaced text in a post (rich text)", - "settings.style.fonts.family": "Font name", - "settings.style.fonts.size": "Size (in px)", - "settings.style.fonts.weight": "Weight (boldness)", - "settings.style.fonts.custom": "Custom", - "settings.style.preview.header": "Preview", - "settings.style.preview.content": "Content", - "settings.style.preview.error": "Example error", - "settings.style.preview.button": "Button", - "settings.style.preview.text": "A bunch of more {0} and {1}", - "settings.style.preview.mono": "content", - "settings.style.preview.input": "Just landed in L.A.", - "settings.style.preview.faint_link": "helpful manual", - "settings.style.preview.fine_print": "Read our {0} to learn nothing useful!", - "settings.style.preview.header_faint": "This is fine", - "settings.style.preview.checkbox": "I have skimmed over terms and conditions", - "settings.style.preview.link": "a nice lil' link", - "settings.version.title": "Version", - "settings.version.backend_version": "Backend Version", - "settings.version.frontend_version": "Frontend Version", - "timeline.collapse": "Collapse", - "timeline.conversation": "Conversation", - "timeline.error_fetching": "Error fetching updates", - "timeline.load_older": "Load older statuses", - "timeline.no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated", - "timeline.repeated": "repeated", - "timeline.show_new": "Show new", - "timeline.up_to_date": "Up-to-date", - "timeline.no_more_statuses": "No more statuses", - "timeline.no_statuses": "No statuses", - "status.reply_to": "Reply to", - "status.replies_list": "Replies:", - "user_card.approve": "Approve", - "user_card.block": "Block", - "user_card.blocked": "Blocked!", - "user_card.deny": "Deny", - "user_card.favorites": "Favorites", - "user_card.follow": "Follow", - "user_card.follow_sent": "Request sent!", - "user_card.follow_progress": "Requesting…", - "user_card.follow_again": "Send request again?", - "user_card.follow_unfollow": "Unfollow", - "user_card.followees": "Following", - "user_card.followers": "Followers", - "user_card.following": "Following!", - "user_card.follows_you": "Follows you!", - "user_card.its_you": "It's you!", - "user_card.media": "Media", - "user_card.mute": "Mute", - "user_card.muted": "Muted", - "user_card.per_day": "per day", - "user_card.remote_follow": "Remote follow", - "user_card.statuses": "Statuses", - "user_card.unblock": "Unblock", - "user_card.unblock_progress": "Unblocking...", - "user_card.block_progress": "Blocking...", - "user_card.unmute": "Unmute", - "user_card.unmute_progress": "Unmuting...", - "user_card.mute_progress": "Muting...", - "user_profile.timeline_title": "User Timeline", - "user_profile.profile_does_not_exist": "Sorry, this profile does not exist.", - "user_profile.profile_loading_error": "Sorry, there was an error loading this profile.", - "who_to_follow.more": "More", - "who_to_follow.who_to_follow": "Who to follow", - "tool_tip.media_upload": "Upload Media", - "tool_tip.repeat": "Repeat", - "tool_tip.reply": "Reply", - "tool_tip.favorite": "Favorite", - "tool_tip.user_settings": "User Settings", - "upload.error.base": "Upload failed.", - "upload.error.file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", - "upload.error.default": "Try again later", - "upload.file_size_units.B": "B", - "upload.file_size_units.KiB": "KiB", - "upload.file_size_units.MiB": "MiB", - "upload.file_size_units.GiB": "GiB", - "upload.file_size_units.TiB": "TiB" + "chat.title": "చాట్", + "features_panel.chat": "చాట్", + "features_panel.gopher": "గోఫర్", + "features_panel.media_proxy": "మీడియా ప్రాక్సీ", + "features_panel.scope_options": "స్కోప్ ఎంపికలు", + "features_panel.text_limit": "వచన పరిమితి", + "features_panel.title": "లక్షణాలు", + "features_panel.who_to_follow": "ఎవరిని అనుసరించాలి", + "finder.error_fetching_user": "వినియోగదారుని పొందడంలో లోపం", + "finder.find_user": "వినియోగదారుని కనుగొనండి", + "general.apply": "వర్తించు", + "general.submit": "సమర్పించు", + "general.more": "మరిన్ని", + "general.generic_error": "ఒక తప్పిదం సంభవించినది", + "general.optional": "ఐచ్చికం", + "image_cropper.crop_picture": "చిత్రాన్ని కత్తిరించండి", + "image_cropper.save": "దాచు", + "image_cropper.save_without_cropping": "కత్తిరించకుండా సేవ్ చేయి", + "image_cropper.cancel": "రద్దుచేయి", + "login.login": "లాగిన్", + "login.description": "OAuth తో లాగిన్ అవ్వండి", + "login.logout": "లాగౌట్", + "login.password": "సంకేతపదము", + "login.placeholder": "ఉదా. lain", + "login.register": "నమోదు చేసుకోండి", + "login.username": "వాడుకరి పేరు", + "login.hint": "చర్చలో చేరడానికి లాగిన్ అవ్వండి", + "media_modal.previous": "ముందరి పుట", + "media_modal.next": "తరువాత", + "nav.about": "గురించి", + "nav.back": "వెనక్కి", + "nav.chat": "స్థానిక చాట్", + "nav.friend_requests": "అనుసరించడానికి అభ్యర్థనలు", + "nav.mentions": "ప్రస్తావనలు", + "nav.dms": "నేరుగా పంపిన సందేశాలు", + "nav.public_tl": "ప్రజా కాలక్రమం", + "nav.timeline": "కాలక్రమం", + "nav.twkn": "మొత్తం తెలిసిన నెట్వర్క్", + "nav.user_search": "వాడుకరి శోధన", + "nav.who_to_follow": "ఎవరిని అనుసరించాలి", + "nav.preferences": "ప్రాధాన్యతలు", + "notifications.broken_favorite": "తెలియని స్థితి, దాని కోసం శోధిస్తోంది...", + "notifications.favorited_you": "మీ స్థితిని ఇష్టపడ్డారు", + "notifications.followed_you": "మిమ్మల్ని అనుసరించారు", + "notifications.load_older": "పాత నోటిఫికేషన్లను లోడ్ చేయండి", + "notifications.notifications": "ప్రకటనలు", + "notifications.read": "చదివాను!", + "notifications.repeated_you": "మీ స్థితిని పునరావృతం చేసారు", + "notifications.no_more_notifications": "ఇక నోటిఫికేషన్లు లేవు", + "post_status.new_status": "క్రొత్త స్థితిని పోస్ట్ చేయండి", + "post_status.account_not_locked_warning": "మీ ఖాతా {౦} కాదు. ఎవరైనా మిమ్మల్ని అనుసరించి అనుచరులకు మాత్రమే ఉద్దేశించిన పోస్టులను చూడవచ్చు.", + "post_status.account_not_locked_warning_link": "తాళం వేయబడినది", + "post_status.attachments_sensitive": "జోడింపులను సున్నితమైనవిగా గుర్తించండి", + "post_status.content_type.text/plain": "సాధారణ అక్షరాలు", + "post_status.content_type.text/html": "హెచ్టిఎమ్ఎల్", + "post_status.content_type.text/markdown": "మార్క్డౌన్", + "post_status.content_warning": "విషయం (ఐచ్ఛికం)", + "post_status.default": "ఇప్పుడే విజయవాడలో దిగాను.", + "post_status.direct_warning": "ఈ పోస్ట్ మాత్రమే పేర్కొన్న వినియోగదారులకు మాత్రమే కనిపిస్తుంది.", + "post_status.posting": "పోస్ట్ చేస్తున్నా", + "post_status.scope.direct": "ప్రత్యక్ష - పేర్కొన్న వినియోగదారులకు మాత్రమే పోస్ట్ చేయబడుతుంది", + "post_status.scope.private": "అనుచరులకు మాత్రమే - అనుచరులకు మాత్రమే పోస్ట్ చేయబడుతుంది", + "post_status.scope.public": "పబ్లిక్ - ప్రజా కాలక్రమాలకు పోస్ట్ చేయబడుతుంది", + "post_status.scope.unlisted": "జాబితా చేయబడనిది - ప్రజా కాలక్రమాలకు పోస్ట్ చేయవద్దు", + "registration.bio": "బయో", + "registration.email": "ఈ మెయిల్", + "registration.fullname": "ప్రదర్శన పేరు", + "registration.password_confirm": "పాస్వర్డ్ నిర్ధారణ", + "registration.registration": "నమోదు", + "registration.token": "ఆహ్వాన టోకెన్", + "registration.captcha": "కాప్చా", + "registration.new_captcha": "కొత్త కాప్చా పొందుటకు చిత్రం మీద క్లిక్ చేయండి", + "registration.username_placeholder": "ఉదా. lain", + "registration.fullname_placeholder": "ఉదా. Lain Iwakura", + "registration.bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.", + "registration.validations.username_required": "ఖాళీగా విడిచిపెట్టరాదు", + "registration.validations.fullname_required": "ఖాళీగా విడిచిపెట్టరాదు", + "registration.validations.email_required": "ఖాళీగా విడిచిపెట్టరాదు", + "registration.validations.password_required": "ఖాళీగా విడిచిపెట్టరాదు", + "registration.validations.password_confirmation_required": "ఖాళీగా విడిచిపెట్టరాదు", + "registration.validations.password_confirmation_match": "సంకేతపదం వలె ఉండాలి", + "settings.app_name": "అనువర్తన పేరు", + "settings.attachmentRadius": "జోడింపులు", + "settings.attachments": "జోడింపులు", + "settings.autoload": "క్రిందికి స్క్రోల్ చేయబడినప్పుడు స్వయంచాలక లోడింగ్ని ప్రారంభించు", + "settings.avatar": "అవతారం", + "settings.avatarAltRadius": "అవతారాలు (ప్రకటనలు)", + "settings.avatarRadius": "అవతారాలు", + "settings.background": "బ్యాక్గ్రౌండు", + "settings.bio": "బయో", + "settings.blocks_tab": "బ్లాక్లు", + "settings.btnRadius": "బటన్లు", + "settings.cBlue": "నీలం (ప్రత్యుత్తరం, అనుసరించండి)", + "settings.cGreen": "Green (Retweet)", + "settings.cOrange": "ఆరెంజ్ (ఇష్టపడు)", + "settings.cRed": "Red (Cancel)", + "settings.change_password": "పాస్వర్డ్ మార్చండి", + "settings.change_password_error": "మీ పాస్వర్డ్ను మార్చడంలో సమస్య ఉంది.", + "settings.changed_password": "పాస్వర్డ్ విజయవంతంగా మార్చబడింది!", + "settings.collapse_subject": "Collapse posts with subjects", + "settings.composing": "Composing", + "settings.confirm_new_password": "కొత్త పాస్వర్డ్ను నిర్ధారించండి", + "settings.current_avatar": "మీ ప్రస్తుత అవతారం", + "settings.current_password": "ప్రస్తుత పాస్వర్డ్", + "settings.current_profile_banner": "మీ ప్రస్తుత ప్రొఫైల్ బ్యానర్", + "settings.data_import_export_tab": "Data Import / Export", + "settings.default_vis": "Default visibility scope", + "settings.delete_account": "Delete Account", + "settings.delete_account_description": "మీ ఖాతా మరియు మీ అన్ని సందేశాలను శాశ్వతంగా తొలగించండి.", + "settings.delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.", + "settings.delete_account_instructions": "ఖాతా తొలగింపును నిర్ధారించడానికి దిగువ ఇన్పుట్లో మీ పాస్వర్డ్ను టైప్ చేయండి.", + "settings.avatar_size_instruction": "అవతార్ చిత్రాలకు సిఫార్సు చేసిన కనీస పరిమాణం 150x150 పిక్సెల్స్.", + "settings.export_theme": "Save preset", + "settings.filtering": "వడపోత", + "settings.filtering_explanation": "All statuses containing these words will be muted, one per line", + "settings.follow_export": "Follow export", + "settings.follow_export_button": "Export your follows to a csv file", + "settings.follow_export_processing": "Processing, you'll soon be asked to download your file", + "settings.follow_import": "Follow import", + "settings.follow_import_error": "అనుచరులను దిగుమతి చేయడంలో లోపం", + "settings.follows_imported": "Follows imported! Processing them will take a while.", + "settings.foreground": "Foreground", + "settings.general": "General", + "settings.hide_attachments_in_convo": "సంభాషణలలో జోడింపులను దాచు", + "settings.hide_attachments_in_tl": "కాలక్రమంలో జోడింపులను దాచు", + "settings.hide_muted_posts": "మ్యూట్ చేసిన వినియోగదారుల యొక్క పోస్ట్లను దాచిపెట్టు", + "settings.max_thumbnails": "Maximum amount of thumbnails per post", + "settings.hide_isp": "Hide instance-specific panel", + "settings.preload_images": "Preload images", + "settings.use_one_click_nsfw": "కేవలం ఒక క్లిక్ తో NSFW జోడింపులను తెరవండి", + "settings.hide_post_stats": "Hide post statistics (e.g. the number of favorites)", + "settings.hide_user_stats": "Hide user statistics (e.g. the number of followers)", + "settings.hide_filtered_statuses": "Hide filtered statuses", + "settings.import_followers_from_a_csv_file": "Import follows from a csv file", + "settings.import_theme": "Load preset", + "settings.inputRadius": "Input fields", + "settings.checkboxRadius": "Checkboxes", + "settings.instance_default": "(default: {value})", + "settings.instance_default_simple": "(default)", + "settings.interface": "Interface", + "settings.interfaceLanguage": "Interface language", + "settings.invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.", + "settings.limited_availability": "మీ బ్రౌజర్లో అందుబాటులో లేదు", + "settings.links": "Links", + "settings.lock_account_description": "మీ ఖాతాను ఆమోదించిన అనుచరులకు మాత్రమే పరిమితం చేయండి", + "settings.loop_video": "Loop videos", + "settings.loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")", + "settings.mutes_tab": "మ్యూట్ చేయబడినవి", + "settings.play_videos_in_modal": "మీడియా వీక్షికలో నేరుగా వీడియోలను ప్లే చేయి", + "settings.use_contain_fit": "అటాచ్మెంట్ సూక్ష్మచిత్రాలను కత్తిరించవద్దు", + "settings.name": "Name", + "settings.name_bio": "పేరు & బయో", + "settings.new_password": "కొత్త సంకేతపదం", + "settings.notification_visibility": "చూపించవలసిన నోటిఫికేషన్ రకాలు", + "settings.notification_visibility_follows": "Follows", + "settings.notification_visibility_likes": "ఇష్టాలు", + "settings.notification_visibility_mentions": "ప్రస్తావనలు", + "settings.notification_visibility_repeats": "పునఃప్రసారాలు", + "settings.no_rich_text_description": "అన్ని పోస్ట్ల నుండి రిచ్ టెక్స్ట్ ఫార్మాటింగ్ను స్ట్రిప్ చేయండి", + "settings.no_blocks": "బ్లాక్స్ లేవు", + "settings.no_mutes": "మ్యూట్లు లేవు", + "settings.hide_follows_description": "నేను ఎవరిని అనుసరిస్తున్నానో చూపించవద్దు", + "settings.hide_followers_description": "నన్ను ఎవరు అనుసరిస్తున్నారో చూపవద్దు", + "settings.show_admin_badge": "నా ప్రొఫైల్ లో అడ్మిన్ బ్యాడ్జ్ చూపించు", + "settings.show_moderator_badge": "నా ప్రొఫైల్లో మోడరేటర్ బ్యాడ్జ్ని చూపించు", + "settings.nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding", + "settings.oauth_tokens": "OAuth tokens", + "settings.token": "Token", + "settings.refresh_token": "Refresh Token", + "settings.valid_until": "Valid Until", + "settings.revoke_token": "Revoke", + "settings.panelRadius": "Panels", + "settings.pause_on_unfocused": "Pause streaming when tab is not focused", + "settings.presets": "Presets", + "settings.profile_background": "Profile Background", + "settings.profile_banner": "Profile Banner", + "settings.profile_tab": "Profile", + "settings.radii_help": "Set up interface edge rounding (in pixels)", + "settings.replies_in_timeline": "Replies in timeline", + "settings.reply_link_preview": "Enable reply-link preview on mouse hover", + "settings.reply_visibility_all": "Show all replies", + "settings.reply_visibility_following": "Only show replies directed at me or users I'm following", + "settings.reply_visibility_self": "Only show replies directed at me", + "settings.saving_err": "Error saving settings", + "settings.saving_ok": "Settings saved", + "settings.security_tab": "Security", + "settings.scope_copy": "Copy scope when replying (DMs are always copied)", + "settings.set_new_avatar": "Set new avatar", + "settings.set_new_profile_background": "Set new profile background", + "settings.set_new_profile_banner": "Set new profile banner", + "settings.settings": "Settings", + "settings.subject_input_always_show": "Always show subject field", + "settings.subject_line_behavior": "Copy subject when replying", + "settings.subject_line_email": "Like email: \"re: subject\"", + "settings.subject_line_mastodon": "Like mastodon: copy as is", + "settings.subject_line_noop": "Do not copy", + "settings.post_status_content_type": "Post status content type", + "settings.stop_gifs": "Play-on-hover GIFs", + "settings.streaming": "Enable automatic streaming of new posts when scrolled to the top", + "settings.text": "Text", + "settings.theme": "Theme", + "settings.theme_help": "Use hex color codes (#rrggbb) to customize your color theme.", + "settings.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.", + "settings.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.", + "settings.tooltipRadius": "Tooltips/alerts", + "settings.upload_a_photo": "Upload a photo", + "settings.user_settings": "User Settings", + "settings.values.false": "no", + "settings.values.true": "yes", + "settings.notifications": "Notifications", + "settings.enable_web_push_notifications": "Enable web push notifications", + "settings.style.switcher.keep_color": "Keep colors", + "settings.style.switcher.keep_shadows": "Keep shadows", + "settings.style.switcher.keep_opacity": "Keep opacity", + "settings.style.switcher.keep_roundness": "Keep roundness", + "settings.style.switcher.keep_fonts": "Keep fonts", + "settings.style.switcher.save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.", + "settings.style.switcher.reset": "Reset", + "settings.style.switcher.clear_all": "Clear all", + "settings.style.switcher.clear_opacity": "Clear opacity", + "settings.style.common.color": "Color", + "settings.style.common.opacity": "Opacity", + "settings.style.common.contrast.hint": "Contrast ratio is {ratio}, it {level} {context}", + "settings.style.common.contrast.level.aa": "meets Level AA guideline (minimal)", + "settings.style.common.contrast.level.aaa": "meets Level AAA guideline (recommended)", + "settings.style.common.contrast.level.bad": "doesn't meet any accessibility guidelines", + "settings.style.common.contrast.context.18pt": "for large (18pt+) text", + "settings.style.common.contrast.context.text": "for text", + "settings.style.common_colors._tab_label": "Common", + "settings.style.common_colors.main": "Common colors", + "settings.style.common_colors.foreground_hint": "See \"Advanced\" tab for more detailed control", + "settings.style.common_colors.rgbo": "Icons, accents, badges", + "settings.style.advanced_colors._tab_label": "Advanced", + "settings.style.advanced_colors.alert": "Alert background", + "settings.style.advanced_colors.alert_error": "Error", + "settings.style.advanced_colors.badge": "Badge background", + "settings.style.advanced_colors.badge_notification": "Notification", + "settings.style.advanced_colors.panel_header": "Panel header", + "settings.style.advanced_colors.top_bar": "Top bar", + "settings.style.advanced_colors.borders": "Borders", + "settings.style.advanced_colors.buttons": "Buttons", + "settings.style.advanced_colors.inputs": "Input fields", + "settings.style.advanced_colors.faint_text": "Faded text", + "settings.style.radii._tab_label": "Roundness", + "settings.style.shadows._tab_label": "Shadow and lighting", + "settings.style.shadows.component": "Component", + "settings.style.shadows.override": "Override", + "settings.style.shadows.shadow_id": "Shadow #{value}", + "settings.style.shadows.blur": "Blur", + "settings.style.shadows.spread": "Spread", + "settings.style.shadows.inset": "Inset", + "settings.style.shadows.hint": "For shadows you can also use --variable as a color value to use CSS3 variables. Please note that setting opacity won't work in this case.", + "settings.style.shadows.filter_hint.always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.", + "settings.style.shadows.filter_hint.drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.", + "settings.style.shadows.filter_hint.avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.", + "settings.style.shadows.filter_hint.spread_zero": "Shadows with spread > 0 will appear as if it was set to zero", + "settings.style.shadows.filter_hint.inset_classic": "Inset shadows will be using {0}", + "settings.style.shadows.components.panel": "Panel", + "settings.style.shadows.components.panelHeader": "Panel header", + "settings.style.shadows.components.topBar": "Top bar", + "settings.style.shadows.components.avatar": "User avatar (in profile view)", + "settings.style.shadows.components.avatarStatus": "User avatar (in post display)", + "settings.style.shadows.components.popup": "Popups and tooltips", + "settings.style.shadows.components.button": "Button", + "settings.style.shadows.components.buttonHover": "Button (hover)", + "settings.style.shadows.components.buttonPressed": "Button (pressed)", + "settings.style.shadows.components.buttonPressedHover": "Button (pressed+hover)", + "settings.style.shadows.components.input": "Input field", + "settings.style.fonts._tab_label": "Fonts", + "settings.style.fonts.help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.", + "settings.style.fonts.components.interface": "Interface", + "settings.style.fonts.components.input": "Input fields", + "settings.style.fonts.components.post": "Post text", + "settings.style.fonts.components.postCode": "Monospaced text in a post (rich text)", + "settings.style.fonts.family": "Font name", + "settings.style.fonts.size": "Size (in px)", + "settings.style.fonts.weight": "Weight (boldness)", + "settings.style.fonts.custom": "Custom", + "settings.style.preview.header": "Preview", + "settings.style.preview.content": "Content", + "settings.style.preview.error": "Example error", + "settings.style.preview.button": "Button", + "settings.style.preview.text": "A bunch of more {0} and {1}", + "settings.style.preview.mono": "content", + "settings.style.preview.input": "Just landed in L.A.", + "settings.style.preview.faint_link": "helpful manual", + "settings.style.preview.fine_print": "Read our {0} to learn nothing useful!", + "settings.style.preview.header_faint": "This is fine", + "settings.style.preview.checkbox": "I have skimmed over terms and conditions", + "settings.style.preview.link": "a nice lil' link", + "settings.version.title": "Version", + "settings.version.backend_version": "Backend Version", + "settings.version.frontend_version": "Frontend Version", + "timeline.collapse": "Collapse", + "timeline.conversation": "Conversation", + "timeline.error_fetching": "Error fetching updates", + "timeline.load_older": "Load older statuses", + "timeline.no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated", + "timeline.repeated": "repeated", + "timeline.show_new": "Show new", + "timeline.up_to_date": "Up-to-date", + "timeline.no_more_statuses": "No more statuses", + "timeline.no_statuses": "No statuses", + "status.reply_to": "Reply to", + "status.replies_list": "Replies:", + "user_card.approve": "Approve", + "user_card.block": "Block", + "user_card.blocked": "Blocked!", + "user_card.deny": "Deny", + "user_card.favorites": "Favorites", + "user_card.follow": "Follow", + "user_card.follow_sent": "Request sent!", + "user_card.follow_progress": "Requesting…", + "user_card.follow_again": "Send request again?", + "user_card.follow_unfollow": "Unfollow", + "user_card.followees": "Following", + "user_card.followers": "Followers", + "user_card.following": "Following!", + "user_card.follows_you": "Follows you!", + "user_card.its_you": "It's you!", + "user_card.media": "Media", + "user_card.mute": "Mute", + "user_card.muted": "Muted", + "user_card.per_day": "per day", + "user_card.remote_follow": "Remote follow", + "user_card.statuses": "Statuses", + "user_card.unblock": "Unblock", + "user_card.unblock_progress": "Unblocking...", + "user_card.block_progress": "Blocking...", + "user_card.unmute": "Unmute", + "user_card.unmute_progress": "Unmuting...", + "user_card.mute_progress": "Muting...", + "user_profile.timeline_title": "User Timeline", + "user_profile.profile_does_not_exist": "Sorry, this profile does not exist.", + "user_profile.profile_loading_error": "Sorry, there was an error loading this profile.", + "who_to_follow.more": "More", + "who_to_follow.who_to_follow": "Who to follow", + "tool_tip.media_upload": "Upload Media", + "tool_tip.repeat": "Repeat", + "tool_tip.reply": "Reply", + "tool_tip.favorite": "Favorite", + "tool_tip.user_settings": "User Settings", + "upload.error.base": "Upload failed.", + "upload.error.file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", + "upload.error.default": "Try again later", + "upload.file_size_units.B": "B", + "upload.file_size_units.KiB": "KiB", + "upload.file_size_units.MiB": "MiB", + "upload.file_size_units.GiB": "GiB", + "upload.file_size_units.TiB": "TiB" } diff --git a/src/i18n/zh.json b/src/i18n/zh.json index 80c4e0d8..f95dc498 100644 --- a/src/i18n/zh.json +++ b/src/i18n/zh.json @@ -25,13 +25,14 @@ "more": "更多", "generic_error": "发生一个错误", "optional": "可选项", - "show_more": "显示更多", - "show_less": "显示更少", + "show_more": "展开", + "show_less": "收起", "cancel": "取消", "disable": "禁用", "enable": "启用", "confirm": "确认", - "verify": "验证" + "verify": "验证", + "dismiss": "忽略" }, "image_cropper": { "crop_picture": "裁剪图片", @@ -57,9 +58,9 @@ "enter_recovery_code": "输入一个恢复码", "enter_two_factor_code": "输入一个双重因素验证码", "recovery_code": "恢复码", - "heading" : { - "totp" : "双重因素验证", - "recovery" : "双重因素恢复" + "heading": { + "totp": "双重因素验证", + "recovery": "双重因素恢复" } }, "media_modal": { @@ -68,8 +69,8 @@ }, "nav": { "about": "关于", - "back": "Back", - "chat": "本地聊天", + "back": "后退", + "chat": "本站聊天", "friend_requests": "关注请求", "mentions": "提及", "interactions": "互动", @@ -80,7 +81,8 @@ "user_search": "用户搜索", "search": "搜索", "who_to_follow": "推荐关注", - "preferences": "偏好设置" + "preferences": "偏好设置", + "administration": "管理员" }, "notifications": { "broken_favorite": "未知的状态,正在搜索中...", @@ -90,7 +92,10 @@ "notifications": "通知", "read": "阅读!", "repeated_you": "转发了你的状态", - "no_more_notifications": "没有更多的通知" + "no_more_notifications": "没有更多的通知", + "reacted_with": "和 {0} 互动过", + "migrated_to": "迁移到", + "follow_request": "想要关注你" }, "polls": { "add_poll": "增加问卷调查", @@ -111,8 +116,9 @@ }, "interactions": { "favs_repeats": "转发和收藏", - "follows": "新的关注着", - "load_older": "加载更早的互动" + "follows": "新的关注者", + "load_older": "加载更早的互动", + "moves": "用户迁移" }, "post_status": { "new_status": "发布新状态", @@ -151,9 +157,9 @@ "token": "邀请码", "captcha": "CAPTCHA", "new_captcha": "点击图片获取新的验证码", - "username_placeholder": "例如: lain", - "fullname_placeholder": "例如: Lain Iwakura", - "bio_placeholder": "例如:\n你好, 我是 Lain.\n我是一个住在上海的宅男。你可能在某处见过我。", + "username_placeholder": "例如:lain", + "fullname_placeholder": "例如:岩仓玲音", + "bio_placeholder": "例如:\n你好,我是玲音。\n我是一个住在日本郊区的动画少女。你可能在 Wired 见过我。", "validations": { "username_required": "不能留空", "fullname_required": "不能留空", @@ -171,17 +177,17 @@ "security": "安全", "enter_current_password_to_confirm": "输入你当前密码来确认你的身份", "mfa": { - "otp" : "OTP", - "setup_otp" : "设置 OTP", - "wait_pre_setup_otp" : "预设 OTP", - "confirm_and_enable" : "确认并启用 OTP", + "otp": "OTP", + "setup_otp": "设置 OTP", + "wait_pre_setup_otp": "预设 OTP", + "confirm_and_enable": "确认并启用 OTP", "title": "双因素验证", - "generate_new_recovery_codes" : "生成新的恢复码", - "warning_of_generate_new_codes" : "当你生成新的恢复码时,你的就恢复码就失效了。", - "recovery_codes" : "恢复码。", - "waiting_a_recovery_codes": "接受备份码。。。", - "recovery_codes_warning" : "抄写这些号码,或者保存在安全的地方。这些号码不会再次显示。如果你无法访问你的 2FA app,也丢失了你的恢复码,你的账号就再也无法登录了。", - "authentication_methods" : "身份验证方法", + "generate_new_recovery_codes": "生成新的恢复码", + "warning_of_generate_new_codes": "当你生成新的恢复码时,你的旧恢复码就失效了。", + "recovery_codes": "恢复码。", + "waiting_a_recovery_codes": "正在接收备份码……", + "recovery_codes_warning": "抄写这些号码,或者保存在安全的地方。这些号码不会再次显示。如果你无法访问你的 2FA app,也丢失了你的恢复码,你的账号就再也无法登录了。", + "authentication_methods": "身份验证方法", "scan": { "title": "扫一下", "desc": "使用你的双因素验证 app,扫描这个二维码,或者输入这些文字密钥:", @@ -222,7 +228,7 @@ "data_import_export_tab": "数据导入/导出", "default_vis": "默认可见范围", "delete_account": "删除账户", - "delete_account_description": "永久删除你的帐号和所有消息。", + "delete_account_description": "永久删除你的帐号和所有数据。", "delete_account_error": "删除账户时发生错误,如果一直删除不了,请联系实例管理员。", "delete_account_instructions": "在下面输入你的密码来确认删除账户", "avatar_size_instruction": "推荐的头像图片最小的尺寸是 150x150 像素。", @@ -263,7 +269,7 @@ "loop_video_silent_only": "只循环没有声音的视频(例如:Mastodon 里的“GIF”)", "mutes_tab": "隐藏", "play_videos_in_modal": "在弹出框内播放视频", - "use_contain_fit": "生成缩略图时不要裁剪附件。", + "use_contain_fit": "生成缩略图时不要裁剪附件", "name": "名字", "name_bio": "名字及简介", "new_password": "新密码", @@ -348,7 +354,14 @@ "save_load_hint": "\"保留\" 选项在选择或加载主题时保留当前设置的选项,在导出主题时还会存储上述选项。当所有复选框未设置时,导出主题将保存所有内容。", "reset": "重置", "clear_all": "清除全部", - "clear_opacity": "清除透明度" + "clear_opacity": "清除透明度", + "load_theme": "加载主题", + "help": { + "upgraded_from_v2": "PleromaFE 已升级,主题会和你记忆中的不太一样。" + }, + "use_source": "新版本", + "use_snapshot": "老版本", + "keep_as_is": "保持原状" }, "common": { "color": "颜色", @@ -441,7 +454,7 @@ "mono": "内容", "input": "刚刚抵达上海", "faint_link": "帮助菜单", - "fine_print": "阅读我们的 {0} 学不到什么东东!", + "fine_print": "阅读我们的 {0} ,然而什么也学不到!", "header_faint": "这很正常", "checkbox": "我已经浏览了 TOC", "link": "一个很棒的摇滚链接" @@ -451,7 +464,20 @@ "title": "版本", "backend_version": "后端版本", "frontend_version": "前端版本" - } + }, + "notification_setting_filters": "过滤器", + "domain_mutes": "域名", + "changed_email": "邮箱修改成功!", + "change_email_error": "修改你的电子邮箱时发生错误", + "change_email": "修改电子邮箱", + "allow_following_move": "正在关注的账号迁移时自动重新关注", + "notification_setting_privacy_option": "在通知推送中隐藏发送者和内容", + "notification_setting_privacy": "隐私", + "hide_follows_count_description": "不显示关注数", + "notification_visibility_emoji_reactions": "互动", + "notification_visibility_moves": "用户迁移", + "new_email": "新邮箱", + "emoji_reactions_on_timeline": "在时间线上显示表情符号互动" }, "time": { "day": "{0} 天", @@ -533,7 +559,7 @@ "muted": "已隐藏", "per_day": "每天", "remote_follow": "跨站关注", - "report": "报告", + "report": "报告", "statuses": "状态", "subscribe": "订阅", "unsubscribe": "退订", @@ -561,7 +587,10 @@ "quarantine": "从联合实例中禁止用户帖子", "delete_user": "删除用户", "delete_user_confirmation": "你确认吗?此操作无法撤销。" - } + }, + "hidden": "已隐藏", + "show_repeats": "显示转发", + "hide_repeats": "隐藏转发" }, "user_profile": { "timeline_title": "用户时间线", @@ -586,9 +615,11 @@ "repeat": "转发", "reply": "回复", "favorite": "收藏", - "user_settings": "用户设置" + "user_settings": "用户设置", + "reject_follow_request": "拒绝关注请求", + "add_reaction": "添加互动" }, - "upload":{ + "upload": { "error": { "base": "上传不成功。", "file_too_big": "文件太大了 [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", @@ -605,8 +636,8 @@ "search": { "people": "人", "hashtags": "Hashtags", - "person_talking": "{count} 人谈论", - "people_talking": "{count} 人谈论", + "person_talking": "{count} 人正在讨论", + "people_talking": "{count} 人正在讨论", "no_results": "没有搜索结果" }, "password_reset": { @@ -619,5 +650,49 @@ "not_found": "我们无法找到匹配的邮箱地址或者用户名。", "too_many_requests": "你触发了尝试的限制,请稍后再试。", "password_reset_disabled": "密码重置已经被禁用。请联系你的实例管理员。" + }, + "remote_user_resolver": { + "error": "未找到。", + "searching_for": "搜索", + "remote_user_resolver": "远程用户解析器" + }, + "emoji": { + "keep_open": "选择器保持打开", + "stickers": "贴图", + "unicode": "Unicode 表情符号", + "custom": "自定义表情符号", + "add_emoji": "插入表情符号", + "search_emoji": "搜索表情符号", + "emoji": "表情符号" + }, + "about": { + "mrf": { + "simple": { + "quarantine_desc": "本实例只会把公开状态发送非下列实例:", + "quarantine": "隔离", + "reject_desc": "本实例不会接收来自下列实例的消息:", + "reject": "拒绝", + "accept_desc": "本实例只接收来自下列实例的消息:", + "simple_policies": "站规", + "accept": "接受", + "media_removal": "移除媒体" + }, + "mrf_policies_desc": "MRF 策略会影响本实例的互通行为。以下策略已启用:", + "mrf_policies": "已启动 MRF 策略", + "keyword": { + "ftl_removal": "从“全部已知网络”时间线上移除", + "keyword_policies": "关键词策略", + "is_replaced_by": "→", + "replace": "替换", + "reject": "拒绝" + }, + "federation": "联邦" + } + }, + "domain_mute_card": { + "unmute_progress": "正在取消隐藏……", + "unmute": "取消隐藏", + "mute_progress": "隐藏中……", + "mute": "隐藏" } } diff --git a/src/lib/event_target_polyfill.js b/src/lib/event_target_polyfill.js new file mode 100644 index 00000000..2042c770 --- /dev/null +++ b/src/lib/event_target_polyfill.js @@ -0,0 +1,9 @@ +import EventTargetPolyfill from '@ungap/event-target' + +try { + /* eslint-disable no-new */ + new EventTarget() + /* eslint-enable no-new */ +} catch (e) { + window.EventTarget = EventTargetPolyfill +} diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js index cad7ea25..8ecb66a8 100644 --- a/src/lib/persisted_state.js +++ b/src/lib/persisted_state.js @@ -1,13 +1,12 @@ import merge from 'lodash.merge' -import objectPath from 'object-path' import localforage from 'localforage' -import { each } from 'lodash' +import { each, get, set } from 'lodash' let loaded = false const defaultReducer = (state, paths) => ( paths.length === 0 ? state : paths.reduce((substate, path) => { - objectPath.set(substate, path, objectPath.get(state, path)) + set(substate, path, get(state, path)) return substate }, {}) ) diff --git a/src/main.js b/src/main.js index a9db1cff..9a201e4f 100644 --- a/src/main.js +++ b/src/main.js @@ -2,6 +2,9 @@ import Vue from 'vue' import VueRouter from 'vue-router' import Vuex from 'vuex' +import 'custom-event-polyfill' +import './lib/event_target_polyfill.js' + import interfaceModule from './modules/interface.js' import instanceModule from './modules/instance.js' import statusesModule from './modules/statuses.js' @@ -28,7 +31,6 @@ import VueChatScroll from 'vue-chat-scroll' import VueClickOutside from 'v-click-outside' import PortalVue from 'portal-vue' import VBodyScrollLock from './directives/body_scroll_lock' -import VTooltip from 'v-tooltip' import afterStoreSetup from './boot/after_store.js' @@ -41,21 +43,16 @@ Vue.use(VueChatScroll) Vue.use(VueClickOutside) Vue.use(PortalVue) Vue.use(VBodyScrollLock) -Vue.use(VTooltip, { - popover: { - defaultTrigger: 'hover click', - defaultContainer: false, - defaultOffset: 5 - } -}) const i18n = new VueI18n({ // By default, use the browser locale, we will update it if neccessary - locale: currentLocale, + locale: 'en', fallbackLocale: 'en', - messages + messages: messages.default }) +messages.setLanguage(i18n, currentLocale) + const persistedStateOptions = { paths: [ 'config', diff --git a/src/modules/api.js b/src/modules/api.js index eb6a7980..748570e5 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -6,6 +6,7 @@ const api = { backendInteractor: backendInteractorService(), fetchers: {}, socket: null, + mastoUserSocket: null, followRequests: [] }, mutations: { @@ -15,7 +16,8 @@ const api = { addFetcher (state, { fetcherName, fetcher }) { state.fetchers[fetcherName] = fetcher }, - removeFetcher (state, { fetcherName }) { + removeFetcher (state, { fetcherName, fetcher }) { + window.clearInterval(fetcher) delete state.fetchers[fetcherName] }, setWsToken (state, token) { @@ -29,25 +31,135 @@ const api = { } }, actions: { - startFetchingTimeline (store, { timeline = 'friends', tag = false, userId = false }) { - // Don't start fetching if we already are. + // Global MastoAPI socket control, in future should disable ALL sockets/(re)start relevant sockets + enableMastoSockets (store) { + const { state, dispatch } = store + if (state.mastoUserSocket) return + return dispatch('startMastoUserSocket') + }, + disableMastoSockets (store) { + const { state, dispatch } = store + if (!state.mastoUserSocket) return + return dispatch('stopMastoUserSocket') + }, + + // MastoAPI 'User' sockets + startMastoUserSocket (store) { + return new Promise((resolve, reject) => { + try { + const { state, dispatch, rootState } = store + const timelineData = rootState.statuses.timelines.friends + state.mastoUserSocket = state.backendInteractor.startUserSocket({ store }) + state.mastoUserSocket.addEventListener( + 'message', + ({ detail: message }) => { + if (!message) return // pings + if (message.event === 'notification') { + dispatch('addNewNotifications', { + notifications: [message.notification], + older: false + }) + } else if (message.event === 'update') { + dispatch('addNewStatuses', { + statuses: [message.status], + userId: false, + showImmediately: timelineData.visibleStatuses.length === 0, + timeline: 'friends' + }) + } + } + ) + state.mastoUserSocket.addEventListener('error', ({ detail: error }) => { + console.error('Error in MastoAPI websocket:', error) + }) + state.mastoUserSocket.addEventListener('close', ({ detail: closeEvent }) => { + const ignoreCodes = new Set([ + 1000, // Normal (intended) closure + 1001 // Going away + ]) + const { code } = closeEvent + if (ignoreCodes.has(code)) { + console.debug(`Not restarting socket becasue of closure code ${code} is in ignore list`) + } else { + console.warn(`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`) + dispatch('startFetchingTimeline', { timeline: 'friends' }) + dispatch('startFetchingNotifications') + dispatch('restartMastoUserSocket') + } + }) + resolve() + } catch (e) { + reject(e) + } + }) + }, + restartMastoUserSocket ({ dispatch }) { + // This basically starts MastoAPI user socket and stops conventional + // fetchers when connection reestablished + return dispatch('startMastoUserSocket').then(() => { + dispatch('stopFetchingTimeline', { timeline: 'friends' }) + dispatch('stopFetchingNotifications') + }) + }, + stopMastoUserSocket ({ state, dispatch }) { + dispatch('startFetchingTimeline', { timeline: 'friends' }) + dispatch('startFetchingNotifications') + console.log(state.mastoUserSocket) + state.mastoUserSocket.close() + }, + + // Timelines + startFetchingTimeline (store, { + timeline = 'friends', + tag = false, + userId = false + }) { if (store.state.fetchers[timeline]) return - const fetcher = store.state.backendInteractor.startFetchingTimeline({ timeline, store, userId, tag }) + const fetcher = store.state.backendInteractor.startFetchingTimeline({ + timeline, store, userId, tag + }) store.commit('addFetcher', { fetcherName: timeline, fetcher }) }, - startFetchingNotifications (store) { - // Don't start fetching if we already are. - if (store.state.fetchers['notifications']) return + stopFetchingTimeline (store, timeline) { + const fetcher = store.state.fetchers[timeline] + if (!fetcher) return + store.commit('removeFetcher', { fetcherName: timeline, fetcher }) + }, + // Notifications + startFetchingNotifications (store) { + if (store.state.fetchers.notifications) return const fetcher = store.state.backendInteractor.startFetchingNotifications({ store }) store.commit('addFetcher', { fetcherName: 'notifications', fetcher }) }, - stopFetching (store, fetcherName) { - const fetcher = store.state.fetchers[fetcherName] - window.clearInterval(fetcher) - store.commit('removeFetcher', { fetcherName }) + stopFetchingNotifications (store) { + const fetcher = store.state.fetchers.notifications + if (!fetcher) return + store.commit('removeFetcher', { fetcherName: 'notifications', fetcher }) }, + fetchAndUpdateNotifications (store) { + store.state.backendInteractor.fetchAndUpdateNotifications({ store }) + }, + + // Follow requests + startFetchingFollowRequests (store) { + if (store.state.fetchers['followRequests']) return + const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store }) + + store.commit('addFetcher', { fetcherName: 'followRequests', fetcher }) + }, + stopFetchingFollowRequests (store) { + const fetcher = store.state.fetchers.followRequests + if (!fetcher) return + store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher }) + }, + removeFollowRequest (store, request) { + let requests = store.state.followRequests.filter((it) => it !== request) + store.commit('setFollowRequests', requests) + }, + + // Pleroma websocket setWsToken (store, token) { store.commit('setWsToken', token) }, @@ -65,10 +177,6 @@ const api = { disconnectFromSocket ({ commit, state }) { state.socket && state.socket.disconnect() commit('setSocket', null) - }, - removeFollowRequest (store, request) { - let requests = store.state.followRequests.filter((it) => it !== request) - store.commit('setFollowRequests', requests) } } } diff --git a/src/modules/auth_flow.js b/src/modules/auth_flow.js index d0a90feb..956d40e8 100644 --- a/src/modules/auth_flow.js +++ b/src/modules/auth_flow.js @@ -7,7 +7,6 @@ const RECOVERY_STRATEGY = 'recovery' // initial state const state = { - app: null, settings: {}, strategy: PASSWORD_STRATEGY, initStrategy: PASSWORD_STRATEGY // default strategy from config @@ -16,14 +15,10 @@ const state = { const resetState = (state) => { state.strategy = state.initStrategy state.settings = {} - state.app = null } // getters const getters = { - app: (state, getters) => { - return state.app - }, settings: (state, getters) => { return state.settings }, @@ -55,9 +50,8 @@ const mutations = { requireToken (state) { state.strategy = TOKEN_STRATEGY }, - requireMFA (state, { app, settings }) { + requireMFA (state, { settings }) { state.settings = settings - state.app = app state.strategy = TOTP_STRATEGY // default strategy of MFA }, requireRecovery (state) { diff --git a/src/modules/config.js b/src/modules/config.js index 78314118..47b24d77 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -1,10 +1,25 @@ import { set, delete as del } from 'vue' import { setPreset, applyTheme } from '../services/style_setter/style_setter.js' +import messages from '../i18n/messages' const browserLocale = (window.navigator.language || 'en').split('-')[0] +/* TODO this is a bit messy. + * We need to declare settings with their types and also deal with + * instance-default settings in some way, hopefully try to avoid copy-pasta + * in general. + */ +export const multiChoiceProperties = [ + 'postContentType', + 'subjectLineBehavior' +] + export const defaultState = { colors: {}, + theme: undefined, + customTheme: undefined, + customThemeSource: undefined, + hideISP: false, // bad name: actually hides posts of muted USERS hideMutedPosts: undefined, // instance default collapseMessageWithSubject: undefined, // instance default @@ -19,6 +34,7 @@ export const defaultState = { autoLoad: true, streaming: false, hoverPreview: true, + emojiReactionsOnTimeline: true, autohideFloatingPostButton: false, pauseOnUnfocused: true, stopGifs: false, @@ -27,13 +43,17 @@ export const defaultState = { follows: true, mentions: true, likes: true, - repeats: true + repeats: true, + moves: true, + emojiReactions: false, + followRequest: true }, webPushNotifications: false, muteWords: [], highlight: {}, interfaceLanguage: browserLocale, hideScopeNotice: false, + useStreamingApi: false, scopeCopy: undefined, // instance default subjectLineBehavior: undefined, // instance default alwaysShowSubjectInput: undefined, // instance default @@ -44,6 +64,7 @@ export const defaultState = { playVideosInModal: false, useOneClickNsfw: false, useContainFit: false, + greentext: undefined, // instance default hidePostStats: undefined, // instance default hideUserStats: undefined // instance default } @@ -90,10 +111,15 @@ const config = { commit('setOption', { name, value }) switch (name) { case 'theme': - setPreset(value, commit) + setPreset(value) break case 'customTheme': - applyTheme(value, commit) + case 'customThemeSource': + applyTheme(value) + break + case 'interfaceLanguage': + messages.setLanguage(this.getters.i18n, value) + break } } } diff --git a/src/modules/instance.js b/src/modules/instance.js index 7b0e0da4..ec5f4e54 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -1,51 +1,60 @@ import { set } from 'vue' -import { setPreset } from '../services/style_setter/style_setter.js' +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' const defaultState = { - // Stuff from static/config.json and apiConfig + // Stuff from apiConfig name: 'Pleroma FE', registrationOpen: true, - safeDM: true, - textlimit: 5000, server: 'http://localhost:4040/', - theme: 'pleroma-dark', - background: '/static/aurora_borealis.jpg', - logo: '/static/logo.png', - logoMask: true, - logoMargin: '.2em', - redirectRootNoLogin: '/main/all', - redirectRootLogin: '/main/friends', - showInstanceSpecificPanel: false, - alwaysShowSubjectInput: true, - hideMutedPosts: false, - collapseMessageWithSubject: false, - hidePostStats: false, - hideUserStats: false, - hideFilteredStatuses: false, - disableChat: false, - scopeCopy: true, - subjectLineBehavior: 'email', - postContentType: 'text/plain', - nsfwCensorImage: undefined, + textlimit: 5000, + themeData: undefined, vapidPublicKey: undefined, - noAttachmentLinks: false, - showFeaturesPanel: true, + + // Stuff from static/config.json + alwaysShowSubjectInput: true, + background: '/static/aurora_borealis.jpg', + collapseMessageWithSubject: false, + disableChat: false, + greentext: false, + hideFilteredStatuses: false, + hideMutedPosts: false, + hidePostStats: false, + hideSitename: false, + hideUserStats: false, + loginMethod: 'password', + logo: '/static/logo.png', + logoMargin: '.2em', + logoMask: true, minimalScopesMode: false, + nsfwCensorImage: undefined, + postContentType: 'text/plain', + redirectRootLogin: '/main/friends', + redirectRootNoLogin: '/main/all', + scopeCopy: true, + showFeaturesPanel: true, + showInstanceSpecificPanel: false, + sidebarRight: false, + subjectLineBehavior: 'email', + theme: 'pleroma-dark', // Nasty stuff - pleromaBackend: true, - emoji: [], - emojiFetched: false, customEmoji: [], customEmojiFetched: false, - restrictedNicknames: [], + emoji: [], + emojiFetched: false, + pleromaBackend: true, postFormats: [], + restrictedNicknames: [], + safeDM: true, + knownDomains: [], // Feature-set, apparently, not everything here is reported... - mediaProxyAvailable: false, chatAvailable: false, gopherAvailable: false, + mediaProxyAvailable: false, suggestionsEnabled: false, suggestionsWeb: '', @@ -73,6 +82,9 @@ const instance = { if (typeof value !== 'undefined') { set(state, name, value) } + }, + setKnownDomains (state, domains) { + state.knownDomains = domains } }, getters: { @@ -94,6 +106,9 @@ const instance = { dispatch('initializeSocket') } break + case 'theme': + dispatch('setTheme', value) + break } }, async getStaticEmoji ({ commit }) { @@ -145,9 +160,23 @@ const instance = { } }, - setTheme ({ commit }, themeName) { + setTheme ({ commit, rootState }, themeName) { commit('setInstanceOption', { name: 'theme', value: themeName }) - return setPreset(themeName, commit) + 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) { @@ -158,6 +187,18 @@ const instance = { state.emojiFetched = true dispatch('getStaticEmoji') } + }, + + async getKnownDomains ({ commit, rootState }) { + try { + const result = await apiService.fetchKnownDomains({ + credentials: rootState.users.currentUser.credentials + }) + commit('setKnownDomains', result) + } catch (e) { + console.warn("Can't load known domains") + console.warn(e) + } } } } diff --git a/src/modules/interface.js b/src/modules/interface.js index 5b2762e5..eeebd65e 100644 --- a/src/modules/interface.js +++ b/src/modules/interface.js @@ -1,6 +1,8 @@ import { set, delete as del } from 'vue' const defaultState = { + settingsModalState: 'hidden', + settingsModalLoaded: false, settings: { currentSaveStateNotice: null, noticeClearTimeout: null, @@ -35,6 +37,27 @@ const interfaceMod = { }, setMobileLayout (state, value) { state.mobileLayout = value + }, + closeSettingsModal (state) { + state.settingsModalState = 'hidden' + }, + togglePeekSettingsModal (state) { + switch (state.settingsModalState) { + case 'minimized': + state.settingsModalState = 'visible' + return + case 'visible': + state.settingsModalState = 'minimized' + return + default: + throw new Error('Illegal minimization state of settings modal') + } + }, + openSettingsModal (state) { + state.settingsModalState = 'visible' + if (!state.settingsModalLoaded) { + state.settingsModalLoaded = true + } } }, actions: { @@ -49,6 +72,15 @@ const interfaceMod = { }, setMobileLayout ({ commit }, value) { commit('setMobileLayout', value) + }, + closeSettingsModal ({ commit }) { + commit('closeSettingsModal') + }, + openSettingsModal ({ commit }) { + commit('openSettingsModal') + }, + togglePeekSettingsModal ({ commit }) { + commit('togglePeekSettingsModal') } } } diff --git a/src/modules/oauth_tokens.js b/src/modules/oauth_tokens.js index 0159a3f1..907cae4a 100644 --- a/src/modules/oauth_tokens.js +++ b/src/modules/oauth_tokens.js @@ -9,7 +9,7 @@ const oauthTokens = { }) }, revokeToken ({ rootState, commit, state }, id) { - rootState.api.backendInteractor.revokeOAuthToken(id).then((response) => { + rootState.api.backendInteractor.revokeOAuthToken({ id }).then((response) => { if (response.status === 201) { commit('swapTokens', state.tokens.filter(token => token.id !== id)) } diff --git a/src/modules/polls.js b/src/modules/polls.js index e6158b63..92b89a06 100644 --- a/src/modules/polls.js +++ b/src/modules/polls.js @@ -40,7 +40,7 @@ const polls = { commit('mergeOrAddPoll', poll) }, updateTrackedPoll ({ rootState, dispatch, commit }, pollId) { - rootState.api.backendInteractor.fetchPoll(pollId).then(poll => { + rootState.api.backendInteractor.fetchPoll({ pollId }).then(poll => { setTimeout(() => { if (rootState.polls.trackedPolls[pollId]) { dispatch('updateTrackedPoll', pollId) @@ -59,7 +59,7 @@ const polls = { commit('untrackPoll', pollId) }, votePoll ({ rootState, commit }, { id, pollId, choices }) { - return rootState.api.backendInteractor.vote(pollId, choices).then(poll => { + return rootState.api.backendInteractor.vote({ pollId, choices }).then(poll => { commit('mergeOrAddPoll', poll) return poll }) diff --git a/src/modules/statuses.js b/src/modules/statuses.js index f11ffdcd..9a2e0df1 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -1,7 +1,21 @@ -import { remove, slice, each, findIndex, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash' +import { + remove, + slice, + each, + findIndex, + find, + maxBy, + minBy, + merge, + first, + last, + isArray, + omitBy +} from 'lodash' import { set } from 'vue' +import { isStatusNotification } from '../services/notification_utils/notification_utils.js' import apiService from '../services/api/api.service.js' -// import parse from '../services/status_parser/status_parser.js' +import { muteWordHits } from '../services/status_parser/status_parser.js' const emptyTl = (userId = 0) => ({ statuses: [], @@ -38,6 +52,7 @@ export const defaultState = () => ({ notifications: emptyNotifications(), favorites: new Set(), error: false, + errorData: null, timelines: { mentions: emptyTl(), public: emptyTl(), @@ -66,7 +81,9 @@ const visibleNotificationTypes = (rootState) => { rootState.config.notificationVisibility.likes && 'like', rootState.config.notificationVisibility.mentions && 'mention', rootState.config.notificationVisibility.repeats && 'repeat', - rootState.config.notificationVisibility.follows && 'follow' + rootState.config.notificationVisibility.follows && 'follow', + rootState.config.notificationVisibility.moves && 'move', + rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reactions' ].filter(_ => _) } @@ -305,11 +322,15 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters }) => { each(notifications, (notification) => { - if (notification.type !== 'follow') { + if (isStatusNotification(notification.type)) { notification.action = addStatusToGlobalStorage(state, notification.action).item notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item } + if (notification.type === 'pleroma:emoji_reaction') { + dispatch('fetchEmojiReactionsBy', notification.status.id) + } + // Only add a new notification if we don't have one for the same action if (!state.notifications.idStore.hasOwnProperty(notification.id)) { state.notifications.maxId = notification.id > state.notifications.maxId @@ -338,11 +359,19 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot case 'follow': i18nString = 'followed_you' break + case 'move': + i18nString = 'migrated_to' + break + case 'follow_request': + i18nString = 'follow_request' + break } - if (i18nString) { + if (notification.type === 'pleroma:emoji_reaction') { + notifObj.body = rootGetters.i18n.t('notifications.reacted_with', [notification.emoji]) + } else if (i18nString) { notifObj.body = rootGetters.i18n.t('notifications.' + i18nString) - } else { + } else if (isStatusNotification(notification.type)) { notifObj.body = notification.status.text } @@ -352,11 +381,22 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot notifObj.image = status.attachments[0].url } - if (!notification.seen && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.type)) { - let notification = new window.Notification(title, notifObj) + const reasonsToMuteNotif = ( + notification.seen || + state.notifications.desktopNotificationSilence || + !visibleNotificationTypes.includes(notification.type) || + ( + notification.type === 'mention' && status && ( + status.muted || + muteWordHits(status, rootGetters.mergedConfig.muteWords).length === 0 + ) + ) + ) + if (!reasonsToMuteNotif) { + let desktopNotification = new window.Notification(title, notifObj) // Chrome is known for not closing notifications automatically // according to MDN, anyway. - setTimeout(notification.close.bind(notification), 5000) + setTimeout(desktopNotification.close.bind(desktopNotification), 5000) } } } else if (notification.seen) { @@ -479,6 +519,9 @@ export const mutations = { setError (state, { value }) { state.error = value }, + setErrorData (state, { value }) { + state.errorData = value + }, setNotificationsLoading (state, { value }) { state.notifications.loading = value }, @@ -493,6 +536,17 @@ export const mutations = { notification.seen = true }) }, + markSingleNotificationAsSeen (state, { id }) { + const notification = find(state.notifications.data, n => n.id === id) + if (notification) notification.seen = true + }, + dismissNotification (state, { id }) { + state.notifications.data = state.notifications.data.filter(n => n.id !== id) + }, + updateNotification (state, { id, updater }) { + const notification = find(state.notifications.data, n => n.id === id) + notification && updater(notification) + }, queueFlush (state, { timeline, id }) { state.timelines[timeline].flushMarker = id }, @@ -510,6 +564,53 @@ export const mutations = { newStatus.fave_num = newStatus.favoritedBy.length newStatus.favorited = !!newStatus.favoritedBy.find(({ id }) => currentUser.id === id) }, + addEmojiReactionsBy (state, { id, emojiReactions, currentUser }) { + const status = state.allStatusesObject[id] + set(status, 'emoji_reactions', emojiReactions) + }, + addOwnReaction (state, { id, emoji, currentUser }) { + const status = state.allStatusesObject[id] + const reactionIndex = findIndex(status.emoji_reactions, { name: emoji }) + const reaction = status.emoji_reactions[reactionIndex] || { name: emoji, count: 0, accounts: [] } + + const newReaction = { + ...reaction, + count: reaction.count + 1, + me: true, + accounts: [ + ...reaction.accounts, + currentUser + ] + } + + // Update count of existing reaction if it exists, otherwise append at the end + if (reactionIndex >= 0) { + set(status.emoji_reactions, reactionIndex, newReaction) + } else { + set(status, 'emoji_reactions', [...status.emoji_reactions, newReaction]) + } + }, + removeOwnReaction (state, { id, emoji, currentUser }) { + const status = state.allStatusesObject[id] + const reactionIndex = findIndex(status.emoji_reactions, { name: emoji }) + if (reactionIndex < 0) return + + const reaction = status.emoji_reactions[reactionIndex] + const accounts = reaction.accounts || [] + + const newReaction = { + ...reaction, + count: reaction.count - 1, + me: false, + accounts: accounts.filter(acc => acc.id !== currentUser.id) + } + + if (newReaction.count > 0) { + set(status.emoji_reactions, reactionIndex, newReaction) + } else { + set(status, 'emoji_reactions', status.emoji_reactions.filter(r => r.name !== emoji)) + } + }, updateStatusWithPoll (state, { id, poll }) { const status = state.allStatusesObject[id] status.poll = poll @@ -528,6 +629,9 @@ const statuses = { setError ({ rootState, commit }, { value }) { commit('setError', { value }) }, + setErrorData ({ rootState, commit }, { value }) { + commit('setErrorData', { value }) + }, setNotificationsLoading ({ rootState, commit }, { value }) { commit('setNotificationsLoading', { value }) }, @@ -538,7 +642,7 @@ const statuses = { commit('setNotificationsSilence', { value }) }, fetchStatus ({ rootState, dispatch }, id) { - rootState.api.backendInteractor.fetchStatus({ id }) + return rootState.api.backendInteractor.fetchStatus({ id }) .then((status) => dispatch('addNewStatuses', { statuses: [status] })) }, deleteStatus ({ rootState, commit }, status) { @@ -551,45 +655,45 @@ const statuses = { favorite ({ rootState, commit }, status) { // Optimistic favoriting... commit('setFavorited', { status, value: true }) - rootState.api.backendInteractor.favorite(status.id) + rootState.api.backendInteractor.favorite({ id: status.id }) .then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser })) }, unfavorite ({ rootState, commit }, status) { // Optimistic unfavoriting... commit('setFavorited', { status, value: false }) - rootState.api.backendInteractor.unfavorite(status.id) + rootState.api.backendInteractor.unfavorite({ id: status.id }) .then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser })) }, fetchPinnedStatuses ({ rootState, dispatch }, userId) { - rootState.api.backendInteractor.fetchPinnedStatuses(userId) + rootState.api.backendInteractor.fetchPinnedStatuses({ id: userId }) .then(statuses => dispatch('addNewStatuses', { statuses, timeline: 'user', userId, showImmediately: true, noIdUpdate: true })) }, pinStatus ({ rootState, dispatch }, statusId) { - return rootState.api.backendInteractor.pinOwnStatus(statusId) + return rootState.api.backendInteractor.pinOwnStatus({ id: statusId }) .then((status) => dispatch('addNewStatuses', { statuses: [status] })) }, unpinStatus ({ rootState, dispatch }, statusId) { - rootState.api.backendInteractor.unpinOwnStatus(statusId) + rootState.api.backendInteractor.unpinOwnStatus({ id: statusId }) .then((status) => dispatch('addNewStatuses', { statuses: [status] })) }, muteConversation ({ rootState, commit }, statusId) { - return rootState.api.backendInteractor.muteConversation(statusId) + return rootState.api.backendInteractor.muteConversation({ id: statusId }) .then((status) => commit('setMutedStatus', status)) }, unmuteConversation ({ rootState, commit }, statusId) { - return rootState.api.backendInteractor.unmuteConversation(statusId) + return rootState.api.backendInteractor.unmuteConversation({ id: statusId }) .then((status) => commit('setMutedStatus', status)) }, retweet ({ rootState, commit }, status) { // Optimistic retweeting... commit('setRetweeted', { status, value: true }) - rootState.api.backendInteractor.retweet(status.id) + rootState.api.backendInteractor.retweet({ id: status.id }) .then(status => commit('setRetweetedConfirm', { status: status.retweeted_status, user: rootState.users.currentUser })) }, unretweet ({ rootState, commit }, status) { // Optimistic unretweeting... commit('setRetweeted', { status, value: false }) - rootState.api.backendInteractor.unretweet(status.id) + rootState.api.backendInteractor.unretweet({ id: status.id }) .then(status => commit('setRetweetedConfirm', { status, user: rootState.users.currentUser })) }, queueFlush ({ rootState, commit }, { timeline, id }) { @@ -602,21 +706,68 @@ const statuses = { credentials: rootState.users.currentUser.credentials }) }, + markSingleNotificationAsSeen ({ rootState, commit }, { id }) { + commit('markSingleNotificationAsSeen', { id }) + apiService.markNotificationsAsSeen({ + single: true, + id, + credentials: rootState.users.currentUser.credentials + }) + }, + dismissNotificationLocal ({ rootState, commit }, { id }) { + commit('dismissNotification', { id }) + }, + dismissNotification ({ rootState, commit }, { id }) { + commit('dismissNotification', { id }) + rootState.api.backendInteractor.dismissNotification({ id }) + }, + updateNotification ({ rootState, commit }, { id, updater }) { + commit('updateNotification', { id, updater }) + }, fetchFavsAndRepeats ({ rootState, commit }, id) { Promise.all([ - rootState.api.backendInteractor.fetchFavoritedByUsers(id), - rootState.api.backendInteractor.fetchRebloggedByUsers(id) + rootState.api.backendInteractor.fetchFavoritedByUsers({ id }), + rootState.api.backendInteractor.fetchRebloggedByUsers({ id }) ]).then(([favoritedByUsers, rebloggedByUsers]) => { commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser }) commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser }) }) }, + reactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) { + const currentUser = rootState.users.currentUser + if (!currentUser) return + + commit('addOwnReaction', { id, emoji, currentUser }) + rootState.api.backendInteractor.reactWithEmoji({ id, emoji }).then( + ok => { + dispatch('fetchEmojiReactionsBy', id) + } + ) + }, + unreactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) { + const currentUser = rootState.users.currentUser + if (!currentUser) return + + commit('removeOwnReaction', { id, emoji, currentUser }) + rootState.api.backendInteractor.unreactWithEmoji({ id, emoji }).then( + ok => { + dispatch('fetchEmojiReactionsBy', id) + } + ) + }, + fetchEmojiReactionsBy ({ rootState, commit }, id) { + rootState.api.backendInteractor.fetchEmojiReactions({ id }).then( + emojiReactions => { + commit('addEmojiReactionsBy', { id, emojiReactions, currentUser: rootState.users.currentUser }) + } + ) + }, fetchFavs ({ rootState, commit }, id) { - rootState.api.backendInteractor.fetchFavoritedByUsers(id) + rootState.api.backendInteractor.fetchFavoritedByUsers({ id }) .then(favoritedByUsers => commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser })) }, fetchRepeats ({ rootState, commit }, id) { - rootState.api.backendInteractor.fetchRebloggedByUsers(id) + rootState.api.backendInteractor.fetchRebloggedByUsers({ id }) .then(rebloggedByUsers => commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })) }, search (store, { q, resolve, limit, offset, following }) { diff --git a/src/modules/users.js b/src/modules/users.js index 1c9ff5e8..f9329f2a 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -32,7 +32,7 @@ const getNotificationPermission = () => { } const blockUser = (store, id) => { - return store.rootState.api.backendInteractor.blockUser(id) + return store.rootState.api.backendInteractor.blockUser({ id }) .then((relationship) => { store.commit('updateUserRelationship', [relationship]) store.commit('addBlockId', id) @@ -43,12 +43,17 @@ const blockUser = (store, id) => { } const unblockUser = (store, id) => { - return store.rootState.api.backendInteractor.unblockUser(id) + return store.rootState.api.backendInteractor.unblockUser({ id }) .then((relationship) => store.commit('updateUserRelationship', [relationship])) } const muteUser = (store, id) => { - return store.rootState.api.backendInteractor.muteUser(id) + const predictedRelationship = store.state.relationships[id] || { id } + predictedRelationship.muting = true + store.commit('updateUserRelationship', [predictedRelationship]) + store.commit('addMuteId', id) + + return store.rootState.api.backendInteractor.muteUser({ id }) .then((relationship) => { store.commit('updateUserRelationship', [relationship]) store.commit('addMuteId', id) @@ -56,7 +61,11 @@ const muteUser = (store, id) => { } const unmuteUser = (store, id) => { - return store.rootState.api.backendInteractor.unmuteUser(id) + const predictedRelationship = store.state.relationships[id] || { id } + predictedRelationship.muting = false + store.commit('updateUserRelationship', [predictedRelationship]) + + return store.rootState.api.backendInteractor.unmuteUser({ id }) .then((relationship) => store.commit('updateUserRelationship', [relationship])) } @@ -72,11 +81,17 @@ const showReblogs = (store, userId) => { .then((relationship) => store.commit('updateUserRelationship', [relationship])) } +const muteDomain = (store, domain) => { + return store.rootState.api.backendInteractor.muteDomain({ domain }) + .then(() => store.commit('addDomainMute', domain)) +} + +const unmuteDomain = (store, domain) => { + return store.rootState.api.backendInteractor.unmuteDomain({ domain }) + .then(() => store.commit('removeDomainMute', domain)) +} + export const mutations = { - setMuted (state, { user: { id }, muted }) { - const user = state.usersObject[id] - set(user, 'muted', muted) - }, tagUser (state, { user: { id }, tag }) { const user = state.usersObject[id] const tags = user.tags || [] @@ -95,9 +110,9 @@ export const mutations = { newRights[right] = value set(user, 'rights', newRights) }, - updateActivationStatus (state, { user: { id }, status }) { + updateActivationStatus (state, { user: { id }, deactivated }) { const user = state.usersObject[id] - set(user, 'deactivated', !status) + set(user, 'deactivated', deactivated) }, setCurrentUser (state, user) { state.lastLoginName = user.screen_name @@ -136,26 +151,18 @@ export const mutations = { } }, addNewUsers (state, users) { - each(users, (user) => mergeOrAdd(state.users, state.usersObject, user)) + each(users, (user) => { + if (user.relationship) { + set(state.relationships, user.relationship.id, user.relationship) + } + mergeOrAdd(state.users, state.usersObject, user) + }) }, updateUserRelationship (state, relationships) { relationships.forEach((relationship) => { - const user = state.usersObject[relationship.id] - if (user) { - user.follows_you = relationship.followed_by - user.following = relationship.following - user.muted = relationship.muting - user.statusnet_blocking = relationship.blocking - user.subscribed = relationship.subscribing - user.showing_reblogs = relationship.showing_reblogs - } + set(state.relationships, relationship.id, relationship) }) }, - updateBlocks (state, blockedUsers) { - // Reset statusnet_blocking of all fetched users - each(state.users, (user) => { user.statusnet_blocking = false }) - each(blockedUsers, (user) => mergeOrAdd(state.users, state.usersObject, user)) - }, saveBlockIds (state, blockIds) { state.currentUser.blockIds = blockIds }, @@ -164,11 +171,6 @@ export const mutations = { state.currentUser.blockIds.push(blockId) } }, - updateMutes (state, mutedUsers) { - // Reset muted of all fetched users - each(state.users, (user) => { user.muted = false }) - each(mutedUsers, (user) => mergeOrAdd(state.users, state.usersObject, user)) - }, saveMuteIds (state, muteIds) { state.currentUser.muteIds = muteIds }, @@ -177,6 +179,20 @@ export const mutations = { state.currentUser.muteIds.push(muteId) } }, + saveDomainMutes (state, domainMutes) { + state.currentUser.domainMutes = domainMutes + }, + addDomainMute (state, domain) { + if (state.currentUser.domainMutes.indexOf(domain) === -1) { + state.currentUser.domainMutes.push(domain) + } + }, + removeDomainMute (state, domain) { + const index = state.currentUser.domainMutes.indexOf(domain) + if (index !== -1) { + state.currentUser.domainMutes.splice(index, 1) + } + }, setPinnedToUser (state, status) { const user = state.usersObject[status.user.id] const index = user.pinnedStatusIds.indexOf(status.id) @@ -220,6 +236,10 @@ export const getters = { return state.usersObject[query.toLowerCase()] } return result + }, + relationship: state => id => { + const rel = id && state.relationships[id] + return rel || { id, loading: true } } } @@ -230,7 +250,8 @@ export const defaultState = { users: [], usersObject: {}, signUpPending: false, - signUpErrors: [] + signUpErrors: [], + relationships: {} } const users = { @@ -255,7 +276,7 @@ const users = { return store.rootState.api.backendInteractor.fetchBlocks() .then((blocks) => { store.commit('saveBlockIds', map(blocks, 'id')) - store.commit('updateBlocks', blocks) + store.commit('addNewUsers', blocks) return blocks }) }, @@ -274,8 +295,8 @@ const users = { fetchMutes (store) { return store.rootState.api.backendInteractor.fetchMutes() .then((mutes) => { - store.commit('updateMutes', mutes) store.commit('saveMuteIds', map(mutes, 'id')) + store.commit('addNewUsers', mutes) return mutes }) }, @@ -297,6 +318,25 @@ const users = { unmuteUsers (store, ids = []) { return Promise.all(ids.map(id => unmuteUser(store, id))) }, + fetchDomainMutes (store) { + return store.rootState.api.backendInteractor.fetchDomainMutes() + .then((domainMutes) => { + store.commit('saveDomainMutes', domainMutes) + return domainMutes + }) + }, + muteDomain (store, domain) { + return muteDomain(store, domain) + }, + unmuteDomain (store, domain) { + return unmuteDomain(store, domain) + }, + muteDomains (store, domains = []) { + return Promise.all(domains.map(domain => muteDomain(store, domain))) + }, + unmuteDomains (store, domain = []) { + return Promise.all(domain.map(domain => unmuteDomain(store, domain))) + }, fetchFriends ({ rootState, commit }, id) { const user = rootState.users.usersObject[id] const maxId = last(user.friendIds) @@ -324,13 +364,18 @@ const users = { commit('clearFollowers', userId) }, subscribeUser ({ rootState, commit }, id) { - return rootState.api.backendInteractor.subscribeUser(id) + return rootState.api.backendInteractor.subscribeUser({ id }) .then((relationship) => commit('updateUserRelationship', [relationship])) }, unsubscribeUser ({ rootState, commit }, id) { - return rootState.api.backendInteractor.unsubscribeUser(id) + return rootState.api.backendInteractor.unsubscribeUser({ id }) .then((relationship) => commit('updateUserRelationship', [relationship])) }, + toggleActivationStatus ({ rootState, commit }, { user }) { + const api = user.deactivated ? rootState.api.backendInteractor.activateUser : rootState.api.backendInteractor.deactivateUser + api({ user }) + .then(({ deactivated }) => commit('updateActivationStatus', { user, deactivated })) + }, registerPushNotifications (store) { const token = store.state.currentUser.credentials const vapidPublicKey = store.rootState.instance.vapidPublicKey @@ -368,8 +413,10 @@ const users = { }, addNewNotifications (store, { notifications }) { const users = map(notifications, 'from_profile') + const targetUsers = map(notifications, 'target').filter(_ => _) const notificationIds = notifications.map(_ => _.id) store.commit('addNewUsers', users) + store.commit('addNewUsers', targetUsers) const notificationsObject = store.rootState.statuses.notifications.idStore const relevantNotifications = Object.entries(notificationsObject) @@ -381,8 +428,8 @@ const users = { store.commit('setUserForNotification', notification) }) }, - searchUsers (store, query) { - return store.rootState.api.backendInteractor.searchUsers(query) + searchUsers (store, { query }) { + return store.rootState.api.backendInteractor.searchUsers({ query }) .then((users) => { store.commit('addNewUsers', users) return users @@ -394,7 +441,9 @@ const users = { let rootState = store.rootState try { - let data = await rootState.api.backendInteractor.register(userInfo) + let data = await rootState.api.backendInteractor.register( + { params: { ...userInfo } } + ) store.commit('signUpSuccess') store.commit('setToken', data.access_token) store.dispatch('loginUser', data.access_token) @@ -431,9 +480,10 @@ const users = { store.commit('clearCurrentUser') store.dispatch('disconnectFromSocket') store.commit('clearToken') - store.dispatch('stopFetching', 'friends') + store.dispatch('stopFetchingTimeline', 'friends') store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken())) - store.dispatch('stopFetching', 'notifications') + store.dispatch('stopFetchingNotifications') + store.dispatch('stopFetchingFollowRequests') store.commit('clearNotifications') store.commit('resetStatuses') }) @@ -450,6 +500,7 @@ const users = { user.credentials = accessToken user.blockIds = [] user.muteIds = [] + user.domainMutes = [] commit('setCurrentUser', user) commit('addNewUsers', [user]) @@ -468,11 +519,24 @@ const users = { store.dispatch('initializeSocket') } - // Start getting fresh posts. - store.dispatch('startFetchingTimeline', { timeline: 'friends' }) + const startPolling = () => { + // Start getting fresh posts. + store.dispatch('startFetchingTimeline', { timeline: 'friends' }) - // Start fetching notifications - store.dispatch('startFetchingNotifications') + // Start fetching notifications + store.dispatch('startFetchingNotifications') + } + + if (store.getters.mergedConfig.useStreamingApi) { + store.dispatch('enableMastoSockets').catch((error) => { + console.error('Failed initializing MastoAPI Streaming socket', error) + startPolling() + }).then(() => { + setTimeout(() => store.dispatch('setNotificationsSilence', false), 10000) + }) + } else { + startPolling() + } // Get user mutes store.dispatch('fetchMutes') diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 8f5eb416..dfffc291 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -1,10 +1,8 @@ -import { each, map, concat, last } from 'lodash' +import { each, map, concat, last, get } from 'lodash' import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js' -import 'whatwg-fetch' import { RegistrationError, StatusCodeError } from '../errors/errors' /* eslint-env browser */ -const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json' const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import' const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' @@ -12,22 +10,25 @@ const CHANGE_EMAIL_URL = '/api/pleroma/change_email' const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' const TAG_USER_URL = '/api/pleroma/admin/users/tag' const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}` -const ACTIVATION_STATUS_URL = screenName => `/api/pleroma/admin/users/${screenName}/activation_status` +const ACTIVATE_USER_URL = '/api/pleroma/admin/users/activate' +const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate' const ADMIN_USERS_URL = '/api/pleroma/admin/users' const SUGGESTIONS_URL = '/api/v1/suggestions' const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings' +const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read' const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa' const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes' const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp' const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp' -const MFA_DISABLE_OTP_URL = '/api/pleroma/account/mfa/totp' +const MFA_DISABLE_OTP_URL = '/api/pleroma/accounts/mfa/totp' const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials' const MASTODON_REGISTRATION_URL = '/api/v1/accounts' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications' +const MASTODON_DISMISS_NOTIFICATION_URL = id => `/api/v1/notifications/${id}/dismiss` const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite` const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite` const MASTODON_RETWEET_URL = id => `/api/v1/statuses/${id}/reblog` @@ -71,6 +72,12 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute` const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute` const MASTODON_SEARCH_2 = `/api/v2/search` const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' +const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks' +const MASTODON_STREAMING = '/api/v1/streaming' +const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers' +const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/reactions` +const PLEROMA_EMOJI_REACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` +const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` const oldfetch = window.fetch @@ -317,7 +324,8 @@ const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => { const args = [ maxId && `max_id=${maxId}`, sinceId && `since_id=${sinceId}`, - limit && `limit=${limit}` + limit && `limit=${limit}`, + `with_relationships=true` ].filter(_ => _).join('&') url = url + (args ? '?' + args : '') @@ -351,7 +359,8 @@ const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => { const args = [ maxId && `max_id=${maxId}`, sinceId && `since_id=${sinceId}`, - limit && `limit=${limit}` + limit && `limit=${limit}`, + `with_relationships=true` ].filter(_ => _).join('&') url += args ? '?' + args : '' @@ -396,8 +405,8 @@ const fetchStatus = ({ id, credentials }) => { .then((data) => parseStatus(data)) } -const tagUser = ({ tag, credentials, ...options }) => { - const screenName = options.screen_name +const tagUser = ({ tag, credentials, user }) => { + const screenName = user.screen_name const form = { nicknames: [screenName], tags: [tag] @@ -413,8 +422,8 @@ const tagUser = ({ tag, credentials, ...options }) => { }) } -const untagUser = ({ tag, credentials, ...options }) => { - const screenName = options.screen_name +const untagUser = ({ tag, credentials, user }) => { + const screenName = user.screen_name const body = { nicknames: [screenName], tags: [tag] @@ -430,7 +439,7 @@ const untagUser = ({ tag, credentials, ...options }) => { }) } -const addRight = ({ right, credentials, ...user }) => { +const addRight = ({ right, credentials, user }) => { const screenName = user.screen_name return fetch(PERMISSION_GROUP_URL(screenName, right), { @@ -440,7 +449,7 @@ const addRight = ({ right, credentials, ...user }) => { }) } -const deleteRight = ({ right, credentials, ...user }) => { +const deleteRight = ({ right, credentials, user }) => { const screenName = user.screen_name return fetch(PERMISSION_GROUP_URL(screenName, right), { @@ -450,23 +459,29 @@ const deleteRight = ({ right, credentials, ...user }) => { }) } -const setActivationStatus = ({ status, credentials, ...user }) => { - const screenName = user.screen_name - const body = { - status: status - } - - const headers = authHeaders(credentials) - headers['Content-Type'] = 'application/json' - - return fetch(ACTIVATION_STATUS_URL(screenName), { - method: 'PUT', - headers: headers, - body: JSON.stringify(body) - }) +const activateUser = ({ credentials, user: { screen_name: nickname } }) => { + return promisedRequest({ + url: ACTIVATE_USER_URL, + method: 'PATCH', + credentials, + payload: { + nicknames: [nickname] + } + }).then(response => get(response, 'users.0')) } -const deleteUser = ({ credentials, ...user }) => { +const deactivateUser = ({ credentials, user: { screen_name: nickname } }) => { + return promisedRequest({ + url: DEACTIVATE_USER_URL, + method: 'PATCH', + credentials, + payload: { + nicknames: [nickname] + } + }).then(response => get(response, 'users.0')) +} + +const deleteUser = ({ credentials, user }) => { const screenName = user.screen_name const headers = authHeaders(credentials) @@ -523,22 +538,32 @@ const fetchTimeline = ({ if (timeline === 'public' || timeline === 'publicAndExternal') { params.push(['only_media', false]) } + if (timeline !== 'favorites') { + params.push(['with_muted', withMuted]) + } - params.push(['count', 20]) - params.push(['with_muted', withMuted]) + params.push(['limit', 20]) const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&') url += `?${queryString}` - + let status = '' + let statusText = '' return fetch(url, { headers: authHeaders(credentials) }) .then((data) => { - if (data.ok) { - return data - } - throw new Error('Error fetching timeline', data) + status = data.status + statusText = data.statusText + return data }) .then((data) => data.json()) - .then((data) => data.map(isNotifications ? parseNotification : parseStatus)) + .then((data) => { + if (!data.error) { + return data.map(isNotifications ? parseNotification : parseStatus) + } else { + data.status = status + data.statusText = statusText + return data + } + }) } const fetchPinnedStatuses = ({ id, credentials }) => { @@ -820,12 +845,16 @@ const suggestions = ({ credentials }) => { }).then((data) => data.json()) } -const markNotificationsAsSeen = ({ id, credentials }) => { +const markNotificationsAsSeen = ({ id, credentials, single = false }) => { const body = new FormData() - body.append('latest_id', id) + if (single) { + body.append('id', id) + } else { + body.append('max_id', id) + } - return fetch(QVITTER_USER_NOTIFICATIONS_READ_URL, { + return fetch(NOTIFICATION_READ_URL, { body, headers: authHeaders(credentials), method: 'POST' @@ -856,12 +885,44 @@ const fetchPoll = ({ pollId, credentials }) => { ) } -const fetchFavoritedByUsers = ({ id }) => { - return promisedRequest({ url: MASTODON_STATUS_FAVORITEDBY_URL(id) }).then((users) => users.map(parseUser)) +const fetchFavoritedByUsers = ({ id, credentials }) => { + return promisedRequest({ + url: MASTODON_STATUS_FAVORITEDBY_URL(id), + method: 'GET', + credentials + }).then((users) => users.map(parseUser)) } -const fetchRebloggedByUsers = ({ id }) => { - return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser)) +const fetchRebloggedByUsers = ({ id, credentials }) => { + return promisedRequest({ + url: MASTODON_STATUS_REBLOGGEDBY_URL(id), + method: 'GET', + credentials + }).then((users) => users.map(parseUser)) +} + +const fetchEmojiReactions = ({ id, credentials }) => { + return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id), credentials }) + .then((reactions) => reactions.map(r => { + r.accounts = r.accounts.map(parseUser) + return r + })) +} + +const reactWithEmoji = ({ id, emoji, credentials }) => { + return promisedRequest({ + url: PLEROMA_EMOJI_REACT_URL(id, emoji), + method: 'PUT', + credentials + }).then(parseStatus) +} + +const unreactWithEmoji = ({ id, emoji, credentials }) => { + return promisedRequest({ + url: PLEROMA_EMOJI_UNREACT_URL(id, emoji), + method: 'DELETE', + credentials + }).then(parseStatus) } const reportUser = ({ credentials, userId, statusIds, comment, forward }) => { @@ -914,6 +975,8 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => { params.push(['following', true]) } + params.push(['with_relationships', true]) + let queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&') url += `?${queryString}` @@ -932,6 +995,134 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => { }) } +const fetchKnownDomains = ({ credentials }) => { + return promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials }) +} + +const fetchDomainMutes = ({ credentials }) => { + return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials }) +} + +const muteDomain = ({ domain, credentials }) => { + return promisedRequest({ + url: MASTODON_DOMAIN_BLOCKS_URL, + method: 'POST', + payload: { domain }, + credentials + }) +} + +const unmuteDomain = ({ domain, credentials }) => { + return promisedRequest({ + url: MASTODON_DOMAIN_BLOCKS_URL, + method: 'DELETE', + payload: { domain }, + credentials + }) +} + +const dismissNotification = ({ credentials, id }) => { + return promisedRequest({ + url: MASTODON_DISMISS_NOTIFICATION_URL(id), + method: 'POST', + payload: { 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 + '?') +} + +const MASTODON_STREAMING_EVENTS = new Set([ + 'update', + 'notification', + 'delete', + 'filters_changed' +]) + +// A thin wrapper around WebSocket API that allows adding a pre-processor to it +// Uses EventTarget and a CustomEvent to proxy events +export const ProcessedWS = ({ + url, + preprocessor = handleMastoWS, + id = 'Unknown' +}) => { + const eventTarget = new EventTarget() + const socket = new WebSocket(url) + if (!socket) throw new Error(`Failed to create socket ${id}`) + const proxy = (original, eventName, processor = a => a) => { + original.addEventListener(eventName, (eventData) => { + eventTarget.dispatchEvent(new CustomEvent( + eventName, + { detail: processor(eventData) } + )) + }) + } + socket.addEventListener('open', (wsEvent) => { + console.debug(`[WS][${id}] Socket connected`, wsEvent) + }) + socket.addEventListener('error', (wsEvent) => { + console.debug(`[WS][${id}] Socket errored`, wsEvent) + }) + socket.addEventListener('close', (wsEvent) => { + console.debug( + `[WS][${id}] Socket disconnected with code ${wsEvent.code}`, + wsEvent + ) + }) + // Commented code reason: very spammy, uncomment to enable message debug logging + /* + socket.addEventListener('message', (wsEvent) => { + console.debug( + `[WS][${id}] Message received`, + wsEvent + ) + }) + /**/ + + proxy(socket, 'open') + proxy(socket, 'close') + proxy(socket, 'message', preprocessor) + proxy(socket, 'error') + + // 1000 = Normal Closure + eventTarget.close = () => { socket.close(1000, 'Shutting down socket') } + + return eventTarget +} + +export const handleMastoWS = (wsEvent) => { + const { data } = wsEvent + if (!data) return + const parsedEvent = JSON.parse(data) + const { event, payload } = parsedEvent + if (MASTODON_STREAMING_EVENTS.has(event)) { + // MastoBE and PleromaBE both send payload for delete as a PLAIN string + if (event === 'delete') { + return { event, id: payload } + } + const data = payload ? JSON.parse(payload) : null + if (event === 'update') { + return { event, status: parseStatus(data) } + } else if (event === 'notification') { + return { event, notification: parseNotification(data) } + } + } else { + console.warn('Unknown event', wsEvent) + return null + } +} + const apiService = { verifyCredentials, fetchTimeline, @@ -971,7 +1162,8 @@ const apiService = { deleteUser, addRight, deleteRight, - setActivationStatus, + activateUser, + deactivateUser, register, getCaptcha, updateAvatar, @@ -993,14 +1185,22 @@ const apiService = { denyUser, suggestions, markNotificationsAsSeen, + dismissNotification, vote, fetchPoll, fetchFavoritedByUsers, fetchRebloggedByUsers, + fetchEmojiReactions, + reactWithEmoji, + unreactWithEmoji, reportUser, updateNotificationSettings, search2, - searchUsers + searchUsers, + fetchKnownDomains, + fetchDomainMutes, + muteDomain, + unmuteDomain } 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 index d6617276..e1c32860 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -1,226 +1,39 @@ -import apiService from '../api/api.service.js' +import apiService, { getMastodonSocketURI, ProcessedWS } from '../api/api.service.js' import timelineFetcherService 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' -const backendInteractorService = credentials => { - const fetchStatus = ({ id }) => { - return apiService.fetchStatus({ id, credentials }) - } - - const fetchConversation = ({ id }) => { - return apiService.fetchConversation({ id, credentials }) - } - - const fetchFriends = ({ id, maxId, sinceId, limit }) => { - return apiService.fetchFriends({ id, maxId, sinceId, limit, credentials }) - } - - const exportFriends = ({ id }) => { - return apiService.exportFriends({ id, credentials }) - } - - const fetchFollowers = ({ id, maxId, sinceId, limit }) => { - return apiService.fetchFollowers({ id, maxId, sinceId, limit, credentials }) - } - - const fetchUser = ({ id }) => { - return apiService.fetchUser({ id, credentials }) - } - - const fetchUserRelationship = ({ id }) => { - return apiService.fetchUserRelationship({ id, credentials }) - } - - const followUser = ({ id, reblogs }) => { - return apiService.followUser({ credentials, id, reblogs }) - } - - const unfollowUser = (id) => { - return apiService.unfollowUser({ credentials, id }) - } - - const blockUser = (id) => { - return apiService.blockUser({ credentials, id }) - } - - const unblockUser = (id) => { - return apiService.unblockUser({ credentials, id }) - } - - const approveUser = (id) => { - return apiService.approveUser({ credentials, id }) - } - - const denyUser = (id) => { - return apiService.denyUser({ credentials, id }) - } - - const startFetchingTimeline = ({ timeline, store, userId = false, tag }) => { +const backendInteractorService = credentials => ({ + startFetchingTimeline ({ timeline, store, userId = false, tag }) { return timelineFetcherService.startFetching({ timeline, store, credentials, userId, tag }) - } + }, - const startFetchingNotifications = ({ store }) => { + startFetchingNotifications ({ store }) { return notificationsFetcher.startFetching({ store, credentials }) - } + }, - // eslint-disable-next-line camelcase - const tagUser = ({ screen_name }, tag) => { - return apiService.tagUser({ screen_name, tag, credentials }) - } + fetchAndUpdateNotifications ({ store }) { + return notificationsFetcher.fetchAndUpdate({ store, credentials }) + }, - // eslint-disable-next-line camelcase - const untagUser = ({ screen_name }, tag) => { - return apiService.untagUser({ screen_name, tag, credentials }) - } + startFetchingFollowRequests ({ store }) { + return followRequestFetcher.startFetching({ store, credentials }) + }, - // eslint-disable-next-line camelcase - const addRight = ({ screen_name }, right) => { - return apiService.addRight({ screen_name, right, credentials }) - } + startUserSocket ({ store }) { + const serv = store.rootState.instance.server.replace('http', 'ws') + const url = serv + getMastodonSocketURI({ credentials, stream: 'user' }) + return ProcessedWS({ url, id: 'User' }) + }, - // eslint-disable-next-line camelcase - const deleteRight = ({ screen_name }, right) => { - return apiService.deleteRight({ screen_name, right, credentials }) - } + ...Object.entries(apiService).reduce((acc, [key, func]) => { + return { + ...acc, + [key]: (args) => func({ credentials, ...args }) + } + }, {}), - // eslint-disable-next-line camelcase - const setActivationStatus = ({ screen_name }, status) => { - return apiService.setActivationStatus({ screen_name, status, credentials }) - } - - // eslint-disable-next-line camelcase - const deleteUser = ({ screen_name }) => { - return apiService.deleteUser({ screen_name, credentials }) - } - - const vote = (pollId, choices) => { - return apiService.vote({ credentials, pollId, choices }) - } - - const fetchPoll = (pollId) => { - return apiService.fetchPoll({ credentials, pollId }) - } - - const updateNotificationSettings = ({ settings }) => { - return apiService.updateNotificationSettings({ credentials, settings }) - } - - const fetchMutes = () => apiService.fetchMutes({ credentials }) - const muteUser = (id) => apiService.muteUser({ credentials, id }) - const unmuteUser = (id) => apiService.unmuteUser({ credentials, id }) - const subscribeUser = (id) => apiService.subscribeUser({ credentials, id }) - const unsubscribeUser = (id) => apiService.unsubscribeUser({ credentials, id }) - const fetchBlocks = () => apiService.fetchBlocks({ credentials }) - const fetchFollowRequests = () => apiService.fetchFollowRequests({ credentials }) - const fetchOAuthTokens = () => apiService.fetchOAuthTokens({ credentials }) - const revokeOAuthToken = (id) => apiService.revokeOAuthToken({ id, credentials }) - const fetchPinnedStatuses = (id) => apiService.fetchPinnedStatuses({ credentials, id }) - const pinOwnStatus = (id) => apiService.pinOwnStatus({ credentials, id }) - const unpinOwnStatus = (id) => apiService.unpinOwnStatus({ credentials, id }) - const muteConversation = (id) => apiService.muteConversation({ credentials, id }) - const unmuteConversation = (id) => apiService.unmuteConversation({ credentials, id }) - - const getCaptcha = () => apiService.getCaptcha() - const register = (params) => apiService.register({ credentials, params }) - const updateAvatar = ({ avatar }) => apiService.updateAvatar({ credentials, avatar }) - const updateBg = ({ background }) => apiService.updateBg({ credentials, background }) - const updateBanner = ({ banner }) => apiService.updateBanner({ credentials, banner }) - const updateProfile = ({ params }) => apiService.updateProfile({ credentials, params }) - - const importBlocks = (file) => apiService.importBlocks({ file, credentials }) - const importFollows = (file) => apiService.importFollows({ file, credentials }) - - const deleteAccount = ({ password }) => apiService.deleteAccount({ credentials, password }) - const changeEmail = ({ email, password }) => apiService.changeEmail({ credentials, email, password }) - const changePassword = ({ password, newPassword, newPasswordConfirmation }) => - apiService.changePassword({ credentials, password, newPassword, newPasswordConfirmation }) - - const fetchSettingsMFA = () => apiService.settingsMFA({ credentials }) - const generateMfaBackupCodes = () => apiService.generateMfaBackupCodes({ credentials }) - const mfaSetupOTP = () => apiService.mfaSetupOTP({ credentials }) - const mfaConfirmOTP = ({ password, token }) => apiService.mfaConfirmOTP({ credentials, password, token }) - const mfaDisableOTP = ({ password }) => apiService.mfaDisableOTP({ credentials, password }) - - const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({ id }) - const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({ id }) - const reportUser = (params) => apiService.reportUser({ credentials, ...params }) - - const favorite = (id) => apiService.favorite({ id, credentials }) - const unfavorite = (id) => apiService.unfavorite({ id, credentials }) - const retweet = (id) => apiService.retweet({ id, credentials }) - const unretweet = (id) => apiService.unretweet({ id, credentials }) - const search2 = ({ q, resolve, limit, offset, following }) => - apiService.search2({ credentials, q, resolve, limit, offset, following }) - const searchUsers = (query) => apiService.searchUsers({ query, credentials }) - - const backendInteractorServiceInstance = { - fetchStatus, - fetchConversation, - fetchFriends, - exportFriends, - fetchFollowers, - followUser, - unfollowUser, - blockUser, - unblockUser, - fetchUser, - fetchUserRelationship, - verifyCredentials: apiService.verifyCredentials, - startFetchingTimeline, - startFetchingNotifications, - fetchMutes, - muteUser, - unmuteUser, - subscribeUser, - unsubscribeUser, - fetchBlocks, - fetchOAuthTokens, - revokeOAuthToken, - fetchPinnedStatuses, - pinOwnStatus, - unpinOwnStatus, - muteConversation, - unmuteConversation, - tagUser, - untagUser, - addRight, - deleteRight, - deleteUser, - setActivationStatus, - register, - getCaptcha, - updateAvatar, - updateBg, - updateBanner, - updateProfile, - importBlocks, - importFollows, - deleteAccount, - changeEmail, - changePassword, - fetchSettingsMFA, - generateMfaBackupCodes, - mfaSetupOTP, - mfaConfirmOTP, - mfaDisableOTP, - fetchFollowRequests, - approveUser, - denyUser, - vote, - fetchPoll, - fetchFavoritedByUsers, - fetchRebloggedByUsers, - reportUser, - favorite, - unfavorite, - retweet, - unretweet, - updateNotificationSettings, - search2, - searchUsers - } - - return backendInteractorServiceInstance -} + verifyCredentials: apiService.verifyCredentials +}) export default backendInteractorService diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js index d1b17c61..ec104269 100644 --- a/src/services/color_convert/color_convert.js +++ b/src/services/color_convert/color_convert.js @@ -1,16 +1,27 @@ -import { map } from 'lodash' +import { invertLightness, contrastRatio } from 'chromatism' -const rgb2hex = (r, g, b) => { +// useful for visualizing color when debugging +export const consoleColor = (color) => console.log('%c##########', 'background: ' + color + '; color: ' + color) + +/** + * Convert r, g, b values into hex notation. All components are [0-255] + * + * @param {Number|String|Object} r - Either red component, {r,g,b} object, or hex string + * @param {Number} [g] - Green component + * @param {Number} [b] - Blue component + */ +export const rgb2hex = (r, g, b) => { if (r === null || typeof r === 'undefined') { return undefined } - if (r[0] === '#') { + // TODO: clean up this mess + if (r[0] === '#' || r === 'transparent') { return r } if (typeof r === 'object') { ({ r, g, b } = r) } - [r, g, b] = map([r, g, b], (val) => { + [r, g, b] = [r, g, b].map(val => { val = Math.ceil(val) val = val < 0 ? 0 : val val = val > 255 ? 255 : val @@ -58,7 +69,7 @@ const srgbToLinear = (srgb) => { * @param {Object} srgb - sRGB color * @returns {Number} relative luminance */ -const relativeLuminance = (srgb) => { +export const relativeLuminance = (srgb) => { const { r, g, b } = srgbToLinear(srgb) return 0.2126 * r + 0.7152 * g + 0.0722 * b } @@ -71,7 +82,7 @@ const relativeLuminance = (srgb) => { * @param {Object} b - sRGB color * @returns {Number} color ratio */ -const getContrastRatio = (a, b) => { +export const getContrastRatio = (a, b) => { const la = relativeLuminance(a) const lb = relativeLuminance(b) const [l1, l2] = la > lb ? [la, lb] : [lb, la] @@ -79,6 +90,17 @@ const getContrastRatio = (a, b) => { return (l1 + 0.05) / (l2 + 0.05) } +/** + * Same as `getContrastRatio` but for multiple layers in-between + * + * @param {Object} text - text color (topmost layer) + * @param {[Object, Number]} layers[] - layers between text and bedrock + * @param {Object} bedrock - layer at the very bottom + */ +export const getContrastRatioLayers = (text, layers, bedrock) => { + return getContrastRatio(alphaBlendLayers(bedrock, layers), text) +} + /** * This performs alpha blending between solid background and semi-transparent foreground * @@ -87,7 +109,7 @@ const getContrastRatio = (a, b) => { * @param {Object} bg - bottom layer color * @returns {Object} sRGB of resulting color */ -const alphaBlend = (fg, fga, bg) => { +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 @@ -97,14 +119,30 @@ const alphaBlend = (fg, fga, bg) => { }, {}) } -const invert = (rgb) => { +/** + * Same as `alphaBlend` but for multiple layers in-between + * + * @param {Object} bedrock - layer at the very bottom + * @param {[Object, Number]} layers[] - layers between text and bedrock + */ +export const alphaBlendLayers = (bedrock, layers) => layers.reduce((acc, [color, opacity]) => { + return alphaBlend(color, opacity, acc) +}, bedrock) + +export const invert = (rgb) => { return 'rgb'.split('').reduce((acc, c) => { acc[c] = 255 - rgb[c] return acc }, {}) } -const hex2rgb = (hex) => { +/** + * Converts #rrggbb hex notation into an {r, g, b} object + * + * @param {String} hex - #rrggbb string + * @returns {Object} rgb representation of the color, values are 0-255 + */ +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), @@ -113,18 +151,72 @@ const hex2rgb = (hex) => { } : null } -const mixrgb = (a, b) => { - return Object.keys(a).reduce((acc, k) => { +/** + * Old somewhat weird function for mixing two colors together + * + * @param {Object} a - one color (rgb) + * @param {Object} b - other color (rgb) + * @returns {Object} result + */ +export const mixrgb = (a, b) => { + return 'rgb'.split('').reduce((acc, k) => { acc[k] = (a[k] + b[k]) / 2 return acc }, {}) } - -export { - rgb2hex, - hex2rgb, - mixrgb, - invert, - getContrastRatio, - alphaBlend +/** + * Converts rgb object into a CSS rgba() color + * + * @param {Object} color - rgb + * @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})` +} + +/** + * Get text color for given background color and intended text color + * This checks if text and background don't have enough color and inverts + * text color's lightness if needed. If text color is still not enough it + * will fall back to black or white + * + * @param {Object} bg - background color + * @param {Object} text - intended text color + * @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) { + // B&W + return contrastRatio(bg, text).rgb + } + // Inverted color + return result + } + return text +} + +/** + * Converts color to CSS Color value + * + * @param {Object|String} input - color + * @param {Number} [a] - alpha value + * @returns {String} a CSS Color value + */ +export const getCssColor = (input, a) => { + let rgb = {} + if (typeof input === 'object') { + rgb = input + } else if (typeof input === 'string') { + if (input.startsWith('#')) { + rgb = hex2rgb(input) + } else { + return input + } + } + return rgba2css({ ...rgb, a }) } diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 5f45660d..c7ed65a4 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -1,3 +1,6 @@ +import escape from 'escape-html' +import { isStatusNotification } from '../notification_utils/notification_utils.js' + const qvitterStatusType = (status) => { if (status.is_post_verb) { return 'status' @@ -41,11 +44,19 @@ export const parseUser = (data) => { } output.name = data.display_name - output.name_html = addEmojis(data.display_name, data.emojis) + output.name_html = addEmojis(escape(data.display_name), data.emojis) output.description = data.note output.description_html = addEmojis(data.note, data.emojis) + output.fields = data.fields + output.fields_html = data.fields.map(field => { + return { + name: addEmojis(field.name, data.emojis), + value: addEmojis(field.value, data.emojis) + } + }) + // Utilize avatar_static for gif avatars? output.profile_image_url = data.avatar output.profile_image_url_original = data.avatar @@ -64,15 +75,11 @@ export const parseUser = (data) => { output.token = data.pleroma.chat_token if (relationship) { - output.follows_you = relationship.followed_by - output.requested = relationship.requested - output.following = relationship.following - output.statusnet_blocking = relationship.blocking - output.muted = relationship.muting - output.showing_reblogs = relationship.showing_reblogs - output.subscribed = relationship.subscribing + output.relationship = relationship } + output.allow_following_move = data.pleroma.allow_following_move + output.hide_follows = data.pleroma.hide_follows output.hide_followers = data.pleroma.hide_followers output.hide_follows_count = data.pleroma.hide_follows_count @@ -95,6 +102,7 @@ export const parseUser = (data) => { if (data.source) { output.description = data.source.note output.default_scope = data.source.privacy + output.fields = data.source.fields if (data.source.pleroma) { output.no_rich_text = data.source.pleroma.no_rich_text output.show_role = data.source.pleroma.show_role @@ -124,16 +132,10 @@ export const parseUser = (data) => { output.statusnet_profile_url = data.statusnet_profile_url - output.statusnet_blocking = data.statusnet_blocking - output.is_local = data.is_local output.role = data.role output.show_role = data.show_role - output.follows_you = data.follows_you - - output.muted = data.muted - if (data.rights) { output.rights = { moderator: data.rights.delete_others_notice, @@ -147,10 +149,16 @@ export const parseUser = (data) => { output.hide_follows_count = data.hide_follows_count output.hide_followers_count = data.hide_followers_count output.background_image = data.background_image - // on mastoapi this info is contained in a "relationship" - output.following = data.following // Websocket token output.token = data.token + + // Convert relationsip data to expected format + output.relationship = { + muting: data.muted, + blocking: data.statusnet_blocking, + followed_by: data.follows_you, + following: data.following + } } output.created_at = new Date(data.created_at) @@ -202,7 +210,7 @@ export const addEmojis = (string, emojis) => { const regexSafeShortCode = emoji.shortcode.replace(matchOperatorsRegex, '\\$&') return acc.replace( new RegExp(`:${regexSafeShortCode}:`, 'g'), - `<img src='${emoji.url}' alt='${emoji.shortcode}' title='${emoji.shortcode}' class='emoji' />` + `<img src='${emoji.url}' alt=':${emoji.shortcode}:' title=':${emoji.shortcode}:' class='emoji' />` ) }, string) } @@ -233,6 +241,7 @@ export const parseStatus = (data) => { output.is_local = pleroma.local output.in_reply_to_screen_name = data.pleroma.in_reply_to_account_acct output.thread_muted = pleroma.thread_muted + output.emoji_reactions = pleroma.emoji_reactions } else { output.text = data.content output.summary = data.spoiler_text @@ -246,7 +255,7 @@ export const parseStatus = (data) => { output.retweeted_status = parseStatus(data.reblog) } - output.summary_html = addEmojis(data.spoiler_text, data.emojis) + output.summary_html = addEmojis(escape(data.spoiler_text), data.emojis) output.external_url = data.url output.poll = data.poll output.pinned = data.pinned @@ -332,11 +341,13 @@ export const parseNotification = (data) => { if (masto) { output.type = mastoDict[data.type] || data.type output.seen = data.pleroma.is_seen - output.status = output.type === 'follow' - ? null - : parseStatus(data.status) + output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null output.action = output.status // TODO: Refactor, this is unneeded + output.target = output.type !== 'move' + ? null + : parseUser(data.target) output.from_profile = parseUser(data.account) + output.emoji = data.emoji } else { const parsedNotice = parseStatus(data.notice) output.type = data.ntype diff --git a/src/services/errors/errors.js b/src/services/errors/errors.js index 590552da..d4cf9132 100644 --- a/src/services/errors/errors.js +++ b/src/services/errors/errors.js @@ -32,12 +32,18 @@ export class RegistrationError extends Error { } if (typeof error === 'object') { + const errorContents = JSON.parse(error.error) + // keys will have the property that has the error, for example 'ap_id', + // 'email' or 'captcha', the value will be an array of its error + // like "ap_id": ["has been taken"] or "captcha": ["Invalid CAPTCHA"] + // replace ap_id with username - if (error.ap_id) { - error.username = error.ap_id - delete error.ap_id + if (errorContents.ap_id) { + errorContents.username = errorContents.ap_id + delete errorContents.ap_id } - this.message = humanizeErrors(error) + + this.message = humanizeErrors(errorContents) } else { this.message = error } diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index 598cb5f7..08f4c4d6 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -1,24 +1,27 @@ -const fetchUser = (attempt, user, store) => new Promise((resolve, reject) => { +const fetchRelationship = (attempt, userId, store) => new Promise((resolve, reject) => { setTimeout(() => { - store.state.api.backendInteractor.fetchUser({ id: user.id }) - .then((user) => store.commit('addNewUsers', [user])) - .then(() => resolve([user.following, user.requested, user.locked, attempt])) + store.state.api.backendInteractor.fetchUserRelationship({ id: userId }) + .then((relationship) => { + store.commit('updateUserRelationship', [relationship]) + return relationship + }) + .then((relationship) => resolve([relationship.following, relationship.requested, relationship.locked, attempt])) .catch((e) => reject(e)) }, 500) }).then(([following, sent, locked, attempt]) => { if (!following && !(locked && sent) && attempt <= 3) { // If we BE reports that we still not following that user - retry, // increment attempts by one - fetchUser(++attempt, user, store) + fetchRelationship(++attempt, userId, store) } }) -export const requestFollow = (user, store) => new Promise((resolve, reject) => { - store.state.api.backendInteractor.followUser({ id: user.id }) +export const requestFollow = (userId, store) => new Promise((resolve, reject) => { + store.state.api.backendInteractor.followUser({ id: userId }) .then((updated) => { store.commit('updateUserRelationship', [updated]) - if (updated.following || (user.locked && user.requested)) { + if (updated.following || (updated.locked && updated.requested)) { // If we get result immediately or the account is locked, just stop. resolve() return @@ -31,15 +34,15 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => { // don't know that yet. // Recursive Promise, it will call itself up to 3 times. - return fetchUser(1, user, store) + return fetchRelationship(1, updated, store) .then(() => { resolve() }) }) }) -export const requestUnfollow = (user, store) => new Promise((resolve, reject) => { - store.state.api.backendInteractor.unfollowUser(user.id) +export const requestUnfollow = (userId, store) => new Promise((resolve, reject) => { + store.state.api.backendInteractor.unfollowUser({ id: userId }) .then((updated) => { store.commit('updateUserRelationship', [updated]) resolve({ diff --git a/src/services/new_api/mfa.js b/src/services/new_api/mfa.js index cbba06d5..c944667c 100644 --- a/src/services/new_api/mfa.js +++ b/src/services/new_api/mfa.js @@ -1,9 +1,9 @@ -const verifyOTPCode = ({ app, instance, mfaToken, code }) => { +const verifyOTPCode = ({ clientId, clientSecret, instance, mfaToken, code }) => { const url = `${instance}/oauth/mfa/challenge` const form = new window.FormData() - form.append('client_id', app.client_id) - form.append('client_secret', app.client_secret) + form.append('client_id', clientId) + form.append('client_secret', clientSecret) form.append('mfa_token', mfaToken) form.append('code', code) form.append('challenge_type', 'totp') @@ -14,12 +14,12 @@ const verifyOTPCode = ({ app, instance, mfaToken, code }) => { }).then((data) => data.json()) } -const verifyRecoveryCode = ({ app, instance, mfaToken, code }) => { +const verifyRecoveryCode = ({ clientId, clientSecret, instance, mfaToken, code }) => { const url = `${instance}/oauth/mfa/challenge` const form = new window.FormData() - form.append('client_id', app.client_id) - form.append('client_secret', app.client_secret) + form.append('client_id', clientId) + form.append('client_secret', clientSecret) form.append('mfa_token', mfaToken) form.append('code', code) form.append('challenge_type', 'recovery') diff --git a/src/services/new_api/oauth.js b/src/services/new_api/oauth.js index d0d18c03..3c8e64bd 100644 --- a/src/services/new_api/oauth.js +++ b/src/services/new_api/oauth.js @@ -12,7 +12,7 @@ export const getOrCreateApp = ({ clientId, clientSecret, instance, commit }) => form.append('client_name', `PleromaFE_${window.___pleromafe_commit_hash}_${(new Date()).toISOString()}`) form.append('redirect_uris', REDIRECT_URI) - form.append('scopes', 'read write follow') + form.append('scopes', 'read write follow push admin') return window.fetch(url, { method: 'POST', @@ -28,7 +28,7 @@ const login = ({ instance, clientId }) => { response_type: 'code', client_id: clientId, redirect_uri: REDIRECT_URI, - scope: 'read write follow' + scope: 'read write follow push admin' } const dataString = reduce(data, (acc, v, k) => { diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index 7021adbd..eb479227 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -1,4 +1,4 @@ -import { filter, sortBy } from 'lodash' +import { filter, sortBy, includes } from 'lodash' export const notificationsFromStore = store => store.state.statuses.notifications.data @@ -6,9 +6,16 @@ export const visibleTypes = store => ([ store.state.config.notificationVisibility.likes && 'like', store.state.config.notificationVisibility.mentions && 'mention', store.state.config.notificationVisibility.repeats && 'repeat', - store.state.config.notificationVisibility.follows && 'follow' + store.state.config.notificationVisibility.follows && 'follow', + store.state.config.notificationVisibility.followRequest && 'follow_request', + store.state.config.notificationVisibility.moves && 'move', + store.state.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction' ].filter(_ => _)) +const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reaction'] + +export const isStatusNotification = (type) => includes(statusNotifications, type) + const sortById = (a, b) => { const seqA = Number(a.id) const seqB = Number(b.id) @@ -25,7 +32,7 @@ const sortById = (a, b) => { } } -export const visibleNotificationsFromStore = (store, types) => { +export const filteredNotificationsFromStore = (store, types) => { // map is just to clone the array since sort mutates it and it causes some issues let sortedNotifications = notificationsFromStore(store).map(_ => _).sort(sortById) sortedNotifications = sortBy(sortedNotifications, 'seen') @@ -35,4 +42,4 @@ export const visibleNotificationsFromStore = (store, types) => { } export const unseenNotificationsFromStore = store => - filter(visibleNotificationsFromStore(store), ({ seen }) => !seen) + filter(filteredNotificationsFromStore(store), ({ seen }) => !seen) diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index 47008026..64499a1b 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -2,7 +2,6 @@ import apiService from '../api/api.service.js' const update = ({ store, notifications, older }) => { store.dispatch('setNotificationsError', { value: false }) - store.dispatch('addNewNotifications', { notifications, older }) } @@ -30,9 +29,9 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => { // load unread notifications repeatedly to provide consistency between browser tabs const notifications = timelineData.data - const unread = notifications.filter(n => !n.seen).map(n => n.id) - if (unread.length) { - args['since'] = Math.min(...unread) + const readNotifsIds = notifications.filter(n => n.seen).map(n => n.id) + if (readNotifsIds.length) { + args['since'] = Math.max(...readNotifsIds) fetchNotifications({ store, args, older }) } diff --git a/src/services/push/push.js b/src/services/push/push.js index 1b189a29..5836fc26 100644 --- a/src/services/push/push.js +++ b/src/services/push/push.js @@ -65,7 +65,8 @@ function sendSubscriptionToBackEnd (subscription, token, notificationVisibility) follow: notificationVisibility.follows, favourite: notificationVisibility.likes, mention: notificationVisibility.mentions, - reblog: notificationVisibility.repeats + reblog: notificationVisibility.repeats, + move: notificationVisibility.moves } } }) diff --git a/src/services/resettable_async_component.js b/src/services/resettable_async_component.js new file mode 100644 index 00000000..517bbd88 --- /dev/null +++ b/src/services/resettable_async_component.js @@ -0,0 +1,32 @@ +import Vue from 'vue' + +/* By default async components don't have any way to recover, if component is + * failed, it is failed forever. This helper tries to remedy that by recreating + * async component when retry is requested (by user). You need to emit the + * `resetAsyncComponent` event from child to reset the component. Generally, + * this should be done from error component but could be done from loading or + * actual target component itself if needs to be. + */ +function getResettableAsyncComponent (asyncComponent, options) { + const asyncComponentFactory = () => () => ({ + component: asyncComponent(), + ...options + }) + + const observe = Vue.observable({ c: asyncComponentFactory() }) + + return { + functional: true, + render (createElement, { data, children }) { + // emit event resetAsyncComponent to reloading + data.on = {} + data.on.resetAsyncComponent = () => { + observe.c = asyncComponentFactory() + // parent.$forceUpdate() + } + return createElement(observe.c, data, children) + } + } +} + +export default getResettableAsyncComponent diff --git a/src/services/status_parser/status_parser.js b/src/services/status_parser/status_parser.js index 900cd56e..ed0f6d57 100644 --- a/src/services/status_parser/status_parser.js +++ b/src/services/status_parser/status_parser.js @@ -1,15 +1,11 @@ -import sanitize from 'sanitize-html' +import { filter } from 'lodash' -export const removeAttachmentLinks = (html) => { - return sanitize(html, { - allowedTags: false, - allowedAttributes: false, - exclusiveFilter: ({ tag, attribs }) => tag === 'a' && typeof attribs.class === 'string' && attribs.class.match(/attachment/) +export const muteWordHits = (status, muteWords) => { + const statusText = status.text.toLowerCase() + const statusSummary = status.summary.toLowerCase() + const hits = filter(muteWords, (muteWord) => { + return statusText.includes(muteWord.toLowerCase()) || statusSummary.includes(muteWord.toLowerCase()) }) -} -export const parse = (html) => { - return removeAttachmentLinks(html) + return hits } - -export default parse diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index eaa495c4..fbdcf562 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -1,78 +1,9 @@ -import { times } from 'lodash' -import { brightness, invertLightness, convert, contrastRatio } from 'chromatism' -import { rgb2hex, hex2rgb, mixrgb, getContrastRatio, alphaBlend } from '../color_convert/color_convert.js' +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' -// While this is not used anymore right now, I left it in if we want to do custom -// styles that aren't just colors, so user can pick from a few different distinct -// styles as well as set their own colors in the future. - -const setStyle = (href, commit) => { - /*** - What's going on here? - I want to make it easy for admins to style this application. To have - a good set of default themes, I chose the system from base16 - (https://chriskempson.github.io/base16/) to style all elements. They - all have the base00..0F classes. So the only thing an admin needs to - do to style Pleroma is to change these colors in that one css file. - Some default things (body text color, link color) need to be set dy- - namically, so this is done here by waiting for the stylesheet to be - loaded and then creating an element with the respective classes. - - It is a bit weird, but should make life for admins somewhat easier. - ***/ - const head = document.head - const body = document.body - body.classList.add('hidden') - const cssEl = document.createElement('link') - cssEl.setAttribute('rel', 'stylesheet') - cssEl.setAttribute('href', href) - head.appendChild(cssEl) - - const setDynamic = () => { - const baseEl = document.createElement('div') - body.appendChild(baseEl) - - let colors = {} - times(16, (n) => { - const name = `base0${n.toString(16).toUpperCase()}` - baseEl.setAttribute('class', name) - const color = window.getComputedStyle(baseEl).getPropertyValue('color') - colors[name] = color - }) - - body.removeChild(baseEl) - - const styleEl = document.createElement('style') - head.appendChild(styleEl) - // const styleSheet = styleEl.sheet - - body.classList.remove('hidden') - } - - cssEl.addEventListener('load', setDynamic) -} - -const rgb2rgba = function (rgba) { - return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})` -} - -const getTextColor = function (bg, text, preserve) { - const bgIsLight = convert(bg).hsl.l > 50 - const textIsLight = convert(text).hsl.l > 50 - - if ((bgIsLight && textIsLight) || (!bgIsLight && !textIsLight)) { - const base = typeof text.a !== 'undefined' ? { a: text.a } : {} - const result = Object.assign(base, invertLightness(text).rgb) - if (!preserve && getContrastRatio(bg, result) < 4.5) { - return contrastRatio(bg, text).rgb - } - return result - } - return text -} - -const applyTheme = (input, commit) => { - const { rules, theme } = generatePreset(input) +export const applyTheme = (input) => { + const { rules } = generatePreset(input) const head = document.head const body = document.body body.classList.add('hidden') @@ -87,14 +18,9 @@ const applyTheme = (input, commit) => { styleSheet.insertRule(`body { ${rules.shadows} }`, 'index-max') styleSheet.insertRule(`body { ${rules.fonts} }`, 'index-max') body.classList.remove('hidden') - - // commit('setOption', { name: 'colors', value: htmlColors }) - // commit('setOption', { name: 'radii', value: radii }) - commit('setOption', { name: 'customTheme', value: input }) - commit('setOption', { name: 'colors', value: theme.colors }) } -const getCssShadow = (input, usesDropShadow) => { +export const getCssShadow = (input, usesDropShadow) => { if (input.length === 0) { return 'none' } @@ -132,122 +58,18 @@ const getCssShadowFilter = (input) => { .join(' ') } -const getCssColor = (input, a) => { - let rgb = {} - if (typeof input === 'object') { - rgb = input - } else if (typeof input === 'string') { - if (input.startsWith('#')) { - rgb = hex2rgb(input) - } else if (input.startsWith('--')) { - return `var(${input})` - } else { - return input - } - } - return rgb2rgba({ ...rgb, a }) -} +export const generateColors = (themeData) => { + const sourceColors = !themeData.themeEngineVersion + ? colors2to3(themeData.colors || themeData) + : themeData.colors || themeData -const generateColors = (input) => { - const colors = {} - const opacity = Object.assign({ - alert: 0.5, - input: 0.5, - faint: 0.5 - }, Object.entries(input.opacity || {}).reduce((acc, [k, v]) => { - if (typeof v !== 'undefined') { - acc[k] = v - } - return acc - }, {})) - const col = Object.entries(input.colors || input).reduce((acc, [k, v]) => { - if (typeof v === 'object') { - acc[k] = v - } else { - acc[k] = hex2rgb(v) - } - return acc - }, {}) - - const isLightOnDark = convert(col.bg).hsl.l < convert(col.text).hsl.l - const mod = isLightOnDark ? 1 : -1 - - colors.text = col.text - colors.lightText = brightness(20 * mod, colors.text).rgb - colors.link = col.link - colors.faint = col.faint || Object.assign({}, col.text) - - colors.bg = col.bg - colors.lightBg = col.lightBg || brightness(5, colors.bg).rgb - - colors.fg = col.fg - colors.fgText = col.fgText || getTextColor(colors.fg, colors.text) - colors.fgLink = col.fgLink || getTextColor(colors.fg, colors.link, true) - - colors.border = col.border || brightness(2 * mod, colors.fg).rgb - - colors.btn = col.btn || Object.assign({}, col.fg) - colors.btnText = col.btnText || getTextColor(colors.btn, colors.fgText) - - colors.input = col.input || Object.assign({}, col.fg) - colors.inputText = col.inputText || getTextColor(colors.input, colors.lightText) - - colors.panel = col.panel || Object.assign({}, col.fg) - colors.panelText = col.panelText || getTextColor(colors.panel, colors.fgText) - colors.panelLink = col.panelLink || getTextColor(colors.panel, colors.fgLink) - colors.panelFaint = col.panelFaint || getTextColor(colors.panel, colors.faint) - - colors.topBar = col.topBar || Object.assign({}, col.fg) - colors.topBarText = col.topBarText || getTextColor(colors.topBar, colors.fgText) - colors.topBarLink = col.topBarLink || getTextColor(colors.topBar, colors.fgLink) - - colors.faintLink = col.faintLink || Object.assign({}, col.link) - colors.linkBg = alphaBlend(colors.link, 0.4, colors.bg) - - colors.icon = mixrgb(colors.bg, colors.text) - - colors.cBlue = col.cBlue || hex2rgb('#0000FF') - colors.cRed = col.cRed || hex2rgb('#FF0000') - colors.cGreen = col.cGreen || hex2rgb('#00FF00') - colors.cOrange = col.cOrange || hex2rgb('#E3FF00') - - colors.alertError = col.alertError || Object.assign({}, colors.cRed) - colors.alertErrorText = getTextColor(alphaBlend(colors.alertError, opacity.alert, colors.bg), colors.text) - colors.alertErrorPanelText = getTextColor(alphaBlend(colors.alertError, opacity.alert, colors.panel), colors.panelText) - - colors.alertWarning = col.alertWarning || Object.assign({}, colors.cOrange) - colors.alertWarningText = getTextColor(alphaBlend(colors.alertWarning, opacity.alert, colors.bg), colors.text) - colors.alertWarningPanelText = getTextColor(alphaBlend(colors.alertWarning, opacity.alert, colors.panel), colors.panelText) - - colors.badgeNotification = col.badgeNotification || Object.assign({}, colors.cRed) - colors.badgeNotificationText = contrastRatio(colors.badgeNotification).rgb - - Object.entries(opacity).forEach(([ k, v ]) => { - if (typeof v === 'undefined') return - if (k === 'alert') { - colors.alertError.a = v - colors.alertWarning.a = v - return - } - if (k === 'faint') { - colors[k + 'Link'].a = v - colors['panelFaint'].a = v - } - if (k === 'bg') { - colors['lightBg'].a = v - } - if (colors[k]) { - colors[k].a = v - } else { - console.error('Wrong key ' + k) - } - }) + 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) : rgb2rgba(v) + acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgba2css(v) return acc }, { complete: {}, solid: {} }) return { @@ -264,7 +86,7 @@ const generateColors = (input) => { } } -const generateRadii = (input) => { +export const generateRadii = (input) => { let inputRadii = input.radii || {} // v1 -> v2 if (typeof input.btnRadius !== 'undefined') { @@ -297,7 +119,7 @@ const generateRadii = (input) => { } } -const generateFonts = (input) => { +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 @@ -332,89 +154,123 @@ const generateFonts = (input) => { } } -const generateShadows = (input) => { - const border = (top, shadow) => ({ - x: 0, - y: top ? 1 : -1, - blur: 0, +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: 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 = { + color: '#000000', + alpha: 0.6 + }], + topBar: [{ x: 0, y: 0, blur: 4, spread: 0, - color: '--faint', + 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 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 - }], - ...(input.shadows || {}) - } + const inputShadows = input.shadows && !input.themeEngineVersion + ? shadows2to3(input.shadows, input.opacity) + : input.shadows || {} + 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.1: if shadow doesn't have non-inset shadows with spread > 0 - optionally + // 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)}`, @@ -429,7 +285,7 @@ const generateShadows = (input) => { } } -const composePreset = (colors, radii, shadows, fonts) => { +export const composePreset = (colors, radii, shadows, fonts) => { return { rules: { ...shadows.rules, @@ -446,98 +302,110 @@ const composePreset = (colors, radii, shadows, fonts) => { } } -const generatePreset = (input) => { - const shadows = generateShadows(input) +export const generatePreset = (input) => { const colors = generateColors(input) - const radii = generateRadii(input) - const fonts = generateFonts(input) - - return composePreset(colors, radii, shadows, fonts) + return composePreset( + colors, + generateRadii(input), + generateShadows(input, colors.theme.colors, colors.mod), + generateFonts(input) + ) } -const getThemes = () => { - return window.fetch('/static/styles.json') +export const getThemes = () => { + const cache = 'no-store' + + return window.fetch('/static/styles.json', { cache }) .then((data) => data.json()) .then((themes) => { - return Promise.all(Object.entries(themes).map(([k, v]) => { + return Object.entries(themes).map(([k, v]) => { + let promise = null if (typeof v === 'object') { - return Promise.resolve([k, v]) + promise = Promise.resolve(v) } else if (typeof v === 'string') { - return window.fetch(v) + promise = window.fetch(v, { cache }) .then((data) => data.json()) - .then((theme) => { - return [k, theme] - }) .catch((e) => { console.error(e) - return [] + return null }) } - })) + return [k, promise] + }) }) .then((promises) => { return promises - .filter(([k, v]) => v) .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 } + } + }, {}) +} -const setPreset = (val, commit) => { - return getThemes().then((themes) => { - const theme = themes[val] ? themes[val] : themes['pleroma-dark'] - const isV1 = Array.isArray(theme) - const data = isV1 ? {} : theme.theme - - if (isV1) { - const bgRgb = hex2rgb(theme[1]) - const fgRgb = hex2rgb(theme[2]) - const textRgb = hex2rgb(theme[3]) - const linkRgb = hex2rgb(theme[4]) - - const cRedRgb = hex2rgb(theme[5] || '#FF0000') - const cGreenRgb = hex2rgb(theme[6] || '#00FF00') - const cBlueRgb = hex2rgb(theme[7] || '#0000FF') - const cOrangeRgb = hex2rgb(theme[8] || '#E3FF00') - - data.colors = { - bg: bgRgb, - fg: fgRgb, - text: textRgb, - link: linkRgb, - cRed: cRedRgb, - cBlue: cBlueRgb, - cGreen: cGreenRgb, - cOrange: cOrangeRgb +/** + * 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 }) => 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 } - } - - // This is a hack, this function is only called during initial load. - // We want to cancel loading the theme from config.json if we're already - // loading a theme from the persisted state. - // Needed some way of dealing with the async way of things. - // load config -> set preset -> wait for styles.json to load -> - // load persisted state -> set colors -> styles.json loaded -> set colors - if (!window.themeLoaded) { - applyTheme(data, commit) - } - }) + ], []) + return { ...shadowsAcc, [slotName]: newShadow } + }, {}) } -export { - setStyle, - setPreset, - applyTheme, - getTextColor, - generateColors, - generateRadii, - generateShadows, - generateFonts, - generatePreset, - getThemes, - composePreset, - getCssShadow, - getCssShadowFilter +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 } + } + + return { theme: data, source: theme.source } + }) } + +export const setPreset = (val) => getPreset(val).then(data => applyTheme(data.theme)) diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js new file mode 100644 index 00000000..0c1fe543 --- /dev/null +++ b/src/services/theme_data/pleromafe.js @@ -0,0 +1,631 @@ +import { invertLightness, brightness } from 'chromatism' +import { alphaBlend, mixrgb } from '../color_convert/color_convert.js' +/* This is a definition of all layer combinations + * each key is a topmost layer, each value represents layer underneath + * this is essentially a simplified tree + */ +export const LAYERS = { + undelay: null, // root + topBar: null, // no transparency support + badge: null, // no transparency support + profileTint: null, // doesn't matter + fg: null, + bg: 'underlay', + highlight: 'bg', + panel: 'bg', + popover: 'bg', + selectedMenu: 'popover', + btn: 'bg', + btnPanel: 'panel', + btnTopBar: 'topBar', + input: 'bg', + inputPanel: 'panel', + inputTopBar: 'topBar', + alert: 'bg', + alertPanel: 'panel', + poll: 'bg' +} + +/* By default opacity slots have 1 as default opacity + * this allows redefining it to something else + */ +export const DEFAULT_OPACITY = { + profileTint: 0.5, + alert: 0.5, + input: 0.5, + faint: 0.5, + underlay: 0.15 +} + +/** SUBJECT TO CHANGE IN THE FUTURE, this is all beta + * Color and opacity slots definitions. Each key represents a slot. + * + * Short-hands: + * String beginning with `--` - value after dashes treated as sole + * dependency - i.e. `--value` equivalent to { depends: ['value']} + * String beginning with `#` - value would be treated as solid color + * defined in hexadecimal representation (i.e. #FFFFFF) and will be + * used as default. `#FFFFFF` is equivalent to { default: '#FFFFFF'} + * + * Full definition: + * @property {String[]} depends - color slot names this color depends ones. + * cyclic dependencies are supported to some extent but not recommended. + * @property {String} [opacity] - opacity slot used by this color slot. + * opacity is inherited from parents. To break inheritance graph use null + * @property {Number} [priority] - EXPERIMENTAL. used to pre-sort slots so + * that slots with higher priority come earlier + * @property {Function(mod, ...colors)} [color] - function that will be + * used to determine the color. By default it just copies first color in + * dependency list. + * @argument {Number} mod - `1` (light-on-dark) or `-1` (dark-on-light) + * depending on background color (for textColor)/given color. + * @argument {...Object} deps - each argument after mod represents each + * color from `depends` array. All colors take user customizations into + * account and represented by { r, g, b } objects. + * @returns {Object} resulting color, should be in { r, g, b } form + * + * @property {Boolean|String} [textColor] - true to mark color slot as text + * color. This enables automatic text color generation for the slot. Use + * 'preserve' string if you don't want text color to fall back to + * black/white. Use 'bw' to only ever use black or white. This also makes + * following properties required: + * @property {String} [layer] - which layer the text sit on top on - used + * to account for transparency in text color calculation + * layer is inherited from parents. To break inheritance graph use null + * @property {String} [variant] - which color slot is background (same as + * above, used to account for transparency) + */ +export const SLOT_INHERITANCE = { + bg: { + depends: [], + opacity: 'bg', + priority: 1 + }, + fg: { + depends: [], + priority: 1 + }, + text: { + depends: [], + layer: 'bg', + opacity: null, + priority: 1 + }, + underlay: { + default: '#000000', + opacity: 'underlay' + }, + link: { + depends: ['accent'], + priority: 1 + }, + accent: { + depends: ['link'], + priority: 1 + }, + faint: { + depends: ['text'], + opacity: 'faint' + }, + faintLink: { + depends: ['link'], + opacity: 'faint' + }, + postFaintLink: { + depends: ['postLink'], + opacity: 'faint' + }, + + cBlue: '#0000ff', + cRed: '#FF0000', + cGreen: '#00FF00', + cOrange: '#E3FF00', + + profileBg: { + depends: ['bg'], + color: (mod, bg) => ({ + r: Math.floor(bg.r * 0.53), + g: Math.floor(bg.g * 0.56), + b: Math.floor(bg.b * 0.59) + }) + }, + profileTint: { + depends: ['bg'], + layer: 'profileTint', + opacity: 'profileTint' + }, + + highlight: { + depends: ['bg'], + color: (mod, bg) => brightness(5 * mod, bg).rgb + }, + highlightLightText: { + depends: ['lightText'], + layer: 'highlight', + textColor: true + }, + highlightPostLink: { + depends: ['postLink'], + layer: 'highlight', + textColor: 'preserve' + }, + highlightFaintText: { + depends: ['faint'], + layer: 'highlight', + textColor: true + }, + highlightFaintLink: { + depends: ['faintLink'], + layer: 'highlight', + textColor: 'preserve' + }, + highlightPostFaintLink: { + depends: ['postFaintLink'], + layer: 'highlight', + textColor: 'preserve' + }, + highlightText: { + depends: ['text'], + layer: 'highlight', + textColor: true + }, + highlightLink: { + depends: ['link'], + layer: 'highlight', + textColor: 'preserve' + }, + highlightIcon: { + depends: ['highlight', 'highlightText'], + color: (mod, bg, text) => mixrgb(bg, text) + }, + + popover: { + depends: ['bg'], + opacity: 'popover' + }, + popoverLightText: { + depends: ['lightText'], + layer: 'popover', + textColor: true + }, + popoverPostLink: { + depends: ['postLink'], + layer: 'popover', + textColor: 'preserve' + }, + popoverFaintText: { + depends: ['faint'], + layer: 'popover', + textColor: true + }, + popoverFaintLink: { + depends: ['faintLink'], + layer: 'popover', + textColor: 'preserve' + }, + popoverPostFaintLink: { + depends: ['postFaintLink'], + layer: 'popover', + textColor: 'preserve' + }, + popoverText: { + depends: ['text'], + layer: 'popover', + textColor: true + }, + popoverLink: { + depends: ['link'], + layer: 'popover', + textColor: 'preserve' + }, + popoverIcon: { + depends: ['popover', 'popoverText'], + color: (mod, bg, text) => mixrgb(bg, text) + }, + + selectedPost: '--highlight', + selectedPostFaintText: { + depends: ['highlightFaintText'], + layer: 'highlight', + variant: 'selectedPost', + textColor: true + }, + selectedPostLightText: { + depends: ['highlightLightText'], + layer: 'highlight', + variant: 'selectedPost', + textColor: true + }, + selectedPostPostLink: { + depends: ['highlightPostLink'], + layer: 'highlight', + variant: 'selectedPost', + textColor: 'preserve' + }, + selectedPostFaintLink: { + depends: ['highlightFaintLink'], + layer: 'highlight', + variant: 'selectedPost', + textColor: 'preserve' + }, + selectedPostText: { + depends: ['highlightText'], + layer: 'highlight', + variant: 'selectedPost', + textColor: true + }, + selectedPostLink: { + depends: ['highlightLink'], + layer: 'highlight', + variant: 'selectedPost', + textColor: 'preserve' + }, + selectedPostIcon: { + depends: ['selectedPost', 'selectedPostText'], + color: (mod, bg, text) => mixrgb(bg, text) + }, + + selectedMenu: { + depends: ['bg'], + color: (mod, bg) => brightness(5 * mod, bg).rgb + }, + selectedMenuLightText: { + depends: ['highlightLightText'], + layer: 'selectedMenu', + variant: 'selectedMenu', + textColor: true + }, + selectedMenuFaintText: { + depends: ['highlightFaintText'], + layer: 'selectedMenu', + variant: 'selectedMenu', + textColor: true + }, + selectedMenuFaintLink: { + depends: ['highlightFaintLink'], + layer: 'selectedMenu', + variant: 'selectedMenu', + textColor: 'preserve' + }, + selectedMenuText: { + depends: ['highlightText'], + layer: 'selectedMenu', + variant: 'selectedMenu', + textColor: true + }, + selectedMenuLink: { + depends: ['highlightLink'], + layer: 'selectedMenu', + variant: 'selectedMenu', + textColor: 'preserve' + }, + selectedMenuIcon: { + depends: ['selectedMenu', 'selectedMenuText'], + color: (mod, bg, text) => mixrgb(bg, text) + }, + + selectedMenuPopover: { + depends: ['popover'], + color: (mod, bg) => brightness(5 * mod, bg).rgb + }, + selectedMenuPopoverLightText: { + depends: ['selectedMenuLightText'], + layer: 'selectedMenuPopover', + variant: 'selectedMenuPopover', + textColor: true + }, + selectedMenuPopoverFaintText: { + depends: ['selectedMenuFaintText'], + layer: 'selectedMenuPopover', + variant: 'selectedMenuPopover', + textColor: true + }, + selectedMenuPopoverFaintLink: { + depends: ['selectedMenuFaintLink'], + layer: 'selectedMenuPopover', + variant: 'selectedMenuPopover', + textColor: 'preserve' + }, + selectedMenuPopoverText: { + depends: ['selectedMenuText'], + layer: 'selectedMenuPopover', + variant: 'selectedMenuPopover', + textColor: true + }, + selectedMenuPopoverLink: { + depends: ['selectedMenuLink'], + layer: 'selectedMenuPopover', + variant: 'selectedMenuPopover', + textColor: 'preserve' + }, + selectedMenuPopoverIcon: { + depends: ['selectedMenuPopover', 'selectedMenuText'], + color: (mod, bg, text) => mixrgb(bg, text) + }, + + lightText: { + depends: ['text'], + layer: 'bg', + textColor: 'preserve', + color: (mod, text) => brightness(20 * mod, text).rgb + }, + + postLink: { + depends: ['link'], + layer: 'bg', + textColor: 'preserve' + }, + + border: { + depends: ['fg'], + opacity: 'border', + color: (mod, fg) => brightness(2 * mod, fg).rgb + }, + + poll: { + depends: ['accent', 'bg'], + copacity: 'poll', + color: (mod, accent, bg) => alphaBlend(accent, 0.4, bg) + }, + pollText: { + depends: ['text'], + layer: 'poll', + textColor: true + }, + + icon: { + depends: ['bg', 'text'], + inheritsOpacity: false, + color: (mod, bg, text) => mixrgb(bg, text) + }, + + // Foreground + fgText: { + depends: ['text'], + layer: 'fg', + textColor: true + }, + fgLink: { + depends: ['link'], + layer: 'fg', + textColor: 'preserve' + }, + + // Panel header + panel: { + depends: ['fg'], + opacity: 'panel' + }, + panelText: { + depends: ['text'], + layer: 'panel', + textColor: true + }, + panelFaint: { + depends: ['fgText'], + layer: 'panel', + opacity: 'faint', + textColor: true + }, + panelLink: { + depends: ['fgLink'], + layer: 'panel', + textColor: 'preserve' + }, + + // Top bar + topBar: '--fg', + topBarText: { + depends: ['fgText'], + layer: 'topBar', + textColor: true + }, + topBarLink: { + depends: ['fgLink'], + layer: 'topBar', + textColor: 'preserve' + }, + + // Tabs + tab: { + depends: ['btn'] + }, + tabText: { + depends: ['btnText'], + layer: 'btn', + textColor: true + }, + tabActiveText: { + depends: ['text'], + layer: 'bg', + textColor: true + }, + + // Buttons + btn: { + depends: ['fg'], + variant: 'btn', + opacity: 'btn' + }, + btnText: { + depends: ['fgText'], + layer: 'btn', + textColor: true + }, + btnPanelText: { + depends: ['btnText'], + layer: 'btnPanel', + variant: 'btn', + textColor: true + }, + btnTopBarText: { + depends: ['btnText'], + layer: 'btnTopBar', + variant: 'btn', + textColor: true + }, + + // Buttons: pressed + btnPressed: { + depends: ['btn'], + layer: 'btn' + }, + btnPressedText: { + depends: ['btnText'], + layer: 'btn', + variant: 'btnPressed', + textColor: true + }, + btnPressedPanel: { + depends: ['btnPressed'], + layer: 'btn' + }, + btnPressedPanelText: { + depends: ['btnPanelText'], + layer: 'btnPanel', + variant: 'btnPressed', + textColor: true + }, + btnPressedTopBar: { + depends: ['btnPressed'], + layer: 'btn' + }, + btnPressedTopBarText: { + depends: ['btnTopBarText'], + layer: 'btnTopBar', + variant: 'btnPressed', + textColor: true + }, + + // Buttons: toggled + btnToggled: { + depends: ['btn'], + layer: 'btn', + color: (mod, btn) => brightness(mod * 20, btn).rgb + }, + btnToggledText: { + depends: ['btnText'], + layer: 'btn', + variant: 'btnToggled', + textColor: true + }, + btnToggledPanelText: { + depends: ['btnPanelText'], + layer: 'btnPanel', + variant: 'btnToggled', + textColor: true + }, + btnToggledTopBarText: { + depends: ['btnTopBarText'], + layer: 'btnTopBar', + variant: 'btnToggled', + textColor: true + }, + + // Buttons: disabled + btnDisabled: { + depends: ['btn', 'bg'], + color: (mod, btn, bg) => alphaBlend(btn, 0.25, bg) + }, + btnDisabledText: { + depends: ['btnText', 'btnDisabled'], + layer: 'btn', + variant: 'btnDisabled', + color: (mod, text, btn) => alphaBlend(text, 0.25, btn) + }, + btnDisabledPanelText: { + depends: ['btnPanelText', 'btnDisabled'], + layer: 'btnPanel', + variant: 'btnDisabled', + color: (mod, text, btn) => alphaBlend(text, 0.25, btn) + }, + btnDisabledTopBarText: { + depends: ['btnTopBarText', 'btnDisabled'], + layer: 'btnTopBar', + variant: 'btnDisabled', + color: (mod, text, btn) => alphaBlend(text, 0.25, btn) + }, + + // Input fields + input: { + depends: ['fg'], + opacity: 'input' + }, + inputText: { + depends: ['text'], + layer: 'input', + textColor: true + }, + inputPanelText: { + depends: ['panelText'], + layer: 'inputPanel', + variant: 'input', + textColor: true + }, + inputTopbarText: { + depends: ['topBarText'], + layer: 'inputTopBar', + variant: 'input', + textColor: true + }, + + alertError: { + depends: ['cRed'], + opacity: 'alert' + }, + alertErrorText: { + depends: ['text'], + layer: 'alert', + variant: 'alertError', + textColor: true + }, + alertErrorPanelText: { + depends: ['panelText'], + layer: 'alertPanel', + variant: 'alertError', + textColor: true + }, + + alertWarning: { + depends: ['cOrange'], + opacity: 'alert' + }, + alertWarningText: { + depends: ['text'], + layer: 'alert', + variant: 'alertWarning', + textColor: true + }, + alertWarningPanelText: { + depends: ['panelText'], + layer: 'alertPanel', + variant: 'alertWarning', + textColor: true + }, + + alertNeutral: { + depends: ['text'], + opacity: 'alert' + }, + alertNeutralText: { + depends: ['text'], + layer: 'alert', + variant: 'alertNeutral', + color: (mod, text) => invertLightness(text).rgb, + textColor: true + }, + alertNeutralPanelText: { + depends: ['panelText'], + layer: 'alertPanel', + variant: 'alertNeutral', + textColor: true + }, + + badgeNotification: '--cRed', + badgeNotificationText: { + depends: ['text', 'badgeNotification'], + layer: 'badge', + variant: 'badgeNotification', + textColor: 'bw' + } +} diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js new file mode 100644 index 00000000..dd87e3cf --- /dev/null +++ b/src/services/theme_data/theme_data.service.js @@ -0,0 +1,405 @@ +import { convert, brightness, contrastRatio } from 'chromatism' +import { alphaBlendLayers, getTextColor, relativeLuminance } from '../color_convert/color_convert.js' +import { LAYERS, DEFAULT_OPACITY, SLOT_INHERITANCE } from './pleromafe.js' + +/* + * # What's all this? + * Here be theme engine for pleromafe. All of this supposed to ease look + * and feel customization, making widget styles and make developer's life + * easier when it comes to supporting themes. Like many other theme systems + * it operates on color definitions, or "slots" - for example you define + * "button" color slot and then in UI component Button's CSS you refer to + * it as a CSS3 Variable. + * + * Some applications allow you to customize colors for certain things. + * Some UI toolkits allow you to define colors for each type of widget. + * Most of them are pretty barebones and have no assistance for common + * problems and cases, and in general themes themselves are very hard to + * maintain in all aspects. This theme engine tries to solve all of the + * common problems with themes. + * + * You don't have redefine several similar colors if you just want to + * change one color - all color slots are derived from other ones, so you + * can have at least one or two "basic" colors defined and have all other + * components inherit and modify basic ones. + * + * You don't have to test contrast ratio for colors or pick text color for + * each element even if you have light-on-dark elements in dark-on-light + * theme. + * + * You don't have to maintain order of code for inheriting slots from othet + * slots - dependency graph resolving does it for you. + */ + +/* This indicates that this version of code outputs similar theme data and + * should be incremented if output changes - for instance if getTextColor + * function changes and older themes no longer render text colors as + * author intended previously. + */ +export const CURRENT_VERSION = 3 + +export const getLayersArray = (layer, data = LAYERS) => { + let array = [layer] + let parent = data[layer] + while (parent) { + array.unshift(parent) + parent = data[parent] + } + return array +} + +export const getLayers = (layer, variant = layer, opacitySlot, colors, opacity) => { + return getLayersArray(layer).map((currentLayer) => ([ + currentLayer === layer + ? colors[variant] + : colors[currentLayer], + currentLayer === layer + ? opacity[opacitySlot] || 1 + : opacity[currentLayer] + ])) +} + +const getDependencies = (key, inheritance) => { + const data = inheritance[key] + if (typeof data === 'string' && data.startsWith('--')) { + return [data.substring(2)] + } else { + if (data === null) return [] + const { depends, layer, variant } = data + const layerDeps = layer + ? getLayersArray(layer).map(currentLayer => { + return currentLayer === layer + ? variant || layer + : currentLayer + }) + : [] + if (Array.isArray(depends)) { + return [...depends, ...layerDeps] + } else { + return [...layerDeps] + } + } +} + +/** + * Sorts inheritance object topologically - dependant slots come after + * dependencies + * + * @property {Object} inheritance - object defining the nodes + * @property {Function} getDeps - function that returns dependencies for + * given value and inheritance object. + * @returns {String[]} keys of inheritance object, sorted in topological + * order. Additionally, dependency-less nodes will always be first in line + */ +export const topoSort = ( + inheritance = SLOT_INHERITANCE, + getDeps = getDependencies +) => { + // This is an implementation of https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm + + const allKeys = Object.keys(inheritance) + const whites = new Set(allKeys) + const grays = new Set() + const blacks = new Set() + const unprocessed = [...allKeys] + const output = [] + + const step = (node) => { + if (whites.has(node)) { + // Make node "gray" + whites.delete(node) + grays.add(node) + // Do step for each node connected to it (one way) + getDeps(node, inheritance).forEach(step) + // Make node "black" + grays.delete(node) + blacks.add(node) + // 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 + } else { + throw new Error('Unintended condition in topoSort!') + } + } + while (unprocessed.length > 0) { + step(unprocessed.pop()) + } + return output.sort((a, b) => { + const depsA = getDeps(a, inheritance).length + const depsB = getDeps(b, inheritance).length + + if (depsA === depsB || (depsB !== 0 && depsA !== 0)) return 0 + if (depsA === 0 && depsB !== 0) return -1 + if (depsB === 0 && depsA !== 0) return 1 + }) +} + +const expandSlotValue = (value) => { + if (typeof value === 'object') return value + return { + depends: value.startsWith('--') ? [value.substring(2)] : [], + default: value.startsWith('#') ? value : undefined + } +} +/** + * retrieves opacity slot for given slot. This goes up the depenency graph + * to find which parent has opacity slot defined for it. + * TODO refactor this + */ +export const getOpacitySlot = ( + k, + inheritance = SLOT_INHERITANCE, + getDeps = getDependencies +) => { + const value = expandSlotValue(inheritance[k]) + if (value.opacity === null) return + if (value.opacity) return value.opacity + const findInheritedOpacity = (key, visited = [k]) => { + const depSlot = getDeps(key, inheritance)[0] + if (depSlot === undefined) return + const dependency = inheritance[depSlot] + if (dependency === undefined) return + if (dependency.opacity || dependency === null) { + return dependency.opacity + } else if (dependency.depends && visited.includes(depSlot)) { + return findInheritedOpacity(depSlot, [...visited, depSlot]) + } else { + return null + } + } + if (value.depends) { + return findInheritedOpacity(k) + } +} + +/** + * retrieves layer slot for given slot. This goes up the depenency graph + * to find which parent has opacity slot defined for it. + * this is basically copypaste of getOpacitySlot except it checks if key is + * in LAYERS + * TODO refactor this + */ +export const getLayerSlot = ( + k, + inheritance = SLOT_INHERITANCE, + getDeps = getDependencies +) => { + const value = expandSlotValue(inheritance[k]) + if (LAYERS[k]) return k + if (value.layer === null) return + if (value.layer) return value.layer + const findInheritedLayer = (key, visited = [k]) => { + const depSlot = getDeps(key, inheritance)[0] + if (depSlot === undefined) return + const dependency = inheritance[depSlot] + if (dependency === undefined) return + if (dependency.layer || dependency === null) { + return dependency.layer + } else if (dependency.depends) { + return findInheritedLayer(dependency, [...visited, depSlot]) + } else { + return null + } + } + if (value.depends) { + return findInheritedLayer(k) + } +} + +/** + * topologically sorted SLOT_INHERITANCE + */ +export const SLOT_ORDERED = topoSort( + Object.entries(SLOT_INHERITANCE) + .sort(([aK, aV], [bK, bV]) => ((aV && aV.priority) || 0) - ((bV && bV.priority) || 0)) + .reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}) +) + +/** + * All opacity slots used in color slots, their default values and affected + * color slots. + */ +export const OPACITIES = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) => { + const opacity = getOpacitySlot(k, SLOT_INHERITANCE, getDependencies) + if (opacity) { + return { + ...acc, + [opacity]: { + defaultValue: DEFAULT_OPACITY[opacity] || 1, + affectedSlots: [...((acc[opacity] && acc[opacity].affectedSlots) || []), k] + } + } + } else { + return acc + } +}, {}) + +/** + * Handle dynamic color + */ +export const computeDynamicColor = (sourceColor, getColor, mod) => { + if (typeof sourceColor !== 'string' || !sourceColor.startsWith('--')) return sourceColor + let targetColor = null + // Color references other color + const [variable, modifier] = sourceColor.split(/,/g).map(str => str.trim()) + const variableSlot = variable.substring(2) + targetColor = getColor(variableSlot) + if (modifier) { + targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb + } + return targetColor +} + +/** + * THE function you want to use. Takes provided colors and opacities + * value and uses inheritance data to figure out color needed for the slot. + */ +export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({ colors, opacity }, key) => { + const sourceColor = sourceColors[key] + const value = expandSlotValue(SLOT_INHERITANCE[key]) + const deps = getDependencies(key, SLOT_INHERITANCE) + const isTextColor = !!value.textColor + const variant = value.variant || value.layer + + let backgroundColor = null + + if (isTextColor) { + backgroundColor = alphaBlendLayers( + { ...(colors[deps[0]] || convert(sourceColors[key] || '#FF00FF').rgb) }, + getLayers( + getLayerSlot(key) || 'bg', + variant || 'bg', + getOpacitySlot(variant), + colors, + opacity + ) + ) + } else if (variant && variant !== key) { + backgroundColor = colors[variant] || convert(sourceColors[variant]).rgb + } else { + backgroundColor = colors.bg || convert(sourceColors.bg) + } + + const isLightOnDark = relativeLuminance(backgroundColor) < 0.5 + const mod = isLightOnDark ? 1 : -1 + + let outputColor = null + if (sourceColor) { + // Color is defined in source color + let targetColor = sourceColor + if (targetColor === 'transparent') { + // We take only layers below current one + const layers = getLayers( + getLayerSlot(key), + key, + getOpacitySlot(key) || key, + colors, + opacity + ).slice(0, -1) + targetColor = { + ...alphaBlendLayers( + convert('#FF00FF').rgb, + layers + ), + a: 0 + } + } else if (typeof sourceColor === 'string' && sourceColor.startsWith('--')) { + targetColor = computeDynamicColor( + sourceColor, + variableSlot => colors[variableSlot] || sourceColors[variableSlot], + mod + ) + } else if (typeof sourceColor === 'string' && sourceColor.startsWith('#')) { + targetColor = convert(targetColor).rgb + } + outputColor = { ...targetColor } + } else if (value.default) { + // same as above except in object form + outputColor = convert(value.default).rgb + } else { + // calculate color + const defaultColorFunc = (mod, dep) => ({ ...dep }) + const colorFunc = value.color || defaultColorFunc + + if (value.textColor) { + if (value.textColor === 'bw') { + outputColor = contrastRatio(backgroundColor).rgb + } else { + let color = { ...colors[deps[0]] } + if (value.color) { + color = colorFunc(mod, ...deps.map((dep) => ({ ...colors[dep] }))) + } + outputColor = getTextColor( + backgroundColor, + { ...color }, + value.textColor === 'preserve' + ) + } + } else { + // background color case + outputColor = colorFunc( + mod, + ...deps.map((dep) => ({ ...colors[dep] })) + ) + } + } + if (!outputColor) { + throw new Error('Couldn\'t generate color for ' + key) + } + + const opacitySlot = value.opacity || getOpacitySlot(key) + const ownOpacitySlot = value.opacity + + if (ownOpacitySlot === null) { + outputColor.a = 1 + } else if (sourceColor === 'transparent') { + outputColor.a = 0 + } else { + const opacityOverriden = ownOpacitySlot && sourceOpacity[opacitySlot] !== undefined + + const dependencySlot = deps[0] + const dependencyColor = dependencySlot && colors[dependencySlot] + + if (!ownOpacitySlot && dependencyColor && !value.textColor && ownOpacitySlot !== null) { + // Inheriting color from dependency (weird, i know) + // except if it's a text color or opacity slot is set to 'null' + outputColor.a = dependencyColor.a + } else if (!dependencyColor && !opacitySlot) { + // Remove any alpha channel if no dependency and no opacitySlot found + delete outputColor.a + } else { + // Otherwise try to assign opacity + if (dependencyColor && dependencyColor.a === 0) { + // transparent dependency shall make dependents transparent too + outputColor.a = 0 + } else { + // Otherwise check if opacity is overriden and use that or default value instead + outputColor.a = Number( + opacityOverriden + ? sourceOpacity[opacitySlot] + : (OPACITIES[opacitySlot] || {}).defaultValue + ) + } + } + } + + if (Number.isNaN(outputColor.a) || outputColor.a === undefined) { + outputColor.a = 1 + } + + if (opacitySlot) { + return { + colors: { ...colors, [key]: outputColor }, + opacity: { ...opacity, [opacitySlot]: outputColor.a } + } + } else { + return { + colors: { ...colors, [key]: outputColor }, + opacity + } + } +}, { colors: {}, opacity: {} }) diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 9eb30c2d..c6b28ad5 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -6,6 +6,7 @@ const update = ({ store, statuses, timeline, showImmediately, userId }) => { const ccTimeline = camelCase(timeline) store.dispatch('setError', { value: false }) + store.dispatch('setErrorData', { value: null }) store.dispatch('addNewStatuses', { timeline: ccTimeline, @@ -45,6 +46,10 @@ const fetchAndUpdate = ({ return apiService.fetchTimeline(args) .then((statuses) => { + if (statuses.error) { + store.dispatch('setErrorData', { value: statuses }) + return + } if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) { store.dispatch('queueFlush', { timeline: timeline, id: timelineData.maxId }) } diff --git a/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js b/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js new file mode 100644 index 00000000..de6f20ef --- /dev/null +++ b/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js @@ -0,0 +1,94 @@ +/** + * This is a tiny purpose-built HTML parser/processor. This basically detects any type of visual newline and + * allows it to be processed, useful for greentexting, mostly + * + * known issue: doesn't handle CDATA so nested CDATA might not work well + * + * @param {Object} input - input data + * @param {(string) => string} processor - function that will be called on every line + * @return {string} processed html + */ +export const processHtml = (html, processor) => { + const handledTags = new Set(['p', 'br', 'div']) + const openCloseTags = new Set(['p', 'div']) + + let buffer = '' // Current output buffer + const level = [] // How deep we are in tags and which tags were there + let textBuffer = '' // Current line content + let tagBuffer = null // Current tag buffer, if null = we are not currently reading a tag + + // Extracts tag name from tag, i.e. <span a="b"> => span + const getTagName = (tag) => { + const result = /(?:<\/(\w+)>|<(\w+)\s?[^/]*?\/?>)/gi.exec(tag) + return result && (result[1] || result[2]) + } + + const flush = () => { // Processes current line buffer, adds it to output buffer and clears line buffer + if (textBuffer.trim().length > 0) { + buffer += processor(textBuffer) + } else { + buffer += textBuffer + } + textBuffer = '' + } + + const handleBr = (tag) => { // handles single newlines/linebreaks/selfclosing + flush() + buffer += tag + } + + const handleOpen = (tag) => { // handles opening tags + flush() + buffer += tag + level.push(tag) + } + + const handleClose = (tag) => { // handles closing tags + flush() + buffer += tag + if (level[level.length - 1] === tag) { + level.pop() + } + } + + for (let i = 0; i < html.length; i++) { + const char = html[i] + if (char === '<' && tagBuffer === null) { + tagBuffer = char + } else if (char !== '>' && tagBuffer !== null) { + tagBuffer += char + } else if (char === '>' && tagBuffer !== null) { + tagBuffer += char + const tagFull = tagBuffer + tagBuffer = null + const tagName = getTagName(tagFull) + if (handledTags.has(tagName)) { + if (tagName === 'br') { + handleBr(tagFull) + } else if (openCloseTags.has(tagName)) { + if (tagFull[1] === '/') { + handleClose(tagFull) + } else if (tagFull[tagFull.length - 2] === '/') { + // self-closing + handleBr(tagFull) + } else { + handleOpen(tagFull) + } + } + } else { + textBuffer += tagFull + } + } else if (char === '\n') { + handleBr(char) + } else { + textBuffer += char + } + } + if (tagBuffer) { + textBuffer += tagBuffer + } + + flush() + + return buffer +} diff --git a/static/config.json b/static/config.json index c8267869..0030f78f 100644 --- a/static/config.json +++ b/static/config.json @@ -1,23 +1,28 @@ { - "theme": "pleroma-dark", - "background": "/static/aurora_borealis.jpg", - "logo": "/static/logo.png", - "logoMask": true, - "logoMargin": ".1em", - "redirectRootNoLogin": "/main/all", - "redirectRootLogin": "/main/friends", - "showInstanceSpecificPanel": false, - "collapseMessageWithSubject": false, - "scopeCopy": true, - "subjectLineBehavior": "email", - "postContentType": "text/plain", "alwaysShowSubjectInput": true, + "background": "/static/aurora_borealis.jpg", + "collapseMessageWithSubject": false, + "disableChat": false, + "greentext": false, + "hideFilteredStatuses": false, + "hideMutedPosts": false, "hidePostStats": false, + "hideSitename": false, "hideUserStats": false, "loginMethod": "password", - "webPushNotifications": false, - "noAttachmentLinks": false, + "logo": "/static/logo.png", + "logoMargin": ".1em", + "logoMask": true, + "minimalScopesMode": false, "nsfwCensorImage": "", + "postContentType": "text/plain", + "redirectRootLogin": "/main/friends", + "redirectRootNoLogin": "/main/all", + "scopeCopy": true, "showFeaturesPanel": true, - "minimalScopesMode": false + "showInstanceSpecificPanel": false, + "sidebarRight": false, + "subjectLineBehavior": "email", + "theme": "pleroma-dark", + "webPushNotifications": false } diff --git a/static/css/base16-3024.css b/static/css/base16-3024.css deleted file mode 100644 index 91859e27..00000000 --- a/static/css/base16-3024.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #090300; } -.base01-background { background-color: #3a3432; } -.base02-background { background-color: #4a4543; } -.base03-background { background-color: #5c5855; } -.base04-background { background-color: #807d7c; } -.base05-background { background-color: #a5a2a2; } -.base06-background { background-color: #d6d5d4; } -.base07-background { background-color: #f7f7f7; } -.base08-background { background-color: #db2d20; } -.base09-background { background-color: #e8bbd0; } -.base0A-background { background-color: #fded02; } -.base0B-background { background-color: #01a252; } -.base0C-background { background-color: #b5e4f4; } -.base0D-background { background-color: #01a0e4; } -.base0E-background { background-color: #a16a94; } -.base0F-background { background-color: #cdab53; } - -.base00 { color: #090300; } -.base01 { color: #3a3432; } -.base02 { color: #4a4543; } -.base03 { color: #5c5855; } -.base04 { color: #807d7c; } -.base05 { color: #a5a2a2; } -.base06 { color: #d6d5d4; } -.base07 { color: #f7f7f7; } -.base08 { color: #db2d20; } -.base09 { color: #e8bbd0; } -.base0A { color: #fded02; } -.base0B { color: #01a252; } -.base0C { color: #b5e4f4; } -.base0D { color: #01a0e4; } -.base0E { color: #a16a94; } -.base0F { color: #cdab53; } diff --git a/static/css/base16-apathy.css b/static/css/base16-apathy.css deleted file mode 100644 index 2e99ba1f..00000000 --- a/static/css/base16-apathy.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #031A16; } -.base01-background { background-color: #0B342D; } -.base02-background { background-color: #184E45; } -.base03-background { background-color: #2B685E; } -.base04-background { background-color: #5F9C92; } -.base05-background { background-color: #81B5AC; } -.base06-background { background-color: #A7CEC8; } -.base07-background { background-color: #D2E7E4; } -.base08-background { background-color: #3E9688; } -.base09-background { background-color: #3E7996; } -.base0A-background { background-color: #3E4C96; } -.base0B-background { background-color: #883E96; } -.base0C-background { background-color: #963E4C; } -.base0D-background { background-color: #96883E; } -.base0E-background { background-color: #4C963E; } -.base0F-background { background-color: #3E965B; } - -.base00 { color: #031A16; } -.base01 { color: #0B342D; } -.base02 { color: #184E45; } -.base03 { color: #2B685E; } -.base04 { color: #5F9C92; } -.base05 { color: #81B5AC; } -.base06 { color: #A7CEC8; } -.base07 { color: #D2E7E4; } -.base08 { color: #3E9688; } -.base09 { color: #3E7996; } -.base0A { color: #3E4C96; } -.base0B { color: #883E96; } -.base0C { color: #963E4C; } -.base0D { color: #96883E; } -.base0E { color: #4C963E; } -.base0F { color: #3E965B; } diff --git a/static/css/base16-ashes.css b/static/css/base16-ashes.css deleted file mode 100644 index d10e1918..00000000 --- a/static/css/base16-ashes.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #1C2023; } -.base01-background { background-color: #393F45; } -.base02-background { background-color: #565E65; } -.base03-background { background-color: #747C84; } -.base04-background { background-color: #ADB3BA; } -.base05-background { background-color: #C7CCD1; } -.base06-background { background-color: #DFE2E5; } -.base07-background { background-color: #F3F4F5; } -.base08-background { background-color: #C7AE95; } -.base09-background { background-color: #C7C795; } -.base0A-background { background-color: #AEC795; } -.base0B-background { background-color: #95C7AE; } -.base0C-background { background-color: #95AEC7; } -.base0D-background { background-color: #AE95C7; } -.base0E-background { background-color: #C795AE; } -.base0F-background { background-color: #C79595; } - -.base00 { color: #1C2023; } -.base01 { color: #393F45; } -.base02 { color: #565E65; } -.base03 { color: #747C84; } -.base04 { color: #ADB3BA; } -.base05 { color: #C7CCD1; } -.base06 { color: #DFE2E5; } -.base07 { color: #F3F4F5; } -.base08 { color: #C7AE95; } -.base09 { color: #C7C795; } -.base0A { color: #AEC795; } -.base0B { color: #95C7AE; } -.base0C { color: #95AEC7; } -.base0D { color: #AE95C7; } -.base0E { color: #C795AE; } -.base0F { color: #C79595; } diff --git a/static/css/base16-atelier-cave.css b/static/css/base16-atelier-cave.css deleted file mode 100644 index 5ac17f97..00000000 --- a/static/css/base16-atelier-cave.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #19171c; } -.base01-background { background-color: #26232a; } -.base02-background { background-color: #585260; } -.base03-background { background-color: #655f6d; } -.base04-background { background-color: #7e7887; } -.base05-background { background-color: #8b8792; } -.base06-background { background-color: #e2dfe7; } -.base07-background { background-color: #efecf4; } -.base08-background { background-color: #be4678; } -.base09-background { background-color: #aa573c; } -.base0A-background { background-color: #a06e3b; } -.base0B-background { background-color: #2a9292; } -.base0C-background { background-color: #398bc6; } -.base0D-background { background-color: #576ddb; } -.base0E-background { background-color: #955ae7; } -.base0F-background { background-color: #bf40bf; } - -.base00 { color: #19171c; } -.base01 { color: #26232a; } -.base02 { color: #585260; } -.base03 { color: #655f6d; } -.base04 { color: #7e7887; } -.base05 { color: #8b8792; } -.base06 { color: #e2dfe7; } -.base07 { color: #efecf4; } -.base08 { color: #be4678; } -.base09 { color: #aa573c; } -.base0A { color: #a06e3b; } -.base0B { color: #2a9292; } -.base0C { color: #398bc6; } -.base0D { color: #576ddb; } -.base0E { color: #955ae7; } -.base0F { color: #bf40bf; } diff --git a/static/css/base16-atelier-dune.css b/static/css/base16-atelier-dune.css deleted file mode 100644 index cfb2d9a1..00000000 --- a/static/css/base16-atelier-dune.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #20201d; } -.base01-background { background-color: #292824; } -.base02-background { background-color: #6e6b5e; } -.base03-background { background-color: #7d7a68; } -.base04-background { background-color: #999580; } -.base05-background { background-color: #a6a28c; } -.base06-background { background-color: #e8e4cf; } -.base07-background { background-color: #fefbec; } -.base08-background { background-color: #d73737; } -.base09-background { background-color: #b65611; } -.base0A-background { background-color: #ae9513; } -.base0B-background { background-color: #60ac39; } -.base0C-background { background-color: #1fad83; } -.base0D-background { background-color: #6684e1; } -.base0E-background { background-color: #b854d4; } -.base0F-background { background-color: #d43552; } - -.base00 { color: #20201d; } -.base01 { color: #292824; } -.base02 { color: #6e6b5e; } -.base03 { color: #7d7a68; } -.base04 { color: #999580; } -.base05 { color: #a6a28c; } -.base06 { color: #e8e4cf; } -.base07 { color: #fefbec; } -.base08 { color: #d73737; } -.base09 { color: #b65611; } -.base0A { color: #ae9513; } -.base0B { color: #60ac39; } -.base0C { color: #1fad83; } -.base0D { color: #6684e1; } -.base0E { color: #b854d4; } -.base0F { color: #d43552; } diff --git a/static/css/base16-atelier-estuary.css b/static/css/base16-atelier-estuary.css deleted file mode 100644 index 76d82c75..00000000 --- a/static/css/base16-atelier-estuary.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #22221b; } -.base01-background { background-color: #302f27; } -.base02-background { background-color: #5f5e4e; } -.base03-background { background-color: #6c6b5a; } -.base04-background { background-color: #878573; } -.base05-background { background-color: #929181; } -.base06-background { background-color: #e7e6df; } -.base07-background { background-color: #f4f3ec; } -.base08-background { background-color: #ba6236; } -.base09-background { background-color: #ae7313; } -.base0A-background { background-color: #a5980d; } -.base0B-background { background-color: #7d9726; } -.base0C-background { background-color: #5b9d48; } -.base0D-background { background-color: #36a166; } -.base0E-background { background-color: #5f9182; } -.base0F-background { background-color: #9d6c7c; } - -.base00 { color: #22221b; } -.base01 { color: #302f27; } -.base02 { color: #5f5e4e; } -.base03 { color: #6c6b5a; } -.base04 { color: #878573; } -.base05 { color: #929181; } -.base06 { color: #e7e6df; } -.base07 { color: #f4f3ec; } -.base08 { color: #ba6236; } -.base09 { color: #ae7313; } -.base0A { color: #a5980d; } -.base0B { color: #7d9726; } -.base0C { color: #5b9d48; } -.base0D { color: #36a166; } -.base0E { color: #5f9182; } -.base0F { color: #9d6c7c; } diff --git a/static/css/base16-atelier-forest.css b/static/css/base16-atelier-forest.css deleted file mode 100644 index 8108ed8f..00000000 --- a/static/css/base16-atelier-forest.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #1b1918; } -.base01-background { background-color: #2c2421; } -.base02-background { background-color: #68615e; } -.base03-background { background-color: #766e6b; } -.base04-background { background-color: #9c9491; } -.base05-background { background-color: #a8a19f; } -.base06-background { background-color: #e6e2e0; } -.base07-background { background-color: #f1efee; } -.base08-background { background-color: #f22c40; } -.base09-background { background-color: #df5320; } -.base0A-background { background-color: #c38418; } -.base0B-background { background-color: #7b9726; } -.base0C-background { background-color: #3d97b8; } -.base0D-background { background-color: #407ee7; } -.base0E-background { background-color: #6666ea; } -.base0F-background { background-color: #c33ff3; } - -.base00 { color: #1b1918; } -.base01 { color: #2c2421; } -.base02 { color: #68615e; } -.base03 { color: #766e6b; } -.base04 { color: #9c9491; } -.base05 { color: #a8a19f; } -.base06 { color: #e6e2e0; } -.base07 { color: #f1efee; } -.base08 { color: #f22c40; } -.base09 { color: #df5320; } -.base0A { color: #c38418; } -.base0B { color: #7b9726; } -.base0C { color: #3d97b8; } -.base0D { color: #407ee7; } -.base0E { color: #6666ea; } -.base0F { color: #c33ff3; } diff --git a/static/css/base16-atelier-heath.css b/static/css/base16-atelier-heath.css deleted file mode 100644 index 8858cb80..00000000 --- a/static/css/base16-atelier-heath.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #1b181b; } -.base01-background { background-color: #292329; } -.base02-background { background-color: #695d69; } -.base03-background { background-color: #776977; } -.base04-background { background-color: #9e8f9e; } -.base05-background { background-color: #ab9bab; } -.base06-background { background-color: #d8cad8; } -.base07-background { background-color: #f7f3f7; } -.base08-background { background-color: #ca402b; } -.base09-background { background-color: #a65926; } -.base0A-background { background-color: #bb8a35; } -.base0B-background { background-color: #918b3b; } -.base0C-background { background-color: #159393; } -.base0D-background { background-color: #516aec; } -.base0E-background { background-color: #7b59c0; } -.base0F-background { background-color: #cc33cc; } - -.base00 { color: #1b181b; } -.base01 { color: #292329; } -.base02 { color: #695d69; } -.base03 { color: #776977; } -.base04 { color: #9e8f9e; } -.base05 { color: #ab9bab; } -.base06 { color: #d8cad8; } -.base07 { color: #f7f3f7; } -.base08 { color: #ca402b; } -.base09 { color: #a65926; } -.base0A { color: #bb8a35; } -.base0B { color: #918b3b; } -.base0C { color: #159393; } -.base0D { color: #516aec; } -.base0E { color: #7b59c0; } -.base0F { color: #cc33cc; } diff --git a/static/css/base16-atelier-lakeside.css b/static/css/base16-atelier-lakeside.css deleted file mode 100644 index 77d44c5f..00000000 --- a/static/css/base16-atelier-lakeside.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #161b1d; } -.base01-background { background-color: #1f292e; } -.base02-background { background-color: #516d7b; } -.base03-background { background-color: #5a7b8c; } -.base04-background { background-color: #7195a8; } -.base05-background { background-color: #7ea2b4; } -.base06-background { background-color: #c1e4f6; } -.base07-background { background-color: #ebf8ff; } -.base08-background { background-color: #d22d72; } -.base09-background { background-color: #935c25; } -.base0A-background { background-color: #8a8a0f; } -.base0B-background { background-color: #568c3b; } -.base0C-background { background-color: #2d8f6f; } -.base0D-background { background-color: #257fad; } -.base0E-background { background-color: #6b6bb8; } -.base0F-background { background-color: #b72dd2; } - -.base00 { color: #161b1d; } -.base01 { color: #1f292e; } -.base02 { color: #516d7b; } -.base03 { color: #5a7b8c; } -.base04 { color: #7195a8; } -.base05 { color: #7ea2b4; } -.base06 { color: #c1e4f6; } -.base07 { color: #ebf8ff; } -.base08 { color: #d22d72; } -.base09 { color: #935c25; } -.base0A { color: #8a8a0f; } -.base0B { color: #568c3b; } -.base0C { color: #2d8f6f; } -.base0D { color: #257fad; } -.base0E { color: #6b6bb8; } -.base0F { color: #b72dd2; } diff --git a/static/css/base16-atelier-plateau.css b/static/css/base16-atelier-plateau.css deleted file mode 100644 index a7445030..00000000 --- a/static/css/base16-atelier-plateau.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #1b1818; } -.base01-background { background-color: #292424; } -.base02-background { background-color: #585050; } -.base03-background { background-color: #655d5d; } -.base04-background { background-color: #7e7777; } -.base05-background { background-color: #8a8585; } -.base06-background { background-color: #e7dfdf; } -.base07-background { background-color: #f4ecec; } -.base08-background { background-color: #ca4949; } -.base09-background { background-color: #b45a3c; } -.base0A-background { background-color: #a06e3b; } -.base0B-background { background-color: #4b8b8b; } -.base0C-background { background-color: #5485b6; } -.base0D-background { background-color: #7272ca; } -.base0E-background { background-color: #8464c4; } -.base0F-background { background-color: #bd5187; } - -.base00 { color: #1b1818; } -.base01 { color: #292424; } -.base02 { color: #585050; } -.base03 { color: #655d5d; } -.base04 { color: #7e7777; } -.base05 { color: #8a8585; } -.base06 { color: #e7dfdf; } -.base07 { color: #f4ecec; } -.base08 { color: #ca4949; } -.base09 { color: #b45a3c; } -.base0A { color: #a06e3b; } -.base0B { color: #4b8b8b; } -.base0C { color: #5485b6; } -.base0D { color: #7272ca; } -.base0E { color: #8464c4; } -.base0F { color: #bd5187; } diff --git a/static/css/base16-atelier-savanna.css b/static/css/base16-atelier-savanna.css deleted file mode 100644 index be728d07..00000000 --- a/static/css/base16-atelier-savanna.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #171c19; } -.base01-background { background-color: #232a25; } -.base02-background { background-color: #526057; } -.base03-background { background-color: #5f6d64; } -.base04-background { background-color: #78877d; } -.base05-background { background-color: #87928a; } -.base06-background { background-color: #dfe7e2; } -.base07-background { background-color: #ecf4ee; } -.base08-background { background-color: #b16139; } -.base09-background { background-color: #9f713c; } -.base0A-background { background-color: #a07e3b; } -.base0B-background { background-color: #489963; } -.base0C-background { background-color: #1c9aa0; } -.base0D-background { background-color: #478c90; } -.base0E-background { background-color: #55859b; } -.base0F-background { background-color: #867469; } - -.base00 { color: #171c19; } -.base01 { color: #232a25; } -.base02 { color: #526057; } -.base03 { color: #5f6d64; } -.base04 { color: #78877d; } -.base05 { color: #87928a; } -.base06 { color: #dfe7e2; } -.base07 { color: #ecf4ee; } -.base08 { color: #b16139; } -.base09 { color: #9f713c; } -.base0A { color: #a07e3b; } -.base0B { color: #489963; } -.base0C { color: #1c9aa0; } -.base0D { color: #478c90; } -.base0E { color: #55859b; } -.base0F { color: #867469; } diff --git a/static/css/base16-atelier-seaside.css b/static/css/base16-atelier-seaside.css deleted file mode 100644 index 8b391466..00000000 --- a/static/css/base16-atelier-seaside.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #131513; } -.base01-background { background-color: #242924; } -.base02-background { background-color: #5e6e5e; } -.base03-background { background-color: #687d68; } -.base04-background { background-color: #809980; } -.base05-background { background-color: #8ca68c; } -.base06-background { background-color: #cfe8cf; } -.base07-background { background-color: #f4fbf4; } -.base08-background { background-color: #e6193c; } -.base09-background { background-color: #87711d; } -.base0A-background { background-color: #98981b; } -.base0B-background { background-color: #29a329; } -.base0C-background { background-color: #1999b3; } -.base0D-background { background-color: #3d62f5; } -.base0E-background { background-color: #ad2bee; } -.base0F-background { background-color: #e619c3; } - -.base00 { color: #131513; } -.base01 { color: #242924; } -.base02 { color: #5e6e5e; } -.base03 { color: #687d68; } -.base04 { color: #809980; } -.base05 { color: #8ca68c; } -.base06 { color: #cfe8cf; } -.base07 { color: #f4fbf4; } -.base08 { color: #e6193c; } -.base09 { color: #87711d; } -.base0A { color: #98981b; } -.base0B { color: #29a329; } -.base0C { color: #1999b3; } -.base0D { color: #3d62f5; } -.base0E { color: #ad2bee; } -.base0F { color: #e619c3; } diff --git a/static/css/base16-atelier-sulphurpool.css b/static/css/base16-atelier-sulphurpool.css deleted file mode 100644 index fb44d6e0..00000000 --- a/static/css/base16-atelier-sulphurpool.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #202746; } -.base01-background { background-color: #293256; } -.base02-background { background-color: #5e6687; } -.base03-background { background-color: #6b7394; } -.base04-background { background-color: #898ea4; } -.base05-background { background-color: #979db4; } -.base06-background { background-color: #dfe2f1; } -.base07-background { background-color: #f5f7ff; } -.base08-background { background-color: #c94922; } -.base09-background { background-color: #c76b29; } -.base0A-background { background-color: #c08b30; } -.base0B-background { background-color: #ac9739; } -.base0C-background { background-color: #22a2c9; } -.base0D-background { background-color: #3d8fd1; } -.base0E-background { background-color: #6679cc; } -.base0F-background { background-color: #9c637a; } - -.base00 { color: #202746; } -.base01 { color: #293256; } -.base02 { color: #5e6687; } -.base03 { color: #6b7394; } -.base04 { color: #898ea4; } -.base05 { color: #979db4; } -.base06 { color: #dfe2f1; } -.base07 { color: #f5f7ff; } -.base08 { color: #c94922; } -.base09 { color: #c76b29; } -.base0A { color: #c08b30; } -.base0B { color: #ac9739; } -.base0C { color: #22a2c9; } -.base0D { color: #3d8fd1; } -.base0E { color: #6679cc; } -.base0F { color: #9c637a; } diff --git a/static/css/base16-bespin.css b/static/css/base16-bespin.css deleted file mode 100644 index 48a9dcf7..00000000 --- a/static/css/base16-bespin.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #28211c; } -.base01-background { background-color: #36312e; } -.base02-background { background-color: #5e5d5c; } -.base03-background { background-color: #666666; } -.base04-background { background-color: #797977; } -.base05-background { background-color: #8a8986; } -.base06-background { background-color: #9d9b97; } -.base07-background { background-color: #baae9e; } -.base08-background { background-color: #cf6a4c; } -.base09-background { background-color: #cf7d34; } -.base0A-background { background-color: #f9ee98; } -.base0B-background { background-color: #54be0d; } -.base0C-background { background-color: #afc4db; } -.base0D-background { background-color: #5ea6ea; } -.base0E-background { background-color: #9b859d; } -.base0F-background { background-color: #937121; } - -.base00 { color: #28211c; } -.base01 { color: #36312e; } -.base02 { color: #5e5d5c; } -.base03 { color: #666666; } -.base04 { color: #797977; } -.base05 { color: #8a8986; } -.base06 { color: #9d9b97; } -.base07 { color: #baae9e; } -.base08 { color: #cf6a4c; } -.base09 { color: #cf7d34; } -.base0A { color: #f9ee98; } -.base0B { color: #54be0d; } -.base0C { color: #afc4db; } -.base0D { color: #5ea6ea; } -.base0E { color: #9b859d; } -.base0F { color: #937121; } diff --git a/static/css/base16-brewer.css b/static/css/base16-brewer.css deleted file mode 100644 index c88f219b..00000000 --- a/static/css/base16-brewer.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #0c0d0e; } -.base01-background { background-color: #2e2f30; } -.base02-background { background-color: #515253; } -.base03-background { background-color: #737475; } -.base04-background { background-color: #959697; } -.base05-background { background-color: #b7b8b9; } -.base06-background { background-color: #dadbdc; } -.base07-background { background-color: #fcfdfe; } -.base08-background { background-color: #e31a1c; } -.base09-background { background-color: #e6550d; } -.base0A-background { background-color: #dca060; } -.base0B-background { background-color: #31a354; } -.base0C-background { background-color: #80b1d3; } -.base0D-background { background-color: #3182bd; } -.base0E-background { background-color: #756bb1; } -.base0F-background { background-color: #b15928; } - -.base00 { color: #0c0d0e; } -.base01 { color: #2e2f30; } -.base02 { color: #515253; } -.base03 { color: #737475; } -.base04 { color: #959697; } -.base05 { color: #b7b8b9; } -.base06 { color: #dadbdc; } -.base07 { color: #fcfdfe; } -.base08 { color: #e31a1c; } -.base09 { color: #e6550d; } -.base0A { color: #dca060; } -.base0B { color: #31a354; } -.base0C { color: #80b1d3; } -.base0D { color: #3182bd; } -.base0E { color: #756bb1; } -.base0F { color: #b15928; } diff --git a/static/css/base16-bright.css b/static/css/base16-bright.css deleted file mode 100644 index c2333b8d..00000000 --- a/static/css/base16-bright.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #000000; } -.base01-background { background-color: #303030; } -.base02-background { background-color: #505050; } -.base03-background { background-color: #b0b0b0; } -.base04-background { background-color: #d0d0d0; } -.base05-background { background-color: #e0e0e0; } -.base06-background { background-color: #f5f5f5; } -.base07-background { background-color: #ffffff; } -.base08-background { background-color: #fb0120; } -.base09-background { background-color: #fc6d24; } -.base0A-background { background-color: #fda331; } -.base0B-background { background-color: #a1c659; } -.base0C-background { background-color: #76c7b7; } -.base0D-background { background-color: #6fb3d2; } -.base0E-background { background-color: #d381c3; } -.base0F-background { background-color: #be643c; } - -.base00 { color: #000000; } -.base01 { color: #303030; } -.base02 { color: #505050; } -.base03 { color: #b0b0b0; } -.base04 { color: #d0d0d0; } -.base05 { color: #e0e0e0; } -.base06 { color: #f5f5f5; } -.base07 { color: #ffffff; } -.base08 { color: #fb0120; } -.base09 { color: #fc6d24; } -.base0A { color: #fda331; } -.base0B { color: #a1c659; } -.base0C { color: #76c7b7; } -.base0D { color: #6fb3d2; } -.base0E { color: #d381c3; } -.base0F { color: #be643c; } diff --git a/static/css/base16-chalk.css b/static/css/base16-chalk.css deleted file mode 100644 index e3cb3c20..00000000 --- a/static/css/base16-chalk.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #151515; } -.base01-background { background-color: #202020; } -.base02-background { background-color: #303030; } -.base03-background { background-color: #505050; } -.base04-background { background-color: #b0b0b0; } -.base05-background { background-color: #d0d0d0; } -.base06-background { background-color: #e0e0e0; } -.base07-background { background-color: #f5f5f5; } -.base08-background { background-color: #fb9fb1; } -.base09-background { background-color: #eda987; } -.base0A-background { background-color: #ddb26f; } -.base0B-background { background-color: #acc267; } -.base0C-background { background-color: #12cfc0; } -.base0D-background { background-color: #6fc2ef; } -.base0E-background { background-color: #e1a3ee; } -.base0F-background { background-color: #deaf8f; } - -.base00 { color: #151515; } -.base01 { color: #202020; } -.base02 { color: #303030; } -.base03 { color: #505050; } -.base04 { color: #b0b0b0; } -.base05 { color: #d0d0d0; } -.base06 { color: #e0e0e0; } -.base07 { color: #f5f5f5; } -.base08 { color: #fb9fb1; } -.base09 { color: #eda987; } -.base0A { color: #ddb26f; } -.base0B { color: #acc267; } -.base0C { color: #12cfc0; } -.base0D { color: #6fc2ef; } -.base0E { color: #e1a3ee; } -.base0F { color: #deaf8f; } diff --git a/static/css/base16-codeschool.css b/static/css/base16-codeschool.css deleted file mode 100644 index 00194bbf..00000000 --- a/static/css/base16-codeschool.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #232c31; } -.base01-background { background-color: #1c3657; } -.base02-background { background-color: #2a343a; } -.base03-background { background-color: #3f4944; } -.base04-background { background-color: #84898c; } -.base05-background { background-color: #9ea7a6; } -.base06-background { background-color: #a7cfa3; } -.base07-background { background-color: #b5d8f6; } -.base08-background { background-color: #2a5491; } -.base09-background { background-color: #43820d; } -.base0A-background { background-color: #a03b1e; } -.base0B-background { background-color: #237986; } -.base0C-background { background-color: #b02f30; } -.base0D-background { background-color: #484d79; } -.base0E-background { background-color: #c59820; } -.base0F-background { background-color: #c98344; } - -.base00 { color: #232c31; } -.base01 { color: #1c3657; } -.base02 { color: #2a343a; } -.base03 { color: #3f4944; } -.base04 { color: #84898c; } -.base05 { color: #9ea7a6; } -.base06 { color: #a7cfa3; } -.base07 { color: #b5d8f6; } -.base08 { color: #2a5491; } -.base09 { color: #43820d; } -.base0A { color: #a03b1e; } -.base0B { color: #237986; } -.base0C { color: #b02f30; } -.base0D { color: #484d79; } -.base0E { color: #c59820; } -.base0F { color: #c98344; } diff --git a/static/css/base16-darktooth.css b/static/css/base16-darktooth.css deleted file mode 100644 index 53448706..00000000 --- a/static/css/base16-darktooth.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #1D2021; } -.base01-background { background-color: #32302F; } -.base02-background { background-color: #504945; } -.base03-background { background-color: #665C54; } -.base04-background { background-color: #928374; } -.base05-background { background-color: #A89984; } -.base06-background { background-color: #D5C4A1; } -.base07-background { background-color: #FDF4C1; } -.base08-background { background-color: #FB543F; } -.base09-background { background-color: #FE8625; } -.base0A-background { background-color: #FAC03B; } -.base0B-background { background-color: #95C085; } -.base0C-background { background-color: #8BA59B; } -.base0D-background { background-color: #0D6678; } -.base0E-background { background-color: #8F4673; } -.base0F-background { background-color: #A87322; } - -.base00 { color: #1D2021; } -.base01 { color: #32302F; } -.base02 { color: #504945; } -.base03 { color: #665C54; } -.base04 { color: #928374; } -.base05 { color: #A89984; } -.base06 { color: #D5C4A1; } -.base07 { color: #FDF4C1; } -.base08 { color: #FB543F; } -.base09 { color: #FE8625; } -.base0A { color: #FAC03B; } -.base0B { color: #95C085; } -.base0C { color: #8BA59B; } -.base0D { color: #0D6678; } -.base0E { color: #8F4673; } -.base0F { color: #A87322; } diff --git a/static/css/base16-default-dark.css b/static/css/base16-default-dark.css deleted file mode 100644 index 3cd7e860..00000000 --- a/static/css/base16-default-dark.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #181818; } -.base01-background { background-color: #282828; } -.base02-background { background-color: #383838; } -.base03-background { background-color: #585858; } -.base04-background { background-color: #b8b8b8; } -.base05-background { background-color: #d8d8d8; } -.base06-background { background-color: #e8e8e8; } -.base07-background { background-color: #f8f8f8; } -.base08-background { background-color: #ab4642; } -.base09-background { background-color: #dc9656; } -.base0A-background { background-color: #f7ca88; } -.base0B-background { background-color: #a1b56c; } -.base0C-background { background-color: #86c1b9; } -.base0D-background { background-color: #7cafc2; } -.base0E-background { background-color: #ba8baf; } -.base0F-background { background-color: #a16946; } - -.base00 { color: #181818; } -.base01 { color: #282828; } -.base02 { color: #383838; } -.base03 { color: #585858; } -.base04 { color: #b8b8b8; } -.base05 { color: #d8d8d8; } -.base06 { color: #e8e8e8; } -.base07 { color: #f8f8f8; } -.base08 { color: #ab4642; } -.base09 { color: #dc9656; } -.base0A { color: #f7ca88; } -.base0B { color: #a1b56c; } -.base0C { color: #86c1b9; } -.base0D { color: #7cafc2; } -.base0E { color: #ba8baf; } -.base0F { color: #a16946; } diff --git a/static/css/base16-default-light.css b/static/css/base16-default-light.css deleted file mode 100644 index 7e660c30..00000000 --- a/static/css/base16-default-light.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #f8f8f8; } -.base01-background { background-color: #e8e8e8; } -.base02-background { background-color: #d8d8d8; } -.base03-background { background-color: #b8b8b8; } -.base04-background { background-color: #585858; } -.base05-background { background-color: #383838; } -.base06-background { background-color: #282828; } -.base07-background { background-color: #181818; } -.base08-background { background-color: #ab4642; } -.base09-background { background-color: #dc9656; } -.base0A-background { background-color: #f7ca88; } -.base0B-background { background-color: #a1b56c; } -.base0C-background { background-color: #86c1b9; } -.base0D-background { background-color: #7cafc2; } -.base0E-background { background-color: #ba8baf; } -.base0F-background { background-color: #a16946; } - -.base00 { color: #f8f8f8; } -.base01 { color: #e8e8e8; } -.base02 { color: #d8d8d8; } -.base03 { color: #b8b8b8; } -.base04 { color: #585858; } -.base05 { color: #383838; } -.base06 { color: #282828; } -.base07 { color: #181818; } -.base08 { color: #ab4642; } -.base09 { color: #dc9656; } -.base0A { color: #f7ca88; } -.base0B { color: #a1b56c; } -.base0C { color: #86c1b9; } -.base0D { color: #7cafc2; } -.base0E { color: #ba8baf; } -.base0F { color: #a16946; } diff --git a/static/css/base16-eighties.css b/static/css/base16-eighties.css deleted file mode 100644 index 8ffcf04d..00000000 --- a/static/css/base16-eighties.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #2d2d2d; } -.base01-background { background-color: #393939; } -.base02-background { background-color: #515151; } -.base03-background { background-color: #747369; } -.base04-background { background-color: #a09f93; } -.base05-background { background-color: #d3d0c8; } -.base06-background { background-color: #e8e6df; } -.base07-background { background-color: #f2f0ec; } -.base08-background { background-color: #f2777a; } -.base09-background { background-color: #f99157; } -.base0A-background { background-color: #ffcc66; } -.base0B-background { background-color: #99cc99; } -.base0C-background { background-color: #66cccc; } -.base0D-background { background-color: #6699cc; } -.base0E-background { background-color: #cc99cc; } -.base0F-background { background-color: #d27b53; } - -.base00 { color: #2d2d2d; } -.base01 { color: #393939; } -.base02 { color: #515151; } -.base03 { color: #747369; } -.base04 { color: #a09f93; } -.base05 { color: #d3d0c8; } -.base06 { color: #e8e6df; } -.base07 { color: #f2f0ec; } -.base08 { color: #f2777a; } -.base09 { color: #f99157; } -.base0A { color: #ffcc66; } -.base0B { color: #99cc99; } -.base0C { color: #66cccc; } -.base0D { color: #6699cc; } -.base0E { color: #cc99cc; } -.base0F { color: #d27b53; } diff --git a/static/css/base16-embers.css b/static/css/base16-embers.css deleted file mode 100644 index 74e9b769..00000000 --- a/static/css/base16-embers.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #16130F; } -.base01-background { background-color: #2C2620; } -.base02-background { background-color: #433B32; } -.base03-background { background-color: #5A5047; } -.base04-background { background-color: #8A8075; } -.base05-background { background-color: #A39A90; } -.base06-background { background-color: #BEB6AE; } -.base07-background { background-color: #DBD6D1; } -.base08-background { background-color: #826D57; } -.base09-background { background-color: #828257; } -.base0A-background { background-color: #6D8257; } -.base0B-background { background-color: #57826D; } -.base0C-background { background-color: #576D82; } -.base0D-background { background-color: #6D5782; } -.base0E-background { background-color: #82576D; } -.base0F-background { background-color: #825757; } - -.base00 { color: #16130F; } -.base01 { color: #2C2620; } -.base02 { color: #433B32; } -.base03 { color: #5A5047; } -.base04 { color: #8A8075; } -.base05 { color: #A39A90; } -.base06 { color: #BEB6AE; } -.base07 { color: #DBD6D1; } -.base08 { color: #826D57; } -.base09 { color: #828257; } -.base0A { color: #6D8257; } -.base0B { color: #57826D; } -.base0C { color: #576D82; } -.base0D { color: #6D5782; } -.base0E { color: #82576D; } -.base0F { color: #825757; } diff --git a/static/css/base16-flat.css b/static/css/base16-flat.css deleted file mode 100644 index 72918a5d..00000000 --- a/static/css/base16-flat.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #2C3E50; } -.base01-background { background-color: #34495E; } -.base02-background { background-color: #7F8C8D; } -.base03-background { background-color: #95A5A6; } -.base04-background { background-color: #BDC3C7; } -.base05-background { background-color: #e0e0e0; } -.base06-background { background-color: #f5f5f5; } -.base07-background { background-color: #ECF0F1; } -.base08-background { background-color: #E74C3C; } -.base09-background { background-color: #E67E22; } -.base0A-background { background-color: #F1C40F; } -.base0B-background { background-color: #2ECC71; } -.base0C-background { background-color: #1ABC9C; } -.base0D-background { background-color: #3498DB; } -.base0E-background { background-color: #9B59B6; } -.base0F-background { background-color: #be643c; } - -.base00 { color: #2C3E50; } -.base01 { color: #34495E; } -.base02 { color: #7F8C8D; } -.base03 { color: #95A5A6; } -.base04 { color: #BDC3C7; } -.base05 { color: #e0e0e0; } -.base06 { color: #f5f5f5; } -.base07 { color: #ECF0F1; } -.base08 { color: #E74C3C; } -.base09 { color: #E67E22; } -.base0A { color: #F1C40F; } -.base0B { color: #2ECC71; } -.base0C { color: #1ABC9C; } -.base0D { color: #3498DB; } -.base0E { color: #9B59B6; } -.base0F { color: #be643c; } diff --git a/static/css/base16-github.css b/static/css/base16-github.css deleted file mode 100644 index 080ed34c..00000000 --- a/static/css/base16-github.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #ffffff; } -.base01-background { background-color: #f5f5f5; } -.base02-background { background-color: #c8c8fa; } -.base03-background { background-color: #969896; } -.base04-background { background-color: #e8e8e8; } -.base05-background { background-color: #333333; } -.base06-background { background-color: #ffffff; } -.base07-background { background-color: #ffffff; } -.base08-background { background-color: #ed6a43; } -.base09-background { background-color: #0086b3; } -.base0A-background { background-color: #795da3; } -.base0B-background { background-color: #183691; } -.base0C-background { background-color: #183691; } -.base0D-background { background-color: #795da3; } -.base0E-background { background-color: #a71d5d; } -.base0F-background { background-color: #333333; } - -.base00 { color: #ffffff; } -.base01 { color: #f5f5f5; } -.base02 { color: #c8c8fa; } -.base03 { color: #969896; } -.base04 { color: #e8e8e8; } -.base05 { color: #333333; } -.base06 { color: #ffffff; } -.base07 { color: #ffffff; } -.base08 { color: #ed6a43; } -.base09 { color: #0086b3; } -.base0A { color: #795da3; } -.base0B { color: #183691; } -.base0C { color: #183691; } -.base0D { color: #795da3; } -.base0E { color: #a71d5d; } -.base0F { color: #333333; } diff --git a/static/css/base16-google-dark.css b/static/css/base16-google-dark.css deleted file mode 100644 index 988eac51..00000000 --- a/static/css/base16-google-dark.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #1d1f21; } -.base01-background { background-color: #282a2e; } -.base02-background { background-color: #373b41; } -.base03-background { background-color: #969896; } -.base04-background { background-color: #b4b7b4; } -.base05-background { background-color: #c5c8c6; } -.base06-background { background-color: #e0e0e0; } -.base07-background { background-color: #ffffff; } -.base08-background { background-color: #CC342B; } -.base09-background { background-color: #F96A38; } -.base0A-background { background-color: #FBA922; } -.base0B-background { background-color: #198844; } -.base0C-background { background-color: #3971ED; } -.base0D-background { background-color: #3971ED; } -.base0E-background { background-color: #A36AC7; } -.base0F-background { background-color: #3971ED; } - -.base00 { color: #1d1f21; } -.base01 { color: #282a2e; } -.base02 { color: #373b41; } -.base03 { color: #969896; } -.base04 { color: #b4b7b4; } -.base05 { color: #c5c8c6; } -.base06 { color: #e0e0e0; } -.base07 { color: #ffffff; } -.base08 { color: #CC342B; } -.base09 { color: #F96A38; } -.base0A { color: #FBA922; } -.base0B { color: #198844; } -.base0C { color: #3971ED; } -.base0D { color: #3971ED; } -.base0E { color: #A36AC7; } -.base0F { color: #3971ED; } diff --git a/static/css/base16-google-light.css b/static/css/base16-google-light.css deleted file mode 100644 index 2ee2a606..00000000 --- a/static/css/base16-google-light.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #ffffff; } -.base01-background { background-color: #e0e0e0; } -.base02-background { background-color: #c5c8c6; } -.base03-background { background-color: #b4b7b4; } -.base04-background { background-color: #969896; } -.base05-background { background-color: #373b41; } -.base06-background { background-color: #282a2e; } -.base07-background { background-color: #1d1f21; } -.base08-background { background-color: #CC342B; } -.base09-background { background-color: #F96A38; } -.base0A-background { background-color: #FBA922; } -.base0B-background { background-color: #198844; } -.base0C-background { background-color: #3971ED; } -.base0D-background { background-color: #3971ED; } -.base0E-background { background-color: #A36AC7; } -.base0F-background { background-color: #3971ED; } - -.base00 { color: #ffffff; } -.base01 { color: #e0e0e0; } -.base02 { color: #c5c8c6; } -.base03 { color: #b4b7b4; } -.base04 { color: #969896; } -.base05 { color: #373b41; } -.base06 { color: #282a2e; } -.base07 { color: #1d1f21; } -.base08 { color: #CC342B; } -.base09 { color: #F96A38; } -.base0A { color: #FBA922; } -.base0B { color: #198844; } -.base0C { color: #3971ED; } -.base0D { color: #3971ED; } -.base0E { color: #A36AC7; } -.base0F { color: #3971ED; } diff --git a/static/css/base16-grayscale-dark.css b/static/css/base16-grayscale-dark.css deleted file mode 100644 index dc0dd03a..00000000 --- a/static/css/base16-grayscale-dark.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #101010; } -.base01-background { background-color: #252525; } -.base02-background { background-color: #464646; } -.base03-background { background-color: #525252; } -.base04-background { background-color: #ababab; } -.base05-background { background-color: #b9b9b9; } -.base06-background { background-color: #e3e3e3; } -.base07-background { background-color: #f7f7f7; } -.base08-background { background-color: #7c7c7c; } -.base09-background { background-color: #999999; } -.base0A-background { background-color: #a0a0a0; } -.base0B-background { background-color: #8e8e8e; } -.base0C-background { background-color: #868686; } -.base0D-background { background-color: #686868; } -.base0E-background { background-color: #747474; } -.base0F-background { background-color: #5e5e5e; } - -.base00 { color: #101010; } -.base01 { color: #252525; } -.base02 { color: #464646; } -.base03 { color: #525252; } -.base04 { color: #ababab; } -.base05 { color: #b9b9b9; } -.base06 { color: #e3e3e3; } -.base07 { color: #f7f7f7; } -.base08 { color: #7c7c7c; } -.base09 { color: #999999; } -.base0A { color: #a0a0a0; } -.base0B { color: #8e8e8e; } -.base0C { color: #868686; } -.base0D { color: #686868; } -.base0E { color: #747474; } -.base0F { color: #5e5e5e; } diff --git a/static/css/base16-grayscale-light.css b/static/css/base16-grayscale-light.css deleted file mode 100644 index f9fd213a..00000000 --- a/static/css/base16-grayscale-light.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #f7f7f7; } -.base01-background { background-color: #e3e3e3; } -.base02-background { background-color: #b9b9b9; } -.base03-background { background-color: #ababab; } -.base04-background { background-color: #525252; } -.base05-background { background-color: #464646; } -.base06-background { background-color: #252525; } -.base07-background { background-color: #101010; } -.base08-background { background-color: #7c7c7c; } -.base09-background { background-color: #999999; } -.base0A-background { background-color: #a0a0a0; } -.base0B-background { background-color: #8e8e8e; } -.base0C-background { background-color: #868686; } -.base0D-background { background-color: #686868; } -.base0E-background { background-color: #747474; } -.base0F-background { background-color: #5e5e5e; } - -.base00 { color: #f7f7f7; } -.base01 { color: #e3e3e3; } -.base02 { color: #b9b9b9; } -.base03 { color: #ababab; } -.base04 { color: #525252; } -.base05 { color: #464646; } -.base06 { color: #252525; } -.base07 { color: #101010; } -.base08 { color: #7c7c7c; } -.base09 { color: #999999; } -.base0A { color: #a0a0a0; } -.base0B { color: #8e8e8e; } -.base0C { color: #868686; } -.base0D { color: #686868; } -.base0E { color: #747474; } -.base0F { color: #5e5e5e; } diff --git a/static/css/base16-green-screen.css b/static/css/base16-green-screen.css deleted file mode 100644 index 205efeae..00000000 --- a/static/css/base16-green-screen.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #001100; } -.base01-background { background-color: #003300; } -.base02-background { background-color: #005500; } -.base03-background { background-color: #007700; } -.base04-background { background-color: #009900; } -.base05-background { background-color: #00bb00; } -.base06-background { background-color: #00dd00; } -.base07-background { background-color: #00ff00; } -.base08-background { background-color: #007700; } -.base09-background { background-color: #009900; } -.base0A-background { background-color: #007700; } -.base0B-background { background-color: #00bb00; } -.base0C-background { background-color: #005500; } -.base0D-background { background-color: #009900; } -.base0E-background { background-color: #00bb00; } -.base0F-background { background-color: #005500; } - -.base00 { color: #001100; } -.base01 { color: #003300; } -.base02 { color: #005500; } -.base03 { color: #007700; } -.base04 { color: #009900; } -.base05 { color: #00bb00; } -.base06 { color: #00dd00; } -.base07 { color: #00ff00; } -.base08 { color: #007700; } -.base09 { color: #009900; } -.base0A { color: #007700; } -.base0B { color: #00bb00; } -.base0C { color: #005500; } -.base0D { color: #009900; } -.base0E { color: #00bb00; } -.base0F { color: #005500; } diff --git a/static/css/base16-harmonic16-dark.css b/static/css/base16-harmonic16-dark.css deleted file mode 100644 index 0c2c7ce4..00000000 --- a/static/css/base16-harmonic16-dark.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #0b1c2c; } -.base01-background { background-color: #223b54; } -.base02-background { background-color: #405c79; } -.base03-background { background-color: #627e99; } -.base04-background { background-color: #aabcce; } -.base05-background { background-color: #cbd6e2; } -.base06-background { background-color: #e5ebf1; } -.base07-background { background-color: #f7f9fb; } -.base08-background { background-color: #bf8b56; } -.base09-background { background-color: #bfbf56; } -.base0A-background { background-color: #8bbf56; } -.base0B-background { background-color: #56bf8b; } -.base0C-background { background-color: #568bbf; } -.base0D-background { background-color: #8b56bf; } -.base0E-background { background-color: #bf568b; } -.base0F-background { background-color: #bf5656; } - -.base00 { color: #0b1c2c; } -.base01 { color: #223b54; } -.base02 { color: #405c79; } -.base03 { color: #627e99; } -.base04 { color: #aabcce; } -.base05 { color: #cbd6e2; } -.base06 { color: #e5ebf1; } -.base07 { color: #f7f9fb; } -.base08 { color: #bf8b56; } -.base09 { color: #bfbf56; } -.base0A { color: #8bbf56; } -.base0B { color: #56bf8b; } -.base0C { color: #568bbf; } -.base0D { color: #8b56bf; } -.base0E { color: #bf568b; } -.base0F { color: #bf5656; } diff --git a/static/css/base16-harmonic16-light.css b/static/css/base16-harmonic16-light.css deleted file mode 100644 index 37bb7679..00000000 --- a/static/css/base16-harmonic16-light.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #f7f9fb; } -.base01-background { background-color: #e5ebf1; } -.base02-background { background-color: #cbd6e2; } -.base03-background { background-color: #aabcce; } -.base04-background { background-color: #627e99; } -.base05-background { background-color: #405c79; } -.base06-background { background-color: #223b54; } -.base07-background { background-color: #0b1c2c; } -.base08-background { background-color: #bf8b56; } -.base09-background { background-color: #bfbf56; } -.base0A-background { background-color: #8bbf56; } -.base0B-background { background-color: #56bf8b; } -.base0C-background { background-color: #568bbf; } -.base0D-background { background-color: #8b56bf; } -.base0E-background { background-color: #bf568b; } -.base0F-background { background-color: #bf5656; } - -.base00 { color: #f7f9fb; } -.base01 { color: #e5ebf1; } -.base02 { color: #cbd6e2; } -.base03 { color: #aabcce; } -.base04 { color: #627e99; } -.base05 { color: #405c79; } -.base06 { color: #223b54; } -.base07 { color: #0b1c2c; } -.base08 { color: #bf8b56; } -.base09 { color: #bfbf56; } -.base0A { color: #8bbf56; } -.base0B { color: #56bf8b; } -.base0C { color: #568bbf; } -.base0D { color: #8b56bf; } -.base0E { color: #bf568b; } -.base0F { color: #bf5656; } diff --git a/static/css/base16-hopscotch.css b/static/css/base16-hopscotch.css deleted file mode 100644 index f2ad232c..00000000 --- a/static/css/base16-hopscotch.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #322931; } -.base01-background { background-color: #433b42; } -.base02-background { background-color: #5c545b; } -.base03-background { background-color: #797379; } -.base04-background { background-color: #989498; } -.base05-background { background-color: #b9b5b8; } -.base06-background { background-color: #d5d3d5; } -.base07-background { background-color: #ffffff; } -.base08-background { background-color: #dd464c; } -.base09-background { background-color: #fd8b19; } -.base0A-background { background-color: #fdcc59; } -.base0B-background { background-color: #8fc13e; } -.base0C-background { background-color: #149b93; } -.base0D-background { background-color: #1290bf; } -.base0E-background { background-color: #c85e7c; } -.base0F-background { background-color: #b33508; } - -.base00 { color: #322931; } -.base01 { color: #433b42; } -.base02 { color: #5c545b; } -.base03 { color: #797379; } -.base04 { color: #989498; } -.base05 { color: #b9b5b8; } -.base06 { color: #d5d3d5; } -.base07 { color: #ffffff; } -.base08 { color: #dd464c; } -.base09 { color: #fd8b19; } -.base0A { color: #fdcc59; } -.base0B { color: #8fc13e; } -.base0C { color: #149b93; } -.base0D { color: #1290bf; } -.base0E { color: #c85e7c; } -.base0F { color: #b33508; } diff --git a/static/css/base16-ir-black.css b/static/css/base16-ir-black.css deleted file mode 100644 index 8d14ab9b..00000000 --- a/static/css/base16-ir-black.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #000000; } -.base01-background { background-color: #242422; } -.base02-background { background-color: #484844; } -.base03-background { background-color: #6c6c66; } -.base04-background { background-color: #918f88; } -.base05-background { background-color: #b5b3aa; } -.base06-background { background-color: #d9d7cc; } -.base07-background { background-color: #fdfbee; } -.base08-background { background-color: #ff6c60; } -.base09-background { background-color: #e9c062; } -.base0A-background { background-color: #ffffb6; } -.base0B-background { background-color: #a8ff60; } -.base0C-background { background-color: #c6c5fe; } -.base0D-background { background-color: #96cbfe; } -.base0E-background { background-color: #ff73fd; } -.base0F-background { background-color: #b18a3d; } - -.base00 { color: #000000; } -.base01 { color: #242422; } -.base02 { color: #484844; } -.base03 { color: #6c6c66; } -.base04 { color: #918f88; } -.base05 { color: #b5b3aa; } -.base06 { color: #d9d7cc; } -.base07 { color: #fdfbee; } -.base08 { color: #ff6c60; } -.base09 { color: #e9c062; } -.base0A { color: #ffffb6; } -.base0B { color: #a8ff60; } -.base0C { color: #c6c5fe; } -.base0D { color: #96cbfe; } -.base0E { color: #ff73fd; } -.base0F { color: #b18a3d; } diff --git a/static/css/base16-isotope.css b/static/css/base16-isotope.css deleted file mode 100644 index f7a4a0b4..00000000 --- a/static/css/base16-isotope.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #000000; } -.base01-background { background-color: #404040; } -.base02-background { background-color: #606060; } -.base03-background { background-color: #808080; } -.base04-background { background-color: #c0c0c0; } -.base05-background { background-color: #d0d0d0; } -.base06-background { background-color: #e0e0e0; } -.base07-background { background-color: #ffffff; } -.base08-background { background-color: #ff0000; } -.base09-background { background-color: #ff9900; } -.base0A-background { background-color: #ff0099; } -.base0B-background { background-color: #33ff00; } -.base0C-background { background-color: #00ffff; } -.base0D-background { background-color: #0066ff; } -.base0E-background { background-color: #cc00ff; } -.base0F-background { background-color: #3300ff; } - -.base00 { color: #000000; } -.base01 { color: #404040; } -.base02 { color: #606060; } -.base03 { color: #808080; } -.base04 { color: #c0c0c0; } -.base05 { color: #d0d0d0; } -.base06 { color: #e0e0e0; } -.base07 { color: #ffffff; } -.base08 { color: #ff0000; } -.base09 { color: #ff9900; } -.base0A { color: #ff0099; } -.base0B { color: #33ff00; } -.base0C { color: #00ffff; } -.base0D { color: #0066ff; } -.base0E { color: #cc00ff; } -.base0F { color: #3300ff; } diff --git a/static/css/base16-london-tube.css b/static/css/base16-london-tube.css deleted file mode 100644 index 0537d1ad..00000000 --- a/static/css/base16-london-tube.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #231f20; } -.base01-background { background-color: #1c3f95; } -.base02-background { background-color: #5a5758; } -.base03-background { background-color: #737171; } -.base04-background { background-color: #959ca1; } -.base05-background { background-color: #d9d8d8; } -.base06-background { background-color: #e7e7e8; } -.base07-background { background-color: #ffffff; } -.base08-background { background-color: #ee2e24; } -.base09-background { background-color: #f386a1; } -.base0A-background { background-color: #ffd204; } -.base0B-background { background-color: #00853e; } -.base0C-background { background-color: #85cebc; } -.base0D-background { background-color: #009ddc; } -.base0E-background { background-color: #98005d; } -.base0F-background { background-color: #b06110; } - -.base00 { color: #231f20; } -.base01 { color: #1c3f95; } -.base02 { color: #5a5758; } -.base03 { color: #737171; } -.base04 { color: #959ca1; } -.base05 { color: #d9d8d8; } -.base06 { color: #e7e7e8; } -.base07 { color: #ffffff; } -.base08 { color: #ee2e24; } -.base09 { color: #f386a1; } -.base0A { color: #ffd204; } -.base0B { color: #00853e; } -.base0C { color: #85cebc; } -.base0D { color: #009ddc; } -.base0E { color: #98005d; } -.base0F { color: #b06110; } diff --git a/static/css/base16-macintosh.css b/static/css/base16-macintosh.css deleted file mode 100644 index d5969fec..00000000 --- a/static/css/base16-macintosh.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #000000; } -.base01-background { background-color: #404040; } -.base02-background { background-color: #404040; } -.base03-background { background-color: #808080; } -.base04-background { background-color: #808080; } -.base05-background { background-color: #c0c0c0; } -.base06-background { background-color: #c0c0c0; } -.base07-background { background-color: #ffffff; } -.base08-background { background-color: #dd0907; } -.base09-background { background-color: #ff6403; } -.base0A-background { background-color: #fbf305; } -.base0B-background { background-color: #1fb714; } -.base0C-background { background-color: #02abea; } -.base0D-background { background-color: #0000d3; } -.base0E-background { background-color: #4700a5; } -.base0F-background { background-color: #90713a; } - -.base00 { color: #000000; } -.base01 { color: #404040; } -.base02 { color: #404040; } -.base03 { color: #808080; } -.base04 { color: #808080; } -.base05 { color: #c0c0c0; } -.base06 { color: #c0c0c0; } -.base07 { color: #ffffff; } -.base08 { color: #dd0907; } -.base09 { color: #ff6403; } -.base0A { color: #fbf305; } -.base0B { color: #1fb714; } -.base0C { color: #02abea; } -.base0D { color: #0000d3; } -.base0E { color: #4700a5; } -.base0F { color: #90713a; } diff --git a/static/css/base16-marrakesh.css b/static/css/base16-marrakesh.css deleted file mode 100644 index 91f0471f..00000000 --- a/static/css/base16-marrakesh.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #201602; } -.base01-background { background-color: #302e00; } -.base02-background { background-color: #5f5b17; } -.base03-background { background-color: #6c6823; } -.base04-background { background-color: #86813b; } -.base05-background { background-color: #948e48; } -.base06-background { background-color: #ccc37a; } -.base07-background { background-color: #faf0a5; } -.base08-background { background-color: #c35359; } -.base09-background { background-color: #b36144; } -.base0A-background { background-color: #a88339; } -.base0B-background { background-color: #18974e; } -.base0C-background { background-color: #75a738; } -.base0D-background { background-color: #477ca1; } -.base0E-background { background-color: #8868b3; } -.base0F-background { background-color: #b3588e; } - -.base00 { color: #201602; } -.base01 { color: #302e00; } -.base02 { color: #5f5b17; } -.base03 { color: #6c6823; } -.base04 { color: #86813b; } -.base05 { color: #948e48; } -.base06 { color: #ccc37a; } -.base07 { color: #faf0a5; } -.base08 { color: #c35359; } -.base09 { color: #b36144; } -.base0A { color: #a88339; } -.base0B { color: #18974e; } -.base0C { color: #75a738; } -.base0D { color: #477ca1; } -.base0E { color: #8868b3; } -.base0F { color: #b3588e; } diff --git a/static/css/base16-materia.css b/static/css/base16-materia.css deleted file mode 100644 index 41d935dd..00000000 --- a/static/css/base16-materia.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #263238; } -.base01-background { background-color: #2C393F; } -.base02-background { background-color: #37474F; } -.base03-background { background-color: #707880; } -.base04-background { background-color: #C9CCD3; } -.base05-background { background-color: #CDD3DE; } -.base06-background { background-color: #D5DBE5; } -.base07-background { background-color: #FFFFFF; } -.base08-background { background-color: #EC5F67; } -.base09-background { background-color: #EA9560; } -.base0A-background { background-color: #FFCC00; } -.base0B-background { background-color: #8BD649; } -.base0C-background { background-color: #80CBC4; } -.base0D-background { background-color: #89DDFF; } -.base0E-background { background-color: #82AAFF; } -.base0F-background { background-color: #EC5F67; } - -.base00 { color: #263238; } -.base01 { color: #2C393F; } -.base02 { color: #37474F; } -.base03 { color: #707880; } -.base04 { color: #C9CCD3; } -.base05 { color: #CDD3DE; } -.base06 { color: #D5DBE5; } -.base07 { color: #FFFFFF; } -.base08 { color: #EC5F67; } -.base09 { color: #EA9560; } -.base0A { color: #FFCC00; } -.base0B { color: #8BD649; } -.base0C { color: #80CBC4; } -.base0D { color: #89DDFF; } -.base0E { color: #82AAFF; } -.base0F { color: #EC5F67; } diff --git a/static/css/base16-mexico-light.css b/static/css/base16-mexico-light.css deleted file mode 100644 index 1916c67b..00000000 --- a/static/css/base16-mexico-light.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #f8f8f8; } -.base01-background { background-color: #e8e8e8; } -.base02-background { background-color: #d8d8d8; } -.base03-background { background-color: #b8b8b8; } -.base04-background { background-color: #585858; } -.base05-background { background-color: #383838; } -.base06-background { background-color: #282828; } -.base07-background { background-color: #181818; } -.base08-background { background-color: #ab4642; } -.base09-background { background-color: #dc9656; } -.base0A-background { background-color: #f79a0e; } -.base0B-background { background-color: #538947; } -.base0C-background { background-color: #4b8093; } -.base0D-background { background-color: #7cafc2; } -.base0E-background { background-color: #96609e; } -.base0F-background { background-color: #a16946; } - -.base00 { color: #f8f8f8; } -.base01 { color: #e8e8e8; } -.base02 { color: #d8d8d8; } -.base03 { color: #b8b8b8; } -.base04 { color: #585858; } -.base05 { color: #383838; } -.base06 { color: #282828; } -.base07 { color: #181818; } -.base08 { color: #ab4642; } -.base09 { color: #dc9656; } -.base0A { color: #f79a0e; } -.base0B { color: #538947; } -.base0C { color: #4b8093; } -.base0D { color: #7cafc2; } -.base0E { color: #96609e; } -.base0F { color: #a16946; } diff --git a/static/css/base16-mocha.css b/static/css/base16-mocha.css deleted file mode 100644 index 6cb2fb58..00000000 --- a/static/css/base16-mocha.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #3B3228; } -.base01-background { background-color: #534636; } -.base02-background { background-color: #645240; } -.base03-background { background-color: #7e705a; } -.base04-background { background-color: #b8afad; } -.base05-background { background-color: #d0c8c6; } -.base06-background { background-color: #e9e1dd; } -.base07-background { background-color: #f5eeeb; } -.base08-background { background-color: #cb6077; } -.base09-background { background-color: #d28b71; } -.base0A-background { background-color: #f4bc87; } -.base0B-background { background-color: #beb55b; } -.base0C-background { background-color: #7bbda4; } -.base0D-background { background-color: #8ab3b5; } -.base0E-background { background-color: #a89bb9; } -.base0F-background { background-color: #bb9584; } - -.base00 { color: #3B3228; } -.base01 { color: #534636; } -.base02 { color: #645240; } -.base03 { color: #7e705a; } -.base04 { color: #b8afad; } -.base05 { color: #d0c8c6; } -.base06 { color: #e9e1dd; } -.base07 { color: #f5eeeb; } -.base08 { color: #cb6077; } -.base09 { color: #d28b71; } -.base0A { color: #f4bc87; } -.base0B { color: #beb55b; } -.base0C { color: #7bbda4; } -.base0D { color: #8ab3b5; } -.base0E { color: #a89bb9; } -.base0F { color: #bb9584; } diff --git a/static/css/base16-monokai.css b/static/css/base16-monokai.css deleted file mode 100644 index fc7ccf47..00000000 --- a/static/css/base16-monokai.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #272822; } -.base01-background { background-color: #383830; } -.base02-background { background-color: #49483e; } -.base03-background { background-color: #75715e; } -.base04-background { background-color: #a59f85; } -.base05-background { background-color: #f8f8f2; } -.base06-background { background-color: #f5f4f1; } -.base07-background { background-color: #f9f8f5; } -.base08-background { background-color: #f92672; } -.base09-background { background-color: #fd971f; } -.base0A-background { background-color: #f4bf75; } -.base0B-background { background-color: #a6e22e; } -.base0C-background { background-color: #a1efe4; } -.base0D-background { background-color: #66d9ef; } -.base0E-background { background-color: #ae81ff; } -.base0F-background { background-color: #cc6633; } - -.base00 { color: #272822; } -.base01 { color: #383830; } -.base02 { color: #49483e; } -.base03 { color: #75715e; } -.base04 { color: #a59f85; } -.base05 { color: #f8f8f2; } -.base06 { color: #f5f4f1; } -.base07 { color: #f9f8f5; } -.base08 { color: #f92672; } -.base09 { color: #fd971f; } -.base0A { color: #f4bf75; } -.base0B { color: #a6e22e; } -.base0C { color: #a1efe4; } -.base0D { color: #66d9ef; } -.base0E { color: #ae81ff; } -.base0F { color: #cc6633; } diff --git a/static/css/base16-ocean.css b/static/css/base16-ocean.css deleted file mode 100644 index 8622d17e..00000000 --- a/static/css/base16-ocean.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #2b303b; } -.base01-background { background-color: #343d46; } -.base02-background { background-color: #4f5b66; } -.base03-background { background-color: #65737e; } -.base04-background { background-color: #a7adba; } -.base05-background { background-color: #c0c5ce; } -.base06-background { background-color: #dfe1e8; } -.base07-background { background-color: #eff1f5; } -.base08-background { background-color: #bf616a; } -.base09-background { background-color: #d08770; } -.base0A-background { background-color: #ebcb8b; } -.base0B-background { background-color: #a3be8c; } -.base0C-background { background-color: #96b5b4; } -.base0D-background { background-color: #8fa1b3; } -.base0E-background { background-color: #b48ead; } -.base0F-background { background-color: #ab7967; } - -.base00 { color: #2b303b; } -.base01 { color: #343d46; } -.base02 { color: #4f5b66; } -.base03 { color: #65737e; } -.base04 { color: #a7adba; } -.base05 { color: #c0c5ce; } -.base06 { color: #dfe1e8; } -.base07 { color: #eff1f5; } -.base08 { color: #bf616a; } -.base09 { color: #d08770; } -.base0A { color: #ebcb8b; } -.base0B { color: #a3be8c; } -.base0C { color: #96b5b4; } -.base0D { color: #8fa1b3; } -.base0E { color: #b48ead; } -.base0F { color: #ab7967; } diff --git a/static/css/base16-oceanicnext.css b/static/css/base16-oceanicnext.css deleted file mode 100644 index df4d9ef5..00000000 --- a/static/css/base16-oceanicnext.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #1B2B34; } -.base01-background { background-color: #343D46; } -.base02-background { background-color: #4F5B66; } -.base03-background { background-color: #65737E; } -.base04-background { background-color: #A7ADBA; } -.base05-background { background-color: #C0C5CE; } -.base06-background { background-color: #CDD3DE; } -.base07-background { background-color: #D8DEE9; } -.base08-background { background-color: #EC5f67; } -.base09-background { background-color: #F99157; } -.base0A-background { background-color: #FAC863; } -.base0B-background { background-color: #99C794; } -.base0C-background { background-color: #5FB3B3; } -.base0D-background { background-color: #6699CC; } -.base0E-background { background-color: #C594C5; } -.base0F-background { background-color: #AB7967; } - -.base00 { color: #1B2B34; } -.base01 { color: #343D46; } -.base02 { color: #4F5B66; } -.base03 { color: #65737E; } -.base04 { color: #A7ADBA; } -.base05 { color: #C0C5CE; } -.base06 { color: #CDD3DE; } -.base07 { color: #D8DEE9; } -.base08 { color: #EC5f67; } -.base09 { color: #F99157; } -.base0A { color: #FAC863; } -.base0B { color: #99C794; } -.base0C { color: #5FB3B3; } -.base0D { color: #6699CC; } -.base0E { color: #C594C5; } -.base0F { color: #AB7967; } diff --git a/static/css/base16-paraiso.css b/static/css/base16-paraiso.css deleted file mode 100644 index b68c9407..00000000 --- a/static/css/base16-paraiso.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #2f1e2e; } -.base01-background { background-color: #41323f; } -.base02-background { background-color: #4f424c; } -.base03-background { background-color: #776e71; } -.base04-background { background-color: #8d8687; } -.base05-background { background-color: #a39e9b; } -.base06-background { background-color: #b9b6b0; } -.base07-background { background-color: #e7e9db; } -.base08-background { background-color: #ef6155; } -.base09-background { background-color: #f99b15; } -.base0A-background { background-color: #fec418; } -.base0B-background { background-color: #48b685; } -.base0C-background { background-color: #5bc4bf; } -.base0D-background { background-color: #06b6ef; } -.base0E-background { background-color: #815ba4; } -.base0F-background { background-color: #e96ba8; } - -.base00 { color: #2f1e2e; } -.base01 { color: #41323f; } -.base02 { color: #4f424c; } -.base03 { color: #776e71; } -.base04 { color: #8d8687; } -.base05 { color: #a39e9b; } -.base06 { color: #b9b6b0; } -.base07 { color: #e7e9db; } -.base08 { color: #ef6155; } -.base09 { color: #f99b15; } -.base0A { color: #fec418; } -.base0B { color: #48b685; } -.base0C { color: #5bc4bf; } -.base0D { color: #06b6ef; } -.base0E { color: #815ba4; } -.base0F { color: #e96ba8; } diff --git a/static/css/base16-phd.css b/static/css/base16-phd.css deleted file mode 100644 index 54276ab1..00000000 --- a/static/css/base16-phd.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #061229; } -.base01-background { background-color: #2a3448; } -.base02-background { background-color: #4d5666; } -.base03-background { background-color: #717885; } -.base04-background { background-color: #9a99a3; } -.base05-background { background-color: #b8bbc2; } -.base06-background { background-color: #dbdde0; } -.base07-background { background-color: #ffffff; } -.base08-background { background-color: #d07346; } -.base09-background { background-color: #f0a000; } -.base0A-background { background-color: #fbd461; } -.base0B-background { background-color: #99bf52; } -.base0C-background { background-color: #72b9bf; } -.base0D-background { background-color: #5299bf; } -.base0E-background { background-color: #9989cc; } -.base0F-background { background-color: #b08060; } - -.base00 { color: #061229; } -.base01 { color: #2a3448; } -.base02 { color: #4d5666; } -.base03 { color: #717885; } -.base04 { color: #9a99a3; } -.base05 { color: #b8bbc2; } -.base06 { color: #dbdde0; } -.base07 { color: #ffffff; } -.base08 { color: #d07346; } -.base09 { color: #f0a000; } -.base0A { color: #fbd461; } -.base0B { color: #99bf52; } -.base0C { color: #72b9bf; } -.base0D { color: #5299bf; } -.base0E { color: #9989cc; } -.base0F { color: #b08060; } diff --git a/static/css/base16-pico.css b/static/css/base16-pico.css deleted file mode 100644 index 86482b72..00000000 --- a/static/css/base16-pico.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #000000; } -.base01-background { background-color: #1d2b53; } -.base02-background { background-color: #7e2553; } -.base03-background { background-color: #008751; } -.base04-background { background-color: #ab5236; } -.base05-background { background-color: #5f574f; } -.base06-background { background-color: #c2c3c7; } -.base07-background { background-color: #fff1e8; } -.base08-background { background-color: #ff004d; } -.base09-background { background-color: #ffa300; } -.base0A-background { background-color: #fff024; } -.base0B-background { background-color: #00e756; } -.base0C-background { background-color: #29adff; } -.base0D-background { background-color: #83769c; } -.base0E-background { background-color: #ff77a8; } -.base0F-background { background-color: #ffccaa; } - -.base00 { color: #000000; } -.base01 { color: #1d2b53; } -.base02 { color: #7e2553; } -.base03 { color: #008751; } -.base04 { color: #ab5236; } -.base05 { color: #5f574f; } -.base06 { color: #c2c3c7; } -.base07 { color: #fff1e8; } -.base08 { color: #ff004d; } -.base09 { color: #ffa300; } -.base0A { color: #fff024; } -.base0B { color: #00e756; } -.base0C { color: #29adff; } -.base0D { color: #83769c; } -.base0E { color: #ff77a8; } -.base0F { color: #ffccaa; } diff --git a/static/css/base16-pleroma-dark.css b/static/css/base16-pleroma-dark.css deleted file mode 100644 index e1d46f92..00000000 --- a/static/css/base16-pleroma-dark.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #161c20; } -.base01-background { background-color: #282e32; } -.base02-background { background-color: #343a3f; } -.base03-background { background-color: #4e5256; } -.base04-background { background-color: #ababab; } -.base05-background { background-color: #b9b9b9; } -.base06-background { background-color: #d0d0d0; } -.base07-background { background-color: #e7e7e7; } -.base08-background { background-color: #baaa9c; } -.base09-background { background-color: #999999; } -.base0A-background { background-color: #a0a0a0; } -.base0B-background { background-color: #8e8e8e; } -.base0C-background { background-color: #868686; } -.base0D-background { background-color: #686868; } -.base0E-background { background-color: #747474; } -.base0F-background { background-color: #5e5e5e; } - -.base00 { color: #161c20; } -.base01 { color: #282e32; } -.base02 { color: #343a3f; } -.base03 { color: #4e5256; } -.base04 { color: #ababab; } -.base05 { color: #b9b9b9; } -.base06 { color: #d0d0d0; } -.base07 { color: #e7e7e7; } -.base08 { color: #baaa9c; } -.base09 { color: #999999; } -.base0A { color: #a0a0a0; } -.base0B { color: #8e8e8e; } -.base0C { color: #868686; } -.base0D { color: #686868; } -.base0E { color: #747474; } -.base0F { color: #5e5e5e; } diff --git a/static/css/base16-pleroma-light.css b/static/css/base16-pleroma-light.css deleted file mode 100644 index 1a85689a..00000000 --- a/static/css/base16-pleroma-light.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #f2f4f6; } -.base01-background { background-color: #dde2e6; } -.base02-background { background-color: #c0c6cb; } -.base03-background { background-color: #a4a4a4; } -.base04-background { background-color: #545454; } -.base05-background { background-color: #304055; } -.base06-background { background-color: #040404; } -.base07-background { background-color: #000000; } -.base08-background { background-color: #e92f2f; } -.base09-background { background-color: #e09448; } -.base0A-background { background-color: #dddd13; } -.base0B-background { background-color: #0ed839; } -.base0C-background { background-color: #23edda; } -.base0D-background { background-color: #3b48e3; } -.base0E-background { background-color: #f996e2; } -.base0F-background { background-color: #69542d; } - -.base00 { color: #f2f4f6; } -.base01 { color: #dde2e6; } -.base02 { color: #c0c6cb; } -.base03 { color: #a4a4a4; } -.base04 { color: #545454; } -.base05 { color: #304055; } -.base06 { color: #040404; } -.base07 { color: #000000; } -.base08 { color: #e46f0f; } -.base09 { color: #e09448; } -.base0A { color: #dddd13; } -.base0B { color: #0ed839; } -.base0C { color: #23edda; } -.base0D { color: #3b48e3; } -.base0E { color: #f996e2; } -.base0F { color: #69542d; } diff --git a/static/css/base16-pop.css b/static/css/base16-pop.css deleted file mode 100644 index 14acac17..00000000 --- a/static/css/base16-pop.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #000000; } -.base01-background { background-color: #202020; } -.base02-background { background-color: #303030; } -.base03-background { background-color: #505050; } -.base04-background { background-color: #b0b0b0; } -.base05-background { background-color: #d0d0d0; } -.base06-background { background-color: #e0e0e0; } -.base07-background { background-color: #ffffff; } -.base08-background { background-color: #eb008a; } -.base09-background { background-color: #f29333; } -.base0A-background { background-color: #f8ca12; } -.base0B-background { background-color: #37b349; } -.base0C-background { background-color: #00aabb; } -.base0D-background { background-color: #0e5a94; } -.base0E-background { background-color: #b31e8d; } -.base0F-background { background-color: #7a2d00; } - -.base00 { color: #000000; } -.base01 { color: #202020; } -.base02 { color: #303030; } -.base03 { color: #505050; } -.base04 { color: #b0b0b0; } -.base05 { color: #d0d0d0; } -.base06 { color: #e0e0e0; } -.base07 { color: #ffffff; } -.base08 { color: #eb008a; } -.base09 { color: #f29333; } -.base0A { color: #f8ca12; } -.base0B { color: #37b349; } -.base0C { color: #00aabb; } -.base0D { color: #0e5a94; } -.base0E { color: #b31e8d; } -.base0F { color: #7a2d00; } diff --git a/static/css/base16-railscasts.css b/static/css/base16-railscasts.css deleted file mode 100644 index 18f43bfd..00000000 --- a/static/css/base16-railscasts.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #2b2b2b; } -.base01-background { background-color: #272935; } -.base02-background { background-color: #3a4055; } -.base03-background { background-color: #5a647e; } -.base04-background { background-color: #d4cfc9; } -.base05-background { background-color: #e6e1dc; } -.base06-background { background-color: #f4f1ed; } -.base07-background { background-color: #f9f7f3; } -.base08-background { background-color: #da4939; } -.base09-background { background-color: #cc7833; } -.base0A-background { background-color: #ffc66d; } -.base0B-background { background-color: #a5c261; } -.base0C-background { background-color: #519f50; } -.base0D-background { background-color: #6d9cbe; } -.base0E-background { background-color: #b6b3eb; } -.base0F-background { background-color: #bc9458; } - -.base00 { color: #2b2b2b; } -.base01 { color: #272935; } -.base02 { color: #3a4055; } -.base03 { color: #5a647e; } -.base04 { color: #d4cfc9; } -.base05 { color: #e6e1dc; } -.base06 { color: #f4f1ed; } -.base07 { color: #f9f7f3; } -.base08 { color: #da4939; } -.base09 { color: #cc7833; } -.base0A { color: #ffc66d; } -.base0B { color: #a5c261; } -.base0C { color: #519f50; } -.base0D { color: #6d9cbe; } -.base0E { color: #b6b3eb; } -.base0F { color: #bc9458; } diff --git a/static/css/base16-seti-ui.css b/static/css/base16-seti-ui.css deleted file mode 100644 index bd4f9cc4..00000000 --- a/static/css/base16-seti-ui.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #151718; } -.base01-background { background-color: #8ec43d; } -.base02-background { background-color: #3B758C; } -.base03-background { background-color: #41535B; } -.base04-background { background-color: #43a5d5; } -.base05-background { background-color: #d6d6d6; } -.base06-background { background-color: #eeeeee; } -.base07-background { background-color: #ffffff; } -.base08-background { background-color: #Cd3f45; } -.base09-background { background-color: #db7b55; } -.base0A-background { background-color: #e6cd69; } -.base0B-background { background-color: #9fca56; } -.base0C-background { background-color: #55dbbe; } -.base0D-background { background-color: #55b5db; } -.base0E-background { background-color: #a074c4; } -.base0F-background { background-color: #8a553f; } - -.base00 { color: #151718; } -.base01 { color: #8ec43d; } -.base02 { color: #3B758C; } -.base03 { color: #41535B; } -.base04 { color: #43a5d5; } -.base05 { color: #d6d6d6; } -.base06 { color: #eeeeee; } -.base07 { color: #ffffff; } -.base08 { color: #Cd3f45; } -.base09 { color: #db7b55; } -.base0A { color: #e6cd69; } -.base0B { color: #9fca56; } -.base0C { color: #55dbbe; } -.base0D { color: #55b5db; } -.base0E { color: #a074c4; } -.base0F { color: #8a553f; } diff --git a/static/css/base16-shapeshifter.css b/static/css/base16-shapeshifter.css deleted file mode 100644 index ded18069..00000000 --- a/static/css/base16-shapeshifter.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #f9f9f9; } -.base01-background { background-color: #e0e0e0; } -.base02-background { background-color: #ababab; } -.base03-background { background-color: #555555; } -.base04-background { background-color: #343434; } -.base05-background { background-color: #102015; } -.base06-background { background-color: #040404; } -.base07-background { background-color: #000000; } -.base08-background { background-color: #e92f2f; } -.base09-background { background-color: #e09448; } -.base0A-background { background-color: #dddd13; } -.base0B-background { background-color: #0ed839; } -.base0C-background { background-color: #23edda; } -.base0D-background { background-color: #3b48e3; } -.base0E-background { background-color: #f996e2; } -.base0F-background { background-color: #69542d; } - -.base00 { color: #f9f9f9; } -.base01 { color: #e0e0e0; } -.base02 { color: #ababab; } -.base03 { color: #555555; } -.base04 { color: #343434; } -.base05 { color: #102015; } -.base06 { color: #040404; } -.base07 { color: #000000; } -.base08 { color: #e92f2f; } -.base09 { color: #e09448; } -.base0A { color: #dddd13; } -.base0B { color: #0ed839; } -.base0C { color: #23edda; } -.base0D { color: #3b48e3; } -.base0E { color: #f996e2; } -.base0F { color: #69542d; } diff --git a/static/css/base16-solar-flare.css b/static/css/base16-solar-flare.css deleted file mode 100644 index 7d1d3862..00000000 --- a/static/css/base16-solar-flare.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #18262F; } -.base01-background { background-color: #222E38; } -.base02-background { background-color: #586875; } -.base03-background { background-color: #667581; } -.base04-background { background-color: #85939E; } -.base05-background { background-color: #A6AFB8; } -.base06-background { background-color: #E8E9ED; } -.base07-background { background-color: #F5F7FA; } -.base08-background { background-color: #EF5253; } -.base09-background { background-color: #E66B2B; } -.base0A-background { background-color: #E4B51C; } -.base0B-background { background-color: #7CC844; } -.base0C-background { background-color: #52CBB0; } -.base0D-background { background-color: #33B5E1; } -.base0E-background { background-color: #A363D5; } -.base0F-background { background-color: #D73C9A; } - -.base00 { color: #18262F; } -.base01 { color: #222E38; } -.base02 { color: #586875; } -.base03 { color: #667581; } -.base04 { color: #85939E; } -.base05 { color: #A6AFB8; } -.base06 { color: #E8E9ED; } -.base07 { color: #F5F7FA; } -.base08 { color: #EF5253; } -.base09 { color: #E66B2B; } -.base0A { color: #E4B51C; } -.base0B { color: #7CC844; } -.base0C { color: #52CBB0; } -.base0D { color: #33B5E1; } -.base0E { color: #A363D5; } -.base0F { color: #D73C9A; } diff --git a/static/css/base16-solarized-dark.css b/static/css/base16-solarized-dark.css deleted file mode 100644 index ac16f12c..00000000 --- a/static/css/base16-solarized-dark.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #002b36; } -.base01-background { background-color: #073642; } -.base02-background { background-color: #586e75; } -.base03-background { background-color: #657b83; } -.base04-background { background-color: #839496; } -.base05-background { background-color: #93a1a1; } -.base06-background { background-color: #eee8d5; } -.base07-background { background-color: #fdf6e3; } -.base08-background { background-color: #dc322f; } -.base09-background { background-color: #cb4b16; } -.base0A-background { background-color: #b58900; } -.base0B-background { background-color: #859900; } -.base0C-background { background-color: #2aa198; } -.base0D-background { background-color: #268bd2; } -.base0E-background { background-color: #6c71c4; } -.base0F-background { background-color: #d33682; } - -.base00 { color: #002b36; } -.base01 { color: #073642; } -.base02 { color: #586e75; } -.base03 { color: #657b83; } -.base04 { color: #839496; } -.base05 { color: #93a1a1; } -.base06 { color: #eee8d5; } -.base07 { color: #fdf6e3; } -.base08 { color: #dc322f; } -.base09 { color: #cb4b16; } -.base0A { color: #b58900; } -.base0B { color: #859900; } -.base0C { color: #2aa198; } -.base0D { color: #268bd2; } -.base0E { color: #6c71c4; } -.base0F { color: #d33682; } diff --git a/static/css/base16-solarized-light.css b/static/css/base16-solarized-light.css deleted file mode 100644 index 7164cb04..00000000 --- a/static/css/base16-solarized-light.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #fdf6e3; } -.base01-background { background-color: #eee8d5; } -.base02-background { background-color: #93a1a1; } -.base03-background { background-color: #839496; } -.base04-background { background-color: #657b83; } -.base05-background { background-color: #586e75; } -.base06-background { background-color: #073642; } -.base07-background { background-color: #002b36; } -.base08-background { background-color: #dc322f; } -.base09-background { background-color: #cb4b16; } -.base0A-background { background-color: #b58900; } -.base0B-background { background-color: #859900; } -.base0C-background { background-color: #2aa198; } -.base0D-background { background-color: #268bd2; } -.base0E-background { background-color: #6c71c4; } -.base0F-background { background-color: #d33682; } - -.base00 { color: #fdf6e3; } -.base01 { color: #eee8d5; } -.base02 { color: #93a1a1; } -.base03 { color: #839496; } -.base04 { color: #657b83; } -.base05 { color: #586e75; } -.base06 { color: #073642; } -.base07 { color: #002b36; } -.base08 { color: #dc322f; } -.base09 { color: #cb4b16; } -.base0A { color: #b58900; } -.base0B { color: #859900; } -.base0C { color: #2aa198; } -.base0D { color: #268bd2; } -.base0E { color: #6c71c4; } -.base0F { color: #d33682; } diff --git a/static/css/base16-spacemacs.css b/static/css/base16-spacemacs.css deleted file mode 100644 index 48737650..00000000 --- a/static/css/base16-spacemacs.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #1f2022; } -.base01-background { background-color: #282828; } -.base02-background { background-color: #444155; } -.base03-background { background-color: #585858; } -.base04-background { background-color: #b8b8b8; } -.base05-background { background-color: #a3a3a3; } -.base06-background { background-color: #e8e8e8; } -.base07-background { background-color: #f8f8f8; } -.base08-background { background-color: #f2241f; } -.base09-background { background-color: #ffa500; } -.base0A-background { background-color: #b1951d; } -.base0B-background { background-color: #67b11d; } -.base0C-background { background-color: #2d9574; } -.base0D-background { background-color: #4f97d7; } -.base0E-background { background-color: #a31db1; } -.base0F-background { background-color: #b03060; } - -.base00 { color: #1f2022; } -.base01 { color: #282828; } -.base02 { color: #444155; } -.base03 { color: #585858; } -.base04 { color: #b8b8b8; } -.base05 { color: #a3a3a3; } -.base06 { color: #e8e8e8; } -.base07 { color: #f8f8f8; } -.base08 { color: #f2241f; } -.base09 { color: #ffa500; } -.base0A { color: #b1951d; } -.base0B { color: #67b11d; } -.base0C { color: #2d9574; } -.base0D { color: #4f97d7; } -.base0E { color: #a31db1; } -.base0F { color: #b03060; } diff --git a/static/css/base16-summerfruit-dark.css b/static/css/base16-summerfruit-dark.css deleted file mode 100644 index 1c8f2332..00000000 --- a/static/css/base16-summerfruit-dark.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #151515; } -.base01-background { background-color: #202020; } -.base02-background { background-color: #303030; } -.base03-background { background-color: #505050; } -.base04-background { background-color: #B0B0B0; } -.base05-background { background-color: #D0D0D0; } -.base06-background { background-color: #E0E0E0; } -.base07-background { background-color: #FFFFFF; } -.base08-background { background-color: #FF0086; } -.base09-background { background-color: #FD8900; } -.base0A-background { background-color: #ABA800; } -.base0B-background { background-color: #00C918; } -.base0C-background { background-color: #1FAAAA; } -.base0D-background { background-color: #3777E6; } -.base0E-background { background-color: #AD00A1; } -.base0F-background { background-color: #CC6633; } - -.base00 { color: #151515; } -.base01 { color: #202020; } -.base02 { color: #303030; } -.base03 { color: #505050; } -.base04 { color: #B0B0B0; } -.base05 { color: #D0D0D0; } -.base06 { color: #E0E0E0; } -.base07 { color: #FFFFFF; } -.base08 { color: #FF0086; } -.base09 { color: #FD8900; } -.base0A { color: #ABA800; } -.base0B { color: #00C918; } -.base0C { color: #1FAAAA; } -.base0D { color: #3777E6; } -.base0E { color: #AD00A1; } -.base0F { color: #CC6633; } diff --git a/static/css/base16-summerfruit-light.css b/static/css/base16-summerfruit-light.css deleted file mode 100644 index cb54d4c5..00000000 --- a/static/css/base16-summerfruit-light.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #FFFFFF; } -.base01-background { background-color: #E0E0E0; } -.base02-background { background-color: #D0D0D0; } -.base03-background { background-color: #B0B0B0; } -.base04-background { background-color: #000000; } -.base05-background { background-color: #101010; } -.base06-background { background-color: #151515; } -.base07-background { background-color: #202020; } -.base08-background { background-color: #FF0086; } -.base09-background { background-color: #FD8900; } -.base0A-background { background-color: #ABA800; } -.base0B-background { background-color: #00C918; } -.base0C-background { background-color: #1FAAAA; } -.base0D-background { background-color: #3777E6; } -.base0E-background { background-color: #AD00A1; } -.base0F-background { background-color: #CC6633; } - -.base00 { color: #FFFFFF; } -.base01 { color: #E0E0E0; } -.base02 { color: #D0D0D0; } -.base03 { color: #B0B0B0; } -.base04 { color: #000000; } -.base05 { color: #101010; } -.base06 { color: #151515; } -.base07 { color: #202020; } -.base08 { color: #FF0086; } -.base09 { color: #FD8900; } -.base0A { color: #ABA800; } -.base0B { color: #00C918; } -.base0C { color: #1FAAAA; } -.base0D { color: #3777E6; } -.base0E { color: #AD00A1; } -.base0F { color: #CC6633; } diff --git a/static/css/base16-tomorrow-night.css b/static/css/base16-tomorrow-night.css deleted file mode 100644 index 09ecf08e..00000000 --- a/static/css/base16-tomorrow-night.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #1d1f21; } -.base01-background { background-color: #282a2e; } -.base02-background { background-color: #373b41; } -.base03-background { background-color: #969896; } -.base04-background { background-color: #b4b7b4; } -.base05-background { background-color: #c5c8c6; } -.base06-background { background-color: #e0e0e0; } -.base07-background { background-color: #ffffff; } -.base08-background { background-color: #cc6666; } -.base09-background { background-color: #de935f; } -.base0A-background { background-color: #f0c674; } -.base0B-background { background-color: #b5bd68; } -.base0C-background { background-color: #8abeb7; } -.base0D-background { background-color: #81a2be; } -.base0E-background { background-color: #b294bb; } -.base0F-background { background-color: #a3685a; } - -.base00 { color: #1d1f21; } -.base01 { color: #282a2e; } -.base02 { color: #373b41; } -.base03 { color: #969896; } -.base04 { color: #b4b7b4; } -.base05 { color: #c5c8c6; } -.base06 { color: #e0e0e0; } -.base07 { color: #ffffff; } -.base08 { color: #cc6666; } -.base09 { color: #de935f; } -.base0A { color: #f0c674; } -.base0B { color: #b5bd68; } -.base0C { color: #8abeb7; } -.base0D { color: #81a2be; } -.base0E { color: #b294bb; } -.base0F { color: #a3685a; } diff --git a/static/css/base16-tomorrow.css b/static/css/base16-tomorrow.css deleted file mode 100644 index f1486823..00000000 --- a/static/css/base16-tomorrow.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #ffffff; } -.base01-background { background-color: #e0e0e0; } -.base02-background { background-color: #d6d6d6; } -.base03-background { background-color: #8e908c; } -.base04-background { background-color: #969896; } -.base05-background { background-color: #4d4d4c; } -.base06-background { background-color: #282a2e; } -.base07-background { background-color: #1d1f21; } -.base08-background { background-color: #c82829; } -.base09-background { background-color: #f5871f; } -.base0A-background { background-color: #eab700; } -.base0B-background { background-color: #718c00; } -.base0C-background { background-color: #3e999f; } -.base0D-background { background-color: #4271ae; } -.base0E-background { background-color: #8959a8; } -.base0F-background { background-color: #a3685a; } - -.base00 { color: #ffffff; } -.base01 { color: #e0e0e0; } -.base02 { color: #d6d6d6; } -.base03 { color: #8e908c; } -.base04 { color: #969896; } -.base05 { color: #4d4d4c; } -.base06 { color: #282a2e; } -.base07 { color: #1d1f21; } -.base08 { color: #c82829; } -.base09 { color: #f5871f; } -.base0A { color: #eab700; } -.base0B { color: #718c00; } -.base0C { color: #3e999f; } -.base0D { color: #4271ae; } -.base0E { color: #8959a8; } -.base0F { color: #a3685a; } diff --git a/static/css/base16-twilight.css b/static/css/base16-twilight.css deleted file mode 100644 index c8dfda3f..00000000 --- a/static/css/base16-twilight.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #1e1e1e; } -.base01-background { background-color: #323537; } -.base02-background { background-color: #464b50; } -.base03-background { background-color: #5f5a60; } -.base04-background { background-color: #838184; } -.base05-background { background-color: #a7a7a7; } -.base06-background { background-color: #c3c3c3; } -.base07-background { background-color: #ffffff; } -.base08-background { background-color: #cf6a4c; } -.base09-background { background-color: #cda869; } -.base0A-background { background-color: #f9ee98; } -.base0B-background { background-color: #8f9d6a; } -.base0C-background { background-color: #afc4db; } -.base0D-background { background-color: #7587a6; } -.base0E-background { background-color: #9b859d; } -.base0F-background { background-color: #9b703f; } - -.base00 { color: #1e1e1e; } -.base01 { color: #323537; } -.base02 { color: #464b50; } -.base03 { color: #5f5a60; } -.base04 { color: #838184; } -.base05 { color: #a7a7a7; } -.base06 { color: #c3c3c3; } -.base07 { color: #ffffff; } -.base08 { color: #cf6a4c; } -.base09 { color: #cda869; } -.base0A { color: #f9ee98; } -.base0B { color: #8f9d6a; } -.base0C { color: #afc4db; } -.base0D { color: #7587a6; } -.base0E { color: #9b859d; } -.base0F { color: #9b703f; } diff --git a/static/css/base16-unikitty-dark.css b/static/css/base16-unikitty-dark.css deleted file mode 100644 index e6ef32e3..00000000 --- a/static/css/base16-unikitty-dark.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #2e2a31; } -.base01-background { background-color: #4a464d; } -.base02-background { background-color: #666369; } -.base03-background { background-color: #838085; } -.base04-background { background-color: #9f9da2; } -.base05-background { background-color: #bcbabe; } -.base06-background { background-color: #d8d7da; } -.base07-background { background-color: #f5f4f7; } -.base08-background { background-color: #d8137f; } -.base09-background { background-color: #d65407; } -.base0A-background { background-color: #dc8a0e; } -.base0B-background { background-color: #17ad98; } -.base0C-background { background-color: #149bda; } -.base0D-background { background-color: #796af5; } -.base0E-background { background-color: #bb60ea; } -.base0F-background { background-color: #c720ca; } - -.base00 { color: #2e2a31; } -.base01 { color: #4a464d; } -.base02 { color: #666369; } -.base03 { color: #838085; } -.base04 { color: #9f9da2; } -.base05 { color: #bcbabe; } -.base06 { color: #d8d7da; } -.base07 { color: #f5f4f7; } -.base08 { color: #d8137f; } -.base09 { color: #d65407; } -.base0A { color: #dc8a0e; } -.base0B { color: #17ad98; } -.base0C { color: #149bda; } -.base0D { color: #796af5; } -.base0E { color: #bb60ea; } -.base0F { color: #c720ca; } diff --git a/static/css/base16-unikitty-light.css b/static/css/base16-unikitty-light.css deleted file mode 100644 index 7e4c51b7..00000000 --- a/static/css/base16-unikitty-light.css +++ /dev/null @@ -1,33 +0,0 @@ -.base00-background { background-color: #ffffff; } -.base01-background { background-color: #e1e1e2; } -.base02-background { background-color: #c4c3c5; } -.base03-background { background-color: #a7a5a8; } -.base04-background { background-color: #89878b; } -.base05-background { background-color: #6c696e; } -.base06-background { background-color: #4f4b51; } -.base07-background { background-color: #322d34; } -.base08-background { background-color: #d8137f; } -.base09-background { background-color: #d65407; } -.base0A-background { background-color: #dc8a0e; } -.base0B-background { background-color: #17ad98; } -.base0C-background { background-color: #149bda; } -.base0D-background { background-color: #775dff; } -.base0E-background { background-color: #aa17e6; } -.base0F-background { background-color: #e013d0; } - -.base00 { color: #ffffff; } -.base01 { color: #e1e1e2; } -.base02 { color: #c4c3c5; } -.base03 { color: #a7a5a8; } -.base04 { color: #89878b; } -.base05 { color: #6c696e; } -.base06 { color: #4f4b51; } -.base07 { color: #322d34; } -.base08 { color: #d8137f; } -.base09 { color: #d65407; } -.base0A { color: #dc8a0e; } -.base0B { color: #17ad98; } -.base0C { color: #149bda; } -.base0D { color: #775dff; } -.base0E { color: #aa17e6; } -.base0F { color: #e013d0; } diff --git a/static/css/themes.json b/static/css/themes.json deleted file mode 100644 index ea8e5a0c..00000000 --- a/static/css/themes.json +++ /dev/null @@ -1,66 +0,0 @@ -[ -"base16-pleroma-dark.css", -"base16-pleroma-light.css", -"base16-3024.css", -"base16-apathy.css", -"base16-ashes.css", -"base16-atelier-cave.css", -"base16-atelier-dune.css", -"base16-atelier-estuary.css", -"base16-atelier-forest.css", -"base16-atelier-heath.css", -"base16-atelier-lakeside.css", -"base16-atelier-plateau.css", -"base16-atelier-savanna.css", -"base16-atelier-seaside.css", -"base16-atelier-sulphurpool.css", -"base16-bespin.css", -"base16-brewer.css", -"base16-bright.css", -"base16-chalk.css", -"base16-codeschool.css", -"base16-darktooth.css", -"base16-default-dark.css", -"base16-default-light.css", -"base16-eighties.css", -"base16-embers.css", -"base16-flat.css", -"base16-github.css", -"base16-google-dark.css", -"base16-google-light.css", -"base16-grayscale-dark.css", -"base16-grayscale-light.css", -"base16-green-screen.css", -"base16-harmonic16-dark.css", -"base16-harmonic16-light.css", -"base16-hopscotch.css", -"base16-ir-black.css", -"base16-isotope.css", -"base16-london-tube.css", -"base16-macintosh.css", -"base16-marrakesh.css", -"base16-materia.css", -"base16-mexico-light.css", -"base16-mocha.css", -"base16-monokai.css", -"base16-ocean.css", -"base16-oceanicnext.css", -"base16-paraiso.css", -"base16-phd.css", -"base16-pico.css", -"base16-pop.css", -"base16-railscasts.css", -"base16-seti-ui.css", -"base16-shapeshifter.css", -"base16-solar-flare.css", -"base16-solarized-dark.css", -"base16-solarized-light.css", -"base16-spacemacs.css", -"base16-summerfruit-dark.css", -"base16-summerfruit-light.css", -"base16-tomorrow-night.css", -"base16-tomorrow.css", -"base16-twilight.css", -"base16-unikitty-dark.css", -"base16-unikitty-light.css" -] diff --git a/static/font/LICENSE.txt b/static/font/LICENSE.txt deleted file mode 100755 index 95966f00..00000000 --- a/static/font/LICENSE.txt +++ /dev/null @@ -1,39 +0,0 @@ -Font license info - - -## Font Awesome - - Copyright (C) 2016 by Dave Gandy - - Author: Dave Gandy - License: SIL () - Homepage: http://fortawesome.github.com/Font-Awesome/ - - -## Entypo - - Copyright (C) 2012 by Daniel Bruce - - Author: Daniel Bruce - License: SIL (http://scripts.sil.org/OFL) - Homepage: http://www.entypo.com - - -## Iconic - - Copyright (C) 2012 by P.J. Onori - - Author: P.J. Onori - License: SIL (http://scripts.sil.org/OFL) - Homepage: http://somerandomdude.com/work/iconic/ - - -## Fontelico - - Copyright (C) 2012 by Fontello project - - Author: Crowdsourced, for Fontello project - License: SIL (http://scripts.sil.org/OFL) - Homepage: http://fontello.com - - diff --git a/static/font/README.txt b/static/font/README.txt deleted file mode 100755 index beaab336..00000000 --- a/static/font/README.txt +++ /dev/null @@ -1,75 +0,0 @@ -This webfont is generated by http://fontello.com open source project. - - -================================================================================ -Please, note, that you should obey original font licenses, used to make this -webfont pack. Details available in LICENSE.txt file. - -- Usually, it's enough to publish content of LICENSE.txt file somewhere on your - site in "About" section. - -- If your project is open-source, usually, it will be ok to make LICENSE.txt - file publicly available in your repository. - -- Fonts, used in Fontello, don't require a clickable link on your site. - But any kind of additional authors crediting is welcome. -================================================================================ - - -Comments on archive content ---------------------------- - -- /font/* - fonts in different formats - -- /css/* - different kinds of css, for all situations. Should be ok with - twitter bootstrap. Also, you can skip <i> style and assign icon classes - directly to text elements, if you don't mind about IE7. - -- demo.html - demo file, to show your webfont content - -- LICENSE.txt - license info about source fonts, used to build your one. - -- config.json - keeps your settings. You can import it back into fontello - anytime, to continue your work - - -Why so many CSS files ? ------------------------ - -Because we like to fit all your needs :) - -- basic file, <your_font_name>.css - is usually enough, it contains @font-face - and character code definitions - -- *-ie7.css - if you need IE7 support, but still don't wish to put char codes - directly into html - -- *-codes.css and *-ie7-codes.css - if you like to use your own @font-face - rules, but still wish to benefit from css generation. That can be very - convenient for automated asset build systems. When you need to update font - - no need to manually edit files, just override old version with archive - content. See fontello source code for examples. - -- *-embedded.css - basic css file, but with embedded WOFF font, to avoid - CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain. - We strongly recommend to resolve this issue by `Access-Control-Allow-Origin` - server headers. But if you ok with dirty hack - this file is for you. Note, - that data url moved to separate @font-face to avoid problems with <IE9, when - string is too long. - -- animate.css - use it to get ideas about spinner rotation animation. - - -Attention for server setup --------------------------- - -You MUST setup server to reply with proper `mime-types` for font files - -otherwise some browsers will fail to show fonts. - -Usually, `apache` already has necessary settings, but `nginx` and other -webservers should be tuned. Here is list of mime types for our file extensions: - -- `application/vnd.ms-fontobject` - eot -- `application/x-font-woff` - woff -- `application/x-font-ttf` - ttf -- `image/svg+xml` - svg diff --git a/static/font/css/animation.css b/static/font/css/animation.css deleted file mode 100755 index ac5a9562..00000000 --- a/static/font/css/animation.css +++ /dev/null @@ -1,85 +0,0 @@ -/* - Animation example, for spinners -*/ -.animate-spin { - -moz-animation: spin 2s infinite linear; - -o-animation: spin 2s infinite linear; - -webkit-animation: spin 2s infinite linear; - animation: spin 2s infinite linear; - display: inline-block; -} -@-moz-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - -o-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -moz-transform: rotate(359deg); - -o-transform: rotate(359deg); - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -@-webkit-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - -o-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -moz-transform: rotate(359deg); - -o-transform: rotate(359deg); - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -@-o-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - -o-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -moz-transform: rotate(359deg); - -o-transform: rotate(359deg); - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -@-ms-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - -o-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -moz-transform: rotate(359deg); - -o-transform: rotate(359deg); - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -@keyframes spin { - 0% { - -moz-transform: rotate(0deg); - -o-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -moz-transform: rotate(359deg); - -o-transform: rotate(359deg); - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} diff --git a/static/font/css/fontello-codes.css b/static/font/css/fontello-codes.css deleted file mode 100755 index 87b4930e..00000000 --- a/static/font/css/fontello-codes.css +++ /dev/null @@ -1,48 +0,0 @@ - -.icon-cancel:before { content: '\e800'; } /* '' */ -.icon-upload:before { content: '\e801'; } /* '' */ -.icon-star:before { content: '\e802'; } /* '' */ -.icon-star-empty:before { content: '\e803'; } /* '' */ -.icon-retweet:before { content: '\e804'; } /* '' */ -.icon-eye-off:before { content: '\e805'; } /* '' */ -.icon-search:before { content: '\e806'; } /* '' */ -.icon-cog:before { content: '\e807'; } /* '' */ -.icon-logout:before { content: '\e808'; } /* '' */ -.icon-down-open:before { content: '\e809'; } /* '' */ -.icon-attach:before { content: '\e80a'; } /* '' */ -.icon-picture:before { content: '\e80b'; } /* '' */ -.icon-video:before { content: '\e80c'; } /* '' */ -.icon-right-open:before { content: '\e80d'; } /* '' */ -.icon-left-open:before { content: '\e80e'; } /* '' */ -.icon-up-open:before { content: '\e80f'; } /* '' */ -.icon-bell-ringing-o:before { content: '\e810'; } /* '' */ -.icon-lock:before { content: '\e811'; } /* '' */ -.icon-globe:before { content: '\e812'; } /* '' */ -.icon-brush:before { content: '\e813'; } /* '' */ -.icon-attention:before { content: '\e814'; } /* '' */ -.icon-plus:before { content: '\e815'; } /* '' */ -.icon-adjust:before { content: '\e816'; } /* '' */ -.icon-edit:before { content: '\e817'; } /* '' */ -.icon-pencil:before { content: '\e818'; } /* '' */ -.icon-pin:before { content: '\e819'; } /* '' */ -.icon-wrench:before { content: '\e81a'; } /* '' */ -.icon-chart-bar:before { content: '\e81b'; } /* '' */ -.icon-zoom-in:before { content: '\e81c'; } /* '' */ -.icon-spin3:before { content: '\e832'; } /* '' */ -.icon-spin4:before { content: '\e834'; } /* '' */ -.icon-link-ext:before { content: '\f08e'; } /* '' */ -.icon-link-ext-alt:before { content: '\f08f'; } /* '' */ -.icon-menu:before { content: '\f0c9'; } /* '' */ -.icon-mail-alt:before { content: '\f0e0'; } /* '' */ -.icon-gauge:before { content: '\f0e4'; } /* '' */ -.icon-comment-empty:before { content: '\f0e5'; } /* '' */ -.icon-bell-alt:before { content: '\f0f3'; } /* '' */ -.icon-plus-squared:before { content: '\f0fe'; } /* '' */ -.icon-reply:before { content: '\f112'; } /* '' */ -.icon-smile:before { content: '\f118'; } /* '' */ -.icon-lock-open-alt:before { content: '\f13e'; } /* '' */ -.icon-ellipsis:before { content: '\f141'; } /* '' */ -.icon-play-circled:before { content: '\f144'; } /* '' */ -.icon-thumbs-up-alt:before { content: '\f164'; } /* '' */ -.icon-binoculars:before { content: '\f1e5'; } /* '' */ -.icon-user-plus:before { content: '\f234'; } /* '' */ \ No newline at end of file diff --git a/static/font/css/fontello-embedded.css b/static/font/css/fontello-embedded.css deleted file mode 100755 index 861ef86e..00000000 --- a/static/font/css/fontello-embedded.css +++ /dev/null @@ -1,101 +0,0 @@ -@font-face { - font-family: 'fontello'; - src: url('../font/fontello.eot?899037'); - src: url('../font/fontello.eot?899037#iefix') format('embedded-opentype'), - url('../font/fontello.svg?899037#fontello') format('svg'); - font-weight: normal; - font-style: normal; -} -@font-face { - font-family: 'fontello'; - src: url('data:application/octet-stream;base64,d09GRgABAAAAAC/YAA8AAAAAThAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABWAAAADsAAABUIIslek9TLzIAAAGUAAAAQwAAAFY+L1N4Y21hcAAAAdgAAAGHAAAEdO/gU91jdnQgAAADYAAAABMAAAAgBv/+9GZwZ20AAAN0AAAFkAAAC3CKkZBZZ2FzcAAACQQAAAAIAAAACAAAABBnbHlmAAAJDAAAIi4AADVSSwKKUmhlYWQAACs8AAAAMgAAADYW8cayaGhlYQAAK3AAAAAgAAAAJAfJBAtobXR4AAArkAAAAGMAAADArL//3mxvY2EAACv0AAAAYgAAAGJHzDggbWF4cAAALFgAAAAgAAAAIAGFDaZuYW1lAAAseAAAAXcAAALNzJ0fIXBvc3QAAC3wAAABbAAAAg3WuIl7cHJlcAAAL1wAAAB6AAAAhuVBK7x4nGNgZGBg4GIwYLBjYHJx8wlh4MtJLMljkGJgYYAAkDwymzEnMz2RgQPGA8qxgGkOIGaDiAIAJjsFSAB4nGNgZJ7JOIGBlYGBqYppDwMDQw+EZnzAYMjIBBRlYGVmwAoC0lxTGBxeMHwyYY78X8gQxZzOMA8ozAiSAwD3dAwvAHic3dTJThtBFIXh38aQCTI4IXGAQBgyMcRR5DUSkh8jirLgefBz8RYssHSWVbA3OdX3LhOFdbr1We1Wy92t+5eBZWDJjm0A/W/0fETv0Gd73fklHnfnByz8/YD3PhpoS981KbNyWa7KvNyWRR3WUT2tZ3Vaz+v8ZnJ3B6K76qK76vpvV/1z6/muP7r9Z7f/+sPerur7WQd+oxUe8JBHfu4nrLLGU57xnBcMeckr1nnNG0a8ZYNNtnjHNjt+q1322PedPvCRT3zmC4cc+f1P+MrYP79yjyf937fV9tGf5rdxm3BorSh5Dii1tpRaX0qtOyXPCyVPDiXPECVPE6XWo5InjFJ7OiVPHSXPHyWXgJKbQMl1oOROUHIxKLkdlFwRSu4JJZeFkhtDybWh5O5QcoEouUWUXKXXTnCfaBJcKuUiuFnKLLheymVwx5Sr4KIp18FtU+bBlVNug3unLILLpw6D1wB1FLwaqKfB64J6FrxCqNPQ/kPqefCqoc6D1w83k8D4NzQIsWEAeJxjYEADEhDInP4/CYQBEw4D9wB4nK1WaXfTRhQdeUmchCwlCy1qYcTEabBGJmzBgAlBsmMgXZytlaCLFDvpvvGJ3+Bf82Tac+g3flrvGy8kkLTncJqTo3fnzdXM22USWpLYC+uRlJsvxdTWJo3sPAnphk3LUXwoO3shZYrJ3wVREK2W2rcdh0REIlC1rrBEEPseWZpkfOhRRsu2pFdNyi096S5b40G9Vd9+GjrKsTuhpGYzdGg9siVVGFWiSKY9UtKmZaj6K0krvL/CzFfNUMKITiJpvBnG0EjeG2e0ymg1tuMoimyy3ChSJJrhQRR5lNUS5+SKCQzKB82Q8sqnEeXD/Iis2KOcVrBLttP8vi95p3c5P7Ffb1G25EAfyI7s4Ox0JV+EW1th3LST7ShUEXbXd0Js2exU/2aP8ppGA7crMr3QjGCpfIUQKz+hzP4hWS2cT/mSR6NaspETQetlTuxLPoHW44gpcc0YWdDd0QkR1P2SMwz2mD4e/PHeKZYLEwJ4HMt6RyWcCBMpYXM0SdowcmAlZYsqqfWumDjldVrEW8J+7drRl85o41B3YjxbDx1bOVHJ8WhSp5lMndpJzaMpDaKUdCZ4zK8DKD+iSV5tYzWJlUfTOGbGhEQiAi3cS1NBLDuxpCkEzaMZvbkbprl2LVqkyQP13KP39OZWuLnTU9oO9LNGf1anYjrYC9PpaeQv8Wna5SJF6frpGX5M4kHWAjKRLTbDlIMHb/0O0svXlhyF1wbY7u3zK6h91kTwpAH7G9AeT9UpCUyFmFWIVkBirWtZlsnVrBapyNR3Q5pWvqzTBIpyHBfHvoxx/V8zM5aYEr7fidOzIy49c+1LCNMcfJt1PZrXqcVyAXFmeU6nWZbv6zTH8gOd5lme1+kIS1unoyw/1GmB5Uc6HWN5QQuadN/BkIsw5AIOkDCEpQNDWF6CISwVDGG5CENYFmEIyyUYwvJjGMJyGYawvKxl1dRTSePamVgGbEJgYo4eucxF5WoquVRCu2hUakOeEm6VVBTPqn9loF488oY5sBZIl8iaXzHOlY9G5fjWFS1vGjtXwLHqbx+O9jnxUtaLhT8F/9XWVCW9Ys3Dk6vwG4aebCeqNql4dE2Xz1U9uv5fVFRYC/QbSIVYKMqybHBnIoSPOp2GaqCVQ8xszDy063XLmp/D/TcxQhZQ/fg3FBoL3INOWUlZ7eCs1dfbstw7g3I4EyxJMTfz+lb4IiOz0n6RWcqej3wecAWMSmXYagOtFbzZJzEPmd4kzwRxW1E2SNrYzgSJDRzzgHnznQQmYeqqDeRO4YYN+AVhbsF5J1yieqMsh+5F7PMopPxbp+JE9qhojMCz2Rthr+9Cym9xDCQ0+aV+DFQVoakYNRXQNFJuqAZfxtm6bULGDvQjKnbDsqziw8cW95WSbRmEfKSI1aOjn9Zeok6q3H5mFJfvnb4FwSA1MX9733RxkMq7WskyR20DU7calVPXmkPjVYfq5lH1vePsEzlrmm66Jx56X9Oq28HFXCyw9m0O0lImF9T1YYUNosvFpVDqZTRJ77gHGBYY0O9Qio3/q/rYfJ4rVYXRcSTfTtS30edgDPwP2H9H9QPQ92Pocg0uz/eaE59u9OFsma6iF+un6Dcwa625WboG3NB0A+IhR62OuMoNfKcGcXqkuRzpIeBj3RXiAcAmgMXgE921jOZTAKP5jDk+wOfMYdBkDoMt5jDYZs4awA5zGOwyh8Eecxh8wZx1gC+ZwyBkDoOIOQyeMCcAeMocBl8xh8HXzGHwDXPuA3zLHAYxcxgkzGGwr+nWMMwtXtBdoLZBVaADU09Y3MPiUFNlyP6OF4b9vUHM/sEgpv6o6faQ+hMvDPVng5j6i0FM/VXTnSH1N14Y6u8GMfUPg5j6TL8Yy2UGv4x8lwoHlF1sPufvifcP28VAuQABAAH//wAPeJzFewtwVNeZ5vnPOffZt9+3b7ek7larW90tWkKIfoIQonkKgwABMpYwKDIGHCODbI8TOzbKZALjwhsHPIzL601qnLDjuGrXccYDicNuHk5l7CSLd6ucyYSkMjtbm8emcLLrSWU9mR0Gmv3/2y0hYnuyO1VbK6R777nncc/5z//4/v8/MGDsxm/4X/FPsm6WrHek2wKaZBxGBHDGZwGrD9tx25ZKrDdr+0FNLwONLrnyGsjTpVrshBpdHKyOOvyv/KOBvsDzz+NlNED3wM2y3//88/6HHXr43Of8727o76cGTOKcLopzosJ0FmRLWJ1tqq+v4HcNxnFWI8xQjVkdVE2dZZrQZrEDl+MKCJwuF2yaSckn8BUfXT2UKWXSxezKWMhUEr3Zcs7Hk1Ctzd8jtprpSufylXI1WkrCKihWa6WiI9RewCotQ1V4aa7S4ZfspM1j7bFP2qkQd+KxTSnn2hvRJKSct61q5ky66n3bSb1ixM7Y/jN+G85Ew8GrZtK8Gur2OTyUCsl2a/7hiQtOKuXgBTp7ejqTsMu5ij0c39U+7GJeDTL8ob35LtJhhHWyRL095DelUGhz2MLeJOyoUKK9gLQPR2wfuLuTq5Rr4Txds+7OKI445780YEWsf7xqORYMvOHrhNhHPSlrDmIp+JXlf73xluUJgHbypBYypQ7R1/1WROlpRKONHvziwjwM3I18vTveZvu8hq6pigDr1gllu6NOKCAUuxdqywA5QqtFw83ZZdLvMzv+2L/95ZF7//uLS773vQbOM2q+9zyXvJD+/vfTL/xydhbON6ccf58J4w/N+YYc4CdYF1vP1tXXpEGqxNY4BQ3UowaoUlPljI58rgHXponr5DiyDptQAAuj6+pOVzbW5UR6wi7v2GoeWWUZ9EMpmEn3Q4spiE0iXfQ0Lx+5cnUVVLqaT7WuotMJSYgEka/4JVO//paicpQumMH91i/g4s4bjm8G1hrKpIRD+nkr5bmg45vG1+mNqfOYdDvM+JyQZgEXEizY4XSYly3rshm34bJ6n/ITr3nZ671sdjiXtRnFa2Izheuicd5BWiBBblwSV/hF3L92Nsw2sDvYHfXxcgdncreKIrVrPQc+tm5JHoVKBTnCFKnMIglRnOAoAxV/Z5gq8HeGCXFsEakYUWp0a7i/rdtOaEp7b7bWD7VyTdUcKOe0tBqxnWIVxauEkmVHVI4kyqTd3e8n/VEbhlIxWsNqpJKjOWEkZ9iJ2rhJPshgbS2XryVRr0C1d2AFpD92+xQcCXg2HQw4gQ0DnsClVb9YFVdMbYPRNvZ40ePZc+1fFoudiil8nm4PGJGJ2/5EXvU4+fH/fHzJw9/ZuHZ/pnIg5bl/R+bI6vWDa08+Bfcg2x/c6AkEPAMbAh+ScG9j771FI6+aWqH7kW3BQujEs2bVUFVbBaVxffvHOiDWNhUOdy+dPrLFPHnvwfqa7gPVMPLbjRs3HkAZsVFndbHxutmJ4uBDlcRHtr7cNTZRd4hqIFE7ARMcxDTqMi+/raOeQJ3F779ZKwSMMwAxwQSI0ckvRzN2OKQobb1Q7gfVdoYBbGIzpGM/H5ZJjtRCvXvi7Jtn8ReSfYP2qwcfGzv7wTofuu/086fvG4KNr0bgiXvO8mcuPas+2fhUohB5dePwkaf+9eljg3Ld4We2PXbw1UhLZi6KvSKEazjCNtbXHZocXSuZXGVyYOWejoDEGTWZA7mEyVlSUbM4Y5jFJQnkGH543527d962ubeQToVDmuLgpHNpHyAPZFGh4uZrTtSxcW/ztALcZdS0qBHyuTxqBry6HFFzpYyUMspYLTfPJp1YwH+opIlXkGmK0dZgmitifNWuR3bxPQ/tgbiufdD0hHtUxT/m1bRtbe2GJgPHdSvQEd2hBtRNjlT0HtOvH9Z0MJUP6r5ottlW3xZrN3QRPI6S5o9Hdyh+bbMtpdFsbMKhVePjHx4ff4TqA8lIR1H1qZExUIa8+mg8YGr3GNaQotaTik+1iv54hx8szW3b1p5aqlmaPbaoqWeVoqyPt5q2B1CVunvAmJjhl1ge7R7qLRtVBSpYlStcVY4yRXAFpVAKJiQ7ShKqAp+mghhH2SSJFGzUybRlC9m8pnSg3nL8gCRCM1YJll3qRaLuq3wmrWpB24mWikkONmrFdG41ZOiCequE5Hei4MAh1A2g6+fXT02tP6+bAM1itgzV7i+qHPWH6ml81xN3rvoctF9O3APLPBUeUnwGF1Pr4dT6KVP3GCoSF5mh8Qh2lFyHPp+n8aZp+885vsuoCc+hYTTwxbzde1u8wgeYzdrqjhdQHEaQCgz5jPBINEQ2DzV4Og9NCBI1WspYvNg4gBa3ccDj2Y936IEeT9za54FnGnd7PPAnnqS5z+Np/BBfe/Z54vitG40bj4mL4h62nHXW4/RtV/zYBPI8sFFghSVsOSwnixZN51G5QTVKCkkjCuaqWMRHNerU6BF5FQtJ3nr7zuENm+Ue+NXYVN8mq32skeuZTiXVPhiNldsbX+yLWVbMgR8VU0PVaiO0Th58fAv8iqoCu/5w86ZvTGHHdmtT3zR1NFOxgwXY3l6OYcd2nUvq+OGAr9gIjT5+QNbh7Vg/dST6SdRFF+UyV479iB2WsdvruxI2Kh0/rsnntQzJWTyCAEmS5Sb0dJShVRSgClJPqPUJRCmKq+SVCZL40WCgvzeXaYsGOoOd4XBId1GHj0xcEiDSValFIdvVZCi0edV8sJyLBlGbo82sBZt2EA4N7x3GXz507e3zeyEByWsnUKYsVcyhiJg7y9lrJ7qrUM6KuWyZx5YO83V71snBxtWrMxcmIXEOjedeaqjzF3QzdH2vy4L8BboxD+lfd83NFa9hO9gHUDn9PjvDPsP+jL1Wb3uqzg391MemU1KRj6xApTs2gCqWyZaCrrFIyOK6EdGnw2AEQCqGnA56OconJ2s67QdhIv0QYHo05EZ7gtm210YNPvx/19O2YXxhBLBHJ+u5L7z4wp8+9+lnnj795OMnP3r8w793bObwwak794xv31qpVHL4r1JyEINEK2hTUWoTYDuEVVFF5lB/umXErW4536pHqa4CbgLiWxU3winhpsBv9Z8va5FmWWB7rdU+iu2jrfGpnsavtcancrRVXty/Fmzi6fkNv2z7N5NSwAu85yNf5fgau91X8JLfvv76zSoRdHwjLiTG6/dvafaDRTXvd918CzN13/zsz25O4+eL+jTugiRVNH6CV/5HI36sD4zg8/VP3OwLX4GEW9H4KfX5D+891M9udr7neihbLmf52y6Pkl77Nn9IbEW9Fq3bhqvX2Lxai4c42kujBSFrxrxqQ63Gj6BCi3v2owbrafywpdqeM+H+xl2muR9rYAnpOWpADed16Lf5s/Pfglu/FY263+KOC1lJi9ZaCpSfbvwAljRHJS2Kn0ma+03+540fNn7gPprwGffz7jToO2hxXuHbmrpagVvdgajt6uosWfeFpbVWJV7ch+oYx/1ha23P0Uqe8zy4D7+xBL9mUj1OwGwtivzCD4lviElWYGW2mm1nX64Hq2UuWcLH0aiMjKJx2rT15XaU5iUKIhQUbjiKkL8FYDX8RcDPmLWZaRq5iC4C2/pyN3bIvV8HpjGu8X1uP4bSjiAZZzKLzUHOUlskadMV5Vwbbw2s8dHJyXoU2JbNG9YPrhxY1tHmREIBVoCCQb4RAeMoYdwIQV2SHjsJxWGooFRVcoSP8ZJH4JxTcOxIqYzmJaslQStV8zky4SjQRfQ9yKqXq/BrJ6WMrCgNDha2FRL/kFu7fW3uHxKFbb2DK8u1EdnZ+NKqZGLEiS3nKweK/TACiWSjJpVlXaTjewYVMdnpmJYn331v9Utzd65NFPLrcrl1+UJi7Z1zX6re2533+HSnc2l184adG6orlpbLS49tGNs4cj2myK5liq4M9khlHrvwE6iDNdTBhXoewQpDtMD4USQ1ug0ArkWBCYS6MJoJZ6vhgIpOQ7gLF+qDqNIyGGg80B/HhRUdLYLrvAAJRF0Ab6ac62+5/nbw6f/4DA/h4+fvWzXOx1afa3zdwfcRWIce9X2Hn3768H1JJm5cRzw7ifOx4Gvw9/zhrS8bYxNrh9jX2FfYRTQLz7BTTEW8xNBI4Czx6Ufse4iqJtlOtg4dpRJLsTZmEgfAc/AsPANPwifgUfgQHIK7UZ3/mP1X5AkVHcjdsA16sL/OVHgH/hq+C2/AN+DrsAJK+A7oPRtBTjPx++tbXz+FbERk+xp5BPj0/34OGhvBNQN+C9imjv9/hJicdHeiXkHXRxNcO8o0VWgkcbpQ9Rmmg9BhBjXWMdSRCGrH8cbEhCI5wt7RJhnrgxLQtiriEEqbwklOVaU5htIcQ7k5hqI0x1D24NqVLR3/zC9PTq5tcxHiD+Ey/Dv4MtwBe9i32evsS+yL7M/ZF9hH2IeRRiojVQH4Z+LnUNqLSYJK5K4BQXGSc/RyqtEcOThrQM1VbK2cUyv9kvQjRUnsAthpNa2htGcQVZb6OUJPfI0qWkUtQJ4U+T5qGh9IU+Q0+ivmtGHI0KB5h1wnlJ+SU84X3QZqlBrjB/I4LI6az1EZtQ5iWfyU6mjoczlk3tERq5WjeVUr0lDRWhQ7a46GM8Cuqpbkds3RXOdLy+dUp0TjdOKEamqnQHdUpfEq2Aoxcb6fV8hzQ0xcwnkXk7JTOEUcFTvX0m5gBMF0tYKj4IVWn6tGi1VcLi7LViOZKhlBfK+lNZ/I4RSonKd5IeAo4zqcKo6EE3ZqSY7UqdYc1ArDgD5lpZ8ifS41itgijbNBN9Kha82p5oYhUqtmaI5E4GIFCSLQ20QTVUX/k379gCuLIL36cdf8kKvmiO5VNYJqGx0B1wtA7Ry1VQdeeuhbDz74rSvfOaY++lUIcx0dfymCkTDCW66rArdMSlNRJeioEIWQ+KOCiqBRkSq2BN0CJS4FR98KP8Y1A5ugScOOJpeKVwjbF5Y6+nrAFYND2FAlV1RT6BKZX6gGjoaoUxHoHkrwaR6/DAgcVeqg0w0HFgj3Q4qwLPw8t9o6hKooYUV4pNeDH1KlLg25syjJzRQQM3EOiqR5kv8J3NS0kNQMiR/kPixzH7oP3K8LHFqgLUSTjSMolsaFLgzNUVVF1wPSxnFwcOETEh1tPWhy/AGFY4kLS6AfSKRCQfTgd7huC3QwyZSjZANFSUDGhIFGFoSX+4gcEmtUnAPSSUpNVzRLYgGdYMWdiCV5CLtzcj65qSOpVFVTDMu89/fGwAIv9o+Q2iBCKxbKPP4AzdzEHeJIamyEE5EeP3DDBBF66LVfvPaQe2n8DeicwmO6UDzYDIdAX0Rz6QpctRQV6YomTrgv8JnrRFbAleNea0LXTE0qqmIRa+DSLAOJouASRJALn07vhYHbKlTwSROHVHBZptQ0DQxF13QkkiBaIjuYQvioWpHoRpi6nwtSZj4kgFTxH05i6Q5Juy5Vv4lzQP/NZ9geDmo7RysrVfRmhQggjaWu6BI8Ma9i4aqlpfukD0yPjb66giTHvQgJU0qDYpamS2Ae0EPEvzgPE/ELbSXSO6D4SRdzDy4aizLmM3yKQSFXJDUSHcVE4X7kEaBQpkDvUXIdCenjpqlQTNNjKMQauAe4ZokCgSRQAZeHHWnf8dLwRm6nNVMQkeQASc1NgS4WQiSBrha1IX6icZS4HjR8hsVlQHPjWp8TZ0Q3auQoS9c7EQ7zoELYBKEq8NkFXNzhpF24aqv5NKqJPCEQRKoahaMQtcKbX3hs54YNu2Di0Ql4LtXV+Ka9awUMp6a+e/xl6Mn/i12rJybg71JTqcY3a+M2VqDtuPF3iEH+J2JWP+tCO3qwbnXgfnPDxUUjTeyZYEg7ZMqjZILJTXSBqNyHdstSEHKmGRJ3luKEszdb4DZToEJOUFN0Io3oijxaC8JT2UV+Iflp+XA5Ty8QSUabnpwAh+JmhLVyNVSXRXTFTO0+zXQvKKqa9igaUsPS7tEtHT5vR4x06NoLobQRseElI51L7z2im6aOF7B+gCBYqqhBbqDJVXng2tuZTDCELlAmI0JB2275I0iMEGKxDFtSzzHkTZfs9yu4GiYoeyPcmItgo5lSNlNyF0JZmHymlYqpVTLNVI0bm6JAVpTAoQilnCspZwaB3xUXG15JRmfwgQpfp7dvucjwrdZbSrtcSTHuYsMPuFjVZr31nnnCSpJ/GJek9CaIO0YZCwUtD7bTgooS6c0G05Q8WrDoaNzhpd1PjvHxUxzmTcA7//6jFT6968nnn9wFAx9qaZCHXnNj8bjcn+F3VeSIlYitNoNWj9SHVwVQUDQmy5TEGlmLkrKpiXMGmIZKVpNHGQFodggnKpmQ0zoWVAXUDzDSBOPIKzRfhRM+8pAH1Gqvwf9Bh9g/60P15Yu7oPt09Hf2mUS3yGFsw/rVQ8v7l+SSHU4YKaHaBlG2lke4HyHDqxJ+CbfSc5VmOAF3DyvybkRDa0UoVrt4wBFRyFRAy7cSkfCr+u31CkQM4zUjhH/dU+sbAxS/hDczSUNoHbrptRoDbvwI3syWlW49VjvXeOIcny2dKwX6ArcHXlt7+9rOKpydH6Lx9SPNAdZNoXoOq3HUruVsa4xNGo6gw5nPNp74LPSXz5X9/tsDfa085jaB6yPvmHWzx+u4CK4kHJ+G5qpNEKpERnNjUCHcgvwtgi4owA7sbrJQEnbhDeQdROitHShB72rJZt/dcLIeYqwrFYsG/IbuElpDQpdahC5n0hqgJ1oqok+ed2lo+6FFxTeKJ0qb4QOWIhvflV5EFctE8kpj4IrYZu+/st9e5ZywSydKQyNo+mTjLyVeoV8+cKWx7C34VCKy/619kcgJx9W9D7g+oYf1s7tf0UjvQSvk1sE0XUU1rLFpg4yRaxymEXd7BWq9bkoq3H9rExJNuqNsEjBBfqr7lvaFS0E7XYpE3EQdBbWaqq9YVUh9CDf6nRekPcrVUjBNuLCLvOd8ENW7ZQZM/IWHHd/fJsBUwc97f+y3YaKWKPD+DjiUKBQStQl4+CopPLp8zedA49doIv3gt/0bj0GyMFiAvhV90PjJMRJvXPOc+Kwbf7Vx1aX6QA5tYE8+hAacdlSMoIzgniFQmHVjJa1EChyOZ5bG3GTPfIo6uxA0SUK2mTihrGKR0D+C0yjBWwq0NRPTcLhxIjLorIpE4LgzDv/K2/HxHR88e/aDqU1thvGn9/HC1i6/uZCM/k3jhG2vxp2E47Xxv3GyW6fg7JtPcTughrSpuSHettR24/GUP7woH8b1CNSCXhZkEXa4fjdTTGXWD6YwZxEN6WLWh9hVn/W4gXoDgRHy26wXTYNUd+FNlXsYgpMtoZDPpyNKQLUaCUXssC/oCwb8ulf3Wh7TkJpEi08sHQwgp0IwE3T/Il3BkvuUpdKhM3DoNL/0j0+M8DfOuKXGT5BAyeuv8aHrc5vF3mtvwzuN3fDS5etz/ISrchfycxnWxzayc/X2zhjijnAI1aPwInpg60GDIhia0uLOAZyuQDxGWRYNOfAounOqNNVphaBp0wprzNA1Y5rpOH9k2dq7uuiGfv979TMMN1ZkTDBDM5CLA6uHKiWbsuQ5O5PxEB/P2778Ytu3Chbbvug/mRJEiz5nqd2aBXNumH3O1LvxAQub55OFhaH3TBZ+utmJYu3NTnPE+3Nm2+9MIpJdfUD8EvFOno2xl+u+bge9Hz66rkyeQouyOebCQnEUbROCOzlD5zom1GZkrplxULzKfGQu867WkjIS+252ojTqkt9upTYTGfxdmQwKzC3pATaycfmynrElY3bIMlke8robmCOPTrOdTiCXkPKNRGZNpZTjMFBeEh3BfA7SETd7Sbls1JfkLfqA3Nk1gJKJ+pS6lvE1vH3igaPrN+IM5HhYqZR233H3jtPlQYNbf++xTTnIQ8baDXv3Qcmt3HP32OaNlVU69/yvVq1Z37B3/+GPP3BsnTuGmKwPzxz7Ax1dwNCB3TuXLR9esdIIi6IwnMBPdY86tCnX05DNqlTy3XXU++O6zl04RjnrA+IXuFedbC27rU5OB+rm5QAbmpQP3zwDAMfEfADULyhENNtCznB4su4FtDF2mHVCp5wn4nKkAIUIog65yEmgvG6VHHginktk1WnWI6WWYynvxjGq1CgHv75j5/iGPfcduefIjnVdXWrW1x4oBYXJM5DNPTV1Z0OJ+cmB7ubduc13PvbwR+buosYz2DilZHXVFxKTieTKjRE7mdqxbs/uCzuXdAQgKPzq3r+Y3P9ULtt4OyBV3S1tvrM7HWvbuahtpMsXYgu52CsuL69hc/VwDwLdIJqdWj86Yl3ob8gWgO9GVY6e0M38LFJIuqaMrME+pqqWipTrY+jzz/5TbRflcCfr5mBHtlLNliiNC7diIQftm/pbQMhVC+GgewpowWrk0VqUugglL2CgJwjlNx5eQD8o4Lp52Yl7DjSeVgKyjj7ufQc8jg8SaAV3n1+APW67BcxzHjUC/IgSIdzCjqpaV3xux7jjLJwloNhrjPWwYn1ZCB1y5qYGmhyF+p+/x3mHSs1uK7mnHYLlXB4X2IlrobAXQpSmYIVbQeJ5SvBLtr/xTiwcGmtc9nhWUg6hsNP0q3rkzNT662/R9Hl0/RTsBgvXszRAzZLmSpx9YcwjTKhcv4KLm17LY3RjTfuNl738jVYusVYv94BUdNb0GBV0XhRJ+0UxiulFKWMy46OUrsuVXDcs0gpnV5ozFZHmGSL3uFmrnFlcnrQD1/7WTSSJoJtDet/SzKJ8EwQWMlRgg4/yTj43BTUv4xfFW/wS4s8VbGm9QGfoBO5D88BG0/m9Zf6otroHS5LObizkb0lOkxwdRxJi/IfvVT/4OJkm0okUVewnd9lltSvl7LVL3VVo67w0mspt6ODxdT2dH/hyKlZd8pflipVOermVDCa9afWPp0OZIejvFVVs/p8aG5s8+ZUO54larD0O7fHohuPOq31jibOZvBFCc2SG9Lg4tM4X3dXdO1jG9Wkun13B9dFZQbLtFfSpxtDPvmspcLVqcKF1kx0QIzqWNdWN+QpVEzMG0IkmNiPBPeI0gwhV5R4y0/gdQI8U7xMMneHRbaMb160eWrmiXBpY1lvI58J2OGQpMQJpaTXJq2H3kEuSYqVVNMjueQuVzhdWc9n5Klhos/Bga7T9lOwZ5sUkv1wY7OM9w7mj+Cf7qwO/XCkC7XGxoquqe/omtniNdthTGOrhfSv6G8+22vymdf+zHaZ97de2uWPs5Fe/89WTY5eTBbcpOK2HX33f165age+Lvq7e0Vh/b1c9O9/kD1oPO1r3//JHx4PBx84evnhq+/ZTF1uy/IBL4yhbzQ7UrTIZkJyHomgtix5FIwvzx5tuQfHJJoq/WU0Qni0415P1ILBVg93pREcowKIQVV0DggQkG4xWAgEuyf0w73ddbjS4ZDtcNJSr5tz4+TBfQwGa8jCk0Bd/58FvPQRjtw34ve23b4ylcmks80e+CR97/Oen8oVjf9zRLXQfRwUuLOm1NTug+ScOwuM/h8DPH+cntp8cHX5wSbxS6u8eighl+8lnT25v/OSu56flXTldWgZwdPz8is/R4/FwoXh2HKumn5+n0WtiL9rIMnuu7mn3cuIqzuZJlEcuEyoCFKXFd9hJZXyGjMNmFEoK5EivnEc93e/XnJPbsGBSsk2TQmewqCE5gq5tlpKPtwblkiCP2ZkOZTJ0KKujVykTpCynKa1fzNpqL8HHHEHMyrCsobIilgQnYmtJiNpJAdeNrgEIDfYY8O1C4ur4h8evJgrx/oHuED95r5LqSylHPg5OemBgUh/oMowlg/BvEoVV4+OrColYcXzq1LbxswHTgxokHfGYgbPj2x+f3lVebCMyrMTW1ofppE8nEE50cTIKLa2eQqiUhUVIjfqZAk0E5tQJBNXqaKWSKTmZ7oyuxHubxxkWzihk5g8uzJ9OoOTqe1mPC01Fet5VqeebhQs+5/Qt5mMtqdcLbpMLTV17gRTtBRvW/LYBgaZOEhXWS7Eu8v1g3uMDCntxmHf72OFMNLw6SgaPvDvePB+LiqNS7ldco7+QnadUaCqK2nUETKloQROtj51eMbxnT23OThmNn3o8kPDEY3wOTu9NXtn/GRkKSNNCVCtynSv21geSIfWMz/FAktL3SdP2n/nxVqYsxMHCLM6yrNo8LZuwm1bCQNYzXM+F3O2WtdPoeJCm664Ho08wXdNH19ajUQf1Ylube9IaNSAhPWQihH5I+mHXfcXFDXMHnXJ0wamq3M8pliaGRW1RNI1/JHL/6Qdt6Yu3S9/o1Ba/jLcHGj9vaUdItXTgJwenHj99at8QXzF98uzHD1RvWxR7A1g/zsc2+toNK9C3YkWf32e2/7eWeou37i/ednJ6BfU9OTUky4ePb1scpmvGK/kJlOcMO9yUyBQqMXQg2FFOAQmFNfOE6KoQdBFuoDb73k0EtiFkRw6IQAekHgaW6uyI2eGAz1BZBjIaaT10NH47AZ5EPdg8IkmmJhrh29wQ5+IsuO1Hh95Nj38+GV2cBr9wz9P8qXuJSUkUL8zjmhC808I16XqnQh4pmn+6IwCQhNEYChX+uBAm3PI/m2HXoqMs+KO3lptHBovO5WaUtRmOTTRvzVedbgEv82HZxS3f45mxVozWzd+zdparZxYcD3CVG50j4DAKrC2mqcwCy3U+Fidbq+V8NqdGgnaU8OMtObqxUKjxZrA7ZIT0W7I8ZbNgnvdE7ManbI9U3FjORTHnxnJi7Ha2vb51C+haZwclyZFky4M4DzmC4qDNMl3osyoshONI9d6/yOApbsRqWV+ke02uEg4ZKCe1MuUWEQy4eikzv/NIWFtzIo7muJlXqiFxyrv1wxwRGF6GRamYlGoUV0uaAzudcRKonxL2XjvwWVdTfdbv8Kc7DWgzDMNRUt1bNmf3FAsbw1hpdwzGcyHTpypSDUT8bYWYraucW7pFiaNP99bpfLo7HvQ1Pu2OBodcnDmQaQsV0p2ZzshwvhdCPn9svq6eWR4y03bMiaUdK9QeS4W8kT7HlpZPrbfiSA+4/lSA2ahrlrGV7K/r0dISrunoTfFExGuhay9GJCgU/SaruVS1hEeCxjWgUySKxpUZHEhTGOI30j4m6Lq1GTEc87J529n3/p2o4bFFPTWU2uLvaI4Nsf04tdf2YF9d24IGNRUMMlarFJcv7e3Jd6c7kx1tQTuIyBBX56953eCVTf5ZS6LDCI7daNb8C/orFaPZSKYV0lEWnuAJx9c6ZfYJ6YXPnHYPylERf/+HVzaGXzf1c7oJDzfv/IXGONY0Xm3uUwKuWI1H4FTDah5U88Fa/Pu89dJcM45D11ac8qJ8RISRt/vYDvZw/aG+LDe1VKdPCF4Mc6mLEQYaWivN1GZ9wEyvybxHmcfLvR5+FPE483pM77QKHC2EzsU006XUx5muywmD0qoonNu2btm8ccPaNdXS8mVLerrT8Y5oJBQwDVSTOuh+F+6hoUhyVSmRkbNv/ucb9zTyQsaHxCLqnnSMNF3h8rASLTbtSxUJifY+Ak9MfpQ/+soj6kn4i9fc+NVrljqjm6+7ATAk1gw+NA4VEmdyKxux9bukFUrmBrs8nr7xg+N9Hs9tA3OJAhz66Msf48e/+Oht7+7bHLTxaqIP/jC+fX1yxbrqinQ7N9P4Y1YLCfa/AQUT96AAAHicY2BkYGAAYu14F754fpuvDNzML4AiDDc3HWyC0f+//k9iqWBOB3I5GJhAogBW9Q1VAAB4nGNgZGBgjvxfyMDAUvb/6//PLBUMQBEUYAAAo0gG23icTY3REYAgDEN7wAJMwjws4gBOwr+bOAkO4CfGppTTj3e5JJDGLhKz0j5SUWXegFg9pz/wpAKQcIoQy5Wwq1aM+Ra3bdKzr95ze/Pdrt1Fnb3R3S/qUgz7n/835tYLSYU3FQAAAAAAAEoAzgESAWwB8gKkAwYDyARKBIAE6gVkBrYG7AcgB1YIJghuDHIMsA00DXwNuA6uDzAPqhASEHQRKBH+Eo4TLBOKE/AUYBUSFaQWPhaqFwAXjBf2GE4YkBk2Gf4aqQAAAAEAAAAwAfgACwAAAAAAAgAsADwAcwAAAKoLcAAAAAB4nHWQy07CQBSG/5GLCokaTdw6KwMxlkviAhISEgxsdEMMW1NKaUtKh0wHEl7Dd/BhfAmfxZ92MAZim+l855szZ04HwDW+IZA/Txw5C5wxyvkEp+hZLtA/Wy6SXyyXUMWb5TL9u+UKHhBYruIGH6wgiueMFvi0LHAlLi2f4ELcWS7QP1ouknuWS7gVr5bL9J7lCiYitVzFvfgaqNVWR0FoZG1Ql+1mqyOnW6moosSNpbs2odKp7Mu5Sowfx8rx1HLPYz9Yx67eh/t54us0UolsOc29GvmJr13jz3bV003QNmYu51ot5dBmyJVWC98zTmjMqtto/D0PAyissIVGxKsKYSBRo61zbqOJFjqkKTMkM/OsCAlcxDQu1twRZisp4z7HnFFC6zMjJjvw+F0e+TEp4P6YVfTR6mE8Ie3OiDIv2ZfD7g6zRqQky3QzO/vtPcWGp7VpDXftutRZVxLDgxqS97FbW9B49E52K4a2iwbff/7vB+x4hFUAeJxtUGmz1CAQTL9NyOHu877v+0SfpX+IkNkEHwHkcF1/vSRbfnNqauihm6keirPiFF3x/7jAGTYoUYGhRoMWHa5gix3OcRXXcB03cBO3cBt3cBf3cB8P8BCP8BhP8BTP8Bwv8BKv8Bpv8Bbv8B4f8BGfwPEZX3CBrwWTwkjSLDltxVCGKHy3FE6zi8faUzwQxZqOxO1+zwIJL6eNtCPTdrQptoM9GG4dGSZiFHKqnZIxeap+qYFs59U4xZVvNe1PqE5uPc970pp7Zcac3Jbaystq1LanqvcpTG2eSCYqa0qnU2Bi+JFCLGlQkeXnUumNU4YdfMZTKyfhI++Fr/9YO3NlqpDZb2v93mhlLjn9jtt/gAsdy5lMamah9NJVo0gj7aSd83U8fUCzWszkdnHAw88kPA2VJ6ePVZiVpt3iel1nkTVZrlxQIevFkUvlpaZhF6c094HnvbOm65WxMmnhQ5sCeb6MLoq/SB+SJXicY/DewXAiKGIjI2Nf5AbGnRwMHAzJBRsZWJ02MTAyaIEYm7mYGDkgLD4GMIvNaRfTAaA0J5DN7rSLwQHCZmZw2ajC2BEYscGhI2Ijc4rLRjUQbxdHAwMji0NHckgESEkkEGzmYWLk0drB+L91A0vvRiYGFwAMdiP0AAA=') format('woff'), - url('data:application/octet-stream;base64,AAEAAAAPAIAAAwBwR1NVQiCLJXoAAAD8AAAAVE9TLzI+L1N4AAABUAAAAFZjbWFw7+BT3QAAAagAAAR0Y3Z0IAb//vQAAEH4AAAAIGZwZ22KkZBZAABCGAAAC3BnYXNwAAAAEAAAQfAAAAAIZ2x5ZksCilIAAAYcAAA1UmhlYWQW8cayAAA7cAAAADZoaGVhB8kECwAAO6gAAAAkaG10eKy//94AADvMAAAAwGxvY2FHzDggAAA8jAAAAGJtYXhwAYUNpgAAPPAAAAAgbmFtZcydHyEAAD0QAAACzXBvc3TWuIl7AAA/4AAAAg1wcmVw5UErvAAATYgAAACGAAEAAAAKADAAPgACREZMVAAObGF0bgAaAAQAAAAAAAAAAQAAAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAEDmQGQAAUAAAJ6ArwAAACMAnoCvAAAAeAAMQECAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAQOgA8jQDWf9xAFoDZwCeAAAAAQAAAAAAAAAAAAUAAAADAAAALAAAAAQAAAIwAAEAAAAAASoAAwABAAAALAADAAoAAAIwAAQA/gAAACQAIAAEAAToHOgy6DTwj/DJ8ODw5fDz8P7xEvEY8T7xQfFE8WTx5fI0//8AAOgA6DLoNPCO8Mnw4PDk8PPw/vES8RjxPvFB8UTxZPHl8jT//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAkAFwAXABcAF4AXgBeAGAAYABgAGAAYABgAGAAYABgAGAAAAABAAIAAwAEAAUABgAHAAgACQAKAAsADAANAA4ADwAQABEAEgATABQAFQAWABcAGAAZABoAGwAcAB0AHgAfACAAIQAiACMAJAAlACYAJwAoACkAKgArACwALQAuAC8AAAEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAkQAAAAAAAAALwAA6AAAAOgAAAAAAQAA6AEAAOgBAAAAAgAA6AIAAOgCAAAAAwAA6AMAAOgDAAAABAAA6AQAAOgEAAAABQAA6AUAAOgFAAAABgAA6AYAAOgGAAAABwAA6AcAAOgHAAAACAAA6AgAAOgIAAAACQAA6AkAAOgJAAAACgAA6AoAAOgKAAAACwAA6AsAAOgLAAAADAAA6AwAAOgMAAAADQAA6A0AAOgNAAAADgAA6A4AAOgOAAAADwAA6A8AAOgPAAAAEAAA6BAAAOgQAAAAEQAA6BEAAOgRAAAAEgAA6BIAAOgSAAAAEwAA6BMAAOgTAAAAFAAA6BQAAOgUAAAAFQAA6BUAAOgVAAAAFgAA6BYAAOgWAAAAFwAA6BcAAOgXAAAAGAAA6BgAAOgYAAAAGQAA6BkAAOgZAAAAGgAA6BoAAOgaAAAAGwAA6BsAAOgbAAAAHAAA6BwAAOgcAAAAHQAA6DIAAOgyAAAAHgAA6DQAAOg0AAAAHwAA8I4AAPCOAAAAIAAA8I8AAPCPAAAAIQAA8MkAAPDJAAAAIgAA8OAAAPDgAAAAIwAA8OQAAPDkAAAAJAAA8OUAAPDlAAAAJQAA8PMAAPDzAAAAJgAA8P4AAPD+AAAAJwAA8RIAAPESAAAAKAAA8RgAAPEYAAAAKQAA8T4AAPE+AAAAKgAA8UEAAPFBAAAAKwAA8UQAAPFEAAAALAAA8WQAAPFkAAAALQAA8eUAAPHlAAAALgAA8jQAAPI0AAAALwABAAD/9gLUAo0AJAAeQBsiGRAHBAACAUcDAQIAAm8BAQAAZhQcFBQEBRgrJRQPAQYiLwEHBiIvASY0PwEnJjQ/ATYyHwE3NjIfARYUDwEXFgLUD0wQLBCkpBAsEEwQEKSkEBBMECwQpKQQLBBMDw+kpA93FhBMDw+lpQ8PTBAsEKSkECwQTBAQpKQQEEwPLg+kpA8ABAAA/7gDoQM1AAgAEQApAEAARkBDNQEHBgkAAgIAAkcACQYJbwgBBgcGbwAHAwdvAAQAAgRUBQEDAQEAAgMAYAAEBAJYAAIEAkw9PCMzIyIyJTkYEgoFHSslNCYOAh4BNjc0Jg4CHgE2NxUUBiMhIiYnNTQ2FzMeATsBMjY3MzIWAwYrARUUBgcjIiYnNSMiJj8BNjIfARYCyhQeFAIYGhiNFCASAhYcGEYgFvzLFx4BIBbuDDYjjyI2De4WILYJGI8UD48PFAGPFxMR+goeCvoSJA4WAhIgEgQaDA4WAhIgEgQaibMWICAWsxYgAR8oKB8eAVIW+g8UARYO+iwR+goK+hEAAAAAAQAA/9EDoQNHAB8AHUAaEg8KBAMFAAIBRwACAAJvAQEAAGYdFBcDBRcrARQPARMVFA4BLwEHBiImNTQ3EycmNTQ3JTc2Mh8BBRYDoQ/KMAwVDPv6DBYMATDLDh8BGH4LIAx9ARggAfAMD8X+6QwLEAEHhIQHEgoECAEXxQ8MFQUo/hcX/igFAAIAAP/RA6EDRwAJACkAJ0AkHBkUDg0JCAcGBQMBDAACAUcAAgACbwEBAABmJSQXFhIQAwUUKwE3LwEPARcHNxcTFA8BExUUIyIvAQcGIiY1NDcTJyY1NDclNzYyHwEFFgJ7qutqaeyrKdPT/g/KMBcKDPv6DBYMATDLDh8BGH4LIAx9ARggASmmItXVIqbrb28BsgwPxf7pDBwHhIQHEgoECAEXxQ8MFQUo/hcX/igFAAAAAAIAAP//BDACgwAhAEMAQkA/IgEEBgFHAwEBBwYHAQZtCQEGBAcGBGsIAQIABwECB2AABAAABFQABAQAWAUBAAQATEJAFiElGCEWFSgTCgUdKyUUBichIiYvAS4BMxEjIi4BPwE2Mh8BFhQGByMVITIfARYlFA8BBiIvASY0NjsBNSEiLwEmNDY3ITIWHwEeARURMzIWAsoKCP3pBQYCAwECAWsPFAEIswsgDLIJFg5rAUEJBVkEAWUIsgwgC7MIFg5r/r4JBVkECggCGAQGAgMBAmsOFhIHDAECAwQBDAFPFhsK1gwM1gocFAHWBmwF4g0K1g0N1gobFtYHawUNCgECAwUCCAP+shYAAAAFAAD/ygPoArgACQAaAD4ARABXAFdAVDQbAgAEUwYCAgBSQwIBAlBCKScIAQYGAQRHAAUEBW8AAgABAAIBbQABBgABBmsABgMABgNrAAMDbgAEAAAEVAAEBABYAAAEAExMSxMuGSQUHQcFGislNy4BNzQ3BgcWATQmByIGFRQWMjY1NDYzMjY3FBUGAg8BBiMiJyY1NDcuAScmNDc+ATMyFzc2MzIWHwEWBxYTFAYHExYXFAcGBw4BIzc+ATcmJzceARcWATYrMDgBIoBVXgFqEAtGZBAWEEQwCxDKO+o7HAUKB0QJGVCGMgsLVvyXMjIfBQoDDgskCwEJFVhJnQT6CxYnVNx8KXfIRUFdIzViIAtwTyNqPUM6QYSQAWcLEAFkRQsQEAswRBB1BAFp/lppMgknBgoHKiR4TREqEoOYCjYJBgYUBgEF/v1OgBsBGBleExMkLWBqSgqEaWRAPyRiNhMAAAL///9xA6EDFAAIACEAVEAKHwEBAA4BAwECR0uwIVBYQBYABAAAAQQAYAABAAMCAQNgAAICDQJJG0AdAAIDAnAABAAAAQQAYAABAwMBVAABAQNYAAMBA0xZtxcjFBMSBQUZKwE0LgEGFBY+AQEUBiIvAQYjIi4CPgQeAhcUBxcWAoOS0JKS0JIBHiw6FL9ke1CSaEACPGyOpI5sPAFFvxUBiWeSApbKmAaM/podKhW/RT5qkKKObjoEQmaWTXtkvxUAAAACAAD/uANaAxIACABqAEVAQmVZTEEEAAQ7CgIBADQoGxAEAwEDRwAFBAVvBgEEAARvAAABAG8AAQMBbwADAgNvAAICZlxbU1FJSCsqIiATEgcFFisBNCYiDgEWMjYlFRQGDwEGBxYXFhQHDgEnIi8BBgcGBwYrASImNScmJwcGIicmJyY0Nz4BNyYvAS4BJzU0Nj8BNjcmJyY0Nz4BMzIfATY3Njc2OwEyFh8BFhc3NjIXFhcWFAcOAQcWHwEeAQI7UnhSAlZ0VgEcCAdoCgsTKAYFD1ANBwdNGRoJBwQQfAgMEBsXTwYQBkYWBAUIKAoPCGYHCAEKBWgIDhclBgUPUA0HCE0YGgkIAxF8BwwBDxwXTwUPB0gUBAQJKAoPCGYHCgFlO1RUdlRUeHwHDAEQHhUbMgYOBhVQAQU8DQhMHBAKB2cJDDwFBkAeBQ4GDDIPHBsPAQwHfAcMARAZGiAtBwwHFFAFPA0ITBwQCgdnCQs7BQVDHAUOBgwyDxwaEAEMAAAAAgAAAAADawLKACcAQABCQD8UAQIBAUcABgIFAgYFbQAFAwIFA2sABAMAAwQAbQABAAIGAQJgAAMEAANUAAMDAFgAAAMATBYjGSUqJScHBRsrJRQWDwEOAQcjIiY1ETQ2OwEyFhUXFg8BDgEnIyIGBxEUFhczMh4CARQHAQYiJj0BIyImPQE0NjczNTQ2FhcBFgFlAgECAQgIskNeXkOyCAoBAQECAQgIsiU0ATYktAYCBgICBgv+0QscFvoOFhYO+hYcCwEvCzUCEgUOCQIDXkMBiENeCggLCQYNBwgBNCb+eCU0AQQCCAEsDgv+0AoUD6EWDtYPFAGhDhYCCf7QCgAAAAABAAD/7gO2AjAAFAAZQBYNAQABAUcCAQEAAW8AAABmFBcSAwUXKwkBBiInASY0PwE2MhcJATYyHwEWFAOr/mIKHgr+YgsLXQoeCgEoASgLHAxcCwGW/mMLCwGdCx4KXAsL/tgBKAsLXAscAAAB//7/ewO4A2cAMQAfQBwAAQAAAVQAAQEAWAIBAAEATAEAKikAMQExAwUUKxciJy4BNwE2Fx4BFxYHAQ4BJyY2NwE2FgcBBhcWNzY3ATYmJyYHAQYeAjcBNhYHAQb0ZkRIBFYB8FBeLEYMGlD+JihgIB4GLAFMGDQa/rQsGAwMGBYB2jIgPDY2/hJCBGSGSgHwGDQa/hBShUhGwF4B8FAaDEYsYFD+JigKIBhkKgFOGjQY/rQsGggCBBYB2jJ2EA4y/hJMhmIEQAHuGC4a/hBSAAAAAAT///+4BC8DEgAIAA8AHwAvAFVAUh0UAgEDDwEAAQ4NDAkEAgAcFQIEAgRHAAIABAACBG0ABgcBAwEGA2AAAQAAAgEAYAAEBQUEVAAEBAVYAAUEBUwREC4rJiMZFxAfER8TExIIBRcrARQOASY0Nh4BARUhNTcXASUhIgYHERQWNyEyNicRNCYXERQGByEiJjcRNDY3ITIWAWU+Wj4+Wj4CPPzusloBHQEe/IMHCgEMBgN9BwwBClE0JfyDJDYBNCUDfSU0AhgtPgJCVkIEOv76+muzWQEdoQoI/VoHDAEKCAKmCAoS/VolNAE2JAKmJTQBNgAL////cQQvAxIADwAfAC8APwBPAF8AbwB/AI8AnwCvAMRAGZBAAgkIiIBgIAQFBHg4AgMCUDAAAwEABEdLsCFQWEA3ABUSDAIICRUIYBMBCRABBAUJBGARDQIFDgYCAgMFAmAPAQMKAQABAwBgCwcCAQEUWAAUFA0USRtAPgAVEgwCCAkVCGATAQkQAQQFCQRgEQ0CBQ4GAgIDBQJgDwEDCgEAAQMAYAsHAgEUFAFUCwcCAQEUWAAUARRMWUAmrqumo56blpSOjIaEfnx2c25rZmReW1ZUTks1NTUmNSY1NTMWBR0rFzU0JgcjIgYdARQWOwEyNic1NCYrASIGHQEUFjczMjYnNTQmJyMiBh0BFBYXMzI2ARE0JiMhIgYXERQWMyEyNgE1NCYHIyIGHQEUFjsBMjYBNTQmByMiBgcVFBY7ATI2AxE0JgchIgYXERQWFyEyNhc1NCYrASIGBxUUFjczMjY3NTQmJyMiBgcVFBYXMzI2NzU0JgcjIgYHFRQWOwEyNjcRFAYjISImNxE0NjchMhbWFA9IDhYWDkgOFgEUD0gOFhYOSA4WARQPSA4WFg5IDhYCOxYO/lMOFgEUDwGtDxT9xRQPSA4WFg5IDhYDERYORw8UARYORw8U1RYO/lMOFgEUDwGtDxTXFg5HDxQBFg5HDxQBFg5HDxQBFg5HDxQBFg5HDxQBFg5HDxRINCX8gyQ2ATQlA30lNCRIDhYBFA9IDhYW5EgOFhYOSA4WARTmRw8UARYORw8UARb+YQEeDhYWDv7iDhYWApFHDxYBFBBHDhYW/YtIDhYBFA9IDhYWAbsBHQ8WARQQ/uMPFAEWyUgOFhYOSA4WARTmRw8UARYORw8UARbkRw8WARQQRw4WFmf9EiU0NCUC7iU0ATYAAQAA/8cCdANLABQAF0AUCQEAAQFHAAEAAW8AAABmHBICBRYrCQEGIi8BJjQ3CQEmND8BNjIXARYUAmr+YgscC10LCwEo/tgLC10KHgoBngoBcP5hCgpdCxwLASkBKAscC10LC/5iCxwAAAAAAQAA/8cCmANLABQAF0AUAQEAAQFHAAEAAW8AAABmFxcCBRYrCQIWFA8BBiInASY0NwE2Mh8BFhQCjv7XASkKCl0LHAv+YgsLAZ4KHgpdCgKx/tj+1woeCl0KCgGfCh4KAZ4LC10KHgABAAAAAAO2Ak0AFAAZQBYFAQACAUcAAgACbwEBAABmFxQSAwUXKyUHBiInCQEGIi8BJjQ3ATYyFwEWFAOrXAseCv7Y/tgLHAtdCwsBngscCwGeC3JcCgoBKf7XCgpcCx4KAZ4KCv5iCxwAAAAEAAD/dQPAA1kAKgA0AD0ATgC3QBE2NAIEAB0OAgEEAkdMAQEBRkuwGlBYQCkFAQQAAQAEAW0DAQEGAAEGawAGBwAGB2sIAQAADEgABwcCWAACAg0CSRtLsCRQWEAmBQEEAAEABAFtAwEBBgABBmsABgcABgdrAAcAAgcCXAgBAAAMAEkbQCcIAQAEAG8FAQQBBG8DAQEGAW8ABgcGbwAHAgIHVAAHBwJYAAIHAkxZWUAXAQBKSERDOjkwLxsZFhUSEAAqASoJBRQrASIGFRQXBgcOARUUBwYHFBY7ARQeATI+ATUzMjY1JicmNTQmJyYnNjU0JgUGBwYVMzQ3NjclBx4BBzM2JyYBMhYVFBYzMhYUBiMiJjU0NgHyFiAFRzgzOjoqTSod+SZBTkEm+R0qTSs6OTQ3RwQf/rU7Hh1HFhgxAjkwMi4BRwEdHv43BAUvIQQFBQQoOgUDWR8WCgwLJyRpNrV9W0EdKidCJiZCJyodQVt9tTZpJCcLDggWHy02SERRRDY4LTQ0LW5EUEVH/RgFBCEvBQgFOigEBQAAAAIAAAAAAoMDEgAHAB8AKkAnBQMCAAECAQACbQACAm4ABAEBBFQABAQBWAABBAFMIxMlNhMQBgUaKxMhNTQmDgEXBREUBgchIiYnETQ2FzM1NDYyFgcVMzIWswEdVHZUAQHQIBb96RceASAWEZTMlgISFx4BrGw7VAJQPaH+vhYeASAVAUIWIAFsZpSUZmweAAP//f+4A1kDEgAMAb0B9wJ3S7AJUFhBPAC9ALsAuACfAJYAiAAGAAMAAACPAAEAAgADANoA0wBtAFkAUQBCAD4AMwAgABkACgAHAAIBngGYAZYBjAGLAXoBdQFlAWMBAwDhAOAADAAGAAcBUwFNASgAAwAIAAYB9AHbAdEBywHAAb4BOAEzAAgAAQAIAAYARxtLsApQWEFDALsAuACfAIgABAAFAAAAvQABAAMABQCPAAEAAgADANoA0wBtAFkAUQBCAD4AMwAgABkACgAHAAIBngGYAZYBjAGLAXoBdQFlAWMBAwDhAOAADAAGAAcBUwFNASgAAwAIAAYB9AHbAdEBywHAAb4BOAEzAAgAAQAIAAcARwCWAAEABQABAEYbQTwAvQC7ALgAnwCWAIgABgADAAAAjwABAAIAAwDaANMAbQBZAFEAQgA+ADMAIAAZAAoABwACAZ4BmAGWAYwBiwF6AXUBZQFjAQMA4QDgAAwABgAHAVMBTQEoAAMACAAGAfQB2wHRAcsBwAG+ATgBMwAIAAEACAAGAEdZWUuwCVBYQDUAAgMHAwIHbQAHBgMHBmsABggDBghrAAgBAwgBawABAW4JAQADAwBUCQEAAANYBQQCAwADTBtLsApQWEA6BAEDBQIFA2UAAgcFAgdrAAcGBQcGawAGCAUGCGsACAEFCAFrAAEBbgkBAAUFAFQJAQAABVYABQAFShtANQACAwcDAgdtAAcGAwcGawAGCAMGCGsACAEDCAFrAAEBbgkBAAMDAFQJAQAAA1gFBAIDAANMWVlBGQABAAAB2AHWAbkBtwFXAVYAxwDFALUAtACxAK4AeQB2AAcABgAAAAwAAQAMAAoABQAUKwEyHgEUDgEiLgI+AQEOAQcyPgE1PgE3NhcmNj8BNj8BBiY1FAc0JgY1LgQvASY0LwEHBhQqARQiBiIHNicmIzYmJzMuAicuAQcGFB8BFgYeAQcGDwEGFhcWFAYiDwEGJicmJyYHJicmBzImBz4BIzY/ATYnFj8BNjc2MhYzFjQnMicmJyYHBhciDwEGLwEmJyIHNiYjNicmIg8BBh4BMhcWByIGIgYWBy4BJxYnIyIGIicmNzQXJwYHMjY/ATYXNxcmBwYHFgcnLgEnIgcGBx4CFDcWBzIXFhcWBycmBhYzIg8BBh8BBhY3Bh8DHgIXBhYHIgY1HgIUFjc2Jy4CNTMyHwEGHgIzHgEHMh4EHwMWMj8BNhYXFjciHwEeARUeARc2NQYWMzY1Bi8BJjQmNhcyNi4CJwYmJxQGFSM2ND8BNi8BJgciBw4DJicuATQ/ATYnNj8BNjsBMjQ2JiMWNhcWNycmNxY3HgIfARY2NxYXHgE+ASY1JzUuATY3NDY/ATYnMjcnJiI3Nic+ATMWNic+ATcWNiY+ARU3NiMWNzYnNiYnMzI1NicmAzY3JiIvATYmLwEmLwEmDwEiDwEVJiciLgEOAQ8BJjYmBg8BBjYGFQ4BFS4BNx4BFxYHBgcGFxQGFgGtdMZycsboyG4GerwBEwIIAwECBAMRFRMKAQwCCAYDAQcGBAQKBQYEAQgBAgEDAwQEBAQGAQYCCAkFBAYCBAMBCAwBBRwEAwICAQgBDgECBwkDBAQBBAIDAQcKAgQFDQMDFA4TBAgGAQIBAgUJAgETCQYEAgUGCgMIBAcFAgMGCQQGAQUJBAUDAwIFBAEOBwsPBBADAwEIBAgBCAMBCAQDAgIDBAIEEgUDDAwBAwMCDBkbAwYFBRMFAwsEDQsBBAIGBAgECQRRMgQFAgYFAwEYCgECBwUEAwQEBAECAQEBAgoHBxIEBwkEAwgEAg4BAQICDgIEAgIPCAMEAwIDBQEECgoBBAgEBQwHAgMIAwkHFgYGBQgIEAQUCgECBAIGAw4DBAEKBQgRCgICAgIBBQIEAQoCAwwDAggBAggDAQMCBwsEAQICCBQDCAoBAgEEAgMFAgEDAgEDAQQYAwkDAQEBAw0CDgQCAwEEAwUCBggEAgIBCAQEBwgFBwwEBAICAgYBBQQDAgMFDAQCEgEEAgIFDgkCAgoIBQkCBgYHBQkMCmlzUAEMAQ0BBAMVAQMFAgMCAgEFDAgDBgYGBgEBBAgECgEHBgIKAgQBDAEBAgIECw8BAgkKAQMSdMTqxHR0xOrEdP7dAQgCBgYBBAgDBQsBDAEDAgIMAQoHAgMEAgQBAgYMBQYDAwIEAQEDAwQCBAEDAwICCAQCBgQBAwQBBAQGBwMIBwoHBAUGBQwDAQIEAgEDDAkOAwQFBwgFAxECAw4IBQwDAQMJCQYEAwYBDgQKBAECBQICBgoEBwcHAQkFCAcIAwIHAwIEAgYCBAUKAwMOAgUCAgUEBwIBCggPAgMDBwMCDgMCAwQGBAYEBAEBLU8EAQgEAwQGDwoCBgQFBAUOCRQLAgEGGgIBFwUEBgMFFAMDEAUCAQQIBQgEAQsYDQUMAgIEBAwIDgQOAQoLFAcIAQUDDQIBAgESAwoEBAkFBgIDCgMCAwUMAhAIEgMDBAQGAgQKBw4BBQIEAQQCAhAFDwUCBQMCCwIIBAQCAgQYDgkOBQkBBAYBAgMCAQQDBgcGBQIPCgEEAQIDAQIDCAUXBAIICAMFDgIKCgUBAgMECwkFAgICAgYCCgYKBAQEAwEECgQGAQcCAQcGBQQCAwEFBAL+DRVVAgIFBAYCDwEBAgECAQEDAgoDBgICBQYHAw4GAgEFBAIIAQIIAgICAgUcCBEJDgkMAgQQBwACAAD/pQOPAyQADAAXACJAHxQBAQIRBQIAAQJHAAIBAm8AAQABbwAAAGYbFiIDBRcrJRQGJyInPgEnNDYyFgEWFAcBLgEnATYyAdCue1FERFIBWHpYAZ4gIf7CFFI4AT4gXtF8sAEoJ4pSPVhYAfUgXiD+wjdUFAE+IAAAA//1/7gD8wNZAA8AIQAzAGRADBsRAgMCCQECAQACR0uwJFBYQB0AAgUDBQIDbQADAAABAwBgAAEABAEEXAAFBQwFSRtAIgAFAgVvAAIDAm8AAwAAAQMAYAABBAQBVAABAQRYAAQBBExZQAkXOCcnJiMGBRorJTU0JisBIgYdARQWFzMyNicTNCcmKwEiBwYVFxQWNzMyNgMBFgcOAQchIiYnJjcBPgEyFgI7CgdsBwoKB2wHCgEKBQcHegYIBQkMB2cIDAgBrBQVCSIS/KYSIgkVFAGtCSImIlpqCAoKCGoICgEM1wEBBgQGBgQI/wUIAQYCEPzuIyMREgEUECMjAxIRFBQAAAAAAQAAAAADEgMSACMAKUAmAAQDBG8AAQABcAUBAwAAA1QFAQMDAFgCAQADAEwjMyUjMyMGBRorARUUBicjFRQGByMiJjc1IyImJzU0NjczNTQ2OwEyFhcVMzIWAxIgFuggFmsWIAHoFx4BIBboHhdrFx4B6BceAb5rFiAB6RYeASAV6R4XaxceAegWICAW6CAAAv/9/7gDXwMSAAcAFAArQCgAAwAAAQMAYAQBAQICAVQEAQEBAlgAAgECTAAAEhEMCwAHAAcRBQUVKyURIg4CHgEBFA4BIi4CPgEyHgEBrVOMUAJUiAIBcsboyG4Gerz0un41AmBSjKSMUgEwdcR0dMTqxHR0xAAABQAAAAAD5AMSAAYADwA5AD4ASAEHQBVAPjsQAwIBBwAENAEBAAJHQQEEAUZLsApQWEAwAAcDBAMHBG0AAAQBAQBlAAMABAADBGAIAQEABgUBBl8ABQICBVQABQUCWAACBQJMG0uwC1BYQCkAAAQBAQBlBwEDAAQAAwRgCAEBAAYFAQZfAAUCAgVUAAUFAlgAAgUCTBtLsBhQWEAwAAcDBAMHBG0AAAQBAQBlAAMABAADBGAIAQEABgUBBl8ABQICBVQABQUCWAACBQJMG0AxAAcDBAMHBG0AAAQBBAABbQADAAQAAwRgCAEBAAYFAQZfAAUCAgVUAAUFAlgAAgUCTFlZWUAWAABEQz08MS4pJh4bFhMABgAGFAkFFSslNycHFTMVASYPAQYWPwE2ExUUBiMhIiY1ETQ2NyEyFx4BDwEGJyYjISIGBxEUFhchMjY9ATQ/ATYWAxcBIzUBByc3NjIfARYUAfBAVUA1ARUJCcQJEgnECSReQ/4wQ15eQwHQIx4JAwcbCAoNDP4wJTQBNiQB0CU0BSQIGDeh/omhAm8zoTMQLBBVEMRBVUEfNgGSCQnECRIJxAn+vmpDXl5DAdBCXgEOBBMGHAgEAzQl/jAlNAE2JEYHBSQICAGPoP6JoAEuNKE0Dw9VECwABAAA/7gDTQMGAAYAFAAZACQAhkAXHgECBR0WDgcEAwIZAwIDAAMBAQEABEdLsBJQWEAnAAUCBW8AAgMCbwADAANvAAABAQBjBgEBBAQBUgYBAQEEVwAEAQRLG0AmAAUCBW8AAgMCbwADAANvAAABAG8GAQEEBAFSBgEBAQRXAAQBBEtZQBIAACEgGBcQDwkIAAYABhQHBRUrMzcnBxUzFQE0IyIHAQYVFDMyNwE2JxcBIzUBFA8BJzc2Mh8BFssygzNIAV8MBQT+0QQNBQQBLwMe6P4w6ANNFF3oXRQ7FoMUM4MzPEcCBgwE/tIEBgwEAS4Ecej+L+kBmh0VXelcFRWDFgACAAD/cQKDAxIACwAuAGO2BwECAQABR0uwIVBYQBsABwgGAgABBwBgCQUCAQQBAgMBAmAAAwMNA0kbQCQAAwIDcAAHCAYCAAEHAGAJBQIBAgIBVAkFAgEBAlgEAQIBAkxZQA4tLBMzERQiMxUVEwoFHSsBNTQmIgYdARQWMjYFFAYnIwMOAQcjIicDIyImJzQ2MxEiLgE2NyEyFhQGJxEyFgEMChAKChAKAXcWDu8dAQoGAQ8CK+EPFAFYNx0qAi4bAWUdKiodN1gBd/oICgoI+ggKCr0OFgH+8gcIAQ8BDxQPRW4BHio6KgEsOCwB/uJuAAAAAwAA/30DoAMSAAgAFAAuADNAMCYBBAMoJxIDAgQAAQEAA0cAAwQDbwAEAgRvAAIAAm8AAAEAbwABAWYcIy0YEgUFGSs3NCYOAh4BNiUBBiIvASY0NwEeASUUBw4BJyImNDY3MhYXFhQPARUXNj8BNjIW1hQeFAIYGhgBZv6DFToWOxUVAXwWVAGZDRuCT2iSkmggRhkJCaNsAipLIQ8KJA4WAhIgEgQa9v6DFBQ9FDsWAXw3VN0WJUteAZLQkAIUEAYSB159PAIZLRQKAAAAAAUAAP+4BHcDEgADAAcADQARABUAZkBjAAUKBW8PAQoDCm8MAQMIA28OAQgBCG8LAQEAAW8JBwIDAAYAbw0BBgQEBlINAQYGBFYABAYEShISDg4ICAQEAAASFRIVFBMOEQ4REA8IDQgNDAsKCQQHBAcGBQADAAMREAUVKwERIxEBESMRARUhETMRAREjESURIxEBZY8BZY4CyvuJRwLLjwFljwFl/uIBHgEe/cQCPP19SANa/O4B9P5TAa3W/X0CgwAAAAAD////cQOhAxQAIwAsAEUAoUAaHxgCAwQTEgEDAAMNBgIBAEMBBwEyAQkHBUdLsCFQWEAwAAQGAwYEA20AAQAHAAEHbQAKAAYECgZgBQEDAgEAAQMAYAAHAAkIBwlgAAgIDQhJG0A3AAQGAwYEA20AAQAHAAEHbQAICQhwAAoABgQKBmAFAQMCAQABAwBgAAcJCQdUAAcHCVgACQcJTFlAED08NTMUExUUIyYUIyMLBR0rARUUBicjFRQGJyMiJjc1IyImJzU0NjsBNTQ2OwEyFhcVMzIWFzQuAQYUFj4BARQGIi8BBiMiLgI+BB4CFxQHFxYCOwoHfQwGJAcMAX0HCgEMBn0KCCQHCgF9BwpIktCSktCSAR4qPBS/ZHtQkmhAAjxsjqSObDwBRb8VAZskBwwBfQcMAQoIfQoIJAcKfQgKCgh9ChlnkgKWypgGjP6aHSoVv0U+apCijm46BEJmlk17ZL8VAAAC//3/cQPrA1kAJwBQALBADiQWBgMBAkxCNAMEAwJHS7AhUFhAJgABAgMCAQNtBwEDBAIDBGsAAgIAWAYBAAAMSAAEBAVYAAUFDQVJG0uwJFBYQCMAAQIDAgEDbQcBAwQCAwRrAAQABQQFXAACAgBYBgEAAAwCSRtAKQABAgMCAQNtBwEDBAIDBGsGAQAAAgEAAmAABAUFBFQABAQFWAAFBAVMWVlAFykoAQBHRTEvKFApUBQSDAoAJwEnCAUUKwEiBwYHBgcUFh8BMzI1Njc2NzYzMhYXBwYWHwEWPgEvAS4BDwEmJyYBIhUGBwYHBiMiJyYnNzYmLwEmDgEfAR4BPwEWFxYzMjc2NzY3NCYvAQHug3FtQ0UFBQQEVBMFNTNTV2NPjjQ6CQIM9wsUCgQ6AhIJQURaXAEzEwU1M1NWY1BIRTU7CAIL+AsUCgQ6AhIKQERaXWaCcW5CRQUFBAQDWUA+a26BCAkCARJiU1EvMT44OQkTAzIDCRYQ4wgLBjxGJij+BBJiU1EvMSAeODkJEwMyAwkWEOMICwY8RiYoQD5rboIICAIBAAAAAAL///9iA+oDWQAfAEEASUAKBAECAAFHMQEBREuwJFBYQBMAAgABAAIBbQABAW4DAQAADABJG0APAwEAAgBvAAIBAm8AAQFmWUANAQAhIBQTAB8BHwQFFCsBIgcGBzE2NzYXFhcWFxYGBwYXHgE3PgE3NiYnLgEnJgEiBwYHBgcGFhcWFxYXFjc2NzEGBwYnJicmJyY2NzYmJyYB8ldRVERWbGpnak9CISEGJQ4aEDMRAwoCIwElJpBeW/4FGA8EBAYBJAIkJkhbe3d5fWFWbGpna09CISAFJQgGDhIDWR0eOUUVFB4gT0JWU7NRKRsQAREDDwZaw1ldkCYl/u4QBAYIBlrDWV1IWyQiGBlRRRUUHiBPQlZTs1EVIQ4SAAAAAAIAAAAAA+gDWQAnAD8AfUATKAEBBhEBAgE3LgIEAiEBBQQER0uwJFBYQCQABAIFAgQFbQAFAwIFA2sAAQACBAECYAADAAADAFwABgYMBkkbQCwABgEGbwAEAgUCBAVtAAUDAgUDawABAAIEAQJgAAMAAANUAAMDAFgAAAMATFlACjobJTU2JTMHBRsrARUUBiMhIiY1ETQ2NyEyFh0BFAYjISIGBxEUFhchMjY9ATQ2OwEyFhMRFA4BLwEBBiIvASY0NwEnJjQ2MyEyFgMSXkP+MENeXkMBiQcKCgf+dyU0ATYkAdAlNAoIJAgK1hYcC2L+lAUQBEAGBgFsYgsWDgEdDxQBU7JDXl5DAdBCXgEKCCQICjQl/jAlNAE2JLIICgoB2v7jDxQCDGL+lAYGQAUOBgFsYgscFhYAAAACAAD/uANZAxIAGAAoADJALxIJAgIAAUcAAgABAAIBbQAEAAACBABgAAEDAwFUAAEBA1gAAwEDTDU3FBkzBQUZKwERNCYnISIGHwEBBhQfARYyNwEXFjMyNzYTERQGByEiJjURNDY3ITIWAsoUD/70GBMSUP7WCws5CxwLASpRCg8GCBWPXkP96UNeXkMCF0NeAVMBDA8UAS0QUP7WCx4KOQoKASpQCwMKATX96EJeAWBBAhhCXgFgAAAAAAMAAAAAA1oCywAPAB8ALwA3QDQoAQQFCAACAAECRwAFAAQDBQRgAAMAAgEDAmAAAQAAAVQAAQEAWAAAAQBMJjUmNSYzBgUaKyUVFAYHISImJzU0NjchMhYDFRQGJyEiJic1NDYXITIWAxUUBiMhIiYnNTQ2FyEyFgNZFBD87w8UARYOAxEPFgEUEPzvDxQBFg4DEQ8WARQQ/O8PFAEWDgMRDxZrRw8UARYORw8UARYBEEgOFgEUD0gOFgEUAQ5HDhYWDkcPFgEUAAAAAAL///+4A+kCygAZADgALUAqCQACAgMBRwADAgNvAAIBAm8AAQAAAVQAAQEAWAAAAQBMNzQmJDozBAUWKwERFAYHISImNxEWFxYXHgI3MzI+ATc2NzY3FAYHBg8BDgInIyImLwEuAS8BJicuASc0NjMhMhYD6DQl/MokNgEZH8pMICZEGwIcQigfX7cgGDYp0jQ1DCIeDQIMHhEeDSIGk2ASIzwBLisDNiQ2Ac3+RSU0ATYkAbsbFok3GBocARocF0R8Fr8sUB2SIycJEgwBCgoSCBwDZUIOF1IkKzo0AAAABwAA/7gD6ALKAAgAEQAjACwANQA+AFAAZEBhLQECBjYJAgMHJAACAQADRwgBAgYHBgIHbQAHAwYHA2sJAQMABgMAawQBAAEGAAFrAAsABgILBmAFAQEKCgFUBQEBAQpYAAoBCkxNTEVCPTw5ODQzMC8rKicmExQTEgwFGCs3NCYiBh4CNhM0JiIOAR4BNhc3Ni4BBg8BDgEHBh4BNjc2JiU0JiIOAR4BNgE0JiIOAR4BNhc0JiIOAR4BNhcUBwYjISInJjU0PgIyHgLWKjosAig+Jm0oPiYELjYw6zkDEBocAzghNggLLFhKDQkaAVYqPCgCLDgu/pgoPiYELjYw9ig+JgQuNjCvTwoU/PIUCk9QhLzIvIRQ1h4qKjwoAiwBFh4qKjwoAizw1Q4aBgwQ1QMsIStMGC4rIUAlHioqPCgCLAGBHioqPCgCLE8eKio8KAIs3pF8ERF7kma4iE5OiLgAAAACAAD/cQPoAsoAFwA9AGJADDQIAgEAJgsCAwICR0uwIVBYQBcABAUBAAEEAGAAAQACAwECYAADAw0DSRtAHgADAgNwAAQFAQABBABgAAECAgFUAAEBAlgAAgECTFlAEQEAOzokIh0bEhAAFwEXBgUUKwEiDgEHFBYfAQcGBzY/ARcWMzI+Ai4BARQOASMiJwYHBgcjIiYnNSY2Jj8BNj8BPgI/AS4BJzQ+ASAeAQH0csZ0AVBJMA8NGlVFGCAmInLGdAJ4wgGAhuaIJypukxskAwgOAgIEAgMMBA0UBxQQBw9YZAGG5gEQ5oYCg06ETD5yKRw1My4kPBUDBU6EmIRO/uJhpGAEYSYIBAwJAQIIBAMPBQ4WCBwcEyoyklRhpGBgpAAAAgAA/3EDxANaAAwANACeQAsaDQIBBgABAgACR0uwIVBYQCcAAQYDBgEDbQUBAwAGAwBrAAACBgACawAGBgxIAAICBFgABAQNBEkbS7AkUFhAJAABBgMGAQNtBQEDAAYDAGsAAAIGAAJrAAIABAIEXAAGBgwGSRtAJQAGAQZvAAEDAW8FAQMAA28AAAIAbwACBAQCVAACAgRYAAQCBExZWUAKHyISIyMTEgcFGysFNCMiJjc0IhUUFjcyJRQGKwEUBiImNSMiJjU+BDc0NjcmNTQ+ARYVFAceARcUHgMB/QkhMAESOigJAccqHfpUdlT6HSocLjAkEgKEaQUgLCAFaoIBFiIwMFkIMCEJCSk6AakdKjtUVDsqHRgyVF6ITVSSEAoLFx4CIhULChCSVE6GYFI0AAACAAD/uANZAxIAIwAzAEFAPg0BAAEfAQQDAkcCAQABAwEAA20FAQMEAQMEawAHAAEABwFgAAQGBgRUAAQEBlgABgQGTDU1IzMWIyQjCAUcKwE1NCYHIzU0JicjIgYHFSMiBgcVFBY3MxUUFjsBMjY3NTMyNhMRFAYHISImNRE0NjchMhYCyhQPsxYORw8UAbIPFAEWDrIWDkcPFAGzDhaOXkP96UNeXkMCF0NeAUFIDhYBsw8UARYOsxQPSA4WAbMOFhYOsxQBP/3oQl4BYEECGEJeAWAAAAABAAD/uAPoAzUAKwApQCYmAQQDAUcAAwQDbwAEAQRvAAECAW8AAgACbwAAAGYjFxM9FwUFGSslFAcOAgcGIiY1NDY3NjU0LgUrARUUBiInASY0NwE2MhYHFTMgFxYD6EcBCgQFBxEKAgEDFCI4PlZWN30UIAn+4wsLAR0LHBgCfQGOWh7oXZ8EEhAECgwIBRQDJh84WkAwHhIGjw4WCwEeCh4KAR4KFA+P4UsABf/9/7gDXwMSABMAHAAlADYAQwBCQD8dFAICAwFHAAkABgMJBmAFAQMEAQIBAwJgAAEAAAcBAGAABwgIB1QABwcIWAAIBwhMQUAXFxYTFBMZGRIKBR0rAQ4BLgEnJj4BFhceATI2Nz4BHgElFAYiJj4CFgUUBiIuAT4BFhc0LgIiDgIeAz4DNxQOASIuAj4BMh4BAnkVcI5yFAQOHBoEDkxeSg8EHBoQ/uYqOiwCKD4mASAqPCgCLDgujTpeho6IXDwCOGCEkoJiNklyxujIbgZ6vPS6fgEBQ1QCUEUOGgkMECw4OCwPDgoa5R4qKjwoAiwcHioqPCgCLKtJhGA4OGCEkoRePAQ0ZnxNdcR0dMTqxHR0xAAAAAEAAAAAAoMDWgAjAGZLsCRQWEAgAAQFAAUEAG0CBgIAAQUAAWsAAQFuAAUFA1gAAwMMBUkbQCUABAUABQQAbQIGAgABBQABawABAW4AAwUFA1QAAwMFWAAFAwVMWUATAQAgHxsYFBMQDgkGACMBIwcFFCsBMhYXERQGByEiJicRNDYXMzU0Nh4BBxQGKwEiJjU0JiIGFxUCTRceASAW/ekXHgEgFhGUzJYCFA8kDhZUdlQBAaweF/6+Fh4BIBUBQhYgAbNnlAKQaQ4WFg47VFQ7swAAAwAAAAADEgH0AA8AHwAvACJAHwUDAgEAAAFUBQMCAQEAWAQCAgABAEw1NTU1NTMGBRorExUUBicjIiYnNTQ2NzMyFgUVFAYnIyImNzU0NjczMhYFFRQGJyMiJj0BNDY3MzIW1h4XaxceASAWaxYgAR0gFmsWIAEeF2sXHgEfIBZrFiAgFmsXHgG+axYgAR4XaxceASAWaxYgAR4XaxceASAWaxYgAR4XaxceASAAAAAC//3/uANZAxIADAAaACZAIwMBAAIAbwACAQECVAACAgFYAAECAUwBABkYBwYADAEMBAUUKwEyHgEUDgEiLgI+AQE2NCclJgYVERQXFjI3Aa10xnJyxujIbgZ6vAFQEhL+0BEkEgkSCAMSdMTqxHR0xOrEdP40CioKsgsVFP6aFAsEBQADAAD/uAN9AxIACAAYAFUATkBLSgEIBx8bAgADAAEBADERAgIBBEcABwgHbwAIAwhvBgEDAANvAAABAG8ABAIEcAABAgIBVAABAQJYBQECAQJMLywVJD8mNRMSCQUdKzc0LgEOAR4BNhMRFAYHIyImJxE0NhczMhYFFAcWFRYHFgcGBxYHBgcjIi4BJyYnIiYnETQ+Ajc2Nz4CNz4DMzIeBAYXFA4BBw4CBzMyFo8WHRQBFh0UWhQQoA8UARYOoA8WApQfCQEZCQkJFgUgJEpIJVYyKkUTDxQBFBs6HCYSCg4GBQQGEBUPGSoYFAgGAgIMCAwBCAQDmytAaw8UARYdFAEWASz+mw8UARYOAWUOFgEUDzAjGRIqIh8jHxU+JysBEg4PGAEWDgFlDhYBQCMxEgoiFBgWGCIWDBIaGCASDRUsFhQEDA4GQAAAAAUAAP9xA+gDWQAQABQAJQAvADkA20AXMykCBwghAQUCHRUNDAQABQNHBAEFAUZLsCFQWEAtBgwDCwQBBwIHAQJtAAIFBwIFawAFAAcFAGsJAQcHCFgKAQgIDEgEAQAADQBJG0uwJFBYQCwGDAMLBAEHAgcBAm0AAgUHAgVrAAUABwUAawQBAABuCQEHBwhYCgEICAwHSRtAMgYMAwsEAQcCBwECbQACBQcCBWsABQAHBQBrBAEAAG4KAQgHBwhUCgEICAdWCQEHCAdKWVlAIBERAAA3NTIxLSsoJyQiHx4bGREUERQTEgAQAA83DQUVKwERFAYHERQGByEiJicREzYzIREjEQERFAYHISImJxEiJicRMzIXJRUjNTQ2OwEyFgUVIzU0NjsBMhYBiRYOFBD+4w8UAYsEDQGfjgI7Fg7+4w8UAQ8UAe0NBP4+xQoIoQgKAXfFCgihCAoCpv5UDxQB/r8PFAEWDgEdAegM/ngBiP4M/uMPFAEWDgFBFg4BrAytfX0ICgoIfX0ICgoAAAADAAD/uAR4AxMACAAsAE8Ad0B0LCUCCgcgHw4DAwIyEwIECANHAAEHAW8ABwoHbw4BAAoNCgANbQALDQINCwJtDAEKAA0LCg1gBgECBQEDCAIDYAAIBAQIVAAICARYCQEECARMAQBNS0pIRURBPzYzMS8pKCQiHBsXFRIQCgkFBAAIAQgPBRQrASImPgEeAgYFMzIWBxUUBisBFRQGByMiJj0BIyImJzU0NjczNTQ2FzMyFhcBFBY3MxUGIyEiJjU0PgUXMhceATI2NzYzMhcjIgYVAYlZfgJ6tngGhAHDxAcMAQoIxAwGawgKxQcKAQwGxQoIawcKAf5lKh2PJjn+GENSBAwSHiY6IQsLLFRkVCwLC0kwfR0qAWV+sIACfLR6SQwGawgKxQcKAQwGxQoIawcKAcQHDAEKCP6/HSwBhRxOQx44QjY4IhoCCiIiIiIKNiodAAAAAAEAAAABAAArX0QOXw889QALA+gAAAAA2bLBggAAAADZssGC//X/YgR4A2cAAAAIAAIAAAAAAAAAAQAAA1n/cQAABHb/9f/zBHgAAQAAAAAAAAAAAAAAAAAAADAD6AAAAxEAAAOgAAADoAAAA6AAAAQvAAAD6AAAA6D//wNZAAADoAAAA+gAAAOr//4EL///BC///wLKAAACygAAA+gAAAPoAAACggAAA1n//QOgAAAD6P/1AxEAAANZ//0D6AAAA1kAAAKCAAADoAAABHYAAAOg//8D6P/9A+n//wPoAAADWQAAA1kAAAPo//8D6AAAA+gAAAPoAAADWQAAA+gAAANZ//0CggAAAxEAAANZ//0DoAAAA+gAAAR2AAAAAAAAAEoAzgESAWwB8gKkAwYDyARKBIAE6gVkBrYG7AcgB1YIJghuDHIMsA00DXwNuA6uDzAPqhASEHQRKBH+Eo4TLBOKE/AUYBUSFaQWPhaqFwAXjBf2GE4YkBk2Gf4aqQAAAAEAAAAwAfgACwAAAAAAAgAsADwAcwAAAKoLcAAAAAAAAAASAN4AAQAAAAAAAAA1AAAAAQAAAAAAAQAIADUAAQAAAAAAAgAHAD0AAQAAAAAAAwAIAEQAAQAAAAAABAAIAEwAAQAAAAAABQALAFQAAQAAAAAABgAIAF8AAQAAAAAACgArAGcAAQAAAAAACwATAJIAAwABBAkAAABqAKUAAwABBAkAAQAQAQ8AAwABBAkAAgAOAR8AAwABBAkAAwAQAS0AAwABBAkABAAQAT0AAwABBAkABQAWAU0AAwABBAkABgAQAWMAAwABBAkACgBWAXMAAwABBAkACwAmAclDb3B5cmlnaHQgKEMpIDIwMTkgYnkgb3JpZ2luYWwgYXV0aG9ycyBAIGZvbnRlbGxvLmNvbWZvbnRlbGxvUmVndWxhcmZvbnRlbGxvZm9udGVsbG9WZXJzaW9uIDEuMGZvbnRlbGxvR2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0Lmh0dHA6Ly9mb250ZWxsby5jb20AQwBvAHAAeQByAGkAZwBoAHQAIAAoAEMAKQAgADIAMAAxADkAIABiAHkAIABvAHIAaQBnAGkAbgBhAGwAIABhAHUAdABoAG8AcgBzACAAQAAgAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAGYAbwBuAHQAZQBsAGwAbwBSAGUAZwB1AGwAYQByAGYAbwBuAHQAZQBsAGwAbwBmAG8AbgB0AGUAbABsAG8AVgBlAHIAcwBpAG8AbgAgADEALgAwAGYAbwBuAHQAZQBsAGwAbwBHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAGgAdAB0AHAAOgAvAC8AZgBvAG4AdABlAGwAbABvAC4AYwBvAG0AAAAAAgAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExAAZjYW5jZWwGdXBsb2FkBHN0YXIKc3Rhci1lbXB0eQdyZXR3ZWV0B2V5ZS1vZmYGc2VhcmNoA2NvZwZsb2dvdXQJZG93bi1vcGVuBmF0dGFjaAdwaWN0dXJlBXZpZGVvCnJpZ2h0LW9wZW4JbGVmdC1vcGVuB3VwLW9wZW4OYmVsbC1yaW5naW5nLW8EbG9jawVnbG9iZQVicnVzaAlhdHRlbnRpb24EcGx1cwZhZGp1c3QEZWRpdAZwZW5jaWwDcGluBndyZW5jaAljaGFydC1iYXIHem9vbS1pbgVzcGluMwVzcGluNAhsaW5rLWV4dAxsaW5rLWV4dC1hbHQEbWVudQhtYWlsLWFsdAVnYXVnZQ1jb21tZW50LWVtcHR5CGJlbGwtYWx0DHBsdXMtc3F1YXJlZAVyZXBseQVzbWlsZQ1sb2NrLW9wZW4tYWx0CGVsbGlwc2lzDHBsYXktY2lyY2xlZA10aHVtYnMtdXAtYWx0CmJpbm9jdWxhcnMJdXNlci1wbHVzAAAAAAAAAQAB//8ADwAAAAAAAAAAAAAAAAAAAAAAGAAYABgAGANn/2IDZ/9isAAsILAAVVhFWSAgS7gADlFLsAZTWliwNBuwKFlgZiCKVViwAiVhuQgACABjYyNiGyEhsABZsABDI0SyAAEAQ2BCLbABLLAgYGYtsAIsIGQgsMBQsAQmWrIoAQpDRWNFUltYISMhG4pYILBQUFghsEBZGyCwOFBYIbA4WVkgsQEKQ0VjRWFksChQWCGxAQpDRWNFILAwUFghsDBZGyCwwFBYIGYgiophILAKUFhgGyCwIFBYIbAKYBsgsDZQWCGwNmAbYFlZWRuwAStZWSOwAFBYZVlZLbADLCBFILAEJWFkILAFQ1BYsAUjQrAGI0IbISFZsAFgLbAELCMhIyEgZLEFYkIgsAYjQrEBCkNFY7EBCkOwAWBFY7ADKiEgsAZDIIogirABK7EwBSWwBCZRWGBQG2FSWVgjWSEgsEBTWLABKxshsEBZI7AAUFhlWS2wBSywB0MrsgACAENgQi2wBiywByNCIyCwACNCYbACYmawAWOwAWCwBSotsAcsICBFILALQ2O4BABiILAAUFiwQGBZZrABY2BEsAFgLbAILLIHCwBDRUIqIbIAAQBDYEItsAkssABDI0SyAAEAQ2BCLbAKLCAgRSCwASsjsABDsAQlYCBFiiNhIGQgsCBQWCGwABuwMFBYsCAbsEBZWSOwAFBYZVmwAyUjYUREsAFgLbALLCAgRSCwASsjsABDsAQlYCBFiiNhIGSwJFBYsAAbsEBZI7AAUFhlWbADJSNhRESwAWAtsAwsILAAI0KyCwoDRVghGyMhWSohLbANLLECAkWwZGFELbAOLLABYCAgsAxDSrAAUFggsAwjQlmwDUNKsABSWCCwDSNCWS2wDywgsBBiZrABYyC4BABjiiNhsA5DYCCKYCCwDiNCIy2wECxLVFixBGREWSSwDWUjeC2wESxLUVhLU1ixBGREWRshWSSwE2UjeC2wEiyxAA9DVVixDw9DsAFhQrAPK1mwAEOwAiVCsQwCJUKxDQIlQrABFiMgsAMlUFixAQBDYLAEJUKKiiCKI2GwDiohI7ABYSCKI2GwDiohG7EBAENgsAIlQrACJWGwDiohWbAMQ0ewDUNHYLACYiCwAFBYsEBgWWawAWMgsAtDY7gEAGIgsABQWLBAYFlmsAFjYLEAABMjRLABQ7AAPrIBAQFDYEItsBMsALEAAkVUWLAPI0IgRbALI0KwCiOwAWBCIGCwAWG1EBABAA4AQkKKYLESBiuwcisbIlktsBQssQATKy2wFSyxARMrLbAWLLECEystsBcssQMTKy2wGCyxBBMrLbAZLLEFEystsBossQYTKy2wGyyxBxMrLbAcLLEIEystsB0ssQkTKy2wHiwAsA0rsQACRVRYsA8jQiBFsAsjQrAKI7ABYEIgYLABYbUQEAEADgBCQopgsRIGK7ByKxsiWS2wHyyxAB4rLbAgLLEBHistsCEssQIeKy2wIiyxAx4rLbAjLLEEHistsCQssQUeKy2wJSyxBh4rLbAmLLEHHistsCcssQgeKy2wKCyxCR4rLbApLCA8sAFgLbAqLCBgsBBgIEMjsAFgQ7ACJWGwAWCwKSohLbArLLAqK7AqKi2wLCwgIEcgILALQ2O4BABiILAAUFiwQGBZZrABY2AjYTgjIIpVWCBHICCwC0NjuAQAYiCwAFBYsEBgWWawAWNgI2E4GyFZLbAtLACxAAJFVFiwARawLCqwARUwGyJZLbAuLACwDSuxAAJFVFiwARawLCqwARUwGyJZLbAvLCA1sAFgLbAwLACwAUVjuAQAYiCwAFBYsEBgWWawAWOwASuwC0NjuAQAYiCwAFBYsEBgWWawAWOwASuwABa0AAAAAABEPiM4sS8BFSotsDEsIDwgRyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsABDYTgtsDIsLhc8LbAzLCA8IEcgsAtDY7gEAGIgsABQWLBAYFlmsAFjYLAAQ2GwAUNjOC2wNCyxAgAWJSAuIEewACNCsAIlSYqKRyNHI2EgWGIbIVmwASNCsjMBARUUKi2wNSywABawBCWwBCVHI0cjYbAJQytlii4jICA8ijgtsDYssAAWsAQlsAQlIC5HI0cjYSCwBCNCsAlDKyCwYFBYILBAUVizAiADIBuzAiYDGllCQiMgsAhDIIojRyNHI2EjRmCwBEOwAmIgsABQWLBAYFlmsAFjYCCwASsgiophILACQ2BkI7ADQ2FkUFiwAkNhG7ADQ2BZsAMlsAJiILAAUFiwQGBZZrABY2EjICCwBCYjRmE4GyOwCENGsAIlsAhDRyNHI2FgILAEQ7ACYiCwAFBYsEBgWWawAWNgIyCwASsjsARDYLABK7AFJWGwBSWwAmIgsABQWLBAYFlmsAFjsAQmYSCwBCVgZCOwAyVgZFBYIRsjIVkjICCwBCYjRmE4WS2wNyywABYgICCwBSYgLkcjRyNhIzw4LbA4LLAAFiCwCCNCICAgRiNHsAErI2E4LbA5LLAAFrADJbACJUcjRyNhsABUWC4gPCMhG7ACJbACJUcjRyNhILAFJbAEJUcjRyNhsAYlsAUlSbACJWG5CAAIAGNjIyBYYhshWWO4BABiILAAUFiwQGBZZrABY2AjLiMgIDyKOCMhWS2wOiywABYgsAhDIC5HI0cjYSBgsCBgZrACYiCwAFBYsEBgWWawAWMjICA8ijgtsDssIyAuRrACJUZSWCA8WS6xKwEUKy2wPCwjIC5GsAIlRlBYIDxZLrErARQrLbA9LCMgLkawAiVGUlggPFkjIC5GsAIlRlBYIDxZLrErARQrLbA+LLA1KyMgLkawAiVGUlggPFkusSsBFCstsD8ssDYriiAgPLAEI0KKOCMgLkawAiVGUlggPFkusSsBFCuwBEMusCsrLbBALLAAFrAEJbAEJiAuRyNHI2GwCUMrIyA8IC4jOLErARQrLbBBLLEIBCVCsAAWsAQlsAQlIC5HI0cjYSCwBCNCsAlDKyCwYFBYILBAUVizAiADIBuzAiYDGllCQiMgR7AEQ7ACYiCwAFBYsEBgWWawAWNgILABKyCKimEgsAJDYGQjsANDYWRQWLACQ2EbsANDYFmwAyWwAmIgsABQWLBAYFlmsAFjYbACJUZhOCMgPCM4GyEgIEYjR7ABKyNhOCFZsSsBFCstsEIssDUrLrErARQrLbBDLLA2KyEjICA8sAQjQiM4sSsBFCuwBEMusCsrLbBELLAAFSBHsAAjQrIAAQEVFBMusDEqLbBFLLAAFSBHsAAjQrIAAQEVFBMusDEqLbBGLLEAARQTsDIqLbBHLLA0Ki2wSCywABZFIyAuIEaKI2E4sSsBFCstsEkssAgjQrBIKy2wSiyyAABBKy2wSyyyAAFBKy2wTCyyAQBBKy2wTSyyAQFBKy2wTiyyAABCKy2wTyyyAAFCKy2wUCyyAQBCKy2wUSyyAQFCKy2wUiyyAAA+Ky2wUyyyAAE+Ky2wVCyyAQA+Ky2wVSyyAQE+Ky2wViyyAABAKy2wVyyyAAFAKy2wWCyyAQBAKy2wWSyyAQFAKy2wWiyyAABDKy2wWyyyAAFDKy2wXCyyAQBDKy2wXSyyAQFDKy2wXiyyAAA/Ky2wXyyyAAE/Ky2wYCyyAQA/Ky2wYSyyAQE/Ky2wYiywNysusSsBFCstsGMssDcrsDsrLbBkLLA3K7A8Ky2wZSywABawNyuwPSstsGYssDgrLrErARQrLbBnLLA4K7A7Ky2waCywOCuwPCstsGkssDgrsD0rLbBqLLA5Ky6xKwEUKy2wayywOSuwOystsGwssDkrsDwrLbBtLLA5K7A9Ky2wbiywOisusSsBFCstsG8ssDorsDsrLbBwLLA6K7A8Ky2wcSywOiuwPSstsHIsswkEAgNFWCEbIyFZQiuwCGWwAyRQeLABFTAtAEu4AMhSWLEBAY5ZsAG5CAAIAGNwsQAFQrIAAQAqsQAFQrMKAgEIKrEABUKzDgABCCqxAAZCugLAAAEACSqxAAdCugBAAAEACSqxAwBEsSQBiFFYsECIWLEDZESxJgGIUVi6CIAAAQRAiGNUWLEDAERZWVlZswwCAQwquAH/hbAEjbECAEQAAA==') format('truetype'); -} -/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ -/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ -/* -@media screen and (-webkit-min-device-pixel-ratio:0) { - @font-face { - font-family: 'fontello'; - src: url('../font/fontello.svg?899037#fontello') format('svg'); - } -} -*/ - - [class^="icon-"]:before, [class*=" icon-"]:before { - font-family: "fontello"; - font-style: normal; - font-weight: normal; - speak: none; - - display: inline-block; - text-decoration: inherit; - width: 1em; - margin-right: .2em; - text-align: center; - /* opacity: .8; */ - - /* For safety - reset parent styles, that can break glyph codes*/ - font-variant: normal; - text-transform: none; - - /* fix buttons height, for twitter bootstrap */ - line-height: 1em; - - /* Animation center compensation - margins should be symmetric */ - /* remove if not needed */ - margin-left: .2em; - - /* you can be more comfortable with increased icons size */ - /* font-size: 120%; */ - - /* Uncomment for 3D effect */ - /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ -} -.icon-cancel:before { content: '\e800'; } /* '' */ -.icon-upload:before { content: '\e801'; } /* '' */ -.icon-star:before { content: '\e802'; } /* '' */ -.icon-star-empty:before { content: '\e803'; } /* '' */ -.icon-retweet:before { content: '\e804'; } /* '' */ -.icon-eye-off:before { content: '\e805'; } /* '' */ -.icon-search:before { content: '\e806'; } /* '' */ -.icon-cog:before { content: '\e807'; } /* '' */ -.icon-logout:before { content: '\e808'; } /* '' */ -.icon-down-open:before { content: '\e809'; } /* '' */ -.icon-attach:before { content: '\e80a'; } /* '' */ -.icon-picture:before { content: '\e80b'; } /* '' */ -.icon-video:before { content: '\e80c'; } /* '' */ -.icon-right-open:before { content: '\e80d'; } /* '' */ -.icon-left-open:before { content: '\e80e'; } /* '' */ -.icon-up-open:before { content: '\e80f'; } /* '' */ -.icon-bell-ringing-o:before { content: '\e810'; } /* '' */ -.icon-lock:before { content: '\e811'; } /* '' */ -.icon-globe:before { content: '\e812'; } /* '' */ -.icon-brush:before { content: '\e813'; } /* '' */ -.icon-attention:before { content: '\e814'; } /* '' */ -.icon-plus:before { content: '\e815'; } /* '' */ -.icon-adjust:before { content: '\e816'; } /* '' */ -.icon-edit:before { content: '\e817'; } /* '' */ -.icon-pencil:before { content: '\e818'; } /* '' */ -.icon-pin:before { content: '\e819'; } /* '' */ -.icon-wrench:before { content: '\e81a'; } /* '' */ -.icon-chart-bar:before { content: '\e81b'; } /* '' */ -.icon-zoom-in:before { content: '\e81c'; } /* '' */ -.icon-spin3:before { content: '\e832'; } /* '' */ -.icon-spin4:before { content: '\e834'; } /* '' */ -.icon-link-ext:before { content: '\f08e'; } /* '' */ -.icon-link-ext-alt:before { content: '\f08f'; } /* '' */ -.icon-menu:before { content: '\f0c9'; } /* '' */ -.icon-mail-alt:before { content: '\f0e0'; } /* '' */ -.icon-gauge:before { content: '\f0e4'; } /* '' */ -.icon-comment-empty:before { content: '\f0e5'; } /* '' */ -.icon-bell-alt:before { content: '\f0f3'; } /* '' */ -.icon-plus-squared:before { content: '\f0fe'; } /* '' */ -.icon-reply:before { content: '\f112'; } /* '' */ -.icon-smile:before { content: '\f118'; } /* '' */ -.icon-lock-open-alt:before { content: '\f13e'; } /* '' */ -.icon-ellipsis:before { content: '\f141'; } /* '' */ -.icon-play-circled:before { content: '\f144'; } /* '' */ -.icon-thumbs-up-alt:before { content: '\f164'; } /* '' */ -.icon-binoculars:before { content: '\f1e5'; } /* '' */ -.icon-user-plus:before { content: '\f234'; } /* '' */ \ No newline at end of file diff --git a/static/font/css/fontello-ie7-codes.css b/static/font/css/fontello-ie7-codes.css deleted file mode 100755 index 11c8c10a..00000000 --- a/static/font/css/fontello-ie7-codes.css +++ /dev/null @@ -1,48 +0,0 @@ - -.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-upload { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-star { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-star-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-retweet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-eye-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-search { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-logout { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-down-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-attach { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-video { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-left-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-up-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-bell-ringing-o { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-brush { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-attention { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-adjust { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-edit { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-pencil { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-pin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-wrench { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-chart-bar { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-zoom-in { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-link-ext-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-mail-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-gauge { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-comment-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-bell-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-smile { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-ellipsis { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-play-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-thumbs-up-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-user-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file diff --git a/static/font/css/fontello-ie7.css b/static/font/css/fontello-ie7.css deleted file mode 100755 index edf83afe..00000000 --- a/static/font/css/fontello-ie7.css +++ /dev/null @@ -1,59 +0,0 @@ -[class^="icon-"], [class*=" icon-"] { - font-family: 'fontello'; - font-style: normal; - font-weight: normal; - - /* fix buttons height */ - line-height: 1em; - - /* you can be more comfortable with increased icons size */ - /* font-size: 120%; */ -} - -.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-upload { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-star { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-star-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-retweet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-eye-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-search { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-logout { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-down-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-attach { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-video { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-left-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-up-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-bell-ringing-o { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-brush { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-attention { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-adjust { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-edit { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-pencil { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-pin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-wrench { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-chart-bar { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-zoom-in { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-link-ext-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-mail-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-gauge { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-comment-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-bell-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-smile { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-ellipsis { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-play-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-thumbs-up-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-user-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file diff --git a/static/font/css/fontello.css b/static/font/css/fontello.css deleted file mode 100755 index a6b3c919..00000000 --- a/static/font/css/fontello.css +++ /dev/null @@ -1,104 +0,0 @@ -@font-face { - font-family: 'fontello'; - src: url('../font/fontello.eot?70867224'); - src: url('../font/fontello.eot?70867224#iefix') format('embedded-opentype'), - url('../font/fontello.woff2?70867224') format('woff2'), - url('../font/fontello.woff?70867224') format('woff'), - url('../font/fontello.ttf?70867224') format('truetype'), - url('../font/fontello.svg?70867224#fontello') format('svg'); - font-weight: normal; - font-style: normal; -} -/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ -/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ -/* -@media screen and (-webkit-min-device-pixel-ratio:0) { - @font-face { - font-family: 'fontello'; - src: url('../font/fontello.svg?70867224#fontello') format('svg'); - } -} -*/ - - [class^="icon-"]:before, [class*=" icon-"]:before { - font-family: "fontello"; - font-style: normal; - font-weight: normal; - speak: none; - - display: inline-block; - text-decoration: inherit; - width: 1em; - margin-right: .2em; - text-align: center; - /* opacity: .8; */ - - /* For safety - reset parent styles, that can break glyph codes*/ - font-variant: normal; - text-transform: none; - - /* fix buttons height, for twitter bootstrap */ - line-height: 1em; - - /* Animation center compensation - margins should be symmetric */ - /* remove if not needed */ - margin-left: .2em; - - /* you can be more comfortable with increased icons size */ - /* font-size: 120%; */ - - /* Font smoothing. That was taken from TWBS */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - - /* Uncomment for 3D effect */ - /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ -} - -.icon-cancel:before { content: '\e800'; } /* '' */ -.icon-upload:before { content: '\e801'; } /* '' */ -.icon-star:before { content: '\e802'; } /* '' */ -.icon-star-empty:before { content: '\e803'; } /* '' */ -.icon-retweet:before { content: '\e804'; } /* '' */ -.icon-eye-off:before { content: '\e805'; } /* '' */ -.icon-search:before { content: '\e806'; } /* '' */ -.icon-cog:before { content: '\e807'; } /* '' */ -.icon-logout:before { content: '\e808'; } /* '' */ -.icon-down-open:before { content: '\e809'; } /* '' */ -.icon-attach:before { content: '\e80a'; } /* '' */ -.icon-picture:before { content: '\e80b'; } /* '' */ -.icon-video:before { content: '\e80c'; } /* '' */ -.icon-right-open:before { content: '\e80d'; } /* '' */ -.icon-left-open:before { content: '\e80e'; } /* '' */ -.icon-up-open:before { content: '\e80f'; } /* '' */ -.icon-bell-ringing-o:before { content: '\e810'; } /* '' */ -.icon-lock:before { content: '\e811'; } /* '' */ -.icon-globe:before { content: '\e812'; } /* '' */ -.icon-brush:before { content: '\e813'; } /* '' */ -.icon-attention:before { content: '\e814'; } /* '' */ -.icon-plus:before { content: '\e815'; } /* '' */ -.icon-adjust:before { content: '\e816'; } /* '' */ -.icon-edit:before { content: '\e817'; } /* '' */ -.icon-pencil:before { content: '\e818'; } /* '' */ -.icon-pin:before { content: '\e819'; } /* '' */ -.icon-wrench:before { content: '\e81a'; } /* '' */ -.icon-chart-bar:before { content: '\e81b'; } /* '' */ -.icon-zoom-in:before { content: '\e81c'; } /* '' */ -.icon-spin3:before { content: '\e832'; } /* '' */ -.icon-spin4:before { content: '\e834'; } /* '' */ -.icon-link-ext:before { content: '\f08e'; } /* '' */ -.icon-link-ext-alt:before { content: '\f08f'; } /* '' */ -.icon-menu:before { content: '\f0c9'; } /* '' */ -.icon-mail-alt:before { content: '\f0e0'; } /* '' */ -.icon-gauge:before { content: '\f0e4'; } /* '' */ -.icon-comment-empty:before { content: '\f0e5'; } /* '' */ -.icon-bell-alt:before { content: '\f0f3'; } /* '' */ -.icon-plus-squared:before { content: '\f0fe'; } /* '' */ -.icon-reply:before { content: '\f112'; } /* '' */ -.icon-smile:before { content: '\f118'; } /* '' */ -.icon-lock-open-alt:before { content: '\f13e'; } /* '' */ -.icon-ellipsis:before { content: '\f141'; } /* '' */ -.icon-play-circled:before { content: '\f144'; } /* '' */ -.icon-thumbs-up-alt:before { content: '\f164'; } /* '' */ -.icon-binoculars:before { content: '\f1e5'; } /* '' */ -.icon-user-plus:before { content: '\f234'; } /* '' */ \ No newline at end of file diff --git a/static/font/demo.html b/static/font/demo.html deleted file mode 100755 index afae72fa..00000000 --- a/static/font/demo.html +++ /dev/null @@ -1,374 +0,0 @@ -<!DOCTYPE html> -<html> - <head><!--[if lt IE 9]><script language="javascript" type="text/javascript" src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]--> - <meta charset="UTF-8"><style>/* - * Bootstrap v2.2.1 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */ -.clearfix { - *zoom: 1; -} -.clearfix:before, -.clearfix:after { - display: table; - content: ""; - line-height: 0; -} -.clearfix:after { - clear: both; -} -html { - font-size: 100%; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; -} -a:focus { - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} -a:hover, -a:active { - outline: 0; -} -button, -input, -select, -textarea { - margin: 0; - font-size: 100%; - vertical-align: middle; -} -button, -input { - *overflow: visible; - line-height: normal; -} -button::-moz-focus-inner, -input::-moz-focus-inner { - padding: 0; - border: 0; -} -body { - margin: 0; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 20px; - color: #333; - background-color: #fff; -} -a { - color: #08c; - text-decoration: none; -} -a:hover { - color: #005580; - text-decoration: underline; -} -.row { - margin-left: -20px; - *zoom: 1; -} -.row:before, -.row:after { - display: table; - content: ""; - line-height: 0; -} -.row:after { - clear: both; -} -[class*="span"] { - float: left; - min-height: 1px; - margin-left: 20px; -} -.container, -.navbar-static-top .container, -.navbar-fixed-top .container, -.navbar-fixed-bottom .container { - width: 940px; -} -.span12 { - width: 940px; -} -.span11 { - width: 860px; -} -.span10 { - width: 780px; -} -.span9 { - width: 700px; -} -.span8 { - width: 620px; -} -.span7 { - width: 540px; -} -.span6 { - width: 460px; -} -.span5 { - width: 380px; -} -.span4 { - width: 300px; -} -.span3 { - width: 220px; -} -.span2 { - width: 140px; -} -.span1 { - width: 60px; -} -[class*="span"].pull-right, -.row-fluid [class*="span"].pull-right { - float: right; -} -.container { - margin-right: auto; - margin-left: auto; - *zoom: 1; -} -.container:before, -.container:after { - display: table; - content: ""; - line-height: 0; -} -.container:after { - clear: both; -} -p { - margin: 0 0 10px; -} -.lead { - margin-bottom: 20px; - font-size: 21px; - font-weight: 200; - line-height: 30px; -} -small { - font-size: 85%; -} -h1 { - margin: 10px 0; - font-family: inherit; - font-weight: bold; - line-height: 20px; - color: inherit; - text-rendering: optimizelegibility; -} -h1 small { - font-weight: normal; - line-height: 1; - color: #999; -} -h1 { - line-height: 40px; -} -h1 { - font-size: 38.5px; -} -h1 small { - font-size: 24.5px; -} -body { - margin-top: 90px; -} -.header { - position: fixed; - top: 0; - left: 50%; - margin-left: -480px; - background-color: #fff; - border-bottom: 1px solid #ddd; - padding-top: 10px; - z-index: 10; -} -.footer { - color: #ddd; - font-size: 12px; - text-align: center; - margin-top: 20px; -} -.footer a { - color: #ccc; - text-decoration: underline; -} -.the-icons { - font-size: 14px; - line-height: 24px; -} -.switch { - position: absolute; - right: 0; - bottom: 10px; - color: #666; -} -.switch input { - margin-right: 0.3em; -} -.codesOn .i-name { - display: none; -} -.codesOn .i-code { - display: inline; -} -.i-code { - display: none; -} -@font-face { - font-family: 'fontello'; - src: url('./font/fontello.eot?56851497'); - src: url('./font/fontello.eot?56851497#iefix') format('embedded-opentype'), - url('./font/fontello.woff?56851497') format('woff'), - url('./font/fontello.ttf?56851497') format('truetype'), - url('./font/fontello.svg?56851497#fontello') format('svg'); - font-weight: normal; - font-style: normal; - } - - - .demo-icon - { - font-family: "fontello"; - font-style: normal; - font-weight: normal; - speak: none; - - display: inline-block; - text-decoration: inherit; - width: 1em; - margin-right: .2em; - text-align: center; - /* opacity: .8; */ - - /* For safety - reset parent styles, that can break glyph codes*/ - font-variant: normal; - text-transform: none; - - /* fix buttons height, for twitter bootstrap */ - line-height: 1em; - - /* Animation center compensation - margins should be symmetric */ - /* remove if not needed */ - margin-left: .2em; - - /* You can be more comfortable with increased icons size */ - /* font-size: 120%; */ - - /* Font smoothing. That was taken from TWBS */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - - /* Uncomment for 3D effect */ - /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ - } - </style> - <link rel="stylesheet" href="css/animation.css"><!--[if IE 7]><link rel="stylesheet" href="css/" + font.fontname + "-ie7.css"><![endif]--> - <script> - function toggleCodes(on) { - var obj = document.getElementById('icons'); - - if (on) { - obj.className += ' codesOn'; - } else { - obj.className = obj.className.replace(' codesOn', ''); - } - } - - </script> - </head> - <body> - <div class="container header"> - <h1>fontello <small>font demo</small></h1> - <label class="switch"> - <input type="checkbox" onclick="toggleCodes(this.checked)">show codes - </label> - </div> - <div class="container" id="icons"> - <div class="row"> - <div class="the-icons span3" title="Code: 0xe800"><i class="demo-icon icon-cancel"></i> <span class="i-name">icon-cancel</span><span class="i-code">0xe800</span></div> - <div class="the-icons span3" title="Code: 0xe801"><i class="demo-icon icon-upload"></i> <span class="i-name">icon-upload</span><span class="i-code">0xe801</span></div> - <div class="the-icons span3" title="Code: 0xe802"><i class="demo-icon icon-star"></i> <span class="i-name">icon-star</span><span class="i-code">0xe802</span></div> - <div class="the-icons span3" title="Code: 0xe803"><i class="demo-icon icon-star-empty"></i> <span class="i-name">icon-star-empty</span><span class="i-code">0xe803</span></div> - </div> - <div class="row"> - <div class="the-icons span3" title="Code: 0xe804"><i class="demo-icon icon-retweet"></i> <span class="i-name">icon-retweet</span><span class="i-code">0xe804</span></div> - <div class="the-icons span3" title="Code: 0xe805"><i class="demo-icon icon-eye-off"></i> <span class="i-name">icon-eye-off</span><span class="i-code">0xe805</span></div> - <div class="the-icons span3" title="Code: 0xe806"><i class="demo-icon icon-search"></i> <span class="i-name">icon-search</span><span class="i-code">0xe806</span></div> - <div class="the-icons span3" title="Code: 0xe807"><i class="demo-icon icon-cog"></i> <span class="i-name">icon-cog</span><span class="i-code">0xe807</span></div> - </div> - <div class="row"> - <div class="the-icons span3" title="Code: 0xe808"><i class="demo-icon icon-logout"></i> <span class="i-name">icon-logout</span><span class="i-code">0xe808</span></div> - <div class="the-icons span3" title="Code: 0xe809"><i class="demo-icon icon-down-open"></i> <span class="i-name">icon-down-open</span><span class="i-code">0xe809</span></div> - <div class="the-icons span3" title="Code: 0xe80a"><i class="demo-icon icon-attach"></i> <span class="i-name">icon-attach</span><span class="i-code">0xe80a</span></div> - <div class="the-icons span3" title="Code: 0xe80b"><i class="demo-icon icon-picture"></i> <span class="i-name">icon-picture</span><span class="i-code">0xe80b</span></div> - </div> - <div class="row"> - <div class="the-icons span3" title="Code: 0xe80c"><i class="demo-icon icon-video"></i> <span class="i-name">icon-video</span><span class="i-code">0xe80c</span></div> - <div class="the-icons span3" title="Code: 0xe80d"><i class="demo-icon icon-right-open"></i> <span class="i-name">icon-right-open</span><span class="i-code">0xe80d</span></div> - <div class="the-icons span3" title="Code: 0xe80e"><i class="demo-icon icon-left-open"></i> <span class="i-name">icon-left-open</span><span class="i-code">0xe80e</span></div> - <div class="the-icons span3" title="Code: 0xe80f"><i class="demo-icon icon-up-open"></i> <span class="i-name">icon-up-open</span><span class="i-code">0xe80f</span></div> - </div> - <div class="row"> - <div class="the-icons span3" title="Code: 0xe810"><i class="demo-icon icon-bell-ringing-o"></i> <span class="i-name">icon-bell-ringing-o</span><span class="i-code">0xe810</span></div> - <div class="the-icons span3" title="Code: 0xe811"><i class="demo-icon icon-lock"></i> <span class="i-name">icon-lock</span><span class="i-code">0xe811</span></div> - <div class="the-icons span3" title="Code: 0xe812"><i class="demo-icon icon-globe"></i> <span class="i-name">icon-globe</span><span class="i-code">0xe812</span></div> - <div class="the-icons span3" title="Code: 0xe813"><i class="demo-icon icon-brush"></i> <span class="i-name">icon-brush</span><span class="i-code">0xe813</span></div> - </div> - <div class="row"> - <div class="the-icons span3" title="Code: 0xe814"><i class="demo-icon icon-attention"></i> <span class="i-name">icon-attention</span><span class="i-code">0xe814</span></div> - <div class="the-icons span3" title="Code: 0xe815"><i class="demo-icon icon-plus"></i> <span class="i-name">icon-plus</span><span class="i-code">0xe815</span></div> - <div class="the-icons span3" title="Code: 0xe816"><i class="demo-icon icon-adjust"></i> <span class="i-name">icon-adjust</span><span class="i-code">0xe816</span></div> - <div class="the-icons span3" title="Code: 0xe817"><i class="demo-icon icon-edit"></i> <span class="i-name">icon-edit</span><span class="i-code">0xe817</span></div> - </div> - <div class="row"> - <div class="the-icons span3" title="Code: 0xe818"><i class="demo-icon icon-pencil"></i> <span class="i-name">icon-pencil</span><span class="i-code">0xe818</span></div> - <div class="the-icons span3" title="Code: 0xe819"><i class="demo-icon icon-pin"></i> <span class="i-name">icon-pin</span><span class="i-code">0xe819</span></div> - <div class="the-icons span3" title="Code: 0xe81a"><i class="demo-icon icon-wrench"></i> <span class="i-name">icon-wrench</span><span class="i-code">0xe81a</span></div> - <div class="the-icons span3" title="Code: 0xe81b"><i class="demo-icon icon-chart-bar"></i> <span class="i-name">icon-chart-bar</span><span class="i-code">0xe81b</span></div> - </div> - <div class="row"> - <div class="the-icons span3" title="Code: 0xe81c"><i class="demo-icon icon-zoom-in"></i> <span class="i-name">icon-zoom-in</span><span class="i-code">0xe81c</span></div> - <div class="the-icons span3" title="Code: 0xe832"><i class="demo-icon icon-spin3 animate-spin"></i> <span class="i-name">icon-spin3</span><span class="i-code">0xe832</span></div> - <div class="the-icons span3" title="Code: 0xe834"><i class="demo-icon icon-spin4 animate-spin"></i> <span class="i-name">icon-spin4</span><span class="i-code">0xe834</span></div> - <div class="the-icons span3" title="Code: 0xf08e"><i class="demo-icon icon-link-ext"></i> <span class="i-name">icon-link-ext</span><span class="i-code">0xf08e</span></div> - </div> - <div class="row"> - <div class="the-icons span3" title="Code: 0xf08f"><i class="demo-icon icon-link-ext-alt"></i> <span class="i-name">icon-link-ext-alt</span><span class="i-code">0xf08f</span></div> - <div class="the-icons span3" title="Code: 0xf0c9"><i class="demo-icon icon-menu"></i> <span class="i-name">icon-menu</span><span class="i-code">0xf0c9</span></div> - <div class="the-icons span3" title="Code: 0xf0e0"><i class="demo-icon icon-mail-alt"></i> <span class="i-name">icon-mail-alt</span><span class="i-code">0xf0e0</span></div> - <div class="the-icons span3" title="Code: 0xf0e4"><i class="demo-icon icon-gauge"></i> <span class="i-name">icon-gauge</span><span class="i-code">0xf0e4</span></div> - </div> - <div class="row"> - <div class="the-icons span3" title="Code: 0xf0e5"><i class="demo-icon icon-comment-empty"></i> <span class="i-name">icon-comment-empty</span><span class="i-code">0xf0e5</span></div> - <div class="the-icons span3" title="Code: 0xf0f3"><i class="demo-icon icon-bell-alt"></i> <span class="i-name">icon-bell-alt</span><span class="i-code">0xf0f3</span></div> - <div class="the-icons span3" title="Code: 0xf0fe"><i class="demo-icon icon-plus-squared"></i> <span class="i-name">icon-plus-squared</span><span class="i-code">0xf0fe</span></div> - <div class="the-icons span3" title="Code: 0xf112"><i class="demo-icon icon-reply"></i> <span class="i-name">icon-reply</span><span class="i-code">0xf112</span></div> - </div> - <div class="row"> - <div class="the-icons span3" title="Code: 0xf118"><i class="demo-icon icon-smile"></i> <span class="i-name">icon-smile</span><span class="i-code">0xf118</span></div> - <div class="the-icons span3" title="Code: 0xf13e"><i class="demo-icon icon-lock-open-alt"></i> <span class="i-name">icon-lock-open-alt</span><span class="i-code">0xf13e</span></div> - <div class="the-icons span3" title="Code: 0xf141"><i class="demo-icon icon-ellipsis"></i> <span class="i-name">icon-ellipsis</span><span class="i-code">0xf141</span></div> - <div class="the-icons span3" title="Code: 0xf144"><i class="demo-icon icon-play-circled"></i> <span class="i-name">icon-play-circled</span><span class="i-code">0xf144</span></div> - </div> - <div class="row"> - <div class="the-icons span3" title="Code: 0xf164"><i class="demo-icon icon-thumbs-up-alt"></i> <span class="i-name">icon-thumbs-up-alt</span><span class="i-code">0xf164</span></div> - <div class="the-icons span3" title="Code: 0xf1e5"><i class="demo-icon icon-binoculars"></i> <span class="i-name">icon-binoculars</span><span class="i-code">0xf1e5</span></div> - <div class="the-icons span3" title="Code: 0xf234"><i class="demo-icon icon-user-plus"></i> <span class="i-name">icon-user-plus</span><span class="i-code">0xf234</span></div> - </div> - </div> - <div class="container footer">Generated by <a href="http://fontello.com">fontello.com</a></div> - </body> -</html> \ No newline at end of file diff --git a/static/font/font/fontello.eot b/static/font/font/fontello.eot deleted file mode 100755 index 1703fd97..00000000 Binary files a/static/font/font/fontello.eot and /dev/null differ diff --git a/static/font/font/fontello.svg b/static/font/font/fontello.svg deleted file mode 100755 index f5e497ce..00000000 --- a/static/font/font/fontello.svg +++ /dev/null @@ -1,104 +0,0 @@ -<?xml version="1.0" standalone="no"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg xmlns="http://www.w3.org/2000/svg"> -<metadata>Copyright (C) 2019 by original authors @ fontello.com</metadata> -<defs> -<font id="fontello" horiz-adv-x="1000" > -<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="857" descent="-143" /> -<missing-glyph horiz-adv-x="1000" /> -<glyph glyph-name="cancel" unicode="" d="M724 119q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" /> - -<glyph glyph-name="upload" unicode="" d="M714 36q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m143 0q0 14-10 25t-26 10-25-10-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-38t-38-16h-821q-23 0-38 16t-16 38v179q0 22 16 38t38 15h238q12-31 39-51t62-20h143q34 0 61 20t40 51h238q22 0 38-15t16-38z m-182 361q-9-22-33-22h-143v-250q0-15-10-25t-25-11h-143q-15 0-25 11t-11 25v250h-143q-23 0-33 22-9 22 8 39l250 250q10 10 25 10t25-10l250-250q18-17 8-39z" horiz-adv-x="928.6" /> - -<glyph glyph-name="star" unicode="" d="M929 496q0-12-15-27l-202-197 48-279q0-4 0-12 0-11-6-19t-17-9q-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" /> - -<glyph glyph-name="star-empty" unicode="" d="M635 297l170 166-235 34-106 213-105-213-236-34 171-166-41-235 211 111 211-111z m294 199q0-12-15-27l-202-197 48-279q0-4 0-12 0-28-23-28-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" /> - -<glyph glyph-name="retweet" unicode="" d="M714 18q0-7-5-13t-13-5h-535q-5 0-8 1t-5 4-3 4-2 7 0 6v335h-107q-15 0-25 11t-11 25q0 13 8 23l179 214q11 12 27 12t28-12l178-214q9-10 9-23 0-15-11-25t-25-11h-107v-214h321q9 0 14-6l89-108q4-5 4-11z m357 232q0-13-8-23l-178-214q-12-13-28-13t-27 13l-179 214q-8 10-8 23 0 14 11 25t25 11h107v214h-322q-9 0-14 7l-89 107q-4 5-4 11 0 7 5 12t13 6h536q4 0 7-1t5-4 3-5 2-6 1-7v-334h107q14 0 25-11t10-25z" horiz-adv-x="1071.4" /> - -<glyph glyph-name="eye-off" unicode="" d="M310 112l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-91l157 280q4-25 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-85t81-103q11-19 11-39z" horiz-adv-x="1000" /> - -<glyph glyph-name="search" unicode="" d="M643 393q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-22-50t-50-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" /> - -<glyph glyph-name="cog" unicode="" d="M571 357q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 16 20 16h124q7 0 13-5t7-12l15-103q28-9 51-20l79 59q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-12 0-7-4-13-9-12-29-37t-30-40q15-28 23-54l102-16q7-1 12-7t4-13z" horiz-adv-x="857.1" /> - -<glyph glyph-name="logout" unicode="" d="M357 53q0-2 1-11t0-14-2-14-5-10-12-4h-178q-67 0-114 47t-47 114v392q0 67 47 114t114 47h178q8 0 13-5t5-13q0-2 1-11t0-15-2-13-5-11-12-3h-178q-37 0-63-26t-27-64v-392q0-37 27-63t63-27h174t6 0 7-2 4-3 4-5 1-8z m518 304q0-14-11-25l-303-304q-11-10-25-10t-25 10-11 25v161h-250q-14 0-25 11t-11 25v214q0 15 11 25t25 11h250v161q0 14 11 25t25 10 25-10l303-304q11-10 11-25z" horiz-adv-x="928.6" /> - -<glyph glyph-name="down-open" unicode="" d="M939 406l-414-413q-10-11-25-11t-25 11l-414 413q-11 11-11 26t11 25l93 92q10 11 25 11t25-11l296-296 296 296q11 11 25 11t26-11l92-92q11-11 11-25t-11-26z" horiz-adv-x="1000" /> - -<glyph glyph-name="attach" unicode="" d="M244-133q-102 0-170 72-72 70-74 166t84 190l496 496q80 80 174 54 44-12 79-47t47-79q26-96-54-176l-474-474q-40-40-88-46-48-4-80 28-30 24-27 74t47 92l332 334q24 26 50 0t0-50l-332-332q-44-44-20-70 12-8 24-6 24 4 46 26l474 474q50 50 34 108-16 60-76 76-54 14-108-36l-494-494q-66-76-64-143t52-117q50-48 117-50t141 62l496 494q24 24 50 0 26-22 0-48l-496-496q-82-82-186-82z" horiz-adv-x="939" /> - -<glyph glyph-name="picture" unicode="" d="M357 536q0-45-31-76t-76-32-76 32-31 76 31 76 76 31 76-31 31-76z m572-215v-250h-786v107l178 179 90-89 285 285z m53 393h-893q-7 0-12-5t-6-13v-678q0-7 6-13t12-5h893q7 0 13 5t5 13v678q0 8-5 13t-13 5z m89-18v-678q0-37-26-63t-63-27h-893q-36 0-63 27t-26 63v678q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" /> - -<glyph glyph-name="video" unicode="" d="M214-36v72q0 14-10 25t-25 10h-72q-14 0-25-10t-11-25v-72q0-14 11-25t25-11h72q14 0 25 11t10 25z m0 214v72q0 14-10 25t-25 11h-72q-14 0-25-11t-11-25v-72q0-14 11-25t25-10h72q14 0 25 10t10 25z m0 215v71q0 15-10 25t-25 11h-72q-14 0-25-11t-11-25v-71q0-15 11-25t25-11h72q14 0 25 11t10 25z m572-429v286q0 14-11 25t-25 11h-429q-14 0-25-11t-10-25v-286q0-14 10-25t25-11h429q15 0 25 11t11 25z m-572 643v71q0 15-10 26t-25 10h-72q-14 0-25-10t-11-26v-71q0-14 11-25t25-11h72q14 0 25 11t10 25z m786-643v72q0 14-11 25t-25 10h-71q-15 0-25-10t-11-25v-72q0-14 11-25t25-11h71q15 0 25 11t11 25z m-214 429v285q0 15-11 26t-25 10h-429q-14 0-25-10t-10-26v-285q0-15 10-25t25-11h429q15 0 25 11t11 25z m214-215v72q0 14-11 25t-25 11h-71q-15 0-25-11t-11-25v-72q0-14 11-25t25-10h71q15 0 25 10t11 25z m0 215v71q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-71q0-15 11-25t25-11h71q15 0 25 11t11 25z m0 214v71q0 15-11 26t-25 10h-71q-15 0-25-10t-11-26v-71q0-14 11-25t25-11h71q15 0 25 11t11 25z m71 89v-750q0-37-26-63t-63-26h-893q-36 0-63 26t-26 63v750q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" /> - -<glyph glyph-name="right-open" unicode="" d="M618 368l-414-415q-11-10-25-10t-25 10l-93 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l93 93q10 11 25 11t25-11l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" /> - -<glyph glyph-name="left-open" unicode="" d="M654 689l-297-296 297-297q10-10 10-25t-10-25l-93-93q-11-10-25-10t-25 10l-414 415q-11 10-11 25t11 25l414 414q10 11 25 11t25-11l93-93q10-10 10-25t-10-25z" horiz-adv-x="714.3" /> - -<glyph glyph-name="up-open" unicode="" d="M939 114l-92-92q-11-10-26-10t-25 10l-296 297-296-297q-11-10-25-10t-25 10l-93 92q-11 11-11 26t11 25l414 414q11 10 25 10t25-10l414-414q11-11 11-25t-11-26z" horiz-adv-x="1000" /> - -<glyph glyph-name="bell-ringing-o" unicode="" d="M498 857c-30 0-54-24-54-53 0-8 2-15 5-22-147-22-236-138-236-245 0-268-95-393-177-462 0-39 32-71 71-71h249c0-79 63-143 142-143s142 64 142 143h249c39 0 71 32 71 71-82 69-178 194-178 462 0 107-88 223-235 245 2 7 4 14 4 22 0 29-24 53-53 53z m-309-45c-81-74-118-170-118-275l71 0c0 89 28 162 95 223l-48 52z m617 0l-48-52c67-61 96-134 95-223l71 0c1 105-37 201-118 275z m-397-799c5 0 9-4 9-9 0-44 36-80 80-80 5 0 9-4 9-9s-4-9-9-9c-54 0-98 44-98 98 0 5 4 9 9 9z" horiz-adv-x="1000" /> - -<glyph glyph-name="lock" unicode="" d="M179 428h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" /> - -<glyph glyph-name="globe" unicode="" d="M429 786q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m153-291q-2-1-6-5t-7-6q1 0 2 3t3 6 2 4q3 4 12 8 8 4 29 7 19 5 29-6-1 1 5 7t8 7q2 1 8 3t9 4l1 12q-7-1-10 4t-3 12q0-2-4-5 0 4-2 5t-7-1-5-1q-5 2-8 5t-5 9-2 8q-1 3-5 6t-5 6q-1 1-2 3t-1 4-3 3-3 1-4-3-4-5-2-3q-2 1-4 1t-2-1-3-1-3-2q-1-2-4-2t-5-1q8 3-1 6-5 2-9 2 6 2 5 6t-5 8h3q-1 2-5 5t-10 5-7 3q-5 3-19 5t-18 1q-3-4-3-6t2-8 2-7q1-3-3-7t-3-7q0-4 7-9t6-12q-2-4-9-9t-9-6q-3-5-1-11t6-9q1-1 1-2t-2-3-3-2-4-2l-1-1q-7-3-12 3t-7 15q-4 14-9 17-13 4-16-1-3 7-23 15-14 5-33 2 4 0 0 8-4 9-10 7 1 3 2 10t0 7q2 8 7 13 1 1 4 5t5 7 1 4q19-3 28 6 2 3 6 9t6 10q5 3 8 3t8-3 8-3q8-1 8 6t-4 11q7 0 2 10-2 4-5 5-6 2-15-3-4-2 2-4-1 0-6-6t-9-10-9 3q0 0-3 7t-5 8q-5 0-9-9 1 5-6 9t-14 4q11 7-4 15-4 3-12 3t-11-2q-2-4-3-7t3-4 6-3 6-2 5-2q8-6 5-8-1 0-5-2t-6-2-4-2q-1-3 0-8t-1-8q-3 3-5 10t-4 9q4-5-14-3l-5 0q-3 0-9-1t-12-1-7 5q-3 4 0 11 0 2 2 1-2 2-6 5t-6 5q-25-8-52-23 3 0 6 1 3 1 8 4t5 3q19 7 24 4l3 2q7-9 11-14-4 3-17 1-11-3-12-7 4-6 2-10-2 2-6 6t-8 6-8 3q-9 0-13-1-81-45-131-124 4-4 7-4 2-1 3-5t1-6 6 1q5-4 2-10 1 0 25-15 10-10 11-12 2-6-5-10-1 1-5 5t-5 2q-2-3 0-10t6-7q-4 0-5-9t-2-20 0-13l1-1q-2-6 3-19t12-11q-7-1 11-24 3-4 4-5 2-1 7-4t9-6 5-5q2-3 6-13t8-13q-2-3 5-11t6-13q-1 0-2-1t-1 0q2-4 9-8t8-7q1-2 1-6t2-6 4-1q2 11-13 35-8 13-9 16-2 2-4 8t-2 8q1 0 3 0t5-2 4-3 1-1q-1-4 1-10t7-10 10-11 6-7q4-4 8-11t0-8q5 0 11-5t10-11q3-5 4-15t3-13q1-4 5-8t7-5l9-5t7-3q3-2 10-6t12-7q6-2 9-2t8 1 8 2q8 1 16-8t12-12q20-10 30-6-1 0 1-4t4-9 5-8 3-5q3-3 10-8t10-8q4 2 4 5-1-5 4-11t10-6q8 2 8 18-17-8-27 10 0 0-2 3t-2 5-1 4 0 5 2 1q5 0 6 2t-1 7-2 8q-1 4-6 11t-7 8q-3-5-9-4t-9 5q0-1-1-3t-1-4q-7 0-8 0 1 2 1 10t2 13q1 2 3 6t5 9 2 7-3 5-9 1q-11 0-15-11-1-2-2-6t-2-6-5-4q-4-2-14-1t-13 3q-8 4-13 16t-5 20q0 6 1 15t2 14-3 14q2 1 5 5t5 6q2 1 3 1t3 0 2 1 1 3q0 1-2 2-1 1-2 1 4-1 16 1t15-1q9-6 12 1 0 1-1 6t0 7q3-15 16-5 2-1 9-3t9-2q2-1 4-3t3-3 3 0 5 4q5-8 7-13 6-23 10-25 4-2 6-1t3 5 0 8-1 7l-1 5v10l0 4q-8 2-10 7t0 10 9 10q0 1 4 2t9 4 7 4q12 11 8 20 4 0 6 5 0 0-2 2t-5 2-2 2q5 2 1 8 3 2 4 7t4 5q5-6 12-1 5 5 1 9 2 4 11 6t10 5q4-1 5 1t0 7 2 7q2 2 9 5t7 2l9 7q2 2 0 2 10-1 18 6 5 6-4 11 2 4-1 5t-9 4q2 0 7 0t5 1q9 5-3 9-10 2-24-7z m-91-490q115 21 195 106-1 2-7 2t-7 2q-10 4-13 5 1 4-1 7t-5 5-7 5-6 4q-1 1-4 3t-4 3-4 2-5 2-5-1l-2-1q-2 0-3-1t-3-2-2-1 0-2q-12 10-20 13-3 0-6 3t-6 4-6 0-6-3q-3-3-4-9t-1-7q-4 3 0 10t1 10q-1 3-6 2t-6-2-7-5-5-3-4-3-5-5q-2-2-4-6t-2-6q-1 2-7 3t-5 3q1-5 2-19t3-22q4-17-7-26-15-14-16-23-2-12 7-14 0-4-5-12t-4-12q0-3 2-9z" horiz-adv-x="857.1" /> - -<glyph glyph-name="brush" unicode="" d="M464 209q0-124-87-212t-210-87q-81 0-149 40 68 39 109 108t40 151q0 61 44 105t105 44 105-44 43-105z m415 562q32-32 32-79t-33-79l-318-318q-20 55-61 97t-97 62l318 318q32 32 79 32t80-33z" horiz-adv-x="928" /> - -<glyph glyph-name="attention" unicode="" d="M571 90v106q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-106q0-8 5-13t12-6h108q7 0 12 6t5 13z m-1 208l10 257q0 6-5 10-7 6-14 6h-122q-6 0-14-6-5-4-5-12l9-255q0-5 6-9t13-3h103q8 0 14 3t5 9z m-7 522l428-786q20-35-1-70-9-17-26-26t-35-10h-858q-18 0-35 10t-26 26q-21 35-1 70l429 786q9 17 26 27t36 10 36-10 27-27z" horiz-adv-x="1000" /> - -<glyph glyph-name="plus" unicode="" d="M786 446v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q23 0 38-16t16-38z" horiz-adv-x="785.7" /> - -<glyph glyph-name="adjust" unicode="" d="M429 53v608q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41z m428 304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> - -<glyph glyph-name="edit" unicode="" d="M496 196l64 65-85 85-64-65v-31h53v-54h32z m245 402q-9 9-18 0l-196-196q-9-9 0-18t18 0l196 196q9 9 0 18z m45-331v-106q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-8-8-18-4-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v70q0 7 5 12l36 36q8 8 20 4t11-16z m-54 411l161-160-375-375h-161v160z m248-73l-51-52-161 161 51 52q16 15 38 15t38-15l85-85q16-16 16-38t-16-38z" horiz-adv-x="1000" /> - -<glyph glyph-name="pencil" unicode="" d="M203 0l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" /> - -<glyph glyph-name="pin" unicode="" d="M268 375v250q0 8-5 13t-13 5-13-5-5-13v-250q0-8 5-13t13-5 13 5 5 13z m375-197q0-14-11-25t-25-10h-239l-29-270q-1-7-6-11t-11-5h-1q-15 0-17 15l-43 271h-225q-15 0-25 10t-11 25q0 69 44 124t99 55v286q-29 0-50 21t-22 50 22 50 50 22h357q29 0 50-22t21-50-21-50-50-21v-286q55 0 99-55t44-124z" horiz-adv-x="642.9" /> - -<glyph glyph-name="wrench" unicode="" d="M214 36q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m354 243q0-22-13-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t5-14z" horiz-adv-x="928.6" /> - -<glyph glyph-name="chart-bar" unicode="" d="M357 357v-286h-143v286h143z m214 286v-572h-142v572h142z m572-643v-72h-1143v858h71v-786h1072z m-357 500v-429h-143v429h143z m214 214v-643h-143v643h143z" horiz-adv-x="1142.9" /> - -<glyph glyph-name="zoom-in" unicode="" d="M571 411v-36q0-7-5-13t-12-5h-125v-125q0-7-6-13t-12-5h-36q-7 0-13 5t-5 13v125h-125q-7 0-12 5t-6 13v36q0 7 6 12t12 5h125v125q0 8 5 13t13 5h36q7 0 12-5t6-13v-125h125q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" /> - -<glyph glyph-name="spin3" unicode="" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" /> - -<glyph glyph-name="spin4" unicode="" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" /> - -<glyph glyph-name="link-ext" unicode="" d="M786 339v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" /> - -<glyph glyph-name="link-ext-alt" unicode="" d="M714 339v268q0 15-10 25t-25 11h-268q-24 0-33-22-10-23 8-39l80-80-298-298q-11-11-11-26t11-25l57-57q11-10 25-10t25 10l298 298 81-80q10-11 25-11 6 0 14 3 21 10 21 33z m143 286v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> - -<glyph glyph-name="menu" unicode="" d="M857 107v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" /> - -<glyph glyph-name="mail-alt" unicode="" d="M1000 461v-443q0-37-26-63t-63-27h-822q-36 0-63 27t-26 63v443q25-27 56-49 202-137 278-192 32-24 51-37t53-27 61-13h2q28 0 61 13t53 27 51 37q95 68 278 192 32 22 56 49z m0 164q0-44-27-84t-68-69q-210-146-262-181-5-4-23-17t-30-22-29-18-32-15-28-5h-2q-12 0-27 5t-32 15-30 18-30 22-23 17q-51 35-147 101t-114 80q-35 23-65 64t-31 77q0 43 23 72t66 29h822q36 0 63-26t26-63z" horiz-adv-x="1000" /> - -<glyph glyph-name="gauge" unicode="" d="M214 214q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m107 250q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m239-268l57 213q3 14-5 27t-21 16-27-3-17-22l-56-213q-33-3-60-25t-35-55q-11-43 11-81t66-50 81 11 50 66q9 33-4 65t-40 51z m369 18q0 30-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m-358 357q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m250-107q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m179-250q0-145-79-269-10-17-30-17h-782q-20 0-30 17-79 123-79 269 0 102 40 194t106 160 160 107 194 39 194-39 160-107 106-160 40-194z" horiz-adv-x="1000" /> - -<glyph glyph-name="comment-empty" unicode="" d="M500 643q-114 0-213-39t-157-105-59-142q0-62 40-119t113-98l48-28-15-53q-13-51-39-97 85 36 154 96l24 21 32-3q38-5 72-5 114 0 213 39t157 105 59 142-59 142-157 105-213 39z m500-286q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12h-3q-8 0-15 6t-9 15v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 97 67 179t182 130 251 48 251-48 182-130 67-179z" horiz-adv-x="1000" /> - -<glyph glyph-name="bell-alt" unicode="" d="M509-89q0 8-9 8-33 0-57 24t-23 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m455 160q0-29-21-50t-50-21h-250q0-59-42-101t-101-42-101 42-42 101h-250q-29 0-50 21t-21 50q28 24 51 49t47 67 42 89 27 115 11 145q0 84 66 157t171 89q-5 10-5 21 0 23 16 38t38 16 38-16 16-38q0-11-5-21 106-16 171-89t66-157q0-78 11-145t28-115 41-89 48-67 50-49z" horiz-adv-x="1000" /> - -<glyph glyph-name="plus-squared" unicode="" d="M714 321v72q0 14-10 25t-25 10h-179v179q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-179h-178q-15 0-25-10t-11-25v-72q0-14 11-25t25-10h178v-179q0-14 11-25t25-11h71q15 0 25 11t11 25v179h179q14 0 25 10t10 25z m143 304v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> - -<glyph glyph-name="reply" unicode="" d="M1000 232q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" /> - -<glyph glyph-name="smile" unicode="" d="M633 257q-21-67-77-109t-127-41-128 41-77 109q-4 14 3 27t21 18q14 4 27-2t17-22q14-44 52-72t85-28 84 28 52 72q4 15 18 22t27 2 21-18 2-27z m-276 243q0-30-21-51t-50-21-51 21-21 51 21 50 51 21 50-21 21-50z m286 0q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m143-143q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> - -<glyph glyph-name="lock-open-alt" unicode="" d="M589 428q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-14-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" /> - -<glyph glyph-name="ellipsis" unicode="" d="M214 446v-107q0-22-15-38t-38-15h-107q-23 0-38 15t-16 38v107q0 23 16 38t38 16h107q22 0 38-16t15-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-15 38v107q0 23 15 38t38 16h107q23 0 38-16t16-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-16 38v107q0 23 16 38t38 16h107q23 0 38-16t16-38z" horiz-adv-x="785.7" /> - -<glyph glyph-name="play-circled" unicode="" d="M429 786q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m214-460q18 10 18 31t-18 31l-304 178q-17 11-35 1-18-11-18-31v-358q0-20 18-31 9-4 17-4 10 0 18 5z" horiz-adv-x="857.1" /> - -<glyph glyph-name="thumbs-up-alt" unicode="" d="M143 107q0 15-11 25t-25 11q-15 0-25-11t-11-25q0-15 11-25t25-11q15 0 25 11t11 25z m89 286v-357q0-15-10-25t-26-11h-160q-15 0-25 11t-11 25v357q0 14 11 25t25 10h160q15 0 26-10t10-25z m661 0q0-48-31-83 9-25 9-43 1-42-24-76 9-31 0-66-9-31-31-52 5-62-27-101-36-43-110-44h-72q-37 0-80 9t-68 16-67 22q-69 24-88 25-15 0-25 11t-11 25v357q0 14 10 25t24 11q13 1 42 33t57 67q38 49 56 67 10 10 17 27t10 27 8 34q4 22 7 34t11 29 19 28q10 11 25 11 25 0 46-6t33-15 22-22 14-25 7-28 2-25 1-22q0-21-6-43t-10-33-16-31q-1-4-5-10t-6-13-5-13h155q43 0 75-32t32-75z" horiz-adv-x="928.6" /> - -<glyph glyph-name="binoculars" unicode="" d="M393 678v-428q0-15-11-25t-25-11v-321q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v285l139 488q4 12 17 12h237z m178 0v-392h-142v392h142z m429-500v-285q0-15-11-25t-25-11h-285q-15 0-25 11t-11 25v321q-15 0-25 11t-11 25v428h237q13 0 17-12z m-589 661v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z m375 0v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z" horiz-adv-x="1000" /> - -<glyph glyph-name="user-plus" unicode="" d="M393 357q-89 0-152 63t-62 151 62 152 152 63 151-63 63-152-63-151-151-63z m536-71h196q7 0 13-6t5-12v-107q0-8-5-13t-13-5h-196v-197q0-7-6-12t-12-6h-107q-8 0-13 6t-5 12v197h-197q-7 0-12 5t-6 13v107q0 7 6 12t12 6h197v196q0 7 5 13t13 5h107q7 0 12-5t6-13v-196z m-411-125q0-29 21-51t50-21h143v-133q-38-28-95-28h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q11 0 22-10 44-34 86-51t92-17 92 17 86 51q11 10 22 10 73 0 121-54h-125q-29 0-50-21t-21-50v-107z" horiz-adv-x="1142.9" /> -</font> -</defs> -</svg> \ No newline at end of file diff --git a/static/font/font/fontello.ttf b/static/font/font/fontello.ttf deleted file mode 100755 index e9ed7803..00000000 Binary files a/static/font/font/fontello.ttf and /dev/null differ diff --git a/static/font/font/fontello.woff b/static/font/font/fontello.woff deleted file mode 100755 index 1d5025d3..00000000 Binary files a/static/font/font/fontello.woff and /dev/null differ diff --git a/static/font/font/fontello.woff2 b/static/font/font/fontello.woff2 deleted file mode 100755 index 078991eb..00000000 Binary files a/static/font/font/fontello.woff2 and /dev/null differ diff --git a/static/font/config.json b/static/fontello.json similarity index 83% rename from static/font/config.json rename to static/fontello.json index c0cf1727..ac3f0a18 100755 --- a/static/font/config.json +++ b/static/fontello.json @@ -303,6 +303,78 @@ "css": "gauge", "code": 61668, "src": "fontawesome" + }, + { + "uid": "31972e4e9d080eaa796290349ae6c1fd", + "css": "users", + "code": 59421, + "src": "fontawesome" + }, + { + "uid": "e82cedfa1d5f15b00c5a81c9bd731ea2", + "css": "info-circled", + "code": 59423, + "src": "fontawesome" + }, + { + "uid": "w3nzesrlbezu6f30q7ytyq919p6gdlb6", + "css": "home-2", + "code": 59425, + "src": "typicons" + }, + { + "uid": "dcedf50ab1ede3283d7a6c70e2fe32f3", + "css": "chat", + "code": 59422, + "src": "fontawesome" + }, + { + "uid": "3a00327e61b997b58518bd43ed83c3df", + "css": "login", + "code": 59424, + "src": "fontawesome" + }, + { + "uid": "f3ebd6751c15a280af5cc5f4a764187d", + "css": "arrow-curved", + "code": 59426, + "src": "iconic" + }, + { + "uid": "0ddd3e8201ccc7d41f7b7c9d27eca6c1", + "css": "link", + "code": 59427, + "src": "fontawesome" + }, + { + "uid": "4aad6bb50b02c18508aae9cbe14e784e", + "css": "share", + "code": 61920, + "src": "fontawesome" + }, + { + "uid": "8b80d36d4ef43889db10bc1f0dc9a862", + "css": "user", + "code": 59428, + "src": "fontawesome" + }, + { + "uid": "12f4ece88e46abd864e40b35e05b11cd", + "css": "ok", + "code": 59431, + "src": "fontawesome" + }, + { + "uid": "4109c474ff99cad28fd5a2c38af2ec6f", + "css": "filter", + "code": 61616, + "src": "fontawesome" + }, + { + "uid": "9a76bc135eac17d2c8b8ad4a5774fc87", + "css": "download", + "code": 59429, + "src": "fontawesome" } ] } \ No newline at end of file diff --git a/static/styles.json b/static/styles.json index 842092c4..23f57c65 100644 --- a/static/styles.json +++ b/static/styles.json @@ -1,6 +1,7 @@ { - "pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], - "pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], + "pleroma-dark": "/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" ], @@ -11,5 +12,6 @@ "redmond-xxi": "/static/themes/redmond-xxi.json", "breezy-dark": "/static/themes/breezy-dark.json", "breezy-light": "/static/themes/breezy-light.json", - "mammal": "/static/themes/mammal.json" + "mammal": "/static/themes/mammal.json", + "paper": "/static/themes/paper.json" } diff --git a/static/terms-of-service.html b/static/terms-of-service.html index c880b72c..b2c66815 100644 --- a/static/terms-of-service.html +++ b/static/terms-of-service.html @@ -1,8 +1,9 @@ <h4>Terms of Service</h4> -<p>This is the default placeholder ToS. You should change it to fit the needs of your instance.</p> +<p>This is the default placeholder ToS. You should copy it over to your static folder and edit it to fit the needs of your instance.</p> <p>To do so, place a file at <code>"/instance/static/terms-of-service.html"</code> in your Pleroma install containing the real ToS for your instance.</p> +<p>See the <a href='https://docs.pleroma.social/backend/configuration/static_dir/'>Pleroma documentation</a> for more information.</p> <br> -<img src="/static/logo.png" style="display: block; margin: auto;"> +<img src="/static/logo.png" style="display: block; margin: auto; max-width: 100%; height: 50px; object-fit: contain;" /> diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json index 6119bf88..76b962c5 100644 --- a/static/themes/breezy-dark.json +++ b/static/themes/breezy-dark.json @@ -1,7 +1,9 @@ { "_pleroma_theme_version": 2, "name": "Breezy Dark (beta)", - "theme": { + "source": { + "themeEngineVersion": 3, + "fonts": {}, "shadows": { "panel": [ { @@ -19,7 +21,7 @@ "y": "0", "blur": "0", "spread": "1", - "color": "#ffffff", + "color": "--btn,900", "alpha": "0.15", "inset": true }, @@ -40,7 +42,7 @@ "blur": "40", "spread": "-40", "inset": true, - "color": "#ffffff", + "color": "--panel,900", "alpha": "0.1" } ], @@ -50,8 +52,8 @@ "y": "0", "blur": 0, "spread": "1", - "color": "--link", - "alpha": "0.3", + "color": "--accent", + "alpha": "1", "inset": true }, { @@ -65,21 +67,12 @@ } ], "buttonPressed": [ - { - "x": 0, - "y": 0, - "blur": "0", - "spread": "50", - "color": "--faint", - "alpha": 1, - "inset": true - }, { "x": 0, "y": "0", "blur": 0, "spread": "1", - "color": "#ffffff", + "color": "--btn,900", "alpha": 0.2, "inset": true }, @@ -99,31 +92,30 @@ "y": "0", "blur": 0, "spread": "1", - "color": "#FFFFFF", + "color": "--input,900", "alpha": "0.2", "inset": true } ] }, - "fonts": {}, - "opacity": { - "input": "1", - "panel": "0" - }, + "opacity": {}, "colors": { "bg": "#31363b", "text": "#eff0f1", "link": "#3daee9", "fg": "#31363b", - "panel": "#31363b", - "input": "#232629", - "topBarLink": "#eff0f1", - "btn": "#31363b", + "panel": "transparent", + "input": "--bg,-6.47", + "topBarLink": "--topBarText", + "btn": "--bg", "border": "#4c545b", "cRed": "#da4453", "cBlue": "#3daee9", "cGreen": "#27ae60", - "cOrange": "#f67400" + "cOrange": "#f67400", + "btnPressed": "--accent", + "selectedMenu": "--accent", + "selectedMenuPopover": "--accent" }, "radii": { "btn": "2", diff --git a/static/themes/breezy-light.json b/static/themes/breezy-light.json index becf704f..0968fff0 100644 --- a/static/themes/breezy-light.json +++ b/static/themes/breezy-light.json @@ -1,7 +1,9 @@ { "_pleroma_theme_version": 2, "name": "Breezy Light (beta)", - "theme": { + "source": { + "themeEngineVersion": 3, + "fonts": {}, "shadows": { "panel": [ { @@ -19,7 +21,7 @@ "y": "0", "blur": "0", "spread": "1", - "color": "#000000", + "color": "--btn,900", "alpha": "0.3", "inset": true }, @@ -40,7 +42,7 @@ "blur": "40", "spread": "-40", "inset": true, - "color": "#ffffff", + "color": "--panel,900", "alpha": "0.1" } ], @@ -50,8 +52,8 @@ "y": "0", "blur": 0, "spread": "1", - "color": "--link", - "alpha": "0.3", + "color": "--accent", + "alpha": "1", "inset": true }, { @@ -65,21 +67,12 @@ } ], "buttonPressed": [ - { - "x": 0, - "y": 0, - "blur": "0", - "spread": "50", - "color": "--faint", - "alpha": 1, - "inset": true - }, { "x": 0, "y": "0", "blur": 0, "spread": "1", - "color": "#ffffff", + "color": "--btn,900", "alpha": 0.2, "inset": true }, @@ -99,31 +92,30 @@ "y": "0", "blur": 0, "spread": "1", - "color": "#000000", + "color": "--input,900", "alpha": "0.2", "inset": true } ] }, - "fonts": {}, "opacity": { "input": "1" }, "colors": { "bg": "#eff0f1", "text": "#232627", - "link": "#2980b9", - "fg": "#bcc2c7", - "panel": "#475057", - "panelText": "#fcfcfc", - "input": "#fcfcfc", - "topBar": "#475057", - "topBarLink": "#eff0f1", - "btn": "#eff0f1", + "fg": "#475057", + "accent": "#2980b9", + "input": "--bg,-6.47", + "topBarLink": "--topBarText", + "btn": "--bg", "cRed": "#da4453", "cBlue": "#2980b9", "cGreen": "#27ae60", - "cOrange": "#f67400" + "cOrange": "#f67400", + "btnPressed": "--accent", + "selectedMenu": "--accent", + "selectedMenuPopover": "--accent" }, "radii": { "btn": "2", diff --git a/static/themes/paper.json b/static/themes/paper.json new file mode 100644 index 00000000..a3b90a0a --- /dev/null +++ b/static/themes/paper.json @@ -0,0 +1,172 @@ +{ + "_pleroma_theme_version": 2, + "name": "Paper", + "source": { + "themeEngineVersion": 3, + "fonts": {}, + "shadows": { + "panel": [ + { + "x": "0", + "y": "2", + "blur": "9", + "spread": 0, + "inset": false, + "color": "#668bb2", + "alpha": "0.1" + }, + { + "x": "0", + "y": "1", + "blur": "2", + "spread": "-1", + "inset": false, + "color": "#668bb2", + "alpha": "0.1" + } + ], + "topBar": [ + { + "x": 0, + "y": "3", + "blur": "8", + "spread": 0, + "inset": false, + "color": "#3e618e", + "alpha": "0.1" + }, + { + "x": 0, + "y": "1", + "blur": "4", + "spread": 0, + "inset": false, + "color": "#3e618e", + "alpha": "0.1" + } + ], + "button": [ + { + "x": 0, + "y": "2", + "blur": "5", + "spread": 0, + "color": "#463f78", + "alpha": "0.1", + "inset": false + } + ], + "input": [ + { + "x": 0, + "y": "1", + "blur": "2", + "spread": 0, + "inset": true, + "color": "#6277b7", + "alpha": "0.1" + } + ], + "buttonHover": [ + { + "x": 0, + "y": "2", + "blur": "5", + "spread": 0, + "color": "#494949", + "alpha": "0.1" + }, + { + "x": 0, + "y": "2", + "blur": "0", + "spread": "20", + "color": "#ffffff", + "alpha": "1", + "inset": true + } + ], + "buttonPressed": [ + { + "x": 0, + "y": 0, + "blur": "4", + "spread": "0", + "color": "#494949", + "alpha": "0.8", + "inset": false + } + ], + "avatarStatus": [ + { + "x": "0", + "y": "2", + "blur": "4", + "spread": "0", + "inset": false, + "color": "#3e618e", + "alpha": "0.1" + } + ], + "avatar": [ + { + "x": 0, + "y": "2", + "blur": "5", + "spread": "0", + "color": "#3e618e", + "alpha": "0.9" + } + ], + "popup": [ + { + "x": "0", + "y": "3", + "blur": "11", + "spread": 0, + "color": "#668bb2", + "alpha": "0.2" + }, + { + "x": "0", + "y": "2", + "blur": "3", + "spread": "-1", + "color": "#668bb2", + "alpha": "0.2" + } + ] + }, + "opacity": { + "underlay": "1", + "border": "0" + }, + "colors": { + "bg": "#ffffff", + "fg": "#f6f6f6", + "text": "#494949", + "underlay": "#ffffff", + "link": "#788ca1", + "accent": "#97a0aa", + "cBlue": "#788ca1", + "cRed": "#eed7ce", + "cGreen": "#788ca1", + "cOrange": "#788ca1", + "postLink": "#788ca1", + "border": "#ffffff", + "icon": "#b6c9c4", + "panel": "#ffffff", + "topBarText": "#4b4b4b" + }, + "radii": { + "btn": "0", + "input": "0", + "checkbox": "0", + "panel": "0", + "avatar": "2", + "avatarAlt": "2", + "tooltip": "0", + "attachment": "0" + } + } +} diff --git a/static/themes/pleroma-dark.json b/static/themes/pleroma-dark.json new file mode 100644 index 00000000..2703fba1 --- /dev/null +++ b/static/themes/pleroma-dark.json @@ -0,0 +1,191 @@ +{ + "_pleroma_theme_version": 2, + "name": "Pleroma Dark", + "source": { + "themeEngineVersion": 3, + "fonts": {}, + "shadows": { + "buttonHover": [ + { + "x": 0, + "y": 0, + "blur": "1", + "spread": "2", + "color": "#b9b9ba", + "alpha": "0.4", + "inset": true + }, + { + "x": 0, + "y": 1, + "blur": 0, + "spread": 0, + "color": "#FFFFFF", + "alpha": 0.2, + "inset": true + }, + { + "x": 0, + "y": -1, + "blur": 0, + "spread": 0, + "color": "#000000", + "alpha": 0.2, + "inset": true + } + ], + "buttonPressed": [ + { + "x": 0, + "y": 0, + "blur": 4, + "spread": 0, + "color": "#000000", + "alpha": 1, + "inset": true + }, + { + "x": 0, + "y": 1, + "blur": 0, + "spread": 0, + "color": "#000000", + "alpha": 0.2, + "inset": true + }, + { + "x": 0, + "y": -1, + "blur": 0, + "spread": 0, + "color": "#FFFFFF", + "alpha": 0.2, + "inset": true + }, + { + "x": 0, + "y": 0, + "blur": "2", + "spread": 0, + "inset": false, + "color": "#000000", + "alpha": 1 + } + ], + "panelHeader": [ + { + "x": 0, + "y": "1", + "blur": "3", + "spread": 0, + "inset": false, + "color": "#000000", + "alpha": "0.4" + }, + { + "x": "0", + "y": "1", + "blur": "0", + "spread": 0, + "inset": true, + "color": "#ffffff", + "alpha": "0.2" + } + ], + "panel": [ + { + "x": "0", + "y": "0", + "blur": "3", + "spread": 0, + "color": "#000000", + "alpha": "0.5" + }, + { + "x": "0", + "y": "4", + "blur": "6", + "spread": "3", + "inset": false, + "color": "#000000", + "alpha": "0.3" + } + ], + "button": [ + { + "x": 0, + "y": 0, + "blur": 2, + "spread": 0, + "color": "#000000", + "alpha": 1 + }, + { + "x": 0, + "y": 1, + "blur": 0, + "spread": 0, + "color": "#FFFFFF", + "alpha": 0.2, + "inset": true + }, + { + "x": 0, + "y": -1, + "blur": 0, + "spread": 0, + "color": "#000000", + "alpha": 0.2, + "inset": true + } + ], + "topBar": [ + { + "x": 0, + "y": "1", + "blur": 4, + "spread": 0, + "color": "#000000", + "alpha": "0.4" + }, + { + "x": 0, + "y": "2", + "blur": "7", + "spread": 0, + "inset": false, + "color": "#000000", + "alpha": "0.3" + } + ] + }, + "opacity": { + "underlay": "0.6" + }, + "colors": { + "bg": "#0f161e", + "fg": "#151e2b", + "text": "#b9b9ba", + "underlay": "#090e14", + "accent": "#e2b188", + "cBlue": "#81beea", + "cRed": "#d31014", + "cGreen": "#5dc94a", + "cOrange": "#ffc459", + "border": "--fg,3", + "topBarText": "--text,-9.75", + "topBarLink": "--topBarText", + "btnToggled": "--accent,-24.2", + "alertErrorText": "--text,21.2", + "badgeNotification": "#e15932", + "badgeNotificationText": "#ffffff" + }, + "radii": { + "btn": "3", + "input": "3", + "panel": "3", + "avatar": "3", + "attachment": "3" + } + } +} diff --git a/static/themes/pleroma-light.json b/static/themes/pleroma-light.json new file mode 100644 index 00000000..05fc300a --- /dev/null +++ b/static/themes/pleroma-light.json @@ -0,0 +1,219 @@ +{ + "_pleroma_theme_version": 2, + "name": "Pleroma Light", + "source": { + "themeEngineVersion": 3, + "fonts": {}, + "shadows": { + "button": [ + { + "x": 0, + "y": 0, + "blur": 2, + "spread": 0, + "color": "#000000", + "alpha": "0.2" + }, + { + "x": 0, + "y": 1, + "blur": 0, + "spread": 0, + "color": "#FFFFFF", + "alpha": "0.5", + "inset": true + }, + { + "x": 0, + "y": -1, + "blur": 0, + "spread": 0, + "color": "#000000", + "alpha": 0.2, + "inset": true + } + ], + "buttonHover": [ + { + "x": 0, + "y": 0, + "blur": "2", + "spread": 0, + "color": "#000000", + "alpha": "0.2" + }, + { + "x": 0, + "y": "0", + "blur": "1", + "spread": "2", + "color": "#ffc39f", + "alpha": "1", + "inset": true + }, + { + "x": 0, + "y": -1, + "blur": 0, + "spread": 0, + "color": "#000000", + "alpha": 0.2, + "inset": true + } + ], + "input": [ + { + "x": 0, + "y": 1, + "blur": 0, + "spread": 0, + "color": "#000000", + "alpha": 0.2, + "inset": true + }, + { + "x": 0, + "y": -1, + "blur": 0, + "spread": 0, + "color": "#FFFFFF", + "alpha": 0.2, + "inset": true + }, + { + "x": 0, + "y": 0, + "blur": "2", + "inset": true, + "spread": 0, + "color": "#000000", + "alpha": "0.15" + } + ], + "panel": [ + { + "x": "0", + "y": 1, + "blur": "3", + "spread": 0, + "color": "#000000", + "alpha": "0.5" + }, + { + "x": "0", + "y": "3", + "blur": "6", + "spread": "1", + "inset": false, + "color": "#000000", + "alpha": "0.2" + } + ], + "panelHeader": [ + { + "x": 0, + "y": "1", + "blur": 0, + "spread": 0, + "inset": true, + "color": "#ffffff", + "alpha": "0.5" + }, + { + "x": 0, + "y": "1", + "blur": "3", + "spread": 0, + "inset": false, + "color": "#000000", + "alpha": "0.3" + } + ], + "buttonPressed": [ + { + "x": 0, + "y": 0, + "blur": 4, + "spread": 0, + "color": "#000000", + "alpha": "0.2" + }, + { + "x": 0, + "y": 1, + "blur": "1", + "spread": "2", + "color": "#000000", + "alpha": "0.3", + "inset": true + }, + { + "x": 0, + "y": -1, + "blur": 0, + "spread": 0, + "color": "#FFFFFF", + "alpha": 0.2, + "inset": true + } + ], + "popup": [ + { + "x": "1", + "y": "2", + "blur": "2", + "spread": 0, + "color": "#000000", + "alpha": "0.2" + }, + { + "x": "1", + "y": "3", + "blur": "7", + "spread": "0", + "inset": false, + "color": "#000000", + "alpha": "0.2" + } + ], + "avatarStatus": [ + { + "x": 0, + "y": "1", + "blur": "4", + "spread": "0", + "inset": false, + "color": "#000000", + "alpha": "0.2" + } + ] + }, + "opacity": { + "underlay": "0.4" + }, + "colors": { + "bg": "#f2f6f9", + "fg": "#d6dfed", + "text": "#304055", + "underlay": "#5d6086", + "accent": "#f55b1b", + "cBlue": "#0095ff", + "cRed": "#d31014", + "cGreen": "#0fa00f", + "cOrange": "#ffa500", + "border": "#d8e6f9", + "topBarText": "#304055", + "topBarLink": "--topBarText", + "btnToggled": "--accent,-24.2", + "input": "#dee3ed", + "badgeNotification": "#e83802" + }, + "radii": { + "btn": "3", + "input": "3", + "panel": "3", + "avatar": "3", + "attachment": "3" + } + } +} diff --git a/static/themes/redmond-xx-se.json b/static/themes/redmond-xx-se.json index 70ee89d1..7a4a29da 100644 --- a/static/themes/redmond-xx-se.json +++ b/static/themes/redmond-xx-se.json @@ -1,7 +1,8 @@ { "_pleroma_theme_version": 2, "name": "Redmond XX SE", - "theme": { + "source": { + "themeEngineVersion": 3, "shadows": { "panel": [ { @@ -268,6 +269,7 @@ "bg": "#c0c0c0", "text": "#000000", "link": "#0000ff", + "accent": "#000080", "fg": "#c0c0c0", "panel": "#000080", "panelFaint": "#c0c0c0", @@ -275,13 +277,16 @@ "topBar": "#000080", "topBarLink": "#ffffff", "btn": "#c0c0c0", + "btnToggled": "--btn", "faint": "#3f3f3f", "faintLink": "#404080", "border": "#808080", "cRed": "#FF0000", "cBlue": "#008080", "cGreen": "#008000", - "cOrange": "#808000" + "cOrange": "#808000", + "highlight": "--accent", + "selectedPost": "--bg,-10" }, "radii": { "btn": "0", diff --git a/static/themes/redmond-xx.json b/static/themes/redmond-xx.json index 4fd6a369..ff95b1e0 100644 --- a/static/themes/redmond-xx.json +++ b/static/themes/redmond-xx.json @@ -1,7 +1,8 @@ { "_pleroma_theme_version": 2, "name": "Redmond XX", - "theme": { + "source": { + "themeEngineVersion": 3, "shadows": { "panel": [ { @@ -259,6 +260,7 @@ "bg": "#c0c0c0", "text": "#000000", "link": "#0000ff", + "accent": "#000080", "fg": "#c0c0c0", "panel": "#000080", "panelFaint": "#c0c0c0", @@ -266,13 +268,16 @@ "topBar": "#000080", "topBarLink": "#ffffff", "btn": "#c0c0c0", + "btnToggled": "--btn", "faint": "#3f3f3f", "faintLink": "#404080", "border": "#808080", "cRed": "#FF0000", "cBlue": "#008080", "cGreen": "#008000", - "cOrange": "#808000" + "cOrange": "#808000", + "highlight": "--accent", + "selectedPost": "--bg,-10" }, "radii": { "btn": "0", diff --git a/static/themes/redmond-xxi.json b/static/themes/redmond-xxi.json index d10bf138..f788bdb8 100644 --- a/static/themes/redmond-xxi.json +++ b/static/themes/redmond-xxi.json @@ -1,7 +1,8 @@ { "_pleroma_theme_version": 2, "name": "Redmond XXI", - "theme": { + "source": { + "themeEngineVersion": 3, "shadows": { "panel": [ { @@ -241,6 +242,7 @@ "bg": "#d6d6ce", "text": "#000000", "link": "#0000ff", + "accent": "#0a246a", "fg": "#d6d6ce", "panel": "#042967", "panelFaint": "#FFFFFF", @@ -248,13 +250,16 @@ "topBar": "#042967", "topBarLink": "#ffffff", "btn": "#d6d6ce", + "btnToggled": "--btn", "faint": "#3f3f3f", "faintLink": "#404080", "border": "#808080", "cRed": "#c42726", "cBlue": "#6699cc", "cGreen": "#669966", - "cOrange": "#cc6633" + "cOrange": "#cc6633", + "highlight": "--accent", + "selectedPost": "--bg,-10" }, "radii": { "btn": "0", diff --git a/test/e2e/nightwatch.conf.js b/test/e2e/nightwatch.conf.js index 2fc3af0b..07d974df 100644 --- a/test/e2e/nightwatch.conf.js +++ b/test/e2e/nightwatch.conf.js @@ -1,4 +1,4 @@ -require('babel-register') +require('@babel/register') var config = require('../../config') // http://nightwatchjs.org/guide#settings-file diff --git a/test/unit/karma.conf.js b/test/unit/karma.conf.js index 8af05c24..45d74f14 100644 --- a/test/unit/karma.conf.js +++ b/test/unit/karma.conf.js @@ -5,6 +5,7 @@ // var path = require('path') var merge = require('webpack-merge') +var HtmlWebpackPlugin = require('html-webpack-plugin') var baseConfig = require('../../build/webpack.base.conf') var utils = require('../../build/utils') var webpack = require('webpack') @@ -24,6 +25,11 @@ var webpackConfig = merge(baseConfig, { plugins: [ new webpack.DefinePlugin({ 'process.env': require('../../config/test.env') + }), + new HtmlWebpackPlugin({ + filename: 'index.html', + template: 'index.html', + inject: true }) ] }) diff --git a/test/unit/specs/components/emoji_input.spec.js b/test/unit/specs/components/emoji_input.spec.js index b1b98802..045b47fd 100644 --- a/test/unit/specs/components/emoji_input.spec.js +++ b/test/unit/specs/components/emoji_input.spec.js @@ -36,7 +36,8 @@ describe('EmojiInput', () => { input.setValue(initialString) wrapper.setData({ caret: initialString.length }) wrapper.vm.insert({ insertion: '(test)', keepOpen: false }) - expect(wrapper.emitted().input[0][0]).to.eql('Testing (test) ') + const inputEvents = wrapper.emitted().input + expect(inputEvents[inputEvents.length - 1][0]).to.eql('Testing (test) ') }) it('inserts string at the end with trailing space (source has a trailing space)', () => { @@ -46,7 +47,8 @@ describe('EmojiInput', () => { input.setValue(initialString) wrapper.setData({ caret: initialString.length }) wrapper.vm.insert({ insertion: '(test)', keepOpen: false }) - expect(wrapper.emitted().input[0][0]).to.eql('Testing (test) ') + const inputEvents = wrapper.emitted().input + expect(inputEvents[inputEvents.length - 1][0]).to.eql('Testing (test) ') }) it('inserts string at the begginning without leading space', () => { @@ -56,7 +58,8 @@ describe('EmojiInput', () => { input.setValue(initialString) wrapper.setData({ caret: 0 }) wrapper.vm.insert({ insertion: '(test)', keepOpen: false }) - expect(wrapper.emitted().input[0][0]).to.eql('(test) Testing') + const inputEvents = wrapper.emitted().input + expect(inputEvents[inputEvents.length - 1][0]).to.eql('(test) Testing') }) it('inserts string between words without creating extra spaces', () => { @@ -66,7 +69,8 @@ describe('EmojiInput', () => { input.setValue(initialString) wrapper.setData({ caret: 6 }) wrapper.vm.insert({ insertion: ':ebin:', keepOpen: false }) - expect(wrapper.emitted().input[0][0]).to.eql('Spurdo :ebin: Sparde') + const inputEvents = wrapper.emitted().input + expect(inputEvents[inputEvents.length - 1][0]).to.eql('Spurdo :ebin: Sparde') }) it('inserts string between words without creating extra spaces (other caret)', () => { @@ -76,7 +80,8 @@ describe('EmojiInput', () => { input.setValue(initialString) wrapper.setData({ caret: 7 }) wrapper.vm.insert({ insertion: ':ebin:', keepOpen: false }) - expect(wrapper.emitted().input[0][0]).to.eql('Spurdo :ebin: Sparde') + const inputEvents = wrapper.emitted().input + expect(inputEvents[inputEvents.length - 1][0]).to.eql('Spurdo :ebin: Sparde') }) it('inserts string without any padding if padEmoji setting is set to false', () => { @@ -86,7 +91,8 @@ describe('EmojiInput', () => { input.setValue(initialString) wrapper.setData({ caret: initialString.length, keepOpen: false }) wrapper.vm.insert({ insertion: ':spam:' }) - expect(wrapper.emitted().input[0][0]).to.eql('Eat some spam!:spam:') + const inputEvents = wrapper.emitted().input + expect(inputEvents[inputEvents.length - 1][0]).to.eql('Eat some spam!:spam:') }) it('correctly sets caret after insertion at beginning', (done) => { diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js index 1b6a47d7..dcf066f9 100644 --- a/test/unit/specs/components/user_profile.spec.js +++ b/test/unit/specs/components/user_profile.spec.js @@ -19,6 +19,7 @@ const actions = { const testGetters = { findUser: state => getters.findUser(state.users), + relationship: state => getters.relationship(state.users), mergedConfig: state => ({ colors: '', highlight: {}, @@ -96,7 +97,8 @@ const externalProfileStore = new Vuex.Store({ credentials: '' }, usersObject: { 100: extUser }, - users: [extUser] + users: [extUser], + relationships: {} } } }) @@ -164,7 +166,8 @@ const localProfileStore = new Vuex.Store({ credentials: '' }, usersObject: { 100: localUser, 'testuser': localUser }, - users: [localUser] + users: [localUser], + relationships: {} } } }) diff --git a/test/unit/specs/modules/statuses.spec.js b/test/unit/specs/modules/statuses.spec.js index f794997b..fe42e85b 100644 --- a/test/unit/specs/modules/statuses.spec.js +++ b/test/unit/specs/modules/statuses.spec.js @@ -241,6 +241,54 @@ describe('Statuses module', () => { }) }) + describe('emojiReactions', () => { + it('increments count in existing reaction', () => { + const state = defaultState() + const status = makeMockStatus({ id: '1' }) + status.emoji_reactions = [ { name: '😂', count: 1, accounts: [] } ] + + mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) + mutations.addOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } }) + expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(2) + expect(state.allStatusesObject['1'].emoji_reactions[0].me).to.eql(true) + expect(state.allStatusesObject['1'].emoji_reactions[0].accounts[0].id).to.eql('me') + }) + + it('adds a new reaction', () => { + const state = defaultState() + const status = makeMockStatus({ id: '1' }) + status.emoji_reactions = [] + + mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) + mutations.addOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } }) + expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(1) + expect(state.allStatusesObject['1'].emoji_reactions[0].me).to.eql(true) + expect(state.allStatusesObject['1'].emoji_reactions[0].accounts[0].id).to.eql('me') + }) + + it('decreases count in existing reaction', () => { + const state = defaultState() + const status = makeMockStatus({ id: '1' }) + status.emoji_reactions = [ { name: '😂', count: 2, accounts: [{ id: 'me' }] } ] + + mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) + mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } }) + expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(1) + expect(state.allStatusesObject['1'].emoji_reactions[0].me).to.eql(false) + expect(state.allStatusesObject['1'].emoji_reactions[0].accounts).to.eql([]) + }) + + it('removes a reaction', () => { + const state = defaultState() + const status = makeMockStatus({ id: '1' }) + status.emoji_reactions = [{ name: '😂', count: 1, accounts: [{ id: 'me' }] }] + + mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) + mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } }) + expect(state.allStatusesObject['1'].emoji_reactions.length).to.eql(0) + }) + }) + describe('showNewStatuses', () => { it('resets the minId to the min of the visible statuses when adding new to visible statuses', () => { const state = defaultState() diff --git a/test/unit/specs/modules/users.spec.js b/test/unit/specs/modules/users.spec.js index eeb7afef..670acfc8 100644 --- a/test/unit/specs/modules/users.spec.js +++ b/test/unit/specs/modules/users.spec.js @@ -18,20 +18,6 @@ describe('The users module', () => { expect(state.users).to.eql([user]) expect(state.users[0].name).to.eql('Dude') }) - - it('sets a mute bit on users', () => { - const state = cloneDeep(defaultState) - const user = { id: '1', name: 'Guy' } - - mutations.addNewUsers(state, [user]) - mutations.setMuted(state, { user, muted: true }) - - expect(user.muted).to.eql(true) - - mutations.setMuted(state, { user, muted: false }) - - expect(user.muted).to.eql(false) - }) }) describe('findUser', () => { diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js index 49f378e2..166fce2b 100644 --- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js +++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js @@ -277,6 +277,19 @@ describe('API Entities normalizer', () => { expect(parsedUser).to.have.property('description_html').that.contains('<img') }) + it('adds emojis to user profile fields', () => { + const user = makeMockUserMasto({ emojis: makeMockEmojiMasto(), fields: [{ name: ':thinking:', value: ':image:' }] }) + + const parsedUser = parseUser(user) + + expect(parsedUser).to.have.property('fields_html').to.be.an('array') + + const field = parsedUser.fields_html[0] + + expect(field).to.have.property('name').that.contains('<img') + expect(field).to.have.property('value').that.contains('<img') + }) + it('adds hide_follows and hide_followers user settings', () => { const user = makeMockUserMasto({ pleroma: { hide_followers: true, hide_follows: false, hide_followers_count: false, hide_follows_count: true } }) @@ -325,9 +338,9 @@ describe('API Entities normalizer', () => { describe('MastoAPI emoji adder', () => { const emojis = makeMockEmojiMasto() - const imageHtml = '<img src="https://example.com/image.png" alt="image" title="image" class="emoji" />' + const imageHtml = '<img src="https://example.com/image.png" alt=":image:" title=":image:" class="emoji" />' .replace(/"/g, '\'') - const thinkHtml = '<img src="https://example.com/think.png" alt="thinking" title="thinking" class="emoji" />' + const thinkHtml = '<img src="https://example.com/think.png" alt=":thinking:" title=":thinking:" class="emoji" />' .replace(/"/g, '\'') it('correctly replaces shortcodes in supplied string', () => { @@ -353,8 +366,8 @@ describe('API Entities normalizer', () => { shortcode: '[a-z] {|}*' }]) const result = addEmojis('This post has :c++: emoji and :[a-z] {|}*: emoji', emojis) - expect(result).to.include('title=\'c++\'') - expect(result).to.include('title=\'[a-z] {|}*\'') + expect(result).to.include('title=\':c++:\'') + expect(result).to.include('title=\':[a-z] {|}*:\'') }) }) }) diff --git a/test/unit/specs/services/notification_utils/notification_utils.spec.js b/test/unit/specs/services/notification_utils/notification_utils.spec.js index 1baa5fc9..00628d83 100644 --- a/test/unit/specs/services/notification_utils/notification_utils.spec.js +++ b/test/unit/specs/services/notification_utils/notification_utils.spec.js @@ -1,7 +1,7 @@ import * as NotificationUtils from 'src/services/notification_utils/notification_utils.js' describe('NotificationUtils', () => { - describe('visibleNotificationsFromStore', () => { + describe('filteredNotificationsFromStore', () => { it('should return sorted notifications with configured types', () => { const store = { state: { @@ -47,7 +47,7 @@ describe('NotificationUtils', () => { type: 'like' } ] - expect(NotificationUtils.visibleNotificationsFromStore(store)).to.eql(expected) + expect(NotificationUtils.filteredNotificationsFromStore(store)).to.eql(expected) }) }) diff --git a/test/unit/specs/services/status_parser/status_parses.spec.js b/test/unit/specs/services/status_parser/status_parses.spec.js deleted file mode 100644 index 7afd5042..00000000 --- a/test/unit/specs/services/status_parser/status_parses.spec.js +++ /dev/null @@ -1,17 +0,0 @@ -import { removeAttachmentLinks } from '../../../../../src/services/status_parser/status_parser.js' - -const example = '<div class="status-content">@<a href="https://sealion.club/user/4" class="h-card mention" title="dewoo">dwmatiz</a> <a href="https://social.heldscal.la/file/3deb764ada10ce64a61b7a070b75dac45f86d2d5bf213bf18873da71d8714d86.png" title="https://social.heldscal.la/file/3deb764ada10ce64a61b7a070b75dac45f86d2d5bf213bf18873da71d8714d86.png" class="attachment" id="attachment-159853" rel="nofollow external">https://social.heldscal.la/attachment/159853</a></div>' - -describe('statusParser.removeAttachmentLinks', () => { - const exampleWithoutAttachmentLinks = '<div class="status-content">@<a href="https://sealion.club/user/4" class="h-card mention" title="dewoo">dwmatiz</a> </div>' - - it('removes attachment links', () => { - const parsed = removeAttachmentLinks(example) - expect(parsed).to.eql(exampleWithoutAttachmentLinks) - }) - - it('works when the class is empty', () => { - const parsed = removeAttachmentLinks('<a></a>') - expect(parsed).to.eql('<a></a>') - }) -}) diff --git a/test/unit/specs/services/theme_data/sanity_checks.spec.js b/test/unit/specs/services/theme_data/sanity_checks.spec.js new file mode 100644 index 00000000..f0072e7d --- /dev/null +++ b/test/unit/specs/services/theme_data/sanity_checks.spec.js @@ -0,0 +1,28 @@ +import { getColors } from 'src/services/theme_data/theme_data.service.js' + +const checkColors = (output) => { + expect(output).to.have.property('colors') + Object.entries(output.colors).forEach(([key, v]) => { + expect(v, key).to.be.an('object') + expect(v, key).to.include.all.keys('r', 'g', 'b') + 'rgba'.split('').forEach(k => { + if ((k === 'a' && v.hasOwnProperty('a')) || k !== 'a') { + expect(v[k], key + '.' + k).to.be.a('number') + expect(v[k], key + '.' + k).to.be.least(0) + expect(v[k], key + '.' + k).to.be.most(k === 'a' ? 1 : 255) + } + }) + }) +} + +describe('Theme Data utility functions', () => { + const context = require.context('static/themes/', false, /\.json$/) + context.keys().forEach((key) => { + it(`Should render all colors for ${key} properly`, () => { + const { theme, source } = context(key) + const data = source || theme + const colors = getColors(data.colors, data.opacity, 1) + checkColors(colors) + }) + }) +}) diff --git a/test/unit/specs/services/theme_data/theme_data.spec.js b/test/unit/specs/services/theme_data/theme_data.spec.js new file mode 100644 index 00000000..c634cade --- /dev/null +++ b/test/unit/specs/services/theme_data/theme_data.spec.js @@ -0,0 +1,89 @@ +import { getLayersArray, topoSort } from 'src/services/theme_data/theme_data.service.js' + +describe('Theme Data utility functions', () => { + describe('getLayersArray', () => { + const fixture = { + layer1: null, + layer2: 'layer1', + layer3a: 'layer2', + layer3b: 'layer2' + } + + it('should expand layers properly (3b)', () => { + const out = getLayersArray('layer3b', fixture) + expect(out).to.eql(['layer1', 'layer2', 'layer3b']) + }) + + it('should expand layers properly (3a)', () => { + const out = getLayersArray('layer3a', fixture) + expect(out).to.eql(['layer1', 'layer2', 'layer3a']) + }) + + it('should expand layers properly (2)', () => { + const out = getLayersArray('layer2', fixture) + expect(out).to.eql(['layer1', 'layer2']) + }) + + it('should expand layers properly (1)', () => { + const out = getLayersArray('layer1', fixture) + expect(out).to.eql(['layer1']) + }) + }) + + describe('topoSort', () => { + const fixture1 = { + layerA: [], + layer1A: ['layerA'], + layer2A: ['layer1A'], + layerB: [], + layer1B: ['layerB'], + layer2B: ['layer1B'], + layer3AB: ['layer2B', 'layer2A'] + } + + // Same thing but messed up order + const fixture2 = { + layer1A: ['layerA'], + layer1B: ['layerB'], + layer2A: ['layer1A'], + layerB: [], + layer3AB: ['layer2B', 'layer2A'], + layer2B: ['layer1B'], + layerA: [] + } + + it('should make a topologically sorted array', () => { + const out = topoSort(fixture1, (node, inheritance) => inheritance[node]) + // This basically checks all ordering that matters + expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A')) + expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A')) + expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B')) + expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B')) + expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB')) + expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB')) + }) + + it('order in object shouldn\'t matter', () => { + const out = topoSort(fixture2, (node, inheritance) => inheritance[node]) + // This basically checks all ordering that matters + expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A')) + expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A')) + expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B')) + expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B')) + expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB')) + expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB')) + }) + + it('dependentless nodes should be first', () => { + const out = topoSort(fixture2, (node, inheritance) => inheritance[node]) + // This basically checks all ordering that matters + expect(out.indexOf('layerA')).to.eql(0) + expect(out.indexOf('layerB')).to.eql(1) + }) + + it('ignores cyclic dependencies', () => { + const out = topoSort({ a: 'b', b: 'a', c: 'a' }, (node, inheritance) => [inheritance[node]]) + expect(out.indexOf('a')).to.be.below(out.indexOf('c')) + }) + }) +}) diff --git a/test/unit/specs/services/tiny_post_html_processor/tiny_post_html_processor.spec.js b/test/unit/specs/services/tiny_post_html_processor/tiny_post_html_processor.spec.js new file mode 100644 index 00000000..f301429d --- /dev/null +++ b/test/unit/specs/services/tiny_post_html_processor/tiny_post_html_processor.spec.js @@ -0,0 +1,96 @@ +import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js' + +describe('TinyPostHTMLProcessor', () => { + describe('with processor that keeps original line should not make any changes to HTML when', () => { + const processorKeep = (line) => line + it('fed with regular HTML with newlines', () => { + const inputOutput = '1<br/>2<p class="lol">3 4</p> 5 \n 6 <p > 7 <br> 8 </p> <br>\n<br/>' + expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput) + }) + + it('fed with possibly broken HTML with invalid tags/composition', () => { + const inputOutput = '<feeee dwdwddddddw> <i>ayy<b>lm</i>ao</b> </section>' + expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput) + }) + + it('fed with very broken HTML with broken composition', () => { + const inputOutput = '</p> lmao what </div> whats going on <div> wha <p>' + expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput) + }) + + it('fed with sorta valid HTML but tags aren\'t closed', () => { + const inputOutput = 'just leaving a <div> hanging' + expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput) + }) + + it('fed with not really HTML at this point... tags that aren\'t finished', () => { + const inputOutput = 'do you expect me to finish this <div class=' + expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput) + }) + + it('fed with dubiously valid HTML (p within p and also div inside p)', () => { + const inputOutput = 'look ma <p> p \nwithin <p> p! </p> and a <br/><div>div!</div></p>' + expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput) + }) + + it('fed with maybe valid HTML? self-closing divs and ps', () => { + const inputOutput = 'a <div class="what"/> what now <p aria-label="wtf"/> ?' + expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput) + }) + + it('fed with valid XHTML containing a CDATA', () => { + const inputOutput = 'Yes, it is me, <![CDATA[DIO]]>' + expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput) + }) + }) + describe('with processor that replaces lines with word "_" should match expected line when', () => { + const processorReplace = (line) => '_' + it('fed with regular HTML with newlines', () => { + const input = '1<br/>2<p class="lol">3 4</p> 5 \n 6 <p > 7 <br> 8 </p> <br>\n<br/>' + const output = '_<br/>_<p class="lol">_</p>_\n_<p >_<br>_</p> <br>\n<br/>' + expect(processHtml(input, processorReplace)).to.eql(output) + }) + + it('fed with possibly broken HTML with invalid tags/composition', () => { + const input = '<feeee dwdwddddddw> <i>ayy<b>lm</i>ao</b> </section>' + const output = '_' + expect(processHtml(input, processorReplace)).to.eql(output) + }) + + it('fed with very broken HTML with broken composition', () => { + const input = '</p> lmao what </div> whats going on <div> wha <p>' + const output = '</p>_</div>_<div>_<p>' + expect(processHtml(input, processorReplace)).to.eql(output) + }) + + it('fed with sorta valid HTML but tags aren\'t closed', () => { + const input = 'just leaving a <div> hanging' + const output = '_<div>_' + expect(processHtml(input, processorReplace)).to.eql(output) + }) + + it('fed with not really HTML at this point... tags that aren\'t finished', () => { + const input = 'do you expect me to finish this <div class=' + const output = '_' + expect(processHtml(input, processorReplace)).to.eql(output) + }) + + it('fed with dubiously valid HTML (p within p and also div inside p)', () => { + const input = 'look ma <p> p \nwithin <p> p! </p> and a <br/><div>div!</div></p>' + const output = '_<p>_\n_<p>_</p>_<br/><div>_</div></p>' + expect(processHtml(input, processorReplace)).to.eql(output) + }) + + it('fed with maybe valid HTML? self-closing divs and ps', () => { + const input = 'a <div class="what"/> what now <p aria-label="wtf"/> ?' + const output = '_<div class="what"/>_<p aria-label="wtf"/>_' + expect(processHtml(input, processorReplace)).to.eql(output) + }) + + it('fed with valid XHTML containing a CDATA', () => { + const input = 'Yes, it is me, <![CDATA[DIO]]>' + const output = '_' + expect(processHtml(input, processorReplace)).to.eql(output) + }) + }) +}) diff --git a/yarn.lock b/yarn.lock index 4e0d9a22..f05b00b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8,12 +8,221 @@ dependencies: "@babel/highlight" "^7.0.0" +"@babel/code-frame@^7.5.5": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" + integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/core@^7.7.5": + version "7.7.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.5.tgz#ae1323cd035b5160293307f50647e83f8ba62f7e" + integrity sha512-M42+ScN4+1S9iB6f+TL7QBpoQETxbclx+KNoKJABghnKYE+fMzSGqst0BZJc8CpI625bwPwYgUyRvxZ+0mZzpw== + dependencies: + "@babel/code-frame" "^7.5.5" + "@babel/generator" "^7.7.4" + "@babel/helpers" "^7.7.4" + "@babel/parser" "^7.7.5" + "@babel/template" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" + convert-source-map "^1.7.0" + debug "^4.1.0" + json5 "^2.1.0" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.4.tgz#db651e2840ca9aa66f327dcec1dc5f5fa9611369" + integrity sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg== + dependencies: + "@babel/types" "^7.7.4" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.7.4.tgz#bb3faf1e74b74bd547e867e48f551fa6b098b6ce" + integrity sha512-2BQmQgECKzYKFPpiycoF9tlb5HA4lrVyAmLLVK177EcQAqjVLciUb2/R+n1boQ9y5ENV3uz2ZqiNw7QMBBw1Og== + dependencies: + "@babel/types" "^7.7.4" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.7.4.tgz#5f73f2b28580e224b5b9bd03146a4015d6217f5f" + integrity sha512-Biq/d/WtvfftWZ9Uf39hbPBYDUo986m5Bb4zhkeYDGUllF43D+nUe5M6Vuo6/8JDK/0YX/uBdeoQpyaNhNugZQ== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.7.4" + "@babel/types" "^7.7.4" + +"@babel/helper-call-delegate@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.7.4.tgz#621b83e596722b50c0066f9dc37d3232e461b801" + integrity sha512-8JH9/B7J7tCYJ2PpWVpw9JhPuEVHztagNVuQAFBVFYluRMlpG7F1CgKEgGeL6KFqcsIa92ZYVj6DSc0XwmN1ZA== + dependencies: + "@babel/helper-hoist-variables" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" + +"@babel/helper-create-regexp-features-plugin@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.7.4.tgz#6d5762359fd34f4da1500e4cff9955b5299aaf59" + integrity sha512-Mt+jBKaxL0zfOIWrfQpnfYCN7/rS6GKx6CCCfuoqVVd+17R8zNDlzVYmIi9qyb2wOk002NsmSTDymkIygDUH7A== + dependencies: + "@babel/helper-regex" "^7.4.4" + regexpu-core "^4.6.0" + +"@babel/helper-define-map@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.7.4.tgz#2841bf92eb8bd9c906851546fe6b9d45e162f176" + integrity sha512-v5LorqOa0nVQUvAUTUF3KPastvUt/HzByXNamKQ6RdJRTV7j8rLL+WB5C/MzzWAwOomxDhYFb1wLLxHqox86lg== + dependencies: + "@babel/helper-function-name" "^7.7.4" + "@babel/types" "^7.7.4" + lodash "^4.17.13" + +"@babel/helper-explode-assignable-expression@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.7.4.tgz#fa700878e008d85dc51ba43e9fb835cddfe05c84" + integrity sha512-2/SicuFrNSXsZNBxe5UGdLr+HZg+raWBLE9vC98bdYOKX/U6PY0mdGlYUJdtTDPSU0Lw0PNbKKDpwYHJLn2jLg== + dependencies: + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" + +"@babel/helper-function-name@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz#ab6e041e7135d436d8f0a3eca15de5b67a341a2e" + integrity sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ== + dependencies: + "@babel/helper-get-function-arity" "^7.7.4" + "@babel/template" "^7.7.4" + "@babel/types" "^7.7.4" + +"@babel/helper-get-function-arity@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz#cb46348d2f8808e632f0ab048172130e636005f0" + integrity sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA== + dependencies: + "@babel/types" "^7.7.4" + +"@babel/helper-hoist-variables@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.7.4.tgz#612384e3d823fdfaaf9fce31550fe5d4db0f3d12" + integrity sha512-wQC4xyvc1Jo/FnLirL6CEgPgPCa8M74tOdjWpRhQYapz5JC7u3NYU1zCVoVAGCE3EaIP9T1A3iW0WLJ+reZlpQ== + dependencies: + "@babel/types" "^7.7.4" + +"@babel/helper-member-expression-to-functions@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.7.4.tgz#356438e2569df7321a8326644d4b790d2122cb74" + integrity sha512-9KcA1X2E3OjXl/ykfMMInBK+uVdfIVakVe7W7Lg3wfXUNyS3Q1HWLFRwZIjhqiCGbslummPDnmb7vIekS0C1vw== + dependencies: + "@babel/types" "^7.7.4" + +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz#e5a92529f8888bf319a6376abfbd1cebc491ad91" + integrity sha512-dGcrX6K9l8258WFjyDLJwuVKxR4XZfU0/vTUgOQYWEnRD8mgr+p4d6fCUMq/ys0h4CCt/S5JhbvtyErjWouAUQ== + dependencies: + "@babel/types" "^7.7.4" + "@babel/helper-module-imports@^7.0.0-beta.49": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d" dependencies: "@babel/types" "^7.0.0" +"@babel/helper-module-transforms@^7.7.4", "@babel/helper-module-transforms@^7.7.5": + version "7.7.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.7.5.tgz#d044da7ffd91ec967db25cd6748f704b6b244835" + integrity sha512-A7pSxyJf1gN5qXVcidwLWydjftUN878VkalhXX5iQDuGyiGK3sOrrKKHF4/A4fwHtnsotv/NipwAeLzY4KQPvw== + dependencies: + "@babel/helper-module-imports" "^7.7.4" + "@babel/helper-simple-access" "^7.7.4" + "@babel/helper-split-export-declaration" "^7.7.4" + "@babel/template" "^7.7.4" + "@babel/types" "^7.7.4" + lodash "^4.17.13" + +"@babel/helper-optimise-call-expression@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.7.4.tgz#034af31370d2995242aa4df402c3b7794b2dcdf2" + integrity sha512-VB7gWZ2fDkSuqW6b1AKXkJWO5NyNI3bFL/kK79/30moK57blr6NbH8xcl2XcKCwOmJosftWunZqfO84IGq3ZZg== + dependencies: + "@babel/types" "^7.7.4" + +"@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-regex@^7.0.0", "@babel/helper-regex@^7.4.4": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.5.5.tgz#0aa6824f7100a2e0e89c1527c23936c152cab351" + integrity sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw== + dependencies: + lodash "^4.17.13" + +"@babel/helper-remap-async-to-generator@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.7.4.tgz#c68c2407350d9af0e061ed6726afb4fff16d0234" + integrity sha512-Sk4xmtVdM9sA/jCI80f+KS+Md+ZHIpjuqmYPk1M7F/upHou5e4ReYmExAiu6PVe65BhJPZA2CY9x9k4BqE5klw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.7.4" + "@babel/helper-wrap-function" "^7.7.4" + "@babel/template" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" + +"@babel/helper-replace-supers@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.7.4.tgz#3c881a6a6a7571275a72d82e6107126ec9e2cdd2" + integrity sha512-pP0tfgg9hsZWo5ZboYGuBn/bbYT/hdLPVSS4NMmiRJdwWhP0IznPwN9AE1JwyGsjSPLC364I0Qh5p+EPkGPNpg== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.7.4" + "@babel/helper-optimise-call-expression" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" + +"@babel/helper-simple-access@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.7.4.tgz#a169a0adb1b5f418cfc19f22586b2ebf58a9a294" + integrity sha512-zK7THeEXfan7UlWsG2A6CI/L9jVnI5+xxKZOdej39Y0YtDYKx9raHk5F2EtK9K8DHRTihYwg20ADt9S36GR78A== + dependencies: + "@babel/template" "^7.7.4" + "@babel/types" "^7.7.4" + +"@babel/helper-split-export-declaration@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz#57292af60443c4a3622cf74040ddc28e68336fd8" + integrity sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug== + dependencies: + "@babel/types" "^7.7.4" + +"@babel/helper-wrap-function@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.7.4.tgz#37ab7fed5150e22d9d7266e830072c0cdd8baace" + integrity sha512-VsfzZt6wmsocOaVU0OokwrIytHND55yvyT4BPB9AIIgwr8+x7617hetdJTsuGwygN5RC6mxA9EJztTjuwm2ofg== + dependencies: + "@babel/helper-function-name" "^7.7.4" + "@babel/template" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" + +"@babel/helpers@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.7.4.tgz#62c215b9e6c712dadc15a9a0dcab76c92a940302" + integrity sha512-ak5NGZGJ6LV85Q1Zc9gn2n+ayXOizryhjSUBTdu5ih1tlVCJeuQENzc4ItyCVhINVXvIT/ZQ4mheGIsfBkpskg== + dependencies: + "@babel/template" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" + "@babel/highlight@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" @@ -22,12 +231,461 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/polyfill@^7.0.0": - version "7.2.5" - resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.2.5.tgz#6c54b964f71ad27edddc567d065e57e87ed7fa7d" +"@babel/parser@^7.7.4", "@babel/parser@^7.7.5": + version "7.7.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.5.tgz#cbf45321619ac12d83363fcf9c94bb67fa646d71" + integrity sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig== + +"@babel/plugin-proposal-async-generator-functions@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.7.4.tgz#0351c5ac0a9e927845fffd5b82af476947b7ce6d" + integrity sha512-1ypyZvGRXriY/QP668+s8sFr2mqinhkRDMPSQLNghCQE+GAkFtp+wkHVvg2+Hdki8gwP+NFzJBJ/N1BfzCCDEw== dependencies: - core-js "^2.5.7" - regenerator-runtime "^0.12.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-remap-async-to-generator" "^7.7.4" + "@babel/plugin-syntax-async-generators" "^7.7.4" + +"@babel/plugin-proposal-dynamic-import@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.7.4.tgz#dde64a7f127691758cbfed6cf70de0fa5879d52d" + integrity sha512-StH+nGAdO6qDB1l8sZ5UBV8AC3F2VW2I8Vfld73TMKyptMU9DY5YsJAS8U81+vEtxcH3Y/La0wG0btDrhpnhjQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-dynamic-import" "^7.7.4" + +"@babel/plugin-proposal-json-strings@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.7.4.tgz#7700a6bfda771d8dc81973249eac416c6b4c697d" + integrity sha512-wQvt3akcBTfLU/wYoqm/ws7YOAQKu8EVJEvHip/mzkNtjaclQoCCIqKXFP5/eyfnfbQCDV3OLRIK3mIVyXuZlw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-json-strings" "^7.7.4" + +"@babel/plugin-proposal-object-rest-spread@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.7.4.tgz#cc57849894a5c774214178c8ab64f6334ec8af71" + integrity sha512-rnpnZR3/iWKmiQyJ3LKJpSwLDcX/nSXhdLk4Aq/tXOApIvyu7qoabrige0ylsAJffaUC51WiBu209Q0U+86OWQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.7.4" + +"@babel/plugin-proposal-optional-catch-binding@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.7.4.tgz#ec21e8aeb09ec6711bc0a39ca49520abee1de379" + integrity sha512-DyM7U2bnsQerCQ+sejcTNZh8KQEUuC3ufzdnVnSiUv/qoGJp2Z3hanKL18KDhsBT5Wj6a7CMT5mdyCNJsEaA9w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.7.4" + +"@babel/plugin-proposal-unicode-property-regex@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.7.4.tgz#7c239ccaf09470dbe1d453d50057460e84517ebb" + integrity sha512-cHgqHgYvffluZk85dJ02vloErm3Y6xtH+2noOBOJ2kXOJH3aVCDnj5eR/lVNlTnYu4hndAPJD3rTFjW3qee0PA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-async-generators@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.7.4.tgz#331aaf310a10c80c44a66b238b6e49132bd3c889" + integrity sha512-Li4+EjSpBgxcsmeEF8IFcfV/+yJGxHXDirDkEoyFjumuwbmfCVHUt0HuowD/iGM7OhIRyXJH9YXxqiH6N815+g== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-dynamic-import@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.7.4.tgz#29ca3b4415abfe4a5ec381e903862ad1a54c3aec" + integrity sha512-jHQW0vbRGvwQNgyVxwDh4yuXu4bH1f5/EICJLAhl1SblLs2CDhrsmCk+v5XLdE9wxtAFRyxx+P//Iw+a5L/tTg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-json-strings@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.7.4.tgz#86e63f7d2e22f9e27129ac4e83ea989a382e86cc" + integrity sha512-QpGupahTQW1mHRXddMG5srgpHWqRLwJnJZKXTigB9RPFCCGbDGCgBeM/iC82ICXp414WeYx/tD54w7M2qRqTMg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-jsx@^7.2.0": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.7.4.tgz#dab2b56a36fb6c3c222a1fbc71f7bf97f327a9ec" + integrity sha512-wuy6fiMe9y7HeZBWXYCGt2RGxZOj0BImZ9EyXJVnVGBKO/Br592rbR3rtIQn0eQhAk9vqaKP5n8tVqEFBQMfLg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-object-rest-spread@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.7.4.tgz#47cf220d19d6d0d7b154304701f468fc1cc6ff46" + integrity sha512-mObR+r+KZq0XhRVS2BrBKBpr5jqrqzlPvS9C9vuOf5ilSwzloAl7RPWLrgKdWS6IreaVrjHxTjtyqFiOisaCwg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.7.4.tgz#a3e38f59f4b6233867b4a92dcb0ee05b2c334aa6" + integrity sha512-4ZSuzWgFxqHRE31Glu+fEr/MirNZOMYmD/0BhBWyLyOOQz/gTAl7QmWm2hX1QxEIXsr2vkdlwxIzTyiYRC4xcQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-top-level-await@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.7.4.tgz#bd7d8fa7b9fee793a36e4027fd6dd1aa32f946da" + integrity sha512-wdsOw0MvkL1UIgiQ/IFr3ETcfv1xb8RMM0H9wbiDyLaJFyiDg5oZvDLCXosIXmFeIlweML5iOBXAkqddkYNizg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-arrow-functions@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.7.4.tgz#76309bd578addd8aee3b379d809c802305a98a12" + integrity sha512-zUXy3e8jBNPiffmqkHRNDdZM2r8DWhCB7HhcoyZjiK1TxYEluLHAvQuYnTT+ARqRpabWqy/NHkO6e3MsYB5YfA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-async-to-generator@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.7.4.tgz#694cbeae6d613a34ef0292713fa42fb45c4470ba" + integrity sha512-zpUTZphp5nHokuy8yLlyafxCJ0rSlFoSHypTUWgpdwoDXWQcseaect7cJ8Ppk6nunOM6+5rPMkod4OYKPR5MUg== + dependencies: + "@babel/helper-module-imports" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-remap-async-to-generator" "^7.7.4" + +"@babel/plugin-transform-block-scoped-functions@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.7.4.tgz#d0d9d5c269c78eaea76227ace214b8d01e4d837b" + integrity sha512-kqtQzwtKcpPclHYjLK//3lH8OFsCDuDJBaFhVwf8kqdnF6MN4l618UDlcA7TfRs3FayrHj+svYnSX8MC9zmUyQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-block-scoping@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.7.4.tgz#200aad0dcd6bb80372f94d9e628ea062c58bf224" + integrity sha512-2VBe9u0G+fDt9B5OV5DQH4KBf5DoiNkwFKOz0TCvBWvdAN2rOykCTkrL+jTLxfCAm76l9Qo5OqL7HBOx2dWggg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + lodash "^4.17.13" + +"@babel/plugin-transform-classes@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.7.4.tgz#c92c14be0a1399e15df72667067a8f510c9400ec" + integrity sha512-sK1mjWat7K+buWRuImEzjNf68qrKcrddtpQo3swi9j7dUcG6y6R6+Di039QN2bD1dykeswlagupEmpOatFHHUg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.7.4" + "@babel/helper-define-map" "^7.7.4" + "@babel/helper-function-name" "^7.7.4" + "@babel/helper-optimise-call-expression" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.7.4" + "@babel/helper-split-export-declaration" "^7.7.4" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.7.4.tgz#e856c1628d3238ffe12d668eb42559f79a81910d" + integrity sha512-bSNsOsZnlpLLyQew35rl4Fma3yKWqK3ImWMSC/Nc+6nGjC9s5NFWAer1YQ899/6s9HxO2zQC1WoFNfkOqRkqRQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-destructuring@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.7.4.tgz#2b713729e5054a1135097b6a67da1b6fe8789267" + integrity sha512-4jFMXI1Cu2aXbcXXl8Lr6YubCn6Oc7k9lLsu8v61TZh+1jny2BWmdtvY9zSUlLdGUvcy9DMAWyZEOqjsbeg/wA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-dotall-regex@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.7.4.tgz#f7ccda61118c5b7a2599a72d5e3210884a021e96" + integrity sha512-mk0cH1zyMa/XHeb6LOTXTbG7uIJ8Rrjlzu91pUx/KS3JpcgaTDwMS8kM+ar8SLOvlL2Lofi4CGBAjCo3a2x+lw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-duplicate-keys@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.7.4.tgz#3d21731a42e3f598a73835299dd0169c3b90ac91" + integrity sha512-g1y4/G6xGWMD85Tlft5XedGaZBCIVN+/P0bs6eabmcPP9egFleMAo65OOjlhcz1njpwagyY3t0nsQC9oTFegJA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-exponentiation-operator@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.7.4.tgz#dd30c0191e3a1ba19bcc7e389bdfddc0729d5db9" + integrity sha512-MCqiLfCKm6KEA1dglf6Uqq1ElDIZwFuzz1WH5mTf8k2uQSxEJMbOIEh7IZv7uichr7PMfi5YVSrr1vz+ipp7AQ== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-for-of@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.7.4.tgz#248800e3a5e507b1f103d8b4ca998e77c63932bc" + integrity sha512-zZ1fD1B8keYtEcKF+M1TROfeHTKnijcVQm0yO/Yu1f7qoDoxEIc/+GX6Go430Bg84eM/xwPFp0+h4EbZg7epAA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-function-name@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.7.4.tgz#75a6d3303d50db638ff8b5385d12451c865025b1" + integrity sha512-E/x09TvjHNhsULs2IusN+aJNRV5zKwxu1cpirZyRPw+FyyIKEHPXTsadj48bVpc1R5Qq1B5ZkzumuFLytnbT6g== + dependencies: + "@babel/helper-function-name" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-literals@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.7.4.tgz#27fe87d2b5017a2a5a34d1c41a6b9f6a6262643e" + integrity sha512-X2MSV7LfJFm4aZfxd0yLVFrEXAgPqYoDG53Br/tCKiKYfX0MjVjQeWPIhPHHsCqzwQANq+FLN786fF5rgLS+gw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-member-expression-literals@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.7.4.tgz#aee127f2f3339fc34ce5e3055d7ffbf7aa26f19a" + integrity sha512-9VMwMO7i69LHTesL0RdGy93JU6a+qOPuvB4F4d0kR0zyVjJRVJRaoaGjhtki6SzQUu8yen/vxPKN6CWnCUw6bA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-modules-amd@^7.7.5": + version "7.7.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.5.tgz#39e0fb717224b59475b306402bb8eedab01e729c" + integrity sha512-CT57FG4A2ZUNU1v+HdvDSDrjNWBrtCmSH6YbbgN3Lrf0Di/q/lWRxZrE72p3+HCCz9UjfZOEBdphgC0nzOS6DQ== + dependencies: + "@babel/helper-module-transforms" "^7.7.5" + "@babel/helper-plugin-utils" "^7.0.0" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-commonjs@^7.7.5": + version "7.7.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.5.tgz#1d27f5eb0bcf7543e774950e5b2fa782e637b345" + integrity sha512-9Cq4zTFExwFhQI6MT1aFxgqhIsMWQWDVwOgLzl7PTWJHsNaqFvklAU+Oz6AQLAS0dJKTwZSOCo20INwktxpi3Q== + dependencies: + "@babel/helper-module-transforms" "^7.7.5" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-simple-access" "^7.7.4" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-systemjs@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.7.4.tgz#cd98152339d3e763dfe838b7d4273edaf520bb30" + integrity sha512-y2c96hmcsUi6LrMqvmNDPBBiGCiQu0aYqpHatVVu6kD4mFEXKjyNxd/drc18XXAf9dv7UXjrZwBVmTTGaGP8iw== + dependencies: + "@babel/helper-hoist-variables" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-umd@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.7.4.tgz#1027c355a118de0aae9fee00ad7813c584d9061f" + integrity sha512-u2B8TIi0qZI4j8q4C51ktfO7E3cQ0qnaXFI1/OXITordD40tt17g/sXqgNNCcMTcBFKrUPcGDx+TBJuZxLx7tw== + dependencies: + "@babel/helper-module-transforms" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.7.4.tgz#fb3bcc4ee4198e7385805007373d6b6f42c98220" + integrity sha512-jBUkiqLKvUWpv9GLSuHUFYdmHg0ujC1JEYoZUfeOOfNydZXp1sXObgyPatpcwjWgsdBGsagWW0cdJpX/DO2jMw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.7.4" + +"@babel/plugin-transform-new-target@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.7.4.tgz#4a0753d2d60639437be07b592a9e58ee00720167" + integrity sha512-CnPRiNtOG1vRodnsyGX37bHQleHE14B9dnnlgSeEs3ek3fHN1A1SScglTCg1sfbe7sRQ2BUcpgpTpWSfMKz3gg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-object-super@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.7.4.tgz#48488937a2d586c0148451bf51af9d7dda567262" + integrity sha512-ho+dAEhC2aRnff2JCA0SAK7V2R62zJd/7dmtoe7MHcso4C2mS+vZjn1Pb1pCVZvJs1mgsvv5+7sT+m3Bysb6eg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.7.4" + +"@babel/plugin-transform-parameters@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.7.4.tgz#da4555c97f39b51ac089d31c7380f03bca4075ce" + integrity sha512-VJwhVePWPa0DqE9vcfptaJSzNDKrWU/4FbYCjZERtmqEs05g3UMXnYMZoXja7JAJ7Y7sPZipwm/pGApZt7wHlw== + dependencies: + "@babel/helper-call-delegate" "^7.7.4" + "@babel/helper-get-function-arity" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-property-literals@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.7.4.tgz#2388d6505ef89b266103f450f9167e6bd73f98c2" + integrity sha512-MatJhlC4iHsIskWYyawl53KuHrt+kALSADLQQ/HkhTjX954fkxIEh4q5slL4oRAnsm/eDoZ4q0CIZpcqBuxhJQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-regenerator@^7.7.5": + version "7.7.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.5.tgz#3a8757ee1a2780f390e89f246065ecf59c26fce9" + integrity sha512-/8I8tPvX2FkuEyWbjRCt4qTAgZK0DVy8QRguhA524UH48RfGJy94On2ri+dCuwOpcerPRl9O4ebQkRcVzIaGBw== + dependencies: + regenerator-transform "^0.14.0" + +"@babel/plugin-transform-reserved-words@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.7.4.tgz#6a7cf123ad175bb5c69aec8f6f0770387ed3f1eb" + integrity sha512-OrPiUB5s5XvkCO1lS7D8ZtHcswIC57j62acAnJZKqGGnHP+TIc/ljQSrgdX/QyOTdEK5COAhuc820Hi1q2UgLQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-runtime@^7.7.6": + version "7.7.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.7.6.tgz#4f2b548c88922fb98ec1c242afd4733ee3e12f61" + integrity sha512-tajQY+YmXR7JjTwRvwL4HePqoL3DYxpYXIHKVvrOIvJmeHe2y1w4tz5qz9ObUDC9m76rCzIMPyn4eERuwA4a4A== + dependencies: + "@babel/helper-module-imports" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + resolve "^1.8.1" + semver "^5.5.1" + +"@babel/plugin-transform-shorthand-properties@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.7.4.tgz#74a0a9b2f6d67a684c6fbfd5f0458eb7ba99891e" + integrity sha512-q+suddWRfIcnyG5YiDP58sT65AJDZSUhXQDZE3r04AuqD6d/XLaQPPXSBzP2zGerkgBivqtQm9XKGLuHqBID6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-spread@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.7.4.tgz#aa673b356fe6b7e70d69b6e33a17fef641008578" + integrity sha512-8OSs0FLe5/80cndziPlg4R0K6HcWSM0zyNhHhLsmw/Nc5MaA49cAsnoJ/t/YZf8qkG7fD+UjTRaApVDB526d7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-sticky-regex@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.7.4.tgz#ffb68c05090c30732076b1285dc1401b404a123c" + integrity sha512-Ls2NASyL6qtVe1H1hXts9yuEeONV2TJZmplLONkMPUG158CtmnrzW5Q5teibM5UVOFjG0D3IC5mzXR6pPpUY7A== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + +"@babel/plugin-transform-template-literals@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.7.4.tgz#1eb6411736dd3fe87dbd20cc6668e5121c17d604" + integrity sha512-sA+KxLwF3QwGj5abMHkHgshp9+rRz+oY9uoRil4CyLtgEuE/88dpkeWgNk5qKVsJE9iSfly3nvHapdRiIS2wnQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-typeof-symbol@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.7.4.tgz#3174626214f2d6de322882e498a38e8371b2140e" + integrity sha512-KQPUQ/7mqe2m0B8VecdyaW5XcQYaePyl9R7IsKd+irzj6jvbhoGnRE+M0aNkyAzI07VfUQ9266L5xMARitV3wg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-unicode-regex@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.7.4.tgz#a3c0f65b117c4c81c5b6484f2a5e7b95346b83ae" + integrity sha512-N77UUIV+WCvE+5yHw+oks3m18/umd7y392Zv7mYTpFqHtkpcc+QUz+gLJNTWVlWROIWeLqY0f3OjZxV5TcXnRw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/preset-env@^7.7.6": + version "7.7.6" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.6.tgz#39ac600427bbb94eec6b27953f1dfa1d64d457b2" + integrity sha512-k5hO17iF/Q7tR9Jv8PdNBZWYW6RofxhnxKjBMc0nG4JTaWvOTiPoO/RLFwAKcA4FpmuBFm6jkoqaRJLGi0zdaQ== + dependencies: + "@babel/helper-module-imports" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-async-generator-functions" "^7.7.4" + "@babel/plugin-proposal-dynamic-import" "^7.7.4" + "@babel/plugin-proposal-json-strings" "^7.7.4" + "@babel/plugin-proposal-object-rest-spread" "^7.7.4" + "@babel/plugin-proposal-optional-catch-binding" "^7.7.4" + "@babel/plugin-proposal-unicode-property-regex" "^7.7.4" + "@babel/plugin-syntax-async-generators" "^7.7.4" + "@babel/plugin-syntax-dynamic-import" "^7.7.4" + "@babel/plugin-syntax-json-strings" "^7.7.4" + "@babel/plugin-syntax-object-rest-spread" "^7.7.4" + "@babel/plugin-syntax-optional-catch-binding" "^7.7.4" + "@babel/plugin-syntax-top-level-await" "^7.7.4" + "@babel/plugin-transform-arrow-functions" "^7.7.4" + "@babel/plugin-transform-async-to-generator" "^7.7.4" + "@babel/plugin-transform-block-scoped-functions" "^7.7.4" + "@babel/plugin-transform-block-scoping" "^7.7.4" + "@babel/plugin-transform-classes" "^7.7.4" + "@babel/plugin-transform-computed-properties" "^7.7.4" + "@babel/plugin-transform-destructuring" "^7.7.4" + "@babel/plugin-transform-dotall-regex" "^7.7.4" + "@babel/plugin-transform-duplicate-keys" "^7.7.4" + "@babel/plugin-transform-exponentiation-operator" "^7.7.4" + "@babel/plugin-transform-for-of" "^7.7.4" + "@babel/plugin-transform-function-name" "^7.7.4" + "@babel/plugin-transform-literals" "^7.7.4" + "@babel/plugin-transform-member-expression-literals" "^7.7.4" + "@babel/plugin-transform-modules-amd" "^7.7.5" + "@babel/plugin-transform-modules-commonjs" "^7.7.5" + "@babel/plugin-transform-modules-systemjs" "^7.7.4" + "@babel/plugin-transform-modules-umd" "^7.7.4" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.7.4" + "@babel/plugin-transform-new-target" "^7.7.4" + "@babel/plugin-transform-object-super" "^7.7.4" + "@babel/plugin-transform-parameters" "^7.7.4" + "@babel/plugin-transform-property-literals" "^7.7.4" + "@babel/plugin-transform-regenerator" "^7.7.5" + "@babel/plugin-transform-reserved-words" "^7.7.4" + "@babel/plugin-transform-shorthand-properties" "^7.7.4" + "@babel/plugin-transform-spread" "^7.7.4" + "@babel/plugin-transform-sticky-regex" "^7.7.4" + "@babel/plugin-transform-template-literals" "^7.7.4" + "@babel/plugin-transform-typeof-symbol" "^7.7.4" + "@babel/plugin-transform-unicode-regex" "^7.7.4" + "@babel/types" "^7.7.4" + browserslist "^4.6.0" + core-js-compat "^3.4.7" + invariant "^2.2.2" + js-levenshtein "^1.1.3" + semver "^5.5.0" + +"@babel/register@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.7.4.tgz#45a4956471a9df3b012b747f5781cc084ee8f128" + integrity sha512-/fmONZqL6ZMl9KJUYajetCrID6m0xmL4odX7v+Xvoxcv0DdbP/oO0TWIeLUCHqczQ6L6njDMqmqHFy2cp3FFsA== + dependencies: + find-cache-dir "^2.0.0" + lodash "^4.17.13" + make-dir "^2.1.0" + pirates "^4.0.0" + source-map-support "^0.5.16" + +"@babel/runtime@^7.7.6": + version "7.7.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f" + integrity sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw== + dependencies: + regenerator-runtime "^0.13.2" + +"@babel/template@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.4.tgz#428a7d9eecffe27deac0a98e23bf8e3675d2a77b" + integrity sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.4" + "@babel/types" "^7.7.4" + +"@babel/traverse@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.7.4.tgz#9c1e7c60fb679fe4fcfaa42500833333c2058558" + integrity sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw== + dependencies: + "@babel/code-frame" "^7.5.5" + "@babel/generator" "^7.7.4" + "@babel/helper-function-name" "^7.7.4" + "@babel/helper-split-export-declaration" "^7.7.4" + "@babel/parser" "^7.7.4" + "@babel/types" "^7.7.4" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" "@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49": version "7.2.2" @@ -37,12 +695,43 @@ lodash "^4.17.10" to-fast-properties "^2.0.0" +"@babel/types@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.7.4.tgz#516570d539e44ddf308c07569c258ff94fde9193" + integrity sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA== + dependencies: + esutils "^2.0.2" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + "@chenfengyuan/vue-qrcode@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@chenfengyuan/vue-qrcode/-/vue-qrcode-1.0.0.tgz#07103fe2048ce0160cddde836fb5378cc7951d6f" dependencies: qrcode "^1.3.0" +"@ungap/event-target@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@ungap/event-target/-/event-target-0.1.0.tgz#88d527d40de86c4b0c99a060ca241d755999915b" + integrity sha512-W2oyj0Fe1w/XhPZjkI3oUcDUAmu5P4qsdT2/2S8aMhtAWM/CE/jYWtji0pKNPDfxLI75fa5gWSEmnynKMNP/oA== + +"@vue/babel-helper-vue-jsx-merge-props@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.0.0.tgz#048fe579958da408fb7a8b2a3ec050b50a661040" + integrity sha512-6tyf5Cqm4m6v7buITuwS+jHzPlIPxbFzEhXR5JGZpbrvOcp1hiQKckd305/3C7C36wFekNTQSxAtgeM0j0yoUw== + +"@vue/babel-plugin-transform-vue-jsx@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@vue/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-1.1.2.tgz#c0a3e6efc022e75e4247b448a8fc6b86f03e91c0" + integrity sha512-YfdaoSMvD1nj7+DsrwfTvTnhDXI7bsuh+Y5qWwvQXlD24uLgnsoww3qbiZvWf/EoviZMrvqkqN4CBw0W3BWUTQ== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/plugin-syntax-jsx" "^7.2.0" + "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0" + html-tags "^2.0.0" + lodash.kebabcase "^4.1.1" + svg-tags "^1.0.0" + "@vue/test-utils@^1.0.0-beta.26": version "1.0.0-beta.28" resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.28.tgz#767c43413df8cde86128735e58923803e444b9a5" @@ -227,6 +916,13 @@ agent-base@2: extend "~3.0.0" semver "~5.0.1" +agent-base@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" + integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== + dependencies: + es6-promisify "^5.0.0" + ajv-errors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" @@ -366,7 +1062,7 @@ array-union@^1.0.1: dependencies: array-uniq "^1.0.1" -array-uniq@^1.0.1, array-uniq@^1.0.2: +array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" @@ -485,7 +1181,7 @@ babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: esutils "^2.0.2" js-tokens "^3.0.2" -babel-core@^6.0.0, babel-core@^6.1.4, babel-core@^6.26.0: +babel-core@^6.1.4, babel-core@^6.26.0: version "6.26.3" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" dependencies: @@ -531,121 +1227,6 @@ babel-generator@^6.26.0: source-map "^0.5.7" trim-right "^1.0.1" -babel-helper-bindify-decorators@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz#14c19e5f142d7b47f19a52431e52b1ccbc40a330" - dependencies: - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" - dependencies: - babel-helper-explode-assignable-expression "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-call-delegate@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" - dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-define-map@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-helper-explode-assignable-expression@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" - dependencies: - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-explode-class@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz#7dc2a3910dee007056e1e31d640ced3d54eaa9eb" - dependencies: - babel-helper-bindify-decorators "^6.24.1" - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-function-name@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" - dependencies: - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-get-function-arity@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-hoist-variables@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-optimise-call-expression@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-regex@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" - dependencies: - babel-runtime "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-helper-remap-async-to-generator@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-replace-supers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" - dependencies: - babel-helper-optimise-call-expression "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-vue-jsx-merge-props@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz#22aebd3b33902328e513293a8e4992b384f9f1b6" - babel-helpers@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" @@ -653,13 +1234,15 @@ babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-loader@^7.0.0: - version "7.1.5" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.5.tgz#e3ee0cd7394aa557e013b02d3e492bfd07aa6d68" +babel-loader@^8.0.6: + version "8.0.6" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.6.tgz#e33bdb6f362b03f4bb141a0c21ab87c501b70dfb" + integrity sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw== dependencies: - find-cache-dir "^1.0.0" + find-cache-dir "^2.0.0" loader-utils "^1.0.2" mkdirp "^0.5.1" + pify "^4.0.1" babel-messages@^6.23.0: version "6.23.0" @@ -667,19 +1250,17 @@ babel-messages@^6.23.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-add-module-exports@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz#9ae9a1f4a8dc67f0cdec4f4aeda1e43a5ff65e25" - -babel-plugin-check-es2015-constants@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" +babel-plugin-dynamic-import-node@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" + integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== dependencies: - babel-runtime "^6.22.0" + object.assign "^4.1.0" -babel-plugin-lodash@^3.2.11: +babel-plugin-lodash@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/babel-plugin-lodash/-/babel-plugin-lodash-3.3.4.tgz#4f6844358a1340baed182adbeffa8df9967bc196" + integrity sha512-yDZLjK7TCkWl1gpBeBGmuaDIFhZKmkoL+Cu2MUUjv5VxUZx/z7tBGBCBcQs5RI1Bkz5LLmNdjx7paOyQtMovyg== dependencies: "@babel/helper-module-imports" "^7.0.0-beta.49" "@babel/types" "^7.0.0-beta.49" @@ -687,369 +1268,7 @@ babel-plugin-lodash@^3.2.11: lodash "^4.17.10" require-package-name "^2.0.1" -babel-plugin-syntax-async-functions@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" - -babel-plugin-syntax-async-generators@^6.5.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a" - -babel-plugin-syntax-class-properties@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" - -babel-plugin-syntax-decorators@^6.13.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b" - -babel-plugin-syntax-dynamic-import@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da" - -babel-plugin-syntax-exponentiation-operator@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" - -babel-plugin-syntax-jsx@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" - -babel-plugin-syntax-object-rest-spread@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" - -babel-plugin-syntax-trailing-function-commas@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" - -babel-plugin-transform-async-generator-functions@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz#f058900145fd3e9907a6ddf28da59f215258a5db" - dependencies: - babel-helper-remap-async-to-generator "^6.24.1" - babel-plugin-syntax-async-generators "^6.5.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-async-to-generator@^6.22.0, babel-plugin-transform-async-to-generator@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" - dependencies: - babel-helper-remap-async-to-generator "^6.24.1" - babel-plugin-syntax-async-functions "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-class-properties@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac" - dependencies: - babel-helper-function-name "^6.24.1" - babel-plugin-syntax-class-properties "^6.8.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-decorators@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz#788013d8f8c6b5222bdf7b344390dfd77569e24d" - dependencies: - babel-helper-explode-class "^6.24.1" - babel-plugin-syntax-decorators "^6.13.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-arrow-functions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoping@^6.23.0, babel-plugin-transform-es2015-block-scoping@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" - dependencies: - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-plugin-transform-es2015-classes@^6.23.0, babel-plugin-transform-es2015-classes@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" - dependencies: - babel-helper-define-map "^6.24.1" - babel-helper-function-name "^6.24.1" - babel-helper-optimise-call-expression "^6.24.1" - babel-helper-replace-supers "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-computed-properties@^6.22.0, babel-plugin-transform-es2015-computed-properties@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-destructuring@^6.22.0, babel-plugin-transform-es2015-destructuring@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-duplicate-keys@^6.22.0, babel-plugin-transform-es2015-duplicate-keys@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-for-of@^6.22.0, babel-plugin-transform-es2015-for-of@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-function-name@^6.22.0, babel-plugin-transform-es2015-function-name@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-literals@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" - dependencies: - babel-plugin-transform-es2015-modules-commonjs "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: - version "6.26.2" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" - dependencies: - babel-plugin-transform-strict-mode "^6.24.1" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-types "^6.26.0" - -babel-plugin-transform-es2015-modules-systemjs@^6.23.0, babel-plugin-transform-es2015-modules-systemjs@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" - dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-modules-umd@^6.23.0, babel-plugin-transform-es2015-modules-umd@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" - dependencies: - babel-plugin-transform-es2015-modules-amd "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-object-super@^6.22.0, babel-plugin-transform-es2015-object-super@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" - dependencies: - babel-helper-replace-supers "^6.24.1" - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-parameters@^6.23.0, babel-plugin-transform-es2015-parameters@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" - dependencies: - babel-helper-call-delegate "^6.24.1" - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-shorthand-properties@^6.22.0, babel-plugin-transform-es2015-shorthand-properties@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-spread@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-sticky-regex@^6.22.0, babel-plugin-transform-es2015-sticky-regex@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" - dependencies: - babel-helper-regex "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-template-literals@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-typeof-symbol@^6.22.0, babel-plugin-transform-es2015-typeof-symbol@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-unicode-regex@^6.22.0, babel-plugin-transform-es2015-unicode-regex@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" - dependencies: - babel-helper-regex "^6.24.1" - babel-runtime "^6.22.0" - regexpu-core "^2.0.0" - -babel-plugin-transform-exponentiation-operator@^6.22.0, babel-plugin-transform-exponentiation-operator@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" - dependencies: - babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" - babel-plugin-syntax-exponentiation-operator "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-object-rest-spread@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" - dependencies: - babel-plugin-syntax-object-rest-spread "^6.8.0" - babel-runtime "^6.26.0" - -babel-plugin-transform-regenerator@^6.22.0, babel-plugin-transform-regenerator@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" - dependencies: - regenerator-transform "^0.10.0" - -babel-plugin-transform-runtime@^6.0.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz#88490d446502ea9b8e7efb0fe09ec4d99479b1ee" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-strict-mode@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-vue-jsx@3: - version "3.7.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-3.7.0.tgz#d40492e6692a36b594f7e9a1928f43e969740960" - dependencies: - esutils "^2.0.2" - -babel-preset-env@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a" - dependencies: - babel-plugin-check-es2015-constants "^6.22.0" - babel-plugin-syntax-trailing-function-commas "^6.22.0" - babel-plugin-transform-async-to-generator "^6.22.0" - babel-plugin-transform-es2015-arrow-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoping "^6.23.0" - babel-plugin-transform-es2015-classes "^6.23.0" - babel-plugin-transform-es2015-computed-properties "^6.22.0" - babel-plugin-transform-es2015-destructuring "^6.23.0" - babel-plugin-transform-es2015-duplicate-keys "^6.22.0" - babel-plugin-transform-es2015-for-of "^6.23.0" - babel-plugin-transform-es2015-function-name "^6.22.0" - babel-plugin-transform-es2015-literals "^6.22.0" - babel-plugin-transform-es2015-modules-amd "^6.22.0" - babel-plugin-transform-es2015-modules-commonjs "^6.23.0" - babel-plugin-transform-es2015-modules-systemjs "^6.23.0" - babel-plugin-transform-es2015-modules-umd "^6.23.0" - babel-plugin-transform-es2015-object-super "^6.22.0" - babel-plugin-transform-es2015-parameters "^6.23.0" - babel-plugin-transform-es2015-shorthand-properties "^6.22.0" - babel-plugin-transform-es2015-spread "^6.22.0" - babel-plugin-transform-es2015-sticky-regex "^6.22.0" - babel-plugin-transform-es2015-template-literals "^6.22.0" - babel-plugin-transform-es2015-typeof-symbol "^6.23.0" - babel-plugin-transform-es2015-unicode-regex "^6.22.0" - babel-plugin-transform-exponentiation-operator "^6.22.0" - babel-plugin-transform-regenerator "^6.22.0" - browserslist "^3.2.6" - invariant "^2.2.2" - semver "^5.3.0" - -babel-preset-es2015@^6.0.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939" - dependencies: - babel-plugin-check-es2015-constants "^6.22.0" - babel-plugin-transform-es2015-arrow-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoping "^6.24.1" - babel-plugin-transform-es2015-classes "^6.24.1" - babel-plugin-transform-es2015-computed-properties "^6.24.1" - babel-plugin-transform-es2015-destructuring "^6.22.0" - babel-plugin-transform-es2015-duplicate-keys "^6.24.1" - babel-plugin-transform-es2015-for-of "^6.22.0" - babel-plugin-transform-es2015-function-name "^6.24.1" - babel-plugin-transform-es2015-literals "^6.22.0" - babel-plugin-transform-es2015-modules-amd "^6.24.1" - babel-plugin-transform-es2015-modules-commonjs "^6.24.1" - babel-plugin-transform-es2015-modules-systemjs "^6.24.1" - babel-plugin-transform-es2015-modules-umd "^6.24.1" - babel-plugin-transform-es2015-object-super "^6.24.1" - babel-plugin-transform-es2015-parameters "^6.24.1" - babel-plugin-transform-es2015-shorthand-properties "^6.24.1" - babel-plugin-transform-es2015-spread "^6.22.0" - babel-plugin-transform-es2015-sticky-regex "^6.24.1" - babel-plugin-transform-es2015-template-literals "^6.22.0" - babel-plugin-transform-es2015-typeof-symbol "^6.22.0" - babel-plugin-transform-es2015-unicode-regex "^6.24.1" - babel-plugin-transform-regenerator "^6.24.1" - -babel-preset-stage-2@^6.0.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz#d9e2960fb3d71187f0e64eec62bc07767219bdc1" - dependencies: - babel-plugin-syntax-dynamic-import "^6.18.0" - babel-plugin-transform-class-properties "^6.24.1" - babel-plugin-transform-decorators "^6.24.1" - babel-preset-stage-3 "^6.24.1" - -babel-preset-stage-3@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz#836ada0a9e7a7fa37cb138fb9326f87934a48395" - dependencies: - babel-plugin-syntax-trailing-function-commas "^6.22.0" - babel-plugin-transform-async-generator-functions "^6.24.1" - babel-plugin-transform-async-to-generator "^6.24.1" - babel-plugin-transform-exponentiation-operator "^6.24.1" - babel-plugin-transform-object-rest-spread "^6.22.0" - -babel-register@^6.0.0, babel-register@^6.26.0: +babel-register@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" dependencies: @@ -1061,7 +1280,7 @@ babel-register@^6.0.0, babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: +babel-runtime@^6.22.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -1078,7 +1297,7 @@ babel-template@^6.24.1, babel-template@^6.26.0: babylon "^6.18.0" lodash "^4.17.4" -babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.26.0: +babel-traverse@^6.23.1, babel-traverse@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" dependencies: @@ -1092,7 +1311,7 @@ babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.26.0: invariant "^2.2.2" lodash "^4.17.4" -babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.26.0: +babel-types@^6.23.0, babel-types@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" dependencies: @@ -1153,6 +1372,11 @@ better-assert@~1.0.0: dependencies: callsite "1.0.0" +big-integer@^1.6.17: + version "1.6.48" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" + integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== + big.js@^3.1.3: version "3.2.0" resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" @@ -1165,6 +1389,14 @@ binary-extensions@^1.0.0: version "1.12.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14" +binary@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk= + dependencies: + buffers "~0.1.1" + chainsaw "~0.1.0" + blob@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" @@ -1177,6 +1409,11 @@ bluebird@^3.5.3: version "3.5.4" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.4.tgz#d6cc661595de30d5b3af5fcedd3c0b3ef6ec5714" +bluebird@~3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM= + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -1309,12 +1546,14 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" -browserslist@^3.2.6: - version "3.2.8" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6" +browserslist@^4.6.0, browserslist@^4.8.2: + version "4.8.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.8.2.tgz#b45720ad5fbc8713b7253c20766f701c9a694289" + integrity sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA== dependencies: - caniuse-lite "^1.0.30000844" - electron-to-chromium "^1.3.47" + caniuse-lite "^1.0.30001015" + electron-to-chromium "^1.3.322" + node-releases "^1.1.42" buffer-alloc-unsafe@^1.1.0: version "1.1.0" @@ -1335,6 +1574,11 @@ buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" +buffer-indexof-polyfill@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.1.tgz#a9fb806ce8145d5428510ce72f278bb363a638bf" + integrity sha1-qfuAbOgUXVQoUQznLyeLs2OmOL8= + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -1347,6 +1591,11 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s= + builtin-modules@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -1460,9 +1709,10 @@ caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: version "1.0.30000928" resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000928.tgz#2e83d2b14276442da239511615eb7c62fed0cfa7" -caniuse-lite@^1.0.30000844: - version "1.0.30000928" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000928.tgz#805e828dc72b06498e3683a32e61c7507fd67b88" +caniuse-lite@^1.0.30001015: + version "1.0.30001015" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001015.tgz#15a7ddf66aba786a71d99626bc8f2b91c6f0f5f0" + integrity sha512-/xL2AbW/XWHNu1gnIrO8UitBGoFthcsDgU9VLK1/dpsoxbaD5LscHozKze05R6WLsBvLhqv78dAPozMFQBYLbQ== caseless@~0.12.0: version "0.12.0" @@ -1483,6 +1733,13 @@ chai@^3.5.0: deep-eql "^0.1.3" type-detect "^1.0.0" +chainsaw@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg= + dependencies: + traverse ">=0.3.0 <0.4" + chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -1798,6 +2055,13 @@ convert-source-map@^1.5.1: dependencies: safe-buffer "~5.1.1" +convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -1821,7 +2085,15 @@ copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" -core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.7: +core-js-compat@^3.4.7: + version "3.4.8" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.4.8.tgz#f72e6a4ed76437ea710928f44615f926a81607d5" + integrity sha512-l3WTmnXHV2Sfu5VuD7EHE2w7y+K68+kULKt5RJg8ZJk3YhHF1qLD4O8v8AmNq+8vbOwnPFFDvds25/AoEvMqlQ== + dependencies: + browserslist "^4.8.2" + semver "^6.3.0" + +core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0: version "2.6.2" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.2.tgz#267988d7268323b349e20b4588211655f0e83944" @@ -2014,6 +2286,11 @@ currently-unhandled@^0.4.1: dependencies: array-find-index "^1.0.1" +custom-event-polyfill@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz#9bc993ddda937c1a30ccd335614c6c58c4f87aee" + integrity sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w== + custom-event@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" @@ -2050,6 +2327,7 @@ dateformat@^1.0.6: de-indent@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" + integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0= debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: version "2.6.9" @@ -2267,7 +2545,7 @@ domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" -domelementtype@1, domelementtype@^1.3.0: +domelementtype@1: version "1.3.1" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" @@ -2281,12 +2559,6 @@ domhandler@2.1: dependencies: domelementtype "1" -domhandler@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" - dependencies: - domelementtype "1" - domutils@1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485" @@ -2300,12 +2572,12 @@ domutils@1.5.1: dom-serializer "0" domelementtype "1" -domutils@^1.5.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= dependencies: - dom-serializer "0" - domelementtype "1" + readable-stream "^2.0.2" duplexify@^3.4.2, duplexify@^3.6.0: version "3.7.1" @@ -2331,10 +2603,15 @@ ejs@2.5.7: version "2.5.7" resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" -electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.47: +electron-to-chromium@^1.2.7: version "1.3.100" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.100.tgz#899fb088def210aee6b838a47655bbb299190e13" +electron-to-chromium@^1.3.322: + version "1.3.322" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz#a6f7e1c79025c2b05838e8e344f6e89eb83213a8" + integrity sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA== + elliptic@^6.0.0: version "6.4.1" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a" @@ -2359,6 +2636,13 @@ encodeurl@~1.0.1, encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= + dependencies: + iconv-lite "~0.4.13" + end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" @@ -2414,7 +2698,7 @@ ent@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" -entities@^1.1.1, entities@~1.1.1: +entities@~1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -2449,9 +2733,22 @@ es-to-primitive@^1.2.0: is-date-object "^1.0.1" is-symbol "^1.0.2" -escape-html@~1.0.3: +es6-promise@^4.0.3: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + +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" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" @@ -2946,14 +3243,6 @@ find-cache-dir@^0.1.1: mkdirp "^0.5.1" pkg-dir "^1.0.0" -find-cache-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" - dependencies: - commondir "^1.0.1" - make-dir "^1.0.0" - pkg-dir "^2.0.0" - find-cache-dir@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" @@ -3010,6 +3299,18 @@ follow-redirects@^1.0.0: dependencies: debug "=3.1.0" +"fontello-webpack-plugin@https://github.com/w3geo/fontello-webpack-plugin.git#6149eac8f2672ab6da089e8802fbfcac98487186": + version "0.4.8" + resolved "https://github.com/w3geo/fontello-webpack-plugin.git#6149eac8f2672ab6da089e8802fbfcac98487186" + dependencies: + form-data "^2.1.2" + html-webpack-plugin "^3.2.0" + https-proxy-agent "^2.1.1" + lodash "^4.17.4" + node-fetch "^1.6.3" + unzipper "^0.10.5" + webpack-sources "^0.2.0" + for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -3024,6 +3325,15 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" +form-data@^2.1.2: + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -3085,6 +3395,16 @@ fsevents@^1.2.7: nan "^2.12.1" node-pre-gyp "^0.12.0" +fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + ftp@~0.3.10: version "0.3.10" resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" @@ -3222,7 +3542,7 @@ glob@^7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^11.7.0: +globals@^11.1.0, globals@^11.7.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -3244,6 +3564,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: version "4.1.15" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" +graceful-fs@^4.2.2: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + "graceful-readlink@>= 1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" @@ -3407,7 +3732,12 @@ html-minifier@^3.2.3: relateurl "0.2.x" uglify-js "3.4.x" -html-webpack-plugin@^3.0.0: +html-tags@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b" + integrity sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos= + +html-webpack-plugin@^3.0.0, html-webpack-plugin@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b" dependencies: @@ -3419,17 +3749,6 @@ html-webpack-plugin@^3.0.0: toposort "^1.0.0" util.promisify "1.0.0" -htmlparser2@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464" - dependencies: - domelementtype "^1.3.0" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^3.0.6" - htmlparser2@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" @@ -3493,13 +3812,21 @@ https-proxy-agent@1: debug "2" extend "3" +https-proxy-agent@^2.1.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" + integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== + dependencies: + agent-base "^4.3.0" + debug "^3.1.0" + iconv-lite@0.4.23, iconv-lite@^0.4.4: version "0.4.23" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.4.24: +iconv-lite@^0.4.24, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" dependencies: @@ -3599,6 +3926,11 @@ inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" +inherits@~2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" @@ -3859,7 +4191,7 @@ is-regex@^1.0.4: dependencies: has "^1.0.1" -is-stream@^1.1.0: +is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -3990,6 +4322,11 @@ js-base64@^2.1.9: version "2.5.0" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.0.tgz#42255ba183ab67ce59a0dee640afdc00ab5ae93e" +js-levenshtein@^1.1.3: + version "1.1.6" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" + integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" @@ -4027,6 +4364,11 @@ jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" @@ -4069,6 +4411,13 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" +json5@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6" + integrity sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ== + dependencies: + minimist "^1.2.0" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -4203,6 +4552,11 @@ lie@3.1.1: dependencies: immediate "~3.0.5" +listenercount@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc= + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -4379,10 +4733,6 @@ lodash.clone@3.0.3: lodash._bindcallback "^3.0.0" lodash._isiterateecall "^3.0.0" -lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - lodash.create@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7" @@ -4402,10 +4752,6 @@ lodash.defaultsdeep@4.3.2: lodash.mergewith "^4.0.0" lodash.rest "^4.0.0" -lodash.escaperegexp@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" - lodash.find@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-3.2.1.tgz#046e319f3ace912ac6c9246c7f683c5ec07b36ad" @@ -4437,18 +4783,19 @@ lodash.isplainobject@^3.0.0, lodash.isplainobject@^3.2.0: lodash.isarguments "^3.0.0" lodash.keysin "^3.0.0" -lodash.isplainobject@^4.0.0, lodash.isplainobject@^4.0.6: +lodash.isplainobject@^4.0.0: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - lodash.istypedarray@^3.0.0: version "3.0.6" resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62" +lodash.kebabcase@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" + integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY= + lodash.keys@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" @@ -4488,7 +4835,7 @@ lodash.merge@^3.3.2: lodash.keysin "^3.0.0" lodash.toplainobject "^3.0.0" -lodash.mergewith@^4.0.0, lodash.mergewith@^4.6.1: +lodash.mergewith@^4.0.0: version "4.6.1" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" @@ -4525,6 +4872,11 @@ lodash@^4.16.4, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.2 version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" +lodash@^4.17.13: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" @@ -4585,13 +4937,7 @@ lru-cache@~2.6.5: version "2.6.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5" -make-dir@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" - dependencies: - pify "^3.0.0" - -make-dir@^2.0.0: +make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" dependencies: @@ -4826,7 +5172,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -4973,6 +5319,14 @@ no-case@^2.2.0: dependencies: lower-case "^1.1.1" +node-fetch@^1.6.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + node-libs-browser@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.0.tgz#c72f60d9d46de08a940dedbb25f3ffa2f9bbaa77" @@ -5001,6 +5355,11 @@ node-libs-browser@^2.0.0: util "^0.11.0" vm-browserify "0.0.4" +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= + node-pre-gyp@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" @@ -5016,6 +5375,13 @@ node-pre-gyp@^0.12.0: semver "^5.3.0" tar "^4" +node-releases@^1.1.42: + version "1.1.42" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.42.tgz#a999f6a62f8746981f6da90627a8d2fc090bbad7" + integrity sha512-OQ/ESmUqGawI2PRX+XIRao44qWYBBfN54ImQYdWVTQqUckuejOg76ysSqDBK8NG3zwySRVnX36JwDQ6x+9GxzA== + dependencies: + semver "^6.3.0" + nomnomnomnom@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/nomnomnomnom/-/nomnomnomnom-2.0.1.tgz#b2239f031c8d04da67e32836e1e3199e12f7a8e2" @@ -5132,20 +5498,26 @@ object-hash@^1.1.4: version "1.3.1" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" -object-keys@^1.0.12: +object-keys@^1.0.11, object-keys@^1.0.12: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" -object-path@^0.11.3: - version "0.11.4" - resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949" - object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" dependencies: isobject "^3.0.0" +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + object.getownpropertydescriptors@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" @@ -5501,6 +5873,13 @@ pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" +pirates@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== + dependencies: + node-modules-regexp "^1.0.0" + pkg-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" @@ -5523,11 +5902,6 @@ pngjs@^3.3.0: version "3.3.3" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.3.tgz#85173703bde3edac8998757b96e5821d0966a21b" -popper.js@^1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2" - integrity sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA== - portal-vue@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-2.1.4.tgz#1fc679d77e294dc8d026f1eb84aa467de11b392e" @@ -5831,14 +6205,6 @@ postcss@^7.0.0: source-map "^0.6.1" supports-color "^6.1.0" -postcss@^7.0.5: - version "7.0.8" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.8.tgz#2a3c5f2bdd00240cd0d0901fd998347c93d36696" - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.0.0" - prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -6107,14 +6473,6 @@ readable-stream@1.1.x: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^3.0.6: - version "3.1.1" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06" - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - readdirp@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" @@ -6150,7 +6508,14 @@ reduce-function-call@^1.0.1: dependencies: balanced-match "^0.4.2" -regenerate@^1.2.1: +regenerate-unicode-properties@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" + integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.2.1, regenerate@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" @@ -6158,16 +6523,16 @@ regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" -regenerator-runtime@^0.12.0: - version "0.12.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" +regenerator-runtime@^0.13.2: + version "0.13.3" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" + integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw== -regenerator-transform@^0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" +regenerator-transform@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb" + integrity sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ== dependencies: - babel-runtime "^6.18.0" - babel-types "^6.19.0" private "^0.1.6" regex-cache@^0.4.2: @@ -6195,24 +6560,40 @@ regexpu-core@^1.0.0: regjsgen "^0.2.0" regjsparser "^0.1.4" -regexpu-core@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" +regexpu-core@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6" + integrity sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg== dependencies: - regenerate "^1.2.1" - regjsgen "^0.2.0" - regjsparser "^0.1.4" + regenerate "^1.4.0" + regenerate-unicode-properties "^8.1.0" + regjsgen "^0.5.0" + regjsparser "^0.6.0" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.1.0" regjsgen@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" +regjsgen@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" + integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== + regjsparser@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" dependencies: jsesc "~0.5.0" +regjsparser@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.0.tgz#f1e6ae8b7da2bae96c99399b868cd6c933a2ba9c" + integrity sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ== + dependencies: + jsesc "~0.5.0" + relateurl@0.2.x: version "0.2.7" resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" @@ -6317,6 +6698,13 @@ resolve@^1.10.0, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1: dependencies: path-parse "^1.0.6" +resolve@^1.3.2: + version "1.13.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.13.1.tgz#be0aa4c06acd53083505abb35f4d66932ab35d16" + integrity sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w== + dependencies: + path-parse "^1.0.6" + restore-cursor@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" @@ -6339,6 +6727,13 @@ rfdc@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2" +rimraf@2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" @@ -6388,21 +6783,6 @@ samsam@1.x, samsam@^1.1.3: version "1.3.0" resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50" -sanitize-html@^1.13.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.20.0.tgz#9a602beb1c9faf960fb31f9890f61911cc4d9156" - dependencies: - chalk "^2.4.1" - htmlparser2 "^3.10.0" - lodash.clonedeep "^4.5.0" - lodash.escaperegexp "^4.1.2" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.mergewith "^4.6.1" - postcss "^7.0.5" - srcset "^1.0.0" - xtend "^4.0.1" - "sass-loader@git://github.com/webpack-contrib/sass-loader": version "7.1.0" resolved "git://github.com/webpack-contrib/sass-loader#e279f2a129eee0bd0b624b5acd498f23a81ee35e" @@ -6440,10 +6820,20 @@ selenium-server@2.53.1: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" +semver@^5.4.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + semver@^5.5.1: version "5.7.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" +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@~5.0.1: version "5.0.3" resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" @@ -6507,9 +6897,10 @@ set-value@^2.0.0: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4: +setimmediate@^1.0.4, setimmediate@~1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= setprototypeof@1.1.0: version "1.1.0" @@ -6673,6 +7064,11 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" +source-list-map@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.2.tgz#9889019d1024cce55cdc069498337ef6186a11a1" + integrity sha1-mIkBnRAkzOVc3AaUmDN+9hhqEaE= + source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" @@ -6693,6 +7089,14 @@ source-map-support@^0.4.15: dependencies: source-map "^0.5.6" +source-map-support@^0.5.16: + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-support@~0.5.10: version "0.5.12" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" @@ -6704,7 +7108,7 @@ source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" -source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7: +source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -6750,13 +7154,6 @@ sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" -srcset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef" - dependencies: - array-uniq "^1.0.2" - number-is-nan "^1.0.0" - sshpk@^1.7.0: version "1.16.0" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.0.tgz#1d4963a2fbffe58050aa9084ca20be81741c07de" @@ -6856,7 +7253,7 @@ string-width@^3.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string_decoder@^1.0.0, string_decoder@^1.1.1: +string_decoder@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" dependencies: @@ -6940,12 +7337,17 @@ supports-color@^5.3.0, supports-color@^5.4.0: dependencies: has-flag "^3.0.0" -supports-color@^6.0.0, supports-color@^6.1.0: +supports-color@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" dependencies: has-flag "^3.0.0" +svg-tags@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" + integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q= + svgo@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" @@ -7102,6 +7504,11 @@ tough-cookie@~2.4.3: psl "^1.1.24" punycode "^1.4.1" +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= + trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -7172,6 +7579,29 @@ underscore@~1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8" +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-value-ecmascript@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277" + integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57" + integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw== + union-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" @@ -7212,6 +7642,22 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" +unzipper@^0.10.5: + version "0.10.5" + resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.5.tgz#4d189ae6f8af634b26efe1a1817c399e0dd4a1a0" + integrity sha512-i5ufkXNjWZYxU/0nKKf6LkvW8kn9YzRvfwuPWjXP+JTFce/8bqeR0gEfbiN2IDdJa6ZU6/2IzFRLK0z1v0uptw== + dependencies: + big-integer "^1.6.17" + binary "~0.3.0" + bluebird "~3.4.1" + buffer-indexof-polyfill "~1.0.0" + duplexer2 "~0.1.4" + fstream "^1.0.12" + graceful-fs "^4.2.2" + listenercount "~1.0.1" + readable-stream "~2.3.6" + setimmediate "~1.0.4" + upath@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" @@ -7256,7 +7702,7 @@ useragent@2.3.0: lru-cache "4.1.x" tmp "0.0.x" -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -7295,15 +7741,6 @@ v-click-outside@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/v-click-outside/-/v-click-outside-2.1.3.tgz#b7297abe833a439dc0895e6418a494381e64b5e7" -v-tooltip@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-2.0.2.tgz#8610d9eece2cc44fd66c12ef2f12eec6435cab9b" - integrity sha512-xQ+qzOFfywkLdjHknRPgMMupQNS8yJtf9Utd5Dxiu/0n4HtrxqsgDtN2MLZ0LKbburtSAQgyypuE/snM8bBZhw== - dependencies: - lodash "^4.17.11" - popper.js "^1.15.0" - vue-resize "^0.4.5" - validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -7378,11 +7815,6 @@ vue-loader@^14.0.0: vue-style-loader "^4.0.1" vue-template-es2015-compiler "^1.6.0" -vue-resize@^0.4.5: - version "0.4.5" - resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-0.4.5.tgz#4777a23042e3c05620d9cbda01c0b3cc5e32dcea" - integrity sha512-bhP7MlgJQ8TIkZJXAfDf78uJO+mEI3CaLABLjv0WNzr4CcGRGPIAItyWYnP6LsPA4Oq0WE+suidNs6dgpO4RHg== - vue-router@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.2.tgz#dedc67afe6c4e2bc25682c8b1c2a8c0d7c7e56be" @@ -7394,9 +7826,10 @@ vue-style-loader@^4.0.0, vue-style-loader@^4.0.1: hash-sum "^1.0.2" loader-utils "^1.0.2" -vue-template-compiler@^2.3.4: - version "2.5.21" - resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.21.tgz#a57ceb903177e8f643560a8d639a0f8db647054a" +vue-template-compiler@^2.6.11: + version "2.6.11" + resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz#c04704ef8f498b153130018993e56309d4698080" + integrity sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA== dependencies: de-indent "^1.0.2" he "^1.1.0" @@ -7405,9 +7838,10 @@ vue-template-es2015-compiler@^1.6.0: version "1.9.1" resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825" -vue@^2.5.13: - version "2.5.21" - resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85" +vue@^2.6.11: + version "2.6.11" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5" + integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ== vuelidate@^0.7.4: version "0.7.4" @@ -7459,6 +7893,14 @@ webpack-merge@^0.14.1: lodash.isplainobject "^3.2.0" lodash.merge "^3.3.2" +webpack-sources@^0.2.0: + version "0.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.2.3.tgz#17c62bfaf13c707f9d02c479e0dcdde8380697fb" + integrity sha1-F8Yr+vE8cH+dAsR54Nzd6DgGl/s= + dependencies: + source-list-map "^1.1.1" + source-map "~0.5.3" + webpack-sources@^1.1.0, webpack-sources@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85" @@ -7495,10 +7937,6 @@ webpack@^4.0.0: watchpack "^1.5.0" webpack-sources "^1.3.0" -whatwg-fetch@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" - whet.extend@~0.9.9: version "0.9.9" resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" @@ -7570,7 +8008,7 @@ xregexp@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" -xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: +xtend@^4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"