mobileで簡単に文字装飾するPageMenu
https://gyazo.com/c585586170e85923cb4e8de0abc5dd0b
使い方
1. mod.jsを自分のprojectのどこかのコードブロックに貼り付ける 2. 以下のコードを自分のページに貼り付ける
./mod.jsは貼り付けたコードのURLに置き換える
code: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にしていた
本体
そんなに長いコードではない
code: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 et.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};
code:mod.ts
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/// <reference lib="dom" />
import { caret, insertText } from "../scrapbox-userscript-std/dom.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;
ボタンのスタイルをあてる
code: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で指定している
code: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を作る
code:mod.ts
if (!document.getElementById(id)) {
scrapbox.PageMenu.addMenu({
title: id,
image: dummyImage,
});
}
ボタンを入れる
以前のボタンは一旦全部消す
code: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を入れている
code: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}]],
},
];