Skip to content

Commit 5e4e7c5

Browse files
authored
Merge pull request #45 from takker99/feature-take
✨ caretとselectionを操作する内部クラスを無理やり引きずり出す
2 parents d3e1be6 + 6f84c31 commit 5e4e7c5

File tree

8 files changed

+297
-0
lines changed

8 files changed

+297
-0
lines changed

browser/dom/cursor.d.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/// <reference no-default-lib="true"/>
2+
/// <reference lib="esnext"/>
3+
/// <reference lib="dom" />
4+
5+
import { BaseStore } from "../../deps/scrapbox.ts";
6+
import { Position } from "./position.ts";
7+
8+
export interface SetPositionOptions {
9+
/** カーソルが画面外に移動したとき、カーソルが見える位置までページをスクロールするかどうか
10+
*
11+
* @default true
12+
*/
13+
scrollInView?: boolean;
14+
15+
/** カーソル移動イベントの発生箇所?
16+
*
17+
* コード内だと、"mouse"が指定されていた場合があった。詳細は不明
18+
*/
19+
source?: string;
20+
}
21+
22+
/** カーソル操作クラス */
23+
export declare class Cursor extends BaseStore {
24+
constructor();
25+
26+
/** カーソルの位置を初期化し、editorからカーソルを外す */
27+
clear(): void;
28+
29+
/** カーソルの位置を取得する */
30+
getPosition(): Position;
31+
32+
/** カーソルが表示されているか調べる */
33+
getVisible(): boolean;
34+
35+
/** カーソルを指定した位置に動かす */
36+
setPosition(
37+
position: Position,
38+
option?: SetPositionOptions,
39+
): void;
40+
41+
/** popup menuを表示する */
42+
showEditPopupMenu(): void;
43+
44+
/** popup menuを消す */
45+
hidePopupMenu(): void;
46+
47+
/** #text-inputにカーソルをfocusし、同時にカーソルを表示する */
48+
focus(): void;
49+
50+
/** #text-inputからfocusを外す。カーソルの表示状態は変えない */
51+
blur(): void;
52+
53+
/** カーソルの位置が行や列の外に出ていた場合に、存在する行と列の中に納める */
54+
fixPosition(): void;
55+
56+
/** カーソルが行頭にいてかつ表示されていたら`true` */
57+
isAtLineHead(): boolean;
58+
59+
/** カーソルが行末にいてかつ表示されていたら`true` */
60+
isAtLineTail(): boolean;
61+
62+
/** カーソルを表示する
63+
*
64+
* #text-inputのfocus状態は変えない
65+
*/
66+
show(): void;
67+
68+
/** カーソルを非表示にする
69+
*
70+
* touch deviceの場合は、#text-inputからfocusを外す
71+
*/
72+
hide(): void;
73+
74+
/** カーソル操作コマンド
75+
*
76+
* | Command | Description |
77+
* | ------ | ----------- |
78+
* | go-up | 1行上に動かす |
79+
* | go-down | 1行下に動かす |
80+
* | go-left | 1文字左に動かす |
81+
* | go-right | 1文字右に動かす |
82+
* | go-forward | Emacs key bindingsで使われているコマンド。go-rightとほぼ同じ |
83+
* | go-backward | Emacs key bindingsで使われているコマンド。go-leftとほぼ同じ |
84+
* | go-top | タイトル行の行頭に飛ぶ |
85+
* | go-bottom | 最後の行の行末に飛ぶ |
86+
* | go-word-head | 1単語右に動かす |
87+
* | go-word-tail | 1単語左に動かす |
88+
* | go-line-head | 行頭に飛ぶ |
89+
* | go-line-tail | 行末に飛ぶ |
90+
* | go-pagedown | 1ページ分下の行に飛ぶ |
91+
* | go-pageup | 1ページ分上の行に飛ぶ |
92+
*/
93+
goByAction(
94+
action:
95+
| "go-up"
96+
| "go-down"
97+
| "go-left"
98+
| "go-right"
99+
| "go-forward"
100+
| "go-backward"
101+
| "go-top"
102+
| "go-bottom"
103+
| "go-word-head"
104+
| "go-word-tail"
105+
| "go-line-head"
106+
| "go-line-tail"
107+
| "go-pagedown"
108+
| "go-pageup",
109+
): void;
110+
111+
/* `scrapbox.Page.lines`とほぼ同じ */
112+
get lines(): unknown[];
113+
114+
/* `scrapbox.Project.pages`とほぼ同じ */
115+
get pages(): unknown;
116+
117+
private goUp(): void;
118+
private goPageUp(): void;
119+
private goDown(): void;
120+
private goPageDown(): void;
121+
private getNextLineHead(): void;
122+
private getPrevLineTail(): void;
123+
private goBackward(init?: { scrollInView: boolean }): void;
124+
private goForward(init?: { scrollInView: boolean }): void;
125+
private goLeft(): void;
126+
private goRight(): void;
127+
/** タイトルの先頭文字に飛ぶ */
128+
private goTop(): void;
129+
/** 最後の行の末尾に飛ぶ */
130+
private goBottom(): void;
131+
private goWordHead(): void;
132+
private getWordHead(): Position;
133+
private goWordTail(): void;
134+
private getWordTail(): Position;
135+
/** インデントの後ろに飛ぶ
136+
*
137+
* インデントの後ろかインデントの中にいるときは行頭に飛ぶ
138+
*/
139+
private goLineHead(): void;
140+
/** 行末に飛ぶ */
141+
private goLineTail(): void;
142+
143+
private sync(): void;
144+
private syncNow(): void;
145+
private updateTemporalHorizontalPoint(): number;
146+
private emitScroll(): void;
147+
emitChange(event: string): void;
148+
149+
private data: Position;
150+
private temporalHorizontalPoint: number;
151+
private visible: boolean;
152+
private visiblePopupMenu: boolean;
153+
private focusTextarea: boolean;
154+
}

browser/dom/cursor.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference no-default-lib="true"/>
2+
/// <reference lib="esnext"/>
3+
/// <reference lib="dom" />
4+
5+
import { takeStores } from "./stores.ts";
6+
import { Cursor } from "./cursor.d.ts";
7+
8+
export const takeCursor = (): Cursor => {
9+
for (const store of takeStores()) {
10+
if ("goByAction" in store) return store;
11+
}
12+
throw Error('#text-input must has a "Cursor" store.');
13+
};

browser/dom/mod.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ export * from "./caret.ts";
88
export * from "./dom.ts";
99
export * from "./open.ts";
1010
export * from "./cache.ts";
11+
export * from "./cursor.ts";
12+
export * from "./selection.ts";

browser/dom/position.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** editor上の位置情報 */
2+
export interface Position {
3+
/** 行数 */ line: number;
4+
/** 何文字目の後ろにいるか */ char: number;
5+
}

browser/dom/selection.d.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/// <reference no-default-lib="true"/>
2+
/// <reference lib="esnext"/>
3+
/// <reference lib="dom" />
4+
5+
import { BaseStore } from "../../deps/scrapbox.ts";
6+
import { Position } from "./position.ts";
7+
8+
export interface Range {
9+
start: Position;
10+
end: Position;
11+
}
12+
13+
export declare class Selection extends BaseStore {
14+
constructor();
15+
16+
/** `scrapbox.Page.lines`とほぼ同じ */
17+
get lines(): unknown[];
18+
19+
/** 現在の選択範囲を取得する */
20+
getRange(init?: { normalizeOrder: boolean }): Range;
21+
22+
/** 選択範囲を変更する */
23+
setRange(range: Range): void;
24+
25+
/** 選択を解除する */
26+
clear(): void;
27+
28+
/** algorithmがよくわからない
29+
*
30+
* 何らかの条件に基づいて、startとendを入れ替えているのはわかる
31+
*/
32+
normalizeOrder(range: Range): Range;
33+
34+
/** 選択範囲の文字列を取得する */
35+
getSelectedText(): string;
36+
37+
/** 選択範囲の描画上の高さを取得する */
38+
getSelectionsHeight(): number;
39+
40+
/** 選択範囲の右上のy座標を取得する */
41+
getSelectionTop(): number;
42+
43+
/** 全選択する */
44+
selectAll(): void;
45+
46+
/** 与えられた選択範囲が空かどうか判定する
47+
*
48+
* defaultだと、このclassが持っている選択範囲を判定する
49+
*/
50+
hasSelection(range?: Range): boolean;
51+
52+
/** 与えられた範囲が1行だけ選択しているかどうか判定する
53+
*
54+
* defaultだと、このclassが持っている選択範囲を判定する
55+
*/
56+
hasSingleLineSelection(range?: Range): boolean;
57+
58+
/** 与えられた範囲が2行以上選択しているかどうか判定する
59+
*
60+
* defaultだと、このclassが持っている選択範囲を判定する
61+
*/
62+
hasMultiLinesSelection(range?: Range): boolean;
63+
64+
/** 全選択しているかどうか */
65+
hasSelectionAll(): boolean;
66+
67+
private fixPosition(position: Position): void;
68+
private fixRange(): void;
69+
private data: Range;
70+
}

browser/dom/selection.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference no-default-lib="true"/>
2+
/// <reference lib="esnext"/>
3+
/// <reference lib="dom" />
4+
5+
import { takeStores } from "./stores.ts";
6+
import { Selection } from "./selection.d.ts";
7+
8+
export const takeSelection = (): Selection => {
9+
for (const store of takeStores()) {
10+
if ("hasSelection" in store) return store;
11+
}
12+
throw Error('#text-input must has a "Selection" store.');
13+
};

browser/dom/stores.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/// <reference no-default-lib="true"/>
2+
/// <reference lib="esnext"/>
3+
/// <reference lib="dom" />
4+
5+
import { textInput } from "./dom.ts";
6+
import { Cursor } from "./cursor.d.ts";
7+
import { Selection } from "./selection.d.ts";
8+
9+
export const takeStores = (): (Cursor | Selection)[] => {
10+
const textarea = textInput();
11+
if (!textarea) {
12+
throw Error(`#text-input is not found.`);
13+
}
14+
15+
const reactKey = Object.keys(textarea)
16+
.find((key) => key.startsWith("__reactFiber"));
17+
if (!reactKey) {
18+
throw Error(
19+
'#text-input must has the property whose name starts with "__reactFiber"',
20+
);
21+
}
22+
23+
// @ts-ignore DOMを無理矢理objectとして扱っている
24+
return (textarea[
25+
reactKey
26+
] as ReactFiber).return.return.stateNode._stores;
27+
};
28+
29+
interface ReactFiber {
30+
return: {
31+
return: {
32+
stateNode: {
33+
_stores: (Cursor | Selection)[];
34+
};
35+
};
36+
};
37+
}

deps/scrapbox.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,7 @@ export type {
2525
export type {
2626
Scrapbox,
2727
} from "https://raw.githubusercontent.com/scrapbox-jp/types/0.3.3/userscript.ts";
28+
export type {
29+
BaseStore,
30+
} from "https://raw.githubusercontent.com/scrapbox-jp/types/0.3.3/baseStore.ts";
2831
export * from "https://esm.sh/@progfay/[email protected]";

0 commit comments

Comments
 (0)