Skip to content

Commit 3c80802

Browse files
Issue 807 - Customizable Markdown options (#808)
1 parent 32ffdd7 commit 3c80802

File tree

10 files changed

+142
-39
lines changed

10 files changed

+142
-39
lines changed

packages/dash-table/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

55
## [Unreleased]
6+
### Added
7+
- [#808](https://github.com/plotly/dash-table/pull/808)Fix a regression introduced with [#787](https://github.com/plotly/dash-table/pull/787) making it impossible to open markdown links in the current tab.
8+
- Adds a new `markdown_options` property that supports:
9+
- `link_target` nested prop with values `_blank`, `_parent`, `_self`, `_top` or an arbitrary string (default: `_blank`)
10+
611
### Fixed
712
- [#806](https://github.com/plotly/dash-table/pull/806) Fix a bug where fixed rows a misaligned after navigating or editing cells [#803](https://github.com/plotly/dash-table/issues/803)
813

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { reduce, toPairs, assoc } from 'ramda';
2+
import { toCamelCase } from 'dash-table/derived/style/py2jsCssProperties';
3+
4+
const objPropsToCamel = (value: any): any => (value !== null && typeof value === 'object') ?
5+
reduce((
6+
acc,
7+
[key, pValue]: [string, any]
8+
) => assoc(toCamelCase(key.split('_')), objPropsToCamel(pValue), acc),
9+
{} as any,
10+
toPairs(value)
11+
) :
12+
Array.isArray(value) ?
13+
value.map(objPropsToCamel, value) :
14+
value;
15+
16+
export default objPropsToCamel;

packages/dash-table/src/dash-table/components/CellFactory.tsx

+13-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React, { CSSProperties } from 'react';
44
import { matrixMap2, matrixMap3 } from 'core/math/matrixZipMap';
55
import { arrayMap2 } from 'core/math/arrayZipMap';
66

7-
import { ICellFactoryProps } from 'dash-table/components/Table/props';
7+
import { ICellFactoryProps, IMarkdownOptions } from 'dash-table/components/Table/props';
88
import derivedCellWrappers from 'dash-table/derived/cell/wrappers';
99
import derivedCellContents from 'dash-table/derived/cell/contents';
1010
import derivedCellOperations from 'dash-table/derived/cell/operations';
@@ -14,6 +14,7 @@ import { derivedRelevantCellStyles } from 'dash-table/derived/style';
1414
import { IEdgesMatrices } from 'dash-table/derived/edges/type';
1515
import { memoizeOne } from 'core/memoizer';
1616
import memoizerCache from 'core/cache/memoizer';
17+
import Markdown from 'dash-table/utils/Markdown';
1718

1819
export default class CellFactory {
1920

@@ -33,6 +34,10 @@ export default class CellFactory {
3334
private readonly relevantStyles = derivedRelevantCellStyles()
3435
) { }
3536

37+
private getMarkdown = memoizeOne((
38+
options: IMarkdownOptions
39+
) => new Markdown(options));
40+
3641
public createCells(dataEdges: IEdgesMatrices | undefined, dataOpEdges: IEdgesMatrices | undefined) {
3742
const {
3843
active_cell,
@@ -44,6 +49,7 @@ export default class CellFactory {
4449
id,
4550
is_focused,
4651
loading_state,
52+
markdown_options,
4753
row_deletable,
4854
row_selectable,
4955
selected_cells,
@@ -121,13 +127,16 @@ export default class CellFactory {
121127
selected_cells
122128
);
123129

130+
const markdown = this.getMarkdown(markdown_options);
131+
124132
const partialCellContents = this.cellContents.partialGet(
125133
visibleColumns,
126134
virtualized.data,
127135
virtualized.offset,
128136
!!is_focused,
129137
dropdowns,
130-
loading_state
138+
loading_state,
139+
markdown
131140
);
132141

133142
const cellContents = this.cellContents.get(
@@ -139,7 +148,8 @@ export default class CellFactory {
139148
virtualized.offset,
140149
!!is_focused,
141150
dropdowns,
142-
loading_state
151+
loading_state,
152+
markdown
143153
);
144154

145155
const ops = this.getDataOpCells(

packages/dash-table/src/dash-table/components/CellMarkdown/index.tsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ interface IProps {
1111
active: boolean;
1212
applyFocus: boolean;
1313
className: string;
14+
markdown: Markdown;
1415
value: any;
1516
}
1617

1718
export default class CellMarkdown extends PureComponent<IProps, {}> {
1819

19-
getMarkdown = memoizeOne((value: string, _ready: any) => ({
20+
getMarkdown = memoizeOne((value: any, md: Markdown, _ready: any) => ({
2021
dangerouslySetInnerHTML: {
21-
__html: Markdown.render(String(value))
22+
__html: md.render(String(value))
2223
}
2324
}));
2425

@@ -41,13 +42,14 @@ export default class CellMarkdown extends PureComponent<IProps, {}> {
4142
render() {
4243
const {
4344
className,
45+
markdown,
4446
value
4547
} = this.props;
4648

4749
return (<div
4850
ref='el'
4951
className={[className, 'cell-markdown'].join(' ')}
50-
{...this.getMarkdown(value, Markdown.isReady)}
52+
{...this.getMarkdown(value, markdown, Markdown.isReady)}
5153
/>);
5254
}
5355

packages/dash-table/src/dash-table/components/Table/props.ts

+6
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ export interface INumberLocale {
155155
separate_4digits?: boolean;
156156
}
157157

158+
export interface IMarkdownOptions {
159+
link_target: '_blank' | '_parent' | '_self' | '_top' | string;
160+
}
161+
158162
export type NumberFormat = ({
159163
locale: INumberLocale;
160164
nully: any;
@@ -315,6 +319,7 @@ export interface IProps {
315319
hidden_columns?: string[];
316320
include_headers_on_copy_paste?: boolean;
317321
locale_format: INumberLocale;
322+
markdown_options: IMarkdownOptions;
318323
merge_duplicate_headers?: boolean;
319324
fixed_columns?: Fixed;
320325
fixed_rows?: Fixed;
@@ -491,6 +496,7 @@ export interface ICellFactoryProps {
491496
id: string;
492497
is_focused?: boolean;
493498
loading_state: boolean;
499+
markdown_options: IMarkdownOptions;
494500
paginator: IPaginator;
495501
row_deletable: boolean;
496502
row_selectable: Selection;

packages/dash-table/src/dash-table/dash/DataTable.js

+22
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ export const defaultProps = {
7070
data: 0
7171
},
7272

73+
markdown_options: {
74+
link_target: '_blank'
75+
},
76+
7377
tooltip: {},
7478
tooltip_conditional: [],
7579
tooltip_data: [],
@@ -423,6 +427,24 @@ export const propTypes = {
423427
separate_4digits: PropTypes.bool
424428
}),
425429

430+
/**
431+
* The `markdown_options` property allows customization of the markdown cells behavior.
432+
* 'link_target': (default: '_blank') the link's behavior (_blank opens the link in a
433+
* new tab, _parent opens the link in the parent frame, _self opens the link in the
434+
* current tab, and _top opens the link in the top frame) or a string
435+
*/
436+
markdown_options: PropTypes.exact({
437+
link_target: PropTypes.oneOfType([
438+
PropTypes.string,
439+
PropTypes.oneOf([
440+
'_blank',
441+
'_parent',
442+
'_self',
443+
'_top'
444+
])
445+
]).isRequired
446+
}),
447+
426448
/**
427449
* The `css` property is a way to embed CSS selectors and rules
428450
* onto the page.

packages/dash-table/src/dash-table/derived/cell/contents.tsx

+23-6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { memoizeOne } from 'core/memoizer';
2121
import getFormatter from 'dash-table/type/formatter';
2222
import { shallowClone } from 'core/math/matrixZipMap';
2323
import CellMarkdown from 'dash-table/components/CellMarkdown';
24+
import Markdown from 'dash-table/utils/Markdown';
2425

2526
const mapData = R.addIndex<Datum, JSX.Element[]>(R.map);
2627
const mapRow = R.addIndex<IColumn, JSX.Element>(R.map);
@@ -68,7 +69,8 @@ class Contents {
6869
_offset: IViewportOffset,
6970
isFocused: boolean,
7071
dropdowns: (IDropdown | undefined)[][],
71-
data_loading: boolean
72+
data_loading: boolean,
73+
markdown: Markdown
7274
): JSX.Element[][] => {
7375
const formatters = R.map(getFormatter, columns);
7476

@@ -84,7 +86,8 @@ class Contents {
8486
rowIndex,
8587
datum,
8688
formatters,
87-
data_loading
89+
data_loading,
90+
markdown
8891
), columns), data);
8992
});
9093

@@ -97,7 +100,8 @@ class Contents {
97100
offset: IViewportOffset,
98101
isFocused: boolean,
99102
dropdowns: (IDropdown | undefined)[][],
100-
data_loading: boolean
103+
data_loading: boolean,
104+
markdown: Markdown
101105
): JSX.Element[][] => {
102106
if (!activeCell) {
103107
return contents;
@@ -124,13 +128,26 @@ class Contents {
124128
iActive,
125129
data[i],
126130
formatters,
127-
data_loading
131+
data_loading,
132+
markdown
128133
);
129134

130135
return contents;
131136
});
132137

133-
private getContent(active: boolean, applyFocus: boolean, isFocused: boolean, column: IColumn, dropdown: IDropdown | undefined, columnIndex: number, rowIndex: number, datum: any, formatters: ((value: any) => any)[], data_loading: boolean) {
138+
private getContent(
139+
active: boolean,
140+
applyFocus: boolean,
141+
isFocused: boolean,
142+
column: IColumn,
143+
dropdown: IDropdown | undefined,
144+
columnIndex: number,
145+
rowIndex: number,
146+
datum: any,
147+
formatters: ((value: any) => any)[],
148+
data_loading: boolean,
149+
markdown: Markdown
150+
) {
134151

135152
const className = [
136153
...(active ? ['input-active'] : []),
@@ -139,7 +156,6 @@ class Contents {
139156
].join(' ');
140157

141158
const cellType = getCellType(active, column.editable, dropdown && dropdown.options, column.presentation, data_loading);
142-
143159
switch (cellType) {
144160
case CellType.Dropdown:
145161
return (<CellDropdown
@@ -170,6 +186,7 @@ class Contents {
170186
active={active}
171187
applyFocus={applyFocus}
172188
className={className}
189+
markdown={markdown}
173190
value={datum[column.id]}
174191
/>);
175192
case CellType.DropdownLabel:

packages/dash-table/src/dash-table/derived/style/py2jsCssProperties.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import cssProperties from './cssProperties';
22

33
export type StyleProperty = string | number;
44

5-
const toCamelCase = (fragments: string[]) => fragments.map((f, i) => i ?
5+
export const toCamelCase = (fragments: string[]) => fragments.map((f, i) => i ?
66
f.charAt(0).toUpperCase() + f.substring(1) :
77
f
88
).join('');
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
11
import { Remarkable } from 'remarkable';
2+
import objPropsToCamel from 'core/objPropsToCamel';
23
import LazyLoader from 'dash-table/LazyLoader';
4+
import { IMarkdownOptions } from 'dash-table/components/Table/props';
35

46
export default class Markdown {
57

6-
static isReady: Promise<boolean> | true = new Promise<boolean>(resolve => {
7-
Markdown.hljsResolve = resolve;
8-
});
9-
10-
static render = (value: string) => {
11-
return Markdown.md.render(value);
12-
}
13-
14-
private static hljsResolve: () => any;
15-
16-
private static hljs: any;
17-
18-
private static readonly md: Remarkable = new Remarkable({
8+
private readonly md: Remarkable = new Remarkable({
199
highlight: (str: string, lang: string) => {
2010
if (Markdown.hljs) {
2111
if (lang && Markdown.hljs.getLanguage(lang)) {
@@ -32,12 +22,28 @@ export default class Markdown {
3222
}
3323
return '';
3424
},
35-
linkTarget:'_blank'
25+
...objPropsToCamel(this.options)
26+
});
27+
28+
constructor(private readonly options: IMarkdownOptions) {
29+
30+
}
31+
32+
public render = (value: string) => this.md.render(value);
33+
34+
public static get isReady() {
35+
return Markdown._isReady;
36+
}
37+
38+
private static hljs: any;
39+
private static hljsResolve: () => any;
40+
private static _isReady: Promise<boolean> | true = new Promise<boolean>(resolve => {
41+
Markdown.hljsResolve = resolve;
3642
});
3743

3844
private static async loadhljs() {
3945
Markdown.hljs = await LazyLoader.hljs;
4046
Markdown.hljsResolve();
41-
Markdown.isReady = true;
47+
Markdown._isReady = true;
4248
}
4349
}

0 commit comments

Comments
 (0)