generated at
personal-pin
個人用ピン留めscript
全体には反映されず、自分にだけ反映される
mod.js ではなくpersonal-pin/@types/ mod.d.ts の内容を表示している

ピン留めの個人版Mijinko_SDnishio
プロジェクト全体に反映されるのではなく、自分にしか反映されないピン留め
pin-diary-3に手を加えれば作れそうtakker
なるほど、これがあれば個人projectの自分用ピン留めと来訪者用ピン留めができるかnishio
リマインドにも使えそうtakker
もしこれができるようになったら、@ページをピン留めしたい…Mijinko_SD
と思ったけれど@ページ関しては、いつでもアクセスしたいわけじゃなくて通知を受け取りたいだけだから違うか
仮にピン留めしたとしても新着に気付けるわけではない
それなら未読のときだけpinされるようにすればいいなtakker
未読検出は簡単
天才Mijinko_SD
使用例:未読の@ページをピンする
unreadDM.js
import { launch } from "./mod.js"; const title = "@Mijinko_SD"; launch(async () => { const res = await fetch(`/api/pages/villagepump/${title}`); if (!res.ok) return []; const { persistent, lastAccessed, updated } = await res.json(); return persistent && lastAccessed < updated ? [title] : []; });

2022-11-18 これ動いていますか?というかそもそも使っている人いますか?takker
自分は使っていないので、まともに動作しているのかわからない
1箇所修正すれば動作しましたMijinko_SD
テスト時のソースコード:/Mijinko-other/villagepump/personal-pin
修正ありがとうございます!takker

名付けてみたがなんか微妙takker
BetterDiscordに似たような名前のプラグインがあるMijinko_SD
あらあらtakker
別の名前にしよう
いい感じの名前かいてけ
秘密ピン
こっそりピン留め
あとで読む
安直かなwogikaze
あとで読む以外の使い道もあるのでびみょいtakker
myMyピン止め
機能の特徴が簡潔に書かれていてわかりやすいと思うMijinko_SD
個人的にはmは大文字の方が好みMijinko_SD
大文字にしたtakker

2022-06-14
18:43:17 できたtakker
bundleなしで、そのまま実行できるようにした
型チェックはしたけど動作は未検証
試してみてください

コアモジュール
launch にピン止めしたいページを渡して実行する
titles にページのタイトルのリストを渡す
前から順にピン止めされる
函数を渡すこともできる
更新するたびにピンするページを変えたいときに使う
asyncがついた函数も渡せるMijinko_SD
函数を渡した場合、その函数はページ名が入った配列を返さなければならないMijinko_SD
iteratorでも可takker
実装はpin-diary-3の改造
arrow functionsにしたり型をちゃんと書いたり不要な函数を削ったりしている
$ deno check --remote -r=https://scrapbox.io https://scrapbox.io/api/code/villagepump/personal-pin/mod.js
mod.js
// @ts-check /// <reference no-default-lib="true" /> /// <reference lib="esnext" /> /// <reference lib="dom" /> /// <reference types="/api/code/villagepump/personal-pin%2F@types/mod.d.ts" /> /** * @typedef {import("https://raw.githubusercontent.com/scrapbox-jp/types/0.3.5/scrapbox.ts").Scrapbox} Scrapbox */ /** @type {Scrapbox} */ // @ts-ignore declare使えないので無理やり色々やっている const scrapbox = window.scrapbox; /** @typedef {object} LaunchOptions * @property {string} [project] ピン留めするプロジェクト名 既定は現在のプロジェクト名 * @property {number} [interval=60000] ピン留め更新処理の間隔 */ /** ピン留め処理を開始する * * @param {Iterable<string>|(()=>(Iterable<string>|Promise<Iterable<string>>))} titles ピン留めしたいページのタイトル * @param {LaunchOptions} [options] オプション * @return {()=>void} 後始末用函数 */ export const launch = (titles, options) => { const { project = scrapbox.Project.name, interval = 60000 } = options ?? {}; /** @type {number | undefined} */ let updateTimer; const end = () => clearInterval(updateTimer); const start = async () => { end(); await updatePins(project, titles); updateTimer = setInterval(() => updatePins(project, titles), interval); }; const handleChange = () => // トップページでのみ起動する scrapbox.Layout === "list" && !location.pathname.startsWith("/stream") && scrapbox.Project.name === project
ここ !== じゃなくて === かも ↑ Mijinko_SD
ほんとだtakker
直しました

mod.js
? start() : end(); handleChange(); scrapbox.addListener("layout:changed", handleChange); //トップページからトップページに移動してもlayout:changedは呼ばれないので、ここでhandleChangeをかける scrapbox.addListener("project:changed", handleChange); return () => { end(); scrapbox.removeListener("layout:changed", handleChange); scrapbox.removeListener("project:changed", handleChange); }; }; /** ピン留め処理 * * @param {string} project プロジェクト名 * @param {Iterable<string>|(()=>(Iterable<string>|Promise<Iterable<string>>))} titles ピン留めしたいページのタイトル */ const updatePins = async (project, titles) => { /** タイトルのリスト * * `titles`の型を調整したもの * * @type {Iterable<string>} */ let list; if (typeof titles === "function") { const result = titles(); list = result instanceof Promise ? await result : result; } else { list = titles; } // ピン留めカードを一旦全部消してから作り直す deletePseudoCards(); for (const title of list) { const card = makePseudoCard(project, title); // ピン留め済みなら何もしない if (card.classList.contains("pin")) continue; const pins = listPins(); pin(card); if (pins.length > 0) { const lastPin = pins[pins.length - 1]; lastPin.parentElement?.insertBefore?.(card, lastPin); } else { cardList()?.insertAdjacentElement?.("afterbegin", card); } } }; /** 指定したカードをpinする * * @param {HTMLLIElement} card * @return {void} */ const pin = (card) => { card.getElementsByClassName("hover")?.[0] ?.insertAdjacentHTML("afterend", '<div class="pin"></div>'); card.classList.add("pin"); }; /** トップページのカードリストを取得する */ const cardList = () => { const cards = document.getElementsByClassName("page-list")?.[0] ?.getElementsByClassName( "grid", )?.[0]; if (!cards) return undefined; if (!(cards instanceof HTMLUListElement)) { throw TypeError('".page-list .grid" must be ul.grid'); } return cards; }; /** ピン留めページを取得する */ const listPins = () => Array.from( cardList()?.getElementsByClassName?.( "page-list-item grid-style-item pin", ) ?? [], ); const id = "personal-pin-card"; /** ページカードを作る * * ページリストにあるときはそれをコピーし、ないときは空のページカードを作る * @param {string} project プロジェクト名 * @param {string} title ページタイトル * @return {HTMLLIElement} */ const makePseudoCard = (project, title) => { const path = `/${project}/${encodeTitleURI(title)}`; const card = cardList()?.querySelector?.( `li.page-list-item a[href="${path}"]`, )?.parentNode; if (!card) { const card = document.createElement("li"); card.dataset.id = id; card.classList.add("page-list-item", "grid-style-item"); card.style.opacity = "0.7"; card.insertAdjacentHTML( "beforeend", `<a href="/${scrapbox.Project.name}/${ encodeURIComponent(title) }" rel="route"> <div class="hover"></div> <div class="content"> <div class="header"> <div class="title">${title}</div> </div> <div class="description"> <div class="line-img"> <div></div><div></div><div></div><div></div><div></div> </div> </div> </div> </a>`, ); return card; } const cloned = card.cloneNode(true); if (!(cloned instanceof HTMLLIElement)) { throw TypeError('"li.page-list-item" must be ul.grid'); } cloned.dataset.id = id; return cloned; }; /** このscriptで作成したカードを全部消す */ const deletePseudoCards = () => { document.querySelectorAll(`li[data-id="${id}"]`) .forEach((card) => card.remove()); }; const noEncodeChars = '@$&+=:;",'; const noTailChars = ':;",'; /** titleをURIで使える形式にEncodeする * * ported from https://github.com/takker99/scrapbox-userscript-std/blob/0.14.5/title.ts#L28 * * @param {string} title 変換するtitle * @return {string} 変換後の文字列 */ const encodeTitleURI = (title) => { return [...title].map((char, index) => { if (char === " ") return "_"; if ( !noEncodeChars.includes(char) || (index === title.length - 1 && noTailChars.includes(char)) ) { return encodeURIComponent(char); } return char; }).join(""); };