Compare commits

...

21 commits

Author SHA1 Message Date
Essem 029be44897 merge: feat: Add language metadata to notes (!401)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/401

Closes #253
2024-04-07 15:10:05 +00:00
dakkar 0690b9a429 merge: fix: load libopenmpt on demand (!469)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/469

Approved-by: dakkar <dakkar@thenautilus.net>
Approved-by: Marie <marie@kaifa.ch>
2024-04-07 14:56:16 +00:00
Alina Sireneva ecfaf7ff7a chore: added license and patch info 2024-03-14 21:39:34 +03:00
Alina Sireneva a69315a24b fix: added wasm in vite config 2024-03-14 14:41:24 +03:00
Alina Sireneva d991eccd3f fix: Promise.resolve 2024-03-11 16:42:10 +03:00
Alina Sireneva 0085305579 fix: load libopenmpt on demand 2024-03-11 15:32:59 +03:00
Essem 184707e048 Merge branch 'develop' into 'feat/note-lang'
# Conflicts:
#   locales/en-US.yml
#   packages/backend/src/core/activitypub/models/ApNoteService.ts
#   packages/frontend/src/components/MkPostForm.vue
2024-03-02 19:50:43 +00:00
Essem 8ccb89fb42
Revert langmap change in i/update 2024-02-25 22:13:39 -06:00
Essem 6998d4f6d2
Revert languages in profile settings 2024-02-25 21:59:14 -06:00
Essem 5bd2272afa
Fix oddity 2024-02-25 16:14:05 -06:00
Essem 03876616d2
feat: Add lang parameter to post elements 2024-02-25 16:07:55 -06:00
Essem 8860e6866b
refactor: Simplify some parts of the post form language handling 2024-02-25 16:07:55 -06:00
Essem d534a1cded
refactor: Revert langmap 2024-02-25 16:07:55 -06:00
Essem b427b1ae73
fix: Use proper source text for note updates 2024-02-25 16:07:55 -06:00
Essem f1be178b52
refactor: Smarter contentMap checking 2024-02-25 16:07:54 -06:00
Essem 212aaab5b7
fix: Add some more regional language variants to langmap 2024-02-25 16:07:14 -06:00
Essem 8a416cd302
feat: Add langPref config option 2024-02-25 16:07:14 -06:00
Essem 4f45e72799
fix: Do not convert langs to lowercase 2024-02-25 16:06:49 -06:00
Essem 112272c254
fix: Do not discard region tag in post form unless necessary 2024-02-25 16:05:28 -06:00
Essem 91ba7a45c6
fix: Add ja-JP locale for noLanguage 2024-02-25 16:05:28 -06:00
Essem a76d3cf861
feat: Add language metadata to notes 2024-02-25 16:04:53 -06:00
45 changed files with 670 additions and 80 deletions

View file

@ -106,7 +106,7 @@ redis:
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
# You can set scope to local (default value) or global
# You can set scope to local (default value) or global
# (include notes from remote).
#meilisearch:
@ -204,7 +204,7 @@ signToActivityPubGet: true
checkActivityPubGetSignature: false
# For security reasons, uploading attachments from the intranet is prohibited,
# but exceptions can be made from the following settings. Default value is "undefined".
# but exceptions can be made from the following settings. Default value is "undefined".
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
#allowedPrivateNetworks: [
# '127.0.0.1/32'
@ -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

View file

@ -163,7 +163,7 @@ redis:
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
# You can set scope to local (default value) or global
# You can set scope to local (default value) or global
# (include notes from remote).
#meilisearch:
@ -261,7 +261,7 @@ signToActivityPubGet: true
checkActivityPubGetSignature: false
# For security reasons, uploading attachments from the intranet is prohibited,
# but exceptions can be made from the following settings. Default value is "undefined".
# but exceptions can be made from the following settings. Default value is "undefined".
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
#allowedPrivateNetworks: [
# '127.0.0.1/32'
@ -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

View file

@ -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

View file

@ -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
View file

@ -5093,6 +5093,10 @@ export interface Locale extends ILocale {
*
*/
"enableHorizontalSwipe": string;
/**
*
*/
"noLanguage": string;
/**
*
*/

View file

@ -1269,6 +1269,7 @@ hemisphere: "お住まいの地域"
withSensitive: "センシティブなファイルを含むノートを表示"
userSaysSomethingSensitive: "{name}のセンシティブなファイルを含む投稿"
enableHorizontalSwipe: "スワイプしてタブを切り替える"
noLanguage: "言語なし"
loading: "読み込み中"
surrender: "やめる"
gameRetry: "リトライ"

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

View file

@ -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,

View file

@ -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)),

View file

@ -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)),

View file

@ -285,7 +285,7 @@ export class ApRendererService {
if (instance && instance.softwareName === 'pleroma') isMastodon = true;
}
}
const object: ILike = {
type: 'Like',
id: `${this.config.url}/likes/${noteReaction.id}`,
@ -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,

View file

@ -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 };
}
}

View file

@ -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;

View file

@ -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 {
@ -120,7 +120,7 @@ export class NoteEntityService implements OnModuleInit {
followerId: meId,
},
});
hide = !isFollowing;
} else {
// フォロワーかどうか
@ -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_),
} : {}),

View file

@ -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',
];

View file

@ -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,
})

View file

@ -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;
}

View file

@ -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,

View file

@ -382,7 +382,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
updates.backgroundUrl = null;
updates.backgroundBlurhash = null;
}
if (ps.avatarDecorations) {
const decorations = await this.avatarDecorationService.getAll(true);
const [myRoles, myPolicies] = await Promise.all([this.roleService.getUserRoles(user.id), this.roleService.getUserPolicies(user.id)]);

View file

@ -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,

View file

@ -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,

View file

@ -43,7 +43,6 @@ html
link(rel='stylesheet' href='/assets/phosphor-icons/bold/style.css')
link(rel='stylesheet' href='/static-assets/fonts/sharkey-icons/style.css')
link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
script(src='/client-assets/libopenmpt.js')
if !config.clientManifestExists
script(type="module" src="/vite/@vite/client")
@ -73,7 +72,6 @@ html
script.
var VERSION = "#{version}";
var CLIENT_ENTRY = "#{clientEntry.file}";
window.libopenmpt = window.Module;
script(type='application/json' id='misskey_meta' data-generated-at=now)
!= metaJson

File diff suppressed because one or more lines are too long

View file

@ -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 FunctionLint
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();

View file

@ -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();

View file

@ -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();

View file

@ -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,
},
@ -745,7 +813,7 @@ async function post(ev?: MouseEvent) {
visibility.value = 'home';
}
}
if (defaultStore.state.warnMissingAltText) {
const filesData = toRaw(files.value);
@ -765,7 +833,7 @@ async function post(ev?: MouseEvent) {
});
if (canceled) return;
if (result === 'cancel') return;
if (result === 'cancel') return;
}
}
@ -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;
}

View file

@ -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<{

View file

@ -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, []);

View file

@ -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 FunctionLint
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();

View file

@ -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();

View file

@ -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();

View file

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

View file

@ -1,9 +1,12 @@
/* global libopenmpt UTF8ToString writeAsciiToMemory */
// @ts-nocheck
/* eslint-disable */
const ChiptuneAudioContext = window.AudioContext || window.webkitAudioContext;
export function ChiptuneJsConfig (repeatCount: number, context: AudioContext) {
let libopenmpt
let libopenmptLoadPromise
export function ChiptuneJsConfig (repeatCount?: number, context?: AudioContext) {
this.repeatCount = repeatCount;
this.context = context;
}
@ -20,6 +23,28 @@ export function ChiptuneJsPlayer (config: object) {
this.volume = 1;
}
ChiptuneJsPlayer.prototype.initialize = function() {
if (libopenmptLoadPromise) return libopenmptLoadPromise;
if (libopenmpt) return Promise.resolve();
libopenmptLoadPromise = new Promise(async (resolve, reject) => {
try {
const { Module } = await import('./libopenmpt/libopenmpt.js');
await new Promise((resolve) => {
Module['onRuntimeInitialized'] = resolve;
})
libopenmpt = Module;
resolve()
} catch (e) {
reject(e)
} finally {
libopenmptLoadPromise = undefined;
}
})
return libopenmptLoadPromise;
}
ChiptuneJsPlayer.prototype.constructor = ChiptuneJsPlayer;
ChiptuneJsPlayer.prototype.fireEvent = function (eventName: string, response) {
@ -61,12 +86,12 @@ ChiptuneJsPlayer.prototype.seek = function (position: number) {
ChiptuneJsPlayer.prototype.metadata = function () {
const data = {};
const keys = UTF8ToString(libopenmpt._openmpt_module_get_metadata_keys(this.currentPlayingNode.modulePtr)).split(';');
const keys = libopenmpt.UTF8ToString(libopenmpt._openmpt_module_get_metadata_keys(this.currentPlayingNode.modulePtr)).split(';');
let keyNameBuffer = 0;
for (const key of keys) {
keyNameBuffer = libopenmpt._malloc(key.length + 1);
writeAsciiToMemory(key, keyNameBuffer);
data[key] = UTF8ToString(libopenmpt._openmpt_module_get_metadata(this.currentPlayingNode.modulePtr, keyNameBuffer));
libopenmpt.writeAsciiToMemory(key, keyNameBuffer);
data[key] = libopenmpt.UTF8ToString(libopenmpt._openmpt_module_get_metadata(this.currentPlayingNode.modulePtr, keyNameBuffer));
libopenmpt._free(keyNameBuffer);
}
return data;
@ -84,7 +109,7 @@ ChiptuneJsPlayer.prototype.unlock = function () {
};
ChiptuneJsPlayer.prototype.load = function (input) {
return new Promise((resolve, reject) => {
return this.initialize().then(() => new Promise((resolve, reject) => {
if(this.touchLocked) {
this.unlock();
}
@ -106,7 +131,7 @@ ChiptuneJsPlayer.prototype.load = function (input) {
reject(error);
});
}
});
}));
};
ChiptuneJsPlayer.prototype.play = function (buffer: ArrayBuffer) {
@ -180,7 +205,7 @@ ChiptuneJsPlayer.prototype.getPatternNumRows = function (pattern: number) {
ChiptuneJsPlayer.prototype.getPatternRowChannel = function (pattern: number, row: number, channel: number) {
if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) {
return UTF8ToString(libopenmpt._openmpt_module_format_pattern_row_channel(this.currentPlayingNode.modulePtr, pattern, row, channel, 0, true));
return libopenmpt.UTF8ToString(libopenmpt._openmpt_module_format_pattern_row_channel(this.currentPlayingNode.modulePtr, pattern, row, channel, 0, true));
}
return '';
};

View file

@ -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',
];

View file

@ -0,0 +1,25 @@
Copyright (c) 2004-2024, OpenMPT Project Developers and Contributors
Copyright (c) 1997-2003, Olivier Lapicque
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the OpenMPT project nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,23 @@
modifications made to `libopenmpt.js` (can be taken from https://lib.openmpt.org/libopenmpt/download/):
at the beginning of the file:
```js
// @ts-nocheck
/* eslint-disable */
```
at the end of the file:
```js
Module.UTF8ToString = UTF8ToString;
Module.writeAsciiToMemory = writeAsciiToMemory;
export { Module }
```
replace
```
wasmBinaryFile="libopenmpt.wasm"
```
with
```
wasmBinaryFile=new URL("./libopenmpt.wasm", import.meta.url).href
```

View file

@ -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を同期

View file

@ -8,7 +8,7 @@ import meta from '../../package.json';
import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name.js';
import pluginJson5 from './vite.json5.js';
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue'];
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue', '.wasm'];
const hash = (str: string, seed = 0): number => {
let h1 = 0xdeadbeef ^ seed,

View file

@ -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'];

View file

@ -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;

View file

@ -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;

View file

@ -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';