Refactoring

This commit is contained in:
syuilo
2019-05-21 03:07:11 +09:00
parent 262d5ead51
commit 5511b6e013
41 changed files with 764 additions and 1408 deletions

View File

@@ -0,0 +1,56 @@
<template>
<div class="ecsvsegy" v-if="!fetching">
<sequential-entrance animation="entranceFromTop" delay="25">
<template v-for="note in notes">
<mk-note-detail class="post" :note="note" :key="note.id"/>
</template>
</sequential-entrance>
<div class="more" v-if="more">
<ui-button inline @click="fetchMore()">{{ $t('@.load-more') }}</ui-button>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import paging from '../../../common/scripts/paging';
export default Vue.extend({
i18n: i18n(),
mixins: [
paging({
captureWindowScroll: true,
}),
],
props: {
pagination: {
required: true
},
extract: {
required: false
}
},
computed: {
notes() {
return this.extract ? this.extract(this.items) : this.items;
}
}
});
</script>
<style lang="stylus" scoped>
.ecsvsegy
margin 0 auto
> * > .post
margin-bottom 16px
> .more
margin 32px 16px 16px 16px
text-align center
</style>

View File

@@ -4,9 +4,9 @@
<div class="newer-indicator" :style="{ top: $store.state.uiHeaderHeight + 'px' }" v-show="queue.length > 0"></div>
<div class="empty" v-if="notes.length == 0 && !fetching && inited">{{ $t('@.no-notes') }}</div>
<div class="empty" v-if="empty">{{ $t('@.no-notes') }}</div>
<mk-error v-if="!fetching && !inited" @retry="init()"/>
<mk-error v-if="error" @retry="init()"/>
<div class="placeholder" v-if="fetching">
<template v-for="i in 10">
@@ -17,8 +17,8 @@
<!-- トランジションを有効にするとなぜかメモリリークする -->
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div" ref="notes">
<template v-for="(note, i) in _notes">
<mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)" :compact="true" ref="note"/>
<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
<mk-note :note="note" :key="note.id" :compact="true" ref="note"/>
<p class="date" :key="note.id + '_date'" v-if="i != items.length - 1 && note._date != _notes[i + 1]._date">
<span><fa icon="angle-up"/>{{ note._datetext }}</span>
<span><fa icon="angle-down"/>{{ _notes[i + 1]._datetext }}</span>
</p>
@@ -39,152 +39,66 @@ import Vue from 'vue';
import i18n from '../../../i18n';
import * as config from '../../../config';
import shouldMuteNote from '../../../common/scripts/should-mute-note';
const displayLimit = 30;
import paging from '../../../common/scripts/paging';
export default Vue.extend({
i18n: i18n(),
props: {
makePromise: {
required: true
}
},
mixins: [
paging({
captureWindowScroll: true,
data() {
return {
notes: [],
queue: [],
fetching: true,
moreFetching: false,
inited: false,
more: false
};
onQueueChanged: (self, x) => {
if (x.length > 0) {
self.$store.commit('indicate', true);
} else {
self.$store.commit('indicate', false);
}
},
onPrepend: (self, note, silent) => {
// 弾く
if (shouldMuteNote(self.$store.state.i, self.$store.state.settings, note)) return false;
// タブが非表示またはスクロール位置が最上部ではないならタイトルで通知
if (document.hidden || !self.isScrollTop()) {
self.$store.commit('pushBehindNote', note);
}
if (self.isScrollTop()) {
// サウンドを再生する
if (self.$store.state.device.enableSounds && !silent) {
const sound = new Audio(`${config.url}/assets/post.mp3`);
sound.volume = self.$store.state.device.soundVolume;
sound.play();
}
}
}
}),
],
props: {
pagination: {
required: true
},
},
computed: {
_notes(): any[] {
return (this.notes as any).map(note => {
const date = new Date(note.createdAt).getDate();
const month = new Date(note.createdAt).getMonth() + 1;
note._date = date;
note._datetext = this.$t('@.month-and-day').replace('{month}', month.toString()).replace('{day}', date.toString());
return note;
return (this.items as any).map(item => {
const date = new Date(item.createdAt).getDate();
const month = new Date(item.createdAt).getMonth() + 1;
item._date = date;
item._datetext = this.$t('@.month-and-day').replace('{month}', month.toString()).replace('{day}', date.toString());
return item;
});
}
},
created() {
this.init();
},
mounted() {
window.addEventListener('scroll', this.onScroll, { passive: true });
},
beforeDestroy() {
window.removeEventListener('scroll', this.onScroll);
},
methods: {
isScrollTop() {
return window.scrollY <= 8;
},
focus() {
(this.$refs.notes as any).children[0].focus ? (this.$refs.notes as any).children[0].focus() : (this.$refs.notes as any).$el.children[0].focus();
},
onNoteUpdated(i, note) {
Vue.set((this as any).notes, i, note);
},
reload() {
this.queue = [];
this.notes = [];
this.init();
},
async init() {
this.fetching = true;
await (this.makePromise()).then(x => {
if (Array.isArray(x)) {
this.notes = x;
} else {
this.notes = x.notes;
this.more = x.more;
}
this.inited = true;
this.fetching = false;
this.$emit('inited');
}, e => {
this.fetching = false;
});
},
async fetchMore() {
if (!this.more || this.moreFetching || this.notes.length === 0) return;
this.moreFetching = true;
this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
this.notes = this.notes.concat(x.notes);
this.more = x.more;
this.moreFetching = false;
}, e => {
this.moreFetching = false;
});
},
prepend(note, silent = false) {
// 弾く
if (shouldMuteNote(this.$store.state.i, this.$store.state.settings, note)) return;
// タブが非表示またはスクロール位置が最上部ではないならタイトルで通知
if (document.hidden || !this.isScrollTop()) {
this.$store.commit('pushBehindNote', note);
}
if (this.isScrollTop()) {
// Prepend the note
this.notes.unshift(note);
// サウンドを再生する
if (this.$store.state.device.enableSounds && !silent) {
const sound = new Audio(`${config.url}/assets/post.mp3`);
sound.volume = this.$store.state.device.soundVolume;
sound.play();
}
// オーバーフローしたら古い投稿は捨てる
if (this.notes.length >= displayLimit) {
this.notes = this.notes.slice(0, displayLimit);
this.more = true;
}
} else {
this.queue.push(note);
}
},
append(note) {
this.notes.push(note);
},
releaseQueue() {
for (const n of this.queue) {
this.prepend(n, true);
}
this.queue = [];
},
onScroll() {
if (this.isScrollTop()) {
this.releaseQueue();
}
if (this.$store.state.settings.fetchOnScroll) {
const current = window.scrollY + window.innerHeight;
if (current > document.body.offsetHeight - 8) this.fetchMore();
}
}
}
});
</script>

View File

@@ -1,6 +1,6 @@
<template>
<div>
<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')">
<mk-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')">
<template #header>
<slot></slot>
</template>
@@ -11,36 +11,23 @@
<script lang="ts">
import Vue from 'vue';
const fetchLimit = 10;
export default Vue.extend({
props: ['list'],
data() {
return {
connection: null,
date: null,
makePromise: cursor => this.$root.api('notes/user-list-timeline', {
listId: this.list.id,
limit: fetchLimit + 1,
untilId: cursor ? cursor : undefined,
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
}).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
return {
notes: notes,
more: true
};
} else {
return {
notes: notes,
more: false
};
}
})
pagination: {
endpoint: 'notes/user-list-timeline',
limit: 10,
params: init => ({
listId: this.list.id,
untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
})
}
};
},
watch: {

View File

@@ -1,83 +0,0 @@
<template>
<div class="ecsvsegy" v-if="!fetching">
<sequential-entrance animation="entranceFromTop" delay="25">
<template v-for="favorite in favorites">
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
</template>
</sequential-entrance>
<div class="more" v-if="existMore">
<ui-button inline @click="fetchMore()">{{ $t('@.load-more') }}</ui-button>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import Progress from '../../../common/scripts/loading';
export default Vue.extend({
i18n: i18n('.vue'),
data() {
return {
fetching: true,
favorites: [],
existMore: false,
moreFetching: false
};
},
created() {
this.fetch();
},
methods: {
fetch() {
Progress.start();
this.fetching = true;
this.$root.api('i/favorites', {
limit: 11
}).then(favorites => {
if (favorites.length == 11) {
this.existMore = true;
favorites.pop();
}
this.favorites = favorites;
this.fetching = false;
Progress.done();
});
},
fetchMore() {
this.moreFetching = true;
this.$root.api('i/favorites', {
limit: 11,
untilId: this.favorites[this.favorites.length - 1].id
}).then(favorites => {
if (favorites.length == 11) {
this.existMore = true;
favorites.pop();
} else {
this.existMore = false;
}
this.favorites = this.favorites.concat(favorites);
this.moreFetching = false;
});
}
}
});
</script>
<style lang="stylus" scoped>
.ecsvsegy
margin 0 auto
> * > .post
margin-bottom 16px
> .more
margin 32px 16px 16px 16px
text-align center
</style>

View File

@@ -1,55 +0,0 @@
<template>
<div class="glowckho" v-if="!fetching">
<sequential-entrance animation="entranceFromTop" delay="25">
<template v-for="note in notes">
<mk-note-detail class="post" :note="note" :key="note.id"/>
</template>
</sequential-entrance>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Progress from '../../../common/scripts/loading';
export default Vue.extend({
data() {
return {
fetching: true,
notes: [],
};
},
created() {
this.fetch();
},
methods: {
fetch() {
Progress.start();
this.fetching = true;
this.$root.api('notes/featured', {
limit: 30
}).then(notes => {
notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
this.notes = notes;
this.fetching = false;
Progress.done();
});
},
}
});
</script>
<style lang="stylus" scoped>
.glowckho
margin 0 auto
> * > .post
margin-bottom 16px
> .more
margin 32px 16px 16px 16px
text-align center
</style>

View File

@@ -1,6 +1,6 @@
<template>
<div>
<mk-notes ref="timeline" :make-promise="makePromise" @inited="inited">
<mk-notes ref="timeline" :pagination="pagination" @inited="inited">
<template #header>
<header class="oxgbmvii">
<span><fa icon="search"/> {{ q }}</span>
@@ -16,30 +16,15 @@ import i18n from '../../../i18n';
import Progress from '../../../common/scripts/loading';
import { genSearchQuery } from '../../../common/scripts/gen-search-query';
const limit = 20;
export default Vue.extend({
i18n: i18n('desktop/views/pages/search.vue'),
data() {
return {
makePromise: async cursor => this.$root.api('notes/search', {
limit: limit + 1,
offset: cursor ? cursor : undefined,
...(await genSearchQuery(this, this.q))
}).then(notes => {
if (notes.length == limit + 1) {
notes.pop();
return {
notes: notes,
cursor: cursor ? cursor + limit : limit
};
} else {
return {
notes: notes,
more: false
};
}
})
pagination: {
endpoint: 'notes/search',
limit: 20,
params: () => genSearchQuery(this, this.q)
}
};
},
computed: {

View File

@@ -1,6 +1,6 @@
<template>
<div>
<mk-notes ref="timeline" :make-promise="makePromise" @inited="inited">
<mk-notes ref="timeline" :pagination="pagination" @inited="inited">
<template #header>
<header class="wqraeznr">
<span><fa icon="hashtag"/> {{ $route.params.tag }}</span>
@@ -15,30 +15,17 @@ import Vue from 'vue';
import i18n from '../../../i18n';
import Progress from '../../../common/scripts/loading';
const limit = 20;
export default Vue.extend({
i18n: i18n('desktop/views/pages/tag.vue'),
data() {
return {
makePromise: cursor => this.$root.api('notes/search-by-tag', {
limit: limit + 1,
offset: cursor ? cursor : undefined,
tag: this.$route.params.tag
}).then(notes => {
if (notes.length == limit + 1) {
notes.pop();
return {
notes: notes,
cursor: cursor ? cursor + limit : limit
};
} else {
return {
notes: notes,
more: false
};
pagination: {
endpoint: 'notes/search-by-tag',
limit: 20,
params: {
tag: this.$route.params.tag
}
})
}
};
},
watch: {

View File

@@ -1,6 +1,6 @@
<template>
<div>
<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')">
<mk-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')">
<template #header>
<slot></slot>
<div v-if="src == 'home' && alone" class="ibpylqas">
@@ -16,8 +16,6 @@
import Vue from 'vue';
import i18n from '../../../i18n';
const fetchLimit = 10;
export default Vue.extend({
i18n: i18n('desktop/views/components/timeline.core.vue'),
@@ -42,7 +40,7 @@ export default Vue.extend({
},
query: {},
endpoint: null,
makePromise: null
pagination: null
};
},
@@ -109,25 +107,14 @@ export default Vue.extend({
this.connection.on('mention', onNote);
}
this.makePromise = cursor => this.$root.api(this.endpoint, {
limit: fetchLimit + 1,
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
untilId: cursor ? cursor : undefined,
...this.baseQuery, ...this.query
}).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
return {
notes: notes,
more: true
};
} else {
return {
notes: notes,
more: false
};
}
});
this.pagination = {
endpoint: this.endpoint,
limit: 10,
params: init => ({
untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
...this.baseQuery, ...this.query
})
};
},
methods: {

View File

@@ -1,8 +1,8 @@
<template>
<div>
<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')">
<mk-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')">
<template #header>
<header class="oh5y2r7l5lx8j6jj791ykeiwgihheguk">
<header class="kugajpep">
<span :data-active="mode == 'default'" @click="mode = 'default'"><fa :icon="['far', 'comment-alt']"/> {{ $t('default') }}</span>
<span :data-active="mode == 'with-replies'" @click="mode = 'with-replies'"><fa icon="comments"/> {{ $t('with-replies') }}</span>
<span :data-active="mode == 'with-media'" @click="mode = 'with-media'"><fa :icon="['far', 'images']"/> {{ $t('with-media') }}</span>
@@ -17,8 +17,6 @@
import Vue from 'vue';
import i18n from '../../../../i18n';
const fetchLimit = 10;
export default Vue.extend({
i18n: i18n('desktop/views/pages/user/user.timeline.vue'),
@@ -30,28 +28,17 @@ export default Vue.extend({
mode: 'default',
unreadCount: 0,
date: null,
makePromise: cursor => this.$root.api('users/notes', {
userId: this.user.id,
limit: fetchLimit + 1,
includeReplies: this.mode == 'with-replies',
includeMyRenotes: this.mode != 'my-posts',
withFiles: this.mode == 'with-media',
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
untilId: cursor ? cursor : undefined
}).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
return {
notes: notes,
more: true
};
} else {
return {
notes: notes,
more: false
};
}
})
pagination: {
endpoint: 'users/notes',
limit: 10,
params: init => ({
userId: this.user.id,
untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
includeReplies: this.mode == 'with-replies',
includeMyRenotes: this.mode != 'my-posts',
withFiles: this.mode == 'with-media',
})
}
};
},
@@ -88,7 +75,7 @@ export default Vue.extend({
</script>
<style lang="stylus" scoped>
.oh5y2r7l5lx8j6jj791ykeiwgihheguk
.kugajpep
padding 0 8px
z-index 10
background var(--faceHeader)