diff --git a/website/.eslintrc.js b/website/.eslintrc.js index dc8a649ce5..f064ddaeaf 100644 --- a/website/.eslintrc.js +++ b/website/.eslintrc.js @@ -23,6 +23,12 @@ module.exports = { }, ], "linebreak-style": [2, "unix"], + "import/no-unresolved": [ + 2, + { + ignore: ["^@theme-original"], + }, + ], }, settings: { "import/resolver": { diff --git a/website/config/og/basic/RobotoMono-Bold.ttf b/website/config/og/basic/RobotoMono-Bold.ttf new file mode 100644 index 0000000000..900fce6848 Binary files /dev/null and b/website/config/og/basic/RobotoMono-Bold.ttf differ diff --git a/website/config/og/basic/RobotoMono-Regular.ttf b/website/config/og/basic/RobotoMono-Regular.ttf new file mode 100644 index 0000000000..7c4ce36a44 Binary files /dev/null and b/website/config/og/basic/RobotoMono-Regular.ttf differ diff --git a/website/config/og/basic/preview.png b/website/config/og/basic/preview.png new file mode 100644 index 0000000000..812b1b749a Binary files /dev/null and b/website/config/og/basic/preview.png differ diff --git a/website/config/og/basic/template.json b/website/config/og/basic/template.json new file mode 100644 index 0000000000..80fc4f6161 --- /dev/null +++ b/website/config/og/basic/template.json @@ -0,0 +1,11 @@ +{ + "image": "preview.png", + "font": "RobotoMono-Bold.ttf", + "layout": [ + { + "name": "title", + "top": 400, + "left": 210 + } + ] +} diff --git a/website/config/og/config.json b/website/config/og/config.json new file mode 100644 index 0000000000..80b8556b4f --- /dev/null +++ b/website/config/og/config.json @@ -0,0 +1,12 @@ +{ + "outputDir": "assets/og", + "textWidthLimit": 1100, + "quality": 70, + "rules": [ + { + "name": "basic", + "priority": 0, + "pattern": "." + } + ] +} diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index ba930f23e4..85d5f3716b 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -1,4 +1,5 @@ require("dotenv").config(); +const path = require("path"); const { REDIRECTS, SECTIONS, LEGACY_ROUTES } = require("./routes.config"); const DOMAIN = "https://feature-sliced.design/"; @@ -10,6 +11,13 @@ const TWITTER = "https://twitter.com/feature_sliced"; const OPEN_COLLECTIVE = "https://opencollective.com/feature-sliced"; const DEFAULT_LOCALE = "ru"; +const DOCUSAURUS_PLUGIN_OG = [ + path.resolve(__dirname, "./plugins/docusaurus-plugin-og"), + { + templatesDir: path.resolve(__dirname, "config/og"), + }, +]; + /** @typedef {import('@docusaurus/types').DocusaurusConfig} Config */ /** @type {Config["themeConfig"]["navbar"]} */ @@ -254,6 +262,8 @@ const plugins = [ }, ], process.env.HOTJAR_ID && "docusaurus-plugin-hotjar", // For preventing crashing + // FIXME: Docusaurus Open Graph Plugin Experimental. + process.env.OG_EXP && DOCUSAURUS_PLUGIN_OG, ].filter(Boolean); /** @type {Config["themeConfig"]["algolia"]} */ @@ -313,6 +323,8 @@ const metadatas = [ */ const customFields = { legacyRoutes: LEGACY_ROUTES, + // FIXME: Open Graph Experimental Mode. + isOGExperimental: process.env.OG_EXP, }; /** @type {Config} */ diff --git a/website/plugins/docusaurus-plugin-open-graph-image/Readme.md b/website/plugins/docusaurus-plugin-og/Readme.md similarity index 100% rename from website/plugins/docusaurus-plugin-open-graph-image/Readme.md rename to website/plugins/docusaurus-plugin-og/Readme.md diff --git a/website/plugins/docusaurus-plugin-open-graph-image/config.js b/website/plugins/docusaurus-plugin-og/config.js similarity index 79% rename from website/plugins/docusaurus-plugin-open-graph-image/config.js rename to website/plugins/docusaurus-plugin-og/config.js index 3eea8c914c..ee9cd8ac0c 100644 --- a/website/plugins/docusaurus-plugin-open-graph-image/config.js +++ b/website/plugins/docusaurus-plugin-og/config.js @@ -32,13 +32,7 @@ const Config = object({ }); function validateConfig(config) { - if (is(config, Config)) { - return config.rules.reduce((validationResult, rule) => { - if (!is(rule, Rule)) return false; - return validationResult; - }, true); - } - return false; + return is(config, Config); } module.exports = { getConfig }; diff --git a/website/plugins/docusaurus-plugin-open-graph-image/font.js b/website/plugins/docusaurus-plugin-og/font.js similarity index 69% rename from website/plugins/docusaurus-plugin-open-graph-image/font.js rename to website/plugins/docusaurus-plugin-og/font.js index b2689a2b8a..a6477a71bb 100644 --- a/website/plugins/docusaurus-plugin-open-graph-image/font.js +++ b/website/plugins/docusaurus-plugin-og/font.js @@ -22,14 +22,12 @@ function createSVGText( ) { const attributes = { fill, stroke }; const options = { fontSize, anchor: "top", attributes }; + // Remove all emoji from text + const filteredText = text.replace( + /([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g, + "", + ); - /* If font width more than widthLimit => scale font width to ~90% of widthLimit */ - if (widthLimit) { - const { width } = font.getMetrics(text, options); - if (width > widthLimit) - options.fontSize = Math.trunc((fontSize * 0.9) / (width / widthLimit)); - } - - return font.getSVG(text, options); + return font.getSVG(filteredText, options); } module.exports = { createSVGText, createFontsMapFromTemplates }; diff --git a/website/plugins/docusaurus-plugin-open-graph-image/image.js b/website/plugins/docusaurus-plugin-og/image.js similarity index 100% rename from website/plugins/docusaurus-plugin-open-graph-image/image.js rename to website/plugins/docusaurus-plugin-og/image.js diff --git a/website/plugins/docusaurus-plugin-open-graph-image/index.js b/website/plugins/docusaurus-plugin-og/index.js similarity index 98% rename from website/plugins/docusaurus-plugin-open-graph-image/index.js rename to website/plugins/docusaurus-plugin-og/index.js index 8cdd278d5f..47b42661c0 100644 --- a/website/plugins/docusaurus-plugin-open-graph-image/index.js +++ b/website/plugins/docusaurus-plugin-og/index.js @@ -11,7 +11,7 @@ const { Logger } = require("./utils"); module.exports = function (_, { templatesDir }) { return { - name: "docusaurus-plugin-open-graph-image", + name: "docusaurus-plugin-og", async postBuild({ plugins, outDir, i18n }) { Logger.info(`OG: work in progress.`); diff --git a/website/plugins/docusaurus-plugin-open-graph-image/layout.js b/website/plugins/docusaurus-plugin-og/layout.js similarity index 100% rename from website/plugins/docusaurus-plugin-open-graph-image/layout.js rename to website/plugins/docusaurus-plugin-og/layout.js diff --git a/website/plugins/docusaurus-plugin-open-graph-image/rules.js b/website/plugins/docusaurus-plugin-og/rules.js similarity index 100% rename from website/plugins/docusaurus-plugin-open-graph-image/rules.js rename to website/plugins/docusaurus-plugin-og/rules.js diff --git a/website/plugins/docusaurus-plugin-open-graph-image/template.js b/website/plugins/docusaurus-plugin-og/template.js similarity index 76% rename from website/plugins/docusaurus-plugin-open-graph-image/template.js rename to website/plugins/docusaurus-plugin-og/template.js index 29532a3c14..531a921a82 100644 --- a/website/plugins/docusaurus-plugin-open-graph-image/template.js +++ b/website/plugins/docusaurus-plugin-og/template.js @@ -1,6 +1,6 @@ const { resolve } = require("path"); const { readdir, readFile } = require("fs/promises"); -const { object, string, number, array, is } = require("superstruct"); +const { object, string, number, array, is, optional } = require("superstruct"); const { objectFromBuffer, Logger } = require("./utils"); const dirIgnore = ["config.json"]; @@ -38,11 +38,11 @@ async function getTemplates(templatesDir, encode = "utf8") { // TODO: May be with postEffects, images and etc? (optional fontSize, fill and etc) const Layout = object({ - type: string(), + type: optional(string()), name: string(), - fontSize: number(), - fill: string(), - stroke: string(), + fontSize: optional(number()), + fill: optional(string()), + stroke: optional(string()), top: number(), left: number(), }); @@ -54,16 +54,7 @@ const Template = object({ }); function validateTemplate({ params }) { - if (is(params, Template)) { - if (params.layout.length === 0) return false; - - return params.layout.reduce((validationResult, layout) => { - if (!is(layout, Layout)) return false; - return validationResult; - }, true); - } - - return false; + return is(params, Template); } module.exports = { getTemplates }; diff --git a/website/plugins/docusaurus-plugin-open-graph-image/utils.js b/website/plugins/docusaurus-plugin-og/utils.js similarity index 100% rename from website/plugins/docusaurus-plugin-open-graph-image/utils.js rename to website/plugins/docusaurus-plugin-og/utils.js diff --git a/website/src/shared/lib/og/index.js b/website/src/shared/lib/og/index.js new file mode 100644 index 0000000000..8e1539904b --- /dev/null +++ b/website/src/shared/lib/og/index.js @@ -0,0 +1,11 @@ +import React from "react"; +import Head from "@docusaurus/Head"; + +export function OGMeta({ imgUrl }) { + return ( + + + + + ); +} diff --git a/website/src/theme/DocItem/index.js b/website/src/theme/DocItem/index.js new file mode 100644 index 0000000000..33638e97ce --- /dev/null +++ b/website/src/theme/DocItem/index.js @@ -0,0 +1,20 @@ +import React from "react"; +import OriginalDocItem from "@theme-original/DocItem"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import { OGMeta } from "@site/src/shared/lib/og"; +import { useDocOGUrl } from "./lib"; + +function DocItem(props) { + const { content } = props; + const { isOGExperimental } = useDocusaurusContext().siteConfig.customFields; + const ogUrl = useDocOGUrl(content.metadata); + + return ( + <> + {isOGExperimental && } + + + ); +} + +export default DocItem; diff --git a/website/src/theme/DocItem/lib/index.js b/website/src/theme/DocItem/lib/index.js new file mode 100644 index 0000000000..35c015c891 --- /dev/null +++ b/website/src/theme/DocItem/lib/index.js @@ -0,0 +1,12 @@ +import sha1 from "sha1"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; + +export function useDocOGUrl(metadata, buildOGPath = "assets/og/") { + const { siteConfig, i18n } = useDocusaurusContext(); + const hashFileName = sha1(metadata.id + i18n.currentLocale); + + /* OG Preview images build with locale prefix (.../en/assets/...) for not default locales */ + return `${siteConfig.url}${ + i18n.currentLocale !== i18n.defaultLocale ? `/${i18n.currentLocale}` : "" + }/${buildOGPath}${hashFileName}.jpg`; +}