mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Add Markdown cell format
Summary: Text columns can now display their values as Markdown-formatted text by changing their cell format to "Markdown". A minimal subset of the Markdown specification is currently supported. Test Plan: Browser tests. Reviewers: Spoffy, dsagal Reviewed By: Spoffy, dsagal Subscribers: dsagal, Spoffy Differential Revision: https://phab.getgrist.com/D4326
This commit is contained in:
parent
5c486e686e
commit
292c894b93
@ -135,7 +135,9 @@ class ParagraphRenderer extends FormRenderer {
|
||||
return css.paragraph(
|
||||
css.paragraph.cls(`-alignment-${this.layoutNode.alignment || 'left'}`),
|
||||
el => {
|
||||
el.innerHTML = sanitizeHTML(marked(this.layoutNode.text || '**Lorem** _ipsum_ dolor'));
|
||||
el.innerHTML = sanitizeHTML(marked(this.layoutNode.text || '**Lorem** _ipsum_ dolor', {
|
||||
async: false,
|
||||
}));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -505,7 +505,7 @@ export const cssMarkdownRender = styled('div', `
|
||||
export function markdown(obs: BindableValue<string>, ...args: IDomArgs<HTMLDivElement>) {
|
||||
return cssMarkdownRender(el => {
|
||||
dom.autoDisposeElem(el, subscribeBindable(obs, val => {
|
||||
el.innerHTML = sanitizeHTML(marked(val));
|
||||
el.innerHTML = sanitizeHTML(marked(val, {async: false}));
|
||||
}));
|
||||
}, ...args);
|
||||
}
|
||||
|
@ -25,5 +25,5 @@ export function markdown(markdownObs: BindableValue<string>): DomElementMethod {
|
||||
}
|
||||
|
||||
function setMarkdownValue(elem: Element, markdownValue: string): void {
|
||||
elem.innerHTML = sanitizeHTML(marked(markdownValue));
|
||||
elem.innerHTML = sanitizeHTML(marked(markdownValue, {async: false}));
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import {logTelemetryEvent} from 'app/client/lib/telemetry';
|
||||
import {getWelcomeHomeUrl, urlState} from 'app/client/models/gristUrlState';
|
||||
import {renderer} from 'app/client/ui/DocTutorialRenderer';
|
||||
import {cssPopupBody, FLOATING_POPUP_TOOLTIP_KEY, FloatingPopup} from 'app/client/ui/FloatingPopup';
|
||||
import {sanitizeHTML} from 'app/client/ui/sanitizeHTML';
|
||||
import {sanitizeTutorialHTML} from 'app/client/ui/sanitizeHTML';
|
||||
import {hoverTooltip, setHoverTooltip} from 'app/client/ui/tooltips';
|
||||
import {basicButton, primaryButton, textButton} from 'app/client/ui2018/buttons';
|
||||
import {mediaXSmall, theme, vars} from 'app/client/ui2018/cssVars';
|
||||
@ -13,7 +13,7 @@ import {loadingSpinner} from 'app/client/ui2018/loaders';
|
||||
import {confirmModal, modal} from 'app/client/ui2018/modals';
|
||||
import {parseUrlId} from 'app/common/gristUrls';
|
||||
import {dom, makeTestId, Observable, styled} from 'grainjs';
|
||||
import {marked} from 'marked';
|
||||
import {marked, Token} from 'marked';
|
||||
import debounce = require('lodash/debounce');
|
||||
import range = require('lodash/range');
|
||||
import sortBy = require('lodash/sortBy');
|
||||
@ -219,7 +219,7 @@ export class DocTutorial extends FloatingPopup {
|
||||
return value ? String(value) : undefined;
|
||||
};
|
||||
|
||||
const walkTokens = (token: marked.Token) => {
|
||||
const walkTokens = (token: Token) => {
|
||||
if (token.type === 'image') {
|
||||
imageUrls.push(token.href);
|
||||
}
|
||||
@ -231,13 +231,13 @@ export class DocTutorial extends FloatingPopup {
|
||||
|
||||
let slideContent = getValue('slide_content');
|
||||
if (!slideContent) { return null; }
|
||||
slideContent = sanitizeHTML(await marked.parse(slideContent, {
|
||||
slideContent = sanitizeTutorialHTML(await marked.parse(slideContent, {
|
||||
async: true, renderer, walkTokens
|
||||
}));
|
||||
|
||||
let boxContent = getValue('box_content');
|
||||
if (boxContent) {
|
||||
boxContent = sanitizeHTML(await marked.parse(boxContent, {
|
||||
boxContent = sanitizeTutorialHTML(await marked.parse(boxContent, {
|
||||
async: true, renderer, walkTokens
|
||||
}));
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import {marked} from 'marked';
|
||||
|
||||
export const renderer = new marked.Renderer();
|
||||
|
||||
renderer.image = (href: string | null, title: string | null, _text: string) => {
|
||||
renderer.image = ({href, title}) => {
|
||||
let classes = 'doc-tutorial-popup-thumbnail';
|
||||
const hash = href?.split('#')?.[1];
|
||||
if (hash) {
|
||||
@ -17,6 +17,6 @@ renderer.image = (href: string | null, title: string | null, _text: string) => {
|
||||
</div>`;
|
||||
};
|
||||
|
||||
renderer.link = (href: string | null, _title: string | null, text: string) => {
|
||||
renderer.link = ({href, text}) => {
|
||||
return `<a href="${href}" target="_blank">${text}</a>`;
|
||||
};
|
||||
|
12
app/client/ui/MarkdownCellRenderer.ts
Normal file
12
app/client/ui/MarkdownCellRenderer.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import {gristIconLink} from 'app/client/ui2018/links';
|
||||
import escape from 'lodash/escape';
|
||||
import {marked} from 'marked';
|
||||
|
||||
export const renderer = new marked.Renderer();
|
||||
|
||||
renderer.link = ({href, text}) => gristIconLink(href, text).outerHTML;
|
||||
|
||||
// Disable Markdown features that we aren't ready to support yet.
|
||||
renderer.hr = ({raw}) => raw;
|
||||
renderer.html = ({raw}) => escape(raw);
|
||||
renderer.image = ({raw}) => raw;
|
@ -1,16 +1,30 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
import createDOMPurifier from 'dompurify';
|
||||
|
||||
const config = {
|
||||
export function sanitizeHTML(source: string | Node): string {
|
||||
return defaultPurifier.sanitize(source);
|
||||
}
|
||||
|
||||
export function sanitizeTutorialHTML(source: string | Node): string {
|
||||
return tutorialPurifier.sanitize(source, {
|
||||
ADD_TAGS: ['iframe'],
|
||||
ADD_ATTR: ['allowFullscreen'],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
DOMPurify.addHook('uponSanitizeAttribute', (node) => {
|
||||
const defaultPurifier = createDOMPurifier();
|
||||
defaultPurifier.addHook('uponSanitizeAttribute', handleSanitizeAttribute);
|
||||
|
||||
const tutorialPurifier = createDOMPurifier();
|
||||
tutorialPurifier.addHook('uponSanitizeAttribute', handleSanitizeAttribute);
|
||||
tutorialPurifier.addHook('uponSanitizeElement', handleSanitizeTutorialElement);
|
||||
|
||||
function handleSanitizeAttribute(node: Element) {
|
||||
if (!('target' in node)) { return; }
|
||||
|
||||
node.setAttribute('target', '_blank');
|
||||
});
|
||||
DOMPurify.addHook('uponSanitizeElement', (node, data) => {
|
||||
}
|
||||
|
||||
function handleSanitizeTutorialElement(node: Element, data: createDOMPurifier.SanitizeElementHookEvent) {
|
||||
if (data.tagName !== 'iframe') { return; }
|
||||
|
||||
const src = node.getAttribute('src');
|
||||
@ -18,9 +32,5 @@ DOMPurify.addHook('uponSanitizeElement', (node, data) => {
|
||||
return;
|
||||
}
|
||||
|
||||
return node.parentNode?.removeChild(node);
|
||||
});
|
||||
|
||||
export function sanitizeHTML(source: string | Node): string {
|
||||
return DOMPurify.sanitize(source, config);
|
||||
node.parentNode?.removeChild(node);
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ export type IconName = "ChartArea" |
|
||||
"FieldFunctionEqual" |
|
||||
"FieldInteger" |
|
||||
"FieldLink" |
|
||||
"FieldMarkdown" |
|
||||
"FieldNumeric" |
|
||||
"FieldReference" |
|
||||
"FieldSpinner" |
|
||||
@ -185,6 +186,7 @@ export const IconList: IconName[] = ["ChartArea",
|
||||
"FieldFunctionEqual",
|
||||
"FieldInteger",
|
||||
"FieldLink",
|
||||
"FieldMarkdown",
|
||||
"FieldNumeric",
|
||||
"FieldReference",
|
||||
"FieldSpinner",
|
||||
|
@ -895,6 +895,13 @@ export const theme = {
|
||||
undefined, colors.slate),
|
||||
widgetGallerySecondaryHeaderBgHover: new CustomProp(
|
||||
'theme-widget-gallery-secondary-header-bg-hover', undefined, '#7E7E85'),
|
||||
|
||||
/* Markdown Cell */
|
||||
markdownCellLightBg: new CustomProp('theme-markdown-cell-light-bg', undefined, colors.lightGrey),
|
||||
markdownCellLightBorder: new CustomProp('theme-markdown-cell-light-border', undefined,
|
||||
colors.mediumGreyOpaque),
|
||||
markdownCellMediumBorder: new CustomProp('theme-markdown-cell-medium-border', undefined,
|
||||
colors.darkGrey),
|
||||
};
|
||||
|
||||
const cssColors = values(colors).map(v => v.decl()).join('\n');
|
||||
|
@ -56,7 +56,7 @@ import { IconName } from './IconList';
|
||||
/**
|
||||
* Defaults for all icons.
|
||||
*/
|
||||
const iconDiv = styled('div', `
|
||||
const iconStyles = `
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
@ -66,24 +66,35 @@ const iconDiv = styled('div', `
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: var(--icon-color, var(--grist-theme-text, black));
|
||||
`);
|
||||
`;
|
||||
|
||||
export const cssIconBackground = styled(iconDiv, `
|
||||
background-color: var(--icon-background, inherit);
|
||||
-webkit-mask: none;
|
||||
& .${iconDiv.className} {
|
||||
transition: inherit;
|
||||
display: block;
|
||||
}
|
||||
`);
|
||||
const cssIconDiv = styled('div', iconStyles);
|
||||
|
||||
const cssIconSpan = styled('span', iconStyles);
|
||||
|
||||
export function icon(name: IconName, ...domArgs: DomElementArg[]): HTMLElement {
|
||||
return iconDiv(
|
||||
return cssIconDiv(
|
||||
dom.style('-webkit-mask-image', `var(--icon-${name})`),
|
||||
...domArgs
|
||||
);
|
||||
}
|
||||
|
||||
export function iconSpan(name: IconName, ...domArgs: DomElementArg[]): HTMLElement {
|
||||
return cssIconSpan(
|
||||
dom.style('-webkit-mask-image', `var(--icon-${name})`),
|
||||
...domArgs
|
||||
);
|
||||
}
|
||||
|
||||
export const cssIconSpanBackground = styled(cssIconSpan, `
|
||||
background-color: var(--icon-background, inherit);
|
||||
-webkit-mask: none;
|
||||
& .${cssIconSpan.className} {
|
||||
transition: inherit;
|
||||
display: block;
|
||||
}
|
||||
`);
|
||||
|
||||
/**
|
||||
* Container box for an icon to serve as a button..
|
||||
*/
|
||||
|
@ -1,9 +1,9 @@
|
||||
import {findLinks} from 'app/client/lib/textUtils';
|
||||
import { sameDocumentUrlState, urlState } from 'app/client/models/gristUrlState';
|
||||
import { hideInPrintView, testId, theme } from 'app/client/ui2018/cssVars';
|
||||
import {cssIconBackground, icon} from 'app/client/ui2018/icons';
|
||||
import { CellValue } from 'app/plugin/GristData';
|
||||
import { dom, DomArg, IDomArgs, Observable, styled } from 'grainjs';
|
||||
import {sameDocumentUrlState, urlState} from 'app/client/models/gristUrlState';
|
||||
import {hideInPrintView, testId, theme} from 'app/client/ui2018/cssVars';
|
||||
import {cssIconSpanBackground, iconSpan} from 'app/client/ui2018/icons';
|
||||
import {CellValue} from 'app/plugin/GristData';
|
||||
import {dom, DomArg, IDomArgs, Observable, styled} from 'grainjs';
|
||||
|
||||
/**
|
||||
* Styling for a simple <A HREF> link.
|
||||
@ -37,6 +37,19 @@ export function gristLink(href: string|Observable<string>, ...args: IDomArgs<HTM
|
||||
);
|
||||
}
|
||||
|
||||
export function gristIconLink(href: string, label = href) {
|
||||
return cssMaybeWrap(
|
||||
gristLink(href,
|
||||
cssIconSpanBackground(
|
||||
iconSpan("FieldLink", testId('tb-link-icon')),
|
||||
dom.cls(cssHoverInText.className),
|
||||
),
|
||||
),
|
||||
linkColor(label),
|
||||
testId("text-link"),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If possible (i.e. if `url` points to somewhere in the current document)
|
||||
* use pushUrl to navigate without reloading or opening a new tab
|
||||
@ -60,17 +73,7 @@ export function makeLinks(text: string) {
|
||||
const domElements: DomArg[] = [];
|
||||
for (const {value, isLink} of findLinks(text)) {
|
||||
if (isLink) {
|
||||
// Wrap link with a span to provide hover on and to override wrapping.
|
||||
domElements.push(cssMaybeWrap(
|
||||
gristLink(value,
|
||||
cssIconBackground(
|
||||
icon("FieldLink", testId('tb-link-icon')),
|
||||
dom.cls(cssHoverInText.className),
|
||||
),
|
||||
),
|
||||
linkColor(value),
|
||||
testId("text-link")
|
||||
));
|
||||
domElements.push(gristIconLink(value));
|
||||
} else {
|
||||
domElements.push(value);
|
||||
}
|
||||
|
@ -31,7 +31,8 @@ import {Computed, Disposable, dom, DomElementArg, makeTestId, MutableObsArray,
|
||||
obsArray, Observable, styled, subscribeElem} from 'grainjs';
|
||||
import debounce from 'lodash/debounce';
|
||||
import noop from 'lodash/noop';
|
||||
import {marked} from 'marked';
|
||||
import {Marked} from 'marked';
|
||||
import {markedHighlight} from 'marked-highlight';
|
||||
import {v4 as uuidv4} from 'uuid';
|
||||
|
||||
const t = makeT('FormulaEditor');
|
||||
@ -802,6 +803,7 @@ class ChatHistory extends Disposable {
|
||||
public lastSuggestedFormula: Computed<string|null>;
|
||||
|
||||
private _element: HTMLElement;
|
||||
private _marked: Marked;
|
||||
|
||||
constructor(private _options: {
|
||||
column: ColumnRec,
|
||||
@ -845,6 +847,17 @@ class ChatHistory extends Disposable {
|
||||
this.lastSuggestedFormula = Computed.create(this, use => {
|
||||
return [...use(this.conversationHistory)].reverse().find(({formula}) => formula)?.formula ?? null;
|
||||
});
|
||||
|
||||
const highlightCodePromise = buildCodeHighlighter({maxLines: 60});
|
||||
this._marked = new Marked(
|
||||
markedHighlight({
|
||||
async: true,
|
||||
highlight: async (code) => {
|
||||
const highlightCode = await highlightCodePromise;
|
||||
return highlightCode(code);
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public thinking(on = true) {
|
||||
@ -864,10 +877,6 @@ class ChatHistory extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
public supportsMarkdown() {
|
||||
return this._options.column.chatHistory.peek().get().state !== undefined;
|
||||
}
|
||||
|
||||
public addResponse(message: ChatMessage) {
|
||||
// Clear any thinking from messages.
|
||||
this.thinking(false);
|
||||
@ -958,6 +967,10 @@ class ChatHistory extends Disposable {
|
||||
);
|
||||
}
|
||||
|
||||
private _supportsMarkdown() {
|
||||
return this._options.column.chatHistory.peek().get().state !== undefined;
|
||||
}
|
||||
|
||||
private _buildIntroMessage() {
|
||||
return cssAiIntroMessage(
|
||||
cssAvatar(cssAiImage()),
|
||||
@ -1010,14 +1023,10 @@ class ChatHistory extends Disposable {
|
||||
* Renders the message as markdown if possible, otherwise as a code block.
|
||||
*/
|
||||
private _render(message: string, ...args: DomElementArg[]) {
|
||||
if (this.supportsMarkdown()) {
|
||||
if (this._supportsMarkdown()) {
|
||||
return dom('div',
|
||||
(el) => subscribeElem(el, gristThemeObs(), async () => {
|
||||
const highlightCode = await buildCodeHighlighter({maxLines: 60});
|
||||
const content = sanitizeHTML(marked(message, {
|
||||
highlight: (code) => highlightCode(code)
|
||||
}));
|
||||
el.innerHTML = content;
|
||||
el.innerHTML = sanitizeHTML(await this._marked.parse(message));
|
||||
}),
|
||||
...args
|
||||
);
|
||||
|
@ -2,7 +2,7 @@ import { DataRowModel } from 'app/client/models/DataRowModel';
|
||||
import { ViewFieldRec } from 'app/client/models/entities/ViewFieldRec';
|
||||
import { constructUrl } from 'app/client/models/gristUrlState';
|
||||
import { testId, theme } from 'app/client/ui2018/cssVars';
|
||||
import { cssIconBackground, icon } from 'app/client/ui2018/icons';
|
||||
import { cssIconSpanBackground, iconSpan } from 'app/client/ui2018/icons';
|
||||
import { cssHoverIn, gristLink } from 'app/client/ui2018/links';
|
||||
import { NTextBox } from 'app/client/widgets/NTextBox';
|
||||
import { CellValue } from 'app/common/DocActions';
|
||||
@ -27,8 +27,8 @@ export class HyperLinkTextBox extends NTextBox {
|
||||
dom.cls('text_wrapping', this.wrapping),
|
||||
dom.maybe((use) => Boolean(use(value)), () =>
|
||||
gristLink(url,
|
||||
cssIconBackground(
|
||||
icon("FieldLink", testId('tb-link-icon')),
|
||||
cssIconSpanBackground(
|
||||
iconSpan("FieldLink", testId('tb-link-icon')),
|
||||
dom.cls(cssHoverOnField.className),
|
||||
),
|
||||
testId('tb-link'),
|
||||
|
179
app/client/widgets/MarkdownTextBox.ts
Normal file
179
app/client/widgets/MarkdownTextBox.ts
Normal file
@ -0,0 +1,179 @@
|
||||
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 { dom, styled, subscribeBindable } from 'grainjs';
|
||||
import { Marked } from 'marked';
|
||||
import { markedHighlight } from 'marked-highlight';
|
||||
|
||||
/**
|
||||
* Creates a widget for displaying Markdown-formatted text.
|
||||
*/
|
||||
export class MarkdownTextBox extends NTextBox {
|
||||
private _marked: Marked;
|
||||
|
||||
constructor(field: ViewFieldRec) {
|
||||
super(field);
|
||||
|
||||
const highlightCodePromise = buildCodeHighlighter({maxLines: 60});
|
||||
this._marked = new Marked(
|
||||
markedHighlight({
|
||||
async: true,
|
||||
highlight: async (code) => {
|
||||
const highlightCode = await highlightCodePromise;
|
||||
return highlightCode(code);
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public buildDom(row: DataRowModel) {
|
||||
const value = row.cells[this.field.colId()];
|
||||
return cssFieldClip(
|
||||
cssFieldClip.cls('-text-wrap', this.wrapping),
|
||||
dom.style('text-align', this.alignment),
|
||||
(el) => dom.autoDisposeElem(el, subscribeBindable(value, async () => {
|
||||
el.innerHTML = sanitizeHTML(await this._marked.parse(String(value.peek()), {gfm: false, renderer}));
|
||||
this.field.viewSection().events.trigger('rowHeightChange');
|
||||
})),
|
||||
// Note: the DOM needs to be rebuilt on theme change, as Ace needs to switch between
|
||||
// light and dark themes. If we switch to using a custom Grist Ace theme (with CSS
|
||||
// variables used for highlighting), we can remove the listener below (and elsewhere).
|
||||
(el) => dom.autoDisposeElem(el, subscribeBindable(gristThemeObs(), async () => {
|
||||
el.innerHTML = sanitizeHTML(await this._marked.parse(String(value.peek()), {gfm: false, renderer}));
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const cssFieldClip = styled('div.field_clip', `
|
||||
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;
|
||||
}
|
||||
& > *: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 {
|
||||
list-style-position: inside;
|
||||
padding-left: 1em;
|
||||
}
|
||||
& li > ol, & li > ul {
|
||||
margin: 0;
|
||||
}
|
||||
&:not(&-text-wrap) li {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
& 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;
|
||||
}
|
||||
`);
|
@ -28,9 +28,7 @@ export class NTextBox extends NewAbstractWidget {
|
||||
this.alignment = fromKo(this.options.prop('alignment'));
|
||||
this.wrapping = fromKo(this.field.wrap);
|
||||
|
||||
this.autoDispose(this.wrapping.addListener(() => {
|
||||
this.field.viewSection().events.trigger('rowHeightChange');
|
||||
}));
|
||||
this._addRowHeightListeners();
|
||||
}
|
||||
|
||||
public buildConfigDom(_gristDoc: GristDoc): DomContents {
|
||||
@ -112,4 +110,12 @@ export class NTextBox extends NewAbstractWidget {
|
||||
makeLinks(use(this.valueFormatter).formatAny(use(value), t)))
|
||||
);
|
||||
}
|
||||
|
||||
private _addRowHeightListeners() {
|
||||
for (const obs of [this.wrapping, fromKo(this.field.config.widget)]) {
|
||||
this.autoDispose(obs.addListener(() => {
|
||||
this.field.viewSection().events.trigger('rowHeightChange');
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,15 @@ export const typeDefs: any = {
|
||||
wrap: undefined,
|
||||
}
|
||||
},
|
||||
Markdown: {
|
||||
cons: 'MarkdownTextBox',
|
||||
editCons: 'TextEditor',
|
||||
icon: 'FieldMarkdown',
|
||||
options: {
|
||||
alignment: 'left',
|
||||
wrap: undefined,
|
||||
}
|
||||
},
|
||||
HyperLink: {
|
||||
cons: 'HyperLinkTextBox',
|
||||
editCons: 'HyperLinkEditor',
|
||||
|
@ -11,6 +11,7 @@ import DateTimeEditor from 'app/client/widgets/DateTimeEditor';
|
||||
import DateTimeTextBox from 'app/client/widgets/DateTimeTextBox';
|
||||
import {HyperLinkEditor} from 'app/client/widgets/HyperLinkEditor';
|
||||
import {HyperLinkTextBox} from 'app/client/widgets/HyperLinkTextBox';
|
||||
import {MarkdownTextBox} from 'app/client/widgets/MarkdownTextBox';
|
||||
import {NewAbstractWidget} from 'app/client/widgets/NewAbstractWidget';
|
||||
import {NewBaseEditor} from 'app/client/widgets/NewBaseEditor';
|
||||
import {NTextBox} from 'app/client/widgets/NTextBox';
|
||||
@ -36,6 +37,7 @@ export const nameToWidget = {
|
||||
'NumericEditor': NumericEditor,
|
||||
'HyperLinkTextBox': HyperLinkTextBox,
|
||||
'HyperLinkEditor': HyperLinkEditor,
|
||||
'MarkdownTextBox': MarkdownTextBox,
|
||||
'Spinner': Spinner,
|
||||
'CheckBox': ToggleCheckBox,
|
||||
'CheckBoxEditor': CheckBoxEditor,
|
||||
|
@ -447,6 +447,9 @@ export const ThemeColors = t.iface([], {
|
||||
"widget-gallery-secondary-header-fg": "string",
|
||||
"widget-gallery-secondary-header-bg": "string",
|
||||
"widget-gallery-secondary-header-bg-hover": "string",
|
||||
"markdown-cell-light-bg": "string",
|
||||
"markdown-cell-light-border": "string",
|
||||
"markdown-cell-medium-border": "string",
|
||||
});
|
||||
|
||||
const exportedTypeSuite: t.ITypeSuite = {
|
||||
|
@ -583,6 +583,11 @@ export interface ThemeColors {
|
||||
'widget-gallery-secondary-header-fg': string;
|
||||
'widget-gallery-secondary-header-bg': string;
|
||||
'widget-gallery-secondary-header-bg-hover': string;
|
||||
|
||||
/* Markdown Cell */
|
||||
'markdown-cell-light-bg': string;
|
||||
'markdown-cell-light-border': string;
|
||||
'markdown-cell-medium-border': string;
|
||||
}
|
||||
|
||||
export const ThemePrefsChecker = createCheckers(ThemePrefsTI).ThemePrefs as CheckerT<ThemePrefs>;
|
||||
|
@ -562,4 +562,9 @@ export const GristDark: ThemeColors = {
|
||||
'widget-gallery-secondary-header-fg': '#FFFFFF',
|
||||
'widget-gallery-secondary-header-bg': '#70707D',
|
||||
'widget-gallery-secondary-header-bg-hover': '#60606D',
|
||||
|
||||
/* Markdown Cell */
|
||||
'markdown-cell-light-bg': '#494958',
|
||||
'markdown-cell-light-border': '#32323F',
|
||||
'markdown-cell-medium-border': '#555563',
|
||||
};
|
||||
|
@ -562,4 +562,9 @@ export const GristLight: ThemeColors = {
|
||||
'widget-gallery-secondary-header-fg': '#FFFFFF',
|
||||
'widget-gallery-secondary-header-bg': '#929299',
|
||||
'widget-gallery-secondary-header-bg-hover': '#7E7E85',
|
||||
|
||||
/* Markdown Cell */
|
||||
'markdown-cell-light-bg': '#F7F7F7',
|
||||
'markdown-cell-light-border': '#E8E8E8',
|
||||
'markdown-cell-medium-border': '#D9D9D9',
|
||||
};
|
||||
|
@ -63,7 +63,6 @@
|
||||
"@types/jsonwebtoken": "7.2.8",
|
||||
"@types/lodash": "4.14.117",
|
||||
"@types/lru-cache": "5.1.1",
|
||||
"@types/marked": "4.0.8",
|
||||
"@types/mime-types": "2.1.0",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/moment-timezone": "0.5.9",
|
||||
@ -165,7 +164,8 @@
|
||||
"knockout": "3.5.0",
|
||||
"locale-currency": "0.0.2",
|
||||
"lodash": "4.17.21",
|
||||
"marked": "4.2.12",
|
||||
"marked": "14.0.0",
|
||||
"marked-highlight": "2.1.4",
|
||||
"minio": "8.0.0",
|
||||
"moment": "2.29.4",
|
||||
"moment-timezone": "0.5.35",
|
||||
|
@ -24,6 +24,7 @@
|
||||
--icon-FieldFunctionEqual: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTUuNSw2IEwxMC41LDYgQzEwLjc3NjE0MjQsNiAxMSw2LjIyMzg1NzYzIDExLDYuNSBDMTEsNi43NzYxNDIzNyAxMC43NzYxNDI0LDcgMTAuNSw3IEw1LjUsNyBDNS4yMjM4NTc2Myw3IDUsNi43NzYxNDIzNyA1LDYuNSBDNSw2LjIyMzg1NzYzIDUuMjIzODU3NjMsNiA1LjUsNiBaIE01LjUsOSBMMTAuNSw5IEMxMC43NzYxNDI0LDkgMTEsOS4yMjM4NTc2MyAxMSw5LjUgQzExLDkuNzc2MTQyMzcgMTAuNzc2MTQyNCwxMCAxMC41LDEwIEw1LjUsMTAgQzUuMjIzODU3NjMsMTAgNSw5Ljc3NjE0MjM3IDUsOS41IEM1LDkuMjIzODU3NjMgNS4yMjM4NTc2Myw5IDUuNSw5IFogTTQsMyBDMy40NDc3MTUyNSwzIDMsMy40NDc3MTUyNSAzLDQgTDMsMTIgQzMsMTIuNTUyMjg0NyAzLjQ0NzcxNTI1LDEzIDQsMTMgTDEyLDEzIEMxMi41NTIyODQ3LDEzIDEzLDEyLjU1MjI4NDcgMTMsMTIgTDEzLDQgQzEzLDMuNDQ3NzE1MjUgMTIuNTUyMjg0NywzIDEyLDMgTDQsMyBaIE00LDIgTDEyLDIgQzEzLjEwNDU2OTUsMiAxNCwyLjg5NTQzMDUgMTQsNCBMMTQsMTIgQzE0LDEzLjEwNDU2OTUgMTMuMTA0NTY5NSwxNCAxMiwxNCBMNCwxNCBDMi44OTU0MzA1LDE0IDIsMTMuMTA0NTY5NSAyLDEyIEwyLDQgQzIsMi44OTU0MzA1IDIuODk1NDMwNSwyIDQsMiBaIiBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48L3N2Zz4=');
|
||||
--icon-FieldInteger: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEzLDEzIEwxMywzIEwxMiwzIEwxMiwyIEwxNCwyIEwxNCwzIEwxNCwxMyBMMTQsMTQgTDEyLDE0IEwxMiwxMyBMMTMsMTMgWiBNMiwxMyBMMiwzIEwyLDIgTDQsMiBMNCwzIEwzLDMgTDMsMTMgTDQsMTMgTDQsMTQgTDIsMTQgTDIsMTMgWiBNOC45MDIzNDM3NSwxMiBMNy44MzU5Mzc1LDEyIEw3LjgzNTkzNzUsNS4xNzM4MjgxMiBMNy43ODMyMDMxMiw1LjE3MzgyODEyIEw1LjkzMTY0MDYyLDYuNTA5NzY1NjIgTDUuOTMxNjQwNjIsNS40MDgyMDMxMiBMNy44MzU5Mzc1LDQuMDAxOTUzMTIgTDguOTAyMzQzNzUsNC4wMDE5NTMxMiBMOC45MDIzNDM3NSwxMiBaIiBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48L3N2Zz4=');
|
||||
--icon-FieldLink: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEyLjI5Mjg5MzIsMyBMOC41LDMgQzguMjIzODU3NjMsMyA4LDIuNzc2MTQyMzcgOCwyLjUgQzgsMi4yMjM4NTc2MyA4LjIyMzg1NzYzLDIgOC41LDIgTDEzLjUsMiBDMTMuNzc2MTQyNCwyIDE0LDIuMjIzODU3NjMgMTQsMi41IEwxNCw3LjUgQzE0LDcuNzc2MTQyMzcgMTMuNzc2MTQyNCw4IDEzLjUsOCBDMTMuMjIzODU3Niw4IDEzLDcuNzc2MTQyMzcgMTMsNy41IEwxMywzLjcwNzEwNjc4IEw3Ljg1MzU1MzM5LDguODUzNTUzMzkgQzcuNjU4MjkxMjQsOS4wNDg4MTU1NCA3LjM0MTcwODc2LDkuMDQ4ODE1NTQgNy4xNDY0NDY2MSw4Ljg1MzU1MzM5IEM2Ljk1MTE4NDQ2LDguNjU4MjkxMjQgNi45NTExODQ0Niw4LjM0MTcwODc2IDcuMTQ2NDQ2NjEsOC4xNDY0NDY2MSBMMTIuMjkyODkzMiwzIFogTTExLDEwLjUgQzExLDEwLjIyMzg1NzYgMTEuMjIzODU3NiwxMCAxMS41LDEwIEMxMS43NzYxNDI0LDEwIDEyLDEwLjIyMzg1NzYgMTIsMTAuNSBMMTIsMTIuNSBDMTIsMTMuMzI4NDI3MSAxMS4zMjg0MjcxLDE0IDEwLjUsMTQgTDMuNSwxNCBDMi42NzE1NzI4OCwxNCAyLDEzLjMyODQyNzEgMiwxMi41IEwyLDUuNSBDMiw0LjY3MTU3Mjg4IDIuNjcxNTcyODgsNCAzLjUsNCBMNS41LDQgQzUuNzc2MTQyMzcsNCA2LDQuMjIzODU3NjMgNiw0LjUgQzYsNC43NzYxNDIzNyA1Ljc3NjE0MjM3LDUgNS41LDUgTDMuNSw1IEMzLjIyMzg1NzYzLDUgMyw1LjIyMzg1NzYzIDMsNS41IEwzLDEyLjUgQzMsMTIuNzc2MTQyNCAzLjIyMzg1NzYzLDEzIDMuNSwxMyBMMTAuNSwxMyBDMTAuNzc2MTQyNCwxMyAxMSwxMi43NzYxNDI0IDExLDEyLjUgTDExLDEwLjUgWiIgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIi8+PC9zdmc+');
|
||||
--icon-FieldMarkdown: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PGcgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIGNsYXNzPSJuYy1pY29uLXdyYXBwZXIiPjxwYXRoIGQ9Ik0uNSAxMS41TC41IDQuNSA0IDguNSA3LjUgNC41IDcuNSAxMS41TTE1LjUgOC41TDEzIDExIDEwLjUgOC41TTEzIDExTDEzIDUuNSIvPjwvZz48L3N2Zz4=');
|
||||
--icon-FieldNumeric: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTQuOTAyMzQzNzUsMTIgTDMuODM1OTM3NSwxMiBMMy44MzU5Mzc1LDUuMTczODI4MTIgTDMuNzgzMjAzMTIsNS4xNzM4MjgxMiBMMS45MzE2NDA2Miw2LjUwOTc2NTYyIEwxLjkzMTY0MDYyLDUuNDA4MjAzMTIgTDMuODM1OTM3NSw0LjAwMTk1MzEyIEw0LjkwMjM0Mzc1LDQuMDAxOTUzMTIgTDQuOTAyMzQzNzUsMTIgWiBNNS41NTI3MzQzOCwxMS4yNzM0Mzc1IEM1LjU1MjczNDM4LDEwLjc2OTUzMTIgNS45Mjc3MzQzOCwxMC40MTc5Njg4IDYuMzkwNjI1LDEwLjQxNzk2ODggQzYuODc2OTUzMTIsMTAuNDE3OTY4OCA3LjI0NjA5Mzc1LDEwLjc2OTUzMTIgNy4yNDYwOTM3NSwxMS4yNzM0Mzc1IEM3LjI0NjA5Mzc1LDExLjc1MzkwNjIgNi44NzY5NTMxMiwxMi4xMDU0Njg4IDYuMzkwNjI1LDEyLjEwNTQ2ODggQzUuOTI3NzM0MzgsMTIuMTA1NDY4OCA1LjU1MjczNDM4LDExLjc1MzkwNjIgNS41NTI3MzQzOCwxMS4yNzM0Mzc1IFogTTguMzA2NjQwNjIsNy44NjMyODEyNSBMOC4zMDY2NDA2Miw4LjE0NDUzMTI1IEM4LjMwNjY0MDYyLDEwLjA2MDU0NjkgOC45OTgwNDY4OCwxMS4yMjA3MDMxIDEwLjA5Mzc1LDExLjIyMDcwMzEgQzExLjE5NTMxMjUsMTEuMjIwNzAzMSAxMS44ODA4NTk0LDEwLjA2MDU0NjkgMTEuODgwODU5NCw4LjE0NDUzMTI1IEwxMS44ODA4NTk0LDcuODYzMjgxMjUgQzExLjg4MDg1OTQsNS45NTMxMjUgMTEuMTk1MzEyNSw0Ljc4MTI1IDEwLjA5Mzc1LDQuNzgxMjUgQzguOTk4MDQ2ODgsNC43ODEyNSA4LjMwNjY0MDYyLDUuOTUzMTI1IDguMzA2NjQwNjIsNy44NjMyODEyNSBaIE03LjIzNDM3NSw4LjE1NjI1IEw3LjIzNDM3NSw3Ljg1NzQyMTg4IEM3LjIzNDM3NSw1LjQzNzUgOC4yODMyMDMxMiwzLjgzNzg5MDYyIDEwLjEwNTQ2ODgsMy44Mzc4OTA2MiBDMTEuOTE2MDE1NiwzLjgzNzg5MDYyIDEyLjk1MzEyNSw1LjQyNTc4MTI1IDEyLjk1MzEyNSw3Ljg1NzQyMTg4IEwxMi45NTMxMjUsOC4xNTYyNSBDMTIuOTUzMTI1LDEwLjU3NjE3MTkgMTEuOTEwMTU2MiwxMi4xNjQwNjI1IDEwLjA5Mzc1LDEyLjE2NDA2MjUgQzguMjgzMjAzMTIsMTIuMTY0MDYyNSA3LjIzNDM3NSwxMC41ODIwMzEyIDcuMjM0Mzc1LDguMTU2MjUgWiIgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIi8+PC9zdmc+');
|
||||
--icon-FieldReference: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTUuODUzNTUzMzksMTAuODUzNTUzNCBDNS42NTgyOTEyNCwxMS4wNDg4MTU1IDUuMzQxNzA4NzYsMTEuMDQ4ODE1NSA1LjE0NjQ0NjYxLDEwLjg1MzU1MzQgQzQuOTUxMTg0NDYsMTAuNjU4MjkxMiA0Ljk1MTE4NDQ2LDEwLjM0MTcwODggNS4xNDY0NDY2MSwxMC4xNDY0NDY2IEwxMC4xNDY0NDY2LDUuMTQ2NDQ2NjEgQzEwLjM0MTcwODgsNC45NTExODQ0NiAxMC42NTgyOTEyLDQuOTUxMTg0NDYgMTAuODUzNTUzNCw1LjE0NjQ0NjYxIEMxMS4wNDg4MTU1LDUuMzQxNzA4NzYgMTEuMDQ4ODE1NSw1LjY1ODI5MTI0IDEwLjg1MzU1MzQsNS44NTM1NTMzOSBMNS44NTM1NTMzOSwxMC44NTM1NTM0IFogTTcuMzUzNTUzMzksNS4zNTM1NTMzOSBDNy4xNTgyOTEyNCw1LjU0ODgxNTU0IDYuODQxNzA4NzYsNS41NDg4MTU1NCA2LjY0NjQ0NjYxLDUuMzUzNTUzMzkgQzYuNDUxMTg0NDYsNS4xNTgyOTEyNCA2LjQ1MTE4NDQ2LDQuODQxNzA4NzYgNi42NDY0NDY2MSw0LjY0NjQ0NjYxIEw4LjMxODUxODIsMi45NzQzNzUwMyBDOS42MTg0NjQ0NSwxLjY3NDk1NTEgMTEuNzI1NTM1NiwxLjY3NDk1NTEgMTMuMDI1NjI1LDIuOTc0NTE4MiBDMTQuMzI1MDQ0OSw0LjI3NDQ2NDQ1IDE0LjMyNTA0NDksNi4zODE1MzU1NSAxMy4wMjU1NTM0LDcuNjgxNTUzMzkgTDExLjM1MzU1MzQsOS4zNTM1NTMzOSBDMTEuMTU4MjkxMiw5LjU0ODgxNTU0IDEwLjg0MTcwODgsOS41NDg4MTU1NCAxMC42NDY0NDY2LDkuMzUzNTUzMzkgQzEwLjQ1MTE4NDUsOS4xNTgyOTEyNCAxMC40NTExODQ1LDguODQxNzA4NzYgMTAuNjQ2NDQ2Niw4LjY0NjQ0NjYxIEwxMi4zMTgzNzUsNi45NzQ1MTgyIEMxMy4yMjc0NjE1LDYuMDY1MDYzNDcgMTMuMjI3NDYxNSw0LjU5MDkzNjUzIDEyLjMxODUxODIsMy42ODE2MjQ5NyBDMTEuNDA5MDYzNSwyLjc3MjUzODQ2IDkuOTM0OTM2NTMsMi43NzI1Mzg0NiA5LjAyNTU1MzM5LDMuNjgxNTUzMzkgTDcuMzUzNTUzMzksNS4zNTM1NTMzOSBaIE00LjY0NjQ0NjYxLDYuNjQ2NDQ2NjEgQzQuODQxNzA4NzYsNi40NTExODQ0NiA1LjE1ODI5MTI0LDYuNDUxMTg0NDYgNS4zNTM1NTMzOSw2LjY0NjQ0NjYxIEM1LjU0ODgxNTU0LDYuODQxNzA4NzYgNS41NDg4MTU1NCw3LjE1ODI5MTI0IDUuMzUzNTUzMzksNy4zNTM1NTMzOSBMMy42ODE2MjQ5Nyw5LjAyNTQ4MTggQzIuNzcyNTM4NDYsOS45MzQ5MzY1MyAyLjc3MjUzODQ2LDExLjQwOTA2MzUgMy42ODE0ODE4LDEyLjMxODM3NSBDNC41OTA5MzY1MywxMy4yMjc0NjE1IDYuMDY1MDYzNDcsMTMuMjI3NDYxNSA2Ljk3NDQ0NjYxLDEyLjMxODQ0NjYgTDguNjQ2NDQ2NjEsMTAuNjQ2NDQ2NiBDOC44NDE3MDg3NiwxMC40NTExODQ1IDkuMTU4MjkxMjQsMTAuNDUxMTg0NSA5LjM1MzU1MzM5LDEwLjY0NjQ0NjYgQzkuNTQ4ODE1NTQsMTAuODQxNzA4OCA5LjU0ODgxNTU0LDExLjE1ODI5MTIgOS4zNTM1NTMzOSwxMS4zNTM1NTM0IEw3LjY4MTQ4MTgsMTMuMDI1NjI1IEM2LjM4MTUzNTU1LDE0LjMyNTA0NDkgNC4yNzQ0NjQ0NSwxNC4zMjUwNDQ5IDIuOTc0Mzc1MDMsMTMuMDI1NDgxOCBDMS42NzQ5NTUxLDExLjcyNTUzNTYgMS42NzQ5NTUxLDkuNjE4NDY0NDUgMi45NzQ0NDY2MSw4LjMxODQ0NjYxIEw0LjY0NjQ0NjYxLDYuNjQ2NDQ2NjEgWiIgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIi8+PC9zdmc+');
|
||||
--icon-FieldSpinner: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTExLjE3NDYwNDMsMTAuMTIwMzcxNyBDMTEuMzg0MjY3Miw5Ljk0MDY2MDYyIDExLjY5OTkxNzIsOS45NjQ5NDEzOSAxMS44Nzk2MjgzLDEwLjE3NDYwNDMgQzEyLjA1OTMzOTQsMTAuMzg0MjY3MiAxMi4wMzUwNTg2LDEwLjY5OTkxNzIgMTEuODI1Mzk1NywxMC44Nzk2MjgzIEw4LjMyNTM5NTY5LDEzLjg3OTYyODMgQzguMTM4MTUwODIsMTQuMDQwMTIzOSA3Ljg2MTg0OTE4LDE0LjA0MDEyMzkgNy42NzQ2MDQzMSwxMy44Nzk2MjgzIEw0LjE3NDYwNDMxLDEwLjg3OTYyODMgQzMuOTY0OTQxMzksMTAuNjk5OTE3MiAzLjk0MDY2MDYyLDEwLjM4NDI2NzIgNC4xMjAzNzE3LDEwLjE3NDYwNDMgQzQuMzAwMDgyNzcsOS45NjQ5NDEzOSA0LjYxNTczMjc3LDkuOTQwNjYwNjIgNC44MjUzOTU2OSwxMC4xMjAzNzE3IEw4LDEyLjg0MTQ2MTEgTDExLjE3NDYwNDMsMTAuMTIwMzcxNyBaIE00LjgyNTM5NTY5LDUuODc5NjI4MyBDNC42MTU3MzI3Nyw2LjA1OTMzOTM4IDQuMzAwMDgyNzcsNi4wMzUwNTg2MSA0LjEyMDM3MTcsNS44MjUzOTU2OSBDMy45NDA2NjA2Miw1LjYxNTczMjc3IDMuOTY0OTQxMzksNS4zMDAwODI3NyA0LjE3NDYwNDMxLDUuMTIwMzcxNyBMNy42NzQ2MDQzMSwyLjEyMDM3MTcgQzcuODYxODQ5MTgsMS45NTk4NzYxIDguMTM4MTUwODIsMS45NTk4NzYxIDguMzI1Mzk1NjksMi4xMjAzNzE3IEwxMS44MjUzOTU3LDUuMTIwMzcxNyBDMTIuMDM1MDU4Niw1LjMwMDA4Mjc3IDEyLjA1OTMzOTQsNS42MTU3MzI3NyAxMS44Nzk2MjgzLDUuODI1Mzk1NjkgQzExLjY5OTkxNzIsNi4wMzUwNTg2MSAxMS4zODQyNjcyLDYuMDU5MzM5MzggMTEuMTc0NjA0Myw1Ljg3OTYyODMgTDgsMy4xNTg1Mzg4OSBMNC44MjUzOTU2OSw1Ljg3OTYyODMgWiIgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIi8+PC9zdmc+');
|
||||
|
1
static/ui-icons/Fields/FieldMarkdown.svg
Normal file
1
static/ui-icons/Fields/FieldMarkdown.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 16 16"><title>markdown</title><g stroke-width="1" stroke-linejoin="round" fill="none" stroke="#000000" stroke-linecap="round" class="nc-icon-wrapper"><polyline points=".5 11.5 .5 4.5 4 8.5 7.5 4.5 7.5 11.5"></polyline><polyline points="15.5 8.5 13 11 10.5 8.5" stroke="#000000"></polyline><line x1="13" y1="11" x2="13" y2="5.5" stroke="#000000"></line></g></svg>
|
After Width: | Height: | Size: 438 B |
@ -125,8 +125,7 @@ describe('CellColor', function() {
|
||||
await gu.getSection('TABLE1').click();
|
||||
let cell = await gu.getCell('B', 1).doClick();
|
||||
await gu.enterCell('foo');
|
||||
await driver.findContent('.test-select-button', /HyperLink/).click();
|
||||
await gu.waitForServer();
|
||||
await gu.setFieldWidgetType('HyperLink');
|
||||
|
||||
// check default color of hyperlink
|
||||
cell = await gu.getCell('B', 1).find('.field_clip');
|
||||
@ -219,8 +218,7 @@ describe('CellColor', function() {
|
||||
await gu.getSection('TABLE1').click();
|
||||
|
||||
// change widget to hyper link
|
||||
await driver.findContent('.test-select-button', /HyperLink/).click();
|
||||
await gu.waitForServer();
|
||||
await gu.setFieldWidgetType('HyperLink');
|
||||
const cell = gu.getCell('A', 1).find('.field_clip');
|
||||
|
||||
// check cell show hyperlink
|
||||
@ -450,8 +448,7 @@ describe('CellColor', function() {
|
||||
await gu.waitForServer();
|
||||
|
||||
// change format to hyperlink
|
||||
await driver.findContent('.test-select-button', /HyperLink/).click();
|
||||
await gu.waitForServer();
|
||||
await gu.setFieldWidgetType('HyperLink');
|
||||
|
||||
// check color is still ok
|
||||
cell = gu.getCell('A', 1).find('.field_clip');
|
||||
|
18
yarn.lock
18
yarn.lock
@ -944,11 +944,6 @@
|
||||
resolved "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz"
|
||||
integrity sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==
|
||||
|
||||
"@types/marked@4.0.8":
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.0.8.tgz#b316887ab3499d0a8f4c70b7bd8508f92d477955"
|
||||
integrity sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==
|
||||
|
||||
"@types/mime-types@2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.0.tgz"
|
||||
@ -5535,10 +5530,15 @@ make-fetch-happen@^9.1.0:
|
||||
socks-proxy-agent "^6.0.0"
|
||||
ssri "^8.0.0"
|
||||
|
||||
marked@4.2.12:
|
||||
version "4.2.12"
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-4.2.12.tgz#d69a64e21d71b06250da995dcd065c11083bebb5"
|
||||
integrity sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==
|
||||
marked-highlight@2.1.4:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/marked-highlight/-/marked-highlight-2.1.4.tgz#33d4d74b55e5acc76ee95fa5117e847795392a42"
|
||||
integrity sha512-D1GOkcdzP+1dzjoColL7umojefFrASDuLeyaHS0Zr/Uo9jkr1V6vpLRCzfi1djmEaWyK0SYMFtHnpkZ+cwFT1w==
|
||||
|
||||
marked@14.0.0:
|
||||
version "14.0.0"
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-14.0.0.tgz#79a1477358a59e0660276f8fec76de2c33f35d83"
|
||||
integrity sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==
|
||||
|
||||
md5.js@^1.3.4:
|
||||
version "1.3.5"
|
||||
|
Loading…
Reference in New Issue
Block a user