Skip to content

Commit 41deacb

Browse files
authored
Merge pull request #9 from takker99/refactor-changes
Refactor changes
2 parents 5936f83 + 06c2c5c commit 41deacb

File tree

7 files changed

+208
-103
lines changed

7 files changed

+208
-103
lines changed

browser/websocket/patch.test.ts renamed to browser/websocket/diffToChanges.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// <reference lib="deno.unstable" />
2-
import { diffToChanges } from "./patch.ts";
2+
import { diffToChanges } from "./diffToChanges.ts";
33
import { assertEquals } from "../../deps/testing.ts";
44

55
Deno.test("diffToChanges()", async ({ step }) => {

browser/websocket/patch.ts renamed to browser/websocket/diffToChanges.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type Options = {
1111
userId: string;
1212
};
1313
export function* diffToChanges(
14-
left: Omit<Line, "userId" | "updated" | "created">[],
14+
left: Pick<Line, "text" | "id">[],
1515
right: string[],
1616
{ userId }: Options,
1717
): Generator<DeleteCommit | InsertCommit | UpdateCommit, void, unknown> {

browser/websocket/makeChanges.ts

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { diffToChanges } from "./diffToChanges.ts";
2+
import type { Line } from "../../deps/scrapbox.ts";
3+
import {
4+
Block,
5+
convertToBlock,
6+
Node,
7+
packRows,
8+
parseToRows,
9+
} from "../../deps/scrapbox.ts";
10+
import type { Change } from "../../deps/socket.ts";
11+
import { toTitleLc } from "../../title.ts";
12+
13+
export interface HeadData {
14+
commitId: string;
15+
persistent: boolean;
16+
image: string | null;
17+
linksLc: string[];
18+
lines: Line[];
19+
}
20+
export interface Init {
21+
userId: string;
22+
head: HeadData;
23+
}
24+
export function makeChanges(
25+
left: Pick<Line, "text" | "id">[],
26+
right: string[],
27+
{ userId, head }: Init,
28+
) {
29+
// 本文の差分
30+
const changes: Change[] = [...diffToChanges(left, right, { userId })];
31+
32+
// titleの差分を入れる
33+
// 空ページの場合もタイトル変更commitを入れる
34+
if (left[0].text !== right[0] || !head.persistent) {
35+
changes.push({ title: right[0] });
36+
}
37+
38+
// descriptionsの差分を入れる
39+
const leftDescriptions = left.slice(1, 6).map((line) => line.text);
40+
const rightDescriptions = right.slice(1, 6);
41+
if (leftDescriptions.join("") !== rightDescriptions.join("")) {
42+
changes.push({ descriptions: rightDescriptions });
43+
}
44+
45+
// リンクと画像の差分を入れる
46+
const [linksLc, image] = findLinksAndImage(right.join("\n"));
47+
if (
48+
head.linksLc.length !== linksLc.length ||
49+
!head.linksLc.every((link) => linksLc.includes(link))
50+
) {
51+
changes.push({ links: linksLc });
52+
}
53+
if (head.image !== image) {
54+
changes.push({ image });
55+
}
56+
57+
return changes;
58+
}
59+
60+
/** テキストに含まれる全てのリンクと最初の画像を探す */
61+
function findLinksAndImage(text: string): [string[], string | null] {
62+
const rows = parseToRows(text);
63+
const blocks = packRows(rows, { hasTitle: true }).flatMap((pack) => {
64+
switch (pack.type) {
65+
case "codeBlock":
66+
case "title":
67+
return [];
68+
case "line":
69+
case "table":
70+
return [convertToBlock(pack)];
71+
}
72+
});
73+
74+
const linksLc = [] as string[];
75+
let image: string | null = null;
76+
77+
const lookup = (node: Node) => {
78+
switch (node.type) {
79+
case "hashTag":
80+
linksLc.push(toTitleLc(node.href));
81+
return;
82+
case "link": {
83+
if (node.pathType !== "relative") return;
84+
linksLc.push(toTitleLc(node.href));
85+
return;
86+
}
87+
case "image":
88+
case "strongImage": {
89+
image ??= node.src.endsWith("/thumb/1000")
90+
? node.src.replace(/\/thumb\/1000$/, "/raw")
91+
: node.src;
92+
return;
93+
}
94+
case "strong":
95+
case "quote":
96+
case "decoration": {
97+
for (const n of node.nodes) {
98+
lookup(n);
99+
}
100+
return;
101+
}
102+
default:
103+
return;
104+
}
105+
};
106+
for (const node of blocksToNodes(blocks)) {
107+
lookup(node);
108+
}
109+
110+
return [linksLc, image];
111+
}
112+
113+
function* blocksToNodes(blocks: Iterable<Block>) {
114+
for (const block of blocks) {
115+
switch (block.type) {
116+
case "codeBlock":
117+
case "title":
118+
continue;
119+
case "line":
120+
for (const node of block.nodes) {
121+
yield node;
122+
}
123+
continue;
124+
case "table": {
125+
for (const row of block.cells) {
126+
for (const nodes of row) {
127+
for (const node of nodes) {
128+
yield node;
129+
}
130+
}
131+
}
132+
continue;
133+
}
134+
}
135+
}
136+
}

browser/websocket/room.ts

+40-55
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
import {
2-
Change,
3-
CommitNotification,
4-
socketIO,
5-
wrap,
6-
} from "../../deps/socket.ts";
1+
import { CommitNotification, socketIO, wrap } from "../../deps/socket.ts";
72
import { getProjectId, getUserId } from "./id.ts";
8-
import { diffToChanges } from "./patch.ts";
93
import { applyCommit } from "./applyCommit.ts";
4+
import { toTitleLc } from "../../title.ts";
5+
import { makeChanges } from "./makeChanges.ts";
106
import type { Line } from "../../deps/scrapbox.ts";
117
import { ensureEditablePage, pushCommit } from "./_fetch.ts";
128
export type { CommitNotification };
@@ -48,9 +44,13 @@ export async function joinPageRoom(
4844
]);
4945

5046
// 接続したページの情報
51-
let parentId = page.commitId;
52-
let created = page.persistent;
53-
let lines = page.lines;
47+
let head = {
48+
persistent: page.persistent,
49+
lines: page.lines,
50+
image: page.image,
51+
commitId: page.commitId,
52+
linksLc: page.links.map((link) => toTitleLc(link)),
53+
};
5454
const pageId = page.id;
5555

5656
const io = await socketIO();
@@ -63,55 +63,36 @@ export async function joinPageRoom(
6363
// subscribe the latest commit
6464
(async () => {
6565
for await (const { id, changes } of response("commit")) {
66-
parentId = id;
67-
lines = applyCommit(lines, changes, { updated: id, userId });
66+
head.commitId = id;
67+
head.lines = applyCommit(head.lines, changes, { updated: id, userId });
6868
}
6969
})();
7070

7171
return {
7272
patch: async (update: (before: Line[]) => string[] | Promise<string[]>) => {
73-
const tryPush = async () => {
74-
const pending = update(lines);
75-
const newLines = pending instanceof Promise ? await pending : pending;
76-
const changes: Change[] = [
77-
...diffToChanges(lines, newLines, { userId }),
78-
];
79-
80-
// 変更後のlinesを計算する
81-
const changedLines = applyCommit(lines, changes, {
82-
userId,
83-
});
84-
// タイトルの変更チェック
85-
// 空ページの場合もタイトル変更commitを入れる
86-
if (lines[0].text !== changedLines[0].text || !created) {
87-
changes.push({ title: changedLines[0].text });
88-
}
89-
// サムネイルの変更チェック
90-
const oldDescriptions = lines.slice(1, 6).map((line) => line.text);
91-
const newDescriptions = changedLines.slice(1, 6).map((lines) =>
92-
lines.text
93-
);
94-
if (oldDescriptions.join("\n") !== newDescriptions.join("\n")) {
95-
changes.push({ descriptions: newDescriptions });
96-
}
97-
98-
// pushする
99-
const { commitId } = await pushCommit(request, changes, {
100-
parentId,
101-
projectId,
102-
pageId,
103-
userId,
104-
});
105-
106-
// pushに成功したら、localにも変更を反映する
107-
parentId = commitId;
108-
created = true;
109-
lines = changedLines;
110-
};
111-
11273
for (let i = 0; i < 3; i++) {
11374
try {
114-
await tryPush();
75+
const pending = update(head.lines);
76+
const newLines = pending instanceof Promise ? await pending : pending;
77+
const changes = makeChanges(head.lines, newLines, {
78+
userId,
79+
head,
80+
});
81+
82+
const { commitId } = await pushCommit(request, changes, {
83+
parentId: head.commitId,
84+
projectId,
85+
pageId,
86+
userId,
87+
});
88+
89+
// pushに成功したら、localにも変更を反映する
90+
head.commitId = commitId;
91+
head.persistent = true;
92+
head.lines = applyCommit(head.lines, changes, {
93+
updated: commitId,
94+
userId,
95+
});
11596
break;
11697
} catch (_e: unknown) {
11798
if (i === 2) {
@@ -122,9 +103,13 @@ export async function joinPageRoom(
122103
);
123104
try {
124105
const page = await ensureEditablePage(project, title);
125-
parentId = page.commitId;
126-
created = page.persistent;
127-
lines = page.lines;
106+
head = {
107+
persistent: page.persistent,
108+
lines: page.lines,
109+
image: page.image,
110+
commitId: page.commitId,
111+
linksLc: page.links.map((link) => toTitleLc(link)),
112+
};
128113
} catch (e: unknown) {
129114
throw e;
130115
}

browser/websocket/shortcuts.ts

+27-44
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { Change, socketIO, wrap } from "../../deps/socket.ts";
1+
import { socketIO, wrap } from "../../deps/socket.ts";
22
import { getProjectId, getUserId } from "./id.ts";
3-
import { diffToChanges } from "./patch.ts";
4-
import { applyCommit } from "./applyCommit.ts";
3+
import { makeChanges } from "./makeChanges.ts";
54
import { pinNumber } from "./pin.ts";
65
import type { Line } from "../../deps/scrapbox.ts";
6+
import { toTitleLc } from "../../title.ts";
77
import { ensureEditablePage, pushCommit, pushWithRetry } from "./_fetch.ts";
88

99
/** 指定したページを削除する
@@ -68,53 +68,32 @@ export async function patch(
6868
getUserId(),
6969
]);
7070

71-
let persistent = page.persistent;
72-
let lines = page.lines;
73-
let parentId = page.commitId;
71+
let head = {
72+
persistent: page.persistent,
73+
lines: page.lines,
74+
image: page.image,
75+
commitId: page.commitId,
76+
linksLc: page.links.map((link) => toTitleLc(link)),
77+
};
7478
const pageId = page.id;
7579

7680
const io = await socketIO();
7781
try {
7882
const { request } = wrap(io);
7983

80-
const tryPush = async () => {
81-
const pending = update(lines);
82-
const newLines = pending instanceof Promise ? await pending : pending;
83-
const changes: Change[] = [
84-
...diffToChanges(lines, newLines, { userId }),
85-
];
86-
87-
// 変更後のlinesを計算する
88-
const changedLines = applyCommit(lines, changes, {
89-
userId,
90-
});
91-
// タイトルの変更チェック
92-
// 空ページの場合もタイトル変更commitを入れる
93-
if (lines[0].text !== changedLines[0].text || !persistent) {
94-
changes.push({ title: changedLines[0].text });
95-
}
96-
// サムネイルの変更チェック
97-
const oldDescriptions = lines.slice(1, 6).map((line) => line.text);
98-
const newDescriptions = changedLines.slice(1, 6).map((lines) =>
99-
lines.text
100-
);
101-
if (oldDescriptions.join("\n") !== newDescriptions.join("\n")) {
102-
changes.push({ descriptions: newDescriptions });
103-
}
104-
105-
// pushする
106-
await pushCommit(request, changes, {
107-
parentId,
108-
projectId,
109-
pageId,
110-
userId,
111-
});
112-
};
113-
11484
// 3回retryする
11585
for (let i = 0; i < 3; i++) {
11686
try {
117-
await tryPush();
87+
const pending = update(head.lines);
88+
const newLines = pending instanceof Promise ? await pending : pending;
89+
const changes = makeChanges(head.lines, newLines, { userId, head });
90+
91+
await pushCommit(request, changes, {
92+
parentId: head.commitId,
93+
projectId,
94+
pageId,
95+
userId,
96+
});
11897
break;
11998
} catch (_e: unknown) {
12099
if (i === 2) {
@@ -125,9 +104,13 @@ export async function patch(
125104
);
126105
try {
127106
const page = await ensureEditablePage(project, title);
128-
parentId = page.commitId;
129-
persistent = page.persistent;
130-
lines = page.lines;
107+
head = {
108+
persistent: page.persistent,
109+
lines: page.lines,
110+
image: page.image,
111+
commitId: page.commitId,
112+
linksLc: page.links.map((link) => toTitleLc(link)),
113+
};
131114
} catch (e: unknown) {
132115
throw e;
133116
}

deps/scrapbox.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ export type {
1414
Scrapbox,
1515
} from "https://raw.githubusercontent.com/scrapbox-jp/types/0.0.8/mod.ts";
1616
import type { Page } from "https://raw.githubusercontent.com/scrapbox-jp/types/0.0.8/mod.ts";
17+
export * from "https://esm.sh/@progfay/[email protected]";
1718
export type Line = Page["lines"][0];

deps/socket.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ export type {
99
ProjectUpdatesStreamCommit,
1010
ProjectUpdatesStreamEvent,
1111
UpdateCommit,
12-
} from "https://raw.githubusercontent.com/takker99/scrapbox-userscript-websocket/0.1.3/mod.ts";
12+
} from "https://raw.githubusercontent.com/takker99/scrapbox-userscript-websocket/0.1.4/mod.ts";
1313
export {
1414
socketIO,
1515
wrap,
16-
} from "https://raw.githubusercontent.com/takker99/scrapbox-userscript-websocket/0.1.3/mod.ts";
16+
} from "https://raw.githubusercontent.com/takker99/scrapbox-userscript-websocket/0.1.4/mod.ts";

0 commit comments

Comments
 (0)