settings
code:style.css
body {
--new-button-bg: transparent;
--new-button-hover-bg: transparent;
--new-button-active-bg: transparent;
--search-form-text-color: #555; }
.grid li.page-list-item.grid-style-item:not(.pin) a {
}
.grid li.page-list-item.grid-style-item:not(.pin) a:hover {
}
/* コード記法の行番号を表示 -- ウィンドウ幅768px以上で適用 */
@media screen and (min-width: 768px) {
.section-title, .code-block-start {
counter-reset: codeline
}
.code-block code>span:not(class) { counter-increment: codeline
}
.code-block code>span:not(class)::before { content: counter(codeline);
position: absolute;
display: inline-block;
left: -4em;
z-index: 10;
min-width: 50px;
text-align: right;
vertical-align: bottom;
/* ↓行番号のフォントとか色とかの指定はここ */
font-family: monospace;
color: grey
}
/* カーソル行の行番号を濃く表示する */
.code-block code>span:not(class)::before { opacity: .5
}
.cursor-line .code-block code>span:not(class)::before { opacity: 1;
font-weight: bolder
}
}
/* ピン留め改行 */
.page-list-item.pin + .page-list-item:not(.pin) {
grid-column-start: 1;
}
/* リスト協調表示 */
.indent-mark .char-index {
--ir-opacity: 0.06;
--ir-red: rgba(255, 0, 0, var(--ir-opacity));
--ir-orange: rgba(255, 165, 0, var(--ir-opacity));
--ir-yellow: rgba(255, 255, 0, var(--ir-opacity));
--ir-green: rgba(0, 128, 0, var(--ir-opacity));
--ir-blue: rgba(0, 0, 255, var(--ir-opacity));
--ir-violet: rgba(238, 130, 238, var(--ir-opacity));
--ir-purple: rgba(128, 0, 128, var(--ir-opacity));
}
.indent-mark .char-index:nth-child(7n+1) {
background-color: var(--ir-red)
}
.indent-mark .char-index:nth-child(7n+2) {
background-color: var(--ir-orange)
}
.indent-mark .char-index:nth-child(7n+3) {
background-color: var(--ir-yellow)
}
.indent-mark .char-index:nth-child(7n+4) {
background-color: var(--ir-green)
}
.indent-mark .char-index:nth-child(7n+5) {
background-color: var(--ir-blue)
}
.indent-mark .char-index:nth-child(7n+6) {
background-color: var(--ir-violet)
}
.indent-mark .char-index:nth-child(7n) {
background-color: var(--ir-purple)
}
.indent-mark .char-index:nth-last-child(1) {
background-color: transparent;
}
:root {
}
.line .indent-mark .dot::before {
display: block;
position: absolute;
right: -4px;
top: -10px;
font-family: 'Font Awesome 5 Free';
font-weight: 900;
font-size: 6px;
content: '\f068';
color: var(--li-color-0);
}
.line .indent-mark .dot {
background-color: transparent;
}
.line .indent-mark .c-0+.dot::before {
font-size: 10px;
}
.line .indent-mark .c-1+.dot::before {
font-size: 10px;
}
.line .indent-mark .c-2+.dot::before {
font-size: 10px;
}
.line .indent-mark .c-3+.dot::before {
font-size: 9px;
}
.line .indent-mark .c-4+.dot::before {
font-size: 8px;
}
.line .indent-mark .c-0+.dot::before {
color: var(--li-color-1);
}
.line .indent-mark .c-1+.dot::before {
color: var(--li-color-2);
}
.line .indent-mark .c-2+.dot::before {
color: var(--li-color-3);
}
.line .indent-mark .c-3+.dot::before {
color: var(--li-color-4);
}
.line .indent-mark .c-4+.dot::before {
color: var(--li-color-5);
}
/* アイコン丸くする */
.line img.icon {
border-radius: 2px;
margin-left: 2px;
transform: translateY(2px);
}
/* フォント変更 */
.editor,
.stream {
font-family: 'Noto Sans JP', 'Hiragino Maru Gothic W4 JIS2004', '游ゴシック', YuGothic, 'ヒラギノ角ゴ ProN W3', 'Hiragino Kaku Gothic ProN', 'メイリオ',
Meiryo, 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
/* [ ]で蛍光ライン */
background:linear-gradient(#ffffff00 60%,#a8ffd3 80%)
}
.deco-\# {
font-weight: 600;
background:linear-gradient(#ffffff00 60%,#ffff00 89%)
}
.deco-\& {
font-weight: bold;
}
.deco-\% {
font-weight: bold;
}
.deco-\" {
font-weight: bold;
}
.deco-\( {
font-weight: bold;
}
/* !にブラーかける */
span.deco-\! {
/*background-color: var(--page-text-color, #FFF);*/ filter: blur(4px);
}
div.cursor-line > span.text > span * span.deco-\! {
filter: unset;
}
span.deco-\! > * img {
filter: blur(4px);
}
test
code: script.js
import '/api/code/kn1cht/embed-tweet/script.js'
$(() => {
.done((script, textStatus) => {
mermaid.mermaidAPI.initialize({ startOnLoad: false })
const mermaidViewer = new MermaidViewer()
mermaidViewer.onScrapboxPageChanged()
scrapbox.on("page:changed", () => mermaidViewer.onScrapboxPageChanged())
scrapbox.on("lines:changed", () => mermaidViewer.onScrapboxLinesChanged())
})
.fail((jqxhr, settings, exception) => {
console.error(exception)
})
const MermaidViewer = function() {
const DEFAULT_SHOW_CODE = false
this.recentMermaidCodes = new Map()
this.codeViewStatusRepository = new MermaidCodeViewStatusRepository()
this.onScrapboxLinesChanged = function() {
if (scrapbox.Page.lines) {
this.updateDiagrams()
}
}
this.onScrapboxPageChanged = function() {
if (scrapbox.Page.lines) {
this.updateDiagrams()
this.setAllCodeViewStatus(DEFAULT_SHOW_CODE)
}
}
// すべてのコードブロックの表示ステータスを変更
// 引数: value 表示ステータス (true|false)
this.setAllCodeViewStatus = function(value) {
for (const id, code of this.recentMermaidCodes) { code.setCodeViewStatus(value)
}
}
// 変更があればダイアグラムを更新
this.updateDiagrams = function() {
const newCodes = this.findMermaidCodes()
const diff = MermaidViewerUtils.diffMermaidCodes(this.recentMermaidCodes, newCodes)
for (const item of diff) {
if (item.op === "delete") {
item.code.deleteDiagram()
} else {
item.code.updateDiagram()
}
}
this.recentMermaidCodes = newCodes
}
// mermaidコードをページ内から検索
// 戻り値: Map型
// キー: コードブロックのID(最初の行ID)
// 値: MermaidCode
this.findMermaidCodes = function() {
const result = new Map()
var text, filename, id, lastLineId, lineIds
for (const line of scrapbox.Page.lines) {
if (line.codeBlock && line.codeBlock.lang === "mermaid") {
if (line.codeBlock.start) {
text = ""
id = line.id
lineIds = new Set()
} else {
text += "\n" + line.text
}
lineIds.add(line.id)
if (line.codeBlock.end) {
lastLineId = line.id
text = text.trim()
result.set(id, new MermaidCode(id, text, lastLineId, lineIds, this.codeViewStatusRepository))
}
}
}
return result
}
}
const MermaidCode = function(id, text, lastLineId, lineIds, codeViewStatusRepository) {
const MERMAID_SVG_ID_PREFIX = "mermaid-"
this.id = id
this.text = text
this.lastLineId = lastLineId
this.lineIds = lineIds
this.codeViewStatusRepository = codeViewStatusRepository
this.svgId = MERMAID_SVG_ID_PREFIX + id
// mermaidダイアグラムを更新
this.updateDiagram = function() {
try {
const svg = mermaid.mermaidAPI.render(this.svgId, this.text)
$("#" + this.svgId).remove()
$("#L" + this.lastLineId).after(svg)
} catch (e) {
console.error(e)
$("#L" + this.lastLineId).after($("#" + this.svgId))
}
$("#" + this.svgId)
.on("click", () => this.onSvgClicked())
.css("cursor","pointer")
}
// mermaidダイアグラムを削除
this.deleteDiagram = function() {
$("#" + this.svgId).remove()
}
// mermaidダイアグラム(SVG)がクリックされたときのイベントハンドラ
// コードブロックの表示ステータスを変更
this.onSvgClicked = function() {
this.codeViewStatusRepository.changeStatus(this.id)
this.applyCodeView()
}
// コードブロックの表示ステータスを適用
this.applyCodeView = function() {
const status = this.codeViewStatusRepository.getStatus(this.id)
for (const lineId of this.lineIds) {
if (status) {
$("#L" + lineId).show(100)
} else {
$("#L" + lineId).hide(100)
}
}
}
// コードブロックの表示ステータスを変更
this.setCodeViewStatus = function(value) {
this.codeViewStatusRepository.setStatus(this.id, value)
this.applyCodeView()
}
}
const MermaidCodeViewStatusRepository = function() {
this.status = new Map()
this.defaultValue = true
this.changeStatus = function(id) {
const old = this.status.has(id) ? this.status.get(id) : this.defaultValue
this.status.set(id, !old)
}
this.getStatus = function(id) {
return this.status.has(id) ? this.status.get(id) : this.defaultValue
}
this.setStatus = function(id, value) {
this.status.set(id, value)
}
}
const MermaidViewerUtils = {}
// 2つのMap型に格納されたコードの差分を返す
// 引数: oldMap 古い値(Map型)
// 引数: newMap 新しい値(Map型)
MermaidViewerUtils.diffMermaidCodes = function(oldMap, newMap) {
const result = []
const intersection = new Set()
if (!oldMap.has(key)) {
result.push({ op: "new", key: key, code: newMap.get(key) })
} else {
intersection.add(key)
}
}
if (!newMap.has(key)) {
result.push({ op: "delete", key: key, code: oldMap.get(key) })
intersection.delete(key)
}
}
for (const key of intersection) {
const oldVal = oldMap.get(key)
const newVal = newMap.get(key)
if (oldVal.text !== newVal.text) {
result.push({ op: "changed", key: key, code: newMap.get(key) })
}
}
return result;
}
})