From 1d3b1ac934e5dacb05d227ddc1ab0cbd8e16e169 Mon Sep 17 00:00:00 2001 From: shpuld Date: Sat, 2 Mar 2019 16:57:32 +0200 Subject: [PATCH 0001/1077] start working on one tap notifications --- src/App.js | 4 ++++ src/App.scss | 24 +++++++++++++++++++++++- src/App.vue | 8 +++++++- static/font/config.json | 12 ++++++++++++ static/font/css/fontello-codes.css | 2 ++ static/font/css/fontello-embedded.css | 14 ++++++++------ static/font/css/fontello-ie7-codes.css | 2 ++ static/font/css/fontello-ie7.css | 2 ++ static/font/css/fontello.css | 16 +++++++++------- static/font/demo.html | 20 +++++++++++--------- static/font/font/fontello.eot | Bin 17472 -> 18108 bytes static/font/font/fontello.svg | 4 ++++ static/font/font/fontello.ttf | Bin 17304 -> 17940 bytes static/font/font/fontello.woff | Bin 10572 -> 10868 bytes static/font/font/fontello.woff2 | Bin 8932 -> 9212 bytes 15 files changed, 84 insertions(+), 24 deletions(-) diff --git a/src/App.js b/src/App.js index 214e0f48..5e5b6eea 100644 --- a/src/App.js +++ b/src/App.js @@ -26,6 +26,7 @@ export default { }, data: () => ({ mobileActivePanel: 'timeline', + notificationsOpen: false, finderHidden: true, supportsMask: window.CSS && window.CSS.supports && ( window.CSS.supports('mask-size', 'contain') || @@ -101,6 +102,9 @@ export default { }, toggleMobileSidebar () { this.$refs.sideDrawer.toggleDrawer() + }, + toggleMobileNotifications () { + this.notificationsOpen = !this.notificationsOpen } } } diff --git a/src/App.scss b/src/App.scss index 7c6970c1..3edc41a0 100644 --- a/src/App.scss +++ b/src/App.scss @@ -661,6 +661,28 @@ nav { border-radius: var(--inputRadius, $fallback--inputRadius); } +.mobile-notifications { + position: fixed; + width: 100vw; + height: 100vh; + top: 50px; + left: 0; + z-index: 1001; + overflow-x: hidden; + overflow-y: scroll; + transition-property: transform; + transition-duration: 0.35s; + transform: translate(0); + + .notifications { + padding: 0; + } + + &.closed { + transform: translate(100%); + } +} + @keyframes shakeError { 0% { transform: translateX(0); @@ -723,7 +745,7 @@ nav { .login-hint { text-align: center; - + @media all and (min-width: 801px) { display: none; } diff --git a/src/App.vue b/src/App.vue index acbbeb75..aad84804 100644 --- a/src/App.vue +++ b/src/App.vue @@ -10,7 +10,6 @@
-
{{sitename}}
@@ -18,11 +17,18 @@ + + +
+
+
+ +
diff --git a/src/components/scope_selector/scope_selector.js b/src/components/scope_selector/scope_selector.js new file mode 100644 index 00000000..578f1ba5 --- /dev/null +++ b/src/components/scope_selector/scope_selector.js @@ -0,0 +1,55 @@ +const ScopeSelector = { + props: [ + 'showAll', + 'userEnabled', + 'userDefault', + 'originalScope', + 'initialScope', + 'onScopeChange' + ], + data () { + return { + currentScope: this.initialScope + } + }, + computed: { + showNothing () { + return !this.showPublic && !this.showUnlisted && !this.showPrivate && !this.showDirect + }, + showPublic () { + return this.originalScope !== 'direct' && this.shouldShow('public') + }, + showUnlisted () { + return this.originalScope !== 'direct' && this.shouldShow('unlisted') + }, + showPrivate () { + return this.originalScope !== 'direct' && this.shouldShow('private') + }, + showDirect () { + return this.shouldShow('direct') + }, + css () { + return { + public: {selected: this.currentScope === 'public'}, + unlisted: {selected: this.currentScope === 'unlisted'}, + private: {selected: this.currentScope === 'private'}, + direct: {selected: this.currentScope === 'direct'} + } + } + }, + methods: { + shouldShow (scope) { + return this.showAll || + this.currentScope === scope || + this.originalScope === scope || + this.userDefault === scope || + this.userEnabled.includes(scope) + }, + changeVis (scope) { + this.currentScope = scope + this.onScopeChange && this.onScopeChange(scope) + } + } +} + +export default ScopeSelector diff --git a/src/components/scope_selector/scope_selector.vue b/src/components/scope_selector/scope_selector.vue new file mode 100644 index 00000000..33ea488f --- /dev/null +++ b/src/components/scope_selector/scope_selector.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js index 6e2dff7b..104be1a8 100644 --- a/src/components/settings/settings.js +++ b/src/components/settings/settings.js @@ -60,13 +60,18 @@ const settings = { alwaysShowSubjectInputLocal: typeof user.alwaysShowSubjectInput === 'undefined' ? instance.alwaysShowSubjectInput : user.alwaysShowSubjectInput, - alwaysShowSubjectInputDefault: instance.alwaysShowSubjectInput, + alwaysShowSubjectInputDefault: this.$t('settings.values.' + instance.alwaysShowSubjectInput), scopeCopyLocal: typeof user.scopeCopy === 'undefined' ? instance.scopeCopy : user.scopeCopy, scopeCopyDefault: this.$t('settings.values.' + instance.scopeCopy), + minimalScopesModeLocal: typeof user.minimalScopesMode === 'undefined' + ? instance.minimalScopesMode + : user.minimalScopesMode, + minimalScopesModeDefault: this.$t('settings.values.' + instance.minimalScopesMode), + stopGifs: user.stopGifs, webPushNotificationsLocal: user.webPushNotifications, loopVideoSilentOnlyLocal: user.loopVideosSilentOnly, @@ -175,6 +180,9 @@ const settings = { postContentTypeLocal (value) { this.$store.dispatch('setOption', { name: 'postContentType', value }) }, + minimalScopesModeLocal (value) { + this.$store.dispatch('setOption', { name: 'minimalScopesMode', value }) + }, stopGifs (value) { this.$store.dispatch('setOption', { name: 'stopGifs', value }) }, diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue index 5041b3a3..d9b72ee0 100644 --- a/src/components/settings/settings.vue +++ b/src/components/settings/settings.vue @@ -122,6 +122,12 @@
+
  • + + +
  • diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js index d6972737..1911ab23 100644 --- a/src/components/user_settings/user_settings.js +++ b/src/components/user_settings/user_settings.js @@ -4,6 +4,7 @@ import get from 'lodash/get' 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' @@ -66,6 +67,7 @@ const UserSettings = { }, components: { StyleSwitcher, + ScopeSelector, TabSwitcher, ImageCropper, BlockList, @@ -78,8 +80,8 @@ const UserSettings = { pleromaBackend () { return this.$store.state.instance.pleromaBackend }, - scopeOptionsEnabled () { - return this.$store.state.instance.scopeOptionsEnabled + scopeOptionsMinimal () { + return this.$store.state.instance.scopeOptionsMinimal }, vis () { return { diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue index a1123638..7bd391ba 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -29,13 +29,13 @@

    -
    +
    - - - - +

    diff --git a/src/i18n/en.json b/src/i18n/en.json index c5a4a90d..fb8fcfcc 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -211,6 +211,7 @@ "saving_ok": "Settings saved", "security_tab": "Security", "scope_copy": "Copy scope when replying (DMs are always copied)", + "minimal_scopes_mode": "Minimize post scope selection options", "set_new_avatar": "Set new avatar", "set_new_profile_background": "Set new profile background", "set_new_profile_banner": "Set new profile banner", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 6799cc96..89aa43f4 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -111,6 +111,8 @@ "import_theme": "Загрузить Тему", "inputRadius": "Поля ввода", "checkboxRadius": "Чекбоксы", + "instance_default": "(по умолчанию: {value})", + "instance_default_simple": "(по умолчанию)", "interface": "Интерфейс", "interfaceLanguage": "Язык интерфейса", "limited_availability": "Не доступно в вашем браузере", @@ -149,7 +151,11 @@ "reply_visibility_all": "Показывать все ответы", "reply_visibility_following": "Показывать только ответы мне и тех на кого я подписан", "reply_visibility_self": "Показывать только ответы мне", + "saving_err": "Не удалось сохранить настройки", + "saving_ok": "Сохранено", "security_tab": "Безопасность", + "scope_copy": "Копировать видимость поста при ответе (всегда включено для Личных Сообщений)", + "minimal_scopes_mode": "Минимизировать набор опций видимости поста", "set_new_avatar": "Загрузить новый аватар", "set_new_profile_background": "Загрузить новый фон профиля", "set_new_profile_banner": "Загрузить новый баннер профиля", @@ -164,6 +170,10 @@ "theme_help_v2_2": "Под некоторыми полями ввода это идикаторы контрастности, наведите на них мышью чтобы узнать больше. Приспользовании прозрачности контраст расчитывается для наихудшего варианта.", "tooltipRadius": "Всплывающие подсказки/уведомления", "user_settings": "Настройки пользователя", + "values": { + "false": "нет", + "true": "да" + }, "style": { "switcher": { "keep_color": "Оставить цвета", diff --git a/src/modules/config.js b/src/modules/config.js index 1c30c203..6d8aad35 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -32,7 +32,8 @@ const defaultState = { scopeCopy: undefined, // instance default subjectLineBehavior: undefined, // instance default alwaysShowSubjectInput: undefined, // instance default - postContentType: undefined // instance default + postContentType: undefined, // instance default + minimalScopesMode: undefined // instance default } const config = { diff --git a/src/modules/instance.js b/src/modules/instance.js index c31d02b9..7b67890f 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -15,7 +15,6 @@ const defaultState = { redirectRootNoLogin: '/main/all', redirectRootLogin: '/main/friends', showInstanceSpecificPanel: false, - scopeOptionsEnabled: true, formattingOptionsEnabled: false, alwaysShowSubjectInput: true, collapseMessageWithSubject: false, @@ -31,6 +30,7 @@ const defaultState = { vapidPublicKey: undefined, noAttachmentLinks: false, showFeaturesPanel: true, + minimalScopesMode: false, // Nasty stuff pleromaBackend: true, diff --git a/static/config.json b/static/config.json index 533a5b08..b436daae 100644 --- a/static/config.json +++ b/static/config.json @@ -21,5 +21,6 @@ "webPushNotifications": false, "noAttachmentLinks": false, "nsfwCensorImage": "", - "showFeaturesPanel": true + "showFeaturesPanel": true, + "minimalScopesMode": false } From c7e180080afd0e255e439030df800f79d33ff5de Mon Sep 17 00:00:00 2001 From: shpuld Date: Sun, 3 Mar 2019 16:33:40 +0200 Subject: [PATCH 0003/1077] more work with notifications drawer --- src/App.js | 14 +++++++++++++- src/App.scss | 2 +- src/App.vue | 10 ++++++---- src/boot/after_store.js | 3 +++ src/modules/interface.js | 10 +++++++++- 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/App.js b/src/App.js index 5e5b6eea..b6234a08 100644 --- a/src/App.js +++ b/src/App.js @@ -39,6 +39,10 @@ export default { created () { // Load the locale from the storage this.$i18n.locale = this.$store.state.config.interfaceLanguage + window.addEventListener('resize', this.updateMobileState) + }, + destroyed () { + window.removeEventListener('resize', this.updateMobileState) }, computed: { currentUser () { return this.$store.state.users.currentUser }, @@ -87,7 +91,8 @@ export default { unseenNotificationsCount () { return this.unseenNotifications.length }, - showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel } + showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }, + isMobileLayout () { return this.$store.state.interface.mobileLayout } }, methods: { scrollToTop () { @@ -105,6 +110,13 @@ export default { }, toggleMobileNotifications () { this.notificationsOpen = !this.notificationsOpen + }, + updateMobileState () { + const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth + const changed = width <= 800 !== this.isMobileLayout + if (changed) { + this.$store.dispatch('setMobileLayout', width <= 800) + } } } } diff --git a/src/App.scss b/src/App.scss index 3edc41a0..775bc1c8 100644 --- a/src/App.scss +++ b/src/App.scss @@ -667,7 +667,7 @@ nav { height: 100vh; top: 50px; left: 0; - z-index: 1001; + z-index: 9; overflow-x: hidden; overflow-y: scroll; transition-property: transform; diff --git a/src/App.vue b/src/App.vue index aad84804..d83d5dbe 100644 --- a/src/App.vue +++ b/src/App.vue @@ -25,11 +25,13 @@

    - -
    - +
    + +
    + +
    - diff --git a/src/components/status/status.js b/src/components/status/status.js index 9e18fe15..20ca86a6 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -25,11 +25,11 @@ const Status = { 'replies', 'isPreview', 'noHeading', - 'inlineExpanded' + 'inlineExpanded', + 'replying' ], data () { return { - replying: false, expanded: false, unmuted: false, userExpanded: false, @@ -307,7 +307,7 @@ const Status = { } }, toggleReplying () { - this.replying = !this.replying + this.$emit('toggleReplying') }, gotoOriginal (id) { // only handled by conversation, not status_or_conversation diff --git a/src/components/status_or_conversation/status_or_conversation.js b/src/components/status_or_conversation/status_or_conversation.js index 441552ca..749f7665 100644 --- a/src/components/status_or_conversation/status_or_conversation.js +++ b/src/components/status_or_conversation/status_or_conversation.js @@ -5,7 +5,8 @@ const statusOrConversation = { props: ['statusoid'], data () { return { - expanded: false + expanded: false, + replying: false } }, components: { @@ -15,6 +16,9 @@ const statusOrConversation = { methods: { toggleExpanded () { this.expanded = !this.expanded + }, + toggleReplying () { + this.replying = !this.replying } } } diff --git a/src/components/status_or_conversation/status_or_conversation.vue b/src/components/status_or_conversation/status_or_conversation.vue index 9647d5eb..43a60c3a 100644 --- a/src/components/status_or_conversation/status_or_conversation.vue +++ b/src/components/status_or_conversation/status_or_conversation.vue @@ -1,7 +1,23 @@ From 63d7c7bd80cf8028cdefee99c1cb75614385f96b Mon Sep 17 00:00:00 2001 From: dave Date: Mon, 11 Mar 2019 16:24:37 -0400 Subject: [PATCH 0025/1077] #433: persistency of status form --- src/components/conversation/conversation.js | 36 +++++++++++++------ src/components/conversation/conversation.vue | 31 +++++++++++----- src/components/status/status.js | 7 ++-- .../status_or_conversation.js | 26 -------------- .../status_or_conversation.vue | 30 ---------------- src/components/timeline/timeline.js | 4 +-- src/components/timeline/timeline.vue | 8 ++++- 7 files changed, 60 insertions(+), 82 deletions(-) delete mode 100644 src/components/status_or_conversation/status_or_conversation.js delete mode 100644 src/components/status_or_conversation/status_or_conversation.vue diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 95e484cd..4cae0bdb 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -1,4 +1,4 @@ -import { reduce, filter } from 'lodash' +import { reduce, filter, findIndex } from 'lodash' import Status from '../status/status.vue' const sortById = (a, b) => { @@ -25,13 +25,13 @@ const sortAndFilterConversation = (conversation) => { const conversation = { data () { return { - highlight: null + highlight: null, + expanded: false } }, props: [ 'statusoid', - 'collapsable', - 'replying' + 'collapsable' ], computed: { status () { @@ -49,9 +49,18 @@ const conversation = { return [] } + if (!this.expanded) { + return [this.status] + } + const conversationId = this.status.statusnet_conversation_id const statuses = this.$store.state.statuses.allStatuses const conversation = filter(statuses, { statusnet_conversation_id: conversationId }) + + const statusIndex = findIndex(conversation, { id: this.statusId }) + if (statusIndex !== -1) { + conversation[statusIndex] = this.status + } return sortAndFilterConversation(conversation) }, replies () { @@ -75,11 +84,13 @@ const conversation = { components: { Status }, - created () { - this.fetchConversation() - }, watch: { - '$route': 'fetchConversation' + '$route': 'fetchConversation', + expanded (value) { + if (value) { + this.fetchConversation() + } + } }, methods: { fetchConversation () { @@ -99,13 +110,16 @@ const conversation = { return this.replies[id] || [] }, focused (id) { - return id === this.statusId + return this.expanded && id === this.statusId }, setHighlight (id) { this.highlight = id }, - toggleReplying () { - this.$emit('toggleReplying') + getHighlight () { + return this.expanded ? this.highlight : null + }, + toggleExpanded () { + this.expanded = !this.expanded } } } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 42d009c9..b208d540 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -1,9 +1,9 @@ + + diff --git a/src/components/status/status.js b/src/components/status/status.js index 20ca86a6..8e489704 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -25,11 +25,11 @@ const Status = { 'replies', 'isPreview', 'noHeading', - 'inlineExpanded', - 'replying' + 'inlineExpanded' ], data () { return { + replying: false, expanded: false, unmuted: false, userExpanded: false, @@ -307,10 +307,9 @@ const Status = { } }, toggleReplying () { - this.$emit('toggleReplying') + this.replying = !this.replying }, gotoOriginal (id) { - // only handled by conversation, not status_or_conversation if (this.inConversation) { this.$emit('goto', id) } diff --git a/src/components/status_or_conversation/status_or_conversation.js b/src/components/status_or_conversation/status_or_conversation.js deleted file mode 100644 index 749f7665..00000000 --- a/src/components/status_or_conversation/status_or_conversation.js +++ /dev/null @@ -1,26 +0,0 @@ -import Status from '../status/status.vue' -import Conversation from '../conversation/conversation.vue' - -const statusOrConversation = { - props: ['statusoid'], - data () { - return { - expanded: false, - replying: false - } - }, - components: { - Status, - Conversation - }, - methods: { - toggleExpanded () { - this.expanded = !this.expanded - }, - toggleReplying () { - this.replying = !this.replying - } - } -} - -export default statusOrConversation diff --git a/src/components/status_or_conversation/status_or_conversation.vue b/src/components/status_or_conversation/status_or_conversation.vue deleted file mode 100644 index 43a60c3a..00000000 --- a/src/components/status_or_conversation/status_or_conversation.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - - - diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index c45f8947..1da7d5cc 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -1,6 +1,6 @@ import Status from '../status/status.vue' import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js' -import StatusOrConversation from '../status_or_conversation/status_or_conversation.vue' +import Conversation from '../conversation/conversation.vue' import { throttle } from 'lodash' const Timeline = { @@ -43,7 +43,7 @@ const Timeline = { }, components: { Status, - StatusOrConversation + Conversation }, created () { const store = this.$store diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 8f28d65c..e0a34bd1 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -16,7 +16,13 @@
    - +
    From 4efcda1b4137fa14659a586d4d8559dcdd0f479b Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 11 Mar 2019 22:41:08 +0200 Subject: [PATCH 0026/1077] Added some tests --- .../entity_normalizer.service.js | 2 +- .../entity_normalizer.spec.js | 75 ++++++++++++++++++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 633bd3dc..b7e5711e 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -142,7 +142,7 @@ const parseAttachment = (data) => { return output } -const addEmojis = (string, emojis) => { +export const addEmojis = (string, emojis) => { return emojis.reduce((acc, emoji) => { return acc.replace( new RegExp(`:${emoji.shortcode}:`, 'g'), 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 6245361c..2b0b0d6d 100644 --- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js +++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js @@ -1,4 +1,4 @@ -import { parseStatus, parseUser, parseNotification } from '../../../../../src/services/entity_normalizer/entity_normalizer.service.js' +import { parseStatus, parseUser, parseNotification, addEmojis } from '../../../../../src/services/entity_normalizer/entity_normalizer.service.js' import mastoapidata from '../../../../fixtures/mastoapi.json' import qvitterapidata from '../../../../fixtures/statuses.json' @@ -143,6 +143,23 @@ const makeMockNotificationQvitter = (overrides = {}) => { }, overrides) } +const makeMockEmojiMasto = (overrides = [{}]) => { + return [ + Object.assign({ + shortcode: 'image', + static_url: 'https://example.com/image.png', + url: 'https://example.com/image.png', + visible_in_picker: false + }, overrides[0]), + Object.assign({ + shortcode: 'thinking', + static_url: 'https://example.com/think.png', + url: 'https://example.com/think.png', + visible_in_picker: false + }, overrides[1]) + ] +} + parseNotification parseUser parseStatus @@ -218,6 +235,22 @@ describe('API Entities normalizer', () => { expect(parsedRepeat).to.have.property('retweeted_status') expect(parsedRepeat).to.have.deep.property('retweeted_status.id', 'deadbeef') }) + + it('adds emojis to post content', () => { + const post = makeMockStatusMasto({ emojis: makeMockEmojiMasto(), content: 'Makes you think :thinking:' }) + + const parsedPost = parseStatus(post) + + expect(parsedPost).to.have.property('statusnet_html').that.contains(' { + const post = makeMockStatusMasto({ emojis: makeMockEmojiMasto(), spoiler_text: 'CW: 300 IQ :thinking:' }) + + const parsedPost = parseStatus(post) + + expect(parsedPost).to.have.property('summary_html').that.contains(' { expect(parseUser(local)).to.have.property('is_local', true) expect(parseUser(remote)).to.have.property('is_local', false) }) + + it('adds emojis to user name', () => { + const user = makeMockUserMasto({ emojis: makeMockEmojiMasto(), display_name: 'The :thinking: thinker' }) + + const parsedUser = parseUser(user) + + expect(parsedUser).to.have.property('name_html').that.contains(' { + const user = makeMockUserMasto({ emojis: makeMockEmojiMasto(), note: 'Hello i like to :thinking: a lot' }) + + const parsedUser = parseUser(user) + + expect(parsedUser).to.have.property('description_html').that.contains(' { expect(parseNotification(notif)).to.have.deep.property('from_profile.id', 'spurdo') }) }) + + describe('MastoAPI emoji adder', () => { + const emojis = makeMockEmojiMasto() + const imageHtml = 'image' + .replace(/"/g, '\'') + const thinkHtml = 'thinking' + .replace(/"/g, '\'') + + it('correctly replaces shortcodes in supplied string', () => { + const result = addEmojis('This post has :image: emoji and :thinking: emoji', emojis) + expect(result).to.include(thinkHtml) + expect(result).to.include(imageHtml) + }) + + it('handles consecutive emojis correctly', () => { + const result = addEmojis('Lelel emoji spam :thinking::thinking::thinking::thinking:', emojis) + expect(result).to.include(thinkHtml + thinkHtml + thinkHtml + thinkHtml) + }) + + it('Doesn\'t replace nonexistent emojis', () => { + const result = addEmojis('Admin add the :tenshi: emoji', emojis) + expect(result).to.equal('Admin add the :tenshi: emoji') + }) + }) }) From 130fb7ae1e037ad6fe3653e31185f76b4e0c281f Mon Sep 17 00:00:00 2001 From: dave Date: Mon, 11 Mar 2019 16:48:27 -0400 Subject: [PATCH 0027/1077] #434 - fix plain text issue --- src/i18n/ar.json | 2 +- src/i18n/ca.json | 2 +- src/i18n/cs.json | 2 +- src/i18n/de.json | 2 +- src/i18n/eo.json | 2 +- src/i18n/es.json | 2 +- src/i18n/fi.json | 2 +- src/i18n/fr.json | 2 +- src/i18n/ga.json | 2 +- src/i18n/he.json | 2 +- src/i18n/it.json | 2 +- src/i18n/ja.json | 2 +- src/i18n/ko.json | 2 +- src/i18n/nb.json | 2 +- src/i18n/nl.json | 2 +- src/i18n/oc.json | 2 +- src/i18n/pt.json | 2 +- src/i18n/zh.json | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/i18n/ar.json b/src/i18n/ar.json index 242dab78..72e3010f 100644 --- a/src/i18n/ar.json +++ b/src/i18n/ar.json @@ -49,7 +49,7 @@ "account_not_locked_warning_link": "مقفل", "attachments_sensitive": "اعتبر المرفقات كلها كمحتوى حساس", "content_type": { - "plain_text": "نص صافٍ" + "text/plain": "نص صافٍ" }, "content_warning": "الموضوع (اختياري)", "default": "وصلت للتوّ إلى لوس أنجلس.", diff --git a/src/i18n/ca.json b/src/i18n/ca.json index d2f285df..8fa3a88b 100644 --- a/src/i18n/ca.json +++ b/src/i18n/ca.json @@ -49,7 +49,7 @@ "account_not_locked_warning_link": "bloquejat", "attachments_sensitive": "Marca l'adjunt com a delicat", "content_type": { - "plain_text": "Text pla" + "text/plain": "Text pla" }, "content_warning": "Assumpte (opcional)", "default": "Em sento…", diff --git a/src/i18n/cs.json b/src/i18n/cs.json index 51e9d342..020092a6 100644 --- a/src/i18n/cs.json +++ b/src/i18n/cs.json @@ -71,7 +71,7 @@ "account_not_locked_warning_link": "uzamčen", "attachments_sensitive": "Označovat přílohy jako citlivé", "content_type": { - "plain_text": "Prostý text", + "text/plain": "Prostý text", "text/html": "HTML", "text/markdown": "Markdown" }, diff --git a/src/i18n/de.json b/src/i18n/de.json index 07d44348..fa9db16c 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -55,7 +55,7 @@ "account_not_locked_warning_link": "gesperrt", "attachments_sensitive": "Anhänge als heikel markieren", "content_type": { - "plain_text": "Nur Text" + "text/plain": "Nur Text" }, "content_warning": "Betreff (optional)", "default": "Sitze gerade im Hofbräuhaus.", diff --git a/src/i18n/eo.json b/src/i18n/eo.json index 34851a44..6c5b3a74 100644 --- a/src/i18n/eo.json +++ b/src/i18n/eo.json @@ -71,7 +71,7 @@ "account_not_locked_warning_link": "ŝlosita", "attachments_sensitive": "Marki kunsendaĵojn kiel konsternajn", "content_type": { - "plain_text": "Plata teksto" + "text/plain": "Plata teksto" }, "content_warning": "Temo (malnepra)", "default": "Ĵus alvenis al la Universala Kongreso!", diff --git a/src/i18n/es.json b/src/i18n/es.json index fe96dd08..a692eef9 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -61,7 +61,7 @@ "account_not_locked_warning_link": "bloqueada", "attachments_sensitive": "Contenido sensible", "content_type": { - "plain_text": "Texto Plano" + "text/plain": "Texto Plano" }, "content_warning": "Tema (opcional)", "default": "Acabo de aterrizar en L.A.", diff --git a/src/i18n/fi.json b/src/i18n/fi.json index 4f0ffb4b..fbe676cf 100644 --- a/src/i18n/fi.json +++ b/src/i18n/fi.json @@ -60,7 +60,7 @@ "account_not_locked_warning_link": "lukittu", "attachments_sensitive": "Merkkaa liitteet arkaluonteisiksi", "content_type": { - "plain_text": "Tavallinen teksti" + "text/plain": "Tavallinen teksti" }, "content_warning": "Aihe (valinnainen)", "default": "Tulin juuri saunasta.", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 1209556a..8f9f243e 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -51,7 +51,7 @@ "account_not_locked_warning_link": "verrouillé", "attachments_sensitive": "Marquer le média comme sensible", "content_type": { - "plain_text": "Texte brut" + "text/plain": "Texte brut" }, "content_warning": "Sujet (optionnel)", "default": "Écrivez ici votre prochain statut.", diff --git a/src/i18n/ga.json b/src/i18n/ga.json index 5be9297a..31250876 100644 --- a/src/i18n/ga.json +++ b/src/i18n/ga.json @@ -49,7 +49,7 @@ "account_not_locked_warning_link": "faoi glas", "attachments_sensitive": "Marcáil ceangaltán mar íogair", "content_type": { - "plain_text": "Gnáth-théacs" + "text/plain": "Gnáth-théacs" }, "content_warning": "Teideal (roghnach)", "default": "Lá iontach anseo i nGaillimh", diff --git a/src/i18n/he.json b/src/i18n/he.json index 213e6170..ea581e05 100644 --- a/src/i18n/he.json +++ b/src/i18n/he.json @@ -49,7 +49,7 @@ "account_not_locked_warning_link": "נעול", "attachments_sensitive": "סמן מסמכים מצורפים כלא בטוחים לצפייה", "content_type": { - "plain_text": "טקסט פשוט" + "text/plain": "טקסט פשוט" }, "content_warning": "נושא (נתון לבחירה)", "default": "הרגע נחת ב-ל.א.", diff --git a/src/i18n/it.json b/src/i18n/it.json index 385d21aa..f441292e 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -175,7 +175,7 @@ "account_not_locked_warning_link": "bloccato", "attachments_sensitive": "Segna allegati come sensibili", "content_type": { - "plain_text": "Testo normale" + "text/plain": "Testo normale" }, "content_warning": "Oggetto (facoltativo)", "default": "Appena atterrato in L.A.", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index f39a5a7c..b77f5531 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -61,7 +61,7 @@ "account_not_locked_warning_link": "ロックされたアカウント", "attachments_sensitive": "ファイルをNSFWにする", "content_type": { - "plain_text": "プレーンテキスト" + "text/plain": "プレーンテキスト" }, "content_warning": "せつめい (かかなくてもよい)", "default": "はねだくうこうに、つきました。", diff --git a/src/i18n/ko.json b/src/i18n/ko.json index 336e464f..402a354c 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -56,7 +56,7 @@ "account_not_locked_warning_link": "잠김", "attachments_sensitive": "첨부물을 민감함으로 설정", "content_type": { - "plain_text": "평문" + "text/plain": "평문" }, "content_warning": "주제 (필수 아님)", "default": "LA에 도착!", diff --git a/src/i18n/nb.json b/src/i18n/nb.json index 39e054f7..298dc0b9 100644 --- a/src/i18n/nb.json +++ b/src/i18n/nb.json @@ -49,7 +49,7 @@ "account_not_locked_warning_link": "låst", "attachments_sensitive": "Merk vedlegg som sensitive", "content_type": { - "plain_text": "Klar tekst" + "text/plain": "Klar tekst" }, "content_warning": "Tema (valgfritt)", "default": "Landet akkurat i L.A.", diff --git a/src/i18n/nl.json b/src/i18n/nl.json index 799e22b9..7e2f0604 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -57,7 +57,7 @@ "account_not_locked_warning_link": "gesloten", "attachments_sensitive": "Markeer bijlage als gevoelig", "content_type": { - "plain_text": "Gewone tekst" + "text/plain": "Gewone tekst" }, "content_warning": "Onderwerp (optioneel)", "default": "Tijd voor een pauze!", diff --git a/src/i18n/oc.json b/src/i18n/oc.json index fd5ccc97..baac3d25 100644 --- a/src/i18n/oc.json +++ b/src/i18n/oc.json @@ -71,7 +71,7 @@ "account_not_locked_warning_link": "clavat", "attachments_sensitive": "Marcar las pèças juntas coma sensiblas", "content_type": { - "plain_text": "Tèxte brut" + "text/plain": "Tèxte brut" }, "content_warning": "Avís de contengut (opcional)", "default": "Escrivètz aquí vòstre estatut.", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index cbc2c9a3..29ab995b 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -71,7 +71,7 @@ "account_not_locked_warning_link": "fechada", "attachments_sensitive": "Marcar anexos como sensíveis", "content_type": { - "plain_text": "Texto puro" + "text/plain": "Texto puro" }, "content_warning": "Assunto (opcional)", "default": "Acabei de chegar no Rio!", diff --git a/src/i18n/zh.json b/src/i18n/zh.json index 089a98e2..da6dae5f 100644 --- a/src/i18n/zh.json +++ b/src/i18n/zh.json @@ -49,7 +49,7 @@ "account_not_locked_warning_link": "上锁", "attachments_sensitive": "标记附件为敏感内容", "content_type": { - "plain_text": "纯文本" + "text/plain": "纯文本" }, "content_warning": "主题(可选)", "default": "刚刚抵达上海", From a6a162177b8347917494ddd18de9d239b15bd7fa Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 11 Mar 2019 23:03:55 +0200 Subject: [PATCH 0028/1077] instead of filtering nulls, let's just not have them in the first place --- src/modules/users.js | 6 +++--- .../entity_normalizer.service.js | 15 +++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/modules/users.js b/src/modules/users.js index 5eabb1ec..d04c7f0b 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -1,5 +1,5 @@ import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' -import { compact, map, each, merge, find, omitBy } from 'lodash' +import { compact, map, each, merge, find } from 'lodash' import { set } from 'vue' import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js' import oauthApi from '../services/new_api/oauth' @@ -11,7 +11,7 @@ export const mergeOrAdd = (arr, obj, item) => { const oldItem = obj[item.id] if (oldItem) { // We already have this, so only merge the new info. - merge(oldItem, omitBy(item, _ => _ === null)) + merge(oldItem, item) return { item: oldItem, new: false } } else { // This is a new item, prepare it @@ -39,7 +39,7 @@ export const mutations = { }, setCurrentUser (state, user) { state.lastLoginName = user.screen_name - state.currentUser = merge(state.currentUser || {}, omitBy(user, _ => _ === null)) + state.currentUser = merge(state.currentUser || {}, user) }, clearCurrentUser (state) { state.currentUser = false diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index d20ce77f..7c840552 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -39,10 +39,10 @@ export const parseUser = (data) => { return output } - output.name = null // missing + // output.name = ??? missing output.name_html = data.display_name - output.description = null // missing + // output.description = ??? missing output.description_html = data.note // Utilize avatar_static for gif avatars? @@ -83,7 +83,7 @@ export const parseUser = (data) => { output.friends_count = data.friends_count - output.bot = null // missing + // output.bot = ??? missing output.statusnet_profile_url = data.statusnet_profile_url @@ -134,7 +134,7 @@ const parseAttachment = (data) => { output.meta = data.meta // not present in BE yet } else { output.mimetype = data.mimetype - output.meta = null // missing + // output.meta = ??? missing } output.url = data.url @@ -166,7 +166,7 @@ export const parseStatus = (data) => { output.in_reply_to_user_id = data.in_reply_to_account_id // Missing!! fix in UI? - output.in_reply_to_screen_name = null + // output.in_reply_to_screen_name = ??? // Not exactly the same but works output.statusnet_conversation_id = data.id @@ -179,8 +179,7 @@ export const parseStatus = (data) => { output.summary_html = data.spoiler_text output.external_url = data.url - // FIXME missing!! - output.is_local = false + // output.is_local = ??? missing } else { output.favorited = data.favorited output.fave_num = data.fave_num @@ -259,7 +258,7 @@ export const parseNotification = (data) => { if (masto) { output.type = mastoDict[data.type] || data.type - output.seen = null // missing + // output.seen = ??? missing output.status = parseStatus(data.status) output.action = output.status // not sure output.from_profile = parseUser(data.account) From 03f1b9525213daac521dea3fbdc1b7f70e5e0064 Mon Sep 17 00:00:00 2001 From: Rond Date: Mon, 11 Mar 2019 18:26:40 -0300 Subject: [PATCH 0029/1077] fixing typos and badly translated strings --- src/i18n/pt.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/i18n/pt.json b/src/i18n/pt.json index cbc2c9a3..3c2d2be4 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -51,7 +51,7 @@ "public_tl": "Linha do tempo pública", "timeline": "Linha do tempo", "twkn": "Toda a rede conhecida", - "user_search": "Busca de usuário", + "user_search": "Buscar usuários", "who_to_follow": "Quem seguir", "preferences": "Preferências" }, @@ -67,8 +67,8 @@ }, "post_status": { "new_status": "Postar novo status", - "account_not_locked_warning": "Sua conta não está {0}. Qualquer pessoa pode te seguir para ver seus posts restritos.", - "account_not_locked_warning_link": "fechada", + "account_not_locked_warning": "Sua conta não é {0}. Qualquer pessoa pode te seguir e ver seus posts privados (só para seguidores).", + "account_not_locked_warning_link": "restrita", "attachments_sensitive": "Marcar anexos como sensíveis", "content_type": { "plain_text": "Texto puro" @@ -115,7 +115,7 @@ "avatarRadius": "Avatares", "background": "Pano de Fundo", "bio": "Biografia", - "blocks_tab": "Blocos", + "blocks_tab": "Bloqueios", "btnRadius": "Botões", "cBlue": "Azul (Responder, seguir)", "cGreen": "Verde (Repetir)", @@ -125,7 +125,7 @@ "change_password_error": "Houve um erro ao modificar sua senha.", "changed_password": "Senha modificada com sucesso!", "collapse_subject": "Esconder posts com assunto", - "composing": "Escrevendo", + "composing": "Escrita", "confirm_new_password": "Confirmar nova senha", "current_avatar": "Seu avatar atual", "current_password": "Sua senha atual", @@ -139,7 +139,7 @@ "avatar_size_instruction": "O tamanho mínimo recomendado para imagens de avatar é 150x150 pixels.", "export_theme": "Salvar predefinições", "filtering": "Filtragem", - "filtering_explanation": "Todas as postagens contendo estas palavras serão silenciadas, uma por linha.", + "filtering_explanation": "Todas as postagens contendo estas palavras serão silenciadas; uma palavra por linha.", "follow_export": "Exportar quem você segue", "follow_export_button": "Exportar quem você segue para um arquivo CSV", "follow_export_processing": "Processando. Em breve você receberá a solicitação de download do arquivo", @@ -178,7 +178,7 @@ "name_bio": "Nome & Biografia", "new_password": "Nova senha", "notification_visibility": "Tipos de notificação para mostrar", - "notification_visibility_follows": "Seguidos", + "notification_visibility_follows": "Seguidas", "notification_visibility_likes": "Favoritos", "notification_visibility_mentions": "Menções", "notification_visibility_repeats": "Repetições", @@ -187,7 +187,7 @@ "no_mutes": "Sem silenciados", "hide_follows_description": "Não mostrar quem estou seguindo", "hide_followers_description": "Não mostrar quem me segue", - "show_admin_badge": "Mostrar distintivo de Administrador em meu perfil", + "show_admin_badge": "Mostrar título de Administrador em meu perfil", "show_moderator_badge": "Mostrar título de Moderador em meu perfil", "nsfw_clickthrough": "Habilitar clique para ocultar anexos sensíveis", "oauth_tokens": "Token OAuth", @@ -201,9 +201,9 @@ "profile_background": "Pano de fundo de perfil", "profile_banner": "Capa de perfil", "profile_tab": "Perfil", - "radii_help": "Arredondar arestas da interface (em píxeis)", + "radii_help": "Arredondar arestas da interface (em pixel)", "replies_in_timeline": "Respostas na linha do tempo", - "reply_link_preview": "Habilitar a pré-visualização de link de respostas ao passar o mouse.", + "reply_link_preview": "Habilitar a pré-visualização de de respostas ao passar o mouse.", "reply_visibility_all": "Mostrar todas as respostas", "reply_visibility_following": "Só mostrar respostas direcionadas a mim ou a usuários que sigo", "reply_visibility_self": "Só mostrar respostas direcionadas a mim", @@ -212,7 +212,7 @@ "security_tab": "Segurança", "scope_copy": "Copiar opções de privacidade ao responder (Mensagens diretas sempre copiam)", "set_new_avatar": "Alterar avatar", - "set_new_profile_background": "Alterar o plano de fundo de perfil", + "set_new_profile_background": "Alterar o pano de fundo de perfil", "set_new_profile_banner": "Alterar capa de perfil", "settings": "Configurações", "subject_input_always_show": "Sempre mostrar campo de assunto", @@ -220,9 +220,9 @@ "subject_line_email": "Como em email: \"re: assunto\"", "subject_line_mastodon": "Como o Mastodon: copiar como está", "subject_line_noop": "Não copiar", - "post_status_content_type": "Postar tipo de conteúdo do status", - "stop_gifs": "Reproduzir GIFs ao passar o cursor em cima", - "streaming": "Habilitar o fluxo automático de postagens quando ao topo da página", + "post_status_content_type": "Tipo de conteúdo do status", + "stop_gifs": "Reproduzir GIFs ao passar o cursor", + "streaming": "Habilitar o fluxo automático de postagens no topo da página", "text": "Texto", "theme": "Tema", "theme_help": "Use cores em código hexadecimal (#rrggbb) para personalizar seu esquema de cores.", @@ -235,7 +235,7 @@ "false": "não", "true": "sim" }, - "notifications": "Notifications", + "notifications": "Notificações", "enable_web_push_notifications": "Habilitar notificações web push", "style": { "switcher": { @@ -245,7 +245,7 @@ "keep_roundness": "Manter arredondado", "keep_fonts": "Manter fontes", "save_load_hint": "Manter as opções preserva as opções atuais ao selecionar ou carregar temas; também salva as opções ao exportar um tempo. Quanto todos os campos estiverem desmarcados, tudo será salvo ao exportar o tema.", - "reset": "Voltar ao padrão", + "reset": "Restaurar o padrão", "clear_all": "Limpar tudo", "clear_opacity": "Limpar opacidade" }, @@ -319,7 +319,7 @@ }, "fonts": { "_tab_label": "Fontes", - "help": "Selecionar fonte dos elementos da interface. Para fonte \"personalizada\" você deve entrar exatamente o nome da fonte no sistema.", + "help": "Selecione as fontes dos elementos da interface. Para fonte \"personalizada\" você deve inserir o mesmo nome da fonte no sistema.", "components": { "interface": "Interface", "input": "Campo de entrada", @@ -383,7 +383,7 @@ "mute": "Silenciar", "muted": "Silenciado", "per_day": "por dia", - "remote_follow": "Seguidor Remoto", + "remote_follow": "Seguir remotamente", "statuses": "Postagens", "unblock": "Desbloquear", "unblock_progress": "Desbloqueando...", From b9877a4323ab0025c14a8126eb2f6268a4bea6ac Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 11 Mar 2019 23:08:14 +0200 Subject: [PATCH 0030/1077] actually use embedded relationship if it's present --- .../entity_normalizer/entity_normalizer.service.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 7c840552..37c1f544 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -59,10 +59,13 @@ export const parseUser = (data) => { output.statusnet_profile_url = data.url if (data.pleroma) { - const pleroma = data.pleroma - output.follows_you = pleroma.follows_you - output.statusnet_blocking = pleroma.statusnet_blocking - output.muted = pleroma.muted + const relationship = data.pleroma.relationship + + if (relationship) { + output.follows_you = relationship.follows_you + output.statusnet_blocking = relationship.statusnet_blocking + output.muted = relationship.muted + } } // Missing, trying to recover From ce8b5fcd11aed0dedf8dd8c16190e9f1a826da2d Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 12 Mar 2019 21:49:03 +0200 Subject: [PATCH 0031/1077] fix embedded relationship card parsing --- .../entity_normalizer/entity_normalizer.service.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 37c1f544..9f9db563 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -62,9 +62,10 @@ export const parseUser = (data) => { const relationship = data.pleroma.relationship if (relationship) { - output.follows_you = relationship.follows_you - output.statusnet_blocking = relationship.statusnet_blocking - output.muted = relationship.muted + output.follows_you = relationship.followed_by + output.following = relationship.following + output.statusnet_blocking = relationship.blocking + output.muted = relationship.muting } } From 27cbe3ca658e3f9a40650f119854cd7d31094085 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 12 Mar 2019 22:10:22 +0200 Subject: [PATCH 0032/1077] =?UTF-8?q?=E3=83=AC=E3=82=A4=E3=83=B3=E3=81=9B?= =?UTF-8?q?=E3=82=93=E3=81=B1=E3=81=84=E3=81=AB=E3=82=B5=E3=83=B3=E3=82=AD?= =?UTF-8?q?=E3=83=A5=E3=83=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/user_profile/user_profile.js | 7 ++++--- src/modules/users.js | 10 ++++------ src/services/api/api.service.js | 18 ------------------ .../backend_interactor_service.js | 5 ----- 4 files changed, 8 insertions(+), 32 deletions(-) diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index a8dfce2f..216ac392 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -100,9 +100,10 @@ const UserProfile = { if (this.userId && !this.$route.params.name) { fetchPromise = this.$store.dispatch('fetchUser', this.userId) } else { - fetchPromise = this.$store.dispatch('fetchUserByScreenName', this.userName) - .then(userId => { - this.fetchedUserId = userId + fetchPromise = this.$store.dispatch('fetchUser', this.userName) + .then(({ id }) => { + console.log(arguments) + this.fetchedUserId = id }) } return fetchPromise diff --git a/src/modules/users.js b/src/modules/users.js index d04c7f0b..27114684 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -152,12 +152,10 @@ const users = { actions: { fetchUser (store, id) { return store.rootState.api.backendInteractor.fetchUser({ id }) - .then((user) => store.commit('addNewUsers', [user])) - }, - fetchUserByScreenName (store, screenName) { - return store.rootState.api.backendInteractor.figureOutUserId({ screenName }) - .then((qvitterUserData) => store.rootState.api.backendInteractor.fetchUser({ id: qvitterUserData.id })) - .then((user) => store.commit('addNewUsers', [user]) || user.id) + .then((user) => { + store.commit('addNewUsers', [user]) + return user + }) }, fetchUserRelationship (store, id) { return store.rootState.api.backendInteractor.fetchUserRelationship({ id }) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 5a0aa2de..1c6703b7 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -32,7 +32,6 @@ const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json' const BLOCKING_URL = '/api/blocks/create.json' const UNBLOCKING_URL = '/api/blocks/destroy.json' -const USER_URL = '/api/users/show.json' const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' @@ -273,22 +272,6 @@ const fetchUserRelationship = ({id, credentials}) => { }) } -// TODO remove once MastoAPI supports screen_name in fetchUser one -const figureOutUserId = ({screenName, credentials}) => { - let url = `${USER_URL}/?user_id=${screenName}` - return fetch(url, { headers: authHeaders(credentials) }) - .then((response) => { - return new Promise((resolve, reject) => response.json() - .then((json) => { - if (!response.ok) { - return reject(new StatusCodeError(response.status, json, { url }, response)) - } - return resolve(json) - })) - }) - .then((data) => parseUser(data)) -} - const fetchFriends = ({id, page, credentials}) => { let url = `${FRIENDS_URL}?user_id=${id}` if (page) { @@ -622,7 +605,6 @@ const apiService = { unblockUser, fetchUser, fetchUserRelationship, - figureOutUserId, favorite, unfavorite, retweet, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index 48689167..cbd0b733 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -26,10 +26,6 @@ const backendInteractorService = (credentials) => { return apiService.fetchAllFollowing({username, credentials}) } - const figureOutUserId = ({screenName}) => { - return apiService.figureOutUserId({screenName, credentials}) - } - const fetchUser = ({id}) => { return apiService.fetchUser({id, credentials}) } @@ -99,7 +95,6 @@ const backendInteractorService = (credentials) => { unfollowUser, blockUser, unblockUser, - figureOutUserId, fetchUser, fetchUserRelationship, fetchAllFollowing, From 644eba87fe1d290bfce298a0b744375608a688f3 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 12 Mar 2019 22:14:41 +0200 Subject: [PATCH 0033/1077] whoops --- src/components/user_profile/user_profile.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 216ac392..1bf4a86d 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -102,7 +102,6 @@ const UserProfile = { } else { fetchPromise = this.$store.dispatch('fetchUser', this.userName) .then(({ id }) => { - console.log(arguments) this.fetchedUserId = id }) } From cd9a7dd48802fff8942ae607a23677cfb43a7b14 Mon Sep 17 00:00:00 2001 From: dave Date: Tue, 12 Mar 2019 17:16:57 -0400 Subject: [PATCH 0034/1077] #436: integrate mastoAPI notifications --- src/components/notification/notification.js | 3 + src/components/notification/notification.vue | 83 +++++++++++-------- src/components/notifications/notifications.js | 5 +- .../notifications/notifications.vue | 3 +- src/i18n/en.json | 2 + src/modules/statuses.js | 36 +++++++- src/services/api/api.service.js | 29 ++++++- .../entity_normalizer.service.js | 16 +++- 8 files changed, 135 insertions(+), 42 deletions(-) diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index fe5b7018..c86f22bb 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -21,6 +21,9 @@ const Notification = { }, userProfileLink (user) { return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames) + }, + dismiss () { + this.$store.dispatch('dismissNotifications', { id: this.notification.id }) } }, computed: { diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index 5e9cef97..b281c7cf 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -1,44 +1,57 @@ + + \ No newline at end of file diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 9fc5e38a..68004d54 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -53,7 +53,10 @@ const Notifications = { }, methods: { markAsSeen () { - this.$store.dispatch('markNotificationsAsSeen', this.visibleNotifications) + this.$store.dispatch('markNotificationsAsSeen') + }, + clear () { + this.$store.dispatch('clearNotifications') }, fetchOlderNotifications () { const store = this.$store diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index 6f162b62..6438361d 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -9,7 +9,8 @@
    {{$t('timeline.error_fetching')}}
    - + +
    diff --git a/src/i18n/en.json b/src/i18n/en.json index 01fe2fba..38abfaf5 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -62,6 +62,8 @@ "load_older": "Load older notifications", "notifications": "Notifications", "read": "Read!", + "clear": "Clear!", + "dismiss": "Dismiss!", "repeated_you": "repeated your status", "no_more_notifications": "No more notifications" }, diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 6b512fa3..fde783a5 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -1,4 +1,4 @@ -import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray } from 'lodash' +import { remove, slice, each, find, findIndex, maxBy, minBy, merge, first, last, isArray } from 'lodash' import apiService from '../services/api/api.service.js' // import parse from '../services/status_parser/status_parser.js' @@ -390,6 +390,27 @@ export const mutations = { notification.seen = true }) }, + clearNotifications (state) { + state.notifications.data = [] + state.notifications.idStore = {} + state.notifications.maxId = 0 + state.notifications.minId = 0 + }, + dismissNotifications (state, { id }) { + const { data } = state.notifications + const idx = findIndex(data, { id }) + + if (idx !== -1) { + const notification = data[idx] + data.splice(idx, 1) + delete state.notifications.idStore[id] + if (state.notifications.maxId === notification.id) { + state.notifications.maxId = data.length ? maxBy(data, 'id').id : 0 + } else if (state.notifications.minId === notification.id) { + state.notifications.minId = data.length ? minBy(data, 'id').id : 0 + } + } + }, queueFlush (state, { timeline, id }) { state.timelines[timeline].flushMarker = id } @@ -474,6 +495,19 @@ const statuses = { id: rootState.statuses.notifications.maxId, credentials: rootState.users.currentUser.credentials }) + }, + clearNotifications ({ rootState, commit }) { + commit('clearNotifications') + apiService.clearNotifications({ + credentials: rootState.users.currentUser.credentials + }) + }, + dismissNotifications ({ rootState, commit }, { id }) { + commit('dismissNotifications', { id }) + apiService.dismissNotifications({ + id, + credentials: rootState.users.currentUser.credentials + }) } }, mutations diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 2de87026..9d3139ce 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -29,7 +29,6 @@ const BANNER_UPDATE_URL = '/api/account/update_profile_banner.json' const PROFILE_UPDATE_URL = '/api/account/update_profile.json' const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json' const QVITTER_USER_TIMELINE_URL = '/api/qvitter/statuses/user_timeline.json' -const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json' const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json' const BLOCKING_URL = '/api/blocks/create.json' const UNBLOCKING_URL = '/api/blocks/destroy.json' @@ -43,6 +42,9 @@ const DENY_USER_URL = '/api/pleroma/friendships/deny' const SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' +const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications' +const MASTODON_USER_NOTIFICATIONS_CLEAR_URL = '/api/v1/notifications/clear' +const MASTODON_USER_NOTIFICATIONS_DISMISS_URL = '/api/v1/notifications/dismiss' import { each, map } from 'lodash' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' @@ -345,7 +347,7 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use friends: FRIENDS_TIMELINE_URL, mentions: MENTIONS_URL, dms: DM_TIMELINE_URL, - notifications: QVITTER_USER_NOTIFICATIONS_URL, + notifications: MASTODON_USER_NOTIFICATIONS_URL, 'publicAndExternal': PUBLIC_AND_EXTERNAL_TIMELINE_URL, user: QVITTER_USER_TIMELINE_URL, media: QVITTER_USER_TIMELINE_URL, @@ -575,6 +577,25 @@ const markNotificationsAsSeen = ({id, credentials}) => { }).then((data) => data.json()) } +const clearNotifications = ({ credentials }) => { + return fetch(MASTODON_USER_NOTIFICATIONS_CLEAR_URL, { + headers: authHeaders(credentials), + method: 'POST' + }).then((data) => data.json()) +} + +const dismissNotifications = ({ id, credentials }) => { + const body = new FormData() + + body.append('id', id) + + return fetch(MASTODON_USER_NOTIFICATIONS_DISMISS_URL, { + body, + headers: authHeaders(credentials), + method: 'POST' + }).then((data) => data.json()) +} + const apiService = { verifyCredentials, fetchTimeline, @@ -615,7 +636,9 @@ const apiService = { approveUser, denyUser, suggestions, - markNotificationsAsSeen + markNotificationsAsSeen, + clearNotifications, + dismissNotifications } export default apiService diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index d20ce77f..81b88bf0 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -249,6 +249,18 @@ export const parseStatus = (data) => { return output } +export const parseFollow = (data) => { + const output = {} + output.id = String(data.id) + output.visibility = true + output.created_at = new Date(data.created_at) + + // Converting to string, the right way. + output.user = parseUser(data.account) + + return output +} + export const parseNotification = (data) => { const mastoDict = { 'favourite': 'like', @@ -260,7 +272,9 @@ export const parseNotification = (data) => { if (masto) { output.type = mastoDict[data.type] || data.type output.seen = null // missing - output.status = parseStatus(data.status) + output.status = output.type === 'follow' + ? parseFollow(data) + : parseStatus(data.status) output.action = output.status // not sure output.from_profile = parseUser(data.account) } else { From 1387dfb889f8b59d7790cf794cb6498a0f308607 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 13 Mar 2019 11:29:45 +0100 Subject: [PATCH 0035/1077] Load persistedStated with async/await. --- src/main.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main.js b/src/main.js index a3265e3a..9ffc3727 100644 --- a/src/main.js +++ b/src/main.js @@ -53,9 +53,10 @@ const persistedStateOptions = { 'users.lastLoginName', 'oauth' ] -} +}; -createPersistedState(persistedStateOptions).then((persistedState) => { +(async () => { + const persistedState = await createPersistedState(persistedStateOptions) const store = new Vuex.Store({ modules: { interface: interfaceModule, @@ -75,7 +76,7 @@ createPersistedState(persistedStateOptions).then((persistedState) => { }) afterStoreSetup({ store, i18n }) -}) +})() // These are inlined by webpack's DefinePlugin /* eslint-disable */ From c030e622544346f6359e4df86bf20e503b0f2c3a Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 13 Mar 2019 11:57:30 +0100 Subject: [PATCH 0036/1077] afterStoreSetup: refactor. --- src/boot/after_store.js | 240 +++++++++++++++++++++------------------- 1 file changed, 128 insertions(+), 112 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index cd88c188..d9706960 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -4,119 +4,137 @@ import routes from './routes' import App from '../App.vue' -const afterStoreSetup = ({ store, i18n }) => { - window.fetch('/api/statusnet/config.json') - .then((res) => res.json()) - .then((data) => { - const { name, closed: registrationClosed, textlimit, uploadlimit, server, vapidPublicKey } = data.site +const getStatusnetConfig = async ({ store }) => { + try { + const res = await window.fetch('/api/statusnet/config.json') + const data = await res.json() + const { name, closed: registrationClosed, textlimit, uploadlimit, server, vapidPublicKey } = data.site - store.dispatch('setInstanceOption', { name: 'name', value: name }) - store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) - store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) }) - store.dispatch('setInstanceOption', { name: 'server', value: server }) + store.dispatch('setInstanceOption', { name: 'name', value: name }) + store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) + store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) }) + store.dispatch('setInstanceOption', { name: 'server', value: server }) - // TODO: default values for this stuff, added if to not make it break on - // my dev config out of the box. - if (uploadlimit) { - store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadlimit.uploadlimit) }) - store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadlimit.avatarlimit) }) - store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadlimit.backgroundlimit) }) - store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadlimit.bannerlimit) }) + // TODO: default values for this stuff, added if to not make it break on + // my dev config out of the box. + if (uploadlimit) { + store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadlimit.uploadlimit) }) + store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadlimit.avatarlimit) }) + store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadlimit.backgroundlimit) }) + store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadlimit.bannerlimit) }) + } + + if (vapidPublicKey) { + store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) + } + + return data.site.pleromafe + } catch (error) { + console.error('Could not load statusnet config, potentially fatal') + console.error(error) + } +} + +const getStaticConfig = async () => { + try { + const res = await window.fetch('/static/config.json') + return res.json() + } catch (error) { + console.warn('Failed to load static/config.json, continuing without it.') + console.warn(error) + return {} + } +} + +const setSettings = async ({ apiConfig, staticConfig, store }) => { + const overrides = window.___pleromafe_dev_overrides || {} + const env = window.___pleromafe_mode.NODE_ENV + + // This takes static config and overrides properties that are present in apiConfig + let config = {} + if (overrides.staticConfigPreference && env === 'development') { + console.warn('OVERRIDING API CONFIG WITH STATIC CONFIG') + config = Object.assign({}, apiConfig, staticConfig) + } else { + config = Object.assign({}, staticConfig, apiConfig) + } + + const copyInstanceOption = (name) => { + store.dispatch('setInstanceOption', { name, value: config[name] }) + } + + copyInstanceOption('nsfwCensorImage') + copyInstanceOption('background') + copyInstanceOption('hidePostStats') + copyInstanceOption('hideUserStats') + copyInstanceOption('hideFilteredStatuses') + copyInstanceOption('logo') + + store.dispatch('setInstanceOption', { + name: 'logoMask', + value: typeof config.logoMask === 'undefined' + ? true + : config.logoMask + }) + + store.dispatch('setInstanceOption', { + name: 'logoMargin', + value: typeof config.logoMargin === 'undefined' + ? 0 + : config.logoMargin + }) + + copyInstanceOption('redirectRootNoLogin') + copyInstanceOption('redirectRootLogin') + copyInstanceOption('showInstanceSpecificPanel') + copyInstanceOption('scopeOptionsEnabled') + copyInstanceOption('formattingOptionsEnabled') + copyInstanceOption('collapseMessageWithSubject') + copyInstanceOption('loginMethod') + copyInstanceOption('scopeCopy') + copyInstanceOption('subjectLineBehavior') + copyInstanceOption('postContentType') + copyInstanceOption('alwaysShowSubjectInput') + copyInstanceOption('noAttachmentLinks') + copyInstanceOption('showFeaturesPanel') + + if ((config.chatDisabled)) { + store.dispatch('disableChat') + } else { + store.dispatch('initializeSocket') + } + + return store.dispatch('setTheme', config['theme']) +} + +const afterStoreSetup = async ({ store, i18n }) => { + const apiConfig = await getStatusnetConfig({ store }) + const staticConfig = await getStaticConfig() + await setSettings({ store, apiConfig, staticConfig }) + // Now we have the server settings and can try logging in + if (store.state.oauth.token) { + store.dispatch('loginUser', store.state.oauth.token) + } + + const router = new VueRouter({ + mode: 'history', + routes: routes(store), + scrollBehavior: (to, _from, savedPosition) => { + if (to.matched.some(m => m.meta.dontScroll)) { + return false } + return savedPosition || { x: 0, y: 0 } + } + }) - if (vapidPublicKey) { - store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) - } - - var apiConfig = data.site.pleromafe - - window.fetch('/static/config.json') - .then((res) => res.json()) - .catch((err) => { - console.warn('Failed to load static/config.json, continuing without it.') - console.warn(err) - return {} - }) - .then((staticConfig) => { - const overrides = window.___pleromafe_dev_overrides || {} - const env = window.___pleromafe_mode.NODE_ENV - - // This takes static config and overrides properties that are present in apiConfig - let config = {} - if (overrides.staticConfigPreference && env === 'development') { - console.warn('OVERRIDING API CONFIG WITH STATIC CONFIG') - config = Object.assign({}, apiConfig, staticConfig) - } else { - config = Object.assign({}, staticConfig, apiConfig) - } - - const copyInstanceOption = (name) => { - store.dispatch('setInstanceOption', {name, value: config[name]}) - } - - copyInstanceOption('nsfwCensorImage') - copyInstanceOption('background') - copyInstanceOption('hidePostStats') - copyInstanceOption('hideUserStats') - copyInstanceOption('hideFilteredStatuses') - copyInstanceOption('logo') - - store.dispatch('setInstanceOption', { - name: 'logoMask', - value: typeof config.logoMask === 'undefined' - ? true - : config.logoMask - }) - - store.dispatch('setInstanceOption', { - name: 'logoMargin', - value: typeof config.logoMargin === 'undefined' - ? 0 - : config.logoMargin - }) - - copyInstanceOption('redirectRootNoLogin') - copyInstanceOption('redirectRootLogin') - copyInstanceOption('showInstanceSpecificPanel') - copyInstanceOption('scopeOptionsEnabled') - copyInstanceOption('formattingOptionsEnabled') - copyInstanceOption('collapseMessageWithSubject') - copyInstanceOption('loginMethod') - copyInstanceOption('scopeCopy') - copyInstanceOption('subjectLineBehavior') - copyInstanceOption('postContentType') - copyInstanceOption('alwaysShowSubjectInput') - copyInstanceOption('noAttachmentLinks') - copyInstanceOption('showFeaturesPanel') - - if (config.chatDisabled) { - store.dispatch('disableChat') - } - - return store.dispatch('setTheme', config['theme']) - }) - .then(() => { - const router = new VueRouter({ - mode: 'history', - routes: routes(store), - scrollBehavior: (to, _from, savedPosition) => { - if (to.matched.some(m => m.meta.dontScroll)) { - return false - } - return savedPosition || { x: 0, y: 0 } - } - }) - - /* eslint-disable no-new */ - new Vue({ - router, - store, - i18n, - el: '#app', - render: h => h(App) - }) - }) - }) + /* eslint-disable no-new */ + new Vue({ + router, + store, + i18n, + el: '#app', + render: h => h(App) + }) window.fetch('/static/terms-of-service.html') .then((res) => res.text()) @@ -157,7 +175,7 @@ const afterStoreSetup = ({ store, i18n }) => { store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html }) }) - window.fetch('/nodeinfo/2.0.json') + return window.fetch('/nodeinfo/2.0.json') .then((res) => res.json()) .then((data) => { const metadata = data.metadata @@ -167,8 +185,6 @@ const afterStoreSetup = ({ store, i18n }) => { store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') }) store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) - store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats }) - store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames }) const suggestions = metadata.suggestions From f535ccd92583819f48d32c381b234ab26508666d Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 13 Mar 2019 12:10:57 +0100 Subject: [PATCH 0037/1077] afterStoreSetup: refactor TOS and panel fetching, handle 404s. --- src/boot/after_store.js | 65 +++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index d9706960..c1073012 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -107,10 +107,43 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { return store.dispatch('setTheme', config['theme']) } +const getTOS = async ({ store }) => { + try { + const res = await window.fetch('/static/terms-of-service.html') + if (res.ok) { + const html = await res.text() + store.dispatch('setInstanceOption', { name: 'tos', value: html }) + } else { + throw (res) + } + } catch (e) { + console.warn("Can't load TOS") + console.warn(e) + } +} + +const getInstancePanel = async ({ store }) => { + try { + const res = await window.fetch('/instance/panel.html') + if (res.ok) { + const html = await res.text() + store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html }) + } else { + throw (res) + } + } catch (e) { + console.warn("Can't load instance panel") + console.log(e) + } +} + const afterStoreSetup = async ({ store, i18n }) => { const apiConfig = await getStatusnetConfig({ store }) const staticConfig = await getStaticConfig() await setSettings({ store, apiConfig, staticConfig }) + await getTOS({ store }) + await getInstancePanel({ store }) + // Now we have the server settings and can try logging in if (store.state.oauth.token) { store.dispatch('loginUser', store.state.oauth.token) @@ -127,21 +160,6 @@ const afterStoreSetup = async ({ store, i18n }) => { } }) - /* eslint-disable no-new */ - new Vue({ - router, - store, - i18n, - el: '#app', - render: h => h(App) - }) - - window.fetch('/static/terms-of-service.html') - .then((res) => res.text()) - .then((html) => { - store.dispatch('setInstanceOption', { name: 'tos', value: html }) - }) - window.fetch('/api/pleroma/emoji.json') .then( (res) => res.json() @@ -169,13 +187,7 @@ const afterStoreSetup = async ({ store, i18n }) => { store.dispatch('setInstanceOption', { name: 'emoji', value: emoji }) }) - window.fetch('/instance/panel.html') - .then((res) => res.text()) - .then((html) => { - store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html }) - }) - - return window.fetch('/nodeinfo/2.0.json') + window.fetch('/nodeinfo/2.0.json') .then((res) => res.json()) .then((data) => { const metadata = data.metadata @@ -191,6 +203,15 @@ const afterStoreSetup = async ({ store, i18n }) => { store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled }) store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web }) }) + + /* eslint-disable no-new */ + return new Vue({ + router, + store, + i18n, + el: '#app', + render: h => h(App) + }) } export default afterStoreSetup From 446785ddce5f78af7087a47d82f45633fc2f7da5 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 13 Mar 2019 12:26:40 +0100 Subject: [PATCH 0038/1077] afterStoreSetup: Emoji and nodeinfo refactor. --- src/boot/after_store.js | 115 ++++++++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 45 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index c1073012..f9e14eb2 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -133,7 +133,73 @@ const getInstancePanel = async ({ store }) => { } } catch (e) { console.warn("Can't load instance panel") - console.log(e) + console.warn(e) + } +} + +const getStaticEmoji = async ({ store }) => { + try { + const res = await window.fetch('/static/emoji.json') + if (res.ok) { + const values = await res.json() + const emoji = Object.keys(values).map((key) => { + return { shortcode: key, image_url: false, 'utf': values[key] } + }) + store.dispatch('setInstanceOption', { name: 'emoji', value: emoji }) + } else { + throw (res) + } + } catch (e) { + console.warn("Can't load static emoji") + console.warn(e) + } +} + +// This is also used to indicate if we have a 'pleroma backend' or not. +// Somewhat weird, should probably be somewhere else. +const getCustomEmoji = async ({ store }) => { + try { + const res = await window.fetch('/api/pleroma/emoji.json') + if (res.ok) { + const values = await res.json() + const emoji = Object.keys(values).map((key) => { + return { shortcode: key, image_url: values[key] } + }) + store.dispatch('setInstanceOption', { name: 'customEmoji', value: emoji }) + store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: true }) + } else { + throw (res) + } + } catch (e) { + store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: false }) + console.warn("Can't load custom emojis, maybe not a Pleroma instance?") + console.warn(e) + } +} + +const getNodeInfo = async ({ store }) => { + try { + const res = await window.fetch('/nodeinfo/2.0.json') + if (res.ok) { + const data = await res.json() + const metadata = data.metadata + + const features = metadata.features + store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') }) + store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') }) + store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) + + store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames }) + + const suggestions = metadata.suggestions + store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled }) + store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web }) + } else { + throw (res) + } + } catch (e) { + console.warn('Could not load nodeinfo') + console.warn(e) } } @@ -143,6 +209,9 @@ const afterStoreSetup = async ({ store, i18n }) => { await setSettings({ store, apiConfig, staticConfig }) await getTOS({ store }) await getInstancePanel({ store }) + await getStaticEmoji({ store }) + await getCustomEmoji({ store }) + await getNodeInfo({ store }) // Now we have the server settings and can try logging in if (store.state.oauth.token) { @@ -160,50 +229,6 @@ const afterStoreSetup = async ({ store, i18n }) => { } }) - window.fetch('/api/pleroma/emoji.json') - .then( - (res) => res.json() - .then( - (values) => { - const emoji = Object.keys(values).map((key) => { - return { shortcode: key, image_url: values[key] } - }) - store.dispatch('setInstanceOption', { name: 'customEmoji', value: emoji }) - store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: true }) - }, - (failure) => { - store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: false }) - } - ), - (error) => console.log(error) - ) - - window.fetch('/static/emoji.json') - .then((res) => res.json()) - .then((values) => { - const emoji = Object.keys(values).map((key) => { - return { shortcode: key, image_url: false, 'utf': values[key] } - }) - store.dispatch('setInstanceOption', { name: 'emoji', value: emoji }) - }) - - window.fetch('/nodeinfo/2.0.json') - .then((res) => res.json()) - .then((data) => { - const metadata = data.metadata - - const features = metadata.features - store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') }) - store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') }) - store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) - - store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames }) - - const suggestions = metadata.suggestions - store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled }) - store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web }) - }) - /* eslint-disable no-new */ return new Vue({ router, From 48ac96cfc7c9c3328d7a18707f00a2fe1bc743a1 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 13 Mar 2019 12:41:39 +0100 Subject: [PATCH 0039/1077] afterStoreSetup: Handle 404 cases. --- src/boot/after_store.js | 48 ++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index f9e14eb2..a51f895e 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -7,28 +7,32 @@ import App from '../App.vue' const getStatusnetConfig = async ({ store }) => { try { const res = await window.fetch('/api/statusnet/config.json') - const data = await res.json() - const { name, closed: registrationClosed, textlimit, uploadlimit, server, vapidPublicKey } = data.site + if (res.ok) { + const data = await res.json() + const { name, closed: registrationClosed, textlimit, uploadlimit, server, vapidPublicKey } = data.site - store.dispatch('setInstanceOption', { name: 'name', value: name }) - store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) - store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) }) - store.dispatch('setInstanceOption', { name: 'server', value: server }) + store.dispatch('setInstanceOption', { name: 'name', value: name }) + store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) + store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) }) + store.dispatch('setInstanceOption', { name: 'server', value: server }) - // TODO: default values for this stuff, added if to not make it break on - // my dev config out of the box. - if (uploadlimit) { - store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadlimit.uploadlimit) }) - store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadlimit.avatarlimit) }) - store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadlimit.backgroundlimit) }) - store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadlimit.bannerlimit) }) + // TODO: default values for this stuff, added if to not make it break on + // my dev config out of the box. + if (uploadlimit) { + store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadlimit.uploadlimit) }) + store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadlimit.avatarlimit) }) + store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadlimit.backgroundlimit) }) + store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadlimit.bannerlimit) }) + } + + if (vapidPublicKey) { + store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) + } + + return data.site.pleromafe + } else { + throw (res) } - - if (vapidPublicKey) { - store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) - } - - return data.site.pleromafe } catch (error) { console.error('Could not load statusnet config, potentially fatal') console.error(error) @@ -38,7 +42,11 @@ const getStatusnetConfig = async ({ store }) => { const getStaticConfig = async () => { try { const res = await window.fetch('/static/config.json') - return res.json() + if (res.ok) { + return res.json() + } else { + throw (res) + } } catch (error) { console.warn('Failed to load static/config.json, continuing without it.') console.warn(error) From 5318227c378a9cd44baa4cc12dbb1935826c843b Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 13 Mar 2019 13:29:34 +0100 Subject: [PATCH 0040/1077] afterStoreSetup: Move log in and theme load to afterStoreSetup. --- src/boot/after_store.js | 10 ++++++++++ src/lib/persisted_state.js | 12 ------------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index a51f895e..3d400ffa 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -212,6 +212,16 @@ const getNodeInfo = 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 apiConfig = await getStatusnetConfig({ store }) const staticConfig = await getStaticConfig() await setSettings({ store, apiConfig, staticConfig }) diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js index e828a74b..7ab89c12 100644 --- a/src/lib/persisted_state.js +++ b/src/lib/persisted_state.js @@ -60,18 +60,6 @@ export default function createPersistedState ({ merge({}, store.state, savedState) ) } - 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 - }) - } - if (store.state.oauth.token) { - store.dispatch('loginUser', store.state.oauth.token) - } loaded = true } catch (e) { console.log("Couldn't load state") From 2f7d890ad228a2aeb8643373c9c9a6d925c6ff7f Mon Sep 17 00:00:00 2001 From: dave Date: Wed, 13 Mar 2019 14:08:03 -0400 Subject: [PATCH 0041/1077] #436: add dismiss button, disable is_seen part --- src/components/notification/notification.vue | 93 +++++++++---------- src/components/status/status.js | 6 +- src/components/status/status.vue | 3 + .../entity_normalizer.service.js | 6 +- .../notifications_fetcher.service.js | 22 +++-- 5 files changed, 68 insertions(+), 62 deletions(-) diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index b281c7cf..87b0fde2 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -1,57 +1,54 @@ - - \ No newline at end of file diff --git a/src/components/status/status.js b/src/components/status/status.js index 9e18fe15..4cd26be3 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -25,7 +25,8 @@ const Status = { 'replies', 'isPreview', 'noHeading', - 'inlineExpanded' + 'inlineExpanded', + 'isNotification' ], data () { return { @@ -365,6 +366,9 @@ const Status = { setMedia () { const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments return () => this.$store.dispatch('setMedia', attachments) + }, + dismissNotification () { + this.$emit('dismissNotification') } }, watch: { diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 1f6d0325..28a44346 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -52,6 +52,9 @@ + + + diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js index a79aa636..88fab656 100644 --- a/src/components/mobile_nav/mobile_nav.js +++ b/src/components/mobile_nav/mobile_nav.js @@ -1,11 +1,13 @@ import SideDrawer from '../side_drawer/side_drawer.vue' import Notifications from '../notifications/notifications.vue' +import MobilePostStatusModal from '../mobile_post_status_modal/mobile_post_status_modal.vue' import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils' const MobileNav = { components: { SideDrawer, - Notifications + Notifications, + MobilePostStatusModal }, data: () => ({ notificationsOpen: false @@ -25,9 +27,19 @@ const MobileNav = { }, toggleMobileNotifications () { this.notificationsOpen = !this.notificationsOpen + if (!this.notificationsOpen) { + this.markNotificationsAsSeen() + } }, scrollToTop () { window.scrollTo(0, 0) + }, + logout () { + this.$router.replace('/main/public') + this.$store.dispatch('logout') + }, + markNotificationsAsSeen () { + this.$refs.notifications.markAsSeen() } } } diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue index 8f682c39..af2d6d5a 100644 --- a/src/components/mobile_nav/mobile_nav.vue +++ b/src/components/mobile_nav/mobile_nav.vue @@ -1,6 +1,6 @@ diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 9fc5e38a..d3db4b29 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -7,6 +7,9 @@ import { } from '../../services/notification_utils/notification_utils.js' const Notifications = { + props: [ + 'noHeading' + ], created () { const store = this.$store const credentials = store.state.users.currentUser.credentials diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index 6f162b62..634a03ac 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -1,7 +1,7 @@