logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://anongit.hacktivis.me/git/pleroma-fe.git/
commit: 5c6d29193ef24b19c8b3f76eabb05663e107571c
parent a84424408dc787b922474e932f5e37758e20c189
Author: HJ <30-hj@users.noreply.git.pleroma.social>
Date:   Tue, 31 Dec 2024 11:17:22 +0000

Merge branch 'drafts-improvements' into 'develop'

Drafts improvements

See merge request pleroma/pleroma-fe!1964

Diffstat:

Achangelog.d/drafts-imp.skip0
Msrc/App.scss25++++++++++++++++++-------
Msrc/components/draft/draft.js42+++++++++++++++++++++++++++++++++++++++++-
Msrc/components/draft/draft.vue117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/components/drafts/drafts.vue12++++++++++++
Msrc/components/menu_item.style.js27+++++++++++++++++++++++++--
Msrc/components/mobile_nav/mobile_nav.vue3+++
Msrc/components/nav_panel/nav_panel.vue5-----
Msrc/components/navigation/navigation_entry.vue26+++++++++-----------------
Msrc/components/post_status_form/post_status_form.js43++++++++++++++++++++++++++++---------------
Asrc/components/post_status_form/post_status_form.scss258+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/post_status_form/post_status_form.vue333+++++++++++++------------------------------------------------------------------
Msrc/i18n/en.json4++++
13 files changed, 547 insertions(+), 348 deletions(-)

diff --git a/changelog.d/drafts-imp.skip b/changelog.d/drafts-imp.skip diff --git a/src/App.scss b/src/App.scss @@ -390,6 +390,24 @@ nav { } } +.menu-item { + line-height: var(--__line-height); + font-family: inherit; + font-weight: 400; + font-size: 100%; + cursor: pointer; + + a, + button:not(.button-default) { + color: var(--text); + font-size: 100%; + } + + &.disabled { + cursor: not-allowed; + } +} + .menu-item, .list-item { display: block; @@ -397,10 +415,6 @@ nav { border: none; outline: none; text-align: initial; - font-size: inherit; - font-family: inherit; - font-weight: 400; - cursor: pointer; color: inherit; clear: both; position: relative; @@ -410,7 +424,6 @@ nav { border-width: 0; border-top-width: 1px; width: 100%; - line-height: var(--__line-height); padding: var(--__vertical-gap) var(--__horizontal-gap); background: transparent; @@ -450,10 +463,8 @@ nav { border: none; outline: none; display: inline; - font-size: 100%; font-family: inherit; line-height: unset; - color: var(--text); } &:first-child { diff --git a/src/components/draft/draft.js b/src/components/draft/draft.js @@ -2,13 +2,25 @@ 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' +import Gallery from 'src/components/gallery/gallery.vue' +import { cloneDeep } from 'lodash' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { + faPollH +} from '@fortawesome/free-solid-svg-icons' + +library.add( + faPollH +) const Draft = { components: { PostStatusForm, EditStatusForm, ConfirmModal, - StatusContent + StatusContent, + Gallery }, props: { draft: { @@ -18,6 +30,7 @@ const Draft = { }, data () { return { + referenceDraft: cloneDeep(this.draft), editing: false, showingConfirmDialog: false } @@ -32,6 +45,11 @@ const Draft = { return {} } }, + safeToSave () { + return this.draft.status || + this.draft.files?.length || + this.draft.hasPoll + }, postStatusFormProps () { return { draftId: this.draft.id, @@ -40,6 +58,28 @@ const Draft = { }, refStatus () { return this.draft.refId ? this.$store.state.statuses.allStatusesObject[this.draft.refId] : undefined + }, + localCollapseSubjectDefault () { + return this.$store.getters.mergedConfig.collapseMessageWithSubject + }, + nsfwClickthrough () { + if (!this.draft.nsfw) { + return false + } + if (this.draft.summary && this.localCollapseSubjectDefault) { + return false + } + return true + } + }, + watch: { + editing (newVal) { + if (newVal) return + if (this.safeToSave) { + this.$store.dispatch('addOrSaveDraft', { draft: this.draft }) + } else { + this.$store.dispatch('addOrSaveDraft', { draft: this.referenceDraft }) + } } }, methods: { diff --git a/src/components/draft/draft.vue b/src/components/draft/draft.vue @@ -1,21 +1,5 @@ <template> <article class="Draft"> - <div class="actions"> - <button - class="btn button-default" - :class="{ toggled: editing }" - :aria-expanded="editing" - @click.prevent.stop="toggleEditing" - > - {{ $t('drafts.continue') }} - </button> - <button - class="btn button-default" - @click.prevent.stop="abandon" - > - {{ $t('drafts.abandon') }} - </button> - </div> <div v-if="!editing" class="status-content" @@ -42,15 +26,50 @@ :compact="true" /> </div> - <p>{{ draft.status }}</p> + <div class="status-preview"> + <span class="status_content"> + <p v-if="draft.spoilerText"> + <i> + {{ draft.spoilerText }}: + </i> + </p> + <p v-if="draft.status">{{ draft.status }}</p> + <p v-else class="faint">{{ $t('drafts.empty') }}</p> + </span> + <gallery + v-if="draft.files?.length !== 0" + class="attachments media-body" + :compact="true" + :nsfw="nsfwClickthrough" + :attachments="draft.files" + :limit="1" + size="small" + @play="$emit('mediaplay', attachment.id)" + @pause="$emit('mediapause', attachment.id)" + /> + <div + v-if="draft.poll.options" + class="poll-indicator-container" + :title="$t('drafts.poll_tooltip')" + > + <div class="poll-indicator"> + <FAIcon + icon="poll-h" + size="3x" + /> + </div> + </div> + </div> </div> <div v-if="editing"> <PostStatusForm v-if="draft.type !== 'edit'" + :disable-draft="true" v-bind="postStatusFormProps" /> <EditStatusForm v-else + :disable-draft="true" :params="postStatusFormProps" /> </div> @@ -66,6 +85,21 @@ {{ $t('drafts.abandon_confirm') }} </confirm-modal> </teleport> + <div class="actions"> + <button + class="btn button-default" + :aria-expanded="editing" + @click.prevent.stop="toggleEditing" + > + {{ editing ? $t('drafts.save') : $t('drafts.continue') }} + </button> + <button + class="btn button-default" + @click.prevent.stop="abandon" + > + {{ $t('drafts.abandon') }} + </button> + </div> </article> </template> @@ -73,17 +107,55 @@ <style lang="scss"> .Draft { - margin: 1em; + position: relative; .status-content { - border: 1px solid; - border-color: var(--faint); - border-radius: var(--inputRadius); - color: var(--text); padding: 0.5em; margin: 0.5em 0; } + .status-preview { + display: grid; + grid-template-columns: 1fr; + grid-auto-columns: 10em; + grid-auto-flow: column; + grid-gap: 0.5em; + align-items: start; + max-width: 100%; + + p { + word-wrap: break-word; + white-space: normal; + overflow-x: hidden; + } + + .poll-indicator-container { + border-radius: var(--roundness); + display: grid; + justify-items: center; + align-items: center; + align-self: start; + height: 0; + padding-bottom: 62.5%; + position: relative; + } + + .poll-indicator { + box-sizing: border-box; + border: 1px solid var(--border); + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + display: grid; + justify-items: center; + align-items: center; + width: 100%; + height: 100%; + } + } + .actions { display: flex; flex-direction: row; @@ -93,7 +165,6 @@ flex: 1; margin-left: 1em; margin-right: 1em; - max-width: 10em; } } } diff --git a/src/components/drafts/drafts.vue b/src/components/drafts/drafts.vue @@ -7,11 +7,17 @@ </div> </div> <div class="panel-body"> + <p v-if="drafts.length === 0"> + {{ $t('drafts.no_drafts') }} + </p> <List + v-else :items="drafts" + :non-interactive="true" > <template #item="{ item: draft }"> <Draft + class="draft" :draft="draft" /> </template> @@ -22,3 +28,9 @@ </template> <script src="./drafts.js"></script> + +<style lang="scss"> +.draft { + margin: 1em 0; +} +</style> diff --git a/src/components/menu_item.style.js b/src/components/menu_item.style.js @@ -11,8 +11,9 @@ export default { 'Avatar' ], states: { - hover: ':hover', - active: '.-active' + hover: ':hover:not(.disabled)', + active: '.-active', + disabled: '.disabled' }, defaultRules: [ { @@ -85,6 +86,28 @@ export default { textColor: '--link', textAuto: 'no-preserve' } + }, + { + component: 'Text', + parent: { + component: 'MenuItem', + state: ['disabled'] + }, + directives: { + textOpacity: 0.25, + textOpacityMode: 'blend' + } + }, + { + component: 'Icon', + parent: { + component: 'MenuItem', + state: ['disabled'] + }, + directives: { + textOpacity: 0.25, + textOpacityMode: 'blend' + } } ] } diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue @@ -202,6 +202,9 @@ .title { font-size: 1.3em; margin-left: 0.6em; + white-space: nowrap; + overflow-x: hidden; + text-overflow: ellipsis; } } diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue @@ -154,11 +154,6 @@ font-size: 1.1em; } - .timelines-chevron { - margin-left: 0.8em; - font-size: 1.1em; - } - .timelines-background { padding: 0 0 0 0.6em; } diff --git a/src/components/navigation/navigation_entry.vue b/src/components/navigation/navigation_entry.vue @@ -82,8 +82,12 @@ --__horizontal-gap: 0.5em; --__vertical-gap: 0.4em; - padding: 0; - display: flex; + padding: var(--__vertical-gap) var(--__horizontal-gap); + display: grid; + grid-template-columns: 1fr; + grid-auto-columns: var(--__line-height); + grid-auto-flow: column; + grid-gap: var(--__horizontal-gap); align-items: baseline; &[aria-expanded] { @@ -93,8 +97,6 @@ .main-link { line-height: var(--__line-height); box-sizing: border-box; - flex: 1; - padding: var(--__vertical-gap) var(--__horizontal-gap); } .menu-icon { @@ -104,26 +106,16 @@ margin-right: var(--__horizontal-gap); } - .timelines-chevron { - line-height: var(--__line-height); - padding: 0; - width: var(--__line-height); - margin-right: 0; - } - + .timelines-chevron, .extra-button { line-height: var(--__line-height); + width: 100%; padding: 0; - width: var(--__line-height); text-align: center; - - &:last-child { - margin-right: calc(-1 * var(--__horizontal-gap)); - } } .badge { - margin: 0 var(--__horizontal-gap); + justify-self: center; } .iconEmoji { diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js @@ -7,6 +7,7 @@ import PollForm from '../poll/poll_form.vue' import Attachment from '../attachment/attachment.vue' import Gallery from 'src/components/gallery/gallery.vue' import StatusContent from '../status_content/status_content.vue' +import Popover from 'src/components/popover/popover.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' @@ -25,7 +26,10 @@ import { faUpload, faBan, faTimes, - faCircleNotch + faCircleNotch, + faChevronDown, + faChevronLeft, + faChevronRight } from '@fortawesome/free-solid-svg-icons' library.add( @@ -34,7 +38,10 @@ library.add( faUpload, faBan, faTimes, - faCircleNotch + faCircleNotch, + faChevronDown, + faChevronLeft, + faChevronRight ) const buildMentionsString = ({ user, attentions = [] }, currentUser) => { @@ -111,7 +118,8 @@ const PostStatusForm = { 'resize', 'mediaplay', 'mediapause', - 'can-close' + 'can-close', + 'update' ], components: { MediaUpload, @@ -123,7 +131,8 @@ const PostStatusForm = { Attachment, StatusContent, Gallery, - DraftCloser + DraftCloser, + Popover }, mounted () { this.updateIdempotencyKey() @@ -210,7 +219,7 @@ const PostStatusForm = { emojiInputShown: false, idempotencyKey: '', saveInhibited: true, - savable: false + saveable: false } }, computed: { @@ -335,7 +344,7 @@ const PostStatusForm = { return this.$store.getters.mergedConfig.autoSaveDraft }, autoSaveState () { - if (this.savable) { + if (this.saveable) { return this.$t('post_status.auto_save_saving') } else if (this.newStatus.id) { return this.$t('post_status.auto_save_saved') @@ -343,6 +352,12 @@ const PostStatusForm = { return this.$t('post_status.auto_save_nothing_new') } }, + safeToSaveDraft () { + return this.newStatus.status || + this.newStatus.spoilerText || + this.newStatus.files?.length || + this.newStatus.hasPoll + }, ...mapGetters(['mergedConfig']), ...mapState({ mobileLayout: state => state.interface.mobileLayout @@ -355,7 +370,7 @@ const PostStatusForm = { this.statusChanged() } }, - savable (val) { + saveable (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) { @@ -374,7 +389,7 @@ const PostStatusForm = { this.autoPreview() this.updateIdempotencyKey() this.debouncedMaybeAutoSaveDraft() - this.savable = true + this.saveable = true this.saveInhibited = false }, clearStatus () { @@ -403,7 +418,7 @@ const PostStatusForm = { el.style.height = undefined this.error = null if (this.preview) this.previewStatus() - this.savable = false + this.saveable = false }, async postStatus (event, newStatus, opts = {}) { if (this.posting && !this.optimisticPosting) { return } @@ -738,21 +753,19 @@ const PostStatusForm = { saveDraft () { if (!this.disableDraft && !this.saveInhibited) { - if (this.newStatus.status || - this.newStatus.files?.length || - this.newStatus.hasPoll) { + if (this.safeToSaveDraft) { return this.$store.dispatch('addOrSaveDraft', { draft: this.newStatus }) .then(id => { if (this.newStatus.id !== id) { this.newStatus.id = id } - this.savable = false + this.saveable = 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 + this.saveable = false }) } } @@ -780,7 +793,7 @@ const PostStatusForm = { // No draft available, fall back }, requestClose () { - if (!this.savable) { + if (!this.saveable) { this.$emit('can-close') } else { this.$refs.draftCloser.requestClose() diff --git a/src/components/post_status_form/post_status_form.scss b/src/components/post_status_form/post_status_form.scss @@ -0,0 +1,258 @@ +.post-status-form { + position: relative; + + .attachments { + margin-bottom: 0.5em; + } + + .more-post-actions { + height: 100%; + + .btn { + height: 100%; + } + } + + .form-bottom { + display: flex; + justify-content: space-between; + padding: 0.5em; + height: 2.5em; + + .post-button-group { + width: 10em; + display: flex; + + .post-button { + flex: 1 0 auto; + } + + .more-post-actions { + flex: 0 0 auto; + } + } + + p { + margin: 0.35em; + padding: 0.35em; + display: flex; + } + } + + .form-bottom-left { + display: flex; + flex: 1; + padding-right: 7px; + margin-right: 7px; + max-width: 10em; + } + + .preview-heading { + display: flex; + flex-wrap: wrap; + } + + .preview-toggle { + flex: 10 0 auto; + cursor: pointer; + user-select: none; + padding-left: 0.5em; + + &:hover { + text-decoration: underline; + } + + svg, + i { + margin-left: 0.2em; + font-size: 0.8em; + transform: rotate(90deg); + } + } + + .preview-container { + margin-bottom: 1em; + } + + .preview-error { + font-style: italic; + color: var(--textFaint); + } + + .preview-status { + border: 1px solid var(--border); + border-radius: var(--roundness); + padding: 0.5em; + margin: 0; + } + + .reply-or-quote-selector { + flex: 1 0 auto; + margin-bottom: 0.5em; + display: grid; + grid-template-columns: 1fr 1fr; + } + + .text-format { + .only-format { + color: var(--textFaint); + } + } + + .visibility-tray { + display: flex; + justify-content: space-between; + padding-top: 5px; + align-items: baseline; + } + + .visibility-notice.edit-warning { + > :first-child { + margin-top: 0; + } + + > :last-child { + margin-bottom: 0; + } + } + + // Order is not necessary but a good indicator + .media-upload-icon { + order: 1; + justify-content: left; + } + + .emoji-icon { + order: 2; + justify-content: center; + } + + .poll-icon { + order: 3; + justify-content: right; + } + + .media-upload-icon, + .poll-icon, + .emoji-icon { + font-size: 1.85em; + line-height: 1.1; + flex: 1; + padding: 0 0.1em; + display: flex; + align-items: center; + } + + .error { + text-align: center; + } + + .media-upload-wrapper { + margin-right: 0.2em; + margin-bottom: 0.5em; + width: 18em; + + img, + video { + object-fit: contain; + max-height: 10em; + } + + .video { + max-height: 10em; + } + + input { + flex: 1; + width: 100%; + } + } + + .status-input-wrapper { + display: flex; + position: relative; + width: 100%; + flex-direction: column; + } + + .btn[disabled] { + cursor: not-allowed; + } + + form { + display: flex; + flex-direction: column; + margin: 0.6em; + position: relative; + } + + .form-group { + display: flex; + flex-direction: column; + padding: 0.25em 0.5em 0.5em; + line-height: 1.85; + } + + .input.form-post-body { + // TODO: make a resizable textarea component? + box-sizing: content-box; // needed for easier computation of dynamic size + overflow: hidden; + transition: min-height 200ms 100ms; + // stock padding + 1 line of text (for counter) + padding-bottom: calc(var(--_padding) + var(--post-line-height) * 1em); + // two lines of text + height: calc(var(--post-line-height) * 1em); + min-height: calc(var(--post-line-height) * 1em); + resize: none; + background: transparent; + + &.scrollable-form { + overflow-y: auto; + } + } + + .main-input { + position: relative; + } + + .character-counter { + position: absolute; + bottom: 0; + right: 0; + padding: 0; + margin: 0 0.5em; + + &.error { + color: var(--cRed); + } + } + + @keyframes fade-in { + from { opacity: 0; } + to { opacity: 0.6; } + } + + @keyframes fade-out { + from { opacity: 0.6; } + to { opacity: 0; } + } + + .drop-indicator { + position: absolute; + width: 100%; + height: 100%; + font-size: 5em; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.6; + color: var(--text); + background-color: var(--bg); + border-radius: var(--roundness); + border: 2px dashed var(--text); + } + + .auto-save-status { + align-self: center; + } +} diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue @@ -264,6 +264,12 @@ :visible="pollFormVisible" :params="newStatus.poll" /> + <span + v-if="!disableDraft && shouldAutoSaveDraft" + class="auto-save-status" + > + {{ autoSaveState }} + </span> <div ref="bottom" class="form-bottom" @@ -296,41 +302,54 @@ <FAIcon icon="poll-h" /> </button> </div> - <span - v-if="!disableDraft && shouldAutoSaveDraft" - class="auto-save-status" - > - {{ autoSaveState }} - </span> - <button - v-else-if="!disableDraft" - class="btn button-default" - @click="saveDraft" - > - {{ $t('post_status.save_to_drafts_button') }} - </button> - <button - v-if="posting" - disabled - class="btn button-default" - > - {{ $t('post_status.posting') }} - </button> - <button - v-else-if="isOverLengthLimit" - disabled - class="btn button-default" - > - {{ $t('post_status.post') }} - </button> - <button - v-else - :disabled="uploadingFiles || disableSubmit" - class="btn button-default" - @click.stop.prevent="postStatus($event, newStatus)" - > - {{ $t('post_status.post') }} - </button> + <div class="btn-group post-button-group"> + <button + class="btn button-default post-button" + :disabled="isOverLengthLimit || posting || uploadingFiles || disableSubmit" + @click.stop.prevent="postStatus($event, newStatus)" + > + <template v-if="posting"> + {{ $t('post_status.posting') }} + </template> + <template v-else> + {{ $t('post_status.post') }} + </template> + </button> + <Popover + v-if="!disableDraft" + class="more-post-actions" + :normal-button="true" + trigger="click" + placement="bottom" + :offset="{ y: 5 }" + :bound-to="{ x: 'container' }" + > + <template #trigger> + <FAIcon + class="fa-scale-110 fa-old-padding" + icon="chevron-down" + /> + </template> + <template #content="{close}"> + <div + class="dropdown-menu" + role="menu" + > + <button + v-if="!disableDraft" + class="menu-item dropdown-item dropdown-item-icon" + role="menu" + :disabled="!safeToSaveDraft" + :class="{ disabled: !safeToSaveDraft }" + @click.prevent="saveDraft" + @click="close" + > + {{ $t('post_status.save_to_drafts_button') }} + </button> + </div> + </template> + </Popover> + </div> </div> <div v-show="showDropIcon !== 'hide'" @@ -391,246 +410,4 @@ <script src="./post_status_form.js"></script> -<style lang="scss"> -.post-status-form { - position: relative; - - .attachments { - margin-bottom: 0.5em; - } - - .form-bottom { - display: flex; - justify-content: space-between; - padding: 0.5em; - height: 2.5em; - - button { - width: 10em; - } - - p { - margin: 0.35em; - padding: 0.35em; - display: flex; - } - } - - .form-bottom-left { - display: flex; - flex: 1; - padding-right: 7px; - margin-right: 7px; - max-width: 10em; - } - - .preview-heading { - display: flex; - flex-wrap: wrap; - } - - .preview-toggle { - flex: 10 0 auto; - cursor: pointer; - user-select: none; - padding-left: 0.5em; - - &:hover { - text-decoration: underline; - } - - svg, - i { - margin-left: 0.2em; - font-size: 0.8em; - transform: rotate(90deg); - } - } - - .preview-container { - margin-bottom: 1em; - } - - .preview-error { - font-style: italic; - color: var(--textFaint); - } - - .preview-status { - border: 1px solid var(--border); - border-radius: var(--roundness); - padding: 0.5em; - margin: 0; - } - - .reply-or-quote-selector { - flex: 1 0 auto; - margin-bottom: 0.5em; - display: grid; - grid-template-columns: 1fr 1fr; - } - - .text-format { - .only-format { - color: var(--textFaint); - } - } - - .visibility-tray { - display: flex; - justify-content: space-between; - padding-top: 5px; - align-items: baseline; - } - - .visibility-notice.edit-warning { - > :first-child { - margin-top: 0; - } - - > :last-child { - margin-bottom: 0; - } - } - - // Order is not necessary but a good indicator - .media-upload-icon { - order: 1; - justify-content: left; - } - - .emoji-icon { - order: 2; - justify-content: center; - } - - .poll-icon { - order: 3; - justify-content: right; - } - - .media-upload-icon, - .poll-icon, - .emoji-icon { - font-size: 1.85em; - line-height: 1.1; - flex: 1; - padding: 0 0.1em; - display: flex; - align-items: center; - } - - .error { - text-align: center; - } - - .media-upload-wrapper { - margin-right: 0.2em; - margin-bottom: 0.5em; - width: 18em; - - img, - video { - object-fit: contain; - max-height: 10em; - } - - .video { - max-height: 10em; - } - - input { - flex: 1; - width: 100%; - } - } - - .status-input-wrapper { - display: flex; - position: relative; - width: 100%; - flex-direction: column; - } - - .btn[disabled] { - cursor: not-allowed; - } - - form { - display: flex; - flex-direction: column; - margin: 0.6em; - position: relative; - } - - .form-group { - display: flex; - flex-direction: column; - padding: 0.25em 0.5em 0.5em; - line-height: 1.85; - } - - .input.form-post-body { - // TODO: make a resizable textarea component? - box-sizing: content-box; // needed for easier computation of dynamic size - overflow: hidden; - transition: min-height 200ms 100ms; - // stock padding + 1 line of text (for counter) - padding-bottom: calc(var(--_padding) + var(--post-line-height) * 1em); - // two lines of text - height: calc(var(--post-line-height) * 1em); - min-height: calc(var(--post-line-height) * 1em); - resize: none; - background: transparent; - - &.scrollable-form { - overflow-y: auto; - } - } - - .main-input { - position: relative; - } - - .character-counter { - position: absolute; - bottom: 0; - right: 0; - padding: 0; - margin: 0 0.5em; - - &.error { - color: var(--cRed); - } - } - - @keyframes fade-in { - from { opacity: 0; } - to { opacity: 0.6; } - } - - @keyframes fade-out { - from { opacity: 0.6; } - to { opacity: 0; } - } - - .drop-indicator { - position: absolute; - width: 100%; - height: 100%; - font-size: 5em; - display: flex; - align-items: center; - justify-content: center; - opacity: 0.6; - color: var(--text); - background-color: var(--bg); - border-radius: var(--roundness); - border: 2px dashed var(--text); - } - - .auto-save-status { - align-self: center; - } -} -</style> +<style src="./post_status_form.scss" lang="scss"></style> diff --git a/src/i18n/en.json b/src/i18n/en.json @@ -1513,7 +1513,11 @@ }, "drafts": { "drafts": "Drafts", + "no_drafts": "You have no drafts", + "empty": "(No content)", + "poll_tooltip": "Draft contains a poll", "continue": "Continue composing", + "save": "Save without posting", "abandon": "Abandon draft", "abandon_confirm_title": "Abandon confirmation", "abandon_confirm": "Do you really want to abandon this draft?",