./mod.js
は貼り付けたコードのURLに置き換えるjsimport { mount } from "./mod.js";
if (/mobile/i.test(navigator.userAgent)) {
mount();
}
continue
を間違えて return
にしていたmod.jsvar 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;
}
a
と ul
の間に div.dropdown-backdrop
が挟まるので、 a + ul
ではなく a ~ ul
で指定しているmod.tsa#${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);
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`;
}
}
mod.tsexport 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}]]`,
},
];