Socket
Socket
Sign inDemoInstall

react-marksome

Package Overview
Dependencies
3
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.3.0 to 1.0.0

213

dist/react-marksome.cjs.development.js

@@ -10,27 +10,16 @@ 'use strict';

const STRONG_TEXT_REGEXP = /([*_])\1\1?((?:\[.*?\][([].*?[)\]]|.)*?)\1?\1\1/g;
const EMPHASIZED_TEXT_REGEXP = /([*_])((?:\[.*?\][([].*?[)\]]|.)*?)\1/g;
const REFERENCE_LINK_TEXT_REGEXP = /\[([^\]]*)\] ?\[([^\]]*)\]/g;
// Capture [text][reference] or [reference] (taking into account escaped squared brackets)
const REFERENCE_LINK_REGEXP = /(?<!\\)(?:\\\\)*(?:\[(.+?)(?<!\\)(?:\\\\)*\])?\[((?:(?<!\\)(?:\\\\)*\\[[]|[^[])+?)(?<!\\)(?:\\\\)*\]/g; // Capture sequence of '*' or '_' (non-escaped)
function matchAll(regexp, text, onMatch) {
let match;
while ((match = regexp.exec(text)) !== null) {
onMatch(match);
}
}
const EMPH_SEQUENCE_REGEXP = /(?<!\\)(?:\\\\)*(\*+|_+)/g;
function parseSegments(text) {
const matches = [];
matchAll(REFERENCE_LINK_TEXT_REGEXP, text, referenceLinkRegExpMatch => {
const innerText = referenceLinkRegExpMatch[1];
const pendingOpenersByBlockIndex = new Map([[-1, []]]);
const referenceLinkMatches = [];
matchAll(REFERENCE_LINK_REGEXP, text, referenceLinkRegExpMatch => {
const reference = referenceLinkRegExpMatch[2];
const startIndex = referenceLinkRegExpMatch.index;
if (!innerText || !reference || startIndex == null) {
return;
}
const innerText = referenceLinkRegExpMatch[1] || reference;
const endIndex = startIndex + referenceLinkRegExpMatch[0].length;
matches.push({
const match = {
type: 'reference-link',

@@ -42,17 +31,115 @@ innerText,

offset: 1
});
};
pendingOpenersByBlockIndex.set(referenceLinkMatches.length, []);
referenceLinkMatches.push(match);
matches.push(match);
});
matchAll(STRONG_TEXT_REGEXP, text, strongRegExpMatch => {
const inlineMatch = getInlineMatchFromRegexpMatch(strongRegExpMatch, 'strong');
let currentReferenceLinkIndex = 0;
matchAll(EMPH_SEQUENCE_REGEXP, text, emphCharRegExpMatch => {
const char = emphCharRegExpMatch[1][0];
const length = emphCharRegExpMatch[0].length;
const index = emphCharRegExpMatch.index;
const previousCharInfo = getCharInfo(text[index - 1]);
const nextCharInfo = getCharInfo(text[index + length]);
const leftFlanking = !nextCharInfo || nextCharInfo === '.' && !!previousCharInfo;
const rightFlanking = !previousCharInfo || previousCharInfo === '.' && !!nextCharInfo;
let canOpen = leftFlanking;
let canClose = rightFlanking;
if (inlineMatch) {
matches.push(inlineMatch);
if (char === '_') {
canOpen = leftFlanking && (!rightFlanking || previousCharInfo === '.');
canClose = rightFlanking && (!leftFlanking || nextCharInfo === '.');
}
});
matchAll(EMPHASIZED_TEXT_REGEXP, text, emphasisRegExpMatch => {
const inlineMatch = getInlineMatchFromRegexpMatch(emphasisRegExpMatch, 'emphasis');
if (inlineMatch) {
matches.push(inlineMatch);
if (!canOpen && !canClose) {
return;
} // identify current delimiter block index
let blockIndex = -1;
for (; currentReferenceLinkIndex < referenceLinkMatches.length; currentReferenceLinkIndex++) {
const currentReferenceLinkMatch = referenceLinkMatches[currentReferenceLinkIndex]; // comes before the current block -> it's outside a block
if (currentReferenceLinkMatch.startIndex > index) {
break;
} // comes after the current block -> check next block
if (currentReferenceLinkMatch.endIndex <= index) {
continue;
} // it's inside the block but outside it's innerText -> ignore this emph char match
if (currentReferenceLinkMatch.startIndex + currentReferenceLinkMatch.offset + currentReferenceLinkMatch.innerText.length < index) {
currentReferenceLinkIndex++;
return;
}
blockIndex = currentReferenceLinkIndex;
break;
}
const delimiter = {
char,
index,
length,
type: canOpen && canClose ? '<>' : canClose ? '>' : '<'
};
const pendingOpeners = pendingOpenersByBlockIndex.get(blockIndex); // can close -> look for last compatible opener
if (delimiter.type !== '<') {
for (let pendingOpenerIndex = pendingOpeners.length - 1; pendingOpenerIndex >= 0; pendingOpenerIndex--) {
const pendingOpener = pendingOpeners[pendingOpenerIndex]; // ensure that the pendingOpener is the same character
if (pendingOpener.char !== delimiter.char) {
continue;
} // from spec (https://spec.commonmark.org/0.30/#emphasis-and-strong-emphasis rule 9)
// If one of the delimiters can both open and close emphasis,
// then the sum of the lengths of the delimiter runs containing the opening and closing delimiters
// must not be a multiple of 3 unless both lengths are multiples of 3
if (pendingOpener.type === '<>' || delimiter.type === '<>') {
if ((pendingOpener.length + delimiter.length) % 3 === 0) {
if (pendingOpener.length % 3 || delimiter.length % 3) {
continue;
}
}
} // it's a match!
delimiter.type = '>';
let matchDelimiterLength = Math.min(delimiter.length, pendingOpener.length); // for each pair -> extract strong based on matchDelimiterInnerOffset
while (matchDelimiterLength > 1) {
matches.push(createInlineStyleMatch(text, 'strong', pendingOpener, delimiter));
matchDelimiterLength -= 2;
} // if one left -> extract emphasis based on matchDelimiterInnerOffset
if (matchDelimiterLength) {
matches.push(createInlineStyleMatch(text, 'emphasis', pendingOpener, delimiter));
} // if opener is wider than closer
if (pendingOpener.length) {
// remove openers until current one (exclusive)
pendingOpeners.splice(pendingOpenerIndex + 1);
} else {
// remove openers until current one (inclusive)
pendingOpeners.splice(pendingOpenerIndex); // if closer is wider than opener -> look for more openers
if (delimiter.length) {
continue;
}
}
break;
}
}
if (delimiter.type !== '>') {
pendingOpeners.push(delimiter);
}
});

@@ -63,18 +150,23 @@ matches.sort((a, b) => a.startIndex - b.startIndex);

function getInlineMatchFromRegexpMatch(regexpMatch, inlineType) {
const startIndex = regexpMatch.index;
const innerText = regexpMatch[2];
function matchAll(regexp, text, onMatch) {
let match;
if (startIndex == null || !innerText) {
return;
while ((match = regexp.exec(text)) !== null) {
onMatch(match);
}
}
const decoratedText = regexpMatch[0];
const offset = decoratedText.indexOf(innerText);
const endIndex = startIndex + decoratedText.length;
function createInlineStyleMatch(text, type, opener, closer) {
const innerTextStartIndex = opener.index + opener.length;
const innerTextEndIndex = closer.index;
const offset = type === 'strong' ? 2 : 1; // adjust delimiters
opener.length -= offset;
closer.length -= offset;
closer.index += offset;
return {
type: inlineType,
startIndex,
endIndex,
innerText,
type,
startIndex: innerTextStartIndex - offset,
endIndex: innerTextEndIndex + offset,
innerText: text.slice(innerTextStartIndex, innerTextEndIndex),
offset

@@ -84,9 +176,23 @@ };

function getCharInfo(char) {
// detect spaces
if (!char || /\s/.exec(char)) {
return ' ';
} // detect punctuation
if (/[!"#$%&'()*+,.\/:;<=>?@[\\\]^_`{|}~-]/.exec(char)) {
return '.';
}
return;
}
function getSegmentsFromMatches(text, matches) {
if (!matches.length) {
return [text];
return [unescapeText(text)];
}
const firstMatchStartIndex = matches[0].startIndex;
const segments = firstMatchStartIndex > 0 ? [text.slice(0, firstMatchStartIndex)] : [];
const segments = firstMatchStartIndex > 0 ? [unescapeText(text.slice(0, firstMatchStartIndex))] : [];

@@ -96,14 +202,6 @@ while (matches.length) {

const currentMatchTextStart = currentMatch.startIndex + currentMatch.offset;
const innerMatches = [];
const innerMatches = []; // find innerMatches
for (let i = 0; i < matches.length;) {
const otherMatch = matches[i]; // if not an inner match, continue to the next
if (otherMatch.endIndex > currentMatch.endIndex) {
i++;
continue;
} // remove it from matches
matches.splice(i, 1);
while (matches.length && matches[0].endIndex < currentMatch.endIndex) {
const otherMatch = matches.shift();
otherMatch.startIndex -= currentMatchTextStart;

@@ -132,3 +230,3 @@ otherMatch.endIndex -= currentMatchTextStart;

if (textAfterLastMatch) {
segments.push(textAfterLastMatch);
segments.push(unescapeText(textAfterLastMatch));
}

@@ -140,2 +238,7 @@ }

function unescapeText(text) {
// subset of escapable markdown chars which are used as markers in this lib
return text.replace(/\\([*[\\\]_])/g, '$1');
}
function Marksome({

@@ -142,0 +245,0 @@ text,

@@ -1,2 +0,2 @@

"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e,n=require("react"),t=(e=n)&&"object"==typeof e&&"default"in e?e.default:e;const r=/([*_])\1\1?((?:\[.*?\][([].*?[)\]]|.)*?)\1?\1\1/g,s=/([*_])((?:\[.*?\][([].*?[)\]]|.)*?)\1/g,c=/\[([^\]]*)\] ?\[([^\]]*)\]/g;function o(e,n,t){let r;for(;null!==(r=e.exec(n));)t(r)}function u(e){const n=[];return o(c,e,e=>{const t=e[1],r=e[2],s=e.index;t&&r&&null!=s&&n.push({type:"reference-link",innerText:t,reference:r,startIndex:s,endIndex:s+e[0].length,offset:1})}),o(r,e,e=>{const t=f(e,"strong");t&&n.push(t)}),o(s,e,e=>{const t=f(e,"emphasis");t&&n.push(t)}),n.sort((e,n)=>e.startIndex-n.startIndex),function e(n,t){if(!t.length)return[n];const r=t[0].startIndex,s=r>0?[n.slice(0,r)]:[];for(;t.length;){const r=t.shift(),c=r.startIndex+r.offset,o=[];for(let e=0;e<t.length;){const n=t[e];n.endIndex>r.endIndex?e++:(t.splice(e,1),n.startIndex-=c,n.endIndex-=c,o.push(n))}const u=e(r.innerText,o);s.push("reference-link"===r.type?{type:r.type,content:u,reference:r.reference}:{type:r.type,content:u});const f=t.length?n.slice(r.endIndex,t[0].startIndex):n.slice(r.endIndex);f&&s.push(f)}return s}(e,n)}function f(e,n){const t=e.index,r=e[2];if(null==t||!r)return;const s=e[0],c=s.indexOf(r);return{type:n,startIndex:t,endIndex:t+s.length,innerText:r,offset:c}}exports.Marksome=function({text:e,references:r,...s}){const c=n.useMemo(()=>u(e),[e]);return t.createElement("span",Object.assign({},s),function e(r,s){return r.map((r,c)=>{if("string"==typeof r)return r;switch(r.type){case"strong":return t.createElement("strong",{key:c},e(r.content,s));case"emphasis":return t.createElement("em",{key:c},e(r.content,s));case"reference-link":{const o=null==s?void 0:s[r.reference],u=e(r.content,s);if(!o)return t.createElement("span",{key:c},u);if("string"==typeof o)return t.createElement("a",{key:c,href:o},u);const f=o(u);return n.cloneElement(f,{key:c})}default:return null}})}(c,r))},exports.parseSegments=u;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e,t=require("react"),n=(e=t)&&"object"==typeof e&&"default"in e?e.default:e;const r=/(?<!\\)(?:\\\\)*(?:\[(.+?)(?<!\\)(?:\\\\)*\])?\[((?:(?<!\\)(?:\\\\)*\\[[]|[^[])+?)(?<!\\)(?:\\\\)*\]/g,s=/(?<!\\)(?:\\\\)*(\*+|_+)/g;function c(e){const t=[],n=new Map([[-1,[]]]),c=[];o(r,e,e=>{const r=e[2],s=e.index,o={type:"reference-link",innerText:e[1]||r,reference:r,startIndex:s,endIndex:s+e[0].length,offset:1};n.set(c.length,[]),c.push(o),t.push(o)});let u=0;return o(s,e,r=>{const s=r[1][0],o=r[0].length,f=r.index,a=l(e[f-1]),d=l(e[f+o]),h=!d||"."===d&&!!a,p=!a||"."===a&&!!d;let x=h,g=p;if("_"===s&&(x=h&&(!p||"."===a),g=p&&(!h||"."===d)),!x&&!g)return;let y=-1;for(;u<c.length;u++){const e=c[u];if(e.startIndex>f)break;if(!(e.endIndex<=f)){if(e.startIndex+e.offset+e.innerText.length<f)return void u++;y=u;break}}const I={char:s,index:f,length:o,type:x&&g?"<>":g?">":"<"},m=n.get(y);if("<"!==I.type)for(let n=m.length-1;n>=0;n--){const r=m[n];if(r.char!==I.char)continue;if(("<>"===r.type||"<>"===I.type)&&(r.length+I.length)%3==0&&(r.length%3||I.length%3))continue;I.type=">";let s=Math.min(I.length,r.length);for(;s>1;)t.push(i(e,"strong",r,I)),s-=2;if(s&&t.push(i(e,"emphasis",r,I)),r.length)m.splice(n+1);else if(m.splice(n),I.length)continue;break}">"!==I.type&&m.push(I)}),t.sort((e,t)=>e.startIndex-t.startIndex),function e(t,n){if(!n.length)return[f(t)];const r=n[0].startIndex,s=r>0?[f(t.slice(0,r))]:[];for(;n.length;){const r=n.shift(),c=r.startIndex+r.offset,o=[];for(;n.length&&n[0].endIndex<r.endIndex;){const e=n.shift();e.startIndex-=c,e.endIndex-=c,o.push(e)}const i=e(r.innerText,o);s.push("reference-link"===r.type?{type:r.type,content:i,reference:r.reference}:{type:r.type,content:i});const l=n.length?t.slice(r.endIndex,n[0].startIndex):t.slice(r.endIndex);l&&s.push(f(l))}return s}(e,t)}function o(e,t,n){let r;for(;null!==(r=e.exec(t));)n(r)}function i(e,t,n,r){const s=n.index+n.length,c=r.index,o="strong"===t?2:1;return n.length-=o,r.length-=o,r.index+=o,{type:t,startIndex:s-o,endIndex:c+o,innerText:e.slice(s,c),offset:o}}function l(e){return!e||/\s/.exec(e)?" ":/[!"#$%&'()*+,.\/:;<=>?@[\\\]^_`{|}~-]/.exec(e)?".":void 0}function f(e){return e.replace(/\\([*[\\\]_])/g,"$1")}exports.Marksome=function({text:e,references:r,...s}){const o=t.useMemo(()=>c(e),[e]);return n.createElement("span",Object.assign({},s),function e(r,s){return r.map((r,c)=>{if("string"==typeof r)return r;switch(r.type){case"strong":return n.createElement("strong",{key:c},e(r.content,s));case"emphasis":return n.createElement("em",{key:c},e(r.content,s));case"reference-link":{const o=null==s?void 0:s[r.reference],i=e(r.content,s);if(!o)return n.createElement("span",{key:c},i);if("string"==typeof o)return n.createElement("a",{key:c,href:o},i);const l=o(i);return t.cloneElement(l,{key:c})}default:return null}})}(o,r))},exports.parseSegments=c;
//# sourceMappingURL=react-marksome.cjs.production.min.js.map
import React, { useMemo, cloneElement } from 'react';
const STRONG_TEXT_REGEXP = /([*_])\1\1?((?:\[.*?\][([].*?[)\]]|.)*?)\1?\1\1/g;
const EMPHASIZED_TEXT_REGEXP = /([*_])((?:\[.*?\][([].*?[)\]]|.)*?)\1/g;
const REFERENCE_LINK_TEXT_REGEXP = /\[([^\]]*)\] ?\[([^\]]*)\]/g;
// Capture [text][reference] or [reference] (taking into account escaped squared brackets)
const REFERENCE_LINK_REGEXP = /(?<!\\)(?:\\\\)*(?:\[(.+?)(?<!\\)(?:\\\\)*\])?\[((?:(?<!\\)(?:\\\\)*\\[[]|[^[])+?)(?<!\\)(?:\\\\)*\]/g; // Capture sequence of '*' or '_' (non-escaped)
function matchAll(regexp, text, onMatch) {
let match;
while ((match = regexp.exec(text)) !== null) {
onMatch(match);
}
}
const EMPH_SEQUENCE_REGEXP = /(?<!\\)(?:\\\\)*(\*+|_+)/g;
function parseSegments(text) {
const matches = [];
matchAll(REFERENCE_LINK_TEXT_REGEXP, text, referenceLinkRegExpMatch => {
const innerText = referenceLinkRegExpMatch[1];
const pendingOpenersByBlockIndex = new Map([[-1, []]]);
const referenceLinkMatches = [];
matchAll(REFERENCE_LINK_REGEXP, text, referenceLinkRegExpMatch => {
const reference = referenceLinkRegExpMatch[2];
const startIndex = referenceLinkRegExpMatch.index;
if (!innerText || !reference || startIndex == null) {
return;
}
const innerText = referenceLinkRegExpMatch[1] || reference;
const endIndex = startIndex + referenceLinkRegExpMatch[0].length;
matches.push({
const match = {
type: 'reference-link',

@@ -34,17 +23,115 @@ innerText,

offset: 1
});
};
pendingOpenersByBlockIndex.set(referenceLinkMatches.length, []);
referenceLinkMatches.push(match);
matches.push(match);
});
matchAll(STRONG_TEXT_REGEXP, text, strongRegExpMatch => {
const inlineMatch = getInlineMatchFromRegexpMatch(strongRegExpMatch, 'strong');
let currentReferenceLinkIndex = 0;
matchAll(EMPH_SEQUENCE_REGEXP, text, emphCharRegExpMatch => {
const char = emphCharRegExpMatch[1][0];
const length = emphCharRegExpMatch[0].length;
const index = emphCharRegExpMatch.index;
const previousCharInfo = getCharInfo(text[index - 1]);
const nextCharInfo = getCharInfo(text[index + length]);
const leftFlanking = !nextCharInfo || nextCharInfo === '.' && !!previousCharInfo;
const rightFlanking = !previousCharInfo || previousCharInfo === '.' && !!nextCharInfo;
let canOpen = leftFlanking;
let canClose = rightFlanking;
if (inlineMatch) {
matches.push(inlineMatch);
if (char === '_') {
canOpen = leftFlanking && (!rightFlanking || previousCharInfo === '.');
canClose = rightFlanking && (!leftFlanking || nextCharInfo === '.');
}
});
matchAll(EMPHASIZED_TEXT_REGEXP, text, emphasisRegExpMatch => {
const inlineMatch = getInlineMatchFromRegexpMatch(emphasisRegExpMatch, 'emphasis');
if (inlineMatch) {
matches.push(inlineMatch);
if (!canOpen && !canClose) {
return;
} // identify current delimiter block index
let blockIndex = -1;
for (; currentReferenceLinkIndex < referenceLinkMatches.length; currentReferenceLinkIndex++) {
const currentReferenceLinkMatch = referenceLinkMatches[currentReferenceLinkIndex]; // comes before the current block -> it's outside a block
if (currentReferenceLinkMatch.startIndex > index) {
break;
} // comes after the current block -> check next block
if (currentReferenceLinkMatch.endIndex <= index) {
continue;
} // it's inside the block but outside it's innerText -> ignore this emph char match
if (currentReferenceLinkMatch.startIndex + currentReferenceLinkMatch.offset + currentReferenceLinkMatch.innerText.length < index) {
currentReferenceLinkIndex++;
return;
}
blockIndex = currentReferenceLinkIndex;
break;
}
const delimiter = {
char,
index,
length,
type: canOpen && canClose ? '<>' : canClose ? '>' : '<'
};
const pendingOpeners = pendingOpenersByBlockIndex.get(blockIndex); // can close -> look for last compatible opener
if (delimiter.type !== '<') {
for (let pendingOpenerIndex = pendingOpeners.length - 1; pendingOpenerIndex >= 0; pendingOpenerIndex--) {
const pendingOpener = pendingOpeners[pendingOpenerIndex]; // ensure that the pendingOpener is the same character
if (pendingOpener.char !== delimiter.char) {
continue;
} // from spec (https://spec.commonmark.org/0.30/#emphasis-and-strong-emphasis rule 9)
// If one of the delimiters can both open and close emphasis,
// then the sum of the lengths of the delimiter runs containing the opening and closing delimiters
// must not be a multiple of 3 unless both lengths are multiples of 3
if (pendingOpener.type === '<>' || delimiter.type === '<>') {
if ((pendingOpener.length + delimiter.length) % 3 === 0) {
if (pendingOpener.length % 3 || delimiter.length % 3) {
continue;
}
}
} // it's a match!
delimiter.type = '>';
let matchDelimiterLength = Math.min(delimiter.length, pendingOpener.length); // for each pair -> extract strong based on matchDelimiterInnerOffset
while (matchDelimiterLength > 1) {
matches.push(createInlineStyleMatch(text, 'strong', pendingOpener, delimiter));
matchDelimiterLength -= 2;
} // if one left -> extract emphasis based on matchDelimiterInnerOffset
if (matchDelimiterLength) {
matches.push(createInlineStyleMatch(text, 'emphasis', pendingOpener, delimiter));
} // if opener is wider than closer
if (pendingOpener.length) {
// remove openers until current one (exclusive)
pendingOpeners.splice(pendingOpenerIndex + 1);
} else {
// remove openers until current one (inclusive)
pendingOpeners.splice(pendingOpenerIndex); // if closer is wider than opener -> look for more openers
if (delimiter.length) {
continue;
}
}
break;
}
}
if (delimiter.type !== '>') {
pendingOpeners.push(delimiter);
}
});

@@ -55,18 +142,23 @@ matches.sort((a, b) => a.startIndex - b.startIndex);

function getInlineMatchFromRegexpMatch(regexpMatch, inlineType) {
const startIndex = regexpMatch.index;
const innerText = regexpMatch[2];
function matchAll(regexp, text, onMatch) {
let match;
if (startIndex == null || !innerText) {
return;
while ((match = regexp.exec(text)) !== null) {
onMatch(match);
}
}
const decoratedText = regexpMatch[0];
const offset = decoratedText.indexOf(innerText);
const endIndex = startIndex + decoratedText.length;
function createInlineStyleMatch(text, type, opener, closer) {
const innerTextStartIndex = opener.index + opener.length;
const innerTextEndIndex = closer.index;
const offset = type === 'strong' ? 2 : 1; // adjust delimiters
opener.length -= offset;
closer.length -= offset;
closer.index += offset;
return {
type: inlineType,
startIndex,
endIndex,
innerText,
type,
startIndex: innerTextStartIndex - offset,
endIndex: innerTextEndIndex + offset,
innerText: text.slice(innerTextStartIndex, innerTextEndIndex),
offset

@@ -76,9 +168,23 @@ };

function getCharInfo(char) {
// detect spaces
if (!char || /\s/.exec(char)) {
return ' ';
} // detect punctuation
if (/[!"#$%&'()*+,.\/:;<=>?@[\\\]^_`{|}~-]/.exec(char)) {
return '.';
}
return;
}
function getSegmentsFromMatches(text, matches) {
if (!matches.length) {
return [text];
return [unescapeText(text)];
}
const firstMatchStartIndex = matches[0].startIndex;
const segments = firstMatchStartIndex > 0 ? [text.slice(0, firstMatchStartIndex)] : [];
const segments = firstMatchStartIndex > 0 ? [unescapeText(text.slice(0, firstMatchStartIndex))] : [];

@@ -88,14 +194,6 @@ while (matches.length) {

const currentMatchTextStart = currentMatch.startIndex + currentMatch.offset;
const innerMatches = [];
const innerMatches = []; // find innerMatches
for (let i = 0; i < matches.length;) {
const otherMatch = matches[i]; // if not an inner match, continue to the next
if (otherMatch.endIndex > currentMatch.endIndex) {
i++;
continue;
} // remove it from matches
matches.splice(i, 1);
while (matches.length && matches[0].endIndex < currentMatch.endIndex) {
const otherMatch = matches.shift();
otherMatch.startIndex -= currentMatchTextStart;

@@ -124,3 +222,3 @@ otherMatch.endIndex -= currentMatchTextStart;

if (textAfterLastMatch) {
segments.push(textAfterLastMatch);
segments.push(unescapeText(textAfterLastMatch));
}

@@ -132,2 +230,7 @@ }

function unescapeText(text) {
// subset of escapable markdown chars which are used as markers in this lib
return text.replace(/\\([*[\\\]_])/g, '$1');
}
function Marksome({

@@ -134,0 +237,0 @@ text,

{
"version": "0.3.0",
"version": "1.0.0",
"license": "MIT",

@@ -45,7 +45,7 @@ "repository": "github:miguel-silva/react-marksome",

"path": "dist/react-marksome.cjs.production.min.js",
"limit": "1 KB"
"limit": "1.5 KB"
},
{
"path": "dist/react-marksome.esm.js",
"limit": "1 KB"
"limit": "1.5 KB"
}

@@ -76,4 +76,3 @@ ],

"typescript": "^4.1.3"
},
"dependencies": {}
}
}

@@ -92,12 +92,33 @@ # react-marksome

## Rationale
## Supported Markdown
The current subset of markdown that is supported is:
- \*\*strong text\*\*
- \*emphasized text\*
- \[link description\]\[reference\]
### Emphasis and strong emphasis
By restricting ourselves to only support some markdown we're able to:
_Emphasis_ (\*Emphasis\*) and **strong emphasis** (\*\*strong emphasis\*\*) parcing respects the [related commonmark spec section](https://spec.commonmark.org/0.30/#emphasis-and-strong-emphasis).
### Link references
Influenced by the [related commonmark spec section](https://spec.commonmark.org/0.30/#emphasis-and-strong-emphasis), link references can be defined in a couple of ways:
- Full reference links:
- input: \[react-marksome's Github page\]\[react-marksome github\]
- output: [react-marksome's Github page][react-marksome github]
- Shortcut reference links:
- input: \[react-marksome github\]
- output: [react-marksome github]
There are certain quirks in marksome that are non-spec:
1. it matches reference links regardless if the corresponding reference labels are defined as keys in the `references` prop or not
2. reference labels are kept as is when looking for the corresponding key in `references` prop (ex: case-sensitive, no space-trimming, etc)
3. nested squared brackets don't follow the same rules (ex: marksome supports unbalanced brackets)
If reference links are not being matched as you desire, disable unintended matches by escaping the related opening (\\\[) or closing (\\\]) brackets.
## Rationale
By restricting ourselves to support only [some markdown](#supported-markdown) we're able to:
- build a light package ([bundlephobia](https://bundlephobia.com/result?p=react-marksome))

@@ -120,38 +141,39 @@ - that provides a flexible, readable and condensed format for singleline pieces of text

<p><strong>caniuse-lite db date: 15/02/2020</strong></p>
<p><strong>caniuse-lite db date: 2nd Jan 2022</strong></p>
<ul>
<li>and_chr 87</li>
<li>and_ff 83</li>
<li>and_chr 97</li>
<li>and_ff 95</li>
<li>and_qq 10.4</li>
<li>android 81</li>
<li>chrome 87</li>
<li>chrome 86</li>
<li>chrome 85</li>
<li>edge 87</li>
<li>edge 86</li>
<li>firefox 84</li>
<li>firefox 83</li>
<li>ios_saf 14.0-14.3</li>
<li>android 97</li>
<li>chrome 97</li>
<li>chrome 96</li>
<li>chrome 95</li>
<li>chrome 94</li>
<li>chrome 93</li>
<li>chrome 92</li>
<li>edge 97</li>
<li>edge 96</li>
<li>firefox 96</li>
<li>firefox 95</li>
<li>firefox 94</li>
<li>ios_saf 15.2-15.3</li>
<li>ios_saf 15.0-15.1</li>
<li>ios_saf 14.5-14.8</li>
<li>ios_saf 14.0-14.4</li>
<li>ios_saf 13.4-13.7</li>
<li>ios_saf 13.3</li>
<li>ios_saf 13.2</li>
<li>ios_saf 13.0-13.1</li>
<li>ios_saf 12.2-12.4</li>
<li>opera 72</li>
<li>opera 71</li>
<li>ios_saf 12.2-12.5</li>
<li>op_mob 64</li>
<li>opera 82</li>
<li>opera 81</li>
<li>safari 15.2-15.3</li>
<li>safari 15.1</li>
<li>safari 15</li>
<li>safari 14.1</li>
<li>safari 14</li>
<li>safari 13.1</li>
<li>safari 13</li>
<li>samsung 13.0</li>
<li>samsung 12.0</li>
<li>samsung 16.0</li>
<li>samsung 15.0</li>
</ul>
</details>
## Alternatives
If you're looking for wider markdown support:
- [snarkdown](https://www.npmjs.com/package/snarkdown) for lightweight Markdown parser that returns plain HTML string
- [markdown-to-jsx](https://www.npmjs.com/package/markdown-to-jsx) for a lot configurability and extensibility
## Commands

@@ -180,1 +202,3 @@

- [devuo](https://github.com/devuo) for providing some ideas and inspiration!
[react-marksome github]: https://github.com/miguel-silva/react-marksome

@@ -24,2 +24,9 @@ export type Segment = InlineStyleSegment | ReferenceLinkSegment | string;

type InlineStyleDelimiter = {
char: '*' | '_';
index: number;
length: number;
type: '<' | '>' | '<>';
};
type ReferenceLinkMatch = {

@@ -34,34 +41,26 @@ type: 'reference-link';

const STRONG_TEXT_REGEXP = /([*_])\1\1?((?:\[.*?\][([].*?[)\]]|.)*?)\1?\1\1/g;
// Capture [text][reference] or [reference] (taking into account escaped squared brackets)
const REFERENCE_LINK_REGEXP = /(?<!\\)(?:\\\\)*(?:\[(.+?)(?<!\\)(?:\\\\)*\])?\[((?:(?<!\\)(?:\\\\)*\\[[]|[^[])+?)(?<!\\)(?:\\\\)*\]/g;
const EMPHASIZED_TEXT_REGEXP = /([*_])((?:\[.*?\][([].*?[)\]]|.)*?)\1/g;
// Capture sequence of '*' or '_' (non-escaped)
const EMPH_SEQUENCE_REGEXP = /(?<!\\)(?:\\\\)*(\*+|_+)/g;
const REFERENCE_LINK_TEXT_REGEXP = /\[([^\]]*)\] ?\[([^\]]*)\]/g;
function matchAll(
regexp: RegExp,
text: string,
onMatch: (match: RegExpExecArray) => void,
) {
let match: RegExpExecArray | null;
while ((match = regexp.exec(text)) !== null) {
onMatch(match);
}
}
export function parseSegments(text: string): Segment[] {
const matches: Match[] = [];
matchAll(REFERENCE_LINK_TEXT_REGEXP, text, (referenceLinkRegExpMatch) => {
const innerText = referenceLinkRegExpMatch[1];
const pendingOpenersByBlockIndex = new Map<number, InlineStyleDelimiter[]>([
[-1, []],
]);
const referenceLinkMatches: ReferenceLinkMatch[] = [];
matchAll(REFERENCE_LINK_REGEXP, text, (referenceLinkRegExpMatch) => {
const reference = referenceLinkRegExpMatch[2];
const startIndex = referenceLinkRegExpMatch.index;
if (!innerText || !reference || startIndex == null) {
return;
}
const innerText = referenceLinkRegExpMatch[1] || reference;
const endIndex = startIndex + referenceLinkRegExpMatch[0].length;
matches.push({
const match: ReferenceLinkMatch = {
type: 'reference-link',

@@ -73,25 +72,155 @@ innerText,

offset: 1,
});
};
pendingOpenersByBlockIndex.set(referenceLinkMatches.length, []);
referenceLinkMatches.push(match);
matches.push(match);
});
matchAll(STRONG_TEXT_REGEXP, text, (strongRegExpMatch) => {
const inlineMatch = getInlineMatchFromRegexpMatch(
strongRegExpMatch,
'strong',
);
let currentReferenceLinkIndex = 0;
if (inlineMatch) {
matches.push(inlineMatch);
matchAll(EMPH_SEQUENCE_REGEXP, text, (emphCharRegExpMatch) => {
const char = emphCharRegExpMatch[1][0] as '*' | '_';
const length = emphCharRegExpMatch[0].length;
const index = emphCharRegExpMatch.index;
const previousCharInfo = getCharInfo(text[index - 1]);
const nextCharInfo = getCharInfo(text[index + length]);
const leftFlanking =
!nextCharInfo || (nextCharInfo === '.' && !!previousCharInfo);
const rightFlanking =
!previousCharInfo || (previousCharInfo === '.' && !!nextCharInfo);
let canOpen = leftFlanking;
let canClose = rightFlanking;
if (char === '_') {
canOpen = leftFlanking && (!rightFlanking || previousCharInfo === '.');
canClose = rightFlanking && (!leftFlanking || nextCharInfo === '.');
}
});
matchAll(EMPHASIZED_TEXT_REGEXP, text, (emphasisRegExpMatch) => {
const inlineMatch = getInlineMatchFromRegexpMatch(
emphasisRegExpMatch,
'emphasis',
);
if (!canOpen && !canClose) {
return;
}
if (inlineMatch) {
matches.push(inlineMatch);
// identify current delimiter block index
let blockIndex = -1;
for (
;
currentReferenceLinkIndex < referenceLinkMatches.length;
currentReferenceLinkIndex++
) {
const currentReferenceLinkMatch =
referenceLinkMatches[currentReferenceLinkIndex];
// comes before the current block -> it's outside a block
if (currentReferenceLinkMatch.startIndex > index) {
break;
}
// comes after the current block -> check next block
if (currentReferenceLinkMatch.endIndex <= index) {
continue;
}
// it's inside the block but outside it's innerText -> ignore this emph char match
if (
currentReferenceLinkMatch.startIndex +
currentReferenceLinkMatch.offset +
currentReferenceLinkMatch.innerText.length <
index
) {
currentReferenceLinkIndex++;
return;
}
blockIndex = currentReferenceLinkIndex;
break;
}
const delimiter: InlineStyleDelimiter = {
char,
index,
length,
type: canOpen && canClose ? '<>' : canClose ? '>' : '<',
};
const pendingOpeners = pendingOpenersByBlockIndex.get(blockIndex)!;
// can close -> look for last compatible opener
if (delimiter.type !== '<') {
for (
let pendingOpenerIndex = pendingOpeners.length - 1;
pendingOpenerIndex >= 0;
pendingOpenerIndex--
) {
const pendingOpener = pendingOpeners[pendingOpenerIndex];
// ensure that the pendingOpener is the same character
if (pendingOpener.char !== delimiter.char) {
continue;
}
// from spec (https://spec.commonmark.org/0.30/#emphasis-and-strong-emphasis rule 9)
// If one of the delimiters can both open and close emphasis,
// then the sum of the lengths of the delimiter runs containing the opening and closing delimiters
// must not be a multiple of 3 unless both lengths are multiples of 3
if (pendingOpener.type === '<>' || delimiter.type === '<>') {
if ((pendingOpener.length + delimiter.length) % 3 === 0) {
if (pendingOpener.length % 3 || delimiter.length % 3) {
continue;
}
}
}
// it's a match!
delimiter.type = '>';
let matchDelimiterLength = Math.min(
delimiter.length,
pendingOpener.length,
);
// for each pair -> extract strong based on matchDelimiterInnerOffset
while (matchDelimiterLength > 1) {
matches.push(
createInlineStyleMatch(text, 'strong', pendingOpener, delimiter),
);
matchDelimiterLength -= 2;
}
// if one left -> extract emphasis based on matchDelimiterInnerOffset
if (matchDelimiterLength) {
matches.push(
createInlineStyleMatch(text, 'emphasis', pendingOpener, delimiter),
);
}
// if opener is wider than closer
if (pendingOpener.length) {
// remove openers until current one (exclusive)
pendingOpeners.splice(pendingOpenerIndex + 1);
} else {
// remove openers until current one (inclusive)
pendingOpeners.splice(pendingOpenerIndex);
// if closer is wider than opener -> look for more openers
if (delimiter.length) {
continue;
}
}
break;
}
}
if (delimiter.type !== '>') {
pendingOpeners.push(delimiter);
}
});

@@ -104,25 +233,34 @@

function getInlineMatchFromRegexpMatch(
regexpMatch: RegExpMatchArray,
inlineType: 'strong' | 'emphasis',
): InlineStyleMatch | undefined {
const startIndex = regexpMatch.index;
const innerText = regexpMatch[2];
if (startIndex == null || !innerText) {
return;
function matchAll(
regexp: RegExp,
text: string,
onMatch: (match: RegExpExecArray) => void,
) {
let match: RegExpExecArray | null;
while ((match = regexp.exec(text)) !== null) {
onMatch(match);
}
}
const decoratedText = regexpMatch[0];
function createInlineStyleMatch(
text: string,
type: 'strong' | 'emphasis',
opener: InlineStyleDelimiter,
closer: InlineStyleDelimiter,
): InlineStyleMatch {
const innerTextStartIndex = opener.index + opener.length;
const innerTextEndIndex = closer.index;
const offset = decoratedText.indexOf(innerText);
const offset = type === 'strong' ? 2 : 1;
const endIndex = startIndex + decoratedText.length;
// adjust delimiters
opener.length -= offset;
closer.length -= offset;
closer.index += offset;
return {
type: inlineType,
startIndex,
endIndex,
innerText,
type,
startIndex: innerTextStartIndex - offset,
endIndex: innerTextEndIndex + offset,
innerText: text.slice(innerTextStartIndex, innerTextEndIndex),
offset,

@@ -132,5 +270,19 @@ };

function getCharInfo(char: string | undefined): ' ' | '.' | undefined {
// detect spaces
if (!char || /\s/.exec(char)) {
return ' ';
}
// detect punctuation
if (/[!"#$%&'()*+,.\/:;<=>?@[\\\]^_`{|}~-]/.exec(char)) {
return '.';
}
return;
}
function getSegmentsFromMatches(text: string, matches: Match[]): Segment[] {
if (!matches.length) {
return [text];
return [unescapeText(text)];
}

@@ -141,3 +293,5 @@

const segments: Segment[] =
firstMatchStartIndex > 0 ? [text.slice(0, firstMatchStartIndex)] : [];
firstMatchStartIndex > 0
? [unescapeText(text.slice(0, firstMatchStartIndex))]
: [];

@@ -151,15 +305,6 @@ while (matches.length) {

for (let i = 0; i < matches.length; ) {
const otherMatch = matches[i];
// find innerMatches
while (matches.length && matches[0].endIndex < currentMatch.endIndex) {
const otherMatch = matches.shift()!;
// if not an inner match, continue to the next
if (otherMatch.endIndex > currentMatch.endIndex) {
i++;
continue;
}
// remove it from matches
matches.splice(i, 1);
otherMatch.startIndex -= currentMatchTextStart;

@@ -194,3 +339,3 @@ otherMatch.endIndex -= currentMatchTextStart;

if (textAfterLastMatch) {
segments.push(textAfterLastMatch);
segments.push(unescapeText(textAfterLastMatch));
}

@@ -201,1 +346,6 @@ }

}
function unescapeText(text: string) {
// subset of escapable markdown chars which are used as markers in this lib
return text.replace(/\\([*[\\\]_])/g, '$1');
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc