use sortablejs-vue3 instead of vuedraggable for more stability
This commit is contained in:
		
							parent
							
								
									fe158339da
								
							
						
					
					
						commit
						60b3d73cc9
					
				
					 8 changed files with 176 additions and 179 deletions
				
			
		|  | @ -46,6 +46,8 @@ | ||||||
| 		"s-age": "1.1.2", | 		"s-age": "1.1.2", | ||||||
| 		"sass": "1.57.0", | 		"sass": "1.57.0", | ||||||
| 		"seedrandom": "3.0.5", | 		"seedrandom": "3.0.5", | ||||||
|  | 		"sortablejs": "^1.15.0", | ||||||
|  | 		"sortablejs-vue3": "^1.2.3", | ||||||
| 		"strict-event-emitter-types": "2.0.0", | 		"strict-event-emitter-types": "2.0.0", | ||||||
| 		"stringz": "2.1.0", | 		"stringz": "2.1.0", | ||||||
| 		"syuilo-password-strength": "0.0.1", | 		"syuilo-password-strength": "0.0.1", | ||||||
|  | @ -61,8 +63,7 @@ | ||||||
| 		"vanilla-tilt": "1.8.0", | 		"vanilla-tilt": "1.8.0", | ||||||
| 		"vite": "4.0.2", | 		"vite": "4.0.2", | ||||||
| 		"vue": "3.2.45", | 		"vue": "3.2.45", | ||||||
| 		"vue-prism-editor": "2.0.0-alpha.2", | 		"vue-prism-editor": "2.0.0-alpha.2" | ||||||
| 		"vuedraggable": "4.0.1" |  | ||||||
| 	}, | 	}, | ||||||
| 	"devDependencies": { | 	"devDependencies": { | ||||||
| 		"@types/escape-regexp": "0.0.1", | 		"@types/escape-regexp": "0.0.1", | ||||||
|  |  | ||||||
|  | @ -43,7 +43,7 @@ | ||||||
| 		<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" :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 class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> | 		<XPostFormAttaches v-model="files" class="attaches" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> | ||||||
| 		<XPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/> | 		<XPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/> | ||||||
| 		<XNotePreview v-if="showPreview" class="preview" :text="text"/> | 		<XNotePreview v-if="showPreview" class="preview" :text="text"/> | ||||||
| 		<footer> | 		<footer> | ||||||
|  | @ -370,10 +370,6 @@ function detachFile(id) { | ||||||
| 	files = files.filter(x => x.id !== id); | 	files = files.filter(x => x.id !== id); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function updateFiles(_files) { |  | ||||||
| 	files = _files; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function updateFileSensitive(file, sensitive) { | function updateFileSensitive(file, sensitive) { | ||||||
| 	files[files.findIndex(x => x.id === file.id)].isSensitive = sensitive; | 	files[files.findIndex(x => x.id === file.id)].isSensitive = sensitive; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| <template> | <template> | ||||||
| <div v-show="files.length != 0" class="skeikyzd"> | <div v-show="props.modelValue.length != 0" class="skeikyzd"> | ||||||
| 	<XDraggable v-model="_files" class="files" item-key="id" animation="150" delay="100" delay-on-touch-only="true"> | 	<Sortable :list="props.modelValue" class="files" item-key="id" :options="{ animation: 150, delay: 100, delayOnTouchOnly: true }" @end="onSorted"> | ||||||
| 		<template #item="{element}"> | 		<template #item="{element}"> | ||||||
| 			<div class="file" @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)"> | 			<div class="file" @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)"> | ||||||
| 				<MkDriveFileThumbnail :data-id="element.id" class="thumbnail" :file="element" fit="cover"/> | 				<MkDriveFileThumbnail :data-id="element.id" class="thumbnail" :file="element" fit="cover"/> | ||||||
|  | @ -9,128 +9,116 @@ | ||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
| 		</template> | 		</template> | ||||||
| 	</XDraggable> | 	</Sortable> | ||||||
| 	<p class="remain">{{ 16 - files.length }}/16</p> | 	<p class="remain">{{ 16 - props.modelValue.length }}/16</p> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts" setup> | ||||||
| import { defineComponent, defineAsyncComponent } from 'vue'; | import { defineAsyncComponent } from 'vue'; | ||||||
| import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; | import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; | ||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
|  | import { deepClone } from '@/scripts/clone'; | ||||||
|  | import { i18n } from '@/i18n'; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | const Sortable = defineAsyncComponent(() => import('sortablejs-vue3').then(x => x.Sortable)); | ||||||
| 	components: { |  | ||||||
| 		XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), |  | ||||||
| 		MkDriveFileThumbnail, |  | ||||||
| 	}, |  | ||||||
| 
 | 
 | ||||||
| 	props: { | const props = defineProps<{ | ||||||
| 		files: { | 	modelValue: any[]; | ||||||
| 			type: Array, | 	detachMediaFn: () => void; | ||||||
| 			required: true, | }>(); | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits<{ | ||||||
|  | 	(ev: 'update:modelValue', value: any[]): void; | ||||||
|  | 	(ev: 'detach'): void; | ||||||
|  | 	(ev: 'changeSensitive'): void; | ||||||
|  | 	(ev: 'changeName'): void; | ||||||
|  | }>(); | ||||||
|  | 
 | ||||||
|  | let menuShowing = false; | ||||||
|  | 
 | ||||||
|  | function onSorted(event) { | ||||||
|  | 	const items = deepClone(props.modelValue); | ||||||
|  | 	const item = items.splice(event.oldIndex, 1)[0]; | ||||||
|  | 	items.splice(event.newIndex, 0, item); | ||||||
|  | 	emit('update:modelValue', items); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function detachMedia(id) { | ||||||
|  | 	if (props.detachMediaFn) { | ||||||
|  | 		props.detachMediaFn(id); | ||||||
|  | 	} else { | ||||||
|  | 		emit('detach', id); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function toggleSensitive(file) { | ||||||
|  | 	os.api('drive/files/update', { | ||||||
|  | 		fileId: file.id, | ||||||
|  | 		isSensitive: !file.isSensitive, | ||||||
|  | 	}).then(() => { | ||||||
|  | 		emit('changeSensitive', file, !file.isSensitive); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | async function rename(file) { | ||||||
|  | 	const { canceled, result } = await os.inputText({ | ||||||
|  | 		title: i18n.ts.enterFileName, | ||||||
|  | 		default: file.name, | ||||||
|  | 		allowEmpty: false, | ||||||
|  | 	}); | ||||||
|  | 	if (canceled) return; | ||||||
|  | 	os.api('drive/files/update', { | ||||||
|  | 		fileId: file.id, | ||||||
|  | 		name: result, | ||||||
|  | 	}).then(() => { | ||||||
|  | 		emit('changeName', file, result); | ||||||
|  | 		file.name = result; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function describe(file) { | ||||||
|  | 	os.popup(defineAsyncComponent(() => import('@/components/MkMediaCaption.vue')), { | ||||||
|  | 		title: i18n.ts.describeFile, | ||||||
|  | 		input: { | ||||||
|  | 			placeholder: i18n.ts.inputNewDescription, | ||||||
|  | 			default: file.comment !== null ? file.comment : '', | ||||||
| 		}, | 		}, | ||||||
| 		detachMediaFn: { | 		image: file, | ||||||
| 			type: Function, | 	}, { | ||||||
| 			required: false, | 		done: result => { | ||||||
| 		}, | 			if (!result || result.canceled) return; | ||||||
| 	}, | 			let comment = result.result.length === 0 ? null : result.result; | ||||||
| 
 |  | ||||||
| 	emits: ['updated', 'detach', 'changeSensitive', 'changeName'], |  | ||||||
| 
 |  | ||||||
| 	data() { |  | ||||||
| 		return { |  | ||||||
| 			menu: null as Promise<null> | null, |  | ||||||
| 		}; |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	computed: { |  | ||||||
| 		_files: { |  | ||||||
| 			get() { |  | ||||||
| 				return this.files; |  | ||||||
| 			}, |  | ||||||
| 			set(value) { |  | ||||||
| 				this.$emit('updated', value); |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	methods: { |  | ||||||
| 		detachMedia(id) { |  | ||||||
| 			if (this.detachMediaFn) { |  | ||||||
| 				this.detachMediaFn(id); |  | ||||||
| 			} else { |  | ||||||
| 				this.$emit('detach', id); |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 		toggleSensitive(file) { |  | ||||||
| 			os.api('drive/files/update', { | 			os.api('drive/files/update', { | ||||||
| 				fileId: file.id, | 				fileId: file.id, | ||||||
| 				isSensitive: !file.isSensitive, | 				comment: comment, | ||||||
| 			}).then(() => { | 			}).then(() => { | ||||||
| 				this.$emit('changeSensitive', file, !file.isSensitive); | 				file.comment = comment; | ||||||
| 			}); |  | ||||||
| 		}, |  | ||||||
| 		async rename(file) { |  | ||||||
| 			const { canceled, result } = await os.inputText({ |  | ||||||
| 				title: this.$ts.enterFileName, |  | ||||||
| 				default: file.name, |  | ||||||
| 				allowEmpty: false, |  | ||||||
| 			}); |  | ||||||
| 			if (canceled) return; |  | ||||||
| 			os.api('drive/files/update', { |  | ||||||
| 				fileId: file.id, |  | ||||||
| 				name: result, |  | ||||||
| 			}).then(() => { |  | ||||||
| 				this.$emit('changeName', file, result); |  | ||||||
| 				file.name = result; |  | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
|  | 	}, 'closed'); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 		async describe(file) { | function showFileMenu(file, ev: MouseEvent) { | ||||||
| 			os.popup(defineAsyncComponent(() => import('@/components/MkMediaCaption.vue')), { | 	if (menuShowing) return; | ||||||
| 				title: this.$ts.describeFile, | 	os.popupMenu([{ | ||||||
| 				input: { | 		text: i18n.ts.renameFile, | ||||||
| 					placeholder: this.$ts.inputNewDescription, | 		icon: 'ti ti-forms', | ||||||
| 					default: file.comment !== null ? file.comment : '', | 		action: () => { rename(file); }, | ||||||
| 				}, | 	}, { | ||||||
| 				image: file, | 		text: file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive, | ||||||
| 			}, { | 		icon: file.isSensitive ? 'ti ti-eye-off' : 'ti ti-eye', | ||||||
| 				done: result => { | 		action: () => { toggleSensitive(file); }, | ||||||
| 					if (!result || result.canceled) return; | 	}, { | ||||||
| 					let comment = result.result.length === 0 ? null : result.result; | 		text: i18n.ts.describeFile, | ||||||
| 					os.api('drive/files/update', { | 		icon: 'ti ti-forms', | ||||||
| 						fileId: file.id, | 		action: () => { describe(file); }, | ||||||
| 						comment: comment, | 	}, { | ||||||
| 					}).then(() => { | 		text: i18n.ts.attachCancel, | ||||||
| 						file.comment = comment; | 		icon: 'ti ti-circle-x', | ||||||
| 					}); | 		action: () => { detachMedia(file.id); }, | ||||||
| 				}, | 	}], ev.currentTarget ?? ev.target).then(() => menuShowing = false); | ||||||
| 			}, 'closed'); | 	menuShowing = true; | ||||||
| 		}, | } | ||||||
| 
 |  | ||||||
| 		showFileMenu(file, ev: MouseEvent) { |  | ||||||
| 			if (this.menu) return; |  | ||||||
| 			this.menu = os.popupMenu([{ |  | ||||||
| 				text: this.$ts.renameFile, |  | ||||||
| 				icon: 'ti ti-forms', |  | ||||||
| 				action: () => { this.rename(file); }, |  | ||||||
| 			}, { |  | ||||||
| 				text: file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive, |  | ||||||
| 				icon: file.isSensitive ? 'ti ti-eye-off' : 'ti ti-eye', |  | ||||||
| 				action: () => { this.toggleSensitive(file); }, |  | ||||||
| 			}, { |  | ||||||
| 				text: this.$ts.describeFile, |  | ||||||
| 				icon: 'ti ti-forms', |  | ||||||
| 				action: () => { this.describe(file); }, |  | ||||||
| 			}, { |  | ||||||
| 				text: this.$ts.attachCancel, |  | ||||||
| 				icon: 'ti ti-circle-x', |  | ||||||
| 				action: () => { this.detachMedia(file.id); }, |  | ||||||
| 			}], ev.currentTarget ?? ev.target).then(() => this.menu = null); |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| }); |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
|  |  | ||||||
|  | @ -9,11 +9,11 @@ | ||||||
| 			<MkButton inline primary class="mk-widget-add" @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> | 			<MkButton inline primary class="mk-widget-add" @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> | ||||||
| 			<MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton> | 			<MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton> | ||||||
| 		</header> | 		</header> | ||||||
| 		<XDraggable | 		<Sortable | ||||||
| 			v-model="widgets_" | 			:list="props.widgets" | ||||||
| 			item-key="id" | 			item-key="id" | ||||||
| 			handle=".handle" | 			:options="{ handle: '.handle', animation: 150 }" | ||||||
| 			animation="150" | 			@end="onSorted" | ||||||
| 		> | 		> | ||||||
| 			<template #item="{element}"> | 			<template #item="{element}"> | ||||||
| 				<div class="customize-container"> | 				<div class="customize-container"> | ||||||
|  | @ -24,7 +24,7 @@ | ||||||
| 					</div> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
| 			</template> | 			</template> | ||||||
| 		</XDraggable> | 		</Sortable> | ||||||
| 	</template> | 	</template> | ||||||
| 	<component :is="`mkw-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" class="widget" :widget="widget" @updateProps="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/> | 	<component :is="`mkw-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" class="widget" :widget="widget" @updateProps="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/> | ||||||
| </div> | </div> | ||||||
|  | @ -38,8 +38,9 @@ import MkButton from '@/components/MkButton.vue'; | ||||||
| import { widgets as widgetDefs } from '@/widgets'; | import { widgets as widgetDefs } from '@/widgets'; | ||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
| import { i18n } from '@/i18n'; | import { i18n } from '@/i18n'; | ||||||
|  | import { deepClone } from '@/scripts/clone'; | ||||||
| 
 | 
 | ||||||
| const XDraggable = defineAsyncComponent(() => import('vuedraggable')); | const Sortable = defineAsyncComponent(() => import('sortablejs-vue3').then(x => x.Sortable)); | ||||||
| 
 | 
 | ||||||
| type Widget = { | type Widget = { | ||||||
| 	name: string; | 	name: string; | ||||||
|  | @ -82,12 +83,13 @@ const removeWidget = (widget) => { | ||||||
| const updateWidget = (id, data) => { | const updateWidget = (id, data) => { | ||||||
| 	emit('updateWidget', { id, data }); | 	emit('updateWidget', { id, data }); | ||||||
| }; | }; | ||||||
| const widgets_ = computed({ | 
 | ||||||
| 	get: () => props.widgets, | function onSorted(event) { | ||||||
| 	set: (value) => { | 	const items = deepClone(props.widgets); | ||||||
| 		emit('updateWidgets', value); | 	const item = items.splice(event.oldIndex, 1)[0]; | ||||||
| 	}, | 	items.splice(event.newIndex, 0, item); | ||||||
| }); | 	emit('updateWidgets', items); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| function onContextmenu(widget: Widget, ev: MouseEvent) { | function onContextmenu(widget: Widget, ev: MouseEvent) { | ||||||
| 	const isLink = (el: HTMLElement) => { | 	const isLink = (el: HTMLElement) => { | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| <template> | <template> | ||||||
| <XDraggable v-model="blocks" tag="div" item-key="id" handle=".drag-handle" :group="{ name: 'blocks' }" animation="150" swap-threshold="0.5"> | <Sortable :list="blocks" tag="div" item-key="id" :options="{ handle: '.drag-handle', group: { name: 'blocks' }, animation: 150, swapThreshold: 0.5 }"> | ||||||
| 	<template #item="{element}"> | 	<template #item="{element}"> | ||||||
| 		<component :is="'x-' + element.type" :value="element" :hpml="hpml" @update:value="updateItem" @remove="() => removeItem(element)"/> | 		<component :is="'x-' + element.type" :value="element" :hpml="hpml" @update:value="updateItem" @remove="() => removeItem(element)"/> | ||||||
| 	</template> | 	</template> | ||||||
| </XDraggable> | </Sortable> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|  | @ -27,14 +27,14 @@ import * as os from '@/os'; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| 	components: { | 	components: { | ||||||
| 		XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), | 		Sortable: defineAsyncComponent(() => import('sortablejs-vue3').then(x => x.Sortable)), | ||||||
| 		XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost, XCounter, XRadioButton, XCanvas, XNote | 		XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost, XCounter, XRadioButton, XCanvas, XNote, | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	props: { | 	props: { | ||||||
| 		modelValue: { | 		modelValue: { | ||||||
| 			type: Array, | 			type: Array, | ||||||
| 			required: true | 			required: true, | ||||||
| 		}, | 		}, | ||||||
| 		hpml: { | 		hpml: { | ||||||
| 			required: true, | 			required: true, | ||||||
|  | @ -50,8 +50,8 @@ export default defineComponent({ | ||||||
| 			}, | 			}, | ||||||
| 			set(value) { | 			set(value) { | ||||||
| 				this.$emit('update:modelValue', value); | 				this.$emit('update:modelValue', value); | ||||||
| 			} | 			}, | ||||||
| 		} | 		}, | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	methods: { | 	methods: { | ||||||
|  | @ -60,7 +60,7 @@ export default defineComponent({ | ||||||
| 			const newValue = [ | 			const newValue = [ | ||||||
| 				...this.blocks.slice(0, i), | 				...this.blocks.slice(0, i), | ||||||
| 				v, | 				v, | ||||||
| 				...this.blocks.slice(i + 1) | 				...this.blocks.slice(i + 1), | ||||||
| 			]; | 			]; | ||||||
| 			this.$emit('update:modelValue', newValue); | 			this.$emit('update:modelValue', newValue); | ||||||
| 		}, | 		}, | ||||||
|  | @ -69,10 +69,10 @@ export default defineComponent({ | ||||||
| 			const i = this.blocks.findIndex(x => x.id === el.id); | 			const i = this.blocks.findIndex(x => x.id === el.id); | ||||||
| 			const newValue = [ | 			const newValue = [ | ||||||
| 				...this.blocks.slice(0, i), | 				...this.blocks.slice(0, i), | ||||||
| 				...this.blocks.slice(i + 1) | 				...this.blocks.slice(i + 1), | ||||||
| 			]; | 			]; | ||||||
| 			this.$emit('update:modelValue', newValue); | 			this.$emit('update:modelValue', newValue); | ||||||
| 		}, | 		}, | ||||||
| 	} | 	}, | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -54,7 +54,7 @@ | ||||||
| 
 | 
 | ||||||
| 		<div v-else-if="tab === 'variables'"> | 		<div v-else-if="tab === 'variables'"> | ||||||
| 			<div class="qmuvgica"> | 			<div class="qmuvgica"> | ||||||
| 				<XDraggable v-show="variables.length > 0" v-model="variables" tag="div" class="variables" item-key="name" handle=".drag-handle" :group="{ name: 'variables' }" animation="150" swap-threshold="0.5"> | 				<Sortable v-show="variables.length > 0" v-model="variables" tag="div" class="variables" item-key="name" handle=".drag-handle" :group="{ name: 'variables' }" animation="150" swap-threshold="0.5"> | ||||||
| 					<template #item="{element}"> | 					<template #item="{element}"> | ||||||
| 						<XVariable | 						<XVariable | ||||||
| 							:model-value="element" | 							:model-value="element" | ||||||
|  | @ -66,7 +66,7 @@ | ||||||
| 							@remove="() => removeVariable(element)" | 							@remove="() => removeVariable(element)" | ||||||
| 						/> | 						/> | ||||||
| 					</template> | 					</template> | ||||||
| 				</XDraggable> | 				</Sortable> | ||||||
| 
 | 
 | ||||||
| 				<MkButton v-if="!readonly" class="add" @click="addVariable()"><i class="ti ti-plus"></i></MkButton> | 				<MkButton v-if="!readonly" class="add" @click="addVariable()"><i class="ti ti-plus"></i></MkButton> | ||||||
| 			</div> | 			</div> | ||||||
|  | @ -107,7 +107,7 @@ import { mainRouter } from '@/router'; | ||||||
| import { i18n } from '@/i18n'; | import { i18n } from '@/i18n'; | ||||||
| import { definePageMetadata } from '@/scripts/page-metadata'; | import { definePageMetadata } from '@/scripts/page-metadata'; | ||||||
| import { $i } from '@/account'; | import { $i } from '@/account'; | ||||||
| const XDraggable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); | const Sortable = defineAsyncComponent(() => import('sortablejs-vue3').then(x => x.default)); | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	initPageId?: string; | 	initPageId?: string; | ||||||
|  | @ -186,24 +186,24 @@ function save() { | ||||||
| 	if (pageId) { | 	if (pageId) { | ||||||
| 		options.pageId = pageId; | 		options.pageId = pageId; | ||||||
| 		os.api('pages/update', options) | 		os.api('pages/update', options) | ||||||
| 		.then(page => { | 			.then(page => { | ||||||
| 			currentName = name.trim(); | 				currentName = name.trim(); | ||||||
| 			os.alert({ | 				os.alert({ | ||||||
| 				type: 'success', | 					type: 'success', | ||||||
| 				text: i18n.ts._pages.updated, | 					text: i18n.ts._pages.updated, | ||||||
| 			}); | 				}); | ||||||
| 		}).catch(onError); | 			}).catch(onError); | ||||||
| 	} else { | 	} else { | ||||||
| 		os.api('pages/create', options) | 		os.api('pages/create', options) | ||||||
| 		.then(created => { | 			.then(created => { | ||||||
| 			pageId = created.id; | 				pageId = created.id; | ||||||
| 			currentName = name.trim(); | 				currentName = name.trim(); | ||||||
| 			os.alert({ | 				os.alert({ | ||||||
| 				type: 'success', | 					type: 'success', | ||||||
| 				text: i18n.ts._pages.created, | 					text: i18n.ts._pages.created, | ||||||
| 			}); | 				}); | ||||||
| 			mainRouter.push(`/pages/edit/${pageId}`); | 				mainRouter.push(`/pages/edit/${pageId}`); | ||||||
| 		}).catch(onError); | 			}).catch(onError); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -438,7 +438,7 @@ definePageMetadata(computed(() => { | ||||||
| 	return { | 	return { | ||||||
| 		title: title, | 		title: title, | ||||||
| 		icon: 'ti ti-pencil', | 		icon: 'ti ti-pencil', | ||||||
| 		}; | 	}; | ||||||
| })); | })); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| 	<FromSlot class="_formBlock"> | 	<FromSlot class="_formBlock"> | ||||||
| 		<template #label>{{ i18n.ts.reactionSettingDescription }}</template> | 		<template #label>{{ i18n.ts.reactionSettingDescription }}</template> | ||||||
| 		<div v-panel style="border-radius: 6px;"> | 		<div v-panel style="border-radius: 6px;"> | ||||||
| 			<XDraggable v-model="reactions" class="zoaiodol" :item-key="item => item" animation="150" delay="100" delay-on-touch-only="true"> | 			<Sortable :list="reactions" class="zoaiodol" :item-key="item => item" :options="{ animation: 150, delay: 100, delayOnTouchOnly: true }" @end="onSorted"> | ||||||
| 				<template #item="{element}"> | 				<template #item="{element}"> | ||||||
| 					<button class="_button item" @click="remove(element, $event)"> | 					<button class="_button item" @click="remove(element, $event)"> | ||||||
| 						<MkEmoji :emoji="element" :normal="true"/> | 						<MkEmoji :emoji="element" :normal="true"/> | ||||||
|  | @ -12,7 +12,9 @@ | ||||||
| 				<template #footer> | 				<template #footer> | ||||||
| 					<button class="_button add" @click="chooseEmoji"><i class="ti ti-plus"></i></button> | 					<button class="_button add" @click="chooseEmoji"><i class="ti ti-plus"></i></button> | ||||||
| 				</template> | 				</template> | ||||||
| 			</XDraggable> | 			</Sortable> | ||||||
|  | 			<!-- TODO: https://github.com/MaxLeiter/sortablejs-vue3/issues/52 が実装されたら消す --> | ||||||
|  | 			<button class="_button add" @click="chooseEmoji"><i class="ti ti-plus"></i></button> | ||||||
| 		</div> | 		</div> | ||||||
| 		<template #caption>{{ i18n.ts.reactionSettingDescription2 }} <button class="_textButton" @click="preview">{{ i18n.ts.preview }}</button></template> | 		<template #caption>{{ i18n.ts.reactionSettingDescription2 }} <button class="_textButton" @click="preview">{{ i18n.ts.preview }}</button></template> | ||||||
| 	</FromSlot> | 	</FromSlot> | ||||||
|  | @ -55,7 +57,7 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { defineAsyncComponent, watch } from 'vue'; | import { defineAsyncComponent, watch } from 'vue'; | ||||||
| import XDraggable from 'vuedraggable'; | import { Sortable } from 'sortablejs-vue3'; | ||||||
| import FormInput from '@/components/form/input.vue'; | import FormInput from '@/components/form/input.vue'; | ||||||
| import FormRadios from '@/components/form/radios.vue'; | import FormRadios from '@/components/form/radios.vue'; | ||||||
| import FromSlot from '@/components/form/slot.vue'; | import FromSlot from '@/components/form/slot.vue'; | ||||||
|  | @ -75,6 +77,11 @@ const reactionPickerWidth = $computed(defaultStore.makeGetterSetter('reactionPic | ||||||
| const reactionPickerHeight = $computed(defaultStore.makeGetterSetter('reactionPickerHeight')); | const reactionPickerHeight = $computed(defaultStore.makeGetterSetter('reactionPickerHeight')); | ||||||
| const reactionPickerUseDrawerForMobile = $computed(defaultStore.makeGetterSetter('reactionPickerUseDrawerForMobile')); | const reactionPickerUseDrawerForMobile = $computed(defaultStore.makeGetterSetter('reactionPickerUseDrawerForMobile')); | ||||||
| 
 | 
 | ||||||
|  | function onSorted(event) { | ||||||
|  | 	const item = reactions.splice(event.oldIndex, 1)[0]; | ||||||
|  | 	reactions.splice(event.newIndex, 0, item); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function save() { | function save() { | ||||||
| 	defaultStore.set('reactions', reactions); | 	defaultStore.set('reactions', reactions); | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										37
									
								
								yarn.lock
									
										
									
									
									
								
							
							
						
						
									
										37
									
								
								yarn.lock
									
										
									
									
									
								
							|  | @ -5193,6 +5193,8 @@ __metadata: | ||||||
|     s-age: 1.1.2 |     s-age: 1.1.2 | ||||||
|     sass: 1.57.0 |     sass: 1.57.0 | ||||||
|     seedrandom: 3.0.5 |     seedrandom: 3.0.5 | ||||||
|  |     sortablejs: ^1.15.0 | ||||||
|  |     sortablejs-vue3: ^1.2.3 | ||||||
|     start-server-and-test: 1.15.2 |     start-server-and-test: 1.15.2 | ||||||
|     strict-event-emitter-types: 2.0.0 |     strict-event-emitter-types: 2.0.0 | ||||||
|     stringz: 2.1.0 |     stringz: 2.1.0 | ||||||
|  | @ -5212,7 +5214,6 @@ __metadata: | ||||||
|     vue-eslint-parser: ^9.1.0 |     vue-eslint-parser: ^9.1.0 | ||||||
|     vue-prism-editor: 2.0.0-alpha.2 |     vue-prism-editor: 2.0.0-alpha.2 | ||||||
|     vue-tsc: ^1.0.14 |     vue-tsc: ^1.0.14 | ||||||
|     vuedraggable: 4.0.1 |  | ||||||
|   languageName: unknown |   languageName: unknown | ||||||
|   linkType: soft |   linkType: soft | ||||||
| 
 | 
 | ||||||
|  | @ -15315,10 +15316,23 @@ __metadata: | ||||||
|   languageName: node |   languageName: node | ||||||
|   linkType: hard |   linkType: hard | ||||||
| 
 | 
 | ||||||
| "sortablejs@npm:1.10.2": | "sortablejs-vue3@npm:^1.2.3": | ||||||
|   version: 1.10.2 |   version: 1.2.3 | ||||||
|   resolution: "sortablejs@npm:1.10.2" |   resolution: "sortablejs-vue3@npm:1.2.3" | ||||||
|   checksum: 37f8d47a9702b93c38077c5e0af90174dcf8e95cf96fe61a722033003eb293bdf3832e4a943f281eaedc433e24cd7d5a48a408706a71a21e75bc11ced0b358da |   dependencies: | ||||||
|  |     sortablejs: ^1.15.0 | ||||||
|  |     vue: ^3.2.37 | ||||||
|  |   peerDependencies: | ||||||
|  |     sortablejs: ^1.15.0 | ||||||
|  |     vue: ^3.2.25 | ||||||
|  |   checksum: 1cf069db4e950a9b447d98d6f4d233082f84b43ac88735e3aab07f2dcebee99026425ee5fc69b04893fe2bb9040fa8cda088a57205f7c46453043d32764b5b36 | ||||||
|  |   languageName: node | ||||||
|  |   linkType: hard | ||||||
|  | 
 | ||||||
|  | "sortablejs@npm:^1.15.0": | ||||||
|  |   version: 1.15.0 | ||||||
|  |   resolution: "sortablejs@npm:1.15.0" | ||||||
|  |   checksum: bb82223a663484640d317cad510ac987f26b7a443631040407224de1be069afcc6c39048b6d8527f10f269e33595e8128d7de2fac23517c8260470f77f932d55 | ||||||
|   languageName: node |   languageName: node | ||||||
|   linkType: hard |   linkType: hard | ||||||
| 
 | 
 | ||||||
|  | @ -17163,7 +17177,7 @@ __metadata: | ||||||
|   languageName: node |   languageName: node | ||||||
|   linkType: hard |   linkType: hard | ||||||
| 
 | 
 | ||||||
| "vue@npm:3.2.45": | "vue@npm:3.2.45, vue@npm:^3.2.37": | ||||||
|   version: 3.2.45 |   version: 3.2.45 | ||||||
|   resolution: "vue@npm:3.2.45" |   resolution: "vue@npm:3.2.45" | ||||||
|   dependencies: |   dependencies: | ||||||
|  | @ -17176,17 +17190,6 @@ __metadata: | ||||||
|   languageName: node |   languageName: node | ||||||
|   linkType: hard |   linkType: hard | ||||||
| 
 | 
 | ||||||
| "vuedraggable@npm:4.0.1": |  | ||||||
|   version: 4.0.1 |  | ||||||
|   resolution: "vuedraggable@npm:4.0.1" |  | ||||||
|   dependencies: |  | ||||||
|     sortablejs: 1.10.2 |  | ||||||
|   peerDependencies: |  | ||||||
|     vue: ^3.0.1 |  | ||||||
|   checksum: 039e5d38560144299be3689270728639d041737b487cbb7dfec09b6c372b48804031785f9b40e6e14da0d213315b98ddc005713cc60a749cf98028e0c16fd866 |  | ||||||
|   languageName: node |  | ||||||
|   linkType: hard |  | ||||||
| 
 |  | ||||||
| "w3c-xmlserializer@npm:^4.0.0": | "w3c-xmlserializer@npm:^4.0.0": | ||||||
|   version: 4.0.0 |   version: 4.0.0 | ||||||
|   resolution: "w3c-xmlserializer@npm:4.0.0" |   resolution: "w3c-xmlserializer@npm:4.0.0" | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue