Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/zenn-cli/src/server/api/articles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import Express from 'express';
import { getLocalArticle, getLocalArticleMetaList } from '../lib/articles';
import { getValidSortTypes } from '../../common/helper';

export function getArticle(req: Express.Request, res: Express.Response) {
const article = getLocalArticle(req.params.slug);
export async function getArticle(req: Express.Request, res: Express.Response) {
const article = await getLocalArticle(req.params.slug);
if (!article) {
res
.status(404)
Expand Down
4 changes: 2 additions & 2 deletions packages/zenn-cli/src/server/api/books.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function getBooks(req: Express.Request, res: Express.Response) {
res.json({ books });
}

export function getChapter(req: Express.Request, res: Express.Response) {
export async function getChapter(req: Express.Request, res: Express.Response) {
const bookSlug = req.params.book_slug;
const book = getLocalBookMeta(bookSlug);
if (!book) {
Expand All @@ -42,7 +42,7 @@ export function getChapter(req: Express.Request, res: Express.Response) {
return;
}
const chapterFilename = req.params.chapter_filename;
const chapter = getLocalChapter(book, chapterFilename);
const chapter = await getLocalChapter(book, chapterFilename);
if (!chapter) {
res
.status(404)
Expand Down
4 changes: 2 additions & 2 deletions packages/zenn-cli/src/server/lib/articles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import { ItemSortType } from '../../common/types';
import markdownToHtml from 'zenn-markdown-html';
import { parseToc } from 'zenn-markdown-html/lib/utils';

export function getLocalArticle(slug: string): null | Article {
export async function getLocalArticle(slug: string): Promise<null | Article> {
const data = readArticleFile(slug);
if (!data) return null;
const { meta, bodyMarkdown } = data;
const rawHtml = markdownToHtml(bodyMarkdown, {
const rawHtml = await markdownToHtml(bodyMarkdown, {
embedOrigin: process.env.VITE_EMBED_SERVER_ORIGIN,
});
const bodyHtml = completeHtml(rawHtml);
Expand Down
6 changes: 3 additions & 3 deletions packages/zenn-cli/src/server/lib/books.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ export function getLocalBookMetaList(sort?: ItemSortType): BookMeta[] {
return books;
}

export function getLocalChapter(
export async function getLocalChapter(
book: BookMeta,
chapterFilename: string
): null | Chapter {
): Promise<null | Chapter> {
const data = readChapterFile(book, chapterFilename);
if (!data) return null;

const { meta, bodyMarkdown } = data;
const rawHtml = markdownToHtml(bodyMarkdown, {
const rawHtml = await markdownToHtml(bodyMarkdown, {
embedOrigin: process.env.VITE_EMBED_SERVER_ORIGIN,
});
const bodyHtml = completeHtml(rawHtml);
Expand Down
9 changes: 9 additions & 0 deletions packages/zenn-content-css/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ import 'zenn-content-css';

zncの外側の要素にはスタイルが適用されないことに注意してください。

## シンタックスハイライトについて

コードブロックのシンタックスハイライトは **Shiki** を使用しています。

- `_shiki.scss`: Shiki 用スタイル(現行)
- `_prism.scss`: Prism.js 用スタイル(非推奨・後方互換性のため残存)

`_prism.scss` は、Prism.js で既に変換された既存記事のために残しています。すぐに削除する予定はありませんが、将来的に削除される可能性があります。

## 開発者向けドキュメント

https://zenn-dev.github.io/zenn-docs-for-developers/guides/zenn-editor/zenn-content-css
6 changes: 0 additions & 6 deletions packages/zenn-content-css/src/_content.scss
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,7 @@
border-radius: variables.$rounded-xs;
word-break: normal; // iOSで折り返されるのを防ぐ
word-wrap: normal; // iOSで折り返されるのを防ぐ
/* flex + codeの隣に疑似要素を配置することで横スクロール時の右端の余白を作る */
display: flex;
&:after {
content: '';
width: 8px;
flex-shrink: 0;
}
code {
margin: 0;
padding: 0;
Expand Down
9 changes: 9 additions & 0 deletions packages/zenn-content-css/src/_prism.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
// ============================================================
// [非推奨] Prism.js 用スタイル
// ============================================================
// このファイルは Prism.js で既に変換された HTML のために残しています。
// 新規記事は Shiki でハイライトされるため、このファイルは使用されません。
//
// すぐに削除する予定はありませんが、将来的に削除される可能性があります。
// ============================================================

@mixin styles {
pre[class*='language-'] {
position: relative;
Expand Down
29 changes: 29 additions & 0 deletions packages/zenn-content-css/src/_shiki.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@mixin styles {
// Shiki base styles
pre.shiki {
position: relative;
overflow-x: auto;
}

// diff 行は背景色を全幅にするため inline-block + min-width
.shiki .line.diff {
display: inline-block;
min-width: 100%;
}

// diff モード: 追加行(+)の背景色
.shiki .line.diff.add {
background: rgba(0, 146, 27, 0.2);
}

// diff モード: 削除行(-)の背景色
.shiki .line.diff.remove {
background: rgba(218, 54, 50, 0.2);
}

// diff プレフィックス(+, -, <, >)は選択不可にする
// ref: https://github.com/zenn-dev/zenn-editor/issues/148
.shiki .diff-prefix {
user-select: none;
}
}
2 changes: 2 additions & 0 deletions packages/zenn-content-css/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@use './content' as content;
@use './embed' as embed;
@use './prism' as prism;
@use './shiki' as shiki;
@use './message' as message;
@use './footnotes' as footnotes;

Expand Down Expand Up @@ -76,6 +77,7 @@
@include content.styles;
@include embed.styles;
@include prism.styles;
@include shiki.styles;
@include message.styles;
@include footnotes.styles;
}
Expand Down
10 changes: 1 addition & 9 deletions packages/zenn-markdown-html/.babelrc.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
{
"targets": "node 16.0",
"presets": ["@babel/preset-env", "@babel/preset-typescript"],
"plugins": [
[
"prismjs",
{
"languages": "all"
}
]
]
"presets": ["@babel/preset-env", "@babel/preset-typescript"]
}
16 changes: 8 additions & 8 deletions packages/zenn-markdown-html/__tests__/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import markdownToHtml from '../src/index';
import { parse } from 'node-html-parser';

describe('MarkdownからHTMLへの変換テスト', () => {
test('markdownからhtmlへ変換する', () => {
const html = markdownToHtml('Hello\n## hey\n\n- first\n- second\n');
test('markdownからhtmlへ変換する', async () => {
const html = await markdownToHtml('Hello\n## hey\n\n- first\n- second\n');
const p = parse(html).querySelector('p');
const h2 = parse(html).querySelector('h2');
const ul = parse(html).querySelector('ul');
Expand All @@ -20,13 +20,13 @@ describe('MarkdownからHTMLへの変換テスト', () => {
expect(liElms[1].innerHTML).toBe('second');
});

test('インラインコメントはhtmlに変換しない', () => {
const html = markdownToHtml(`<!-- hey -->`);
test('インラインコメントはhtmlに変換しない', async () => {
const html = await markdownToHtml(`<!-- hey -->`);
expect(html).not.toContain('hey');
});

test('脚注に docId を設定する', () => {
const html = markdownToHtml(`Hello[^1]World!\n\n[^1]: hey`);
test('脚注に docId を設定する', async () => {
const html = await markdownToHtml(`Hello[^1]World!\n\n[^1]: hey`);
// expect(html).toContain('<a href="#fn-27-1" id="fnref-27-1">[1]</a>');
expect(html).toEqual(
expect.stringMatching(
Expand All @@ -35,8 +35,8 @@ describe('MarkdownからHTMLへの変換テスト', () => {
);
});

test('dataスキーマの画像は除外する', () => {
const html = markdownToHtml(`![](data:image/png;base64,xxxx)`);
test('dataスキーマの画像は除外する', async () => {
const html = await markdownToHtml(`![](data:image/png;base64,xxxx)`);
expect(html).toContain('<img alt="" class="md-img" loading="lazy" />');
});
});
20 changes: 10 additions & 10 deletions packages/zenn-markdown-html/__tests__/br.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,28 @@ import { describe, test, expect } from 'vitest';
import markdownToHtml from '../src/index';

describe('<br /> のテスト', () => {
test('段落内の<br />は保持する', () => {
test('段落内の<br />は保持する', async () => {
const patterns = ['foo<br>bar', 'foo<br/>bar', 'foo<br />bar'];
patterns.forEach((pattern) => {
const html = markdownToHtml(pattern);
for (const pattern of patterns) {
const html = await markdownToHtml(pattern);
expect(html).toContain('foo<br />bar');
});
}
});
test('テーブル内の<br />は保持する', () => {
test('テーブル内の<br />は保持する', async () => {
const tableString = [
`| a | b |`,
`| --- | --- |`,
`| foo<br>bar | c |`,
].join('\n');
const html = markdownToHtml(tableString);
const html = await markdownToHtml(tableString);
expect(html).toContain('foo<br />bar');
});
test('インラインコード内の<br />はエスケープする', () => {
const html = markdownToHtml('foo`<br>`bar');
test('インラインコード内の<br />はエスケープする', async () => {
const html = await markdownToHtml('foo`<br>`bar');
expect(html).toContain('foo<code>&lt;br&gt;</code>bar');
});
test('コードブロック内の<br />はエスケープする', () => {
const html = markdownToHtml('```\n<br>\n```');
test('コードブロック内の<br />はエスケープする', async () => {
const html = await markdownToHtml('```\n<br>\n```');
expect(html).toContain('&lt;br&gt;');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ describe('Blueprintue埋め込み要素のテスト', () => {

describe('デフォルトの挙動', () => {
describe('有効なURLの場合', () => {
test('<iframe />に変換する', () => {
const html = markdownToHtml(`@[blueprintue](${validUrl})`);
test('<iframe />に変換する', async () => {
const html = await markdownToHtml(`@[blueprintue](${validUrl})`);
const iframe = parse(html).querySelector(
`span.embed-blueprintue iframe`
);
Expand All @@ -21,8 +21,8 @@ describe('Blueprintue埋め込み要素のテスト', () => {
});

describe('無効なURLの場合', () => {
test('エラーメッセージを出力する', () => {
const html = markdownToHtml(`@[blueprintue](${invalidUrl})`);
test('エラーメッセージを出力する', async () => {
const html = await markdownToHtml(`@[blueprintue](${invalidUrl})`);

expect(html).toContain(
'「https://blueprintue.com/render/」から始まる正しいURLを指定してください'
Expand All @@ -32,10 +32,10 @@ describe('Blueprintue埋め込み要素のテスト', () => {
});

describe('customEmbed.blueprintue()を設定している場合', () => {
test('渡した関数を実行する', () => {
test('渡した関数を実行する', async () => {
const customizeText = 'customized text!';
const mock = vi.fn().mockReturnValue(customizeText);
const html = markdownToHtml(`@[blueprintue](${validUrl})`, {
const html = await markdownToHtml(`@[blueprintue](${validUrl})`, {
customEmbed: { blueprintue: mock },
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ describe('LinkCard埋め込み要素のテスト', () => {

describe('デフォルトの挙動', () => {
describe('有効なURL', () => {
test('リンクに変換する', () => {
const html = markdownToHtml(`@[card](${validUrl})`);
test('リンクに変換する', async () => {
const html = await markdownToHtml(`@[card](${validUrl})`);
const link = parse(html).querySelector(`a`);

expect(link?.attributes).toEqual(
Expand All @@ -23,25 +23,25 @@ describe('LinkCard埋め込み要素のテスト', () => {
});

describe('無効なURLの場合', () => {
test('エラーメッセージを出力する', () => {
const html = markdownToHtml(`@[card](${invalidUrl})`);
test('エラーメッセージを出力する', async () => {
const html = await markdownToHtml(`@[card](${invalidUrl})`);
expect(html).toContain('URLが不正です');
});
});

describe('メールアドレス', () => {
test('メールアドレスのまま出力する', () => {
const html = markdownToHtml(`ec2-user@33.80.180.159`);
test('メールアドレスのまま出力する', async () => {
const html = await markdownToHtml(`ec2-user@33.80.180.159`);
expect(html).toContain('ec2-user@33.80.180.159');
expect(html).not.toContain('URLが不正です');
});
});
});

describe('embedOriginを設定している場合', () => {
test('渡したembedOriginを`src`として<iframe />を表示する', () => {
test('渡したembedOriginを`src`として<iframe />を表示する', async () => {
const embedOrigin = 'https://embed.example.com';
const html = markdownToHtml(validUrl, { embedOrigin });
const html = await markdownToHtml(validUrl, { embedOrigin });
const iframe = parse(html).querySelector('span.zenn-embedded iframe');

expect(iframe?.attributes).toEqual(
Expand All @@ -54,11 +54,11 @@ describe('LinkCard埋め込み要素のテスト', () => {
});

describe('customEmbed.card()を設定している場合', () => {
test('渡した関数を実行する', () => {
test('渡した関数を実行する', async () => {
const url = 'https://example.com';
const customizeText = 'customized text';
const mock = vi.fn().mockReturnValue(customizeText);
const html = markdownToHtml(`@[card](${url})`, {
const html = await markdownToHtml(`@[card](${url})`, {
customEmbed: { card: mock },
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ describe('Codepen埋め込み要素のテスト', () => {

describe('デフォルトの挙動', () => {
describe('有効なURLの場合', () => {
test('<iframe />に変換する', () => {
const html = markdownToHtml(`@[codepen](${validUrl})`);
test('<iframe />に変換する', async () => {
const html = await markdownToHtml(`@[codepen](${validUrl})`);
const iframe = parse(html).querySelector(`span.embed-codepen iframe`);
const passedUrl = validUrl.replace('/pen/', '/embed/');

Expand All @@ -21,18 +21,18 @@ describe('Codepen埋め込み要素のテスト', () => {
});

describe('無効なURLの場合', () => {
test('エラーメッセージを出力する', () => {
const html = markdownToHtml(`@[codepen](${invalidUrl})`);
test('エラーメッセージを出力する', async () => {
const html = await markdownToHtml(`@[codepen](${invalidUrl})`);
expect(html).toContain('CodePenのURLが不正です');
});
});
});

describe('customEmbed.codepen()を設定している場合', () => {
test('渡した関数を実行する', () => {
test('渡した関数を実行する', async () => {
const customizeText = 'customized text';
const mock = vi.fn().mockReturnValue(customizeText);
const html = markdownToHtml(`@[codepen](${invalidUrl})`, {
const html = await markdownToHtml(`@[codepen](${invalidUrl})`, {
customEmbed: { codepen: mock },
});

Expand Down
Loading
Loading