jiro4989
プロフィール画像
https://lh4.googleusercontent.com/-j_KBtztsqEc/AAAAAAAAAAI/AAAAAAAAAFE/vBJ2jwo9y30/photo.jpg
プロフィール
誕生 西暦1995年
趣味
良さげなOSSにCIを導入するPRを出したり
一人用のゲームか協力ゲーが好き
対人ゲーは嫌い。全くやらない
ゲーム作る
音楽作る
運用してるアプリ
nimbot Nimのコードをコンパイルして実行するのをSlackのチャットからやるbot 使う人いなくなったので停止した
SNS
技術、ゲーム、イラスト話をしてることが多い
技術系のサービス
/icons/qiita.icon Qiita jiro4989 より汎用性の高い情報の発信、アドベントカレンダーとか。真面目に書いてる /icons/hr.icon
以降は全部スクリプトです
UserScript
code:script.js
// 選択範囲の文字列から作業にかかる見積もり工数の数値のみを抽出し、
// 合計で何時間(&何人日)工数がかかるのかを計算して、選択範囲のすぐ下に追記する
scrapbox.PopupMenu.addButton({
title: 'WorkTime',
onClick: text => {
// テキストの行末に時間の数値文字列(\d+h)が含まれるかを判定する
const existsTimeInt = (line) => line.match(/.*\d+h\s*$/);
// 文字列から時間文字列の数値部分のみ取得する
const getTimeString = (line) => {
return line.replace(/.* (\d+)h\s*$/, function() { return arguments1 }); }
// 選択範囲の文字列から時間(H)を取得して合計を算出する
const t = text.split("\n")
.filter((line) => existsTimeInt(line))
.map((line) => getTimeString(line))
.map((line) => Number(line))
.reduce((x, y) => x + y);
// 時間(人日)
const ht = t / 8;
// 表示用文字列の生成 (見積もり/実績)
// なお実績は手入力を想定する
const nt = text + "\n" +
" 見積もり\n" +
" " + t + " 時間\n" +
" " + ht + " 人日\n" +
" 実績\n" +
" ? 時間\n" +
" ? 人日";
return nt;
}
});
カレンダータグを生成する
code:script.js
scrapbox.PopupMenu.addButton({
title: 'AddCalendarTags',
onClick: text => {
const startYear = window.prompt("カレンダータグの開始年を入力してください。(yyyy)", "2019");
let str = "";
for (let i=1; i<=12; i++) {
str += [${startYear}/${i}] ;
if (i % 6 === 0) str += "\n";
}
return str;
}
});
code:script.js
setTimeout(() => {
// チェックボックスとして使用する文字セットのリスト
const checkboxSetList = [
];
/*
*/
const allBoxes = checkboxSetList.reduce((accu, current) => accu.concat(current), []);
const startsWithBoxReg = new RegExp('^\\s*(' + allBoxes.join('|') + ')');
const targetProject = scrapbox.Project.name;
class KeydownEvent {
constructor() {
this.textArea = document.getElementById('text-input');
this.event = document.createEvent('UIEvent');
this.event.initEvent('keydown', true, true);
}
dispatch(keyCode, withShift = false, withCtrl = false, withAlt = false, withCommand = false) {
this.event.keyCode = keyCode;
this.event.shiftKey = withShift;
this.event.ctrlKey = withCtrl;
this.event.altKey = withAlt;
this.event.metaKey = withCommand;
this.textArea.dispatchEvent(this.event);
}
}
// ボックスクリックでオンオフする
$('#app-container').off(click.toggleCheckBox_${targetProject}, '.lines');
$('#app-container').on(click.toggleCheckBox_${targetProject}, '.lines', async event => {
if (scrapbox.Project.name !== targetProject) {
$('#app-container').off(click.toggleCheckBox_${targetProject}, '.lines');
return;
}
const target = event.target;
if (!isFirstElementChild(target)||!isCharSpan(target, allBoxes)) return;
await new Promise(resolve => setTimeout(resolve, 30));
let lineString;
try {
lineString = getCursorLineString();
} catch (err) {
console.log(err);
return;
}
if (!startsWithBoxReg.test(lineString)) return;
const targetX = target.getBoundingClientRect().left;
const cursorX = document.getElementsByClassName('cursor')0.getBoundingClientRect().left; const keydownEvent = new KeydownEvent();
if (cursorX <= targetX) {
keydownEvent.dispatch(39); // →
}
keydownEvent.dispatch(8); // Backspace
const newBox = (() => {
const trimmedLineString = lineString.trim();
for (const checkboxSet of checkboxSetList) {
for (let i = 0; i < checkboxSet.length; i++) {
if (trimmedLineString.startsWith(checkboxSeti)) { }
}
}
return target.textContent;
})();
writeText(newBox);
// この下のコメントアウトを解除すると、checked時に取消線を入れて時刻を追記します
// Mac、Porterでのみ動作します
/*
if (/Mobile/.test(navigator.userAgent)) return;
const targetBoxSet = checkboxSetList0; if (!targetBoxSet.includes(newBox) || newBox === targetBoxSet0) return; await new Promise(resolve => setTimeout(resolve, 30));
keydownEvent.dispatch(39, true, false, false, true); // shift + command + →
writeText('-');
keydownEvent.dispatch(39, false, false, false, true); // command + →
const now = moment().format('HH:mm');
writeText( ${now});
*/
});
// ボックス行で改行すると次行にボックス自動挿入
$('#text-input').off(keydown.autoInsertCheckBox_${targetProject});
$('#text-input').on(keydown.autoInsertCheckBox_${targetProject}, async event => {
if (scrapbox.Project.name !== targetProject) {
$('#text-input').off(keydown.autoInsertCheckBox_${targetProject});
return;
}
switch (event.key) {
case 'Enter': {
let currentLineString;
try {
currentLineString = getCursorLineString();
} catch (err) {
console.log(err);
return;
}
if (!startsWithBoxReg.test(currentLineString)) return;
await new Promise(resolve => setTimeout(resolve, 30));
let nextLineString;
try {
nextLineString = getCursorLineString();
} catch (err) {
console.log(err);
return;
}
if (!startsWithBoxReg.test(nextLineString)) {
const trimmedLineString = currentLineString.trim();
const targetBoxSet = checkboxSetList.find(boxSet => {
return boxSet.some(box => trimmedLineString.startsWith(box));
});
writeText(targetBoxSet0); }
return;
}
default: {
return;
}
}
});
function isFirstElementChild(element) {
return element.parentNode.firstElementChild === element;
}
function getCursorLineString() {
return document.querySelector('.lines div.line.cursor-line').textContent;
}
function isCharSpan(element, targetCharList) {
return element.tagName === 'SPAN'
&& targetCharList.includes(element.textContent)
&& element.classList.value.split(' ').some(value => /^c\-\d+$/.test(value));
}
function writeText(text) {
const textArea = document.getElementById('text-input');
textArea.value = text;
textArea.dispatchEvent(new InputEvent('input', {bubbles: true, cancelable: true}));
}
}, 1500);
code:script.js
scrapbox.PageMenu.addMenu({
title: '見出し',
onClick: () => {
scrapbox.PageMenu('見出し').removeAllItems()
for (let line of scrapbox.Page.lines) {
if (!line.section.start) continue
if (!line.text.startsWith("[* ") && !line.text.startsWith("[** ") && !line.text.startsWith("[*** ")) continue
if (!line.nodes) continue
const image = ""
const noIcon = false
// hogeとhogeは先頭をインデントする。
const marginLeft = line.text.startsWith("[* ") ? " ・" : ""
const marginLeft2 = line.text.startsWith("[** ") ? "●" : ""
const title = marginLeft + marginLeft2 + renderPlainText(line.nodes)
const onClick = () => location.hash = line.id
scrapbox.PageMenu('見出し').addItem({ title, image, onClick })
}
}
})
function renderPlainText(node) {
if (node instanceof Array) return node.map(node => renderPlainText(node)).join('')
if (typeof node === 'string') return node
switch (node.type) {
case 'icon':
case 'strong-icon':
return node.unit.page
}
return renderPlainText(node.children)
}
function getIconUrl(node) {
if (/icon/.test(node.type)) {
return /api/pages/${node.unit.project||scrapbox.Project.name}/${node.unit.page}/icon
}
if (node instanceof Array) {
return node.map(getIconUrl).find(img => img)
}
return null
}
参考
https://gyazo.com/364f369f7714b4e7fb2a6ed1ce5b58de 発表スライド用