diff --git a/app/hooks/useEmbeds.ts b/app/hooks/useEmbeds.ts index 5526b9369..56482a999 100644 --- a/app/hooks/useEmbeds.ts +++ b/app/hooks/useEmbeds.ts @@ -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 | 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 | undefined = + find( + integrations.orderedData, + (integration) => integration.service === e.name + ); + + if (integration?.settings) { + e.settings = integration.settings; + } + + return e; }), [integrations.orderedData] ); diff --git a/shared/editor/components/DisabledEmbed.tsx b/shared/editor/components/DisabledEmbed.tsx index 4566b5818..28f021b3f 100644 --- a/shared/editor/components/DisabledEmbed.tsx +++ b/shared/editor/components/DisabledEmbed.tsx @@ -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) { +export default function DisabledEmbed( + props: Omit & ThemeProps +) { return ( { + const cache = React.useMemo( + () => getMatchingEmbed(embeds, node.attrs.href), + [embeds, node.attrs.href] + ); + + if (!cache) { + return null; + } + + const { embed, matches } = cache; + + if (embedsDisabled) { + return ( + + ); + } + + if (embed.transformMatch) { + const src = embed.transformMatch(matches); + return ( + + ); + } + + if ("component" in embed) { + return ( + // @ts-expect-error Component type + + ); + } + + return null; +}; + +export default EmbedComponent; diff --git a/shared/editor/components/Styles.ts b/shared/editor/components/Styles.ts index b5184dcd3..7781f911d 100644 --- a/shared/editor/components/Styles.ts +++ b/shared/editor/components/Styles.ts @@ -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; diff --git a/shared/editor/embeds/Abstract.test.ts b/shared/editor/embeds/Abstract.test.ts deleted file mode 100644 index e70d352d1..000000000 --- a/shared/editor/embeds/Abstract.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Abstract.tsx b/shared/editor/embeds/Abstract.tsx deleted file mode 100644 index f0437d8b7..000000000 --- a/shared/editor/embeds/Abstract.tsx +++ /dev/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 ( - - ); -} - -Abstract.ENABLED = [ - new RegExp("^https?://share\\.(?:go)?abstract\\.com/(.*)$"), - new RegExp("^https?://app\\.(?:go)?abstract\\.com/(?:share|embed)/(.*)$"), -]; - -export default Abstract; diff --git a/shared/editor/embeds/Airtable.test.ts b/shared/editor/embeds/Airtable.test.ts deleted file mode 100644 index 4b8c0c507..000000000 --- a/shared/editor/embeds/Airtable.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Airtable.tsx b/shared/editor/embeds/Airtable.tsx deleted file mode 100644 index f00ae0f1d..000000000 --- a/shared/editor/embeds/Airtable.tsx +++ /dev/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 ( - - ); -} - -Airtable.ENABLED = [URL_REGEX]; - -export default Airtable; diff --git a/shared/editor/embeds/Berrycast.test.ts b/shared/editor/embeds/Berrycast.test.ts deleted file mode 100644 index cd22f8d62..000000000 --- a/shared/editor/embeds/Berrycast.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import Berrycast from "./Berrycast"; - -describe("Berrycast", () => { - Berrycast.ENABLED[0]; - - // TODO - test.todo(""); -}); diff --git a/shared/editor/embeds/Berrycast.tsx b/shared/editor/embeds/Berrycast.tsx index befad4cdc..8a7881ece 100644 --- a/shared/editor/embeds/Berrycast.tsx +++ b/shared/editor/embeds/Berrycast.tsx @@ -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(null); const { width } = useComponentSize(ref.current); @@ -23,5 +21,3 @@ export default function Berrycast(props: Props) { ); } - -Berrycast.ENABLED = [URL_REGEX]; diff --git a/shared/editor/embeds/Bilibili.test.ts b/shared/editor/embeds/Bilibili.test.ts deleted file mode 100644 index 74c6a4864..000000000 --- a/shared/editor/embeds/Bilibili.test.ts +++ /dev/null @@ -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 - ); - }); -}); diff --git a/shared/editor/embeds/Bilibili.tsx b/shared/editor/embeds/Bilibili.tsx deleted file mode 100644 index ac9d92117..000000000 --- a/shared/editor/embeds/Bilibili.tsx +++ /dev/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 ( - - ); -} - -Bilibili.ENABLED = [URL_REGEX]; diff --git a/shared/editor/embeds/Canva.tsx b/shared/editor/embeds/Canva.tsx deleted file mode 100644 index 99f187c60..000000000 --- a/shared/editor/embeds/Canva.tsx +++ /dev/null @@ -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 ( - - ); -} - -Canva.ENABLED = [ - /^https:\/\/(?:www\.)?canva\.com\/design\/([a-zA-Z0-9]*)\/(.*)$/, -]; - -export default Canva; diff --git a/shared/editor/embeds/Cawemo.test.ts b/shared/editor/embeds/Cawemo.test.ts deleted file mode 100644 index 88bc35c9b..000000000 --- a/shared/editor/embeds/Cawemo.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Cawemo.tsx b/shared/editor/embeds/Cawemo.tsx deleted file mode 100644 index f033d0eba..000000000 --- a/shared/editor/embeds/Cawemo.tsx +++ /dev/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 ( - - ); -} - -Cawemo.ENABLED = [URL_REGEX]; diff --git a/shared/editor/embeds/ClickUp.test.ts b/shared/editor/embeds/ClickUp.test.ts deleted file mode 100644 index 6b5f3645b..000000000 --- a/shared/editor/embeds/ClickUp.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/ClickUp.tsx b/shared/editor/embeds/ClickUp.tsx deleted file mode 100644 index 53edd2144..000000000 --- a/shared/editor/embeds/ClickUp.tsx +++ /dev/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 ; -} - -ClickUp.ENABLED = [ - new RegExp("^https?://share\\.clickup\\.com/[a-z]/[a-z]/(.*)/(.*)$"), - new RegExp("^https?://sharing\\.clickup\\.com/[0-9]+/[a-z]/[a-z]/(.*)/(.*)$"), -]; diff --git a/shared/editor/embeds/Codepen.test.ts b/shared/editor/embeds/Codepen.test.ts deleted file mode 100644 index 4e1f6d14c..000000000 --- a/shared/editor/embeds/Codepen.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Codepen.tsx b/shared/editor/embeds/Codepen.tsx deleted file mode 100644 index 3851f1a65..000000000 --- a/shared/editor/embeds/Codepen.tsx +++ /dev/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 ; -} - -Codepen.ENABLED = [URL_REGEX]; diff --git a/shared/editor/embeds/DBDiagram.test.ts b/shared/editor/embeds/DBDiagram.test.ts deleted file mode 100644 index 42665486c..000000000 --- a/shared/editor/embeds/DBDiagram.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import DBDiagram from "./DBDiagram"; - -describe("DBDiagram", () => { - DBDiagram.ENABLED[0]; - - // TODO - test.todo(""); -}); diff --git a/shared/editor/embeds/DBDiagram.tsx b/shared/editor/embeds/DBDiagram.tsx deleted file mode 100644 index d5aa4cc77..000000000 --- a/shared/editor/embeds/DBDiagram.tsx +++ /dev/null @@ -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 ( - - ); -} - -DBDiagram.ENABLED = [new RegExp("^https://dbdiagram.io/(embed|d)/(\\w+)$")]; diff --git a/shared/editor/embeds/Descript.test.ts b/shared/editor/embeds/Descript.test.ts deleted file mode 100644 index 0bf1610c8..000000000 --- a/shared/editor/embeds/Descript.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Descript.tsx b/shared/editor/embeds/Descript.tsx deleted file mode 100644 index 2d25453c2..000000000 --- a/shared/editor/embeds/Descript.tsx +++ /dev/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 ( - - ); -} - -Descript.ENABLED = [ - new RegExp("^https?://share\\.descript\\.com/view/(\\w+)$"), -]; diff --git a/shared/editor/embeds/Diagrams.test.ts b/shared/editor/embeds/Diagrams.test.ts deleted file mode 100644 index 1411ab9ef..000000000 --- a/shared/editor/embeds/Diagrams.test.ts +++ /dev/null @@ -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 - ); - }); -}); diff --git a/shared/editor/embeds/Diagrams.tsx b/shared/editor/embeds/Diagrams.tsx index 484340f36..afaea633b 100644 --- a/shared/editor/embeds/Diagrams.tsx +++ b/shared/editor/embeds/Diagrams.tsx @@ -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; diff --git a/shared/editor/embeds/Figma.test.ts b/shared/editor/embeds/Figma.test.ts deleted file mode 100644 index 1e915d6cd..000000000 --- a/shared/editor/embeds/Figma.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Figma.tsx b/shared/editor/embeds/Figma.tsx deleted file mode 100644 index 4c699da42..000000000 --- a/shared/editor/embeds/Figma.tsx +++ /dev/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 ( - - ); -} - -Figma.ENABLED = [ - new RegExp( - "^https://([w.-]+\\.)?figma\\.com/(file|proto)/([0-9a-zA-Z]{22,128})(?:/.*)?$" - ), -]; - -export default Figma; diff --git a/shared/editor/embeds/Framer.test.ts b/shared/editor/embeds/Framer.test.ts deleted file mode 100644 index 9ae8cde4b..000000000 --- a/shared/editor/embeds/Framer.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Framer.tsx b/shared/editor/embeds/Framer.tsx deleted file mode 100644 index a14e5c179..000000000 --- a/shared/editor/embeds/Framer.tsx +++ /dev/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 ( - - ); -} - -Framer.ENABLED = [new RegExp("^https://framer.cloud/(.*)$")]; - -export default Framer; diff --git a/shared/editor/embeds/Gist.test.ts b/shared/editor/embeds/Gist.test.ts deleted file mode 100644 index 314d3f941..000000000 --- a/shared/editor/embeds/Gist.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Gist.tsx b/shared/editor/embeds/Gist.tsx index 75de907d6..265381e2e 100644 --- a/shared/editor/embeds/Gist.tsx +++ b/shared/editor/embeds/Gist.tsx @@ -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; diff --git a/shared/editor/embeds/GitLabSnippet.test.ts b/shared/editor/embeds/GitLabSnippet.test.ts deleted file mode 100644 index fbd1d5b90..000000000 --- a/shared/editor/embeds/GitLabSnippet.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/GitLabSnippet.tsx b/shared/editor/embeds/GitLabSnippet.tsx index 05bb38883..4b74c8911 100644 --- a/shared/editor/embeds/GitLabSnippet.tsx +++ b/shared/editor/embeds/GitLabSnippet.tsx @@ -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; diff --git a/shared/editor/embeds/Gliffy.test.ts b/shared/editor/embeds/Gliffy.test.ts deleted file mode 100644 index fe72fc4ff..000000000 --- a/shared/editor/embeds/Gliffy.test.ts +++ /dev/null @@ -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 - ); - }); -}); diff --git a/shared/editor/embeds/Gliffy.tsx b/shared/editor/embeds/Gliffy.tsx deleted file mode 100644 index 56e9132d1..000000000 --- a/shared/editor/embeds/Gliffy.tsx +++ /dev/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 ( - - ); -} - -Gliffy.ENABLED = [new RegExp("https?://go\\.gliffy\\.com/go/share/(.*)$")]; - -export default Gliffy; diff --git a/shared/editor/embeds/GoogleCalendar.test.ts b/shared/editor/embeds/GoogleCalendar.test.ts deleted file mode 100644 index 2d981a4c2..000000000 --- a/shared/editor/embeds/GoogleCalendar.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/GoogleCalendar.tsx b/shared/editor/embeds/GoogleCalendar.tsx deleted file mode 100644 index c978f20fa..000000000 --- a/shared/editor/embeds/GoogleCalendar.tsx +++ /dev/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 ( - - ); -} - -GoogleCalendar.ENABLED = [ - new RegExp("^https?://calendar\\.google\\.com/calendar/embed\\?src=(.*)$"), -]; - -export default GoogleCalendar; diff --git a/shared/editor/embeds/GoogleDocs.test.ts b/shared/editor/embeds/GoogleDocs.test.ts deleted file mode 100644 index ba1cd758b..000000000 --- a/shared/editor/embeds/GoogleDocs.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/GoogleDocs.tsx b/shared/editor/embeds/GoogleDocs.tsx deleted file mode 100644 index 4cd833ff6..000000000 --- a/shared/editor/embeds/GoogleDocs.tsx +++ /dev/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 ( - - } - canonicalUrl={props.attrs.href} - title="Google Docs" - border - /> - ); -} - -GoogleDocs.ENABLED = [ - new RegExp("^https?://docs\\.google\\.com/document/(.*)$"), -]; - -export default GoogleDocs; diff --git a/shared/editor/embeds/GoogleDrawings.test.ts b/shared/editor/embeds/GoogleDrawings.test.ts deleted file mode 100644 index 40a94e1b7..000000000 --- a/shared/editor/embeds/GoogleDrawings.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/GoogleDrawings.tsx b/shared/editor/embeds/GoogleDrawings.tsx deleted file mode 100644 index 3f1659092..000000000 --- a/shared/editor/embeds/GoogleDrawings.tsx +++ /dev/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 ( - - } - 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; diff --git a/shared/editor/embeds/GoogleDrive.test.ts b/shared/editor/embeds/GoogleDrive.test.ts deleted file mode 100644 index 4cc4d1c1b..000000000 --- a/shared/editor/embeds/GoogleDrive.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/GoogleDrive.tsx b/shared/editor/embeds/GoogleDrive.tsx deleted file mode 100644 index e17e4ac5b..000000000 --- a/shared/editor/embeds/GoogleDrive.tsx +++ /dev/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 ( - - } - title="Google Drive" - canonicalUrl={props.attrs.href} - border - /> - ); -} - -GoogleDrive.ENABLED = [ - new RegExp("^https?://drive\\.google\\.com/file/d/(.*)$"), -]; - -export default GoogleDrive; diff --git a/shared/editor/embeds/GoogleForms.test.ts b/shared/editor/embeds/GoogleForms.test.ts deleted file mode 100644 index 24a8a3c0f..000000000 --- a/shared/editor/embeds/GoogleForms.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/GoogleForms.tsx b/shared/editor/embeds/GoogleForms.tsx deleted file mode 100644 index 906824589..000000000 --- a/shared/editor/embeds/GoogleForms.tsx +++ /dev/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 ( - - } - canonicalUrl={props.attrs.href} - title="Google Forms" - border - /> - ); -} - -GoogleForms.ENABLED = [ - new RegExp("^https?://docs\\.google\\.com/forms/d/(.+)$"), -]; - -export default GoogleForms; diff --git a/shared/editor/embeds/GoogleLookerStudio.test.ts b/shared/editor/embeds/GoogleLookerStudio.test.ts deleted file mode 100644 index 0e8709756..000000000 --- a/shared/editor/embeds/GoogleLookerStudio.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/GoogleLookerStudio.tsx b/shared/editor/embeds/GoogleLookerStudio.tsx deleted file mode 100644 index b105889f3..000000000 --- a/shared/editor/embeds/GoogleLookerStudio.tsx +++ /dev/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 ( - - } - 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; diff --git a/shared/editor/embeds/GoogleMaps.test.ts b/shared/editor/embeds/GoogleMaps.test.ts deleted file mode 100644 index 20bf3372e..000000000 --- a/shared/editor/embeds/GoogleMaps.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/GoogleMaps.tsx b/shared/editor/embeds/GoogleMaps.tsx deleted file mode 100644 index 9039f6e0a..000000000 --- a/shared/editor/embeds/GoogleMaps.tsx +++ /dev/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 ; -} - -GoogleMaps.ENABLED = [ - new RegExp("^https?://www\\.google\\.com/maps/embed\\?(.*)$"), -]; - -export default GoogleMaps; diff --git a/shared/editor/embeds/GoogleSheets.test.ts b/shared/editor/embeds/GoogleSheets.test.ts deleted file mode 100644 index e929fc56d..000000000 --- a/shared/editor/embeds/GoogleSheets.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/GoogleSheets.tsx b/shared/editor/embeds/GoogleSheets.tsx deleted file mode 100644 index de0122c38..000000000 --- a/shared/editor/embeds/GoogleSheets.tsx +++ /dev/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 ( - - } - canonicalUrl={props.attrs.href} - title="Google Sheets" - border - /> - ); -} - -GoogleSheets.ENABLED = [ - new RegExp("^https?://docs\\.google\\.com/spreadsheets/d/(.*)$"), -]; - -export default GoogleSheets; diff --git a/shared/editor/embeds/GoogleSlides.test.ts b/shared/editor/embeds/GoogleSlides.test.ts deleted file mode 100644 index 4295a1751..000000000 --- a/shared/editor/embeds/GoogleSlides.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/GoogleSlides.tsx b/shared/editor/embeds/GoogleSlides.tsx deleted file mode 100644 index 067ad5a01..000000000 --- a/shared/editor/embeds/GoogleSlides.tsx +++ /dev/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 ( - - } - canonicalUrl={props.attrs.href} - title="Google Slides" - border - /> - ); -} - -GoogleSlides.ENABLED = [ - new RegExp("^https?://docs\\.google\\.com/presentation/d/(.*)$"), -]; - -export default GoogleSlides; diff --git a/shared/editor/embeds/Grist.test.tsx b/shared/editor/embeds/Grist.test.tsx deleted file mode 100644 index b01bdbb02..000000000 --- a/shared/editor/embeds/Grist.test.tsx +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Grist.tsx b/shared/editor/embeds/Grist.tsx deleted file mode 100644 index ae780178a..000000000 --- a/shared/editor/embeds/Grist.tsx +++ /dev/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 ( - - } - title="Grist Spreadsheet" - canonicalUrl={props.attrs.href} - border - /> - ); -} - -Grist.ENABLED = [new RegExp("^https?://([a-z.-]+\\.)?getgrist\\.com/(.+)$")]; - -Grist.URL_PATH_REGEX = /(.+)/; - -export default Grist; diff --git a/shared/editor/embeds/InVision.test.ts b/shared/editor/embeds/InVision.test.ts deleted file mode 100644 index 88d936d48..000000000 --- a/shared/editor/embeds/InVision.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/InVision.tsx b/shared/editor/embeds/InVision.tsx index 92e2c33b7..d749945ee 100644 --- a/shared/editor/embeds/InVision.tsx +++ b/shared/editor/embeds/InVision.tsx @@ -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 (
@@ -29,6 +24,4 @@ function InVision(props: Props) { return ; } -InVision.ENABLED = [IFRAME_REGEX, IMAGE_REGEX]; - export default InVision; diff --git a/shared/editor/embeds/Instagram.test.ts b/shared/editor/embeds/Instagram.test.ts deleted file mode 100644 index 707b48ff7..000000000 --- a/shared/editor/embeds/Instagram.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Instagram.tsx b/shared/editor/embeds/Instagram.tsx deleted file mode 100644 index 5c18401bc..000000000 --- a/shared/editor/embeds/Instagram.tsx +++ /dev/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 ; -} - -Instagram.ENABLED = [ - /^https?:\/\/www\.instagram\.com\/(p|reel)\/([\w-]+)(\/?utm_source=\w+)?/, -]; - -export default Instagram; diff --git a/shared/editor/embeds/JSFiddle.test.ts b/shared/editor/embeds/JSFiddle.test.ts deleted file mode 100644 index 9917d78e1..000000000 --- a/shared/editor/embeds/JSFiddle.test.ts +++ /dev/null @@ -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 - ); - }); -}); diff --git a/shared/editor/embeds/JSFiddle.tsx b/shared/editor/embeds/JSFiddle.tsx index 8e9a43863..6a9a7b402 100644 --- a/shared/editor/embeds/JSFiddle.tsx +++ b/shared/editor/embeds/JSFiddle.tsx @@ -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; diff --git a/shared/editor/embeds/Linkedin.test.ts b/shared/editor/embeds/Linkedin.test.ts deleted file mode 100644 index bb802e030..000000000 --- a/shared/editor/embeds/Linkedin.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Linkedin.tsx b/shared/editor/embeds/Linkedin.tsx index a5f38798b..fe07c07a6 100644 --- a/shared/editor/embeds/Linkedin.tsx +++ b/shared/editor/embeds/Linkedin.tsx @@ -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; diff --git a/shared/editor/embeds/Loom.test.ts b/shared/editor/embeds/Loom.test.ts deleted file mode 100644 index 5441b804e..000000000 --- a/shared/editor/embeds/Loom.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Loom.tsx b/shared/editor/embeds/Loom.tsx deleted file mode 100644 index 99421b292..000000000 --- a/shared/editor/embeds/Loom.tsx +++ /dev/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 ; -} - -Loom.ENABLED = [/^https:\/\/(www\.)?(use)?loom\.com\/(embed|share)\/(.*)$/]; - -export default Loom; diff --git a/shared/editor/embeds/Lucidchart.test.ts b/shared/editor/embeds/Lucidchart.test.ts deleted file mode 100644 index 4a915b3ed..000000000 --- a/shared/editor/embeds/Lucidchart.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Lucidchart.tsx b/shared/editor/embeds/Lucidchart.tsx deleted file mode 100644 index 9cec8a50d..000000000 --- a/shared/editor/embeds/Lucidchart.tsx +++ /dev/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 ( - - ); -} - -Lucidchart.ENABLED = [ - /^https?:\/\/(www\.|app\.)?(lucidchart\.com|lucid\.app)\/documents\/(embeddedchart|view|edit)\/(?[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\/(?[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; diff --git a/shared/editor/embeds/Marvel.test.ts b/shared/editor/embeds/Marvel.test.ts deleted file mode 100644 index bd7690625..000000000 --- a/shared/editor/embeds/Marvel.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Marvel.tsx b/shared/editor/embeds/Marvel.tsx deleted file mode 100644 index 7f1059b13..000000000 --- a/shared/editor/embeds/Marvel.tsx +++ /dev/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 ( - - ); -} - -Marvel.ENABLED = [new RegExp("^https://marvelapp\\.com/([A-Za-z0-9-]{6})/?$")]; - -export default Marvel; diff --git a/shared/editor/embeds/Mindmeister.test.ts b/shared/editor/embeds/Mindmeister.test.ts deleted file mode 100644 index 6faa19242..000000000 --- a/shared/editor/embeds/Mindmeister.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Mindmeister.tsx b/shared/editor/embeds/Mindmeister.tsx deleted file mode 100644 index 299a0bfc2..000000000 --- a/shared/editor/embeds/Mindmeister.tsx +++ /dev/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 ( - - ); -} - -Mindmeister.ENABLED = [ - new RegExp( - "^https://([w.-]+\\.)?(mindmeister\\.com|mm\\.tt)(/maps/public_map_shell)?/(\\d+)(\\?t=.*)?(/.*)?$" - ), -]; - -export default Mindmeister; diff --git a/shared/editor/embeds/Miro.test.ts b/shared/editor/embeds/Miro.test.ts deleted file mode 100644 index fb6800623..000000000 --- a/shared/editor/embeds/Miro.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Miro.tsx b/shared/editor/embeds/Miro.tsx deleted file mode 100644 index c71a4a47b..000000000 --- a/shared/editor/embeds/Miro.tsx +++ /dev/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 ( - - ); -} - -RealtimeBoard.ENABLED = [ - /^https:\/\/(realtimeboard|miro)\.com\/app\/board\/(.*)$/, -]; - -export default RealtimeBoard; diff --git a/shared/editor/embeds/ModeAnalytics.test.ts b/shared/editor/embeds/ModeAnalytics.test.ts deleted file mode 100644 index 76ab839b1..000000000 --- a/shared/editor/embeds/ModeAnalytics.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/ModeAnalytics.tsx b/shared/editor/embeds/ModeAnalytics.tsx deleted file mode 100644 index d8d6ce305..000000000 --- a/shared/editor/embeds/ModeAnalytics.tsx +++ /dev/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 ( - - ); -} - -ModeAnalytics.ENABLED = [ - new RegExp("^https://([w.-]+\\.)?modeanalytics\\.com/(.*)/reports/(.*)$"), -]; - -export default ModeAnalytics; diff --git a/shared/editor/embeds/Otter.test.ts b/shared/editor/embeds/Otter.test.ts deleted file mode 100644 index 72503d306..000000000 --- a/shared/editor/embeds/Otter.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Otter.tsx b/shared/editor/embeds/Otter.tsx deleted file mode 100644 index d0264c7fe..000000000 --- a/shared/editor/embeds/Otter.tsx +++ /dev/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 ( - - ); -} - -Otter.ENABLED = [new RegExp("^https?://otter\\.ai/[su]/(.*)$")]; - -export default Otter; diff --git a/shared/editor/embeds/Pitch.test.ts b/shared/editor/embeds/Pitch.test.ts deleted file mode 100644 index c07b62f2b..000000000 --- a/shared/editor/embeds/Pitch.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Pitch.tsx b/shared/editor/embeds/Pitch.tsx deleted file mode 100644 index d0177e717..000000000 --- a/shared/editor/embeds/Pitch.tsx +++ /dev/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 ( - - ); -} - -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; diff --git a/shared/editor/embeds/Prezi.test.ts b/shared/editor/embeds/Prezi.test.ts deleted file mode 100644 index fb304f057..000000000 --- a/shared/editor/embeds/Prezi.test.ts +++ /dev/null @@ -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 - ); - }); -}); diff --git a/shared/editor/embeds/Prezi.tsx b/shared/editor/embeds/Prezi.tsx deleted file mode 100644 index 0f27605ab..000000000 --- a/shared/editor/embeds/Prezi.tsx +++ /dev/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 ; -} - -Prezi.ENABLED = [new RegExp("^https://prezi\\.com/view/(.*)$")]; - -export default Prezi; diff --git a/shared/editor/embeds/Scribe.tsx b/shared/editor/embeds/Scribe.tsx deleted file mode 100644 index 54abae908..000000000 --- a/shared/editor/embeds/Scribe.tsx +++ /dev/null @@ -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 ( - - ); -} - -Scribe.ENABLED = [/^https?:\/\/scribehow\.com\/shared\/(.*)$/]; diff --git a/shared/editor/embeds/Spotify.test.ts b/shared/editor/embeds/Spotify.test.ts deleted file mode 100644 index 385e77ef8..000000000 --- a/shared/editor/embeds/Spotify.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Spotify.tsx b/shared/editor/embeds/Spotify.tsx index 3fe315687..e6b4233d8 100644 --- a/shared/editor/embeds/Spotify.tsx +++ b/shared/editor/embeds/Spotify.tsx @@ -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; diff --git a/shared/editor/embeds/Tldraw.test.ts b/shared/editor/embeds/Tldraw.test.ts deleted file mode 100644 index 8e0f59ae8..000000000 --- a/shared/editor/embeds/Tldraw.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Tldraw.tsx b/shared/editor/embeds/Tldraw.tsx deleted file mode 100644 index 7d014e47d..000000000 --- a/shared/editor/embeds/Tldraw.tsx +++ /dev/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 ( - - ); -} - -Tldraw.ENABLED = [ - new RegExp("^https?://(beta|www|old)\\.tldraw\\.com/[rsv]/(.*)"), -]; - -export default Tldraw; diff --git a/shared/editor/embeds/Trello.test.ts b/shared/editor/embeds/Trello.test.ts deleted file mode 100644 index b4fb26d6a..000000000 --- a/shared/editor/embeds/Trello.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Trello.tsx b/shared/editor/embeds/Trello.tsx index 541cf3d8c..e45dc9363 100644 --- a/shared/editor/embeds/Trello.tsx +++ b/shared/editor/embeds/Trello.tsx @@ -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; diff --git a/shared/editor/embeds/Typeform.test.ts b/shared/editor/embeds/Typeform.test.ts deleted file mode 100644 index 7acaa570d..000000000 --- a/shared/editor/embeds/Typeform.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Typeform.tsx b/shared/editor/embeds/Typeform.tsx deleted file mode 100644 index 50700ddb9..000000000 --- a/shared/editor/embeds/Typeform.tsx +++ /dev/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 ; -} - -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; diff --git a/shared/editor/embeds/Valtown.tsx b/shared/editor/embeds/Valtown.tsx deleted file mode 100644 index 586b84999..000000000 --- a/shared/editor/embeds/Valtown.tsx +++ /dev/null @@ -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 ( - - ); -} - -Valtown.ENABLED = [/^https?:\/\/(?:www.)?val\.town\/(?:v|embed)\/(.*)$/]; diff --git a/shared/editor/embeds/Vimeo.test.ts b/shared/editor/embeds/Vimeo.test.ts deleted file mode 100644 index e314403c7..000000000 --- a/shared/editor/embeds/Vimeo.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Vimeo.tsx b/shared/editor/embeds/Vimeo.tsx index 6eb80960e..fbef42725 100644 --- a/shared/editor/embeds/Vimeo.tsx +++ b/shared/editor/embeds/Vimeo.tsx @@ -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; diff --git a/shared/editor/embeds/Whimsical.test.ts b/shared/editor/embeds/Whimsical.test.ts deleted file mode 100644 index e9849042f..000000000 --- a/shared/editor/embeds/Whimsical.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/shared/editor/embeds/Whimsical.tsx b/shared/editor/embeds/Whimsical.tsx deleted file mode 100644 index d95493e7c..000000000 --- a/shared/editor/embeds/Whimsical.tsx +++ /dev/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 ( - - ); -} - -Whimsical.ENABLED = [ - /^https?:\/\/whimsical\.com\/[0-9a-zA-Z-_~]*-([a-zA-Z0-9]+)\/?$/, -]; - -export default Whimsical; diff --git a/shared/editor/embeds/YouTube.test.ts b/shared/editor/embeds/YouTube.test.ts deleted file mode 100644 index 4fd0e1c3a..000000000 --- a/shared/editor/embeds/YouTube.test.ts +++ /dev/null @@ -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 - ); - }); -}); diff --git a/shared/editor/embeds/YouTube.tsx b/shared/editor/embeds/YouTube.tsx index 32d5bd841..fd1fca82f 100644 --- a/shared/editor/embeds/YouTube.tsx +++ b/shared/editor/embeds/YouTube.tsx @@ -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; diff --git a/shared/editor/embeds/index.tsx b/shared/editor/embeds/index.tsx index 31150df91..f0d6fa401 100644 --- a/shared/editor/embeds/index.tsx +++ b/shared/editor/embeds/index.tsx @@ -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; + /** Whether the embed should be visible in menus, always true */ visible?: boolean; - active?: (state: EditorState) => boolean; - component: typeof React.Component | React.FC; + /** + * 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; + /** The integration settings, if any */ settings?: IntegrationSettings; constructor(options: Omit) { @@ -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: 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: 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: 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: 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: 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: 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: 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: 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: DBDiagram, - component: DBDiagram, + }), + new EmbedDescriptor({ + title: "Diagrams.net", + name: IntegrationService.Diagrams, + keywords: "diagrams drawio", + regexMatch: [/^https:\/\/viewer\.diagrams\.net\/(?!proxy).*(title=\\w+)?/], + icon: 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: 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: Figma, - component: Figma, }), new EmbedDescriptor({ title: "Framer", keywords: "design prototyping", + regexMatch: [new RegExp("^https://framer.cloud/(.*)$")], + transformMatch: (matches) => matches[0], icon: 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: GitHub, component: Gist, }), new EmbedDescriptor({ title: "GitLab Snippet", keywords: "code", + regexMatch: [ + new RegExp(`^https://gitlab\\.com/(([a-zA-Z\\d-]+)/)*-/snippets/\\d+$`), + ], icon: GitLab, component: GitLabSnippet, }), new EmbedDescriptor({ title: "Gliffy", keywords: "diagram", + regexMatch: [new RegExp("https?://go\\.gliffy\\.com/go/share/(.*)$")], + transformMatch: (matches: RegExpMatchArray) => matches[0], icon: Gliffy, - component: Gliffy, - }), - new EmbedDescriptor({ - title: "Diagrams.net", - keywords: "diagrams drawio", - icon: 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: 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: 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: 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: 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: 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: 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: Google Calendar, - component: GoogleCalendar, - }), - new EmbedDescriptor({ - title: "Google Looker Studio", - keywords: "bi business intelligence", - icon: ( - 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: 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: ( + 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: 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: 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: InVision, component: InVision, }), @@ -310,6 +385,7 @@ const embeds: EmbedDescriptor[] = [ title: "JSFiddle", keywords: "code", defaultHidden: true, + regexMatch: [new RegExp("^https?://jsfiddle\\.net/(.*)/(.*)$")], icon: 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: 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: Loom, - component: Loom, }), new EmbedDescriptor({ title: "Lucidchart", keywords: "chart", + regexMatch: [ + /^https?:\/\/(www\.|app\.)?(lucidchart\.com|lucid\.app)\/documents\/(embeddedchart|view|edit)\/(?[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\/(?[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: 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: 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: 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: 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: 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: 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: 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: Prezi, - component: Prezi, }), new EmbedDescriptor({ title: "Scribe", keywords: "screencast", + regexMatch: [/^https?:\/\/scribehow\.com\/shared\/(.*)$/], + transformMatch: (matches: RegExpMatchArray) => + `https://scribehow.com/embed/${matches[1]}`, icon: Scribe, - component: Scribe, }), new EmbedDescriptor({ title: "Spotify", keywords: "music", + regexMatch: [new RegExp("^https?://open\\.spotify\\.com/(.*)$")], icon: 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: Tldraw, - component: Tldraw, }), new EmbedDescriptor({ title: "Trello", keywords: "kanban", + regexMatch: [/^https:\/\/trello\.com\/(c|b)\/([^/]*)(.*)?$/], icon: 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: 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: Valtown, - component: Valtown, }), new EmbedDescriptor({ title: "Vimeo", keywords: "video", + regexMatch: [ + /(http|https)?:\/\/(www\.)?vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|)(\d+)(?:\/|\?)?([\d\w]+)?/, + ], icon: 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: 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: YouTube, component: YouTube, }), diff --git a/shared/editor/lib/embeds.ts b/shared/editor/lib/embeds.ts new file mode 100644 index 000000000..b96742528 --- /dev/null +++ b/shared/editor/lib/embeds.ts @@ -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; +} diff --git a/shared/editor/lib/filterExcessSeparators.test.ts b/shared/editor/lib/filterExcessSeparators.test.ts index 8099a92b5..9f4e767e4 100644 --- a/shared/editor/lib/filterExcessSeparators.test.ts +++ b/shared/editor/lib/filterExcessSeparators.test.ts @@ -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", () => { diff --git a/shared/editor/nodes/Embed.tsx b/shared/editor/nodes/Embed.tsx index 0876d2f37..ce64a4e56 100644 --- a/shared/editor/nodes/Embed.tsx +++ b/shared/editor/nodes/Embed.tsx @@ -4,14 +4,14 @@ import { Command } from "prosemirror-state"; import * as React from "react"; import { Primitive } from "utility-types"; import { sanitizeUrl } from "../../utils/urls"; -import DisabledEmbed from "../components/DisabledEmbed"; +import EmbedComponent from "../components/Embed"; +import defaultEmbeds from "../embeds"; +import { getMatchingEmbed } from "../lib/embeds"; import { MarkdownSerializerState } from "../lib/markdown/serializer"; import embedsRule from "../rules/embeds"; import { ComponentProps } from "../types"; import Node from "./Node"; -const cache = {}; - export default class Embed extends Node { get name() { return "embed"; @@ -29,33 +29,56 @@ export default class Embed extends Node { { tag: "iframe", getAttrs: (dom: HTMLIFrameElement) => { - const { embeds } = this.editor.props; - const href = dom.getAttribute("src") || ""; + const embeds = this.editor?.props.embeds ?? defaultEmbeds; + const href = dom.getAttribute("data-canonical-url") || ""; + const response = getMatchingEmbed(embeds, href); - if (embeds) { - for (const embed of embeds) { - const matches = embed.matcher(href); - if (matches) { - return { - href, - }; - } - } + if (response) { + return { + href, + }; } return false; }, }, - ], - toDOM: (node) => [ - "iframe", { - class: "embed", - src: sanitizeUrl(node.attrs.href), - contentEditable: "false", + tag: "a.embed", + getAttrs: (dom: HTMLAnchorElement) => ({ + href: dom.getAttribute("href"), + }), }, - 0, ], + toDOM: (node) => { + const embeds = this.editor?.props.embeds ?? defaultEmbeds; + const response = getMatchingEmbed(embeds, node.attrs.href); + const src = response?.embed.transformMatch?.(response.matches); + + if (src) { + return [ + "iframe", + { + class: "embed", + frameborder: "0", + src: sanitizeUrl(src), + contentEditable: "false", + allowfullscreen: "true", + "data-canonical-url": sanitizeUrl(node.attrs.href), + }, + ]; + } else { + return [ + "a", + { + class: "embed", + href: sanitizeUrl(node.attrs.href), + contentEditable: "false", + "data-canonical-url": sanitizeUrl(node.attrs.href), + }, + response?.embed.title ?? node.attrs.href, + ]; + } + }, toPlainText: (node) => node.attrs.href, }; } @@ -64,52 +87,14 @@ export default class Embed extends Node { return [embedsRule(this.options.embeds)]; } - component({ isEditable, isSelected, theme, node }: ComponentProps) { + component(props: ComponentProps) { const { embeds, embedsDisabled } = this.editor.props; - // matches are cached in module state to avoid re running loops and regex - // here. Unfortunately this function is not compatible with React.memo or - // we would use that instead. - const hit = cache[node.attrs.href]; - let Component = hit ? hit.Component : undefined; - let matches = hit ? hit.matches : undefined; - let embed = hit ? hit.embed : undefined; - - if (!Component) { - for (const e of embeds) { - const m = e.matcher(node.attrs.href); - if (m) { - Component = e.component; - matches = m; - embed = e; - cache[node.attrs.href] = { Component, embed, matches }; - } - } - } - - if (!Component) { - return null; - } - - if (embedsDisabled) { - return ( - - ); - } - return ( - ); }