rashita
https://gyazo.com/a7d04dd3f6631faff7f43cfd4ce1adcc
倉下忠憲のwebアバター的な存在。だいたいのサービスでrashitaか「らした」で登録しているが、twitterだけrashita2である。 Netgraphy
著作リスト
モットー
好きな言葉
ツール(道具)
Cosense
WorkFlowy
Textbox
BextEditor
メソッド(手法)
バザール執筆法
以下UserScriptの設定
code:script.js
scrapbox.PopupMenu.addButton({
title: 'pop',
onClick: text => {
const lines = text.split(/\r\n/g); .trim()
.replace(/\^\+\.icon\]/gm, '')
.replace(/^#/g, '')
lines.shift();
const body = encodeURIComponent("+ scrapbox.Page.title + "");
const encodedTitle = encodeURIComponent(title);
window.open(https://scrapbox.io/${scrapbox.Project.name}/${encodedTitle}?body=${body});
//return ""; // 元のページにはリンクを残さない。
}
});
code:script.js
requestAnimationFrame(() => {
if (window.KCS?.role === 'SV') return;
cosense.on('lines:changed', ({ by }) => {
if (by === 'edit') updateKnowledgeReviewState();
});
cosense.on('page:changed', updateRINKDom);
function updateRINKDom () {
console.log("ページがチェンジしました")
if (scrapbox.Layout === 'page') {
const tempElem = document.getElementById("tempRINKList");
if (tempElem)document.querySelector('.page-sidebar')?.removeChild(tempElem);
}
}
async function updateKnowledgeReviewState () {
if (cosense.Layout !== 'page') return;
//ページのタイトルがナンバリングになっているかを確認する
if (!(/^\d{2}\./.test(cosense.Page.title)))return;
console.log("このページのタイトルは、二ケタの半角数字+.ではじまっています");
if (!cosense.Page.metadata?.links?.includes('情報カード')) return;
console.log("情報カードというリンクを持っています");
const matchedLink = cosense.Page.metadata?.links?.find(link =>
typeof link === "string" && /^\d{2}\./.test(link)
);
console.log("このページのリンクに二ケタの半角数字+.ではじまるページがあります");
if (!matchedLink) return;
const tempJSON = await fetchPageJSON(encodeURI(matchedLink))
const refpageList = tempJSON.relatedPages.links1hop
setRINK(refpageList)
return;
}
async function fetchPageJSON(encodedPageTitle) {
const url = https://scrapbox.io/api/pages/${scrapbox.Project.name}/${encodedPageTitle};
const response = await fetch(url);
if (!response.ok) {
throw new Error(${response.status} ${response.statusText});
}
const json = await response.json();
return json;
}
function setRINK(arry){
const titles = arry.map(obj => obj.title).sort();
if (document.getElementById("tempRINKList")?.innerText == titles.join("\n")) {
const tempElem = document.getElementById("tempRINKList");
const sidebar = document.querySelector(".page-sidebar");
if (tempElem && tempElem.parentElement !== sidebar) {
// 親要素が .page-sidebar ではない場合の処理
sidebar.insertBefore(tempElem, sidebar.firstChild);
tempElem.style.position = "static"
}
return
}
const divBody = document.createElement("div")
divBody.id = "tempRINKList"
divBody.innerText = titles.join("\n")
const sidebar = document.querySelector(".page-sidebar")
if (sidebar){
sidebar.prepend(divBody)
divBody.style.position = "static"
}else{
const appContainer = document.querySelector(".app .container .page-column")
appContainer.append(divBody)
}
}
});
code:style.css
position: absolute;top: 72px;left: 20px;background: floralwhite;padding: 1em;
width:280px;border-radius: 5px;
}
code:script.js
cosense.PageMenu.addItem({
title: 'ページ末尾に日時を挿入',
onClick: insertTail
});
function insertTail () {
cosense.Page.insertLine(new Date().toString(), cosense.Page.lines.length);
}
code:script.js
scrapbox.PopupMenu.addButton({
title: 'Del',
onClick: () =>{return ""}
});
scrapbox.PopupMenu.addButton({
title: '- を足す',
onClick: text =>{
return "- " + text;
}
});
リンクページ取得
code:script.js
const getlinkpage = function () {
const url = location.href.replace("io/", "io/api/pages/");
let x = new XMLHttpRequest();
x.open("get", url, false);
x.send(null);
let json = x.responseText;
let pagedata = JSON.parse( json );
let linkPagesData = pagedata"links"; //ループで回して、リンクページの本文を取得していく
let pageContents = [];
let rooturl = location.href.replace("io/", "io/api/pages/").replace(new RegExp("/" + encodeURI(scrapbox.Page.title) + "$"),"/");
for (let i = 0, len = linkPagesData.length; i < len; ++i) {
let relatedurl = rooturl + encodeURI(linkPagesDatai) + "/text"; //これでXMLHttpRequestを回していく
let y = new XMLHttpRequest();
y.open("get", relatedurl, false);
y.send(null);
let c = y.responseText;
if (! (c == '{"name":"NotFoundError","message":"Page not found."}') ){
c = removehashtag(c,scrapbox.Page.title); //もとハッシュタグを削除しない場合はこの行をコメントアウト
c = removebracket(c);//本文中からブラケット[]を取り除かない場合はこの行をコメントアウト
pageContents.push(addmargin(c));
}
}
//取得したページの中身を使って、新しいwindowをオープンする。
var win = window.open();
win.document.open();
win.document.write(<title>temporary</title>);
win.document.write('<pre>');
win.document.write(pageContents.join('\n'));
win.document.write('</pre>');
win.document.close();
}
function removehashtag(text,pageTitle){//本文中からもともとのハッシュタグを削除する
return text.replace(new RegExp('#' + scrapbox.Page.title),"")
}
function removebracket(text){//本文中から閉じと開きのブラケットを削除する
return text.replace(/\|\/g,"") }
function addmargin(text){ //二行目以降に半角スペースを追加する
return text.replace(/\n/g,"\n ")
}
scrapbox.PageMenu.addMenu({
title: 'リンクページ取得',
onClick:getlinkpage
})
code:script.js
scrapbox.PopupMenu.addButton({
title: 'shorten',
onClick: text => {
const lines = text.split(/\r\n/g); var body = [];
for (var i = 0, len = lines.length; i < len; ++i) {
bodyi = linesi.replace(/^\s\s\s\s/g, '') .replace(/\s\s\s\s/g, ' ').replace(/^$/g,''); }
return body.join('\n');
}
})
code:script.js
scrapbox.PageMenu.addMenu({
title: '一覧',
onClick: () => {
if (document.getElementById("tempThemeList")){
document.getElementById("tempThemeList").remove();
return
}
const tempBody = getBodyText("%E7%99%BA%E6%83%B3%E5%B8%B3")
async function getBodyText(encodedPageTitle){
const url = https://scrapbox.io/api/pages/${scrapbox.Project.name}/${encodedPageTitle};
const response = await fetch(url);
if (!response.ok) {
throw new Error(${response.status} ${response.statusText});
}
const json = await response.json();
const jsonLinks1hop = json.relatedPages.links1hop //arry
//cosense.Page.title
const result = jsonLinks1hop
.filter(item => item.title !== cosense.Page.title && Array.isArray(item.infoboxResult) && item.infoboxResult.length > 0)
.map(item => ({
title: item.title,
infoboxes: item.infoboxResult.map(entry => entry.infobox)
.slice(0, 30) //ひとまず30個までにしておく
}));
//relatedPages links1hop {title,infoboxResult{title,infobox{番号付きリスト または 概要}}
const divBody = document.createElement("div")
divBody.id = "tempThemeList"
result.forEach(item => {
// div 要素を作成
const div = document.createElement('div');
// infobox のテキストを抽出(キー名を無視して値だけ取り出す)
const infoboxTexts = item.infoboxes
.map(box => {
// box がオブジェクトの場合(例: { "番号付きリスト または 概要": "..." })
if (typeof box === 'object' && box !== null) {
return Object.values(box).join('\n'); // 値だけを結合(複数キーでも対応可)
}
return box; // box が文字列ならそのまま返す
})
.join('\n'); // 複数 infobox がある場合、さらに改行でつなぐ
// innerText に整形して設定
div.innerText = ${item.title}\n${infoboxTexts};
// たとえば body に追加(必要に応じて別の要素でもOK)
divBody.appendChild(div);
});
const appContainer = document.querySelector(".app .container .page-column")
appContainer.append(divBody)
}
}
})
code:script_old.js
scrapbox.PageMenu.addMenu({
title: '一覧',
onClick: () => {
if (document.getElementById("tempThemeList")){
document.getElementById("tempThemeList").remove();
return
}
const tempBody = getBodyText("%E5%80%89%E4%B8%8B%E5%BF%A0%E6%86%B2%E3%81%AE%E4%B8%BB%E8%A6%81%E3%81%AA%E9%96%A2%E5%BF%83%E4%BA%8B")
async function getBodyText(encodedPageTitle){
const url = https://scrapbox.io/api/pages/${scrapbox.Project.name}/${encodedPageTitle}/text;
const response = await fetch(url);
if (!response.ok) {
throw new Error(${response.status} ${response.statusText});
}
const text = await response.text();
const divBody = document.createElement("div")
divBody.id = "tempThemeList"
divBody.innerText = text
console.log(divBody)
const appContainer = document.querySelector(".app .container .page-column")
appContainer.append(divBody)
}
}
})
code:style.css
position: absolute;top: 72px;left: 20px;background: floralwhite;padding: 1em;
width:300px;border-radius: 5px;
}
padding-bottom: 10px;
border-bottom: 1px solid #ddd; }
background-color: #f5f5f5; /* お好みの薄い色に */ }
code:script.js
scrapbox.PageMenu.addMenu({
title: '末尾',
onClick: () => {
$("html,body").animate({scrollTop:$('.related-page-sort-menu').offset().top});
}
})
code:script.js
scrapbox.PopupMenu.addButton({
title: 'branch',
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.shift()
const body = encodeURIComponent(lines.join('\n'))
window.open(${projectRoot}/${title}?body=${body})
return [${title}]
}
})
関連ページの取得(工事中ですが、ある程度は動きます)
code:script0.js
const makepage = function () {
const url = location.href.replace("io/", "io/api/pages/");
let x = new XMLHttpRequest();
x.open("get", url, false);
x.send(null);
let json = x.responseText;
let pagedata = JSON.parse( json );
let relatedPagesDatalinks1hop = relatedPagesData"links1hop"; //ループで回して、関連ページの本文を取得していく
let pageContents = [];
let rooturl = location.href.replace("io/", "io/api/pages/").replace(new RegExp("/" + encodeURI(scrapbox.Page.title) + "$"),"/");
for (let i = 0, len = relatedPagesDatalinks1hop.length; i < len; ++i) {
let relatedurl = rooturl + relatedPagesDatalinks1hopi"title" + "/text"; //これでXMLHttpRequestを回していく
let y = new XMLHttpRequest();
y.open("get", relatedurl, false);
y.send(null);
//各ページに含まれる[]を取り除いたり、関連ページのおおもとのページのハッシュタグを削除してもいい。ただし、本文中に書き込まれている場合はややこしい。
let c = y.responseText;
c = removehashtag(c,scrapbox.Page.title); //もとハッシュタグを削除しない場合はこの行をコメントアウト
c = removebracket(c)//本文中からブラケット[]を取り除かない場合はこの行をコメントアウト
pageContents.push(addmargin(c));
}
//取得したページの中身を使って、新しいwindowをオープンする。
var win = window.open();
win.document.open();
win.document.write(<title>temporary</title>);
win.document.write('<pre>');
win.document.write(pageContents.join('\n'));
win.document.write('</pre>');
win.document.close();
}
function removehashtag(text,pageTitle){//本文中からもともとのハッシュタグを削除する
return text.replace(new RegExp('#' + scrapbox.Page.title),"")
}
function removebracket(text){
return text.replace(/\|\/g,"") }
function addmargin(text){ //二行目以降に半角スペースを追加する
return text.replace(/\n/g,"\n ")
}
scrapbox.PageMenu.addMenu({
title: '関連ページ取得',
onClick:makepage
})
ツイートボタン
code:script.js
scrapbox.PageMenu.addItem({
title: 'Tweet',
onClick: () => window.open(https://twitter.com/intent/tweet?url=${encodeURIComponent(location.href)}&text=${encodeURIComponent(window.scrapbox.Page.title)})
})
前日の日付にリンクする矢印アイコン
code:script.js
async function tempPageLink(link){
console.log(link + "を表示します")
await cosense.Page.show(link)
}
function getPageText(encodedPageTitle) {
const texts = cosense.Page.lines.map(line => line.text);
return texts.join("")
}
async function fetchPageJSON(encodedPageTitle) {
const url = https://scrapbox.io/api/pages/${scrapbox.Project.name}/${encodedPageTitle};
const response = await fetch(url);
if (!response.ok) {
throw new Error(${response.status} ${response.statusText});
}
const json = await response.json();
return json;
}
function extractYearMonth(text) {
const regex = /(\d{4})年(\d{1,2})月(.*)$/;
const match = text.match(regex);
if (match) {
const year = parseInt(match1, 10); // 最初のキャプチャグループ (年) const month = parseInt(match2, 10); // 2番目のキャプチャグループ (月) return { year, month , titlebody};
} else {
return null; // マッチしなかった場合
}
}
function extractCategory_old(text){
const foundKeyword = keywordsArray.find(keyword => text.includes(keyword));
if (foundKeyword) console.log(foundKeyword + "です")
return foundKeyword
}
function extractCategory(text) {
const foundKeyword = keywordsArray.find(keyword => {
const pattern = new RegExp((?:#|\\[)${escapeRegExp(keyword)}(?:\\])?);
return pattern.test(text);
});
if (foundKeyword) console.log(foundKeyword + "です");
return foundKeyword || null;
}
// 正規表現の特殊文字をエスケープする関数
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|\\\]/g, '\\$&'); }
async function getrefPagesList(title){
const json = await fetchPageJSON(title)
const refPagelist = json.relatedPages.links1hop
return refPagelist
}
scrapbox.PageMenu.addMenu({
title: '前へ',
onClick: () => {
const path = location.pathname.split('/');
if (/^\d{4}年\d{1,2}月(.*)$/.test(decodeURIComponent(title))) {
console.log("何かの月ページです")
const dataset = extractYearMonth(decodeURIComponent(title))
const d = new Date(dataset.year, dataset.month - 1, 1);
d.setMonth(d.getMonth() - 1);
// 新しい年と月を取得
const prevYear = d.getFullYear();
const prevMonth = d.getMonth() + 1; // getMonth()は0始まりなので+1する
const prev = ${prevYear}年${prevMonth}月${dataset.titlebody};
tempPageLink(prev)
}
if (/^\d{4}%2F\d{1,2}$/.test(title)) {
console.log("月ページです")
const parts = decodeURIComponent(title).split('/');
const year = parseInt(parts0, 10); const month = parseInt(parts1, 10); // 数字でない、または月が範囲外の場合はエラー
if (isNaN(year) || isNaN(month) || month < 1 || month > 12) {
console.error("Invalid year or month in title.", currentPageTitle);
return null;
}
// Dateオブジェクトを作成
// JavaScriptの月は0から始まるため、month-1 を渡す
// 日付は1日として設定することで、日による影響を排除
const d = new Date(year, month - 1, 1);
// 月を1ヶ月前に戻す
d.setMonth(d.getMonth() - 1);
// 新しい年と月を取得
const prevYear = d.getFullYear();
const prevMonth = d.getMonth() + 1; // getMonth()は0始まりなので+1する
tempPageLink(${prevYear}/${prevMonth})
return
}
if (/^\d{4}%2F\d{1,2}%2F\d{1,2}$/.test(title)) {
console.log("日ごとページです")
const d = new Date(decodeURIComponent(title));
d.setDate(d.getDate() - 1);
// 年、月、日をそれぞれ取得
const year = d.getFullYear();
const month = d.getMonth() + 1; // getMonth() は0から11を返すため、+1する
const day = d.getDate();
// ゼロ埋めされていない形式で日付文字列を構築
const rowPrev = ${year}/${month}/${day};
tempPageLink(rowPrev)
return
}
console.log("その他のページです")
const text = getPageText(title);
const category = extractCategory(text)
if (!category) return //指定したカテゴリがなければ終了
movePreCategoryPage(category)
async function movePreCategoryPage(category){
const templist = await getrefPagesList(encodeURIComponent(category))
console.log(category + "において" + title + "を探します")
const foundIndex = templist.findIndex(item => item.title === cosense.Page.title);
if (foundIndex === -1) {
console.log('${title}' を持つオブジェクトは見つかりませんでした。);
return null;
}
// 最初の要素の場合
if (foundIndex === 0) {
// 配列が空でないことを確認
if (templist.length > 0) {
console.log('${title}' は最初の要素です。配列の最後の要素を取得します。);
tempPageLink(prePageObj.title)
return;
} else {
console.log("配列が空のため、前の要素も最後の要素もありません。");
return;
}
} else {
// 最初の要素ではない場合
console.log('${title}' の前の要素を取得します。);
tempPageLink(prePageObj.title)
return;
}
}
return
}
});
次のページに移動するボタン
code:script.js
scrapbox.PageMenu.addMenu({
title: '次へ',
onClick: () => {
const path = location.pathname.split('/');
if (/^\d{4}年\d{1,2}月(.*)$/.test(decodeURIComponent(title))) {
console.log("月トピックページです")
const dataset = extractYearMonth(decodeURIComponent(title))
const d = new Date(dataset.year, dataset.month - 1, 1);
d.setMonth(d.getMonth() + 1);
// 新しい年と月を取得
const nextYear = d.getFullYear();
const nextMonth = d.getMonth() + 1; // getMonth()は0始まりなので+1する
const next = ${nextYear}年${nextMonth}月${dataset.titlebody};
tempPageLink(encodeURIComponent(next))
}
if (/^\d{4}%2F\d{1,2}%2F\d{1,2}$/.test(title)) {
console.log("日ごとノートです。")
const d = new Date(decodeURIComponent(title));
d.setDate(d.getDate() + 1);
// 年、月、日をそれぞれ取得
const year = d.getFullYear();
const month = d.getMonth() + 1; // getMonth() は0から11を返すため、+1する
const day = d.getDate();
// ゼロ埋めされていない形式で日付文字列を構築
const rowNext = ${year}/${month}/${day};
const next = encodeURIComponent(rowNext);
//location.href = https://scrapbox.io/${project}/${next};
tempPageLink(next)
}
if (/^\d{4}%2F\d{1,2}$/.test(title)) {
console.log("月ページです")
const parts = decodeURIComponent(title).split('/');
const year = parseInt(parts0, 10); const month = parseInt(parts1, 10); // 数字でない、または月が範囲外の場合はエラー
if (isNaN(year) || isNaN(month) || month < 1 || month > 12) {
console.error("Invalid year or month in title.", currentPageTitle);
return null;
}
// Dateオブジェクトを作成
// JavaScriptの月は0から始まるため、month-1 を渡す
// 日付は1日として設定することで、日による影響を排除
const d = new Date(year, month - 1, 1);
// 月を1ヶ月前に戻す
d.setMonth(d.getMonth() + 1);
// 新しい年と月を取得
const nextYear = d.getFullYear();
const nextMonth = d.getMonth() + 1; // getMonth()は0始まりなので+1する
const nextPrev = ${nextYear}/${nextMonth}
const next = encodeURIComponent(nextPrev);
tempPageLink(prev)
return
}
console.log("その他のページです")
const text = getPageText(title);
const category = extractCategory(text)
if (!category) return //指定したカテゴリがなければ終了
moveNextCategoryPage(category)
async function moveNextCategoryPage(category) {
// getrefPagesList は非同期処理なので await で待つ
const templist = await getrefPagesList(encodeURIComponent(category));
// 現在のページのタイトル (cosense.Page.title) を探す
// title という変数が見当たらないため、cosense.Page.title を直接参照します
console.log(category + "において" + cosense.Page.title + "を探します");
// templist (ページリスト) 内で現在のページのインデックスを見つける
const foundIndex = templist.findIndex(item => item.title === cosense.Page.title);
// --- 見つからなかった場合の処理 ---
if (foundIndex === -1) {
console.log('${cosense.Page.title}' を持つオブジェクトは見つかりませんでした。);
return null; // または適切なエラーハンドリング
}
// --- 次の要素の計算ロジック ---
let nextPageObj;
// 配列の最後の要素の場合
if (foundIndex === templist.length - 1) {
// 配列が空でないことを確認 (念のため)
if (templist.length > 0) {
console.log('${cosense.Page.title}' は最後の要素です。配列の最初の要素を取得します。);
nextPageObj = templist0; // 最後の要素なら最初の要素に戻る } else {
console.log("配列が空のため、次の要素も最初の要素もありません。");
return null;
}
} else {
// 最後の要素ではない場合
console.log('${cosense.Page.title}' の次の要素を取得します。);
}
// --- 新しいページのリンクを呼び出す ---
// tempPageLink が実際にページの遷移を行う関数と想定
if (nextPageObj) { // nextPageObj が null でないことを確認
tempPageLink(nextPageObj.title);
} else {
// ここに到達することは稀ですが、念のため
console.log("次のページオブジェクトが取得できませんでした。");
}
return; // 関数を終了
}
}
});
code:old-script.js
const intervalTime = 10000
let isStarted = false
let interval = undefined
scrapbox.PageMenu.addMenu({
title: 'random-screen-saver',
})
scrapbox.PageMenu('random-screen-saver').addItem({
title: () => (isStarted)? "end screen saver." : "start screen saver!",
onClick: () => {
isStarted = !isStarted
if (!isStarted) {
clearInterval(interval)
return
}
clickRandomButton()
interval = setInterval(clickRandomButton, intervalTime)
}
})
function clickRandomButton() {
const button = $('.random-jump-button')
if (!button) return
// location.href = button.attr('href');
}