記事の見た目をいじる の変更点
更新- 追加された行はこの色です。
- 削除された行はこの色です。
- プログラミング/svelte/記事の見た目をいじる へ行く。
- プログラミング/svelte/記事の見た目をいじる の差分を削除
[[プログラミング/svelte]]
* いろいろきれいに見えるようにする [#k9f0334b]
#contents
* メモ: style の :global について [#xfd2fae0]
svelte の style 定義には自動的にコンポーネントインスタンスの id が追加されるため、
そのコンポーネントに直接含まれる(子コンポーネントは除外)エレメントにしか効果を及ぼさない。
コンポーネントを超えて効果を期待する場合には :global を付ける。
https://svelte.jp/docs/svelte-components#style
LANG: html
<style>
:global(strong) {
/* これはすべての <strong> に適用されます */
margin: 0;
}
div :global(strong) {
/* これは「このコンポーネント内の <div> 要素」の中にある
「すべての <strong> 要素」に
適用されます */
color: goldenrod;
}
</style>
上記の例では div に :global がかかっていないため、div にはコンポーネントインスタンスの
ID が付き、その下にあることが条件になっている。
「このコンポーネントの子供」に適用したい場合にはこのテクニックが役に立ちそう。
* ArticleElement コンポーネントを作成 [#q2fec75a]
db の Article と同名だとややこしいことになるようなので ArticleElement とした。
src/routes/articles/ArticleElement.svelte に置くことも考えたのだけれど、
関連コードが増えてフォルダーを掘りたくなる気もしたので
src/lib/components/ArticleElement.svelte にした
LANG: html
<script lang="ts" context="module">
import { marked } from 'marked';
import { baseUrl } from 'marked-base-url';
import markedLinkifyIt from 'marked-linkify-it';
import { markedHighlight } from 'marked-highlight';
import hljs from 'highlight.js';
import markedKatex from 'marked-katex-extension';
import { page } from '$app/stores';
import 'highlight.js/styles/stackoverflow-dark.min.css';
marked.use(baseUrl('https://example.com/folder/'));
const linkifyItSchemas = {};
const linkifyItOptions = {};
marked.use(markedLinkifyIt(linkifyItSchemas, linkifyItOptions));
marked.use(
markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
},
})
);
marked.use(
markedKatex({
throwOnError: false,
})
);
</script>
<script lang="ts">
export let title: string;
export let author: string;
export let date: Date;
export let body: string;
export let permLink: string;
</script>
<article class="prose max-w-full p-4">
<h1>{title}</h1>
<div>
<span>{author}</span>
<span>{date.toLocaleString()}</span>
<span><a href={permLink + '/edit'}>編集</a></span>
</div>
<content>
{@html marked.parse(body)}
</content>
</article>
src/routes/articles/[...titleOrId]/+page.svelte
LANG: html
<script lang="ts">
import type { PageData } from './$types';
import ArticleElement from '$lib/components/ArticleElement.svelte';
import { encodeTitle } from '../lib';
export let data: PageData;
</script>
<ArticleElement
title={data.article.title}
body={data.article.body}
author={data.article.author.name}
date={data.article.createdAt}
permLink={data.urlRoot + '/articles/' + encodeTitle(data.article.title)}
/>
* 見出しのレベルを調整 [#s2a8d1d1]
ブログ記事ではタイトルが h1 なので本文中では一番大きいのが h2 になる。
LANG: ts
// デフォルトのレンダラ
const vanillaRenderer = new marked.Renderer();
// 見出しの階層を1つ下げる
marked.use({
renderer: {
heading(text, level, raw) {
return vanillaRenderer.heading(text, level + 1, raw);
}
}
});
* ベース URL [#db9a96e3]
LANG: ts
import { baseUrl } from 'marked-base-url';
import { PUBLIC_URL_ROOT } from '$env/static/public';
...
// ベースアドレス
marked.use(baseUrl(PUBLIC_URL_ROOT));
* シンタックスハイライト [#fea7e75c]
痒い所に手が届くよう、marked-highlight を使わず自分でやろう。
- highlightjs-svelte
- highlightjs-cobol
- highlightjs-iptables
- highlightjs-vba
あたりが気になるけど、さしあたりは svelte だけ入れとく。
LANG: console
$ pnpm rm marked-highlight
$ pnpm i highlightjs-svelte
$ pnpm add --save-dev @types/highlightjs-svelte
ERR_PNPM_FETCH_404 GET https://registry.npmjs.org/@types%2Fhighlightjs-svelte: Not Found - 404
This error happened while installing a direct dependency of C:\Users\osamu\Desktop\svelte\authtest
@types/highlightjs-svelte is not in the npm registry, or you have no permission to fetch it.
No authorization header was set for the request.
Progress: resolved 42, reused 42, downloaded 0, added 0
あーと、highlightjs-svelte にはタイプ定義が提供されていない。
仕方がないので、
src/node_modules/@types/highlightjs-svelte.d.ts
LANG: ts
import type { HLJSApi } from 'highlight.js';
declare function hljs_svelte(hljs: HLJSApi): void;
export default hljs_svelte;
を作成した。どうやら src/@types ではだめで src/node_module/@types じゃないと読み込んでくれないらしい。
で、これだと .gitignore の node_module というルールに引っ掛かってしまうので、
.gitignore
- node_modules
+ node_modules/*
+ !/src/node_modules/@types/*
として、このフォルダの中身だけ特別にコミットするようにした。
** marked のエクステンション codeRenderer を作る [#z7d15cbe]
コンポーネントからはこのように使える。
src/lib/components/ArticleElement.svelte
LANG: html
<style context="module">
+ import { codeRenderer } from './codeRenderer';
+ import { decolatorsInfo } from './codeDecolators/codeDecolatorInfo';
+
+ // 各デコレータが追加する css をまとめる
+ const codeRendererCss = decolatorsInfo.map(info=>info.css).join("\n");
...
// シンタックスハイライト
+ marked.use(codeRenderer);
...
</script>
+ <svelte:head>
+ {@html '<style>' + codeRendererCss + '</style>'}
+ </svelte:head>
decolatorsInfo には孫ライブラリが必要な css を個別に追加するので、
それを繋げてヘッダーに入れている。
LANG: html
<style>{@html codeRendererCss}</style>
としたのでは動かなかった。<style> タグ自体を文字列として生成したらうまくいった。
codeRenderer は marked からコードブロックごとに呼び出されることになる。
デコレータ的なアルゴリズムで
- rowsDecorator rows(50) などとして表示行数を変更できる
- numDecorator 行番号付与
- diffDecorator diff 的な +/- 行に色を付ける
- splitterDecorator 行ごとに処理できるよう改行単位でタグを閉じる
- highlightDecorator シンタックスハイライト
などを各 Decorator に分業させてコードブロックを処理する。
src/lib/components/codeRenderer.ts
LANG: ts
import { rowsDecorator } from './codeDecolators/rowsDecorator';
import { numDecorator } from './codeDecolators/numDecorator';
import { diffDecorator } from './codeDecolators/diffDecorator';
import { splitterDecorator } from './codeDecolators/splitterDecorator';
import { highlightDecorator } from './codeDecolators/highlightDecorator';
// HTML の特殊文字をエスケープ
// https://stackoverflow.com/questions/1787322/what-is-the-htmlspecialchars-equivalent-in-javascript
export function escapeHtml(text: string) {
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
return text.replace(/[&<>"']/g, (c) => map[c as keyof typeof map]);
}
function unescapeHtml(text: string) {
const map = { '&':'&', '<':'<', '>':'>', '"':'"', ''':"'" };
return text.replace(/&(?:amp|lt|gt|quot|#039);/g, (code) => map[code as keyof typeof map]);
}
//====================
// CodeBlockDecolator
export declare type CodeBlock = {
code: string,
info: string[],
params: string[],
tags: string[],
codeClasses: string[],
maxRows?: number,
rawHtml?: string, // ここに代入されればそのままそれを出力する
};
// デコレータ鎖は先頭から実行される
export declare type Decolator = (chain: Decolator[], block: CodeBlock) => CodeBlock;
const decolators: Decolator[] = [
rowsDecorator,
numDecorator,
diffDecorator,
splitterDecorator, // 行ごとに分けられるようにするため改行の前後でタグを閉じる・開く
highlightDecorator, // highlight.js を呼び出す
];
export function callDecolatorChain(chain: Decolator[], block: CodeBlock) {
const first = chain[0];
return first ? first(chain.slice(1), block) : block;
}
//====================
// code ブロックをレンダリングする
export const codeRenderer = {renderer:{
code: (code: string, infoString = '', escaped: boolean) => {
if(escaped) {
code = unescapeHtml(code);
}
// infoString は info param0 param1 param2
// info は some:some:some
const params = infoString.split(' ');
const info = params.length ? params.shift()!.split(':') : [];
const block = callDecolatorChain(decolators, {code, info, params, tags: [], codeClasses: []});
return block.rawHtml ? block.rawHtml
: `<pre class="marked-code">`
+ (block.tags.length ? `<span class="tags">${block.tags.join(' ')}</span>` : '')
+ `<code class="${block.codeClasses.join(' ')}" style="max-height:${block.maxRows || 30}lh">${block.code}</code>`
+ `</pre>`;
}
}};
CodeBlock に新しいオプションを追加したり、~
Decorator を増やしたりすることで、~
ブロックの内容を画像にして表示するなど、ブロック要素プラグインのような機能をどんどん追加できるはず。
ここでは個別のコードは載せないが、かなりいろいろ表示できるようになった。
LANG: console
$ git add . && git commit -m "コードブロックの表示を改善した"
modified: .gitignore
modified: package.json
modified: pnpm-lock.yaml
new file: src/lib/components/ArticleElement.svelte
new file: src/lib/components/codeDecolators/codeDecolatorInfo.ts
new file: src/lib/components/codeDecolators/diffDecorator.ts
new file: src/lib/components/codeDecolators/highlightDecorator.ts
new file: src/lib/components/codeDecolators/numDecorator.ts
new file: src/lib/components/codeDecolators/rowsDecorator.ts
new file: src/lib/components/codeDecolators/splitterDecorator.ts
new file: src/lib/components/codeRenderer.ts
new file: src/node_modules/@types/highlightjs-svelte.d.ts
modified: src/routes/articles/[...titleOrId]/+page.svelte
この時のコミットに対して後に不具合修正を入れた
コードブロック表示の不具合修正 e57b415177da49da10b41cf8b4e5f1d935faea89
Counter: 1894 (from 2010/06/03),
today: 2,
yesterday: 1