$ deno run --allow-run --reload https://scrapbox.io/api/code/Mijinko/リンクにアイコンを付けるCSSを生成するスクリプト/script.ts
script.ts/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
import { ChildStyle, Style, toCSSText, writeText } from "./deps.ts";
import {
generateExternalLinkStyle,
generateLinkIconStyle,
LinkInit,
} from "./stylesheetGenerator.ts";
/**
* CSSの生成に使用するオブジェクト
*/
const externalLinks: LinkInit[] = [{
// PDF
url: {
url: ".pdf",
selectorSyntax: "$=",
},
style: { "content": "'\\f1c1'" },
}, {
// GitHub
url: [
"https://github.com",
"https://raw.githubusercontent.com",
"https://gist.github.com",
],
style: { "content": "'\\f09b'" },
}, {
// Twitter
url: [
"https://twitter.com",
"https://pbs.twimg.com",
"https://video.twimg.com",
],
style: { "content": "'\\f099'" },
}, {
// Wikipedia
url: [
"https://ja.wikipedia.org",
"https://en.wikipedia.org",
],
style: { "content": "'\\f266'" },
}, {
// Amazon
url: [
"https://www.amazon.co.jp",
"https://amazon.co.jp",
],
style: { "content": "'\\f270'" },
}, {
// Discord
url: [
"https://discord.com",
"https://discord.app",
"https://discordapp.com",
"https://discordstatus.com/",
],
style: { "content": "'\\f392'" },
}, {
// Cloudflare
url: [
"https://www.cloudflare.com",
"https://cdnjs.cloudflare.com",
],
style: { "content": "'\\e07d'" },
}, {
// Steam
url: [
"https://store.steampowered.com",
"https://help.steampowered.com",
"https://steamcommunity.com",
"https://s.team",
],
style: { "content": "'\\f1b6'" },
}, {
// Steam外部サービス
url: [
// SteamDB
"https://steamdb.info",
"https://steamstat.us",
],
style: { "content": "'\\f3f6'" },
}, {
// Googleドライブ
url: [
"https://drive.google.com",
],
style: { "content": "'\\f3aa'" },
}];
const cssImport =
'@import "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css";';
const fontFamily: ChildStyle = {
"font-family": "'Font Awesome 6 Free', 'Font Awesome 6 Brands'",
};
const elStyle: Style = generateExternalLinkStyle(externalLinks, {
icon: fontFamily,
});
const liStyleBase: ChildStyle = {
"font-weight": 400,
"margin-right": "1px",
};
const liStyle: Style = generateLinkIconStyle(externalLinks, {
iconBase: { ...fontFamily, ...liStyleBase },
});
const extraStyle: Style = {
// 標準機能のTwitterアイコンを非表示にする
"i.fa-twitter::before": {
"content": "none",
},
".line .link .favicon.fa-twitter": {
"margin": 0,
},
};
await writeText(
cssImport + toCSSText({ ...elStyle, ...liStyle, ...extraStyle }),
);
stylesheetGenerator.ts/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
import { ChildStyle, Style } from "./deps.ts";
export type AttributeSelectorOperator = "^=" | "=" | "$=" | "*=" | "~=" | "|=";
export interface URLInit {
url: string;
/**
* 属性セレクタに使用する演算子
* 指定しない場合は`^=`が用いられる
*/
selectorSyntax: AttributeSelectorOperator;
}
export interface LinkInit {
url: string | URLInit | (string | URLInit)[];
style: ChildStyle;
}
/**
* 外部リンクを区別するUserCSSを生成する関数
*/
export function generateExternalLinkStyle(
/** アイコンを付与しないURLが入ったオブジェクト */
links: LinkInit[],
/** オプションで設定するスタイル(CSS) */
style?: {
/** 外部リンクを区別するアイコンに設定するスタイル */
icon?: ChildStyle;
},
): Style {
const urls = getURLFromLinkInit(links);
const selector =
".line span:not(.modal-image):not(.pointing-device-map) > a.link:not(.icon):not(:is(" +
urls.reduce(
(pre, crt) => `${pre}[href${crt.selectorSyntax}"${crt.url}"],`,
"",
) +
"))::after";
const iconStyleDefault: ChildStyle = {
"display": "inline-block",
"padding-right": "2px",
"font-size": ".5em",
"font-weight": "bold",
"font-family": "'Font Awesome 5 Free', 'Font Awesome 5 Brands'",
"content": "'\\f35d'",
"cursor": "text",
};
const iconStyle: Style = {
[selector]: style?.icon === undefined
? iconStyleDefault
: { ...iconStyleDefault, ...style.icon },
};
return iconStyle;
}
/**
* 特定のリンクにアイコンをつけるUserCSSを生成する関数
*/
export function generateLinkIconStyle(
/** アイコンを付与するリンクの情報 */
links: LinkInit[],
/** オプションで設定するスタイル(CSS) */
style?: {
/** アイコンに共通で設定するスタイル */
iconBase?: ChildStyle;
},
): Style {
const allURLs = getURLFromLinkInit(links);
const baseSelector = ".line span:not(.deco-\\.) > span > a.link:is(" +
allURLs.reduce(
(pre, crt) => `${pre}[href${crt.selectorSyntax}"${crt.url}"],`,
"",
) +
")::before";
const baseStyleDefault: ChildStyle = {
"display": "inline-block",
"width": "auto",
"height": "1em",
"line-height": "1em",
"vertical-align": "middle",
"text-align": "center",
"background-size": "contain",
"background-repeat": "no-repeat",
"font-family": "'Font Awesome 5 Free', 'Font Awesome 5 Brands'",
"cursor": "text",
};
/** 先頭にアイコンを付けるための共通のスタイル */
const baseStyle: Style = {
[baseSelector]: style?.iconBase === undefined
? baseStyleDefault
: { ...baseStyleDefault, ...style.iconBase },
};
const individualStyle: Style = {};
for (const link of links) {
const urls = getURLFromLinkInit([link]);
const selector = ":is(.line, .line .deco) a.link:is(" +
urls.reduce(
(pre, crt) => `${pre}[href${crt.selectorSyntax}"${crt.url}"],`,
"",
) +
")::before";
individualStyle[selector] = link.style;
}
return { ...baseStyle, ...individualStyle };
}
/**
* LinkInitの配列からURLを取り出す
* @param {LinkInit[]} links 取り出し元のオブジェクト
* @param {AttributeSelectorOperator} [defaultOperator="^="] selectorSyntaxが指定されていなかった際に使用するデフォルト値
* @return {URLInit[]}
*/
function getURLFromLinkInit(
links: LinkInit[],
defaultOperator: AttributeSelectorOperator = "^=",
): URLInit[] {
const resultURLs: URLInit[] = [];
for (const link of links) {
if (Array.isArray(link.url)) {
for (const url of link.url) {
if (typeof url === "string") {
resultURLs.push({ url: url, selectorSyntax: defaultOperator });
} else {
resultURLs.push(url);
}
}
} else {
const url = link.url;
if (typeof url === "string") {
resultURLs.push({ url: url, selectorSyntax: defaultOperator });
} else {
resultURLs.push(url);
}
}
}
return resultURLs;
}
deps.ts// CSSテキストを生成するのに使う
export {
toCSSText,
} from "https://scrapbox.io/api/code/Mijinko/SCSSっぽい連想配列からCSSを生成するTypeScript関数/style.ts";
export type {
ChildStyle,
Style,
} from "https://scrapbox.io/api/code/Mijinko/SCSSっぽい連想配列からCSSを生成するTypeScript関数/style.ts";
// クリップボードにコピーするやつ
export { writeText } from "https://deno.land/x/copy_paste@v1.1.3/mod.ts";
style.css@import "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css";.line span:not(.modal-image):not(.pointing-device-map) > a.link:not(.icon):not(:is([href$=".pdf"],[href^="https://github.com"],[href^="https://raw.githubusercontent.com"],[href^="https://gist.github.com"],[href^="https://twitter.com"],[href^="https://pbs.twimg.com"],[href^="https://video.twimg.com"],[href^="https://ja.wikipedia.org"],[href^="https://en.wikipedia.org"],[href^="https://www.amazon.co.jp"],[href^="https://amazon.co.jp"],[href^="https://discord.com"],[href^="https://discord.app"],[href^="https://discordapp.com"],[href^="https://discordstatus.com/"],[href^="https://www.cloudflare.com"],[href^="https://cdnjs.cloudflare.com"],[href^="https://store.steampowered.com"],[href^="https://help.steampowered.com"],[href^="https://steamcommunity.com"],[href^="https://s.team"],[href^="https://steamdb.info"],[href^="https://steamstat.us"],[href^="https://drive.google.com"],))::after{display:inline-block;padding-right:2px;font-size:.5em;font-weight:bold;font-family:'Font Awesome 6 Free', 'Font Awesome 6 Brands';content:'\f35d';cursor:text;}.line span:not(.deco-\.) > span > a.link:is([href$=".pdf"],[href^="https://github.com"],[href^="https://raw.githubusercontent.com"],[href^="https://gist.github.com"],[href^="https://twitter.com"],[href^="https://pbs.twimg.com"],[href^="https://video.twimg.com"],[href^="https://ja.wikipedia.org"],[href^="https://en.wikipedia.org"],[href^="https://www.amazon.co.jp"],[href^="https://amazon.co.jp"],[href^="https://discord.com"],[href^="https://discord.app"],[href^="https://discordapp.com"],[href^="https://discordstatus.com/"],[href^="https://www.cloudflare.com"],[href^="https://cdnjs.cloudflare.com"],[href^="https://store.steampowered.com"],[href^="https://help.steampowered.com"],[href^="https://steamcommunity.com"],[href^="https://s.team"],[href^="https://steamdb.info"],[href^="https://steamstat.us"],[href^="https://drive.google.com"],)::before{display:inline-block;width:auto;height:1em;line-height:1em;vertical-align:middle;text-align:center;background-size:contain;background-repeat:no-repeat;font-family:'Font Awesome 6 Free', 'Font Awesome 6 Brands';cursor:text;font-weight:400;margin-right:1px;}:is(.line, .line .deco) a.link:is([href$=".pdf"],)::before{content:'\f1c1';}:is(.line, .line .deco) a.link:is([href^="https://github.com"],[href^="https://raw.githubusercontent.com"],[href^="https://gist.github.com"],)::before{content:'\f09b';}:is(.line, .line .deco) a.link:is([href^="https://twitter.com"],[href^="https://pbs.twimg.com"],[href^="https://video.twimg.com"],)::before{content:'\f099';}:is(.line, .line .deco) a.link:is([href^="https://ja.wikipedia.org"],[href^="https://en.wikipedia.org"],)::before{content:'\f266';}:is(.line, .line .deco) a.link:is([href^="https://www.amazon.co.jp"],[href^="https://amazon.co.jp"],)::before{content:'\f270';}:is(.line, .line .deco) a.link:is([href^="https://discord.com"],[href^="https://discord.app"],[href^="https://discordapp.com"],[href^="https://discordstatus.com/"],)::before{content:'\f392';}:is(.line, .line .deco) a.link:is([href^="https://www.cloudflare.com"],[href^="https://cdnjs.cloudflare.com"],)::before{content:'\e07d';}:is(.line, .line .deco) a.link:is([href^="https://store.steampowered.com"],[href^="https://help.steampowered.com"],[href^="https://steamcommunity.com"],[href^="https://s.team"],)::before{content:'\f1b6';}:is(.line, .line .deco) a.link:is([href^="https://steamdb.info"],[href^="https://steamstat.us"],)::before{content:'\f3f6';}:is(.line, .line .deco) a.link:is([href^="https://drive.google.com"],)::before{content:'\f3aa';}i.fa-twitter::before{content:none;}.line .link .favicon.fa-twitter{margin:0;}