added drawing canvas =w=

This commit is contained in:
ulith 2025-03-06 23:10:22 +13:00
parent 790e12c6b3
commit 905c5917ff
5 changed files with 308 additions and 315 deletions

View 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>

View file

@ -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);
}
}
}

View 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;
}
}
}

View file

@ -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 {

View file

@ -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"
},