fix: Enable embeds within HTML and PDF exports (#6290)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import find from "lodash/find";
|
||||
import * as React from "react";
|
||||
import embeds, { EmbedDescriptor } from "@shared/editor/embeds";
|
||||
import embeds from "@shared/editor/embeds";
|
||||
import { IntegrationType } from "@shared/types";
|
||||
import Integration from "~/models/Integration";
|
||||
import Logger from "~/utils/Logger";
|
||||
@@ -9,8 +9,7 @@ import useStores from "./useStores";
|
||||
/**
|
||||
* Hook to get all embed configuration for the current team
|
||||
*
|
||||
* @param loadIfMissing Should we load integration settings if they are not
|
||||
* locally available
|
||||
* @param loadIfMissing Should we load integration settings if they are not locally available
|
||||
* @returns A list of embed descriptors
|
||||
*/
|
||||
export default function useEmbeds(loadIfMissing = false) {
|
||||
@@ -36,14 +35,18 @@ export default function useEmbeds(loadIfMissing = false) {
|
||||
return React.useMemo(
|
||||
() =>
|
||||
embeds.map((e) => {
|
||||
const em: Integration<IntegrationType.Embed> | undefined = find(
|
||||
integrations.orderedData,
|
||||
(i) => i.service === e.component.name.toLowerCase()
|
||||
);
|
||||
return new EmbedDescriptor({
|
||||
...e,
|
||||
settings: em?.settings,
|
||||
});
|
||||
// Find any integrations that match this embed and inject the settings
|
||||
const integration: Integration<IntegrationType.Embed> | undefined =
|
||||
find(
|
||||
integrations.orderedData,
|
||||
(integration) => integration.service === e.name
|
||||
);
|
||||
|
||||
if (integration?.settings) {
|
||||
e.settings = integration.settings;
|
||||
}
|
||||
|
||||
return e;
|
||||
}),
|
||||
[integrations.orderedData]
|
||||
);
|
||||
|
||||
@@ -4,10 +4,12 @@ import { DefaultTheme, ThemeProps } from "styled-components";
|
||||
import { EmbedProps as Props } from "../embeds";
|
||||
import Widget from "./Widget";
|
||||
|
||||
export default function DisabledEmbed(props: Props & ThemeProps<DefaultTheme>) {
|
||||
export default function DisabledEmbed(
|
||||
props: Omit<Props, "matches"> & ThemeProps<DefaultTheme>
|
||||
) {
|
||||
return (
|
||||
<Widget
|
||||
title={props.embed.title}
|
||||
title={props.embed.name}
|
||||
href={props.attrs.href}
|
||||
icon={props.embed.icon}
|
||||
context={props.attrs.href.replace(/^https?:\/\//, "")}
|
||||
|
||||
73
shared/editor/components/Embed.tsx
Normal file
73
shared/editor/components/Embed.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import * as React from "react";
|
||||
import { EmbedDescriptor } from "../embeds";
|
||||
import { getMatchingEmbed } from "../lib/embeds";
|
||||
import { ComponentProps } from "../types";
|
||||
import DisabledEmbed from "./DisabledEmbed";
|
||||
import Frame from "./Frame";
|
||||
|
||||
const EmbedComponent = ({
|
||||
isEditable,
|
||||
isSelected,
|
||||
theme,
|
||||
node,
|
||||
embeds,
|
||||
embedsDisabled,
|
||||
}: ComponentProps & {
|
||||
embeds: EmbedDescriptor[];
|
||||
embedsDisabled?: boolean;
|
||||
}) => {
|
||||
const cache = React.useMemo(
|
||||
() => getMatchingEmbed(embeds, node.attrs.href),
|
||||
[embeds, node.attrs.href]
|
||||
);
|
||||
|
||||
if (!cache) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { embed, matches } = cache;
|
||||
|
||||
if (embedsDisabled) {
|
||||
return (
|
||||
<DisabledEmbed
|
||||
attrs={node.attrs.href}
|
||||
embed={embed}
|
||||
isEditable={isEditable}
|
||||
isSelected={isSelected}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (embed.transformMatch) {
|
||||
const src = embed.transformMatch(matches);
|
||||
return (
|
||||
<Frame
|
||||
src={src}
|
||||
isSelected={isSelected}
|
||||
canonicalUrl={node.attrs.href}
|
||||
title={embed.title}
|
||||
referrerPolicy="origin"
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if ("component" in embed) {
|
||||
return (
|
||||
// @ts-expect-error Component type
|
||||
<embed.component
|
||||
attrs={node.attrs}
|
||||
matches={matches}
|
||||
isEditable={isEditable}
|
||||
isSelected={isSelected}
|
||||
embed={embed}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default EmbedComponent;
|
||||
@@ -403,6 +403,13 @@ li {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
iframe.embed {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
border: 1px solid ${props.theme.embedBorder};
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.image,
|
||||
.video {
|
||||
line-height: 0;
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import Abstract from "./Abstract";
|
||||
|
||||
describe("Abstract", () => {
|
||||
const match = Abstract.ENABLED[0];
|
||||
const match2 = Abstract.ENABLED[1];
|
||||
|
||||
test("to be enabled on share subdomain link", () => {
|
||||
expect(
|
||||
"https://share.goabstract.com/aaec8bba-f473-4f64-96e7-bff41c70ff8a".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://share.abstract.com/aaec8bba-f473-4f64-96e7-bff41c70ff8a".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect(
|
||||
"https://app.goabstract.com/share/aaec8bba-f473-4f64-96e7-bff41c70ff8a".match(
|
||||
match2
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://app.abstract.com/share/aaec8bba-f473-4f64-96e7-bff41c70ff8a".match(
|
||||
match2
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on embed link", () => {
|
||||
expect(
|
||||
"https://app.goabstract.com/embed/aaec8bba-f473-4f64-96e7-bff41c70ff8a".match(
|
||||
match2
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://app.abstract.com/embed/aaec8bba-f473-4f64-96e7-bff41c70ff8a".match(
|
||||
match2
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://sharedgoabstract.com/f473".match(match)).toBe(null);
|
||||
expect("https://share.goabstractacom/f473".match(match)).toBe(null);
|
||||
expect("https://app1goabstract.com/share/f473".match(match2)).toBe(null);
|
||||
expect("https://app.goabstractacom/share/f473".match(match2)).toBe(null);
|
||||
expect("https://abstract.com".match(match)).toBe(null);
|
||||
expect("https://goabstract.com".match(match)).toBe(null);
|
||||
expect("https://app.goabstract.com".match(match)).toBe(null);
|
||||
expect("https://abstract.com/features".match(match)).toBe(null);
|
||||
expect("https://app.abstract.com/home".match(match)).toBe(null);
|
||||
expect("https://abstract.com/pricing".match(match)).toBe(null);
|
||||
expect("https://goabstract.com/pricing".match(match)).toBe(null);
|
||||
expect("https://www.goabstract.com/pricing".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Abstract(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
const shareId = matches[1];
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`https://app.goabstract.com/embed/${shareId}`}
|
||||
title={`Abstract (${shareId})`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Abstract.ENABLED = [
|
||||
new RegExp("^https?://share\\.(?:go)?abstract\\.com/(.*)$"),
|
||||
new RegExp("^https?://app\\.(?:go)?abstract\\.com/(?:share|embed)/(.*)$"),
|
||||
];
|
||||
|
||||
export default Abstract;
|
||||
@@ -1,29 +0,0 @@
|
||||
import Airtable from "./Airtable";
|
||||
|
||||
describe("Airtable", () => {
|
||||
const match = Airtable.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect("https://airtable.com/shrEoQs3erLnppMie".match(match)).toBeTruthy();
|
||||
expect(
|
||||
"https://airtable.com/shrEoQs3erLnppMie/tbl232i54yebXpr1J".match(match)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://airtable.com/app0lrLlMprqMbz11/shrEoQs3erLnppMie/tbl232i54yebXpr1J".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on embed link", () => {
|
||||
expect(
|
||||
"https://airtable.com/embed/shrEoQs3erLnppMie".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://airtable.com".match(match)).toBe(null);
|
||||
expect("https://airtable.com/features".match(match)).toBe(null);
|
||||
expect("https://airtable.com/pricing".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
const URL_REGEX = new RegExp(
|
||||
"^https://airtable.com/(?:embed/)?(app.*/)?(shr.*)$"
|
||||
);
|
||||
|
||||
function Airtable(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
const appId = matches[1];
|
||||
const shareId = matches[2];
|
||||
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`https://airtable.com/embed/${appId ?? ""}${shareId}`}
|
||||
title={`Airtable (${shareId})`}
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Airtable.ENABLED = [URL_REGEX];
|
||||
|
||||
export default Airtable;
|
||||
@@ -1,8 +0,0 @@
|
||||
import Berrycast from "./Berrycast";
|
||||
|
||||
describe("Berrycast", () => {
|
||||
Berrycast.ENABLED[0];
|
||||
|
||||
// TODO
|
||||
test.todo("");
|
||||
});
|
||||
@@ -3,9 +3,7 @@ import Frame from "../components/Frame";
|
||||
import useComponentSize from "../components/hooks/useComponentSize";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
const URL_REGEX = /^https:\/\/(www\.)?berrycast.com\/conversations\/(.*)$/;
|
||||
|
||||
export default function Berrycast(props: Props) {
|
||||
export default function Berrycast({ matches, ...props }: Props) {
|
||||
const normalizedUrl = props.attrs.href.replace(/\/$/, "");
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
const { width } = useComponentSize(ref.current);
|
||||
@@ -23,5 +21,3 @@ export default function Berrycast(props: Props) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Berrycast.ENABLED = [URL_REGEX];
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import Bilibili from "./Bilibili";
|
||||
|
||||
describe("Bilibili", () => {
|
||||
const match = Bilibili.ENABLED[0];
|
||||
|
||||
test("to be enabled on video link", () => {
|
||||
expect(
|
||||
"https://www.bilibili.com/video/BV1CV411s7jd?spm_id_from=333.999.0.0".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://youtu.be".match(match)).toBe(null);
|
||||
expect("https://bilibili.com".match(match)).toBe(null);
|
||||
expect("https://www.bilibili.com".match(match)).toBe(null);
|
||||
expect("https://www.bilibili.com/logout".match(match)).toBe(null);
|
||||
expect("https://www.bilibili.com/feed/subscriptions".match(match)).toBe(
|
||||
null
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
const URL_REGEX =
|
||||
/(?:https?:\/\/)?(www\.bilibili\.com)\/video\/([\w\d]+)?(\?\S+)?/i;
|
||||
|
||||
export default function Bilibili(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
const videoId = matches[2];
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`https://player.bilibili.com/player.html?bvid=${videoId}&page=1&high_quality=1`}
|
||||
title={`Bilibili Embed (${videoId})`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Bilibili.ENABLED = [URL_REGEX];
|
||||
@@ -1,22 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Canva(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
const embedId = matches[1];
|
||||
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`https://www.canva.com/design/${embedId}/view?embed`}
|
||||
title="Canva"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Canva.ENABLED = [
|
||||
/^https:\/\/(?:www\.)?canva\.com\/design\/([a-zA-Z0-9]*)\/(.*)$/,
|
||||
];
|
||||
|
||||
export default Canva;
|
||||
@@ -1,22 +0,0 @@
|
||||
import Cawemo from "./Cawemo";
|
||||
|
||||
describe("Cawemo", () => {
|
||||
const match = Cawemo.ENABLED[0];
|
||||
|
||||
test("to be enabled on embed link", () => {
|
||||
expect(
|
||||
"https://cawemo.com/embed/a82e9f22-e283-4253-8d11".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect(
|
||||
"https://cawemo.com/embed/a82e9f22-e283-4253-8d11".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://cawemo.com/".match(match)).toBe(null);
|
||||
expect("https://cawemo.com/diagrams".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
const URL_REGEX = new RegExp("^https?://cawemo.com/(?:share|embed)/(.*)$");
|
||||
|
||||
export default function Cawemo(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
const shareId = matches[1];
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`https://cawemo.com/embed/${shareId}`}
|
||||
title={"Cawemo Embed"}
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Cawemo.ENABLED = [URL_REGEX];
|
||||
@@ -1,19 +0,0 @@
|
||||
import ClickUp from "./ClickUp";
|
||||
|
||||
describe("ClickUp", () => {
|
||||
const match = ClickUp.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect(
|
||||
"https://share.clickup.com/b/h/6-9310960-2/c9d837d74182317".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://share.clickup.com".match(match)).toBe(null);
|
||||
expect("https://sharedclickup.com/a/b/c/d".match(match)).toBe(null);
|
||||
expect("https://share.clickupdcom/a/b/c/d".match(match)).toBe(null);
|
||||
expect("https://clickup.com/".match(match)).toBe(null);
|
||||
expect("https://clickup.com/features".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
export default function ClickUp(props: Props) {
|
||||
return <Frame {...props} src={props.attrs.href} title="ClickUp Embed" />;
|
||||
}
|
||||
|
||||
ClickUp.ENABLED = [
|
||||
new RegExp("^https?://share\\.clickup\\.com/[a-z]/[a-z]/(.*)/(.*)$"),
|
||||
new RegExp("^https?://sharing\\.clickup\\.com/[0-9]+/[a-z]/[a-z]/(.*)/(.*)$"),
|
||||
];
|
||||
@@ -1,22 +0,0 @@
|
||||
import Codepen from "./Codepen";
|
||||
|
||||
describe("Codepen", () => {
|
||||
const match = Codepen.ENABLED[0];
|
||||
|
||||
test("to be enabled on pen link", () => {
|
||||
expect(
|
||||
"https://codepen.io/chriscoyier/pen/gfdDu".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on embed link", () => {
|
||||
expect(
|
||||
"https://codepen.io/chriscoyier/embed/gfdDu".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://codepen.io".match(match)).toBe(null);
|
||||
expect("https://codepen.io/chriscoyier".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
const URL_REGEX = new RegExp("^https://codepen.io/(.*?)/(pen|embed)/(.*)$");
|
||||
|
||||
export default function Codepen(props: Props) {
|
||||
const normalizedUrl = props.attrs.href.replace(/\/pen\//, "/embed/");
|
||||
return <Frame {...props} src={normalizedUrl} title="Codepen Embed" />;
|
||||
}
|
||||
|
||||
Codepen.ENABLED = [URL_REGEX];
|
||||
@@ -1,8 +0,0 @@
|
||||
import DBDiagram from "./DBDiagram";
|
||||
|
||||
describe("DBDiagram", () => {
|
||||
DBDiagram.ENABLED[0];
|
||||
|
||||
// TODO
|
||||
test.todo("");
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
export default function DBDiagram(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
const shareId = matches[2];
|
||||
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`https://dbdiagram.io/embed/${shareId}`}
|
||||
title={`DBDiagram (${shareId})`}
|
||||
width="100%"
|
||||
height="400px"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
DBDiagram.ENABLED = [new RegExp("^https://dbdiagram.io/(embed|d)/(\\w+)$")];
|
||||
@@ -1,10 +0,0 @@
|
||||
import Descript from "./Descript";
|
||||
|
||||
describe("Descript", () => {
|
||||
const match = Descript.ENABLED[0];
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://shareddescript.com/view/c9d8".match(match)).toBe(null);
|
||||
expect("https://share.descriptdcom/view/c9d8".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
export default function Descript(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
const shareId = matches[1];
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`https://share.descript.com/embed/${shareId}`}
|
||||
title={`Descript (${shareId})`}
|
||||
width="400px"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Descript.ENABLED = [
|
||||
new RegExp("^https?://share\\.descript\\.com/view/(\\w+)$"),
|
||||
];
|
||||
@@ -1,25 +0,0 @@
|
||||
import Diagrams from "./Diagrams";
|
||||
|
||||
describe("Diagrams", () => {
|
||||
const match = Diagrams.ENABLED[0];
|
||||
|
||||
test("to be enabled on viewer link", () => {
|
||||
expect(
|
||||
"https://viewer.diagrams.net/?target=blank&nav=1#ABCDefgh_A12345-6789".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled on the proxy path", () => {
|
||||
expect("https://app.diagrams.net/proxy?url=malicious".match(match)).toBe(
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://app.diagrams.net/#ABCDefgh_A12345-6789".match(match)).toBe(
|
||||
null
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -3,9 +3,9 @@ import Frame from "../components/Frame";
|
||||
import Image from "../components/Img";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Diagrams(props: Props) {
|
||||
function Diagrams({ matches, ...props }: Props) {
|
||||
const { embed } = props;
|
||||
const embedUrl = props.attrs.matches[0];
|
||||
const embedUrl = matches[0];
|
||||
const params = new URL(embedUrl).searchParams;
|
||||
const titlePrefix = embed.settings?.url ? "Draw.io" : "Diagrams.net";
|
||||
const title = params.get("title")
|
||||
@@ -31,10 +31,4 @@ function Diagrams(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
Diagrams.ENABLED = [
|
||||
/^https:\/\/viewer\.diagrams\.net\/(?!proxy).*(title=\\w+)?/,
|
||||
];
|
||||
|
||||
Diagrams.URL_PATH_REGEX = /\/(?!proxy).*(title=\\w+)?/;
|
||||
|
||||
export default Diagrams;
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import Figma from "./Figma";
|
||||
|
||||
describe("Figma", () => {
|
||||
const match = Figma.ENABLED[0];
|
||||
|
||||
test("to be enabled on file link", () => {
|
||||
expect(
|
||||
"https://www.figma.com/file/LKQ4FJ4bTnCSjedbRpk931".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on prototype link", () => {
|
||||
expect(
|
||||
"https://www.figma.com/proto/LKQ4FJ4bTnCSjedbRpk931".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://www.figma.com".match(match)).toBe(null);
|
||||
expect("https://www.figma.com/features".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://wwww.figmaacom/file/LKQ4FJ4bTnCSjedbRpk931".match(match)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://wwwwfigma.com/file/LKQ4FJ4bTnCSjedbRpk931".match(match)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Figma(props: Props) {
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`https://www.figma.com/embed?embed_host=outline&url=${props.attrs.href}`}
|
||||
title="Figma Embed"
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Figma.ENABLED = [
|
||||
new RegExp(
|
||||
"^https://([w.-]+\\.)?figma\\.com/(file|proto)/([0-9a-zA-Z]{22,128})(?:/.*)?$"
|
||||
),
|
||||
];
|
||||
|
||||
export default Figma;
|
||||
@@ -1,13 +0,0 @@
|
||||
import Framer from "./Framer";
|
||||
|
||||
describe("Framer", () => {
|
||||
const match = Framer.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect("https://framer.cloud/PVwJO".match(match)).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled on root", () => {
|
||||
expect("https://framer.cloud".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Framer(props: Props) {
|
||||
return (
|
||||
<Frame {...props} src={props.attrs.href} title="Framer Embed" border />
|
||||
);
|
||||
}
|
||||
|
||||
Framer.ENABLED = [new RegExp("^https://framer.cloud/(.*)$")];
|
||||
|
||||
export default Framer;
|
||||
@@ -1,37 +0,0 @@
|
||||
import Gist from "./Gist";
|
||||
|
||||
describe("Gist", () => {
|
||||
const match = Gist.ENABLED[0];
|
||||
|
||||
test("to be enabled on gist link", () => {
|
||||
expect(
|
||||
"https://gist.github.com/wmertens/0b4fd66ca7055fd290ecc4b9d95271a9".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://gist.github.com/n3n/eb51ada6308b539d388c8ff97711adfa".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://gist.github.com/ShubhanjanMedhi-dev/900c9c14093611898a4a085938bb90d9".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect(
|
||||
"https://gistigithub.com/n3n/eb51ada6308b539d388c8ff97711adfa".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://gist.githubbcom/n3n/eb51ada6308b539d388c8ff97711adfa".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
expect("https://gist.github.com/tommoor".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -29,10 +29,4 @@ function Gist(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
Gist.ENABLED = [
|
||||
new RegExp(
|
||||
"^https://gist\\.github\\.com/([a-zA-Z\\d](?:[a-zA-Z\\d]|-(?=[a-zA-Z\\d])){0,38})/(.*)$"
|
||||
),
|
||||
];
|
||||
|
||||
export default Gist;
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import GitLabSnippet from "./GitLabSnippet";
|
||||
|
||||
describe("GitLabSnippet", () => {
|
||||
const match = GitLabSnippet.ENABLED[0];
|
||||
|
||||
test("to be enabled on snippet link", () => {
|
||||
expect("https://gitlab.com/-/snippets/1234".match(match)).toBeTruthy();
|
||||
expect(
|
||||
"https://gitlab.com/gitlab-org/gitlab/-/snippets/2256824".match(match)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://gitlab.com/group/project/sub-project/sub-sub-project/test/-/snippets/9876".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://gitlab.com".match(match)).toBe(null);
|
||||
expect("https://gitlab.com/gitlab-org".match(match)).toBe(null);
|
||||
expect("https://gitlab.com/gitlab-org/gitlab".match(match)).toBe(null);
|
||||
expect("https://gitlab.com/gitlab-org/gitlab/-/issues".match(match)).toBe(
|
||||
null
|
||||
);
|
||||
expect(
|
||||
"https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137948".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -2,8 +2,6 @@ import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
const GITLAB_NAMESPACE_REGEX = "(([a-zA-Z\\d-]+)/)";
|
||||
|
||||
const Iframe = styled.iframe`
|
||||
margin-top: 8px;
|
||||
`;
|
||||
@@ -29,10 +27,4 @@ function GitLabSnippet(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
GitLabSnippet.ENABLED = [
|
||||
new RegExp(
|
||||
`^https://gitlab\\.com/${GITLAB_NAMESPACE_REGEX}*-/snippets/\\d+$`
|
||||
),
|
||||
];
|
||||
|
||||
export default GitLabSnippet;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import Gliffy from "./Gliffy";
|
||||
|
||||
describe("Gliffy", () => {
|
||||
const match = Gliffy.ENABLED[0];
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://gotgliffy.com/go/share/c9d837d74182317".match(match)).toBe(
|
||||
null
|
||||
);
|
||||
expect("https://go.gliffyycom/go/share/c9d837d74182317".match(match)).toBe(
|
||||
null
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Gliffy(props: Props) {
|
||||
return (
|
||||
<Frame {...props} src={props.attrs.href} title="Gliffy Embed" border />
|
||||
);
|
||||
}
|
||||
|
||||
Gliffy.ENABLED = [new RegExp("https?://go\\.gliffy\\.com/go/share/(.*)$")];
|
||||
|
||||
export default Gliffy;
|
||||
@@ -1,29 +0,0 @@
|
||||
import GoogleCalendar from "./GoogleCalendar";
|
||||
|
||||
describe("GoogleCalendar", () => {
|
||||
const match = GoogleCalendar.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect(
|
||||
"https://calendar.google.com/calendar/embed?src=tom%40outline.com&ctz=America%2FSao_Paulo".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://calendar.google.com/calendar".match(match)).toBe(null);
|
||||
expect("https://calendar.google.com".match(match)).toBe(null);
|
||||
expect("https://www.google.com".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://calendarrgoogle.com/calendar/embed?src=tom%40outline.com&ctz=America%2FSao_Paulo".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://calendar.googleecom/calendar/embed?src=tom%40outline.com&ctz=America%2FSao_Paulo".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function GoogleCalendar(props: Props) {
|
||||
return (
|
||||
<Frame {...props} src={props.attrs.href} title="Google Calendar" border />
|
||||
);
|
||||
}
|
||||
|
||||
GoogleCalendar.ENABLED = [
|
||||
new RegExp("^https?://calendar\\.google\\.com/calendar/embed\\?src=(.*)$"),
|
||||
];
|
||||
|
||||
export default GoogleCalendar;
|
||||
@@ -1,44 +0,0 @@
|
||||
import GoogleDocs from "./GoogleDocs";
|
||||
|
||||
describe("GoogleDocs", () => {
|
||||
const match = GoogleDocs.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect(
|
||||
"https://docs.google.com/document/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigRIt2cj_Pd-kgtaNQY6H0Jzn0_CVGbxC1GcK5IoNzU615lzguexFwxasAW/pubhtml".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://docs.google.com/document/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigRIt2cj_Pd-kgtaNQY6H0Jzn0_CVGbxC1GcK5IoNzU615lzguexFwxasAW/pub".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://docs.google.com/document/d/1SsDfWzFFTjZM2LanvpyUzjKhqVQpwpTMeiPeYxhVqOg/edit".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://docs.google.com/document/d/1SsDfWzFFTjZM2LanvpyUzjKhqVQpwpTMeiPeYxhVqOg/preview".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://docs.google.com/document".match(match)).toBe(null);
|
||||
expect("https://docs.google.com".match(match)).toBe(null);
|
||||
expect("https://www.google.com".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://docssgoogle.com/document/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigRIt2cj_Pd-kgtaNQY6H0Jzn0_CVGbxC1GcK5IoNzU615lzguexFwxasAW/pubhtml".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://docs.googleecom/document/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigRIt2cj_Pd-kgtaNQY6H0Jzn0_CVGbxC1GcK5IoNzU615lzguexFwxasAW/pubhtml".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import Image from "../components/Img";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function GoogleDocs(props: Props) {
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={props.attrs.href.replace("/edit", "/preview")}
|
||||
icon={
|
||||
<Image
|
||||
src="/images/google-docs.png"
|
||||
alt="Google Docs Icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
}
|
||||
canonicalUrl={props.attrs.href}
|
||||
title="Google Docs"
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
GoogleDocs.ENABLED = [
|
||||
new RegExp("^https?://docs\\.google\\.com/document/(.*)$"),
|
||||
];
|
||||
|
||||
export default GoogleDocs;
|
||||
@@ -1,39 +0,0 @@
|
||||
import GoogleDrawings from "./GoogleDrawings";
|
||||
|
||||
describe("GoogleDrawings", () => {
|
||||
const match = GoogleDrawings.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect(
|
||||
"https://docs.google.com/drawings/d/1zELtJ4HSCnjGCGSoCgqGe3F8p6o7R8Vjk8MDR6dKf-U/edit".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://docs.google.com/drawings/d/1zELtJ4HSCnjGCGSoCgqGe3F8p6o7R8Vjk8MDR6dKf-U/edit?usp=sharing".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect(
|
||||
"https://docs.google.com/drawings/d/e/2PACF-1vRtzIzEWN6svSrIYZq-kq2XZEN6WaOFXHbPKRLXNOFRlxLIdJg0Vo6RfretGqs9SzD-fUazLeS594Kw/pub?w=960&h=720".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
expect("https://docs.google.com/drawings".match(match)).toBe(null);
|
||||
expect("https://docs.google.com".match(match)).toBe(null);
|
||||
expect("https://www.google.com".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://docssgoogle.com/drawings/d/1zDMtJ4HSCnjGCGSoCgqGe3F8p6o7R8Vjk8MDR6dKf-U/edit".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://docs.googleecom/drawings/d/1zDMtJ4HSCnjGCGSoCgqGe3F8p6o7R8Vjk8MDR6dKf-U/edit".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import Image from "../components/Img";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function GoogleDrawings(props: Props) {
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={props.attrs.href.replace("/edit", "/preview")}
|
||||
icon={
|
||||
<Image
|
||||
src="/images/google-drawings.png"
|
||||
alt="Google Drawings"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
}
|
||||
canonicalUrl={props.attrs.href.replace("/preview", "/edit")}
|
||||
title="Google Drawings"
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
GoogleDrawings.ENABLED = [
|
||||
new RegExp(
|
||||
"^https://docs\\.google\\.com/drawings/d/(.*)/(edit|preview)(.*)$"
|
||||
),
|
||||
];
|
||||
|
||||
export default GoogleDrawings;
|
||||
@@ -1,39 +0,0 @@
|
||||
import GoogleDrive from "./GoogleDrive";
|
||||
|
||||
describe("GoogleDrive", () => {
|
||||
const match = GoogleDrive.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect(
|
||||
"https://drive.google.com/file/d/1ohkOgmE8MiNx68u6ynBfYkgjeKu_x3ZK/view?usp=sharing".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://drive.google.com/file/d/1ohkOgmE8MiNx68u6ynBfYkgjeKu_x3ZK/preview?usp=sharing".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://drive.google.com/file/d/1ohkOgmE8MiNx68u6ynBfYkgjeKu_x3ZK/preview?usp=sharing&resourceKey=BG8k4dEt1p2gisnVdlaSpA".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://drive.google.com/file".match(match)).toBe(null);
|
||||
expect("https://drive.google.com".match(match)).toBe(null);
|
||||
expect("https://www.google.com".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://driveegoogle.com/file/d/1ohkOgmE8MiNx68u6ynBfYkgjeKu_x3ZK/view?usp=sharing".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://drive.googleecom/file/d/1ohkOgmE8MiNx68u6ynBfYkgjeKu_x3ZK/view?usp=sharing".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import Image from "../components/Img";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function GoogleDrive(props: Props) {
|
||||
return (
|
||||
<Frame
|
||||
src={props.attrs.href.replace("/view", "/preview")}
|
||||
icon={
|
||||
<Image
|
||||
src="/images/google-drive.png"
|
||||
alt="Google Drive Icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
}
|
||||
title="Google Drive"
|
||||
canonicalUrl={props.attrs.href}
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
GoogleDrive.ENABLED = [
|
||||
new RegExp("^https?://drive\\.google\\.com/file/d/(.*)$"),
|
||||
];
|
||||
|
||||
export default GoogleDrive;
|
||||
@@ -1,48 +0,0 @@
|
||||
import GoogleForms from "./GoogleForms";
|
||||
|
||||
describe("GoogleForms", () => {
|
||||
const match = GoogleForms.ENABLED[0];
|
||||
|
||||
test("to be enabled on long-form share links", () => {
|
||||
expect(
|
||||
"https://docs.google.com/forms/d/e/1FAIpQLSetbCGiE8DhfVQZMtLE_CU2MwpSsrkXi690hkEDREOvMu8VYQ/viewform?usp=sf_link".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://docs.google.com/forms/d/e/1FAIpQLSetbCGiE8DhfVQZMtLE_CU2MwpSsrkXi690hkEDREOvMu8VYQ/viewform".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://docs.google.com/forms/d/e/1FAIpQLSetbCGiE8DhfVQZMtLE_CU2MwpSsrkXi690hkEDREOvMu8VYQ/viewform?embedded=true".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on edit links", () => {
|
||||
expect(
|
||||
"https://docs.google.com/forms/d/1zG75dmHQGpomQlWB6VtRhWajNer7mKMjtApM_aRAJV8/edit".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://docs.google.com/forms".match(match)).toBe(null);
|
||||
expect("https://docs.google.com/forms/d/".match(match)).toBe(null);
|
||||
expect("https://docs.google.com".match(match)).toBe(null);
|
||||
expect("https://www.google.com".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://docssgoogle.com/forms/d/e/1FAIpQLSetbCGiE8DhfVQZMtLE_CU2MwpSsrkXi690hkEDREOvMu8VYQ/viewform?usp=sf_link".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://docs.googleecom/forms/d/e/1FAIpQLSetbCGiE8DhfVQZMtLE_CU2MwpSsrkXi690hkEDREOvMu8VYQ/viewform".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,33 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import Image from "../components/Img";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function GoogleForms(props: Props) {
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={props.attrs.href.replace(
|
||||
/\/(edit|viewform)(\?.+)?$/,
|
||||
"/viewform?embedded=true"
|
||||
)}
|
||||
icon={
|
||||
<Image
|
||||
src="/images/google-forms.png"
|
||||
alt="Google Forms Icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
}
|
||||
canonicalUrl={props.attrs.href}
|
||||
title="Google Forms"
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
GoogleForms.ENABLED = [
|
||||
new RegExp("^https?://docs\\.google\\.com/forms/d/(.+)$"),
|
||||
];
|
||||
|
||||
export default GoogleForms;
|
||||
@@ -1,34 +0,0 @@
|
||||
import GoogleLookerStudio from "./GoogleLookerStudio";
|
||||
|
||||
describe("GoogleLookerStudio", () => {
|
||||
const match = GoogleLookerStudio.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect(
|
||||
"https://lookerstudio.google.com/embed/reporting/aab01789-f3a2-4ff3-9cba-c4c94c4a92e8/page/7zFD".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://datastudio.google.com/embed/reporting/aab01789-f3a2-4ff3-9cba-c4c94c4a92e8/page/7zFD".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://lookerstudio.google.com/u/0/".match(match)).toBe(null);
|
||||
expect("https://lookerstudio.google.com".match(match)).toBe(null);
|
||||
expect("https://www.google.com".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://lookerstudioogoogle.com/embed/reporting/aab01789-f3a2-4ff3-9cba-c4c94c4a92e8/page/7zFD".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://lookerstudio.googleecom/embed/reporting/aab01789-f3a2-4ff3-9cba-c4c94c4a92e8/page/7zFD".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import Image from "../components/Img";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function GoogleLookerStudio(props: Props) {
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={props.attrs.href.replace("u/0", "embed").replace("/edit", "")}
|
||||
icon={
|
||||
<Image
|
||||
src="/images/google-lookerstudio.png"
|
||||
alt="Google Looker Studio Icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
}
|
||||
canonicalUrl={props.attrs.href}
|
||||
title="Google Looker Studio"
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
GoogleLookerStudio.ENABLED = [
|
||||
new RegExp(
|
||||
"^https?://(lookerstudio|datastudio)\\.google\\.com/(embed|u/0)/reporting/(.*)/page/(.*)(/edit)?$"
|
||||
),
|
||||
];
|
||||
|
||||
export default GoogleLookerStudio;
|
||||
@@ -1,18 +0,0 @@
|
||||
import GoogleMaps from "./GoogleMaps";
|
||||
|
||||
describe("GoogleMaps", () => {
|
||||
const match = GoogleMaps.ENABLED[0];
|
||||
|
||||
test("to be enabled", () => {
|
||||
expect(
|
||||
"https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d50977.036904273686!2d174.74383592605594!3d-37.00825027293197!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x6d0d4fe87ef3d5bb%3A0xf00ef62249b7130!2sAuckland%20Airport!5e0!3m2!1sen!2snz!4v1691573100204!5m2!1sen!2snz".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://www.google.com/maps/embed".match(match)).toBe(null);
|
||||
expect("https://goo.gl/maps/".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function GoogleMaps(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
const source = matches[0];
|
||||
|
||||
return <Frame {...props} src={source} title="Google Maps" />;
|
||||
}
|
||||
|
||||
GoogleMaps.ENABLED = [
|
||||
new RegExp("^https?://www\\.google\\.com/maps/embed\\?(.*)$"),
|
||||
];
|
||||
|
||||
export default GoogleMaps;
|
||||
@@ -1,34 +0,0 @@
|
||||
import GoogleSheets from "./GoogleSheets";
|
||||
|
||||
describe("GoogleSheets", () => {
|
||||
const match = GoogleSheets.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect(
|
||||
"https://docs.google.com/spreadsheets/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigRIt2cj_Pd-kgtaNQY6H0Jzn0_CVGbxC1GcK5IoNzU615lzguexFwxasAW/pub".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://docs.google.com/spreadsheets/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigR/edit".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://docs.google.com/spreadsheets".match(match)).toBe(null);
|
||||
expect("https://docs.google.com".match(match)).toBe(null);
|
||||
expect("https://www.google.com".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://docssgoogle.com/spreadsheets/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigRIt2cj_Pd-kgtaNQY6H0Jzn0_CVGbxC1GcK5IoNzU615lzguexFwxasAW/pub".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://docs.googleecom/spreadsheets/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigRIt2cj_Pd-kgtaNQY6H0Jzn0_CVGbxC1GcK5IoNzU615lzguexFwxasAW/pub".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import Image from "../components/Img";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function GoogleSheets(props: Props) {
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={props.attrs.href.replace("/edit", "/preview")}
|
||||
icon={
|
||||
<Image
|
||||
src="/images/google-sheets.png"
|
||||
alt="Google Sheets Icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
}
|
||||
canonicalUrl={props.attrs.href}
|
||||
title="Google Sheets"
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
GoogleSheets.ENABLED = [
|
||||
new RegExp("^https?://docs\\.google\\.com/spreadsheets/d/(.*)$"),
|
||||
];
|
||||
|
||||
export default GoogleSheets;
|
||||
@@ -1,39 +0,0 @@
|
||||
import GoogleSlides from "./GoogleSlides";
|
||||
|
||||
describe("GoogleSlides", () => {
|
||||
const match = GoogleSlides.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect(
|
||||
"https://docs.google.com/presentation/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigRIt2cj_Pd-kgtaNQY6H0Jzn0_CVGbxC1GcK5IoNzU615lzguexFwxasAW/pub?start=false&loop=false&delayms=3000".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://docs.google.com/presentation/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigRIt2cj_Pd-kgtaNQY6H0Jzn0_CVGbxC1GcK5IoNzU615lzguexFwxasAW/pub".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://docs.google.com/presentation/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigR/edit".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://docs.google.com/presentation".match(match)).toBe(null);
|
||||
expect("https://docs.google.com".match(match)).toBe(null);
|
||||
expect("https://www.google.com".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://docssgoogle.com/presentation/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigRIt2cj_Pd-kgtaNQY6H0Jzn0_CVGbxC1GcK5IoNzU615lzguexFwxasAW/pub?start=false&loop=false&delayms=3000".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://docs.googleecom/presentation/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigRIt2cj_Pd-kgtaNQY6H0Jzn0_CVGbxC1GcK5IoNzU615lzguexFwxasAW/pub?start=false&loop=false&delayms=3000".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import Image from "../components/Img";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function GoogleSlides(props: Props) {
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={props.attrs.href
|
||||
.replace("/edit", "/preview")
|
||||
.replace("/pub", "/embed")}
|
||||
icon={
|
||||
<Image
|
||||
src="/images/google-slides.png"
|
||||
alt="Google Slides Icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
}
|
||||
canonicalUrl={props.attrs.href}
|
||||
title="Google Slides"
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
GoogleSlides.ENABLED = [
|
||||
new RegExp("^https?://docs\\.google\\.com/presentation/d/(.*)$"),
|
||||
];
|
||||
|
||||
export default GoogleSlides;
|
||||
@@ -1,33 +0,0 @@
|
||||
import Grist from "./Grist";
|
||||
|
||||
describe("Grist", () => {
|
||||
const match = Grist.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect(
|
||||
"https://templates.getgrist.com/doc/afterschool-program/p/2?embed=true".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://docs.getgrist.com/sg5V93LuAije/Untitled-document/p/22?embed=true".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://templates.getgrist.com/doc/afterschool-program".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
// Self hosted not yet supported
|
||||
expect(
|
||||
"http://grist.my.host.com/o/docs/doc/new~5cCkr6CtMArdA62ohSy5xB/p/1?embed=true".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://my.get.grist.com/doc/afterschool-program?embed=true".match(match)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import Image from "../components/Img";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Grist(props: Props) {
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={props.attrs.href.replace(/(\?embed=true)?$/, "?embed=true")}
|
||||
icon={
|
||||
<Image
|
||||
src="/images/grist.png"
|
||||
alt="Grist Icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
}
|
||||
title="Grist Spreadsheet"
|
||||
canonicalUrl={props.attrs.href}
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Grist.ENABLED = [new RegExp("^https?://([a-z.-]+\\.)?getgrist\\.com/(.+)$")];
|
||||
|
||||
Grist.URL_PATH_REGEX = /(.+)/;
|
||||
|
||||
export default Grist;
|
||||
@@ -1,21 +0,0 @@
|
||||
import InVision from "./InVision";
|
||||
|
||||
describe("InVision", () => {
|
||||
const match = InVision.ENABLED[0];
|
||||
|
||||
test("to be enabled on shortlink", () => {
|
||||
expect("https://invis.io/69PG07QYQTE".match(match)).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on share", () => {
|
||||
expect(
|
||||
"https://projects.invisionapp.com/share/69PG07QYQTE".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://invis.io".match(match)).toBe(null);
|
||||
expect("https://invisionapp.com".match(match)).toBe(null);
|
||||
expect("https://projects.invisionapp.com".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -3,13 +3,8 @@ import Frame from "../components/Frame";
|
||||
import ImageZoom from "../components/ImageZoom";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
const IFRAME_REGEX =
|
||||
/^https:\/\/(invis\.io\/.*)|(projects\.invisionapp\.com\/share\/.*)$/;
|
||||
const IMAGE_REGEX =
|
||||
/^https:\/\/(opal\.invisionapp\.com\/static-signed\/live-embed\/.*)$/;
|
||||
|
||||
function InVision(props: Props) {
|
||||
if (IMAGE_REGEX.test(props.attrs.href)) {
|
||||
function InVision({ matches, ...props }: Props) {
|
||||
if (/opal\.invisionapp\.com/.test(props.attrs.href)) {
|
||||
return (
|
||||
<div className={props.isSelected ? "ProseMirror-selectednode" : ""}>
|
||||
<ImageZoom zoomMargin={24}>
|
||||
@@ -29,6 +24,4 @@ function InVision(props: Props) {
|
||||
return <Frame {...props} src={props.attrs.href} title="InVision Embed" />;
|
||||
}
|
||||
|
||||
InVision.ENABLED = [IFRAME_REGEX, IMAGE_REGEX];
|
||||
|
||||
export default InVision;
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import Instagram from "./Instagram";
|
||||
|
||||
describe("Instagram", () => {
|
||||
const match = Instagram.ENABLED[0];
|
||||
|
||||
test("to be enabled on post link", () => {
|
||||
expect(
|
||||
"https://www.instagram.com/p/CrL74G6Bxgw/?utm_source=ig_web_copy_link".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on reel link", () => {
|
||||
expect(
|
||||
"https://www.instagram.com/reel/Cxdyt_fMnwN/?utm_source=ig_web_copy_link".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://www.instagram.com".match(match)).toBe(null);
|
||||
expect("https://www.instagram.com/reel/".match(match)).toBe(null);
|
||||
expect("https://www.instagram.com/p/".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Instagram(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
return <Frame {...props} src={`${matches[0]}/embed`} title="Instagram" />;
|
||||
}
|
||||
|
||||
Instagram.ENABLED = [
|
||||
/^https?:\/\/www\.instagram\.com\/(p|reel)\/([\w-]+)(\/?utm_source=\w+)?/,
|
||||
];
|
||||
|
||||
export default Instagram;
|
||||
@@ -1,11 +0,0 @@
|
||||
import JSFiddle from "./JSFiddle";
|
||||
|
||||
describe("JSFiddle", () => {
|
||||
const match = JSFiddle.ENABLED[0];
|
||||
|
||||
test("to not be enabled for invalid urls", () => {
|
||||
expect("https://jsfiddleenet/go/share/c9d837d74182317".match(match)).toBe(
|
||||
null
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import { useTheme } from "styled-components";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function JSFiddle(props: Props) {
|
||||
function JSFiddle({ matches, ...props }: Props) {
|
||||
const normalizedUrl = props.attrs.href.replace(/(\/embedded)?\/$/, "");
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -20,6 +20,4 @@ function JSFiddle(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
JSFiddle.ENABLED = [new RegExp("^https?://jsfiddle\\.net/(.*)/(.*)$")];
|
||||
|
||||
export default JSFiddle;
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import Linkedin from "./Linkedin";
|
||||
|
||||
describe("Linkedin", () => {
|
||||
const match = Linkedin.ENABLED[0];
|
||||
|
||||
test("to be enabled on post link", () => {
|
||||
expect(
|
||||
"https://www.linkedin.com/posts/github_highlight-your-expertise-with-github-certifications-activity-7117157514097352704-F4Mz?utm_source=share&utm_medium=member_desktop".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on embed link", () => {
|
||||
expect(
|
||||
"https://www.linkedin.com/embed/feed/update/urn:li:share:7117157513422090241".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://www.linkedin.com/".match(match)).toBe(null);
|
||||
expect("https://www.linkedin.com/posts/".match(match)).toBe(null);
|
||||
expect("https://www.linkedin.com/embed/".match(match)).toBe(null);
|
||||
expect("https://www.linkedin.com/embed/feed/update/".match(match)).toBe(
|
||||
null
|
||||
);
|
||||
expect(
|
||||
"https://www.linkedin.com/embed/feed/update/urn:li:".match(match)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://www.linkedin.com/embed/feed/update/urn:li:share:".match(match)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://www.linkedin.com/embed/feed/update/urn:li:ugcPost:".match(match)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -2,8 +2,7 @@ import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Linkedin(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
function Linkedin({ matches, ...props }: Props) {
|
||||
const objectId = matches[2];
|
||||
const postType = matches[1];
|
||||
if (matches[3] === "embed") {
|
||||
@@ -18,8 +17,4 @@ function Linkedin(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
Linkedin.ENABLED = [
|
||||
/^https:\/\/www\.linkedin\.com\/(?:posts\/.*-(ugcPost|activity)-(\d+)-.*|(embed)\/(?:feed\/update\/urn:li:(?:ugcPost|share):(?:\d+)))/,
|
||||
];
|
||||
|
||||
export default Linkedin;
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import Loom from "./Loom";
|
||||
|
||||
describe("Loom", () => {
|
||||
const match = Loom.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect(
|
||||
"https://www.loom.com/share/55327cbb265743f39c2c442c029277e0".match(match)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://www.useloom.com/share/55327cbb265743f39c2c442c029277e0".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on embed link", () => {
|
||||
expect(
|
||||
"https://www.loom.com/embed/55327cbb265743f39c2c442c029277e0".match(match)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
"https://www.useloom.com/embed/55327cbb265743f39c2c442c029277e0".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://www.useloom.com".match(match)).toBe(null);
|
||||
expect("https://www.useloom.com/features".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://www.loommcom/share/55327cbb265743f39c2c442c029277e0".match(match)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Loom(props: Props) {
|
||||
const normalizedUrl = props.attrs.href.replace("share", "embed");
|
||||
return <Frame {...props} src={normalizedUrl} title="Loom Embed" />;
|
||||
}
|
||||
|
||||
Loom.ENABLED = [/^https:\/\/(www\.)?(use)?loom\.com\/(embed|share)\/(.*)$/];
|
||||
|
||||
export default Loom;
|
||||
@@ -1,53 +0,0 @@
|
||||
import Lucidchart from "./Lucidchart";
|
||||
|
||||
describe("Lucidchart", () => {
|
||||
const match = Lucidchart.ENABLED[0];
|
||||
|
||||
test("to be enabled on view link", () => {
|
||||
expect(
|
||||
"https://www.lucidchart.com/documents/view/2f4a79cb-7637-433d-8ffb-27cce65a05e7".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on root link", () => {
|
||||
expect(
|
||||
"https://lucidchart.com/documents/view/2f4a79cb-7637-433d-8ffb-27cce65a05e7".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on app link", () => {
|
||||
expect(
|
||||
"https://app.lucidchart.com/documents/view/2f4a79cb-7637-433d-8ffb-27cce65a05e7".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on visited link", () => {
|
||||
expect(
|
||||
"https://www.lucidchart.com/documents/view/2f4a79cb-7637-433d-8ffb-27cce65a05e7/0".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on embedded link", () => {
|
||||
expect(
|
||||
"https://app.lucidchart.com/documents/embeddedchart/1af2bdfa-da7d-4ea1-aa1d-bec5677a9837".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://lucidchart.com".match(match)).toBe(null);
|
||||
expect("https://app.lucidchart.com".match(match)).toBe(null);
|
||||
expect("https://www.lucidchart.com".match(match)).toBe(null);
|
||||
expect("https://www.lucidchart.com/features".match(match)).toBe(null);
|
||||
expect("https://www.lucidchart.com/documents/view".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Lucidchart(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
const chartId = matches.groups?.chartId;
|
||||
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`https://lucidchart.com/documents/embeddedchart/${chartId}`}
|
||||
title="Lucidchart Embed"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Lucidchart.ENABLED = [
|
||||
/^https?:\/\/(www\.|app\.)?(lucidchart\.com|lucid\.app)\/documents\/(embeddedchart|view|edit)\/(?<chartId>[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})(?:.*)?$/,
|
||||
/^https?:\/\/(www\.|app\.)?(lucid\.app|lucidchart\.com)\/lucidchart\/(?<chartId>[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\/(embeddedchart|view|edit)(?:.*)?$/,
|
||||
];
|
||||
|
||||
export default Lucidchart;
|
||||
@@ -1,15 +0,0 @@
|
||||
import Marvel from "./Marvel";
|
||||
|
||||
describe("Marvel", () => {
|
||||
const match = Marvel.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect("https://marvelapp.com/75hj91".match(match)).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://marvelapp.com".match(match)).toBe(null);
|
||||
expect("https://marvelapp.com/features".match(match)).toBe(null);
|
||||
expect("https://marvelapppcom/75hj91".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Marvel(props: Props) {
|
||||
return (
|
||||
<Frame {...props} src={props.attrs.href} title="Marvel Embed" border />
|
||||
);
|
||||
}
|
||||
|
||||
Marvel.ENABLED = [new RegExp("^https://marvelapp\\.com/([A-Za-z0-9-]{6})/?$")];
|
||||
|
||||
export default Marvel;
|
||||
@@ -1,58 +0,0 @@
|
||||
import Mindmeister from "./Mindmeister";
|
||||
|
||||
describe("Mindmeister", () => {
|
||||
const match = Mindmeister.ENABLED[0];
|
||||
|
||||
test("to be enabled on mm.tt link", () => {
|
||||
expect("https://mm.tt/326377934".match(match)).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on mm.tt link with token parameter", () => {
|
||||
expect("https://mm.tt/326377934?t=r9NcnTRr18".match(match)).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on embed link", () => {
|
||||
expect(
|
||||
"https://www.mindmeister.com/maps/public_map_shell/326377934/paper-digital-or-online-mind-mapping".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on public link", () => {
|
||||
expect(
|
||||
"https://www.mindmeister.com/326377934/paper-digital-or-online-mind-mapping".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled without www", () => {
|
||||
expect(
|
||||
"https://mindmeister.com/326377934/paper-digital-or-online-mind-mapping".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled without slug", () => {
|
||||
expect("https://mindmeister.com/326377934".match(match)).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://mindmeister.com".match(match)).toBe(null);
|
||||
expect("https://www.mindmeister.com/pricing".match(match)).toBe(null);
|
||||
expect("https://www.mmttt/326377934".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://www.mindmeisterrcom/maps/public_map_shell/326377934/paper-digital-or-online-mind-mapping".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
expect("https://wwwwmm.tt/326377934".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://wwwwmindmeister.com/maps/public_map_shell/326377934/paper-digital-or-online-mind-mapping".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Mindmeister(props: Props) {
|
||||
const chartId =
|
||||
props.attrs.matches[4] +
|
||||
(props.attrs.matches[5] || "") +
|
||||
(props.attrs.matches[6] || "");
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`https://www.mindmeister.com/maps/public_map_shell/${chartId}`}
|
||||
title="Mindmeister Embed"
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Mindmeister.ENABLED = [
|
||||
new RegExp(
|
||||
"^https://([w.-]+\\.)?(mindmeister\\.com|mm\\.tt)(/maps/public_map_shell)?/(\\d+)(\\?t=.*)?(/.*)?$"
|
||||
),
|
||||
];
|
||||
|
||||
export default Mindmeister;
|
||||
@@ -1,30 +0,0 @@
|
||||
import Miro from "./Miro";
|
||||
|
||||
describe("Miro", () => {
|
||||
const match = Miro.ENABLED[0];
|
||||
|
||||
test("to be enabled on old domain share link", () => {
|
||||
expect(
|
||||
"https://realtimeboard.com/app/board/o9J_k0fwiss=".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect("https://miro.com/app/board/o9J_k0fwiss=".match(match)).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to extract the domain as part of the match for later use", () => {
|
||||
expect(
|
||||
"https://realtimeboard.com/app/board/o9J_k0fwiss=".match(match)?.[1]
|
||||
).toBe("realtimeboard");
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://miro.com".match(match)).toBe(null);
|
||||
expect("https://realtimeboard.com".match(match)).toBe(null);
|
||||
expect("https://realtimeboard.com/features".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://realtimeboarddcom/app/board/o9J_k0fwiss=".match(match)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function RealtimeBoard(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
const domain = matches[1];
|
||||
const boardId = matches[2];
|
||||
const titleName = domain === "realtimeboard" ? "RealtimeBoard" : "Miro";
|
||||
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`https://${domain}.com/app/embed/${boardId}`}
|
||||
title={`${titleName} (${boardId})`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
RealtimeBoard.ENABLED = [
|
||||
/^https:\/\/(realtimeboard|miro)\.com\/app\/board\/(.*)$/,
|
||||
];
|
||||
|
||||
export default RealtimeBoard;
|
||||
@@ -1,23 +0,0 @@
|
||||
import ModeAnalytics from "./ModeAnalytics";
|
||||
|
||||
describe("ModeAnalytics", () => {
|
||||
const match = ModeAnalytics.ENABLED[0];
|
||||
|
||||
test("to be enabled on report link", () => {
|
||||
expect(
|
||||
"https://modeanalytics.com/outline/reports/5aca06064f56".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://modeanalytics.com".match(match)).toBe(null);
|
||||
expect("https://modeanalytics.com/outline".match(match)).toBe(null);
|
||||
expect("https://modeanalytics.com/outline/reports".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://modeanalyticsscom/outline/reports/5aca06064f56".match(match)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://wwwwmodeanalytics.com/outline/reports/5aca06064f56".match(match)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function ModeAnalytics(props: Props) {
|
||||
// Allow users to paste embed or standard urls and handle them the same
|
||||
const normalizedUrl = props.attrs.href.replace(/\/embed$/, "");
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`${normalizedUrl}/embed`}
|
||||
title="Mode Analytics Embed"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
ModeAnalytics.ENABLED = [
|
||||
new RegExp("^https://([w.-]+\\.)?modeanalytics\\.com/(.*)/reports/(.*)$"),
|
||||
];
|
||||
|
||||
export default ModeAnalytics;
|
||||
@@ -1,9 +0,0 @@
|
||||
import Otter from "./Otter";
|
||||
|
||||
describe("Otter", () => {
|
||||
const match = Otter.ENABLED[0];
|
||||
|
||||
test("to not be enabled for invalid urls", () => {
|
||||
expect("https://otterrai/s/c9d837d74182317".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Otter(props: Props) {
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={props.attrs.href}
|
||||
title="Otter.ai Embed"
|
||||
referrerPolicy="origin"
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Otter.ENABLED = [new RegExp("^https?://otter\\.ai/[su]/(.*)$")];
|
||||
|
||||
export default Otter;
|
||||
@@ -1,18 +0,0 @@
|
||||
import Pitch from "./Pitch";
|
||||
|
||||
describe("Pitch", () => {
|
||||
const match = Pitch.ENABLED[0];
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect(
|
||||
"https://appppitch.com/app/presentation/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/player/c9d837d74182317".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://app.pitchhcom/app/presentation/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/player/c9d837d74182317".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Pitch(props: Props) {
|
||||
const shareId = props.attrs.matches[1];
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`https://pitch.com/embed/${shareId}`}
|
||||
title="Pitch Embed"
|
||||
height="414px"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Pitch.ENABLED = [
|
||||
new RegExp(
|
||||
"^https?://app\\.pitch\\.com/app/(?:presentation/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|public/player)/(.*)$"
|
||||
),
|
||||
new RegExp("^https?://pitch\\.com/embed/(.*)$"),
|
||||
];
|
||||
|
||||
export default Pitch;
|
||||
@@ -1,25 +0,0 @@
|
||||
import Prezi from "./Prezi";
|
||||
|
||||
describe("Prezi", () => {
|
||||
const match = Prezi.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect(
|
||||
"https://prezi.com/view/39mn8Rn1ZkoeEKQCgk5C".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on embed link", () => {
|
||||
expect(
|
||||
"https://prezi.com/view/39mn8Rn1ZkoeEKQCgk5C/embed".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://prezi.com".match(match)).toBe(null);
|
||||
expect("https://prezi.com/pricing".match(match)).toBe(null);
|
||||
expect("https://preziicom/view/39mn8Rn1ZkoeEKQCgk5C".match(match)).toBe(
|
||||
null
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Prezi(props: Props) {
|
||||
const url = props.attrs.href.replace(/\/embed$/, "");
|
||||
return <Frame {...props} src={`${url}/embed`} title="Prezi Embed" border />;
|
||||
}
|
||||
|
||||
Prezi.ENABLED = [new RegExp("^https://prezi\\.com/view/(.*)$")];
|
||||
|
||||
export default Prezi;
|
||||
@@ -1,19 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
export default function Scribe(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
const shareId = matches[1];
|
||||
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`https://scribehow.com/embed/${shareId}`}
|
||||
title="Scribe"
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Scribe.ENABLED = [/^https?:\/\/scribehow\.com\/shared\/(.*)$/];
|
||||
@@ -1,37 +0,0 @@
|
||||
import Spotify from "./Spotify";
|
||||
|
||||
describe("Spotify", () => {
|
||||
const match = Spotify.ENABLED[0];
|
||||
|
||||
test("to be enabled on song link", () => {
|
||||
expect(
|
||||
"https://open.spotify.com/track/29G1ScCUhgjgI0H72qN4DE?si=DxjEUxV2Tjmk6pSVckPDRg".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on playlist link", () => {
|
||||
expect(
|
||||
"https://open.spotify.com/user/spotify/playlist/29G1ScCUhgjgI0H72qN4DE?si=DxjEUxV2Tjmk6pSVckPDRg".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://spotify.com".match(match)).toBe(null);
|
||||
expect("https://open.spotify.com".match(match)).toBe(null);
|
||||
expect("https://www.spotify.com".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://opennspotify.com/track/29G1ScCUhgjgI0H72qN4DE?si=DxjEUxV2Tjmk6pSVckPDRg".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
expect(
|
||||
"https://open.spotifyycom/track/29G1ScCUhgjgI0H72qN4DE?si=DxjEUxV2Tjmk6pSVckPDRg".match(
|
||||
match
|
||||
)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import styled from "styled-components";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Spotify(props: Props) {
|
||||
function Spotify({ matches, ...props }: Props) {
|
||||
let pathname = "";
|
||||
try {
|
||||
const parsed = new URL(props.attrs.href);
|
||||
@@ -39,6 +39,4 @@ const SpotifyFrame = styled(Frame)`
|
||||
border-radius: 13px;
|
||||
`;
|
||||
|
||||
Spotify.ENABLED = [new RegExp("^https?://open\\.spotify\\.com/(.*)$")];
|
||||
|
||||
export default Spotify;
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import Tldraw from "./Tldraw";
|
||||
|
||||
describe("Tldraw", () => {
|
||||
const match = Tldraw.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect(
|
||||
"https://beta.tldraw.com/r/v2_c_r5WVtGaktE99D3wyFFsoL".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled for snapshot link", () => {
|
||||
expect(
|
||||
"https://beta.tldraw.com/s/v2_c_r5WVtGaktE99D3wyFFsoL".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled for read only urls having v", () => {
|
||||
expect(
|
||||
"https://www.tldraw.com/v/-2VIEZVwrbtdNq0Pmfpf2?viewport=-608%2C-549%2C1998%2C1943&page=page%3AWd7DfjA-igxOMso931W_S".match(
|
||||
match
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://wwww.tldraw.com/r/c9d837d74182317".match(match)).toBe(null);
|
||||
expect("https://wwwwtldraw.com/r/c9d837d74182317".match(match)).toBe(null);
|
||||
expect("https://www.tldrawwcom/r/c9d837d74182317".match(match)).toBe(null);
|
||||
expect(
|
||||
"https://beta.tldraw.com/e/v2_c_r5WVtGaktE99D3wyFFsoL".match(match)
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Tldraw(props: Props) {
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={props.attrs.href}
|
||||
title="Tldraw Embed"
|
||||
referrerPolicy="origin"
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Tldraw.ENABLED = [
|
||||
new RegExp("^https?://(beta|www|old)\\.tldraw\\.com/[rsv]/(.*)"),
|
||||
];
|
||||
|
||||
export default Tldraw;
|
||||
@@ -1,9 +0,0 @@
|
||||
import Trello from "./Trello";
|
||||
|
||||
describe("Trello", () => {
|
||||
const match = Trello.ENABLED[0];
|
||||
|
||||
test("to not be enabled for invalid urls", () => {
|
||||
expect("https://trelloocom/c/c9d837d74182317".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -2,8 +2,7 @@ import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Trello(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
function Trello({ matches, ...props }: Props) {
|
||||
const objectId = matches[2];
|
||||
|
||||
if (matches[1] === "c") {
|
||||
@@ -28,6 +27,4 @@ function Trello(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
Trello.ENABLED = [/^https:\/\/trello\.com\/(c|b)\/([^/]*)(.*)?$/];
|
||||
|
||||
export default Trello;
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import Typeform from "./Typeform";
|
||||
|
||||
describe("Typeform", () => {
|
||||
const match = Typeform.ENABLED[0];
|
||||
|
||||
test("to be enabled on share link", () => {
|
||||
expect(
|
||||
"https://beardyman.typeform.com/to/zvlr4L".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://www.typeform.com".match(match)).toBe(null);
|
||||
expect("https://typeform.com/to/zvlr4L".match(match)).toBe(null);
|
||||
expect("https://typeform.com/features".match(match)).toBe(null);
|
||||
expect("https://beardymanntypeform.com/to/zvlr4L".match(match)).toBe(null);
|
||||
expect("https://beardyman.typeformmcom/to/zvlr4L".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Typeform(props: Props) {
|
||||
return <Frame {...props} src={props.attrs.href} title="Typeform Embed" />;
|
||||
}
|
||||
|
||||
Typeform.ENABLED = [
|
||||
new RegExp(
|
||||
"^https://([A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)\\.typeform\\.com/to/(.*)$"
|
||||
),
|
||||
];
|
||||
|
||||
export default Typeform;
|
||||
@@ -1,19 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
export default function Valtown(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
const valId = matches[1];
|
||||
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`https://www.val.town/embed/${valId}`}
|
||||
title="Valtown"
|
||||
border
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Valtown.ENABLED = [/^https?:\/\/(?:www.)?val\.town\/(?:v|embed)\/(.*)$/];
|
||||
@@ -1,20 +0,0 @@
|
||||
import Vimeo from "./Vimeo";
|
||||
|
||||
describe("Vimeo", () => {
|
||||
const match = Vimeo.ENABLED[0];
|
||||
|
||||
test("to be enabled on video link", () => {
|
||||
expect("https://vimeo.com/265045525".match(match)).toBeTruthy();
|
||||
expect("https://vimeo.com/265045525/b9fefc8598".match(match)).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://vimeo.com".match(match)).toBe(null);
|
||||
expect("https://www.vimeo.com".match(match)).toBe(null);
|
||||
expect("https://vimeo.com/upgrade".match(match)).toBe(null);
|
||||
expect("https://vimeo.com/features/video-marketing".match(match)).toBe(
|
||||
null
|
||||
);
|
||||
expect("https://www.vimeoocom/265045525".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -2,8 +2,7 @@ import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Vimeo(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
function Vimeo({ matches, ...props }: Props) {
|
||||
const videoId = matches[4];
|
||||
const hId = matches[5];
|
||||
|
||||
@@ -21,8 +20,4 @@ function Vimeo(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
Vimeo.ENABLED = [
|
||||
/(http|https)?:\/\/(www\.)?vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|)(\d+)(?:\/|\?)?([\d\w]+)?/,
|
||||
];
|
||||
|
||||
export default Vimeo;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import Whimsical from "./Whimsical";
|
||||
|
||||
describe("Whimsical", () => {
|
||||
const match = Whimsical.ENABLED[0];
|
||||
|
||||
test("to not be enabled for invalid urls", () => {
|
||||
expect("https://whimsicallcom/a-c9d837d74182317".match(match)).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function Whimsical(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
const boardId = matches[1];
|
||||
|
||||
return (
|
||||
<Frame
|
||||
{...props}
|
||||
src={`https://whimsical.com/embed/${boardId}`}
|
||||
title="Whimsical"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Whimsical.ENABLED = [
|
||||
/^https?:\/\/whimsical\.com\/[0-9a-zA-Z-_~]*-([a-zA-Z0-9]+)\/?$/,
|
||||
];
|
||||
|
||||
export default Whimsical;
|
||||
@@ -1,41 +0,0 @@
|
||||
import YouTube from "./YouTube";
|
||||
|
||||
describe("YouTube", () => {
|
||||
const match = YouTube.ENABLED[0];
|
||||
|
||||
test("to be enabled on video link", () => {
|
||||
expect(
|
||||
"https://www.youtube.com/watch?v=dQw4w9WgXcQ".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on video link with timestamp", () => {
|
||||
expect(
|
||||
"https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=123s".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on embed link", () => {
|
||||
expect(
|
||||
"https://www.youtube.com/embed?v=dQw4w9WgXcQ".match(match)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on shortlink", () => {
|
||||
expect("https://youtu.be/dQw4w9WgXcQ".match(match)).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to be enabled on shortlink with timestamp", () => {
|
||||
expect("https://youtu.be/dQw4w9WgXcQ?t=123".match(match)).toBeTruthy();
|
||||
});
|
||||
|
||||
test("to not be enabled elsewhere", () => {
|
||||
expect("https://youtu.be".match(match)).toBe(null);
|
||||
expect("https://youtube.com".match(match)).toBe(null);
|
||||
expect("https://www.youtube.com".match(match)).toBe(null);
|
||||
expect("https://www.youtube.com/logout".match(match)).toBe(null);
|
||||
expect("https://www.youtube.com/feed/subscriptions".match(match)).toBe(
|
||||
null
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -2,8 +2,7 @@ import * as React from "react";
|
||||
import Frame from "../components/Frame";
|
||||
import { EmbedProps as Props } from ".";
|
||||
|
||||
function YouTube(props: Props) {
|
||||
const { matches } = props.attrs;
|
||||
function YouTube({ matches, ...props }: Props) {
|
||||
const videoId = matches[1];
|
||||
|
||||
let start;
|
||||
@@ -26,8 +25,4 @@ function YouTube(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
YouTube.ENABLED = [
|
||||
/(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([a-zA-Z0-9_-]{11})([\&\?](.*))?$/i,
|
||||
];
|
||||
|
||||
export default YouTube;
|
||||
|
||||
@@ -1,67 +1,29 @@
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import { Primitive } from "utility-types";
|
||||
import { IntegrationType } from "../../types";
|
||||
import { IntegrationService, IntegrationType } from "../../types";
|
||||
import type { IntegrationSettings } from "../../types";
|
||||
import { urlRegex } from "../../utils/urls";
|
||||
import Image from "../components/Img";
|
||||
import Abstract from "./Abstract";
|
||||
import Airtable from "./Airtable";
|
||||
import Berrycast from "./Berrycast";
|
||||
import Bilibili from "./Bilibili";
|
||||
import Canva from "./Canva";
|
||||
import Cawemo from "./Cawemo";
|
||||
import ClickUp from "./ClickUp";
|
||||
import Codepen from "./Codepen";
|
||||
import DBDiagram from "./DBDiagram";
|
||||
import Descript from "./Descript";
|
||||
import Diagrams from "./Diagrams";
|
||||
import Figma from "./Figma";
|
||||
import Framer from "./Framer";
|
||||
import Gist from "./Gist";
|
||||
import GitLabSnippet from "./GitLabSnippet";
|
||||
import Gliffy from "./Gliffy";
|
||||
import GoogleCalendar from "./GoogleCalendar";
|
||||
import GoogleDocs from "./GoogleDocs";
|
||||
import GoogleDrawings from "./GoogleDrawings";
|
||||
import GoogleDrive from "./GoogleDrive";
|
||||
import GoogleForms from "./GoogleForms";
|
||||
import GoogleLookerStudio from "./GoogleLookerStudio";
|
||||
import GoogleMaps from "./GoogleMaps";
|
||||
import GoogleSheets from "./GoogleSheets";
|
||||
import GoogleSlides from "./GoogleSlides";
|
||||
import Grist from "./Grist";
|
||||
import InVision from "./InVision";
|
||||
import Instagram from "./Instagram";
|
||||
import JSFiddle from "./JSFiddle";
|
||||
import Linkedin from "./Linkedin";
|
||||
import Loom from "./Loom";
|
||||
import Lucidchart from "./Lucidchart";
|
||||
import Marvel from "./Marvel";
|
||||
import Mindmeister from "./Mindmeister";
|
||||
import Miro from "./Miro";
|
||||
import ModeAnalytics from "./ModeAnalytics";
|
||||
import Otter from "./Otter";
|
||||
import Pitch from "./Pitch";
|
||||
import Prezi from "./Prezi";
|
||||
import Scribe from "./Scribe";
|
||||
import Spotify from "./Spotify";
|
||||
import Tldraw from "./Tldraw";
|
||||
import Trello from "./Trello";
|
||||
import Typeform from "./Typeform";
|
||||
import Valtown from "./Valtown";
|
||||
import Vimeo from "./Vimeo";
|
||||
import Whimsical from "./Whimsical";
|
||||
import YouTube from "./YouTube";
|
||||
|
||||
export type EmbedProps = {
|
||||
isSelected: boolean;
|
||||
isEditable: boolean;
|
||||
embed: EmbedDescriptor;
|
||||
matches: RegExpMatchArray;
|
||||
attrs: {
|
||||
href: string;
|
||||
matches: RegExpMatchArray;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -75,17 +37,40 @@ const Img = styled(Image)`
|
||||
`;
|
||||
|
||||
export class EmbedDescriptor {
|
||||
/** An icon that will be used to represent the embed in menus */
|
||||
icon?: React.ReactNode;
|
||||
/** The name of the embed. If this embed has a matching integration it should match IntegrationService */
|
||||
name?: string;
|
||||
title?: string;
|
||||
/** The title of the embed */
|
||||
title: string;
|
||||
/** A keyboard shortcut that will trigger the embed */
|
||||
shortcut?: string;
|
||||
/** Keywords that will match this embed in menus */
|
||||
keywords?: string;
|
||||
/** A tooltip that will be shown in menus */
|
||||
tooltip?: string;
|
||||
/** Whether the embed should be hidden in menus by default */
|
||||
defaultHidden?: boolean;
|
||||
/** A regex that will be used to match the embed when pasting a URL */
|
||||
regexMatch?: RegExp[];
|
||||
/**
|
||||
* A function that will be used to transform the URL. The resulting string is passed as the src
|
||||
* to the iframe. You can perform any transformations you want here, including changing the domain
|
||||
*
|
||||
* If a custom display is needed this function should be left undefined and `component` should be
|
||||
* used instead.
|
||||
*/
|
||||
transformMatch?: (matches: RegExpMatchArray) => string;
|
||||
/** The node attributes */
|
||||
attrs?: Record<string, Primitive>;
|
||||
/** Whether the embed should be visible in menus, always true */
|
||||
visible?: boolean;
|
||||
active?: (state: EditorState) => boolean;
|
||||
component: typeof React.Component | React.FC<any>;
|
||||
/**
|
||||
* A React component that will be used to render the embed, if displaying a simple iframe then
|
||||
* `transformMatch` should be used instead.
|
||||
*/
|
||||
component?: React.FunctionComponent<EmbedProps>;
|
||||
/** The integration settings, if any */
|
||||
settings?: IntegrationSettings<IntegrationType.Embed>;
|
||||
|
||||
constructor(options: Omit<EmbedDescriptor, "matcher">) {
|
||||
@@ -96,30 +81,23 @@ export class EmbedDescriptor {
|
||||
this.keywords = options.keywords;
|
||||
this.tooltip = options.tooltip;
|
||||
this.defaultHidden = options.defaultHidden;
|
||||
this.regexMatch = options.regexMatch;
|
||||
this.transformMatch = options.transformMatch;
|
||||
this.attrs = options.attrs;
|
||||
this.visible = options.visible;
|
||||
this.active = options.active;
|
||||
this.component = options.component;
|
||||
this.settings = options.settings;
|
||||
}
|
||||
|
||||
matcher(url: string): boolean | [] | RegExpMatchArray {
|
||||
const regex = urlRegex(this.settings?.url);
|
||||
matcher(url: string): false | RegExpMatchArray {
|
||||
const regexes = this.regexMatch ?? [];
|
||||
const settingsDomainRegex = urlRegex(this.settings?.url);
|
||||
|
||||
// @ts-expect-error not aware of static
|
||||
const regexes = this.component.ENABLED;
|
||||
|
||||
regex &&
|
||||
regexes.unshift(
|
||||
new RegExp(
|
||||
// @ts-expect-error not aware of static
|
||||
`^${regex.source}${this.component.URL_PATH_REGEX.source}$`
|
||||
)
|
||||
);
|
||||
if (settingsDomainRegex) {
|
||||
regexes.unshift(settingsDomainRegex);
|
||||
}
|
||||
|
||||
for (const regex of regexes) {
|
||||
const result = url.match(regex);
|
||||
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
@@ -135,18 +113,29 @@ const embeds: EmbedDescriptor[] = [
|
||||
keywords: "design",
|
||||
defaultHidden: true,
|
||||
icon: <Img src="/images/abstract.png" alt="Abstract" />,
|
||||
component: Abstract,
|
||||
regexMatch: [
|
||||
new RegExp("^https?://share\\.(?:go)?abstract\\.com/(.*)$"),
|
||||
new RegExp("^https?://app\\.(?:go)?abstract\\.com/(?:share|embed)/(.*)$"),
|
||||
],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
`https://app.goabstract.com/embed/${matches[1]}`,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Airtable",
|
||||
keywords: "spreadsheet",
|
||||
icon: <Img src="/images/airtable.png" alt="Airtable" />,
|
||||
component: Airtable,
|
||||
regexMatch: [
|
||||
new RegExp("^https://airtable.com/(?:embed/)?(app.*/)?(shr.*)$"),
|
||||
new RegExp("^https://airtable.com/(app.*/)?(pag.*)/form$"),
|
||||
],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
`https://airtable.com/embed/${matches[1] ?? ""}${matches[2]}`,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Berrycast",
|
||||
keywords: "video",
|
||||
defaultHidden: true,
|
||||
regexMatch: [/^https:\/\/(www\.)?berrycast.com\/conversations\/(.*)$/i],
|
||||
icon: <Img src="/images/berrycast.png" alt="Berrycast" />,
|
||||
component: Berrycast,
|
||||
}),
|
||||
@@ -154,155 +143,241 @@ const embeds: EmbedDescriptor[] = [
|
||||
title: "Bilibili",
|
||||
keywords: "video",
|
||||
defaultHidden: true,
|
||||
regexMatch: [
|
||||
/(?:https?:\/\/)?(www\.bilibili\.com)\/video\/([\w\d]+)?(\?\S+)?/i,
|
||||
],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
`https://player.bilibili.com/player.html?bvid=${matches[2]}&page=1&high_quality=1`,
|
||||
icon: <Img src="/images/bilibili.png" alt="Bilibili" />,
|
||||
component: Bilibili,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Canva",
|
||||
keywords: "design",
|
||||
regexMatch: [
|
||||
/^https:\/\/(?:www\.)?canva\.com\/design\/([a-zA-Z0-9]*)\/(.*)$/,
|
||||
],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
`https://www.canva.com/design/${matches[1]}/view?embed`,
|
||||
icon: <Img src="/images/canva.png" alt="Canva" />,
|
||||
component: Canva,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Cawemo",
|
||||
keywords: "bpmn process",
|
||||
defaultHidden: true,
|
||||
regexMatch: [new RegExp("^https?://cawemo.com/(?:share|embed)/(.*)$")],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
`https://cawemo.com/embed/${matches[1]}`,
|
||||
icon: <Img src="/images/cawemo.png" alt="Cawemo" />,
|
||||
component: Cawemo,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "ClickUp",
|
||||
keywords: "project",
|
||||
regexMatch: [
|
||||
new RegExp("^https?://share\\.clickup\\.com/[a-z]/[a-z]/(.*)/(.*)$"),
|
||||
new RegExp(
|
||||
"^https?://sharing\\.clickup\\.com/[0-9]+/[a-z]/[a-z]/(.*)/(.*)$"
|
||||
),
|
||||
],
|
||||
transformMatch: (matches: RegExpMatchArray) => matches[0],
|
||||
icon: <Img src="/images/clickup.png" alt="ClickUp" />,
|
||||
component: ClickUp,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Codepen",
|
||||
keywords: "code editor",
|
||||
regexMatch: [new RegExp("^https://codepen.io/(.*?)/(pen|embed)/(.*)$")],
|
||||
transformMatch: (matches) =>
|
||||
`https://codepen.io/${matches[1]}/embed/${matches[3]}`,
|
||||
icon: <Img src="/images/codepen.png" alt="Codepen" />,
|
||||
component: Codepen,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "DBDiagram",
|
||||
keywords: "diagrams database",
|
||||
regexMatch: [new RegExp("^https://dbdiagram.io/(embed|d)/(\\w+)$")],
|
||||
transformMatch: (matches) => `https://dbdiagram.io/embed/${matches[2]}`,
|
||||
icon: <Img src="/images/dbdiagram.png" alt="DBDiagram" />,
|
||||
component: DBDiagram,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Diagrams.net",
|
||||
name: IntegrationService.Diagrams,
|
||||
keywords: "diagrams drawio",
|
||||
regexMatch: [/^https:\/\/viewer\.diagrams\.net\/(?!proxy).*(title=\\w+)?/],
|
||||
icon: <Img src="/images/diagrams.png" alt="Diagrams.net" />,
|
||||
component: Diagrams,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Descript",
|
||||
keywords: "audio",
|
||||
regexMatch: [new RegExp("^https?://share\\.descript\\.com/view/(\\w+)$")],
|
||||
transformMatch: (matches) =>
|
||||
`https://share.descript.com/embed/${matches[1]}`,
|
||||
icon: <Img src="/images/descript.png" alt="Descript" />,
|
||||
component: Descript,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Figma",
|
||||
keywords: "design svg vector",
|
||||
regexMatch: [
|
||||
new RegExp(
|
||||
"^https://([w.-]+\\.)?figma\\.com/(file|proto)/([0-9a-zA-Z]{22,128})(?:/.*)?$"
|
||||
),
|
||||
],
|
||||
transformMatch: (matches) =>
|
||||
`https://www.figma.com/embed?embed_host=outline&url=${encodeURIComponent(
|
||||
matches[0]
|
||||
)}`,
|
||||
icon: <Img src="/images/figma.png" alt="Figma" />,
|
||||
component: Figma,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Framer",
|
||||
keywords: "design prototyping",
|
||||
regexMatch: [new RegExp("^https://framer.cloud/(.*)$")],
|
||||
transformMatch: (matches) => matches[0],
|
||||
icon: <Img src="/images/framer.png" alt="Framer" />,
|
||||
component: Framer,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "GitHub Gist",
|
||||
keywords: "code",
|
||||
regexMatch: [
|
||||
new RegExp(
|
||||
"^https://gist\\.github\\.com/([a-zA-Z\\d](?:[a-zA-Z\\d]|-(?=[a-zA-Z\\d])){0,38})/(.*)$"
|
||||
),
|
||||
],
|
||||
icon: <Img src="/images/github-gist.png" alt="GitHub" />,
|
||||
component: Gist,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "GitLab Snippet",
|
||||
keywords: "code",
|
||||
regexMatch: [
|
||||
new RegExp(`^https://gitlab\\.com/(([a-zA-Z\\d-]+)/)*-/snippets/\\d+$`),
|
||||
],
|
||||
icon: <Img src="/images/gitlab.png" alt="GitLab" />,
|
||||
component: GitLabSnippet,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Gliffy",
|
||||
keywords: "diagram",
|
||||
regexMatch: [new RegExp("https?://go\\.gliffy\\.com/go/share/(.*)$")],
|
||||
transformMatch: (matches: RegExpMatchArray) => matches[0],
|
||||
icon: <Img src="/images/gliffy.png" alt="Gliffy" />,
|
||||
component: Gliffy,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Diagrams.net",
|
||||
keywords: "diagrams drawio",
|
||||
icon: <Img src="/images/diagrams.png" alt="Diagrams.net" />,
|
||||
component: Diagrams,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Maps",
|
||||
keywords: "maps",
|
||||
regexMatch: [new RegExp("^https?://www\\.google\\.com/maps/embed\\?(.*)$")],
|
||||
transformMatch: (matches: RegExpMatchArray) => matches[0],
|
||||
icon: <Img src="/images/google-maps.png" alt="Google Maps" />,
|
||||
component: GoogleMaps,
|
||||
visible: true,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Drawings",
|
||||
keywords: "drawings",
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
matches[0].replace("/edit", "/preview"),
|
||||
regexMatch: [
|
||||
new RegExp(
|
||||
"^https://docs\\.google\\.com/drawings/d/(.*)/(edit|preview)(.*)$"
|
||||
),
|
||||
],
|
||||
icon: <Img src="/images/google-drawings.png" alt="Google Drawings" />,
|
||||
component: GoogleDrawings,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Drive",
|
||||
keywords: "drive",
|
||||
regexMatch: [new RegExp("^https?://drive\\.google\\.com/file/d/(.*)$")],
|
||||
transformMatch: (matches) =>
|
||||
matches[0].replace("/view", "/preview").replace("/edit", "/preview"),
|
||||
icon: <Img src="/images/google-drive.png" alt="Google Drive" />,
|
||||
component: GoogleDrive,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Docs",
|
||||
keywords: "documents word",
|
||||
regexMatch: [new RegExp("^https?://docs\\.google\\.com/document/(.*)$")],
|
||||
transformMatch: (matches) =>
|
||||
matches[0].replace("/view", "/preview").replace("/edit", "/preview"),
|
||||
icon: <Img src="/images/google-docs.png" alt="Google Docs" />,
|
||||
component: GoogleDocs,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Sheets",
|
||||
keywords: "excel spreadsheet",
|
||||
regexMatch: [
|
||||
new RegExp("^https?://docs\\.google\\.com/spreadsheets/d/(.*)$"),
|
||||
],
|
||||
transformMatch: (matches) =>
|
||||
matches[0].replace("/view", "/preview").replace("/edit", "/preview"),
|
||||
icon: <Img src="/images/google-sheets.png" alt="Google Sheets" />,
|
||||
component: GoogleSheets,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Slides",
|
||||
keywords: "presentation slideshow",
|
||||
regexMatch: [
|
||||
new RegExp("^https?://docs\\.google\\.com/presentation/d/(.*)$"),
|
||||
],
|
||||
transformMatch: (matches) =>
|
||||
matches[0].replace("/edit", "/preview").replace("/pub", "/embed"),
|
||||
icon: <Img src="/images/google-slides.png" alt="Google Slides" />,
|
||||
component: GoogleSlides,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Calendar",
|
||||
keywords: "calendar",
|
||||
regexMatch: [
|
||||
new RegExp(
|
||||
"^https?://calendar\\.google\\.com/calendar/embed\\?src=(.*)$"
|
||||
),
|
||||
],
|
||||
transformMatch: (matches: RegExpMatchArray) => matches[0],
|
||||
icon: <Img src="/images/google-calendar.png" alt="Google Calendar" />,
|
||||
component: GoogleCalendar,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Looker Studio",
|
||||
keywords: "bi business intelligence",
|
||||
icon: (
|
||||
<Img src="/images/google-lookerstudio.png" alt="Google Looker Studio" />
|
||||
),
|
||||
component: GoogleLookerStudio,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Forms",
|
||||
keywords: "form survey",
|
||||
regexMatch: [new RegExp("^https?://docs\\.google\\.com/forms/d/(.+)$")],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
matches[0].replace(
|
||||
/\/(edit|viewform)(\?.+)?$/,
|
||||
"/viewform?embedded=true"
|
||||
),
|
||||
icon: <Img src="/images/google-forms.png" alt="Google Forms" />,
|
||||
component: GoogleForms,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Looker Studio",
|
||||
keywords: "bi business intelligence",
|
||||
regexMatch: [
|
||||
new RegExp(
|
||||
"^https?://(lookerstudio|datastudio)\\.google\\.com/(embed|u/0)/reporting/(.*)/page/(.*)(/edit)?$"
|
||||
),
|
||||
],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
matches[0].replace("u/0", "embed").replace("/edit", ""),
|
||||
icon: (
|
||||
<Img src="/images/google-lookerstudio.png" alt="Google Looker Studio" />
|
||||
),
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Grist",
|
||||
name: IntegrationService.Grist,
|
||||
keywords: "spreadsheet",
|
||||
regexMatch: [new RegExp("^https?://([a-z.-]+\\.)?getgrist\\.com/(.+)$")],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
matches[0].replace(/(\?embed=true)?$/, "?embed=true"),
|
||||
icon: <Img src="/images/grist.png" alt="Grist" />,
|
||||
component: Grist,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Instagram",
|
||||
keywords: "post",
|
||||
regexMatch: [
|
||||
/^https?:\/\/www\.instagram\.com\/(p|reel)\/([\w-]+)(\/?utm_source=\w+)?/,
|
||||
],
|
||||
transformMatch: (matches: RegExpMatchArray) => `${matches[0]}/embed`,
|
||||
icon: <Img src="/images/instagram.png" alt="Instagram" />,
|
||||
component: Instagram,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "InVision",
|
||||
keywords: "design prototype",
|
||||
defaultHidden: true,
|
||||
regexMatch: [
|
||||
/^https:\/\/(invis\.io\/.*)|(projects\.invisionapp\.com\/share\/.*)$/,
|
||||
/^https:\/\/(opal\.invisionapp\.com\/static-signed\/live-embed\/.*)$/,
|
||||
],
|
||||
icon: <Img src="/images/invision.png" alt="InVision" />,
|
||||
component: InVision,
|
||||
}),
|
||||
@@ -310,6 +385,7 @@ const embeds: EmbedDescriptor[] = [
|
||||
title: "JSFiddle",
|
||||
keywords: "code",
|
||||
defaultHidden: true,
|
||||
regexMatch: [new RegExp("^https?://jsfiddle\\.net/(.*)/(.*)$")],
|
||||
icon: <Img src="/images/jsfiddle.png" alt="JSFiddle" />,
|
||||
component: JSFiddle,
|
||||
}),
|
||||
@@ -317,117 +393,176 @@ const embeds: EmbedDescriptor[] = [
|
||||
title: "LinkedIn",
|
||||
keywords: "post",
|
||||
defaultHidden: true,
|
||||
regexMatch: [
|
||||
/^https:\/\/www\.linkedin\.com\/(?:posts\/.*-(ugcPost|activity)-(\d+)-.*|(embed)\/(?:feed\/update\/urn:li:(?:ugcPost|share):(?:\d+)))/,
|
||||
],
|
||||
icon: <Img src="/images/linkedin.png" alt="LinkedIn" />,
|
||||
component: Linkedin,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Loom",
|
||||
keywords: "video screencast",
|
||||
regexMatch: [/^https:\/\/(www\.)?(use)?loom\.com\/(embed|share)\/(.*)$/],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
matches[0].replace("share", "embed"),
|
||||
icon: <Img src="/images/loom.png" alt="Loom" />,
|
||||
component: Loom,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Lucidchart",
|
||||
keywords: "chart",
|
||||
regexMatch: [
|
||||
/^https?:\/\/(www\.|app\.)?(lucidchart\.com|lucid\.app)\/documents\/(embeddedchart|view|edit)\/(?<chartId>[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})(?:.*)?$/,
|
||||
/^https?:\/\/(www\.|app\.)?(lucid\.app|lucidchart\.com)\/lucidchart\/(?<chartId>[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\/(embeddedchart|view|edit)(?:.*)?$/,
|
||||
],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
`https://lucidchart.com/documents/embeddedchart/${matches.groups?.chartId}`,
|
||||
icon: <Img src="/images/lucidchart.png" alt="Lucidchart" />,
|
||||
component: Lucidchart,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Marvel",
|
||||
keywords: "design prototype",
|
||||
regexMatch: [new RegExp("^https://marvelapp\\.com/([A-Za-z0-9-]{6})/?$")],
|
||||
transformMatch: (matches: RegExpMatchArray) => matches[0],
|
||||
icon: <Img src="/images/marvel.png" alt="Marvel" />,
|
||||
component: Marvel,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Mindmeister",
|
||||
keywords: "mindmap",
|
||||
regexMatch: [
|
||||
new RegExp(
|
||||
"^https://([w.-]+\\.)?(mindmeister\\.com|mm\\.tt)(/maps/public_map_shell)?/(\\d+)(\\?t=.*)?(/.*)?$"
|
||||
),
|
||||
],
|
||||
transformMatch: (matches: RegExpMatchArray) => {
|
||||
const chartId = matches[4] + (matches[5] || "") + (matches[6] || "");
|
||||
return `https://www.mindmeister.com/maps/public_map_shell/${chartId}`;
|
||||
},
|
||||
icon: <Img src="/images/mindmeister.png" alt="Mindmeister" />,
|
||||
component: Mindmeister,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Miro",
|
||||
keywords: "whiteboard",
|
||||
regexMatch: [/^https:\/\/(realtimeboard|miro)\.com\/app\/board\/(.*)$/],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
`https://${matches[1]}.com/app/embed/${matches[2]}`,
|
||||
icon: <Img src="/images/miro.png" alt="Miro" />,
|
||||
component: Miro,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Mode",
|
||||
keywords: "analytics",
|
||||
defaultHidden: true,
|
||||
regexMatch: [
|
||||
new RegExp("^https://([w.-]+\\.)?modeanalytics\\.com/(.*)/reports/(.*)$"),
|
||||
],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
`${matches[0].replace(/\/embed$/, "")}/embed`,
|
||||
icon: <Img src="/images/mode-analytics.png" alt="Mode" />,
|
||||
component: ModeAnalytics,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Otter.ai",
|
||||
keywords: "audio transcription meeting notes",
|
||||
defaultHidden: true,
|
||||
regexMatch: [new RegExp("^https?://otter\\.ai/[su]/(.*)$")],
|
||||
transformMatch: (matches: RegExpMatchArray) => matches[0],
|
||||
icon: <Img src="/images/otter.png" alt="Otter.ai" />,
|
||||
component: Otter,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Pitch",
|
||||
keywords: "presentation",
|
||||
defaultHidden: true,
|
||||
regexMatch: [
|
||||
new RegExp(
|
||||
"^https?://app\\.pitch\\.com/app/(?:presentation/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|public/player)/(.*)$"
|
||||
),
|
||||
new RegExp("^https?://pitch\\.com/embed/(.*)$"),
|
||||
],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
`https://pitch.com/embed/${matches[1]}`,
|
||||
icon: <Img src="/images/pitch.png" alt="Pitch" />,
|
||||
component: Pitch,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Prezi",
|
||||
keywords: "presentation",
|
||||
regexMatch: [new RegExp("^https://prezi\\.com/view/(.*)$")],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
`${matches[0].replace(/\/embed$/, "")}/embed`,
|
||||
icon: <Img src="/images/prezi.png" alt="Prezi" />,
|
||||
component: Prezi,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Scribe",
|
||||
keywords: "screencast",
|
||||
regexMatch: [/^https?:\/\/scribehow\.com\/shared\/(.*)$/],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
`https://scribehow.com/embed/${matches[1]}`,
|
||||
icon: <Img src="/images/scribe.png" alt="Scribe" />,
|
||||
component: Scribe,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Spotify",
|
||||
keywords: "music",
|
||||
regexMatch: [new RegExp("^https?://open\\.spotify\\.com/(.*)$")],
|
||||
icon: <Img src="/images/spotify.png" alt="Spotify" />,
|
||||
component: Spotify,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Tldraw",
|
||||
keywords: "draw schematics diagrams",
|
||||
regexMatch: [
|
||||
new RegExp("^https?://(beta|www|old)\\.tldraw\\.com/[rsv]/(.*)"),
|
||||
],
|
||||
transformMatch: (matches: RegExpMatchArray) => matches[0],
|
||||
icon: <Img src="/images/tldraw.png" alt="Tldraw" />,
|
||||
component: Tldraw,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Trello",
|
||||
keywords: "kanban",
|
||||
regexMatch: [/^https:\/\/trello\.com\/(c|b)\/([^/]*)(.*)?$/],
|
||||
icon: <Img src="/images/trello.png" alt="Trello" />,
|
||||
component: Trello,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Typeform",
|
||||
keywords: "form survey",
|
||||
regexMatch: [
|
||||
new RegExp(
|
||||
"^https://([A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)\\.typeform\\.com/to/(.*)$"
|
||||
),
|
||||
],
|
||||
transformMatch: (matches: RegExpMatchArray) => matches[0],
|
||||
icon: <Img src="/images/typeform.png" alt="Typeform" />,
|
||||
component: Typeform,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Valtown",
|
||||
keywords: "code",
|
||||
regexMatch: [/^https?:\/\/(?:www.)?val\.town\/(?:v|embed)\/(.*)$/],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
`https://www.val.town/embed/${matches[1]}`,
|
||||
icon: <Img src="/images/valtown.png" alt="Valtown" />,
|
||||
component: Valtown,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Vimeo",
|
||||
keywords: "video",
|
||||
regexMatch: [
|
||||
/(http|https)?:\/\/(www\.)?vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|)(\d+)(?:\/|\?)?([\d\w]+)?/,
|
||||
],
|
||||
icon: <Img src="/images/vimeo.png" alt="Vimeo" />,
|
||||
component: Vimeo,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Whimsical",
|
||||
keywords: "whiteboard",
|
||||
regexMatch: [
|
||||
/^https?:\/\/whimsical\.com\/[0-9a-zA-Z-_~]*-([a-zA-Z0-9]+)\/?$/,
|
||||
],
|
||||
transformMatch: (matches: RegExpMatchArray) =>
|
||||
`https://whimsical.com/embed/${matches[1]}`,
|
||||
icon: <Img src="/images/whimsical.png" alt="Whimsical" />,
|
||||
component: Whimsical,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "YouTube",
|
||||
keywords: "google video",
|
||||
regexMatch: [
|
||||
/(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([a-zA-Z0-9_-]{11})([\&\?](.*))?$/i,
|
||||
],
|
||||
icon: <Img src="/images/youtube.png" alt="YouTube" />,
|
||||
component: YouTube,
|
||||
}),
|
||||
|
||||
15
shared/editor/lib/embeds.ts
Normal file
15
shared/editor/lib/embeds.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { EmbedDescriptor } from "../embeds";
|
||||
|
||||
export function getMatchingEmbed(
|
||||
embeds: EmbedDescriptor[],
|
||||
href: string
|
||||
): { embed: EmbedDescriptor; matches: RegExpMatchArray } | undefined {
|
||||
for (const e of embeds) {
|
||||
const matches = e.matcher(href);
|
||||
if (matches) {
|
||||
return { embed: e, matches };
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { EmbedDescriptor } from "../embeds";
|
||||
import filterExcessSeparators from "./filterExcessSeparators";
|
||||
|
||||
const embedDescriptor = {
|
||||
const embedDescriptor = new EmbedDescriptor({
|
||||
title: "Test",
|
||||
icon: () => null,
|
||||
matcher: () => true,
|
||||
component: () => null,
|
||||
};
|
||||
});
|
||||
|
||||
describe("filterExcessSeparators", () => {
|
||||
test("filter hanging end separators", () => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user