From d0075026290c90d8406c7ac81413259a8ae58ec7 Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Fri, 15 Nov 2019 08:39:21 +0200 Subject: [PATCH 001/147] add fetching for emoji reactions, draft design --- src/components/conversation/conversation.js | 1 + src/components/status/status.js | 6 ++++ src/components/status/status.vue | 28 +++++++++++++++++++ src/modules/statuses.js | 14 +++++++++- src/services/api/api.service.js | 6 ++++ .../backend_interactor_service.js | 2 ++ 6 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 72ee9c39..715804ff 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -149,6 +149,7 @@ const conversation = { if (!id) return this.highlight = id this.$store.dispatch('fetchFavsAndRepeats', id) + this.$store.dispatch('fetchEmojiReactions', id) }, getHighlight () { return this.isExpanded ? this.highlight : null diff --git a/src/components/status/status.js b/src/components/status/status.js index 4fbd5ac3..8268e615 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -278,6 +278,12 @@ const Status = { hidePostStats () { return this.mergedConfig.hidePostStats }, + emojiReactions () { + return { + '🤔': [{ 'id': 'xyz..' }, { 'id': 'zyx...' }], + '🐻': [{ 'id': 'abc...' }] + } + }, ...mapGetters(['mergedConfig']) }, components: { diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 65778b2e..aae58a5e 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -354,6 +354,17 @@ +
+ +
+
currentUser.id === id) }, + addEmojiReactions (state, { id, emojiReactions, currentUser }) { + const status = state.allStatusesObject[id] + status.emojiReactions = emojiReactions + status.reactedWithEmoji = findKey(emojiReactions, { id: currentUser.id }) + }, updateStatusWithPoll (state, { id, poll }) { const status = state.allStatusesObject[id] status.poll = poll @@ -611,6 +616,13 @@ const statuses = { commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser }) }) }, + fetchEmojiReactions ({ rootState, commit }, id) { + rootState.api.backendInteractor.fetchEmojiReactions(id).then( + emojiReactions => { + commit('addEmojiReactions', { id, emojiReactions, currentUser: rootState.users.currentUser }) + } + ) + }, fetchFavs ({ rootState, commit }, id) { rootState.api.backendInteractor.fetchFavoritedByUsers(id) .then(favoritedByUsers => commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser })) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 8f5eb416..7ef4b74a 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -71,6 +71,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute` const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute` const MASTODON_SEARCH_2 = `/api/v2/search` const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' +const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/emoji_reactions_by` const oldfetch = window.fetch @@ -864,6 +865,10 @@ const fetchRebloggedByUsers = ({ id }) => { return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser)) } +const fetchEmojiReactions = ({ id }) => { + return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id) }) +} + const reportUser = ({ credentials, userId, statusIds, comment, forward }) => { return promisedRequest({ url: MASTODON_REPORT_USER_URL, @@ -997,6 +1002,7 @@ const apiService = { fetchPoll, fetchFavoritedByUsers, fetchRebloggedByUsers, + fetchEmojiReactions, reportUser, updateNotificationSettings, search2, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index d6617276..52234fcc 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -143,6 +143,7 @@ const backendInteractorService = credentials => { const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({ id }) const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({ id }) + const fetchEmojiReactions = (id) => apiService.fetchEmojiReactions({ id }) const reportUser = (params) => apiService.reportUser({ credentials, ...params }) const favorite = (id) => apiService.favorite({ id, credentials }) @@ -210,6 +211,7 @@ const backendInteractorService = credentials => { fetchPoll, fetchFavoritedByUsers, fetchRebloggedByUsers, + fetchEmojiReactions, reportUser, favorite, unfavorite, From de945ba3e9470b28dd010fb32f658b42053f19d3 Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Fri, 15 Nov 2019 16:29:25 +0200 Subject: [PATCH 002/147] wip commit, add basic popover for emoji reaction select --- src/components/react_button/react_button.js | 50 +++++++++++++ src/components/react_button/react_button.vue | 78 ++++++++++++++++++++ src/components/status/status.js | 2 + src/components/status/status.vue | 10 ++- src/i18n/en.json | 1 + 5 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 src/components/react_button/react_button.js create mode 100644 src/components/react_button/react_button.vue diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js new file mode 100644 index 00000000..d1d15d93 --- /dev/null +++ b/src/components/react_button/react_button.js @@ -0,0 +1,50 @@ +import { mapGetters } from 'vuex' + +const ReactButton = { + props: ['status', 'loggedIn'], + data () { + return { + animated: false, + showTooltip: false, + popperOptions: { + modifiers: { + preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' } + } + } + } + }, + methods: { + openReactionSelect () { + console.log('test') + this.showTooltip = true + }, + closeReactionSelect () { + this.showTooltip = false + }, + favorite () { + if (!this.status.favorited) { + this.$store.dispatch('favorite', { id: this.status.id }) + } else { + this.$store.dispatch('unfavorite', { id: this.status.id }) + } + this.animated = true + setTimeout(() => { + this.animated = false + }, 500) + } + }, + computed: { + emojis () { + return this.$store.state.instance.emoji || [] + }, + classes () { + return { + 'icon-smile': true, + 'animate-spin': this.animated + } + }, + ...mapGetters(['mergedConfig']) + } +} + +export default ReactButton diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue new file mode 100644 index 00000000..93638770 --- /dev/null +++ b/src/components/react_button/react_button.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/components/status/status.js b/src/components/status/status.js index 8268e615..8c6fc0cf 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -1,5 +1,6 @@ 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' @@ -289,6 +290,7 @@ const Status = { components: { Attachment, FavoriteButton, + ReactButton, RetweetButton, ExtraButtons, PostStatusForm, diff --git a/src/components/status/status.vue b/src/components/status/status.vue index aae58a5e..d455ccf6 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -356,12 +356,12 @@
@@ -393,6 +393,10 @@ :logged-in="loggedIn" :status="status" /> + Date: Sun, 15 Dec 2019 14:29:45 -0500 Subject: [PATCH 003/147] wire up staff accounts with correct store data --- src/boot/after_store.js | 11 ++++------- src/components/staff_panel/staff_panel.js | 3 ++- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 228a0497..0bb1b2b4 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -185,12 +185,9 @@ const getAppSecret = async ({ store }) => { }) } -const resolveStaffAccounts = async ({ store, accounts }) => { - const backendInteractor = store.state.api.backendInteractor - let nicknames = accounts.map(uri => uri.split('/').pop()) - .map(id => backendInteractor.fetchUser({ id })) - nicknames = await Promise.all(nicknames) - +const resolveStaffAccounts = ({ store, accounts }) => { + const nicknames = accounts.map(uri => uri.split('/').pop()) + nicknames.map(nickname => store.dispatch('fetchUser', nickname)) store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames }) } @@ -236,7 +233,7 @@ const getNodeInfo = async ({ store }) => { }) const accounts = metadata.staffAccounts - await resolveStaffAccounts({ store, accounts }) + resolveStaffAccounts({ store, accounts }) } else { throw (res) } diff --git a/src/components/staff_panel/staff_panel.js b/src/components/staff_panel/staff_panel.js index 93e950ad..4f98fff6 100644 --- a/src/components/staff_panel/staff_panel.js +++ b/src/components/staff_panel/staff_panel.js @@ -1,3 +1,4 @@ +import map from 'lodash/map' import BasicUserCard from '../basic_user_card/basic_user_card.vue' const StaffPanel = { @@ -6,7 +7,7 @@ const StaffPanel = { }, computed: { staffAccounts () { - return this.$store.state.instance.staffAccounts + return map(this.$store.state.instance.staffAccounts, nickname => this.$store.getters.findUser(nickname)).filter(_ => _) } } } From e5a34870f0f7154712783fb6d9c20edf4c06ad35 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sat, 28 Dec 2019 15:55:42 +0200 Subject: [PATCH 004/147] Accent works --- src/App.scss | 2 +- src/components/color_input/color_input.vue | 42 +++++++++++++++++-- .../sticker_picker/sticker_picker.vue | 2 +- .../style_switcher/style_switcher.js | 5 ++- .../style_switcher/style_switcher.vue | 11 ++++- src/i18n/en.json | 1 + src/services/style_setter/style_setter.js | 13 +++++- static/themes/breezy-dark.json | 2 +- static/themes/breezy-light.json | 2 +- 9 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/App.scss b/src/App.scss index 754ca62e..3b03a761 100644 --- a/src/App.scss +++ b/src/App.scss @@ -198,7 +198,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 { &, diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue index 9db62e81..fa26d079 100644 --- a/src/components/color_input/color_input.vue +++ b/src/components/color_input/color_input.vue @@ -18,7 +18,7 @@ @input="$emit('input', typeof value === 'undefined' ? fallback : undefined)" >
@@ -639,6 +694,18 @@ } } + &-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/i18n/en.json b/src/i18n/en.json index 75d66b9f..31f4ac24 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -21,6 +21,12 @@ "chat": { "title": "Chat" }, + "domain_mute_card": { + "mute": "Mute", + "mute_progress": "Muting...", + "unmute": "Unmute", + "unmute_progress": "Unmuting..." + }, "exporter": { "export": "Export", "processing": "Processing, you'll soon be asked to download your file" @@ -264,6 +270,7 @@ "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.", "delete_account_instructions": "Type your password in the input below to confirm account deletion.", "discoverable": "Allow discovery of this account in search results and other services", + "domain_mutes": "Domains", "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.", "pad_emoji": "Pad emoji with spaces when adding from picker", "export_theme": "Save preset", @@ -361,6 +368,7 @@ "post_status_content_type": "Post status content type", "stop_gifs": "Play-on-hover GIFs", "streaming": "Enable automatic streaming of new posts when scrolled to the top", + "user_mutes": "Users", "useStreamingApi": "Receive posts and notifications real-time", "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)", "text": "Text", @@ -369,6 +377,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", "upload_a_photo": "Upload a photo", "user_settings": "User Settings", "values": { diff --git a/src/modules/users.js b/src/modules/users.js index b9ed0efa..ce3e595d 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -72,6 +72,16 @@ const showReblogs = (store, userId) => { .then((relationship) => store.commit('updateUserRelationship', [relationship])) } +const muteDomain = (store, domain) => { + return store.rootState.api.backendInteractor.muteDomain({ domain }) + .then(() => store.commit('addDomainMute', domain)) +} + +const unmuteDomain = (store, domain) => { + return store.rootState.api.backendInteractor.unmuteDomain({ domain }) + .then(() => store.commit('removeDomainMute', domain)) +} + export const mutations = { setMuted (state, { user: { id }, muted }) { const user = state.usersObject[id] @@ -177,6 +187,20 @@ export const mutations = { state.currentUser.muteIds.push(muteId) } }, + saveDomainMutes (state, domainMutes) { + state.currentUser.domainMutes = domainMutes + }, + addDomainMute (state, domain) { + if (state.currentUser.domainMutes.indexOf(domain) === -1) { + state.currentUser.domainMutes.push(domain) + } + }, + removeDomainMute (state, domain) { + const index = state.currentUser.domainMutes.indexOf(domain) + if (index !== -1) { + state.currentUser.domainMutes.splice(index, 1) + } + }, setPinnedToUser (state, status) { const user = state.usersObject[status.user.id] const index = user.pinnedStatusIds.indexOf(status.id) @@ -297,6 +321,25 @@ const users = { unmuteUsers (store, ids = []) { return Promise.all(ids.map(id => unmuteUser(store, id))) }, + fetchDomainMutes (store) { + return store.rootState.api.backendInteractor.fetchDomainMutes() + .then((domainMutes) => { + store.commit('saveDomainMutes', domainMutes) + return domainMutes + }) + }, + muteDomain (store, domain) { + return muteDomain(store, domain) + }, + unmuteDomain (store, domain) { + return unmuteDomain(store, domain) + }, + muteDomains (store, domains = []) { + return Promise.all(domains.map(domain => muteDomain(store, domain))) + }, + unmuteDomains (store, domain = []) { + return Promise.all(domain.map(domain => unmuteDomain(store, domain))) + }, fetchFriends ({ rootState, commit }, id) { const user = rootState.users.usersObject[id] const maxId = last(user.friendIds) @@ -460,6 +503,7 @@ const users = { user.credentials = accessToken user.blockIds = [] user.muteIds = [] + user.domainMutes = [] commit('setCurrentUser', user) commit('addNewUsers', [user]) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index ef0267aa..dcbedd8b 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -72,6 +72,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute` const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute` const MASTODON_SEARCH_2 = `/api/v2/search` const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' +const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks' const MASTODON_STREAMING = '/api/v1/streaming' const oldfetch = window.fetch @@ -948,6 +949,28 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => { }) } +const fetchDomainMutes = ({ credentials }) => { + return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials }) +} + +const muteDomain = ({ domain, credentials }) => { + return promisedRequest({ + url: MASTODON_DOMAIN_BLOCKS_URL, + method: 'POST', + payload: { domain }, + credentials + }) +} + +const unmuteDomain = ({ domain, credentials }) => { + return promisedRequest({ + url: MASTODON_DOMAIN_BLOCKS_URL, + method: 'DELETE', + payload: { domain }, + credentials + }) +} + export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => { return Object.entries({ ...(credentials @@ -1110,7 +1133,10 @@ const apiService = { reportUser, updateNotificationSettings, search2, - searchUsers + searchUsers, + fetchDomainMutes, + muteDomain, + unmuteDomain } export default apiService From f052ac4a1e59685332bf3798ce3978d6304816d8 Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Wed, 15 Jan 2020 22:38:31 +0200 Subject: [PATCH 040/147] Add domain mutes to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42554607..b09eb3a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Private mode support - Support for 'Move' type notifications - Pleroma AMOLED dark theme +- User level domain mutes, under User Settings -> Mutes ### Changed - Captcha now resets on failed registrations - Notifications column now cleans itself up to optimize performance when tab is left open for a long time From f16ec75c7011fa7e6d5deb7763553b2c70d9a86e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 16 Jan 2020 20:53:05 +0200 Subject: [PATCH 041/147] opacity handling --- .../style_switcher/style_switcher.js | 10 +- src/services/color_convert/color_convert.js | 2 +- src/services/style_setter/style_setter.js | 70 +------ src/services/theme_data/theme_data.service.js | 193 +++++++++++++----- 4 files changed, 149 insertions(+), 126 deletions(-) diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js index 98c2cbc5..16be209a 100644 --- a/src/components/style_switcher/style_switcher.js +++ b/src/components/style_switcher/style_switcher.js @@ -15,7 +15,7 @@ import { import { CURRENT_VERSION, SLOT_INHERITANCE, - DEFAULT_OPACITY, + OPACITIES, getLayers } from '../../services/theme_data/theme_data.service.js' import ColorInput from '../color_input/color_input.vue' @@ -74,8 +74,8 @@ export default { .map(key => [key, '']) .reduce((acc, [key, val]) => ({ ...acc, [ key + 'ColorLocal' ]: val }), {}), - ...Object.keys(DEFAULT_OPACITY) - .map(key => [key, undefined]) + ...Object.keys(OPACITIES) + .map(key => console.log(key) || [key, '']) .reduce((acc, [key, val]) => ({ ...acc, [ key + 'OpacityLocal' ]: val }), {}), shadowSelected: undefined, @@ -115,8 +115,8 @@ export default { .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {}) }, currentOpacity () { - return Object.keys(DEFAULT_OPACITY) - .map(key => [key, this[key + 'OpacityLocal']]) + return Object.keys(OPACITIES) + .map(key => console.log(key) || [key, this[key + 'OpacityLocal']]) .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {}) }, currentRadii () { diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js index c727a9fe..6b228a58 100644 --- a/src/services/color_convert/color_convert.js +++ b/src/services/color_convert/color_convert.js @@ -159,7 +159,7 @@ export const hex2rgb = (hex) => { * @returns {Object} result */ export const mixrgb = (a, b) => { - return Object.keys(a).reduce((acc, k) => { + return 'rgb'.split('').reduce((acc, k) => { acc[k] = (a[k] + b[k]) / 2 return acc }, {}) diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index c1a25101..9237a8dc 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -1,7 +1,7 @@ import { times } from 'lodash' import { convert } from 'chromatism' import { rgb2hex, hex2rgb, rgba2css, getCssColor } from '../color_convert/color_convert.js' -import { getColors, DEFAULT_OPACITY } from '../theme_data/theme_data.service.js' +import { getColors } 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 @@ -115,76 +115,12 @@ const getCssShadowFilter = (input) => { } export const generateColors = (themeData) => { - const rawOpacity = Object.assign({ ...DEFAULT_OPACITY }, Object.entries(themeData.opacity || {}).reduce((acc, [k, v]) => { - if (typeof v !== 'undefined') { - acc[k] = v - } - return acc - }, {})) - - const inputColors = themeData.colors || themeData - - const opacity = { - ...rawOpacity, - ...Object.entries(inputColors).reduce((acc, [k, v]) => { - if (v === 'transparent') { - acc[k] = 0 - } - return acc - }, {}) - } - - // Cycle one: just whatever we have - const sourceColors = Object.entries(inputColors).reduce((acc, [k, v]) => { - if (typeof v === 'object') { - acc[k] = v - } else { - let value = v - if (v === 'transparent') { - // TODO: hack to keep rest of the code from complaining - value = '#FF00FF' - } - if (!value || value.startsWith('--')) { - acc[k] = value - } else { - acc[k] = hex2rgb(value) - } - } - return acc - }, {}) + const sourceColors = themeData.colors || themeData const isLightOnDark = convert(sourceColors.bg).hsl.l < convert(sourceColors.text).hsl.l const mod = isLightOnDark ? 1 : -1 - const colors = getColors(sourceColors, opacity, mod) - - // Inheriting opacities - 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['faintLink'].a = v - colors['panelFaint'].a = v - colors['lightBgFaintText'].a = v - colors['lightBgFaintLink'].a = v - } - if (k === 'bg') { - colors['lightBg'].a = v - } - if (k === 'badge') { - colors['badgeNotification'].a = v - return - } - if (colors[k]) { - colors[k].a = v - } else { - console.error('Wrong key ' + k) - } - }) + const { colors, opacity } = getColors(sourceColors, themeData.opacity || {}, mod) const htmlColors = Object.entries(colors) .reduce((acc, [k, v]) => { diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js index a345d996..e76d70ed 100644 --- a/src/services/theme_data/theme_data.service.js +++ b/src/services/theme_data/theme_data.service.js @@ -26,24 +26,17 @@ export const LAYERS = { } export const DEFAULT_OPACITY = { - panel: 1, - btn: 1, - border: 1, - bg: 1, - badge: 1, - text: 1, alert: 0.5, input: 0.5, faint: 0.5, - underlay: 0.15, - poll: 1, - topBar: 1 + underlay: 0.15 } export const SLOT_INHERITANCE = { bg: { depends: [], - priority: 1 + priority: 1, + opacity: 'bg' }, fg: { depends: [], @@ -53,7 +46,10 @@ export const SLOT_INHERITANCE = { depends: [], priority: 1 }, - underlay: '#000000', + underlay: { + default: '#000000', + opacity: 'underlay' + }, link: { depends: ['accent'], priority: 1 @@ -62,8 +58,14 @@ export const SLOT_INHERITANCE = { depends: ['link'], priority: 1 }, - faint: '--text', - faintLink: '--link', + faint: { + depends: ['text'], + opacity: 'faint' + }, + faintLink: { + depends: ['link'], + opacity: 'faint' + }, cBlue: '#0000ff', cRed: '#FF0000', @@ -158,11 +160,13 @@ export const SLOT_INHERITANCE = { 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: { @@ -173,6 +177,7 @@ export const SLOT_INHERITANCE = { icon: { depends: ['bg', 'text'], + inheritsOpacity: false, color: (mod, bg, text) => mixrgb(bg, text) }, @@ -189,7 +194,10 @@ export const SLOT_INHERITANCE = { }, // Panel header - panel: '--fg', + panel: { + depends: ['fg'], + opacity: 'panel' + }, panelText: { depends: ['fgText'], layer: 'panel', @@ -198,6 +206,7 @@ export const SLOT_INHERITANCE = { panelFaint: { depends: ['fgText'], layer: 'panel', + opacity: 'faint', textColor: true }, panelLink: { @@ -233,7 +242,10 @@ export const SLOT_INHERITANCE = { }, // Buttons - btn: '--fg', + btn: { + depends: ['fg'], + opacity: 'btn' + }, btnText: { depends: ['fgText'], layer: 'btn', @@ -325,7 +337,10 @@ export const SLOT_INHERITANCE = { }, // Input fields - input: '--fg', + input: { + depends: ['fg'], + opacity: 'input' + }, inputText: { depends: ['text'], layer: 'input', @@ -344,7 +359,10 @@ export const SLOT_INHERITANCE = { textColor: true }, - alertError: '--cRed', + alertError: { + depends: ['cRed'], + opacity: 'alert' + }, alertErrorText: { depends: ['text'], layer: 'alert', @@ -358,7 +376,10 @@ export const SLOT_INHERITANCE = { textColor: true }, - alertWarning: '--cOrange', + alertWarning: { + depends: ['cOrange'], + opacity: 'alert' + }, alertWarningText: { depends: ['text'], layer: 'alert', @@ -465,78 +486,144 @@ export const topoSort = ( return output } +export const getOpacitySlot = ( + v, + inheritance = SLOT_INHERITANCE, + getDeps = getDependencies +) => { + if (v.opacity === null) return + if (v.opacity) return v.opacity + const findInheritedOpacity = (val) => { + const depSlot = val.depends[0] + if (depSlot === undefined) return + const dependency = getDeps(depSlot, inheritance)[0] + if (dependency === undefined) return + if (dependency.opacity || dependency === null) { + return dependency.opacity + } else if (dependency.depends) { + return findInheritedOpacity(dependency) + } else { + return null + } + } + if (v.depends) { + return findInheritedOpacity(v) + } +} + 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 }), {}) ) -console.log(SLOT_ORDERED) +export const SLOTS_OPACITIES_DICT = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) => { + const opacity = getOpacitySlot(v, SLOT_INHERITANCE, getDependencies) + if (opacity) { + return { ...acc, [k]: opacity } + } else { + return acc + } +}, {}) -export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.reduce((acc, key) => { +export const OPACITIES = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) => { + const opacity = getOpacitySlot(v, SLOT_INHERITANCE, getDependencies) + if (opacity) { + return { + ...acc, + [opacity]: { + defaultValue: DEFAULT_OPACITY[opacity] || 1, + affectedSlots: [...((acc[opacity] && acc[opacity].affectedSlots) || []), k] + } + } + } else { + return acc + } +}, {}) + +export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.reduce(({ colors, opacity }, key) => { const value = SLOT_INHERITANCE[key] + const isObject = typeof value === 'object' + const isString = typeof value === 'string' const sourceColor = sourceColors[key] + let outputColor = null if (sourceColor) { + // Color is defined in source color let targetColor = sourceColor - if (typeof sourceColor === 'string' && sourceColor.startsWith('--')) { + if (targetColor === 'transparent') { + targetColor = { + // TODO: try to use alpha-blended background here + ...convert('#FF00FF').rgb, + a: 0 + } + } else if (typeof sourceColor === 'string' && sourceColor.startsWith('--')) { + // Color references other color const [variable, modifier] = sourceColor.split(/,/g).map(str => str.trim()) const variableSlot = variable.substring(2) - targetColor = acc[variableSlot] || sourceColors[variableSlot] + targetColor = colors[variableSlot] || sourceColors[variableSlot] if (modifier) { - console.log(targetColor, acc, variableSlot) targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb } - console.log(targetColor, acc, variableSlot) + } else if (typeof sourceColor === 'string' && sourceColor.startsWith('#')) { + targetColor = convert(targetColor).rgb } - return { ...acc, [key]: { ...targetColor } } - } else if (typeof value === 'string' && value.startsWith('#')) { - return { ...acc, [key]: convert(value).rgb } + outputColor = { ...targetColor } + } else if (isString && value.startsWith('#')) { + // slot: '#000000' shorthand + outputColor = convert(value).rgb + } else if (isObject && value.default) { + // same as above except in object form + outputColor = convert(value.default).rgb } else { - const isObject = typeof value === 'object' + // calculate color const defaultColorFunc = (mod, dep) => ({ ...dep }) const deps = getDependencies(key, SLOT_INHERITANCE) const colorFunc = (isObject && value.color) || defaultColorFunc if (value.textColor) { + // textColor case const bg = alphaBlendLayers( - { ...acc[deps[0]] }, + { ...colors[deps[0]] }, getLayers( value.layer, value.variant || value.layer, - acc, - sourceOpacity + colors, + opacity ) ) if (value.textColor === 'bw') { - return { - ...acc, - [key]: contrastRatio(bg).rgb - } + outputColor = contrastRatio(bg).rgb } else { - let color = { ...acc[deps[0]] } + let color = { ...colors[deps[0]] } if (value.color) { const isLightOnDark = convert(bg).hsl.l < convert(color).hsl.l const mod = isLightOnDark ? 1 : -1 - color = value.color(mod, ...deps.map((dep) => ({ ...acc[dep] }))) + color = value.color(mod, ...deps.map((dep) => ({ ...colors[dep] }))) } - return { - ...acc, - [key]: getTextColor( - bg, - { ...color }, - value.textColor === 'preserve' - ) - } - } - } else { - return { - ...acc, - [key]: colorFunc( - mod, - ...deps.map((dep) => ({ ...acc[dep] })) + outputColor = getTextColor( + bg, + { ...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 = SLOTS_OPACITIES_DICT[key] + if (opacitySlot && outputColor.a === undefined) { + outputColor.a = sourceOpacity[opacitySlot] || OPACITIES[opacitySlot].defaultValue || 1 + } + return { + colors: { ...colors, [key]: outputColor }, + opacity: { ...opacity, [opacitySlot]: outputColor.a } + } +}, { colors: {}, opacity: {} }) From e070ec4b66e10c6f18acd0dbdb9dceb7eb0514b7 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 16 Jan 2020 21:34:33 +0200 Subject: [PATCH 042/147] more opacity handling --- .../style_switcher/style_switcher.js | 5 ++++- src/services/theme_data/theme_data.service.js | 21 ++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js index 16be209a..b78d8236 100644 --- a/src/components/style_switcher/style_switcher.js +++ b/src/components/style_switcher/style_switcher.js @@ -16,7 +16,8 @@ import { CURRENT_VERSION, SLOT_INHERITANCE, OPACITIES, - getLayers + getLayers, + getOpacitySlot } from '../../services/theme_data/theme_data.service.js' import ColorInput from '../color_input/color_input.vue' import RangeInput from '../range_input/range_input.vue' @@ -162,6 +163,7 @@ export default { ) if (!slotIsText) return acc const { layer, variant } = slotIsBaseText ? { layer: 'bg' } : value + const opacitySlot = getOpacitySlot(SLOT_INHERITANCE[variant || layer]) const background = variant || layer const textColors = [ key, @@ -171,6 +173,7 @@ export default { const layers = getLayers( layer, variant || layer, + opacitySlot, colorsConverted, opacity ) diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js index e76d70ed..5dbef554 100644 --- a/src/services/theme_data/theme_data.service.js +++ b/src/services/theme_data/theme_data.service.js @@ -412,14 +412,13 @@ export const getLayersArray = (layer, data = LAYERS) => { return array } -export const getLayers = (layer, variant = layer, colors, opacity) => { +export const getLayers = (layer, variant = layer, opacitySlot, colors, opacity) => { return getLayersArray(layer).map((currentLayer) => ([ currentLayer === layer ? colors[variant] : colors[currentLayer], - // TODO: Remove this hack when opacities/layers system is improved - currentLayer.startsWith('btn') - ? opacity.btn + currentLayer === layer + ? opacity[opacitySlot] || 1 : opacity[currentLayer] ])) } @@ -587,6 +586,7 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu getLayers( value.layer, value.variant || value.layer, + getOpacitySlot(SLOT_INHERITANCE[value.variant || value.layer]), colors, opacity ) @@ -622,8 +622,15 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu if (opacitySlot && outputColor.a === undefined) { outputColor.a = sourceOpacity[opacitySlot] || OPACITIES[opacitySlot].defaultValue || 1 } - return { - colors: { ...colors, [key]: outputColor }, - opacity: { ...opacity, [opacitySlot]: outputColor.a } + if (opacitySlot) { + return { + colors: { ...colors, [key]: outputColor }, + opacity: { ...opacity, [opacitySlot]: outputColor.a } + } + } else { + return { + colors: { ...colors, [key]: outputColor }, + opacity + } } }, { colors: {}, opacity: {} }) From 8536f3cc320d550340a834c8357e1c8fd4318649 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 16 Jan 2020 21:59:06 +0200 Subject: [PATCH 043/147] small fix --- src/components/style_switcher/style_switcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js index b78d8236..03cbb2a1 100644 --- a/src/components/style_switcher/style_switcher.js +++ b/src/components/style_switcher/style_switcher.js @@ -163,8 +163,8 @@ export default { ) if (!slotIsText) return acc const { layer, variant } = slotIsBaseText ? { layer: 'bg' } : value - const opacitySlot = getOpacitySlot(SLOT_INHERITANCE[variant || layer]) const background = variant || layer + const opacitySlot = getOpacitySlot(SLOT_INHERITANCE[background]) const textColors = [ key, ...(background === 'bg' ? ['cRed', 'cGreen', 'cBlue', 'cOrange'] : []) From 552d13a060fe680d1f0800e311f69be8ba25057b Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 16 Jan 2020 23:09:46 +0200 Subject: [PATCH 044/147] better fallback for transparent colors --- src/services/theme_data/theme_data.service.js | 65 +++++++++++++++++-- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js index 5dbef554..b885c56c 100644 --- a/src/services/theme_data/theme_data.service.js +++ b/src/services/theme_data/theme_data.service.js @@ -35,8 +35,8 @@ export const DEFAULT_OPACITY = { export const SLOT_INHERITANCE = { bg: { depends: [], - priority: 1, - opacity: 'bg' + opacity: 'bg', + priority: 1 }, fg: { depends: [], @@ -272,6 +272,9 @@ export const SLOT_INHERITANCE = { variant: 'btnPressed', textColor: true }, + btnPressedPanel: { + depends: ['btnPressed'] + }, btnPressedPanelText: { depends: ['btnPanelText'], layer: 'btnPanel', @@ -490,8 +493,13 @@ export const getOpacitySlot = ( inheritance = SLOT_INHERITANCE, getDeps = getDependencies ) => { - if (v.opacity === null) return - if (v.opacity) return v.opacity + const value = typeof v === 'string' + ? { + depends: v.startsWith('--') ? [v.substring(2)] : [] + } + : v + if (value.opacity === null) return + if (value.opacity) return v.opacity const findInheritedOpacity = (val) => { const depSlot = val.depends[0] if (depSlot === undefined) return @@ -505,8 +513,40 @@ export const getOpacitySlot = ( return null } } - if (v.depends) { - return findInheritedOpacity(v) + if (value.depends) { + return findInheritedOpacity(value) + } +} + +export const getLayerSlot = ( + k, + v, + inheritance = SLOT_INHERITANCE, + getDeps = getDependencies +) => { + const value = typeof v === 'string' + ? { + depends: v.startsWith('--') ? [v.substring(2)] : [] + } + : v + if (LAYERS[k]) return k + if (value.layer === null) return + if (value.layer) return v.layer + const findInheritedLayer = (val) => { + const depSlot = val.depends[0] + if (depSlot === undefined) return + const dependency = getDeps(depSlot, inheritance)[0] + if (dependency === undefined) return + if (dependency.layer || dependency === null) { + return dependency.layer + } else if (dependency.depends) { + return findInheritedLayer(dependency) + } else { + return null + } + } + if (value.depends) { + return findInheritedLayer(value) } } @@ -550,9 +590,20 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu // Color is defined in source color let targetColor = sourceColor if (targetColor === 'transparent') { + // We take only layers below current one + const layers = getLayers( + getLayerSlot(key, value), + key, + value.opacity || key, + colors, + opacity + ).slice(0, -1) targetColor = { // TODO: try to use alpha-blended background here - ...convert('#FF00FF').rgb, + ...alphaBlendLayers( + convert('#FF00FF').rgb, + layers + ), a: 0 } } else if (typeof sourceColor === 'string' && sourceColor.startsWith('--')) { From c351e5124c41be994a85c1562aed72d6f48bc273 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 16 Jan 2020 23:28:42 +0200 Subject: [PATCH 045/147] fix selectedPost/selectedMenu --- src/services/theme_data/theme_data.service.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js index b885c56c..a0448eb3 100644 --- a/src/services/theme_data/theme_data.service.js +++ b/src/services/theme_data/theme_data.service.js @@ -105,21 +105,25 @@ export const SLOT_INHERITANCE = { selectedPostFaintText: { depends: ['lightBgFaintText'], layer: 'lightBg', + variant: 'selectedPost', textColor: true }, selectedPostFaintLink: { depends: ['lightBgFaintLink'], layer: 'lightBg', + variant: 'selectedPost', textColor: 'preserve' }, selectedPostText: { depends: ['lightBgText'], layer: 'lightBg', + variant: 'selectedPost', textColor: true }, selectedPostLink: { depends: ['lightBgLink'], layer: 'lightBg', + variant: 'selectedPost', textColor: 'preserve' }, selectedPostIcon: { @@ -131,21 +135,25 @@ export const SLOT_INHERITANCE = { selectedMenuFaintText: { depends: ['lightBgFaintText'], layer: 'lightBg', + variant: 'selectedMenu', textColor: true }, selectedMenuFaintLink: { depends: ['lightBgFaintLink'], layer: 'lightBg', + variant: 'selectedMenu', textColor: 'preserve' }, selectedMenuText: { depends: ['lightBgText'], layer: 'lightBg', + variant: 'selectedMenu', textColor: true }, selectedMenuLink: { depends: ['lightBgLink'], layer: 'lightBg', + variant: 'selectedMenu', textColor: 'preserve' }, selectedMenuIcon: { From 1f5ada08c156687c6a8de22f9f8acb6c5c15824b Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 16 Jan 2020 23:29:19 +0200 Subject: [PATCH 046/147] themes update --- static/themes/breezy-dark.json | 3 ++- static/themes/breezy-light.json | 3 ++- static/themes/redmond-xx-se.json | 5 ++++- static/themes/redmond-xx.json | 5 ++++- static/themes/redmond-xxi.json | 5 ++++- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json index ce0f10ab..97e81f3d 100644 --- a/static/themes/breezy-dark.json +++ b/static/themes/breezy-dark.json @@ -114,7 +114,8 @@ "cGreen": "#27ae60", "cOrange": "#f67400", "btnPressed": "--accent", - "lightBg": "--accent,-20" + "lightBg": "--accent", + "selectedPost": "--bg,10" }, "radii": { "btn": "2", diff --git a/static/themes/breezy-light.json b/static/themes/breezy-light.json index c4e54c6d..fd307b80 100644 --- a/static/themes/breezy-light.json +++ b/static/themes/breezy-light.json @@ -117,7 +117,8 @@ "cGreen": "#27ae60", "cOrange": "#f67400", "btnPressed": "--accent", - "lightBg": "--accent,-20" + "lightBg": "--accent", + "selectedPost": "--bg,10" }, "radii": { "btn": "2", diff --git a/static/themes/redmond-xx-se.json b/static/themes/redmond-xx-se.json index 70ee89d1..13ed3b0e 100644 --- a/static/themes/redmond-xx-se.json +++ b/static/themes/redmond-xx-se.json @@ -268,6 +268,7 @@ "bg": "#c0c0c0", "text": "#000000", "link": "#0000ff", + "accent": "#000080", "fg": "#c0c0c0", "panel": "#000080", "panelFaint": "#c0c0c0", @@ -281,7 +282,9 @@ "cRed": "#FF0000", "cBlue": "#008080", "cGreen": "#008000", - "cOrange": "#808000" + "cOrange": "#808000", + "lightBg": "--accent", + "selectedPost": "--bg,-10" }, "radii": { "btn": "0", diff --git a/static/themes/redmond-xx.json b/static/themes/redmond-xx.json index 4fd6a369..7c687ae0 100644 --- a/static/themes/redmond-xx.json +++ b/static/themes/redmond-xx.json @@ -259,6 +259,7 @@ "bg": "#c0c0c0", "text": "#000000", "link": "#0000ff", + "accent": "#000080", "fg": "#c0c0c0", "panel": "#000080", "panelFaint": "#c0c0c0", @@ -272,7 +273,9 @@ "cRed": "#FF0000", "cBlue": "#008080", "cGreen": "#008000", - "cOrange": "#808000" + "cOrange": "#808000", + "lightBg": "--accent", + "selectedPost": "--bg,-10" }, "radii": { "btn": "0", diff --git a/static/themes/redmond-xxi.json b/static/themes/redmond-xxi.json index d10bf138..5371ee64 100644 --- a/static/themes/redmond-xxi.json +++ b/static/themes/redmond-xxi.json @@ -241,6 +241,7 @@ "bg": "#d6d6ce", "text": "#000000", "link": "#0000ff", + "accent": "#0a246a", "fg": "#d6d6ce", "panel": "#042967", "panelFaint": "#FFFFFF", @@ -254,7 +255,9 @@ "cRed": "#c42726", "cBlue": "#6699cc", "cGreen": "#669966", - "cOrange": "#cc6633" + "cOrange": "#cc6633", + "lightBg": "--accent", + "selectedPost": "--bg,-10" }, "radii": { "btn": "0", From 24a7a9bfd8dbdaae8b5a2e5dde5cb754a122905b Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 16 Jan 2020 23:30:13 +0200 Subject: [PATCH 047/147] lint --- src/components/style_switcher/style_switcher.vue | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue index 2fca5570..e3b899f0 100644 --- a/src/components/style_switcher/style_switcher.vue +++ b/src/components/style_switcher/style_switcher.vue @@ -215,7 +215,10 @@ :label="$t('settings.text')" :fallback="previewTheme.colors.alertErrorText" /> - + - +

{{ $t('settings.style.advanced_colors.badge') }}

@@ -244,7 +250,10 @@ :label="$t('settings.text')" :fallback="previewTheme.colors.badgeNotificationText" /> - +

{{ $t('settings.style.advanced_colors.panel_header') }}

From f77d675434ad7238e36e712ed69d01bc3233b156 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 17 Jan 2020 00:27:46 +0200 Subject: [PATCH 048/147] optimized theme loading so that it wouldn't wait until ALL themes are loaded to select one by default --- .../style_switcher/style_switcher.js | 23 ++++++- src/services/style_setter/style_setter.js | 67 +++++++++---------- 2 files changed, 53 insertions(+), 37 deletions(-) diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js index 03cbb2a1..52ece3a1 100644 --- a/src/components/style_switcher/style_switcher.js +++ b/src/components/style_switcher/style_switcher.js @@ -96,9 +96,26 @@ export default { created () { const self = this - getThemes().then((themesComplete) => { - self.availableStyles = themesComplete - }) + 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.normalizeLocalState(this.$store.getters.mergedConfig.customTheme) diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index 9237a8dc..872dd393 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -336,25 +336,23 @@ export const getThemes = () => { return window.fetch('/static/styles.json') .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) .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 @@ -363,33 +361,34 @@ export const getThemes = () => { } export 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 + return getThemes() + .then((themes) => console.log(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]) + 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') + 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 } - } + 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) - } - }) + // 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) + } + }) } From 62343f6099ca06449a6755487930ea80706e1335 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 19 Jan 2020 20:59:54 +0200 Subject: [PATCH 049/147] documentation --- src/services/theme_data/theme_data.service.js | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js index a0448eb3..9f010fdf 100644 --- a/src/services/theme_data/theme_data.service.js +++ b/src/services/theme_data/theme_data.service.js @@ -1,7 +1,42 @@ import { convert, brightness, contrastRatio } from 'chromatism' import { alphaBlend, alphaBlendLayers, getTextColor, mixrgb } from '../color_convert/color_convert.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 + /* 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 @@ -25,6 +60,9 @@ export const LAYERS = { poll: 'bg' } +/* By default opacity slots have 1 as default opacity + * this allows redefining it to something else + */ export const DEFAULT_OPACITY = { alert: 0.5, input: 0.5, @@ -32,6 +70,44 @@ export const DEFAULT_OPACITY = { 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: [], @@ -456,6 +532,16 @@ const getDependencies = (key, inheritance) => { } } +/** + * 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 + */ export const topoSort = ( inheritance = SLOT_INHERITANCE, getDeps = getDependencies @@ -496,6 +582,11 @@ export const topoSort = ( return output } +/** + * 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 = ( v, inheritance = SLOT_INHERITANCE, @@ -526,6 +617,13 @@ export const getOpacitySlot = ( } } +/** + * 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, v, @@ -558,12 +656,19 @@ export const getLayerSlot = ( } } +/** + * topologically sorted SLOT_INHERITANCE + additional priority sort + */ 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 }), {}) ) +/** + * Dictionary where keys are color slots and values are opacity associated + * with them + */ export const SLOTS_OPACITIES_DICT = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) => { const opacity = getOpacitySlot(v, SLOT_INHERITANCE, getDependencies) if (opacity) { @@ -573,6 +678,10 @@ export const SLOTS_OPACITIES_DICT = Object.entries(SLOT_INHERITANCE).reduce((acc } }, {}) +/** + * 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(v, SLOT_INHERITANCE, getDependencies) if (opacity) { @@ -588,6 +697,10 @@ export const OPACITIES = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) = } }, {}) +/** + * THE function you want to use. Takes provided colors and opacities, mod + * value and uses inheritance data to figure out color needed for the slot. + */ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.reduce(({ colors, opacity }, key) => { const value = SLOT_INHERITANCE[key] const isObject = typeof value === 'object' From 7d7ccf729855283ad0f5b9d6943b8d86ed1f180d Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 19 Jan 2020 22:44:35 +0200 Subject: [PATCH 050/147] fix some contrast ratios not displaying --- .../contrast_ratio/contrast_ratio.vue | 12 +++++++++--- .../style_switcher/style_switcher.vue | 18 +++++++++--------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/components/contrast_ratio/contrast_ratio.vue b/src/components/contrast_ratio/contrast_ratio.vue index 15a450a2..5e79ef41 100644 --- a/src/components/contrast_ratio/contrast_ratio.vue +++ b/src/components/contrast_ratio/contrast_ratio.vue @@ -37,9 +37,15 @@ @@ -23,6 +65,31 @@ flex-wrap: wrap; } +.reacted-users { + padding: 0.5em; +} + +.reacted-user { + padding: 0.25em; + display: flex; + flex-direction: row; + + .reacted-user-names { + display: flex; + flex-direction: column; + margin-left: 0.5em; + + img { + width: 1em; + height: 1em; + } + } + + .reacted-user-screen-name { + font-size: 9px; + } +} + .emoji-reaction { padding: 0 0.5em; margin-right: 0.5em; @@ -38,6 +105,26 @@ &:focus { outline: none; } + + &.not-clickable { + cursor: default; + &:hover { + box-shadow: $fallback--buttonShadow; + box-shadow: var(--buttonShadow); + } + } +} + +.emoji-reaction-expand { + padding: 0 0.5em; + margin-right: 0.5em; + margin-top: 0.5em; + display: flex; + align-items: center; + justify-content: center; + &:hover { + text-decoration: underline; + } } .picked-reaction { diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index 16124e50..411c0271 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -78,6 +78,13 @@ {{ $t('notifications.migrated_to') }} + + + + {{ notification.emoji }} + + +
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() } }, diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue index c925dd71..fb43ebaf 100644 --- a/src/components/react_button/react_button.vue +++ b/src/components/react_button/react_button.vue @@ -54,6 +54,10 @@ .reaction-picker-filter { padding: 0.5em; + display: flex; + input { + flex: 1; + } } .reaction-picker-divider { diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue index cef492f3..60cb8a87 100644 --- a/src/components/settings/settings.vue +++ b/src/components/settings/settings.vue @@ -92,6 +92,11 @@ {{ $t('settings.reply_link_preview') }} +
  • + + {{ $t('settings.emoji_reactions_on_timeline') }} + +
  • @@ -328,6 +333,11 @@ {{ $t('settings.notification_visibility_moves') }} +
  • + + {{ $t('settings.notification_visibility_emoji_reactions') }} + +
  • diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 0a82dcbe..83f07dac 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -369,6 +369,7 @@ diff --git a/src/i18n/en.json b/src/i18n/en.json index 74e71fc8..d0d654d3 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -126,7 +126,8 @@ "read": "Read!", "repeated_you": "repeated your status", "no_more_notifications": "No more notifications", - "migrated_to": "migrated to" + "migrated_to": "migrated to", + "reacted_with": "reacted with {0}" }, "polls": { "add_poll": "Add Poll", @@ -283,6 +284,7 @@ "domain_mutes": "Domains", "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.", "pad_emoji": "Pad emoji with spaces when adding from picker", + "emoji_reactions_on_timeline": "Show emoji reactions on timeline", "export_theme": "Save preset", "filtering": "Filtering", "filtering_explanation": "All statuses containing these words will be muted, one per line", @@ -331,6 +333,7 @@ "notification_visibility_mentions": "Mentions", "notification_visibility_repeats": "Repeats", "notification_visibility_moves": "User Migrates", + "notification_visibility_emoji_reactions": "Reactions", "no_rich_text_description": "Strip rich text formatting from all posts", "no_blocks": "No blocks", "no_mutes": "No mutes", diff --git a/src/i18n/fi.json b/src/i18n/fi.json index e7ed5408..ac8b2ac9 100644 --- a/src/i18n/fi.json +++ b/src/i18n/fi.json @@ -53,7 +53,8 @@ "notifications": "Ilmoitukset", "read": "Lue!", "repeated_you": "toisti viestisi", - "no_more_notifications": "Ei enempää ilmoituksia" + "no_more_notifications": "Ei enempää ilmoituksia", + "reacted_with": "lisäsi reaktion {0}" }, "polls": { "add_poll": "Lisää äänestys", @@ -140,6 +141,7 @@ "delete_account_description": "Poista tilisi ja viestisi pysyvästi.", "delete_account_error": "Virhe poistaessa tiliäsi. Jos virhe jatkuu, ota yhteyttä palvelimesi ylläpitoon.", "delete_account_instructions": "Syötä salasanasi vahvistaaksesi tilin poiston.", + "emoji_reactions_on_timeline": "Näytä emojireaktiot aikajanalla", "export_theme": "Tallenna teema", "filtering": "Suodatus", "filtering_explanation": "Kaikki viestit, jotka sisältävät näitä sanoja, suodatetaan. Yksi sana per rivi.", @@ -183,6 +185,7 @@ "notification_visibility_likes": "Tykkäykset", "notification_visibility_mentions": "Maininnat", "notification_visibility_repeats": "Toistot", + "notification_visibility_emoji_reactions": "Reaktiot", "no_rich_text_description": "Älä näytä tekstin muotoilua.", "hide_network_description": "Älä näytä seurauksiani tai seuraajiani", "nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse", diff --git a/src/modules/config.js b/src/modules/config.js index de9f041b..8381fa53 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -20,6 +20,7 @@ export const defaultState = { autoLoad: true, streaming: false, hoverPreview: true, + emojiReactionsOnTimeline: true, autohideFloatingPostButton: false, pauseOnUnfocused: true, stopGifs: false, @@ -29,7 +30,8 @@ export const defaultState = { mentions: true, likes: true, repeats: true, - moves: true + moves: true, + emojiReactions: false }, webPushNotifications: false, muteWords: [], diff --git a/src/modules/statuses.js b/src/modules/statuses.js index ea0c1749..25b62ac7 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -81,7 +81,8 @@ const visibleNotificationTypes = (rootState) => { rootState.config.notificationVisibility.mentions && 'mention', rootState.config.notificationVisibility.repeats && 'repeat', rootState.config.notificationVisibility.follows && 'follow', - rootState.config.notificationVisibility.moves && 'move' + rootState.config.notificationVisibility.moves && 'move', + rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reactions' ].filter(_ => _) } @@ -325,6 +326,10 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item } + if (notification.type === 'pleroma:emoji_reaction') { + dispatch('fetchEmojiReactionsBy', notification.status.id) + } + // Only add a new notification if we don't have one for the same action if (!state.notifications.idStore.hasOwnProperty(notification.id)) { state.notifications.maxId = notification.id > state.notifications.maxId @@ -358,7 +363,9 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot break } - if (i18nString) { + if (notification.type === 'pleroma:emoji_reaction') { + notifObj.body = rootGetters.i18n.t('notifications.reacted_with', [notification.emoji]) + } else if (i18nString) { notifObj.body = rootGetters.i18n.t('notifications.' + i18nString) } else { notifObj.body = notification.status.text @@ -371,10 +378,10 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot } if (!notification.seen && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.type)) { - let notification = new window.Notification(title, notifObj) + let desktopNotification = new window.Notification(title, notifObj) // Chrome is known for not closing notifications automatically // according to MDN, anyway. - setTimeout(notification.close.bind(notification), 5000) + setTimeout(desktopNotification.close.bind(desktopNotification), 5000) } } } else if (notification.seen) { @@ -537,12 +544,13 @@ export const mutations = { }, addOwnReaction (state, { id, emoji, currentUser }) { const status = state.allStatusesObject[id] - const reactionIndex = findIndex(status.emoji_reactions, { emoji }) - const reaction = status.emoji_reactions[reactionIndex] || { emoji, count: 0, accounts: [] } + const reactionIndex = findIndex(status.emoji_reactions, { name: emoji }) + const reaction = status.emoji_reactions[reactionIndex] || { name: emoji, count: 0, accounts: [] } const newReaction = { ...reaction, count: reaction.count + 1, + me: true, accounts: [ ...reaction.accounts, currentUser @@ -558,21 +566,23 @@ export const mutations = { }, removeOwnReaction (state, { id, emoji, currentUser }) { const status = state.allStatusesObject[id] - const reactionIndex = findIndex(status.emoji_reactions, { emoji }) + const reactionIndex = findIndex(status.emoji_reactions, { name: emoji }) if (reactionIndex < 0) return const reaction = status.emoji_reactions[reactionIndex] + const accounts = reaction.accounts || [] const newReaction = { ...reaction, count: reaction.count - 1, - accounts: reaction.accounts.filter(acc => acc.id === currentUser.id) + me: false, + accounts: accounts.filter(acc => acc.id !== currentUser.id) } if (newReaction.count > 0) { set(status.emoji_reactions, reactionIndex, newReaction) } else { - set(status, 'emoji_reactions', status.emoji_reactions.filter(r => r.emoji !== emoji)) + set(status, 'emoji_reactions', status.emoji_reactions.filter(r => r.name !== emoji)) } }, updateStatusWithPoll (state, { id, poll }) { @@ -681,18 +691,22 @@ const statuses = { }, reactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) { const currentUser = rootState.users.currentUser + if (!currentUser) return + commit('addOwnReaction', { id, emoji, currentUser }) rootState.api.backendInteractor.reactWithEmoji({ id, emoji }).then( - status => { + ok => { dispatch('fetchEmojiReactionsBy', id) } ) }, unreactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) { const currentUser = rootState.users.currentUser + if (!currentUser) return + commit('removeOwnReaction', { id, emoji, currentUser }) rootState.api.backendInteractor.unreactWithEmoji({ id, emoji }).then( - status => { + ok => { dispatch('fetchEmojiReactionsBy', id) } ) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index b794fd58..20eaa9a0 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -74,9 +74,9 @@ 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 PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/emoji_reactions_by` -const PLEROMA_EMOJI_REACT_URL = id => `/api/v1/pleroma/statuses/${id}/react_with_emoji` -const PLEROMA_EMOJI_UNREACT_URL = id => `/api/v1/pleroma/statuses/${id}/unreact_with_emoji` +const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/reactions` +const PLEROMA_EMOJI_REACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` +const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` const oldfetch = window.fetch @@ -888,25 +888,27 @@ const fetchRebloggedByUsers = ({ id }) => { return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser)) } -const fetchEmojiReactions = ({ id }) => { - return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id) }) +const fetchEmojiReactions = ({ id, credentials }) => { + return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id), credentials }) + .then((reactions) => reactions.map(r => { + r.accounts = r.accounts.map(parseUser) + return r + })) } const reactWithEmoji = ({ id, emoji, credentials }) => { return promisedRequest({ - url: PLEROMA_EMOJI_REACT_URL(id), - method: 'POST', - credentials, - payload: { emoji } + url: PLEROMA_EMOJI_REACT_URL(id, emoji), + method: 'PUT', + credentials }).then(parseStatus) } const unreactWithEmoji = ({ id, emoji, credentials }) => { return promisedRequest({ - url: PLEROMA_EMOJI_UNREACT_URL(id), - method: 'POST', - credentials, - payload: { emoji } + url: PLEROMA_EMOJI_UNREACT_URL(id, emoji), + method: 'DELETE', + credentials }).then(parseStatus) } diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 0a8abbbd..84169a7b 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -354,6 +354,7 @@ export const parseNotification = (data) => { ? null : parseUser(data.target) output.from_profile = parseUser(data.account) + output.emoji = data.emoji } else { const parsedNotice = parseStatus(data.notice) output.type = data.ntype diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index 860620fc..b17bd7bf 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -7,7 +7,8 @@ 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.moves && 'move' + store.state.config.notificationVisibility.moves && 'move', + store.state.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction' ].filter(_ => _)) const sortById = (a, b) => { diff --git a/test/unit/specs/modules/statuses.spec.js b/test/unit/specs/modules/statuses.spec.js index e53aa388..fe42e85b 100644 --- a/test/unit/specs/modules/statuses.spec.js +++ b/test/unit/specs/modules/statuses.spec.js @@ -245,11 +245,12 @@ describe('Statuses module', () => { it('increments count in existing reaction', () => { const state = defaultState() const status = makeMockStatus({ id: '1' }) - status.emoji_reactions = [ { emoji: '😂', count: 1, accounts: [] } ] + status.emoji_reactions = [ { name: '😂', count: 1, accounts: [] } ] mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) mutations.addOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } }) expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(2) + expect(state.allStatusesObject['1'].emoji_reactions[0].me).to.eql(true) expect(state.allStatusesObject['1'].emoji_reactions[0].accounts[0].id).to.eql('me') }) @@ -261,27 +262,29 @@ describe('Statuses module', () => { mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) mutations.addOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } }) expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(1) + expect(state.allStatusesObject['1'].emoji_reactions[0].me).to.eql(true) expect(state.allStatusesObject['1'].emoji_reactions[0].accounts[0].id).to.eql('me') }) it('decreases count in existing reaction', () => { const state = defaultState() const status = makeMockStatus({ id: '1' }) - status.emoji_reactions = [ { emoji: '😂', count: 2, accounts: [{ id: 'me' }] } ] + status.emoji_reactions = [ { name: '😂', count: 2, accounts: [{ id: 'me' }] } ] mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) - mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: {} }) + mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } }) expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(1) + expect(state.allStatusesObject['1'].emoji_reactions[0].me).to.eql(false) expect(state.allStatusesObject['1'].emoji_reactions[0].accounts).to.eql([]) }) it('removes a reaction', () => { const state = defaultState() const status = makeMockStatus({ id: '1' }) - status.emoji_reactions = [{ emoji: '😂', count: 1, accounts: [{ id: 'me' }] }] + status.emoji_reactions = [{ name: '😂', count: 1, accounts: [{ id: 'me' }] }] mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) - mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: {} }) + mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } }) expect(state.allStatusesObject['1'].emoji_reactions.length).to.eql(0) }) }) From 60446c56a5c8d4b4d669fbd4803e77ce686daac9 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 12 Feb 2020 00:45:37 +0200 Subject: [PATCH 115/147] fix v2 breezy themes having messed up pressed buttons. updated v2.1 breezy themes to have derived colors instead of fixed ones. --- .../style_switcher/style_switcher.js | 7 +++++-- src/services/color_convert/color_convert.js | 2 -- src/services/style_setter/style_setter.js | 9 +++++---- static/themes/breezy-dark.json | 14 +++++++------- static/themes/breezy-light.json | 19 ++++++++----------- 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js index 17ae9f30..807f9efc 100644 --- a/src/components/style_switcher/style_switcher.js +++ b/src/components/style_switcher/style_switcher.js @@ -62,6 +62,7 @@ export default { selected: this.$store.getters.mergedConfig.theme, themeWarning: undefined, tempImportFile: undefined, + engineVersion: 0, previewShadows: {}, previewColors: {}, @@ -510,7 +511,7 @@ export default { colors: this.currentColors }) this.previewShadows = generateShadows( - { shadows: this.shadowsLocal }, + { shadows: this.shadowsLocal, opacity: this.previewTheme.opacity, themeEngineVersion: this.engineVersion }, this.previewColors.theme.colors, this.previewColors.mod ) @@ -607,6 +608,8 @@ export default { } } + this.engineVersion = version + // Stuff that differs between V1 and V2 if (version === 1) { this.fgColorLocal = rgb2hex(colors.btn) @@ -653,7 +656,7 @@ export default { if (!this.keepShadows) { this.clearShadows() if (version === 2) { - this.shadowsLocal = shadows2to3(shadows) + this.shadowsLocal = shadows2to3(shadows, this.previewTheme.opacity) } else { this.shadowsLocal = shadows } diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js index 3b6fdcc7..ec104269 100644 --- a/src/services/color_convert/color_convert.js +++ b/src/services/color_convert/color_convert.js @@ -214,8 +214,6 @@ export const getCssColor = (input, a) => { } else if (typeof input === 'string') { if (input.startsWith('#')) { rgb = hex2rgb(input) - } else if (input.startsWith('--')) { - return `var(${input})` } else { return input } diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index 48f51c59..c8610507 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -1,6 +1,6 @@ import { convert } from 'chromatism' import { rgb2hex, hex2rgb, rgba2css, getCssColor, relativeLuminance } from '../color_convert/color_convert.js' -import { getColors, computeDynamicColor } from '../theme_data/theme_data.service.js' +import { getColors, computeDynamicColor, getOpacitySlot } from '../theme_data/theme_data.service.js' export const applyTheme = (input) => { const { rules } = generatePreset(input) @@ -242,7 +242,7 @@ export const generateShadows = (input, colors) => { input: 'input' } const inputShadows = input.shadows && !input.themeEngineVersion - ? shadows2to3(input.shadows) + ? shadows2to3(input.shadows, input.opacity) : input.shadows || {} const shadows = Object.entries({ ...DEFAULT_SHADOWS, @@ -368,14 +368,15 @@ export const colors2to3 = (colors) => { * * Back in v2 shadows allowed you to use dynamic colors however those used pure CSS3 variables */ -export const shadows2to3 = (shadows) => { +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) ? 1 : def.alpha + alpha: isDynamic(def) ? getOpacity(def) || 1 : def.alpha } ], []) return { ...shadowsAcc, [slotName]: newShadow } diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json index 236b94ad..76b962c5 100644 --- a/static/themes/breezy-dark.json +++ b/static/themes/breezy-dark.json @@ -21,7 +21,7 @@ "y": "0", "blur": "0", "spread": "1", - "color": "#ffffff", + "color": "--btn,900", "alpha": "0.15", "inset": true }, @@ -42,7 +42,7 @@ "blur": "40", "spread": "-40", "inset": true, - "color": "#ffffff", + "color": "--panel,900", "alpha": "0.1" } ], @@ -72,7 +72,7 @@ "y": "0", "blur": 0, "spread": "1", - "color": "#ffffff", + "color": "--btn,900", "alpha": 0.2, "inset": true }, @@ -92,7 +92,7 @@ "y": "0", "blur": 0, "spread": "1", - "color": "#FFFFFF", + "color": "--input,900", "alpha": "0.2", "inset": true } @@ -105,9 +105,9 @@ "link": "#3daee9", "fg": "#31363b", "panel": "transparent", - "input": "#232629", - "topBarLink": "#eff0f1", - "btn": "#31363b", + "input": "--bg,-6.47", + "topBarLink": "--topBarText", + "btn": "--bg", "border": "#4c545b", "cRed": "#da4453", "cBlue": "#3daee9", diff --git a/static/themes/breezy-light.json b/static/themes/breezy-light.json index d3f74cec..0968fff0 100644 --- a/static/themes/breezy-light.json +++ b/static/themes/breezy-light.json @@ -21,7 +21,7 @@ "y": "0", "blur": "0", "spread": "1", - "color": "#000000", + "color": "--btn,900", "alpha": "0.3", "inset": true }, @@ -42,7 +42,7 @@ "blur": "40", "spread": "-40", "inset": true, - "color": "#ffffff", + "color": "--panel,900", "alpha": "0.1" } ], @@ -72,7 +72,7 @@ "y": "0", "blur": 0, "spread": "1", - "color": "#ffffff", + "color": "--btn,900", "alpha": 0.2, "inset": true }, @@ -92,7 +92,7 @@ "y": "0", "blur": 0, "spread": "1", - "color": "#000000", + "color": "--input,900", "alpha": "0.2", "inset": true } @@ -104,14 +104,11 @@ "colors": { "bg": "#eff0f1", "text": "#232627", - "fg": "#bcc2c7", + "fg": "#475057", "accent": "#2980b9", - "panel": "#475057", - "panelText": "#fcfcfc", - "input": "#fcfcfc", - "topBar": "#475057", - "topBarLink": "#eff0f1", - "btn": "#eff0f1", + "input": "--bg,-6.47", + "topBarLink": "--topBarText", + "btn": "--bg", "cRed": "#da4453", "cBlue": "#2980b9", "cGreen": "#27ae60", From 2274976c09449838f3f6496c4d908a604d8d91d9 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 12 Feb 2020 01:10:00 +0200 Subject: [PATCH 116/147] post-merge fix --- src/components/emoji_reactions/emoji_reactions.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue index 290b25b3..77e08297 100644 --- a/src/components/emoji_reactions/emoji_reactions.vue +++ b/src/components/emoji_reactions/emoji_reactions.vue @@ -35,7 +35,7 @@