Skip to content

Commit d8d6653

Browse files
committed
markdown styling
1 parent 4a5c1fd commit d8d6653

File tree

5 files changed

+226
-7
lines changed

5 files changed

+226
-7
lines changed

services/static-webserver/client/Manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"css": [
3636
"jsontreeviewer/jsonTree.css",
3737
"hint/hint.css",
38+
"markdown/markdown.css",
3839
"common/common.css"
3940
]
4041
},

services/static-webserver/client/source/class/osparc/Application.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ qx.Class.define("osparc.Application", {
101101

102102
this.__initRouting();
103103
this.__startupChecks();
104+
105+
/*
106+
qx.bom.Stylesheet.createElement(`
107+
.osparc-markdown p { margin: 0; }
108+
.osparc-markdown p + p { margin-top: .35em; }
109+
`);
110+
*/
104111
},
105112

106113
__preloadCalls: async function() {

services/static-webserver/client/source/class/osparc/conversation/MessageUI.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,24 @@ qx.Class.define("osparc.conversation.MessageUI", {
100100
});
101101
this.getChildControl("header-layout").addAt(control, isMyMessage ? 0 : 2);
102102
break;
103-
case "message-content":
104-
control = new osparc.ui.markdown.Markdown().set({
105-
noMargin: true,
106-
allowGrowX: true,
103+
case "message-content": {
104+
// outer bubble
105+
const maxWidth = 300;
106+
const bubble = new qx.ui.container.Composite(new qx.ui.layout.VBox()).set({
107+
decorator: "chat-bubble",
108+
padding: 8,
109+
maxWidth,
107110
});
108-
control.getContentElement().setStyles({
109-
"text-align": isMyMessage ? "right" : "left",
111+
control = new osparc.ui.markdown.Markdown2().set({
112+
maxWidth,
110113
});
111-
this._add(control, {
114+
bubble.add(control);
115+
this._add(bubble, {
112116
row: 1,
113117
column: 1,
114118
});
115119
break;
120+
}
116121
case "spacer":
117122
control = new qx.ui.core.Spacer();
118123
this._add(control, {
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* oSPARC - The SIMCORE frontend - https://osparc.io
3+
* Copyright: 2019 IT'IS Foundation - https://itis.swiss
4+
* License: MIT - https://opensource.org/licenses/MIT
5+
* Authors: Ignacio Pascual (ignapas)
6+
* Odei Maiz (odeimaiz)
7+
*/
8+
9+
/**
10+
* @asset(marked/marked.min.js)
11+
* @assert(markdown.css)
12+
* @ignore(marked)
13+
*/
14+
15+
/* global marked */
16+
17+
/**
18+
* This class is just a special kind of rich label that takes markdown raw text, compiles it to HTML,
19+
* sanitizes it and applies it to its value property.
20+
*/
21+
qx.Class.define("osparc.ui.markdown.Markdown2", {
22+
extend: qx.ui.embed.Html,
23+
24+
/**
25+
* Markdown constructor. It directly accepts markdown as its first argument.
26+
* @param {String} markdown Plain text accepting markdown syntax. Its compiled version will be set in the value property of the label.
27+
*/
28+
construct: function(markdown) {
29+
this.base(arguments);
30+
31+
this.set({
32+
allowGrowX: true,
33+
allowGrowY: true,
34+
overflowX: "hidden",
35+
overflowY: "hidden",
36+
});
37+
38+
const markdownCssUri = qx.util.ResourceManager.getInstance().toUri("markdown/markdown.css");
39+
qx.module.Css.includeStylesheet(markdownCssUri);
40+
41+
this.__loadMarked = new Promise((resolve, reject) => {
42+
if (typeof marked === "function") {
43+
resolve(marked);
44+
} else {
45+
const loader = new qx.util.DynamicScriptLoader([
46+
"marked/marked.min.js"
47+
]);
48+
loader.addListenerOnce("ready", () => resolve(marked), this);
49+
loader.addListenerOnce("failed", e =>
50+
reject(Error(`Failed to load ${e.getData()}`))
51+
);
52+
loader.start();
53+
}
54+
});
55+
56+
if (markdown) {
57+
this.setValue(markdown);
58+
}
59+
60+
this.addListenerOnce("appear", () => {
61+
this.getContentElement().addClass("osparc-markdown");
62+
});
63+
},
64+
65+
properties: {
66+
/**
67+
* Holds the raw markdown text and updates the label's {@link #value} whenever new markdown arrives.
68+
*/
69+
value: {
70+
check: "String",
71+
apply: "__applyMarkdown"
72+
},
73+
74+
noMargin: {
75+
check: "Boolean",
76+
init: false
77+
}
78+
},
79+
80+
events: {
81+
"resized": "qx.event.type.Event",
82+
},
83+
84+
members: {
85+
__loadMarked: null,
86+
/**
87+
* Apply function for the markdown property. Compiles the markdown text to HTML and applies it to the value property of the label.
88+
* @param {String} value Plain text accepting markdown syntax.
89+
*/
90+
__applyMarkdown: function(value = "") {
91+
this.__loadMarked.then(() => {
92+
const renderer = {
93+
link(link) {
94+
const linkColor = qx.theme.manager.Color.getInstance().resolve("link");
95+
let linkHtml = `<a href="${link.href}" title="${link.title || ""}" style="color: ${linkColor};">`
96+
if (link.tokens && link.tokens.length) {
97+
const linkRepresentation = link.tokens[0];
98+
if (linkRepresentation.type === "text") {
99+
linkHtml += linkRepresentation.text;
100+
} else if (linkRepresentation.type === "image") {
101+
linkHtml += `<img src="${linkRepresentation.href}" tile alt="${linkRepresentation.text}"></img>`;
102+
}
103+
}
104+
linkHtml += `</a>`;
105+
return linkHtml;
106+
}
107+
};
108+
marked.use({ renderer });
109+
// By default, Markdown requires two spaces at the end of a line or a blank line between paragraphs to produce a line break.
110+
// With this, a single line break (Enter) in your Markdown input will render as a <br> in HTML.
111+
marked.setOptions({ breaks: true });
112+
113+
const html = marked.parse(value);
114+
115+
const safeHtml = osparc.wrapper.DOMPurify.getInstance().sanitize(html);
116+
117+
this.setHtml(safeHtml);
118+
119+
// for some reason the content is not immediately there
120+
qx.event.Timer.once(() => {
121+
this.__resizeMe();
122+
}, this, 100);
123+
124+
this.__resizeMe();
125+
}).catch(error => console.error(error));
126+
},
127+
128+
__getDomElement: function() {
129+
if (!this.getContentElement || this.getContentElement() === null) {
130+
return null;
131+
}
132+
const domElement = this.getContentElement().getDomElement();
133+
if (domElement) {
134+
return domElement;
135+
}
136+
return null;
137+
},
138+
139+
// qx.ui.embed.html scale to content
140+
__resizeMe: function() {
141+
const domElement = this.__getDomElement();
142+
if (domElement === null) {
143+
return;
144+
}
145+
this.setHeight(null); // let it auto-size
146+
this.setMinHeight(domElement.scrollHeight); // so layout respects full content
147+
},
148+
}
149+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
.osparc-markdown {
2+
line-height: 1.35;
3+
word-break: break-word;
4+
}
5+
6+
/* paragraphs */
7+
.osparc-markdown p {
8+
margin: 0;
9+
}
10+
.osparc-markdown p + p {
11+
margin-top: .35em;
12+
}
13+
14+
/* lists */
15+
.osparc-markdown ul,
16+
.osparc-markdown ol {
17+
margin: .25em 0;
18+
padding-left: 1em;
19+
}
20+
.osparc-markdown li {
21+
margin: 0;
22+
padding: 0;
23+
}
24+
25+
/* blockquotes */
26+
.osparc-markdown blockquote {
27+
margin: .25em 0;
28+
padding-left: 0.6em;
29+
}
30+
31+
/* code blocks */
32+
.osparc-markdown pre {
33+
margin: .25em 0;
34+
padding: .4em .6em;
35+
border-radius: 4px;
36+
white-space: pre-wrap;
37+
word-break: break-word;
38+
}
39+
.osparc-markdown code {
40+
font-family: monospace;
41+
font-size: 0.95em;
42+
padding: 0 .25em;
43+
border-radius: 4px;
44+
}
45+
46+
/* headings */
47+
.osparc-markdown h1,
48+
.osparc-markdown h2,
49+
.osparc-markdown h3,
50+
.osparc-markdown h4,
51+
.osparc-markdown h5,
52+
.osparc-markdown h6 {
53+
margin: .4em 0 .25em;
54+
line-height: 1.2;
55+
font-weight: 600;
56+
font-size: 1em; /* normalize size inside chat */
57+
}

0 commit comments

Comments
 (0)