mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-25 17:23:08 +02:00
merge: feat: Add language metadata to notes (!401)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/401 Closes #253
This commit is contained in:
commit
5ee2cc06d8
37 changed files with 580 additions and 68 deletions
|
@ -212,5 +212,9 @@ checkActivityPubGetSignature: false
|
|||
|
||||
#customMOTD: ['Hello World', 'The sharks rule all', 'Shonks']
|
||||
|
||||
# Prefer these languages for remote notes with language-specific content
|
||||
# Must be valid language codes according to BCP 47
|
||||
#langPref: ['en', 'ja']
|
||||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
|
|
@ -269,5 +269,9 @@ checkActivityPubGetSignature: false
|
|||
|
||||
#customMOTD: ['Hello World', 'The sharks rule all', 'Shonks']
|
||||
|
||||
# Prefer these languages for remote notes with language-specific content
|
||||
# Must be valid language codes according to BCP 47
|
||||
#langPref: ['en', 'ja']
|
||||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
|
|
@ -284,6 +284,10 @@ checkActivityPubGetSignature: false
|
|||
|
||||
#customMOTD: ['Hello World', 'The sharks rule all', 'Shonks']
|
||||
|
||||
# Prefer these languages for remote notes with language-specific content
|
||||
# Must be valid language codes according to BCP 47
|
||||
#langPref: ['en', 'ja']
|
||||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
||||
|
|
|
@ -1270,6 +1270,7 @@ hemisphere: "Where are you located"
|
|||
withSensitive: "Include notes with sensitive files"
|
||||
userSaysSomethingSensitive: "Post by {name} contains sensitive content"
|
||||
enableHorizontalSwipe: "Swipe to switch tabs"
|
||||
noLanguage: "No language"
|
||||
loading: "Loading"
|
||||
surrender: "Cancel"
|
||||
gameRetry: "Retry"
|
||||
|
|
4
locales/index.d.ts
vendored
4
locales/index.d.ts
vendored
|
@ -5093,6 +5093,10 @@ export interface Locale extends ILocale {
|
|||
* スワイプしてタブを切り替える
|
||||
*/
|
||||
"enableHorizontalSwipe": string;
|
||||
/**
|
||||
* 言語なし
|
||||
*/
|
||||
"noLanguage": string;
|
||||
/**
|
||||
* 読み込み中
|
||||
*/
|
||||
|
|
|
@ -1269,6 +1269,7 @@ hemisphere: "お住まいの地域"
|
|||
withSensitive: "センシティブなファイルを含むノートを表示"
|
||||
userSaysSomethingSensitive: "{name}のセンシティブなファイルを含む投稿"
|
||||
enableHorizontalSwipe: "スワイプしてタブを切り替える"
|
||||
noLanguage: "言語なし"
|
||||
loading: "読み込み中"
|
||||
surrender: "やめる"
|
||||
gameRetry: "リトライ"
|
||||
|
|
13
packages/backend/migration/1706852162173-add-post-lang.js
Normal file
13
packages/backend/migration/1706852162173-add-post-lang.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export class AddPostLang1706852162173 {
|
||||
name = 'AddPostLang1706852162173'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" ADD "lang" character varying(10)`);
|
||||
await queryRunner.query(`ALTER TABLE "note_edit" ADD "lang" character varying(10)`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "lang"`);
|
||||
await queryRunner.query(`ALTER TABLE "note_edit" DROP COLUMN "lang"`);
|
||||
}
|
||||
}
|
|
@ -91,6 +91,8 @@ type Source = {
|
|||
|
||||
customMOTD?: string[];
|
||||
|
||||
langPref?: string[];
|
||||
|
||||
signToActivityPubGet?: boolean;
|
||||
checkActivityPubGetSignature?: boolean;
|
||||
|
||||
|
@ -153,6 +155,7 @@ export type Config = {
|
|||
customMOTD: string[] | undefined;
|
||||
signToActivityPubGet: boolean;
|
||||
checkActivityPubGetSignature: boolean | undefined;
|
||||
langPref: string[];
|
||||
|
||||
version: string;
|
||||
publishTarballInsteadOfProvideRepositoryUrl: boolean;
|
||||
|
@ -269,6 +272,7 @@ export function loadConfig(): Config {
|
|||
inboxJobMaxAttempts: config.inboxJobMaxAttempts,
|
||||
proxyRemoteFiles: config.proxyRemoteFiles,
|
||||
customMOTD: config.customMOTD,
|
||||
langPref: config.langPref ?? ['en', 'ja'],
|
||||
signToActivityPubGet: config.signToActivityPubGet ?? true,
|
||||
checkActivityPubGetSignature: config.checkActivityPubGetSignature,
|
||||
mediaProxy: externalMediaProxy ?? internalMediaProxy,
|
||||
|
|
|
@ -133,6 +133,7 @@ type Option = {
|
|||
createdAt?: Date | null;
|
||||
name?: string | null;
|
||||
text?: string | null;
|
||||
lang?: string | null;
|
||||
reply?: MiNote | null;
|
||||
renote?: MiNote | null;
|
||||
files?: MiDriveFile[] | null;
|
||||
|
@ -603,6 +604,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
: null,
|
||||
name: data.name,
|
||||
text: data.text,
|
||||
lang: data.lang,
|
||||
hasPoll: data.poll != null,
|
||||
cw: data.cw ?? null,
|
||||
tags: tags.map(tag => normalizeForSearch(tag)),
|
||||
|
|
|
@ -123,6 +123,7 @@ type Option = {
|
|||
createdAt?: Date | null;
|
||||
name?: string | null;
|
||||
text?: string | null;
|
||||
lang?: string | null;
|
||||
reply?: MiNote | null;
|
||||
renote?: MiNote | null;
|
||||
files?: MiDriveFile[] | null;
|
||||
|
@ -429,6 +430,9 @@ export class NoteEditService implements OnApplicationShutdown {
|
|||
if (oldnote.hasPoll !== !!data.poll) {
|
||||
update.hasPoll = !!data.poll;
|
||||
}
|
||||
if (data.lang !== oldnote.lang) {
|
||||
update.lang = data.lang;
|
||||
}
|
||||
|
||||
const poll = await this.pollsRepository.findOneBy({ noteId: oldnote.id });
|
||||
|
||||
|
@ -443,6 +447,7 @@ export class NoteEditService implements OnApplicationShutdown {
|
|||
oldText: oldnote.text || undefined,
|
||||
newText: update.text || undefined,
|
||||
cw: update.cw || undefined,
|
||||
lang: update.lang || undefined,
|
||||
fileIds: undefined,
|
||||
oldDate: exists ? oldnote.updatedAt as Date : this.idService.parse(oldnote.id).date,
|
||||
updatedAt: new Date(),
|
||||
|
@ -462,6 +467,7 @@ export class NoteEditService implements OnApplicationShutdown {
|
|||
: null,
|
||||
name: data.name,
|
||||
text: data.text,
|
||||
lang: data.lang,
|
||||
hasPoll: data.poll != null,
|
||||
cw: data.cw ?? null,
|
||||
tags: tags.map(tag => normalizeForSearch(tag)),
|
||||
|
|
|
@ -451,9 +451,15 @@ export class ApRendererService {
|
|||
_misskey_content: text,
|
||||
source: {
|
||||
content: text,
|
||||
contentMap: note.lang ? {
|
||||
[note.lang]: text,
|
||||
} : undefined,
|
||||
mediaType: 'text/x.misskeymarkdown',
|
||||
},
|
||||
}),
|
||||
contentMap: note.lang && content ? {
|
||||
[note.lang]: content,
|
||||
} : undefined,
|
||||
_misskey_quote: quote,
|
||||
quoteUrl: quote,
|
||||
quoteUri: quote,
|
||||
|
@ -743,9 +749,15 @@ export class ApRendererService {
|
|||
_misskey_content: text,
|
||||
source: {
|
||||
content: text,
|
||||
contentMap: note.lang ? {
|
||||
[note.lang]: text,
|
||||
} : undefined,
|
||||
mediaType: 'text/x.misskeymarkdown',
|
||||
},
|
||||
}),
|
||||
contentMap: note.lang && content ? {
|
||||
[note.lang]: content,
|
||||
} : undefined,
|
||||
_misskey_quote: quote,
|
||||
quoteUrl: quote,
|
||||
quoteUri: quote,
|
||||
|
|
|
@ -25,6 +25,7 @@ import { StatusError } from '@/misc/status-error.js';
|
|||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { checkHttps } from '@/misc/check-https.js';
|
||||
import { langs } from '@/misc/langmap.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
|
||||
|
@ -39,7 +40,7 @@ import { ApMentionService } from './ApMentionService.js';
|
|||
import { ApQuestionService } from './ApQuestionService.js';
|
||||
import { ApImageService } from './ApImageService.js';
|
||||
import type { Resolver } from '../ApResolverService.js';
|
||||
import type { IObject, IPost } from '../type.js';
|
||||
import type { IObject, IPost, Obj } from '../type.js';
|
||||
|
||||
@Injectable()
|
||||
export class ApNoteService {
|
||||
|
@ -173,12 +174,17 @@ export class ApNoteService {
|
|||
|
||||
// テキストのパース
|
||||
let text: string | null = null;
|
||||
if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
|
||||
text = note.source.content;
|
||||
let lang: string | null = null;
|
||||
if (note.source?.mediaType === 'text/x.misskeymarkdown' && (typeof note.source.content === 'string' || note.source.contentMap)) {
|
||||
const guessed = this.guessLang(note.source);
|
||||
text = guessed.text;
|
||||
lang = guessed.lang;
|
||||
} else if (typeof note._misskey_content !== 'undefined') {
|
||||
text = note._misskey_content;
|
||||
} else if (typeof note.content === 'string') {
|
||||
text = this.apMfmService.htmlToMfm(note.content, note.tag);
|
||||
} else if (typeof note.content === 'string' || note.contentMap) {
|
||||
const guessed = this.guessLang(note);
|
||||
lang = guessed.lang;
|
||||
if (guessed.text) text = this.apMfmService.htmlToMfm(guessed.text, note.tag);
|
||||
}
|
||||
|
||||
const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined);
|
||||
|
@ -310,6 +316,7 @@ export class ApNoteService {
|
|||
name: note.name,
|
||||
cw,
|
||||
text,
|
||||
lang,
|
||||
localOnly: false,
|
||||
visibility,
|
||||
visibleUsers,
|
||||
|
@ -400,12 +407,17 @@ export class ApNoteService {
|
|||
|
||||
// テキストのパース
|
||||
let text: string | null = null;
|
||||
if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
|
||||
text = note.source.content;
|
||||
let lang: string | null = null;
|
||||
if (note.source?.mediaType === 'text/x.misskeymarkdown' && (typeof note.source.content === 'string' || note.source.contentMap)) {
|
||||
const guessed = this.guessLang(note.source);
|
||||
text = guessed.text;
|
||||
lang = guessed.lang;
|
||||
} else if (typeof note._misskey_content !== 'undefined') {
|
||||
text = note._misskey_content;
|
||||
} else if (typeof note.content === 'string') {
|
||||
text = this.apMfmService.htmlToMfm(note.content, note.tag);
|
||||
} else if (typeof note.content === 'string' || note.contentMap) {
|
||||
const guessed = this.guessLang(note);
|
||||
lang = guessed.lang;
|
||||
if (guessed.text) text = this.apMfmService.htmlToMfm(guessed.text, note.tag);
|
||||
}
|
||||
|
||||
const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined);
|
||||
|
@ -537,6 +549,7 @@ export class ApNoteService {
|
|||
name: note.name,
|
||||
cw,
|
||||
text,
|
||||
lang,
|
||||
localOnly: false,
|
||||
visibility,
|
||||
visibleUsers,
|
||||
|
@ -654,4 +667,40 @@ export class ApNoteService {
|
|||
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
||||
}));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private guessLang(source: { contentMap?: Obj | null, content?: string | null }): { lang: string | null, text: string | null } {
|
||||
// do we have a map?
|
||||
if (source.contentMap) {
|
||||
const entries = Object.entries(source.contentMap);
|
||||
|
||||
// only one entry: take that
|
||||
if (entries.length === 1) {
|
||||
return { lang: entries[0][0], text: entries[0][1] };
|
||||
}
|
||||
|
||||
// did the sender indicate a preferred language?
|
||||
if (source.content) {
|
||||
for (const e of entries) {
|
||||
if (e[1] === source.content) {
|
||||
return { lang: e[0], text: e[1] };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// can we find one of *our* preferred languages?
|
||||
for (const prefLang of this.config.langPref) {
|
||||
if (source.contentMap[prefLang]) {
|
||||
return { lang: prefLang, text: source.contentMap[prefLang] };
|
||||
}
|
||||
}
|
||||
|
||||
// bah, just pick one
|
||||
return { lang: entries[0][0], text: entries[0][1] };
|
||||
}
|
||||
|
||||
// no map, so we don't know the language, just take whatever
|
||||
// content we got
|
||||
return { lang: null, text: source.content ?? null };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ export interface IObject {
|
|||
inReplyTo?: any;
|
||||
replies?: ICollection;
|
||||
content?: string | null;
|
||||
contentMap?: Obj | null;
|
||||
startTime?: Date;
|
||||
endTime?: Date;
|
||||
icon?: any;
|
||||
|
@ -114,6 +115,7 @@ export interface IPost extends IObject {
|
|||
type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video' | 'Event';
|
||||
source?: {
|
||||
content: string;
|
||||
contentMap?: Obj | null;
|
||||
mediaType: string;
|
||||
};
|
||||
_misskey_quote?: string;
|
||||
|
@ -128,6 +130,7 @@ export interface IQuestion extends IObject {
|
|||
actor: string;
|
||||
source?: {
|
||||
content: string;
|
||||
contentMap?: Obj | null;
|
||||
mediaType: string;
|
||||
};
|
||||
_misskey_quote?: string;
|
||||
|
|
|
@ -17,12 +17,12 @@ import { bindThis } from '@/decorators.js';
|
|||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { DebounceLoader } from '@/misc/loader.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
||||
import type { ReactionService } from '../ReactionService.js';
|
||||
import type { UserEntityService } from './UserEntityService.js';
|
||||
import type { DriveFileEntityService } from './DriveFileEntityService.js';
|
||||
import type { Config } from '@/config.js';
|
||||
|
||||
@Injectable()
|
||||
export class NoteEntityService implements OnModuleInit {
|
||||
|
@ -373,6 +373,7 @@ export class NoteEntityService implements OnModuleInit {
|
|||
uri: note.uri ?? undefined,
|
||||
url: note.url ?? undefined,
|
||||
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
|
||||
lang: note.lang,
|
||||
...(meId && Object.keys(note.reactions).length > 0 ? {
|
||||
myReaction: this.populateMyReaction(note, meId, options?._hint_),
|
||||
} : {}),
|
||||
|
|
|
@ -35,6 +35,9 @@ export const langmap = {
|
|||
'ar-SA': {
|
||||
nativeName: 'العربية (السعودية)',
|
||||
},
|
||||
'ay': {
|
||||
nativeName: 'Aymar aru',
|
||||
},
|
||||
'ay-BO': {
|
||||
nativeName: 'Aymar aru',
|
||||
},
|
||||
|
@ -44,6 +47,9 @@ export const langmap = {
|
|||
'az-AZ': {
|
||||
nativeName: 'Azərbaycan dili',
|
||||
},
|
||||
'be': {
|
||||
nativeName: 'Беларуская',
|
||||
},
|
||||
'be-BY': {
|
||||
nativeName: 'Беларуская',
|
||||
},
|
||||
|
@ -65,6 +71,9 @@ export const langmap = {
|
|||
'br': {
|
||||
nativeName: 'Brezhoneg',
|
||||
},
|
||||
'bs': {
|
||||
nativeName: 'Bosanski',
|
||||
},
|
||||
'bs-BA': {
|
||||
nativeName: 'Bosanski',
|
||||
},
|
||||
|
@ -77,7 +86,7 @@ export const langmap = {
|
|||
'cak': {
|
||||
nativeName: 'Maya Kaqchikel',
|
||||
},
|
||||
'ck-US': {
|
||||
'chr': {
|
||||
nativeName: 'ᏣᎳᎩ (tsalagi)',
|
||||
},
|
||||
'cs': {
|
||||
|
@ -152,9 +161,6 @@ export const langmap = {
|
|||
'en-ZA': {
|
||||
nativeName: 'English (South Africa)',
|
||||
},
|
||||
'en@pirate': {
|
||||
nativeName: 'English (Pirate)',
|
||||
},
|
||||
'eo': {
|
||||
nativeName: 'Esperanto',
|
||||
},
|
||||
|
@ -248,6 +254,9 @@ export const langmap = {
|
|||
'fr-CH': {
|
||||
nativeName: 'Français (Suisse)',
|
||||
},
|
||||
'fy': {
|
||||
nativeName: 'Frysk',
|
||||
},
|
||||
'fy-NL': {
|
||||
nativeName: 'Frysk',
|
||||
},
|
||||
|
@ -266,16 +275,22 @@ export const langmap = {
|
|||
'gl-ES': {
|
||||
nativeName: 'Galego',
|
||||
},
|
||||
'gn': {
|
||||
nativeName: 'Avañe\'ẽ',
|
||||
},
|
||||
'gn-PY': {
|
||||
nativeName: 'Avañe\'ẽ',
|
||||
},
|
||||
'gu': {
|
||||
nativeName: 'ગુજરાતી',
|
||||
},
|
||||
'gu-IN': {
|
||||
nativeName: 'ગુજરાતી',
|
||||
},
|
||||
'gv': {
|
||||
nativeName: 'Gaelg',
|
||||
},
|
||||
'gx-GR': {
|
||||
'grc': {
|
||||
nativeName: 'Ἑλληνική ἀρχαία',
|
||||
},
|
||||
'he': {
|
||||
|
@ -338,12 +353,21 @@ export const langmap = {
|
|||
'ja-JP': {
|
||||
nativeName: '日本語 (日本)',
|
||||
},
|
||||
'jv': {
|
||||
nativeName: 'Basa Jawa',
|
||||
},
|
||||
'jv-ID': {
|
||||
nativeName: 'Basa Jawa',
|
||||
},
|
||||
'ka': {
|
||||
nativeName: 'ქართული',
|
||||
},
|
||||
'ka-GE': {
|
||||
nativeName: 'ქართული',
|
||||
},
|
||||
'kk': {
|
||||
nativeName: 'Қазақша',
|
||||
},
|
||||
'kk-KZ': {
|
||||
nativeName: 'Қазақша',
|
||||
},
|
||||
|
@ -371,6 +395,9 @@ export const langmap = {
|
|||
'ko-KR': {
|
||||
nativeName: '한국어 (한국)',
|
||||
},
|
||||
'ku': {
|
||||
nativeName: 'Kurdî',
|
||||
},
|
||||
'ku-TR': {
|
||||
nativeName: 'Kurdî',
|
||||
},
|
||||
|
@ -386,6 +413,9 @@ export const langmap = {
|
|||
'lb': {
|
||||
nativeName: 'Lëtzebuergesch',
|
||||
},
|
||||
'li': {
|
||||
nativeName: 'Lèmbörgs',
|
||||
},
|
||||
'li-NL': {
|
||||
nativeName: 'Lèmbörgs',
|
||||
},
|
||||
|
@ -404,6 +434,9 @@ export const langmap = {
|
|||
'mai': {
|
||||
nativeName: 'मैथिली, মৈথিলী',
|
||||
},
|
||||
'mg': {
|
||||
nativeName: 'Malagasy',
|
||||
},
|
||||
'mg-MG': {
|
||||
nativeName: 'Malagasy',
|
||||
},
|
||||
|
@ -419,6 +452,9 @@ export const langmap = {
|
|||
'ml-IN': {
|
||||
nativeName: 'മലയാളം',
|
||||
},
|
||||
'mn': {
|
||||
nativeName: 'Монгол',
|
||||
},
|
||||
'mn-MN': {
|
||||
nativeName: 'Монгол',
|
||||
},
|
||||
|
@ -443,6 +479,9 @@ export const langmap = {
|
|||
'my': {
|
||||
nativeName: 'ဗမာစကာ',
|
||||
},
|
||||
'nan': {
|
||||
nativeName: '閩南語',
|
||||
},
|
||||
'no': {
|
||||
nativeName: 'Norsk',
|
||||
},
|
||||
|
@ -467,12 +506,18 @@ export const langmap = {
|
|||
'nl-NL': {
|
||||
nativeName: 'Nederlands (Nederland)',
|
||||
},
|
||||
'nn': {
|
||||
nativeName: 'Norsk (nynorsk)',
|
||||
},
|
||||
'nn-NO': {
|
||||
nativeName: 'Norsk (nynorsk)',
|
||||
},
|
||||
'oc': {
|
||||
nativeName: 'Occitan',
|
||||
},
|
||||
'or': {
|
||||
nativeName: 'ଓଡ଼ିଆ',
|
||||
},
|
||||
'or-IN': {
|
||||
nativeName: 'ଓଡ଼ିଆ',
|
||||
},
|
||||
|
@ -488,6 +533,9 @@ export const langmap = {
|
|||
'pl-PL': {
|
||||
nativeName: 'Polski',
|
||||
},
|
||||
'ps': {
|
||||
nativeName: 'پښتو',
|
||||
},
|
||||
'ps-AF': {
|
||||
nativeName: 'پښتو',
|
||||
},
|
||||
|
@ -500,9 +548,15 @@ export const langmap = {
|
|||
'pt-PT': {
|
||||
nativeName: 'Português (Portugal)',
|
||||
},
|
||||
'qu': {
|
||||
nativeName: 'Qhichwa',
|
||||
},
|
||||
'qu-PE': {
|
||||
nativeName: 'Qhichwa',
|
||||
},
|
||||
'rm': {
|
||||
nativeName: 'Rumantsch',
|
||||
},
|
||||
'rm-CH': {
|
||||
nativeName: 'Rumantsch',
|
||||
},
|
||||
|
@ -518,15 +572,24 @@ export const langmap = {
|
|||
'ru-RU': {
|
||||
nativeName: 'Русский',
|
||||
},
|
||||
'sa': {
|
||||
nativeName: 'संस्कृतम्',
|
||||
},
|
||||
'sa-IN': {
|
||||
nativeName: 'संस्कृतम्',
|
||||
},
|
||||
'se': {
|
||||
nativeName: 'Davvisámegiella',
|
||||
},
|
||||
'se-NO': {
|
||||
nativeName: 'Davvisámegiella',
|
||||
},
|
||||
'sh': {
|
||||
nativeName: 'српскохрватски',
|
||||
},
|
||||
'si': {
|
||||
nativeName: 'සිංහල',
|
||||
},
|
||||
'si-LK': {
|
||||
nativeName: 'සිංහල',
|
||||
},
|
||||
|
@ -542,6 +605,9 @@ export const langmap = {
|
|||
'sl-SI': {
|
||||
nativeName: 'Slovenščina',
|
||||
},
|
||||
'so': {
|
||||
nativeName: 'Soomaaliga',
|
||||
},
|
||||
'so-SO': {
|
||||
nativeName: 'Soomaaliga',
|
||||
},
|
||||
|
@ -602,12 +668,18 @@ export const langmap = {
|
|||
'tlh': {
|
||||
nativeName: 'tlhIngan-Hol',
|
||||
},
|
||||
'tok': {
|
||||
nativeName: 'Toki Pona',
|
||||
},
|
||||
'tr': {
|
||||
nativeName: 'Türkçe',
|
||||
},
|
||||
'tr-TR': {
|
||||
nativeName: 'Türkçe',
|
||||
},
|
||||
'tt': {
|
||||
nativeName: 'татарча',
|
||||
},
|
||||
'tt-RU': {
|
||||
nativeName: 'татарча',
|
||||
},
|
||||
|
@ -635,6 +707,9 @@ export const langmap = {
|
|||
'vi-VN': {
|
||||
nativeName: 'Tiếng Việt',
|
||||
},
|
||||
'xh': {
|
||||
nativeName: 'isiXhosa',
|
||||
},
|
||||
'xh-ZA': {
|
||||
nativeName: 'isiXhosa',
|
||||
},
|
||||
|
@ -644,6 +719,9 @@ export const langmap = {
|
|||
'yi-DE': {
|
||||
nativeName: 'ייִדיש (German)',
|
||||
},
|
||||
'yue': {
|
||||
nativeName: '粵語',
|
||||
},
|
||||
'zh': {
|
||||
nativeName: '中文',
|
||||
},
|
||||
|
@ -665,7 +743,16 @@ export const langmap = {
|
|||
'zh-TW': {
|
||||
nativeName: '中文(台灣)',
|
||||
},
|
||||
'zu': {
|
||||
nativeName: 'isiZulu',
|
||||
},
|
||||
'zu-ZA': {
|
||||
nativeName: 'isiZulu',
|
||||
},
|
||||
};
|
||||
|
||||
export const langs: string[] = [
|
||||
...(Object.keys(langmap).filter(tag => tag.indexOf('-') < 0)),
|
||||
'zh-Hans',
|
||||
'zh-Hant',
|
||||
];
|
||||
|
|
|
@ -61,6 +61,12 @@ export class MiNote {
|
|||
})
|
||||
public text: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 10,
|
||||
nullable: true,
|
||||
})
|
||||
public lang: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Entity, JoinColumn, Column, ManyToOne, PrimaryColumn, Index } from "typeorm";
|
||||
import { Entity, JoinColumn, Column, ManyToOne, PrimaryColumn, Index } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
import { MiNote } from './Note.js';
|
||||
import type { MiDriveFile } from './DriveFile.js';
|
||||
|
@ -11,46 +11,52 @@ export class NoteEdit {
|
|||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: "The ID of note.",
|
||||
comment: 'The ID of note.',
|
||||
})
|
||||
public noteId: MiNote["id"];
|
||||
public noteId: MiNote['id'];
|
||||
|
||||
@ManyToOne((type) => MiNote, {
|
||||
onDelete: "CASCADE",
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public note: MiNote | null;
|
||||
|
||||
@Column("text", {
|
||||
@Column('text', {
|
||||
nullable: true,
|
||||
})
|
||||
public oldText: string | null;
|
||||
|
||||
@Column("text", {
|
||||
@Column('text', {
|
||||
nullable: true,
|
||||
})
|
||||
public newText: string | null;
|
||||
|
||||
@Column("varchar", {
|
||||
@Column('varchar', {
|
||||
length: 512,
|
||||
nullable: true,
|
||||
})
|
||||
public cw: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 10,
|
||||
nullable: true,
|
||||
})
|
||||
public lang: string | null;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
array: true,
|
||||
default: "{}",
|
||||
default: '{}',
|
||||
})
|
||||
public fileIds: MiDriveFile["id"][];
|
||||
public fileIds: MiDriveFile['id'][];
|
||||
|
||||
@Column("timestamp with time zone", {
|
||||
comment: "The updated date of the Note.",
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The updated date of the Note.',
|
||||
})
|
||||
public updatedAt: Date;
|
||||
|
||||
@Column("timestamp with time zone", {
|
||||
comment: "The old date from before the edit",
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The old date from before the edit',
|
||||
})
|
||||
public oldDate: Date;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { langs } from '@/misc/langmap.js';
|
||||
|
||||
export const packedNoteSchema = {
|
||||
type: 'object',
|
||||
|
@ -26,6 +27,11 @@ export const packedNoteSchema = {
|
|||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
lang: {
|
||||
type: 'string',
|
||||
enum: langs,
|
||||
nullable: true,
|
||||
},
|
||||
cw: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
|
|
|
@ -20,6 +20,7 @@ import { isPureRenote } from '@/misc/is-pure-renote.js';
|
|||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { langs } from '@/misc/langmap.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
@ -148,6 +149,7 @@ export const paramDef = {
|
|||
visibleUserIds: { type: 'array', uniqueItems: true, items: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
} },
|
||||
lang: { type: 'string', enum: langs, nullable: true, maxLength: 10 },
|
||||
cw: { type: 'string', nullable: true, minLength: 1, maxLength: 500 },
|
||||
localOnly: { type: 'boolean', default: false },
|
||||
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
|
||||
|
@ -384,6 +386,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
|
||||
} : undefined,
|
||||
text: ps.text ?? undefined,
|
||||
lang: ps.lang,
|
||||
reply,
|
||||
renote,
|
||||
cw: ps.cw,
|
||||
|
|
|
@ -13,6 +13,7 @@ import { NoteEditService } from '@/core/NoteEditService.js';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { isPureRenote } from '@/misc/is-pure-renote.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { langs } from '@/misc/langmap.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
@ -197,6 +198,7 @@ export const paramDef = {
|
|||
format: 'misskey:id',
|
||||
},
|
||||
},
|
||||
lang: { type: 'string', enum: langs, nullable: true, maxLength: 10 },
|
||||
cw: { type: 'string', nullable: true, minLength: 1, maxLength: 500 },
|
||||
localOnly: { type: 'boolean', default: false },
|
||||
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
|
||||
|
@ -436,6 +438,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
|
||||
} : undefined,
|
||||
text: ps.text ?? undefined,
|
||||
lang: ps.lang,
|
||||
reply,
|
||||
renote,
|
||||
cw: ps.cw,
|
||||
|
|
|
@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
|
||||
<div style="container-type: inline-size;">
|
||||
<p v-if="appearNote.cw != null" :class="$style.cw">
|
||||
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
|
||||
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :lang="appearNote.lang" :author="appearNote.user" :nyaize="'respect'"/>
|
||||
<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll" style="margin: 4px 0;" @click.stop/>
|
||||
</p>
|
||||
<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
|
||||
|
@ -65,6 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
v-if="appearNote.text"
|
||||
:parsedNodes="parsed"
|
||||
:text="appearNote.text"
|
||||
:lang="appearNote.lang"
|
||||
:author="appearNote.user"
|
||||
:nyaize="'respect'"
|
||||
:emojiUrls="appearNote.emojis"
|
||||
|
@ -76,7 +77,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkLoading v-if="translating" mini/>
|
||||
<div v-else-if="translation">
|
||||
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
|
||||
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
||||
<Mfm :text="translation.text" :lang="nativeLang" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
||||
</div>
|
||||
</div>
|
||||
<MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton>
|
||||
|
@ -217,6 +218,7 @@ import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
|
|||
import { shouldCollapsed } from '@/scripts/collapsed.js';
|
||||
import { useRouter } from '@/router/supplier.js';
|
||||
import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
|
@ -305,11 +307,12 @@ const renoteCollapsed = ref(
|
|||
defaultStore.state.collapseRenotes && isRenote && (
|
||||
($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || // `||` must be `||`! See https://github.com/misskey-dev/misskey/issues/13131
|
||||
(appearNote.value.myReaction != null)
|
||||
)
|
||||
),
|
||||
);
|
||||
const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null);
|
||||
const animated = computed(() => parsed.value ? checkAnimationFromMfm(parsed.value) : null);
|
||||
const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
|
||||
const nativeLang = ref(miLocalStorage.getItem('lang') ?? window.navigator.language);
|
||||
|
||||
/* Overload FunctionにLintが対応していないのでコメントアウト
|
||||
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
|
||||
|
@ -416,7 +419,7 @@ function boostVisibility() {
|
|||
}
|
||||
}
|
||||
|
||||
function renote(visibility: Visibility, localOnly: boolean = false) {
|
||||
function renote(visibility: Visibility, localOnly = false) {
|
||||
pleaseLogin();
|
||||
showMovedDialog();
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</header>
|
||||
<div :class="$style.noteContent">
|
||||
<p v-if="appearNote.cw != null" :class="$style.cw">
|
||||
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
|
||||
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :lang="appearNote.lang" :author="appearNote.user" :nyaize="'respect'"/>
|
||||
<MkCwButton v-model="showContent" :text="appearNote.text" :files="appearNote.files" :poll="appearNote.poll"/>
|
||||
</p>
|
||||
<div v-show="appearNote.cw == null || showContent">
|
||||
|
@ -78,6 +78,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
v-if="appearNote.text"
|
||||
:parsedNodes="parsed"
|
||||
:text="appearNote.text"
|
||||
:lang="appearNote.lang"
|
||||
:author="appearNote.user"
|
||||
:nyaize="'respect'"
|
||||
:emojiUrls="appearNote.emojis"
|
||||
|
@ -90,7 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkLoading v-if="translating" mini/>
|
||||
<div v-else-if="translation">
|
||||
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
|
||||
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
||||
<Mfm :text="translation.text" :lang="nativeLang" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
||||
</div>
|
||||
</div>
|
||||
<MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton>
|
||||
|
@ -258,6 +259,7 @@ import MkPagination, { type Paging } from '@/components/MkPagination.vue';
|
|||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
|
||||
const props = defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
|
@ -321,6 +323,7 @@ const replies = ref<Misskey.entities.Note[]>([]);
|
|||
const quotes = ref<Misskey.entities.Note[]>([]);
|
||||
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i?.id));
|
||||
const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null);
|
||||
const nativeLang = ref(miLocalStorage.getItem('lang') ?? window.navigator.language);
|
||||
|
||||
watch(() => props.expandAllCws, (expandAllCws) => {
|
||||
if (expandAllCws !== showContent.value) showContent.value = expandAllCws;
|
||||
|
@ -438,7 +441,7 @@ function boostVisibility() {
|
|||
}
|
||||
}
|
||||
|
||||
function renote(visibility: Visibility, localOnly: boolean = false) {
|
||||
function renote(visibility: Visibility, localOnly = false) {
|
||||
pleaseLogin();
|
||||
showMovedDialog();
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
|
||||
<div :class="$style.content">
|
||||
<p v-if="note.cw != null" :class="$style.cw">
|
||||
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'respect'"/>
|
||||
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :lang="note.lang" :author="note.user" :nyaize="'respect'"/>
|
||||
<MkCwButton v-model="showContent" :text="note.text" :files="note.files" :poll="note.poll"/>
|
||||
</p>
|
||||
<div v-show="note.cw == null || showContent">
|
||||
|
@ -281,7 +281,7 @@ function boostVisibility() {
|
|||
}
|
||||
}
|
||||
|
||||
function renote(visibility: Visibility, localOnly: boolean = false) {
|
||||
function renote(visibility: Visibility, localOnly = false) {
|
||||
pleaseLogin();
|
||||
showMovedDialog();
|
||||
|
||||
|
|
|
@ -32,6 +32,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<span :class="$style.headerRightButtonText">{{ channel.name }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<button v-click-anime v-tooltip="i18n.ts.language" :class="['_button', $style.headerRightItem]" @click="setLanguage">
|
||||
<span><i class="ph-translate ph-bold ph-lg"></i></span>
|
||||
<span v-if="language" :class="$style.headerRightButtonText">{{ language }}</span>
|
||||
</button>
|
||||
<button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified'" @click="toggleLocalOnly">
|
||||
<span v-if="!localOnly"><i class="ph-rocket-launch ph-bold ph-lg"></i></span>
|
||||
<span v-else><i class="ph-rocket ph-bold ph-lg"></i></span>
|
||||
|
@ -65,16 +69,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</div>
|
||||
<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
|
||||
<input v-show="useCw" ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown">
|
||||
<input v-show="useCw" ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" :lang="language ?? undefined" @keydown="onKeydown">
|
||||
<div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
|
||||
<div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div>
|
||||
<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
|
||||
<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" :lang="language ?? undefined" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
|
||||
<div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div>
|
||||
</div>
|
||||
<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
|
||||
<XPostFormAttaches v-model="files" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName" @replaceFile="replaceFile"/>
|
||||
<MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
|
||||
<MkNotePreview v-if="showPreview" :class="$style.preview" :text="text" :files="files" :poll="poll ?? undefined" :useCw="useCw" :cw="cw" :user="postAccount ?? $i"/>
|
||||
<MkPollEditor v-if="poll" v-model="poll" :lang="language ?? undefined" @destroyed="poll = null"/>
|
||||
<MkNotePreview v-if="showPreview" :class="$style.preview" :text="text" :lang="language ?? undefined" :files="files" :poll="poll ?? undefined" :useCw="useCw" :cw="cw" :user="postAccount ?? $i"/>
|
||||
<div v-if="showingOptions" style="padding: 8px 16px;">
|
||||
</div>
|
||||
<footer :class="$style.footer">
|
||||
|
@ -130,6 +134,8 @@ import { miLocalStorage } from '@/local-storage.js';
|
|||
import { claimAchievement } from '@/scripts/achievements.js';
|
||||
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
||||
import { mfmFunctionPicker } from '@/scripts/mfm-function-picker.js';
|
||||
import { langmap, langs } from '@/scripts/langmap.js';
|
||||
import { MenuItem } from '@/types/menu.js';
|
||||
|
||||
const $i = signinRequired();
|
||||
|
||||
|
@ -144,6 +150,7 @@ const props = withDefaults(defineProps<{
|
|||
initialText?: string;
|
||||
initialCw?: string;
|
||||
initialVisibility?: (typeof Misskey.noteVisibilities)[number];
|
||||
initialLanguage?: (typeof Misskey.languages)[number];
|
||||
initialFiles?: Misskey.entities.DriveFile[];
|
||||
initialLocalOnly?: boolean;
|
||||
initialVisibleUsers?: Misskey.entities.UserDetailed[];
|
||||
|
@ -202,6 +209,7 @@ const recentHashtags = ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'
|
|||
const imeText = ref('');
|
||||
const showingOptions = ref(false);
|
||||
const textAreaReadOnly = ref(false);
|
||||
const language = ref<string | null>(props.initialLanguage ?? defaultStore.state.recentlyUsedPostLanguages[0] ?? attemptNormalizeLang(localStorage.getItem('lang')));
|
||||
|
||||
const draftKey = computed((): string => {
|
||||
let key = props.channel ? `channel:${props.channel.id}` : '';
|
||||
|
@ -362,6 +370,7 @@ function watchForDraft() {
|
|||
watch(files, () => saveDraft(), { deep: true });
|
||||
watch(visibility, () => saveDraft());
|
||||
watch(localOnly, () => saveDraft());
|
||||
watch(language, () => saveDraft());
|
||||
}
|
||||
|
||||
function MFMWindow() {
|
||||
|
@ -536,6 +545,64 @@ async function toggleReactionAcceptance() {
|
|||
reactionAcceptance.value = select.result;
|
||||
}
|
||||
|
||||
function attemptNormalizeLang(lang: string | null) {
|
||||
if (lang == null) return null;
|
||||
if (!langs[lang]) lang = lang.split('-')[0];
|
||||
return lang;
|
||||
}
|
||||
|
||||
function setLanguage(ev: MouseEvent) {
|
||||
const actions: Array<MenuItem> = [];
|
||||
|
||||
if (language.value != null) actions.push({
|
||||
text: langmap[language.value].nativeName,
|
||||
danger: false,
|
||||
active: true,
|
||||
action: () => {},
|
||||
});
|
||||
|
||||
// Show recently used language first
|
||||
let recentlyUsedLanguagesExist = false;
|
||||
for (const lang of defaultStore.state.recentlyUsedPostLanguages) {
|
||||
if (lang === language.value) continue;
|
||||
if (!langs.includes(lang)) continue;
|
||||
actions.push({
|
||||
text: langmap[lang].nativeName,
|
||||
danger: false,
|
||||
active: false,
|
||||
action: () => {
|
||||
language.value = lang;
|
||||
},
|
||||
});
|
||||
recentlyUsedLanguagesExist = true;
|
||||
}
|
||||
if (recentlyUsedLanguagesExist) actions.push({ type: 'divider' });
|
||||
|
||||
actions.push({
|
||||
text: i18n.ts.noLanguage,
|
||||
danger: false,
|
||||
active: false,
|
||||
action: () => {
|
||||
language.value = null;
|
||||
},
|
||||
});
|
||||
|
||||
for (const lang of langs) {
|
||||
if (lang === language.value) continue;
|
||||
if (defaultStore.state.recentlyUsedPostLanguages.includes(lang)) continue;
|
||||
actions.push({
|
||||
text: langmap[lang].nativeName,
|
||||
danger: false,
|
||||
active: false,
|
||||
action: () => {
|
||||
language.value = lang;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
os.popupMenu(actions, ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
function pushVisibleUser(user: Misskey.entities.UserDetailed) {
|
||||
if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) {
|
||||
visibleUsers.value.push(user);
|
||||
|
@ -677,6 +744,7 @@ function saveDraft() {
|
|||
cw: cw.value,
|
||||
visibility: visibility.value,
|
||||
localOnly: localOnly.value,
|
||||
lang: language.value,
|
||||
files: files.value,
|
||||
poll: poll.value,
|
||||
},
|
||||
|
@ -777,6 +845,7 @@ async function post(ev?: MouseEvent) {
|
|||
channelId: props.channel ? props.channel.id : undefined,
|
||||
poll: poll.value,
|
||||
cw: useCw.value ? cw.value ?? '' : null,
|
||||
lang: language.value ?? null,
|
||||
localOnly: localOnly.value,
|
||||
visibility: visibility.value,
|
||||
visibleUserIds: visibility.value === 'specified' ? visibleUsers.value.map(u => u.id) : undefined,
|
||||
|
@ -884,6 +953,17 @@ async function post(ev?: MouseEvent) {
|
|||
text: err.message + '\n' + (err as any).id,
|
||||
});
|
||||
});
|
||||
|
||||
// update recentlyUsedLanguages
|
||||
if (language.value != null) {
|
||||
const maxLength = 6;
|
||||
const filteredRecentlyUsed = defaultStore.state.recentlyUsedPostLanguages.filter((lang) => {
|
||||
return (lang !== language.value && langs.includes(lang));
|
||||
});
|
||||
const recentlyUsedLangs = [language.value].concat(filteredRecentlyUsed).slice(0, maxLength);
|
||||
|
||||
defaultStore.set('recentlyUsedPostLanguages', recentlyUsedLangs);
|
||||
}
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
|
@ -986,6 +1066,7 @@ onMounted(() => {
|
|||
useCw.value = draft.data.useCw;
|
||||
visibility.value = draft.data.visibility;
|
||||
localOnly.value = draft.data.localOnly;
|
||||
language.value = draft.data.lang;
|
||||
files.value = (draft.data.files || []).filter(draftFile => draftFile);
|
||||
|
||||
if (draft.data.poll) {
|
||||
|
@ -1011,6 +1092,7 @@ onMounted(() => {
|
|||
}
|
||||
visibility.value = init.visibility;
|
||||
localOnly.value = init.localOnly ?? false;
|
||||
language.value = init.lang;
|
||||
quoteId.value = init.renote ? init.renote.id : null;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ const props = defineProps<{
|
|||
initialText?: string;
|
||||
initialCw?: string;
|
||||
initialVisibility?: (typeof Misskey.noteVisibilities)[number];
|
||||
initialLanguage?: (typeof Misskey.languages)[number];
|
||||
initialFiles?: Misskey.entities.DriveFile[];
|
||||
initialLocalOnly?: boolean;
|
||||
initialVisibleUsers?: Misskey.entities.UserDetailed[];
|
||||
|
@ -31,7 +32,7 @@ const props = defineProps<{
|
|||
instant?: boolean;
|
||||
fixed?: boolean;
|
||||
autofocus?: boolean;
|
||||
editId?: Misskey.entities.Note["id"];
|
||||
editId?: Misskey.entities.Note['id'];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
|
@ -9,14 +9,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
||||
<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deletedNote }})</span>
|
||||
<MkA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`" @click.stop><i class="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA>
|
||||
<Mfm v-if="note.text" :text="note.text" :author="note.user" :nyaize="'respect'" :isAnim="allowAnim" :emojiUrls="note.emojis"/>
|
||||
<Mfm v-if="note.text" :text="note.text" :lang="note.lang" :author="note.user" :nyaize="'respect'" :isAnim="allowAnim" :emojiUrls="note.emojis"/>
|
||||
<MkButton v-if="!allowAnim && animated && !hideFiles" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton>
|
||||
<MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated && !hideFiles" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton>
|
||||
<div v-if="note.text && translating || note.text && translation" :class="$style.translation">
|
||||
<MkLoading v-if="translating" mini/>
|
||||
<div v-else>
|
||||
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
|
||||
<Mfm :text="translation.text" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis"/>
|
||||
<Mfm :text="translation.text" :lang="nativeLang" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis"/>
|
||||
</div>
|
||||
</div>
|
||||
<MkA v-if="note.renoteId" :class="$style.rp" :to="`/notes/${note.renoteId}`" @click.stop>RN: ...</MkA>
|
||||
|
@ -51,6 +51,7 @@ import { defaultStore } from '@/store.js';
|
|||
import { useRouter } from '@/router/supplier.js';
|
||||
import * as os from '@/os.js';
|
||||
import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
|
||||
const props = defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
|
@ -72,6 +73,7 @@ function noteclick(id: string) {
|
|||
const parsed = computed(() => props.note.text ? mfm.parse(props.note.text) : null);
|
||||
const animated = computed(() => parsed.value ? checkAnimationFromMfm(parsed.value) : null);
|
||||
let allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
|
||||
const nativeLang = ref(miLocalStorage.getItem('lang') ?? window.navigator.language);
|
||||
|
||||
const isLong = defaultStore.state.expandLongNote && !props.hideFiles ? false : shouldCollapsed(props.note, []);
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div :class="[{ [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click.stop="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined">
|
||||
<div style="container-type: inline-size;">
|
||||
<p v-if="appearNote.cw != null" :class="$style.cw">
|
||||
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
|
||||
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :lang="appearNote.lang" :author="appearNote.user" :nyaize="'respect'"/>
|
||||
<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll" style="margin: 4px 0;" @click.stop/>
|
||||
</p>
|
||||
<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
|
||||
|
@ -67,6 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
v-if="appearNote.text"
|
||||
:parsedNodes="parsed"
|
||||
:text="appearNote.text"
|
||||
:lang="appearNote.lang"
|
||||
:author="appearNote.user"
|
||||
:nyaize="'respect'"
|
||||
:emojiUrls="appearNote.emojis"
|
||||
|
@ -78,7 +79,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkLoading v-if="translating" mini/>
|
||||
<div v-else-if="translation">
|
||||
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
|
||||
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
||||
<Mfm :text="translation.text" :lang="nativeLang" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
||||
</div>
|
||||
</div>
|
||||
<MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton>
|
||||
|
@ -218,6 +219,7 @@ import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
|
|||
import { shouldCollapsed } from '@/scripts/collapsed.js';
|
||||
import { useRouter } from '@/router/supplier.js';
|
||||
import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
|
@ -306,11 +308,12 @@ const renoteCollapsed = ref(
|
|||
defaultStore.state.collapseRenotes && isRenote && (
|
||||
($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || // `||` must be `||`! See https://github.com/misskey-dev/misskey/issues/13131
|
||||
(appearNote.value.myReaction != null)
|
||||
)
|
||||
),
|
||||
);
|
||||
const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null);
|
||||
const animated = computed(() => parsed.value ? checkAnimationFromMfm(parsed.value) : null);
|
||||
const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
|
||||
const nativeLang = ref(miLocalStorage.getItem('lang') ?? window.navigator.language);
|
||||
|
||||
/* Overload FunctionにLintが対応していないのでコメントアウト
|
||||
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
|
||||
|
@ -417,7 +420,7 @@ function boostVisibility() {
|
|||
}
|
||||
}
|
||||
|
||||
function renote(visibility: Visibility, localOnly: boolean = false) {
|
||||
function renote(visibility: Visibility, localOnly = false) {
|
||||
pleaseLogin();
|
||||
showMovedDialog();
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</header>
|
||||
<div :class="$style.noteContent">
|
||||
<p v-if="appearNote.cw != null" :class="$style.cw">
|
||||
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
|
||||
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :lang="appearNote.lang" :author="appearNote.user" :nyaize="'respect'"/>
|
||||
<MkCwButton v-model="showContent" :text="appearNote.text" :files="appearNote.files" :poll="appearNote.poll"/>
|
||||
</p>
|
||||
<div v-show="appearNote.cw == null || showContent">
|
||||
|
@ -86,6 +86,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
v-if="appearNote.text"
|
||||
:parsedNodes="parsed"
|
||||
:text="appearNote.text"
|
||||
:lang="appearNote.lang"
|
||||
:author="appearNote.user"
|
||||
:nyaize="'respect'"
|
||||
:emojiUrls="appearNote.emojis"
|
||||
|
@ -98,7 +99,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkLoading v-if="translating" mini/>
|
||||
<div v-else-if="translation">
|
||||
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
|
||||
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
||||
<Mfm :text="translation.text" :lang="nativeLang" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
||||
</div>
|
||||
</div>
|
||||
<MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton>
|
||||
|
@ -266,6 +267,7 @@ import MkPagination, { type Paging } from '@/components/MkPagination.vue';
|
|||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
|
||||
const props = defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
|
@ -330,6 +332,7 @@ const replies = ref<Misskey.entities.Note[]>([]);
|
|||
const quotes = ref<Misskey.entities.Note[]>([]);
|
||||
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i?.id));
|
||||
const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null);
|
||||
const nativeLang = ref(miLocalStorage.getItem('lang') ?? window.navigator.language);
|
||||
|
||||
watch(() => props.expandAllCws, (expandAllCws) => {
|
||||
if (expandAllCws !== showContent.value) showContent.value = expandAllCws;
|
||||
|
@ -447,7 +450,7 @@ function boostVisibility() {
|
|||
}
|
||||
}
|
||||
|
||||
function renote(visibility: Visibility, localOnly: boolean = false) {
|
||||
function renote(visibility: Visibility, localOnly = false) {
|
||||
pleaseLogin();
|
||||
showMovedDialog();
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<SkNoteHeader :class="$style.header" :note="note" :classic="true" :mini="true"/>
|
||||
<div :class="$style.content">
|
||||
<p v-if="note.cw != null" :class="$style.cw">
|
||||
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'respect'"/>
|
||||
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :lang="note.lang" :author="note.user" :nyaize="'respect'"/>
|
||||
<MkCwButton v-model="showContent" :text="note.text" :files="note.files" :poll="note.poll"/>
|
||||
</p>
|
||||
<div v-show="note.cw == null || showContent">
|
||||
|
@ -295,7 +295,7 @@ function boostVisibility() {
|
|||
}
|
||||
}
|
||||
|
||||
function renote(visibility: Visibility, localOnly: boolean = false) {
|
||||
function renote(visibility: Visibility, localOnly = false) {
|
||||
pleaseLogin();
|
||||
showMovedDialog();
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ type MfmProps = {
|
|||
enableEmojiMenu?: boolean;
|
||||
enableEmojiMenuReaction?: boolean;
|
||||
isAnim?: boolean;
|
||||
lang?: Misskey.entities.Note['lang'];
|
||||
};
|
||||
|
||||
type MfmEvents = {
|
||||
|
@ -475,5 +476,6 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
|
|||
return h('span', {
|
||||
// https://codeday.me/jp/qa/20190424/690106.html
|
||||
style: props.nowrap ? 'white-space: pre; word-wrap: normal; overflow: hidden; text-overflow: ellipsis;' : 'white-space: pre-wrap;',
|
||||
lang: props.lang ?? undefined,
|
||||
}, genEl(rootAst, props.rootScale ?? 1));
|
||||
}
|
||||
|
|
|
@ -35,6 +35,9 @@ export const langmap = {
|
|||
'ar-SA': {
|
||||
nativeName: 'العربية (السعودية)',
|
||||
},
|
||||
'ay': {
|
||||
nativeName: 'Aymar aru',
|
||||
},
|
||||
'ay-BO': {
|
||||
nativeName: 'Aymar aru',
|
||||
},
|
||||
|
@ -44,6 +47,9 @@ export const langmap = {
|
|||
'az-AZ': {
|
||||
nativeName: 'Azərbaycan dili',
|
||||
},
|
||||
'be': {
|
||||
nativeName: 'Беларуская',
|
||||
},
|
||||
'be-BY': {
|
||||
nativeName: 'Беларуская',
|
||||
},
|
||||
|
@ -65,6 +71,9 @@ export const langmap = {
|
|||
'br': {
|
||||
nativeName: 'Brezhoneg',
|
||||
},
|
||||
'bs': {
|
||||
nativeName: 'Bosanski',
|
||||
},
|
||||
'bs-BA': {
|
||||
nativeName: 'Bosanski',
|
||||
},
|
||||
|
@ -77,7 +86,7 @@ export const langmap = {
|
|||
'cak': {
|
||||
nativeName: 'Maya Kaqchikel',
|
||||
},
|
||||
'ck-US': {
|
||||
'chr': {
|
||||
nativeName: 'ᏣᎳᎩ (tsalagi)',
|
||||
},
|
||||
'cs': {
|
||||
|
@ -152,9 +161,6 @@ export const langmap = {
|
|||
'en-ZA': {
|
||||
nativeName: 'English (South Africa)',
|
||||
},
|
||||
'en@pirate': {
|
||||
nativeName: 'English (Pirate)',
|
||||
},
|
||||
'eo': {
|
||||
nativeName: 'Esperanto',
|
||||
},
|
||||
|
@ -248,6 +254,9 @@ export const langmap = {
|
|||
'fr-CH': {
|
||||
nativeName: 'Français (Suisse)',
|
||||
},
|
||||
'fy': {
|
||||
nativeName: 'Frysk',
|
||||
},
|
||||
'fy-NL': {
|
||||
nativeName: 'Frysk',
|
||||
},
|
||||
|
@ -266,16 +275,22 @@ export const langmap = {
|
|||
'gl-ES': {
|
||||
nativeName: 'Galego',
|
||||
},
|
||||
'gn': {
|
||||
nativeName: 'Avañe\'ẽ',
|
||||
},
|
||||
'gn-PY': {
|
||||
nativeName: 'Avañe\'ẽ',
|
||||
},
|
||||
'gu': {
|
||||
nativeName: 'ગુજરાતી',
|
||||
},
|
||||
'gu-IN': {
|
||||
nativeName: 'ગુજરાતી',
|
||||
},
|
||||
'gv': {
|
||||
nativeName: 'Gaelg',
|
||||
},
|
||||
'gx-GR': {
|
||||
'grc': {
|
||||
nativeName: 'Ἑλληνική ἀρχαία',
|
||||
},
|
||||
'he': {
|
||||
|
@ -338,12 +353,21 @@ export const langmap = {
|
|||
'ja-JP': {
|
||||
nativeName: '日本語 (日本)',
|
||||
},
|
||||
'jv': {
|
||||
nativeName: 'Basa Jawa',
|
||||
},
|
||||
'jv-ID': {
|
||||
nativeName: 'Basa Jawa',
|
||||
},
|
||||
'ka': {
|
||||
nativeName: 'ქართული',
|
||||
},
|
||||
'ka-GE': {
|
||||
nativeName: 'ქართული',
|
||||
},
|
||||
'kk': {
|
||||
nativeName: 'Қазақша',
|
||||
},
|
||||
'kk-KZ': {
|
||||
nativeName: 'Қазақша',
|
||||
},
|
||||
|
@ -371,6 +395,9 @@ export const langmap = {
|
|||
'ko-KR': {
|
||||
nativeName: '한국어 (한국)',
|
||||
},
|
||||
'ku': {
|
||||
nativeName: 'Kurdî',
|
||||
},
|
||||
'ku-TR': {
|
||||
nativeName: 'Kurdî',
|
||||
},
|
||||
|
@ -386,6 +413,9 @@ export const langmap = {
|
|||
'lb': {
|
||||
nativeName: 'Lëtzebuergesch',
|
||||
},
|
||||
'li': {
|
||||
nativeName: 'Lèmbörgs',
|
||||
},
|
||||
'li-NL': {
|
||||
nativeName: 'Lèmbörgs',
|
||||
},
|
||||
|
@ -404,6 +434,9 @@ export const langmap = {
|
|||
'mai': {
|
||||
nativeName: 'मैथिली, মৈথিলী',
|
||||
},
|
||||
'mg': {
|
||||
nativeName: 'Malagasy',
|
||||
},
|
||||
'mg-MG': {
|
||||
nativeName: 'Malagasy',
|
||||
},
|
||||
|
@ -419,6 +452,9 @@ export const langmap = {
|
|||
'ml-IN': {
|
||||
nativeName: 'മലയാളം',
|
||||
},
|
||||
'mn': {
|
||||
nativeName: 'Монгол',
|
||||
},
|
||||
'mn-MN': {
|
||||
nativeName: 'Монгол',
|
||||
},
|
||||
|
@ -443,6 +479,9 @@ export const langmap = {
|
|||
'my': {
|
||||
nativeName: 'ဗမာစကာ',
|
||||
},
|
||||
'nan': {
|
||||
nativeName: '閩南語',
|
||||
},
|
||||
'no': {
|
||||
nativeName: 'Norsk',
|
||||
},
|
||||
|
@ -467,12 +506,18 @@ export const langmap = {
|
|||
'nl-NL': {
|
||||
nativeName: 'Nederlands (Nederland)',
|
||||
},
|
||||
'nn': {
|
||||
nativeName: 'Norsk (nynorsk)',
|
||||
},
|
||||
'nn-NO': {
|
||||
nativeName: 'Norsk (nynorsk)',
|
||||
},
|
||||
'oc': {
|
||||
nativeName: 'Occitan',
|
||||
},
|
||||
'or': {
|
||||
nativeName: 'ଓଡ଼ିଆ',
|
||||
},
|
||||
'or-IN': {
|
||||
nativeName: 'ଓଡ଼ିଆ',
|
||||
},
|
||||
|
@ -488,6 +533,9 @@ export const langmap = {
|
|||
'pl-PL': {
|
||||
nativeName: 'Polski',
|
||||
},
|
||||
'ps': {
|
||||
nativeName: 'پښتو',
|
||||
},
|
||||
'ps-AF': {
|
||||
nativeName: 'پښتو',
|
||||
},
|
||||
|
@ -500,9 +548,15 @@ export const langmap = {
|
|||
'pt-PT': {
|
||||
nativeName: 'Português (Portugal)',
|
||||
},
|
||||
'qu': {
|
||||
nativeName: 'Qhichwa',
|
||||
},
|
||||
'qu-PE': {
|
||||
nativeName: 'Qhichwa',
|
||||
},
|
||||
'rm': {
|
||||
nativeName: 'Rumantsch',
|
||||
},
|
||||
'rm-CH': {
|
||||
nativeName: 'Rumantsch',
|
||||
},
|
||||
|
@ -518,15 +572,24 @@ export const langmap = {
|
|||
'ru-RU': {
|
||||
nativeName: 'Русский',
|
||||
},
|
||||
'sa': {
|
||||
nativeName: 'संस्कृतम्',
|
||||
},
|
||||
'sa-IN': {
|
||||
nativeName: 'संस्कृतम्',
|
||||
},
|
||||
'se': {
|
||||
nativeName: 'Davvisámegiella',
|
||||
},
|
||||
'se-NO': {
|
||||
nativeName: 'Davvisámegiella',
|
||||
},
|
||||
'sh': {
|
||||
nativeName: 'српскохрватски',
|
||||
},
|
||||
'si': {
|
||||
nativeName: 'සිංහල',
|
||||
},
|
||||
'si-LK': {
|
||||
nativeName: 'සිංහල',
|
||||
},
|
||||
|
@ -542,6 +605,9 @@ export const langmap = {
|
|||
'sl-SI': {
|
||||
nativeName: 'Slovenščina',
|
||||
},
|
||||
'so': {
|
||||
nativeName: 'Soomaaliga',
|
||||
},
|
||||
'so-SO': {
|
||||
nativeName: 'Soomaaliga',
|
||||
},
|
||||
|
@ -602,12 +668,18 @@ export const langmap = {
|
|||
'tlh': {
|
||||
nativeName: 'tlhIngan-Hol',
|
||||
},
|
||||
'tok': {
|
||||
nativeName: 'Toki Pona',
|
||||
},
|
||||
'tr': {
|
||||
nativeName: 'Türkçe',
|
||||
},
|
||||
'tr-TR': {
|
||||
nativeName: 'Türkçe',
|
||||
},
|
||||
'tt': {
|
||||
nativeName: 'татарча',
|
||||
},
|
||||
'tt-RU': {
|
||||
nativeName: 'татарча',
|
||||
},
|
||||
|
@ -635,6 +707,9 @@ export const langmap = {
|
|||
'vi-VN': {
|
||||
nativeName: 'Tiếng Việt',
|
||||
},
|
||||
'xh': {
|
||||
nativeName: 'isiXhosa',
|
||||
},
|
||||
'xh-ZA': {
|
||||
nativeName: 'isiXhosa',
|
||||
},
|
||||
|
@ -644,6 +719,9 @@ export const langmap = {
|
|||
'yi-DE': {
|
||||
nativeName: 'ייִדיש (German)',
|
||||
},
|
||||
'yue': {
|
||||
nativeName: '粵語',
|
||||
},
|
||||
'zh': {
|
||||
nativeName: '中文',
|
||||
},
|
||||
|
@ -665,7 +743,16 @@ export const langmap = {
|
|||
'zh-TW': {
|
||||
nativeName: '中文(台灣)',
|
||||
},
|
||||
'zu': {
|
||||
nativeName: 'isiZulu',
|
||||
},
|
||||
'zu-ZA': {
|
||||
nativeName: 'isiZulu',
|
||||
},
|
||||
};
|
||||
|
||||
export const langs: string[] = [
|
||||
...(Object.keys(langmap).filter(tag => tag.indexOf('-') < 0)),
|
||||
'zh-Hans',
|
||||
'zh-Hant',
|
||||
];
|
||||
|
|
|
@ -529,6 +529,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||
where: 'device',
|
||||
default: { type: 'syuilo/bubble2', volume: 1 } as SoundStore,
|
||||
},
|
||||
recentlyUsedPostLanguages: {
|
||||
where: 'account',
|
||||
default: [] as string[],
|
||||
},
|
||||
}));
|
||||
|
||||
// TODO: 他のタブと永続化されたstateを同期
|
||||
|
|
|
@ -2278,6 +2278,9 @@ type IWebhooksShowResponse = operations['i/webhooks/show']['responses']['200']['
|
|||
// @public (undocumented)
|
||||
type IWebhooksUpdateRequest = operations['i/webhooks/update']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
export const languages: readonly ["ach", "ady", "af", "ak", "ar", "az", "bg", "bn", "br", "ca", "cak", "cs", "cy", "da", "de", "dsb", "el", "en", "eo", "es", "et", "eu", "fa", "ff", "fi", "fo", "fr", "ga", "gd", "gl", "gv", "he", "hi", "hr", "hsb", "ht", "hu", "hy", "id", "is", "it", "ja", "km", "kl", "kab", "kn", "ko", "kw", "la", "lb", "lt", "lv", "mai", "mk", "ml", "mr", "ms", "mt", "my", "no", "nb", "ne", "nl", "oc", "pa", "pl", "pt", "ro", "ru", "sh", "sk", "sl", "sq", "sr", "su", "sv", "sw", "ta", "te", "tg", "th", "fil", "tlh", "tr", "uk", "ur", "uz", "vi", "yi", "zh"];
|
||||
|
||||
// @public (undocumented)
|
||||
type MeDetailed = components['schemas']['MeDetailed'];
|
||||
|
||||
|
|
|
@ -4061,6 +4061,8 @@ export type components = {
|
|||
/** Format: date-time */
|
||||
deletedAt?: string | null;
|
||||
text: string | null;
|
||||
/** @enum {string|null} */
|
||||
lang: 'ach' | 'ady' | 'af' | 'ak' | 'ar' | 'ay' | 'az' | 'be' | 'bg' | 'bn' | 'br' | 'bs' | 'ca' | 'cak' | 'chr' | 'cs' | 'cy' | 'da' | 'de' | 'dsb' | 'el' | 'en' | 'eo' | 'es' | 'et' | 'eu' | 'fa' | 'ff' | 'fi' | 'fo' | 'fr' | 'fy' | 'ga' | 'gd' | 'gl' | 'gn' | 'gu' | 'gv' | 'grc' | 'he' | 'hi' | 'hr' | 'hsb' | 'ht' | 'hu' | 'hy' | 'id' | 'is' | 'it' | 'ja' | 'jv' | 'ka' | 'kk' | 'km' | 'kl' | 'kab' | 'kn' | 'ko' | 'ku' | 'kw' | 'la' | 'lb' | 'li' | 'lt' | 'lv' | 'mai' | 'mg' | 'mk' | 'ml' | 'mn' | 'mr' | 'ms' | 'mt' | 'my' | 'nan' | 'no' | 'nb' | 'ne' | 'nl' | 'nn' | 'oc' | 'or' | 'pa' | 'pl' | 'ps' | 'pt' | 'qu' | 'rm' | 'ro' | 'ru' | 'sa' | 'se' | 'sh' | 'si' | 'sk' | 'sl' | 'so' | 'sq' | 'sr' | 'su' | 'sv' | 'sw' | 'ta' | 'te' | 'tg' | 'th' | 'fil' | 'tlh' | 'tok' | 'tr' | 'tt' | 'uk' | 'ur' | 'uz' | 'vi' | 'xh' | 'yi' | 'yue' | 'zh' | 'zu' | 'zh-Hans' | 'zh-Hant';
|
||||
cw?: string | null;
|
||||
/** Format: id */
|
||||
userId: string;
|
||||
|
@ -19347,7 +19349,7 @@ export type operations = {
|
|||
birthday?: string | null;
|
||||
listenbrainz?: string | null;
|
||||
/** @enum {string|null} */
|
||||
lang?: null | 'ach' | 'ady' | 'af' | 'af-NA' | 'af-ZA' | 'ak' | 'ar' | 'ar-AR' | 'ar-MA' | 'ar-SA' | 'ay-BO' | 'az' | 'az-AZ' | 'be-BY' | 'bg' | 'bg-BG' | 'bn' | 'bn-IN' | 'bn-BD' | 'br' | 'bs-BA' | 'ca' | 'ca-ES' | 'cak' | 'ck-US' | 'cs' | 'cs-CZ' | 'cy' | 'cy-GB' | 'da' | 'da-DK' | 'de' | 'de-AT' | 'de-DE' | 'de-CH' | 'dsb' | 'el' | 'el-GR' | 'en' | 'en-GB' | 'en-AU' | 'en-CA' | 'en-IE' | 'en-IN' | 'en-PI' | 'en-SG' | 'en-UD' | 'en-US' | 'en-ZA' | 'en@pirate' | 'eo' | 'eo-EO' | 'es' | 'es-AR' | 'es-419' | 'es-CL' | 'es-CO' | 'es-EC' | 'es-ES' | 'es-LA' | 'es-NI' | 'es-MX' | 'es-US' | 'es-VE' | 'et' | 'et-EE' | 'eu' | 'eu-ES' | 'fa' | 'fa-IR' | 'fb-LT' | 'ff' | 'fi' | 'fi-FI' | 'fo' | 'fo-FO' | 'fr' | 'fr-CA' | 'fr-FR' | 'fr-BE' | 'fr-CH' | 'fy-NL' | 'ga' | 'ga-IE' | 'gd' | 'gl' | 'gl-ES' | 'gn-PY' | 'gu-IN' | 'gv' | 'gx-GR' | 'he' | 'he-IL' | 'hi' | 'hi-IN' | 'hr' | 'hr-HR' | 'hsb' | 'ht' | 'hu' | 'hu-HU' | 'hy' | 'hy-AM' | 'id' | 'id-ID' | 'is' | 'is-IS' | 'it' | 'it-IT' | 'ja' | 'ja-JP' | 'jv-ID' | 'ka-GE' | 'kk-KZ' | 'km' | 'kl' | 'km-KH' | 'kab' | 'kn' | 'kn-IN' | 'ko' | 'ko-KR' | 'ku-TR' | 'kw' | 'la' | 'la-VA' | 'lb' | 'li-NL' | 'lt' | 'lt-LT' | 'lv' | 'lv-LV' | 'mai' | 'mg-MG' | 'mk' | 'mk-MK' | 'ml' | 'ml-IN' | 'mn-MN' | 'mr' | 'mr-IN' | 'ms' | 'ms-MY' | 'mt' | 'mt-MT' | 'my' | 'no' | 'nb' | 'nb-NO' | 'ne' | 'ne-NP' | 'nl' | 'nl-BE' | 'nl-NL' | 'nn-NO' | 'oc' | 'or-IN' | 'pa' | 'pa-IN' | 'pl' | 'pl-PL' | 'ps-AF' | 'pt' | 'pt-BR' | 'pt-PT' | 'qu-PE' | 'rm-CH' | 'ro' | 'ro-RO' | 'ru' | 'ru-RU' | 'sa-IN' | 'se-NO' | 'sh' | 'si-LK' | 'sk' | 'sk-SK' | 'sl' | 'sl-SI' | 'so-SO' | 'sq' | 'sq-AL' | 'sr' | 'sr-RS' | 'su' | 'sv' | 'sv-SE' | 'sw' | 'sw-KE' | 'ta' | 'ta-IN' | 'te' | 'te-IN' | 'tg' | 'tg-TJ' | 'th' | 'th-TH' | 'fil' | 'tlh' | 'tr' | 'tr-TR' | 'tt-RU' | 'uk' | 'uk-UA' | 'ur' | 'ur-PK' | 'uz' | 'uz-UZ' | 'vi' | 'vi-VN' | 'xh-ZA' | 'yi' | 'yi-DE' | 'zh' | 'zh-Hans' | 'zh-Hant' | 'zh-CN' | 'zh-HK' | 'zh-SG' | 'zh-TW' | 'zu-ZA';
|
||||
lang?: null | 'ach' | 'ady' | 'af' | 'ak' | 'ar' | 'ay' | 'az' | 'be' | 'bg' | 'bn' | 'br' | 'bs' | 'ca' | 'cak' | 'chr' | 'cs' | 'cy' | 'da' | 'de' | 'dsb' | 'el' | 'en' | 'eo' | 'es' | 'et' | 'eu' | 'fa' | 'ff' | 'fi' | 'fo' | 'fr' | 'fy' | 'ga' | 'gd' | 'gl' | 'gn' | 'gu' | 'gv' | 'grc' | 'he' | 'hi' | 'hr' | 'hsb' | 'ht' | 'hu' | 'hy' | 'id' | 'is' | 'it' | 'ja' | 'jv' | 'ka' | 'kk' | 'km' | 'kl' | 'kab' | 'kn' | 'ko' | 'ku' | 'kw' | 'la' | 'lb' | 'li' | 'lt' | 'lv' | 'mai' | 'mg' | 'mk' | 'ml' | 'mn' | 'mr' | 'ms' | 'mt' | 'my' | 'nan' | 'no' | 'nb' | 'ne' | 'nl' | 'nn' | 'oc' | 'or' | 'pa' | 'pl' | 'ps' | 'pt' | 'qu' | 'rm' | 'ro' | 'ru' | 'sa' | 'se' | 'sh' | 'si' | 'sk' | 'sl' | 'so' | 'sq' | 'sr' | 'su' | 'sv' | 'sw' | 'ta' | 'te' | 'tg' | 'th' | 'fil' | 'tlh' | 'tok' | 'tr' | 'tt' | 'uk' | 'ur' | 'uz' | 'vi' | 'xh' | 'yi' | 'yue' | 'zh' | 'zu' | 'zh-Hans' | 'zh-Hant';
|
||||
/** Format: misskey:id */
|
||||
avatarId?: string | null;
|
||||
avatarDecorations?: ({
|
||||
|
@ -21002,6 +21004,8 @@ export type operations = {
|
|||
*/
|
||||
visibility?: 'public' | 'home' | 'followers' | 'specified';
|
||||
visibleUserIds?: string[];
|
||||
/** @enum {string|null} */
|
||||
lang?: 'ach' | 'ady' | 'af' | 'ak' | 'ar' | 'ay' | 'az' | 'be' | 'bg' | 'bn' | 'br' | 'bs' | 'ca' | 'cak' | 'chr' | 'cs' | 'cy' | 'da' | 'de' | 'dsb' | 'el' | 'en' | 'eo' | 'es' | 'et' | 'eu' | 'fa' | 'ff' | 'fi' | 'fo' | 'fr' | 'fy' | 'ga' | 'gd' | 'gl' | 'gn' | 'gu' | 'gv' | 'grc' | 'he' | 'hi' | 'hr' | 'hsb' | 'ht' | 'hu' | 'hy' | 'id' | 'is' | 'it' | 'ja' | 'jv' | 'ka' | 'kk' | 'km' | 'kl' | 'kab' | 'kn' | 'ko' | 'ku' | 'kw' | 'la' | 'lb' | 'li' | 'lt' | 'lv' | 'mai' | 'mg' | 'mk' | 'ml' | 'mn' | 'mr' | 'ms' | 'mt' | 'my' | 'nan' | 'no' | 'nb' | 'ne' | 'nl' | 'nn' | 'oc' | 'or' | 'pa' | 'pl' | 'ps' | 'pt' | 'qu' | 'rm' | 'ro' | 'ru' | 'sa' | 'se' | 'sh' | 'si' | 'sk' | 'sl' | 'so' | 'sq' | 'sr' | 'su' | 'sv' | 'sw' | 'ta' | 'te' | 'tg' | 'th' | 'fil' | 'tlh' | 'tok' | 'tr' | 'tt' | 'uk' | 'ur' | 'uz' | 'vi' | 'xh' | 'yi' | 'yue' | 'zh' | 'zu' | 'zh-Hans' | 'zh-Hant';
|
||||
cw?: string | null;
|
||||
/** @default false */
|
||||
localOnly?: boolean;
|
||||
|
@ -22759,6 +22763,8 @@ export type operations = {
|
|||
*/
|
||||
visibility?: 'public' | 'home' | 'followers' | 'specified';
|
||||
visibleUserIds?: string[];
|
||||
/** @enum {string|null} */
|
||||
lang?: 'ach' | 'ady' | 'af' | 'ak' | 'ar' | 'ay' | 'az' | 'be' | 'bg' | 'bn' | 'br' | 'bs' | 'ca' | 'cak' | 'chr' | 'cs' | 'cy' | 'da' | 'de' | 'dsb' | 'el' | 'en' | 'eo' | 'es' | 'et' | 'eu' | 'fa' | 'ff' | 'fi' | 'fo' | 'fr' | 'fy' | 'ga' | 'gd' | 'gl' | 'gn' | 'gu' | 'gv' | 'grc' | 'he' | 'hi' | 'hr' | 'hsb' | 'ht' | 'hu' | 'hy' | 'id' | 'is' | 'it' | 'ja' | 'jv' | 'ka' | 'kk' | 'km' | 'kl' | 'kab' | 'kn' | 'ko' | 'ku' | 'kw' | 'la' | 'lb' | 'li' | 'lt' | 'lv' | 'mai' | 'mg' | 'mk' | 'ml' | 'mn' | 'mr' | 'ms' | 'mt' | 'my' | 'nan' | 'no' | 'nb' | 'ne' | 'nl' | 'nn' | 'oc' | 'or' | 'pa' | 'pl' | 'ps' | 'pt' | 'qu' | 'rm' | 'ro' | 'ru' | 'sa' | 'se' | 'sh' | 'si' | 'sk' | 'sl' | 'so' | 'sq' | 'sr' | 'su' | 'sv' | 'sw' | 'ta' | 'te' | 'tg' | 'th' | 'fil' | 'tlh' | 'tok' | 'tr' | 'tt' | 'uk' | 'ur' | 'uz' | 'vi' | 'xh' | 'yi' | 'yue' | 'zh' | 'zu' | 'zh-Hans' | 'zh-Hant';
|
||||
cw?: string | null;
|
||||
/** @default false */
|
||||
localOnly?: boolean;
|
||||
|
|
|
@ -338,3 +338,96 @@ export type ModerationLogPayloads = {
|
|||
fileId: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const languages = [
|
||||
'ach',
|
||||
'ady',
|
||||
'af',
|
||||
'ak',
|
||||
'ar',
|
||||
'az',
|
||||
'bg',
|
||||
'bn',
|
||||
'br',
|
||||
'ca',
|
||||
'cak',
|
||||
'cs',
|
||||
'cy',
|
||||
'da',
|
||||
'de',
|
||||
'dsb',
|
||||
'el',
|
||||
'en',
|
||||
'eo',
|
||||
'es',
|
||||
'et',
|
||||
'eu',
|
||||
'fa',
|
||||
'ff',
|
||||
'fi',
|
||||
'fo',
|
||||
'fr',
|
||||
'ga',
|
||||
'gd',
|
||||
'gl',
|
||||
'gv',
|
||||
'he',
|
||||
'hi',
|
||||
'hr',
|
||||
'hsb',
|
||||
'ht',
|
||||
'hu',
|
||||
'hy',
|
||||
'id',
|
||||
'is',
|
||||
'it',
|
||||
'ja',
|
||||
'km',
|
||||
'kl',
|
||||
'kab',
|
||||
'kn',
|
||||
'ko',
|
||||
'kw',
|
||||
'la',
|
||||
'lb',
|
||||
'lt',
|
||||
'lv',
|
||||
'mai',
|
||||
'mk',
|
||||
'ml',
|
||||
'mr',
|
||||
'ms',
|
||||
'mt',
|
||||
'my',
|
||||
'no',
|
||||
'nb',
|
||||
'ne',
|
||||
'nl',
|
||||
'oc',
|
||||
'pa',
|
||||
'pl',
|
||||
'pt',
|
||||
'ro',
|
||||
'ru',
|
||||
'sh',
|
||||
'sk',
|
||||
'sl',
|
||||
'sq',
|
||||
'sr',
|
||||
'su',
|
||||
'sv',
|
||||
'sw',
|
||||
'ta',
|
||||
'te',
|
||||
'tg',
|
||||
'th',
|
||||
'fil',
|
||||
'tlh',
|
||||
'tr',
|
||||
'uk',
|
||||
'ur',
|
||||
'uz',
|
||||
'vi',
|
||||
'yi',
|
||||
'zh',
|
||||
] as const;
|
||||
|
|
|
@ -19,6 +19,7 @@ export const mutedNoteReasons = consts.mutedNoteReasons;
|
|||
export const followingVisibilities = consts.followingVisibilities;
|
||||
export const followersVisibilities = consts.followersVisibilities;
|
||||
export const moderationLogTypes = consts.moderationLogTypes;
|
||||
export const languages = consts.languages;
|
||||
|
||||
// api extractor not supported yet
|
||||
//export * as api from './api.js';
|
||||
|
|
Loading…
Reference in a new issue