2018-09-29 14:15:36 +02:00
|
|
|
const stringPadding = require('string-padding');
|
2018-05-23 13:14:38 +02:00
|
|
|
const urlUtils = require('lib/urlUtils');
|
2018-09-24 21:15:23 +02:00
|
|
|
const MarkdownIt = require('markdown-it');
|
2020-01-30 23:05:23 +02:00
|
|
|
const { setupLinkify } = require('lib/joplin-renderer');
|
2018-05-23 13:14:38 +02:00
|
|
|
|
2020-06-06 17:00:20 +02:00
|
|
|
// Taken from codemirror/addon/edit/continuelist.js
|
2020-06-16 14:00:17 +02:00
|
|
|
const listRegex = /^(\s*)([*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]\s))(\s*)/;
|
2020-06-06 17:00:20 +02:00
|
|
|
const emptyListRegex = /^(\s*)([*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/;
|
|
|
|
|
2020-10-09 19:35:46 +02:00
|
|
|
export interface MarkdownTableHeader {
|
|
|
|
name: string,
|
|
|
|
label: string,
|
|
|
|
filter?: Function,
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface MarkdownTableRow {
|
|
|
|
[key:string]: string,
|
|
|
|
}
|
|
|
|
|
2017-08-02 19:47:25 +02:00
|
|
|
const markdownUtils = {
|
2020-02-08 00:15:41 +02:00
|
|
|
// Titles for markdown links only need escaping for [ and ]
|
2020-10-09 19:35:46 +02:00
|
|
|
escapeTitleText(text:string) {
|
2020-02-08 00:15:41 +02:00
|
|
|
return text.replace(/(\[|\])/g, '\\$1');
|
|
|
|
},
|
|
|
|
|
2020-10-09 19:35:46 +02:00
|
|
|
escapeLinkUrl(url:string) {
|
2017-08-02 19:47:25 +02:00
|
|
|
url = url.replace(/\(/g, '%28');
|
|
|
|
url = url.replace(/\)/g, '%29');
|
2020-05-25 10:52:10 +02:00
|
|
|
url = url.replace(/ /g, '%20');
|
2017-08-02 19:47:25 +02:00
|
|
|
return url;
|
|
|
|
},
|
|
|
|
|
2020-10-09 19:35:46 +02:00
|
|
|
prependBaseUrl(md:string, baseUrl:string) {
|
2019-07-30 09:35:42 +02:00
|
|
|
// eslint-disable-next-line no-useless-escape
|
2020-10-09 19:35:46 +02:00
|
|
|
return md.replace(/(\]\()([^\s\)]+)(.*?\))/g, (_match:any, before:string, url:string, after:string) => {
|
2018-05-23 13:14:38 +02:00
|
|
|
return before + urlUtils.prependBaseUrl(url, baseUrl) + after;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2020-10-09 19:35:46 +02:00
|
|
|
extractImageUrls(md:string) {
|
2018-09-24 21:15:23 +02:00
|
|
|
const markdownIt = new MarkdownIt();
|
2019-05-10 02:06:06 +02:00
|
|
|
setupLinkify(markdownIt); // Necessary to support file:/// links
|
|
|
|
|
2018-09-24 21:15:23 +02:00
|
|
|
const env = {};
|
|
|
|
const tokens = markdownIt.parse(md, env);
|
2020-10-09 19:35:46 +02:00
|
|
|
const output:string[] = [];
|
2018-09-24 21:15:23 +02:00
|
|
|
|
2020-10-09 19:35:46 +02:00
|
|
|
const searchUrls = (tokens:any[]) => {
|
2018-09-24 21:15:23 +02:00
|
|
|
for (let i = 0; i < tokens.length; i++) {
|
|
|
|
const token = tokens[i];
|
|
|
|
|
|
|
|
if (token.type === 'image') {
|
|
|
|
for (let j = 0; j < token.attrs.length; j++) {
|
|
|
|
const a = token.attrs[j];
|
|
|
|
if (a[0] === 'src' && a.length >= 2 && a[1]) {
|
|
|
|
output.push(a[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-07-29 15:43:53 +02:00
|
|
|
|
2018-09-24 21:15:23 +02:00
|
|
|
if (token.children && token.children.length) {
|
|
|
|
searchUrls(token.children);
|
|
|
|
}
|
|
|
|
}
|
2019-07-29 15:43:53 +02:00
|
|
|
};
|
2018-09-24 21:15:23 +02:00
|
|
|
|
|
|
|
searchUrls(tokens);
|
|
|
|
|
2018-05-23 15:25:59 +02:00
|
|
|
return output;
|
|
|
|
},
|
|
|
|
|
2020-06-06 17:00:20 +02:00
|
|
|
// The match results has 5 items
|
|
|
|
// Full match array is
|
|
|
|
// [Full match, whitespace, list token, ol line number, whitespace following token]
|
2020-10-09 19:35:46 +02:00
|
|
|
olLineNumber(line:string) {
|
2020-06-06 17:00:20 +02:00
|
|
|
const match = line.match(listRegex);
|
|
|
|
return match ? Number(match[3]) : 0;
|
|
|
|
},
|
|
|
|
|
2020-10-09 19:35:46 +02:00
|
|
|
extractListToken(line:string) {
|
2020-06-06 17:00:20 +02:00
|
|
|
const match = line.match(listRegex);
|
|
|
|
return match ? match[2] : '';
|
|
|
|
},
|
|
|
|
|
2020-10-09 19:35:46 +02:00
|
|
|
isListItem(line:string) {
|
2020-06-06 17:00:20 +02:00
|
|
|
return listRegex.test(line);
|
|
|
|
},
|
|
|
|
|
2020-10-09 19:35:46 +02:00
|
|
|
isEmptyListItem(line:string) {
|
2020-06-06 17:00:20 +02:00
|
|
|
return emptyListRegex.test(line);
|
2018-06-14 09:52:12 +02:00
|
|
|
},
|
|
|
|
|
2020-10-09 19:35:46 +02:00
|
|
|
createMarkdownTable(headers:MarkdownTableHeader[], rows:MarkdownTableRow[]):string {
|
2020-03-14 01:46:14 +02:00
|
|
|
const output = [];
|
2018-09-28 22:03:28 +02:00
|
|
|
|
|
|
|
const headersMd = [];
|
|
|
|
const lineMd = [];
|
|
|
|
for (let i = 0; i < headers.length; i++) {
|
|
|
|
const h = headers[i];
|
|
|
|
headersMd.push(stringPadding(h.label, 3, ' ', stringPadding.RIGHT));
|
|
|
|
lineMd.push('---');
|
|
|
|
}
|
|
|
|
|
|
|
|
output.push(headersMd.join(' | '));
|
|
|
|
output.push(lineMd.join(' | '));
|
|
|
|
|
|
|
|
for (let i = 0; i < rows.length; i++) {
|
|
|
|
const row = rows[i];
|
|
|
|
const rowMd = [];
|
|
|
|
for (let j = 0; j < headers.length; j++) {
|
|
|
|
const h = headers[j];
|
|
|
|
const value = h.filter ? h.filter(row[h.name]) : row[h.name];
|
|
|
|
rowMd.push(stringPadding(value, 3, ' ', stringPadding.RIGHT));
|
|
|
|
}
|
|
|
|
output.push(rowMd.join(' | '));
|
|
|
|
}
|
|
|
|
|
|
|
|
return output.join('\n');
|
|
|
|
},
|
2020-05-09 16:55:00 +02:00
|
|
|
|
2020-10-09 19:35:46 +02:00
|
|
|
titleFromBody(body:string) {
|
2020-05-09 16:55:00 +02:00
|
|
|
if (!body) return '';
|
|
|
|
const mdLinkRegex = /!?\[([^\]]+?)\]\(.+?\)/g;
|
|
|
|
const emptyMdLinkRegex = /!?\[\]\((.+?)\)/g;
|
|
|
|
const filterRegex = /^[# \n\t*`-]*/;
|
|
|
|
const lines = body.trim().split('\n');
|
|
|
|
const title = lines[0].trim();
|
|
|
|
return title.replace(filterRegex, '').replace(mdLinkRegex, '$1').replace(emptyMdLinkRegex, '$1').substring(0,80);
|
|
|
|
},
|
2017-08-02 19:47:25 +02:00
|
|
|
};
|
|
|
|
|
2020-10-09 19:35:46 +02:00
|
|
|
export default markdownUtils;
|