デッキまわりをCompositon API / Setup Sugarに (#8410)
* universal.widgets.vue * column.vue, antenna-column.vue * direct-column.vue, list-column.vue * main-column.vue * wip * ✌️ * fix * ✌️ * ✌️
This commit is contained in:
		| @@ -17,7 +17,8 @@ | ||||
| 			:key="ids[0]" | ||||
| 			class="column" | ||||
| 			:column="columns.find(c => c.id === ids[0])" | ||||
| 			:style="columns.find(c => c.id === ids[0]).flexible ? { flex: 1, minWidth: '350px' } : { width: columns.find(c => c.id === ids[0]).width + 'px' }" | ||||
| 			 :is-stacked="false" | ||||
| 			:style="columns.find(c => c.id === ids[0])!.flexible ? { flex: 1, minWidth: '350px' } : { width: columns.find(c => c.id === ids[0])!.width + 'px' }" | ||||
| 			@parent-focus="moveFocus(ids[0], $event)" | ||||
| 		/> | ||||
| 	</template> | ||||
| @@ -25,8 +26,8 @@ | ||||
| 	<div v-if="isMobile" class="buttons"> | ||||
| 		<button class="button nav _button" @click="drawerMenuShowing = true"><i class="fas fa-bars"></i><span v-if="menuIndicated" class="indicator"><i class="fas fa-circle"></i></span></button> | ||||
| 		<button class="button home _button" @click="$router.push('/')"><i class="fas fa-home"></i></button> | ||||
| 		<button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button> | ||||
| 		<button class="button post _button" @click="post()"><i class="fas fa-pencil-alt"></i></button> | ||||
| 		<button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i?.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button> | ||||
| 		<button class="button post _button" @click="os.post()"><i class="fas fa-pencil-alt"></i></button> | ||||
| 	</div> | ||||
|  | ||||
| 	<transition :name="$store.state.animation ? 'menu-back' : ''"> | ||||
| @@ -45,8 +46,8 @@ | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { computed, defineComponent, provide, ref, watch } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { computed, provide, ref, watch } from 'vue'; | ||||
| import { v4 as uuid } from 'uuid'; | ||||
| import DeckColumnCore from '@/ui/deck/column-core.vue'; | ||||
| import XSidebar from '@/ui/_common_/sidebar.vue'; | ||||
| @@ -60,102 +61,82 @@ import { useRoute } from 'vue-router'; | ||||
| import { $i } from '@/account'; | ||||
| import { i18n } from '@/i18n'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XCommon, | ||||
| 		XSidebar, | ||||
| 		XDrawerMenu, | ||||
| 		DeckColumnCore, | ||||
| 	}, | ||||
|  | ||||
| 	setup() { | ||||
| 		const isMobile = ref(window.innerWidth <= 500); | ||||
| 		window.addEventListener('resize', () => { | ||||
| 			isMobile.value = window.innerWidth <= 500; | ||||
| 		}); | ||||
|  | ||||
| 		const drawerMenuShowing = ref(false); | ||||
|  | ||||
| 		const route = useRoute(); | ||||
| 		watch(route, () => { | ||||
| 			drawerMenuShowing.value = false; | ||||
| 		}); | ||||
|  | ||||
| 		const columns = deckStore.reactiveState.columns; | ||||
| 		const layout = deckStore.reactiveState.layout; | ||||
| 		const menuIndicated = computed(() => { | ||||
| 			if ($i == null) return false; | ||||
| 			for (const def in menuDef) { | ||||
| 				if (menuDef[def].indicated) return true; | ||||
| 			} | ||||
| 			return false; | ||||
| 		}); | ||||
|  | ||||
| 		const addColumn = async (ev) => { | ||||
| 			const columns = [ | ||||
| 				'main', | ||||
| 				'widgets', | ||||
| 				'notifications', | ||||
| 				'tl', | ||||
| 				'antenna', | ||||
| 				'list', | ||||
| 				'mentions', | ||||
| 				'direct', | ||||
| 			]; | ||||
|  | ||||
| 			const { canceled, result: column } = await os.select({ | ||||
| 				title: i18n.ts._deck.addColumn, | ||||
| 				items: columns.map(column => ({ | ||||
| 					value: column, text: i18n.t('_deck._columns.' + column) | ||||
| 				})) | ||||
| 			}); | ||||
| 			if (canceled) return; | ||||
|  | ||||
| 			addColumnToStore({ | ||||
| 				type: column, | ||||
| 				id: uuid(), | ||||
| 				name: i18n.t('_deck._columns.' + column), | ||||
| 				width: 330, | ||||
| 			}); | ||||
| 		}; | ||||
|  | ||||
| 		const onContextmenu = (ev) => { | ||||
| 			os.contextMenu([{ | ||||
| 				text: i18n.ts._deck.addColumn, | ||||
| 				icon: null, | ||||
| 				action: addColumn | ||||
| 			}], ev); | ||||
| 		}; | ||||
|  | ||||
| 		provide('shouldSpacerMin', true); | ||||
| 		if (deckStore.state.navWindow) { | ||||
| 			provide('navHook', (url) => { | ||||
| 				os.pageWindow(url); | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		document.documentElement.style.overflowY = 'hidden'; | ||||
| 		document.documentElement.style.scrollBehavior = 'auto'; | ||||
| 		window.addEventListener('wheel', (ev) => { | ||||
| 			if (getScrollContainer(ev.target) == null) { | ||||
| 				document.documentElement.scrollLeft += ev.deltaY > 0 ? 96 : -96; | ||||
| 			} | ||||
| 		}); | ||||
| 		loadDeck(); | ||||
|  | ||||
| 		return { | ||||
| 			isMobile, | ||||
| 			deckStore, | ||||
| 			drawerMenuShowing, | ||||
| 			columns, | ||||
| 			layout, | ||||
| 			menuIndicated, | ||||
| 			onContextmenu, | ||||
| 			wallpaper: localStorage.getItem('wallpaper') != null, | ||||
| 			post: os.post, | ||||
| 		}; | ||||
| 	}, | ||||
| const isMobile = ref(window.innerWidth <= 500); | ||||
| window.addEventListener('resize', () => { | ||||
| 	isMobile.value = window.innerWidth <= 500; | ||||
| }); | ||||
|  | ||||
| const drawerMenuShowing = ref(false); | ||||
|  | ||||
| const route = useRoute(); | ||||
| watch(route, () => { | ||||
| 	drawerMenuShowing.value = false; | ||||
| }); | ||||
|  | ||||
| const columns = deckStore.reactiveState.columns; | ||||
| const layout = deckStore.reactiveState.layout; | ||||
| const menuIndicated = computed(() => { | ||||
| 	if ($i == null) return false; | ||||
| 	for (const def in menuDef) { | ||||
| 		if (menuDef[def].indicated) return true; | ||||
| 	} | ||||
| 	return false; | ||||
| }); | ||||
|  | ||||
| const addColumn = async (ev) => { | ||||
| 	const columns = [ | ||||
| 		'main', | ||||
| 		'widgets', | ||||
| 		'notifications', | ||||
| 		'tl', | ||||
| 		'antenna', | ||||
| 		'list', | ||||
| 		'mentions', | ||||
| 		'direct', | ||||
| 	]; | ||||
|  | ||||
| 	const { canceled, result: column } = await os.select({ | ||||
| 		title: i18n.ts._deck.addColumn, | ||||
| 		items: columns.map(column => ({ | ||||
| 			value: column, text: i18n.t('_deck._columns.' + column) | ||||
| 		})) | ||||
| 	}); | ||||
| 	if (canceled) return; | ||||
|  | ||||
| 	addColumnToStore({ | ||||
| 		type: column, | ||||
| 		id: uuid(), | ||||
| 		name: i18n.t('_deck._columns.' + column), | ||||
| 		width: 330, | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| const onContextmenu = (ev) => { | ||||
| 	os.contextMenu([{ | ||||
| 		text: i18n.ts._deck.addColumn, | ||||
| 		action: addColumn, | ||||
| 	}], ev); | ||||
| }; | ||||
|  | ||||
| provide('shouldSpacerMin', true); | ||||
| if (deckStore.state.navWindow) { | ||||
| 	provide('navHook', (url) => { | ||||
| 		os.pageWindow(url); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| document.documentElement.style.overflowY = 'hidden'; | ||||
| document.documentElement.style.scrollBehavior = 'auto'; | ||||
| window.addEventListener('wheel', (ev) => { | ||||
| 	if (getScrollContainer(ev.target as HTMLElement) == null) { | ||||
| 		document.documentElement.scrollLeft += ev.deltaY > 0 ? 96 : -96; | ||||
| 	} | ||||
| }); | ||||
| loadDeck(); | ||||
|  | ||||
| function moveFocus(id: string, direction: 'up' | 'down' | 'left' | 'right') { | ||||
| 	// TODO?? | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   | ||||
| @@ -1,75 +1,62 @@ | ||||
| <template> | ||||
| <XColumn :func="{ handler: setAntenna, title: $ts.selectAntenna }" :column="column" :is-stacked="isStacked"> | ||||
| <XColumn :func="{ handler: setAntenna, title: $ts.selectAntenna }" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> | ||||
| 	<template #header> | ||||
| 		<i class="fas fa-satellite"></i><span style="margin-left: 8px;">{{ column.name }}</span> | ||||
| 	</template> | ||||
|  | ||||
| 	<XTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @after="() => $emit('loaded')"/> | ||||
| 	<XTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @after="() => emit('loaded')"/> | ||||
| </XColumn> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { onMounted } from 'vue'; | ||||
| import XColumn from './column.vue'; | ||||
| import XTimeline from '@/components/timeline.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { updateColumn } from './deck-store'; | ||||
| import { updateColumn, Column } from './deck-store'; | ||||
| import { i18n } from '@/i18n'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XColumn, | ||||
| 		XTimeline, | ||||
| 	}, | ||||
| const props = defineProps<{ | ||||
| 	column: Column; | ||||
| 	isStacked: boolean; | ||||
| }>(); | ||||
|  | ||||
| 	props: { | ||||
| 		column: { | ||||
| 			type: Object, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		isStacked: { | ||||
| 			type: Boolean, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| const emit = defineEmits<{ | ||||
| 	(e: 'loaded'): void; | ||||
| 	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; | ||||
| }>(); | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 		}; | ||||
| 	}, | ||||
| let timeline = $ref<InstanceType<typeof XTimeline>>(); | ||||
|  | ||||
| 	watch: { | ||||
| 		mediaOnly() { | ||||
| 			(this.$refs.timeline as any).reload(); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		if (this.column.antennaId == null) { | ||||
| 			this.setAntenna(); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		async setAntenna() { | ||||
| 			const antennas = await os.api('antennas/list'); | ||||
| 			const { canceled, result: antenna } = await os.select({ | ||||
| 				title: this.$ts.selectAntenna, | ||||
| 				items: antennas.map(x => ({ | ||||
| 					value: x, text: x.name | ||||
| 				})), | ||||
| 				default: this.column.antennaId | ||||
| 			}); | ||||
| 			if (canceled) return; | ||||
| 			updateColumn(this.column.id, { | ||||
| 				antennaId: antenna.id | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		focus() { | ||||
| 			(this.$refs.timeline as any).focus(); | ||||
| 		} | ||||
| onMounted(() => { | ||||
| 	if (props.column.antennaId == null) { | ||||
| 		setAntenna(); | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| async function setAntenna() { | ||||
| 	const antennas = await os.api('antennas/list'); | ||||
| 	const { canceled, result: antenna } = await os.select({ | ||||
| 		title: i18n.ts.selectAntenna, | ||||
| 		items: antennas.map(x => ({ | ||||
| 			value: x, text: x.name | ||||
| 		})), | ||||
| 		default: props.column.antennaId | ||||
| 	}); | ||||
| 	if (canceled) return; | ||||
| 	updateColumn(props.column.id, { | ||||
| 		antennaId: antenna.id | ||||
| 	}); | ||||
| } | ||||
| /* | ||||
| function focus() { | ||||
| 	timeline.focus(); | ||||
| } | ||||
|  | ||||
| defineExpose({ | ||||
| 	focus, | ||||
| }); | ||||
| */ | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   | ||||
| @@ -1,17 +1,18 @@ | ||||
| <template> | ||||
| <!-- TODO: リファクタの余地がありそう --> | ||||
| <XMainColumn v-if="column.type === 'main'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> | ||||
| <XWidgetsColumn v-else-if="column.type === 'widgets'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> | ||||
| <XNotificationsColumn v-else-if="column.type === 'notifications'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> | ||||
| <XTlColumn v-else-if="column.type === 'tl'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> | ||||
| <XListColumn v-else-if="column.type === 'list'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> | ||||
| <XAntennaColumn v-else-if="column.type === 'antenna'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> | ||||
| <XMentionsColumn v-else-if="column.type === 'mentions'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> | ||||
| <XDirectColumn v-else-if="column.type === 'direct'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> | ||||
| <div v-if="!column">たぶん見えちゃいけないやつ</div> | ||||
| <XMainColumn v-else-if="column.type === 'main'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> | ||||
| <XWidgetsColumn v-else-if="column.type === 'widgets'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> | ||||
| <XNotificationsColumn v-else-if="column.type === 'notifications'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> | ||||
| <XTlColumn v-else-if="column.type === 'tl'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> | ||||
| <XListColumn v-else-if="column.type === 'list'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> | ||||
| <XAntennaColumn v-else-if="column.type === 'antenna'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> | ||||
| <XMentionsColumn v-else-if="column.type === 'mentions'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> | ||||
| <XDirectColumn v-else-if="column.type === 'direct'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import XMainColumn from './main-column.vue'; | ||||
| import XTlColumn from './tl-column.vue'; | ||||
| import XAntennaColumn from './antenna-column.vue'; | ||||
| @@ -20,33 +21,24 @@ import XNotificationsColumn from './notifications-column.vue'; | ||||
| import XWidgetsColumn from './widgets-column.vue'; | ||||
| import XMentionsColumn from './mentions-column.vue'; | ||||
| import XDirectColumn from './direct-column.vue'; | ||||
| import { Column } from './deck-store'; | ||||
|  | ||||
| defineProps<{ | ||||
| 	column?: Column; | ||||
| 	isStacked: boolean; | ||||
| }>(); | ||||
|  | ||||
| const emit = defineEmits<{ | ||||
| 	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; | ||||
| }>(); | ||||
|  | ||||
| /* | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XMainColumn, | ||||
| 		XTlColumn, | ||||
| 		XAntennaColumn, | ||||
| 		XListColumn, | ||||
| 		XNotificationsColumn, | ||||
| 		XWidgetsColumn, | ||||
| 		XMentionsColumn, | ||||
| 		XDirectColumn | ||||
| 	}, | ||||
| 	props: { | ||||
| 		column: { | ||||
| 			type: Object, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		isStacked: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		focus() { | ||||
| 			this.$children[0].focus(); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| */ | ||||
| </script> | ||||
|   | ||||
| @@ -31,238 +31,211 @@ | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| export type DeckFunc = { | ||||
| 	title: string; | ||||
| 	handler: (payload: MouseEvent) => void; | ||||
| 	icon?: string; | ||||
| }; | ||||
| </script> | ||||
| <script lang="ts" setup> | ||||
| import { onBeforeUnmount, onMounted, provide, watch } from 'vue'; | ||||
| import * as os from '@/os'; | ||||
| import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn } from './deck-store'; | ||||
| import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column } from './deck-store'; | ||||
| import { deckStore } from './deck-store'; | ||||
| import { i18n } from '@/i18n'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	provide: { | ||||
| 		shouldHeaderThin: true, | ||||
| 		shouldOmitHeaderTitle: true, | ||||
| 	}, | ||||
| provide('shouldHeaderThin', true); | ||||
| provide('shouldOmitHeaderTitle', true); | ||||
|  | ||||
| 	props: { | ||||
| 		column: { | ||||
| 			type: Object, | ||||
| 			required: false, | ||||
| 			default: null | ||||
| 		}, | ||||
| 		isStacked: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		}, | ||||
| 		func: { | ||||
| 			type: Object, | ||||
| 			required: false, | ||||
| 			default: null | ||||
| 		}, | ||||
| 		naked: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		}, | ||||
| 		indicated: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			deckStore, | ||||
| 			dragging: false, | ||||
| 			draghover: false, | ||||
| 			dropready: false, | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		isMainColumn(): boolean { | ||||
| 			return this.column.type === 'main'; | ||||
| 		}, | ||||
|  | ||||
| 		active(): boolean { | ||||
| 			return this.column.active !== false; | ||||
| 		}, | ||||
|  | ||||
| 		keymap(): any { | ||||
| 			return { | ||||
| 				'shift+up': () => this.$parent.$emit('parent-focus', 'up'), | ||||
| 				'shift+down': () => this.$parent.$emit('parent-focus', 'down'), | ||||
| 				'shift+left': () => this.$parent.$emit('parent-focus', 'left'), | ||||
| 				'shift+right': () => this.$parent.$emit('parent-focus', 'right'), | ||||
| 			}; | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	watch: { | ||||
| 		active(v) { | ||||
| 			this.$emit('change-active-state', v); | ||||
| 		}, | ||||
|  | ||||
| 		dragging(v) { | ||||
| 			os.deckGlobalEvents.emit(v ? 'column.dragStart' : 'column.dragEnd'); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		os.deckGlobalEvents.on('column.dragStart', this.onOtherDragStart); | ||||
| 		os.deckGlobalEvents.on('column.dragEnd', this.onOtherDragEnd); | ||||
| 	}, | ||||
|  | ||||
| 	beforeUnmount() { | ||||
| 		os.deckGlobalEvents.off('column.dragStart', this.onOtherDragStart); | ||||
| 		os.deckGlobalEvents.off('column.dragEnd', this.onOtherDragEnd); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		onOtherDragStart() { | ||||
| 			this.dropready = true; | ||||
| 		}, | ||||
|  | ||||
| 		onOtherDragEnd() { | ||||
| 			this.dropready = false; | ||||
| 		}, | ||||
|  | ||||
| 		toggleActive() { | ||||
| 			if (!this.isStacked) return; | ||||
| 			updateColumn(this.column.id, { | ||||
| 				active: !this.column.active | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		getMenu() { | ||||
| 			const items = [{ | ||||
| 				icon: 'fas fa-pencil-alt', | ||||
| 				text: this.$ts.edit, | ||||
| 				action: async () => { | ||||
| 					const { canceled, result } = await os.form(this.column.name, { | ||||
| 						name: { | ||||
| 							type: 'string', | ||||
| 							label: this.$ts.name, | ||||
| 							default: this.column.name | ||||
| 						}, | ||||
| 						width: { | ||||
| 							type: 'number', | ||||
| 							label: this.$ts.width, | ||||
| 							default: this.column.width | ||||
| 						}, | ||||
| 						flexible: { | ||||
| 							type: 'boolean', | ||||
| 							label: this.$ts.flexible, | ||||
| 							default: this.column.flexible | ||||
| 						} | ||||
| 					}); | ||||
| 					if (canceled) return; | ||||
| 					updateColumn(this.column.id, result); | ||||
| 				} | ||||
| 			}, null, { | ||||
| 				icon: 'fas fa-arrow-left', | ||||
| 				text: this.$ts._deck.swapLeft, | ||||
| 				action: () => { | ||||
| 					swapLeftColumn(this.column.id); | ||||
| 				} | ||||
| 			}, { | ||||
| 				icon: 'fas fa-arrow-right', | ||||
| 				text: this.$ts._deck.swapRight, | ||||
| 				action: () => { | ||||
| 					swapRightColumn(this.column.id); | ||||
| 				} | ||||
| 			}, this.isStacked ? { | ||||
| 				icon: 'fas fa-arrow-up', | ||||
| 				text: this.$ts._deck.swapUp, | ||||
| 				action: () => { | ||||
| 					swapUpColumn(this.column.id); | ||||
| 				} | ||||
| 			} : undefined, this.isStacked ? { | ||||
| 				icon: 'fas fa-arrow-down', | ||||
| 				text: this.$ts._deck.swapDown, | ||||
| 				action: () => { | ||||
| 					swapDownColumn(this.column.id); | ||||
| 				} | ||||
| 			} : undefined, null, { | ||||
| 				icon: 'fas fa-window-restore', | ||||
| 				text: this.$ts._deck.stackLeft, | ||||
| 				action: () => { | ||||
| 					stackLeftColumn(this.column.id); | ||||
| 				} | ||||
| 			}, this.isStacked ? { | ||||
| 				icon: 'fas fa-window-maximize', | ||||
| 				text: this.$ts._deck.popRight, | ||||
| 				action: () => { | ||||
| 					popRightColumn(this.column.id); | ||||
| 				} | ||||
| 			} : undefined, null, { | ||||
| 				icon: 'fas fa-trash-alt', | ||||
| 				text: this.$ts.remove, | ||||
| 				danger: true, | ||||
| 				action: () => { | ||||
| 					removeColumn(this.column.id); | ||||
| 				} | ||||
| 			}]; | ||||
|  | ||||
| 			return items; | ||||
| 		}, | ||||
|  | ||||
| 		onContextmenu(ev: MouseEvent) { | ||||
| 			os.contextMenu(this.getMenu(), ev); | ||||
| 		}, | ||||
|  | ||||
| 		goTop() { | ||||
| 			this.$refs.body.scrollTo({ | ||||
| 				top: 0, | ||||
| 				behavior: 'smooth' | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		onDragstart(e) { | ||||
| 			e.dataTransfer.effectAllowed = 'move'; | ||||
| 			e.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, this.column.id); | ||||
|  | ||||
| 			// Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう | ||||
| 			// SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately | ||||
| 			window.setTimeout(() => { | ||||
| 				this.dragging = true; | ||||
| 			}, 10); | ||||
| 		}, | ||||
|  | ||||
| 		onDragend(e) { | ||||
| 			this.dragging = false; | ||||
| 		}, | ||||
|  | ||||
| 		onDragover(e) { | ||||
| 			// 自分自身がドラッグされている場合 | ||||
| 			if (this.dragging) { | ||||
| 				// 自分自身にはドロップさせない | ||||
| 				e.dataTransfer.dropEffect = 'none'; | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			const isDeckColumn = e.dataTransfer.types[0] == _DATA_TRANSFER_DECK_COLUMN_; | ||||
|  | ||||
| 			e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; | ||||
|  | ||||
| 			if (!this.dragging && isDeckColumn) this.draghover = true; | ||||
| 		}, | ||||
|  | ||||
| 		onDragleave() { | ||||
| 			this.draghover = false; | ||||
| 		}, | ||||
|  | ||||
| 		onDrop(e) { | ||||
| 			this.draghover = false; | ||||
| 			os.deckGlobalEvents.emit('column.dragEnd'); | ||||
|  | ||||
| 			const id = e.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_); | ||||
| 			if (id != null && id != '') { | ||||
| 				swapColumn(this.column.id, id); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| const props = withDefaults(defineProps<{ | ||||
| 	column: Column; | ||||
| 	isStacked?: boolean; | ||||
| 	func?: DeckFunc | null; | ||||
| 	naked?: boolean; | ||||
| 	indicated?: boolean; | ||||
| }>(), { | ||||
| 	isStacked: false, | ||||
| 	func: null, | ||||
| 	naked: false, | ||||
| 	indicated: false, | ||||
| }); | ||||
|  | ||||
| const emit = defineEmits<{ | ||||
| 	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; | ||||
| 	(e: 'change-active-state', v: boolean): void; | ||||
| }>(); | ||||
|  | ||||
| let body = $ref<HTMLDivElement>(); | ||||
|  | ||||
| let dragging = $ref(false); | ||||
| watch($$(dragging), v => os.deckGlobalEvents.emit(v ? 'column.dragStart' : 'column.dragEnd')); | ||||
|  | ||||
| let draghover = $ref(false); | ||||
| let dropready = $ref(false); | ||||
|  | ||||
| const isMainColumn = $computed(() => props.column.type === 'main'); | ||||
| const active = $computed(() => props.column.active !== false); | ||||
| watch($$(active), v => emit('change-active-state', v)); | ||||
|  | ||||
| const keymap = $computed(() => ({ | ||||
| 	'shift+up': () => emit('parent-focus', 'up'), | ||||
| 	'shift+down': () => emit('parent-focus', 'down'), | ||||
| 	'shift+left': () => emit('parent-focus', 'left'), | ||||
| 	'shift+right': () => emit('parent-focus', 'right'), | ||||
| })); | ||||
|  | ||||
| onMounted(() => { | ||||
| 	os.deckGlobalEvents.on('column.dragStart', onOtherDragStart); | ||||
| 	os.deckGlobalEvents.on('column.dragEnd', onOtherDragEnd); | ||||
| }); | ||||
|  | ||||
| onBeforeUnmount(() => { | ||||
| 	os.deckGlobalEvents.off('column.dragStart', onOtherDragStart); | ||||
| 	os.deckGlobalEvents.off('column.dragEnd', onOtherDragEnd); | ||||
| }); | ||||
|  | ||||
|  | ||||
| function onOtherDragStart() { | ||||
| 	dropready = true; | ||||
| } | ||||
|  | ||||
| function onOtherDragEnd() { | ||||
| 	dropready = false; | ||||
| } | ||||
|  | ||||
| function toggleActive() { | ||||
| 	if (!props.isStacked) return; | ||||
| 	updateColumn(props.column.id, { | ||||
| 		active: !props.column.active | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| function getMenu() { | ||||
| 	const items = [{ | ||||
| 		icon: 'fas fa-pencil-alt', | ||||
| 		text: i18n.ts.edit, | ||||
| 		action: async () => { | ||||
| 			const { canceled, result } = await os.form(props.column.name, { | ||||
| 				name: { | ||||
| 					type: 'string', | ||||
| 					label: i18n.ts.name, | ||||
| 					default: props.column.name | ||||
| 				}, | ||||
| 				width: { | ||||
| 					type: 'number', | ||||
| 					label: i18n.ts.width, | ||||
| 					default: props.column.width | ||||
| 				}, | ||||
| 				flexible: { | ||||
| 					type: 'boolean', | ||||
| 					label: i18n.ts.flexible, | ||||
| 					default: props.column.flexible | ||||
| 				} | ||||
| 			}); | ||||
| 			if (canceled) return; | ||||
| 			updateColumn(props.column.id, result); | ||||
| 		} | ||||
| 	}, null, { | ||||
| 		icon: 'fas fa-arrow-left', | ||||
| 		text: i18n.ts._deck.swapLeft, | ||||
| 		action: () => { | ||||
| 			swapLeftColumn(props.column.id); | ||||
| 		} | ||||
| 	}, { | ||||
| 		icon: 'fas fa-arrow-right', | ||||
| 		text: i18n.ts._deck.swapRight, | ||||
| 		action: () => { | ||||
| 			swapRightColumn(props.column.id); | ||||
| 		} | ||||
| 	}, props.isStacked ? { | ||||
| 		icon: 'fas fa-arrow-up', | ||||
| 		text: i18n.ts._deck.swapUp, | ||||
| 		action: () => { | ||||
| 			swapUpColumn(props.column.id); | ||||
| 		} | ||||
| 	} : undefined, props.isStacked ? { | ||||
| 		icon: 'fas fa-arrow-down', | ||||
| 		text: i18n.ts._deck.swapDown, | ||||
| 		action: () => { | ||||
| 			swapDownColumn(props.column.id); | ||||
| 		} | ||||
| 	} : undefined, null, { | ||||
| 		icon: 'fas fa-window-restore', | ||||
| 		text: i18n.ts._deck.stackLeft, | ||||
| 		action: () => { | ||||
| 			stackLeftColumn(props.column.id); | ||||
| 		} | ||||
| 	}, props.isStacked ? { | ||||
| 		icon: 'fas fa-window-maximize', | ||||
| 		text: i18n.ts._deck.popRight, | ||||
| 		action: () => { | ||||
| 			popRightColumn(props.column.id); | ||||
| 		} | ||||
| 	} : undefined, null, { | ||||
| 		icon: 'fas fa-trash-alt', | ||||
| 		text: i18n.ts.remove, | ||||
| 		danger: true, | ||||
| 		action: () => { | ||||
| 			removeColumn(props.column.id); | ||||
| 		} | ||||
| 	}]; | ||||
| 	return items; | ||||
| } | ||||
|  | ||||
| function onContextmenu(ev: MouseEvent) { | ||||
| 	os.contextMenu(getMenu(), ev); | ||||
| } | ||||
|  | ||||
| function goTop() { | ||||
| 	body.scrollTo({ | ||||
| 		top: 0, | ||||
| 		behavior: 'smooth' | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| function onDragstart(e) { | ||||
| 	e.dataTransfer.effectAllowed = 'move'; | ||||
| 	e.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, props.column.id); | ||||
|  | ||||
| 	// Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう | ||||
| 	// SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately | ||||
| 	window.setTimeout(() => { | ||||
| 		dragging = true; | ||||
| 	}, 10); | ||||
| } | ||||
|  | ||||
| function onDragend(e) { | ||||
| 	dragging = false; | ||||
| } | ||||
|  | ||||
| function onDragover(e) { | ||||
| 	// 自分自身がドラッグされている場合 | ||||
| 	if (dragging) { | ||||
| 		// 自分自身にはドロップさせない | ||||
| 		e.dataTransfer.dropEffect = 'none'; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	const isDeckColumn = e.dataTransfer.types[0] == _DATA_TRANSFER_DECK_COLUMN_; | ||||
|  | ||||
| 	e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; | ||||
|  | ||||
| 	if (!dragging && isDeckColumn) draghover = true; | ||||
| } | ||||
|  | ||||
| function onDragleave() { | ||||
| 	draghover = false; | ||||
| } | ||||
|  | ||||
| function onDrop(e) { | ||||
| 	draghover = false; | ||||
| 	os.deckGlobalEvents.emit('column.dragEnd'); | ||||
|  | ||||
| 	const id = e.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_); | ||||
| 	if (id != null && id != '') { | ||||
| 		swapColumn(props.column.id, id); | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   | ||||
| @@ -1,8 +1,9 @@ | ||||
| import { throttle } from 'throttle-debounce'; | ||||
| import { i18n } from '@/i18n'; | ||||
| import { api } from '@/os'; | ||||
| import { markRaw, watch } from 'vue'; | ||||
| import { markRaw } from 'vue'; | ||||
| import { Storage } from '../../pizzax'; | ||||
| import { notificationTypes } from 'misskey-js'; | ||||
|  | ||||
| type ColumnWidget = { | ||||
| 	name: string; | ||||
| @@ -10,13 +11,18 @@ type ColumnWidget = { | ||||
| 	data: Record<string, any>; | ||||
| }; | ||||
|  | ||||
| type Column = { | ||||
| export type Column = { | ||||
| 	id: string; | ||||
| 	type: string; | ||||
| 	name: string | null; | ||||
| 	width: number; | ||||
| 	widgets?: ColumnWidget[]; | ||||
| 	active?: boolean; | ||||
| 	flexible?: boolean; | ||||
| 	antennaId?: string; | ||||
| 	listId?: string; | ||||
| 	includingTypes?: typeof notificationTypes[number][]; | ||||
| 	tl?: 'home' | 'local' | 'social' | 'global'; | ||||
| }; | ||||
|  | ||||
| function copy<T>(x: T): T { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <XColumn :column="column" :is-stacked="isStacked"> | ||||
| <XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> | ||||
| 	<template #header><i class="fas fa-envelope" style="margin-right: 8px;"></i>{{ column.name }}</template> | ||||
|  | ||||
| 	<XNotes :pagination="pagination"/> | ||||
| @@ -7,21 +7,25 @@ | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { computed } from 'vue'; | ||||
| import { } from 'vue'; | ||||
| import XColumn from './column.vue'; | ||||
| import XNotes from '@/components/notes.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { Column } from './deck-store'; | ||||
|  | ||||
| const props = defineProps<{ | ||||
| 	column: Record<string, unknown>; // TODO | ||||
| defineProps<{ | ||||
| 	column: Column; | ||||
| 	isStacked: boolean; | ||||
| }>(); | ||||
|  | ||||
| const emit = defineEmits<{ | ||||
| 	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; | ||||
| }>(); | ||||
|  | ||||
| const pagination = { | ||||
| 	point: 'notes/mentions' as const, | ||||
| 	endpoint: 'notes/mentions' as const, | ||||
| 	limit: 10, | ||||
| 	params: computed(() => ({ | ||||
| 		visibility: 'specified' as const, | ||||
| 	})), | ||||
| 	params: { | ||||
| 		visibility: 'specified' | ||||
| 	}, | ||||
| }; | ||||
| </script> | ||||
|   | ||||
| @@ -1,75 +1,65 @@ | ||||
| <template> | ||||
| <XColumn :func="{ handler: setList, title: $ts.selectList }" :column="column" :is-stacked="isStacked"> | ||||
| <XColumn :func="{ handler: setList, title: $ts.selectList }" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> | ||||
| 	<template #header> | ||||
| 		<i class="fas fa-list-ul"></i><span style="margin-left: 8px;">{{ column.name }}</span> | ||||
| 	</template> | ||||
|  | ||||
| 	<XTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" @after="() => $emit('loaded')"/> | ||||
| 	<XTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" @after="() => emit('loaded')"/> | ||||
| </XColumn> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import {  } from 'vue'; | ||||
| import XColumn from './column.vue'; | ||||
| import XTimeline from '@/components/timeline.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { updateColumn } from './deck-store'; | ||||
| import { updateColumn, Column } from './deck-store'; | ||||
| import { i18n } from '@/i18n'; | ||||
|  | ||||
| const props = defineProps<{ | ||||
| 	column: Column; | ||||
| 	isStacked: boolean; | ||||
| }>(); | ||||
|  | ||||
| const emit = defineEmits<{ | ||||
| 	(e: 'loaded'): void; | ||||
| 	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; | ||||
| }>(); | ||||
|  | ||||
| let timeline = $ref<InstanceType<typeof XTimeline>>(); | ||||
|  | ||||
| if (props.column.listId == null) { | ||||
| 	setList(); | ||||
| } | ||||
|  | ||||
| async function setList() { | ||||
| 	const lists = await os.api('users/lists/list'); | ||||
| 	const { canceled, result: list } = await os.select({ | ||||
| 		title: i18n.ts.selectList, | ||||
| 		items: lists.map(x => ({ | ||||
| 			value: x, text: x.name | ||||
| 		})), | ||||
| 		default: props.column.listId | ||||
| 	}); | ||||
| 	if (canceled) return; | ||||
| 	updateColumn(props.column.id, { | ||||
| 		listId: list.id | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| /* | ||||
| function focus() { | ||||
| 	timeline.focus(); | ||||
| } | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XColumn, | ||||
| 		XTimeline, | ||||
| 	}, | ||||
|  | ||||
| 	props: { | ||||
| 		column: { | ||||
| 			type: Object, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		isStacked: { | ||||
| 			type: Boolean, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	watch: { | ||||
| 		mediaOnly() { | ||||
| 			(this.$refs.timeline as any).reload(); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		if (this.column.listId == null) { | ||||
| 			this.setList(); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		async setList() { | ||||
| 			const lists = await os.api('users/lists/list'); | ||||
| 			const { canceled, result: list } = await os.select({ | ||||
| 				title: this.$ts.selectList, | ||||
| 				items: lists.map(x => ({ | ||||
| 					value: x, text: x.name | ||||
| 				})), | ||||
| 				default: this.column.listId | ||||
| 			}); | ||||
| 			if (canceled) return; | ||||
| 			updateColumn(this.column.id, { | ||||
| 				listId: list.id | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		focus() { | ||||
| 			(this.$refs.timeline as any).focus(); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| */ | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <XColumn v-if="deckStore.state.alwaysShowMainColumn || $route.name !== 'index'" :column="column" :is-stacked="isStacked"> | ||||
| <XColumn v-if="deckStore.state.alwaysShowMainColumn || $route.name !== 'index'" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> | ||||
| 	<template #header> | ||||
| 		<template v-if="pageInfo"> | ||||
| 			<i :class="pageInfo.icon"></i> | ||||
| @@ -20,72 +20,59 @@ | ||||
| </XColumn> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import XColumn from './column.vue'; | ||||
| import XNotes from '@/components/notes.vue'; | ||||
| import { deckStore } from '@/ui/deck/deck-store'; | ||||
| import { deckStore, Column } from '@/ui/deck/deck-store'; | ||||
| import * as os from '@/os'; | ||||
| import * as symbols from '@/symbols'; | ||||
| import { i18n } from '@/i18n'; | ||||
| import { router } from '@/router'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XColumn, | ||||
| 		XNotes | ||||
| 	}, | ||||
| defineProps<{ | ||||
| 	column: Column; | ||||
| 	isStacked: boolean; | ||||
| }>(); | ||||
|  | ||||
| 	props: { | ||||
| 		column: { | ||||
| 			type: Object, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		isStacked: { | ||||
| 			type: Boolean, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| const emit = defineEmits<{ | ||||
| 	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; | ||||
| }>(); | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			deckStore, | ||||
| 			pageInfo: null, | ||||
| 		} | ||||
| 	}, | ||||
| let pageInfo = $ref<Record<string, any> | null>(null); | ||||
|  | ||||
| 	methods: { | ||||
| 		changePage(page) { | ||||
| 			if (page == null) return; | ||||
| 			if (page[symbols.PAGE_INFO]) { | ||||
| 				this.pageInfo = page[symbols.PAGE_INFO]; | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		back() { | ||||
| 			history.back(); | ||||
| 		}, | ||||
|  | ||||
| 		onContextmenu(ev: MouseEvent) { | ||||
| 			const isLink = (el: HTMLElement) => { | ||||
| 				if (el.tagName === 'A') return true; | ||||
| 				if (el.parentElement) { | ||||
| 					return isLink(el.parentElement); | ||||
| 				} | ||||
| 			}; | ||||
| 			if (isLink(ev.target)) return; | ||||
| 			if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; | ||||
| 			if (window.getSelection().toString() !== '') return; | ||||
| 			const path = this.$route.path; | ||||
| 			os.contextMenu([{ | ||||
| 				type: 'label', | ||||
| 				text: path, | ||||
| 			}, { | ||||
| 				icon: 'fas fa-window-maximize', | ||||
| 				text: this.$ts.openInWindow, | ||||
| 				action: () => { | ||||
| 					os.pageWindow(path); | ||||
| 				} | ||||
| 			}], ev); | ||||
| 		}, | ||||
| function changePage(page) { | ||||
| 	if (page == null) return; | ||||
| 	if (page[symbols.PAGE_INFO]) { | ||||
| 		pageInfo = page[symbols.PAGE_INFO]; | ||||
| 	} | ||||
| }); | ||||
| } | ||||
| /* | ||||
| function back() { | ||||
| 	history.back(); | ||||
| } | ||||
| */ | ||||
| function onContextmenu(ev: MouseEvent) { | ||||
| 	if (!ev.target) return; | ||||
|  | ||||
| 	const isLink = (el: HTMLElement) => { | ||||
| 		if (el.tagName === 'A') return true; | ||||
| 		if (el.parentElement) { | ||||
| 			return isLink(el.parentElement); | ||||
| 		} | ||||
| 	}; | ||||
| 	if (isLink(ev.target as HTMLElement)) return; | ||||
| 	if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes((ev.target as HTMLElement).tagName) || (ev.target as HTMLElement).attributes['contenteditable']) return; | ||||
| 	if (window.getSelection()?.toString() !== '') return; | ||||
| 	const path = router.currentRoute.value.path; | ||||
| 	os.contextMenu([{ | ||||
| 		type: 'label', | ||||
| 		text: path, | ||||
| 	}, { | ||||
| 		icon: 'fas fa-window-maximize', | ||||
| 		text: i18n.ts.openInWindow, | ||||
| 		action: () => { | ||||
| 			os.pageWindow(path); | ||||
| 		} | ||||
| 	}], ev); | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <XColumn :column="column" :is-stacked="isStacked"> | ||||
| <XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> | ||||
| 	<template #header><i class="fas fa-at" style="margin-right: 8px;"></i>{{ column.name }}</template> | ||||
|  | ||||
| 	<XNotes :pagination="pagination"/> | ||||
| @@ -10,13 +10,17 @@ | ||||
| import { } from 'vue'; | ||||
| import XColumn from './column.vue'; | ||||
| import XNotes from '@/components/notes.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { Column } from './deck-store'; | ||||
|  | ||||
| const props = defineProps<{ | ||||
| 	column: Record<string, unknown>; // TODO | ||||
| defineProps<{ | ||||
| 	column: Column; | ||||
| 	isStacked: boolean; | ||||
| }>(); | ||||
|  | ||||
| const emit = defineEmits<{ | ||||
| 	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; | ||||
| }>(); | ||||
|  | ||||
| const pagination = { | ||||
| 	endpoint: 'notes/mentions' as const, | ||||
| 	limit: 10, | ||||
|   | ||||
| @@ -1,53 +1,38 @@ | ||||
| <template> | ||||
| <XColumn :column="column" :is-stacked="isStacked" :func="{ handler: func, title: $ts.notificationSetting }"> | ||||
| <XColumn :column="column" :is-stacked="isStacked" :func="{ handler: func, title: $ts.notificationSetting }" @parent-focus="$event => emit('parent-focus', $event)"> | ||||
| 	<template #header><i class="fas fa-bell" style="margin-right: 8px;"></i>{{ column.name }}</template> | ||||
|  | ||||
| 	<XNotifications :include-types="column.includingTypes"/> | ||||
| </XColumn> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import XColumn from './column.vue'; | ||||
| import XNotifications from '@/components/notifications.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { updateColumn } from './deck-store'; | ||||
| import { Column } from './deck-store'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XColumn, | ||||
| 		XNotifications | ||||
| 	}, | ||||
| const props = defineProps<{ | ||||
| 	column: Column; | ||||
| 	isStacked: boolean; | ||||
| }>(); | ||||
|  | ||||
| 	props: { | ||||
| 		column: { | ||||
| 			type: Object, | ||||
| 			required: true | ||||
| const emit = defineEmits<{ | ||||
| 	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; | ||||
| }>(); | ||||
|  | ||||
| function func() { | ||||
| 	os.popup(import('@/components/notification-setting-window.vue'), { | ||||
| 		includingTypes: props.column.includingTypes, | ||||
| 	}, { | ||||
| 		done: async (res) => { | ||||
| 			const { includingTypes } = res; | ||||
| 			updateColumn(props.column.id, { | ||||
| 				includingTypes: includingTypes | ||||
| 			}); | ||||
| 		}, | ||||
| 		isStacked: { | ||||
| 			type: Boolean, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		func() { | ||||
| 			os.popup(import('@/components/notification-setting-window.vue'), { | ||||
| 				includingTypes: this.column.includingTypes, | ||||
| 			}, { | ||||
| 				done: async (res) => { | ||||
| 					const { includingTypes } = res; | ||||
| 					updateColumn(this.column.id, { | ||||
| 						includingTypes: includingTypes | ||||
| 					}); | ||||
| 				}, | ||||
| 			}, 'closed'); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| 	}, 'closed'); | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <XColumn :func="{ handler: setType, title: $ts.timeline }" :column="column" :is-stacked="isStacked" :indicated="indicated" @change-active-state="onChangeActiveState"> | ||||
| <XColumn :func="{ handler: setType, title: $ts.timeline }" :column="column" :is-stacked="isStacked" :indicated="indicated" @change-active-state="onChangeActiveState" @parent-focus="$event => emit('parent-focus', $event)"> | ||||
| 	<template #header> | ||||
| 		<i v-if="column.tl === 'home'" class="fas fa-home"></i> | ||||
| 		<i v-else-if="column.tl === 'local'" class="fas fa-comments"></i> | ||||
| @@ -15,108 +15,103 @@ | ||||
| 		</p> | ||||
| 		<p class="desc">{{ $t('disabled-timeline.description') }}</p> | ||||
| 	</div> | ||||
| 	<XTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl" @after="() => $emit('loaded')" @queue="queueUpdated" @note="onNote"/> | ||||
| 	<XTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl" @after="() => emit('loaded')" @queue="queueUpdated" @note="onNote"/> | ||||
| </XColumn> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { onMounted } from 'vue'; | ||||
| import XColumn from './column.vue'; | ||||
| import XTimeline from '@/components/timeline.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { removeColumn, updateColumn } from './deck-store'; | ||||
| import { removeColumn, updateColumn, Column } from './deck-store'; | ||||
| import { $i } from '@/account'; | ||||
| import { instance } from '@/instance'; | ||||
| import { i18n } from '@/i18n'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XColumn, | ||||
| 		XTimeline, | ||||
| 	}, | ||||
| const props = defineProps<{ | ||||
| 	column: Column; | ||||
| 	isStacked: boolean; | ||||
| }>(); | ||||
|  | ||||
| 	props: { | ||||
| 		column: { | ||||
| 			type: Object, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		isStacked: { | ||||
| 			type: Boolean, | ||||
| 			required: true | ||||
| const emit = defineEmits<{ | ||||
| 	(e: 'loaded'): void; | ||||
| 	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; | ||||
| }>(); | ||||
|  | ||||
| let disabled = $ref(false); | ||||
| let indicated = $ref(false); | ||||
| let columnActive = $ref(true); | ||||
|  | ||||
| onMounted(() => { | ||||
| 	if (props.column.tl == null) { | ||||
| 		setType(); | ||||
| 	} else if ($i) { | ||||
| 		disabled = !$i.isModerator && !$i.isAdmin && ( | ||||
| 			instance.disableLocalTimeline && ['local', 'social'].includes(props.column.tl) || | ||||
| 			instance.disableGlobalTimeline && ['global'].includes(props.column.tl)); | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| async function setType() { | ||||
| 	const { canceled, result: src } = await os.select({ | ||||
| 		title: i18n.ts.timeline, | ||||
| 		items: [{ | ||||
| 			value: 'home' as const, text: i18n.ts._timelines.home | ||||
| 		}, { | ||||
| 			value: 'local' as const, text: i18n.ts._timelines.local | ||||
| 		}, { | ||||
| 			value: 'social' as const, text: i18n.ts._timelines.social | ||||
| 		}, { | ||||
| 			value: 'global' as const, text: i18n.ts._timelines.global | ||||
| 		}], | ||||
| 	}); | ||||
| 	if (canceled) { | ||||
| 		if (props.column.tl == null) { | ||||
| 			removeColumn(props.column.id); | ||||
| 		} | ||||
| 	}, | ||||
| 		return; | ||||
| 	} | ||||
| 	updateColumn(props.column.id, { | ||||
| 		tl: src | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			disabled: false, | ||||
| 			indicated: false, | ||||
| 			columnActive: true, | ||||
| 		}; | ||||
| 	}, | ||||
| function queueUpdated(q) { | ||||
| 	if (columnActive) { | ||||
| 		indicated = q !== 0; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function onNote() { | ||||
| 	if (!columnActive) { | ||||
| 		indicated = true; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function onChangeActiveState(state) { | ||||
| 	columnActive = state; | ||||
|  | ||||
| 	if (columnActive) { | ||||
| 		indicated = false; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /* | ||||
| export default defineComponent({ | ||||
| 	watch: { | ||||
| 		mediaOnly() { | ||||
| 			(this.$refs.timeline as any).reload(); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		if (this.column.tl == null) { | ||||
| 			this.setType(); | ||||
| 		} else { | ||||
| 			this.disabled = !this.$i.isModerator && !this.$i.isAdmin && ( | ||||
| 				this.$instance.disableLocalTimeline && ['local', 'social'].includes(this.column.tl) || | ||||
| 				this.$instance.disableGlobalTimeline && ['global'].includes(this.column.tl)); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		async setType() { | ||||
| 			const { canceled, result: src } = await os.select({ | ||||
| 				title: this.$ts.timeline, | ||||
| 				items: [{ | ||||
| 					value: 'home', text: this.$ts._timelines.home | ||||
| 				}, { | ||||
| 					value: 'local', text: this.$ts._timelines.local | ||||
| 				}, { | ||||
| 					value: 'social', text: this.$ts._timelines.social | ||||
| 				}, { | ||||
| 					value: 'global', text: this.$ts._timelines.global | ||||
| 				}] | ||||
| 			}); | ||||
| 			if (canceled) { | ||||
| 				if (this.column.tl == null) { | ||||
| 					removeColumn(this.column.id); | ||||
| 				} | ||||
| 				return; | ||||
| 			} | ||||
| 			updateColumn(this.column.id, { | ||||
| 				tl: src | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		queueUpdated(q) { | ||||
| 			if (this.columnActive) { | ||||
| 				this.indicated = q !== 0; | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		onNote() { | ||||
| 			if (!this.columnActive) { | ||||
| 				this.indicated = true; | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		onChangeActiveState(state) { | ||||
| 			this.columnActive = state; | ||||
|  | ||||
| 			if (this.columnActive) { | ||||
| 				this.indicated = false; | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		focus() { | ||||
| 			(this.$refs.timeline as any).focus(); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| */ | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   | ||||
| @@ -1,64 +1,49 @@ | ||||
| <template> | ||||
| <XColumn :func="{ handler: func, title: $ts.editWidgets }" :naked="true" :column="column" :is-stacked="isStacked"> | ||||
| <XColumn :func="{ handler: func, title: $ts.editWidgets }" :naked="true" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> | ||||
| 	<template #header><i class="fas fa-window-maximize" style="margin-right: 8px;"></i>{{ column.name }}</template> | ||||
|  | ||||
| 	<div class="wtdtxvec"> | ||||
| 		<XWidgets :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> | ||||
| 		<XWidgets v-if="column.widgets" :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> | ||||
| 	</div> | ||||
| </XColumn> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent, defineAsyncComponent } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import XWidgets from '@/components/widgets.vue'; | ||||
| import XColumn from './column.vue'; | ||||
| import { addColumnWidget, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store'; | ||||
| import { addColumnWidget, Column, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XColumn, | ||||
| 		XWidgets, | ||||
| 	}, | ||||
| const props = defineProps<{ | ||||
| 	column: Column; | ||||
| 	isStacked: boolean; | ||||
| }>(); | ||||
|  | ||||
| 	props: { | ||||
| 		column: { | ||||
| 			type: Object, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		isStacked: { | ||||
| 			type: Boolean, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 	}, | ||||
| const emit = defineEmits<{ | ||||
| 	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; | ||||
| }>(); | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			edit: false, | ||||
| 		}; | ||||
| 	}, | ||||
| let edit = $ref(false); | ||||
|  | ||||
| 	methods: { | ||||
| 		addWidget(widget) { | ||||
| 			addColumnWidget(this.column.id, widget); | ||||
| 		}, | ||||
| function addWidget(widget) { | ||||
| 	addColumnWidget(props.column.id, widget); | ||||
| } | ||||
|  | ||||
| 		removeWidget(widget) { | ||||
| 			removeColumnWidget(this.column.id, widget); | ||||
| 		}, | ||||
| function removeWidget(widget) { | ||||
| 	removeColumnWidget(props.column.id, widget); | ||||
| } | ||||
|  | ||||
| 		updateWidget({ id, data }) { | ||||
| 			updateColumnWidget(this.column.id, id, data); | ||||
| 		}, | ||||
| function updateWidget({ id, data }) { | ||||
| 	updateColumnWidget(props.column.id, id, data); | ||||
| } | ||||
|  | ||||
| 		updateWidgets(widgets) { | ||||
| 			setColumnWidgets(this.column.id, widgets); | ||||
| 		}, | ||||
| function updateWidgets(widgets) { | ||||
| 	setColumnWidgets(props.column.id, widgets); | ||||
| } | ||||
|  | ||||
| 		func() { | ||||
| 			this.edit = !this.edit; | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| function func() { | ||||
| 	edit = !edit; | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   | ||||
| @@ -1,58 +1,50 @@ | ||||
| <template> | ||||
| <div class="efzpzdvf"> | ||||
| 	<XWidgets :edit="editMode" :widgets="$store.reactiveState.widgets.value" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> | ||||
| 	<XWidgets :edit="editMode" :widgets="defaultStore.reactiveState.widgets.value" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> | ||||
|  | ||||
| 	<button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="fas fa-check"></i> {{ $ts.editWidgetsExit }}</button> | ||||
| 	<button v-else class="_textButton" style="font-size: 0.9em;" @click="editMode = true"><i class="fas fa-pencil-alt"></i> {{ $ts.editWidgets }}</button> | ||||
| 	<button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="fas fa-check"></i> {{ i18n.ts.editWidgetsExit }}</button> | ||||
| 	<button v-else class="_textButton" style="font-size: 0.9em;" @click="editMode = true"><i class="fas fa-pencil-alt"></i> {{ i18n.ts.editWidgets }}</button> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent, defineAsyncComponent } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { onMounted } from 'vue'; | ||||
| import XWidgets from '@/components/widgets.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { i18n } from '@/i18n'; | ||||
| import { defaultStore } from '@/store'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XWidgets | ||||
| 	}, | ||||
| const emit = defineEmits<{ | ||||
| 	(e: 'mounted', el: Element): void; | ||||
| }>(); | ||||
|  | ||||
| 	emits: ['mounted'], | ||||
| let editMode = $ref(false); | ||||
| let rootEl = $ref<HTMLDivElement>(); | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			editMode: false, | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		this.$emit('mounted', this.$el); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		addWidget(widget) { | ||||
| 			this.$store.set('widgets', [{ | ||||
| 				...widget, | ||||
| 				place: null, | ||||
| 			}, ...this.$store.state.widgets]); | ||||
| 		}, | ||||
|  | ||||
| 		removeWidget(widget) { | ||||
| 			this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id != widget.id)); | ||||
| 		}, | ||||
|  | ||||
| 		updateWidget({ id, data }) { | ||||
| 			this.$store.set('widgets', this.$store.state.widgets.map(w => w.id === id ? { | ||||
| 				...w, | ||||
| 				data: data | ||||
| 			} : w)); | ||||
| 		}, | ||||
|  | ||||
| 		updateWidgets(widgets) { | ||||
| 			this.$store.set('widgets', widgets); | ||||
| 		} | ||||
| 	} | ||||
| onMounted(() => { | ||||
| 	emit('mounted', rootEl); | ||||
| }); | ||||
|  | ||||
| function addWidget(widget) { | ||||
| 	defaultStore.set('widgets', [{ | ||||
| 		...widget, | ||||
| 		place: null, | ||||
| 	}, ...defaultStore.state.widgets]); | ||||
| } | ||||
|  | ||||
| function removeWidget(widget) { | ||||
| 	defaultStore.set('widgets', defaultStore.state.widgets.filter(w => w.id != widget.id)); | ||||
| } | ||||
|  | ||||
| function updateWidget({ id, data }) { | ||||
| 	defaultStore.set('widgets', defaultStore.state.widgets.map(w => w.id === id ? { | ||||
| 		...w, | ||||
| 		data: data | ||||
| 	} : w)); | ||||
| } | ||||
|  | ||||
| function updateWidgets(widgets) { | ||||
| 	defaultStore.set('widgets', widgets); | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 tamaina
					tamaina