script.jsimport {scrapboxDOM} from '/api/code/takker/scrapbox-dom-accessor/script.js';
import {getCharBorder} from '/api/code/takker/scrapbox-position/script.js';
import {CursorObserver} from '/api/code/takker/scrapbox-cursor-observer/script.js';
import {cursor} from '/api/code/takker/scrapbox-cursor-position-3/script.js';
import {char as c} from '/api/code/takker/scrapbox-char-info/script.js';
import {line as l} from '/api/code/takker/scrapbox-line-info-2/script.js';
// 左右の文字のDOMの間の番号を取得する
const borderIndex = ({left,right}) => right ? c(right).index : left ? c(left).index + 1 : undefined;
class Selection {
constructor() {
this._cursorObserver = undefined;
// 先に.selectionの監視を開始する
this._selectionObserver = new MutationObserver(mutations => {
if (mutations.flatMap(mutation => [...mutation.addedNodes])
.some(element =>element.classList.contains('selections'))) {
this._selectMode = true;
this._recordSelectionEdge(); // 選択範囲の開始位置を記録しておく
}
if (mutations.flatMap(mutation => [...mutation.removedNodes])
.some(element =>element.classList.contains('selections'))) {
this._selectMode = false;
}
});
this._selectionObserver.observe(scrapboxDOM.editor, { childList: true });
this._cursorObserver = new CursorObserver();
this._cursorObserver.start();
this._recordedEdge = {};
}
// 選択範囲が存在するかどうか
get exist() {
return this._selectMode;
}
// 選択範囲の開始位置と終了位置を返す
get range() {
if (!this.exist) return {message: '[range@scrapbox-selection-2] No range found.'};
// 現在のcursorの位置
//console.log({cursorEdge: this._cursorObserver, recordedEdge: this._recordedEdge});
// 順番を判定する
if (this._recordedEdge.lineNo > this._cursorObserver.lineNo) {
return {
start: this._cursorObserver,
end: this._recordedEdge,
};
}
if (this._recordedEdge.lineNo < this._cursorObserver.lineNo) {
return {
start: this._recordedEdge,
end: this._cursorObserver,
};
}
// 行が同じの場合は列で比較する
if (this._recordedEdge.index > this._cursorObserver.index) {
return {
start: this._cursorObserver,
end: this._recordedEdge,
};
}
if (this._recordedEdge.index < this._cursorObserver.index) {
return {
start: this._recordedEdge,
end: this._cursorObserver,
};
}
// 完全に位置が一致している場合
return {message: '[range@scrapbox-selection-2] the start position is the same as the end one.'};
}
get text() {
const {start, end, message} = this.range;
if (message) {
console.log(message);
return [];
}
const texts = scrapbox.Page.lines.map(line => line.text);
/*console.log('[text@scrapbox-selection-2] %o', {start,end})
console.log('[text@scrapbox-selection-2] %o', {
start: {
left: texts[start.lineNo].split('')[start.index - 1],
right: texts[start.lineNo].split('')[start.index],
line: texts[start.lineNo],
},
end: {
left: texts[end.lineNo].split('')[end.index - 1],
right: texts[end.lineNo].split('')[end.index],
line: texts[end.lineNo],
},
});*/
// 選択範囲が一行に収まっているとき
if (start.lineNo === end.lineNo) {
return [texts[start.lineNo].substring(start.index, end.index)];
}
// 選択範囲が複数行にまたがるとき
return [
texts[start.lineNo].substring(start.index),
...texts.slice(start.lineNo + 1, 1 + end.lineNo - 1),
texts[end.lineNo].substring(0, end.index),
];
}
//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 = selections[0].getBoundingClientRect();
const endRect = (selections[2] ?? selections[0]).getBoundingClientRect();
// 選択範囲の端の文字を取得する
const start = getCharBorder({x: startRect.left, y: startRect.top});
const end = getCharBorder({x: endRect.right, y: endRect.bottom});
console.log('[scrapbox-selection-2] the selection edges: %o',{start, end});
// 行数と列数を計算する
const startEdge = {
index: borderIndex(start),
lineNo: l(start.line).index,
};
const endEdge = {
index: borderIndex(end),
lineNo: l(end.line).index,
};
// cursorの位置を取得する
const temp = cursor();
const cursorEdge = {
index: temp.index,
lineNo: l(temp.line).index,
};
// 選択範囲が一行のときは、選択範囲の端の座標から計算した位置を使う
// 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 = {
index: this._cursorObserver.index,
lineNo: this._cursorObserver.lineNo,
};
}
}
export const selection = new Selection();