mirror of
https://activitypub.software/TransFem-org/Sharkey.git
synced 2024-12-15 13:25:44 +01:00
commit
78ff90f2cc
17 changed files with 124 additions and 33 deletions
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -12,6 +12,20 @@
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
## 2024.3.1
|
||||||
|
|
||||||
|
### General
|
||||||
|
-
|
||||||
|
|
||||||
|
### Client
|
||||||
|
- Fix: 絵文字関係の不具合を修正 (#13485)
|
||||||
|
- 履歴に残っている or ピン留めされた絵文字がコントロールパネルより削除されていた際にリアクションデッキが表示できなくなる
|
||||||
|
- Unicode絵文字が履歴に残っている or ピン留めされているとリアクションデッキが表示できなくなる
|
||||||
|
- Fix: カスタム絵文字の画像読み込みに失敗した際はテキストではなくダミー画像を表示 #13487
|
||||||
|
|
||||||
|
### Server
|
||||||
|
-
|
||||||
|
|
||||||
## 2024.3.0
|
## 2024.3.0
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
|
|
@ -380,8 +380,11 @@ hcaptcha: "hCaptcha"
|
||||||
enableHcaptcha: "Activer hCaptcha"
|
enableHcaptcha: "Activer hCaptcha"
|
||||||
hcaptchaSiteKey: "Clé du site"
|
hcaptchaSiteKey: "Clé du site"
|
||||||
hcaptchaSecretKey: "Clé secrète"
|
hcaptchaSecretKey: "Clé secrète"
|
||||||
|
mcaptcha: "mCaptcha"
|
||||||
|
enableMcaptcha: "Activer mCaptcha"
|
||||||
mcaptchaSiteKey: "Clé du site"
|
mcaptchaSiteKey: "Clé du site"
|
||||||
mcaptchaSecretKey: "Clé secrète"
|
mcaptchaSecretKey: "Clé secrète"
|
||||||
|
mcaptchaInstanceUrl: "URL de l'instance de mCaptcha"
|
||||||
recaptcha: "reCAPTCHA"
|
recaptcha: "reCAPTCHA"
|
||||||
enableRecaptcha: "Activer reCAPTCHA"
|
enableRecaptcha: "Activer reCAPTCHA"
|
||||||
recaptchaSiteKey: "Clé du site"
|
recaptchaSiteKey: "Clé du site"
|
||||||
|
@ -523,7 +526,7 @@ hideThisNote: "Masquer cette note"
|
||||||
showFeaturedNotesInTimeline: "Afficher les notes des Tendances dans le fil d'actualité"
|
showFeaturedNotesInTimeline: "Afficher les notes des Tendances dans le fil d'actualité"
|
||||||
objectStorage: "Stockage d'objets"
|
objectStorage: "Stockage d'objets"
|
||||||
useObjectStorage: "Utiliser le stockage d'objets"
|
useObjectStorage: "Utiliser le stockage d'objets"
|
||||||
objectStorageBaseUrl: "Base URL"
|
objectStorageBaseUrl: "URL de base"
|
||||||
objectStorageBaseUrlDesc: "Préfixe d’URL utilisé pour construire l’URL vers le référencement d’objet (média). Spécifiez son URL si vous utilisez un CDN ou un proxy, sinon spécifiez l’adresse accessible au public selon le guide de service que vous allez utiliser. P.ex. 'https://<bucket>.s3.amazonaws.com' pour AWS S3 et 'https://storage.googleapis.com/<bucket>' pour GCS."
|
objectStorageBaseUrlDesc: "Préfixe d’URL utilisé pour construire l’URL vers le référencement d’objet (média). Spécifiez son URL si vous utilisez un CDN ou un proxy, sinon spécifiez l’adresse accessible au public selon le guide de service que vous allez utiliser. P.ex. 'https://<bucket>.s3.amazonaws.com' pour AWS S3 et 'https://storage.googleapis.com/<bucket>' pour GCS."
|
||||||
objectStorageBucket: "Bucket"
|
objectStorageBucket: "Bucket"
|
||||||
objectStorageBucketDesc: "Veuillez spécifier le nom du compartiment utilisé sur le service configuré."
|
objectStorageBucketDesc: "Veuillez spécifier le nom du compartiment utilisé sur le service configuré."
|
||||||
|
@ -628,6 +631,7 @@ medium: "Moyen"
|
||||||
small: "Petit"
|
small: "Petit"
|
||||||
generateAccessToken: "Générer un jeton d'accès"
|
generateAccessToken: "Générer un jeton d'accès"
|
||||||
permission: "Autorisations "
|
permission: "Autorisations "
|
||||||
|
adminPermission: "Droits de l'administrateur"
|
||||||
enableAll: "Tout activer"
|
enableAll: "Tout activer"
|
||||||
disableAll: "Tout désactiver"
|
disableAll: "Tout désactiver"
|
||||||
tokenRequested: "Autoriser l'accès au compte"
|
tokenRequested: "Autoriser l'accès au compte"
|
||||||
|
@ -1031,12 +1035,18 @@ nonSensitiveOnlyForLocalLikeOnlyForRemote: "Non sensibles seulement (mentions j'
|
||||||
rolesAssignedToMe: "Rôles attribués à moi"
|
rolesAssignedToMe: "Rôles attribués à moi"
|
||||||
resetPasswordConfirm: "Souhaitez-vous réinitialiser votre mot de passe ?"
|
resetPasswordConfirm: "Souhaitez-vous réinitialiser votre mot de passe ?"
|
||||||
sensitiveWords: "Mots sensibles"
|
sensitiveWords: "Mots sensibles"
|
||||||
|
sensitiveWordsDescription2: "Séparer par une espace pour créer une expression AND ; entourer de barres obliques pour créer une expression régulière."
|
||||||
|
prohibitedWords: "Mots interdits"
|
||||||
|
prohibitedWordsDescription2: "Séparer par une espace pour créer une expression AND ; entourer de barres obliques pour créer une expression régulière."
|
||||||
hiddenTags: "Hashtags cachés"
|
hiddenTags: "Hashtags cachés"
|
||||||
hiddenTagsDescription: "Les hashtags définis ne s'afficheront pas dans les tendances. Vous pouvez définir plusieurs hashtags en faisant un saut de ligne."
|
hiddenTagsDescription: "Les hashtags définis ne s'afficheront pas dans les tendances. Vous pouvez définir plusieurs hashtags en faisant un saut de ligne."
|
||||||
notesSearchNotAvailable: "La recherche de notes n'est pas disponible."
|
notesSearchNotAvailable: "La recherche de notes n'est pas disponible."
|
||||||
license: "Licence"
|
license: "Licence"
|
||||||
|
unfavoriteConfirm: "Vraiment supprimer des favoris ?"
|
||||||
myClips: "Mes clips"
|
myClips: "Mes clips"
|
||||||
drivecleaner: "Nettoyeur du Disque"
|
drivecleaner: "Nettoyeur du Disque"
|
||||||
|
retryAllQueuesNow: "Réessayer tous les fils d'attente immédiatement"
|
||||||
|
retryAllQueuesConfirmTitle: "Vraiment réessayer ?"
|
||||||
retryAllQueuesConfirmText: "Cela peut augmenter temporairement la charge du serveur."
|
retryAllQueuesConfirmText: "Cela peut augmenter temporairement la charge du serveur."
|
||||||
enableChartsForRemoteUser: "Générer les graphiques pour les utilisateurs distants"
|
enableChartsForRemoteUser: "Générer les graphiques pour les utilisateurs distants"
|
||||||
enableChartsForFederatedInstances: "Générer les graphiques pour les instances distantes"
|
enableChartsForFederatedInstances: "Générer les graphiques pour les instances distantes"
|
||||||
|
@ -1046,6 +1056,8 @@ limitWidthOfReaction: "Limiter la largeur maximale des réactions et les affiche
|
||||||
noteIdOrUrl: "Identifiant de la note ou URL"
|
noteIdOrUrl: "Identifiant de la note ou URL"
|
||||||
video: "Vidéo"
|
video: "Vidéo"
|
||||||
videos: "Vidéos"
|
videos: "Vidéos"
|
||||||
|
audio: "Audio"
|
||||||
|
audioFiles: "Fichiers audio"
|
||||||
dataSaver: "Économiseur de données"
|
dataSaver: "Économiseur de données"
|
||||||
accountMigration: "Migration de compte"
|
accountMigration: "Migration de compte"
|
||||||
accountMoved: "Cet·te utilisateur·rice a migré son compte vers :"
|
accountMoved: "Cet·te utilisateur·rice a migré son compte vers :"
|
||||||
|
@ -1084,7 +1096,10 @@ specifyUser: "Spécifier l'utilisateur·rice"
|
||||||
failedToPreviewUrl: "Aperçu d'URL échoué"
|
failedToPreviewUrl: "Aperçu d'URL échoué"
|
||||||
update: "Mettre à jour"
|
update: "Mettre à jour"
|
||||||
rolesThatCanBeUsedThisEmojiAsReaction: "Rôles qui peuvent utiliser cet émoji comme réaction"
|
rolesThatCanBeUsedThisEmojiAsReaction: "Rôles qui peuvent utiliser cet émoji comme réaction"
|
||||||
|
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Si aucun rôle n'est spécifié, tout le monde peut utiliser cet émoji comme réaction."
|
||||||
|
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Il faut un rôle public."
|
||||||
cancelReactionConfirm: "Supprimez la réaction ?"
|
cancelReactionConfirm: "Supprimez la réaction ?"
|
||||||
|
changeReactionConfirm: "Changer la réaction ?"
|
||||||
later: "Plus tard"
|
later: "Plus tard"
|
||||||
goToMisskey: "Retour vers Misskey"
|
goToMisskey: "Retour vers Misskey"
|
||||||
additionalEmojiDictionary: "Dictionnaires d'émojis additionnels"
|
additionalEmojiDictionary: "Dictionnaires d'émojis additionnels"
|
||||||
|
@ -1110,11 +1125,13 @@ used: "Utilisé"
|
||||||
expired: "Expiré"
|
expired: "Expiré"
|
||||||
doYouAgree: "Êtes-vous d’accord ?"
|
doYouAgree: "Êtes-vous d’accord ?"
|
||||||
beSureToReadThisAsItIsImportant: "Assurez-vous de le lire ; c'est important."
|
beSureToReadThisAsItIsImportant: "Assurez-vous de le lire ; c'est important."
|
||||||
|
iHaveReadXCarefullyAndAgree: "J'ai lu le contenu de « {x} » et donne mon accord."
|
||||||
dialog: "Dialogue"
|
dialog: "Dialogue"
|
||||||
icon: "Avatar"
|
icon: "Avatar"
|
||||||
forYou: "Pour vous"
|
forYou: "Pour vous"
|
||||||
currentAnnouncements: "Annonces actuelles"
|
currentAnnouncements: "Annonces actuelles"
|
||||||
pastAnnouncements: "Annonces passées"
|
pastAnnouncements: "Annonces passées"
|
||||||
|
youHaveUnreadAnnouncements: "Il y a des annonces non lues."
|
||||||
replies: "Réponses"
|
replies: "Réponses"
|
||||||
renotes: "Renotes"
|
renotes: "Renotes"
|
||||||
loadReplies: "Inclure les réponses"
|
loadReplies: "Inclure les réponses"
|
||||||
|
@ -1129,6 +1146,7 @@ showRenotes: "Afficher les renotes"
|
||||||
edited: "Modifié"
|
edited: "Modifié"
|
||||||
notificationRecieveConfig: "Paramètres des notifications"
|
notificationRecieveConfig: "Paramètres des notifications"
|
||||||
mutualFollow: "Abonnement mutuel"
|
mutualFollow: "Abonnement mutuel"
|
||||||
|
fileAttachedOnly: "Avec fichiers joints seulement"
|
||||||
showRepliesToOthersInTimeline: "Afficher les réponses aux autres dans le fil"
|
showRepliesToOthersInTimeline: "Afficher les réponses aux autres dans le fil"
|
||||||
hideRepliesToOthersInTimeline: "Masquer les réponses aux autres dans le fil"
|
hideRepliesToOthersInTimeline: "Masquer les réponses aux autres dans le fil"
|
||||||
showRepliesToOthersInTimelineAll: "Afficher les réponses de toutes les personnes que vous suivez dans le fil"
|
showRepliesToOthersInTimelineAll: "Afficher les réponses de toutes les personnes que vous suivez dans le fil"
|
||||||
|
@ -1137,6 +1155,11 @@ confirmShowRepliesAll: "Cette opération est irréversible. Voulez-vous vraiment
|
||||||
confirmHideRepliesAll: "Cette opération est irréversible. Voulez-vous vraiment masquer les réponses de toutes les personnes que vous suivez dans le fil ?"
|
confirmHideRepliesAll: "Cette opération est irréversible. Voulez-vous vraiment masquer les réponses de toutes les personnes que vous suivez dans le fil ?"
|
||||||
externalServices: "Services externes"
|
externalServices: "Services externes"
|
||||||
sourceCode: "Code source"
|
sourceCode: "Code source"
|
||||||
|
sourceCodeIsNotYetProvided: "Le code source n'est pas encore disponible. Veuillez signaler ce problème aux administrateurs."
|
||||||
|
repositoryUrl: "URL du dépôt"
|
||||||
|
repositoryUrlDescription: "Entrez l'URL du dépôt où se trouve le code source ici. Si vous utilisez Misskey tel quel (sans changer le code source), entrez https://github.com/misskey-dev/misskey"
|
||||||
|
feedback: "Commentaires"
|
||||||
|
feedbackUrl: "URL pour les commentaires"
|
||||||
impressum: "Impressum"
|
impressum: "Impressum"
|
||||||
impressumUrl: "URL de l'impressum"
|
impressumUrl: "URL de l'impressum"
|
||||||
impressumDescription: "Dans certains pays comme l'Allemagne, il est obligatoire d'afficher les informations sur l'opérateur d'un site (un impressum)."
|
impressumDescription: "Dans certains pays comme l'Allemagne, il est obligatoire d'afficher les informations sur l'opérateur d'un site (un impressum)."
|
||||||
|
@ -1164,11 +1187,32 @@ remainingN: "Restants : {n}"
|
||||||
overwriteContentConfirm: "Voulez-vous remplacer le contenu actuel ?"
|
overwriteContentConfirm: "Voulez-vous remplacer le contenu actuel ?"
|
||||||
seasonalScreenEffect: "Effet d'écran saisonnier"
|
seasonalScreenEffect: "Effet d'écran saisonnier"
|
||||||
decorate: "Décorer"
|
decorate: "Décorer"
|
||||||
|
addMfmFunction: "Insérer MFM"
|
||||||
|
enableQuickAddMfmFunction: "Afficher le sélecteur de MFM avancé"
|
||||||
|
bubbleGame: "Jeu de bulles"
|
||||||
sfx: "Effets sonores"
|
sfx: "Effets sonores"
|
||||||
|
soundWillBePlayed: "Le son sera joué"
|
||||||
showReplay: "Voir le replay"
|
showReplay: "Voir le replay"
|
||||||
|
replay: "Rediffusion"
|
||||||
|
replaying: "En cours de rediffusion"
|
||||||
|
endReplay: "Arrêter la rediffusion"
|
||||||
|
copyReplayData: "Copier les données de la rediffusion"
|
||||||
ranking: "Classement"
|
ranking: "Classement"
|
||||||
lastNDays: "Derniers {n} jours"
|
lastNDays: "Derniers {n} jours"
|
||||||
|
backToTitle: "Retourner au titre"
|
||||||
|
hemisphere: "Votre région"
|
||||||
|
enableHorizontalSwipe: "Glisser pour changer d'onglet"
|
||||||
|
loading: "Chargement en cours"
|
||||||
surrender: "Annuler"
|
surrender: "Annuler"
|
||||||
|
gameRetry: "Réessayer"
|
||||||
|
_bubbleGame:
|
||||||
|
howToPlay: "Comment jouer"
|
||||||
|
hold: "Réserver"
|
||||||
|
_score:
|
||||||
|
score: "Score"
|
||||||
|
scoreYen: "Montant gagné"
|
||||||
|
highScore: "Meilleur score"
|
||||||
|
yen: "{yen} yens"
|
||||||
_announcement:
|
_announcement:
|
||||||
forExistingUsers: "Pour les utilisateurs existants seulement"
|
forExistingUsers: "Pour les utilisateurs existants seulement"
|
||||||
readConfirmTitle: "Marquer comme lu ?"
|
readConfirmTitle: "Marquer comme lu ?"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2024.3.0",
|
"version": "2024.3.1",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</ol>
|
</ol>
|
||||||
<ol v-else-if="emojis.length > 0" ref="suggests" :class="$style.list">
|
<ol v-else-if="emojis.length > 0" ref="suggests" :class="$style.list">
|
||||||
<li v-for="emoji in emojis" :key="emoji.emoji" :class="$style.item" tabindex="-1" @click="complete(type, emoji.emoji)" @keydown="onKeydown">
|
<li v-for="emoji in emojis" :key="emoji.emoji" :class="$style.item" tabindex="-1" @click="complete(type, emoji.emoji)" @keydown="onKeydown">
|
||||||
<MkCustomEmoji v-if="'isCustomEmoji' in emoji && emoji.isCustomEmoji" :name="emoji.emoji" :class="$style.emoji"/>
|
<MkCustomEmoji v-if="'isCustomEmoji' in emoji && emoji.isCustomEmoji" :name="emoji.emoji" :class="$style.emoji" :fallbackToImage="true"/>
|
||||||
<MkEmoji v-else :emoji="emoji.emoji" :class="$style.emoji"/>
|
<MkEmoji v-else :emoji="emoji.emoji" :class="$style.emoji"/>
|
||||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
<span v-if="q" :class="$style.emojiName" v-html="sanitizeHtml(emoji.name.replace(q, `<b>${q}</b>`))"></span>
|
<span v-if="q" :class="$style.emojiName" v-html="sanitizeHtml(emoji.name.replace(q, `<b>${q}</b>`))"></span>
|
||||||
|
@ -77,7 +77,7 @@ const emojiDb = computed(() => {
|
||||||
unicodeEmojiDB.push({
|
unicodeEmojiDB.push({
|
||||||
emoji: emoji,
|
emoji: emoji,
|
||||||
name: k,
|
name: k,
|
||||||
aliasOf: getEmojiName(emoji)!,
|
aliasOf: getEmojiName(emoji),
|
||||||
url: char2path(emoji),
|
url: char2path(emoji),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
@pointerenter="computeButtonTitle"
|
@pointerenter="computeButtonTitle"
|
||||||
@click="emit('chosen', emoji, $event)"
|
@click="emit('chosen', emoji, $event)"
|
||||||
>
|
>
|
||||||
<MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true"/>
|
<MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true" :fallbackToImage="true"/>
|
||||||
<MkEmoji v-else class="emoji" :emoji="emoji" :normal="true"/>
|
<MkEmoji v-else class="emoji" :emoji="emoji" :normal="true"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -87,7 +87,7 @@ const shown = ref(!!props.initialShown);
|
||||||
function computeButtonTitle(ev: MouseEvent): void {
|
function computeButtonTitle(ev: MouseEvent): void {
|
||||||
const elm = ev.target as HTMLElement;
|
const elm = ev.target as HTMLElement;
|
||||||
const emoji = elm.dataset.emoji as string;
|
const emoji = elm.dataset.emoji as string;
|
||||||
elm.title = getEmojiName(emoji) ?? emoji;
|
elm.title = getEmojiName(emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
function nestedChosen(emoji: any, ev: MouseEvent) {
|
function nestedChosen(emoji: any, ev: MouseEvent) {
|
||||||
|
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@click="chosen(emoji, $event)"
|
@click="chosen(emoji, $event)"
|
||||||
>
|
>
|
||||||
<MkCustomEmoji class="emoji" :name="emoji.name"/>
|
<MkCustomEmoji class="emoji" :name="emoji.name" :fallbackToImage="true"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="searchResultUnicode.length > 0" class="body">
|
<div v-if="searchResultUnicode.length > 0" class="body">
|
||||||
|
@ -353,7 +353,7 @@ watch(q, () => {
|
||||||
searchResultUnicode.value = Array.from(searchUnicode());
|
searchResultUnicode.value = Array.from(searchUnicode());
|
||||||
});
|
});
|
||||||
|
|
||||||
function canReact(emoji: Misskey.entities.EmojiSimple | UnicodeEmojiDef): boolean {
|
function canReact(emoji: Misskey.entities.EmojiSimple | UnicodeEmojiDef | string): boolean {
|
||||||
return !props.targetNote || checkReactionPermissions($i!, props.targetNote, emoji);
|
return !props.targetNote || checkReactionPermissions($i!, props.targetNote, emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,11 +378,14 @@ function getKey(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef):
|
||||||
return typeof emoji === 'string' ? emoji : 'char' in emoji ? emoji.char : `:${emoji.name}:`;
|
return typeof emoji === 'string' ? emoji : 'char' in emoji ? emoji.char : `:${emoji.name}:`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDef(emoji: string) {
|
function getDef(emoji: string): string | Misskey.entities.EmojiSimple | UnicodeEmojiDef {
|
||||||
if (emoji.includes(':')) {
|
if (emoji.includes(':')) {
|
||||||
return customEmojisMap.get(emoji.replace(/:/g, ''))!;
|
// カスタム絵文字が存在する場合はその情報を持つオブジェクトを返し、
|
||||||
|
// サーバの管理画面から削除された等で情報が見つからない場合は名前の文字列をそのまま返しておく(undefinedを返すとエラーになるため)
|
||||||
|
const name = emoji.replaceAll(':', '');
|
||||||
|
return customEmojisMap.get(name) ?? emoji;
|
||||||
} else {
|
} else {
|
||||||
return getUnicodeEmoji(emoji)!;
|
return getUnicodeEmoji(emoji);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,7 +393,7 @@ function getDef(emoji: string) {
|
||||||
function computeButtonTitle(ev: MouseEvent): void {
|
function computeButtonTitle(ev: MouseEvent): void {
|
||||||
const elm = ev.target as HTMLElement;
|
const elm = ev.target as HTMLElement;
|
||||||
const emoji = elm.dataset.emoji as string;
|
const emoji = elm.dataset.emoji as string;
|
||||||
elm.title = getEmojiName(emoji) ?? emoji;
|
elm.title = getEmojiName(emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
function chosen(emoji: any, ev?: MouseEvent) {
|
function chosen(emoji: any, ev?: MouseEvent) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkCustomEmoji v-if="reaction[0] === ':'" ref="elRef" :name="reaction" :normal="true" :noStyle="noStyle" :url="emojiUrl"/>
|
<MkCustomEmoji v-if="reaction[0] === ':'" ref="elRef" :name="reaction" :normal="true" :noStyle="noStyle" :url="emojiUrl" :fallbackToImage="true"/>
|
||||||
<MkEmoji v-else ref="elRef" :emoji="reaction" :normal="true" :noStyle="noStyle"/>
|
<MkEmoji v-else ref="elRef" :emoji="reaction" :normal="true" :noStyle="noStyle"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ function getReactionName(reaction: string): string {
|
||||||
if (trimLocal.startsWith(':')) {
|
if (trimLocal.startsWith(':')) {
|
||||||
return trimLocal;
|
return trimLocal;
|
||||||
}
|
}
|
||||||
return getEmojiName(reaction) ?? reaction;
|
return getEmojiName(reaction);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -48,3 +48,18 @@ export const Missing = {
|
||||||
name: Default.args.name,
|
name: Default.args.name,
|
||||||
},
|
},
|
||||||
} satisfies StoryObj<typeof MkCustomEmoji>;
|
} satisfies StoryObj<typeof MkCustomEmoji>;
|
||||||
|
export const ErrorToText = {
|
||||||
|
...Default,
|
||||||
|
args: {
|
||||||
|
url: 'https://example.com/404',
|
||||||
|
name: Default.args.name,
|
||||||
|
},
|
||||||
|
} satisfies StoryObj<typeof MkCustomEmoji>;
|
||||||
|
export const ErrorToImage = {
|
||||||
|
...Default,
|
||||||
|
args: {
|
||||||
|
url: 'https://example.com/404',
|
||||||
|
name: Default.args.name,
|
||||||
|
fallbackToImage: true,
|
||||||
|
},
|
||||||
|
} satisfies StoryObj<typeof MkCustomEmoji>;
|
||||||
|
|
|
@ -4,7 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<span v-if="errored">:{{ customEmojiName }}:</span>
|
<img
|
||||||
|
v-if="errored && fallbackToImage"
|
||||||
|
:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
|
||||||
|
src="/client-assets/dummy.png"
|
||||||
|
:title="alt"
|
||||||
|
/>
|
||||||
|
<span v-else-if="errored">:{{ customEmojiName }}:</span>
|
||||||
<img
|
<img
|
||||||
v-else
|
v-else
|
||||||
:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
|
:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
|
||||||
|
@ -39,6 +45,7 @@ const props = defineProps<{
|
||||||
useOriginalSize?: boolean;
|
useOriginalSize?: boolean;
|
||||||
menu?: boolean;
|
menu?: boolean;
|
||||||
menuReaction?: boolean;
|
menuReaction?: boolean;
|
||||||
|
fallbackToImage?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const react = inject<((name: string) => void) | null>('react', null);
|
const react = inject<((name: string) => void) | null>('react', null);
|
||||||
|
|
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, inject } from 'vue';
|
import { computed, inject } from 'vue';
|
||||||
import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base.js';
|
import { char2fluentEmojiFilePath, char2twemojiFilePath } from '@/scripts/emoji-base.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { colorizeEmoji, getEmojiName } from '@/scripts/emojilist.js';
|
import { colorizeEmoji, getEmojiName } from '@/scripts/emojilist.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
@ -34,8 +34,7 @@ const colorizedNativeEmoji = computed(() => colorizeEmoji(props.emoji));
|
||||||
|
|
||||||
// Searching from an array with 2000 items for every emoji felt like too energy-consuming, so I decided to do it lazily on pointerenter
|
// Searching from an array with 2000 items for every emoji felt like too energy-consuming, so I decided to do it lazily on pointerenter
|
||||||
function computeTitle(event: PointerEvent): void {
|
function computeTitle(event: PointerEvent): void {
|
||||||
const title = getEmojiName(props.emoji as string) ?? props.emoji as string;
|
(event.target as HTMLElement).title = getEmojiName(props.emoji);
|
||||||
(event.target as HTMLElement).title = title;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClick(ev: MouseEvent) {
|
function onClick(ev: MouseEvent) {
|
||||||
|
|
|
@ -407,6 +407,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
|
||||||
useOriginalSize: scale >= 2.5,
|
useOriginalSize: scale >= 2.5,
|
||||||
menu: props.enableEmojiMenu,
|
menu: props.enableEmojiMenu,
|
||||||
menuReaction: props.enableEmojiMenuReaction,
|
menuReaction: props.enableEmojiMenuReaction,
|
||||||
|
fallbackToImage: false,
|
||||||
})];
|
})];
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
|
|
@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="misskey">Misskey</div>
|
<div class="misskey">Misskey</div>
|
||||||
<div class="version">v{{ version }}</div>
|
<div class="version">v{{ version }}</div>
|
||||||
<span v-for="emoji in easterEggEmojis" :key="emoji.id" class="emoji" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }">
|
<span v-for="emoji in easterEggEmojis" :key="emoji.id" class="emoji" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }">
|
||||||
<MkCustomEmoji v-if="emoji.emoji[0] === ':'" class="emoji" :name="emoji.emoji" :normal="true" :noStyle="true"/>
|
<MkCustomEmoji v-if="emoji.emoji[0] === ':'" class="emoji" :name="emoji.emoji" :normal="true" :noStyle="true" :fallbackToImage="true"/>
|
||||||
<MkEmoji v-else class="emoji" :emoji="emoji.emoji" :normal="true" :noStyle="true"/>
|
<MkEmoji v-else class="emoji" :emoji="emoji.emoji" :normal="true" :noStyle="true"/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
>
|
>
|
||||||
<template #item="{element}">
|
<template #item="{element}">
|
||||||
<button class="_button" :class="$style.emojisItem" @click="removeReaction(element, $event)">
|
<button class="_button" :class="$style.emojisItem" @click="removeReaction(element, $event)">
|
||||||
<MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true"/>
|
<MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true" :fallbackToImage="true"/>
|
||||||
<MkEmoji v-else :emoji="element" :normal="true"/>
|
<MkEmoji v-else :emoji="element" :normal="true"/>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
>
|
>
|
||||||
<template #item="{element}">
|
<template #item="{element}">
|
||||||
<button class="_button" :class="$style.emojisItem" @click="removeEmoji(element, $event)">
|
<button class="_button" :class="$style.emojisItem" @click="removeEmoji(element, $event)">
|
||||||
<MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true"/>
|
<MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true" :fallbackToImage="true"/>
|
||||||
<MkEmoji v-else :emoji="element" :normal="true"/>
|
<MkEmoji v-else :emoji="element" :normal="true"/>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { UnicodeEmojiDef } from './emojilist.js';
|
import { UnicodeEmojiDef } from './emojilist.js';
|
||||||
|
|
||||||
export function checkReactionPermissions(me: Misskey.entities.MeDetailed, note: Misskey.entities.Note, emoji: Misskey.entities.EmojiSimple | UnicodeEmojiDef): boolean {
|
export function checkReactionPermissions(me: Misskey.entities.MeDetailed, note: Misskey.entities.Note, emoji: Misskey.entities.EmojiSimple | UnicodeEmojiDef | string): boolean {
|
||||||
if ('char' in emoji) return true; // UnicodeEmojiDefなら常にリアクション可能
|
if (typeof emoji === 'string') return true; // UnicodeEmojiDefにも無い絵文字であれば文字列で来る。Unicode絵文字であることには変わりないので常にリアクション可能とする;
|
||||||
|
if ('char' in emoji) return true; // UnicodeEmojiDefなら常にリアクション可能
|
||||||
|
|
||||||
emoji = emoji as Misskey.entities.EmojiSimple;
|
const roleIdsThatCanBeUsedThisEmojiAsReaction = emoji.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [];
|
||||||
const roleIdsThatCanBeUsedThisEmojiAsReaction = emoji.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [];
|
return !(emoji.localOnly && note.user.host !== me.host)
|
||||||
return !(emoji.localOnly && note.user.host !== me.host)
|
|
||||||
&& !(emoji.isSensitive && (note.reactionAcceptance === 'nonSensitiveOnly' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote'))
|
&& !(emoji.isSensitive && (note.reactionAcceptance === 'nonSensitiveOnly' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote'))
|
||||||
&& (roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || me.roles.some(role => roleIdsThatCanBeUsedThisEmojiAsReaction.includes(role.id)));
|
&& (roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || me.roles.some(role => roleIdsThatCanBeUsedThisEmojiAsReaction.includes(role.id)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,21 +39,29 @@ for (let i = 0; i < emojilist.length; i++) {
|
||||||
|
|
||||||
export const emojiCharByCategory = _charGroupByCategory;
|
export const emojiCharByCategory = _charGroupByCategory;
|
||||||
|
|
||||||
export function getUnicodeEmoji(char: string): UnicodeEmojiDef | null {
|
export function getUnicodeEmoji(char: string): UnicodeEmojiDef | string {
|
||||||
// Colorize it because emojilist.json assumes that
|
// Colorize it because emojilist.json assumes that
|
||||||
return unicodeEmojisMap.get(colorizeEmoji(char)) ?? null;
|
return unicodeEmojisMap.get(colorizeEmoji(char))
|
||||||
|
// カラースタイル絵文字がjsonに無い場合はテキストスタイル絵文字にフォールバックする
|
||||||
|
?? unicodeEmojisMap.get(char)
|
||||||
|
// それでも見つからない場合はそのまま返す(絵文字情報がjsonに無い場合、このフォールバックが無いとレンダリングに失敗する)
|
||||||
|
?? char;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEmojiName(char: string): string | null {
|
export function getEmojiName(char: string): string {
|
||||||
// Colorize it because emojilist.json assumes that
|
// Colorize it because emojilist.json assumes that
|
||||||
const idx = _indexByChar.get(colorizeEmoji(char));
|
const idx = _indexByChar.get(colorizeEmoji(char)) ?? _indexByChar.get(char);
|
||||||
if (idx == null) {
|
if (idx === undefined) {
|
||||||
return null;
|
// 絵文字情報がjsonに無い場合は名前の取得が出来ないのでそのまま返すしか無い
|
||||||
|
return char;
|
||||||
} else {
|
} else {
|
||||||
return emojilist[idx].name;
|
return emojilist[idx].name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* テキストスタイル絵文字(U+260Eなどの1文字で表現される絵文字)をカラースタイル絵文字に変換します(VS16:U+FE0Fを付与)。
|
||||||
|
*/
|
||||||
export function colorizeEmoji(char: string) {
|
export function colorizeEmoji(char: string) {
|
||||||
return char.length === 1 ? `${char}\uFE0F` : char;
|
return char.length === 1 ? `${char}\uFE0F` : char;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2024.3.0",
|
"version": "2024.3.1",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"types": "./built/dts/index.d.ts",
|
"types": "./built/dts/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|
Loading…
Reference in a new issue