diff --git a/index.html b/index.html index 228d8c28..2bccf3e6 100644 --- a/index.html +++ b/index.html @@ -1,56 +1,25 @@ - - - - Akkoma - - - - - - - - - - - - - -
- - - + + + + + Akkoma + + + + + + + + + + + + +
+ + + + diff --git a/src/App.scss b/src/App.scss index 5fce8a8c..289bfe2f 100644 --- a/src/App.scss +++ b/src/App.scss @@ -409,7 +409,7 @@ textarea, color: $fallback--lightText; color: var(--inputText, $fallback--lightText); font-family: sans-serif; - font-family: var(--inputFont, sans-serif); + font-family: var(--interfaceFont, sans-serif); font-size: 1em; margin: 0; box-sizing: border-box; diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 81117ca6..b20d3df6 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -423,6 +423,9 @@ const getNodeInfo = async ({ store }) => { typeof federation.enabled === 'undefined' ? true : federation.enabled }) + store.dispatch('setInstanceOption', { name: 'publicTimelineVisibility', value: metadata.publicTimelineVisibility }) + store.dispatch('setInstanceOption', { name: 'federatedTimelineAvailable', value: metadata.federatedTimelineAvailable }) + const accountActivationRequired = metadata.accountActivationRequired store.dispatch('setInstanceOption', { name: 'accountActivationRequired', @@ -511,9 +514,6 @@ const afterStoreSetup = async ({ store, i18n }) => { ]) // Start fetching things that don't need to block the UI - store.dispatch('fetchMutes') - store.dispatch('startFetchingAnnouncements') - store.dispatch('startFetchingReports') getTOS({ store }) getStickers({ store }) diff --git a/src/components/desktop_nav/desktop_nav.js b/src/components/desktop_nav/desktop_nav.js index bacdf53b..6a007347 100644 --- a/src/components/desktop_nav/desktop_nav.js +++ b/src/components/desktop_nav/desktop_nav.js @@ -1,6 +1,11 @@ import SearchBar from 'components/search_bar/search_bar.vue' import ConfirmModal from '../confirm_modal/confirm_modal.vue' import { library } from '@fortawesome/fontawesome-svg-core' +import { + publicTimelineVisible, + federatedTimelineVisible, + bubbleTimelineVisible, +} from '../../lib/timeline_visibility' import { faSignInAlt, faSignOutAlt, @@ -19,6 +24,7 @@ import { faInfoCircle, faUserTie } from '@fortawesome/free-solid-svg-icons' +import { mapState } from 'vuex' library.add( faSignInAlt, @@ -125,7 +131,12 @@ export default { }, showBubbleTimeline() { return this.$store.state.instance.localBubbleInstances.length > 0 - } + }, + ...mapState({ + publicTimelineVisible, + federatedTimelineVisible, + bubbleTimelineVisible, + }) }, methods: { scrollToTop() { diff --git a/src/components/desktop_nav/desktop_nav.vue b/src/components/desktop_nav/desktop_nav.vue index 3b66cc3e..898e6884 100644 --- a/src/components/desktop_nav/desktop_nav.vue +++ b/src/components/desktop_nav/desktop_nav.vue @@ -46,6 +46,7 @@ @@ -69,6 +70,7 @@ { + const allEmojis = state.instance.emoji.concat(state.instance.customEmoji) + return allEmojis.find(emoji => emoji.replacement === replacement) +} + const EmojiReactions = { name: 'EmojiReactions', components: { @@ -56,6 +61,8 @@ const EmojiReactions = { }, reactWith(emoji) { this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji }) + const emojiObject = findEmojiByReplacement(this.$store.state, emoji) + this.$store.commit('emojiUsed', emojiObject) }, unreact(emoji) { this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji }) diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue index 83fa9c3e..2015ee3c 100644 --- a/src/components/emoji_reactions/emoji_reactions.vue +++ b/src/components/emoji_reactions/emoji_reactions.vue @@ -56,6 +56,7 @@ display: flex; margin-top: 0.25em; flex-wrap: wrap; + container-type: inline-size; } .unicode-emoji { @@ -72,6 +73,9 @@ justify-content: center; box-sizing: border-box; .reaction-emoji { + width: auto; + max-width: 96cqw; + height: 2.55em !important; margin-right: 0.25em; } img.reaction-emoji { diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js index 09ea8085..9151055c 100644 --- a/src/components/extra_buttons/extra_buttons.js +++ b/src/components/extra_buttons/extra_buttons.js @@ -15,6 +15,7 @@ import { faBookmark as faBookmarkReg, faFlag } from '@fortawesome/free-regular-svg-icons' +import { mapState } from 'vuex' library.add( faEllipsisH, @@ -240,9 +241,9 @@ const ExtraButtons = { isEdited() { return this.status.edited_at !== null }, - editingAvailable() { + editingAvailable () { return this.$store.state.instance.editingAvailable - } + }, } } diff --git a/src/components/poll/poll_form.js b/src/components/poll/poll_form.js index 67bc0d20..66daa768 100644 --- a/src/components/poll/poll_form.js +++ b/src/components/poll/poll_form.js @@ -100,12 +100,9 @@ export default { convertExpiryFromUnit(unit, amount) { // Note: we want seconds and not milliseconds switch (unit) { - case 'minutes': - return 0.001 * amount * DateUtils.MINUTE - case 'hours': - return 0.001 * amount * DateUtils.HOUR - case 'days': - return 0.001 * amount * DateUtils.DAY + case 'minutes': return amount * DateUtils.MINUTE / 1000 + case 'hours': return amount * DateUtils.HOUR / 1000 + case 'days': return amount * DateUtils.DAY / 1000 } }, expiryAmountChange() { diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue index 39b12a6f..48a7495f 100644 --- a/src/components/popover/popover.vue +++ b/src/components/popover/popover.vue @@ -112,7 +112,7 @@ svg { width: 22px; margin-right: 0.75rem; - color: var(--menuPopoverIcon, $fallback--icon); + color: var(--popoverIcon, $fallback--icon) } } diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index fec7ad36..fe174e9f 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -47,6 +47,14 @@ const pxStringToNumber = (str) => { return Number(str.substring(0, str.length - 2)) } +const deleteDraft = (draftKey) => { + const draftData = JSON.parse(localStorage.getItem('drafts') || '{}'); + + delete draftData[draftKey]; + + localStorage.setItem('drafts', JSON.stringify(draftData)); +} + const PostStatusForm = { props: [ 'statusId', @@ -160,6 +168,36 @@ const PostStatusForm = { } } + if (!this.statusId) { + let draftKey = 'status'; + if (this.replyTo) { + draftKey = 'reply:' + this.replyTo; + } else if (this.quoteId) { + draftKey = 'quote:' + this.quoteId; + } + + const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[draftKey]; + + if (draft) { + statusParams = { + spoilerText: draft.data.spoilerText, + status: draft.data.status, + sensitiveIfSubject, + nsfw: draft.data.nsfw, + files: draft.data.files, + poll: draft.data.poll, + mediaDescriptions: draft.data.mediaDescriptions, + visibility: draft.data.visibility, + language: draft.data.language, + contentType: draft.data.contentType + } + + if (draft.data.poll) { + this.togglePollForm(); + } + } + } + return { dropFiles: [], uploadingFiles: false, @@ -291,6 +329,7 @@ const PostStatusForm = { statusChanged() { this.autoPreview() this.updateIdempotencyKey() + this.saveDraft() }, clearStatus() { const newStatus = this.newStatus @@ -398,42 +437,66 @@ const PostStatusForm = { } const newStatus = this.newStatus this.previewLoading = true - statusPoster - .postStatus({ - status: newStatus.status, - spoilerText: newStatus.spoilerText || null, - visibility: newStatus.visibility, - sensitive: newStatus.nsfw, - media: [], - store: this.$store, - inReplyToStatusId: this.replyTo, - quoteId: this.quoteId, - contentType: newStatus.contentType, - language: newStatus.language, - poll: {}, - preview: true - }) - .then((data) => { - // Don't apply preview if not loading, because it means - // user has closed the preview manually. - if (!this.previewLoading) return - if (!data.error) { - this.preview = data - } else { - this.preview = { error: data.error } - } - }) - .catch((error) => { - this.preview = { error } - }) - .finally(() => { - this.previewLoading = false - }) + statusPoster.postStatus({ + status: newStatus.status, + spoilerText: newStatus.spoilerText || null, + visibility: newStatus.visibility, + sensitive: newStatus.nsfw, + media: [], + store: this.$store, + inReplyToStatusId: this.replyTo, + quoteId: this.quoteId, + contentType: newStatus.contentType, + language: newStatus.language, + poll: {}, + preview: true + }).then((data) => { + // Don't apply preview if not loading, because it means + // user has closed the preview manually. + if (!this.previewLoading) return + if (!data.error) { + this.preview = data + } else { + this.preview = { error: data.error } + } + }).catch((error) => { + this.preview = { error } + }).finally(() => { + this.previewLoading = false + }) + + let draftKey = 'status'; + if (this.replyTo) { + draftKey = 'reply:' + this.replyTo; + } else if (this.quoteId) { + draftKey = 'quote:' + this.quoteId; + } + deleteDraft(draftKey) }, - debouncePreviewStatus: debounce(function () { - this.previewStatus() - }, 500), - autoPreview() { + debouncePreviewStatus: debounce(function () { this.previewStatus() }, 500), + saveDraft() { + const draftData = JSON.parse(localStorage.getItem('drafts') || '{}'); + + let draftKey = 'status'; + if (this.replyTo) { + draftKey = 'reply:' + this.replyTo; + } else if (this.quoteId) { + draftKey = 'quote:' + this.quoteId; + } + + if (this.newStatus.status || this.newStatus.spoilerText || this.newStatus.files.length > 0 || this.newStatus.poll.length > 0) { + draftData[draftKey] = { + updatedAt: new Date(), + data: this.newStatus, + }; + + localStorage.setItem('drafts', JSON.stringify(draftData)); + + } else { + deleteDraft(draftKey); + } + }, + autoPreview () { if (!this.preview) return this.previewLoading = true this.debouncePreviewStatus() diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index eb35082e..a2649c84 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -307,12 +307,14 @@ > {{ $t('post_status.post') }} - + + + +