Merge tag 'tags/2024.3.3'

This commit is contained in:
jaina heartles 2024-05-14 16:48:25 -07:00
commit df14b213b4
91 changed files with 432 additions and 214 deletions

File diff suppressed because one or more lines are too long

View file

@ -21,12 +21,12 @@
"@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",
"@syuilo/aiscript": "0.17.0",
"@phosphor-icons/web": "^2.0.3",
"@transfem-org/sfm-js": "0.24.5",
"@twemoji/parser": "15.0.0",
"@vitejs/plugin-vue": "5.0.4",
"@vue/compiler-sfc": "3.4.21",

View file

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

View file

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

View file

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

View file

@ -55,8 +55,6 @@ import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js';
import { defaultStore } from '@/store.js';
console.log(defaultStore.state.noteDesign, defaultStore.state.noteDesign === 'sharkey');
const props = defineProps<{
pagination: Paging;
noGap?: boolean;

View file

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

View file

@ -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][]);
}
/*

View file

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

View file

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

View file

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

View file

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

View file

@ -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}:`,

View file

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

View file

@ -43,7 +43,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"/>

View file

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

View file

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

View file

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

View file

@ -46,7 +46,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"/>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,9 +16,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<template #default="{ items }">
<MkDateSeparatedList v-slot="{ item }" :items="items" :direction="'down'" :noGap="false" :ad="false">
<MkDateSeparatedList v-if="defaultStore.state.noteDesign === 'misskey'"
v-slot="{ item }" :items="items" :direction="'down'" :noGap="false" :ad="false">
<MkNote :key="item.id" :note="item.note" :class="$style.note"/>
</MkDateSeparatedList>
<MkDateSeparatedList v-if="defaultStore.state.noteDesign === 'sharkey'"
v-slot="{ item }" :items="items" :direction="'down'" :noGap="false" :ad="false">
<SkNote :key="item.id" :note="item.note" :class="$style.note"/>
</MkDateSeparatedList>
</template>
</MkPagination>
</MkSpacer>
@ -28,10 +33,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import MkPagination from '@/components/MkPagination.vue';
import MkNote from '@/components/MkNote.vue';
import SkNote from '@/components/SkNote.vue';
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { infoImageUrl } from '@/instance.js';
import { defaultStore } from '@/store.js';
const pagination = {
endpoint: 'i/favorites' as const,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,16 +1,23 @@
/* global libopenmpt UTF8ToString writeAsciiToMemory */
/* eslint-disable */
const ChiptuneAudioContext = window.AudioContext || window.webkitAudioContext;
export function ChiptuneJsConfig (repeatCount: number, context: AudioContext) {
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;
}
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();
@ -20,6 +27,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<void>(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 +90,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 +113,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 +135,7 @@ ChiptuneJsPlayer.prototype.load = function (input) {
reject(error);
});
}
});
}));
};
ChiptuneJsPlayer.prototype.play = function (buffer: ArrayBuffer) {
@ -180,7 +209,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

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

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

View 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']),
},
});
}

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,