ProseMirror Guide (3)

정현석·2020년 12월 5일
0

ProseMirror

목록 보기
3/7

Schemas

Each ProseMirror document has a schema associated with it. The schema describes the kind of nodes that may occur in the document, and the way they are nested. For example, it might say that the top-level node can contain one or more blocks, and that paragraph nodes can contain any number of inline nodes, with any marks applied to them.

There is a package with a basic schema available, but the nice thing about ProseMirror is that it allows you to define your own schemas.

Node Types

document의 각 노드는 타입을 가지고, 이는 의미론적인 의미와 그것의 properties를 나타내며, 에디터에서 어떻게 렌더링되어야하는지를 나타낸다.

스키마를 정의할 때는, 그 스키마 내에서 발생할 노드 타입을 spec object로 정의해야한다:

const trivialSchema = new Schema({
  nodes: {
    doc: {content: "paragraph+"},
    paragraph: {content: "text*"},
    text: {inline: true},
    /* ... and so on */
  }
})

이는 document가 하나 이상의 paragraph를 허용한다는 의미이다. 그리고 각 paragraph는 어떤 숫자의 text든 받아들인다는 것이다.

각 스키마는 최소한 하나의 top-level노드 타입을 가져야만 한다, 기본설정은 doc이고, 그것의 기본 자식은 text이다.

인라인으로 취급되는는 노드는 인라인 프로퍼티로 정의해야한다. text는 이미 기본적으로 인라인이므로 생략해도좋다.

Content Expressions

The strings in the content fields in the example schema above are called content expressions. They control what sequences of child nodes are valid for this node type.

You can say, for example "paragraph" for “one paragraph”, or "paragraph+" to express “one or more paragraphs”. Similarly, "paragraph*" means “zero or more paragraphs” and "caption?" means “zero or one caption node”. You can also use regular-expression-like ranges, such as {2} (“exactly two”) {1, 5} (“one to five”) or {2,} (“two or more”) after node names.

Such expressions can be combined to create a sequence, for example "heading paragraph+" means ‘first a heading, then one or more paragraphs’. You can also use the pipe | operator to indicate a choice between two expressions, as in "(paragraph | blockquote)+".

Some groups of element types will appear multiple times in your schema—for example you might have a concept of “block” nodes, that may appear at the top level but also nested inside of blockquotes. You can create a node group by giving your node specs a group property, and then refer to that group by its name in your expressions.

const groupSchema = new Schema({
  nodes: {
    doc: {content: "block+"},
    paragraph: {group: "block", content: "text*"},
    blockquote: {group: "block", content: "block+"},
    text: {}
  }
})
Here "block+" is equivalent to "(paragraph | blockquote)+".

It is recommended to always require at least one child node in nodes that have block content (such as "doc" and "blockquote" in the example above), because browsers will completely collapse the node when it's empty, making it rather hard to edit.

The order in which your nodes appear in an or-expression is significant. When creating a default instance for a non-optional node, for example to make sure a document still conforms to the schema after a replace step the first type in the expression will be used. If that is a group, the first type in the group (determined by the order in which the group's members appear in your nodes map) is used. If I switched the positions of "paragraph" and "blockquote" in the the example schema, you'd get a stack overflow as soon as the editor tried to create a block node—it'd create a "blockquote" node, whose content requires at least one block, so it'd try to create another "blockquote" as content, and so on.

Not every node-manipulating function in the library checks that it is dealing with valid content—higher level concepts like transforms do, but primitive node-creation methods usually don't and instead put the responsibility for providing sane input on their caller. It is perfectly possible to use, for example NodeType.create, to create a node with invalid content. For nodes that are ‘open’ on the edge of slices, this is even a reasonable thing to do. There is a separate createChecked method, as well as an after-the-fact check method that can be used to assert that a given node's content is valid.

Marks

Mark는 인라인 콘텐츠에 추가적인 스타일링과 정보를 제공하기 위해 이용된다. 스키마는 허용되는 모든 마크 타입을 정의해야한다. 마크 타입은 노드 타입과 같은 오브젝트이고, mark object를 태그하기 위해 사용되고, 추가적인 정보를 제공해야한다.

기본적으로, 인라인 콘텐츠를 가지는 노드는 모든 스키마에 정의된 마크를 허용한다. 이것은 spec에서 정의할 수 있다.

여기 간단한 스키마가있다. strong과 emphasis 마크를 paragraph의 text에 지원하지만, heading에는 지원하지 않는다.

const markSchema = new Schema({
  nodes: {
    doc: {content: "block+"},
    paragraph: {group: "block", content: "text*", marks: "_"},
    heading: {group: "block", content: "text*", marks: ""},
    text: {inline: true}
  },
  marks: {
    strong: {},
    em: {}
  }
})

mark의 세트는 마크 이름이나 마크 그룹의 space-separated string으로 해석된다. "_"는 와일드 카드이다. 비어있는 스트링은 공집합이다.

Attributes

The document schema also defines which attributes each node or mark has. If your node type requires extra node-specific information to be stored, such as the level of a heading node, that is best done with an attribute.

Attribute sets are represented as plain objects with a predefined (per node or mark) set of properties holding any JSON-serializeable values. To specify what attributes it allows, use the optional attrs field in a node or mark spec.


  heading: {
    content: "text*",
    attrs: {level: {default: 1}}
  }

In this schema, every instance of the heading node will have a level attribute under .attrs.level. If it isn't specified when the node is created, it will default to 1.

When you don't give a default value for an attribute, an error will be raised when you attempt to create such a node without specifying that attribute.

That will also make it impossible for the library to generate such nodes as filler to satisfy schema constraints during a transform or when calling createAndFill. This is why you are not allowed to put such nodes in a required position in the schema—in order to be able to enforce the schema constraints, the editor needs to be able to generate empty nodes to fill missing pieces in the content.

Serialization and Parsing

이들을 브라우저에서 편집하기 위해선, 이들을 browser DOM의 document 노드로 표현할 수 있어야한다. 가장 쉬운 방법은 각 노드의 DOM representation을 schema에 포함하는 것인데, node spec의 toDOM 필드를 이용한다.

이 필드는 function을 가져야하고, node를 인자로 받아 DOM structure의 정보를 리턴한다. 이것은 직접적안 DOM 노드 혹은 배열이 될 수 있다:

const schema = new Schema({
  nodes: {
    doc: {content: "paragraph+"},
    paragraph: {
      content: "text*",
      toDOM(node) { return ["p", 0] }
    },
    text: {}
  }
})

["p", 0] 이러한 표현은 paragraph가 HTML p 태그로 렌더링된다는 것이다. 0은 'hole'인데, 이 내용이 렌더링되어야하는 위치이다. 여러분은 또한 HTML attrbitues를 tag name 다음 포함하는 오브젝트를 포함할 수 있는데, 예를 들어 ["div",{class: "c"}, 0] 와 같은 방식이다. 리프 노드는 DOM 표현에 hole을 가질 필요는 없다. 왜냐하면 그들은 content를 가지지 않기 때문이다.

Mark spec은 유사한 toDOM 메서드를 허용하지만, 그들은 직접적으로 content를 래핑하는 단일 태그로써 렌더링하기위해 요구되며, 그럼으로써 컨텐츠는 언제나 직접적으로 반환되는 노드로 가고, hole은 specify될 필요는 없다.

여러분은 또한 document를 DOM 데이터로부터 파싱되어야만 할 필요가 있을것이다, 예를 들어 사용자가 무언가를 editor로 붙여넣을때. model module은 또한 그에 관한 기능을 제공해야한다. 그리고 여러분은 파싱하는 방법에 대한 정보를 직접적으로 여러분의 스키마에 parseDOM 프로퍼티와 함께 포함해야한다.

이는 parse 규칙의 리스트이며, DOM을 주어진 node 혹은 mark를 묘사하기 위해 구성하는 규칙이다. 예를 들어 기본 스키마는 emphasis mark에 대해 다음과 같은 규칙을 가진다.

  parseDOM: [
    {tag: "em"},                 // Match <em> nodes
    {tag: "i"},                  // and <i> nodes
    {style: "font-style=italic"} // and inline 'font-style: italic'
  ]

파싱 규칙의 태그로는 CSS 선택자를 사용할 수도 있다, 그렇기 때문에 "div.myClass"같은것도 할 수 있다. 비슷하게, 스타일 또한 inline CSS 스타일과 매칭된다.

스키마가 parseDOM annotation을 포함할 때, 여러분은 DOMParser 오브젝트와 DOMParser.fromSchema를 만들어낼 수 있다. 이것은 에디터로부터 행해지고, 기본 클립보드 파서를 만들어내며, 오버라이딩하는것도 가능하다.

document는 또한 빌트인 JSON serialization 포맷을 이용한다. 여러분은 toJSON을 불러 안전하게 JSON.stringify를 통과한 오브젝트를 받을 수 있고, 스키마 오브젝트는 nodeFromJSON 메소드를 가지는데, 이는 이 표현을 다시 document로 표현한다.

Extending a schema

The nodes and marks options passed to the Schema constructor take OrderedMap objects as well as plain JavaScript objects. The resulting schema's spec.nodes and spec.marks properties are always OrderedMaps, which can be used as the basis for further schemas.

Such maps support a number of methods to conveniently create updated versions. For example you could say schema.markSpec.remove("blockquote") to derive a set of nodes without the blockquote node, which can then be passed as the nodes field for a new schema.

The schema-list module exports a convenience method to add the nodes exported by those modules to a nodeset.

profile
데이터 사이언스 공부중

0개의 댓글