Merge branch 'develop' of codeberg.org:calckey/calckey into develop

This commit is contained in:
ThatOneCalculator 2023-03-19 00:26:32 -07:00
commit 77912ae7b5
No known key found for this signature in database
GPG key ID: 8703CACD01000000
20 changed files with 203 additions and 6 deletions

View file

@ -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" 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." 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" defaultReaction: "Default emoji reaction for outgoing and incoming posts"
license: "License"
_sensitiveMediaDetection: _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." description: "Reduces the effort of server moderation through automatically recognizing NSFW media via Machine Learning. This will slightly increase the load on the server."

View file

@ -935,6 +935,7 @@ moveFromLabel: "引っ越し元のアカウント:"
moveFromDescription: "別のアカウントからこのアカウントにフォロワーを引き継いで引っ越したい場合、ここでエイリアスを作成しておく必要があります。必ず引っ越しを実行する前に作成してください!引っ越し元のアカウントをこのように入力してください:@person@instance.com" moveFromDescription: "別のアカウントからこのアカウントにフォロワーを引き継いで引っ越したい場合、ここでエイリアスを作成しておく必要があります。必ず引っ越しを実行する前に作成してください!引っ越し元のアカウントをこのように入力してください:@person@instance.com"
migrationConfirm: "本当にこのアカウントを {account} に引っ越しますか?一度引っ越しを行うと取り消せず、二度とこのアカウントを元の状態で使用することはできません。\nまた、引っ越し先のアカウントでエイリアスを作成したことを確認してください。" migrationConfirm: "本当にこのアカウントを {account} に引っ越しますか?一度引っ越しを行うと取り消せず、二度とこのアカウントを元の状態で使用することはできません。\nまた、引っ越し先のアカウントでエイリアスを作成したことを確認してください。"
defaultReaction: "リモートとローカルの投稿に対するデフォルトの絵文字リアクション" defaultReaction: "リモートとローカルの投稿に対するデフォルトの絵文字リアクション"
license: "ライセンス"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。"

View file

@ -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"`);
}
}

View file

@ -55,4 +55,9 @@ export class Emoji {
array: true, length: 128, default: '{}', array: true, length: 128, default: '{}',
}) })
public aliases: string[]; public aliases: string[];
@Column('varchar', {
length: 1024, nullable: true,
})
public license: string | null;
} }

View file

@ -15,6 +15,7 @@ export const EmojiRepository = db.getRepository(Emoji).extend({
host: emoji.host, host: emoji.host,
// || emoji.originalUrl してるのは後方互換性のため // || emoji.originalUrl してるのは後方互換性のため
url: emoji.publicUrl || emoji.originalUrl, url: emoji.publicUrl || emoji.originalUrl,
license: emoji.license,
}; };
}, },

View file

@ -40,5 +40,10 @@ export const packedEmojiSchema = {
optional: false, optional: false,
nullable: false, nullable: false,
}, },
license: {
type: "string",
optional: false,
nullable: true,
},
}, },
} as const; } as const;

View file

@ -75,6 +75,7 @@ export async function importCustomEmojis(
originalUrl: driveFile.url, originalUrl: driveFile.url,
publicUrl: driveFile.webpublicUrl ?? driveFile.url, publicUrl: driveFile.webpublicUrl ?? driveFile.url,
type: driveFile.webpublicType ?? driveFile.type, type: driveFile.webpublicType ?? driveFile.type,
license: emojiInfo.license,
}).then((x) => Emojis.findOneByOrFail(x.identifiers[0])); }).then((x) => Emojis.findOneByOrFail(x.identifiers[0]));
} }

View file

@ -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_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_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_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_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_deleteAllFiles from "./endpoints/admin/federation/delete-all-files.js";
import * as ep___admin_federation_refreshRemoteInstanceMetadata from "./endpoints/admin/federation/refresh-remote-instance-metadata.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_folders_update from "./endpoints/drive/folders/update.js";
import * as ep___drive_stream from "./endpoints/drive/stream.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___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___endpoint from "./endpoints/endpoint.js";
import * as ep___endpoints from "./endpoints/endpoints.js"; import * as ep___endpoints from "./endpoints/endpoints.js";
import * as ep___exportCustomEmojis from "./endpoints/export-custom-emojis.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/remove-aliases-bulk", ep___admin_emoji_removeAliasesBulk],
["admin/emoji/set-aliases-bulk", ep___admin_emoji_setAliasesBulk], ["admin/emoji/set-aliases-bulk", ep___admin_emoji_setAliasesBulk],
["admin/emoji/set-category-bulk", ep___admin_emoji_setCategoryBulk], ["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/emoji/update", ep___admin_emoji_update],
["admin/federation/delete-all-files", ep___admin_federation_deleteAllFiles], ["admin/federation/delete-all-files", ep___admin_federation_deleteAllFiles],
[ [
@ -471,6 +474,7 @@ const eps = [
["drive/folders/update", ep___drive_folders_update], ["drive/folders/update", ep___drive_folders_update],
["drive/stream", ep___drive_stream], ["drive/stream", ep___drive_stream],
["email-address/available", ep___emailAddress_available], ["email-address/available", ep___emailAddress_available],
["emoji", ep___emoji],
["endpoint", ep___endpoint], ["endpoint", ep___endpoint],
["endpoints", ep___endpoints], ["endpoints", ep___endpoints],
["export-custom-emojis", ep___exportCustomEmojis], ["export-custom-emojis", ep___exportCustomEmojis],

View file

@ -49,6 +49,7 @@ export default define(meta, paramDef, async (ps, me) => {
originalUrl: file.url, originalUrl: file.url,
publicUrl: file.webpublicUrl ?? file.url, publicUrl: file.webpublicUrl ?? file.url,
type: file.webpublicType ?? file.type, type: file.webpublicType ?? file.type,
license: null,
}).then((x) => Emojis.findOneByOrFail(x.identifiers[0])); }).then((x) => Emojis.findOneByOrFail(x.identifiers[0]));
await db.queryResultCache!.remove(["meta_emojis"]); await db.queryResultCache!.remove(["meta_emojis"]);

View file

@ -73,6 +73,7 @@ export default define(meta, paramDef, async (ps, me) => {
originalUrl: driveFile.url, originalUrl: driveFile.url,
publicUrl: driveFile.webpublicUrl ?? driveFile.url, publicUrl: driveFile.webpublicUrl ?? driveFile.url,
type: driveFile.webpublicType ?? driveFile.type, type: driveFile.webpublicType ?? driveFile.type,
license: emoji.license,
}).then((x) => Emojis.findOneByOrFail(x.identifiers[0])); }).then((x) => Emojis.findOneByOrFail(x.identifiers[0]));
await db.queryResultCache!.remove(["meta_emojis"]); await db.queryResultCache!.remove(["meta_emojis"]);

View file

@ -55,6 +55,11 @@ export const meta = {
optional: false, optional: false,
nullable: false, nullable: false,
}, },
license: {
type: "string",
optional: false,
nullable: true,
},
}, },
}, },
}, },

View file

@ -55,6 +55,11 @@ export const meta = {
optional: false, optional: false,
nullable: false, nullable: false,
}, },
license: {
type: "string",
optional: false,
nullable: true,
},
}, },
}, },
}, },

View file

@ -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"]);
});

View file

@ -34,6 +34,10 @@ export const paramDef = {
type: "string", type: "string",
}, },
}, },
license: {
type: "string",
nullable: true,
},
}, },
required: ["id", "name", "aliases"], required: ["id", "name", "aliases"],
} as const; } as const;
@ -48,6 +52,7 @@ export default define(meta, paramDef, async (ps) => {
name: ps.name, name: ps.name,
category: ps.category, category: ps.category,
aliases: ps.aliases, aliases: ps.aliases,
license: ps.license,
}); });
await db.queryResultCache!.remove(["meta_emojis"]); await db.queryResultCache!.remove(["meta_emojis"]);

View 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);
});

View file

@ -14,9 +14,11 @@
</div> </div>
<header v-if="title" :class="$style.title"><Mfm :text="title"/></header> <header v-if="title" :class="$style.title"><Mfm :text="title"/></header>
<div v-if="text" :class="$style.text"><Mfm :text="text"/></div> <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> <template v-if="input.type === 'password'" #prefix><i class="ph-password ph-bold ph-lg"></i></template>
</MkInput> </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> <MkSelect v-if="select" v-model="selectedValue" autofocus>
<template v-if="select.items"> <template v-if="select.items">
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option> <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 MkModal from '@/components/MkModal.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/form/input.vue'; import MkInput from '@/components/form/input.vue';
import MkTextarea from '@/components/form/textarea.vue';
import MkSelect from '@/components/form/select.vue'; import MkSelect from '@/components/form/select.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';

View file

@ -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: { export function inputNumber(props: {
title?: string | null; title?: string | null;
text?: string | null; text?: string | null;

View file

@ -22,6 +22,9 @@
<template #label>{{ i18n.ts.tags }}</template> <template #label>{{ i18n.ts.tags }}</template>
<template #caption>{{ i18n.ts.setMultipleBySeparatingWithSpace }}</template> <template #caption>{{ i18n.ts.setMultipleBySeparatingWithSpace }}</template>
</MkInput> </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> <MkButton danger @click="del()"><i class="ph-trash ph-bold ph-lg"></i> {{ i18n.ts.delete }}</MkButton>
</div> </div>
</div> </div>
@ -33,6 +36,7 @@ import { } from 'vue';
import XModalWindow from '@/components/MkModalWindow.vue'; import XModalWindow from '@/components/MkModalWindow.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/form/input.vue'; import MkInput from '@/components/form/input.vue';
import MkTextarea from '@/components/form/textarea.vue';
import * as os from '@/os'; import * as os from '@/os';
import { unique } from '@/scripts/array'; import { unique } from '@/scripts/array';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
@ -47,6 +51,7 @@ let name: string = $ref(props.emoji.name);
let category: string = $ref(props.emoji.category); let category: string = $ref(props.emoji.category);
let aliases: string = $ref(props.emoji.aliases.join(' ')); let aliases: string = $ref(props.emoji.aliases.join(' '));
let categories: string[] = $ref(emojiCategories); let categories: string[] = $ref(emojiCategories);
let license: string = $ref(props.emoji.license ?? '');
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'done', v: { deleted?: boolean, updated?: any }): void, (ev: 'done', v: { deleted?: boolean, updated?: any }): void,
@ -63,6 +68,7 @@ async function update() {
name, name,
category, category,
aliases: aliases.split(' '), aliases: aliases.split(' '),
license: license === '' ? null : license,
}); });
emit('done', { emit('done', {
@ -71,6 +77,7 @@ async function update() {
name, name,
category, category,
aliases: aliases.split(' '), aliases: aliases.split(' '),
license: license === '' ? null : license,
}, },
}); });

View file

@ -18,6 +18,7 @@
<MkButton inline @click="addTagBulk">Add tag</MkButton> <MkButton inline @click="addTagBulk">Add tag</MkButton>
<MkButton inline @click="removeTagBulk">Remove tag</MkButton> <MkButton inline @click="removeTagBulk">Remove tag</MkButton>
<MkButton inline @click="setTagBulk">Set tag</MkButton> <MkButton inline @click="setTagBulk">Set tag</MkButton>
<MkButton inline @click="setLicenseBulk">Set license</MkButton>
<MkButton inline danger @click="delBulk">Delete</MkButton> <MkButton inline danger @click="delBulk">Delete</MkButton>
</div> </div>
<MkPagination ref="emojisPaginationComponent" :pagination="pagination"> <MkPagination ref="emojisPaginationComponent" :pagination="pagination">
@ -258,6 +259,18 @@ const setTagBulk = async () => {
emojisPaginationComponent.value.reload(); 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 delBulk = async () => {
const { canceled } = await os.confirm({ const { canceled } = await os.confirm({
type: 'warning', type: 'warning',

View file

@ -3,7 +3,7 @@
<img :src="emoji.url" class="img" :alt="emoji.name"/> <img :src="emoji.url" class="img" :alt="emoji.name"/>
<div class="body"> <div class="body">
<div class="name _monospace">{{ emoji.name }}</div> <div class="name _monospace">{{ emoji.name }}</div>
<div class="info">{{ emoji.aliases.join(' ') }}</div> <div class="info">{{ emoji.aliases.join(" ") }}</div>
</div> </div>
</button> </button>
</template> </template>
@ -20,15 +20,26 @@ const props = defineProps<{
function menu(ev) { function menu(ev) {
os.popupMenu([{ os.popupMenu([{
type: 'label', type: "label",
text: ':' + props.emoji.name + ':', text: ":" + props.emoji.name + ":",
}, { }, {
text: i18n.ts.copy, text: i18n.ts.copy,
icon: 'ph-clipboard-text ph-bold ph-lg', icon: "ph-clipboard-text ph-bold ph-lg",
action: () => { action: () => {
copyToClipboard(`:${props.emoji.name}:`); copyToClipboard(`:${props.emoji.name}:`);
os.success(); 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); }], ev.currentTarget ?? ev.target);
} }
</script> </script>