wip
This commit is contained in:
parent
85197f4936
commit
3c0a878b1a
|
@ -9276,6 +9276,14 @@ export interface Locale extends ILocale {
|
||||||
* 特殊
|
* 特殊
|
||||||
*/
|
*/
|
||||||
"specialBlocks": string;
|
"specialBlocks": string;
|
||||||
|
/**
|
||||||
|
* タイトルを入力
|
||||||
|
*/
|
||||||
|
"inputTitleHere": string;
|
||||||
|
/**
|
||||||
|
* ここに移動
|
||||||
|
*/
|
||||||
|
"moveToHere": string;
|
||||||
"blocks": {
|
"blocks": {
|
||||||
/**
|
/**
|
||||||
* テキスト
|
* テキスト
|
||||||
|
|
|
@ -2446,6 +2446,8 @@ _pages:
|
||||||
contentBlocks: "コンテンツ"
|
contentBlocks: "コンテンツ"
|
||||||
inputBlocks: "入力"
|
inputBlocks: "入力"
|
||||||
specialBlocks: "特殊"
|
specialBlocks: "特殊"
|
||||||
|
inputTitleHere: "タイトルを入力"
|
||||||
|
moveToHere: "ここに移動"
|
||||||
blocks:
|
blocks:
|
||||||
text: "テキスト"
|
text: "テキスト"
|
||||||
textarea: "テキストエリア"
|
textarea: "テキストエリア"
|
||||||
|
|
|
@ -246,9 +246,11 @@ import { isEnabledUrlPreview } from '@/instance.js';
|
||||||
import { getAppearNote } from '@/scripts/get-appear-note.js';
|
import { getAppearNote } from '@/scripts/get-appear-note.js';
|
||||||
import { type Keymap } from '@/scripts/hotkey.js';
|
import { type Keymap } from '@/scripts/hotkey.js';
|
||||||
|
|
||||||
|
type Tab = 'replies' | 'renotes' | 'reactions';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
initialTab: string;
|
initialTab?: Tab;
|
||||||
}>(), {
|
}>(), {
|
||||||
initialTab: 'replies',
|
initialTab: 'replies',
|
||||||
});
|
});
|
||||||
|
@ -332,7 +334,7 @@ provide('react', (reaction: string) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const tab = ref(props.initialTab);
|
const tab = ref<Tab>(props.initialTab);
|
||||||
const reactionTabType = ref<string | null>(null);
|
const reactionTabType = ref<string | null>(null);
|
||||||
|
|
||||||
const renotesPagination = computed<Paging>(() => ({
|
const renotesPagination = computed<Paging>(() => ({
|
||||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="rootEl">
|
<div ref="rootEl" :class="$style.root">
|
||||||
<div ref="headerEl" :class="$style.header">
|
<div ref="headerEl" :class="$style.header">
|
||||||
<slot name="header"></slot>
|
<slot name="header"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -84,8 +84,16 @@ defineExpose({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang='scss' module>
|
<style lang='scss' module>
|
||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
flex-grow: 1;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
--MI-stickyTop: v-bind("childStickyTop + 'px'");
|
--MI-stickyTop: v-bind("childStickyTop + 'px'");
|
||||||
--MI-stickyBottom: v-bind("childStickyBottom + 'px'");
|
--MI-stickyBottom: v-bind("childStickyBottom + 'px'");
|
||||||
|
@ -93,12 +101,14 @@ defineExpose({
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
|
flex-shrink: 0;
|
||||||
top: var(--MI-stickyTop, 0);
|
top: var(--MI-stickyTop, 0);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
|
flex-shrink: 0;
|
||||||
bottom: var(--MI-stickyBottom, 0);
|
bottom: var(--MI-stickyBottom, 0);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- eslint-disable vue/no-mutating-props -->
|
<!-- eslint-disable vue/no-mutating-props -->
|
||||||
<XContainer :draggable="true" @remove="() => emit('remove')">
|
<XContainer :draggable="true" :blockId="modelValue.id" @remove="() => emit('remove')">
|
||||||
<template #header><i class="ti ti-photo"></i> {{ i18n.ts._pages.blocks.image }}</template>
|
<template #header><i class="ti ti-photo"></i> {{ i18n.ts._pages.blocks.image }}</template>
|
||||||
<template #func>
|
<template #func>
|
||||||
<button @click="choose()">
|
<button @click="choose()">
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- eslint-disable vue/no-mutating-props -->
|
<!-- eslint-disable vue/no-mutating-props -->
|
||||||
<XContainer :draggable="true" @remove="() => emit('remove')">
|
<XContainer :draggable="true" :blockId="modelValue.id" @remove="() => emit('remove')">
|
||||||
<template #header><i class="ti ti-note"></i> {{ i18n.ts._pages.blocks.note }}</template>
|
<template #header><i class="ti ti-note"></i> {{ i18n.ts._pages.blocks.note }}</template>
|
||||||
|
|
||||||
<section style="padding: 16px;" class="_gaps_s">
|
<section style="padding: 16px;" class="_gaps_s">
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- eslint-disable vue/no-mutating-props -->
|
<!-- eslint-disable vue/no-mutating-props -->
|
||||||
<XContainer :draggable="true" @remove="() => emit('remove')">
|
<XContainer :draggable="true" :blockId="modelValue.id" @remove="() => emit('remove')">
|
||||||
<template #header><i class="ti ti-note"></i> {{ props.modelValue.title }}</template>
|
<template #header><i class="ti ti-note"></i> {{ props.modelValue.title }}</template>
|
||||||
<template #func>
|
<template #func>
|
||||||
<button class="_button" @click="rename()">
|
<button class="_button" @click="rename()">
|
||||||
|
@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
import { defineAsyncComponent, inject, onMounted, watch, ref } from 'vue';
|
import { defineAsyncComponent, inject, onMounted, watch, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- eslint-disable vue/no-mutating-props -->
|
<!-- eslint-disable vue/no-mutating-props -->
|
||||||
<XContainer :draggable="true" @remove="() => emit('remove')">
|
<XContainer :draggable="true" :blockId="modelValue.id" @remove="() => emit('remove')">
|
||||||
<template #header><i class="ti ti-align-left"></i> {{ i18n.ts._pages.blocks.text }}</template>
|
<template #header><i class="ti ti-align-left"></i> {{ i18n.ts._pages.blocks.text }}</template>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
@ -15,7 +15,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
import { watch, ref, shallowRef, onMounted, onUnmounted } from 'vue';
|
import { watch, ref, shallowRef, onMounted, onUnmounted } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import XContainer from '../page-editor.container.vue';
|
import XContainer from '../page-editor.container.vue';
|
||||||
|
|
|
@ -2,21 +2,53 @@
|
||||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Sortable :modelValue="modelValue" tag="div" itemKey="id" handle=".drag-handle" :group="{ name: 'blocks' }" :animation="150" :swapThreshold="0.5" @update:modelValue="v => emit('update:modelValue', v)">
|
<div
|
||||||
<template #item="{element}">
|
@dragstart.capture="dragStart"
|
||||||
<div :class="$style.item">
|
@dragend.capture="dragEnd"
|
||||||
<!-- divが無いとエラーになる https://github.com/SortableJS/vue.draggable.next/issues/189 -->
|
@drop.capture="dragEnd"
|
||||||
<component :is="getComponent(element.type)" :modelValue="element" @update:modelValue="updateItem" @remove="() => removeItem(element)"/>
|
>
|
||||||
|
<div
|
||||||
|
data-after-id="__FIRST__"
|
||||||
|
:class="[$style.insertBetweenRoot, {
|
||||||
|
[$style.insertBetweenDraggingOver]: draggingOverAfterId === '__FIRST__' && draggingBlockId !== modelValue[0]?.id,
|
||||||
|
}]"
|
||||||
|
@dragover="insertBetweenDragOver($event, '__FIRST__')"
|
||||||
|
@dragleave="insertBetweenDragLeave"
|
||||||
|
@drop="insertBetweenDrop($event, '__FIRST__')"
|
||||||
|
>
|
||||||
|
<div :class="$style.insertBetweenBorder"></div>
|
||||||
|
<span :class="$style.insertBetweenText">{{ i18n.ts._pages.moveToHere }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-for="block, index in modelValue" :key="block.id" :class="$style.item">
|
||||||
|
<!-- divが無いとエラーになる https://github.com/SortableJS/vue.draggable.next/issues/189 -->
|
||||||
|
<component
|
||||||
|
:is="getComponent(block.type)"
|
||||||
|
:modelValue="block"
|
||||||
|
@update:modelValue="updateItem"
|
||||||
|
@remove="() => removeItem(block)"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
:data-after-id="block.id"
|
||||||
|
:class="[$style.insertBetweenRoot, {
|
||||||
|
[$style.insertBetweenDraggingOver]: draggingOverAfterId === block.id && draggingBlockId !== block.id && draggingBlockId !== modelValue[index + 1]?.id,
|
||||||
|
}]"
|
||||||
|
@dragover="insertBetweenDragOver($event, block.id, modelValue[index + 1]?.id)"
|
||||||
|
@dragleave="insertBetweenDragLeave"
|
||||||
|
@drop="insertBetweenDrop($event, block.id, modelValue[index + 1]?.id)"
|
||||||
|
>
|
||||||
|
<div :class="$style.insertBetweenBorder"></div>
|
||||||
|
<span :class="$style.insertBetweenText">{{ i18n.ts._pages.moveToHere }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</Sortable>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent } from 'vue';
|
import { ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
import XSection from './els/page-editor.el.section.vue';
|
import XSection from './els/page-editor.el.section.vue';
|
||||||
import XText from './els/page-editor.el.text.vue';
|
import XText from './els/page-editor.el.text.vue';
|
||||||
import XImage from './els/page-editor.el.image.vue';
|
import XImage from './els/page-editor.el.image.vue';
|
||||||
|
@ -32,8 +64,6 @@ function getComponent(type: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: Misskey.entities.Page['content'];
|
modelValue: Misskey.entities.Page['content'];
|
||||||
}>();
|
}>();
|
||||||
|
@ -42,7 +72,75 @@ const emit = defineEmits<{
|
||||||
(ev: 'update:modelValue', value: Misskey.entities.Page['content']): void;
|
(ev: 'update:modelValue', value: Misskey.entities.Page['content']): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
function updateItem(v) {
|
const isDragging = ref(false);
|
||||||
|
const draggingOverAfterId = ref<string | null>(null);
|
||||||
|
const draggingBlockId = ref<string | null>(null);
|
||||||
|
|
||||||
|
function dragStart(ev: DragEvent) {
|
||||||
|
if (ev.target instanceof HTMLElement) {
|
||||||
|
const blockId = ev.target.dataset.blockId;
|
||||||
|
if (blockId != null) {
|
||||||
|
console.log('dragStart', blockId);
|
||||||
|
ev.dataTransfer!.setData('text/plain', blockId);
|
||||||
|
isDragging.value = true;
|
||||||
|
draggingBlockId.value = blockId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragEnd() {
|
||||||
|
isDragging.value = false;
|
||||||
|
draggingBlockId.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertBetweenDragOver(ev: DragEvent, id: string, nextId?: string) {
|
||||||
|
if (draggingBlockId.value === id || draggingBlockId.value === nextId) return;
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
if (ev.target instanceof HTMLElement) {
|
||||||
|
const afterId = ev.target.dataset.afterId;
|
||||||
|
if (afterId != null) {
|
||||||
|
draggingOverAfterId.value = afterId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertBetweenDragLeave() {
|
||||||
|
draggingOverAfterId.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertBetweenDrop(ev: DragEvent, id: string, nextId?: string) {
|
||||||
|
if (draggingBlockId.value === id || draggingBlockId.value === nextId) return;
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
if (ev.target instanceof HTMLElement) {
|
||||||
|
const afterId = ev.target.dataset.afterId; // insert after this
|
||||||
|
const moveId = ev.dataTransfer?.getData('text/plain');
|
||||||
|
if (afterId != null && moveId != null) {
|
||||||
|
const oldValue = props.modelValue.filter((x) => x.id !== moveId);
|
||||||
|
const afterIdAt = afterId === '__FIRST__' ? 0 : oldValue.findIndex((x) => x.id === afterId);
|
||||||
|
const movingBlock = props.modelValue.find((x) => x.id === moveId);
|
||||||
|
if (afterId === '__FIRST__' && movingBlock != null) {
|
||||||
|
const newValue = [
|
||||||
|
movingBlock,
|
||||||
|
...oldValue,
|
||||||
|
];
|
||||||
|
emit('update:modelValue', newValue);
|
||||||
|
} else if (afterIdAt >= 0 && movingBlock != null) {
|
||||||
|
const newValue = [
|
||||||
|
...oldValue.slice(0, afterIdAt + 1),
|
||||||
|
movingBlock,
|
||||||
|
...oldValue.slice(afterIdAt + 1),
|
||||||
|
];
|
||||||
|
emit('update:modelValue', newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isDragging.value = false;
|
||||||
|
draggingOverAfterId.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateItem(v: Misskey.entities.PageBlock) {
|
||||||
const i = props.modelValue.findIndex(x => x.id === v.id);
|
const i = props.modelValue.findIndex(x => x.id === v.id);
|
||||||
const newValue = [
|
const newValue = [
|
||||||
...props.modelValue.slice(0, i),
|
...props.modelValue.slice(0, i),
|
||||||
|
@ -52,8 +150,8 @@ function updateItem(v) {
|
||||||
emit('update:modelValue', newValue);
|
emit('update:modelValue', newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeItem(el) {
|
function removeItem(v: Misskey.entities.PageBlock) {
|
||||||
const i = props.modelValue.findIndex(x => x.id === el.id);
|
const i = props.modelValue.findIndex(x => x.id === v.id);
|
||||||
const newValue = [
|
const newValue = [
|
||||||
...props.modelValue.slice(0, i),
|
...props.modelValue.slice(0, i),
|
||||||
...props.modelValue.slice(i + 1),
|
...props.modelValue.slice(i + 1),
|
||||||
|
@ -63,9 +161,51 @@ function removeItem(el) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.item {
|
.insertBetweenRoot {
|
||||||
& + .item {
|
height: calc(var(--MI-margin) * 2);
|
||||||
margin-top: 16px;
|
width: 100%;
|
||||||
|
border-radius: 2px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertBetweenBorder {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
height: 4px;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: var(--MI_THEME-accent);
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertBetweenText {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
color: var(--MI_THEME-fgOnAccent);
|
||||||
|
padding: 0 14px;
|
||||||
|
line-height: 24px;
|
||||||
|
border-radius: 999px;
|
||||||
|
display: none;
|
||||||
|
background-color: var(--MI_THEME-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertBetweenBorder,
|
||||||
|
.insertBetweenText {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertBetweenDraggingOver {
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
.insertBetweenBorder {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.insertBetweenText {
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,24 +4,35 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="cpjygsrt">
|
<div
|
||||||
<header>
|
:class="[$style.blockContainerRoot, {
|
||||||
<div class="title"><slot name="header"></slot></div>
|
[$style.dragging]: isDragging,
|
||||||
<div class="buttons">
|
[$style.draggingOver]: isDraggingOver,
|
||||||
<slot name="func"></slot>
|
}]"
|
||||||
<button v-if="removable" class="_button" @click="remove()">
|
@dragover="dragOver"
|
||||||
|
@dragleave="dragLeave"
|
||||||
|
@drop="drop"
|
||||||
|
>
|
||||||
|
<header :class="$style.blockContainerHeader">
|
||||||
|
<div :class="$style.title"><slot name="header"></slot></div>
|
||||||
|
<div :class="$style.buttons">
|
||||||
|
<button v-if="removable" :class="$style.blockContainerActionButton" class="_button" @click="remove()">
|
||||||
<i class="ti ti-trash"></i>
|
<i class="ti ti-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="draggable" class="drag-handle _button">
|
<button
|
||||||
|
v-if="draggable"
|
||||||
|
draggable="true"
|
||||||
|
:class="$style.blockContainerActionButton"
|
||||||
|
class="_button"
|
||||||
|
:data-block-id="blockId"
|
||||||
|
@dragstart="dragStart"
|
||||||
|
@dragend="dragEnd"
|
||||||
|
>
|
||||||
<i class="ti ti-menu-2"></i>
|
<i class="ti ti-menu-2"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="_button" @click="toggleContent(!showBody)">
|
|
||||||
<template v-if="showBody"><i class="ti ti-chevron-up"></i></template>
|
|
||||||
<template v-else><i class="ti ti-chevron-down"></i></template>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div v-show="showBody" class="body">
|
<div :class="$style.blockContainerBody" tabindex="0">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,6 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
|
blockId: string;
|
||||||
expanded?: boolean;
|
expanded?: boolean;
|
||||||
removable?: boolean;
|
removable?: boolean;
|
||||||
draggable?: boolean;
|
draggable?: boolean;
|
||||||
|
@ -40,24 +52,80 @@ const props = withDefaults(defineProps<{
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'toggle', show: boolean): void;
|
|
||||||
(ev: 'remove'): void;
|
(ev: 'remove'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const showBody = ref(props.expanded);
|
|
||||||
|
|
||||||
function toggleContent(show: boolean) {
|
|
||||||
showBody.value = show;
|
|
||||||
emit('toggle', show);
|
|
||||||
}
|
|
||||||
|
|
||||||
function remove() {
|
function remove() {
|
||||||
emit('remove');
|
emit('remove');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDragging = ref(false);
|
||||||
|
function dragStart(ev: DragEvent) {
|
||||||
|
ev.dataTransfer?.setData('text/plain', props.blockId);
|
||||||
|
isDragging.value = true;
|
||||||
|
}
|
||||||
|
function dragEnd() {
|
||||||
|
isDragging.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDraggingOver = ref(false);
|
||||||
|
function dragOver(ev: DragEvent) {
|
||||||
|
if (isDragging.value) {
|
||||||
|
// ブロックの中にドロップできるのは自分自身だけ
|
||||||
|
ev.preventDefault();
|
||||||
|
isDraggingOver.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function dragLeave() {
|
||||||
|
isDraggingOver.value = false;
|
||||||
|
}
|
||||||
|
function drop() {
|
||||||
|
// 自分自身しかドロップできないので何もしない
|
||||||
|
isDraggingOver.value = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" module>
|
||||||
.cpjygsrt {
|
.blockContainerRoot {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blockContainerHeader {
|
||||||
|
position: absolute;
|
||||||
|
box-sizing: border-box;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
transform: translateY(-100%);
|
||||||
|
z-index: 1;
|
||||||
|
display: none;
|
||||||
|
gap: var(--MI-margin);
|
||||||
|
|
||||||
|
height: 42px;
|
||||||
|
padding: 6px 14px;
|
||||||
|
background-color: var(--MI_THEME-panel);
|
||||||
|
border: 2px solid var(--MI_THEME-accent);
|
||||||
|
border-bottom: none;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
|
||||||
|
> .title {
|
||||||
|
line-height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blockContainerActionButton {
|
||||||
|
display: block;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
line-height: 26px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blockContainerBody {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--MI_THEME-panel);
|
background: var(--MI_THEME-panel);
|
||||||
|
@ -67,62 +135,45 @@ function remove() {
|
||||||
&:hover {
|
&:hover {
|
||||||
border: solid 2px var(--MI_THEME-X13);
|
border: solid 2px var(--MI_THEME-X13);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.warn {
|
.blockContainerRoot.dragging {
|
||||||
border: solid 2px #dec44c;
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: var(--MI_THEME-bg);
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 8px 0 8px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.error {
|
&.draggingOver::after {
|
||||||
border: solid 2px #f00;
|
outline: dashed 2px var(--MI_THEME-accent);
|
||||||
|
outline-offset: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> header {
|
.blockContainerHeader {
|
||||||
> .title {
|
display: flex;
|
||||||
z-index: 1;
|
}
|
||||||
margin: 0;
|
|
||||||
padding: 0 16px;
|
|
||||||
line-height: 42px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
font-weight: bold;
|
|
||||||
box-shadow: 0 1px rgba(#000, 0.07);
|
|
||||||
|
|
||||||
> i {
|
.blockContainerBody {
|
||||||
margin-right: 6px;
|
border: solid 2px var(--MI_THEME-accent);
|
||||||
}
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:empty {
|
@container (min-width: 700px) {
|
||||||
display: none;
|
.blockContainerRoot:focus-within {
|
||||||
}
|
.blockContainerHeader {
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .buttons {
|
.blockContainerBody {
|
||||||
position: absolute;
|
border: solid 2px var(--MI_THEME-accent);
|
||||||
z-index: 2;
|
border-top-right-radius: 0;
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
> button {
|
|
||||||
padding: 0;
|
|
||||||
width: 42px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
line-height: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drag-handle {
|
|
||||||
cursor: move;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .body {
|
|
||||||
::v-deep(.juejbjww), ::v-deep(.eiipwacr) {
|
|
||||||
&:not(.inline):first-child {
|
|
||||||
margin-top: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.inline):last-child {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div v-if="fetchStatus === 'loading'">
|
<div v-if="fetchStatus === 'loading'">
|
||||||
<MkLoading/>
|
<MkLoading/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="fetchStatus === 'done' && page != null" :class="$style.pageMain">
|
<div v-else-if="fetchStatus === 'done' && page != null" class="_gaps" :class="$style.pageMain">
|
||||||
<div :class="$style.pageBanner">
|
<div :class="$style.pageBanner">
|
||||||
<div v-if="page?.eyeCatchingImageId" :class="$style.pageBannerImage">
|
<div v-if="page?.eyeCatchingImageId" :class="$style.pageBannerImage">
|
||||||
<MkMediaImage
|
<MkMediaImage
|
||||||
|
@ -22,22 +22,32 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.pageBannerTitle" class="_gaps_s">
|
<div :class="$style.pageBannerTitle" class="_gaps_s">
|
||||||
<h1></h1>
|
<input v-model="title" :class="$style.titleForm" :placeholder="i18n.ts._pages.inputTitleHere"/>
|
||||||
<div :class="$style.pageBannerTitleSub">
|
<div :class="$style.pageBannerTitleSub">
|
||||||
<div v-if="page?.user" :class="$style.pageBannerTitleUser">
|
<div v-if="page?.user" :class="$style.pageBannerTitleUser">
|
||||||
<MkAvatar :user="page.user" :class="$style.avatar" indicator link preview/> <MkA :to="`/@${username}`"><MkUserName :user="page.user" :nowrap="false"/></MkA>
|
<MkAvatar :user="page.user" :class="$style.avatar" indicator/> <MkUserName :user="page.user" :nowrap="false"/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.pageBannerTitleSubActions">
|
<div :class="$style.pageBannerTitleSubActions">
|
||||||
<MkA v-if="page?.userId === $i?.id" v-tooltip="i18n.ts._pages.editThisPage" :to="`/pages/edit/${page.id}`" class="_button" :class="$style.generalActionButton"><i class="ti ti-pencil ti-fw"></i></MkA>
|
|
||||||
<button v-tooltip="i18n.ts.share" class="_button" :class="$style.generalActionButton" @click="share"><i class="ti ti-share ti-fw"></i></button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div :class="$style.pageContent">
|
||||||
|
<XBlocks v-model="content"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="fetchStatus === 'notMe'" class="_fullInfo">
|
<div v-else-if="fetchStatus === 'notMe'" class="_fullInfo">
|
||||||
This page is not yours
|
This page is not yours
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
<template #footer>
|
||||||
|
<div :class="$style.footer">
|
||||||
|
<div class="_buttons" :class="$style.footerInner">
|
||||||
|
<MkButton primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
||||||
|
<MkButton @click="show"><i class="ti ti-eye"></i> {{ i18n.ts.show }}</MkButton>
|
||||||
|
<MkButton v-if="initPageId != null" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -69,6 +79,18 @@ const $i = signinRequired();
|
||||||
|
|
||||||
const fetchStatus = ref<'loading' | 'done' | 'notMe'>('loading');
|
const fetchStatus = ref<'loading' | 'done' | 'notMe'>('loading');
|
||||||
const page = ref<Partial<Misskey.entities.Page> | null>(null);
|
const page = ref<Partial<Misskey.entities.Page> | null>(null);
|
||||||
|
const title = computed({
|
||||||
|
get: () => page.value?.title ?? '',
|
||||||
|
set: (value) => {
|
||||||
|
if (page.value) {
|
||||||
|
page.value.title = value;
|
||||||
|
} else {
|
||||||
|
page.value = {
|
||||||
|
title: value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
const content = computed<Misskey.entities.Page['content']>({
|
const content = computed<Misskey.entities.Page['content']>({
|
||||||
get: () => page.value?.content ?? [],
|
get: () => page.value?.content ?? [],
|
||||||
set: (value) => {
|
set: (value) => {
|
||||||
|
@ -82,6 +104,22 @@ const content = computed<Misskey.entities.Page['content']>({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function onTitleUpdated(ev: Event) {
|
||||||
|
title.value = (ev.target as HTMLDivElement).innerText;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function show() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function del() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
if (props.initPageId) {
|
if (props.initPageId) {
|
||||||
const _page = await misskeyApi('pages/show', {
|
const _page = await misskeyApi('pages/show', {
|
||||||
|
@ -91,6 +129,7 @@ async function init() {
|
||||||
fetchStatus.value = 'notMe';
|
fetchStatus.value = 'notMe';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
page.value = _page;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page.value === null) {
|
if (page.value === null) {
|
||||||
|
@ -127,7 +166,7 @@ definePageMetadata(() => ({
|
||||||
|
|
||||||
.pageBanner {
|
.pageBanner {
|
||||||
width: calc(100% + 4rem);
|
width: calc(100% + 4rem);
|
||||||
margin: -2rem -2rem 1.5rem;
|
margin: -2rem -2rem 0.5rem;
|
||||||
border-radius: var(--MI-radius) var(--MI-radius) 0 0;
|
border-radius: var(--MI-radius) var(--MI-radius) 0 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -149,11 +188,36 @@ definePageMetadata(() => ({
|
||||||
.pageBannerTitle {
|
.pageBannerTitle {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
h1 {
|
.titleForm {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font: inherit;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--MI_THEME-fg);
|
color: var(--MI_THEME-fg);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid var(--MI_THEME-divider);
|
||||||
|
transition: border-color 0.1s ease-out;
|
||||||
|
background-color: var(--MI_THEME-bg);
|
||||||
|
border-radius: var(--MI-radius) var(--MI-radius) 0 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--MI_THEME-inputBorderHover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--MI_THEME-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 2px solid var(--MI_THEME-focus);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pageBannerTitleSub {
|
.pageBannerTitleSub {
|
||||||
|
@ -181,4 +245,16 @@ definePageMetadata(() => ({
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
backdrop-filter: var(--MI-blur, blur(15px));
|
||||||
|
background: var(--MI_THEME-acrylicBg);
|
||||||
|
border-top: solid .5px var(--MI_THEME-divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerInner {
|
||||||
|
padding: 16px;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue