├── packages ├── webembeds-website │ ├── pages │ │ ├── app.css │ │ ├── _app.tsx │ │ ├── api │ │ │ ├── html.ts │ │ │ └── embed.ts │ │ ├── _document.tsx │ │ ├── test.tsx │ │ └── index.tsx │ ├── .gitignore │ ├── next-env.d.ts │ ├── postcss.config.js │ ├── public │ │ ├── favicons │ │ │ ├── favicon.ico │ │ │ ├── apple-icon.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon-96x96.png │ │ │ ├── ms-icon-144x144.png │ │ │ ├── ms-icon-150x150.png │ │ │ ├── ms-icon-310x310.png │ │ │ ├── ms-icon-70x70.png │ │ │ ├── apple-icon-57x57.png │ │ │ ├── apple-icon-60x60.png │ │ │ ├── apple-icon-72x72.png │ │ │ ├── apple-icon-76x76.png │ │ │ ├── android-icon-144x144.png │ │ │ ├── android-icon-192x192.png │ │ │ ├── android-icon-36x36.png │ │ │ ├── android-icon-48x48.png │ │ │ ├── android-icon-72x72.png │ │ │ ├── android-icon-96x96.png │ │ │ ├── apple-icon-114x114.png │ │ │ ├── apple-icon-120x120.png │ │ │ ├── apple-icon-144x144.png │ │ │ ├── apple-icon-152x152.png │ │ │ ├── apple-icon-180x180.png │ │ │ └── apple-icon-precomposed.png │ │ └── js │ │ │ └── buttons.js │ ├── components │ │ ├── Layout.tsx │ │ └── Loader.tsx │ ├── types.ts │ ├── README.md │ ├── .eslintrc.json │ ├── styles │ │ └── main.css │ ├── package.json │ ├── tsconfig.json │ └── tailwind.config.js └── webembeds-core │ ├── .eslintignore │ ├── .gitignore │ ├── src │ ├── utils │ │ ├── common.ts │ │ ├── providers │ │ │ ├── index.ts │ │ │ ├── codepen.provider.ts │ │ │ ├── loom.provider.ts │ │ │ ├── facebook.provider.ts │ │ │ ├── gist.provider.ts │ │ │ ├── instagram.provider.ts │ │ │ ├── tenor.provider.ts │ │ │ ├── expo.provider.ts │ │ │ ├── giphy.provider.ts │ │ │ ├── glitch.provider.ts │ │ │ ├── snappify.provider.ts │ │ │ ├── opensea.provider.ts │ │ │ └── twitch.provider.ts │ │ ├── graphql.ts │ │ ├── requestHandler.ts │ │ └── html.utils.ts │ ├── index.ts │ ├── modules │ │ ├── Platform.ts │ │ └── WebembedHandler.ts │ └── types.ts │ ├── jest.config.ts │ ├── tsconfig.json │ ├── .eslintrc.json │ ├── webpack.config.js │ ├── README.md │ └── package.json ├── .env.example ├── .vscode └── settings.json ├── package.json ├── README.md ├── example.md └── .gitignore /packages/webembeds-website/pages/app.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/webembeds-core/.eslintignore: -------------------------------------------------------------------------------- 1 | build/** 2 | -------------------------------------------------------------------------------- /packages/webembeds-core/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules -------------------------------------------------------------------------------- /packages/webembeds-website/.gitignore: -------------------------------------------------------------------------------- 1 | .next 2 | node_modules -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | FB_APP_TOKEN= 2 | 3 | HASHNODE_GRAPHQL_URL= 4 | HASHNODE_GRAPHQL_USER_ACCESS_TOKEN= -------------------------------------------------------------------------------- /packages/webembeds-website/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.format.enable": true, 3 | "eslint.debug": true, 4 | "eslint.packageManager": "yarn", 5 | } 6 | -------------------------------------------------------------------------------- /packages/webembeds-website/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/favicon.ico -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/apple-icon.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/ms-icon-144x144.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/ms-icon-150x150.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/ms-icon-310x310.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/ms-icon-70x70.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/apple-icon-57x57.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/apple-icon-60x60.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/apple-icon-72x72.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/apple-icon-76x76.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/android-icon-144x144.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/android-icon-192x192.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/android-icon-36x36.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/android-icon-48x48.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/android-icon-72x72.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/android-icon-96x96.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/apple-icon-114x114.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/apple-icon-120x120.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/apple-icon-144x144.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/apple-icon-152x152.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/apple-icon-180x180.png -------------------------------------------------------------------------------- /packages/webembeds-website/public/favicons/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hashnode/webembeds/HEAD/packages/webembeds-website/public/favicons/apple-icon-precomposed.png -------------------------------------------------------------------------------- /packages/webembeds-website/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface Props {} 4 | 5 | function Layout(props: Props) { 6 | const {} = props 7 | 8 | return ( 9 |
10 | 11 |
12 | ) 13 | } 14 | 15 | export default Layout 16 | -------------------------------------------------------------------------------- /packages/webembeds-website/types.ts: -------------------------------------------------------------------------------- 1 | type EmbedRequest = { 2 | url?: string, 3 | customHost?: string, 4 | } 5 | 6 | type CustomResponse = { 7 | data: {}, 8 | error?: boolean | true, 9 | message?: string | "", 10 | } 11 | 12 | export type { 13 | EmbedRequest, 14 | CustomResponse, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/utils/common.ts: -------------------------------------------------------------------------------- 1 | import type { APIResponse } from "../types"; 2 | 3 | // eslint-disable-next-line import/prefer-default-export 4 | export const apiResponse = ({ data, message, error }: { data: {}, message: "", error: true }): APIResponse => ({ 5 | data: data || null, 6 | message: message || null, 7 | error: error || true, 8 | }); 9 | -------------------------------------------------------------------------------- /packages/webembeds-website/components/Loader.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface Props {} 4 | 5 | function Loader(props: Props) { 6 | const {} = props 7 | 8 | return
9 |
10 |
11 | } 12 | 13 | export default Loader 14 | -------------------------------------------------------------------------------- /packages/webembeds-website/README.md: -------------------------------------------------------------------------------- 1 | # webembeds-website 2 | Built and supported by [Hashnode](https://hashnode.com) 3 | 4 | - Checkout demo here https://webembeds.com/demo 5 | 6 | This package deals with the website. Right now website only hosts a demo of webembeds. 7 | 8 | Built with Next.js 9 | 10 | ## Contributing 11 | Please check this README.md on instructions to contributing. https://github.com/Hashnode/webembeds/blob/master/README.md 12 | -------------------------------------------------------------------------------- /packages/webembeds-core/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/en/configuration.html 4 | */ 5 | 6 | export default { 7 | testEnvironment: 'node', 8 | transform: { 9 | "^.+\\.tsx?$": "ts-jest" 10 | }, 11 | moduleFileExtensions: [ 12 | "ts", 13 | "tsx", 14 | "js", 15 | "jsx", 16 | "json", 17 | "node", 18 | ], 19 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(ts|js)x?$', 20 | }; 21 | -------------------------------------------------------------------------------- /packages/webembeds-website/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { AppProps } from "next/app"; 4 | import Head from "next/head"; 5 | 6 | import "../styles/main.css"; 7 | 8 | // eslint-disable-next-line react/jsx-props-no-spreading 9 | const WebembedsApp = ({ 10 | Component, 11 | pageProps, 12 | }: AppProps) => { 13 | return ( 14 | <> 15 | 16 | Webembeds 17 | 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default WebembedsApp; 24 | -------------------------------------------------------------------------------- /packages/webembeds-website/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "plugin:react/recommended" 8 | ], 9 | "parser": "@typescript-eslint/parser", 10 | "parserOptions": { 11 | "ecmaFeatures": { 12 | "jsx": true 13 | }, 14 | "ecmaVersion": 12 15 | }, 16 | "plugins": [ 17 | "react", 18 | "@typescript-eslint" 19 | ], 20 | "rules": { 21 | "quotes": ["error", "double"], 22 | "react/prop-types": 0 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/webembeds-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noImplicitAny": false, 15 | "outDir": "./build", 16 | "sourceMap": true, 17 | "declaration": true, 18 | }, 19 | "include": ["./src/**/*", "__tests__/**/*"], 20 | } 21 | -------------------------------------------------------------------------------- /packages/webembeds-core/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "jest": true 5 | }, 6 | "extends": ["airbnb-base"], 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { 9 | "ecmaVersion": 12, 10 | "sourceType": "module" 11 | }, 12 | "plugins": ["@typescript-eslint", "jest"], 13 | "rules": { 14 | "quotes": ["error", "double"], 15 | "import/extensions": "off", 16 | "no-undef": "off" 17 | }, 18 | "settings": { 19 | "import/resolver": { 20 | "node": { 21 | "paths": ["src"], 22 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/index.ts: -------------------------------------------------------------------------------- 1 | import WebembedHandler from "./modules/WebembedHandler"; 2 | import type { WebEmbedInitOptions } from "./types"; 3 | 4 | /** 5 | * @param {string} incomingURL 6 | * @param {object} options 7 | */ 8 | function init(incomingURL: string, options?: WebEmbedInitOptions) { 9 | try { 10 | // eslint-disable-next-line no-new 11 | new URL(incomingURL); 12 | } catch (error) { 13 | return { 14 | output: null, 15 | error: true, 16 | }; 17 | } 18 | 19 | const handler = new WebembedHandler(incomingURL, options || {}); 20 | 21 | return handler.generateResponse(); 22 | } 23 | 24 | export default init; 25 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/utils/providers/index.ts: -------------------------------------------------------------------------------- 1 | import * as GithubGist from "./gist.provider"; 2 | import * as ExpoSnack from "./expo.provider"; 3 | import * as Giphy from "./giphy.provider"; 4 | import * as Instagram from "./instagram.provider"; 5 | import * as Twitch from "./twitch.provider"; 6 | import * as Glitch from "./glitch.provider"; 7 | import * as Facebook from "./facebook.provider"; 8 | import * as Opensea from "./opensea.provider"; 9 | import * as Snappify from "./snappify.provider"; 10 | 11 | export default { 12 | GithubGist, 13 | ExpoSnack, 14 | Giphy, 15 | Instagram, 16 | Twitch, 17 | Glitch, 18 | Facebook, 19 | Opensea, 20 | Snappify, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/webembeds-core/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | mode: process.env.NODE_ENV, 5 | entry: "./src/index.ts", 6 | devtool: "inline-source-map", 7 | module: { 8 | rules: [ 9 | { 10 | test: /\.tsx?$/, 11 | use: "ts-loader", 12 | exclude: /node_modules/, 13 | }, 14 | ], 15 | }, 16 | resolve: { 17 | extensions: [".ts", ".js"], 18 | }, 19 | target: "node", 20 | output: { 21 | filename: "bundle.js", 22 | path: path.resolve(__dirname, "build"), 23 | libraryTarget: "umd", // very important line 24 | umdNamedDefine: true, // very important line 25 | globalObject: "this", 26 | library: "webembed", 27 | }, 28 | externals: ["axios", "cheerio"], 29 | }; 30 | -------------------------------------------------------------------------------- /packages/webembeds-website/pages/api/html.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | import webembed from "@webembeds/core"; 3 | 4 | import type { EmbedRequest } from "../../types"; 5 | 6 | export default async function handler(req: NextApiRequest, res: NextApiResponse) { 7 | const { url = "" }: EmbedRequest = req.query; 8 | const embedURL = decodeURIComponent(url); 9 | const embedResponse = await webembed(embedURL); 10 | 11 | res.setHeader("Content-Type", "text/html; charset='utf-8'"); 12 | res.setHeader("Access-Control-Allow-Origin", "*"); 13 | 14 | if (embedResponse.output) { 15 | if (embedResponse.output.html) { 16 | return res.send(`${embedResponse.output.html}`); 17 | } 18 | } 19 | 20 | res.send("Not available"); 21 | } 22 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/utils/providers/codepen.provider.ts: -------------------------------------------------------------------------------- 1 | import Platform from "../../modules/Platform"; 2 | import type { OEmbedResponseType, PlatformType } from "../../types"; 3 | 4 | export default class Giphy extends Platform { 5 | // eslint-disable-next-line no-useless-constructor 6 | constructor(args: PlatformType) { 7 | super(args); 8 | } 9 | 10 | run = async (): Promise => { 11 | const host = "https://codepen.io/"; 12 | let path = this.embedURL.split(host)[1]; 13 | path = path.replace("pen", "embed"); 14 | 15 | return { 16 | version: 0.1, 17 | type: "rich", 18 | title: "Codepen", 19 | html: ``, 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/utils/providers/loom.provider.ts: -------------------------------------------------------------------------------- 1 | import Platform from "../../modules/Platform"; 2 | import type { OEmbedResponseType, PlatformType } from "../../types"; 3 | 4 | export default class Loom extends Platform { 5 | // eslint-disable-next-line no-useless-constructor 6 | constructor(args: PlatformType) { 7 | super(args); 8 | } 9 | 10 | run = async (): Promise => { 11 | const loomId = this.embedURL.replace("https://www.loom.com/share/", ""); 12 | return { 13 | version: 0.1, 14 | type: "rich", 15 | title: "Loom", 16 | html: `
`, 17 | }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/webembeds-website/styles/main.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* Taken from: Source https://loading.io/css/ */ 6 | 7 | .lds-ring { 8 | display: inline-block; 9 | position: relative; 10 | width: 80px; 11 | height: 80px; 12 | } 13 | .lds-ring div { 14 | box-sizing: border-box; 15 | display: block; 16 | position: absolute; 17 | width: 64px; 18 | height: 64px; 19 | margin: 8px; 20 | border: 8px solid #333; 21 | border-radius: 50%; 22 | animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; 23 | border-color: #333 transparent transparent transparent; 24 | } 25 | .lds-ring div:nth-child(1) { 26 | animation-delay: -0.45s; 27 | } 28 | .lds-ring div:nth-child(2) { 29 | animation-delay: -0.3s; 30 | } 31 | .lds-ring div:nth-child(3) { 32 | animation-delay: -0.15s; 33 | } 34 | @keyframes lds-ring { 35 | 0% { 36 | transform: rotate(0deg); 37 | } 38 | 100% { 39 | transform: rotate(360deg); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/webembeds-website/pages/api/embed.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | import webembed from "@webembeds/core"; 3 | 4 | import type { EmbedRequest, CustomResponse } from "../../types"; 5 | 6 | export default async function handler(req: NextApiRequest, res: NextApiResponse) { 7 | const { url = "", customHost = "", ...restOfTheQueryParams }: EmbedRequest = req.query; 8 | 9 | const embedURL = decodeURIComponent(url); 10 | 11 | res.setHeader("Access-Control-Allow-Origin", "*"); 12 | res.setHeader("Access-Control-Allow-Headers", "*"); 13 | 14 | res.setHeader("Cache-Control", "public, s-maxage=31540000"); // 1 year 15 | 16 | // Twitch needs a parent url where the embed is being used. 17 | 18 | const embedResponse = await webembed(embedURL, { 19 | host: decodeURIComponent(customHost), 20 | webembedWrap: true, 21 | queryParams: { 22 | ...restOfTheQueryParams, 23 | }, 24 | }); 25 | 26 | res.json({ data: embedResponse }); 27 | } 28 | -------------------------------------------------------------------------------- /packages/webembeds-core/README.md: -------------------------------------------------------------------------------- 1 | # webembeds-core (⚠️ Not stable yet) 2 | Current version : 0.0.1 3 | 4 | Built and supported by [Hashnode](https://hashnode.com) 5 | 6 | - Checkout demo here https://webembeds.com 7 | 8 | This is the core package that deals with the whole embedding flow. 9 | The build file can be imported elsewhere and used directly 10 | 11 | **Example**: 12 | 13 | ```js 14 | const webembed = require("../build/bundle"); 15 | (async function () { 16 | try { 17 | const output = await webembed.default("https://www.youtube.com/watch?v=32I0Qso4sDg"); 18 | console.log("Embed output", output); 19 | } catch (error) { 20 | console.log(error); 21 | } 22 | }()); 23 | ``` 24 | 25 | ## TODO 26 | [-] Add minimal tests to make sure embeds are working fine. (WIP) 27 | 28 | ## Future plans 29 | [-] Ship `@webembeds/core` as a separate npm package. 30 | 31 | ## Contributing 32 | Please check this README.md on instructions to contributing. https://github.com/Hashnode/webembeds/blob/master/README.md 33 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/utils/providers/facebook.provider.ts: -------------------------------------------------------------------------------- 1 | import { makeRequest } from "../requestHandler"; 2 | import Platform from "../../modules/Platform"; 3 | import type { OEmbedResponseType, PlatformType } from "../../types"; 4 | 5 | const { FB_APP_TOKEN } = process.env; 6 | 7 | export default class Facebook extends Platform { 8 | hasError: boolean = false; 9 | 10 | // eslint-disable-next-line no-useless-constructor 11 | constructor(args: PlatformType) { 12 | super(args); 13 | if (!FB_APP_TOKEN) { 14 | this.hasError = true; 15 | } 16 | } 17 | 18 | run = async (): Promise => { 19 | if (this.hasError) { 20 | return null; 21 | } 22 | 23 | const response = await makeRequest(`${this.targetURL}?url=${encodeURIComponent(this.embedURL)}&access_token=${FB_APP_TOKEN}`); 24 | const data = response ? response.data : null; 25 | 26 | if (!data) { 27 | return null; 28 | } 29 | 30 | return data; 31 | } 32 | } 33 | 34 | export {}; 35 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/utils/providers/gist.provider.ts: -------------------------------------------------------------------------------- 1 | import Platform from "../../modules/Platform"; 2 | import type { OEmbedResponseType, PlatformType } from "../../types"; 3 | import { wrapHTML } from "../html.utils"; 4 | 5 | export default class GithubGist extends Platform { 6 | // eslint-disable-next-line no-useless-constructor 7 | constructor(args: PlatformType) { 8 | super(args); 9 | } 10 | 11 | run = async (): Promise => { 12 | const response = { 13 | version: 0.1, 14 | type: "rich", 15 | title: "Github Gist", 16 | html: "", 17 | }; 18 | 19 | // if (this.options.webembedWrap) { 20 | // response.html = ``; 21 | // response.html = wrapHTML(response); 22 | // return response; 23 | // } 24 | 25 | return { 26 | ...response, 27 | html: ``, 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webembeds", 3 | "version": "0.0.1", 4 | "author": "Girish Patil ", 5 | "license": "MIT", 6 | "private": true, 7 | "workspaces": [ 8 | "packages/*" 9 | ], 10 | "scripts": { 11 | "website:dev": "yarn workspace @webembeds/website dev", 12 | "website:start": "yarn workspace @webembeds/website start", 13 | "website:build": "yarn workspace @webembeds/website build", 14 | "core:watch": "yarn workspace @webembeds/core watch", 15 | "core:build": "yarn workspace @webembeds/core build", 16 | "build-all": "yarn core:build && yarn website:build", 17 | "test": "yarn workspace @webembeds/core test", 18 | "commit": "git-cz" 19 | }, 20 | "dependencies": { 21 | "axios": "^0.21.1" 22 | }, 23 | "devDependencies": { 24 | "commitizen": "^4.2.3", 25 | "cz-conventional-changelog": "^3.3.0", 26 | "typescript": "^4.1.3" 27 | }, 28 | "engines": { 29 | "node": ">=12.x" 30 | }, 31 | "config": { 32 | "commitizen": { 33 | "path": "./node_modules/cz-conventional-changelog" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/utils/providers/instagram.provider.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | import { makeRequest } from "../requestHandler"; 3 | import Platform from "../../modules/Platform"; 4 | import type { OEmbedResponseType, PlatformType } from "../../types"; 5 | import { wrapHTML } from "../html.utils"; 6 | 7 | const { FB_APP_TOKEN } = process.env; 8 | 9 | export default class Instagram extends Platform { 10 | hasError: boolean = false; 11 | 12 | // eslint-disable-next-line no-useless-constructor 13 | constructor(args: PlatformType) { 14 | super(args); 15 | if (!FB_APP_TOKEN) { 16 | this.hasError = true; 17 | } 18 | } 19 | 20 | run = async (): Promise => { 21 | if (this.hasError) { 22 | return null; 23 | } 24 | 25 | const response = await makeRequest(`${this.targetURL}?url=${encodeURIComponent(this.embedURL)}&access_token=${FB_APP_TOKEN}`); 26 | const data = response ? response.data : null; 27 | 28 | if (!data) { 29 | return null; 30 | } 31 | 32 | return data; 33 | } 34 | } 35 | 36 | export {}; 37 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/utils/providers/tenor.provider.ts: -------------------------------------------------------------------------------- 1 | import Platform from "../../modules/Platform"; 2 | import type { 3 | OEmbedResponseType, 4 | PlatformType, 5 | } from "../../types"; 6 | import { wrapHTML } from "../html.utils"; 7 | 8 | export default class Tenor extends Platform { 9 | // eslint-disable-next-line no-useless-constructor 10 | constructor(args: PlatformType) { 11 | super(args); 12 | } 13 | 14 | run = async (): Promise => { 15 | const splits = this.embedURL.split("/"); 16 | const lastPart = splits[splits.length - 1]; 17 | const extractedID = lastPart.substring( 18 | lastPart.lastIndexOf("-") + 1, 19 | ); 20 | 21 | const html = ``; 22 | 23 | const temp = { 24 | version: 0.1, 25 | type: "rich", 26 | title: "Tenor", 27 | html, 28 | }; 29 | 30 | const wrappedHTML = wrapHTML(temp); 31 | 32 | return { 33 | ...temp, 34 | html: wrappedHTML, 35 | }; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /packages/webembeds-website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webembeds/website", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "dev": "next -p 3001", 8 | "build": "next build", 9 | "start": "next start" 10 | }, 11 | "dependencies": { 12 | "@webembeds/core": "0.0.1", 13 | "autoprefixer": "^10.2.4", 14 | "axios": "^0.21.1", 15 | "express": "^4.17.1", 16 | "next": "^10.0.4", 17 | "postcss": "^8.2.4", 18 | "react": "^17.0.1", 19 | "react-dom": "^17.0.1", 20 | "tailwindcss": "^2.0.2", 21 | "url-parse": "^1.4.7" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^14.14.20", 25 | "@types/react": "^17.0.0", 26 | "@typescript-eslint/eslint-plugin": "^4.12.0", 27 | "@typescript-eslint/parser": "^4.12.0", 28 | "eslint": "^7.17.0", 29 | "eslint-config-airbnb": "^18.2.1", 30 | "eslint-plugin-import": "^2.22.1", 31 | "eslint-plugin-jsx-a11y": "^6.4.1", 32 | "eslint-plugin-react": "^7.22.0", 33 | "eslint-plugin-react-hooks": "^4.2.0", 34 | "typescript": "^4.1.3" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/utils/providers/expo.provider.ts: -------------------------------------------------------------------------------- 1 | import UrlParse from "url-parse"; 2 | import Platform from "../../modules/Platform"; 3 | import type { OEmbedResponseType, PlatformType } from "../../types"; 4 | 5 | export default class ExpoSnack extends Platform { 6 | // eslint-disable-next-line no-useless-constructor 7 | constructor(args: PlatformType) { 8 | super(args); 9 | } 10 | 11 | run = async (): Promise => { 12 | const { cheerio } = this; 13 | 14 | const url = UrlParse(this.embedURL, true); 15 | const snackId = url.pathname.replace(/^\/|\/$/g, ""); 16 | 17 | const { theme = "light" } = this.queryParams; 18 | 19 | const html = `
`; 20 | 21 | return { 22 | version: 0.1, 23 | type: "rich", 24 | title: "Expo", 25 | html, 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/utils/providers/giphy.provider.ts: -------------------------------------------------------------------------------- 1 | import { makeRequest } from "../requestHandler"; 2 | import Platform from "../../modules/Platform"; 3 | import type { OEmbedResponseType, PlatformType } from "../../types"; 4 | import { wrapHTML } from "../html.utils"; 5 | 6 | export default class Giphy extends Platform { 7 | // eslint-disable-next-line no-useless-constructor 8 | constructor(args: PlatformType) { 9 | super(args); 10 | } 11 | 12 | run = async (): Promise => { 13 | const response = await makeRequest(`${this.targetURL}?url=${encodeURIComponent(this.embedURL)}`); 14 | const data = response ? response.data : null; 15 | 16 | if (!data) { 17 | return null; 18 | } 19 | 20 | const { url } = data; 21 | 22 | if (!url) { 23 | return null; 24 | } 25 | 26 | const cleanURL = url.replace("/giphy.gif", ""); 27 | const extractedID = cleanURL.substr(cleanURL.lastIndexOf("/") + 1); 28 | 29 | const html = ``; 30 | 31 | const temp = { 32 | version: 0.1, 33 | type: "rich", 34 | title: "Giphy", 35 | html, 36 | }; 37 | 38 | const wrappedHTML = wrapHTML(temp); 39 | 40 | return { 41 | ...temp, 42 | html: wrappedHTML, 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/utils/providers/glitch.provider.ts: -------------------------------------------------------------------------------- 1 | import Platform from "../../modules/Platform"; 2 | import type { OEmbedResponseType, PlatformType } from "../../types"; 3 | 4 | export default class Glitch extends Platform { 5 | // eslint-disable-next-line no-useless-constructor 6 | constructor(args: PlatformType) { 7 | super(args); 8 | } 9 | 10 | // 16 | 17 | // https://glitch.com/embed/#!/embed/remote-hands?path=README.md&previewSize=100 18 | // https://glitch.com/edit/#!/remote-hands?path=README.md%3A1%3A0 19 | // https://glitch.com/embed/#!/embed/remote-hands?previewSize=100&previewFirst=true&sidebarCollapsed=true 20 | run = async (): Promise => { 21 | return { 22 | version: 0.1, 23 | type: "rich", 24 | title: "Glitch", 25 | html: ``, 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/webembeds-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webembeds/core", 3 | "version": "0.0.1", 4 | "main": "build/bundle.js", 5 | "license": "MIT", 6 | "types": "build/index.d.ts", 7 | "scripts": { 8 | "watch": "rimraf ./build && NODE_ENV=development node_modules/.bin/webpack --watch", 9 | "build": "NODE_ENV=production node_modules/.bin/webpack", 10 | "test": "jest" 11 | }, 12 | "devDependencies": { 13 | "@types/async": "^3.2.5", 14 | "@types/axios": "^0.14.0", 15 | "@types/cheerio": "^0.22.23", 16 | "@types/jest": "^26.0.20", 17 | "@types/url-metadata": "^2.1.0", 18 | "@types/url-parse": "^1.4.3", 19 | "@typescript-eslint/eslint-plugin": "^4.12.0", 20 | "@typescript-eslint/parser": "^4.12.0", 21 | "eslint": "^7.17.0", 22 | "eslint-config-airbnb-base": "^14.2.1", 23 | "eslint-plugin-import": "^2.22.1", 24 | "eslint-plugin-jest": "^24.1.5", 25 | "jest": "^26.6.3", 26 | "ts-jest": "^26.5.1", 27 | "ts-loader": "^8.0.13", 28 | "ts-node": "^9.1.1", 29 | "webpack": "^5.11.1", 30 | "webpack-cli": "^4.3.1" 31 | }, 32 | "dependencies": { 33 | "@types/url-metadata": "^2.1.0", 34 | "async": "^3.2.0", 35 | "axios": "^0.21.1", 36 | "cheerio": "^1.0.0-rc.5", 37 | "fastimage": "^3.2.0", 38 | "node-fetch": "^2.6.1", 39 | "oembed": "^0.1.2", 40 | "request": "^2.88.2", 41 | "rimraf": "^5.0.0", 42 | "sanitize-html": "^2.6.1", 43 | "url-metadata": "^2.5.0", 44 | "url-parse": "^1.4.7" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/utils/providers/snappify.provider.ts: -------------------------------------------------------------------------------- 1 | import Platform from "../../modules/Platform"; 2 | import type { OEmbedResponseType, PlatformType } from "../../types"; 3 | import { makeRequest } from "../requestHandler"; 4 | 5 | export default class Snappify extends Platform { 6 | // eslint-disable-next-line no-useless-constructor 7 | constructor(args: PlatformType) { 8 | super(args); 9 | } 10 | 11 | run = async (): Promise => { 12 | const response = await makeRequest(`${this.targetURL}?url=${encodeURIComponent(this.embedURL)}`); 13 | const data = response ? response.data : null; 14 | 15 | if (!data || !data.width || !data.height) { 16 | return null; 17 | } 18 | 19 | const host = "https://snappify.com/"; 20 | let path = this.embedURL.split(host)[1]; 21 | path = path.replace("view", "embed"); 22 | 23 | const aspectRatioPercentage = (1 / (data.width / data.height)) * 100; 24 | 25 | const wrapperDivStyle = `position:relative;overflow:hidden;margin-left:auto;margin-right:auto;border-radius:10px;width:100%;max-width:${data.width}px`; 26 | const aspectRatioDivStyle = `width:100%;padding-bottom:${aspectRatioPercentage}%`; 27 | const iframeStyle = "position:absolute;left:0;top:0;width:100%"; 28 | 29 | return { 30 | version: 0.1, 31 | type: "rich", 32 | title: "snappify", 33 | html: `
`, 34 | }; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /packages/webembeds-website/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Document, { Html, Head, Main, NextScript } from "next/document"; 3 | 4 | class CustomDocument extends Document { 5 | render() { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | ); 30 | } 31 | } 32 | 33 | export default CustomDocument; 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![webembeds logo](https://user-images.githubusercontent.com/12823517/167348331-92344edd-e253-4bf7-ba1a-b92daae8ab3b.png) 2 | 3 | 4 | # Webembeds 5 | 6 | **(⚠️ Not to be used in production yet)** 7 | 8 | Built and supported by [Hashnode](https://hashnode.com) 9 | 10 | - Checkout demo here https://webembeds.com/demo 11 | 12 | This project is in its very infant stage. There is high scope of improvement here and we have some plans for improvements and stability. We would appreciate any kind of contribution to this project. 13 | 14 | ## Development 15 | - Run `yarn` within the repo 16 | - Run `core:watch` to start the website in development mode 17 | - Run `website:dev` to start the website in development mode (Make sure to rerun when you change @webembeds/core files) 18 | 19 | ## Host it anywhere 20 | - Clone the repo 21 | - Run `yarn` within the repo 22 | - `yarn build-all && yarn website:start` 23 | - Visit http://localhost:3000 24 | 25 | ## Contributing 26 | - We will be working mainly on `development` branch and the `master` branch remains untouched. 27 | - Create feature branches checked out from `development` branch and raise PR against `development` once done. 28 | - Continuous deployment is setup for `development` branch to be deployed to https://staging.webembeds.com and the master branch to https://webembeds.com 29 | 30 | Clone the repo and run 31 | 32 | `yarn` and checkout package.json for all available scripts. 33 | 34 | ## Bugs, Future plans and Improvements 35 | 36 | Please visit [issue section](https://github.com/Hashnode/webembeds/issues) of this repo. 37 | 38 | ### Commit style 39 | We follow conventional commits specs (https://www.conventionalcommits.org/en/v1.0.0/). 40 | Once done you can run `git cz` or `yarn commit` to commit your changes. 41 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/utils/providers/opensea.provider.ts: -------------------------------------------------------------------------------- 1 | import sanitizeHtml from "sanitize-html"; 2 | 3 | import Platform from "../../modules/Platform"; 4 | import type { OEmbedResponseType, PlatformType } from "../../types"; 5 | 6 | export default class Opensea extends Platform { 7 | // eslint-disable-next-line no-useless-constructor 8 | constructor(args: PlatformType) { 9 | super(args); 10 | } 11 | 12 | run = async (): Promise => { 13 | const url = new URL(this.embedURL); 14 | url.searchParams.set("embed", "true"); 15 | 16 | let html = ""; 17 | 18 | if (url.pathname.includes("/assets")) { 19 | const pathnameChunks = url.pathname.split("/"); 20 | 21 | const hasNetwork = pathnameChunks.length === 5; 22 | const network = hasNetwork ? pathnameChunks[2] : "mainnet"; 23 | const contractAddress = hasNetwork ? pathnameChunks[3] : pathnameChunks[2]; 24 | const tokenId = hasNetwork ? pathnameChunks[4] : pathnameChunks[3]; 25 | 26 | html = ` 31 | 32 | `; 33 | } else { 34 | html = `
`; 35 | } 36 | 37 | return { 38 | version: 0.1, 39 | type: "rich", 40 | title: "Opensea", 41 | html, 42 | }; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/utils/graphql.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | 3 | const { HASHNODE_GRAPHQL_URL, HASHNODE_GRAPHQL_USER_ACCESS_TOKEN } = process.env; 4 | 5 | if (!HASHNODE_GRAPHQL_URL) { 6 | throw new Error("HASHNODE_GRAPHQL_URL is not defined"); 7 | } 8 | 9 | if (!HASHNODE_GRAPHQL_USER_ACCESS_TOKEN) { 10 | throw new Error("HASHNODE_GRAPHQL_USER_ACCESS_TOKEN is not defined"); 11 | } 12 | 13 | // client tracking (https://dev.stellate.co/docs/graphql-metrics/clients) 14 | const STELLATE_CLIENT_NAME_HEADER = "x-graphql-client-name"; 15 | const STELLATE_CLIENT_VERSION_HEADER = "x-graphql-client-version"; 16 | 17 | const isServer = typeof window === "undefined"; 18 | 19 | /** 20 | * Executes a GraphQL query and returns the data and errors. 21 | */ 22 | export const fetchGraphQL = async (options: { 23 | query: string; 24 | variables?: Record 25 | }) => { 26 | const { query, variables = {} } = options || {}; 27 | 28 | try { 29 | const response = await fetch(HASHNODE_GRAPHQL_URL, { 30 | method: "POST", 31 | headers: { 32 | "Content-Type": "application/json", 33 | "hn-trace-app": "Embeds", 34 | [STELLATE_CLIENT_NAME_HEADER]: "webembeds", 35 | [STELLATE_CLIENT_VERSION_HEADER]: isServer ? "server" : "browser", 36 | Authorization: HASHNODE_GRAPHQL_USER_ACCESS_TOKEN, 37 | }, 38 | body: JSON.stringify({ 39 | query, 40 | ...(variables ? { variables } : {}), 41 | }), 42 | }); 43 | 44 | if (!response.ok) { 45 | throw new Error(`Error fetching GraphQL. Status code: ${response.status}.`); 46 | } 47 | 48 | const json = await response.json(); 49 | const { data, errors } = json; 50 | return { data, errors }; 51 | } catch (error) { 52 | console.error("Error fetching GraphQL", { error }); 53 | throw error; 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /example.md: -------------------------------------------------------------------------------- 1 | ## Markdown embed examples 2 | 3 | %[https://opensea.io/assets/0x1301566b3cb584e550a02d09562041ddc4989b91/28] 4 | 5 | %[https://opensea.io/assets/0x495f947276749ce646f68ac8c248420045cb7b5e/103558253085473991925726424936776054683291300677177700259118885402889773121537] 6 | 7 | %[https://opensea.io/collection/cyberpunk-vol-1] 8 | 9 | %[https://codesandbox.io/s/y2lrywpk21] 10 | 11 | %[https://codepen.io/szs/pen/JhgKC] 12 | 13 | %[https://vimeo.com/336812660] 14 | 15 | %[https://www.loom.com/share/0281766fa2d04bb788eaf19e65135184] 16 | 17 | %[https://anchor.fm/startapodcast/episodes/Whats-your-podcast-about-e17krq/a-a2q3ft] 18 | 19 | %[https://soundcloud.com/hit-jatt/jatt-disde-arjan-dhillon] 20 | 21 | %[https://repl.it/@GirishPatil4/AdvancedRespectfulGigahertz] 22 | %[https://replit.com/@BearGrappler/playground] 23 | 24 | 25 | %[https://runkit.com/runkit/welcome] 26 | 27 | %[https://open.spotify.com/track/3G8o2zm7LaF6eeVuvLlrkJ?si=Sx1sCnhDT6GXqSLIwSLOeQ] 28 | 29 | %[https://gist.github.com/theevilhead/7ac2fbc3cda897ebd87dbe9aeac130d6] 30 | 31 | %[https://www.canva.com/design/DAET1m0_11c/jFBlYrKc8CQCb2boU9KC-A/view] 32 | 33 | %[https://www.youtube.com/watch?v=32I0Qso4sDg] 34 | 35 | %[https://glitch.com/edit/#!/remote-hands] 36 | 37 | %[https://snack.expo.io/@girishhashnode/unnamed-snack] 38 | 39 | %[https://www.twitch.tv/fresh] 40 | 41 | %[https://twitter.com/hashnode/status/1352525138659430400] 42 | 43 | %[https://hashnode.com] 44 | 45 | %[https://www.canva.com/design/DAEWSa9kfIs/view] 46 | 47 | %[https://www.canva.com/design/DAEWRhUKdvg/view] 48 | 49 | %[https://giphy.com/gifs/cbsnews-inauguration-2021-XEMbxm9vl9JIIMcE7M] 50 | 51 | %[https://www.instagram.com/p/CL8vNB_n_I3/] 52 | 53 | %[https://www.facebook.com/barackobama/posts/10158541668386749] 54 | 55 | %[https://fb.watch/4yOE3vHgMr] 56 | 57 | %[https://www.instagram.com/reel/CMgbGuOgo9 58 | 59 | %[https://snappify.com/view/bcc54061-6e8f-44c5-a4f4-1abcad520108] 60 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/utils/requestHandler.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { info as FastImage } from "fastimage"; 3 | import urlMetaData, { Result } from "url-metadata"; 4 | import type { RequestResponseType } from "../types"; 5 | 6 | export const makeRequest = async (url: string): Promise => { 7 | try { 8 | const response = await axios.get(url, { 9 | params: { 10 | format: "json", 11 | }, 12 | headers: { 13 | // eslint-disable-next-line max-len 14 | // "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36", 15 | // Connection: "keep-alive", 16 | // Accept: "*/*", 17 | // "accept-encoding": "gzip, deflate, br", 18 | }, 19 | }); 20 | return response; 21 | } catch (error) { 22 | // console.log(error); 23 | return null; 24 | } 25 | }; 26 | 27 | // eslint-disable-next-line max-len 28 | export const getMetaData = (url: string): Promise => new Promise((resolve, reject) => { 29 | urlMetaData(url).then( 30 | (metadata: urlMetaData.Result) => { 31 | // success handler 32 | // if (!metadata["og:image:width"] || !metadata["og:image:height"]) { 33 | // FastImage(metadata["og:image"], (error: any, imageData: any): any => { 34 | // if (error) { 35 | // console.log(error); 36 | // return resolve(metadata); 37 | // } 38 | // const newMetaData = metadata; 39 | // if (imageData) { 40 | // newMetaData["og:image:width"] = imageData.width; 41 | // newMetaData["og:image:height"] = imageData.height; 42 | // } 43 | // return resolve(newMetaData); 44 | // }); 45 | // } 46 | resolve(metadata); 47 | }, 48 | (error) => { // failure handler 49 | console.log(error); 50 | reject(error); 51 | }, 52 | ); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/modules/Platform.ts: -------------------------------------------------------------------------------- 1 | import cheerio from "cheerio"; 2 | import queryString from "querystring"; 3 | import { makeRequest } from "../utils/requestHandler"; 4 | import { wrapHTML } from "../utils/html.utils"; 5 | 6 | import type { 7 | OembedRequestQueryParamsType, 8 | OEmbedResponseType, 9 | PlatformType, 10 | WebEmbedInitOptions, 11 | RequestResponseType, 12 | } from "../types"; 13 | 14 | class Platform { 15 | provider: { 16 | custom? : boolean, 17 | customClass?: any, 18 | discover: boolean, 19 | noCustomWrap?: boolean, 20 | } | null; 21 | 22 | embedURL: string; 23 | 24 | targetURL: string | undefined; 25 | 26 | response: RequestResponseType = null; 27 | 28 | queryParams: OembedRequestQueryParamsType; 29 | 30 | cheerio: any; 31 | 32 | options: WebEmbedInitOptions; 33 | 34 | constructor({ 35 | provider, targetURL, embedURL, queryParams, options, 36 | }: PlatformType) { 37 | this.provider = provider; 38 | this.targetURL = targetURL; 39 | this.embedURL = embedURL; 40 | this.queryParams = queryParams; 41 | this.cheerio = cheerio; 42 | 43 | this.options = { 44 | host: options.host || null, 45 | queryParams: options.queryParams, 46 | webembedWrap: options.webembedWrap || false, 47 | }; 48 | } 49 | 50 | async run(): Promise { 51 | const qs = queryString.stringify({ 52 | ...this.queryParams, 53 | url: this.embedURL, 54 | }); 55 | 56 | const response = await makeRequest(`${this.targetURL}?${qs}`); 57 | this.response = response; 58 | 59 | if (response && response.data) { 60 | let { html } = response.data; 61 | 62 | if (this.provider && !this.provider.noCustomWrap) { 63 | html = wrapHTML(response.data, this.queryParams); 64 | } 65 | 66 | return { 67 | version: 0.1, 68 | type: "rich", 69 | title: "WebEmbed", 70 | html, 71 | }; 72 | } 73 | 74 | return null; 75 | } 76 | } 77 | 78 | export default Platform; 79 | -------------------------------------------------------------------------------- /packages/webembeds-website/pages/test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import React, { useRef, useState } from "react"; 3 | import axios from "axios"; 4 | 5 | function Index() { 6 | const urlRef = useRef(null); 7 | const [isEmbedVisible, setEmbedVisible] = useState(false); 8 | const [embedData, setEmbedData] = useState({} as any); 9 | const [currentURL, setURL] = useState(""); 10 | 11 | const onSubmit = () => { 12 | const url = urlRef?.current?.value || ""; 13 | 14 | setURL(url); 15 | 16 | // axios.get(`/api/embed/?url=${encodeURIComponent(url)}`) 17 | // .then((res) => { 18 | // console.log(res.data.data.output); 19 | // setEmbedVisible(true); 20 | // if (res.data.data.output && res.data.data.output.html) { 21 | // setEmbedData(res.data.data.output.html); 22 | // } else { 23 | // setEmbedData(res.data.data.output); 24 | // } 25 | // }) 26 | // .catch(console.log); 27 | }; 28 | 29 | return ( 30 | <> 31 |
e.preventDefault()}> 32 | 33 | 34 | {/* { 35 | isEmbedVisible 36 | &&
37 | } */} 38 | 39 | { 40 | currentURL ? ( 41 |
48 | `, 48 | }; 49 | 50 | const wrappedHTML = wrapHTML(response, {}); 51 | 52 | return { 53 | version: 0.1, 54 | type: "rich", 55 | title: "Twitch", 56 | html: wrappedHTML, 57 | }; 58 | } catch (error) { 59 | return null; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env* 73 | !.env.example 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | 118 | .DS_Store 119 | -------------------------------------------------------------------------------- /packages/webembeds-website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "./**/*" 4 | ], 5 | "compilerOptions": { 6 | "jsx": "preserve", 7 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 8 | /* Basic Options */ 9 | // "incremental": true, /* Enable incremental compilation */ 10 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 11 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */// "lib": [], /* Specify library files to be included in the compilation. */ 12 | "allowJs": true, /* Allow javascript files to be compiled. */// "checkJs": true, /* Report errors in .js files. */ 13 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 14 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 15 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 16 | "sourceMap": true, /* Generates corresponding '.map' file. */// "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./build", /* Redirect output structure to the directory. */// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 18 | // "composite": true, /* Enable project compilation */ 19 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 20 | // "removeComments": true, /* Do not emit comments to output. */ 21 | // "noEmit": true, /* Do not emit outputs. */ 22 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 23 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 24 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 27 | // "strictNullChecks": true, /* Enable strict null checks. */ 28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 29 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 30 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 31 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 32 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 33 | /* Additional Checks */ 34 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 39 | /* Module Resolution Options */ 40 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 42 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 43 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 44 | // "typeRoots": [], /* List of folders to include type definitions from. */ 45 | // "types": [], /* Type declaration files to be included in compilation. */ 46 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 47 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 49 | /* Source Map Options */ 50 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 51 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 54 | /* Experimental Options */ 55 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 56 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 57 | /* Advanced Options */ 58 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 59 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, 60 | "lib": [ 61 | "dom", 62 | "dom.iterable", 63 | "esnext" 64 | ], 65 | "noEmit": true, 66 | "moduleResolution": "node", 67 | "resolveJsonModule": true, 68 | "isolatedModules": true 69 | }, 70 | "exclude": [ 71 | "node_modules" 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /packages/webembeds-core/src/modules/WebembedHandler.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | import oembed from "oembed"; 3 | import tryEach from "async/tryEach"; 4 | import Platform from "./Platform"; 5 | import oEmbedProviders from "../utils/providers/oembed.providers"; 6 | import { getMetaData } from "../utils/requestHandler"; 7 | import { wrapFallbackHTML, wrapHTML } from "../utils/html.utils"; 8 | 9 | import type { 10 | OEmbedResponseType, 11 | ProviderDetails, 12 | } from "../types"; 13 | 14 | export default class WebembedHandler { 15 | // The main embed URL 16 | embedURL: string; 17 | 18 | finalResponse: {} = {}; 19 | 20 | queryParams: { 21 | forceFallback: boolean, 22 | } = { 23 | forceFallback: false, 24 | }; 25 | 26 | platform: any = {}; 27 | 28 | matchedPlatform: {} | null = null; 29 | 30 | providerDetails: ProviderDetails; 31 | 32 | options: any; 33 | 34 | constructor(incomingURL: string, options: any) { 35 | const { queryParams = {} } = options; 36 | // Replace x.com with twitter.com before doing anything as Provider is setup for twitter.com 37 | const twitterXRegex = new RegExp(/https?:\/\/([a-zA-Z0-9-]+\.)*x\.com/); 38 | this.embedURL = incomingURL.match(twitterXRegex) ? incomingURL.replace(twitterXRegex, "https://twitter.com") : incomingURL; 39 | this.options = options; 40 | this.queryParams = queryParams; 41 | this.providerDetails = this.detectProvider(); 42 | } 43 | 44 | /** 45 | * @desc Goes through providers list and tries to find respective provider for incoming embedURL 46 | * @returns {object} providerDetails.provider the respective provider object from list 47 | * @returns {targetURL} providerDetails.targetURL the final url where the request 48 | * must be made with embedURL, if no targetURL found, it will be the embedURL itself. 49 | */ 50 | detectProvider = () => { 51 | let destinationProvider: { endpoints: any, provider_name: string } | null = null; 52 | let targetURL = null; // The endpoint that the embedURL should be queried upon 53 | 54 | let found = false; 55 | oEmbedProviders.some((provider: { endpoints: any[], provider_name: string }) => { 56 | provider.endpoints.some((endpoint) => { 57 | if (!endpoint.schemes || endpoint.schemes.length === 0) { 58 | // If there are no schemes Ex. https://www.beautiful.ai/ 59 | // Consider the url to be the targetURL 60 | 61 | if (this.embedURL.match(endpoint.url.replace(/\*/g, ".*").replace(/\//g, "\/").replace(/\//g, "\\/"))) { 62 | targetURL = endpoint.url; 63 | destinationProvider = provider; 64 | return true; 65 | } 66 | return false; 67 | } 68 | 69 | found = endpoint.schemes.some((scheme: string) => { 70 | // eslint-disable-next-line no-useless-escape 71 | if (this.embedURL.match(scheme.replace(/\*/g, ".*").replace(/\//g, "\/").replace(/\//g, "\\/"))) { 72 | targetURL = endpoint.url; 73 | destinationProvider = provider; 74 | return true; 75 | } 76 | return false; 77 | }); 78 | return found; 79 | }); 80 | return found; 81 | }); 82 | return { 83 | provider: destinationProvider, 84 | targetURL: targetURL || this.embedURL, 85 | }; 86 | } 87 | 88 | generateOEmbed = (callback: any) => { 89 | const { embedURL, queryParams } = this; 90 | const { provider } = this.providerDetails; 91 | 92 | if (!provider || (provider && provider.custom)) { 93 | callback(true); 94 | return; 95 | } 96 | 97 | const { noCustomWrap = false } = provider; 98 | 99 | oembed.fetch(embedURL, { format: "json", ...queryParams }, (error: any, result: OEmbedResponseType): any => { 100 | if (error) { 101 | callback(true); 102 | return; 103 | } 104 | const final = result; 105 | 106 | if (final && final.html && !noCustomWrap) { 107 | final.html = wrapHTML(final); 108 | } 109 | 110 | callback(null, final); 111 | }); 112 | } 113 | 114 | // eslint-disable-next-line no-async-promise-executor 115 | generateManually = async () => { 116 | const { provider, targetURL } = this.providerDetails; 117 | const { embedURL, queryParams } = this; 118 | 119 | if (!provider || !targetURL) { 120 | throw new Error(); 121 | } 122 | 123 | // This should fetch an oembed response 124 | if (provider && provider.custom && provider.customClass) { 125 | const CustomClass = provider.customClass; 126 | this.platform = new CustomClass({ 127 | provider, targetURL, embedURL, queryParams, options: this.options, 128 | }); 129 | } else { 130 | this.platform = new Platform({ 131 | provider, targetURL, embedURL, queryParams, options: this.options, 132 | }); 133 | } 134 | 135 | const finalResponse = await this.platform.run(); 136 | return finalResponse; 137 | } 138 | 139 | // Generate a common fallback here by scraping for the common metadata from the platform 140 | // Use this.platform to generate fallback as it already has a response object 141 | generateFallback = async () => { 142 | try { 143 | const data = await getMetaData(this.embedURL); 144 | const html = await wrapFallbackHTML(data); 145 | return { ...data, html }; 146 | } catch (error) { 147 | return null; 148 | } 149 | }; 150 | 151 | /** 152 | * First try with oembed() 153 | If error is thrown 154 | - Try with our providers list 155 | 1. detect platform 156 | 2. make a request 157 | 3. generate response if successful request is made 158 | If request breaks or some error is returned 159 | - Try with fallback response 160 | - Try generating fallback cover with the response details 161 | If this fails too, return a fatal error 162 | */ 163 | // eslint-disable-next-line max-len 164 | generateOutput = async (): Promise => new Promise((resolve, reject) => { 165 | if (this.queryParams.forceFallback) { 166 | tryEach([this.generateFallback], 167 | (error: any, results: any): void => { 168 | if (error) { 169 | return reject(error); 170 | } 171 | return resolve(results); 172 | }); 173 | } 174 | 175 | const { provider } = this.providerDetails; 176 | 177 | let actions: any = []; 178 | 179 | if (provider && provider.provider_name === "Twitter") { 180 | actions = [this.generateManually, this.generateFallback]; 181 | } else { 182 | actions = [this.generateOEmbed, this.generateManually, this.generateFallback]; 183 | } 184 | 185 | tryEach(actions, 186 | (error: any, results: any): void => { 187 | if (error) { 188 | reject(error); 189 | } 190 | resolve(results); 191 | }); 192 | }) 193 | 194 | generateResponse = async (): Promise<{ output?: OEmbedResponseType | null, error?: boolean }> => { 195 | const output = await this.generateOutput(); 196 | 197 | if (output) { 198 | return { 199 | output, 200 | error: false, 201 | }; 202 | } 203 | 204 | return { output: null, error: true }; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /packages/webembeds-website/pages/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import React, { useState, useEffect, useRef } from "react"; 3 | import Loader from "../components/Loader"; 4 | 5 | const links: any = { 6 | spotify: "https://open.spotify.com/track/3G8o2zm7LaF6eeVuvLlrkJ?si=Sx1sCnhDT6GXqSLIwSLOeQ", 7 | gist: "https://gist.github.com/theevilhead/908d9f761a82809a33fcede797d06334", 8 | // canva1: "https://www.canva.com/design/DAEWSa9kfIs/view", 9 | // canva2: "https://www.canva.com/design/DAEWRhUKdvg/view", 10 | twitter: "https://twitter.com/hashnode/status/1352525138659430400", 11 | expo: "https://snack.expo.io/@girishhashnode/unnamed-snack", 12 | runkit: "https://runkit.com/runkit/welcome", 13 | canva: "https://www.canva.com/design/DAET1m0_11c/jFBlYrKc8CQCb2boU9KC-A/view", 14 | codepen: "https://codepen.io/bsehovac/pen/EMyWVv", 15 | youtube: "https://www.youtube.com/watch?v=32I0Qso4sDg", 16 | glitch: "https://glitch.com/edit/#!/remote-hands", 17 | twitch: "https://www.twitch.tv/fresh", 18 | giphy: "https://giphy.com/gifs/cbsnews-inauguration-2021-XEMbxm9vl9JIIMcE7M", 19 | metascraper: "https://metascraper.js.org/", 20 | repl: "https://repl.it/@GirishPatil4/AdvancedRespectfulGigahertz", 21 | soundcloud: "https://soundcloud.com/hit-jatt/jatt-disde-arjan-dhillon", 22 | anchor: "https://anchor.fm/startapodcast/episodes/Whats-your-podcast-about-e17krq/a-a2q3ft", 23 | loom: "https://www.loom.com/share/0281766fa2d04bb788eaf19e65135184", 24 | vimeo: "https://vimeo.com/485593347", 25 | snappify: "https://snappify.com/view/bcc54061-6e8f-44c5-a4f4-1abcad520108", 26 | fallback: "https://github.com", 27 | }; 28 | 29 | function Demo() { 30 | const urlRef = useRef(null); 31 | const [result, setResult] = useState<{ output?: { html?: string }; error: boolean } | null>(); 32 | const [isLoading, setLoading] = useState(false); 33 | let parentNode: HTMLElement | null = null; 34 | 35 | useEffect(() => { 36 | parentNode = document.getElementById("embed-platform"); 37 | 38 | if (!window) { 39 | return; 40 | } 41 | 42 | (window as any).adjustIframeSize = function(id: string, newHeight: string) { 43 | const ele = document.getElementById(id); 44 | if (!ele) return; 45 | ele.style.height = parseInt(newHeight) + "px"; 46 | } 47 | 48 | // Can be ported to others too 49 | window.addEventListener("message", function(e) { 50 | if (e.origin !== "https://runkit.com") return; 51 | 52 | try { 53 | var data = JSON.parse(e.data); 54 | } catch (e) { 55 | return false; 56 | } 57 | 58 | if (data.context !== "iframe.resize") { 59 | return false; 60 | } 61 | 62 | const iframe: HTMLIFrameElement | null = document.querySelector("iframe[src=\"" + data.src + "\"]"); 63 | 64 | if (!iframe) { 65 | return false; 66 | } 67 | 68 | iframe.setAttribute("width", "100%"); 69 | 70 | if (data.height) { 71 | iframe.setAttribute("height", data.height); 72 | } 73 | }); 74 | 75 | handleURL(links.vimeo); 76 | }, []); 77 | 78 | const handleURL = async (incomingURL?: string) => { 79 | if (null === urlRef) { 80 | return; 81 | } 82 | 83 | if (null === urlRef.current) { 84 | return; 85 | } 86 | 87 | parentNode = document.getElementById("embed-platform"); 88 | if (!parentNode) { 89 | return; 90 | } 91 | 92 | const url = incomingURL || (urlRef !== null ? urlRef.current.value : null); 93 | 94 | if (!url) { 95 | return; 96 | } 97 | 98 | setLoading(true); 99 | setResult(null); 100 | parentNode.innerHTML = ""; 101 | 102 | const requestURL = `/api/embed?url=${encodeURIComponent(url)}&customHost=${encodeURIComponent(window.location.hostname)}`; 103 | const response = await fetch(requestURL, { 104 | method: "GET", 105 | headers: { 106 | Accept: "application/json", 107 | "Content-Type": "application/json", 108 | }, 109 | }); 110 | const json = await response.json(); 111 | 112 | setLoading(false); 113 | 114 | if(parentNode && !url.includes("gist.github.com")) { 115 | parentNode.innerHTML = json.data.output.html; 116 | Array.from(parentNode.querySelectorAll("script")).forEach( async oldScript => { 117 | const newScript = document.createElement("script"); 118 | Array.from(oldScript.attributes).forEach( attr => newScript.setAttribute(attr.name, attr.value) ); 119 | 120 | if (oldScript.innerHTML) { 121 | newScript.appendChild(document.createTextNode(oldScript.innerHTML)); 122 | } 123 | 124 | if (oldScript && oldScript.parentNode) { 125 | oldScript.parentNode.replaceChild(newScript, oldScript); 126 | } 127 | }); 128 | } else { 129 | 130 | const gistFrame = document.createElement("iframe"); 131 | gistFrame.setAttribute("width", "100%"); 132 | gistFrame.setAttribute("frameBorder", "0"); 133 | gistFrame.setAttribute("scrolling", "no"); 134 | gistFrame.id = `gist-${new Date().getTime()}`; 135 | 136 | parentNode.innerHTML = ""; 137 | parentNode.appendChild(gistFrame); 138 | 139 | // Create the iframe's document 140 | const gistFrameHTML = `${json.data.output.html}`; 141 | 142 | // Set iframe's document with a trigger for this document to adjust the height 143 | let gistFrameDoc = gistFrame.contentWindow?.document; 144 | 145 | if (gistFrame.contentDocument) { 146 | gistFrameDoc = gistFrame.contentDocument; 147 | } else if (gistFrame.contentWindow) { 148 | gistFrameDoc = gistFrame.contentWindow.document; 149 | } 150 | 151 | if (!gistFrameDoc) { 152 | return; 153 | } 154 | gistFrameDoc.open(); 155 | gistFrameDoc.writeln(gistFrameHTML); 156 | gistFrameDoc.close(); 157 | } 158 | 159 | // setResult(json ? json.data : null); 160 | setLoading(false); 161 | }; 162 | 163 | return ( 164 |
165 |
166 | Webembeds 167 |
168 | {/* Follow @hashnode 169 |   */} 170 | Star us on Github 171 |
172 |
173 | 174 |
175 | {/*
176 | {result ?
{JSON.stringify(result)}
: "No result"} 177 |
*/} 178 | {isLoading ? : null} 179 | {/* {result && !result.error && !isLoading ? ( */} 180 |
185 | {/* ) : null} */} 186 | {result && result.error ? "Something went wrong" : ""} 187 |
188 | 189 |
190 | 191 |
192 |
193 | 199 | 208 |
209 | 210 |

Or select from below

211 | 212 |
213 | {Object.keys(links).map((key, index) => { 214 | return ( 215 | 222 | ); 223 | })} 224 |
225 |
226 |
227 | Made with ❤️   by{" "} 228 | 229 | Hashnode 230 | 231 |
232 |
233 | ); 234 | } 235 | 236 | export default Demo; 237 | -------------------------------------------------------------------------------- /packages/webembeds-website/public/js/buttons.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * github-buttons v2.14.2 3 | * (c) 2021 なつき 4 | * @license BSD-2-Clause 5 | */ 6 | !function(){"use strict";var e=window.document,t=e.location,o=window.Math,r=window.HTMLElement,a=window.XMLHttpRequest,n="github-button",i="https://buttons.github.io/buttons.html",l="github.com",c=a&&"prototype"in a&&"withCredentials"in a.prototype,d=c&&r&&"attachShadow"in r.prototype&&!("prototype"in r.prototype.attachShadow),s=function(e,t,o,r){null==t&&(t="&"),null==o&&(o="="),null==r&&(r=window.decodeURIComponent);for(var a={},n=e.split(t),i=0,l=n.length;i'}}},eye:{heights:{16:{width:16,path:''}}},heart:{heights:{16:{width:16,path:''}}},"issue-opened":{heights:{16:{width:16,path:''}}},"mark-github":{heights:{16:{width:16,path:''}}},"repo-forked":{heights:{16:{width:16,path:''}}},"repo-template":{heights:{16:{width:16,path:''}}},star:{heights:{16:{width:16,path:''}}}},z=function(e,t){e=w(e).replace(/^octicon-/,""),m(k,e)||(e="mark-github");var o=t>=24&&24 in k[e].heights?24:16,r=k[e].heights[o];return'"},C={},F=function(e,t){var o=C[e]||(C[e]=[]);if(!(o.push(t)>1)){var r=v((function(){for(delete C[e];t=o.shift();)t.apply(null,arguments)}));if(c){var n=new a;f(n,"abort",r),f(n,"error",r),f(n,"load",(function(){var e;try{e=JSON.parse(this.responseText)}catch(e){return void r(e)}r(200!==this.status,e)})),n.open("GET",e),n.send()}else{var i=this||window;i._=function(e){i._=null,r(200!==e.meta.status,e.data)};var l=p(i.document)("script",{async:!0,src:e+(-1!==e.indexOf("?")?"&":"?")+"callback=_"}),d=function(){i._&&i._({meta:{}})};f(l,"load",d),f(l,"error",d),l.readyState&&g(l,/de|m/,d),i.document.getElementsByTagName("head")[0].appendChild(l)}}},A=function(e,t,o){var r=p(e.ownerDocument),a=e.appendChild(r("style",{type:"text/css"})),n="body{margin:0}a{text-decoration:none;outline:0}.widget{display:inline-block;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif;font-size:0;line-height:0;white-space:nowrap}.btn,.social-count{position:relative;display:inline-block;height:14px;padding:2px 5px;font-size:11px;font-weight:600;line-height:14px;vertical-align:bottom;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-repeat:repeat-x;background-position:-1px -1px;background-size:110% 110%;border:1px solid}.btn{border-radius:.25em}.btn:not(:last-child){border-radius:.25em 0 0 .25em}.social-count{border-left:0;border-radius:0 .25em .25em 0}.widget-lg .btn,.widget-lg .social-count{height:20px;padding:3px 10px;font-size:12px;line-height:20px}.octicon{display:inline-block;vertical-align:text-top;fill:currentColor}"+function(e){if(null==e)return x.light;if(m(x,e))return x[e];var t=s(e,";",":",(function(e){return e.replace(/^[ \t\n\f\r]+|[ \t\n\f\r]+$/g,"")}));return x[m(x,t["no-preference"])?t["no-preference"]:"light"]+y("light",t.light)+y("dark",t.dark)}(t["data-color-scheme"]);a.styleSheet?a.styleSheet.cssText=n:a.appendChild(e.ownerDocument.createTextNode(n));var i="large"===w(t["data-size"]),c=r("a",{className:"btn",href:t.href,rel:"noopener",target:"_blank",title:t.title||void 0,"aria-label":t["aria-label"]||void 0,innerHTML:z(t["data-icon"],i?16:14)},[" ",r("span",{},[t["data-text"]||""])]),d=e.appendChild(r("div",{className:"widget"+(i?" widget-lg":"")},[c])),f=c.hostname.replace(/\.$/,"");if(f.length1?o.ceil(o.round(e*M)/M*2)/2:o.ceil(e))||0},E=function(e,t){e.style.width=t[0]+"px",e.style.height=t[1]+"px"},_=function(t,r){if(null!=t&&null!=r)if(t.getAttribute&&(t=function(e){for(var t={href:e.href,title:e.title,"aria-label":e.getAttribute("aria-label")},o=["icon","color-scheme","text","size","show-count"],r=0,a=o.length;r { 17 | const allTags: any = []; 18 | let i = 0; 19 | 20 | do { 21 | let currentNode = allNodes[i]; 22 | const temp = {} as any; 23 | Object.keys(currentNode.attribs).forEach((key) => { 24 | temp[key] = currentNode.attribs[key]; 25 | }); 26 | temp.type = currentNode.type; 27 | temp.name = currentNode.name; 28 | temp.namespace = currentNode.namespace; 29 | 30 | if (temp) { 31 | allTags.push(temp); 32 | } 33 | 34 | i += 1; 35 | if (allNodes[i]) { 36 | currentNode = allNodes[i]; 37 | } else { 38 | i = -1; 39 | } 40 | } while (i >= 0); 41 | 42 | return allTags; 43 | }; 44 | 45 | // eslint-disable-next-line no-unused-vars 46 | export const extractMetaTags = ($: any) => { 47 | const metaTags = $.html($("meta")).toArray(); 48 | return nodeToObject(metaTags); 49 | }; 50 | 51 | // eslint-disable-next-line no-unused-vars 52 | export const extractLinkTags = ($: any) => { 53 | const linkTags = $($.html($("link"))).toArray(); 54 | return nodeToObject(linkTags); 55 | }; 56 | 57 | // export const extractOEmbedContent = (metaTags: []): { 58 | // oEmbed: {}, custom: {} 59 | // } => { 60 | // const filteredMetaTags = metaTags.filter((tag: MetaTagType) => { 61 | // if (tag && tag.property && tag.property.match(/[og:].*/)) { 62 | // return tag; 63 | // } 64 | // return false; 65 | // }); 66 | 67 | // const reformedTags: any = {}; 68 | // filteredMetaTags.forEach((tag: MetaTagType) => { 69 | // const { property, ...remaining } = tag; 70 | // reformedTags[property.replace("og:", "")] = remaining; 71 | // }); 72 | 73 | // console.log(reformedTags); 74 | 75 | // return { 76 | // oEmbed: {}, 77 | // custom: {}, 78 | // }; 79 | // }; 80 | 81 | export const wrapHTML = (oembedResponse: OEmbedResponseType, 82 | customAtrributes: CustomAtrributes = {}) => { 83 | const { html = "" } = oembedResponse; 84 | 85 | const $ = cheerio.load(html, { xmlMode: true }); 86 | const iframe = $("iframe"); 87 | 88 | const iframeExists = iframe.length > 0; 89 | 90 | const { width = "100%", height = "100%" } = customAtrributes; 91 | 92 | const fHeight = Number(oembedResponse.height) > 0 ? Number(oembedResponse.height) : 360; 93 | const fWidth = Number(oembedResponse.width) > 0 ? Number(oembedResponse.width) : 640; 94 | const paddingTop = fHeight / fWidth; 95 | 96 | if (iframeExists) { 97 | iframe.attr("width", String(width)); 98 | iframe.attr("height", String(height)); 99 | 100 | iframe.attr("style", "position: absolute; top: 0; left: 0; border: 0;"); 101 | iframe.attr("class", "webembed-iframe"); 102 | 103 | $("iframe").wrap( 104 | `
`, 105 | ); 106 | } 107 | 108 | return $.html(); 109 | }; 110 | 111 | async function uploadImageByUrl(url: string) { 112 | let properURL: URL; 113 | try { 114 | properURL = new URL(url); 115 | if (properURL.hostname.includes("hashnode.com") || properURL.hostname.includes("images.unsplash.com")) { 116 | return url; 117 | } 118 | } catch (error) { 119 | throw new Error(`Invalid URL: ${url}`); 120 | } 121 | 122 | const { data, errors } = await fetchGraphQL({ 123 | query: ` 124 | mutation UploadImageByURL($input: UploadImageInput!) { 125 | uploadImageByURL(input: $input) { 126 | imageURL 127 | } 128 | } 129 | `, 130 | variables: { 131 | input: { 132 | url: properURL.toString(), 133 | }, 134 | }, 135 | }); 136 | 137 | if (!data || !data.uploadImageByURL?.imageURL || !!errors) { 138 | console.error("Unexpected response uploading image", { data, errors }); 139 | throw new Error("Error uploading image"); 140 | } 141 | 142 | return data.uploadImageByURL.imageURL; 143 | } 144 | 145 | export const wrapFallbackHTML = async (data: urlMetadata.Result) => { 146 | let mainURL; 147 | let isGitHubLink = false; 148 | 149 | const desc = data["og:description"] || data.description; 150 | let coverImage: any = data["og:image"] || data.image; 151 | 152 | if (isProd) { 153 | // Download the image and upload to our CDN 154 | coverImage = (await uploadImageByUrl(coverImage)) || coverImage; 155 | } 156 | 157 | try { 158 | mainURL = new URL(data["og:url"] || data.url).hostname; 159 | } catch (error) { 160 | mainURL = "/"; 161 | } 162 | 163 | const description = `${desc.substring(0, 150)}${ 164 | desc.length > 150 ? "..." : "" 165 | }`; 166 | 167 | if (mainURL.includes("github.com")) { 168 | isGitHubLink = true; 169 | } 170 | 171 | return ` 172 | 175 | 176 | 208 | `; 209 | }; 210 | -------------------------------------------------------------------------------- /packages/webembeds-website/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const colors = { 2 | black: "#000", 3 | white: "#fff", 4 | rose: { 5 | 50: "#fff1f2", 6 | 100: "#ffe4e6", 7 | 200: "#fecdd3", 8 | 300: "#fda4af", 9 | 400: "#fb7185", 10 | 500: "#f43f5e", 11 | 600: "#e11d48", 12 | 700: "#be123c", 13 | 800: "#9f1239", 14 | 900: "#881337", 15 | }, 16 | pink: { 17 | 50: "#fdf2f8", 18 | 100: "#fce7f3", 19 | 200: "#fbcfe8", 20 | 300: "#f9a8d4", 21 | 400: "#f472b6", 22 | 500: "#ec4899", 23 | 600: "#db2777", 24 | 700: "#be185d", 25 | 800: "#9d174d", 26 | 900: "#831843", 27 | }, 28 | fuchsia: { 29 | 50: "#fdf4ff", 30 | 100: "#fae8ff", 31 | 200: "#f5d0fe", 32 | 300: "#f0abfc", 33 | 400: "#e879f9", 34 | 500: "#d946ef", 35 | 600: "#c026d3", 36 | 700: "#a21caf", 37 | 800: "#86198f", 38 | 900: "#701a75", 39 | }, 40 | purple: { 41 | 50: "#faf5ff", 42 | 100: "#f3e8ff", 43 | 200: "#e9d5ff", 44 | 300: "#d8b4fe", 45 | 400: "#c084fc", 46 | 500: "#a855f7", 47 | 600: "#9333ea", 48 | 700: "#7e22ce", 49 | 800: "#6b21a8", 50 | 900: "#581c87", 51 | }, 52 | violet: { 53 | 50: "#f5f3ff", 54 | 100: "#ede9fe", 55 | 200: "#ddd6fe", 56 | 300: "#c4b5fd", 57 | 400: "#a78bfa", 58 | 500: "#8b5cf6", 59 | 600: "#7c3aed", 60 | 700: "#6d28d9", 61 | 800: "#5b21b6", 62 | 900: "#4c1d95", 63 | }, 64 | indigo: { 65 | 50: "#eef2ff", 66 | 100: "#e0e7ff", 67 | 200: "#c7d2fe", 68 | 300: "#a5b4fc", 69 | 400: "#818cf8", 70 | 500: "#6366f1", 71 | 600: "#4f46e5", 72 | 700: "#4338ca", 73 | 800: "#3730a3", 74 | 900: "#312e81", 75 | }, 76 | blue: { 77 | 50: "#eff6ff", 78 | 100: "#dbeafe", 79 | 200: "#bfdbfe", 80 | 300: "#93c5fd", 81 | 400: "#60a5fa", 82 | 500: "#3b82f6", 83 | 600: "#2563eb", 84 | 700: "#1d4ed8", 85 | 800: "#1e40af", 86 | 900: "#1e3a8a", 87 | }, 88 | lightBlue: { 89 | 50: "#f0f9ff", 90 | 100: "#e0f2fe", 91 | 200: "#bae6fd", 92 | 300: "#7dd3fc", 93 | 400: "#38bdf8", 94 | 500: "#0ea5e9", 95 | 600: "#0284c7", 96 | 700: "#0369a1", 97 | 800: "#075985", 98 | 900: "#0c4a6e", 99 | }, 100 | cyan: { 101 | 50: "#ecfeff", 102 | 100: "#cffafe", 103 | 200: "#a5f3fc", 104 | 300: "#67e8f9", 105 | 400: "#22d3ee", 106 | 500: "#06b6d4", 107 | 600: "#0891b2", 108 | 700: "#0e7490", 109 | 800: "#155e75", 110 | 900: "#164e63", 111 | }, 112 | teal: { 113 | 50: "#f0fdfa", 114 | 100: "#ccfbf1", 115 | 200: "#99f6e4", 116 | 300: "#5eead4", 117 | 400: "#2dd4bf", 118 | 500: "#14b8a6", 119 | 600: "#0d9488", 120 | 700: "#0f766e", 121 | 800: "#115e59", 122 | 900: "#134e4a", 123 | }, 124 | emerald: { 125 | 50: "#ecfdf5", 126 | 100: "#d1fae5", 127 | 200: "#a7f3d0", 128 | 300: "#6ee7b7", 129 | 400: "#34d399", 130 | 500: "#10b981", 131 | 600: "#059669", 132 | 700: "#047857", 133 | 800: "#065f46", 134 | 900: "#064e3b", 135 | }, 136 | green: { 137 | 50: "#f0fdf4", 138 | 100: "#dcfce7", 139 | 200: "#bbf7d0", 140 | 300: "#86efac", 141 | 400: "#4ade80", 142 | 500: "#22c55e", 143 | 600: "#16a34a", 144 | 700: "#15803d", 145 | 800: "#166534", 146 | 900: "#14532d", 147 | }, 148 | lime: { 149 | 50: "#f7fee7", 150 | 100: "#ecfccb", 151 | 200: "#d9f99d", 152 | 300: "#bef264", 153 | 400: "#a3e635", 154 | 500: "#84cc16", 155 | 600: "#65a30d", 156 | 700: "#4d7c0f", 157 | 800: "#3f6212", 158 | 900: "#365314", 159 | }, 160 | yellow: { 161 | 50: "#fefce8", 162 | 100: "#fef9c3", 163 | 200: "#fef08a", 164 | 300: "#fde047", 165 | 400: "#facc15", 166 | 500: "#eab308", 167 | 600: "#ca8a04", 168 | 700: "#a16207", 169 | 800: "#854d0e", 170 | 900: "#713f12", 171 | }, 172 | amber: { 173 | 50: "#fffbeb", 174 | 100: "#fef3c7", 175 | 200: "#fde68a", 176 | 300: "#fcd34d", 177 | 400: "#fbbf24", 178 | 500: "#f59e0b", 179 | 600: "#d97706", 180 | 700: "#b45309", 181 | 800: "#92400e", 182 | 900: "#78350f", 183 | }, 184 | orange: { 185 | 50: "#fff7ed", 186 | 100: "#ffedd5", 187 | 200: "#fed7aa", 188 | 300: "#fdba74", 189 | 400: "#fb923c", 190 | 500: "#f97316", 191 | 600: "#ea580c", 192 | 700: "#c2410c", 193 | 800: "#9a3412", 194 | 900: "#7c2d12", 195 | }, 196 | red: { 197 | 50: "#fef2f2", 198 | 100: "#fee2e2", 199 | 200: "#fecaca", 200 | 300: "#fca5a5", 201 | 400: "#f87171", 202 | 500: "#ef4444", 203 | 600: "#dc2626", 204 | 700: "#b91c1c", 205 | 800: "#991b1b", 206 | 900: "#7f1d1d", 207 | }, 208 | warmGray: { 209 | 50: "#fafaf9", 210 | 100: "#f5f5f4", 211 | 200: "#e7e5e4", 212 | 300: "#d6d3d1", 213 | 400: "#a8a29e", 214 | 500: "#78716c", 215 | 600: "#57534e", 216 | 700: "#44403c", 217 | 800: "#292524", 218 | 900: "#1c1917", 219 | }, 220 | trueGray: { 221 | 50: "#fafafa", 222 | 100: "#f5f5f5", 223 | 200: "#e5e5e5", 224 | 300: "#d4d4d4", 225 | 400: "#a3a3a3", 226 | 500: "#737373", 227 | 600: "#525252", 228 | 700: "#404040", 229 | 800: "#262626", 230 | 900: "#171717", 231 | }, 232 | gray: { 233 | 50: "#fafafa", 234 | 100: "#f4f4f5", 235 | 200: "#e4e4e7", 236 | 300: "#d4d4d8", 237 | 400: "#a1a1aa", 238 | 500: "#71717a", 239 | 600: "#52525b", 240 | 700: "#3f3f46", 241 | 800: "#27272a", 242 | 900: "#18181b", 243 | }, 244 | coolGray: { 245 | 50: "#f9fafb", 246 | 100: "#f3f4f6", 247 | 200: "#e5e7eb", 248 | 300: "#d1d5db", 249 | 400: "#9ca3af", 250 | 500: "#6b7280", 251 | 600: "#4b5563", 252 | 700: "#374151", 253 | 800: "#1f2937", 254 | 900: "#111827", 255 | }, 256 | blueGray: { 257 | 50: "#f8fafc", 258 | 100: "#f1f5f9", 259 | 200: "#e2e8f0", 260 | 300: "#cbd5e1", 261 | 400: "#94a3b8", 262 | 500: "#64748b", 263 | 600: "#475569", 264 | 700: "#334155", 265 | 800: "#1e293b", 266 | 900: "#0f172a", 267 | }, 268 | }; 269 | 270 | module.exports = { 271 | purge: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"], 272 | presets: [], 273 | darkMode: false, // or 'media' or 'class' 274 | theme: { 275 | screens: { 276 | sm: "640px", 277 | md: "768px", 278 | lg: "1024px", 279 | xl: "1280px", 280 | "2xl": "1536px", 281 | }, 282 | colors: { 283 | transparent: "transparent", 284 | current: "currentColor", 285 | 286 | black: colors.black, 287 | white: colors.white, 288 | gray: colors.coolGray, 289 | red: colors.red, 290 | yellow: colors.amber, 291 | green: colors.emerald, 292 | blue: colors.blue, 293 | indigo: colors.indigo, 294 | purple: colors.violet, 295 | pink: colors.pink, 296 | }, 297 | spacing: { 298 | px: "1px", 299 | 0: "0px", 300 | 0.5: "0.125rem", 301 | 1: "0.25rem", 302 | 1.5: "0.375rem", 303 | 2: "0.5rem", 304 | 2.5: "0.625rem", 305 | 3: "0.75rem", 306 | 3.5: "0.875rem", 307 | 4: "1rem", 308 | 5: "1.25rem", 309 | 6: "1.5rem", 310 | 7: "1.75rem", 311 | 8: "2rem", 312 | 9: "2.25rem", 313 | 10: "2.5rem", 314 | 11: "2.75rem", 315 | 12: "3rem", 316 | 14: "3.5rem", 317 | 16: "4rem", 318 | 20: "5rem", 319 | 24: "6rem", 320 | 28: "7rem", 321 | 32: "8rem", 322 | 36: "9rem", 323 | 40: "10rem", 324 | 44: "11rem", 325 | 48: "12rem", 326 | 52: "13rem", 327 | 56: "14rem", 328 | 60: "15rem", 329 | 64: "16rem", 330 | 72: "18rem", 331 | 80: "20rem", 332 | 96: "24rem", 333 | }, 334 | animation: { 335 | none: "none", 336 | spin: "spin 1s linear infinite", 337 | ping: "ping 1s cubic-bezier(0, 0, 0.2, 1) infinite", 338 | pulse: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite", 339 | bounce: "bounce 1s infinite", 340 | }, 341 | backgroundColor: (theme) => theme("colors"), 342 | backgroundImage: { 343 | none: "none", 344 | "gradient-to-t": "linear-gradient(to top, var(--tw-gradient-stops))", 345 | "gradient-to-tr": "linear-gradient(to top right, var(--tw-gradient-stops))", 346 | "gradient-to-r": "linear-gradient(to right, var(--tw-gradient-stops))", 347 | "gradient-to-br": "linear-gradient(to bottom right, var(--tw-gradient-stops))", 348 | "gradient-to-b": "linear-gradient(to bottom, var(--tw-gradient-stops))", 349 | "gradient-to-bl": "linear-gradient(to bottom left, var(--tw-gradient-stops))", 350 | "gradient-to-l": "linear-gradient(to left, var(--tw-gradient-stops))", 351 | "gradient-to-tl": "linear-gradient(to top left, var(--tw-gradient-stops))", 352 | }, 353 | backgroundOpacity: (theme) => theme("opacity"), 354 | backgroundPosition: { 355 | bottom: "bottom", 356 | center: "center", 357 | left: "left", 358 | "left-bottom": "left bottom", 359 | "left-top": "left top", 360 | right: "right", 361 | "right-bottom": "right bottom", 362 | "right-top": "right top", 363 | top: "top", 364 | }, 365 | backgroundSize: { 366 | auto: "auto", 367 | cover: "cover", 368 | contain: "contain", 369 | }, 370 | borderColor: (theme) => ({ 371 | ...theme("colors"), 372 | DEFAULT: theme("colors.gray.200", "currentColor"), 373 | }), 374 | borderOpacity: (theme) => theme("opacity"), 375 | borderRadius: { 376 | none: "0px", 377 | sm: "0.125rem", 378 | DEFAULT: "0.25rem", 379 | md: "0.375rem", 380 | lg: "0.5rem", 381 | xl: "0.75rem", 382 | "2xl": "1rem", 383 | "3xl": "1.5rem", 384 | full: "9999px", 385 | }, 386 | borderWidth: { 387 | DEFAULT: "1px", 388 | 0: "0px", 389 | 2: "2px", 390 | 4: "4px", 391 | 8: "8px", 392 | }, 393 | boxShadow: { 394 | sm: "0 1px 2px 0 rgba(0, 0, 0, 0.05)", 395 | DEFAULT: "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)", 396 | md: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)", 397 | lg: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)", 398 | xl: "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)", 399 | "2xl": "0 25px 50px -12px rgba(0, 0, 0, 0.25)", 400 | inner: "inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)", 401 | none: "none", 402 | }, 403 | container: {}, 404 | cursor: { 405 | auto: "auto", 406 | default: "default", 407 | pointer: "pointer", 408 | wait: "wait", 409 | text: "text", 410 | move: "move", 411 | help: "help", 412 | "not-allowed": "not-allowed", 413 | }, 414 | divideColor: (theme) => theme("borderColor"), 415 | divideOpacity: (theme) => theme("borderOpacity"), 416 | divideWidth: (theme) => theme("borderWidth"), 417 | fill: { current: "currentColor" }, 418 | flex: { 419 | 1: "1 1 0%", 420 | auto: "1 1 auto", 421 | initial: "0 1 auto", 422 | none: "none", 423 | }, 424 | flexGrow: { 425 | 0: "0", 426 | DEFAULT: "1", 427 | }, 428 | flexShrink: { 429 | 0: "0", 430 | DEFAULT: "1", 431 | }, 432 | fontFamily: { 433 | sans: [ 434 | "ui-sans-serif", 435 | "system-ui", 436 | "-apple-system", 437 | "BlinkMacSystemFont", 438 | "\"Segoe UI\"", 439 | "Roboto", 440 | "\"Helvetica Neue\"", 441 | "Arial", 442 | "\"Noto Sans\"", 443 | "sans-serif", 444 | "\"Apple Color Emoji\"", 445 | "\"Segoe UI Emoji\"", 446 | "\"Segoe UI Symbol\"", 447 | "\"Noto Color Emoji\"", 448 | ], 449 | serif: ["ui-serif", "Georgia", "Cambria", "\"Times New Roman\"", "Times", "serif"], 450 | mono: [ 451 | "ui-monospace", 452 | "SFMono-Regular", 453 | "Menlo", 454 | "Monaco", 455 | "Consolas", 456 | "\"Liberation Mono\"", 457 | "\"Courier New\"", 458 | "monospace", 459 | ], 460 | }, 461 | fontSize: { 462 | xs: ["0.75rem", { lineHeight: "1rem" }], 463 | sm: ["0.875rem", { lineHeight: "1.25rem" }], 464 | base: ["1rem", { lineHeight: "1.5rem" }], 465 | lg: ["1.125rem", { lineHeight: "1.75rem" }], 466 | xl: ["1.25rem", { lineHeight: "1.75rem" }], 467 | "2xl": ["1.5rem", { lineHeight: "2rem" }], 468 | "3xl": ["1.875rem", { lineHeight: "2.25rem" }], 469 | "4xl": ["2.25rem", { lineHeight: "2.5rem" }], 470 | "5xl": ["3rem", { lineHeight: "1" }], 471 | "6xl": ["3.75rem", { lineHeight: "1" }], 472 | "7xl": ["4.5rem", { lineHeight: "1" }], 473 | "8xl": ["6rem", { lineHeight: "1" }], 474 | "9xl": ["8rem", { lineHeight: "1" }], 475 | }, 476 | fontWeight: { 477 | thin: "100", 478 | extralight: "200", 479 | light: "300", 480 | normal: "400", 481 | medium: "500", 482 | semibold: "600", 483 | bold: "700", 484 | extrabold: "800", 485 | black: "900", 486 | }, 487 | gap: (theme) => theme("spacing"), 488 | gradientColorStops: (theme) => theme("colors"), 489 | gridAutoColumns: { 490 | auto: "auto", 491 | min: "min-content", 492 | max: "max-content", 493 | fr: "minmax(0, 1fr)", 494 | }, 495 | gridAutoRows: { 496 | auto: "auto", 497 | min: "min-content", 498 | max: "max-content", 499 | fr: "minmax(0, 1fr)", 500 | }, 501 | gridColumn: { 502 | auto: "auto", 503 | "span-1": "span 1 / span 1", 504 | "span-2": "span 2 / span 2", 505 | "span-3": "span 3 / span 3", 506 | "span-4": "span 4 / span 4", 507 | "span-5": "span 5 / span 5", 508 | "span-6": "span 6 / span 6", 509 | "span-7": "span 7 / span 7", 510 | "span-8": "span 8 / span 8", 511 | "span-9": "span 9 / span 9", 512 | "span-10": "span 10 / span 10", 513 | "span-11": "span 11 / span 11", 514 | "span-12": "span 12 / span 12", 515 | "span-full": "1 / -1", 516 | }, 517 | gridColumnEnd: { 518 | auto: "auto", 519 | 1: "1", 520 | 2: "2", 521 | 3: "3", 522 | 4: "4", 523 | 5: "5", 524 | 6: "6", 525 | 7: "7", 526 | 8: "8", 527 | 9: "9", 528 | 10: "10", 529 | 11: "11", 530 | 12: "12", 531 | 13: "13", 532 | }, 533 | gridColumnStart: { 534 | auto: "auto", 535 | 1: "1", 536 | 2: "2", 537 | 3: "3", 538 | 4: "4", 539 | 5: "5", 540 | 6: "6", 541 | 7: "7", 542 | 8: "8", 543 | 9: "9", 544 | 10: "10", 545 | 11: "11", 546 | 12: "12", 547 | 13: "13", 548 | }, 549 | gridRow: { 550 | auto: "auto", 551 | "span-1": "span 1 / span 1", 552 | "span-2": "span 2 / span 2", 553 | "span-3": "span 3 / span 3", 554 | "span-4": "span 4 / span 4", 555 | "span-5": "span 5 / span 5", 556 | "span-6": "span 6 / span 6", 557 | "span-full": "1 / -1", 558 | }, 559 | gridRowStart: { 560 | auto: "auto", 561 | 1: "1", 562 | 2: "2", 563 | 3: "3", 564 | 4: "4", 565 | 5: "5", 566 | 6: "6", 567 | 7: "7", 568 | }, 569 | gridRowEnd: { 570 | auto: "auto", 571 | 1: "1", 572 | 2: "2", 573 | 3: "3", 574 | 4: "4", 575 | 5: "5", 576 | 6: "6", 577 | 7: "7", 578 | }, 579 | transformOrigin: { 580 | center: "center", 581 | top: "top", 582 | "top-right": "top right", 583 | right: "right", 584 | "bottom-right": "bottom right", 585 | bottom: "bottom", 586 | "bottom-left": "bottom left", 587 | left: "left", 588 | "top-left": "top left", 589 | }, 590 | gridTemplateColumns: { 591 | none: "none", 592 | 1: "repeat(1, minmax(0, 1fr))", 593 | 2: "repeat(2, minmax(0, 1fr))", 594 | 3: "repeat(3, minmax(0, 1fr))", 595 | 4: "repeat(4, minmax(0, 1fr))", 596 | 5: "repeat(5, minmax(0, 1fr))", 597 | 6: "repeat(6, minmax(0, 1fr))", 598 | 7: "repeat(7, minmax(0, 1fr))", 599 | 8: "repeat(8, minmax(0, 1fr))", 600 | 9: "repeat(9, minmax(0, 1fr))", 601 | 10: "repeat(10, minmax(0, 1fr))", 602 | 11: "repeat(11, minmax(0, 1fr))", 603 | 12: "repeat(12, minmax(0, 1fr))", 604 | }, 605 | gridTemplateRows: { 606 | none: "none", 607 | 1: "repeat(1, minmax(0, 1fr))", 608 | 2: "repeat(2, minmax(0, 1fr))", 609 | 3: "repeat(3, minmax(0, 1fr))", 610 | 4: "repeat(4, minmax(0, 1fr))", 611 | 5: "repeat(5, minmax(0, 1fr))", 612 | 6: "repeat(6, minmax(0, 1fr))", 613 | }, 614 | height: (theme) => ({ 615 | auto: "auto", 616 | ...theme("spacing"), 617 | "1/2": "50%", 618 | "1/3": "33.333333%", 619 | "2/3": "66.666667%", 620 | "1/4": "25%", 621 | "2/4": "50%", 622 | "3/4": "75%", 623 | "1/5": "20%", 624 | "2/5": "40%", 625 | "3/5": "60%", 626 | "4/5": "80%", 627 | "1/6": "16.666667%", 628 | "2/6": "33.333333%", 629 | "3/6": "50%", 630 | "4/6": "66.666667%", 631 | "5/6": "83.333333%", 632 | full: "100%", 633 | screen: "100vh", 634 | }), 635 | inset: (theme, { negative }) => ({ 636 | auto: "auto", 637 | ...theme("spacing"), 638 | ...negative(theme("spacing")), 639 | "1/2": "50%", 640 | "1/3": "33.333333%", 641 | "2/3": "66.666667%", 642 | "1/4": "25%", 643 | "2/4": "50%", 644 | "3/4": "75%", 645 | full: "100%", 646 | "-1/2": "-50%", 647 | "-1/3": "-33.333333%", 648 | "-2/3": "-66.666667%", 649 | "-1/4": "-25%", 650 | "-2/4": "-50%", 651 | "-3/4": "-75%", 652 | "-full": "-100%", 653 | }), 654 | keyframes: { 655 | spin: { 656 | to: { 657 | transform: "rotate(360deg)", 658 | }, 659 | }, 660 | ping: { 661 | "75%, 100%": { 662 | transform: "scale(2)", 663 | opacity: "0", 664 | }, 665 | }, 666 | pulse: { 667 | "50%": { 668 | opacity: ".5", 669 | }, 670 | }, 671 | bounce: { 672 | "0%, 100%": { 673 | transform: "translateY(-25%)", 674 | animationTimingFunction: "cubic-bezier(0.8,0,1,1)", 675 | }, 676 | "50%": { 677 | transform: "none", 678 | animationTimingFunction: "cubic-bezier(0,0,0.2,1)", 679 | }, 680 | }, 681 | }, 682 | letterSpacing: { 683 | tighter: "-0.05em", 684 | tight: "-0.025em", 685 | normal: "0em", 686 | wide: "0.025em", 687 | wider: "0.05em", 688 | widest: "0.1em", 689 | }, 690 | lineHeight: { 691 | none: "1", 692 | tight: "1.25", 693 | snug: "1.375", 694 | normal: "1.5", 695 | relaxed: "1.625", 696 | loose: "2", 697 | 3: ".75rem", 698 | 4: "1rem", 699 | 5: "1.25rem", 700 | 6: "1.5rem", 701 | 7: "1.75rem", 702 | 8: "2rem", 703 | 9: "2.25rem", 704 | 10: "2.5rem", 705 | }, 706 | listStyleType: { 707 | none: "none", 708 | disc: "disc", 709 | decimal: "decimal", 710 | }, 711 | margin: (theme, { negative }) => ({ 712 | auto: "auto", 713 | ...theme("spacing"), 714 | ...negative(theme("spacing")), 715 | }), 716 | maxHeight: (theme) => ({ 717 | ...theme("spacing"), 718 | full: "100%", 719 | screen: "100vh", 720 | }), 721 | maxWidth: (theme, { breakpoints }) => ({ 722 | none: "none", 723 | 0: "0rem", 724 | xs: "20rem", 725 | sm: "24rem", 726 | md: "28rem", 727 | lg: "32rem", 728 | xl: "36rem", 729 | "2xl": "42rem", 730 | "3xl": "48rem", 731 | "4xl": "56rem", 732 | "5xl": "64rem", 733 | "6xl": "72rem", 734 | "7xl": "80rem", 735 | full: "100%", 736 | min: "min-content", 737 | max: "max-content", 738 | prose: "65ch", 739 | ...breakpoints(theme("screens")), 740 | }), 741 | minHeight: { 742 | 0: "0px", 743 | full: "100%", 744 | screen: "100vh", 745 | }, 746 | minWidth: { 747 | 0: "0px", 748 | full: "100%", 749 | min: "min-content", 750 | max: "max-content", 751 | }, 752 | objectPosition: { 753 | bottom: "bottom", 754 | center: "center", 755 | left: "left", 756 | "left-bottom": "left bottom", 757 | "left-top": "left top", 758 | right: "right", 759 | "right-bottom": "right bottom", 760 | "right-top": "right top", 761 | top: "top", 762 | }, 763 | opacity: { 764 | 0: "0", 765 | 5: "0.05", 766 | 10: "0.1", 767 | 20: "0.2", 768 | 25: "0.25", 769 | 30: "0.3", 770 | 40: "0.4", 771 | 50: "0.5", 772 | 60: "0.6", 773 | 70: "0.7", 774 | 75: "0.75", 775 | 80: "0.8", 776 | 90: "0.9", 777 | 95: "0.95", 778 | 100: "1", 779 | }, 780 | order: { 781 | first: "-9999", 782 | last: "9999", 783 | none: "0", 784 | 1: "1", 785 | 2: "2", 786 | 3: "3", 787 | 4: "4", 788 | 5: "5", 789 | 6: "6", 790 | 7: "7", 791 | 8: "8", 792 | 9: "9", 793 | 10: "10", 794 | 11: "11", 795 | 12: "12", 796 | }, 797 | outline: { 798 | none: ["2px solid transparent", "2px"], 799 | white: ["2px dotted white", "2px"], 800 | black: ["2px dotted black", "2px"], 801 | }, 802 | padding: (theme) => theme("spacing"), 803 | placeholderColor: (theme) => theme("colors"), 804 | placeholderOpacity: (theme) => theme("opacity"), 805 | ringColor: (theme) => ({ 806 | DEFAULT: theme("colors.blue.500", "#3b82f6"), 807 | ...theme("colors"), 808 | }), 809 | ringOffsetColor: (theme) => theme("colors"), 810 | ringOffsetWidth: { 811 | 0: "0px", 812 | 1: "1px", 813 | 2: "2px", 814 | 4: "4px", 815 | 8: "8px", 816 | }, 817 | ringOpacity: (theme) => ({ 818 | DEFAULT: "0.5", 819 | ...theme("opacity"), 820 | }), 821 | ringWidth: { 822 | DEFAULT: "3px", 823 | 0: "0px", 824 | 1: "1px", 825 | 2: "2px", 826 | 4: "4px", 827 | 8: "8px", 828 | }, 829 | rotate: { 830 | "-180": "-180deg", 831 | "-90": "-90deg", 832 | "-45": "-45deg", 833 | "-12": "-12deg", 834 | "-6": "-6deg", 835 | "-3": "-3deg", 836 | "-2": "-2deg", 837 | "-1": "-1deg", 838 | 0: "0deg", 839 | 1: "1deg", 840 | 2: "2deg", 841 | 3: "3deg", 842 | 6: "6deg", 843 | 12: "12deg", 844 | 45: "45deg", 845 | 90: "90deg", 846 | 180: "180deg", 847 | }, 848 | scale: { 849 | 0: "0", 850 | 50: ".5", 851 | 75: ".75", 852 | 90: ".9", 853 | 95: ".95", 854 | 100: "1", 855 | 105: "1.05", 856 | 110: "1.1", 857 | 125: "1.25", 858 | 150: "1.5", 859 | }, 860 | skew: { 861 | "-12": "-12deg", 862 | "-6": "-6deg", 863 | "-3": "-3deg", 864 | "-2": "-2deg", 865 | "-1": "-1deg", 866 | 0: "0deg", 867 | 1: "1deg", 868 | 2: "2deg", 869 | 3: "3deg", 870 | 6: "6deg", 871 | 12: "12deg", 872 | }, 873 | space: (theme, { negative }) => ({ 874 | ...theme("spacing"), 875 | ...negative(theme("spacing")), 876 | }), 877 | stroke: { 878 | current: "currentColor", 879 | }, 880 | strokeWidth: { 881 | 0: "0", 882 | 1: "1", 883 | 2: "2", 884 | }, 885 | textColor: (theme) => theme("colors"), 886 | textOpacity: (theme) => theme("opacity"), 887 | transitionDuration: { 888 | DEFAULT: "150ms", 889 | 75: "75ms", 890 | 100: "100ms", 891 | 150: "150ms", 892 | 200: "200ms", 893 | 300: "300ms", 894 | 500: "500ms", 895 | 700: "700ms", 896 | 1000: "1000ms", 897 | }, 898 | transitionDelay: { 899 | 75: "75ms", 900 | 100: "100ms", 901 | 150: "150ms", 902 | 200: "200ms", 903 | 300: "300ms", 904 | 500: "500ms", 905 | 700: "700ms", 906 | 1000: "1000ms", 907 | }, 908 | transitionProperty: { 909 | none: "none", 910 | all: "all", 911 | DEFAULT: "background-color, border-color, color, fill, stroke, opacity, box-shadow, transform", 912 | colors: "background-color, border-color, color, fill, stroke", 913 | opacity: "opacity", 914 | shadow: "box-shadow", 915 | transform: "transform", 916 | }, 917 | transitionTimingFunction: { 918 | DEFAULT: "cubic-bezier(0.4, 0, 0.2, 1)", 919 | linear: "linear", 920 | in: "cubic-bezier(0.4, 0, 1, 1)", 921 | out: "cubic-bezier(0, 0, 0.2, 1)", 922 | "in-out": "cubic-bezier(0.4, 0, 0.2, 1)", 923 | }, 924 | translate: (theme, { negative }) => ({ 925 | ...theme("spacing"), 926 | ...negative(theme("spacing")), 927 | "1/2": "50%", 928 | "1/3": "33.333333%", 929 | "2/3": "66.666667%", 930 | "1/4": "25%", 931 | "2/4": "50%", 932 | "3/4": "75%", 933 | full: "100%", 934 | "-1/2": "-50%", 935 | "-1/3": "-33.333333%", 936 | "-2/3": "-66.666667%", 937 | "-1/4": "-25%", 938 | "-2/4": "-50%", 939 | "-3/4": "-75%", 940 | "-full": "-100%", 941 | }), 942 | width: (theme) => ({ 943 | auto: "auto", 944 | ...theme("spacing"), 945 | "1/2": "50%", 946 | "1/3": "33.333333%", 947 | "2/3": "66.666667%", 948 | "1/4": "25%", 949 | "2/4": "50%", 950 | "3/4": "75%", 951 | "1/5": "20%", 952 | "2/5": "40%", 953 | "3/5": "60%", 954 | "4/5": "80%", 955 | "1/6": "16.666667%", 956 | "2/6": "33.333333%", 957 | "3/6": "50%", 958 | "4/6": "66.666667%", 959 | "5/6": "83.333333%", 960 | "1/12": "8.333333%", 961 | "2/12": "16.666667%", 962 | "3/12": "25%", 963 | "4/12": "33.333333%", 964 | "5/12": "41.666667%", 965 | "6/12": "50%", 966 | "7/12": "58.333333%", 967 | "8/12": "66.666667%", 968 | "9/12": "75%", 969 | "10/12": "83.333333%", 970 | "11/12": "91.666667%", 971 | full: "100%", 972 | screen: "100vw", 973 | min: "min-content", 974 | max: "max-content", 975 | }), 976 | zIndex: { 977 | auto: "auto", 978 | 0: "0", 979 | 10: "10", 980 | 20: "20", 981 | 30: "30", 982 | 40: "40", 983 | 50: "50", 984 | }, 985 | }, 986 | variantOrder: [ 987 | "first", 988 | "last", 989 | "odd", 990 | "even", 991 | "visited", 992 | "checked", 993 | "group-hover", 994 | "group-focus", 995 | "focus-within", 996 | "hover", 997 | "focus", 998 | "focus-visible", 999 | "active", 1000 | "disabled", 1001 | ], 1002 | variants: { 1003 | accessibility: ["responsive", "focus-within", "focus"], 1004 | alignContent: ["responsive"], 1005 | alignItems: ["responsive"], 1006 | alignSelf: ["responsive"], 1007 | animation: ["responsive"], 1008 | appearance: ["responsive"], 1009 | backgroundAttachment: ["responsive"], 1010 | backgroundClip: ["responsive"], 1011 | backgroundColor: ["responsive", "dark", "group-hover", "focus-within", "hover", "focus"], 1012 | backgroundImage: ["responsive"], 1013 | backgroundOpacity: ["responsive", "group-hover", "focus-within", "hover", "focus"], 1014 | backgroundPosition: ["responsive"], 1015 | backgroundRepeat: ["responsive"], 1016 | backgroundSize: ["responsive"], 1017 | borderCollapse: ["responsive"], 1018 | borderColor: ["responsive", "dark", "group-hover", "focus-within", "hover", "focus"], 1019 | borderOpacity: ["responsive", "group-hover", "focus-within", "hover", "focus"], 1020 | borderRadius: ["responsive"], 1021 | borderStyle: ["responsive"], 1022 | borderWidth: ["responsive"], 1023 | boxShadow: ["responsive", "group-hover", "focus-within", "hover", "focus"], 1024 | boxSizing: ["responsive"], 1025 | clear: ["responsive"], 1026 | container: ["responsive"], 1027 | cursor: ["responsive"], 1028 | display: ["responsive"], 1029 | divideColor: ["responsive", "dark"], 1030 | divideOpacity: ["responsive"], 1031 | divideStyle: ["responsive"], 1032 | divideWidth: ["responsive"], 1033 | fill: ["responsive"], 1034 | flex: ["responsive"], 1035 | flexDirection: ["responsive"], 1036 | flexGrow: ["responsive"], 1037 | flexShrink: ["responsive"], 1038 | flexWrap: ["responsive"], 1039 | float: ["responsive"], 1040 | fontFamily: ["responsive"], 1041 | fontSize: ["responsive"], 1042 | fontSmoothing: ["responsive"], 1043 | fontStyle: ["responsive"], 1044 | fontVariantNumeric: ["responsive"], 1045 | fontWeight: ["responsive"], 1046 | gap: ["responsive"], 1047 | gradientColorStops: ["responsive", "dark", "hover", "focus"], 1048 | gridAutoColumns: ["responsive"], 1049 | gridAutoFlow: ["responsive"], 1050 | gridAutoRows: ["responsive"], 1051 | gridColumn: ["responsive"], 1052 | gridColumnEnd: ["responsive"], 1053 | gridColumnStart: ["responsive"], 1054 | gridRow: ["responsive"], 1055 | gridRowEnd: ["responsive"], 1056 | gridRowStart: ["responsive"], 1057 | gridTemplateColumns: ["responsive"], 1058 | gridTemplateRows: ["responsive"], 1059 | height: ["responsive"], 1060 | inset: ["responsive"], 1061 | justifyContent: ["responsive"], 1062 | justifyItems: ["responsive"], 1063 | justifySelf: ["responsive"], 1064 | letterSpacing: ["responsive"], 1065 | lineHeight: ["responsive"], 1066 | listStylePosition: ["responsive"], 1067 | listStyleType: ["responsive"], 1068 | margin: ["responsive"], 1069 | maxHeight: ["responsive"], 1070 | maxWidth: ["responsive"], 1071 | minHeight: ["responsive"], 1072 | minWidth: ["responsive"], 1073 | objectFit: ["responsive"], 1074 | objectPosition: ["responsive"], 1075 | opacity: ["responsive", "group-hover", "focus-within", "hover", "focus"], 1076 | order: ["responsive"], 1077 | outline: ["responsive", "focus-within", "focus"], 1078 | overflow: ["responsive"], 1079 | overscrollBehavior: ["responsive"], 1080 | padding: ["responsive"], 1081 | placeContent: ["responsive"], 1082 | placeItems: ["responsive"], 1083 | placeSelf: ["responsive"], 1084 | placeholderColor: ["responsive", "dark", "focus"], 1085 | placeholderOpacity: ["responsive", "focus"], 1086 | pointerEvents: ["responsive"], 1087 | position: ["responsive"], 1088 | resize: ["responsive"], 1089 | ringColor: ["responsive", "dark", "focus-within", "focus"], 1090 | ringOffsetColor: ["responsive", "dark", "focus-within", "focus"], 1091 | ringOffsetWidth: ["responsive", "focus-within", "focus"], 1092 | ringOpacity: ["responsive", "focus-within", "focus"], 1093 | ringWidth: ["responsive", "focus-within", "focus"], 1094 | rotate: ["responsive", "hover", "focus"], 1095 | scale: ["responsive", "hover", "focus"], 1096 | skew: ["responsive", "hover", "focus"], 1097 | space: ["responsive"], 1098 | stroke: ["responsive"], 1099 | strokeWidth: ["responsive"], 1100 | tableLayout: ["responsive"], 1101 | textAlign: ["responsive"], 1102 | textColor: ["responsive", "dark", "group-hover", "focus-within", "hover", "focus"], 1103 | textDecoration: ["responsive", "group-hover", "focus-within", "hover", "focus"], 1104 | textOpacity: ["responsive", "group-hover", "focus-within", "hover", "focus"], 1105 | textOverflow: ["responsive"], 1106 | textTransform: ["responsive"], 1107 | transform: ["responsive"], 1108 | transformOrigin: ["responsive"], 1109 | transitionDelay: ["responsive"], 1110 | transitionDuration: ["responsive"], 1111 | transitionProperty: ["responsive"], 1112 | transitionTimingFunction: ["responsive"], 1113 | translate: ["responsive", "hover", "focus"], 1114 | userSelect: ["responsive"], 1115 | verticalAlign: ["responsive"], 1116 | visibility: ["responsive"], 1117 | whitespace: ["responsive"], 1118 | width: ["responsive"], 1119 | wordBreak: ["responsive"], 1120 | zIndex: ["responsive", "focus-within", "focus"], 1121 | }, 1122 | plugins: [], 1123 | } --------------------------------------------------------------------------------