@ampless/plugin-youtube 1.0.0-alpha.5 → 1.0.0-alpha.7

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/editor.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Node } from '@tiptap/core';
2
+ import { TiptapNodeMarkdownAdapters } from 'ampless';
2
3
 
3
4
  declare module '@tiptap/core' {
4
5
  interface Commands<ReturnType> {
@@ -35,4 +36,16 @@ declare const youtubeEditor: {
35
36
  */
36
37
  declare const editorExtension: Node<any, any>;
37
38
 
38
- export { AmplessYoutubeNode, editorExtension, youtubeEditor };
39
+ /**
40
+ * tiptap → markdown adapter map. Serialises `amplessYoutube` nodes back
41
+ * to a bare `https://youtu.be/<videoId>` URL line so the admin's
42
+ * "format: tiptap → markdown" body switch is lossless. The reverse
43
+ * direction (URL → embed node on paste) is handled by the existing paste
44
+ * rule + `extractSingleUrl` path in the runtime.
45
+ *
46
+ * `update-ampless` reads this export (via namespace import `* as`) and
47
+ * wires it into `installAdminTiptapNodeMarkdown`.
48
+ */
49
+ declare const tiptapNodeToMarkdown: TiptapNodeMarkdownAdapters;
50
+
51
+ export { AmplessYoutubeNode, editorExtension, tiptapNodeToMarkdown, youtubeEditor };
package/dist/editor.js CHANGED
@@ -6,6 +6,23 @@ import {
6
6
 
7
7
  // src/editor.tsx
8
8
  import { Node, mergeAttributes } from "@tiptap/core";
9
+ function getBareUrlLinkHref(el) {
10
+ if (el.tagName.toLowerCase() === "a") {
11
+ return el.getAttribute("href");
12
+ }
13
+ if (el.tagName.toLowerCase() !== "p") return null;
14
+ const children = Array.from(el.children);
15
+ if (children.length !== 1) return null;
16
+ const link = children[0];
17
+ if (!(link instanceof HTMLElement)) return null;
18
+ if (link.tagName.toLowerCase() !== "a") return null;
19
+ const href = link.getAttribute("href")?.trim();
20
+ if (!href) return null;
21
+ const linkText = link.textContent?.trim();
22
+ if (linkText !== href) return null;
23
+ if (el.textContent?.trim() !== linkText) return null;
24
+ return href;
25
+ }
9
26
  var AmplessYoutubeNode = Node.create({
10
27
  name: "amplessYoutube",
11
28
  group: "block",
@@ -16,7 +33,12 @@ var AmplessYoutubeNode = Node.create({
16
33
  return {
17
34
  videoId: {
18
35
  default: "",
19
- parseHTML: (el) => el.getAttribute("data-video-id") ?? "",
36
+ parseHTML: (el) => {
37
+ const dataVideoId = el.getAttribute("data-video-id");
38
+ if (dataVideoId != null) return dataVideoId;
39
+ const href = getBareUrlLinkHref(el);
40
+ return href ? parseYoutubeUrl(href) ?? "" : "";
41
+ },
20
42
  renderHTML: (attrs) => ({
21
43
  "data-video-id": String(attrs.videoId ?? "")
22
44
  })
@@ -37,6 +59,49 @@ var AmplessYoutubeNode = Node.create({
37
59
  return [
38
60
  {
39
61
  tag: "div[data-ampless-youtube]"
62
+ },
63
+ {
64
+ tag: "p",
65
+ priority: 100,
66
+ getAttrs: (el) => {
67
+ const href = getBareUrlLinkHref(el) ?? "";
68
+ const videoId = parseYoutubeUrl(href);
69
+ if (!videoId) return false;
70
+ return { videoId, start: null };
71
+ }
72
+ },
73
+ {
74
+ // Defensive fallback for genuinely top-level bare-URL `<a>` tags
75
+ // (no parent block, or directly under `<body>` — = HTML fragments
76
+ // without a paragraph wrapper at all). The primary case is the
77
+ // markdown bare URL line `<p><a href=URL>URL</a></p>`, which is
78
+ // already handled by the `tag: 'p'` rule above (priority 100,
79
+ // `getBareUrlLinkHref`). This rule covers the rare standalone-link
80
+ // shape that bypasses that rule entirely.
81
+ //
82
+ // Scope is deliberately narrow:
83
+ // 1. Link text equals href — `<a href="URL">caption</a>` stays
84
+ // a captioned Link, not silently swallowed into an embed.
85
+ // 2. Parent is null or `<body>` only. Inside any content block
86
+ // (`<p>`, `<li>`, `<blockquote>`, `<div>`, …) the autolink
87
+ // stays an inline Link mark to preserve the surrounding
88
+ // structure. Promoting it would split `<li>` into an empty
89
+ // paragraph item plus a list-external embed, break
90
+ // blockquotes, etc.
91
+ tag: "a[href]",
92
+ priority: 100,
93
+ getAttrs: (el) => {
94
+ const link = el;
95
+ const parent = link.parentElement;
96
+ if (parent && parent.tagName.toLowerCase() !== "body") return false;
97
+ const href = link.getAttribute("href")?.trim() ?? "";
98
+ if (!href) return false;
99
+ const linkText = link.textContent?.trim() ?? "";
100
+ if (linkText !== href) return false;
101
+ const videoId = parseYoutubeUrl(href);
102
+ if (!videoId) return false;
103
+ return { videoId, start: null };
104
+ }
40
105
  }
41
106
  ];
42
107
  },
@@ -96,8 +161,16 @@ var youtubeEditor = {
96
161
  extension: AmplessYoutubeNode
97
162
  };
98
163
  var editorExtension = AmplessYoutubeNode;
164
+ var tiptapNodeToMarkdown = {
165
+ amplessYoutube: (node) => {
166
+ const videoId = String(node.attrs?.videoId ?? "").trim();
167
+ if (!videoId) return null;
168
+ return `https://youtu.be/${videoId}`;
169
+ }
170
+ };
99
171
  export {
100
172
  AmplessYoutubeNode,
101
173
  editorExtension,
174
+ tiptapNodeToMarkdown,
102
175
  youtubeEditor
103
176
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ampless/plugin-youtube",
3
- "version": "1.0.0-alpha.5",
3
+ "version": "1.0.0-alpha.7",
4
4
  "description": "YouTube embed plugin for ampless — renders youtube.com / youtu.be URLs inline as iframes in tiptap + markdown posts",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,7 +32,7 @@
32
32
  "peerDependencies": {
33
33
  "@tiptap/core": "^3",
34
34
  "react": "^18 || ^19",
35
- "ampless": "1.0.0-alpha.44"
35
+ "ampless": "1.0.0-alpha.46"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@tiptap/core": "^3.23.6",