Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85824550
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
21 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index b48d7c65c8..6d13e66d97 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -1,186 +1,187 @@
<template>
<div
ref="root"
v-click-outside="onClickOutside"
class="emoji-input"
:class="{ 'with-picker': !hideEmojiButton }"
>
<slot />
<template v-if="enableEmojiPicker">
<button
v-if="!hideEmojiButton"
class="button-unstyled emoji-picker-icon"
type="button"
:title="$t('emoji.add_emoji')"
+ :aria-expanded="showPicker"
@click.prevent="togglePicker"
>
<FAIcon :icon="['far', 'smile-beam']" />
</button>
<EmojiPicker
v-if="enableEmojiPicker"
ref="picker"
:class="{ hide: !showPicker }"
:enable-sticker-picker="enableStickerPicker"
class="emoji-picker-panel"
@emoji="insert"
@sticker-uploaded="onStickerUploaded"
@sticker-upload-failed="onStickerUploadFailed"
/>
</template>
<div
ref="panel"
class="autocomplete-panel"
:class="{ hide: !showSuggestions }"
>
<div
ref="panel-body"
class="autocomplete-panel-body"
>
<div
v-for="(suggestion, index) in suggestions"
:key="index"
class="autocomplete-item"
:class="{ highlighted: index === highlighted }"
@click.stop.prevent="onClick($event, suggestion)"
>
<span class="image">
<img
v-if="suggestion.img"
:src="suggestion.img"
>
<span v-else>{{ suggestion.replacement }}</span>
</span>
<div class="label">
<span class="displayText">{{ suggestion.displayText }}</span>
<span class="detailText">{{ suggestion.detailText }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script src="./emoji_input.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.emoji-input {
display: flex;
flex-direction: column;
position: relative;
&.with-picker input {
padding-right: 30px;
}
.emoji-picker-icon {
position: absolute;
top: 0;
right: 0;
margin: .2em .25em;
font-size: 16px;
cursor: pointer;
line-height: 24px;
&:hover i {
color: $fallback--text;
color: var(--text, $fallback--text);
}
}
.emoji-picker-panel {
position: absolute;
z-index: 20;
margin-top: 2px;
&.hide {
display: none
}
}
.autocomplete {
&-panel {
position: absolute;
z-index: 20;
margin-top: 2px;
&.hide {
display: none
}
&-body {
margin: 0 0.5em 0 0.5em;
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5);
box-shadow: var(--popupShadow);
min-width: 75%;
background-color: $fallback--bg;
background-color: var(--popover, $fallback--bg);
color: $fallback--link;
color: var(--popoverText, $fallback--link);
--faint: var(--popoverFaintText, $fallback--faint);
--faintLink: var(--popoverFaintLink, $fallback--faint);
--lightText: var(--popoverLightText, $fallback--lightText);
--postLink: var(--popoverPostLink, $fallback--link);
--postFaintLink: var(--popoverPostFaintLink, $fallback--link);
--icon: var(--popoverIcon, $fallback--icon);
}
}
&-item {
display: flex;
cursor: pointer;
padding: 0.2em 0.4em;
border-bottom: 1px solid rgba(0, 0, 0, 0.4);
height: 32px;
.image {
width: 32px;
height: 32px;
line-height: 32px;
text-align: center;
font-size: 32px;
margin-right: 4px;
img {
width: 32px;
height: 32px;
object-fit: contain;
}
}
.label {
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 0.1em 0 0.2em;
.displayText {
line-height: 1.5;
}
.detailText {
font-size: 9px;
line-height: 9px;
}
}
&.highlighted {
background-color: $fallback--fg;
background-color: var(--selectedMenuPopover, $fallback--fg);
color: var(--selectedMenuPopoverText, $fallback--text);
--faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
--faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
--lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
--icon: var(--selectedMenuPopoverIcon, $fallback--icon);
}
}
}
input, textarea {
flex: 1 0 auto;
}
}
</style>
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 4485c3b120..44aec7ba7b 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -1,602 +1,604 @@
<template>
<div
ref="form"
class="post-status-form"
>
<form
autocomplete="off"
@submit.prevent
@dragover.prevent="fileDrag"
>
<div
v-show="showDropIcon !== 'hide'"
:style="{ animation: showDropIcon === 'show' ? 'fade-in 0.25s' : 'fade-out 0.5s' }"
class="drop-indicator"
@dragleave="fileDragStop"
@drop.stop="fileDrop"
>
<FAIcon :icon="uploadFileLimitReached ? 'ban' : 'upload'" />
</div>
<div class="form-group">
<i18n-t
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private' && !disableLockWarning"
keypath="post_status.account_not_locked_warning"
tag="p"
class="visibility-notice"
scope="global"
>
<a
class="button-unstyled -link"
role="button"
:title="$t('post_status.scope_notice_dismiss')"
@click="openProfileTab"
>
{{ $t('post_status.account_not_locked_warning_link') }}
</a>
</i18n-t>
<p
v-if="!hideScopeNotice && newStatus.visibility === 'public'"
class="visibility-notice notice-dismissible"
>
<span>{{ $t('post_status.scope_notice.public') }}</span>
<a
class="fa-scale-110 fa-old-padding dismiss"
role="button"
:title="$t('post_status.scope_notice_dismiss')"
@click.prevent="dismissScopeNotice()"
>
<FAIcon icon="times" />
</a>
</p>
<p
v-else-if="!hideScopeNotice && newStatus.visibility === 'unlisted'"
class="visibility-notice notice-dismissible"
>
<span>{{ $t('post_status.scope_notice.unlisted') }}</span>
<a
class="fa-scale-110 fa-old-padding dismiss"
role="button"
:title="$t('post_status.scope_notice_dismiss')"
@click.prevent="dismissScopeNotice()"
>
<FAIcon icon="times" />
</a>
</p>
<p
v-else-if="!hideScopeNotice && newStatus.visibility === 'private' && $store.state.users.currentUser.locked"
class="visibility-notice notice-dismissible"
>
<span>{{ $t('post_status.scope_notice.private') }}</span>
<a
class="fa-scale-110 fa-old-padding dismiss"
role="button"
:title="$t('post_status.scope_notice_dismiss')"
@click.prevent="dismissScopeNotice()"
>
<FAIcon icon="times" />
</a>
</p>
<p
v-else-if="newStatus.visibility === 'direct'"
class="visibility-notice"
>
<span v-if="safeDMEnabled">{{ $t('post_status.direct_warning_to_first_only') }}</span>
<span v-else>{{ $t('post_status.direct_warning_to_all') }}</span>
</p>
<div
v-if="!disablePreview"
class="preview-heading faint"
>
<a
class="preview-toggle faint"
@click.stop.prevent="togglePreview"
>
{{ $t('post_status.preview') }}
<FAIcon :icon="showPreview ? 'chevron-left' : 'chevron-right'" />
</a>
<div
v-show="previewLoading"
class="preview-spinner"
>
<FAIcon
class="fa-old-padding"
spin
icon="circle-notch"
/>
</div>
</div>
<div
v-if="showPreview"
class="preview-container"
>
<div
v-if="!preview"
class="preview-status"
>
{{ $t('general.loading') }}
</div>
<div
v-else-if="preview.error"
class="preview-status preview-error"
>
{{ preview.error }}
</div>
<StatusContent
v-else
:status="preview"
class="preview-status"
/>
</div>
<EmojiInput
v-if="!disableSubject && (newStatus.spoilerText || alwaysShowSubject)"
v-model="newStatus.spoilerText"
enable-emoji-picker
:suggest="emojiSuggestor"
class="form-control"
>
<input
v-model="newStatus.spoilerText"
type="text"
:placeholder="$t('post_status.content_warning')"
:disabled="posting && !optimisticPosting"
:aria-label="$t('post_status.content_warning')"
size="1"
class="form-post-subject"
>
</EmojiInput>
<EmojiInput
ref="emoji-input"
v-model="newStatus.status"
:suggest="emojiUserSuggestor"
:placement="emojiPickerPlacement"
class="form-control main-input"
enable-emoji-picker
hide-emoji-button
:newline-on-ctrl-enter="submitOnEnter"
enable-sticker-picker
@input="onEmojiInputInput"
@sticker-uploaded="addMediaFile"
@sticker-upload-failed="uploadFailed"
@shown="handleEmojiInputShow"
>
<textarea
ref="textarea"
v-model="newStatus.status"
:placeholder="placeholder || $t('post_status.default')"
:aria-label="$t('post_status.textarea_label')"
rows="1"
cols="1"
:disabled="posting && !optimisticPosting"
class="form-post-body"
:class="{ 'scrollable-form': !!maxHeight }"
@keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
@keydown.meta.enter="postStatus($event, newStatus)"
@keydown.ctrl.enter="!submitOnEnter && postStatus($event, newStatus)"
@input="resize"
@compositionupdate="resize"
@paste="paste"
/>
<p
v-if="hasStatusLengthLimit"
class="character-counter faint"
:class="{ error: isOverLengthLimit }"
>
{{ charactersLeft }}
</p>
</EmojiInput>
<div
v-if="!disableScopeSelector"
class="visibility-tray"
>
<scope-selector
:show-all="showAllScopes"
:user-default="userDefaultScope"
:original-scope="copyMessageScope"
:initial-scope="newStatus.visibility"
:on-scope-change="changeVis"
/>
<div
v-if="postFormats.length > 1"
class="text-format"
>
<Select
id="post-content-type"
v-model="newStatus.contentType"
class="form-control"
>
<option
v-for="postFormat in postFormats"
:key="postFormat"
:value="postFormat"
>
{{ $t(`post_status.content_type["${postFormat}"]`) }}
</option>
</Select>
</div>
<div
v-if="postFormats.length === 1 && postFormats[0] !== 'text/plain'"
class="text-format"
>
<span class="only-format">
{{ $t(`post_status.content_type["${postFormats[0]}"]`) }}
</span>
</div>
</div>
</div>
<poll-form
v-if="pollsAvailable"
ref="pollForm"
:visible="pollFormVisible"
@update-poll="setPoll"
/>
<div
ref="bottom"
class="form-bottom"
>
<div class="form-bottom-left">
<media-upload
ref="mediaUpload"
class="media-upload-icon"
:drop-files="dropFiles"
:disabled="uploadFileLimitReached"
@uploading="startedUploadingFiles"
@uploaded="addMediaFile"
@upload-failed="uploadFailed"
@all-uploaded="finishedUploadingFiles"
/>
<button
class="emoji-icon button-unstyled"
:title="$t('emoji.add_emoji')"
+ :aria-expanded="$refs['emoji-input'] ? $refs['emoji-input'].showPicker : false"
@click="showEmojiPicker"
>
<FAIcon icon="smile-beam" />
</button>
<button
v-if="pollsAvailable"
class="poll-icon button-unstyled"
:class="{ selected: pollFormVisible }"
:title="$t('polls.add_poll')"
+ :aria-expanded="pollFormVisible"
@click="togglePollForm"
>
<FAIcon icon="poll-h" />
</button>
</div>
<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>
<!-- touchstart is used to keep the OSK at the same position after a message send -->
<button
v-else
:disabled="uploadingFiles || disableSubmit"
class="btn button-default"
@touchstart.stop.prevent="postStatus($event, newStatus)"
@click.stop.prevent="postStatus($event, newStatus)"
>
{{ $t('post_status.post') }}
</button>
</div>
<div
v-if="error"
class="alert error"
>
Error: {{ error }}
<button
class="button-unstyled"
@click="clearError"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="times"
/>
</button>
</div>
<gallery
v-if="newStatus.files && newStatus.files.length > 0"
class="attachments"
:grid="true"
:nsfw="false"
:attachments="newStatus.files"
:descriptions="newStatus.mediaDescriptions"
:set-media="() => $store.dispatch('setMedia', newStatus.files)"
:editable="true"
:edit-attachment="editAttachment"
:remove-attachment="removeMediaFile"
:shift-up-attachment="newStatus.files.length > 1 && shiftUpMediaFile"
:shift-dn-attachment="newStatus.files.length > 1 && shiftDnMediaFile"
@play="$emit('mediaplay', attachment.id)"
@pause="$emit('mediapause', attachment.id)"
/>
<div
v-if="newStatus.files.length > 0 && !disableSensitivityCheckbox"
class="upload_settings"
>
<Checkbox v-model="newStatus.nsfw">
{{ $t('post_status.attachments_sensitive') }}
</Checkbox>
</div>
</form>
</div>
</template>
<script src="./post_status_form.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.post-status-form {
position: relative;
.attachments {
margin-bottom: 0.5em;
}
.form-bottom {
display: flex;
justify-content: space-between;
padding: 0.5em;
height: 32px;
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;
padding-left: 0.5em;
}
.preview-toggle {
flex: 1;
cursor: pointer;
user-select: none;
&: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: $fallback--faint;
color: var(--faint, $fallback--faint);
}
.preview-status {
border: 1px solid $fallback--border;
border: 1px solid var(--border, $fallback--border);
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
padding: 0.5em;
margin: 0;
line-height: 1.4em;
}
.text-format {
.only-format {
color: $fallback--faint;
color: var(--faint, $fallback--faint);
}
}
.visibility-tray {
display: flex;
justify-content: space-between;
padding-top: 5px;
}
.media-upload-icon, .poll-icon, .emoji-icon {
font-size: 26px;
line-height: 1.1;
flex: 1;
padding: 0 0.1em;
&.selected, &:hover {
// needs to be specific to override icon default color
svg, i, label {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
}
&.disabled {
svg, i {
cursor: not-allowed;
color: $fallback--icon;
color: var(--btnDisabledText, $fallback--icon);
&:hover {
color: $fallback--icon;
color: var(--btnDisabledText, $fallback--icon);
}
}
}
}
// Order is not necessary but a good indicator
.media-upload-icon {
order: 1;
text-align: left;
}
.emoji-icon {
order: 2;
text-align: center;
}
.poll-icon {
order: 3;
text-align: right;
}
.poll-icon {
cursor: pointer;
}
.error {
text-align: center;
}
.media-upload-wrapper {
margin-right: .2em;
margin-bottom: .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 {
cursor: pointer;
}
.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:24px;
}
form textarea.form-cw {
line-height:16px;
resize: none;
overflow: hidden;
transition: min-height 200ms 100ms;
min-height: 1px;
}
.form-post-body {
height: 16px; // Only affects the empty-height
line-height: 16px;
resize: none;
overflow: hidden;
transition: min-height 200ms 100ms;
padding-bottom: 1.75em;
min-height: 1px;
box-sizing: content-box;
&.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: $fallback--cRed;
color: var(--cRed, $fallback--cRed);
}
}
.btn {
cursor: pointer;
}
.btn[disabled] {
cursor: not-allowed;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 0.6; }
}
@keyframes fade-out {
from { opacity: 0.6; }
to { opacity: 0; }
}
.drop-indicator {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
font-size: 5em;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.6;
color: $fallback--text;
color: var(--text, $fallback--text);
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
border: 2px dashed $fallback--text;
border: 2px dashed var(--text, $fallback--text);
}
}
</style>
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Oct 29, 4:28 PM (1 d, 11 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1798350
Default Alt Text
(21 KB)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline