Refactor client (#4307)

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Fix bug

* 🎨

* 🎨

* 🎨
This commit is contained in:
syuilo
2019-02-18 09:17:55 +09:00
committed by GitHub
parent efd0368e56
commit ba1492f977
37 changed files with 738 additions and 1526 deletions

View File

@@ -1,13 +1,10 @@
<template>
<div class="oxgbmvii">
<div class="notes">
<header>
<div>
<mk-notes ref="timeline" :make-promise="makePromise" @inited="inited">
<header slot="header" class="oxgbmvii">
<span><fa icon="search"/> {{ q }}</span>
</header>
<p v-if="!fetching && notAvailable">{{ $t('not-available') }}</p>
<p v-if="!fetching && empty"><fa icon="search"/> {{ $t('not-found', { q }) }}</p>
<mk-notes ref="timeline" :more="existMore ? more : null"/>
</div>
</mk-notes>
</div>
</template>
@@ -22,27 +19,40 @@ export default Vue.extend({
i18n: i18n('desktop/views/pages/search.vue'),
data() {
return {
fetching: true,
moreFetching: false,
existMore: false,
offset: 0,
empty: false,
notAvailable: false
makePromise: cursor => this.$root.api('notes/search', {
limit: limit + 1,
offset: cursor ? cursor : undefined,
query: this.q
}).then(notes => {
if (notes.length == limit + 1) {
notes.pop();
return {
notes: notes,
cursor: cursor ? cursor + limit : limit
};
} else {
return {
notes: notes,
cursor: null
};
}
})
};
},
watch: {
$route: 'fetch'
},
computed: {
q(): string {
return this.$route.query.q;
}
},
watch: {
$route() {
this.$refs.timeline.reload();
}
},
mounted() {
document.addEventListener('keydown', this.onDocumentKeydown);
window.addEventListener('scroll', this.onScroll, { passive: true });
this.fetch();
Progress.start();
},
beforeDestroy() {
document.removeEventListener('keydown', this.onDocumentKeydown);
@@ -56,75 +66,23 @@ export default Vue.extend({
}
}
},
fetch() {
this.fetching = true;
Progress.start();
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
this.$root.api('notes/search', {
limit: limit + 1,
offset: this.offset,
query: this.q
}).then(notes => {
if (notes.length == 0) this.empty = true;
if (notes.length == limit + 1) {
notes.pop();
this.existMore = true;
}
res(notes);
this.fetching = false;
Progress.done();
}, (e: string) => {
this.fetching = false;
Progress.done();
if (e === 'searching not available') this.notAvailable = true;
});
}));
inited() {
Progress.done();
},
more() {
this.offset += limit;
const promise = this.$root.api('notes/search', {
limit: limit + 1,
offset: this.offset,
query: this.q
});
promise.then(notes => {
if (notes.length == limit + 1) {
notes.pop();
} else {
this.existMore = false;
}
for (const n of notes) {
(this.$refs.timeline as any).append(n);
}
this.moreFetching = false;
});
return promise;
}
}
});
</script>
<style lang="stylus" scoped>
.oxgbmvii
> .notes
background var(--face)
box-shadow var(--shadow)
border-radius var(--round)
overflow hidden
padding 0 8px
z-index 10
background var(--faceHeader)
box-shadow 0 var(--lineWidth) var(--desktopTimelineHeaderShadow)
> header
padding 0 8px
z-index 10
background var(--faceHeader)
box-shadow 0 var(--lineWidth) var(--desktopTimelineHeaderShadow)
> span
padding 0 8px
font-size 0.9em
line-height 42px
color var(--text)
> span
padding 0 8px
font-size 0.9em
line-height 42px
color var(--text)
</style>

View File

@@ -1,7 +1,10 @@
<template>
<div>
<p :class="$style.empty" v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q: $route.params.tag }) }}</p>
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
<mk-notes ref="timeline" :make-promise="makePromise" @inited="inited">
<header class="wqraeznr" slot="header">
<span><fa icon="hashtag"/> {{ $route.params.tag }}</span>
</header>
</mk-notes>
</div>
</template>
@@ -16,21 +19,35 @@ export default Vue.extend({
i18n: i18n('desktop/views/pages/tag.vue'),
data() {
return {
fetching: true,
moreFetching: false,
existMore: false,
offset: 0,
empty: false
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,
cursor: null
};
}
})
};
},
watch: {
$route: 'fetch'
$route() {
this.$refs.timeline.reload();
}
},
mounted() {
document.addEventListener('keydown', this.onDocumentKeydown);
window.addEventListener('scroll', this.onScroll, { passive: true });
this.fetch();
Progress.start();
},
beforeDestroy() {
document.removeEventListener('keydown', this.onDocumentKeydown);
@@ -44,73 +61,23 @@ export default Vue.extend({
}
}
},
fetch() {
this.fetching = true;
Progress.start();
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
this.$root.api('notes/search_by_tag', {
limit: limit + 1,
offset: this.offset,
tag: this.$route.params.tag
}).then(notes => {
if (notes.length == 0) this.empty = true;
if (notes.length == limit + 1) {
notes.pop();
this.existMore = true;
}
res(notes);
this.fetching = false;
Progress.done();
}, rej);
}));
inited() {
Progress.done();
},
more() {
this.offset += limit;
const promise = this.$root.api('notes/search_by_tag', {
limit: limit + 1,
offset: this.offset,
tag: this.$route.params.tag
});
promise.then(notes => {
if (notes.length == limit + 1) {
notes.pop();
} else {
this.existMore = false;
}
for (const n of notes) {
(this.$refs.timeline as any).append(n);
}
this.moreFetching = false;
});
return promise;
}
}
});
</script>
<style lang="stylus" module>
.notes
background var(--face)
box-shadow var(--shadow)
border-radius var(--round)
overflow hidden
.empty
display block
margin 0 auto
padding 32px
max-width 400px
text-align center
color #999
> [data-icon]
display block
margin-bottom 16px
font-size 3em
color #ccc
<style lang="stylus" scoped>
.wqraeznr
padding 0 8px
z-index 10
background var(--faceHeader)
box-shadow 0 var(--lineWidth) var(--desktopTimelineHeaderShadow)
> span
padding 0 8px
font-size 0.9em
line-height 42px
color var(--text)
</style>

View File

@@ -5,8 +5,11 @@
<router-link to="/explore">{{ $t('@.empty-timeline-info.explore') }}</router-link>
</div>
<mk-notes ref="timeline" :more="existMore ? more : null">
<p :class="$style.empty" slot="empty">
<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')">
<template slot="header">
<slot></slot>
</template>
<p slot="empty">
<fa :icon="['far', 'comments']"/>{{ $t('empty') }}
</p>
</mk-notes>
@@ -21,6 +24,7 @@ const fetchLimit = 10;
export default Vue.extend({
i18n: i18n('desktop/views/components/timeline.core.vue'),
props: {
src: {
type: String,
@@ -33,9 +37,6 @@ export default Vue.extend({
data() {
return {
fetching: true,
moreFetching: false,
existMore: false,
connection: null,
date: null,
baseQuery: {
@@ -44,21 +45,18 @@ export default Vue.extend({
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
},
query: {},
endpoint: null
endpoint: null,
makePromise: null
};
},
computed: {
alone(): boolean {
return this.$store.state.i.followingCount == 0;
},
canFetchMore(): boolean {
return !this.moreFetching && !this.fetching && this.existMore;
}
},
mounted() {
created() {
const prepend = note => {
(this.$refs.timeline as any).prepend(note);
};
@@ -109,7 +107,25 @@ export default Vue.extend({
this.connection.on('mention', onNote);
}
this.fetch();
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,
cursor: notes[notes.length - 1].id
};
} else {
return {
notes: notes,
cursor: null
};
}
});
},
beforeDestroy() {
@@ -117,57 +133,8 @@ export default Vue.extend({
},
methods: {
fetch() {
this.fetching = true;
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
this.$root.api(this.endpoint, Object.assign({
limit: fetchLimit + 1,
untilDate: this.date ? this.date.getTime() : undefined
}, this.baseQuery, this.query)).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
this.existMore = true;
}
res(notes);
this.fetching = false;
this.$emit('loaded');
}, rej);
}));
},
more() {
if (!this.canFetchMore) return;
this.moreFetching = true;
const promise = this.$root.api(this.endpoint, Object.assign({
limit: fetchLimit + 1,
untilId: (this.$refs.timeline as any).tail().id
}, this.baseQuery, this.query));
promise.then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
} else {
this.existMore = false;
}
for (const n of notes) {
(this.$refs.timeline as any).append(n);
}
this.moreFetching = false;
});
return promise;
},
focus() {
(this.$refs.timeline as any).focus();
},
warp(date) {
this.date = date;
this.fetch();
}
}
});
@@ -186,20 +153,3 @@ export default Vue.extend({
margin 0 0 8px 0
</style>
<style lang="stylus" module>
.empty
display block
margin 0 auto
padding 32px
max-width 400px
text-align center
color #999
> [data-icon]
display block
margin-bottom 16px
font-size 3em
color #ccc
</style>

View File

@@ -1,29 +1,23 @@
<template>
<div class="mk-timeline">
<div class="pwbzawku">
<mk-post-form class="form" v-if="$store.state.settings.showPostFormOnTopOfTl"/>
<div class="main">
<header>
<span :data-active="src == 'home'" @click="src = 'home'"><fa icon="home"/> {{ $t('home') }}</span>
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline"><fa :icon="['far', 'comments']"/> {{ $t('local') }}</span>
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline"><fa icon="share-alt"/> {{ $t('hybrid') }}</span>
<span :data-active="src == 'global'" @click="src = 'global'" v-if="enableGlobalTimeline"><fa icon="globe"/> {{ $t('global') }}</span>
<span :data-active="src == 'tag'" @click="src = 'tag'" v-if="tagTl"><fa icon="hashtag"/> {{ tagTl.title }}</span>
<span :data-active="src == 'list'" @click="src = 'list'" v-if="list"><fa icon="list"/> {{ list.title }}</span>
<div class="buttons">
<button :data-active="src == 'mentions'" @click="src = 'mentions'" :title="$t('mentions')"><fa icon="at"/><i class="badge" v-if="$store.state.i.hasUnreadMentions"><fa icon="circle"/></i></button>
<button :data-active="src == 'messages'" @click="src = 'messages'" :title="$t('messages')"><fa :icon="['far', 'envelope']"/><i class="badge" v-if="$store.state.i.hasUnreadSpecifiedNotes"><fa icon="circle"/></i></button>
<button @click="chooseTag" :title="$t('hashtag')" ref="tagButton"><fa icon="hashtag"/></button>
<button @click="chooseList" :title="$t('list')" ref="listButton"><fa icon="list"/></button>
</div>
</header>
<x-core v-if="src == 'home'" ref="tl" key="home" src="home"/>
<x-core v-if="src == 'local'" ref="tl" key="local" src="local"/>
<x-core v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
<x-core v-if="src == 'global'" ref="tl" key="global" src="global"/>
<x-core v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
<x-core v-if="src == 'messages'" ref="tl" key="messages" src="messages"/>
<x-core v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/>
<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
<component :is="src == 'list' ? 'mk-user-list-timeline' : 'x-core'" ref="tl" v-bind="options">
<header class="zahtxcqi">
<span :data-active="src == 'home'" @click="src = 'home'"><fa icon="home"/> {{ $t('home') }}</span>
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline"><fa :icon="['far', 'comments']"/> {{ $t('local') }}</span>
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline"><fa icon="share-alt"/> {{ $t('hybrid') }}</span>
<span :data-active="src == 'global'" @click="src = 'global'" v-if="enableGlobalTimeline"><fa icon="globe"/> {{ $t('global') }}</span>
<span :data-active="src == 'tag'" @click="src = 'tag'" v-if="tagTl"><fa icon="hashtag"/> {{ tagTl.title }}</span>
<span :data-active="src == 'list'" @click="src = 'list'" v-if="list"><fa icon="list"/> {{ list.title }}</span>
<div class="buttons">
<button :data-active="src == 'mentions'" @click="src = 'mentions'" :title="$t('mentions')"><fa icon="at"/><i class="badge" v-if="$store.state.i.hasUnreadMentions"><fa icon="circle"/></i></button>
<button :data-active="src == 'messages'" @click="src = 'messages'" :title="$t('messages')"><fa :icon="['far', 'envelope']"/><i class="badge" v-if="$store.state.i.hasUnreadSpecifiedNotes"><fa icon="circle"/></i></button>
<button @click="chooseTag" :title="$t('hashtag')" ref="tagButton"><fa icon="hashtag"/></button>
<button @click="chooseList" :title="$t('list')" ref="listButton"><fa icon="list"/></button>
</div>
</header>
</component>
</div>
</div>
</template>
@@ -51,6 +45,16 @@ export default Vue.extend({
};
},
computed: {
options(): any {
return {
...(this.src == 'list' ? { list: this.list } : { src: this.src }),
...(this.src == 'tag' ? { tagTl: this.tagTl } : {}),
key: this.src == 'list' ? this.list.id : this.src
}
}
},
watch: {
src() {
this.saveSrc();
@@ -186,88 +190,82 @@ export default Vue.extend({
</script>
<style lang="stylus" scoped>
.mk-timeline
.pwbzawku
> .form
margin-bottom 16px
box-shadow var(--shadow)
border-radius var(--round)
> .main
background var(--face)
box-shadow var(--shadow)
border-radius var(--round)
overflow hidden
.zahtxcqi
padding 0 8px
z-index 10
background var(--faceHeader)
box-shadow 0 var(--lineWidth) var(--desktopTimelineHeaderShadow)
> header
padding 0 8px
z-index 10
background var(--faceHeader)
box-shadow 0 var(--lineWidth) var(--desktopTimelineHeaderShadow)
> .buttons
position absolute
z-index 2
top 0
right 0
padding-right 8px
> .buttons
position absolute
z-index 2
top 0
right 0
padding-right 8px
> button
padding 0 8px
font-size 0.9em
line-height 42px
color var(--faceTextButton)
> .badge
position absolute
top -4px
right 4px
font-size 10px
color var(--notificationIndicator)
&:hover
color var(--faceTextButtonHover)
&[data-active]
color var(--primary)
cursor default
&:before
content ""
display block
position absolute
bottom 0
left 0
width 100%
height 2px
background var(--primary)
> span
display inline-block
padding 0 10px
> button
padding 0 8px
font-size 0.9em
line-height 42px
font-size 12px
user-select none
color var(--faceTextButton)
> .badge
position absolute
top -4px
right 4px
font-size 10px
color var(--notificationIndicator)
&:hover
color var(--faceTextButtonHover)
&[data-active]
color var(--primary)
cursor default
font-weight bold
&:before
content ""
display block
position absolute
bottom 0
left -8px
width calc(100% + 16px)
left 0
width 100%
height 2px
background var(--primary)
&:not([data-active])
color var(--desktopTimelineSrc)
cursor pointer
> span
display inline-block
padding 0 10px
line-height 42px
font-size 12px
user-select none
&:hover
color var(--desktopTimelineSrcHover)
&[data-active]
color var(--primary)
cursor default
font-weight bold
&:before
content ""
display block
position absolute
bottom 0
left -8px
width calc(100% + 16px)
height 2px
background var(--primary)
&:not([data-active])
color var(--desktopTimelineSrc)
cursor pointer
&:hover
color var(--desktopTimelineSrcHover)
</style>

View File

@@ -10,7 +10,7 @@
</ui-container>
</div>
<x-photos :user="user"/>
<x-timeline class="timeline" ref="tl" :user="user"/>
<x-timeline ref="tl" :user="user"/>
</div>
</template>
@@ -51,7 +51,4 @@ export default Vue.extend({
> *
margin-bottom 16px
> .timeline
box-shadow var(--shadow)
</style>

View File

@@ -1,12 +1,12 @@
<template>
<div class="oh5y2r7l5lx8j6jj791ykeiwgihheguk">
<header>
<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>
<span :data-active="mode == 'my-posts'" @click="mode = 'my-posts'"><fa icon="user"/> {{ $t('my-posts') }}</span>
</header>
<mk-notes ref="timeline" :more="existMore ? more : null">
<div>
<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')">
<header slot="header" class="oh5y2r7l5lx8j6jj791ykeiwgihheguk">
<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>
<span :data-active="mode == 'my-posts'" @click="mode = 'my-posts'"><fa icon="user"/> {{ $t('my-posts') }}</span>
</header>
<p class="empty" slot="empty"><fa :icon="['far', 'comments']"/>{{ $t('empty') }}</p>
</mk-notes>
</div>
@@ -20,29 +20,47 @@ const fetchLimit = 10;
export default Vue.extend({
i18n: i18n('desktop/views/pages/user/user.timeline.vue'),
props: ['user'],
data() {
return {
fetching: true,
moreFetching: false,
existMore: false,
mode: 'default',
unreadCount: 0,
date: null
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',
untilId: cursor ? cursor : undefined
}).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
return {
notes: notes,
cursor: notes[notes.length - 1].id
};
} else {
return {
notes: notes,
cursor: null
};
}
})
};
},
watch: {
mode() {
this.fetch();
(this.$refs.timeline as any).reload();
}
},
mounted() {
document.addEventListener('keydown', this.onDocumentKeydown);
this.fetch(() => this.$emit('loaded'));
},
beforeDestroy() {
@@ -58,58 +76,9 @@ export default Vue.extend({
}
},
fetch(cb?) {
this.fetching = true;
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
this.$root.api('users/notes', {
userId: this.user.id,
limit: fetchLimit + 1,
untilDate: this.date ? this.date.getTime() : new Date().getTime() + 1000 * 86400 * 365,
includeReplies: this.mode == 'with-replies',
includeMyRenotes: this.mode != 'my-posts',
withFiles: this.mode == 'with-media'
}).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
this.existMore = true;
}
res(notes);
this.fetching = false;
if (cb) cb();
}, rej);
}));
},
more() {
this.moreFetching = true;
const promise = 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: new Date((this.$refs.timeline as any).tail().createdAt).getTime()
});
promise.then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
} else {
this.existMore = false;
}
for (const n of notes) {
(this.$refs.timeline as any).append(n);
}
this.moreFetching = false;
});
return promise;
},
warp(date) {
this.date = date;
this.fetch();
(this.$refs.timeline as any).reload();
}
}
});
@@ -117,59 +86,38 @@ export default Vue.extend({
<style lang="stylus" scoped>
.oh5y2r7l5lx8j6jj791ykeiwgihheguk
background var(--face)
border-radius var(--round)
overflow hidden
padding 0 8px
z-index 10
background var(--faceHeader)
box-shadow 0 1px var(--desktopTimelineHeaderShadow)
> header
padding 0 8px
z-index 10
background var(--faceHeader)
box-shadow 0 1px var(--desktopTimelineHeaderShadow)
> span
display inline-block
padding 0 10px
line-height 42px
font-size 12px
user-select none
> span
display inline-block
padding 0 10px
line-height 42px
font-size 12px
user-select none
&[data-active]
color var(--primary)
cursor default
font-weight bold
&[data-active]
color var(--primary)
cursor default
font-weight bold
&:before
content ""
display block
position absolute
bottom 0
left -8px
width calc(100% + 16px)
height 2px
background var(--primary)
&:not([data-active])
color var(--desktopTimelineSrc)
cursor pointer
&:hover
color var(--desktopTimelineSrcHover)
> .mk-notes
> .empty
display block
margin 0 auto
padding 32px
max-width 400px
text-align center
color var(--text)
> [data-icon]
&:before
content ""
display block
margin-bottom 16px
font-size 3em
color var(--faceHeaderText);
position absolute
bottom 0
left -8px
width calc(100% + 16px)
height 2px
background var(--primary)
&:not([data-active])
color var(--desktopTimelineSrc)
cursor pointer
&:hover
color var(--desktopTimelineSrcHover)
</style>