Skip to content

Commit

Permalink
0.2.2 (#26)
Browse files Browse the repository at this point in the history
* supporting non-table content after the markdown table, to add metadata about the table.
* sanitizing wikilinks so that they can be used in the cell with alias.
  • Loading branch information
ganesshkumar authored May 2, 2022
1 parent 33776b1 commit fb6db9e
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 77 deletions.
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "markdown-table-editor",
"name": "Markdown Table Editor",
"version": "0.2.1",
"version": "0.2.2",
"minAppVersion": "0.12.0",
"description": "An Obsidian plugin to provide an editor for Markdown tables. It can open CSV, Microsoft Excel/Google Sheets data as Markdown tables from Obsidian Markdown editor.",
"author": "Ganessh Kumar R P <rpganesshkumar@gmail.com>",
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "markdown-table-editor",
"version": "0.2.1",
"version": "0.2.2",
"description": "An Obsidian plugin to provide an editor for Markdown tables. It can open CSV, Microsoft Excel/Google Sheets data as Markdown tables from Obsidian Markdown editor.",
"main": "main.js",
"scripts": {
Expand All @@ -21,7 +21,7 @@
"builtin-modules": "^3.2.0",
"esbuild": "0.13.12",
"esbuild-plugin-less": "^1.1.6",
"obsidian": "^0.13.30",
"obsidian": "^0.14.6",
"tslib": "2.3.1",
"typescript": "4.4.4"
},
Expand Down
123 changes: 91 additions & 32 deletions src/utils/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,90 @@ export function parseMarkdownTable(data: string): any[][] | undefined {
return undefined;
}

export function parseInputData(input: string): any[][] | undefined {
let {data, meta}: {data: string[][], meta: any} = Papa.parse((input || '').trim());
function sanitizeWikiLinks(input: string): string {
const matches = (input || '').matchAll(/\[\[\w*\|\w*\]\]/g);
let match = matches.next();
while (!match.done) {
const value = match.value['0'];
input = input.replace(value, value.replace('|', '\\|'));
match = matches.next()
}
return input;
}

function extractAfterContent(input: string[][]): string[][] {
if (input && input[0]?.length && input[0].length > 1) {
let idx = -1;
for (idx = 0; idx < input?.length; idx++) {
if (input[idx]?.length == 1) {
break;
}
}

return input.splice(idx);
}

return [] as string[][];
}

function removeAlignmentRow(input: string[][]) {
// Remove the second row that represents the alignment
if (input.length > 1) {
input.splice(1, 1);
}
}

function mergeWikiLinkCells(input: string[][]): string[][] {
return input.map((row: string[]) => {
let writeIndex = 0;
const result: string[] = [];

for (let index = 0; index < row.length; index++) {
// Last cell in a row
if (index === row.length - 1) {
result.push(row[index]);
continue;
}

if (row[index].includes('[[') && row[index].endsWith('\\') && row[index + 1].includes(']]')) {
// Cells with wiki links
let current = row[index];
let offset = 1;
while (current.includes('[[') && current.endsWith('\\') && row[index + offset].includes(']]')) {
current = `${current}|${row[index + offset]}`;
offset++;
}
result[writeIndex] = current;
writeIndex++;
index = index + offset;
} else {
// Normal cells
result[writeIndex] = row[index];
writeIndex++;
}
}

return result;
});
}

const papaConfig = {
delimiter: '|',
escapeChar: '\\',
}

export function parseInputData(input: string): { content: string[][], afterContent: string[][] } | undefined {
input = sanitizeWikiLinks(input);

let { data, meta }: { data: string[][], meta: any } = Papa.parse((input || '').trim(), papaConfig);
let afterContent: string[][] = undefined;

if (data && data[0]?.length && data[0].length > 1) {
afterContent = extractAfterContent(data);

if (meta.delimiter === '|') {
// Markdown table
// Remove the second row that represents the alignment
if (data.length > 1) {
data.splice(1, 1);
}
removeAlignmentRow(data);

// Remove the first and last column that are empty when we parsed the data
data = data.map((row: string[]) => {
Expand All @@ -66,30 +140,15 @@ export function parseInputData(input: string): any[][] | undefined {
});

// Handing [[link|alias]] in a cell
data = data.map((row: string[]) => {
let writeIndex = 0;
const result: string[] = [];
for (let index = 0; index < row.length; index++) {
if (index === row.length - 1) {
result.push(row[index]);
continue;
}

if (row[index].includes('[[') && row[index].endsWith('\\') && row[index + 1].includes(']]')) {
result[writeIndex] = `${row[index]}|${row[index + 1]}`;
writeIndex++;
index++;
} else {
result[writeIndex] = row[index];
writeIndex++;
}
}
return result;
});
data = mergeWikiLinkCells(data);
}

return data;
return {
content: data,
afterContent
};
}

return undefined;
}

Expand All @@ -108,12 +167,12 @@ export function sanitize(data: string[][]) {
export const toMarkdown = (values: any[][], colJustify: string[]): string => {
const cols = values[0]?.length;
let maxColWidth = Array(cols).fill(0);

// Find column width for result
for (let rowIdx = 0; rowIdx < values.length; rowIdx++) {
for (let colIdx = 0; colIdx < values[0].length; colIdx++) {
maxColWidth[colIdx] = values[rowIdx][colIdx].length > maxColWidth[colIdx] ?
values[rowIdx][colIdx].length : maxColWidth[colIdx];
maxColWidth[colIdx] = values[rowIdx][colIdx].length > maxColWidth[colIdx] ?
values[rowIdx][colIdx].length : maxColWidth[colIdx];
}
}

Expand Down Expand Up @@ -148,8 +207,8 @@ export const toMarkdown = (values: any[][], colJustify: string[]): string => {
alignMarker = `|${alignMarker}`;

const rows = values.slice(1)
.map(row => lineformatter(row))
.join('\n');
.map(row => lineformatter(row))
.join('\n');

return `${header}\n${alignMarker}\n${rows}`;
}
78 changes: 46 additions & 32 deletions src/views/TableEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@ type Props = {
supressNotices: boolean
}

export const TableEditor = ({leafId, cursor, inputData, updateViewData, supressNotices = false}: Props) => {
let _leafid = leafId;
let _cursor = cursor;
export const TableEditor = ({ leafId, cursor, inputData, updateViewData, supressNotices = false }: Props) => {
let _leafid = leafId;
let _cursor = cursor;

const app = useApp();

const [newRows, setNewRows] = React.useState(3);
const [newCols, setNewCols] = React.useState(3);
const [values, setValues] = React.useState([[''], ['']]);
const [afterValue, setAfterValue] = React.useState('');
const [colJustify, setColJustify] = React.useState([])
const [copyText, setCopyText] = React.useState('Copy as Markdown');
const [autoFocusCell, setAutoFocusCell] = React.useState({row: -1, col: -1});
const [autoFocusCell, setAutoFocusCell] = React.useState({ row: -1, col: -1 });

const onContentChanged = (rowIndex: number, colIndex: number, value: string) => {
const newValues = [...values];
Expand All @@ -33,26 +34,35 @@ export const TableEditor = ({leafId, cursor, inputData, updateViewData, supressN

const computeAutoFocusRow = React.useCallback((values: string[][]) => {
if (!values || !values.length || values.length === 0 || !values[0] || !values[0].length || values[0].length === 0 || !values[0][0]) {
setAutoFocusCell({row: 0, col: 0});
setAutoFocusCell({ row: 0, col: 0 });
} else {
setAutoFocusCell({row: 1, col: 0});
setAutoFocusCell({ row: 1, col: 0 });
}
}, [inputData]);

React.useEffect(() => {
let data = parseInputData(inputData);
if (!data) {
let result = parseInputData(inputData);

if (!result) {
result = { content: undefined, afterContent: [] as string[][] }
}

let { content, afterContent } = result;

if (!content) {
if (!supressNotices) {
new Notice("Selection is not a valid Markdown table or CSV or Excel data. Creating a new table!");
}
data = [[''], ['']];
content = [[''], ['']];
}

data = sanitize(data);
content = sanitize(content);
const processedAfterContent = afterContent.map(row => row.join('')).join(' \n');

setValues(data);
setColJustify(Array(data[0].length).fill('LEFT'));
computeAutoFocusRow(data);
setValues(content);
setColJustify(Array(content[0].length).fill('LEFT'));
setAfterValue(processedAfterContent);
computeAutoFocusRow(content);
}, [inputData]);

React.useEffect(() => {
Expand All @@ -62,12 +72,6 @@ export const TableEditor = ({leafId, cursor, inputData, updateViewData, supressN
updateViewData(toMarkdown(values, colJustify));
}, [values, colJustify]);


const copyClicked = () => {
setCopyText('Copied!');
navigator?.clipboard?.writeText(toMarkdown(values, colJustify));
}

const newTableClicked = () => {
const newValues = Array(newRows).fill([]).map(_ => Array(newCols).fill(''));
setValues(newValues);
Expand All @@ -86,12 +90,22 @@ export const TableEditor = ({leafId, cursor, inputData, updateViewData, supressN
return false;
}

const getOutput = () => {
const tableContent = toMarkdown(values, colJustify);
return `${tableContent} \n${afterValue}`;
}

const copyClicked = () => {
setCopyText('Copied!');
navigator?.clipboard?.writeText(getOutput());
}

const replaceClicked = () => {
const editorLeaf = app.workspace.activeLeaf;

let leaf = app.workspace.getLeafById(_leafid);
app.workspace.setActiveLeaf(leaf, false, true);

let view = app.workspace.getActiveViewOfType(MarkdownView);
let line = parseInt(_cursor);

Expand All @@ -115,13 +129,13 @@ export const TableEditor = ({leafId, cursor, inputData, updateViewData, supressN
const startCursor = { line: lineAbove, ch: 0 };
const endCursor = { line: lineBelow, ch: view.editor.getLine(lineBelow).length };

view.editor.replaceRange(toMarkdown(values, colJustify), startCursor, endCursor);
view.editor.replaceRange(getOutput(), startCursor, endCursor);
}

return (
<>
<div className='mte button-container'>
Rows : <input type='text' onChange={e => setNewRows(parseInt(e.target.value))} placeholder='3'/>
Rows : <input type='text' onChange={e => setNewRows(parseInt(e.target.value))} placeholder='3' />
Columns : <input type='text' onChange={e => setNewCols(parseInt(e.target.value))} placeholder='3' />
<button onClick={newTableClicked}>New Table</button>
<button onClick={clearClicked}>Clear Table</button>
Expand All @@ -133,24 +147,24 @@ export const TableEditor = ({leafId, cursor, inputData, updateViewData, supressN
gridTemplateColumns: `repeat(${values[0]?.length}, 1fr)`
}}>
{
values.map((row, rowIdx) =>
row.map((value: string, colIdx: number) =>
<Cell key={`${rowIdx}-${colIdx}`}
values.map((row, rowIdx) =>
row.map((value: string, colIdx: number) =>
<Cell key={`${rowIdx}-${colIdx}`}
row={rowIdx} col={colIdx}
content={value} values={values} setValues={setValues}
colJustify={colJustify} setColJustify={setColJustify}
onContentChanged={onContentChanged}
autoFocus={shouldAutoFocus(rowIdx, colIdx)}
onFocus={() => setAutoFocusCell({row: rowIdx, col: colIdx})}
content={value} values={values} setValues={setValues}
colJustify={colJustify} setColJustify={setColJustify}
onContentChanged={onContentChanged}
autoFocus={shouldAutoFocus(rowIdx, colIdx)}
onFocus={() => setAutoFocusCell({ row: rowIdx, col: colIdx })}
/>))
.flat()
.flat()
}
</div>
<div className='mte button-container'>
<button onClick={copyClicked}>{copyText}</button>
<button onClick={replaceClicked}>Update Table</button>
</div>

</>
);
};
3 changes: 2 additions & 1 deletion versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"0.1.1": "0.12.0",
"0.1.2": "0.12.0",
"0.2.0": "0.12.0",
"0.2.1": "0.12.0"
"0.2.1": "0.12.0",
"0.2.2": "0.12.0"
}
18 changes: 9 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -587,10 +587,10 @@ mime@^1.4.1:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==

moment@2.29.1:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
moment@2.29.2:
version "2.29.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4"
integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==

ms@2.1.2:
version "2.1.2"
Expand All @@ -616,15 +616,15 @@ object-assign@^4.1.1:
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=

obsidian@^0.13.30:
version "0.13.30"
resolved "https://registry.yarnpkg.com/obsidian/-/obsidian-0.13.30.tgz#215697376c5c0059d7661df400440d97224d3f95"
integrity sha512-uAOrIyeHE9qYzg1Qjfpy/qlyLUFX9oyKWeHYO8NVDoI+pm5VUTMe7XWcsXPwb9iVsVmggVJcdV15Vqm9bljhxQ==
obsidian@^0.14.6:
version "0.14.6"
resolved "https://registry.yarnpkg.com/obsidian/-/obsidian-0.14.6.tgz#010e16da3a1a7725f5e91beb9f14ec8abd00c15d"
integrity sha512-oXPJ8Zt10WhN19bk5l4mZuXRZbbdT1QoMgxGGJ0bB7UcJa0bozDzugS5L/QiV9gDoujpUPxDWNVahEel6r0Fpw==
dependencies:
"@codemirror/state" "^0.19.6"
"@codemirror/view" "^0.19.31"
"@types/codemirror" "0.0.108"
moment "2.29.1"
moment "2.29.2"

papaparse@^5.3.1:
version "5.3.1"
Expand Down

0 comments on commit fb6db9e

Please sign in to comment.