generated at
scrapbox-card-container

UI
<card-container>
属性
cards
表示するcardのデータ
↓の形式のobjectをJSON文字列にして入れる
ts
type Cards = { project: string; title: string; theme: string; linked: number; updated: number; description: string; image?: string; }[];
sort
cardの並び順
linked
updated
title
機能
閉じるボタンをつけたい
主にmobile用
PCではmouseoverしたら消えるようにする
optionでつけないようにもできる
css.js
export const css = ` :host { background-color: #FFF; box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12); position: absolute; max-width: 80vw; box-sizing: content-box; z-index: 9000; } ul { display: flex; padding: 0px; margin: 0px; list-style: none; overflow-x: auto; overflow-y: visible; } ul * { padding: 5px } `;

script.js
import {style, ul, h, fragment} from '../easyDOMgenerator/script.js'; import {css} from './css.js'; import {relatedPageCard} from '../scrapbox-card-bubble-2%2FpageCard/script.js'; const TAG_NAME = 'card-container'; customElements.define(TAG_NAME, class extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({mode: 'open'}); shadowRoot.appendChild(fragment(style(css),ul({id: 'list'}))); this._list = shadowRoot.getElementById('list'); this._eventCallbacks = []; } get cards() { return JSON.parse(this.getAttribute('cards')); } get sort() { return this.getAttribute('sort') ?? 'linked'; } position({top, left}) { this.style.top = `${top}px`; this.style.left = `${left}px`; } show() { // 空っぽの場合は表示しない // defaultで<style>が含まれるので、nodeが2個以上のときのみ表示する if (this._list.childElementCount === 0) { this.hide(); return; } this.hidden = false; } hide() { this.hidden = true; } on(event, selecter, callback) { if (this._eventCallbacks[event]) this.shadowRoot.removeEventListener(event, this._eventCallbacks[event], {capture: true}); this._eventCallbacks[event] = e => { if (!e.target.matches(selecter)) return; callback(e); }; this.shadowRoot.addEventListener(event, this._eventCallbacks[event], {capture: true}); } // page cardを更新する attributeChangedCallback(name, oldValue, newValue) { // (4) switch (name) { case 'cards': const cards = JSON.parse(newValue).sort((a,b) => { switch(this.sort) { case 'updated': return b.updated - a.updated; case 'linked': default: return b.linked - a.linked; } }); if (this._list.children.length > cards.length) { [...this._list.children].slice(cards.length).forEach(card => card.remove()); } this._list.children.forEach((card, i) => { card.project = cards[i].project; card.title = cards[i].title; card.description = cards[i].description; card.thumbnail = cards[i].image; card.dataset.linked = cards[i].linked; card.dataset.updated = cards[i].updated; }); if (this._list.children.length < cards.length) { this._list.append(...cards .slice(this._list.children.length) .map(({project, title, description, image, linked, updated}) => relatedPageCard({ project, title, description, thumbnail: image, 'data-linked': linked, 'data-updated': updated, }) ) ); } break; case 'sort': const sortedCards = [...this._link.children] .sort((a,b) => { switch(this.sort) { case 'updated': return b.dataset.updated - a.dataset.updated; case 'linked': default: return b.dataset.linked - a.dataset.linked; } }); this._link.children.forEach(card => card.remove()); this._link.append(...sortedCards); break; } } static get observedAttributes() { return ['cards', 'sort']; } }); export const cardContainer = (...params) => h(TAG_NAME, ...params);

test code
js
import('/api/code/programming-notes/scrapbox-card-container/test1.js');
test1.js
import {cardContainer} from './script.js'; import {scrapboxDOM} from '../scrapbox-dom-accessor/script.js'; (async () => { const path = '/takker/2021-04-28'; const res = await fetch(`/api/pages${path}`); const {relatedPages: {links1hop}} = await res.json(); const box = cardContainer({cards: JSON.stringify( links1hop.map(({title, descriptions, image, linked, updated}) => ({ project: 'takker', title, description: descriptions.join('\n'), image, linked, updated, }) ) )}); scrapboxDOM.editor.append(box); const observer = new MutationObserver(async () =>{ box.position({ top: parseInt(scrapboxDOM.cursor.style.top) - box.clientHeight, left: parseInt(scrapboxDOM.cursor.style.left), }); box.show(); }); observer.observe(scrapboxDOM.cursor, {attributes: true}); })();