generated at
Scrapboxの選択範囲内の文字を取得してみる
PopupMenuを介さずに、Scrapboxの選択範囲内の文字列を取得するcodeを作ってみる
ScrapVim-lite-2とかで使えるようになる

方針
.selection のDOMRectを取得して、文字の範囲を計算する
.line と結びついていないから、座標から行を探す処理を書かないといけない
left top から選択範囲を含む行を探す
top は多分Element.getBoundingClientRect() top と同じ
left は行の左端からの距離になっている
div.line のDOMRect内に入るかどうかで判定する
これなら行の折返しがあっても認識できる
行内から文字を探す

実装
interface
getSelectionInfo()
現在の選択範囲の情報を取得する
return
ts
type Response = { start: { line: HTMLDivElement; char: HTMLSpanElement; }; end:{ line: HTMLDivElement; char: HTMLSpanElement; }; } | undefined;
getSelectedText()
選択中の文字列を取得する
return: string | undefined ;

処理の手順
選択範囲の存在を確認する
js
document.getElementsByClassName('selections').length !== 0;
selection 3つのDOMRectを取得する
開始位置と終了位置が分かればいいので、真ん中はいらない
座標から文字のDOMを取得する

js-script-button
06:52:43 実装テスト中
scrapbox-positionのバグを潰している感じ
変数ミスが結構ある
06:57:02 Element.getBoundingClientRect()だと、文字がずれる?
小数点のせいかな?
丸めて渡すか。
選択範囲の方ではなく、文字の方のDOMRectの小数点が原因
簡単には直せないな
こちらで一文字ずらすしかなさそうだ
07:00:38 他の文字のDOMRectも表示してみる
07:02:46 最後のselectionから得る座標値は左上ではなく右下だった
これ最後尾の次の文字を選択してしまうな
まあそこは後で処理方法考えよう
07:19:08 一文字ずれているから、最後尾は考えなくていい
逆に先頭文字をずらす
07:24:31 文字によってずれたりずれなかったりするから一筋縄では行かない
07:28:53 ずれる条件の仮説
文字を囲んでいる <span> のDOMRectの数値が整数のときはずれない
小数になるとずれる
対策
小数点以下を切り捨てる?
四捨五入だと繰り上がってしまってうまく動かない
07:06:20 popup menuが混ざってしまうのか
取り除く
07:14:09 code block名に選択範囲の開始/終了が入っていると正常にDOMを取得できない?
07:16:58 行頭文字よりも左側にある判定になってしまう
07:17:51 変数の書き換え忘れだった
07:51:41 ずれるの直した
08:08:42 選択範囲の文字を取得する getSelectedText のテスト
08:10:17 問題なさそう
cursorのない行の記法をコピーできないのが問題かな
textContentに出てこない
対処方法
記法をparseする
リンクとかなら出来る
コードブロックのファイル名は無理そう
切り取ってclipboardに書き込む
firefoxではclipboardの中身を読み込めないから無理
popup menuを使う
popup menuの存在に応じて切り替えれば出来ないこともない
挙動が一部変わってしまう
コードブロックのみ
focusある無しで文字列変化しないので問題なし
普通の文章
popup menuから取得できる
普通の文章+コードブロック
popup menuから取得できる
コードブロック+普通の文章
popup menuが現れない
普通の文章の部分を自力でparseする必要がある
scrabox.Page.lines を使う
これで出来るやtakker
記法が隠れている行の一部が選択範囲にあっても問題ない
spanの文字番号と実際の文字列の文字番号とが対応している
コードブロック先頭の場合も同様
08:35:59 とりあえずcodeは完成した
{type: 'LF'} のときの処理は書いていない
改行文字をどう扱うかでこの辺は変えるしか無いな
あとで別ページに書き出しておく
script.js
import {getChar} from '/api/code/takker/scrapbox-position/script.js'; import {scrapboxDOM} from '/api/code/takker/scrapbox-dom-accessor/script.js'; export function execute(){ window.getSelectionInfo = getSelectionInfo; window.getSelectedText = getSelectedText; } export function getSelectionInfo() { const selections = scrapboxDOM.selections?.getElementsByClassName('selection'); if (!selections) return undefined; const startRect = selections[0].getBoundingClientRect(); const endRect = (selections[2] ?? selections[0]).getBoundingClientRect(); const start = getChar(startRect); const end = getChar({x: endRect.right,y: endRect.bottom}); if (start.type !== 'DOM' | end.type !== 'DOM') return undefined; // 末尾の文字がずれているのを直す const index = parseInt(end.char.className.replace(/c-(\d+)/,'$1')); const endChar = index === 0 ? end.char : end.line.getElementsByClassName(`c-${index-1}`)?.[0]; return { start: {line: start.line, char: start.char}, end: {line: end.line, char: endChar}, }; } export function getSelectedText() { const selection = getSelectionInfo(); if (!selection) return undefined; const texts = scrapbox.Page.lines.map(line => line.text); const lines = [...scrapboxDOM.lines.children]; const start = { index: parseInt(selection.start.char.className.replace(/c-(\d+)/,'$1')), no: lines.indexOf(selection.start.line), }; const end = { index: parseInt(selection.end.char.className.replace(/c-(\d+)/,'$1')), no: lines.indexOf(selection.end.line), }; const selectedLines = texts.slice(start.no + 1, 1 + end.no - 1); console.log({start,end}); return [ texts[start.no].substring(start.index), ...selectedLines, texts[end.no].substring(0,1 + end.index), ]; }

#2020-12-11 02:28:01
#2020-12-10 01:39:17
#2020-11-25 19:18:39