@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.
Files changed (136) hide show
  1. package/dist/cjs/MarkdownActions.js +235 -0
  2. package/dist/cjs/MarkdownEditor.js +180 -0
  3. package/dist/cjs/__fixtures__/FakeSdk.js +183 -0
  4. package/dist/cjs/__fixtures__/asset/index.js +37 -0
  5. package/dist/cjs/__fixtures__/content-type/index.js +16 -0
  6. package/dist/cjs/__fixtures__/entry/index.js +33 -0
  7. package/dist/cjs/__fixtures__/fixtures.js +71 -0
  8. package/dist/cjs/__fixtures__/locale/index.js +40 -0
  9. package/dist/cjs/__fixtures__/space/index.js +16 -0
  10. package/dist/cjs/codemirrorImports.js +9 -0
  11. package/dist/cjs/components/HeadingSelector.js +66 -0
  12. package/dist/cjs/components/InsertLinkSelector.js +86 -0
  13. package/dist/cjs/components/MarkdownBottomBar.js +111 -0
  14. package/dist/cjs/components/MarkdownConstraints.js +79 -0
  15. package/dist/cjs/components/MarkdownPreview.js +249 -0
  16. package/dist/cjs/components/MarkdownTabs.js +128 -0
  17. package/dist/cjs/components/MarkdownTextarea/CodeMirrorWrapper.js +383 -0
  18. package/dist/cjs/components/MarkdownTextarea/MarkdownCommands.js +233 -0
  19. package/dist/cjs/components/MarkdownTextarea/MarkdownTextarea.js +190 -0
  20. package/dist/cjs/components/MarkdownTextarea/createMarkdownEditor.js +101 -0
  21. package/dist/cjs/components/MarkdownToolbar.js +367 -0
  22. package/dist/cjs/components/icons.js +193 -0
  23. package/dist/cjs/dialogs/CheatsheetModalDialog.js +239 -0
  24. package/dist/cjs/dialogs/ConfirmInsertAssetModalDialog.js +94 -0
  25. package/dist/cjs/dialogs/EmdebExternalContentDialog.js +202 -0
  26. package/dist/cjs/dialogs/InsertLinkModalDialog.js +149 -0
  27. package/dist/cjs/dialogs/InsertTableModalDialog.js +140 -0
  28. package/dist/cjs/dialogs/SpecialCharacterModalDialog.js +146 -0
  29. package/dist/cjs/dialogs/ZenModeModalDialog.js +257 -0
  30. package/dist/cjs/dialogs/openMarkdownDialog.js +121 -0
  31. package/dist/cjs/dialogs/renderMarkdownDialog.js +108 -0
  32. package/dist/cjs/index.js +29 -0
  33. package/dist/cjs/types.js +20 -0
  34. package/dist/cjs/utils/insertAssetLinks.js +122 -0
  35. package/dist/cjs/utils/insertAssetLinks.spec.js +22 -0
  36. package/dist/cjs/utils/isValidUrl.js +22 -0
  37. package/dist/cjs/utils/linkOrganizer.js +187 -0
  38. package/dist/cjs/utils/linkOrganizer.spec.js +96 -0
  39. package/dist/cjs/utils/replaceMailtoAmp.js +15 -0
  40. package/dist/cjs/utils/replaceMailtoAmp.spec.js +22 -0
  41. package/dist/cjs/utils/specialCharacters.js +228 -0
  42. package/dist/cjs/utils/userAgent.js +28 -0
  43. package/dist/esm/MarkdownActions.js +186 -0
  44. package/dist/esm/MarkdownEditor.js +118 -0
  45. package/dist/esm/__fixtures__/FakeSdk.js +173 -0
  46. package/dist/esm/__fixtures__/asset/index.js +6 -0
  47. package/dist/esm/__fixtures__/content-type/index.js +2 -0
  48. package/dist/esm/__fixtures__/entry/index.js +5 -0
  49. package/dist/esm/__fixtures__/fixtures.js +6 -0
  50. package/dist/esm/__fixtures__/locale/index.js +15 -0
  51. package/dist/esm/__fixtures__/space/index.js +2 -0
  52. package/dist/esm/codemirrorImports.js +5 -0
  53. package/dist/esm/components/HeadingSelector.js +17 -0
  54. package/dist/esm/components/InsertLinkSelector.js +37 -0
  55. package/dist/esm/components/MarkdownBottomBar.js +46 -0
  56. package/dist/esm/components/MarkdownConstraints.js +25 -0
  57. package/dist/esm/components/MarkdownPreview.js +195 -0
  58. package/dist/esm/components/MarkdownTabs.js +74 -0
  59. package/dist/esm/components/MarkdownTextarea/CodeMirrorWrapper.js +329 -0
  60. package/dist/esm/components/MarkdownTextarea/MarkdownCommands.js +218 -0
  61. package/dist/esm/components/MarkdownTextarea/MarkdownTextarea.js +136 -0
  62. package/dist/esm/components/MarkdownTextarea/createMarkdownEditor.js +52 -0
  63. package/dist/esm/components/MarkdownToolbar.js +302 -0
  64. package/dist/esm/components/icons.js +112 -0
  65. package/dist/esm/dialogs/CheatsheetModalDialog.js +177 -0
  66. package/dist/esm/dialogs/ConfirmInsertAssetModalDialog.js +37 -0
  67. package/dist/esm/dialogs/EmdebExternalContentDialog.js +140 -0
  68. package/dist/esm/dialogs/InsertLinkModalDialog.js +92 -0
  69. package/dist/esm/dialogs/InsertTableModalDialog.js +78 -0
  70. package/dist/esm/dialogs/SpecialCharacterModalDialog.js +84 -0
  71. package/dist/esm/dialogs/ZenModeModalDialog.js +195 -0
  72. package/dist/esm/dialogs/openMarkdownDialog.js +72 -0
  73. package/dist/esm/dialogs/renderMarkdownDialog.js +59 -0
  74. package/dist/esm/index.js +5 -0
  75. package/dist/esm/types.js +10 -0
  76. package/dist/esm/utils/insertAssetLinks.js +99 -0
  77. package/dist/esm/utils/insertAssetLinks.spec.js +18 -0
  78. package/dist/esm/utils/isValidUrl.js +4 -0
  79. package/dist/esm/utils/linkOrganizer.js +152 -0
  80. package/dist/esm/utils/linkOrganizer.spec.js +53 -0
  81. package/dist/esm/utils/replaceMailtoAmp.js +5 -0
  82. package/dist/esm/utils/replaceMailtoAmp.spec.js +18 -0
  83. package/dist/esm/utils/specialCharacters.js +218 -0
  84. package/dist/esm/utils/userAgent.js +13 -0
  85. package/dist/{MarkdownActions.d.ts → types/MarkdownActions.d.ts} +38 -38
  86. package/dist/{MarkdownEditor.d.ts → types/MarkdownEditor.d.ts} +22 -22
  87. package/dist/types/__fixtures__/FakeSdk.d.ts +8 -0
  88. package/dist/types/__fixtures__/asset/index.d.ts +6 -0
  89. package/dist/types/__fixtures__/content-type/index.d.ts +2 -0
  90. package/dist/types/__fixtures__/entry/index.d.ts +5 -0
  91. package/dist/types/__fixtures__/fixtures.d.ts +6 -0
  92. package/dist/types/__fixtures__/locale/index.d.ts +42 -0
  93. package/dist/types/__fixtures__/space/index.d.ts +2 -0
  94. package/dist/{codemirrorImports.d.ts → types/codemirrorImports.d.ts} +5 -5
  95. package/dist/{components → types/components}/HeadingSelector.d.ts +7 -7
  96. package/dist/{components → types/components}/InsertLinkSelector.d.ts +9 -9
  97. package/dist/{components → types/components}/MarkdownBottomBar.d.ts +11 -11
  98. package/dist/{components → types/components}/MarkdownConstraints.d.ts +6 -6
  99. package/dist/{components → types/components}/MarkdownPreview.d.ts +14 -14
  100. package/dist/{components → types/components}/MarkdownTabs.d.ts +8 -8
  101. package/dist/{components → types/components}/MarkdownTextarea/CodeMirrorWrapper.d.ts +58 -58
  102. package/dist/{components → types/components}/MarkdownTextarea/MarkdownCommands.d.ts +33 -33
  103. package/dist/{components → types/components}/MarkdownTextarea/MarkdownTextarea.d.ts +17 -17
  104. package/dist/{components → types/components}/MarkdownTextarea/createMarkdownEditor.d.ts +55 -55
  105. package/dist/{components → types/components}/MarkdownToolbar.d.ts +12 -12
  106. package/dist/types/components/icons.d.ts +18 -0
  107. package/dist/{dialogs → types/dialogs}/CheatsheetModalDialog.d.ts +4 -4
  108. package/dist/{dialogs → types/dialogs}/ConfirmInsertAssetModalDialog.d.ts +23 -23
  109. package/dist/{dialogs → types/dialogs}/EmdebExternalContentDialog.d.ts +9 -9
  110. package/dist/{dialogs → types/dialogs}/InsertLinkModalDialog.d.ts +17 -17
  111. package/dist/{dialogs → types/dialogs}/InsertTableModalDialog.d.ts +13 -13
  112. package/dist/{dialogs → types/dialogs}/SpecialCharacterModalDialog.d.ts +9 -9
  113. package/dist/{dialogs → types/dialogs}/ZenModeModalDialog.d.ts +24 -24
  114. package/dist/{dialogs → types/dialogs}/openMarkdownDialog.d.ts +5 -5
  115. package/dist/{dialogs → types/dialogs}/renderMarkdownDialog.d.ts +8 -8
  116. package/dist/{index.d.ts → types/index.d.ts} +5 -5
  117. package/dist/{types.d.ts → types/types.d.ts} +75 -75
  118. package/dist/{utils → types/utils}/insertAssetLinks.d.ts +29 -29
  119. package/dist/types/utils/insertAssetLinks.spec.d.ts +1 -0
  120. package/dist/{utils → types/utils}/isValidUrl.d.ts +2 -2
  121. package/dist/{utils → types/utils}/linkOrganizer.d.ts +6 -6
  122. package/dist/types/utils/linkOrganizer.spec.d.ts +1 -0
  123. package/dist/{utils → types/utils}/replaceMailtoAmp.d.ts +1 -1
  124. package/dist/types/utils/replaceMailtoAmp.spec.d.ts +1 -0
  125. package/dist/{utils → types/utils}/specialCharacters.d.ts +4 -4
  126. package/dist/{utils → types/utils}/userAgent.d.ts +1 -1
  127. package/package.json +25 -11
  128. package/CHANGELOG.md +0 -308
  129. package/dist/components/icons.d.ts +0 -18
  130. package/dist/field-editor-markdown.cjs.development.js +0 -3605
  131. package/dist/field-editor-markdown.cjs.development.js.map +0 -1
  132. package/dist/field-editor-markdown.cjs.production.min.js +0 -216
  133. package/dist/field-editor-markdown.cjs.production.min.js.map +0 -1
  134. package/dist/field-editor-markdown.esm.js +0 -3595
  135. package/dist/field-editor-markdown.esm.js.map +0 -1
  136. 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: `![${title}](${fileUrl})`
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,4 @@
1
+ export const urlRegex = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?$/;
2
+ export function isValidUrl(value) {
3
+ return urlRegex.test(value);
4
+ }
@@ -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,5 @@
1
+ export const replaceMailtoAmp = (string)=>{
2
+ return string.replace(/href="mailto:[^"]*&/g, function(match) {
3
+ return match.replace(/&/g, '&');
4
+ });
5
+ };
@@ -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&amp;body=Hello%20world">Send Email</a>';
4
+ test('replace &amp; 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&amp;body=Hello%20world">Visit Website</a>';
10
+ const newStr = replaceMailtoAmp(str);
11
+ expect(newStr).toBe('<a href="https://example.com?subject=Hello&amp;body=Hello%20world">Visit Website</a>');
12
+ });
13
+ test('no replace if no &amp;', ()=>{
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
+ }