npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

djnote

v0.1.0

Published

djot ベースのメモ/講義資料処理系。setext 見出し・RFC822 メタデータ・祖先要素への属性付与に対応。

Readme

djnote

djot をベースにした、メモ・講義資料用の HTML 変換ツール。 djot がそのまま持っている機能(表・数式・任意要素への属性・見出しの自動 id・ 見出しからの <section> 自動生成)に、次の 3 つだけを足している。

  1. setext 見出し==== / ----)を ATX 見出しに変換する
  2. RFC822 形式のメタデータ(MultiMarkdown title block)を先頭から取り出す
  3. 祖先要素への属性付与[]{up=n key=value}

ほぼClaude Opus5に作ってもらった物です。

インストール

npm install -g djnote     # コマンドとして使う
npm install djnote        # ライブラリとして使う

記法

メタデータ

先頭から最初の空行までが RFC822 形式のメタデータになる。

Title: 第3回 知識表現の基礎
Author: 健司
Keywords: djot, 知識表現,
  トリプル記法
Css: style.css

本文はここから
  • 空白始まりの行は直前の値への継続行(folding)
  • キーは小文字化し空白・アンダースコア・ハイフンを除去して正規化する (Key Words:keywords
  • 先頭行が キー: の形でなければメタデータ無しと判定するので、 「注意: これは本文です」で始まる文書が誤って食われることはない 特別扱いされるキー:

| キー | 出力 | |------|------| | title | <title> | | lang | <html lang="..."> | | css | カンマ区切り → <link rel="stylesheet"> | | javascript | カンマ区切り → <script src="..." defer> | | turtle | <script type="text/turtle"> として埋め込み | | その他 | <meta name="キー" content="値"> |

javascript / turtle

Css: base.css, print.css
Javascript: highlight.js, mermaid.js
Turtle: @prefix ed: <http://example.org/ed#> .
  @prefix dcterms: <http://purl.org/dc/terms/> .
  <> a ed:Lecture ;
     dcterms:title "第3回 知識表現の基礎" ;
     ed:code "IT101" .
<link rel="stylesheet" href="base.css">
<link rel="stylesheet" href="print.css">
<script type="text/turtle">
@prefix ed: <http://example.org/ed#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
<> a ed:Lecture ;
   dcterms:title "第3回 知識表現の基礎" ;
   ed:code "IT101" .
</script>
<script src="highlight.js" defer></script>
<script src="mermaid.js" defer></script>

turtle だけは他のキーと違い、継続行を改行のまま保持する(他のキーは RFC822 の folding に従って空白で連結される)。共通のインデントは取り除かれ、 相対的なインデントは残る。turtle の値は djot として解釈されないので、 引用符がスマート引用符に変換されることもない。

RFC822 形式の規則どおり、値の中に空行は置けない(空行はメタデータ全体の 終端になる)。Turtle は空行を必要としないので実用上は問題ない。

複数行キーは extractMetadata(src, { multilineKeys: [...] }) で変更できる。

見出し

第3回 知識表現の基礎
====================

用語の整理
----------

これらは # / ## に変換される。コードフェンスの中は変換しない。 ==== の行は空行に置き換えるので、警告に出る行番号は元ファイルと一致する。

祖先要素への属性付与

djot 標準のスパン記法のまま、up というキーで「何段外の要素か」を指定する。

段落全体に属性を付ける []{up=1 class=lead}
<p class="lead">段落全体に属性を付ける</p>

| 書き方 | 対象 | |--------|------| | []{abc=xyz} | スパン自身(djot 標準) | | []{up=0 abc=xyz} | 同上(up を落とすだけ) | | []{up=1 abc=xyz} | スパンを直接含む要素(<p>, <td>, <li> …) | | []{up=2 abc=xyz} | そのさらに 1 つ外側 |

表のセルに書けば up=1<td>up=2<tr>up=3<table> になる。

  • 段数の数え方は既定では自動生成される <section> を飛ばす--count-sections / countSections: true で数に含める
  • スパンに内容がある場合([中身]{up=1 class=x})は、属性だけ祖先に移し 内容はその場に残す
  • 段数が文書の深さを超えたときは警告を出し、属性は捨てずにスパンに残す

コマンド

djnote note.dj                 # note.html を作る
djnote --overwrite note.dj     # note.html が既にあっても上書き
djnote --stdout note.dj        # 標準出力へ
djnote note.dj -o out.html     # 出力先を明示
djnote src/*.dj -o build/      # まとめて変換
djnote --body note.dj          # <body> の中身だけ
cat note.dj | djnote           # パイプ(標準出力へ)

-o を付けない場合、入力ファイルの拡張子を出力形式に置き換えたファイルを 同じ場所に作るnote.djnote.html)。出力先が既に存在するときは 何も書かずに終了コード 1 で停止する。上書きしたいときは --overwrite

-o で出力先を明示した場合はこの確認をしない(ビルド先の作り直しを 毎回止められると困るため)。--emit-djot のように出力先が入力ファイルと 同じになる場合は、上書きを避けて停止する。

主なオプション:

| オプション | 意味 | |------------|------| | --overwrite | 既存の出力ファイルを警告なしで上書き | | --stdout | 標準出力に書き出す | | --body | <body> の中身だけを出力 | | --emit-djot | 前処理後の djot を出力(一括正規化用) | | --count-sections | up= の段数に <section> を含める | | --no-setext | setext 見出しの変換をしない | | --no-fix-lists | 入れ子リスト前の空行補完をしない | | --css <url> | スタイルシートを追加(複数可) | | --js <url> | スクリプトを追加(複数可) | | --no-defer | スクリプトに defer を付けない | | --no-turtle | turtle を埋め込まない | | --server | 簡易 Web サーバを起動 | | --port <n> | 待ち受けポート(既定: 4000) | | --host <addr> | 待ち受けアドレス(既定: 127.0.0.1) | | --math <name> | mathjax(既定)/ katex / none |

数式は文書に数式が含まれるときだけ CDN のスクリプトを差し込む。

サーバ

djnote --server                 # http://127.0.0.1:4000/
djnote --server --port 4001
djnote --server --host 0.0.0.0 --port 8080

カレントディレクトリをそのまま配信する。要求されたリソースの実体が無い ときは、それを生成できる元ファイルを探して変換して返す。

拡張子つきの URL

URL で表現が名指しされているので、ネゴシエーションはしない。

| 要求 | 動作 | |------|------| | /style.css | 実ファイルをそのまま返す | | /abc.html | 無ければ abc.dj を探して変換して返す | | /missing.html | 元ファイルも無ければ 404 |

拡張子なしの URL — コンテントネゴシエーション

拡張子の無い URL は、Accept ヘッダを見て表現を選ぶ(RFC 9110 §12)。 候補には実ファイルと生成できるものの両方が入る。

metadata.djmetadata.ttl が置いてあるディレクトリで /metadata を 要求した場合:

| Accept | 応答 | Content-Location | |--------|------|------------------| | text/turtle | 200 text/turtle | /metadata.ttl | | text/html | 200 text/htmlmetadata.dj から生成) | /metadata.html | | text/html;q=0.5,text/turtle;q=0.9 | 200 text/turtle | /metadata.ttl | | */*(curl の既定) | 200 text/html | /metadata.html | | text/plain | 200 text/plain | /metadata.dj | | image/png | 406 Not Acceptable(候補一覧を本文に出す) | — |

ネゴシエーションした応答には Vary: Accept と、実際に選ばれた表現を示す Content-Location を付ける。

同じ media type に実ファイルと生成物の両方があるときは実ファイルを優先する。 Accept がワイルドカードだけのときの順序は DEFAULT_PREFERENCE (HTML が先頭)で決まり、preference オプションで変更できる。 Generator の入力になっている拡張子(.dj)の実ファイルは優先度を下げて ありtext/plain のように名指しで要求されたときだけ選ばれる。

ディレクトリ要求 /sub/sub/index として同じ仕組みにかかるので、 index.dj があれば HTML が、index.ttl があれば Turtle が返る。

生成結果はディスクに書かない。キャッシュもしないので、.dj を編集して リロードすればそのまま反映される。root の外を指す要求は 403 で拒否する。

ブラウザが遮断するポート(6000、6665〜6669、10080 など)を指定した場合は、 起動時に警告を出す。

変換規則を足す

Generator を実装して渡すだけで、サーバの対応形式が増える。

Generator は「どの media type を作れるか」を軸に宣言する。拡張子は /abc.ttl のような名指しの URL を解決するための別名として持つ。

import { startNoteServer, defaultGenerators } from "djnote";
import type { Generator } from "djnote";

const djotToTurtle: Generator = {
  name: "djot → turtle",
  mediaType: "text/turtle",   // ネゴシエーションで使われる
  extension: ".ttl",          // /abc.ttl を解決するための別名
  source: ".dj",
  quality: 1,                 // Accept がワイルドカードのときの優先度
  async build({ sourcePath, mediaType }) {
    /* 文字列か Buffer を返す */
  },
};

await startNoteServer({
  port: 6001,
  generators: [...defaultGenerators, djotToTurtle],
});

charset は media type から自動で付く(text/*application/json など)。 同じ拡張子に複数の Generator を登録した場合は、元ファイルが実在する 最初のものが使われる。

API

import { renderBody, renderDocument, parseNote } from "djnote";

// <body> の中身だけが欲しいとき(テンプレートエンジンや SSG に差し込む)
const { meta, html, warnings } = renderBody(source);

// 完全な HTML 文書
const { html } = renderDocument(source, {
  css: ["/style.css"],
  javascript: ["/app.js"],
  lang: "ja",
});

// AST が欲しいとき
const { doc, djot } = parseNote(source, { keepSourcePositions: true });

低レベルの部品も個別に使える。

| 関数 | 役割 | |------|------| | extractMetadata(src) | { meta, body, consumedLines } | | preprocess(src, opts) | setext 変換・入れ子リスト補正 | | applyAncestorAttributes(doc, opts) | up= の適用(doc を破壊的に変更) | | renderBodyFromDoc(doc) | 加工済み doc → HTML 断片 | | startNoteServer(opts) | サーバ起動。{ port, host, root, close() } を返す | | findGeneration(path, gens) | 拡張子つきパスの生成元を探す | | findRepresentations(base, gens) | 拡張子なしパスの表現をすべて集める | | parseAccept(header) | Accept ヘッダを解釈する | | selectVariant(variants, ranges) | 候補から最良の表現を選ぶ | | contentTypeOf(mediaType) | charset 付きの Content-Type を作る |

Markdown からの移行メモ

djot は Markdown ではないので、次の違いに注意。

  • 強調が逆_強調_<em>*強調*<strong>
  • 入れ子リストの前に空行が必要* a の次行に + b と書いても djot では入れ子にならない。 本ツールは既定でこれを検出して空行を補うが(--no-fix-lists で無効化)、 一度 --emit-djot でファイル自体を正規化しておくのが確実
  • インデントコードブロックが無い。4 スペース字下げは単なる段落
  • 同じ階層のリストマーカーを *+- と使い分ける流儀はそのまま使える

一括正規化はこうする。

for f in *.md; do djnote --emit-djot "$f" > "${f%.md}.dj"; done

ライセンス

MIT