mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-04-28 17:46:56 +00:00
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:
commit
1fc5b17082
37 changed files with 404 additions and 20 deletions
|
@ -221,9 +221,10 @@ checkActivityPubGetSignature: false
|
||||||
# For security reasons, uploading attachments from the intranet is prohibited,
|
# For security reasons, uploading attachments from the intranet is prohibited,
|
||||||
# but exceptions can be made from the following settings. Default value is "undefined".
|
# 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)).
|
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
|
||||||
#allowedPrivateNetworks: [
|
allowedPrivateNetworks: [
|
||||||
# '127.0.0.1/32'
|
'127.0.0.1/32',
|
||||||
#]
|
'192.168.65.0/24'
|
||||||
|
]
|
||||||
|
|
||||||
#customMOTD: ['Hello World', 'The sharks rule all', 'Shonks']
|
#customMOTD: ['Hello World', 'The sharks rule all', 'Shonks']
|
||||||
|
|
||||||
|
|
11
.config/docker_ci.env
Normal file
11
.config/docker_ci.env
Normal 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}"
|
31
.forgejo/workflows/tests.yml
Normal file
31
.forgejo/workflows/tests.yml
Normal 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
1
.gitignore
vendored
|
@ -39,6 +39,7 @@ coverage
|
||||||
!/.config/docker_example.yml
|
!/.config/docker_example.yml
|
||||||
!/.config/docker_example.env
|
!/.config/docker_example.env
|
||||||
!/.config/cypress-devcontainer.yml
|
!/.config/cypress-devcontainer.yml
|
||||||
|
!/.config/docker_ci.env
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
compose.yml
|
compose.yml
|
||||||
.devcontainer/compose.yml
|
.devcontainer/compose.yml
|
||||||
|
|
51
compose.local-dev.yml
Normal file
51
compose.local-dev.yml
Normal 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
46
compose.local-test.yml
Normal 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
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;
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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" 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="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 v-if="emojis.length > 0" class="emoji-count"><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,16 @@ 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) {
|
||||||
|
|
|
@ -32,7 +32,7 @@ const devConfig: UserConfig = {
|
||||||
publicDir: '../assets',
|
publicDir: '../assets',
|
||||||
base: './',
|
base: './',
|
||||||
server: {
|
server: {
|
||||||
host: 'localhost',
|
host: '0.0.0.0',
|
||||||
port: 5173,
|
port: 5173,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
|
|
42
setup-dev.sh
Executable file
42
setup-dev.sh
Executable 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
44
setup-tests.sh
Executable 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
|
|
@ -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"
|
||||||
|
|
16
tests.sh
Executable file
16
tests.sh
Executable 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
|
Loading…
Add table
Reference in a new issue