Compare commits

...

9 Commits

Author SHA1 Message Date
330ea7d210 12.18.0 2020-02-20 07:30:43 +09:00
1edd173a29 Add sounds 2020-02-20 07:29:34 +09:00
98d873a7f9 Update search-by-tag.ts 2020-02-20 07:19:27 +09:00
09175b84df Fix #6016 2020-02-20 07:18:40 +09:00
177e19632a Fix #6016 2020-02-20 07:18:16 +09:00
8e6207f3e9 Remove header transition 2020-02-20 06:42:20 +09:00
ff3a97f6cf Fix #5943 2020-02-20 06:38:19 +09:00
b8e155ab40 🎨 2020-02-20 06:08:54 +09:00
b8e7df198d Improve sound 2020-02-20 06:08:49 +09:00
20 changed files with 153 additions and 130 deletions

View File

@ -1,6 +1,16 @@
ChangeLog ChangeLog
========= =========
12.18.0 (2020/02/20)
-------------------
### ✨Improvements
* 効果音設定を強化
* UIの調整
### 🐛Fixes
* ユーザープレビューが稀に画面上から消えなくなってしまう問題を修正
* ハッシュタグ検索が遅い問題を修正
12.17.0 (2020/02/20) 12.17.0 (2020/02/20)
------------------- -------------------
### ✨Improvements ### ✨Improvements

View File

@ -437,6 +437,7 @@ _sfx:
notification: "通知" notification: "通知"
chat: "チャット" chat: "チャット"
chatBg: "チャット(バックグラウンド)" chatBg: "チャット(バックグラウンド)"
antenna: "アンテナ受信"
_ago: _ago:
unknown: "謎" unknown: "謎"

View File

@ -1,7 +1,7 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>", "author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.17.0", "version": "12.18.0",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -565,9 +565,7 @@ export default Vue.extend({
}); });
} }
const audio = new Audio(`/assets/sounds/${this.$store.state.device.sfxNotification}.mp3`); this.$root.sound('notification');
audio.volume = this.$store.state.device.sfxVolume;
audio.play();
}, },
onMousedown(e) { onMousedown(e) {
@ -628,18 +626,6 @@ export default Vue.extend({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.header-enter-active, .header-leave-active {
transition: opacity 0.5s, transform 0.5s !important;
}
.header-enter {
opacity: 0;
transform: scale(0.9);
}
.header-leave-to {
opacity: 0;
transform: scale(0.9);
}
.nav-enter-active, .nav-enter-active,
.nav-leave-active { .nav-leave-active {
opacity: 1; opacity: 1;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -53,11 +53,7 @@ export default Vue.extend({
(this.$refs.tl as any).prepend(note); (this.$refs.tl as any).prepend(note);
if (this.sound) { if (this.sound) {
const audio = new Audio(note.userId === this.$store.state.i.id this.$root.sound(note.userId === this.$store.state.i.id ? 'noteMy' : 'note');
? `/assets/sounds/${this.$store.state.device.sfxNoteMy}.mp3`
: `/assets/sounds/${this.$store.state.device.sfxNote}.mp3`);
audio.volume = this.$store.state.device.sfxVolume;
audio.play();
} }
}; };

View File

@ -53,6 +53,7 @@ export default Vue.extend({
return { return {
u: null, u: null,
show: false, show: false,
closed: false,
top: 0, top: 0,
left: 0, left: 0,
}; };
@ -68,6 +69,7 @@ export default Vue.extend({
{ userId: this.user }; { userId: this.user };
this.$root.api('users/show', query).then(user => { this.$root.api('users/show', query).then(user => {
if (this.closed) return;
this.u = user; this.u = user;
this.show = true; this.show = true;
}); });
@ -83,6 +85,7 @@ export default Vue.extend({
methods: { methods: {
close() { close() {
this.closed = true;
this.show = false; this.show = false;
if (this.$refs.content) (this.$refs.content as any).style.pointerEvents = 'none'; if (this.$refs.content) (this.$refs.content as any).style.pointerEvents = 'none';
} }

View File

@ -39,11 +39,15 @@ export default {
self.hideTimer = setTimeout(self.close, 500); self.hideTimer = setTimeout(self.close, 500);
}); });
self.checkTimer = setInterval(() => {
if (!document.body.contains(el)) self.close();
}, 1000);
document.body.appendChild(self.tag.$el); document.body.appendChild(self.tag.$el);
self.checkTimer = setInterval(() => {
if (!document.body.contains(el)) {
clearTimeout(self.showTimer);
clearTimeout(self.hideTimer);
self.close();
}
}, 1000);
}; };
el.addEventListener('mouseover', () => { el.addEventListener('mouseover', () => {
@ -66,9 +70,6 @@ export default {
unbind(el, binding, vn) { unbind(el, binding, vn) {
const self = el._userPreviewDirective_; const self = el._userPreviewDirective_;
clearTimeout(self.showTimer);
clearTimeout(self.hideTimer);
clearInterval(self.checkTimer); clearInterval(self.checkTimer);
self.close();
} }
}; };

View File

@ -189,6 +189,13 @@ os.init(async () => {
if (cb) vm.$once('closed', cb); if (cb) vm.$once('closed', cb);
(vm as any).focus(); (vm as any).focus();
}, },
sound(type: string) {
const sound = this.$store.state.device['sfx' + type.substr(0, 1).toUpperCase() + type.substr(1)];
if (sound == null) return;
const audio = new Audio(`/assets/sounds/${sound}.mp3`);
audio.volume = this.$store.state.device.sfxVolume;
audio.play();
}
}, },
router: router, router: router,
render: createEl => createEl(App) render: createEl => createEl(App)
@ -198,4 +205,96 @@ os.init(async () => {
// マウント // マウント
app.$mount('#app'); app.$mount('#app');
if (app.$store.getters.isSignedIn) {
const main = os.stream.useSharedConnection('main');
// 自分の情報が更新されたとき
main.on('meUpdated', i => {
app.$store.dispatch('mergeMe', i);
});
main.on('readAllNotifications', () => {
app.$store.dispatch('mergeMe', {
hasUnreadNotification: false
});
});
main.on('unreadNotification', () => {
app.$store.dispatch('mergeMe', {
hasUnreadNotification: true
});
});
main.on('unreadMention', () => {
app.$store.dispatch('mergeMe', {
hasUnreadMentions: true
});
});
main.on('readAllUnreadMentions', () => {
app.$store.dispatch('mergeMe', {
hasUnreadMentions: false
});
});
main.on('unreadSpecifiedNote', () => {
app.$store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: true
});
});
main.on('readAllUnreadSpecifiedNotes', () => {
app.$store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: false
});
});
main.on('readAllMessagingMessages', () => {
app.$store.dispatch('mergeMe', {
hasUnreadMessagingMessage: false
});
});
main.on('unreadMessagingMessage', () => {
app.$store.dispatch('mergeMe', {
hasUnreadMessagingMessage: true
});
app.sound('chatBg');
});
main.on('readAllAntennas', () => {
app.$store.dispatch('mergeMe', {
hasUnreadAntenna: false
});
});
main.on('unreadAntenna', () => {
app.$store.dispatch('mergeMe', {
hasUnreadAntenna: true
});
app.sound('antenna');
});
main.on('readAllAnnouncements', () => {
app.$store.dispatch('mergeMe', {
hasUnreadAnnouncement: false
});
});
main.on('clientSettingUpdated', x => {
app.$store.commit('settings/set', {
key: x.key,
value: x.value
});
});
// トークンが再生成されたとき
// このままではMisskeyが利用できないので強制的にサインアウトさせる
main.on('myTokenRegenerated', () => {
os.signout();
});
}
}); });

View File

@ -3,7 +3,7 @@ import Vue from 'vue';
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import initStore from './store'; import initStore from './store';
import { apiUrl, version, locale } from './config'; import { apiUrl, version } from './config';
import Progress from './scripts/loading'; import Progress from './scripts/loading';
import Stream from './scripts/stream'; import Stream from './scripts/stream';
@ -142,98 +142,6 @@ export default class MiOS extends EventEmitter {
@autobind @autobind
private initStream() { private initStream() {
this.stream = new Stream(this); this.stream = new Stream(this);
if (this.store.getters.isSignedIn) {
const main = this.stream.useSharedConnection('main');
// 自分の情報が更新されたとき
main.on('meUpdated', i => {
this.store.dispatch('mergeMe', i);
});
main.on('readAllNotifications', () => {
this.store.dispatch('mergeMe', {
hasUnreadNotification: false
});
});
main.on('unreadNotification', () => {
this.store.dispatch('mergeMe', {
hasUnreadNotification: true
});
});
main.on('unreadMention', () => {
this.store.dispatch('mergeMe', {
hasUnreadMentions: true
});
});
main.on('readAllUnreadMentions', () => {
this.store.dispatch('mergeMe', {
hasUnreadMentions: false
});
});
main.on('unreadSpecifiedNote', () => {
this.store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: true
});
});
main.on('readAllUnreadSpecifiedNotes', () => {
this.store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: false
});
});
main.on('readAllMessagingMessages', () => {
this.store.dispatch('mergeMe', {
hasUnreadMessagingMessage: false
});
});
main.on('unreadMessagingMessage', () => {
this.store.dispatch('mergeMe', {
hasUnreadMessagingMessage: true
});
const audio = new Audio(`/assets/sounds/${this.store.state.device.sfxChatBg}.mp3`);
audio.volume = this.store.state.device.sfxVolume;
audio.play();
});
main.on('readAllAntennas', () => {
this.store.dispatch('mergeMe', {
hasUnreadAntenna: false
});
});
main.on('unreadAntenna', () => {
this.store.dispatch('mergeMe', {
hasUnreadAntenna: true
});
});
main.on('readAllAnnouncements', () => {
this.store.dispatch('mergeMe', {
hasUnreadAnnouncement: false
});
});
main.on('clientSettingUpdated', x => {
this.store.commit('settings/set', {
key: x.key,
value: x.value
});
});
// トークンが再生成されたとき
// このままではMisskeyが利用できないので強制的にサインアウトさせる
main.on('myTokenRegenerated', () => {
this.signout();
});
}
} }
/** /**

View File

@ -196,7 +196,7 @@ export default Vue.extend({
> button { > button {
display: block; display: block;
margin: 0 auto; margin: 0 auto;
padding: 8px 12px; padding: 8px 16px;
border-radius: 32px; border-radius: 32px;
} }
} }

View File

@ -184,10 +184,7 @@ export default Vue.extend({
}, },
onMessage(message) { onMessage(message) {
// サウンドを再生する this.$root.sound('chat');
const audio = new Audio(`/assets/sounds/${this.$store.state.device.sfxChat}.mp3`);
audio.volume = this.$store.state.device.sfxVolume;
audio.play();
const isBottom = this.isBottom(); const isBottom = this.isBottom();

View File

@ -37,6 +37,11 @@
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option> <option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
<template #text><button class="_textButton" @click="listen(sfxChatBg)" v-if="sfxChatBg"><fa :icon="faPlay"/> {{ $t('listen') }}</button></template> <template #text><button class="_textButton" @click="listen(sfxChatBg)" v-if="sfxChatBg"><fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
</mk-select> </mk-select>
<mk-select v-model="sfxAntenna">
<template #label>{{ $t('_sfx.antenna') }}</template>
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
<template #text><button class="_textButton" @click="listen(sfxAntenna)" v-if="sfxAntenna"><fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
</mk-select>
</div> </div>
</section> </section>
@ -97,6 +102,10 @@ const sounds = [
'syuilo/pope1', 'syuilo/pope1',
'syuilo/pope2', 'syuilo/pope2',
'syuilo/waon', 'syuilo/waon',
'syuilo/popo',
'syuilo/triple',
'syuilo/poi1',
'syuilo/poi2',
'aisha/1', 'aisha/1',
'aisha/2', 'aisha/2',
'aisha/3', 'aisha/3',
@ -196,6 +205,11 @@ export default Vue.extend({
get() { return this.$store.state.device.sfxChatBg; }, get() { return this.$store.state.device.sfxChatBg; },
set(value) { this.$store.commit('device/set', { key: 'sfxChatBg', value }); } set(value) { this.$store.commit('device/set', { key: 'sfxChatBg', value }); }
}, },
sfxAntenna: {
get() { return this.$store.state.device.sfxAntenna; },
set(value) { this.$store.commit('device/set', { key: 'sfxAntenna', value }); }
},
}, },
watch: { watch: {

View File

@ -47,6 +47,7 @@ const defaultDeviceSettings = {
sfxNotification: 'syuilo/pope2', sfxNotification: 'syuilo/pope2',
sfxChat: 'syuilo/pope1', sfxChat: 'syuilo/pope1',
sfxChatBg: 'syuilo/waon', sfxChatBg: 'syuilo/waon',
sfxAntenna: 'syuilo/triple',
userData: {}, userData: {},
}; };

3
src/misc/safe-for-sql.ts Normal file
View File

@ -0,0 +1,3 @@
export function safeForSql(text: string): boolean {
return /[\0\x08\x09\x1a\n\r"'\\\%]/g.test(text);
}

View File

@ -3,6 +3,7 @@ import define from '../../define';
import { fetchMeta } from '../../../../misc/fetch-meta'; import { fetchMeta } from '../../../../misc/fetch-meta';
import { Notes } from '../../../../models'; import { Notes } from '../../../../models';
import { Note } from '../../../../models/entities/note'; import { Note } from '../../../../models/entities/note';
import { safeForSql } from '../../../../misc/safe-for-sql';
/* /*
トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要 トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要
@ -113,7 +114,7 @@ export default define(meta, async () => {
for (let i = 0; i < range; i++) { for (let i = 0; i < range; i++) {
countPromises.push(Promise.all(hots.map(tag => Notes.createQueryBuilder('note') countPromises.push(Promise.all(hots.map(tag => Notes.createQueryBuilder('note')
.select('count(distinct note.userId)') .select('count(distinct note.userId)')
.where(':tag = ANY(note.tags)', { tag: tag }) .where(`'{"${safeForSql(tag) ? tag : 'aichan_kawaii'}"}' <@ note.tags`)
.andWhere('note.createdAt < :lt', { lt: new Date(now.getTime() - (interval * i)) }) .andWhere('note.createdAt < :lt', { lt: new Date(now.getTime() - (interval * i)) })
.andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - (interval * (i + 1))) }) .andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - (interval * (i + 1))) })
.cache(60000) // 1 min .cache(60000) // 1 min
@ -127,7 +128,7 @@ export default define(meta, async () => {
const totalCounts = await Promise.all(hots.map(tag => Notes.createQueryBuilder('note') const totalCounts = await Promise.all(hots.map(tag => Notes.createQueryBuilder('note')
.select('count(distinct note.userId)') .select('count(distinct note.userId)')
.where(':tag = ANY(note.tags)', { tag: tag }) .where(`'{"${safeForSql(tag) ? tag : 'aichan_kawaii'}"}' <@ note.tags`)
.andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - rangeA) }) .andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - rangeA) })
.cache(60000 * 60) // 60 min .cache(60000 * 60) // 60 min
.getRawOne() .getRawOne()

View File

@ -6,6 +6,7 @@ import { Notes } from '../../../../models';
import { generateMuteQuery } from '../../common/generate-mute-query'; import { generateMuteQuery } from '../../common/generate-mute-query';
import { generateVisibilityQuery } from '../../common/generate-visibility-query'; import { generateVisibilityQuery } from '../../common/generate-visibility-query';
import { Brackets } from 'typeorm'; import { Brackets } from 'typeorm';
import { safeForSql } from '../../../../misc/safe-for-sql';
export const meta = { export const meta = {
desc: { desc: {
@ -99,14 +100,16 @@ export default define(meta, async (ps, me) => {
if (me) generateMuteQuery(query, me); if (me) generateMuteQuery(query, me);
if (ps.tag) { if (ps.tag) {
query.andWhere(':tag = ANY(note.tags)', { tag: ps.tag.toLowerCase() }); if (!safeForSql(ps.tag)) return;
query.andWhere(`'{"${ps.tag.toLowerCase()}"}' <@ note.tags`);
} else { } else {
let i = 0; let i = 0;
query.andWhere(new Brackets(qb => { query.andWhere(new Brackets(qb => {
for (const tags of ps.query!) { for (const tags of ps.query!) {
qb.orWhere(new Brackets(qb => { qb.orWhere(new Brackets(qb => {
for (const tag of tags) { for (const tag of tags) {
qb.andWhere(`:tag${i} = ANY(note.tags)`, { [`tag${i}`]: tag.toLowerCase() }); if (!safeForSql(tag)) return;
qb.andWhere(`'{"${tag.toLowerCase()}"}' <@ note.tags`);
i++; i++;
} }
})); }));