You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@answer.apache.org by li...@apache.org on 2024/06/06 01:59:23 UTC
(incubator-answer-plugins) 02/17: feat(embed-basic): add i18n support for basic embed plugin
This is an automated email from the ASF dual-hosted git repository.
linkinstar pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-answer-plugins.git
commit bbda77a14ec7f4439b35b1dfd7a683a01600a4ce
Author: robin <ro...@rnode.me>
AuthorDate: Mon May 27 16:46:23 2024 +0800
feat(embed-basic): add i18n support for basic embed plugin
---
embed-basic/Component.tsx | 64 +++++++++
embed-basic/hooks.ts | 246 ++++++++++++++++++++++++++++++++
embed-basic/i18n/en_US.yaml | 11 +-
embed-basic/i18n/index.ts | 7 +
embed-basic/i18n/zh_CN.yaml | 11 +-
embed-basic/index.ts | 34 +++++
embed-basic/modal.tsx | 149 +++++++++++++++++++
embed-basic/package.json | 17 +++
embed-basic/pnpm-lock.yaml | 315 +++++++++++++++++++++++++++++++++++++++++
embed-basic/tsconfig.json | 31 ++++
embed-basic/tsconfig.node.json | 10 ++
11 files changed, 893 insertions(+), 2 deletions(-)
diff --git a/embed-basic/Component.tsx b/embed-basic/Component.tsx
new file mode 100644
index 0000000..f58e2ce
--- /dev/null
+++ b/embed-basic/Component.tsx
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { useState } from 'react';
+import { Button } from 'react-bootstrap';
+
+import EmbedModal from './modal';
+import { useRenderEmbed } from './hooks';
+
+const Component = ({ editor, previewElement }) => {
+ const [show, setShowState] = useState(false);
+
+ useRenderEmbed(previewElement);
+
+ const handleShow = () => {
+ setShowState(true);
+ };
+
+ const handleConfirm = ({ title, url }) => {
+ setShowState(false);
+ // 判断光标是否在行首
+ const cursor = editor.getCursor();
+ if (cursor.ch !== 0) {
+ editor.replaceSelection('\n');
+ }
+ const embed = `\n[${title}](${url} "@embed")\n`;
+ editor.replaceSelection(embed);
+ editor.focus();
+ };
+ return (
+ <>
+ <Button
+ variant="link"
+ className="p-0 b-0 btn-no-border d-flex justify-content-center align-items-center text-black"
+ style={{ width: '1.5rem', height: '1.5rem' }}
+ onClick={handleShow}>
+ <i className="bi bi-window" />
+ </Button>
+ <EmbedModal
+ show={show}
+ setShowState={setShowState}
+ onConfirm={handleConfirm}
+ />
+ </>
+ );
+};
+
+export default Component;
diff --git a/embed-basic/hooks.ts b/embed-basic/hooks.ts
new file mode 100644
index 0000000..1c3a981
--- /dev/null
+++ b/embed-basic/hooks.ts
@@ -0,0 +1,246 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { useEffect, useState } from 'react';
+
+interface Config {
+ platform: string;
+ enable: boolean;
+}
+const useRenderEmbed = (element: HTMLElement) => {
+ const [configs, setConfigs] = useState<Config[] | null>(null);
+
+ const embeds = [
+ {
+ name: 'YouTube',
+ regexs: [
+ /https:\/\/youtu\.be\/([a-zA-Z0-9_-]{11})/,
+ /https:\/\/www\.youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})/,
+ /https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]{11})/,
+ ],
+ embed: (videoId: string) => {
+ return `<iframe width="100%" height="380" src="https://www.youtube.com/embed/${videoId}" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`;
+ },
+ },
+ {
+ name: 'Twitter',
+ regexs: [
+ /https:\/\/twitter\.com\/[a-zA-Z0-9_]+\/status\/([a-zA-Z0-9_]+)/,
+ /https:\/\/x\.com\/[a-zA-Z0-9_]+\/status\/([a-zA-Z0-9_]+)/,
+ ],
+ embed: (_, url, title = '') => {
+ const blockquoteElement = document.createElement('blockquote');
+ blockquoteElement.classList.add('twitter-tweet');
+
+ const anchorElement = document.createElement('a');
+ anchorElement.href = url.replace('x.com', 'twitter.com');
+
+ anchorElement.textContent = title;
+ blockquoteElement.appendChild(anchorElement);
+ const scriptElement = document.createElement('script');
+ scriptElement.src = 'https://platform.twitter.com/widgets.js';
+ scriptElement.async = true;
+
+ const styleElement = document.createElement('style');
+ styleElement.innerHTML = `
+ .twitter-tweet {
+ display: block;
+ margin: 0 auto;
+ }
+ `;
+
+ return [styleElement, blockquoteElement, scriptElement];
+ },
+ },
+ {
+ name: 'CodePen',
+ regexs: [
+ /https:\/\/codepen\.io\/[a-zA-Z0-9_]+\/pen\/([a-zA-Z0-9_]+)/,
+ /https:\/\/codepen\.io\/[a-zA-Z0-9_]+\/full\/([a-zA-Z0-9_]+)/,
+ ],
+ embed: (penId) => {
+ return `<iframe width="100%" height="380" scrolling="no" title="CodePen Embed" src="https://codepen.io/${penId}/embed/preview/${penId}?height=265&theme-id=0&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"></iframe>`;
+ },
+ },
+ {
+ name: 'JSFiddle',
+ regexs: [
+ /https:\/\/jsfiddle\.net\/[a-zA-Z0-9_]+\/([a-zA-Z0-9_]+)/,
+ /https:\/\/jsfiddle\.net\/[a-zA-Z0-9_]+\/([a-zA-Z0-9_]+)\/embed/,
+ ],
+ embed: (fiddleId: string) => {
+ return `<iframe width="100%" height="380" src="https://jsfiddle.net/${fiddleId}/embedded/" allowfullscreen="allowfullscreen" allowpaymentrequest frameborder="0"></iframe>`;
+ },
+ },
+ {
+ name: 'GithubGist',
+ regexs: [
+ /https:\/\/gist\.github\.com\/[a-zA-Z0-9_]+\/([a-zA-Z0-9_]+)/,
+ /https:\/\/gist\.github\.com\/[a-zA-Z0-9_]+\/([a-zA-Z0-9_]+)\.js/,
+ ],
+ embed: (_, url) => {
+ const scriptUrl = url.indexOf('.js') > -1 ? url : `${url}.js`;
+ console.log(scriptUrl);
+ return `<iframe
+ width="100%"
+ height="350"
+ src="data:text/html;charset=utf-8,
+ <head><base target='_blank' /></head>
+ <body style='margin:0;'><script src='${scriptUrl}'></script>
+ </body>">`;
+ },
+ },
+ {
+ name: 'Figma',
+ regexs: [
+ /https:\/\/www\.figma\.com\/design\/[a-zA-Z0-9_]+\/([a-zA-Z0-9_]+)/,
+ /https:\/\/www\.figma\.com\/file\/[a-zA-Z0-9_]+\/([a-zA-Z0-9_]+)/,
+ ],
+ embed: (_, url) => {
+ return `<iframe style="border: none;" width="100%" height="450
+" src="https://www.figma.com/embed?embed_host=share&url=${url}" allowfullscreen></iframe>`;
+ },
+ },
+ {
+ name: 'Excalidraw',
+ regexs: [
+ /https:\/\/excalidraw\.com\/#json=([a-zA-Z0-9_,-]+)/,
+ /https:\/\/excalidraw\.com\/([a-zA-Z0-9_,-]+)/,
+ ],
+ embed: (excalidrawId: string) => {
+ return `<iframe width="100%" height="380" src="https://excalidraw.com/${excalidrawId}/embed" frameborder="0"></iframe>`;
+ },
+ },
+ {
+ name: 'Loom',
+ regexs: [
+ /https:\/\/www\.loom\.com\/embed\/([a-zA-Z0-9_]+)/,
+ /https:\/\/www\.loom\.com\/share\/([a-zA-Z0-9_]+)/,
+ ],
+ embed: (loomId: string) => {
+ return `<iframe width="100%" height="380" src="https://www.loom.com/embed/${loomId}" frameborder="0"></iframe>`;
+ },
+ },
+ {
+ name: 'Dropbox',
+ regexs: [
+ /https:\/\/www\.dropbox\.com\/s\/([a-zA-Z0-9_]+)\/[a-zA-Z0-9_]+/,
+ ],
+ embed: (dropboxId: string) => {
+ return `<iframe width="100%" height="380" src="https://www.dropbox.com/s/${dropboxId}?raw=1" frameborder="0"></iframe>`;
+ },
+ },
+ ];
+
+ const filteredEmbeds = embeds.filter((embed) => {
+ const finded = configs?.find(
+ (config) => config.platform === embed.name && config.enable,
+ );
+ return finded;
+ });
+
+ const renderEmbed = (
+ url: string,
+ title: string,
+ ): string | HTMLElement | HTMLElement[] => {
+ let html: string | HTMLElement | HTMLElement[] = '';
+
+ filteredEmbeds.forEach((embed) => {
+ if (html) return;
+ embed.regexs.forEach((regex) => {
+ if (html) return;
+ const match = url.match(regex);
+ if (match) {
+ html = embed.embed(match[1], url, title);
+ }
+ });
+ });
+
+ return html;
+ };
+
+ const render = () => {
+ if (!element) {
+ return;
+ }
+
+ const links = element.querySelectorAll('a');
+ links.forEach((link) => {
+ const url = link.getAttribute('href') || '';
+ const title = link.getAttribute('title') || '';
+ if (!url) {
+ return;
+ }
+ if (title !== '@embed') {
+ return;
+ }
+ const embed = renderEmbed(url, link?.textContent || '');
+ if (embed) {
+ if (typeof embed === 'string') {
+ link.innerHTML = embed;
+ } else if (Array.isArray(embed)) {
+ link.innerHTML = '';
+ embed.forEach((item) => {
+ link.appendChild(item);
+ });
+ } else {
+ link.innerHTML = '';
+ link.appendChild(embed);
+ }
+ } else {
+ link.innerHTML = `
+ <div class="border rounded p-3">
+ <div class="text-secondary">${url}</div>
+ <div class="text-body">${link.textContent}</div>
+ </div>
+ `;
+ }
+ });
+ };
+
+ const getConfig = () => {
+ fetch('/answer/api/v1/embed/config')
+ .then((response) => response.json())
+ .then((result) => setConfigs(result.data));
+ };
+ useEffect(() => {
+ getConfig();
+ }, []);
+
+ useEffect(() => {
+ if (!element) {
+ return;
+ }
+
+ if (!configs) {
+ return;
+ }
+
+ render();
+ const observer = new MutationObserver(() => {
+ render();
+ });
+
+ observer.observe(element, {
+ childList: true,
+ });
+ }, [element, configs]);
+};
+
+export { useRenderEmbed };
diff --git a/embed-basic/i18n/en_US.yaml b/embed-basic/i18n/en_US.yaml
index c12aab1..c47eb2a 100644
--- a/embed-basic/i18n/en_US.yaml
+++ b/embed-basic/i18n/en_US.yaml
@@ -46,4 +46,13 @@ plugin:
twitter:
other: X(Twitter)
youtube:
- other: YouTube
\ No newline at end of file
+ other: YouTube
+ frontend:
+ header: Add Embed
+ title: Title
+ url: URL
+ cancel: Cancel
+ add: Add
+ required_title: Title is required
+ required_url: URL is required
+ invalid_url: Invalid URL
\ No newline at end of file
diff --git a/embed-basic/i18n/index.ts b/embed-basic/i18n/index.ts
new file mode 100644
index 0000000..89fd071
--- /dev/null
+++ b/embed-basic/i18n/index.ts
@@ -0,0 +1,7 @@
+import en_US from './en_US.yaml';
+import zh_CN from './zh_CN.yaml';
+
+export default {
+ en_US,
+ zh_CN,
+};
diff --git a/embed-basic/i18n/zh_CN.yaml b/embed-basic/i18n/zh_CN.yaml
index b9cec97..f28ef41 100644
--- a/embed-basic/i18n/zh_CN.yaml
+++ b/embed-basic/i18n/zh_CN.yaml
@@ -46,4 +46,13 @@ plugin:
twitter:
other: X(Twitter)
youtube:
- other: YouTube
\ No newline at end of file
+ other: YouTube
+ frontend:
+ header: 添加嵌入
+ title: 标题
+ url: URL
+ cancel: 取消
+ add: 添加
+ required_title: 标题是必需的
+ required_url: URL 是必需的
+ invalid_url: 无效的 URL
diff --git a/embed-basic/index.ts b/embed-basic/index.ts
new file mode 100644
index 0000000..cba01d2
--- /dev/null
+++ b/embed-basic/index.ts
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import Component from './Component';
+import { useRenderEmbed } from './hooks';
+import i18nConfig from './i18n';
+
+export default {
+ info: {
+ slug_name: 'basic_embed',
+ type: 'editor',
+ },
+ component: Component,
+ i18nConfig,
+ hooks: {
+ useRender: [useRenderEmbed],
+ },
+};
diff --git a/embed-basic/modal.tsx b/embed-basic/modal.tsx
new file mode 100644
index 0000000..f156be1
--- /dev/null
+++ b/embed-basic/modal.tsx
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { useState } from 'react';
+import { Modal, Button, Form } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+const EmbedModal = ({ show, setShowState, onConfirm }) => {
+ const [title, setTitle] = useState({
+ value: '',
+ isInvalid: false,
+ errorMsg: '',
+ });
+ const [url, setUrl] = useState({
+ value: '',
+ isInvalid: false,
+ errorMsg: '',
+ });
+ const { t } = useTranslation('plugin', {
+ keyPrefix: 'basic_embed.frontend',
+ });
+
+ const handleHide = () => {
+ setShowState(false);
+ };
+
+ const handleChangeTitle = (e) => {
+ setTitle({
+ value: e.target.value,
+ isInvalid: false,
+ errorMsg: '',
+ });
+ };
+
+ const handleChangeUrl = (e) => {
+ setUrl({
+ value: e.target.value,
+ isInvalid: false,
+ errorMsg: '',
+ });
+ };
+
+ const handleSubmit = () => {
+ if (!title.value) {
+ setTitle({
+ ...title,
+ isInvalid: true,
+ errorMsg: t('required_title'),
+ });
+ return;
+ }
+ if (!url.value) {
+ setUrl({
+ ...url,
+ isInvalid: true,
+ errorMsg: t('required_url'),
+ });
+ return;
+ }
+ const urlRegex =
+ /^(https?:\/\/)?([\da-z\\.-]+)\.([a-z\\.]{2,6})([\\/\w \\.-]*)*\/?$/;
+ if (!urlRegex.test(url.value)) {
+ setUrl({
+ ...url,
+ isInvalid: true,
+ errorMsg: t('invalid_url'),
+ });
+ return;
+ }
+ onConfirm({
+ title: title.value,
+ url: url.value,
+ });
+ setShowState(false);
+
+ setTitle({
+ value: '',
+ isInvalid: false,
+ errorMsg: '',
+ });
+
+ setUrl({
+ value: '',
+ isInvalid: false,
+ errorMsg: '',
+ });
+ };
+ return (
+ <Modal show={show} onHide={handleHide}>
+ <Modal.Header closeButton>
+ <Modal.Title>{t('header')}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form>
+ <Form.Group className="mb-3" controlId="editor.plugin.embed.title">
+ <Form.Label>{t('title')}</Form.Label>
+ <Form.Control
+ type="text"
+ value={title.value}
+ isInvalid={title.isInvalid}
+ onChange={handleChangeTitle}
+ />
+ <Form.Control.Feedback type="invalid">
+ {title.errorMsg}
+ </Form.Control.Feedback>
+ </Form.Group>
+ <Form.Group className="mb-3" controlId="editor.plugin.embed.url">
+ <Form.Label>{t('url')}</Form.Label>
+ <Form.Control
+ type="url"
+ value={url.value}
+ isInvalid={url.isInvalid}
+ onChange={handleChangeUrl}
+ />
+ <Form.Control.Feedback type="invalid">
+ {url.errorMsg}
+ </Form.Control.Feedback>
+ </Form.Group>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button variant="link" onClick={handleHide}>
+ {t('cancel')}
+ </Button>
+ <Button variant="primary" onClick={handleSubmit}>
+ {t('add')}
+ </Button>
+ </Modal.Footer>
+ </Modal>
+ );
+};
+
+export default EmbedModal;
diff --git a/embed-basic/package.json b/embed-basic/package.json
new file mode 100644
index 0000000..b46ca78
--- /dev/null
+++ b/embed-basic/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "embed-basic",
+ "version": "0.0.1",
+ "description": "",
+ "main": "index.ts",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Answer.dev",
+ "peerDependencies": {
+ "react": ">=16.14.0",
+ "react-bootstrap":">2.0.0",
+ "react-dom": ">=16.14.0",
+ "react-i18next":"^11.18.3"
+ }
+}
diff --git a/embed-basic/pnpm-lock.yaml b/embed-basic/pnpm-lock.yaml
new file mode 100644
index 0000000..321e769
--- /dev/null
+++ b/embed-basic/pnpm-lock.yaml
@@ -0,0 +1,315 @@
+lockfileVersion: '6.0'
+
+dependencies:
+ react:
+ specifier: '>=16.14.0'
+ version: 16.14.0
+ react-bootstrap:
+ specifier: '>2.0.0'
+ version: 2.0.1(react-dom@16.14.0)(react@16.14.0)
+ react-dom:
+ specifier: '>=16.14.0'
+ version: 16.14.0(react@16.14.0)
+ react-i18next:
+ specifier: ^11.18.3
+ version: 11.18.3(i18next@23.11.5)(react-dom@16.14.0)(react@16.14.0)
+
+packages:
+
+ /@babel/runtime@7.24.6:
+ resolution: {integrity: sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ regenerator-runtime: 0.14.1
+ dev: false
+
+ /@popperjs/core@2.11.8:
+ resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
+ dev: false
+
+ /@react-aria/ssr@3.9.4(react@16.14.0):
+ resolution: {integrity: sha512-4jmAigVq409qcJvQyuorsmBR4+9r3+JEC60wC+Y0MZV0HCtTmm8D9guYXlJMdx0SSkgj0hHAyFm/HvPNFofCoQ==}
+ engines: {node: '>= 12'}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@swc/helpers': 0.5.11
+ react: 16.14.0
+ dev: false
+
+ /@restart/context@2.1.4(react@16.14.0):
+ resolution: {integrity: sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==}
+ peerDependencies:
+ react: '>=16.3.2'
+ dependencies:
+ react: 16.14.0
+ dev: false
+
+ /@restart/hooks@0.3.27(react@16.14.0):
+ resolution: {integrity: sha512-s984xV/EapUIfkjlf8wz9weP2O9TNKR96C68FfMEy2bE69+H4cNv3RD4Mf97lW7Htt7PjZrYTjSC8f3SB9VCXw==}
+ peerDependencies:
+ react: '>=16.8.0'
+ dependencies:
+ dequal: 2.0.3
+ react: 16.14.0
+ dev: false
+
+ /@restart/hooks@0.4.16(react@16.14.0):
+ resolution: {integrity: sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==}
+ peerDependencies:
+ react: '>=16.8.0'
+ dependencies:
+ dequal: 2.0.3
+ react: 16.14.0
+ dev: false
+
+ /@restart/ui@0.2.6(react-dom@16.14.0)(react@16.14.0):
+ resolution: {integrity: sha512-lcutEWPvsxz0uEyRxuysCbHBfXDFnMKNMNTsnuPmLFjZXgW9fVmhksS6rpFklXHMwxOM9g6hRTBq0gS3QRKgzQ==}
+ peerDependencies:
+ react: '>=16.14.0'
+ react-dom: '>=16.14.0'
+ dependencies:
+ '@babel/runtime': 7.24.6
+ '@popperjs/core': 2.11.8
+ '@react-aria/ssr': 3.9.4(react@16.14.0)
+ '@restart/hooks': 0.4.16(react@16.14.0)
+ '@types/warning': 3.0.3
+ dequal: 2.0.3
+ dom-helpers: 5.2.1
+ prop-types: 15.8.1
+ react: 16.14.0
+ react-dom: 16.14.0(react@16.14.0)
+ uncontrollable: 7.2.1(react@16.14.0)
+ warning: 4.0.3
+ dev: false
+
+ /@swc/helpers@0.5.11:
+ resolution: {integrity: sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==}
+ dependencies:
+ tslib: 2.6.2
+ dev: false
+
+ /@types/invariant@2.2.37:
+ resolution: {integrity: sha512-IwpIMieE55oGWiXkQPSBY1nw1nFs6bsKXTFskNY8sdS17K24vyEBRQZEwlRS7ZmXCWnJcQtbxWzly+cODWGs2A==}
+ dev: false
+
+ /@types/prop-types@15.7.12:
+ resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
+ dev: false
+
+ /@types/react-transition-group@4.4.10:
+ resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==}
+ dependencies:
+ '@types/react': 18.3.3
+ dev: false
+
+ /@types/react@18.3.3:
+ resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==}
+ dependencies:
+ '@types/prop-types': 15.7.12
+ csstype: 3.1.3
+ dev: false
+
+ /@types/warning@3.0.3:
+ resolution: {integrity: sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==}
+ dev: false
+
+ /classnames@2.5.1:
+ resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
+ dev: false
+
+ /csstype@3.1.3:
+ resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+ dev: false
+
+ /dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+ dev: false
+
+ /dom-helpers@5.2.1:
+ resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
+ dependencies:
+ '@babel/runtime': 7.24.6
+ csstype: 3.1.3
+ dev: false
+
+ /html-parse-stringify@3.0.1:
+ resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
+ dependencies:
+ void-elements: 3.1.0
+ dev: false
+
+ /i18next@23.11.5:
+ resolution: {integrity: sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==}
+ dependencies:
+ '@babel/runtime': 7.24.6
+ dev: false
+
+ /invariant@2.2.4:
+ resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
+ dependencies:
+ loose-envify: 1.4.0
+ dev: false
+
+ /js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+ dev: false
+
+ /loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+ dependencies:
+ js-tokens: 4.0.0
+ dev: false
+
+ /object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /prop-types-extra@1.1.1(react@16.14.0):
+ resolution: {integrity: sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==}
+ peerDependencies:
+ react: '>=0.14.0'
+ dependencies:
+ react: 16.14.0
+ react-is: 16.13.1
+ warning: 4.0.3
+ dev: false
+
+ /prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+ dev: false
+
+ /react-bootstrap@2.0.1(react-dom@16.14.0)(react@16.14.0):
+ resolution: {integrity: sha512-vx4fi3mHWWIzaAOJEzFfmTn/GHtirpASRk0C1PDBW5OZAt/VxLgUt+bvrSSh83eRmInq3wy8mERJMwuyI61KPA==}
+ peerDependencies:
+ react: '>=16.14.0'
+ react-dom: '>=16.14.0'
+ dependencies:
+ '@babel/runtime': 7.24.6
+ '@restart/context': 2.1.4(react@16.14.0)
+ '@restart/hooks': 0.3.27(react@16.14.0)
+ '@restart/ui': 0.2.6(react-dom@16.14.0)(react@16.14.0)
+ '@types/invariant': 2.2.37
+ '@types/prop-types': 15.7.12
+ '@types/react': 18.3.3
+ '@types/react-transition-group': 4.4.10
+ '@types/warning': 3.0.3
+ classnames: 2.5.1
+ dom-helpers: 5.2.1
+ invariant: 2.2.4
+ prop-types: 15.8.1
+ prop-types-extra: 1.1.1(react@16.14.0)
+ react: 16.14.0
+ react-dom: 16.14.0(react@16.14.0)
+ react-transition-group: 4.4.5(react-dom@16.14.0)(react@16.14.0)
+ uncontrollable: 7.2.1(react@16.14.0)
+ warning: 4.0.3
+ dev: false
+
+ /react-dom@16.14.0(react@16.14.0):
+ resolution: {integrity: sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==}
+ peerDependencies:
+ react: ^16.14.0
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ prop-types: 15.8.1
+ react: 16.14.0
+ scheduler: 0.19.1
+ dev: false
+
+ /react-i18next@11.18.3(i18next@23.11.5)(react-dom@16.14.0)(react@16.14.0):
+ resolution: {integrity: sha512-EttTX31HbqzZymUM3SIrMPuvamfSXFZVsDHm/ZAqoDfTLjhzlwyxqfbDNxcKNAGOi2mjZaXfR7hSNMlvLNpB/g==}
+ peerDependencies:
+ i18next: '>= 19.0.0'
+ react: '>= 16.8.0'
+ react-dom: '*'
+ react-native: '*'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+ react-native:
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.6
+ html-parse-stringify: 3.0.1
+ i18next: 23.11.5
+ react: 16.14.0
+ react-dom: 16.14.0(react@16.14.0)
+ dev: false
+
+ /react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+ dev: false
+
+ /react-lifecycles-compat@3.0.4:
+ resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
+ dev: false
+
+ /react-transition-group@4.4.5(react-dom@16.14.0)(react@16.14.0):
+ resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
+ peerDependencies:
+ react: '>=16.6.0'
+ react-dom: '>=16.6.0'
+ dependencies:
+ '@babel/runtime': 7.24.6
+ dom-helpers: 5.2.1
+ loose-envify: 1.4.0
+ prop-types: 15.8.1
+ react: 16.14.0
+ react-dom: 16.14.0(react@16.14.0)
+ dev: false
+
+ /react@16.14.0:
+ resolution: {integrity: sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ prop-types: 15.8.1
+ dev: false
+
+ /regenerator-runtime@0.14.1:
+ resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
+ dev: false
+
+ /scheduler@0.19.1:
+ resolution: {integrity: sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==}
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ dev: false
+
+ /tslib@2.6.2:
+ resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
+ dev: false
+
+ /uncontrollable@7.2.1(react@16.14.0):
+ resolution: {integrity: sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==}
+ peerDependencies:
+ react: '>=15.0.0'
+ dependencies:
+ '@babel/runtime': 7.24.6
+ '@types/react': 18.3.3
+ invariant: 2.2.4
+ react: 16.14.0
+ react-lifecycles-compat: 3.0.4
+ dev: false
+
+ /void-elements@3.1.0:
+ resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /warning@4.0.3:
+ resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==}
+ dependencies:
+ loose-envify: 1.4.0
+ dev: false
diff --git a/embed-basic/tsconfig.json b/embed-basic/tsconfig.json
new file mode 100644
index 0000000..f6c88fb
--- /dev/null
+++ b/embed-basic/tsconfig.json
@@ -0,0 +1,31 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": [
+ "ES2020",
+ "DOM",
+ "DOM.Iterable"
+ ],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noImplicitAny": false,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ },
+ "references": [
+ {
+ "path": "./tsconfig.node.json"
+ }
+ ]
+}
diff --git a/embed-basic/tsconfig.node.json b/embed-basic/tsconfig.node.json
new file mode 100644
index 0000000..42872c5
--- /dev/null
+++ b/embed-basic/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}