Compare commits

..

11 Commits

Author SHA1 Message Date
syuilo
d38fc490ad 10.97.2 2019-03-18 02:22:28 +09:00
syuilo
662167e792 Remove unused import 2019-03-18 02:21:29 +09:00
syuilo
36c91f03d9 10.97.1 2019-03-18 01:59:59 +09:00
syuilo
33ccee26b5 Update black.json5 2019-03-18 01:29:58 +09:00
syuilo
ed5cb991e3 10.97.0 2019-03-18 01:11:18 +09:00
syuilo
bea84ec2bf New Crowdin translations (#4509)
* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (English)
2019-03-18 01:10:03 +09:00
syuilo
08c176e549 不明なリアクションのフォールバックに star を使うようにするオプション 2019-03-18 01:03:35 +09:00
syuilo
810ed50976 Update black.json5 2019-03-18 00:41:30 +09:00
MeiMei
2684541693 Custom reaction (#4517)
* Custom reaction

* increase limit of reactions/delete

* リアクションの場合は OS標準の絵文字を使用 を迂回する

* カスタムリアクションを無効にする設定

* fix

* disableCustomReaction --> enableEmojiReaction

* Avoid MFM rendering

* 🎨

* 🎨

* Auto accept

* custom emoji reaction

* Improve usability

* Extract emojiRegex

* Fix

* Clean up

* 🎨

* 🎨

* toDbReaction で reaction は必須に

あとフォールバックは like に

* Clean up

* Make required

* 3eb08748fe (r266241728)

* Refactor

* Allow null
2019-03-18 00:03:57 +09:00
syuilo
a5b12bac54 Change default dark theme 2019-03-17 20:48:55 +09:00
syuilo
fea1b06e43 Update black.json5 2019-03-17 20:46:14 +09:00
35 changed files with 380 additions and 91 deletions

View File

@@ -5,6 +5,20 @@ If you encounter any problems with updating, please try the following:
1. `npm run clean` or `npm run cleanall` 1. `npm run clean` or `npm run cleanall`
2. Retry update (Don't forget `npm i`) 2. Retry update (Don't forget `npm i`)
10.97.2
----------
* ビルド時に警告が出ないように修正
10.97.1
----------
* デザインの調整
10.97.0
----------
* リアクションに絵文字やカスタム絵文字を使えるように
* 不明なリアクションのフォールバックに star を使えるように
* デザインの調整
10.96.0 10.96.0
---------- ----------
* 連合ユーザーの投稿に対してActivityPubオブジェクトを要求されたら元のインスタンスにリダイレクトするように * 連合ユーザーの投稿に対してActivityPubオブジェクトを要求されたら元のインスタンスにリダイレクトするように

View File

@@ -2,12 +2,12 @@
meta: meta:
lang: "Čeština" lang: "Čeština"
common: common:
misskey: "⭐ ve fediverse" misskey: "⭐ ve fedivesmíru"
about-title: "⭐ ve fediverse." about-title: "⭐ ve fedivesmíru."
about: "Děkujeme, že jste našli Misskey. Misskey je <b>decentralizovaná mikroblogovací platforma</b> zrozená na Zemi. Neboť existuje ve Fediverse (vesmíru, kde jsou organizovány různé sociální sítě), je vzájemně propojena s jinými sociálními sítěmi. Co takhle si chvilku odpočinout od ruchu a shonu města a ponořit se do nového internetu?" about: "Děkujeme, že jste našli Misskey. Misskey je <b>decentralizovaná mikroblogovací platforma</b> zrozená na Zemi. Neboť existuje ve fedivesmíru (vesmíru, kde jsou organizovány různé sociální sítě), je vzájemně propojena s jinými sociálními sítěmi. Co takhle si chvilku odpočinout od ruchu a shonu města a ponořit se do nového internetu?"
intro: intro:
title: "Co je Misskey?" title: "Co je Misskey?"
about: "Misskey je open-source <b>decentralizovaný mikroblogovací software</b>. Má sofistikované, zcela přizpůsobitelné uživatelské rozhraní, různé způsoby reagování na příspěvky, bezplatné úložiště souborů nabízející integrovaný management system, a další pokročilé vlastnosti. Misskey je navíc připojeno k systému sítí zvanému „fediverse“, který nám dovoluje komunikovat s uživateli na jiných sociálních sítí. Pokud například něco napíšete, nebude to posláno pouze uživatelů Misskey, ale také lidem na sítích Mastodon a Pleroma. Jen si představte, že planeta posílá jiné planetě rádiový signál, aby s ní komunikovala." about: "Misskey je open-source <b>decentralizovaný mikroblogovací software</b>. Má sofistikované, zcela přizpůsobitelné uživatelské rozhraní, různé způsoby reagování na příspěvky, bezplatné úložiště souborů nabízející integrovaný management system, a další pokročilé vlastnosti. Misskey je navíc připojeno k systému sítí zvanému „fedivesmír“ nebo „fediverse“, který nám dovoluje komunikovat s uživateli na jiných sociálních sítí. Pokud například něco napíšete, nebude to posláno pouze uživatelů Misskey, ale také lidem na sítích Mastodon a Pleroma. Jen si představte, že planeta posílá jiné planetě rádiový signál, aby s ní komunikovala."
features: "Vlastnosti" features: "Vlastnosti"
rich-contents: "Příspěvky" rich-contents: "Příspěvky"
rich-contents-desc: "Pouze napište svoje nápady, žhavá témata a cokoliv, co chcete sdílet. Můžete ozdobit svá slova, připojit vaše oblíbené obrázky, posílat soubory včetně videí či vytvořit hlasování to je jen několik věcí, co můžete dělat s Misskey!" rich-contents-desc: "Pouze napište svoje nápady, žhavá témata a cokoliv, co chcete sdílet. Můžete ozdobit svá slova, připojit vaše oblíbené obrázky, posílat soubory včetně videí či vytvořit hlasování to je jen několik věcí, co můžete dělat s Misskey!"
@@ -300,7 +300,7 @@ common/views/pages/explore.vue:
recently-updated-users: "Nedávno aktívni uživatelé" recently-updated-users: "Nedávno aktívni uživatelé"
recently-registered-users: "Nedávno registrovaní uživatelé" recently-registered-users: "Nedávno registrovaní uživatelé"
popular-tags: "Populární tagy" popular-tags: "Populární tagy"
federated: "Z fediverse" federated: "Z fedivesmíru"
explore: "Prozkoumat {host}" explore: "Prozkoumat {host}"
common/views/components/url-preview.vue: common/views/components/url-preview.vue:
enable-player: "Otevřít v přehrávači" enable-player: "Otevřít v přehrávači"
@@ -836,7 +836,7 @@ admin/views/index.vue:
emoji: "Emoji" emoji: "Emoji"
moderators: "Moderátoři" moderators: "Moderátoři"
users: "Uživatelé" users: "Uživatelé"
federation: "Z fediversu" federation: "Federovaná"
announcements: "Oznámení" announcements: "Oznámení"
hashtags: "Hashtagy" hashtags: "Hashtagy"
queue: "Fronta úloh" queue: "Fronta úloh"
@@ -848,7 +848,7 @@ admin/views/dashboard.vue:
drive: "Disk" drive: "Disk"
instances: "Instance" instances: "Instance"
this-instance: "Tato instance" this-instance: "Tato instance"
federated: "Z fediversu" federated: "Federovaná"
admin/views/abuse.vue: admin/views/abuse.vue:
details: "Popis" details: "Popis"
remove-report: "Odstranit" remove-report: "Odstranit"
@@ -995,14 +995,15 @@ admin/views/announcements.vue:
admin/views/hashtags.vue: admin/views/hashtags.vue:
hided-tags: "Skryté tagy" hided-tags: "Skryté tagy"
admin/views/federation.vue: admin/views/federation.vue:
federation: "Z fediversu" instance: "Instance"
host: "Hostitel" host: "Hostitel"
notes: "Poznámky" notes: "Poznámky"
users: "Uživatelé" users: "Uživatelé"
caught-at: "Vytvořeno"
status: "Status" status: "Status"
latest-request-received-at: "Poslední požadavek přijat" latest-request-received-at: "Poslední požadavek přijat"
block: "Blokován" block: "Blokován"
instances: "Instance" instances: "Federovaná"
states: states:
all: "Všechny" all: "Všechny"
blocked: "Blokován" blocked: "Blokován"

View File

@@ -1113,6 +1113,7 @@ admin/views/instance.vue:
disable-local-timeline: "Disable the Local Timeline" disable-local-timeline: "Disable the Local Timeline"
disable-global-timeline: "Disable global timeline" disable-global-timeline: "Disable global timeline"
disabling-timelines-info: "Even if you disable these timelines, the administrator as well as moderators can use them continually." disabling-timelines-info: "Even if you disable these timelines, the administrator as well as moderators can use them continually."
enable-emoji-reaction: "Enable pictograms for reactions"
invite: "Invite" invite: "Invite"
save: "Save" save: "Save"
saved: "Saved" saved: "Saved"
@@ -1275,12 +1276,13 @@ admin/views/announcements.vue:
admin/views/hashtags.vue: admin/views/hashtags.vue:
hided-tags: "Hidden Tags" hided-tags: "Hidden Tags"
admin/views/federation.vue: admin/views/federation.vue:
federation: "Federation" instance: "Instance"
host: "Host" host: "Host"
notes: "Notes" notes: "Notes"
users: "Users" users: "Users"
following: "Following" following: "Following"
followers: "Followers" followers: "Followers"
caught-at: "Created at"
status: "Statuses" status: "Statuses"
latest-request-sent-at: "Time of last request sent" latest-request-sent-at: "Time of last request sent"
latest-request-received-at: "Last request received at" latest-request-received-at: "Last request received at"
@@ -1289,7 +1291,7 @@ admin/views/federation.vue:
block: "Block" block: "Block"
marked-as-closed: "Marked as closed" marked-as-closed: "Marked as closed"
lookup: "Look up" lookup: "Look up"
instances: "Instances" instances: "Federated"
instance-not-registered: "The instance has not been discovered" instance-not-registered: "The instance has not been discovered"
sort: "Sort by" sort: "Sort by"
sorts: sorts:

View File

@@ -815,11 +815,11 @@ admin/views/announcements.vue:
remove: "eliminar" remove: "eliminar"
add: "Agregar" add: "Agregar"
admin/views/federation.vue: admin/views/federation.vue:
instance: "Instancia"
host: "Host" host: "Host"
following: "Siguiendo" following: "Siguiendo"
status: "Estado" status: "Estado"
block: "Bloquear" block: "Bloquear"
instances: "Instancia"
states: states:
all: "Todo" all: "Todo"
blocked: "Bloquear" blocked: "Bloquear"

View File

@@ -1142,12 +1142,13 @@ admin/views/announcements.vue:
admin/views/hashtags.vue: admin/views/hashtags.vue:
hided-tags: "Tags cachés" hided-tags: "Tags cachés"
admin/views/federation.vue: admin/views/federation.vue:
federation: "Fédération" instance: "Instance"
host: "Hôte" host: "Hôte"
notes: "Notes" notes: "Notes"
users: "Utilisateur·rice·s" users: "Utilisateur·rice·s"
following: "Abonnements" following: "Abonnements"
followers: "Abonné·e·s" followers: "Abonné·e·s"
caught-at: "Créé le"
status: "Statuts" status: "Statuts"
latest-request-sent-at: "Dernière requête envoyée" latest-request-sent-at: "Dernière requête envoyée"
latest-request-received-at: "Dernière requête reçue" latest-request-received-at: "Dernière requête reçue"
@@ -1155,7 +1156,7 @@ admin/views/federation.vue:
block: "Bloquer" block: "Bloquer"
marked-as-closed: "Marquées comme fermées" marked-as-closed: "Marquées comme fermées"
lookup: "Recherche" lookup: "Recherche"
instances: "Instances" instances: "Fédérées"
sort: "Trier par" sort: "Trier par"
sorts: sorts:
caughtAtAsc: "Date dinscription (Ascendant)" caughtAtAsc: "Date dinscription (Ascendant)"

View File

@@ -1238,6 +1238,8 @@ admin/views/instance.vue:
disable-local-timeline: "ローカルタイムラインを無効にする" disable-local-timeline: "ローカルタイムラインを無効にする"
disable-global-timeline: "グローバルタイムラインを無効にする" disable-global-timeline: "グローバルタイムラインを無効にする"
disabling-timelines-info: "これらのタイムラインを無効にしても、管理者およびモデレーターは引き続き利用できます。" disabling-timelines-info: "これらのタイムラインを無効にしても、管理者およびモデレーターは引き続き利用できます。"
enable-emoji-reaction: "リアクションに絵文字を使えるようにする"
use-star-for-reaction-fallback: "不明なリアクションのフォールバックに star を使う"
invite: "招待" invite: "招待"
save: "保存" save: "保存"
saved: "保存しました" saved: "保存しました"

View File

@@ -988,7 +988,7 @@ admin/views/announcements.vue:
add: "増やす" add: "増やす"
saved: "保存したで!" saved: "保存したで!"
admin/views/federation.vue: admin/views/federation.vue:
federation: "連合" instance: "インスタンス"
host: "ホスト" host: "ホスト"
notes: "投稿" notes: "投稿"
users: "ユーザー" users: "ユーザー"
@@ -997,7 +997,7 @@ admin/views/federation.vue:
status: "ステータス" status: "ステータス"
block: "ブロック" block: "ブロック"
lookup: "照会" lookup: "照会"
instances: "インスタンス" instances: "連合"
states: states:
all: "すべて" all: "すべて"
blocked: "ブロック" blocked: "ブロック"

View File

@@ -1275,12 +1275,13 @@ admin/views/announcements.vue:
admin/views/hashtags.vue: admin/views/hashtags.vue:
hided-tags: "Hidden Tags" hided-tags: "Hidden Tags"
admin/views/federation.vue: admin/views/federation.vue:
federation: "연합" instance: "인스턴스"
host: "호스트" host: "호스트"
notes: "글" notes: "글"
users: "사용자" users: "사용자"
following: "팔로우 중" following: "팔로우 중"
followers: "팔로워" followers: "팔로워"
caught-at: "등록 날짜"
status: "상태" status: "상태"
latest-request-sent-at: "마지막으로 요청을 전송한 시간" latest-request-sent-at: "마지막으로 요청을 전송한 시간"
latest-request-received-at: "마지막으로 요청을 받은 시간" latest-request-received-at: "마지막으로 요청을 받은 시간"
@@ -1289,7 +1290,7 @@ admin/views/federation.vue:
block: "차단" block: "차단"
marked-as-closed: "폐쇄된 것으로 표시" marked-as-closed: "폐쇄된 것으로 표시"
lookup: "조회" lookup: "조회"
instances: "인스턴스" instances: "연합"
instance-not-registered: "해당 인스턴스가 등록되어 있지 않습니다" instance-not-registered: "해당 인스턴스가 등록되어 있지 않습니다"
sort: "정렬" sort: "정렬"
sorts: sorts:

View File

@@ -124,10 +124,12 @@ common:
fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół" fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół"
note-visibility: "Widoczność wpisów" note-visibility: "Widoczność wpisów"
web-search-engine: "Wyszukiwarka internetowa" web-search-engine: "Wyszukiwarka internetowa"
line-width: "Szerokości linii"
line-width-thin: "Cienka" line-width-thin: "Cienka"
line-width-normal: "Normalna" line-width-normal: "Normalna"
line-width-thick: "Gruba" line-width-thick: "Gruba"
font-size: "Rozmiar tekstu" font-size: "Rozmiar tekstu"
font-size-x-small: "Małe"
font-size-medium: "Normalna" font-size-medium: "Normalna"
font-size-large: "Trochę duży" font-size-large: "Trochę duży"
font-size-x-large: "Duży" font-size-x-large: "Duży"
@@ -143,6 +145,10 @@ common:
wallpaper: "Tapeta" wallpaper: "Tapeta"
choose-wallpaper: "Wybierz tapetę" choose-wallpaper: "Wybierz tapetę"
timeline: "Oś czasu" timeline: "Oś czasu"
sound: "Dźwięk"
test: "Test"
update: "Aktualizacja Misskey"
version: "Wersja:"
navbar-position-left: "Z lewej" navbar-position-left: "Z lewej"
search: "Szukaj" search: "Szukaj"
delete: "Usuń" delete: "Usuń"
@@ -941,13 +947,14 @@ admin/views/announcements.vue:
are-you-sure: "Usunąć \"$1\"?" are-you-sure: "Usunąć \"$1\"?"
removed: "Usunięto" removed: "Usunięto"
admin/views/federation.vue: admin/views/federation.vue:
instance: "Instancja"
notes: "Wpis" notes: "Wpis"
users: "Użytkownicy" users: "Użytkownicy"
following: "Śledzisz" following: "Śledzisz"
followers: "Śledzący" followers: "Śledzący"
caught-at: "Utworzono"
status: "Stan" status: "Stan"
block: "Zablokuj" block: "Zablokuj"
instances: "Instancja"
sort: "Sortuj" sort: "Sortuj"
states: states:
all: "Wszyscy" all: "Wszyscy"

View File

@@ -1275,12 +1275,13 @@ admin/views/announcements.vue:
admin/views/hashtags.vue: admin/views/hashtags.vue:
hided-tags: "隐藏标签" hided-tags: "隐藏标签"
admin/views/federation.vue: admin/views/federation.vue:
federation: "联合" instance: ""
host: "主机名" host: "主机名"
notes: "帖子" notes: "帖子"
users: "用户" users: "用户"
following: "正在关注" following: "正在关注"
followers: "关注者" followers: "关注者"
caught-at: "注册日期"
status: "状态" status: "状态"
latest-request-sent-at: "上次发送的请求" latest-request-sent-at: "上次发送的请求"
latest-request-received-at: "上次收到的请求" latest-request-received-at: "上次收到的请求"
@@ -1289,7 +1290,7 @@ admin/views/federation.vue:
block: "拉黑" block: "拉黑"
marked-as-closed: "标记为已关闭" marked-as-closed: "标记为已关闭"
lookup: "查询" lookup: "查询"
instances: "实例" instances: "联合"
instance-not-registered: "实例未注册" instance-not-registered: "实例未注册"
sort: "排序" sort: "排序"
sorts: sorts:

View File

@@ -1,7 +1,7 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "10.96.0", "version": "10.97.2",
"codename": "nighthike", "codename": "nighthike",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -25,6 +25,8 @@
<ui-switch v-model="disableLocalTimeline">{{ $t('disable-local-timeline') }}</ui-switch> <ui-switch v-model="disableLocalTimeline">{{ $t('disable-local-timeline') }}</ui-switch>
<ui-switch v-model="disableGlobalTimeline">{{ $t('disable-global-timeline') }}</ui-switch> <ui-switch v-model="disableGlobalTimeline">{{ $t('disable-global-timeline') }}</ui-switch>
<ui-info>{{ $t('disabling-timelines-info') }}</ui-info> <ui-info>{{ $t('disabling-timelines-info') }}</ui-info>
<ui-switch v-model="enableEmojiReaction">{{ $t('enable-emoji-reaction') }}</ui-switch>
<ui-switch v-model="useStarForReactionFallback">{{ $t('use-star-for-reaction-fallback') }}</ui-switch>
</section> </section>
<section class="fit-bottom"> <section class="fit-bottom">
<header><fa icon="cloud"/> {{ $t('drive-config') }}</header> <header><fa icon="cloud"/> {{ $t('drive-config') }}</header>
@@ -155,6 +157,8 @@ export default Vue.extend({
disableRegistration: false, disableRegistration: false,
disableLocalTimeline: false, disableLocalTimeline: false,
disableGlobalTimeline: false, disableGlobalTimeline: false,
enableEmojiReaction: true,
useStarForReactionFallback: false,
mascotImageUrl: null, mascotImageUrl: null,
bannerUrl: null, bannerUrl: null,
errorImageUrl: null, errorImageUrl: null,
@@ -206,6 +210,8 @@ export default Vue.extend({
this.disableRegistration = meta.disableRegistration; this.disableRegistration = meta.disableRegistration;
this.disableLocalTimeline = meta.disableLocalTimeline; this.disableLocalTimeline = meta.disableLocalTimeline;
this.disableGlobalTimeline = meta.disableGlobalTimeline; this.disableGlobalTimeline = meta.disableGlobalTimeline;
this.enableEmojiReaction = meta.enableEmojiReaction;
this.useStarForReactionFallback = meta.useStarForReactionFallback;
this.mascotImageUrl = meta.mascotImageUrl; this.mascotImageUrl = meta.mascotImageUrl;
this.bannerUrl = meta.bannerUrl; this.bannerUrl = meta.bannerUrl;
this.errorImageUrl = meta.errorImageUrl; this.errorImageUrl = meta.errorImageUrl;
@@ -267,6 +273,8 @@ export default Vue.extend({
disableRegistration: this.disableRegistration, disableRegistration: this.disableRegistration,
disableLocalTimeline: this.disableLocalTimeline, disableLocalTimeline: this.disableLocalTimeline,
disableGlobalTimeline: this.disableGlobalTimeline, disableGlobalTimeline: this.disableGlobalTimeline,
enableEmojiReaction: this.enableEmojiReaction,
useStarForReactionFallback: this.useStarForReactionFallback,
mascotImageUrl: this.mascotImageUrl, mascotImageUrl: this.mascotImageUrl,
bannerUrl: this.bannerUrl, bannerUrl: this.bannerUrl,
errorImageUrl: this.errorImageUrl, errorImageUrl: this.errorImageUrl,

View File

@@ -29,7 +29,11 @@ export default Vue.extend({
customEmojis: { customEmojis: {
required: false, required: false,
default: () => [] default: () => []
} },
isReaction: {
type: Boolean,
default: false
},
}, },
data() { data() {
@@ -46,7 +50,7 @@ export default Vue.extend({
}, },
useOsDefaultEmojis(): boolean { useOsDefaultEmojis(): boolean {
return this.$store.state.device.useOsDefaultEmojis; return this.$store.state.device.useOsDefaultEmojis && !this.isReaction;
} }
}, },

View File

@@ -1,19 +1,5 @@
<template> <template>
<span class="mk-reaction-icon"> <mk-emoji :emoji="str.startsWith(':') ? null : str" :name="str.startsWith(':') ? str.substr(1, str.length - 2) : null" :is-reaction="true" :custom-emojis="customEmojis" :normal="true"/>
<img v-if="reaction == 'like'" src="https://twemoji.maxcdn.com/2/svg/1f44d.svg" :alt="$t('@.reactions.like')">
<img v-if="reaction == 'love'" src="https://twemoji.maxcdn.com/2/svg/2764.svg" :alt="$t('@.reactions.love')">
<img v-if="reaction == 'laugh'" src="https://twemoji.maxcdn.com/2/svg/1f606.svg" :alt="$t('@.reactions.laugh')">
<img v-if="reaction == 'hmm'" src="https://twemoji.maxcdn.com/2/svg/1f914.svg" :alt="$t('@.reactions.hmm')">
<img v-if="reaction == 'surprise'" src="https://twemoji.maxcdn.com/2/svg/1f62e.svg" :alt="$t('@.reactions.surprise')">
<img v-if="reaction == 'congrats'" src="https://twemoji.maxcdn.com/2/svg/1f389.svg" :alt="$t('@.reactions.congrats')">
<img v-if="reaction == 'angry'" src="https://twemoji.maxcdn.com/2/svg/1f4a2.svg" :alt="$t('@.reactions.angry')">
<img v-if="reaction == 'confused'" src="https://twemoji.maxcdn.com/2/svg/1f625.svg" :alt="$t('@.reactions.confused')">
<img v-if="reaction == 'rip'" src="https://twemoji.maxcdn.com/2/svg/1f607.svg" :alt="$t('@.reactions.rip')">
<template v-if="reaction == 'pudding'">
<img v-if="$store.getters.isSignedIn && $store.state.settings.iLikeSushi" src="https://twemoji.maxcdn.com/2/svg/1f363.svg" :alt="$t('@.reactions.pudding')">
<img v-else src="https://twemoji.maxcdn.com/2/svg/1f36e.svg" :alt="$t('@.reactions.pudding')">
</template>
</span>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -21,7 +7,35 @@ import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
export default Vue.extend({ export default Vue.extend({
i18n: i18n(), i18n: i18n(),
props: ['reaction'] props: {
reaction: {
type: String,
required: true
},
},
data() {
return {
customEmojis: (this.$root.getMetaSync() || { emojis: [] }).emojis || []
};
},
computed: {
str(): any {
switch (this.reaction) {
case 'like': return '👍';
case 'love': return '❤';
case 'laugh': return '😆';
case 'hmm': return '🤔';
case 'surprise': return '😮';
case 'congrats': return '🎉';
case 'angry': return '💢';
case 'confused': return '😥';
case 'rip': return '😇';
case 'pudding': return (this.$store.getters.isSignedIn && this.$store.state.settings.iLikeSushi) ? '🍣' : '🍮';
case 'star': return '⭐';
default: return this.reaction;
}
},
},
}); });
</script> </script>

View File

@@ -3,7 +3,7 @@
<div class="backdrop" ref="backdrop" @click="close"></div> <div class="backdrop" ref="backdrop" @click="close"></div>
<div class="popover" :class="{ isMobile: $root.isMobile }" ref="popover"> <div class="popover" :class="{ isMobile: $root.isMobile }" ref="popover">
<p v-if="!$root.isMobile">{{ title }}</p> <p v-if="!$root.isMobile">{{ title }}</p>
<div ref="buttons" :class="{ showFocus }"> <div class="buttons" ref="buttons" :class="{ showFocus }">
<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" :title="$t('@.reactions.like')" v-particle><mk-reaction-icon reaction="like"/></button> <button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" :title="$t('@.reactions.like')" v-particle><mk-reaction-icon reaction="like"/></button>
<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" :title="$t('@.reactions.love')" v-particle><mk-reaction-icon reaction="love"/></button> <button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" :title="$t('@.reactions.love')" v-particle><mk-reaction-icon reaction="love"/></button>
<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" :title="$t('@.reactions.laugh')" v-particle><mk-reaction-icon reaction="laugh"/></button> <button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" :title="$t('@.reactions.laugh')" v-particle><mk-reaction-icon reaction="laugh"/></button>
@@ -15,6 +15,9 @@
<button @click="react('rip')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="9" :title="$t('@.reactions.rip')" v-particle><mk-reaction-icon reaction="rip"/></button> <button @click="react('rip')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="9" :title="$t('@.reactions.rip')" v-particle><mk-reaction-icon reaction="rip"/></button>
<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" :title="$t('@.reactions.pudding')" v-particle><mk-reaction-icon reaction="pudding"/></button> <button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" :title="$t('@.reactions.pudding')" v-particle><mk-reaction-icon reaction="pudding"/></button>
</div> </div>
<div v-if="enableEmojiReaction" class="text">
<input v-model="text" placeholder="または絵文字を入力" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -23,6 +26,7 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import anime from 'animejs'; import anime from 'animejs';
import { emojiRegex } from '../../../../../misc/emoji-regex';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('common/views/components/reaction-picker.vue'), i18n: i18n('common/views/components/reaction-picker.vue'),
@@ -56,6 +60,8 @@ export default Vue.extend({
data() { data() {
return { return {
title: this.$t('choose-reaction'), title: this.$t('choose-reaction'),
text: null,
enableEmojiReaction: false,
focus: null focus: null
}; };
}, },
@@ -94,6 +100,10 @@ export default Vue.extend({
}, },
mounted() { mounted() {
this.$root.getMeta().then(meta => {
this.enableEmojiReaction = meta.enableEmojiReaction;
});
this.$nextTick(() => { this.$nextTick(() => {
this.focus = 0; this.focus = 0;
@@ -143,6 +153,17 @@ export default Vue.extend({
}); });
}, },
reactText() {
if (!this.text) return;
this.react(this.text);
},
tryReactText() {
if (!this.text) return;
if (!this.text.match(emojiRegex)) return;
this.reactText();
},
onMouseover(e) { onMouseover(e) {
this.title = e.target.title; this.title = e.target.title;
}, },
@@ -256,9 +277,9 @@ export default Vue.extend({
color var(--popupFg) color var(--popupFg)
border-bottom solid var(--lineWidth) var(--faceDivider) border-bottom solid var(--lineWidth) var(--faceDivider)
> div > .buttons
padding 4px padding 4px
width 240px width 216px
text-align center text-align center
&.showFocus &.showFocus
@@ -283,6 +304,9 @@ export default Vue.extend({
font-size 24px font-size 24px
border-radius 2px border-radius 2px
> *
height 1em
&:hover &:hover
background var(--reactionPickerButtonHoverBg) background var(--reactionPickerButtonHoverBg)
@@ -290,4 +314,29 @@ export default Vue.extend({
background var(--primary) background var(--primary)
box-shadow inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15) box-shadow inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15)
> .text
width 216px
padding 4px 8px 8px 8px
> input
width 100%
padding 10px
margin 0
text-align center
font-size 16px
color var(--desktopPostFormTextareaFg)
background var(--desktopPostFormTextareaBg)
outline none
border solid 1px var(--primaryAlpha01)
border-radius 4px
transition border-color .2s ease
&:hover
border-color var(--primaryAlpha02)
transition border-color .1s ease
&:focus
border-color var(--primaryAlpha05)
transition border-color 0s ease
</style> </style>

View File

@@ -136,12 +136,8 @@ export default Vue.extend({
&:hover &:hover
background var(--reactionViewerButtonHoverBg) background var(--reactionViewerButtonHoverBg)
> .mk-reaction-icon
font-size 1.4em
> span > span
font-size 1.1em font-size 1.1em
line-height 32px line-height 32px
vertical-align middle
color var(--text) color var(--text)
</style> </style>

View File

@@ -16,11 +16,11 @@ import App from './app.vue';
import checkForUpdate from './common/scripts/check-for-update'; import checkForUpdate from './common/scripts/check-for-update';
import MiOS from './mios'; import MiOS from './mios';
import { version, codename, lang, locale } from './config'; import { version, codename, lang, locale } from './config';
import { builtinThemes, applyTheme, darkTheme } from './theme'; import { builtinThemes, applyTheme, blackTheme } from './theme';
import Dialog from './common/views/components/dialog.vue'; import Dialog from './common/views/components/dialog.vue';
if (localStorage.getItem('theme') == null) { if (localStorage.getItem('theme') == null) {
applyTheme(darkTheme); applyTheme(blackTheme);
} }
//#region FontAwesome //#region FontAwesome

View File

@@ -49,7 +49,7 @@ const defaultDeviceSettings = {
roundedCorners: true, roundedCorners: true,
reduceMotion: false, reduceMotion: false,
darkmode: true, darkmode: true,
darkTheme: 'dark', darkTheme: 'bb5a8287-a072-4b0a-8ae5-ea2a0d33f4f2',
lightTheme: 'light', lightTheme: 'light',
lineWidth: 1, lineWidth: 1,
fontSize: 0, fontSize: 0,

View File

@@ -3,18 +3,37 @@
name: 'Future', name: 'Future',
author: 'syuilo', author: 'syuilo',
desc: 'Sci-fi flavored',
base: 'dark', base: 'dark',
vars: { vars: {
primary: 'rgb(94, 158, 185)', c0: '#0c0c0c',
secondary: 'rgb(22, 24, 30)', c1: 'rgb(255, 105, 78)',
text: 'rgb(214, 218, 224)', c2: 'rgb(99, 197, 210)',
c4: 'rgb(253, 254, 214)',
c3: 'rgb(204, 254, 253)',
primary: '$c1',
secondary: '#131313',
text: '$c3',
}, },
props: { props: {
renoteGradient: '#0a2d3c', bg: '$c0',
renoteText: '$primary', noteText: '$c4',
quoteBorder: '$primary', noteHeaderAcct: ':alpha<0.65<$c4',
noteHeaderInfo: ':alpha<0.5<$c4',
subNoteText: ':alpha<0.7<$c4',
renoteGradient: 'rgba(0, 0, 0, 0)',
renoteText: '$c2',
quoteBorder: '$c2',
mfmHashtag: '$c1',
mfmUrl: '$c2',
mfmLink: '$c2',
mfmMention: '$c1',
mfmMentionForeground: '#fff',
notificationIndicator: '$c2',
link: '$c2',
desktopHeaderBg: '$secondary',
}, },
} }

File diff suppressed because one or more lines are too long

1
src/misc/emoji-regex.ts Normal file

File diff suppressed because one or more lines are too long

View File

@@ -13,6 +13,7 @@ const defaultMeta: any = {
originalUsersCount: 0 originalUsersCount: 0
}, },
maxNoteTextLength: 1000, maxNoteTextLength: 1000,
enableEmojiReaction: true,
enableTwitterIntegration: false, enableTwitterIntegration: false,
enableGithubIntegration: false, enableGithubIntegration: false,
enableDiscordIntegration: false, enableDiscordIntegration: false,

View File

@@ -10,6 +10,7 @@ export default function(reaction: string): string {
case 'confused': return '😥'; case 'confused': return '😥';
case 'rip': return '😇'; case 'rip': return '😇';
case 'pudding': return '🍮'; case 'pudding': return '🍮';
default: return ''; case 'star': return '';
default: return reaction;
} }
} }

61
src/misc/reaction-lib.ts Normal file
View File

@@ -0,0 +1,61 @@
import Emoji from '../models/emoji';
import { emojiRegex } from './emoji-regex';
import fetchMeta from './fetch-meta';
const basic10: Record<string, string> = {
'👍': 'like',
'❤': 'love', // ここに記述する場合は異体字セレクタを入れない
'😆': 'laugh',
'🤔': 'hmm',
'😮': 'surprise',
'🎉': 'congrats',
'💢': 'angry',
'😥': 'confused',
'😇': 'rip',
'🍮': 'pudding',
};
export async function getFallbackReaction(): Promise<string> {
const meta = await fetchMeta();
return meta.useStarForReactionFallback ? 'star' : 'like';
}
export async function toDbReaction(reaction: string, enableEmoji = true): Promise<string> {
if (reaction == null) return await getFallbackReaction();
// 既存の文字列リアクションはそのまま
if (Object.values(basic10).includes(reaction)) return reaction;
if (!enableEmoji) return await getFallbackReaction();
// Unicode絵文字
const match = emojiRegex.exec(reaction);
if (match) {
// 合字を含む1つの絵文字
const unicode = match[0];
// 異体字セレクタ除去後の絵文字
const normalized = unicode.match('\u200d') ? unicode : unicode.replace(/\ufe0f/g, '');
// Unicodeプリンは寿司化不能とするため文字列化しない
if (normalized === '🍮') return normalized;
// プリン以外の既存のリアクションは文字列化する
if (basic10[normalized]) return basic10[normalized];
// それ以外はUnicodeのまま
return normalized;
}
const custom = reaction.match(/:([\w+-]+):/);
if (custom) {
const emoji = await Emoji.findOne({
host: null,
name: custom[1],
});
if (emoji) return reaction;
}
return await getFallbackReaction();
}

View File

@@ -194,6 +194,8 @@ export type IMeta = {
disableRegistration?: boolean; disableRegistration?: boolean;
disableLocalTimeline?: boolean; disableLocalTimeline?: boolean;
disableGlobalTimeline?: boolean; disableGlobalTimeline?: boolean;
enableEmojiReaction?: boolean;
useStarForReactionFallback?: boolean;
hidedTags?: string[]; hidedTags?: string[];
mascotImageUrl?: string; mascotImageUrl?: string;
bannerUrl?: string; bannerUrl?: string;

View File

@@ -1,5 +1,4 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import $ from 'cafy';
import * as deepcopy from 'deepcopy'; import * as deepcopy from 'deepcopy';
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid'; import isObjectId from '../misc/is-objectid';
@@ -20,19 +19,6 @@ export interface INoteReaction {
reaction: string; reaction: string;
} }
export const validateReaction = $.str.or([
'like',
'love',
'laugh',
'hmm',
'surprise',
'congrats',
'angry',
'confused',
'rip',
'pudding'
]);
/** /**
* Pack a reaction for API response * Pack a reaction for API response
*/ */

View File

@@ -12,6 +12,7 @@ import { packMany as packFileMany, IDriveFile } from './drive-file';
import Following from './following'; import Following from './following';
import Emoji from './emoji'; import Emoji from './emoji';
import { dbLogger } from '../db/logger'; import { dbLogger } from '../db/logger';
import { unique, concat } from '../prelude/array';
const Note = db.get<INote>('notes'); const Note = db.get<INote>('notes');
Note.createIndex('uri', { sparse: true, unique: true }); Note.createIndex('uri', { sparse: true, unique: true });
@@ -242,6 +243,11 @@ export const pack = async (
const id = _note._id; const id = _note._id;
// Some counts
_note.renoteCount = _note.renoteCount || 0;
_note.repliesCount = _note.repliesCount || 0;
_note.reactionCounts = _note.reactionCounts || {};
// _note._userを消す前か、_note.userを解決した後でないとホストがわからない // _note._userを消す前か、_note.userを解決した後でないとホストがわからない
if (_note._user) { if (_note._user) {
const host = _note._user.host; const host = _note._user.host;
@@ -253,6 +259,8 @@ export const pack = async (
fields: { _id: false } fields: { _id: false }
}); });
} else { } else {
_note.emojis = unique(concat([_note.emojis, Object.keys(_note.reactionCounts)]));
_note.emojis = Emoji.find({ _note.emojis = Emoji.find({
name: { $in: _note.emojis }, name: { $in: _note.emojis },
host: host host: host
@@ -290,11 +298,6 @@ export const pack = async (
// Populate files // Populate files
_note.files = packFileMany(_note.fileIds || []); _note.files = packFileMany(_note.fileIds || []);
// Some counts
_note.renoteCount = _note.renoteCount || 0;
_note.repliesCount = _note.repliesCount || 0;
_note.reactionCounts = _note.reactionCounts || {};
// 後方互換性のため // 後方互換性のため
_note.mediaIds = _note.fileIds; _note.mediaIds = _note.fileIds;
_note.media = _note.files; _note.media = _note.files;

View File

@@ -3,7 +3,6 @@ import Note from '../../../models/note';
import { IRemoteUser } from '../../../models/user'; import { IRemoteUser } from '../../../models/user';
import { ILike } from '../type'; import { ILike } from '../type';
import create from '../../../services/note/reaction/create'; import create from '../../../services/note/reaction/create';
import { validateReaction } from '../../../models/note-reaction';
export default async (actor: IRemoteUser, activity: ILike) => { export default async (actor: IRemoteUser, activity: ILike) => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id; const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
@@ -18,12 +17,5 @@ export default async (actor: IRemoteUser, activity: ILike) => {
throw new Error(); throw new Error();
} }
let reaction = 'like'; await create(actor, note, activity._misskey_reaction);
// 他のMisskeyインスタンスからのリアクション
if (activity._misskey_reaction && validateReaction.ok(activity._misskey_reaction)) {
reaction = activity._misskey_reaction;
}
await create(actor, note, reaction);
}; };

View File

@@ -41,6 +41,20 @@ export const meta = {
} }
}, },
enableEmojiReaction: {
validator: $.optional.nullable.bool,
desc: {
'ja-JP': '絵文字リアクションを有効にするか否か'
}
},
useStarForReactionFallback: {
validator: $.optional.nullable.bool,
desc: {
'ja-JP': '不明なリアクションのフォールバックに star リアクションを使うか'
}
},
hidedTags: { hidedTags: {
validator: $.optional.nullable.arr($.str), validator: $.optional.nullable.arr($.str),
desc: { desc: {
@@ -351,6 +365,14 @@ export default define(meta, async (ps) => {
set.disableGlobalTimeline = ps.disableGlobalTimeline; set.disableGlobalTimeline = ps.disableGlobalTimeline;
} }
if (typeof ps.enableEmojiReaction === 'boolean') {
set.enableEmojiReaction = ps.enableEmojiReaction;
}
if (typeof ps.useStarForReactionFallback === 'boolean') {
set.useStarForReactionFallback = ps.useStarForReactionFallback;
}
if (Array.isArray(ps.hidedTags)) { if (Array.isArray(ps.hidedTags)) {
set.hidedTags = ps.hidedTags; set.hidedTags = ps.hidedTags;
} }

View File

@@ -70,6 +70,10 @@ export const meta = {
type: 'boolean', type: 'boolean',
description: 'Whether disabled GTL.', description: 'Whether disabled GTL.',
}, },
enableEmojiReaction: {
type: 'boolean',
description: 'Whether enabled emoji reaction.',
},
} }
} }
}; };
@@ -107,6 +111,7 @@ export default define(meta, async (ps, me) => {
disableRegistration: instance.disableRegistration, disableRegistration: instance.disableRegistration,
disableLocalTimeline: instance.disableLocalTimeline, disableLocalTimeline: instance.disableLocalTimeline,
disableGlobalTimeline: instance.disableGlobalTimeline, disableGlobalTimeline: instance.disableGlobalTimeline,
enableEmojiReaction: instance.enableEmojiReaction,
driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, driveCapacityPerLocalUserMb: instance.localDriveCapacityMb,
driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb,
cacheRemoteFiles: instance.cacheRemoteFiles, cacheRemoteFiles: instance.cacheRemoteFiles,
@@ -147,6 +152,7 @@ export default define(meta, async (ps, me) => {
} }
if (me && (me.isAdmin || me.isModerator)) { if (me && (me.isAdmin || me.isModerator)) {
response.useStarForReactionFallback = instance.useStarForReactionFallback;
response.hidedTags = instance.hidedTags; response.hidedTags = instance.hidedTags;
response.recaptchaSecretKey = instance.recaptchaSecretKey; response.recaptchaSecretKey = instance.recaptchaSecretKey;
response.proxyAccount = instance.proxyAccount; response.proxyAccount = instance.proxyAccount;

View File

@@ -1,7 +1,6 @@
import $ from 'cafy'; import $ from 'cafy';
import ID, { transform } from '../../../../../misc/cafy-id'; import ID, { transform } from '../../../../../misc/cafy-id';
import createReaction from '../../../../../services/note/reaction/create'; import createReaction from '../../../../../services/note/reaction/create';
import { validateReaction } from '../../../../../models/note-reaction';
import define from '../../../define'; import define from '../../../define';
import { getNote } from '../../../common/getters'; import { getNote } from '../../../common/getters';
import { ApiError } from '../../../error'; import { ApiError } from '../../../error';
@@ -30,7 +29,7 @@ export const meta = {
}, },
reaction: { reaction: {
validator: $.str.pipe(validateReaction.ok), validator: $.str,
desc: { desc: {
'ja-JP': 'リアクションの種類' 'ja-JP': 'リアクションの種類'
} }

View File

@@ -20,7 +20,7 @@ export const meta = {
limit: { limit: {
duration: ms('1hour'), duration: ms('1hour'),
max: 5, max: 60,
minInterval: ms('3sec') minInterval: ms('3sec')
}, },

View File

@@ -392,7 +392,8 @@ export const schemas = {
'angry', 'angry',
'confused', 'confused',
'rip', 'rip',
'pudding' 'pudding',
'star'
], ],
description: 'The reaction type.' description: 'The reaction type.'
}, },

View File

@@ -10,6 +10,8 @@ import { deliver } from '../../../queue';
import { renderActivity } from '../../../remote/activitypub/renderer'; import { renderActivity } from '../../../remote/activitypub/renderer';
import perUserReactionsChart from '../../../services/chart/per-user-reactions'; import perUserReactionsChart from '../../../services/chart/per-user-reactions';
import { IdentifiableError } from '../../../misc/identifiable-error'; import { IdentifiableError } from '../../../misc/identifiable-error';
import { toDbReaction } from '../../../misc/reaction-lib';
import fetchMeta from '../../../misc/fetch-meta';
export default async (user: IUser, note: INote, reaction: string) => { export default async (user: IUser, note: INote, reaction: string) => {
// Myself // Myself
@@ -17,6 +19,9 @@ export default async (user: IUser, note: INote, reaction: string) => {
throw new IdentifiableError('2d8e7297-1873-4c00-8404-792c68d7bef0', 'cannot react to my note'); throw new IdentifiableError('2d8e7297-1873-4c00-8404-792c68d7bef0', 'cannot react to my note');
} }
const meta = await fetchMeta();
reaction = await toDbReaction(reaction, meta.enableEmojiReaction);
// Create reaction // Create reaction
await NoteReaction.insert({ await NoteReaction.insert({
createdAt: new Date(), createdAt: new Date(),

91
test/reaction-lib.ts Normal file
View File

@@ -0,0 +1,91 @@
/*
* Tests of MFM
*
* How to run the tests:
* > mocha test/reaction-lib.ts --require ts-node/register
*
* To specify test:
* > mocha test/reaction-lib.ts --require ts-node/register -g 'test name'
*/
import * as assert from 'assert';
import { toDbReaction } from '../src/misc/reaction-lib';
describe('toDbReaction', async () => {
it('既存の文字列リアクションはそのまま', async () => {
assert.strictEqual(await toDbReaction('like'), 'like');
});
it('Unicodeプリンは寿司化不能とするため文字列化しない', async () => {
assert.strictEqual(await toDbReaction('🍮'), '🍮');
});
it('プリン以外の既存のリアクションは文字列化する like', async () => {
assert.strictEqual(await toDbReaction('👍'), 'like');
});
it('プリン以外の既存のリアクションは文字列化する love', async () => {
assert.strictEqual(await toDbReaction('❤️'), 'love');
});
it('プリン以外の既存のリアクションは文字列化する love 異体字セレクタなし', async () => {
assert.strictEqual(await toDbReaction('❤'), 'love');
});
it('プリン以外の既存のリアクションは文字列化する laugh', async () => {
assert.strictEqual(await toDbReaction('😆'), 'laugh');
});
it('プリン以外の既存のリアクションは文字列化する hmm', async () => {
assert.strictEqual(await toDbReaction('🤔'), 'hmm');
});
it('プリン以外の既存のリアクションは文字列化する surprise', async () => {
assert.strictEqual(await toDbReaction('😮'), 'surprise');
});
it('プリン以外の既存のリアクションは文字列化する congrats', async () => {
assert.strictEqual(await toDbReaction('🎉'), 'congrats');
});
it('プリン以外の既存のリアクションは文字列化する angry', async () => {
assert.strictEqual(await toDbReaction('💢'), 'angry');
});
it('プリン以外の既存のリアクションは文字列化する confused', async () => {
assert.strictEqual(await toDbReaction('😥'), 'confused');
});
it('プリン以外の既存のリアクションは文字列化する rip', async () => {
assert.strictEqual(await toDbReaction('😇'), 'rip');
});
it('それ以外はUnicodeのまま', async () => {
assert.strictEqual(await toDbReaction('🍅'), '🍅');
});
it('異体字セレクタ除去', async () => {
assert.strictEqual(await toDbReaction('㊗️'), '㊗');
});
it('異体字セレクタ除去 必要なし', async () => {
assert.strictEqual(await toDbReaction('㊗'), '㊗');
});
it('fallback - undefined', async () => {
assert.strictEqual(await toDbReaction(undefined), 'like');
});
it('fallback - null', async () => {
assert.strictEqual(await toDbReaction(null), 'like');
});
it('fallback - empty', async () => {
assert.strictEqual(await toDbReaction(''), 'like');
});
it('fallback - unknown', async () => {
assert.strictEqual(await toDbReaction('unknown'), 'like');
});
});