Move services to public (#6156)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2024-07-28 14:55:43 +07:00
committed by Andrey Sobolev
parent 9aad4a4561
commit ddecae80dd
578 changed files with 109084 additions and 390 deletions
@@ -0,0 +1,112 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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.
//
/* eslint-env jest */
import { setMetadata } from '@hcengineering/platform'
import serverCore from '@hcengineering/server-core'
import { jsonToHTML, htmlToJSON } from '@hcengineering/text'
import { markupToMarkdown, markdownToMarkup, parseMessageMarkdown, serializeMessage } from '..'
import { appendGuestLinkToModel, stripGuestLink } from '../../sync/guest'
import { GithubKit } from '../extensions'
const refUrl: string = 'ref://'
const imageUrl: string = 'http://localhost'
const guestUrl: string = 'http://localhost:8080/guest'
const extensions = [GithubKit]
describe('server', () => {
it('embedded markup parsing', () => {
const markdown = `test5
<img width="721" alt="Screenshot 2024-01-22 at 10 39 21" src="https://github.com/haiodo-dev/my-issues/assets/477235/2452713a-ede2-4e0d-a448-9b1687c95cd9">
<img alt="Screenshot 2024-01-22 at 10 39 26" src="https://github.com/haiodo-dev/my-issues/assets/477235/6a8799fd-242d-4e70-9eba-0a769eede71b">
`
const json = parseMessageMarkdown(markdown, refUrl, imageUrl, guestUrl)
const html = jsonToHTML(json, extensions)
const json2 = htmlToJSON(html, extensions)
const newMarkdown = serializeMessage(json2, refUrl, imageUrl)
console.log(newMarkdown)
})
it('add html link', () => {
const markdown = `test5
<img width="721" alt="Screenshot 2024-01-22 at 10 39 21" src="https://github.com/haiodo-dev/my-issues/assets/477235/2452713a-ede2-4e0d-a448-9b1687c95cd9">
<img alt="Screenshot 2024-01-22 at 10 39 26" src="https://github.com/haiodo-dev/my-issues/assets/477235/6a8799fd-242d-4e70-9eba-0a769eede71b">
<sub>
View at Huly <a href="https://github.com/haiodo-dev/my-issues/issues/1">TSK-1023</a>
</sub>
`
const json = parseMessageMarkdown(markdown, refUrl, imageUrl, guestUrl)
console.log(json)
const html = jsonToHTML(json, extensions)
const json2 = htmlToJSON(html, extensions)
const newMarkdown = serializeMessage(json2, refUrl, imageUrl)
console.log(newMarkdown)
})
it('check parsing with sub', async () => {
const markdown =
'qwe4 qwe6\n\n![](http://localhost:8080/files?workspace=github&file=76e25453-186d-46e5-b9cd-d296a5342ce2&width=997)qwe 77\n\nzzz2 3\n\n<sub>View in Huly <a href="http://localhost:8080/guest/github?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsaW5rSWQiOiI2NWRlYWU4NGExZTRkY2Q2OWFlMzlmOTMiLCJndWVzdCI6InRydWUiLCJlbWFpbCI6IiNndWVzdEBoYy5lbmdpbmVlcmluZyIsIndvcmtzcGFjZSI6ImdpdGh1YiIsInByb2R1Y3RJZCI6IiJ9.6RJjjnn9JgDxQmsZ3AmMj8aqHI7Px4BwPxqyMK83OyM">TSK-50</a></sub>'
const json = parseMessageMarkdown(markdown, refUrl, imageUrl, guestUrl)
setMetadata(serverCore.metadata.FrontUrl, 'http://localhost:8080')
await stripGuestLink(json)
const newMarkdown = serializeMessage(json, refUrl, imageUrl)
console.log(json, newMarkdown)
expect(newMarkdown).toBe(
'qwe4 qwe6\n\n![](http://localhost76e25453-186d-46e5-b9cd-d296a5342ce2&width=997)qwe 77\n\nzzz2 3'
)
})
it('code block', async () => {
const markdown = '```bash\n2\nbash qwe\n3\nbase qwe2\n4\nbaseh qwe4\n5\n```'
const markup = markdownToMarkup(markdown)
const markdown2 = await markupToMarkdown(markup)
expect(markdown2).toBe(markdown)
})
it('block image', async () => {
const markdown = 'qwerty\n<img width="320" src="http://example.com/image" alt="image">'
const markup = markdownToMarkup(markdown)
const markdown2 = await markupToMarkdown(markup)
expect(markdown2).toBe(markdown)
})
it('inline image', async () => {
const markdown = '* line 1\n* line 2\n <img width="320" src="http://example.com/image" alt="image">'
const markup = markdownToMarkup(markdown)
const markdown2 = await markupToMarkdown(markup)
expect(markdown2).toBe(markdown)
})
it('test view in serialization', async () => {
const markdown = '```bash\n2\nbash qwe\n3\nbase qwe2\n4\nbaseh qwe4\n5\n```'
const json = parseMessageMarkdown(markdown, refUrl, imageUrl, guestUrl)
appendGuestLinkToModel(json, 'http://test.com', 'TSK-1235')
const serializedMarkdown = serializeMessage(json, refUrl, imageUrl)
await stripGuestLink(json)
const serializedMarkdown2 = serializeMessage(json, refUrl, imageUrl)
expect(serializedMarkdown).toBe(
'```bash\n2\nbash qwe\n3\nbase qwe2\n4\nbaseh qwe4\n5\n```\n\n<sub><a href="http://test.com">Huly&reg;: <b>TSK-1235</b></a></sub>'
)
expect(serializedMarkdown2).toBe('```bash\n2\nbash qwe\n3\nbase qwe2\n4\nbaseh qwe4\n5\n```')
})
})
@@ -0,0 +1,81 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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.
//
/* eslint-env jest */
import { htmlToJSON, jsonToHTML } from '@hcengineering/text'
import { parseMessageMarkdown, serializeMessage } from '..'
import { defaultExtensions } from '../extensions'
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
import { dirname, join } from 'path'
import { gunzipSync } from 'zlib'
import { calcSørensenDiceCoefficient, isMarkdownsEquals } from '../compare'
const refUrl: string = 'ref://'
const imageUrl: string = 'http://localhost'
const markdownSamples = JSON.parse(gunzipSync(readFileSync('./src/markdown/__tests__/markdown.json.gz')).toString())
const testStrings = Array.from((markdownSamples)).reduce((decodedStrings: { repoName: string, markdownSource: string, markdown: string }[], { markdownEncoded, markdownEncoding, repoName, markdownSource }: any) => {
if (markdownEncoding === 'base64') {
decodedStrings.push({ repoName, markdownSource, markdown: atob(markdownEncoded) })
}
return decodedStrings
}, [])
describe('server', () => {
it('test-all', () => {
let i = 0
const dta = testStrings
let errors: number = 0
let minK = 100
let minKi = 0
const result = []
for (const sample of dta) {
try {
const fileName = join('src', 'markdown', '__tests__', 'markdowns', `${i}${sample.repoName}`, sample.markdownSource)
const json = parseMessageMarkdown(sample.markdown, refUrl, imageUrl)
const html = jsonToHTML(json, defaultExtensions)
const json2 = htmlToJSON(html, defaultExtensions)
const newMarkdown = serializeMessage(json2, refUrl, imageUrl)
const k = calcSørensenDiceCoefficient(sample.markdown, newMarkdown) * 100
minK = Math.min(k, minK)
if (minK === k) {
minKi = i
}
const equals = isMarkdownsEquals(sample.markdown, newMarkdown)
if (k < 50) {
if (!existsSync(dirname(fileName))) {
mkdirSync(dirname(fileName), { recursive: true })
}
writeFileSync(fileName + '_source.md', sample.markdown)
writeFileSync(fileName + '_target.md', newMarkdown)
}
result.push(`${fileName}: ${k} ${equals}`)
} catch (e) {
errors++
// console.error(e)
}
i++
}
console.log('Result', result.join('\n'))
console.log('MinK:', minK, minKi)
console.log('Errors:', errors)
})
})
@@ -0,0 +1,964 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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.
//
/* eslint-env jest */
import {
MarkupMarkType,
MarkupNode,
MarkupNodeType,
jsonToMarkup,
MarkdownState,
traverseAllMarks,
traverseMarkupNode
} from '@hcengineering/text'
import { markdownToMarkup, markupToMarkdown, parseMessageMarkdown, serializeMessage } from '..'
describe('server', () => {
it('todos serialize and back', async () => {
const markdown = `# Contribution checklist
## Brief description
## Checklist
* [ ] - Are screenshots added to PR if applicable?
* [x] - Does the code work as expected and all the requirements in the task are covered?
* [x] - Are all new user-facing texts added through the translations mechanism?
* [x] - Are all of the requirements in the task well tested?
* [x] - Tested in Chrome?
* [x] - Tested in Safari?
* [x] - Have you checked the new code for typos, TODOs, commented LOCs, debug code, etc.?
* [x] - Ensure your branch is up to date with the \`main\` branch
* [x] - Is there any redundant or duplicate code?
* [x] - Are required links added to PR?
* [x] - Is the new code well documented?
## Related issues
A list of closed updated issues`
const markup = markdownToMarkup(markdown)
const markdownAgain = await markupToMarkdown(markup)
expect(markdownAgain).toBe(markdown)
})
it('html to markdown with links', async () => {
const data = jsonToMarkup({
type: MarkupNodeType.doc,
content: [
{
type: MarkupNodeType.paragraph,
content: [
{
type: MarkupNodeType.text,
text: 'As a part of the Platform it would be nice to have an '
},
{
type: MarkupNodeType.text,
text: 'effect',
marks: [{ type: MarkupMarkType.code, attrs: {} }]
},
{
type: MarkupNodeType.text,
text: ' subsystem. Something similar to '
},
{
type: MarkupNodeType.text,
text: 'https://www.effect.website',
marks: [
{
type: MarkupMarkType.link,
attrs: {
href: 'https://www.effect.website'
}
}
]
}
]
}
]
})
const markDownData = await markupToMarkdown(data)
expect(markDownData).toEqual(
'As a part of the Platform it would be nice to have an `effect` subsystem. Something similar to <https://www.effect.website>'
)
})
it('html to markdown with links-2', async () => {
const data = jsonToMarkup({
type: MarkupNodeType.doc,
content: [
{
type: MarkupNodeType.paragraph,
content: [
{
type: MarkupNodeType.text,
text: 'As a part of the Platform it would be nice to have an '
},
{
type: MarkupNodeType.text,
text: 'effect',
marks: [{ type: MarkupMarkType.code, attrs: {} }]
},
{
type: MarkupNodeType.text,
text: ' subsystem. Something similar to '
},
{
type: MarkupNodeType.text,
text: 'Effects',
marks: [
{
type: MarkupMarkType.link,
attrs: {
href: 'https://www.effect.website'
}
}
]
}
]
}
]
})
const markDownData = await markupToMarkdown(data)
expect(markDownData).toEqual(
'As a part of the Platform it would be nice to have an `effect` subsystem. Something similar to [Effects](https://www.effect.website)'
)
})
it('markup to markdown', async () => {
const data = jsonToMarkup({
type: MarkupNodeType.doc,
content: [
{
type: MarkupNodeType.paragraph,
content: [
{
type: MarkupNodeType.reference,
attrs: {
id: '629d8b615bdc96430ced15a0',
objectclass: 'contact:class:Person',
label: 'Sobolev Andrey'
}
},
{
type: MarkupNodeType.text,
text: ' '
},
{
type: MarkupNodeType.reference,
attrs: {
id: '64db123e602161ac4482475c',
objectclass: 'github:class:GithubPullRequest',
label: 'UBERF-16'
}
},
{
type: MarkupNodeType.text,
text: ' link test'
}
]
}
]
})
const data2 =
'[Sobolev Andrey](https://front.hc.engineering/open/?workspace=workspace&_class=contact%3Aclass%3APerson&_id=629d8b615bdc96430ced15a0&label=Sobolev%20Andrey) [UBERF-16](https://front.hc.engineering/open/?workspace=workspace&_class=github%3Aclass%3AGithubPullRequest&_id=64db123e602161ac4482475c&label=UBERF-16) link test'
const html = await markupToMarkdown(data, 'https://front.hc.engineering/open/?workspace=workspace')
expect(html).toEqual(data2)
})
it('markdown to markup', () => {
const data =
'[Sobolev Andrey](https://front.hc.engineering/open/?workspace=workspace&_class=contact%3Aclass%3APerson&_id=629d8b615bdc96430ced15a0&label=Sobolev%20Andrey) [UBERF-16](https://front.hc.engineering/open/?workspace=workspace&_class=github%3Aclass%3AGithubPullRequest&_id=64db123e602161ac4482475c&label=UBERF-16) link test'
const data2 = jsonToMarkup({
type: MarkupNodeType.doc,
content: [
{
type: MarkupNodeType.paragraph,
content: [
{
type: MarkupNodeType.reference,
content: [
{
type: MarkupNodeType.text,
text: 'Sobolev Andrey',
marks: []
}
],
attrs: {
label: 'Sobolev Andrey',
id: '629d8b615bdc96430ced15a0',
objectclass: 'contact:class:Person'
}
},
{
type: MarkupNodeType.text,
text: ' ',
marks: []
},
{
type: MarkupNodeType.reference,
content: [
{
type: MarkupNodeType.text,
text: 'UBERF-16',
marks: []
}
],
attrs: {
label: 'UBERF-16',
id: '64db123e602161ac4482475c',
objectclass: 'github:class:GithubPullRequest'
}
},
{
type: MarkupNodeType.text,
text: ' link test',
marks: []
}
]
}
]
})
const markup = markdownToMarkup(data, 'https://front.hc.engineering/open/?workspace=workspace')
expect(markup).toEqual(data2)
})
it('test conversion', async () => {
const data = jsonToMarkup({
type: MarkupNodeType.doc,
content: [
{
type: MarkupNodeType.paragraph,
content: [
{
type: MarkupNodeType.text,
text: 'Yahoo',
marks: [{ type: MarkupMarkType.bold, attrs: {} }]
},
{
type: MarkupNodeType.text,
text: ' something is strange'
},
{
type: MarkupNodeType.hard_break
},
{
type: MarkupNodeType.text,
text: 'some more line 2'
}
]
},
{
type: MarkupNodeType.heading,
attrs: {
level: 2
},
content: [
{
type: MarkupNodeType.text,
text: 'Header'
}
]
},
{
type: MarkupNodeType.paragraph,
content: [
{
type: MarkupNodeType.text,
text: 'Test 3'
},
{
type: MarkupNodeType.hard_break
},
{
type: MarkupNodeType.hard_break
}
]
},
{
type: MarkupNodeType.bullet_list,
content: [
{
type: MarkupNodeType.list_item,
content: [
{
type: MarkupNodeType.paragraph,
content: [
{
type: MarkupNodeType.text,
text: 'qeqwe'
}
]
}
]
},
{
type: MarkupNodeType.list_item,
content: [
{
type: MarkupNodeType.paragraph,
content: [
{
type: MarkupNodeType.text,
text: 'qwewqe'
}
]
}
]
}
]
},
{
type: MarkupNodeType.paragraph
},
{
type: MarkupNodeType.taskList,
content: [
{
type: MarkupNodeType.taskItem,
attrs: {
checked: 'false'
},
content: [
{
type: MarkupNodeType.paragraph,
content: [
{
type: MarkupNodeType.text,
text: 'qweqwewq'
}
]
}
]
}
]
}
]
})
const markdown = await markupToMarkdown(data, 'https://front.hc.engineering/open/?workspace=workspace')
const markup = markdownToMarkup(markdown, 'https://front.hc.engineering/open/?workspace=workspace')
console.log(markdown, markup)
})
it('Check parsing header', () => {
const t1 = '# This is header'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual(t1)
})
it('Check parsing bullets', () => {
const t1 = '* Section A\n Some text\n* Section B\n Some more text'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual(t1)
})
it('Check parsing bullets-2', () => {
const t1 = '* Section A\n* Some section2'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual('* Section A\n* Some section2')
})
it('Check ordered list', () => {
const t1 = '1. Section A\n Some text\n2. Section B\n Some more text'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].type).toEqual(MarkupNodeType.ordered_list)
expect(msg.content?.[0].content?.[0].type).toEqual(MarkupNodeType.list_item)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual(t1)
})
it('Check styles', () => {
const t1 = '**BOLD _ITALIC_ BOLD**'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual('**BOLD *ITALIC* BOLD**')
})
it('Check styles-2', () => {
const t1 = '**BOLD *ITALIC* BOLD**'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual(t1)
})
it('Check styles-3', () => {
const t1 = 'Some *EM **MORE EM***'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].type).toEqual(MarkupNodeType.paragraph)
expect(msg.content?.[0].content?.length).toEqual(3)
expect(msg.content?.[0].content?.[2]?.marks?.length).toEqual(2)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual(t1)
})
it('Check hardbreaks', () => {
const t1 = 'foo\\\nbaz'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].content?.[0].type).toEqual(MarkupNodeType.text)
expect(msg.content?.[0].content?.[1].type).toEqual(MarkupNodeType.hard_break)
expect(msg.content?.[0].content?.[2].type).toEqual(MarkupNodeType.text)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual('foo\\\nbaz')
})
it('Check hardbreaks with spaces', () => {
const t1 = 'foo \nbaz'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].content?.[0].type).toEqual(MarkupNodeType.text)
expect(msg.content?.[0].content?.[1].type).toEqual(MarkupNodeType.hard_break)
expect(msg.content?.[0].content?.[2].type).toEqual(MarkupNodeType.text)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual('foo\\\nbaz')
})
it('Check softbreaks', () => {
const t1 = 'foo\nbaz'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].content?.[0].type).toEqual(MarkupNodeType.text)
expect(msg.content?.[0].content?.[0].text).toEqual('foo\nbaz')
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual('foo\nbaz')
})
it('Check softbreaks with spaces', () => {
const t1 = 'foo \n baz'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].content?.[0].type).toEqual(MarkupNodeType.text)
expect(msg.content?.[0].content?.[0].text).toEqual('foo\nbaz')
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual('foo\nbaz')
})
it('Check images', () => {
const t1 = 'Some text\nsome text ![This is Alt](http://url/a.png "This is title") Some text'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].content?.[1].type).toEqual(MarkupNodeType.image)
expect(msg.content?.[0].content?.[1].attrs?.src).toEqual('http://url/a.png')
expect(msg.content?.[0].content?.[1].attrs?.title).toEqual('This is title')
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual(t1)
})
it('Check block quote', () => {
const t1 = '> Some quoted text\nand some more text'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].type).toEqual(MarkupNodeType.blockquote)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual('> Some quoted text\n> and some more text')
})
it('Check block quote-2', () => {
const t1 = '> Some quoted text\n> and some more text'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].type).toEqual(MarkupNodeType.blockquote)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual(t1)
})
it('Check block quote-3', () => {
const t1 = '> Some quoted text\n\nand some more text'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].type).toEqual(MarkupNodeType.blockquote)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual(t1)
})
it('Check code block', () => {
const t1 = "```\n# code block\nprint '3 backticks or'\nprint 'indent 4 spaces'\n```"
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].type).toEqual(MarkupNodeType.code_block)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual(t1)
})
it('Check inline block', () => {
const t1 = 'Hello `Some code` block'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].type).toEqual(MarkupNodeType.paragraph)
expect(msg.content?.[0].content?.length).toEqual(3)
expect(msg.content?.[0].content?.[1].marks?.[0].type).toEqual(MarkupMarkType.code)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual(t1)
})
it('Check underline heading rule', () => {
const t1 = 'Hello\n---\nSome text'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual('## Hello\n\nSome text')
})
it('Check horizontal line', () => {
const t1 = 'Hello\n\n---\n\nSome text'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.length).toEqual(3)
expect(msg.content?.[1].type).toEqual(MarkupNodeType.horizontal_rule)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual(t1)
})
it('Check big inline block', () => {
const t1 = 'Hello ```Some code``` block'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].type).toEqual(MarkupNodeType.paragraph)
expect(msg.content?.[0].content?.length).toEqual(3)
expect(msg.content?.[0].content?.[1].marks?.[0].type).toEqual(MarkupMarkType.code)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual('Hello `Some code` block')
})
it('Check code block language', () => {
const t1 = "```typescript\n# code block\nprint '3 backticks or'\nprint 'indent 4 spaces'\n```"
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].type).toEqual(MarkupNodeType.code_block)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual(t1)
})
it('Check link', () => {
const t1 = 'Some text [Link Alt](http://a.com) some more text'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual(t1)
})
it('Check link bold', () => {
const t1 = '**[link](foo) is bold**"'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].content?.[1].marks?.[0]?.type).toEqual(MarkupMarkType.bold)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual(t1)
})
it('Check overlapping inline marks', () => {
const t1 = 'This is **strong *emphasized text with `code` in* it**'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].content?.length).toEqual(6)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual(t1)
})
it('Check emph url', () => {
const t1 = 'Link to *<https://hardware.it>*'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg.type).toEqual(MarkupNodeType.doc)
expect(msg.content?.[0].content?.length).toEqual(2)
const md = serializeMessage(msg, 'ref://', 'http://')
expect(md).toEqual('Link to *<https://hardware.it>*')
})
it('check header hard_break serialize', () => {
const doc: MarkupNode = {
type: MarkupNodeType.doc,
content: [
{
type: MarkupNodeType.paragraph,
content: [
{
type: MarkupNodeType.text,
text: '# Hello'
},
{
type: MarkupNodeType.hard_break
},
{
type: MarkupNodeType.text,
text: 'World'
}
]
}
]
}
const md = serializeMessage(doc, 'ref://', 'http://')
expect(md).toEqual('# Hello\\\nWorld')
})
it('Check inline html - 1', () => {
const t1 = '<div><a href="bar">*foo*</a></div>'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg).toEqual({
type: MarkupNodeType.doc,
content: [
{
type: MarkupNodeType.paragraph,
content: [
{
type: MarkupNodeType.text,
text: '*foo*',
marks: [
{
type: MarkupMarkType.link,
attrs: {
href: 'bar',
class: 'cursor-pointer',
rel: 'noopener noreferrer',
target: '_blank'
}
}
]
}
]
}
]
})
})
it('Check inline html - 2', () => {
const t1 = '<h1>hello</h1>\n<h2>world</h2>'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg).toEqual({
type: MarkupNodeType.doc,
content: [
{
type: MarkupNodeType.heading,
attrs: { level: 1 },
content: [
{
type: MarkupNodeType.text,
text: 'hello'
}
]
},
{
type: MarkupNodeType.heading,
attrs: { level: 2 },
content: [
{
type: MarkupNodeType.text,
text: 'world'
}
]
}
]
})
})
it('Check inline html - 3', () => {
const t1 = '* line 1\n* line 2\n <img width="320" src="http://example.com/image" alt="image">'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
expect(msg).toEqual({
type: MarkupNodeType.doc,
content: [
{
type: MarkupNodeType.bullet_list,
content: [
{
type: MarkupNodeType.list_item,
content: [
{
type: MarkupNodeType.paragraph,
content: [
{
type: MarkupNodeType.text,
text: 'line 1',
marks: []
}
]
}
]
},
{
type: MarkupNodeType.list_item,
content: [
{
type: MarkupNodeType.paragraph,
content: [
{
type: MarkupNodeType.text,
text: 'line 2\n',
marks: []
},
{
type: MarkupNodeType.image,
attrs: {
src: 'http://example.com/image',
alt: 'image',
width: 320,
align: null,
height: null,
title: null,
'file-id': null
}
}
]
}
]
}
]
}
]
})
})
it('Check traverse', () => {
const t1 = '**[link](foo) is bold**"'
const msg = parseMessageMarkdown(t1, 'ref://', 'http://', 'http://')
const nodes = []
traverseMarkupNode(msg, (node) => {
nodes.push(node)
})
expect(nodes.length).toEqual(5)
const marks = []
traverseAllMarks(msg, (node, mark) => {
marks.push(mark)
})
expect(marks.length).toEqual(3)
})
it('check serialize variant', () => {
const node: MarkupNode = {
content: [
{
content: [
{
content: [
{
content: [
{ text: 'test1 ', type: MarkupNodeType.text },
{ text: undefined, type: MarkupNodeType.hard_break },
{
text: 'Italic',
marks: [{ type: MarkupMarkType.em, attrs: [] }],
type: MarkupNodeType.text
}
],
type: MarkupNodeType.paragraph
}
],
type: MarkupNodeType.list_item
},
{
content: [
{
content: [
{ text: 'test2 ', type: MarkupNodeType.text },
{ text: 'BOLD', marks: [{ type: MarkupMarkType.bold, attrs: [] }], type: MarkupNodeType.text }
],
type: MarkupNodeType.paragraph
}
],
type: MarkupNodeType.list_item
}
],
type: MarkupNodeType.bullet_list
}
],
type: MarkupNodeType.doc
}
const msg = serializeMessage(node, 'ref://', 'http://')
expect(msg).toEqual('* test1 \\\n *Italic*\n* test2 **BOLD**')
})
it('check serialize throw unsupported', () => {
const node: MarkupNode = {
content: [
{
content: [
{
content: [
{
content: [
{ text: 'test1 ', type: MarkupNodeType.text },
{ text: undefined, type: MarkupNodeType.text },
{ text: undefined, type: (MarkupNodeType.text + 'qwe') as MarkupNodeType },
{ text: undefined, type: MarkupNodeType.text }
],
type: MarkupNodeType.paragraph
}
],
type: MarkupNodeType.list_item
}
],
type: MarkupNodeType.bullet_list
}
],
type: MarkupNodeType.doc
}
expect(() => serializeMessage(node, 'ref://', 'http://')).toThrowError(
'Token type `textqwe` not supported by Markdown renderer'
)
})
it('check markdown state', () => {
const st = new MarkdownState()
st.text('qwe', true)
expect(st.out).toEqual('qwe')
})
it('check markdown state', () => {
const st = new MarkdownState()
const o1 = st.quote("qwe'")
const o2 = st.quote('qwe"')
expect(o1).toEqual('"qwe\'"')
expect(o2).toEqual("'qwe\"'")
})
it('check horizontal rule', () => {
const node: MarkupNode = {
content: [
{
attrs: {},
type: MarkupNodeType.horizontal_rule
}
],
type: MarkupNodeType.doc
}
expect(serializeMessage(node, 'ref://', 'http://')).toEqual('---')
})
it('check code_text', () => {
const node: MarkupNode = {
type: MarkupNodeType.doc,
content: [
{
type: MarkupNodeType.paragraph,
content: [
{
type: MarkupNodeType.text,
text: 'Link to '
},
{
type: MarkupNodeType.text,
text: 'https://hardware.it',
marks: [
{
type: MarkupMarkType.em,
attrs: {}
},
{
type: MarkupMarkType.link,
attrs: {
title: 'Some title',
href: 'https://hardware.it'
}
}
]
}
]
}
]
}
expect(serializeMessage(node, 'ref://', 'http://')).toEqual(
'Link to *[https://hardware.it](https://hardware.it "Some title")*'
)
})
it('check swithc marks', () => {
const node: MarkupNode = {
type: MarkupNodeType.doc,
content: [
{
type: MarkupNodeType.paragraph,
content: [
{
type: MarkupNodeType.text,
text: 'Link to ',
marks: [
{
type: MarkupMarkType.bold,
attrs: {}
}
]
},
{
type: MarkupNodeType.text,
text: 'https://hardware.it',
marks: [
{
type: MarkupMarkType.em,
attrs: {}
},
{
type: MarkupMarkType.bold,
attrs: {}
}
]
}
]
}
]
}
expect(serializeMessage(node, 'ref://', 'http://')).toEqual('**Link to *https://hardware.it***')
})
})
@@ -0,0 +1,77 @@
import { ServerKit } from '@hcengineering/text'
import { AnyExtension, Extension, Node, mergeAttributes } from '@tiptap/core'
export interface SubLinkOptions {
HTMLAttributes: Record<string, any>
hasHulyText: (text: string) => boolean
hasHulyLink: (href: string) => boolean
}
export const SubLink = Node.create<SubLinkOptions>({
name: 'subLink',
addOptions () {
return {
HTMLAttributes: {},
hasHulyText: (text: string) => false,
hasHulyLink: (href: string) => false
}
},
group: 'block',
content: 'inline*',
parseHTML () {
// this plugin contains special parse rule that matches DOM element only when:
// - it has a special inner text
// - or it has a special link
// When no match, the element won't be parsed as sub node but will be processed by other extensions
return [
{
tag: 'sub',
getAttrs: (el: HTMLElement | string) => {
if (typeof el !== 'string') {
if (this.options.hasHulyText(el.textContent ?? '')) {
return null
}
const link = el.querySelector('a[href]')
const href = link?.getAttribute('href') ?? ''
if (link != null && this.options.hasHulyLink(href)) {
return null
}
}
// no match
return false
}
}
]
},
renderHTML ({ HTMLAttributes }) {
return ['sub', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
}
})
export interface GithubKitOptions {
sub?: Partial<SubLinkOptions> | false
}
export const GithubKit = Extension.create<GithubKitOptions>({
name: 'githubKit',
addExtensions () {
return [
ServerKit.configure({
image: {
getBlobRef: async () => ({ src: '', srcset: '' })
}
}),
...(this.options.sub !== false ? [SubLink.configure({ ...this.options.sub })] : [])
]
}
})
export const defaultExtensions: AnyExtension[] = [GithubKit.configure({})]
@@ -0,0 +1,83 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// 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 {
MarkupNode,
jsonToMarkup,
markupToJSON,
MarkdownParser,
storeNodes,
storeMarks,
MarkdownState
} from '@hcengineering/text'
import { GithubKit } from './extensions'
import { hasHulyLink, hasHulyLinkText } from '../sync/guest'
/**
* @public
*/
export function parseMessageMarkdown (
message: string | undefined,
refUrl: string,
imageUrl: string,
guestUrl: string
): MarkupNode {
const extensions = [
GithubKit.configure({
sub: {
hasHulyText: hasHulyLinkText,
hasHulyLink: (href) => hasHulyLink(href, guestUrl)
}
})
]
const parser = new MarkdownParser(extensions, refUrl, imageUrl)
return parser.parse(message ?? '')
}
/**
* @public
*/
export function serializeMessage (node: MarkupNode, refUrl: string, imageUrl: string): string {
const state = new MarkdownState(storeNodes, storeMarks, { tightLists: true, refUrl, imageUrl })
state.renderContent(node)
return state.out
}
/**
* @public
*/
export async function markupToMarkdown (
markup: string,
refUrl: string = 'ref://',
imageUrl: string = 'http://localhost',
preprocessor?: (nodes: MarkupNode) => Promise<void>
): Promise<string> {
const json = markupToJSON(markup)
await preprocessor?.(json)
return serializeMessage(json, refUrl, imageUrl)
}
/**
* @public
*/
export function markdownToMarkup (
message: string,
refUrl: string = 'ref://',
imageUrl: string = 'http://localhost',
guestUrl: string = 'http://localhost/guest'
): string {
const json = parseMessageMarkdown(message, refUrl, imageUrl, guestUrl)
return jsonToMarkup(json)
}