diff --git a/.changeset/chatty-kangaroos-rule.md b/.changeset/chatty-kangaroos-rule.md index 962f4a1eb..342ac6efb 100644 --- a/.changeset/chatty-kangaroos-rule.md +++ b/.changeset/chatty-kangaroos-rule.md @@ -2,7 +2,6 @@ "@studiocms/dashboard": patch "@studiocms/auth": patch "@studiocms/core": patch -"@studiocms/ui": patch "studiocms": patch --- @@ -112,8 +111,4 @@ Auth system overhaul: ## **`@studiocms/dashboard`** -- Refactor to utilize new `@studiocms/auth` lib for user verification - -## **`@studiocms/ui`** - -- Update `` component's available types \ No newline at end of file +- Refactor to utilize new `@studiocms/auth` lib for user verification \ No newline at end of file diff --git a/.changeset/config.json b/.changeset/config.json index 8fa03f0db..7d57c6726 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -14,13 +14,12 @@ "@studiocms/frontend", "@studiocms/imagehandler", "@studiocms/renderers", - "@studiocms/robotstxt", - "@studiocms/ui" + "@studiocms/robotstxt" ] ], "linked": [], "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", - "ignore": ["docs", "node-playground", "ui-playground"] + "ignore": ["docs", "node-playground"] } diff --git a/.changeset/curvy-mirrors-play.md b/.changeset/curvy-mirrors-play.md deleted file mode 100644 index 988a91c58..000000000 --- a/.changeset/curvy-mirrors-play.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@studiocms/ui": patch ---- - -Applied various changes and fixes to different parts of the UI libary. - -- Fixed a CSS leak caused by importing css files instead of having scoped styles -- Adjusted dropdown helper API for better DX -- Adjusted modal helper API for better DX -- Various CSS fixes for different components diff --git a/.changeset/mean-apples-joke.md b/.changeset/mean-apples-joke.md deleted file mode 100644 index fae72edb8..000000000 --- a/.changeset/mean-apples-joke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@studiocms/ui": patch ---- - -Added a new searchable select component and improved accessibility for normal selects diff --git a/.changeset/pre.json b/.changeset/pre.json index 83181a942..cdb8db215 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -13,9 +13,7 @@ "@studiocms/renderers": "0.1.0-beta.4", "@studiocms/robotstxt": "0.1.0-beta.4", "@studiocms/blog": "0.1.0-beta.1", - "@studiocms/ui": "0.1.0-beta.7", "node-playground": "0.0.1", - "ui-playground": "0.0.1", "docs": "0.0.1" }, "changesets": [ diff --git a/.changeset/rotten-dancers-trade.md b/.changeset/rotten-dancers-trade.md deleted file mode 100644 index 960cc7f00..000000000 --- a/.changeset/rotten-dancers-trade.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@studiocms/ui": patch ---- - -Added a new UI library for the dashboard rework diff --git a/.changeset/sharp-zoos-tickle.md b/.changeset/sharp-zoos-tickle.md deleted file mode 100644 index 7f1811374..000000000 --- a/.changeset/sharp-zoos-tickle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@studiocms/ui": patch ---- - -Added a theme helper and theme toggle component diff --git a/.changeset/spotty-beds-kiss.md b/.changeset/spotty-beds-kiss.md index e2c4cbafd..3701720c4 100644 --- a/.changeset/spotty-beds-kiss.md +++ b/.changeset/spotty-beds-kiss.md @@ -10,7 +10,6 @@ "@studiocms/auth": patch "@studiocms/blog": patch "@studiocms/core": patch -"@studiocms/ui": patch "studiocms": patch --- diff --git a/.changeset/thick-dryers-train.md b/.changeset/thick-dryers-train.md deleted file mode 100644 index 2020539c6..000000000 --- a/.changeset/thick-dryers-train.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@studiocms/ui": patch ---- - -Adjusted persistent toasts to include an outline for better visibility diff --git a/.config/tsconfig.json b/.config/tsconfig.json deleted file mode 100644 index 0967ef424..000000000 --- a/.config/tsconfig.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/.github/actions/install/action.yml b/.github/actions/install/action.yml deleted file mode 100644 index 943a1e426..000000000 --- a/.github/actions/install/action.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Install Tools & Dependencies -description: Installs pnpm, Node.js & package dependencies - -runs: - using: composite - steps: - - name: Setup pnpm (corepack enabled) - uses: pnpm/action-setup@v3 - - - name: Setup Node.js 20.x - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4 - with: - node-version-file: '.node-version' - cache: 'pnpm' - - - name: Install Dependencies - run: pnpm ci:install - shell: bash \ No newline at end of file diff --git a/.github/workflows/ci-pr-i18n-changeset.yml b/.github/workflows/ci-pr-i18n-changeset.yml index 2ae9e91d6..1bdfd15a6 100644 --- a/.github/workflows/ci-pr-i18n-changeset.yml +++ b/.github/workflows/ci-pr-i18n-changeset.yml @@ -19,10 +19,10 @@ jobs: token: ${{ secrets.STUDIOCMS_SERVICE_TOKEN }} - name: Install Tools & Dependencies - uses: ./.github/actions/install + uses: withstudiocms/automations/.github/actions/install@main - name: Create Translation Changesets - run: pnpm translations:changeset + run: pnpm ci:translations:changeset env: CI_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/ci-pr-lunaria-overview.yml b/.github/workflows/ci-pr-lunaria-overview.yml index dfbb09f53..77fa90b2b 100644 --- a/.github/workflows/ci-pr-lunaria-overview.yml +++ b/.github/workflows/ci-pr-lunaria-overview.yml @@ -21,7 +21,7 @@ jobs: token: ${{ secrets.STUDIOCMS_SERVICE_TOKEN }} - name: Install Tools & Dependencies - uses: ./.github/actions/install + uses: withstudiocms/automations/.github/actions/install@main - name: Generate Lunaria Overview uses: lunariajs/action@astro-docs diff --git a/.github/workflows/ci-pr-snapshots.yml b/.github/workflows/ci-pr-snapshots.yml index bd19cf47b..2313c4236 100644 --- a/.github/workflows/ci-pr-snapshots.yml +++ b/.github/workflows/ci-pr-snapshots.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install Tools & Dependencies - uses: ./.github/actions/install + uses: withstudiocms/automations/.github/actions/install@main - name: Publish packages run: pnpm ci:snapshot diff --git a/.github/workflows/ci-push-main.yml b/.github/workflows/ci-push-main.yml index 1b8539a15..c26ba4d2c 100644 --- a/.github/workflows/ci-push-main.yml +++ b/.github/workflows/ci-push-main.yml @@ -36,7 +36,7 @@ jobs: token: ${{ secrets.STUDIOCMS_SERVICE_TOKEN }} - name: Install Tools & Dependencies - uses: ./.github/actions/install + uses: withstudiocms/automations/.github/actions/install@main - name: Format code run: pnpm run lint:fix @@ -81,7 +81,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install Tools & Dependencies - uses: ./.github/actions/install + uses: withstudiocms/automations/.github/actions/install@main - name: Create Release Pull Request or Publish to npm id: changesets @@ -126,15 +126,15 @@ jobs: # token: ${{ secrets.STUDIOCMS_SERVICE_TOKEN }} # - name: Install Tools & Dependencies -# uses: ./.github/actions/install +# uses: withstudiocms/automations/.github/actions/install@main # - name: Build Lunaria Overview -# run: pnpm lunaria:build +# run: pnpm ci:lunaria:build # - name: Upload Overview # uses: actions/upload-pages-artifact@v3 # with: -# path: "www/docs/dist/lunaria/" +# path: "docs/dist/lunaria/" # lunaria-deploy: # name: Deploy Lunaria Overview diff --git a/.github/workflows/ci-report-lunaria.yml b/.github/workflows/ci-report-lunaria.yml index fc52710b4..78c9a0a20 100644 --- a/.github/workflows/ci-report-lunaria.yml +++ b/.github/workflows/ci-report-lunaria.yml @@ -17,11 +17,11 @@ jobs: fetch-depth: 0 - name: Install Tools & Dependencies - uses: ./.github/actions/install + uses: withstudiocms/automations/.github/actions/install@main - id: message name: Format Discord message - run: pnpm tsm --require=./scripts/filter-warnings.cjs ./www/docs/scripts/lunaria-report-bot.ts + run: pnpm ci:lunaria:report discord_message: name: Send Discord Message diff --git a/.github/workflows/lunaria-build.yml b/.github/workflows/lunaria-build.yml index 65de2bbc5..66af5c587 100644 --- a/.github/workflows/lunaria-build.yml +++ b/.github/workflows/lunaria-build.yml @@ -19,15 +19,15 @@ jobs: token: ${{ secrets.STUDIOCMS_SERVICE_TOKEN }} - name: Install Tools & Dependencies - uses: ./.github/actions/install + uses: withstudiocms/automations/.github/actions/install@main - name: Build Lunaria Overview - run: pnpm lunaria:build + run: pnpm ci:lunaria:build - name: Upload Overview uses: actions/upload-pages-artifact@v3 with: - path: "www/docs/dist/lunaria/" + path: "docs/dist/lunaria/" deploy-overview: runs-on: ubuntu-latest diff --git a/.moon/tasks.yml b/.moon/tasks.yml deleted file mode 100644 index ec353bf8d..000000000 --- a/.moon/tasks.yml +++ /dev/null @@ -1,25 +0,0 @@ -tasks: - changeset: - command: changeset - platform: node - ci-publish: - command: 'pnpm changeset publish' - platform: node - ci-version: - command: 'pnpm changeset version' - platform: node - lint: - command: 'biome check .' - platform: node - lint-fix: - command: 'biome check --apply .' - platform: node - moon: - command: moon - platform: node - dev: - command: 'pnpm --filter node-playground dev' - platform: node - update: - command: 'pnpm install' - platform: node \ No newline at end of file diff --git a/.moon/toolchain.yml b/.moon/toolchain.yml deleted file mode 100644 index 35a77ab1d..000000000 --- a/.moon/toolchain.yml +++ /dev/null @@ -1,103 +0,0 @@ -# Enables and configures Node.js. -node: - - # Defines the explicit Node.js version specification to use. If this field is not defined, the global node binary will be used. - # Version can also be defined with .prototools or with the MOON_NODE_VERSION environment variable. - version: '20.14.0' - - # Defines which package manager to utilize. Supports npm (default), pnpm, yarn, or bun. - packageManager: 'pnpm' - - # Optional fields for defining package manager specific configuration. The chosen setting is dependent on the value of node.packageManager. If these settings are not defined, the latest version of the active package manager will be used (when applicable). - # pnpm: - - # The version setting defines the explicit package manager version specification to use. If this field is not defined, the global npm, pnpm, yarn, and bun binaries will be used. - # Version can also be defined with .prototools or with the MOON_NPM_VERSION, MOON_PNPM_VERSION, MOON_YARN_VERSION, or MOON_BUN_VERSION environment variables. - # version: '' - - # Customize the arguments that will be passed to the package manager's install command, when the InstallDeps action is triggered in the pipeline. These arguments are used both locally and in CI. - # installArgs: ['--immutable'] - - # Injects the currently configured Node.js version as an engines constraint to the root package.json field. - addEnginesConstraint: true - - # Additional command line arguments to pass to the node binary when it's being executed by running a target. This will apply arguments to all Node.js based targets, and cannot be changed on a per target basis. - # binExecArgs: - - # Will dedupe dependencies after they have been installed, added, removing, or changed in any way, in an effort to keep the workspace tree as clean and lean as possible. - dedupeOnLockfileChange: true - - - # When syncing project dependencies, customize the format that will be used for the dependency version range. The following formats are supported (but use the one most applicable to your chosen package manager): - - # - file (npm default) - Uses file:../relative/path and copies package contents. - # - link - Uses link:../relative/path and symlinks package contents. - # - star - Uses an explicit *. - # - version - Uses the explicit version from the dependent project's package.json, e.g., "1.2.3". - # - version-caret - Uses the version from the dependent project's package.json as a caret range, e.g., "^1.2.3". - # - version-tilde - Uses the version from the dependent project's package.json as a tilde range, e.g., "~1.2.3". - # - workspace (bun/pnpm/yarn default) - Uses workspace:*, which resolves to "1.2.3". Requires package workspaces. - # - workspace-caret - Uses workspace:^, which resolves to "^1.2.3". Requires package workspaces. - # - workspace-tilde - Uses workspace:~, which resolves to "~1.2.3". Requires package workspaces. - # - # This setting does not apply to peer dependencies, as they will always use a format of ^.0.0. Furthermore, if a package manager does not support a chosen format, it will fallback to another format! - dependencyVersionFormat: workspace - - - # Will infer and automatically create tasks from package.json scripts. - # - # This requires the project's language to be "javascript" or "typescript", a package.json to exist in the project, and will take the following into account: - # - # Script names will be converted to kebab-case, and will become the task ID. - # Pre, post, and life cycle hooks are ignored. - # Tasks defined in .moon/tasks.yml or moon.yml take precedence over scripts of the same name. - # To verify inferred tasks, run moon project (pass --json to view raw config and options). Tasks that are inferred will run through the configured package manager. - inferTasksFromScripts: true - - # Supports the "single version policy" or "one version rule" patterns by only allowing dependencies in the root package.json, and only installing dependencies in the workspace root, and not within individual projects. It also bypasses all workspaces checks to determine package locations. - # - # This setting does not verify that other package.jsons do not have dependencies, it merely runs "install dependency" commands in the root. It's up to you to ensure that other package.jsons do not have dependencies. - rootPackageOnly: false - - # Will sync a project's dependencies as normal dependencies within the project's package.json. If a dependent project does not have a package.json, or if a dependency of the same name has an explicit version already defined, the sync will be skipped. - syncProjectWorkspaceDependencies: true - - # Will sync the currently configured Node.js version to a 3rd-party version manager's config/rc file. Supports "nodenv" (syncs to .node-version), "nvm" (syncs to .nvmrc), or none (default). - # - # This is a special setting that ensure other Node.js processes outside of our toolchain are utilizing the same version, which is a very common practice when managing dependencies. - syncVersionManagerConfig: nodenv - -# Dictates how moon interacts with and utilizes TypeScript within the workspace. -typescript: - - # When syncing project references and a depended on project does not have a tsconfig.json, automatically create one. - createMissingConfig: true - - # When enabled and syncing project references, will inject each project reference as an entry in the include field of the respective project's tsconfig.json. These includes are sometimes required by editors for auto-completion, intellisense, and automatic imports. - includeProjectReferenceSources: true - - # When enabled, will automatically inject shared types (types/**/*) into the include field of each project's tsconfig.json. The shared types folder must be named types and must exist relative to the root setting. - includeSharedTypes: true - - # Defines the file name of the tsconfig.json found in the project root. We utilize this setting when syncing project references between projects. Defaults to tsconfig.json. - projectConfigFileName: 'tsconfig.json' - - # Defines the TypeScript root (relative from moon's workspace root), where project reference composition, common compiler options, and shared types will be located. - root: '.' - - # Defines the file name of the tsconfig.json found in the root of all projects. We utilize this setting when syncing projects as references. - rootConfigFileName: 'tsconfig.json' - - # Defines the file name of the config file found in the root that houses shared compiler options. - rootOptionsConfigFileName: '.config/tsconfig.json' - - # Updates the outDir compiler option in each project's tsconfig.json to route to moon's cache folder. This is useful when using project references and wanting to keep all the compiled .d.ts files out of the project folder. - routeOutDirToCache: true - - # Will sync a project's dependencies (when applicable) as project references within that project's tsconfig.json, and the root tsconfig.json. - # - # This setting assumes you're using the file organization as defined in our official TypeScript project references in-depth guide. - syncProjectReferences: true - - # Will sync a project's tsconfig.json project references to the paths compiler option, using the referenced project's package.json name. - syncProjectReferencesToPaths: true \ No newline at end of file diff --git a/.moon/workspace.yml b/.moon/workspace.yml deleted file mode 100644 index 3c3d1cadd..000000000 --- a/.moon/workspace.yml +++ /dev/null @@ -1,69 +0,0 @@ -# https://moonrepo.dev/docs/config/workspace -$schema: 'https://moonrepo.dev/schemas/workspace.json' - -projects: - root: '.' - # Packages - studiocms: 'packages/studiocms' - studiocms_assets: 'packages/studiocms_assets' - studiocms_auth: 'packages/studiocms_auth' - studiocms_betaresources: 'packages/studiocms_betaresources' - studiocms_blog: 'packages/studiocms_blog' - studiocms_core: 'packages/studiocms_core' - studiocms_dashboard: 'packages/studiocms_dashboard' - studiocms_frontend: 'packages/studiocms_frontend' - studiocms_imagehandler: 'packages/studiocms_imagehandler' - studiocms_renderers: 'packages/studiocms_renderers' - studiocms_robotstxt: 'packages/studiocms_robotstxt' - studiocms_ui: 'packages/studiocms_ui' - - # Playgrounds - playground: 'playgrounds/node' - ui-playground: 'playgrounds/ui' - # cloudflare-playground: 'playgrounds/cloudflare' - Removed for now till we start experimenting with Cloudflare again. - - # Web Sites & Docs - web: 'www/web' - docs: 'www/docs' - -# https://moonrepo.dev/docs/config/workspace#constraints -constraints: - # https://moonrepo.dev/docs/config/workspace#enforceprojecttyperelationships - enforceProjectTypeRelationships: true - -# https://moonrepo.dev/docs/config/workspace#experiments -experiments: - strictProjectAliases: true - -# https://moonrepo.dev/docs/config/workspace#runner -runner: - cacheLifetime: "7 days" - inheritColorsForPipedTasks: true - logRunningCommand: true - -# https://moonrepo.dev/docs/config/workspace#telemetry -telemetry: true - -# Configures the version control system to utilize within the workspace. A VCS -# is required for determining touched (added, modified, etc) files, calculating file hashes, -# computing affected files, and much more. -vcs: - # https://moonrepo.dev/docs/config/workspace#defaultbranch - defaultBranch: 'main' - - # https://moonrepo.dev/docs/config/workspace#hooks - # hooks: - - # https://moonrepo.dev/docs/config/workspace#synchooks - # syncHooks: - - # https://moonrepo.dev/docs/config/workspace#manager - manager: 'git' - - # https://moonrepo.dev/docs/config/workspace#provider - provider: 'github' - - # https://moonrepo.dev/docs/config/workspace#remotecandidates - remoteCandidates: - - 'origin' - - 'upstream' \ No newline at end of file diff --git a/.prototools b/.prototools index 5b0a0f262..ff2083597 100644 --- a/.prototools +++ b/.prototools @@ -1,8 +1,6 @@ -biome = "1.9.2" -moon = "1.28.3" +biome = "1.9.4" node = "20.14.0" pnpm = "9.5.0" [plugins] biome = "source:https://raw.githubusercontent.com/Phault/proto-toml-plugins/main/biome/plugin.toml" -moon = "source:https://raw.githubusercontent.com/moonrepo/moon/master/proto-plugin.toml" diff --git a/.vscode/settings.json b/.vscode/settings.json index 44fcf1104..31abbe325 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,5 +8,22 @@ "[mdx]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "editor.gotoLocation.multipleDefinitions": "goto" + "editor.gotoLocation.multipleDefinitions": "goto", + "cSpell.words": [ + "astrojs", + "CMSSDK", + "Coolify", + "Libravatar", + "liverender", + "Markdoc", + "mdast", + "onest", + "oslojs", + "prototools", + "robotstxt", + "studiocms", + "twoslash", + "vite", + "withstudiocms" + ] } diff --git a/README.md b/README.md index a2362fb40..662d0314e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ To see how to get started, check out the [StudioCMS README](./packages/studiocms ## Sponsor - + ## StudioCMS Dashboard i18n Status @@ -45,7 +45,7 @@ For an up-to-date list of our main tools check out our [`.prototools`](.prototoo For more information about Proto checkout [Proto's Website](https://moonrepo.dev/proto) -## This is a [`Moonrepo`](https://moonrepo.dev) +## This is a [`Moon repository`](https://moonrepo.dev) Follow install instructions listed on Moonrepo's docs, and you'll be all set to go! Its even a super easy single line command you put in your terminal! @@ -65,16 +65,15 @@ Steps to get a running playground should be the following: - clone repo - run `pnpm i --frozen-lockfile` -- change `dbStartPage` in the [node playground's](./playgrounds/node/studiocms.config.mjs) config to `true` +- change `dbStartPage` in the [node playground's](./playground/studiocms.config.mts) config to `true` - read the first time setup instructions listed in the [main package readme](./packages/studiocms/README.md#first-start-and-setup) then replace the astro db commands with the following: Commands to run: - - `pnpm playground:login` - Login your CLI to Astro Studio - - `pnpm playground:push` - Creates the base tables on the remote database. + - `pnpm playground:push` - Push to your libSQL database assigned via environment variables - `pnpm playground:dev` - Starts the Dev server connected to the linked database -Once that process completes successfuly you are ready to navigate to http://localhost:4321/start and follow the instructions to get started. +Once that process completes successfully you are ready to navigate to http://localhost:4321/start and follow the instructions to get started. It will redirect and ask you to shutdown and change the above mentioned config option `dbStartPage` to `false` at which point that will enable full functionality of the CMS. you can now restart the dev server with `astro dev --remote` to continue viewing your new site! diff --git a/www/assets/banner-readme.png b/assets/banner-readme.png similarity index 100% rename from www/assets/banner-readme.png rename to assets/banner-readme.png diff --git a/www/assets/discord-emoji-dark.png b/assets/discord-emoji-dark.png similarity index 100% rename from www/assets/discord-emoji-dark.png rename to assets/discord-emoji-dark.png diff --git a/www/assets/discord-emoji-light.png b/assets/discord-emoji-light.png similarity index 100% rename from www/assets/discord-emoji-light.png rename to assets/discord-emoji-light.png diff --git a/playgrounds/node/public/studiocms-auth/logo-adaptive.svg b/assets/logo-adaptive.svg similarity index 100% rename from playgrounds/node/public/studiocms-auth/logo-adaptive.svg rename to assets/logo-adaptive.svg diff --git a/www/assets/logo-dark.svg b/assets/logo-dark.svg similarity index 100% rename from www/assets/logo-dark.svg rename to assets/logo-dark.svg diff --git a/www/assets/logo-discord.png b/assets/logo-discord.png similarity index 100% rename from www/assets/logo-discord.png rename to assets/logo-discord.png diff --git a/www/assets/logo-discord.svg b/assets/logo-discord.svg similarity index 100% rename from www/assets/logo-discord.svg rename to assets/logo-discord.svg diff --git a/www/assets/logo-light.svg b/assets/logo-light.svg similarity index 100% rename from www/assets/logo-light.svg rename to assets/logo-light.svg diff --git a/www/assets/logo-outlined-adaptive.svg b/assets/logo-outlined-adaptive.svg similarity index 100% rename from www/assets/logo-outlined-adaptive.svg rename to assets/logo-outlined-adaptive.svg diff --git a/www/assets/logo-outlined-dark.svg b/assets/logo-outlined-dark.svg similarity index 100% rename from www/assets/logo-outlined-dark.svg rename to assets/logo-outlined-dark.svg diff --git a/www/assets/logo-outlined-light.svg b/assets/logo-outlined-light.svg similarity index 100% rename from www/assets/logo-outlined-light.svg rename to assets/logo-outlined-light.svg diff --git a/www/assets/old/banner-readme.png b/assets/old/banner-readme.png similarity index 100% rename from www/assets/old/banner-readme.png rename to assets/old/banner-readme.png diff --git a/www/assets/old/discord-emoji-dark.png b/assets/old/discord-emoji-dark.png similarity index 100% rename from www/assets/old/discord-emoji-dark.png rename to assets/old/discord-emoji-dark.png diff --git a/www/assets/old/discord-emoji-light.png b/assets/old/discord-emoji-light.png similarity index 100% rename from www/assets/old/discord-emoji-light.png rename to assets/old/discord-emoji-light.png diff --git a/www/assets/old/logo-dark.svg b/assets/old/logo-dark.svg similarity index 100% rename from www/assets/old/logo-dark.svg rename to assets/old/logo-dark.svg diff --git a/www/assets/old/logo-discord.png b/assets/old/logo-discord.png similarity index 100% rename from www/assets/old/logo-discord.png rename to assets/old/logo-discord.png diff --git a/www/assets/old/logo-discord.svg b/assets/old/logo-discord.svg similarity index 100% rename from www/assets/old/logo-discord.svg rename to assets/old/logo-discord.svg diff --git a/www/assets/old/logo-light.svg b/assets/old/logo-light.svg similarity index 100% rename from www/assets/old/logo-light.svg rename to assets/old/logo-light.svg diff --git a/www/assets/old/studioCMS-dark.png b/assets/old/studioCMS-dark.png similarity index 100% rename from www/assets/old/studioCMS-dark.png rename to assets/old/studioCMS-dark.png diff --git a/www/assets/old/studioCMS.png b/assets/old/studioCMS.png similarity index 100% rename from www/assets/old/studioCMS.png rename to assets/old/studioCMS.png diff --git a/www/assets/old/studiocms-new-logos.zip b/assets/old/studiocms-new-logos.zip similarity index 100% rename from www/assets/old/studiocms-new-logos.zip rename to assets/old/studiocms-new-logos.zip diff --git a/www/assets/studioCMS.png b/assets/studioCMS.png similarity index 100% rename from www/assets/studioCMS.png rename to assets/studioCMS.png diff --git a/www/assets/studiocms-dark.png b/assets/studiocms-dark.png similarity index 100% rename from www/assets/studiocms-dark.png rename to assets/studiocms-dark.png diff --git a/www/assets/studiocms-new-logos.zip b/assets/studiocms-new-logos.zip similarity index 100% rename from www/assets/studiocms-new-logos.zip rename to assets/studiocms-new-logos.zip diff --git a/www/docs/.gitignore b/docs/.gitignore similarity index 100% rename from www/docs/.gitignore rename to docs/.gitignore diff --git a/playgrounds/node/.vscode/extensions.json b/docs/.vscode/extensions.json similarity index 100% rename from playgrounds/node/.vscode/extensions.json rename to docs/.vscode/extensions.json diff --git a/playgrounds/node/.vscode/launch.json b/docs/.vscode/launch.json similarity index 100% rename from playgrounds/node/.vscode/launch.json rename to docs/.vscode/launch.json diff --git a/www/docs/README.md b/docs/README.md similarity index 100% rename from www/docs/README.md rename to docs/README.md diff --git a/www/docs/astro.config.mts b/docs/astro.config.mts similarity index 88% rename from www/docs/astro.config.mts rename to docs/astro.config.mts index 789aefe84..76b0f2672 100644 --- a/www/docs/astro.config.mts +++ b/docs/astro.config.mts @@ -2,9 +2,9 @@ import starlight from '@astrojs/starlight'; import starlightUtils from '@lorenzo_lewis/starlight-utils'; import { defineConfig } from 'astro/config'; import starlightImageZoom from 'starlight-image-zoom'; -import getCoolifyURL from './hostUtils'; -import rehypePluginKit from './src/plugins/rehypePluginKit'; -import { typeDocPlugins, typeDocSideBarEntry } from './typedoc.config'; +import getCoolifyURL from './hostUtils.ts'; +import rehypePluginKit from './src/plugins/rehypePluginKit.ts'; +import { typeDocPlugins, typeDocSideBarEntry } from './typedoc.config.ts'; // Define the Site URL const site = getCoolifyURL(true) || 'https://docs.studiocms.dev/'; @@ -30,9 +30,6 @@ export const locales = { export default defineConfig({ site, - experimental: { - directRenderScript: true, - }, image: { remotePatterns: [{ protocol: 'https' }], }, @@ -73,7 +70,7 @@ export default defineConfig({ './src/styles/starlight.css', ], editLink: { - baseUrl: 'https://github.com/withstudiocms/studiocms/tree/main/www/docs', + baseUrl: 'https://github.com/withstudiocms/studiocms/tree/main/docs', }, head: [ // { @@ -143,21 +140,6 @@ export default defineConfig({ autogenerate: { directory: 'customizing/studiocms-renderers' }, collapsed: true, }, - { - label: '@studiocms/ui', - badge: { text: 'New', variant: 'success' }, - items: [ - { label: 'Getting Started', link: 'customizing/studiocms-ui/' }, - { - label: 'Components', - autogenerate: { - directory: 'customizing/studiocms-ui/components', - collapsed: true, - }, - }, - ], - collapsed: true, - }, ], }, ], diff --git a/www/docs/ec.config.mjs b/docs/ec.config.mjs similarity index 87% rename from www/docs/ec.config.mjs rename to docs/ec.config.mjs index 74fc964ab..bb02c7ba3 100644 --- a/www/docs/ec.config.mjs +++ b/docs/ec.config.mjs @@ -24,7 +24,9 @@ export default defineEcConfig({ }), ], styleOverrides: { - // @ts-expect-error - This is not a Standard EC config option, but it's a valid one from a plugin + frames: { + editorActiveTabIndicatorBottomColor: 'var(--sl-color-accent)', + }, twoSlash: { cursorColor: '#f8f8f2', }, diff --git a/www/docs/hostUtils.ts b/docs/hostUtils.ts similarity index 100% rename from www/docs/hostUtils.ts rename to docs/hostUtils.ts diff --git a/www/docs/lunaria.config.ts b/docs/lunaria.config.ts similarity index 100% rename from www/docs/lunaria.config.ts rename to docs/lunaria.config.ts diff --git a/www/docs/lunaria/components.ts b/docs/lunaria/components.ts similarity index 100% rename from www/docs/lunaria/components.ts rename to docs/lunaria/components.ts diff --git a/www/docs/lunaria/styles.ts b/docs/lunaria/styles.ts similarity index 100% rename from www/docs/lunaria/styles.ts rename to docs/lunaria/styles.ts diff --git a/www/docs/package.json b/docs/package.json similarity index 98% rename from www/docs/package.json rename to docs/package.json index 8d46c63a3..b84128564 100644 --- a/www/docs/package.json +++ b/docs/package.json @@ -16,7 +16,7 @@ "@studiocms/renderers": "workspace:*", "@studiocms/blog": "workspace:*", "@studiocms/devapps": "workspace:*", - "@studiocms/ui": "workspace:*", + "@studiocms/ui": "catalog:", "astro": "catalog:", "@astrojs/check": "catalog:", diff --git a/www/docs/public/logo-light.svg b/docs/public/logo-light.svg similarity index 100% rename from www/docs/public/logo-light.svg rename to docs/public/logo-light.svg diff --git a/www/docs/public/og.jpg b/docs/public/og.jpg similarity index 100% rename from www/docs/public/og.jpg rename to docs/public/og.jpg diff --git a/www/docs/scripts/lunaria-report-bot.ts b/docs/scripts/lunaria-report-bot.ts similarity index 100% rename from www/docs/scripts/lunaria-report-bot.ts rename to docs/scripts/lunaria-report-bot.ts diff --git a/www/docs/scripts/lunaria.mts b/docs/scripts/lunaria.mts similarity index 100% rename from www/docs/scripts/lunaria.mts rename to docs/scripts/lunaria.mts diff --git a/www/docs/src/assets/avatar.png b/docs/src/assets/avatar.png similarity index 100% rename from www/docs/src/assets/avatar.png rename to docs/src/assets/avatar.png diff --git a/www/docs/src/assets/gallery/CreateNewPageDark-1.png b/docs/src/assets/gallery/CreateNewPageDark-1.png similarity index 100% rename from www/docs/src/assets/gallery/CreateNewPageDark-1.png rename to docs/src/assets/gallery/CreateNewPageDark-1.png diff --git a/www/docs/src/assets/gallery/CreateNewPageDark.png b/docs/src/assets/gallery/CreateNewPageDark.png similarity index 100% rename from www/docs/src/assets/gallery/CreateNewPageDark.png rename to docs/src/assets/gallery/CreateNewPageDark.png diff --git a/www/docs/src/assets/gallery/CreateNewPageLight-1.png b/docs/src/assets/gallery/CreateNewPageLight-1.png similarity index 100% rename from www/docs/src/assets/gallery/CreateNewPageLight-1.png rename to docs/src/assets/gallery/CreateNewPageLight-1.png diff --git a/www/docs/src/assets/gallery/CreateNewPageLight-2.png b/docs/src/assets/gallery/CreateNewPageLight-2.png similarity index 100% rename from www/docs/src/assets/gallery/CreateNewPageLight-2.png rename to docs/src/assets/gallery/CreateNewPageLight-2.png diff --git a/www/docs/src/assets/gallery/DashboardDark.png b/docs/src/assets/gallery/DashboardDark.png similarity index 100% rename from www/docs/src/assets/gallery/DashboardDark.png rename to docs/src/assets/gallery/DashboardDark.png diff --git a/www/docs/src/assets/gallery/DashboardLight.png b/docs/src/assets/gallery/DashboardLight.png similarity index 100% rename from www/docs/src/assets/gallery/DashboardLight.png rename to docs/src/assets/gallery/DashboardLight.png diff --git a/www/docs/src/assets/gallery/EditPageDark-1.png b/docs/src/assets/gallery/EditPageDark-1.png similarity index 100% rename from www/docs/src/assets/gallery/EditPageDark-1.png rename to docs/src/assets/gallery/EditPageDark-1.png diff --git a/www/docs/src/assets/gallery/EditPageDark-2.png b/docs/src/assets/gallery/EditPageDark-2.png similarity index 100% rename from www/docs/src/assets/gallery/EditPageDark-2.png rename to docs/src/assets/gallery/EditPageDark-2.png diff --git a/www/docs/src/assets/gallery/EditPageLight-1.png b/docs/src/assets/gallery/EditPageLight-1.png similarity index 100% rename from www/docs/src/assets/gallery/EditPageLight-1.png rename to docs/src/assets/gallery/EditPageLight-1.png diff --git a/www/docs/src/assets/gallery/EditPageLight-2.png b/docs/src/assets/gallery/EditPageLight-2.png similarity index 100% rename from www/docs/src/assets/gallery/EditPageLight-2.png rename to docs/src/assets/gallery/EditPageLight-2.png diff --git a/www/docs/src/assets/gallery/ExistingPageLight.png b/docs/src/assets/gallery/ExistingPageLight.png similarity index 100% rename from www/docs/src/assets/gallery/ExistingPageLight.png rename to docs/src/assets/gallery/ExistingPageLight.png diff --git a/www/docs/src/assets/gallery/ExistingPagesDark.png b/docs/src/assets/gallery/ExistingPagesDark.png similarity index 100% rename from www/docs/src/assets/gallery/ExistingPagesDark.png rename to docs/src/assets/gallery/ExistingPagesDark.png diff --git a/www/docs/src/assets/gallery/LoginPageDark.png b/docs/src/assets/gallery/LoginPageDark.png similarity index 100% rename from www/docs/src/assets/gallery/LoginPageDark.png rename to docs/src/assets/gallery/LoginPageDark.png diff --git a/www/docs/src/assets/gallery/LoginPageLight.png b/docs/src/assets/gallery/LoginPageLight.png similarity index 100% rename from www/docs/src/assets/gallery/LoginPageLight.png rename to docs/src/assets/gallery/LoginPageLight.png diff --git a/www/docs/src/assets/gallery/SiteAdminsDark.png b/docs/src/assets/gallery/SiteAdminsDark.png similarity index 100% rename from www/docs/src/assets/gallery/SiteAdminsDark.png rename to docs/src/assets/gallery/SiteAdminsDark.png diff --git a/www/docs/src/assets/gallery/SiteConfigDark.png b/docs/src/assets/gallery/SiteConfigDark.png similarity index 100% rename from www/docs/src/assets/gallery/SiteConfigDark.png rename to docs/src/assets/gallery/SiteConfigDark.png diff --git a/www/docs/src/assets/gallery/UserProfileDark.png b/docs/src/assets/gallery/UserProfileDark.png similarity index 100% rename from www/docs/src/assets/gallery/UserProfileDark.png rename to docs/src/assets/gallery/UserProfileDark.png diff --git a/www/docs/src/assets/gallery/UserProfileLight.png b/docs/src/assets/gallery/UserProfileLight.png similarity index 100% rename from www/docs/src/assets/gallery/UserProfileLight.png rename to docs/src/assets/gallery/UserProfileLight.png diff --git a/www/docs/src/assets/index.ts b/docs/src/assets/index.ts similarity index 100% rename from www/docs/src/assets/index.ts rename to docs/src/assets/index.ts diff --git a/www/docs/src/assets/web-vitals/cv-analytics-dark.png b/docs/src/assets/web-vitals/cv-analytics-dark.png similarity index 100% rename from www/docs/src/assets/web-vitals/cv-analytics-dark.png rename to docs/src/assets/web-vitals/cv-analytics-dark.png diff --git a/www/docs/src/assets/web-vitals/cv-analytics-light.png b/docs/src/assets/web-vitals/cv-analytics-light.png similarity index 100% rename from www/docs/src/assets/web-vitals/cv-analytics-light.png rename to docs/src/assets/web-vitals/cv-analytics-light.png diff --git a/www/docs/src/assets/web-vitals/cv-byroute-dark.png b/docs/src/assets/web-vitals/cv-byroute-dark.png similarity index 100% rename from www/docs/src/assets/web-vitals/cv-byroute-dark.png rename to docs/src/assets/web-vitals/cv-byroute-dark.png diff --git a/www/docs/src/assets/web-vitals/cv-byroute-light.png b/docs/src/assets/web-vitals/cv-byroute-light.png similarity index 100% rename from www/docs/src/assets/web-vitals/cv-byroute-light.png rename to docs/src/assets/web-vitals/cv-byroute-light.png diff --git a/www/docs/src/assets/web-vitals/cv-progressbars-dark.png b/docs/src/assets/web-vitals/cv-progressbars-dark.png similarity index 100% rename from www/docs/src/assets/web-vitals/cv-progressbars-dark.png rename to docs/src/assets/web-vitals/cv-progressbars-dark.png diff --git a/www/docs/src/assets/web-vitals/cv-progressbars-light.png b/docs/src/assets/web-vitals/cv-progressbars-light.png similarity index 100% rename from www/docs/src/assets/web-vitals/cv-progressbars-light.png rename to docs/src/assets/web-vitals/cv-progressbars-light.png diff --git a/www/docs/src/assets/web-vitals/pagespeed-dark.png b/docs/src/assets/web-vitals/pagespeed-dark.png similarity index 100% rename from www/docs/src/assets/web-vitals/pagespeed-dark.png rename to docs/src/assets/web-vitals/pagespeed-dark.png diff --git a/www/docs/src/assets/web-vitals/pagespeed-light.png b/docs/src/assets/web-vitals/pagespeed-light.png similarity index 100% rename from www/docs/src/assets/web-vitals/pagespeed-light.png rename to docs/src/assets/web-vitals/pagespeed-light.png diff --git a/www/docs/src/components/ContributorList.astro b/docs/src/components/ContributorList.astro similarity index 100% rename from www/docs/src/components/ContributorList.astro rename to docs/src/components/ContributorList.astro diff --git a/www/docs/src/components/DropdownScript.astro b/docs/src/components/DropdownScript.astro similarity index 100% rename from www/docs/src/components/DropdownScript.astro rename to docs/src/components/DropdownScript.astro diff --git a/www/docs/src/components/FacePile.astro b/docs/src/components/FacePile.astro similarity index 100% rename from www/docs/src/components/FacePile.astro rename to docs/src/components/FacePile.astro diff --git a/www/docs/src/components/Gallery.astro b/docs/src/components/Gallery.astro similarity index 100% rename from www/docs/src/components/Gallery.astro rename to docs/src/components/Gallery.astro diff --git a/www/docs/src/components/Integration.astro b/docs/src/components/Integration.astro similarity index 100% rename from www/docs/src/components/Integration.astro rename to docs/src/components/Integration.astro diff --git a/www/docs/src/components/ModalScript.astro b/docs/src/components/ModalScript.astro similarity index 100% rename from www/docs/src/components/ModalScript.astro rename to docs/src/components/ModalScript.astro diff --git a/www/docs/src/components/PackageCatalog.astro b/docs/src/components/PackageCatalog.astro similarity index 93% rename from www/docs/src/components/PackageCatalog.astro rename to docs/src/components/PackageCatalog.astro index 6fa1f7543..28b325e68 100644 --- a/www/docs/src/components/PackageCatalog.astro +++ b/docs/src/components/PackageCatalog.astro @@ -42,7 +42,7 @@ const packages = (await getCollection('package-catalog'))

{description}

- {Astro.locals.t('package-catalog.readmore.start')} {Astro.locals.t('package-catalog.readmore.end')} + {Astro.locals.t('package-catalog.readmore.start')} {Astro.locals.t('package-catalog.readmore.end')}{(href.startsWith('https://') || href.startsWith('http://')) && ' ⤴'} <>{ index < packages.length - 1 &&
} )) diff --git a/www/docs/src/components/PreviewCard.astro b/docs/src/components/PreviewCard.astro similarity index 100% rename from www/docs/src/components/PreviewCard.astro rename to docs/src/components/PreviewCard.astro diff --git a/www/docs/src/components/ReadMore.astro b/docs/src/components/ReadMore.astro similarity index 100% rename from www/docs/src/components/ReadMore.astro rename to docs/src/components/ReadMore.astro diff --git a/www/docs/src/components/Sponsors.astro b/docs/src/components/Sponsors.astro similarity index 100% rename from www/docs/src/components/Sponsors.astro rename to docs/src/components/Sponsors.astro diff --git a/www/docs/src/components/ThemeHelperScript.astro b/docs/src/components/ThemeHelperScript.astro similarity index 100% rename from www/docs/src/components/ThemeHelperScript.astro rename to docs/src/components/ThemeHelperScript.astro diff --git a/www/docs/src/components/ToasterScript.astro b/docs/src/components/ToasterScript.astro similarity index 100% rename from www/docs/src/components/ToasterScript.astro rename to docs/src/components/ToasterScript.astro diff --git a/www/docs/src/components/TursoCLI.astro b/docs/src/components/TursoCLI.astro similarity index 100% rename from www/docs/src/components/TursoCLI.astro rename to docs/src/components/TursoCLI.astro diff --git a/www/docs/src/components/Version.astro b/docs/src/components/Version.astro similarity index 100% rename from www/docs/src/components/Version.astro rename to docs/src/components/Version.astro diff --git a/www/docs/src/components/Youtube.astro b/docs/src/components/Youtube.astro similarity index 100% rename from www/docs/src/components/Youtube.astro rename to docs/src/components/Youtube.astro diff --git a/www/docs/src/components/landing/Card.astro b/docs/src/components/landing/Card.astro similarity index 100% rename from www/docs/src/components/landing/Card.astro rename to docs/src/components/landing/Card.astro diff --git a/www/docs/src/components/landing/ListCard.astro b/docs/src/components/landing/ListCard.astro similarity index 100% rename from www/docs/src/components/landing/ListCard.astro rename to docs/src/components/landing/ListCard.astro diff --git a/www/docs/src/components/landing/SplitCard.astro b/docs/src/components/landing/SplitCard.astro similarity index 100% rename from www/docs/src/components/landing/SplitCard.astro rename to docs/src/components/landing/SplitCard.astro diff --git a/docs/src/content.config.ts b/docs/src/content.config.ts new file mode 100644 index 000000000..ea8715a5e --- /dev/null +++ b/docs/src/content.config.ts @@ -0,0 +1,65 @@ +import { defineCollection, reference, z } from 'astro:content'; +import { docsLoader, i18nLoader } from '@astrojs/starlight/loaders'; +import { docsSchema, i18nSchema } from '@astrojs/starlight/schema'; +import { glob } from 'astro/loaders'; + +const packageCatalogSchema = z.object({ + name: z.string(), + description: z.string(), + docsLink: z.string(), + githubURL: z.string(), + catalog: z + .union([z.literal('studiocms'), z.literal('community')]) + .optional() + .default('studiocms'), + isPlugin: z.boolean().optional().default(false), + publiclyUsable: z.boolean().optional().default(false), + released: z.boolean().optional().default(true), +}); + +const baseSchema = z.object({ + type: z.literal('base').optional().default('base'), + i18nReady: z.boolean().optional().default(false), +}); + +const integrationSchema = baseSchema.extend({ + type: z.literal('integration'), + catalogEntry: reference('package-catalog'), +}); + +const redirectSchema = baseSchema.extend({ + type: z.literal('redirect'), + redirect: z.string(), +}); + +export const collections = { + docs: defineCollection({ + loader: docsLoader(), + schema: docsSchema({ extend: z.union([baseSchema, integrationSchema, redirectSchema]) }), + }), + i18n: defineCollection({ + loader: i18nLoader(), + schema: i18nSchema({ + extend: z.object({ + 'site-title.labels.docs': z.string().optional(), + 'site-title.labels.main-site': z.string().optional(), + 'site-title.labels.live-demo': z.string().optional(), + 'sponsors.sponsoredby': z.string().optional(), + 'package-catalog.readmore.start': z.string().optional(), + 'package-catalog.readmore.end': z.string().optional(), + 'integration-labels.changelog': z.string().optional(), + 'contributors.core-packages': z.string().optional(), + 'contributors.ui-library': z.string().optional(), + 'contributors.devapps': z.string().optional(), + 'contributors.plugins': z.string().optional(), + 'contributors.documentation': z.string().optional(), + 'contributors.website': z.string().optional(), + 'contributors.bots': z.string().optional(), + }), + }), + }), + 'package-catalog': defineCollection({ + loader: glob({ pattern: '*.json', base: 'src/content/package-catalog' }), + schema: packageCatalogSchema, + }), +}; diff --git a/www/docs/src/content/docs/config-reference/dashboard.mdx b/docs/src/content/docs/config-reference/dashboard/index.mdx similarity index 68% rename from www/docs/src/content/docs/config-reference/dashboard.mdx rename to docs/src/content/docs/config-reference/dashboard/index.mdx index 13040ab26..944827c2d 100644 --- a/www/docs/src/content/docs/config-reference/dashboard.mdx +++ b/docs/src/content/docs/config-reference/dashboard/index.mdx @@ -79,59 +79,6 @@ export default defineStudioCMSConfig({ }) ``` -### `UnoCSSConfigOverride` - -- **Type:** `unocssConfigSchema{} | undefined{}` - -Allows customization of the UnoCSS Configuration. - -#### `injectReset` - -- **Type:** `boolean | undefined` -- **Default:** `false` - -Allows the user to enable or disable the UnoCSS Default Reset import. - -#### `injectEntry` - -- **Type:** `boolean | undefined` -- **Default:** `false` - -Allows the user to enable or disable the UnoCSS Default Entry import. - -#### `presetsConfig` - -- **Type:** `unocssPresetsSchema{} | undefined{}` - -Allows the user to modify the included UnoCSS Presets. - -##### `presetDaisyUI` - -- **Type:** `unocssDaisyUISchema{} | undefined{}` - -Allows the user to enable or disable the UnoCSS DaisyUI Preset. - -###### `themes` - -- **Type:** `Array | undefined` -- **Default:** `['dark', 'light']` - -Allows the user to use any of the available DaisyUI themes. - -###### `lightTheme` - -- **Type:** `string | undefined` -- **Default:** `'light'` - -Allows the user to set the default light theme. - -###### `darkTheme` - -- **Type:** `string | undefined` -- **Default:** `'dark'` - -Allows the user to set the default dark theme. - ### `developerConfig` - **Type:** `developerConfigSchema{} | undefined{}` diff --git a/www/docs/src/content/docs/config-reference/default-frontend-config.mdx b/docs/src/content/docs/config-reference/default-frontend-config/index.mdx similarity index 100% rename from www/docs/src/content/docs/config-reference/default-frontend-config.mdx rename to docs/src/content/docs/config-reference/default-frontend-config/index.mdx diff --git a/www/docs/src/content/docs/config-reference/image-service.mdx b/docs/src/content/docs/config-reference/image-service/index.mdx similarity index 100% rename from www/docs/src/content/docs/config-reference/image-service.mdx rename to docs/src/content/docs/config-reference/image-service/index.mdx diff --git a/docs/src/content/docs/config-reference/included-integrations/index.mdx b/docs/src/content/docs/config-reference/included-integrations/index.mdx new file mode 100644 index 000000000..ac1ac45c0 --- /dev/null +++ b/docs/src/content/docs/config-reference/included-integrations/index.mdx @@ -0,0 +1,28 @@ +--- +i18nReady: true +title: includedIntegrations +description: A reference page for includedIntegrations +sidebar: + order: 6 +--- + +`includedIntegrations` is an object that is used to determine which Astro Integrations should be included in the `studioCMS`. Currently there are three Integrations that can be included: `useAstroRobots`, `astroRobotsConfig`, and `useInoxSitemap`. + +## Usage + +```ts twoslash {2-6} title="studiocms.config.mjs" +import { defineStudioCMSConfig } from 'studiocms'; +// ---cut--- +export default defineStudioCMSConfig({ + includedIntegrations: { + robotsTXT: true, + }, +}) +``` + +### `robotsTXT` + +- **Type:** `boolean` | `RobotsConfig{}` | `undefined` +- **Default:** `true` + +Allows the user to enable/disable the use of the StudioCMS Custom `astro-robots-txt` Integration. For more information on this Integration please visit the [Astro Robots Integration](https://www.npmjs.com/package/astro-robots). \ No newline at end of file diff --git a/www/docs/src/content/docs/config-reference/options-schema.mdx b/docs/src/content/docs/config-reference/options-schema.mdx similarity index 88% rename from www/docs/src/content/docs/config-reference/options-schema.mdx rename to docs/src/content/docs/config-reference/options-schema.mdx index a1817c1e7..76e860dad 100644 --- a/www/docs/src/content/docs/config-reference/options-schema.mdx +++ b/docs/src/content/docs/config-reference/options-schema.mdx @@ -21,7 +21,9 @@ export default defineStudioCMSConfig({ defaultFrontEndConfig: {}, dashboardConfig: {}, includedIntegrations: {}, + plugins: [], dateLocale: 'en-us', + overrides: {}, verbose: false, }); ``` @@ -85,6 +87,25 @@ export default defineStudioCMSConfig({ [See `includedIntegrations` for full options](/config-reference/included-integrations) +## `plugins` + +`plugins` is an array of plugins that can be used to extend the functionality of `studioCMS`. + +- **Type:** `Array` +- **Default:** `[]` + +### Usage + +```ts twoslash {2} title="studiocms.config.mjs" +import { defineStudioCMSConfig } from 'studiocms'; +// ---cut--- +export default defineStudioCMSConfig({ + plugins: [ + // Add your plugins here + ], +}) +``` + ## `dateLocale` Date locale used for formatting dates diff --git a/www/docs/src/content/docs/config-reference/overrides.mdx b/docs/src/content/docs/config-reference/overrides/index.mdx similarity index 100% rename from www/docs/src/content/docs/config-reference/overrides.mdx rename to docs/src/content/docs/config-reference/overrides/index.mdx diff --git a/www/docs/src/content/docs/config-reference/renderer-config.mdx b/docs/src/content/docs/config-reference/renderer-config/index.mdx similarity index 68% rename from www/docs/src/content/docs/config-reference/renderer-config.mdx rename to docs/src/content/docs/config-reference/renderer-config/index.mdx index c28bec468..34e5d05f1 100644 --- a/www/docs/src/content/docs/config-reference/renderer-config.mdx +++ b/docs/src/content/docs/config-reference/renderer-config/index.mdx @@ -12,10 +12,10 @@ import ReadMore from '~/components/ReadMore.astro'; The Markdown Content Renderer to use for rendering pages within StudioCMS -- **Type:** `'marked'` | `'markdoc'` | `'astro'` | `'mdx'` | `CustomRenderer` -- **Default:** `'marked'` +- **Type:** `'astro'` | `'markdoc'` | `'mdx'` | `CustomRenderer` +- **Default:** `'astro'` -`renderer` determines how Markdown content should be rendered in `studioCMS`. This is used to setup your content data. The default value is `marked` but you can also use `markdoc` or `astro` which uses Astro's built-in Remark processor. +`renderer` determines how Markdown content should be rendered in `studioCMS`. This is used to setup your content data. The default value is `astro` which uses the Astro built-in markdown processor but you can also use `markdoc`, `mdx` or define a Custom Renderer. ### Usage @@ -24,22 +24,13 @@ import { defineStudioCMSConfig } from 'studiocms'; // ---cut--- export default defineStudioCMSConfig({ rendererConfig: { - renderer: 'marked', - markedConfig: {}, + renderer: 'astro', markdocConfig: {}, mdxConfig: {}, }, }) ``` -## `markedConfig` - -`markedConfig` is an object that is used to determine how content should be rendered in the `studioCMS`. This is used to setup your content data. - -### Usage - -[See `markedConfig` for full options](/config-reference/marked-config) - ## `markdocConfig` `markdocConfig` is an object that is used to determine how content should be rendered in `studiocms` while using the MarkDoc renderer. diff --git a/www/docs/src/content/docs/config-reference/markdoc-config.mdx b/docs/src/content/docs/config-reference/renderer-config/markdoc-config.mdx similarity index 95% rename from www/docs/src/content/docs/config-reference/markdoc-config.mdx rename to docs/src/content/docs/config-reference/renderer-config/markdoc-config.mdx index 612c3231c..37d7c00b5 100644 --- a/www/docs/src/content/docs/config-reference/markdoc-config.mdx +++ b/docs/src/content/docs/config-reference/renderer-config/markdoc-config.mdx @@ -30,7 +30,7 @@ This property has the following options: ### `renderType` -- **Type:** `'html' | 'react-static' | markdocRenderer` +- **Type:** `'html' | 'react-static' | MarkdocRenderer` The MarkDoc content renderer type to use for rendering pages and posts can be `html`, `react-static` or a custom markdocRenderer. diff --git a/www/docs/src/content/docs/config-reference/mdx-config.mdx b/docs/src/content/docs/config-reference/renderer-config/mdx-config.mdx similarity index 100% rename from www/docs/src/content/docs/config-reference/mdx-config.mdx rename to docs/src/content/docs/config-reference/renderer-config/mdx-config.mdx diff --git a/www/docs/src/content/docs/contributing/code-contributions.mdx b/docs/src/content/docs/contributing/code-contributions.mdx similarity index 97% rename from www/docs/src/content/docs/contributing/code-contributions.mdx rename to docs/src/content/docs/contributing/code-contributions.mdx index 2d3144c20..4978cf68b 100644 --- a/www/docs/src/content/docs/contributing/code-contributions.mdx +++ b/docs/src/content/docs/contributing/code-contributions.mdx @@ -27,7 +27,7 @@ Scan through our [existing issues](https://github.com/withstudiocms/studiocms/is - Using the command line: - [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them. -2. Install or update **Node.js** and **pnpm**, to the versions specified in [`.prototools`](https://github.com/astrolicious/studiocms/blob/main/.prototools). +2. Install or update **Node.js** and **pnpm**, to the versions specified in [`.prototools`](https://github.com/withstudiocms/studiocms/blob/main/.prototools). 3. Create a working branch and start with your changes! diff --git a/www/docs/src/content/docs/contributing/getting-started.mdx b/docs/src/content/docs/contributing/getting-started.mdx similarity index 100% rename from www/docs/src/content/docs/contributing/getting-started.mdx rename to docs/src/content/docs/contributing/getting-started.mdx diff --git a/www/docs/src/content/docs/contributing/translations.mdx b/docs/src/content/docs/contributing/translations.mdx similarity index 100% rename from www/docs/src/content/docs/contributing/translations.mdx rename to docs/src/content/docs/contributing/translations.mdx diff --git a/www/docs/src/content/docs/customizing/studiocms-renderers/index.mdx b/docs/src/content/docs/customizing/studiocms-renderers/index.mdx similarity index 73% rename from www/docs/src/content/docs/customizing/studiocms-renderers/index.mdx rename to docs/src/content/docs/customizing/studiocms-renderers/index.mdx index f7e5ddaf6..457732e55 100644 --- a/www/docs/src/content/docs/customizing/studiocms-renderers/index.mdx +++ b/docs/src/content/docs/customizing/studiocms-renderers/index.mdx @@ -13,17 +13,16 @@ import { defineStudioCMSConfig } from 'studiocms'; // ---cut--- export default defineStudioCMSConfig({ rendererConfig: { - renderer: 'marked', - markedConfig: {}, + renderer: 'astro', markdocConfig: {}, mdxConfig: {}, }, }) ``` -## Marked Renderer +## Astro Remark Renderer -The default renderer for StudioCMS is the `marked` renderer. This renderer is used to render markdown content into HTML. This renderer is used by default when no other renderer is specified. +The default renderer for StudioCMS is the `astro` renderer. This renderer is used to render markdown content into HTML. This renderer is used by default when no other renderer is specified and adapts the markdown configuration from your `astro.config.mjs` file. ### Usage @@ -32,14 +31,14 @@ import { defineStudioCMSConfig } from 'studiocms'; // ---cut--- export default defineStudioCMSConfig({ rendererConfig: { - renderer: 'marked' + renderer: 'astro' }, }) ``` ### Configuration -see [Marked Configuration](/config-reference/marked-config/) for more information on how to configure the Marked renderer. +see [Marked Configuration](/config-reference/renderer-config/marked-config/) for more information on how to configure the Marked renderer. ## MDX Renderer @@ -59,7 +58,7 @@ export default defineStudioCMSConfig({ ### Configuration -See [MDX Configuration](/config-reference/mdx-config/) for more information on how to configure the MDX renderer. +See [MDX Configuration](/config-reference/renderer-config/mdx-config/) for more information on how to configure the MDX renderer. ## MarkDoc Renderer @@ -79,7 +78,7 @@ export default defineStudioCMSConfig({ ### Configuration -see [MarkDoc Configuration](/config-reference/markdoc-config/) for more information on how to configure the MarkDoc renderer. +see [MarkDoc Configuration](/config-reference/renderer-config/markdoc-config/) for more information on how to configure the MarkDoc renderer. ### Custom Renderers @@ -87,22 +86,6 @@ StudioCMS's MarkDoc renderer allows you to define custom renderers for your cont see [MarkDoc Renderers](/customizing/studiocms-renderers/markdoc/) for more information on how to define custom renderers for MarkDoc. -## Astro Remark Renderer - -The default Astro renderer for their markdown content. This renderer adapts the markdown configuration from your `astro.config.mjs` file. - -### Usage - -```ts twoslash {3} title="studiocms.config.mjs" -import { defineStudioCMSConfig } from 'studiocms'; -// ---cut--- -export default defineStudioCMSConfig({ - rendererConfig: { - renderer: 'astro' - }, -}) -``` - ### Configuration See [Astro's Markdown Configuration](https://docs.astro.build/en/reference/configuration-reference/#markdown-options) for more information on how to configure the Astro Remark renderer. diff --git a/www/docs/src/content/docs/customizing/studiocms-renderers/markdoc.mdx b/docs/src/content/docs/customizing/studiocms-renderers/markdoc.mdx similarity index 96% rename from www/docs/src/content/docs/customizing/studiocms-renderers/markdoc.mdx rename to docs/src/content/docs/customizing/studiocms-renderers/markdoc.mdx index 1ce9dc500..fd6f358b0 100644 --- a/www/docs/src/content/docs/customizing/studiocms-renderers/markdoc.mdx +++ b/docs/src/content/docs/customizing/studiocms-renderers/markdoc.mdx @@ -108,4 +108,4 @@ Normally, users will not need to install the `@studiocms/renderers` package dire {/* */} -For more information on how to configure the MarkDoc renderer, see the [MarkDoc Configuration](/config-reference/markdoc-config/) documentation. +For more information on how to configure the MarkDoc renderer, see the [MarkDoc Configuration](/config-reference/renderer-config/markdoc-config/) documentation. diff --git a/www/docs/src/content/docs/how-it-works/index.mdx b/docs/src/content/docs/how-it-works/index.mdx similarity index 95% rename from www/docs/src/content/docs/how-it-works/index.mdx rename to docs/src/content/docs/how-it-works/index.mdx index cd5125b57..1a2bfbe9f 100644 --- a/www/docs/src/content/docs/how-it-works/index.mdx +++ b/docs/src/content/docs/how-it-works/index.mdx @@ -85,15 +85,12 @@ The StudioCMS Auth integration provides authentication configuration options for ### StudioCMS: Dashboard -The StudioCMS Dashboard is a web interface that allows you to manage your StudioCMS project. It provides a user-friendly interface for creating, editing, and deleting content for your project. The StudioCMS Dashboard is built with [Astro](https://astro.build) and [DaisyUI](https://daisyui.com/) with [UnoCSS](https://unocss.dev). +The StudioCMS Dashboard is a web interface that allows you to manage your StudioCMS project. It provides a user-friendly interface for creating, editing, and deleting content for your project. The StudioCMS Dashboard is built with [Astro](https://astro.build) and our [`@studiocms/ui](/customizing/studiocms-ui/) library. #### Tech Stack - **Dashboard**: The StudioCMS Dashboard provides a user-friendly interface for managing your StudioCMS project. -- **UnoCSS**: The StudioCMS Dashboard uses UnoCSS for styling and theming. -- **DaisyUI**: The StudioCMS Dashboard uses DaisyUI for styling and theming. - **AuthConfig**: The StudioCMS Dashboard provides authentication configuration options. -- **Astrolace**: The StudioCMS Dashboard is built using [Astrolace](https://github.com/matthiesenxyz/astrolace) a [Shoelace.style](https://shoelace.style) Astro Integration. - **DashboardRouteOverride**: The StudioCMS Dashboard provides a dashboard route override configuration option. Allowing you to change the default route for the StudioCMS Dashboard and API. ### StudioCMS: ImageHandler diff --git a/www/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx similarity index 99% rename from www/docs/src/content/docs/index.mdx rename to docs/src/content/docs/index.mdx index d24f71530..47b78c449 100644 --- a/www/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -51,7 +51,7 @@ import { Center } from '@studiocms/ui/components'; For a more in-depth guide, check out the [Getting Started](/start-here/getting-started) guide. - Looking for a libSQL database? Check out [Turso](https://turso.tech). + Looking for a libSQL database? Check out [Turso](https://tur.so/studiocms). diff --git a/www/docs/src/content/docs/package-catalog/community-integrations/web-vitals.mdx b/docs/src/content/docs/package-catalog/community-integrations/web-vitals.mdx similarity index 100% rename from www/docs/src/content/docs/package-catalog/community-integrations/web-vitals.mdx rename to docs/src/content/docs/package-catalog/community-integrations/web-vitals.mdx diff --git a/www/docs/src/content/docs/package-catalog/index.mdx b/docs/src/content/docs/package-catalog/index.mdx similarity index 100% rename from www/docs/src/content/docs/package-catalog/index.mdx rename to docs/src/content/docs/package-catalog/index.mdx diff --git a/www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-full-page-view.png b/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-full-page-view.png similarity index 100% rename from www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-full-page-view.png rename to docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-full-page-view.png diff --git a/www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-closed.png b/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-closed.png similarity index 100% rename from www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-closed.png rename to docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-closed.png diff --git a/www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-expanded.png b/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-expanded.png similarity index 100% rename from www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-expanded.png rename to docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-expanded.png diff --git a/www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-2-importer.png b/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-2-importer.png similarity index 100% rename from www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-2-importer.png rename to docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-2-importer.png diff --git a/www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-blog.mdx b/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-blog.mdx similarity index 80% rename from www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-blog.mdx rename to docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-blog.mdx index 6c2c2e85f..4ed6bb1cd 100644 --- a/www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-blog.mdx +++ b/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-blog.mdx @@ -39,7 +39,7 @@ This Astro integration enables the StudioCMS Blog feature in your Astro project. 2. Add `@studiocms/blog` to your astro config file: - ```ts twoslash title="astro.config.mjs" ins={5, 14-19} + ```ts twoslash title="astro.config.mjs" ins={5, 14-21} import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; import db from '@astrojs/db'; @@ -52,12 +52,15 @@ This Astro integration enables the StudioCMS Blog feature in your Astro project. adapter: node({ mode: "standalone" }), integrations: [ db(), // REQUIRED - studioCMS(), // REQUIRED - studioCMSBlog({ - config: { - title: "My StudioCMS Blog", - description: "A Simple Blog build with Astro, AstroDB, and StudioCMS.", - }, + studioCMS({ + plugins: [ + studioCMSBlog({ + config: { + title: "My StudioCMS Blog", + description: "A Simple Blog build with Astro, AstroDB, and StudioCMS.", + }, + }), + ], }), ], }); @@ -78,7 +81,7 @@ This integration will add the following new routes to your StudioCMS Controlled ### Example config -```ts twoslash title="astro.config.mjs" {3-4, 13-14} ins={5, 15-20} +```ts twoslash title="astro.config.mjs" {3-4, 13-14} ins={5, 15-22} import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; import db from '@astrojs/db'; @@ -92,13 +95,16 @@ export default defineConfig({ adapter: node({ mode: "standalone" }), integrations: [ db(), // REQUIRED - studioCMS(), // REQUIRED - studioCMSBlog({ - config: { - title: "My StudioCMS Blog", - description: "A Simple Blog build with Astro, Astrojs/DB, and StudioCMS.", - }, - }), + studioCMS({ + plugins: [ + studioCMSBlog({ + config: { + title: "My StudioCMS Blog", + description: "A Simple Blog build with Astro, AstroDB, and StudioCMS.", + }, + }), + ], + }), ], }); ``` diff --git a/www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-devapps.mdx b/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-devapps.mdx similarity index 100% rename from www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-devapps.mdx rename to docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-devapps.mdx diff --git a/www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-ui.mdx b/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-ui.mdx similarity index 77% rename from www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-ui.mdx rename to docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-ui.mdx index 36bc106d5..ba39f6b6c 100644 --- a/www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-ui.mdx +++ b/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-ui.mdx @@ -1,9 +1,10 @@ --- title: "@studiocms/ui" type: "redirect" -redirect: "/customizing/studiocms-ui/" +redirect: "https://ui.studiocms.dev" description: "The UI library for StudioCMS. Includes the layouts & components we use to build StudioCMS." sidebar: + label: "@studiocms/ui ⤴" badge: text: 'Publicly Usable' variant: 'caution' diff --git a/www/docs/src/content/docs/start-here/configuration.mdx b/docs/src/content/docs/start-here/configuration.mdx similarity index 100% rename from www/docs/src/content/docs/start-here/configuration.mdx rename to docs/src/content/docs/start-here/configuration.mdx diff --git a/www/docs/src/content/docs/start-here/environment-variables.mdx b/docs/src/content/docs/start-here/environment-variables.mdx similarity index 83% rename from www/docs/src/content/docs/start-here/environment-variables.mdx rename to docs/src/content/docs/start-here/environment-variables.mdx index 38c8d48bf..c78b20061 100644 --- a/www/docs/src/content/docs/start-here/environment-variables.mdx +++ b/docs/src/content/docs/start-here/environment-variables.mdx @@ -17,12 +17,10 @@ You can create a `.env` file in the root directory of your project and add the r For future reference on how to work with environment variables within Astro you can checkout [Environment Variables](https://docs.astro.build/guides/environment-variables) from the Astro documentation. - - ## Required Environment Variables +In order to use StudioCMS, there are a few required environment variables that you must set up in your `.env` file. + ### Database URL and Token for `@astrojs/db` `ASTRO_DB_REMOTE_URL` - The connection URL to your libSQL server @@ -50,13 +48,15 @@ openssl rand --base64 16 ``` -## oAuth Authentication Environment Variables +## Optional Environment Variables + +### oAuth Authentication Environment Variables For more information about setting up oAuth authentication, see the [Configure oAuth Authentication](/start-here/getting-started/#optional-configure-oauth-authentication) documentation. -### GitHub (Optional) +#### GitHub (Optional) To authenticate with GitHub, you need to add the following environment variables to your `.env` file: @@ -71,7 +71,7 @@ CMS_GITHUB_REDIRECT_URI= `CMS_GITHUB_REDIRECT_URI` is optional if you are using multiple redirect URIs with GitHub oAuth. -### Discord (Optional) +#### Discord (Optional) ```bash title=".env" # credentials for Discord OAuth @@ -80,7 +80,7 @@ CMS_DISCORD_CLIENT_SECRET= CMS_DISCORD_REDIRECT_URI= ``` -### Google (Optional) +#### Google (Optional) ```bash title=".env" # credentials for Google OAuth @@ -89,7 +89,7 @@ CMS_GOOGLE_CLIENT_SECRET= CMS_GOOGLE_REDIRECT_URI= ``` -### Auth0 (Optional) +#### Auth0 (Optional) ```bash title=".env" # credentials for auth0 OAuth @@ -99,9 +99,9 @@ CMS_AUTH0_DOMAIN= CMS_AUTH0_REDIRECT_URI= ``` -## Image Handler Environment Variables +### Image Handler Environment Variables -### Cloudinary (Optional) +#### Cloudinary (Optional) If you choose to use the built-in Cloudinary plugin, you will need to define the following: diff --git a/www/docs/src/content/docs/start-here/gallery.mdx b/docs/src/content/docs/start-here/gallery.mdx similarity index 66% rename from www/docs/src/content/docs/start-here/gallery.mdx rename to docs/src/content/docs/start-here/gallery.mdx index d75881bb0..aa423bba3 100644 --- a/www/docs/src/content/docs/start-here/gallery.mdx +++ b/docs/src/content/docs/start-here/gallery.mdx @@ -1,8 +1,11 @@ --- i18nReady: true title: Gallery -description: A small gallery of images to show off the StudioCMS +description: A small gallery of images to show off StudioCMS tableOfContents: false +banner: + content: | + These images are for 0.1.0-beta.7 and below. sidebar: order: 5 --- diff --git a/www/docs/src/content/docs/start-here/getting-started.mdx b/docs/src/content/docs/start-here/getting-started.mdx similarity index 100% rename from www/docs/src/content/docs/start-here/getting-started.mdx rename to docs/src/content/docs/start-here/getting-started.mdx diff --git a/www/docs/src/content/docs/start-here/why-studioCMS.mdx b/docs/src/content/docs/start-here/why-studioCMS.mdx similarity index 100% rename from www/docs/src/content/docs/start-here/why-studioCMS.mdx rename to docs/src/content/docs/start-here/why-studioCMS.mdx diff --git a/www/docs/src/content/docs/studioCMS-dark.png b/docs/src/content/docs/studioCMS-dark.png similarity index 100% rename from www/docs/src/content/docs/studioCMS-dark.png rename to docs/src/content/docs/studioCMS-dark.png diff --git a/www/docs/src/content/docs/studioCMS.png b/docs/src/content/docs/studioCMS.png similarity index 100% rename from www/docs/src/content/docs/studioCMS.png rename to docs/src/content/docs/studioCMS.png diff --git a/www/docs/src/content/i18n/en.json b/docs/src/content/i18n/en.json similarity index 100% rename from www/docs/src/content/i18n/en.json rename to docs/src/content/i18n/en.json diff --git a/www/docs/src/content/package-catalog/astrojs-web-vitals.json b/docs/src/content/package-catalog/astrojs-web-vitals.json similarity index 100% rename from www/docs/src/content/package-catalog/astrojs-web-vitals.json rename to docs/src/content/package-catalog/astrojs-web-vitals.json diff --git a/www/docs/src/content/package-catalog/studiocms-blog.json b/docs/src/content/package-catalog/studiocms-blog.json similarity index 100% rename from www/docs/src/content/package-catalog/studiocms-blog.json rename to docs/src/content/package-catalog/studiocms-blog.json diff --git a/www/docs/src/content/package-catalog/studiocms-devapps.json b/docs/src/content/package-catalog/studiocms-devapps.json similarity index 100% rename from www/docs/src/content/package-catalog/studiocms-devapps.json rename to docs/src/content/package-catalog/studiocms-devapps.json diff --git a/www/docs/src/content/package-catalog/studiocms-ui.json b/docs/src/content/package-catalog/studiocms-ui.json similarity index 84% rename from www/docs/src/content/package-catalog/studiocms-ui.json rename to docs/src/content/package-catalog/studiocms-ui.json index 0914fe8ad..4eedb3481 100644 --- a/www/docs/src/content/package-catalog/studiocms-ui.json +++ b/docs/src/content/package-catalog/studiocms-ui.json @@ -2,9 +2,9 @@ "$schema": "../../../.astro/collections/package-catalog.schema.json", "name": "@studiocms/ui", "description": "The UI library for StudioCMS. Includes the layouts & components we use to build StudioCMS.", - "docsLink": "/customizing/studiocms-ui/", + "docsLink": "https://ui.studiocms.dev", "githubURL": "https://github.com/withstudiocms/studiocms/tree/main/packages/studiocms_ui/", "catalog": "studiocms", - "released": false, + "released": true, "publiclyUsable": true } diff --git a/www/docs/src/content/package-catalog/studiocms.json b/docs/src/content/package-catalog/studiocms.json similarity index 100% rename from www/docs/src/content/package-catalog/studiocms.json rename to docs/src/content/package-catalog/studiocms.json diff --git a/www/docs/src/env.d.ts b/docs/src/env.d.ts similarity index 100% rename from www/docs/src/env.d.ts rename to docs/src/env.d.ts diff --git a/www/docs/src/plugins/rehype.types.ts b/docs/src/plugins/rehype.types.ts similarity index 100% rename from www/docs/src/plugins/rehype.types.ts rename to docs/src/plugins/rehype.types.ts diff --git a/www/docs/src/plugins/rehypeAutolink.ts b/docs/src/plugins/rehypeAutolink.ts similarity index 96% rename from www/docs/src/plugins/rehypeAutolink.ts rename to docs/src/plugins/rehypeAutolink.ts index 237af75c0..97f6dc2ae 100644 --- a/www/docs/src/plugins/rehypeAutolink.ts +++ b/docs/src/plugins/rehypeAutolink.ts @@ -3,7 +3,7 @@ import { h } from 'hastscript'; import { escape as esc } from 'html-escaper'; import rehypeAutoLink from 'rehype-autolink-headings'; import type { Options as rehypeAutolinkHeadingsOptions } from 'rehype-autolink-headings'; -import type { RehypePlugin } from './rehype.types'; +import type { RehypePlugin } from './rehype.types.ts'; const AnchorLinkIcon = h( 'span', diff --git a/www/docs/src/plugins/rehypeExternalLinks.ts b/docs/src/plugins/rehypeExternalLinks.ts similarity index 87% rename from www/docs/src/plugins/rehypeExternalLinks.ts rename to docs/src/plugins/rehypeExternalLinks.ts index f1203c27c..9f519f119 100644 --- a/www/docs/src/plugins/rehypeExternalLinks.ts +++ b/docs/src/plugins/rehypeExternalLinks.ts @@ -1,5 +1,5 @@ import rehypeExternal from 'rehype-external-links'; -import type { RehypePlugin } from './rehype.types'; +import type { RehypePlugin } from './rehype.types.ts'; // biome-ignore lint/suspicious/noExplicitAny: any is used to match the generic type export const rehypeExternalLinks: [RehypePlugin, any] = [ diff --git a/www/docs/src/plugins/rehypePluginKit.ts b/docs/src/plugins/rehypePluginKit.ts similarity index 51% rename from www/docs/src/plugins/rehypePluginKit.ts rename to docs/src/plugins/rehypePluginKit.ts index 4a74d0a0b..972516689 100644 --- a/www/docs/src/plugins/rehypePluginKit.ts +++ b/docs/src/plugins/rehypePluginKit.ts @@ -1,7 +1,7 @@ import rehypeSlug from 'rehype-slug'; -import type { RehypePlugins } from './rehype.types'; -import rehypeAutolinkHeadings from './rehypeAutolink'; -import rehypeExternalLinks from './rehypeExternalLinks'; +import type { RehypePlugins } from './rehype.types.ts'; +import rehypeAutolinkHeadings from './rehypeAutolink.ts'; +import rehypeExternalLinks from './rehypeExternalLinks.ts'; export const rehypePluginKit: RehypePlugins = [ rehypeSlug, diff --git a/www/docs/src/share-link.ts b/docs/src/share-link.ts similarity index 100% rename from www/docs/src/share-link.ts rename to docs/src/share-link.ts diff --git a/www/docs/src/starlightOverrides/Head.astro b/docs/src/starlightOverrides/Head.astro similarity index 100% rename from www/docs/src/starlightOverrides/Head.astro rename to docs/src/starlightOverrides/Head.astro diff --git a/www/docs/src/starlightOverrides/PageTitle.astro b/docs/src/starlightOverrides/PageTitle.astro similarity index 100% rename from www/docs/src/starlightOverrides/PageTitle.astro rename to docs/src/starlightOverrides/PageTitle.astro diff --git a/www/docs/src/starlightOverrides/Sidebar.astro b/docs/src/starlightOverrides/Sidebar.astro similarity index 100% rename from www/docs/src/starlightOverrides/Sidebar.astro rename to docs/src/starlightOverrides/Sidebar.astro diff --git a/www/docs/src/starlightOverrides/SiteTitle.astro b/docs/src/starlightOverrides/SiteTitle.astro similarity index 100% rename from www/docs/src/starlightOverrides/SiteTitle.astro rename to docs/src/starlightOverrides/SiteTitle.astro diff --git a/www/docs/src/styles/sponsorcolors.css b/docs/src/styles/sponsorcolors.css similarity index 100% rename from www/docs/src/styles/sponsorcolors.css rename to docs/src/styles/sponsorcolors.css diff --git a/www/docs/src/styles/starlight.css b/docs/src/styles/starlight.css similarity index 94% rename from www/docs/src/styles/starlight.css rename to docs/src/styles/starlight.css index 2e424e718..919e3a3cb 100644 --- a/www/docs/src/styles/starlight.css +++ b/docs/src/styles/starlight.css @@ -77,9 +77,11 @@ border-radius: 8px; } -code, +div.expressive-code, starlight-file-tree { - border-radius: 4px; + border-radius: 8px; + overflow: hidden; + border: 1px solid var(--ec-brdCol); } aside.starlight-aside { @@ -195,3 +197,10 @@ button[aria-label="Menu"][aria-controls="starlight__sidebar"] { a { text-decoration: none; } + +.expressive-code figcaption, +.expressive-code figcaption::before, +.expressive-code pre, +.expressive-code span.title { + border: none !important; +} diff --git a/www/docs/src/typedocHelpers.ts b/docs/src/typedocHelpers.ts similarity index 96% rename from www/docs/src/typedocHelpers.ts rename to docs/src/typedocHelpers.ts index db522629b..b5700471e 100644 --- a/www/docs/src/typedocHelpers.ts +++ b/docs/src/typedocHelpers.ts @@ -2,7 +2,7 @@ import type { StarlightTypeDocOptions } from 'starlight-typedoc'; // Utility function to create TypeDoc related paths export function getFilePathToPackage(name: string, path: string) { - return `../../packages/${name}/${path}`; + return `../packages/${name}/${path}`; } // Utility function to create TypeDoc options for the StudioCMS packages so that each package documentation is the same when generated diff --git a/www/docs/src/util-server.ts b/docs/src/util-server.ts similarity index 100% rename from www/docs/src/util-server.ts rename to docs/src/util-server.ts diff --git a/www/docs/src/util/SponsorLink.astro b/docs/src/util/SponsorLink.astro similarity index 100% rename from www/docs/src/util/SponsorLink.astro rename to docs/src/util/SponsorLink.astro diff --git a/www/docs/src/util/contributors.config.ts b/docs/src/util/contributors.config.ts similarity index 94% rename from www/docs/src/util/contributors.config.ts rename to docs/src/util/contributors.config.ts index a4d7b0450..0bb3d6702 100644 --- a/www/docs/src/util/contributors.config.ts +++ b/docs/src/util/contributors.config.ts @@ -23,9 +23,10 @@ export const contributorConfig = (Astro: AstroGlobal): ContributorConfig[] => [ paths: [ // OLD Paths 'packages/studioCMS/', + 'playgrounds/node/', // NEW Paths 'README.md', - 'playgrounds/node/', + 'playground/', 'packages/studiocms/', 'packages/studiocms_assets/', 'packages/studiocms_auth/', @@ -48,6 +49,10 @@ export const contributorConfig = (Astro: AstroGlobal): ContributorConfig[] => [ type: 'byPath', paths: ['packages/studiocms_ui/', 'playgrounds/ui/'], }, + { + repo: 'withstudiocms/ui', + type: 'all', + }, ], }, { @@ -81,7 +86,7 @@ export const contributorConfig = (Astro: AstroGlobal): ContributorConfig[] => [ { repo: 'withstudiocms/studiocms', type: 'byPath', - paths: ['www/docs/'], + paths: ['www/docs/', 'docs/'], }, ], }, diff --git a/www/docs/src/util/getContributors.ts b/docs/src/util/getContributors.ts similarity index 98% rename from www/docs/src/util/getContributors.ts rename to docs/src/util/getContributors.ts index 79c8597ef..37eeaa396 100644 --- a/www/docs/src/util/getContributors.ts +++ b/docs/src/util/getContributors.ts @@ -1,6 +1,6 @@ import type { AstroGlobal } from 'astro'; -import { cachedFetch } from '../util-server'; -import { StudioCMSServiceAccounts, contributorConfig } from './contributors.config'; +import { cachedFetch } from '../util-server.ts'; +import { StudioCMSServiceAccounts, contributorConfig } from './contributors.config.ts'; export interface Contributor { login: string; diff --git a/www/docs/starlight-types.ts b/docs/starlight-types.ts similarity index 100% rename from www/docs/starlight-types.ts rename to docs/starlight-types.ts diff --git a/www/docs/starlight-virtual.d.ts b/docs/starlight-virtual.d.ts similarity index 100% rename from www/docs/starlight-virtual.d.ts rename to docs/starlight-virtual.d.ts diff --git a/www/docs/tsconfig.json b/docs/tsconfig.json similarity index 65% rename from www/docs/tsconfig.json rename to docs/tsconfig.json index 754f6f39c..462e11b6d 100644 --- a/www/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -1,13 +1,12 @@ { "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], "exclude": ["dist"], "compilerOptions": { "allowJs": true, - "moduleResolution": "bundler", "baseUrl": ".", "paths": { "~/*": ["src/*"] - }, - "outDir": "../../.moon/cache/types/www/docs" + } } } diff --git a/www/docs/typedoc.config.ts b/docs/typedoc.config.ts similarity index 94% rename from www/docs/typedoc.config.ts rename to docs/typedoc.config.ts index 6f4637a49..773bdd190 100644 --- a/www/docs/typedoc.config.ts +++ b/docs/typedoc.config.ts @@ -1,7 +1,7 @@ import type { StarlightPlugin } from '@astrojs/starlight/types'; import { createStarlightTypeDocPlugin } from 'starlight-typedoc'; -import { getFilePathToPackage, makeTypedocOpts } from './src/typedocHelpers'; -import type { SidebarGroup } from './starlight-types'; +import { getFilePathToPackage, makeTypedocOpts } from './src/typedocHelpers.ts'; +import type { SidebarGroup } from './starlight-types.ts'; // Create Starlight TypeDoc Plugins for different parts of the Astro StudioCMS Project @@ -39,11 +39,7 @@ const TypeDocPlugins = (isProd: boolean, testingMode: boolean): StarlightPlugin[ name: 'studiocms', output: 'studiocms', dir: 'studiocms', - entryPoints: [ - getFilePathToPackage('studiocms', 'src/index.ts'), - getFilePathToPackage('studiocms', 'src/integration.ts'), - getFilePathToPackage('studiocms', 'src/updateCheck.ts'), - ], + entryPoints: [getFilePathToPackage('studiocms', 'src/index.ts')], }) ), tdAuth( @@ -95,9 +91,9 @@ const TypeDocPlugins = (isProd: boolean, testingMode: boolean): StarlightPlugin[ getFilePathToPackage('studiocms_core', 'src/schemas/config/index.ts'), getFilePathToPackage('studiocms_core', 'src/schemas/config/integrations.ts'), getFilePathToPackage('studiocms_core', 'src/schemas/config/markdoc.ts'), - getFilePathToPackage('studiocms_core', 'src/schemas/config/marked.ts'), getFilePathToPackage('studiocms_core', 'src/schemas/config/rendererConfig.ts'), - getFilePathToPackage('studiocms_core', 'src/schemas/config/unocss.ts'), + getFilePathToPackage('studiocms_core', 'src/schemas/plugins/shared.ts'), + getFilePathToPackage('studiocms_core', 'src/schemas/plugins/index.ts'), getFilePathToPackage('studiocms_core', 'src/lib/index.ts'), getFilePathToPackage('studiocms_core', 'src/lib/configManager.ts'), getFilePathToPackage('studiocms_core', 'src/lib/convertDashboardLinksType.ts'), @@ -116,6 +112,7 @@ const TypeDocPlugins = (isProd: boolean, testingMode: boolean): StarlightPlugin[ getFilePathToPackage('studiocms_core', 'src/db/tables.ts'), getFilePathToPackage('studiocms_core', 'src/db/tsTables.ts'), getFilePathToPackage('studiocms_core', 'src/components/index.ts'), + getFilePathToPackage('studiocms_core', 'src/i18n/index.ts'), ], }) ), @@ -145,6 +142,7 @@ const TypeDocPlugins = (isProd: boolean, testingMode: boolean): StarlightPlugin[ entryPoints: [ getFilePathToPackage('studiocms_frontend', 'src/index.ts'), getFilePathToPackage('studiocms_frontend', 'src/integration.ts'), + getFilePathToPackage('studiocms_frontend', 'src/schema.ts'), getFilePathToPackage('studiocms_frontend', 'src/components/index.ts'), ], }) @@ -160,7 +158,7 @@ const TypeDocPlugins = (isProd: boolean, testingMode: boolean): StarlightPlugin[ getFilePathToPackage('studiocms_imagehandler', 'src/integration.ts'), getFilePathToPackage('studiocms_imagehandler', 'src/components/index.ts'), getFilePathToPackage('studiocms_imagehandler', 'src/components/props.ts'), - getFilePathToPackage('studiocms_imagehandler', 'src/plugins/cloudinary.ts'), + getFilePathToPackage('studiocms_imagehandler', 'src/components/plugins/cloudinary.ts'), ], }) ), @@ -183,7 +181,6 @@ const TypeDocPlugins = (isProd: boolean, testingMode: boolean): StarlightPlugin[ getFilePathToPackage('studiocms_renderers', 'src/lib/markdoc/index.ts'), getFilePathToPackage('studiocms_renderers', 'src/lib/markdoc/markdocHTML.ts'), getFilePathToPackage('studiocms_renderers', 'src/lib/markdoc/markdocReactStatic.ts'), - getFilePathToPackage('studiocms_renderers', 'src/lib/marked/index.ts'), getFilePathToPackage('studiocms_renderers', 'src/lib/mdx/index.ts'), ], }) @@ -209,15 +206,16 @@ const TypeDocPlugins = (isProd: boolean, testingMode: boolean): StarlightPlugin[ entryPoints: [ getFilePathToPackage('studiocms_devapps', 'src/index.ts'), getFilePathToPackage('studiocms_devapps', 'src/integration.ts'), + getFilePathToPackage('studiocms_devapps', 'src/apps/libsqlViewer.ts'), + getFilePathToPackage('studiocms_devapps', 'src/apps/wp-importer.ts'), + getFilePathToPackage('studiocms_devapps', 'src/apps/utils/index.ts'), + getFilePathToPackage('studiocms_devapps', 'src/schema/index.ts'), + getFilePathToPackage('studiocms_devapps', 'src/schema/appsConfig.ts'), + getFilePathToPackage('studiocms_devapps', 'src/schema/wp-api/index.ts'), getFilePathToPackage('studiocms_devapps', 'src/utils/pathGenerator.ts'), getFilePathToPackage('studiocms_devapps', 'src/utils/wp-api/utils.ts'), getFilePathToPackage('studiocms_devapps', 'src/utils/wp-api/converters.ts'), getFilePathToPackage('studiocms_devapps', 'src/utils/wp-api/index.ts'), - getFilePathToPackage('studiocms_devapps', 'src/schema/index.ts'), - getFilePathToPackage('studiocms_devapps', 'src/schema/appsConfig.ts'), - getFilePathToPackage('studiocms_devapps', 'src/schema/wp-api/index.ts'), - getFilePathToPackage('studiocms_devapps', 'src/apps/libsqlViewer.ts'), - getFilePathToPackage('studiocms_devapps', 'src/apps/wp-importer.ts'), ], }) ), diff --git a/package.json b/package.json index 4168a3453..ab93ea9a8 100644 --- a/package.json +++ b/package.json @@ -6,26 +6,19 @@ "node": "20.14.0" }, "scripts": { - "moon": "moon", - "changeset": "changeset", + "build": "pnpm --filter node-playground build", + "build:docs": "pnpm --filter docs build", + "docs:dev": "pnpm --filter docs dev", "playground:dev": "pnpm --filter node-playground dev", - "playground:login": "pnpm --filter node-playground db:login", - "playground:link": "pnpm --filter node-playground db:link", "playground:push": "pnpm --filter node-playground db:push", - "ui:dev": "pnpm --filter ui-playground dev", - "ui:build": "pnpm --filter ui-playground build", - "ui:preview": "pnpm --filter ui-playground preview", - "build": "pnpm --filter node-playground build", + + "changeset": "changeset", "lint": "biome check .", "lint:fix": "biome check --write .", - "build:web": "pnpm --filter web build", - "build:docs": "pnpm --filter docs build", - "update:web": "pnpm --filter web up --latest", - "update:docs": "pnpm --filter docs up --latest", - "web:dev": "pnpm --filter web dev", - "docs:dev": "pnpm --filter docs dev", - "lunaria:build": "pnpm --filter docs lunaria:build", - "translations:changeset": "tsm --require=./scripts/filter-warnings.cjs ./scripts/translation-changeset.ts", + + "ci:lunaria:build": "pnpm --filter docs lunaria:build", + "ci:lunaria:report": "pnpm tsm --require=./scripts/filter-warnings.cjs ./www/docs/scripts/lunaria-report-bot.ts", + "ci:translations:changeset": "tsm --require=./scripts/filter-warnings.cjs ./scripts/translation-changeset.ts", "ci:lint": "biome ci --formatter-enabled=true --organize-imports-enabled=true --reporter=github", "ci:install": "pnpm install --frozen-lockfile", "ci:version": "pnpm changeset version", @@ -35,7 +28,6 @@ "devDependencies": { "@actions/core": "^1.11.1", "@biomejs/biome": "1.9.4", - "@moonrepo/cli": "1.28.3", "@changesets/cli": "2.27.9", "@changesets/config": "3.0.3", "@changesets/changelog-github": "0.5.0", diff --git a/packages/studiocms/LICENSE b/packages/studiocms/LICENSE index a30d1fbe4..94787104c 100644 --- a/packages/studiocms/LICENSE +++ b/packages/studiocms/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Astrolicious - StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares +Copyright (c) 2024 StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/studiocms/README.md b/packages/studiocms/README.md index 0cf5b48f6..16e85c435 100644 --- a/packages/studiocms/README.md +++ b/packages/studiocms/README.md @@ -2,9 +2,12 @@ As part of our efforts, we're excited to introduce StudioCMS - a dedicated content management system (CMS) built on top of Astro's latest feature, [Astro DB](https://docs.astro.build/en/guides/astro-db/). This project was developed by [Adam](https://github.com/Adammatthiesen), [Dreyfus](https://github.com/dreyfus92), and [Jumper](https://github.com/jdtjenkins), three passionate members of the Astro community. +> [!IMPORTANT] +> This project is still in early development and it is not yet ready for production use. If you encounter any issues or have ideas for new features, please let us know by [opening an issue](https://github.com/withstudiocms/studiocms/issues/new/choose) on our GitHub repository. + ## Sponsor - + ## Why another CMS? @@ -25,17 +28,17 @@ StudioCMS is built from the ground up to seamlessly integrate with Astro's robus ## Key Features -**Astro Foundation:** StudioCMS leverages Astro's robust and efficient framework, providing a solid base for building and scaling applications. +**Built on Astro:** StudioCMS leverages Astro's robust and efficient framework, providing a solid base for building and scaling applications. -**Enhanced Markdown:** We've incorporated 'Marked' with support for extensions, enriching the markdown experience with greater flexibility and functionality. +**Support for Markdown:** Incorporates Astro's Markdown processing, enriching the markdown experience with greater flexibility and functionality. -**Shiki Syntax Highlighting:** StudioCMS offers Shiki-powered syntax highlighting, ensuring your code is both visually appealing and easy to read. This is especially useful in non-Cloudflare environments due to bundle size considerations. +**Markdoc Integration:** Provides an alternative to Astro's Markdown with Markdoc, offering users a choice for their markdown processing needs. -**Markdoc Integration:** In addition to 'Marked', StudioCMS provides an alternative with Markdoc, offering users a choice for their markdown processing needs. +**Secure libSQL Database:** All data is securely housed within your libSQL database, ensuring accessibility to your data to only authorized users to your libSQL provider or Self-hosted server. -**Built-in Authentication:** StudioCMS features built-in authentication with support for multiple platforms, including Local and Github, enhancing security and user management (currently in development). +**Built-in Authentication:** Features built-in authentication with support for multiple platforms including Local and Github, enhancing security and user management. -**Unpic Image Service:** StudioCMS includes a free and efficient image service, Unpic, which makes managing external URLs straightforward, with support for major CDNs. +**Web Vitals:**Integration with '@astrojs/web-vitals' for monitoring and providing insights into web performance metrics, ensuring optimal user experiences. ## A Community-Driven Effort diff --git a/packages/studiocms/dev.d.ts b/packages/studiocms/dev.d.ts new file mode 100644 index 000000000..ea19848c7 --- /dev/null +++ b/packages/studiocms/dev.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/packages/studiocms/env.d.ts b/packages/studiocms/env.d.ts deleted file mode 100644 index 86fd4d5bb..000000000 --- a/packages/studiocms/env.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -interface ImportMetaEnv { - readonly PROD: boolean; - readonly BASE_URL: string; -} - -interface ImportMeta { - readonly env: ImportMetaEnv; -} diff --git a/packages/studiocms/moon.yml b/packages/studiocms/moon.yml deleted file mode 100644 index 8dce3a008..000000000 --- a/packages/studiocms/moon.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: 'typescript' -tags: ['astro', 'typescript'] - -project: - name: 'studiocms' - description: 'A CMS that bridges Astro with Astro DB & Studio' - maintainers: ['Adammatthiesen','jdtjenkins','dreyfus92'] \ No newline at end of file diff --git a/packages/studiocms/package.json b/packages/studiocms/package.json index b8d010e99..cd4269db1 100644 --- a/packages/studiocms/package.json +++ b/packages/studiocms/package.json @@ -1,7 +1,7 @@ { "name": "studiocms", "version": "0.1.0-beta.7", - "description": "A dedicated CMS for Astro DB. Built from the ground up by the Astro community.", + "description": "A dedicated CMS for Astro and Astro DB. Built from the ground up by the Astro community.", "author": { "name": "Adam Matthiesen | Jacob Jenkins | Paul Valladares", "url": "https://studiocms.dev" @@ -17,17 +17,17 @@ ], "license": "MIT", "keywords": [ - "astro", + "cms", "astrocms", "astrodb", - "astrolicious", "astrostudio", - "astro-integration", + "astrostudiocms", + "studiocms", "astro-studio", "astro-studiocms", - "cms", - "studiocms", - "withastro" + "astro", + "withastro", + "astro-integration" ], "homepage": "https://studiocms.dev", "publishConfig": { @@ -35,11 +35,13 @@ }, "sideEffects": false, "files": [ - "src" + "src", + "CHANGELOG.md", + "LICENSE", + "README.md" ], "exports": { - ".": "./src/index.ts", - "./blog": "./src/blog/index.ts" + ".": "./src/index.ts" }, "type": "module", "dependencies": { @@ -53,62 +55,31 @@ "@studiocms/renderers": "workspace:*", "@studiocms/robotstxt": "workspace:*", + "@studiocms/ui": "catalog:", "astro-integration-kit": "catalog:", "package-json": "catalog:studiocms", "semver": "catalog:studiocms", - "mrmime": "catalog:studiocms-core", - "remark-rehype": "catalog:studiocms-core", - "mdast-util-to-hast": "catalog:studiocms-core", - - "@oslojs/crypto": "catalog:studiocms-auth", - "@oslojs/encoding": "catalog:studiocms-auth", - "@oslojs/binary": "catalog:studiocms-auth", - "@types/bcryptjs": "catalog:studiocms-auth", - "bcryptjs": "catalog:studiocms-auth", - "@types/three": "catalog:studiocms-auth", - "arctic": "catalog:studiocms-auth", - "three": "catalog:studiocms-auth", - - "@fontsource-variable/onest": "catalog:studiocms-shared", "@inox-tools/runtime-logger": "catalog:studiocms-shared", "@matthiesenxyz/astrodtsbuilder": "catalog:studiocms-shared", "@matthiesenxyz/integration-utils": "catalog:studiocms-shared", "rollup-plugin-copy": "catalog:studiocms-shared", - "marked": "catalog:studiocms-renderer", - "marked-alert": "catalog:studiocms-renderer", - "marked-emoji": "catalog:studiocms-renderer", - "marked-footnote": "catalog:studiocms-renderer", - "marked-shiki": "catalog:studiocms-renderer", - "marked-smartypants": "catalog:studiocms-renderer", - "@markdoc/markdoc": "catalog:studiocms-renderer", - "shiki": "catalog:studiocms-renderer", - "@shikijs/transformers": "catalog:studiocms-renderer", - - "@cloudinary/url-gen": "catalog:studiocms-imagehandler", - - "@matthiesenxyz/astrolace": "catalog:studiocms-shared", - "@matthiesenxyz/unocss-preset-daisyui": "catalog:studiocms-shared", - "@unocss/astro": "catalog:studiocms-shared", - "@unocss/reset": "catalog:studiocms-shared", - "daisyui": "catalog:studiocms-shared", - "unocss": "catalog:studiocms-shared" + "mdast": "catalog:studiocms-shared", + "mdast-util-from-markdown": "catalog:studiocms-shared", + "mdast-util-to-markdown": "catalog:studiocms-shared", + "mdast-util-to-string": "catalog:studiocms-shared", + "unist-util-visit": "catalog:studiocms-shared" }, "peerDependencies": { "@astrojs/db": "catalog:min", "astro": "catalog:min", - "@studiocms/blog": "workspace:*" - }, - "peerDependenciesMeta": { - "@studiocms/blog": { - "optional": true - } + "vite": "catalog:min" }, "devDependencies": { - "vite": "catalog:", "typescript": "catalog:", - "@types/semver": "catalog:studiocms" + "@types/semver": "catalog:studiocms", + "@types/mdast": "catalog:studiocms-shared" } } diff --git a/packages/studiocms/src/blog/index.ts b/packages/studiocms/src/blog/index.ts deleted file mode 100644 index c841f2a88..000000000 --- a/packages/studiocms/src/blog/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import studioCMSBlog from '@studiocms/blog'; - -/** - * # StudioCMS Blog Theme(Integration) - * *Note: To use this export, you must have `@studiocms/blog` installed in your project dependencies.* - * #### Powered by [`astro-theme-provider`](https://github.com/astrolicious/astro-theme-provider) by [Bryce Russell](https://github.com/BryceRussell) - * - * This theme provides a Blog Index Page and RSS Feed for your StudioCMS Site as well as route handling for Blog Posts. - * - * @example - * import { defineConfig } from 'astro/config'; - * import db from '@astrojs/db'; - * import studioCMS from 'studiocms'; - * import studioCMSBlog from 'studiocms/blog'; - * - * // https://astro.build/config - * export default defineConfig({ - * site: "https://example.com", - * output: "server", - * adapter: ... - * integrations: [ - * db(), // REQUIRED - `@astrojs/db` must be included in the integrations list - * studioCMS(), // REQUIRED - StudioCMS must be included in the integrations list - * studioCMSBlog({ - * config: { - * title: "My StudioCMS Blog", - * description: "A Simple Blog build with Astro, Astrojs/DB, and StudioCMS.". - * }, - * }), - * ], - * }); - */ -const integration = studioCMSBlog; - -export default integration; diff --git a/packages/studiocms/src/hooks/build-done.ts b/packages/studiocms/src/hooks/build-done.ts new file mode 100644 index 000000000..3ea0476c4 --- /dev/null +++ b/packages/studiocms/src/hooks/build-done.ts @@ -0,0 +1,21 @@ +import { integrationLogger } from '@matthiesenxyz/integration-utils/astroUtils'; +import { defineUtility } from 'astro-integration-kit'; +import type { Messages } from '../types'; + +export const buildDone = defineUtility('astro:build:done')( + ({ logger }, verbose: boolean, messages: Messages) => { + // Log messages at the end of the build + for (const { label, message, logLevel } of messages) { + integrationLogger( + { + logger: logger.fork(label), + logLevel, + verbose: logLevel === 'info' ? verbose : true, + }, + message + ); + } + } +); + +export default buildDone; diff --git a/packages/studiocms/src/hooks/config-done.ts b/packages/studiocms/src/hooks/config-done.ts new file mode 100644 index 000000000..cad955435 --- /dev/null +++ b/packages/studiocms/src/hooks/config-done.ts @@ -0,0 +1,56 @@ +import astroDTSBuilder from '@matthiesenxyz/astrodtsbuilder'; +import { createResolver, defineUtility } from 'astro-integration-kit'; +import type { Messages } from '../types'; + +const { resolve } = createResolver(import.meta.url); + +export const configDone = defineUtility('astro:config:done')( + ({ injectTypes }, messages: Messages) => { + // Make DTS file for StudioCMS Plugins Virtual Module + const dtsFile = astroDTSBuilder(); + + dtsFile.addSingleLineNote( + 'This file is auto-generated by StudioCMS and should not be modified.' + ); + + dtsFile.addModule('studiocms:plugins', { + defaultExport: { + typeDef: `import('${resolve('../index.ts')}').SafePluginListType`, + }, + }); + + dtsFile.addModule('studiocms:changelog', { + defaultExport: { + typeDef: 'string', + }, + }); + + dtsFile.addModule('studiocms:mode', { + defaultExport: { + typeDef: `{ output: 'static' | 'server', prerenderRoutes: boolean }`, + }, + namedExports: [ + { + name: 'output', + typeDef: `'static' | 'server'`, + }, + { + name: 'prerenderRoutes', + typeDef: 'boolean', + }, + ], + }); + + // Inject the DTS file + injectTypes(dtsFile.makeAstroInjectedType('plugins.d.ts')); + + // Log Setup Complete + messages.push({ + label: 'studiocms:setup', + logLevel: 'info', + message: 'Setup Complete. 🚀', + }); + } +); + +export default configDone; diff --git a/packages/studiocms/src/hooks/config-setup.ts b/packages/studiocms/src/hooks/config-setup.ts new file mode 100644 index 000000000..cfc9ed0a3 --- /dev/null +++ b/packages/studiocms/src/hooks/config-setup.ts @@ -0,0 +1,182 @@ +import { addIntegrationArray } from '@matthiesenxyz/integration-utils/aikUtils'; +import { + integrationLogger, + nodeNamespaceBuiltinsAstro as nodeNamespace, +} from '@matthiesenxyz/integration-utils/astroUtils'; +import auth from '@studiocms/auth'; +import core from '@studiocms/core'; +import { StudioCMSError } from '@studiocms/core/errors'; +import type { SafePluginListType, StudioCMSConfig } from '@studiocms/core/schemas'; +import { checkAstroConfig, configResolver, watchStudioCMSConfig } from '@studiocms/core/utils'; +import dashboard from '@studiocms/dashboard'; +import frontend from '@studiocms/frontend'; +import imageHandler from '@studiocms/imagehandler'; +import renderers from '@studiocms/renderers'; +import robotsTXT from '@studiocms/robotstxt'; +import ui from '@studiocms/ui'; +import { addVirtualImports, defineUtility } from 'astro-integration-kit'; +import { compare as semCompare } from 'semver'; +import type { ConfigSetupOptions } from '../types'; +import { changelogHelper } from '../utils/changelog'; + +export const configSetup = defineUtility('astro:config:setup')( + async (params, o: ConfigSetupOptions) => { + // Destructure the params + const { + logger, + config: { output }, + } = params; + + // Destructure the options + const { messages, opts, pkgName, pkgVersion } = o; + + logger.info('Checking configuration...'); + + // Watch the StudioCMS Config File(s) for changes (including creation/deletion) + const configFileResponse = watchStudioCMSConfig(params); + if (configFileResponse) { + messages.push({ + label: 'studiocms:config', + logLevel: 'error', + message: configFileResponse, + }); + } + + // Resolve Options + const options: StudioCMSConfig = await configResolver(params, opts); + + const { + verbose, + rendererConfig, + defaultFrontEndConfig, + includedIntegrations, + plugins, + dashboardConfig: { prerender }, + } = options; + + // Check if the dashboard routes should be prerendered + const prerenderRoutes = output === 'static' || prerender; + + // Setup Logger + integrationLogger({ logger, logLevel: 'info', verbose }, 'Setting up StudioCMS...'); + + // Check Astro Config for required settings + checkAstroConfig(params); + + // Setup Logger + integrationLogger({ logger, logLevel: 'info', verbose }, 'Setting up StudioCMS internals...'); + + // Setup StudioCMS Integrations Array (Default Integrations) + const integrations = [ + { integration: nodeNamespace() }, + { integration: ui() }, + { integration: core(options, prerenderRoutes) }, + { integration: renderers(rendererConfig, verbose) }, + { integration: imageHandler(options) }, + { integration: auth(options, prerenderRoutes) }, + { integration: dashboard(options, prerenderRoutes) }, + ]; + + // Frontend Integration (Default) + if (defaultFrontEndConfig !== false) { + integrations.push({ integration: frontend(options) }); + } + + integrationLogger({ logger, logLevel: 'info', verbose }, 'Adding optional integrations...'); + + // Robots.txt Integration (Default) + if (includedIntegrations.robotsTXT === true) { + integrations.push({ integration: robotsTXT() }); + } else if (typeof includedIntegrations.robotsTXT === 'object') { + integrations.push({ integration: robotsTXT(includedIntegrations.robotsTXT) }); + } + + // Initialize and Add the default StudioCMS Plugin to the Safe Plugin List + const safePluginList: SafePluginListType = [ + { + name: 'StudioCMS (Default)', + identifier: 'studiocms', + }, + ]; + + integrationLogger({ logger, logLevel: 'info', verbose }, 'Setting up StudioCMS plugins...'); + + // Resolve StudioCMS Plugins + for (const { + name, + identifier, + studiocmsMinimumVersion, + integration, + frontendNavigationLinks, + pageTypes, + settingsPage, + } of plugins || []) { + // Check if the identifier is reserved + if (identifier === 'studiocms') { + throw new StudioCMSError( + 'Plugin Identifier "studiocms" is reserved for the default StudioCMS package.', + `Plugin ${name} has the identifier "studiocms" which is reserved for the default StudioCMS package, please change the identifier to something else, if the plugin is from a third party, please contact the author to change the identifier.` + ); + } + + // Check if the plugin has a minimum version requirement + const comparison = semCompare(studiocmsMinimumVersion, pkgVersion); + + if (comparison === 1) { + throw new StudioCMSError( + `Plugin ${name} requires StudioCMS version ${studiocmsMinimumVersion} or higher.`, + `Plugin ${name} requires StudioCMS version ${studiocmsMinimumVersion} or higher, please update StudioCMS to the required version, contact the plugin author to update the minimum version requirement or remove the plugin from the StudioCMS config.` + ); + } + + // Add the plugin Integration to the Astro config + if (integration && Array.isArray(integration)) { + integrations.push(...integration.map((integration) => ({ integration }))); + } else if (integration) { + integrations.push({ integration }); + } + + safePluginList.push({ + identifier, + name, + frontendNavigationLinks, + pageTypes, + settingsPage, + }); + } + + // Setup Integrations + addIntegrationArray(params, integrations); + + // Generate the Virtual Imports for the plugins + addVirtualImports(params, { + name: pkgName, + imports: { + 'studiocms:plugins': `export default ${JSON.stringify(safePluginList)};`, + 'studiocms:mode': ` + export const output = ${JSON.stringify(output)}; + export const prerenderRoutes = ${prerenderRoutes}; + export default { output, prerenderRoutes }; + `, + }, + }); + + let pluginListLength = 0; + let pluginListMessage = ''; + + pluginListLength = safePluginList.length; + pluginListMessage = safePluginList.map((p, i) => ` ${i + 1}. ${p.name}`).join('\n'); + + o.messages.push({ + label: 'studiocms:plugins', + logLevel: 'info', + message: `Currently Installed StudioCMS Plugins: (${pluginListLength})\n${pluginListMessage}`, + }); + + changelogHelper(params); + + return options as StudioCMSConfig; + } +); + +export default configSetup; diff --git a/packages/studiocms/src/hooks/db-setup.ts b/packages/studiocms/src/hooks/db-setup.ts new file mode 100644 index 000000000..d06548d9e --- /dev/null +++ b/packages/studiocms/src/hooks/db-setup.ts @@ -0,0 +1,7 @@ +import { defineUtility } from 'astro-integration-kit'; + +export const dbSetup = defineUtility('astro:db:setup')(({ extendDb }) => { + extendDb({ configEntrypoint: '@studiocms/core/db/config' }); +}); + +export default dbSetup; diff --git a/packages/studiocms/src/hooks/server-start.ts b/packages/studiocms/src/hooks/server-start.ts new file mode 100644 index 000000000..986e2d4fb --- /dev/null +++ b/packages/studiocms/src/hooks/server-start.ts @@ -0,0 +1,52 @@ +import { integrationLogger } from '@matthiesenxyz/integration-utils/astroUtils'; +import { defineUtility } from 'astro-integration-kit'; +import packageJson from 'package-json'; +import { compare as semCompare } from 'semver'; +import type { ServerStartOptions } from '../types'; + +export const serverStart = defineUtility('astro:server:start')( + async ({ logger: l }, { messages, pkgName, pkgVersion, verbose }: ServerStartOptions) => { + const logger = l.fork(`${pkgName}:update-check`); + + try { + const { version: latestVersion } = await packageJson(pkgName.toLowerCase()); + + const comparison = semCompare(pkgVersion, latestVersion); + + if (comparison === -1) { + logger.warn( + `A new version of '${pkgName}' is available. Please update to ${latestVersion} using your favorite package manager.` + ); + } else if (comparison === 0) { + logger.info(`You are using the latest version of '${pkgName}' (${pkgVersion})`); + } else { + logger.info( + `You are using a newer version (${pkgVersion}) of '${pkgName}' than the latest release (${latestVersion})` + ); + } + } catch (error) { + if (error instanceof Error) { + logger.error(`Error fetching latest version from npm registry: ${error.message}`); + } else { + // Handle the case where error is not an Error object + logger.error( + 'An unknown error occurred while fetching the latest version from the npm registry.' + ); + } + } + + // Log all messages + for (const { label, message, logLevel } of messages) { + integrationLogger( + { + logger: l.fork(label), + logLevel, + verbose: logLevel === 'info' ? verbose : true, + }, + message + ); + } + } +); + +export default serverStart; diff --git a/packages/studiocms/src/index.ts b/packages/studiocms/src/index.ts index c5a1f7079..dd718f4c0 100644 --- a/packages/studiocms/src/index.ts +++ b/packages/studiocms/src/index.ts @@ -1,27 +1,67 @@ -import { defineStudioCMSConfig, defineStudioCMSPlugin } from '@studiocms/core/lib'; -import type { CustomRenderer, Renderer, StudioCMSOptions } from '@studiocms/core/schemas'; -import type { StudioCMSPluginOptions } from '@studiocms/core/types'; -import integration from './integration'; +import { + type CustomRenderer, + type Renderer, + type SafePluginListType, + type StudioCMSConfig, + type StudioCMSOptions, + type StudioCMSPlugin, + type StudioCMSPluginOptions, + definePlugin, +} from '@studiocms/core/schemas'; +import { defineStudioCMSConfig } from '@studiocms/core/utils'; +import type { AstroIntegration } from 'astro'; +import { name as pkgName, version as pkgVersion } from '../package.json'; +import buildDone from './hooks/build-done'; +import configDone from './hooks/config-done'; +import configSetup from './hooks/config-setup'; +import dbSetup from './hooks/db-setup'; +import serverStart from './hooks/server-start'; +import type { Messages } from './types'; /** * **StudioCMS Integration** * * A CMS built for Astro by the Astro Community for the Astro Community. * - * > **Note: Astro SSR adapters that are configured for Image Optimization will automatically take full control of the Image Optimization Service. Making the `imageService` option in this integration not have any effect.** - * - * @see [GitHub Repo: 'astrolicious/studiocms'](https://github.com/astrolicious/studiocms) for more information on how to contribute to StudioCMS. - * @see [StudioCMS Docs](https://docs.studiocms.xyz) for more information on how to use StudioCMS. + * @see The [GitHub Repo: `withstudiocms/studiocms`](https://github.com/withstudiocms/studiocms) for more information on how to contribute to StudioCMS. + * @see The [StudioCMS Docs](https://docs.studiocms.dev) for more information on how to use StudioCMS. * */ -export const studioCMS = integration; - -export default studioCMS; +export function studioCMSIntegration(opts?: StudioCMSOptions): AstroIntegration { + // Resolved Options for StudioCMS + let options: StudioCMSConfig; + // Messages Array for Logging + const messages: Messages = []; -// Config Utility -export { defineStudioCMSConfig, type StudioCMSOptions }; + return { + name: pkgName, + hooks: { + // DB Setup: Setup the Database Connection for AstroDB and StudioCMS + 'astro:db:setup': (params) => dbSetup(params), + // Config Setup: Main Setup for StudioCMS + 'astro:config:setup': async (params) => { + options = await configSetup(params, { pkgName, pkgVersion, opts, messages }); + }, + // Config Done: Make DTS file for StudioCMS Plugins Virtual Module + 'astro:config:done': (params) => configDone(params, messages), + // DEV SERVER: Check for updates on server start and log messages + 'astro:server:start': async (params) => + await serverStart(params, { pkgName, pkgVersion, verbose: options.verbose, messages }), + // BUILD: Log messages at the end of the build + 'astro:build:done': (params) => buildDone(params, options.verbose, messages), + }, + }; +} -// Plugin System -export { defineStudioCMSPlugin, type StudioCMSPluginOptions }; +export default studioCMSIntegration; -export type { CustomRenderer, Renderer }; +export { + defineStudioCMSConfig, + definePlugin, + type StudioCMSPlugin, + type CustomRenderer, + type Renderer, + type StudioCMSOptions, + type StudioCMSPluginOptions, + type SafePluginListType, +}; diff --git a/packages/studiocms/src/integration.ts b/packages/studiocms/src/integration.ts deleted file mode 100644 index d612617e3..000000000 --- a/packages/studiocms/src/integration.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { addIntegrationArray } from '@matthiesenxyz/integration-utils/aikUtils'; -import { - integrationLogger, - nodeNamespaceBuiltinsAstro, -} from '@matthiesenxyz/integration-utils/astroUtils'; -import studioCMSAuth from '@studiocms/auth'; -import studioCMSCore from '@studiocms/core'; -import { getStudioConfigFileUrl, studioCMSPluginList } from '@studiocms/core/lib'; -import { - type StudioCMSOptions, - StudioCMSOptionsSchema as optionsSchema, -} from '@studiocms/core/schemas'; -import { CoreStrings, robotsTXTPreset } from '@studiocms/core/strings'; -import { - addIntegrationArrayWithCheck, - checkAstroConfig, - configResolver, -} from '@studiocms/core/utils'; -import studioCMSDashboard from '@studiocms/dashboard'; -import studioCMSFrontend from '@studiocms/frontend'; -import studioCMSImageHandler from '@studiocms/imagehandler'; -import studioCMSRenderers from '@studiocms/renderers'; -import studioCMSRobotsTXT from '@studiocms/robotstxt'; -import { defineIntegration } from 'astro-integration-kit'; -import { name, version } from '../package.json'; -import { updateCheck } from './updateCheck'; - -// Main Integration -export default defineIntegration({ - name, - optionsSchema, - setup({ name, options }) { - // Register StudioCMS into the StudioCMS Plugin List - studioCMSPluginList.set(name, { name, label: 'StudioCMS' }); - - // Resolve Options - let resolvedOptions: StudioCMSOptions; - - return { - hooks: { - // Configure `@astrojs/db` integration to include the StudioCMS Database Tables - 'astro:db:setup': ({ extendDb }) => { - extendDb({ configEntrypoint: '@studiocms/core/db/config' }); - }, - 'astro:config:setup': async (params) => { - // Destructure Params - const { config: astroConfig, addWatchFile, logger } = params; - - // Watch the StudioCMS Config File for changes (including creation/deletion) - addWatchFile(getStudioConfigFileUrl(astroConfig.root)); - - // Resolve Options - const ResolvedOptions = await configResolver(params, options); - - // Set Resolved Options - resolvedOptions = ResolvedOptions; - - // Break out resolved options - const { - verbose, - rendererConfig, - dbStartPage, - dashboardConfig, - defaultFrontEndConfig, - imageService, - overrides, - includedIntegrations, - } = ResolvedOptions; - - // Setup Logger - integrationLogger({ logger, logLevel: 'info', verbose }, CoreStrings.Start); - - // Check Astro Config for required settings - checkAstroConfig(params); - - // Setup Integrations (Internal) - addIntegrationArray(params, [ - { integration: nodeNamespaceBuiltinsAstro() }, - { integration: studioCMSCore(resolvedOptions) }, - { integration: studioCMSRenderers(rendererConfig) }, - { - integration: studioCMSFrontend({ - verbose, - dbStartPage, - defaultFrontEndConfig, - }), - }, - { - integration: studioCMSImageHandler({ - verbose, - imageService, - overrides, - }), - }, - { - integration: studioCMSAuth({ - verbose, - dbStartPage, - dashboardConfig, - }), - }, - { - integration: studioCMSDashboard({ - verbose, - dbStartPage, - dashboardConfig, - }), - }, - ]); - - // Setup Integrations (External / Optional) - addIntegrationArrayWithCheck(params, [ - { - enabled: includedIntegrations.useAstroRobots, - knownSimilar: ['astro-robots', 'astro-robots-txt'], - integration: studioCMSRobotsTXT({ - ...robotsTXTPreset, - ...includedIntegrations.astroRobotsConfig, - }), - }, - ]); - }, - 'astro:server:start': async (params) => { - // Check for Updates on Development Server Start - updateCheck(params, name, version); - }, - }, - }; - }, -}); diff --git a/packages/studiocms/src/types.ts b/packages/studiocms/src/types.ts new file mode 100644 index 000000000..46f51e035 --- /dev/null +++ b/packages/studiocms/src/types.ts @@ -0,0 +1,21 @@ +import type { StudioCMSOptions } from '@studiocms/core/schemas'; + +export type Messages = { + label: string; + logLevel: 'info' | 'warn' | 'error' | 'debug'; + message: string; +}[]; + +export type ServerStartOptions = { + pkgName: string; + pkgVersion: string; + verbose: boolean; + messages: Messages; +}; + +export type ConfigSetupOptions = { + pkgName: string; + pkgVersion: string; + opts: StudioCMSOptions; + messages: Messages; +}; diff --git a/packages/studiocms/src/updateCheck.ts b/packages/studiocms/src/updateCheck.ts deleted file mode 100644 index bb7f9df96..000000000 --- a/packages/studiocms/src/updateCheck.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { defineUtility } from 'astro-integration-kit'; -import packageJson from 'package-json'; -import * as semver from 'semver'; - -/** - * Fetches the latest version of a package from the npm registry. - * @param packageName - The name of the package. - * @returns A promise that resolves to the latest version of the package. - */ -async function fetchlatestVersion(packageName: string): Promise { - const { version } = await packageJson(packageName.toLowerCase()); - return version; -} - -/** - * Checks for updates of a specified package on npm registry. - * @param {import("astro").HookParameters<"astro:config:setup">} params - The Astro parameters object. - * @param currentVersion - The current version of the package. - */ -export const updateCheck = defineUtility('astro:server:start')( - async (params, name: string, currentVersion: string): Promise => { - const logger = params.logger.fork(`${name}:update-check`); - - try { - const latestVersion = await fetchlatestVersion(name); - - const comparison = semver.compare(currentVersion, latestVersion); - - if (comparison === -1) { - logger.warn( - `A new version of 'studiocms' is available. Please update to ${latestVersion} using your favorite package manager.` - ); - } else if (comparison === 0) { - logger.info(`You are using the latest version of '${name}' (${currentVersion})`); - } else { - logger.info( - `You are using a newer version (${currentVersion}) of '${name}' than the latest release (${latestVersion})` - ); - } - } catch (error) { - if (error instanceof Error) { - logger.error(`Error fetching latest version from npm registry: ${error.message}`); - } else { - // Handle the case where error is not an Error object - logger.error( - 'An unknown error occurred while fetching the latest version from the npm registry.' - ); - } - } - } -); diff --git a/packages/studiocms/src/utils/changelog.ts b/packages/studiocms/src/utils/changelog.ts new file mode 100644 index 000000000..ea0e88888 --- /dev/null +++ b/packages/studiocms/src/utils/changelog.ts @@ -0,0 +1,67 @@ +import { addVirtualImports, createResolver, defineUtility } from 'astro-integration-kit'; +import type { List, Root } from 'mdast'; +import { toMarkdown } from 'mdast-util-to-markdown'; +import { loadChangelog, semverCategories } from './changelogLoader'; + +const { resolve } = createResolver(import.meta.url); + +export const changelogHelper = defineUtility('astro:config:setup')(async (params) => { + const changelog = loadChangelog(resolve('../../CHANGELOG.md')); + + // Generate markdown output + const output: string[] = ['# Release Notes']; + + const ast: Root = { + type: 'root', + children: [], + }; + + // Get the latest version changelog + const latestVersion = changelog.versions[0]; + + // Get the latest version changes + const latestVersionChanges: List = { type: 'list', children: [] }; + + if (latestVersion) { + for (const semverCategory of semverCategories) { + for (const listItem of latestVersion.changes[semverCategory].children) { + latestVersionChanges.children.push(listItem); + } + } + + if (latestVersion.includes.size) { + latestVersionChanges.children.push({ + type: 'listItem', + children: [ + { + type: 'paragraph', + children: [ + { type: 'text', value: `Includes: ${[...latestVersion.includes].join(', ')} ` }, + ], + }, + ], + }); + } + + ast.children.push({ + type: 'heading', + depth: 2, + children: [{ type: 'text', value: `${latestVersion.version}` }], + }); + } + + if (latestVersionChanges.children.length) { + ast.children.push(latestVersionChanges); + } + + output.push(toMarkdown(ast, { bullet: '-' })); + + const markdownString = output.join('\n'); + + addVirtualImports(params, { + name: 'studiocms/changelog', + imports: { + 'studiocms:changelog': `export default ${JSON.stringify(markdownString)};`, + }, + }); +}); diff --git a/packages/studiocms/src/utils/changelogLoader.ts b/packages/studiocms/src/utils/changelogLoader.ts new file mode 100644 index 000000000..4deafdee8 --- /dev/null +++ b/packages/studiocms/src/utils/changelogLoader.ts @@ -0,0 +1,140 @@ +import { readFileSync } from 'node:fs'; +import type { List } from 'mdast'; +import { fromMarkdown } from 'mdast-util-from-markdown'; +import { toString as ToString } from 'mdast-util-to-string'; +import { visit } from 'unist-util-visit'; + +export type Changelog = { + packageName: string; + versions: Version[]; +}; + +export type Version = { + version: string; + changes: { [key in SemverCategory]: List }; + includes: Set; +}; + +export const semverCategories = ['major', 'minor', 'patch'] as const; +export type SemverCategory = (typeof semverCategories)[number]; + +export function loadChangelog(path: string): Changelog { + let markdown = readFileSync(path, 'utf8'); + + // Convert GitHub usernames in "Thanks ..." sentences to links + markdown = markdown.replace( + /(?<=Thank[^.!]*? )@([a-z0-9-]+)(?=[\s,.!])/gi, + '[@$1](https://github.com/$1)' + ); + + const ast = fromMarkdown(markdown); + // const lines = readFileSync(path, 'utf8') + // .split(/\r?\n/) + // .map((line) => line.trimEnd()) + const changelog: Changelog = { + packageName: '', + versions: [], + }; + type ParserState = 'packageName' | 'version' | 'semverCategory' | 'changes'; + let state: ParserState = 'packageName'; + let version: Version | undefined; + let semverCategory: SemverCategory | undefined; + + function handleNode(node: ReturnType['children'][number]) { + if (node.type === 'heading') { + if (node.depth === 1) { + if (state !== 'packageName') throw new Error('Unexpected h1'); + changelog.packageName = ToString(node); + state = 'version'; + return; + } + if (node.depth === 2) { + if (state === 'packageName') throw new Error('Unexpected h2'); + version = { + version: ToString(node), + changes: { + major: { type: 'list', children: [] }, + minor: { type: 'list', children: [] }, + patch: { type: 'list', children: [] }, + }, + includes: new Set(), + }; + changelog.versions.push(version); + state = 'semverCategory'; + return; + } + if (node.depth === 3) { + if (state === 'packageName' || state === 'version') throw new Error('Unexpected h3'); + semverCategory = (ToString(node).split(' ')[0] || '').toLowerCase() as SemverCategory; + if (!semverCategories.includes(semverCategory)) + throw new Error(`Unexpected semver category: ${semverCategory}`); + state = 'changes'; + return; + } + } + if (node.type === 'list') { + if (state !== 'changes' || !version || !semverCategory) throw new Error('Unexpected list'); + // Go through list items + for (let listItemIdx = 0; listItemIdx < node.children.length; listItemIdx++) { + const listItem = node.children[listItemIdx]; + if (!listItem) continue; + + // Check if the current list item ends with a nested sublist that consists + // of items matching the pattern `@` + const lastChild = listItem.children[listItem.children.length - 1]; + if (lastChild?.type === 'list') { + const packageRefs: string[] = []; + // biome-ignore lint/complexity/noForEach: + lastChild.children.forEach((subListItem) => { + const text = ToString(subListItem); + if (parsePackageReference(text)) packageRefs.push(text); + }); + if (packageRefs.length === lastChild.children.length) { + // If so, add the packages to `includes` + for (const packageRef of packageRefs) { + version.includes.add(packageRef); + } + // Remove the sub-list from the list item + listItem.children.pop(); + } + } + + const firstPara = + listItem.children[0]?.type === 'paragraph' ? listItem.children[0] : undefined; + if (firstPara) { + // Remove IDs like `bfed62a: ...` or `... [85dbab8]` from the first paragraph + visit(firstPara, 'text', (textNode) => { + textNode.value = textNode.value.replace(/(^[0-9a-f]{7,}: | \[[0-9a-f]{7,}\]$)/, ''); + }); + // Skip list items that only contain the text `Updated dependencies` + const firstParaText = ToString(firstPara); + if (firstParaText === 'Updated dependencies') continue; + // If the list item is a package reference, add it to `includes` instead + const packageRef = parsePackageReference(firstParaText); + if (packageRef) { + version.includes.add(firstParaText); + continue; + } + // Add the list item to the changes + version.changes[semverCategory].children.push(listItem); + } + } + return; + } + throw new Error(`Unexpected node: ${JSON.stringify(node)}`); + } + + // biome-ignore lint/complexity/noForEach: + ast.children.forEach((node) => { + handleNode(node); + }); + + return changelog; +} + +function parsePackageReference(str: string) { + const matches = str.match(/^([@/a-z0-9-]+)@([0-9.]+)$/); + if (!matches) return; + const [, packageName, version] = matches; + return { packageName, version }; +} diff --git a/packages/studiocms/tsconfig.json b/packages/studiocms/tsconfig.json index 73fb2bc44..e511a952b 100644 --- a/packages/studiocms/tsconfig.json +++ b/packages/studiocms/tsconfig.json @@ -1,86 +1,4 @@ { "extends": "astro/tsconfigs/strictest", - "files": [], - "compilerOptions": { - "outDir": "../../.moon/cache/types/packages/studiocms", - "composite": true, - "noEmit": false, - "allowImportingTsExtensions": false, - "emitDeclarationOnly": false, - "paths": { - "node-playground/*": ["../../playgrounds/node/src/*"], - "@studiocms/assets": ["../studiocms_assets/src/index.ts"], - "@studiocms/assets/*": ["../studiocms_assets/src/*"], - "@studiocms/auth": ["../studiocms_auth/src/index.ts"], - "@studiocms/auth/*": ["../studiocms_auth/src/*"], - "@studiocms/betaresources": ["../studiocms_betaresources/src/index.ts"], - "@studiocms/betaresources/*": ["../studiocms_betaresources/src/*"], - "@studiocms/blog": ["../studiocms_blog/index.ts"], - "@studiocms/blog/*": ["../studiocms_blog/src/*"], - "@studiocms/core": ["../studiocms_core/src/index.ts"], - "@studiocms/core/*": ["../studiocms_core/src/*"], - "@studiocms/dashboard": ["../studiocms_dashboard/src/index.ts"], - "@studiocms/dashboard/*": ["../studiocms_dashboard/src/*"], - "@studiocms/frontend": ["../studiocms_frontend/src/index.ts"], - "@studiocms/frontend/*": ["../studiocms_frontend/src/*"], - "@studiocms/imagehandler": ["../studiocms_imagehandler/src/index.ts"], - "@studiocms/imagehandler/*": ["../studiocms_imagehandler/src/*"], - "@studiocms/renderers": ["../studiocms_renderers/src/index.ts"], - "@studiocms/renderers/*": ["../studiocms_renderers/src/*"], - "@studiocms/robotstxt": ["../studiocms_robotstxt/src/index.ts"], - "@studiocms/robotstxt/*": ["../studiocms_robotstxt/src/*"] - } - }, - "references": [ - { - "path": "../../playgrounds/node" - }, - { - "path": "../studiocms_assets" - }, - { - "path": "../studiocms_auth" - }, - { - "path": "../studiocms_betaresources" - }, - { - "path": "../studiocms_blog" - }, - { - "path": "../studiocms_core" - }, - { - "path": "../studiocms_dashboard" - }, - { - "path": "../studiocms_frontend" - }, - { - "path": "../studiocms_imagehandler" - }, - { - "path": "../studiocms_renderers" - }, - { - "path": "../studiocms_robotstxt" - } - ], - "include": [ - "./package.json", - "../../playgrounds/node/**/*", - "../../playgrounds/node/.astro/**/*", - "../studiocms_assets/**/*", - "../studiocms_auth/**/*", - "../studiocms_betaresources/**/*", - "../studiocms_blog/**/*", - "../studiocms_core/**/*", - "../studiocms_dashboard/**/*", - "../studiocms_frontend/**/*", - "../studiocms_imagehandler/**/*", - "../studiocms_renderers/**/*", - "../studiocms_robotstxt/**/*", - "./**/*", - "./src/**/*.json" - ] + "include": ["./package.json", "./**/*", "./src/**/*.json"] } diff --git a/packages/studiocms_assets/LICENSE b/packages/studiocms_assets/LICENSE index a30d1fbe4..94787104c 100644 --- a/packages/studiocms_assets/LICENSE +++ b/packages/studiocms_assets/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Astrolicious - StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares +Copyright (c) 2024 StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/studiocms_assets/moon.yml b/packages/studiocms_assets/moon.yml deleted file mode 100644 index aa80fbb0b..000000000 --- a/packages/studiocms_assets/moon.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: 'typescript' -tags: ['astro', 'typescript'] - -project: - name: '@studiocms/assets' - description: 'A CMS that bridges Astro with Astro DB & Studio' - maintainers: ['Adammatthiesen','jdtjenkins','dreyfus92'] \ No newline at end of file diff --git a/packages/studiocms_assets/package.json b/packages/studiocms_assets/package.json index 5dd2cfc23..9b6bd99f7 100644 --- a/packages/studiocms_assets/package.json +++ b/packages/studiocms_assets/package.json @@ -40,7 +40,6 @@ "astro": "catalog:min" }, "devDependencies": { - "vite": "catalog:", "typescript": "catalog:" } } diff --git a/packages/studiocms_assets/src/svgs/discord.svg b/packages/studiocms_assets/src/svgs/discord.svg index d8d7fee4b..dfe3f56d1 100644 --- a/packages/studiocms_assets/src/svgs/discord.svg +++ b/packages/studiocms_assets/src/svgs/discord.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/studiocms_assets/tsconfig.json b/packages/studiocms_assets/tsconfig.json index c2c3dc4d8..d175d082a 100644 --- a/packages/studiocms_assets/tsconfig.json +++ b/packages/studiocms_assets/tsconfig.json @@ -2,25 +2,10 @@ "extends": "astro/tsconfigs/strictest", "files": [], "compilerOptions": { - "outDir": "../../.moon/cache/types/packages/studiocms_assets", "composite": true, "noEmit": false, "allowImportingTsExtensions": false, - "emitDeclarationOnly": false, - "paths": { - "node-playground/*": ["../../playgrounds/node/src/*"] - } + "emitDeclarationOnly": false }, - "references": [ - { - "path": "../../playgrounds/node" - } - ], - "include": [ - "./package.json", - "../../playgrounds/node/**/*", - "../../playgrounds/node/.astro/**/*", - "./**/*", - "./src/**/*.json" - ] + "include": ["./package.json", "./**/*", "./src/**/*.json"] } diff --git a/packages/studiocms_auth/LICENSE b/packages/studiocms_auth/LICENSE index a30d1fbe4..94787104c 100644 --- a/packages/studiocms_auth/LICENSE +++ b/packages/studiocms_auth/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Astrolicious - StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares +Copyright (c) 2024 StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/studiocms_auth/env.d.ts b/packages/studiocms_auth/env.d.ts index 86fd4d5bb..b14f5fc22 100644 --- a/packages/studiocms_auth/env.d.ts +++ b/packages/studiocms_auth/env.d.ts @@ -1,3 +1,5 @@ +/// + interface ImportMetaEnv { readonly PROD: boolean; readonly BASE_URL: string; diff --git a/packages/studiocms_auth/moon.yml b/packages/studiocms_auth/moon.yml deleted file mode 100644 index d0fed609c..000000000 --- a/packages/studiocms_auth/moon.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: 'typescript' -tags: ['astro', 'typescript'] - -project: - name: '@studiocms/auth' - description: 'A CMS that bridges Astro with Astro DB & Studio' - maintainers: ['Adammatthiesen','jdtjenkins','dreyfus92'] \ No newline at end of file diff --git a/packages/studiocms_auth/package.json b/packages/studiocms_auth/package.json index cd8e150d7..247564bef 100644 --- a/packages/studiocms_auth/package.json +++ b/packages/studiocms_auth/package.json @@ -35,7 +35,7 @@ "type": "module", "dependencies": { "@studiocms/core": "workspace:*", - "@studiocms/ui": "workspace:*", + "@studiocms/ui": "catalog:", "astro-integration-kit": "catalog:", "@fontsource-variable/onest": "catalog:studiocms-shared", "@inox-tools/runtime-logger": "catalog:studiocms-shared", @@ -47,16 +47,14 @@ "@oslojs/encoding": "catalog:studiocms-auth", "@types/three": "catalog:studiocms-auth", "arctic": "catalog:studiocms-auth", - "three": "catalog:studiocms-auth", - "@types/bcryptjs": "catalog:studiocms-auth", - "bcryptjs": "catalog:studiocms-auth" + "three": "catalog:studiocms-auth" }, "peerDependencies": { "@astrojs/db": "catalog:min", - "astro": "catalog:min" + "astro": "catalog:min", + "vite": "catalog:min" }, "devDependencies": { - "vite": "catalog:", "typescript": "catalog:" } } diff --git a/packages/studiocms_auth/src/astroenv/env.ts b/packages/studiocms_auth/src/astroenv/env.ts deleted file mode 100644 index e72998b95..000000000 --- a/packages/studiocms_auth/src/astroenv/env.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { AstroConfig } from 'astro'; -import { envField } from 'astro/config'; - -export const astroENV: AstroConfig['experimental']['env'] = { - validateSecrets: true, - schema: { - // Auth Encryption Key - CMS_ENCRYPTION_KEY: envField.string({ - context: 'server', - access: 'secret', - optional: false, - }), - // GitHub Auth Provider Environment Variables - CMS_GITHUB_CLIENT_ID: envField.string({ - context: 'server', - access: 'secret', - optional: true, - }), - CMS_GITHUB_CLIENT_SECRET: envField.string({ - context: 'server', - access: 'secret', - optional: true, - }), - CMS_GITHUB_REDIRECT_URI: envField.string({ - context: 'server', - access: 'secret', - optional: true, - }), - // Discord Auth Provider Environment Variables - CMS_DISCORD_CLIENT_ID: envField.string({ - context: 'server', - access: 'secret', - optional: true, - }), - CMS_DISCORD_CLIENT_SECRET: envField.string({ - context: 'server', - access: 'secret', - optional: true, - }), - CMS_DISCORD_REDIRECT_URI: envField.string({ - context: 'server', - access: 'secret', - optional: true, - }), - // Google Auth Provider Environment Variables - CMS_GOOGLE_CLIENT_ID: envField.string({ - context: 'server', - access: 'secret', - optional: true, - }), - CMS_GOOGLE_CLIENT_SECRET: envField.string({ - context: 'server', - access: 'secret', - optional: true, - }), - CMS_GOOGLE_REDIRECT_URI: envField.string({ - context: 'server', - access: 'secret', - optional: true, - }), - // Auth0 Auth Provider Environment Variables - CMS_AUTH0_CLIENT_ID: envField.string({ - context: 'server', - access: 'secret', - optional: true, - }), - CMS_AUTH0_CLIENT_SECRET: envField.string({ - context: 'server', - access: 'secret', - optional: true, - }), - CMS_AUTH0_DOMAIN: envField.string({ - context: 'server', - access: 'secret', - optional: true, - }), - CMS_AUTH0_REDIRECT_URI: envField.string({ - context: 'server', - access: 'secret', - optional: true, - }), - }, -}; diff --git a/packages/studiocms_auth/src/components/OAuthButtonStack.astro b/packages/studiocms_auth/src/components/OAuthButtonStack.astro index bae54cdfc..113fac6c3 100644 --- a/packages/studiocms_auth/src/components/OAuthButtonStack.astro +++ b/packages/studiocms_auth/src/components/OAuthButtonStack.astro @@ -1,10 +1,10 @@ --- -import { getLangFromUrl, useTranslations } from 'studiocms:i18n'; +import { useTranslations } from 'studiocms:i18n'; import { Divider } from '@studiocms/ui/components'; import OAuthButton from './OAuthButton.astro'; import { providerData, showOAuth } from './oAuthButtonProviders'; -const lang = getLangFromUrl(Astro.url); +const lang = 'en-us'; const t = useTranslations(lang, '@studiocms/auth:oauth-stack'); const shouldShowOAuth = showOAuth && providerData.some(({ enabled }) => enabled); diff --git a/packages/studiocms_auth/src/components/StaticAuthCheck.astro b/packages/studiocms_auth/src/components/StaticAuthCheck.astro index 8452ea897..672601501 100644 --- a/packages/studiocms_auth/src/components/StaticAuthCheck.astro +++ b/packages/studiocms_auth/src/components/StaticAuthCheck.astro @@ -1,33 +1,20 @@ --- import { getUserData } from 'studiocms:auth/lib/user'; -import { StudioCMSRoutes } from 'studiocms:helpers/routemap'; -import { getLangFromUrl, useTranslatedPath } from 'studiocms:i18n'; +import { StudioCMSRoutes } from 'studiocms:lib'; const { isLoggedIn } = await getUserData(Astro); -// Get the language and translations -const referer = Astro.request.headers.get('referer'); - -if (!referer) { - throw new Error('No referer found'); -} - -const lang = getLangFromUrl(new URL(referer)); -const translatePath = useTranslatedPath(lang); -const { - mainLinks: { dashboardIndex }, -} = StudioCMSRoutes; --- \ No newline at end of file diff --git a/packages/studiocms_auth/src/components/oAuthButtonProviders.ts b/packages/studiocms_auth/src/components/oAuthButtonProviders.ts index ca56993a9..19985b365 100644 --- a/packages/studiocms_auth/src/components/oAuthButtonProviders.ts +++ b/packages/studiocms_auth/src/components/oAuthButtonProviders.ts @@ -1,28 +1,12 @@ import { authEnvCheck } from 'studiocms:auth/utils/authEnvCheck'; -import { StudioCMSRoutes } from 'studiocms:helpers/routemap'; -import Config from 'virtual:studiocms/config'; +import { AuthConfig } from 'studiocms:config'; +import { StudioCMSRoutes } from 'studiocms:lib'; -const { - dashboardConfig: { - AuthConfig: { providers }, - }, -} = Config; - -const { - authLinks: { googleIndex, auth0Index, discordIndex, githubIndex }, -} = StudioCMSRoutes; - -const { - DISCORD: { ENABLED: discordEnabled }, - GITHUB: { ENABLED: githubEnabled }, - GOOGLE: { ENABLED: googleEnabled }, - AUTH0: { ENABLED: auth0Enabled }, - SHOW_OAUTH, -} = await authEnvCheck(providers); +const authEnv = await authEnvCheck(AuthConfig.providers); -export const showOAuth = SHOW_OAUTH; +export const showOAuth = authEnv.SHOW_OAUTH; -type ProviderData = { +export type ProviderData = { enabled: boolean; href: string; label: string; @@ -31,27 +15,27 @@ type ProviderData = { export const providerData: ProviderData[] = [ { - enabled: githubEnabled, - href: githubIndex, + enabled: authEnv.GITHUB.ENABLED, + href: StudioCMSRoutes.authLinks.githubIndex, label: 'GitHub', - image: ``, + image: ``, }, { - enabled: discordEnabled, - href: discordIndex, + enabled: authEnv.DISCORD.ENABLED, + href: StudioCMSRoutes.authLinks.discordIndex, label: 'Discord', - image: ``, + image: ``, }, { - enabled: googleEnabled, - href: googleIndex, + enabled: authEnv.GOOGLE.ENABLED, + href: StudioCMSRoutes.authLinks.googleIndex, label: 'Google', - image: ``, + image: ``, }, { - enabled: auth0Enabled, - href: auth0Index, + enabled: authEnv.AUTH0.ENABLED, + href: StudioCMSRoutes.authLinks.auth0Index, label: 'Auth0', - image: ``, + image: ``, }, ]; diff --git a/packages/studiocms_auth/src/hooks/config-done.ts b/packages/studiocms_auth/src/hooks/config-done.ts new file mode 100644 index 000000000..9c01286ca --- /dev/null +++ b/packages/studiocms_auth/src/hooks/config-done.ts @@ -0,0 +1,13 @@ +import { defineUtility } from 'astro-integration-kit'; +import authLibDTS from '../stubs/auth-lib'; +import authScriptsDTS from '../stubs/auth-scripts'; +import authUtilsDTS from '../stubs/auth-utils'; + +export const configDone = defineUtility('astro:config:done')(({ injectTypes }) => { + // Inject Types + injectTypes(authLibDTS); + injectTypes(authUtilsDTS); + injectTypes(authScriptsDTS); +}); + +export default configDone; diff --git a/packages/studiocms_auth/src/hooks/config-setup.ts b/packages/studiocms_auth/src/hooks/config-setup.ts new file mode 100644 index 000000000..f57e0756f --- /dev/null +++ b/packages/studiocms_auth/src/hooks/config-setup.ts @@ -0,0 +1,259 @@ +import { runtimeLogger } from '@inox-tools/runtime-logger'; +import { integrationLogger } from '@matthiesenxyz/integration-utils/astroUtils'; +import { makePublicRoute } from '@studiocms/core/lib'; +import { addAstroEnvConfig } from '@studiocms/core/utils'; +import { addVirtualImports, createResolver, defineUtility } from 'astro-integration-kit'; +import { envField } from 'astro/config'; +import copy from 'rollup-plugin-copy'; +import type { StudioCMSAuthOptions } from '../schema'; +import { checkEnvKeys } from '../utils/checkENV'; +import { injectAuthAPIRoutes, injectAuthPageRoutes } from '../utils/routeBuilder'; + +export const configSetup = defineUtility('astro:config:setup')( + (params, name: string, options: StudioCMSAuthOptions, prerenderRoutes: boolean) => { + // Destructure Params + const { logger, updateConfig } = params; + + // Destructure Options + const { + verbose, + dashboardConfig: { + dashboardEnabled, + AuthConfig: { + providers: { + github: githubAPI, + discord: discordAPI, + google: googleAPI, + auth0: auth0API, + usernameAndPassword: usernameAndPasswordAPI, + usernameAndPasswordConfig: { allowUserRegistration }, + }, + }, + }, + } = options; + + // Create resolver relative to this file + const { resolve } = createResolver(import.meta.url); + + // Log that Setup is Starting + integrationLogger({ logger, logLevel: 'info', verbose }, 'Setting up StudioCMS Auth...'); + + // Inject `@it-astro:logger:{name}` Logger for runtime logging + runtimeLogger(params, { name: 'studiocms-auth' }); + + // Check for Authentication Environment Variables + checkEnvKeys(logger, options); + + // Update Astro Config with Environment Variables (`astro:env`) + addAstroEnvConfig(params, { + validateSecrets: true, + schema: { + // Auth Encryption Key + CMS_ENCRYPTION_KEY: envField.string({ + context: 'server', + access: 'secret', + optional: false, + }), + // GitHub Auth Provider Environment Variables + CMS_GITHUB_CLIENT_ID: envField.string({ + context: 'server', + access: 'secret', + optional: true, + }), + CMS_GITHUB_CLIENT_SECRET: envField.string({ + context: 'server', + access: 'secret', + optional: true, + }), + CMS_GITHUB_REDIRECT_URI: envField.string({ + context: 'server', + access: 'secret', + optional: true, + }), + // Discord Auth Provider Environment Variables + CMS_DISCORD_CLIENT_ID: envField.string({ + context: 'server', + access: 'secret', + optional: true, + }), + CMS_DISCORD_CLIENT_SECRET: envField.string({ + context: 'server', + access: 'secret', + optional: true, + }), + CMS_DISCORD_REDIRECT_URI: envField.string({ + context: 'server', + access: 'secret', + optional: true, + }), + // Google Auth Provider Environment Variables + CMS_GOOGLE_CLIENT_ID: envField.string({ + context: 'server', + access: 'secret', + optional: true, + }), + CMS_GOOGLE_CLIENT_SECRET: envField.string({ + context: 'server', + access: 'secret', + optional: true, + }), + CMS_GOOGLE_REDIRECT_URI: envField.string({ + context: 'server', + access: 'secret', + optional: true, + }), + // Auth0 Auth Provider Environment Variables + CMS_AUTH0_CLIENT_ID: envField.string({ + context: 'server', + access: 'secret', + optional: true, + }), + CMS_AUTH0_CLIENT_SECRET: envField.string({ + context: 'server', + access: 'secret', + optional: true, + }), + CMS_AUTH0_DOMAIN: envField.string({ + context: 'server', + access: 'secret', + optional: true, + }), + CMS_AUTH0_REDIRECT_URI: envField.string({ + context: 'server', + access: 'secret', + optional: true, + }), + }, + }); + + // injectAuthHelper + addVirtualImports(params, { + name, + imports: { + 'studiocms:auth/lib/encryption': `export * from '${resolve('../lib/encryption.ts')}'`, + 'studiocms:auth/lib/password': `export * from '${resolve('../lib/password.ts')}'`, + 'studiocms:auth/lib/rate-limit': `export * from '${resolve('../lib/rate-limit.ts')}'`, + 'studiocms:auth/lib/session': `export * from '${resolve('../lib/session.ts')}'`, + 'studiocms:auth/lib/types': `export * from '${resolve('../lib/types.ts')}'`, + 'studiocms:auth/lib/user': `export * from '${resolve('../lib/user.ts')}'`, + 'studiocms:auth/utils/authEnvCheck': `export * from '${resolve('../utils/authEnvCheck.ts')}'`, + 'studiocms:auth/utils/validImages': `export * from '${resolve('../utils/validImages.ts')}'`, + 'studiocms:auth/utils/getLabelForPermissionLevel': `export * from '${resolve('../utils/getLabelForPermissionLevel.ts')}'`, + 'studiocms:auth/scripts/three': `import ${JSON.stringify(resolve('../scripts/three.ts'))}`, + 'studiocms:auth/scripts/formListener': `export * from '${resolve('../scripts/formListener.ts')}'`, + }, + }); + + integrationLogger({ logger, logLevel: 'info', verbose }, 'Updating user Astro config...'); + + // Update Astro Config + updateConfig({ + vite: { + optimizeDeps: { + exclude: ['three'], + }, + plugins: [ + copy({ + copyOnce: true, + hook: 'buildStart', + targets: [ + { + src: resolve('../public/*'), + dest: makePublicRoute('auth'), + }, + ], + }), + ], + }, + }); + + // Inject API Routes + injectAuthAPIRoutes(params, { + options, + routes: [ + { + pattern: 'login', + entrypoint: resolve('../routes/api/login.ts'), + enabled: usernameAndPasswordAPI, + }, + { + pattern: 'logout', + entrypoint: resolve('../routes/api/logout.ts'), + enabled: dashboardEnabled && !options.dbStartPage, + }, + { + pattern: 'register', + entrypoint: resolve('../routes/api/register.ts'), + enabled: usernameAndPasswordAPI && allowUserRegistration, + }, + { + pattern: 'github', + entrypoint: resolve('../routes/api/github/index.ts'), + enabled: githubAPI, + }, + { + pattern: 'github/callback', + entrypoint: resolve('../routes/api/github/callback.ts'), + enabled: githubAPI, + }, + { + pattern: 'discord', + entrypoint: resolve('../routes/api/discord/index.ts'), + enabled: discordAPI, + }, + { + pattern: 'discord/callback', + entrypoint: resolve('../routes/api/discord/callback.ts'), + enabled: discordAPI, + }, + { + pattern: 'google', + entrypoint: resolve('../routes/api/google/index.ts'), + enabled: googleAPI, + }, + { + pattern: 'google/callback', + entrypoint: resolve('../routes/api/google/callback.ts'), + enabled: googleAPI, + }, + { + pattern: 'auth0', + entrypoint: resolve('../routes/api/auth0/index.ts'), + enabled: auth0API, + }, + { + pattern: 'auth0/callback', + entrypoint: resolve('../routes/api/auth0/callback.ts'), + enabled: auth0API, + }, + ], + }); + + injectAuthPageRoutes( + params, + { + options, + routes: [ + { + pattern: 'login/', + entrypoint: resolve('../routes/login.astro'), + enabled: dashboardEnabled && !options.dbStartPage, + }, + { + pattern: 'logout/', + entrypoint: resolve('../routes/logout.astro'), + enabled: dashboardEnabled && !options.dbStartPage, + }, + { + pattern: 'signup/', + entrypoint: resolve('../routes/signup.astro'), + enabled: usernameAndPasswordAPI && allowUserRegistration, + }, + ], + }, + prerenderRoutes + ); + } +); + +export default configSetup; diff --git a/packages/studiocms_auth/src/index.ts b/packages/studiocms_auth/src/index.ts index 38ebcc424..0fea21e2d 100644 --- a/packages/studiocms_auth/src/index.ts +++ b/packages/studiocms_auth/src/index.ts @@ -1,8 +1,20 @@ -import integration from './integration'; +import type { AstroIntegration } from 'astro'; +import { name } from '../package.json'; +import configDone from './hooks/config-done'; +import configSetup from './hooks/config-setup'; +import type { StudioCMSAuthOptions } from './schema'; /** * StudioCMS Auth Integration */ -const studioCMSAuth = integration; +function studioCMSAuth(options: StudioCMSAuthOptions, prerenderRoutes: boolean): AstroIntegration { + return { + name, + hooks: { + 'astro:config:setup': (params) => configSetup(params, name, options, prerenderRoutes), + 'astro:config:done': (params) => configDone(params), + }, + }; +} export default studioCMSAuth; diff --git a/packages/studiocms_auth/src/integration.ts b/packages/studiocms_auth/src/integration.ts deleted file mode 100644 index a47d20298..000000000 --- a/packages/studiocms_auth/src/integration.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { runtimeLogger } from '@inox-tools/runtime-logger'; -import { integrationLogger } from '@matthiesenxyz/integration-utils/astroUtils'; -import { DashboardStrings } from '@studiocms/core/strings'; -import { addAstroEnvConfig } from '@studiocms/core/utils'; -import { addVirtualImports, createResolver, defineIntegration } from 'astro-integration-kit'; -import copy from 'rollup-plugin-copy'; -import { name } from '../package.json'; -import { astroENV } from './astroenv/env'; -import { StudioCMSAuthOptionsSchema } from './schema'; -import authLibDTS from './stubs/auth-lib'; -import authScriptsDTS from './stubs/auth-scripts'; -import authUtilsDTS from './stubs/auth-utils'; -import { checkEnvKeys } from './utils/checkENV'; -import { injectAuthAPIRoutes, injectAuthPageRoutes } from './utils/routeBuilder'; - -export default defineIntegration({ - name, - optionsSchema: StudioCMSAuthOptionsSchema, - setup({ - name, - options, - options: { - dashboardConfig: { - dashboardEnabled, - AuthConfig: { - providers: { - github: githubAPI, - discord: discordAPI, - google: googleAPI, - auth0: auth0API, - usernameAndPassword: usernameAndPasswordAPI, - usernameAndPasswordConfig: { allowUserRegistration }, - }, - }, - }, - }, - }) { - // Create resolver relative to this file - const { resolve } = createResolver(import.meta.url); - - return { - hooks: { - 'astro:config:setup': async (params) => { - // Destructure Params - const { logger, updateConfig } = params; - - // Log that Setup is Starting - integrationLogger( - { logger, logLevel: 'info', verbose: options.verbose }, - DashboardStrings.Setup - ); - - // Inject `@it-astro:logger:{name}` Logger for runtime logging - runtimeLogger(params, { name: 'studiocms-auth' }); - - // Check for Authentication Environment Variables - checkEnvKeys(logger, options); - - // Update Astro Config with Environment Variables (`astro:env`) - addAstroEnvConfig(params, astroENV); - - // injectAuthHelper - addVirtualImports(params, { - name, - imports: { - 'studiocms:auth/lib/encryption': `export * from '${resolve('./lib/encryption.ts')}'`, - 'studiocms:auth/lib/password': `export * from '${resolve('./lib/password.ts')}'`, - 'studiocms:auth/lib/rate-limit': `export * from '${resolve('./lib/rate-limit.ts')}'`, - 'studiocms:auth/lib/session': `export * from '${resolve('./lib/session.ts')}'`, - 'studiocms:auth/lib/types': `export * from '${resolve('./lib/types.ts')}'`, - 'studiocms:auth/lib/user': `export * from '${resolve('./lib/user.ts')}'`, - 'studiocms:auth/utils/authEnvCheck': `export * from '${resolve('./utils/authEnvCheck.ts')}'`, - 'studiocms:auth/utils/validImages': `export * from '${resolve('./utils/validImages.ts')}'`, - 'studiocms:auth/scripts/three': `import ${JSON.stringify(resolve('./scripts/three.ts'))}`, - 'studiocms:auth/scripts/formListener': `export * from '${resolve('./scripts/formListener.ts')}'`, - }, - }); - - // Update Astro Config - updateConfig({ - security: { - checkOrigin: true, - }, - experimental: { - directRenderScript: true, - serverIslands: true, - }, - vite: { - optimizeDeps: { - exclude: ['astro:db', 'three'], - }, - plugins: [ - copy({ - copyOnce: true, - hook: 'buildStart', - targets: [ - { - src: resolve('./resources/*'), - dest: 'public/studiocms-auth/', - }, - ], - }), - ], - }, - }); - - // Inject API Routes - injectAuthAPIRoutes(params, { - options, - routes: [ - { - pattern: 'login', - entrypoint: resolve('./routes/api/login.ts'), - enabled: usernameAndPasswordAPI, - }, - { - pattern: 'logout', - entrypoint: resolve('./routes/api/logout.ts'), - enabled: dashboardEnabled && !options.dbStartPage, - }, - { - pattern: 'register', - entrypoint: resolve('./routes/api/register.ts'), - enabled: usernameAndPasswordAPI && allowUserRegistration, - }, - { - pattern: 'github', - entrypoint: resolve('./routes/api/github/index.ts'), - enabled: githubAPI, - }, - { - pattern: 'github/callback', - entrypoint: resolve('./routes/api/github/callback.ts'), - enabled: githubAPI, - }, - { - pattern: 'discord', - entrypoint: resolve('./routes/api/discord/index.ts'), - enabled: discordAPI, - }, - { - pattern: 'discord/callback', - entrypoint: resolve('./routes/api/discord/callback.ts'), - enabled: discordAPI, - }, - { - pattern: 'google', - entrypoint: resolve('./routes/api/google/index.ts'), - enabled: googleAPI, - }, - { - pattern: 'google/callback', - entrypoint: resolve('./routes/api/google/callback.ts'), - enabled: googleAPI, - }, - { - pattern: 'auth0', - entrypoint: resolve('./routes/api/auth0/index.ts'), - enabled: auth0API, - }, - { - pattern: 'auth0/callback', - entrypoint: resolve('./routes/api/auth0/callback.ts'), - enabled: auth0API, - }, - ], - }); - - injectAuthPageRoutes(params, { - options, - routes: [ - { - pattern: 'login/', - entrypoint: resolve('./routes/login.astro'), - enabled: dashboardEnabled && !options.dbStartPage, - }, - { - pattern: 'logout/', - entrypoint: resolve('./routes/logout.astro'), - enabled: dashboardEnabled && !options.dbStartPage, - }, - { - pattern: 'signup/', - entrypoint: resolve('./routes/signup.astro'), - enabled: usernameAndPasswordAPI && allowUserRegistration, - }, - ], - }); - }, - 'astro:config:done': async ({ injectTypes }) => { - // Inject Types - injectTypes(authLibDTS); - injectTypes(authUtilsDTS); - injectTypes(authScriptsDTS); - }, - }, - }; - }, -}); diff --git a/packages/studiocms_auth/src/layouts/AuthLayout.astro b/packages/studiocms_auth/src/layouts/AuthLayout.astro index 8d79a6927..582ec3b18 100644 --- a/packages/studiocms_auth/src/layouts/AuthLayout.astro +++ b/packages/studiocms_auth/src/layouts/AuthLayout.astro @@ -2,86 +2,53 @@ import '@fontsource-variable/onest/index.css'; import '@studiocms/ui/css/global.css'; import './authlayout.css'; -import { Image } from 'astro:assets'; -import { db, eq } from 'astro:db'; -import { validImages } from 'studiocms:auth/utils/validImages'; -import Config from 'virtual:studiocms/config'; -import version from 'virtual:studiocms/version'; +import { Generator } from 'studiocms:components'; import onestWoff2 from '@fontsource-variable/onest/files/onest-latin-wght-normal.woff2?url'; -import { CMSSiteConfigId } from '@studiocms/core/consts'; -import { tsSiteConfig } from '@studiocms/core/db/tsTables'; import { Toaster } from '@studiocms/ui/components'; import OAuthButtonStack from '../components/OAuthButtonStack.astro'; -import StudioCMSLogoSVG from '../components/StudioCMSLogoSVG.astro'; - -const { - dashboardConfig: { faviconURL }, -} = Config; +import StaticAuthCheck from '../components/StaticAuthCheck.astro'; +import FallbackCanvas from './FallbackCanvas.astro'; +import ThreeCanvasLoader from './ThreeCanvasLoader.astro'; interface Props { title: string; description: string; lang: string; disableScreen?: boolean; + checkLogin?: boolean; + pagePreRendered?: boolean; } -const { title, description, lang, disableScreen } = Astro.props; - -// Get the site config -const siteConfig = await db - .select() - .from(tsSiteConfig) - .where(eq(tsSiteConfig.id, CMSSiteConfigId)) - .get(); - -// Get the login page background and custom image from the site config -const loginPageBackground = siteConfig?.loginPageBackground; -const loginPageCustomImage = siteConfig?.loginPageCustomImage; - -const fallbackImageSrc = - loginPageBackground === 'custom' - ? loginPageCustomImage - : validImages.find((x) => x.name !== 'custom' && x.name === loginPageBackground)?.dark; // TODO: Adapt to theme +const { title, description, lang, disableScreen, checkLogin, pagePreRendered = true } = Astro.props; --- + {/* Global Metadata */} - - - - + + + {/* Favicon */} + + + + + {/* Primary Meta Tags */} {title} + + {/* Fonts */} +
-
-
+ { + pagePreRendered ? : + }
- + + + + + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/components/islands/configuration/LightVsDark.astro b/packages/studiocms_dashboard/src/components/islands/configuration/LightVsDark.astro new file mode 100644 index 000000000..70d456e43 --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/configuration/LightVsDark.astro @@ -0,0 +1,65 @@ +--- + +interface Props { + light: ImageMetadata; + dark: ImageMetadata; +} + +const { light, dark } = Astro.props; +--- +
+
+
+
+ +
+
+ + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/components/islands/content-mgmt/Create.astro b/packages/studiocms_dashboard/src/components/islands/content-mgmt/Create.astro new file mode 100644 index 000000000..58d5ac3fb --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/content-mgmt/Create.astro @@ -0,0 +1,35 @@ +--- +import { StudioCMSRoutes } from 'studiocms:lib'; +--- +
+
+
+
+ + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/components/islands/content-mgmt/Edit.astro b/packages/studiocms_dashboard/src/components/islands/content-mgmt/Edit.astro new file mode 100644 index 000000000..f5bc7f537 --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/content-mgmt/Edit.astro @@ -0,0 +1,61 @@ +--- +import { StudioCMSRoutes } from 'studiocms:lib'; +--- +
+
+
+
+ + + + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/components/islands/content-mgmt/PageHeader.astro b/packages/studiocms_dashboard/src/components/islands/content-mgmt/PageHeader.astro new file mode 100644 index 000000000..02b4b54ab --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/content-mgmt/PageHeader.astro @@ -0,0 +1,102 @@ +--- +import { Button } from '@studiocms/ui/components'; +import Icon from '@studiocms/ui/utils/Icon.astro'; +--- + + + + + + + diff --git a/packages/studiocms_dashboard/src/components/islands/content-mgmt/PageList.astro b/packages/studiocms_dashboard/src/components/islands/content-mgmt/PageList.astro new file mode 100644 index 000000000..da3b81b4b --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/content-mgmt/PageList.astro @@ -0,0 +1,40 @@ +--- +import studioCMS_SDK_Cache from 'studiocms:sdk/cache'; +import TreeRenderer from './TreeRenderer.astro'; + +const { data } = await studioCMS_SDK_Cache.GET.pageFolderTree(); + +// const testData = [ +// { +// id: '1', +// name: 'Page 1', +// page: true, +// children: [], +// }, +// { +// id: '2', +// name: 'Page 2', +// page: true, +// children: [], +// }, +// { +// id: '3', +// name: 'Test Folder', +// page: false, +// children: [ +// { +// id: '4', +// name: 'Sub Folder', +// page: false, +// children: [ +// { id: '5', name: 'Page 3', page: true, children: [] }, +// { id: '6', name: 'Page 4', page: true, children: [] }, +// ], +// }, +// { id: '7', name: 'Page 5', page: true, children: [] }, +// ], +// }, +// ]; +--- + + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/components/islands/content-mgmt/TreeRenderer.astro b/packages/studiocms_dashboard/src/components/islands/content-mgmt/TreeRenderer.astro new file mode 100644 index 000000000..44c233c15 --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/content-mgmt/TreeRenderer.astro @@ -0,0 +1,51 @@ +--- +import type { FolderNode } from '@studiocms/core/sdk-utils/types'; +import InnerSidebarFolder from '../../InnerSidebarFolder.astro'; +import InnerSidebarLink from '../../InnerSidebarLink.astro'; + +// Props passed to the component +interface Props { + data: FolderNode[]; +} + +const { data } = Astro.props; + +/** + * Recursively sorts a tree of pages and folders by name (A to Z). + * @param data - The array of folders and pages to sort. + * @returns A new array with the sorted structure. + */ +function sortTree(data: FolderNode[]): FolderNode[] { + return data + .map((node) => ({ + ...node, + children: sortTree(node.children), // Recursively sort children + })) + .sort((a, b) => a.name.localeCompare(b.name)); // Sort current level by name +} + +const sortedData = sortTree(data); +--- + +{sortedData.map(({ id, name, page, children }) => ( + page ? ( + + {name} + + ) : ( + + {children && children.length > 0 && ( +
+ +
+ )} +
+ ) +))} + + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/components/islands/dashboard/UserName.astro b/packages/studiocms_dashboard/src/components/islands/dashboard/UserName.astro new file mode 100644 index 000000000..6412ec2bc --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/dashboard/UserName.astro @@ -0,0 +1,6 @@ +--- +import { getUserData } from 'studiocms:auth/lib/user'; + +const user = await getUserData(Astro); +--- + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/components/islands/profile/BasicInfo.astro b/packages/studiocms_dashboard/src/components/islands/profile/BasicInfo.astro new file mode 100644 index 000000000..e9d1d2458 --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/profile/BasicInfo.astro @@ -0,0 +1,67 @@ +--- +import { getUserData } from 'studiocms:auth/lib/user'; +import { Button, Card, Input } from '@studiocms/ui/components'; +import Icon from '@studiocms/ui/utils/Icon.astro'; + +const data = await getUserData(Astro); +--- + + + +
+

Basic Info

+ +
+ +
+
+ +
+ + + + + + + + + +
+ +
+ + + + diff --git a/packages/studiocms_dashboard/src/components/islands/profile/BasicInfoFallback.astro b/packages/studiocms_dashboard/src/components/islands/profile/BasicInfoFallback.astro new file mode 100644 index 000000000..060e3385c --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/profile/BasicInfoFallback.astro @@ -0,0 +1,44 @@ +--- +import { Button, Card, Input } from '@studiocms/ui/components'; +import Icon from '@studiocms/ui/utils/Icon.astro'; +--- + +
+

Basic Info

+
+ +
+
+
+ + + + +
+
+ + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/components/islands/profile/SocialSignin.astro b/packages/studiocms_dashboard/src/components/islands/profile/SocialSignin.astro new file mode 100644 index 000000000..90323adf0 --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/profile/SocialSignin.astro @@ -0,0 +1,152 @@ +--- +import { getUserData } from 'studiocms:auth/lib/user'; +import studioCMS_SDK from 'studiocms:sdk'; +import type { CombinedUserData } from 'studiocms:sdk/types'; +import { Button, Card, Center } from '@studiocms/ui/components'; +import Icon from '@studiocms/ui/utils/Icon.astro'; +import { providerData } from './oAuthButtonProviders'; + +const { user } = await getUserData(Astro); + +let userOAuthData: CombinedUserData['oAuthData'] | undefined; + +if (user) { + const userFullData = await studioCMS_SDK.GET.databaseEntry.users.byId(user.id); + + if (userFullData) { + userOAuthData = userFullData.oAuthData; + } +} + +const hasOAuthData = userOAuthData && userOAuthData.length > 0; + +const connectedAccounts: { provider: string }[] = []; + +if (hasOAuthData) { + for (const provider in userOAuthData) { + connectedAccounts.push({ provider }); + } +} + +const socialProviders = providerData.map((providerData) => ({ + ...providerData, + connected: connectedAccounts.some( + ({ provider }) => provider === providerData.label.toLowerCase() + ), +})); + +const connectedProviders = socialProviders.filter(({ connected }) => connected); +const unconnectedProviders = socialProviders.filter(({ connected }) => !connected); +--- + + + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/components/islands/profile/SocialSigninFallback.astro b/packages/studiocms_dashboard/src/components/islands/profile/SocialSigninFallback.astro new file mode 100644 index 000000000..05a16c829 --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/profile/SocialSigninFallback.astro @@ -0,0 +1,83 @@ +--- +import { Card, Center } from '@studiocms/ui/components'; +--- + + + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/components/islands/profile/UpdatePassword.astro b/packages/studiocms_dashboard/src/components/islands/profile/UpdatePassword.astro new file mode 100644 index 000000000..241fb9006 --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/profile/UpdatePassword.astro @@ -0,0 +1,102 @@ +--- +import { getUserData } from 'studiocms:auth/lib/user'; +import { Button, Card, Input } from '@studiocms/ui/components'; +import Icon from '@studiocms/ui/utils/Icon.astro'; + +const data = await getUserData(Astro); + +const userHasNoPassword = data.user?.password === null; +--- + + + +
+

{userHasNoPassword ? "Create" : "Update"} Password

+ +
+ +
+
+ + {userHasNoPassword && ( +

+ It looks like your account doesn't have a password set. You can set one by using the form below. +

+ )} + +
+ + + + + + + +
+
+ + + + diff --git a/packages/studiocms_dashboard/src/components/islands/profile/UpdatePasswordFallback.astro b/packages/studiocms_dashboard/src/components/islands/profile/UpdatePasswordFallback.astro new file mode 100644 index 000000000..df69f0cf5 --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/profile/UpdatePasswordFallback.astro @@ -0,0 +1,48 @@ +--- +import { Button, Card, Input } from '@studiocms/ui/components'; +import Icon from '@studiocms/ui/utils/Icon.astro'; +--- + + +
+

Update Password

+
+ +
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/components/islands/profile/oAuthButtonProviders.ts b/packages/studiocms_dashboard/src/components/islands/profile/oAuthButtonProviders.ts new file mode 100644 index 000000000..19985b365 --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/profile/oAuthButtonProviders.ts @@ -0,0 +1,41 @@ +import { authEnvCheck } from 'studiocms:auth/utils/authEnvCheck'; +import { AuthConfig } from 'studiocms:config'; +import { StudioCMSRoutes } from 'studiocms:lib'; + +const authEnv = await authEnvCheck(AuthConfig.providers); + +export const showOAuth = authEnv.SHOW_OAUTH; + +export type ProviderData = { + enabled: boolean; + href: string; + label: string; + image: string; +}; + +export const providerData: ProviderData[] = [ + { + enabled: authEnv.GITHUB.ENABLED, + href: StudioCMSRoutes.authLinks.githubIndex, + label: 'GitHub', + image: ``, + }, + { + enabled: authEnv.DISCORD.ENABLED, + href: StudioCMSRoutes.authLinks.discordIndex, + label: 'Discord', + image: ``, + }, + { + enabled: authEnv.GOOGLE.ENABLED, + href: StudioCMSRoutes.authLinks.googleIndex, + label: 'Google', + image: ``, + }, + { + enabled: authEnv.AUTH0.ENABLED, + href: StudioCMSRoutes.authLinks.auth0Index, + label: 'Auth0', + image: ``, + }, +]; diff --git a/packages/studiocms_dashboard/src/components/islands/sidebar/Admin.astro b/packages/studiocms_dashboard/src/components/islands/sidebar/Admin.astro new file mode 100644 index 000000000..3f786de02 --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/sidebar/Admin.astro @@ -0,0 +1,104 @@ +--- +import { getUserData } from 'studiocms:auth/lib/user'; +import { verifyUserPermissionLevel } from 'studiocms:auth/lib/user'; +import { useTranslations } from 'studiocms:i18n'; +import pluginList from 'studiocms:plugins'; +import { Divider } from '@studiocms/ui/components'; +import SidebarLink from '../../SidebarLink.astro'; +import SidebarPluginLink from '../../SidebarPluginLink.astro'; +import { getSidebarLinks } from '../../sidebarConfig'; + +const lang = 'en-us'; +const t = useTranslations(lang, '@studiocms/dashboard:sidebar'); + +const data = await getUserData(Astro); + +const isAdmin = await verifyUserPermissionLevel(data, 'admin'); + +const isOwner = await verifyUserPermissionLevel(data, 'owner'); + +const filteredPluginList = pluginList.filter((plugin) => !!plugin.settingsPage); + +const sidebar = getSidebarLinks(lang); + +Astro.response.headers.set('Cache-Control', 'max-age=604800'); +// 1. Set cookie if admin +// 2. check on subsequent loads if cookie is there +// 3. if yes, show static shit +// 4. render server island +--- +{isAdmin && ( + {t('category-2-header')} + + {t('category-3-header')} + +)} + + diff --git a/packages/studiocms_dashboard/src/components/islands/sidebar/Editor.astro b/packages/studiocms_dashboard/src/components/islands/sidebar/Editor.astro new file mode 100644 index 000000000..89053b7fa --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/sidebar/Editor.astro @@ -0,0 +1,18 @@ +--- +import { getUserData } from 'studiocms:auth/lib/user'; +import { verifyUserPermissionLevel } from 'studiocms:auth/lib/user'; +import SidebarLink from '../../SidebarLink.astro'; +import { getSidebarLinks } from '../../sidebarConfig'; + +const lang = 'en-us'; +const sidebar = getSidebarLinks(lang); + +const data = await getUserData(Astro); + +const isEditor = await verifyUserPermissionLevel(data, 'editor'); + +Astro.response.headers.set('Cache-Control', 'max-age=604800'); +--- +{ isEditor && sidebar.editorLinks.map(({ href, icon, title }) => ( + {title} +))} \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/components/islands/sidebar/UserAccount.astro b/packages/studiocms_dashboard/src/components/islands/sidebar/UserAccount.astro new file mode 100644 index 000000000..236d2301d --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/sidebar/UserAccount.astro @@ -0,0 +1,23 @@ +--- +import { getUserData } from 'studiocms:auth/lib/user'; +import { getLabelForPermissionLevel } from 'studiocms:auth/utils/getLabelForPermissionLevel'; +import { User } from '@studiocms/ui/components'; + +const data = await getUserData(Astro); + +// biome-ignore lint/suspicious/noExplicitAny: +type PropsOf any> = Parameters[0]; + +type UserProps = PropsOf; + +const userProps: UserProps = { + name: data.user?.name || 'Visitor', + description: getLabelForPermissionLevel(data.permissionLevel), + loading: 'eager', +}; + +if (data.user?.avatar) { + userProps.avatar = data.user.avatar; +} +--- + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/components/islands/sidebar/VersionCheck.astro b/packages/studiocms_dashboard/src/components/islands/sidebar/VersionCheck.astro new file mode 100644 index 000000000..ffedd21dd --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/sidebar/VersionCheck.astro @@ -0,0 +1,308 @@ +--- +import changelog from 'studiocms:changelog'; +import { StudioCMSRoutes } from 'studiocms:lib'; +import { StudioCMSRenderer } from 'studiocms:renderer'; +import studioCMS_SDK_Cache from 'studiocms:sdk/cache'; +import currentVersion from 'studiocms:version'; +import { Modal } from '@studiocms/ui/components'; +import { compare } from 'semver'; +import { dateWithTimeAndZone } from '../../component-scripts/dateWithTimeAndZone'; +import { timeAgo } from '../../component-scripts/timeAgo'; +import VersionCheckPart from '../../shared-parts/VersionCheck.astro'; +import VersionCheckChangelog from './VersionCheckChangelog.astro'; + +const latestVersion = await studioCMS_SDK_Cache.GET.latestVersion(); + +const lastChecked = latestVersion.lastCacheUpdate; + +const comparison = compare(currentVersion, latestVersion.version); + +const status = comparison === -1 ? 'outdated' : comparison === 0 ? 'latest' : 'future'; +--- +
+ +
+ + + +

+ Version Information + +

+ +
+
+
+ Current Version + v{currentVersion} +
+ +
+ Latest Version + v{latestVersion.version} +
+ +
+ Last Update Check + + + ({timeAgo(lastChecked)}) + +
+
+ +
+
+ + Full Changelog + +
+ + +
+
+ + + + diff --git a/packages/studiocms_dashboard/src/components/islands/sidebar/VersionCheckChangelog.astro b/packages/studiocms_dashboard/src/components/islands/sidebar/VersionCheckChangelog.astro new file mode 100644 index 000000000..63cccc220 --- /dev/null +++ b/packages/studiocms_dashboard/src/components/islands/sidebar/VersionCheckChangelog.astro @@ -0,0 +1,101 @@ +--- + +interface Props { + link: string; +} + +const { link } = Astro.props; +--- + + + + + + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/components/shared-parts/VersionCheck.astro b/packages/studiocms_dashboard/src/components/shared-parts/VersionCheck.astro new file mode 100644 index 000000000..2127c929b --- /dev/null +++ b/packages/studiocms_dashboard/src/components/shared-parts/VersionCheck.astro @@ -0,0 +1,78 @@ +--- +import currentVersion from 'studiocms:version'; + +interface Props { + status?: 'outdated' | 'latest' | 'future'; +} + +const { status } = Astro.props; +--- + + + v{currentVersion} + {status && } + + + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/components/sidebarConfig.ts b/packages/studiocms_dashboard/src/components/sidebarConfig.ts new file mode 100644 index 000000000..ba16c4d2a --- /dev/null +++ b/packages/studiocms_dashboard/src/components/sidebarConfig.ts @@ -0,0 +1,56 @@ +import { type UiLanguageKeys, useTranslations } from 'studiocms:i18n'; +import { StudioCMSRoutes } from 'studiocms:lib'; +import type { HeroIconName } from '@studiocms/ui/utils/iconType.ts'; + +interface SidebarLink { + title: string; + icon: HeroIconName; + href: string; +} + +export function getSidebarLinks(lang: UiLanguageKeys) { + const t = useTranslations(lang, '@studiocms/dashboard:sidebar'); + + // Base links + const baseLinks: SidebarLink[] = [ + { + title: t('dashboard-link-label'), + icon: 'home', + href: StudioCMSRoutes.mainLinks.dashboardIndex, + }, + ]; + + // Editor links + const editorLinks: SidebarLink[] = [ + { + title: t('content-management-label'), + icon: 'pencil-square', + href: StudioCMSRoutes.mainLinks.contentManagement, + }, + ]; + + // Admin links + const adminLinks: SidebarLink[] = [ + { + title: t('user-management-label'), + icon: 'user-group', + href: StudioCMSRoutes.mainLinks.userManagement, + }, + ]; + + // Owner links + const ownerLinks: SidebarLink[] = [ + { + title: t('site-configuration-label'), + icon: 'cog-6-tooth', + href: StudioCMSRoutes.mainLinks.siteConfiguration, + }, + ]; + + return { + baseLinks, + editorLinks, + adminLinks, + ownerLinks, + }; +} diff --git a/packages/studiocms_dashboard/src/firstTimeSetupRoutes/main.astro b/packages/studiocms_dashboard/src/firstTimeSetupRoutes/main.astro index 952ac7a2f..2a718f392 100644 --- a/packages/studiocms_dashboard/src/firstTimeSetupRoutes/main.astro +++ b/packages/studiocms_dashboard/src/firstTimeSetupRoutes/main.astro @@ -50,7 +50,7 @@ import { makePageTitle } from '../utils';
-
+ diff --git a/packages/studiocms_dashboard/src/hooks/configDone.ts b/packages/studiocms_dashboard/src/hooks/configDone.ts new file mode 100644 index 000000000..f13e276c7 --- /dev/null +++ b/packages/studiocms_dashboard/src/hooks/configDone.ts @@ -0,0 +1,8 @@ +import { defineUtility } from 'astro-integration-kit'; +import webVitalDtsFile from '../stubs/webVitals'; + +export const configDone = defineUtility('astro:config:done')(({ injectTypes }) => { + injectTypes(webVitalDtsFile); +}); + +export default configDone; diff --git a/packages/studiocms_dashboard/src/hooks/configSetup.ts b/packages/studiocms_dashboard/src/hooks/configSetup.ts new file mode 100644 index 000000000..e5c8c36ed --- /dev/null +++ b/packages/studiocms_dashboard/src/hooks/configSetup.ts @@ -0,0 +1,160 @@ +import { runtimeLogger } from '@inox-tools/runtime-logger'; +import { integrationLogger } from '@matthiesenxyz/integration-utils/astroUtils'; +import { createResolver, defineUtility } from 'astro-integration-kit'; +import type { StudioCMSDashboardOptions } from '../schema'; +import { injectDashboardAPIRoutes } from '../utils/addAPIRoutes'; +import { checkForWebVitals } from '../utils/checkForWebVitalsPlugin'; +import { injectDashboardRoute } from '../utils/injectRouteArray'; + +const { resolve } = createResolver(import.meta.url); + +export const configSetup = defineUtility('astro:config:setup')( + (params, name: string, options: StudioCMSDashboardOptions, prerenderRoutes: boolean) => { + // Destructure the params object + const { logger, injectRoute } = params; + + // Destructure the options object + const { + verbose, + dbStartPage, + dashboardConfig: { + dashboardEnabled, + inject404Route, + AuthConfig: { enabled: authEnabled }, + }, + } = options; + + const shouldInject404Route = inject404Route && dashboardEnabled; + + // Log that the setup has started + integrationLogger({ logger, logLevel: 'info', verbose }, 'Setting up StudioCMS Dashboard...'); + + // Inject `@it-astro:logger:{name}` Logger for runtime logging + runtimeLogger(params, { name: 'studiocms-dashboard' }); + + // Check for `@astrojs/web-vitals` Integration + checkForWebVitals(params, { name, verbose }); + + // Inject First Time Setup Routes if dbStartPage is enabled + if (dbStartPage) { + integrationLogger( + { logger, logLevel: 'info', verbose }, + 'Injecting First Time Setup Routes...' + ); + injectRoute({ + pattern: 'start', + entrypoint: resolve('../firstTimeSetupRoutes/main.astro'), + prerender: false, + }); + injectRoute({ + pattern: 'done', + entrypoint: resolve('../firstTimeSetupRoutes/done.astro'), + prerender: false, + }); + } + + // Inject 404 Route if enabled + if (shouldInject404Route) { + integrationLogger({ logger, logLevel: 'info', verbose }, 'Injecting 404 Route...'); + injectRoute({ + pattern: '404', + entrypoint: resolve('../routes/404.astro'), + prerender: true, + }); + } + + // Inject API Routes + injectDashboardAPIRoutes(params, { + options, + routes: [ + { + enabled: dashboardEnabled && !dbStartPage, + pattern: 'liverender', + entrypoint: resolve('../routes/studiocms_api/LiveRender.astro'), + }, + { + enabled: dashboardEnabled && !dbStartPage && authEnabled, + pattern: 'config/site', + entrypoint: resolve('../routes/studiocms_api/config/site.ts'), + }, + { + enabled: dashboardEnabled && !dbStartPage && authEnabled, + pattern: 'config/admin', + entrypoint: resolve('../routes/studiocms_api/config/admin.ts'), + }, + { + enabled: dashboardEnabled && !dbStartPage && authEnabled, + pattern: 'pages/create', + entrypoint: resolve('../routes/studiocms_api/pages/create.ts'), + }, + { + enabled: dashboardEnabled && !dbStartPage && authEnabled, + pattern: 'pages/edit', + entrypoint: resolve('../routes/studiocms_api/pages/edit.ts'), + }, + { + enabled: dashboardEnabled && !dbStartPage && authEnabled, + pattern: 'pages/delete', + entrypoint: resolve('../routes/studiocms_api/pages/delete.ts'), + }, + { + enabled: dbStartPage, + pattern: 'setup', + entrypoint: resolve('../routes/studiocms_api/firstTimeSetup.ts'), + }, + ], + }); + + // Inject Routes + injectDashboardRoute( + params, + { + options, + routes: [ + { + enabled: dashboardEnabled && !dbStartPage, + pattern: 'test', + entrypoint: resolve('../routes/test.astro'), + }, + + { + enabled: dashboardEnabled && !dbStartPage, + pattern: '/', + entrypoint: resolve('../routes/index.astro'), + }, + { + enabled: dashboardEnabled && !dbStartPage, + pattern: 'content-management', + entrypoint: resolve('../routes/content-management.astro'), + }, + { + enabled: dashboardEnabled && !dbStartPage, + pattern: 'profile', + entrypoint: resolve('../routes/profile.astro'), + }, + { + enabled: dashboardEnabled && !dbStartPage, + pattern: 'configuration', + entrypoint: resolve('../routes/configuration.astro'), + }, + // { + // enabled: dashboardEnabled && !dbStartPage, + // pattern: 'create-page', + // entrypoint: resolve('../routes/create-page.astro'), + // }, + // { + // enabled: dashboardEnabled && !dbStartPage, + // pattern: 'user-management', + // entrypoint: resolve('../routes/user-management.astro'), + // }, + ], + }, + prerenderRoutes + ); + + // Log that the setup is complete + integrationLogger({ logger, logLevel: 'info', verbose }, 'StudioCMS Dashboard complete!'); + } +); + +export default configSetup; diff --git a/packages/studiocms_dashboard/src/hooks/serverStart.ts b/packages/studiocms_dashboard/src/hooks/serverStart.ts new file mode 100644 index 000000000..735c7d715 --- /dev/null +++ b/packages/studiocms_dashboard/src/hooks/serverStart.ts @@ -0,0 +1,16 @@ +import { integrationLogger } from '@matthiesenxyz/integration-utils/astroUtils'; +import { defineUtility } from 'astro-integration-kit'; +import type { StudioCMSDashboardOptions } from '../schema'; + +export const serverStart = defineUtility('astro:server:start')( + ({ logger }, { dbStartPage }: StudioCMSDashboardOptions) => { + if (dbStartPage) { + integrationLogger( + { logger, logLevel: 'warn', verbose: true }, + 'Start Page is Enabled. This will be the only page available until you initialize your database and disable the config option forcing this page to be displayed. To get started, visit http://localhost:4321/start/ in your browser to initialize your database. And Setup your installation.' + ); + } + } +); + +export default serverStart; diff --git a/packages/studiocms_dashboard/src/index.ts b/packages/studiocms_dashboard/src/index.ts index 08c6e9158..4f4e6a58f 100644 --- a/packages/studiocms_dashboard/src/index.ts +++ b/packages/studiocms_dashboard/src/index.ts @@ -1,8 +1,25 @@ -import integration from './integration'; +import type { AstroIntegration } from 'astro'; +import { name } from '../package.json'; +import configDone from './hooks/configDone'; +import configSetup from './hooks/configSetup'; +import serverStart from './hooks/serverStart'; +import type { StudioCMSDashboardOptions } from './schema'; /** * StudioCMS Dashboard Integration */ -const studioCMSDashboard = integration; +function studioCMSDashboard( + options: StudioCMSDashboardOptions, + prerenderRoutes: boolean +): AstroIntegration { + return { + name, + hooks: { + 'astro:config:setup': (params) => configSetup(params, name, options, prerenderRoutes), + 'astro:config:done': (params) => configDone(params), + 'astro:server:start': (params) => serverStart(params, options), + }, + }; +} export default studioCMSDashboard; diff --git a/packages/studiocms_dashboard/src/integration.ts b/packages/studiocms_dashboard/src/integration.ts deleted file mode 100644 index 1d6a891b2..000000000 --- a/packages/studiocms_dashboard/src/integration.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { runtimeLogger } from '@inox-tools/runtime-logger'; -import astrolace from '@matthiesenxyz/astrolace'; -import { addIntegrationArray } from '@matthiesenxyz/integration-utils/aikUtils'; -import { integrationLogger } from '@matthiesenxyz/integration-utils/astroUtils'; -import { presetDaisy } from '@matthiesenxyz/unocss-preset-daisyui'; -import { DashboardStrings, DbErrors } from '@studiocms/core/strings'; -import type { InjectedType } from 'astro'; -import { createResolver, defineIntegration } from 'astro-integration-kit'; -import { - presetTypography, - presetUno, - presetWebFonts, - presetWind, - transformerDirectives, -} from 'unocss'; -import UnocssAstroIntegration from 'unocss/astro'; -import type { DarkModeSelectors } from 'unocss/preset-mini'; -import { name } from '../package.json'; -import { StudioCMSDashboardOptionsSchema } from './schema'; -import { checkForWebVitals } from './utils/checkForWebVitalsPlugin'; -import { injectRouteArray } from './utils/injectRouteArray'; - -const darkModeSelector: DarkModeSelectors = { - dark: '[data-theme="dark"]', -}; - -export default defineIntegration({ - name, - optionsSchema: StudioCMSDashboardOptionsSchema, - setup({ name, options }) { - // Create resolver relative to this file - const { resolve } = createResolver(import.meta.url); - - // Declaration for Web Vitals DTS File - let WEBVITALSDTSFILE: InjectedType; - - return { - hooks: { - 'astro:config:setup': async (params) => { - // Destructure Params - const { logger } = params; - - // Destructure Options - const { - verbose, - dashboardConfig: { UnoCSSConfigOverride }, - } = options; - - // Log that the setup has started - integrationLogger({ logger, logLevel: 'info', verbose }, DashboardStrings.Setup); - - // Inject `@it-astro:logger:{name}` Logger for runtime logging - runtimeLogger(params, { name: 'studiocms-dashboard' }); - - // Add Dashboard Integrations - integrationLogger( - { logger, logLevel: 'info', verbose }, - DashboardStrings.AddIntegrations - ); - addIntegrationArray(params, [ - { integration: astrolace({ injectCss: false }) }, - { - integration: UnocssAstroIntegration({ - configFile: false, - injectReset: UnoCSSConfigOverride.injectReset, - injectEntry: UnoCSSConfigOverride.injectEntry, - transformers: [transformerDirectives()], - presets: [ - presetUno({ - dark: darkModeSelector, - }), - presetWind({ - dark: darkModeSelector, - }), - presetDaisy({ - themes: ['light', 'dark'], - darkTheme: 'dark', - }), - presetTypography(), - presetWebFonts({ - provider: 'google', - fonts: { - // Required Fonts for Google Icons - sans: 'Roboto', - mono: ['Fira Code', 'Fira Mono:400,700'], - }, - }), - ], - }), - }, - ]); - - // Inject Routes - injectRouteArray(params, { - options, - routes: [ - { - enabled: options.dbStartPage, - pattern: 'start/', - entrypoint: resolve('./firstTimeSetupRoutes/main.astro'), - _non_dashboard: true, - }, - { - enabled: options.dbStartPage, - pattern: 'done/', - entrypoint: resolve('./firstTimeSetupRoutes/done.astro'), - _non_dashboard: true, - }, - { - enabled: options.dbStartPage, - pattern: 'api/setup', - entrypoint: resolve('./routes/api/firstTimeSetup.ts'), - _non_dashboard: true, - }, - { - enabled: options.dashboardConfig.dashboardEnabled && !options.dbStartPage, - pattern: '/', - entrypoint: resolve('./routes/index.astro'), - }, - { - enabled: options.dashboardConfig.dashboardEnabled && !options.dbStartPage, - pattern: 'profile/', - entrypoint: resolve('./routes/profile.astro'), - }, - { - enabled: options.dashboardConfig.dashboardEnabled && !options.dbStartPage, - pattern: 'configuration/', - entrypoint: resolve('./routes/configuration/index.astro'), - }, - { - enabled: options.dashboardConfig.dashboardEnabled && !options.dbStartPage, - pattern: 'configuration/admins/', - entrypoint: resolve('./routes/configuration/admins.astro'), - }, - { - enabled: options.dashboardConfig.dashboardEnabled && !options.dbStartPage, - pattern: 'new/page/', - entrypoint: resolve('./routes/create-page.astro'), - }, - { - enabled: options.dashboardConfig.dashboardEnabled && !options.dbStartPage, - pattern: 'page-list/', - entrypoint: resolve('./routes/page-list.astro'), - }, - { - enabled: options.dashboardConfig.dashboardEnabled && !options.dbStartPage, - pattern: 'edit/pages/[...id]', - entrypoint: resolve('./routes/edit-pages/[...id].astro'), - }, - { - enabled: options.dashboardConfig.dashboardEnabled && !options.dbStartPage, - pattern: 'delete/pages/[...id]', - entrypoint: resolve('./routes/delete-pages/[...id].astro'), - }, - { - enabled: options.dashboardConfig.dashboardEnabled && !options.dbStartPage, - pattern: 'api/liverender', - entrypoint: resolve('./routes/api/LiveRender.astro'), - }, - { - enabled: - options.dashboardConfig.dashboardEnabled && - !options.dbStartPage && - options.dashboardConfig.AuthConfig.enabled, - pattern: 'api/config/site', - entrypoint: resolve('./routes/api/config/site.ts'), - }, - { - enabled: - options.dashboardConfig.dashboardEnabled && - !options.dbStartPage && - options.dashboardConfig.AuthConfig.enabled, - pattern: 'api/config/admin', - entrypoint: resolve('./routes/api/config/admin.ts'), - }, - { - enabled: - options.dashboardConfig.dashboardEnabled && - !options.dbStartPage && - options.dashboardConfig.AuthConfig.enabled, - pattern: 'api/pages/create', - entrypoint: resolve('./routes/api/pages/create.ts'), - }, - { - enabled: - options.dashboardConfig.dashboardEnabled && - !options.dbStartPage && - options.dashboardConfig.AuthConfig.enabled, - pattern: 'api/pages/edit', - entrypoint: resolve('./routes/api/pages/edit.ts'), - }, - { - enabled: - options.dashboardConfig.dashboardEnabled && - !options.dbStartPage && - options.dashboardConfig.AuthConfig.enabled, - pattern: 'api/pages/delete', - entrypoint: resolve('./routes/api/pages/delete.ts'), - }, - ], - }); - - // Check for `@astrojs/web-vitals` Integration - const { webVitalDtsFile } = checkForWebVitals(params, { name, verbose }); - - // Set the Web Vitals DTS File - WEBVITALSDTSFILE = webVitalDtsFile; - - // Log that the setup is complete - integrationLogger({ logger, logLevel: 'info', verbose }, DashboardStrings.SetupComplete); - }, - 'astro:config:done': async ({ injectTypes }) => { - // Inject the Web Vitals DTS File - injectTypes(WEBVITALSDTSFILE); - }, - 'astro:server:start': async ({ logger }) => { - // Display Console Message if dbStartPage(First Time DB Initialization) is enabled - if (options.dbStartPage) { - integrationLogger({ logger, logLevel: 'warn', verbose: true }, DbErrors.DbStartPage); - } - }, - }, - }; - }, -}); diff --git a/packages/studiocms_dashboard/src/routes/404.astro b/packages/studiocms_dashboard/src/routes/404.astro new file mode 100644 index 000000000..ab3bb9ced --- /dev/null +++ b/packages/studiocms_dashboard/src/routes/404.astro @@ -0,0 +1,34 @@ +--- +import '../styles/404.css'; +import { Button } from '@studiocms/ui/components'; +import { Layout } from '../components'; +--- + + +
+
+ + + + + +

Error 404

+

Page not found

+

The page you are looking for might have been removed, had its name changed or is temporarily unavailable.

+ +
+
+
+ + diff --git a/packages/studiocms_dashboard/src/routes/api/LiveRender.astro b/packages/studiocms_dashboard/src/routes/api/LiveRender.astro deleted file mode 100644 index 4201ebf29..000000000 --- a/packages/studiocms_dashboard/src/routes/api/LiveRender.astro +++ /dev/null @@ -1,20 +0,0 @@ ---- -export const partial = true; -import { StudioCMSRenderer } from 'studiocms:renderer'; - -type Content = string | null; - -const query = Astro.url.searchParams.get('content'); -const preQuery = Astro.url.searchParams.get('preload-content'); - -let content: Content = null; - -if (query && query !== 'null') { - content = query; -} else if (preQuery && preQuery !== 'null') { - content = preQuery; -} else { - content = 'No content to display'; -} ---- - \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/routes/api/config/admin.ts b/packages/studiocms_dashboard/src/routes/api/config/admin.ts deleted file mode 100644 index 170e9d7ab..000000000 --- a/packages/studiocms_dashboard/src/routes/api/config/admin.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { logger } from '@it-astro:logger:studiocms-dashboard'; -import { db } from 'astro:db'; -// import { authHelper } from 'studiocms:auth/helpers'; -import Config from 'virtual:studiocms/config'; -import { tsPermissions } from '@studiocms/core/db/tsTables'; -import type { APIContext } from 'astro'; -import { simpleResponse } from '../../../utils/simpleResponse'; - -import { getUserData } from 'studiocms:auth/lib/user'; - -const { - dashboardConfig: { - developerConfig: { testingAndDemoMode }, - }, -} = Config; - -export async function POST(context: APIContext): Promise { - // Check if testing and demo mode is enabled - if (testingAndDemoMode) { - logger.warn('Testing and demo mode is enabled, this action is disabled.'); - return simpleResponse(400, 'Testing and demo mode is enabled, this action is disabled.'); - } - - // Map Locals - // const locals = context.locals; - const userdata = await getUserData(context); - - // Check if user is logged in - if (!userdata.isLoggedIn) { - return simpleResponse(403, 'Unauthorized'); - } - - // Check if user has permission - if (userdata.isLoggedIn) { - const { permissionLevel } = userdata; - if (permissionLevel !== 'admin') { - return simpleResponse(403, 'Unauthorized'); - } - } - - // Get form data - const formData = await context.request.formData(); - const newUser = formData.get('username')?.toString(); - const rank = formData.get('rank')?.toString(); - - // Check if newUser and rank Exists - if (!newUser || !rank) { - logger.error('Invalid User or Rank'); - return simpleResponse(400, 'Invalid User or Rank'); - } - - // Check if User already exists - const currentAdmins = await db.select().from(tsPermissions); - for (const admin of currentAdmins) { - if (admin.user === newUser) { - logger.error('Admin already exists'); - return simpleResponse(400, 'Admin already exists'); - } - } - - // Update Database - try { - await db.insert(tsPermissions).values({ user: newUser, rank: rank }).returning(); - } catch (error) { - // Log error - if (error instanceof Error) { - logger.error(error.message); - } - // Return Error Response - return simpleResponse(500, 'Error updating Admin list'); - } - - // Return Response - logger.info('New Admin Added'); - return simpleResponse(200, 'New Admin Added'); -} diff --git a/packages/studiocms_dashboard/src/routes/api/config/site.ts b/packages/studiocms_dashboard/src/routes/api/config/site.ts deleted file mode 100644 index 42fd2e6ef..000000000 --- a/packages/studiocms_dashboard/src/routes/api/config/site.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { logger } from '@it-astro:logger:studiocms-dashboard'; -// import { authHelper } from 'studiocms:auth/helpers'; -import Config from 'virtual:studiocms/config'; -import type { APIContext } from 'astro'; -import { astroDb } from '../../../utils/astroDb'; -import { simpleResponse } from '../../../utils/simpleResponse'; - -import { getUserData } from 'studiocms:auth/lib/user'; - -const { - dashboardConfig: { - developerConfig: { testingAndDemoMode }, - }, -} = Config; - -export async function POST(context: APIContext): Promise { - // Check if testing and demo mode is enabled - if (testingAndDemoMode) { - logger.warn('Testing and demo mode is enabled, this action is disabled.'); - return simpleResponse(400, 'Testing and demo mode is enabled, this action is disabled.'); - } - - // Map Locals - // const locals = context.locals; - const userdata = await getUserData(context); - - // Check if user is logged in - if (!userdata.isLoggedIn) { - return simpleResponse(403, 'Unauthorized'); - } - - // Check if user has permission - if (userdata.isLoggedIn) { - const { permissionLevel } = userdata; - if (permissionLevel !== 'admin') { - return simpleResponse(403, 'Unauthorized'); - } - } - - // Get form data - const formData = await context.request.formData(); - const title = formData.get('title')?.toString(); - const description = formData.get('description')?.toString(); - - // Check if title and description exists - if (!title || !description) { - logger.error('Invalid title or description'); - return simpleResponse(400, 'Invalid title or description'); - } - - // Update Database - try { - await astroDb().siteConfig().update({ title, description }); - } catch (error) { - // Log error - if (error instanceof Error) { - logger.error(error.message); - } - // Return Error Response - return simpleResponse(500, 'Error updating site config'); - } - - // Return Response - logger.info('Site config updated'); - return simpleResponse(200, 'Site config updated'); -} diff --git a/packages/studiocms_dashboard/src/routes/configuration.astro b/packages/studiocms_dashboard/src/routes/configuration.astro new file mode 100644 index 000000000..3192db639 --- /dev/null +++ b/packages/studiocms_dashboard/src/routes/configuration.astro @@ -0,0 +1,36 @@ +--- +import { prerenderRoutes as pagePreRendered } from 'studiocms:mode'; +import { Button } from '@studiocms/ui/components'; +import Icon from '@studiocms/ui/utils/Icon.astro'; +// import { useTranslations } from 'studiocms:i18n'; +import { Layout } from '../components'; +import PageHeader from '../components/PageHeader.astro'; +import ConfigForm from '../components/islands/configuration/ConfigForm.astro'; + +const lang = 'en-us'; +// const t = useTranslations(lang, '@studiocms/dashboard:index'); +--- + + + +
+ + + +
+ + { + pagePreRendered + ? + : + } + +
diff --git a/packages/studiocms_dashboard/src/routes/configuration/admins.astro b/packages/studiocms_dashboard/src/routes/configuration/admins.astro deleted file mode 100644 index af95c53a3..000000000 --- a/packages/studiocms_dashboard/src/routes/configuration/admins.astro +++ /dev/null @@ -1,176 +0,0 @@ ---- -import { logger } from '@it-astro:logger:studiocms-dashboard'; -import { getPermissionsList, getSiteConfig } from 'studiocms:helpers/contentHelper'; -import { StudioCMSRoutes } from 'studiocms:helpers/routemap'; -import { - Alert, - Button, - Divider, - Icon, - Input, - Radio, - RadioGroup, -} from '@matthiesenxyz/astrolace/components'; -import { toPascalCase } from '@matthiesenxyz/integration-utils/textFormatters'; -import check2Circle from '@studiocms/assets/svgs/check2-circle.svg'; -import exclamationOctagon from '@studiocms/assets/svgs/exclamation-octagon.svg'; -import { Layout } from '../../components'; -import CrumbStack from '../../components/CrumbStack.astro'; -import { makePageTitle } from '../../utils/makePageTitle'; - -import { getUserData, verifyUserPermissionLevel } from 'studiocms:auth/lib/user'; - -const user = await getUserData(Astro); - -if (!user.user) { - return Astro.redirect(StudioCMSRoutes.authLinks.loginURL); -} - -const { - mainLinks: { dashboardIndex, siteConfiguration, configurationAdmins }, - endpointLinks: { - config: { adminConfig: adminConfigEndpoint }, - }, -} = StudioCMSRoutes; - -const adminList = await getPermissionsList(); -const contextConfig = await getSiteConfig(); - -// Check Permission Level -if (!verifyUserPermissionLevel(user, 'admin')) { - logger.info('User is not an admin. Redirecting to profile page.'); - return Astro.redirect(StudioCMSRoutes.mainLinks.userProfile); -} ---- - - - - - -
- -

Site Admins

- -
-
Current Admins:
- -
- - - - - - - - - { - adminList.map((admin) => { - return ( - - - { - admin.rank === 'admin' ? - : - - } - - ) - }) - } - -
UsernameRank
{admin.username}{toPascalCase(admin.rank)}{toPascalCase(admin.rank)}
-
-
- -
- - Note: To delete admins you can do this from your database. - -
- -
- - - -
Add New Admin or Editor
- - - - - - Editor - Admin - - -
- -
- - -
- - - - Something went wrong! - -
-
- - - Success! - -
-
- -
-
- - - \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/routes/configuration/index.astro b/packages/studiocms_dashboard/src/routes/configuration/index.astro deleted file mode 100644 index 9c5961bfd..000000000 --- a/packages/studiocms_dashboard/src/routes/configuration/index.astro +++ /dev/null @@ -1,141 +0,0 @@ ---- -import { logger } from '@it-astro:logger:studiocms-dashboard'; -import { getSiteConfig } from 'studiocms:helpers/contentHelper'; -import { StudioCMSRoutes } from 'studiocms:helpers/routemap'; -import { Alert, Button, Divider, Icon, Input } from '@matthiesenxyz/astrolace/components'; -import check2Circle from '@studiocms/assets/svgs/check2-circle.svg'; -import exclamationOctagon from '@studiocms/assets/svgs/exclamation-octagon.svg'; -import { Layout } from '../../components'; -import CrumbStack from '../../components/CrumbStack.astro'; -import { makePageTitle } from '../../utils/makePageTitle'; - -import { getUserData, verifyUserPermissionLevel } from 'studiocms:auth/lib/user'; - -const user = await getUserData(Astro); - -if (!user.user) { - return Astro.redirect(StudioCMSRoutes.authLinks.loginURL); -} - -const { - mainLinks: { dashboardIndex, siteConfiguration, configurationAdmins }, - endpointLinks: { - config: { siteConfig: siteConfigEndpoint }, - }, -} = StudioCMSRoutes; - -const contextConfig = await getSiteConfig(); - -// Check Permission Level -if (!verifyUserPermissionLevel(user, 'admin')) { - logger.info('User is not an admin. Redirecting to profile page.'); - return Astro.redirect(StudioCMSRoutes.mainLinks.userProfile); -} ---- - - - - - -
- -

Site Configuration

- -
- -
- -
-
- - - - - - - - - -
- -
- - -
- - - - - Something went wrong! - -
-
- - - Success! - -
-
- -
-
- - \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/routes/content-management.astro b/packages/studiocms_dashboard/src/routes/content-management.astro new file mode 100644 index 000000000..b8ea35c32 --- /dev/null +++ b/packages/studiocms_dashboard/src/routes/content-management.astro @@ -0,0 +1,114 @@ +--- +import { prerenderRoutes as pagePreRendered } from 'studiocms:mode'; +import { Button, Divider, Input } from '@studiocms/ui/components'; +import Icon from '@studiocms/ui/utils/Icon.astro'; +// import { useTranslations } from 'studiocms:i18n'; +import { Layout } from '../components'; +import Create from '../components/islands/content-mgmt/Create.astro'; +import Edit from '../components/islands/content-mgmt/Edit.astro'; +import PageHeader from '../components/islands/content-mgmt/PageHeader.astro'; +import PageList from '../components/islands/content-mgmt/PageList.astro'; + +const lang = 'en-us'; +// const t = useTranslations(lang, '@studiocms/dashboard:index'); +--- + + + +
+ +
+ +
+ +
+ +
+

Select a page or create a new one to get started!

+
+ + + + +
+ + diff --git a/packages/studiocms_dashboard/src/routes/create-page.astro b/packages/studiocms_dashboard/src/routes/create-page.astro index d306ddbd3..f0b17a90d 100644 --- a/packages/studiocms_dashboard/src/routes/create-page.astro +++ b/packages/studiocms_dashboard/src/routes/create-page.astro @@ -1,52 +1,16 @@ --- -import { logger } from '@it-astro:logger:studiocms-dashboard'; -import { getSiteConfig } from 'studiocms:helpers/contentHelper'; -import { StudioCMSRoutes } from 'studiocms:helpers/routemap'; +import { prerenderRoutes as pagePreRendered } from 'studiocms:mode'; import { Layout } from '../components'; -import CrumbStack from '../components/CrumbStack.astro'; -import PageEditAndCreateForm from '../components/PageEditAndCreateForm.astro'; -import { makePageTitle } from '../utils/makePageTitle'; -import { getUserData, verifyUserPermissionLevel } from 'studiocms:auth/lib/user'; - -const user = await getUserData(Astro); - -if (!user.user) { - return Astro.redirect(StudioCMSRoutes.authLinks.loginURL); -} - -const { - mainLinks: { dashboardIndex, pageNew, pageEdit }, - endpointLinks: { - partials: { livePreviewBox }, - pages: { createPages: createPagesEndpoint }, - }, -} = StudioCMSRoutes; - -const contextConfig = await getSiteConfig(); - -// Check Permission Level -if (!verifyUserPermissionLevel(user, 'editor')) { - logger.info('User is not an admin or editor. Redirecting to profile page.'); - return Astro.redirect(StudioCMSRoutes.mainLinks.userProfile); -} +const lang = 'en-us'; +// const t = useTranslations(lang, '@studiocms/dashboard:index'); --- - - - - + title="Create Page" + requiredPermission='editor' + {pagePreRendered} + {lang} + > - \ No newline at end of file + diff --git a/packages/studiocms_dashboard/src/routes/delete-pages/[...id].astro b/packages/studiocms_dashboard/src/routes/delete-pages/[...id].astro deleted file mode 100644 index 4f2649a14..000000000 --- a/packages/studiocms_dashboard/src/routes/delete-pages/[...id].astro +++ /dev/null @@ -1,150 +0,0 @@ ---- -import { logger } from '@it-astro:logger:studiocms-dashboard'; -import { getPageById, getSiteConfig } from 'studiocms:helpers/contentHelper'; -import { StudioCMSRoutes, getDeleteRoute } from 'studiocms:helpers/routemap'; -import { - Alert, - Button, - Divider, - Icon, - Input, - VisuallyHidden, -} from '@matthiesenxyz/astrolace/components'; -import check2Circle from '@studiocms/assets/svgs/check2-circle.svg'; -import exclamationOctagon from '@studiocms/assets/svgs/exclamation-octagon.svg'; -import { Layout } from '../../components'; -import CrumbStack from '../../components/CrumbStack.astro'; -import { makePageTitle } from '../../utils/makePageTitle'; - -import { getUserData, verifyUserPermissionLevel } from 'studiocms:auth/lib/user'; - -const user = await getUserData(Astro); - -if (!user.user) { - return Astro.redirect(StudioCMSRoutes.authLinks.loginURL); -} - -const { - mainLinks: { dashboardIndex, pageEdit }, - endpointLinks: { - pages: { deletePages: deletePagesEndpoint }, - }, -} = StudioCMSRoutes; - -const { id } = Astro.params; - -if (!id) { - return Astro.redirect(dashboardIndex); -} -const contextConfig = await getSiteConfig(); -const page = await getPageById(id); - -if (!page) { - return Astro.redirect(dashboardIndex); -} - -const deleteURL = await getDeleteRoute(id); - -// Check Permission Level -if (!verifyUserPermissionLevel(user, 'editor')) { - logger.info('User is not an admin or editor. Redirecting to profile page.'); - return Astro.redirect(StudioCMSRoutes.mainLinks.userProfile); -} ---- - - - - -
- -

Delete Page: {page.title}

- -
- - Slug: {page.slug} - -
- -
- -
- - - - - - - - - - - - - - - - - Something went wrong! - -
-
- - - Success! - -
-
- - -
{pageEdit}
-
- -
-
-
- - \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/routes/edit-pages/[...id].astro b/packages/studiocms_dashboard/src/routes/edit-pages/[...id].astro deleted file mode 100644 index bd914740b..000000000 --- a/packages/studiocms_dashboard/src/routes/edit-pages/[...id].astro +++ /dev/null @@ -1,66 +0,0 @@ ---- -import { logger } from '@it-astro:logger:studiocms-dashboard'; -import { getPageById, getSiteConfig } from 'studiocms:helpers/contentHelper'; -import { StudioCMSRoutes, getEditRoute } from 'studiocms:helpers/routemap'; -import { Layout } from '../../components'; -import CrumbStack from '../../components/CrumbStack.astro'; -import PageEditAndCreateForm from '../../components/PageEditAndCreateForm.astro'; -import { makePageTitle } from '../../utils/makePageTitle'; - -import { getUserData, verifyUserPermissionLevel } from 'studiocms:auth/lib/user'; - -const user = await getUserData(Astro); - -if (!user.user) { - return Astro.redirect(StudioCMSRoutes.authLinks.loginURL); -} - -const { - mainLinks: { dashboardIndex, pageEdit }, - endpointLinks: { - partials: { livePreviewBox }, - pages: { editPages: editPagesEndpoint }, - }, -} = StudioCMSRoutes; - -const { id } = Astro.params; - -if (!id) { - return Astro.redirect(dashboardIndex); -} -const contextConfig = await getSiteConfig(); -const pageData = await getPageById(id); - -if (!pageData.id) { - return Astro.redirect(dashboardIndex); -} - -const editRoute = await getEditRoute(id); - -// Check Permission Level -if (!verifyUserPermissionLevel(user, 'editor')) { - logger.info('User is not an admin or editor. Redirecting to profile page.'); - return Astro.redirect(StudioCMSRoutes.mainLinks.userProfile); -} ---- - - - - - - - diff --git a/packages/studiocms_dashboard/src/routes/index.astro b/packages/studiocms_dashboard/src/routes/index.astro index 95518de42..902bfe832 100644 --- a/packages/studiocms_dashboard/src/routes/index.astro +++ b/packages/studiocms_dashboard/src/routes/index.astro @@ -1,84 +1,41 @@ --- -import { logger } from '@it-astro:logger:studiocms-dashboard'; -import { getWebVitals } from 'studiocms-dashboard:web-vitals'; -import { getSiteConfig } from 'studiocms:helpers/contentHelper'; -import { StudioCMSRoutes } from 'studiocms:helpers/routemap'; -import { Alert, Card, Details, Divider, Icon } from '@matthiesenxyz/astrolace/components'; -import check2Circle from '@studiocms/assets/svgs/check2-circle.svg'; -import exclamationTriangle from '@studiocms/assets/svgs/exclamation-triangle.svg'; +// import { getWebVitals } from 'studiocms-dashboard:web-vitals'; +import { useTranslations } from 'studiocms:i18n'; +import { prerenderRoutes as pagePreRendered } from 'studiocms:mode'; +import { Card } from '@studiocms/ui/components'; import { Layout } from '../components'; -import CrumbStack from '../components/CrumbStack.astro'; -import DashboardButtons from '../components/DashboardButtons.astro'; -import WebVitalPanel from '../components/WebVitalPanel.astro'; -import { makePageTitle } from '../utils/makePageTitle'; +import DashboardPageHeader from '../components/DashboardPageHeader.astro'; -import { getUserData, verifyUserPermissionLevel } from 'studiocms:auth/lib/user'; - -const user = await getUserData(Astro); - -if (!user.user) { - return Astro.redirect(StudioCMSRoutes.authLinks.loginURL); -} - -const { - mainLinks: { dashboardIndex }, -} = StudioCMSRoutes; - -const contextConfig = await getSiteConfig(); +const lang = 'en-us'; +const t = useTranslations(lang, '@studiocms/dashboard:index'); // Get the web vitals -const webVitals = await getWebVitals(); - -// Check Permission Level -if (!verifyUserPermissionLevel(user, 'editor')) { - logger.info('User is not an admin or editor. Redirecting to profile page.'); - return Astro.redirect(StudioCMSRoutes.mainLinks.userProfile); -} +// const webVitals = await getWebVitals(); --- - - - -
- -

Dashboard

- - -

This is StudioCMS a free and open-source CMS built from the ground up by the Astro Community.

-
- - - - - -

This project is Experimental and should not be used in production at this time.

-
- - { webVitals.length > 0 && ( - - - - -
-
- -

Web Vitals is ENABLED! Performance Metrics are being collected.

-
- -
- -
- )} - - - -
-
\ No newline at end of file + title={t('title')} + requiredPermission='editor' + {pagePreRendered} + {lang} + > + +
+ + {t('sub-header')} +
+ +
+ + + + + + + + + + +
+ + diff --git a/packages/studiocms_dashboard/src/routes/page-list.astro b/packages/studiocms_dashboard/src/routes/page-list.astro deleted file mode 100644 index 155672693..000000000 --- a/packages/studiocms_dashboard/src/routes/page-list.astro +++ /dev/null @@ -1,58 +0,0 @@ ---- -import { logger } from '@it-astro:logger:studiocms-dashboard'; -import { getPageList, getSiteConfig } from 'studiocms:helpers/contentHelper'; -import { StudioCMSRoutes } from 'studiocms:helpers/routemap'; -import { Layout } from '../components'; -import CrumbStack from '../components/CrumbStack.astro'; -import PageListTable from '../components/PageListTable.astro'; -import { makePageTitle } from '../utils'; - -import { getUserData, verifyUserPermissionLevel } from 'studiocms:auth/lib/user'; - -const user = await getUserData(Astro); - -if (!user.user) { - return Astro.redirect(StudioCMSRoutes.authLinks.loginURL); -} - -const { permissionLevel } = user; - -const { - mainLinks: { dashboardIndex, pageEdit }, -} = StudioCMSRoutes; - -const contextConfig = await getSiteConfig(); -const pages = await getPageList(); - -// Check Permission Level -if (!verifyUserPermissionLevel(user, 'editor')) { - logger.info('User is not an admin or editor. Redirecting to profile page.'); - return Astro.redirect(StudioCMSRoutes.mainLinks.userProfile); -} ---- - - - - - -
- -

Existing Pages

- -
-
- -
-
- -
-
- diff --git a/packages/studiocms_dashboard/src/routes/profile.astro b/packages/studiocms_dashboard/src/routes/profile.astro index 949cd8b0d..e35314537 100644 --- a/packages/studiocms_dashboard/src/routes/profile.astro +++ b/packages/studiocms_dashboard/src/routes/profile.astro @@ -1,72 +1,83 @@ --- -import { getSiteConfig } from 'studiocms:helpers/contentHelper'; -import { StudioCMSRoutes } from 'studiocms:helpers/routemap'; -import { Card, Divider } from '@matthiesenxyz/astrolace/components'; -import { toPascalCase } from '@matthiesenxyz/integration-utils/textFormatters'; +// import { useTranslations } from 'studiocms:i18n'; +import { AuthConfig } from 'studiocms:config'; +import { prerenderRoutes as pagePreRendered } from 'studiocms:mode'; import { Layout } from '../components'; -import CrumbStack from '../components/CrumbStack.astro'; -import { makePageTitle } from '../utils'; +import PageHeader from '../components/PageHeader.astro'; +import BasicInfo from '../components/islands/profile/BasicInfo.astro'; +import BasicInfoFallback from '../components/islands/profile/BasicInfoFallback.astro'; +import SocialSignin from '../components/islands/profile/SocialSignin.astro'; +import SocialSigninFallback from '../components/islands/profile/SocialSigninFallback.astro'; +import UpdatePassword from '../components/islands/profile/UpdatePassword.astro'; +import UpdatePasswordFallback from '../components/islands/profile/UpdatePasswordFallback.astro'; +import { providerData, showOAuth } from '../components/islands/profile/oAuthButtonProviders'; -import { getUserData } from 'studiocms:auth/lib/user'; - -const user = await getUserData(Astro); - -if (!user.user) { - return Astro.redirect(StudioCMSRoutes.authLinks.loginURL); -} - -const { - permissionLevel, - user: { name, username, url }, -} = user; +const lang = 'en-us'; +// const t = useTranslations(lang, '@studiocms/dashboard:index'); const { - mainLinks: { dashboardIndex, userProfile }, -} = StudioCMSRoutes; + providers: { usernameAndPassword }, +} = AuthConfig; -const contextConfig = await getSiteConfig(); +const shouldShowOAuth = showOAuth && providerData.some(({ enabled }) => enabled); --- + title={'User Profile'} + requiredPermission="visitor" + {pagePreRendered} + {lang} + > - +
+ +
-
+
-

User Profile

+ { pagePreRendered ? ( + <> + + + + + { usernameAndPassword && ( + + + + )} + + { shouldShowOAuth && ( + + + + )} - -
- {username}'s Profile -
- -
- - Display Name: {name} - - - - Role: {toPascalCase(permissionLevel)} - - - { url && ( - - Url: {url} - - - )} -
-
+ + ) : ( + <> + -
+ { usernameAndPassword && ( + + )} + + { shouldShowOAuth && ( + + )} + + + )} + +
+ + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/routes/studiocms_api/LiveRender.astro b/packages/studiocms_dashboard/src/routes/studiocms_api/LiveRender.astro new file mode 100644 index 000000000..2aeb9b05c --- /dev/null +++ b/packages/studiocms_dashboard/src/routes/studiocms_api/LiveRender.astro @@ -0,0 +1,30 @@ +--- +export const partial = true; +import { StudioCMSRenderer } from 'studiocms:renderer'; + +type Content = string; + +const queryParam = Astro.url.searchParams.get('content'); +const preQuery = Astro.url.searchParams.get('preload-content'); + +async function setContent() { + const jsonData: { content: string | undefined } | undefined = await Astro.request.json(); + + if (jsonData?.content) { + return jsonData.content; + } + + if (queryParam && queryParam !== 'null') { + return queryParam; + } + + if (preQuery && preQuery !== 'null') { + return preQuery; + } + + return 'No content to display'; +} + +const content: Content = await setContent(); +--- + \ No newline at end of file diff --git a/packages/studiocms_dashboard/src/routes/studiocms_api/config/admin.ts b/packages/studiocms_dashboard/src/routes/studiocms_api/config/admin.ts new file mode 100644 index 000000000..43c5d9e46 --- /dev/null +++ b/packages/studiocms_dashboard/src/routes/studiocms_api/config/admin.ts @@ -0,0 +1,68 @@ +import { logger } from '@it-astro:logger:studiocms-dashboard'; +import { developerConfig } from 'studiocms:config'; +import studioCMS_SDK from 'studiocms:sdk'; +import type { APIContext } from 'astro'; +import { simpleResponse } from '../../../utils/simpleResponse'; + +import { getUserData, verifyUserPermissionLevel } from 'studiocms:auth/lib/user'; + +const { testingAndDemoMode } = developerConfig; + +export async function POST(context: APIContext): Promise { + // Check if testing and demo mode is enabled + if (testingAndDemoMode) { + logger.warn('Testing and demo mode is enabled, this action is disabled.'); + return simpleResponse(400, 'Testing and demo mode is enabled, this action is disabled.'); + } + + const userData = await getUserData(context); + + // Check if user is logged in + if (!userData.isLoggedIn) { + return simpleResponse(403, 'Unauthorized'); + } + + // Check if user has permission + const isAuthorized = await verifyUserPermissionLevel(userData, 'admin'); + if (!isAuthorized) { + return simpleResponse(403, 'Unauthorized'); + } + + // Get form data + const formData = await context.request.formData(); + const newUser = formData.get('userid')?.toString(); + const rank = formData.get('rank')?.toString(); + + // Check if newUser and rank Exists + if (!newUser || !rank) { + logger.error('Invalid User or Rank'); + return simpleResponse(400, 'Invalid User or Rank'); + } + + // Check userID + const userExists = await studioCMS_SDK.GET.databaseEntry.users.byId(newUser); + + if (!userExists) { + logger.error('User does not exist'); + return simpleResponse(400, 'User does not exist'); + } + + try { + const userHasRank = await studioCMS_SDK.AUTH.permission.currentStatus(newUser); + + if (userHasRank) { + await studioCMS_SDK.UPDATE.permissions({ user: userHasRank.user, rank: rank }); + logger.info('New Admin Added'); + return simpleResponse(200, 'New Admin Added'); + } + + await studioCMS_SDK.POST.databaseEntry.permissions(newUser, rank); + logger.info('New Admin Added'); + return simpleResponse(200, 'New Admin Added'); + } catch (error) { + if (error instanceof Error) { + logger.error(error.message); + } + return simpleResponse(500, 'Error updating Admin list'); + } +} diff --git a/packages/studiocms_dashboard/src/routes/studiocms_api/config/site.ts b/packages/studiocms_dashboard/src/routes/studiocms_api/config/site.ts new file mode 100644 index 000000000..708c5cd9f --- /dev/null +++ b/packages/studiocms_dashboard/src/routes/studiocms_api/config/site.ts @@ -0,0 +1,77 @@ +import { logger } from '@it-astro:logger:studiocms-dashboard'; +import { getUserData, verifyUserPermissionLevel } from 'studiocms:auth/lib/user'; +import { developerConfig } from 'studiocms:config'; +import studioCMS_SDK from 'studiocms:sdk'; +import { CMSSiteConfigId } from '@studiocms/core/consts'; +import type { APIContext } from 'astro'; +import { simpleResponse } from '../../../utils/simpleResponse'; + +const { testingAndDemoMode } = developerConfig; + +export async function POST(context: APIContext): Promise { + // Check if testing and demo mode is enabled + if (testingAndDemoMode) { + logger.warn('Testing and demo mode is enabled, this action is disabled.'); + return simpleResponse(400, 'Testing and demo mode is enabled, this action is disabled.'); + } + + // Map Locals + // const locals = context.locals; + const userData = await getUserData(context); + + // Check if user is logged in + if (!userData.isLoggedIn) { + return simpleResponse(403, 'Unauthorized'); + } + + // Check if user has permission + const isAuthorized = await verifyUserPermissionLevel(userData, 'owner'); + if (!isAuthorized) { + return simpleResponse(403, 'Unauthorized'); + } + + // Get form data + const formData = await context.request.formData(); + const title = formData.get('title')?.toString(); + const description = formData.get('description')?.toString(); + const defaultOgImage = formData.get('defaultOgImage')?.toString(); + const loginPageBackground = formData.get('loginPageBackground')?.toString(); + const loginPageCustomImage = formData.get('loginPageCustomImage')?.toString(); + const siteIcon = formData.get('siteIcon')?.toString(); + + // Check if title and description exists + if ( + !title || + !description || + !defaultOgImage || + !loginPageBackground || + !loginPageCustomImage || + !siteIcon + ) { + logger.error('Invalid form data'); + return simpleResponse(400, 'Invalid form data'); + } + + // Update Database + try { + await studioCMS_SDK.UPDATE.siteConfig({ + title, + description, + id: CMSSiteConfigId, + defaultOgImage, + loginPageBackground, + loginPageCustomImage, + siteIcon, + }); + + logger.info('Site config updated'); + return simpleResponse(200, 'Site config updated'); + } catch (error) { + // Log error + if (error instanceof Error) { + logger.error(error.message); + } + // Return Error Response + return simpleResponse(500, 'Error updating site config'); + } +} diff --git a/packages/studiocms_dashboard/src/routes/api/firstTimeSetup.ts b/packages/studiocms_dashboard/src/routes/studiocms_api/firstTimeSetup.ts similarity index 67% rename from packages/studiocms_dashboard/src/routes/api/firstTimeSetup.ts rename to packages/studiocms_dashboard/src/routes/studiocms_api/firstTimeSetup.ts index 04ad1b376..097fb2874 100644 --- a/packages/studiocms_dashboard/src/routes/api/firstTimeSetup.ts +++ b/packages/studiocms_dashboard/src/routes/studiocms_api/firstTimeSetup.ts @@ -1,13 +1,6 @@ -import { db, eq } from 'astro:db'; import { verifyPasswordStrength } from 'studiocms:auth/lib/password'; import { createLocalUser, verifyUsernameInput } from 'studiocms:auth/lib/user'; -import { CMSSiteConfigId } from '@studiocms/core/consts'; -import { - tsPageContent, - tsPageData, - tsPermissions, - tsSiteConfig, -} from '@studiocms/core/db/tsTables'; +import studioCMS_SDK from 'studiocms:sdk'; import type { APIContext } from 'astro'; function parseFormDataEntryToString(formData: FormData, key: string): string | null { @@ -49,14 +42,12 @@ export async function POST(context: APIContext): Promise { // Set the default og image to the hero image if not provided const DefaultHeroOrUserSetOgImage = ogImage || HERO_IMAGE; - // Generate the page IDs - const homePageID = crypto.randomUUID(); - const aboutPageID = crypto.randomUUID(); + let userId = ''; if (!title || !description) { return new Response( JSON.stringify({ - error: 'Missing Data: Title and Description are required', + error: 'Missing Data: Site Title and Description are required', }), { status: 400, @@ -92,7 +83,7 @@ export async function POST(context: APIContext): Promise { return new Response( JSON.stringify({ error: - 'Invalid Password: Password must be between 6 and 255 characters, not be a known unsafe password, and not be in the pwned password database', + 'Invalid Password: Password must be between 6 and 255 characters, and not be in the pwned password database.', }), { status: 400, @@ -103,12 +94,8 @@ export async function POST(context: APIContext): Promise { try { // Create the new user const newUser = await createLocalUser(name, username, email, password); - - // Insert the new user into the permissions table - await db.insert(tsPermissions).values({ - user: newUser.id, - rank: 'owner', - }); + await studioCMS_SDK.POST.databaseEntry.permissions(newUser.id, 'owner'); + userId = newUser.id; } catch (error) { return new Response( JSON.stringify({ @@ -123,7 +110,7 @@ export async function POST(context: APIContext): Promise { if (!oAuthAdmin || oAuthAdmin) { return new Response( JSON.stringify({ - error: 'oAuth Admin setup not yet implemented', + error: 'Initial oAuth admin setup is not yet implemented', }), { status: 400, @@ -137,11 +124,7 @@ export async function POST(context: APIContext): Promise { // }); } - const Config = await db - .select() - .from(tsSiteConfig) - .where(eq(tsSiteConfig.id, CMSSiteConfigId)) - .get(); + const Config = await studioCMS_SDK.GET.database.config(); if (Config) { return new Response( @@ -155,79 +138,79 @@ export async function POST(context: APIContext): Promise { ); } - // Insert Site Config - await db - .insert(tsSiteConfig) - .values({ - title: title, - description: description, - defaultOgImage: DefaultHeroOrUserSetOgImage, - }) - .catch(() => { - return new Response( - JSON.stringify({ - error: 'Config Error', - }), - { - status: 400, - } - ); - }); - - await db - .insert(tsPageData) - .values([ + await studioCMS_SDK.INIT.siteConfig({ + title, + description, + defaultOgImage: DefaultHeroOrUserSetOgImage, + }).catch(() => { + return new Response( + JSON.stringify({ + error: 'Config insert error', + }), { - id: homePageID, - title: 'Home', - slug: 'index', - showOnNav: true, - contentLang: 'default', - description: 'Index page', - heroImage: DefaultHeroOrUserSetOgImage, - }, + status: 400, + } + ); + }); + + await studioCMS_SDK.INIT.ghostUser().catch(() => { + return new Response( + JSON.stringify({ + error: 'Default Ghost user insert error', + }), { - id: aboutPageID, - title: 'About', - slug: 'about', - showOnNav: true, - contentLang: 'default', - description: 'About page', - heroImage: DefaultHeroOrUserSetOgImage, - }, - ]) - .catch(() => { - return new Response( - JSON.stringify({ - error: 'Page Data Error', - }), - { - status: 400, - } - ); - }); + status: 400, + } + ); + }); - // Insert Page Content - await db - .insert(tsPageContent) - .values([ + await studioCMS_SDK.POST.databaseEntries + .pages([ { - id: crypto.randomUUID(), - contentId: homePageID, - contentLang: 'default', - content: LOREM_IPSUM, + pageData: { + title: 'Home', + slug: 'index', + showOnNav: true, + contentLang: 'default', + description: 'Index page', + heroImage: DefaultHeroOrUserSetOgImage, + authorId: userId, + package: 'studiocms', + publishedAt: new Date(), + showAuthor: false, + showContributors: false, + updatedAt: new Date(), + }, + pageContent: { + content: LOREM_IPSUM, + contentLang: 'default', + }, }, { - id: crypto.randomUUID(), - contentId: aboutPageID, - contentLang: 'default', - content: LOREM_IPSUM, + pageData: { + title: 'About', + slug: 'about', + showOnNav: true, + contentLang: 'default', + description: 'About page', + heroImage: DefaultHeroOrUserSetOgImage, + authorId: userId, + package: 'studiocms', + publishedAt: new Date(), + showAuthor: false, + showContributors: false, + updatedAt: new Date(), + }, + pageContent: { + content: LOREM_IPSUM, + contentLang: 'default', + }, }, ]) .catch(() => { return new Response( JSON.stringify({ - error: 'Page Content Error', + error: 'Default pages insert error', }), { status: 400, diff --git a/packages/studiocms_dashboard/src/routes/api/pages/create.ts b/packages/studiocms_dashboard/src/routes/studiocms_api/pages/create.ts similarity index 95% rename from packages/studiocms_dashboard/src/routes/api/pages/create.ts rename to packages/studiocms_dashboard/src/routes/studiocms_api/pages/create.ts index ced1f6cba..563c250f6 100644 --- a/packages/studiocms_dashboard/src/routes/api/pages/create.ts +++ b/packages/studiocms_dashboard/src/routes/studiocms_api/pages/create.ts @@ -1,6 +1,6 @@ import { logger } from '@it-astro:logger:studiocms-dashboard'; // import { authHelper } from 'studiocms:auth/helpers'; -import Config from 'virtual:studiocms/config'; +import Config from 'studiocms:config'; import type { APIContext } from 'astro'; import { astroDb } from '../../../utils/astroDb'; import { simpleResponse } from '../../../utils/simpleResponse'; @@ -98,7 +98,9 @@ export async function POST(context: APIContext): Promise { return simpleResponse(500, 'Error creating page'); } - await astroDb().pageContent().insert({ id: newPage.id, lang: 'default', content }); + await astroDb() + .pageContent() + .insert({ contentId: newPage.id, contentLang: 'default', content }); } catch (error) { if (error instanceof Error) { logger.error(error.message); diff --git a/packages/studiocms_dashboard/src/routes/api/pages/delete.ts b/packages/studiocms_dashboard/src/routes/studiocms_api/pages/delete.ts similarity index 98% rename from packages/studiocms_dashboard/src/routes/api/pages/delete.ts rename to packages/studiocms_dashboard/src/routes/studiocms_api/pages/delete.ts index 15fc2fdfb..c965f0789 100644 --- a/packages/studiocms_dashboard/src/routes/api/pages/delete.ts +++ b/packages/studiocms_dashboard/src/routes/studiocms_api/pages/delete.ts @@ -1,6 +1,6 @@ import { logger } from '@it-astro:logger:studiocms-dashboard'; // import { authHelper } from 'studiocms:auth/helpers'; -import Config from 'virtual:studiocms/config'; +import Config from 'studiocms:config'; import type { APIContext } from 'astro'; import { astroDb } from '../../../utils/astroDb'; import { simpleResponse } from '../../../utils/simpleResponse'; diff --git a/packages/studiocms_dashboard/src/routes/api/pages/edit.ts b/packages/studiocms_dashboard/src/routes/studiocms_api/pages/edit.ts similarity index 98% rename from packages/studiocms_dashboard/src/routes/api/pages/edit.ts rename to packages/studiocms_dashboard/src/routes/studiocms_api/pages/edit.ts index fd1390503..0c72fa1b0 100644 --- a/packages/studiocms_dashboard/src/routes/api/pages/edit.ts +++ b/packages/studiocms_dashboard/src/routes/studiocms_api/pages/edit.ts @@ -1,6 +1,6 @@ import { logger } from '@it-astro:logger:studiocms-dashboard'; // import { authHelper } from 'studiocms:auth/helpers'; -import Config from 'virtual:studiocms/config'; +import Config from 'studiocms:config'; import type { APIContext } from 'astro'; import { astroDb } from '../../../utils/astroDb'; import { simpleResponse } from '../../../utils/simpleResponse'; diff --git a/packages/studiocms_dashboard/src/routes/test.astro b/packages/studiocms_dashboard/src/routes/test.astro new file mode 100644 index 000000000..67579aaf5 --- /dev/null +++ b/packages/studiocms_dashboard/src/routes/test.astro @@ -0,0 +1,22 @@ +--- +import { prerenderRoutes as pagePreRendered } from 'studiocms:mode'; +// import { useTranslations } from 'studiocms:i18n'; +import { Layout } from '../components'; +import TestIsland from '../components/islands/TestIsland.astro'; + +const lang = 'en-us'; +// const t = useTranslations(lang, '@studiocms/dashboard:index'); +--- + + + Sus + +
+ +
+
diff --git a/packages/studiocms_dashboard/src/routes/user-management.astro b/packages/studiocms_dashboard/src/routes/user-management.astro new file mode 100644 index 000000000..6c23c6d4d --- /dev/null +++ b/packages/studiocms_dashboard/src/routes/user-management.astro @@ -0,0 +1,21 @@ +--- +import { prerenderRoutes as pagePreRendered } from 'studiocms:mode'; +// import { useTranslations } from 'studiocms:i18n'; +import { Layout } from '../components'; +import PageHeader from '../components/PageHeader.astro'; + +const lang = 'en-us'; +// const t = useTranslations(lang, '@studiocms/dashboard:index'); +--- + + +
+ +
+ +
diff --git a/packages/studiocms_dashboard/src/stubs/webVitals.ts b/packages/studiocms_dashboard/src/stubs/webVitals.ts new file mode 100644 index 000000000..3f328eef1 --- /dev/null +++ b/packages/studiocms_dashboard/src/stubs/webVitals.ts @@ -0,0 +1,35 @@ +import DTSBuilder from '@matthiesenxyz/astrodtsbuilder'; +import { createResolver } from 'astro-integration-kit'; + +const { resolve } = createResolver(import.meta.url); + +const dtsFile = DTSBuilder(); + +dtsFile.addSingleLineNote( + 'This file is generated by StudioCMS and should not be modified manually.' +); + +dtsFile.addModule('studiocms-dashboard:web-vitals', { + namedExports: [ + { + name: 'getWebVitals', + multiLineDescription: [ + '# Web Vitals Helper Function', + '', + '@returns Promise', + ], + typeDef: `typeof import('${resolve('../utils/webVital.ts')}').getWebVitals`, + }, + ], + typeExports: [ + { + singleLineDescription: 'Web Vitals Response Item', + typeDef: `import('${resolve('../utils/webVital.ts')}').WebVitalsResponseItem`, + name: 'WebVitalsResponseItem', + }, + ], +}); + +const webVitalDtsFile = dtsFile.makeAstroInjectedType('web-vitals.d.ts'); + +export default webVitalDtsFile; diff --git a/packages/studiocms_dashboard/src/styles/404.css b/packages/studiocms_dashboard/src/styles/404.css new file mode 100644 index 000000000..29a85d2ea --- /dev/null +++ b/packages/studiocms_dashboard/src/styles/404.css @@ -0,0 +1,52 @@ +.notfound-container { + position: relative; + height: 80vh; + & .notfound { + position: absolute; + left: 50%; + top: 50%; + -webkit-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + max-width: 460px; + width: 100%; + text-align: center; + line-height: 1.4; + + & svg { + position: relative; + width: 250px; + height: 250px; + margin: 0px auto 50px; + } + + & h1 { + font-weight: 700; + margin: 0; + font-size: 3rem; + text-align: center; + height: 40px; + line-height: 40px; + } + + & h2 { + font-size: 2rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 7px; + } + + & p { + font-size: 1rem; + font-weight: 500; + text-wrap: balance; + } + + & .return { + align-items: center; + justify-content: center; + text-align: center; + color: hsl(var(--text-normal)); + } + } +} diff --git a/packages/studiocms_dashboard/src/styles/base.css b/packages/studiocms_dashboard/src/styles/base.css index 91c0da2a3..81e9f89a7 100644 --- a/packages/studiocms_dashboard/src/styles/base.css +++ b/packages/studiocms_dashboard/src/styles/base.css @@ -7,10 +7,16 @@ body { overflow-y: auto; overflow-x: hidden; scrollbar-width: thin; + display: flex; + align-items: center; + font-family: "Onest Variable", sans-serif; } main { min-height: 100vh; + width: 100%; + padding: 1.5rem; + position: relative; } :root, @@ -27,3 +33,181 @@ main { .scrollbar ::-webkit-scrollbar { width: 2px; } + +.sidebar-header { + display: flex; + flex-direction: row; + gap: 1rem; + align-items: center; +} + +.sidebar-logo { + width: 2.75rem; + height: auto; +} + +.sidebar-title { + font-size: 1.75em; + font-weight: 700; +} + +.nav-toggle { + position: absolute !important; + top: 1rem !important; + left: 1rem !important; + display: none !important; + z-index: 40 !important; +} + +@media screen and (max-width: 840px) { + main { + padding-top: 4rem; + } + .nav-toggle { + display: flex !important; + } +} + +.sidebar-header-text { + display: flex; + flex-direction: column; +} + +.sidebar-link-group { + display: flex; + flex-direction: column; + gap: .375rem; + width: 100%; +} + +.sidebar-link-group.hidden { + display: none; +} + +.empty-placeholder-span { + width: 100%; + text-align: center; + color: hsl(var(--text-muted)); + font-size: .875em; +} + +.user-dropdown-trigger-container { + width: calc(280px - 3rem); + cursor: pointer; + border: 1px solid hsl(var(--border)); + padding: .5rem; + border-radius: .5rem; + transition: all .15s ease; +} + +.user-dropdown-trigger-container:hover { + background-color: hsla(var(--border), 0.5); +} + +.sidebar { + justify-content: space-between; +} + +.sidebar-links-container { + gap: .75rem; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +.outer-sidebar-container { + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.sui-modal.lg { + width: 650px !important; +} + +.dashboard-footer { + display: flex; + flex-direction: column; + align-items: center; + gap: .25rem; + + color: hsl(var(--text-muted)); + + font-size: .875em; + + & strong { + font-weight: 700; + } + + & a { + color: hsl(var(--text-muted)); + text-decoration: none; + } +} + +.dashboard-footer a:hover { + color: hsl(var(--primary-hover)); + text-decoration: underline; +} + +.container { + display: flex; + flex-direction: column; + height: 100%; + min-height: 90vh; + + & .page-header { + margin-bottom: 1.5rem; + } +} + +.dashboard-grid-container { + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-auto-flow: dense; + gap: 1rem; + width: 100%; + padding: 1rem; + + & .grid-item { + padding: 1rem; + } + + & .span-2 { + grid-column: span 2; + } + + & .span-3 { + grid-column: span 3; + } +} + +@media (max-width: 1200px) { + .dashboard-grid-container { + grid-template-columns: repeat(2, 1fr); + + & .span-3 { + grid-column: span 2; + } + + & .span-2 { + grid-column: span 2; + } + } +} + +@media (max-width: 768px) { + .dashboard-grid-container { + grid-template-columns: 1fr; + + & .span-3 { + grid-column: span 1; + } + + & .span-2 { + grid-column: span 1; + } + } +} diff --git a/packages/studiocms_dashboard/src/uno.config.ts b/packages/studiocms_dashboard/src/uno.config.ts deleted file mode 100644 index 79109af8a..000000000 --- a/packages/studiocms_dashboard/src/uno.config.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { presetDaisy } from '@matthiesenxyz/unocss-preset-daisyui'; -import { - defineConfig, - presetTypography, - presetUno, - presetWebFonts, - presetWind, - transformerDirectives, -} from 'unocss'; -import type { DarkModeSelectors } from 'unocss/preset-mini'; - -const darkModeSelector: DarkModeSelectors = { - dark: '[data-theme="dark"]', -}; - -export default defineConfig({ - transformers: [transformerDirectives()], - presets: [ - presetUno({ - dark: darkModeSelector, - }), - presetWind({ - dark: darkModeSelector, - }), - presetDaisy({ - themes: ['light', 'dark'], - darkTheme: 'dark', - }), - presetTypography(), - presetWebFonts({ - provider: 'google', - fonts: { - // Required Fonts for Google Icons - sans: 'Roboto', - mono: ['Fira Code', 'Fira Mono:400,700'], - }, - }), - ], -}); diff --git a/packages/studiocms_dashboard/src/utils/addAPIRoutes.ts b/packages/studiocms_dashboard/src/utils/addAPIRoutes.ts new file mode 100644 index 000000000..29ad574f3 --- /dev/null +++ b/packages/studiocms_dashboard/src/utils/addAPIRoutes.ts @@ -0,0 +1,50 @@ +import { integrationLogger } from '@matthiesenxyz/integration-utils/astroUtils'; +import { makeAPIRoute } from '@studiocms/core/lib'; +import { defineUtility } from 'astro-integration-kit'; +import type { StudioCMSDashboardOptions } from '../schema'; + +const apiRoute = makeAPIRoute('dashboard'); + +export const injectDashboardAPIRoutes = defineUtility('astro:config:setup')( + ( + params, + opts: { + options: StudioCMSDashboardOptions; + routes: { + enabled: boolean; + pattern: string; + entrypoint: string; + }[]; + } + ) => { + const { injectRoute, logger } = params; + + const { + options: { + verbose, + dashboardConfig: { dashboardEnabled }, + }, + routes, + } = opts; + + // Check if the Dashboard is enabled + if (dashboardEnabled) { + // Log that the Dashboard is enabled + integrationLogger({ logger, logLevel: 'info', verbose }, 'Injecting Up API Routes...'); + // Inject the API routes + for (const route of routes) { + const { enabled, pattern, entrypoint } = route; + + if (!enabled) { + continue; + } + + injectRoute({ + pattern: apiRoute(pattern), + entrypoint, + prerender: false, + }); + } + } + } +); diff --git a/packages/studiocms_dashboard/src/utils/astroDb.ts b/packages/studiocms_dashboard/src/utils/astroDb.ts index b03d9313a..bf8152e62 100644 --- a/packages/studiocms_dashboard/src/utils/astroDb.ts +++ b/packages/studiocms_dashboard/src/utils/astroDb.ts @@ -4,72 +4,45 @@ import { db, eq } from 'astro:db'; import { CMSSiteConfigId } from '@studiocms/core/consts'; import { tsPageContent, tsPageData, tsSiteConfig } from '@studiocms/core/db/tsTables'; -type PageDataType = { - id: string; - package: string; - title: string; - description: string; - showOnNav: boolean; - publishedAt: Date; - updatedAt: Date | null; - slug: string; - contentLang: string; - heroImage: string; -}; - -type PageDataInsert = { - title: string; - description: string; - slug: string; - package: string; - showOnNav: boolean; - publishedAt: Date; - contentLang: string; - heroImage: string; -}; - -type PageDataUpdate = { - id: string; - package: string; - title: string; - description: string; - showOnNav: boolean; - updatedAt: Date | null; - slug: string; - heroImage: string; -}; - -type PageDataReturnID = { - id: string; -}; - -type PageContentInsert = { - id: string; - lang: string; - content: string; -}; - -type PageContentUpdate = { - content: string; - id: string; -}; +/** + * @deprecated moved into StudioCMS SDK + */ +type PageDataInsert = typeof tsPageData.$inferInsert; +/** + * @deprecated moved into StudioCMS SDK + */ +type PageDataSelect = typeof tsPageData.$inferSelect; +/** + * @deprecated moved into StudioCMS SDK + */ +type PageDataReturnID = Pick; -type SiteConfigUpdate = { - title: string; - description: string; -}; +/** + * @deprecated moved into StudioCMS SDK + */ +type PageContentInsert = typeof tsPageContent.$inferInsert; +/** + * @deprecated moved into StudioCMS SDK + */ +type PageContentSelect = typeof tsPageContent.$inferSelect; -type SiteConfigReturn = { - id: number; - title: string; - description: string; -}; +/** + * @deprecated moved into StudioCMS SDK + */ +type SiteConfigInsert = typeof tsSiteConfig.$inferInsert; +/** + * @deprecated moved into StudioCMS SDK + */ +type SiteConfigSelect = typeof tsSiteConfig.$inferSelect; +/** + * @deprecated moved into StudioCMS SDK + */ export const astroDb = () => { return { pageData() { return { - async getBySlug(slug: string, pkg: string): Promise { + async getBySlug(slug: string, pkg: string): Promise { const pageData = await db .select() .from(tsPageData) @@ -91,12 +64,15 @@ export const astroDb = () => { id: randomUUID(), slug: data.slug, title: data.title, - package: data.package, + package: data.package || 'studiocms', description: data.description, contentLang: contentLang, - heroImage: data.heroImage, - publishedAt: data.publishedAt, - showOnNav: data.showOnNav, + heroImage: data.heroImage || '', + publishedAt: data.publishedAt || new Date(), + showOnNav: data.showOnNav || false, + catagories: data.catagories || [], + tags: data.tags || [], + updatedAt: new Date(), }) .returning({ id: tsPageData.id }) .catch((error) => { @@ -106,7 +82,7 @@ export const astroDb = () => { return newEntry.pop(); }, - async update(data: PageDataUpdate) { + async update(data: PageDataSelect) { await db .update(tsPageData) .set({ @@ -135,15 +111,15 @@ export const astroDb = () => { .insert(tsPageContent) .values({ id: randomUUID(), - contentId: data.id, - contentLang: data.lang, - content: data.content, + contentId: data.contentId, + contentLang: data.contentLang || 'default', + content: data.content || '', }) .catch((error) => { logger.error(error); }); }, - async update(data: PageContentUpdate) { + async update(data: PageContentSelect) { await db .update(tsPageContent) .set({ content: data.content }) @@ -156,7 +132,7 @@ export const astroDb = () => { }, siteConfig() { return { - async update(data: SiteConfigUpdate): Promise { + async update(data: SiteConfigInsert): Promise { return await db .update(tsSiteConfig) .set(data) diff --git a/packages/studiocms_dashboard/src/utils/checkForWebVitalsPlugin.ts b/packages/studiocms_dashboard/src/utils/checkForWebVitalsPlugin.ts index ccd4090fe..7ad0bb95a 100644 --- a/packages/studiocms_dashboard/src/utils/checkForWebVitalsPlugin.ts +++ b/packages/studiocms_dashboard/src/utils/checkForWebVitalsPlugin.ts @@ -1,6 +1,4 @@ -import DTSBuilder from '@matthiesenxyz/astrodtsbuilder'; import { integrationLogger } from '@matthiesenxyz/integration-utils/astroUtils'; -import { webVitalStrings } from '@studiocms/core/strings'; import { addVirtualImports, createResolver, @@ -20,7 +18,7 @@ export const checkForWebVitals = defineUtility('astro:config:setup')( ) => { integrationLogger( { logger: params.logger, logLevel: 'info', verbose: opts.verbose }, - webVitalStrings.checkForWebVitals + "Checking for '@astrojs/web-vitals' integration..." ); // Check for Web Vitals @@ -28,13 +26,13 @@ export const checkForWebVitals = defineUtility('astro:config:setup')( // Log that the Web Vitals Integration is Present integrationLogger( { logger: params.logger, logLevel: 'info', verbose: opts.verbose }, - webVitalStrings.webVitalsFound + 'Web Vitals Integration Found!' ); } else { // Log that the Web Vitals Integration is Missing integrationLogger( - { logger: params.logger, logLevel: 'warn', verbose: opts.verbose }, - webVitalStrings.webVitalsMissing + { logger: params.logger, logLevel: 'info', verbose: opts.verbose }, + 'Web Vitals integration not found. If you wish to use Web Vitals, please install the `@astrojs/web-vitals` package.' ); } @@ -45,36 +43,5 @@ export const checkForWebVitals = defineUtility('astro:config:setup')( 'studiocms-dashboard:web-vitals': `export * from "${resolve('./webVital.ts')}"`, }, }); - - const dtsFile = DTSBuilder(); - - dtsFile.addSingleLineNote( - 'This file is generated by StudioCMS and should not be modified manually.' - ); - - dtsFile.addModule('studiocms-dashboard:web-vitals', { - namedExports: [ - { - name: 'getWebVitals', - multiLineDescription: [ - '# Web Vitals Helper Function', - '', - '@returns Promise', - ], - typeDef: `typeof import('${resolve('./webVital.ts')}').getWebVitals`, - }, - ], - typeExports: [ - { - singleLineDescription: 'Web Vitals Response Item', - typeDef: `import('${resolve('./webVital.ts')}').WebVitalsResponseItem`, - name: 'WebVitalsResponseItem', - }, - ], - }); - - const webVitalDtsFile = dtsFile.makeAstroInjectedType('web-vitals.d.ts'); - - return { webVitalDtsFile }; } ); diff --git a/packages/studiocms_dashboard/src/utils/injectRouteArray.ts b/packages/studiocms_dashboard/src/utils/injectRouteArray.ts index 82c4bbdd3..817abb0be 100644 --- a/packages/studiocms_dashboard/src/utils/injectRouteArray.ts +++ b/packages/studiocms_dashboard/src/utils/injectRouteArray.ts @@ -1,3 +1,4 @@ +import { integrationLogger } from '@matthiesenxyz/integration-utils/astroUtils'; import { removeLeadingTrailingSlashes } from '@studiocms/core/lib'; import { defineUtility } from 'astro-integration-kit'; import type { StudioCMSDashboardOptions } from '../schema'; @@ -51,3 +52,61 @@ export const injectRouteArray = defineUtility('astro:config:setup')( } } ); + +export const injectDashboardRoute = defineUtility('astro:config:setup')( + ( + params, + opts: { + options: StudioCMSDashboardOptions; + routes: { + enabled: boolean; + pattern: string; + entrypoint: string; + prerender?: boolean; + }[]; + }, + prerenderRoutes: boolean + ) => { + const { injectRoute, logger } = params; + + const { + options: { + verbose, + dashboardConfig: { dashboardRouteOverride, dashboardEnabled }, + }, + routes, + } = opts; + + // Check if the Dashboard is enabled + if (dashboardEnabled) { + // Log that the Dashboard is enabled + integrationLogger({ logger, logLevel: 'info', verbose }, 'Injecting Page Routes...'); + + const defaultDashboardRoute = dashboardRouteOverride + ? removeLeadingTrailingSlashes(dashboardRouteOverride) + : 'dashboard'; + + const makeDashboardRoute = (path: string) => { + return `${defaultDashboardRoute}/${path}`; + }; + + for (const route of routes) { + const { enabled, pattern, entrypoint, prerender = prerenderRoutes } = route; + + if (enabled) { + injectRoute({ + pattern: makeDashboardRoute(pattern), + entrypoint, + prerender, + }); + } + } + } else { + // Log that the Dashboard is disabled + integrationLogger( + { logger, logLevel: 'info', verbose }, + 'Dashboard is Disabled, Some tools and Utilities are still available for developers who are customizing their setup!' + ); + } + } +); diff --git a/packages/studiocms_dashboard/src/utils/isDashboardRoute.ts b/packages/studiocms_dashboard/src/utils/isDashboardRoute.ts index 2f454ebdd..c74368791 100644 --- a/packages/studiocms_dashboard/src/utils/isDashboardRoute.ts +++ b/packages/studiocms_dashboard/src/utils/isDashboardRoute.ts @@ -1,9 +1,7 @@ -import Config from 'virtual:studiocms/config'; -import { removeLeadingTrailingSlashes } from '@studiocms/core/lib'; +import { dashboardConfig } from 'studiocms:config'; +import { removeLeadingTrailingSlashes } from 'studiocms:lib'; -const { - dashboardConfig: { dashboardRouteOverride }, -} = Config; +const { dashboardRouteOverride } = dashboardConfig; // Get the User set or Default dashboard URL const dashboardURL = dashboardRouteOverride diff --git a/packages/studiocms_dashboard/src/utils/webVital.ts b/packages/studiocms_dashboard/src/utils/webVital.ts index 32b8680c3..30ecf6154 100644 --- a/packages/studiocms_dashboard/src/utils/webVital.ts +++ b/packages/studiocms_dashboard/src/utils/webVital.ts @@ -39,7 +39,7 @@ export async function getWebVitals(): Promise { } } } catch (error) { - logger.error( + logger.warn( `Error getting @astrojs/web-vitals table data. If you have not installed the package you can disregard this error.\n - ${error}` ); return [] as WebVitalsResponseItem[]; diff --git a/packages/studiocms_dashboard/tsconfig.json b/packages/studiocms_dashboard/tsconfig.json index 0d73211ac..d175d082a 100644 --- a/packages/studiocms_dashboard/tsconfig.json +++ b/packages/studiocms_dashboard/tsconfig.json @@ -2,48 +2,10 @@ "extends": "astro/tsconfigs/strictest", "files": [], "compilerOptions": { - "outDir": "../../.moon/cache/types/packages/studiocms_dashboard", "composite": true, "noEmit": false, "allowImportingTsExtensions": false, - "emitDeclarationOnly": false, - "paths": { - "node-playground/*": ["../../playgrounds/node/src/*"], - "@studiocms/assets": ["../studiocms_assets/src/index.ts"], - "@studiocms/assets/*": ["../studiocms_assets/src/*"], - "@studiocms/betaresources": ["../studiocms_betaresources/src/index.ts"], - "@studiocms/betaresources/*": ["../studiocms_betaresources/src/*"], - "@studiocms/core": ["../studiocms_core/src/index.ts"], - "@studiocms/core/*": ["../studiocms_core/src/*"], - "@studiocms/renderers": ["../studiocms_renderers/src/index.ts"], - "@studiocms/renderers/*": ["../studiocms_renderers/src/*"] - } + "emitDeclarationOnly": false }, - "references": [ - { - "path": "../../playgrounds/node" - }, - { - "path": "../studiocms_assets" - }, - { - "path": "../studiocms_betaresources" - }, - { - "path": "../studiocms_core" - }, - { - "path": "../studiocms_renderers" - } - ], - "include": [ - "./package.json", - "../../playgrounds/node/.astro/**/*", - "../studiocms_assets/**/*", - "../studiocms_betaresources/**/*", - "../studiocms_core/**/*", - "../studiocms_renderers/**/*", - "./**/*", - "./src/**/*.json" - ] + "include": ["./package.json", "./**/*", "./src/**/*.json"] } diff --git a/packages/studiocms_dashboard/virtuals.d.ts b/packages/studiocms_dashboard/virtuals.d.ts index 254409467..c234c77da 100644 --- a/packages/studiocms_dashboard/virtuals.d.ts +++ b/packages/studiocms_dashboard/virtuals.d.ts @@ -1,105 +1 @@ -/// - -/** - * # DEV TIP - * - * Wanting to extend StudioCMS? You can do so by defining a new module in the `virtual:studiocms` namespace within your project with the following format: - * - * This module can also be delcared from `@astrolicious/studiocms`. - * - * @example - * declare module 'virtual:studiocms/config' { - * const Config: import('@astrolicious/studiocms').StudioCMSOptions; - * export default Config; - * } - */ -declare module 'virtual:studiocms/config' { - const Config: import('@studiocms/core/schemas').StudioCMSOptions; - export default Config; -} - -declare module 'virtual:studiocms/astromdremarkConfig' { - const markdownConfig: import('astro').AstroConfig['markdown']; - export default markdownConfig; -} - -declare module 'virtual:studiocms/version' { - const Version: string; - export default Version; -} - -declare module 'virtual:studiocms/pluginSystem' { - export const externalNav: typeof import('@studiocms/core/lib').externalNavigation; - export const dashboardPageLinks: typeof import('@studiocms/core/lib').dashboardPageLinksMap; - export const pluginList: typeof import('@studiocms/core/lib').studioCMSPluginList; - export const customRenderers: string[]; -} - -interface Window { - theme: { - setTheme: (theme: 'auto' | 'dark' | 'light') => void; - getTheme: () => 'auto' | 'dark' | 'light'; - getSystemTheme: () => 'light' | 'dark'; - getDefaultTheme: () => 'auto' | 'dark' | 'light'; - }; -} - -declare module 'studiocms:components' { - export const Avatar: typeof import('@studiocms/core/components').Avatar; - export const FormattedDate: typeof import('@studiocms/core/components').FormattedDate; - export const Genericheader: typeof import('@studiocms/core/components').GenericHeader; - export const Navigation: typeof import('@studiocms/core/components').Navigation; -} - -declare module 'studiocms:helpers' { - export const authHelper: typeof import('@studiocms/core/helpers').authHelper; - export const urlGenFactory: typeof import('@studiocms/core/helpers').urlGenFactory; - export const pathWithBase: typeof import('@studiocms/core/helpers').pathWithBase; - export const fileWithBase: typeof import('@studiocms/core/helpers').fileWithBase; -} - -declare module 'studiocms:helpers/contentHelper' { - export const contentHelper: typeof import('@studiocms/core/helpers').contentHelper; - export const getSiteConfig: typeof import('@studiocms/core/helpers').getSiteConfig; - export const getPageById: typeof import('@studiocms/core/helpers').getPageById; - export const getPageList: typeof import('@studiocms/core/helpers').getPageList; - export const getUserList: typeof import('@studiocms/core/helpers').getUserList; - export const getUserById: typeof import('@studiocms/core/helpers').getUserById; - export const getPermissionsList: typeof import('@studiocms/core/helpers').getPermissionsList; - export type ContentHelperTempResponse = import( - '@studiocms/core/helpers' - ).ContentHelperTempResponse; - export type SiteConfigResponse = import('@studiocms/core/helpers').SiteConfigResponse; - export type pageDataReponse = import('@studiocms/core/helpers').pageDataReponse; - export type UserResponse = import('@studiocms/core/helpers').UserResponse; -} - -declare module 'studiocms:helpers/headDefaults' { - export const headDefaults: typeof import('@studiocms/core/helpers').headDefaults; -} - -declare module 'studiocms:helpers/routemap' { - export const getSluggedRoute: typeof import('@studiocms/core/helpers').getSluggedRoute; - export const getEditRoute: typeof import('@studiocms/core/helpers').getEditRoute; - export const getDeleteRoute: typeof import('@studiocms/core/helpers').getDeleteRoute; - export const makeNonDashboardRoute: typeof import( - '@studiocms/core/helpers' - ).makeNonDashboardRoute; - export const makeDashboardRoute: typeof import('@studiocms/core/helpers').makeDashboardRoute; - export const makeAPIDashboardRoute: typeof import( - '@studiocms/core/helpers' - ).makeAPIDashboardRoute; - export const StudioCMSRoutes: typeof import('@studiocms/core/helpers').StudioCMSRoutes; - export const sideBarLinkMap: typeof import('@studiocms/core/helpers').sideBarLinkMap; -} - -declare module 'studiocms:renderer' { - export const StudioCMSRenderer: typeof import( - '@studiocms/renderers/components' - ).StudioCMSRenderer; -} - -declare module 'studiocms-dashboard:web-vitals' { - export type WebVitalsResponseItem = import('./src/utils/webVital').WebVitalsResponseItem; - export const getWebVitals: typeof import('./src/utils/webVital').getWebVitals; -} +/// diff --git a/packages/studiocms_devapps/LICENSE b/packages/studiocms_devapps/LICENSE index a30d1fbe4..94787104c 100644 --- a/packages/studiocms_devapps/LICENSE +++ b/packages/studiocms_devapps/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Astrolicious - StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares +Copyright (c) 2024 StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/studiocms_devapps/env.d.ts b/packages/studiocms_devapps/env.d.ts index 881617f2d..44c97a4a9 100644 --- a/packages/studiocms_devapps/env.d.ts +++ b/packages/studiocms_devapps/env.d.ts @@ -1,3 +1,6 @@ +/// +/// + interface ImportMetaEnv { readonly PROD: boolean; readonly BASE_URL: string; diff --git a/packages/studiocms_devapps/moon.yml b/packages/studiocms_devapps/moon.yml deleted file mode 100644 index 91420a6d9..000000000 --- a/packages/studiocms_devapps/moon.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: 'typescript' -tags: ['astro', 'typescript'] - -project: - name: 'studiocms_devapps' - description: 'A CMS that bridges Astro with Astro DB & Studio' - maintainers: ['Adammatthiesen','jdtjenkins','dreyfus92'] \ No newline at end of file diff --git a/packages/studiocms_devapps/package.json b/packages/studiocms_devapps/package.json index 19420a1fb..6cefd6e53 100644 --- a/packages/studiocms_devapps/package.json +++ b/packages/studiocms_devapps/package.json @@ -49,10 +49,10 @@ }, "peerDependencies": { "@astrojs/db": "catalog:min", - "astro": "catalog:min" + "astro": "catalog:min", + "vite": "catalog:min" }, "devDependencies": { - "vite": "catalog:", "typescript": "catalog:" } } diff --git a/packages/studiocms_devapps/src/integration.ts b/packages/studiocms_devapps/src/integration.ts index 7288ca9b7..a6b789e14 100644 --- a/packages/studiocms_devapps/src/integration.ts +++ b/packages/studiocms_devapps/src/integration.ts @@ -29,7 +29,7 @@ export default defineIntegration({ return { hooks: { - 'astro:config:setup': async (params) => { + 'astro:config:setup': (params) => { // Destructure Params const { logger, addDevToolbarApp, command, config } = params; @@ -64,7 +64,7 @@ export default defineIntegration({ if (libSQLViewer.enabled) { verbose && logger.info('Adding Dev Toolbar App: libSQLViewer'); injectDevRoute(params, { - entrypoint: resolve('routes/libsql-viewer.astro'), + entrypoint: resolve('./routes/libsql-viewer.astro'), pattern: EndpointPath(libSQLViewer.endpoint), }); addDevToolbarApp({ @@ -79,7 +79,7 @@ export default defineIntegration({ if (wpApiImporter.enabled) { verbose && logger.info('Adding Dev Toolbar App: WP-API Importer'); injectDevRoute(params, { - entrypoint: resolve('routes/wp-api-importer.ts'), + entrypoint: resolve('./routes/wp-api-importer.ts'), pattern: WP_API_URL, }); addDevToolbarApp({ diff --git a/packages/studiocms_devapps/src/utils/pathGenerator.ts b/packages/studiocms_devapps/src/utils/pathGenerator.ts index 4019970b4..a07eb648a 100644 --- a/packages/studiocms_devapps/src/utils/pathGenerator.ts +++ b/packages/studiocms_devapps/src/utils/pathGenerator.ts @@ -2,30 +2,30 @@ const base = stripTrailingSlash(import.meta.env.BASE_URL); /** Get the a root-relative URL path with the site’s `base` prefixed. */ export function pathWithBase(path: string) { - let newpath = path; - newpath = stripLeadingSlash(newpath); - return newpath ? `${base}/${newpath}` : `${base}/`; + let newPath = path; + newPath = stripLeadingSlash(newPath); + return newPath ? `${base}/${newPath}` : `${base}/`; } /** Ensure the passed path does not start with a leading slash. */ export function stripLeadingSlash(href: string) { - let newhref = href; - if (newhref[0] === '/') newhref = newhref.slice(1); - return newhref; + let newHref = href; + if (newHref[0] === '/') newHref = newHref.slice(1); + return newHref; } /** Ensure the passed path does not end with a trailing slash. */ export function stripTrailingSlash(href: string) { - let newhref = href; - if (newhref[newhref.length - 1] === '/') newhref = newhref.slice(0, -1); - return newhref; + let newHref = href; + if (newHref[newHref.length - 1] === '/') newHref = newHref.slice(0, -1); + return newHref; } /** Ensure the passed path starts with a leading slash. */ export function ensureLeadingSlash(href: string): string { - let newhref = href; - if (newhref[0] !== '/') newhref = `/${newhref}`; - return newhref; + let newHref = href; + if (newHref[0] !== '/') newHref = `/${newHref}`; + return newHref; } /** Endpoint path generator */ diff --git a/packages/studiocms_devapps/src/utils/wp-api/converters.ts b/packages/studiocms_devapps/src/utils/wp-api/converters.ts index 13b43ca47..2f2bbda80 100644 --- a/packages/studiocms_devapps/src/utils/wp-api/converters.ts +++ b/packages/studiocms_devapps/src/utils/wp-api/converters.ts @@ -78,48 +78,48 @@ export const ConvertToPageContent = async ( return pageContent; }; -export const generateCatagories = async (categories: number[], endpoint: string) => { - const newCatagories: Category[] = []; +export const generateCategories = async (categories: number[], endpoint: string) => { + const newCategories: Category[] = []; - for (const catagoryId of categories) { - // Check if catagory already exists in the database - const catagoryExists = await db + for (const categoryId of categories) { + // Check if category already exists in the database + const categoryExists = await db .select() .from(tsPageDataCategories) - .where(eq(tsPageDataCategories.id, catagoryId)) + .where(eq(tsPageDataCategories.id, categoryId)) .get(); - if (catagoryExists) { - console.log(`Catagory with id ${catagoryId} already exists in the database`); + if (categoryExists) { + console.log(`Category with id ${categoryId} already exists in the database`); continue; } - const catagoryURL = apiEndpoint(endpoint, 'catagories', `${catagoryId}`); - const response = await fetch(catagoryURL); + const categoryURL = apiEndpoint(endpoint, 'categories', `${categoryId}`); + const response = await fetch(categoryURL); const json = await response.json(); - newCatagories.push(json); + newCategories.push(json); } - if (newCatagories.length > 0) { - const catagoryData = newCatagories.map((catagory) => { + if (newCategories.length > 0) { + const categoryData = newCategories.map((category) => { const data: typeof tsPageDataCategories.$inferInsert = { - id: catagory.id, - name: catagory.name, - slug: catagory.slug, - description: catagory.description, - meta: JSON.stringify(catagory.meta), + id: category.id, + name: category.name, + slug: category.slug, + description: category.description, + meta: JSON.stringify(category.meta), }; - if (catagory.parent) { - data.parent = catagory.parent; + if (category.parent) { + data.parent = category.parent; } return data; }); - for (const catagory of catagoryData) { - console.log(`Inserting catagory with id ${catagory.id} into the database`); - await db.insert(tsPageDataCategories).values(catagory); + for (const category of categoryData) { + console.log(`Inserting category with id ${category.id} into the database`); + await db.insert(tsPageDataCategories).values(category); } } }; @@ -181,7 +181,7 @@ export const ConvertToPostData = async ( const pkg = useBlogPkg ? '@studiocms/blog' : 'studiocms'; - await generateCatagories(data.categories, endpoint); + await generateCategories(data.categories, endpoint); await generateTags(data.tags, endpoint); const pageData: PageData = { @@ -194,7 +194,7 @@ export const ConvertToPostData = async ( showOnNav: false, contentLang: 'default', package: pkg, - catagories: JSON.stringify(data.categories), + categories: JSON.stringify(data.categories), tags: JSON.stringify(data.tags), }; diff --git a/packages/studiocms_devapps/src/utils/wp-api/index.ts b/packages/studiocms_devapps/src/utils/wp-api/index.ts index 68166bf8a..584123140 100644 --- a/packages/studiocms_devapps/src/utils/wp-api/index.ts +++ b/packages/studiocms_devapps/src/utils/wp-api/index.ts @@ -1,5 +1,4 @@ import path from 'node:path'; -/// import { db, eq } from 'astro:db'; import Config from 'virtual:studiocms-devapps/wp-api/configPath'; import { CMSSiteConfigId } from '@studiocms/core/consts'; diff --git a/packages/studiocms_devapps/src/utils/wp-api/utils.ts b/packages/studiocms_devapps/src/utils/wp-api/utils.ts index e09486476..7bb390a04 100644 --- a/packages/studiocms_devapps/src/utils/wp-api/utils.ts +++ b/packages/studiocms_devapps/src/utils/wp-api/utils.ts @@ -108,7 +108,7 @@ export const downloadAndUpdateImages = async (html: string, pathToFolder: string export const apiEndpoint = ( endpoint: string, - type: 'posts' | 'pages' | 'media' | 'catagories' | 'tags' | 'settings', + type: 'posts' | 'pages' | 'media' | 'categories' | 'tags' | 'settings', path?: string ) => { if (!endpoint) { diff --git a/packages/studiocms_devapps/tsconfig.json b/packages/studiocms_devapps/tsconfig.json index b8c89347f..bdc2dd11c 100644 --- a/packages/studiocms_devapps/tsconfig.json +++ b/packages/studiocms_devapps/tsconfig.json @@ -2,7 +2,6 @@ "extends": "astro/tsconfigs/strictest", "files": [], "compilerOptions": { - "outDir": "../../.moon/cache/types/packages/studiocms", "composite": true, "noEmit": false, "allowImportingTsExtensions": false, diff --git a/packages/studiocms_frontend/LICENSE b/packages/studiocms_frontend/LICENSE index a30d1fbe4..94787104c 100644 --- a/packages/studiocms_frontend/LICENSE +++ b/packages/studiocms_frontend/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Astrolicious - StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares +Copyright (c) 2024 StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/studiocms_frontend/moon.yml b/packages/studiocms_frontend/moon.yml deleted file mode 100644 index ece72be08..000000000 --- a/packages/studiocms_frontend/moon.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: 'typescript' -tags: ['astro', 'typescript'] - -project: - name: '@studiocms/frontend' - description: 'A CMS that bridges Astro with Astro DB & Studio' - maintainers: ['Adammatthiesen','jdtjenkins','dreyfus92'] \ No newline at end of file diff --git a/packages/studiocms_frontend/package.json b/packages/studiocms_frontend/package.json index 82028e0b4..b20b8eac5 100644 --- a/packages/studiocms_frontend/package.json +++ b/packages/studiocms_frontend/package.json @@ -37,14 +37,14 @@ "dependencies": { "@matthiesenxyz/integration-utils": "catalog:studiocms-shared", "@studiocms/core": "workspace:*", - "@studiocms/renderers": "workspace:*", + "@studiocms/ui": "catalog:", "astro-integration-kit": "catalog:" }, "peerDependencies": { - "astro": "catalog:min" + "astro": "catalog:min", + "vite": "catalog:min" }, "devDependencies": { - "vite": "catalog:", "typescript": "catalog:" } } diff --git a/packages/studiocms_frontend/src/components/BaseHead.astro b/packages/studiocms_frontend/src/components/BaseHead.astro index 3b3fc5508..c3d1ee94c 100644 --- a/packages/studiocms_frontend/src/components/BaseHead.astro +++ b/packages/studiocms_frontend/src/components/BaseHead.astro @@ -1,17 +1,30 @@ --- -import { fileWithBase } from 'studiocms:helpers'; -import { headDefaults } from 'studiocms:helpers/headDefaults'; -import Config from 'virtual:studiocms/config'; -import { type HeadConfigSchema, createHead } from '@studiocms/core/lib'; +import { defaultFrontEndConfig } from 'studiocms:config'; +import { + type HeadConfig, + type HeadConfigSchema, + type HeadUserConfig, + createHead, + // fileWithBase, + headDefaults, +} from 'studiocms:lib'; import type { z } from 'astro/zod'; // // Import the global.css file here so that it is included on // // all pages through the use of the component. import './global.css'; -const { - defaultFrontEndConfig: { htmlDefaultHead, favicon }, -} = Config; +let htmlDefaultHead: HeadUserConfig = []; +let favicon = ''; + +if (defaultFrontEndConfig && typeof defaultFrontEndConfig === 'object') { + if (defaultFrontEndConfig.htmlDefaultHead) { + htmlDefaultHead.push(...defaultFrontEndConfig.htmlDefaultHead); + } + if (defaultFrontEndConfig.favicon) { + favicon = defaultFrontEndConfig.favicon; + } +} interface Props { title: string; @@ -20,7 +33,7 @@ interface Props { image?: string | undefined; } -const canonicalURL = Astro.site ? new URL(Astro.url.pathname, Astro.site) : undefined; +const canonicalURL = Astro.url; const { title, @@ -47,17 +60,17 @@ const StudioCMSFrontEndHeads: z.input> = [ makeHeader.push(...StudioCMSFrontEndHeads); // Link to sitemap, but only when `site` is set. -if (Astro.site) { - makeHeader.push({ - tag: 'link', - attrs: { - rel: 'sitemap', - href: fileWithBase('/sitemap-index.xml'), - }, - }); -} +// if (Astro.site) { +// makeHeader.push({ +// tag: 'link', +// attrs: { +// rel: 'sitemap', +// href: fileWithBase('/sitemap-index.xml'), +// }, +// }); +// } -const head = createHead(makeHeader, htmlDefaultHead); +const head = createHead(makeHeader, htmlDefaultHead as HeadConfig); --- {head.map(({ tag: Tag, attrs, content }) => )} \ No newline at end of file diff --git a/packages/studiocms_frontend/src/components/Layout.astro b/packages/studiocms_frontend/src/components/Layout.astro index a534ea517..a457baccf 100644 --- a/packages/studiocms_frontend/src/components/Layout.astro +++ b/packages/studiocms_frontend/src/components/Layout.astro @@ -1,15 +1,23 @@ --- import { Navigation } from 'studiocms:components'; -import { getSiteConfig } from 'studiocms:helpers/contentHelper'; -import Config from 'virtual:studiocms/config'; +import { defaultFrontEndConfig } from 'studiocms:config'; +import studioCMS_SDK from 'studiocms:sdk'; import BaseHead from './BaseHead.astro'; import Footer from './Footer.astro'; -const { title: SiteTitle, description: SiteDescription } = await getSiteConfig(); +const { title: SiteTitle, description: SiteDescription } = + (await studioCMS_SDK.GET.database.config()) || { + title: 'StudioCMS - Database Unavailable', + description: 'StudioCMS - Database Unavailable', + }; -const { - defaultFrontEndConfig: { htmlDefaultLanguage }, -} = Config; +let htmlDefaultLanguage = 'en'; + +if (defaultFrontEndConfig) { + if (typeof defaultFrontEndConfig === 'object' && defaultFrontEndConfig.htmlDefaultLanguage) { + htmlDefaultLanguage = defaultFrontEndConfig.htmlDefaultLanguage; + } +} type Props = { title: string; diff --git a/packages/studiocms_frontend/src/hooks/configSetup.ts b/packages/studiocms_frontend/src/hooks/configSetup.ts new file mode 100644 index 000000000..23b8edafa --- /dev/null +++ b/packages/studiocms_frontend/src/hooks/configSetup.ts @@ -0,0 +1,66 @@ +import { integrationLogger } from '@matthiesenxyz/integration-utils/astroUtils'; +import { createResolver, defineUtility } from 'astro-integration-kit'; +import type { StudioCMSFrontEndOptions } from '../schema'; + +// Define log level +const logLevel = 'info'; + +// Create resolver relative to this file +const { resolve } = createResolver(import.meta.url); + +export const configSetup = defineUtility('astro:config:setup')( + ( + { logger, injectRoute }, + { verbose, dbStartPage, defaultFrontEndConfig }: StudioCMSFrontEndOptions + ) => { + // Log Setup + integrationLogger({ logger, logLevel, verbose }, 'Setting up Frontend...'); + + // Check if Database Start Page is enabled + let shouldInject = false; + + if (typeof defaultFrontEndConfig === 'boolean') { + shouldInject = defaultFrontEndConfig; + } else if (typeof defaultFrontEndConfig === 'object') { + shouldInject = defaultFrontEndConfig.injectDefaultFrontEndRoutes; + } + + switch (dbStartPage) { + case true: + integrationLogger( + { logger, logLevel, verbose }, + 'Database Start Page enabled, skipping Default Frontend Routes Injection... Please follow the Database Setup Guide to create your Frontend.' + ); + break; + case false: + integrationLogger( + { logger, logLevel, verbose }, + 'Database Start Page disabled, checking for Default Frontend Routes Injection...' + ); + + if (shouldInject) { + integrationLogger( + { logger, logLevel, verbose }, + 'Route Injection enabled, Injecting Default Frontend Routes...' + ); + + injectRoute({ + pattern: '/', + entrypoint: resolve('../routes/index.astro'), + prerender: false, + }); + + injectRoute({ + pattern: '[...slug]', + entrypoint: resolve('../routes/[...slug].astro'), + prerender: false, + }); + + integrationLogger({ logger, logLevel, verbose }, 'Frontend Routes Injected!'); + } + break; + } + } +); + +export default configSetup; diff --git a/packages/studiocms_frontend/src/index.ts b/packages/studiocms_frontend/src/index.ts index 9a0ae4890..321cfd799 100644 --- a/packages/studiocms_frontend/src/index.ts +++ b/packages/studiocms_frontend/src/index.ts @@ -1,8 +1,18 @@ -import integration from './integration'; +import type { AstroIntegration } from 'astro'; +import { name } from '../package.json'; +import configSetup from './hooks/configSetup'; +import type { StudioCMSFrontEndOptions } from './schema'; /** * StudioCMS Frontend Integration */ -const studioCMSFrontend = integration; +function studioCMSFrontend(options: StudioCMSFrontEndOptions): AstroIntegration { + return { + name, + hooks: { + 'astro:config:setup': (params) => configSetup(params, options), + }, + }; +} export default studioCMSFrontend; diff --git a/packages/studiocms_frontend/src/integration.ts b/packages/studiocms_frontend/src/integration.ts deleted file mode 100644 index e16baf677..000000000 --- a/packages/studiocms_frontend/src/integration.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { createResolver, defineIntegration } from 'astro-integration-kit'; -import { name } from '../package.json'; -import { StudioCMSFrontEndOptionsSchema } from './schema'; -import { makeFrontend } from './utils/makeFrontend'; - -/** - * StudioCMS Frontend Integration - */ -export default defineIntegration({ - name, - optionsSchema: StudioCMSFrontEndOptionsSchema, - setup({ options }) { - // Create resolver relative to this file - const { resolve } = createResolver(import.meta.url); - - return { - hooks: { - 'astro:config:setup': async (params) => { - // Create the default frontend Routes - makeFrontend(params, { - options, - default404Route: resolve('./routes/404.astro'), - routes: [ - { pattern: '/', entrypoint: resolve('./routes/index.astro') }, - { pattern: '[...slug]', entrypoint: resolve('./routes/[...slug].astro') }, - ], - }); - }, - }, - }; - }, -}); diff --git a/packages/studiocms_frontend/src/routes/404.astro b/packages/studiocms_frontend/src/routes/404.astro deleted file mode 100644 index c210aa67f..000000000 --- a/packages/studiocms_frontend/src/routes/404.astro +++ /dev/null @@ -1,33 +0,0 @@ ---- -import Layout from '../components/Layout.astro'; ---- - -
-

Error 404

-

Page not found

-

- Sorry, the page you are looking for does not exist. Please check the URL in the address bar and try again. -

-

- If you believe this is an error, please contact the site administrator. -

- -

- -

- -
- -
- - \ No newline at end of file diff --git a/packages/studiocms_frontend/src/routes/[...slug].astro b/packages/studiocms_frontend/src/routes/[...slug].astro index 888e19698..328034b24 100644 --- a/packages/studiocms_frontend/src/routes/[...slug].astro +++ b/packages/studiocms_frontend/src/routes/[...slug].astro @@ -1,6 +1,6 @@ --- -import { contentHelper } from 'studiocms:helpers/contentHelper'; import { StudioCMSRenderer } from 'studiocms:renderer'; +import studioCMS_SDK from 'studiocms:sdk'; import Layout from '../components/Layout.astro'; const { slug } = Astro.params; @@ -9,11 +9,19 @@ if (!slug) { return new Response(null, { status: 404 }); } -const { title, description, heroImage, content, id } = await contentHelper(slug); +const page = await studioCMS_SDK.GET.databaseEntry.pages.bySlug(slug, 'studiocms'); -if (!id) { +if (!page) { return new Response(null, { status: 404 }); } + +const { title, description, heroImage, defaultContent } = page; + +if (!defaultContent) { + return new Response(null, { status: 404 }); +} + +const content = defaultContent.content || ''; --- diff --git a/packages/studiocms_frontend/src/routes/index.astro b/packages/studiocms_frontend/src/routes/index.astro index 0659ecce9..9da9538ea 100644 --- a/packages/studiocms_frontend/src/routes/index.astro +++ b/packages/studiocms_frontend/src/routes/index.astro @@ -1,13 +1,21 @@ --- -import { contentHelper } from 'studiocms:helpers/contentHelper'; import { StudioCMSRenderer } from 'studiocms:renderer'; +import studioCMS_SDK from 'studiocms:sdk'; import Layout from '../components/Layout.astro'; -const { title, description, heroImage, content, id } = await contentHelper('index'); +const index = await studioCMS_SDK.GET.databaseEntry.pages.bySlug('index', 'studiocms'); -if (!id) { +if (!index) { return new Response(null, { status: 404 }); } + +const { title, description, heroImage, defaultContent } = index; + +if (!defaultContent) { + return new Response(null, { status: 404 }); +} + +const content = defaultContent.content || ''; --- diff --git a/packages/studiocms_frontend/src/utils/makeFrontend.ts b/packages/studiocms_frontend/src/utils/makeFrontend.ts deleted file mode 100644 index 9310e9fd6..000000000 --- a/packages/studiocms_frontend/src/utils/makeFrontend.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { integrationLogger } from '@matthiesenxyz/integration-utils/astroUtils'; -import { MakeFrontendStrings } from '@studiocms/core/strings'; -import { defineUtility } from 'astro-integration-kit'; -import type { StudioCMSDashboardOptions } from '../integration'; - -export const makeFrontend = defineUtility('astro:config:setup')( - ( - params, - options: { - options: StudioCMSDashboardOptions; - routes: { - pattern: string; - entrypoint: string; - }[]; - default404Route: string; - } - ) => { - // Destructure Params - const { injectRoute, logger } = params; - - // Destructure Options - const { - routes, - default404Route, - options: { - dbStartPage, - verbose, - defaultFrontEndConfig: { injectDefaultFrontEndRoutes, inject404Route }, - }, - } = options; - - // Check if DB Start Page is enabled - if (dbStartPage) { - integrationLogger( - { logger, logLevel: 'info', verbose }, - MakeFrontendStrings.DBStartPageEnabled - ); - return; - } - - integrationLogger({ logger, logLevel: 'info', verbose }, MakeFrontendStrings.NoDBStartPage); - - // Inject Default Frontend Routes if Enabled - if (injectDefaultFrontEndRoutes) { - integrationLogger( - { logger, logLevel: 'info', verbose }, - MakeFrontendStrings.InjectDefaultFrontendRoutes - ); - for (const route of routes) { - injectRoute({ - pattern: route.pattern, - entrypoint: route.entrypoint, - }); - } - - // Inject 404 Route - if (inject404Route) { - integrationLogger( - { logger, logLevel: 'info', verbose }, - MakeFrontendStrings.Inject404Route - ); - injectRoute({ - pattern: '404', - entrypoint: default404Route, - }); - } - integrationLogger( - { logger, logLevel: 'info', verbose }, - MakeFrontendStrings.DefaultRoutesInjected - ); - } - } -); diff --git a/packages/studiocms_frontend/tsconfig.json b/packages/studiocms_frontend/tsconfig.json index 124e2e005..d175d082a 100644 --- a/packages/studiocms_frontend/tsconfig.json +++ b/packages/studiocms_frontend/tsconfig.json @@ -2,37 +2,10 @@ "extends": "astro/tsconfigs/strictest", "files": [], "compilerOptions": { - "outDir": "../../.moon/cache/types/packages/studiocms_frontend", "composite": true, "noEmit": false, "allowImportingTsExtensions": false, - "emitDeclarationOnly": false, - "paths": { - "node-playground/*": ["../../playgrounds/node/src/*"], - "@studiocms/core": ["../studiocms_core/src/index.ts"], - "@studiocms/core/*": ["../studiocms_core/src/*"], - "@studiocms/renderers": ["../studiocms_renderers/src/index.ts"], - "@studiocms/renderers/*": ["../studiocms_renderers/src/*"] - } + "emitDeclarationOnly": false }, - "references": [ - { - "path": "../../playgrounds/node" - }, - { - "path": "../studiocms_core" - }, - { - "path": "../studiocms_renderers" - } - ], - "include": [ - "./package.json", - "../../playgrounds/node/**/*", - "../../playgrounds/node/.astro/**/*", - "../studiocms_core/**/*", - "../studiocms_renderers/**/*", - "./**/*", - "./src/**/*.json" - ] + "include": ["./package.json", "./**/*", "./src/**/*.json"] } diff --git a/packages/studiocms_frontend/virtuals.d.ts b/packages/studiocms_frontend/virtuals.d.ts index c6caba49b..c234c77da 100644 --- a/packages/studiocms_frontend/virtuals.d.ts +++ b/packages/studiocms_frontend/virtuals.d.ts @@ -1,71 +1 @@ -/** - * # DEV TIP - * - * Wanting to extend StudioCMS? You can do so by defining a new module in the `virtual:studiocms` namespace within your project with the following format: - * - * This module can also be delcared from `@studiocms/core`. - * - * @example - * declare module 'virtual:studiocms/config' { - * const Config: import('@studiocms/core').StudioCMSOptions; - * export default Config; - * } - */ -declare module 'virtual:studiocms/config' { - const Config: import('@studiocms/core/schemas').StudioCMSOptions; - export default Config; -} - -declare module 'virtual:studiocms/version' { - const Version: string; - export default Version; -} - -declare module 'studiocms:components' { - export const Avatar: typeof import('@studiocms/core/components').Avatar; - export const FormattedDate: typeof import('@studiocms/core/components').FormattedDate; - export const Genericheader: typeof import('@studiocms/core/components').GenericHeader; - export const Navigation: typeof import('@studiocms/core/components').Navigation; -} - -declare module 'studiocms:helpers' { - export const authHelper: typeof import('@studiocms/core/helpers').authHelper; - export const urlGenFactory: typeof import('@studiocms/core/helpers').urlGenFactory; - export const pathWithBase: typeof import('@studiocms/core/helpers').pathWithBase; - export const fileWithBase: typeof import('@studiocms/core/helpers').fileWithBase; -} - -declare module 'studiocms:helpers/contentHelper' { - export const contentHelper: typeof import('@studiocms/core/helpers').contentHelper; - export const getSiteConfig: typeof import('@studiocms/core/helpers').getSiteConfig; - export const getPageById: typeof import('@studiocms/core/helpers').getPageById; - export const getPageList: typeof import('@studiocms/core/helpers').getPageList; - export const getUserList: typeof import('@studiocms/core/helpers').getUserList; - export const getUserById: typeof import('@studiocms/core/helpers').getUserById; - export const getPermissionsList: typeof import('@studiocms/core/helpers').getPermissionsList; - export type ContentHelperTempResponse = import( - '@studiocms/core/helpers' - ).ContentHelperTempResponse; - export type SiteConfigResponse = import('@studiocms/core/helpers').SiteConfigResponse; - export type pageDataReponse = import('@studiocms/core/helpers').pageDataReponse; - export type UserResponse = import('@studiocms/core/helpers').UserResponse; -} - -declare module 'studiocms:helpers/headDefaults' { - export const headDefaults: typeof import('@studiocms/core/helpers').headDefaults; -} - -declare module 'studiocms:helpers/routemap' { - export const getSluggedRoute: typeof import('@studiocms/core/helpers').getSluggedRoute; - export const getEditRoute: typeof import('@studiocms/core/helpers').getEditRoute; - export const getDeleteRoute: typeof import('@studiocms/core/helpers').getDeleteRoute; - export const makeNonDashboardRoute: typeof import( - '@studiocms/core/helpers' - ).makeNonDashboardRoute; - export const makeDashboardRoute: typeof import('@studiocms/core/helpers').makeDashboardRoute; - export const makeAPIDashboardRoute: typeof import( - '@studiocms/core/helpers' - ).makeAPIDashboardRoute; - export const StudioCMSRoutes: typeof import('@studiocms/core/helpers').StudioCMSRoutes; - export const sideBarLinkMap: typeof import('@studiocms/core/helpers').sideBarLinkMap; -} +/// diff --git a/packages/studiocms_imagehandler/LICENSE b/packages/studiocms_imagehandler/LICENSE index a30d1fbe4..94787104c 100644 --- a/packages/studiocms_imagehandler/LICENSE +++ b/packages/studiocms_imagehandler/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Astrolicious - StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares +Copyright (c) 2024 StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/studiocms_imagehandler/moon.yml b/packages/studiocms_imagehandler/moon.yml deleted file mode 100644 index e029ecb8d..000000000 --- a/packages/studiocms_imagehandler/moon.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: 'typescript' -tags: ['astro', 'typescript'] - -project: - name: '@studiocms/imagehandler' - description: 'A CMS that bridges Astro with Astro DB & Studio' - maintainers: ['Adammatthiesen','jdtjenkins','dreyfus92'] \ No newline at end of file diff --git a/packages/studiocms_imagehandler/package.json b/packages/studiocms_imagehandler/package.json index 4bf56477b..c7a03c762 100644 --- a/packages/studiocms_imagehandler/package.json +++ b/packages/studiocms_imagehandler/package.json @@ -41,10 +41,10 @@ "astro-integration-kit": "catalog:" }, "peerDependencies": { - "astro": "catalog:min" + "astro": "catalog:min", + "vite": "catalog:min" }, "devDependencies": { - "vite": "catalog:", "typescript": "catalog:" } } diff --git a/packages/studiocms_imagehandler/src/componentResolver.ts b/packages/studiocms_imagehandler/src/componentResolver.ts deleted file mode 100644 index 29ae6b1cb..000000000 --- a/packages/studiocms_imagehandler/src/componentResolver.ts +++ /dev/null @@ -1,83 +0,0 @@ -import DTSBuilder from '@matthiesenxyz/astrodtsbuilder'; -import { fileFactory } from '@matthiesenxyz/integration-utils/fileFactory'; -import { addVirtualImports, createResolver, defineUtility } from 'astro-integration-kit'; - -export const componentResolver = defineUtility('astro:config:setup')( - (params, options: { name: string; CustomImageOverride: string | undefined }) => { - // Destructure Params - const { config: astroConfig } = params; - - // Create Resolver for User-Defined Virtual Imports - const { resolve: rootResolve } = createResolver(astroConfig.root.pathname); - const { resolve } = createResolver(import.meta.url); - - // Create Virtual Resolver - let customImageResolved: string; - - if (options.CustomImageOverride) { - customImageResolved = rootResolve(options.CustomImageOverride); - } else { - customImageResolved = resolve('./components/CustomImage.astro'); - } - - addVirtualImports(params, { - name: options.name, - imports: { - 'studiocms:imageHandler/components': `export { default as CustomImage } from '${customImageResolved}';`, - }, - }); - - const customImageDTS = fileFactory(); - - customImageDTS.addLines('// This file is generated by StudioCMS\n\n'); - - customImageDTS.addLines(`declare module 'studiocms:imageHandler/components' { - /** - * # Custom Image Component for StudioCMS:imageHandler - * - * This component will adapt to the current configuration of the StudioCMS image handler and will render the used image accordingly. - * - * The default configuration will use '@unpic/astro' to allow for image optimization and lazy loading from most popular image hosting services. - * - * @props {string} src - Image Source - * @props {string} alt - Image Alt - * @props {number} width - Image Width - * @props {number} height - Image Height - */ - export const CustomImage: typeof import('${customImageResolved}').default; - }`); - - const dtsFile = DTSBuilder(); - - dtsFile.addSingleLineNote( - 'This file is generated by StudioCMS and should not be modified manually.' - ); - - dtsFile.addModule('studiocms:imageHandler', { - namedExports: [ - { - name: 'CustomImage', - multiLineDescription: [ - '# Custom Image Component for StudioCMS:imageHandler', - '', - 'This component will adapt to the current configuration of the StudioCMS image handler and will render the used image accordingly.', - '', - 'The default configuration will use `@unpic/astro` to allow for image optimization and lazy loading from most popular image hosting services.', - '', - '@props {string} src - Image Source', - '@props {string} alt - Image Alt', - '@props {number} width - Image Width', - '@props {number} height - Image Height', - ], - typeDef: `typeof import('${customImageResolved}').default`, - }, - ], - }); - - const dtsFileAstro = dtsFile.makeAstroInjectedType('imageHandler.d.ts'); - - return { - imageHandlerDtsFile: dtsFileAstro, - }; - } -); diff --git a/packages/studiocms_imagehandler/src/components/CustomImage.astro b/packages/studiocms_imagehandler/src/components/CustomImage.astro index d3a21783a..2d1f94fcc 100644 --- a/packages/studiocms_imagehandler/src/components/CustomImage.astro +++ b/packages/studiocms_imagehandler/src/components/CustomImage.astro @@ -1,6 +1,6 @@ --- import { Image } from 'astro:assets'; -import Config from 'virtual:studiocms/config'; +import Config from 'studiocms:config'; import { cloudinaryPlugin } from './plugins/cloudinary'; import type { SharedProps } from './props'; diff --git a/packages/studiocms_imagehandler/src/hooks/configDone.ts b/packages/studiocms_imagehandler/src/hooks/configDone.ts new file mode 100644 index 000000000..d0b3159ec --- /dev/null +++ b/packages/studiocms_imagehandler/src/hooks/configDone.ts @@ -0,0 +1,37 @@ +import DTSBuilder from '@matthiesenxyz/astrodtsbuilder'; +import { defineUtility } from 'astro-integration-kit'; + +export const configDone = defineUtility('astro:config:done')( + ({ injectTypes }, imageComponentPath: string) => { + const dtsFile = DTSBuilder(); + + dtsFile.addSingleLineNote( + 'This file is generated by StudioCMS and should not be modified manually.' + ); + + dtsFile.addModule('studiocms:imageHandler/components', { + namedExports: [ + { + name: 'CustomImage', + multiLineDescription: [ + '# Custom Image Component for StudioCMS:imageHandler', + '', + 'This component will adapt to the current configuration of the StudioCMS image handler and will render the used image accordingly.', + '', + '@props {string} src - Image Source', + '@props {string} alt - Image Alt', + '@props {number} width - Image Width', + '@props {number} height - Image Height', + ], + typeDef: `typeof import('${imageComponentPath}').default`, + }, + ], + }); + + const dtsFileAstro = dtsFile.makeAstroInjectedType('imageHandler.d.ts'); + + injectTypes(dtsFileAstro); + } +); + +export default configDone; diff --git a/packages/studiocms_imagehandler/src/hooks/configSetup.ts b/packages/studiocms_imagehandler/src/hooks/configSetup.ts new file mode 100644 index 000000000..aa9b398ef --- /dev/null +++ b/packages/studiocms_imagehandler/src/hooks/configSetup.ts @@ -0,0 +1,87 @@ +import { integrationLogger } from '@matthiesenxyz/integration-utils/astroUtils'; +import { addAstroEnvConfig } from '@studiocms/core/utils'; +import { addVirtualImports, createResolver, defineUtility } from 'astro-integration-kit'; +import { envField } from 'astro/config'; +import { loadEnv } from 'vite'; +import type { StudioCMSImageHandlerOptions } from '../schema'; + +export const configSetup = defineUtility('astro:config:setup')( + (params, name: string, options: StudioCMSImageHandlerOptions) => { + // Load Environment Variables + const env = loadEnv('all', process.cwd(), 'CMS'); + + // Destructure the params object + const { logger, updateConfig } = params; + + // Destructure the options object + const { + verbose, + imageService: { cdnPlugin }, + } = options; + + // Add Astro Environment Configuration + addAstroEnvConfig(params, { + validateSecrets: false, + schema: { + CMS_CLOUDINARY_CLOUDNAME: envField.string({ + context: 'server', + access: 'secret', + optional: true, + }), + }, + }); + + // Check for Cloudinary CDN Plugin + if (cdnPlugin === 'cloudinary-js') { + if (!env.CMS_CLOUDINARY_CLOUDNAME) { + integrationLogger( + { logger, logLevel: 'warn', verbose: true }, + 'Using the Cloudinary CDN JS SDK Plugin requires the CMS_CLOUDINARY_CLOUDNAME environment variable to be set. Please add this to your .env file.' + ); + } + } + + // Setup and Configure CustomImage Component + integrationLogger( + { logger, logLevel: 'info', verbose }, + 'Configuring CustomImage Component...' + ); + + // Create resolver relative to this file + const { resolve } = createResolver(import.meta.url); + + // Create resolver relative to Astro config root + const { resolve: astroConfigResolve } = createResolver(params.config.root.pathname); + + const imageComponentPath = options.overrides.CustomImageOverride + ? astroConfigResolve(options.overrides.CustomImageOverride) + : resolve('../components/CustomImage.astro'); + + addVirtualImports(params, { + name: name, + imports: { + 'studiocms:imageHandler/components': `export { default as CustomImage } from '${imageComponentPath}';`, + }, + }); + + // Update the Astro Config with the Image Service Configuration to allow for remote images + integrationLogger( + { logger, logLevel: 'info', verbose }, + 'Updating Astro Config with Image Service Configuration to allow for remote images...' + ); + updateConfig({ + image: { + remotePatterns: [ + { + protocol: 'https', + }, + ], + }, + }); + + // Return the imageComponentPath to be used in another hook + return imageComponentPath; + } +); + +export default configSetup; diff --git a/packages/studiocms_imagehandler/src/index.ts b/packages/studiocms_imagehandler/src/index.ts index 764afab26..e9cad8131 100644 --- a/packages/studiocms_imagehandler/src/index.ts +++ b/packages/studiocms_imagehandler/src/index.ts @@ -1,8 +1,25 @@ -import integration from './integration'; +import type { AstroIntegration } from 'astro'; +import { name as pkgName } from '../package.json'; +import configDone from './hooks/configDone'; +import configSetup from './hooks/configSetup'; +import type { StudioCMSImageHandlerOptions } from './schema'; /** - * StudioCMS Image Handler + * StudioCMS Image Handler Integration */ -const studioCMSImageHandler = integration; +function studioCMSImageHandler(options: StudioCMSImageHandlerOptions): AstroIntegration { + // Define the Image Component Path + let imageComponentPath: string; + + return { + name: pkgName, + hooks: { + 'astro:config:setup': (params) => { + imageComponentPath = configSetup(params, pkgName, options); + }, + 'astro:config:done': (params) => configDone(params, imageComponentPath), + }, + }; +} export default studioCMSImageHandler; diff --git a/packages/studiocms_imagehandler/src/integration.ts b/packages/studiocms_imagehandler/src/integration.ts deleted file mode 100644 index 89d851e02..000000000 --- a/packages/studiocms_imagehandler/src/integration.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { integrationLogger } from '@matthiesenxyz/integration-utils/astroUtils'; -import { imageHandlerStrings } from '@studiocms/core/strings'; -import { addAstroEnvConfig } from '@studiocms/core/utils'; -import type { InjectedType } from 'astro'; -import { defineIntegration } from 'astro-integration-kit'; -import { envField } from 'astro/config'; -import { loadEnv } from 'vite'; -import { name } from '../package.json'; -import { componentResolver } from './componentResolver'; -import { StudioCMSImageHandlerOptionsSchema } from './schema'; - -export default defineIntegration({ - name, - optionsSchema: StudioCMSImageHandlerOptionsSchema, - setup({ - name, - options: { - verbose, - imageService: { cdnPlugin }, - overrides: { CustomImageOverride }, - }, - }) { - // Load Environment Variables - const env = loadEnv('all', process.cwd(), 'CMS'); - - // Define the DTS File - let dtsFile: InjectedType; - - return { - hooks: { - 'astro:config:setup': (params) => { - // Destructure Params - const { logger, updateConfig } = params; - - // Add Astro Environment Configuration - addAstroEnvConfig(params, { - validateSecrets: false, - schema: { - CMS_CLOUDINARY_CLOUDNAME: envField.string({ - context: 'server', - access: 'secret', - optional: true, - }), - }, - }); - - // Check for Cloudinary CDN Plugin - if (cdnPlugin === 'cloudinary-js') { - if (!env.CMS_CLOUDINARY_CLOUDNAME) { - integrationLogger( - { logger, logLevel: 'warn', verbose: true }, - imageHandlerStrings.CloudinaryCDNWarning - ); - } - } - - // Setup and Configure CustomImage Component - integrationLogger( - { logger, logLevel: 'info', verbose }, - imageHandlerStrings.CustomImageLog - ); - const { imageHandlerDtsFile } = componentResolver(params, { - name, - CustomImageOverride, - }); - - // Update the Astro Config with the Image Service Configuration to allow for remote images - integrationLogger( - { logger, logLevel: 'info', verbose }, - imageHandlerStrings.updateConfig - ); - updateConfig({ - image: { - remotePatterns: [ - { - protocol: 'https', - }, - ], - }, - }); - - // Return the Custom Image DTS File - dtsFile = imageHandlerDtsFile; - }, - 'astro:config:done': ({ injectTypes }) => { - injectTypes(dtsFile); - }, - }, - }; - }, -}); diff --git a/packages/studiocms_imagehandler/tsconfig.json b/packages/studiocms_imagehandler/tsconfig.json index 5f1025bda..d175d082a 100644 --- a/packages/studiocms_imagehandler/tsconfig.json +++ b/packages/studiocms_imagehandler/tsconfig.json @@ -2,30 +2,10 @@ "extends": "astro/tsconfigs/strictest", "files": [], "compilerOptions": { - "outDir": "../../.moon/cache/types/packages/studiocms_imagehandler", "composite": true, "noEmit": false, "allowImportingTsExtensions": false, - "emitDeclarationOnly": false, - "paths": { - "node-playground/*": ["../../playgrounds/node/src/*"], - "@studiocms/core": ["../studiocms_core/src/index.ts"], - "@studiocms/core/*": ["../studiocms_core/src/*"] - } + "emitDeclarationOnly": false }, - "references": [ - { - "path": "../../playgrounds/node" - }, - { - "path": "../studiocms_core" - } - ], - "include": [ - "./package.json", - "../../playgrounds/node/.astro/**/*", - "../studiocms_core/**/*", - "./**/*", - "./src/**/*.json" - ] + "include": ["./package.json", "./**/*", "./src/**/*.json"] } diff --git a/packages/studiocms_imagehandler/virtuals.d.ts b/packages/studiocms_imagehandler/virtuals.d.ts index dcb348fe9..c234c77da 100644 --- a/packages/studiocms_imagehandler/virtuals.d.ts +++ b/packages/studiocms_imagehandler/virtuals.d.ts @@ -1,17 +1 @@ -/** - * # DEV TIP - * - * Wanting to extend StudioCMS? You can do so by defining a new module in the `virtual:studiocms` namespace within your project with the following format: - * - * This module can also be delcared from `@studiocms/core`. - * - * @example - * declare module 'virtual:studiocms/config' { - * const Config: import('@studiocms/core').StudioCMSConfig; - * export default Config; - * } - */ -declare module 'virtual:studiocms/config' { - const Config: import('@studiocms/core/schemas').StudioCMSConfig; - export default Config; -} +/// diff --git a/packages/studiocms_renderers/LICENSE b/packages/studiocms_renderers/LICENSE index a30d1fbe4..94787104c 100644 --- a/packages/studiocms_renderers/LICENSE +++ b/packages/studiocms_renderers/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Astrolicious - StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares +Copyright (c) 2024 StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/studiocms_renderers/moon.yml b/packages/studiocms_renderers/moon.yml deleted file mode 100644 index ebba8757e..000000000 --- a/packages/studiocms_renderers/moon.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: 'typescript' -tags: ['astro', 'typescript'] - -project: - name: '@studiocms/renderers' - description: 'A CMS that bridges Astro with Astro DB & Studio' - maintainers: ['Adammatthiesen','jdtjenkins','dreyfus92'] \ No newline at end of file diff --git a/packages/studiocms_renderers/package.json b/packages/studiocms_renderers/package.json index f039fc2e4..9d0de20a1 100644 --- a/packages/studiocms_renderers/package.json +++ b/packages/studiocms_renderers/package.json @@ -31,7 +31,10 @@ ], "exports": { ".": "./src/index.ts", - "./components": "./src/components/index.ts", + "./components/Renderer": { + "default": "./src/components/Renderer.js", + "types": "./src/components/Renderer.d.ts" + }, "./contentrenderer": "./src/lib/contentrenderer.ts", "./exports": "./src/exports/index.ts", "./exports/markdoc/react": "./src/exports/markdocRenderers/markdocReact.ts" @@ -39,21 +42,15 @@ "type": "module", "dependencies": { "@studiocms/core": "workspace:*", + "astro-integration-kit": "catalog:", - "@astrojs/markdown-remark": "catalog:", + "@inox-tools/runtime-logger": "catalog:studiocms-shared", "@matthiesenxyz/astrodtsbuilder": "catalog:studiocms-shared", "@matthiesenxyz/integration-utils": "catalog:studiocms-shared", + "@markdoc/markdoc": "catalog:studiocms-renderer", "@mdx-js/mdx": "catalog:studiocms-renderer", - "@shikijs/transformers": "catalog:studiocms-renderer", - "marked": "catalog:studiocms-renderer", - "marked-alert": "catalog:studiocms-renderer", - "marked-emoji": "catalog:studiocms-renderer", - "marked-footnote": "catalog:studiocms-renderer", - "marked-shiki": "catalog:studiocms-renderer", - "marked-smartypants": "catalog:studiocms-renderer", - "shiki": "catalog:studiocms-renderer", "react-dom": "catalog:studiocms-renderer", "react": "catalog:studiocms-renderer", "remark-gfm": "catalog:studiocms-renderer", @@ -62,7 +59,9 @@ }, "peerDependencies": { "@astrojs/react": "catalog:", - "astro": "catalog:min" + "@astrojs/markdown-remark": "catalog:min", + "astro": "catalog:min", + "vite": "catalog:min" }, "peerDependenciesMeta": { "@astrojs/react": { @@ -72,7 +71,6 @@ "devDependencies": { "@types/react-dom": "catalog:studiocms-renderer", "@types/react": "catalog:studiocms-renderer", - "vite": "catalog:", "typescript": "catalog:" } } diff --git a/packages/studiocms_renderers/src/components/Renderer.d.ts b/packages/studiocms_renderers/src/components/Renderer.d.ts new file mode 100644 index 000000000..335ca816e --- /dev/null +++ b/packages/studiocms_renderers/src/components/Renderer.d.ts @@ -0,0 +1,12 @@ +export interface Props { + content: string; + + // biome-ignore lint/suspicious/noExplicitAny: + [name: string]: any; +} + +// biome-ignore lint/suspicious/noExplicitAny: +// biome-ignore lint/style/noVar: +export var Renderer: (props: Props) => any; + +export default Renderer; diff --git a/packages/studiocms_renderers/src/components/Renderer.js b/packages/studiocms_renderers/src/components/Renderer.js new file mode 100644 index 000000000..5e9904bb7 --- /dev/null +++ b/packages/studiocms_renderers/src/components/Renderer.js @@ -0,0 +1,21 @@ +import contentRenderer from 'studiocms:renderer/current'; +import { HTMLString } from 'astro/runtime/server/index.js'; + +export const Renderer = Object.assign( + function Renderer(result, attributes, slots) { + return { + get [Symbol.toStringTag]() { + return 'AstroComponent'; + }, + async *[Symbol.asyncIterator]() { + const content = attributes.content; + yield new HTMLString(await contentRenderer(content)); + }, + }; + }, + { + isAstroComponentFactory: true, + } +); + +export default Renderer; diff --git a/packages/studiocms_renderers/src/components/StudioCMSRenderer.astro b/packages/studiocms_renderers/src/components/StudioCMSRenderer.astro deleted file mode 100644 index 37f2194e7..000000000 --- a/packages/studiocms_renderers/src/components/StudioCMSRenderer.astro +++ /dev/null @@ -1,45 +0,0 @@ ---- -import { logger } from '@it-astro:logger:studiocms-renderer'; -import rendererConfig from 'studiocms:renderer/config'; -import type { StudioCMSRendererConfig } from '@studiocms/core/schemas/renderer'; -import renderAstroMD from '../lib/astro-remark'; -import builtInContentRenderer from '../lib/contentRenderer'; -import renderMarkDoc from '../lib/markdoc'; -import renderMarked from '../lib/marked'; -import renderAstroMDX from '../lib/mdx'; - -type Props = { - content: string; - renderer?: StudioCMSRendererConfig['renderer']; -}; - -const { content, renderer = rendererConfig.renderer } = Astro.props; - -function rendererSelect(renderer: StudioCMSRendererConfig['renderer']) { - if (renderer === 'marked') { - logger.debug('Using built-in renderer: marked'); - return renderMarked; - } - if (renderer === 'astro') { - logger.debug('Using built-in renderer: astro remark'); - return renderAstroMD; - } - if (renderer === 'markdoc') { - logger.debug('Using built-in renderer: markdoc'); - return renderMarkDoc; - } - if (renderer === 'mdx') { - logger.debug('Using built-in renderer: mdx'); - return renderAstroMDX; - } - if (renderer.renderer) { - logger.debug(`Using custom renderer: ${renderer.name}`); - return renderer.renderer; - } - return renderMarked; -} - -const renderedContent = builtInContentRenderer({ content, renderer: rendererSelect(renderer) }); ---- - - \ No newline at end of file diff --git a/packages/studiocms_renderers/src/components/index.ts b/packages/studiocms_renderers/src/components/index.ts deleted file mode 100644 index c151cebed..000000000 --- a/packages/studiocms_renderers/src/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as StudioCMSRenderer } from './StudioCMSRenderer.astro'; diff --git a/packages/studiocms_renderers/src/exports/markdocRenderers/markdocReact.ts b/packages/studiocms_renderers/src/exports/markdocRenderers/markdocReact.ts index 4a0eaac9e..fc6d9785d 100644 --- a/packages/studiocms_renderers/src/exports/markdocRenderers/markdocReact.ts +++ b/packages/studiocms_renderers/src/exports/markdocRenderers/markdocReact.ts @@ -1,6 +1,6 @@ import reactRenderer from '@astrojs/react/server.js'; import Markdoc, { type RenderableTreeNode } from '@markdoc/markdoc'; -import type { markdocRenderer } from '@studiocms/core/schemas/renderer'; +import type { MarkdocRenderer } from '@studiocms/core/schemas/renderer'; import { experimental_AstroContainer as AstroContainer } from 'astro/container'; import React from 'react'; import ReactWrapper from './markdocReact-components/MarkDocReactWrapper.astro'; @@ -22,7 +22,7 @@ export type markdocReactComponents = {} | undefined; * @param components - The React components to use for rendering the content * @returns The MarkDoc React Renderer for StudioCMS MarkDoc */ -export function markDocRenderReact(components?: markdocReactComponents): markdocRenderer { +export function markDocRenderReact(components?: markdocReactComponents): MarkdocRenderer { return { name: 'react', renderer: async (content: RenderableTreeNode) => { diff --git a/packages/studiocms_renderers/src/hooks/config-done.ts b/packages/studiocms_renderers/src/hooks/config-done.ts new file mode 100644 index 000000000..6a9510547 --- /dev/null +++ b/packages/studiocms_renderers/src/hooks/config-done.ts @@ -0,0 +1,17 @@ +import { defineUtility } from 'astro-integration-kit'; +import rendererDTS from '../stubs/renderer'; +import rendererConfigDTS from '../stubs/renderer-config'; +import rendererMarkdownConfigDTS from '../stubs/renderer-markdownConfig'; + +export const configDone = defineUtility('astro:config:done')(({ injectTypes }) => { + // Inject Types for Renderer + injectTypes(rendererDTS); + + // Inject Types for Renderer Config + injectTypes(rendererConfigDTS); + + // Inject Types for Astro Markdown Config + injectTypes(rendererMarkdownConfigDTS); +}); + +export default configDone; diff --git a/packages/studiocms_renderers/src/hooks/config-setup.ts b/packages/studiocms_renderers/src/hooks/config-setup.ts new file mode 100644 index 000000000..068d2ae5d --- /dev/null +++ b/packages/studiocms_renderers/src/hooks/config-setup.ts @@ -0,0 +1,48 @@ +import { runtimeLogger } from '@inox-tools/runtime-logger'; +import { integrationLogger } from '@matthiesenxyz/integration-utils/astroUtils'; +import { stringify } from '@studiocms/core/lib'; +import type { StudioCMSRendererConfig } from '@studiocms/core/schemas/renderer'; +import { addVirtualImports, createResolver, defineUtility } from 'astro-integration-kit'; + +type ConfigSetupOptions = { + options: StudioCMSRendererConfig; + verbose?: boolean | undefined; + pkgName: string; +}; + +const { resolve } = createResolver(import.meta.url); + +const RendererComponent = resolve('../components/Renderer.js'); + +export const configSetup = defineUtility('astro:config:setup')( + (params, { verbose = false, options, pkgName }: ConfigSetupOptions) => { + // Destructure the params + const { logger, config } = params; + + // Log that Setup is Starting + integrationLogger({ logger, logLevel: 'info', verbose }, 'Setting up StudioCMS Renderer...'); + // Setup the runtime logger + runtimeLogger(params, { name: 'studiocms-renderer' }); + + // Add Virtual Imports + addVirtualImports(params, { + name: pkgName, + imports: { + 'studiocms:renderer': `export { default as StudioCMSRenderer } from '${RendererComponent}';`, + 'studiocms:renderer/config': `export default ${stringify(options)}`, + 'studiocms:renderer/astroMarkdownConfig': `export default ${stringify(config.markdown)}`, + 'studiocms:renderer/current': ` + export * from '${resolve('../lib/contentRenderer.ts')}'; + import contentRenderer from '${resolve('../lib/contentRenderer.ts')}'; + export default contentRenderer; + `, + }, + }); + integrationLogger( + { logger, logLevel: 'info', verbose }, + 'StudioCMS Renderer Virtual Imports Added...' + ); + } +); + +export default configSetup; diff --git a/packages/studiocms_renderers/src/index.ts b/packages/studiocms_renderers/src/index.ts index 9e1127440..a7a3a46f3 100644 --- a/packages/studiocms_renderers/src/index.ts +++ b/packages/studiocms_renderers/src/index.ts @@ -1,14 +1,30 @@ -import { - type StudioCMSRendererConfig, - StudioCMSRendererConfigSchema, -} from '@studiocms/core/schemas/renderer'; -import integration from './integration'; +import type { StudioCMSRendererConfig } from '@studiocms/core/schemas/renderer'; +import type { AstroIntegration } from 'astro'; +import { name as pkgName } from '../package.json'; +import configDone from './hooks/config-done'; +import configSetup from './hooks/config-setup'; /** - * StudioCMS Renderers Integration + * **StudioCMS Renderers Integration** + * + * @param options StudioCMS Renderer Configuration + * @returns AstroIntegration + * + * @see [StudioCMS Docs](https://docs.studiocms.dev) for more information on how to use StudioCMS. */ -const studioCMSRenderers = integration; +export function studioCMSRenderers( + options: StudioCMSRendererConfig, + verbose?: boolean +): AstroIntegration { + return { + name: pkgName, + hooks: { + 'astro:config:setup': (params) => configSetup(params, { options, verbose, pkgName }), + 'astro:config:done': (params) => configDone(params), + }, + }; +} export default studioCMSRenderers; -export { StudioCMSRendererConfigSchema, type StudioCMSRendererConfig }; +export type { StudioCMSRendererConfig }; diff --git a/packages/studiocms_renderers/src/integration.ts b/packages/studiocms_renderers/src/integration.ts deleted file mode 100644 index 8c6bbdb63..000000000 --- a/packages/studiocms_renderers/src/integration.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { runtimeLogger } from '@inox-tools/runtime-logger'; -import { stringify } from '@studiocms/core/lib'; -import { StudioCMSRendererConfigSchema } from '@studiocms/core/schemas/renderer'; -import { addVirtualImports, createResolver, defineIntegration } from 'astro-integration-kit'; -import { name } from '../package.json'; -import { rendererDTS } from './stubs/renderer'; -import { rendererConfigDTS } from './stubs/renderer-config'; -import { rendererAstroMarkdownDTS } from './stubs/renderer-markdownConfig'; - -/** - * StudioCMS Renderers Integration - */ -export default defineIntegration({ - name, - optionsSchema: StudioCMSRendererConfigSchema, - setup({ name, options }) { - // Create resolver relative to this file - const { resolve } = createResolver(import.meta.url); - - // Import the Renderer Component - const RendererComponent = resolve('./components/StudioCMSRenderer.astro'); - - return { - hooks: { - 'astro:config:setup': async (params) => { - // Destructure the params - const { - config: { markdown: astroMarkdown }, - } = params; - - // Setup the runtime logger - runtimeLogger(params, { name: 'studiocms-renderer' }); - - // Add Virtual Imports - addVirtualImports(params, { - name, - imports: { - 'studiocms:renderer': `export { default as StudioCMSRenderer } from '${RendererComponent}';`, - 'studiocms:renderer/config': `export default ${stringify(options)}`, - 'studiocms:renderer/astroMarkdownConfig': `export default ${stringify(astroMarkdown)}`, - }, - }); - }, - 'astro:config:done': async ({ injectTypes }) => { - // Inject Types for Renderer - injectTypes(rendererDTS(RendererComponent)); - - // Inject Types for Renderer Config - injectTypes(rendererConfigDTS()); - - // Inject Types for Astro Markdown Config - injectTypes(rendererAstroMarkdownDTS()); - }, - }, - }; - }, -}); diff --git a/packages/studiocms_renderers/src/lib/astro-remark/index.ts b/packages/studiocms_renderers/src/lib/astro-remark.ts similarity index 57% rename from packages/studiocms_renderers/src/lib/astro-remark/index.ts rename to packages/studiocms_renderers/src/lib/astro-remark.ts index a4bc92e6e..5cf5a735e 100644 --- a/packages/studiocms_renderers/src/lib/astro-remark/index.ts +++ b/packages/studiocms_renderers/src/lib/astro-remark.ts @@ -1,6 +1,7 @@ import astroMarkdownConfig from 'studiocms:renderer/astroMarkdownConfig'; -import { type AstroMarkdownOptions, createMarkdownProcessor } from '@astrojs/markdown-remark'; -import { HTMLString } from './html-string'; +import { createMarkdownProcessor } from '@astrojs/markdown-remark'; + +const cachedProcessor = await createMarkdownProcessor(astroMarkdownConfig); /** * Render Astro Markdown @@ -14,10 +15,7 @@ import { HTMLString } from './html-string'; * @returns The rendered content */ export async function renderAstroMD(content: string) { - const processor = await createMarkdownProcessor(astroMarkdownConfig as AstroMarkdownOptions); - - const result = await processor.render(content); - return `${new HTMLString(result.code)}`; + return (await cachedProcessor.render(content)).code; } export default renderAstroMD; diff --git a/packages/studiocms_renderers/src/lib/astro-remark/html-string.ts b/packages/studiocms_renderers/src/lib/astro-remark/html-string.ts deleted file mode 100644 index a4d7c67ab..000000000 --- a/packages/studiocms_renderers/src/lib/astro-remark/html-string.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class HTMLString extends String { - get [Symbol.toStringTag]() { - return 'HTMLString'; - } -} diff --git a/packages/studiocms_renderers/src/lib/contentRenderer.ts b/packages/studiocms_renderers/src/lib/contentRenderer.ts index 90c4ec517..bad0c2961 100644 --- a/packages/studiocms_renderers/src/lib/contentRenderer.ts +++ b/packages/studiocms_renderers/src/lib/contentRenderer.ts @@ -1,38 +1,44 @@ -import type { Renderer } from '@studiocms/core/schemas/renderer'; +import { logger } from '@it-astro:logger:studiocms-renderer'; +import rendererConfig from 'studiocms:renderer/config'; +import renderAstroMD from './astro-remark'; +import renderMarkDoc from './markdoc'; +import renderAstroMDX from './mdx'; -/** - * Content Renderer Type - * - * Renders content based on the renderer configuration - */ -export type ContentRenderer = { - content: string; - renderer: Renderer; -}; +const { renderer } = rendererConfig; /** - * Content Renderer + * Renders the given content using a specified renderer. * - * Renders content based on the renderer configuration + * The renderer can be a custom object with a `renderer` function and a `name` property, + * or a string indicating one of the built-in renderers ('astro', 'markdoc', 'mdx'). * - * @param content - The content to render - * @param renderer - The renderer function to use - * @returns The rendered content - * - * @example - * function sampleRenderer(content: string): Promise { - * // Assuming the renderer function processes the content and returns a string - * return `

${content}

`; - * } - * - * const renderedContent = contentRenderer({ - * content: 'Hello, world!', - * renderer: sampleRenderer, - * }); + * @param content - The content to be rendered. + * @returns A promise that resolves to the rendered content as a string. + * @throws Will throw an error if the custom renderer object is invalid. */ -export async function contentRenderer({ content, renderer }: ContentRenderer): Promise { - // Assuming the renderer function processes the content and returns a string - return renderer(content); +export async function contentRenderer(content: string): Promise { + if (typeof renderer === 'object') { + if (!renderer.renderer || !renderer.name) { + throw new Error('Invalid custom renderer'); + } + logger.debug(`Using custom renderer: ${renderer.name}`); + return await renderer.renderer(content); + } + + switch (renderer) { + case 'astro': + logger.debug('Using built-in renderer: astro remark'); + return await renderAstroMD(content); + case 'markdoc': + logger.debug('Using built-in renderer: markdoc'); + return await renderMarkDoc(content); + case 'mdx': + logger.debug('Using built-in renderer: mdx'); + return await renderAstroMDX(content); + default: + logger.error(`Unknown renderer: ${renderer}, falling back to astro remark`); + return await renderAstroMD(content); + } } export default contentRenderer; diff --git a/packages/studiocms_renderers/src/lib/markdoc/markdocHTML.ts b/packages/studiocms_renderers/src/lib/markdoc-renderers/markdocHTML.ts similarity index 68% rename from packages/studiocms_renderers/src/lib/markdoc/markdocHTML.ts rename to packages/studiocms_renderers/src/lib/markdoc-renderers/markdocHTML.ts index 231eeb49a..2403236e1 100644 --- a/packages/studiocms_renderers/src/lib/markdoc/markdocHTML.ts +++ b/packages/studiocms_renderers/src/lib/markdoc-renderers/markdocHTML.ts @@ -1,7 +1,7 @@ import Markdoc, { type RenderableTreeNode } from '@markdoc/markdoc'; -import type { markdocRenderer } from '@studiocms/core/schemas/renderer'; +import type { MarkdocRenderer } from '@studiocms/core/schemas/renderer'; -export function renderHTML(): markdocRenderer { +export function renderHTML(): MarkdocRenderer { return { name: 'html', renderer: async (content: RenderableTreeNode) => { diff --git a/packages/studiocms_renderers/src/lib/markdoc/markdocReactStatic.ts b/packages/studiocms_renderers/src/lib/markdoc-renderers/markdocReactStatic.ts similarity index 68% rename from packages/studiocms_renderers/src/lib/markdoc/markdocReactStatic.ts rename to packages/studiocms_renderers/src/lib/markdoc-renderers/markdocReactStatic.ts index f2594f646..5e1cab7ba 100644 --- a/packages/studiocms_renderers/src/lib/markdoc/markdocReactStatic.ts +++ b/packages/studiocms_renderers/src/lib/markdoc-renderers/markdocReactStatic.ts @@ -1,7 +1,7 @@ import Markdoc, { type RenderableTreeNode } from '@markdoc/markdoc'; -import type { markdocRenderer } from '@studiocms/core/schemas/renderer'; +import type { MarkdocRenderer } from '@studiocms/core/schemas/renderer'; -export function renderReactStatic(): markdocRenderer { +export function renderReactStatic(): MarkdocRenderer { return { name: 'react-static', renderer: async (content: RenderableTreeNode) => { diff --git a/packages/studiocms_renderers/src/lib/markdoc/index.ts b/packages/studiocms_renderers/src/lib/markdoc.ts similarity index 69% rename from packages/studiocms_renderers/src/lib/markdoc/index.ts rename to packages/studiocms_renderers/src/lib/markdoc.ts index a3044251e..6e7309f5a 100644 --- a/packages/studiocms_renderers/src/lib/markdoc/index.ts +++ b/packages/studiocms_renderers/src/lib/markdoc.ts @@ -1,16 +1,16 @@ import { logger } from '@it-astro:logger:studiocms-renderer'; import rendererConfig from 'studiocms:renderer/config'; -import Markdoc from '@markdoc/markdoc'; -import type { markdocRenderer } from '@studiocms/core/schemas/renderer'; -import renderHTML from './markdocHTML'; -import renderReactStatic from './markdocReactStatic'; +import Markdoc, { type ConfigType, type ParserArgs } from '@markdoc/markdoc'; +import type { MarkdocRenderer } from '@studiocms/core/schemas/renderer'; +import renderHTML from './markdoc-renderers/markdocHTML'; +import renderReactStatic from './markdoc-renderers/markdocReactStatic'; // Destructure the Markdoc configuration from the rendererConfig const { markdocConfig: { argParse, transformConfig, renderType }, } = rendererConfig; -const renderers: markdocRenderer[] = [renderHTML(), renderReactStatic()]; +const renderers: MarkdocRenderer[] = [renderHTML(), renderReactStatic()]; /** * Render a Markdown string into HTML using the Markdoc renderer @@ -23,14 +23,14 @@ const renderers: markdocRenderer[] = [renderHTML(), renderReactStatic()]; */ export async function renderMarkDoc(input: string): Promise { // Parse the input string into an AST - const ast = Markdoc.parse(input, argParse); + const ast = Markdoc.parse(input, argParse as ParserArgs); // Transform the AST into content - const content = Markdoc.transform(ast, transformConfig); + const content = Markdoc.transform(ast, transformConfig as ConfigType); const renderer = renderers.find((r) => r.name === renderType); if (renderer) { logger.debug(`Rendering content with built-in renderer: ${renderer.name}`); - return renderer.renderer(content); + return renderer.renderer(content as string); } if ( renderType !== 'html' && @@ -39,7 +39,7 @@ export async function renderMarkDoc(input: string): Promise { renderType.renderer ) { logger.debug(`Rendering content with custom renderer: ${renderType.name}`); - return renderType.renderer(content).catch((e) => { + return renderType.renderer(content as string).catch((e) => { throw new Error(`Failed to render content with custom renderer: [${renderType.name}]: ${e}`); }); } diff --git a/packages/studiocms_renderers/src/lib/marked/emoji-en-US.json b/packages/studiocms_renderers/src/lib/marked/emoji-en-US.json deleted file mode 100644 index 0bab06c0c..000000000 --- a/packages/studiocms_renderers/src/lib/marked/emoji-en-US.json +++ /dev/null @@ -1,3351 +0,0 @@ -{ - "😀": ["grinning_face", "face", "smile", "happy", "joy", ":D", "grin"], - "😃": [ - "grinning_face_with_big_eyes", - "face", - "happy", - "joy", - "haha", - ":D", - ":)", - "smile", - "funny" - ], - "😄": [ - "grinning_face_with_smiling_eyes", - "face", - "happy", - "joy", - "funny", - "haha", - "laugh", - "like", - ":D", - ":)", - "smile" - ], - "😁": ["beaming_face_with_smiling_eyes", "face", "happy", "smile", "joy", "kawaii"], - "😆": [ - "grinning_squinting_face", - "happy", - "joy", - "lol", - "satisfied", - "haha", - "face", - "glad", - "XD", - "laugh" - ], - "😅": ["grinning_face_with_sweat", "face", "hot", "happy", "laugh", "sweat", "smile", "relief"], - "🤣": [ - "rolling_on_the_floor_laughing", - "face", - "rolling", - "floor", - "laughing", - "lol", - "haha", - "rofl" - ], - "😂": ["face_with_tears_of_joy", "face", "cry", "tears", "weep", "happy", "happytears", "haha"], - "🙂": ["slightly_smiling_face", "face", "smile"], - "🙃": ["upside_down_face", "face", "flipped", "silly", "smile"], - "😉": ["winking_face", "face", "happy", "mischievous", "secret", ";)", "smile", "eye"], - "😊": [ - "smiling_face_with_smiling_eyes", - "face", - "smile", - "happy", - "flushed", - "crush", - "embarrassed", - "shy", - "joy" - ], - "😇": ["smiling_face_with_halo", "face", "angel", "heaven", "halo", "innocent"], - "🥰": [ - "smiling_face_with_hearts", - "face", - "love", - "like", - "affection", - "valentines", - "infatuation", - "crush", - "hearts", - "adore" - ], - "😍": [ - "smiling_face_with_heart_eyes", - "face", - "love", - "like", - "affection", - "valentines", - "infatuation", - "crush", - "heart" - ], - "🤩": ["star_struck", "face", "smile", "starry", "eyes", "grinning"], - "😘": [ - "face_blowing_a_kiss", - "face", - "love", - "like", - "affection", - "valentines", - "infatuation", - "kiss" - ], - "😗": ["kissing_face", "love", "like", "face", "3", "valentines", "infatuation", "kiss"], - "☺️": ["smiling_face", "face", "blush", "massage", "happiness"], - "😚": [ - "kissing_face_with_closed_eyes", - "face", - "love", - "like", - "affection", - "valentines", - "infatuation", - "kiss" - ], - "😙": [ - "kissing_face_with_smiling_eyes", - "face", - "affection", - "valentines", - "infatuation", - "kiss" - ], - "😋": [ - "face_savoring_food", - "happy", - "joy", - "tongue", - "smile", - "face", - "silly", - "yummy", - "nom", - "delicious", - "savouring" - ], - "😛": [ - "face_with_tongue", - "face", - "prank", - "childish", - "playful", - "mischievous", - "smile", - "tongue" - ], - "😜": [ - "winking_face_with_tongue", - "face", - "prank", - "childish", - "playful", - "mischievous", - "smile", - "wink", - "tongue" - ], - "🤪": ["zany_face", "face", "goofy", "crazy"], - "😝": [ - "squinting_face_with_tongue", - "face", - "prank", - "playful", - "mischievous", - "smile", - "tongue" - ], - "🤑": ["money_mouth_face", "face", "rich", "dollar", "money"], - "🤗": ["hugging_face", "face", "smile", "hug"], - "🤭": ["face_with_hand_over_mouth", "face", "whoops", "shock", "surprise"], - "🤫": ["shushing_face", "face", "quiet", "shhh"], - "🤔": ["thinking_face", "face", "hmmm", "think", "consider"], - "🤐": ["zipper_mouth_face", "face", "sealed", "zipper", "secret"], - "🤨": [ - "face_with_raised_eyebrow", - "face", - "distrust", - "scepticism", - "disapproval", - "disbelief", - "surprise", - "suspicious" - ], - "😐": ["neutral_face", "indifference", "meh", ":|", "neutral"], - "😑": ["expressionless_face", "face", "indifferent", "-_-", "meh", "deadpan"], - "😶": ["face_without_mouth", "face"], - "😏": ["smirking_face", "face", "smile", "mean", "prank", "smug", "sarcasm"], - "😒": [ - "unamused_face", - "indifference", - "bored", - "straight face", - "serious", - "sarcasm", - "unimpressed", - "skeptical", - "dubious", - "ugh", - "side_eye" - ], - "🙄": ["face_with_rolling_eyes", "face", "eyeroll", "frustrated"], - "😬": ["grimacing_face", "face", "grimace", "teeth"], - "🤥": ["lying_face", "face", "lie", "pinocchio"], - "😌": ["relieved_face", "face", "relaxed", "phew", "massage", "happiness"], - "😔": ["pensive_face", "face", "sad", "depressed", "upset"], - "😪": ["sleepy_face", "face", "tired", "rest", "nap"], - "🤤": ["drooling_face", "face"], - "😴": ["sleeping_face", "face", "tired", "sleepy", "night", "zzz"], - "😷": ["face_with_medical_mask", "face", "sick", "ill", "disease", "covid"], - "🤒": ["face_with_thermometer", "sick", "temperature", "thermometer", "cold", "fever", "covid"], - "🤕": ["face_with_head_bandage", "injured", "clumsy", "bandage", "hurt"], - "🤢": ["nauseated_face", "face", "vomit", "gross", "green", "sick", "throw up", "ill"], - "🤮": ["face_vomiting", "face", "sick"], - "🤧": ["sneezing_face", "face", "gesundheit", "sneeze", "sick", "allergy"], - "🥵": ["hot_face", "face", "feverish", "heat", "red", "sweating"], - "🥶": ["cold_face", "face", "blue", "freezing", "frozen", "frostbite", "icicles"], - "🥴": ["woozy_face", "face", "dizzy", "intoxicated", "tipsy", "wavy"], - "😵": ["dizzy_face", "spent", "unconscious", "xox", "dizzy"], - "🤯": ["exploding_head", "face", "shocked", "mind", "blown"], - "🤠": ["cowboy_hat_face", "face", "cowgirl", "hat"], - "🥳": ["partying_face", "face", "celebration", "woohoo"], - "😎": ["smiling_face_with_sunglasses", "face", "cool", "smile", "summer", "beach", "sunglass"], - "🤓": ["nerd_face", "face", "nerdy", "geek", "dork"], - "🧐": ["face_with_monocle", "face", "stuffy", "wealthy"], - "😕": ["confused_face", "face", "indifference", "huh", "weird", "hmmm", ":/"], - "😟": ["worried_face", "face", "concern", "nervous", ":("], - "🙁": ["slightly_frowning_face", "face", "frowning", "disappointed", "sad", "upset"], - "☹️": ["frowning_face", "face", "sad", "upset", "frown"], - "😮": ["face_with_open_mouth", "face", "surprise", "impressed", "wow", "whoa", ":O"], - "😯": ["hushed_face", "face", "woo", "shh"], - "😲": ["astonished_face", "face", "xox", "surprised", "poisoned"], - "😳": ["flushed_face", "face", "blush", "shy", "flattered"], - "🥺": ["pleading_face", "face", "begging", "mercy", "cry", "tears", "sad", "grievance"], - "😦": ["frowning_face_with_open_mouth", "face", "aw", "what"], - "😧": ["anguished_face", "face", "stunned", "nervous"], - "😨": ["fearful_face", "face", "scared", "terrified", "nervous"], - "😰": ["anxious_face_with_sweat", "face", "nervous", "sweat"], - "😥": ["sad_but_relieved_face", "face", "phew", "sweat", "nervous"], - "😢": ["crying_face", "face", "tears", "sad", "depressed", "upset", ":'("], - "😭": ["loudly_crying_face", "sobbing", "face", "cry", "tears", "sad", "upset", "depressed"], - "😱": ["face_screaming_in_fear", "face", "munch", "scared", "omg"], - "😖": ["confounded_face", "face", "confused", "sick", "unwell", "oops", ":S"], - "😣": ["persevering_face", "face", "sick", "no", "upset", "oops"], - "😞": ["disappointed_face", "face", "sad", "upset", "depressed", ":("], - "😓": ["downcast_face_with_sweat", "face", "hot", "sad", "tired", "exercise"], - "😩": ["weary_face", "face", "tired", "sleepy", "sad", "frustrated", "upset"], - "😫": ["tired_face", "sick", "whine", "upset", "frustrated"], - "🥱": ["yawning_face", "tired", "sleepy"], - "😤": ["face_with_steam_from_nose", "face", "gas", "phew", "proud", "pride"], - "😡": ["pouting_face", "angry", "mad", "hate", "despise"], - "😠": ["angry_face", "mad", "face", "annoyed", "frustrated"], - "🤬": [ - "face_with_symbols_on_mouth", - "face", - "swearing", - "cursing", - "cussing", - "profanity", - "expletive" - ], - "😈": ["smiling_face_with_horns", "devil", "horns"], - "👿": ["angry_face_with_horns", "devil", "angry", "horns"], - "💀": ["skull", "dead", "skeleton", "creepy", "death", "dead"], - "☠️": ["skull_and_crossbones", "poison", "danger", "deadly", "scary", "death", "pirate", "evil"], - "💩": ["pile_of_poo", "hankey", "shitface", "fail", "turd", "shit"], - "🤡": ["clown_face", "face"], - "👹": [ - "ogre", - "monster", - "red", - "mask", - "halloween", - "scary", - "creepy", - "devil", - "demon", - "japanese_ogre" - ], - "👺": ["goblin", "red", "evil", "mask", "monster", "scary", "creepy", "japanese_goblin"], - "👻": ["ghost", "halloween", "spooky", "scary"], - "👽": ["alien", "UFO", "paul", "weird", "outer_space"], - "👾": ["alien_monster", "game", "arcade", "play"], - "🤖": ["robot", "computer", "machine", "bot"], - "😺": ["grinning_cat", "animal", "cats", "happy", "smile"], - "😸": ["grinning_cat_with_smiling_eyes", "animal", "cats", "smile"], - "😹": ["cat_with_tears_of_joy", "animal", "cats", "haha", "happy", "tears"], - "😻": [ - "smiling_cat_with_heart_eyes", - "animal", - "love", - "like", - "affection", - "cats", - "valentines", - "heart" - ], - "😼": ["cat_with_wry_smile", "animal", "cats", "smirk"], - "😽": ["kissing_cat", "animal", "cats", "kiss"], - "🙀": ["weary_cat", "animal", "cats", "munch", "scared", "scream"], - "😿": ["crying_cat", "animal", "tears", "weep", "sad", "cats", "upset", "cry"], - "😾": ["pouting_cat", "animal", "cats"], - "🙈": ["see_no_evil_monkey", "monkey", "animal", "nature", "haha"], - "🙉": ["hear_no_evil_monkey", "animal", "monkey", "nature"], - "🙊": ["speak_no_evil_monkey", "monkey", "animal", "nature", "omg"], - "💋": ["kiss_mark", "face", "lips", "love", "like", "affection", "valentines"], - "💌": ["love_letter", "email", "like", "affection", "envelope", "valentines"], - "💘": ["heart_with_arrow", "love", "like", "heart", "affection", "valentines"], - "💝": ["heart_with_ribbon", "love", "valentines"], - "💖": ["sparkling_heart", "love", "like", "affection", "valentines"], - "💗": ["growing_heart", "like", "love", "affection", "valentines", "pink"], - "💓": ["beating_heart", "love", "like", "affection", "valentines", "pink", "heart"], - "💞": ["revolving_hearts", "love", "like", "affection", "valentines"], - "💕": ["two_hearts", "love", "like", "affection", "valentines", "heart"], - "💟": ["heart_decoration", "purple-square", "love", "like"], - "❣️": ["heart_exclamation", "decoration", "love"], - "💔": ["broken_heart", "sad", "sorry", "break", "heart", "heartbreak"], - "❤️": ["red_heart", "love", "like", "valentines"], - "🧡": ["orange_heart", "love", "like", "affection", "valentines"], - "💛": ["yellow_heart", "love", "like", "affection", "valentines"], - "💚": ["green_heart", "love", "like", "affection", "valentines"], - "💙": ["blue_heart", "love", "like", "affection", "valentines"], - "💜": ["purple_heart", "love", "like", "affection", "valentines"], - "🤎": ["brown_heart", "coffee"], - "🖤": ["black_heart", "evil"], - "🤍": ["white_heart", "pure"], - "💯": [ - "hundred_points", - "score", - "perfect", - "numbers", - "century", - "exam", - "quiz", - "test", - "pass", - "hundred", - "100" - ], - "💢": ["anger_symbol", "angry", "mad"], - "💥": ["collision", "bomb", "explode", "explosion", "blown"], - "💫": ["dizzy", "star", "sparkle", "shoot", "magic"], - "💦": ["sweat_droplets", "water", "drip", "oops"], - "💨": ["dashing_away", "wind", "air", "fast", "shoo", "fart", "smoke", "puff"], - "🕳️": ["hole", "embarrassing"], - "💣": ["bomb", "boom", "explode", "explosion", "terrorism"], - "💬": ["speech_balloon", "bubble", "words", "message", "talk", "chatting"], - "👁️‍🗨️": ["eye_in_speech_bubble", "info"], - "🗨️": ["left_speech_bubble", "words", "message", "talk", "chatting"], - "🗯️": ["right_anger_bubble", "caption", "speech", "thinking", "mad"], - "💭": ["thought_balloon", "bubble", "cloud", "speech", "thinking", "dream"], - "💤": ["zzz", "sleepy", "tired", "dream"], - "👋": [ - "waving_hand", - "wave", - "hands", - "gesture", - "goodbye", - "solong", - "farewell", - "hello", - "hi", - "palm" - ], - "🤚": ["raised_back_of_hand", "fingers", "raised", "backhand"], - "🖐️": ["hand_with_fingers_splayed", "hand", "fingers", "palm"], - "✋": ["raised_hand", "fingers", "stop", "highfive", "palm", "ban"], - "🖖": ["vulcan_salute", "hand", "fingers", "spock", "star trek"], - "👌": ["ok_hand", "fingers", "limbs", "perfect", "ok", "okay"], - "🤏": ["pinching_hand", "tiny", "small", "size"], - "✌️": ["victory_hand", "fingers", "ohyeah", "hand", "peace", "victory", "two"], - "🤞": ["crossed_fingers", "good", "lucky"], - "🤟": ["love_you_gesture", "hand", "fingers", "gesture"], - "🤘": ["sign_of_the_horns", "hand", "fingers", "evil_eye", "sign_of_horns", "rock_on"], - "🤙": ["call_me_hand", "hands", "gesture", "shaka"], - "👈": ["backhand_index_pointing_left", "direction", "fingers", "hand", "left"], - "👉": ["backhand_index_pointing_right", "fingers", "hand", "direction", "right"], - "👆": ["backhand_index_pointing_up", "fingers", "hand", "direction", "up"], - "🖕": ["middle_finger", "hand", "fingers", "rude", "middle", "flipping"], - "👇": ["backhand_index_pointing_down", "fingers", "hand", "direction", "down"], - "☝️": ["index_pointing_up", "hand", "fingers", "direction", "up"], - "👍": [ - "thumbs_up", - "thumbsup", - "yes", - "awesome", - "good", - "agree", - "accept", - "cool", - "hand", - "like", - "+1" - ], - "👎": ["thumbs_down", "thumbsdown", "no", "dislike", "hand", "-1"], - "✊": ["raised_fist", "fingers", "hand", "grasp"], - "👊": ["oncoming_fist", "angry", "violence", "fist", "hit", "attack", "hand"], - "🤛": ["left_facing_fist", "hand", "fistbump"], - "🤜": ["right_facing_fist", "hand", "fistbump"], - "👏": ["clapping_hands", "hands", "praise", "applause", "congrats", "yay"], - "🙌": ["raising_hands", "gesture", "hooray", "yea", "celebration", "hands"], - "👐": ["open_hands", "fingers", "butterfly", "hands", "open"], - "🤲": ["palms_up_together", "hands", "gesture", "cupped", "prayer"], - "🤝": ["handshake", "agreement", "shake"], - "🙏": [ - "folded_hands", - "please", - "hope", - "wish", - "namaste", - "highfive", - "pray", - "thank you", - "thanks", - "appreciate" - ], - "✍️": ["writing_hand", "lower_left_ballpoint_pen", "stationery", "write", "compose"], - "💅": ["nail_polish", "nail_care", "beauty", "manicure", "finger", "fashion", "nail", "slay"], - "🤳": ["selfie", "camera", "phone"], - "💪": ["flexed_biceps", "arm", "flex", "hand", "summer", "strong", "biceps"], - "🦾": ["mechanical_arm", "accessibility"], - "🦿": ["mechanical_leg", "accessibility"], - "🦵": ["leg", "kick", "limb"], - "🦶": ["foot", "kick", "stomp"], - "👂": ["ear", "face", "hear", "sound", "listen"], - "🦻": ["ear_with_hearing_aid", "accessibility"], - "👃": ["nose", "smell", "sniff"], - "🧠": ["brain", "smart", "intelligent"], - "🦷": ["tooth", "teeth", "dentist"], - "🦴": ["bone", "skeleton"], - "👀": ["eyes", "look", "watch", "stalk", "peek", "see"], - "👁️": ["eye", "face", "look", "see", "watch", "stare"], - "👅": ["tongue", "mouth", "playful"], - "👄": ["mouth", "kiss"], - "👶": ["baby", "child", "boy", "girl", "toddler"], - "🧒": ["child", "gender-neutral", "young"], - "👦": ["boy", "man", "male", "guy", "teenager"], - "👧": ["girl", "female", "woman", "teenager"], - "🧑": ["person", "gender-neutral"], - "👱": ["person_blond_hair", "hairstyle"], - "👨": ["man", "mustache", "father", "dad", "guy", "classy", "sir", "moustache"], - "🧔": ["man_beard", "person", "bewhiskered"], - "👨‍🦰": ["man_red_hair", "hairstyle"], - "👨‍🦱": ["man_curly_hair", "hairstyle"], - "👨‍🦳": ["man_white_hair", "old", "elder"], - "👨‍🦲": ["man_bald", "hairless"], - "👩": ["woman", "female", "girls", "lady"], - "👩‍🦰": ["woman_red_hair", "hairstyle"], - "🧑‍🦰": ["person_red_hair", "hairstyle"], - "👩‍🦱": ["woman_curly_hair", "hairstyle"], - "🧑‍🦱": ["person_curly_hair", "hairstyle"], - "👩‍🦳": ["woman_white_hair", "old", "elder"], - "🧑‍🦳": ["person_white_hair", "elder", "old"], - "👩‍🦲": ["woman_bald", "hairless"], - "🧑‍🦲": ["person_bald", "hairless"], - "👱‍♀️": ["woman_blond_hair", "woman", "female", "girl", "blonde", "person"], - "👱‍♂️": ["man_blond_hair", "man", "male", "boy", "blonde", "guy", "person"], - "🧓": ["older_person", "human", "elder", "senior", "gender-neutral"], - "👴": ["old_man", "human", "male", "men", "old", "elder", "senior"], - "👵": ["old_woman", "human", "female", "women", "lady", "old", "elder", "senior"], - "🙍": ["person_frowning", "worried"], - "🙍‍♂️": ["man_frowning", "male", "boy", "man", "sad", "depressed", "discouraged", "unhappy"], - "🙍‍♀️": [ - "woman_frowning", - "female", - "girl", - "woman", - "sad", - "depressed", - "discouraged", - "unhappy" - ], - "🙎": ["person_pouting", "upset"], - "🙎‍♂️": ["man_pouting", "male", "boy", "man"], - "🙎‍♀️": ["woman_pouting", "female", "girl", "woman"], - "🙅": ["person_gesturing_no", "decline"], - "🙅‍♂️": ["man_gesturing_no", "male", "boy", "man", "nope"], - "🙅‍♀️": ["woman_gesturing_no", "female", "girl", "woman", "nope"], - "🙆": ["person_gesturing_ok", "agree"], - "🙆‍♂️": ["man_gesturing_ok", "men", "boy", "male", "blue", "human", "man"], - "🙆‍♀️": ["woman_gesturing_ok", "women", "girl", "female", "pink", "human", "woman"], - "💁": ["person_tipping_hand", "information"], - "💁‍♂️": ["man_tipping_hand", "male", "boy", "man", "human", "information"], - "💁‍♀️": ["woman_tipping_hand", "female", "girl", "woman", "human", "information"], - "🙋": ["person_raising_hand", "question"], - "🙋‍♂️": ["man_raising_hand", "male", "boy", "man"], - "🙋‍♀️": ["woman_raising_hand", "female", "girl", "woman"], - "🧏": ["deaf_person", "accessibility"], - "🧏‍♂️": ["deaf_man", "accessibility"], - "🧏‍♀️": ["deaf_woman", "accessibility"], - "🙇": ["person_bowing", "respectiful"], - "🙇‍♂️": ["man_bowing", "man", "male", "boy"], - "🙇‍♀️": ["woman_bowing", "woman", "female", "girl"], - "🤦": ["person_facepalming", "disappointed"], - "🤦‍♂️": ["man_facepalming", "man", "male", "boy", "disbelief"], - "🤦‍♀️": ["woman_facepalming", "woman", "female", "girl", "disbelief"], - "🤷": ["person_shrugging", "regardless"], - "🤷‍♂️": ["man_shrugging", "man", "male", "boy", "confused", "indifferent", "doubt"], - "🤷‍♀️": ["woman_shrugging", "woman", "female", "girl", "confused", "indifferent", "doubt"], - "🧑‍⚕️": ["health_worker", "hospital"], - "👨‍⚕️": ["man_health_worker", "doctor", "nurse", "therapist", "healthcare", "man", "human"], - "👩‍⚕️": ["woman_health_worker", "doctor", "nurse", "therapist", "healthcare", "woman", "human"], - "🧑‍🎓": ["student", "learn"], - "👨‍🎓": ["man_student", "graduate", "man", "human"], - "👩‍🎓": ["woman_student", "graduate", "woman", "human"], - "🧑‍🏫": ["teacher", "professor"], - "👨‍🏫": ["man_teacher", "instructor", "professor", "man", "human"], - "👩‍🏫": ["woman_teacher", "instructor", "professor", "woman", "human"], - "🧑‍⚖️": ["judge", "law"], - "👨‍⚖️": ["man_judge", "justice", "court", "man", "human"], - "👩‍⚖️": ["woman_judge", "justice", "court", "woman", "human"], - "🧑‍🌾": ["farmer", "crops"], - "👨‍🌾": ["man_farmer", "rancher", "gardener", "man", "human"], - "👩‍🌾": ["woman_farmer", "rancher", "gardener", "woman", "human"], - "🧑‍🍳": ["cook", "food", "kitchen", "culinary"], - "👨‍🍳": ["man_cook", "chef", "man", "human"], - "👩‍🍳": ["woman_cook", "chef", "woman", "human"], - "🧑‍🔧": ["mechanic", "worker", "technician"], - "👨‍🔧": ["man_mechanic", "plumber", "man", "human", "wrench"], - "👩‍🔧": ["woman_mechanic", "plumber", "woman", "human", "wrench"], - "🧑‍🏭": ["factory_worker", "labor"], - "👨‍🏭": ["man_factory_worker", "assembly", "industrial", "man", "human"], - "👩‍🏭": ["woman_factory_worker", "assembly", "industrial", "woman", "human"], - "🧑‍💼": ["office_worker", "business"], - "👨‍💼": ["man_office_worker", "business", "manager", "man", "human"], - "👩‍💼": ["woman_office_worker", "business", "manager", "woman", "human"], - "🧑‍🔬": ["scientist", "chemistry"], - "👨‍🔬": ["man_scientist", "biologist", "chemist", "engineer", "physicist", "man", "human"], - "👩‍🔬": ["woman_scientist", "biologist", "chemist", "engineer", "physicist", "woman", "human"], - "🧑‍💻": ["technologist", "computer"], - "👨‍💻": [ - "man_technologist", - "coder", - "developer", - "engineer", - "programmer", - "software", - "man", - "human", - "laptop", - "computer" - ], - "👩‍💻": [ - "woman_technologist", - "coder", - "developer", - "engineer", - "programmer", - "software", - "woman", - "human", - "laptop", - "computer" - ], - "🧑‍🎤": ["singer", "song", "artist", "performer"], - "👨‍🎤": ["man_singer", "rockstar", "entertainer", "man", "human"], - "👩‍🎤": ["woman_singer", "rockstar", "entertainer", "woman", "human"], - "🧑‍🎨": ["artist", "painting", "draw", "creativity"], - "👨‍🎨": ["man_artist", "painter", "man", "human"], - "👩‍🎨": ["woman_artist", "painter", "woman", "human"], - "🧑‍✈️": ["pilot", "fly", "plane", "airplane"], - "👨‍✈️": ["man_pilot", "aviator", "plane", "man", "human"], - "👩‍✈️": ["woman_pilot", "aviator", "plane", "woman", "human"], - "🧑‍🚀": ["astronaut", "outerspace"], - "👨‍🚀": ["man_astronaut", "space", "rocket", "man", "human"], - "👩‍🚀": ["woman_astronaut", "space", "rocket", "woman", "human"], - "🧑‍🚒": ["firefighter", "fire"], - "👨‍🚒": ["man_firefighter", "fireman", "man", "human"], - "👩‍🚒": ["woman_firefighter", "fireman", "woman", "human"], - "👮": ["police_officer", "cop"], - "👮‍♂️": ["man_police_officer", "man", "police", "law", "legal", "enforcement", "arrest", "911"], - "👮‍♀️": [ - "woman_police_officer", - "woman", - "police", - "law", - "legal", - "enforcement", - "arrest", - "911", - "female" - ], - "🕵️": ["detective", "human", "spy"], - "🕵️‍♂️": ["man_detective", "crime"], - "🕵️‍♀️": ["woman_detective", "human", "spy", "detective", "female", "woman"], - "💂": ["guard", "protect"], - "💂‍♂️": ["man_guard", "uk", "gb", "british", "male", "guy", "royal"], - "💂‍♀️": ["woman_guard", "uk", "gb", "british", "female", "royal", "woman"], - "👷": ["construction_worker", "labor", "build"], - "👷‍♂️": [ - "man_construction_worker", - "male", - "human", - "wip", - "guy", - "build", - "construction", - "worker", - "labor" - ], - "👷‍♀️": [ - "woman_construction_worker", - "female", - "human", - "wip", - "build", - "construction", - "worker", - "labor", - "woman" - ], - "🤴": ["prince", "boy", "man", "male", "crown", "royal", "king"], - "👸": ["princess", "girl", "woman", "female", "blond", "crown", "royal", "queen"], - "👳": ["person_wearing_turban", "headdress"], - "👳‍♂️": ["man_wearing_turban", "male", "indian", "hinduism", "arabs"], - "👳‍♀️": ["woman_wearing_turban", "female", "indian", "hinduism", "arabs", "woman"], - "👲": ["man_with_skullcap", "male", "boy", "chinese"], - "🧕": ["woman_with_headscarf", "female", "hijab", "mantilla", "tichel"], - "🤵": ["man_in_tuxedo", "couple", "marriage", "wedding", "groom"], - "👰": ["bride_with_veil", "couple", "marriage", "wedding", "woman", "bride"], - "🤰": ["pregnant_woman", "baby"], - "🤱": ["breast_feeding", "nursing", "baby"], - "👼": ["baby_angel", "heaven", "wings", "halo"], - "🎅": ["santa_claus", "festival", "man", "male", "xmas", "father christmas"], - "🤶": ["mrs_claus", "woman", "female", "xmas", "mother christmas"], - "🦸": ["superhero", "marvel"], - "🦸‍♂️": ["man_superhero", "man", "male", "good", "hero", "superpowers"], - "🦸‍♀️": ["woman_superhero", "woman", "female", "good", "heroine", "superpowers"], - "🦹": ["supervillain", "marvel"], - "🦹‍♂️": ["man_supervillain", "man", "male", "evil", "bad", "criminal", "hero", "superpowers"], - "🦹‍♀️": [ - "woman_supervillain", - "woman", - "female", - "evil", - "bad", - "criminal", - "heroine", - "superpowers" - ], - "🧙": ["mage", "magic"], - "🧙‍♂️": ["man_mage", "man", "male", "mage", "sorcerer"], - "🧙‍♀️": ["woman_mage", "woman", "female", "mage", "witch"], - "🧚": ["fairy", "wings", "magical"], - "🧚‍♂️": ["man_fairy", "man", "male"], - "🧚‍♀️": ["woman_fairy", "woman", "female"], - "🧛": ["vampire", "blood", "twilight"], - "🧛‍♂️": ["man_vampire", "man", "male", "dracula"], - "🧛‍♀️": ["woman_vampire", "woman", "female"], - "🧜": ["merperson", "sea"], - "🧜‍♂️": ["merman", "man", "male", "triton"], - "🧜‍♀️": ["mermaid", "woman", "female", "merwoman", "ariel"], - "🧝": ["elf", "magical"], - "🧝‍♂️": ["man_elf", "man", "male"], - "🧝‍♀️": ["woman_elf", "woman", "female"], - "🧞": ["genie", "magical", "wishes"], - "🧞‍♂️": ["man_genie", "man", "male"], - "🧞‍♀️": ["woman_genie", "woman", "female"], - "🧟": ["zombie", "dead"], - "🧟‍♂️": ["man_zombie", "man", "male", "dracula", "undead", "walking dead"], - "🧟‍♀️": ["woman_zombie", "woman", "female", "undead", "walking dead"], - "💆": ["person_getting_massage", "relax"], - "💆‍♂️": ["man_getting_massage", "male", "boy", "man", "head"], - "💆‍♀️": ["woman_getting_massage", "female", "girl", "woman", "head"], - "💇": ["person_getting_haircut", "hairstyle"], - "💇‍♂️": ["man_getting_haircut", "male", "boy", "man"], - "💇‍♀️": ["woman_getting_haircut", "female", "girl", "woman"], - "🚶": ["person_walking", "move"], - "🚶‍♂️": ["man_walking", "human", "feet", "steps"], - "🚶‍♀️": ["woman_walking", "human", "feet", "steps", "woman", "female"], - "🧍": ["person_standing", "still"], - "🧍‍♂️": ["man_standing", "still"], - "🧍‍♀️": ["woman_standing", "still"], - "🧎": ["person_kneeling", "pray", "respectful"], - "🧎‍♂️": ["man_kneeling", "pray", "respectful"], - "🧎‍♀️": ["woman_kneeling", "respectful", "pray"], - "🧑‍🦯": ["person_with_probing_cane", "blind"], - "👨‍🦯": ["man_with_probing_cane", "blind"], - "👩‍🦯": ["woman_with_probing_cane", "blind"], - "🧑‍🦼": ["person_in_motorized_wheelchair", "disability", "accessibility"], - "👨‍🦼": ["man_in_motorized_wheelchair", "disability", "accessibility"], - "👩‍🦼": ["woman_in_motorized_wheelchair", "disability", "accessibility"], - "🧑‍🦽": ["person_in_manual_wheelchair", "disability", "accessibility"], - "👨‍🦽": ["man_in_manual_wheelchair", "disability", "accessibility"], - "👩‍🦽": ["woman_in_manual_wheelchair", "disability", "accessibility"], - "🏃": ["person_running", "move"], - "🏃‍♂️": ["man_running", "man", "walking", "exercise", "race", "running"], - "🏃‍♀️": ["woman_running", "woman", "walking", "exercise", "race", "running", "female"], - "💃": ["woman_dancing", "female", "girl", "woman", "fun"], - "🕺": ["man_dancing", "male", "boy", "fun", "dancer"], - "🕴️": ["man_in_suit_levitating", "suit", "business", "levitate", "hover", "jump"], - "👯": ["people_with_bunny_ears", "perform", "costume"], - "👯‍♂️": ["men_with_bunny_ears", "male", "bunny", "men", "boys"], - "👯‍♀️": ["women_with_bunny_ears", "female", "bunny", "women", "girls"], - "🧖": ["person_in_steamy_room", "relax", "spa"], - "🧖‍♂️": ["man_in_steamy_room", "male", "man", "spa", "steamroom", "sauna"], - "🧖‍♀️": ["woman_in_steamy_room", "female", "woman", "spa", "steamroom", "sauna"], - "🧗": ["person_climbing", "sport"], - "🧗‍♂️": ["man_climbing", "sports", "hobby", "man", "male", "rock"], - "🧗‍♀️": ["woman_climbing", "sports", "hobby", "woman", "female", "rock"], - "🤺": ["person_fencing", "sports", "fencing", "sword"], - "🏇": ["horse_racing", "animal", "betting", "competition", "gambling", "luck"], - "⛷️": ["skier", "sports", "winter", "snow"], - "🏂": ["snowboarder", "sports", "winter"], - "🏌️": ["person_golfing", "sports", "business"], - "🏌️‍♂️": ["man_golfing", "sport"], - "🏌️‍♀️": ["woman_golfing", "sports", "business", "woman", "female"], - "🏄": ["person_surfing", "sport", "sea"], - "🏄‍♂️": ["man_surfing", "sports", "ocean", "sea", "summer", "beach"], - "🏄‍♀️": ["woman_surfing", "sports", "ocean", "sea", "summer", "beach", "woman", "female"], - "🚣": ["person_rowing_boat", "sport", "move"], - "🚣‍♂️": ["man_rowing_boat", "sports", "hobby", "water", "ship"], - "🚣‍♀️": ["woman_rowing_boat", "sports", "hobby", "water", "ship", "woman", "female"], - "🏊": ["person_swimming", "sport", "pool"], - "🏊‍♂️": ["man_swimming", "sports", "exercise", "human", "athlete", "water", "summer"], - "🏊‍♀️": [ - "woman_swimming", - "sports", - "exercise", - "human", - "athlete", - "water", - "summer", - "woman", - "female" - ], - "⛹️": ["person_bouncing_ball", "sports", "human"], - "⛹️‍♂️": ["man_bouncing_ball", "sport"], - "⛹️‍♀️": ["woman_bouncing_ball", "sports", "human", "woman", "female"], - "🏋️": ["person_lifting_weights", "sports", "training", "exercise"], - "🏋️‍♂️": ["man_lifting_weights", "sport"], - "🏋️‍♀️": ["woman_lifting_weights", "sports", "training", "exercise", "woman", "female"], - "🚴": ["person_biking", "bicycle", "bike", "cyclist", "sport", "move"], - "🚴‍♂️": ["man_biking", "bicycle", "bike", "cyclist", "sports", "exercise", "hipster"], - "🚴‍♀️": [ - "woman_biking", - "bicycle", - "bike", - "cyclist", - "sports", - "exercise", - "hipster", - "woman", - "female" - ], - "🚵": ["person_mountain_biking", "bicycle", "bike", "cyclist", "sport", "move"], - "🚵‍♂️": [ - "man_mountain_biking", - "bicycle", - "bike", - "cyclist", - "transportation", - "sports", - "human", - "race" - ], - "🚵‍♀️": [ - "woman_mountain_biking", - "bicycle", - "bike", - "cyclist", - "transportation", - "sports", - "human", - "race", - "woman", - "female" - ], - "🤸": ["person_cartwheeling", "sport", "gymnastic"], - "🤸‍♂️": ["man_cartwheeling", "gymnastics"], - "🤸‍♀️": ["woman_cartwheeling", "gymnastics"], - "🤼": ["people_wrestling", "sport"], - "🤼‍♂️": ["men_wrestling", "sports", "wrestlers"], - "🤼‍♀️": ["women_wrestling", "sports", "wrestlers"], - "🤽": ["person_playing_water_polo", "sport"], - "🤽‍♂️": ["man_playing_water_polo", "sports", "pool"], - "🤽‍♀️": ["woman_playing_water_polo", "sports", "pool"], - "🤾": ["person_playing_handball", "sport"], - "🤾‍♂️": ["man_playing_handball", "sports"], - "🤾‍♀️": ["woman_playing_handball", "sports"], - "🤹": ["person_juggling", "performance", "balance"], - "🤹‍♂️": ["man_juggling", "juggle", "balance", "skill", "multitask"], - "🤹‍♀️": ["woman_juggling", "juggle", "balance", "skill", "multitask"], - "🧘": ["person_in_lotus_position", "meditate"], - "🧘‍♂️": [ - "man_in_lotus_position", - "man", - "male", - "meditation", - "yoga", - "serenity", - "zen", - "mindfulness" - ], - "🧘‍♀️": [ - "woman_in_lotus_position", - "woman", - "female", - "meditation", - "yoga", - "serenity", - "zen", - "mindfulness" - ], - "🛀": ["person_taking_bath", "clean", "shower", "bathroom"], - "🛌": ["person_in_bed", "bed", "rest"], - "🧑‍🤝‍🧑": ["people_holding_hands", "friendship"], - "👭": [ - "women_holding_hands", - "pair", - "friendship", - "couple", - "love", - "like", - "female", - "people", - "human" - ], - "👫": [ - "woman_and_man_holding_hands", - "pair", - "people", - "human", - "love", - "date", - "dating", - "like", - "affection", - "valentines", - "marriage" - ], - "👬": [ - "men_holding_hands", - "pair", - "couple", - "love", - "like", - "bromance", - "friendship", - "people", - "human" - ], - "💏": ["kiss", "pair", "valentines", "love", "like", "dating", "marriage"], - "👩‍❤️‍💋‍👨": ["kiss_woman_man", "love"], - "👨‍❤️‍💋‍👨": ["kiss_man_man", "pair", "valentines", "love", "like", "dating", "marriage"], - "👩‍❤️‍💋‍👩": ["kiss_woman_woman", "pair", "valentines", "love", "like", "dating", "marriage"], - "💑": [ - "couple_with_heart", - "pair", - "love", - "like", - "affection", - "human", - "dating", - "valentines", - "marriage" - ], - "👩‍❤️‍👨": ["couple_with_heart_woman_man", "love"], - "👨‍❤️‍👨": [ - "couple_with_heart_man_man", - "pair", - "love", - "like", - "affection", - "human", - "dating", - "valentines", - "marriage" - ], - "👩‍❤️‍👩": [ - "couple_with_heart_woman_woman", - "pair", - "love", - "like", - "affection", - "human", - "dating", - "valentines", - "marriage" - ], - "👪": ["family", "home", "parents", "child", "mom", "dad", "father", "mother", "people", "human"], - "👨‍👩‍👦": ["family_man_woman_boy", "love"], - "👨‍👩‍👧": ["family_man_woman_girl", "home", "parents", "people", "human", "child"], - "👨‍👩‍👧‍👦": ["family_man_woman_girl_boy", "home", "parents", "people", "human", "children"], - "👨‍👩‍👦‍👦": ["family_man_woman_boy_boy", "home", "parents", "people", "human", "children"], - "👨‍👩‍👧‍👧": ["family_man_woman_girl_girl", "home", "parents", "people", "human", "children"], - "👨‍👨‍👦": ["family_man_man_boy", "home", "parents", "people", "human", "children"], - "👨‍👨‍👧": ["family_man_man_girl", "home", "parents", "people", "human", "children"], - "👨‍👨‍👧‍👦": ["family_man_man_girl_boy", "home", "parents", "people", "human", "children"], - "👨‍👨‍👦‍👦": ["family_man_man_boy_boy", "home", "parents", "people", "human", "children"], - "👨‍👨‍👧‍👧": ["family_man_man_girl_girl", "home", "parents", "people", "human", "children"], - "👩‍👩‍👦": ["family_woman_woman_boy", "home", "parents", "people", "human", "children"], - "👩‍👩‍👧": ["family_woman_woman_girl", "home", "parents", "people", "human", "children"], - "👩‍👩‍👧‍👦": ["family_woman_woman_girl_boy", "home", "parents", "people", "human", "children"], - "👩‍👩‍👦‍👦": ["family_woman_woman_boy_boy", "home", "parents", "people", "human", "children"], - "👩‍👩‍👧‍👧": ["family_woman_woman_girl_girl", "home", "parents", "people", "human", "children"], - "👨‍👦": ["family_man_boy", "home", "parent", "people", "human", "child"], - "👨‍👦‍👦": ["family_man_boy_boy", "home", "parent", "people", "human", "children"], - "👨‍👧": ["family_man_girl", "home", "parent", "people", "human", "child"], - "👨‍👧‍👦": ["family_man_girl_boy", "home", "parent", "people", "human", "children"], - "👨‍👧‍👧": ["family_man_girl_girl", "home", "parent", "people", "human", "children"], - "👩‍👦": ["family_woman_boy", "home", "parent", "people", "human", "child"], - "👩‍👦‍👦": ["family_woman_boy_boy", "home", "parent", "people", "human", "children"], - "👩‍👧": ["family_woman_girl", "home", "parent", "people", "human", "child"], - "👩‍👧‍👦": ["family_woman_girl_boy", "home", "parent", "people", "human", "children"], - "👩‍👧‍👧": ["family_woman_girl_girl", "home", "parent", "people", "human", "children"], - "🗣️": ["speaking_head", "user", "person", "human", "sing", "say", "talk"], - "👤": ["bust_in_silhouette", "user", "person", "human"], - "👥": ["busts_in_silhouette", "user", "person", "human", "group", "team"], - "👣": ["footprints", "feet", "tracking", "walking", "beach"], - "🐵": ["monkey_face", "animal", "nature", "circus"], - "🐒": ["monkey", "animal", "nature", "banana", "circus"], - "🦍": ["gorilla", "animal", "nature", "circus"], - "🦧": ["orangutan", "animal"], - "🐶": ["dog_face", "animal", "friend", "nature", "woof", "puppy", "pet", "faithful"], - "🐕": ["dog", "animal", "nature", "friend", "doge", "pet", "faithful"], - "🦮": ["guide_dog", "animal", "blind"], - "🐕‍🦺": ["service_dog", "blind", "animal"], - "🐩": ["poodle", "dog", "animal", "101", "nature", "pet"], - "🐺": ["wolf", "animal", "nature", "wild"], - "🦊": ["fox", "animal", "nature", "face"], - "🦝": ["raccoon", "animal", "nature"], - "🐱": ["cat_face", "animal", "meow", "nature", "pet", "kitten"], - "🐈": ["cat", "animal", "meow", "pet", "cats"], - "🦁": ["lion", "animal", "nature"], - "🐯": ["tiger_face", "animal", "cat", "danger", "wild", "nature", "roar"], - "🐅": ["tiger", "animal", "nature", "roar"], - "🐆": ["leopard", "animal", "nature"], - "🐴": ["horse_face", "animal", "brown", "nature"], - "🐎": ["horse", "animal", "gamble", "luck"], - "🦄": ["unicorn", "animal", "nature", "mystical"], - "🦓": ["zebra", "animal", "nature", "stripes", "safari"], - "🦌": ["deer", "animal", "nature", "horns", "venison"], - "🐮": ["cow_face", "beef", "ox", "animal", "nature", "moo", "milk"], - "🐂": ["ox", "animal", "cow", "beef"], - "🐃": ["water_buffalo", "animal", "nature", "ox", "cow"], - "🐄": ["cow", "beef", "ox", "animal", "nature", "moo", "milk"], - "🐷": ["pig_face", "animal", "oink", "nature"], - "🐖": ["pig", "animal", "nature"], - "🐗": ["boar", "animal", "nature"], - "🐽": ["pig_nose", "animal", "oink"], - "🐏": ["ram", "animal", "sheep", "nature"], - "🐑": ["ewe", "animal", "nature", "wool", "shipit"], - "🐐": ["goat", "animal", "nature"], - "🐪": ["camel", "animal", "hot", "desert", "hump"], - "🐫": ["two_hump_camel", "animal", "nature", "hot", "desert", "hump"], - "🦙": ["llama", "animal", "nature", "alpaca"], - "🦒": ["giraffe", "animal", "nature", "spots", "safari"], - "🐘": ["elephant", "animal", "nature", "nose", "th", "circus"], - "🦏": ["rhinoceros", "animal", "nature", "horn"], - "🦛": ["hippopotamus", "animal", "nature"], - "🐭": ["mouse_face", "animal", "nature", "cheese_wedge", "rodent"], - "🐁": ["mouse", "animal", "nature", "rodent"], - "🐀": ["rat", "animal", "mouse", "rodent"], - "🐹": ["hamster", "animal", "nature"], - "🐰": ["rabbit_face", "animal", "nature", "pet", "spring", "magic", "bunny"], - "🐇": ["rabbit", "animal", "nature", "pet", "magic", "spring"], - "🐿️": ["chipmunk", "animal", "nature", "rodent", "squirrel"], - "🦔": ["hedgehog", "animal", "nature", "spiny"], - "🦇": ["bat", "animal", "nature", "blind", "vampire"], - "🐻": ["bear", "animal", "nature", "wild"], - "🐨": ["koala", "animal", "nature"], - "🐼": ["panda", "animal", "nature"], - "🦥": ["sloth", "animal"], - "🦦": ["otter", "animal"], - "🦨": ["skunk", "animal"], - "🦘": ["kangaroo", "animal", "nature", "australia", "joey", "hop", "marsupial"], - "🦡": ["badger", "animal", "nature", "honey"], - "🐾": ["paw_prints", "animal", "tracking", "footprints", "dog", "cat", "pet", "feet"], - "🦃": ["turkey", "animal", "bird"], - "🐔": ["chicken", "animal", "cluck", "nature", "bird"], - "🐓": ["rooster", "animal", "nature", "chicken"], - "🐣": ["hatching_chick", "animal", "chicken", "egg", "born", "baby", "bird"], - "🐤": ["baby_chick", "animal", "chicken", "bird"], - "🐥": ["front_facing_baby_chick", "animal", "chicken", "baby", "bird"], - "🐦": ["bird", "animal", "nature", "fly", "tweet", "spring"], - "🐧": ["penguin", "animal", "nature"], - "🕊️": ["dove", "animal", "bird"], - "🦅": ["eagle", "animal", "nature", "bird"], - "🦆": ["duck", "animal", "nature", "bird", "mallard"], - "🦢": ["swan", "animal", "nature", "bird"], - "🦉": ["owl", "animal", "nature", "bird", "hoot"], - "🦩": ["flamingo", "animal"], - "🦚": ["peacock", "animal", "nature", "peahen", "bird"], - "🦜": ["parrot", "animal", "nature", "bird", "pirate", "talk"], - "🐸": ["frog", "animal", "nature", "croak", "toad"], - "🐊": ["crocodile", "animal", "nature", "reptile", "lizard", "alligator"], - "🐢": ["turtle", "animal", "slow", "nature", "tortoise"], - "🦎": ["lizard", "animal", "nature", "reptile"], - "🐍": ["snake", "animal", "evil", "nature", "hiss", "python"], - "🐲": ["dragon_face", "animal", "myth", "nature", "chinese", "green"], - "🐉": ["dragon", "animal", "myth", "nature", "chinese", "green"], - "🦕": [ - "sauropod", - "animal", - "nature", - "dinosaur", - "brachiosaurus", - "brontosaurus", - "diplodocus", - "extinct" - ], - "🦖": ["t_rex", "animal", "nature", "dinosaur", "tyrannosaurus", "extinct"], - "🐳": ["spouting_whale", "animal", "nature", "sea", "ocean"], - "🐋": ["whale", "animal", "nature", "sea", "ocean"], - "🐬": ["dolphin", "animal", "nature", "fish", "sea", "ocean", "flipper", "fins", "beach"], - "🐟": ["fish", "animal", "food", "nature"], - "🐠": ["tropical_fish", "animal", "swim", "ocean", "beach", "nemo"], - "🐡": ["blowfish", "animal", "nature", "food", "sea", "ocean"], - "🦈": ["shark", "animal", "nature", "fish", "sea", "ocean", "jaws", "fins", "beach"], - "🐙": ["octopus", "animal", "creature", "ocean", "sea", "nature", "beach"], - "🐚": ["spiral_shell", "nature", "sea", "beach"], - "🐌": ["snail", "slow", "animal", "shell"], - "🦋": ["butterfly", "animal", "insect", "nature", "caterpillar"], - "🐛": ["bug", "animal", "insect", "nature", "worm"], - "🐜": ["ant", "animal", "insect", "nature", "bug"], - "🐝": ["honeybee", "animal", "insect", "nature", "bug", "spring", "honey"], - "🐞": ["lady_beetle", "animal", "insect", "nature", "ladybug"], - "🦗": ["cricket", "animal", "chirp"], - "🕷️": ["spider", "animal", "arachnid"], - "🕸️": ["spider_web", "animal", "insect", "arachnid", "silk"], - "🦂": ["scorpion", "animal", "arachnid"], - "🦟": ["mosquito", "animal", "nature", "insect", "malaria"], - "🦠": ["microbe", "amoeba", "bacteria", "germs", "virus", "covid"], - "💐": ["bouquet", "flowers", "nature", "spring"], - "🌸": ["cherry_blossom", "nature", "plant", "spring", "flower"], - "💮": ["white_flower", "japanese", "spring"], - "🏵️": ["rosette", "flower", "decoration", "military"], - "🌹": ["rose", "flowers", "valentines", "love", "spring"], - "🥀": ["wilted_flower", "plant", "nature", "flower", "rose"], - "🌺": ["hibiscus", "plant", "vegetable", "flowers", "beach"], - "🌻": ["sunflower", "nature", "plant", "fall"], - "🌼": ["blossom", "nature", "flowers", "yellow"], - "🌷": ["tulip", "flowers", "plant", "nature", "summer", "spring"], - "🌱": ["seedling", "plant", "nature", "grass", "lawn", "spring"], - "🌲": ["evergreen_tree", "plant", "nature"], - "🌳": ["deciduous_tree", "plant", "nature"], - "🌴": ["palm_tree", "plant", "vegetable", "nature", "summer", "beach", "mojito", "tropical"], - "🌵": ["cactus", "vegetable", "plant", "nature"], - "🌾": ["sheaf_of_rice", "nature", "plant"], - "🌿": ["herb", "vegetable", "plant", "medicine", "weed", "grass", "lawn"], - "☘️": ["shamrock", "vegetable", "plant", "nature", "irish", "clover"], - "🍀": ["four_leaf_clover", "vegetable", "plant", "nature", "lucky", "irish"], - "🍁": ["maple_leaf", "nature", "plant", "vegetable", "ca", "fall"], - "🍂": ["fallen_leaf", "nature", "plant", "vegetable", "leaves"], - "🍃": [ - "leaf_fluttering_in_wind", - "nature", - "plant", - "tree", - "vegetable", - "grass", - "lawn", - "spring" - ], - "🍇": ["grapes", "fruit", "food", "wine"], - "🍈": ["melon", "fruit", "nature", "food"], - "🍉": ["watermelon", "fruit", "food", "picnic", "summer"], - "🍊": ["tangerine", "food", "fruit", "nature", "orange"], - "🍋": ["lemon", "fruit", "nature"], - "🍌": ["banana", "fruit", "food", "monkey"], - "🍍": ["pineapple", "fruit", "nature", "food"], - "🥭": ["mango", "fruit", "food", "tropical"], - "🍎": ["red_apple", "fruit", "mac", "school"], - "🍏": ["green_apple", "fruit", "nature"], - "🍐": ["pear", "fruit", "nature", "food"], - "🍑": ["peach", "fruit", "nature", "food"], - "🍒": ["cherries", "food", "fruit"], - "🍓": ["strawberry", "fruit", "food", "nature"], - "🥝": ["kiwi_fruit", "fruit", "food"], - "🍅": ["tomato", "fruit", "vegetable", "nature", "food"], - "🥥": ["coconut", "fruit", "nature", "food", "palm"], - "🥑": ["avocado", "fruit", "food"], - "🍆": ["eggplant", "vegetable", "nature", "food", "aubergine"], - "🥔": ["potato", "food", "tuber", "vegatable", "starch"], - "🥕": ["carrot", "vegetable", "food", "orange"], - "🌽": ["ear_of_corn", "food", "vegetable", "plant"], - "🌶️": ["hot_pepper", "food", "spicy", "chilli", "chili"], - "🥒": ["cucumber", "fruit", "food", "pickle"], - "🥬": ["leafy_green", "food", "vegetable", "plant", "bok choy", "cabbage", "kale", "lettuce"], - "🥦": ["broccoli", "fruit", "food", "vegetable"], - "🧄": ["garlic", "food", "spice", "cook"], - "🧅": ["onion", "cook", "food", "spice"], - "🍄": ["mushroom", "plant", "vegetable"], - "🥜": ["peanuts", "food", "nut"], - "🌰": ["chestnut", "food", "squirrel"], - "🍞": ["bread", "food", "wheat", "breakfast", "toast"], - "🥐": ["croissant", "food", "bread", "french"], - "🥖": ["baguette_bread", "food", "bread", "french", "france", "bakery"], - "🥨": ["pretzel", "food", "bread", "twisted", "germany", "bakery"], - "🥯": ["bagel", "food", "bread", "bakery", "schmear", "jewish_bakery"], - "🥞": ["pancakes", "food", "breakfast", "flapjacks", "hotcakes", "brunch"], - "🧇": ["waffle", "food", "breakfast", "brunch"], - "🧀": ["cheese_wedge", "food", "chadder", "swiss"], - "🍖": ["meat_on_bone", "good", "food", "drumstick"], - "🍗": ["poultry_leg", "food", "meat", "drumstick", "bird", "chicken", "turkey"], - "🥩": ["cut_of_meat", "food", "cow", "meat", "cut", "chop", "lambchop", "porkchop"], - "🥓": ["bacon", "food", "breakfast", "pork", "pig", "meat", "brunch"], - "🍔": ["hamburger", "meat", "fast food", "beef", "cheeseburger", "mcdonalds", "burger king"], - "🍟": ["french_fries", "chips", "snack", "fast food", "potato"], - "🍕": ["pizza", "food", "party", "italy"], - "🌭": ["hot_dog", "food", "frankfurter", "america"], - "🥪": ["sandwich", "food", "lunch", "bread", "toast", "bakery"], - "🌮": ["taco", "food", "mexican"], - "🌯": ["burrito", "food", "mexican"], - "🥙": ["stuffed_flatbread", "food", "flatbread", "stuffed", "gyro", "mediterranean"], - "🧆": ["falafel", "food", "mediterranean"], - "🥚": ["egg", "food", "chicken", "breakfast"], - "🍳": ["cooking", "food", "breakfast", "kitchen", "egg", "skillet"], - "🥘": ["shallow_pan_of_food", "food", "cooking", "casserole", "paella", "skillet"], - "🍲": ["pot_of_food", "food", "meat", "soup", "hot pot"], - "🥣": ["bowl_with_spoon", "food", "breakfast", "cereal", "oatmeal", "porridge"], - "🥗": ["green_salad", "food", "healthy", "lettuce", "vegetable"], - "🍿": ["popcorn", "food", "movie theater", "films", "snack", "drama"], - "🧈": ["butter", "food", "cook"], - "🧂": ["salt", "condiment", "shaker"], - "🥫": ["canned_food", "food", "soup", "tomatoes"], - "🍱": ["bento_box", "food", "japanese", "box", "lunch"], - "🍘": ["rice_cracker", "food", "japanese", "snack", "senbei"], - "🍙": ["rice_ball", "food", "japanese", "onigiri", "omusubi"], - "🍚": ["cooked_rice", "food", "asian"], - "🍛": ["curry_rice", "food", "spicy", "hot", "indian"], - "🍜": ["steaming_bowl", "food", "japanese", "noodle", "chopsticks", "ramen"], - "🍝": ["spaghetti", "food", "italian", "pasta", "noodle"], - "🍠": ["roasted_sweet_potato", "food", "nature", "plant"], - "🍢": ["oden", "skewer", "food", "japanese"], - "🍣": ["sushi", "food", "fish", "japanese", "rice"], - "🍤": ["fried_shrimp", "food", "animal", "appetizer", "summer"], - "🍥": [ - "fish_cake_with_swirl", - "food", - "japan", - "sea", - "beach", - "narutomaki", - "pink", - "swirl", - "kamaboko", - "surimi", - "ramen" - ], - "🥮": ["moon_cake", "food", "autumn", "dessert"], - "🍡": ["dango", "food", "dessert", "sweet", "japanese", "barbecue", "meat"], - "🥟": ["dumpling", "food", "empanada", "pierogi", "potsticker", "gyoza"], - "🥠": ["fortune_cookie", "food", "prophecy", "dessert"], - "🥡": ["takeout_box", "food", "leftovers"], - "🦀": ["crab", "animal", "crustacean"], - "🦞": ["lobster", "animal", "nature", "bisque", "claws", "seafood"], - "🦐": ["shrimp", "animal", "ocean", "nature", "seafood"], - "🦑": ["squid", "animal", "nature", "ocean", "sea"], - "🦪": ["oyster", "food"], - "🍦": ["soft_ice_cream", "food", "hot", "dessert", "summer"], - "🍧": ["shaved_ice", "hot", "dessert", "summer"], - "🍨": ["ice_cream", "food", "hot", "dessert"], - "🍩": ["doughnut", "food", "dessert", "snack", "sweet", "donut"], - "🍪": ["cookie", "food", "snack", "oreo", "chocolate", "sweet", "dessert"], - "🎂": ["birthday_cake", "food", "dessert", "cake"], - "🍰": ["shortcake", "food", "dessert"], - "🧁": ["cupcake", "food", "dessert", "bakery", "sweet"], - "🥧": ["pie", "food", "dessert", "pastry"], - "🍫": ["chocolate_bar", "food", "snack", "dessert", "sweet"], - "🍬": ["candy", "snack", "dessert", "sweet", "lolly"], - "🍭": ["lollipop", "food", "snack", "candy", "sweet"], - "🍮": ["custard", "dessert", "food", "pudding", "flan"], - "🍯": ["honey_pot", "bees", "sweet", "kitchen"], - "🍼": ["baby_bottle", "food", "container", "milk"], - "🥛": ["glass_of_milk", "beverage", "drink", "cow"], - "☕": ["hot_beverage", "beverage", "caffeine", "latte", "espresso", "coffee", "mug"], - "🍵": ["teacup_without_handle", "drink", "bowl", "breakfast", "green", "british"], - "🍶": ["sake", "wine", "drink", "drunk", "beverage", "japanese", "alcohol", "booze"], - "🍾": ["bottle_with_popping_cork", "drink", "wine", "bottle", "celebration"], - "🍷": ["wine_glass", "drink", "beverage", "drunk", "alcohol", "booze"], - "🍸": ["cocktail_glass", "drink", "drunk", "alcohol", "beverage", "booze", "mojito"], - "🍹": ["tropical_drink", "beverage", "cocktail", "summer", "beach", "alcohol", "booze", "mojito"], - "🍺": [ - "beer_mug", - "relax", - "beverage", - "drink", - "drunk", - "party", - "pub", - "summer", - "alcohol", - "booze" - ], - "🍻": [ - "clinking_beer_mugs", - "relax", - "beverage", - "drink", - "drunk", - "party", - "pub", - "summer", - "alcohol", - "booze" - ], - "🥂": [ - "clinking_glasses", - "beverage", - "drink", - "party", - "alcohol", - "celebrate", - "cheers", - "wine", - "champagne", - "toast" - ], - "🥃": [ - "tumbler_glass", - "drink", - "beverage", - "drunk", - "alcohol", - "liquor", - "booze", - "bourbon", - "scotch", - "whisky", - "glass", - "shot" - ], - "🥤": ["cup_with_straw", "drink", "soda"], - "🧃": ["beverage_box", "drink"], - "🧉": ["mate", "drink", "tea", "beverage"], - "🧊": ["ice", "water", "cold"], - "🥢": ["chopsticks", "food"], - "🍽️": ["fork_and_knife_with_plate", "food", "eat", "meal", "lunch", "dinner", "restaurant"], - "🍴": ["fork_and_knife", "cutlery", "kitchen"], - "🥄": ["spoon", "cutlery", "kitchen", "tableware"], - "🔪": ["kitchen_knife", "knife", "blade", "cutlery", "kitchen", "weapon"], - "🏺": ["amphora", "vase", "jar"], - "🌍": ["globe_showing_europe_africa", "globe", "world", "earth", "international"], - "🌎": ["globe_showing_americas", "globe", "world", "USA", "earth", "international"], - "🌏": ["globe_showing_asia_australia", "globe", "world", "east", "earth", "international"], - "🌐": ["globe_with_meridians", "earth", "international", "world", "internet", "interweb", "i18n"], - "🗺️": ["world_map", "location", "direction"], - "🗾": ["map_of_japan", "nation", "country", "japanese", "asia"], - "🧭": ["compass", "magnetic", "navigation", "orienteering"], - "🏔️": ["snow_capped_mountain", "photo", "nature", "environment", "winter", "cold"], - "⛰️": ["mountain", "photo", "nature", "environment"], - "🌋": ["volcano", "photo", "nature", "disaster"], - "🗻": ["mount_fuji", "photo", "mountain", "nature", "japanese"], - "🏕️": ["camping", "photo", "outdoors", "tent"], - "🏖️": ["beach_with_umbrella", "weather", "summer", "sunny", "sand", "mojito"], - "🏜️": ["desert", "photo", "warm", "saharah"], - "🏝️": ["desert_island", "photo", "tropical", "mojito"], - "🏞️": ["national_park", "photo", "environment", "nature"], - "🏟️": ["stadium", "photo", "place", "sports", "concert", "venue"], - "🏛️": ["classical_building", "art", "culture", "history"], - "🏗️": ["building_construction", "wip", "working", "progress"], - "🧱": ["brick", "bricks"], - "🏘️": ["houses", "buildings", "photo"], - "🏚️": ["derelict_house", "abandon", "evict", "broken", "building"], - "🏠": ["house", "building", "home"], - "🏡": ["house_with_garden", "home", "plant", "nature"], - "🏢": ["office_building", "building", "bureau", "work"], - "🏣": ["japanese_post_office", "building", "envelope", "communication"], - "🏤": ["post_office", "building", "email"], - "🏥": ["hospital", "building", "health", "surgery", "doctor"], - "🏦": ["bank", "building", "money", "sales", "cash", "business", "enterprise"], - "🏨": ["hotel", "building", "accomodation", "checkin"], - "🏩": ["love_hotel", "like", "affection", "dating"], - "🏪": ["convenience_store", "building", "shopping", "groceries"], - "🏫": ["school", "building", "student", "education", "learn", "teach"], - "🏬": ["department_store", "building", "shopping", "mall"], - "🏭": ["factory", "building", "industry", "pollution", "smoke"], - "🏯": ["japanese_castle", "photo", "building"], - "🏰": ["castle", "building", "royalty", "history"], - "💒": ["wedding", "love", "like", "affection", "couple", "marriage", "bride", "groom"], - "🗼": ["tokyo_tower", "photo", "japanese"], - "🗽": ["statue_of_liberty", "american", "newyork"], - "⛪": ["church", "building", "religion", "christ"], - "🕌": ["mosque", "islam", "worship", "minaret"], - "🛕": ["hindu_temple", "religion"], - "🕍": ["synagogue", "judaism", "worship", "temple", "jewish"], - "⛩️": ["shinto_shrine", "temple", "japan", "kyoto"], - "🕋": ["kaaba", "mecca", "mosque", "islam"], - "⛲": ["fountain", "photo", "summer", "water", "fresh"], - "⛺": ["tent", "photo", "camping", "outdoors"], - "🌁": ["foggy", "photo", "mountain"], - "🌃": ["night_with_stars", "evening", "city", "downtown"], - "🏙️": ["cityscape", "photo", "night life", "urban"], - "🌄": ["sunrise_over_mountains", "view", "vacation", "photo"], - "🌅": ["sunrise", "morning", "view", "vacation", "photo"], - "🌆": ["cityscape_at_dusk", "photo", "evening", "sky", "buildings"], - "🌇": ["sunset", "photo", "good morning", "dawn"], - "🌉": ["bridge_at_night", "photo", "sanfrancisco"], - "♨️": ["hot_springs", "bath", "warm", "relax"], - "🎠": ["carousel_horse", "photo", "carnival"], - "🎡": ["ferris_wheel", "photo", "carnival", "londoneye"], - "🎢": ["roller_coaster", "carnival", "playground", "photo", "fun"], - "💈": ["barber_pole", "hair", "salon", "style"], - "🎪": ["circus_tent", "festival", "carnival", "party"], - "🚂": ["locomotive", "transportation", "vehicle", "train"], - "🚃": ["railway_car", "transportation", "vehicle"], - "🚄": ["high_speed_train", "transportation", "vehicle"], - "🚅": ["bullet_train", "transportation", "vehicle", "speed", "fast", "public", "travel"], - "🚆": ["train", "transportation", "vehicle"], - "🚇": ["metro", "transportation", "blue-square", "mrt", "underground", "tube"], - "🚈": ["light_rail", "transportation", "vehicle"], - "🚉": ["station", "transportation", "vehicle", "public"], - "🚊": ["tram", "transportation", "vehicle"], - "🚝": ["monorail", "transportation", "vehicle"], - "🚞": ["mountain_railway", "transportation", "vehicle"], - "🚋": ["tram_car", "transportation", "vehicle", "carriage", "public", "travel"], - "🚌": ["bus", "car", "vehicle", "transportation"], - "🚍": ["oncoming_bus", "vehicle", "transportation"], - "🚎": ["trolleybus", "bart", "transportation", "vehicle"], - "🚐": ["minibus", "vehicle", "car", "transportation"], - "🚑": ["ambulance", "health", "911", "hospital"], - "🚒": ["fire_engine", "transportation", "cars", "vehicle"], - "🚓": ["police_car", "vehicle", "cars", "transportation", "law", "legal", "enforcement"], - "🚔": ["oncoming_police_car", "vehicle", "law", "legal", "enforcement", "911"], - "🚕": ["taxi", "uber", "vehicle", "cars", "transportation"], - "🚖": ["oncoming_taxi", "vehicle", "cars", "uber"], - "🚗": ["automobile", "red", "transportation", "vehicle"], - "🚘": ["oncoming_automobile", "car", "vehicle", "transportation"], - "🚙": ["sport_utility_vehicle", "transportation", "vehicle"], - "🚚": ["delivery_truck", "cars", "transportation"], - "🚛": ["articulated_lorry", "vehicle", "cars", "transportation", "express"], - "🚜": ["tractor", "vehicle", "car", "farming", "agriculture"], - "🏎️": ["racing_car", "sports", "race", "fast", "formula", "f1"], - "🏍️": ["motorcycle", "race", "sports", "fast"], - "🛵": ["motor_scooter", "vehicle", "vespa", "sasha"], - "🦽": ["manual_wheelchair", "accessibility"], - "🦼": ["motorized_wheelchair", "accessibility"], - "🛺": ["auto_rickshaw", "move", "transportation"], - "🚲": ["bicycle", "bike", "sports", "exercise", "hipster"], - "🛴": ["kick_scooter", "vehicle", "kick", "razor"], - "🛹": ["skateboard", "board"], - "🚏": ["bus_stop", "transportation", "wait"], - "🛣️": ["motorway", "road", "cupertino", "interstate", "highway"], - "🛤️": ["railway_track", "train", "transportation"], - "🛢️": ["oil_drum", "barrell"], - "⛽": ["fuel_pump", "gas station", "petroleum"], - "🚨": [ - "police_car_light", - "police", - "ambulance", - "911", - "emergency", - "alert", - "error", - "pinged", - "law", - "legal" - ], - "🚥": ["horizontal_traffic_light", "transportation", "signal"], - "🚦": ["vertical_traffic_light", "transportation", "driving"], - "🛑": ["stop_sign", "stop"], - "🚧": ["construction", "wip", "progress", "caution", "warning"], - "⚓": ["anchor", "ship", "ferry", "sea", "boat"], - "⛵": ["sailboat", "ship", "summer", "transportation", "water", "sailing"], - "🛶": ["canoe", "boat", "paddle", "water", "ship"], - "🚤": ["speedboat", "ship", "transportation", "vehicle", "summer"], - "🛳️": ["passenger_ship", "yacht", "cruise", "ferry"], - "⛴️": ["ferry", "boat", "ship", "yacht"], - "🛥️": ["motor_boat", "ship"], - "🚢": ["ship", "transportation", "titanic", "deploy"], - "✈️": ["airplane", "vehicle", "transportation", "flight", "fly"], - "🛩️": ["small_airplane", "flight", "transportation", "fly", "vehicle"], - "🛫": ["airplane_departure", "airport", "flight", "landing"], - "🛬": ["airplane_arrival", "airport", "flight", "boarding"], - "🪂": ["parachute", "fly", "glide"], - "💺": ["seat", "sit", "airplane", "transport", "bus", "flight", "fly"], - "🚁": ["helicopter", "transportation", "vehicle", "fly"], - "🚟": ["suspension_railway", "vehicle", "transportation"], - "🚠": ["mountain_cableway", "transportation", "vehicle", "ski"], - "🚡": ["aerial_tramway", "transportation", "vehicle", "ski"], - "🛰️": ["satellite", "communication", "gps", "orbit", "spaceflight", "NASA", "ISS"], - "🚀": ["rocket", "launch", "ship", "staffmode", "NASA", "outer space", "outer_space", "fly"], - "🛸": ["flying_saucer", "transportation", "vehicle", "ufo"], - "🛎️": ["bellhop_bell", "service"], - "🧳": ["luggage", "packing", "travel"], - "⌛": ["hourglass_done", "time", "clock", "oldschool", "limit", "exam", "quiz", "test"], - "⏳": ["hourglass_not_done", "oldschool", "time", "countdown"], - "⌚": ["watch", "time", "accessories"], - "⏰": ["alarm_clock", "time", "wake"], - "⏱️": ["stopwatch", "time", "deadline"], - "⏲️": ["timer_clock", "alarm"], - "🕰️": ["mantelpiece_clock", "time"], - "🕛": [ - "twelve_o_clock", - "12", - "00:00", - "0000", - "12:00", - "1200", - "time", - "noon", - "midnight", - "midday", - "late", - "early", - "schedule" - ], - "🕧": ["twelve_thirty", "00:30", "0030", "12:30", "1230", "time", "late", "early", "schedule"], - "🕐": ["one_o_clock", "1", "1:00", "100", "13:00", "1300", "time", "late", "early", "schedule"], - "🕜": ["one_thirty", "1:30", "130", "13:30", "1330", "time", "late", "early", "schedule"], - "🕑": ["two_o_clock", "2", "2:00", "200", "14:00", "1400", "time", "late", "early", "schedule"], - "🕝": ["two_thirty", "2:30", "230", "14:30", "1430", "time", "late", "early", "schedule"], - "🕒": ["three_o_clock", "3", "3:00", "300", "15:00", "1500", "time", "late", "early", "schedule"], - "🕞": ["three_thirty", "3:30", "330", "15:30", "1530", "time", "late", "early", "schedule"], - "🕓": ["four_o_clock", "4", "4:00", "400", "16:00", "1600", "time", "late", "early", "schedule"], - "🕟": ["four_thirty", "4:30", "430", "16:30", "1630", "time", "late", "early", "schedule"], - "🕔": ["five_o_clock", "5", "5:00", "500", "17:00", "1700", "time", "late", "early", "schedule"], - "🕠": ["five_thirty", "5:30", "530", "17:30", "1730", "time", "late", "early", "schedule"], - "🕕": [ - "six_o_clock", - "6", - "6:00", - "600", - "18:00", - "1800", - "time", - "late", - "early", - "schedule", - "dawn", - "dusk" - ], - "🕡": ["six_thirty", "6:30", "630", "18:30", "1830", "time", "late", "early", "schedule"], - "🕖": ["seven_o_clock", "7", "7:00", "700", "19:00", "1900", "time", "late", "early", "schedule"], - "🕢": ["seven_thirty", "7:30", "730", "19:30", "1930", "time", "late", "early", "schedule"], - "🕗": ["eight_o_clock", "8", "8:00", "800", "20:00", "2000", "time", "late", "early", "schedule"], - "🕣": ["eight_thirty", "8:30", "830", "20:30", "2030", "time", "late", "early", "schedule"], - "🕘": ["nine_o_clock", "9", "9:00", "900", "21:00", "2100", "time", "late", "early", "schedule"], - "🕤": ["nine_thirty", "9:30", "930", "21:30", "2130", "time", "late", "early", "schedule"], - "🕙": [ - "ten_o_clock", - "10", - "10:00", - "1000", - "22:00", - "2200", - "time", - "late", - "early", - "schedule" - ], - "🕥": ["ten_thirty", "10:30", "1030", "22:30", "2230", "time", "late", "early", "schedule"], - "🕚": [ - "eleven_o_clock", - "11", - "11:00", - "1100", - "23:00", - "2300", - "time", - "late", - "early", - "schedule" - ], - "🕦": ["eleven_thirty", "11:30", "1130", "23:30", "2330", "time", "late", "early", "schedule"], - "🌑": ["new_moon", "nature", "twilight", "planet", "space", "night", "evening", "sleep"], - "🌒": [ - "waxing_crescent_moon", - "nature", - "twilight", - "planet", - "space", - "night", - "evening", - "sleep" - ], - "🌓": [ - "first_quarter_moon", - "nature", - "twilight", - "planet", - "space", - "night", - "evening", - "sleep" - ], - "🌔": [ - "waxing_gibbous_moon", - "nature", - "night", - "sky", - "gray", - "twilight", - "planet", - "space", - "evening", - "sleep" - ], - "🌕": [ - "full_moon", - "nature", - "yellow", - "twilight", - "planet", - "space", - "night", - "evening", - "sleep" - ], - "🌖": [ - "waning_gibbous_moon", - "nature", - "twilight", - "planet", - "space", - "night", - "evening", - "sleep", - "waxing_gibbous_moon" - ], - "🌗": ["last_quarter_moon", "nature", "twilight", "planet", "space", "night", "evening", "sleep"], - "🌘": [ - "waning_crescent_moon", - "nature", - "twilight", - "planet", - "space", - "night", - "evening", - "sleep" - ], - "🌙": ["crescent_moon", "night", "sleep", "sky", "evening", "magic"], - "🌚": ["new_moon_face", "nature", "twilight", "planet", "space", "night", "evening", "sleep"], - "🌛": [ - "first_quarter_moon_face", - "nature", - "twilight", - "planet", - "space", - "night", - "evening", - "sleep" - ], - "🌜": [ - "last_quarter_moon_face", - "nature", - "twilight", - "planet", - "space", - "night", - "evening", - "sleep" - ], - "🌡️": ["thermometer", "weather", "temperature", "hot", "cold"], - "☀️": ["sun", "weather", "nature", "brightness", "summer", "beach", "spring"], - "🌝": ["full_moon_face", "nature", "twilight", "planet", "space", "night", "evening", "sleep"], - "🌞": ["sun_with_face", "nature", "morning", "sky"], - "🪐": ["ringed_planet", "outerspace"], - "⭐": ["star", "night", "yellow"], - "🌟": ["glowing_star", "night", "sparkle", "awesome", "good", "magic"], - "🌠": ["shooting_star", "night", "photo"], - "🌌": ["milky_way", "photo", "space", "stars"], - "☁️": ["cloud", "weather", "sky"], - "⛅": ["sun_behind_cloud", "weather", "nature", "cloudy", "morning", "fall", "spring"], - "⛈️": ["cloud_with_lightning_and_rain", "weather", "lightning"], - "🌤️": ["sun_behind_small_cloud", "weather"], - "🌥️": ["sun_behind_large_cloud", "weather"], - "🌦️": ["sun_behind_rain_cloud", "weather"], - "🌧️": ["cloud_with_rain", "weather"], - "🌨️": ["cloud_with_snow", "weather"], - "🌩️": ["cloud_with_lightning", "weather", "thunder"], - "🌪️": ["tornado", "weather", "cyclone", "twister"], - "🌫️": ["fog", "weather"], - "🌬️": ["wind_face", "gust", "air"], - "🌀": [ - "cyclone", - "weather", - "swirl", - "blue", - "cloud", - "vortex", - "spiral", - "whirlpool", - "spin", - "tornado", - "hurricane", - "typhoon" - ], - "🌈": ["rainbow", "nature", "happy", "unicorn_face", "photo", "sky", "spring"], - "🌂": ["closed_umbrella", "weather", "rain", "drizzle"], - "☂️": ["umbrella", "weather", "spring"], - "☔": ["umbrella_with_rain_drops", "rainy", "weather", "spring"], - "⛱️": ["umbrella_on_ground", "weather", "summer"], - "⚡": ["high_voltage", "thunder", "weather", "lightning bolt", "fast", "zap"], - "❄️": ["snowflake", "winter", "season", "cold", "weather", "christmas", "xmas"], - "☃️": ["snowman", "winter", "season", "cold", "weather", "christmas", "xmas", "frozen"], - "⛄": [ - "snowman_without_snow", - "winter", - "season", - "cold", - "weather", - "christmas", - "xmas", - "frozen", - "without_snow" - ], - "☄️": ["comet", "space"], - "🔥": ["fire", "hot", "cook", "flame"], - "💧": ["droplet", "water", "drip", "faucet", "spring"], - "🌊": ["water_wave", "sea", "water", "wave", "nature", "tsunami", "disaster"], - "🎃": ["jack_o_lantern", "halloween", "light", "pumpkin", "creepy", "fall"], - "🎄": ["christmas_tree", "festival", "vacation", "december", "xmas", "celebration"], - "🎆": ["fireworks", "photo", "festival", "carnival", "congratulations"], - "🎇": ["sparkler", "stars", "night", "shine"], - "🧨": ["firecracker", "dynamite", "boom", "explode", "explosion", "explosive"], - "✨": ["sparkles", "stars", "shine", "shiny", "cool", "awesome", "good", "magic"], - "🎈": ["balloon", "party", "celebration", "birthday", "circus"], - "🎉": [ - "party_popper", - "party", - "congratulations", - "birthday", - "magic", - "circus", - "celebration", - "tada" - ], - "🎊": ["confetti_ball", "festival", "party", "birthday", "circus"], - "🎋": [ - "tanabata_tree", - "plant", - "nature", - "branch", - "summer", - "bamboo", - "wish", - "star_festival", - "tanzaku" - ], - "🎍": [ - "pine_decoration", - "japanese", - "plant", - "nature", - "vegetable", - "panda", - "new_years", - "bamboo" - ], - "🎎": ["japanese_dolls", "japanese", "toy", "kimono"], - "🎏": ["carp_streamer", "fish", "japanese", "koinobori", "carp", "banner"], - "🎐": ["wind_chime", "nature", "ding", "spring", "bell"], - "🎑": ["moon_viewing_ceremony", "photo", "japan", "asia", "tsukimi"], - "🧧": ["red_envelope", "gift"], - "🎀": ["ribbon", "decoration", "pink", "girl", "bowtie"], - "🎁": ["wrapped_gift", "present", "birthday", "christmas", "xmas"], - "🎗️": ["reminder_ribbon", "sports", "cause", "support", "awareness"], - "🎟️": ["admission_tickets", "sports", "concert", "entrance"], - "🎫": ["ticket", "event", "concert", "pass"], - "🎖️": ["military_medal", "award", "winning", "army"], - "🏆": ["trophy", "win", "award", "contest", "place", "ftw", "ceremony"], - "🏅": ["sports_medal", "award", "winning"], - "🥇": ["1st_place_medal", "award", "winning", "first"], - "🥈": ["2nd_place_medal", "award", "second"], - "🥉": ["3rd_place_medal", "award", "third"], - "⚽": ["soccer_ball", "sports", "football"], - "⚾": ["baseball", "sports", "balls"], - "🥎": ["softball", "sports", "balls"], - "🏀": ["basketball", "sports", "balls", "NBA"], - "🏐": ["volleyball", "sports", "balls"], - "🏈": ["american_football", "sports", "balls", "NFL"], - "🏉": ["rugby_football", "sports", "team"], - "🎾": ["tennis", "sports", "balls", "green"], - "🥏": ["flying_disc", "sports", "frisbee", "ultimate"], - "🎳": ["bowling", "sports", "fun", "play"], - "🏏": ["cricket_game", "sports"], - "🏑": ["field_hockey", "sports"], - "🏒": ["ice_hockey", "sports"], - "🥍": ["lacrosse", "sports", "ball", "stick"], - "🏓": ["ping_pong", "sports", "pingpong"], - "🏸": ["badminton", "sports"], - "🥊": ["boxing_glove", "sports", "fighting"], - "🥋": ["martial_arts_uniform", "judo", "karate", "taekwondo"], - "🥅": ["goal_net", "sports"], - "⛳": ["flag_in_hole", "sports", "business", "flag", "hole", "summer"], - "⛸️": ["ice_skate", "sports"], - "🎣": ["fishing_pole", "food", "hobby", "summer"], - "🤿": ["diving_mask", "sport", "ocean"], - "🎽": ["running_shirt", "play", "pageant"], - "🎿": ["skis", "sports", "winter", "cold", "snow"], - "🛷": ["sled", "sleigh", "luge", "toboggan"], - "🥌": ["curling_stone", "sports"], - "🎯": ["direct_hit", "game", "play", "bar", "target", "bullseye"], - "🪀": ["yo_yo", "toy"], - "🪁": ["kite", "wind", "fly"], - "🎱": ["pool_8_ball", "pool", "hobby", "game", "luck", "magic"], - "🔮": ["crystal_ball", "disco", "party", "magic", "circus", "fortune_teller"], - "🧿": ["nazar_amulet", "bead", "charm"], - "🎮": ["video_game", "play", "console", "PS4", "controller"], - "🕹️": ["joystick", "game", "play"], - "🎰": ["slot_machine", "bet", "gamble", "vegas", "fruit machine", "luck", "casino"], - "🎲": ["game_die", "dice", "random", "tabletop", "play", "luck"], - "🧩": ["puzzle_piece", "interlocking", "puzzle", "piece"], - "🧸": ["teddy_bear", "plush", "stuffed"], - "♠️": ["spade_suit", "poker", "cards", "suits", "magic"], - "♥️": ["heart_suit", "poker", "cards", "magic", "suits"], - "♦️": ["diamond_suit", "poker", "cards", "magic", "suits"], - "♣️": ["club_suit", "poker", "cards", "magic", "suits"], - "♟️": ["chess_pawn", "expendable"], - "🃏": ["joker", "poker", "cards", "game", "play", "magic"], - "🀄": ["mahjong_red_dragon", "game", "play", "chinese", "kanji"], - "🎴": ["flower_playing_cards", "game", "sunset", "red"], - "🎭": ["performing_arts", "acting", "theater", "drama"], - "🖼️": ["framed_picture", "photography"], - "🎨": ["artist_palette", "design", "paint", "draw", "colors"], - "🧵": ["thread", "needle", "sewing", "spool", "string"], - "🧶": ["yarn", "ball", "crochet", "knit"], - "👓": ["glasses", "fashion", "accessories", "eyesight", "nerdy", "dork", "geek"], - "🕶️": ["sunglasses", "face", "cool", "accessories"], - "🥽": ["goggles", "eyes", "protection", "safety"], - "🥼": ["lab_coat", "doctor", "experiment", "scientist", "chemist"], - "🦺": ["safety_vest", "protection"], - "👔": ["necktie", "shirt", "suitup", "formal", "fashion", "cloth", "business"], - "👕": ["t_shirt", "fashion", "cloth", "casual", "shirt", "tee"], - "👖": ["jeans", "fashion", "shopping"], - "🧣": ["scarf", "neck", "winter", "clothes"], - "🧤": ["gloves", "hands", "winter", "clothes"], - "🧥": ["coat", "jacket"], - "🧦": ["socks", "stockings", "clothes"], - "👗": ["dress", "clothes", "fashion", "shopping"], - "👘": ["kimono", "dress", "fashion", "women", "female", "japanese"], - "🥻": ["sari", "dress"], - "🩱": ["one_piece_swimsuit", "fashion"], - "🩲": ["briefs", "clothing"], - "🩳": ["shorts", "clothing"], - "👙": ["bikini", "swimming", "female", "woman", "girl", "fashion", "beach", "summer"], - "👚": ["woman_s_clothes", "fashion", "shopping_bags", "female"], - "👛": ["purse", "fashion", "accessories", "money", "sales", "shopping"], - "👜": ["handbag", "fashion", "accessory", "accessories", "shopping"], - "👝": ["clutch_bag", "bag", "accessories", "shopping"], - "🛍️": ["shopping_bags", "mall", "buy", "purchase"], - "🎒": ["backpack", "student", "education", "bag"], - "👞": ["man_s_shoe", "fashion", "male"], - "👟": ["running_shoe", "shoes", "sports", "sneakers"], - "🥾": ["hiking_boot", "backpacking", "camping", "hiking"], - "🥿": ["flat_shoe", "ballet", "slip-on", "slipper"], - "👠": ["high_heeled_shoe", "fashion", "shoes", "female", "pumps", "stiletto"], - "👡": ["woman_s_sandal", "shoes", "fashion", "flip flops"], - "🩰": ["ballet_shoes", "dance"], - "👢": ["woman_s_boot", "shoes", "fashion"], - "👑": ["crown", "king", "kod", "leader", "royalty", "lord"], - "👒": ["woman_s_hat", "fashion", "accessories", "female", "lady", "spring"], - "🎩": ["top_hat", "magic", "gentleman", "classy", "circus"], - "🎓": [ - "graduation_cap", - "school", - "college", - "degree", - "university", - "graduation", - "cap", - "hat", - "legal", - "learn", - "education" - ], - "🧢": ["billed_cap", "cap", "baseball"], - "⛑️": ["rescue_worker_s_helmet", "construction", "build"], - "📿": ["prayer_beads", "dhikr", "religious"], - "💄": ["lipstick", "female", "girl", "fashion", "woman"], - "💍": [ - "ring", - "wedding", - "propose", - "marriage", - "valentines", - "diamond", - "fashion", - "jewelry", - "gem", - "engagement" - ], - "💎": ["gem_stone", "blue", "ruby", "diamond", "jewelry"], - "🔇": ["muted_speaker", "sound", "volume", "silence", "quiet"], - "🔈": ["speaker_low_volume", "sound", "volume", "silence", "broadcast"], - "🔉": ["speaker_medium_volume", "volume", "speaker", "broadcast"], - "🔊": ["speaker_high_volume", "volume", "noise", "noisy", "speaker", "broadcast"], - "📢": ["loudspeaker", "volume", "sound"], - "📣": ["megaphone", "sound", "speaker", "volume"], - "📯": ["postal_horn", "instrument", "music"], - "🔔": ["bell", "sound", "notification", "christmas", "xmas", "chime"], - "🔕": ["bell_with_slash", "sound", "volume", "mute", "quiet", "silent"], - "🎼": ["musical_score", "treble", "clef", "compose"], - "🎵": ["musical_note", "score", "tone", "sound"], - "🎶": ["musical_notes", "music", "score"], - "🎙️": ["studio_microphone", "sing", "recording", "artist", "talkshow"], - "🎚️": ["level_slider", "scale"], - "🎛️": ["control_knobs", "dial"], - "🎤": ["microphone", "sound", "music", "PA", "sing", "talkshow"], - "🎧": ["headphone", "music", "score", "gadgets"], - "📻": ["radio", "communication", "music", "podcast", "program"], - "🎷": ["saxophone", "music", "instrument", "jazz", "blues"], - "🎸": ["guitar", "music", "instrument"], - "🎹": ["musical_keyboard", "piano", "instrument", "compose"], - "🎺": ["trumpet", "music", "brass"], - "🎻": ["violin", "music", "instrument", "orchestra", "symphony"], - "🪕": ["banjo", "music", "instructment"], - "🥁": ["drum", "music", "instrument", "drumsticks", "snare"], - "📱": ["mobile_phone", "technology", "apple", "gadgets", "dial"], - "📲": ["mobile_phone_with_arrow", "iphone", "incoming"], - "☎️": ["telephone", "technology", "communication", "dial"], - "📞": ["telephone_receiver", "technology", "communication", "dial"], - "📟": ["pager", "bbcall", "oldschool", "90s"], - "📠": ["fax_machine", "communication", "technology"], - "🔋": ["battery", "power", "energy", "sustain"], - "🔌": ["electric_plug", "charger", "power"], - "💻": ["laptop", "technology", "screen", "display", "monitor"], - "🖥️": ["desktop_computer", "technology", "computing", "screen"], - "🖨️": ["printer", "paper", "ink"], - "⌨️": ["keyboard", "technology", "computer", "type", "input", "text"], - "🖱️": ["computer_mouse", "click"], - "🖲️": ["trackball", "technology", "trackpad"], - "💽": ["computer_disk", "technology", "record", "data", "disk", "90s"], - "💾": ["floppy_disk", "oldschool", "technology", "save", "90s", "80s"], - "💿": ["optical_disk", "technology", "dvd", "disk", "disc", "90s"], - "📀": ["dvd", "cd", "disk", "disc"], - "🧮": ["abacus", "calculation"], - "🎥": ["movie_camera", "film", "record"], - "🎞️": ["film_frames", "movie"], - "📽️": ["film_projector", "video", "tape", "record", "movie"], - "🎬": ["clapper_board", "movie", "film", "record"], - "📺": ["television", "technology", "program", "oldschool", "show"], - "📷": ["camera", "gadgets", "photography"], - "📸": ["camera_with_flash", "photography", "gadgets"], - "📹": ["video_camera", "film", "record"], - "📼": ["videocassette", "record", "video", "oldschool", "90s", "80s"], - "🔍": ["magnifying_glass_tilted_left", "search", "zoom", "find", "detective"], - "🔎": ["magnifying_glass_tilted_right", "search", "zoom", "find", "detective"], - "🕯️": ["candle", "fire", "wax"], - "💡": ["light_bulb", "light", "electricity", "idea"], - "🔦": ["flashlight", "dark", "camping", "sight", "night"], - "🏮": ["red_paper_lantern", "light", "paper", "halloween", "spooky"], - "🪔": ["diya_lamp", "lighting"], - "📔": ["notebook_with_decorative_cover", "classroom", "notes", "record", "paper", "study"], - "📕": ["closed_book", "read", "library", "knowledge", "textbook", "learn"], - "📖": ["open_book", "book", "read", "library", "knowledge", "literature", "learn", "study"], - "📗": ["green_book", "read", "library", "knowledge", "study"], - "📘": ["blue_book", "read", "library", "knowledge", "learn", "study"], - "📙": ["orange_book", "read", "library", "knowledge", "textbook", "study"], - "📚": ["books", "literature", "library", "study"], - "📓": ["notebook", "stationery", "record", "notes", "paper", "study"], - "📒": ["ledger", "notes", "paper"], - "📃": ["page_with_curl", "documents", "office", "paper"], - "📜": ["scroll", "documents", "ancient", "history", "paper"], - "📄": ["page_facing_up", "documents", "office", "paper", "information"], - "📰": ["newspaper", "press", "headline"], - "🗞️": ["rolled_up_newspaper", "press", "headline"], - "📑": ["bookmark_tabs", "favorite", "save", "order", "tidy"], - "🔖": ["bookmark", "favorite", "label", "save"], - "🏷️": ["label", "sale", "tag"], - "💰": ["money_bag", "dollar", "payment", "coins", "sale"], - "💴": ["yen_banknote", "money", "sales", "japanese", "dollar", "currency"], - "💵": ["dollar_banknote", "money", "sales", "bill", "currency"], - "💶": ["euro_banknote", "money", "sales", "dollar", "currency"], - "💷": [ - "pound_banknote", - "british", - "sterling", - "money", - "sales", - "bills", - "uk", - "england", - "currency" - ], - "💸": ["money_with_wings", "dollar", "bills", "payment", "sale"], - "💳": ["credit_card", "money", "sales", "dollar", "bill", "payment", "shopping"], - "🧾": ["receipt", "accounting", "expenses"], - "💹": ["chart_increasing_with_yen", "green-square", "graph", "presentation", "stats"], - "💱": ["currency_exchange", "money", "sales", "dollar", "travel"], - "💲": ["heavy_dollar_sign", "money", "sales", "payment", "currency", "buck"], - "✉️": ["envelope", "letter", "postal", "inbox", "communication"], - "📧": ["e_mail", "communication", "inbox"], - "📨": ["incoming_envelope", "email", "inbox"], - "📩": ["envelope_with_arrow", "email", "communication"], - "📤": ["outbox_tray", "inbox", "email"], - "📥": ["inbox_tray", "email", "documents"], - "📦": ["package", "mail", "gift", "cardboard", "box", "moving"], - "📫": ["closed_mailbox_with_raised_flag", "email", "inbox", "communication"], - "📪": ["closed_mailbox_with_lowered_flag", "email", "communication", "inbox"], - "📬": ["open_mailbox_with_raised_flag", "email", "inbox", "communication"], - "📭": ["open_mailbox_with_lowered_flag", "email", "inbox"], - "📮": ["postbox", "email", "letter", "envelope"], - "🗳️": ["ballot_box_with_ballot", "election", "vote"], - "✏️": ["pencil", "stationery", "write", "paper", "writing", "school", "study"], - "✒️": ["black_nib", "pen", "stationery", "writing", "write"], - "🖋️": ["fountain_pen", "stationery", "writing", "write"], - "🖊️": ["pen", "stationery", "writing", "write"], - "🖌️": ["paintbrush", "drawing", "creativity", "art"], - "🖍️": ["crayon", "drawing", "creativity"], - "📝": [ - "memo", - "write", - "documents", - "stationery", - "pencil", - "paper", - "writing", - "legal", - "exam", - "quiz", - "test", - "study", - "compose" - ], - "💼": ["briefcase", "business", "documents", "work", "law", "legal", "job", "career"], - "📁": ["file_folder", "documents", "business", "office"], - "📂": ["open_file_folder", "documents", "load"], - "🗂️": ["card_index_dividers", "organizing", "business", "stationery"], - "📅": ["calendar", "schedule"], - "📆": ["tear_off_calendar", "schedule", "date", "planning"], - "🗒️": ["spiral_notepad", "memo", "stationery"], - "🗓️": ["spiral_calendar", "date", "schedule", "planning"], - "📇": ["card_index", "business", "stationery"], - "📈": [ - "chart_increasing", - "graph", - "presentation", - "stats", - "recovery", - "business", - "economics", - "money", - "sales", - "good", - "success" - ], - "📉": [ - "chart_decreasing", - "graph", - "presentation", - "stats", - "recession", - "business", - "economics", - "money", - "sales", - "bad", - "failure" - ], - "📊": ["bar_chart", "graph", "presentation", "stats"], - "📋": ["clipboard", "stationery", "documents"], - "📌": ["pushpin", "stationery", "mark", "here"], - "📍": ["round_pushpin", "stationery", "location", "map", "here"], - "📎": ["paperclip", "documents", "stationery"], - "🖇️": ["linked_paperclips", "documents", "stationery"], - "📏": [ - "straight_ruler", - "stationery", - "calculate", - "length", - "math", - "school", - "drawing", - "architect", - "sketch" - ], - "📐": ["triangular_ruler", "stationery", "math", "architect", "sketch"], - "✂️": ["scissors", "stationery", "cut"], - "🗃️": ["card_file_box", "business", "stationery"], - "🗄️": ["file_cabinet", "filing", "organizing"], - "🗑️": ["wastebasket", "bin", "trash", "rubbish", "garbage", "toss"], - "🔒": ["locked", "security", "password", "padlock"], - "🔓": ["unlocked", "privacy", "security"], - "🔏": ["locked_with_pen", "security", "secret"], - "🔐": ["locked_with_key", "security", "privacy"], - "🔑": ["key", "lock", "door", "password"], - "🗝️": ["old_key", "lock", "door", "password"], - "🔨": ["hammer", "tools", "build", "create"], - "🪓": ["axe", "tool", "chop", "cut"], - "⛏️": ["pick", "tools", "dig"], - "⚒️": ["hammer_and_pick", "tools", "build", "create"], - "🛠️": ["hammer_and_wrench", "tools", "build", "create"], - "🗡️": ["dagger", "weapon"], - "⚔️": ["crossed_swords", "weapon"], - "🔫": ["pistol", "violence", "weapon", "revolver"], - "🏹": ["bow_and_arrow", "sports"], - "🛡️": ["shield", "protection", "security"], - "🔧": ["wrench", "tools", "diy", "ikea", "fix", "maintainer"], - "🔩": ["nut_and_bolt", "handy", "tools", "fix"], - "⚙️": ["gear", "cog"], - "🗜️": ["clamp", "tool"], - "⚖️": ["balance_scale", "law", "fairness", "weight"], - "🦯": ["probing_cane", "accessibility"], - "🔗": ["link", "rings", "url"], - "⛓️": ["chains", "lock", "arrest"], - "🧰": ["toolbox", "tools", "diy", "fix", "maintainer", "mechanic"], - "🧲": ["magnet", "attraction", "magnetic"], - "⚗️": ["alembic", "distilling", "science", "experiment", "chemistry"], - "🧪": ["test_tube", "chemistry", "experiment", "lab", "science"], - "🧫": ["petri_dish", "bacteria", "biology", "culture", "lab"], - "🧬": ["dna", "biologist", "genetics", "life"], - "🔬": ["microscope", "laboratory", "experiment", "zoomin", "science", "study"], - "🔭": ["telescope", "stars", "space", "zoom", "science", "astronomy"], - "📡": ["satellite_antenna", "communication", "future", "radio", "space"], - "💉": [ - "syringe", - "health", - "hospital", - "drugs", - "blood", - "medicine", - "needle", - "doctor", - "nurse" - ], - "🩸": ["drop_of_blood", "period", "hurt", "harm", "wound"], - "💊": ["pill", "health", "medicine", "doctor", "pharmacy", "drug"], - "🩹": ["adhesive_bandage", "heal"], - "🩺": ["stethoscope", "health"], - "🚪": ["door", "house", "entry", "exit"], - "🛏️": ["bed", "sleep", "rest"], - "🛋️": ["couch_and_lamp", "read", "chill"], - "🪑": ["chair", "sit", "furniture"], - "🚽": ["toilet", "restroom", "wc", "washroom", "bathroom", "potty"], - "🚿": ["shower", "clean", "water", "bathroom"], - "🛁": ["bathtub", "clean", "shower", "bathroom"], - "🪒": ["razor", "cut"], - "🧴": ["lotion_bottle", "moisturizer", "sunscreen"], - "🧷": ["safety_pin", "diaper"], - "🧹": ["broom", "cleaning", "sweeping", "witch"], - "🧺": ["basket", "laundry"], - "🧻": ["roll_of_paper", "roll"], - "🧼": ["soap", "bar", "bathing", "cleaning", "lather"], - "🧽": ["sponge", "absorbing", "cleaning", "porous"], - "🧯": ["fire_extinguisher", "quench"], - "🛒": ["shopping_cart", "trolley"], - "🚬": ["cigarette", "kills", "tobacco", "joint", "smoke"], - "⚰️": [ - "coffin", - "vampire", - "dead", - "die", - "death", - "rip", - "graveyard", - "cemetery", - "casket", - "funeral", - "box" - ], - "⚱️": ["funeral_urn", "dead", "die", "death", "rip", "ashes"], - "🗿": ["moai", "rock", "easter island"], - "🏧": ["atm_sign", "money", "sales", "cash", "blue-square", "payment", "bank"], - "🚮": ["litter_in_bin_sign", "blue-square", "sign", "human", "info"], - "🚰": ["potable_water", "blue-square", "liquid", "restroom", "cleaning", "faucet"], - "♿": ["wheelchair_symbol", "blue-square", "disabled", "accessibility"], - "🚹": ["men_s_room", "toilet", "restroom", "wc", "blue-square", "gender", "male"], - "🚺": ["women_s_room", "purple-square", "woman", "female", "toilet", "loo", "restroom", "gender"], - "🚻": ["restroom", "blue-square", "toilet", "refresh", "wc", "gender"], - "🚼": ["baby_symbol", "orange-square", "child"], - "🚾": ["water_closet", "toilet", "restroom", "blue-square"], - "🛂": ["passport_control", "custom", "blue-square"], - "🛃": ["customs", "passport", "border", "blue-square"], - "🛄": ["baggage_claim", "blue-square", "airport", "transport"], - "🛅": ["left_luggage", "blue-square", "travel"], - "⚠️": ["warning", "exclamation", "wip", "alert", "error", "problem", "issue"], - "🚸": ["children_crossing", "school", "warning", "danger", "sign", "driving", "yellow-diamond"], - "⛔": ["no_entry", "limit", "security", "privacy", "bad", "denied", "stop", "circle"], - "🚫": ["prohibited", "forbid", "stop", "limit", "denied", "disallow", "circle"], - "🚳": ["no_bicycles", "no_bikes", "bicycle", "bike", "cyclist", "prohibited", "circle"], - "🚭": ["no_smoking", "cigarette", "blue-square", "smell", "smoke"], - "🚯": ["no_littering", "trash", "bin", "garbage", "circle"], - "🚱": ["non_potable_water", "drink", "faucet", "tap", "circle"], - "🚷": ["no_pedestrians", "rules", "crossing", "walking", "circle"], - "📵": ["no_mobile_phones", "iphone", "mute", "circle"], - "🔞": ["no_one_under_eighteen", "18", "drink", "pub", "night", "minor", "circle"], - "☢️": ["radioactive", "nuclear", "danger"], - "☣️": ["biohazard", "danger"], - "⬆️": ["up_arrow", "blue-square", "continue", "top", "direction"], - "↗️": ["up_right_arrow", "blue-square", "point", "direction", "diagonal", "northeast"], - "➡️": ["right_arrow", "blue-square", "next"], - "↘️": ["down_right_arrow", "blue-square", "direction", "diagonal", "southeast"], - "⬇️": ["down_arrow", "blue-square", "direction", "bottom"], - "↙️": ["down_left_arrow", "blue-square", "direction", "diagonal", "southwest"], - "⬅️": ["left_arrow", "blue-square", "previous", "back"], - "↖️": ["up_left_arrow", "blue-square", "point", "direction", "diagonal", "northwest"], - "↕️": ["up_down_arrow", "blue-square", "direction", "way", "vertical"], - "↔️": ["left_right_arrow", "shape", "direction", "horizontal", "sideways"], - "↩️": ["right_arrow_curving_left", "back", "return", "blue-square", "undo", "enter"], - "↪️": ["left_arrow_curving_right", "blue-square", "return", "rotate", "direction"], - "⤴️": ["right_arrow_curving_up", "blue-square", "direction", "top"], - "⤵️": ["right_arrow_curving_down", "blue-square", "direction", "bottom"], - "🔃": ["clockwise_vertical_arrows", "sync", "cycle", "round", "repeat"], - "🔄": ["counterclockwise_arrows_button", "blue-square", "sync", "cycle"], - "🔙": ["back_arrow", "arrow", "words", "return"], - "🔚": ["end_arrow", "words", "arrow"], - "🔛": ["on_arrow", "arrow", "words"], - "🔜": ["soon_arrow", "arrow", "words"], - "🔝": ["top_arrow", "words", "blue-square"], - "🛐": ["place_of_worship", "religion", "church", "temple", "prayer"], - "⚛️": ["atom_symbol", "science", "physics", "chemistry"], - "🕉️": ["om", "hinduism", "buddhism", "sikhism", "jainism"], - "✡️": ["star_of_david", "judaism"], - "☸️": ["wheel_of_dharma", "hinduism", "buddhism", "sikhism", "jainism"], - "☯️": ["yin_yang", "balance"], - "✝️": ["latin_cross", "christianity"], - "☦️": ["orthodox_cross", "suppedaneum", "religion"], - "☪️": ["star_and_crescent", "islam"], - "☮️": ["peace_symbol", "hippie"], - "🕎": ["menorah", "hanukkah", "candles", "jewish"], - "🔯": ["dotted_six_pointed_star", "purple-square", "religion", "jewish", "hexagram"], - "♈": ["aries", "sign", "purple-square", "zodiac", "astrology"], - "♉": ["taurus", "purple-square", "sign", "zodiac", "astrology"], - "♊": ["gemini", "sign", "zodiac", "purple-square", "astrology"], - "♋": ["cancer", "sign", "zodiac", "purple-square", "astrology"], - "♌": ["leo", "sign", "purple-square", "zodiac", "astrology"], - "♍": ["virgo", "sign", "zodiac", "purple-square", "astrology"], - "♎": ["libra", "sign", "purple-square", "zodiac", "astrology"], - "♏": ["scorpio", "sign", "zodiac", "purple-square", "astrology"], - "♐": ["sagittarius", "sign", "zodiac", "purple-square", "astrology"], - "♑": ["capricorn", "sign", "zodiac", "purple-square", "astrology"], - "♒": ["aquarius", "sign", "purple-square", "zodiac", "astrology"], - "♓": ["pisces", "purple-square", "sign", "zodiac", "astrology"], - "⛎": ["ophiuchus", "sign", "purple-square", "constellation", "astrology"], - "🔀": ["shuffle_tracks_button", "blue-square", "shuffle", "music", "random"], - "🔁": ["repeat_button", "loop", "record"], - "🔂": ["repeat_single_button", "blue-square", "loop"], - "▶️": ["play_button", "blue-square", "right", "direction", "play"], - "⏩": ["fast_forward_button", "blue-square", "play", "speed", "continue"], - "⏭️": ["next_track_button", "forward", "next", "blue-square"], - "⏯️": ["play_or_pause_button", "blue-square", "play", "pause"], - "◀️": ["reverse_button", "blue-square", "left", "direction"], - "⏪": ["fast_reverse_button", "play", "blue-square"], - "⏮️": ["last_track_button", "backward"], - "🔼": ["upwards_button", "blue-square", "triangle", "direction", "point", "forward", "top"], - "⏫": ["fast_up_button", "blue-square", "direction", "top"], - "🔽": ["downwards_button", "blue-square", "direction", "bottom"], - "⏬": ["fast_down_button", "blue-square", "direction", "bottom"], - "⏸️": ["pause_button", "pause", "blue-square"], - "⏹️": ["stop_button", "blue-square"], - "⏺️": ["record_button", "blue-square"], - "⏏️": ["eject_button", "blue-square"], - "🎦": ["cinema", "blue-square", "record", "film", "movie", "curtain", "stage", "theater"], - "🔅": ["dim_button", "sun", "afternoon", "warm", "summer"], - "🔆": ["bright_button", "sun", "light"], - "📶": [ - "antenna_bars", - "blue-square", - "reception", - "phone", - "internet", - "connection", - "wifi", - "bluetooth", - "bars" - ], - "📳": ["vibration_mode", "orange-square", "phone"], - "📴": ["mobile_phone_off", "mute", "orange-square", "silence", "quiet"], - "♀️": ["female_sign", "woman", "women", "lady", "girl"], - "♂️": ["male_sign", "man", "boy", "men"], - "⚕️": ["medical_symbol", "health", "hospital"], - "♾️": ["infinity", "forever"], - "♻️": ["recycling_symbol", "arrow", "environment", "garbage", "trash"], - "⚜️": ["fleur_de_lis", "decorative", "scout"], - "🔱": ["trident_emblem", "weapon", "spear"], - "📛": ["name_badge", "fire", "forbid"], - "🔰": ["japanese_symbol_for_beginner", "badge", "shield"], - "⭕": ["hollow_red_circle", "circle", "round"], - "✅": ["check_mark_button", "green-square", "ok", "agree", "vote", "election", "answer", "tick"], - "☑️": [ - "check_box_with_check", - "ok", - "agree", - "confirm", - "black-square", - "vote", - "election", - "yes", - "tick" - ], - "✔️": ["check_mark", "ok", "nike", "answer", "yes", "tick"], - "✖️": ["multiplication_sign", "math", "calculation"], - "❌": ["cross_mark", "no", "delete", "remove", "cancel", "red"], - "❎": ["cross_mark_button", "x", "green-square", "no", "deny"], - "➕": ["plus_sign", "math", "calculation", "addition", "more", "increase"], - "➖": ["minus_sign", "math", "calculation", "subtract", "less"], - "➗": ["division_sign", "divide", "math", "calculation"], - "➰": ["curly_loop", "scribble", "draw", "shape", "squiggle"], - "➿": ["double_curly_loop", "tape", "cassette"], - "〽️": ["part_alternation_mark", "graph", "presentation", "stats", "business", "economics", "bad"], - "✳️": ["eight_spoked_asterisk", "star", "sparkle", "green-square"], - "✴️": ["eight_pointed_star", "orange-square", "shape", "polygon"], - "❇️": ["sparkle", "stars", "green-square", "awesome", "good", "fireworks"], - "‼️": ["double_exclamation_mark", "exclamation", "surprise"], - "⁉️": ["exclamation_question_mark", "wat", "punctuation", "surprise"], - "❓": ["question_mark", "doubt", "confused"], - "❔": ["white_question_mark", "doubts", "gray", "huh", "confused"], - "❕": ["white_exclamation_mark", "surprise", "punctuation", "gray", "wow", "warning"], - "❗": [ - "exclamation_mark", - "heavy_exclamation_mark", - "danger", - "surprise", - "punctuation", - "wow", - "warning" - ], - "〰️": ["wavy_dash", "draw", "line", "moustache", "mustache", "squiggle", "scribble"], - "©️": ["copyright", "ip", "license", "circle", "law", "legal"], - "®️": ["registered", "alphabet", "circle"], - "™️": ["trade_mark", "trademark", "brand", "law", "legal"], - "#️⃣": ["keycap_", "symbol", "blue-square", "twitter"], - "*️⃣": ["keycap_", "star", "keycap"], - "0️⃣": ["keycap_0", "0", "numbers", "blue-square", "null", "zero"], - "1️⃣": ["keycap_1", "blue-square", "numbers", "1", "one"], - "2️⃣": ["keycap_2", "numbers", "2", "prime", "blue-square", "two"], - "3️⃣": ["keycap_3", "3", "numbers", "prime", "blue-square", "three"], - "4️⃣": ["keycap_4", "4", "numbers", "blue-square", "four"], - "5️⃣": ["keycap_5", "5", "numbers", "blue-square", "prime", "five"], - "6️⃣": ["keycap_6", "6", "numbers", "blue-square", "six"], - "7️⃣": ["keycap_7", "7", "numbers", "blue-square", "prime", "seven"], - "8️⃣": ["keycap_8", "8", "blue-square", "numbers", "eight"], - "9️⃣": ["keycap_9", "blue-square", "numbers", "9", "nine"], - "🔟": ["keycap_10", "numbers", "10", "blue-square", "ten"], - "🔠": ["input_latin_uppercase", "alphabet", "words", "letters", "uppercase", "blue-square"], - "🔡": ["input_latin_lowercase", "blue-square", "letters", "lowercase", "alphabet"], - "🔢": ["input_numbers", "numbers", "blue-square", "1234", "1", "2", "3", "4"], - "🔣": [ - "input_symbols", - "blue-square", - "music", - "note", - "ampersand", - "percent", - "glyphs", - "characters" - ], - "🔤": ["input_latin_letters", "blue-square", "alphabet"], - "🅰️": ["a_button", "red-square", "alphabet", "letter"], - "🆎": ["ab_button", "red-square", "alphabet"], - "🅱️": ["b_button", "red-square", "alphabet", "letter"], - "🆑": ["cl_button", "alphabet", "words", "red-square"], - "🆒": ["cool_button", "words", "blue-square"], - "🆓": ["free_button", "blue-square", "words"], - "ℹ️": ["information", "blue-square", "alphabet", "letter"], - "🆔": ["id_button", "purple-square", "words"], - "Ⓜ️": ["circled_m", "alphabet", "blue-circle", "letter"], - "🆕": ["new_button", "blue-square", "words", "start"], - "🆖": ["ng_button", "blue-square", "words", "shape", "icon"], - "🅾️": ["o_button", "alphabet", "red-square", "letter"], - "🆗": ["ok_button", "good", "agree", "yes", "blue-square"], - "🅿️": ["p_button", "cars", "blue-square", "alphabet", "letter"], - "🆘": ["sos_button", "help", "red-square", "words", "emergency", "911"], - "🆙": ["up_button", "blue-square", "above", "high"], - "🆚": ["vs_button", "words", "orange-square"], - "🈁": ["japanese_here_button", "blue-square", "here", "katakana", "japanese", "destination"], - "🈂️": ["japanese_service_charge_button", "japanese", "blue-square", "katakana"], - "🈷️": [ - "japanese_monthly_amount_button", - "chinese", - "month", - "moon", - "japanese", - "orange-square", - "kanji" - ], - "🈶": ["japanese_not_free_of_charge_button", "orange-square", "chinese", "have", "kanji"], - "🈯": ["japanese_reserved_button", "chinese", "point", "green-square", "kanji"], - "🉐": ["japanese_bargain_button", "chinese", "kanji", "obtain", "get", "circle"], - "🈹": ["japanese_discount_button", "cut", "divide", "chinese", "kanji", "pink-square"], - "🈚": [ - "japanese_free_of_charge_button", - "nothing", - "chinese", - "kanji", - "japanese", - "orange-square" - ], - "🈲": [ - "japanese_prohibited_button", - "kanji", - "japanese", - "chinese", - "forbidden", - "limit", - "restricted", - "red-square" - ], - "🉑": [ - "japanese_acceptable_button", - "ok", - "good", - "chinese", - "kanji", - "agree", - "yes", - "orange-circle" - ], - "🈸": ["japanese_application_button", "chinese", "japanese", "kanji", "orange-square"], - "🈴": ["japanese_passing_grade_button", "japanese", "chinese", "join", "kanji", "red-square"], - "🈳": ["japanese_vacancy_button", "kanji", "japanese", "chinese", "empty", "sky", "blue-square"], - "㊗️": ["japanese_congratulations_button", "chinese", "kanji", "japanese", "red-circle"], - "㊙️": ["japanese_secret_button", "privacy", "chinese", "sshh", "kanji", "red-circle"], - "🈺": ["japanese_open_for_business_button", "japanese", "opening hours", "orange-square"], - "🈵": ["japanese_no_vacancy_button", "full", "chinese", "japanese", "red-square", "kanji"], - "🔴": ["red_circle", "shape", "error", "danger"], - "🟠": ["orange_circle", "round"], - "🟡": ["yellow_circle", "round"], - "🟢": ["green_circle", "round"], - "🔵": ["blue_circle", "shape", "icon", "button"], - "🟣": ["purple_circle", "round"], - "🟤": ["brown_circle", "round"], - "⚫": ["black_circle", "shape", "button", "round"], - "⚪": ["white_circle", "shape", "round"], - "🟥": ["red_square"], - "🟧": ["orange_square"], - "🟨": ["yellow_square"], - "🟩": ["green_square"], - "🟦": ["blue_square"], - "🟪": ["purple_square"], - "🟫": ["brown_square"], - "⬛": ["black_large_square", "shape", "icon", "button"], - "⬜": ["white_large_square", "shape", "icon", "stone", "button"], - "◼️": ["black_medium_square", "shape", "button", "icon"], - "◻️": ["white_medium_square", "shape", "stone", "icon"], - "◾": ["black_medium_small_square", "icon", "shape", "button"], - "◽": ["white_medium_small_square", "shape", "stone", "icon", "button"], - "▪️": ["black_small_square", "shape", "icon"], - "▫️": ["white_small_square", "shape", "icon"], - "🔶": ["large_orange_diamond", "shape", "jewel", "gem"], - "🔷": ["large_blue_diamond", "shape", "jewel", "gem"], - "🔸": ["small_orange_diamond", "shape", "jewel", "gem"], - "🔹": ["small_blue_diamond", "shape", "jewel", "gem"], - "🔺": ["red_triangle_pointed_up", "shape", "direction", "up", "top"], - "🔻": ["red_triangle_pointed_down", "shape", "direction", "bottom"], - "💠": ["diamond_with_a_dot", "jewel", "blue", "gem", "crystal", "fancy"], - "🔘": ["radio_button", "input", "old", "music", "circle"], - "🔳": ["white_square_button", "shape", "input"], - "🔲": ["black_square_button", "shape", "input", "frame"], - "🏁": ["chequered_flag", "contest", "finishline", "race", "gokart"], - "🚩": ["triangular_flag", "mark", "milestone", "place"], - "🎌": ["crossed_flags", "japanese", "nation", "country", "border"], - "🏴": ["black_flag", "pirate"], - "🏳️": ["white_flag", "losing", "loser", "lost", "surrender", "give up", "fail"], - "🏳️‍🌈": [ - "rainbow_flag", - "flag", - "rainbow", - "pride", - "gay", - "lgbt", - "queer", - "homosexual", - "lesbian", - "bisexual" - ], - "🏴‍☠️": ["pirate_flag", "skull", "crossbones", "flag", "banner"], - "🇦🇨": ["flag_ascension_island"], - "🇦🇩": ["flag_andorra", "ad", "flag", "nation", "country", "banner", "andorra"], - "🇦🇪": [ - "flag_united_arab_emirates", - "united", - "arab", - "emirates", - "flag", - "nation", - "country", - "banner", - "united_arab_emirates" - ], - "🇦🇫": ["flag_afghanistan", "af", "flag", "nation", "country", "banner", "afghanistan"], - "🇦🇬": [ - "flag_antigua_barbuda", - "antigua", - "barbuda", - "flag", - "nation", - "country", - "banner", - "antigua_barbuda" - ], - "🇦🇮": ["flag_anguilla", "ai", "flag", "nation", "country", "banner", "anguilla"], - "🇦🇱": ["flag_albania", "al", "flag", "nation", "country", "banner", "albania"], - "🇦🇲": ["flag_armenia", "am", "flag", "nation", "country", "banner", "armenia"], - "🇦🇴": ["flag_angola", "ao", "flag", "nation", "country", "banner", "angola"], - "🇦🇶": ["flag_antarctica", "aq", "flag", "nation", "country", "banner", "antarctica"], - "🇦🇷": ["flag_argentina", "ar", "flag", "nation", "country", "banner", "argentina"], - "🇦🇸": [ - "flag_american_samoa", - "american", - "ws", - "flag", - "nation", - "country", - "banner", - "american_samoa" - ], - "🇦🇹": ["flag_austria", "at", "flag", "nation", "country", "banner", "austria"], - "🇦🇺": ["flag_australia", "au", "flag", "nation", "country", "banner", "australia"], - "🇦🇼": ["flag_aruba", "aw", "flag", "nation", "country", "banner", "aruba"], - "🇦🇽": [ - "flag_aland_islands", - "Åland", - "islands", - "flag", - "nation", - "country", - "banner", - "aland_islands" - ], - "🇦🇿": ["flag_azerbaijan", "az", "flag", "nation", "country", "banner", "azerbaijan"], - "🇧🇦": [ - "flag_bosnia_herzegovina", - "bosnia", - "herzegovina", - "flag", - "nation", - "country", - "banner", - "bosnia_herzegovina" - ], - "🇧🇧": ["flag_barbados", "bb", "flag", "nation", "country", "banner", "barbados"], - "🇧🇩": ["flag_bangladesh", "bd", "flag", "nation", "country", "banner", "bangladesh"], - "🇧🇪": ["flag_belgium", "be", "flag", "nation", "country", "banner", "belgium"], - "🇧🇫": [ - "flag_burkina_faso", - "burkina", - "faso", - "flag", - "nation", - "country", - "banner", - "burkina_faso" - ], - "🇧🇬": ["flag_bulgaria", "bg", "flag", "nation", "country", "banner", "bulgaria"], - "🇧🇭": ["flag_bahrain", "bh", "flag", "nation", "country", "banner", "bahrain"], - "🇧🇮": ["flag_burundi", "bi", "flag", "nation", "country", "banner", "burundi"], - "🇧🇯": ["flag_benin", "bj", "flag", "nation", "country", "banner", "benin"], - "🇧🇱": [ - "flag_st_barthelemy", - "saint", - "barthélemy", - "flag", - "nation", - "country", - "banner", - "st_barthelemy" - ], - "🇧🇲": ["flag_bermuda", "bm", "flag", "nation", "country", "banner", "bermuda"], - "🇧🇳": ["flag_brunei", "bn", "darussalam", "flag", "nation", "country", "banner", "brunei"], - "🇧🇴": ["flag_bolivia", "bo", "flag", "nation", "country", "banner", "bolivia"], - "🇧🇶": [ - "flag_caribbean_netherlands", - "bonaire", - "flag", - "nation", - "country", - "banner", - "caribbean_netherlands" - ], - "🇧🇷": ["flag_brazil", "br", "flag", "nation", "country", "banner", "brazil"], - "🇧🇸": ["flag_bahamas", "bs", "flag", "nation", "country", "banner", "bahamas"], - "🇧🇹": ["flag_bhutan", "bt", "flag", "nation", "country", "banner", "bhutan"], - "🇧🇻": ["flag_bouvet_island", "norway"], - "🇧🇼": ["flag_botswana", "bw", "flag", "nation", "country", "banner", "botswana"], - "🇧🇾": ["flag_belarus", "by", "flag", "nation", "country", "banner", "belarus"], - "🇧🇿": ["flag_belize", "bz", "flag", "nation", "country", "banner", "belize"], - "🇨🇦": ["flag_canada", "ca", "flag", "nation", "country", "banner", "canada"], - "🇨🇨": [ - "flag_cocos_islands", - "cocos", - "keeling", - "islands", - "flag", - "nation", - "country", - "banner", - "cocos_islands" - ], - "🇨🇩": [ - "flag_congo_kinshasa", - "congo", - "democratic", - "republic", - "flag", - "nation", - "country", - "banner", - "congo_kinshasa" - ], - "🇨🇫": [ - "flag_central_african_republic", - "central", - "african", - "republic", - "flag", - "nation", - "country", - "banner", - "central_african_republic" - ], - "🇨🇬": [ - "flag_congo_brazzaville", - "congo", - "flag", - "nation", - "country", - "banner", - "congo_brazzaville" - ], - "🇨🇭": ["flag_switzerland", "ch", "flag", "nation", "country", "banner", "switzerland"], - "🇨🇮": [ - "flag_cote_d_ivoire", - "ivory", - "coast", - "flag", - "nation", - "country", - "banner", - "cote_d_ivoire" - ], - "🇨🇰": [ - "flag_cook_islands", - "cook", - "islands", - "flag", - "nation", - "country", - "banner", - "cook_islands" - ], - "🇨🇱": ["flag_chile", "flag", "nation", "country", "banner", "chile"], - "🇨🇲": ["flag_cameroon", "cm", "flag", "nation", "country", "banner", "cameroon"], - "🇨🇳": ["flag_china", "china", "chinese", "prc", "flag", "country", "nation", "banner"], - "🇨🇴": ["flag_colombia", "co", "flag", "nation", "country", "banner", "colombia"], - "🇨🇵": ["flag_clipperton_island"], - "🇨🇷": ["flag_costa_rica", "costa", "rica", "flag", "nation", "country", "banner", "costa_rica"], - "🇨🇺": ["flag_cuba", "cu", "flag", "nation", "country", "banner", "cuba"], - "🇨🇻": ["flag_cape_verde", "cabo", "verde", "flag", "nation", "country", "banner", "cape_verde"], - "🇨🇼": ["flag_curacao", "curaçao", "flag", "nation", "country", "banner", "curacao"], - "🇨🇽": [ - "flag_christmas_island", - "christmas", - "island", - "flag", - "nation", - "country", - "banner", - "christmas_island" - ], - "🇨🇾": ["flag_cyprus", "cy", "flag", "nation", "country", "banner", "cyprus"], - "🇨🇿": ["flag_czechia", "cz", "flag", "nation", "country", "banner", "czechia"], - "🇩🇪": ["flag_germany", "german", "nation", "flag", "country", "banner", "germany"], - "🇩🇬": ["flag_diego_garcia"], - "🇩🇯": ["flag_djibouti", "dj", "flag", "nation", "country", "banner", "djibouti"], - "🇩🇰": ["flag_denmark", "dk", "flag", "nation", "country", "banner", "denmark"], - "🇩🇲": ["flag_dominica", "dm", "flag", "nation", "country", "banner", "dominica"], - "🇩🇴": [ - "flag_dominican_republic", - "dominican", - "republic", - "flag", - "nation", - "country", - "banner", - "dominican_republic" - ], - "🇩🇿": ["flag_algeria", "dz", "flag", "nation", "country", "banner", "algeria"], - "🇪🇦": ["flag_ceuta_melilla"], - "🇪🇨": ["flag_ecuador", "ec", "flag", "nation", "country", "banner", "ecuador"], - "🇪🇪": ["flag_estonia", "ee", "flag", "nation", "country", "banner", "estonia"], - "🇪🇬": ["flag_egypt", "eg", "flag", "nation", "country", "banner", "egypt"], - "🇪🇭": [ - "flag_western_sahara", - "western", - "sahara", - "flag", - "nation", - "country", - "banner", - "western_sahara" - ], - "🇪🇷": ["flag_eritrea", "er", "flag", "nation", "country", "banner", "eritrea"], - "🇪🇸": ["flag_spain", "spain", "flag", "nation", "country", "banner"], - "🇪🇹": ["flag_ethiopia", "et", "flag", "nation", "country", "banner", "ethiopia"], - "🇪🇺": ["flag_european_union", "european", "union", "flag", "banner"], - "🇫🇮": ["flag_finland", "fi", "flag", "nation", "country", "banner", "finland"], - "🇫🇯": ["flag_fiji", "fj", "flag", "nation", "country", "banner", "fiji"], - "🇫🇰": [ - "flag_falkland_islands", - "falkland", - "islands", - "malvinas", - "flag", - "nation", - "country", - "banner", - "falkland_islands" - ], - "🇫🇲": [ - "flag_micronesia", - "micronesia", - "federated", - "states", - "flag", - "nation", - "country", - "banner" - ], - "🇫🇴": [ - "flag_faroe_islands", - "faroe", - "islands", - "flag", - "nation", - "country", - "banner", - "faroe_islands" - ], - "🇫🇷": ["flag_france", "banner", "flag", "nation", "france", "french", "country"], - "🇬🇦": ["flag_gabon", "ga", "flag", "nation", "country", "banner", "gabon"], - "🇬🇧": [ - "flag_united_kingdom", - "united", - "kingdom", - "great", - "britain", - "northern", - "ireland", - "flag", - "nation", - "country", - "banner", - "british", - "UK", - "english", - "england", - "union jack", - "united_kingdom" - ], - "🇬🇩": ["flag_grenada", "gd", "flag", "nation", "country", "banner", "grenada"], - "🇬🇪": ["flag_georgia", "ge", "flag", "nation", "country", "banner", "georgia"], - "🇬🇫": [ - "flag_french_guiana", - "french", - "guiana", - "flag", - "nation", - "country", - "banner", - "french_guiana" - ], - "🇬🇬": ["flag_guernsey", "gg", "flag", "nation", "country", "banner", "guernsey"], - "🇬🇭": ["flag_ghana", "gh", "flag", "nation", "country", "banner", "ghana"], - "🇬🇮": ["flag_gibraltar", "gi", "flag", "nation", "country", "banner", "gibraltar"], - "🇬🇱": ["flag_greenland", "gl", "flag", "nation", "country", "banner", "greenland"], - "🇬🇲": ["flag_gambia", "gm", "flag", "nation", "country", "banner", "gambia"], - "🇬🇳": ["flag_guinea", "gn", "flag", "nation", "country", "banner", "guinea"], - "🇬🇵": ["flag_guadeloupe", "gp", "flag", "nation", "country", "banner", "guadeloupe"], - "🇬🇶": [ - "flag_equatorial_guinea", - "equatorial", - "gn", - "flag", - "nation", - "country", - "banner", - "equatorial_guinea" - ], - "🇬🇷": ["flag_greece", "gr", "flag", "nation", "country", "banner", "greece"], - "🇬🇸": [ - "flag_south_georgia_south_sandwich_islands", - "south", - "georgia", - "sandwich", - "islands", - "flag", - "nation", - "country", - "banner", - "south_georgia_south_sandwich_islands" - ], - "🇬🇹": ["flag_guatemala", "gt", "flag", "nation", "country", "banner", "guatemala"], - "🇬🇺": ["flag_guam", "gu", "flag", "nation", "country", "banner", "guam"], - "🇬🇼": [ - "flag_guinea_bissau", - "gw", - "bissau", - "flag", - "nation", - "country", - "banner", - "guinea_bissau" - ], - "🇬🇾": ["flag_guyana", "gy", "flag", "nation", "country", "banner", "guyana"], - "🇭🇰": [ - "flag_hong_kong_sar_china", - "hong", - "kong", - "flag", - "nation", - "country", - "banner", - "hong_kong_sar_china" - ], - "🇭🇲": ["flag_heard_mcdonald_islands"], - "🇭🇳": ["flag_honduras", "hn", "flag", "nation", "country", "banner", "honduras"], - "🇭🇷": ["flag_croatia", "hr", "flag", "nation", "country", "banner", "croatia"], - "🇭🇹": ["flag_haiti", "ht", "flag", "nation", "country", "banner", "haiti"], - "🇭🇺": ["flag_hungary", "hu", "flag", "nation", "country", "banner", "hungary"], - "🇮🇨": [ - "flag_canary_islands", - "canary", - "islands", - "flag", - "nation", - "country", - "banner", - "canary_islands" - ], - "🇮🇩": ["flag_indonesia", "flag", "nation", "country", "banner", "indonesia"], - "🇮🇪": ["flag_ireland", "ie", "flag", "nation", "country", "banner", "ireland"], - "🇮🇱": ["flag_israel", "il", "flag", "nation", "country", "banner", "israel"], - "🇮🇲": ["flag_isle_of_man", "isle", "man", "flag", "nation", "country", "banner", "isle_of_man"], - "🇮🇳": ["flag_india", "in", "flag", "nation", "country", "banner", "india"], - "🇮🇴": [ - "flag_british_indian_ocean_territory", - "british", - "indian", - "ocean", - "territory", - "flag", - "nation", - "country", - "banner", - "british_indian_ocean_territory" - ], - "🇮🇶": ["flag_iraq", "iq", "flag", "nation", "country", "banner", "iraq"], - "🇮🇷": ["flag_iran", "iran", "islamic", "republic", "flag", "nation", "country", "banner"], - "🇮🇸": ["flag_iceland", "is", "flag", "nation", "country", "banner", "iceland"], - "🇮🇹": ["flag_italy", "italy", "flag", "nation", "country", "banner"], - "🇯🇪": ["flag_jersey", "je", "flag", "nation", "country", "banner", "jersey"], - "🇯🇲": ["flag_jamaica", "jm", "flag", "nation", "country", "banner", "jamaica"], - "🇯🇴": ["flag_jordan", "jo", "flag", "nation", "country", "banner", "jordan"], - "🇯🇵": ["flag_japan", "japanese", "nation", "flag", "country", "banner", "japan", "jp", "ja"], - "🇰🇪": ["flag_kenya", "ke", "flag", "nation", "country", "banner", "kenya"], - "🇰🇬": ["flag_kyrgyzstan", "kg", "flag", "nation", "country", "banner", "kyrgyzstan"], - "🇰🇭": ["flag_cambodia", "kh", "flag", "nation", "country", "banner", "cambodia"], - "🇰🇮": ["flag_kiribati", "ki", "flag", "nation", "country", "banner", "kiribati"], - "🇰🇲": ["flag_comoros", "km", "flag", "nation", "country", "banner", "comoros"], - "🇰🇳": [ - "flag_st_kitts_nevis", - "saint", - "kitts", - "nevis", - "flag", - "nation", - "country", - "banner", - "st_kitts_nevis" - ], - "🇰🇵": [ - "flag_north_korea", - "north", - "korea", - "nation", - "flag", - "country", - "banner", - "north_korea" - ], - "🇰🇷": [ - "flag_south_korea", - "south", - "korea", - "nation", - "flag", - "country", - "banner", - "south_korea" - ], - "🇰🇼": ["flag_kuwait", "kw", "flag", "nation", "country", "banner", "kuwait"], - "🇰🇾": [ - "flag_cayman_islands", - "cayman", - "islands", - "flag", - "nation", - "country", - "banner", - "cayman_islands" - ], - "🇰🇿": ["flag_kazakhstan", "kz", "flag", "nation", "country", "banner", "kazakhstan"], - "🇱🇦": [ - "flag_laos", - "lao", - "democratic", - "republic", - "flag", - "nation", - "country", - "banner", - "laos" - ], - "🇱🇧": ["flag_lebanon", "lb", "flag", "nation", "country", "banner", "lebanon"], - "🇱🇨": ["flag_st_lucia", "saint", "lucia", "flag", "nation", "country", "banner", "st_lucia"], - "🇱🇮": ["flag_liechtenstein", "li", "flag", "nation", "country", "banner", "liechtenstein"], - "🇱🇰": ["flag_sri_lanka", "sri", "lanka", "flag", "nation", "country", "banner", "sri_lanka"], - "🇱🇷": ["flag_liberia", "lr", "flag", "nation", "country", "banner", "liberia"], - "🇱🇸": ["flag_lesotho", "ls", "flag", "nation", "country", "banner", "lesotho"], - "🇱🇹": ["flag_lithuania", "lt", "flag", "nation", "country", "banner", "lithuania"], - "🇱🇺": ["flag_luxembourg", "lu", "flag", "nation", "country", "banner", "luxembourg"], - "🇱🇻": ["flag_latvia", "lv", "flag", "nation", "country", "banner", "latvia"], - "🇱🇾": ["flag_libya", "ly", "flag", "nation", "country", "banner", "libya"], - "🇲🇦": ["flag_morocco", "ma", "flag", "nation", "country", "banner", "morocco"], - "🇲🇨": ["flag_monaco", "mc", "flag", "nation", "country", "banner", "monaco"], - "🇲🇩": ["flag_moldova", "moldova", "republic", "flag", "nation", "country", "banner"], - "🇲🇪": ["flag_montenegro", "me", "flag", "nation", "country", "banner", "montenegro"], - "🇲🇫": ["flag_st_martin"], - "🇲🇬": ["flag_madagascar", "mg", "flag", "nation", "country", "banner", "madagascar"], - "🇲🇭": [ - "flag_marshall_islands", - "marshall", - "islands", - "flag", - "nation", - "country", - "banner", - "marshall_islands" - ], - "🇲🇰": [ - "flag_north_macedonia", - "macedonia", - "flag", - "nation", - "country", - "banner", - "north_macedonia" - ], - "🇲🇱": ["flag_mali", "ml", "flag", "nation", "country", "banner", "mali"], - "🇲🇲": ["flag_myanmar", "mm", "flag", "nation", "country", "banner", "myanmar"], - "🇲🇳": ["flag_mongolia", "mn", "flag", "nation", "country", "banner", "mongolia"], - "🇲🇴": ["flag_macao_sar_china", "macao", "flag", "nation", "country", "banner", "macao_sar_china"], - "🇲🇵": [ - "flag_northern_mariana_islands", - "northern", - "mariana", - "islands", - "flag", - "nation", - "country", - "banner", - "northern_mariana_islands" - ], - "🇲🇶": ["flag_martinique", "mq", "flag", "nation", "country", "banner", "martinique"], - "🇲🇷": ["flag_mauritania", "mr", "flag", "nation", "country", "banner", "mauritania"], - "🇲🇸": ["flag_montserrat", "ms", "flag", "nation", "country", "banner", "montserrat"], - "🇲🇹": ["flag_malta", "mt", "flag", "nation", "country", "banner", "malta"], - "🇲🇺": ["flag_mauritius", "mu", "flag", "nation", "country", "banner", "mauritius"], - "🇲🇻": ["flag_maldives", "mv", "flag", "nation", "country", "banner", "maldives"], - "🇲🇼": ["flag_malawi", "mw", "flag", "nation", "country", "banner", "malawi"], - "🇲🇽": ["flag_mexico", "mx", "flag", "nation", "country", "banner", "mexico"], - "🇲🇾": ["flag_malaysia", "my", "flag", "nation", "country", "banner", "malaysia"], - "🇲🇿": ["flag_mozambique", "mz", "flag", "nation", "country", "banner", "mozambique"], - "🇳🇦": ["flag_namibia", "na", "flag", "nation", "country", "banner", "namibia"], - "🇳🇨": [ - "flag_new_caledonia", - "new", - "caledonia", - "flag", - "nation", - "country", - "banner", - "new_caledonia" - ], - "🇳🇪": ["flag_niger", "ne", "flag", "nation", "country", "banner", "niger"], - "🇳🇫": [ - "flag_norfolk_island", - "norfolk", - "island", - "flag", - "nation", - "country", - "banner", - "norfolk_island" - ], - "🇳🇬": ["flag_nigeria", "flag", "nation", "country", "banner", "nigeria"], - "🇳🇮": ["flag_nicaragua", "ni", "flag", "nation", "country", "banner", "nicaragua"], - "🇳🇱": ["flag_netherlands", "nl", "flag", "nation", "country", "banner", "netherlands"], - "🇳🇴": ["flag_norway", "no", "flag", "nation", "country", "banner", "norway"], - "🇳🇵": ["flag_nepal", "np", "flag", "nation", "country", "banner", "nepal"], - "🇳🇷": ["flag_nauru", "nr", "flag", "nation", "country", "banner", "nauru"], - "🇳🇺": ["flag_niue", "nu", "flag", "nation", "country", "banner", "niue"], - "🇳🇿": [ - "flag_new_zealand", - "new", - "zealand", - "flag", - "nation", - "country", - "banner", - "new_zealand" - ], - "🇴🇲": ["flag_oman", "om_symbol", "flag", "nation", "country", "banner", "oman"], - "🇵🇦": ["flag_panama", "pa", "flag", "nation", "country", "banner", "panama"], - "🇵🇪": ["flag_peru", "pe", "flag", "nation", "country", "banner", "peru"], - "🇵🇫": [ - "flag_french_polynesia", - "french", - "polynesia", - "flag", - "nation", - "country", - "banner", - "french_polynesia" - ], - "🇵🇬": [ - "flag_papua_new_guinea", - "papua", - "new", - "guinea", - "flag", - "nation", - "country", - "banner", - "papua_new_guinea" - ], - "🇵🇭": ["flag_philippines", "ph", "flag", "nation", "country", "banner", "philippines"], - "🇵🇰": ["flag_pakistan", "pk", "flag", "nation", "country", "banner", "pakistan"], - "🇵🇱": ["flag_poland", "pl", "flag", "nation", "country", "banner", "poland"], - "🇵🇲": [ - "flag_st_pierre_miquelon", - "saint", - "pierre", - "miquelon", - "flag", - "nation", - "country", - "banner", - "st_pierre_miquelon" - ], - "🇵🇳": [ - "flag_pitcairn_islands", - "pitcairn", - "flag", - "nation", - "country", - "banner", - "pitcairn_islands" - ], - "🇵🇷": [ - "flag_puerto_rico", - "puerto", - "rico", - "flag", - "nation", - "country", - "banner", - "puerto_rico" - ], - "🇵🇸": [ - "flag_palestinian_territories", - "palestine", - "palestinian", - "territories", - "flag", - "nation", - "country", - "banner", - "palestinian_territories" - ], - "🇵🇹": ["flag_portugal", "pt", "flag", "nation", "country", "banner", "portugal"], - "🇵🇼": ["flag_palau", "pw", "flag", "nation", "country", "banner", "palau"], - "🇵🇾": ["flag_paraguay", "py", "flag", "nation", "country", "banner", "paraguay"], - "🇶🇦": ["flag_qatar", "qa", "flag", "nation", "country", "banner", "qatar"], - "🇷🇪": ["flag_reunion", "réunion", "flag", "nation", "country", "banner", "reunion"], - "🇷🇴": ["flag_romania", "ro", "flag", "nation", "country", "banner", "romania"], - "🇷🇸": ["flag_serbia", "rs", "flag", "nation", "country", "banner", "serbia"], - "🇷🇺": ["flag_russia", "russian", "federation", "flag", "nation", "country", "banner", "russia"], - "🇷🇼": ["flag_rwanda", "rw", "flag", "nation", "country", "banner", "rwanda"], - "🇸🇦": ["flag_saudi_arabia", "flag", "nation", "country", "banner", "saudi_arabia"], - "🇸🇧": [ - "flag_solomon_islands", - "solomon", - "islands", - "flag", - "nation", - "country", - "banner", - "solomon_islands" - ], - "🇸🇨": ["flag_seychelles", "sc", "flag", "nation", "country", "banner", "seychelles"], - "🇸🇩": ["flag_sudan", "sd", "flag", "nation", "country", "banner", "sudan"], - "🇸🇪": ["flag_sweden", "se", "flag", "nation", "country", "banner", "sweden"], - "🇸🇬": ["flag_singapore", "sg", "flag", "nation", "country", "banner", "singapore"], - "🇸🇭": [ - "flag_st_helena", - "saint", - "helena", - "ascension", - "tristan", - "cunha", - "flag", - "nation", - "country", - "banner", - "st_helena" - ], - "🇸🇮": ["flag_slovenia", "si", "flag", "nation", "country", "banner", "slovenia"], - "🇸🇯": ["flag_svalbard_jan_mayen"], - "🇸🇰": ["flag_slovakia", "sk", "flag", "nation", "country", "banner", "slovakia"], - "🇸🇱": [ - "flag_sierra_leone", - "sierra", - "leone", - "flag", - "nation", - "country", - "banner", - "sierra_leone" - ], - "🇸🇲": ["flag_san_marino", "san", "marino", "flag", "nation", "country", "banner", "san_marino"], - "🇸🇳": ["flag_senegal", "sn", "flag", "nation", "country", "banner", "senegal"], - "🇸🇴": ["flag_somalia", "so", "flag", "nation", "country", "banner", "somalia"], - "🇸🇷": ["flag_suriname", "sr", "flag", "nation", "country", "banner", "suriname"], - "🇸🇸": ["flag_south_sudan", "south", "sd", "flag", "nation", "country", "banner", "south_sudan"], - "🇸🇹": [ - "flag_sao_tome_principe", - "sao", - "tome", - "principe", - "flag", - "nation", - "country", - "banner", - "sao_tome_principe" - ], - "🇸🇻": [ - "flag_el_salvador", - "el", - "salvador", - "flag", - "nation", - "country", - "banner", - "el_salvador" - ], - "🇸🇽": [ - "flag_sint_maarten", - "sint", - "maarten", - "dutch", - "flag", - "nation", - "country", - "banner", - "sint_maarten" - ], - "🇸🇾": [ - "flag_syria", - "syrian", - "arab", - "republic", - "flag", - "nation", - "country", - "banner", - "syria" - ], - "🇸🇿": ["flag_eswatini", "sz", "flag", "nation", "country", "banner", "eswatini"], - "🇹🇦": ["flag_tristan_da_cunha"], - "🇹🇨": [ - "flag_turks_caicos_islands", - "turks", - "caicos", - "islands", - "flag", - "nation", - "country", - "banner", - "turks_caicos_islands" - ], - "🇹🇩": ["flag_chad", "td", "flag", "nation", "country", "banner", "chad"], - "🇹🇫": [ - "flag_french_southern_territories", - "french", - "southern", - "territories", - "flag", - "nation", - "country", - "banner", - "french_southern_territories" - ], - "🇹🇬": ["flag_togo", "tg", "flag", "nation", "country", "banner", "togo"], - "🇹🇭": ["flag_thailand", "th", "flag", "nation", "country", "banner", "thailand"], - "🇹🇯": ["flag_tajikistan", "tj", "flag", "nation", "country", "banner", "tajikistan"], - "🇹🇰": ["flag_tokelau", "tk", "flag", "nation", "country", "banner", "tokelau"], - "🇹🇱": [ - "flag_timor_leste", - "timor", - "leste", - "flag", - "nation", - "country", - "banner", - "timor_leste" - ], - "🇹🇲": ["flag_turkmenistan", "flag", "nation", "country", "banner", "turkmenistan"], - "🇹🇳": ["flag_tunisia", "tn", "flag", "nation", "country", "banner", "tunisia"], - "🇹🇴": ["flag_tonga", "to", "flag", "nation", "country", "banner", "tonga"], - "🇹🇷": ["flag_turkey", "turkey", "flag", "nation", "country", "banner"], - "🇹🇹": [ - "flag_trinidad_tobago", - "trinidad", - "tobago", - "flag", - "nation", - "country", - "banner", - "trinidad_tobago" - ], - "🇹🇻": ["flag_tuvalu", "flag", "nation", "country", "banner", "tuvalu"], - "🇹🇼": ["flag_taiwan", "tw", "flag", "nation", "country", "banner", "taiwan"], - "🇹🇿": ["flag_tanzania", "tanzania", "united", "republic", "flag", "nation", "country", "banner"], - "🇺🇦": ["flag_ukraine", "ua", "flag", "nation", "country", "banner", "ukraine"], - "🇺🇬": ["flag_uganda", "ug", "flag", "nation", "country", "banner", "uganda"], - "🇺🇲": ["flag_u_s_outlying_islands"], - "🇺🇳": ["flag_united_nations", "un", "flag", "banner"], - "🇺🇸": [ - "flag_united_states", - "united", - "states", - "america", - "flag", - "nation", - "country", - "banner", - "united_states" - ], - "🇺🇾": ["flag_uruguay", "uy", "flag", "nation", "country", "banner", "uruguay"], - "🇺🇿": ["flag_uzbekistan", "uz", "flag", "nation", "country", "banner", "uzbekistan"], - "🇻🇦": [ - "flag_vatican_city", - "vatican", - "city", - "flag", - "nation", - "country", - "banner", - "vatican_city" - ], - "🇻🇨": [ - "flag_st_vincent_grenadines", - "saint", - "vincent", - "grenadines", - "flag", - "nation", - "country", - "banner", - "st_vincent_grenadines" - ], - "🇻🇪": [ - "flag_venezuela", - "ve", - "bolivarian", - "republic", - "flag", - "nation", - "country", - "banner", - "venezuela" - ], - "🇻🇬": [ - "flag_british_virgin_islands", - "british", - "virgin", - "islands", - "bvi", - "flag", - "nation", - "country", - "banner", - "british_virgin_islands" - ], - "🇻🇮": [ - "flag_u_s_virgin_islands", - "virgin", - "islands", - "us", - "flag", - "nation", - "country", - "banner", - "u_s_virgin_islands" - ], - "🇻🇳": ["flag_vietnam", "viet", "nam", "flag", "nation", "country", "banner", "vietnam"], - "🇻🇺": ["flag_vanuatu", "vu", "flag", "nation", "country", "banner", "vanuatu"], - "🇼🇫": [ - "flag_wallis_futuna", - "wallis", - "futuna", - "flag", - "nation", - "country", - "banner", - "wallis_futuna" - ], - "🇼🇸": ["flag_samoa", "ws", "flag", "nation", "country", "banner", "samoa"], - "🇽🇰": ["flag_kosovo", "xk", "flag", "nation", "country", "banner", "kosovo"], - "🇾🇪": ["flag_yemen", "ye", "flag", "nation", "country", "banner", "yemen"], - "🇾🇹": ["flag_mayotte", "yt", "flag", "nation", "country", "banner", "mayotte"], - "🇿🇦": [ - "flag_south_africa", - "south", - "africa", - "flag", - "nation", - "country", - "banner", - "south_africa" - ], - "🇿🇲": ["flag_zambia", "zm", "flag", "nation", "country", "banner", "zambia"], - "🇿🇼": ["flag_zimbabwe", "zw", "flag", "nation", "country", "banner", "zimbabwe"], - "🏴󠁧󠁢󠁥󠁮󠁧󠁿": ["flag_england", "flag", "english"], - "🏴󠁧󠁢󠁳󠁣󠁴󠁿": ["flag_scotland", "flag", "scottish"], - "🏴󠁧󠁢󠁷󠁬󠁳󠁿": ["flag_wales", "flag", "welsh"], - "🥲": ["smiling face with tear", "sad", "cry", "pretend"], - "🥸": ["disguised face", "pretent", "brows", "glasses", "moustache"], - "🤌": ["pinched fingers", "size", "tiny", "small"], - "🫀": ["anatomical heart", "health", "heartbeat"], - "🫁": ["lungs", "breathe"], - "🥷": ["ninja", "ninjutsu", "skills", "japanese"], - "🤵‍♂️": ["man in tuxedo", "formal", "fashion"], - "🤵‍♀️": ["woman in tuxedo", "formal", "fashion"], - "👰‍♂️": ["man with veil", "wedding", "marriage"], - "👰‍♀️": ["woman with veil", "wedding", "marriage"], - "👩‍🍼": ["woman feeding baby", "birth", "food"], - "👨‍🍼": ["man feeding baby", "birth", "food"], - "🧑‍🍼": ["person feeding baby", "birth", "food"], - "🧑‍🎄": ["mx claus", "christmas"], - "🫂": ["people hugging", "care"], - "🐈‍⬛": ["black cat", "superstition", "luck"], - "🦬": ["bison", "ox"], - "🦣": ["mammoth", "elephant", "tusks"], - "🦫": ["beaver", "animal", "rodent"], - "🐻‍❄️": ["polar bear", "animal", "arctic"], - "🦤": ["dodo", "animal", "bird"], - "🪶": ["feather", "bird", "fly"], - "🦭": ["seal", "animal", "creature", "sea"], - "🪲": ["beetle", "insect"], - "🪳": ["cockroach", "insect", "pests"], - "🪰": ["fly", "insect"], - "🪱": ["worm", "animal"], - "🪴": ["potted plant", "greenery", "house"], - "🫐": ["blueberries", "fruit"], - "🫒": ["olive", "fruit"], - "🫑": ["bell pepper", "fruit", "plant"], - "🫓": ["flatbread", "flour", "food", "bakery"], - "🫔": ["tamale", "food", "masa"], - "🫕": ["fondue", "cheese", "pot", "food"], - "🫖": ["teapot", "drink", "hot"], - "🧋": ["bubble tea", "taiwan", "boba", "milk tea", "straw"], - "🪨": ["rock", "stone"], - "🪵": ["wood", "nature", "timber", "trunk"], - "🛖": ["hut", "house", "structure"], - "🛻": ["pickup truck", "car", "transportation"], - "🛼": ["roller skate", "footwear", "sports"], - "🪄": ["magic wand", "supernature", "power"], - "🪅": ["pinata", "mexico", "candy", "celebration"], - "🪆": ["nesting dolls", "matryoshka", "toy"], - "🪡": ["sewing needle", "stitches"], - "🪢": ["knot", "rope", "scout"], - "🩴": ["thong sandal", "footwear", "summer"], - "🪖": ["military helmet", "army", "protection"], - "🪗": ["accordion", "music"], - "🪘": ["long drum", "music"], - "🪙": ["coin", "money", "currency"], - "🪃": ["boomerang", "weapon"], - "🪚": ["carpentry saw", "cut", "chop"], - "🪛": ["screwdriver", "tools"], - "🪝": ["hook", "tools"], - "🪜": ["ladder", "tools"], - "🛗": ["elevator", "lift"], - "🪞": ["mirror", "reflection"], - "🪟": ["window", "scenery"], - "🪠": ["plunger", "toilet"], - "🪤": ["mouse trap", "cheese"], - "🪣": ["bucket", "water", "container"], - "🪥": ["toothbrush", "hygiene", "dental"], - "🪦": ["headstone", "death", "rip", "grave"], - "🪧": ["placard", "announcement"], - "⚧️": ["transgender symbol", "transgender", "lgbtq"], - "🏳️‍⚧️": ["transgender flag", "transgender", "flag", "pride", "lgbtq"], - "😶‍🌫️": ["face in clouds", "shower", "steam", "dream"], - "😮‍💨": ["face exhaling", "relieve", "relief", "tired", "sigh"], - "😵‍💫": ["face with spiral eyes", "sick", "ill", "confused", "nauseous", "nausea"], - "❤️‍🔥": ["heart on fire", "passionate", "enthusiastic"], - "❤️‍🩹": ["mending heart", "broken heart", "bandage", "wounded"], - "🧔‍♂️": ["man beard", "facial hair"], - "🧔‍♀️": ["woman beard", "facial hair"], - "🫠": ["melting face", "hot", "heat"], - "🫢": ["face with open eyes and hand over mouth", "silence", "secret", "shock", "surprise"], - "🫣": ["face with peeking eye", "scared", "frightening", "embarrassing", "shy"], - "🫡": ["saluting face", "respect", "salute"], - "🫥": ["dotted line face", "invisible", "lonely", "isolation", "depression"], - "🫤": ["face with diagonal mouth", "skeptic", "confuse", "frustrated", "indifferent"], - "🥹": ["face holding back tears", "touched", "gratitude", "cry"], - "🫱": ["rightwards hand", "palm", "offer"], - "🫲": ["leftwards hand", "palm", "offer"], - "🫳": ["palm down hand", "palm", "drop"], - "🫴": ["palm up hand", "lift", "offer", "demand"], - "🫰": ["hand with index finger and thumb crossed", "heart", "love", "money", "expensive"], - "🫵": ["index pointing at the viewer", "you", "recruit"], - "🫶": ["heart hands", "love", "appreciation", "support"], - "🫦": ["biting lip", "flirt", "sexy", "pain", "worry"], - "🫅": ["person with crown", "royalty", "power"], - "🫃": ["pregnant man", "baby", "belly"], - "🫄": ["pregnant person", "baby", "belly"], - "🧌": ["troll", "mystical", "monster"], - "🪸": ["coral", "ocean", "sea", "reef"], - "🪷": ["lotus", "flower", "calm", "meditation"], - "🪹": ["empty nest", "bird"], - "🪺": ["nest with eggs", "bird"], - "🫘": ["beans", "food"], - "🫗": ["pouring liquid", "cup", "water"], - "🫙": ["jar", "container", "sauce"], - "🛝": ["playground slide", "fun", "park"], - "🛞": ["wheel", "car", "transport"], - "🛟": ["ring buoy", "life saver", "life preserver"], - "🪬": ["hamsa", "religion", "protection"], - "🪩": ["mirror ball", "disco", "dance", "party"], - "🪫": ["low battery", "drained", "dead"], - "🩼": ["crutch", "accessibility", "assist"], - "🩻": ["x-ray", "skeleton", "medicine"], - "🫧": ["bubbles", "soap", "fun", "carbonation", "sparkling"], - "🪪": ["identification card", "document"], - "🟰": ["heavy equals sign", "math"], - "🫨": ["shaking face", "dizzy", "shock", "blurry", "earthquake"], - "🩷": ["pink heart", "valentines"], - "🩵": ["light blue heart", "ice", "baby blue"], - "🩶": ["grey heart", "silver", "monochrome"], - "🫷": ["leftwards pushing hand", "highfive", "pressing", "stop"], - "🫸": ["rightwards pushing hand", "highfive", "pressing", "stop"], - "🫎": ["moose", "shrek", "canada", "sweden", "sven", "cool"], - "🫏": ["donkey", "eeyore", "mule"], - "🪽": ["wing", "angel", "birds", "flying"], - "🐦‍⬛": ["black bird", "crow"], - "🪿": ["goose", "silly", "jemima", "goosebumps"], - "🪼": ["jellyfish", "sting", "tentacles"], - "🪻": ["hyacinth", "flower", "lavender"], - "🫚": ["ginger root", "spice", "yellow", "cooking", "gingerbread"], - "🫛": ["pea pod", "cozy", "green"], - "🪭": ["folding hand fan", "flamenco", "hot"], - "🪮": ["hair pick", "afro", "comb"], - "🪇": ["maracas", "music", "instrument", "percussion"], - "🪈": ["flute", "bamboo", "music", "instrument", "pied piper"], - "🪯": ["khanda", "Sikhism", "religion"], - "🛜": ["wireless", "wifi", "internet", "contactless", "signal"] -} diff --git a/packages/studiocms_renderers/src/lib/marked/index.ts b/packages/studiocms_renderers/src/lib/marked/index.ts deleted file mode 100644 index 0c75b9e7f..000000000 --- a/packages/studiocms_renderers/src/lib/marked/index.ts +++ /dev/null @@ -1,163 +0,0 @@ -import rendererConfig from 'studiocms:renderer/config'; -import { - transformerMetaHighlight, - transformerMetaWordHighlight, - transformerNotationDiff, - transformerNotationErrorLevel, - transformerNotationFocus, - transformerNotationHighlight, - transformerNotationWordHighlight, -} from '@shikijs/transformers'; -import { type MarkedExtension, marked } from 'marked'; -import markedAlert from 'marked-alert'; -import { markedEmoji } from 'marked-emoji'; -import markedFootnote from 'marked-footnote'; -import markedShiki from 'marked-shiki'; -import { markedSmartypants } from 'marked-smartypants'; -import { createHighlighterCore } from 'shiki/core'; -import getWasm from 'shiki/wasm'; -import emojiList from './emoji-en-US.json'; - -// Load markedConfig from rendererConfig -const { loadmarkedExtensions, includedExtensions, highlighterConfig } = rendererConfig.markedConfig; - -/** - * Create a Map of Emoji Names to Emoji Characters - */ -export const emojiMap = Object.entries(emojiList).reduce( - (ret, [emoji, names]) => { - for (const name of names) { - ret[name] = ret[name] || emoji; - } - return ret; - }, - {} as Record -); - -/** - * Render Markdown Content using Marked - * - * Marked is A markdown parser and compiler. Built for speed. - * @see https://marked.js.org/ for more info about marked. - * - * @param input Markdown Content - * @returns Converted HTML Content - */ -export async function renderMarked(input: string): Promise { - const customMarkedExtList: MarkedExtension[] = []; - - // MarkedAlert Extension - if (includedExtensions.markedAlert) { - customMarkedExtList.push(markedAlert()); - } - - // MarkedEmoji Extension - if (includedExtensions.markedEmoji) { - customMarkedExtList.push(markedEmoji({ emojis: emojiMap, unicode: true })); - } - - // MarkedFootnote Extension - if (includedExtensions.markedFootnote) { - customMarkedExtList.push(markedFootnote()); - } - - // MarkedSmartypants Extension - if (includedExtensions.markedSmartypants) { - customMarkedExtList.push(markedSmartypants()); - } - - // Setup Marked Highlighter - if (highlighterConfig.highlighter === 'shiki') { - // Create Shiki Highlighter - - const { shikiConfig } = highlighterConfig; - - // Load Shiki Themes - const shikiThemeConfig = []; - - // Set the Default Bundle Themes - const studioCMSDefaultThemes = [ - import('shiki/themes/houston.mjs'), - import('shiki/themes/github-dark.mjs'), - import('shiki/themes/github-light.mjs'), - import('shiki/themes/night-owl.mjs'), - ]; - - // Add the Default Themes to the Config - shikiThemeConfig.push(...studioCMSDefaultThemes); - - // Add any additional UserLoaded Themes to the Config - if (shikiConfig.loadThemes) { - shikiThemeConfig.push(...shikiConfig.loadThemes); - } - - // Load Shiki Languages - const shikiLangsConfig = []; - - // Set the Default Bundle Languages - const studioCMSDefaultLangs = [ - import('shiki/langs/astro.mjs'), - import('shiki/langs/typescript.mjs'), - import('shiki/langs/javascript.mjs'), - import('shiki/langs/json.mjs'), - import('shiki/langs/css.mjs'), - import('shiki/langs/html.mjs'), - import('shiki/langs/markdown.mjs'), - import('shiki/langs/mdx.mjs'), - import('shiki/langs/yaml.mjs'), - import('shiki/langs/rust.mjs'), - import('shiki/langs/python.mjs'), - import('shiki/langs/go.mjs'), - ]; - - // Add the Default Languages to the Config - shikiLangsConfig.push(...studioCMSDefaultLangs); - - // Add any additional UserLoaded Languages to the Config - if (shikiConfig.loadLangs) { - shikiLangsConfig.push(...shikiConfig.loadLangs); - } - - // Create Shiki Highlighter - const shikiHighlighter = await createHighlighterCore({ - themes: shikiThemeConfig, - langs: shikiLangsConfig, - loadWasm: getWasm, - }); - - // Add Shiki to the Custom Marked Extensions - customMarkedExtList.push( - markedShiki({ - highlight(code, lang, props) { - return shikiHighlighter.codeToHtml(code, { - lang, - theme: shikiConfig.theme, - meta: { __raw: props.join(' ') }, // required by `transformerMeta*` - transformers: [ - transformerNotationDiff(), - transformerNotationHighlight(), - transformerNotationWordHighlight(), - transformerNotationFocus(), - transformerNotationErrorLevel(), - transformerMetaHighlight(), - transformerMetaWordHighlight(), - ], - }); - }, - }) - ); - } - - // Load any additional User-Defined marked extensions - if (loadmarkedExtensions) { - customMarkedExtList.push(...loadmarkedExtensions); - } - - marked.use(...customMarkedExtList, { async: true, gfm: true }); - - const content = await marked.parse(input); - - return content; -} - -export default renderMarked; diff --git a/packages/studiocms_renderers/src/lib/mdx/index.ts b/packages/studiocms_renderers/src/lib/mdx.ts similarity index 94% rename from packages/studiocms_renderers/src/lib/mdx/index.ts rename to packages/studiocms_renderers/src/lib/mdx.ts index c2f47c06c..2628af279 100644 --- a/packages/studiocms_renderers/src/lib/mdx/index.ts +++ b/packages/studiocms_renderers/src/lib/mdx.ts @@ -50,9 +50,7 @@ export async function renderAstroMDX(content: string) { }); // Render the MDX content to a string - const renderedContent = renderToString(createElement(MDXContent)); - - return renderedContent; + return renderToString(createElement(MDXContent)); } export default renderAstroMDX; diff --git a/packages/studiocms_renderers/src/stubs/renderer-config.ts b/packages/studiocms_renderers/src/stubs/renderer-config.ts index 117ebd868..59ca049b6 100644 --- a/packages/studiocms_renderers/src/stubs/renderer-config.ts +++ b/packages/studiocms_renderers/src/stubs/renderer-config.ts @@ -4,24 +4,19 @@ import { createResolver } from 'astro-integration-kit'; // Create resolver relative to this file const { resolve } = createResolver(import.meta.url); -/** - * Generate the `config.d.ts` file - */ -export const rendererConfigDTS = () => { - const dtsFile = DTSBuilder(); +const rendererConfig = DTSBuilder(); - dtsFile.addSingleLineNote( - 'This file is generated by StudioCMS and should not be modified manually.' - ); +rendererConfig.addSingleLineNote( + 'This file is generated by StudioCMS and should not be modified manually.' +); - dtsFile.addModule('studiocms:renderer/config', { - defaultExport: { - singleLineDescription: 'Renderer Configuration', - typeDef: `import('${resolve('../index')}').StudioCMSRendererConfig`, - }, - }); +rendererConfig.addModule('studiocms:renderer/config', { + defaultExport: { + singleLineDescription: 'Renderer Configuration', + typeDef: `import('${resolve('../index')}').StudioCMSRendererConfig`, + }, +}); - const dtsText = dtsFile.makeAstroInjectedType('config.d.ts'); +const rendererConfigDTS = rendererConfig.makeAstroInjectedType('config.d.ts'); - return dtsText; -}; +export default rendererConfigDTS; diff --git a/packages/studiocms_renderers/src/stubs/renderer-markdownConfig.ts b/packages/studiocms_renderers/src/stubs/renderer-markdownConfig.ts index d57753d04..5f77ed395 100644 --- a/packages/studiocms_renderers/src/stubs/renderer-markdownConfig.ts +++ b/packages/studiocms_renderers/src/stubs/renderer-markdownConfig.ts @@ -1,21 +1,19 @@ import DTSBuilder from '@matthiesenxyz/astrodtsbuilder'; -/** - * Generate the `astroMarkdownConfig.d.ts` file - */ -export const rendererAstroMarkdownDTS = () => { - const dtsFile = DTSBuilder(); - dtsFile.addSingleLineNote( - 'This file is generated by StudioCMS and should not be modified manually.' - ); +const rendererMarkdownConfig = DTSBuilder(); - dtsFile.addModule('studiocms:renderer/astroMarkdownConfig', { - defaultExport: { - typeDef: `import('astro').AstroConfig['markdown']`, - }, - }); +rendererMarkdownConfig.addSingleLineNote( + 'This file is generated by StudioCMS and should not be modified manually.' +); - const dtsText = dtsFile.makeAstroInjectedType('astroMarkdownConfig.d.ts'); +rendererMarkdownConfig.addModule('studiocms:renderer/astroMarkdownConfig', { + defaultExport: { + typeDef: `import('astro').AstroUserConfig['markdown']`, + }, +}); - return dtsText; -}; +const rendererMarkdownConfigDTS = rendererMarkdownConfig.makeAstroInjectedType( + 'astroMarkdownConfig.d.ts' +); + +export default rendererMarkdownConfigDTS; diff --git a/packages/studiocms_renderers/src/stubs/renderer.ts b/packages/studiocms_renderers/src/stubs/renderer.ts index 200f6c088..1d69a3d9d 100644 --- a/packages/studiocms_renderers/src/stubs/renderer.ts +++ b/packages/studiocms_renderers/src/stubs/renderer.ts @@ -1,26 +1,51 @@ import DTSBuilder from '@matthiesenxyz/astrodtsbuilder'; +import { createResolver } from 'astro-integration-kit'; -/** - * Generate the `renderer.d.ts` file - */ -export const rendererDTS = (rendererPath: string) => { - const dtsFile = DTSBuilder(); - - dtsFile.addSingleLineNote( - 'This file is generated by StudioCMS and should not be modified manually.' - ); - - dtsFile.addModule('studiocms:renderer', { - namedExports: [ - { - name: 'StudioCMSRenderer', - multiLineDescription: ['# StudioCMS Content Renderer component'], - typeDef: `typeof import('${rendererPath}').default`, - }, - ], - }); - - const dtsText = dtsFile.makeAstroInjectedType('renderer.d.ts'); - - return dtsText; -}; +// Create resolver relative to this file +const { resolve } = createResolver(import.meta.url); + +// Import the Renderer Component +const RendererComponentTypes = resolve('../components/Renderer.d.ts'); + +const renderer = DTSBuilder(); + +renderer.addSingleLineNote( + 'This file is generated by StudioCMS and should not be modified manually.' +); + +renderer.addModule('studiocms:renderer', { + namedExports: [ + { + name: 'StudioCMSRenderer', + multiLineDescription: [ + '**StudioCMS Content Renderer component**', + '', + '@property {string} content - The content to render', + '@example', + '```astro', + '---', + "import { StudioCMSRenderer } from 'studiocms:renderer';", + '---', + '', + '```', + ], + typeDef: `typeof import('${RendererComponentTypes}').default`, + }, + ], +}); + +renderer.addModule('studiocms:renderer/current', { + namedExports: [ + { + name: 'contentRenderer', + typeDef: `typeof import('${resolve('../lib/contentRenderer.ts')}').contentRenderer`, + }, + ], + defaultExport: { + typeDef: `typeof import('${resolve('../lib/contentRenderer.ts')}').default`, + }, +}); + +const rendererDTS = renderer.makeAstroInjectedType('renderer.d.ts'); + +export default rendererDTS; diff --git a/packages/studiocms_renderers/tsconfig.json b/packages/studiocms_renderers/tsconfig.json index d8530b08e..f036c3c4b 100644 --- a/packages/studiocms_renderers/tsconfig.json +++ b/packages/studiocms_renderers/tsconfig.json @@ -1,32 +1,5 @@ { "extends": "astro/tsconfigs/strictest", "files": [], - "compilerOptions": { - "outDir": "../../.moon/cache/types/packages/studiocms_renderers", - "composite": true, - "noEmit": false, - "allowImportingTsExtensions": false, - "emitDeclarationOnly": false, - "paths": { - "node-playground/*": ["../../playgrounds/node/src/*"], - "@studiocms/core": ["../studiocms_core/src/index.ts"], - "@studiocms/core/*": ["../studiocms_core/src/*"] - } - }, - "references": [ - { - "path": "../../playgrounds/node" - }, - { - "path": "../studiocms_core" - } - ], - "include": [ - "./package.json", - "../../playgrounds/node/**/*", - "../../playgrounds/node/.astro/**/*", - "../studiocms_core/**/*", - "./**/*", - "./src/**/*.json" - ] + "include": ["./package.json", "./**/*", "./src/**/*.json"] } diff --git a/packages/studiocms_renderers/virtuals.d.ts b/packages/studiocms_renderers/virtuals.d.ts index d6333d962..c234c77da 100644 --- a/packages/studiocms_renderers/virtuals.d.ts +++ b/packages/studiocms_renderers/virtuals.d.ts @@ -1,83 +1 @@ -/** - * # DEV TIP - * - * Wanting to extend StudioCMS? You can do so by defining a new module in the `virtual:studiocms` namespace within your project with the following format: - * - * This module can also be delcared from `@astrolicious/studiocms`. - * - * @example - * declare module 'virtual:studiocms/config' { - * const Config: import('@astrolicious/studiocms').StudioCMSOptions; - * export default Config; - * } - */ -declare module 'virtual:studiocms/config' { - const Config: import('@studiocms/core/schemas').StudioCMSOptions; - export default Config; -} - -declare module 'studiocms:renderer/config' { - const Config: import('./src/index').StudioCMSRendererConfig; - export default Config; -} - -declare module 'studiocms:renderer/astroMarkdownConfig' { - const markdownConfig: import('astro').AstroConfig['markdown']; - export default markdownConfig; -} - -declare module 'virtual:studiocms/pluginSystem' { - export const externalNav: typeof import('@studiocms/core/lib').externalNavigation; - export const dashboardPageLinks: typeof import('@studiocms/core/lib').dashboardPageLinksMap; - export const pluginList: typeof import('@studiocms/core/lib').studioCMSPluginList; - export const customRenderers: string[]; -} - -declare module 'studiocms:components' { - export const Avatar: typeof import('@studiocms/core/components').Avatar; - export const FormattedDate: typeof import('@studiocms/core/components').FormattedDate; - export const Genericheader: typeof import('@studiocms/core/components').GenericHeader; - export const Navigation: typeof import('@studiocms/core/components').Navigation; -} - -declare module 'studiocms:helpers' { - export const authHelper: typeof import('@studiocms/core/helpers').authHelper; - export const urlGenFactory: typeof import('@studiocms/core/helpers').urlGenFactory; - export const toCamelCase: typeof import('@studiocms/core/helpers').toCamelCase; - export const toPascalCase: typeof import('@studiocms/core/helpers').toPascalCase; - export const pathWithBase: typeof import('@studiocms/core/helpers').pathWithBase; - export const fileWithBase: typeof import('@studiocms/core/helpers').fileWithBase; -} - -declare module 'studiocms:helpers/contentHelper' { - export const contentHelper: typeof import('@studiocms/core/helpers').contentHelper; - export const getSiteConfig: typeof import('@studiocms/core/helpers').getSiteConfig; - export const getPageList: typeof import('@studiocms/core/helpers').getPageList; - export const getUserList: typeof import('@studiocms/core/helpers').getUserList; - export const getUserById: typeof import('@studiocms/core/helpers').getUserById; - export type ContentHelperTempResponse = import( - '@studiocms/core/helpers' - ).ContentHelperTempResponse; - export type SiteConfigResponse = import('@studiocms/core/helpers').SiteConfigResponse; - export type pageDataReponse = import('@studiocms/core/helpers').pageDataReponse; - export type UserResponse = import('@studiocms/core/helpers').UserResponse; -} - -declare module 'studiocms:helpers/headDefaults' { - export const headDefaults: typeof import('@studiocms/core/helpers').headDefaults; -} - -declare module 'studiocms:helpers/routemap' { - export const getSluggedRoute: typeof import('@studiocms/core/helpers').getSluggedRoute; - export const getEditRoute: typeof import('@studiocms/core/helpers').getEditRoute; - export const getDeleteRoute: typeof import('@studiocms/core/helpers').getDeleteRoute; - export const makeNonDashboardRoute: typeof import( - '@studiocms/core/helpers' - ).makeNonDashboardRoute; - export const makeDashboardRoute: typeof import('@studiocms/core/helpers').makeDashboardRoute; - export const makeAPIDashboardRoute: typeof import( - '@studiocms/core/helpers' - ).makeAPIDashboardRoute; - export const StudioCMSRoutes: typeof import('@studiocms/core/helpers').StudioCMSRoutes; - export const sideBarLinkMap: typeof import('@studiocms/core/helpers').sideBarLinkMap; -} +/// diff --git a/packages/studiocms_robotstxt/LICENSE b/packages/studiocms_robotstxt/LICENSE index a30d1fbe4..94787104c 100644 --- a/packages/studiocms_robotstxt/LICENSE +++ b/packages/studiocms_robotstxt/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Astrolicious - StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares +Copyright (c) 2024 StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/studiocms_robotstxt/moon.yml b/packages/studiocms_robotstxt/moon.yml deleted file mode 100644 index f3357ec75..000000000 --- a/packages/studiocms_robotstxt/moon.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: 'typescript' -tags: ['astro', 'typescript'] - -project: - name: '@studiocms/robotstxt' - description: 'A CMS that bridges Astro with Astro DB & Studio' - maintainers: ['Adammatthiesen','jdtjenkins','dreyfus92'] \ No newline at end of file diff --git a/packages/studiocms_robotstxt/src/consts.ts b/packages/studiocms_robotstxt/src/consts.ts index b3d5aaee0..bb2392212 100644 --- a/packages/studiocms_robotstxt/src/consts.ts +++ b/packages/studiocms_robotstxt/src/consts.ts @@ -126,7 +126,7 @@ export type SearchEngineOptimization = { PrerenderLLC: 'prerender'; }; -export type UsertAgentType = +export type UserAgentType = | '*' | SearchEngines[keyof SearchEngines] | SocialNetwork[keyof SocialNetwork] diff --git a/packages/studiocms_robotstxt/src/core.ts b/packages/studiocms_robotstxt/src/core.ts index 76b02a3f5..5c5f24b7c 100644 --- a/packages/studiocms_robotstxt/src/core.ts +++ b/packages/studiocms_robotstxt/src/core.ts @@ -72,12 +72,12 @@ function throwMsg( ) { const sentenceHead = '\x1b[1mRefer:\x1b[22m'; - const failured = (message: string) => { - logger.info(`\x1b[31mFailured! [${message}]\x1b[39m`); + const failure = (message: string) => { + logger.info(`\x1b[31mFailure! [${message}]\x1b[39m`); }; const warn = (message: string) => { - logger.warn(`Skiped! [${message}].`); + logger.warn(`Skipped! [${message}].`); }; switch (type) { @@ -85,15 +85,15 @@ function throwMsg( warn(msg); break; case 'error': - failured(msg); + failure(msg); throw new Error(`${msg}`); case true: - failured(msg); + failure(msg); throw new Error( `${msg}\n${sentenceHead}\n Visit \x1b[4m${'https://developers.google.com/search/docs/crawling-indexing/robots/create-robots-txt#useful-robots.txt-rules'}\x1b[24m for instructions.` ); default: - failured(msg); + failure(msg); throw new Error( `${msg}\n${sentenceHead}\n Visit \x1b[4m${'https://yandex.com/support/webmaster/controlling-robot/robots-txt.html#recommend'}\x1b[24m for instructions.` ); @@ -158,7 +158,7 @@ export function generateContent( ? policy.userAgent : [policy.userAgent || '*']; for (const userAgent of userAgents) { - // skiped + // skipped if (userAgent) { content += `User-agent: ${userAgent}\n`; } diff --git a/packages/studiocms_robotstxt/src/index.ts b/packages/studiocms_robotstxt/src/index.ts index 6d0b99d7c..d1b8bf2b4 100644 --- a/packages/studiocms_robotstxt/src/index.ts +++ b/packages/studiocms_robotstxt/src/index.ts @@ -1,6 +1,6 @@ import type { AstroConfig, AstroIntegration } from 'astro'; import { name } from '../package.json'; -import type { UsertAgentType } from './consts'; +import type { UserAgentType } from './consts'; import fs from 'node:fs'; import { fileURLToPath } from 'node:url'; @@ -79,7 +79,7 @@ export interface PolicyOptions { * ``` * Verified bots, refer to [DITIG](https://www.ditig.com/robots-txt-template#regular-template) or [Cloudflare Radar](https://radar.cloudflare.com/traffic/verified-bots). */ - userAgent?: UsertAgentType | UsertAgentType[]; + userAgent?: UserAgentType | UserAgentType[]; /** * @description * [ At least one or more `allow` or `disallow` entries per rule ] Allows indexing site sections or individual pages. @@ -148,33 +148,42 @@ const defaultConfig: RobotsConfig = { host: false, policy: [ { - userAgent: '*', - allow: '/', + userAgent: ['*'], + allow: ['/'], + disallow: ['/dashboard/'], }, ], }; -export default function createRobotsIntegration(astroConfig: RobotsConfig): AstroIntegration { - let config: AstroConfig; +/** + * **Robots.txt Integration** + * + * A simple integration to generate a `robots.txt` file for your Astro project. + * + * @param astroConfig Robots Configuration + * @returns AstroIntegration + */ +export default function createRobotsIntegration(options?: RobotsConfig): AstroIntegration { + let astroConfig: AstroConfig; let finalSiteMapHref: string; let executionTime: number; - const megeredConfig = { ...defaultConfig, ...astroConfig }; + const config = { ...defaultConfig, ...options }; return { name, hooks: { 'astro:config:setup': ({ config: cfg }) => { - config = cfg; + astroConfig = cfg; }, 'astro:build:start': () => { - finalSiteMapHref = new URL(config.base, config.site).href; + finalSiteMapHref = new URL(astroConfig.base, astroConfig.site).href; }, 'astro:build:done': async ({ dir, logger }) => { executionTime = measureExecutionTime(() => { fs.writeFileSync( new URL('robots.txt', dir), - generateContent(megeredConfig, finalSiteMapHref, logger), + generateContent(config, finalSiteMapHref, logger), 'utf-8' ); }); diff --git a/packages/studiocms_robotstxt/src/robots.txt b/packages/studiocms_robotstxt/src/robots.txt deleted file mode 100644 index 84b9cab27..000000000 --- a/packages/studiocms_robotstxt/src/robots.txt +++ /dev/null @@ -1,200 +0,0 @@ -################################# ROBOTS.TXT ################################### -# # -# Alphabetically ordered whitelisting of legitimate web robots, which obey the # -# Robots Exclusion Standard (robots.txt). Each bot is shortly described in a # -# comment above the (list of) user-agent(s). Comment out or delete lines which # -# contain User-agents you do not wish to allow on your website. # -# Important: Blank lines are not allowed in the final robots.txt file! # -# Updates can be retrieved from: https://www.ditig.com/robots-txt-template # -# # -# This document is licensed with a CC BY-NC-SA 4.0 license. # -# # -# Last update: 2023-03-15 # -# # -################################################################################ -# so.com chinese search engine -User-agent: 360Spider -User-agent: 360Spider-Image -User-agent: 360Spider-Video - -# google.com landing page quality checks -User-agent: AdsBot-Google -User-agent: AdsBot-Google-Mobile - -# google.com app resource fetcher -User-agent: AdsBot-Google-Mobile-Apps - -# bing ads bot -User-agent: adidxbot - -# apple.com search engine -User-agent: Applebot -User-agent: AppleNewsBot - -# baidu.com chinese search engine -User-agent: Baiduspider -User-agent: Baiduspider-image -User-agent: Baiduspider-news -User-agent: Baiduspider-video - -# bing.com international search engine -User-agent: bingbot -User-agent: BingPreview - -# bublup.com suggestion/search engine -User-agent: BublupBot - -# commoncrawl.org open repository of web crawl data -User-agent: CCBot - -# cliqz.com german in-product search engine -User-agent: Cliqzbot - -# coccoc.com vietnamese search engine -User-agent: coccoc -User-agent: coccocbot-image -User-agent: coccocbot-web - -# daum.net korean search engine -User-agent: Daumoa - -# dazoo.fr french search engine -User-agent: Dazoobot - -# deusu.de german search engine -User-agent: DeuSu - -# duckduckgo.com international privacy search engine -User-agent: DuckDuckBot -User-agent: DuckDuckGo-Favicons-Bot - -# eurip.com european search engine -User-agent: EuripBot - -# exploratodo.com latin search engine -User-agent: Exploratodo - -# facebook.com social network -User-agent: facebookcatalog -User-agent: facebookexternalhit -User-agent: Facebot - -# feedly.com feed fetcher -User-agent: Feedly - -# findx.com european search engine -User-agent: Findxbot - -# goo.ne.jp japanese search engine -User-agent: gooblog - -# google.com international search engine -User-agent: Googlebot -User-agent: Googlebot-Image -User-agent: Googlebot-Mobile -User-agent: Googlebot-News -User-agent: Googlebot-Video - -# so.com chinese search engine -User-agent: HaoSouSpider - -# goo.ne.jp japanese search engine -User-agent: ichiro - -# istella.it italian search engine -User-agent: istellabot - -# jike.com / chinaso.com chinese search engine -User-agent: JikeSpider - -# lycos.com & hotbot.com international search engine -User-agent: Lycos - -# mail.ru russian search engine -User-agent: Mail.Ru - -# google.com adsense bot -User-agent: Mediapartners-Google - -# Preview bot for Microsoft products -User-agent: MicrosoftPreview - -# mojeek.com search engine -User-agent: MojeekBot - -# bing.com international search engine -User-agent: msnbot -User-agent: msnbot-media - -# orange.com international search engine -User-agent: OrangeBot - -# pinterest.com social networtk -User-agent: Pinterest - -# botje.nl dutch search engine -User-agent: Plukkie - -# qwant.com french search engine -User-agent: Qwantify - -# rambler.ru russian search engine -User-agent: Rambler - -# seznam.cz czech search engine -User-agent: SeznamBot - -# soso.com chinese search engine -User-agent: Sosospider - -# yahoo.com international search engine -User-agent: Slurp - -# sogou.com chinese search engine -User-agent: Sogou blog -User-agent: Sogou inst spider -User-agent: Sogou News Spider -User-agent: Sogou Orion spider -User-agent: Sogou spider2 -User-agent: Sogou web spider - -# sputnik.ru russian search engine -User-agent: SputnikBot - -# ask.com international search engine -User-agent: Teoma - -# twitter.com social media bot -User-agent: Twitterbot - -# whatsapp.com preview bot -User-agent: WhatsApp - -# wotbox.com international search engine -User-agent: wotbox - -# yacy.net p2p search software -User-agent: yacybot - -# yandex.com russian search engine -User-agent: Yandex -User-agent: YandexMobileBot - -# search.naver.com south korean search engine -User-agent: Yeti - -# yioop.com international search engine -User-agent: YioopBot - -# yooz.ir iranian search engine -User-agent: yoozBot - -# youdao.com chinese search engine -User-agent: YoudaoBot - -# crawling rule(s) for above bots -Disallow: - -# disallow all other bots -User-agent: * -Disallow: / \ No newline at end of file diff --git a/packages/studiocms_robotstxt/tsconfig.json b/packages/studiocms_robotstxt/tsconfig.json index 803f32d0a..d175d082a 100644 --- a/packages/studiocms_robotstxt/tsconfig.json +++ b/packages/studiocms_robotstxt/tsconfig.json @@ -2,25 +2,10 @@ "extends": "astro/tsconfigs/strictest", "files": [], "compilerOptions": { - "outDir": "../../.moon/cache/types/packages/studiocms_robotstxt", "composite": true, "noEmit": false, "allowImportingTsExtensions": false, - "emitDeclarationOnly": false, - "paths": { - "node-playground/*": ["../../playgrounds/node/src/*"] - } + "emitDeclarationOnly": false }, - "references": [ - { - "path": "../../playgrounds/node" - } - ], - "include": [ - "./package.json", - "../../playgrounds/node/**/*", - "../../playgrounds/node/.astro/**/*", - "./**/*", - "./src/**/*.json" - ] + "include": ["./package.json", "./**/*", "./src/**/*.json"] } diff --git a/packages/studiocms_ui/.gitignore b/packages/studiocms_ui/.gitignore deleted file mode 100644 index b512c09d4..000000000 --- a/packages/studiocms_ui/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules \ No newline at end of file diff --git a/packages/studiocms_ui/README.md b/packages/studiocms_ui/README.md deleted file mode 100644 index 28e24e8c5..000000000 --- a/packages/studiocms_ui/README.md +++ /dev/null @@ -1,564 +0,0 @@ -# @studiocms/ui - -This is the UI library that we use to build StudioCMS. - -All of these components can be tested [here](https://ui-testing.studiocms.dev). - -## Components & how to use them -All components are exported from `@studiocms/ui/components`. - -### Button -A simple button component. Use it like this: - -```html - -``` - -You can pass the following props: -```ts -type Props = { - as?: As; // What the button should be rendered as. Set to "a" for an anchor tag, etc. - size?: 'sm' | 'md' | 'lg'; - fullWidth?: boolean; // Make the button take up full width - color?: 'default' | 'primary' | 'success' | 'warning' | 'danger'; - variant?: 'solid' | 'outlined' | 'flat'; - class?: string; - disabled?: boolean; - // Note: you can pass all other HTML attributes and they will be applied. -}; -``` - -### Card -A simple card component with support for a header and footer. Use it like this: -```html - -
Header Content
-
Body Content
-
Footer Content
-
-``` - -You can pass the following props: -```ts -type Props = { - as?: As; - class?: string; - fullWidth?: boolean; - fullHeight?: boolean; -}; -``` - -### Center -A component that automatically centers all of it's content. Use it like this: -```html -
Content in here will be centered!
-``` - -### Checkbox -A checkbox component. Use it like this: -```html - -``` - -You can pass the following props: -```ts -type Props = { - label: string; - size?: 'sm' | 'md' | 'lg'; - color?: 'default' | 'primary' | 'success' | 'warning' | 'danger'; - defaultChecked?: boolean; - disabled?: boolean; - name?: string; // If you want to use this in forms. If unset, a name will be auto-generated. - isRequired?: boolean; -}; -``` - -### Divider -A simple divider. Use it like this: -```html -Divider Label -``` - -### Dropdown -A dropdown component. Use it like this: -```html - -
-
Your Trigger goes here!
-
-
-``` -**This component needs a helper to function.** Add it in a script tag: -```html - -``` -You can pass the following props: -```ts -type Option = { - label: string; - value: string; - disabled?: boolean; - color?: 'default' | 'primary' | 'success' | 'warning' | 'danger'; -}; - -type Props = { - id: string; - options: Option[]; - disabled?: boolean; - align?: 'start' | 'center' | 'end'; - triggerOn?: 'left' | 'right' | 'both'; -}; -``` - -### Input -A text input. Use it like this: -```html - -``` -You can pass the following props: -```ts -type Props = { - label?: string; - type?: 'text' | 'password'; - placeholder?: string; - isRequired?: boolean; - name?: string; - disabled?: boolean; - defaultValue?: string; - class?: string; -}; -``` - -### Modal -A modal component with form support. Use it like this: -```html - - -

Header content

-
Modal content
-
- -``` -**This component needs a helper to function.** Add it in a script tag: -```html - -``` -You can make the modal a form and also display "confirm" and "cancel" buttons individually: -```html - -

Header content

-
Modal content
-
- -``` -*Note: The order in which you supply the buttons does not change the buttons order in the modal.* - -After adding a button, you can register a callback that will be fired when a button is clicked. If you made the modal a form, this will also give you a way to access the form data: -```html - -``` -You can pass the following props: -```ts -type Props = { - id: string; - size?: 'sm' | 'md' | 'lg'; - dismissable?: boolean; - buttons?: ('confirm' | 'cancel')[]; - isForm?: boolean; -}; -``` - -### RadioGroup -A radio group. Use it like this: -```html - -``` -You can pass the following props: -```ts -type Option = { - label: string; - value: string; - disabled?: boolean; -}; - -type Props = { - label: string; - options: Option[]; - color?: 'default' | 'primary' | 'success' | 'warning' | 'danger'; - defaultValue?: string; // Needs to be the value of an option! - disabled?: boolean; - name?: string; - isRequired?: boolean; - horizontal?: boolean; - class?: string; -}; -``` - -### Row -A simple row component, essentially a flex div to wrap stuff in. Use it like this: -```html - - - - -``` -You can pass the following props: -```ts -type Props = { - alignCenter?: boolean; - gapSize?: 'sm' | 'md' | 'lg'; -}; -``` - -### Select -A select component. Use it like this: -```html - - - diff --git a/packages/studiocms_ui/src/components/ThemeToggle.astro b/packages/studiocms_ui/src/components/ThemeToggle.astro deleted file mode 100644 index bc6472b5c..000000000 --- a/packages/studiocms_ui/src/components/ThemeToggle.astro +++ /dev/null @@ -1,40 +0,0 @@ ---- -import type { ComponentProps } from "astro/types"; -import Button from "./Button.astro"; - -interface Props extends ComponentProps {}; - -const props = Astro.props; ---- - - - - - - diff --git a/packages/studiocms_ui/src/components/Toast/Toaster.astro b/packages/studiocms_ui/src/components/Toast/Toaster.astro deleted file mode 100644 index 513a415c1..000000000 --- a/packages/studiocms_ui/src/components/Toast/Toaster.astro +++ /dev/null @@ -1,327 +0,0 @@ ---- -type Props = { - position?: - | 'top-left' - | 'top-right' - | 'top-center' - | 'bottom-left' - | 'bottom-right' - | 'bottom-center'; - duration?: number; - closeButton?: boolean; - offset?: number; - gap?: number; -}; - -const { - position = 'top-center', - duration = 4000, - closeButton = false, - offset = 32, - gap = 8, -} = Astro.props; ---- -
-
-
- - - diff --git a/packages/studiocms_ui/src/components/Toast/index.ts b/packages/studiocms_ui/src/components/Toast/index.ts deleted file mode 100644 index 6a452210d..000000000 --- a/packages/studiocms_ui/src/components/Toast/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as Toaster } from './Toaster.astro'; -export { toast } from './toast'; diff --git a/packages/studiocms_ui/src/components/Toast/toast.ts b/packages/studiocms_ui/src/components/Toast/toast.ts deleted file mode 100644 index b3974e450..000000000 --- a/packages/studiocms_ui/src/components/Toast/toast.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { ToastProps } from '../../types'; - -/** - * A function to create toasts with. - - * @param props The props to pass to the toast - */ -function toast(props: ToastProps) { - const createToast = new CustomEvent('createtoast', { - detail: props, - }); - - document.dispatchEvent(createToast); -} - -export { toast }; diff --git a/packages/studiocms_ui/src/components/Toggle.astro b/packages/studiocms_ui/src/components/Toggle.astro deleted file mode 100644 index 959fc0e97..000000000 --- a/packages/studiocms_ui/src/components/Toggle.astro +++ /dev/null @@ -1,146 +0,0 @@ ---- -import type { StudioCMSColorway } from '../utils/colors'; -import { generateID } from '../utils/generateID'; - -type Props = { - label: string; - size?: 'sm' | 'md' | 'lg'; - color?: StudioCMSColorway; - defaultChecked?: boolean; - disabled?: boolean; - name?: string; - isRequired?: boolean; -}; - -const { - size = 'md', - color = 'default', - defaultChecked, - disabled, - name = generateID('checkbox'), - label, - isRequired, -} = Astro.props; ---- -