ushiwaka
https://scrapbox.io/files/6870c5368566de327d7411af.jpg
About me
ushiwaka(うしわか)、2025年8月現在19歳
名前の由来は
繋がりは薄いが先祖に牛若丸がいた
ボカロPである 故 wowaka氏のアルファベットの手触りが好き
海外で活躍することを考えて英語表記
多摩美術大学メディア芸術学部
2022年3月~ イラスト制作開始
目標はマルチクリエイター
絵に関して言えば未経験から三年でここまで来れたのだから、それに懸けれる熱意とかみたいな、確かにそういう才能は少しはあったのかもしれない
少し前に「ushiwakaさんは将来イラストレーターになられるのですか?」と聞かれたとき、はいそうですと答えられなかった
特に苦労せずただただ熱中して歩んで来たものに対して、一生を捧げられる覚悟は私にはまだないのだと気づいた
イラストの表現方法の模索のために世界を広げてみると、まだまだ自分の知らない魅力的な表現で溢れていることを知った
いつしか「表現はイラストを通してのみ行われる」と無意識に錯覚していた私にとって大きな見落としだった
普段何となく観たり聴いたりしている映像アニメ音楽を「受け身的なメディア」⇒「人の手で作られたメディア」として楽しむと、なんだか自分もやりたくなってきた
ということで今はイラストレーターを目指すというよりは、いろんな表現方法を使ってものづくりを楽しみたいと思っています、そういう意味で 私の目標はマルチクリエイターです
---
日記テンプレート
code:style.css
/* ②のやり方を DailyReport に適用(Font Awesomeで表示、画像は隠す) */
button#DailyReport.tool-btn:hover { text-decoration: none; }
button#DailyReport.tool-btn::before {
position: absolute;
content: '\f067'; /* 好みでアイコンは変更OK */
font: 900 21px/46px 'Font Awesome 5 Free';
}
button#DailyReport.tool-btn img { opacity: 0; }
code:script.js
// ==UserScript==
// @name DailyReport (FA icon) + Weekly tag on Monday
// @namespace https://scrapbox.io/
// @version 1.3
// @description 入力した日付でテンプレ展開。月曜なら最初のナビ直下に週タグを1行。#日記は最初のナビの直前に配置。
// ==/UserScript==
(async () => {
// --- dayjs を読み込み(非モジュール) ---
const importExternalJs = (url) =>
new Promise((res, rej) => {
if (document.querySelector(script[src="${url}"])) return res();
const s = document.createElement("script");
s.src = url;
s.addEventListener("load", res);
s.addEventListener("error", rej);
document.body.appendChild(s);
});
await importExternalJs("https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.8.36/dayjs.min.js");
// insertText(失敗時フォールバック付き)
let insertText;
try {
({ insertText } = await import("/api/code/customize/scrapbox-insert-text/script.js"));
} catch {
insertText = ({ text }) => {
const ta = document.getElementById("text-input");
if (!ta) return;
ta.focus();
const start = ta.selectionStart || 0;
ta.setRangeText(text);
ta.selectionStart = ta.selectionEnd = start + text.length;
const ev = document.createEvent("UIEvent");
ev.initEvent("input", true, false);
ta.dispatchEvent(ev);
};
}
// --------- フォーマッタ ----------
const pad2 = (n) => String(n).padStart(2, "0");
// アーカイブ用: YYYY_MM/DD, YYYY_MM
const fmtUDate = (d) => ${d.year()}_${pad2(d.month() + 1)}/${pad2(d.date())};
const fmtUMonth = (d) => ${d.year()}_${pad2(d.month() + 1)};
// --------- テンプレ生成(#日記をナビ直前に移動) ----------
function buildBody(today /* dayjs */, yesterday, tomorrow) {
const line = <- [${fmtUDate(tomorrow)}] / [${fmtUMonth(today)}] / [${fmtUDate(yesterday)}] ->;
// 月曜なら週タグを1行(例: 2025/08/18-08/24)
const isMonday = today.day() === 1; // 0=Sun,1=Mon
const weekEnd = today.clone().add(6, "day");
const weeklyTag = [${today.format("YYYY/MM/DD")}-${pad2(weekEnd.month() + 1)}/${pad2(weekEnd.date())}];
// 並び:
// 1) YYYY/MM/DD(本文1行目)※タイトルは別で存在
// 2) #日記
// 3) ナビ
// 4) (月曜のみ)週タグ
// 5) 空行×5
// 6) ナビ(再掲)
return [
${today.format("YYYY/MM/DD")},
#日記,
${line},
...(isMonday ? weeklyTag : []),
"", "", "", "", "", // 5空行
${line},
].join("\n");
}
// --------- メニュー(①ベース。CSSで②のFAアイコンを当てる) ----------
scrapbox.PageMenu.addMenu({
title: "DailyReport", // ← CSS側で button#DailyReport に②のFAアイコンを適用
image: "https://scrapbox.io/assets/img/logo.png",
onClick: async () => {
// タイトルのみの新規ページでのみ実行
if (!scrapbox.Page.lines || !(scrapbox.Page.lines.length == 1)) return;
const input = prompt("テンプレを展開する日付を相対(+N / -N)または絶対(YYYY-M-D)で入力(空欄=今日)");
if (input === null) return;
const raw = (input || "").trim();
const diff = raw ? parseInt(raw, 10) : 0;
const isRel = raw === "" || !Number.isNaN(diff);
const isAbs = raw.split("-").length === 3 && dayjs(raw).isValid();
if (!isRel && !isAbs) return;
const base = isAbs ? dayjs(raw).startOf("day") : dayjs().startOf("day").add(diff, "day");
const yesterday = base.clone().subtract(1, "day");
const tomorrow = base.clone().add(1, "day");
const ok = confirm(対象の日付は ${base.format("YYYY.M.D")} でよいですか?);
if (!ok) return;
insertText({ text: buildBody(base, yesterday, tomorrow) });
},
});
})();
code:script.js
// ---- split pins/non-pins with robust SPA handling & fail-safe ----
(function splitPinsStable(){
const GRID_SEL = '.page-list ul.grid';
let gridObs = null, rootObs = null, failTimer = null;
const raf = () => new Promise(r => requestAnimationFrame(r));
const isPin = li => li.classList.contains('pin') || li.classList.contains('pinned');
function measureCols(grid){
const cs = getComputedStyle(grid);
const tpl = cs.gridTemplateColumns;
if (tpl && tpl !== 'none') {
const n = tpl.split(' ').filter(Boolean).length;
if (n > 0) return n;
}
// CSSが未適用の瞬間などは推測(PC想定=7)
return matchMedia('(min-width:1024px)').matches ? 7 : 2;
}
const hasBigFirst = grid =>
!!grid.querySelector(':scope > li.page-list-item:first-child');
function calcRows(pinCount, cols, big){
if (pinCount <= 0) return 0;
const cap = big ? Math.max(0, cols - 2) : cols; // 1〜2行目の空き
let rem = pinCount, rows = 0;
rows++; rem -= cap;
if (rem > 0){ rows++; rem -= cap; }
if (rem > 0){ rows += Math.ceil(rem / cols); }
return rows;
}
function reset(items){
items.forEach(li=>{
li.style.removeProperty('grid-row');
li.style.removeProperty('grid-column');
delete li.dataset.rowLocked;
});
}
function hideWhileWorking(grid){
grid.classList.add('rowbreak-working');
clearTimeout(failTimer);
// 何があっても最長1秒で必ず表示に戻す
failTimer = setTimeout(()=> grid.classList.remove('rowbreak-working'), 1000);
}
function showAfter(grid){
clearTimeout(failTimer);
grid.classList.remove('rowbreak-working');
}
function apply(){
const grid = document.querySelector(GRID_SEL);
if (!grid) return;
hideWhileWorking(grid);
try {
const items = Array.from(grid.querySelectorAll(':scope > li.page-list-item'));
const pins = items.filter(isPin);
const rest = items.filter(li => !isPin(li));
reset(items);
const cols = measureCols(grid);
const rows = calcRows(pins.length, cols, hasBigFirst(grid));
rest.forEach((li, i)=>{
const row = rows + 1 + Math.floor(i / cols);
const col = (i % cols) + 1;
li.style.setProperty('grid-row', ${row} / span 1, 'important');
li.style.setProperty('grid-column', ${col} / span 1, 'important');
li.dataset.rowLocked = '1';
});
} finally {
showAfter(grid);
}
}
function attachGridObserver(){
const grid = document.querySelector(GRID_SEL);
if (!grid) return;
if (gridObs) gridObs.disconnect();
gridObs = new MutationObserver(apply);
gridObs.observe(grid, { childList: true });
}
async function boot(){
// 初期:グリッド出現まで待ち→適用
for (let i=0; i<180 && !document.querySelector(GRID_SEL); i++) {
await raf();
}
apply();
attachGridObserver();
// SPA遷移でグリッドが差し替わるのを常時監視
if (rootObs) rootObs.disconnect();
rootObs = new MutationObserver((muts)=>{
for (const m of muts){
const nodes = ...m.addedNodes, ...m.removedNodes;
if (nodes.some(n => n.nodeType===1 && (n.matches?.(GRID_SEL) || n.querySelector?.(GRID_SEL)))){
requestAnimationFrame(()=>{ apply(); attachGridObserver(); });
break;
}
}
});
rootObs.observe(document.body, { childList:true, subtree:true });
// 履歴遷移/復帰・リサイズ・タブ復帰でも再適用
addEventListener('popstate', ()=> requestAnimationFrame(()=>{ apply(); attachGridObserver(); }));
addEventListener('pageshow', ()=> requestAnimationFrame(()=>{ apply(); attachGridObserver(); }));
addEventListener('resize', ()=> apply(), { passive:true });
document.addEventListener('visibilitychange', ()=> {
if (document.visibilityState === 'visible') apply();
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot, { once:true });
} else {
boot();
}
})();
code:script.js
import("https://scrapbox.io/api/code/ushiwaka/diary-template.js");
code:diary-template.js
/* auto diary @ 04:00, with #日記 / keep API-less + small changes */
/* ---- small helper to load dayjs (same style as original) ---- */
export const importExternalJs = async (url) =>
new Promise((res, rej) => {
if (document.querySelector(script[src="${url}"])) return res();
const s = document.createElement("script");
s.src = url;
s.addEventListener("load", res);
s.addEventListener("error", rej);
document.body.appendChild(s);
});
/* load dayjs (non-module) */
await importExternalJs("https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.8.36/dayjs.min.js");
/* ---- insertText (original style, no dependency) ---- */
export function insertText({ text }) {
const cursor = document.getElementById("text-input");
if (!cursor) return;
cursor.focus();
const start = cursor.selectionStart || 0;
cursor.setRangeText(text);
cursor.selectionStart = cursor.selectionEnd = start + text.length;
const uiEvent = document.createEvent("UIEvent");
uiEvent.initEvent("input", true, false);
cursor.dispatchEvent(uiEvent);
}
/* ---------------- date helpers (keep original formats) ---------------- */
const fmtD = (d) => dayjs(d).format("YYYY.MM.DD");
const fmtM = (d) => dayjs(d).format("YYYY.MM");
/* same template as your menu version, but最後のタグを #日記 に */
const templateFor = (today) => {
const y = dayjs(today).subtract(1, "day");
const t = dayjs(today).add(1, "day");
return (
${fmtD(today)}\n +
<- [${fmtD(t)}] / [${fmtM(today)}] / [${fmtD(y)}] ->\n +
\n\n\n\n +
<- [${fmtD(t)}] / [${fmtM(today)}] / [${fmtD(y)}] ->\n +
#日記
);
};
/* ---------------- Scrapbox navigation helpers ---------------- */
function isEmptyNewPage() {
const lines = scrapbox?.Page?.lines;
return Array.isArray(lines) && lines.length === 1; // title only
}
async function ensureOnTitle(title) {
if (scrapbox.Page?.title === title) return;
const proj = encodeURIComponent(scrapbox.Project.name);
location.href = /${proj}/${encodeURIComponent(title)};
// wait SPA mount
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 100));
if (scrapbox?.Page?.title === title) break;
}
}
/* ---------------- create once for a given date ---------------- */
async function createDiaryFor(dateObj) {
const title = fmtD(dateObj);
await ensureOnTitle(title);
if (!isEmptyNewPage()) return; // already has body → do nothing
insertText({ text: templateFor(dateObj) });
}
/* ---------------- 04:00 auto schedule (local time) ---------------- */
(function scheduleNext04() {
const now = new Date();
const next = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 4, 0, 0, 0);
if (now >= next) next.setDate(next.getDate() + 1);
const delay = next.getTime() - now.getTime();
setTimeout(async () => {
try {
await createDiaryFor(new Date()); // today
} finally {
scheduleNext04(); // re-arm
}
}, delay);
console.log("diary@04:00 next:", dayjs(next).format("YYYY.MM.DD HH:mm"));
})();
/* ---------------- (optional) manual menu stays available ---------------- */
scrapbox.PageMenu.addMenu({
title: "DailyDiary",
image: "https://cdnjs.cloudflare.com/ajax/libs/twemoji/12.0.4/svg/1f4dd.svg",
onClick: () => {
if (!isEmptyNewPage()) {
alert("空の新規ページ(タイトルのみ)の状態で実行してください。");
return;
}
const input = prompt("日付: 空欄=今日 / +N, -N / 2025-08-16, 2025.8.16, 2025/08/16 など");
if (input === null) return;
const raw = (input || "").trim();
let base = dayjs().startOf("day");
if (!raw) {
/* today */
} else if (/^+-?\d+$/.test(raw)) {
base = base.add(parseInt(raw, 10), "day");
} else {
const m = raw.match(/^(\d{4}).\-/(\d{1,2}).\-/(\d{1,2})$/);
if (!m) return alert("形式が不正です");
const d = dayjs(new Date(+m1, +m2 - 1, +m3));
if (!d.isValid()) return alert("日付が不正です");
base = d.startOf("day");
}
createDiaryFor(base.toDate());
},
});
code:script.js