@contentful/field-editor-markdown 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/MarkdownActions.js +235 -0
- package/dist/cjs/MarkdownEditor.js +180 -0
- package/dist/cjs/__fixtures__/FakeSdk.js +183 -0
- package/dist/cjs/__fixtures__/asset/index.js +37 -0
- package/dist/cjs/__fixtures__/content-type/index.js +16 -0
- package/dist/cjs/__fixtures__/entry/index.js +33 -0
- package/dist/cjs/__fixtures__/fixtures.js +71 -0
- package/dist/cjs/__fixtures__/locale/index.js +40 -0
- package/dist/cjs/__fixtures__/space/index.js +16 -0
- package/dist/cjs/codemirrorImports.js +9 -0
- package/dist/cjs/components/HeadingSelector.js +66 -0
- package/dist/cjs/components/InsertLinkSelector.js +86 -0
- package/dist/cjs/components/MarkdownBottomBar.js +111 -0
- package/dist/cjs/components/MarkdownConstraints.js +79 -0
- package/dist/cjs/components/MarkdownPreview.js +249 -0
- package/dist/cjs/components/MarkdownTabs.js +128 -0
- package/dist/cjs/components/MarkdownTextarea/CodeMirrorWrapper.js +383 -0
- package/dist/cjs/components/MarkdownTextarea/MarkdownCommands.js +233 -0
- package/dist/cjs/components/MarkdownTextarea/MarkdownTextarea.js +190 -0
- package/dist/cjs/components/MarkdownTextarea/createMarkdownEditor.js +101 -0
- package/dist/cjs/components/MarkdownToolbar.js +367 -0
- package/dist/cjs/components/icons.js +193 -0
- package/dist/cjs/dialogs/CheatsheetModalDialog.js +239 -0
- package/dist/cjs/dialogs/ConfirmInsertAssetModalDialog.js +94 -0
- package/dist/cjs/dialogs/EmdebExternalContentDialog.js +202 -0
- package/dist/cjs/dialogs/InsertLinkModalDialog.js +149 -0
- package/dist/cjs/dialogs/InsertTableModalDialog.js +140 -0
- package/dist/cjs/dialogs/SpecialCharacterModalDialog.js +146 -0
- package/dist/cjs/dialogs/ZenModeModalDialog.js +257 -0
- package/dist/cjs/dialogs/openMarkdownDialog.js +121 -0
- package/dist/cjs/dialogs/renderMarkdownDialog.js +108 -0
- package/dist/cjs/index.js +29 -0
- package/dist/cjs/types.js +20 -0
- package/dist/cjs/utils/insertAssetLinks.js +122 -0
- package/dist/cjs/utils/insertAssetLinks.spec.js +22 -0
- package/dist/cjs/utils/isValidUrl.js +22 -0
- package/dist/cjs/utils/linkOrganizer.js +187 -0
- package/dist/cjs/utils/linkOrganizer.spec.js +96 -0
- package/dist/cjs/utils/replaceMailtoAmp.js +15 -0
- package/dist/cjs/utils/replaceMailtoAmp.spec.js +22 -0
- package/dist/cjs/utils/specialCharacters.js +228 -0
- package/dist/cjs/utils/userAgent.js +28 -0
- package/dist/esm/MarkdownActions.js +186 -0
- package/dist/esm/MarkdownEditor.js +118 -0
- package/dist/esm/__fixtures__/FakeSdk.js +173 -0
- package/dist/esm/__fixtures__/asset/index.js +6 -0
- package/dist/esm/__fixtures__/content-type/index.js +2 -0
- package/dist/esm/__fixtures__/entry/index.js +5 -0
- package/dist/esm/__fixtures__/fixtures.js +6 -0
- package/dist/esm/__fixtures__/locale/index.js +15 -0
- package/dist/esm/__fixtures__/space/index.js +2 -0
- package/dist/esm/codemirrorImports.js +5 -0
- package/dist/esm/components/HeadingSelector.js +17 -0
- package/dist/esm/components/InsertLinkSelector.js +37 -0
- package/dist/esm/components/MarkdownBottomBar.js +46 -0
- package/dist/esm/components/MarkdownConstraints.js +25 -0
- package/dist/esm/components/MarkdownPreview.js +195 -0
- package/dist/esm/components/MarkdownTabs.js +74 -0
- package/dist/esm/components/MarkdownTextarea/CodeMirrorWrapper.js +329 -0
- package/dist/esm/components/MarkdownTextarea/MarkdownCommands.js +218 -0
- package/dist/esm/components/MarkdownTextarea/MarkdownTextarea.js +136 -0
- package/dist/esm/components/MarkdownTextarea/createMarkdownEditor.js +52 -0
- package/dist/esm/components/MarkdownToolbar.js +302 -0
- package/dist/esm/components/icons.js +112 -0
- package/dist/esm/dialogs/CheatsheetModalDialog.js +177 -0
- package/dist/esm/dialogs/ConfirmInsertAssetModalDialog.js +37 -0
- package/dist/esm/dialogs/EmdebExternalContentDialog.js +140 -0
- package/dist/esm/dialogs/InsertLinkModalDialog.js +92 -0
- package/dist/esm/dialogs/InsertTableModalDialog.js +78 -0
- package/dist/esm/dialogs/SpecialCharacterModalDialog.js +84 -0
- package/dist/esm/dialogs/ZenModeModalDialog.js +195 -0
- package/dist/esm/dialogs/openMarkdownDialog.js +72 -0
- package/dist/esm/dialogs/renderMarkdownDialog.js +59 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/types.js +10 -0
- package/dist/esm/utils/insertAssetLinks.js +99 -0
- package/dist/esm/utils/insertAssetLinks.spec.js +18 -0
- package/dist/esm/utils/isValidUrl.js +4 -0
- package/dist/esm/utils/linkOrganizer.js +152 -0
- package/dist/esm/utils/linkOrganizer.spec.js +53 -0
- package/dist/esm/utils/replaceMailtoAmp.js +5 -0
- package/dist/esm/utils/replaceMailtoAmp.spec.js +18 -0
- package/dist/esm/utils/specialCharacters.js +218 -0
- package/dist/esm/utils/userAgent.js +13 -0
- package/dist/{MarkdownActions.d.ts → types/MarkdownActions.d.ts} +38 -38
- package/dist/{MarkdownEditor.d.ts → types/MarkdownEditor.d.ts} +22 -22
- package/dist/types/__fixtures__/FakeSdk.d.ts +8 -0
- package/dist/types/__fixtures__/asset/index.d.ts +6 -0
- package/dist/types/__fixtures__/content-type/index.d.ts +2 -0
- package/dist/types/__fixtures__/entry/index.d.ts +5 -0
- package/dist/types/__fixtures__/fixtures.d.ts +6 -0
- package/dist/types/__fixtures__/locale/index.d.ts +42 -0
- package/dist/types/__fixtures__/space/index.d.ts +2 -0
- package/dist/{codemirrorImports.d.ts → types/codemirrorImports.d.ts} +5 -5
- package/dist/{components → types/components}/HeadingSelector.d.ts +7 -7
- package/dist/{components → types/components}/InsertLinkSelector.d.ts +9 -9
- package/dist/{components → types/components}/MarkdownBottomBar.d.ts +11 -11
- package/dist/{components → types/components}/MarkdownConstraints.d.ts +6 -6
- package/dist/{components → types/components}/MarkdownPreview.d.ts +14 -14
- package/dist/{components → types/components}/MarkdownTabs.d.ts +8 -8
- package/dist/{components → types/components}/MarkdownTextarea/CodeMirrorWrapper.d.ts +58 -58
- package/dist/{components → types/components}/MarkdownTextarea/MarkdownCommands.d.ts +33 -33
- package/dist/{components → types/components}/MarkdownTextarea/MarkdownTextarea.d.ts +17 -17
- package/dist/{components → types/components}/MarkdownTextarea/createMarkdownEditor.d.ts +55 -55
- package/dist/{components → types/components}/MarkdownToolbar.d.ts +12 -12
- package/dist/types/components/icons.d.ts +18 -0
- package/dist/{dialogs → types/dialogs}/CheatsheetModalDialog.d.ts +4 -4
- package/dist/{dialogs → types/dialogs}/ConfirmInsertAssetModalDialog.d.ts +23 -23
- package/dist/{dialogs → types/dialogs}/EmdebExternalContentDialog.d.ts +9 -9
- package/dist/{dialogs → types/dialogs}/InsertLinkModalDialog.d.ts +17 -17
- package/dist/{dialogs → types/dialogs}/InsertTableModalDialog.d.ts +13 -13
- package/dist/{dialogs → types/dialogs}/SpecialCharacterModalDialog.d.ts +9 -9
- package/dist/{dialogs → types/dialogs}/ZenModeModalDialog.d.ts +24 -24
- package/dist/{dialogs → types/dialogs}/openMarkdownDialog.d.ts +5 -5
- package/dist/{dialogs → types/dialogs}/renderMarkdownDialog.d.ts +8 -8
- package/dist/{index.d.ts → types/index.d.ts} +5 -5
- package/dist/{types.d.ts → types/types.d.ts} +75 -75
- package/dist/{utils → types/utils}/insertAssetLinks.d.ts +29 -29
- package/dist/types/utils/insertAssetLinks.spec.d.ts +1 -0
- package/dist/{utils → types/utils}/isValidUrl.d.ts +2 -2
- package/dist/{utils → types/utils}/linkOrganizer.d.ts +6 -6
- package/dist/types/utils/linkOrganizer.spec.d.ts +1 -0
- package/dist/{utils → types/utils}/replaceMailtoAmp.d.ts +1 -1
- package/dist/types/utils/replaceMailtoAmp.spec.d.ts +1 -0
- package/dist/{utils → types/utils}/specialCharacters.d.ts +4 -4
- package/dist/{utils → types/utils}/userAgent.d.ts +1 -1
- package/package.json +25 -11
- package/CHANGELOG.md +0 -308
- package/dist/components/icons.d.ts +0 -18
- package/dist/field-editor-markdown.cjs.development.js +0 -3605
- package/dist/field-editor-markdown.cjs.development.js.map +0 -1
- package/dist/field-editor-markdown.cjs.production.min.js +0 -216
- package/dist/field-editor-markdown.cjs.production.min.js.map +0 -1
- package/dist/field-editor-markdown.esm.js +0 -3595
- package/dist/field-editor-markdown.esm.js.map +0 -1
- package/dist/index.js +0 -8
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { MarkdownDialogType } from '../types';
|
|
3
|
+
import { CheatsheetModalDialog } from './CheatsheetModalDialog';
|
|
4
|
+
import { ConfirmInsertAssetModalDialog } from './ConfirmInsertAssetModalDialog';
|
|
5
|
+
import { EmbedExternalContentModal } from './EmdebExternalContentDialog';
|
|
6
|
+
import { InsertLinkModal } from './InsertLinkModalDialog';
|
|
7
|
+
import { InsertTableModal } from './InsertTableModalDialog';
|
|
8
|
+
import { SpecialCharacterModalDialog } from './SpecialCharacterModalDialog';
|
|
9
|
+
import { ZenModeModalDialog } from './ZenModeModalDialog';
|
|
10
|
+
export const renderMarkdownDialog = (sdk)=>{
|
|
11
|
+
const parameters = sdk.parameters.invocation;
|
|
12
|
+
if (parameters.type === MarkdownDialogType.cheatsheet) {
|
|
13
|
+
sdk.window.startAutoResizer();
|
|
14
|
+
return React.createElement(CheatsheetModalDialog, null);
|
|
15
|
+
} else if (parameters.type === MarkdownDialogType.insertLink) {
|
|
16
|
+
const selectedText = parameters.selectedText;
|
|
17
|
+
sdk.window.startAutoResizer();
|
|
18
|
+
return React.createElement(InsertLinkModal, {
|
|
19
|
+
selectedText: selectedText,
|
|
20
|
+
onClose: sdk.close
|
|
21
|
+
});
|
|
22
|
+
} else if (parameters.type === MarkdownDialogType.insertSpecialCharacter) {
|
|
23
|
+
sdk.window.startAutoResizer();
|
|
24
|
+
return React.createElement(SpecialCharacterModalDialog, {
|
|
25
|
+
onClose: sdk.close
|
|
26
|
+
});
|
|
27
|
+
} else if (parameters.type === MarkdownDialogType.insertTable) {
|
|
28
|
+
sdk.window.startAutoResizer();
|
|
29
|
+
return React.createElement(InsertTableModal, {
|
|
30
|
+
onClose: sdk.close
|
|
31
|
+
});
|
|
32
|
+
} else if (parameters.type === MarkdownDialogType.embedExternalContent) {
|
|
33
|
+
sdk.window.startAutoResizer();
|
|
34
|
+
return React.createElement(EmbedExternalContentModal, {
|
|
35
|
+
onClose: sdk.close
|
|
36
|
+
});
|
|
37
|
+
} else if (parameters.type === MarkdownDialogType.confirmInsertAsset) {
|
|
38
|
+
const locale = parameters.locale;
|
|
39
|
+
const assets = parameters.assets;
|
|
40
|
+
sdk.window.startAutoResizer();
|
|
41
|
+
return React.createElement(ConfirmInsertAssetModalDialog, {
|
|
42
|
+
onClose: sdk.close,
|
|
43
|
+
locale: locale,
|
|
44
|
+
assets: assets
|
|
45
|
+
});
|
|
46
|
+
} else if (parameters.type === MarkdownDialogType.zenMode) {
|
|
47
|
+
const locale = parameters.locale;
|
|
48
|
+
const initialValue = parameters.initialValue;
|
|
49
|
+
sdk.window.updateHeight('100%');
|
|
50
|
+
return React.createElement(ZenModeModalDialog, {
|
|
51
|
+
onClose: sdk.close,
|
|
52
|
+
saveValueToSDK: ()=>{},
|
|
53
|
+
initialValue: initialValue,
|
|
54
|
+
locale: locale,
|
|
55
|
+
sdk: sdk
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return React.createElement("div", null);
|
|
59
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import './codemirrorImports';
|
|
2
|
+
export { MarkdownEditorConnected as MarkdownEditor } from './MarkdownEditor';
|
|
3
|
+
export { MarkdownPreview } from './components/MarkdownPreview';
|
|
4
|
+
export { openMarkdownDialog } from './dialogs/openMarkdownDialog';
|
|
5
|
+
export { renderMarkdownDialog } from './dialogs/renderMarkdownDialog';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export var MarkdownDialogType;
|
|
2
|
+
(function(MarkdownDialogType) {
|
|
3
|
+
MarkdownDialogType["cheatsheet"] = 'markdown-cheatsheet';
|
|
4
|
+
MarkdownDialogType["insertLink"] = 'markdown-insertLink';
|
|
5
|
+
MarkdownDialogType["insertSpecialCharacter"] = 'markdown-insertSpecialCharacter';
|
|
6
|
+
MarkdownDialogType["insertTable"] = 'markdown-insertTable';
|
|
7
|
+
MarkdownDialogType["embedExternalContent"] = 'markdown-embedExternalContent';
|
|
8
|
+
MarkdownDialogType["confirmInsertAsset"] = 'markdown-confirmInsertAsset';
|
|
9
|
+
MarkdownDialogType["zenMode"] = 'markdown-zenMode';
|
|
10
|
+
})(MarkdownDialogType || (MarkdownDialogType = {}));
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import get from 'lodash/get';
|
|
2
|
+
import isObject from 'lodash/isObject';
|
|
3
|
+
function normalizeWhiteSpace(str) {
|
|
4
|
+
if (str) {
|
|
5
|
+
return str.trim().replace(/\s{2,}/g, ' ');
|
|
6
|
+
} else {
|
|
7
|
+
return str;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function removeExtension(str) {
|
|
11
|
+
return str.replace(/\.\w+$/g, '');
|
|
12
|
+
}
|
|
13
|
+
function fileNameToTitle(str) {
|
|
14
|
+
return normalizeWhiteSpace(removeExtension(str).replace(/_/g, ' '));
|
|
15
|
+
}
|
|
16
|
+
export function replaceAssetDomain(fileUrl) {
|
|
17
|
+
const assetDomainMap = {
|
|
18
|
+
images: 'images.ctfassets.net',
|
|
19
|
+
assets: 'assets.ctfassets.net',
|
|
20
|
+
downloads: 'downloads.ctfassets.net',
|
|
21
|
+
videos: 'videos.ctfassets.net'
|
|
22
|
+
};
|
|
23
|
+
return fileUrl.replace(/(images|assets|downloads|videos).contentful.com/, (_, p1)=>{
|
|
24
|
+
return assetDomainMap[p1];
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function makeAssetLink(asset, { localeCode , fallbackCode , defaultLocaleCode }) {
|
|
28
|
+
const localizedFile = get(asset, [
|
|
29
|
+
'fields',
|
|
30
|
+
'file',
|
|
31
|
+
localeCode
|
|
32
|
+
]);
|
|
33
|
+
const fallbackFile = fallbackCode ? get(asset, [
|
|
34
|
+
'fields',
|
|
35
|
+
'file',
|
|
36
|
+
fallbackCode
|
|
37
|
+
]) : null;
|
|
38
|
+
const defaultFile = get(asset, [
|
|
39
|
+
'fields',
|
|
40
|
+
'file',
|
|
41
|
+
defaultLocaleCode
|
|
42
|
+
]);
|
|
43
|
+
const file = localizedFile || fallbackFile || defaultFile;
|
|
44
|
+
if (isObject(file) && file.url) {
|
|
45
|
+
const title = get(asset, [
|
|
46
|
+
'fields',
|
|
47
|
+
'title',
|
|
48
|
+
localeCode
|
|
49
|
+
]) || get(asset, [
|
|
50
|
+
'fields',
|
|
51
|
+
'title',
|
|
52
|
+
fallbackCode || ''
|
|
53
|
+
]) || get(asset, [
|
|
54
|
+
'fields',
|
|
55
|
+
'title',
|
|
56
|
+
defaultLocaleCode
|
|
57
|
+
]) || fileNameToTitle(file.fileName);
|
|
58
|
+
const fileUrl = replaceAssetDomain(file.url);
|
|
59
|
+
return {
|
|
60
|
+
title,
|
|
61
|
+
asset,
|
|
62
|
+
url: fileUrl,
|
|
63
|
+
isLocalized: Boolean(localizedFile),
|
|
64
|
+
isFallback: Boolean(fallbackFile),
|
|
65
|
+
asMarkdown: ``
|
|
66
|
+
};
|
|
67
|
+
} else {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export async function insertAssetLinks(assets, locales) {
|
|
72
|
+
const otherLocales = assets.filter((asset)=>{
|
|
73
|
+
return !get(asset, [
|
|
74
|
+
'fields',
|
|
75
|
+
'file',
|
|
76
|
+
locales.localeCode
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
const linksWithMeta = assets.map((asset)=>makeAssetLink(asset, locales)).filter((asset)=>asset !== null);
|
|
80
|
+
if (otherLocales.length > 0) {
|
|
81
|
+
const fallbackAssets = linksWithMeta.filter(({ isLocalized })=>!isLocalized).map(({ title , isFallback , asset })=>{
|
|
82
|
+
const code = isFallback ? locales.fallbackCode : locales.defaultLocaleCode;
|
|
83
|
+
return {
|
|
84
|
+
title,
|
|
85
|
+
thumbnailUrl: asset.fields.file[code].url,
|
|
86
|
+
thumbnailAltText: title,
|
|
87
|
+
description: isFallback ? `Fallback locale (${code})` : `Default locale (${code})`,
|
|
88
|
+
asset: asset
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
fallbacks: fallbackAssets,
|
|
93
|
+
links: linksWithMeta
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
links: linksWithMeta
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { replaceAssetDomain } from './insertAssetLinks';
|
|
2
|
+
describe('replaceAssetDomain', ()=>{
|
|
3
|
+
it('should replace contentful.com domains to ctfassets.net', ()=>{
|
|
4
|
+
expect(replaceAssetDomain('//images.contentful.com/image.jpg')).toBe('//images.ctfassets.net/image.jpg');
|
|
5
|
+
expect(replaceAssetDomain('//videos.contentful.com/video.mp4')).toBe('//videos.ctfassets.net/video.mp4');
|
|
6
|
+
expect(replaceAssetDomain('//downloads.contentful.com/file.doc')).toBe('//downloads.ctfassets.net/file.doc');
|
|
7
|
+
expect(replaceAssetDomain('//assets.contentful.com/file.doc')).toBe('//assets.ctfassets.net/file.doc');
|
|
8
|
+
expect(replaceAssetDomain('https://images.contentful.com/image.jpg')).toBe('https://images.ctfassets.net/image.jpg');
|
|
9
|
+
expect(replaceAssetDomain('https://videos.contentful.com/video.mp4')).toBe('https://videos.ctfassets.net/video.mp4');
|
|
10
|
+
expect(replaceAssetDomain('https://downloads.contentful.com/file.doc')).toBe('https://downloads.ctfassets.net/file.doc');
|
|
11
|
+
expect(replaceAssetDomain('https://assets.contentful.com/file.doc')).toBe('https://assets.ctfassets.net/file.doc');
|
|
12
|
+
});
|
|
13
|
+
it('should not replace domains not listed on the map', ()=>{
|
|
14
|
+
expect(replaceAssetDomain('//documents.contentful.com/image.jpg')).toBe('//documents.contentful.com/image.jpg');
|
|
15
|
+
expect(replaceAssetDomain('anyotherdomain.com/file.doc')).toBe('anyotherdomain.com/file.doc');
|
|
16
|
+
expect(replaceAssetDomain('https://anyotherdomain.com/file.doc')).toBe('https://anyotherdomain.com/file.doc');
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import extend from 'lodash/extend';
|
|
2
|
+
import isObject from 'lodash/isObject';
|
|
3
|
+
import isString from 'lodash/isString';
|
|
4
|
+
import max from 'lodash/max';
|
|
5
|
+
import isFinite from 'lodash/isFinite';
|
|
6
|
+
import forEach from 'lodash/forEach';
|
|
7
|
+
function extractTitle(title) {
|
|
8
|
+
title = title || '';
|
|
9
|
+
title = title.replace(/^ *('|"|\()*/, '');
|
|
10
|
+
title = title.replace(/('|"|\))* *$/, '');
|
|
11
|
+
return title;
|
|
12
|
+
}
|
|
13
|
+
function head(text) {
|
|
14
|
+
return text.split(' ').shift();
|
|
15
|
+
}
|
|
16
|
+
function tail(text) {
|
|
17
|
+
const segments = text.split(' ');
|
|
18
|
+
segments.shift();
|
|
19
|
+
return segments.join(' ');
|
|
20
|
+
}
|
|
21
|
+
const PROCESSORS = {
|
|
22
|
+
inline: function(match) {
|
|
23
|
+
return {
|
|
24
|
+
match: match[0],
|
|
25
|
+
text: match[1],
|
|
26
|
+
href: head(match[2]),
|
|
27
|
+
title: extractTitle(tail(match[2]))
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
ref: function(match) {
|
|
31
|
+
return {
|
|
32
|
+
match: match[0],
|
|
33
|
+
text: match[1],
|
|
34
|
+
id: match[2]
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
label: function(match) {
|
|
38
|
+
return {
|
|
39
|
+
match: match[0],
|
|
40
|
+
id: match[1],
|
|
41
|
+
href: head(match[2]),
|
|
42
|
+
title: extractTitle(tail(match[2]))
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const REGEXS = {
|
|
47
|
+
inline: /\[([^\r\n[\]]+)]\(([^\r\n)]+)\)/,
|
|
48
|
+
ref: /\[([^\r\n[\]]+)] ?\[([^\r\n[\]]+)]/,
|
|
49
|
+
label: /^ {0,3}\[([^\r\n[\]]+)]:\s+(.+)$/
|
|
50
|
+
};
|
|
51
|
+
export const findInline = makeFinder('inline');
|
|
52
|
+
export const findRefs = makeFinder('ref');
|
|
53
|
+
export const findLabels = makeFinder('label');
|
|
54
|
+
export function convertInlineToRef(text) {
|
|
55
|
+
let id = findMaxLabelId(text);
|
|
56
|
+
forEach(findInline(text), (inline)=>{
|
|
57
|
+
id += 1;
|
|
58
|
+
text = text.replace(inline.match, buildRef(inline, id));
|
|
59
|
+
text += '\n' + buildLabel(inline, id);
|
|
60
|
+
});
|
|
61
|
+
return text;
|
|
62
|
+
}
|
|
63
|
+
function mergeLabels(text) {
|
|
64
|
+
const byHref = {};
|
|
65
|
+
const byOldId = {};
|
|
66
|
+
forEach(findLabels(text), (label)=>{
|
|
67
|
+
const alreadyAdded = byHref[label.href];
|
|
68
|
+
const current = extend({}, label);
|
|
69
|
+
if (!alreadyAdded) {
|
|
70
|
+
byHref[current.href] = current;
|
|
71
|
+
} else if (hasTitle(current) && !hasTitle(alreadyAdded)) {
|
|
72
|
+
alreadyAdded.title = current.title;
|
|
73
|
+
}
|
|
74
|
+
byOldId[current.id] = alreadyAdded || current;
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
byHref: byHref,
|
|
78
|
+
byOldId: byOldId
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function rewriteRefs(text) {
|
|
82
|
+
const merged = mergeLabels(text);
|
|
83
|
+
const hrefToRefId = {};
|
|
84
|
+
const labels = [];
|
|
85
|
+
const rewrites = [];
|
|
86
|
+
let i = 1;
|
|
87
|
+
forEach(findRefs(text), (ref)=>{
|
|
88
|
+
const oldLabel = merged.byOldId[ref.id];
|
|
89
|
+
if (!oldLabel) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const href = oldLabel.href;
|
|
93
|
+
let newRefId = hrefToRefId[href];
|
|
94
|
+
if (!newRefId) {
|
|
95
|
+
hrefToRefId[href] = newRefId = i;
|
|
96
|
+
i += 1;
|
|
97
|
+
labels.push(extend({
|
|
98
|
+
newId: newRefId
|
|
99
|
+
}, oldLabel));
|
|
100
|
+
}
|
|
101
|
+
rewrites.push(extend({
|
|
102
|
+
newId: newRefId
|
|
103
|
+
}, ref));
|
|
104
|
+
});
|
|
105
|
+
forEach(findLabels(text), (label)=>{
|
|
106
|
+
text = text.replace(label.match, '');
|
|
107
|
+
});
|
|
108
|
+
text = text.replace(/[\r\n\s]*$/, '');
|
|
109
|
+
text += '\n\n';
|
|
110
|
+
forEach(rewrites, (ref)=>{
|
|
111
|
+
text = text.replace(ref.match, buildRef(ref, ref.newId));
|
|
112
|
+
});
|
|
113
|
+
forEach(labels, (label)=>{
|
|
114
|
+
text += '\n' + buildLabel(label, label.newId);
|
|
115
|
+
});
|
|
116
|
+
return text;
|
|
117
|
+
}
|
|
118
|
+
function makeFinder(type) {
|
|
119
|
+
return (text)=>findAll(text, type).map(PROCESSORS[type]);
|
|
120
|
+
}
|
|
121
|
+
export function findMaxLabelId(textOrLabels) {
|
|
122
|
+
if (isString(textOrLabels)) {
|
|
123
|
+
textOrLabels = findLabels(textOrLabels);
|
|
124
|
+
}
|
|
125
|
+
const ids = textOrLabels.map((x)=>x.id).map((x)=>parseInt(x, 10)).filter((x)=>isFinite(x) && x > 0);
|
|
126
|
+
return ids.length > 0 ? max(ids) || 0 : 0;
|
|
127
|
+
}
|
|
128
|
+
function findAll(text, type) {
|
|
129
|
+
const flags = 'g' + (type === 'label' ? 'm' : '');
|
|
130
|
+
const matches = [];
|
|
131
|
+
const re = new RegExp(REGEXS[type].source, flags);
|
|
132
|
+
let found = re.exec(text);
|
|
133
|
+
while(found){
|
|
134
|
+
matches.push(found);
|
|
135
|
+
re.lastIndex = found.index + found[0].length;
|
|
136
|
+
found = re.exec(text);
|
|
137
|
+
}
|
|
138
|
+
return matches;
|
|
139
|
+
}
|
|
140
|
+
function hasTitle(link) {
|
|
141
|
+
return isObject(link) && isString(link.title) && link.title.length > 0;
|
|
142
|
+
}
|
|
143
|
+
function buildLabel(link, id) {
|
|
144
|
+
let markup = '[' + id + ']: ' + link.href;
|
|
145
|
+
if (hasTitle(link)) {
|
|
146
|
+
markup += ' "' + link.title + '"';
|
|
147
|
+
}
|
|
148
|
+
return markup;
|
|
149
|
+
}
|
|
150
|
+
function buildRef(link, id) {
|
|
151
|
+
return '[' + link.text + '][' + id + ']';
|
|
152
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as LinkOrganizer from './linkOrganizer';
|
|
2
|
+
describe('Link organizer', ()=>{
|
|
3
|
+
describe('Inline link finder', ()=>{
|
|
4
|
+
it('Finds all inline links in text', ()=>{
|
|
5
|
+
const subject = 'test [link](http://url.com) test [link2](http://url2.com) test [link3](http://url.com)';
|
|
6
|
+
const found = LinkOrganizer.findInline(subject);
|
|
7
|
+
expect(found).toHaveLength(3);
|
|
8
|
+
expect(found[0].match).toBe('[link](http://url.com)');
|
|
9
|
+
expect(found[0].text).toBe('link');
|
|
10
|
+
expect(found[0].href).toBe('http://url.com');
|
|
11
|
+
expect(found[0].title).toBe('');
|
|
12
|
+
});
|
|
13
|
+
it('Finds and standardizes title', ()=>{
|
|
14
|
+
const subject = 'test [x](http://url.com "title!") [y](http://xyz.com title 2 )';
|
|
15
|
+
const found = LinkOrganizer.findInline(subject);
|
|
16
|
+
expect(found).toHaveLength(2);
|
|
17
|
+
expect(found[0].title).toBe('title!');
|
|
18
|
+
expect(found[1].title).toBe('title 2');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe('Reference finder', ()=>{
|
|
22
|
+
it('Finds all references in text', ()=>{
|
|
23
|
+
const subject = 'test [x][1] test [y][2] [with space separator] [3]';
|
|
24
|
+
const found = LinkOrganizer.findRefs(subject);
|
|
25
|
+
expect(found).toHaveLength(3);
|
|
26
|
+
expect(found[0].match).toBe('[x][1]');
|
|
27
|
+
expect(found[2].match).toBe('[with space separator] [3]');
|
|
28
|
+
expect(found[1].text).toBe('y');
|
|
29
|
+
expect(found[1].id).toBe('2');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('Label finder', ()=>{
|
|
33
|
+
const subject = [
|
|
34
|
+
'[1]: http://test.com',
|
|
35
|
+
'[2]: http://url.com',
|
|
36
|
+
'[string]: http://url.com',
|
|
37
|
+
'[4]: http://test.com "Hello world"'
|
|
38
|
+
].join('\n');
|
|
39
|
+
it('Finds all labels', ()=>{
|
|
40
|
+
const found = LinkOrganizer.findLabels(subject);
|
|
41
|
+
expect(found).toHaveLength(4);
|
|
42
|
+
expect(found[0].id).toBe('1');
|
|
43
|
+
expect(found[2].id).toBe('string');
|
|
44
|
+
expect(found[0].href).toBe('http://test.com');
|
|
45
|
+
expect(found[3].href).toBe('http://test.com');
|
|
46
|
+
expect(found[1].title).toBe('');
|
|
47
|
+
expect(found[3].title).toBe('Hello world');
|
|
48
|
+
});
|
|
49
|
+
it('Finds max label id', ()=>{
|
|
50
|
+
expect(LinkOrganizer.findMaxLabelId(subject)).toBe(4);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { replaceMailtoAmp } from './replaceMailtoAmp';
|
|
2
|
+
describe('replace inside mailto', ()=>{
|
|
3
|
+
let str = '<a href="mailto:example@example.com?subject=Hello&body=Hello%20world">Send Email</a>';
|
|
4
|
+
test('replace & with &', ()=>{
|
|
5
|
+
const newStr = replaceMailtoAmp(str);
|
|
6
|
+
expect(newStr).toBe('<a href="mailto:example@example.com?subject=Hello&body=Hello%20world">Send Email</a>');
|
|
7
|
+
});
|
|
8
|
+
test('no replace if not inside mailto', ()=>{
|
|
9
|
+
str = '<a href="https://example.com?subject=Hello&body=Hello%20world">Visit Website</a>';
|
|
10
|
+
const newStr = replaceMailtoAmp(str);
|
|
11
|
+
expect(newStr).toBe('<a href="https://example.com?subject=Hello&body=Hello%20world">Visit Website</a>');
|
|
12
|
+
});
|
|
13
|
+
test('no replace if no &', ()=>{
|
|
14
|
+
str = '<a href="mailto:example@example.com?subject=Hello">Send Email</a>';
|
|
15
|
+
const newStr = replaceMailtoAmp(str);
|
|
16
|
+
expect(newStr).toBe('<a href="mailto:example@example.com?subject=Hello">Send Email</a>');
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
export const specialCharacters = [
|
|
2
|
+
{
|
|
3
|
+
code: 180,
|
|
4
|
+
desc: 'acute accent'
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
code: 38,
|
|
8
|
+
desc: 'ampersand'
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
code: 166,
|
|
12
|
+
desc: 'broken vertical bar'
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
code: 8226,
|
|
16
|
+
desc: 'bullet'
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
code: 184,
|
|
20
|
+
desc: 'cedilla'
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
code: 162,
|
|
24
|
+
desc: 'cent'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
code: 169,
|
|
28
|
+
desc: 'copyright'
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
code: 176,
|
|
32
|
+
desc: 'degree'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
code: 247,
|
|
36
|
+
desc: 'division'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
code: 189,
|
|
40
|
+
desc: 'fraction half'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
code: 188,
|
|
44
|
+
desc: 'fraction quarter'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
code: 190,
|
|
48
|
+
desc: 'fraction three quarters'
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
code: 62,
|
|
52
|
+
desc: 'greater than'
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
code: 161,
|
|
56
|
+
desc: 'inverted exclamation mark'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
code: 191,
|
|
60
|
+
desc: 'inverted question mark'
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
code: 171,
|
|
64
|
+
desc: 'left-pointing double angle quotation mark'
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
code: 60,
|
|
68
|
+
desc: 'less than'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
code: 175,
|
|
72
|
+
desc: 'macron'
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
code: 181,
|
|
76
|
+
desc: 'micro'
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
code: 160,
|
|
80
|
+
desc: 'non-breaking space'
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
code: 172,
|
|
84
|
+
desc: 'not'
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
code: 182,
|
|
88
|
+
desc: 'paragraph'
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
code: 177,
|
|
92
|
+
desc: 'plus-minus'
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
code: 34,
|
|
96
|
+
desc: 'quotation mark'
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
code: 174,
|
|
100
|
+
desc: 'registered'
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
code: 187,
|
|
104
|
+
desc: 'right-pointing double angle quotation mark'
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
code: 167,
|
|
108
|
+
desc: 'section'
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
code: 168,
|
|
112
|
+
desc: 'umlaut/diaeresis'
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
code: 215,
|
|
116
|
+
desc: 'multiplication'
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
code: 8482,
|
|
120
|
+
desc: 'trade mark'
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
code: 8364,
|
|
124
|
+
desc: 'euro'
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
code: 163,
|
|
128
|
+
desc: 'pound'
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
code: 165,
|
|
132
|
+
desc: 'yen'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
code: 8222,
|
|
136
|
+
desc: 'double low-9 quotation mark'
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
code: 710,
|
|
140
|
+
desc: 'modifier circumflex accent'
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
code: 8224,
|
|
144
|
+
desc: 'dagger'
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
code: 8225,
|
|
148
|
+
desc: 'double dagger'
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
code: 8230,
|
|
152
|
+
desc: 'horizontal ellipsis'
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
code: 8220,
|
|
156
|
+
desc: 'left double quotation mark'
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
code: 8249,
|
|
160
|
+
desc: 'single left-pointing angle quotation mark'
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
code: 8216,
|
|
164
|
+
desc: 'left single quotation mark'
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
code: 183,
|
|
168
|
+
desc: 'middle dot'
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
code: 8212,
|
|
172
|
+
desc: 'em dash'
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
code: 8211,
|
|
176
|
+
desc: 'en dash'
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
code: 8240,
|
|
180
|
+
desc: 'per mille'
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
code: 8250,
|
|
184
|
+
desc: 'single right-pointing angle quotation mark'
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
code: 8217,
|
|
188
|
+
desc: 'right single quotation mark'
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
code: 8218,
|
|
192
|
+
desc: 'single low-9 quotation mark'
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
code: 170,
|
|
196
|
+
desc: 'feminine ordinal indicator'
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
code: 186,
|
|
200
|
+
desc: 'masculine ordinal indicator'
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
code: 8221,
|
|
204
|
+
desc: 'right double quotation mark'
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
code: 732,
|
|
208
|
+
desc: 'small tilde'
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
code: 9829,
|
|
212
|
+
desc: 'black heart'
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
code: 9830,
|
|
216
|
+
desc: 'diamond'
|
|
217
|
+
}
|
|
218
|
+
];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import get from 'lodash/get';
|
|
2
|
+
const userAgent = get(window, 'navigator.userAgent', '');
|
|
3
|
+
const platform = get(window, 'navigator.platform', '');
|
|
4
|
+
let ctrlKey = 'Ctrl';
|
|
5
|
+
const tests = {
|
|
6
|
+
ios: /(iphone os|ipad|iphone|ipod)/i.test(userAgent) && !window.MSStream
|
|
7
|
+
};
|
|
8
|
+
if (tests.ios || /mac(68k|ppc|intel)/i.test(platform)) {
|
|
9
|
+
ctrlKey = 'Cmd';
|
|
10
|
+
}
|
|
11
|
+
export function getCtrlKey() {
|
|
12
|
+
return ctrlKey;
|
|
13
|
+
}
|