Merge branch 'develop' into future-2024-04-25-post
This commit is contained in:
commit
30bd7768d6
70 changed files with 305 additions and 192 deletions
|
@ -21,10 +21,11 @@
|
|||
"@github/webauthn-json": "2.1.1",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
||||
"@phosphor-icons/web": "^2.0.3",
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-replace": "5.0.5",
|
||||
"@rollup/pluginutils": "5.1.0",
|
||||
"@transfem-org/sfm-js": "0.24.4",
|
||||
"@transfem-org/sfm-js": "0.24.5",
|
||||
"@syuilo/aiscript": "0.18.0",
|
||||
"@phosphor-icons/web": "^2.0.3",
|
||||
"@twemoji/parser": "15.0.0",
|
||||
|
|
|
@ -238,7 +238,7 @@ function exec() {
|
|||
return;
|
||||
}
|
||||
|
||||
emojis.value = searchEmoji(props.q.toLowerCase(), emojiDb.value);
|
||||
emojis.value = searchEmoji(props.q.normalize('NFC').toLowerCase(), emojiDb.value);
|
||||
} else if (props.type === 'mfmTag') {
|
||||
if (!props.q || props.q === '') {
|
||||
mfmTags.value = MFM_TAGS;
|
||||
|
|
|
@ -205,7 +205,7 @@ watch(q, () => {
|
|||
return;
|
||||
}
|
||||
|
||||
const newQ = q.value.replace(/:/g, '').toLowerCase();
|
||||
const newQ = q.value.replace(/:/g, '').normalize('NFC').toLowerCase();
|
||||
|
||||
const searchCustom = () => {
|
||||
const max = 100;
|
||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<MkPagination v-slot="{items}" :pagination="pagination" class="urempief" :class="{ grid: viewMode === 'grid' }">
|
||||
<MkPagination v-slot="{items}" :pagination="pagination" :displayLimit="50" class="urempief" :class="{ grid: viewMode === 'grid' }">
|
||||
<MkA
|
||||
v-for="file in (items as Misskey.entities.DriveFile[])"
|
||||
:key="file.id"
|
||||
|
|
|
@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #header>
|
||||
<template v-if="pageMetadata">
|
||||
<i v-if="pageMetadata.icon" :class="pageMetadata.icon" style="margin-right: 0.5em;"></i>
|
||||
<span>{{ pageMetadata.title }}</span>
|
||||
<span><MkUserName v-if="pageMetadata.userName?.name" :user="pageMetadata.userName" />{{ pageMetadata.title }}</span>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
|
@ -43,6 +43,7 @@ import { claimAchievement } from '@/scripts/achievements.js';
|
|||
import { getScrollContainer } from '@/scripts/scroll.js';
|
||||
import { useRouterFactory } from '@/router/supplier.js';
|
||||
import { mainRouter } from '@/router/main.js';
|
||||
import MkUserName from './global/MkUserName.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
initialPath: string;
|
||||
|
|
|
@ -395,10 +395,10 @@ const prepend = (item: MisskeyEntity): void => {
|
|||
* @param newItems 新しいアイテムの配列
|
||||
*/
|
||||
function unshiftItems(newItems: MisskeyEntity[]) {
|
||||
const length = newItems.length + items.value.size;
|
||||
items.value = new Map([...arrayToEntries(newItems), ...items.value].slice(0, props.displayLimit));
|
||||
|
||||
if (length >= props.displayLimit) more.value = true;
|
||||
const prevLength = items.value.size;
|
||||
items.value = new Map([...arrayToEntries(newItems), ...items.value].slice(0, newItems.length + props.displayLimit));
|
||||
// if we truncated, mark that there are more values to fetch
|
||||
if (items.value.size < prevLength) more.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -406,10 +406,10 @@ function unshiftItems(newItems: MisskeyEntity[]) {
|
|||
* @param oldItems 古いアイテムの配列
|
||||
*/
|
||||
function concatItems(oldItems: MisskeyEntity[]) {
|
||||
const length = oldItems.length + items.value.size;
|
||||
items.value = new Map([...items.value, ...arrayToEntries(oldItems)].slice(0, props.displayLimit));
|
||||
|
||||
if (length >= props.displayLimit) more.value = true;
|
||||
const prevLength = items.value.size;
|
||||
items.value = new Map([...items.value, ...arrayToEntries(oldItems)].slice(0, oldItems.length + props.displayLimit));
|
||||
// if we truncated, mark that there are more values to fetch
|
||||
if (items.value.size < prevLength) more.value = true;
|
||||
}
|
||||
|
||||
function executeQueue() {
|
||||
|
@ -418,7 +418,7 @@ function executeQueue() {
|
|||
}
|
||||
|
||||
function prependQueue(newItem: MisskeyEntity) {
|
||||
queue.value = new Map([[newItem.id, newItem], ...queue.value].slice(0, props.displayLimit) as [string, MisskeyEntity][]);
|
||||
queue.value = new Map([[newItem.id, newItem], ...queue.value] as [string, MisskeyEntity][]);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
import { computed, ref } from 'vue';
|
||||
import { instance } from '@/instance.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import sanitizeHtml from '@/scripts/sanitize-html.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
|
|
|
@ -16,9 +16,20 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
<div v-else-if="phase === 'howToReact'" class="_gaps">
|
||||
<div style="text-align: center; padding: 0 16px;">{{ i18n.ts._initialTutorial._reaction.description }}</div>
|
||||
<div>{{ i18n.ts._initialTutorial._reaction.letsTryReacting }}</div>
|
||||
<I18n :src="i18n.ts._initialTutorial._reaction.letsTryReacting" tag="div">
|
||||
<template #reaction>
|
||||
<i class="ph-smiley ph-bold ph-lg"></i>
|
||||
</template>
|
||||
</I18n>
|
||||
<MkNote :class="$style.exampleNoteRoot" :note="exampleNote" :mock="true" @reaction="addReaction" @removeReaction="removeReaction"/>
|
||||
<div v-if="onceReacted"><b style="color: var(--accent);"><i class="ph-check ph-bold ph-lg"></i> {{ i18n.ts._initialTutorial.wellDone }}</b> {{ i18n.ts._initialTutorial._reaction.reactNotification }}<br>{{ i18n.ts._initialTutorial._reaction.reactDone }}</div>
|
||||
<div v-if="onceReacted">
|
||||
<b style="color: var(--accent);"><i class="ph-check ph-bold ph-lg"></i> {{ i18n.ts._initialTutorial.wellDone }}</b> {{ i18n.ts._initialTutorial._reaction.reactNotification }}<br>
|
||||
<I18n :src="i18n.ts._initialTutorial._reaction.reactDone">
|
||||
<template #undo>
|
||||
<i class="ph-minus ph-bold ph-lg"></i>
|
||||
</template>
|
||||
</I18n>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<MkPagination :pagination="pagination">
|
||||
<MkPagination :pagination="pagination" :displayLimit="50">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
|
|
|
@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import sanitizeHtml from '@/scripts/sanitize-html.js';
|
||||
import XSigninDialog from '@/components/MkSigninDialog.vue';
|
||||
import XSignupDialog from '@/components/MkSignupDialog.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
|
|
|
@ -85,6 +85,7 @@ const errored = ref(url.value == null);
|
|||
|
||||
function onClick(ev: MouseEvent) {
|
||||
if (props.menu) {
|
||||
ev.stopPropagation();
|
||||
os.popupMenu([{
|
||||
type: 'label',
|
||||
text: `:${props.name}:`,
|
||||
|
|
|
@ -4,8 +4,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<img v-if="!useOsNativeEmojis" :class="$style.root" :src="url" :alt="props.emoji" decoding="async" @pointerenter="computeTitle" @click="onClick" v-on:click.stop/>
|
||||
<span v-else :alt="props.emoji" @pointerenter="computeTitle" @click="onClick" v-on:click.stop>{{ colorizedNativeEmoji }}</span>
|
||||
<img v-if="!useOsNativeEmojis" :class="$style.root" :src="url" :alt="props.emoji" decoding="async" @pointerenter="computeTitle" @click="onClick"/>
|
||||
<span v-else :alt="props.emoji" @pointerenter="computeTitle" @click="onClick">{{ colorizedNativeEmoji }}</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -39,6 +39,7 @@ function computeTitle(event: PointerEvent): void {
|
|||
|
||||
function onClick(ev: MouseEvent) {
|
||||
if (props.menu) {
|
||||
ev.stopPropagation();
|
||||
os.popupMenu([{
|
||||
type: 'label',
|
||||
text: props.emoji,
|
||||
|
|
|
@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</FormSplit>
|
||||
</div>
|
||||
|
||||
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
|
||||
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination" :displayLimit="50">
|
||||
<div :class="$style.items">
|
||||
<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" :class="$style.item" :to="`/instance-info/${instance.host}`">
|
||||
<MkInstanceCardMini :instance="instance"/>
|
||||
|
|
|
@ -130,7 +130,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import sanitizeHtml from '@/scripts/sanitize-html.js';
|
||||
import { computed, watch, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import XEmojis from './about.emojis.vue';
|
||||
|
|
|
@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
-->
|
||||
|
||||
<MkPagination v-slot="{items}" ref="reports" :pagination="pagination" style="margin-top: var(--margin);">
|
||||
<MkPagination v-slot="{items}" ref="reports" :pagination="pagination" :displayLimit="50" style="margin-top: var(--margin);">
|
||||
<XAbuseReport v-for="report in items" :key="report.id" :report="report" @resolved="resolved"/>
|
||||
</MkPagination>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :contentMax="900">
|
||||
<div class="_gaps_m">
|
||||
<MkPagination ref="paginationComponent" :pagination="pagination">
|
||||
<MkPagination ref="paginationComponent" :pagination="pagination" :displayLimit="50">
|
||||
<template #default="{ items }">
|
||||
<div class="_gaps_s">
|
||||
<SkApprovalUser v-for="item in items" :key="item.id" :user="(item as any)" :onDeleted="deleted"/>
|
||||
|
|
|
@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</FormSplit>
|
||||
</div>
|
||||
|
||||
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
|
||||
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination" :displayLimit="50">
|
||||
<div :class="$style.instances">
|
||||
<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" :class="$style.instance" :to="`/instance-info/${instance.host}`">
|
||||
<MkInstanceCardMini :instance="instance"/>
|
||||
|
|
|
@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<option value="-usedAt">{{ i18n.ts.usedAt }} ({{ i18n.ts.descendingOrder }})</option>
|
||||
</MkSelect>
|
||||
</div>
|
||||
<MkPagination ref="pagingComponent" :pagination="pagination">
|
||||
<MkPagination ref="pagingComponent" :pagination="pagination" :displayLimit="50">
|
||||
<template #default="{ items }">
|
||||
<div class="_gaps_s">
|
||||
<MkInviteCode v-for="item in items" :key="item.id" :invite="(item as any)" :onDeleted="deleted" moderator/>
|
||||
|
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkInput>
|
||||
</div>
|
||||
|
||||
<MkPagination v-slot="{items}" ref="logs" :pagination="pagination" style="margin-top: var(--margin);">
|
||||
<MkPagination v-slot="{items}" ref="logs" :pagination="pagination" :displayLimit="50" style="margin-top: var(--margin);">
|
||||
<div class="_gaps_s">
|
||||
<XModLog v-for="item in items" :key="item.id" :log="item"/>
|
||||
</div>
|
||||
|
|
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div class="_gaps">
|
||||
<MkButton primary rounded @click="assign"><i class="ph-plus ph-bold ph-lg"></i> {{ i18n.ts.assign }}</MkButton>
|
||||
|
||||
<MkPagination :pagination="usersPagination">
|
||||
<MkPagination :pagination="usersPagination" :displayLimit="50">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
|
|
|
@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkInput>
|
||||
</div>
|
||||
|
||||
<MkPagination v-slot="{items}" ref="paginationComponent" :pagination="pagination">
|
||||
<MkPagination v-slot="{items}" ref="paginationComponent" :pagination="pagination" :displayLimit="50">
|
||||
<div :class="$style.users">
|
||||
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" :class="$style.user" :to="`/admin/user/${user.id}`">
|
||||
<MkUserCardMini :user="user"/>
|
||||
|
|
|
@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkButton inline @click="setLicenseBulk">Set License</MkButton>
|
||||
<MkButton inline danger @click="delBulk">Delete</MkButton>
|
||||
</div>
|
||||
<MkPagination ref="emojisPaginationComponent" :pagination="pagination">
|
||||
<MkPagination ref="emojisPaginationComponent" :pagination="pagination" :displayLimit="50">
|
||||
<template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template>
|
||||
<template #default="{items}">
|
||||
<div class="ldhfsamy">
|
||||
|
@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #label>{{ i18n.ts.host }}</template>
|
||||
</MkInput>
|
||||
</FormSplit>
|
||||
<MkPagination :pagination="remotePagination">
|
||||
<MkPagination :pagination="remotePagination" :displayLimit="50">
|
||||
<template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template>
|
||||
<template #default="{items}">
|
||||
<div class="ldhfsamy">
|
||||
|
@ -352,6 +352,7 @@ definePageMetadata(() => ({
|
|||
> .img {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
> .body {
|
||||
|
@ -398,6 +399,7 @@ definePageMetadata(() => ({
|
|||
> .img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
> .body {
|
||||
|
|
|
@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</div>
|
||||
<MkButton rounded style="margin: 0 auto;" @click="changeImage">{{ i18n.ts.selectFile }}</MkButton>
|
||||
<MkInput v-model="name" pattern="[a-z0-9_]" autocapitalize="off">
|
||||
<MkInput v-model="name" autocapitalize="off">
|
||||
<template #label>{{ i18n.ts.name }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="category" :datalist="customEmojiCategories">
|
||||
|
|
|
@ -7,11 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div class="_gaps_m">
|
||||
<MkSelect v-model="type">
|
||||
<option value="all">{{ i18n.ts.all }}</option>
|
||||
<option value="following" v-if="hasSender">{{ i18n.ts.following }}</option>
|
||||
<option value="follower" v-if="hasSender">{{ i18n.ts.followers }}</option>
|
||||
<option value="mutualFollow" v-if="hasSender">{{ i18n.ts.mutualFollow }}</option>
|
||||
<option value="followingOrFollower" v-if="hasSender">{{ i18n.ts.followingOrFollower }}</option>
|
||||
<option value="list" v-if="hasSender">{{ i18n.ts.userList }}</option>
|
||||
<option v-if="hasSender" value="following">{{ i18n.ts.following }}</option>
|
||||
<option v-if="hasSender" value="follower">{{ i18n.ts.followers }}</option>
|
||||
<option v-if="hasSender" value="mutualFollow">{{ i18n.ts.mutualFollow }}</option>
|
||||
<option v-if="hasSender" value="followingOrFollower">{{ i18n.ts.followingOrFollower }}</option>
|
||||
<option v-if="hasSender" value="list">{{ i18n.ts.userList }}</option>
|
||||
<option value="never">{{ i18n.ts.none }}</option>
|
||||
</MkSelect>
|
||||
|
||||
|
|
|
@ -140,6 +140,7 @@ type Profile = {
|
|||
hot: Record<keyof typeof defaultStoreSaveKeys, unknown>;
|
||||
cold: Record<keyof typeof coldDeviceStorageSaveKeys, unknown>;
|
||||
fontSize: string | null;
|
||||
lang: string | null;
|
||||
cornerRadius: string | null;
|
||||
useSystemFont: 't' | null;
|
||||
wallpaper: string | null;
|
||||
|
@ -198,6 +199,7 @@ function getSettings(): Profile['settings'] {
|
|||
hot,
|
||||
cold,
|
||||
fontSize: miLocalStorage.getItem('fontSize'),
|
||||
lang: miLocalStorage.getItem('lang'),
|
||||
cornerRadius: miLocalStorage.getItem('cornerRadius'),
|
||||
useSystemFont: miLocalStorage.getItem('useSystemFont') as 't' | null,
|
||||
wallpaper: miLocalStorage.getItem('wallpaper'),
|
||||
|
@ -313,6 +315,13 @@ async function applyProfile(id: string): Promise<void> {
|
|||
miLocalStorage.removeItem('fontSize');
|
||||
}
|
||||
|
||||
// lang
|
||||
if (settings.lang) {
|
||||
miLocalStorage.setItem('lang', settings.lang);
|
||||
} else {
|
||||
miLocalStorage.removeItem('lang');
|
||||
}
|
||||
|
||||
// cornerRadius
|
||||
if (settings.cornerRadius) {
|
||||
miLocalStorage.setItem('cornerRadius', settings.cornerRadius);
|
||||
|
|
|
@ -130,7 +130,7 @@ definePageMetadata(() => ({
|
|||
title: i18n.ts.user,
|
||||
icon: 'ph-user ph-bold ph-lg',
|
||||
...user.value ? {
|
||||
title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
|
||||
title: user.value.name ? ` (@${user.value.username})` : `@${user.value.username}`,
|
||||
subtitle: `@${getAcct(user.value)}`,
|
||||
userName: user.value,
|
||||
avatar: user.value,
|
||||
|
|
|
@ -99,7 +99,7 @@ export class Autocomplete {
|
|||
const isHashtag = hashtagIndex !== -1;
|
||||
const isMfmParam = mfmParamIndex !== -1 && afterLastMfmParam?.includes('.') && !afterLastMfmParam?.includes(' ');
|
||||
const isMfmTag = mfmTagIndex !== -1 && !isMfmParam;
|
||||
const isEmoji = emojiIndex !== -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':');
|
||||
const isEmoji = emojiIndex !== -1 && text.split(/:[\p{Letter}\p{Number}\p{Mark}_+-]+:/u).pop()!.includes(':');
|
||||
|
||||
let opened = false;
|
||||
|
||||
|
@ -125,7 +125,7 @@ export class Autocomplete {
|
|||
if (isEmoji && !opened && this.onlyType.includes('emoji')) {
|
||||
const emoji = text.substring(emojiIndex + 1);
|
||||
if (!emoji.includes(' ')) {
|
||||
this.open('emoji', emoji);
|
||||
this.open('emoji', emoji.normalize('NFC'));
|
||||
opened = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: Array<string | string[]>): boolean {
|
||||
import type { Note, MeDetailed } from "misskey-js/entities.js";
|
||||
|
||||
export function checkWordMute(note: Note, me: MeDetailed | null | undefined, mutedWords: Array<string | string[]>): boolean {
|
||||
// 自分自身
|
||||
if (me && (note.userId === me.id)) return false;
|
||||
|
||||
if (mutedWords.length > 0) {
|
||||
const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim();
|
||||
const text = getNoteText(note);
|
||||
|
||||
if (text === '') return false;
|
||||
|
||||
|
@ -40,3 +42,25 @@ export function checkWordMute(note: Record<string, any>, me: Record<string, any>
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getNoteText(note: Note): string {
|
||||
const textParts: string[] = [];
|
||||
|
||||
if (note.cw)
|
||||
textParts.push(note.cw);
|
||||
|
||||
if (note.text)
|
||||
textParts.push(note.text);
|
||||
|
||||
if (note.files)
|
||||
for (const file of note.files)
|
||||
if (file.comment)
|
||||
textParts.push(file.comment);
|
||||
|
||||
if (note.poll)
|
||||
for (const choice of note.poll.choices)
|
||||
if (choice.text)
|
||||
textParts.push(choice.text);
|
||||
|
||||
return textParts.join('\n').trim();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// @ts-nocheck
|
||||
/* eslint-disable */
|
||||
|
||||
const ChiptuneAudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
|
@ -6,6 +5,11 @@ const ChiptuneAudioContext = window.AudioContext || window.webkitAudioContext;
|
|||
let libopenmpt
|
||||
let libopenmptLoadPromise
|
||||
|
||||
type ChiptuneJsConfig = {
|
||||
repeatCount: number | null;
|
||||
context: AudioContext | null;
|
||||
};
|
||||
|
||||
export function ChiptuneJsConfig (repeatCount?: number, context?: AudioContext) {
|
||||
this.repeatCount = repeatCount;
|
||||
this.context = context;
|
||||
|
@ -13,7 +17,7 @@ export function ChiptuneJsConfig (repeatCount?: number, context?: AudioContext)
|
|||
|
||||
ChiptuneJsConfig.prototype.constructor = ChiptuneJsConfig;
|
||||
|
||||
export function ChiptuneJsPlayer (config: object) {
|
||||
export function ChiptuneJsPlayer (config: ChiptuneJsConfig) {
|
||||
this.config = config;
|
||||
this.audioContext = config.context || new ChiptuneAudioContext();
|
||||
this.context = this.audioContext.createGain();
|
||||
|
@ -27,7 +31,7 @@ ChiptuneJsPlayer.prototype.initialize = function() {
|
|||
if (libopenmptLoadPromise) return libopenmptLoadPromise;
|
||||
if (libopenmpt) return Promise.resolve();
|
||||
|
||||
libopenmptLoadPromise = new Promise(async (resolve, reject) => {
|
||||
libopenmptLoadPromise = new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
const { Module } = await import('./libopenmpt/libopenmpt.js');
|
||||
await new Promise((resolve) => {
|
||||
|
|
|
@ -9,9 +9,9 @@ const koRegex3 = /(야(?=\?))|(야$)|(야(?= ))/gm;
|
|||
|
||||
function ifAfter(prefix, fn) {
|
||||
const preLen = prefix.length;
|
||||
const regex = new RegExp(prefix,'i');
|
||||
return (x,pos,string) => {
|
||||
return pos > 0 && string.substring(pos-preLen,pos).match(regex) ? fn(x) : x;
|
||||
const regex = new RegExp(prefix, 'i');
|
||||
return (x, pos, string) => {
|
||||
return pos > 0 && string.substring(pos - preLen, pos).match(regex) ? fn(x) : x;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ export function nyaize(text: string): string {
|
|||
.replace(/one/gi, ifAfter('every', x => x === 'ONE' ? 'NYAN' : 'nyan'))
|
||||
// ko-KR
|
||||
.replace(koRegex1, match => String.fromCharCode(
|
||||
match.charCodeAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0),
|
||||
match.charCodeAt(0) + '냐'.charCodeAt(0) - '나'.charCodeAt(0),
|
||||
))
|
||||
.replace(koRegex2, '다냥')
|
||||
.replace(koRegex3, '냥');
|
||||
|
|
18
packages/frontend/src/scripts/sanitize-html.ts
Normal file
18
packages/frontend/src/scripts/sanitize-html.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: dakkar and other Sharkey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import original from 'sanitize-html';
|
||||
|
||||
export default function sanitizeHtml(str: string | null): string | null {
|
||||
if (str == null) return str;
|
||||
return original(str, {
|
||||
allowedTags: original.defaults.allowedTags.concat(['img', 'audio', 'video', 'center', 'details', 'summary']),
|
||||
allowedAttributes: {
|
||||
...original.defaults.allowedAttributes,
|
||||
a: original.defaults.allowedAttributes.a.concat(['style']),
|
||||
img: original.defaults.allowedAttributes.img.concat(['style']),
|
||||
},
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue