mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-12 06:54:33 +00:00
Merge pull request 'Sort custom emoticons in picker by sortKey (fixes #18)' (#22) from emoticon-sorting into dev
Reviewed-on: https://codeberg.org/yeentown/barkey/pulls/22 Reviewed-by: zima <zima@noreply.codeberg.org>
This commit is contained in:
commit
dd90724c54
19 changed files with 160 additions and 16 deletions
42
locales/index.d.ts
vendored
42
locales/index.d.ts
vendored
|
@ -11374,6 +11374,48 @@ export interface Locale extends ILocale {
|
||||||
* Remote followers may have incomplete or outdated activity
|
* Remote followers may have incomplete or outdated activity
|
||||||
*/
|
*/
|
||||||
"remoteFollowersWarning": string;
|
"remoteFollowersWarning": string;
|
||||||
|
/**
|
||||||
|
* Sort key
|
||||||
|
*/
|
||||||
|
"sortKey": string;
|
||||||
|
"_unicodeEmoji": {
|
||||||
|
/**
|
||||||
|
* Smileys
|
||||||
|
*/
|
||||||
|
"face": string;
|
||||||
|
/**
|
||||||
|
* People
|
||||||
|
*/
|
||||||
|
"people": string;
|
||||||
|
/**
|
||||||
|
* Animals & nature
|
||||||
|
*/
|
||||||
|
"animals_and_nature": string;
|
||||||
|
/**
|
||||||
|
* Food & drink
|
||||||
|
*/
|
||||||
|
"food_and_drink": string;
|
||||||
|
/**
|
||||||
|
* Activity
|
||||||
|
*/
|
||||||
|
"activity": string;
|
||||||
|
/**
|
||||||
|
* Travel & places
|
||||||
|
*/
|
||||||
|
"travel_and_places": string;
|
||||||
|
/**
|
||||||
|
* Objects
|
||||||
|
*/
|
||||||
|
"objects": string;
|
||||||
|
/**
|
||||||
|
* Symbols
|
||||||
|
*/
|
||||||
|
"symbols": string;
|
||||||
|
/**
|
||||||
|
* Flags
|
||||||
|
*/
|
||||||
|
"flags": string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
declare const locales: {
|
declare const locales: {
|
||||||
[lang: string]: Locale;
|
[lang: string]: Locale;
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
export class AddEmojiSortKey1733435351051 {
|
||||||
|
name = 'AddEmojiSortKey1733435351051'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "emoji" ADD "sortKey" character varying(64)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "sortKey"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -76,6 +76,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
isSensitive: boolean;
|
isSensitive: boolean;
|
||||||
localOnly: boolean;
|
localOnly: boolean;
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
|
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
|
||||||
|
sortKey: string | null;
|
||||||
}, moderator?: MiUser): Promise<MiEmoji> {
|
}, moderator?: MiUser): Promise<MiEmoji> {
|
||||||
const emoji = await this.emojisRepository.insertOne({
|
const emoji = await this.emojisRepository.insertOne({
|
||||||
id: this.idService.gen(),
|
id: this.idService.gen(),
|
||||||
|
@ -91,6 +92,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
isSensitive: data.isSensitive,
|
isSensitive: data.isSensitive,
|
||||||
localOnly: data.localOnly,
|
localOnly: data.localOnly,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||||
|
sortKey: data.sortKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data.host == null) {
|
if (data.host == null) {
|
||||||
|
@ -121,6 +123,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
isSensitive?: boolean;
|
isSensitive?: boolean;
|
||||||
localOnly?: boolean;
|
localOnly?: boolean;
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][];
|
roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][];
|
||||||
|
sortKey?: string | null;
|
||||||
}, moderator?: MiUser): Promise<void> {
|
}, moderator?: MiUser): Promise<void> {
|
||||||
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
|
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
|
||||||
const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
|
const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
|
||||||
|
@ -138,6 +141,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
publicUrl: data.driveFile != null ? (data.driveFile.webpublicUrl ?? data.driveFile.url) : undefined,
|
publicUrl: data.driveFile != null ? (data.driveFile.webpublicUrl ?? data.driveFile.url) : undefined,
|
||||||
type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined,
|
type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction ?? undefined,
|
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction ?? undefined,
|
||||||
|
sortKey: data.sortKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.localEmojisCache.refresh();
|
this.localEmojisCache.refresh();
|
||||||
|
|
|
@ -34,6 +34,7 @@ export class EmojiEntityService {
|
||||||
localOnly: emoji.localOnly ? true : undefined,
|
localOnly: emoji.localOnly ? true : undefined,
|
||||||
isSensitive: emoji.isSensitive ? true : undefined,
|
isSensitive: emoji.isSensitive ? true : undefined,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
|
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
|
||||||
|
sortKey: emoji.sortKey,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +63,7 @@ export class EmojiEntityService {
|
||||||
isSensitive: emoji.isSensitive,
|
isSensitive: emoji.isSensitive,
|
||||||
localOnly: emoji.localOnly,
|
localOnly: emoji.localOnly,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||||
|
sortKey: emoji.sortKey,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,4 +81,9 @@ export class MiEmoji {
|
||||||
array: true, length: 128, default: '{}',
|
array: true, length: 128, default: '{}',
|
||||||
})
|
})
|
||||||
public roleIdsThatCanBeUsedThisEmojiAsReaction: string[];
|
public roleIdsThatCanBeUsedThisEmojiAsReaction: string[];
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 64, nullable: true,
|
||||||
|
})
|
||||||
|
public sortKey: string | null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,10 @@ export const packedEmojiSimpleSchema = {
|
||||||
format: 'id',
|
format: 'id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
sortKey: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
@ -102,5 +106,9 @@ export const packedEmojiDetailedSchema = {
|
||||||
format: 'id',
|
format: 'id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
sortKey: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -105,6 +105,7 @@ export class ImportCustomEmojisProcessorService {
|
||||||
isSensitive: emojiInfo.isSensitive,
|
isSensitive: emojiInfo.isSensitive,
|
||||||
localOnly: emojiInfo.localOnly,
|
localOnly: emojiInfo.localOnly,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
|
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
|
||||||
|
sortKey: emojiInfo.sortKey?.normalize('NFC'),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error || typeof e === 'string') {
|
if (e instanceof Error || typeof e === 'string') {
|
||||||
|
|
|
@ -56,6 +56,7 @@ export const paramDef = {
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
} },
|
} },
|
||||||
|
sortKey: { type: 'string', nullable: true },
|
||||||
},
|
},
|
||||||
required: ['name', 'fileId'],
|
required: ['name', 'fileId'],
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -91,6 +92,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
isSensitive: ps.isSensitive ?? false,
|
isSensitive: ps.isSensitive ?? false,
|
||||||
localOnly: ps.localOnly ?? false,
|
localOnly: ps.localOnly ?? false,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
|
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
|
||||||
|
sortKey: ps.sortKey?.normalize('NFC') ?? null,
|
||||||
}, me);
|
}, me);
|
||||||
|
|
||||||
return this.emojiEntityService.packDetailed(emoji);
|
return this.emojiEntityService.packDetailed(emoji);
|
||||||
|
|
|
@ -97,6 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
isSensitive: emoji.isSensitive,
|
isSensitive: emoji.isSensitive,
|
||||||
localOnly: emoji.localOnly,
|
localOnly: emoji.localOnly,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||||
|
sortKey: emoji.sortKey?.normalize('NFC') ?? null,
|
||||||
}, me);
|
}, me);
|
||||||
|
|
||||||
return this.emojiEntityService.packDetailed(addedEmoji);
|
return this.emojiEntityService.packDetailed(addedEmoji);
|
||||||
|
|
|
@ -56,6 +56,10 @@ export const meta = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
sortKey: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -56,6 +56,7 @@ export const paramDef = {
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
} },
|
} },
|
||||||
|
sortKey: { type: 'string', nullable: true },
|
||||||
},
|
},
|
||||||
anyOf: [
|
anyOf: [
|
||||||
{ required: ['id'] },
|
{ required: ['id'] },
|
||||||
|
@ -104,6 +105,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
isSensitive: ps.isSensitive,
|
isSensitive: ps.isSensitive,
|
||||||
localOnly: ps.localOnly,
|
localOnly: ps.localOnly,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||||
|
sortKey: ps.sortKey,
|
||||||
}, me);
|
}, me);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ const devConfig: UserConfig = {
|
||||||
publicDir: '../assets',
|
publicDir: '../assets',
|
||||||
base: '/embed',
|
base: '/embed',
|
||||||
server: {
|
server: {
|
||||||
host: 'localhost',
|
host: true,
|
||||||
port: 5174,
|
port: 5174,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
|
|
|
@ -64,6 +64,7 @@ export function getConfig(): UserConfig {
|
||||||
|
|
||||||
server: {
|
server: {
|
||||||
port: 5174,
|
port: 5174,
|
||||||
|
host: true
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
|
@ -49,6 +49,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkLink :url="emoji.url" target="_blank">{{ emoji.url }}</MkLink>
|
<MkLink :url="emoji.url" target="_blank">{{ emoji.url }}</MkLink>
|
||||||
</template>
|
</template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.sortKey }}</template>
|
||||||
|
<template #value>
|
||||||
|
<span v-if="emoji.sortKey === null || emoji.sortKey === undefined">{{ i18n.ts.none }}</span>
|
||||||
|
<span v-else :class="$style.alias">{{ emoji.sortKey }}</span>
|
||||||
|
</template>
|
||||||
|
</MkKeyValue>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -6,9 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<!-- このコンポーネントの要素のclassは親から利用されるのでむやみに弄らないこと -->
|
<!-- このコンポーネントの要素のclassは親から利用されるのでむやみに弄らないこと -->
|
||||||
<!-- フォルダの中にはカスタム絵文字だけ(Unicode絵文字もこっち) -->
|
<!-- フォルダの中にはカスタム絵文字だけ(Unicode絵文字もこっち) -->
|
||||||
<section v-if="!hasChildSection" v-panel style="border-radius: var(--radius-sm); border-bottom: 0.5px solid var(--divider);">
|
<section v-if="!hasChildSection" :data-shown="shown" v-panel style="border-radius: var(--radius-sm); border-bottom: 0.5px solid var(--divider);">
|
||||||
<header class="_acrylic" @click="shown = !shown">
|
<header class="_acrylic" :data-shown="shown" @click="shown = !shown">
|
||||||
<i class="toggle ti-fw" :class="shown ? 'ti ti-chevron-down' : 'ti ti-chevron-up'"></i> <slot></slot> (<i class="ph-smiley-sticker ph-bold ph-lg"></i>:{{ emojis.length }})
|
<i class="toggle ti-fw" :class="shown ? 'ph-bold ph-caret-down' : 'ph-bold ph-caret-right'"></i> <slot></slot>
|
||||||
|
<span class="emoji-count"><i class="ph-smiley ph-bold ph-lg"></i> {{ emojis.length }}</span>
|
||||||
</header>
|
</header>
|
||||||
<div v-if="shown" class="body">
|
<div v-if="shown" class="body">
|
||||||
<button
|
<button
|
||||||
|
@ -26,16 +27,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<!-- フォルダの中にはカスタム絵文字やフォルダがある -->
|
<!-- フォルダの中にはカスタム絵文字やフォルダがある -->
|
||||||
<section v-else v-panel style="border-radius: var(--radius-sm); border-bottom: 0.5px solid var(--divider);">
|
<section v-else v-panel :data-shown="shown" style="border-radius: var(--radius-sm); border-bottom: 0.5px solid var(--divider);">
|
||||||
<header class="_acrylic" @click="shown = !shown">
|
<header class="_acrylic" :data-shown="shown" @click="shown = !shown">
|
||||||
<i class="toggle ti-fw" :class="shown ? 'ti ti-chevron-down' : 'ti ti-chevron-up'"></i> <slot></slot> (<i class="ti ti-folder ti-fw"></i>:{{ customEmojiTree?.length }} <i class="ph-smiley-sticker ph-bold ph-lg ti-fw"></i>:{{ emojis.length }})
|
<i class="toggle ti-fw" :class="shown ? 'ph-bold ph-caret-down' : 'ph-bold ph-caret-right'"></i> <slot></slot>
|
||||||
|
<span class="emoji-count"><i class="ph-folder ph-bold"></i> {{ customEmojiTree?.length }}</span>
|
||||||
|
<span class="emoji-count" v-if="emojis.length > 0"><i class="ph-smiley ph-bold"></i> {{ emojis.length }}</span>
|
||||||
</header>
|
</header>
|
||||||
<div v-if="shown" style="padding-left: 9px;">
|
<div v-if="shown" style="padding-left: 9px;">
|
||||||
<MkEmojiPickerSection
|
<MkEmojiPickerSection
|
||||||
v-for="child in customEmojiTree"
|
v-for="child in customEmojiTree"
|
||||||
:key="`custom:${child.value}`"
|
:key="`custom:${child.value}`"
|
||||||
:initialShown="initialShown"
|
:initialShown="initialShown"
|
||||||
:emojis="computed(() => customEmojis.filter(e => e.category === child.category).map(e => `:${e.name}:`))"
|
:emojis="computed(() => customEmojis.filter(e => e.category === child.category).sort(compareBySortKey).map(e => `:${e.name}:`))"
|
||||||
:hasChildSection="child.children.length !== 0"
|
:hasChildSection="child.children.length !== 0"
|
||||||
:customEmojiTree="child.children"
|
:customEmojiTree="child.children"
|
||||||
@chosen="nestedChosen"
|
@chosen="nestedChosen"
|
||||||
|
@ -43,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
{{ child.value || i18n.ts.other }}
|
{{ child.value || i18n.ts.other }}
|
||||||
</MkEmojiPickerSection>
|
</MkEmojiPickerSection>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="shown" class="body">
|
<div v-if="shown && emojis.length > 0" class="body">
|
||||||
<button
|
<button
|
||||||
v-for="emoji in emojis"
|
v-for="emoji in emojis"
|
||||||
:key="emoji"
|
:key="emoji"
|
||||||
|
@ -64,7 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { ref, computed, Ref } from 'vue';
|
import { ref, computed, Ref } from 'vue';
|
||||||
import { CustomEmojiFolderTree, getEmojiName } from '@@/js/emojilist.js';
|
import { CustomEmojiFolderTree, getEmojiName } from '@@/js/emojilist.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { customEmojis } from '@/custom-emojis.js';
|
import { customEmojis, compareBySortKey } from '@/custom-emojis.js';
|
||||||
import MkEmojiPickerSection from '@/components/MkEmojiPicker.section.vue';
|
import MkEmojiPickerSection from '@/components/MkEmojiPicker.section.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
|
@ -91,7 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
v-for="child in customEmojiFolderRoot.children"
|
v-for="child in customEmojiFolderRoot.children"
|
||||||
:key="`custom:${child.value}`"
|
:key="`custom:${child.value}`"
|
||||||
:initialShown="false"
|
:initialShown="false"
|
||||||
:emojis="computed(() => customEmojis.filter(e => filterCategory(e, child.value)).map(e => `:${e.name}:`))"
|
:emojis="computed(() => customEmojis.filter(e => filterCategory(e, child.value)).sort(compareBySortKey).map(e => `:${e.name}:`))"
|
||||||
:disabledEmojis="computed(() => customEmojis.filter(e => filterCategory(e, child.value)).filter(e => !canReact(e)).map(e => `:${e.name}:`))"
|
:disabledEmojis="computed(() => customEmojis.filter(e => filterCategory(e, child.value)).filter(e => !canReact(e)).map(e => `:${e.name}:`))"
|
||||||
:hasChildSection="child.children.length !== 0"
|
:hasChildSection="child.children.length !== 0"
|
||||||
:customEmojiTree="child.children"
|
:customEmojiTree="child.children"
|
||||||
|
@ -102,7 +102,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div v-once class="group">
|
<div v-once class="group">
|
||||||
<header class="_acrylic">{{ i18n.ts.emoji }}</header>
|
<header class="_acrylic">{{ i18n.ts.emoji }}</header>
|
||||||
<XSection v-for="category in categories" :key="category" :emojis="emojiCharByCategory.get(category) ?? []" :hasChildSection="false" @chosen="chosen">{{ category }}</XSection>
|
<XSection v-for="category in categories" :key="category" :emojis="emojiCharByCategory.get(category) ?? []" :hasChildSection="false" @chosen="chosen">{{ i18n.ts._unicodeEmoji[category] ?? category }}</XSection>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
|
@ -133,7 +133,7 @@ import { isTouchUsing } from '@/scripts/touch.js';
|
||||||
import { deviceKind } from '@/scripts/device-kind.js';
|
import { deviceKind } from '@/scripts/device-kind.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js';
|
import { customEmojiCategories, customEmojis, customEmojisMap, compareBySortKey } from '@/custom-emojis.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js';
|
import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js';
|
||||||
|
|
||||||
|
@ -186,11 +186,13 @@ function parseAndMergeCategories(input: string, root: CustomEmojiFolderTree): Cu
|
||||||
const parts = input.split('/').map(p => p.trim());
|
const parts = input.split('/').map(p => p.trim());
|
||||||
let currentNode: CustomEmojiFolderTree = root;
|
let currentNode: CustomEmojiFolderTree = root;
|
||||||
|
|
||||||
|
let currentPath = [];
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
|
currentPath.push(part);
|
||||||
let existingNode = currentNode.children.find((node) => node.value === part);
|
let existingNode = currentNode.children.find((node) => node.value === part);
|
||||||
|
|
||||||
if (!existingNode) {
|
if (!existingNode) {
|
||||||
const newNode: CustomEmojiFolderTree = { value: part, category: input, children: [] };
|
const newNode: CustomEmojiFolderTree = { value: part, category: currentPath.join("/"), children: [] };
|
||||||
currentNode.children.push(newNode);
|
currentNode.children.push(newNode);
|
||||||
existingNode = newNode;
|
existingNode = newNode;
|
||||||
}
|
}
|
||||||
|
@ -501,6 +503,8 @@ defineExpose({
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
/* Required to make header background blur work */
|
||||||
|
contain: layout style;
|
||||||
|
|
||||||
&.s1 {
|
&.s1 {
|
||||||
--eachSize: 40px;
|
--eachSize: 40px;
|
||||||
|
@ -698,18 +702,34 @@ defineExpose({
|
||||||
|
|
||||||
::v-deep(section) {
|
::v-deep(section) {
|
||||||
> header {
|
> header {
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
z-index: 1;
|
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
/* Closed sections should not have sticky headers. */
|
||||||
|
&:not([data-shown=false]) {
|
||||||
|
position: sticky;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .emoji-count {
|
||||||
|
float: right;
|
||||||
|
margin-left: 8px;
|
||||||
|
color: var(--fgTransparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open subsections should have a z-index above the parent's header. This prevents the blur effect from getting muddy. */
|
||||||
|
&[data-shown=true] {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .body {
|
> .body {
|
||||||
|
|
|
@ -20,6 +20,19 @@ export const customEmojiCategories = computed<[ ...string[], null ]>(() => {
|
||||||
return markRaw([...Array.from(categories), null]);
|
return markRaw([...Array.from(categories), null]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function compareBySortKey(a: Misskey.entities.EmojiSimple, b: Misskey.entities.EmojiSimple): number {
|
||||||
|
if (a.sortKey === b.sortKey) {
|
||||||
|
if (a.name === b.name)
|
||||||
|
return 0;
|
||||||
|
return (a.name > b.name) ? 1 : -1;
|
||||||
|
}
|
||||||
|
if (a.sortKey === null)
|
||||||
|
return 1;
|
||||||
|
if (b.sortKey === null)
|
||||||
|
return -1;
|
||||||
|
return (a.sortKey > b.sortKey) ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
export const customEmojisMap = new Map<string, Misskey.entities.EmojiSimple>();
|
export const customEmojisMap = new Map<string, Misskey.entities.EmojiSimple>();
|
||||||
watch(customEmojis, emojis => {
|
watch(customEmojis, emojis => {
|
||||||
customEmojisMap.clear();
|
customEmojisMap.clear();
|
||||||
|
|
|
@ -68,6 +68,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
<MkSwitch v-model="isSensitive">isSensitive</MkSwitch>
|
<MkSwitch v-model="isSensitive">isSensitive</MkSwitch>
|
||||||
<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch>
|
<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch>
|
||||||
|
<MkInput v-model="sortKey" autocapitalize="off">
|
||||||
|
<template #label>{{ i18n.ts.sortKey }}</template>
|
||||||
|
</MkInput>
|
||||||
<MkButton v-if="emoji" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
<MkButton v-if="emoji" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
@ -107,6 +110,7 @@ const isSensitive = ref(props.emoji ? props.emoji.isSensitive : false);
|
||||||
const localOnly = ref(props.emoji ? props.emoji.localOnly : false);
|
const localOnly = ref(props.emoji ? props.emoji.localOnly : false);
|
||||||
const roleIdsThatCanBeUsedThisEmojiAsReaction = ref(props.emoji ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
|
const roleIdsThatCanBeUsedThisEmojiAsReaction = ref(props.emoji ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
|
||||||
const rolesThatCanBeUsedThisEmojiAsReaction = ref<Misskey.entities.Role[]>([]);
|
const rolesThatCanBeUsedThisEmojiAsReaction = ref<Misskey.entities.Role[]>([]);
|
||||||
|
const sortKey = ref<string>(props.emoji ? (props.emoji.sortKey ?? '') : '');
|
||||||
const file = ref<Misskey.entities.DriveFile>();
|
const file = ref<Misskey.entities.DriveFile>();
|
||||||
|
|
||||||
watch(roleIdsThatCanBeUsedThisEmojiAsReaction, async () => {
|
watch(roleIdsThatCanBeUsedThisEmojiAsReaction, async () => {
|
||||||
|
@ -153,6 +157,7 @@ async function done() {
|
||||||
isSensitive: isSensitive.value,
|
isSensitive: isSensitive.value,
|
||||||
localOnly: localOnly.value,
|
localOnly: localOnly.value,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: rolesThatCanBeUsedThisEmojiAsReaction.value.map(x => x.id),
|
roleIdsThatCanBeUsedThisEmojiAsReaction: rolesThatCanBeUsedThisEmojiAsReaction.value.map(x => x.id),
|
||||||
|
sortKey: sortKey.value === '' ? null : sortKey.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (file.value) {
|
if (file.value) {
|
||||||
|
|
|
@ -397,3 +397,16 @@ _auth:
|
||||||
allowed: "Allowed"
|
allowed: "Allowed"
|
||||||
_announcement:
|
_announcement:
|
||||||
new: "New"
|
new: "New"
|
||||||
|
|
||||||
|
sortKey: "Sort key"
|
||||||
|
|
||||||
|
_unicodeEmoji:
|
||||||
|
face: "Smileys"
|
||||||
|
people: "People"
|
||||||
|
animals_and_nature: "Animals & nature"
|
||||||
|
food_and_drink: "Food & drink"
|
||||||
|
activity: "Activity"
|
||||||
|
travel_and_places: "Travel & places"
|
||||||
|
objects: "Objects"
|
||||||
|
symbols: "Symbols"
|
||||||
|
flags: "Flags"
|
||||||
|
|
Loading…
Add table
Reference in a new issue