generated at
omakase-links

導入方法
これを自分のページのscriptの先頭に追加する
js
import '/api/code/daiiz/omakase-links/script.js'

script.js
const loadTitles = ({useLinks}) => { fetch(`/api/pages/${scrapbox.Project.name}/search/titles`, { credentials: 'same-origin' }).then(res => { return res.json() }).then(data => { let titles = data.map(p => p.title) if (useLinks) { data.map(p => { p.links.map(l => {if (!titles.includes(l)) titles.push(l)}) }) } titles = titles.sort((a, b) => b.length - a.length) window.titles = titles console.log('omakase-links-✨: load titles, links') scrapbox.PopupMenu.addButton({ title: '[✨]', onClick: autoLink }) }) } // 既に記法になっているなどの理由で、置換すべきでない範囲を取得する const detectLocked = text => { const syntax = /\[*\[.+?\]\]*/g const locked = text.split('').map(c => false) let res while (res = syntax.exec(text)) { for (let i = 0; i < res[0].length; i++) locked[i + res.index] = true } return locked } const autoLink = text => { if (!window.titles || !text) return text const locked = detectLocked(text) const matched = text.split('').map(c => null) for (const title of window.titles) { const regexp = new RegExp(escapeTitle(title), 'gi') let res, found = false while (!found && (res = regexp.exec(text))) { const len = res[0].length const idx = res.index if (matched[idx] === null && !locked[idx]) { found = true const len = res[0].length for (let i = 0; i < len; i++) { if (i === 0 || i === len - 1) { let c = text[idx + i] if (i === 0) c = `[${c}` if (i === len - 1) c = `${c}]` matched[idx + i] = c } else { matched[idx + i] = text[idx + i] } } } } } // 無駄にテロメアがupdateされるのを防ぐ為、何も置換しない時は何も返さない if (matched.join('').length === 0) return // 配列matchedでnullの位置を、元のtextの文字に置き換える return matched.map((c, idx) => c === null ? text[idx] : c).join('') } const escapeTitle = title => { return title.replace(/[$-\/?[-^{|}]/g, '\\$&') } // 置換候補をリアルタイムで更新できないのがツライところ // しかし、毎回API呼んでると時間かかるので、ひとまず妥協 if (!window.titles) loadTitles({useLinks: true})