@nakobase/nakobase-md-html 1.1.0 → 1.2.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAmB1C,eAAO,MAAM,QAAQ,GACnB,UAAU,MAAM,EAChB,UAAU,eAAe,KACxB,MA8BF,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAkB1C,eAAO,MAAM,QAAQ,GACnB,UAAU,MAAM,EAChB,UAAU,eAAe,KACxB,MA6BF,CAAC"}
package/dist/esm/index.js CHANGED
@@ -7,7 +7,6 @@ import { mdCustomBlocks } from './utils/md-custom-blocks';
7
7
  import { CONTAINER_TYPES } from './constants';
8
8
 
9
9
  // plugins
10
- const MdItImSize = require('@steelydylan/markdown-it-imsize').default;
11
10
  const MdItInlineComments = require('markdown-it-inline-comments');
12
11
  const MdItContainer = require('markdown-it-container');
13
12
  const MdItTaskLists = require('markdown-it-task-lists');
@@ -26,7 +25,7 @@ export const mdToHtml = (markdown, options) => {
26
25
  md.linkify.set({
27
26
  fuzzyEmail: false
28
27
  });
29
- md.use(MdItInlineComments).use(MdItImSize).use(MdItTaskLists, {
28
+ md.use(MdItInlineComments).use(MdItTaskLists, {
30
29
  enabled: true
31
30
  }).use(MdItContainer, CONTAINER_TYPES.DETAILS, detailsOptions).use(MdItContainer, CONTAINER_TYPES.BOX, boxOptions).use(MdItContainer, CONTAINER_TYPES.BUBBLE, bubbleOptions).use(MdItContainer, CONTAINER_TYPES.BUBBLE_IMAGE, bubbleImageOptions).use(mdCustomBlocks);
32
31
  if (codeHighlight) {
@@ -1 +1 @@
1
- {"version":3,"file":"sanitizer.d.ts","sourceRoot":"","sources":["../../src/sanitizer.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,KAAG,MA8BpC,CAAC"}
1
+ {"version":3,"file":"sanitizer.d.ts","sourceRoot":"","sources":["../../src/sanitizer.ts"],"names":[],"mappings":"AAgCA,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,KAAG,MAuBpC,CAAC"}
@@ -1,10 +1,12 @@
1
1
  // sanitizer.ts
2
2
  import sanitizeHtml, { defaults } from 'sanitize-html';
3
- const extendedTags = ['iframe', 'code', 'details', 'summary', 'circle', 'img', 'input', 'pre', 'span'];
3
+ const extendedTags = ['iframe', 'code', 'details', 'summary', 'circle', 'img', 'input', 'pre', 'span', 'picture', 'source'];
4
4
  const extendedAttributes = {
5
5
  a: [...(defaults.allowedAttributes.a || []), 'id', 'class', 'data-line'],
6
6
  iframe: ['src', 'width', 'height', 'allow', 'sandbox', 'frameborder'],
7
7
  input: ['type', 'checked', 'disabled', 'readonly', 'value', 'class'],
8
+ source: ['srcset', 'type'],
9
+ img: ['src', 'alt', 'width', 'height'],
8
10
  ul: ['class'],
9
11
  ol: ['class'],
10
12
  li: ['class'],
@@ -31,11 +33,5 @@ export const sanitize = html => sanitizeHtml(html, {
31
33
  'text-align': [/^(?:left|right|center|justify)$/]
32
34
  }
33
35
  },
34
- transformTags: {
35
- a: sanitizeHtml.simpleTransform('a', {
36
- target: '_blank',
37
- rel: 'noopener noreferrer'
38
- })
39
- },
40
36
  disallowedTagsMode: 'discard'
41
37
  });
@@ -1 +1 @@
1
- {"version":3,"file":"md-container.d.ts","sourceRoot":"","sources":["../../../src/utils/md-container.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,uBAAuB,CAAC;AAK/C,eAAO,MAAM,cAAc;uBACG,MAAM;qBAGR,KAAK,EAAE,OAAO,MAAM;CAc/C,CAAC;AAMF,eAAO,MAAM,aAAa;uBACI,MAAM;qBAGR,KAAK,EAAE,OAAO,MAAM;CA4B/C,CAAC;AAMF,eAAO,MAAM,kBAAkB;uBACD,MAAM;qBAGR,KAAK,EAAE,OAAO,MAAM;CAwB/C,CAAC;AAOF,eAAO,MAAM,UAAU;uBACO,MAAM;qBAGR,KAAK,EAAE,OAAO,MAAM;CAc/C,CAAC"}
1
+ {"version":3,"file":"md-container.d.ts","sourceRoot":"","sources":["../../../src/utils/md-container.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,uBAAuB,CAAC;AAK/C,eAAO,MAAM,cAAc;uBACG,MAAM;qBAGR,KAAK,EAAE,OAAO,MAAM;CAc/C,CAAC;AAMF,eAAO,MAAM,aAAa;uBACI,MAAM;qBAGR,KAAK,EAAE,OAAO,MAAM;CA2C/C,CAAC;AAMF,eAAO,MAAM,kBAAkB;uBACD,MAAM;qBAGR,KAAK,EAAE,OAAO,MAAM;CA4C/C,CAAC;AAOF,eAAO,MAAM,UAAU;uBACO,MAAM;qBAGR,KAAK,EAAE,OAAO,MAAM;CAc/C,CAAC"}
@@ -19,7 +19,7 @@ export const detailsOptions = {
19
19
  };
20
20
 
21
21
  // Bubble
22
- // ::: bubble alt="alt" src="src" width="100" height="100" pos="left"
22
+ // ::: bubble alt="alt" src="src" webp="src.webp" width="100" height="100" pos="left"
23
23
  // markdown
24
24
  // :::
25
25
  export const bubbleOptions = {
@@ -41,9 +41,17 @@ export const bubbleOptions = {
41
41
  alt = '',
42
42
  width = '100',
43
43
  height = '100',
44
- pos = 'left'
44
+ pos = 'left',
45
+ webp = ''
45
46
  } = attrs;
46
- return `<div class="bubble ${pos}">` + `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" ` + `width="${escapeHtml(width)}" height="${escapeHtml(height)}">` + `<div class="bubble-content">`;
47
+ const imgHtml = `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" ` + `width="${escapeHtml(width)}" height="${escapeHtml(height)}">`;
48
+ let imageElement;
49
+ if (webp) {
50
+ imageElement = '<picture>' + `<source srcset="${escapeHtml(webp)}" type="image/webp">` + imgHtml + '</picture>';
51
+ } else {
52
+ imageElement = imgHtml;
53
+ }
54
+ return `<div class="bubble ${pos}">` + imageElement + `<div class="bubble-content">`;
47
55
  } else {
48
56
  return `</div></div>\n`;
49
57
  }
@@ -51,17 +59,17 @@ export const bubbleOptions = {
51
59
  };
52
60
 
53
61
  // BubbleImage
54
- // ::: bubbleImage alt="alt" src="src" width="100" height="100" pos="left"
62
+ // ::: bubble-image alt="alt" src="src" webp="src.webp" width="640" height="360"
55
63
  // markdown
56
64
  // :::
57
65
  export const bubbleImageOptions = {
58
66
  validate: function (params) {
59
- return /^bubbleImage(?:\s+\w+="[^"]*")*\s*$/.test(params.trim());
67
+ return /^bubble-image(?:\s+\w+="[^"]*")*\s*$/.test(params.trim());
60
68
  },
61
69
  render: function (tokens, idx) {
62
70
  const isOpeningTag = tokens[idx].nesting === 1;
63
71
  if (isOpeningTag) {
64
- const info = tokens[idx].info.trim().replace(/^bubbleImage\s*/, '');
72
+ const info = tokens[idx].info.trim().replace(/^bubble-image\s*/, '');
65
73
  const regex = /(\w+)="([^"]*)"/g;
66
74
  const attrs = {};
67
75
  let m;
@@ -72,9 +80,17 @@ export const bubbleImageOptions = {
72
80
  src = '',
73
81
  alt = '',
74
82
  width = '640',
75
- height = '360'
83
+ height = '360',
84
+ webp = ''
76
85
  } = attrs;
77
- return `<div class="bubble-image">` + `<div class="bubble-image-wrapper">` + `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" ` + `width="${escapeHtml(width)}" height="${escapeHtml(height)}">` + `</div>` + `<div class="bubble-image-content">`;
86
+ const imgHtml = `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" ` + `width="${escapeHtml(width)}" height="${escapeHtml(height)}">`;
87
+ let imageElement;
88
+ if (webp) {
89
+ imageElement = '<picture>' + `<source srcset="${escapeHtml(webp)}" type="image/webp">` + imgHtml + '</picture>';
90
+ } else {
91
+ imageElement = imgHtml;
92
+ }
93
+ return `<div class="bubble-image">` + `<div class="bubble-image-wrapper">` + imageElement + `</div>` + `<div class="bubble-image-content">`;
78
94
  } else {
79
95
  return `</div></div>\n`;
80
96
  }
@@ -1 +1 @@
1
- {"version":3,"file":"md-custom-blocks.d.ts","sourceRoot":"","sources":["../../../src/utils/md-custom-blocks.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,aAAa,CAAC;AAKrC,wBAAgB,cAAc,CAAC,EAAE,EAAE,UAAU,QAiC5C"}
1
+ {"version":3,"file":"md-custom-blocks.d.ts","sourceRoot":"","sources":["../../../src/utils/md-custom-blocks.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,aAAa,CAAC;AAsDrC,wBAAgB,cAAc,CAAC,EAAE,EAAE,UAAU,QAkC5C"}
@@ -1,27 +1,75 @@
1
1
  import { escapeHtml } from 'markdown-it/lib/common/utils';
2
- const SEPARATOR = '|';
3
- export function mdCustomBlocks(md) {
4
- // External Link with Icon
5
- // @[link-external](url|text)
6
- md.block.ruler.before('paragraph', 'link-external', (state, startLine, endLine, silent) => {
7
- const start = state.bMarks[startLine] + state.tShift[startLine];
8
- const max = state.eMarks[startLine];
9
- const content = state.src.slice(start, max).trim();
10
-
11
- // Check if it's a link-external block
12
- const match = content.match(/^@\[link-external\]\((.*?)\)$/);
13
- if (!match) return false;
2
+ function escapeRegExp(string) {
3
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
4
+ }
5
+ function registerKVBlock(md, block) {
6
+ const escapedMarker = escapeRegExp(block.marker);
7
+ const blockRe = new RegExp(`^@\\[${escapedMarker}\\]\\(([^)]*)\\)$`);
8
+ const attrRe = /([a-zA-Z0-9-_]+)="([^"]*)"/g;
9
+ md.block.ruler.before('paragraph', block.name, (state, start, _end, silent) => {
10
+ const line = state.src.slice(state.bMarks[start] + state.tShift[start], state.eMarks[start]).trim();
11
+ const m = line.match(blockRe);
12
+ if (!m) return false;
14
13
  if (silent) return true;
15
- const [url, text] = match[1].split(SEPARATOR);
16
- if (!url || !text) return false;
17
- const token = state.push('link_external', '', 0);
18
- token.content = `${url}${SEPARATOR}${text}`;
19
- token.markup = 'link-external';
20
- state.line = startLine + 1;
14
+ const attrs = {};
15
+ let mat;
16
+ while (mat = attrRe.exec(m[1])) {
17
+ attrs[mat[1]] = mat[2];
18
+ }
19
+ for (const key of block.requiredKeys) {
20
+ if (!attrs[key]) {
21
+ return false;
22
+ }
23
+ }
24
+ const token = state.push(block.name, '', 0);
25
+ token.meta = attrs;
26
+ state.line = start + 1;
21
27
  return true;
22
28
  });
23
- md.renderer.rules.link_external = (tokens, idx) => {
24
- const [url, text] = tokens[idx].content.split(SEPARATOR);
25
- return `<a href="${escapeHtml(url)}" target="_blank" rel="noopener noreferrer" class="external-link">${escapeHtml(text)}</a>`;
29
+ md.renderer.rules[block.name] = (tokens, idx) => {
30
+ return block.renderer(tokens[idx].meta);
26
31
  };
32
+ }
33
+ export function mdCustomBlocks(md) {
34
+ // @[img](src="", alt="", webp="", width="", height="")
35
+ registerKVBlock(md, {
36
+ name: 'image_with_webp',
37
+ marker: 'img',
38
+ requiredKeys: ['src'],
39
+ renderer: ({
40
+ src,
41
+ alt,
42
+ webp,
43
+ width,
44
+ height,
45
+ caption
46
+ }) => {
47
+ const webpSource = webp ? `<source srcset="${escapeHtml(webp)}" type="image/webp">` : '';
48
+ const altText = alt ? `alt="${escapeHtml(alt)}"` : '';
49
+ const widthAttr = width ? ` width="${escapeHtml(width)}"` : '';
50
+ const heightAttr = height ? ` height="${escapeHtml(height)}"` : '';
51
+ const captionText = caption ? `<em>${escapeHtml(caption)}</em>` : '';
52
+ return `<picture>
53
+ ${webpSource}
54
+ <img src="${escapeHtml(src)}" ${altText}${widthAttr}${heightAttr}>
55
+ ${captionText}
56
+ </picture>`;
57
+ }
58
+ });
59
+
60
+ // @[link-external](url="", text="")
61
+ registerKVBlock(md, {
62
+ name: 'link_external',
63
+ marker: 'link-external',
64
+ requiredKeys: ['url'],
65
+ renderer: ({
66
+ url,
67
+ text
68
+ }) => {
69
+ const label = text || url;
70
+ return `<a href="${escapeHtml(url)}" target="_blank" rel="noopener noreferrer" class="external-link">
71
+ ${escapeHtml(label)}
72
+ </a>`;
73
+ }
74
+ });
27
75
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nakobase/nakobase-md-html",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Convert Markdown to **sanitized HTML** and apply consistent styles – simple, secure, and styled.",
5
5
  "type": "module",
6
6
  "main": "dist/esm/index.js",
@@ -92,7 +92,6 @@
92
92
  "typescript": "^5.5.4"
93
93
  },
94
94
  "dependencies": {
95
- "@steelydylan/markdown-it-imsize": "^1.0.2",
96
95
  "markdown-it": "^12.3.2",
97
96
  "markdown-it-container": "^2.0.0",
98
97
  "markdown-it-custom-block": "^1.0.0",