mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-27 08:03:08 +02:00
🎨
This commit is contained in:
parent
aba06b4ef9
commit
c05ad8990a
4 changed files with 135 additions and 40 deletions
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<Transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened">
|
<Transition :name="transitionName" :duration="transitionDuration" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened">
|
||||||
<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
|
<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
|
||||||
<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
|
<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
|
||||||
<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick">
|
<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick">
|
||||||
|
@ -74,20 +74,27 @@ const type = $computed(() => {
|
||||||
return props.preferType!;
|
return props.preferType!;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
let transitionName = $ref(defaultStore.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : '');
|
||||||
|
let transitionDuration = $ref(defaultStore.state.animation ? 200 : 0);
|
||||||
|
|
||||||
let contentClicking = false;
|
let contentClicking = false;
|
||||||
|
|
||||||
const close = () => {
|
function close(opts: { useSendAnimation?: boolean } = {}) {
|
||||||
|
if (opts.useSendAnimation) {
|
||||||
|
transitionName = 'send';
|
||||||
|
transitionDuration = 400;
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
// eslint-disable-next-line vue/no-mutating-props
|
||||||
if (props.src) props.src.style.pointerEvents = 'auto';
|
if (props.src) props.src.style.pointerEvents = 'auto';
|
||||||
showing = false;
|
showing = false;
|
||||||
emit('close');
|
emit('close');
|
||||||
};
|
}
|
||||||
|
|
||||||
const onBgClick = () => {
|
function onBgClick() {
|
||||||
if (contentClicking) return;
|
if (contentClicking) return;
|
||||||
emit('click');
|
emit('click');
|
||||||
};
|
}
|
||||||
|
|
||||||
if (type === 'drawer') {
|
if (type === 'drawer') {
|
||||||
maxHeight = window.innerHeight / 1.5;
|
maxHeight = window.innerHeight / 1.5;
|
||||||
|
@ -254,6 +261,30 @@ defineExpose({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.send-enter-active, .send-leave-active {
|
||||||
|
> .bg {
|
||||||
|
transition: opacity 0.3s !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .content {
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transform: perspective(50cm) translateZ(0px) translateY(0px) rotateX(0deg);
|
||||||
|
transition: opacity 0.4s cubic-bezier(.5,-0.5,.75,1), transform 0.4s cubic-bezier(.5,-0.5,.75,1) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.send-enter-from, .send-leave-to {
|
||||||
|
> .bg {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .content {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transform: perspective(50cm) translateZ(-300px) translateY(-200px) rotateX(40deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.modal-enter-active, .modal-leave-active {
|
.modal-enter-active, .modal-leave-active {
|
||||||
> .bg {
|
> .bg {
|
||||||
transition: opacity 0.2s !important;
|
transition: opacity 0.2s !important;
|
||||||
|
|
|
@ -22,7 +22,14 @@
|
||||||
<span v-if="visibility === 'specified'"><i class="ti ti-mail"></i></span>
|
<span v-if="visibility === 'specified'"><i class="ti ti-mail"></i></span>
|
||||||
</button>
|
</button>
|
||||||
<button v-tooltip="i18n.ts.previewNoteText" class="_button preview" :class="{ active: showPreview }" @click="showPreview = !showPreview"><i class="ti ti-eye"></i></button>
|
<button v-tooltip="i18n.ts.previewNoteText" class="_button preview" :class="{ active: showPreview }" @click="showPreview = !showPreview"><i class="ti ti-eye"></i></button>
|
||||||
<button class="submit _buttonGradate" :disabled="!canPost" data-cy-open-post-form-submit @click="post">{{ submitText }}<i :class="reply ? 'ti ti-arrow-back-up' : renote ? 'ti ti-quote' : 'ti ti-send'"></i></button>
|
<button v-click-anime class="submit _button" :class="{ posting }" :disabled="!canPost" data-cy-open-post-form-submit @click="post">
|
||||||
|
<div class="inner _buttonGradate">
|
||||||
|
<template v-if="posted"></template>
|
||||||
|
<template v-else-if="posting"><MkEllipsis/></template>
|
||||||
|
<template v-else>{{ submitText }}</template>
|
||||||
|
<i :class="posted ? 'ti ti-check' : reply ? 'ti ti-arrow-back-up' : renote ? 'ti ti-quote' : 'ti ti-send'"></i>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="form" :class="{ fixed }">
|
<div class="form" :class="{ fixed }">
|
||||||
|
@ -41,7 +48,7 @@
|
||||||
</div>
|
</div>
|
||||||
<MkInfo v-if="hasNotSpecifiedMentions" warn class="hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
|
<MkInfo v-if="hasNotSpecifiedMentions" warn class="hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
|
||||||
<input v-show="useCw" ref="cwInputEl" v-model="cw" class="cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown">
|
<input v-show="useCw" ref="cwInputEl" v-model="cw" class="cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown">
|
||||||
<textarea ref="textareaEl" v-model="text" class="text" :class="{ withCw: useCw }" :disabled="posting" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
|
<textarea ref="textareaEl" v-model="text" class="text" :class="{ withCw: useCw }" :disabled="posting || posted" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
|
||||||
<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" class="hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
|
<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" class="hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
|
||||||
<XPostFormAttaches v-model="files" class="attaches" @detach="detachFile" @change-sensitive="updateFileSensitive" @change-name="updateFileName"/>
|
<XPostFormAttaches v-model="files" class="attaches" @detach="detachFile" @change-sensitive="updateFileSensitive" @change-name="updateFileName"/>
|
||||||
<MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
|
<MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
|
||||||
|
@ -90,6 +97,7 @@ import { instance } from '@/instance';
|
||||||
import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account';
|
import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account';
|
||||||
import { uploadFile } from '@/scripts/upload';
|
import { uploadFile } from '@/scripts/upload';
|
||||||
import { deepClone } from '@/scripts/clone';
|
import { deepClone } from '@/scripts/clone';
|
||||||
|
import Ripple from '@/components/MkRipple.vue';
|
||||||
|
|
||||||
const modal = inject('modal');
|
const modal = inject('modal');
|
||||||
|
|
||||||
|
@ -108,6 +116,7 @@ const props = withDefaults(defineProps<{
|
||||||
instant?: boolean;
|
instant?: boolean;
|
||||||
fixed?: boolean;
|
fixed?: boolean;
|
||||||
autofocus?: boolean;
|
autofocus?: boolean;
|
||||||
|
freezeAfterPosted?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
initialVisibleUsers: () => [],
|
initialVisibleUsers: () => [],
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
|
@ -125,6 +134,7 @@ const hashtagsInputEl = $ref<HTMLInputElement | null>(null);
|
||||||
const visibilityButton = $ref<HTMLElement | null>(null);
|
const visibilityButton = $ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
let posting = $ref(false);
|
let posting = $ref(false);
|
||||||
|
let posted = $ref(false);
|
||||||
let text = $ref(props.initialText ?? '');
|
let text = $ref(props.initialText ?? '');
|
||||||
let files = $ref(props.initialFiles ?? []);
|
let files = $ref(props.initialFiles ?? []);
|
||||||
let poll = $ref<{
|
let poll = $ref<{
|
||||||
|
@ -206,7 +216,7 @@ const maxTextLength = $computed((): number => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const canPost = $computed((): boolean => {
|
const canPost = $computed((): boolean => {
|
||||||
return !posting &&
|
return !posting && !posted &&
|
||||||
(1 <= textLength || 1 <= files.length || !!poll || !!props.renote) &&
|
(1 <= textLength || 1 <= files.length || !!poll || !!props.renote) &&
|
||||||
(textLength <= maxTextLength) &&
|
(textLength <= maxTextLength) &&
|
||||||
(!poll || poll.choices.length >= 2);
|
(!poll || poll.choices.length >= 2);
|
||||||
|
@ -559,7 +569,15 @@ function deleteDraft() {
|
||||||
localStorage.setItem('drafts', JSON.stringify(draftData));
|
localStorage.setItem('drafts', JSON.stringify(draftData));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function post() {
|
async function post(ev?: MouseEvent) {
|
||||||
|
if (ev) {
|
||||||
|
const el = ev.currentTarget ?? ev.target;
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
const x = rect.left + (el.offsetWidth / 2);
|
||||||
|
const y = rect.top + (el.offsetHeight / 2);
|
||||||
|
os.popup(Ripple, { x, y }, {}, 'end');
|
||||||
|
}
|
||||||
|
|
||||||
let postData = {
|
let postData = {
|
||||||
text: text === '' ? undefined : text,
|
text: text === '' ? undefined : text,
|
||||||
fileIds: files.length > 0 ? files.map(f => f.id) : undefined,
|
fileIds: files.length > 0 ? files.map(f => f.id) : undefined,
|
||||||
|
@ -594,7 +612,11 @@ async function post() {
|
||||||
|
|
||||||
posting = true;
|
posting = true;
|
||||||
os.api('notes/create', postData, token).then(() => {
|
os.api('notes/create', postData, token).then(() => {
|
||||||
|
if (props.freezeAfterPosted) {
|
||||||
|
posted = true;
|
||||||
|
} else {
|
||||||
clear();
|
clear();
|
||||||
|
}
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
deleteDraft();
|
deleteDraft();
|
||||||
emit('posted');
|
emit('posted');
|
||||||
|
@ -713,6 +735,10 @@ onMounted(() => {
|
||||||
nextTick(() => watchForDraft());
|
nextTick(() => watchForDraft());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
clear,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -793,23 +819,32 @@ onMounted(() => {
|
||||||
|
|
||||||
> .submit {
|
> .submit {
|
||||||
margin: 16px 16px 16px 0;
|
margin: 16px 16px 16px 0;
|
||||||
padding: 0 12px;
|
|
||||||
line-height: 34px;
|
|
||||||
font-weight: bold;
|
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.posting {
|
||||||
|
cursor: wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .inner {
|
||||||
|
padding: 0 12px;
|
||||||
|
line-height: 34px;
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
min-width: 90px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
> i {
|
> i {
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> .form {
|
> .form {
|
||||||
> .preview {
|
> .preview {
|
||||||
|
|
|
@ -1,19 +1,46 @@
|
||||||
<template>
|
<template>
|
||||||
<MkModal ref="modal" :prefer-type="'dialog:top'" @click="$refs.modal.close()" @closed="$emit('closed')">
|
<MkModal ref="modal" :prefer-type="'dialog:top'" @click="modal.close()" @closed="onModalClosed()">
|
||||||
<MkPostForm v-bind="$attrs" @posted="$refs.modal.close()" @cancel="$refs.modal.close()" @esc="$refs.modal.close()"/>
|
<MkPostForm ref="form" v-bind="props" autofocus freeze-after-posted @posted="onPosted" @cancel="modal.close()" @esc="modal.close()"/>
|
||||||
</MkModal>
|
</MkModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { } from 'vue';
|
||||||
|
import * as misskey from 'misskey-js';
|
||||||
import MkModal from '@/components/MkModal.vue';
|
import MkModal from '@/components/MkModal.vue';
|
||||||
import MkPostForm from '@/components/MkPostForm.vue';
|
import MkPostForm from '@/components/MkPostForm.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps<{
|
||||||
components: {
|
reply?: misskey.entities.Note;
|
||||||
MkModal,
|
renote?: misskey.entities.Note;
|
||||||
MkPostForm,
|
channel?: any; // TODO
|
||||||
},
|
mention?: misskey.entities.User;
|
||||||
emits: ['closed'],
|
specified?: misskey.entities.User;
|
||||||
|
initialText?: string;
|
||||||
|
initialVisibility?: typeof misskey.noteVisibilities;
|
||||||
|
initialFiles?: misskey.entities.DriveFile[];
|
||||||
|
initialLocalOnly?: boolean;
|
||||||
|
initialVisibleUsers?: misskey.entities.User[];
|
||||||
|
initialNote?: misskey.entities.Note;
|
||||||
|
instant?: boolean;
|
||||||
|
fixed?: boolean;
|
||||||
|
autofocus?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'closed'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
let modal = $ref<InstanceType<typeof MkModal>>();
|
||||||
|
let form = $ref<InstanceType<typeof MkPostForm>>();
|
||||||
|
|
||||||
|
function onPosted() {
|
||||||
|
modal.close({
|
||||||
|
useSendAnimation: true,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onModalClosed() {
|
||||||
|
emit('closed');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,30 +2,32 @@ import { Directive } from 'vue';
|
||||||
import { defaultStore } from '@/store';
|
import { defaultStore } from '@/store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mounted(el, binding, vn) {
|
mounted(el: HTMLElement, binding, vn) {
|
||||||
/*
|
|
||||||
if (!defaultStore.state.animation) return;
|
if (!defaultStore.state.animation) return;
|
||||||
|
|
||||||
el.classList.add('_anime_bounce_standBy');
|
const target = el.children[0];
|
||||||
|
|
||||||
|
if (target == null) return;
|
||||||
|
|
||||||
|
target.classList.add('_anime_bounce_standBy');
|
||||||
|
|
||||||
el.addEventListener('mousedown', () => {
|
el.addEventListener('mousedown', () => {
|
||||||
el.classList.add('_anime_bounce_standBy');
|
target.classList.add('_anime_bounce_standBy');
|
||||||
el.classList.add('_anime_bounce_ready');
|
target.classList.add('_anime_bounce_ready');
|
||||||
|
|
||||||
el.addEventListener('mouseleave', () => {
|
target.addEventListener('mouseleave', () => {
|
||||||
el.classList.remove('_anime_bounce_ready');
|
target.classList.remove('_anime_bounce_ready');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', () => {
|
||||||
el.classList.add('_anime_bounce');
|
target.classList.add('_anime_bounce');
|
||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener('animationend', () => {
|
el.addEventListener('animationend', () => {
|
||||||
el.classList.remove('_anime_bounce_ready');
|
target.classList.remove('_anime_bounce_ready');
|
||||||
el.classList.remove('_anime_bounce');
|
target.classList.remove('_anime_bounce');
|
||||||
el.classList.add('_anime_bounce_standBy');
|
target.classList.add('_anime_bounce_standBy');
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
},
|
},
|
||||||
} as Directive;
|
} as Directive;
|
||||||
|
|
Loading…
Reference in a new issue