enhance(frontend): 絵文字管理画面β(ローカル)のUI・UX改善 (#15349)
* enhance(frontend): 絵文字管理画面β(ローカル)のUI・UX改善
* fix
* 🎨
* 表示件数をメニューから変更するように
* 確認ダイアログ
* fix i18n
* needWideArea: trueならwidgetの開閉ボタンを表示しないように
* fix: 検索ウィンドウは一つしか開けないように
This commit is contained in:
parent
d6986c57f5
commit
26eec09d0e
43
locales/index.d.ts
vendored
43
locales/index.d.ts
vendored
@ -10588,7 +10588,7 @@ export interface Locale extends ILocale {
|
|||||||
*/
|
*/
|
||||||
"deleteSelectionRows": string;
|
"deleteSelectionRows": string;
|
||||||
/**
|
/**
|
||||||
* 選択範囲の行を削除
|
* 選択範囲の値をクリア
|
||||||
*/
|
*/
|
||||||
"deleteSelectionRanges": string;
|
"deleteSelectionRanges": string;
|
||||||
/**
|
/**
|
||||||
@ -10599,6 +10599,10 @@ export interface Locale extends ILocale {
|
|||||||
* 検索条件を詳細に設定します。
|
* 検索条件を詳細に設定します。
|
||||||
*/
|
*/
|
||||||
"searchSettingCaption": string;
|
"searchSettingCaption": string;
|
||||||
|
/**
|
||||||
|
* 表示件数
|
||||||
|
*/
|
||||||
|
"searchLimit": string;
|
||||||
/**
|
/**
|
||||||
* 並び順
|
* 並び順
|
||||||
*/
|
*/
|
||||||
@ -10611,10 +10615,6 @@ export interface Locale extends ILocale {
|
|||||||
* 絵文字更新・削除時のログが表示されます。更新・削除操作を行ったり、ページを遷移・リロードすると消えます。
|
* 絵文字更新・削除時のログが表示されます。更新・削除操作を行ったり、ページを遷移・リロードすると消えます。
|
||||||
*/
|
*/
|
||||||
"registrationLogsCaption": string;
|
"registrationLogsCaption": string;
|
||||||
/**
|
|
||||||
* エラー
|
|
||||||
*/
|
|
||||||
"alertEmojisRegisterFailedTitle": string;
|
|
||||||
/**
|
/**
|
||||||
* 絵文字の更新・削除に失敗しました。詳細は登録ログをご確認ください。
|
* 絵文字の更新・削除に失敗しました。詳細は登録ログをご確認ください。
|
||||||
*/
|
*/
|
||||||
@ -10691,21 +10691,30 @@ export interface Locale extends ILocale {
|
|||||||
*/
|
*/
|
||||||
"alertDeleteEmojisNothingDescription": string;
|
"alertDeleteEmojisNothingDescription": string;
|
||||||
/**
|
/**
|
||||||
* 確認
|
* ページを移動しますか?
|
||||||
*/
|
*/
|
||||||
"confirmUpdateEmojisTitle": string;
|
"confirmMovePage": string;
|
||||||
|
/**
|
||||||
|
* 表示を変更しますか?
|
||||||
|
*/
|
||||||
|
"confirmChangeView": string;
|
||||||
/**
|
/**
|
||||||
* {count}個の絵文字を更新します。実行しますか?
|
* {count}個の絵文字を更新します。実行しますか?
|
||||||
*/
|
*/
|
||||||
"confirmUpdateEmojisDescription": ParameterizedString<"count">;
|
"confirmUpdateEmojisDescription": ParameterizedString<"count">;
|
||||||
/**
|
|
||||||
* 確認
|
|
||||||
*/
|
|
||||||
"confirmDeleteEmojisTitle": string;
|
|
||||||
/**
|
/**
|
||||||
* チェックがつけられた{count}個の絵文字を削除します。実行しますか?
|
* チェックがつけられた{count}個の絵文字を削除します。実行しますか?
|
||||||
*/
|
*/
|
||||||
"confirmDeleteEmojisDescription": ParameterizedString<"count">;
|
"confirmDeleteEmojisDescription": ParameterizedString<"count">;
|
||||||
|
/**
|
||||||
|
* 今までに加えた変更がすべてリセットされます。
|
||||||
|
*/
|
||||||
|
"confirmResetDescription": string;
|
||||||
|
/**
|
||||||
|
* このページの絵文字に変更が加えられています。
|
||||||
|
* 保存せずにこのままページを移動すると、このページで加えた変更はすべて破棄されます。
|
||||||
|
*/
|
||||||
|
"confirmMovePageDesciption": string;
|
||||||
/**
|
/**
|
||||||
* 絵文字に設定されたロールで検索
|
* 絵文字に設定されたロールで検索
|
||||||
*/
|
*/
|
||||||
@ -10744,26 +10753,14 @@ export interface Locale extends ILocale {
|
|||||||
* このリンクをクリックしてドライブから選択する
|
* このリンクをクリックしてドライブから選択する
|
||||||
*/
|
*/
|
||||||
"emojiInputAreaList3": string;
|
"emojiInputAreaList3": string;
|
||||||
/**
|
|
||||||
* 確認
|
|
||||||
*/
|
|
||||||
"confirmRegisterEmojisTitle": string;
|
|
||||||
/**
|
/**
|
||||||
* リストに表示されている絵文字を新たなカスタム絵文字として登録します。よろしいですか?(負荷を避けるため、一度の操作で登録可能な絵文字は{count}件までです)
|
* リストに表示されている絵文字を新たなカスタム絵文字として登録します。よろしいですか?(負荷を避けるため、一度の操作で登録可能な絵文字は{count}件までです)
|
||||||
*/
|
*/
|
||||||
"confirmRegisterEmojisDescription": ParameterizedString<"count">;
|
"confirmRegisterEmojisDescription": ParameterizedString<"count">;
|
||||||
/**
|
|
||||||
* 確認
|
|
||||||
*/
|
|
||||||
"confirmClearEmojisTitle": string;
|
|
||||||
/**
|
/**
|
||||||
* 編集内容を破棄し、リストに表示されている絵文字をクリアします。よろしいですか?
|
* 編集内容を破棄し、リストに表示されている絵文字をクリアします。よろしいですか?
|
||||||
*/
|
*/
|
||||||
"confirmClearEmojisDescription": string;
|
"confirmClearEmojisDescription": string;
|
||||||
/**
|
|
||||||
* 確認
|
|
||||||
*/
|
|
||||||
"confirmUploadEmojisTitle": string;
|
|
||||||
/**
|
/**
|
||||||
* ドラッグ&ドロップされた{count}個のファイルをドライブにアップロードします。実行しますか?
|
* ドラッグ&ドロップされた{count}個のファイルをドライブにアップロードします。実行しますか?
|
||||||
*/
|
*/
|
||||||
|
@ -2824,13 +2824,13 @@ _customEmojisManager:
|
|||||||
copySelectionRows: "選択行をコピー"
|
copySelectionRows: "選択行をコピー"
|
||||||
copySelectionRanges: "選択範囲をコピー"
|
copySelectionRanges: "選択範囲をコピー"
|
||||||
deleteSelectionRows: "選択行を削除"
|
deleteSelectionRows: "選択行を削除"
|
||||||
deleteSelectionRanges: "選択範囲の行を削除"
|
deleteSelectionRanges: "選択範囲の値をクリア"
|
||||||
searchSettings: "検索設定"
|
searchSettings: "検索設定"
|
||||||
searchSettingCaption: "検索条件を詳細に設定します。"
|
searchSettingCaption: "検索条件を詳細に設定します。"
|
||||||
|
searchLimit: "表示件数"
|
||||||
sortOrder: "並び順"
|
sortOrder: "並び順"
|
||||||
registrationLogs: "登録ログ"
|
registrationLogs: "登録ログ"
|
||||||
registrationLogsCaption: "絵文字更新・削除時のログが表示されます。更新・削除操作を行ったり、ページを遷移・リロードすると消えます。"
|
registrationLogsCaption: "絵文字更新・削除時のログが表示されます。更新・削除操作を行ったり、ページを遷移・リロードすると消えます。"
|
||||||
alertEmojisRegisterFailedTitle: "エラー"
|
|
||||||
alertEmojisRegisterFailedDescription: "絵文字の更新・削除に失敗しました。詳細は登録ログをご確認ください。"
|
alertEmojisRegisterFailedDescription: "絵文字の更新・削除に失敗しました。詳細は登録ログをご確認ください。"
|
||||||
_logs:
|
_logs:
|
||||||
showSuccessLogSwitch: "成功ログを表示"
|
showSuccessLogSwitch: "成功ログを表示"
|
||||||
@ -2852,10 +2852,12 @@ _customEmojisManager:
|
|||||||
markAsDeleteTargetRanges: "選択範囲の行を削除対象にする"
|
markAsDeleteTargetRanges: "選択範囲の行を削除対象にする"
|
||||||
alertUpdateEmojisNothingDescription: "変更された絵文字はありません。"
|
alertUpdateEmojisNothingDescription: "変更された絵文字はありません。"
|
||||||
alertDeleteEmojisNothingDescription: "削除対象の絵文字はありません。"
|
alertDeleteEmojisNothingDescription: "削除対象の絵文字はありません。"
|
||||||
confirmUpdateEmojisTitle: "確認"
|
confirmMovePage: "ページを移動しますか?"
|
||||||
|
confirmChangeView: "表示を変更しますか?"
|
||||||
confirmUpdateEmojisDescription: "{count}個の絵文字を更新します。実行しますか?"
|
confirmUpdateEmojisDescription: "{count}個の絵文字を更新します。実行しますか?"
|
||||||
confirmDeleteEmojisTitle: "確認"
|
|
||||||
confirmDeleteEmojisDescription: "チェックがつけられた{count}個の絵文字を削除します。実行しますか?"
|
confirmDeleteEmojisDescription: "チェックがつけられた{count}個の絵文字を削除します。実行しますか?"
|
||||||
|
confirmResetDescription: "今までに加えた変更がすべてリセットされます。"
|
||||||
|
confirmMovePageDesciption: "このページの絵文字に変更が加えられています。\n保存せずにこのままページを移動すると、このページで加えた変更はすべて破棄されます。"
|
||||||
dialogSelectRoleTitle: "絵文字に設定されたロールで検索"
|
dialogSelectRoleTitle: "絵文字に設定されたロールで検索"
|
||||||
_register:
|
_register:
|
||||||
uploadSettingTitle: "アップロード設定"
|
uploadSettingTitle: "アップロード設定"
|
||||||
@ -2866,11 +2868,8 @@ _customEmojisManager:
|
|||||||
emojiInputAreaList1: "この枠に画像ファイルまたはディレクトリをドラッグ&ドロップ"
|
emojiInputAreaList1: "この枠に画像ファイルまたはディレクトリをドラッグ&ドロップ"
|
||||||
emojiInputAreaList2: "このリンクをクリックしてPCから選択する"
|
emojiInputAreaList2: "このリンクをクリックしてPCから選択する"
|
||||||
emojiInputAreaList3: "このリンクをクリックしてドライブから選択する"
|
emojiInputAreaList3: "このリンクをクリックしてドライブから選択する"
|
||||||
confirmRegisterEmojisTitle: "確認"
|
|
||||||
confirmRegisterEmojisDescription: "リストに表示されている絵文字を新たなカスタム絵文字として登録します。よろしいですか?(負荷を避けるため、一度の操作で登録可能な絵文字は{count}件までです)"
|
confirmRegisterEmojisDescription: "リストに表示されている絵文字を新たなカスタム絵文字として登録します。よろしいですか?(負荷を避けるため、一度の操作で登録可能な絵文字は{count}件までです)"
|
||||||
confirmClearEmojisTitle: "確認"
|
|
||||||
confirmClearEmojisDescription: "編集内容を破棄し、リストに表示されている絵文字をクリアします。よろしいですか?"
|
confirmClearEmojisDescription: "編集内容を破棄し、リストに表示されている絵文字をクリアします。よろしいですか?"
|
||||||
confirmUploadEmojisTitle: "確認"
|
|
||||||
confirmUploadEmojisDescription: "ドラッグ&ドロップされた{count}個のファイルをドライブにアップロードします。実行しますか?"
|
confirmUploadEmojisDescription: "ドラッグ&ドロップされた{count}個のファイルをドライブにアップロードします。実行しますか?"
|
||||||
|
|
||||||
_embedCodeGen:
|
_embedCodeGen:
|
||||||
|
@ -12,12 +12,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
:iconClass="order.direction === '+' ? 'ti ti-arrow-up' : 'ti ti-arrow-down'"
|
:iconClass="order.direction === '+' ? 'ti ti-arrow-up' : 'ti ti-arrow-down'"
|
||||||
:exButtonIconClass="'ti ti-x'"
|
:exButtonIconClass="'ti ti-x'"
|
||||||
:content="order.key"
|
:content="order.key"
|
||||||
|
:class="$style.sortOrderTag"
|
||||||
@click="onToggleSortOrderButtonClicked(order)"
|
@click="onToggleSortOrderButtonClicked(order)"
|
||||||
@exButtonClick="onRemoveSortOrderButtonClicked(order)"
|
@exButtonClick="onRemoveSortOrderButtonClicked(order)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<MkButton :class="$style.sortOrderAddButton" @click="onAddSortOrderButtonClicked">
|
<MkButton :class="$style.sortOrderAddButton" @click="onAddSortOrderButtonClicked">
|
||||||
<span class="ti ti-plus"/>
|
<span class="ti ti-plus"></span>
|
||||||
</MkButton>
|
</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -109,4 +110,9 @@ function emitOrder(sortOrders: SortOrder<T>[]) {
|
|||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
background-color: var(--MI_THEME-buttonBg);
|
background-color: var(--MI_THEME-buttonBg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sortOrderTag {
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -47,7 +47,7 @@ export type SuperMenuDef = {
|
|||||||
active?: boolean;
|
active?: boolean;
|
||||||
action: (ev: MouseEvent) => void;
|
action: (ev: MouseEvent) => void;
|
||||||
} | {
|
} | {
|
||||||
type: 'link';
|
type?: 'link';
|
||||||
to: string;
|
to: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
text: string;
|
text: string;
|
||||||
|
@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.root" @click="(ev) => emit('click', ev)">
|
<div :class="$style.root" @click="(ev) => emit('click', ev)">
|
||||||
<span v-if="iconClass" :class="[$style.icon, iconClass]"/>
|
<span v-if="iconClass" :class="[$style.icon, iconClass]"></span>
|
||||||
<span :class="$style.content">{{ content }}</span>
|
<span :class="$style.content">{{ content }}</span>
|
||||||
<MkButton v-if="exButtonIconClass" :class="$style.exButton" @click="(ev) => emit('exButtonClick', ev)">
|
<MkButton v-if="exButtonIconClass" :class="$style.exButton" @click="(ev) => emit('exButtonClick', ev)">
|
||||||
<span :class="[$style.exButtonIcon, exButtonIconClass]"/>
|
<span :class="[$style.exButtonIcon, exButtonIconClass]"></span>
|
||||||
</MkButton>
|
</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -48,13 +48,16 @@ import { scrollToTop } from '@@/js/scroll.js';
|
|||||||
import { globalEvents } from '@/events.js';
|
import { globalEvents } from '@/events.js';
|
||||||
import { injectReactiveMetadata } from '@/scripts/page-metadata.js';
|
import { injectReactiveMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
|
import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
|
||||||
import { PageHeaderItem } from '@/types/page-header.js';
|
import type { PageHeaderItem } from '@/types/page-header.js';
|
||||||
|
import type { PageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
|
overridePageMetadata?: PageMetadata;
|
||||||
tabs?: Tab[];
|
tabs?: Tab[];
|
||||||
tab?: string;
|
tab?: string;
|
||||||
actions?: PageHeaderItem[] | null;
|
actions?: PageHeaderItem[] | null;
|
||||||
thin?: boolean;
|
thin?: boolean;
|
||||||
|
hideTitle?: boolean;
|
||||||
displayMyAvatar?: boolean;
|
displayMyAvatar?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
tabs: () => ([] as Tab[]),
|
tabs: () => ([] as Tab[]),
|
||||||
@ -64,9 +67,10 @@ const emit = defineEmits<{
|
|||||||
(ev: 'update:tab', key: string);
|
(ev: 'update:tab', key: string);
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const pageMetadata = injectReactiveMetadata();
|
const injectedPageMetadata = injectReactiveMetadata();
|
||||||
|
const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMetadata.value);
|
||||||
|
|
||||||
const hideTitle = inject('shouldOmitHeaderTitle', false);
|
const hideTitle = computed(() => inject('shouldOmitHeaderTitle', false) || props.hideTitle);
|
||||||
const thin_ = props.thin || inject('shouldHeaderThin', false);
|
const thin_ = props.thin || inject('shouldHeaderThin', false);
|
||||||
|
|
||||||
const el = shallowRef<HTMLElement | undefined>(undefined);
|
const el = shallowRef<HTMLElement | undefined>(undefined);
|
||||||
@ -75,7 +79,7 @@ const narrow = ref(false);
|
|||||||
const hasTabs = computed(() => props.tabs.length > 0);
|
const hasTabs = computed(() => props.tabs.length > 0);
|
||||||
const hasActions = computed(() => props.actions && props.actions.length > 0);
|
const hasActions = computed(() => props.actions && props.actions.length > 0);
|
||||||
const show = computed(() => {
|
const show = computed(() => {
|
||||||
return !hideTitle || hasTabs.value || hasActions.value;
|
return !hideTitle.value || hasTabs.value || hasActions.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const preventDrag = (ev: TouchEvent) => {
|
const preventDrag = (ev: TouchEvent) => {
|
||||||
|
@ -39,13 +39,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
{{ cell.value }}
|
{{ cell.value }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="cellType === 'boolean'">
|
<div v-else-if="cellType === 'boolean'">
|
||||||
<span v-if="cell.value === true" class="ti ti-check"/>
|
<div :class="[$style.bool, {
|
||||||
<span v-else class="ti"/>
|
[$style.boolTrue]: cell.value === true,
|
||||||
|
'ti ti-check': cell.value === true,
|
||||||
|
}]"></div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="cellType === 'image'">
|
<div v-else-if="cellType === 'image'">
|
||||||
<img
|
<img
|
||||||
:src="cell.value as string"
|
:src="cell.value"
|
||||||
:alt="cell.value as string"
|
:alt="cell.value"
|
||||||
:class="$style.viewImage"
|
:class="$style.viewImage"
|
||||||
@load="emitContentSizeChanged"
|
@load="emitContentSizeChanged"
|
||||||
/>
|
/>
|
||||||
@ -375,6 +377,31 @@ $cellHeight: 28px;
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bool {
|
||||||
|
position: relative;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
background: var(--MI_THEME-panel);
|
||||||
|
border: solid 2px var(--MI_THEME-divider);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&.boolTrue {
|
||||||
|
border-color: var(--MI_THEME-accent);
|
||||||
|
background: var(--MI_THEME-accent);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
color: var(--MI_THEME-fgOnAccent);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.editingInput {
|
.editingInput {
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -7,7 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div
|
<div
|
||||||
ref="rootEl"
|
ref="rootEl"
|
||||||
class="mk_grid_border"
|
class="mk_grid_border"
|
||||||
:class="[$style.grid]"
|
:class="[$style.grid, {
|
||||||
|
[$style.noOverflowHandling]: rootSetting.noOverflowStyle,
|
||||||
|
'mk_grid_root_rounded': rootSetting.rounded,
|
||||||
|
'mk_grid_root_border': rootSetting.outerBorder,
|
||||||
|
}]"
|
||||||
@mousedown.prevent="onMouseDown"
|
@mousedown.prevent="onMouseDown"
|
||||||
@keydown="onKeyDown"
|
@keydown="onKeyDown"
|
||||||
@contextmenu.prevent.stop="onContextMenu"
|
@contextmenu.prevent.stop="onContextMenu"
|
||||||
@ -77,10 +81,17 @@ const emit = defineEmits<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
settings: GridSetting,
|
settings: GridSetting;
|
||||||
data: DataSource[]
|
data: DataSource[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const rootSetting: Required<GridSetting['root']> = {
|
||||||
|
noOverflowStyle: false,
|
||||||
|
rounded: true,
|
||||||
|
outerBorder: true,
|
||||||
|
...props.settings.root,
|
||||||
|
};
|
||||||
|
|
||||||
// non-reactive
|
// non-reactive
|
||||||
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
|
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
|
||||||
const rowSetting: Required<GridRowSetting> = {
|
const rowSetting: Required<GridRowSetting> = {
|
||||||
@ -1277,32 +1288,48 @@ onMounted(() => {
|
|||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
// firefoxだとスクロールバーがセルに重なって見づらくなってしまうのでスペースを空けておく
|
// firefoxだとスクロールバーがセルに重なって見づらくなってしまうのでスペースを空けておく
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
|
|
||||||
|
&.noOverflowHandling {
|
||||||
|
overflow-x: revert;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
$borderSetting: solid 0.5px var(--MI_THEME-divider);
|
$borderSetting: solid 0.5px var(--MI_THEME-divider);
|
||||||
$borderRadius: var(--MI-radius);
|
|
||||||
|
|
||||||
// 配下コンポーネントを含めて一括してコントロールするため、scopedもmoduleも使用できない
|
// 配下コンポーネントを含めて一括してコントロールするため、scopedもmoduleも使用できない
|
||||||
.mk_grid_border {
|
.mk_grid_border {
|
||||||
|
--rootBorderSetting: none;
|
||||||
|
--borderRadius: 0;
|
||||||
|
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
|
|
||||||
|
&.mk_grid_root_border {
|
||||||
|
--rootBorderSetting: #{$borderSetting};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mk_grid_root_rounded {
|
||||||
|
--borderRadius: var(--MI-radius);
|
||||||
|
}
|
||||||
|
|
||||||
.mk_grid_thead {
|
.mk_grid_thead {
|
||||||
.mk_grid_tr {
|
.mk_grid_tr {
|
||||||
.mk_grid_th {
|
.mk_grid_th {
|
||||||
border-left: $borderSetting;
|
border-left: $borderSetting;
|
||||||
border-top: $borderSetting;
|
border-top: var(--rootBorderSetting);
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
// 左上セル
|
// 左上セル
|
||||||
border-top-left-radius: $borderRadius;
|
border-left: var(--rootBorderSetting);
|
||||||
|
border-top-left-radius: var(--borderRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
// 右上セル
|
// 右上セル
|
||||||
border-top-right-radius: $borderRadius;
|
border-top-right-radius: var(--borderRadius);
|
||||||
border-right: $borderSetting;
|
border-right: var(--rootBorderSetting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1314,9 +1341,14 @@ $borderRadius: var(--MI-radius);
|
|||||||
border-left: $borderSetting;
|
border-left: $borderSetting;
|
||||||
border-top: $borderSetting;
|
border-top: $borderSetting;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
// 左端の列
|
||||||
|
border-left: var(--rootBorderSetting);
|
||||||
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
// 一番右端の列
|
// 一番右端の列
|
||||||
border-right: $borderSetting;
|
border-right: var(--rootBorderSetting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1324,16 +1356,16 @@ $borderRadius: var(--MI-radius);
|
|||||||
.last_row {
|
.last_row {
|
||||||
.mk_grid_td, .mk_grid_th {
|
.mk_grid_td, .mk_grid_th {
|
||||||
// 一番下の行
|
// 一番下の行
|
||||||
border-bottom: $borderSetting;
|
border-bottom: var(--rootBorderSetting);
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
// 左下セル
|
// 左下セル
|
||||||
border-bottom-left-radius: $borderRadius;
|
border-bottom-left-radius: var(--borderRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
// 右下セル
|
// 右下セル
|
||||||
border-bottom-right-radius: $borderRadius;
|
border-bottom-right-radius: var(--borderRadius);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
:data-grid-cell-col="column.index"
|
:data-grid-cell-col="column.index"
|
||||||
>
|
>
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<div :class="$style.left"/>
|
<div :class="$style.left"></div>
|
||||||
<div :class="$style.wrapper">
|
<div :class="$style.wrapper">
|
||||||
<div ref="contentEl" :class="$style.contentArea">
|
<div ref="contentEl" :class="$style.contentArea">
|
||||||
<span v-if="column.setting.icon" class="ti" :class="column.setting.icon" style="line-height: normal"/>
|
<span v-if="column.setting.icon" class="ti" :class="column.setting.icon" style="line-height: normal"></span>
|
||||||
<span v-else>{{ text }}</span>
|
<span v-else>{{ text }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
:class="$style.right"
|
:class="$style.right"
|
||||||
@mousedown="onHandleMouseDown"
|
@mousedown="onHandleMouseDown"
|
||||||
@dblclick="onHandleDoubleClick"
|
@dblclick="onHandleDoubleClick"
|
||||||
/>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -9,6 +9,11 @@ import { GridColumnSetting } from '@/components/grid/column.js';
|
|||||||
import { GridRowSetting } from '@/components/grid/row.js';
|
import { GridRowSetting } from '@/components/grid/row.js';
|
||||||
|
|
||||||
export type GridSetting = {
|
export type GridSetting = {
|
||||||
|
root?: {
|
||||||
|
noOverflowStyle?: boolean;
|
||||||
|
rounded?: boolean;
|
||||||
|
outerBorder?: boolean;
|
||||||
|
};
|
||||||
row?: GridRowSetting;
|
row?: GridRowSetting;
|
||||||
cols: GridColumnSetting[];
|
cols: GridColumnSetting[];
|
||||||
cells?: GridCellSetting;
|
cells?: GridCellSetting;
|
||||||
|
@ -22,7 +22,8 @@ export const gridSortOrderKeys = [
|
|||||||
'isSensitive',
|
'isSensitive',
|
||||||
'localOnly',
|
'localOnly',
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
];
|
] as const satisfies string[];
|
||||||
|
|
||||||
export type GridSortOrderKey = typeof gridSortOrderKeys[number];
|
export type GridSortOrderKey = typeof gridSortOrderKeys[number];
|
||||||
|
|
||||||
export function emptyStrToUndefined(value: string | null) {
|
export function emptyStrToUndefined(value: string | null) {
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<MkWindow
|
||||||
|
ref="uiWindow"
|
||||||
|
:initialWidth="400"
|
||||||
|
:initialHeight="500"
|
||||||
|
:canResize="true"
|
||||||
|
@closed="emit('closed')"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<i class="ti ti-notes" style="margin-right: 0.5em;"></i> {{ i18n.ts._customEmojisManager._gridCommon.registrationLogs }}
|
||||||
|
</template>
|
||||||
|
<MkSpacer>
|
||||||
|
<XRegisterLogs :logs="logs"/>
|
||||||
|
</MkSpacer>
|
||||||
|
</MkWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import MkWindow from '@/components/MkWindow.vue';
|
||||||
|
import XRegisterLogs from '@/pages/admin/custom-emojis-manager.logs.vue';
|
||||||
|
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
import type { RequestLogItem } from './custom-emojis-manager.impl.js';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
logs: RequestLogItem[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'closed'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
</script>
|
@ -0,0 +1,213 @@
|
|||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<MkWindow
|
||||||
|
ref="uiWindow"
|
||||||
|
:initialWidth="400"
|
||||||
|
:initialHeight="500"
|
||||||
|
:canResize="true"
|
||||||
|
@closed="emit('closed')"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<i class="ti ti-search" style="margin-right: 0.5em;"></i> {{ i18n.ts.search }}
|
||||||
|
</template>
|
||||||
|
<div :class="$style.root">
|
||||||
|
<MkSpacer>
|
||||||
|
<div class="_gaps">
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<MkInput
|
||||||
|
v-model="model.name"
|
||||||
|
type="search"
|
||||||
|
autocapitalize="off"
|
||||||
|
>
|
||||||
|
<template #label>name</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkInput
|
||||||
|
v-model="model.category"
|
||||||
|
type="search"
|
||||||
|
autocapitalize="off"
|
||||||
|
>
|
||||||
|
<template #label>category</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkInput
|
||||||
|
v-model="model.aliases"
|
||||||
|
type="search"
|
||||||
|
autocapitalize="off"
|
||||||
|
>
|
||||||
|
<template #label>aliases</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
|
<MkInput
|
||||||
|
v-model="model.type"
|
||||||
|
type="search"
|
||||||
|
autocapitalize="off"
|
||||||
|
>
|
||||||
|
<template #label>type</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkInput
|
||||||
|
v-model="model.license"
|
||||||
|
type="search"
|
||||||
|
autocapitalize="off"
|
||||||
|
>
|
||||||
|
<template #label>license</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkSelect
|
||||||
|
v-model="model.sensitive"
|
||||||
|
>
|
||||||
|
<template #label>sensitive</template>
|
||||||
|
<option :value="null">-</option>
|
||||||
|
<option :value="true">true</option>
|
||||||
|
<option :value="false">false</option>
|
||||||
|
</MkSelect>
|
||||||
|
|
||||||
|
<MkSelect
|
||||||
|
v-model="model.localOnly"
|
||||||
|
>
|
||||||
|
<template #label>localOnly</template>
|
||||||
|
<option :value="null">-</option>
|
||||||
|
<option :value="true">true</option>
|
||||||
|
<option :value="false">false</option>
|
||||||
|
</MkSelect>
|
||||||
|
<MkInput
|
||||||
|
v-model="model.updatedAtFrom"
|
||||||
|
type="date"
|
||||||
|
autocapitalize="off"
|
||||||
|
>
|
||||||
|
<template #label>updatedAt(from)</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkInput
|
||||||
|
v-model="model.updatedAtTo"
|
||||||
|
type="date"
|
||||||
|
autocapitalize="off"
|
||||||
|
>
|
||||||
|
<template #label>updatedAt(to)</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
|
<MkInput
|
||||||
|
v-model="queryRolesText"
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
autocapitalize="off"
|
||||||
|
@click="onQueryRolesEditClicked"
|
||||||
|
>
|
||||||
|
<template #label>role</template>
|
||||||
|
<template #suffix><i class="ti ti-pencil"></i></template>
|
||||||
|
</MkInput>
|
||||||
|
</div>
|
||||||
|
<MkFolder :spacerMax="8" :spacerMin="8">
|
||||||
|
<template #icon><i class="ti ti-arrows-sort"></i></template>
|
||||||
|
<template #label>{{ i18n.ts._customEmojisManager._gridCommon.sortOrder }}</template>
|
||||||
|
<MkSortOrderEditor
|
||||||
|
:baseOrderKeyNames="gridSortOrderKeys"
|
||||||
|
:currentOrders="sortOrders"
|
||||||
|
@update="onSortOrderUpdate"
|
||||||
|
/>
|
||||||
|
</MkFolder>
|
||||||
|
</div>
|
||||||
|
</MkSpacer>
|
||||||
|
<div :class="$style.footerActions">
|
||||||
|
<MkButton primary @click="onSearchRequest">
|
||||||
|
{{ i18n.ts.search }}
|
||||||
|
</MkButton>
|
||||||
|
<MkButton @click="onQueryResetButtonClicked">
|
||||||
|
{{ i18n.ts.reset }}
|
||||||
|
</MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MkWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import MkWindow from '@/components/MkWindow.vue';
|
||||||
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
import MkSortOrderEditor from '@/components/MkSortOrderEditor.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
gridSortOrderKeys,
|
||||||
|
} from './custom-emojis-manager.impl.js';
|
||||||
|
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
|
||||||
|
import type { EmojiSearchQuery } from './custom-emojis-manager.local.list.vue';
|
||||||
|
import type { SortOrder } from '@/components/MkSortOrderEditor.define.js';
|
||||||
|
import type { GridSortOrderKey } from './custom-emojis-manager.impl.js';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
query: EmojiSearchQuery;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'closed'): void;
|
||||||
|
(ev: 'queryUpdated', query: EmojiSearchQuery): void;
|
||||||
|
(ev: 'sortOrderUpdated', orders: SortOrder<GridSortOrderKey>[]): void;
|
||||||
|
(ev: 'search'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const model = ref<EmojiSearchQuery>(props.query);
|
||||||
|
const queryRolesText = computed(() => model.value.roles.map(it => it.name).join(','));
|
||||||
|
|
||||||
|
watch(model, () => {
|
||||||
|
emit('queryUpdated', model.value);
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
|
const sortOrders = ref<SortOrder<GridSortOrderKey>[]>([]);
|
||||||
|
|
||||||
|
function onSortOrderUpdate(orders: SortOrder<GridSortOrderKey>[]) {
|
||||||
|
sortOrders.value = orders;
|
||||||
|
emit('sortOrderUpdated', orders);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSearchRequest() {
|
||||||
|
emit('search');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onQueryResetButtonClicked() {
|
||||||
|
model.value.name = '';
|
||||||
|
model.value.category = '';
|
||||||
|
model.value.aliases = '';
|
||||||
|
model.value.type = '';
|
||||||
|
model.value.license = '';
|
||||||
|
model.value.sensitive = null;
|
||||||
|
model.value.localOnly = null;
|
||||||
|
model.value.updatedAtFrom = '';
|
||||||
|
model.value.updatedAtTo = '';
|
||||||
|
sortOrders.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onQueryRolesEditClicked() {
|
||||||
|
const result = await os.selectRole({
|
||||||
|
initialRoleIds: model.value.roles.map(it => it.id),
|
||||||
|
title: i18n.ts._customEmojisManager._local._list.dialogSelectRoleTitle,
|
||||||
|
publicOnly: true,
|
||||||
|
});
|
||||||
|
if (result.canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
model.value.roles = result.result;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style module>
|
||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerActions {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
padding: var(--MI-margin);
|
||||||
|
background-color: var(--MI_THEME-bg);
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
</style>
|
@ -5,137 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #default>
|
<template #header>
|
||||||
<div class="_gaps">
|
<MkPageHeader :overridePageMetadata="headerPageMetadata" :actions="headerActions"/>
|
||||||
<MkFolder>
|
|
||||||
<template #icon><i class="ti ti-search"></i></template>
|
|
||||||
<template #label>{{ i18n.ts._customEmojisManager._gridCommon.searchSettings }}</template>
|
|
||||||
<template #caption>
|
|
||||||
{{ i18n.ts._customEmojisManager._gridCommon.searchSettingCaption }}
|
|
||||||
</template>
|
</template>
|
||||||
|
<template #default>
|
||||||
<div class="_gaps">
|
<div class="_gaps" :class="$style.main">
|
||||||
<div :class="[[spMode ? $style.searchAreaSp : $style.searchArea]]">
|
|
||||||
<MkInput
|
|
||||||
v-model="queryName"
|
|
||||||
type="search"
|
|
||||||
autocapitalize="off"
|
|
||||||
:class="[$style.col1, $style.row1]"
|
|
||||||
@enter="onSearchRequest"
|
|
||||||
>
|
|
||||||
<template #label>name</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkInput
|
|
||||||
v-model="queryCategory"
|
|
||||||
type="search"
|
|
||||||
autocapitalize="off"
|
|
||||||
:class="[$style.col2, $style.row1]"
|
|
||||||
@enter="onSearchRequest"
|
|
||||||
>
|
|
||||||
<template #label>category</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkInput
|
|
||||||
v-model="queryAliases"
|
|
||||||
type="search"
|
|
||||||
autocapitalize="off"
|
|
||||||
:class="[$style.col3, $style.row1]"
|
|
||||||
@enter="onSearchRequest"
|
|
||||||
>
|
|
||||||
<template #label>aliases</template>
|
|
||||||
</MkInput>
|
|
||||||
|
|
||||||
<MkInput
|
|
||||||
v-model="queryType"
|
|
||||||
type="search"
|
|
||||||
autocapitalize="off"
|
|
||||||
:class="[$style.col1, $style.row2]"
|
|
||||||
@enter="onSearchRequest"
|
|
||||||
>
|
|
||||||
<template #label>type</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkInput
|
|
||||||
v-model="queryLicense"
|
|
||||||
type="search"
|
|
||||||
autocapitalize="off"
|
|
||||||
:class="[$style.col2, $style.row2]"
|
|
||||||
@enter="onSearchRequest"
|
|
||||||
>
|
|
||||||
<template #label>license</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkSelect
|
|
||||||
v-model="querySensitive"
|
|
||||||
:class="[$style.col3, $style.row2]"
|
|
||||||
>
|
|
||||||
<template #label>sensitive</template>
|
|
||||||
<option :value="null">-</option>
|
|
||||||
<option :value="true">true</option>
|
|
||||||
<option :value="false">false</option>
|
|
||||||
</MkSelect>
|
|
||||||
|
|
||||||
<MkSelect
|
|
||||||
v-model="queryLocalOnly"
|
|
||||||
:class="[$style.col1, $style.row3]"
|
|
||||||
>
|
|
||||||
<template #label>localOnly</template>
|
|
||||||
<option :value="null">-</option>
|
|
||||||
<option :value="true">true</option>
|
|
||||||
<option :value="false">false</option>
|
|
||||||
</MkSelect>
|
|
||||||
<MkInput
|
|
||||||
v-model="queryUpdatedAtFrom"
|
|
||||||
type="date"
|
|
||||||
autocapitalize="off"
|
|
||||||
:class="[$style.col2, $style.row3]"
|
|
||||||
@enter="onSearchRequest"
|
|
||||||
>
|
|
||||||
<template #label>updatedAt(from)</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkInput
|
|
||||||
v-model="queryUpdatedAtTo"
|
|
||||||
type="date"
|
|
||||||
autocapitalize="off"
|
|
||||||
:class="[$style.col3, $style.row3]"
|
|
||||||
@enter="onSearchRequest"
|
|
||||||
>
|
|
||||||
<template #label>updatedAt(to)</template>
|
|
||||||
</MkInput>
|
|
||||||
|
|
||||||
<MkInput
|
|
||||||
v-model="queryRolesText"
|
|
||||||
type="text"
|
|
||||||
readonly
|
|
||||||
autocapitalize="off"
|
|
||||||
:class="[$style.col1, $style.row4]"
|
|
||||||
@click="onQueryRolesEditClicked"
|
|
||||||
>
|
|
||||||
<template #label>role</template>
|
|
||||||
<template #suffix><span class="ti ti-pencil"/></template>
|
|
||||||
</MkInput>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<MkFolder :spacerMax="8" :spacerMin="8">
|
|
||||||
<template #icon><i class="ti ti-arrows-sort"></i></template>
|
|
||||||
<template #label>{{ i18n.ts._customEmojisManager._gridCommon.sortOrder }}</template>
|
|
||||||
<MkSortOrderEditor
|
|
||||||
:baseOrderKeyNames="gridSortOrderKeys"
|
|
||||||
:currentOrders="sortOrders"
|
|
||||||
@update="onSortOrderUpdate"
|
|
||||||
/>
|
|
||||||
</MkFolder>
|
|
||||||
|
|
||||||
<div :class="[[spMode ? $style.searchButtonsSp : $style.searchButtons]]">
|
|
||||||
<MkButton primary @click="onSearchRequest">
|
|
||||||
{{ i18n.ts.search }}
|
|
||||||
</MkButton>
|
|
||||||
<MkButton @click="onQueryResetButtonClicked">
|
|
||||||
{{ i18n.ts.reset }}
|
|
||||||
</MkButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</MkFolder>
|
|
||||||
|
|
||||||
<XRegisterLogsFolder :logs="requestLogs"/>
|
|
||||||
|
|
||||||
<component :is="loadingHandler.component.value" v-if="loadingHandler.showing.value"/>
|
<component :is="loadingHandler.component.value" v-if="loadingHandler.showing.value"/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div v-if="gridItems.length === 0" style="text-align: center">
|
<div v-if="gridItems.length === 0" style="text-align: center">
|
||||||
@ -143,11 +17,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div :class="$style.gridArea">
|
<div :class="$style.grid">
|
||||||
<MkGrid :data="gridItems" :settings="setupGrid()" @event="onGridEvent"/>
|
<MkGrid :data="gridItems" :settings="setupGrid()" @event="onGridEvent"/>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<div :class="$style.footer">
|
<template #footer>
|
||||||
|
<div v-if="gridItems.length > 0" :class="$style.footer">
|
||||||
<div :class="$style.left">
|
<div :class="$style.left">
|
||||||
<MkButton danger style="margin-right: auto" @click="onDeleteButtonClicked">
|
<MkButton danger style="margin-right: auto" @click="onDeleteButtonClicked">
|
||||||
{{ i18n.ts.delete }} ({{ deleteItemsCount }})
|
{{ i18n.ts.delete }} ({{ deleteItemsCount }})
|
||||||
@ -166,42 +45,50 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { SortOrder } from '@/components/MkSortOrderEditor.define.js';
|
||||||
|
import type { GridSortOrderKey } from './custom-emojis-manager.impl.js';
|
||||||
|
|
||||||
|
export type EmojiSearchQuery = {
|
||||||
|
name: string | null;
|
||||||
|
category: string | null;
|
||||||
|
aliases: string | null;
|
||||||
|
type: string | null;
|
||||||
|
license: string | null;
|
||||||
|
updatedAtFrom: string | null;
|
||||||
|
updatedAtTo: string | null;
|
||||||
|
sensitive: string | null;
|
||||||
|
localOnly: string | null;
|
||||||
|
roles: { id: string, name: string }[];
|
||||||
|
sortOrders: SortOrder<GridSortOrderKey>[];
|
||||||
|
limit: number;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, useCssModule } from 'vue';
|
import { computed, defineAsyncComponent, onMounted, ref, nextTick, useCssModule } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import {
|
import {
|
||||||
emptyStrToEmptyArray,
|
emptyStrToEmptyArray,
|
||||||
emptyStrToNull,
|
emptyStrToNull,
|
||||||
emptyStrToUndefined,
|
emptyStrToUndefined,
|
||||||
GridSortOrderKey,
|
|
||||||
gridSortOrderKeys,
|
|
||||||
RequestLogItem,
|
RequestLogItem,
|
||||||
roleIdsParser,
|
roleIdsParser,
|
||||||
} from '@/pages/admin/custom-emojis-manager.impl.js';
|
} from '@/pages/admin/custom-emojis-manager.impl.js';
|
||||||
import MkGrid from '@/components/grid/MkGrid.vue';
|
import MkGrid from '@/components/grid/MkGrid.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { validators } from '@/components/grid/cell-validators.js';
|
import { validators } from '@/components/grid/cell-validators.js';
|
||||||
import { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
|
import { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import MkPagingButtons from '@/components/MkPagingButtons.vue';
|
import MkPagingButtons from '@/components/MkPagingButtons.vue';
|
||||||
import XRegisterLogsFolder from '@/pages/admin/custom-emojis-manager.logs-folder.vue';
|
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
|
||||||
import { deviceKind } from '@/scripts/device-kind.js';
|
|
||||||
import { GridSetting } from '@/components/grid/grid.js';
|
import { GridSetting } from '@/components/grid/grid.js';
|
||||||
import { selectFile } from '@/scripts/select-file.js';
|
import { selectFile } from '@/scripts/select-file.js';
|
||||||
import { copyGridDataToClipboard, removeDataFromGrid } from '@/components/grid/grid-utils.js';
|
import { copyGridDataToClipboard, removeDataFromGrid } from '@/components/grid/grid-utils.js';
|
||||||
import MkSortOrderEditor from '@/components/MkSortOrderEditor.vue';
|
|
||||||
import { SortOrder } from '@/components/MkSortOrderEditor.define.js';
|
|
||||||
import { useLoading } from "@/components/hook/useLoading.js";
|
import { useLoading } from "@/components/hook/useLoading.js";
|
||||||
|
|
||||||
type GridItem = {
|
type GridItem = {
|
||||||
@ -230,6 +117,11 @@ function setupGrid(): GridSetting {
|
|||||||
const regex = validators.regex(/^[a-zA-Z0-9_]+$/);
|
const regex = validators.regex(/^[a-zA-Z0-9_]+$/);
|
||||||
const unique = validators.unique();
|
const unique = validators.unique();
|
||||||
return {
|
return {
|
||||||
|
root: {
|
||||||
|
noOverflowStyle: true,
|
||||||
|
rounded: false,
|
||||||
|
outerBorder: false,
|
||||||
|
},
|
||||||
row: {
|
row: {
|
||||||
showNumber: true,
|
showNumber: true,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
@ -381,16 +273,22 @@ const customEmojis = ref<Misskey.entities.EmojiDetailedAdmin[]>([]);
|
|||||||
const allPages = ref<number>(0);
|
const allPages = ref<number>(0);
|
||||||
const currentPage = ref<number>(0);
|
const currentPage = ref<number>(0);
|
||||||
|
|
||||||
const queryName = ref<string | null>(null);
|
const searchQuery = ref<EmojiSearchQuery>({
|
||||||
const queryCategory = ref<string | null>(null);
|
name: null,
|
||||||
const queryAliases = ref<string | null>(null);
|
category: null,
|
||||||
const queryType = ref<string | null>(null);
|
aliases: null,
|
||||||
const queryLicense = ref<string | null>(null);
|
type: null,
|
||||||
const queryUpdatedAtFrom = ref<string | null>(null);
|
license: null,
|
||||||
const queryUpdatedAtTo = ref<string | null>(null);
|
updatedAtFrom: null,
|
||||||
const querySensitive = ref<string | null>(null);
|
updatedAtTo: null,
|
||||||
const queryLocalOnly = ref<string | null>(null);
|
sensitive: null,
|
||||||
const queryRoles = ref<{ id: string, name: string }[]>([]);
|
localOnly: null,
|
||||||
|
roles: [],
|
||||||
|
sortOrders: [],
|
||||||
|
limit: 25,
|
||||||
|
});
|
||||||
|
let searchWindowOpening = false;
|
||||||
|
|
||||||
const previousQuery = ref<string | undefined>(undefined);
|
const previousQuery = ref<string | undefined>(undefined);
|
||||||
const sortOrders = ref<SortOrder<GridSortOrderKey>[]>([]);
|
const sortOrders = ref<SortOrder<GridSortOrderKey>[]>([]);
|
||||||
const requestLogs = ref<RequestLogItem[]>([]);
|
const requestLogs = ref<RequestLogItem[]>([]);
|
||||||
@ -399,8 +297,6 @@ const gridItems = ref<GridItem[]>([]);
|
|||||||
const originGridItems = ref<GridItem[]>([]);
|
const originGridItems = ref<GridItem[]>([]);
|
||||||
const updateButtonDisabled = ref<boolean>(false);
|
const updateButtonDisabled = ref<boolean>(false);
|
||||||
|
|
||||||
const spMode = computed(() => ['smartphone', 'tablet'].includes(deviceKind));
|
|
||||||
const queryRolesText = computed(() => queryRoles.value.map(it => it.name).join(','));
|
|
||||||
const updatedItemsCount = computed(() => {
|
const updatedItemsCount = computed(() => {
|
||||||
return gridItems.value.filter((it, idx) => !it.checked && JSON.stringify(it) !== JSON.stringify(originGridItems.value[idx])).length;
|
return gridItems.value.filter((it, idx) => !it.checked && JSON.stringify(it) !== JSON.stringify(originGridItems.value[idx])).length;
|
||||||
});
|
});
|
||||||
@ -422,12 +318,11 @@ async function onUpdateButtonClicked() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirm = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
title: i18n.ts._customEmojisManager._local._list.confirmUpdateEmojisTitle,
|
|
||||||
text: i18n.tsx._customEmojisManager._local._list.confirmUpdateEmojisDescription({ count: updatedItems.length }),
|
text: i18n.tsx._customEmojisManager._local._list.confirmUpdateEmojisDescription({ count: updatedItems.length }),
|
||||||
});
|
});
|
||||||
if (confirm.canceled) {
|
if (canceled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,7 +353,7 @@ async function onUpdateButtonClicked() {
|
|||||||
if (failedItems.length > 0) {
|
if (failedItems.length > 0) {
|
||||||
await os.alert({
|
await os.alert({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
title: i18n.ts._customEmojisManager._gridCommon.alertEmojisRegisterFailedTitle,
|
title: i18n.ts.somethingHappened,
|
||||||
text: i18n.ts._customEmojisManager._gridCommon.alertEmojisRegisterFailedDescription,
|
text: i18n.ts._customEmojisManager._gridCommon.alertEmojisRegisterFailedDescription,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -489,12 +384,11 @@ async function onDeleteButtonClicked() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirm = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
title: i18n.ts._customEmojisManager._local._list.confirmDeleteEmojisTitle,
|
|
||||||
text: i18n.tsx._customEmojisManager._local._list.confirmDeleteEmojisDescription({ count: deleteItems.length }),
|
text: i18n.tsx._customEmojisManager._local._list.confirmDeleteEmojisDescription({ count: deleteItems.length }),
|
||||||
});
|
});
|
||||||
if (confirm.canceled) {
|
if (canceled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -508,47 +402,35 @@ async function onDeleteButtonClicked() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onGridResetButtonClicked() {
|
async function onGridResetButtonClicked() {
|
||||||
refreshGridItems();
|
const { canceled } = await os.confirm({
|
||||||
}
|
type: 'warning',
|
||||||
|
title: i18n.ts.resetAreYouSure,
|
||||||
async function onQueryRolesEditClicked() {
|
text: i18n.ts._customEmojisManager._local._list.confirmResetDescription,
|
||||||
const result = await os.selectRole({
|
|
||||||
initialRoleIds: queryRoles.value.map(it => it.id),
|
|
||||||
title: i18n.ts._customEmojisManager._local._list.dialogSelectRoleTitle,
|
|
||||||
publicOnly: true,
|
|
||||||
});
|
});
|
||||||
if (result.canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
queryRoles.value = result.result;
|
if (canceled) return;
|
||||||
}
|
|
||||||
|
|
||||||
function onSortOrderUpdate(_sortOrders: SortOrder<GridSortOrderKey>[]) {
|
refreshGridItems();
|
||||||
sortOrders.value = _sortOrders;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onSearchRequest() {
|
async function onSearchRequest() {
|
||||||
await refreshCustomEmojis();
|
await refreshCustomEmojis();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onQueryResetButtonClicked() {
|
|
||||||
queryName.value = null;
|
|
||||||
queryCategory.value = null;
|
|
||||||
queryAliases.value = null;
|
|
||||||
queryType.value = null;
|
|
||||||
queryLicense.value = null;
|
|
||||||
queryUpdatedAtFrom.value = null;
|
|
||||||
queryUpdatedAtTo.value = null;
|
|
||||||
querySensitive.value = null;
|
|
||||||
queryLocalOnly.value = null;
|
|
||||||
queryRoles.value = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onPageChanged(pageNumber: number) {
|
async function onPageChanged(pageNumber: number) {
|
||||||
|
if (updatedItemsCount.value > 0) {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
title: i18n.ts._customEmojisManager._local._list.confirmMovePage,
|
||||||
|
text: i18n.ts._customEmojisManager._local._list.confirmMovePageDesciption,
|
||||||
|
});
|
||||||
|
if (canceled) return;
|
||||||
|
}
|
||||||
|
|
||||||
currentPage.value = pageNumber;
|
currentPage.value = pageNumber;
|
||||||
await refreshCustomEmojis();
|
await nextTick();
|
||||||
|
refreshCustomEmojis();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onGridEvent(event: GridEvent) {
|
function onGridEvent(event: GridEvent) {
|
||||||
@ -574,19 +456,19 @@ function onGridCellValueChange(event: GridCellValueChangeEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function refreshCustomEmojis() {
|
async function refreshCustomEmojis() {
|
||||||
const limit = 100;
|
const limit = searchQuery.value.limit;
|
||||||
|
|
||||||
const query: Misskey.entities.V2AdminEmojiListRequest['query'] = {
|
const query: Misskey.entities.V2AdminEmojiListRequest['query'] = {
|
||||||
name: emptyStrToUndefined(queryName.value),
|
name: emptyStrToUndefined(searchQuery.value.name),
|
||||||
type: emptyStrToUndefined(queryType.value),
|
type: emptyStrToUndefined(searchQuery.value.type),
|
||||||
aliases: emptyStrToUndefined(queryAliases.value),
|
aliases: emptyStrToUndefined(searchQuery.value.aliases),
|
||||||
category: emptyStrToUndefined(queryCategory.value),
|
category: emptyStrToUndefined(searchQuery.value.category),
|
||||||
license: emptyStrToUndefined(queryLicense.value),
|
license: emptyStrToUndefined(searchQuery.value.license),
|
||||||
isSensitive: querySensitive.value ? Boolean(querySensitive.value).valueOf() : undefined,
|
isSensitive: searchQuery.value.sensitive ? Boolean(searchQuery.value.sensitive).valueOf() : undefined,
|
||||||
localOnly: queryLocalOnly.value ? Boolean(queryLocalOnly.value).valueOf() : undefined,
|
localOnly: searchQuery.value.localOnly ? Boolean(searchQuery.value.localOnly).valueOf() : undefined,
|
||||||
updatedAtFrom: emptyStrToUndefined(queryUpdatedAtFrom.value),
|
updatedAtFrom: emptyStrToUndefined(searchQuery.value.updatedAtFrom),
|
||||||
updatedAtTo: emptyStrToUndefined(queryUpdatedAtTo.value),
|
updatedAtTo: emptyStrToUndefined(searchQuery.value.updatedAtTo),
|
||||||
roleIds: queryRoles.value.map(it => it.id),
|
roleIds: searchQuery.value.roles.map(it => it.id),
|
||||||
hostType: 'local',
|
hostType: 'local',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -635,6 +517,83 @@ onMounted(async () => {
|
|||||||
await refreshCustomEmojis();
|
await refreshCustomEmojis();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const headerPageMetadata = computed(() => ({
|
||||||
|
title: i18n.ts._customEmojisManager._local.tabTitleList,
|
||||||
|
icon: 'ti ti-icons',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const headerActions = computed(() => [{
|
||||||
|
icon: 'ti ti-search',
|
||||||
|
text: i18n.ts.search,
|
||||||
|
handler: () => {
|
||||||
|
if (searchWindowOpening) return;
|
||||||
|
searchWindowOpening = true;
|
||||||
|
const { dispose } = os.popup(defineAsyncComponent(() => import('./custom-emojis-manager.local.list.search.vue')), {
|
||||||
|
query: searchQuery.value,
|
||||||
|
}, {
|
||||||
|
queryUpdated: (query: EmojiSearchQuery) => {
|
||||||
|
searchQuery.value = query;
|
||||||
|
},
|
||||||
|
sortOrderUpdated: (orders: SortOrder<GridSortOrderKey>[]) => {
|
||||||
|
sortOrders.value = orders;
|
||||||
|
},
|
||||||
|
search: () => {
|
||||||
|
onSearchRequest();
|
||||||
|
},
|
||||||
|
closed: () => {
|
||||||
|
dispose();
|
||||||
|
searchWindowOpening = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
icon: 'ti ti-list-numbers',
|
||||||
|
text: i18n.ts._customEmojisManager._gridCommon.searchLimit,
|
||||||
|
handler: (ev: MouseEvent) => {
|
||||||
|
async function changeSearchLimit(to: number) {
|
||||||
|
if (updatedItemsCount.value > 0) {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
title: i18n.ts._customEmojisManager._local._list.confirmChangeView,
|
||||||
|
text: i18n.ts._customEmojisManager._local._list.confirmMovePageDesciption,
|
||||||
|
});
|
||||||
|
if (canceled) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchQuery.value.limit = to;
|
||||||
|
refreshCustomEmojis();
|
||||||
|
}
|
||||||
|
|
||||||
|
os.popupMenu([{
|
||||||
|
type: 'radioOption',
|
||||||
|
text: '25',
|
||||||
|
active: computed(() => searchQuery.value.limit === 25),
|
||||||
|
action: () => changeSearchLimit(25),
|
||||||
|
}, {
|
||||||
|
type: 'radioOption',
|
||||||
|
text: '50',
|
||||||
|
active: computed(() => searchQuery.value.limit === 50),
|
||||||
|
action: () => changeSearchLimit(50),
|
||||||
|
}, {
|
||||||
|
type: 'radioOption',
|
||||||
|
text: '100',
|
||||||
|
active: computed(() => searchQuery.value.limit === 100),
|
||||||
|
action: () => changeSearchLimit(100),
|
||||||
|
}], ev.currentTarget ?? ev.target);
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
icon: 'ti ti-notes',
|
||||||
|
text: i18n.ts._customEmojisManager._gridCommon.registrationLogs,
|
||||||
|
handler: () => {
|
||||||
|
const { dispose } = os.popup(defineAsyncComponent(() => import('./custom-emojis-manager.local.list.logs.vue')), {
|
||||||
|
logs: requestLogs.value,
|
||||||
|
}, {
|
||||||
|
closed: () => {
|
||||||
|
dispose();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style module lang="scss">
|
<style module lang="scss">
|
||||||
@ -650,77 +609,21 @@ onMounted(async () => {
|
|||||||
background-color: var(--MI_THEME-infoBg);
|
background-color: var(--MI_THEME-infoBg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.row1 {
|
.main {
|
||||||
grid-row: 1 / 2;
|
height: calc(100vh - var(--MI-stickyTop) - var(--MI-stickyBottom));
|
||||||
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row2 {
|
.grid {
|
||||||
grid-row: 2 / 3;
|
width: max-content;
|
||||||
}
|
border-bottom: 1px solid var(--MI_THEME-divider);
|
||||||
|
|
||||||
.row3 {
|
|
||||||
grid-row: 3 / 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row4 {
|
|
||||||
grid-row: 4 / 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.col1 {
|
|
||||||
grid-column: 1 / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.col2 {
|
|
||||||
grid-column: 2 / 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.col3 {
|
|
||||||
grid-column: 3 / 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.searchArea {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.searchAreaSp {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.searchButtons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.searchButtonsSp {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gridArea {
|
|
||||||
padding-top: 8px;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
background-color: var(--MI_THEME-bg);
|
background-color: var(--MI_THEME-bg);
|
||||||
|
|
||||||
position: sticky;
|
padding: var(--MI-margin);
|
||||||
left:0;
|
border-top: 1px solid var(--MI_THEME-divider);
|
||||||
bottom:0;
|
|
||||||
z-index: 1;
|
|
||||||
// stickyで追従させる都合上、フッター自身でpaddingを持つ必要があるため、親要素で画一的に指定している分をネガティブマージンで相殺している
|
|
||||||
margin-top: calc(var(--MI-margin) * -1);
|
|
||||||
margin-bottom: calc(var(--MI-margin) * -1);
|
|
||||||
padding-top: var(--MI-margin);
|
|
||||||
padding-bottom: var(--MI-margin);
|
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
@ -30,7 +30,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
<XRegisterLogsFolder :logs="requestLogs"/>
|
<MkFolder>
|
||||||
|
<template #icon><i class="ti ti-notes"></i></template>
|
||||||
|
<template #label>{{ i18n.ts._customEmojisManager._gridCommon.registrationLogs }}</template>
|
||||||
|
<template #caption>
|
||||||
|
{{ i18n.ts._customEmojisManager._gridCommon.registrationLogsCaption }}
|
||||||
|
</template>
|
||||||
|
<XRegisterLogs :logs="requestLogs"/>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
:class="[$style.uploadBox, [isDragOver ? $style.dragOver : {}]]"
|
:class="[$style.uploadBox, [isDragOver ? $style.dragOver : {}]]"
|
||||||
@ -91,7 +98,7 @@ import { chooseFileFromDrive, chooseFileFromPc } from '@/scripts/select-file.js'
|
|||||||
import { uploadFile } from '@/scripts/upload.js';
|
import { uploadFile } from '@/scripts/upload.js';
|
||||||
import { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
|
import { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
|
||||||
import { DroppedFile, extractDroppedItems, flattenDroppedFiles } from '@/scripts/file-drop.js';
|
import { DroppedFile, extractDroppedItems, flattenDroppedFiles } from '@/scripts/file-drop.js';
|
||||||
import XRegisterLogsFolder from '@/pages/admin/custom-emojis-manager.logs-folder.vue';
|
import XRegisterLogs from '@/pages/admin/custom-emojis-manager.logs.vue';
|
||||||
import { GridSetting } from '@/components/grid/grid.js';
|
import { GridSetting } from '@/components/grid/grid.js';
|
||||||
import { copyGridDataToClipboard } from '@/components/grid/grid-utils.js';
|
import { copyGridDataToClipboard } from '@/components/grid/grid-utils.js';
|
||||||
import { GridRow } from '@/components/grid/row.js';
|
import { GridRow } from '@/components/grid/row.js';
|
||||||
@ -245,7 +252,6 @@ const isDragOver = ref<boolean>(false);
|
|||||||
async function onRegistryClicked() {
|
async function onRegistryClicked() {
|
||||||
const dialogSelection = await os.confirm({
|
const dialogSelection = await os.confirm({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
title: i18n.ts._customEmojisManager._local._register.confirmRegisterEmojisTitle,
|
|
||||||
text: i18n.tsx._customEmojisManager._local._register.confirmRegisterEmojisDescription({ count: MAXIMUM_EMOJI_REGISTER_COUNT }),
|
text: i18n.tsx._customEmojisManager._local._register.confirmRegisterEmojisDescription({ count: MAXIMUM_EMOJI_REGISTER_COUNT }),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -279,7 +285,7 @@ async function onRegistryClicked() {
|
|||||||
if (failedItems.length > 0) {
|
if (failedItems.length > 0) {
|
||||||
await os.alert({
|
await os.alert({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
title: i18n.ts._customEmojisManager._gridCommon.alertEmojisRegisterFailedTitle,
|
title: i18n.ts.somethingHappened,
|
||||||
text: i18n.ts._customEmojisManager._gridCommon.alertEmojisRegisterFailedDescription,
|
text: i18n.ts._customEmojisManager._gridCommon.alertEmojisRegisterFailedDescription,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -299,7 +305,6 @@ async function onRegistryClicked() {
|
|||||||
async function onClearClicked() {
|
async function onClearClicked() {
|
||||||
const result = await os.confirm({
|
const result = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
title: i18n.ts._customEmojisManager._local._register.confirmClearEmojisTitle,
|
|
||||||
text: i18n.ts._customEmojisManager._local._register.confirmClearEmojisDescription,
|
text: i18n.ts._customEmojisManager._local._register.confirmClearEmojisDescription,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -314,7 +319,6 @@ async function onDrop(ev: DragEvent) {
|
|||||||
const droppedFiles = await extractDroppedItems(ev).then(it => flattenDroppedFiles(it));
|
const droppedFiles = await extractDroppedItems(ev).then(it => flattenDroppedFiles(it));
|
||||||
const confirm = await os.confirm({
|
const confirm = await os.confirm({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
title: i18n.ts._customEmojisManager._local._register.confirmUploadEmojisTitle,
|
|
||||||
text: i18n.tsx._customEmojisManager._local._register.confirmUploadEmojisDescription({ count: droppedFiles.length }),
|
text: i18n.tsx._customEmojisManager._local._register.confirmUploadEmojisDescription({ count: droppedFiles.length }),
|
||||||
});
|
});
|
||||||
if (confirm.canceled) {
|
if (confirm.canceled) {
|
||||||
|
@ -4,33 +4,32 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="_gaps" :class="$style.root">
|
<MkStickyContainer>
|
||||||
<MkTab v-model="modeTab" style="margin-bottom: var(--margin);">
|
<template #header>
|
||||||
<option value="list">{{ i18n.ts._customEmojisManager._local.tabTitleList }}</option>
|
<MkPageHeader v-model:tab="headerTab" :tabs="headerTabs" hideTitle thin/>
|
||||||
<option value="register">{{ i18n.ts._customEmojisManager._local.tabTitleRegister }}</option>
|
</template>
|
||||||
</MkTab>
|
<XListComponent v-if="headerTab === 'list'" key="localList"/>
|
||||||
|
<MkSpacer v-else key="localRegister">
|
||||||
<div>
|
<XRegisterComponent/>
|
||||||
<XListComponent v-if="modeTab === 'list'"/>
|
</MkSpacer>
|
||||||
<XRegisterComponent v-else/>
|
</MkStickyContainer>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkTab from '@/components/MkTab.vue';
|
|
||||||
import XListComponent from '@/pages/admin/custom-emojis-manager.local.list.vue';
|
import XListComponent from '@/pages/admin/custom-emojis-manager.local.list.vue';
|
||||||
import XRegisterComponent from '@/pages/admin/custom-emojis-manager.local.register.vue';
|
import XRegisterComponent from '@/pages/admin/custom-emojis-manager.local.register.vue';
|
||||||
|
|
||||||
type PageMode = 'list' | 'register';
|
type PageMode = 'list' | 'register';
|
||||||
|
|
||||||
const modeTab = ref<PageMode>('list');
|
const headerTab = ref<PageMode>('list');
|
||||||
</script>
|
|
||||||
|
|
||||||
<style module lang="scss">
|
const headerTabs = computed(() => [{
|
||||||
.root {
|
key: 'list',
|
||||||
padding: var(--MI-margin);
|
title: i18n.ts._customEmojisManager._local.tabTitleList,
|
||||||
}
|
}, {
|
||||||
</style>
|
key: 'register',
|
||||||
|
title: i18n.ts._customEmojisManager._local.tabTitleRegister,
|
||||||
|
}]);
|
||||||
|
</script>
|
||||||
|
@ -4,14 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkFolder>
|
<div>
|
||||||
<template #icon><i class="ti ti-notes"></i></template>
|
|
||||||
<template #label>{{ i18n.ts._customEmojisManager._gridCommon.registrationLogs }}</template>
|
|
||||||
<template #caption>
|
|
||||||
{{ i18n.ts._customEmojisManager._gridCommon.registrationLogsCaption }}
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div v-if="logs.length > 0" style="display:flex; flex-direction: column; overflow-y: scroll; gap: 16px;">
|
<div v-if="logs.length > 0" style="display:flex; flex-direction: column; overflow-y: scroll; gap: 16px;">
|
||||||
<MkSwitch v-model="showingSuccessLogs">
|
<MkSwitch v-model="showingSuccessLogs">
|
||||||
<template #label>{{ i18n.ts._customEmojisManager._logs.showSuccessLogSwitch }}</template>
|
<template #label>{{ i18n.ts._customEmojisManager._logs.showSuccessLogSwitch }}</template>
|
||||||
@ -31,20 +24,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div v-else>
|
<div v-else>
|
||||||
{{ i18n.ts._customEmojisManager._logs.logNothing }}
|
{{ i18n.ts._customEmojisManager._logs.logNothing }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import { computed, ref, toRefs } from 'vue';
|
import { computed, ref, toRefs } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
|
|
||||||
import MkGrid from '@/components/grid/MkGrid.vue';
|
import MkGrid from '@/components/grid/MkGrid.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import { GridSetting } from '@/components/grid/grid.js';
|
|
||||||
import { copyGridDataToClipboard } from '@/components/grid/grid-utils.js';
|
import { copyGridDataToClipboard } from '@/components/grid/grid-utils.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
|
||||||
|
import type { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
|
||||||
|
import type { GridSetting } from '@/components/grid/grid.js';
|
||||||
|
|
||||||
function setupGrid(): GridSetting {
|
function setupGrid(): GridSetting {
|
||||||
return {
|
return {
|
||||||
@ -94,9 +85,4 @@ const filteredLogs = computed(() => {
|
|||||||
const forceShowing = showingSuccessLogs.value;
|
const forceShowing = showingSuccessLogs.value;
|
||||||
return logs.value.filter((log) => forceShowing || log.failed);
|
return logs.value.filter((log) => forceShowing || log.failed);
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style module lang="scss">
|
|
||||||
|
|
||||||
</style>
|
|
@ -64,6 +64,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</MkInput>
|
</MkInput>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
<MkFolder :spacerMax="8" :spacerMin="8">
|
<MkFolder :spacerMax="8" :spacerMin="8">
|
||||||
<template #icon><i class="ti ti-arrows-sort"></i></template>
|
<template #icon><i class="ti ti-arrows-sort"></i></template>
|
||||||
<template #label>{{ i18n.ts._customEmojisManager._gridCommon.sortOrder }}</template>
|
<template #label>{{ i18n.ts._customEmojisManager._gridCommon.sortOrder }}</template>
|
||||||
@ -74,6 +76,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
/>
|
/>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkInput
|
||||||
|
v-model="queryLimit"
|
||||||
|
type="number"
|
||||||
|
:max="100"
|
||||||
|
>
|
||||||
|
<template #label>{{ i18n.ts._customEmojisManager._gridCommon.searchLimit }}</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
<div :class="[[spMode ? $style.searchButtonsSp : $style.searchButtons]]">
|
<div :class="[[spMode ? $style.searchButtonsSp : $style.searchButtons]]">
|
||||||
<MkButton primary @click="onSearchRequest">
|
<MkButton primary @click="onSearchRequest">
|
||||||
{{ i18n.ts.search }}
|
{{ i18n.ts.search }}
|
||||||
@ -85,7 +95,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
<XRegisterLogsFolder :logs="requestLogs"/>
|
<MkFolder>
|
||||||
|
<template #icon><i class="ti ti-notes"></i></template>
|
||||||
|
<template #label>{{ i18n.ts._customEmojisManager._gridCommon.registrationLogs }}</template>
|
||||||
|
<template #caption>
|
||||||
|
{{ i18n.ts._customEmojisManager._gridCommon.registrationLogsCaption }}
|
||||||
|
</template>
|
||||||
|
<XRegisterLogs :logs="requestLogs"/>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<component :is="loadingHandler.component.value" v-if="loadingHandler.showing.value"/>
|
<component :is="loadingHandler.component.value" v-if="loadingHandler.showing.value"/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@ -139,7 +156,7 @@ import {
|
|||||||
} from '@/pages/admin/custom-emojis-manager.impl.js';
|
} from '@/pages/admin/custom-emojis-manager.impl.js';
|
||||||
import { GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
|
import { GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import XRegisterLogsFolder from '@/pages/admin/custom-emojis-manager.logs-folder.vue';
|
import XRegisterLogs from '@/pages/admin/custom-emojis-manager.logs.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { GridSetting } from '@/components/grid/grid.js';
|
import { GridSetting } from '@/components/grid/grid.js';
|
||||||
import { deviceKind } from '@/scripts/device-kind.js';
|
import { deviceKind } from '@/scripts/device-kind.js';
|
||||||
@ -246,6 +263,7 @@ const queryHost = ref<string | null>(null);
|
|||||||
const queryLicense = ref<string | null>(null);
|
const queryLicense = ref<string | null>(null);
|
||||||
const queryUri = ref<string | null>(null);
|
const queryUri = ref<string | null>(null);
|
||||||
const queryPublicUrl = ref<string | null>(null);
|
const queryPublicUrl = ref<string | null>(null);
|
||||||
|
const queryLimit = ref<number>(25);
|
||||||
const previousQuery = ref<string | undefined>(undefined);
|
const previousQuery = ref<string | undefined>(undefined);
|
||||||
const sortOrders = ref<SortOrder<GridSortOrderKey>[]>([]);
|
const sortOrders = ref<SortOrder<GridSortOrderKey>[]>([]);
|
||||||
const requestLogs = ref<RequestLogItem[]>([]);
|
const requestLogs = ref<RequestLogItem[]>([]);
|
||||||
@ -325,7 +343,7 @@ async function importEmojis(targets: GridItem[]) {
|
|||||||
if (failedItems.length > 0) {
|
if (failedItems.length > 0) {
|
||||||
await os.alert({
|
await os.alert({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
title: i18n.ts._customEmojisManager._gridCommon.alertEmojisRegisterFailedTitle,
|
title: i18n.ts.somethingHappened,
|
||||||
text: i18n.ts._customEmojisManager._gridCommon.alertEmojisRegisterFailedDescription,
|
text: i18n.ts._customEmojisManager._gridCommon.alertEmojisRegisterFailedDescription,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -355,7 +373,7 @@ async function refreshCustomEmojis() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await loadingHandler.scope(() => misskeyApi('v2/admin/emoji/list', {
|
const result = await loadingHandler.scope(() => misskeyApi('v2/admin/emoji/list', {
|
||||||
limit: 100,
|
limit: queryLimit.value,
|
||||||
query: query,
|
query: query,
|
||||||
page: currentPage.value,
|
page: currentPage.value,
|
||||||
sortKeys: sortOrders.value.map(({ key, direction }) => `${direction}${key}`) as never[],
|
sortKeys: sortOrders.value.map(({ key, direction }) => `${direction}${key}`) as never[],
|
||||||
|
@ -5,12 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- コンテナが入れ子になるのでz-indexが被らないよう大きめの数値を設定する-->
|
<MkStickyContainer>
|
||||||
<MkStickyContainer :headerZIndex="2000">
|
|
||||||
<template #header>
|
<template #header>
|
||||||
<MkPageHeader v-model:tab="headerTab" :tabs="headerTabs"/>
|
<MkPageHeader v-model:tab="headerTab" :tabs="headerTabs"/>
|
||||||
</template>
|
</template>
|
||||||
<XGridLocalComponent v-if="headerTab === 'local'"/>
|
<XGridLocalComponent v-if="headerTab === 'local'" :class="$style.local"/>
|
||||||
<XGridRemoteComponent v-else/>
|
<XGridRemoteComponent v-else/>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</div>
|
</div>
|
||||||
@ -40,5 +39,13 @@ const headerTabs = computed(() => [{
|
|||||||
definePageMetadata(computed(() => ({
|
definePageMetadata(computed(() => ({
|
||||||
title: i18n.ts.customEmojis,
|
title: i18n.ts.customEmojis,
|
||||||
icon: 'ti ti-icons',
|
icon: 'ti ti-icons',
|
||||||
|
needWideArea: true,
|
||||||
})));
|
})));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" module>
|
||||||
|
.local {
|
||||||
|
height: calc(100dvh - var(--MI-stickyTop) - var(--MI-stickyBottom));
|
||||||
|
overflow: clip;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -34,6 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
import { onActivated, onMounted, onUnmounted, provide, watch, ref, computed } from 'vue';
|
import { onActivated, onMounted, onUnmounted, provide, watch, ref, computed } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkSuperMenu from '@/components/MkSuperMenu.vue';
|
import MkSuperMenu from '@/components/MkSuperMenu.vue';
|
||||||
|
import type { SuperMenuDef } from '@/components/MkSuperMenu.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import { lookup } from '@/scripts/lookup.js';
|
import { lookup } from '@/scripts/lookup.js';
|
||||||
@ -55,7 +56,7 @@ const indexInfo = {
|
|||||||
|
|
||||||
provide('shouldOmitHeaderTitle', false);
|
provide('shouldOmitHeaderTitle', false);
|
||||||
|
|
||||||
const INFO = ref(indexInfo);
|
const INFO = ref<PageMetadata>(indexInfo);
|
||||||
const childInfo = ref<null | PageMetadata>(null);
|
const childInfo = ref<null | PageMetadata>(null);
|
||||||
const narrow = ref(false);
|
const narrow = ref(false);
|
||||||
const view = ref(null);
|
const view = ref(null);
|
||||||
@ -81,7 +82,7 @@ const ro = new ResizeObserver((entries, observer) => {
|
|||||||
narrow.value = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
|
narrow.value = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
|
||||||
});
|
});
|
||||||
|
|
||||||
const menuDef = computed(() => [{
|
const menuDef = computed<SuperMenuDef[]>(() => [{
|
||||||
title: i18n.ts.quickAction,
|
title: i18n.ts.quickAction,
|
||||||
items: [{
|
items: [{
|
||||||
type: 'button',
|
type: 'button',
|
||||||
@ -89,7 +90,7 @@ const menuDef = computed(() => [{
|
|||||||
text: i18n.ts.lookup,
|
text: i18n.ts.lookup,
|
||||||
action: adminLookup,
|
action: adminLookup,
|
||||||
}, ...(instance.disableRegistration ? [{
|
}, ...(instance.disableRegistration ? [{
|
||||||
type: 'button',
|
type: 'button' as const,
|
||||||
icon: 'ti ti-user-plus',
|
icon: 'ti ti-user-plus',
|
||||||
text: i18n.ts.createInviteCode,
|
text: i18n.ts.createInviteCode,
|
||||||
action: invite,
|
action: invite,
|
||||||
@ -333,12 +334,14 @@ defineExpose({
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
> .nav {
|
> .nav {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
width: 32%;
|
width: 32%;
|
||||||
max-width: 280px;
|
max-width: 280px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-right: solid 0.5px var(--MI_THEME-divider);
|
border-right: solid 0.5px var(--MI_THEME-divider);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
height: 100%;
|
height: 100dvh;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .main {
|
> .main {
|
||||||
|
@ -44,7 +44,7 @@ const indexInfo = {
|
|||||||
icon: 'ti ti-settings',
|
icon: 'ti ti-settings',
|
||||||
hideHeader: true,
|
hideHeader: true,
|
||||||
};
|
};
|
||||||
const INFO = ref(indexInfo);
|
const INFO = ref<PageMetadata>(indexInfo);
|
||||||
const el = shallowRef<HTMLElement | null>(null);
|
const el = shallowRef<HTMLElement | null>(null);
|
||||||
const childInfo = ref<null | PageMetadata>(null);
|
const childInfo = ref<null | PageMetadata>(null);
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<XWidgets/>
|
<XWidgets/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button v-if="(!isDesktop || pageMetadata?.needWideArea) && !isMobile" :class="$style.widgetButton" class="_button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button>
|
<button v-if="!isDesktop && !pageMetadata?.needWideArea && !isMobile" :class="$style.widgetButton" class="_button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button>
|
||||||
|
|
||||||
<div v-if="isMobile" ref="navFooter" :class="$style.nav">
|
<div v-if="isMobile" ref="navFooter" :class="$style.nav">
|
||||||
<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator" class="_blink"><i class="_indicatorCircle"></i></span></button>
|
<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator" class="_blink"><i class="_indicatorCircle"></i></span></button>
|
||||||
|
Loading…
Reference in New Issue
Block a user