Skip to content
Merged
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
35 changes: 25 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,22 +97,37 @@ https://github.com/owner/repo/blob/main/src/index.ts#L1-L10

## ダークモード

デフォルトの `git-code-embed.min.js` は `prefers-color-scheme` メディアクエリで OS のテーマ設定に自動追従します。
ライトモード固定・ダークモード固定のバンドルも用意しています。
ブログのデザインに合わせて 3 種類のバンドルから選択できます。

| ファイル | 挙動 |
|---|---|
| `git-code-embed.min.js` | OS のダーク/ライト設定に自動追従(推奨) |
| `git-code-embed-light.min.js` | ライトモード固定 |
| `git-code-embed-dark.min.js` | ダークモード固定 |
| ファイル | 挙動 | 推奨用途 |
|---|---|---|
| `git-code-embed.min.js` | `data-theme` 属性によるテーマ切り替えに追従 | テーマ切り替え機能付きのブログ |
| `git-code-embed-light.min.js` | ライトモード固定 | ライト固定デザインのブログ |
| `git-code-embed-dark.min.js` | ダークモード固定 | ダーク固定デザインのブログ |

### `git-code-embed.min.js`(data-theme 追従)の使い方

`<html>` または `<body>` 要素に `data-theme="dark"` / `data-theme="light"` 属性が設定されると、
コードビューのカラーテーマがそれに合わせて切り替わります。
`data-theme` 属性が設定されていない場合はライトテーマが適用されます。

```html
<!-- data-theme 属性でテーマを切り替えるブログ向け -->
<html data-theme="dark">
<!-- コードビューはダークテーマで表示される -->
</html>
```

> **ライト固定・ダーク固定のブログには `-light.min.js` / `-dark.min.js` を使用してください。**
> OS のダーク設定に関わらず、コードビューのテーマをブログに合わせて固定できます。

### はてなブログでの使い方

管理画面の **設定 → 詳細設定** の `<head>` 要素欄に以下を貼り付けます。
OS テーマに自動追従させる場合はデフォルトの URL をそのまま使用してください
ブログのデザインに合わせて使用するファイルを選択してください

```html
<!-- OS のダーク/ライト設定に自動追従(推奨) -->
<!-- data-theme 属性でテーマ切り替えするブログ向け -->
<script src="https://cdn.jsdelivr.net/gh/hirano00o/git-code-embed@v0/dist/git-code-embed.min.js" defer></script>

<!-- ダークモード固定 -->
Expand All @@ -125,7 +140,7 @@ OS テーマに自動追従させる場合はデフォルトの URL をそのま
### ビルド

```bash
# オートテーマ(デフォルト、OS のダーク/ライト設定に自動追従) → dist/git-code-embed.min.js
# オートテーマ(data-theme 属性に追従) → dist/git-code-embed.min.js
npm run build

# ライトモード固定 → dist/git-code-embed-light.min.js
Expand Down
25 changes: 12 additions & 13 deletions dist/git-code-embed-dark.min.js

Large diffs are not rendered by default.

25 changes: 12 additions & 13 deletions dist/git-code-embed-light.min.js

Large diffs are not rendered by default.

25 changes: 12 additions & 13 deletions dist/git-code-embed.min.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "git-code-embed",
"version": "0.2.1",
"version": "0.2.2",
"description": "Embed GitHub code snippets with syntax highlighting in blog posts",
"main": "dist/git-code-embed.min.js",
"scripts": {
Expand Down
18 changes: 17 additions & 1 deletion src/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,26 @@ export const DARK_COLORS = `
.gce-container .hljs-doctag { color: #79c0ff; }
`;

function withDataTheme(colors: string, theme: "dark" | "light"): string {
function scopeToSelector(sel: string): string {
return colors
.replace(/:root/g, sel)
.replace(/\.gce-container/g, `${sel} .gce-container`);
}
return [
scopeToSelector(`html[data-theme="${theme}"]`),
scopeToSelector(`body[data-theme="${theme}"]`),
].join("\n");
}

export function buildThemeCSS(theme: string): string {
if (theme === "dark") return DARK_COLORS;
if (theme === "auto") {
return `${LIGHT_COLORS}\n@media (prefers-color-scheme: dark) {\n${DARK_COLORS}\n}`;
return [
LIGHT_COLORS, // data-theme 未設定時のデフォルト(ライト)として :root に定義
withDataTheme(DARK_COLORS, "dark"),
withDataTheme(LIGHT_COLORS, "light"),
].join("\n");
}
return LIGHT_COLORS;
}
Expand Down
80 changes: 67 additions & 13 deletions test/styles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,26 +120,80 @@ describe("buildThemeCSS", () => {
expect(buildThemeCSS("auto")).toContain(LIGHT_COLORS);
});

it('"auto" → DARK_COLORS が @media (prefers-color-scheme: dark) で囲まれている', () => {
it('"auto" → @media (prefers-color-scheme) ブロックを含まない', () => {
expect(buildThemeCSS("auto")).not.toContain("@media (prefers-color-scheme");
});

it('"auto" → html[data-theme="dark"] ルールを含む', () => {
expect(buildThemeCSS("auto")).toContain('html[data-theme="dark"]');
});

it('"auto" → html[data-theme="dark"] ブロックにダーク CSS 変数が含まれる', () => {
const css = buildThemeCSS("auto");
const start = css.indexOf('html[data-theme="dark"]');
const end = css.indexOf('body[data-theme="dark"]');
expect(start).toBeGreaterThan(-1);
expect(css.slice(start, end)).toContain("--gce-code-text: #e6edf3");
expect(css.slice(start, end)).not.toContain("--gce-code-text: #24292f");
});

it('"auto" → body[data-theme="dark"] ルールを含む', () => {
expect(buildThemeCSS("auto")).toContain('body[data-theme="dark"]');
});

it('"auto" → html[data-theme="light"] ルールを含む', () => {
expect(buildThemeCSS("auto")).toContain('html[data-theme="light"]');
});

it('"auto" → html[data-theme="light"] ブロックにライト CSS 変数が含まれる', () => {
const css = buildThemeCSS("auto");
const mediaStart = css.indexOf("@media (prefers-color-scheme: dark)");
expect(mediaStart).toBeGreaterThan(-1);
// DARK_COLORS はメディアクエリブロック内に含まれる
const afterMedia = css.slice(mediaStart);
expect(afterMedia).toContain(DARK_COLORS);
const start = css.indexOf('html[data-theme="light"]');
const end = css.indexOf('body[data-theme="light"]');
expect(start).toBeGreaterThan(-1);
expect(css.slice(start, end)).toContain("--gce-code-text: #24292f");
expect(css.slice(start, end)).not.toContain("--gce-code-text: #e6edf3");
});

it('"auto" → data-theme ルールが LIGHT_COLORS より後に現れる', () => {
const css = buildThemeCSS("auto");
const lightEnd = css.indexOf(LIGHT_COLORS) + LIGHT_COLORS.length;
const dataThemeIdx = css.indexOf('html[data-theme="dark"]');
expect(dataThemeIdx).toBeGreaterThan(lightEnd);
});

it('"auto" → body[data-theme="light"] ルールを含む', () => {
expect(buildThemeCSS("auto")).toContain('body[data-theme="light"]');
});

it('"auto" → body[data-theme="light"] ブロックにライト CSS 変数が含まれる', () => {
const css = buildThemeCSS("auto");
const start = css.indexOf('body[data-theme="light"]');
expect(start).toBeGreaterThan(-1);
expect(css.slice(start)).toContain("--gce-code-text: #24292f");
expect(css.slice(start)).not.toContain("--gce-code-text: #e6edf3");
});

it('"auto" → data-theme 未設定時のデフォルトとして :root が先頭に存在する', () => {
expect(buildThemeCSS("auto").trimStart()).toMatch(/^:root/);
});

it('"auto" → html[data-theme="dark"] ブロックの hljs セレクタが正しく変換されている', () => {
expect(buildThemeCSS("auto")).toContain(
'html[data-theme="dark"] .gce-container .hljs-keyword'
);
});

it('"auto" → ダーク側の --gce-code-text が @media ブロック内に現れる', () => {
it('"auto" → LIGHT_COLORS 以降に :root が残存しない', () => {
const css = buildThemeCSS("auto");
const mediaStart = css.indexOf("@media (prefers-color-scheme: dark)");
expect(mediaStart).toBeGreaterThan(-1);
const afterMedia = css.slice(mediaStart);
expect(afterMedia).toContain("--gce-code-text: #e6edf3");
expect(css.slice(LIGHT_COLORS.length)).not.toContain(":root");
});

it('"auto" → LIGHT_COLORS が DARK_COLORS より前に現れる', () => {
it('"auto" → body[data-theme="dark"] ブロックにダーク CSS 変数が含まれる', () => {
const css = buildThemeCSS("auto");
expect(css.indexOf(LIGHT_COLORS)).toBeLessThan(css.indexOf(DARK_COLORS));
const start = css.indexOf('body[data-theme="dark"]');
const end = css.indexOf('html[data-theme="light"]');
expect(start).toBeGreaterThan(-1);
expect(css.slice(start, end)).toContain("--gce-code-text: #e6edf3");
});

it('不明な値 → LIGHT_COLORS にフォールバック', () => {
Expand Down