diff options
Diffstat (limited to 'src/client/scripts/paging.ts')
| -rw-r--r-- | src/client/scripts/paging.ts | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/src/client/scripts/paging.ts b/src/client/scripts/paging.ts new file mode 100644 index 0000000000..b24d705f15 --- /dev/null +++ b/src/client/scripts/paging.ts @@ -0,0 +1,134 @@ +import Vue from 'vue'; + +export default (opts) => ({ + data() { + return { + items: [], + offset: 0, + fetching: true, + moreFetching: false, + inited: false, + more: false + }; + }, + + computed: { + empty(): boolean { + return this.items.length == 0 && !this.fetching && this.inited; + }, + + error(): boolean { + return !this.fetching && !this.inited; + }, + }, + + watch: { + pagination() { + this.init(); + } + }, + + created() { + opts.displayLimit = opts.displayLimit || 30; + this.init(); + }, + + methods: { + isScrollTop() { + return window.scrollY <= 8; + }, + + updateItem(i, item) { + Vue.set((this as any).items, i, item); + }, + + reload() { + this.items = []; + this.init(); + }, + + async init() { + this.fetching = true; + if (opts.before) opts.before(this); + let params = typeof this.pagination.params === 'function' ? this.pagination.params(true) : this.pagination.params; + if (params && params.then) params = await params; + const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint; + await this.$root.api(endpoint, { + limit: this.pagination.noPaging ? (this.pagination.limit || 10) : (this.pagination.limit || 10) + 1, + ...params + }).then(x => { + if (!this.pagination.noPaging && (x.length === (this.pagination.limit || 10) + 1)) { + x.pop(); + this.items = x; + this.more = true; + } else { + this.items = x; + this.more = false; + } + this.offset = x.length; + this.inited = true; + this.fetching = false; + if (opts.after) opts.after(this, null); + }, e => { + this.fetching = false; + if (opts.after) opts.after(this, e); + }); + }, + + async fetchMore() { + if (!this.more || this.moreFetching || this.items.length === 0) return; + this.moreFetching = true; + let params = typeof this.pagination.params === 'function' ? this.pagination.params(false) : this.pagination.params; + if (params && params.then) params = await params; + const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint; + await this.$root.api(endpoint, { + limit: (this.pagination.limit || 10) + 1, + ...(this.pagination.offsetMode ? { + offset: this.offset, + } : { + untilId: this.items[this.items.length - 1].id, + }), + ...params + }).then(x => { + if (x.length === (this.pagination.limit || 10) + 1) { + x.pop(); + this.items = this.items.concat(x); + this.more = true; + } else { + this.items = this.items.concat(x); + this.more = false; + } + this.offset += x.length; + this.moreFetching = false; + }, e => { + this.moreFetching = false; + }); + }, + + prepend(item, silent = false) { + if (opts.onPrepend) { + const cancel = opts.onPrepend(this, item, silent); + if (cancel) return; + } + + // Prepend the item + this.items.unshift(item); + + if (this.isScrollTop()) { + // オーバーフローしたら古い投稿は捨てる + if (this.items.length >= opts.displayLimit) { + this.items = this.items.slice(0, opts.displayLimit); + this.more = true; + } + } + }, + + append(item) { + this.items.push(item); + }, + + remove(find) { + this.items = this.items.filter(x => !find(x)); + }, + } +}); |