generated at
mobileで簡単に文字装飾するPageMenu
mobile版scrapboxで簡単に太字斜体などの文字装飾記法を入力するためのボタンをPageMenuに配置するUserScript


使い方
1. mod.jsを自分のprojectのどこかのコードブロックに貼り付ける
コードはこのcomplieボタンから取得してもいい
2. 以下のコードを自分のページに貼り付ける
./mod.js は貼り付けたコードのURLに置き換える
js
import { mount } from "./mod.js"; if (/mobile/i.test(navigator.userAgent)) { mount(); }
PCでも使いたいときは、↑のif文を外す

テスト
スマホでPage Menuから太字にできる
打ち消し線も引ける

2022-03-03
20:09:09 continue を間違えて return にしていた

本体
これでbundleしたもの
そんなに長いコードではない
mod.js
var f=(e,t)=>{if(!(e instanceof HTMLTextAreaElement))throw new TypeError(`"${t}" must be HTMLTextAreaElement but actual is "${e}"`)};var o=()=>{let e=document.getElementById("text-input");if(!!e)return f(e,"textarea#text-input"),e};var l=e=>new Promise(t=>setTimeout(()=>t(),e));function m(){let e=o();if(!e)throw Error("#text-input is not found.");let t=Object.keys(e).find(n=>n.startsWith("__reactFiber"));if(!t)throw Error('div.cursor must has the property whose name starts with "__reactFiber"');return e[t].return.return.stateNode.props}async function g(e){let t=o();if(!t)throw Error("#text-input is not ditected.");t.focus(),t.value=e;let n=new InputEvent("input",{bubbles:!0});t.dispatchEvent(n),await l(1)}var M="mobile-decorate-page-menu",x="/assets/img/favicon/apple-touch-icon.png";function Ge(e){let t=(e?.id??M).replaceAll(" ","_"),n=e?.decorates??H,h=`head style[data-userscript-name="${t}"]`;document.querySelector(h)?.remove?.();let r=document.createElement("style");r.dataset.userscriptName=t,r.textContent=`a#${t}.tool-btn:hover { text-decoration: none; } a#${t}.tool-btn::before { position: absolute; content: "\\f591"; font: 900 20px/46px "Font Awesome 5 Free"; } a#${t}.tool-btn img { opacity: 0; } a#${t}.tool-btn ~ ul a::before { position: absolute; font-family: "Font Awesome 5 Free"; font-weight: 900; } a#${t}.tool-btn ~ ul img { opacity: 0; margin-right: 0; }`,document.head.append(r),document.getElementById(t)||scrapbox.PageMenu.addMenu({title:t,image:x}),scrapbox.PageMenu(t).removeAllItems();let i=0;for(let{title:E,titleStyle:d,icon:s,onClick:y}of n)i++,scrapbox.PageMenu(t).addItem({title:E,...s?{image:x}:{},onClick:async()=>{let{selectedText:c}=m();if(c==="")return;let a=y(c),u=a instanceof Promise?await a:a;u!==void 0&&u!==c&&await g(u)}}),d&&(r.textContent+=`a#${t}.tool-btn ~ ul li:nth-of-type(${i}) a { ${d} } `),!!s&&(r.textContent+=`a#${t}.tool-btn ~ ul li:nth-of-type(${i}) a::before { content:"${s}"; } `)}var H=[{title:"Strong",titleStyle:"font-weight: bold;",icon:"\\f032",onClick:e=>`[* ${e}]`},{title:"Italic",titleStyle:"font-style: italic;",icon:"\\f033",onClick:e=>`[/ ${e}]`},{title:"Strike",titleStyle:"text-decoration-line: line-through;",icon:"\\f0cc",onClick:e=>`[- ${e}]`},{title:"Underline",titleStyle:"text-decoration-line: underline;",icon:"\\f0cd",onClick:e=>`[_ ${e}]`},{title:"Marker",titleStyle:"font-weight: bold;",icon:"\\f5a1",onClick:e=>`[[${e}]]`}];export{H as defaultDecorates,Ge as mount};


mod.ts
/// <reference no-default-lib="true" /> /// <reference lib="esnext" /> /// <reference lib="dom" /> import { caret, insertText } from "../scrapbox-userscript-std/dom.ts"; import type { Scrapbox } from "https://raw.githubusercontent.com/scrapbox-jp/types/0.0.8/mod.ts"; declare const scrapbox: Scrapbox; const userscriptId = "mobile-decorate-page-menu"; const dummyImage = "/assets/img/favicon/apple-touch-icon.png"; export interface Init { id?: string; decorates?: Decorate[]; } export interface Decorate { title: string; titleStyle?: string; icon?: string; onClick: ( text: string, ) => (Promise<string | undefined> | string | undefined); } export function mount(init?: Init) { const id = (init?.id ?? userscriptId).replaceAll(" ", "_"); const decorates = init?.decorates ?? defaultDecorates;

ボタンのスタイルをあてる
mod.ts
const selector = `head style[data-userscript-name="${id}"]`; document.querySelector(selector)?.remove?.(); const style = document.createElement("style"); style.dataset.userscriptName = id; style.textContent = `a#${id}.tool-btn:hover { text-decoration: none; } a#${id}.tool-btn::before { position: absolute; content: "\\f591"; font: 900 20px/46px "Font Awesome 5 Free"; } a#${id}.tool-btn img { opacity: 0; }

mobileだと a ul の間に div.dropdown-backdrop が挟まるので、 a + ul ではなく a ~ ul で指定している
mod.ts
a#${id}.tool-btn ~ ul a::before { position: absolute; font-family: "Font Awesome 5 Free"; font-weight: 900; } a#${id}.tool-btn ~ ul img { opacity: 0; margin-right: 0; }`; document.head.append(style);

Page Menuを作る
mod.ts
if (!document.getElementById(id)) { scrapbox.PageMenu.addMenu({ title: id, image: dummyImage, }); }

ボタンを入れる
以前のボタンは一旦全部消す
mod.ts
scrapbox.PageMenu(id).removeAllItems(); let counter = 0; for (const { title, titleStyle, icon, onClick } of decorates) { counter++; scrapbox.PageMenu(id).addItem({ title, ...(icon ? { image: dummyImage } : {}), onClick: async () => { const { selectedText } = caret(); if (selectedText === "") return; const result = onClick(selectedText); const text = result instanceof Promise ? await result : result; if (text === undefined) return; if (text === selectedText) return; await insertText(text); }, }); if (titleStyle) { style.textContent += `a#${id}.tool-btn ~ ul li:nth-of-type(${counter}) a { ${titleStyle} }\n`; } if (!icon) continue; style.textContent += `a#${id}.tool-btn ~ ul li:nth-of-type(${counter}) a::before { content:"${icon}"; }\n`; } }

既定の設定
太字, 斜体, 打ち消し線, 下線, markerを入れている
mod.ts
export const defaultDecorates: Decorate[] = [ { title: "Strong", titleStyle: "font-weight: bold;", icon: "\\f032", onClick: (text) => `[* ${text}]`, }, { title: "Italic", titleStyle: "font-style: italic;", icon: "\\f033", onClick: (text) => `[/ ${text}]`, }, { title: "Strike", titleStyle: "text-decoration-line: line-through;", icon: "\\f0cc", onClick: (text) => `[- ${text}]`, }, { title: "Underline", titleStyle: "text-decoration-line: underline;", icon: "\\f0cd", onClick: (text) => `[_ ${text}]`, }, { title: "Marker", titleStyle: "font-weight: bold;", icon: "\\f5a1", onClick: (text) => `[[${text}]]`, }, ];

#2022-03-03 20:09:33
#2022-02-20 02:59:06