-
Notifications
You must be signed in to change notification settings - Fork 1
/
storage.ts
122 lines (101 loc) · 3.41 KB
/
storage.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { Line, StringLc } from "./deps/scrapbox.ts";
import { ID } from "./id.ts";
import { produce } from "./deps/immer.ts";
export interface Bubble {
/** project name */
project: string;
/** page titleLc */
titleLc: StringLc;
/** サムネイル */
image: string | null;
/** サムネイル本文 */
descriptions: string[];
/** ページ本文
*
* 1行目にタイトルを入れる
*/
lines: Line[];
/** ページが存在すれば`true`*/
exists: boolean;
/** ページの更新日時 */
updated: number;
/** 内部リンク記法による逆リンクのリスト
*
* 未計算のときは`undefined`になる
*/
linked?: StringLc[];
/** `linked`が不正確な可能性がある場合は`true` */
isLinkedCorrect: boolean;
/** 外部リンク記法による逆リンクのリスト
*
* 未計算のときは`undefined`になる
*/
projectLinked?: ID[];
}
export type BubbleStorage = Map<ID, Bubble>;
/** 指定したリンクが空リンクかどうか判定する */
export const isEmptyLink = (
bubbles: Iterable<Bubble | undefined>,
): boolean => {
let linked = 0;
for (const bubble of bubbles) {
if (!bubble) continue;
// 中身があるなら空リンクでない
if (bubble.exists) return false;
linked += (bubble.linked?.length ?? 0) +
(bubble.projectLinked?.length ?? 0);
// 2つ以上のページから参照されているなら空リンクでない
if (linked > 1) return false;
}
return linked < 2;
};
/** bubbleを更新する
*
* update rule
*
* - 基本的に、updatedが大きい方を採用する
* - linesは、更新日時にかかわらずdummyでないほうを採用する
* - linkedとprojectLinkedは、undefinedでないほうを採用する
*
* 更新がなければ以前のobjectをそのまま返し、更新があれば新しいobjectで返す
*/
export const update = (
prev: Bubble | undefined,
current: Readonly<Bubble>,
): Bubble | undefined =>
produce<Bubble | undefined>(prev, (draft) => {
if (!draft) return current;
if (draft.updated < current.updated) {
// 更新日時が新しければ、そちらを採用する
// linked, projectLinked, linesのみ別途判定する
const { lines, linked, projectLinked, ...rest } = current;
Object.assign(draft, rest);
if (!isDummy(current)) draft.lines = lines;
if (linked) draft.linked ??= linked;
if (projectLinked) draft.projectLinked ??= projectLinked;
return;
}
// `updated`が変化していない場合、変更されている可能性のあるpropertiesは
// - `lines`
// - `linked`
// - `projectLinked`
// に限られる
// 本物の本文がやってきたら、そちらを採用する
if (isDummy(draft) && !isDummy(current)) {
draft.lines = current.lines;
}
// linkedは正確に取得したデータを優先する
if (current.linked) {
if (current.isLinkedCorrect) {
draft.linked = current.linked;
} else if (
!draft.isLinkedCorrect &&
(draft.linked?.length ?? 0) <= current.linked.length
) {
draft.linked = current.linked;
}
}
if (current.projectLinked) draft.projectLinked = current.projectLinked;
});
/** linesがdescriptionからでっち上げられたデータかどうか判定する */
const isDummy = (bubble: Bubble): boolean => bubble.lines[0].id === "dummy";