Skip to content
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
154 changes: 154 additions & 0 deletions browser/dom/cursor.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/// <reference no-default-lib="true"/>
/// <reference lib="esnext"/>
/// <reference lib="dom" />

import { BaseStore } from "../../deps/scrapbox.ts";
import { Position } from "./position.ts";

export interface SetPositionOptions {
/** カーソルが画面外に移動したとき、カーソルが見える位置までページをスクロールするかどうか
*
* @default true
*/
scrollInView?: boolean;

/** カーソル移動イベントの発生箇所?
*
* コード内だと、"mouse"が指定されていた場合があった。詳細は不明
*/
source?: string;
}

/** カーソル操作クラス */
export declare class Cursor extends BaseStore {
constructor();

/** カーソルの位置を初期化し、editorからカーソルを外す */
clear(): void;

/** カーソルの位置を取得する */
getPosition(): Position;

/** カーソルが表示されているか調べる */
getVisible(): boolean;

/** カーソルを指定した位置に動かす */
setPosition(
position: Position,
option?: SetPositionOptions,
): void;

/** popup menuを表示する */
showEditPopupMenu(): void;

/** popup menuを消す */
hidePopupMenu(): void;

/** #text-inputにカーソルをfocusし、同時にカーソルを表示する */
focus(): void;

/** #text-inputからfocusを外す。カーソルの表示状態は変えない */
blur(): void;

/** カーソルの位置が行や列の外に出ていた場合に、存在する行と列の中に納める */
fixPosition(): void;

/** カーソルが行頭にいてかつ表示されていたら`true` */
isAtLineHead(): boolean;

/** カーソルが行末にいてかつ表示されていたら`true` */
isAtLineTail(): boolean;

/** カーソルを表示する
*
* #text-inputのfocus状態は変えない
*/
show(): void;

/** カーソルを非表示にする
*
* touch deviceの場合は、#text-inputからfocusを外す
*/
hide(): void;

/** カーソル操作コマンド
*
* | Command | Description |
* | ------ | ----------- |
* | go-up | 1行上に動かす |
* | go-down | 1行下に動かす |
* | go-left | 1文字左に動かす |
* | go-right | 1文字右に動かす |
* | go-forward | Emacs key bindingsで使われているコマンド。go-rightとほぼ同じ |
* | go-backward | Emacs key bindingsで使われているコマンド。go-leftとほぼ同じ |
* | go-top | タイトル行の行頭に飛ぶ |
* | go-bottom | 最後の行の行末に飛ぶ |
* | go-word-head | 1単語右に動かす |
* | go-word-tail | 1単語左に動かす |
* | go-line-head | 行頭に飛ぶ |
* | go-line-tail | 行末に飛ぶ |
* | go-pagedown | 1ページ分下の行に飛ぶ |
* | go-pageup | 1ページ分上の行に飛ぶ |
*/
goByAction(
action:
| "go-up"
| "go-down"
| "go-left"
| "go-right"
| "go-forward"
| "go-backward"
| "go-top"
| "go-bottom"
| "go-word-head"
| "go-word-tail"
| "go-line-head"
| "go-line-tail"
| "go-pagedown"
| "go-pageup",
): void;

/* `scrapbox.Page.lines`とほぼ同じ */
get lines(): unknown[];

/* `scrapbox.Project.pages`とほぼ同じ */
get pages(): unknown;

private goUp(): void;
private goPageUp(): void;
private goDown(): void;
private goPageDown(): void;
private getNextLineHead(): void;
private getPrevLineTail(): void;
private goBackward(init?: { scrollInView: boolean }): void;
private goForward(init?: { scrollInView: boolean }): void;
private goLeft(): void;
private goRight(): void;
/** タイトルの先頭文字に飛ぶ */
private goTop(): void;
/** 最後の行の末尾に飛ぶ */
private goBottom(): void;
private goWordHead(): void;
private getWordHead(): Position;
private goWordTail(): void;
private getWordTail(): Position;
/** インデントの後ろに飛ぶ
*
* インデントの後ろかインデントの中にいるときは行頭に飛ぶ
*/
private goLineHead(): void;
/** 行末に飛ぶ */
private goLineTail(): void;

private sync(): void;
private syncNow(): void;
private updateTemporalHorizontalPoint(): number;
private emitScroll(): void;
emitChange(event: string): void;

private data: Position;
private temporalHorizontalPoint: number;
private visible: boolean;
private visiblePopupMenu: boolean;
private focusTextarea: boolean;
}
13 changes: 13 additions & 0 deletions browser/dom/cursor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference no-default-lib="true"/>
/// <reference lib="esnext"/>
/// <reference lib="dom" />

import { takeStores } from "./stores.ts";
import { Cursor } from "./cursor.d.ts";

export const takeCursor = (): Cursor => {
for (const store of takeStores()) {
if ("goByAction" in store) return store;
}
throw Error('#text-input must has a "Cursor" store.');
};
2 changes: 2 additions & 0 deletions browser/dom/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export * from "./caret.ts";
export * from "./dom.ts";
export * from "./open.ts";
export * from "./cache.ts";
export * from "./cursor.ts";
export * from "./selection.ts";
5 changes: 5 additions & 0 deletions browser/dom/position.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** editor上の位置情報 */
export interface Position {
/** 行数 */ line: number;
/** 何文字目の後ろにいるか */ char: number;
}
70 changes: 70 additions & 0 deletions browser/dom/selection.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/// <reference no-default-lib="true"/>
/// <reference lib="esnext"/>
/// <reference lib="dom" />

import { BaseStore } from "../../deps/scrapbox.ts";
import { Position } from "./position.ts";

export interface Range {
start: Position;
end: Position;
}

export declare class Selection extends BaseStore {
constructor();

/** `scrapbox.Page.lines`とほぼ同じ */
get lines(): unknown[];

/** 現在の選択範囲を取得する */
getRange(init?: { normalizeOrder: boolean }): Range;

/** 選択範囲を変更する */
setRange(range: Range): void;

/** 選択を解除する */
clear(): void;

/** algorithmがよくわからない
*
* 何らかの条件に基づいて、startとendを入れ替えているのはわかる
*/
normalizeOrder(range: Range): Range;

/** 選択範囲の文字列を取得する */
getSelectedText(): string;

/** 選択範囲の描画上の高さを取得する */
getSelectionsHeight(): number;

/** 選択範囲の右上のy座標を取得する */
getSelectionTop(): number;

/** 全選択する */
selectAll(): void;

/** 与えられた選択範囲が空かどうか判定する
*
* defaultだと、このclassが持っている選択範囲を判定する
*/
hasSelection(range?: Range): boolean;

/** 与えられた範囲が1行だけ選択しているかどうか判定する
*
* defaultだと、このclassが持っている選択範囲を判定する
*/
hasSingleLineSelection(range?: Range): boolean;

/** 与えられた範囲が2行以上選択しているかどうか判定する
*
* defaultだと、このclassが持っている選択範囲を判定する
*/
hasMultiLinesSelection(range?: Range): boolean;

/** 全選択しているかどうか */
hasSelectionAll(): boolean;

private fixPosition(position: Position): void;
private fixRange(): void;
private data: Range;
}
13 changes: 13 additions & 0 deletions browser/dom/selection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference no-default-lib="true"/>
/// <reference lib="esnext"/>
/// <reference lib="dom" />

import { takeStores } from "./stores.ts";
import { Selection } from "./selection.d.ts";

export const takeSelection = (): Selection => {
for (const store of takeStores()) {
if ("hasSelection" in store) return store;
}
throw Error('#text-input must has a "Selection" store.');
};
37 changes: 37 additions & 0 deletions browser/dom/stores.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/// <reference no-default-lib="true"/>
/// <reference lib="esnext"/>
/// <reference lib="dom" />

import { textInput } from "./dom.ts";
import { Cursor } from "./cursor.d.ts";
import { Selection } from "./selection.d.ts";

export const takeStores = (): (Cursor | Selection)[] => {
const textarea = textInput();
if (!textarea) {
throw Error(`#text-input is not found.`);
}

const reactKey = Object.keys(textarea)
.find((key) => key.startsWith("__reactFiber"));
if (!reactKey) {
throw Error(
'#text-input must has the property whose name starts with "__reactFiber"',
);
}

// @ts-ignore DOMを無理矢理objectとして扱っている
return (textarea[
reactKey
] as ReactFiber).return.return.stateNode._stores;
};

interface ReactFiber {
return: {
return: {
stateNode: {
_stores: (Cursor | Selection)[];
};
};
};
}
3 changes: 3 additions & 0 deletions deps/scrapbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ export type {
export type {
Scrapbox,
} from "https://raw.githubusercontent.com/scrapbox-jp/types/0.3.3/userscript.ts";
export type {
BaseStore,
} from "https://raw.githubusercontent.com/scrapbox-jp/types/0.3.3/baseStore.ts";
export * from "https://esm.sh/@progfay/scrapbox-parser@7.2.0";