logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe
commit: cd48268c858f4a7f24767e41e81cb6524393c5e6
parent: 6f32ccf41717c2b59404c04ab0b0cb4b8c8171e3
Author: Henry <spam@hjkos.com>
Date:   Fri, 21 Sep 2018 09:19:02 +0000

Merge branch 'betterStorage' into 'develop'

Better storage

See merge request pleroma/pleroma-fe!343

Diffstat:

Mconfig/index.js5+++++
Msrc/App.js16++++++++--------
Msrc/components/features_panel/features_panel.js12++++++------
Msrc/components/instance_specific_panel/instance_specific_panel.js2+-
Msrc/components/login_form/login_form.js2+-
Msrc/components/post_status_form/post_status_form.js12++++++------
Msrc/components/registration/registration.js6+++---
Msrc/components/settings/settings.js39++++++++++++++++++++++-----------------
Msrc/components/settings/settings.vue20++++++++++++++++++--
Msrc/components/user_settings/user_settings.js4++--
Msrc/components/who_to_follow_panel/who_to_follow_panel.js4++--
Msrc/components/who_to_follow_panel/who_to_follow_panel.vue2+-
Msrc/i18n/en.json9++++++++-
Msrc/lib/persisted_state.js42+++++++++++++++++++++++++++++++-----------
Msrc/main.js198+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/modules/config.js7-------
Asrc/modules/instance.js63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/interface.js36++++++++++++++++++++++++++++++++++++
Msrc/services/api/api.service.js11++++++++---
19 files changed, 318 insertions(+), 172 deletions(-)

diff --git a/config/index.js b/config/index.js @@ -27,6 +27,11 @@ module.exports = { changeOrigin: true, cookieDomainRewrite: 'localhost' }, + '/nodeinfo': { + target: 'http://localhost:4000/', + changeOrigin: true, + cookieDomainRewrite: 'localhost' + }, '/socket': { target: 'http://localhost:4000/', changeOrigin: true, diff --git a/src/App.js b/src/App.js @@ -36,9 +36,9 @@ export default { computed: { currentUser () { return this.$store.state.users.currentUser }, background () { - return this.currentUser.background_image || this.$store.state.config.background + return this.currentUser.background_image || this.$store.state.instance.background }, - enableMask () { return this.supportsMask && this.$store.state.config.logoMask }, + enableMask () { return this.supportsMask && this.$store.state.instance.logoMask }, logoStyle () { return { 'visibility': this.enableMask ? 'hidden' : 'visible' @@ -46,24 +46,24 @@ export default { }, logoMaskStyle () { return this.enableMask ? { - 'mask-image': `url(${this.$store.state.config.logo})` + 'mask-image': `url(${this.$store.state.instance.logo})` } : { 'background-color': this.enableMask ? '' : 'transparent' } }, logoBgStyle () { return Object.assign({ - 'margin': `${this.$store.state.config.logoMargin} 0` + 'margin': `${this.$store.state.instance.logoMargin} 0` }, this.enableMask ? {} : { 'background-color': this.enableMask ? '' : 'transparent' }) }, - logo () { return this.$store.state.config.logo }, + logo () { return this.$store.state.instance.logo }, style () { return { 'background-image': `url(${this.background})` } }, - sitename () { return this.$store.state.config.name }, + sitename () { return this.$store.state.instance.name }, chat () { return this.$store.state.chat.channel.state === 'joined' }, - suggestionsEnabled () { return this.$store.state.config.suggestionsEnabled }, - showInstanceSpecificPanel () { return this.$store.state.config.showInstanceSpecificPanel } + suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled }, + showInstanceSpecificPanel () { return this.$store.state.instance.showInstanceSpecificPanel } }, methods: { activatePanel (panelName) { diff --git a/src/components/features_panel/features_panel.js b/src/components/features_panel/features_panel.js @@ -1,13 +1,13 @@ const FeaturesPanel = { computed: { chat: function () { - return this.$store.state.config.chatAvailable && (!this.$store.state.chatDisabled) + return this.$store.state.instance.chatAvailable && (!this.$store.state.chatDisabled) }, - gopher: function () { return this.$store.state.config.gopherAvailable }, - whoToFollow: function () { return this.$store.state.config.suggestionsEnabled }, - mediaProxy: function () { return this.$store.state.config.mediaProxyAvailable }, - scopeOptions: function () { return this.$store.state.config.scopeOptionsEnabled }, - textlimit: function () { return this.$store.state.config.textlimit } + gopher: function () { return this.$store.state.instance.gopherAvailable }, + whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled }, + mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable }, + scopeOptions: function () { return this.$store.state.instance.scopeOptionsEnabled }, + textlimit: function () { return this.$store.state.instance.textlimit } } } diff --git a/src/components/instance_specific_panel/instance_specific_panel.js b/src/components/instance_specific_panel/instance_specific_panel.js @@ -1,7 +1,7 @@ const InstanceSpecificPanel = { computed: { instanceSpecificPanelContent () { - return this.$store.state.config.instanceSpecificPanelContent + return this.$store.state.instance.instanceSpecificPanelContent } } } diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js @@ -5,7 +5,7 @@ const LoginForm = { }), computed: { loggingIn () { return this.$store.state.users.loggingIn }, - registrationOpen () { return this.$store.state.config.registrationOpen } + registrationOpen () { return this.$store.state.instance.registrationOpen } }, methods: { submit () { diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js @@ -102,7 +102,7 @@ const PostStatusForm = { name: '', utf: utf || '', // eslint-disable-next-line camelcase - img: utf ? '' : this.$store.state.config.server + image_url, + img: utf ? '' : this.$store.state.instance.server + image_url, highlighted: index === this.highlighted })) } else { @@ -120,16 +120,16 @@ const PostStatusForm = { return this.$store.state.users.users }, emoji () { - return this.$store.state.config.emoji || [] + return this.$store.state.instance.emoji || [] }, customEmoji () { - return this.$store.state.config.customEmoji || [] + return this.$store.state.instance.customEmoji || [] }, statusLength () { return this.newStatus.status.length }, statusLengthLimit () { - return this.$store.state.config.textlimit + return this.$store.state.instance.textlimit }, hasStatusLengthLimit () { return this.statusLengthLimit > 0 @@ -141,10 +141,10 @@ const PostStatusForm = { return this.hasStatusLengthLimit && (this.statusLength > this.statusLengthLimit) }, scopeOptionsEnabled () { - return this.$store.state.config.scopeOptionsEnabled + return this.$store.state.instance.scopeOptionsEnabled }, formattingOptionsEnabled () { - return this.$store.state.config.formattingOptionsEnabled + return this.$store.state.instance.formattingOptionsEnabled } }, methods: { diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js @@ -5,16 +5,16 @@ const registration = { registering: false }), created () { - if ((!this.$store.state.config.registrationOpen && !this.token) || !!this.$store.state.users.currentUser) { + if ((!this.$store.state.instance.registrationOpen && !this.token) || !!this.$store.state.users.currentUser) { this.$router.push('/main/all') } // Seems like this doesn't work at first page open for some reason - if (this.$store.state.config.registrationOpen && this.token) { + if (this.$store.state.instance.registrationOpen && this.token) { this.$router.push('/registration') } }, computed: { - termsofservice () { return this.$store.state.config.tos }, + termsofservice () { return this.$store.state.instance.tos }, token () { return this.$route.params.token } }, methods: { diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js @@ -6,25 +6,27 @@ import { filter, trim } from 'lodash' const settings = { data () { - const config = this.$store.state.config + const user = this.$store.state.config + const instance = this.$store.state.instance return { - hideAttachmentsLocal: config.hideAttachments, - hideAttachmentsInConvLocal: config.hideAttachmentsInConv, - hideNsfwLocal: config.hideNsfw, - notificationVisibilityLocal: config.notificationVisibility, - replyVisibilityLocal: config.replyVisibility, - loopVideoLocal: config.loopVideo, - loopVideoSilentOnlyLocal: config.loopVideoSilentOnly, - muteWordsString: config.muteWords.join('\n'), - autoLoadLocal: config.autoLoad, - streamingLocal: config.streaming, - pauseOnUnfocusedLocal: config.pauseOnUnfocused, - hoverPreviewLocal: config.hoverPreview, - collapseMessageWithSubjectLocal: typeof config.collapseMessageWithSubject === 'undefined' - ? config.defaultCollapseMessageWithSubject - : config.collapseMessageWithSubject, - stopGifs: config.stopGifs, + hideAttachmentsLocal: user.hideAttachments, + hideAttachmentsInConvLocal: user.hideAttachmentsInConv, + hideNsfwLocal: user.hideNsfw, + notificationVisibilityLocal: user.notificationVisibility, + replyVisibilityLocal: user.replyVisibility, + loopVideoLocal: user.loopVideo, + loopVideoSilentOnlyLocal: user.loopVideoSilentOnly, + muteWordsString: user.muteWords.join('\n'), + autoLoadLocal: user.autoLoad, + streamingLocal: user.streaming, + pauseOnUnfocusedLocal: user.pauseOnUnfocused, + hoverPreviewLocal: user.hoverPreview, + collapseMessageWithSubjectLocal: typeof user.collapseMessageWithSubject === 'undefined' + ? instance.collapseMessageWithSubject + : user.collapseMessageWithSubject, + collapseMessageWithSubjectDefault: this.$t('settings.values.' + instance.collapseMessageWithSubject), + stopGifs: user.stopGifs, loopSilentAvailable: // Firefox Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') || @@ -42,6 +44,9 @@ const settings = { computed: { user () { return this.$store.state.users.currentUser + }, + currentSaveStateNotice () { + return this.$store.state.interface.settings.currentSaveStateNotice } }, watch: { diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue @@ -1,7 +1,21 @@ <template> <div class="settings panel panel-default"> <div class="panel-heading"> - {{$t('settings.settings')}} + <div class="title"> + {{$t('settings.settings')}} + </div> + + <transition name="fade"> + <template v-if="currentSaveStateNotice"> + <div @click.prevent class="alert error" v-if="currentSaveStateNotice.error"> + {{ $t('settings.saving_err') }} + </div> + + <div @click.prevent class="alert transparent" v-if="!currentSaveStateNotice.error"> + {{ $t('settings.saving_ok') }} + </div> + </template> + </transition> </div> <div class="panel-body"> <tab-switcher> @@ -15,7 +29,9 @@ <ul class="setting-list"> <li> <input type="checkbox" id="collapseMessageWithSubject" v-model="collapseMessageWithSubjectLocal"> - <label for="collapseMessageWithSubject">{{$t('settings.collapse_subject')}}</label> + <label for="collapseMessageWithSubject"> + {{$t('settings.collapse_subject')}} {{$t('settings.instance_default', { value: collapseMessageWithSubjectDefault })}} + </label> </li> <li> <input type="checkbox" id="streaming" v-model="streamingLocal"> diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js @@ -32,10 +32,10 @@ const UserSettings = { return this.$store.state.users.currentUser }, pleromaBackend () { - return this.$store.state.config.pleromaBackend + return this.$store.state.instance.pleromaBackend }, scopeOptionsEnabled () { - return this.$store.state.config.scopeOptionsEnabled + return this.$store.state.instance.scopeOptionsEnabled }, vis () { return { diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js @@ -83,14 +83,14 @@ const WhoToFollowPanel = { moreUrl: function () { var host = window.location.hostname var user = this.user - var suggestionsWeb = this.$store.state.config.suggestionsWeb + var suggestionsWeb = this.$store.state.instance.suggestionsWeb var url url = suggestionsWeb.replace(/{{host}}/g, encodeURIComponent(host)) url = url.replace(/{{user}}/g, encodeURIComponent(user)) return url }, suggestionsEnabled () { - return this.$store.state.config.suggestionsEnabled + return this.$store.state.instance.suggestionsEnabled } }, watch: { diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.vue b/src/components/who_to_follow_panel/who_to_follow_panel.vue @@ -11,7 +11,7 @@ <img v-bind:src="img1"/> <router-link :to="{ name: 'user-profile', params: { id: id1 } }">{{ name1 }}</router-link><br> <img v-bind:src="img2"/> <router-link :to="{ name: 'user-profile', params: { id: id2 } }">{{ name2 }}</router-link><br> <img v-bind:src="img3"/> <router-link :to="{ name: 'user-profile', params: { id: id3 } }">{{ name3 }}</router-link><br> - <img v-bind:src="$store.state.config.logo"> <a v-bind:href="moreUrl" target="_blank">{{$t('who_to_follow.more')}}</a> + <img v-bind:src="$store.state.instance.logo"> <a v-bind:href="moreUrl" target="_blank">{{$t('who_to_follow.more')}}</a> </p> </div> </div> diff --git a/src/i18n/en.json b/src/i18n/en.json @@ -114,6 +114,7 @@ "import_followers_from_a_csv_file": "Import follows from a csv file", "import_theme": "Load preset", "inputRadius": "Input fields", + "instance_default: (default": "{value})", "interfaceLanguage": "Interface language", "invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.", "limited_availability": "Unavailable in your browser", @@ -142,6 +143,8 @@ "reply_visibility_all": "Show all replies", "reply_visibility_following": "Only show replies directed at me or users I'm following", "reply_visibility_self": "Only show replies directed at me", + "saving_err": "Error saving settings", + "saving_ok": "Settings saved", "security_tab": "Security", "set_new_avatar": "Set new avatar", "set_new_profile_background": "Set new profile background", @@ -153,7 +156,11 @@ "theme": "Theme", "theme_help": "Use hex color codes (#rrggbb) to customize your color theme.", "tooltipRadius": "Tooltips/alerts", - "user_settings": "User Settings" + "user_settings": "User Settings", + "values": { + "false": "no", + "true": "yes" + } }, "timeline": { "collapse": "Collapse", diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js @@ -1,7 +1,7 @@ import merge from 'lodash.merge' import objectPath from 'object-path' import localforage from 'localforage' -import { throttle, each } from 'lodash' +import { each } from 'lodash' let loaded = false @@ -12,18 +12,18 @@ const defaultReducer = (state, paths) => ( }, {}) ) +const saveImmedeatelyActions = [ + 'markNotificationsAsSeen', + 'clearCurrentUser', + 'setCurrentUser', + 'setHighlight', + 'setOption' +] + const defaultStorage = (() => { return localforage })() -const defaultSetState = (key, state, storage) => { - if (!loaded) { - console.log('waiting for old state to be loaded...') - } else { - return storage.setItem(key, state) - } -} - export default function createPersistedState ({ key = 'vuex-lz', paths = [], @@ -31,7 +31,14 @@ export default function createPersistedState ({ let value = storage.getItem(key) return value }, - setState = throttle(defaultSetState, 60000), + setState = (key, state, storage) => { + if (!loaded) { + console.log('waiting for old state to be loaded...') + return Promise.resolve() + } else { + return storage.setItem(key, state) + } + }, reducer = defaultReducer, storage = defaultStorage, subscriber = store => handler => store.subscribe(handler) @@ -72,7 +79,20 @@ export default function createPersistedState ({ subscriber(store)((mutation, state) => { try { - setState(key, reducer(state, paths), storage) + if (saveImmedeatelyActions.includes(mutation.type)) { + setState(key, reducer(state, paths), storage) + .then(success => { + if (typeof success !== 'undefined') { + if (mutation.type === 'setOption') { + store.dispatch('settingsSaved', { success }) + } + } + }, error => { + if (mutation.type === 'setOption') { + store.dispatch('settingsSaved', { error }) + } + }) + } } catch (e) { console.log("Couldn't persist state:") console.log(e) diff --git a/src/main.js b/src/main.js @@ -14,6 +14,8 @@ import Registration from './components/registration/registration.vue' import UserSettings from './components/user_settings/user_settings.vue' import FollowRequests from './components/follow_requests/follow_requests.vue' +import interfaceModule from './modules/interface.js' +import instanceModule from './modules/instance.js' import statusesModule from './modules/statuses.js' import usersModule from './modules/users.js' import apiModule from './modules/api.js' @@ -45,23 +47,7 @@ Vue.use(VueChatScroll) const persistedStateOptions = { paths: [ - 'config.collapseMessageWithSubject', - 'config.hideAttachments', - 'config.hideAttachmentsInConv', - 'config.hideNsfw', - 'config.replyVisibility', - 'config.notificationVisibility', - 'config.autoLoad', - 'config.hoverPreview', - 'config.streaming', - 'config.muteWords', - 'config.customTheme', - 'config.highlight', - 'config.loopVideo', - 'config.loopVideoSilentOnly', - 'config.pauseOnUnfocused', - 'config.stopGifs', - 'config.interfaceLanguage', + 'config', 'users.lastLoginName', 'statuses.notifications.maxSavedId' ] @@ -69,6 +55,8 @@ const persistedStateOptions = { const store = new Vuex.Store({ modules: { + interface: interfaceModule, + instance: instanceModule, statuses: statusesModule, users: usersModule, api: apiModule, @@ -92,92 +80,100 @@ window.fetch('/api/statusnet/config.json') .then((data) => { const {name, closed: registrationClosed, textlimit, server} = data.site - store.dispatch('setOption', { name: 'name', value: name }) - store.dispatch('setOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) - store.dispatch('setOption', { name: 'textlimit', value: parseInt(textlimit) }) - store.dispatch('setOption', { 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 }) var apiConfig = data.site.pleromafe window.fetch('/static/config.json') - .then((res) => res.json()) - .then((data) => { - var staticConfig = data - // This takes static config and overrides properties that are present in apiConfig - var config = Object.assign({}, staticConfig, apiConfig) - - var theme = (config.theme) - var background = (config.background) - var logo = (config.logo) - var logoMask = (typeof config.logoMask === 'undefined' ? true : config.logoMask) - var logoMargin = (typeof config.logoMargin === 'undefined' ? 0 : config.logoMargin) - var redirectRootNoLogin = (config.redirectRootNoLogin) - var redirectRootLogin = (config.redirectRootLogin) - var chatDisabled = (config.chatDisabled) - var showInstanceSpecificPanel = (config.showInstanceSpecificPanel) - var scopeOptionsEnabled = (config.scopeOptionsEnabled) - var formattingOptionsEnabled = (config.formattingOptionsEnabled) - var defaultCollapseMessageWithSubject = (config.collapseMessageWithSubject) - - store.dispatch('setOption', { name: 'theme', value: theme }) - store.dispatch('setOption', { name: 'background', value: background }) - store.dispatch('setOption', { name: 'logo', value: logo }) - store.dispatch('setOption', { name: 'logoMask', value: logoMask }) - store.dispatch('setOption', { name: 'logoMargin', value: logoMargin }) - store.dispatch('setOption', { name: 'showInstanceSpecificPanel', value: showInstanceSpecificPanel }) - store.dispatch('setOption', { name: 'scopeOptionsEnabled', value: scopeOptionsEnabled }) - store.dispatch('setOption', { name: 'formattingOptionsEnabled', value: formattingOptionsEnabled }) - store.dispatch('setOption', { name: 'defaultCollapseMessageWithSubject', value: defaultCollapseMessageWithSubject }) - if (chatDisabled) { - store.dispatch('disableChat') - } - - const routes = [ - { name: 'root', - path: '/', - redirect: to => { - return (store.state.users.currentUser ? redirectRootLogin : redirectRootNoLogin) || '/main/all' - }}, - { path: '/main/all', component: PublicAndExternalTimeline }, - { path: '/main/public', component: PublicTimeline }, - { path: '/main/friends', component: FriendsTimeline }, - { path: '/tag/:tag', component: TagTimeline }, - { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, - { name: 'user-profile', path: '/users/:id', component: UserProfile }, - { name: 'mentions', path: '/:username/mentions', component: Mentions }, - { name: 'settings', path: '/settings', component: Settings }, - { name: 'registration', path: '/registration', component: Registration }, - { name: 'registration', path: '/registration/:token', component: Registration }, - { name: 'friend-requests', path: '/friend-requests', component: FollowRequests }, - { name: 'user-settings', path: '/user-settings', component: UserSettings } - ] - - const router = new VueRouter({ - mode: 'history', - routes, - scrollBehavior: (to, from, savedPosition) => { - if (to.matched.some(m => m.meta.dontScroll)) { - return false - } - return savedPosition || { x: 0, y: 0 } - } + .then((res) => res.json()) + .catch((err) => { + console.warn('Failed to load static/config.json, continuing without it.') + console.warn(err) + return {} }) + .then((staticConfig) => { + // This takes static config and overrides properties that are present in apiConfig + var config = Object.assign({}, staticConfig, apiConfig) + + var theme = (config.theme) + var background = (config.background) + var logo = (config.logo) + var logoMask = (typeof config.logoMask === 'undefined' ? true : config.logoMask) + var logoMargin = (typeof config.logoMargin === 'undefined' ? 0 : config.logoMargin) + var redirectRootNoLogin = (config.redirectRootNoLogin) + var redirectRootLogin = (config.redirectRootLogin) + var chatDisabled = (config.chatDisabled) + var showInstanceSpecificPanel = (config.showInstanceSpecificPanel) + var scopeOptionsEnabled = (config.scopeOptionsEnabled) + var formattingOptionsEnabled = (config.formattingOptionsEnabled) + var collapseMessageWithSubject = (config.collapseMessageWithSubject) + + store.dispatch('setInstanceOption', { name: 'theme', value: theme }) + store.dispatch('setInstanceOption', { name: 'background', value: background }) + store.dispatch('setInstanceOption', { name: 'logo', value: logo }) + store.dispatch('setInstanceOption', { name: 'logoMask', value: logoMask }) + store.dispatch('setInstanceOption', { name: 'logoMargin', value: logoMargin }) + store.dispatch('setInstanceOption', { name: 'redirectRootNoLogin', value: redirectRootNoLogin }) + store.dispatch('setInstanceOption', { name: 'redirectRootLogin', value: redirectRootLogin }) + store.dispatch('setInstanceOption', { name: 'showInstanceSpecificPanel', value: showInstanceSpecificPanel }) + store.dispatch('setInstanceOption', { name: 'scopeOptionsEnabled', value: scopeOptionsEnabled }) + store.dispatch('setInstanceOption', { name: 'formattingOptionsEnabled', value: formattingOptionsEnabled }) + store.dispatch('setInstanceOption', { name: 'collapseMessageWithSubject', value: collapseMessageWithSubject }) + if (chatDisabled) { + store.dispatch('disableChat') + } - /* eslint-disable no-new */ - new Vue({ - router, - store, - i18n, - el: '#app', - render: h => h(App) + const routes = [ + { name: 'root', + path: '/', + redirect: to => { + return (store.state.users.currentUser + ? store.state.instance.redirectRootLogin + : store.state.instance.redirectRootNoLogin) || '/main/all' + }}, + { path: '/main/all', component: PublicAndExternalTimeline }, + { path: '/main/public', component: PublicTimeline }, + { path: '/main/friends', component: FriendsTimeline }, + { path: '/tag/:tag', component: TagTimeline }, + { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, + { name: 'user-profile', path: '/users/:id', component: UserProfile }, + { name: 'mentions', path: '/:username/mentions', component: Mentions }, + { name: 'settings', path: '/settings', component: Settings }, + { name: 'registration', path: '/registration', component: Registration }, + { name: 'registration', path: '/registration/:token', component: Registration }, + { name: 'friend-requests', path: '/friend-requests', component: FollowRequests }, + { name: 'user-settings', path: '/user-settings', component: UserSettings } + ] + + const router = new VueRouter({ + mode: 'history', + routes, + 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) + }) }) - }) }) window.fetch('/static/terms-of-service.html') .then((res) => res.text()) .then((html) => { - store.dispatch('setOption', { name: 'tos', value: html }) + store.dispatch('setInstanceOption', { name: 'tos', value: html }) }) window.fetch('/api/pleroma/emoji.json') @@ -188,11 +184,11 @@ window.fetch('/api/pleroma/emoji.json') const emoji = Object.keys(values).map((key) => { return { shortcode: key, image_url: values[key] } }) - store.dispatch('setOption', { name: 'customEmoji', value: emoji }) - store.dispatch('setOption', { name: 'pleromaBackend', value: true }) + store.dispatch('setInstanceOption', { name: 'customEmoji', value: emoji }) + store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: true }) }, (failure) => { - store.dispatch('setOption', { name: 'pleromaBackend', value: false }) + store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: false }) } ), (error) => console.log(error) @@ -204,24 +200,24 @@ window.fetch('/static/emoji.json') const emoji = Object.keys(values).map((key) => { return { shortcode: key, image_url: false, 'utf': values[key] } }) - store.dispatch('setOption', { name: 'emoji', value: emoji }) + store.dispatch('setInstanceOption', { name: 'emoji', value: emoji }) }) window.fetch('/instance/panel.html') .then((res) => res.text()) .then((html) => { - store.dispatch('setOption', { name: 'instanceSpecificPanelContent', value: html }) + store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html }) }) window.fetch('/nodeinfo/2.0.json') .then((res) => res.json()) .then((data) => { const metadata = data.metadata - store.dispatch('setOption', { name: 'mediaProxyAvailable', value: data.metadata.mediaProxy }) - store.dispatch('setOption', { name: 'chatAvailable', value: data.metadata.chat }) - store.dispatch('setOption', { name: 'gopherAvailable', value: data.metadata.gopher }) + store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: data.metadata.mediaProxy }) + store.dispatch('setInstanceOption', { name: 'chatAvailable', value: data.metadata.chat }) + store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: data.metadata.gopher }) const suggestions = metadata.suggestions - store.dispatch('setOption', { name: 'suggestionsEnabled', value: suggestions.enabled }) - store.dispatch('setOption', { name: 'suggestionsWeb', value: suggestions.web }) + store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled }) + store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web }) }) diff --git a/src/modules/config.js b/src/modules/config.js @@ -4,7 +4,6 @@ import StyleSetter from '../services/style_setter/style_setter.js' const browserLocale = (window.navigator.language || 'en').split('-')[0] const defaultState = { - name: 'Pleroma FE', colors: {}, collapseMessageWithSubject: false, hideAttachments: false, @@ -45,18 +44,12 @@ const config = { } }, actions: { - setPageTitle ({state}, option = '') { - document.title = `${option} ${state.name}` - }, setHighlight ({ commit, dispatch }, { user, color, type }) { commit('setHighlight', {user, color, type}) }, setOption ({ commit, dispatch }, { name, value }) { commit('setOption', {name, value}) switch (name) { - case 'name': - dispatch('setPageTitle') - break case 'theme': StyleSetter.setPreset(value, commit) break diff --git a/src/modules/instance.js b/src/modules/instance.js @@ -0,0 +1,63 @@ +import { set } from 'vue' +import StyleSetter from '../services/style_setter/style_setter.js' + +const defaultState = { + // Stuff from static/config.json and apiConfig + name: 'Pleroma FE', + registrationOpen: true, + textlimit: 5000, + server: 'http://localhost:4040/', + theme: 'pleroma-dark', + background: '/static/aurora_borealis.jpg', + logo: '/static/logo.png', + logoMask: true, + logoMargin: '.2em', + redirectRootNoLogin: '/main/all', + redirectRootLogin: '/main/friends', + showInstanceSpecificPanel: false, + scopeOptionsEnabled: true, + formattingOptionsEnabled: false, + collapseMessageWithSubject: false, + disableChat: false, + + // Nasty stuff + pleromaBackend: true, + emoji: [], + customEmoji: [], + + // Feature-set, apparently, not everything here is reported... + mediaProxyAvailable: false, + chatAvailable: false, + gopherAvailable: false, + suggestionsEnabled: false, + suggestionsWeb: '', + + // Html stuff + instanceSpecificPanelContent: '', + tos: '' +} + +const instance = { + state: defaultState, + mutations: { + setInstanceOption (state, { name, value }) { + if (typeof value !== 'undefined') { + set(state, name, value) + } + } + }, + actions: { + setInstanceOption ({ commit, dispatch }, { name, value }) { + commit('setInstanceOption', {name, value}) + switch (name) { + case 'name': + dispatch('setPageTitle') + break + case 'theme': + StyleSetter.setPreset(value, commit) + } + } + } +} + +export default instance diff --git a/src/modules/interface.js b/src/modules/interface.js @@ -0,0 +1,36 @@ +import { set, delete as del } from 'vue' + +const defaultState = { + settings: { + currentSaveStateNotice: null, + noticeClearTimeout: null + } +} + +const interfaceMod = { + state: defaultState, + mutations: { + settingsSaved (state, { success, error }) { + if (success) { + if (state.noticeClearTimeout) { + clearTimeout(state.noticeClearTimeout) + } + set(state.settings, 'currentSaveStateNotice', { error: false, data: success }) + set(state.settings, 'noticeClearTimeout', + setTimeout(() => del(state.settings, 'currentSaveStateNotice'), 2000)) + } else { + set(state.settings, 'currentSaveStateNotice', { error: true, errorData: error }) + } + } + }, + actions: { + setPageTitle ({ rootState }, option = '') { + document.title = `${option} ${rootState.instance.name}` + }, + settingsSaved ({ commit, dispatch }, { success, error }) { + commit('settingsSaved', { success, error }) + } + } +} + +export default interfaceMod diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js @@ -133,8 +133,6 @@ const updateBanner = ({credentials, params}) => { const updateProfile = ({credentials, params}) => { let url = PROFILE_UPDATE_URL - console.log(params) - const form = new FormData() each(params, (value, key) => { @@ -335,7 +333,14 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&') url += `?${queryString}` - return fetch(url, { headers: authHeaders(credentials) }).then((data) => data.json()) + return fetch(url, { headers: authHeaders(credentials) }) + .then((data) => { + if (data.ok) { + return data + } + throw new Error('Error fetching timeline') + }) + .then((data) => data.json()) } const verifyCredentials = (user) => {