Merge branch 'develop' into feat-12192
This commit is contained in:
commit
b0ec255972
|
|
@ -24,10 +24,12 @@
|
|||
- Feat: 今日誕生日のフォロー中のユーザーを一覧表示できるウィジェットを追加
|
||||
- Enhance: 絵文字のオートコンプリート機能強化 #12364
|
||||
- Enhance: ユーザーのRawデータを表示するページが復活
|
||||
- Enhance: リアクション選択時に音を鳴らせるように
|
||||
- fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正
|
||||
- Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367
|
||||
- Fix: コードエディタが正しく表示されない問題を修正
|
||||
- Fix: プロフィールの「ファイル」にセンシティブな画像がある際のデザインを修正
|
||||
- Fix: 一度に大量の通知が入った際に通知音が音割れする問題を修正
|
||||
|
||||
### Server
|
||||
- Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように
|
||||
|
|
|
|||
|
|
@ -1943,6 +1943,7 @@ export interface Locale {
|
|||
"notification": string;
|
||||
"antenna": string;
|
||||
"channel": string;
|
||||
"reaction": string;
|
||||
};
|
||||
"_ago": {
|
||||
"future": string;
|
||||
|
|
|
|||
|
|
@ -1848,6 +1848,7 @@ _sfx:
|
|||
notification: "通知"
|
||||
antenna: "アンテナ受信"
|
||||
channel: "チャンネル通知"
|
||||
reaction: "リアクション選択時"
|
||||
|
||||
_ago:
|
||||
future: "未来"
|
||||
|
|
|
|||
|
|
@ -64,16 +64,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
}
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
noteIds.sort((a, b) => a > b ? -1 : 1);
|
||||
if (ps.untilId) {
|
||||
noteIds = noteIds.filter(id => id < ps.untilId!);
|
||||
}
|
||||
noteIds = noteIds.slice(0, ps.limit);
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const query = this.notesRepository.createQueryBuilder('note')
|
||||
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -163,6 +163,7 @@ import { focusPrev, focusNext } from '@/scripts/focus.js';
|
|||
import { checkWordMute } from '@/scripts/check-word-mute.js';
|
||||
import { userPage } from '@/filters/user.js';
|
||||
import * as os from '@/os.js';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
import { defaultStore, noteViewInterruptors } from '@/store.js';
|
||||
import { reactionPicker } from '@/scripts/reaction-picker.js';
|
||||
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
|
||||
|
|
@ -336,6 +337,8 @@ function react(viaKeyboard = false): void {
|
|||
pleaseLogin();
|
||||
showMovedDialog();
|
||||
if (appearNote.reactionAcceptance === 'likeOnly') {
|
||||
sound.play('reaction');
|
||||
|
||||
if (props.mock) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -354,6 +357,8 @@ function react(viaKeyboard = false): void {
|
|||
} else {
|
||||
blur();
|
||||
reactionPicker.show(reactButton.value, reaction => {
|
||||
sound.play('reaction');
|
||||
|
||||
if (props.mock) {
|
||||
emit('reaction', reaction);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@ import { checkWordMute } from '@/scripts/check-word-mute.js';
|
|||
import { userPage } from '@/filters/user.js';
|
||||
import { notePage } from '@/filters/note.js';
|
||||
import * as os from '@/os.js';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
import { defaultStore, noteViewInterruptors } from '@/store.js';
|
||||
import { reactionPicker } from '@/scripts/reaction-picker.js';
|
||||
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
|
||||
|
|
@ -369,6 +370,8 @@ function react(viaKeyboard = false): void {
|
|||
pleaseLogin();
|
||||
showMovedDialog();
|
||||
if (appearNote.reactionAcceptance === 'likeOnly') {
|
||||
sound.play('reaction');
|
||||
|
||||
os.api('notes/reactions/create', {
|
||||
noteId: appearNote.id,
|
||||
reaction: '❤️',
|
||||
|
|
@ -383,6 +386,8 @@ function react(viaKeyboard = false): void {
|
|||
} else {
|
||||
blur();
|
||||
reactionPicker.show(reactButton.value, reaction => {
|
||||
sound.play('reaction');
|
||||
|
||||
os.api('notes/reactions/create', {
|
||||
noteId: appearNote.id,
|
||||
reaction: reaction,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import MkReactionEffect from '@/components/MkReactionEffect.vue';
|
|||
import { claimAchievement } from '@/scripts/achievements.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
|
||||
const props = defineProps<{
|
||||
reaction: string;
|
||||
|
|
@ -59,6 +60,10 @@ async function toggleReaction() {
|
|||
});
|
||||
if (confirm.canceled) return;
|
||||
|
||||
if (oldReaction !== props.reaction) {
|
||||
sound.play('reaction');
|
||||
}
|
||||
|
||||
if (mock) {
|
||||
emit('reactionToggled', props.reaction, (props.count - 1));
|
||||
return;
|
||||
|
|
@ -75,6 +80,8 @@ async function toggleReaction() {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
sound.play('reaction');
|
||||
|
||||
if (mock) {
|
||||
emit('reactionToggled', props.reaction, (props.count + 1));
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ import { defaultStore } from '@/store.js';
|
|||
|
||||
const masterVolume = computed(defaultStore.makeGetterSetter('sound_masterVolume'));
|
||||
|
||||
const soundsKeys = ['note', 'noteMy', 'notification', 'antenna', 'channel'] as const;
|
||||
const soundsKeys = ['note', 'noteMy', 'notification', 'antenna', 'channel', 'reaction'] as const;
|
||||
|
||||
const sounds = ref<Record<typeof soundsKeys[number], Ref<any>>>({
|
||||
note: defaultStore.reactiveState.sound_note,
|
||||
|
|
@ -46,6 +46,7 @@ const sounds = ref<Record<typeof soundsKeys[number], Ref<any>>>({
|
|||
notification: defaultStore.reactiveState.sound_notification,
|
||||
antenna: defaultStore.reactiveState.sound_antenna,
|
||||
channel: defaultStore.reactiveState.sound_channel,
|
||||
reaction: defaultStore.reactiveState.sound_reaction,
|
||||
});
|
||||
|
||||
async function updated(type: keyof typeof sounds.value, sound) {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@
|
|||
|
||||
import { defaultStore } from '@/store.js';
|
||||
|
||||
const ctx = new AudioContext();
|
||||
let ctx: AudioContext;
|
||||
const cache = new Map<string, AudioBuffer>();
|
||||
let canPlay = true;
|
||||
|
||||
export const soundsTypes = [
|
||||
null,
|
||||
|
|
@ -38,6 +39,8 @@ export const soundsTypes = [
|
|||
'syuilo/waon',
|
||||
'syuilo/popo',
|
||||
'syuilo/triple',
|
||||
'syuilo/bubble1',
|
||||
'syuilo/bubble2',
|
||||
'syuilo/poi1',
|
||||
'syuilo/poi2',
|
||||
'syuilo/pirori',
|
||||
|
|
@ -62,6 +65,9 @@ export const soundsTypes = [
|
|||
] as const;
|
||||
|
||||
export async function loadAudio(file: string, useCache = true) {
|
||||
if (ctx == null) {
|
||||
ctx = new AudioContext();
|
||||
}
|
||||
if (useCache && cache.has(file)) {
|
||||
return cache.get(file)!;
|
||||
}
|
||||
|
|
@ -77,11 +83,18 @@ export async function loadAudio(file: string, useCache = true) {
|
|||
return audioBuffer;
|
||||
}
|
||||
|
||||
export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notification') {
|
||||
export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notification' | 'reaction') {
|
||||
const sound = defaultStore.state[`sound_${type}`];
|
||||
if (_DEV_) console.log('play', type, sound);
|
||||
if (sound.type == null) return;
|
||||
playFile(sound.type, sound.volume);
|
||||
if (sound.type == null || !canPlay) return;
|
||||
|
||||
canPlay = false;
|
||||
playFile(sound.type, sound.volume).then(() => {
|
||||
// ごく短時間に音が重複しないように
|
||||
setTimeout(() => {
|
||||
canPlay = true;
|
||||
}, 25);
|
||||
});
|
||||
}
|
||||
|
||||
export async function playFile(file: string, volume: number) {
|
||||
|
|
|
|||
|
|
@ -411,6 +411,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||
where: 'device',
|
||||
default: { type: 'syuilo/square-pico', volume: 1 },
|
||||
},
|
||||
sound_reaction: {
|
||||
where: 'device',
|
||||
default: { type: 'syuilo/bubble2', volume: 1 },
|
||||
},
|
||||
}));
|
||||
|
||||
// TODO: 他のタブと永続化されたstateを同期
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ import { useStream } from '@/stream.js';
|
|||
import number from '@/filters/number.js';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
import { deepClone } from '@/scripts/clone.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
|
||||
const name = 'jobQueue';
|
||||
|
||||
|
|
@ -102,7 +103,9 @@ const prev = reactive({} as typeof current);
|
|||
let jammedAudioBuffer: AudioBuffer | null = $ref(null);
|
||||
let jammedSoundNodePlaying: boolean = $ref(false);
|
||||
|
||||
sound.loadAudio('syuilo/queue-jammed').then(buf => jammedAudioBuffer = buf);
|
||||
if (defaultStore.state.sound_masterVolume) {
|
||||
sound.loadAudio('syuilo/queue-jammed').then(buf => jammedAudioBuffer = buf);
|
||||
}
|
||||
|
||||
for (const domain of ['inbox', 'deliver']) {
|
||||
prev[domain] = deepClone(current[domain]);
|
||||
|
|
|
|||
Loading…
Reference in New Issue