forked from mirrors/akkoma-fe
added drawing canvas =w=
This commit is contained in:
parent
790e12c6b3
commit
905c5917ff
5 changed files with 308 additions and 315 deletions
144
src/components/drawing_canvas/drawingCanvas.vue
Normal file
144
src/components/drawing_canvas/drawingCanvas.vue
Normal file
|
@ -0,0 +1,144 @@
|
|||
<template>
|
||||
<div class="drawbox" v-if="visible">
|
||||
<canvas width="320" height="320" class="drawCanvas" ref="drawCanvas" @mousedown="startDrawing" @mousemove="draw" @mouseup="stopDrawing" @mouseleave="stopDrawing"></canvas>
|
||||
<div class="toolbar">
|
||||
<input type="color" id="brushColour" v-model="colour"/>
|
||||
<input type="range" id="brushSize" min="1" max="20" v-model="brushSize"/>
|
||||
<button class="button-default" @click="undo"><FAIcon icon="undo" /></button>
|
||||
<button class="button-default" @click="clear"><FAIcon icon="trash" /></button>
|
||||
<button class="button-default" @click="upload"><FAIcon icon="upload" /></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faUndo, faTrash, faUpload } from '@fortawesome/free-solid-svg-icons'
|
||||
library.add(faUndo, faTrash, faUpload)
|
||||
export default {
|
||||
props: ['visible'],
|
||||
data() {
|
||||
return {
|
||||
drawing: false,
|
||||
colour: '#ffffff',
|
||||
brushSize: 5,
|
||||
drawingHist: [],
|
||||
currentLine: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
startDrawing(event) {
|
||||
this.drawing = true;
|
||||
const canvas = this.$refs.drawCanvas;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.lineCap = 'round';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(event.offsetX, event.offsetY);
|
||||
|
||||
this.currentLine = {
|
||||
points: [{ x: event.offsetX, y: event.offsetY }],
|
||||
colour: this.colour,
|
||||
brushSize: this.brushSize
|
||||
};
|
||||
},
|
||||
draw(event) {
|
||||
if (!this.drawing) return;
|
||||
const canvas = this.$refs.drawCanvas;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.lineWidth = this.brushSize;
|
||||
ctx.strokeStyle = this.colour;
|
||||
ctx.lineTo(event.offsetX, event.offsetY);
|
||||
ctx.stroke();
|
||||
this.currentLine.points.push({ x: event.offsetX, y: event.offsetY });
|
||||
},
|
||||
stopDrawing() {
|
||||
if (!this.drawing) return;
|
||||
this.drawing = false;
|
||||
this.drawingHist.push(this.currentLine);
|
||||
this.currentLine = [];
|
||||
},
|
||||
undo() {
|
||||
if (this.drawingHist.length > 0) {
|
||||
this.drawingHist.pop();
|
||||
const canvas = this.$refs.drawCanvas;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
for (const drawing of this.drawingHist) {
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = drawing.brushSize;
|
||||
ctx.strokeStyle = drawing.colour;
|
||||
|
||||
const points = drawing.points;
|
||||
ctx.moveTo(points[0].x, points[0].y);
|
||||
for (const point of points) {
|
||||
ctx.lineTo(point.x, point.y);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
const canvas = this.$refs.drawCanvas;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
this.drawingHist = [];
|
||||
this.currentLine = [];
|
||||
},
|
||||
upload() {
|
||||
const canvas = this.$refs.drawCanvas;
|
||||
const dataURL = canvas.toDataURL('image/png');
|
||||
this.$emit('postDrawing', dataURL);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.drawCanvas {
|
||||
border: 2px solid var(--border);
|
||||
border-radius: var(--btnRadius);
|
||||
margin-top: 4px;
|
||||
}
|
||||
.drawbox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.toolbar {
|
||||
display: flex;
|
||||
max-width: 320px;
|
||||
}
|
||||
.toolbar button {
|
||||
width: 30px;
|
||||
margin: 0px 0.2em;
|
||||
}
|
||||
.toolbar input[type="range"] {
|
||||
-webkit-appearance: none;
|
||||
align-self: center;
|
||||
height: 4px;
|
||||
margin: 0px 4px;
|
||||
border-radius: 2px;
|
||||
background: var(--btn);
|
||||
&::-webkit-slider-thumb {
|
||||
background: var(--link);
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
&::-webkit-slider-runnable-track {
|
||||
background: var(--link);
|
||||
}
|
||||
&::-moz-range-thumb {
|
||||
background: var(--link);
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
&::-moz-range-progress {
|
||||
background: var(--link);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -70,13 +70,29 @@ const mediaUpload = {
|
|||
},
|
||||
props: [
|
||||
'dropFiles',
|
||||
'disabled'
|
||||
'disabled',
|
||||
'drawfile'
|
||||
],
|
||||
watch: {
|
||||
'dropFiles': function (fileInfos) {
|
||||
if (!this.uploading) {
|
||||
this.multiUpload(fileInfos)
|
||||
}
|
||||
},
|
||||
'drawfile': function (newFile) {
|
||||
console.log(newFile);
|
||||
|
||||
const byteString = atob(newFile.split(',')[1]);
|
||||
const arrayBuffer = new ArrayBuffer(byteString.length);
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
uint8Array[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
const mimeType = newFile.split(';')[0].split(':')[1];
|
||||
const file = new Blob([uint8Array], { type: mimeType });
|
||||
file.name = 'drawing.png';
|
||||
|
||||
this.uploadFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import MediaUpload from '../media_upload/media_upload.vue'
|
|||
import ScopeSelector from '../scope_selector/scope_selector.vue'
|
||||
import EmojiInput from '../emoji_input/emoji_input.vue'
|
||||
import PollForm from '../poll/poll_form.vue'
|
||||
import DrawingCanvas from '../drawing_canvas/drawingCanvas.vue'
|
||||
import Attachment from '../attachment/attachment.vue'
|
||||
import Gallery from 'src/components/gallery/gallery.vue'
|
||||
import StatusContent from '../status_content/status_content.vue'
|
||||
|
@ -23,8 +24,10 @@ import {
|
|||
faUpload,
|
||||
faBan,
|
||||
faTimes,
|
||||
faCircleNotch
|
||||
faCircleNotch,
|
||||
faPencilSquare,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import mediaUpload from '../media_upload/media_upload.vue'
|
||||
|
||||
library.add(
|
||||
faSmileBeam,
|
||||
|
@ -32,7 +35,8 @@ library.add(
|
|||
faUpload,
|
||||
faBan,
|
||||
faTimes,
|
||||
faCircleNotch
|
||||
faCircleNotch,
|
||||
faPencilSquare
|
||||
)
|
||||
|
||||
const buildMentionsString = ({ user, attentions = [] }, currentUser) => {
|
||||
|
@ -117,6 +121,7 @@ const PostStatusForm = {
|
|||
MediaUpload,
|
||||
EmojiInput,
|
||||
PollForm,
|
||||
DrawingCanvas,
|
||||
ScopeSelector,
|
||||
Checkbox,
|
||||
Select,
|
||||
|
@ -228,6 +233,8 @@ const PostStatusForm = {
|
|||
newStatus: statusParams,
|
||||
caret: 0,
|
||||
pollFormVisible: false,
|
||||
canvasVisible: false,
|
||||
drawing: null,
|
||||
showDropIcon: 'hide',
|
||||
dropStopTimeout: null,
|
||||
preview: null,
|
||||
|
@ -732,6 +739,9 @@ const PostStatusForm = {
|
|||
togglePollForm () {
|
||||
this.pollFormVisible = !this.pollFormVisible
|
||||
},
|
||||
toggleDrawingCanvas () {
|
||||
this.canvasVisible = !this.canvasVisible
|
||||
},
|
||||
setPoll (poll) {
|
||||
this.newStatus.poll = poll
|
||||
},
|
||||
|
@ -771,6 +781,9 @@ const PostStatusForm = {
|
|||
}
|
||||
}
|
||||
return this.$store.state.users.currentUser.default_scope
|
||||
},
|
||||
storeDrawing(dataURL) {
|
||||
this.drawing = dataURL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,259 +1,127 @@
|
|||
<template>
|
||||
<div
|
||||
ref="form"
|
||||
class="post-status-form"
|
||||
>
|
||||
<form
|
||||
autocomplete="off"
|
||||
@submit.prevent
|
||||
@dragover.prevent="fileDrag"
|
||||
>
|
||||
<div ref="form" class="post-status-form">
|
||||
<form autocomplete="off" @submit.prevent @dragover.prevent="fileDrag">
|
||||
<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"
|
||||
>
|
||||
<button
|
||||
class="button-unstyled -link"
|
||||
@click="openProfileTab"
|
||||
>
|
||||
keypath="post_status.account_not_locked_warning" tag="p" class="visibility-notice" scope="global">
|
||||
<button class="button-unstyled -link" @click="openProfileTab">
|
||||
{{ $t('post_status.account_not_locked_warning_link') }}
|
||||
</button>
|
||||
</i18n-t>
|
||||
<p
|
||||
v-if="!hideScopeNotice && newStatus.visibility === 'public'"
|
||||
class="visibility-notice notice-dismissible"
|
||||
>
|
||||
<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"
|
||||
@click.prevent="dismissScopeNotice()"
|
||||
>
|
||||
<a class="fa-scale-110 fa-old-padding dismiss" @click.prevent="dismissScopeNotice()">
|
||||
<FAIcon icon="times" />
|
||||
</a>
|
||||
</p>
|
||||
<p
|
||||
v-if="!hideScopeNotice && newStatus.visibility === 'local'"
|
||||
class="visibility-notice notice-dismissible"
|
||||
>
|
||||
<p v-if="!hideScopeNotice && newStatus.visibility === 'local'" class="visibility-notice notice-dismissible">
|
||||
<span>{{ $t('post_status.scope_notice.local') }}</span>
|
||||
<a
|
||||
class="fa-scale-110 fa-old-padding dismiss"
|
||||
@click.prevent="dismissScopeNotice()"
|
||||
>
|
||||
<a class="fa-scale-110 fa-old-padding dismiss" @click.prevent="dismissScopeNotice()">
|
||||
<FAIcon icon="times" />
|
||||
</a>
|
||||
</p>
|
||||
<p
|
||||
v-else-if="!hideScopeNotice && newStatus.visibility === 'unlisted'"
|
||||
class="visibility-notice notice-dismissible"
|
||||
>
|
||||
<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"
|
||||
@click.prevent="dismissScopeNotice()"
|
||||
>
|
||||
<a class="fa-scale-110 fa-old-padding 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"
|
||||
>
|
||||
<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"
|
||||
@click.prevent="dismissScopeNotice()"
|
||||
>
|
||||
<a class="fa-scale-110 fa-old-padding dismiss" @click.prevent="dismissScopeNotice()">
|
||||
<FAIcon icon="times" />
|
||||
</a>
|
||||
</p>
|
||||
<p
|
||||
v-else-if="newStatus.visibility === 'direct'"
|
||||
class="visibility-notice"
|
||||
>
|
||||
|
||||
<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="isEdit"
|
||||
class="visibility-notice edit-warning"
|
||||
>
|
||||
|
||||
<div v-if="isEdit" class="visibility-notice edit-warning">
|
||||
<p>{{ $t('post_status.edit_remote_warning') }}</p>
|
||||
<p>{{ $t('post_status.edit_unsupported_warning') }}</p>
|
||||
</div>
|
||||
<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 style="display:flex;">
|
||||
<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="!disableSubject" class="subject-heading faint">
|
||||
<a class="preview-toggle faint" @click.stop.prevent="toggleSubjectVisible">
|
||||
<FAIcon :icon="subjectVisible ? 'chevron-left' : 'chevron-right'" />
|
||||
{{ $t('post_status.toggle_content_warning') }}
|
||||
</a>
|
||||
<div v-show="previewLoading" class="preview-spinner">
|
||||
<FAIcon class="fa-old-padding" spin icon="circle-notch" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="showPreview"
|
||||
class="preview-container"
|
||||
>
|
||||
<div
|
||||
v-if="!preview"
|
||||
class="preview-status"
|
||||
>
|
||||
|
||||
<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"
|
||||
>
|
||||
<div v-else-if="preview.error" class="preview-status preview-error">
|
||||
{{ preview.error }}
|
||||
</div>
|
||||
<StatusContent
|
||||
v-else
|
||||
:status="preview"
|
||||
class="preview-status"
|
||||
/>
|
||||
<StatusContent v-else :status="preview" class="preview-status" />
|
||||
</div>
|
||||
<EmojiInput
|
||||
v-if="subjectVisible"
|
||||
ref="subject-emoji-input"
|
||||
v-model="newStatus.spoilerText"
|
||||
enable-emoji-picker
|
||||
hide-emoji-button
|
||||
:suggest="emojiSuggestor"
|
||||
class="form-control"
|
||||
>
|
||||
<input
|
||||
ref="subject-input"
|
||||
v-model="newStatus.spoilerText"
|
||||
type="text"
|
||||
:placeholder="$t('post_status.content_warning')"
|
||||
:disabled="posting && !optimisticPosting"
|
||||
size="1"
|
||||
class="form-post-subject"
|
||||
@input="onSubjectInput"
|
||||
@focus="focusSubjectInput()"
|
||||
>
|
||||
<EmojiInput v-if="subjectVisible" ref="subject-emoji-input" v-model="newStatus.spoilerText" enable-emoji-picker
|
||||
hide-emoji-button :suggest="emojiSuggestor" class="form-control">
|
||||
<input ref="subject-input" v-model="newStatus.spoilerText" type="text"
|
||||
:placeholder="$t('post_status.content_warning')" :disabled="posting && !optimisticPosting" size="1"
|
||||
class="form-post-subject" @input="onSubjectInput" @focus="focusSubjectInput()">
|
||||
</EmojiInput>
|
||||
<i18n-t
|
||||
v-if="newStatus.files.length !== 0 && newStatus.nsfw === false && newStatus.spoilerText !== ''"
|
||||
keypath="post_status.media_not_sensitive_warning"
|
||||
tag="p"
|
||||
class="visibility-notice"
|
||||
scope="global"
|
||||
>
|
||||
<i18n-t v-if="newStatus.files.length !== 0 && newStatus.nsfw === false && newStatus.spoilerText !== ''"
|
||||
keypath="post_status.media_not_sensitive_warning" tag="p" class="visibility-notice" scope="global">
|
||||
{{ $t('post_status.media_not_sensitive_warning') }}
|
||||
</i18n-t>
|
||||
<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')"
|
||||
rows="1"
|
||||
cols="1"
|
||||
:disabled="posting && !optimisticPosting"
|
||||
class="form-post-body"
|
||||
<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')"
|
||||
rows="1" cols="1" :disabled="posting && !optimisticPosting" class="form-post-body"
|
||||
:class="{ 'scrollable-form': !!maxHeight, '-has-subject': subjectVisible }"
|
||||
@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"
|
||||
@focus="focusStatusInput()"
|
||||
/>
|
||||
<p
|
||||
v-if="hasStatusLengthLimit"
|
||||
class="character-counter faint"
|
||||
:class="{ error: isOverLengthLimit }"
|
||||
>
|
||||
@keydown.ctrl.enter="!submitOnEnter && postStatus($event, newStatus)" @input="resize"
|
||||
@compositionupdate="resize" @paste="paste" @focus="focusStatusInput()" />
|
||||
<p v-if="hasStatusLengthLimit" class="character-counter faint" :class="{ error: isOverLengthLimit }">
|
||||
{{ charactersLeft }}
|
||||
</p>
|
||||
</EmojiInput>
|
||||
<div
|
||||
v-if="!disableScopeSelector"
|
||||
class="visibility-tray"
|
||||
:class="{ 'visibility-tray-edit': isEdit }"
|
||||
>
|
||||
<scope-selector
|
||||
v-if="!disableVisibilitySelector"
|
||||
:user-default="userDefaultScope"
|
||||
:original-scope="copyMessageScope"
|
||||
:initial-scope="newStatus.visibility"
|
||||
:on-scope-change="changeVis"
|
||||
/>
|
||||
<div v-if="!disableScopeSelector" class="visibility-tray" :class="{ 'visibility-tray-edit': isEdit }">
|
||||
<scope-selector v-if="!disableVisibilitySelector" :user-default="userDefaultScope"
|
||||
:original-scope="copyMessageScope" :initial-scope="newStatus.visibility" :on-scope-change="changeVis" />
|
||||
|
||||
<div
|
||||
class="format-selector-container">
|
||||
<div
|
||||
v-if="$store.state.config.languageSelectorVisibility"
|
||||
class="format-selector"
|
||||
>
|
||||
<Select
|
||||
id="post-language"
|
||||
v-model="newStatus.language"
|
||||
class="form-control"
|
||||
>
|
||||
<option
|
||||
v-for="language in postLanguageOptions"
|
||||
:key="language.key"
|
||||
:value="language.value"
|
||||
>
|
||||
<div class="format-selector-container">
|
||||
<div v-if="$store.state.config.languageSelectorVisibility" class="format-selector">
|
||||
<Select id="post-language" v-model="newStatus.language" class="form-control">
|
||||
<option v-for="language in postLanguageOptions" :key="language.key" :value="language.value">
|
||||
{{ language.label }}
|
||||
</option>
|
||||
</Select>
|
||||
</div>
|
||||
<div
|
||||
v-if="postFormats.length > 1"
|
||||
class="text-format format-selector"
|
||||
>
|
||||
<Select
|
||||
id="post-content-type"
|
||||
v-model="newStatus.contentType"
|
||||
class="form-control"
|
||||
>
|
||||
<option
|
||||
v-for="postFormat in postFormats"
|
||||
:key="postFormat"
|
||||
:value="postFormat"
|
||||
>
|
||||
<div v-if="postFormats.length > 1" class="text-format format-selector">
|
||||
<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 format-selector"
|
||||
>
|
||||
<div v-if="postFormats.length === 1 && postFormats[0] !== 'text/plain'" class="text-format format-selector">
|
||||
<span class="only-format">
|
||||
{{ $t(`post_status.content_type["${postFormats[0]}"]`) }}
|
||||
</span>
|
||||
|
@ -261,124 +129,61 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<poll-form
|
||||
v-if="pollsAvailable"
|
||||
ref="pollForm"
|
||||
:visible="pollFormVisible"
|
||||
@update-poll="setPoll"
|
||||
/>
|
||||
<div
|
||||
ref="bottom"
|
||||
class="form-bottom"
|
||||
>
|
||||
<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')"
|
||||
@click="showEmojiPicker"
|
||||
>
|
||||
<FAIcon icon="smile-beam" />
|
||||
<media-upload ref="mediaUpload" class="media-upload-icon" :drop-files="dropFiles"
|
||||
:disabled="uploadFileLimitReached" @uploading="startedUploadingFiles" @uploaded="addMediaFile"
|
||||
@upload-failed="uploadFailed" @all-uploaded="finishedUploadingFiles" :drawfile="drawing"/>
|
||||
<button class="emoji-icon button-unstyled" :title="$t('emoji.add_emoji')" @click="showEmojiPicker">
|
||||
<FAIcon icon="smile-beam"/>
|
||||
</button>
|
||||
<button
|
||||
v-if="pollsAvailable"
|
||||
class="poll-icon button-unstyled"
|
||||
<button v-if="pollsAvailable" class="poll-icon button-unstyled"
|
||||
:class="{ selected: pollFormVisible }"
|
||||
:title="$t('polls.add_poll')"
|
||||
@click="togglePollForm"
|
||||
>
|
||||
<FAIcon icon="poll-h" />
|
||||
:title="$t('polls.add_poll')" @click="togglePollForm">
|
||||
<FAIcon icon="poll-h"/>
|
||||
</button>
|
||||
<button
|
||||
v-if="!disableSubject"
|
||||
class="spoiler-icon button-unstyled"
|
||||
:class="{ selected: subjectVisible }"
|
||||
:title="$t('post_status.toggle_content_warning')"
|
||||
@click="toggleSubjectVisible"
|
||||
>
|
||||
<FAIcon icon="eye-slash" />
|
||||
<button class="canvas-icon button-unstyled"
|
||||
:class="{ selected: canvasVisible }"
|
||||
:title="$t('canvas.show_canvas')" @click="toggleDrawingCanvas">
|
||||
<FAIcon icon="pencil-square"/>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
v-if="posting"
|
||||
disabled
|
||||
class="btn button-default"
|
||||
>
|
||||
<button v-if="posting" disabled class="btn button-default">
|
||||
{{ $t('post_status.posting') }}
|
||||
</button>
|
||||
<button
|
||||
v-else-if="isOverLengthLimit"
|
||||
disabled
|
||||
class="btn button-default"
|
||||
>
|
||||
<button v-else-if="isOverLengthLimit" disabled class="btn button-default">
|
||||
{{ $t('post_status.post') }}
|
||||
</button>
|
||||
<!-- To keep the OSK at the same position after a message send, -->
|
||||
<!-- @touchstart.stop.prevent was used. But while OSK position is -->
|
||||
<!-- quirky, accidental mobile posts caused by the workaround -->
|
||||
<!-- when people tried to scroll were a more serious bug. -->
|
||||
<button
|
||||
v-else
|
||||
:disabled="uploadingFiles || disableSubmit"
|
||||
class="btn button-default"
|
||||
@click.stop.prevent="postStatus($event, newStatus)"
|
||||
>
|
||||
<button v-else :disabled="uploadingFiles || disableSubmit" class="btn button-default"
|
||||
@click.stop.prevent="postStatus($event, newStatus)">
|
||||
{{ $t('post_status.post') }}
|
||||
</button>
|
||||
</div>
|
||||
<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"
|
||||
>
|
||||
<drawingCanvas ref="drawcanvas" @postDrawing="storeDrawing" :visible="canvasVisible"/>
|
||||
<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
|
||||
v-if="error"
|
||||
class="alert error"
|
||||
>
|
||||
<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 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"
|
||||
<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"
|
||||
>
|
||||
: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>
|
||||
|
@ -425,18 +230,17 @@
|
|||
}
|
||||
|
||||
.preview-heading {
|
||||
display: flex;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
.subject-heading {
|
||||
padding-right: 0.5em;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.preview-toggle {
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
&:hover { text-decoration: underline; }
|
||||
svg, i {
|
||||
margin-left: 0.2em;
|
||||
font-size: 0.8em;
|
||||
|
@ -497,13 +301,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
.media-upload-icon, .poll-icon, .emoji-icon, .spoiler-icon {
|
||||
.media-upload-icon,
|
||||
.poll-icon,
|
||||
.emoji-icon,
|
||||
.canvas-icon {
|
||||
font-size: 1.6em;
|
||||
line-height: 1.1;
|
||||
flex: 1;
|
||||
padding: 0 0.1em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--icon, $fallback--icon);
|
||||
|
||||
&.selected, &:hover {
|
||||
// needs to be specific to override icon default color
|
||||
|
@ -518,7 +326,6 @@
|
|||
cursor: not-allowed;
|
||||
color: $fallback--icon;
|
||||
color: var(--btnDisabledText, $fallback--icon);
|
||||
|
||||
&:hover {
|
||||
color: $fallback--icon;
|
||||
color: var(--btnDisabledText, $fallback--icon);
|
||||
|
@ -543,7 +350,7 @@
|
|||
justify-content: left;
|
||||
}
|
||||
|
||||
.spoiler-icon {
|
||||
.canvas-icon {
|
||||
order: 4;
|
||||
justify-content: left;
|
||||
}
|
||||
|
@ -557,7 +364,8 @@
|
|||
margin-bottom: .5em;
|
||||
width: 18em;
|
||||
|
||||
img, video {
|
||||
img,
|
||||
video {
|
||||
object-fit: contain;
|
||||
max-height: 10em;
|
||||
}
|
||||
|
@ -646,13 +454,23 @@
|
|||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 0.6; }
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-out {
|
||||
from { opacity: 0.6; }
|
||||
to { opacity: 0; }
|
||||
from {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.drop-indicator {
|
||||
|
|
|
@ -368,6 +368,9 @@
|
|||
"votes": "votes",
|
||||
"votes_count": "{count} vote | {count} votes"
|
||||
},
|
||||
"canvas": {
|
||||
"show_canvas": "Draw"
|
||||
},
|
||||
"post_status": {
|
||||
"account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
|
||||
"account_not_locked_warning_link": "locked",
|
||||
|
@ -1071,7 +1074,6 @@
|
|||
"show_new": "Show new",
|
||||
"socket_broke": "Realtime connection lost: CloseEvent code {0}",
|
||||
"socket_reconnected": "Realtime connection established",
|
||||
"follow_tag": "Follow hashtag",
|
||||
"unfollow_tag": "Unfollow hashtag",
|
||||
"up_to_date": "Up-to-date"
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue