Merge pull request 'v0.1.0' (#36) from dev into stable

Reviewed-on: https://codeberg.org/yeentown/barkey/pulls/36
This commit is contained in:
zima 2024-12-24 01:59:12 +00:00
commit 1fc5b17082
37 changed files with 404 additions and 20 deletions

View file

@ -221,9 +221,10 @@ checkActivityPubGetSignature: false
# For security reasons, uploading attachments from the intranet is prohibited,
# but exceptions can be made from the following settings. Default value is "undefined".
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
#allowedPrivateNetworks: [
# '127.0.0.1/32'
#]
allowedPrivateNetworks: [
'127.0.0.1/32',
'192.168.65.0/24'
]
#customMOTD: ['Hello World', 'The sharks rule all', 'Shonks']

11
.config/docker_ci.env Normal file
View file

@ -0,0 +1,11 @@
# misskey settings
# MISSKEY_URL=https://example.tld/
# db settings
POSTGRES_PASSWORD=ci
# DATABASE_PASSWORD=${POSTGRES_PASSWORD}
POSTGRES_USER=postgres
# DATABASE_USER=${POSTGRES_USER}
POSTGRES_DB=postgres
# DATABASE_DB=${POSTGRES_DB}
DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}"

View file

@ -0,0 +1,31 @@
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
testCommit:
runs-on: yeentown-dev
container:
image: node:iron
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: ci
redis:
image: redis:latest
steps:
- uses: actions/checkout@v4
- run: |
apt-get update && apt-get install -y git wget curl build-essential python3 ffmpeg
cp .config/ci.yml .config/default.yml
cp .config/ci.yml .config/test.yml
corepack enable
corepack prepare pnpm@latest --activate
git submodule update --init
pnpm install --frozen-lockfile
pnpm run build
pnpm run migrate
pnpm run --filter='!megalodon' test
pnpm run --filter=backend --filter=misskey-js lint
pnpm run --filter=frontend --filter=frontend-embed eslint

1
.gitignore vendored
View file

@ -39,6 +39,7 @@ coverage
!/.config/docker_example.yml
!/.config/docker_example.env
!/.config/cypress-devcontainer.yml
!/.config/docker_ci.env
docker-compose.yml
compose.yml
.devcontainer/compose.yml

51
compose.local-dev.yml Normal file
View file

@ -0,0 +1,51 @@
# このconfigは、 dockerでMisskey本体を起動せず、 redisとpostgresql などだけを起動します
name: barkey-devcontainer
services:
redis:
restart: always
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- ./redis:/data
healthcheck:
test: "redis-cli ping"
interval: 5s
retries: 20
postgres:
restart: always
image: postgres:15-alpine
ports:
- "5432:5432"
env_file:
- .config/docker.env
volumes:
- ./db:/var/lib/postgresql/data
healthcheck:
test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"
interval: 5s
retries: 20
web:
restart: always
image: node:iron
ports:
- 3000:3000
- 5173:5173
working_dir: /host
volumes:
- ./:/host
command: sleep infinity
# meilisearch:
# restart: always
# image: getmeili/meilisearch:v1.3.4
# environment:
# - MEILI_NO_ANALYTICS=true
# - MEILI_ENV=production
# env_file:
# - .config/meilisearch.env
# volumes:
# - ./meili_data:/meili_data

46
compose.local-test.yml Normal file
View file

@ -0,0 +1,46 @@
# このconfigは、 dockerでMisskey本体を起動せず、 redisとpostgresql などだけを起動します
name: barkey-test
services:
redis:
restart: always
image: redis:7-alpine
ports:
- "6379:6379"
healthcheck:
test: "redis-cli ping"
interval: 5s
retries: 20
postgres:
restart: always
image: postgres:15-alpine
ports:
- "5432:5432"
env_file:
- .config/docker.env
healthcheck:
test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"
interval: 5s
retries: 20
web:
restart: always
image: node:iron
ports:
- 3000:3000
working_dir: /host
volumes:
- ./:/host
command: sleep infinity
# meilisearch:
# restart: always
# image: getmeili/meilisearch:v1.3.4
# environment:
# - MEILI_NO_ANALYTICS=true
# - MEILI_ENV=production
# env_file:
# - .config/meilisearch.env
# volumes:
# - ./meili_data:/meili_data

42
locales/index.d.ts vendored
View file

@ -11374,6 +11374,48 @@ export interface Locale extends ILocale {
* Remote followers may have incomplete or outdated activity
*/
"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: {
[lang: string]: Locale;

View file

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

View file

@ -76,6 +76,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
isSensitive: boolean;
localOnly: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
sortKey: string | null;
}, moderator?: MiUser): Promise<MiEmoji> {
const emoji = await this.emojisRepository.insertOne({
id: this.idService.gen(),
@ -91,6 +92,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
isSensitive: data.isSensitive,
localOnly: data.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction,
sortKey: data.sortKey,
});
if (data.host == null) {
@ -121,6 +123,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
isSensitive?: boolean;
localOnly?: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][];
sortKey?: string | null;
}, moderator?: MiUser): Promise<void> {
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
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,
type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined,
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction ?? undefined,
sortKey: data.sortKey,
});
this.localEmojisCache.refresh();

View file

@ -34,6 +34,7 @@ export class EmojiEntityService {
localOnly: emoji.localOnly ? true : undefined,
isSensitive: emoji.isSensitive ? true : undefined,
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
sortKey: emoji.sortKey,
};
}
@ -62,6 +63,7 @@ export class EmojiEntityService {
isSensitive: emoji.isSensitive,
localOnly: emoji.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
sortKey: emoji.sortKey,
};
}

View file

@ -81,4 +81,9 @@ export class MiEmoji {
array: true, length: 128, default: '{}',
})
public roleIdsThatCanBeUsedThisEmojiAsReaction: string[];
@Column('varchar', {
length: 64, nullable: true,
})
public sortKey: string | null;
}

View file

@ -44,6 +44,10 @@ export const packedEmojiSimpleSchema = {
format: 'id',
},
},
sortKey: {
type: 'string',
optional: false, nullable: true,
},
},
} as const;
@ -102,5 +106,9 @@ export const packedEmojiDetailedSchema = {
format: 'id',
},
},
sortKey: {
type: 'string',
optional: false, nullable: true,
},
},
} as const;

View file

@ -105,6 +105,7 @@ export class ImportCustomEmojisProcessorService {
isSensitive: emojiInfo.isSensitive,
localOnly: emojiInfo.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
sortKey: emojiInfo.sortKey?.normalize('NFC'),
});
} catch (e) {
if (e instanceof Error || typeof e === 'string') {

View file

@ -56,6 +56,7 @@ export const paramDef = {
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
type: 'string',
} },
sortKey: { type: 'string', nullable: true },
},
required: ['name', 'fileId'],
} as const;
@ -91,6 +92,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSensitive: ps.isSensitive ?? false,
localOnly: ps.localOnly ?? false,
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
sortKey: ps.sortKey?.normalize('NFC') ?? null,
}, me);
return this.emojiEntityService.packDetailed(emoji);

View file

@ -97,6 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSensitive: emoji.isSensitive,
localOnly: emoji.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
sortKey: emoji.sortKey?.normalize('NFC') ?? null,
}, me);
return this.emojiEntityService.packDetailed(addedEmoji);

View file

@ -56,6 +56,10 @@ export const meta = {
type: 'string',
optional: false, nullable: false,
},
sortKey: {
type: 'string',
optional: false, nullable: true,
},
},
},
},

View file

@ -56,6 +56,7 @@ export const paramDef = {
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
type: 'string',
} },
sortKey: { type: 'string', nullable: true },
},
anyOf: [
{ required: ['id'] },
@ -104,6 +105,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSensitive: ps.isSensitive,
localOnly: ps.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
sortKey: ps.sortKey,
}, me);
});
}

View file

@ -31,7 +31,7 @@ const devConfig: UserConfig = {
publicDir: '../assets',
base: '/embed',
server: {
host: 'localhost',
host: true,
port: 5174,
proxy: {
'/api': {

View file

@ -64,6 +64,7 @@ export function getConfig(): UserConfig {
server: {
port: 5174,
host: true
},
plugins: [

View file

@ -47,6 +47,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #key>{{ i18n.ts.emojiUrl }}</template>
<template #value>
<MkLink :url="emoji.url" target="_blank">{{ emoji.url }}</MkLink>
</template>
</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>

View file

@ -6,9 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<!-- このコンポーネントの要素のclassは親から利用されるのでむやみに弄らないこと -->
<!-- フォルダの中にはカスタム絵文字だけUnicode絵文字もこっち -->
<section v-if="!hasChildSection" v-panel style="border-radius: var(--radius-sm); border-bottom: 0.5px solid var(--divider);">
<header class="_acrylic" @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 }})
<section v-if="!hasChildSection" v-panel :data-shown="shown" style="border-radius: var(--radius-sm); border-bottom: 0.5px solid var(--divider);">
<header class="_acrylic" :data-shown="shown" @click="shown = !shown">
<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>
<div v-if="shown" class="body">
<button
@ -26,16 +27,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</section>
<!-- フォルダの中にはカスタム絵文字やフォルダがある -->
<section v-else v-panel style="border-radius: var(--radius-sm); border-bottom: 0.5px solid var(--divider);">
<header class="_acrylic" @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 }})
<section v-else v-panel :data-shown="shown" style="border-radius: var(--radius-sm); border-bottom: 0.5px solid var(--divider);">
<header class="_acrylic" :data-shown="shown" @click="shown = !shown">
<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 v-if="emojis.length > 0" class="emoji-count"><i class="ph-smiley ph-bold"></i> {{ emojis.length }}</span>
</header>
<div v-if="shown" style="padding-left: 9px;">
<MkEmojiPickerSection
v-for="child in customEmojiTree"
:key="`custom:${child.value}`"
: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"
:customEmojiTree="child.children"
@chosen="nestedChosen"
@ -43,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ child.value || i18n.ts.other }}
</MkEmojiPickerSection>
</div>
<div v-if="shown" class="body">
<div v-if="shown && emojis.length > 0" class="body">
<button
v-for="emoji in emojis"
:key="emoji"
@ -64,7 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref, computed, Ref } from 'vue';
import { CustomEmojiFolderTree, getEmojiName } from '@@/js/emojilist.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';
const props = defineProps<{

View file

@ -91,7 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only
v-for="child in customEmojiFolderRoot.children"
:key="`custom:${child.value}`"
: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}:`))"
:hasChildSection="child.children.length !== 0"
:customEmojiTree="child.children"
@ -102,7 +102,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div v-once class="group">
<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 class="tabs">
@ -133,7 +133,7 @@ import { isTouchUsing } from '@/scripts/touch.js';
import { deviceKind } from '@/scripts/device-kind.js';
import { i18n } from '@/i18n.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 { 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());
let currentNode: CustomEmojiFolderTree = root;
let currentPath = [];
for (const part of parts) {
currentPath.push(part);
let existingNode = currentNode.children.find((node) => node.value === part);
if (!existingNode) {
const newNode: CustomEmojiFolderTree = { value: part, category: input, children: [] };
const newNode: CustomEmojiFolderTree = { value: part, category: currentPath.join("/"), children: [] };
currentNode.children.push(newNode);
existingNode = newNode;
}
@ -501,6 +503,8 @@ defineExpose({
display: flex;
flex-direction: column;
/* Required to make header background blur work */
contain: layout style;
&.s1 {
--eachSize: 40px;
@ -698,18 +702,34 @@ defineExpose({
::v-deep(section) {
> header {
position: sticky;
top: 0;
left: 0;
line-height: 28px;
z-index: 1;
padding: 0 8px;
font-size: 12px;
cursor: pointer;
/* Closed sections should not have sticky headers. */
&:not([data-shown=false]) {
position: sticky;
z-index: 1;
}
&:hover {
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 {

View file

@ -20,6 +20,16 @@ export const customEmojiCategories = computed<[ ...string[], 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>();
watch(customEmojis, emojis => {
customEmojisMap.clear();

View file

@ -68,6 +68,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkFolder>
<MkSwitch v-model="isSensitive">isSensitive</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>
</div>
</MkSpacer>
@ -107,6 +110,7 @@ const isSensitive = ref(props.emoji ? props.emoji.isSensitive : false);
const localOnly = ref(props.emoji ? props.emoji.localOnly : false);
const roleIdsThatCanBeUsedThisEmojiAsReaction = ref(props.emoji ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
const rolesThatCanBeUsedThisEmojiAsReaction = ref<Misskey.entities.Role[]>([]);
const sortKey = ref<string>(props.emoji ? (props.emoji.sortKey ?? '') : '');
const file = ref<Misskey.entities.DriveFile>();
watch(roleIdsThatCanBeUsedThisEmojiAsReaction, async () => {
@ -153,6 +157,7 @@ async function done() {
isSensitive: isSensitive.value,
localOnly: localOnly.value,
roleIdsThatCanBeUsedThisEmojiAsReaction: rolesThatCanBeUsedThisEmojiAsReaction.value.map(x => x.id),
sortKey: sortKey.value === '' ? null : sortKey.value,
};
if (file.value) {

View file

@ -32,7 +32,7 @@ const devConfig: UserConfig = {
publicDir: '../assets',
base: './',
server: {
host: 'localhost',
host: '0.0.0.0',
port: 5173,
proxy: {
'/api': {

42
setup-dev.sh Executable file
View file

@ -0,0 +1,42 @@
#!/bin/bash
read -p "Reset databases? [y/N] " cleardb
case $cleardb in
[Yy] )
echo "Clearning postgres..."
rm -rf ./db
sleep 1
echo "Clearing redis..."
rm -rf ./redis
sleep 1
;;
* )
;;
esac
cp .config/ci.yml .config/default.yml
cp .config/ci.yml .config/test.yml
cp .config/docker_ci.env .config/docker.env
cp compose.local-dev.yml compose.yml
echo Creating environment...
docker compose up -d
docker compose exec web apt-get update
docker compose exec web apt-get install -y bash
echo Installing dependencies...
docker compose exec web apt-get install -y git wget curl build-essential python3 ffmpeg
docker compose exec web corepack enable
docker compose exec web corepack prepare pnpm@latest --activate
docker compose exec web git submodule update --init
docker compose exec web pnpm install --frozen-lockfile
echo Environment is ready.
echo 5173 for dev, and 3000 for prod.
echo Do not forget to \`pnpm migrate\` before your first launch!
docker compose exec web bash
echo Stopping containers...
docker compose stop
rm -f compose.yml

44
setup-tests.sh Executable file
View file

@ -0,0 +1,44 @@
#!/bin/bash
echo "During testing, your local postgres and redis databases will not be touched."
echo "This script will remove the build directories in the following locations:"
echo " ./built"
echo
echo "Do you want to continue? [Y/n]"
read -p "> " confirmation
case $confirmation in
[Yy]* )
echo "Continuing..."
;;
[Nn] )
echo "Exiting..."
exit 0
;;
esac
cp .config/ci.yml .config/default.yml
cp .config/ci.yml .config/test.yml
cp .config/docker_ci.env .config/docker.env
cp compose.local-test.yml compose.yml
echo Verifying containers are down...
docker compose down
docker compose rm
echo Removing any preexisting builds and databases.
echo This may take a few moments...
rm -rf ./built
echo Creating environment
docker compose up -d
docker compose exec web apt-get update
docker compose exec web apt-get install -y bash
echo Building and running tests...
docker compose exec web /host/tests.sh
echo Cleaning up...
docker compose down
docker compose rm
rm -f compose.yml

View file

@ -397,3 +397,16 @@ _auth:
allowed: "Allowed"
_announcement:
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"

16
tests.sh Executable file
View file

@ -0,0 +1,16 @@
#!/bin/bash
# Do not run this on your local machine. Instead, execute setup-tests.sh and it
# will automatically create, test, and tear down the testing environment.
set -e
apt-get update && apt-get install -y git wget curl build-essential python3 ffmpeg
corepack enable
corepack prepare pnpm@latest --activate
git submodule update --init
pnpm install --frozen-lockfile
pnpm run build
pnpm run migrate
pnpm run --filter='!megalodon' test
pnpm run --filter=backend --filter=misskey-js lint
pnpm run --filter=frontend --filter=frontend-embed eslint