diff --git a/changelog.d/colorfuncs.fix b/changelog.d/colorfuncs.fix deleted file mode 100644 index 18c49e2fc..000000000 --- a/changelog.d/colorfuncs.fix +++ /dev/null @@ -1 +0,0 @@ -Fix some of the color manipulation functions diff --git a/changelog.d/drafts.add b/changelog.d/drafts.add deleted file mode 100644 index 1147016e3..000000000 --- a/changelog.d/drafts.add +++ /dev/null @@ -1 +0,0 @@ -Add draft management system diff --git a/src/App.scss b/src/App.scss index 2afc43908..f52ba06b9 100644 --- a/src/App.scss +++ b/src/App.scss @@ -748,12 +748,6 @@ option { margin-left: 0.7em; margin-top: -1em; } - - &.-neutral { - background-color: var(--badgeNeutral); - color: white; - color: var(--badgeNeutralText, white); - } } .alert { diff --git a/src/boot/after_store.js b/src/boot/after_store.js index cf242092a..65a151785 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -368,8 +368,6 @@ const afterStoreSetup = async ({ store, i18n }) => { getInstanceConfig({ store }) ]).catch(e => Promise.reject(e)) - await store.dispatch('loadDrafts') - // Start fetching things that don't need to block the UI store.dispatch('fetchMutes') store.dispatch('startFetchingAnnouncements') diff --git a/src/boot/routes.js b/src/boot/routes.js index 66d937c8e..f87b2ec85 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -26,7 +26,6 @@ import ListsEdit from 'components/lists_edit/lists_edit.vue' import NavPanel from 'src/components/nav_panel/nav_panel.vue' import AnnouncementsPage from 'components/announcements_page/announcements_page.vue' import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue' -import Drafts from 'components/drafts/drafts.vue' import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue' import BookmarkFolderEdit from '../components/bookmark_folder_edit/bookmark_folder_edit.vue' @@ -83,7 +82,6 @@ export default (store) => { { name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute }, { name: 'about', path: '/about', component: About }, { name: 'announcements', path: '/announcements', component: AnnouncementsPage }, - { name: 'drafts', path: '/drafts', component: Drafts }, { name: 'user-profile', path: '/users/:name', component: UserProfile }, { name: 'legacy-user-profile', path: '/:name', component: UserProfile }, { name: 'lists', path: '/lists', component: Lists }, diff --git a/src/components/chat/chat.vue b/src/components/chat/chat.vue index 57158abad..6efe576b8 100644 --- a/src/components/chat/chat.vue +++ b/src/components/chat/chat.vue @@ -76,7 +76,6 @@ :disable-sensitivity-checkbox="true" :disable-submit="errorLoadingChat || !currentChat" :disable-preview="true" - :disable-draft="true" :optimistic-posting="true" :post-handler="sendMessage" :submit-on-enter="!mobileLayout" diff --git a/src/components/draft/draft.js b/src/components/draft/draft.js deleted file mode 100644 index cb07ec5c5..000000000 --- a/src/components/draft/draft.js +++ /dev/null @@ -1,64 +0,0 @@ -import PostStatusForm from 'src/components/post_status_form/post_status_form.vue' -import EditStatusForm from 'src/components/edit_status_form/edit_status_form.vue' -import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue' -import StatusContent from 'src/components/status_content/status_content.vue' - -const Draft = { - components: { - PostStatusForm, - EditStatusForm, - ConfirmModal, - StatusContent - }, - props: { - draft: { - type: Object, - required: true - } - }, - data () { - return { - editing: false, - showingConfirmDialog: false - } - }, - computed: { - relAttrs () { - if (this.draft.type === 'edit') { - return { statusId: this.draft.refId } - } else if (this.draft.type === 'reply') { - return { replyTo: this.draft.refId } - } else { - return {} - } - }, - postStatusFormProps () { - return { - draftId: this.draft.id, - ...this.relAttrs - } - }, - refStatus () { - return this.draft.refId ? this.$store.state.statuses.allStatusesObject[this.draft.refId] : undefined - } - }, - methods: { - toggleEditing () { - this.editing = !this.editing - }, - abandon () { - this.showingConfirmDialog = true - }, - doAbandon () { - this.$store.dispatch('abandonDraft', { id: this.draft.id }) - .then(() => { - this.hideConfirmDialog() - }) - }, - hideConfirmDialog () { - this.showingConfirmDialog = false - } - } -} - -export default Draft diff --git a/src/components/draft/draft.vue b/src/components/draft/draft.vue deleted file mode 100644 index d9d356121..000000000 --- a/src/components/draft/draft.vue +++ /dev/null @@ -1,100 +0,0 @@ - - - - - diff --git a/src/components/draft_closer/draft_closer.js b/src/components/draft_closer/draft_closer.js deleted file mode 100644 index e50ea05ab..000000000 --- a/src/components/draft_closer/draft_closer.js +++ /dev/null @@ -1,52 +0,0 @@ -import DialogModal from 'src/components/dialog_modal/dialog_modal.vue' - -const DraftCloser = { - data () { - return { - showing: false - } - }, - components: { - DialogModal - }, - emits: [ - 'save', - 'discard' - ], - computed: { - action () { - if (this.$store.getters.mergedConfig.autoSaveDraft) { - return 'save' - } else { - return this.$store.getters.mergedConfig.unsavedPostAction - } - }, - shouldConfirm () { - return this.action === 'confirm' - } - }, - methods: { - requestClose () { - if (this.shouldConfirm) { - this.showing = true - } else if (this.action === 'save') { - this.save() - } else { - this.discard() - } - }, - save () { - this.$emit('save') - this.showing = false - }, - discard () { - this.$emit('discard') - this.showing = false - }, - cancel () { - this.showing = false - } - } -} - -export default DraftCloser diff --git a/src/components/draft_closer/draft_closer.vue b/src/components/draft_closer/draft_closer.vue deleted file mode 100644 index 1afb1f44d..000000000 --- a/src/components/draft_closer/draft_closer.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - diff --git a/src/components/drafts/drafts.js b/src/components/drafts/drafts.js deleted file mode 100644 index 201417f66..000000000 --- a/src/components/drafts/drafts.js +++ /dev/null @@ -1,16 +0,0 @@ -import Draft from 'src/components/draft/draft.vue' -import List from 'src/components/list/list.vue' - -const Drafts = { - components: { - Draft, - List - }, - computed: { - drafts () { - return this.$store.getters.draftsArray - } - } -} - -export default Drafts diff --git a/src/components/drafts/drafts.vue b/src/components/drafts/drafts.vue deleted file mode 100644 index e64a506e5..000000000 --- a/src/components/drafts/drafts.vue +++ /dev/null @@ -1,24 +0,0 @@ - - - diff --git a/src/components/edit_status_form/edit_status_form.js b/src/components/edit_status_form/edit_status_form.js deleted file mode 100644 index 323763370..000000000 --- a/src/components/edit_status_form/edit_status_form.js +++ /dev/null @@ -1,44 +0,0 @@ -import PostStatusForm from '../post_status_form/post_status_form.vue' -import statusPosterService from '../../services/status_poster/status_poster.service.js' - -const EditStatusForm = { - components: { - PostStatusForm - }, - props: { - params: { - type: Object, - required: true - } - }, - methods: { - requestClose () { - this.$refs.postStatusForm.requestClose() - }, - doEditStatus ({ status, spoilerText, sensitive, media, contentType, poll }) { - const params = { - store: this.$store, - statusId: this.params.statusId, - status, - spoilerText, - sensitive, - poll, - media, - contentType - } - - return statusPosterService.editStatus(params) - .then((data) => { - return data - }) - .catch((err) => { - console.error('Error editing status', err) - return { - error: err.message - } - }) - } - } -} - -export default EditStatusForm diff --git a/src/components/edit_status_form/edit_status_form.vue b/src/components/edit_status_form/edit_status_form.vue deleted file mode 100644 index 0a7ec760a..000000000 --- a/src/components/edit_status_form/edit_status_form.vue +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/src/components/edit_status_modal/edit_status_modal.js b/src/components/edit_status_modal/edit_status_modal.js index 0de2e53c7..75adfea75 100644 --- a/src/components/edit_status_modal/edit_status_modal.js +++ b/src/components/edit_status_modal/edit_status_modal.js @@ -1,10 +1,11 @@ -import EditStatusForm from '../edit_status_form/edit_status_form.vue' +import PostStatusForm from '../post_status_form/post_status_form.vue' import Modal from '../modal/modal.vue' +import statusPosterService from '../../services/status_poster/status_poster.service.js' import get from 'lodash/get' const EditStatusModal = { components: { - EditStatusForm, + PostStatusForm, Modal }, data () { @@ -42,10 +43,30 @@ const EditStatusModal = { } }, methods: { - closeModal () { - this.$refs.editStatusForm.requestClose() + doEditStatus ({ status, spoilerText, sensitive, media, contentType, poll }) { + const params = { + store: this.$store, + statusId: this.$store.state.editStatus.params.statusId, + status, + spoilerText, + sensitive, + poll, + media, + contentType + } + + return statusPosterService.editStatus(params) + .then((data) => { + return data + }) + .catch((err) => { + console.error('Error editing status', err) + return { + error: err.message + } + }) }, - doCloseModal () { + closeModal () { this.$store.dispatch('closeEditStatusModal') } } diff --git a/src/components/edit_status_modal/edit_status_modal.vue b/src/components/edit_status_modal/edit_status_modal.vue index 42073d7d3..5001bd46c 100644 --- a/src/components/edit_status_modal/edit_status_modal.vue +++ b/src/components/edit_status_modal/edit_status_modal.vue @@ -10,12 +10,13 @@ {{ $t('post_status.edit_status') }} - diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index bc962e32f..11863e975 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -20,8 +20,7 @@ import { faInfoCircle, faStream, faList, - faBullhorn, - faFilePen + faBullhorn } from '@fortawesome/free-solid-svg-icons' library.add( @@ -36,8 +35,7 @@ library.add( faInfoCircle, faStream, faList, - faBullhorn, - faFilePen + faBullhorn ) const NavPanel = { props: ['forceExpand', 'forceEditMode'], diff --git a/src/components/navigation/navigation.js b/src/components/navigation/navigation.js index 46ed3f8c2..156cd8cb3 100644 --- a/src/components/navigation/navigation.js +++ b/src/components/navigation/navigation.js @@ -62,7 +62,6 @@ export const ROOT_ITEMS = { route: 'chats', icon: 'comments', label: 'nav.chats', - badgeStyle: 'notification', badgeGetter: 'unreadChatCount', criteria: ['chats'] }, @@ -70,7 +69,6 @@ export const ROOT_ITEMS = { route: 'friend-requests', icon: 'user-plus', label: 'nav.friend_requests', - badgeStyle: 'notification', criteria: ['lockedUser'], badgeGetter: 'followRequestCount' }, @@ -84,16 +82,8 @@ export const ROOT_ITEMS = { route: 'announcements', icon: 'bullhorn', label: 'nav.announcements', - badgeStyle: 'notification', badgeGetter: 'unreadAnnouncementCount', criteria: ['announcements'] - }, - drafts: { - route: 'drafts', - icon: 'file-pen', - label: 'nav.drafts', - badgeStyle: 'neutral', - badgeGetter: 'draftCount' } } diff --git a/src/components/navigation/navigation_entry.vue b/src/components/navigation/navigation_entry.vue index 4b6c8e299..024ee3148 100644 --- a/src/components/navigation/navigation_entry.vue +++ b/src/components/navigation/navigation_entry.vue @@ -48,8 +48,7 @@
{{ getters[item.badgeGetter] }}
diff --git a/src/components/navigation/navigation_pins.vue b/src/components/navigation/navigation_pins.vue index 37351b918..decd1c04b 100644 --- a/src/components/navigation/navigation_pins.vue +++ b/src/components/navigation/navigation_pins.vue @@ -19,8 +19,7 @@ >{{ item.iconLetter }}
@@ -35,14 +34,6 @@ overflow: hidden; height: 100%; - &.alert-dot-notification { - background-color: var(--badgeNotification); - } - - &.alert-dot-neutral { - background-color: var(--badgeNeutral); - } - .pinned-item { position: relative; flex: 1 0 3em; diff --git a/src/components/poll/poll_form.js b/src/components/poll/poll_form.js index 29ccbc4bf..a20701553 100644 --- a/src/components/poll/poll_form.js +++ b/src/components/poll/poll_form.js @@ -1,5 +1,5 @@ import * as DateUtils from 'src/services/date_utils/date_utils.js' -import { pollFallback } from 'src/services/poll/poll.service.js' +import { uniq } from 'lodash' import { library } from '@fortawesome/fontawesome-svg-core' import Select from '../select/select.vue' import { @@ -17,33 +17,14 @@ export default { Select }, name: 'PollForm', - props: { - visible: {}, - params: { - type: Object, - required: true - } - }, + props: ['visible'], + data: () => ({ + pollType: 'single', + options: ['', ''], + expiryAmount: 10, + expiryUnit: 'minutes' + }), computed: { - pollType: { - get () { return pollFallback(this.params, 'pollType') }, - set (newVal) { this.params.pollType = newVal } - }, - options () { - const hasOptions = !!this.params.options - if (!hasOptions) { - this.params.options = pollFallback(this.params, 'options') - } - return this.params.options - }, - expiryAmount: { - get () { return pollFallback(this.params, 'expiryAmount') }, - set (newVal) { this.params.expiryAmount = newVal } - }, - expiryUnit: { - get () { return pollFallback(this.params, 'expiryUnit') }, - set (newVal) { this.params.expiryUnit = newVal } - }, pollLimits () { return this.$store.state.instance.pollLimits }, @@ -108,6 +89,7 @@ export default { deleteOption (index, event) { if (this.options.length > 2) { this.options.splice(index, 1) + this.updatePollToParent() } }, convertExpiryToUnit (unit, amount) { @@ -122,6 +104,24 @@ export default { Math.max(this.minExpirationInCurrentUnit, this.expiryAmount) this.expiryAmount = Math.min(this.maxExpirationInCurrentUnit, this.expiryAmount) + this.updatePollToParent() + }, + updatePollToParent () { + const expiresIn = this.convertExpiryFromUnit( + this.expiryUnit, + this.expiryAmount + ) + + const options = uniq(this.options.filter(option => option !== '')) + if (options.length < 2) { + this.$emit('update-poll', { error: this.$t('polls.not_enough_options') }) + return + } + this.$emit('update-poll', { + options, + multiple: this.pollType === 'multiple', + expiresIn + }) } } } diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 1ae985498..563dfb966 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -10,13 +10,11 @@ import StatusContent from '../status_content/status_content.vue' import fileTypeService from '../../services/file_type/file_type.service.js' import { findOffset } from '../../services/offset_finder/offset_finder.service.js' import { propsToNative } from '../../services/attributes_helper/attributes_helper.service.js' -import { pollFormToMasto } from 'src/services/poll/poll.service.js' import { reject, map, uniqBy, debounce } from 'lodash' import suggestor from '../emoji_input/suggestor.js' import { mapGetters, mapState } from 'vuex' import Checkbox from '../checkbox/checkbox.vue' import Select from '../select/select.vue' -import DraftCloser from 'src/components/draft_closer/draft_closer.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -57,18 +55,6 @@ const pxStringToNumber = (str) => { return Number(str.substring(0, str.length - 2)) } -const typeAndRefId = ({ replyTo, profileMention, statusId }) => { - if (replyTo) { - return ['reply', replyTo] - } else if (profileMention) { - return ['mention', profileMention] - } else if (statusId) { - return ['edit', statusId] - } else { - return ['new', ''] - } -} - const PostStatusForm = { props: [ 'statusId', @@ -93,7 +79,6 @@ const PostStatusForm = { 'disableSensitivityCheckbox', 'disableSubmit', 'disablePreview', - 'disableDraft', 'placeholder', 'maxHeight', 'postHandler', @@ -103,15 +88,13 @@ const PostStatusForm = { 'submitOnEnter', 'emojiPickerPlacement', 'optimisticPosting', - 'profileMention', - 'draftId' + 'profileMention' ], emits: [ 'posted', 'resize', 'mediaplay', - 'mediapause', - 'can-close' + 'mediapause' ], components: { MediaUpload, @@ -122,8 +105,7 @@ const PostStatusForm = { Select, Attachment, StatusContent, - Gallery, - DraftCloser + Gallery }, mounted () { this.updateIdempotencyKey() @@ -144,54 +126,41 @@ const PostStatusForm = { const { scopeCopy } = this.$store.getters.mergedConfig - const [statusType, refId] = typeAndRefId({ replyTo: this.replyTo, profileMention: this.profileMention && this.repliedUser?.id, statusId: this.statusId }) + if (this.replyTo || this.profileMention) { + const currentUser = this.$store.state.users.currentUser + statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser) + } - // If we are starting a new post, do not associate it with old drafts - let statusParams = !this.disableDraft && (this.draftId || statusType !== 'new') ? this.getDraft(statusType, refId) : null + const scope = ((this.copyMessageScope && scopeCopy) || this.copyMessageScope === 'direct') + ? this.copyMessageScope + : this.$store.state.users.currentUser.default_scope - if (!statusParams) { - if (statusType === 'reply' || statusType === 'mention') { - const currentUser = this.$store.state.users.currentUser - statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser) - } + const { postContentType: contentType, sensitiveByDefault } = this.$store.getters.mergedConfig - const scope = ((this.copyMessageScope && scopeCopy) || this.copyMessageScope === 'direct') - ? this.copyMessageScope - : this.$store.state.users.currentUser.default_scope - - const { postContentType: contentType, sensitiveByDefault } = this.$store.getters.mergedConfig + let statusParams = { + spoilerText: this.subject || '', + status: statusText, + nsfw: !!sensitiveByDefault, + files: [], + poll: {}, + mediaDescriptions: {}, + visibility: scope, + contentType + } + if (this.statusId) { + const statusContentType = this.statusContentType || contentType statusParams = { - type: statusType, - refId, spoilerText: this.subject || '', - status: statusText, - nsfw: !!sensitiveByDefault, - files: [], - poll: {}, - hasPoll: false, - mediaDescriptions: {}, - visibility: scope, - contentType, + status: this.statusText || '', + nsfw: this.statusIsSensitive || !!sensitiveByDefault, + files: this.statusFiles || [], + poll: this.statusPoll || {}, + mediaDescriptions: this.statusMediaDescriptions || {}, + visibility: this.statusScope || scope, + contentType: statusContentType, quoting: false } - - if (statusType === 'edit') { - const statusContentType = this.statusContentType || contentType - statusParams = { - type: statusType, - refId, - spoilerText: this.subject || '', - status: this.statusText || '', - nsfw: this.statusIsSensitive || !!sensitiveByDefault, - files: this.statusFiles || [], - poll: this.statusPoll || {}, - hasPoll: false, - mediaDescriptions: this.statusMediaDescriptions || {}, - visibility: this.statusScope || scope, - contentType: statusContentType - } - } } return { @@ -203,14 +172,13 @@ const PostStatusForm = { highlighted: 0, newStatus: statusParams, caret: 0, + pollFormVisible: false, showDropIcon: 'hide', dropStopTimeout: null, preview: null, previewLoading: false, emojiInputShown: false, - idempotencyKey: '', - saveInhibited: true, - savable: false + idempotencyKey: '' } }, computed: { @@ -325,24 +293,6 @@ const PostStatusForm = { return false }, - debouncedMaybeAutoSaveDraft () { - return debounce(this.maybeAutoSaveDraft, 3000) - }, - pollFormVisible () { - return this.newStatus.hasPoll - }, - shouldAutoSaveDraft () { - return this.$store.getters.mergedConfig.autoSaveDraft - }, - autoSaveState () { - if (this.savable) { - return this.$t('post_status.auto_save_saving') - } else if (this.newStatus.id) { - return this.$t('post_status.auto_save_saved') - } else { - return this.$t('post_status.auto_save_nothing_new') - } - }, ...mapGetters(['mergedConfig']), ...mapState({ mobileLayout: state => state.interface.mobileLayout @@ -354,32 +304,15 @@ const PostStatusForm = { handler () { this.statusChanged() } - }, - savable (val) { - // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event#usage_notes - // MDN says we'd better add the beforeunload event listener only when needed, and remove it when it's no longer needed - if (val) { - this.addBeforeUnloadListener() - } else { - this.removeBeforeUnloadListener() - } } }, - beforeUnmount () { - this.maybeAutoSaveDraft() - this.removeBeforeUnloadListener() - }, methods: { statusChanged () { this.autoPreview() this.updateIdempotencyKey() - this.debouncedMaybeAutoSaveDraft() - this.savable = true - this.saveInhibited = false }, clearStatus () { const newStatus = this.newStatus - this.saveInhibited = true this.newStatus = { status: '', spoilerText: '', @@ -387,10 +320,10 @@ const PostStatusForm = { visibility: newStatus.visibility, contentType: newStatus.contentType, poll: {}, - hasPoll: false, mediaDescriptions: {}, quoting: false } + this.pollFormVisible = false this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile() this.clearPollForm() if (this.preserveFocus) { @@ -403,7 +336,6 @@ const PostStatusForm = { el.style.height = undefined this.error = null if (this.preview) this.previewStatus() - this.savable = false }, async postStatus (event, newStatus, opts = {}) { if (this.posting && !this.optimisticPosting) { return } @@ -421,7 +353,7 @@ const PostStatusForm = { return } - const poll = this.newStatus.hasPoll ? pollFormToMasto(this.newStatus.poll) : {} + const poll = this.pollFormVisible ? this.newStatus.poll : {} if (this.pollContentError) { this.error = this.pollContentError return @@ -456,7 +388,6 @@ const PostStatusForm = { postHandler(postingOptions).then((data) => { if (!data.error) { - this.abandonDraft() this.clearStatus() this.$emit('posted', data) } else { @@ -701,7 +632,7 @@ const PostStatusForm = { this.newStatus.visibility = visibility }, togglePollForm () { - this.newStatus.hasPoll = !this.newStatus.hasPoll + this.pollFormVisible = !this.pollFormVisible }, setPoll (poll) { this.newStatus.poll = poll @@ -734,78 +665,6 @@ const PostStatusForm = { }, propsToNative (props) { return propsToNative(props) - }, - saveDraft () { - if (!this.disableDraft && - !this.saveInhibited) { - if (this.newStatus.status || - this.newStatus.files?.length || - this.newStatus.hasPoll) { - return this.$store.dispatch('addOrSaveDraft', { draft: this.newStatus }) - .then(id => { - if (this.newStatus.id !== id) { - this.newStatus.id = id - } - this.savable = false - }) - } else if (this.newStatus.id) { - // There is a draft, but there is nothing in it, clear it - return this.abandonDraft() - .then(() => { - this.savable = false - }) - } - } - return Promise.resolve() - }, - maybeAutoSaveDraft () { - if (this.shouldAutoSaveDraft) { - this.saveDraft() - } - }, - abandonDraft () { - return this.$store.dispatch('abandonDraft', { id: this.newStatus.id }) - }, - getDraft (statusType, refId) { - const maybeDraft = this.$store.state.drafts.drafts[this.draftId] - if (this.draftId && maybeDraft) { - return maybeDraft - } else { - const existingDrafts = this.$store.getters.draftsByTypeAndRefId(statusType, refId) - - if (existingDrafts.length) { - return existingDrafts[0] - } - } - // No draft available, fall back - }, - requestClose () { - if (!this.savable) { - this.$emit('can-close') - } else { - this.$refs.draftCloser.requestClose() - } - }, - saveAndCloseDraft () { - this.saveDraft().then(() => { - this.$emit('can-close') - }) - }, - discardAndCloseDraft () { - this.abandonDraft().then(() => { - this.$emit('can-close') - }) - }, - addBeforeUnloadListener () { - this._beforeUnloadListener ||= () => { - this.saveDraft() - } - window.addEventListener('beforeunload', this._beforeUnloadListener) - }, - removeBeforeUnloadListener () { - if (this._beforeUnloadListener) { - window.removeEventListener('beforeunload', this._beforeUnloadListener) - } } } } diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index b7a169c58..e76b52a1c 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -262,7 +262,7 @@ v-if="pollsAvailable" ref="pollForm" :visible="pollFormVisible" - :params="newStatus.poll" + @update-poll="setPoll" />
- - {{ autoSaveState }} - -
- @@ -628,9 +610,5 @@ border-radius: var(--roundness); border: 2px dashed var(--text); } - - .auto-save-status { - align-self: center; - } } diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index 3ccf025c8..ba6b1faaf 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -45,11 +45,6 @@ const GeneralTab = { value: mode, label: this.$t(`settings.user_popover_avatar_action_${mode}`) })), - unsavedPostActionOptions: ['save', 'discard', 'confirm'].map(mode => ({ - key: mode, - value: mode, - label: this.$t(`settings.unsaved_post_action_${mode}`) - })), loopSilentAvailable: // Firefox Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') || diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index cc530996e..3cc83b547 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -476,22 +476,6 @@ {{ $t('settings.autocomplete_select_first') }} -
  • - - {{ $t('settings.auto_save_draft') }} - -
  • -
  • - - {{ $t('settings.unsaved_post_action') }} - -
  • diff --git a/src/components/settings_modal/tabs/style_tab/virtual_directives_tab.vue b/src/components/settings_modal/tabs/style_tab/virtual_directives_tab.vue index 158f0a70c..f151d3a87 100644 --- a/src/components/settings_modal/tabs/style_tab/virtual_directives_tab.vue +++ b/src/components/settings_modal/tabs/style_tab/virtual_directives_tab.vue @@ -72,9 +72,9 @@ :compact="true" /> state.instance.pleromaChatMessagesAvailable, supportsAnnouncements: state => state.announcements.supportsAnnouncements }), - ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount', 'draftCount']) + ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount']) }, methods: { toggleDrawer () { diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue index 7dd6ff28f..91b10ea08 100644 --- a/src/components/side_drawer/side_drawer.vue +++ b/src/components/side_drawer/side_drawer.vue @@ -255,27 +255,6 @@ -
  • - - {{ $t('nav.drafts') }} - - {{ draftCount }} - - -
  • - - - - + {{ ' ' }} + + {{ $t('status.reply_to') }} + + + + + + {{ $t('status.reply_to') }} + + @@ -622,15 +609,13 @@ class="status-container reply-form" > diff --git a/src/i18n/en.json b/src/i18n/en.json index b4e56bc11..874620ddd 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -192,8 +192,7 @@ "mobile_notifications_close": "Close notifications", "mobile_notifications_mark_as_seen": "Mark all as seen", "announcements": "Announcements", - "quotes": "Quotes", - "drafts": "Drafts" + "quotes": "Quotes" }, "notifications": { "broken_favorite": "Unknown status, searching for it…", @@ -312,16 +311,7 @@ "private": "Followers-only - post to followers only", "public": "Public - post to public timelines", "unlisted": "Unlisted - do not post to public timelines" - }, - "close_confirm_title": "Closing post form", - "close_confirm": "What do you want to do with your current writing?", - "close_confirm_save_button": "Save", - "close_confirm_discard_button": "Discard", - "close_confirm_continue_composing_button": "Continue composing", - "auto_save_nothing_new": "Nothing new to save.", - "auto_save_saved": "Saved.", - "auto_save_saving": "Saving...", - "save_to_drafts_button": "Save to drafts" + } }, "registration": { "bio_optional": "Bio (optional)", @@ -518,11 +508,6 @@ "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.", "pad_emoji": "Pad emoji with spaces when adding from picker", "autocomplete_select_first": "Automatically select the first candidate when autocomplete results are available", - "unsaved_post_action": "When you try to close an unsaved posting form", - "unsaved_post_action_save": "Save it to drafts", - "unsaved_post_action_discard": "Discard it", - "unsaved_post_action_confirm": "Ask every time", - "auto_save_draft": "Save drafts as you compose", "emoji_reactions_on_timeline": "Show emoji reactions on timeline", "emoji_reactions_scale": "Reactions scale factor", "absolute_time_format": "Use absolute time format", @@ -1233,8 +1218,6 @@ "delete_confirm_accept_button": "Delete", "delete_confirm_cancel_button": "Keep", "reply_to": "Reply to", - "reply_to_with_icon": "{icon} {replyTo}", - "reply_to_with_arg": "{replyToWithIcon} {user}", "mentions": "Mentions", "replies_list": "Replies:", "replies_list_with_others": "Replies (+{numReplies} other): | Replies (+{numReplies} others):", @@ -1511,18 +1494,6 @@ "unicode_domain_indicator": { "tooltip": "This domain contains non-ascii characters." }, - "drafts": { - "drafts": "Drafts", - "continue": "Continue composing", - "abandon": "Abandon draft", - "abandon_confirm_title": "Abandon confirmation", - "abandon_confirm": "Do you really want to abandon this draft?", - "abandon_confirm_accept_button": "Abandon", - "abandon_confirm_cancel_button": "Keep", - "replying": "Replying to {statusLink}", - "editing": "Editing {statusLink}", - "unavailable": "(unavailable)" - }, "splash": { "loading": "Loading...", "theme": "Applying theme, please wait warmly...", diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js index 035eee78d..22ba1e7f0 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, cloneDeep } from 'lodash' -import { storage } from './storage.js' let loaded = false @@ -26,7 +26,7 @@ const saveImmedeatelyActions = [ ] const defaultStorage = (() => { - return storage + return localforage })() export default function createPersistedState ({ diff --git a/src/lib/storage.js b/src/lib/storage.js deleted file mode 100644 index 25a49abc9..000000000 --- a/src/lib/storage.js +++ /dev/null @@ -1,3 +0,0 @@ -import localforage from 'localforage' - -export const storage = localforage diff --git a/src/main.js b/src/main.js index 6b634e742..eee463cc7 100644 --- a/src/main.js +++ b/src/main.js @@ -24,7 +24,6 @@ import pollsModule from './modules/polls.js' import postStatusModule from './modules/postStatus.js' import editStatusModule from './modules/editStatus.js' import statusHistoryModule from './modules/statusHistory.js' -import draftsModule from './modules/drafts.js' import chatsModule from './modules/chats.js' import announcementsModule from './modules/announcements.js' import bookmarkFoldersModule from './modules/bookmark_folders.js' @@ -125,7 +124,6 @@ const persistedStateOptions = { postStatus: postStatusModule, editStatus: editStatusModule, statusHistory: statusHistoryModule, - drafts: draftsModule, chats: chatsModule, announcements: announcementsModule, bookmarkFolders: bookmarkFoldersModule diff --git a/src/modules/config.js b/src/modules/config.js index e1e395855..72db9de03 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -30,8 +30,7 @@ export const multiChoiceProperties = [ 'conversationDisplay', // tree | linear 'conversationOtherRepliesButton', // below | inside 'mentionLinkDisplay', // short | full_for_remote | full - 'userPopoverAvatarAction', // close | zoom | open - 'unsavedPostAction' // save | discard | confirm + 'userPopoverAvatarAction' // close | zoom | open ] export const defaultState = { @@ -186,8 +185,6 @@ export const defaultState = { closingDrawerMarksAsSeen: undefined, // instance default unseenAtTop: undefined, // instance default ignoreInactionableSeen: undefined, // instance default - unsavedPostAction: undefined, // instance default - autoSaveDraft: undefined, // instance default useAbsoluteTimeFormat: undefined, // instance default absoluteTimeFormatMinAge: undefined // instance default } diff --git a/src/modules/drafts.js b/src/modules/drafts.js deleted file mode 100644 index 7a38fe2d5..000000000 --- a/src/modules/drafts.js +++ /dev/null @@ -1,86 +0,0 @@ -import { storage } from 'src/lib/storage.js' - -export const defaultState = { - drafts: {} -} - -export const mutations = { - addOrSaveDraft (state, { draft }) { - state.drafts[draft.id] = draft - }, - abandonDraft (state, { id }) { - delete state.drafts[id] - }, - loadDrafts (state, data) { - state.drafts = data - } -} - -const storageKey = 'pleroma-fe-drafts' - -/* - * Note: we do not use the persist state plugin because - * it is not impossible for a user to have two windows at - * the same time. The persist state plugin is just overriding - * everything with the current state. This isn't good because - * if a draft is created in one window and another draft is - * created in another, the draft in the first window will just - * be overriden. - * Here, we can't guarantee 100% atomicity unless one uses - * different keys, which will just pollute the whole storage. - * It is indeed best to have backend support for this. - */ -const getStorageData = async () => ((await storage.getItem(storageKey)) || {}) - -const saveDraftToStorage = async (draft) => { - const currentData = await getStorageData() - currentData[draft.id] = JSON.parse(JSON.stringify(draft)) - await storage.setItem(storageKey, currentData) -} - -const deleteDraftFromStorage = async (id) => { - const currentData = await getStorageData() - delete currentData[id] - await storage.setItem(storageKey, currentData) -} - -export const actions = { - async addOrSaveDraft (store, { draft }) { - const id = draft.id || (new Date().getTime()).toString() - const draftWithId = { ...draft, id } - store.commit('addOrSaveDraft', { draft: draftWithId }) - await saveDraftToStorage(draftWithId) - return id - }, - async abandonDraft (store, { id }) { - store.commit('abandonDraft', { id }) - await deleteDraftFromStorage(id) - }, - async loadDrafts (store) { - const currentData = await getStorageData() - store.commit('loadDrafts', currentData) - } -} - -export const getters = { - draftsByTypeAndRefId (state) { - return (type, refId) => { - return Object.values(state.drafts).filter(draft => draft.type === type && draft.refId === refId) - } - }, - draftsArray (state) { - return Object.values(state.drafts) - }, - draftCount (state) { - return Object.values(state.drafts).length - } -} - -const drafts = { - state: defaultState, - mutations, - getters, - actions -} - -export default drafts diff --git a/src/modules/instance.js b/src/modules/instance.js index 72e34628e..23d022c6f 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -124,8 +124,6 @@ const defaultState = { closingDrawerMarksAsSeen: true, unseenAtTop: false, ignoreInactionableSeen: false, - unsavedPostAction: 'confirm', - autoSaveDraft: false, useAbsoluteTimeFormat: false, absoluteTimeFormatMinAge: '0d', diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js index 15680c7de..d92bbbe05 100644 --- a/src/services/color_convert/color_convert.js +++ b/src/services/color_convert/color_convert.js @@ -52,6 +52,15 @@ const c2linear = (bit) => { } } +/** + * Converts sRGB into linear RGB + * @param {Object} srgb - sRGB color + * @returns {Object} linear rgb color + */ +const srgbToLinear = (srgb) => { + return 'rgb'.split('').reduce((acc, c) => { acc[c] = c2linear(srgb[c]); return acc }, {}) +} + /** * Calculates relative luminance for given color * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef @@ -61,10 +70,7 @@ const c2linear = (bit) => { * @returns {Number} relative luminance */ export const relativeLuminance = (srgb) => { - const r = c2linear(srgb.r) - const g = c2linear(srgb.g) - const b = c2linear(srgb.b) - + const { r, g, b } = srgbToLinear(srgb) return 0.2126 * r + 0.7152 * g + 0.0722 * b } @@ -104,17 +110,13 @@ export const getContrastRatioLayers = (text, layers, bedrock) => { * @returns {Object} sRGB of resulting color */ export const alphaBlend = (fg, fga, bg) => { - if (fga === 1 || typeof fga === 'undefined') { - return fg - } - - // Simplified https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending - // for opaque bg and transparent fg - return { - r: (fg.r * fga + bg.r * (1 - fga)), - g: (fg.g * fga + bg.g * (1 - fga)), - b: (fg.b * fga + bg.b * (1 - fga)) - } + if (fga === 1 || typeof fga === 'undefined') return fg + return 'rgb'.split('').reduce((acc, c) => { + // Simplified https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending + // for opaque bg and transparent fg + acc[c] = (fg[c] * fga + bg[c] * (1 - fga)) + return acc + }, {}) } /** @@ -128,11 +130,10 @@ export const alphaBlendLayers = (bedrock, layers) => layers.reduce((acc, [color, }, bedrock) export const invert = (rgb) => { - return { - r: 255 - rgb.r, - g: 255 - rgb.g, - b: 255 - rgb.b - } + return 'rgb'.split('').reduce((acc, c) => { + acc[c] = 255 - rgb[c] + return acc + }, {}) } /** @@ -143,7 +144,6 @@ export const invert = (rgb) => { */ export const hex2rgb = (hex) => { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) - return result ? { r: parseInt(result[1], 16), @@ -161,13 +161,11 @@ export const hex2rgb = (hex) => { * @returns {Object} result */ export const mixrgb = (a, b) => { - return { - r: (a.r + b.r) / 2, - g: (a.g + b.g) / 2, - b: (a.b + b.b) / 2 - } + return 'rgb'.split('').reduce((acc, k) => { + acc[k] = (a[k] + b[k]) / 2 + return acc + }, {}) } - /** * Converts rgb object into a CSS rgba() color * @@ -175,33 +173,7 @@ export const mixrgb = (a, b) => { * @returns {String} CSS rgba() color */ export const rgba2css = function (rgba) { - const base = { - r: 0, - g: 0, - b: 0, - a: 1 - } - - if (rgba !== null) { - if (rgba.r !== undefined && !isNaN(rgba.r)) { - base.r = rgba.r - } - if (rgba.g !== undefined && !isNaN(rgba.g)) { - base.g = rgba.g - } - if (rgba.b !== undefined && !isNaN(rgba.b)) { - base.b = rgba.b - } - if (rgba.a !== undefined && !isNaN(rgba.a)) { - base.a = rgba.a - } - } else { - base.r = 255 - base.g = 255 - base.b = 255 - } - - return `rgba(${Math.floor(base.r)}, ${Math.floor(base.g)}, ${Math.floor(base.b)}, ${base.a})` + return `rgba(${Math.floor(rgba.r)}, ${Math.floor(rgba.g)}, ${Math.floor(rgba.b)}, ${rgba.a ?? 1})` } /** diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index cbadac69f..550543e0b 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -442,9 +442,7 @@ export const parseNotification = (data) => { if (masto) { output.type = mastoDict[data.type] || data.type output.seen = data.pleroma.is_seen - // TODO: null check should be a temporary fix, I guess. - // Investigate why backend does this. - output.status = isStatusNotification(output.type) && data.status !== null ? parseStatus(data.status) : null + output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null output.target = output.type !== 'move' ? null : parseUser(data.target) diff --git a/src/services/poll/poll.service.js b/src/services/poll/poll.service.js deleted file mode 100644 index 90f8e87ae..000000000 --- a/src/services/poll/poll.service.js +++ /dev/null @@ -1,36 +0,0 @@ -import * as DateUtils from 'src/services/date_utils/date_utils.js' -import { uniq } from 'lodash' - -const pollFallbackValues = { - pollType: 'single', - options: ['', ''], - expiryAmount: 10, - expiryUnit: 'minutes' -} - -const pollFallback = (object, attr) => { - return object[attr] !== undefined ? object[attr] : pollFallbackValues[attr] -} - -const pollFormToMasto = (poll) => { - const expiresIn = DateUtils.unitToSeconds( - pollFallback(poll, 'expiryUnit'), - pollFallback(poll, 'expiryAmount') - ) - - const options = uniq(pollFallback(poll, 'options').filter(option => option !== '')) - if (options.length < 2) { - return { errorKey: 'polls.not_enough_options' } - } - - return { - options, - multiple: pollFallback(poll, 'pollType') === 'multiple', - expiresIn - } -} - -export { - pollFallback, - pollFormToMasto -} diff --git a/src/sw.js b/src/sw.js index 4a36672c1..8d790446a 100644 --- a/src/sw.js +++ b/src/sw.js @@ -1,6 +1,6 @@ /* eslint-env serviceworker */ -import { storage } from 'src/lib/storage.js' +import localForage from 'localforage' import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js' import { prepareNotificationObject } from './services/notification_utils/notification_utils.js' import { createI18n } from 'vue-i18n' @@ -25,7 +25,7 @@ function getWindowClients () { } const setSettings = async () => { - const vuexState = await storage.getItem('vuex-lz') + const vuexState = await localForage.getItem('vuex-lz') const locale = vuexState.config.interfaceLanguage || 'en' i18n.locale = locale const notificationsNativeArray = Object.entries(vuexState.config.notificationNative)