Merge branch 'develop' into schedule-note

This commit is contained in:
かっこかり 2023-11-23 15:52:52 +09:00 committed by GitHub
commit 8d28c2f9d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 806 additions and 339 deletions

View file

@ -242,29 +242,7 @@ function exec() {
return;
}
const matched: EmojiDef[] = [];
const max = 30;
emojiDb.value.some(x => {
if (x.name.startsWith(props.q ?? '') && !x.aliasOf && !matched.some(y => y.emoji === x.emoji)) matched.push(x);
return matched.length === max;
});
if (matched.length < max) {
emojiDb.value.some(x => {
if (x.name.startsWith(props.q ?? '') && !matched.some(y => y.emoji === x.emoji)) matched.push(x);
return matched.length === max;
});
}
if (matched.length < max) {
emojiDb.value.some(x => {
if (x.name.includes(props.q ?? '') && !matched.some(y => y.emoji === x.emoji)) matched.push(x);
return matched.length === max;
});
}
emojis.value = matched;
emojis.value = emojiAutoComplete(props.q, emojiDb.value);
} else if (props.type === 'mfmTag') {
if (!props.q || props.q === '') {
mfmTags.value = MFM_TAGS;
@ -275,6 +253,82 @@ function exec() {
}
}
type EmojiScore = { emoji: EmojiDef, score: number };
function emojiAutoComplete(query: string | null, emojiDb: EmojiDef[], max = 30): EmojiDef[] {
if (!query) {
return [];
}
const matched = new Map<string, EmojiScore>();
//
emojiDb.some(x => {
if (x.name.startsWith(query) && !x.aliasOf) {
matched.set(x.name, { emoji: x, score: query.length });
}
return matched.size === max;
});
//
if (matched.size < max) {
emojiDb.some(x => {
if (x.name.startsWith(query)) {
matched.set(x.name, { emoji: x, score: query.length });
}
return matched.size === max;
});
}
//
if (matched.size < max) {
emojiDb.some(x => {
if (x.name.includes(query)) {
matched.set(x.name, { emoji: x, score: query.length });
}
return matched.size === max;
});
}
//
if (matched.size < max) {
const queryChars = [...query];
const hitEmojis = new Map<string, EmojiScore>();
for (const x of emojiDb) {
// 1
//
let queryCharHitPos = 0;
let queryCharHitCount = 0;
for (let idx = 0; idx < queryChars.length; idx++) {
queryCharHitPos = x.name.indexOf(queryChars[idx], queryCharHitPos);
if (queryCharHitPos <= -1) {
break;
}
queryCharHitCount++;
}
// 調
if (queryCharHitCount > 2) {
hitEmojis.set(x.name, { emoji: x, score: queryCharHitCount });
}
}
// 66
[...hitEmojis.values()]
.sort((x, y) => y.score - x.score)
.slice(0, 6)
.forEach(it => matched.set(it.emoji.name, it));
}
return [...matched.values()]
.sort((x, y) => y.score - x.score)
.slice(0, max)
.map(it => it.emoji);
}
function onMousedown(event: Event) {
if (!contains(rootEl.value, event.target) && (rootEl.value !== event.target)) props.close();
}

View file

@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<iframe
ref="tweet"
allow="fullscreen;web-share"
sandbox="allow-popups allow-scripts allow-same-origin"
sandbox="allow-popups allow-popups-to-escape-sandbox allow-scripts allow-same-origin"
scrolling="no"
:style="{ position: 'relative', width: '100%', height: `${tweetHeight}px`, border: 0 }"
:src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&amp;hideCard=false&amp;hideThread=false&amp;lang=en&amp;theme=${defaultStore.state.darkMode ? 'dark' : 'light'}&amp;id=${tweetId}`"

View file

@ -65,9 +65,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<img src="https://avatars.githubusercontent.com/u/67428053?v=4" :class="$style.contributorAvatar">
<span :class="$style.contributorUsername">@kakkokari-gtyih</span>
</a>
<a href="https://github.com/taichanNE30" target="_blank" :class="$style.contributor">
<a href="https://github.com/tai-cha" target="_blank" :class="$style.contributor">
<img src="https://avatars.githubusercontent.com/u/40626578?v=4" :class="$style.contributorAvatar">
<span :class="$style.contributorUsername">@taichanNE30</span>
<span :class="$style.contributorUsername">@tai-cha</span>
</a>
</div>
</FormSection>

View file

@ -9,12 +9,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<XHeader :actions="headerActions" :tabs="headerTabs"/>
</template>
<MkSpacer :contentMax="900">
<MkSwitch :modelValue="publishing" @update:modelValue="onChangePublishing">
{{ i18n.ts.publishing }}
</MkSwitch>
<MkSelect v-model="filterType" :class="$style.input" @update:modelValue="filterItems">
<template #label>{{ i18n.ts.state }}</template>
<option value="all">{{ i18n.ts.all }}</option>
<option value="publishing">{{ i18n.ts.publishing }}</option>
<option value="expired">{{ i18n.ts.expired }}</option>
</MkSelect>
<div>
<div v-for="ad in ads" class="_panel _gaps_m" :class="$style.ad">
<MkAd v-if="ad.url" :specify="ad"/>
<MkAd v-if="ad.url" :key="ad.id" :specify="ad"/>
<MkInput v-model="ad.url" type="url">
<template #label>URL</template>
</MkInput>
@ -82,14 +85,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import XHeader from './_header_.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkSelect from '@/components/MkSelect.vue';
import FormSplit from '@/components/form/split.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
@ -101,24 +104,34 @@ let ads: any[] = $ref([]);
const localTime = new Date();
const localTimeDiff = localTime.getTimezoneOffset() * 60 * 1000;
const daysOfWeek: string[] = [i18n.ts._weekday.sunday, i18n.ts._weekday.monday, i18n.ts._weekday.tuesday, i18n.ts._weekday.wednesday, i18n.ts._weekday.thursday, i18n.ts._weekday.friday, i18n.ts._weekday.saturday];
let publishing = false;
const filterType = ref('all');
let publishing: boolean | null = null;
os.api('admin/ad/list', { publishing: publishing }).then(adsResponse => {
ads = adsResponse.map(r => {
const exdate = new Date(r.expiresAt);
const stdate = new Date(r.startsAt);
exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff);
stdate.setMilliseconds(stdate.getMilliseconds() - localTimeDiff);
return {
...r,
expiresAt: exdate.toISOString().slice(0, 16),
startsAt: stdate.toISOString().slice(0, 16),
};
});
if (adsResponse != null) {
ads = adsResponse.map(r => {
const exdate = new Date(r.expiresAt);
const stdate = new Date(r.startsAt);
exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff);
stdate.setMilliseconds(stdate.getMilliseconds() - localTimeDiff);
return {
...r,
expiresAt: exdate.toISOString().slice(0, 16),
startsAt: stdate.toISOString().slice(0, 16),
};
});
}
});
const onChangePublishing = (v) => {
publishing = v;
const filterItems = (v) => {
if (v === 'publishing') {
publishing = true;
} else if (v === 'expired') {
publishing = false;
} else {
publishing = null;
}
refresh();
};
@ -197,6 +210,7 @@ function save(ad) {
function more() {
os.api('admin/ad/list', { untilId: ads.reduce((acc, ad) => ad.id != null ? ad : acc).id, publishing: publishing }).then(adsResponse => {
if (adsResponse == null) return;
ads = ads.concat(adsResponse.map(r => {
const exdate = new Date(r.expiresAt);
const stdate = new Date(r.startsAt);
@ -213,6 +227,7 @@ function more() {
function refresh() {
os.api('admin/ad/list', { publishing: publishing }).then(adsResponse => {
if (adsResponse == null) return;
ads = adsResponse.map(r => {
const exdate = new Date(r.expiresAt);
const stdate = new Date(r.startsAt);
@ -252,4 +267,7 @@ definePageMetadata({
margin-bottom: var(--margin);
}
}
.input {
margin-bottom: 32px;
}
</style>

View file

@ -61,7 +61,7 @@ export const soundsTypes = [
'noizenecio/kick_gaba7',
] as const;
export async function getAudio(file: string, useCache = true) {
export async function loadAudio(file: string, useCache = true) {
if (useCache && cache.has(file)) {
return cache.get(file)!;
}
@ -77,12 +77,6 @@ export async function getAudio(file: string, useCache = true) {
return audioBuffer;
}
export function setVolume(audio: HTMLAudioElement, volume: number): HTMLAudioElement {
const masterVolume = defaultStore.state.sound_masterVolume;
audio.volume = masterVolume - ((1 - volume) * masterVolume);
return audio;
}
export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notification') {
const sound = defaultStore.state[`sound_${type}`];
if (_DEV_) console.log('play', type, sound);
@ -91,16 +85,22 @@ export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notifica
}
export async function playFile(file: string, volume: number) {
const buffer = await loadAudio(file);
createSourceNode(buffer, volume)?.start();
}
export function createSourceNode(buffer: AudioBuffer, volume: number) : AudioBufferSourceNode | null {
const masterVolume = defaultStore.state.sound_masterVolume;
if (masterVolume === 0 || volume === 0) {
return;
return null;
}
const gainNode = ctx.createGain();
gainNode.gain.value = masterVolume * volume;
const soundSource = ctx.createBufferSource();
soundSource.buffer = await getAudio(file);
soundSource.buffer = buffer;
soundSource.connect(gainNode).connect(ctx.destination);
soundSource.start();
return soundSource;
}

View file

@ -99,7 +99,10 @@ const current = reactive({
},
});
const prev = reactive({} as typeof current);
const jammedSound = sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1);
let jammedAudioBuffer: AudioBuffer | null = $ref(null);
let jammedSoundNodePlaying: boolean = $ref(false);
sound.loadAudio('syuilo/queue-jammed').then(buf => jammedAudioBuffer = buf);
for (const domain of ['inbox', 'deliver']) {
prev[domain] = deepClone(current[domain]);
@ -113,8 +116,13 @@ const onStats = (stats) => {
current[domain].waiting = stats[domain].waiting;
current[domain].delayed = stats[domain].delayed;
if (current[domain].waiting > 0 && widgetProps.sound && jammedSound.paused) {
jammedSound.play();
if (current[domain].waiting > 0 && widgetProps.sound && jammedAudioBuffer && !jammedSoundNodePlaying) {
const soundNode = sound.createSourceNode(jammedAudioBuffer, 1);
if (soundNode) {
jammedSoundNodePlaying = true;
soundNode.onended = () => jammedSoundNodePlaying = false;
soundNode.start();
}
}
}
};