scrapbox-card-bubble-2/pageCard
/emoji/warning.iconWIP
scrapbox-card-bubble-2でつかうscrapboxのページカード component
実装を考える
タグ名
page-card
使い方
code:html
<page-card
project="hoge"
title="huga"
theme="default-light"
description="text\ntext\ntext"
thumbnail="https://...">
</page-card>
descriptionにはカードに表示したいテキストを渡す
記法のparseはComponent内部で行う
scrapbox-card-bubble-2/parser
thumbnailがないか、404 Not Foundのときだけdescriptionが使われる
属性じゃなくて、methodで渡すほうがスッキリするかもしれないtakker.icon
projectとtitleは別にいいかもだけど
descriptionを属性としてhtmlにベタ打ちするのはなんか格好が悪い
属性のままにする
attributeChangedCallback()を使って自動更新できる
themeにはscrapboxのthemeの変数を入れる
機能
属性の変更を検知して色々変える
theme
色を変える
といってもComponent側ですることはない
CSSで:host[theme="..."]を書けばいいだけ
project/title
<a>のリンクを変更する
description
構文解析して#descriptionに反映する
thumbnail
#iconのsrcに適用する
error eventを検知し、もし読み込みに失敗したら、代わりに#descriptionを表示する
code
code:script.js
import {style, a, div, li, img, fragment, h} from '../easyDOMgenerator/script.js';
import {parse, css as cardCSS} from '../scrapbox-card-bubble-2%2Fparser/script.js';
import {css} from './css.js';
const TAG_NAME = 'related-page-card';
customElements.define(TAG_NAME, class extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(fragment(
style(css+cardCSS),
li(
a({id: 'link', type: 'link'},
div({id: 'hover'}),
div({id: 'content'},
div({id: 'header'},
div({id: 'title'})
),
div({id: 'description', style: 'display: none;'}),
div({id: 'icon', style: 'display: none;'}, img({loading: 'lazy'}))
),
),
),
));
this._link = shadowRoot.getElementById('link');
this._icon = shadowRoot.getElementById('icon');
this._description = shadowRoot.getElementById('description');
this._title= shadowRoot.getElementById('title');
}
get project() {
return this.getAttribute('project');
}
set project(value) {
this.setAttribute('project', value);
}
get href() {
return this._link.href;
}
get title() {
return this.getAttribute('title');
}
set title(value) {
this.setAttribute('title', value);
}
get thumbnail() {
return this.getAttribute('thumbnail');
}
set thumbnail(value) {
this.setAttribute('thumbnail', value);
}
set description(value) {
this.setAttribute('description', value);
}
// page cardを更新する
attributeChangedCallback(name, oldValue, newValue) { // (4)
switch (name) {
case 'project':
this._updateLink(newValue, this.title);
break;
case 'title':
this._updateLink(this.project, newValue);
break;
case 'description':
// DOMを入れ替える
this._description.textContent = '';
this._description.style = '';
this._description.append(...parse(newValue, this.project));
this._icon.style = 'display: none;';
break;
case 'thumbnail':
// 有効なURLでなければ何もしない
try {
new URL(newValue);
} catch(_) {
return;
}
this._icon.firstElementChild.src = newValue;
this._icon.style = '';
this._description.style = 'display: none;';
this._icon.firstElementChild.onerror = () => {
this._description.style = '';
this._icon.style = 'display: none;';
this._icon.firstElementChild.onerror = undefined;
};
break;
}
}
_updateLink(project, title) {
this._link.href = https://scrapbox.io/${project}/${encodeURIComponent(title)};''
this._title.textContent = title;
// 外部projectの場合は新しいタブで開くようにする
if (scrapbox.Project.name) {
this._link.removeAttribute('target');
this._link.rel = 'route';
} else {
this._link.removeAttribute('rel');
this._link.target = '_blank';
}
}
static get observedAttributes() {
return 'project','title','description', 'thumbnail';
}
});
export const relatedPageCard = (...params) => h(TAG_NAME, ...params);
/takker/takker
CSS
custom properties
[project]に応じてスタイルを変えられるようにしたいなtakker.icon
--card-bg
--card-box-shadow
--card-title-color
-card-title-bg
--card-box-hover-shadow
--card-hover-bg
--card-description-color
<li>部分
assets/app.css 7670行目の.grid liから取得
code:css.js
export const css = `
* {
box-sizing: border-box;
}
li {
display: block;
float: none;
position: relative;
width: 140px;
height: 140px;
}
<a>部分
code:css.js
a{
display: block;
position: relative;
height: inherit;
width: inherit;
overflow: hidden;
text-overflow: ellipsis;
font-family: "Roboto",Helvetica,Arial,"Hiragino Sans",sans-serif;
background-color: var(--card-bg, #fff);
box-shadow: var(--card-box-shadow, 0 2px 0 rgba(0,0,0,0.12));
border-radius: 2px;
color: var(--card-title-color, #555);
word-break: break-word;
text-decoration: none;
}
a:hover {
box-shadow: var(--card-box-hover-shadow, 0 2px 0 rgba(0,0,0,0.23));
}
a:focus {
outline: 0;
box-shadow: 0 0px 0px 3px rgba(102,175,233,0.6);
border-color: #66afe9;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s
}
#hover部分
必要性がよくわからないtakker.icon
aに統合できないか?
code:css.js
#hover {
opacity: 0;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-color: var(--card-hover-bg, rgba(0,0,0,0.05));
mix-blend-mode: multiply;
z-index: 1;
transition: background-color .1s
}
a:hover #hover{
opacity: 1;
}
a:active #hover{
opacity: 1;
background-color: var(--card-active-bg, rgba(0,0,0,0.1))
}
#content部分
code:css.js
#content {
height: calc(100% - 5px);
width: inherit;
display: flex;
flex-direction: column;
overflow: hidden;
}
#header部分
#headerと#titleを一緒にできないかなあtakker.icon
別々にしても意味ない気がする
code:css.js
#header {
width: 100%;
color: #396bdd;
text-overflow: ellipsis;
border-top: var(--card-title-bg, #f2f2f3) solid 10px;
padding: 10px 12px;
}
#title部分
display: -webkit-boxだとタイトルが見切れてしまったので変えた
何故かタイトルが折り返されない
https://gyazo.com/18eec4a0529656d126ae8b5497fd9edf
CSSは同じのはずなんだけど……
15:22:40 aにwhite-space: nowrapを入れてたのが原因だった
code:css.js
#title {
font-size: 14px;
line-height: 20px;
font-weight: bold;
max-height: 60px;
color: var(--card-title-color, #363c49);
margin: 0;
overflow: hidden;
display: block;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
}
#description部分
code:css.js
#description {
line-height: 20px;
padding: 10px 12px 0;
font-size: 12px;
white-space: pre-line;
column-count: 1;
column-gap: 2em;
column-width: 10em;
height: inherit;
color: var(--card-description-color, gray);
flex-shrink: 16;
overflow: hidden;
}
#description内のサムネイル用本文にあてるCSS
scrapbox-card-bubble-2/parser#60592fd41280f000001bcf84
#icon部分
code:css.js
#icon{
display: block;
width: 100%;
padding: 0 5px;
margin: 0 auto;
}
#icon img {
display: block;
width: 100%;
margin: 0 auto;
}
`;
#icon imgにしないと、サムネイル本文のアイコン記法がおかしくなってしまう
test code
code:js
import('/api/code/programming-notes/scrapbox-card-bubble-2%2FpageCard/test1.js')
.then(({execute}) => execute());
code:test1.js
import {relatedPageCard} from './script.js';
import {ul} from '../easyDOMgenerator/script.js';
export async function execute() {
const res = await fetch(/api/pages/${scrapbox.Project.name}/${encodeURIComponent(scrapbox.Page.title)});
const {relatedPages: {links1hop}} = await res.json();
document.getElementById('editor').append(ul({style: 'position: fixed; top: 5vh; left: 5vw; list-style: none;'},
...links1hop.map(({title, descriptions, image}) =>
relatedPageCard({
project: scrapbox.Project.name,
title,
thumbnail: image,
description: descriptions.join('\n')
})
)
));
}
#2021-05-01 10:56:47
#2021-01-15 17:57:47