refactoring

Resolve #7779
This commit is contained in:
syuilo
2021-11-12 02:02:25 +09:00
parent 037837b551
commit 0e4a111f81
1714 changed files with 20803 additions and 11751 deletions

View File

@@ -0,0 +1,80 @@
<template>
<XColumn :func="{ handler: setAntenna, title: $ts.selectAntenna }" :column="column" :is-stacked="isStacked">
<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')"/>
</XColumn>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import XColumn from './column.vue';
import XTimeline from '@/components/timeline.vue';
import * as os from '@/os';
import { updateColumn } from './deck-store';
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.antennaId == null) {
this.setAntenna();
}
},
methods: {
async setAntenna() {
const antennas = await os.api('antennas/list');
const { canceled, result: antenna } = await os.dialog({
title: this.$ts.selectAntenna,
type: null,
select: {
items: antennas.map(x => ({
value: x, text: x.name
})),
default: this.column.antennaId
},
showCancelButton: true
});
if (canceled) return;
updateColumn(this.column.id, {
antennaId: antenna.id
});
},
focus() {
(this.$refs.timeline as any).focus();
}
}
});
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,52 @@
<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)"/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import XMainColumn from './main-column.vue';
import XTlColumn from './tl-column.vue';
import XAntennaColumn from './antenna-column.vue';
import XListColumn from './list-column.vue';
import XNotificationsColumn from './notifications-column.vue';
import XWidgetsColumn from './widgets-column.vue';
import XMentionsColumn from './mentions-column.vue';
import XDirectColumn from './direct-column.vue';
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>

View File

@@ -0,0 +1,408 @@
<template>
<!-- sectionを利用しているのはdeck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
<section class="dnpfarvg _panel _narrow_" :class="{ paged: isMainColumn, naked, active, isStacked, draghover, dragging, dropready }"
@dragover.prevent.stop="onDragover"
@dragleave="onDragleave"
@drop.prevent.stop="onDrop"
v-hotkey="keymap"
:style="{ '--deckColumnHeaderHeight': deckStore.reactiveState.columnHeaderHeight.value + 'px' }"
>
<header :class="{ indicated }"
draggable="true"
@click="goTop"
@dragstart="onDragstart"
@dragend="onDragend"
@contextmenu.prevent.stop="onContextmenu"
>
<button class="toggleActive _button" @click="toggleActive" v-if="isStacked && !isMainColumn">
<template v-if="active"><i class="fas fa-angle-up"></i></template>
<template v-else><i class="fas fa-angle-down"></i></template>
</button>
<div class="action">
<slot name="action"></slot>
</div>
<span class="header"><slot name="header"></slot></span>
<button v-if="func" class="menu _button" v-tooltip="func.title" @click.stop="func.handler"><i :class="func.icon || 'fas fa-cog'"></i></button>
</header>
<div ref="body" v-show="active">
<slot></slot>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import * as os from '@/os';
import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn } from './deck-store';
import { deckStore } from './deck-store';
export default defineComponent({
provide: {
shouldHeaderThin: true,
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(e) {
os.contextMenu(this.getMenu(), e);
},
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
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);
}
}
}
});
</script>
<style lang="scss" scoped>
.dnpfarvg {
--root-margin: 10px;
height: 100%;
overflow: hidden;
contain: content;
box-shadow: 0 0 8px 0 var(--shadow);
&.draghover {
box-shadow: 0 0 0 2px var(--focus);
&:after {
content: "";
display: block;
position: absolute;
z-index: 1000;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--focus);
}
}
&.dragging {
box-shadow: 0 0 0 2px var(--focus);
}
&.dropready {
* {
pointer-events: none;
}
}
&:not(.active) {
flex-basis: var(--deckColumnHeaderHeight);
min-height: var(--deckColumnHeaderHeight);
> header.indicated {
box-shadow: 4px 0px var(--accent) inset;
}
}
&.naked {
background: var(--acrylicBg) !important;
-webkit-backdrop-filter: var(--blur, blur(10px));
backdrop-filter: var(--blur, blur(10px));
> header {
background: transparent;
box-shadow: none;
> button {
color: var(--fg);
}
}
}
&.paged {
background: var(--bg) !important;
}
> header {
position: relative;
display: flex;
z-index: 2;
line-height: var(--deckColumnHeaderHeight);
height: var(--deckColumnHeaderHeight);
padding: 0 16px;
font-size: 0.9em;
color: var(--panelHeaderFg);
background: var(--panelHeaderBg);
box-shadow: 0 1px 0 0 var(--panelHeaderDivider);
cursor: pointer;
&, * {
user-select: none;
}
&.indicated {
box-shadow: 0 3px 0 0 var(--accent);
}
> .header {
display: inline-block;
align-items: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
> span:only-of-type {
width: 100%;
}
> .toggleActive,
> .action > ::v-deep(*),
> .menu {
z-index: 1;
width: var(--deckColumnHeaderHeight);
line-height: var(--deckColumnHeaderHeight);
font-size: 16px;
color: var(--faceTextButton);
&:hover {
color: var(--faceTextButtonHover);
}
&:active {
color: var(--faceTextButtonActive);
}
}
> .toggleActive, > .action {
margin-left: -16px;
}
> .action {
z-index: 1;
}
> .action:empty {
display: none;
}
> .menu {
margin-left: auto;
margin-right: -16px;
}
}
> div {
height: calc(100% - var(--deckColumnHeaderHeight));
overflow: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
}
}
</style>

View File

@@ -0,0 +1,298 @@
import { throttle } from 'throttle-debounce';
import { i18n } from '@/i18n';
import { api } from '@/os';
import { markRaw, watch } from 'vue';
import { Storage } from '../../pizzax';
type ColumnWidget = {
name: string;
id: string;
data: Record<string, any>;
};
type Column = {
id: string;
type: string;
name: string | null;
width: number;
widgets?: ColumnWidget[];
active?: boolean;
};
function copy<T>(x: T): T {
return JSON.parse(JSON.stringify(x));
}
export const deckStore = markRaw(new Storage('deck', {
profile: {
where: 'deviceAccount',
default: 'default'
},
columns: {
where: 'deviceAccount',
default: [] as Column[]
},
layout: {
where: 'deviceAccount',
default: [] as Column['id'][][]
},
columnAlign: {
where: 'deviceAccount',
default: 'left' as 'left' | 'right' | 'center'
},
alwaysShowMainColumn: {
where: 'deviceAccount',
default: true
},
navWindow: {
where: 'deviceAccount',
default: true
},
columnMargin: {
where: 'deviceAccount',
default: 16
},
columnHeaderHeight: {
where: 'deviceAccount',
default: 42
},
}));
export const loadDeck = async () => {
let deck;
try {
deck = await api('i/registry/get', {
scope: ['client', 'deck', 'profiles'],
key: deckStore.state.profile,
});
} catch (e) {
if (e.code === 'NO_SUCH_KEY') {
// 後方互換性のため
if (deckStore.state.profile === 'default') {
saveDeck();
return;
}
deckStore.set('columns', [{
id: 'a',
type: 'main',
name: i18n.locale._deck._columns.main,
width: 350,
}, {
id: 'b',
type: 'notifications',
name: i18n.locale._deck._columns.notifications,
width: 330,
}]);
deckStore.set('layout', [['a'], ['b']]);
return;
}
throw e;
}
deckStore.set('columns', deck.columns);
deckStore.set('layout', deck.layout);
};
// TODO: deckがloadされていない状態でsaveすると意図せず上書きが発生するので対策する
export const saveDeck = throttle(1000, () => {
api('i/registry/set', {
scope: ['client', 'deck', 'profiles'],
key: deckStore.state.profile,
value: {
columns: deckStore.reactiveState.columns.value,
layout: deckStore.reactiveState.layout.value,
}
});
});
export function addColumn(column: Column) {
if (column.name == undefined) column.name = null;
deckStore.push('columns', column);
deckStore.push('layout', [column.id]);
saveDeck();
}
export function removeColumn(id: Column['id']) {
deckStore.set('columns', deckStore.state.columns.filter(c => c.id !== id));
deckStore.set('layout', deckStore.state.layout
.map(ids => ids.filter(_id => _id !== id))
.filter(ids => ids.length > 0));
saveDeck();
}
export function swapColumn(a: Column['id'], b: Column['id']) {
const aX = deckStore.state.layout.findIndex(ids => ids.indexOf(a) != -1);
const aY = deckStore.state.layout[aX].findIndex(id => id == a);
const bX = deckStore.state.layout.findIndex(ids => ids.indexOf(b) != -1);
const bY = deckStore.state.layout[bX].findIndex(id => id == b);
const layout = copy(deckStore.state.layout);
layout[aX][aY] = b;
layout[bX][bY] = a;
deckStore.set('layout', layout);
saveDeck();
}
export function swapLeftColumn(id: Column['id']) {
const layout = copy(deckStore.state.layout);
deckStore.state.layout.some((ids, i) => {
if (ids.includes(id)) {
const left = deckStore.state.layout[i - 1];
if (left) {
layout[i - 1] = deckStore.state.layout[i];
layout[i] = left;
deckStore.set('layout', layout);
}
return true;
}
});
saveDeck();
}
export function swapRightColumn(id: Column['id']) {
const layout = copy(deckStore.state.layout);
deckStore.state.layout.some((ids, i) => {
if (ids.includes(id)) {
const right = deckStore.state.layout[i + 1];
if (right) {
layout[i + 1] = deckStore.state.layout[i];
layout[i] = right;
deckStore.set('layout', layout);
}
return true;
}
});
saveDeck();
}
export function swapUpColumn(id: Column['id']) {
const layout = copy(deckStore.state.layout);
const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id));
const ids = copy(deckStore.state.layout[idsIndex]);
ids.some((x, i) => {
if (x === id) {
const up = ids[i - 1];
if (up) {
ids[i - 1] = id;
ids[i] = up;
layout[idsIndex] = ids;
deckStore.set('layout', layout);
}
return true;
}
});
saveDeck();
}
export function swapDownColumn(id: Column['id']) {
const layout = copy(deckStore.state.layout);
const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id));
const ids = copy(deckStore.state.layout[idsIndex]);
ids.some((x, i) => {
if (x === id) {
const down = ids[i + 1];
if (down) {
ids[i + 1] = id;
ids[i] = down;
layout[idsIndex] = ids;
deckStore.set('layout', layout);
}
return true;
}
});
saveDeck();
}
export function stackLeftColumn(id: Column['id']) {
let layout = copy(deckStore.state.layout);
const i = deckStore.state.layout.findIndex(ids => ids.includes(id));
layout = layout.map(ids => ids.filter(_id => _id !== id));
layout[i - 1].push(id);
layout = layout.filter(ids => ids.length > 0);
deckStore.set('layout', layout);
saveDeck();
}
export function popRightColumn(id: Column['id']) {
let layout = copy(deckStore.state.layout);
const i = deckStore.state.layout.findIndex(ids => ids.includes(id));
const affected = layout[i];
layout = layout.map(ids => ids.filter(_id => _id !== id));
layout.splice(i + 1, 0, [id]);
layout = layout.filter(ids => ids.length > 0);
deckStore.set('layout', layout);
const columns = copy(deckStore.state.columns);
for (const column of columns) {
if (affected.includes(column.id)) {
column.active = true;
}
}
deckStore.set('columns', columns);
saveDeck();
}
export function addColumnWidget(id: Column['id'], widget: ColumnWidget) {
const columns = copy(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const column = copy(deckStore.state.columns[columnIndex]);
if (column == null) return;
if (column.widgets == null) column.widgets = [];
column.widgets.unshift(widget);
columns[columnIndex] = column;
deckStore.set('columns', columns);
saveDeck();
}
export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
const columns = copy(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const column = copy(deckStore.state.columns[columnIndex]);
if (column == null) return;
column.widgets = column.widgets.filter(w => w.id != widget.id);
columns[columnIndex] = column;
deckStore.set('columns', columns);
saveDeck();
}
export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
const columns = copy(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const column = copy(deckStore.state.columns[columnIndex]);
if (column == null) return;
column.widgets = widgets;
columns[columnIndex] = column;
deckStore.set('columns', columns);
saveDeck();
}
export function updateColumnWidget(id: Column['id'], widgetId: string, data: any) {
const columns = copy(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const column = copy(deckStore.state.columns[columnIndex]);
if (column == null) return;
column.widgets = column.widgets.map(w => w.id === widgetId ? {
...w,
data: data
} : w);
columns[columnIndex] = column;
deckStore.set('columns', columns);
saveDeck();
}
export function updateColumn(id: Column['id'], column: Partial<Column>) {
const columns = copy(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const currentColumn = copy(deckStore.state.columns[columnIndex]);
if (currentColumn == null) return;
for (const [k, v] of Object.entries(column)) {
currentColumn[k] = v;
}
columns[columnIndex] = currentColumn;
deckStore.set('columns', columns);
saveDeck();
}

View File

@@ -0,0 +1,55 @@
<template>
<XColumn :column="column" :is-stacked="isStacked">
<template #header><i class="fas fa-envelope" style="margin-right: 8px;"></i>{{ column.name }}</template>
<XNotes :pagination="pagination" @before="before()" @after="after()"/>
</XColumn>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Progress from '@/scripts/loading';
import XColumn from './column.vue';
import XNotes from '@/components/notes.vue';
import * as os from '@/os';
export default defineComponent({
components: {
XColumn,
XNotes
},
props: {
column: {
type: Object,
required: true
},
isStacked: {
type: Boolean,
required: true
}
},
data() {
return {
pagination: {
endpoint: 'notes/mentions',
limit: 10,
params: () => ({
visibility: 'specified'
})
},
}
},
methods: {
before() {
Progress.start();
},
after() {
Progress.done();
}
}
});
</script>

View File

@@ -0,0 +1,80 @@
<template>
<XColumn :func="{ handler: setList, title: $ts.selectList }" :column="column" :is-stacked="isStacked">
<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')"/>
</XColumn>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import XColumn from './column.vue';
import XTimeline from '@/components/timeline.vue';
import * as os from '@/os';
import { updateColumn } from './deck-store';
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.dialog({
title: this.$ts.selectList,
type: null,
select: {
items: lists.map(x => ({
value: x, text: x.name
})),
default: this.column.listId
},
showCancelButton: true
});
if (canceled) return;
updateColumn(this.column.id, {
listId: list.id
});
},
focus() {
(this.$refs.timeline as any).focus();
}
}
});
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,91 @@
<template>
<XColumn v-if="deckStore.state.alwaysShowMainColumn || $route.name !== 'index'" :column="column" :is-stacked="isStacked">
<template #header>
<template v-if="pageInfo">
<i :class="pageInfo.icon"></i>
{{ pageInfo.title }}
</template>
</template>
<MkStickyContainer>
<template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template>
<router-view v-slot="{ Component }">
<transition>
<keep-alive :include="['timeline']">
<component :is="Component" :ref="changePage" @contextmenu.stop="onContextmenu"/>
</keep-alive>
</transition>
</router-view>
</MkStickyContainer>
</XColumn>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import XColumn from './column.vue';
import XNotes from '@/components/notes.vue';
import { deckStore } from '@/ui/deck/deck-store';
import * as os from '@/os';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
XColumn,
XNotes
},
props: {
column: {
type: Object,
required: true
},
isStacked: {
type: Boolean,
required: true
}
},
data() {
return {
deckStore,
pageInfo: null,
}
},
methods: {
changePage(page) {
if (page == null) return;
if (page[symbols.PAGE_INFO]) {
this.pageInfo = page[symbols.PAGE_INFO];
}
},
back() {
history.back();
},
onContextmenu(e) {
const isLink = (el: HTMLElement) => {
if (el.tagName === 'A') return true;
if (el.parentElement) {
return isLink(el.parentElement);
}
};
if (isLink(e.target)) return;
if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(e.target.tagName) || e.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);
}
}], e);
},
}
});
</script>

View File

@@ -0,0 +1,52 @@
<template>
<XColumn :column="column" :is-stacked="isStacked">
<template #header><i class="fas fa-at" style="margin-right: 8px;"></i>{{ column.name }}</template>
<XNotes :pagination="pagination" @before="before()" @after="after()"/>
</XColumn>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Progress from '@/scripts/loading';
import XColumn from './column.vue';
import XNotes from '@/components/notes.vue';
import * as os from '@/os';
export default defineComponent({
components: {
XColumn,
XNotes
},
props: {
column: {
type: Object,
required: true
},
isStacked: {
type: Boolean,
required: true
}
},
data() {
return {
pagination: {
endpoint: 'notes/mentions',
limit: 10,
},
}
},
methods: {
before() {
Progress.start();
},
after() {
Progress.done();
}
}
});
</script>

View File

@@ -0,0 +1,53 @@
<template>
<XColumn :column="column" :is-stacked="isStacked" :func="{ handler: func, title: $ts.notificationSetting }">
<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';
import XColumn from './column.vue';
import XNotifications from '@/components/notifications.vue';
import * as os from '@/os';
import { updateColumn } from './deck-store';
export default defineComponent({
components: {
XColumn,
XNotifications
},
props: {
column: {
type: Object,
required: true
},
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');
}
}
});
</script>

View File

@@ -0,0 +1,137 @@
<template>
<XColumn :func="{ handler: setType, title: $ts.timeline }" :column="column" :is-stacked="isStacked" :indicated="indicated" @change-active-state="onChangeActiveState">
<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>
<i v-else-if="column.tl === 'social'" class="fas fa-share-alt"></i>
<i v-else-if="column.tl === 'global'" class="fas fa-globe"></i>
<span style="margin-left: 8px;">{{ column.name }}</span>
</template>
<div class="iwaalbte" v-if="disabled">
<p>
<i class="fas fa-minus-circle"></i>
{{ $t('disabled-timeline.title') }}
</p>
<p class="desc">{{ $t('disabled-timeline.description') }}</p>
</div>
<XTimeline v-else-if="column.tl" ref="timeline" :src="column.tl" @after="() => $emit('loaded')" @queue="queueUpdated" @note="onNote" :key="column.tl"/>
</XColumn>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import XColumn from './column.vue';
import XTimeline from '@/components/timeline.vue';
import * as os from '@/os';
import { removeColumn, updateColumn } from './deck-store';
export default defineComponent({
components: {
XColumn,
XTimeline,
},
props: {
column: {
type: Object,
required: true
},
isStacked: {
type: Boolean,
required: true
}
},
data() {
return {
disabled: false,
indicated: false,
columnActive: true,
};
},
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.dialog({
title: this.$ts.timeline,
type: null,
select: {
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>
.iwaalbte {
text-align: center;
> p {
margin: 16px;
&.desc {
font-size: 14px;
}
}
}
</style>

View File

@@ -0,0 +1,71 @@
<template>
<XColumn :func="{ handler: func, title: $ts.editWidgets }" :naked="true" :column="column" :is-stacked="isStacked">
<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"/>
</div>
</XColumn>
</template>
<script lang="ts">
import { defineComponent, defineAsyncComponent } from 'vue';
import XWidgets from '@/components/widgets.vue';
import XColumn from './column.vue';
import { addColumnWidget, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store';
export default defineComponent({
components: {
XColumn,
XWidgets,
},
props: {
column: {
type: Object,
required: true,
},
isStacked: {
type: Boolean,
required: true,
},
},
data() {
return {
edit: false,
};
},
methods: {
addWidget(widget) {
addColumnWidget(this.column.id, widget);
},
removeWidget(widget) {
removeColumnWidget(this.column.id, widget);
},
updateWidget({ id, data }) {
updateColumnWidget(this.column.id, id, data);
},
updateWidgets(widgets) {
setColumnWidgets(this.column.id, widgets);
},
func() {
this.edit = !this.edit;
}
}
});
</script>
<style lang="scss" scoped>
.wtdtxvec {
--margin: 8px;
--panelBorder: none;
padding: 0 var(--margin);
}
</style>