mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
89468bd9f0
Summary: There was a bug where stale Markdown values were sometimes shown when moving between pages. It appears to have been an issue with async rendering of Markdown and how cell values were being updated. As a fix, we now use synchronous rendering, which doesn't have any perceptible performance penalty, and behaves predictably. Test Plan: Manual. Reviewers: jarek Reviewed By: jarek Subscribers: jarek Differential Revision: https://phab.getgrist.com/D4379
194 lines
4.7 KiB
TypeScript
194 lines
4.7 KiB
TypeScript
import { domAsync } from 'app/client/lib/domAsync';
|
|
import { DataRowModel } from 'app/client/models/DataRowModel';
|
|
import { ViewFieldRec } from 'app/client/models/entities/ViewFieldRec';
|
|
import { buildCodeHighlighter } from 'app/client/ui/CodeHighlight';
|
|
import { renderer } from 'app/client/ui/MarkdownCellRenderer';
|
|
import { sanitizeHTML } from 'app/client/ui/sanitizeHTML';
|
|
import { theme, vars } from 'app/client/ui2018/cssVars';
|
|
import { gristThemeObs } from 'app/client/ui2018/theme';
|
|
import { NTextBox } from 'app/client/widgets/NTextBox';
|
|
import { AsyncCreate } from 'app/common/AsyncCreate';
|
|
import { dom, styled, subscribeElem } from 'grainjs';
|
|
import { Marked } from 'marked';
|
|
import { markedHighlight } from 'marked-highlight';
|
|
import markedLinkifyIt from 'marked-linkify-it';
|
|
|
|
/**
|
|
* Creates a widget for displaying Markdown-formatted text.
|
|
*/
|
|
export class MarkdownTextBox extends NTextBox {
|
|
private static _marked?: AsyncCreate<Marked>;
|
|
|
|
constructor(field: ViewFieldRec) {
|
|
super(field);
|
|
|
|
if (!MarkdownTextBox._marked) {
|
|
MarkdownTextBox._marked = new AsyncCreate(async () => {
|
|
const highlight = await buildCodeHighlighter({ maxLines: 60 });
|
|
return new Marked(
|
|
markedHighlight({
|
|
highlight: (code) => highlight(code),
|
|
}),
|
|
markedLinkifyIt()
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
public buildDom(row: DataRowModel) {
|
|
const value = row.cells[this.field.colId()];
|
|
return dom(
|
|
"div.field_clip",
|
|
domAsync(
|
|
MarkdownTextBox._marked!.get().then(({ parse }) => {
|
|
const renderMarkdown = (el: HTMLElement) => {
|
|
el.innerHTML = sanitizeHTML(
|
|
parse(String(value.peek()), {
|
|
async: false,
|
|
gfm: false,
|
|
renderer,
|
|
})
|
|
);
|
|
};
|
|
|
|
return cssMarkdown(
|
|
cssMarkdown.cls("-text-wrap", this.wrapping),
|
|
dom.style("text-align", this.alignment),
|
|
(el) => {
|
|
subscribeElem(el, value, () => renderMarkdown(el));
|
|
// Picking up theme changes currently requires a re-render.
|
|
// If we switch to using a custom Ace theme with CSS variables
|
|
// from `cssVars.ts`, we can remove this.
|
|
subscribeElem(el, gristThemeObs(), () => renderMarkdown(el));
|
|
this.field.viewSection().events.trigger("rowHeightChange");
|
|
},
|
|
);
|
|
})
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
const cssMarkdown = styled('div', `
|
|
white-space: nowrap;
|
|
|
|
&-text-wrap {
|
|
white-space: normal;
|
|
word-break: break-word;
|
|
}
|
|
&:not(&-text-wrap) p,
|
|
&:not(&-text-wrap) h1,
|
|
&:not(&-text-wrap) h2,
|
|
&:not(&-text-wrap) h3,
|
|
&:not(&-text-wrap) h4,
|
|
&:not(&-text-wrap) h5,
|
|
&:not(&-text-wrap) h6
|
|
{
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
& > *:first-child {
|
|
margin-top: 0px !important;
|
|
}
|
|
& > :not(blockquote, ol, pre, ul):last-child {
|
|
margin-bottom: 0px !important;
|
|
}
|
|
& h1, & h2, & h3, & h4, & h5, & h6 {
|
|
margin-top: 24px;
|
|
margin-bottom: 16px;
|
|
font-weight: 600;
|
|
line-height: 1.25;
|
|
}
|
|
& h1 {
|
|
padding-bottom: .3em;
|
|
font-size: 2em;
|
|
}
|
|
& h2 {
|
|
padding-bottom: .3em;
|
|
font-size: 1.5em;
|
|
}
|
|
& h3 {
|
|
font-size: 1.25em;
|
|
}
|
|
& h4 {
|
|
font-size: 1em;
|
|
}
|
|
& h5 {
|
|
font-size: .875em;
|
|
}
|
|
& h6 {
|
|
color: ${theme.lightText};
|
|
font-size: .85em;
|
|
}
|
|
& p, & blockquote, & ul, & ol, & dl, & pre {
|
|
margin-top: 0px;
|
|
margin-bottom: 10px;
|
|
}
|
|
& code, & pre {
|
|
color: ${theme.text};
|
|
font-size: 85%;
|
|
background-color: ${theme.markdownCellLightBg};
|
|
border: 0;
|
|
border-radius: 6px;
|
|
}
|
|
& code {
|
|
padding: .2em .4em;
|
|
margin: 0;
|
|
white-space: nowrap;
|
|
}
|
|
&-text-wrap code {
|
|
white-space: break-spaces;
|
|
}
|
|
& pre {
|
|
padding: 16px;
|
|
overflow: auto;
|
|
line-height: 1.45;
|
|
}
|
|
& pre code {
|
|
font-size: 100%;
|
|
display: inline;
|
|
max-width: auto;
|
|
margin: 0;
|
|
padding: 0;
|
|
overflow: visible;
|
|
line-height: inherit;
|
|
word-wrap: normal;
|
|
background: transparent;
|
|
}
|
|
& pre > code {
|
|
background: transparent;
|
|
white-space: nowrap;
|
|
word-break: normal;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
&-text-wrap pre > code {
|
|
white-space: normal;
|
|
}
|
|
& pre .ace-chrome, & pre .ace-dracula {
|
|
background: ${theme.markdownCellLightBg} !important;
|
|
}
|
|
& .ace_indent-guide {
|
|
background: none;
|
|
}
|
|
& .ace_static_highlight {
|
|
white-space: nowrap;
|
|
}
|
|
& ul, & ol {
|
|
padding-left: 2em;
|
|
}
|
|
& li > ol, & li > ul {
|
|
margin: 0;
|
|
}
|
|
& li + li,
|
|
& li > ol > li:first-child,
|
|
& li > ul > li:first-child {
|
|
margin-top: .25em;
|
|
}
|
|
& blockquote {
|
|
font-size: ${vars.mediumFontSize};
|
|
border-left: .25em solid ${theme.markdownCellMediumBorder};
|
|
padding: 0 1em;
|
|
}
|
|
`);
|