@ampless/plugin-youtube 1.0.0-alpha.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.
- package/LICENSE +21 -0
- package/README.ja.md +53 -0
- package/README.md +53 -0
- package/dist/chunk-67ETNQMN.js +39 -0
- package/dist/editor.d.ts +30 -0
- package/dist/editor.js +93 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +45 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ampless contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ja.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @ampless/plugin-youtube
|
|
2
|
+
|
|
3
|
+
> English: [README.md](./README.md)
|
|
4
|
+
|
|
5
|
+
ampless の投稿に YouTube 埋め込みをインラインで表示するプラグイン。`https://youtu.be/<id>` および `https://www.youtube.com/watch?v=<id>` 形式の URL を、プライバシー強化された `youtube-nocookie.com` 上の `<iframe>` 要素として描画する(再生開始まで cookie を設定しない)。
|
|
6
|
+
|
|
7
|
+
## インストール
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm i @ampless/plugin-youtube
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 配線
|
|
14
|
+
|
|
15
|
+
`cms.config.ts`:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { defineConfig } from 'ampless'
|
|
19
|
+
import youtubePlugin from '@ampless/plugin-youtube'
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
plugins: [youtubePlugin()],
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
tiptap エディタ側は `templates/_shared/app/(admin)/admin/_editor-bootstrap.tsx` で Node を登録する:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
'use client'
|
|
30
|
+
import { installAdminEditorExtensions } from '@ampless/admin/editor'
|
|
31
|
+
import { youtubeEditor } from '@ampless/plugin-youtube/editor'
|
|
32
|
+
|
|
33
|
+
export function EditorBootstrap({ children }: { children: React.ReactNode }) {
|
|
34
|
+
installAdminEditorExtensions([youtubeEditor.extension])
|
|
35
|
+
return <>{children}</>
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
その上で `createAdminLayout(admin, { editorBootstrap: EditorBootstrap })` に渡す。
|
|
40
|
+
|
|
41
|
+
## 動作
|
|
42
|
+
|
|
43
|
+
- `amplessYoutube` 型の tiptap ノード(エディタのペーストルールまたは `setYoutube({ videoId })` コマンドで挿入)は YouTube の iframe としてレンダリングされる。
|
|
44
|
+
- markdown 投稿で単独行が YouTube URL の段落(例: 段落の中身が `https://youtu.be/dQw4w9WgXcQ` だけ)も iframe としてレンダリングされる。
|
|
45
|
+
- 段落内インラインの URL はそのまま残る。単独行の URL のみ置換対象。
|
|
46
|
+
|
|
47
|
+
## CSP
|
|
48
|
+
|
|
49
|
+
本プラグインは `<iframe src="https://www.youtube-nocookie.com/...">` を出力する。サイト側の CSP(`next.config.ts` / middleware で設定)の `frame-src` に `youtube-nocookie.com` を含める必要がある。
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @ampless/plugin-youtube
|
|
2
|
+
|
|
3
|
+
> 日本語版: [README.ja.md](./README.ja.md)
|
|
4
|
+
|
|
5
|
+
Inline YouTube embeds for ampless posts. Renders `https://youtu.be/<id>` and `https://www.youtube.com/watch?v=<id>` URLs as `<iframe>` elements pointing at `youtube-nocookie.com` (the privacy-enhanced embed host that does not set cookies before playback starts).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm i @ampless/plugin-youtube
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Wire up
|
|
14
|
+
|
|
15
|
+
`cms.config.ts`:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { defineConfig } from 'ampless'
|
|
19
|
+
import youtubePlugin from '@ampless/plugin-youtube'
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
plugins: [youtubePlugin()],
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For the tiptap editor side, register the Node in `templates/_shared/app/(admin)/admin/_editor-bootstrap.tsx`:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
'use client'
|
|
30
|
+
import { installAdminEditorExtensions } from '@ampless/admin/editor'
|
|
31
|
+
import { youtubeEditor } from '@ampless/plugin-youtube/editor'
|
|
32
|
+
|
|
33
|
+
export function EditorBootstrap({ children }: { children: React.ReactNode }) {
|
|
34
|
+
installAdminEditorExtensions([youtubeEditor.extension])
|
|
35
|
+
return <>{children}</>
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then thread it into `createAdminLayout(admin, { editorBootstrap: EditorBootstrap })`.
|
|
40
|
+
|
|
41
|
+
## Behaviour
|
|
42
|
+
|
|
43
|
+
- Tiptap nodes typed `amplessYoutube` (inserted via the editor's paste rule or the `setYoutube({ videoId })` command) render as a YouTube iframe.
|
|
44
|
+
- Markdown posts with a single-line YouTube URL (e.g. a paragraph whose entire content is `https://youtu.be/dQw4w9WgXcQ`) also render as an iframe.
|
|
45
|
+
- Inline URLs in the middle of a paragraph are left as-is — only single-line URLs convert.
|
|
46
|
+
|
|
47
|
+
## CSP
|
|
48
|
+
|
|
49
|
+
The plugin emits `<iframe src="https://www.youtube-nocookie.com/...">`. Your site's CSP (configured in `next.config.ts` / middleware) needs to include `youtube-nocookie.com` in `frame-src` for the embed to load.
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/shared.tsx
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
var YOUTUBE_URL = /^https:\/\/(?:www\.)?(?:youtube\.com\/watch\?v=([\w-]{11})|youtu\.be\/([\w-]{11}))(?:[?&#]\S*)?$/;
|
|
4
|
+
var VIDEO_ID = /^[\w-]{11}$/;
|
|
5
|
+
function YouTubeEmbed({
|
|
6
|
+
videoId,
|
|
7
|
+
start,
|
|
8
|
+
title
|
|
9
|
+
}) {
|
|
10
|
+
if (typeof videoId !== "string" || !VIDEO_ID.test(videoId)) return null;
|
|
11
|
+
const query = typeof start === "number" && Number.isFinite(start) && start > 0 ? `?start=${Math.floor(start)}` : "";
|
|
12
|
+
const src = `https://www.youtube-nocookie.com/embed/${videoId}${query}`;
|
|
13
|
+
return /* @__PURE__ */ jsx(
|
|
14
|
+
"iframe",
|
|
15
|
+
{
|
|
16
|
+
src,
|
|
17
|
+
title: title ?? `YouTube video ${videoId}`,
|
|
18
|
+
loading: "lazy",
|
|
19
|
+
allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",
|
|
20
|
+
referrerPolicy: "strict-origin-when-cross-origin",
|
|
21
|
+
allowFullScreen: true,
|
|
22
|
+
width: 560,
|
|
23
|
+
height: 315,
|
|
24
|
+
style: { width: "100%", aspectRatio: "16 / 9", height: "auto", border: 0 }
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
function parseYoutubeUrl(url) {
|
|
29
|
+
const trimmed = url.trim();
|
|
30
|
+
const m = trimmed.match(YOUTUBE_URL);
|
|
31
|
+
if (!m) return null;
|
|
32
|
+
return m[1] ?? m[2] ?? null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
YOUTUBE_URL,
|
|
37
|
+
YouTubeEmbed,
|
|
38
|
+
parseYoutubeUrl
|
|
39
|
+
};
|
package/dist/editor.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Node } from '@tiptap/core';
|
|
2
|
+
|
|
3
|
+
declare module '@tiptap/core' {
|
|
4
|
+
interface Commands<ReturnType> {
|
|
5
|
+
amplessYoutube: {
|
|
6
|
+
/** Insert a YouTube embed node with the given video id (+ optional start). */
|
|
7
|
+
setYoutube: (opts: {
|
|
8
|
+
videoId: string;
|
|
9
|
+
start?: number;
|
|
10
|
+
}) => ReturnType;
|
|
11
|
+
/**
|
|
12
|
+
* Insert a YouTube embed from a URL. Returns false (and does
|
|
13
|
+
* nothing) if the URL doesn't match the canonical / short form.
|
|
14
|
+
*/
|
|
15
|
+
insertYoutubeFromUrl: (url: string) => ReturnType;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
declare const AmplessYoutubeNode: Node<any, any>;
|
|
20
|
+
/**
|
|
21
|
+
* Named export consumed by templates' `_editor-bootstrap.tsx`. The
|
|
22
|
+
* `.extension` field is the tiptap Node — the wrapper object exists so
|
|
23
|
+
* future per-instance configuration can be added without breaking the
|
|
24
|
+
* call site.
|
|
25
|
+
*/
|
|
26
|
+
declare const youtubeEditor: {
|
|
27
|
+
extension: Node<any, any>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export { AmplessYoutubeNode, youtubeEditor };
|
package/dist/editor.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
YOUTUBE_URL,
|
|
4
|
+
parseYoutubeUrl
|
|
5
|
+
} from "./chunk-67ETNQMN.js";
|
|
6
|
+
|
|
7
|
+
// src/editor.tsx
|
|
8
|
+
import { Node, mergeAttributes } from "@tiptap/core";
|
|
9
|
+
var AmplessYoutubeNode = Node.create({
|
|
10
|
+
name: "amplessYoutube",
|
|
11
|
+
group: "block",
|
|
12
|
+
atom: true,
|
|
13
|
+
selectable: true,
|
|
14
|
+
draggable: true,
|
|
15
|
+
addAttributes() {
|
|
16
|
+
return {
|
|
17
|
+
videoId: {
|
|
18
|
+
default: "",
|
|
19
|
+
parseHTML: (el) => el.getAttribute("data-video-id") ?? "",
|
|
20
|
+
renderHTML: (attrs) => ({
|
|
21
|
+
"data-video-id": String(attrs.videoId ?? "")
|
|
22
|
+
})
|
|
23
|
+
},
|
|
24
|
+
start: {
|
|
25
|
+
default: null,
|
|
26
|
+
parseHTML: (el) => {
|
|
27
|
+
const v = el.getAttribute("data-start");
|
|
28
|
+
if (v === null) return null;
|
|
29
|
+
const n = Number(v);
|
|
30
|
+
return Number.isFinite(n) ? n : null;
|
|
31
|
+
},
|
|
32
|
+
renderHTML: (attrs) => attrs.start != null ? { "data-start": String(attrs.start) } : {}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
parseHTML() {
|
|
37
|
+
return [
|
|
38
|
+
{
|
|
39
|
+
tag: "div[data-ampless-youtube]"
|
|
40
|
+
}
|
|
41
|
+
];
|
|
42
|
+
},
|
|
43
|
+
renderHTML({ HTMLAttributes }) {
|
|
44
|
+
return [
|
|
45
|
+
"div",
|
|
46
|
+
mergeAttributes(HTMLAttributes, {
|
|
47
|
+
"data-ampless-youtube": "",
|
|
48
|
+
class: "ampless-youtube-placeholder"
|
|
49
|
+
}),
|
|
50
|
+
["span", {}, `YouTube: ${HTMLAttributes["data-video-id"] ?? ""}`]
|
|
51
|
+
];
|
|
52
|
+
},
|
|
53
|
+
addCommands() {
|
|
54
|
+
return {
|
|
55
|
+
setYoutube: (opts) => ({ commands }) => commands.insertContent({
|
|
56
|
+
type: this.name,
|
|
57
|
+
attrs: { videoId: opts.videoId, start: opts.start ?? null }
|
|
58
|
+
}),
|
|
59
|
+
insertYoutubeFromUrl: (url) => ({ commands }) => {
|
|
60
|
+
const videoId = parseYoutubeUrl(url);
|
|
61
|
+
if (!videoId) return false;
|
|
62
|
+
return commands.insertContent({
|
|
63
|
+
type: this.name,
|
|
64
|
+
attrs: { videoId, start: null }
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
addPasteRules() {
|
|
70
|
+
return [
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
|
+
{
|
|
73
|
+
find: YOUTUBE_URL,
|
|
74
|
+
handler: ({ range, match, commands }) => {
|
|
75
|
+
const videoId = match[1] ?? match[2];
|
|
76
|
+
if (!videoId) return;
|
|
77
|
+
commands.deleteRange(range);
|
|
78
|
+
commands.insertContent({
|
|
79
|
+
type: this.name,
|
|
80
|
+
attrs: { videoId, start: null }
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
var youtubeEditor = {
|
|
88
|
+
extension: AmplessYoutubeNode
|
|
89
|
+
};
|
|
90
|
+
export {
|
|
91
|
+
AmplessYoutubeNode,
|
|
92
|
+
youtubeEditor
|
|
93
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { AmplessPlugin } from 'ampless';
|
|
2
|
+
import { ReactElement } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* YouTube canonical / short URL pattern. Anchored with `^...$` so the
|
|
6
|
+
* markdown walker only intercepts paragraphs whose entire content is a
|
|
7
|
+
* single YouTube URL.
|
|
8
|
+
*
|
|
9
|
+
* Captures:
|
|
10
|
+
* - Group 1: video id for `youtube.com/watch?v=<id>`
|
|
11
|
+
* - Group 2: video id for `youtu.be/<id>`
|
|
12
|
+
*
|
|
13
|
+
* The id grammar `[\w-]{11}` matches the canonical 11-character
|
|
14
|
+
* YouTube video id format. Anything else fails the regex.
|
|
15
|
+
*/
|
|
16
|
+
declare const YOUTUBE_URL: RegExp;
|
|
17
|
+
interface YouTubeEmbedProps {
|
|
18
|
+
videoId: string;
|
|
19
|
+
/** Start position in seconds. Forwarded to the embed URL as `?start=`. */
|
|
20
|
+
start?: number;
|
|
21
|
+
/** Embed title for screen readers / iframe `title`. */
|
|
22
|
+
title?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Render a YouTube embed as a single `<iframe>` pointing at
|
|
26
|
+
* youtube-nocookie.com (the privacy-enhanced embed host that does NOT
|
|
27
|
+
* set cookies until the user starts playback). Defensive: an invalid
|
|
28
|
+
* `videoId` (not matching the 11-char pattern) renders nothing.
|
|
29
|
+
*/
|
|
30
|
+
declare function YouTubeEmbed({ videoId, start, title, }: YouTubeEmbedProps): ReactElement | null;
|
|
31
|
+
/**
|
|
32
|
+
* Extract the 11-char video id from a URL string. Returns `null` if the
|
|
33
|
+
* URL doesn't match the canonical / short form. Used by the tiptap
|
|
34
|
+
* editor's paste rule + the `contentFields` markdown walker.
|
|
35
|
+
*/
|
|
36
|
+
declare function parseYoutubeUrl(url: string): string | null;
|
|
37
|
+
|
|
38
|
+
interface YoutubePluginOptions {
|
|
39
|
+
/**
|
|
40
|
+
* Override the plugin's `name` (and therefore admin-side namespace).
|
|
41
|
+
* Useful if a site wants to install two instances or rename the
|
|
42
|
+
* default — though multi-instance is not currently supported by the
|
|
43
|
+
* contentFields registry.
|
|
44
|
+
*/
|
|
45
|
+
name?: string;
|
|
46
|
+
}
|
|
47
|
+
declare function youtubePlugin(opts?: YoutubePluginOptions): AmplessPlugin;
|
|
48
|
+
|
|
49
|
+
export { YOUTUBE_URL, YouTubeEmbed, type YoutubePluginOptions, youtubePlugin as default, parseYoutubeUrl };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
YOUTUBE_URL,
|
|
3
|
+
YouTubeEmbed,
|
|
4
|
+
parseYoutubeUrl
|
|
5
|
+
} from "./chunk-67ETNQMN.js";
|
|
6
|
+
|
|
7
|
+
// src/index.tsx
|
|
8
|
+
import { definePlugin } from "ampless";
|
|
9
|
+
import { jsx } from "react/jsx-runtime";
|
|
10
|
+
function youtubePlugin(opts = {}) {
|
|
11
|
+
return definePlugin({
|
|
12
|
+
name: opts.name ?? "@ampless/plugin-youtube",
|
|
13
|
+
apiVersion: 1,
|
|
14
|
+
packageName: "@ampless/plugin-youtube",
|
|
15
|
+
trust_level: "trusted",
|
|
16
|
+
capabilities: ["contentFields"],
|
|
17
|
+
displayName: { en: "YouTube embeds", ja: "YouTube \u57CB\u3081\u8FBC\u307F" },
|
|
18
|
+
contentFields: [
|
|
19
|
+
{
|
|
20
|
+
kind: "tiptap",
|
|
21
|
+
nodeType: "amplessYoutube",
|
|
22
|
+
render: (node) => {
|
|
23
|
+
const videoId = String(node.attrs?.videoId ?? "");
|
|
24
|
+
const startRaw = node.attrs?.start;
|
|
25
|
+
const start = typeof startRaw === "number" && Number.isFinite(startRaw) ? startRaw : void 0;
|
|
26
|
+
return /* @__PURE__ */ jsx(YouTubeEmbed, { videoId, start });
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
kind: "markdown-url",
|
|
31
|
+
pattern: YOUTUBE_URL,
|
|
32
|
+
render: ({ match }) => {
|
|
33
|
+
const videoId = match[1] ?? match[2] ?? "";
|
|
34
|
+
return /* @__PURE__ */ jsx(YouTubeEmbed, { videoId });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
YOUTUBE_URL,
|
|
42
|
+
YouTubeEmbed,
|
|
43
|
+
youtubePlugin as default,
|
|
44
|
+
parseYoutubeUrl
|
|
45
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ampless/plugin-youtube",
|
|
3
|
+
"version": "1.0.0-alpha.1",
|
|
4
|
+
"description": "YouTube embed plugin for ampless — renders youtube.com / youtu.be URLs inline as iframes in tiptap + markdown posts",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
},
|
|
12
|
+
"./editor": {
|
|
13
|
+
"import": "./dist/editor.js",
|
|
14
|
+
"types": "./dist/editor.d.ts"
|
|
15
|
+
},
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/heavymoons/ampless.git",
|
|
28
|
+
"directory": "packages/plugin-youtube"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/heavymoons/ampless/tree/main/packages/plugin-youtube#readme",
|
|
31
|
+
"bugs": "https://github.com/heavymoons/ampless/issues",
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@tiptap/core": "^3",
|
|
34
|
+
"react": "^18 || ^19",
|
|
35
|
+
"ampless": "1.0.0-alpha.42"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@tiptap/core": "^3.23.6",
|
|
39
|
+
"@types/react": "^19.2.15",
|
|
40
|
+
"@types/react-dom": "^19.2.3",
|
|
41
|
+
"react": "^19.2.6",
|
|
42
|
+
"react-dom": "^19.2.6"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"ampless",
|
|
46
|
+
"plugin",
|
|
47
|
+
"ampless-plugin",
|
|
48
|
+
"youtube",
|
|
49
|
+
"embed",
|
|
50
|
+
"cms"
|
|
51
|
+
],
|
|
52
|
+
"amplessPlugin": {
|
|
53
|
+
"apiVersion": 1,
|
|
54
|
+
"name": "@ampless/plugin-youtube",
|
|
55
|
+
"trustLevel": "trusted",
|
|
56
|
+
"capabilities": [
|
|
57
|
+
"contentFields"
|
|
58
|
+
],
|
|
59
|
+
"displayName": {
|
|
60
|
+
"en": "YouTube embeds",
|
|
61
|
+
"ja": "YouTube 埋め込み"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "tsup",
|
|
66
|
+
"dev": "tsup --watch",
|
|
67
|
+
"lint": "tsc --noEmit",
|
|
68
|
+
"test": "vitest run --passWithNoTests"
|
|
69
|
+
}
|
|
70
|
+
}
|