mirror of
https://iceshrimp.dev/crimekillz/trashposs
synced 2024-11-25 10:19:06 +01:00
feat: set license information for custom emojis (#9719)
Closes: #9711 (please check this issue first) I cherry-picked two commits ([1](8ae9d2eaa8
), [2](ed51209172
)) from [Misskey](https://github.com/misskey-dev/misskey) and made a few changes. 「ライセンス」should be written as "License" in the following screenshots, but it has not yet been translated. It would be nice if we could include multiple lines of text, but I just ported what's been implemented so far in Misskey not to mess things up. This is my first pull request (aside from typo correction). Feel free to point out any issues! ![](https://cdn.discordapp.com/attachments/823878222897741868/1086372711841935440/2023-03-18_042011.png) ![](https://cdn.discordapp.com/attachments/823878222897741868/1086373178214981853/01.png) ![](https://cdn.discordapp.com/attachments/823878222897741868/1086373336709341246/2023-03-18_042629.png) Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> Co-authored-by: naskya <m@naskya.net> Reviewed-on: https://codeberg.org/calckey/calckey/pulls/9719 Co-authored-by: naskya <naskya@noreply.codeberg.org> Co-committed-by: naskya <naskya@noreply.codeberg.org>
This commit is contained in:
parent
e4a6580a60
commit
94eb321431
20 changed files with 203 additions and 6 deletions
|
@ -935,6 +935,7 @@ moveFromLabel: "Account you're moving from:"
|
|||
moveFromDescription: "This will set an alias of your old account so that you can move from that account to this current one. Do this BEFORE moving from your older account. Please enter the tag of the account formatted like @person@instance.com"
|
||||
migrationConfirm: "Are you absolutely sure you want to migrate your acccount to {account}? Once you do this, you won't be able to reverse it, and you won't be able to use your account normally again.\nAlso, please ensure that you've set this current account as the account you're moving from."
|
||||
defaultReaction: "Default emoji reaction for outgoing and incoming posts"
|
||||
license: "License"
|
||||
|
||||
_sensitiveMediaDetection:
|
||||
description: "Reduces the effort of server moderation through automatically recognizing NSFW media via Machine Learning. This will slightly increase the load on the server."
|
||||
|
|
|
@ -935,6 +935,7 @@ moveFromLabel: "引っ越し元のアカウント:"
|
|||
moveFromDescription: "別のアカウントからこのアカウントにフォロワーを引き継いで引っ越したい場合、ここでエイリアスを作成しておく必要があります。必ず引っ越しを実行する前に作成してください!引っ越し元のアカウントをこのように入力してください:@person@instance.com"
|
||||
migrationConfirm: "本当にこのアカウントを {account} に引っ越しますか?一度引っ越しを行うと取り消せず、二度とこのアカウントを元の状態で使用することはできません。\nまた、引っ越し先のアカウントでエイリアスを作成したことを確認してください。"
|
||||
defaultReaction: "リモートとローカルの投稿に対するデフォルトの絵文字リアクション"
|
||||
license: "ライセンス"
|
||||
|
||||
_sensitiveMediaDetection:
|
||||
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。"
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
export class addPropsForCustomEmoji1678945242650 {
|
||||
name = 'addPropsForCustomEmoji1678945242650'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "emoji" ADD "license" character varying(1024)`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "license"`);
|
||||
}
|
||||
}
|
|
@ -55,4 +55,9 @@ export class Emoji {
|
|||
array: true, length: 128, default: '{}',
|
||||
})
|
||||
public aliases: string[];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024, nullable: true,
|
||||
})
|
||||
public license: string | null;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ export const EmojiRepository = db.getRepository(Emoji).extend({
|
|||
host: emoji.host,
|
||||
// || emoji.originalUrl してるのは後方互換性のため
|
||||
url: emoji.publicUrl || emoji.originalUrl,
|
||||
license: emoji.license,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -40,5 +40,10 @@ export const packedEmojiSchema = {
|
|||
optional: false,
|
||||
nullable: false,
|
||||
},
|
||||
license: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
|
|
@ -75,6 +75,7 @@ export async function importCustomEmojis(
|
|||
originalUrl: driveFile.url,
|
||||
publicUrl: driveFile.webpublicUrl ?? driveFile.url,
|
||||
type: driveFile.webpublicType ?? driveFile.type,
|
||||
license: emojiInfo.license,
|
||||
}).then((x) => Emojis.findOneByOrFail(x.identifiers[0]));
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import * as ep___admin_emoji_list from "./endpoints/admin/emoji/list.js";
|
|||
import * as ep___admin_emoji_removeAliasesBulk from "./endpoints/admin/emoji/remove-aliases-bulk.js";
|
||||
import * as ep___admin_emoji_setAliasesBulk from "./endpoints/admin/emoji/set-aliases-bulk.js";
|
||||
import * as ep___admin_emoji_setCategoryBulk from "./endpoints/admin/emoji/set-category-bulk.js";
|
||||
import * as ep___admin_emoji_setLicenseBulk from "./endpoints/admin/emoji/set-license-bulk.js";
|
||||
import * as ep___admin_emoji_update from "./endpoints/admin/emoji/update.js";
|
||||
import * as ep___admin_federation_deleteAllFiles from "./endpoints/admin/federation/delete-all-files.js";
|
||||
import * as ep___admin_federation_refreshRemoteInstanceMetadata from "./endpoints/admin/federation/refresh-remote-instance-metadata.js";
|
||||
|
@ -131,6 +132,7 @@ import * as ep___drive_folders_show from "./endpoints/drive/folders/show.js";
|
|||
import * as ep___drive_folders_update from "./endpoints/drive/folders/update.js";
|
||||
import * as ep___drive_stream from "./endpoints/drive/stream.js";
|
||||
import * as ep___emailAddress_available from "./endpoints/email-address/available.js";
|
||||
import * as ep___emoji from "./endpoints/emoji.js";
|
||||
import * as ep___endpoint from "./endpoints/endpoint.js";
|
||||
import * as ep___endpoints from "./endpoints/endpoints.js";
|
||||
import * as ep___exportCustomEmojis from "./endpoints/export-custom-emojis.js";
|
||||
|
@ -363,6 +365,7 @@ const eps = [
|
|||
["admin/emoji/remove-aliases-bulk", ep___admin_emoji_removeAliasesBulk],
|
||||
["admin/emoji/set-aliases-bulk", ep___admin_emoji_setAliasesBulk],
|
||||
["admin/emoji/set-category-bulk", ep___admin_emoji_setCategoryBulk],
|
||||
["admin/emoji/set-license-bulk", ep___admin_emoji_setLicenseBulk],
|
||||
["admin/emoji/update", ep___admin_emoji_update],
|
||||
["admin/federation/delete-all-files", ep___admin_federation_deleteAllFiles],
|
||||
[
|
||||
|
@ -471,6 +474,7 @@ const eps = [
|
|||
["drive/folders/update", ep___drive_folders_update],
|
||||
["drive/stream", ep___drive_stream],
|
||||
["email-address/available", ep___emailAddress_available],
|
||||
["emoji", ep___emoji],
|
||||
["endpoint", ep___endpoint],
|
||||
["endpoints", ep___endpoints],
|
||||
["export-custom-emojis", ep___exportCustomEmojis],
|
||||
|
|
|
@ -49,6 +49,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
originalUrl: file.url,
|
||||
publicUrl: file.webpublicUrl ?? file.url,
|
||||
type: file.webpublicType ?? file.type,
|
||||
license: null,
|
||||
}).then((x) => Emojis.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
await db.queryResultCache!.remove(["meta_emojis"]);
|
||||
|
|
|
@ -73,6 +73,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
originalUrl: driveFile.url,
|
||||
publicUrl: driveFile.webpublicUrl ?? driveFile.url,
|
||||
type: driveFile.webpublicType ?? driveFile.type,
|
||||
license: emoji.license,
|
||||
}).then((x) => Emojis.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
await db.queryResultCache!.remove(["meta_emojis"]);
|
||||
|
|
|
@ -55,6 +55,11 @@ export const meta = {
|
|||
optional: false,
|
||||
nullable: false,
|
||||
},
|
||||
license: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -55,6 +55,11 @@ export const meta = {
|
|||
optional: false,
|
||||
nullable: false,
|
||||
},
|
||||
license: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import define from "../../../define.js";
|
||||
import { Emojis } from "@/models/index.js";
|
||||
import { In } from "typeorm";
|
||||
import { ApiError } from "../../../error.js";
|
||||
import { db } from "@/db/postgre.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["admin"],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
ids: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
format: "misskey:id",
|
||||
},
|
||||
},
|
||||
license: {
|
||||
type: "string",
|
||||
nullable: true,
|
||||
description: "Use `null` to reset the license.",
|
||||
},
|
||||
},
|
||||
required: ["ids"],
|
||||
} as const;
|
||||
|
||||
export default define(meta, paramDef, async (ps) => {
|
||||
await Emojis.update(
|
||||
{
|
||||
id: In(ps.ids),
|
||||
},
|
||||
{
|
||||
updatedAt: new Date(),
|
||||
license: ps.license,
|
||||
},
|
||||
);
|
||||
|
||||
await db.queryResultCache!.remove(["meta_emojis"]);
|
||||
});
|
|
@ -34,6 +34,10 @@ export const paramDef = {
|
|||
type: "string",
|
||||
},
|
||||
},
|
||||
license: {
|
||||
type: "string",
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
required: ["id", "name", "aliases"],
|
||||
} as const;
|
||||
|
@ -48,6 +52,7 @@ export default define(meta, paramDef, async (ps) => {
|
|||
name: ps.name,
|
||||
category: ps.category,
|
||||
aliases: ps.aliases,
|
||||
license: ps.license,
|
||||
});
|
||||
|
||||
await db.queryResultCache!.remove(["meta_emojis"]);
|
||||
|
|
38
packages/backend/src/server/api/endpoints/emoji.ts
Normal file
38
packages/backend/src/server/api/endpoints/emoji.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { IsNull } from "typeorm";
|
||||
import { Emojis } from "@/models/index.js";
|
||||
import define from "../define.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["meta"],
|
||||
|
||||
requireCredential: false,
|
||||
allowGet: true,
|
||||
cacheSec: 3600,
|
||||
|
||||
res: {
|
||||
type: "object",
|
||||
optional: false, nullable: false,
|
||||
ref: "Emoji",
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: ["name"],
|
||||
} as const;
|
||||
|
||||
export default define(meta, paramDef, async (ps, me) => {
|
||||
const emoji = await Emojis.findOneOrFail({
|
||||
where: {
|
||||
name: ps.name,
|
||||
host: IsNull(),
|
||||
},
|
||||
});
|
||||
|
||||
return Emojis.pack(emoji);
|
||||
});
|
|
@ -14,9 +14,11 @@
|
|||
</div>
|
||||
<header v-if="title" :class="$style.title"><Mfm :text="title"/></header>
|
||||
<div v-if="text" :class="$style.text"><Mfm :text="text"/></div>
|
||||
<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" @keydown="onInputKeydown">
|
||||
<MkInput v-if="input && input.type !== 'paragraph'" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" @keydown="onInputKeydown">
|
||||
<template v-if="input.type === 'password'" #prefix><i class="ph-password ph-bold ph-lg"></i></template>
|
||||
</MkInput>
|
||||
<MkTextarea v-if="input && input.type === 'paragraph'" v-model="inputValue" autofocus :type="paragraph" :placeholder="input.placeholder || undefined">
|
||||
</MkTextarea>
|
||||
<MkSelect v-if="select" v-model="selectedValue" autofocus>
|
||||
<template v-if="select.items">
|
||||
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
|
||||
|
@ -49,6 +51,7 @@ import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue';
|
|||
import MkModal from '@/components/MkModal.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/form/input.vue';
|
||||
import MkTextarea from '@/components/form/textarea.vue';
|
||||
import MkSelect from '@/components/form/select.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
|
|
|
@ -359,6 +359,40 @@ export function inputText(props: {
|
|||
});
|
||||
}
|
||||
|
||||
export function inputParagraph(props: {
|
||||
title?: string | null;
|
||||
text?: string | null;
|
||||
placeholder?: string | null;
|
||||
default?: string | null;
|
||||
}): Promise<
|
||||
| { canceled: true; result: undefined }
|
||||
| {
|
||||
canceled: false;
|
||||
result: string;
|
||||
}
|
||||
> {
|
||||
return new Promise((resolve, reject) => {
|
||||
popup(
|
||||
defineAsyncComponent(() => import("@/components/MkDialog.vue")),
|
||||
{
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
input: {
|
||||
type: "paragraph",
|
||||
placeholder: props.placeholder,
|
||||
default: props.default,
|
||||
},
|
||||
},
|
||||
{
|
||||
done: (result) => {
|
||||
resolve(result ? result : { canceled: true });
|
||||
},
|
||||
},
|
||||
"closed",
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function inputNumber(props: {
|
||||
title?: string | null;
|
||||
text?: string | null;
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
<template #label>{{ i18n.ts.tags }}</template>
|
||||
<template #caption>{{ i18n.ts.setMultipleBySeparatingWithSpace }}</template>
|
||||
</MkInput>
|
||||
<MkTextarea v-model="license" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.license }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton danger @click="del()"><i class="ph-trash ph-bold ph-lg"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -33,6 +36,7 @@ import { } from 'vue';
|
|||
import XModalWindow from '@/components/MkModalWindow.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/form/input.vue';
|
||||
import MkTextarea from '@/components/form/textarea.vue';
|
||||
import * as os from '@/os';
|
||||
import { unique } from '@/scripts/array';
|
||||
import { i18n } from '@/i18n';
|
||||
|
@ -47,6 +51,7 @@ let name: string = $ref(props.emoji.name);
|
|||
let category: string = $ref(props.emoji.category);
|
||||
let aliases: string = $ref(props.emoji.aliases.join(' '));
|
||||
let categories: string[] = $ref(emojiCategories);
|
||||
let license: string = $ref(props.emoji.license ?? '');
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'done', v: { deleted?: boolean, updated?: any }): void,
|
||||
|
@ -63,6 +68,7 @@ async function update() {
|
|||
name,
|
||||
category,
|
||||
aliases: aliases.split(' '),
|
||||
license: license === '' ? null : license,
|
||||
});
|
||||
|
||||
emit('done', {
|
||||
|
@ -71,6 +77,7 @@ async function update() {
|
|||
name,
|
||||
category,
|
||||
aliases: aliases.split(' '),
|
||||
license: license === '' ? null : license,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<MkButton inline @click="addTagBulk">Add tag</MkButton>
|
||||
<MkButton inline @click="removeTagBulk">Remove tag</MkButton>
|
||||
<MkButton inline @click="setTagBulk">Set tag</MkButton>
|
||||
<MkButton inline @click="setLicenseBulk">Set license</MkButton>
|
||||
<MkButton inline danger @click="delBulk">Delete</MkButton>
|
||||
</div>
|
||||
<MkPagination ref="emojisPaginationComponent" :pagination="pagination">
|
||||
|
@ -258,6 +259,18 @@ const setTagBulk = async () => {
|
|||
emojisPaginationComponent.value.reload();
|
||||
};
|
||||
|
||||
const setLicenseBulk = async () => {
|
||||
const { canceled, result } = await os.inputParagraph({
|
||||
title: 'License',
|
||||
});
|
||||
if (canceled) return;
|
||||
await os.apiWithDialog('admin/emoji/set-license-bulk', {
|
||||
ids: selectedEmojis.value,
|
||||
license: result,
|
||||
});
|
||||
emojisPaginationComponent.value.reload();
|
||||
};
|
||||
|
||||
const delBulk = async () => {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<img :src="emoji.url" class="img" :alt="emoji.name"/>
|
||||
<div class="body">
|
||||
<div class="name _monospace">{{ emoji.name }}</div>
|
||||
<div class="info">{{ emoji.aliases.join(' ') }}</div>
|
||||
<div class="info">{{ emoji.aliases.join(" ") }}</div>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
|
@ -20,15 +20,26 @@ const props = defineProps<{
|
|||
|
||||
function menu(ev) {
|
||||
os.popupMenu([{
|
||||
type: 'label',
|
||||
text: ':' + props.emoji.name + ':',
|
||||
type: "label",
|
||||
text: ":" + props.emoji.name + ":",
|
||||
}, {
|
||||
text: i18n.ts.copy,
|
||||
icon: 'ph-clipboard-text ph-bold ph-lg',
|
||||
icon: "ph-clipboard-text ph-bold ph-lg",
|
||||
action: () => {
|
||||
copyToClipboard(`:${props.emoji.name}:`);
|
||||
os.success();
|
||||
}
|
||||
},
|
||||
}, {
|
||||
text: i18n.ts.license,
|
||||
icon: "ph-info ph-bold ph-lg",
|
||||
action: () => {
|
||||
os.apiGet("emoji", { name: props.emoji.name }).then(res => {
|
||||
os.alert({
|
||||
type: "info",
|
||||
text: `${res.license || i18n.ts.notSet}`,
|
||||
});
|
||||
});
|
||||
},
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in a new issue