diff --git a/py/examples/markdown.py b/py/examples/markdown.py
index c0e9e27db8..94d59cd73a 100644
--- a/py/examples/markdown.py
+++ b/py/examples/markdown.py
@@ -1,34 +1,53 @@
# Markdown
# Use a markdown card to display formatted content using #markdown.
# ---
-from h2o_wave import site, ui
+from h2o_wave import site, ui, app, main, Q
-page = site['/demo']
-sample_markdown = '''=
-The **quick** _brown_ fox jumped over the lazy dog.
+sample_markdown='''
+# Heading level 1
+## Heading level 2
+### Heading level 3
+#### Heading level 4
+##### Heading level 5
+###### Heading level 6
+___
+The **quick** __brown__ *fox* **_jumped_ over** the ~~lazy~~ _dog_.
+
+This is superscript and this is subscript !
Block quote:
> The quick brown fox jumped over the lazy dog.
+Ordered list:
+
+1. James Madison
+1. James Monroe
+1. John Quincy Adams
+
Unordered list:
-- The quick brown fox jumped over the lazy dog.
-- The quick brown fox jumped over the lazy dog.
-- The quick brown fox jumped over the lazy dog.
+- George Washington
+* John Adams
++ Thomas Jefferson
++ John Doe
-Ordered list:
+Nested list:
-1. The quick brown fox jumped over the lazy dog.
-1. The quick brown fox jumped over the lazy dog.
-1. The quick brown fox jumped over the lazy dog.
+1. First list item
+ - First nested list item
+ - Second nested list item
Image:
![Monty Python](https://upload.wikimedia.org/wikipedia/en/c/cb/Flyingcircus_2.jpg)
-Table:
+Image caption:
+
+A single track trail outside of Albuquerque, New Mexico.
+
+Table:
| Column 1 | Column 2 | Column 3 |
| -------- | -------- | -------- |
@@ -36,17 +55,12 @@
| Item 1 | Item 2 | Item 3 |
| Item 1 | Item 2 | Item 3 |
-'''
+Inline code:
+
+Use `ui.markdown_card` to start creating your own markdown content!
+
+Code block:
-page['example'] = ui.markdown_card(
- box='1 1 3 10',
- title='I was made using markdown!',
- content=sample_markdown,
-)
-page['example1'] = ui.markdown_card(
- box='4 1 3 10',
- title='I was made using markdown!',
- content='''
```py
from h2o_wave import main, app, Q, ui
@@ -59,8 +73,29 @@ async def serve(q: Q):
content='Hello, world!'
)
- await q.page`.save()
-''',
-)
+ await q.page.save()
+```
+
+
+
+Ignore \*markdown\* formatting.
-page.save()
+Linking:
+
+This framework is made by [h2o.ai](https://h2o.ai)
+'''
+
+@app('/demo')
+async def serve(q: Q):
+ q.page['example'] = ui.markdown_card(
+ box='1 1 3 9',
+ title='Markdown - compact (default)',
+ content=sample_markdown,
+ )
+ q.page['example1'] = ui.markdown_card(
+ box='4 1 3 9',
+ title='Markdown - regular',
+ content=sample_markdown,
+ compact=False
+ )
+ await q.page.save()
diff --git a/py/h2o_lightwave/h2o_lightwave/types.py b/py/h2o_lightwave/h2o_lightwave/types.py
index 6cad85112c..c7dc22ce29 100644
--- a/py/h2o_lightwave/h2o_lightwave/types.py
+++ b/py/h2o_lightwave/h2o_lightwave/types.py
@@ -9608,11 +9608,13 @@ def __init__(
title: str,
content: str,
data: Optional[PackedRecord] = None,
+ compact: Optional[bool] = None,
commands: Optional[List[Command]] = None,
):
_guard_scalar('MarkdownCard.box', box, (str,), False, False, False)
_guard_scalar('MarkdownCard.title', title, (str,), False, False, False)
_guard_scalar('MarkdownCard.content', content, (str,), False, False, False)
+ _guard_scalar('MarkdownCard.compact', compact, (bool,), False, True, False)
_guard_vector('MarkdownCard.commands', commands, (Command,), False, True, False)
self.box = box
"""A string indicating how to place this component on the page."""
@@ -9622,6 +9624,8 @@ def __init__(
"""The markdown content. Supports Github Flavored Markdown (GFM): https://guides.github.com/features/mastering-markdown/"""
self.data = data
"""Additional data for the card."""
+ self.compact = compact
+ """Make spacing tighter. Defaults to True."""
self.commands = commands
"""Contextual menu commands for this component."""
@@ -9630,6 +9634,7 @@ def dump(self) -> Dict:
_guard_scalar('MarkdownCard.box', self.box, (str,), False, False, False)
_guard_scalar('MarkdownCard.title', self.title, (str,), False, False, False)
_guard_scalar('MarkdownCard.content', self.content, (str,), False, False, False)
+ _guard_scalar('MarkdownCard.compact', self.compact, (bool,), False, True, False)
_guard_vector('MarkdownCard.commands', self.commands, (Command,), False, True, False)
return _dump(
view='markdown',
@@ -9637,6 +9642,7 @@ def dump(self) -> Dict:
title=self.title,
content=self.content,
data=self.data,
+ compact=self.compact,
commands=None if self.commands is None else [__e.dump() for __e in self.commands],
)
@@ -9650,18 +9656,22 @@ def load(__d: Dict) -> 'MarkdownCard':
__d_content: Any = __d.get('content')
_guard_scalar('MarkdownCard.content', __d_content, (str,), False, False, False)
__d_data: Any = __d.get('data')
+ __d_compact: Any = __d.get('compact')
+ _guard_scalar('MarkdownCard.compact', __d_compact, (bool,), False, True, False)
__d_commands: Any = __d.get('commands')
_guard_vector('MarkdownCard.commands', __d_commands, (dict,), False, True, False)
box: str = __d_box
title: str = __d_title
content: str = __d_content
data: Optional[PackedRecord] = __d_data
+ compact: Optional[bool] = __d_compact
commands: Optional[List[Command]] = None if __d_commands is None else [Command.load(__e) for __e in __d_commands]
return MarkdownCard(
box,
title,
content,
data,
+ compact,
commands,
)
diff --git a/py/h2o_lightwave/h2o_lightwave/ui.py b/py/h2o_lightwave/h2o_lightwave/ui.py
index 122b580b32..89bafb8628 100644
--- a/py/h2o_lightwave/h2o_lightwave/ui.py
+++ b/py/h2o_lightwave/h2o_lightwave/ui.py
@@ -3388,6 +3388,7 @@ def markdown_card(
title: str,
content: str,
data: Optional[PackedRecord] = None,
+ compact: Optional[bool] = None,
commands: Optional[List[Command]] = None,
) -> MarkdownCard:
"""Create a card that renders Markdown content.
@@ -3402,6 +3403,7 @@ def markdown_card(
title: The title for this card.
content: The markdown content. Supports Github Flavored Markdown (GFM): https://guides.github.com/features/mastering-markdown/
data: Additional data for the card.
+ compact: Make spacing tighter. Defaults to True.
commands: Contextual menu commands for this component.
Returns:
A `h2o_wave.types.MarkdownCard` instance.
@@ -3411,6 +3413,7 @@ def markdown_card(
title,
content,
data,
+ compact,
commands,
)
diff --git a/py/h2o_wave/h2o_wave/types.py b/py/h2o_wave/h2o_wave/types.py
index 6cad85112c..c7dc22ce29 100644
--- a/py/h2o_wave/h2o_wave/types.py
+++ b/py/h2o_wave/h2o_wave/types.py
@@ -9608,11 +9608,13 @@ def __init__(
title: str,
content: str,
data: Optional[PackedRecord] = None,
+ compact: Optional[bool] = None,
commands: Optional[List[Command]] = None,
):
_guard_scalar('MarkdownCard.box', box, (str,), False, False, False)
_guard_scalar('MarkdownCard.title', title, (str,), False, False, False)
_guard_scalar('MarkdownCard.content', content, (str,), False, False, False)
+ _guard_scalar('MarkdownCard.compact', compact, (bool,), False, True, False)
_guard_vector('MarkdownCard.commands', commands, (Command,), False, True, False)
self.box = box
"""A string indicating how to place this component on the page."""
@@ -9622,6 +9624,8 @@ def __init__(
"""The markdown content. Supports Github Flavored Markdown (GFM): https://guides.github.com/features/mastering-markdown/"""
self.data = data
"""Additional data for the card."""
+ self.compact = compact
+ """Make spacing tighter. Defaults to True."""
self.commands = commands
"""Contextual menu commands for this component."""
@@ -9630,6 +9634,7 @@ def dump(self) -> Dict:
_guard_scalar('MarkdownCard.box', self.box, (str,), False, False, False)
_guard_scalar('MarkdownCard.title', self.title, (str,), False, False, False)
_guard_scalar('MarkdownCard.content', self.content, (str,), False, False, False)
+ _guard_scalar('MarkdownCard.compact', self.compact, (bool,), False, True, False)
_guard_vector('MarkdownCard.commands', self.commands, (Command,), False, True, False)
return _dump(
view='markdown',
@@ -9637,6 +9642,7 @@ def dump(self) -> Dict:
title=self.title,
content=self.content,
data=self.data,
+ compact=self.compact,
commands=None if self.commands is None else [__e.dump() for __e in self.commands],
)
@@ -9650,18 +9656,22 @@ def load(__d: Dict) -> 'MarkdownCard':
__d_content: Any = __d.get('content')
_guard_scalar('MarkdownCard.content', __d_content, (str,), False, False, False)
__d_data: Any = __d.get('data')
+ __d_compact: Any = __d.get('compact')
+ _guard_scalar('MarkdownCard.compact', __d_compact, (bool,), False, True, False)
__d_commands: Any = __d.get('commands')
_guard_vector('MarkdownCard.commands', __d_commands, (dict,), False, True, False)
box: str = __d_box
title: str = __d_title
content: str = __d_content
data: Optional[PackedRecord] = __d_data
+ compact: Optional[bool] = __d_compact
commands: Optional[List[Command]] = None if __d_commands is None else [Command.load(__e) for __e in __d_commands]
return MarkdownCard(
box,
title,
content,
data,
+ compact,
commands,
)
diff --git a/py/h2o_wave/h2o_wave/ui.py b/py/h2o_wave/h2o_wave/ui.py
index 122b580b32..89bafb8628 100644
--- a/py/h2o_wave/h2o_wave/ui.py
+++ b/py/h2o_wave/h2o_wave/ui.py
@@ -3388,6 +3388,7 @@ def markdown_card(
title: str,
content: str,
data: Optional[PackedRecord] = None,
+ compact: Optional[bool] = None,
commands: Optional[List[Command]] = None,
) -> MarkdownCard:
"""Create a card that renders Markdown content.
@@ -3402,6 +3403,7 @@ def markdown_card(
title: The title for this card.
content: The markdown content. Supports Github Flavored Markdown (GFM): https://guides.github.com/features/mastering-markdown/
data: Additional data for the card.
+ compact: Make spacing tighter. Defaults to True.
commands: Contextual menu commands for this component.
Returns:
A `h2o_wave.types.MarkdownCard` instance.
@@ -3411,6 +3413,7 @@ def markdown_card(
title,
content,
data,
+ compact,
commands,
)
diff --git a/r/R/ui.R b/r/R/ui.R
index ac25e8c291..d3ae9aa868 100644
--- a/r/R/ui.R
+++ b/r/R/ui.R
@@ -3948,6 +3948,7 @@ ui_list_item1_card <- function(
#' @param title The title for this card.
#' @param content The markdown content. Supports Github Flavored Markdown (GFM): https://guides.github.com/features/mastering-markdown/
#' @param data Additional data for the card.
+#' @param compact Make spacing tighter. Defaults to True.
#' @param commands Contextual menu commands for this component.
#' @return A MarkdownCard instance.
#' @export
@@ -3956,17 +3957,20 @@ ui_markdown_card <- function(
title,
content,
data = NULL,
+ compact = NULL,
commands = NULL) {
.guard_scalar("box", "character", box)
.guard_scalar("title", "character", title)
.guard_scalar("content", "character", content)
# TODO Validate data: Rec
+ .guard_scalar("compact", "logical", compact)
.guard_vector("commands", "WaveCommand", commands)
.o <- list(
box=box,
title=title,
content=content,
data=data,
+ compact=compact,
commands=commands,
view='markdown')
class(.o) <- append(class(.o), c(.wave_obj, "WaveMarkdownCard"))
diff --git a/tools/intellij-plugin/src/main/resources/templates/wave-components.xml b/tools/intellij-plugin/src/main/resources/templates/wave-components.xml
index b24c294fac..149e668f93 100644
--- a/tools/intellij-plugin/src/main/resources/templates/wave-components.xml
+++ b/tools/intellij-plugin/src/main/resources/templates/wave-components.xml
@@ -1667,11 +1667,12 @@
-
+
+
diff --git a/tools/vscode-extension/component-snippets.json b/tools/vscode-extension/component-snippets.json
index 3170dd0182..2e60ea8139 100644
--- a/tools/vscode-extension/component-snippets.json
+++ b/tools/vscode-extension/component-snippets.json
@@ -1360,7 +1360,7 @@
"Wave Full MarkdownCard": {
"prefix": "w_full_markdown_card",
"body": [
- "ui.markdown_card(box='$1', title='$2', content='$3', data=${4:None}, commands=[\n\t\t$5\t\t\n])$0"
+ "ui.markdown_card(box='$1', title='$2', content='$3', data=${4:None}, compact=${5:True}, commands=[\n\t\t$6\t\t\n])$0"
],
"description": "Create a full Wave MarkdownCard."
},
diff --git a/ui/jest.config.js b/ui/jest.config.js
index 9e118b83cf..002e840936 100644
--- a/ui/jest.config.js
+++ b/ui/jest.config.js
@@ -19,13 +19,11 @@ module.exports = {
testEnvironment: "jsdom",
transformIgnorePatterns: [
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$",
- "^.+\\.module\\.(css|sass|scss)$"
],
modulePaths: [],
moduleNameMapper: {
"^react-native$": "react-native-web",
'^d3$': '/node_modules/d3/dist/d3.min.js',
- "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy"
},
moduleFileExtensions: [
"web.js",
diff --git a/ui/src/index.scss b/ui/src/index.scss
index a6c334c294..9186970982 100644
--- a/ui/src/index.scss
+++ b/ui/src/index.scss
@@ -42,6 +42,11 @@
@include scrollbar-styles();
}
+// Markdown code block scrollbar.
+.hljs {
+ @include scrollbar-styles();
+}
+
body {
margin: 0;
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
@@ -241,75 +246,4 @@ $tracking in $fontTrackings {
to {
opacity: 1;
}
-}
-
-// Default markdown syntax highlighting theme - androidstudio.
-pre code.hljs {
- display: block;
- overflow-x: auto;
- padding: 1em
-}
-
-code.hljs {
- padding: 3px 5px
-}
-
-.hljs {
- color: #a9b7c6;
- background: #282b2e;
- @include scrollbar-styles();
-}
-
-.hljs-bullet,
-.hljs-literal,
-.hljs-number,
-.hljs-symbol {
- color: #6897bb
-}
-
-.hljs-deletion,
-.hljs-keyword,
-.hljs-selector-tag {
- color: #cc7832
-}
-
-.hljs-link,
-.hljs-template-variable,
-.hljs-variable {
- color: #629755
-}
-
-.hljs-comment,
-.hljs-quote {
- color: grey
-}
-
-.hljs-meta {
- color: #bbb529
-}
-
-.hljs-addition,
-.hljs-attribute,
-.hljs-string {
- color: #6a8759
-}
-
-.hljs-section,
-.hljs-title,
-.hljs-type {
- color: #ffc66d
-}
-
-.hljs-name,
-.hljs-selector-class,
-.hljs-selector-id {
- color: #e8bf6a
-}
-
-.hljs-emphasis {
- font-style: italic
-}
-
-.hljs-strong {
- font-weight: 700
}
\ No newline at end of file
diff --git a/ui/src/markdown.css b/ui/src/markdown.css
new file mode 100644
index 0000000000..3c00cb5b6e
--- /dev/null
+++ b/ui/src/markdown.css
@@ -0,0 +1,656 @@
+/*
+ ! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com
+*/
+/*
+1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
+2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
+*/
+.wave-prose *,
+.wave-prose ::before,
+.wave-prose ::after {
+ box-sizing: border-box;
+ border-width: 0;
+ border-style: solid;
+ border-color: #e5e7eb;
+}
+
+/*
+1. Add the correct height in Firefox.
+2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
+3. Ensure horizontal rules are visible by default.
+*/
+.wave-prose hr {
+ height: 0;
+ color: inherit;
+ border-top-width: 1px;
+}
+
+/*
+Add the correct text decoration in Chrome, Edge, and Safari.
+*/
+.wave-prose abbr:where([title]) {
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+}
+
+/*
+Remove the default font size and weight for headings.
+*/
+.wave-prose h1,
+.wave-prose h2,
+.wave-prose h3,
+.wave-prose h4,
+.wave-prose h5,
+.wave-prose h6 {
+ font-size: inherit;
+ font-weight: inherit;
+}
+
+/*
+Reset links to optimize for opt-in styling instead of opt-out.
+*/
+.wave-prose a {
+ color: inherit;
+ text-decoration: inherit;
+}
+
+/*
+Add the correct font weight in Edge and Safari.
+*/
+.wave-prose b,
+.wave-prose strong {
+ font-weight: bolder;
+}
+
+/*
+1. Use the user's configured `mono` font family by default.
+2. Correct the odd `em` font sizing in all browsers.
+*/
+.wave-prose code,
+.wave-prose kbd,
+.wave-prose samp,
+.wave-prose pre {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ font-size: 1em;
+}
+
+/*
+Prevent `sub` and `sup` elements from affecting the line height in all browsers.
+*/
+.wave-prose sub,
+.wave-prose sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+.wave-prose sub {
+ bottom: -0.25em;
+}
+
+.wave-prose sup {
+ top: -0.5em;
+}
+
+/*
+1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
+2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
+3. Remove gaps between table borders by default.
+*/
+.wave-markdown table {
+ text-indent: 0;
+ border-color: inherit;
+ border-collapse: collapse;
+}
+
+/*
+Removes the default spacing and border for appropriate elements.
+*/
+.wave-prose blockquote,
+.wave-prose dl,
+.wave-prose dd,
+.wave-prose h1,
+.wave-prose h2,
+.wave-prose h3,
+.wave-prose h4,
+.wave-prose h5,
+.wave-prose h6,
+.wave-prose hr,
+.wave-prose figure,
+.wave-prose p,
+.wave-prose pre {
+ margin: 0;
+}
+
+.wave-prose fieldset {
+ margin: 0;
+ padding: 0;
+}
+
+.wave-prose legend {
+ padding: 0;
+}
+
+.wave-prose ol,
+.wave-prose ul,
+.wave-prose menu {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+/*
+1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
+2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
+ This can trigger a poorly considered lint error in some tools but is included by design.
+*/
+.wave-prose img,
+.wave-prose svg,
+.wave-prose video,
+.wave-prose canvas,
+.wave-prose audio,
+.wave-prose iframe,
+.wave-prose embed,
+.wave-prose object {
+ display: block;
+ vertical-align: middle;
+}
+
+/*
+Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
+*/
+.wave-prose img,
+.wave-prose video {
+ max-width: 100%;
+ max-height: 100%;
+}
+
+.wave-prose {
+ color: var(--wave-text);
+ max-width: 65ch;
+}
+
+.wave-prose p {
+ margin-top: 1.25em;
+ margin-bottom: 1.25em;
+}
+
+.wave-markdown a {
+ color: var(--wave-themePrimary);
+ text-decoration: underline;
+ font-weight: 500;
+}
+
+.wave-markdown a:hover {
+ text-decoration: none;
+}
+
+.wave-prose strong {
+ color: var(--wave-text);
+ font-weight: 600;
+}
+
+.wave-prose a strong {
+ color: inherit;
+}
+
+.wave-prose blockquote strong {
+ color: inherit;
+}
+
+.wave-prose thead th strong {
+ color: inherit;
+}
+
+.wave-prose ol {
+ list-style-type: decimal;
+ margin-top: 1.25em;
+ margin-bottom: 1.25em;
+ padding-left: 1.625em;
+}
+
+.wave-prose ol[type="A"] {
+ list-style-type: upper-alpha;
+}
+
+.wave-prose ol[type="a"] {
+ list-style-type: lower-alpha;
+}
+
+.wave-prose ol[type="A" s] {
+ list-style-type: upper-alpha;
+}
+
+.wave-prose ol[type="a" s] {
+ list-style-type: lower-alpha;
+}
+
+.wave-prose ol[type="I"] {
+ list-style-type: upper-roman;
+}
+
+.wave-prose ol[type="i"] {
+ list-style-type: lower-roman;
+}
+
+.wave-prose ol[type="I" s] {
+ list-style-type: upper-roman;
+}
+
+.wave-prose ol[type="i" s] {
+ list-style-type: lower-roman;
+}
+
+.wave-prose ol[type="1"] {
+ list-style-type: decimal;
+}
+
+.wave-prose ul {
+ list-style-type: disc;
+ margin-top: 1.25em;
+ margin-bottom: 1.25em;
+ padding-left: 1.625em;
+}
+
+.wave-prose ol>li::marker {
+ font-weight: 400;
+ color: var(--wave-text8);
+}
+
+.wave-prose ul>li::marker {
+ color: var(--wave-text2);
+}
+
+.wave-prose dt {
+ color: var(--wave-text);
+ font-weight: 600;
+ margin-top: 1.25em;
+}
+
+.wave-prose hr {
+ border-color: var(--wave-text1);
+ border-top-width: 1px;
+ margin-top: 3em;
+ margin-bottom: 3em;
+}
+
+.wave-prose blockquote {
+ font-weight: 500;
+ font-style: italic;
+ color: var(--wave-text);
+ border-left-width: 0.25rem;
+ border-left-color: var(--wave-text2);
+ quotes: "\201C" "\201D" "\2018" "\2019";
+ margin-top: 1.6em;
+ margin-bottom: 1.6em;
+ padding-left: 1em;
+}
+
+.wave-prose blockquote p:first-of-type::before {
+ content: open-quote;
+}
+
+.wave-prose blockquote p:last-of-type::after {
+ content: close-quote;
+}
+
+.wave-prose h1 {
+ color: var(--wave-text);
+ font-weight: 800;
+ font-size: 2.25em;
+ margin-top: 0;
+ margin-bottom: 0.8888889em;
+ line-height: 1.1111111;
+}
+
+.wave-prose h1 strong {
+ font-weight: 900;
+ color: inherit;
+}
+
+.wave-prose h2 {
+ color: var(--wave-text);
+ font-weight: 700;
+ font-size: 1.5em;
+ margin-top: 2em;
+ margin-bottom: 1em;
+ line-height: 1.3333333;
+}
+
+.wave-prose h2 strong {
+ font-weight: 800;
+ color: inherit;
+}
+
+.wave-prose h3 {
+ color: var(--wave-text);
+ font-weight: 600;
+ font-size: 1.25em;
+ margin-top: 1.6em;
+ margin-bottom: 0.6em;
+ line-height: 1.6;
+}
+
+.wave-prose h3 strong {
+ font-weight: 700;
+ color: inherit;
+}
+
+.wave-prose h4 {
+ color: var(--wave-text);
+ font-weight: 600;
+ margin-top: 1.5em;
+ margin-bottom: 0.5em;
+ line-height: 1.5;
+}
+
+.wave-prose h4 strong {
+ font-weight: 700;
+ color: inherit;
+}
+
+.wave-prose img {
+ margin-top: 2em;
+ margin-bottom: 2em;
+}
+
+.wave-prose picture {
+ display: block;
+ margin-top: 2em;
+ margin-bottom: 2em;
+}
+
+.wave-prose kbd {
+ font-weight: 500;
+ font-family: inherit;
+ color: var(--wave-text);
+ box-shadow:
+ 0 0 0 1px rgb(var(--wave-text) / 10%),
+ 0 3px 0 rgb(var(--wave-text) / 10%);
+ font-size: 0.875em;
+ border-radius: 0.3125rem;
+ padding-top: 0.1875em;
+ padding-right: 0.375em;
+ padding-bottom: 0.1875em;
+ padding-left: 0.375em;
+}
+
+.wave-markdown table {
+ width: 100%;
+ table-layout: auto;
+ text-align: left;
+ margin-top: 2em;
+ margin-bottom: 2em;
+ line-height: 1.7142857;
+}
+
+.wave-prose table {
+ font-size: 0.875em;
+}
+
+.wave-markdown thead {
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+ border-bottom-color: var(--wave-text4);
+}
+
+.wave-markdown thead th {
+ color: var(--wave-text);
+ font-weight: 600;
+ vertical-align: bottom;
+ padding-right: 0.5714286em;
+ padding-bottom: 0.5714286em;
+ padding-left: 0.5714286em;
+}
+
+.wave-markdown tbody tr {
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+ border-bottom-color: var(--wave-text2);
+}
+
+.wave-markdown tbody tr:last-child {
+ border-bottom-width: 0;
+}
+
+.wave-markdown tbody td {
+ vertical-align: baseline;
+ /* padding: 11px 6px; */
+}
+
+.wave-markdown thead th:first-child {
+ padding-left: 0;
+}
+
+.wave-markdown thead th:last-child {
+ padding-right: 0;
+}
+
+.wave-markdown tbody td,
+tfoot td {
+ padding-top: 0.5714286em;
+ padding-right: 0.5714286em;
+ padding-bottom: 0.5714286em;
+ padding-left: 0.5714286em;
+}
+
+.wave-markdown tbody td:first-child,
+tfoot td:first-child {
+ padding-left: 0;
+}
+
+.wave-markdown tbody td:last-child,
+tfoot td:last-child {
+ padding-right: 0;
+}
+
+.wave-markdown tfoot {
+ border-top-width: 1px;
+ border-top-color: var(--wave-text4);
+}
+
+.wave-markdown tfoot td {
+ vertical-align: top;
+}
+
+.wave-prose figure>* {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.wave-prose figcaption {
+ color: var(--wave-text7);
+ font-size: 0.875em;
+ line-height: 1.4285714;
+ margin-top: 0.8571429em;
+}
+
+.wave-prose {
+ font-size: 1rem;
+ line-height: 1.75;
+}
+
+.wave-prose picture>img {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.wave-prose video {
+ margin-top: 2em;
+ margin-bottom: 2em;
+}
+
+.wave-prose li {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+.wave-prose ol>li {
+ padding-left: 0.375em;
+}
+
+.wave-prose ul>li {
+ padding-left: 0.375em;
+}
+
+.wave-prose>ul>li p {
+ margin-top: 0.75em;
+ margin-bottom: 0.75em;
+}
+
+.wave-prose>ul>li>*:first-child {
+ margin-top: 1.25em;
+}
+
+.wave-prose>ul>li>*:last-child {
+ margin-bottom: 1.25em;
+}
+
+.wave-prose>ol>li>*:first-child {
+ margin-top: 1.25em;
+}
+
+.wave-prose>ol>li>*:last-child {
+ margin-bottom: 1.25em;
+}
+
+.wave-prose ul ul,
+ul ol,
+ol ul,
+ol ol {
+ margin-top: 0.75em;
+ margin-bottom: 0.75em;
+}
+
+.wave-prose dl {
+ margin-top: 1.25em;
+ margin-bottom: 1.25em;
+}
+
+.wave-prose dd {
+ margin-top: 0.5em;
+ padding-left: 1.625em;
+}
+
+.wave-prose hr+* {
+ margin-top: 0;
+}
+
+.wave-prose h2+* {
+ margin-top: 0;
+}
+
+.wave-prose h3+* {
+ margin-top: 0;
+}
+
+.wave-prose h4+* {
+ margin-top: 0;
+}
+
+.wave-prose figure {
+ margin-top: 2em;
+ margin-bottom: 2em;
+}
+
+.wave-markdown> :first-child {
+ margin-top: 0;
+}
+
+.wave-markdown> :last-child {
+ margin-bottom: 0;
+}
+
+.wave-prose pre {
+ background-color: #282b2e;
+ overflow-x: auto;
+ font-size: 0.875em;
+ line-height: 1.7142857;
+ margin-top: 1.7142857em 0;
+ border-radius: 0.375rem;
+ padding: 0.8571429em 1.1428571em;
+}
+
+.wave-prose pre code::before,
+pre code::after {
+ content: none;
+}
+
+.wave-prose h2 code {
+ font-size: 0.875em;
+}
+
+.wave-prose h3 code {
+ font-size: 0.9em;
+}
+
+/* Default markdown syntax highlighting theme - androidstudio. */
+pre code.hljs {
+ display: block;
+ overflow-x: auto;
+ padding: 1em;
+}
+
+code.hljs {
+ padding: 3px 5px;
+}
+
+.hljs {
+ background-color: #282b2e;
+ color: #a9b7c6;
+}
+
+.hljs-bullet,
+.hljs-literal,
+.hljs-number,
+.hljs-symbol {
+ color: #6897bb
+}
+
+.hljs-deletion,
+.hljs-keyword,
+.hljs-selector-tag {
+ color: #cc7832
+}
+
+.hljs-link,
+.hljs-template-variable,
+.hljs-variable {
+ color: #629755
+}
+
+.hljs-comment,
+.hljs-quote {
+ color: grey
+}
+
+.hljs-meta {
+ color: #bbb529
+}
+
+.hljs-addition,
+.hljs-attribute,
+.hljs-string {
+ color: #6a8759
+}
+
+.hljs-section,
+.hljs-title,
+.hljs-type {
+ color: #ffc66d
+}
+
+.hljs-name,
+.hljs-selector-class,
+.hljs-selector-id {
+ color: #e8bf6a
+}
+
+.hljs-emphasis {
+ font-style: italic
+}
+
+.hljs-strong {
+ font-weight: 700
+}
\ No newline at end of file
diff --git a/ui/src/markdown.tsx b/ui/src/markdown.tsx
index 7c2c89f542..d997ad4863 100644
--- a/ui/src/markdown.tsx
+++ b/ui/src/markdown.tsx
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Model, Rec, S, unpack } from './core'
+import { B, Model, Rec, S, unpack, xid } from './core'
import hljs from 'highlight.js/lib/core'
import MarkdownIt from 'markdown-it'
import React from 'react'
@@ -20,7 +20,6 @@ import ReactDOM from 'react-dom'
import { stylesheet } from 'typestyle'
import { ClipboardCopyButton } from './copyable_text'
import { cards, grid, substitute } from './layout'
-import { border, clas, cssVar, padding, pc } from './theme'
import { bond } from './ui'
const
@@ -33,42 +32,6 @@ const
body: {
flexGrow: 1,
},
- markdown: {
- $nest: {
- '&>*:first-child': {
- marginTop: 0
- },
- '&>*:last-child': {
- marginBottom: 0
- },
- a: {
- color: cssVar('$themePrimary'),
- $nest: {
- '&:hover': {
- textDecoration: 'none',
- },
- },
- },
- table: {
- width: pc(100),
- borderCollapse: 'collapse',
- },
- tr: {
- borderBottom: border(1, cssVar('$text5')),
- },
- th: {
- padding: padding(11, 6),
- textAlign: 'left',
- },
- td: {
- padding: padding(11, 6),
- },
- img: {
- maxWidth: '100%',
- maxHeight: '100%',
- },
- },
- },
codeblock: {
position: 'relative',
$nest: {
@@ -88,8 +51,8 @@ const
}
},
})
-const highlightSyntax = async (str: S, language: S, codeBlockId: S) => {
- const codeBlock = document.getElementById(codeBlockId)
+const highlightSyntax = async (str: S, language: S, codeElementId: S) => {
+ const codeBlock = document.getElementById(codeElementId)
if (!codeBlock) return ''
if (language) {
try {
@@ -118,21 +81,23 @@ const highlightSyntax = async (str: S, language: S, codeBlockId: S) => {
return highlightedCode
}
-export const Markdown = ({ source }: { source: S }) => {
+export const Markdown = ({ source, compact = true }: { source: S, compact?: B }) => {
const
prevHighlights = React.useRef([]), // Prevent flicker during streaming.
codeBlockIdx = React.useRef(0), // MarkdownIt parses code blocks sequentially, which is a problem for streaming.
markdown = React.useMemo(() => MarkdownIt({
html: true, linkify: true, typographer: true, highlight: (str, lang) => {
const codeBlockId = codeBlockIdx.current.toString()
+ // Use the unique html element id to avoid conflicts when multiple markdown cards are rendered on the same page.
+ const codeElementId = `${xid()}-${codeBlockId}`
if (prevHighlights.current.length === codeBlockIdx.current) prevHighlights.current.push('')
// HACK: MarkdownIt does not support async rules.
// https://github.com/markdown-it/markdown-it/blob/master/docs/development.md#i-need-async-rule-how-to-do-it
- setTimeout(async () => prevHighlights.current[+codeBlockId] = await highlightSyntax(str, lang, codeBlockId), 0)
+ setTimeout(async () => prevHighlights.current[+codeBlockId] = await highlightSyntax(str, lang, codeElementId), 0)
// TODO: Sanitize the HTML.
- const ret = `${prevHighlights.current[codeBlockIdx.current] || str}
`
+ const ret = `${prevHighlights.current[codeBlockIdx.current] || str}
`
codeBlockIdx.current++
return ret
}
@@ -153,7 +118,8 @@ export const Markdown = ({ source }: { source: S }) => {
return false
}
}
- return
+ React.useEffect(() => { import('./markdown.css') }, [])
+ return
}
/**
@@ -182,6 +148,10 @@ interface State {
* Additional data for the card.
**/
data?: Rec
+ /**
+ * Make spacing tighter. Defaults to True.
+ **/
+ compact?: B
}
export const
@@ -195,7 +165,7 @@ export const
)
diff --git a/ui/src/react-app-env.d.ts b/ui/src/react-app-env.d.ts
index 08c16a8a62..9f60f88fc5 100644
--- a/ui/src/react-app-env.d.ts
+++ b/ui/src/react-app-env.d.ts
@@ -50,6 +50,11 @@ declare module '*.svg' {
export default src;
}
+declare module '*.css' {
+ const content: { [className: string]: string };
+ export default content;
+}
+
declare module '*.module.css' {
const classes: { readonly [key: string]: string };
export default classes;
diff --git a/ui/src/setupTests.ts b/ui/src/setupTests.ts
index c456897531..8c038258a7 100644
--- a/ui/src/setupTests.ts
+++ b/ui/src/setupTests.ts
@@ -29,6 +29,8 @@ jest.mock('d3', () => ({
pie: () => ({ value: () => () => [] }),
}))
jest.mock('d3-interpolate', () => ({ extent: () => '' }))
+jest.mock('./markdown.css', () => '')
+
const icons = Object.entries(Icons).reduce((acc, [iconName, iconComponent]) => {
if ('displayName' in iconComponent) acc[iconName.slice(0, -4)] = React.createElement(iconComponent as React.FC)
diff --git a/website/widgets/content/markdown.md b/website/widgets/content/markdown.md
index 07256c53f2..83dea7db75 100644
--- a/website/widgets/content/markdown.md
+++ b/website/widgets/content/markdown.md
@@ -92,3 +92,33 @@ q.page['example'] = ui.markdown_card(
Displaying code with proper syntax highlighting is supported out of the box. The list of supported languages can be found [here](https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md).
Wave uses [AndroidStudio](https://developer.android.com/studio) theme as default, but can be changed by picking one of the plenty [other themes](https://highlightjs.org/static/demo/), downloading its [CSS](https://github.com/highlightjs/highlight.js/tree/main/src/styles) and loading it within the Wave app. See [this example](/docs/examples/markdown-code-theme) to learn how to change the code highlighting theme into a popular `Atom One Dark`.
+
+## Compact
+
+By default, markdown is rendered compactly to fit more content.
+Use `compact=False` for a modern, well-structured and reader-friendly look.
+
+```py
+sample_markdown = '''=
+# Heading
+
+The **quick** _brown_ fox jumped over the lazy dog.
+
+Blockquote:
+
+> The quick brown fox jumped over the lazy dog.
+
+Ordered list:
+
+1. The quick brown fox jumped over the lazy dog.
+1. The quick brown fox jumped over the lazy dog.
+1. The quick brown fox jumped over the lazy dog.
+'''
+
+q.page['example'] = ui.markdown_card(
+ box='1 1 3 10',
+ title='I was made using markdown!',
+ content=sample_markdown,
+ compact=False
+)
+```