Skip to content

Refactor changes #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// <reference lib="deno.unstable" />
import { diffToChanges } from "./patch.ts";
import { diffToChanges } from "./diffToChanges.ts";
import { assertEquals } from "../../deps/testing.ts";

Deno.test("diffToChanges()", async ({ step }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type Options = {
userId: string;
};
export function* diffToChanges(
left: Omit<Line, "userId" | "updated" | "created">[],
left: Pick<Line, "text" | "id">[],
right: string[],
{ userId }: Options,
): Generator<DeleteCommit | InsertCommit | UpdateCommit, void, unknown> {
Expand Down
136 changes: 136 additions & 0 deletions browser/websocket/makeChanges.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { diffToChanges } from "./diffToChanges.ts";
import type { Line } from "../../deps/scrapbox.ts";
import {
Block,
convertToBlock,
Node,
packRows,
parseToRows,
} from "../../deps/scrapbox.ts";
import type { Change } from "../../deps/socket.ts";
import { toTitleLc } from "../../title.ts";

export interface HeadData {
commitId: string;
persistent: boolean;
image: string | null;
linksLc: string[];
lines: Line[];
}
export interface Init {
userId: string;
head: HeadData;
}
export function makeChanges(
left: Pick<Line, "text" | "id">[],
right: string[],
{ userId, head }: Init,
) {
// 本文の差分
const changes: Change[] = [...diffToChanges(left, right, { userId })];

// titleの差分を入れる
// 空ページの場合もタイトル変更commitを入れる
if (left[0].text !== right[0] || !head.persistent) {
changes.push({ title: right[0] });
}

// descriptionsの差分を入れる
const leftDescriptions = left.slice(1, 6).map((line) => line.text);
const rightDescriptions = right.slice(1, 6);
if (leftDescriptions.join("") !== rightDescriptions.join("")) {
changes.push({ descriptions: rightDescriptions });
}

// リンクと画像の差分を入れる
const [linksLc, image] = findLinksAndImage(right.join("\n"));
if (
head.linksLc.length !== linksLc.length ||
!head.linksLc.every((link) => linksLc.includes(link))
) {
changes.push({ links: linksLc });
}
if (head.image !== image) {
changes.push({ image });
}

return changes;
}

/** テキストに含まれる全てのリンクと最初の画像を探す */
function findLinksAndImage(text: string): [string[], string | null] {
const rows = parseToRows(text);
const blocks = packRows(rows, { hasTitle: true }).flatMap((pack) => {
switch (pack.type) {
case "codeBlock":
case "title":
return [];
case "line":
case "table":
return [convertToBlock(pack)];
}
});

const linksLc = [] as string[];
let image: string | null = null;

const lookup = (node: Node) => {
switch (node.type) {
case "hashTag":
linksLc.push(toTitleLc(node.href));
return;
case "link": {
if (node.pathType !== "relative") return;
linksLc.push(toTitleLc(node.href));
return;
}
case "image":
case "strongImage": {
image ??= node.src.endsWith("/thumb/1000")
? node.src.replace(/\/thumb\/1000$/, "/raw")
: node.src;
return;
}
case "strong":
case "quote":
case "decoration": {
for (const n of node.nodes) {
lookup(n);
}
return;
}
default:
return;
}
};
for (const node of blocksToNodes(blocks)) {
lookup(node);
}

return [linksLc, image];
}

function* blocksToNodes(blocks: Iterable<Block>) {
for (const block of blocks) {
switch (block.type) {
case "codeBlock":
case "title":
continue;
case "line":
for (const node of block.nodes) {
yield node;
}
continue;
case "table": {
for (const row of block.cells) {
for (const nodes of row) {
for (const node of nodes) {
yield node;
}
}
}
continue;
}
}
}
}
95 changes: 40 additions & 55 deletions browser/websocket/room.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import {
Change,
CommitNotification,
socketIO,
wrap,
} from "../../deps/socket.ts";
import { CommitNotification, socketIO, wrap } from "../../deps/socket.ts";
import { getProjectId, getUserId } from "./id.ts";
import { diffToChanges } from "./patch.ts";
import { applyCommit } from "./applyCommit.ts";
import { toTitleLc } from "../../title.ts";
import { makeChanges } from "./makeChanges.ts";
import type { Line } from "../../deps/scrapbox.ts";
import { ensureEditablePage, pushCommit } from "./_fetch.ts";
export type { CommitNotification };
Expand Down Expand Up @@ -48,9 +44,13 @@ export async function joinPageRoom(
]);

// 接続したページの情報
let parentId = page.commitId;
let created = page.persistent;
let lines = page.lines;
let head = {
persistent: page.persistent,
lines: page.lines,
image: page.image,
commitId: page.commitId,
linksLc: page.links.map((link) => toTitleLc(link)),
};
const pageId = page.id;

const io = await socketIO();
Expand All @@ -63,55 +63,36 @@ export async function joinPageRoom(
// subscribe the latest commit
(async () => {
for await (const { id, changes } of response("commit")) {
parentId = id;
lines = applyCommit(lines, changes, { updated: id, userId });
head.commitId = id;
head.lines = applyCommit(head.lines, changes, { updated: id, userId });
}
})();

return {
patch: async (update: (before: Line[]) => string[] | Promise<string[]>) => {
const tryPush = async () => {
const pending = update(lines);
const newLines = pending instanceof Promise ? await pending : pending;
const changes: Change[] = [
...diffToChanges(lines, newLines, { userId }),
];

// 変更後のlinesを計算する
const changedLines = applyCommit(lines, changes, {
userId,
});
// タイトルの変更チェック
// 空ページの場合もタイトル変更commitを入れる
if (lines[0].text !== changedLines[0].text || !created) {
changes.push({ title: changedLines[0].text });
}
// サムネイルの変更チェック
const oldDescriptions = lines.slice(1, 6).map((line) => line.text);
const newDescriptions = changedLines.slice(1, 6).map((lines) =>
lines.text
);
if (oldDescriptions.join("\n") !== newDescriptions.join("\n")) {
changes.push({ descriptions: newDescriptions });
}

// pushする
const { commitId } = await pushCommit(request, changes, {
parentId,
projectId,
pageId,
userId,
});

// pushに成功したら、localにも変更を反映する
parentId = commitId;
created = true;
lines = changedLines;
};

for (let i = 0; i < 3; i++) {
try {
await tryPush();
const pending = update(head.lines);
const newLines = pending instanceof Promise ? await pending : pending;
const changes = makeChanges(head.lines, newLines, {
userId,
head,
});

const { commitId } = await pushCommit(request, changes, {
parentId: head.commitId,
projectId,
pageId,
userId,
});

// pushに成功したら、localにも変更を反映する
head.commitId = commitId;
head.persistent = true;
head.lines = applyCommit(head.lines, changes, {
updated: commitId,
userId,
});
break;
} catch (_e: unknown) {
if (i === 2) {
Expand All @@ -122,9 +103,13 @@ export async function joinPageRoom(
);
try {
const page = await ensureEditablePage(project, title);
parentId = page.commitId;
created = page.persistent;
lines = page.lines;
head = {
persistent: page.persistent,
lines: page.lines,
image: page.image,
commitId: page.commitId,
linksLc: page.links.map((link) => toTitleLc(link)),
};
} catch (e: unknown) {
throw e;
}
Expand Down
71 changes: 27 additions & 44 deletions browser/websocket/shortcuts.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Change, socketIO, wrap } from "../../deps/socket.ts";
import { socketIO, wrap } from "../../deps/socket.ts";
import { getProjectId, getUserId } from "./id.ts";
import { diffToChanges } from "./patch.ts";
import { applyCommit } from "./applyCommit.ts";
import { makeChanges } from "./makeChanges.ts";
import { pinNumber } from "./pin.ts";
import type { Line } from "../../deps/scrapbox.ts";
import { toTitleLc } from "../../title.ts";
import { ensureEditablePage, pushCommit, pushWithRetry } from "./_fetch.ts";

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

let persistent = page.persistent;
let lines = page.lines;
let parentId = page.commitId;
let head = {
persistent: page.persistent,
lines: page.lines,
image: page.image,
commitId: page.commitId,
linksLc: page.links.map((link) => toTitleLc(link)),
};
const pageId = page.id;

const io = await socketIO();
try {
const { request } = wrap(io);

const tryPush = async () => {
const pending = update(lines);
const newLines = pending instanceof Promise ? await pending : pending;
const changes: Change[] = [
...diffToChanges(lines, newLines, { userId }),
];

// 変更後のlinesを計算する
const changedLines = applyCommit(lines, changes, {
userId,
});
// タイトルの変更チェック
// 空ページの場合もタイトル変更commitを入れる
if (lines[0].text !== changedLines[0].text || !persistent) {
changes.push({ title: changedLines[0].text });
}
// サムネイルの変更チェック
const oldDescriptions = lines.slice(1, 6).map((line) => line.text);
const newDescriptions = changedLines.slice(1, 6).map((lines) =>
lines.text
);
if (oldDescriptions.join("\n") !== newDescriptions.join("\n")) {
changes.push({ descriptions: newDescriptions });
}

// pushする
await pushCommit(request, changes, {
parentId,
projectId,
pageId,
userId,
});
};

// 3回retryする
for (let i = 0; i < 3; i++) {
try {
await tryPush();
const pending = update(head.lines);
const newLines = pending instanceof Promise ? await pending : pending;
const changes = makeChanges(head.lines, newLines, { userId, head });

await pushCommit(request, changes, {
parentId: head.commitId,
projectId,
pageId,
userId,
});
break;
} catch (_e: unknown) {
if (i === 2) {
Expand All @@ -125,9 +104,13 @@ export async function patch(
);
try {
const page = await ensureEditablePage(project, title);
parentId = page.commitId;
persistent = page.persistent;
lines = page.lines;
head = {
persistent: page.persistent,
lines: page.lines,
image: page.image,
commitId: page.commitId,
linksLc: page.links.map((link) => toTitleLc(link)),
};
} catch (e: unknown) {
throw e;
}
Expand Down
1 change: 1 addition & 0 deletions deps/scrapbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export type {
Scrapbox,
} from "https://raw.githubusercontent.com/scrapbox-jp/types/0.0.8/mod.ts";
import type { Page } from "https://raw.githubusercontent.com/scrapbox-jp/types/0.0.8/mod.ts";
export * from "https://esm.sh/@progfay/[email protected]";
export type Line = Page["lines"][0];
4 changes: 2 additions & 2 deletions deps/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export type {
ProjectUpdatesStreamCommit,
ProjectUpdatesStreamEvent,
UpdateCommit,
} from "https://raw.githubusercontent.com/takker99/scrapbox-userscript-websocket/0.1.3/mod.ts";
} from "https://raw.githubusercontent.com/takker99/scrapbox-userscript-websocket/0.1.4/mod.ts";
export {
socketIO,
wrap,
} from "https://raw.githubusercontent.com/takker99/scrapbox-userscript-websocket/0.1.3/mod.ts";
} from "https://raw.githubusercontent.com/takker99/scrapbox-userscript-websocket/0.1.4/mod.ts";