Skip to content

Commit

Permalink
Merge pull request #215 from nishio/devin/1735797568-translate-japane…
Browse files Browse the repository at this point in the history
…se-comments

Devin/1735797568 translate japanese comments
  • Loading branch information
takker99 authored Jan 13, 2025
2 parents 8fffbb2 + 252ca2b commit c6561a0
Show file tree
Hide file tree
Showing 75 changed files with 2,306 additions and 777 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
local_test/
coverage/
docs/
129 changes: 129 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,132 @@
[![test](https://github.com/takker99/scrapbox-userscript-std/workflows/ci/badge.svg)](https://github.com/takker99/scrapbox-userscript-std/actions?query=workflow%3Aci)

UNOFFICIAL standard module for Scrapbox UserScript

## Getting Started

This library serves as an unofficial standard library for developing Scrapbox
userscripts. It provides a comprehensive set of utilities for interacting with
Scrapbox's features, including REST API operations, browser interactions, and
common utilities.

### Installation

1. Bundler Configuration This library is distributed through JSR (JavaScript
Registry) and requires a bundler configuration. Follow these steps:

a. Configure your bundler to use JSR:

- For esbuild: Add JSR to your import map
- For other bundlers: Refer to your bundler's JSR integration documentation

b. Import the library:

```typescript
// Import commonly used functions
import { getPage } from "jsr:@cosense/std/rest";
import { parseAbsoluteLink } from "jsr:@cosense/std";

// Import specific modules (recommended)
import { getLinks } from "jsr:@cosense/std/rest";
import { press } from "jsr:@cosense/std/browser/dom";
import { getLines } from "jsr:@cosense/std/browser/dom";
```

2. Module Organization The library is organized into the following main modules:

- `rest/`: API operations for Scrapbox REST endpoints
- Page operations
- Project management
- User authentication
- `browser/`: Browser-side operations
- DOM manipulation
- WebSocket communication
- Event handling
- Core utilities:
- `title`: Title parsing and formatting
- `parseAbsoluteLink`: External link analysis
- Additional helper functions

## Examples

### Basic Usage

1. Retrieving Page Information

```typescript
// Get page content and metadata
import { getPage } from "jsr:@cosense/std/rest";

const result = await getPage("projectName", "pageName");
if (result.ok) {
const page = result.val;
console.log("Page title:", page.title);
console.log("Page content:", page.lines.map((line) => line.text));
console.log("Page descriptions:", page.descriptions.join("\n"));
}
```

2. DOM Operations

```typescript
// Interact with the current page's content
import { getLines, press } from "jsr:@cosense/std/browser/dom";

// Get all lines from the current page
const lines = getLines();
console.log(lines.map((line) => line.text));

// Simulate keyboard input
await press("Enter"); // Add a new line
await press("Tab"); // Indent the line
```

3. External Link Analysis

```typescript
// Parse external links (YouTube, Spotify, etc.)
import { parseAbsoluteLink } from "jsr:@cosense/std";
import type { LinkNode } from "@progfay/scrapbox-parser";

// Create a link node with absolute path type
const link = {
type: "link" as const,
pathType: "absolute" as const,
href: "https://www.youtube.com/watch?v=xxxxx",
content: "",
raw: "[https://www.youtube.com/watch?v=xxxxx]",
} satisfies LinkNode & { pathType: "absolute" };

// Parse and handle different link types
const parsed = parseAbsoluteLink(link);
if (parsed?.type === "youtube") {
// Handle YouTube links
console.log("YouTube video ID:", parsed.href.split("v=")[1]);
const params = new URLSearchParams(parsed.href.split("?")[1]);
const start = params.get("t");
if (start) {
console.log("Video timestamp:", start);
}
} else if (parsed?.type === "spotify") {
// Handle Spotify links
const match = parsed.href.match(/spotify\.com\/track\/([^?]+)/);
if (match) {
console.log("Spotify track ID:", match[1]);
}
}
```

### Important Notes

- This library requires a bundler for use in userscripts
- Full TypeScript support with type definitions included
- Comprehensive error handling with type-safe responses
- For more examples and use cases, see the
[Examples](https://github.com/takker99/scrapbox-userscript-std/tree/main/examples)
directory

### Additional Resources

- [JSR Package Page](https://jsr.io/@cosense/std)
- [API Documentation](https://jsr.io/@cosense/std/doc)
- [GitHub Repository](https://github.com/takker99/scrapbox-userscript-std)
24 changes: 15 additions & 9 deletions browser/dom/_internal.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
/** 等値比較用に`AddEventListenerOptions`をencodeする */
/**
* Encodes {@linkcode AddEventListenerOptions} into a number for equality comparison.
* This function converts the options object into a single number where each bit
* represents a specific option (capture, once, passive).
*/
export const encode = (
options: AddEventListenerOptions | boolean | undefined,
): number => {
if (options === undefined) return 0;
if (typeof options === "boolean") return Number(options);
// 各フラグをビットにエンコードする
// Encode each flag into its corresponding bit position
return (
(options.capture ? 1 : 0) |
(options.once ? 2 : 0) |
(options.passive ? 4 : 0)
);
};
/** 等値比較用にencodeした`AddEventListenerOptions`をdecodeする
/**
* Decodes a number back into {@linkcode AddEventListenerOptions} object.
* Each bit in the encoded number represents a specific option:
*
* - `capture`: `0b001`
* - `once`: `0b010`
* - `passive`: `0b100`
* - `0`: `undefined`
* - `capture`: `0b001` (bit 0)
* - `once`: `0b010` (bit 1)
* - `passive`: `0b100` (bit 2)
* - `0`: returns `undefined`
*
* @param encoded `AddEventListenerOptions`をencodeした値
* @returns `AddEventListenerOptions`または`undefined`
* @param encoded The number containing encoded {@linkcode AddEventListenerOptions} flags
* @returns An {@linkcode AddEventListenerOptions} object or {@linkcode undefined} if encoded value is 0
*/
export const decode = (
encoded: number,
Expand Down
40 changes: 23 additions & 17 deletions browser/dom/cache.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
/** scrapbox.ioが管理しているcache storageから、最新のresponseを取得する
/** Retrieves the latest response from the cache storage managed by scrapbox.io
*
* ほぼ https://scrapbox.io/daiiz/ScrapboxでのServiceWorkerとCacheの活用#5d2efaffadf4e70000651173 のパクリ
* This function searches through the cache storage in reverse chronological order
* to find the most recent cached response for a given request.
*
* @param request このrequestに対応するresponseが欲しい
* @param options search paramsを無視したいときとかに使う
* @return cacheがあればそのresponseを、なければ`undefined`を返す
* > [!NOTE]
* > Implementation inspired by Scrapbox's ServiceWorker and Cache usage pattern.
* > For details, see the article "ServiceWorker and Cache Usage in Scrapbox" {@see https://scrapbox.io/daiiz/ScrapboxでのServiceWorkerとCacheの活用#5d2efaffadf4e70000651173}
*
* @param request - The {@linkcode Request} to find a cached response for
* @param options - {@linkcode CacheQueryOptions} (e.g., to ignore search params)
* @returns A {@linkcode Response} if found, otherwise {@linkcode undefined}
*/
export const findLatestCache = async (
request: Request,
Expand All @@ -19,10 +24,10 @@ export const findLatestCache = async (
}
};

/** scrapbox.ioが管理しているREST API系のcache storageにresponseを保存する
/** Saves a response to the REST API cache storage managed by scrapbox.io
*
* @param request このrequestに対応するresponseを保存する
* @param response 保存するresponse
* @param request The {@linkcode Request} to associate with the cached response
* @param response The {@linkcode Response} to cache
*/
export const saveApiCache = async (
request: Request,
Expand All @@ -38,27 +43,28 @@ export const generateCacheName = (date: Date): string =>
`${date.getDate()}`.padStart(2, "0")
}`;

/** prefetchを実行する
*
* prefetchしたデータは`"prefetch"`と`"api-yyyy-MM-dd"`に格納される
/** Executes prefetch operations for specified API URLs
*
* `"prefetch"`に格納されたデータは、次回のリクエストで返却されたときに削除される
* Prefetched data is stored in two locations:
* 1. `"prefetch"` cache - temporary storage, cleared after first use
* 2. `"api-yyyy-MM-dd"` cache - date-based persistent storage
*
* 回線が遅いときは例外を投げる
* > [!NOTE]
* > Throws an exception if the network connection is slow
*
* @param urls prefetchしたいAPIのURLのリスト
* @param urls List of API URLs to prefetch
*/
export const prefetch = (urls: (string | URL)[]): Promise<void> =>
postMessage({
title: "prefetch",
body: { urls: urls.map((url) => url.toString()) },
});

/** 指定したAPIのcacheの更新を依頼する
/** Requests a cache update for the specified API
*
* 更新は10秒ごとに1つずつ実行される
* Updates are processed one at a time with a 10-second interval between each update
*
* @param cacheしたいAPIのURL
* @param url The URL of the API to cache
*/
export const fetchApiCache = (url: string): Promise<void> =>
postMessage({ title: "fetchApiCache", body: { url } });
Expand Down
38 changes: 23 additions & 15 deletions browser/dom/caret.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import { textInput } from "./dom.ts";

/** editor上の位置情報 */
/** Position information within the editor
*
* @see {@linkcode Range} for selection range information
*/
export interface Position {
/** 行数 */ line: number;
/** 何文字目の後ろにいるか */ char: number;
/** Line number (1-based) */ line: number;
/** Character offset within the line (0-based) */ char: number;
}

/** 選択範囲を表すデータ
/** Represents a text selection range in the editor
*
* When no text is selected, {@linkcode start} and {@linkcode end} positions are the same (cursor position)
*
* 選択範囲がないときは、開始と終了が同じ位置になる
* @see {@linkcode Position} for position type details
*/
export interface Range {
/** 選択範囲の開始位置 */ start: Position;
/** 選択範囲の終了位置 */ end: Position;
/** Starting position of the selection */ start: Position;
/** Ending position of the selection */ end: Position;
}

/** #text-inputを構築しているReact Componentに含まれるカーソルの情報 */
/** Cursor information contained within the React Component that builds `#text-input` */
export interface CaretInfo {
/** カーソルの位置 */ position: Position;
/** 選択範囲中の文字列 */ selectedText: string;
/** 選択範囲の位置 */ selectionRange: Range;
/** Current cursor position */ position: Position;
/** Currently selected text */ selectedText: string;
/** Range of the current selection */ selectionRange: Range;
}

interface ReactFiber {
Expand All @@ -32,10 +37,13 @@ interface ReactFiber {
};
}

/** 現在のカーソルと選択範囲の位置情報を取得する
/** Retrieves the current cursor position and text selection information
*
* @return カーソルと選択範囲の情報
* @throws {Error} #text-inputとReact Componentの隠しpropertyが見つからなかった
* @returns A {@linkcode CaretPosition} containing cursor position and text selection information
* @throws {@linkcode Error} when:
* - `#text-input` element is not found
* - React Component's internal properties are not found
* @see {@linkcode CaretInfo} for return type details
*/
export const caret = (): CaretInfo => {
const textarea = textInput();
Expand All @@ -51,7 +59,7 @@ export const caret = (): CaretInfo => {
);
}

// @ts-ignore DOMを無理矢理objectとして扱っている
// @ts-ignore Forcefully treating DOM element as an object to access React internals
return (textarea[
reactKey
] as ReactFiber).return.return.stateNode.props;
Expand Down
8 changes: 4 additions & 4 deletions browser/dom/click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export const click = async (
element.dispatchEvent(new MouseEvent("mouseup", mouseOptions));
element.dispatchEvent(new MouseEvent("click", mouseOptions));

// ScrapboxのReactの処理が終わるまで少し待つ
// 待ち時間は感覚で決めた
// Wait for Scrapbox's React event handlers to complete
// Note: 10ms delay is determined empirically to ensure reliable event processing
await delay(10);
};

Expand Down Expand Up @@ -72,7 +72,7 @@ export const holdDown = async (
element.dispatchEvent(new TouchEvent("touchend", mouseOptions));
element.dispatchEvent(new MouseEvent("click", mouseOptions));

// ScrapboxのReactの処理が終わるまで少し待つ
// 待ち時間は感覚で決めた
// Wait for Scrapbox's React event handlers to complete
// Note: 10ms delay is determined empirically to ensure reliable event processing
await delay(10);
};
Loading

0 comments on commit c6561a0

Please sign in to comment.