UserScript
https://gyazo.com/b92e758087e149d0c5c7607437c14e7e
インポート
code:import.js
// 日記テンプレートの自動生成
import "/api/code/yude/UserScript/diary_new.js";
// import "/api/code/public-minaph/日記を自動作成するUserScript/update.js";
// ツイートメニュー
import "/api/code/yude/UserScript/tweet.js";
// ニコニコ動画のサムネイル宛て URL を画像に変換するためのボタンを表示 (URL 選択時) from /rinsuki
import "/api/code/yude/UserScript/nicothumb2img.js";
// カーソルキーでページ選択
import "/api/code/yude/UserScript/cursor-page-select.js";
// 別プロジェクトへのリンクのうち、リンク切れになっているものをオレンジで表示
import "/api/code/yude/UserScript/404-orange.js";
// 文字数カウント
import "/api/code/yude/UserScript/word-count.js";
// 行頭に引用符を付ける
import "/api/code/yude/UserScript/quote.js";
// ページの一部分を別のページに切り出す
import "/api/code/yude/UserScript/new-page.js";
// 順序付きリストを作成
// import "/api/code/yude/UserScript/ordered-list.js";
// 見える文字数カウンター from /scrasobox
import "/api/code/scrasobox/見える文字数カウンター/script.js";
// ピンしてるページにジャンプするメニュー from /scrasobox
import "/api/code/scrasobox/ピンしてるページにジャンプするメニュー/script.js"
// tweet2image ヘルパー from /ci7lus
import '/api/code/yude/UserScript/tweet-card.js';
// spotify2image ヘルパー
import '/api/code/yude/UserScript/spotify-card.js';
spotify2image ヘルパー
code:spotify-card.js
scrapbox.PopupMenu.addButton({
title: "♫",
onClick: (text) => {
const m = text.match(/open.spotify.com\/track\/(.+)/);
if (!m) return text;
return [https://spotify2image.vercel.app/image/track/${m[1]}.png https://open.spotify.com/track/${m[1]}];
},
});
tweet2image ヘルパー
code:tweet-card.js
scrapbox.PopupMenu.addButton({
title: "🐣",
onClick: (text) => {
const m = text.match(/twitter.com\/(.+)\/status\/(\d+)/);
if (!m) return text;
return [https://tweet2image.vercel.app/${m[2]}.jpg?lang=ja&tz=9 https://twitter.com/${m[1]}/status/${m[2]}];
},
});
ページの一部分を別のページに切り出す
code:new-page.js
scrapbox.PopupMenu.addButton({
title: '㌻',
onClick: text => {
const lines = text.split(/\r\n/g) .trim()
.replace(/\^\+.icon\]/gm, '')
const projectRoot = (() => {
const tmp = location.href.split('/')
tmp.pop()
return tmp.join('/')
})()
const currentPageTitle = decodeURIComponent(location.href.split(/\//g).pop())
lines.unshift(from [${currentPageTitle}])
const body = encodeURIComponent(lines.join('\n'))
window.open(${projectRoot}/${title}?body=${body})
return [${title}]
}
})
行頭に引用符を付ける
code:quote.js
scrapbox.PopupMenu.addButton({
title: '❯❯',
onClick: text => text.split(/\n/).map(line => > ${line}).join('\n')
})
別プロジェクトへのリンクのうち、リンク切れになっているものをオレンジで表示
code:404-orange.js
Array.from(document.querySelectorAll(".page .lines a.page-link"))
.filter(a=>a.innerText.startsWith('/'))
.forEach(async a=>{
const response = await fetch(a.href.replace('scrapbox.io', 'scrapbox.io/api/pages'));
const json = JSON.parse(await response.text());
if(!json.persistent) a.classList.add("empty-page-link")
})
文字数カウント
code:word-count.js
scrapbox.PopupMenu.addButton({
title: function (text) {
const chars = text.replace(/\r\n/g, '').length const words = text.trim().split(/\r\n\s+/).length return ${chars}c ${words}w
},
onClick: () => null
})
nicothumb2img
code:nicothumb2img.js
const niconicoURLRegex = /(?=(?=nico\.ms|nicovideo\.jp\/(?=watch|seiga|)\/)|)?(a-z{2}?1-90-9*)/ scrapbox.PopupMenu.addButton({
title: text => {
const result = text.match(niconicoURLRegex)
if (result == null) return null
return nicothumb2img (${result[0]})
},
onClick: (text) => {
const m = text.match(niconicoURLRegex);
if (!m) return text;
return [https://nicothumb2img.vercel.app/image/${m[0]}#.png https://nico.ms/${m[0]}];
},
});
日記テンプレートの自動生成
code:diary_new.js
/* MIT License Copyright (c) 2020 ci7lus */
import { importExternalJs } from "/api/code/ci7lus/userscript-utils/import-external-js.js";
importExternalJs(
);
import { insertText } from "/api/code/customize/scrapbox-insert-text/script.js";
scrapbox.PageMenu.addMenu({
title: 日記テンプレート,
onClick: () => {
if (!scrapbox.Page.lines || !scrapbox.Page.lines.length == 1) return
const input = prompt(
"日報テンプレートを展開したい日付を相対(d+)または絶対(2020-1-1)で(入力なしで今日)"
)
if (input === null) return
const diff = parseInt(input.trim() || 0)
const abs = input.split("-").length === 3 && dayjs(input)
if ((Number.isNaN(diff) && !abs) || (abs && !abs.isValid())) return
const today = abs
? abs.startOf("days")
: dayjs().startOf("days").add(diff, "days")
const yesterday = today.clone().subtract(1, "days")
const tomorrow = today.clone().add(1, "days")
console.log(today.format(), yesterday.format(), tomorrow.format())
const conf = confirm(対象の日付は ${today.format("YYYY-MM-DD")} ですか?)
if (!conf) return
insertText({
text: `${today.format("YYYY-MM-DD")}\n<- [${tomorrow.format(
"YYYY-MM-DD"
"YYYY-MM-DD"
)}] ->\n#日記\n\n 行動 / 出来事\n`,
})
},
})
code:diary.js
/*
if (!window.dayjs) {
const dayJsMinJs = document.createElement("script");
dayJsMinJs.src =
document.body.appendChild(dayJsMinJs);
}
scrapbox.PageMenu.addMenu({
title: 日記,
onClick: () => {
if (!scrapbox.Page.lines || !scrapbox.Page.lines.length == 1) return;
const input = prompt("日記のテンプレートを展開したい日付を相対(\d+)または絶対(2021-01-01)で(入力なしで今日)");
if (input === null) return
const diff = parseInt(input.trim() || 0);
const abs = input.split("-").length === 3 && dayjs(input);
if ((Number.isNaN(diff) && !abs) || (abs && !abs.isValid())) return;
const today = abs ? abs.startOf("days") : dayjs().startOf("days").add(diff, "days");
const yesterday = today.clone().subtract(1, "days");
const tomorrow = today.clone().add(1, "days");
console.log(today.format(), yesterday.format(), tomorrow.format());
const conf = confirm(対象の日付は ${today.format("YYYY-MM-DD")} ですか?);
if (!conf) return;
const line = document.getElementById("L" + scrapbox.Page.lines0.id); mimicClick(line.id, line.offsetWidth, lastChar.offsetTop + 10);
const textarea = document.getElementById("text-input");
翌日: →\n\n 行動 / 出来事\n `;
const event = document.createEvent("Event");
event.initEvent("input", true, true);
textarea.dispatchEvent(event);
mimicClick(line.id, line.offsetWidth, lastChar.offsetTop + 10);
},
});
const mimicClick = (targetId, left, top) => {
const genEvent = (type) => {
const event = document.createEvent("MouseEvents");
event.initMouseEvent(
type,
true,
true,
window,
1,
0,
0,
left,
top,
false,
false,
false,
false,
0,
null
);
return event;
};
const elm = document.getElementById(targetId);
elm.dispatchEvent(genEvent("mousedown"));
elm.dispatchEvent(genEvent("mouseup"));
elm.dispatchEvent(genEvent("click"));
};
カーソルキーでページを選択
Shift + 矢印キーで使用可能
code:cursor-page-select.js
(() => {
let onKeyDown = function(e){
// トップページでShiftと矢印キーが押されたときだけ
if (scrapbox.Layout === "list" && e.shiftKey && e.code.substr(0, 5) === "Arrow") {
// OK
} else {
return;
}
function getGridNumber() {
return parseInt(
document.querySelector('div.page-list').offsetWidth / document.querySelector('li.page-list-item').offsetWidth
);
};
const grid = getGridNumber();
const distance = {
ArrowUp: - grid,
ArrowLeft: - 1,
ArrowRight: 1,
ArrowDown: grid
let current = document.activeElement.parentElement;
if (! current.classList.contains("page-list-item")) {
// グリッド選択状態でなければ最初のグリッドを選択
current = document.querySelector('.page-list-item');
} else if (0 < distance) {
// 進行方向(右・下)へ移動
for (let i = 0; i < distance; i++) {
current = current.nextElementSibling;
if (current === null) {
return; // 端っこ
}
}
} else {
// 逆方向(左・上)へ移動
for (let i = 0; i > distance; i--) {
current = current.previousElementSibling;
if (current === null) {
return; // 端っこ
}
}
}
current.children0.focus(); };
document.addEventListener('keydown', onKeyDown);
})();
ツイートメニュー
code:tweet.js
scrapbox.PageMenu.addItem({
title: 'ツイート',
onClick: () => window.open(https://twitter.com/intent/tweet?url=${encodeURIComponent(location.href)}&text=${encodeURIComponent(window.scrapbox.Page.title)})
})
// 日記用
scrapbox.PageMenu.addItem({
title: 'ツイート (日記用)',
onClick: () => window.open(https://twitter.com/intent/tweet?url=${encodeURIComponent(location.href)}&text=${encodeURIComponent(window.scrapbox.Page.title)}%20の日記)
})
順序付きリストを作成
code:ordered-list.js
scrapbox.PopupMenu.addButton({
title: 'Ordered list',
onClick: text => text.split(/\n/).map((line, index) => ${index+1}. ${line}).join('\n')
})