checkpoint: working markdown<=>html converter
This commit is contained in:
1 parent
c540073b66
commit
f21bdbdb0c
20 files changed
+1274
-10643
No files matched your search
+3
-2
@@ -96,7 +96,7 @@ API_URI=/_/v1/
|
||||
if db:
|
||||
self.db = db
|
||||
elif self.config.IN_MEMORY_DB:
|
||||
self.db = GrungDB.with_schema(schema, path=None, storage=MemoryStorage)
|
||||
self.db = GrungDB.with_schema(schema, path=self.path.database, storage=MemoryStorage)
|
||||
else:
|
||||
self.db = GrungDB.with_schema(
|
||||
schema, path=self.path.database, sort_keys=True, indent=4, separators=(",", ": ")
|
||||
@@ -165,6 +165,7 @@ API_URI=/_/v1/
|
||||
else:
|
||||
search_uri = uri
|
||||
|
||||
self.log.debug(f"Looking for page with uri {search_uri}")
|
||||
page = table.get(where("uri") == search_uri, recurse=False)
|
||||
if not page:
|
||||
# load the parent to check for write permissions
|
||||
@@ -182,7 +183,7 @@ API_URI=/_/v1/
|
||||
raise UnauthorizedError(f"User {user.doc_id} does not have permission to create under {parent_uri}.")
|
||||
|
||||
obj = getattr(schema, table.name)
|
||||
page = obj(name=page_name, body=obj.default.format(name=page_name), parent=parent)
|
||||
page = obj(name=page_name, body=obj.default.format(name=page_name), parent=parent, uri=search_uri)
|
||||
|
||||
# validate the page name before we try to create anything
|
||||
page._metadata.fields["name"].validate(page, db=self.db)
|
||||
|
||||
+221
-20
@@ -1,41 +1,240 @@
|
||||
from ttfrog import app, schema
|
||||
|
||||
TEMPLATE = """
|
||||
# Heading 1
|
||||
# Wiki Syntax
|
||||
{{toc box depth="3"}}
|
||||
|
||||
## Heading 2
|
||||
## Basic Formatting
|
||||
|
||||
### Heading 3
|
||||
|
||||
#### Heading 4
|
||||
The wiki uses [GitHub-flavored Markdown](https://github.github.com/gfm/).
|
||||
|
||||
##### Heading 5
|
||||
{{style equal-widths striped
|
||||
|
||||
###### Heading 6
|
||||
| Type | To Get |
|
||||
|------|--------|
|
||||
| `*emphasis*` | *emphasis* |
|
||||
| `**bold**` | **bold** |
|
||||
| `[link label](/link/address)` | [link label](/link/address) |
|
||||
| `` `inline` `` | `inline` |
|
||||
| `***` | <hr> |
|
||||
| `# Heading 1` | {{{ <h1>Heading 1</h1> }}} |
|
||||
| `## Heading 2` | {{{ <h2>Heading 2</h2> }}} |
|
||||
| `### Heading 3` | {{{ <h3>Heading 3</h3> }}} |
|
||||
| `#### Heading 4` | {{{ <h4>Heading 4</h4> }}} |
|
||||
| `##### Heading 5` | {{{ <h5>Heading 5</h5> }}} |
|
||||
| `###### Heading 6` | {{{ <h6>Heading 6</h6> }}} |
|
||||
| {{{
|
||||
|
||||
***
|
||||
<pre>
|
||||
* unordered
|
||||
* list
|
||||
* items
|
||||
</pre>
|
||||
|
||||
Normal text.
|
||||
**Bold text.**
|
||||
*Italic Text.*
|
||||
[A link](/).
|
||||
}}} | {{{
|
||||
|
||||
1. a
|
||||
2. numbered
|
||||
3. list.
|
||||
<ul><li>unordered</li><li>list</li><li>items</li></ul>
|
||||
|
||||
> a block quote
|
||||
}}} |
|
||||
| {{{
|
||||
|
||||
| A | Table | Section |
|
||||
| --- | ----- | ------- |
|
||||
| foo | bar | baz |
|
||||
<pre>
|
||||
1. ordered
|
||||
1. list
|
||||
1. items
|
||||
</pre>
|
||||
|
||||
}}} | {{{ <ol><li>ordered</li><li>list</li><li>items</li></ol> }}} |
|
||||
| {{{
|
||||
|
||||
<pre>
|
||||
> blockquote
|
||||
</pre>
|
||||
|
||||
}}} | <blockquote>blockquote</blockquote> |
|
||||
| {{{
|
||||
<pre>
|
||||
```
|
||||
preformatted
|
||||
code
|
||||
block
|
||||
```
|
||||
</pre>
|
||||
}}} | {{{ <pre>preformatted code<br>block</pre> }}} |
|
||||
| {{{
|
||||
|
||||
<pre>
|
||||
\\{\\{\\{
|
||||
|
||||
multi-line content,
|
||||
whitespace preserved,
|
||||
\\*\\*markup\\*\\* and \\<i\\>HTML\\</i\\> included.
|
||||
|
||||
\\}\\}\\}
|
||||
</pre>
|
||||
|
||||
}}} | {{{ <p>multi-line content,<br>whitespace preserved,<br><b>markup</b> and <i>HTML</i> included.</p> }}} |
|
||||
|
||||
}}
|
||||
|
||||
## Macros
|
||||
|
||||
Macros extend authoring options with computed values evaluated at display time.
|
||||
There are two types: *inline*, which can be used in the middle of a line of
|
||||
markdown, and *block*, which enclose a series of lines.
|
||||
|
||||
### Inline Macros
|
||||
|
||||
The basic format of an inline macro is:
|
||||
|
||||
`{{NAME [KEYWORD ..] [PARAM="VALUE" ..]}}`
|
||||
|
||||
* `NAME` is the name of the macro. It is always lower case.
|
||||
* `KEYWORD` is a bare word
|
||||
* `PARAM` is a parameter name
|
||||
* `VALUE` is the value of the associated parameter
|
||||
|
||||
No macro requires keywords or parameters, but some support them; they are documented below.
|
||||
|
||||
### Block Macros
|
||||
|
||||
The format of a block macro uses the same format for name, keywords and parameters. The closing
|
||||
braces must appear on their own line, and both the opening and closing lines must be
|
||||
surrounded by blank lines. This ensures the block macro is not interpreted as markdown.
|
||||
|
||||
```
|
||||
Before the macro..
|
||||
|
||||
{{NAME [KEYWORD ..] [PARAM="VALUE" ..]
|
||||
|
||||
..inside the macro..
|
||||
|
||||
}}
|
||||
|
||||
..after the macro.
|
||||
```
|
||||
|
||||
### Supported Macros
|
||||
|
||||
This section documents all supported macros.
|
||||
|
||||
WIP (move to its own page)
|
||||
|
||||
{{style striped equal-widths
|
||||
|
||||
| Type | To Get | Description |
|
||||
|------|--------|-------------|
|
||||
|{{{ <pre>{{user}}</pre> }}} | {{user}} | A hyperlink to the current user's wiki page. |
|
||||
|{{{ <pre>{{style [STYLE ..]<br>pretty markup goes here!<br><br>}}</pre> }}}| pretty markup goes here! | Styled markup. See Block Styling and Positioning, below. |
|
||||
|{{{ <pre>{{toc [STYLE ..] depth="N"}}</pre>}}} | {{toc depth="1"}} | A table of contents. The default depth is 3, and styles may be assigned as with the `style` macro. |
|
||||
|{{{ <pre>{{widget NAME}}</pre> }}} | *varies* | Insert specified widget; see [Wiki Syntax: Widgets](/Widgets). |
|
||||
|
||||
|
||||
### Block Styling and Positioning
|
||||
|
||||
Several styles are supported by the editor, which may be freely combined. These include:
|
||||
|
||||
* **box**: block element with background and border
|
||||
* **full-width**: Expand block to fill all horizontal space
|
||||
* **right**: right-aligned floating block (maximum 30% of full width)
|
||||
* **left**: left-aligned floating block (maximum 30% of full width)
|
||||
* **center**: centered block
|
||||
* **inline**: contained within the current text line
|
||||
|
||||
#### Examples
|
||||
|
||||
{{style striped equal-widths
|
||||
|
||||
| Type | To Get |
|
||||
|------|--------|
|
||||
| {{{ <pre>{{style box<br><br>😶 box, full-width<br><br>}}<br><br>normal text</pre> }}} | {{{ <div class='box'>😶 box, full-width</div>normal text }}} |
|
||||
| {{{ <pre>{{style right<br><br>😶 right-aligned, inline<br><br>}}<br><br>normal text</pre> }}} | {{{ <div class='right'>😶 right-aligned, inline</div>normal text }}} |
|
||||
| {{{ <pre>{{style box right<br><br>😶 box right-aligned, inline<br><br>}}<br><br>normal text</pre> }}} | {{{ <div class='box right'>😶 box right-aligned, inline</div>normal text }}} |
|
||||
| {{{ <pre>{{style box center<br><br>😶 box center-aligned, block<br><br>}}<br><br>normal text</pre> }}} | {{{ <div class='box center'>😶 box center-aligned, block</div>normal text }}} |
|
||||
| {{{ <pre>{{style box left<br><br>😶 box left-aligned, inline<br><br>}}<br><br>normal text</pre> }}} | {{{ <div class='box left'>😶 box left-aligned, inline</div>normal text }}} |
|
||||
|
||||
}}
|
||||
|
||||
### Tables
|
||||
|
||||
Basic tables follow the Github-Flavoured Markdown. By default, table cells may not contain newlines, but you can add multi-line content to your tables using {{{ {{{ and }}} }}}:
|
||||
|
||||
{{style equal-widths striped
|
||||
|
||||
| Type | To Get |
|
||||
|------|--------|
|
||||
| {{{
|
||||
|
||||
<pre>
|
||||
\\| head 1 \\| head 2 \\|
|
||||
\\| ------ \\| ------ \\|
|
||||
\\| cell 1 \\| cell 2 \\|
|
||||
</pre>
|
||||
|
||||
}}} | {{{ <table><tr><th>head 1</th><th>head 2</th></tr><tr><td>cell 1</td><td>cell 2</td></tr></table> }}} |
|
||||
| {{{
|
||||
|
||||
<pre>
|
||||
\\| head 1 \\| head 2 \\|
|
||||
\\| ------ \\| ------ \\|
|
||||
\\| \\{\\{\\{
|
||||
|
||||
multi-line
|
||||
content
|
||||
|
||||
}}} \\| cell 3 \\|
|
||||
</pre>
|
||||
|
||||
}}} | {{{ <table><tr><th>head 1</th><th>head 2</th></tr><tr><td><p>multi-line<br>content</p></td><td>cell 3</td></tr></table> }}} |
|
||||
|
||||
}}
|
||||
|
||||
|
||||
#### Table Styling
|
||||
|
||||
{{style box right outer
|
||||
|
||||
**Striped, Equal-Widths Example:**
|
||||
|
||||
{{style striped equal-widths inner1
|
||||
|
||||
| A | B|
|
||||
|---|--|
|
||||
| This table has | and |
|
||||
| equal | alternating |
|
||||
| widths! | backgrounds! |
|
||||
|
||||
}}
|
||||
|
||||
|
||||
**Layout Example:**
|
||||
|
||||
{{style layout inner2
|
||||
|
||||
| A | B|
|
||||
|---|--|
|
||||
| row 1, col 1 | row 1, col 2|
|
||||
| row 2, col 1 | row 2, col 2|
|
||||
|
||||
}}
|
||||
|
||||
}}
|
||||
|
||||
|
||||
By wrapping a table in a `style` macro, you can use the following block-level styles on the tables themselves:
|
||||
|
||||
* `striped`: Apply zebra-striping to table rows
|
||||
* `equal-widths`: Enforce equal widths of all table columns
|
||||
* `layout`: Hide the table headers, borders, and background colors. This is useful in cases
|
||||
where you would like to control layout of elements using tables.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def bootstrap():
|
||||
"""
|
||||
Bootstrap the database entries by populating the first Page, the Admin user and the Admins group.
|
||||
Bootstrap the database entries by poplating the first Page, the Admin user and the Admins group.
|
||||
"""
|
||||
app.check_state()
|
||||
|
||||
@@ -45,10 +244,12 @@ def bootstrap():
|
||||
# create the top-level pages
|
||||
root = app.db.save(schema.Page(name=app.config.VIEW_URI, body="This is the home page", uri=""))
|
||||
|
||||
root.add_member(schema.Page(name="Wiki", body=TEMPLATE))
|
||||
|
||||
users = root.add_member(schema.Page(name="User", body="# Users\nusers go here."))
|
||||
groups = root.add_member(schema.Page(name="Group", body="# Groups\ngroups go here."))
|
||||
npcs = root.add_member(schema.Page(name="NPC", body="# NPCS!"))
|
||||
wiki = root.add_member(schema.Page(name="Wiki", body=TEMPLATE))
|
||||
widgets = root.add_member(schema.Page(name="Widget", body="Widgets go here."))
|
||||
|
||||
# create the NPCs
|
||||
npcs.add_member(schema.NPC(name="Sabetha", body=""))
|
||||
|
||||
+54
-1
@@ -20,7 +20,7 @@ from grung.objects import (
|
||||
TextFilePointer,
|
||||
Timestamp,
|
||||
)
|
||||
from grung.validators import PatternValidator, LengthValidator
|
||||
from grung.validators import LengthValidator, PatternValidator
|
||||
from tinydb import where
|
||||
|
||||
from ttfrog.exceptions import MalformedRequestError
|
||||
@@ -303,3 +303,56 @@ class NPC(Page):
|
||||
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class Widget(Page):
|
||||
"""
|
||||
Wiki UX widgets
|
||||
"""
|
||||
|
||||
default = dedent(
|
||||
"""
|
||||
# {name}
|
||||
|
||||
Insert the current user's name.
|
||||
|
||||
## Example
|
||||
|
||||
***
|
||||
|
||||
Hello, <macro name='user' />
|
||||
|
||||
***
|
||||
|
||||
## Template
|
||||
|
||||
```html
|
||||
|
||||
```
|
||||
|
||||
## CSS
|
||||
```css
|
||||
.widget-user {{
|
||||
display: inline- block;
|
||||
border: 1px solid green;
|
||||
border-radius: 5px;
|
||||
padding: 2px;
|
||||
}}
|
||||
```
|
||||
|
||||
## Processor
|
||||
```javascript
|
||||
function(tag, template, css) {{
|
||||
// Return the HTML that should be inserted into the template div.
|
||||
return document.querySelector("nav li.user a:first-child").outerHTML;
|
||||
}};
|
||||
```
|
||||
"""
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
inherited = [field for field in super().fields() if field.name not in ("members", "uid")]
|
||||
return inherited + [
|
||||
String("name", primary_key=True),
|
||||
]
|
||||
@@ -1,20 +1,26 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='editor/editor.css' ) }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id='{% if user.can_write(page) %}editor{% else %}viewer{% endif %}' class='read-only'>
|
||||
{{ page.body }}
|
||||
</div>
|
||||
<div id='content'>{{ page.body }}</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='editor/commonmark.js' ) }}"></script>
|
||||
<script src="{{ url_for('static', filename='editor/turndown.js' ) }}"></script>
|
||||
<script src="{{ url_for('static', filename='editor/turndown-plugin-gfm.js' ) }}"></script>
|
||||
<script src="{{ url_for('static', filename='editor/editor.js' ) }}"></script>
|
||||
<!-- for converting markdown to html -->
|
||||
<script src="{{ url_for('static', filename='editor/purify.min.js' ) }}"></script>
|
||||
<script src="{{ url_for('static', filename='editor/marked.umd.min.js' ) }}"></script>
|
||||
<script src="{{ url_for('static', filename='editor/grung.js' ) }}"></script>
|
||||
{% if user.can_write(page) %}
|
||||
<script src="{{ url_for('static', filename='editor/turndown.js' ) }}"></script>
|
||||
<script src="{{ url_for('static', filename='editor/joplin-turndown-plugin-gfm.js' ) }}"></script>
|
||||
<script src="{{ url_for('static', filename='editor/grung-editor.js' ) }}"></script>
|
||||
{% endif %}
|
||||
<script>
|
||||
const wiki = new Grung{% if user.can_write(page) %}Editor{% endif %}({plugins: [MacroPlugin]});
|
||||
wiki.view();
|
||||
</script>
|
||||
{% endblock %}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,343 +0,0 @@
|
||||
class Editor {
|
||||
|
||||
#states = {
|
||||
VIEW: 'view',
|
||||
EDIT: 'edit',
|
||||
WYSIWYG: 'wysiwyg'
|
||||
}
|
||||
|
||||
commonmark = null;
|
||||
turndown = null;
|
||||
#cachedHTML = null;
|
||||
#cachedMarkdown = null;
|
||||
#state = null;
|
||||
#changed = false;
|
||||
|
||||
constructor(settings) {
|
||||
/*
|
||||
* Create a new Editor instance.
|
||||
*/
|
||||
this.element = document.getElementById(settings.editorId || 'editor');
|
||||
this.source = this.element.innerHTML;
|
||||
this.commonmark = {
|
||||
reader: new commonmark.Parser(),
|
||||
writer: new commonmark.HtmlRenderer(),
|
||||
};
|
||||
this.turndown = new TurndownService({
|
||||
headingStyle: 'atx',
|
||||
codeBlockStyle: 'fenced',
|
||||
});
|
||||
|
||||
this.plugins = {};
|
||||
settings.plugins.forEach(plugin => {
|
||||
this.plugins[plugin.name] = new plugin({name: plugin.name, editor: this});
|
||||
});
|
||||
this.#bindEvents();
|
||||
this.getHTML();
|
||||
this.element.classList.add("loaded");
|
||||
}
|
||||
|
||||
#bindEvents() {
|
||||
this.element.addEventListener('keydown', (evt) => {
|
||||
if (this.#state === this.#states.VIEW) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
if (this.#state === this.#states.EDIT) {
|
||||
evt.preventDefault();
|
||||
this.insertAtCursor(document.createTextNode("\n"));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.#cachedMarkdown != this.element.innerHTML) {
|
||||
this.#changed = true;
|
||||
this.#cachedMarkdown = this.element.innerHTML;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
getState() {
|
||||
return this.#state;
|
||||
}
|
||||
|
||||
setState(newState) {
|
||||
this.#state = newState;
|
||||
Object.values(this.#states).forEach(state => {
|
||||
if (state == newState) {
|
||||
this.element.classList.add(state);
|
||||
} else {
|
||||
this.element.classList.remove(state);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getHTML() {
|
||||
/*
|
||||
* Convert the markdown source to HTML.
|
||||
*/
|
||||
if (this.#changed || !this.#cachedHTML) {
|
||||
var md = this.getMarkdown();
|
||||
var parsed = this.commonmark.reader.parse(md);
|
||||
var html = this.commonmark.writer.render(parsed);
|
||||
Object.values(this.plugins).forEach(plugin => {
|
||||
html = plugin.toHTML(html);
|
||||
});
|
||||
this.#cachedHTML = html;
|
||||
|
||||
}
|
||||
return this.#cachedHTML;
|
||||
}
|
||||
|
||||
getMarkdown() {
|
||||
/*
|
||||
* Return the current markdown.
|
||||
*/
|
||||
if (this.getState() === this.#states.EDIT) {
|
||||
this.#cachedMarkdown = this.element.innerHTML;
|
||||
} else if (this.getState() === this.#states.WYSIWYG) {
|
||||
var md = this.element.innerHTML;
|
||||
Object.values(this.plugins).forEach(plugin => {
|
||||
md = plugin.toMarkdown(md);
|
||||
});
|
||||
this.#cachedMarkdown = this.turndown.turndown(md);
|
||||
} else if (!this.#cachedMarkdown) {
|
||||
this.#cachedMarkdown = this.source;
|
||||
}
|
||||
this.#cachedMarkdown = this.#cachedMarkdown.replaceAll(/^>/mg, '>');
|
||||
return this.#cachedMarkdown;
|
||||
}
|
||||
|
||||
reset() {
|
||||
/*
|
||||
* Discard any unsaved edits and reset the editor to its initial state.
|
||||
*/
|
||||
this.#cachedHTML = null;
|
||||
this.#cachedMarkdown = null;
|
||||
this.view();
|
||||
}
|
||||
|
||||
view() {
|
||||
/*
|
||||
* Convert the editor read-only mode and display the current HTML.
|
||||
*/
|
||||
if (this.getState() === this.#states.VIEW) {
|
||||
return;
|
||||
}
|
||||
this.element.innerHTML = this.getHTML();
|
||||
this.setState(this.#states.VIEW);
|
||||
}
|
||||
|
||||
wysiwyg() {
|
||||
/*
|
||||
* Put the editor in WYSIWYG editing mode.
|
||||
*/
|
||||
if (this.getState() === this.#states.WYSIWYG) {
|
||||
return;
|
||||
}
|
||||
this.#changed = false;
|
||||
this.element.contentEditable = true;
|
||||
this.element.innerHTML = this.getHTML();
|
||||
this.setState(this.#states.WYSIWYG);
|
||||
}
|
||||
|
||||
edit() {
|
||||
/*
|
||||
* Put the editor into source editing mode.
|
||||
*/
|
||||
if (this.#state === this.#states.EDIT) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#changed = false;
|
||||
this.element.contentEditable = true;
|
||||
this.element.innerHTML = this.getMarkdown();
|
||||
this.setState(this.#states.EDIT);
|
||||
}
|
||||
|
||||
insertAtCursor(node) {
|
||||
var sel, range, html;
|
||||
sel = window.getSelection();
|
||||
range = sel.getRangeAt(0);
|
||||
range.deleteContents();
|
||||
range.insertNode(node);
|
||||
range.setStartAfter(node);
|
||||
this.element.focus();
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class EditorPlugin {
|
||||
constructor(settings) {
|
||||
this.name = settings.name;
|
||||
this.editor = settings.editor;
|
||||
};
|
||||
|
||||
cleanAttribute(attribute) {
|
||||
return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : ''
|
||||
}
|
||||
|
||||
toMarkdown(html) {
|
||||
return html;
|
||||
};
|
||||
|
||||
toHTML(md) {
|
||||
return md;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
class TablePlugin extends EditorPlugin {
|
||||
constructor(settings) {
|
||||
super(settings);
|
||||
this.editor.turndown.use(turndownPluginGfm.tables);
|
||||
this.pattern = /(?<contents>(?:\|[^|]+?)+\|[\s\n]*)(?:<|$)/gims;
|
||||
this.dividerPattern = /\|\s*\-+\s*\|/;
|
||||
}
|
||||
|
||||
toHTML(md) {
|
||||
md.matchAll(this.pattern).forEach(matched => {
|
||||
var html = '<table><thead>';
|
||||
var cellTag = 'th';
|
||||
matched.groups.contents.split("\n").forEach(line => {
|
||||
if (this.dividerPattern.test(line)) {
|
||||
html += "</thead><tbody>";
|
||||
cellTag = 'td';
|
||||
} else {
|
||||
html += "<tr>";
|
||||
var cells = line.split("|");
|
||||
html += "<" + cellTag + ">" + cells.slice(1, cells.length - 1).join("</" + cellTag + "><" + cellTag + ">") + "</" + cellTag + ">";
|
||||
html += "</tr>";
|
||||
}
|
||||
});
|
||||
html += "</tbody></table>";
|
||||
md = md.replaceAll(matched[1], html);
|
||||
});
|
||||
return md;
|
||||
}
|
||||
}
|
||||
|
||||
class MacroPlugin extends EditorPlugin {
|
||||
|
||||
macros = {
|
||||
// image: {}
|
||||
// toc {}
|
||||
// widget {}
|
||||
//
|
||||
html: {
|
||||
toHTML: (settings) => {
|
||||
var html = settings.block.replaceAll(">", ">").replaceAll("<", "<");
|
||||
return html;
|
||||
},
|
||||
toMarkdown: (node) => {
|
||||
const utf8encoder = new TextEncoder();
|
||||
return "{{html\n" + b64decode(node.dataset.block) + "\n}}";
|
||||
}
|
||||
},
|
||||
user: {
|
||||
toHTML: (settings) => {
|
||||
return document.querySelector("nav > ul > li.user > a:first-child").outerHTML;
|
||||
},
|
||||
},
|
||||
npc: {
|
||||
toHTML: (settings) => {
|
||||
var name = camelCase(settings.keywords).join(" ");
|
||||
var target = name.replaceAll(" ", "");
|
||||
return `<a href="/NPC/${target}" data-npc-name="${name}">👤 ${name}</a>`;
|
||||
},
|
||||
toMarkdown: (node) => {
|
||||
return `{{npc ${node.firstChild.dataset.npcName}}}`;
|
||||
},
|
||||
},
|
||||
spell: {
|
||||
toHTML: (settings) => {
|
||||
var name = camelCase(settings.keywords).join(" ");
|
||||
var target = name.replaceAll(" ", "");
|
||||
return `<a href="/Spell/${target}" data-spell-name="${name}">✨ ${name}</a>`;
|
||||
},
|
||||
toMarkdown: (node) => {
|
||||
return `{{spell ${node.firstChild.dataset.spellName}}}`;
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
constructor(settings) {
|
||||
super(settings);
|
||||
this.pattern = /(?<contents>{{(?<name>\w+)(?<keywords>(?:\s*\w+)*)(?<parameters>(?:\s+\w+="[^"]*")*?)(?<block>\b.*?)?}})/gms;
|
||||
this.paramPattern = /\b(?<name>[^=]+)="(?<value>[^"]*?)"\b/gm;
|
||||
|
||||
var plugin = this;
|
||||
this.editor.turndown.addRule('macros', {
|
||||
filter: ['span'],
|
||||
replacement: function (content, node, options) {
|
||||
const parser = plugin.macros[node.getAttribute('data-macro-name')].toMarkdown;
|
||||
if (parser) {
|
||||
return parser(node);
|
||||
}
|
||||
|
||||
var md = '{{' + node.getAttribute('data-macro-name');
|
||||
for (var param in node.getAttributeNames()) {
|
||||
var val = node.getAttribute(param);
|
||||
if (val) {
|
||||
md += val + (param == 'data-macro-name' ? " " : `="${node.getAttribute(param)}" `);
|
||||
}
|
||||
}
|
||||
md += '}}';
|
||||
return md;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
toHTML(md) {
|
||||
const utf8encoder = new TextEncoder();
|
||||
var output = md;
|
||||
output.matchAll(this.pattern).forEach(matched => {
|
||||
var macroName = matched.groups.name;
|
||||
var html = `<span class='macro' data-macro-name='${macroName}'`;
|
||||
var settings = {
|
||||
block: matched.groups.block,
|
||||
keywords: matched.groups.keywords,
|
||||
params: {},
|
||||
}
|
||||
matched.groups.parameters.matchAll(this.paramPattern).forEach(param => {
|
||||
settings.params[param.groups.name] = param.groups.value;
|
||||
html += ` data-param-${param.groups.name}="${param.groups.value}"`;
|
||||
});
|
||||
html += ` data-block="${b64encode(matched.groups.block)}"`;
|
||||
html += '>';
|
||||
html += this.macros[macroName].toHTML(settings);
|
||||
html += '</span>';
|
||||
output = output.replaceAll(matched[1], html);
|
||||
});
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
function camelCase(words) {
|
||||
var output = [];
|
||||
words.trim().split(/\s+/g).forEach(word => {
|
||||
var lcWord = word.toLowerCase();
|
||||
output.push(lcWord.charAt(0).toUpperCase() + lcWord.slice(1));
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
function b64encode(input) {
|
||||
const utf8encoder = new TextEncoder();
|
||||
return utf8encoder.encode(input).toBase64();
|
||||
}
|
||||
|
||||
function b64decode(input) {
|
||||
const utf8encoder = new TextEncoder();
|
||||
return utf8encoder.decode(input.fromBase64());
|
||||
}
|
||||
|
||||
|
||||
editor = new Editor({
|
||||
plugins: [TablePlugin, MacroPlugin]
|
||||
});
|
||||
editor.view();
|
||||
@@ -0,0 +1,103 @@
|
||||
class GrungEditor extends Grung {
|
||||
|
||||
constructor(settings) {
|
||||
/*
|
||||
* Create a new Editor instance.
|
||||
*/
|
||||
super(settings);
|
||||
this.states = {
|
||||
VIEW: 'view',
|
||||
EDIT: 'edit',
|
||||
WYSIWYG: 'wysiwyg'
|
||||
}
|
||||
|
||||
this.turndown = new TurndownService({
|
||||
headingStyle: 'atx',
|
||||
codeBlockStyle: 'fenced',
|
||||
});
|
||||
this.turndown.use([turndownPluginGfm.gfm, turndownPluginGfm.tables]);
|
||||
this.turndown.keep(['pre']);
|
||||
this.#bindEvents();
|
||||
|
||||
this.plugins().forEach(plugin => { plugin.setEditable() });
|
||||
}
|
||||
|
||||
#bindEvents() {
|
||||
this.element.addEventListener('keydown', (evt) => {
|
||||
if (this.state === this.states.VIEW) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
if (event.key === 'Enter') {
|
||||
console.log(this.#state, this.#states.EDIT);
|
||||
if (this.#state === this.#states.EDIT) {
|
||||
evt.preventDefault();
|
||||
this.insertAtCursor(document.createTextNode("\n"));
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (this.cachedMarkdown != this.element.textContent) {
|
||||
this.changed = true;
|
||||
this.cachedMarkdown = this.element.textContent;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
HtmlToMarkdown(html) {
|
||||
return this.turndown.turndown(html);
|
||||
}
|
||||
|
||||
getMarkdown() {
|
||||
/*
|
||||
* Return the current markdown.
|
||||
*/
|
||||
if (this.getState() === this.states.EDIT) {
|
||||
this.cachedMarkdown = this.element.innerHTML.replaceAll(/<\/?div>/g, "\n").replaceAll('<br>', "");
|
||||
} else if (this.getState() === this.states.WYSIWYG) {
|
||||
this.cachedMarkdown = this.HtmlToMarkdown(this.element.innerHTML);
|
||||
} else if (!this.cachedMarkdown) {
|
||||
this.cachedMarkdown = this.source;
|
||||
}
|
||||
return this.cachedMarkdown;
|
||||
}
|
||||
|
||||
wysiwyg() {
|
||||
/*
|
||||
* Put the editor in WYSIWYG editing mode.
|
||||
*/
|
||||
if (this.getState() === this.states.WYSIWYG) {
|
||||
return;
|
||||
}
|
||||
this.changed = false;
|
||||
this.element.contentEditable = true;
|
||||
this.element.innerHTML = this.getHTML();
|
||||
this.setState(this.states.WYSIWYG);
|
||||
}
|
||||
|
||||
edit() {
|
||||
/*
|
||||
* Put the editor into source editing mode.
|
||||
*/
|
||||
if (this.state === this.states.EDIT) {
|
||||
return;
|
||||
}
|
||||
this.changed = false;
|
||||
this.element.contentEditable = true;
|
||||
this.element.innerHTML = encodeHtmlEntities(this.getMarkdown());
|
||||
this.setState(this.states.EDIT);
|
||||
}
|
||||
|
||||
insertAtCursor(node) {
|
||||
var sel, range, html;
|
||||
sel = window.getSelection();
|
||||
range = sel.getRangeAt(0);
|
||||
range.deleteContents();
|
||||
range.insertNode(node);
|
||||
range.setStartAfter(node);
|
||||
this.element.focus();
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
class Grung {
|
||||
constructor(settings) {
|
||||
/*
|
||||
* Create a new Editor instance.
|
||||
*/
|
||||
this.element = document.getElementById(settings.editorId || 'content');
|
||||
this.source = this.element.textContent;
|
||||
|
||||
this.marked = marked;
|
||||
this.marked.use({
|
||||
breaks: false,
|
||||
gfm: true,
|
||||
});
|
||||
this.states = {
|
||||
VIEW: 'view',
|
||||
}
|
||||
this.cachedHTML = null;
|
||||
this.cachedMarkdown = null;
|
||||
this.state = null;
|
||||
this.changed = false;
|
||||
this.enabledPlugins = {};
|
||||
|
||||
settings.plugins.forEach(plugin => {
|
||||
this.enabledPlugins[plugin.name] = new plugin({name: plugin.name, editor: this});
|
||||
});
|
||||
this.getHTML();
|
||||
this.element.classList.add("loaded");
|
||||
}
|
||||
|
||||
plugins() {
|
||||
return Object.values(this.enabledPlugins).sort((a, b) => { a.precedence < b.precedence });
|
||||
}
|
||||
|
||||
getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
setState(newState) {
|
||||
this.state = newState;
|
||||
Object.values(this.states).forEach(state => {
|
||||
if (state == newState) {
|
||||
this.element.classList.add(state);
|
||||
} else {
|
||||
this.element.classList.remove(state);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
markdownToHTML(md) {
|
||||
var html = this.marked.parse(md);
|
||||
return html;
|
||||
}
|
||||
|
||||
getHTML(string) {
|
||||
/*
|
||||
* Convert the markdown source to HTML.
|
||||
*/
|
||||
if (this.changed || !this.cachedHTML) {
|
||||
this.cachedHTML = this.markdownToHTML(this.getMarkdown());
|
||||
}
|
||||
return this.cachedHTML;
|
||||
}
|
||||
|
||||
getMarkdown() {
|
||||
if (!this.cachedMarkdown) {
|
||||
this.cachedMarkdown = this.source;
|
||||
}
|
||||
return this.cachedMarkdown;
|
||||
}
|
||||
|
||||
reset() {
|
||||
/*
|
||||
* Discard any unsaved edits and reset the editor to its initial state.
|
||||
*/
|
||||
this.cachedHTML = null;
|
||||
this.cachedMarkdown = null;
|
||||
this.view();
|
||||
}
|
||||
|
||||
view() {
|
||||
/*
|
||||
* Convert the editor read-only mode and display the current HTML.
|
||||
*/
|
||||
if (this.getState() === this.states.VIEW) {
|
||||
return;
|
||||
}
|
||||
this.element.innerHTML = this.getHTML();
|
||||
this.setState(this.states.VIEW);
|
||||
this.contentEditable = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GrungPlugin {
|
||||
|
||||
constructor(settings) {
|
||||
this.name = settings.name;
|
||||
this.editor = settings.editor;
|
||||
this.precedence = 50;
|
||||
};
|
||||
|
||||
setEditable() {
|
||||
};
|
||||
|
||||
toMarkdown(html) {
|
||||
return html;
|
||||
};
|
||||
|
||||
toHTML(md) {
|
||||
return md;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
class MacroPlugin extends GrungPlugin {
|
||||
|
||||
macros = {
|
||||
// image: {}
|
||||
// toc {}
|
||||
// widget {}
|
||||
//
|
||||
style: {
|
||||
inline: false,
|
||||
toHTML: (token, node) => {
|
||||
return node.replace(/class="macro"/, `class="macro ${token.keywords}"`);
|
||||
}
|
||||
},
|
||||
|
||||
multiline: {
|
||||
inline: false,
|
||||
fromHTML: (node, markdown) => {
|
||||
var content = node.innerHTML.replaceAll("<br>", "\0");
|
||||
content = content.replaceAll("{{{", "{{{");
|
||||
content = content.replaceAll("}}}", "}}}");
|
||||
content = encodeHtmlEntities(content);
|
||||
return `{{{${content}}}}`;
|
||||
},
|
||||
},
|
||||
|
||||
user: {
|
||||
inline: true,
|
||||
toHTML: (token, node) => {
|
||||
return node + document.querySelector("nav > ul > li.user > a:first-child").outerHTML;
|
||||
},
|
||||
},
|
||||
toc: {
|
||||
inline: true,
|
||||
element: 'div',
|
||||
toHTML: (token, node) => {
|
||||
return node + "</div>";
|
||||
},
|
||||
postprocess: (html) => {
|
||||
const subList = (depth) => {
|
||||
var li = document.createElement("li");
|
||||
var ul = document.createElement("ul");
|
||||
li.appendChild(ul);
|
||||
return li;
|
||||
};
|
||||
|
||||
const buf = document.createElement('div');
|
||||
buf.innerHTML = html;
|
||||
const tocElement =
|
||||
buf.querySelectorAll('[data-macro-name="toc"]').forEach(tocElement => {
|
||||
|
||||
var params = {
|
||||
depth: tocElement.dataset.paramDepth || 3,
|
||||
keywords: tocElement.dataset.paramKeywords || "",
|
||||
};
|
||||
|
||||
const headings = buf.querySelectorAll("h2, h3, h4, h5, h6");
|
||||
|
||||
const toc = document.createElement("ul");
|
||||
toc.setAttribute('role', 'list');
|
||||
|
||||
var lastDepth = null;
|
||||
var ul = toc;
|
||||
headings.forEach(heading => {
|
||||
var depth = parseInt(heading.nodeName[1]) - 1;
|
||||
if (depth > params.depth) {
|
||||
return;
|
||||
}
|
||||
if (lastDepth === null) {
|
||||
lastDepth = depth;
|
||||
}
|
||||
|
||||
var index = document.createElement("li");
|
||||
index.innerHTML = '<a href="#h">' + heading.innerHTML + "</a>";
|
||||
|
||||
var list = null;
|
||||
if (depth > lastDepth) {
|
||||
list = subList(depth);
|
||||
ul.appendChild(list);
|
||||
ul = list.firstChild;
|
||||
} else if (depth < lastDepth) {
|
||||
var list = subList(depth);
|
||||
toc.appendChild(list);
|
||||
ul = list.firstChild;
|
||||
}
|
||||
ul.appendChild(index);
|
||||
lastDepth = depth;
|
||||
});
|
||||
|
||||
if (params.keywords) {
|
||||
params.keywords.split(" ").forEach(className => {
|
||||
if (className) {
|
||||
toc.classList.add(className);
|
||||
}
|
||||
});
|
||||
}
|
||||
tocElement.appendChild(toc);
|
||||
});
|
||||
return buf.innerHTML;
|
||||
},
|
||||
},
|
||||
npc: {
|
||||
toHTML: (settings) => {
|
||||
var name = camelCase(settings.keywords).join(" ");
|
||||
var target = name.replaceAll(" ", "");
|
||||
return `<a href="/NPC/${target}" data-npc-name="${name}">👤 ${name}</a>`;
|
||||
},
|
||||
fromHTML: (node) => {
|
||||
return `{{npc ${node.firstChild.dataset.npcName}}}`;
|
||||
},
|
||||
},
|
||||
spell: {
|
||||
toHTML: (settings) => {
|
||||
var name = camelCase(settings.keywords).join(" ");
|
||||
var target = name.replaceAll(" ", "");
|
||||
return `<a href="/Spell/${target}" data-spell-name="${name}">✨ ${name}</a>`;
|
||||
},
|
||||
fromHTML: (node) => {
|
||||
return `{{spell ${node.firstChild.dataset.spellName}}}`;
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
getTokens = (pattern, source) => {
|
||||
const matched = source.matchAll(pattern);
|
||||
const tokens = [];
|
||||
if (!matched) {
|
||||
return tokens;
|
||||
}
|
||||
matched.forEach(match => {
|
||||
if (!this.macros[match.groups.name]) {
|
||||
return;
|
||||
}
|
||||
const token = {
|
||||
type: 'macro',
|
||||
source: match[0],
|
||||
matched: match,
|
||||
macro: this.macros[match.groups.name],
|
||||
keywords: (match.groups.keywords || '').trim(),
|
||||
inline: this.macros[match.groups.name].inline,
|
||||
params: {},
|
||||
rendered: '',
|
||||
};
|
||||
if (match.groups.parameters) {
|
||||
var params = decodeHtmlEntities(match.groups.parameters.trim());
|
||||
params.matchAll(this.paramPattern).forEach(param => {
|
||||
if (param.groups) {
|
||||
var name = param.groups.name;
|
||||
token.params[name] = decodeHtmlEntities(param.groups.value.trim());
|
||||
}
|
||||
});
|
||||
}
|
||||
token.rendered = this.renderToken(token);
|
||||
tokens.push(token);
|
||||
});
|
||||
return tokens;
|
||||
}
|
||||
|
||||
renderToken = (token) => {
|
||||
const tag = token.macro.element || (token.inline ? 'span' : 'div');
|
||||
var node = `<${tag} class="macro" data-plugin-name="macro" data-macro-name="${token.matched.groups.name}"`;
|
||||
|
||||
for (var name in token.params) {
|
||||
node += ` data-param-${name}="${(token.params[name] || "").trim()}"`;
|
||||
}
|
||||
|
||||
if (token.keywords) {
|
||||
node += ` data-keywords="${token.keywords}"`;
|
||||
}
|
||||
|
||||
node += ` data-inline="${token.inline}"`;
|
||||
|
||||
node += ">";
|
||||
|
||||
if (token.macro.toHTML) {
|
||||
node = token.macro.toHTML(token, node);
|
||||
}
|
||||
|
||||
if (token.inline) {
|
||||
node += `</${tag}>`;
|
||||
}
|
||||
|
||||
// preserve the wrapping element, unless it is a paragraph.
|
||||
if (token.matched.groups.wrap) {
|
||||
if (token.matched.groups.wrap != '<p>') {
|
||||
node = token.matched.groups.wrap + node + token.matched.groups.endwrap;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
constructor(settings) {
|
||||
super(settings);
|
||||
this.pattern = /(?<!`)(?<wrap><[^>]+?>){{(?<name>\w+)(?<keywords>(?:\s*[\w-]+)*?)?(?<parameters>(?:\s+[\w-]+=\S+?)*)?\s*(?<closed>}})?(?<endwrap><\/[^>]+?>)/mg;
|
||||
this.endPattern = /<p>}}\s*<\/p>/mg;
|
||||
this.paramPattern = /\s*(?<name>[^=]+)="(?<value>[^"]*)"/g;
|
||||
this.multilinePattern = /(?<!\{{3}[\s\n]*)\{{3}[\s\n]*(?<content>(.(?!\}{3})*)+?)[\s\n]*\}{3}/smg;
|
||||
|
||||
const plugin = this;
|
||||
|
||||
this.editor.marked.use({
|
||||
hooks: {
|
||||
preprocess: (source) => {
|
||||
const matched = source.matchAll(plugin.multilinePattern);
|
||||
var md = source;
|
||||
matched.forEach(match => {
|
||||
var wrapper = '<span class="macro" data-plugin-name="macro" data-macro-name="multiline" data-inline="true" style="display: inline-block;">';
|
||||
var content = decodeHtmlEntities(match.groups.content)
|
||||
.replaceAll("\n", "::BR::")
|
||||
.replaceAll('`', '::QU::')
|
||||
.replaceAll('{', '::OC::')
|
||||
.replaceAll('}', '::CC::');
|
||||
md = md.replaceAll(match[0], wrapper + '\0' + content + '\0</span>');
|
||||
});
|
||||
return md;
|
||||
},
|
||||
postprocess: (html) => {
|
||||
plugin.getTokens(plugin.pattern, html).forEach(token => {
|
||||
html = html.replaceAll(token.source, token.rendered);
|
||||
});
|
||||
html = html.replaceAll(plugin.endPattern, '</div>');
|
||||
Object.values(plugin.macros).forEach(macro => {
|
||||
if (macro.postprocess) {
|
||||
html = macro.postprocess(html);
|
||||
}
|
||||
});
|
||||
|
||||
html = html.replaceAll("::BR::", "<br>")
|
||||
.replaceAll('::QU::', '`')
|
||||
.replaceAll('::OC::', '{')
|
||||
.replaceAll('::CC::', '}');
|
||||
|
||||
// remove unsafe html tags
|
||||
return DOMPurify.sanitize(html, {});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setEditable() {
|
||||
this.editor.turndown.addRule('macros', {
|
||||
filter: function (node, options) {
|
||||
return ((node.nodeName === 'DIV' || node.nodeName === 'SPAN') && node.dataset.pluginName == 'macro')
|
||||
},
|
||||
replacement: function (content, node, options) {
|
||||
var macro = plugin.macros[node.getAttribute('data-macro-name')];
|
||||
|
||||
if (macro.fromHTML) {
|
||||
return macro.fromHTML(node, content);
|
||||
}
|
||||
|
||||
var md = '{{' + node.dataset.macroName;
|
||||
if (node.dataset.keywords) {
|
||||
md += " " + node.dataset.keywords;
|
||||
}
|
||||
|
||||
for (var paramName in node.dataset) {
|
||||
if (paramName.indexOf("param") != 0) {
|
||||
continue;
|
||||
}
|
||||
md += ` ${paramName.replace('param', '').toLowerCase()}="${node.dataset[paramName]}"`
|
||||
};
|
||||
|
||||
if (node.dataset.inline == "false") {
|
||||
md = `\n\n${md}\n\n`;
|
||||
md += plugin.editor.HtmlToMarkdown(node.innerHTML);
|
||||
md += "\n\n}}\n\n";
|
||||
} else {
|
||||
md += "}}";
|
||||
}
|
||||
|
||||
// replace nulls with line breaks, for the multiline macro
|
||||
md = md.replaceAll('\0', "\n");
|
||||
|
||||
return md;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function camelCase(words) {
|
||||
var output = [];
|
||||
words.trim().split(/\s+/g).forEach(word => {
|
||||
var lcWord = word.toLowerCase();
|
||||
output.push(lcWord.charAt(0).toUpperCase() + lcWord.slice(1));
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
function b64encode(input) {
|
||||
return new TextEncoder().encode(input).toBase64();
|
||||
}
|
||||
|
||||
function b64decode(input) {
|
||||
return new TextDecoder().decode(Uint8Array.fromBase64(input));
|
||||
}
|
||||
|
||||
function decodeHtmlEntities(html) {
|
||||
var txt = document.createElement("textarea");
|
||||
txt.innerHTML = html;
|
||||
return txt.value;
|
||||
}
|
||||
|
||||
function encodeHtmlEntities(str) {
|
||||
return str.replace(/[\u00A0-\u9999<>\&]/g, i => '&#'+i.charCodeAt(0)+';')
|
||||
}
|
||||
+90
-13
@@ -43,6 +43,7 @@ var rules = {};
|
||||
rules.tableCell = {
|
||||
filter: ['th', 'td'],
|
||||
replacement: function (content, node) {
|
||||
if (tableShouldBeSkipped(nodeParentTable(node))) return content;
|
||||
return cell(content, node)
|
||||
}
|
||||
};
|
||||
@@ -50,19 +51,26 @@ rules.tableCell = {
|
||||
rules.tableRow = {
|
||||
filter: 'tr',
|
||||
replacement: function (content, node) {
|
||||
const parentTable = nodeParentTable(node);
|
||||
if (tableShouldBeSkipped(parentTable)) return content;
|
||||
|
||||
var borderCells = '';
|
||||
var alignMap = { left: ':--', right: '--:', center: ':-:' };
|
||||
|
||||
if (isHeadingRow(node)) {
|
||||
for (var i = 0; i < node.childNodes.length; i++) {
|
||||
const colCount = tableColCount(parentTable);
|
||||
for (var i = 0; i < colCount; i++) {
|
||||
const childNode = colCount >= node.childNodes.length ? null : node.childNodes[i];
|
||||
var border = '---';
|
||||
var align = (
|
||||
node.childNodes[i].getAttribute('align') || ''
|
||||
).toLowerCase();
|
||||
var align = childNode ? (childNode.getAttribute('align') || '').toLowerCase() : '';
|
||||
|
||||
if (align) border = alignMap[align] || border;
|
||||
|
||||
borderCells += cell(border, node.childNodes[i]);
|
||||
if (childNode) {
|
||||
borderCells += cell(border, node.childNodes[i]);
|
||||
} else {
|
||||
borderCells += cell(border, null, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return '\n' + content + (borderCells ? '\n' + borderCells : '')
|
||||
@@ -73,13 +81,27 @@ rules.table = {
|
||||
// Only convert tables with a heading row.
|
||||
// Tables with no heading row are kept using `keep` (see below).
|
||||
filter: function (node) {
|
||||
return node.nodeName === 'TABLE' && isHeadingRow(node.rows[0])
|
||||
return node.nodeName === 'TABLE'
|
||||
},
|
||||
|
||||
replacement: function (content) {
|
||||
replacement: function (content, node) {
|
||||
if (tableShouldBeSkipped(node)) return content;
|
||||
|
||||
// Ensure there are no blank lines
|
||||
content = content.replace('\n\n', '\n');
|
||||
return '\n\n' + content + '\n\n'
|
||||
content = content.replace(/\n+/g, '\n');
|
||||
|
||||
// If table has no heading, add an empty one so as to get a valid Markdown table
|
||||
var secondLine = content.trim().split('\n');
|
||||
if (secondLine.length >= 2) secondLine = secondLine[1];
|
||||
var secondLineIsDivider = secondLine.indexOf('| ---') === 0;
|
||||
|
||||
var columnCount = tableColCount(node);
|
||||
var emptyHeader = '';
|
||||
if (columnCount && !secondLineIsDivider) {
|
||||
emptyHeader = '|' + ' |'.repeat(columnCount) + '\n' + '|' + ' --- |'.repeat(columnCount);
|
||||
}
|
||||
|
||||
return '\n\n' + emptyHeader + content + '\n\n'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -120,16 +142,71 @@ function isFirstTbody (element) {
|
||||
)
|
||||
}
|
||||
|
||||
function cell (content, node) {
|
||||
var index = indexOf.call(node.parentNode.childNodes, node);
|
||||
function cell (content, node = null, index = null) {
|
||||
if (index === null) index = indexOf.call(node.parentNode.childNodes, node);
|
||||
var prefix = ' ';
|
||||
if (index === 0) prefix = '| ';
|
||||
return prefix + content + ' |'
|
||||
let filteredContent = content.trim().replace(/\n\r/g, '<br>').replace(/\n/g, "<br>");
|
||||
filteredContent = filteredContent.replace(/\|+/g, '\\|');
|
||||
while (filteredContent.length < 3) filteredContent += ' ';
|
||||
if (node) filteredContent = handleColSpan(filteredContent, node, ' ');
|
||||
return prefix + filteredContent + ' |'
|
||||
}
|
||||
|
||||
function nodeContainsTable(node) {
|
||||
if (!node.childNodes) return false;
|
||||
|
||||
for (let i = 0; i < node.childNodes.length; i++) {
|
||||
const child = node.childNodes[i];
|
||||
if (child.nodeName === 'TABLE') return true;
|
||||
if (nodeContainsTable(child)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Various conditions under which a table should be skipped - i.e. each cell
|
||||
// will be rendered one after the other as if they were paragraphs.
|
||||
function tableShouldBeSkipped(tableNode) {
|
||||
if (!tableNode) return true;
|
||||
if (!tableNode.rows) return true;
|
||||
if (tableNode.rows.length === 1 && tableNode.rows[0].childNodes.length <= 1) return true; // Table with only one cell
|
||||
|
||||
// Not sure why we're excluding this. possibly because it'll freak out the parser? --evilchili
|
||||
//if (nodeContainsTable(tableNode)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function nodeParentTable(node) {
|
||||
let parent = node.parentNode;
|
||||
while (parent.nodeName !== 'TABLE') {
|
||||
parent = parent.parentNode;
|
||||
if (!parent) return null;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
function handleColSpan(content, node, emptyChar) {
|
||||
const colspan = node.getAttribute('colspan') || 1;
|
||||
for (let i = 1; i < colspan; i++) {
|
||||
content += ' | ' + emptyChar.repeat(3);
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
function tableColCount(node) {
|
||||
let maxColCount = 0;
|
||||
for (let i = 0; i < node.rows.length; i++) {
|
||||
const row = node.rows[i];
|
||||
const colCount = row.childNodes.length;
|
||||
if (colCount > maxColCount) maxColCount = colCount;
|
||||
}
|
||||
return maxColCount
|
||||
}
|
||||
|
||||
function tables (turndownService) {
|
||||
turndownService.keep(function (node) {
|
||||
return node.nodeName === 'TABLE' && !isHeadingRow(node.rows[0])
|
||||
return node.nodeName === 'TABLE'
|
||||
});
|
||||
for (var key in rules) turndownService.addRule(key, rules[key]);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
!function(e,t){"object"==typeof exports&&typeof module<"u"?module.exports=t():"function"==typeof define&&define.amd?define("marked",t):e.marked=t()}(typeof globalThis<"u"?globalThis:typeof self<"u"?self:this,function(){var e,t={},E=t,r={exports:t},i=Object.defineProperty,Z=Object.getOwnPropertyDescriptor,O=Object.getOwnPropertyNames,D=Object.prototype.hasOwnProperty,n={},M=n,j={Hooks:()=>L,Lexer:()=>A,Marked:()=>K,Parser:()=>I,Renderer:()=>_,TextRenderer:()=>P,Tokenizer:()=>v,defaults:()=>l,getDefaults:()=>s,lexer:()=>ie,marked:()=>C,options:()=>V,parse:()=>ne,parseInline:()=>re,parser:()=>se,setOptions:()=>Y,use:()=>ee,walkTokens:()=>te};for(e in j)i(M,e,{get:j[e],enumerable:!0});function s(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}r.exports=(e=>{var t=i({},"__esModule",{value:!0}),r=e,n=void 0,s=void 0;if(r&&"object"==typeof r||"function"==typeof r)for(let e of O(r))D.call(t,e)||e===n||i(t,e,{get:()=>r[e],enumerable:!(s=Z(r,e))||s.enumerable});return t})(n);var l=s();function N(e){l=e}n={exec:()=>null};function a(e,t=""){let n="string"==typeof e?e:e.source,s={replace:(e,t)=>{let r="string"==typeof t?t:t.source;return r=r.replace(o.caret,"$1"),n=n.replace(e,r),s},getRegex:()=>new RegExp(n,t)};return s}var o={codeRemoveIndent:/^(?: {1,4}| {0,3}\t)/gm,outputLinkReplace:/\\([\[\]])/g,indentCodeCompensation:/^(\s+)(?:```)/,beginningSpace:/^\s+/,endingHash:/#$/,startingSpaceChar:/^ /,endingSpaceChar:/ $/,nonSpaceChar:/[^ ]/,newLineCharGlobal:/\n/g,tabCharGlobal:/\t/g,multipleSpaceGlobal:/\s+/g,blankLine:/^[ \t]*$/,doubleBlankLine:/\n[ \t]*\n[ \t]*$/,blockquoteStart:/^ {0,3}>/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] /,listReplaceTask:/^\[[ xX]\] +/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^<a /i,endATag:/^<\/a>/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^</,endAngleBracket:/>$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:e=>new RegExp(`^( {0,3}${e})((?:[ ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`),hrRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}#`),htmlBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}<(?:[a-z].*>|!--)`,"i")},h=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,c=/(?:[*+-]|\d{1,9}[.)])/,p=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,u=a(p).replace(/bull/g,c).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),p=a(p).replace(/bull/g,c).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),g=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,k=/(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/,d=a(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",k).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),c=a(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,c).getRegex(),f="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",x=/<!--(?:-?>|[\s\S]*?(?:-->|$))/,b=a("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|commentLine truncated
|
||||
]`).replace("lheading",u).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},p=/^( {2,}|\\)\n(?!\s*$)/,b=/[\p{P}\p{S}]/u,f=/[\s\p{P}\p{S}]/u,g=/[^\s\p{P}\p{S}]/u,h=a(/^((?![*_])punctSpace)/,"u").replace(/punctSpace/g,f).getRegex(),u=/(?!~)[\p{P}\p{S}]/u,w=/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,y=a(w,"u").replace(/punct/g,b).getRegex(),w=a(w,"u").replace(/punct/g,u).getRegex(),$="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",Q=a($,"gu").replace(/notPunctSpace/g,g).replace(/punctSpace/g,f).replace(/punct/g,b).getRegex(),$=a($,"gu").replace(/notPunctSpace/g,/(?:[^\s\p{P}\p{S}]|~)/u).replace(/punctSpace/g,/(?!~)[\s\p{P}\p{S}]/u).replace(/punct/g,u).getRegex(),u=a("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,g).replace(/punctSpace/g,f).replace(/punct/g,b).getRegex(),g=a(/\\(punct)/,"gu").replace(/punct/g,b).getRegex(),f=a(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),b=a(x).replace("(?:--\x3e|$)","--\x3e").getRegex(),x=a("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment",b).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),b=/(?:\[(?:\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`[^`]*`|[^\[\]\\`])*?/,H=a(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label",b).replace("href",/<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),G=a(/^!?\[(label)\]\[(ref)\]/).replace("label",b).replace("ref",k).getRegex(),k=a(/^!?\[(ref)\](?:\[\])?/).replace("ref",k).getRegex(),g={_backpedal:n,anyPunctuation:g,autolink:f,blockSkip:/\[[^\[\]]*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)|`[^`]*?`|<(?! )[^<>]*?>/g,br:p,code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,del:n,emStrongLDelim:y,emStrongRDelimAst:Q,emStrongRDelimUnd:u,escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,link:H,nolink:k,punctuation:h,reflink:G,reflinkSearch:a("reflink|nolink(?!\\()","g").replace("reflink",G).replace("nolink",k).getRegex(),tag:x,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,url:n},f={...g,link:a(/^!?\[(label)\]\((.*?)\)/).replace("label",b).getRegex(),reflink:a(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",b).getRegex()},y={...g,emStrongRDelimAst:$,emStrongLDelim:w,url:a(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,"i").replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/},Q={...y,br:a(p).replace("{2,}","*").getRegex(),text:a(y.text).replace("\\b_","\\b_| {2,}\\n").replace(/\{2,\}/g,"*").getRegex()},S={normal:d,gfm:c,pedantic:m},R={normal:g,gfm:y,breaks:Q,pedantic:f},W={"&":"&","<":"<",">":">",'"':""","'":"'"},X=e=>W[e];function T(e,t){if(t){if(o.escapeTest.test(e))return e.replace(o.escapeReplace,X)}else if(o.escapeTestNoEncode.test(e))return e.replace(o.escapeReplaceNoEncode,X);return e}function F(e){try{e=encodeURI(e).replace(o.percentDecode,"%")}catch{return null}return e}function U(e,t){let r=e.replace(o.findPipe,(e,t,r)=>{let n=!1,s=t;for(;0<=--s&&"\\"===r[s];)n=!n;return n?"|":" |"}),n=r.split(o.splitPipe),s=0;if(n[0].trim()||n.shift(),0<n.length&&!n.at(-1)?.trim()&&n.pop(),t)if(n.length>t)n.splice(t);else for(;n.length<t;)n.push("");for(;s<n.length;s++)n[s]=n[s].trim().replace(o.slashPipe,"|");return n}function z(e,t,r){var n=e.length;if(0===n)return"";let s=0;for(;s<n;){var i=e.charAt(n-s-1);if(i!==t||r){if(i===t||!r)break;s++}else s++}return e.slice(0,n-s)}function J(e,t,r,n,s){var i=t.href,t=t.title||null,s=e[1].replace(s.other.outputLinkReplace,"$1"),e=(n.state.inLink=!0,{type:"!"===e[0].charAt(0)?"image":"link",raw:r,href:i,title:t,text:s,tokens:n.inlineTokens(s)});return n.state.inLink=!1,e}var v=Line truncated
|
||||
`)}}fences(e){let t=this.rules.block.fences.exec(e);var r;if(t)return r=function(e,t,r){if(null===(e=e.match(r.other.indentCodeCompensation)))return t;let n=e[1];return t.split(`
|
||||
`).map(e=>{var t=e.match(r.other.beginningSpace);if(null===t)return e;var[t]=t;return t.length>=n.length?e.slice(n.length):e}).join(`
|
||||
`)}(e=t[0],t[3]||"",this.rules),{type:"code",raw:e,lang:t[2]&&t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"),text:r}}heading(e){let r=this.rules.block.heading.exec(e);if(r){let t=r[2].trim();if(this.rules.other.endingHash.test(t)){let e=z(t,"#");!this.options.pedantic&&e&&!this.rules.other.endingSpaceChar.test(e)||(t=e.trim())}return{type:"heading",raw:r[0],depth:r[1].length,text:t,tokens:this.lexer.inline(t)}}}hr(e){e=this.rules.block.hr.exec(e);if(e)return{type:"hr",raw:z(e[0],`
|
||||
`)}}blockquote(e){e=this.rules.block.blockquote.exec(e);if(e){let i=z(e[0],`
|
||||
`).split(`
|
||||
`),l="",a="",o=[];for(;0<i.length;){let e=!1,t=[],r;for(r=0;r<i.length;r++)if(this.rules.other.blockquoteStart.test(i[r]))t.push(i[r]),e=!0;else{if(e)break;t.push(i[r])}i=i.slice(r);let n=t.join(`
|
||||
`),s=n.replace(this.rules.other.blockquoteSetextReplace,`
|
||||
$1`).replace(this.rules.other.blockquoteSetextReplace2,"");l=l?l+`
|
||||
`+n:n,a=a?a+`
|
||||
`+s:s;var h=this.lexer.state.top;if(this.lexer.state.top=!0,this.lexer.blockTokens(s,o,!0),this.lexer.state.top=h,0===i.length)break;h=o.at(-1);if("code"===h?.type)break;if("blockquote"===h?.type){var c=h,p=c.raw+`
|
||||
`+i.join(`
|
||||
`),p=this.blockquote(p);o[o.length-1]=p,l=l.substring(0,l.length-c.raw.length)+p.raw,a=a.substring(0,a.length-c.text.length)+p.text;break}if("list"===h?.type){let e=h,t=e.raw+`
|
||||
`+i.join(`
|
||||
`),r=this.list(t);o[o.length-1]=r,l=l.substring(0,l.length-h.raw.length)+r.raw,a=a.substring(0,a.length-e.raw.length)+r.raw,i=t.substring(o.at(-1).raw.length).split(`
|
||||
`)}}return{type:"blockquote",raw:l,tokens:o,text:a}}}list(g){let l=this.rules.block.list.exec(g);if(l){let e=l[1].trim(),t=1<e.length,n={type:"list",raw:"",ordered:t,start:t?+e.slice(0,-1):"",loose:!1,items:[]},s=(e=t?"\\d{1,9}\\"+e.slice(-1):"\\"+e,this.options.pedantic&&(e=t?e:"[*+-]"),this.rules.other.listItemRegex(e)),i=!1;for(;g;){let e=!1,a="",o="";if(!(l=s.exec(g))||this.rules.block.hr.test(g))break;a=l[0],g=g.substring(a.length);let h=l[2].split(`
|
||||
`,1)[0].replace(this.rules.other.listReplaceTabs,e=>" ".repeat(3*e.length)),c=g.split(`
|
||||
`,1)[0],p=!h.trim(),u=0;if(this.options.pedantic?(u=2,o=h.trimStart()):p?u=l[1].length+1:(u=4<(u=l[2].search(this.rules.other.nonSpaceChar))?1:u,o=h.slice(u),u+=l[1].length),p&&this.rules.other.blankLine.test(c)&&(a+=c+`
|
||||
`,g=g.substring(c.length+1),e=!0),!e){let r=this.rules.other.nextBulletRegex(u),n=this.rules.other.hrRegex(u),s=this.rules.other.fencesBeginRegex(u),i=this.rules.other.headingBeginRegex(u),l=this.rules.other.htmlBeginRegex(u);for(;g;){let e=g.split(`
|
||||
`,1)[0],t;if(c=e,t=this.options.pedantic?c=c.replace(this.rules.other.listReplaceNesting," "):c.replace(this.rules.other.tabCharGlobal," "),s.test(c)||i.test(c)||l.test(c)||r.test(c)||n.test(c))break;if(t.search(this.rules.other.nonSpaceChar)>=u||!c.trim())o+=`
|
||||
`+t.slice(u);else{if(p||4<=h.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)||s.test(h)||i.test(h)||n.test(h))break;o+=`
|
||||
`+c}p||c.trim()||(p=!0),a+=e+`
|
||||
`,g=g.substring(e.length+1),h=t.slice(u)}}n.loose||(i?n.loose=!0:this.rules.other.doubleBlankLine.test(a)&&(i=!0));let t=null,r;this.options.gfm&&((t=this.rules.other.listIsTask.exec(o))&&(r="[ ] "!==t[0],o=o.replace(this.rules.other.listReplaceTask,""))),n.items.push({type:"list_item",raw:a,task:!!t,checked:r,loose:!1,text:o,tokens:[]}),n.raw+=a}let r=n.items.at(-1);if(r){r.raw=r.raw.trimEnd(),r.text=r.text.trimEnd(),n.raw=n.raw.trimEnd();for(let r=0;r<n.items.length;r++)if(this.lexer.state.top=!1,n.items[r].tokens=this.lexer.blockTokens(n.items[r].text,[]),!n.loose){let e=n.items[r].tokens.filter(e=>"space"===e.type),t=0<e.length&&e.some(e=>this.rules.other.anyLine.test(e.raw));n.loose=t}if(n.loose)for(let e=0;e<n.items.length;e++)n.items[e].loose=!0;return n}}}html(e){e=this.rules.block.html.exec(e);if(e)return{type:"html",block:!0,raw:e[0],pre:"pre"===e[1]||"script"===e[1]||"style"===e[1],text:e[0]}}def(e){let t=this.rules.block.def.exec(e);var r,n;if(t)return e=t[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal," "),r=t[2]?t[2].replace(this.rules.other.hrefBrackets,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",n=t[3]&&t[3].substring(1,t[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"),{type:"def",tag:e,raw:t[0],href:r,title:n}}table(e){let s=this.rules.block.table.exec(e);if(s&&this.rules.other.tableDelimiter.test(s[2])){let t=U(s[1]),e=s[2].replace(this.rules.other.tableAlignChars,"").split("|"),r=s[3]?.trim()?s[3].replace(this.rules.other.tableRowBlankLine,"").split(`
|
||||
`):[],n={type:"table",raw:s[0],header:[],align:[],rows:[]};if(t.length===e.length){for(var i of e)this.rules.other.tableAlignRight.test(i)?n.align.push("right"):this.rules.other.tableAlignCenter.test(i)?n.align.push("center"):this.rules.other.tableAlignLeft.test(i)?n.align.push("left"):n.align.push(null);for(let e=0;e<t.length;e++)n.header.push({text:t[e],tokens:this.lexer.inline(t[e]),header:!0,align:n.align[e]});for(var l of r)n.rows.push(U(l,n.header.length).map((e,t)=>({text:e,tokens:this.lexer.inline(e),header:!1,align:n.align[t]})));return n}}}lheading(e){let t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:"="===t[2].charAt(0)?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){let t=this.rules.block.paragraph.exec(e);if(t)return e=t[1].charAt(t[1].length-1)===`
|
||||
`?t[1].slice(0,-1):t[1],{type:"paragraph",raw:t[0],text:e,tokens:this.lexer.inline(e)}}text(e){e=this.rules.block.text.exec(e);if(e)return{type:"text",raw:e[0],text:e[0],tokens:this.lexer.inline(e[0])}}escape(e){e=this.rules.inline.escape.exec(e);if(e)return{type:"escape",raw:e[0],text:e[1]}}tag(e){e=this.rules.inline.tag.exec(e);if(e)return!this.lexer.state.inLink&&this.rules.other.startATag.test(e[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(e[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(e[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(e[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:e[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:e[0]}}link(n){let s=this.rules.inline.link.exec(n);if(s){let e=s[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(e)){if(!this.rules.other.endAngleBracket.test(e))return;n=z(e.slice(0,-1),"\\");if((e.length-n.length)%2==0)return}else{var i,n=function(t,r){if(-1===t.indexOf(r[1]))return-1;let n=0;for(let e=0;e<t.length;e++)if("\\"===t[e])e++;else if(t[e]===r[0])n++;else if(t[e]===r[1]&&--n<0)return e;return 0<n?-2:-1}(s[2],"()");if(-2===n)return;-1<n&&(i=(0===s[0].indexOf("!")?5:4)+s[1].length+n,s[2]=s[2].substring(0,n),s[0]=s[0].substring(0,i).trim(),s[3]="")}let t=s[2],r="";return this.options.pedantic?(n=this.rules.other.pedanticHrefTitle.exec(t))&&(t=n[1],r=n[3]):r=s[3]?s[3].slice(1,-1):"",t=t.trim(),this.rules.other.startAngleBracket.test(t)&&(t=this.options.pedantic&&!this.rules.other.endAngleBracket.test(e)?t.slice(1):t.slice(1,-1)),J(s,{href:t&&t.replace(this.rules.inline.anyPunctuation,"$1"),title:r&&r.replace(this.rules.inline.anyPunctuation,"$1")},s[0],this.lexer,this.rules)}}reflink(r,n){let s;if((s=this.rules.inline.reflink.exec(r))||(s=this.rules.inline.nolink.exec(r))){let e=(s[2]||s[1]).replace(this.rules.other.multipleSpaceGlobal," "),t=n[e.toLowerCase()];return t?J(s,t,s[0],this.lexer,this.rules):{type:"text",raw:r=s[0].charAt(0),text:r}}}emStrong(l,a,e=""){let o=this.rules.inline.emStrongLDelim.exec(l);if(!(!o||o[3]&&e.match(this.rules.other.unicodeAlphaNumeric))&&(!o[1]&&!o[2]||!e||this.rules.inline.punctuation.exec(e))){let r=[...o[0]].length-1,e,n,s=r,i=0,t="*"===o[0][0]?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(t.lastIndex=0,a=a.slice(-1*l.length+r);null!=(o=t.exec(a));)if(e=o[1]||o[2]||o[3]||o[4]||o[5]||o[6])if(n=[...e].length,o[3]||o[4])s+=n;else if((o[5]||o[6])&&r%3&&!((r+n)%3))i+=n;else if(!(0<(s-=n))){n=Math.min(n,n+s+i);let e=[...o[0]][0].length,t=l.slice(0,r+o.index+e+n);if(Math.min(r,n)%2)return h=t.slice(1,-1),{type:"em",raw:t,text:h,tokens:this.lexer.inlineTokens(h)};var h=t.slice(2,-2);return{type:"strong",raw:t,text:h,tokens:this.lexer.inlineTokens(h)}}}}codespan(e){let n=this.rules.inline.code.exec(e);if(n){let e=n[2].replace(this.rules.other.newLineCharGlobal," "),t=this.rules.other.nonSpaceChar.test(e),r=this.rules.other.startingSpaceChar.test(e)&&this.rules.other.endingSpaceChar.test(e);return t&&r&&(e=e.substring(1,e.length-1)),{type:"codespan",raw:n[0],text:e}}}br(e){e=this.rules.inline.br.exec(e);if(e)return{type:"br",raw:e[0]}}del(e){e=this.rules.inline.del.exec(e);if(e)return{type:"del",raw:e[0],text:e[2],tokens:this.lexer.inlineTokens(e[2])}}autolink(r){r=this.rules.inline.autolink.exec(r);if(r){let e,t;return t="@"===r[2]?"mailto:"+(e=r[1]):e=r[1],{type:"link",raw:r[0],text:e,href:t,tokens:[{type:"text",raw:e,text:e}]}}}url(e){let r;if(r=this.rules.inline.url.exec(e)){let e,t;if("@"===r[2])e=r[0],t="mailto:"+e;else{for(var n;n=r[0],r[0]=this.rules.inline._backpedal.exec(r[0])?.[0]??"",n!==r[0];);e=r[0],t="www."===r[1]?"http://"+r[0]:r[0]}return{type:"link",raw:r[0],text:e,href:t,tokens:[{type:"text",raw:e,text:e}]}}}inlineText(e){var t,e=this.rules.inline.text.exec(e);if(e)return t=this.lexer.state.inRawBlock,{type:"text",raw:e[0],text:e[0],escaped:t}}},A=class q{tokens;options;state;tokenizer;inlineQueue;constructor(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||l,this.options.tokenizer=this.options.tokenizer||new v,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,(this.tokenizer.lexer=this).inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};let t={other:o,block:S.normal,inline:R.normal};this.options.pedantic?(t.block=S.pedantic,t.inline=R.pedantic):this.options.gfm&&(t.block=S.gfm,this.options.breaks?t.inline=R.breaks:t.inline=R.gfm),this.tokenizer.rules=t}static get rules(){return{block:S,inline:R}}static lex(e,t){return new q(t).lex(e)}static lexInline(e,t){return new q(t).inlineTokens(e)}lex(e){e=e.replace(o.carriageReturn,`
|
||||
`),this.blockTokens(e,this.tokens);for(let e=0;e<this.inlineQueue.length;e++){var t=this.inlineQueue[e];this.inlineTokens(t.src,t.tokens)}return this.inlineQueue=[],this.tokens}blockTokens(i,r=[],n=!1){for(this.options.pedantic&&(i=i.replace(o.tabCharGlobal," ").replace(o.spaceLine,""));i;){let t;if(!this.options.extensions?.block?.some(e=>!!(t=e.call({lexer:this},i,r))&&(i=i.substring(t.raw.length),r.push(t),!0)))if(t=this.tokenizer.space(i)){i=i.substring(t.raw.length);let e=r.at(-1);1===t.raw.length&&void 0!==e?e.raw+=`
|
||||
`:r.push(t)}else if(t=this.tokenizer.code(i)){i=i.substring(t.raw.length);let e=r.at(-1);"paragraph"===e?.type||"text"===e?.type?(e.raw+=(e.raw.endsWith(`
|
||||
`)?"":`
|
||||
`)+t.raw,e.text+=`
|
||||
`+t.text,this.inlineQueue.at(-1).src=e.text):r.push(t)}else if(t=this.tokenizer.fences(i))i=i.substring(t.raw.length),r.push(t);else if(t=this.tokenizer.heading(i))i=i.substring(t.raw.length),r.push(t);else if(t=this.tokenizer.hr(i))i=i.substring(t.raw.length),r.push(t);else if(t=this.tokenizer.blockquote(i))i=i.substring(t.raw.length),r.push(t);else if(t=this.tokenizer.list(i))i=i.substring(t.raw.length),r.push(t);else if(t=this.tokenizer.html(i))i=i.substring(t.raw.length),r.push(t);else if(t=this.tokenizer.def(i)){i=i.substring(t.raw.length);let e=r.at(-1);"paragraph"===e?.type||"text"===e?.type?(e.raw+=(e.raw.endsWith(`
|
||||
`)?"":`
|
||||
`)+t.raw,e.text+=`
|
||||
`+t.raw,this.inlineQueue.at(-1).src=e.text):this.tokens.links[t.tag]||(this.tokens.links[t.tag]={href:t.href,title:t.title},r.push(t))}else if(t=this.tokenizer.table(i))i=i.substring(t.raw.length),r.push(t);else if(t=this.tokenizer.lheading(i))i=i.substring(t.raw.length),r.push(t);else{let s=i;if(this.options.extensions?.startBlock){let t=1/0,r=i.slice(1),n;this.options.extensions.startBlock.forEach(e=>{"number"==typeof(n=e.call({lexer:this},r))&&0<=n&&(t=Math.min(t,n))}),t<1/0&&0<=t&&(s=i.substring(0,t+1))}if(this.state.top&&(t=this.tokenizer.paragraph(s))){let e=r.at(-1);n&&"paragraph"===e?.type?(e.raw+=(e.raw.endsWith(`
|
||||
`)?"":`
|
||||
`)+t.raw,e.text+=`
|
||||
`+t.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=e.text):r.push(t),n=s.length!==i.length,i=i.substring(t.raw.length)}else if(t=this.tokenizer.text(i)){i=i.substring(t.raw.length);let e=r.at(-1);"text"===e?.type?(e.raw+=(e.raw.endsWith(`
|
||||
`)?"":`
|
||||
`)+t.raw,e.text+=`
|
||||
`+t.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=e.text):r.push(t)}else if(i){var e="Infinite loop on byte: "+i.charCodeAt(0);if(this.options.silent){console.error(e);break}throw new Error(e)}}}return this.state.top=!0,r}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(s,r=[]){let n=s,t=null;if(this.tokens.links){let e=Object.keys(this.tokens.links);if(0<e.length)for(;null!=(t=this.tokenizer.rules.inline.reflinkSearch.exec(n));)e.includes(t[0].slice(t[0].lastIndexOf("[")+1,-1))&&(n=n.slice(0,t.index)+"["+"a".repeat(t[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;null!=(t=this.tokenizer.rules.inline.anyPunctuation.exec(n));)n=n.slice(0,t.index)+"++"+n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;null!=(t=this.tokenizer.rules.inline.blockSkip.exec(n));)n=n.slice(0,t.index)+"["+"a".repeat(t[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);n=this.options.hooks?.emStrongMask?.call({lexer:this},n)??n;let i=!1,l="";for(;s;){i||(l=""),i=!1;let t;if(!this.options.extensions?.inline?.some(e=>!!(t=e.call({lexer:this},s,r))&&(s=s.substring(t.raw.length),r.push(t),!0)))if(t=this.tokenizer.escape(s))s=s.substring(t.raw.length),r.push(t);else if(t=this.tokenizer.tag(s))s=s.substring(t.raw.length),r.push(t);else if(t=this.tokenizer.link(s))s=s.substring(t.raw.length),r.push(t);else if(t=this.tokenizer.reflink(s,this.tokens.links)){s=s.substring(t.raw.length);let e=r.at(-1);"text"===t.type&&"text"===e?.type?(e.raw+=t.raw,e.text+=t.text):r.push(t)}else if(t=this.tokenizer.emStrong(s,n,l))s=s.substring(t.raw.length),r.push(t);else if(t=this.tokenizer.codespan(s))s=s.substring(t.raw.length),r.push(t);else if(t=this.tokenizer.br(s))s=s.substring(t.raw.length),r.push(t);else if(t=this.tokenizer.del(s))s=s.substring(t.raw.length),r.push(t);else if(t=this.tokenizer.autolink(s))s=s.substring(t.raw.length),r.push(t);else if(!this.state.inLink&&(t=this.tokenizer.url(s)))s=s.substring(t.raw.length),r.push(t);else{let e=s;if(this.options.extensions?.startInline){let t=1/0,r=s.slice(1),n;this.options.extensions.startInline.forEach(e=>{"number"==typeof(n=e.call({lexer:this},r))&&0<=n&&(t=Math.min(t,n))}),t<1/0&&0<=t&&(e=s.substring(0,t+1))}if(t=this.tokenizer.inlineText(e)){s=s.substring(t.raw.length),"_"!==t.raw.slice(-1)&&(l=t.raw.slice(-1)),i=!0;let e=r.at(-1);"text"===e?.type?(e.raw+=t.raw,e.text+=t.text):r.push(t)}else if(s){var a="Infinite loop on byte: "+s.charCodeAt(0);if(this.options.silent){console.error(a);break}throw new Error(a)}}}return r}},_=class{options;parser;constructor(e){this.options=e||l}space(e){return""}code({text:e,lang:t,escaped:r}){t=(t||"").match(o.notSpaceStart)?.[0],e=e.replace(o.endingNewline,"")+`
|
||||
`;return t?'<pre><code class="language-'+T(t)+'">'+(r?e:T(e,!0))+`</code></pre>
|
||||
`:"<pre><code>"+(r?e:T(e,!0))+`</code></pre>
|
||||
`}blockquote({tokens:e}){return`<blockquote>
|
||||
${this.parser.parse(e)}</blockquote>
|
||||
`}html({text:e}){return e}def(e){return""}heading({tokens:e,depth:t}){return`<h${t}>${this.parser.parseInline(e)}</h${t}>
|
||||
`}hr(e){return`<hr>
|
||||
`}list(t){let e=t.ordered,r=t.start,n="";for(let e=0;e<t.items.length;e++){var s=t.items[e];n+=this.listitem(s)}var i=e?"ol":"ul";return"<"+i+(e&&1!==r?' start="'+r+'"':"")+`>
|
||||
`+n+"</"+i+`>
|
||||
`}listitem(e){let t="";var r;return e.task&&(r=this.checkbox({checked:!!e.checked}),e.loose?"paragraph"===e.tokens[0]?.type?(e.tokens[0].text=r+" "+e.tokens[0].text,e.tokens[0].tokens&&0<e.tokens[0].tokens.length&&"text"===e.tokens[0].tokens[0].type&&(e.tokens[0].tokens[0].text=r+" "+T(e.tokens[0].tokens[0].text),e.tokens[0].tokens[0].escaped=!0)):e.tokens.unshift({type:"text",raw:r+" ",text:r+" ",escaped:!0}):t+=r+" "),`<li>${t+=this.parser.parse(e.tokens,!!e.loose)}</li>
|
||||
`}checkbox({checked:e}){return"<input "+(e?'checked="" ':"")+'disabled="" type="checkbox">'}paragraph({tokens:e}){return`<p>${this.parser.parseInline(e)}</p>
|
||||
`}table(t){let e="",r="";for(let e=0;e<t.header.length;e++)r+=this.tablecell(t.header[e]);e+=this.tablerow({text:r});let n="";for(let e=0;e<t.rows.length;e++){var s=t.rows[e];r="";for(let e=0;e<s.length;e++)r+=this.tablecell(s[e]);n+=this.tablerow({text:r})}return`<table>
|
||||
<thead>
|
||||
`+e+`</thead>
|
||||
`+(n=n&&`<tbody>${n}</tbody>`)+`</table>
|
||||
`}tablerow({text:e}){return`<tr>
|
||||
${e}</tr>
|
||||
`}tablecell(e){var t=this.parser.parseInline(e.tokens),r=e.header?"th":"td";return(e.align?`<${r} align="${e.align}">`:`<${r}>`)+t+`</${r}>
|
||||
`}strong({tokens:e}){return`<strong>${this.parser.parseInline(e)}</strong>`}em({tokens:e}){return`<em>${this.parser.parseInline(e)}</em>`}codespan({text:e}){return`<code>${T(e,!0)}</code>`}br(e){return"<br>"}del({tokens:e}){return`<del>${this.parser.parseInline(e)}</del>`}link({href:e,title:t,tokens:r}){var r=this.parser.parseInline(r),n=F(e);if(null===n)return r;let s='<a href="'+(e=n)+'"';return t&&(s+=' title="'+T(t)+'"'),s+=">"+r+"</a>"}image({href:e,title:t,text:r,tokens:n}){n&&(r=this.parser.parseInline(n,this.parser.textRenderer));n=F(e);if(null===n)return T(r);let s=`<img src="${e=n}" alt="${r}"`;return t&&(s+=` title="${T(t)}"`),s+=">"}text(e){return"tokens"in e&&e.tokens?this.parser.parseInline(e.tokens):"escaped"in e&&e.escaped?e.text:T(e.text)}},P=class{strong({text:e}){return e}em({text:e}){return e}codespan({text:e}){return e}del({text:e}){return e}html({text:e}){return e}text({text:e}){return e}link({text:e}){return""+e}image({text:e}){return""+e}br(){return""}},I=class le{options;renderer;textRenderer;constructor(e){this.options=e||l,this.options.renderer=this.options.renderer||new _,this.renderer=this.options.renderer,this.renderer.options=this.options,(this.renderer.parser=this).textRenderer=new P}static parse(e,t){return new le(t).parse(e)}static parseInline(e,t){return new le(t).parseInline(e)}parse(n,s=!0){let i="";for(let r=0;r<n.length;r++){var e=n[r];if(this.options.extensions?.renderers?.[e.type]){var t=e,l=this.options.extensions.renderers[t.type].call({parser:this},t);if(!1!==l||!["space","hr","heading","code","table","blockquote","list","html","def","paragraph","text"].includes(t.type)){i+=l||"";continue}}var a=e;switch(a.type){case"space":i+=this.renderer.space(a);continue;case"hr":i+=this.renderer.hr(a);continue;case"heading":i+=this.renderer.heading(a);continue;case"code":i+=this.renderer.code(a);continue;case"table":i+=this.renderer.table(a);continue;case"blockquote":i+=this.renderer.blockquote(a);continue;case"list":i+=this.renderer.list(a);continue;case"html":i+=this.renderer.html(a);continue;case"def":i+=this.renderer.def(a);continue;case"paragraph":i+=this.renderer.paragraph(a);continue;case"text":{let e=a,t=this.renderer.text(e);for(;r+1<n.length&&"text"===n[r+1].type;)e=n[++r],t+=`
|
||||
`+this.renderer.text(e);i+=s?this.renderer.paragraph({type:"paragraph",raw:t,text:t,tokens:[{type:"text",raw:t,text:t,escaped:!0}]}):t;continue}default:var o='Token with "'+a.type+'" type was not found.';if(this.options.silent)return console.error(o),"";throw new Error(o)}}return i}parseInline(t,r=this.renderer){let n="";for(let e=0;e<t.length;e++){var s=t[e];if(this.options.extensions?.renderers?.[s.type]){var i=this.options.extensions.renderers[s.type].call({parser:this},s);if(!1!==i||!["escape","html","link","image","strong","em","codespan","br","del","text"].includes(s.type)){n+=i||"";continue}}var l=s;switch(l.type){case"escape":n+=r.text(l);break;case"html":n+=r.html(l);break;case"link":n+=r.link(l);break;case"image":n+=r.image(l);break;case"strong":n+=r.strong(l);break;case"em":n+=r.em(l);break;case"codespan":n+=r.codespan(l);break;case"br":n+=r.br(l);break;case"del":n+=r.del(l);break;case"text":n+=r.text(l);break;default:var a='Token with "'+l.type+'" type was not found.';if(this.options.silent)return console.error(a),"";throw new Error(a)}}return n}},L=class{options;block;constructor(e){this.options=e||l}static passThroughHooks=new Set(["preprocess","postprocess","processAllTokens","emStrongMask"]);static passThroughHooksRespectAsync=new Set(["preprocess","postprocess","processAllTokens"]);preprocess(e){return e}postprocess(e){return e}processAllTokens(e){return e}emStrongMask(e){return e}provideLexer(){return this.block?A.lex:A.lexInline}provideParser(){return this.block?I.parse:I.parseInline}},K=class{defaults=s();options=this.setOptions;parse=this.parseMarkdown(!0);parseInline=this.parseMarkdown(!1);Parser=I;Renderer=_;TextRenderer=P;Lexer=A;Tokenizer=v;Hooks=L;constructor(...e){this.use(...e)}walkTokens(e,r){let n=[];for(var s of e)switch(n=n.concat(r.call(this,s)),s.type){case"table":var t,i,l=s;for(t of l.header)n=n.concat(this.walkTokens(t.tokens,r));for(i of l.rows)for(var a of i)n=n.concat(this.walkTokens(a.tokens,r));break;case"list":l=s;n=n.concat(this.walkTokens(l.items,r));break;default:{let t=s;this.defaults.extensions?.childTokens?.[t.type]?this.defaults.extensions.childTokens[t.type].forEach(e=>{e=t[e].flat(1/0);n=n.concat(this.walkTokens(e,r))}):t.tokens&&(n=n.concat(this.walkTokens(t.tokens,r)))}}return n}use(...e){let s=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach(i=>{let e={...i};if(e.async=this.defaults.async||e.async||!1,i.extensions&&(i.extensions.forEach(n=>{if(!n.name)throw new Error("extension name required");if("renderer"in n){let r=s.renderers[n.name];r?s.renderers[n.name]=function(...e){let t=n.renderer.apply(this,e);return t=!1===t?r.apply(this,e):t}:s.renderers[n.name]=n.renderer}if("tokenizer"in n){if(!n.level||"block"!==n.level&&"inline"!==n.level)throw new Error("extension level must be 'block' or 'inline'");let e=s[n.level];e?e.unshift(n.tokenizer):s[n.level]=[n.tokenizer],n.start&&("block"===n.level?s.startBlock?s.startBlock.push(n.start):s.startBlock=[n.start]:"inline"===n.level&&(s.startInline?s.startInline.push(n.start):s.startInline=[n.start]))}"childTokens"in n&&n.childTokens&&(s.childTokens[n.name]=n.childTokens)}),e.extensions=s),i.renderer){let s=this.defaults.renderer||new _(this.defaults);for(var t in i.renderer){if(!(t in s))throw new Error(`renderer '${t}' does not exist`);if(!["options","parser"].includes(t)){let e=t,r=i.renderer[e],n=s[e];s[e]=(...e)=>{let t=r.apply(s,e);return(t=!1===t?n.apply(s,e):t)||""}}}e.renderer=s}if(i.tokenizer){let s=this.defaults.tokenizer||new v(this.defaults);for(var l in i.tokenizer){if(!(l in s))throw new Error(`tokenizer '${l}' does not exist`);if(!["options","rules","lexer"].includes(l)){let e=l,r=i.tokenizer[e],n=s[e];s[e]=(...e)=>{let t=r.apply(s,e);return t=!1===t?n.apply(s,e):t}}}e.tokenizer=s}if(i.hooks){let s=this.defaults.hooks||new L;for(let t in i.hooks){if(!(t in s))throw new Error(`hook '${t}' does not exist`);if(!["options","block"].includes(t)){let e=t,r=i.hooks[e],n=s[e];L.passThroughHooks.has(t)?s[e]=e=>{if(this.defaults.async&&L.passThroughHooksRespectAsync.has(t))return Promise.resolve(r.call(s,e)).then(e=>n.call(s,e));e=r.call(s,e);return n.call(s,e)}:s[e]=(...e)=>{let t=r.apply(s,e);return t=!1===t?n.apply(s,e):t}}}e.hooks=s}if(i.walkTokens){let r=this.defaults.walkTokens,n=i.walkTokens;e.walkTokens=function(e){let t=[];return t.push(n.call(this,e)),t=r?t.concat(r.call(this,e)):t}}this.defaults={...this.defaults,...e}}),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return A.lex(e,t??this.defaults)}parser(e,t){return I.parse(e,t??this.defaults)}parseMarkdown(a){return(r,e)=>{let t={...e},n={...this.defaults,...t},s=this.onError(!!n.silent,!!n.async);if(!0===this.defaults.async&&!1===t.async)return s(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if("u"<typeof r||null===r)return s(new Error("marked(): input parameter is undefined or null"));i
Line truncated
|
||||
Please report this to https://github.com/markedjs/marked.`,r)return t="<p>An error occurred:</p><pre>"+T(e.message+"",!0)+"</pre>",n?Promise.resolve(t):t;if(n)return Promise.reject(e);throw e}}},B=new K;function C(e,t){return B.parse(e,t)}C.options=C.setOptions=function(e){return B.setOptions(e),N(C.defaults=B.defaults),C},C.getDefaults=s,C.defaults=l,C.use=function(...e){return B.use(...e),N(C.defaults=B.defaults),C},C.walkTokens=function(e,t){return B.walkTokens(e,t)},C.parseInline=B.parseInline,C.Parser=I,C.parser=I.parse,C.Renderer=_,C.TextRenderer=P,C.Lexer=A,C.lexer=A.lex,C.Tokenizer=v,C.Hooks=L;var V=(C.parse=C).options,Y=C.setOptions,ee=C.use,te=C.walkTokens,re=C.parseInline,ne=C,se=I.parse,ie=A.lex;return E!=t&&(r.exports=t),r.exports});
|
||||
@@ -0,0 +1,3 @@
|
||||
/*! @license DOMPurify 3.2.7 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.7/LICENSE */
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).DOMPurify=t()}(this,(function(){"use strict";const{entries:e,setPrototypeOf:t,isFrozen:n,getPrototypeOf:o,getOwnPropertyDescriptor:r}=Object;let{freeze:i,seal:a,create:l}=Object,{apply:c,construct:s}="undefined"!=typeof Reflect&&Reflect;i||(i=function(e){return e}),a||(a=function(e){return e}),c||(c=function(e,t){for(var n=arguments.length,o=new Array(n>2?n-2:0),r=2;r<n;r++)o[r-2]=arguments[r];return e.apply(t,o)}),s||(s=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),o=1;o<t;o++)n[o-1]=arguments[o];return new e(...n)});const u=w(Array.prototype.forEach),m=w(Array.prototype.lastIndexOf),p=w(Array.prototype.pop),f=w(Array.prototype.push),d=w(Array.prototype.splice),h=w(String.prototype.toLowerCase),g=w(String.prototype.toString),T=w(String.prototype.match),y=w(String.prototype.replace),E=w(String.prototype.indexOf),A=w(String.prototype.trim),_=w(Object.prototype.hasOwnProperty),S=w(RegExp.prototype.test),b=(N=TypeError,function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return s(N,t)});var N;function w(e){return function(t){t instanceof RegExp&&(t.lastIndex=0);for(var n=arguments.length,o=new Array(n>1?n-1:0),r=1;r<n;r++)o[r-1]=arguments[r];return c(e,t,o)}}function R(e,o){let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:h;t&&t(e,null);let i=o.length;for(;i--;){let t=o[i];if("string"==typeof t){const e=r(t);e!==t&&(n(o)||(o[i]=e),t=e)}e[t]=!0}return e}function O(e){for(let t=0;t<e.length;t++){_(e,t)||(e[t]=null)}return e}function v(t){const n=l(null);for(const[o,r]of e(t)){_(t,o)&&(Array.isArray(r)?n[o]=O(r):r&&"object"==typeof r&&r.constructor===Object?n[o]=v(r):n[o]=r)}return n}function D(e,t){for(;null!==e;){const n=r(e,t);if(n){if(n.get)return w(n.get);if("function"==typeof n.value)return w(n.value)}e=o(e)}return function(){return null}}const x=i(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","search","section","select","shadow","slot","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),L=i(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","enterkeyhint","exportparts","filter","font","g","glyph","glyphref","hkern","image","inputmode","line","lineargradient","marker","mask","metadata","mpath","part","path","pattern","polygon","polyline","radialgradient","rect","slot","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),C=i(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),k=i(["animate","color-profile","cursor","discard","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignobject","hatch","hatchpath","mesh","meshgradient","meshpatch","meshrow","missing-glyph","script","set","solidcolor","unknown","use"]),I=i(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover","mprescripts"]),M=i(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),U=i(["#text"]),z=i(["accept","action","align","alt","autocapitalize","autocomplete","autopictureinpicture","autoplay","background","bgcolor","border","capture","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","controls","controlslist","coords","crossorigin","datetime","decoding","default","dir","disabled","disablepictureinpicture","disableremoteplayback","download","draggable","enctype","enterkeyhint","exportparts","face","for","headers","height","hidden","high","href","hreflang","id","inert","iLine truncated
|
||||
//# sourceMappingURL=purify.min.js.map
|
||||
@@ -113,6 +113,7 @@ table {
|
||||
|
||||
th {
|
||||
border-bottom: 1px solid #000;
|
||||
padding: 3px;
|
||||
}
|
||||
th, td {
|
||||
padding: 2px;
|
||||
@@ -206,3 +207,148 @@ footer {
|
||||
menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.left {
|
||||
display: block;
|
||||
float: left;
|
||||
margin:5px;
|
||||
max-width: 30%;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: block;
|
||||
float: right;
|
||||
margin: 5px;
|
||||
max-width: 30%;
|
||||
}
|
||||
|
||||
.center {
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.box {
|
||||
border-radius: 5px;
|
||||
border: 1px solid black;
|
||||
background: #DEDEDE;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.striped > table {
|
||||
background: #FAFAFA;
|
||||
}
|
||||
.striped > table tr:nth-child(even) {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.equal-widths > table {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.layout > table {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
.layout > table > thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
table td table {
|
||||
max-width:95%;
|
||||
}
|
||||
|
||||
pre {
|
||||
border: 1px dashed black;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
background: #EEE;
|
||||
}
|
||||
|
||||
code {
|
||||
display: inline-block;
|
||||
border: 1px dashed black;
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
background: #EEE;
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
div.macro {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div[data-macro-name="toc"] {
|
||||
display: inline;
|
||||
float: left;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
margin-right: 2em;
|
||||
margin-bottom: 2em;
|
||||
border: 1px solid #000;
|
||||
padding: 0ch 2ch;
|
||||
}
|
||||
|
||||
div[data-macro-name="toc"] ul {
|
||||
box-sizing: border-box;
|
||||
list-style: none;
|
||||
padding-left: 2ch;
|
||||
font-weight: normal;
|
||||
margin-left: 0px;
|
||||
|
||||
}
|
||||
|
||||
div[data-macro-name="toc"] > ul:first-child {
|
||||
padding-left: 0ch;
|
||||
margin-left: 0px;
|
||||
}
|
||||
div[data-macro-name="toc"] > ul:first-child > li {
|
||||
padding-left: 0ch;
|
||||
margin-left: 0px;
|
||||
}
|
||||
div[data-macro-name="toc"] > ul:first-child > li {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div[data-macro-name="toc"] > ul > li {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div[data-macro-name="toc"] > ul > li {
|
||||
padding-left: 2ch;
|
||||
}
|
||||
|
||||
div[data-macro-name="toc"] a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
div[data-macro-name="toc"] a:hover {
|
||||
background: #CCC;
|
||||
}
|
||||
|
||||
|
||||
#content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#content.loaded {
|
||||
display: block;
|
||||
}
|
||||
#content.wysiwyg {
|
||||
display: none,
|
||||
}
|
||||
#content.view {
|
||||
}
|
||||
|
||||
#content.edit {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
#content.wysiwyg {
|
||||
}
|
||||
|
||||
#content.wysiwyg .md {
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
APIv1 = {
|
||||
get: function(doc_id, callback) {
|
||||
(async () => {
|
||||
const raw = await fetch('/_/v1/get/' + doc_id, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const res = await raw.json();
|
||||
if (res['code'] != 200) {
|
||||
console.error("APIv1 error: ", res)
|
||||
}
|
||||
callback(res);
|
||||
})();
|
||||
},
|
||||
|
||||
put: function(data, callback) {
|
||||
(async () => {
|
||||
const raw = await fetch('/_/v1/put/' + window.location.pathname, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
'body': data
|
||||
}),
|
||||
});
|
||||
const res = await raw.json();
|
||||
if (res['code'] != 200) {
|
||||
console.error("APIv1 error: ", res)
|
||||
}
|
||||
callback(res);
|
||||
})();
|
||||
},
|
||||
|
||||
search: function(space, query, callback) {
|
||||
(async () => {
|
||||
const raw = await fetch('/_/v1/search/' + space, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
'body': query
|
||||
}),
|
||||
});
|
||||
const res = await raw.json();
|
||||
if (res['code'] != 200) {
|
||||
console.error("APIv1 error: ", res)
|
||||
}
|
||||
callback(res);
|
||||
})();
|
||||
},
|
||||
};
|
||||
|
||||
const WIDGETS = {};
|
||||
|
||||
function parseWidgetSource(html) {
|
||||
|
||||
function block(prefix) {
|
||||
return RegExp('##\\s*' + prefix + '.*?```\\w*(.+?)```', 'gims');
|
||||
};
|
||||
|
||||
const template = block("Template").exec(html)[1];
|
||||
const css = block("CSS").exec(html)[1];
|
||||
const processor = block("Processor").exec(html)[1];
|
||||
|
||||
var func;
|
||||
eval("func = " + processor);
|
||||
|
||||
return {
|
||||
template: template,
|
||||
css: css,
|
||||
processor: func
|
||||
};
|
||||
}
|
||||
|
||||
async function processWidgets(html, callback) {
|
||||
|
||||
var widgetPattern = /({{(.+)}})/gm;
|
||||
|
||||
if (!html.match(widgetPattern)) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
html.matchAll(widgetPattern).forEach(match => {
|
||||
var widgetTag = match[1];
|
||||
var widgetName = match[2];
|
||||
if (Object.values(WIDGETS).indexOf(widgetName) == -1) {
|
||||
APIv1.search("Widget", widgetName, (res) => {
|
||||
if (res.code == 200) {
|
||||
var parts = parseWidgetSource(res.response[0].body);
|
||||
WIDGETS[widgetName] = parts.processor;
|
||||
contents = WIDGETS[widgetName](widgetTag, parts.template, parts.css);
|
||||
} else {
|
||||
contents = `Invalid widget: ${widgetName}`;
|
||||
}
|
||||
var rep = `<span class="widget-${widgetName}" data-source="${widgetTag}">${contents}</span>`;
|
||||
html = html.replaceAll(widgetTag, rep);
|
||||
if (parts) {
|
||||
html = `<style type='text/css'>${parts.css}</style>${html}`;
|
||||
}
|
||||
callback(html);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* @toast-ui/editor
|
||||
* @version 3.2.2 | Fri Feb 17 2023
|
||||
* @author NHN Cloud FE Development Lab <dl_javascript@nhn.com>
|
||||
* @license MIT
|
||||
*/.ProseMirror{overflow-X:hidden;color:#222;font-family:Open Sans,Helvetica Neue,Helvetica,Arial,나눔바른고딕,Nanum Barun Gothic,맑은고딕,Malgun Gothic,sans-serif;font-size:13px;height:calc(100% - 36px);overflow-y:auto}.ProseMirror .placeholder{color:#999}.ProseMirror-selectednode,.ProseMirror:focus{outline:none}.html-block.ProseMirror-selectednode,table.ProseMirror-selectednode{border-radius:2px;outline:2px solid #00a9ff}.toastui-editor-contents{font-family:Open Sans,Helvetica Neue,Helvetica,Arial,나눔바른고딕,Nanum Barun Gothic,맑은고딕,Malgun Gothic,sans-serif;font-size:13px;margin:0;padding:0;z-index:20}.toastui-editor-contents :not(table){box-sizing:content-box;line-height:160%}.toastui-editor-contents address,.toastui-editor-contents cite,.toastui-editor-contents dfn,.toastui-editor-contents em,.toastui-editor-contents i,.toastui-editor-contents var{font-style:italic}.toastui-editor-contents strong{font-weight:700}.toastui-editor-contents p{color:#222;margin:10px 0}.toastui-editor-contents>div>div:first-of-type h1,.toastui-editor-contents>h1:first-of-type{margin-top:14px}.toastui-editor-contents h1,.toastui-editor-contents h2,.toastui-editor-contents h3,.toastui-editor-contents h4,.toastui-editor-contents h5,.toastui-editor-contents h6{color:#222;font-weight:700}.toastui-editor-contents h1{border-bottom:3px double #999;font-size:24px;line-height:28px;margin:52px 0 15px;padding-bottom:7px}.toastui-editor-contents h2{border-bottom:1px solid #dbdbdb;font-size:22px;line-height:23px;margin:20px 0 13px;padding-bottom:7px}.toastui-editor-contents h3{font-size:20px;margin:18px 0 2px}.toastui-editor-contents h4{font-size:18px;margin:10px 0 2px}.toastui-editor-contents h3,.toastui-editor-contents h4{line-height:18px}.toastui-editor-contents h5{font-size:16px}.toastui-editor-contents h6{font-size:14px}.toastui-editor-contents h5,.toastui-editor-contents h6{line-height:17px;margin:9px 0 -4px}.toastui-editor-contents del{color:#999}.toastui-editor-contents blockquote{border-left:4px solid #e5e5e5;color:#999;margin:14px 0;padding:0 16px}.toastui-editor-contents blockquote ol,.toastui-editor-contents blockquote p,.toastui-editor-contents blockquote ul{color:#999}.toastui-editor-contents blockquote>:first-child{margin-top:0}.toastui-editor-contents blockquote>:last-child{margin-bottom:0}.toastui-editor-contents code,.toastui-editor-contents pre{border:0;border-radius:0;font-family:Consolas,Courier,Apple SD 산돌고딕 Neo,-apple-system,Lucida Grande,Apple SD Gothic Neo,맑은 고딕,Malgun Gothic,Segoe UI,돋움,dotum,sans-serif}.toastui-editor-contents pre{background-color:#f4f7f8;margin:2px 0 8px;padding:18px}.toastui-editor-contents code{background-color:#f9f2f4;border-radius:2px;color:#c1798b;letter-spacing:-.3px;padding:2px 3px}.toastui-editor-contents pre code{background-color:transparent;color:inherit;padding:0;white-space:pre-wrap}.toastui-editor-contents img{box-sizing:border-box;margin:4px 0 10px;max-width:100%;vertical-align:top}.toastui-editor-contents table{border:1px solid rgba(0,0,0,.1);border-collapse:collapse;box-sizing:border-box;color:#222;margin:12px 0 14px;width:auto}.toastui-editor-contents table td,.toastui-editor-contents table th{border:1px solid rgba(0,0,0,.1);height:32px;padding:5px 14px 5px 12px}.toastui-editor-contents table th{background-color:#555;color:#fff;font-weight:300;padding-top:6px}.toastui-editor-contents th p{color:#fff;margin:0}.toastui-editor-contents td p{margin:0;padding:0 2px}.toastui-editor-contents td.toastui-editor-cell-selected{background-color:#d8dfec}.toastui-editor-contents th.toastui-editor-cell-selected{background-color:#908f8f}.toastui-editor-contents dir,.toastui-editor-contents menu,.toastui-editor-contents ol,.toastui-editor-contents ul{color:#222;display:block;list-style-type:none;margin:6px 0 10px;padding-left:24px}.toastui-editor-contents ol{counter-reset:li;list-style-type:none}.toastui-editor-contents ol>li{counter-increment:li}.toastui-editor-contents ol>li:before,.toastui-editor-contents ul>li:before{display:inline-block;position:absolute}.toastui-editor-contents ul>li:before{background-color:#ccc;border-radius:50%;content:"";height:5px;margin-left:-17px;margin-top:6px;width:5px}.toastui-editor-contents ol>li:before{color:#aaa;content:"." counter(li);direction:rtl;margin-left:-28px;text-align:right;width:24px}.toastui-editor-contents ol ol,.toastui-editor-contents ol ul,.toastui-editor-contents ul ol,.toastui-editor-contents ul ul{margin-bottom:0!important;margin-top:0!important}.toastui-editor-contents ol li,.toastui-editor-contents ul li{position:relative}.toastui-editor-contents ol p,.toastui-editor-contents ul p{margin:0}.toastui-editor-contents hr{border-top:1px solid #eee;margin:16px 0}.toastui-editor-contents a{color:#4b96e6;text-decoration:underline}.toastui-editor-contents a:hover{color:#1f70de}.toastui-editor-contents .image-link{position:relative}.toastui-editor-contents .image-link:hover:before{background:#fff url(datLine truncated
|
||||
@@ -1,24 +0,0 @@
|
||||
/*!
|
||||
* @toast-ui/editor
|
||||
* @version 3.2.2 | Fri Feb 17 2023
|
||||
* @author NHN Cloud FE Development Lab <dl_javascript@nhn.com>
|
||||
* @license MIT
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("prosemirror-inputrules"),require("prosemirror-keymap"),require("prosemirror-model"),require("prosemirror-state"),require("prosemirror-view")):"function"==typeof define&&define.amd?define(["prosemirror-inputrules","prosemirror-keymap","prosemirror-model","prosemirror-state","prosemirror-view"],t):"object"==typeof exports?exports.toastui=t(require("prosemirror-inputrules"),require("prosemirror-keymap"),require("prosemirror-model"),require("prosemirror-state"),require("prosemirror-view")):(e.toastui=e.toastui||{},e.toastui.Editor=t(e[void 0],e[void 0],e[void 0],e[void 0],e[void 0]))}(self,(function(e,t,r,n,i){return function(){var o={368:function(e){
|
||||
/*! @license DOMPurify 2.3.3 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.3/LICENSE */
|
||||
e.exports=function(){"use strict";function e(e){if(Array.isArray(e)){for(var t=0,r=Array(e.length);t<e.length;t++)r[t]=e[t];return r}return Array.from(e)}var t=Object.hasOwnProperty,r=Object.setPrototypeOf,n=Object.isFrozen,i=Object.getPrototypeOf,o=Object.getOwnPropertyDescriptor,a=Object.freeze,s=Object.seal,l=Object.create,u="undefined"!=typeof Reflect&&Reflect,c=u.apply,p=u.construct;c||(c=function(e,t,r){return e.apply(t,r)}),a||(a=function(e){return e}),s||(s=function(e){return e}),p||(p=function(t,r){return new(Function.prototype.bind.apply(t,[null].concat(e(r))))});var f=k(Array.prototype.forEach),d=k(Array.prototype.pop),h=k(Array.prototype.push),g=k(String.prototype.toLowerCase),m=k(String.prototype.match),v=k(String.prototype.replace),y=k(String.prototype.indexOf),b=k(String.prototype.trim),w=k(RegExp.prototype.test),x=C(TypeError);function k(e){return function(t){for(var r=arguments.length,n=Array(r>1?r-1:0),i=1;i<r;i++)n[i-1]=arguments[i];return c(e,t,n)}}function C(e){return function(){for(var t=arguments.length,r=Array(t),n=0;n<t;n++)r[n]=arguments[n];return p(e,r)}}function L(e,t){r&&r(e,null);for(var i=t.length;i--;){var o=t[i];if("string"==typeof o){var a=g(o);a!==o&&(n(t)||(t[i]=a),o=a)}e[o]=!0}return e}function T(e){var r=l(null),n=void 0;for(n in e)c(t,e,[n])&&(r[n]=e[n]);return r}function N(e,t){for(;null!==e;){var r=o(e,t);if(r){if(r.get)return k(r.get);if("function"==typeof r.value)return k(r.value)}e=i(e)}function n(e){return console.warn("fallback value for",e),null}return n}var D=a(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),A=a(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","filter","font","g","glyph","glyphref","hkern","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),E=a(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),S=a(["animate","color-profile","cursor","discard","fedropshadow","feimage","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignobject","hatch","hatchpath","mesh","meshgradient","meshpatch","meshrow","missing-glyph","script","set","solidcolor","unknown","use"]),M=a(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover"]),R=a(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),O=a(["#text"]),q=a(["accept","action","align","alt","autocapitalize","autocomplete","autopictureinpicture","autoplay","background","bgcolor","border","capture","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","controls","controlslist","coords","crossorigin","datetime","decoding","default","dir","disabled","disablepictureinpicture","disableremoteplayback","download","draggable","enctype","enterkeyhint","face","for","headers","height","hidden","high","href","hreflang","id","inputmode","integrity","ismap","kind","label","lang","list","loading","loop","low","max","maxlength","media","method","min","minlength","multiple","muted","name","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","playsinline","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","span","srclang","start","src","srcset","step","style","summary","tabindex","title","translate","type","usemap","valign","value","width","xmlns","slot"]),F=a(["accent-height","accumulatLine truncated
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
***************************************************************************** */
|
||||
var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])},r(e,t)};function n(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var i=function(){return i=Object.assign||function(e){for(var t,r=1,n=arguments.length;r<n;r++)for(var i in t=arguments[r])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e},i.apply(this,arguments)};function o(e,t,r){if(r||2===arguments.length)for(var n,i=0,o=t.length;i<o;i++)!n&&i in t||(n||(n=Array.prototype.slice.call(t,0,i)),n[i]=t[i]);return e.concat(n||Array.prototype.slice.call(t))}var a="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:void 0!==s.g?s.g:"undefined"!=typeof self?self:{},u={};function c(e,t,r){var n,i,o,a,s,l="";for("string"!=typeof t&&(r=t,t=c.defaultChars),void 0===r&&(r=!0),s=function(e){var t,r,n=u[e];if(n)return n;for(n=u[e]=[],t=0;t<128;t++)r=String.fromCharCode(t),/^[0-9a-z]$/i.test(r)?n.push(r):n.push("%"+("0"+t.toString(16).toUpperCase()).slice(-2));for(t=0;t<e.length;t++)n[e.charCodeAt(t)]=e[t];return n}(t),n=0,i=e.length;n<i;n++)if(o=e.charCodeAt(n),r&&37===o&&n+2<i&&/^[0-9a-f]{2}$/i.test(e.slice(n+1,n+3)))l+=e.slice(n,n+3),n+=2;else if(o<128)l+=s[o];else if(o>=55296&&o<=57343){if(o>=55296&&o<=56319&&n+1<i&&(a=e.charCodeAt(n+1))>=56320&&a<=57343){l+=encodeURIComponent(e[n]+e[n+1]),n++;continue}l+="%EF%BF%BD"}else l+=encodeURIComponent(e[n]);return l}c.defaultChars=";/?:@&=+$,-_.!~*'()#",c.componentChars="-_.!~*'()";var p=c,f={},d={},h={Aacute:"Á",aacute:"á",Abreve:"Ă",abreve:"ă",ac:"∾",acd:"∿",acE:"∾̳",Acirc:"Â",acirc:"â",acute:"´",Acy:"А",acy:"а",AElig:"Æ",aelig:"æ",af:"",Afr:"𝔄",afr:"𝔞",Agrave:"À",agrave:"à",alefsym:"ℵ",aleph:"ℵ",Alpha:"Α",alpha:"α",Amacr:"Ā",amacr:"ā",amalg:"⨿",amp:"&",AMP:"&",andand:"⩕",And:"⩓",and:"∧",andd:"⩜",andslope:"⩘",andv:"⩚",ang:"∠",ange:"⦤",angle:"∠",angmsdaa:"⦨",angmsdab:"⦩",angmsdac:"⦪",angmsdad:"⦫",angmsdae:"⦬",angmsdaf:"⦭",angmsdag:"⦮",angmsdah:"⦯",angmsd:"∡",angrt:"∟",angrtvb:"⊾",angrtvbd:"⦝",angsph:"∢",angst:"Å",angzarr:"⍼",Aogon:"Ą",aogon:"ą",Aopf:"𝔸",aopf:"𝕒",apacir:"⩯",ap:"≈",apE:"⩰",ape:"≊",apid:"≋",apos:"'",ApplyFunction:"",approx:"≈",approxeq:"≊",Aring:"Å",aring:"å",Ascr:"𝒜",ascr:"𝒶",Assign:"≔",ast:"*",asymp:"≈",asympeq:"≍",Atilde:"Ã",atilde:"ã",Auml:"Ä",auml:"ä",awconint:"∳",awint:"⨑",backcong:"≌",backepsilon:"϶",backprime:"‵",backsim:"∽",backsimeq:"⋍",Backslash:"∖",Barv:"⫧",barvee:"⊽",barwed:"⌅",Barwed:"⌆",barwedge:"⌅",bbrk:"⎵",bbrktbrk:"⎶",bcong:"≌",Bcy:"Б",bcy:"б",bdquo:"„",becaus:"∵",because:"∵",Because:"∵",bemptyv:"⦰",bepsi:"϶",bernou:"ℬ",Bernoullis:"ℬ",Beta:"Β",beta:"β",beth:"ℶ",between:"≬",Bfr:"𝔅",bfr:"𝔟",bigcap:"⋂",bigcirc:"◯",bigcup:"⋃",bigodot:"⨀",bigoplus:"⨁",bigotimes:"⨂",bigsqcup:"⨆",bigstar:"★",bigtriangledown:"▽",bigtriangleup:"△",biguplus:"⨄",bigvee:"⋁",bigwedge:"⋀",bkarow:"⤍",blacklozenge:"⧫",blacksquare:"▪",blacktriangle:"▴",blacktriangledown:"▾",blacktriangleleft:"◂",blacktriangleright:"▸",blank:"␣",blk12:"▒",blk14:"░",blk34:"▓",block:"█",bne:"=⃥",bnequiv:"≡⃥",bNot:"⫭",bnot:"⌐",Bopf:"𝔹",bopf:"𝕓",bot:"⊥",bottom:"⊥",bowtie:"⋈",boxbox:"⧉",boxdl:"┐",boxdL:"╕",boxDl:"╖",boxDL:"╗",boxdr:"┌",boxdR:"╒",boxDr:"╓",boxDR:"╔",boxh:"─",boxH:"═",boxhd:"┬",boxHd:"╤",boxhD:"╥",boxHD:"╦",boxhu:"┴",boxHu:"╧",boxhU:"╨",boxHU:"╩",boxminus:"⊟",boxplus:"⊞",boxtimes:"⊠",boxul:"┘",boxuL:"╛",boxUl:"╜",boxUL:"╝",boxur:"└",boxuR:"╘",boxUr:"╙",boxUR:"╚",boxv:"│",boxV:"║",boxvh:"┼",boxvH:"╪",boxVh:"╫",boxVH:"╬",boxvl:"┤",boxvL:"╡",boxVl:"╢",boxVL:"╣",boxvr:"├",boxvR:"╞",boxVr:"╟",boxVR:"╠",bprime:"‵",breve:"˘",Breve:"˘",brvbar:"¦",bscr:"𝒷",Bscr:"ℬ",bsemi:"⁏",bsim:"∽",bsime:"⋍",bsolb:"⧅",bsol:"\\",bsolhsub:"⟈",bull:"•",bullet:"•",bump:"≎",bumpE:"⪮",bumpe:"≏",Bumpeq:"≎",bumpeq:"≏",Cacute:"Ć",cacute:"ć",capand:"⩄",capbrcup:"⩉",capcap:"⩋",cap:"∩",Cap:"⋒",capcup:"⩇",capdot:"⩀",CapitalDifferentialD:"ⅅ",caps:"∩︀",caret:"⁁",caron:"ˇ",Cayleys:"ℭ",ccaps:"⩍",Ccaron:"Č",ccaron:"č",Ccedil:"Ç",ccedil:"ç",Ccirc:"Ĉ",ccirc:"ĉ",Cconint:"∰",ccups:"⩌",ccupssm:"⩐",Cdot:"Ċ",cdot:"ċ",cedil:"¸",Cedilla:"¸",cemptyv:"⦲",cent:"¢",centerdot:"·",CenterDot:"·",cfr:"𝔠",Cfr:"ℭ",CHcy:"Ч",chcy:"ч",check:"✓",checkmark:"✓",Chi:"Χ",chi:"χ",circ:"ˆ",circeq:"≗",circlearrowleft:"↺",circlearrowright:"↻",circledast:"⊛",circledcirc:"⊚",circleddash:"⊝",Line truncated
|
||||
@@ -1,9 +0,0 @@
|
||||
@import 'toastui-editor-viewer.min.css';
|
||||
|
||||
#viewer {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.toastui-editor-contents {
|
||||
font-size: var(--default-font-size);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
var viewer = new toastui.Editor({
|
||||
viewer: true,
|
||||
el: document.querySelector("#viewer"),
|
||||
usageStatistics: false,
|
||||
});
|
||||
viewer.setMarkdown(document.getElementById("data_form__body").value);
|
||||
@@ -123,10 +123,13 @@ def view(table, path):
|
||||
clean_table = re.sub(r"[^a-zA-Z0-9]", "", unquote(table))
|
||||
clean_path = re.sub(r"[^a-zA-Z0-9]", "", unquote(path))
|
||||
if clean_table != table or clean_path != clean_path:
|
||||
app.log.warning(f"Invalid table/path: {table=}, {path=}. Redirecting to {clean_table}/{clean_path}")
|
||||
return redirect(url_for("view", table=clean_table, path=clean_path), 302)
|
||||
|
||||
app.log.debug(f"Looking for {table=}, {path=}")
|
||||
page, error = get_page(request.path, table=table, create_okay=True)
|
||||
if error:
|
||||
app.log.error(error)
|
||||
g.messages.append(str(error))
|
||||
return rendered(page)
|
||||
|
||||
@@ -150,6 +153,7 @@ def put(table, path):
|
||||
if parent:
|
||||
parent.update(members=list(set(parent.members + [updated])))
|
||||
app.db.save(parent)
|
||||
app.log.debug(f"Saved page at uri {updated.uri}")
|
||||
return api_response(response=dict(updated))
|
||||
|
||||
|
||||
|
||||
+3
-4
@@ -1,3 +1,4 @@
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import pytest
|
||||
@@ -12,7 +13,7 @@ from ttfrog import schema
|
||||
@pytest.fixture
|
||||
def app():
|
||||
with TemporaryDirectory() as path:
|
||||
fixture_db = GrungDB.with_schema(schema, path=path, storage=MemoryStorage)
|
||||
fixture_db = GrungDB.with_schema(schema, path=Path(path), storage=MemoryStorage)
|
||||
ttfrog.app.load_config(defaults=None, IN_MEMORY_DB=1)
|
||||
ttfrog.app.initialize(db=fixture_db, force=True)
|
||||
yield ttfrog.app
|
||||
@@ -21,8 +22,6 @@ def app():
|
||||
|
||||
def test_create(app):
|
||||
user = schema.User(name="john", email="john@foo", password="powerfulCat")
|
||||
assert user.uid
|
||||
assert user._metadata.fields["uid"].unique
|
||||
|
||||
# insert
|
||||
john_something = app.db.save(user)
|
||||
@@ -32,7 +31,6 @@ def test_create(app):
|
||||
assert app.db.User.get(doc_id=last_insert_id) == john_something
|
||||
assert john_something.name == user.name
|
||||
assert john_something.email == user.email
|
||||
assert john_something.uid == user.uid
|
||||
|
||||
# update
|
||||
john_something.name = "james?"
|
||||
@@ -42,6 +40,7 @@ def test_create(app):
|
||||
assert before_update != after_update
|
||||
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_permissions(app):
|
||||
john = app.db.save(schema.User(name="john", email="john@foo", password="powerfulCat"))
|
||||
players = app.db.save(schema.Group(name="players", members=[john]))
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import pytest
|
||||
from grung.db import GrungDB
|
||||
from tinydb.storages import MemoryStorage
|
||||
|
||||
import ttfrog.app
|
||||
from ttfrog import schema
|
||||
from ttfrog.bootstrap import bootstrap
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
with TemporaryDirectory() as path:
|
||||
fixture_db = GrungDB.with_schema(schema, path=Path(path), storage=MemoryStorage)
|
||||
ttfrog.app.load_config(defaults=None, IN_MEMORY_DB=1)
|
||||
ttfrog.app.initialize(db=fixture_db, force=True)
|
||||
ttfrog.app.web.config.update({"TESTING": True})
|
||||
bootstrap()
|
||||
yield ttfrog.app
|
||||
ttfrog.app.db.truncate()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def routes(app):
|
||||
import ttfrog.web
|
||||
|
||||
return ttfrog.web
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client(routes):
|
||||
return ttfrog.app.web.test_client()
|
||||
|
||||
|
||||
def test_get(client, routes):
|
||||
response = client.get(ttfrog.app.config.VIEW_URI)
|
||||
assert response.status_code == 200
|
||||
Reference in new issue
Block a user