summaryrefslogtreecommitdiff
path: root/src/client/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/scripts')
-rw-r--r--src/client/scripts/i18n.ts44
-rw-r--r--src/client/scripts/is-mobile.ts2
-rw-r--r--src/client/scripts/paging.ts92
-rw-r--r--src/client/scripts/scroll.ts8
-rw-r--r--src/client/scripts/unison-reload.ts10
5 files changed, 92 insertions, 64 deletions
diff --git a/src/client/scripts/i18n.ts b/src/client/scripts/i18n.ts
deleted file mode 100644
index d535e236bb..0000000000
--- a/src/client/scripts/i18n.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-// Notice: Service Workerでも使用します
-export class I18n<T extends Record<string, any>> {
- public locale: T;
-
- constructor(locale: T) {
- this.locale = locale;
-
- if (_DEV_) {
- console.log('i18n', this.locale);
- }
-
- //#region BIND
- this.t = this.t.bind(this);
- //#endregion
- }
-
- // string にしているのは、ドット区切りでのパス指定を許可するため
- // なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
- public t(key: string, args?: Record<string, any>): string {
- try {
- let str = key.split('.').reduce((o, i) => o[i], this.locale) as string;
-
- if (_DEV_) {
- if (!str.includes('{')) {
- console.warn(`i18n: '${key}' has no any arg. so ref prop directly instead of call this method.`);
- }
- }
-
- if (args) {
- for (const [k, v] of Object.entries(args)) {
- str = str.replace(`{${k}}`, v);
- }
- }
- return str;
- } catch (e) {
- if (_DEV_) {
- console.warn(`missing localization '${key}'`);
- return `⚠'${key}'⚠`;
- }
-
- return key;
- }
- }
-}
diff --git a/src/client/scripts/is-mobile.ts b/src/client/scripts/is-mobile.ts
new file mode 100644
index 0000000000..60cb59f91e
--- /dev/null
+++ b/src/client/scripts/is-mobile.ts
@@ -0,0 +1,2 @@
+const ua = navigator.userAgent.toLowerCase();
+export const isMobile = /mobile|iphone|ipad|android/.test(ua);
diff --git a/src/client/scripts/paging.ts b/src/client/scripts/paging.ts
index 3d9668f108..a8f122412c 100644
--- a/src/client/scripts/paging.ts
+++ b/src/client/scripts/paging.ts
@@ -1,9 +1,11 @@
import { markRaw } from 'vue';
import * as os from '@/os';
-import { onScrollTop, isTopVisible } from './scroll';
+import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from './scroll';
const SECOND_FETCH_LIMIT = 30;
+// reversed: items 配列の中身を逆順にする(新しい方が最後)
+
export default (opts) => ({
emits: ['queue'],
@@ -122,10 +124,8 @@ export default (opts) => ({
limit: SECOND_FETCH_LIMIT + 1,
...(this.pagination.offsetMode ? {
offset: this.offset,
- } : this.pagination.reversed ? {
- sinceId: this.items[0].id,
} : {
- untilId: this.items[this.items.length - 1].id,
+ untilId: this.pagination.reversed ? this.items[0].id : this.items[this.items.length - 1].id,
}),
}).then(items => {
for (const item of items) {
@@ -146,26 +146,78 @@ export default (opts) => ({
});
},
- prepend(item) {
- const isTop = this.isBackTop || (document.body.contains(this.$el) && isTopVisible(this.$el));
-
- if (isTop) {
- // Prepend the item
- this.items.unshift(item);
-
- // オーバーフローしたら古いアイテムは捨てる
- if (this.items.length >= opts.displayLimit) {
- this.items = this.items.slice(0, opts.displayLimit);
+ async fetchMoreFeature() {
+ if (!this.more || this.fetching || 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 os.api(endpoint, {
+ ...params,
+ limit: SECOND_FETCH_LIMIT + 1,
+ ...(this.pagination.offsetMode ? {
+ offset: this.offset,
+ } : {
+ sinceId: this.pagination.reversed ? this.items[0].id : this.items[this.items.length - 1].id,
+ }),
+ }).then(items => {
+ for (const item of items) {
+ markRaw(item);
+ }
+ if (items.length > SECOND_FETCH_LIMIT) {
+ items.pop();
+ this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items);
this.more = true;
+ } else {
+ this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items);
+ this.more = false;
+ }
+ this.offset += items.length;
+ this.moreFetching = false;
+ }, e => {
+ this.moreFetching = false;
+ });
+ },
+
+ prepend(item) {
+ if (this.pagination.reversed) {
+ const container = getScrollContainer(this.$el);
+ const pos = getScrollPosition(this.$el);
+ const viewHeight = container.clientHeight;
+ const height = container.scrollHeight;
+ const isBottom = (pos + viewHeight > height - 32);
+ if (isBottom) {
+ // オーバーフローしたら古いアイテムは捨てる
+ if (this.items.length >= opts.displayLimit) {
+ this.items = this.items.slice(-opts.displayLimit);
+ this.more = true;
+ }
+ } else {
+
}
+ this.items.push(item);
+ // TODO
} else {
- this.queue.push(item);
- onScrollTop(this.$el, () => {
- for (const item of this.queue) {
- this.prepend(item);
+ const isTop = this.isBackTop || (document.body.contains(this.$el) && isTopVisible(this.$el));
+
+ if (isTop) {
+ // Prepend the item
+ this.items.unshift(item);
+
+ // オーバーフローしたら古いアイテムは捨てる
+ if (this.items.length >= opts.displayLimit) {
+ this.items = this.items.slice(0, opts.displayLimit);
+ this.more = true;
}
- this.queue = [];
- });
+ } else {
+ this.queue.push(item);
+ onScrollTop(this.$el, () => {
+ for (const item of this.queue) {
+ this.prepend(item);
+ }
+ this.queue = [];
+ });
+ }
}
},
diff --git a/src/client/scripts/scroll.ts b/src/client/scripts/scroll.ts
index 18c3366891..bc6d1530c5 100644
--- a/src/client/scripts/scroll.ts
+++ b/src/client/scripts/scroll.ts
@@ -54,6 +54,14 @@ export function scroll(el: Element, top: number) {
}
}
+export function scrollToTop(el: Element) {
+ scroll(el, 0);
+}
+
+export function scrollToBottom(el: Element) {
+ scroll(el, 99999); // TODO: ちゃんと計算する
+}
+
export function isBottom(el: Element, asobi = 0) {
const container = getScrollContainer(el);
const current = container
diff --git a/src/client/scripts/unison-reload.ts b/src/client/scripts/unison-reload.ts
new file mode 100644
index 0000000000..92556aefaa
--- /dev/null
+++ b/src/client/scripts/unison-reload.ts
@@ -0,0 +1,10 @@
+// SafariがBroadcastChannel未実装なのでライブラリを使う
+import { BroadcastChannel } from 'broadcast-channel';
+
+export const reloadChannel = new BroadcastChannel<'reload'>('reload');
+
+// BroadcastChannelを用いて、クライアントが一斉にreloadするようにします。
+export function unisonReload() {
+ reloadChannel.postMessage('reload');
+ location.reload();
+}