mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
Fix bunch of GitHub integration issues faced by Vlang (#6839)
Signed-off-by: Anna Khismatullina <anna.khismatullina@gmail.com>
This commit is contained in:
parent
b21f860c9a
commit
dbbd653e09
@ -185,6 +185,8 @@
|
||||
{/each}
|
||||
{/if}
|
||||
</th>
|
||||
{:else if node.type === MarkupNodeType.comment}
|
||||
<!-- Ignore -->
|
||||
{:else}
|
||||
unknown node: "{node.type}"
|
||||
{#if nodes.length > 0}
|
||||
|
@ -32,6 +32,7 @@ import { CodeBlockExtension, codeBlockOptions } from '../nodes'
|
||||
import { DefaultKit, DefaultKitOptions } from './default-kit'
|
||||
import { CodeExtension, codeOptions } from '../marks/code'
|
||||
import { NoteBaseExtension } from '../marks/noteBase'
|
||||
import { CommentNode } from '../nodes/comment'
|
||||
|
||||
const headingLevels: Level[] = [1, 2, 3, 4, 5, 6]
|
||||
|
||||
@ -88,6 +89,7 @@ export const ServerKit = Extension.create<ServerKitOptions>({
|
||||
TodoItemNode,
|
||||
TodoListNode,
|
||||
ReferenceNode,
|
||||
CommentNode,
|
||||
NodeUuid,
|
||||
NoteBaseExtension
|
||||
]
|
||||
|
51
packages/text/src/markdown/__tests__/compare.test.ts
Normal file
51
packages/text/src/markdown/__tests__/compare.test.ts
Normal file
@ -0,0 +1,51 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { parseMessageMarkdown, serializeMessage } from '..'
|
||||
import { ServerKit } from '../../kits/server-kit'
|
||||
import { isMarkdownsEquals } from '../compare'
|
||||
|
||||
const refUrl: string = 'ref://'
|
||||
const imageUrl: string = 'http://localhost'
|
||||
|
||||
const extensions = [ServerKit]
|
||||
|
||||
const tests: Array<{ name: string, body: string }> = [
|
||||
{ name: 'Italic', body: '*Asteriscs* and _Underscores_' },
|
||||
{ name: 'Bold', body: '**Asteriscs** and __Underscores__' },
|
||||
{ name: 'Bullet list with asteriscs', body: 'Asterisks :\r\n* Firstly\r\n* Secondly' },
|
||||
{ name: 'Bullet list with dashes', body: 'Dashes :\r\n- Firstly\r\n- Secondly' },
|
||||
{ name: 'TODO list with asteriscs', body: '* [ ] Take\n* [ ] Do\n\n' },
|
||||
{ name: 'TODO list with dashes', body: '- [x] Take\n- [ ] Do\n\n' },
|
||||
{
|
||||
name: 'Different markers',
|
||||
body: 'Asterisks bulleted list:\r\n* Asterisks: *Italic* and **Bold*** Underscores: _Italic_ and __Bold__\r\n\r\nDash bulleted list:\r\n- Asterisks: *Italic* and **Bold**\r\n- Underscores: _Italic_ and __Bold__\r\n-'
|
||||
},
|
||||
{ name: 'Single line comment', body: '<!-- Do not earase me -->' },
|
||||
{
|
||||
name: 'Real with multiline comment',
|
||||
body: '"<!--\r\n\r\nPlease title your PR as follows: `module: description` (e.g. `time: fix date format`).\r\nAlways start with the thing you are fixing, then describe the fix.\r\nDon\'t use past tense (e.g. "fixed foo bar").\r\n\r\nExplain what your PR does and why.\r\n\r\nIf you are adding a new function, please document it and add tests:\r\n\r\n```\r\n// foo does foo and bar\r\nfn foo() {\r\n\r\n// file_test.v\r\nfn test_foo() {\r\n assert foo() == ...\r\n ...\r\n}\r\n```\r\n\r\nIf you are fixing a bug, please add a test that covers it.\r\n\r\nBefore submitting a PR, please run `v test-all` .\r\nSee also `TESTS.md`.\r\n\r\nI try to process PRs as soon as possible. They should be handled within 24 hours.\r\n\r\nApplying labels to PRs is not needed.\r\n\r\nThanks a lot for your contribution!\r\n\r\n-->\r\n\r\nThis PR fix issue #22424\r\n\r\n\r\n"'
|
||||
}
|
||||
]
|
||||
|
||||
describe('md compatibility', () => {
|
||||
tests.forEach(({ name, body: description }) => {
|
||||
it(name, () => {
|
||||
const json = parseMessageMarkdown(description, refUrl, imageUrl, extensions)
|
||||
const serialized = serializeMessage(json, refUrl, imageUrl)
|
||||
expect(isMarkdownsEquals(description, serialized)).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
@ -96,6 +96,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'bulletList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'listItem',
|
||||
@ -154,6 +157,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'todoList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'todoItem',
|
||||
@ -216,6 +222,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'todoList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'todoItem',
|
||||
@ -253,6 +262,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'bulletList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'listItem',
|
||||
@ -313,6 +325,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'todoList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'todoItem',
|
||||
@ -334,6 +349,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'bulletList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'listItem',
|
||||
@ -354,6 +372,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'todoList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'todoItem',
|
||||
@ -375,6 +396,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'bulletList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'listItem',
|
||||
@ -418,6 +442,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'todoList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'todoItem',
|
||||
@ -435,6 +462,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'todoList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'todoItem',
|
||||
@ -487,6 +517,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'todoList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'todoItem',
|
||||
@ -504,6 +537,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'bulletList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'listItem',
|
||||
@ -524,6 +560,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'todoList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'todoItem',
|
||||
@ -549,6 +588,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'bulletList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'listItem',
|
||||
@ -565,6 +607,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'todoList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'todoItem',
|
||||
@ -586,6 +631,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'bulletList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'listItem',
|
||||
@ -633,6 +681,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'todoList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'todoItem',
|
||||
@ -650,6 +701,9 @@ Lorem ipsum dolor sit amet.
|
||||
},
|
||||
{
|
||||
type: 'todoList',
|
||||
attrs: {
|
||||
bullet: '-'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'todoItem',
|
||||
|
@ -34,13 +34,20 @@ export function calcSørensenDiceCoefficient (a: string, b: string): number {
|
||||
* Perform markdown diff/comparison to understand do we have a major differences.
|
||||
*/
|
||||
export function isMarkdownsEquals (source1: string, source2: string): boolean {
|
||||
const lines1 = source1
|
||||
.split('\n')
|
||||
.map((it) => it.trimEnd())
|
||||
.join('\n')
|
||||
const lines2 = source2
|
||||
.split('\n')
|
||||
.map((it) => it.trimEnd())
|
||||
.join('\n')
|
||||
const normalizeLineEndings = (str: string): string => str.replace(/\r?\n/g, '\n')
|
||||
|
||||
const excludeBlankLines = (str: string): string =>
|
||||
str
|
||||
.split('\n')
|
||||
.map((it) => it.trimEnd())
|
||||
.filter((it) => it.length > 0)
|
||||
.join('\n')
|
||||
|
||||
const norm1 = normalizeLineEndings(source1)
|
||||
const lines1 = excludeBlankLines(norm1)
|
||||
|
||||
const norm2 = normalizeLineEndings(source2)
|
||||
const lines2 = excludeBlankLines(norm2)
|
||||
|
||||
return lines1 === lines2
|
||||
}
|
||||
|
@ -417,8 +417,18 @@ const tokensBlock: Record<string, ParsingBlockRule> = {
|
||||
paragraph: { block: MarkupNodeType.paragraph },
|
||||
list_item: { block: MarkupNodeType.list_item },
|
||||
task_item: { block: MarkupNodeType.taskItem, getAttrs: (tok) => ({ 'data-type': 'taskItem' }) },
|
||||
bullet_list: { block: MarkupNodeType.bullet_list },
|
||||
todo_list: { block: MarkupNodeType.todoList },
|
||||
bullet_list: {
|
||||
block: MarkupNodeType.bullet_list,
|
||||
getAttrs: (tok) => ({
|
||||
bullet: tok.markup
|
||||
})
|
||||
},
|
||||
todo_list: {
|
||||
block: MarkupNodeType.todoList,
|
||||
getAttrs: (tok) => ({
|
||||
bullet: tok.markup
|
||||
})
|
||||
},
|
||||
todo_item: {
|
||||
block: MarkupNodeType.todoItem,
|
||||
getAttrs: (tok) => ({
|
||||
@ -523,9 +533,24 @@ const tokensNode: Record<string, ParsingNodeRule> = {
|
||||
hardbreak: { node: MarkupNodeType.hard_break }
|
||||
}
|
||||
const tokensMark: Record<string, ParsingMarkRule> = {
|
||||
em: { mark: MarkupMarkType.em },
|
||||
bold: { mark: MarkupMarkType.bold },
|
||||
strong: { mark: MarkupMarkType.bold },
|
||||
em: {
|
||||
mark: MarkupMarkType.em,
|
||||
getAttrs: (tok: Token, state: MarkdownParseState) => {
|
||||
return { marker: tok.markup }
|
||||
}
|
||||
},
|
||||
bold: {
|
||||
mark: MarkupMarkType.bold,
|
||||
getAttrs: (tok: Token, state: MarkdownParseState) => {
|
||||
return { marker: tok.markup }
|
||||
}
|
||||
},
|
||||
strong: {
|
||||
mark: MarkupMarkType.bold,
|
||||
getAttrs: (tok: Token, state: MarkdownParseState) => {
|
||||
return { marker: tok.markup }
|
||||
}
|
||||
},
|
||||
s: { mark: MarkupMarkType.strike },
|
||||
u: { mark: MarkupMarkType.underline },
|
||||
code_inline: {
|
||||
@ -602,6 +627,7 @@ export class MarkdownParser {
|
||||
html: true
|
||||
})
|
||||
this.tokenizer.core.ruler.after('inline', 'task_list', this.listRule)
|
||||
this.tokenizer.core.ruler.after('inline', 'html_comment', this.htmlCommentRule)
|
||||
|
||||
this.tokenHandlers = tokenHandlers(tokensBlock, tokensNode, tokensMark, specialRule, ignoreRule, extensions)
|
||||
}
|
||||
@ -619,6 +645,19 @@ export class MarkdownParser {
|
||||
return doc
|
||||
}
|
||||
|
||||
htmlCommentRule: RuleCore = (state: StateCore): boolean => {
|
||||
const tokens = state.tokens
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
// Prosemirror entirely ignores comments when parsing, so
|
||||
// here we replaces html comment tag with a custom tag so the comments got parsed as a node
|
||||
if (tokens[i].type === 'html_block' || tokens[i].type === 'html_inline') {
|
||||
const content = tokens[i].content.replaceAll('<!--', '<comment>').replaceAll('-->', '</comment>')
|
||||
tokens[i].content = content
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
listRule: RuleCore = (state: TaskListStateCore): boolean => {
|
||||
const tokens = state.tokens
|
||||
const states: Array<{ closeIdx: number, lastItemIdx: number }> = []
|
||||
@ -647,7 +686,9 @@ export class MarkdownParser {
|
||||
if (itemCloseIdx === -1) {
|
||||
itemCloseIdx = tokens.length - i
|
||||
} else if (tokens[i].type !== tokens[tokens.length - itemCloseIdx].type) {
|
||||
tokens.splice(i + 1, 0, new state.Token('bullet_list_open', 'ul', 1))
|
||||
const bulletListOpen = new state.Token('bullet_list_open', 'ul', 1)
|
||||
bulletListOpen.markup = tokens[i + 1].markup
|
||||
tokens.splice(i + 1, 0, bulletListOpen)
|
||||
tokens.splice(i + 1, 0, new state.Token('bullet_list_close', 'ul', -1))
|
||||
convertTodoList(tokens, i + 2, tokens.length - listCloseIdx, tokens.length - itemCloseIdx)
|
||||
listCloseIdx = tokens.length - i - 1
|
||||
|
@ -4,7 +4,7 @@ import { messageContent, nodeAttrs } from './node'
|
||||
import { MarkupMark, MarkupNode, MarkupNodeType } from '../markup/model'
|
||||
import { defaultExtensions } from '../extensions'
|
||||
|
||||
type FirstDelim = (i: number, attrs?: Record<string, any>) => string
|
||||
type FirstDelim = (i: number, attrs?: Record<string, any>, parentAttrs?: Record<string, any>) => string
|
||||
interface IState {
|
||||
wrapBlock: (delim: string, firstDelim: string | null, node: MarkupNode, f: () => void) => void
|
||||
flushClose: (size: number) => void
|
||||
@ -59,13 +59,14 @@ function isPlainURL (link: MarkupMark, parent: MarkupNode, index: number): boole
|
||||
return index === (parent.content?.length ?? 0) - 1 || !isInSet(link, parent.content?.[index + 1]?.marks ?? [])
|
||||
}
|
||||
|
||||
const formatTodoItem: FirstDelim = (i, attrs) => {
|
||||
const formatTodoItem: FirstDelim = (i, attrs, parentAttrs?: Record<string, any>) => {
|
||||
const meta =
|
||||
attrs?.todoid !== undefined && attrs?.userid !== undefined
|
||||
? `<!-- todoid=${attrs?.todoid},userid=${attrs?.userid} -->`
|
||||
: ''
|
||||
|
||||
return `* [${attrs?.checked === true ? 'x' : ' '}] ${meta}`
|
||||
const bullet = parentAttrs?.bullet ?? '*'
|
||||
return `${bullet} [${attrs?.checked === true ? 'x' : ' '}] ${meta}`
|
||||
}
|
||||
|
||||
// *************************************************************
|
||||
@ -196,6 +197,11 @@ export const storeNodes: Record<string, NodeProcessor> = {
|
||||
')'
|
||||
)
|
||||
},
|
||||
comment: (state, node) => {
|
||||
state.write('<!--')
|
||||
state.renderInline(node)
|
||||
state.write('-->')
|
||||
},
|
||||
hardBreak: (state, node, parent, index) => {
|
||||
const content = messageContent(parent)
|
||||
for (let i = index + 1; i < content.length; i++) {
|
||||
@ -667,7 +673,7 @@ export class MarkdownState implements IState {
|
||||
firstDelim: FirstDelim
|
||||
): void {
|
||||
if (i > 0 && isTight) this.flushClose(1)
|
||||
this.wrapBlock(delim, firstDelim(i, node.content?.[i].attrs), node, () => {
|
||||
this.wrapBlock(delim, firstDelim(i, node.content?.[i].attrs, node.attrs), node, () => {
|
||||
this.render(child, node, i)
|
||||
})
|
||||
}
|
||||
@ -711,8 +717,11 @@ export class MarkdownState implements IState {
|
||||
// : (Mark, bool, string?) → string
|
||||
// Get the markdown string for a given opening or closing mark.
|
||||
markString (mark: MarkupMark, open: boolean, parent: MarkupNode, index: number): string {
|
||||
const info = this.marks[mark.type]
|
||||
const value = open ? info.open : info.close
|
||||
let value = mark.attrs?.marker
|
||||
if (value === undefined) {
|
||||
const info = this.marks[mark.type]
|
||||
value = open ? info.open : info.close
|
||||
}
|
||||
return typeof value === 'string' ? value : value(this, mark, parent, index) ?? ''
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,8 @@ export enum MarkupNodeType {
|
||||
table = 'table',
|
||||
table_row = 'tableRow',
|
||||
table_cell = 'tableCell',
|
||||
table_header = 'tableHeader'
|
||||
table_header = 'tableHeader',
|
||||
comment = 'comment'
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
@ -200,7 +200,7 @@ export function markupToHTML (markup: Markup, extensions?: Extensions): string {
|
||||
/** @public */
|
||||
export function htmlToJSON (html: string, extensions?: Extensions): MarkupNode {
|
||||
extensions = extensions ?? defaultExtensions
|
||||
return generateJSON(html, extensions) as MarkupNode
|
||||
return generateJSON(html, extensions, { preserveWhitespace: 'full' }) as MarkupNode
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
47
packages/text/src/nodes/comment.ts
Normal file
47
packages/text/src/nodes/comment.ts
Normal file
@ -0,0 +1,47 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Node } from '@tiptap/core'
|
||||
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Comment {
|
||||
parentNode: ProseMirrorNode | null
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const CommentNode = Node.create({
|
||||
name: 'comment',
|
||||
group: 'inline',
|
||||
inline: true,
|
||||
content: 'text*',
|
||||
marks: '_',
|
||||
|
||||
parseHTML () {
|
||||
return [
|
||||
{
|
||||
tag: 'comment'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
renderText () {
|
||||
return ''
|
||||
}
|
||||
})
|
@ -18,4 +18,5 @@ export * from './reference'
|
||||
export * from './todo'
|
||||
export * from './file'
|
||||
export * from './codeblock'
|
||||
export * from './comment'
|
||||
export { getDataAttribute } from './utils'
|
||||
|
@ -16,7 +16,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { MarkupNode, ReferenceNode, jsonToPmNode } from '@hcengineering/text'
|
||||
import { MarkupNode, ReferenceNode, CommentNode, jsonToPmNode } from '@hcengineering/text'
|
||||
import { Editor, Extension, mergeAttributes } from '@tiptap/core'
|
||||
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
||||
import { DecorationSet } from '@tiptap/pm/view'
|
||||
@ -86,7 +86,8 @@
|
||||
objectClass
|
||||
}),
|
||||
ReferenceNode,
|
||||
DecorationExtension
|
||||
DecorationExtension,
|
||||
CommentNode
|
||||
],
|
||||
onTransaction: () => {
|
||||
// force re-render so `editor.isActive` works as expected
|
||||
|
@ -399,7 +399,7 @@ A list of closed updated issues`
|
||||
|
||||
const md = serializeMessage(msg, 'ref://', 'http://')
|
||||
|
||||
expect(md).toEqual('**BOLD *ITALIC* BOLD**')
|
||||
expect(md).toEqual(t1)
|
||||
})
|
||||
|
||||
it('Check styles-2', () => {
|
||||
@ -697,6 +697,15 @@ A list of closed updated issues`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: MarkupNodeType.paragraph,
|
||||
content: [
|
||||
{
|
||||
text: '\n',
|
||||
type: MarkupNodeType.text
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: MarkupNodeType.heading,
|
||||
attrs: { level: 2 },
|
||||
@ -719,6 +728,9 @@ A list of closed updated issues`
|
||||
content: [
|
||||
{
|
||||
type: MarkupNodeType.bullet_list,
|
||||
attrs: {
|
||||
bullet: '*'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: MarkupNodeType.list_item,
|
||||
|
Loading…
Reference in New Issue
Block a user