@noirmd/previewer 1.1.0 → 1.2.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.
@@ -10,6 +10,10 @@ interface NReditorProps extends ReactCodeMirrorProps {
10
10
  debounceMs?: number;
11
11
  /** Inject Tailwind v4 browser CDN for preview. Default: false */
12
12
  tailwindCDN?: boolean;
13
+ /** Called when user clicks Guía */
14
+ onGuide?: () => void;
15
+ /** Called when user clicks Configurar */
16
+ onConfig?: () => void;
13
17
  }
14
18
  /**
15
19
  * NReditor — CodeMirror 6 editor with live markdown preview.
@@ -18,6 +22,7 @@ interface NReditorProps extends ReactCodeMirrorProps {
18
22
  * @example
19
23
  * ```tsx
20
24
  * import NReditor from '@noirmd/previewer/editor';
25
+ * import '@noirmd/previewer/editor.css';
21
26
  * import '@noirmd/previewer/markdown.css';
22
27
  *
23
28
  * <NReditor value={md} onChange={setMd} />
@@ -10,6 +10,10 @@ interface NReditorProps extends ReactCodeMirrorProps {
10
10
  debounceMs?: number;
11
11
  /** Inject Tailwind v4 browser CDN for preview. Default: false */
12
12
  tailwindCDN?: boolean;
13
+ /** Called when user clicks Guía */
14
+ onGuide?: () => void;
15
+ /** Called when user clicks Configurar */
16
+ onConfig?: () => void;
13
17
  }
14
18
  /**
15
19
  * NReditor — CodeMirror 6 editor with live markdown preview.
@@ -18,6 +22,7 @@ interface NReditorProps extends ReactCodeMirrorProps {
18
22
  * @example
19
23
  * ```tsx
20
24
  * import NReditor from '@noirmd/previewer/editor';
25
+ * import '@noirmd/previewer/editor.css';
21
26
  * import '@noirmd/previewer/markdown.css';
22
27
  *
23
28
  * <NReditor value={md} onChange={setMd} />
package/dist/NReditor.js CHANGED
@@ -313,6 +313,62 @@ function useDebounce(value, delay) {
313
313
  return debouncedValue;
314
314
  }
315
315
 
316
+ // react/useTailwindCDN.ts
317
+ import { useEffect as useEffect2, useRef } from "react";
318
+ var CDN_URL = "https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4";
319
+ var SCRIPT_ID = "tailwind-cdn-v4-runtime";
320
+ var CONFIG_ID = "tailwind-cdn-v4-config";
321
+ var TAILWIND_CDN_CONFIG = `
322
+ window.tailwind = window.tailwind || {};
323
+ window.tailwind.config = {
324
+ corePlugins: {
325
+ preflight: false,
326
+ },
327
+ };
328
+ `;
329
+ function useLazyTailwindCDN(enabled) {
330
+ const loadedRef = useRef(false);
331
+ useEffect2(() => {
332
+ if (!enabled) {
333
+ window.__twCDNRefs = (window.__twCDNRefs ?? 1) - 1;
334
+ if ((window.__twCDNRefs ?? 0) <= 0) {
335
+ document.getElementById(SCRIPT_ID)?.remove();
336
+ document.getElementById(CONFIG_ID)?.remove();
337
+ window.__twCDNRefs = 0;
338
+ loadedRef.current = false;
339
+ }
340
+ return;
341
+ }
342
+ window.__twCDNRefs = (window.__twCDNRefs ?? 0) + 1;
343
+ if (!document.getElementById(CONFIG_ID)) {
344
+ const configScript = document.createElement("script");
345
+ configScript.id = CONFIG_ID;
346
+ configScript.textContent = TAILWIND_CDN_CONFIG;
347
+ document.head.appendChild(configScript);
348
+ }
349
+ if (!document.getElementById(SCRIPT_ID)) {
350
+ const script = document.createElement("script");
351
+ script.id = SCRIPT_ID;
352
+ script.src = CDN_URL;
353
+ document.head.appendChild(script);
354
+ loadedRef.current = true;
355
+ }
356
+ return () => {
357
+ window.__twCDNRefs = (window.__twCDNRefs ?? 1) - 1;
358
+ if ((window.__twCDNRefs ?? 0) <= 0) {
359
+ document.getElementById(SCRIPT_ID)?.remove();
360
+ document.getElementById(CONFIG_ID)?.remove();
361
+ window.__twCDNRefs = 0;
362
+ loadedRef.current = false;
363
+ }
364
+ };
365
+ }, [enabled]);
366
+ }
367
+ function scanTailwindCDN() {
368
+ const tw = window.tailwind;
369
+ if (tw?.scan) tw.scan();
370
+ }
371
+
316
372
  // react/CustomMarkdownRenderer.tsx
317
373
  import React8, { useState as useState4, useEffect as useEffect5, useCallback as useCallback2, useMemo, useId as useId3, useRef as useRef5 } from "react";
318
374
 
@@ -408,7 +464,7 @@ function parseHtmlAttrs(attrsString) {
408
464
  // core/parser.ts
409
465
  function parseMarkdown(markdown2) {
410
466
  if (!markdown2) return [];
411
- const lines = markdown2.split("\n");
467
+ const lines = markdown2.replace(/\r\n/g, "\n").replace(/\r/g, "").split("\n");
412
468
  const result = [];
413
469
  let i = 0;
414
470
  while (i < lines.length) {
@@ -735,7 +791,7 @@ function splitSlots(rawContent) {
735
791
  }
736
792
 
737
793
  // react/ui-components.tsx
738
- import { useState as useState2, useRef } from "react";
794
+ import { useState as useState2, useRef as useRef2 } from "react";
739
795
 
740
796
  // react/highlightSetup.ts
741
797
  import hljs from "highlight.js/lib/core";
@@ -828,7 +884,7 @@ var IconRenderer = ({ iconName, extraClasses = "" }) => {
828
884
  };
829
885
  var CodeBlock = ({ code, language, title }) => {
830
886
  const [copied, setCopied] = useState2(false);
831
- const timerRef = useRef(null);
887
+ const timerRef = useRef2(null);
832
888
  const lang = language?.split(/[\s{]/)[0]?.trim() || "";
833
889
  let html;
834
890
  try {
@@ -1355,7 +1411,7 @@ var WrapperDirective = ({
1355
1411
  var WrapperDirective_default = WrapperDirective;
1356
1412
 
1357
1413
  // react/directives/SlideDirective.tsx
1358
- import { useState as useState3, useEffect as useEffect2, useCallback, useRef as useRef2, useLayoutEffect } from "react";
1414
+ import { useState as useState3, useEffect as useEffect3, useCallback, useRef as useRef3, useLayoutEffect } from "react";
1359
1415
  import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1360
1416
  var slideCounter = 0;
1361
1417
  var SlideDirective = ({
@@ -1369,7 +1425,7 @@ var SlideDirective = ({
1369
1425
  const [current, setCurrent] = useState3(0);
1370
1426
  const interval = parseInt(props.interval || "3000", 10);
1371
1427
  const speed = parseInt(props.speed || "500", 10);
1372
- const elRefs = useRef2([]);
1428
+ const elRefs = useRef3([]);
1373
1429
  const [maxH, setMaxH] = useState3(0);
1374
1430
  useLayoutEffect(() => {
1375
1431
  let h = 0;
@@ -1382,7 +1438,7 @@ var SlideDirective = ({
1382
1438
  if (elements.length <= 1) return;
1383
1439
  setCurrent((prev) => (prev + 1) % elements.length);
1384
1440
  }, [elements.length]);
1385
- useEffect2(() => {
1441
+ useEffect3(() => {
1386
1442
  if (elements.length <= 1) return;
1387
1443
  const id = setInterval(cycle, interval);
1388
1444
  return () => clearInterval(id);
@@ -1506,15 +1562,6 @@ var DirectiveRenderer_default = DirectiveRenderer;
1506
1562
 
1507
1563
  // react/RawHtmlRenderer.tsx
1508
1564
  import { useRef as useRef4, useEffect as useEffect4 } from "react";
1509
-
1510
- // react/useTailwindCDN.ts
1511
- import { useEffect as useEffect3, useRef as useRef3 } from "react";
1512
- function scanTailwindCDN() {
1513
- const tw = window.tailwind;
1514
- if (tw?.scan) tw.scan();
1515
- }
1516
-
1517
- // react/RawHtmlRenderer.tsx
1518
1565
  import { jsx as jsx11 } from "react/jsx-runtime";
1519
1566
  var RawHtmlRenderer = ({
1520
1567
  content,
@@ -2228,12 +2275,15 @@ var NReditor = ({
2228
2275
  onChange,
2229
2276
  className,
2230
2277
  debounceMs = 300,
2231
- tailwindCDN = false
2278
+ tailwindCDN = false,
2279
+ onGuide,
2280
+ onConfig
2232
2281
  }) => {
2233
2282
  const editorRef = useRef6(null);
2234
2283
  const [isAllFolded, setIsAllFolded] = useState5(false);
2235
2284
  const [editorMode, setEditorMode] = useState5("editor");
2236
2285
  const debouncedContent = useDebounce(value, debounceMs);
2286
+ useLazyTailwindCDN(tailwindCDN);
2237
2287
  const handleToggleFold = () => {
2238
2288
  if (!editorRef.current) return;
2239
2289
  if (isAllFolded) {
@@ -2281,36 +2331,67 @@ var NReditor = ({
2281
2331
  { key: "split", icon: "vertical_split", label: "Split" },
2282
2332
  { key: "preview", icon: "visibility", label: "Preview" }
2283
2333
  ];
2284
- return /* @__PURE__ */ jsxs8("div", { className: `relative flex-1 flex flex-col min-h-0 ${className || ""}`, children: [
2285
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between px-2 py-1.5 border-b border-border/30 bg-background-secondary-solid/50 rounded-t-2xl shrink-0", children: [
2286
- /* @__PURE__ */ jsx13(
2287
- "button",
2288
- {
2289
- onClick: handleToggleFold,
2290
- className: "p-1 px-2 text-xs font-bold bg-background-secondary-solid border border-border rounded hover:bg-border text-accent-primary transition-colors",
2291
- title: isAllFolded ? "Expand all" : "Collapse all",
2292
- children: isAllFolded ? "\u2569" : "\u2566"
2293
- }
2294
- ),
2295
- /* @__PURE__ */ jsx13("div", { className: "flex items-center gap-0.5 bg-black/10 dark:bg-white/5 rounded-lg p-0.5", children: modeButtons.map((btn) => /* @__PURE__ */ jsxs8(
2296
- "button",
2297
- {
2298
- onClick: () => setEditorMode(btn.key),
2299
- className: `flex items-center gap-1 px-2.5 py-1 text-xs rounded-md transition-colors ${editorMode === btn.key ? "bg-accent-primary/20 text-accent-primary font-semibold" : "text-text-secondary hover:text-text-primary hover:bg-white/5"}`,
2300
- title: btn.label,
2301
- children: [
2302
- /* @__PURE__ */ jsx13("span", { className: "material-symbols-rounded text-sm", children: btn.icon }),
2303
- /* @__PURE__ */ jsx13("span", { className: "hidden sm:inline", children: btn.label })
2304
- ]
2305
- },
2306
- btn.key
2307
- )) })
2334
+ return /* @__PURE__ */ jsxs8("div", { className: `nr-editor ${className || ""}`, children: [
2335
+ /* @__PURE__ */ jsxs8("div", { className: "nr-editor-toolbar", children: [
2336
+ /* @__PURE__ */ jsxs8("div", { className: "nr-editor-toolbar-left", children: [
2337
+ /* @__PURE__ */ jsx13(
2338
+ "button",
2339
+ {
2340
+ onClick: handleToggleFold,
2341
+ className: "nr-toolbar-btn nr-toolbar-btn-fold",
2342
+ title: isAllFolded ? "Expandir todo" : "Colapsar todo",
2343
+ "aria-label": isAllFolded ? "Expandir todo" : "Colapsar todo",
2344
+ children: /* @__PURE__ */ jsx13("span", { className: "material-symbols-rounded", children: isAllFolded ? "unfold_more" : "unfold_less" })
2345
+ }
2346
+ ),
2347
+ /* @__PURE__ */ jsx13("div", { className: "nr-toolbar-divider" }),
2348
+ /* @__PURE__ */ jsx13("div", { className: "nr-toolbar-mode-group", children: modeButtons.map((btn) => /* @__PURE__ */ jsxs8(
2349
+ "button",
2350
+ {
2351
+ onClick: () => setEditorMode(btn.key),
2352
+ className: `nr-toolbar-btn nr-toolbar-btn-mode ${editorMode === btn.key ? "nr-toolbar-btn-mode--active" : ""} ${btn.key === "split" ? "nr-toolbar-btn-split" : ""}`,
2353
+ title: btn.label,
2354
+ children: [
2355
+ /* @__PURE__ */ jsx13("span", { className: "material-symbols-rounded", children: btn.icon }),
2356
+ /* @__PURE__ */ jsx13("span", { className: "nr-toolbar-label", children: btn.label })
2357
+ ]
2358
+ },
2359
+ btn.key
2360
+ )) })
2361
+ ] }),
2362
+ /* @__PURE__ */ jsxs8("div", { className: "nr-editor-toolbar-right", children: [
2363
+ onGuide && /* @__PURE__ */ jsxs8(
2364
+ "button",
2365
+ {
2366
+ onClick: onGuide,
2367
+ className: "nr-toolbar-btn nr-toolbar-btn-sm nr-toolbar-btn-guide",
2368
+ title: "Gu\xEDa de sintaxis",
2369
+ children: [
2370
+ /* @__PURE__ */ jsx13("span", { className: "material-symbols-rounded", children: "menu_book" }),
2371
+ /* @__PURE__ */ jsx13("span", { className: "nr-toolbar-label", children: "Gu\xEDa" })
2372
+ ]
2373
+ }
2374
+ ),
2375
+ onGuide && onConfig && /* @__PURE__ */ jsx13("div", { className: "nr-toolbar-divider" }),
2376
+ onConfig && /* @__PURE__ */ jsxs8(
2377
+ "button",
2378
+ {
2379
+ onClick: onConfig,
2380
+ className: "nr-toolbar-btn nr-toolbar-btn-sm nr-toolbar-btn-config",
2381
+ title: "Configurar tema y metadata",
2382
+ children: [
2383
+ /* @__PURE__ */ jsx13("span", { className: "material-symbols-rounded", children: "tune" }),
2384
+ /* @__PURE__ */ jsx13("span", { className: "nr-toolbar-label", children: "Configurar" })
2385
+ ]
2386
+ }
2387
+ )
2388
+ ] })
2308
2389
  ] }),
2309
- /* @__PURE__ */ jsxs8("div", { className: "flex-1 min-h-0 flex", children: [
2390
+ /* @__PURE__ */ jsxs8("div", { className: "nr-editor-body", children: [
2310
2391
  (editorMode === "editor" || editorMode === "split") && /* @__PURE__ */ jsx13(
2311
2392
  "div",
2312
2393
  {
2313
- className: `${editorMode === "split" ? "w-1/2 border-r border-border/30" : "w-full"} flex-1 flex flex-col min-h-0 min-w-0`,
2394
+ className: `nr-editor-pane ${editorMode === "split" ? "nr-editor-pane--half" : ""}`,
2314
2395
  children: /* @__PURE__ */ jsx13(
2315
2396
  CodeMirror,
2316
2397
  {
@@ -2333,8 +2414,8 @@ var NReditor = ({
2333
2414
  (editorMode === "preview" || editorMode === "split") && /* @__PURE__ */ jsx13(
2334
2415
  "div",
2335
2416
  {
2336
- className: `${editorMode === "split" ? "w-1/2" : "w-full"} flex-1 min-h-0 overflow-auto min-w-0`,
2337
- children: /* @__PURE__ */ jsx13("div", { className: "nr-prose h-full shadow-xs overflow-auto p-4", children: /* @__PURE__ */ jsx13(CustomMarkdownRenderer_default, { content: debouncedContent }) })
2417
+ className: `nr-editor-preview ${editorMode === "split" ? "nr-editor-preview--half" : ""}`,
2418
+ children: /* @__PURE__ */ jsx13("div", { className: "nr-editor-prose", children: /* @__PURE__ */ jsx13("div", { className: "nr-prose", children: /* @__PURE__ */ jsx13(CustomMarkdownRenderer_default, { content: debouncedContent }) }) })
2338
2419
  }
2339
2420
  )
2340
2421
  ] })