diff --git a/CHANGELOG.md b/CHANGELOG.md index c011835c3fa31f2c219367005dee5c0fd09ff0a1..7e3eaf1730a04b5d103f048c9627f5134a65130a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,61 @@ 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 +- Greentext now has separate color slot for it +- 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. +- Subject field now appears disabled when posting + +## [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 @@ -13,14 +65,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - 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 diff --git a/README.md b/README.md index 889f0837617b4a290f691143ac508f54870e8644..b66383ade7a4dc704cd47c1f7924aea63dafd91c 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 5cc0135e75021a626cf2b01ac3e8d73b46cc32ea..dfef37a636c90ac04928c608f9806d509affe3a8 100644 --- a/build/webpack.base.conf.js +++ b/build/webpack.base.conf.js @@ -35,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') diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 0a9bbd7abcb028e9e1c9642d903b61a5c460d86a..14b0428f9efc520a806aa8e100e8273d113b2571 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. -### `redirectRootNoLogin`, `redirectRootLogin` -These two settings should point to where FE should redirect visitor when they login/open up website root +### `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. -### `chatDisabled` -hides the chat (TODO: even if it's enabled on backend) +### `nsfwCensorImage` +Use custom image for NSFW'd images -### `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. +### `postContentType` +Default post formatting option (markdown/bbcode/plaintext/etc...) -### `collapseMessageWithSubject` -Collapse post content when post has a subject line (content warning). Instance-default. +### `redirectRootNoLogin`, `redirectRootLogin` +These two settings should point to where FE should redirect visitor when they login/open up website root ### `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. + ### `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,39 +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 - -### `hideSitename` -Hide instance name in header ## 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) @@ -92,13 +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! - -### 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. diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 076bfb1c3b09e104a8df4c27c8306b4bf609d587..f417f33d7579a0b334a121793e9a2cfe04a760cd 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/package.json b/package.json index 5c7fa31e1a4f7dfe8050a7a582ac8f51025684bd..c0665f6eb65d597751b6f35b5f455cd2af610632 100644 --- a/package.json +++ b/package.json @@ -22,24 +22,20 @@ "cropperjs": "^1.4.3", "diff": "^3.0.1", "escape-html": "^1.0.3", - "karma-mocha-reporter": "^2.2.1", "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": { + "karma-mocha-reporter": "^2.2.1", "@babel/core": "^7.7.5", "@babel/plugin-transform-runtime": "^7.7.6", "@babel/preset-env": "^7.7.6", diff --git a/src/App.js b/src/App.js index 61b5eec1c6ca6e16d1a4455fdd90d402f65d55bb..040138c978bd98dfe3e0eea0f417aba57e73be9b 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 () { @@ -99,7 +102,12 @@ export default { }, showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }, isMobileLayout () { return this.$store.state.interface.mobileLayout }, - privateMode () { return this.$store.state.instance.private } + privateMode () { return this.$store.state.instance.private }, + sidebarAlign () { + return { + 'order': this.$store.state.instance.sidebarRight ? 99 : 0 + } + } }, methods: { scrollToTop () { @@ -112,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 922e39b66e74ba801a8f21b2a0aac8536f3cdee4..f2972eda5ad5c6535e77fc216f288a09a9681c13 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; @@ -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 { diff --git a/src/App.vue b/src/App.vue index 1b1c2648cac1292eb18fc4049083fd021ad6bf93..7b9ad3dc1233f33d2311215454ec39c2d3c8c440 100644 --- a/src/App.vue +++ b/src/App.vue @@ -46,15 +46,16 @@ @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" @@ -78,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"> @@ -122,6 +126,7 @@ <MobilePostStatusButton /> <UserReportingModal /> <PostStatusModal /> + <SettingsModal /> <portal-target name="modal" /> </div> </template> diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 0bb1b2b4dae0dd02ecef2f80510b48508fec3d57..0db035475f7f8ce7ba9f59492da471cd2b7e3413 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,9 +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']) } @@ -221,9 +223,16 @@ const getNodeInfo = async ({ store }) => { 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', @@ -232,6 +241,9 @@ const getNodeInfo = async ({ store }) => { : federation.enabled }) + const accountActivationRequired = metadata.accountActivationRequired + store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired }) + const accounts = metadata.staffAccounts resolveStaffAccounts({ store, accounts }) } else { @@ -258,7 +270,7 @@ const checkOAuthToken = async ({ store }) => { try { await store.dispatch('loginUser', store.getters.getUserToken()) } catch (e) { - console.log(e) + console.error(e) } } resolve() @@ -266,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 7400a682ca01d1b5939c38911151312a17c928c5..d98a3b5032ea15307304821750dd192c7cc6a3cd 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/account_actions/account_actions.js b/src/components/account_actions/account_actions.js index d2153680b41bb1b73d72d5f6f964537396a10f91..0826c27583b42f834939e2ca9097b73f472b0602 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 () { diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue index d3235be15d063431f0d154b8b9cd2086398d3d19..744b77d5399b9dba509389ecc73c7ac7501e41e7 100644 --- a/src/components/account_actions/account_actions.vue +++ b/src/components/account_actions/account_actions.vue @@ -1,24 +1,24 @@ <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"> - <template v-if="user.following"> + <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" > @@ -30,7 +30,7 @@ /> </template> <button - v-if="user.statusnet_blocking" + v-if="relationship.blocking" class="btn btn-default btn-block dropdown-item" @click="unblockUser" > @@ -51,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> @@ -62,7 +65,6 @@ <style lang="scss"> @import '../../_variables.scss'; -@import '../popper/popper.scss'; .account-actions { margin: 0 .8em; } @@ -70,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 0000000000000000000000000000000000000000..b68b98f97fb0963ff53bfad9fda2e2c3f96cb52a --- /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.vue b/src/components/attachment/attachment.vue index 0748b2f04282f8ad0d706e6390e1dc28a1de4a3f..a7e217c113e1d7499d4d5a9a00d46bd49eb0ed07 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 1f86e996fe5bf187c6ff41bea8193fd4af187d06..f283ab82a89639d41c9d4814c69c6240cb0c622f 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 8a02174e0ac0c9cac98677711a28f9d664064a18..9e4106102daab02580641403d554e712c5d253d7 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 c459ff1b516582ed5ec882fb07fdd346cda44e8a..0bf4e37bc0906cb35a20d3c30399510dee2eec8a 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 1113f81d86fe2038db0416b5a7b28fbe3432356a..03375b2faaa63aeedf105aa217b8cce11e031fc3 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 0000000000000000000000000000000000000000..8e9923cff8f450d6d46a3569e5835ed79876315d --- /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 9db62e8188f5561e5bd4440ddbb97e721170c54c..8fb16113a14afcddcf9ff9e84e7e006470242ff6 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 15a450a2259edc53d6adca80454da68401c5f7b8..ba92bc170baff8db529475441b191dad94d53c11 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/dialog_modal/dialog_modal.vue b/src/components/dialog_modal/dialog_modal.vue index 55d7a7d24f74f40cffdc9cdbe91f5575d9b4b049..3241ce3eeb8d9dc8b00022eef9b97bee46a2b299 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 index c8e838bac779559845109884cfd08279a197d19d..f234dcb0f376717f11d4c026092e3fa9302775ca 100644 --- a/src/components/domain_mute_card/domain_mute_card.js +++ b/src/components/domain_mute_card/domain_mute_card.js @@ -5,9 +5,20 @@ const DomainMuteCard = { 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) } } } diff --git a/src/components/domain_mute_card/domain_mute_card.vue b/src/components/domain_mute_card/domain_mute_card.vue index 567d81c508445ad8ccefbf5c3ed7446fc4d2d2f0..97aee2431a7b0802973b7900ceb83eb768b3791e 100644 --- a/src/components/domain_mute_card/domain_mute_card.vue +++ b/src/components/domain_mute_card/domain_mute_card.vue @@ -4,6 +4,7 @@ {{ domain }} </div> <ProgressButton + v-if="muted" :click="unmuteDomain" class="btn btn-default" > @@ -12,6 +13,16 @@ {{ $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> @@ -34,5 +45,9 @@ 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 001a22e98da6f6b9223c7c64556d8a3daf24cf65..f4c3479c7d6c3abaf3c079bd9b3484df22e3688b 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 a72156708cd973b7c508cb3bc761671132284abf..e9ac09c348c06d5f097ab67d741235dbdcf51d7c 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 aec5c39d83d263334f8d81783ee87d3015a7976f..15a71effa1f6055bd658ba5443a543419b68e680 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 6608f39304da0c7d2a157ccfd9aee36a60d00751..8bd07e453fff4a28115c254b448d62a0b5837c3b 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 index b799ac9a2496f635710198c19ac5f5eefc386b48..ae7f53be5a2ebe2e713401ca2b5d76f311708845 100644 --- a/src/components/emoji_reactions/emoji_reactions.js +++ b/src/components/emoji_reactions/emoji_reactions.js @@ -1,20 +1,17 @@ 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 + UserAvatar, + Popover }, props: ['status'], data: () => ({ - showAll: false, - popperOptions: { - modifiers: { - preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' } - } - } + showAll: false }), computed: { tooManyReactions () { diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue index e5b6d9f5764ba07b0e619658c839ba939d45d819..bac4c6051b891fa90c2da009e878ada68ce53375 100644 --- a/src/components/emoji_reactions/emoji_reactions.vue +++ b/src/components/emoji_reactions/emoji_reactions.vue @@ -1,15 +1,14 @@ <template> <div class="emoji-reactions"> - <v-popover + <Popover v-for="(reaction) in emojiReactions" :key="reaction.name" - :popper-options="popperOptions" trigger="hover" placement="top" + :offset="{ y: 5 }" > - <div - slot="popover" + slot="content" class="reacted-users" > <div v-if="accountsForEmoji[reaction.name].length"> @@ -24,7 +23,12 @@ :compact="true" /> <div class="reacted-user-names"> - <span class="reacted-user-name" v-html="account.name_html" /> + <!-- 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> @@ -34,6 +38,7 @@ </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)" @@ -42,17 +47,16 @@ <span class="reaction-emoji">{{ reaction.name }}</span> <span>{{ reaction.count }}</span> </button> - </v-popover> + </Popover> <a - v-if="tooManyReactions" - @click="toggleShowAll" - class="emoji-reaction-expand faint" - href='javascript:void(0)' - > - {{ showAll ? $t('general.show_less') : showMoreString }} - </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> @@ -78,6 +82,7 @@ display: flex; flex-direction: column; margin-left: 0.5em; + min-width: 5em; img { width: 1em; @@ -128,7 +133,7 @@ } .picked-reaction { - border: 1px solid var(--link, $fallback--link); + 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); } diff --git a/src/components/export_import/export_import.vue b/src/components/export_import/export_import.vue index 20c6f569d4d74150e5584bbde52f9c0e779e7081..ae00487f28cb18ee9ecef55f610d231227131998 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 5ac73e975acfbb48e01f67b3ea7d037b602cc01a..e4b19d0102f8e5b1814dbce20a66a4b1fc289a3b 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 746f1c9175236bc3f9f3809c8ac1b0113efbc138..bca93ea7448fe381fadeee4309ff14d034a770a2 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 12da264545fbcb8947a834adc9bcfae03e4b01d5..95e7cb6ba7fdb1c95c4f461d684ff80162da1c12 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 f0cbb94b58dff9c0a0779d73f76ef7ef44a3e23b..bfdc137b74e3edd53b6721e2c8308f46d4f17c1d 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 aefd609e022728b2edcc77347a778e260dfa73b4..6dcb6d47bdcb2b90fd544a08c706d9b70d9180b8 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 81e6e6dc0eda3fbfe8d5cd195c8d233acc4ce371..b503783fd68e0390bf79636e2f3771840d08fdd1 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 a89317872811f99ab9720eb6c4b3119f54b819ce..cbd75311e82781b6047b84085acae88fe6d96031 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({ 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 () { + const notifId = this.findFollowRequestNotificationId() this.$store.state.api.backendInteractor.denyUser({ id: this.user.id }) - this.$store.dispatch('removeFollowRequest', this.user) + .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 7abc2161a3f07f3c3f300da89795054c73cea67e..1ffa7b3cfaa259b7e23a43c7b6319e9822809e3b 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/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue index f5ace0cce92baca79db25b9ee451fa102be85c76..dd6800a3029f5aac761c4499bad03ac462bef2fe 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 } } }, diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index abb18c7de70107a7788be0d38785b471e6eae038..24764e800f576fb00b3211cdbd9e047464aad85c 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_upload/media_upload.js b/src/components/media_upload/media_upload.js index f457d0220bfec37be627e482b407fb6784e57d1b..fbb2d03db9c9ea918554165c0f2c8eed9d22b3f7 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 0fc305ac62c2c56118e5be290d98542ea2149b3b..5e31730b590e97b63a4730cd59ac25d8ca7ad960 100644 --- a/src/components/media_upload/media_upload.vue +++ b/src/components/media_upload/media_upload.vue @@ -1,10 +1,5 @@ <template> - <div - class="media-upload" - @drop.prevent - @dragover.prevent="fileDrag" - @drop="fileDrop" - > + <div class="media-upload"> <label class="label" :title="$t('tool_tip.media_upload')" diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue index cee24241b0e09dd1a2d9a103374c9fbca1cc5e4d..2b58913f18affe2e151a55e9145bbc39e6ae9ace 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; + + > * { + pointer-events: initial; + } + + &.modal-background { + pointer-events: initial; + background-color: rgba(0, 0, 0, 0.5); + } - body:not(.scroll-locked) & { - opacity: 0; + &.open { + opacity: 1; } } diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js index 757166ed18c3eb7307797e33da199a1f293055f3..d4fdc53e3c1fcb29107b8c8828d0b91902e06ed9 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 () { @@ -89,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 006d637333440bbde5c6a3bb456baff7c62b9a85..b2d5acc582d77307b620e552da54127c376098b9 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.vue b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue index 8038e587cb0114938334be5174f6d9588aabb54b..acdf822e464a0dbace1057868c97c8e86cf64458 100644 --- a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue +++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue @@ -6,13 +6,13 @@ <div class="panel panel-default base01-background"> <div class="panel-heading timeline-heading base02-background"> <div class="title"> - {{ $t("about.federation") }} + {{ $t("about.mrf.federation") }} </div> </div> <div class="panel-body"> <div class="mrf-section"> - <h2>{{ $t("about.mrf_policies") }}</h2> - <p>{{ $t("about.mrf_policies_desc") }}</p> + <h2>{{ $t("about.mrf.mrf_policies") }}</h2> + <p>{{ $t("about.mrf.mrf_policies_desc") }}</p> <ul> <li @@ -23,13 +23,13 @@ </ul> <h2 v-if="hasInstanceSpecificPolicies"> - {{ $t("about.mrf_policy_simple") }} + {{ $t("about.mrf.simple.simple_policies") }} </h2> <div v-if="acceptInstances.length"> - <h4>{{ $t("about.mrf_policy_simple_accept") }}</h4> + <h4>{{ $t("about.mrf.simple.accept") }}</h4> - <p>{{ $t("about.mrf_policy_simple_accept_desc") }}</p> + <p>{{ $t("about.mrf.simple.accept_desc") }}</p> <ul> <li @@ -41,9 +41,9 @@ </div> <div v-if="rejectInstances.length"> - <h4>{{ $t("about.mrf_policy_simple_reject") }}</h4> + <h4>{{ $t("about.mrf.simple.reject") }}</h4> - <p>{{ $t("about.mrf_policy_simple_reject_desc") }}</p> + <p>{{ $t("about.mrf.simple.reject_desc") }}</p> <ul> <li @@ -55,9 +55,9 @@ </div> <div v-if="quarantineInstances.length"> - <h4>{{ $t("about.mrf_policy_simple_quarantine") }}</h4> + <h4>{{ $t("about.mrf.simple.quarantine") }}</h4> - <p>{{ $t("about.mrf_policy_simple_quarantine_desc") }}</p> + <p>{{ $t("about.mrf.simple.quarantine_desc") }}</p> <ul> <li @@ -69,9 +69,9 @@ </div> <div v-if="ftlRemovalInstances.length"> - <h4>{{ $t("about.mrf_policy_simple_ftl_removal") }}</h4> + <h4>{{ $t("about.mrf.simple.ftl_removal") }}</h4> - <p>{{ $t("about.mrf_policy_simple_ftl_removal_desc") }}</p> + <p>{{ $t("about.mrf.simple.ftl_removal_desc") }}</p> <ul> <li @@ -83,9 +83,9 @@ </div> <div v-if="mediaNsfwInstances.length"> - <h4>{{ $t("about.mrf_policy_simple_media_nsfw") }}</h4> + <h4>{{ $t("about.mrf.simple.media_nsfw") }}</h4> - <p>{{ $t("about.mrf_policy_simple_media_nsfw_desc") }}</p> + <p>{{ $t("about.mrf.simple.media_nsfw_desc") }}</p> <ul> <li @@ -97,9 +97,9 @@ </div> <div v-if="mediaRemovalInstances.length"> - <h4>{{ $t("about.mrf_policy_simple_media_removal") }}</h4> + <h4>{{ $t("about.mrf.simple.media_removal") }}</h4> - <p>{{ $t("about.mrf_policy_simple_media_removal_desc") }}</p> + <p>{{ $t("about.mrf.simple.media_removal_desc") }}</p> <ul> <li diff --git a/src/components/mute_card/mute_card.js b/src/components/mute_card/mute_card.js index 65c9cfb5b01f9181a5164166c766b8b0784a38b2..cbec0e9bb38dcab253e5ab5c2cd6722d44e8ad1b 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.vue b/src/components/nav_panel/nav_panel.vue index 0f3296eb2227ddad7eb55224d284fc62fff05ef0..8cd04dc7b0496f98019989db62e78bc943f8e698 100644 --- a/src/components/nav_panel/nav_panel.vue +++ b/src/components/nav_panel/nav_panel.vue @@ -100,13 +100,25 @@ &: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; diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index e7bd769e05ace53e010194993769cb11b4a37bb0..5aa40e988d2052da279fcf4c2adfc021820c2587 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: { @@ -56,7 +77,10 @@ const Notification = { 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 411c027119d391fca9d0d0a967d851155a36477c..044ac871548113eb0e57b3ea7284fd9f267a201d 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,6 +74,10 @@ <i class="fa icon-user-plus lit" /> <small>{{ $t('notifications.followed_you') }}</small> </span> + <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> @@ -87,30 +91,30 @@ </span> </div> <div - v-if="notification.type === 'follow' || notification.type === 'move'" + v-if="isStatusNotification" class="timeago" > - <span class="faint"> + <router-link + v-if="notification.status" + :to="{ name: 'conversation', params: { id: notification.status.id } }" + class="faint-link" + > <Timeago :time="notification.created_at" :auto-update="240" /> - </span> + </router-link> </div> <div v-else class="timeago" > - <router-link - v-if="notification.status" - :to="{ name: 'conversation', params: { id: notification.status.id } }" - class="faint-link" - > + <span class="faint"> <Timeago :time="notification.created_at" :auto-update="240" /> - </router-link> + </span> </div> <a v-if="needMute" @@ -119,12 +123,30 @@ ><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'" @@ -135,11 +157,9 @@ </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.scss b/src/components/notifications/notifications.scss index 8d819a56a07a73d3c8bb9142130c2630c8a150d2..20797cf96629e6ef45d53dde3aa5d64e05105713 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,38 +48,62 @@ } } - .muted { - padding: .25em .6em; - } - .non-mention { display: flex; flex: 1; flex-wrap: nowrap; padding: 0.6em; min-width: 0; + .avatar-container { width: 32px; height: 32px; } - .status-el { - .status { - padding: 0.25em 0; - color: $fallback--faint; - color: var(--faint, $fallback--faint); - a { - color: var(--faintLink); - } + + .status-body { + color: $fallback--faint; + color: var(--faint, $fallback--faint); + a { + color: var(--faintLink); } - padding: 0; - .media-body { - margin: 0; + .status-content a { + color: var(--postFaintLink); } } } + .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 { @@ -139,6 +165,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); diff --git a/src/components/opacity_input/opacity_input.vue b/src/components/opacity_input/opacity_input.vue index c677f18c73ea234f6eadaff2970d362455ff0148..3cc3942b43c50e7f0575c04a0aed47ebc1defbc7 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 0000000000000000000000000000000000000000..4efebb3ca608df17be4f43bc655761361f75bc4b --- /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 db8e33b3107a5a2c8e121cd3d7f78249dac46113..56e91cca502f800cf870a3661a034937af2f897f 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 0000000000000000000000000000000000000000..5881d266b45bd9510059d8b4fa9d7e798d538c22 --- /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 0000000000000000000000000000000000000000..a271cb1b9b71760c5ad164b680adb96a911e6afb --- /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 06daa8714a4bb0a2511d60e4fc2c87272b408a34..0000000000000000000000000000000000000000 --- 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 74067fef6a890745e5aa7943aa99000b38b2264a..9027566fd699eff1ab99ca0937228850a75107ca 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 () { @@ -218,7 +220,6 @@ const PostStatusForm = { }, addMediaFile (fileInfo) { this.newStatus.files.push(fileInfo) - this.enableSubmit() }, removeMediaFile (fileInfo) { let index = this.newStatus.files.indexOf(fileInfo) @@ -227,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 @@ -250,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 9789a4819b7ca80b3d9c998fff9dc52652976bea..e3d8d087f358f7e793a5b192055ed8018a453b2a 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'" @@ -73,6 +81,7 @@ v-model="newStatus.spoilerText" type="text" :placeholder="$t('post_status.content_warning')" + :disabled="posting" class="form-post-subject" > </EmojiInput> @@ -96,9 +105,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 +179,7 @@ @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="uploadFailed" + @all-uploaded="enableSubmit" /> <div class="emoji-icon" @@ -446,7 +454,8 @@ form { display: flex; flex-direction: column; - padding: 0.6em; + margin: 0.6em; + position: relative; } .form-group { @@ -504,5 +513,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/range_input/range_input.vue b/src/components/range_input/range_input.vue index aaa2ed26c65a17fddb9979cc34973cf1fa11150c..5857a5c1d4e657797e0a12d5c780ef9764b68934 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 index a6cf5b941384bba96536541711d04071e1b63058..f0931446f2c1d47e003161de960684fd4451833c 100644 --- a/src/components/react_button/react_button.js +++ b/src/components/react_button/react_button.js @@ -1,39 +1,30 @@ +import Popover from '../popover/popover.vue' import { mapGetters } from 'vuex' const ReactButton = { - props: ['status', 'loggedIn'], + props: ['status'], data () { return { - showTooltip: false, - filterWord: '', - popperOptions: { - modifiers: { - preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' } - } - } + filterWord: '' } }, + components: { + Popover + }, methods: { - openReactionSelect () { - this.showTooltip = true - this.filterWord = '' - }, - closeReactionSelect () { - this.showTooltip = false - }, - addReaction (event, emoji) { + 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 }) } - this.closeReactionSelect() + close() } }, computed: { commonEmojis () { - return ['â¤ï¸', '😠', '👀', '😂', '🔥'] + return ['ðŸ‘', '😠', '👀', '😂', '🔥'] }, emojis () { if (this.filterWord !== '') { diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue index fb43ebaf6aaa505e85a1d9d8bbd01b2fb7e81b28..0b34add1e261a23918b52637192afc20bce6d207 100644 --- a/src/components/react_button/react_button.vue +++ b/src/components/react_button/react_button.vue @@ -1,13 +1,14 @@ <template> - <v-popover - :popper-options="popperOptions" - :open="showTooltip" - trigger="manual" + <Popover + trigger="click" placement="top" + :offset="{ y: 5 }" class="react-button-popover" - @hide="closeReactionSelect" > - <div slot="popover"> + <div + slot="content" + slot-scope="{close}" + > <div class="reaction-picker-filter"> <input v-model="filterWord" @@ -19,7 +20,7 @@ v-for="emoji in commonEmojis" :key="emoji" class="emoji-button" - @click="addReaction($event, emoji)" + @click="addReaction($event, emoji, close)" > {{ emoji }} </span> @@ -28,23 +29,19 @@ v-for="(emoji, key) in emojis" :key="key" class="emoji-button" - @click="addReaction($event, emoji.replacement)" + @click="addReaction($event, emoji.replacement, close)" > {{ emoji.replacement }} </span> <div class="reaction-bottom-fader" /> </div> </div> - <div - v-if="loggedIn" - @click.prevent="openReactionSelect" - > - <i - class="icon-smile button-icon add-reaction-button" - :title="$t('tool_tip.add_reaction')" - /> - </div> - </v-popover> + <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> diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js index ace8cc7ce85f7b67331d9214d53f7d6296faaf1b..dab06e1ee49401bf5f1addf2dde788ec868709fb 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: { diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue index fdbda0077adc98bd1bfb4b8bc9c6ff70213545cc..a83ca1e527ac7fd4d3c65bacf92621fe3598d6da 100644 --- a/src/components/registration/registration.vue +++ b/src/components/registration/registration.vue @@ -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 d9ec7ecee350ceb62a23b73c2ab7f14904087e1d..a9bb12a187e9c048e9a0dd4719eb2e617348c6e2 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 31a9e9be79323eb08cef08763da09a9dfe03232a..0000000000000000000000000000000000000000 --- a/src/components/settings/settings.js +++ /dev/null @@ -1,128 +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 or perform actions first) - 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) - }) - } - }, - 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 }) - }) - } - } - }, - // 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 60cb8a8761dd8ded7e158c7bebe1a7d01c8fff8d..0000000000000000000000000000000000000000 --- a/src/components/settings/settings.vue +++ /dev/null @@ -1,424 +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="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> - - <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> - <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> - <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 0000000000000000000000000000000000000000..86703697d260397379cb495b29cc127e34287959 --- /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 0000000000000000000000000000000000000000..f0d49c91587b4ac32f960815c577663154621715 --- /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 0000000000000000000000000000000000000000..833ff89a5aca65f4b5a9feb8bf5cd809e2be7322 --- /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 0000000000000000000000000000000000000000..6bc64ed0ed9186720929e4e19c9694708f99fe63 --- /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 0000000000000000000000000000000000000000..48101a9069d4bf08389db59dd7d0a439583829f3 --- /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 0000000000000000000000000000000000000000..a3fef1cf31c3f1a3fde6fa7472c6938f68a59820 --- /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 0000000000000000000000000000000000000000..2156844f2862bd9edd8a9f338fac9383babec285 --- /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 0000000000000000000000000000000000000000..168f89e1918790451615b62fd6b45a25c0c2a092 --- /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 0000000000000000000000000000000000000000..b5d0f5ed1841edfa3c2b2b33865035758ff212c7 --- /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 0000000000000000000000000000000000000000..224a7f47932e337cb3b85f49ef37224668c58f97 --- /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 0000000000000000000000000000000000000000..eea41514dd18cd6b45917eabf94ba94679d49ec7 --- /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 0000000000000000000000000000000000000000..0eb37e44eb1e6550b42e15588bae2bcf9980c717 --- /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 0000000000000000000000000000000000000000..f89c0480af25a492e6c988863f5d0fdca81c1ff9 --- /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 0000000000000000000000000000000000000000..40a87b813a01d05a90bbf35448b7eb050f74d58f --- /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 0000000000000000000000000000000000000000..ceb64efbc88f02cba825ddebad2fb99bc8f8ee6b --- /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 0000000000000000000000000000000000000000..5a1cf2c0634f104cd0b61fa3e1ea8c82c30885c6 --- /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 0000000000000000000000000000000000000000..3e44c95df9b28d90a2172990707262dffee2a099 --- /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 0000000000000000000000000000000000000000..b7a3cb370df9ffb536ada3a40c05293836e28ed3 --- /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 0000000000000000000000000000000000000000..8658b09770a7a2765c6cd8a095358adf644a7b87 --- /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 0000000000000000000000000000000000000000..4aab81eb7e04ddf868bae5c09732c1d3031fb1f8 --- /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 0000000000000000000000000000000000000000..fff4f970c0caa367844fec09211f1207102b6b69 --- /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 100% rename from src/components/user_settings/mfa.js rename to src/components/settings_modal/tabs/security_tab/mfa.js 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 14ea10a1df196c8deddf96d31e77ce4d5836854a..7aca3c8de33125323ed3138b25aacc143389dbfd 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 e6c8ede2b8e7da578dcb3ce6d61e658260182898..d7e98b3c09eb80f1ddcebc85079270607a5fcbfc 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 0000000000000000000000000000000000000000..811161a51b11a6afd6e0fb48fcdad07d8732ff1b --- /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 0000000000000000000000000000000000000000..3d32d73d9602cb0493d1602363ef9abef2abd9d9 --- /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 0000000000000000000000000000000000000000..9d984659b08ddb8898f3a2e27d8394e9be2ec642 --- /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 0000000000000000000000000000000000000000..9d61b0c44cb1507ee2ac948c9d48faab9966b658 --- /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 135c113ae04c8e9b50252a899edc4cf581459a47..926eceff570dc2aedca52bbc21697372edee55d4 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 ad03204120beba513c3544cc8e0d815b43a3f109..d14f854ca22eb331769ef8a9ccecf9fc1b4b0fe8 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,21 @@ </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" /> + <ColorInput + v-model="postGreentextColorLocal" + name="postGreentextColor" + :fallback="previewTheme.colors.cGreen" + :label="$t('settings.greentext')" + /> + <ContrastRatio :contrast="previewContrast.postGreentext" /> <h4>{{ $t('settings.style.advanced_colors.alert') }}</h4> <ColorInput v-model="alertErrorColorLocal" @@ -200,14 +270,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 +326,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 +359,7 @@ /> <ContrastRatio :contrast="previewContrast.panelText" - large="1" + large="true" /> <ColorInput v-model="panelLinkColorLocal" @@ -249,7 +369,7 @@ /> <ContrastRatio :contrast="previewContrast.panelLink" - large="1" + large="true" /> </div> <div class="color-item"> @@ -257,7 +377,7 @@ <ColorInput v-model="topBarColorLocal" name="topBarColor" - :fallback="fgColorLocal" + :fallback="previewTheme.colors.topBar" :label="$t('settings.background')" /> <ColorInput @@ -280,13 +400,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 +422,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 +438,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 +568,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 +577,7 @@ <ColorInput v-model="faintColorLocal" name="faintColor" - :fallback="previewTheme.colors.faint || 1" + :fallback="previewTheme.colors.faint" :label="$t('settings.text')" /> <ColorInput @@ -354,8 +595,145 @@ <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> @@ -491,7 +869,7 @@ {{ $t('settings.style.switcher.clear_all') }} </button> </div> - <shadow-control + <ShadowControl v-model="currentShadow" :ready="!!currentShadowFallback" :fallback="currentShadowFallback" @@ -582,6 +960,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 0000000000000000000000000000000000000000..616bdadf51dd7abf1d57d254eab717b5aa68432d --- /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 0000000000000000000000000000000000000000..d35ff25ea72f56f81e95b627194b777e6cbd004a --- /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 44e4a22f1a848b8a899a0ef15b2c8dc92ff677a1..f9e7b985cece534b6d1e6df33da1dc9576c474bf 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 de8a42d12ac738c3c2e7ebb25c65a96dc499f7ae..815a9e595f516ea764457746e28010514c94a7b3 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 2181ecc7730ce1ab45cefb72fd13dfa0b129c4fc..d1f044f665f23c4e480fe2f1d412ad9a73d2ea84 100644 --- a/src/components/side_drawer/side_drawer.js +++ b/src/components/side_drawer/side_drawer.js @@ -62,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 28637afc511396c1b9a7f0d716978109936a8b67..f253742d1d7799c2e2a10141a01dd30820320ab9 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 @@ -122,9 +122,12 @@ </router-link> </li> <li @click="toggleDrawer"> - <router-link :to="{ name: 'settings' }"> + <a + href="#" + @click="openSettingsModal" + > <i class="button-icon icon-cog" /> {{ $t("settings.settings") }} - </router-link> + </a> </li> <li @click="toggleDrawer"> <router-link :to="{ name: 'about'}"> @@ -223,7 +226,13 @@ 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; @@ -289,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/status/status.js b/src/components/status/status.js index fc5956ecec62d091379bf2a5c241b921b372b152..733825212606819f88dca87ec3e9ecff691051a0 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -1,24 +1,19 @@ -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 { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js' 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 { muteWordHits } from '../../services/status_parser/status_parser.js' +import { unescape, uniqBy } from 'lodash' import { mapGetters, mapState } from 'vuex' const Status = { @@ -43,20 +38,19 @@ const Status = { replying: false, unmuted: false, userExpanded: false, - showingTall: this.inConversation && this.focused, - showingLongSubject: false, - error: null, - // not as computed because it sets the initial state which will be changed later - expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject + 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) @@ -79,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) }, @@ -110,15 +100,43 @@ const Status = { 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 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 hits + return !this.unmuted && !excusesNotToMute && reasonsToMute }, - muted () { return !this.unmuted && ((!(this.inProfile && this.status.user.id === this.profileUserId) && this.status.user.muted) || (!this.inConversation && this.status.thread_muted) || this.muteWordHits.length > 0) }, hideFilteredStatuses () { return this.mergedConfig.hideFilteredStatuses }, @@ -135,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) }, @@ -178,8 +182,11 @@ 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.currentUser.id) { @@ -188,33 +195,6 @@ const Status = { } 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) @@ -228,83 +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) - ) - }, - 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 - }, combinedFavsAndRepeatsUsers () { // Use the status from the global status repository since favs and repeats are saved in it const combinedUsers = [].concat( @@ -313,9 +216,6 @@ const Status = { ) return uniqBy(combinedUsers, 'id') }, - ownStatus () { - return this.status.user.id === this.currentUser.id - }, tags () { return this.status.tags.filter(tagObj => tagObj.hasOwnProperty('name')).map(tagObj => tagObj.name).join(' ') }, @@ -329,21 +229,18 @@ const Status = { }) }, components: { - Attachment, FavoriteButton, ReactButton, RetweetButton, ExtraButtons, PostStatusForm, - Poll, UserCard, UserAvatar, - Gallery, - LinkPreview, AvatarList, Timeago, StatusPopover, - EmojiReactions + EmojiReactions, + StatusContent }, methods: { visibilityIcon (visibility) { @@ -364,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 }, @@ -407,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 83f07dacbdced1646aaab427348f9fd383397b84..336f912a7064ad3a60b161f48d46cc97b9073c47 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,118 +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") }} - <span - v-if="hasImageAttachments" - class="icon-picture" - /> - <span - v-if="hasVideoAttachments" - class="icon-video" - /> - <span - v-if="status.card" - class="icon-link" - /> - </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 @@ -402,7 +319,7 @@ :status="status" /> <ReactButton - :logged-in="loggedIn" + v-if="loggedIn" :status="status" /> <extra-buttons @@ -468,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 & { @@ -564,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; } } @@ -577,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); @@ -588,6 +511,10 @@ $status-margin: 0.75em; display: flex; } + .reply-to-popover { + min-width: 0; + } + .reply-to { display: flex; } @@ -595,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 { @@ -619,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; @@ -774,11 +606,6 @@ $status-margin: 0.75em; } } -.greentext { - color: $fallback--cGreen; - color: var(--cGreen, $fallback--cGreen); -} - .status-conversation { border-left-style: solid; } @@ -831,33 +658,54 @@ $status-margin: 0.75em; } .muted { - padding: 0.25em 0.5em; - button { - margin-left: auto; + 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; } - .muteWords { - margin-left: 10px; + .username, .mute-words { + text-overflow: ellipsis; + overflow: hidden; } -} -a.unmute { - display: block; - margin-left: auto; + .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; + } } .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 0000000000000000000000000000000000000000..c0a71e8f77f1ca57d3a1f05aabc065e651cf06e4 --- /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 0000000000000000000000000000000000000000..7adb67ae34c42555816384405737b6e47682c6ae --- /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(--postGreentext, $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 19f16bd980d8c5144b7497ea88a3d68cfde2e0ca..159132a9e4bd34d53957c3246ec9684c4ceb6511 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 eacf4c06757112561093477debf24463529f4cfe..f5948207fc890cb544eced24064ae1d5b038ce86 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 3863908ab3c687c1bc919a75046fdf0cc6bbccd6..dc449ccb40a66ccf7bfd02fa2a0233f8ed4be69c 100644 --- a/src/components/sticker_picker/sticker_picker.vue +++ b/src/components/sticker_picker/sticker_picker.vue @@ -51,7 +51,7 @@ img { height: 100%; &:hover { - filter: drop-shadow(0 0 5px var(--link, $fallback--link)); + 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 4137bd5960cf887a3105cda7d27eb7f2a302233d..f2ddeb7b98cb066855d8e04b2fa7c21793d411f3 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 101a32bd617f80966c06df7b551d8cec06cdb492..0000000000000000000000000000000000000000 --- 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 ebde4475c5baecc9d10a7a05b5affe6e3595dcc0..0000000000000000000000000000000000000000 --- 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 008e1e9549d0372ec29241e7789dddb0d196d3a0..7891cb78ac4c4356d5fac85e4aa9c3a0b4da1df1 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 3e5eacd55917ba01c73e172fc258acf9765d837c..d2ef48570223862b6f4fb4cb4dc10bd31861ad24 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 { - 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; - } - } + .tab-wrapper { + position: relative; + display: flex; + flex: 0 0 auto; - &: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/user_card/user_card.js b/src/components/user_card/user_card.js index 2f6499105d1a651b7196d6fbe2034dc1f5c0687a..8e6b9d7fa5008564a58df4f8c8f7568de2d2f8f6 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 () { diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index 93d55fff5bff35fa6a805f24c4748b6f1c02cdd3..c4a5ce9d6d3af565c8372bc16eb91148a077abb7 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') }} @@ -286,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; @@ -299,6 +292,11 @@ &-bio { text-align: center; + a { + color: $fallback--link; + color: var(--postLink, $fallback--link); + } + img { object-fit: contain; vertical-align: middle; @@ -460,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); } } @@ -538,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 e9f08015a11cc194cbfa07bb1a1030fe9e1bf944..1db4f648816cd8dfe24d3f8cafa0492b8ddaadae 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 9558a0bd203fa9fb22de0d0d6d071c07439de533..95760bf84b5ae3847de6451618e6c4fff80b0008 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' @@ -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 14082e83942274d63fe0112d3b5a811ab0fad3b8..1871d46c6200f790eab2ae7be8e223a4ef90e7fb 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_settings/user_settings.js b/src/components/user_settings/user_settings.js deleted file mode 100644 index eca6f9b16f95a65d4bb7f294c17daa95deced8e6..0000000000000000000000000000000000000000 --- a/src/components/user_settings/user_settings.js +++ /dev/null @@ -1,393 +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 DomainMuteCard from '../domain_mute_card/domain_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 DomainMuteList = withSubscription({ - fetch: (props, $store) => $store.dispatch('fetchDomainMutes'), - select: (props, $store) => get($store.state.users.currentUser, 'domainMutes', []), - 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, - 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, - 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, - newDomainToMute: '' - } - }, - created () { - this.$store.dispatch('fetchTokens') - }, - components: { - StyleSwitcher, - ScopeSelector, - TabSwitcher, - ImageCropper, - BlockList, - MuteList, - DomainMuteList, - EmojiInput, - Autosuggest, - BlockCard, - MuteCard, - DomainMuteCard, - 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, - 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) - }) - }, - 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) - }, - unmuteDomains (domains) { - return this.$store.dispatch('unmuteDomains', domains) - }, - muteDomain () { - return this.$store.dispatch('muteDomain', this.newDomainToMute) - .then(() => { this.newDomainToMute = '' }) - }, - 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 8b2336b4dd514f8a69df70a7a618259a72189b36..0000000000000000000000000000000000000000 --- a/src/components/user_settings/user_settings.vue +++ /dev/null @@ -1,716 +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="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> - - <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')"> - <tab-switcher> - <div label="Users"> - <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> - - <div :label="$t('settings.domain_mutes')"> - <div class="profile-edit-domain-mute-form"> - <input - v-model="newDomainToMute" - :placeholder="$t('settings.type_domains_to_mute')" - type="text" - @keyup.enter="muteDomain" - > - <ProgressButton - class="btn btn-default" - :click="muteDomain" - > - {{ $t('domain_mute_card.mute') }} - <template slot="progress"> - {{ $t('domain_mute_card.mute_progress') }} - </template> - </ProgressButton> - </div> - <DomainMuteList - :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="() => 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> - </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; - } - } - - &-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; - } -} -</style> diff --git a/src/i18n/ar.json b/src/i18n/ar.json index 72e3010fb04275757009c54938f06b0137633e23..8bba2b97e9e54850ead830cf06fd3d3cf4d83676 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": "غير Ù…Ùدرَج - لا ÙŠÙنشَر على الخيوط الزمنية العمومية" + } + }, + "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": "نعم" } -} \ No newline at end of file + }, + "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 a4b4c16f2285292eaeaae2004287b36206a4871f..a44e58cb653928f6673f83e63de27208023c7c42 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 d0d654d3311bcd786e3aa01fe48df3766c972840..eefe10e5ea34cc2e0cde5f2b6584478ef97bf52d 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1,40 +1,42 @@ { "about": { - "staff": "Staff", - "federation": "Federation", - "mrf_policies": "Enabled MRF Policies", - "mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance. The following policies are enabled:", - "mrf_policy_simple": "Instance-specific Policies", - "mrf_policy_simple_accept": "Accept", - "mrf_policy_simple_accept_desc": "This instance only accepts messages from the following instances:", - "mrf_policy_simple_reject": "Reject", - "mrf_policy_simple_reject_desc": "This instance will not accept messages from the following instances:", - "mrf_policy_simple_quarantine": "Quarantine", - "mrf_policy_simple_quarantine_desc": "This instance will send only public posts to the following instances:", - "mrf_policy_simple_ftl_removal": "Removal from \"The Whole Known Network\" Timeline", - "mrf_policy_simple_ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:", - "mrf_policy_simple_media_removal": "Media Removal", - "mrf_policy_simple_media_removal_desc": "This instance removes media from posts on the following instances:", - "mrf_policy_simple_media_nsfw": "Media Force-set As Sensitive", - "mrf_policy_simple_media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:", "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...", + "mute_progress": "Muting…", "unmute": "Unmute", - "unmute_progress": "Unmuting..." + "unmute_progress": "Unmuting…" }, "exporter": { "export": "Export", @@ -57,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", @@ -91,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": { @@ -118,9 +126,10 @@ "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!", @@ -222,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:", @@ -274,10 +283,11 @@ "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", @@ -293,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", @@ -390,7 +401,7 @@ "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": "Type in domains to mute", + "type_domains_to_mute": "Search domains to mute", "upload_a_photo": "Upload a photo", "user_settings": "User Settings", "values": { @@ -400,11 +411,14 @@ "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", @@ -418,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", @@ -447,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" @@ -467,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.", @@ -581,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", @@ -611,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": { @@ -662,9 +710,11 @@ "reply": "Reply", "favorite": "Favorite", "add_reaction": "Add Reaction", - "user_settings": "User Settings" + "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 163eb7076e77bf6ef91dc03255cfb76676fa4138..931d4c6486913ebe9c5aa07a502b06d5e394a84e 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 5262b2a4ed26392eed1208470a880befc9791f6a..b5ae4275ef28e060d4426775ac0b78a2e90614e4 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 ac8b2ac965dd9f58727c1fba4788bfb166aab149..99a1b53a6787ea7a4fed8854763c8a1207d96099 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...", @@ -54,7 +74,9 @@ "read": "Lue!", "repeated_you": "toisti viestisi", "no_more_notifications": "Ei enempää ilmoituksia", - "reacted_with": "lisäsi reaktion {0}" + "reacted_with": "lisäsi reaktion {0}", + "migrated_to": "siirtyi sivulle", + "follow_request": "haluaa seurata sinua" }, "polls": { "add_poll": "Lisää äänestys", @@ -68,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", @@ -81,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.", @@ -92,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": { @@ -110,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", @@ -151,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", @@ -186,14 +223,14 @@ "notification_visibility_mentions": "Maininnat", "notification_visibility_repeats": "Toistot", "notification_visibility_emoji_reactions": "Reaktiot", - "no_rich_text_description": "Älä näytä tekstin muotoilua.", + "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", @@ -231,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": { @@ -252,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", @@ -276,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", @@ -288,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", @@ -298,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", @@ -306,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ää", @@ -324,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}]", @@ -339,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 5f0053d59dc1fa40d0db5a3eecd182f4ff162921..31b69a0f88c2dddd7b74e61edc327403f6bb71d6 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" + "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" }, - "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" + "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" }, - "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" + "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 :" + } }, - "interactions": { - "favs_repeats": "Partages et favoris", - "follows": "Nouveauxâ‹…elles abonné⋅eâ‹…s ?", - "load_older": "Chargez d'anciennes interactions" + "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" }, - "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" + "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." }, - "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" + "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" + } } - }, - "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:" - } + }, + "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}" }, - "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" + "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" }, - "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" - } + "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)" }, - "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" + "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" + } }, - "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:" + "version": { + "title": "Version", + "backend_version": "Version du Backend", + "frontend_version": "Version du Frontend" }, - "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." - } + "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." }, - "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." + "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" }, - "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" - } + "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 f441292eac6967f4056706799460e04f7051d4fd..6c8be351f80887f783e1dcbe4d128731d174347d 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_easy.json b/src/i18n/ja_easy.json index be447f1cb9863a9c9dfcd3aaf7a74ed5a7ec1fff..978e43b30c8e377710acbf3020309229a99097af 100644 --- a/src/i18n/ja_easy.json +++ b/src/i18n/ja_easy.json @@ -1,22 +1,26 @@ { "about": { - "staff": "スタッフ", - "federation": "フェデレーション", - "mrf_policies": "ゆã†ã“ã†ãªMRFãƒãƒªã‚·ãƒ¼", - "mrf_policies_desc": "MRFãƒãƒªã‚·ãƒ¼ã¯ã€ã“ã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã®ãƒ•ã‚§ãƒ‡ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ã®ãµã‚‹ã¾ã„ã‚’ã€ã„ã˜ã‚Šã¾ã™ã€‚ã“れらã®MRFãƒãƒªã‚·ãƒ¼ãŒã‚†ã†ã“ã†ã«ãªã£ã¦ã„ã¾ã™:", - "mrf_policy_simple": "インスタンスã®ãƒãƒªã‚·ãƒ¼", - "mrf_policy_simple_accept": "ã†ã‘ã„ã‚Œ", - "mrf_policy_simple_accept_desc": "ã“ã®ã‚¤ãƒ³ã‚¹ãƒ³ã‚¹ã¯ã€ã“れらã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã‹ã‚‰ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã®ã¿ã‚’ã†ã‘ã„ã‚Œã¾ã™:", - "mrf_policy_simple_reject": "ãŠã“ã¨ã‚ã‚Š", - "mrf_policy_simple_reject_desc": "ã“ã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã¯ã€ã“れらã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã‹ã‚‰ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’ã†ã‘ã„ã‚Œã¾ã›ã‚“:", - "mrf_policy_simple_quarantine": "ã‘ã‚“ãˆã", - "mrf_policy_simple_quarantine_desc": "ã“ã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã¯ã€ã“れらã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã«ã€ãƒ‘ブリックãªã¨ã†ã“ã†ã®ã¿ã‚’ã€ãŠãã‚Šã¾ã™:", - "mrf_policy_simple_ftl_removal": "「ã¤ãªãŒã£ã¦ã„ã‚‹ã™ã¹ã¦ã®ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã€ã‚¿ã‚¤ãƒ ラインã‹ã‚‰ã®ãžã", - "mrf_policy_simple_ftl_removal_desc": "ã“ã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã¯ã€ã¤ãªãŒã£ã¦ã„ã‚‹ã™ã¹ã¦ã®ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã€ã‚¿ã‚¤ãƒ ラインã‹ã‚‰ã€ã“れらã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã‚’ã€ã¨ã‚Šã®ãžãã¾ã™:", - "mrf_policy_simple_media_removal": "メディアをã®ãžã", - "mrf_policy_simple_media_removal_desc": "ã“ã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã¯ã€ã“れらã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã‹ã‚‰ãŠãられã¦ããŸãƒ¡ãƒ‡ã‚£ã‚¢ã‚’ã€ã¨ã‚Šã®ãžãã¾ã™:", - "mrf_policy_simple_media_nsfw": "メディアをã™ã¹ã¦ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ã«ã™ã‚‹", - "mrf_policy_simple_media_nsfw_desc": "ã“ã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã¯ã€ã“れらã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã‹ã‚‰ãŠãられã¦ããŸãƒ¡ãƒ‡ã‚£ã‚¢ã‚’ã€ã™ã¹ã¦ã€ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ã«ãƒžãƒ¼ã‚¯ã—ã¾ã™:" + "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": "ãƒãƒ£ãƒƒãƒˆ" diff --git a/src/i18n/messages.js b/src/i18n/messages.js index c56ae205aa63c296153753deea2198252eb2081a..c3195f10aacd05896af1df5659829a1ff66cc835 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_pedantic.json'), - ja_easy: require('./ja_easy.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 7e2f060446df9b5d5e61f09a5aae58b0ccd8e60c..af728b6eb7fd7d196b932c9d180f1fa0321b11b2 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 51cadfb686618a71ba12f72e73975562ae96f320..61e093187332c2b538cfde9978012ff46ec46be8 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 4cb2d49738c0540ecb0ddc2844f1858c6d6d69be..f9a729544fde979edb1daf35fbaf701df74bd6ce 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": "Общие", @@ -204,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": "Ðе удалоÑÑŒ Ñохранить наÑтройки", @@ -224,7 +231,7 @@ "text": "ТекÑÑ‚", "theme": "Тема", "theme_help": "ИÑпользуйте шеÑтнадцатеричные коды цветов (#rrggbb) Ð´Ð»Ñ Ð½Ð°Ñтройки темы.", - "theme_help_v2_1": "Ð’Ñ‹ так же можете перепоределить цвета определенных компонентов нажав Ñоотв. галочку. ИÑпользуйте кнопку \"ОчиÑтить вÑÑ‘\" чтобы ÑнÑÑ‚ÑŒ вÑе переопределениÑ", + "theme_help_v2_1": "Ð’Ñ‹ так же можете перепоределить цвета определенных компонентов нажав Ñоотв. галочку. ИÑпользуйте кнопку \"ОчиÑтить вÑÑ‘\" чтобы ÑнÑÑ‚ÑŒ вÑе переопределениÑ.", "theme_help_v2_2": "Под некоторыми полÑми ввода Ñто идикаторы контраÑтноÑти, наведите на них мышью чтобы узнать больше. ПриÑпользовании прозрачноÑти контраÑÑ‚ раÑчитываетÑÑ Ð´Ð»Ñ Ð½Ð°Ð¸Ñ…ÑƒÐ´ÑˆÐµÐ³Ð¾ варианта.", "tooltipRadius": "Ð’Ñплывающие подÑказки/уведомлениÑ", "user_settings": "ÐаÑтройки пользователÑ", @@ -292,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}" }, @@ -340,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": "Свернуть", @@ -359,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": "в день", @@ -382,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": "Ð’Ñ‹ уверены? Ðто дейÑтвие Ð½ÐµÐ»ÑŒÐ·Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ð¸Ñ‚ÑŒ." @@ -410,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 f0953d97090ba2a05f538212030a9c6eeb4dbc24..6022349d3891ad920ec9a4510b0dfae7207464c8 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 8d9462ab2fee2d361102ad4fa7a5dc9ccf212094..f95dc4987271ecf66132e47f675ae53adf1d35da 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": "å¢žåŠ é—®å·è°ƒæŸ¥", @@ -112,7 +117,8 @@ "interactions": { "favs_repeats": "转å‘和收è—", "follows": "新的关注者", - "load_older": "åŠ è½½æ›´æ—©çš„äº’åŠ¨" + "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/persisted_state.js b/src/lib/persisted_state.js index cad7ea2562e5dadef9a317dc544587cbf69b6da6..8ecb66a8c8d30a20d8c80a4ec89fd3d724a40752 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 baf73ac8b5764e286f0a2506cad76feb67c13e4e..9a201e4fac76b041a7931c2b7e764750f7f33252 100644 --- a/src/main.js +++ b/src/main.js @@ -31,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' @@ -44,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/config.js b/src/modules/config.js index 8381fa530cb2844f424f686f5e23acd0b3b5c074..47b24d77263b80973a20b9f2e23f429b834e6e33 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -1,10 +1,24 @@ 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 @@ -31,7 +45,8 @@ export const defaultState = { likes: true, repeats: true, moves: true, - emojiReactions: false + emojiReactions: false, + followRequest: true }, webPushNotifications: false, muteWords: [], @@ -96,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 625323b973ffc70f70d846e722a97083094a3a73..ec5f4e54708f440ab984fc5f5edc7d5985d47c24 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -1,53 +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, + textlimit: 5000, + themeData: undefined, + vapidPublicKey: undefined, + + // Stuff from static/config.json alwaysShowSubjectInput: true, - hideMutedPosts: false, + background: '/static/aurora_borealis.jpg', collapseMessageWithSubject: false, - hidePostStats: false, - hideUserStats: false, - hideFilteredStatuses: false, disableChat: false, - scopeCopy: true, - subjectLineBehavior: 'email', - postContentType: 'text/plain', + 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, - vapidPublicKey: undefined, - noAttachmentLinks: false, + postContentType: 'text/plain', + redirectRootLogin: '/main/friends', + redirectRootNoLogin: '/main/all', + scopeCopy: true, showFeaturesPanel: true, - minimalScopesMode: false, - greentext: false, + 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: '', @@ -75,6 +82,9 @@ const instance = { if (typeof value !== 'undefined') { set(state, name, value) } + }, + setKnownDomains (state, domains) { + state.knownDomains = domains } }, getters: { @@ -96,6 +106,9 @@ const instance = { dispatch('initializeSocket') } break + case 'theme': + dispatch('setTheme', value) + break } }, async getStaticEmoji ({ commit }) { @@ -147,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) { @@ -160,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 5b2762e5ba017711da96e4f13d55bfcf2f794210..eeebd65ebf5047b0083e0ac5849684ac0bc7f82b 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/statuses.js b/src/modules/statuses.js index 25b62ac7fa8f6d09a937afe811777ba0642f5671..9a2e0df1e9646427995bd2573dfa024465fdcde3 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -13,8 +13,9 @@ import { 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: [], @@ -321,7 +322,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters }) => { each(notifications, (notification) => { - if (notification.type !== 'follow' && notification.type !== 'move') { + if (isStatusNotification(notification.type)) { notification.action = addStatusToGlobalStorage(state, notification.action).item notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item } @@ -361,13 +362,16 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot case 'move': i18nString = 'migrated_to' break + case 'follow_request': + i18nString = 'follow_request' + break } 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 } @@ -377,7 +381,18 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot notifObj.image = status.attachments[0].url } - if (!notification.seen && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.type)) { + 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. @@ -521,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 }, @@ -616,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) { @@ -680,6 +706,24 @@ 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 }), diff --git a/src/modules/users.js b/src/modules/users.js index ce3e595d29d7ab31316cc119fd05451ffe5647d6..f9329f2a81b5b6419051b6b389ceb8fcee3536f7 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -48,6 +48,11 @@ const unblockUser = (store, id) => { } const muteUser = (store, 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]) @@ -56,6 +61,10 @@ const muteUser = (store, id) => { } const unmuteUser = (store, 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])) } @@ -83,10 +92,6 @@ const unmuteDomain = (store, 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 || [] @@ -146,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 }, @@ -174,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 }, @@ -244,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 } } } @@ -254,7 +250,8 @@ export const defaultState = { users: [], usersObject: {}, signUpPending: false, - signUpErrors: [] + signUpErrors: [], + relationships: {} } const users = { @@ -279,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 }) }, @@ -298,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 }) }, @@ -374,9 +371,9 @@ const users = { return rootState.api.backendInteractor.unsubscribeUser({ id }) .then((relationship) => commit('updateUserRelationship', [relationship])) }, - toggleActivationStatus ({ rootState, commit }, user) { + toggleActivationStatus ({ rootState, commit }, { user }) { const api = user.deactivated ? rootState.api.backendInteractor.activateUser : rootState.api.backendInteractor.deactivateUser - api(user) + api({ user }) .then(({ deactivated }) => commit('updateActivationStatus', { user, deactivated })) }, registerPushNotifications (store) { @@ -416,7 +413,7 @@ const users = { }, addNewNotifications (store, { notifications }) { const users = map(notifications, 'from_profile') - const targetUsers = map(notifications, 'target') + const targetUsers = map(notifications, 'target').filter(_ => _) const notificationIds = notifications.map(_ => _.id) store.commit('addNewUsers', users) store.commit('addNewUsers', targetUsers) @@ -431,7 +428,7 @@ const users = { store.commit('setUserForNotification', notification) }) }, - searchUsers (store, query) { + searchUsers (store, { query }) { return store.rootState.api.backendInteractor.searchUsers({ query }) .then((users) => { store.commit('addNewUsers', users) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 20eaa9a0f9ad8cb75627401933f6a0ee58667224..dfffc291cda0a446bab4e53c9d8065ee5d138e5c 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -1,10 +1,8 @@ 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' @@ -17,6 +15,7 @@ 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' @@ -29,6 +28,7 @@ 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` @@ -74,6 +74,7 @@ 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}` @@ -323,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 : '') @@ -357,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 : '' @@ -402,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] @@ -419,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] @@ -436,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), { @@ -446,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), { @@ -478,7 +481,7 @@ const deactivateUser = ({ credentials, user: { screen_name: nickname } }) => { }).then(response => get(response, 'users.0')) } -const deleteUser = ({ credentials, ...user }) => { +const deleteUser = ({ credentials, user }) => { const screenName = user.screen_name const headers = authHeaders(credentials) @@ -495,8 +498,7 @@ const fetchTimeline = ({ until = false, userId = false, tag = false, - withMuted = false, - withMove = false + withMuted = false }) => { const timelineUrls = { public: MASTODON_PUBLIC_TIMELINE, @@ -536,12 +538,11 @@ const fetchTimeline = ({ if (timeline === 'public' || timeline === 'publicAndExternal') { params.push(['only_media', false]) } - if (timeline === 'notifications') { - params.push(['with_move', withMove]) + 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}` @@ -844,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' @@ -880,12 +885,20 @@ 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 }) => { @@ -962,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}` @@ -980,6 +995,10 @@ 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 }) } @@ -1002,6 +1021,15 @@ const unmuteDomain = ({ 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 @@ -1157,6 +1185,7 @@ const apiService = { denyUser, suggestions, markNotificationsAsSeen, + dismissNotification, vote, fetchPoll, fetchFavoritedByUsers, @@ -1168,6 +1197,7 @@ const apiService = { updateNotificationSettings, search2, searchUsers, + fetchKnownDomains, fetchDomainMutes, muteDomain, unmuteDomain diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js index d1b17c61a23908edcc6baebe49573b66125771bc..ec10426991cdc3590881cc6ed94305f2e3fd1587 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 }, {}) } +/** + * 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})` +} -export { - rgb2hex, - hex2rgb, - mixrgb, - invert, - getContrastRatio, - alphaBlend +/** + * 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 84169a7b052f1e2b5f4ba23690a2ac90f2a90b73..c7ed65a4d42300bba9be1e4f3781a137f4c75b53 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -1,4 +1,5 @@ import escape from 'escape-html' +import { isStatusNotification } from '../notification_utils/notification_utils.js' const qvitterStatusType = (status) => { if (status.is_post_verb) { @@ -74,13 +75,7 @@ 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 @@ -137,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, @@ -160,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) @@ -215,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) } @@ -346,9 +341,7 @@ export const parseNotification = (data) => { if (masto) { output.type = mastoDict[data.type] || data.type output.seen = data.pleroma.is_seen - output.status = output.type === 'follow' || output.type === 'move' - ? 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 diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index 29b38a0f4952cdd095c0f2c73dbf873854e796fb..08f4c4d631b525b135b63a9d8acc49525a3e8209 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({ id: 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/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index b17bd7bf62ac44e539471e79cdc59f0c4a297a4a..eb479227ca4749737f1b525a4aaa6965dd5f9db6 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 @@ -7,10 +7,15 @@ export const visibleTypes = store => ([ store.state.config.notificationVisibility.mentions && 'mention', store.state.config.notificationVisibility.repeats && 'repeat', 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) diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index 864e32f87894f2cdb92f65572e021ec4e2d5b2a0..64499a1b65b137cff7d7df78ecc30245ad00f7a9 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -11,12 +11,9 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => { const rootState = store.rootState || store.state const timelineData = rootState.statuses.notifications const hideMutedPosts = getters.mergedConfig.hideMutedPosts - const allowFollowingMove = rootState.users.currentUser.allow_following_move args['withMuted'] = !hideMutedPosts - args['withMove'] = !allowFollowingMove - args['timeline'] = 'notifications' if (older) { if (timelineData.minId !== Number.POSITIVE_INFINITY) { diff --git a/src/services/resettable_async_component.js b/src/services/resettable_async_component.js new file mode 100644 index 0000000000000000000000000000000000000000..517bbd886bd9a8e6d9f32baf4547b17a326cfd60 --- /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 900cd56ebdf025ce72359a24034833a039b9ade0..ed0f6d572c0c36e5394c3f379759350a3de1bc89 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 eaa495c4abe8e94b5b48298f94f94af6bbd138ac..fbdcf562317f7b2e58c0604b1f9e35af7abe1886 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 }) -} - -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') +export const generateColors = (themeData) => { + const sourceColors = !themeData.themeEngineVersion + ? colors2to3(themeData.colors || themeData) + : themeData.colors || themeData - 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') +/** + * 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 + } + ], []) + return { ...shadowsAcc, [slotName]: newShadow } + }, {}) +} - data.colors = { - bg: bgRgb, - fg: fgRgb, - text: textRgb, - link: linkRgb, - cRed: cRedRgb, - cBlue: cBlueRgb, - cGreen: cGreenRgb, - cOrange: cOrangeRgb +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 } } - } - // 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 { theme: data, source: theme.source } + }) } -export { - setStyle, - setPreset, - applyTheme, - getTextColor, - generateColors, - generateRadii, - generateShadows, - generateFonts, - generatePreset, - getThemes, - composePreset, - getCssShadow, - getCssShadowFilter -} +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 0000000000000000000000000000000000000000..b577cfab85b4055c5abeeaa7f13f1f839224be67 --- /dev/null +++ b/src/services/theme_data/pleromafe.js @@ -0,0 +1,637 @@ +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' + }, + + postGreentext: { + depends: ['cGreen'], + 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 0000000000000000000000000000000000000000..dd87e3cffe475cd6c567864ffaff8489eaab1152 --- /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/static/config.json b/static/config.json index c82678699c7a93a7ef8efcbf10c8bd95ad462516..0030f78f1266c138d2f82c727fa9eafd00c9631c 100644 --- a/static/config.json +++ b/static/config.json @@ -1,23 +1,28 @@ { - "theme": "pleroma-dark", + "alwaysShowSubjectInput": true, "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, + "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 91859e272144f2d2d954e10c1d22d0a0cb551cf9..0000000000000000000000000000000000000000 --- 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 2e99ba1f00a585964f974a2dfa15574d9fe65e94..0000000000000000000000000000000000000000 --- 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 d10e1918e00794892fb521129bb7c30e0d6ef768..0000000000000000000000000000000000000000 --- 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 5ac17f97eb20b6f7889ed218fc3848ff6a56d250..0000000000000000000000000000000000000000 --- 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 cfb2d9a1efc1d9641cb600a38b91aba3d87f21a2..0000000000000000000000000000000000000000 --- 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 76d82c75478e4c4b2b02303895793d200ec4cf5f..0000000000000000000000000000000000000000 --- 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 8108ed8f6761616a8769663bc0ce4b4d56e05b0b..0000000000000000000000000000000000000000 --- 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 8858cb8078dcab9fb8d2bfca65190e9af9d0bd01..0000000000000000000000000000000000000000 --- 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 77d44c5fa1e47c84667dc939982c36dfaeaf95da..0000000000000000000000000000000000000000 --- 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 a7445030bf5dd9cb704b40c26acd265e23a156f6..0000000000000000000000000000000000000000 --- 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 be728d07d0c05e977a90faafd2a4ec8583795d6d..0000000000000000000000000000000000000000 --- 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 8b3914669aadcf854b008c27fc4e4738028ffe41..0000000000000000000000000000000000000000 --- 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 fb44d6e0f5b8e82947c7457b0f4fa5daa3a3b918..0000000000000000000000000000000000000000 --- 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 48a9dcf764d0835d0dcc56948d89deae86687fdf..0000000000000000000000000000000000000000 --- 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 c88f219b78c2a8e797e05629598e08cb3c153b97..0000000000000000000000000000000000000000 --- 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 c2333b8d1d743677c5c4710169d1e00649717874..0000000000000000000000000000000000000000 --- 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 e3cb3c20c162db1fd8328270abaf36632c1e79cd..0000000000000000000000000000000000000000 --- 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 00194bbfceeb252e679b9e14a010596246378db6..0000000000000000000000000000000000000000 --- 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 5344870647c5d5b4c00a868e7ab27df0da133597..0000000000000000000000000000000000000000 --- 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 3cd7e860c09a4779cc3b0d1afa7791e470a3ef77..0000000000000000000000000000000000000000 --- 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 7e660c3023a496c9bc749a478071dee1e1bc510b..0000000000000000000000000000000000000000 --- 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 8ffcf04d92fd9eb34fb3939204f93949c2da2135..0000000000000000000000000000000000000000 --- 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 74e9b76932fc06b9765879d5b2b93f9395855d53..0000000000000000000000000000000000000000 --- 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 72918a5df9ef9301738adef80f5266cdb75787fc..0000000000000000000000000000000000000000 --- 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 080ed34ce6a19ebfef6fbcc6e91c3641fcb47ae1..0000000000000000000000000000000000000000 --- 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 988eac51d9480ebb260bb1e8b4594e092ebf6295..0000000000000000000000000000000000000000 --- 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 2ee2a606979c43723cadd9b8475525bbf669eb96..0000000000000000000000000000000000000000 --- 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 dc0dd03a036722d040e6d9e6df650a5fe05e8e58..0000000000000000000000000000000000000000 --- 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 f9fd213ae7d3ce27a2f470338e130d4daa614358..0000000000000000000000000000000000000000 --- 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 205efeaec9d9307b00f662a68bf130206cd403a7..0000000000000000000000000000000000000000 --- 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 0c2c7ce42e03c550102a0d9b6af829591c09d311..0000000000000000000000000000000000000000 --- 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 37bb7679a139b8b871fb5e060f3b19a82c2e2b86..0000000000000000000000000000000000000000 --- 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 f2ad232c597304453fee49b86019f7b0bf340d38..0000000000000000000000000000000000000000 --- 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 8d14ab9b8c2d8de31ad5dd1efdb96e03d8a8095e..0000000000000000000000000000000000000000 --- 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 f7a4a0b4b94b3e79c90855086b6b6b5be78b3585..0000000000000000000000000000000000000000 --- 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 0537d1ad5c4f89b7e9939280893454771cbd9a5c..0000000000000000000000000000000000000000 --- 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 d5969fec237af0b6f1ca99d6103d7344528c244d..0000000000000000000000000000000000000000 --- 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 91f0471fc50853c9b2249e518e5437d339a9103c..0000000000000000000000000000000000000000 --- 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 41d935dd14eb865125f6aa2198d9869fc798dcfd..0000000000000000000000000000000000000000 --- 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 1916c67bc3ce29cf2dd0663053f184603ef43fa4..0000000000000000000000000000000000000000 --- 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 6cb2fb5802111818aadad1daf4dc1501d91a0ddb..0000000000000000000000000000000000000000 --- 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 fc7ccf471ae13031848926ed2f00eeb8dd9a0e8f..0000000000000000000000000000000000000000 --- 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 8622d17e0c0128d1c7b04691bf9b17f2a5e88cf6..0000000000000000000000000000000000000000 --- 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 df4d9ef5e2a693a9468d0b65b4d43d93d0c8d4bf..0000000000000000000000000000000000000000 --- 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 b68c940719ff2bd1f9287c83c7609140ce7fa30e..0000000000000000000000000000000000000000 --- 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 54276ab11c501d8bcff5cb752da1b71c358a527b..0000000000000000000000000000000000000000 --- 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 86482b72dec69c2a7cd9de04db8671acbf445431..0000000000000000000000000000000000000000 --- 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 e1d46f92706f981155f30c8bdff9f583d1dea79c..0000000000000000000000000000000000000000 --- 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 1a85689aa9c2a7cd62f0bcf9660a2d98608552eb..0000000000000000000000000000000000000000 --- 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 14acac171819e3a22d283896d2b6daa24a21987c..0000000000000000000000000000000000000000 --- 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 18f43bfd6d3fc915a1b1294fe3816f444889757c..0000000000000000000000000000000000000000 --- 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 bd4f9cc4249bf7135b44a38bcbb36fe5d4522146..0000000000000000000000000000000000000000 --- 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 ded1806915874b90581f38855793ceccfd0c91c3..0000000000000000000000000000000000000000 --- 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 7d1d38624dc9e0cdbb30b4b8d2667d9d67d3829e..0000000000000000000000000000000000000000 --- 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 ac16f12c9040e8ee6abd2c572f22a844c845e2ea..0000000000000000000000000000000000000000 --- 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 7164cb046006b16d93fe1987d279e912eda4760a..0000000000000000000000000000000000000000 --- 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 487376500a77840e6ca29ddd0b2ec3c0c55f962a..0000000000000000000000000000000000000000 --- 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 1c8f2332e4d6c8d7cbcd846355d7071d9957a348..0000000000000000000000000000000000000000 --- 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 cb54d4c540e118a099df8f126d012b7799db40a2..0000000000000000000000000000000000000000 --- 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 09ecf08ef1702f14c5620ca7f59787f2e40ed92f..0000000000000000000000000000000000000000 --- 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 f14868230f86174fdeb2bf00135eda590d8e4467..0000000000000000000000000000000000000000 --- 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 c8dfda3f892789403ba978c0b646c8c51e6148b5..0000000000000000000000000000000000000000 --- 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 e6ef32e3311a14a259072f61e6b4f719fb92181b..0000000000000000000000000000000000000000 --- 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 7e4c51b7a53c7c0b3a0a547d546e0880989ff26b..0000000000000000000000000000000000000000 --- 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 ea8e5a0c4cfb184a2f49fa143bfd9f9d63965c21..0000000000000000000000000000000000000000 --- 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/fontello.json b/static/fontello.json index 5a7086a23100d24936d5f2b87ee39c4cd1052508..ac3f0a18c7bec6815a83857b552423aa04dca689 100755 --- a/static/fontello.json +++ b/static/fontello.json @@ -345,6 +345,36 @@ "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 23508970dba939d135f7464d89152f711e587fda..23f57c65e871c0ee98979dbf415df86c01f4f81e 100644 --- a/static/styles.json +++ b/static/styles.json @@ -1,6 +1,6 @@ { - "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"], @@ -12,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 c02cb71984bbd6af9bf8bdcea61cfadca0161bfe..b2c668151f856204ddeb98f1a98797c5f108cd5a 100644 --- a/static/terms-of-service.html +++ b/static/terms-of-service.html @@ -1,7 +1,9 @@ <h4>Terms of Service</h4> -<p>This is a placeholder ToS.</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>Edit <code>"/static/terms-of-service.html"</code> to make it 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 6119bf8876adbd7f11f70fba4e98782b083c122a..76b962c569643f316b03208002227b6d01c65457 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 becf704fcc8a73806f013aa608f726b0822161b2..0968fff0cb3753d5169956037409a3cab4ee71c8 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 0000000000000000000000000000000000000000..a3b90a0a11df09861ffa3268aee6d755a6d64f12 --- /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 0000000000000000000000000000000000000000..2703fba11aecf29a87677e563b73ca58c240f6db --- /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 0000000000000000000000000000000000000000..05fc300aa7c9958a816a96197a3dfa51e356094d --- /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 70ee89d1b220324ddf9f4898d7ebe7d6c56a885e..24480d2c7bf050b971f89bce2ead456f9d86ae70 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,18 @@ "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", + "selectedMenu": "--accent", + "selectedMenuPopover": "--accent" }, "radii": { "btn": "0", diff --git a/static/themes/redmond-xx.json b/static/themes/redmond-xx.json index 4fd6a36911d33fd053887fdbb377ca615b452807..cf9010fe20319dc628574430edc58dd526ff3f0e 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,18 @@ "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", + "selectedMenu": "--accent", + "selectedMenuPopover": "--accent" }, "radii": { "btn": "0", diff --git a/static/themes/redmond-xxi.json b/static/themes/redmond-xxi.json index d10bf138ffaef05749e96bd8659f81045af78b70..7fdc4a6d654f326280ad868ad82c659ea1a33a88 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,18 @@ "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", + "selectedMenu": "--accent", + "selectedMenuPopover": "--accent" }, "radii": { "btn": "0", diff --git a/test/unit/specs/components/emoji_input.spec.js b/test/unit/specs/components/emoji_input.spec.js index b1b988021ea292d09d325947c164a7aa52e57212..045b47fdde998e514b74fd1170de31680ccf0dab 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 1b6a47d7aa0484cae28dada11f5c954269556e6d..dcf066f9a3f8b73b4181c87f6bd365c5e8cbd366 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/users.spec.js b/test/unit/specs/modules/users.spec.js index eeb7afefe05ec7b5e02e06c69e46286533b39600..670acfc8221251431ce3ed5879fd3b0f2e2b91db 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 cfb380bafcf9d54728ead47b919cd0103c00ae9a..166fce2b7a37f934521ac4720dbaaffebd6e4692 100644 --- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js +++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js @@ -338,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', () => { @@ -366,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/status_parser/status_parses.spec.js b/test/unit/specs/services/status_parser/status_parses.spec.js deleted file mode 100644 index 7afd5042c8da9067e7bb8dca698a6eb48c3bafac..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..f0072e7d567e3c53bc7343e330222ecf486c60d0 --- /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 0000000000000000000000000000000000000000..c634cade967c768e2413fc414504d6a16263ca3a --- /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/yarn.lock b/yarn.lock index b794042f308028ff3f8fd834fade0cc87f631552..f05b00b1513eb84e47f15d889227178033cc266b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1062,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" @@ -2327,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" @@ -2544,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" @@ -2558,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" @@ -2577,13 +2572,6 @@ 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" - dependencies: - dom-serializer "0" - domelementtype "1" - duplexer2@~0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" @@ -2710,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" @@ -3761,17 +3749,6 @@ html-webpack-plugin@^3.0.0, html-webpack-plugin@^3.2.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" @@ -4756,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" @@ -4779,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" @@ -4814,14 +4783,10 @@ 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" @@ -4870,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" @@ -5537,10 +5502,6 @@ 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" @@ -5941,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" @@ -6249,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" @@ -6525,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" @@ -6843,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" @@ -7229,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" @@ -7335,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: @@ -7419,7 +7337,7 @@ 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: @@ -7784,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" @@ -7823,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" @@ -7906,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" @@ -7922,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" @@ -7933,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" @@ -8031,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" @@ -8106,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"