Skip to content

Commit 517fc22

Browse files
authored
Merge pull request #7 from takker99/merge-headless
Merge headless
2 parents 8d3c361 + 152c9b6 commit 517fc22

17 files changed

+1349
-11
lines changed

browser/mod.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export * from "./node.ts";
2+
export * from "./motion.ts";
3+
export * from "./edit.ts";
4+
export * from "./press.ts";
5+
export * from "./click.ts";
6+
export * from "./statusBar.ts";
7+
export * from "./caret.ts";
8+
export * from "./dom.ts";
9+
export * from "./openInTheSameTab.ts";
10+
export * from "./websocket/room.ts";
11+
export * from "./websocket/shortcuts.ts";
12+
export * from "./websocket/stream.ts";

browser/websocket/LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 takker
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

browser/websocket/_fetch.ts

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import {
2+
Change,
3+
CommitNotification,
4+
Delete,
5+
Pin,
6+
ProjectUpdatesStreamCommit,
7+
ProjectUpdatesStreamEvent,
8+
wrap,
9+
} from "../../deps/socket.ts";
10+
import { getPage } from "../../rest/pages.ts";
11+
export type {
12+
CommitNotification,
13+
ProjectUpdatesStreamCommit,
14+
ProjectUpdatesStreamEvent,
15+
};
16+
17+
export type RequestFunc = ReturnType<typeof wrap>["request"];
18+
export type PushCommitInit = {
19+
parentId: string;
20+
projectId: string;
21+
pageId: string;
22+
userId: string;
23+
};
24+
25+
export async function pushCommit(
26+
request: RequestFunc,
27+
changes: Change[] | [Delete] | [Pin],
28+
commitInit: PushCommitInit,
29+
) {
30+
if (changes.length === 0) return { commitId: commitInit.parentId };
31+
const res = await request("socket.io-request", {
32+
method: "commit",
33+
data: {
34+
kind: "page",
35+
...commitInit,
36+
changes,
37+
cursor: null,
38+
freeze: true,
39+
},
40+
});
41+
return res as { commitId: string };
42+
}
43+
44+
export async function pushWithRetry(
45+
request: RequestFunc,
46+
changes: Change[] | [Delete] | [Pin],
47+
{ project, title, retry = 3, parentId, ...commitInit }:
48+
& PushCommitInit
49+
& {
50+
project: string;
51+
title: string;
52+
retry?: number;
53+
},
54+
) {
55+
try {
56+
const res = await pushCommit(request, changes, {
57+
parentId,
58+
...commitInit,
59+
});
60+
parentId = res.commitId;
61+
} catch (_e) {
62+
console.log("Faild to push a commit. Retry after pulling new commits");
63+
for (let i = 0; i < retry; i++) {
64+
const { commitId } = await ensureEditablePage(project, title);
65+
parentId = commitId;
66+
try {
67+
const res = await pushCommit(request, changes, {
68+
parentId,
69+
...commitInit,
70+
});
71+
parentId = res.commitId;
72+
console.log("Success in retrying");
73+
break;
74+
} catch (_e) {
75+
continue;
76+
}
77+
}
78+
throw Error("Faild to retry pushing.");
79+
}
80+
return parentId;
81+
}
82+
83+
export async function ensureEditablePage(project: string, title: string) {
84+
const result = await getPage(project, title);
85+
86+
// TODO: 編集不可なページはStream購読だけ提供する
87+
if (!result.ok) {
88+
throw new Error(`You have no privilege of editing "/${project}/${title}".`);
89+
}
90+
return result.value;
91+
}

browser/websocket/applyCommit.ts

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { CommitNotification } from "../../deps/socket.ts";
2+
import type { Line } from "../../deps/scrapbox.ts";
3+
import { getUnixTimeFromId } from "./id.ts";
4+
5+
export interface ApplyCommitProp {
6+
/** changesの作成日時
7+
*
8+
* UnixTimeか、UnixTimeを含んだidを渡す。
9+
* 何も指定されなかったら、実行時の時刻を用いる
10+
*/
11+
updated?: number | string;
12+
/** user id */ userId: string;
13+
}
14+
15+
/** メタデータを含んだ行にcommitsを適用する
16+
*
17+
* @param lines commitsを適用する行
18+
* @param changes 適用するcommits
19+
*/
20+
export function applyCommit(
21+
lines: readonly Line[],
22+
changes: CommitNotification["changes"],
23+
{ updated, userId }: ApplyCommitProp,
24+
) {
25+
const newLines = [...lines];
26+
const getPos = (lineId: string) => {
27+
const position = newLines.findIndex(({ id }) => id === lineId);
28+
if (position < 0) {
29+
throw RangeError(`No line whose id is ${lineId} found.`);
30+
}
31+
return position;
32+
};
33+
34+
for (const change of changes) {
35+
if ("_insert" in change) {
36+
const created = getUnixTimeFromId(change.lines.id);
37+
const newLine = {
38+
text: change.lines.text,
39+
id: change.lines.id,
40+
userId,
41+
updated: created,
42+
created,
43+
};
44+
if (change._insert === "_end") {
45+
newLines.push(newLine);
46+
} else {
47+
newLines.splice(getPos(change._insert), 0, newLine);
48+
}
49+
} else if ("_update" in change) {
50+
const position = getPos(change._update);
51+
newLines[position].text = change.lines.text;
52+
newLines[position].updated = typeof updated === "string"
53+
? getUnixTimeFromId(updated)
54+
: updated ?? Math.round(new Date().getTime() / 1000);
55+
} else if ("_delete" in change) {
56+
newLines.splice(getPos(change._delete), 1);
57+
}
58+
}
59+
return newLines;
60+
}

browser/websocket/diff.test.ts

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/// <reference lib="deno.unstable" />
2+
import { Change, diff, ExtendedChange, toExtendedChanges } from "./diff.ts";
3+
import { assertEquals, assertStrictEquals } from "../../deps/testing.ts";
4+
5+
Deno.test("diff()", async (t) => {
6+
await t.step("check variables", async ({ step }) => {
7+
await step("return arguments", () => {
8+
assertEquals(diff("aaa", "bbbb").from, "aaa");
9+
assertEquals(diff("aaa", "bbbb").to, "bbbb");
10+
const left = ["aaa", "bbb", 111] as const;
11+
const right = ["ccc", "ddd", 222] as const;
12+
assertStrictEquals(diff(left, right).from, left);
13+
assertStrictEquals(diff(left, right).to, right);
14+
});
15+
});
16+
await t.step("string", async ({ step }) => {
17+
const diffData: [string, string, Change<string>[]][] = [
18+
["kitten", "sitting", [
19+
{ value: "s", type: "added" },
20+
{ value: "k", type: "deleted" },
21+
{ value: "i", type: "common" },
22+
{ value: "t", type: "common" },
23+
{ value: "t", type: "common" },
24+
{ value: "i", type: "added" },
25+
{ value: "e", type: "deleted" },
26+
{ value: "n", type: "common" },
27+
{ value: "g", type: "added" },
28+
]],
29+
["sitting", "kitten", [
30+
{ value: "s", type: "deleted" },
31+
{ value: "k", type: "added" },
32+
{ value: "i", type: "common" },
33+
{ value: "t", type: "common" },
34+
{ value: "t", type: "common" },
35+
{ value: "i", type: "deleted" },
36+
{ value: "e", type: "added" },
37+
{ value: "n", type: "common" },
38+
{ value: "g", type: "deleted" },
39+
]],
40+
];
41+
for (const [before, after, changes] of diffData) {
42+
await step(
43+
`${before}->${after}`,
44+
() => assertEquals([...diff(before, after).buildSES()], changes),
45+
);
46+
}
47+
});
48+
});
49+
50+
Deno.test("toExtendedChanges()", async (t) => {
51+
await t.step("only", async ({ step }) => {
52+
await step("only added", () => {
53+
const before: Change<string>[] = [
54+
{ value: "aaa", type: "added" },
55+
{ value: "bbb", type: "added" },
56+
{ value: "ccc", type: "added" },
57+
];
58+
const after: ExtendedChange<string>[] = [
59+
{ value: "aaa", type: "added" },
60+
{ value: "bbb", type: "added" },
61+
{ value: "ccc", type: "added" },
62+
];
63+
assertEquals([...toExtendedChanges(before)], after);
64+
});
65+
await step("only deleted", () => {
66+
const before: Change<string>[] = [
67+
{ value: "aaa", type: "deleted" },
68+
{ value: "bbb", type: "deleted" },
69+
{ value: "ccc", type: "deleted" },
70+
];
71+
const after: ExtendedChange<string>[] = [
72+
{ value: "aaa", type: "deleted" },
73+
{ value: "bbb", type: "deleted" },
74+
{ value: "ccc", type: "deleted" },
75+
];
76+
assertEquals([...toExtendedChanges(before)], after);
77+
});
78+
await step("only common", () => {
79+
const before: Change<string>[] = [
80+
{ value: "aaa", type: "common" },
81+
{ value: "bbb", type: "common" },
82+
{ value: "ccc", type: "common" },
83+
];
84+
const after: ExtendedChange<string>[] = [
85+
{ value: "aaa", type: "common" },
86+
{ value: "bbb", type: "common" },
87+
{ value: "ccc", type: "common" },
88+
];
89+
assertEquals([...toExtendedChanges(before)], after);
90+
});
91+
});
92+
93+
await t.step("mixed", async ({ step }) => {
94+
await step("added and deleted", () => {
95+
const before: Change<string>[] = [
96+
{ value: "111", type: "added" },
97+
{ value: "aaa", type: "added" },
98+
{ value: "bbb", type: "deleted" },
99+
{ value: "222", type: "added" },
100+
{ value: "eee", type: "added" },
101+
{ value: "fff", type: "deleted" },
102+
{ value: "ggg", type: "deleted" },
103+
{ value: "222", type: "added" },
104+
{ value: "eee", type: "added" },
105+
{ value: "ggg", type: "deleted" },
106+
{ value: "222", type: "added" },
107+
{ value: "fff", type: "deleted" },
108+
{ value: "ggg", type: "deleted" },
109+
{ value: "222", type: "added" },
110+
{ value: "eee", type: "added" },
111+
];
112+
const after: ExtendedChange<string>[] = [
113+
{ value: "111", oldValue: "bbb", type: "replaced" },
114+
{ value: "aaa", oldValue: "fff", type: "replaced" },
115+
{ value: "222", oldValue: "ggg", type: "replaced" },
116+
{ value: "eee", oldValue: "ggg", type: "replaced" },
117+
{ value: "222", oldValue: "fff", type: "replaced" },
118+
{ value: "eee", oldValue: "ggg", type: "replaced" },
119+
{ value: "222", type: "added" },
120+
{ value: "222", type: "added" },
121+
{ value: "eee", type: "added" },
122+
];
123+
assertEquals([...toExtendedChanges(before)], after);
124+
});
125+
await step("added and deleted and common", () => {
126+
const before: Change<string>[] = [
127+
{ value: "111", type: "added" },
128+
{ value: "aaa", type: "added" },
129+
{ value: "bbb", type: "deleted" },
130+
{ value: "ccc", type: "common" },
131+
{ value: "ddd", type: "common" },
132+
{ value: "222", type: "added" },
133+
{ value: "eee", type: "added" },
134+
{ value: "fff", type: "deleted" },
135+
{ value: "ggg", type: "deleted" },
136+
{ value: "ddd", type: "common" },
137+
{ value: "222", type: "added" },
138+
{ value: "eee", type: "added" },
139+
{ value: "ggg", type: "deleted" },
140+
{ value: "ddd", type: "common" },
141+
{ value: "222", type: "added" },
142+
{ value: "fff", type: "deleted" },
143+
{ value: "ggg", type: "deleted" },
144+
{ value: "ddd", type: "common" },
145+
{ value: "222", type: "added" },
146+
{ value: "eee", type: "added" },
147+
{ value: "ddd", type: "common" },
148+
{ value: "fff", type: "deleted" },
149+
{ value: "ggg", type: "deleted" },
150+
{ value: "ddd", type: "common" },
151+
{ value: "222", type: "added" },
152+
{ value: "eee", type: "added" },
153+
{ value: "fff", type: "deleted" },
154+
{ value: "ggg", type: "deleted" },
155+
{ value: "222", type: "added" },
156+
];
157+
const after: ExtendedChange<string>[] = [
158+
{ value: "111", oldValue: "bbb", type: "replaced" },
159+
{ value: "aaa", type: "added" },
160+
{ value: "ccc", type: "common" },
161+
{ value: "ddd", type: "common" },
162+
{ value: "222", oldValue: "fff", type: "replaced" },
163+
{ value: "eee", oldValue: "ggg", type: "replaced" },
164+
{ value: "ddd", type: "common" },
165+
{ value: "222", oldValue: "ggg", type: "replaced" },
166+
{ value: "eee", type: "added" },
167+
{ value: "ddd", type: "common" },
168+
{ value: "222", oldValue: "fff", type: "replaced" },
169+
{ value: "ggg", type: "deleted" },
170+
{ value: "ddd", type: "common" },
171+
{ value: "222", type: "added" },
172+
{ value: "eee", type: "added" },
173+
{ value: "ddd", type: "common" },
174+
{ value: "fff", type: "deleted" },
175+
{ value: "ggg", type: "deleted" },
176+
{ value: "ddd", type: "common" },
177+
{ value: "222", oldValue: "fff", type: "replaced" },
178+
{ value: "eee", oldValue: "ggg", type: "replaced" },
179+
{ value: "222", type: "added" },
180+
];
181+
assertEquals([...toExtendedChanges(before)], after);
182+
});
183+
});
184+
});

0 commit comments

Comments
 (0)