diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 57af29389d..ee94c53da2 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -95,7 +95,7 @@ sensitive: "閲覧注意" add: "追加" reaction: "リアクション" reactionSettingDescription: "リアクションピッカーに表示するリアクションを設定します。" -reactionSettingDescription2: "ドラッグして並び替えます。クリックして削除します。" +reactionSettingDescription2: "ドラッグして並び替え、クリックして削除、+を押して追加します。" rememberNoteVisibility: "公開範囲を記憶する" attachCancel: "添付取り消し" markAsSensitive: "閲覧注意にする" diff --git a/package.json b/package.json index 3e0a3d0cd0..e383349202 100644 --- a/package.json +++ b/package.json @@ -242,13 +242,13 @@ "vanilla-tilt": "1.7.0", "vue": "3.0.3", "vue-color": "2.7.1", - "vue-draggable-next": "1.0.8", "vue-i18n": "9.0.0-beta.7", "vue-json-pretty": "1.7.1", "vue-loader": "16.0.0", "vue-prism-editor": "2.0.0-alpha.2", "vue-router": "4.0.0-rc.6", "vue-style-loader": "4.1.2", + "vuedraggable": "4.0.1", "vuex": "4.0.0-rc.2", "vuex-persistedstate": "3.1.0", "web-push": "3.4.4", diff --git a/src/client/components/post-form-attaches.vue b/src/client/components/post-form-attaches.vue index 6f3d1bca66..4a14112b10 100644 --- a/src/client/components/post-form-attaches.vue +++ b/src/client/components/post-form-attaches.vue @@ -1,12 +1,14 @@ <template> <div class="skeikyzd" v-show="files.length != 0"> - <XDraggable class="files" :list="files" animation="150" delay="100" delay-on-touch-only="true"> - <div v-for="file in files" :key="file.id" @click="showFileMenu(file, $event)" @contextmenu.prevent="showFileMenu(file, $event)"> - <MkDriveFileThumbnail :data-id="file.id" class="thumbnail" :file="file" fit="cover"/> - <div class="sensitive" v-if="file.isSensitive"> - <Fa class="icon" :icon="faExclamationTriangle"/> + <XDraggable class="files" v-model="_files" item-key="id" animation="150" delay="100" delay-on-touch-only="true"> + <template #item="{element}"> + <div @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)"> + <MkDriveFileThumbnail :data-id="element.id" class="thumbnail" :file="element" fit="cover"/> + <div class="sensitive" v-if="element.isSensitive"> + <Fa class="icon" :icon="faExclamationTriangle"/> + </div> </div> - </div> + </template> </XDraggable> <p class="remain">{{ 4 - files.length }}/4</p> </div> @@ -21,7 +23,7 @@ import * as os from '@/os'; export default defineComponent({ components: { - XDraggable: defineAsyncComponent(() => import('vue-draggable-next').then(x => x.VueDraggableNext)), + XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), MkDriveFileThumbnail }, @@ -36,7 +38,7 @@ export default defineComponent({ } }, - emits: ['updated', 'detach'], + emits: ['updated', 'detach', 'changeSensitive', 'changeName'], data() { return { @@ -46,6 +48,17 @@ export default defineComponent({ }; }, + computed: { + _files: { + get() { + return this.files; + }, + set(value) { + this.$emit('updated', value); + } + } + }, + methods: { detachMedia(id) { if (this.detachMediaFn) { @@ -59,8 +72,7 @@ export default defineComponent({ fileId: file.id, isSensitive: !file.isSensitive }).then(() => { - file.isSensitive = !file.isSensitive; - this.$emit('updated', file); + this.$emit('changeSensitive', file, !file.isSensitive); }); }, async rename(file) { @@ -76,8 +88,8 @@ export default defineComponent({ fileId: file.id, name: result }).then(() => { + this.$emit('changeName', file, result); file.name = result; - this.$emit('updated', file); }); }, showFileMenu(file, ev: MouseEvent) { diff --git a/src/client/components/post-form.vue b/src/client/components/post-form.vue index 020b925fb9..3f4561dad8 100644 --- a/src/client/components/post-form.vue +++ b/src/client/components/post-form.vue @@ -36,7 +36,7 @@ </div> <input v-show="useCw" ref="cw" class="cw" v-model="cw" :placeholder="$t('annotation')" @keydown="onKeydown"> <textarea v-model="text" class="text" :class="{ withCw: useCw }" ref="text" :disabled="posting" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste"></textarea> - <XPostFormAttaches class="attaches" :files="files" @updated="updateMedia" @detach="detachMedia"/> + <XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> <XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/> <footer> <button class="_button" @click="chooseFileFrom" v-tooltip="$t('attachFile')"><Fa :icon="faPhotoVideo"/></button> @@ -359,12 +359,20 @@ export default defineComponent({ }); }, - detachMedia(id) { + detachFile(id) { this.files = this.files.filter(x => x.id != id); }, - updateMedia(file) { - this.files[this.files.findIndex(x => x.id === file.id)] = file; + updateFiles(files) { + this.files = files; + }, + + updateFileSensitive(file, sensitive) { + this.files[this.files.findIndex(x => x.id === file.id)].isSensitive = sensitive; + }, + + updateFileName(file, name) { + this.files[this.files.findIndex(x => x.id === file.id)].name = name; }, upload(file: File, name?: string) { diff --git a/src/client/pages/page-editor/page-editor.blocks.vue b/src/client/pages/page-editor/page-editor.blocks.vue index 5cf9eb42f7..ccaa715111 100644 --- a/src/client/pages/page-editor/page-editor.blocks.vue +++ b/src/client/pages/page-editor/page-editor.blocks.vue @@ -1,6 +1,8 @@ <template> -<XDraggable tag="div" :list="blocks" handle=".drag-handle" :group="{ name: 'blocks' }" animation="150" swap-threshold="0.5"> - <component v-for="block in blocks" :is="'x-' + block.type" :value="block" @update:value="updateItem" @remove="() => removeItem(block)" :key="block.id" :hpml="hpml"/> +<XDraggable tag="div" v-model="blocks" item-key="id" handle=".drag-handle" :group="{ name: 'blocks' }" animation="150" swap-threshold="0.5"> + <template #item="{element}"> + <component :is="'x-' + element.type" :value="element" @update:value="updateItem" @remove="() => removeItem(element)" :hpml="hpml"/> + </template> </XDraggable> </template> @@ -25,7 +27,7 @@ import * as os from '@/os'; export default defineComponent({ components: { - XDraggable: defineAsyncComponent(() => import('vue-draggable-next').then(x => x.VueDraggableNext)), + XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost, XCounter, XRadioButton, XCanvas, XNote }, @@ -39,9 +41,16 @@ export default defineComponent({ }, }, + emits: ['update:value'], + computed: { - blocks() { - return this.value; + blocks: { + get() { + return this.value; + }, + set(value) { + this.$emit('update:value', value); + } } }, @@ -57,6 +66,7 @@ export default defineComponent({ }, removeItem(el) { + console.log(el); const i = this.blocks.findIndex(x => x.id === el.id); const newValue = [ ...this.blocks.slice(0, i), diff --git a/src/client/pages/page-editor/page-editor.container.vue b/src/client/pages/page-editor/page-editor.container.vue index 1534641328..46e2dca157 100644 --- a/src/client/pages/page-editor/page-editor.container.vue +++ b/src/client/pages/page-editor/page-editor.container.vue @@ -52,6 +52,7 @@ export default defineComponent({ default: null } }, + emits: ['toggle', 'remove'], data() { return { showBody: this.expanded, diff --git a/src/client/pages/page-editor/page-editor.vue b/src/client/pages/page-editor/page-editor.vue index eab48c766e..abf3264b48 100644 --- a/src/client/pages/page-editor/page-editor.vue +++ b/src/client/pages/page-editor/page-editor.vue @@ -53,18 +53,19 @@ <MkContainer :body-togglable="true" class="_vMargin"> <template #header><Fa :icon="faMagic"/> {{ $t('_pages.variables') }}</template> <div class="qmuvgica"> - <XDraggable tag="div" class="variables" v-show="variables.length > 0" :list="variables" handle=".drag-handle" :group="{ name: 'variables' }" animation="150" swap-threshold="0.5"> - <XVariable v-for="variable in variables" - :value="variable" - :removable="true" - @update:value="v => updateVariable(v)" - @remove="() => removeVariable(variable)" - :key="variable.name" - :hpml="hpml" - :name="variable.name" - :title="variable.name" - :draggable="true" - /> + <XDraggable tag="div" class="variables" v-show="variables.length > 0" v-model="variables" item-key="name" handle=".drag-handle" :group="{ name: 'variables' }" animation="150" swap-threshold="0.5"> + <template #item="{element}"> + <XVariable + :value="element" + :removable="true" + @update:value="v => updateVariable(v)" + @remove="() => removeVariable(element)" + :hpml="hpml" + :name="element.name" + :title="element.name" + :draggable="true" + /> + </template> </XDraggable> <MkButton @click="addVariable()" class="add" v-if="!readonly"><Fa :icon="faPlus"/></MkButton> @@ -109,7 +110,7 @@ import { selectFile } from '@/scripts/select-file'; export default defineComponent({ components: { - XDraggable: defineAsyncComponent(() => import('vue-draggable-next').then(x => x.VueDraggableNext)), + XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), XVariable, XBlocks, MkTextarea, MkContainer, MkButton, MkSelect, MkSwitch, MkInput, }, diff --git a/src/client/pages/settings/reaction.vue b/src/client/pages/settings/reaction.vue index 75dae29068..ec1d4e5cf2 100644 --- a/src/client/pages/settings/reaction.vue +++ b/src/client/pages/settings/reaction.vue @@ -3,16 +3,18 @@ <div class="_formItem"> <div class="_formLabel">{{ $t('reactionSettingDescription') }}</div> <div class="_formPanel"> - <XDraggable class="zoaiodol" :list="reactions" animation="150" delay="100" delay-on-touch-only="true"> - <button class="_button item" v-for="reaction in reactions" :key="reaction" @click="remove(reaction, $event)"> - <MkEmoji :emoji="reaction" :normal="true"/> - </button> + <XDraggable class="zoaiodol" v-model="reactions" :item-key="item => item" animation="150" delay="100" delay-on-touch-only="true"> + <template #item="{element}"> + <button class="_button item" @click="remove(element, $event)"> + <MkEmoji :emoji="element" :normal="true"/> + </button> + </template> <template #footer> - <button>a</button> + <button class="_button add" @click="chooseEmoji"><Fa :icon="faPlus"/></button> </template> </XDraggable> </div> - <div class="_formCaption">{{ $t('reactionSettingDescription2') }} <button class="_textButton" @click="chooseEmoji">{{ $t('chooseEmoji') }}</button></div> + <div class="_formCaption">{{ $t('reactionSettingDescription2') }} <button class="_textButton" @click="preview">{{ $t('preview') }}</button></div> </div> <FormRadios v-model="reactionPickerWidth"> @@ -35,8 +37,8 @@ <script lang="ts"> import { defineComponent } from 'vue'; import { faLaugh, faSave, faEye } from '@fortawesome/free-regular-svg-icons'; -import { faUndo } from '@fortawesome/free-solid-svg-icons'; -import { VueDraggableNext } from 'vue-draggable-next'; +import { faUndo, faPlus } from '@fortawesome/free-solid-svg-icons'; +import XDraggable from 'vuedraggable'; import FormInput from '@/components/form/input.vue'; import FormRadios from '@/components/form/radios.vue'; import FormBase from '@/components/form/base.vue'; @@ -50,7 +52,7 @@ export default defineComponent({ FormButton, FormBase, FormRadios, - XDraggable: VueDraggableNext, + XDraggable, }, emits: ['info'], @@ -66,7 +68,7 @@ export default defineComponent({ } }, reactions: JSON.parse(JSON.stringify(this.$store.state.settings.reactions)), - faLaugh, faSave, faEye, faUndo + faLaugh, faSave, faEye, faUndo, faPlus } }, @@ -152,5 +154,10 @@ export default defineComponent({ padding: 8px; cursor: move; } + + > .add { + display: inline-block; + padding: 8px; + } } </style> diff --git a/src/client/store.ts b/src/client/store.ts index 2c63e79503..23d06664a2 100644 --- a/src/client/store.ts +++ b/src/client/store.ts @@ -298,6 +298,7 @@ export const store = createStore({ }, //#region Deck + // TODO: deck関連は動的にモジュール読み込みしたい addDeckColumn(state, column) { if (column.name == undefined) column.name = null; state.deck.columns.push(column); @@ -415,6 +416,12 @@ export const store = createStore({ column.widgets = column.widgets.filter(w => w.id != x.widget.id); }, + setDeckWidgets(state, x) { + const column = state.deck.columns.find(c => c.id == x.id); + if (column == null) return; + column.widgets = x.widgets; + }, + renameDeckColumn(state, x) { const column = state.deck.columns.find(c => c.id == x.id); if (column == null) return; @@ -422,9 +429,9 @@ export const store = createStore({ }, updateDeckColumn(state, x) { - let column = state.deck.columns.find(c => c.id == x.id); - if (column == null) return; - column = x; + const column = state.deck.columns.findIndex(c => c.id == x.id); + if (column > -1) return; + state.deck.columns[column] = x; }, //#endregion diff --git a/src/client/ui/deck.vue b/src/client/ui/deck.vue index ad65ccd499..b1219c34ff 100644 --- a/src/client/ui/deck.vue +++ b/src/client/ui/deck.vue @@ -3,7 +3,7 @@ <XSidebar ref="nav"/> <!-- TODO: deckMainColumnPlace を見て位置変える --> - <deck-column class="column" v-if="$store.state.device.deckAlwaysShowMainColumn || $route.name !== 'index'"> + <DeckColumn class="column" v-if="$store.state.device.deckAlwaysShowMainColumn || $route.name !== 'index'"> <template #header> <XHeader :info="pageInfo"/> </template> @@ -15,13 +15,13 @@ </keep-alive> </transition> </router-view> - </deck-column> + </DeckColumn> <template v-for="ids in layout"> <div v-if="ids.length > 1" class="folder column"> - <deck-column-core v-for="id in ids" :ref="id" :key="id" :column="columns.find(c => c.id === id)" :is-stacked="true" @parent-focus="moveFocus(id, $event)"/> + <DeckColumnCore v-for="id in ids" :ref="id" :key="id" :column="columns.find(c => c.id === id)" :is-stacked="true" @parent-focus="moveFocus(id, $event)"/> </div> - <deck-column-core v-else class="column" :ref="ids[0]" :key="ids[0]" :column="columns.find(c => c.id === ids[0])" @parent-focus="moveFocus(ids[0], $event)"/> + <DeckColumnCore v-else class="column" :ref="ids[0]" :key="ids[0]" :column="columns.find(c => c.id === ids[0])" @parent-focus="moveFocus(ids[0], $event)"/> </template> <button @click="addColumn" class="_button add"><Fa :icon="faPlus"/></button> diff --git a/src/client/ui/deck/widgets-column.vue b/src/client/ui/deck/widgets-column.vue index e19fb01e5e..f3ad5ab716 100644 --- a/src/client/ui/deck/widgets-column.vue +++ b/src/client/ui/deck/widgets-column.vue @@ -13,14 +13,16 @@ <MkButton inline @click="edit = false">{{ $t('close') }}</MkButton> </header> <XDraggable - :list="column.widgets" + v-model="_widgets" + item-key="id" animation="150" - @sort="onWidgetSort" > - <div v-for="widget in column.widgets" class="customize-container" :key="widget.id" @click="widgetFunc(widget.id)"> - <button class="remove _button" @click.prevent.stop="removeWidget(widget)"><Fa :icon="faTimes"/></button> - <component :is="`mkw-${widget.name}`" :widget="widget" :setting-callback="setting => settings[widget.id] = setting" :column="column"/> - </div> + <template #item="{element}"> + <div class="customize-container" @click="widgetFunc(element.id)"> + <button class="remove _button" @click.prevent.stop="removeWidget(element)"><Fa :icon="faTimes"/></button> + <component :is="`mkw-${element.name}`" :widget="element" :setting-callback="setting => settings[element.id] = setting" :column="column"/> + </div> + </template> </XDraggable> </template> <component v-else class="widget" v-for="widget in column.widgets" :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" :column="column"/> @@ -40,7 +42,7 @@ import { widgets } from '../../widgets'; export default defineComponent({ components: { XColumn, - XDraggable: defineAsyncComponent(() => import('vue-draggable-next').then(x => x.VueDraggableNext)), + XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), MkSelect, MkButton, }, @@ -67,6 +69,20 @@ export default defineComponent({ }; }, + computed: { + _widgets: { + get() { + return this.column.widgets; + }, + set(value) { + this.$store.commit('deviceUser/setDeckWidgets', { + id: this.column.id, + widgets: value + }); + } + } + }, + created() { this.menu = [{ icon: faCog, @@ -82,10 +98,6 @@ export default defineComponent({ this.settings[id](); }, - onWidgetSort() { - this.saveWidgets(); - }, - addWidget() { if (this.widgetAdderSelected == null) return; @@ -107,10 +119,6 @@ export default defineComponent({ widget }); }, - - saveWidgets() { - this.$store.commit('deviceUser/updateDeckColumn', this.column); - } } }); </script> diff --git a/src/client/ui/default.widgets.vue b/src/client/ui/default.widgets.vue index c41ba52a76..deedc912af 100644 --- a/src/client/ui/default.widgets.vue +++ b/src/client/ui/default.widgets.vue @@ -3,20 +3,22 @@ <template v-if="editMode"> <MkButton primary @click="addWidget" class="add"><Fa :icon="faPlus"/></MkButton> <XDraggable - :list="widgets" + v-model="widgets" + item-key="id" handle=".handle" animation="150" class="sortable" - @sort="onWidgetSort" > - <div v-for="widget in widgets" class="customize-container _panel" :key="widget.id"> - <header> - <span class="handle"><Fa :icon="faBars"/></span>{{ $t('_widgets.' + widget.name) }}<button class="remove _button" @click="removeWidget(widget)"><Fa :icon="faTimes"/></button> - </header> - <div @click="widgetFunc(widget.id)"> - <component class="_close_ _forceContainerFull_" :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :setting-callback="setting => settings[widget.id] = setting"/> + <template #item="{element}"> + <div class="customize-container _panel"> + <header> + <span class="handle"><Fa :icon="faBars"/></span>{{ $t('_widgets.' + element.name) }}<button class="remove _button" @click="removeWidget(element)"><Fa :icon="faTimes"/></button> + </header> + <div @click="widgetFunc(element.id)"> + <component class="_close_ _forceContainerFull_" :is="`mkw-${element.name}`" :widget="element" :ref="element.id" :setting-callback="setting => settings[element.id] = setting"/> + </div> </div> - </div> + </template> </XDraggable> <button @click="editMode = false" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faCheck"/> {{ $t('editWidgetsExit') }}</button> </template> @@ -38,7 +40,7 @@ import MkButton from '@/components/ui/button.vue'; export default defineComponent({ components: { MkButton, - XDraggable: defineAsyncComponent(() => import('vue-draggable-next').then(x => x.VueDraggableNext)), + XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), }, emits: ['mounted'], @@ -52,8 +54,13 @@ export default defineComponent({ }, computed: { - widgets(): any { - return this.$store.state.deviceUser.widgets; + widgets: { + get() { + return this.$store.state.deviceUser.widgets; + }, + set(value) { + this.$store.commit('deviceUser/setWidgets', value); + } }, }, @@ -66,11 +73,6 @@ export default defineComponent({ this.settings[id](); }, - onWidgetSort() { - // TODO: vuexを直接書き換えているのでなんとかする - this.saveHome(); - }, - async addWidget() { const { canceled, result: widget } = await os.dialog({ type: null, diff --git a/yarn.lock b/yarn.lock index 92f083e2ea..e56214ff75 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8826,7 +8826,7 @@ sort-keys@^2.0.0: dependencies: is-plain-obj "^1.0.0" -sortablejs@^1.10.2: +sortablejs@1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.10.2.tgz#6e40364d913f98b85a14f6678f92b5c1221f5290" integrity sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A== @@ -10144,13 +10144,6 @@ vue-color@2.7.1: material-colors "^1.0.0" tinycolor2 "^1.1.2" -vue-draggable-next@1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/vue-draggable-next/-/vue-draggable-next-1.0.8.tgz#89a8b347422d20f694f977a125b9af2f67e85b99" - integrity sha512-c15YO8HC2Lo2+rWXNoLDwiAFzf4pY5L4ESJsWp526qWeLAf6WjIXYSchmsO6AD0ozFIc+sp6B9zKafpFotxNIQ== - dependencies: - sortablejs "^1.10.2" - vue-eslint-parser@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz#c43c1c715ff50778b9a7e9a4e16921185f3425d3" @@ -10211,6 +10204,13 @@ vue@3.0.3: "@vue/runtime-dom" "3.0.3" "@vue/shared" "3.0.3" +vuedraggable@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-4.0.1.tgz#3bcaab0808b7944030b7d9a29f9a63d59dfa12c5" + integrity sha512-7qN5jhB1SLfx5P+HCm3JUW+pvgA1bSLgYLSVOeLWBDH9z+zbaEH0OlyZBVMLOxFR+JUHJjwDD0oy7T4r9TEgDA== + dependencies: + sortablejs "1.10.2" + vuex-persistedstate@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/vuex-persistedstate/-/vuex-persistedstate-3.1.0.tgz#a710d01000bff8336bc3b03fa3ef42e376094b71"