vivliostyle / vfm

⬇️ Open and extendable Markdown syntax and toolchain.
https://vivliostyle.github.io/vfm/#/vfm
Other
71 stars 12 forks source link

spec: Frontmatter #76

Closed akabekobeko closed 3 years ago

akabekobeko commented 3 years ago

I proposed it in #1, but I will reprint it because there is no individual issue.

For english:

Frontmatter

Propose Frontmatter as a method of defining metadata in Markdown (file) units.

Purpose

Format

The format follows the famous static site generator.

These are written at the top of Markdown mainly in YAML format (Hugo becomes TOML when defined with +++).

---
property: value
---

For properties, we suggest the following: Minimal because it is a draft.

Property Type Description
title String Markdown The title of the document. Markdown The first # can be a title, but this is not always defined, so I want an explicit title.
author String Author.
akabekobeko commented 3 years ago

以下のドキュメントでは titletheme が実装され、他は未実装となっている。

Vivliostyle Flavored Markdown: Working Draft - VFM: Vivliostyle Flavored Markdown

v1.0 としてやること

v1.0 として対応する動機

既に create-book が生成する Markdown の雛形へ Frontmatter の titletheme が出力されている。本機能が有効となっている前提なのだろう。そのため優先度はあがる。

また本機能のために利用している remark-frontmatter は remark 13 以降にも対応しており VFM v2.0 へそのまま継承可能なためリスクが小さいを判断した。

akabekobeko commented 3 years ago

title についてテストコードを書いて実行したところ、本機能は未実装のようだ。remark-frontmatter は組み込まれているので読み取った結果を rehype で処理する実装が必要。

akabekobeko commented 3 years ago

metadata.ts に Frontmatter を処理している部分があるので console.log を仕掛けてみた。

visit<FrontmatterContent>(tree, ['yaml'], (node) => {
  const value = yaml(node.value);
  console.log(value)
  if (typeof value === 'object') {
    console.log(file.data)
    file.data = {
      ...file.data,
      ...value,
    };
    console.log(file.data)
  }
});

テストコードに

---
title: "Title"
theme: "MyTheme"
class: "test"
---

を指定した状態で実行してみると以下の出力を得られた。

 PASS  tests/block/frontmatter.test.ts
  ● Console

    console.log
      { title: 'Title', theme: 'MyTheme', class: 'test' }

      at src/plugins/metadata.ts:23:13

    console.log
      {}

      at src/plugins/metadata.ts:25:15

    console.log
      { title: 'Title', theme: 'MyTheme', class: 'test' }

      at src/plugins/metadata.ts:30:15

Frontmatter 解析と file.data への設定は実装されている。よって rehype 部分にこれを伝搬して処理できれば本機能へ対応できそうだ。

akabekobeko commented 3 years ago

metadata.tshast 関数を実装して mdast 関数と対になるようメタデータを処理することにする。この関数を rehype-document 後に仕掛けて HAST を加工することで Frontmatter を HTML に反映できるはず。hast 関数に VFile が伝搬されることも確認済み。

ここまで調べられたので Frontmatter とそのメタデータ反映は v1.0 対応としたい。

akabekobeko commented 3 years ago

以下に公開している Frontmatter について

HTML へどのように反映させるかまとめておく。

Property Type HTML 備考
title String <title>タイトル</title> Frontmatter に未指定なら Markdown で最初に定義された # を参照する。
theme String 不明 vivliostyle-theme と紐付けるためのタグを出力する予定だったらしいが、詳細不明。どのような出力にすべきか他のメンバーと検討が必要。
author String <meta name="author" content="著者名"> そのまま meta タグを生成すればよい。
class String <body class="my-class"> Markdown の対応するコンテンツ単位に CSS クラス名を出力する。
toc Boolean なし ドキュメント未掲載だが内部処理として参照されているため、それを理解するまでは残す。

追加の提案として数式の書式 #37 がある。Frontmatter に math を追加して数式の解析を制御しようというもの。

akabekobeko commented 3 years ago

@MurakamiShinyu @yamasy1549 vivliostyle/themes に関わる Frontmatter プロパティーとして theme があります。これが指定された場合、どのような HTML を出力すればよいでしょうか?

考えられるものとしては

でしょうか。個人的には create-book とそこから利用される cli で theme の仕組みは十分なので、VFM としてこのプロパティーに対応しなくてもよいと考えています。

yamasy1549 commented 3 years ago

個人的には create-book とそこから利用される cli で theme の仕組みは十分なので、VFM としてこのプロパティーに対応しなくてもよいと考えています。

よいと思います!

akabekobeko commented 3 years ago

@yamasy1549 ありがとうございます。

@MurakamiShinyu @yamasy1549 補足です。Frontmatter から theme を削除したとしても class が Markdown 単位の <body> に対して CSS クラスを設定する予定ですので、

となります。よって theme は仕様から削除します。残したほうがよい場合は用途 (出力したい HTML) も添えてコメントをお願いします。

theme 以外は前述の表を参考にしてください。math について #37 で結論が出たらそれも追加するかもしれません。

akabekobeko commented 3 years ago

議論中の math 以外を手元で実装してみたが title に問題あり。文書のタイトルは VFM のオプションからも指定可能。よってこれと Frontmatter のどちらを優先するか決める必要がある。

処理順としては

  1. rehype-document によってオプション指定があれば <title> を設定
  2. 今回の実装により Frontmatter の title または # が定義されていれば <title> を設定

となる。これに準じて Markdown 側の指定で後勝ちにしてもよさそうに思える。

MurakamiShinyu commented 3 years ago

Vivliostyle CLI の側では vfm の frontmatter から title と theme を取得するようになってるようです(不要になる?) 次のところあたり:

https://github.com/vivliostyle/vivliostyle-cli/blob/main/src/markdown.ts

https://github.com/vivliostyle/vivliostyle-cli/blob/main/src/config.ts#L268-L270

akabekobeko commented 3 years ago

CLI 側でも Markdown を解析しているのですね。そうなると仕様のすり合わせが必要です。本件の実装にて VFM は以下のようにしようとしています。これを踏まえて @MurakamiShinyu さんと @spring-raining さんに意見をお願いしたいです。

title

  1. VFM オプション title が指定されていれば rehype-document により <title> を設定
  2. Frontmatter の title または # が定義されていれば <title> を設定、1 で処理されたものは上書き (後勝ち、Markdown 勝ち)

Working Draft にオプションとの優先度が言及されていないため、仕様が確定 (↑でなくオプション勝ちでもよい) したら改めて記載予定。

theme

akabekobeko commented 3 years ago

とりあえず PR は待機しておきます。

akabekobeko commented 3 years ago

Frontmatter について VFM は処理しないが CLI などのために定義してもよい、とする方針もありえますね。

ただその場合でも「Markdown 書式」としての VFM ドキュメントでもある Working Draft には記載しておきたいです。記載のないものが登場するとユーザーに不安を与えますし、開発者としても考慮から外れてバグを埋め込む原因にもなりますので。

akabekobeko commented 3 years ago

特に異論もないようですので title の優先度は以下のようにします。

  1. Frontmatter の title
  2. Markdown で最初に登場する #
  3. VFM オプションの title

ドキュメント記載したものも含めて後ほど PR します。

akabekobeko commented 3 years ago

v1.0.0-alpha.18 にて thememath 以外のプロパティー対応を実装しました。

akabekobeko commented 3 years ago

37 にて math: boolean と CLI オプション --math (指定時 true) を実装することになった。振る舞いは #37 のコメントを参照のこと。

akabekobeko commented 3 years ago

89 で本件に関する設計変更があったのでメモ。

Frontmetter 用の Markdown 解析を実行してメタデータ取得してから、その内容を踏まえて処理するようにした。本来は MDAST として Frontmetter が最速実行され、これが生成した VFile.data を読むようにしたかったが少なくとも remark 13 未満 (現行) の tokenizer からはこれを読めないようだ。

設計としては stringify で処理しているが、これは VFM へ移動したほうがよいかもしれない。

89 の後にこのあたりを改めて改善したい。

akabekobeko commented 3 years ago

vivliostyle-cli では VFM 関数経由で VFile を取得してメタデータを読んでいる。VFM 内でも math 処理判定のため同様の事前処理が必要となった。

これらを踏まえ Frontmetter から読み込んだメタデータを型付けして返す公開関数を用意してもよさそうだ。Processor もフル機能は要らないので Frontmetter 処理に必要な最小構成とする。とここまで書いて思い出したがメタデータ読み込みを VFM ではなく stringify で処理したのは Frontmetter のために Markdown が必要なためであった。よって VFM ではこれを処理できない。するとしたら unified エコ システム内で完結する必要あり。

akabekobeko commented 3 years ago

開発者会議 May 2021 で村上さんから挙げられた提案。

HTML のメタデータは基本的に <meta> で指定されるのだから Frontmatter にこれとそのまま対応するプロパティーを追加するのはどうか。

---
meta:
- name: description
  content: text
- name: author
  content: akabeko
---

to JSON (JavaScript Object 参考用)

{
  "meta": [
    {
      "name" : "description",
      "content": "text"
    },
    {
      "name" : "author",
      "content": "akabeko"
    }
  ]
}

to HTML

<meta name="description" content="text">
<meta name="author" content="akabeko">

利点は現在 author を設定しているが、こうした <meta> 系をすべてこれで対応可能なため、個別に Frontmatter プロパティーを増減しなくて済む。author を特別扱いで残すとしても、内部処理としては <meta> を処理する関数に渡すデータにそれを含めれば独立した処理としなくてよい。

MurakamiShinyu commented 3 years ago

meta

HTMLの <meta> 要素の構文のうち、MarkdownのFrontmatterで表現できる必要があるのは <meta name="..." content="..."> だけでよいと思います。したがって、nameをキーに、contentを値とするオブジェクトで表すことでより簡潔に書けると思います。

例:

lang: ja
title: 走れメロス
meta:
  author: 太宰治
  date: 1940-03-23
  description: 古伝説とシルレルの詩から
  keywords: メロス, 暴君ディオニス, セリヌンティウス

↓ HTML出力結果

<html lang="ja">
  <head>
    <title>走れメロス</title>
    <meta name="author" content="太宰治"/>
    <meta name="date" content="1940-03-23"/>
    <meta name="description" content="古伝説とシルレルの詩から"/>
    <meta name="keywords" content="メロス, 暴君ディオニス, セリヌンティウス"/>

著者が複数の場合など、値が配列になっている場合には、同じ name で複数の meta タグを出力するとよいでしょう:

meta:
  author:
    - Karl Marx
    - Friedrich Engels

    <meta name="author" content="Karl Marx"/>
    <meta name="author" content="Friedrich Engels"/>

link

<meta name="..." content="..."> と同様に <link rel="..." href="..."> も必要です。 (https://wiki.whatwg.org/wiki/MetaExtensions では、標準的な metadata の名前が定義されてますが、 <link rel="schema.dc" href="http://purl.org/dc/elements/1.1/"> など link 要素を合わせて使うものとされているものが多い)

例:

link:
  schema.dc: "http://purl.org/dc/elements/1.1/"
  stylesheet:
    - "common.css"
    - media: print
      href: "print.css"
meta:
  dc.modified: 2021-05-11

このように link 要素に name と href 以外の属性も付加する場合には、 {media: print, href: "print.css"} のように指定できるとよいです。

MurakamiShinyu commented 3 years ago

root and body attributes

root: {…} でルート (<html>) 要素の属性、body: {…}<body> 要素の属性を指定できるとよいです。

例:

root:
  lang: ar
  dir: rtl
  class: arabic-site
body:
  class: abstract
  role: doc-abstract

↓ HTML出力結果は

<html lang="ar" dir="rtl" class="arabic-site">
  <head>...</head>
  <body class="abstract" role="doc-abstract">

Note: lang はとても重要な属性なので root: {…} に入れないで直接トップレベルに記述しても同じことになること。 両方に lang があり値が合っていない場合はどちらを優先? dir も同様にする? RTL言語の lang を指定したら同時に dir: rtl も必須なので、その必要がありそう。

追記2021-05-14: lang, dir のほか、CSS組版スタイルシートではルートの id と class を使うのが便利なので、land, dir, id, class を root: {…} に入れないで直接トップレベルに記述してもルートの属性になるようにしたい。 最初の提案で root-attributes, body-attributes としてたのは root, body に直しました。

header-includes

Pandocheader-includes と同様、HTML の <head> 要素に含める HTML 要素をそのまま記述することができるようにする。

例:

header-includes: |
  <style>
  blockquote {
    font-style: italic;
  }
  tr.even {
    background-color: #f0f0f0;
  }
  td, th {
    padding: 0.5em 2em 0.5em 0.5em;
  }
  tbody {
    border-bottom: none;
  }
  </style>

次のように複数の HTML 要素の指定も可能に:

header-includes:
  - <meta property="og:site_name" content="My Site">
  - <script src="myscript.js"></script>
akabekobeko commented 3 years ago

HTML 主体で考えると現在の math や vivliostyle-cli が利用している theme はルートではなく VFM の動作設定ということで

config:
  math: true
  theme: "@vivliostyle/theme-slide"
  partial: false
  disableHtmlFormat: false

のような感じで config にまとめたいですね。これは #98 とも関係します。

MurakamiShinyu commented 3 years ago

未定義 Frontmatter 項目を meta 扱いにする案

Markdown 原稿を書く立場からすると、lang: …title: … は Frontmatter のトップレベルに書けるのに author: …date: …meta: のあとにインデントして書かなければならないというのは不便な感じがする。

lang: en
title: Hello World
meta:
  author: Foo Baa
  date: 2021-05-15

と書くよりも、

lang: en
title: Hello World
author: Foo Baa
date: 2021-05-15

のほうが、書きやすい。

そこで meta: を書かなくてもよいように、次の規則をもうけてはどうだろうか?

akabekobeko commented 3 years ago

実際に採用するかは継続議論するとして、現時点で挙げられているプロパティーをまとめてみます。

Property Type Description
root Object <html> の属性を Key/Value 形式で定義。
body Object <body> の属性を Key/Value 形式で定義。class プロパティーはルートからこちらへ移動。
link Array<Object> <link> の属性を Key/Value 形式で定義したものの配列。
script Array<Object> <script> の属性を Key/Value 形式で定義したものの配列。スクリプト直書きは禁止してファイルのリンクに限定するほうがよいと考えている。
config Object VFM 自信の設定。maththeme をこちらへ移動予定。VFM オプション disableHtmlFormat なども廃止してここで定義する方式にしたい。関連 #98
その他 String <meta> として扱われる。Key = name、Value = content に対応。

その他を <meta> 扱いすると定義済みの名前を name とできない制限あり。

akabekobeko commented 3 years ago

型を検討する場合に便利なサイト。JSON と YAML を相互変換してくれる。

MurakamiShinyu commented 3 years ago

実際に採用するかは継続議論するとして、現時点で挙げられているプロパティーをまとめてみます。

ありがとうございます。

その他を <meta> 扱いすると定義済みの名前を name とできない制限あり。

はい、そこで、 meta プロパティーも残して定義済みの名前を name とする必要があるときは meta: … で記述できるようにするとよいと思います。

その metalink について、Type を Array<Object> とするか、それとも Object としてその Key/Value の Value が Array の場合には同じ Key の複数の <meta> 要素を出力するようにするか、迷ったのですが、

  • Frontmatter 項目として定義されていない名前が Frontmatter のトップレベルにあった場合は、meta: … で指定されているのと同様に HTML <meta> 要素を出力する

の場合に、同じ name の <meta> 要素を複数記述するときに、

---
author:
  - Taro
  - Hanako
---

と書けるようするとなると、 meta: … を使った形式では

---
meta:
  author:
    - Taro
    - Hanako
---

と書くのが自然です。 そこで、 Type を Array<Object> とする案は取り下げて、Type は Object としてその Key/Value の Value が Array の場合には同じ Key の複数の <meta> 要素を出力するとしたいと思います。

それから、 定義済みのプロパティとして https://github.com/vivliostyle/vfm/issues/76#issuecomment-838504296 の lang, dir, id, class、それから header-includes と title

akabekobeko commented 3 years ago

指摘を受けて再定義。

Property Type Description
root Object <html> の属性を Key/Value 形式で定義。
body Object <body> の属性を Key/Value 形式で定義。従来の <body>class プロパティーはここで代替。
link Object <link> の属性を Key/Value 形式で定義。
script Array<Object> <script> の属性を Key/Value 形式で定義。スクリプト直書きは禁止してファイルのリンクに限定するほうがよいと考えている。
lang String <html lang="..."> に対応。
dir String <html dir="..." に対応。"dir"ectory ではなく "dir"ection、テキストの書字方向 ltrrtl などを定義すためのもの。
id String <html id="..."> に対応。
class String <html class=""> に対応。
title String <title> に対応。
header-includes String Pandoc の同プロパティーに対応。HTML の <head> 内を直に複数行テキストとして定義できる。
config Object VFM 自信の設定。maththeme をこちらへ移動予定。VFM オプション disableHtmlFormat なども廃止してここで定義する方式にしたい。関連 #98
meta Object プロパティーが <meta> の Key = name、Value = content に対応。「その他」で既存のルート プロパティーと競合する場合はこちらに定義する。
その他 String <meta> として扱われる。プロパティー名 = name、値 = content に対応。
akabekobeko commented 3 years ago

私の見解。

Frontmatter の抽象化するものがわかりにくい。プロパティーが 5 ぐらいまでなら気にする必要もなかった。しかし

未定義を <meta> として扱うなら Frontmatter は基本的に <meta> の抽象で、例外的に titleconfig があるというのも納得できる。しかし <html> に属するものも追加されて非直感的になった。title など重要度や利用頻度の高いものをルートに置くというのは理解できるものの、暗黙知の複雑化を招いている。

この課題と各種命名を見て考えたこと。

akabekobeko commented 3 years ago

複雑化、混乱を感じるのは

が要因。ルートをタグと config のみにすることで

というメリットがある。デメリットは村上さん提案メリットの裏返しで lang のような重要なものをルートに書けないため、単純なメタデータだけあればよいユーザーとしては定義しにくくなる。

ただしこのデメリットについてはリファレンスにテキストとしてサンプル掲載すればある緩和できるのではないか。typo などを考慮するとゼロから手打ちではなくリファレンスやサンプルからコピペされ、そこから差分編集されるのではなかろうか。

akabekobeko commented 3 years ago

ルートを既定タグと confing だけにした場合を議論するための YAML 参考例。厳し目のルールなので前述の tagNAME にはしていない。

config としていたが VFM 固有であることを明示するために vfm とした。このような命名のタグが <body> 以上の層へ定義されることはないだろう。config もないとは思うが、より安全で VFM 専用なことを明示するためそうしてみた。

html:
  lang: "ja"
  class: "sample"
  dir: "ltr"
  id: "root"
title: "Sample Document"
meta:
  author: "akabeko"
  sample: "contents"
link:
  - rel: "stylesheet"
    type: "text/css"
    href: "sample.css"
  - rel: "stylesheet"
    type: "text/css"
    href: "sample2.css"
script:
  - src: "sample.js"
    defer: "defar"
  - src: "sample2.js"
body:
  class: "sample"
vfm:
  math: false
  partial: false
  disableHtmlFormat: true
  theme: "@vivliostyle/theme-techbook"

author<body> に対する class などが階層化される点は不便になる。ただし直に vivliostyle-cli を利用するユーザーには前述のようにサンプルを提示し、create-book 想定ならテンプレートの Markdown で主要なものを事前定義しておいて穴埋め式にすることでエンド ユーザーの負担を軽減できる。

akabekobeko commented 3 years ago

vivliostyle-cli 向けの話。↑の案でゆく場合、VFM 固有設定は vfm<body> 以上の既定タグについてはそれ以外を読み込んでもらえばよい。現在は独自に remark で解析して VFile を得ているが、どの意見を採用するのであれ vivlistyle-cli など VFM を npm API として参照するもの向けに Frontmatter を TypeScript で型付けしたデータを返すreadMetadata or readFrontmatter 関数を export する予定。

こうすることで vivliostyle-cli の dependencies から VFM と被る unified や remark 参照を消せる。競合による問題を避ける意味でも有用なはず。

akabekobeko commented 3 years ago

現在の VFM には style: string: string[] というオプションがあってこれは rehypejs/rehype-documentstyle にそのまま指定される。しかし Frontmatter で link をサポートするならここで CSS 参照を定義できるし、より詳細な属性も定義可能だから廃止してよさそう。

MurakamiShinyu commented 3 years ago

ここで議論している Frontmatter 拡張案は、次の両方の意味があると考えています:

文書のメタデータということを中心に考えると、言語、タイトル、著者、日付、といった情報(文書メタデータ)は、それが HTML ではどう指定するか(要素の属性か、専用の要素か、meta 要素か)に関わらず、同じ形式(Key: Value)で指定できることが望ましいです。

これらの文書メタデータは、HTML 出力だけではなく、より汎用的に、EPUB 出版物のメタデータ や Web 出版物の Publication Manifest のメタデータなどを出力するのにも使えればよいと考えています。

したがって、まず基本は「Key: Value」形式で文書メタデータを記述できるようにして、HTML 出力では次の方針とする考えです:

このために、

  • HTML の要素と属性の混在

という結果になってます。

たしかに「Key: Value」の指定で、HTML の <meta> タグで出力されるのかどうかが分かりにくいのは問題です。

そこで、

ルートをタグと config のみにすることで

  • ルートには HTML タグと config のみとなり、それ以外 (属性など) はないことが保証される

の案に、 lang, dir, id, class だけを追加するのでどうでしょうか?

ルート要素の idclass は、その文書の ID と文書の種類ごとのクラスを指定するものです。複数の文書で同じスタイルシートを使う場合に、スタイルシートの中で特定の文書 ID や特定の文書クラスに適用するスタイルを使えるようにするために重要です。(VFM の元の Frontmatter 案の class<body> の class 属性を指定するものでしたが、ルート要素の class 属性を指定できるほうが CSS 組版のためにより有用です。)

Frontmatter のルートのプロパティ(Key)として定義するは HTML タグと configlang, dir, id, class に制限する、そして、定義されていない Key が指定されたら HTML <meta> タグで出力するというのがよいと思います。

ここで「HTML タグ」は以下のもの:

(HTML の先頭部分で body 開始タグまでに出現するものをリストアップしたもの。head は不要かもしれない)

以下はこれまで提案してなかったもの:

base

HTML base 要素

例:

base: "https://example.com"

<base href="https://example.com"/>
base:
  href: "https://example.com"
  target: "_blank"

<base href="https://example.com" target="_blank"/>

style

HTML style 要素

例:

style: |
  p {
    color: #26b72b;
  }

<style>
p {
  color: #26b72b;
}
</style>
MurakamiShinyu commented 3 years ago

現在の VFM には style: string: string[] というオプションがあってこれは rehypejs/rehype-documentstyle にそのまま指定される。しかし Frontmatter で link をサポートするならここで CSS 参照を定義できるし、より詳細な属性も定義可能だから廃止してよさそう。

vfm コマンドラインオプションの --style は、Frontmatter でスタイルシートが指定されていなくてもスタイルシート指定付きの HTML を生成するために必要です。 オプションでのスタイルシート指定と Frontmatter でのスタイルシート指定の両方がある場合は、両方ともに HTML に出力し、その順番はオプションで指定されているものを先にするのがよいです。個別の原稿の Frontmatter に指定するスタイルシートは、その原稿用にカスタマイズするためのスタイルシートと考えられるためです。

style (内部スタイルシートと外部スタイルシート)

Frontmatter での外部スタイルシートの指定の方法ですが、

link:
  - rel: "stylesheet"
    href: "style.css"

と書く必要があるとなると、この構文は複雑すぎな気がします。

しかし Frontmatter のルートのプロパティとして定義するのは HTML タグなどに制限する方針から、 stylesheet: "sample.css" だけで指定できるようにするわけにもいきません。

また、link での外部スタイルシートと style での内部スタイルシートとの両方の指定があるとき、HTML 出力でそれらスタイルシートの順番を指定できないという問題もあります。たとえば、

link:
  - rel: "stylesheet"
    href: "style.css"
style: |
  p {
    color: blue;
  }

と書いても、順番を逆に

style: |
  p {
    color: blue;
  }
link:
  - rel: "stylesheet"
    href: "style.css"

と書いても、マップ(連想配列)の項目の順番は意味がないはずなので、HTML 出力は同じ結果になるべきでしょう。 一般に、外部スタイルシートで指定した内容を内部スタイルシートでカスタマイズすることが多いでしょうから、HTML への出力では link で指定されたスタイルシートが先になるのがよいと思います。しかし、外部スタイルシートを内部スタイルシートのあとに指定できないという制限は不自由です。

そこで style プロパティを内部スタイルシートだけでなく外部スタイルシートを指定するのにも使えるようにしたらよいと思います。

例:

style:
  - href: "style.css"
  - |
    p {
      color: blue;
    }
  - href: "print.css"
    media: print

<link ref="stylesheet" href="style.css"/>
<style>
p {
  color: blue;
}
</style>
<link ref="stylesheet" href="print.css" media="print"/>

スタイルシートを1つだけ指定する場合は、配列にする必要はなくて次のように書けるとよいでしょう:

style:
  href: "style.css"
style: |
  p {
    color: blue;
  }

また、値が文字列である場合に、それが href の有効な指定になるものなら href (外部スタイルシート)の指定、そうでないならば内部スタイルシートの内容とみなすことができれば、

style: "style.css"

のように、よりシンプルな指定ができることになります。

内部スタイルシートの指定に属性(例: media)を指定するにはどう記述するかは要検討です。

空文字列をキーにしてその値をスタイルシート内容にする?:

style:
  media: screen
  "" |
    p {
      color: blue;
    }

":" をキーにする案(:: と書けるので書きやすい?):

style:
  media: screen
  :: |
    p {
      color: blue;
    }

それとも、media の指定は次のように書けばよいのだから属性指定は不要?:

style: |
  @media screen {
    p {
      color: blue;
    }
  }
akabekobeko commented 3 years ago

間を空けてしまいましたが議論を継続しましょう。前述のように私はルート要素はタグだけとしたいのですが

という理由から前述の村上さん案で実装してみます。

akabekobeko commented 3 years ago

↑のように書きましたが、それでも実装を避けたいプロパティーがあります。合意、未合意を整理するため、それらを含め改めて表にまとめます。

私も賛成のもの。https://github.com/vivliostyle/vfm/issues/76#issuecomment-841824131 で挙げられていませんでしたが script も追加してあります。

Property Type Parent Description
vfm Object HTML とは対応しない VFM の設定。maththeme をこちらへ移動予定。関連 #98
html Object <html> の属性を Key/Value 形式で定義。lang などルートでも指定可能な属性はルート側を優先する。
lang String <html lang="..."> に対応。
dir String <html dir="..." に対応。"dir"ectory ではなく "dir"ection、テキストの書字方向 ltrrtl などを定義すためのもの。
id String <html id="..."> に対応。
class String <html class=""> に対応。
body Object <html> <body> の属性を Key/Value 形式で定義。
title String <head> <title> に対応。
link Object <head> <link> の属性を Key/Value 形式で定義。順番制御のために Array<Object> としたほうがよいか。
script Array<Object> <head> <script> の属性を Key/Value 形式で定義。スクリプト直書きは禁止してファイルのリンクに限定するほうがよいと考えている。
base Object <head> <base> の属性を Key/Value 形式で定義。
meta Object <head> <meta>namecontent をそれぞれ Key/Value 形式で定義 (列挙)。「その他」で既存ルート プロパティーと競合する場合はこちらに定義する。
その他 String <head> <meta> として扱われる。プロパティー名 = name、値 = content に対応。

私としては反対 (未合意) のもの。

Property Type Parent Description
head String <html> <head> 内容を複数行テキストとして定義。Pandoc の header-includes に相当する。
style String <head> <style> の内容を複数行テキストとして定義。

その理由。

MurakamiShinyu commented 3 years ago

特定のMarkdown原稿ファイルでその原稿内にだけ適用したいスタイルを定義したいという場合、Markdown原稿ファイル内にスタイルシートを書けると便利です。

現状の vfm でも(ほかの多くのMarkdown処理系でも)Markdownファイル内に <style> 要素でスタイルシートを入れることは可能で、その場合、HTML の <body> 要素内に <style> 要素が出力されることになります。HTML 的に正しくありませんが、すべてのメジャーブラウザでそのスタイルシートは有効です。Vivliostyle Coreでもそれを有効にしました (See: https://github.com/vivliostyle/vivliostyle.js/issues/655 )。

Markdown に <style> タグでスタイルシートを入れることは、裏技的ですが、そのニーズがあって使われています。「Markdown CSS 埋め込み」でググるとそのようなノウハウが書かれた記事が見つかります。

このような裏技的な方法ではなく、HTML 的に正しく <head> 要素内に <style> 要素を出力するための frontmatter の記述方法が必要だと思います。

そのために head (Pandoc の header-includes 相当)または style プロパティーのどちらかがあるとよいと思います。 (まずは style に絞ってもよいと思います)

以下、反対されている理由に対してコメントします:

  • head と他の <head> 系について優先順位を決める必要がある

pandocの header-includes と同様、head要素の最後に出力するのでよいでしょう。

  • head があると他の <head> 系の存在がぼやけてわかりにくい
  • headstyle を兼ねられる、共に複数行テキストなので対象と目的が競合している

Markdownは、HTMLコードを直接書くこともできるものを簡易記法で定義するものなので、 <head> 内のHTML要素にしてもHTMLコードを直接書く方法と、より簡潔な記法との両方があって問題はないと思います。

  • stylelink で代替可能

link では外部スタイルシートしか指定できません。特定の原稿ファイル内だけに適用したいスタイルシートを別ファイルにしたくないケースは多いと思います。

  • id など一部プロパティー、meta とその他の競合は優先度に影響しないため許容するが、これらは影響するうえ特に head は他を兼ねられるのでやめたほうがいい

優先度については、前述のとおり(head で指定されたものは最後に出力)。

たしかに style や他の head 内 HTML 要素に対応するプロパティーがあれば head の必要性はそれほどないかもしれません。 しかしMarkdown内にHTMLコードを直接書きたいことがあるように、<head> 内のHTMLコードをYAML形式に変換しないで直接書きたいケースはあると思います。

  • 繰り返しになりますが複数行テキスト系には反対です

複数行テキスト系がダメな理由はなんでしょう?

akabekobeko commented 3 years ago

複数行テキストに反対している理由は、それ自体というより YAML 内に HTML/CSS が直書きされるにも関わらず VFM としてはそれを検証できないためです。VFM としては YAML 処理までしか責任が持てず、もし HTML/CSS に構文エラーがあってもエラーなどは出せません。フリー スタイルで書けることによる想定外の問題も危惧しています。

...と自分で書いて思ったのですが、そもそも YAML 検証も remark プラグインに投げていますし他の処理もエラー ハンドリングを特にしていないのでユーザーの自己責任ということでよさそうですね。

ですので headstyle 間の優先順 (style が後?) だけ決まれば合意ということで、これらも実装します。

akabekobeko commented 3 years ago

style 対応するので linkArray<Object> にします。順番保証は必要なので Array にしたいのと Object をぶら下げることが複雑に感じられる (YAML だとそうでもないとは思いますが) なら style で直書きしてもらう想定です。

akabekobeko commented 3 years ago

以上を踏まえて改めて表を書き出します。これを実装の最終候補とする予定です。

Property Type Parent Description
vfm Object HTML とは対応しない VFM の設定。maththeme をこちらへ移動予定。関連 #98
html Object <html> の属性を Key/Value 形式で定義。lang などルートでも指定可能な属性はルート側を優先する。
lang String <html lang="..."> に対応。
dir String <html dir="..." に対応。"dir"ectory ではなく "dir"ection、テキストの書字方向 ltrrtl などを定義すためのもの。
id String <html id="..."> に対応。
class String <html class=""> に対応。
body Object <html> <body> の属性を Key/Value 形式で定義。
head String <html> <head> 内容を複数行テキストとして定義。Pandoc の header-includes に相当する。すべての <head> 系に対して末尾へ出力される。
title String <head> <title> に対応。
style String <head> <style> の内容を複数行テキストとして定義。すべての <head> 系に対して末尾、ただし head の直前へ出力される。
link Array<Object> <head> <link> の属性を Key/Value 形式の配列で定義。
script Array<Object> <head> <script> の属性を Key/Value 形式の配列で定義。
base Object <head> <base> の属性を Key/Value 形式で定義。
meta Object <head> <meta>namecontent をそれぞれ Key/Value 形式で定義 (列挙)。「その他」で既存ルート プロパティーと競合する場合はこちらに定義する。
その他 String <head> <meta> として扱われる。プロパティー名 = name、値 = content に対応。
MurakamiShinyu commented 3 years ago

headstyle の順番ですが、自由に何でも書ける head は最後にして style はその前がよいと思います。

akabekobeko commented 3 years ago

@MurakamiShinyu ↑↑の表に反映しました。本業多忙につき実装は今週の土日あたりからになりそうです。

またメタデータ周りの設計をけっこういじりそうなので PR が出るまで間があきそうです。そのためで表に問題などがあれば、ここで継続的に指摘や議論をお願いします。

akabekobeko commented 3 years ago

ひととおり実装したが stylehead について問題あり。メタデータに基づき hastscript で Markdown AST を生成しているが以下の問題あり。

プレーンテキストとして HTML を出力できない

AST として { type: 'html', text } を指定すると "Cannot compile unknown node html" というエラーになる。そのため hastscript で何らかのタグにする必要あり。

style のインデントが無視される

h('style', text) で読み込まれた自由書式テキストを指定してもインデントは無視され、改行のみが評価される。rehype-format を有効にしても整形の対象外。

YAML

style: |
  @media screen {
    p {
      color: blue;
    }
  }

HTML

<style>@media screen {
p {
color: blue;
}
}</style>

head を処理できない

style は一つの <style> 内に出力されるため、インデントの問題はあるものの対応可能。しかし head<head> の末尾へそのまま出力する仕様だが、前述のようにプレーン テキスト中の HTML タグがエスケープされる問題により hastscript で単一タグとする必要がある。つまり仕様どおり複数タグ出力とはゆかない。

継続調査するが現在の仕様だと head だけは対応できない。

akabekobeko commented 3 years ago

以下の仕様を見て { type: 'html', text } が通用すると考えていたが実際には "Cannot compile unknown node html" になる。

akabekobeko commented 3 years ago

{ type: 'html', value: text } とした場合

Cannot compile unknown node `html`

  at serialize (node_modules/hast-util-to-html/lib/one.js:24:11)
  at all (node_modules/hast-util-to-html/lib/all.js:15:22)
  at Object.serializeElement [as element] (node_modules/hast-util-to-html/lib/element.js:56:13)
  at serialize (node_modules/hast-util-to-html/lib/one.js:27:24)
  at all (node_modules/hast-util-to-html/lib/all.js:15:22)
  at Object.serializeElement [as element] (node_modules/hast-util-to-html/lib/element.js:56:13)
  at serialize (node_modules/hast-util-to-html/lib/one.js:27:24)
  at Object.all [as root] (node_modules/hast-util-to-html/lib/all.js:15:22)
  at serialize (node_modules/hast-util-to-html/lib/one.js:27:24)
  at toHtml (node_modules/hast-util-to-html/lib/index.js:47:10)

h('', text) とした場合

<div>
  &#x3C;meta name="head1" content="head1">
  &#x3C;meta name="head2" content="head2">
</div>
akabekobeko commented 3 years ago

style の制限事項として YAML 的にインデントが必須。

style: |
  @media screen {
    p {
      color: blue;
    }
  }

は OK だが

style: |
@media screen {
  p {
    color: blue;
  }
}

はエラーになる。

YAMLException: end of the stream or a document separator is expected (30:1)

     27 |   - type: 'text/javascript'
     28 |     src: 'sample2.js'
     29 | style: |
     30 | @media screen {
    ------^
     31 |   p {
     32 |     color: blue;

      106 | const mdast = () => (tree: Node, file: MetadataVFile) => {
      107 |   visit<FrontmatterContent>(tree, ['yaml'], (node) => {
    > 108 |     const value = yaml(node.value);
          |                   ^
      109 |     if (typeof value === 'object') {
      110 |       file.data = {
      111 |         ...file.data,

      at generateError (node_modules/js-yaml/lib/loader.js:183:10)
      at throwError (node_modules/js-yaml/lib/loader.js:187:9)
      at readDocument (node_modules/js-yaml/lib/loader.js:1645:5)
      at loadDocuments (node_modules/js-yaml/lib/loader.js:1688:5)
      at Object.load (node_modules/js-yaml/lib/loader.js:1714:19)
      at src/plugins/metadata.ts:108:19
      at overload (node_modules/unist-util-visit/index.js:27:12)
      at visit (node_modules/unist-util-visit-parents/index.js:56:27)
      at visit (node_modules/unist-util-visit-parents/index.js:67:75)
      at visitParents (node_modules/unist-util-visit-parents/index.js:29:26)
akabekobeko commented 3 years ago

@MurakamiShinyu

現状と見解をまとめます。一読の上、村上さんの意見をうかがいたいです。

style

CSS を定義する目的は達成可能です。ただしファイルを用意せず Frontmatter だけで簡易に CSS を定義する目的としてはイマイチだと思います。

YAML のインデントが必須であり、VS Code のような高機能エディターであっても YAML 内に CSS が記述されることを想定していないため入力補完を利用できません。

一方、ファイルであれば用意の手間こそあれど CSS ファイルとして VS Code など様々なツールの恩恵を受けられますし link で順番保証しつつ定義可能です。本機能を議論した際にも書いていますが CSS 定義として見た場合は link と機能が重複しますし、link のほうが属性を自由に定義できる点で上位互換となります。

以上を踏まえると実装はしましたが採用にはあまり賛成できません。

head

現時点では処理に成功していません。また style と同様に Frontmatter で対応する機能としてイマイチに思えます。<head> として必要な機能は meta などのように個別プロパティーで対応するほうが好ましいのではないでしょうか。

共通

複数行テキストに反対している理由は、それ自体というより YAML 内に HTML/CSS が直書きされるにも関わらず VFM としてはそれを検証できないためです。VFM としては YAML 処理までしか責任が持てず、もし HTML/CSS に構文エラーがあってもエラーなどは出せません。フリー スタイルで書けることによる想定外の問題も危惧しています。

という理由から自由書式 (複数行テキスト) に反対しており、しかし VFM としては YAML パーサーに処理を丸投げしているのでこの範疇で処理可能なら採用としました。

実際に style は挙動と問題点がほぼ想定通りです。しかし他のプロパティーと機能的に重複しながら自由書式としての記述性と出力における再現性がイマイチな点を考慮すると、両プロパティーともやはり好ましくないと考えています。

akabekobeko commented 3 years ago

参考としてローカルで実装したテストコード (head を除く成功したもの) を掲載します。

it('All documents', () => {
  const md = `---
id: 'my-page'
lang: 'ja'
dir: 'ltr'
class: 'my-class'
title: 'Title'
html:
  data-color-mode: 'dark'
  data-light-theme: 'light'
  data-dark-theme: 'dark'
body:
  id: 'body'
  class: 'body'
base:
  target: '_top'
  href: 'https://www.example.com/'
meta:
  sample-meta1: 'meta1'
  sample-meta2: 'meta2'
link:
  - rel: 'stylesheet'
    href: 'sample1.css'
  - rel: 'stylesheet'
    href: 'sample2.css'
script:
  - type: 'text/javascript'
    src: 'sample1.js'
  - type: 'text/javascript'
    src: 'sample2.js'
style: |
  @media screen {
    p {
      color: blue;
    }
  }
author: 'Author'
vfm:
  math: false
  theme: 'theme.css'
---

Text
`;

  const received = stringify(md);
  const expected = `<!doctype html>
<html data-color-mode="dark" data-light-theme="light" data-dark-theme="dark" id="my-page" lang="ja" dir="ltr" class="my-class">
  <head>
    <meta charset="utf-8">
    <title>Title</title>
    <base target="_top" href="https://www.example.com/">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="sample-meta1" content="meta1">
    <meta name="sample-meta2" content="meta2">
    <meta name="author" content="Author">
    <link rel="stylesheet" href="sample1.css">
    <link rel="stylesheet" href="sample2.css">
    <script type="text/javascript" src="sample1.js"></script>
    <script type="text/javascript" src="sample2.js"></script>
    <style>@media screen {
p {
color: blue;
}
}</style>
  </head>
  <body id="body" class="body">
    <p>Text</p>
  </body>
</html>
`;
  expect(received).toBe(expected);
});
MurakamiShinyu commented 3 years ago

stylep > q のようなセレクタが使えない( p &gt; q のように変換されてしまう)というのが問題ですね。 headstyle について、今回は採用しなくてもよいだろうと思います。

将来また再検討ということでよいでしょう。 HTML仕様でexternal stylesheet(<link rel=stylesheet>)とinternal stylesheet(<style>)の両方のしくみがあるように、一方があるからもう一方は不要、という関係ではないと思います。

akabekobeko commented 3 years ago

承知しました。stylehead は将来検討にします。

一方があるからもう一方は不要、という関係ではないと思います。

こちらについても了解しました。ドキュメント込みで本日中に PR を出す予定です。