diff options
Diffstat (limited to 'src')
66 files changed, 341 insertions, 348 deletions
@@ -46,7 +46,7 @@ export default { this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) window.addEventListener('resize', this.updateMobileState) }, - destroyed () { + unmounted () { window.removeEventListener('resize', this.updateMobileState) }, computed: { diff --git a/src/App.vue b/src/App.vue index eb65b548..3532f898 100644 --- a/src/App.vue +++ b/src/App.vue @@ -59,7 +59,7 @@ <UserReportingModal /> <PostStatusModal /> <SettingsModal /> - <portal-target name="modal" /> + <div id="modal" /> <GlobalNoticeList /> </div> </template> diff --git a/src/boot/after_store.js b/src/boot/after_store.js index c4a0a800..5ddc7465 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -1,7 +1,13 @@ -import Vue from 'vue' -import VueRouter from 'vue-router' -import routes from './routes' +import { createApp, configureCompat } from 'vue' +import { createRouter, createWebHistory } from 'vue-router' +import vClickOutside from 'click-outside-vue3' + +import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome' + import App from '../App.vue' +import routes from './routes' +import VBodyScrollLock from 'src/directives/body_scroll_lock' + import { windowWidth } from '../services/window_utils/window_utils' import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js' import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' @@ -9,6 +15,11 @@ import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import { applyTheme } from '../services/style_setter/style_setter.js' import FaviconService from '../services/favicon_service/favicon_service.js' +// disable compat for certain features +configureCompat({ + COMPONENT_V_MODEL: false +}) + let staticInitialResults = null const parsedInitialResults = () => { @@ -367,25 +378,32 @@ const afterStoreSetup = async ({ store, i18n }) => { getTOS({ store }) getStickers({ store }) - const router = new VueRouter({ - mode: 'history', + const router = createRouter({ + history: createWebHistory(), routes: routes(store), scrollBehavior: (to, _from, savedPosition) => { if (to.matched.some(m => m.meta.dontScroll)) { return false } - return savedPosition || { x: 0, y: 0 } + return savedPosition || { left: 0, top: 0 } } }) - /* eslint-disable no-new */ - return new Vue({ - router, - store, - i18n, - el: '#app', - render: h => h(App) - }) + const app = createApp(App) + + app.use(router) + app.use(store) + app.use(i18n) + + app.use(vClickOutside) + app.use(VBodyScrollLock) + + app.component('FAIcon', FontAwesomeIcon) + app.component('FALayers', FontAwesomeLayers) + + app.mount('#app') + + return app } export default afterStoreSetup diff --git a/src/components/auth_form/auth_form.js b/src/components/auth_form/auth_form.js index e9a6e2d5..a86a3dca 100644 --- a/src/components/auth_form/auth_form.js +++ b/src/components/auth_form/auth_form.js @@ -1,3 +1,4 @@ +import { h, resolveComponent } from 'vue' import LoginForm from '../login_form/login_form.vue' import MFARecoveryForm from '../mfa_form/recovery_form.vue' import MFATOTPForm from '../mfa_form/totp_form.vue' @@ -5,8 +6,8 @@ import { mapGetters } from 'vuex' const AuthForm = { name: 'AuthForm', - render (createElement) { - return createElement('component', { is: this.authForm }) + render () { + return h(resolveComponent(this.authForm)) }, computed: { authForm () { diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue index 53deb1df..eeca7828 100644 --- a/src/components/basic_user_card/basic_user_card.vue +++ b/src/components/basic_user_card/basic_user_card.vue @@ -4,7 +4,7 @@ <UserAvatar class="avatar" :user="user" - @click.prevent.native="toggleUserExpanded" + @click.prevent="toggleUserExpanded" /> </router-link> <div diff --git a/src/components/bookmark_timeline/bookmark_timeline.js b/src/components/bookmark_timeline/bookmark_timeline.js index 64b69e5d..5ac43d90 100644 --- a/src/components/bookmark_timeline/bookmark_timeline.js +++ b/src/components/bookmark_timeline/bookmark_timeline.js @@ -9,7 +9,7 @@ const Bookmarks = { components: { Timeline }, - destroyed () { + unmounted () { this.$store.commit('clearTimeline', { timeline: 'bookmarks' }) } } diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index b54f5fb2..aef11712 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -57,7 +57,7 @@ const Chat = { }) this.setChatLayout() }, - destroyed () { + unmounted () { window.removeEventListener('scroll', this.handleScroll) window.removeEventListener('resize', this.handleLayoutChange) this.unsetChatLayout() diff --git a/src/components/chat/chat.vue b/src/components/chat/chat.vue index 94a0097c..493c5d5a 100644 --- a/src/components/chat/chat.vue +++ b/src/components/chat/chat.vue @@ -26,73 +26,71 @@ /> </div> </div> - <template> + <div + ref="scrollable" + class="scrollable-message-list" + :style="{ height: scrollableContainerHeight }" + @scroll="handleScroll" + > + <template v-if="!errorLoadingChat"> + <ChatMessage + v-for="chatViewItem in chatViewItems" + :key="chatViewItem.id" + :author="recipient" + :chat-view-item="chatViewItem" + :hovered-message-chain="chatViewItem.messageChainId === hoveredMessageChainId" + @hover="onMessageHover" + /> + </template> <div - ref="scrollable" - class="scrollable-message-list" - :style="{ height: scrollableContainerHeight }" - @scroll="handleScroll" + v-else + class="chat-loading-error" > - <template v-if="!errorLoadingChat"> - <ChatMessage - v-for="chatViewItem in chatViewItems" - :key="chatViewItem.id" - :author="recipient" - :chat-view-item="chatViewItem" - :hovered-message-chain="chatViewItem.messageChainId === hoveredMessageChainId" - @hover="onMessageHover" - /> - </template> - <div - v-else - class="chat-loading-error" - > - <div class="alert error"> - {{ $t('chats.error_loading_chat') }} - </div> + <div class="alert error"> + {{ $t('chats.error_loading_chat') }} </div> </div> + </div> + <div + ref="footer" + class="panel-body footer" + > <div - ref="footer" - class="panel-body footer" + class="jump-to-bottom-button" + :class="{ 'visible': jumpToBottomButtonVisible }" + @click="scrollDown({ behavior: 'smooth' })" > - <div - class="jump-to-bottom-button" - :class="{ 'visible': jumpToBottomButtonVisible }" - @click="scrollDown({ behavior: 'smooth' })" - > - <span> - <FAIcon icon="chevron-down" /> - <div - v-if="newMessageCount" - class="badge badge-notification unread-chat-count unread-message-count" - > - {{ newMessageCount }} - </div> - </span> - </div> - <PostStatusForm - :disable-subject="true" - :disable-scope-selector="true" - :disable-notice="true" - :disable-lock-warning="true" - :disable-polls="true" - :disable-sensitivity-checkbox="true" - :disable-submit="errorLoadingChat || !currentChat" - :disable-preview="true" - :optimistic-posting="true" - :post-handler="sendMessage" - :submit-on-enter="!mobileLayout" - :preserve-focus="!mobileLayout" - :auto-focus="!mobileLayout" - :placeholder="formPlaceholder" - :file-limit="1" - max-height="160" - emoji-picker-placement="top" - @resize="handleResize" - /> + <span> + <FAIcon icon="chevron-down" /> + <div + v-if="newMessageCount" + class="badge badge-notification unread-chat-count unread-message-count" + > + {{ newMessageCount }} + </div> + </span> </div> - </template> + <PostStatusForm + :disable-subject="true" + :disable-scope-selector="true" + :disable-notice="true" + :disable-lock-warning="true" + :disable-polls="true" + :disable-sensitivity-checkbox="true" + :disable-submit="errorLoadingChat || !currentChat" + :disable-preview="true" + :optimistic-posting="true" + :post-handler="sendMessage" + :submit-on-enter="!mobileLayout" + :preserve-focus="!mobileLayout" + :auto-focus="!mobileLayout" + :placeholder="formPlaceholder" + :file-limit="1" + max-height="160" + emoji-picker-placement="top" + @resize="handleResize" + /> + </div> </div> </div> </div> diff --git a/src/components/chat_title/chat_title.js b/src/components/chat_title/chat_title.js index edfbe7a4..f6e299ad 100644 --- a/src/components/chat_title/chat_title.js +++ b/src/components/chat_title/chat_title.js @@ -1,11 +1,12 @@ -import Vue from 'vue' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import UserAvatar from '../user_avatar/user_avatar.vue' +import RichContent from 'src/components/rich_content/rich_content.jsx' -export default Vue.component('chat-title', { +export default { name: 'ChatTitle', components: { - UserAvatar + UserAvatar, + RichContent }, props: [ 'user', 'withAvatar' @@ -23,4 +24,4 @@ export default Vue.component('chat-title', { return generateProfileLink(user.id, user.screen_name) } } -}) +} diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue index d28c2cfd..ff2b4e77 100644 --- a/src/components/checkbox/checkbox.vue +++ b/src/components/checkbox/checkbox.vue @@ -7,7 +7,7 @@ type="checkbox" :disabled="disabled" :checked="checked" - :indeterminate.prop="indeterminate" + :indeterminate="indeterminate" @change="$emit('change', $event.target.checked)" > <i class="checkbox-indicator" /> diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue index 8fb16113..81462979 100644 --- a/src/components/color_input/color_input.vue +++ b/src/components/color_input/color_input.vue @@ -14,25 +14,25 @@ :checked="present" :disabled="disabled" class="opt" - @change="$emit('input', typeof value === 'undefined' ? fallback : undefined)" + @change="$emit('update:modelValue', typeof value === 'undefined' ? fallback : undefined)" /> <div class="input color-input-field"> <input :id="name + '-t'" class="textColor unstyled" type="text" - :value="value || fallback" + :value="modelValue || fallback" :disabled="!present || disabled" - @input="$emit('input', $event.target.value)" + @input="$emit('update:modelValue', $event.target.value)" > <input v-if="validColor" :id="name" class="nativeColor unstyled" type="color" - :value="value || fallback" + :value="modelValue || fallback" :disabled="!present || disabled" - @input="$emit('input', $event.target.value)" + @input="$emit('update:modelValue', $event.target.value)" > <div v-if="transparentColor" @@ -67,7 +67,7 @@ export default { }, // Color value, should be required but vue cannot tell the difference // between "property missing" and "property set to undefined" - value: { + modelValue: { required: false, type: String, default: undefined @@ -93,16 +93,16 @@ export default { }, computed: { present () { - return typeof this.value !== 'undefined' + return typeof this.modelValue !== 'undefined' }, validColor () { - return hex2rgb(this.value || this.fallback) + return hex2rgb(this.modelValue || this.fallback) }, transparentColor () { - return this.value === 'transparent' + return this.modelValue === 'transparent' }, computedColor () { - return this.value && this.value.startsWith('--') + return this.modelValue && this.modelValue.startsWith('--') } } } diff --git a/src/components/desktop_nav/desktop_nav.vue b/src/components/desktop_nav/desktop_nav.vue index 304baf9d..bab3ca81 100644 --- a/src/components/desktop_nav/desktop_nav.vue +++ b/src/components/desktop_nav/desktop_nav.vue @@ -34,7 +34,7 @@ <search-bar v-if="currentUser || !privateMode" @toggled="onSearchBarToggled" - @click.stop.native + @click.stop /> <button class="button-unstyled nav-icon" diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js index 902ec384..5b51da18 100644 --- a/src/components/emoji_input/emoji_input.js +++ b/src/components/emoji_input/emoji_input.js @@ -31,6 +31,7 @@ library.add( */ const EmojiInput = { + emits: ['update:modelValue'], props: { suggest: { /** @@ -57,8 +58,7 @@ const EmojiInput = { required: true, type: Function }, - // TODO VUE3: change to modelValue, change 'input' event to 'input' - value: { + modelValue: { /** * Used for v-model */ @@ -137,8 +137,8 @@ const EmojiInput = { return (this.wordAtCaret || {}).word || '' }, wordAtCaret () { - if (this.value && this.caret) { - const word = Completion.wordAtPosition(this.value, this.caret - 1) || {} + if (this.modelValue && this.caret) { + const word = Completion.wordAtPosition(this.modelValue, this.caret - 1) || {} return word } } @@ -225,13 +225,13 @@ const EmojiInput = { } }, replace (replacement) { - const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement) - this.$emit('input', newValue) + const newValue = Completion.replaceWord(this.modelValue, this.wordAtCaret, replacement) + this.$emit('update:modelValue', newValue) this.caret = 0 }, insert ({ insertion, keepOpen, surroundingSpace = true }) { - const before = this.value.substring(0, this.caret) || '' - const after = this.value.substring(this.caret) || '' + const before = this.modelValue.substring(0, this.caret) || '' + const after = this.modelValue.substring(this.caret) || '' /* Using a bit more smart approach to padding emojis with spaces: * - put a space before cursor if there isn't one already, unless we @@ -259,7 +259,7 @@ const EmojiInput = { after ].join('') this.keepOpen = keepOpen - this.$emit('input', newValue) + this.$emit('update:modelValue', newValue) const position = this.caret + (insertion + spaceAfter + spaceBefore).length if (!keepOpen) { this.input.focus() @@ -278,8 +278,8 @@ const EmojiInput = { if (len > 0 || suggestion) { const chosenSuggestion = suggestion || this.suggestions[this.highlighted] const replacement = chosenSuggestion.replacement - const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement) - this.$emit('input', newValue) + const newValue = Completion.replaceWord(this.modelValue, this.wordAtCaret, replacement) + this.$emit('update:modelValue', newValue) this.highlighted = 0 const position = this.wordAtCaret.start + replacement.length @@ -455,7 +455,7 @@ const EmojiInput = { this.showPicker = false this.setCaret(e) this.resize() - this.$emit('input', e.target.value) + this.$emit('update:modelValue', e.target.value) }, onClickInput (e) { this.showPicker = false diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index 2716d93f..6b589079 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -1,3 +1,4 @@ +import { defineAsyncComponent } from 'vue' import Checkbox from '../checkbox/checkbox.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -57,7 +58,7 @@ const EmojiPicker = { } }, components: { - StickerPicker: () => import('../sticker_picker/sticker_picker.vue'), + StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')), Checkbox }, methods: { @@ -79,7 +80,7 @@ const EmojiPicker = { }, highlight (key) { const ref = this.$refs['group-' + key] - const top = ref[0].offsetTop + const top = ref.offsetTop this.setShowStickers(false) this.activeGroup = key this.$nextTick(() => { @@ -96,7 +97,7 @@ const EmojiPicker = { } }, triggerLoadMore (target) { - const ref = this.$refs['group-end-custom'][0] + const ref = this.$refs['group-end-custom'] if (!ref) return const bottom = ref.offsetTop + ref.offsetHeight @@ -119,7 +120,7 @@ const EmojiPicker = { this.$nextTick(() => { this.emojisView.forEach(group => { const ref = this.$refs['group-' + group.id] - if (ref[0].offsetTop <= top) { + if (ref.offsetTop <= top) { this.activeGroup = group.id } }) diff --git a/src/components/exporter/exporter.js b/src/components/exporter/exporter.js index 51912ac3..fc75372e 100644 --- a/src/components/exporter/exporter.js +++ b/src/components/exporter/exporter.js @@ -15,18 +15,8 @@ const Exporter = { type: String, default: 'export.csv' }, - exportButtonLabel: { - type: String, - default () { - return this.$t('exporter.export') - } - }, - processingMessage: { - type: String, - default () { - return this.$t('exporter.processing') - } - } + exportButtonLabel: { type: String }, + processingMessage: { type: String } }, data () { return { diff --git a/src/components/exporter/exporter.vue b/src/components/exporter/exporter.vue index d6a03088..79defdf6 100644 --- a/src/components/exporter/exporter.vue +++ b/src/components/exporter/exporter.vue @@ -7,14 +7,14 @@ spin /> - <span>{{ processingMessage }}</span> + <span>{{ processingMessage || $t('exporter.processing') }}</span> </div> <button v-else class="btn button-default" @click="process" > - {{ exportButtonLabel }} + {{ exportButtonLabel || $t('exporter.export') }} </button> </div> </template> diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js index 137ef9c0..24bad937 100644 --- a/src/components/font_control/font_control.js +++ b/src/components/font_control/font_control.js @@ -1,4 +1,4 @@ -import { set } from 'vue' +import { set } from 'lodash' import Select from '../select/select.vue' export default { diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue index 29605084..ddda218b 100644 --- a/src/components/font_control/font_control.vue +++ b/src/components/font_control/font_control.vue @@ -15,7 +15,7 @@ class="opt exlcude-disabled" type="checkbox" :checked="present" - @input="$emit('input', typeof value === 'undefined' ? fallback : undefined)" + @input="$emit('update:modelValue', typeof value === 'undefined' ? fallback : undefined)" > <label v-if="typeof fallback !== 'undefined'" diff --git a/src/components/importer/importer.js b/src/components/importer/importer.js index 59f9beb1..da86a223 100644 --- a/src/components/importer/importer.js +++ b/src/components/importer/importer.js @@ -15,24 +15,9 @@ const Importer = { type: Function, required: true }, - submitButtonLabel: { - type: String, - default () { - return this.$t('importer.submit') - } - }, - successMessage: { - type: String, - default () { - return this.$t('importer.success') - } - }, - errorMessage: { - type: String, - default () { - return this.$t('importer.error') - } - } + submitButtonLabel: { type: String }, + successMessage: { type: String }, + errorMessage: { type: String } }, data () { return { diff --git a/src/components/importer/importer.vue b/src/components/importer/importer.vue index 210823f5..ddc3010e 100644 --- a/src/components/importer/importer.vue +++ b/src/components/importer/importer.vue @@ -18,21 +18,21 @@ class="btn button-default" @click="submit" > - {{ submitButtonLabel }} + {{ submitButtonLabel || $t('importer.submit') }} </button> <div v-if="success"> <FAIcon icon="times" @click="dismiss" /> - <p>{{ successMessage }}</p> + <p>{{ successMessage || $t('importer.success') }}</p> </div> <div v-else-if="error"> <FAIcon icon="times" @click="dismiss" /> - <p>{{ errorMessage }}</p> + <p>{{ errorMessage || $t('importer.error') }}</p> </div> </div> </template> diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 01a90377..ff993664 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -142,7 +142,7 @@ const MediaModal = { document.addEventListener('keyup', this.handleKeyupEvent) document.addEventListener('keydown', this.handleKeydownEvent) }, - destroyed () { + unmounted () { window.removeEventListener('popstate', this.hide) document.removeEventListener('keyup', this.handleKeyupEvent) document.removeEventListener('keydown', this.handleKeydownEvent) diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.js b/src/components/mobile_post_status_button/mobile_post_status_button.js index d27fb3b8..4866ac57 100644 --- a/src/components/mobile_post_status_button/mobile_post_status_button.js +++ b/src/components/mobile_post_status_button/mobile_post_status_button.js @@ -29,7 +29,7 @@ const MobilePostStatusButton = { } window.addEventListener('resize', this.handleOSK) }, - destroyed () { + unmounted () { if (this.autohideFloatingPostButton) { this.deactivateFloatingPostButtonAutohide() } diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue index 96476abe..96b8c3a3 100644 --- a/src/components/moderation_tools/moderation_tools.vue +++ b/src/components/moderation_tools/moderation_tools.vue @@ -132,7 +132,7 @@ </button> </template> </Popover> - <portal to="modal"> + <teleport to="#modal"> <DialogModal v-if="showDeleteUserDialog" :on-cancel="deleteUserDialog.bind(this, false)" @@ -156,7 +156,7 @@ </button> </template> </DialogModal> - </portal> + </teleport> </div> </template> diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index c7305b78..c03ebc89 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -104,9 +104,9 @@ </span> <span v-if="notification.type === 'pleroma:emoji_reaction'"> <small> - <i18n path="notifications.reacted_with"> + <i18n-t keypath="notifications.reacted_with"> <span class="emoji-reaction-emoji">{{ notification.emoji }}</span> - </i18n> + </i18n-t> </small> </span> </div> diff --git a/src/components/opacity_input/opacity_input.vue b/src/components/opacity_input/opacity_input.vue index 3cc3942b..e1d31fc2 100644 --- a/src/components/opacity_input/opacity_input.vue +++ b/src/components/opacity_input/opacity_input.vue @@ -14,7 +14,7 @@ :checked="present" :disabled="disabled" class="opt" - @change="$emit('input', !present ? fallback : undefined)" + @change="$emit('update:modelValue', !present ? fallback : undefined)" /> <input :id="name" @@ -25,7 +25,7 @@ max="1" min="0" step=".05" - @input="$emit('input', $event.target.value)" + @input="$emit('update:modelValue', $event.target.value)" > </div> </template> diff --git a/src/components/poll/poll.js b/src/components/poll/poll.js index a69b7886..eda1733a 100644 --- a/src/components/poll/poll.js +++ b/src/components/poll/poll.js @@ -21,7 +21,7 @@ export default { } this.$store.dispatch('trackPoll', this.pollId) }, - destroyed () { + unmounted () { this.$store.dispatch('untrackPoll', this.pollId) }, computed: { diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue index 63b44e4f..e949c653 100644 --- a/src/components/poll/poll.vue +++ b/src/components/poll/poll.vue @@ -71,13 +71,13 @@ {{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }} · </template> </div> - <i18n :path="expired ? 'polls.expired' : 'polls.expires_in'"> + <i18n-t :keypath="expired ? 'polls.expired' : 'polls.expires_in'"> <Timeago :time="expiresAt" :auto-update="60" :now-threshold="0" /> - </i18n> + </i18n-t> </div> </div> </template> diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js index 6ccf32f0..a30a37c9 100644 --- a/src/components/popover/popover.js +++ b/src/components/popover/popover.js @@ -178,7 +178,7 @@ const Popover = { created () { document.addEventListener('click', this.onClickOutside) }, - destroyed () { + unmounted () { document.removeEventListener('click', this.onClickOutside) this.hidePopover() } diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index 2e0980a2..45ec9656 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -18,9 +18,9 @@ <FAIcon :icon="uploadFileLimitReached ? 'ban' : 'upload'" /> </div> <div class="form-group"> - <i18n + <i18n-t v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private' && !disableLockWarning" - path="post_status.account_not_locked_warning" + keypath="post_status.account_not_locked_warning" tag="p" class="visibility-notice" > @@ -30,7 +30,7 @@ > {{ $t('post_status.account_not_locked_warning_link') }} </button> - </i18n> + </i18n-t> <p v-if="!hideScopeNotice && newStatus.visibility === 'public'" class="visibility-notice notice-dismissible" diff --git a/src/components/public_and_external_timeline/public_and_external_timeline.js b/src/components/public_and_external_timeline/public_and_external_timeline.js index cbd4491b..bfcce6ae 100644 --- a/src/components/public_and_external_timeline/public_and_external_timeline.js +++ b/src/components/public_and_external_timeline/public_and_external_timeline.js @@ -9,7 +9,7 @@ const PublicAndExternalTimeline = { created () { this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' }) }, - destroyed () { + unmounted () { this.$store.dispatch('stopFetchingTimeline', 'publicAndExternal') } } diff --git a/src/components/public_timeline/public_timeline.js b/src/components/public_timeline/public_timeline.js index 66c40d3a..30693544 100644 --- a/src/components/public_timeline/public_timeline.js +++ b/src/components/public_timeline/public_timeline.js @@ -9,7 +9,7 @@ const PublicTimeline = { created () { this.$store.dispatch('startFetchingTimeline', { timeline: 'public' }) }, - destroyed () { + unmounted () { this.$store.dispatch('stopFetchingTimeline', 'public') } diff --git a/src/components/range_input/range_input.vue b/src/components/range_input/range_input.vue index 5857a5c1..82681809 100644 --- a/src/components/range_input/range_input.vue +++ b/src/components/range_input/range_input.vue @@ -15,7 +15,7 @@ class="opt" type="checkbox" :checked="present" - @input="$emit('input', !present ? fallback : undefined)" + @input="$emit('update:modelValue', !present ? fallback : undefined)" > <label v-if="typeof fallback !== 'undefined'" @@ -31,7 +31,7 @@ :max="max || hardMax || 100" :min="min || hardMin || 0" :step="step || 1" - @input="$emit('input', $event.target.value)" + @input="$emit('update:modelValue', $event.target.value)" > <input :id="name" @@ -42,7 +42,7 @@ :max="hardMax" :min="hardMin" :step="step || 1" - @input="$emit('input', $event.target.value)" + @input="$emit('update:modelValue', $event.target.value)" > </div> </template> diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js index 1ac8e8be..531cc480 100644 --- a/src/components/registration/registration.js +++ b/src/components/registration/registration.js @@ -1,9 +1,9 @@ -import { validationMixin } from 'vuelidate' -import { required, requiredIf, sameAs } from 'vuelidate/lib/validators' +import useVuelidate from '@vuelidate/core' +import { required, requiredIf, sameAs } from '@vuelidate/validators' import { mapActions, mapState } from 'vuex' const registration = { - mixins: [validationMixin], + setup () { return { v$: useVuelidate() } }, data: () => ({ user: { email: '', diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx index 46bc661a..97c51189 100644 --- a/src/components/rich_content/rich_content.jsx +++ b/src/components/rich_content/rich_content.jsx @@ -1,4 +1,4 @@ -import Vue from 'vue' +import { h } from 'vue' import { unescape, flattenDeep } from 'lodash' import { getTagName, processTextForEmoji, getAttrs } from 'src/services/html_converter/utility.service.js' import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js' @@ -27,7 +27,7 @@ import './rich_content.scss' * * Apart from that one small hiccup with emit in render this _should_ be vue3-ready */ -export default Vue.component('RichContent', { +export default { name: 'RichContent', props: { // Original html content @@ -58,7 +58,7 @@ export default Vue.component('RichContent', { } }, // NEVER EVER TOUCH DATA INSIDE RENDER - render (h) { + render () { // Pre-process HTML const { newHtml: html } = preProcessPerLine(this.html, this.greentext) let currentMentions = null // Current chain of mentions, we group all mentions together @@ -76,7 +76,7 @@ export default Vue.component('RichContent', { const renderImage = (tag) => { return <StillImage - {...{ attrs: getAttrs(tag) }} + {...getAttrs(tag)} class="img" /> } @@ -222,7 +222,7 @@ export default Vue.component('RichContent', { attrs.target = '_blank' const newChildren = [...children].reverse().map(processItemReverse).reverse() - return <a {...{ attrs }}> + return <a {...attrs}> { newChildren } </a> } @@ -266,7 +266,7 @@ export default Vue.component('RichContent', { return result } -}) +} const getLinkData = (attrs, children, index) => { const stripTags = (item) => { diff --git a/src/components/select/select.vue b/src/components/select/select.vue index 8d6528ff..56e651e3 100644 --- a/src/components/select/select.vue +++ b/src/components/select/select.vue @@ -23,7 +23,8 @@ <style lang="scss"> @import '../../_variables.scss'; -.Select { +/* TODO fix order of styles */ +label.Select { padding: 0; select { diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue index 1805c77f..ccbb2af9 100644 --- a/src/components/settings_modal/settings_modal.vue +++ b/src/components/settings_modal/settings_modal.vue @@ -11,23 +11,25 @@ {{ $t('settings.settings') }} </span> <transition name="fade"> - <template v-if="currentSaveStateNotice"> - <div - v-if="currentSaveStateNotice.error" - class="alert error" - @click.prevent - > - {{ $t('settings.saving_err') }} - </div> + <div> + <template v-if="currentSaveStateNotice"> + <div + v-if="currentSaveStateNotice.error" + class="alert error" + @click.prevent + > + {{ $t('settings.saving_err') }} + </div> - <div - v-if="!currentSaveStateNotice.error" - class="alert transparent" - @click.prevent - > - {{ $t('settings.saving_ok') }} - </div> - </template> + <div + v-if="!currentSaveStateNotice.error" + class="alert transparent" + @click.prevent + > + {{ $t('settings.saving_ok') }} + </div> + </template> + </div> </transition> <button class="btn button-default" diff --git a/src/components/settings_modal/settings_modal_content.js b/src/components/settings_modal/settings_modal_content.js index 9dcf1b5a..deb77298 100644 --- a/src/components/settings_modal/settings_modal_content.js +++ b/src/components/settings_modal/settings_modal_content.js @@ -1,4 +1,4 @@ -import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js' +import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' import DataImportExportTab from './tabs/data_import_export_tab.vue' import MutesAndBlocksTab from './tabs/mutes_and_blocks_tab.vue' @@ -53,6 +53,9 @@ const SettingsModalContent = { }, open () { return this.$store.state.interface.settingsModalState !== 'hidden' + }, + bodyLock () { + return this.$store.state.interface.settingsModalState === 'visible' } }, methods: { diff --git a/src/components/settings_modal/settings_modal_content.vue b/src/components/settings_modal/settings_modal_content.vue index c9ed2a38..0be76d22 100644 --- a/src/components/settings_modal/settings_modal_content.vue +++ b/src/components/settings_modal/settings_modal_content.vue @@ -4,6 +4,7 @@ class="settings_tab-switcher" :side-tab-bar="true" :scrollable-tabs="true" + :body-scroll-lock="bodyLock" > <div :label="$t('settings.general')" diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js index 40a87b81..6cfeea35 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js @@ -2,7 +2,7 @@ import get from 'lodash/get' import map from 'lodash/map' import reject from 'lodash/reject' import Autosuggest from 'src/components/autosuggest/autosuggest.vue' -import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js' +import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' import BlockCard from 'src/components/block_card/block_card.vue' import MuteCard from 'src/components/mute_card/mute_card.vue' import DomainMuteCard from 'src/components/domain_mute_card/domain_mute_card.vue' diff --git a/src/components/settings_modal/tabs/theme_tab/preview.vue b/src/components/settings_modal/tabs/theme_tab/preview.vue index 7ac7b9d3..36613d20 100644 --- a/src/components/settings_modal/tabs/theme_tab/preview.vue +++ b/src/components/settings_modal/tabs/theme_tab/preview.vue @@ -29,14 +29,14 @@ {{ $t('settings.style.preview.content') }} </h4> - <i18n path="settings.style.preview.text"> + <i18n-t keypath="settings.style.preview.text"> <code style="font-family: var(--postCodeFont)"> {{ $t('settings.style.preview.mono') }} </code> <a style="color: var(--link)"> {{ $t('settings.style.preview.link') }} </a> - </i18n> + </i18n-t> <div class="icons"> <FAIcon @@ -72,15 +72,15 @@ :^) </div> <div class="content"> - <i18n - path="settings.style.preview.fine_print" + <i18n-t + keypath="settings.style.preview.fine_print" tag="span" class="faint" > <a style="color: var(--faintLink)"> {{ $t('settings.style.preview.faint_link') }} </a> - </i18n> + </i18n-t> </div> </div> <div class="separator" /> diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js index 0b6669fc..251f63fd 100644 --- a/src/components/settings_modal/tabs/theme_tab/theme_tab.js +++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.js @@ -1,4 +1,3 @@ -import { set, delete as del } from 'vue' import { rgb2hex, hex2rgb, @@ -34,7 +33,7 @@ import OpacityInput from 'src/components/opacity_input/opacity_input.vue' import ShadowControl from 'src/components/shadow_control/shadow_control.vue' import FontControl from 'src/components/font_control/font_control.vue' import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue' -import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js' +import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' import Checkbox from 'src/components/checkbox/checkbox.vue' import Select from 'src/components/select/select.vue' @@ -320,9 +319,9 @@ export default { }, set (val) { if (val) { - set(this.shadowsLocal, this.shadowSelected, this.currentShadowFallback.map(_ => Object.assign({}, _))) + this.shadowsLocal[this.shadowSelected] = this.currentShadowFallback.map(_ => Object.assign({}, _)) } else { - del(this.shadowsLocal, this.shadowSelected) + delete this.shadowsLocal[this.shadowSelected] } } }, @@ -334,7 +333,7 @@ export default { return this.shadowsLocal[this.shadowSelected] }, set (v) { - set(this.shadowsLocal, this.shadowSelected, v) + this.shadowsLocal[this.shadowSelected] = v } }, themeValid () { @@ -557,7 +556,7 @@ export default { .filter(_ => _.endsWith('ColorLocal') || _.endsWith('OpacityLocal')) .filter(_ => !v1OnlyNames.includes(_)) .forEach(key => { - set(this.$data, key, undefined) + this.$data[key] = undefined }) }, @@ -565,7 +564,7 @@ export default { Object.keys(this.$data) .filter(_ => _.endsWith('RadiusLocal')) .forEach(key => { - set(this.$data, key, undefined) + this.$data[key] = undefined }) }, @@ -573,7 +572,7 @@ export default { Object.keys(this.$data) .filter(_ => _.endsWith('OpacityLocal')) .forEach(key => { - set(this.$data, key, undefined) + this.$data[key] = undefined }) }, diff --git a/src/components/settings_modal/tabs/version_tab.vue b/src/components/settings_modal/tabs/version_tab.vue index d35ff25e..0330d49f 100644 --- a/src/components/settings_modal/tabs/version_tab.vue +++ b/src/components/settings_modal/tabs/version_tab.vue @@ -28,4 +28,4 @@ </div> </div> </template> -<script src="./version_tab.js"> +<script src="./version_tab.js" /> diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 1679834e..f778753f 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -1,6 +1,7 @@ <template> <div v-if="!hideStatus" + ref="root" class="Status" :class="[{ '-focused': isFocused }, { '-conversation': inlineExpanded }]" > @@ -122,7 +123,7 @@ > <router-link :to="userProfileLink" - @click.stop.prevent.capture.native="toggleUserExpanded" + @click.stop.prevent.capture="toggleUserExpanded" > <UserAvatar :bot="botIndicator" @@ -190,7 +191,7 @@ <span v-if="status.visibility" class="visibility-icon" - :title="status.visibility | capitalize" + :title="status.visibility" > <FAIcon fixed-width diff --git a/src/components/status_popover/status_popover.js b/src/components/status_popover/status_popover.js index c47f5631..e0962ccd 100644 --- a/src/components/status_popover/status_popover.js +++ b/src/components/status_popover/status_popover.js @@ -1,6 +1,7 @@ import { find } from 'lodash' import { library } from '@fortawesome/fontawesome-svg-core' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' +import { defineAsyncComponent } from 'vue' library.add( faCircleNotch @@ -22,8 +23,8 @@ const StatusPopover = { } }, components: { - Status: () => import('../status/status.vue'), - Popover: () => import('../popover/popover.vue') + Status: defineAsyncComponent(() => import('../status/status.vue')), + Popover: defineAsyncComponent(() => import('../popover/popover.vue')) }, methods: { enter () { diff --git a/src/components/sticker_picker/sticker_picker.js b/src/components/sticker_picker/sticker_picker.js index 8daf3f07..3a2d3914 100644 --- a/src/components/sticker_picker/sticker_picker.js +++ b/src/components/sticker_picker/sticker_picker.js @@ -1,6 +1,6 @@ /* eslint-env browser */ import statusPosterService from '../../services/status_poster/status_poster.service.js' -import TabSwitcher from '../tab_switcher/tab_switcher.js' +import TabSwitcher from '../tab_switcher/tab_switcher.jsx' const StickerPicker = { components: { diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.jsx index 12aac8e6..d9df42ce 100644 --- a/src/components/tab_switcher/tab_switcher.js +++ b/src/components/tab_switcher/tab_switcher.jsx @@ -1,10 +1,13 @@ -import Vue from 'vue' +// eslint-disable-next-line no-unused +import { h } from 'vue' import { mapState } from 'vuex' import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome' import './tab_switcher.scss' -export default Vue.component('tab-switcher', { +const findFirstUsable = (slots) => slots.findIndex(_ => _.props) + +export default { name: 'TabSwitcher', props: { renderOnlyFocused: { @@ -31,11 +34,16 @@ export default Vue.component('tab-switcher', { required: false, type: Boolean, default: false + }, + bodyScrollLock: { + required: false, + type: Boolean, + default: false } }, data () { return { - active: this.$slots.default.findIndex(_ => _.tag) + active: findFirstUsable(this.$slots.default) } }, computed: { @@ -47,17 +55,14 @@ export default Vue.component('tab-switcher', { return this.active } }, - settingsModalVisible () { - return this.settingsModalState === 'visible' - }, ...mapState({ settingsModalState: state => state.interface.settingsModalState }) }, beforeUpdate () { - const currentSlot = this.$slots.default[this.active] - if (!currentSlot.tag) { - this.active = this.$slots.default.findIndex(_ => _.tag) + const currentSlot = this.slots()[this.active] + if (!currentSlot.props) { + this.active = findFirstUsable(this.slots()) } }, methods: { @@ -67,9 +72,13 @@ export default Vue.component('tab-switcher', { this.setTab(index) } }, + // DO NOT put it to computed, it doesn't work (caching?) + slots () { + return this.$slots.default + }, setTab (index) { if (typeof this.onSwitch === 'function') { - this.onSwitch.call(null, this.$slots.default[index].key) + this.onSwitch.call(null, this.slots()[index].key) } this.active = index if (this.scrollableTabs) { @@ -77,27 +86,28 @@ export default Vue.component('tab-switcher', { } } }, - render (h) { - const tabs = this.$slots.default + render () { + const tabs = this.slots() .map((slot, index) => { - if (!slot.tag) return + const props = slot.props + if (!props) return const classesTab = ['tab', 'button-default'] const classesWrapper = ['tab-wrapper'] if (this.activeIndex === index) { classesTab.push('active') classesWrapper.push('active') } - if (slot.data.attrs.image) { + if (props.image) { return ( <div class={classesWrapper.join(' ')}> <button - disabled={slot.data.attrs.disabled} + disabled={props.disabled} onClick={this.clickTab(index)} class={classesTab.join(' ')} type="button" > - <img src={slot.data.attrs.image} title={slot.data.attrs['image-tooltip']}/> - {slot.data.attrs.label ? '' : slot.data.attrs.label} + <img src={props.image} title={props['image-tooltip']}/> + {props.label ? '' : props.label} </button> </div> ) @@ -105,25 +115,26 @@ export default Vue.component('tab-switcher', { return ( <div class={classesWrapper.join(' ')}> <button - disabled={slot.data.attrs.disabled} + disabled={props.disabled} onClick={this.clickTab(index)} class={classesTab.join(' ')} type="button" > - {!slot.data.attrs.icon ? '' : (<FAIcon class="tab-icon" size="2x" fixed-width icon={slot.data.attrs.icon}/>)} + {!props.icon ? '' : (<FAIcon class="tab-icon" size="2x" fixed-width icon={props.icon}/>)} <span class="text"> - {slot.data.attrs.label} + {props.label} </span> </button> </div> ) }) - const contents = this.$slots.default.map((slot, index) => { - if (!slot.tag) return + const contents = this.slots().map((slot, index) => { + const props = slot.props + if (!props) return const active = this.activeIndex === index const classes = [ active ? 'active' : 'hidden' ] - if (slot.data.attrs.fullHeight) { + if (props.fullHeight) { classes.push('full-height') } const renderSlot = (!this.renderOnlyFocused || active) @@ -134,7 +145,7 @@ export default Vue.component('tab-switcher', { <div class={classes}> { this.sideTabBar - ? <h1 class="mobile-label">{slot.data.attrs.label}</h1> + ? <h1 class="mobile-label">{props.label}</h1> : '' } {renderSlot} @@ -147,10 +158,14 @@ export default Vue.component('tab-switcher', { <div class="tabs"> {tabs} </div> - <div ref="contents" class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')} v-body-scroll-lock={this.settingsModalVisible}> + <div + ref="contents" + class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')} + v-body-scroll-lock={this.bodyScrollLock} + > {contents} </div> </div> ) } -}) +} diff --git a/src/components/tag_timeline/tag_timeline.js b/src/components/tag_timeline/tag_timeline.js index 400c6a4b..bda61ae0 100644 --- a/src/components/tag_timeline/tag_timeline.js +++ b/src/components/tag_timeline/tag_timeline.js @@ -18,7 +18,7 @@ const TagTimeline = { this.$store.dispatch('startFetchingTimeline', { timeline: 'tag', tag: this.tag }) } }, - destroyed () { + unmounted () { this.$store.dispatch('stopFetchingTimeline', 'tag') } } diff --git a/src/components/timeago/timeago.vue b/src/components/timeago/timeago.vue index 55a2dd94..bed29020 100644 --- a/src/components/timeago/timeago.vue +++ b/src/components/timeago/timeago.vue @@ -31,7 +31,7 @@ export default { created () { this.refreshRelativeTimeObject() }, - destroyed () { + unmounted () { clearTimeout(this.interval) }, methods: { diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 04f0e7d6..e896a23c 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -104,7 +104,7 @@ const Timeline = { window.addEventListener('keydown', this.handleShortKey) setTimeout(this.determineVisibleStatuses, 250) }, - destroyed () { + unmounted () { window.removeEventListener('scroll', this.handleScroll) window.removeEventListener('keydown', this.handleShortKey) if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false) diff --git a/src/components/user_list_popover/user_list_popover.js b/src/components/user_list_popover/user_list_popover.js index 32ca2b8d..f0a16c38 100644 --- a/src/components/user_list_popover/user_list_popover.js +++ b/src/components/user_list_popover/user_list_popover.js @@ -1,3 +1,5 @@ +import { defineAsyncComponent } from 'vue' + import { library } from '@fortawesome/fontawesome-svg-core' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' @@ -11,8 +13,8 @@ const UserListPopover = { 'users' ], components: { - Popover: () => import('../popover/popover.vue'), - UserAvatar: () => import('../user_avatar/user_avatar.vue') + Popover: defineAsyncComponent(() => import('../popover/popover.vue')), + UserAvatar: defineAsyncComponent(() => import('../user_avatar/user_avatar.vue')) }, computed: { usersCapped () { diff --git a/src/components/user_panel/user_panel.vue b/src/components/user_panel/user_panel.vue index 5685916a..50949b98 100644 --- a/src/components/user_panel/user_panel.vue +++ b/src/components/user_panel/user_panel.vue @@ -2,7 +2,7 @@ <div class="user-panel"> <div v-if="signedIn" - key="user-panel" + key="user-panel-signed" class="panel panel-default signed-in" > <UserCard diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 7a475609..eeb6ea40 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -3,7 +3,7 @@ import UserCard from '../user_card/user_card.vue' import FollowCard from '../follow_card/follow_card.vue' import Timeline from '../timeline/timeline.vue' import Conversation from '../conversation/conversation.vue' -import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js' +import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' import RichContent from 'src/components/rich_content/rich_content.jsx' import List from '../list/list.vue' import withLoadMore from '../../hocs/with_load_more/with_load_more' @@ -47,7 +47,7 @@ const UserProfile = { this.load(routeParams.name || routeParams.id) this.tab = get(this.$route, 'query.tab', defaultTabKey) }, - destroyed () { + unmounted () { this.stopFetching() }, computed: { diff --git a/src/hocs/with_load_more/with_load_more.js b/src/hocs/with_load_more/with_load_more.jsx index 671b2b6f..6cd198ed 100644 --- a/src/hocs/with_load_more/with_load_more.js +++ b/src/hocs/with_load_more/with_load_more.jsx @@ -1,4 +1,5 @@ -import Vue from 'vue' +// eslint-disable-next-line no-unused +import { h } from 'vue' import isEmpty from 'lodash/isEmpty' import { getComponentProps } from '../../services/component_utils/component_utils' import './with_load_more.scss' @@ -23,7 +24,7 @@ const withLoadMore = ({ const originalProps = Object.keys(getComponentProps(WrappedComponent)) const props = originalProps.filter(v => v !== childPropName).concat(additionalPropNames) - return Vue.component('withLoadMore', { + return { props, data () { return { @@ -39,7 +40,7 @@ const withLoadMore = ({ this.fetchEntries() } }, - destroyed () { + unmounted () { window.removeEventListener('scroll', this.scrollLoad) destroy && destroy(this.$props, this.$store) }, @@ -79,7 +80,7 @@ const withLoadMore = ({ } } }, - render (h) { + render () { const props = { props: { ...this.$props, @@ -106,7 +107,7 @@ const withLoadMore = ({ </div> ) } - }) + } } export default withLoadMore diff --git a/src/hocs/with_subscription/with_subscription.js b/src/hocs/with_subscription/with_subscription.jsx index b1244276..4529399e 100644 --- a/src/hocs/with_subscription/with_subscription.js +++ b/src/hocs/with_subscription/with_subscription.jsx @@ -1,4 +1,5 @@ -import Vue from 'vue' +// eslint-disable-next-line no-unused +import { h } from 'vue' import isEmpty from 'lodash/isEmpty' import { getComponentProps } from '../../services/component_utils/component_utils' import './with_subscription.scss' @@ -22,7 +23,7 @@ const withSubscription = ({ const originalProps = Object.keys(getComponentProps(WrappedComponent)) const props = originalProps.filter(v => v !== childPropName).concat(additionalPropNames) - return Vue.component('withSubscription', { + return { props: [ ...props, 'refresh' // boolean saying to force-fetch data whenever created @@ -59,7 +60,7 @@ const withSubscription = ({ } } }, - render (h) { + render () { if (!this.error && !this.loading) { const props = { props: { @@ -88,7 +89,7 @@ const withSubscription = ({ ) } } - }) + } } export default withSubscription diff --git a/src/i18n/en.json b/src/i18n/en.json index 76a871fb..f3401e61 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -502,14 +502,14 @@ "true": "yes" }, "virtual_scrolling": "Optimize timeline rendering", - "use_at_icon": "Display @ symbol as an icon instead of text", + "use_at_icon": "Display {'@'} symbol as an icon instead of text", "mention_link_display": "Display mention links", - "mention_link_display_short": "always as short names (e.g. @foo)", - "mention_link_display_full_for_remote": "as full names only for remote users (e.g. @foo@example.org)", - "mention_link_display_full": "always as full names (e.g. @foo@example.org)", + "mention_link_display_short": "always as short names (e.g. {'@'}foo)", + "mention_link_display_full_for_remote": "as full names only for remote users (e.g. {'@'}foo{'@'}example.org)", + "mention_link_display_full": "always as full names (e.g. {'@'}foo{'@'}example.org)", "mention_link_show_tooltip": "Show full user names as tooltip for remote users", "mention_link_show_avatar": "Show user avatar beside the link", - "mention_link_fade_domain": "Fade domains (e.g. @example.org in @foo@example.org)", + "mention_link_fade_domain": "Fade domains (e.g. {'@'}example.org in {'@'}foo{'@'}example.org)", "mention_link_bolden_you": "Highlight mention of you when you are mentioned", "fun": "Fun", "greentext": "Meme arrows", diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js index 8ecb66a8..24b835da 100644 --- a/src/lib/persisted_state.js +++ b/src/lib/persisted_state.js @@ -1,6 +1,6 @@ import merge from 'lodash.merge' import localforage from 'localforage' -import { each, get, set } from 'lodash' +import { each, get, set, cloneDeep } from 'lodash' let loaded = false @@ -69,7 +69,7 @@ export default function createPersistedState ({ subscriber(store)((mutation, state) => { try { if (saveImmedeatelyActions.includes(mutation.type)) { - setState(key, reducer(state, paths), storage) + setState(key, reducer(cloneDeep(state), paths), storage) .then(success => { if (typeof success !== 'undefined') { if (mutation.type === 'setOption' || mutation.type === 'setCurrentUser') { diff --git a/src/main.js b/src/main.js index bdf8368b..eacd554c 100644 --- a/src/main.js +++ b/src/main.js @@ -1,6 +1,4 @@ -import Vue from 'vue' -import VueRouter from 'vue-router' -import Vuex from 'vuex' +import { createStore } from 'vuex' import 'custom-event-polyfill' import './lib/event_target_polyfill.js' @@ -22,36 +20,18 @@ import pollsModule from './modules/polls.js' import postStatusModule from './modules/postStatus.js' import chatsModule from './modules/chats.js' -import VueI18n from 'vue-i18n' +import { createI18n } from 'vue-i18n' import createPersistedState from './lib/persisted_state.js' import pushNotifications from './lib/push_notifications_plugin.js' import messages from './i18n/messages.js' -import VueClickOutside from 'v-click-outside' -import PortalVue from 'portal-vue' -import VBodyScrollLock from './directives/body_scroll_lock' - -import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome' - import afterStoreSetup from './boot/after_store.js' const currentLocale = (window.navigator.language || 'en').split('-')[0] -Vue.use(Vuex) -Vue.use(VueRouter) -Vue.use(VueI18n) -Vue.use(VueClickOutside) -Vue.use(PortalVue) -Vue.use(VBodyScrollLock) - -Vue.config.ignoredElements = ['pinch-zoom'] - -Vue.component('FAIcon', FontAwesomeIcon) -Vue.component('FALayers', FontAwesomeLayers) - -const i18n = new VueI18n({ +const i18n = createI18n({ // By default, use the browser locale, we will update it if neccessary locale: 'en', fallbackLocale: 'en', @@ -78,17 +58,18 @@ const persistedStateOptions = { console.error(e) storageError = true } - const store = new Vuex.Store({ + const store = createStore({ modules: { i18n: { getters: { - i18n: () => i18n + i18n: () => i18n.global } }, interface: interfaceModule, instance: instanceModule, - statuses: statusesModule, + // TODO refactor users/statuses modules, they depend on each other users: usersModule, + statuses: statusesModule, api: apiModule, config: configModule, serverSideConfig: serverSideConfigModule, diff --git a/src/modules/chats.js b/src/modules/chats.js index 69d683bd..f28c2603 100644 --- a/src/modules/chats.js +++ b/src/modules/chats.js @@ -1,4 +1,4 @@ -import Vue from 'vue' +import { reactive } from 'vue' import { find, omitBy, orderBy, sumBy } from 'lodash' import chatService from '../services/chat_service/chat_service.js' import { parseChat, parseChatMessage } from '../services/entity_normalizer/entity_normalizer.service.js' @@ -13,8 +13,8 @@ const emptyChatList = () => ({ const defaultState = { chatList: emptyChatList(), chatListFetcher: null, - openedChats: {}, - openedChatMessageServices: {}, + openedChats: reactive({}), + openedChatMessageServices: reactive({}), fetcher: undefined, currentChatId: null, lastReadMessageId: null @@ -137,10 +137,10 @@ const chats = { }, addOpenedChat (state, { _dispatch, chat }) { state.currentChatId = chat.id - Vue.set(state.openedChats, chat.id, chat) + state.openedChats[chat.id] = chat if (!state.openedChatMessageServices[chat.id]) { - Vue.set(state.openedChatMessageServices, chat.id, chatService.empty(chat.id)) + state.openedChatMessageServices[chat.id] = chatService.empty(chat.id) } }, setCurrentChatId (state, { chatId }) { @@ -160,7 +160,7 @@ const chats = { } } else { state.chatList.data.push(updatedChat) - Vue.set(state.chatList.idStore, updatedChat.id, updatedChat) + state.chatList.idStore[updatedChat.id] = updatedChat } }) }, @@ -172,7 +172,7 @@ const chats = { chat.updated_at = updatedChat.updated_at } if (!chat) { state.chatList.data.unshift(updatedChat) } - Vue.set(state.chatList.idStore, updatedChat.id, updatedChat) + state.chatList.idStore[updatedChat.id] = updatedChat }, deleteChat (state, { _dispatch, id, _rootGetters }) { state.chats.data = state.chats.data.filter(conversation => @@ -186,8 +186,8 @@ const chats = { commit('setChatListFetcher', { fetcher: undefined }) for (const chatId in state.openedChats) { chatService.clear(state.openedChatMessageServices[chatId]) - Vue.delete(state.openedChats, chatId) - Vue.delete(state.openedChatMessageServices, chatId) + delete state.openedChats[chatId] + delete state.openedChatMessageServices[chatId] } }, setChatsLoading (state, { value }) { @@ -215,8 +215,8 @@ const chats = { for (const chatId in state.openedChats) { if (currentChatId !== chatId) { chatService.clear(state.openedChatMessageServices[chatId]) - Vue.delete(state.openedChats, chatId) - Vue.delete(state.openedChatMessageServices, chatId) + delete state.openedChats[chatId] + delete state.openedChatMessageServices[chatId] } } }, diff --git a/src/modules/config.js b/src/modules/config.js index ef140bcb..ff5ef270 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -1,4 +1,3 @@ -import { set, delete as del } from 'vue' import { setPreset, applyTheme } from '../services/style_setter/style_setter.js' import messages from '../i18n/messages' @@ -122,14 +121,14 @@ const config = { }, mutations: { setOption (state, { name, value }) { - set(state, name, value) + state[name] = value }, setHighlight (state, { user, color, type }) { const data = this.state.config.highlight[user] if (color || type) { - set(state.highlight, user, { color: color || data.color, type: type || data.type }) + state.highlight[user] = { color: color || data.color, type: type || data.type } } else { - del(state.highlight, user) + delete state.highlight[user] } } }, diff --git a/src/modules/instance.js b/src/modules/instance.js index 79c54096..220463ca 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -1,4 +1,3 @@ -import { set } from 'vue' import { getPreset, applyTheme } from '../services/style_setter/style_setter.js' import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import apiService from '../services/api/api.service.js' @@ -102,7 +101,7 @@ const instance = { mutations: { setInstanceOption (state, { name, value }) { if (typeof value !== 'undefined') { - set(state, name, value) + state[name] = value } }, setKnownDomains (state, domains) { diff --git a/src/modules/interface.js b/src/modules/interface.js index d6db32fd..6509386a 100644 --- a/src/modules/interface.js +++ b/src/modules/interface.js @@ -1,5 +1,3 @@ -import { set, delete as del } from 'vue' - const defaultState = { settingsModalState: 'hidden', settingsModalLoaded: false, @@ -29,11 +27,10 @@ const interfaceMod = { if (state.noticeClearTimeout) { clearTimeout(state.noticeClearTimeout) } - set(state.settings, 'currentSaveStateNotice', { error: false, data: success }) - set(state.settings, 'noticeClearTimeout', - setTimeout(() => del(state.settings, 'currentSaveStateNotice'), 2000)) + state.settings.currentSaveStateNotice = { error: false, data: success } + state.settings.noticeClearTimeout = setTimeout(() => delete state.settings.currentSaveStateNotice, 2000) } else { - set(state.settings, 'currentSaveStateNotice', { error: true, errorData: error }) + state.settings.currentSaveStateNotice = { error: true, errorData: error } } }, setNotificationPermission (state, permission) { diff --git a/src/modules/oauth.js b/src/modules/oauth.js index a2a83450..038bc3f3 100644 --- a/src/modules/oauth.js +++ b/src/modules/oauth.js @@ -1,5 +1,3 @@ -import { delete as del } from 'vue' - const oauth = { state: { clientId: false, @@ -29,7 +27,7 @@ const oauth = { state.userToken = false // state.token is userToken with older name, coming from persistent state // let's clear it as well, since it is being used as a fallback of state.userToken - del(state, 'token') + delete state.token } }, getters: { diff --git a/src/modules/polls.js b/src/modules/polls.js index 92b89a06..1c4f98a4 100644 --- a/src/modules/polls.js +++ b/src/modules/polls.js @@ -1,5 +1,4 @@ import { merge } from 'lodash' -import { set } from 'vue' const polls = { state: { @@ -13,25 +12,25 @@ const polls = { // Make expired-state change trigger re-renders properly poll.expired = Date.now() > Date.parse(poll.expires_at) if (existingPoll) { - set(state.pollsObject, poll.id, merge(existingPoll, poll)) + state.pollsObject[poll.id] = merge(existingPoll, poll) } else { - set(state.pollsObject, poll.id, poll) + state.pollsObject[poll.id] = poll } }, trackPoll (state, pollId) { const currentValue = state.trackedPolls[pollId] if (currentValue) { - set(state.trackedPolls, pollId, currentValue + 1) + state.trackedPolls[pollId] = currentValue + 1 } else { - set(state.trackedPolls, pollId, 1) + state.trackedPolls[pollId] = 1 } }, untrackPoll (state, pollId) { const currentValue = state.trackedPolls[pollId] if (currentValue) { - set(state.trackedPolls, pollId, currentValue - 1) + state.trackedPolls[pollId] = currentValue - 1 } else { - set(state.trackedPolls, pollId, 0) + state.trackedPolls[pollId] = 0 } } }, diff --git a/src/modules/statuses.js b/src/modules/statuses.js index ac5d25c4..a13930e9 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -12,7 +12,6 @@ import { isArray, omitBy } from 'lodash' -import { set } from 'vue' import { isStatusNotification, isValidNotification, @@ -92,7 +91,7 @@ const mergeOrAdd = (arr, obj, item) => { // This is a new item, prepare it prepareStatus(item) arr.push(item) - set(obj, item.id, item) + obj[item.id] = item return { item, new: true } } } @@ -131,7 +130,7 @@ const addStatusToGlobalStorage = (state, data) => { if (conversationsObject[conversationId]) { conversationsObject[conversationId].push(status) } else { - set(conversationsObject, conversationId, [status]) + conversationsObject[conversationId] = [status] } } return result @@ -523,7 +522,7 @@ export const mutations = { }, addEmojiReactionsBy (state, { id, emojiReactions, currentUser }) { const status = state.allStatusesObject[id] - set(status, 'emoji_reactions', emojiReactions) + status['emoji_reactions'] = emojiReactions }, addOwnReaction (state, { id, emoji, currentUser }) { const status = state.allStatusesObject[id] @@ -542,9 +541,9 @@ export const mutations = { // Update count of existing reaction if it exists, otherwise append at the end if (reactionIndex >= 0) { - set(status.emoji_reactions, reactionIndex, newReaction) + status.emoji_reactions[reactionIndex] = newReaction } else { - set(status, 'emoji_reactions', [...status.emoji_reactions, newReaction]) + status['emoji_reactions'] = [...status.emoji_reactions, newReaction] } }, removeOwnReaction (state, { id, emoji, currentUser }) { @@ -563,9 +562,9 @@ export const mutations = { } if (newReaction.count > 0) { - set(status.emoji_reactions, reactionIndex, newReaction) + status.emoji_reactions[reactionIndex] = newReaction } else { - set(status, 'emoji_reactions', status.emoji_reactions.filter(r => r.name !== emoji)) + status['emoji_reactions'] = status.emoji_reactions.filter(r => r.name !== emoji) } }, updateStatusWithPoll (state, { id, poll }) { diff --git a/src/modules/users.js b/src/modules/users.js index 05ff44d5..e5889fdb 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -1,7 +1,6 @@ import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import oauthApi from '../services/new_api/oauth.js' import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash' -import { set } from 'vue' import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js' // TODO: Unify with mergeOrAdd in statuses.js @@ -15,9 +14,9 @@ export const mergeOrAdd = (arr, obj, item) => { } else { // This is a new item, prepare it arr.push(item) - set(obj, item.id, item) + obj[item.id] = item if (item.screen_name && !item.screen_name.includes('@')) { - set(obj, item.screen_name.toLowerCase(), item) + obj[item.screen_name.toLowerCase()] = item } return { item, new: true } } @@ -103,23 +102,23 @@ export const mutations = { const user = state.usersObject[id] const tags = user.tags || [] const newTags = tags.concat([tag]) - set(user, 'tags', newTags) + user['tags'] = newTags }, untagUser (state, { user: { id }, tag }) { const user = state.usersObject[id] const tags = user.tags || [] const newTags = tags.filter(t => t !== tag) - set(user, 'tags', newTags) + user['tags'] = newTags }, updateRight (state, { user: { id }, right, value }) { const user = state.usersObject[id] let newRights = user.rights newRights[right] = value - set(user, 'rights', newRights) + user['rights'] = newRights }, updateActivationStatus (state, { user: { id }, deactivated }) { const user = state.usersObject[id] - set(user, 'deactivated', deactivated) + user['deactivated'] = deactivated }, setCurrentUser (state, user) { state.lastLoginName = user.screen_name @@ -148,26 +147,26 @@ export const mutations = { clearFriends (state, userId) { const user = state.usersObject[userId] if (user) { - set(user, 'friendIds', []) + user['friendIds'] = [] } }, clearFollowers (state, userId) { const user = state.usersObject[userId] if (user) { - set(user, 'followerIds', []) + user['followerIds'] = [] } }, addNewUsers (state, users) { each(users, (user) => { if (user.relationship) { - set(state.relationships, user.relationship.id, user.relationship) + state.relationships[user.relationship.id] = user.relationship } mergeOrAdd(state.users, state.usersObject, user) }) }, updateUserRelationship (state, relationships) { relationships.forEach((relationship) => { - set(state.relationships, relationship.id, relationship) + state.relationships[relationship.id] = relationship }) }, saveBlockIds (state, blockIds) { @@ -222,7 +221,7 @@ export const mutations = { }, setColor (state, { user: { id }, highlighted }) { const user = state.usersObject[id] - set(user, 'highlight', highlighted) + user['highlight'] = highlighted }, signUpPending (state) { state.signUpPending = true diff --git a/src/services/resettable_async_component.js b/src/services/resettable_async_component.js index 517bbd88..e85c540b 100644 --- a/src/services/resettable_async_component.js +++ b/src/services/resettable_async_component.js @@ -1,4 +1,5 @@ -import Vue from 'vue' +// TODO investigate if even necessary since VUE3 +import { reactive } from 'vue' /* By default async components don't have any way to recover, if component is * failed, it is failed forever. This helper tries to remedy that by recreating @@ -13,7 +14,7 @@ function getResettableAsyncComponent (asyncComponent, options) { ...options }) - const observe = Vue.observable({ c: asyncComponentFactory() }) + const observe = reactive({ c: asyncComponentFactory() }) return { functional: true, |
