Compare commits
1 Commits
@portaljs/
...
external-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
806bc89e8c |
3518
package-lock.json
generated
3518
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,5 @@
|
|||||||
# @portaljs/components
|
# @portaljs/components
|
||||||
|
|
||||||
## 0.6.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- [`a044f56e`](https://github.com/datopian/portaljs/commit/a044f56e3cbe0519ddf9d24d78b0bb7eac917e1c) Thanks [@luccasmmg](https://github.com/luccasmmg)! - Added plotly components
|
|
||||||
|
|
||||||
## 0.5.10
|
## 0.5.10
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@portaljs/components",
|
"name": "@portaljs/components",
|
||||||
"version": "0.6.0",
|
"version": "0.5.10",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "https://portaljs.org",
|
"description": "https://portaljs.org",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -40,13 +40,11 @@
|
|||||||
"ol": "^7.4.0",
|
"ol": "^7.4.0",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"pdfjs-dist": "2.15.349",
|
"pdfjs-dist": "2.15.349",
|
||||||
"plotly.js": "^2.30.1",
|
|
||||||
"postcss-url": "^10.1.3",
|
"postcss-url": "^10.1.3",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.43.9",
|
"react-hook-form": "^7.43.9",
|
||||||
"react-leaflet": "^4.2.1",
|
"react-leaflet": "^4.2.1",
|
||||||
"react-plotly.js": "^2.6.0",
|
|
||||||
"react-query": "^3.39.3",
|
"react-query": "^3.39.3",
|
||||||
"react-vega": "^7.6.0",
|
"react-vega": "^7.6.0",
|
||||||
"vega": "5.25.0",
|
"vega": "5.25.0",
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import Plot, { PlotParams } from "react-plotly.js";
|
|
||||||
|
|
||||||
export const Plotly: React.FC<PlotParams> = (props) => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Plot {...props} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
import { QueryClient, QueryClientProvider, useQuery } from "react-query";
|
|
||||||
import { Plotly } from "./Plotly";
|
|
||||||
import Papa, { ParseConfig } from "papaparse";
|
|
||||||
import LoadingSpinner from "./LoadingSpinner";
|
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
|
||||||
|
|
||||||
async function getCsv(url: string, bytes: number) {
|
|
||||||
const response = await fetch(url, {
|
|
||||||
headers: {
|
|
||||||
Range: `bytes=0-${bytes}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await response.text();
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function parseCsv(
|
|
||||||
file: string,
|
|
||||||
parsingConfig: ParseConfig,
|
|
||||||
): Promise<any> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
Papa.parse(file, {
|
|
||||||
...parsingConfig,
|
|
||||||
header: true,
|
|
||||||
dynamicTyping: true,
|
|
||||||
skipEmptyLines: true,
|
|
||||||
transform: (value: string): string => {
|
|
||||||
return value.trim();
|
|
||||||
},
|
|
||||||
complete: (results: any) => {
|
|
||||||
return resolve(results);
|
|
||||||
},
|
|
||||||
error: (error: any) => {
|
|
||||||
return reject(error);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlotlyBarChartProps {
|
|
||||||
url?: string;
|
|
||||||
data?: { [key: string]: number | string }[];
|
|
||||||
rawCsv?: string;
|
|
||||||
randomId?: number;
|
|
||||||
bytes?: number;
|
|
||||||
parsingConfig?: ParseConfig;
|
|
||||||
xAxis: string;
|
|
||||||
yAxis: string;
|
|
||||||
lineLabel?: string;
|
|
||||||
title?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PlotlyBarChart: React.FC<PlotlyBarChartProps> = ({
|
|
||||||
url,
|
|
||||||
data,
|
|
||||||
rawCsv,
|
|
||||||
bytes = 5132288,
|
|
||||||
parsingConfig = {},
|
|
||||||
xAxis,
|
|
||||||
yAxis,
|
|
||||||
lineLabel,
|
|
||||||
title = "",
|
|
||||||
}) => {
|
|
||||||
const randomId = Math.random();
|
|
||||||
return (
|
|
||||||
// Provide the client to your App
|
|
||||||
<QueryClientProvider client={queryClient}>
|
|
||||||
<PlotlyBarChartInner
|
|
||||||
url={url}
|
|
||||||
data={data}
|
|
||||||
rawCsv={rawCsv}
|
|
||||||
randomId={randomId}
|
|
||||||
bytes={bytes}
|
|
||||||
parsingConfig={parsingConfig}
|
|
||||||
xAxis={xAxis}
|
|
||||||
yAxis={yAxis}
|
|
||||||
lineLabel={lineLabel ?? yAxis}
|
|
||||||
title={title}
|
|
||||||
/>
|
|
||||||
</QueryClientProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const PlotlyBarChartInner: React.FC<PlotlyBarChartProps> = ({
|
|
||||||
url,
|
|
||||||
data,
|
|
||||||
rawCsv,
|
|
||||||
randomId,
|
|
||||||
bytes,
|
|
||||||
parsingConfig,
|
|
||||||
xAxis,
|
|
||||||
yAxis,
|
|
||||||
lineLabel,
|
|
||||||
title,
|
|
||||||
}) => {
|
|
||||||
if (data) {
|
|
||||||
return (
|
|
||||||
<div className="w-full" style={{ height: "500px" }}>
|
|
||||||
<Plotly
|
|
||||||
layout={{
|
|
||||||
title,
|
|
||||||
}}
|
|
||||||
data={[
|
|
||||||
{
|
|
||||||
x: data.map((d) => d[xAxis]),
|
|
||||||
y: data.map((d) => d[yAxis]),
|
|
||||||
type: "bar",
|
|
||||||
name: lineLabel,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const { data: csvString, isLoading: isDownloadingCSV } = useQuery(
|
|
||||||
["dataCsv", url, randomId],
|
|
||||||
() => getCsv(url as string, bytes ?? 5132288),
|
|
||||||
{ enabled: !!url },
|
|
||||||
);
|
|
||||||
const { data: parsedData, isLoading: isParsing } = useQuery(
|
|
||||||
["dataPreview", csvString, randomId],
|
|
||||||
() =>
|
|
||||||
parseCsv(
|
|
||||||
rawCsv ? (rawCsv as string) : (csvString as string),
|
|
||||||
parsingConfig ?? {},
|
|
||||||
),
|
|
||||||
{ enabled: rawCsv ? true : !!csvString },
|
|
||||||
);
|
|
||||||
if (isParsing || isDownloadingCSV)
|
|
||||||
<div className="w-full flex justify-center items-center h-[500px]">
|
|
||||||
<LoadingSpinner />
|
|
||||||
</div>;
|
|
||||||
if (parsedData)
|
|
||||||
return (
|
|
||||||
<div className="w-full" style={{ height: "500px" }}>
|
|
||||||
<Plotly
|
|
||||||
layout={{
|
|
||||||
title,
|
|
||||||
}}
|
|
||||||
data={[
|
|
||||||
{
|
|
||||||
x: parsedData.data.map((d: any) => d[xAxis]),
|
|
||||||
y: parsedData.data.map((d: any) => d[yAxis]),
|
|
||||||
type: "bar",
|
|
||||||
name: lineLabel,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div className="w-full flex justify-center items-center h-[500px]">
|
|
||||||
<LoadingSpinner />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
import { QueryClient, QueryClientProvider, useQuery } from "react-query";
|
|
||||||
import { Plotly } from "./Plotly";
|
|
||||||
import Papa, { ParseConfig } from "papaparse";
|
|
||||||
import LoadingSpinner from "./LoadingSpinner";
|
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
|
||||||
|
|
||||||
async function getCsv(url: string, bytes: number) {
|
|
||||||
const response = await fetch(url, {
|
|
||||||
headers: {
|
|
||||||
Range: `bytes=0-${bytes}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await response.text();
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function parseCsv(
|
|
||||||
file: string,
|
|
||||||
parsingConfig: ParseConfig,
|
|
||||||
): Promise<any> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
Papa.parse(file, {
|
|
||||||
...parsingConfig,
|
|
||||||
header: true,
|
|
||||||
dynamicTyping: true,
|
|
||||||
skipEmptyLines: true,
|
|
||||||
transform: (value: string): string => {
|
|
||||||
return value.trim();
|
|
||||||
},
|
|
||||||
complete: (results: any) => {
|
|
||||||
return resolve(results);
|
|
||||||
},
|
|
||||||
error: (error: any) => {
|
|
||||||
return reject(error);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlotlyLineChartProps {
|
|
||||||
url?: string;
|
|
||||||
data?: { [key: string]: number | string }[];
|
|
||||||
rawCsv?: string;
|
|
||||||
randomId?: number;
|
|
||||||
bytes?: number;
|
|
||||||
parsingConfig?: ParseConfig;
|
|
||||||
xAxis: string;
|
|
||||||
yAxis: string;
|
|
||||||
lineLabel?: string;
|
|
||||||
title?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PlotlyLineChart: React.FC<PlotlyLineChartProps> = ({
|
|
||||||
url,
|
|
||||||
data,
|
|
||||||
rawCsv,
|
|
||||||
bytes = 5132288,
|
|
||||||
parsingConfig = {},
|
|
||||||
xAxis,
|
|
||||||
yAxis,
|
|
||||||
lineLabel,
|
|
||||||
title = "",
|
|
||||||
}) => {
|
|
||||||
const randomId = Math.random();
|
|
||||||
return (
|
|
||||||
// Provide the client to your App
|
|
||||||
<QueryClientProvider client={queryClient}>
|
|
||||||
<LineChartInner
|
|
||||||
url={url}
|
|
||||||
data={data}
|
|
||||||
rawCsv={rawCsv}
|
|
||||||
randomId={randomId}
|
|
||||||
bytes={bytes}
|
|
||||||
parsingConfig={parsingConfig}
|
|
||||||
xAxis={xAxis}
|
|
||||||
yAxis={yAxis}
|
|
||||||
lineLabel={lineLabel ?? yAxis}
|
|
||||||
title={title}
|
|
||||||
/>
|
|
||||||
</QueryClientProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const LineChartInner: React.FC<PlotlyLineChartProps> = ({
|
|
||||||
url,
|
|
||||||
data,
|
|
||||||
rawCsv,
|
|
||||||
randomId,
|
|
||||||
bytes,
|
|
||||||
parsingConfig,
|
|
||||||
xAxis,
|
|
||||||
yAxis,
|
|
||||||
lineLabel,
|
|
||||||
title,
|
|
||||||
}) => {
|
|
||||||
if (data) {
|
|
||||||
return (
|
|
||||||
<div className="w-full" style={{ height: "500px" }}>
|
|
||||||
<Plotly
|
|
||||||
layout={{
|
|
||||||
title,
|
|
||||||
}}
|
|
||||||
data={[
|
|
||||||
{
|
|
||||||
x: data.map((d) => d[xAxis]),
|
|
||||||
y: data.map((d) => d[yAxis]),
|
|
||||||
mode: "lines",
|
|
||||||
name: lineLabel,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const { data: csvString, isLoading: isDownloadingCSV } = useQuery(
|
|
||||||
["dataCsv", url, randomId],
|
|
||||||
() => getCsv(url as string, bytes ?? 5132288),
|
|
||||||
{ enabled: !!url },
|
|
||||||
);
|
|
||||||
const { data: parsedData, isLoading: isParsing } = useQuery(
|
|
||||||
["dataPreview", csvString, randomId],
|
|
||||||
() =>
|
|
||||||
parseCsv(
|
|
||||||
rawCsv ? (rawCsv as string) : (csvString as string),
|
|
||||||
parsingConfig ?? {},
|
|
||||||
),
|
|
||||||
{ enabled: rawCsv ? true : !!csvString },
|
|
||||||
);
|
|
||||||
if (isParsing || isDownloadingCSV)
|
|
||||||
<div className="w-full flex justify-center items-center h-[500px]">
|
|
||||||
<LoadingSpinner />
|
|
||||||
</div>;
|
|
||||||
if (parsedData)
|
|
||||||
return (
|
|
||||||
<div className="w-full" style={{ height: "500px" }}>
|
|
||||||
<Plotly
|
|
||||||
layout={{
|
|
||||||
title,
|
|
||||||
}}
|
|
||||||
data={[
|
|
||||||
{
|
|
||||||
x: parsedData.data.map((d: any) => d[xAxis]),
|
|
||||||
y: parsedData.data.map((d: any) => d[yAxis]),
|
|
||||||
mode: "lines",
|
|
||||||
name: lineLabel,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div className="w-full flex justify-center items-center h-[500px]">
|
|
||||||
<LoadingSpinner />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -10,6 +10,3 @@ export * from './components/PdfViewer';
|
|||||||
export * from "./components/Excel";
|
export * from "./components/Excel";
|
||||||
export * from "./components/BucketViewer";
|
export * from "./components/BucketViewer";
|
||||||
export * from "./components/Iframe";
|
export * from "./components/Iframe";
|
||||||
export * from "./components/Plotly";
|
|
||||||
export * from "./components/PlotlyLineChart";
|
|
||||||
export * from "./components/PlotlyBarChart";
|
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react';
|
|
||||||
|
|
||||||
import { PlotlyBarChart, PlotlyBarChartProps } from '../src/components/PlotlyBarChart';
|
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
|
|
||||||
const meta: Meta = {
|
|
||||||
title: 'Components/PlotlyBarChart',
|
|
||||||
component: PlotlyBarChart,
|
|
||||||
tags: ['autodocs'],
|
|
||||||
argTypes: {
|
|
||||||
url: {
|
|
||||||
description:
|
|
||||||
'CSV Url to be parsed and used as data source',
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
description:
|
|
||||||
'Data to be displayed. as an array of key value pairs \n\n E.g.: [{ year: 1850, temperature: -0.41765878 }, { year: 1851, temperature: -0.2333498 }, ...]',
|
|
||||||
},
|
|
||||||
rawCsv: {
|
|
||||||
description:
|
|
||||||
'Raw csv data to be parsed and used as data source',
|
|
||||||
},
|
|
||||||
bytes: {
|
|
||||||
description:
|
|
||||||
'How many bytes to read from the url',
|
|
||||||
},
|
|
||||||
parsingConfig: {
|
|
||||||
description: 'If using url or rawCsv, this parsing config will be used to parse the data. Optional, check https://www.papaparse.com/ for more info',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
description: 'Title to display on the chart. Optional.',
|
|
||||||
},
|
|
||||||
lineLabel: {
|
|
||||||
description: 'Label to display on the line, Optional, will use yAxis if not provided',
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
description:
|
|
||||||
'Name of the X axis on the data. Required when the "data" parameter is an URL.',
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
description:
|
|
||||||
'Name of the Y axis on the data. Required when the "data" parameter is an URL.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
|
|
||||||
type Story = StoryObj<PlotlyBarChartProps>;
|
|
||||||
|
|
||||||
export const FromDataPoints: Story = {
|
|
||||||
name: 'Line chart from array of data points',
|
|
||||||
args: {
|
|
||||||
data: [
|
|
||||||
{year: '1850', temperature: -0.41765878},
|
|
||||||
{year: '1851', temperature: -0.2333498},
|
|
||||||
{year: '1852', temperature: -0.22939907},
|
|
||||||
{year: '1853', temperature: -0.27035445},
|
|
||||||
{year: '1854', temperature: -0.29163003},
|
|
||||||
],
|
|
||||||
xAxis: 'year',
|
|
||||||
yAxis: 'temperature',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FromURL: Story = {
|
|
||||||
name: 'Line chart from URL',
|
|
||||||
args: {
|
|
||||||
title: 'Apple Stock Prices',
|
|
||||||
url: 'https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv',
|
|
||||||
xAxis: 'Date',
|
|
||||||
yAxis: 'AAPL.Open',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react';
|
|
||||||
|
|
||||||
import { PlotlyLineChart, PlotlyLineChartProps } from '../src/components/PlotlyLineChart';
|
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
|
|
||||||
const meta: Meta = {
|
|
||||||
title: 'Components/PlotlyLineChart',
|
|
||||||
component: PlotlyLineChart,
|
|
||||||
tags: ['autodocs'],
|
|
||||||
argTypes: {
|
|
||||||
url: {
|
|
||||||
description:
|
|
||||||
'CSV Url to be parsed and used as data source',
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
description:
|
|
||||||
'Data to be displayed. as an array of key value pairs \n\n E.g.: [{ year: 1850, temperature: -0.41765878 }, { year: 1851, temperature: -0.2333498 }, ...]',
|
|
||||||
},
|
|
||||||
rawCsv: {
|
|
||||||
description:
|
|
||||||
'Raw csv data to be parsed and used as data source',
|
|
||||||
},
|
|
||||||
bytes: {
|
|
||||||
description:
|
|
||||||
'How many bytes to read from the url',
|
|
||||||
},
|
|
||||||
parsingConfig: {
|
|
||||||
description: 'If using url or rawCsv, this parsing config will be used to parse the data. Optional, check https://www.papaparse.com/ for more info',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
description: 'Title to display on the chart. Optional.',
|
|
||||||
},
|
|
||||||
lineLabel: {
|
|
||||||
description: 'Label to display on the line, Optional, will use yAxis if not provided',
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
description:
|
|
||||||
'Name of the X axis on the data. Required when the "data" parameter is an URL.',
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
description:
|
|
||||||
'Name of the Y axis on the data. Required when the "data" parameter is an URL.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
|
|
||||||
type Story = StoryObj<PlotlyLineChartProps>;
|
|
||||||
|
|
||||||
export const FromDataPoints: Story = {
|
|
||||||
name: 'Line chart from array of data points',
|
|
||||||
args: {
|
|
||||||
data: [
|
|
||||||
{year: '1850', temperature: -0.41765878},
|
|
||||||
{year: '1851', temperature: -0.2333498},
|
|
||||||
{year: '1852', temperature: -0.22939907},
|
|
||||||
{year: '1853', temperature: -0.27035445},
|
|
||||||
{year: '1854', temperature: -0.29163003},
|
|
||||||
],
|
|
||||||
xAxis: 'year',
|
|
||||||
yAxis: 'temperature',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FromURL: Story = {
|
|
||||||
name: 'Line chart from URL',
|
|
||||||
args: {
|
|
||||||
title: 'Oil Price x Year',
|
|
||||||
url: 'https://raw.githubusercontent.com/datasets/oil-prices/main/data/wti-year.csv',
|
|
||||||
xAxis: 'Date',
|
|
||||||
yAxis: 'Price',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react';
|
|
||||||
|
|
||||||
import { Plotly } from '../src/components/Plotly';
|
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
|
|
||||||
const meta: Meta = {
|
|
||||||
title: 'Components/Plotly',
|
|
||||||
component: Plotly,
|
|
||||||
tags: ['autodocs'],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
|
|
||||||
type Story = StoryObj<any>;
|
|
||||||
|
|
||||||
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
|
|
||||||
export const Primary: Story = {
|
|
||||||
name: 'Chart built with Plotly',
|
|
||||||
args: {
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
x: [1, 2, 3],
|
|
||||||
y: [2, 6, 3],
|
|
||||||
type: 'scatter',
|
|
||||||
mode: 'lines+markers',
|
|
||||||
marker: { color: 'red' },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
layout: {
|
|
||||||
title: 'Chart built with Plotly',
|
|
||||||
xaxis: {
|
|
||||||
title: 'x Axis',
|
|
||||||
},
|
|
||||||
yaxis: {
|
|
||||||
title: 'y Axis',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -110,6 +110,7 @@ function fromMarkdown(opts: FromMarkdownOptions = {}) {
|
|||||||
? "/" + possibleWikiLinkPermalinks[0]
|
? "/" + possibleWikiLinkPermalinks[0]
|
||||||
: possibleWikiLinkPermalinks[0]) ||
|
: possibleWikiLinkPermalinks[0]) ||
|
||||||
"";
|
"";
|
||||||
|
const isExternal = /^https?:\/\//.test(link);
|
||||||
|
|
||||||
wikiLink.data.exists = !!matchingPermalink;
|
wikiLink.data.exists = !!matchingPermalink;
|
||||||
wikiLink.data.permalink = link;
|
wikiLink.data.permalink = link;
|
||||||
@@ -130,7 +131,7 @@ function fromMarkdown(opts: FromMarkdownOptions = {}) {
|
|||||||
wikiLink.data.hName = "a";
|
wikiLink.data.hName = "a";
|
||||||
wikiLink.data.hProperties = {
|
wikiLink.data.hProperties = {
|
||||||
className: classNames + " " + "transclusion",
|
className: classNames + " " + "transclusion",
|
||||||
href: hrefTemplate(link) + headingId,
|
href: hrefTemplate(link) + headingId
|
||||||
};
|
};
|
||||||
wikiLink.data.hChildren = [{ type: "text", value: displayName }];
|
wikiLink.data.hChildren = [{ type: "text", value: displayName }];
|
||||||
|
|
||||||
@@ -163,7 +164,11 @@ function fromMarkdown(opts: FromMarkdownOptions = {}) {
|
|||||||
wikiLink.data.hProperties = {
|
wikiLink.data.hProperties = {
|
||||||
className: classNames,
|
className: classNames,
|
||||||
href: hrefTemplate(link) + headingId,
|
href: hrefTemplate(link) + headingId,
|
||||||
|
target: "_blank",
|
||||||
};
|
};
|
||||||
|
if(isExternal){
|
||||||
|
wikiLink.data.hProperties.target = "_blank"; // Open in a new tab
|
||||||
|
}
|
||||||
wikiLink.data.hChildren = [{ type: "text", value: displayName }];
|
wikiLink.data.hChildren = [{ type: "text", value: displayName }];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ function html(opts: HtmlOptions = {}) {
|
|||||||
: possibleWikiLinkPermalinks[0]) ||
|
: possibleWikiLinkPermalinks[0]) ||
|
||||||
"";
|
"";
|
||||||
|
|
||||||
|
const isExternal = /^https?:\/\//.test(link);
|
||||||
|
const openInNewTab = isExternal ? 'target="_blank"' : '';
|
||||||
|
|
||||||
// remove leading # if the target is a heading on the same page
|
// remove leading # if the target is a heading on the same page
|
||||||
const displayName = alias || target.replace(/^#/, "");
|
const displayName = alias || target.replace(/^#/, "");
|
||||||
// replace spaces with dashes and lowercase headings
|
// replace spaces with dashes and lowercase headings
|
||||||
|
|||||||
@@ -331,4 +331,14 @@ describe("micromark-extension-wiki-link", () => {
|
|||||||
expect(serialized).toBe(`<p><a href="li nk-w(i)th-àcèô íã_a(n)d_underline!:ª%@'*º$ °~./\\#li-nk-w(i)th-àcèô-íã_a(n)d_underline!:ª%@'*º$-°~./\\" class="internal new">li nk-w(i)th-àcèô íã_a(n)d_underline!:ª%@'*º$ °~./\\#LI NK-W(i)th-àcèô íã_a(n)d_uNdErlinE!:ª%@'*º$ °~./\\</a></p>`);
|
expect(serialized).toBe(`<p><a href="li nk-w(i)th-àcèô íã_a(n)d_underline!:ª%@'*º$ °~./\\#li-nk-w(i)th-àcèô-íã_a(n)d_underline!:ª%@'*º$-°~./\\" class="internal new">li nk-w(i)th-àcèô íã_a(n)d_underline!:ª%@'*º$ °~./\\#LI NK-W(i)th-àcèô íã_a(n)d_uNdErlinE!:ª%@'*º$ °~./\\</a></p>`);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("External links", () => {
|
||||||
|
test("parses an external link that opens in a new tab", () => {
|
||||||
|
const serialized = micromark("[google](https://www.google.com/)", "ascii", {
|
||||||
|
extensions: [syntax()],
|
||||||
|
htmlExtensions: [html() as any],
|
||||||
|
});
|
||||||
|
expect(serialized).toBe(`<p><a href="https://www.google.com/" target="_blank">google</a></p>`);
|
||||||
|
});
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user