scrapbox-selection-3
/emoji/warning.iconWIP
2021-07-08
18:57:59 バグってるぅ……takker.icon
調べる
まあバグの原因がすぐに分かって何より
2021-06-10
15:41:14 多分動く
バグ取りは一切していない
2021-06-08
実装
文字列の取得
選択範囲の位置の取得
行番号
座標計算をして取得する
列番号
文字列比較で取得する
選択範囲の開始/終了位置の列番号を使う
選択範囲内に改行を含まない場合に、位置を一意に決めることができない
改行コードを含んでいる場合は、1行に一つしか改行コードが含まれないので特定できる
改行コードを含んでいない文字列は、1行に複数回出現することがある
例えば「ああああああああ」のなかで「あ」だけ選択していた場合は、どの「あ」を選択しているのか全く判別できない
2021-07-08
テストコードtakker.icon
code:js
(async () => {
const {selection} = await import('/api/code/takker/scrapbox-selection-3/script.js');
let observer = new MutationObserver(() => {
const range = selection.range;
if (!range.start || !range.end) {
console.log(range);
return;
}
const {start, end} = range;
const texts = selection.text;
console.log({start, end, 'real texts': texts});
});
observer.observe(document.getElementsByClassName('cursor')?.0, {attributes: true}); })();
dependencies
code:script.js
import {scrapboxDOM} from '../scrapbox-dom-accessor/script.js';
import {position} from '../scrapbox-cursor-position-6/script.js';
import {
getDOMFromPoint,
getLineNo,
getIndex,
} from '../scrapbox-access-nodes@0.1.0/script.js';
class Selection {
constructor() {
this._cursorObserver = undefined;
// 先に.selectionの監視を開始する
this._selectionObserver = new MutationObserver(mutations => {
.some(element =>element.classList.contains('selections'))) {
this._selectMode = true;
this._recordSelectionEdge(); // 選択範囲の開始位置を記録しておく
}
.some(element =>element.classList.contains('selections'))) {
this._selectMode = false;
}
});
this._selectionObserver.observe(scrapboxDOM.editor, { childList: true });
this._cursorObserver = new MutationObserver(mutations => {
if (scrapboxDOM.cursor.style.display === 'none') return;
const {char, line} = position();
this._position = {index: getIndex(char), lineNo: getLineNo(line)};
});
this._cursorObserver.observe(scrapboxDOM.cursor, {attributes: true});
this._recordedEdge = {};
}
// 選択範囲が存在するかどうか
get exist() {
return this.text !== '';
}
// 選択範囲の開始位置と終了位置を返す
get range() {
// 順番を判定する
if (this._recordedEdge.lineNo > this._position.lineNo) {
return {
start: this._position,
end: this._recordedEdge,
};
}
if (this._recordedEdge.lineNo < this._position.lineNo) {
return {
start: this._recordedEdge,
end: this._position,
};
}
// 行が同じの場合は列で比較する
if (this._recordedEdge.index > this._position.index) {
return {
start: this._position,
end: this._recordedEdge,
};
}
if (this._recordedEdge.index < this._position.index) {
return {
start: this._recordedEdge,
end: this._position,
};
}
// 完全に位置が一致している場合
}
get text() {
if (!/mobile/i.test(navigator.userAgent)) return scrapboxDOM.textInput.value;
const range = selection.range;
if (!range.start || !range.end) return;
const {start, end} = range;
return [
scrapbox.Page.linesstart.lineNo.text.substring(start.index), ...scrapbox.Page.lines.map(line => line.text).slice(start.lineNo + 1, end.lineNo),
scrapbox.Page.linesend.lineNo.text.substring(0, end.index + 1), ].join('\n');
}
//cursorがない方の選択範囲の端を取得する
// この関数を呼び出すタイミングではまだ_cursorObserverが呼び出されていないので、独自にcursorの位置を取得する
_recordSelectionEdge() {
if (!this.exist) return undefined;
const selections = scrapboxDOM.selections?.getElementsByClassName('selection');
// 一瞬で選択範囲が消えるとselectionsがundefindeになる場合がある
if (!selections || selections?.length === 0) {
this._selectMode = false;
return undefined;
}
// 選択範囲の端の座標
const startRect = selections0.getBoundingClientRect(); const endRect = (selections2 ?? selections0).getBoundingClientRect(); // 選択範囲の端の文字を取得する
const start = getDOMFromPoint(startRect.left, startRect.top);
const end = getDOMFromPoint(endRect.right, endRect.bottom);
// 行数と列数を計算する
const startEdge = {
index: getIndex(start.char), // 末尾の場合はundefined
lineNo: getLineNo(start.line),
};
const endEdge = {
index: getIndex(end.char), // 末尾の場合はundefined
lineNo: getLineNo(end.line),
};
// cursorの位置を取得する
const temp = position();
const cursorEdge = {
index: getIndex(temp.char),
lineNo: getLineNo(temp.line),
};
// 選択範囲が一行のときは、選択範囲の端の座標から計算した位置を使う
// 1行かつ選択範囲の取得開始直後なら記法が展開されていることが保証されている
if (startEdge.lineNo === endEdge.lineNo) {
if (cursorEdge.index === startEdge.index
&& cursorEdge.lineNo === startEdge.lineNo) {
this._recordedEdge = endEdge;
} else {
this._recordedEdge = startEdge;
}
return;
}
// 選択範囲が複数行に渡るときは、記録しておいたカーソルの位置を用いる
// 記法が隠れている可能性があるので、選択範囲の座標から位置を計算できない
this._recordedEdge = this._position;
}
}
export const selection = new Selection();