@stacksjs/defaults 0.70.207 → 0.70.208
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/TODO.md +21 -0
- package/ai/AGENTS.md +79 -0
- package/ai/README.md +45 -0
- package/ai/claude/launch.json +12 -0
- package/ai/skills/stacks-actions/SKILL.md +128 -0
- package/ai/skills/stacks-ai/SKILL.md +276 -0
- package/ai/skills/stacks-alias/SKILL.md +94 -0
- package/ai/skills/stacks-analytics/SKILL.md +91 -0
- package/ai/skills/stacks-api/SKILL.md +738 -0
- package/ai/skills/stacks-arrays/SKILL.md +277 -0
- package/ai/skills/stacks-auth/SKILL.md +422 -0
- package/ai/skills/stacks-auto-imports/SKILL.md +138 -0
- package/ai/skills/stacks-brandkit/SKILL.md +811 -0
- package/ai/skills/stacks-browse/SKILL.md +127 -0
- package/ai/skills/stacks-browse/scripts/browse.ts +492 -0
- package/ai/skills/stacks-browser/SKILL.md +145 -0
- package/ai/skills/stacks-buddy/SKILL.md +676 -0
- package/ai/skills/stacks-build/SKILL.md +163 -0
- package/ai/skills/stacks-cache/SKILL.md +125 -0
- package/ai/skills/stacks-calendar/SKILL.md +113 -0
- package/ai/skills/stacks-chat/SKILL.md +371 -0
- package/ai/skills/stacks-cli/SKILL.md +112 -0
- package/ai/skills/stacks-cloud/SKILL.md +192 -0
- package/ai/skills/stacks-cms/SKILL.md +217 -0
- package/ai/skills/stacks-collections/SKILL.md +82 -0
- package/ai/skills/stacks-commerce/SKILL.md +114 -0
- package/ai/skills/stacks-composables/SKILL.md +161 -0
- package/ai/skills/stacks-config/SKILL.md +144 -0
- package/ai/skills/stacks-configuration/SKILL.md +154 -0
- package/ai/skills/stacks-cron/SKILL.md +123 -0
- package/ai/skills/stacks-crosswind/SKILL.md +190 -0
- package/ai/skills/stacks-dashboard/SKILL.md +173 -0
- package/ai/skills/stacks-database/SKILL.md +286 -0
- package/ai/skills/stacks-datetime/SKILL.md +269 -0
- package/ai/skills/stacks-dependencies/SKILL.md +111 -0
- package/ai/skills/stacks-deploy/SKILL.md +102 -0
- package/ai/skills/stacks-design-brutalist/SKILL.md +110 -0
- package/ai/skills/stacks-design-minimalist/SKILL.md +115 -0
- package/ai/skills/stacks-design-output/SKILL.md +55 -0
- package/ai/skills/stacks-design-soft/SKILL.md +156 -0
- package/ai/skills/stacks-design-taste/SKILL.md +1012 -0
- package/ai/skills/stacks-design-taste-codex/SKILL.md +294 -0
- package/ai/skills/stacks-desktop/SKILL.md +78 -0
- package/ai/skills/stacks-development/SKILL.md +298 -0
- package/ai/skills/stacks-dns/SKILL.md +149 -0
- package/ai/skills/stacks-docs/SKILL.md +109 -0
- package/ai/skills/stacks-email/SKILL.md +236 -0
- package/ai/skills/stacks-enums/SKILL.md +100 -0
- package/ai/skills/stacks-env/SKILL.md +181 -0
- package/ai/skills/stacks-error-handling/SKILL.md +137 -0
- package/ai/skills/stacks-events/SKILL.md +228 -0
- package/ai/skills/stacks-faker/SKILL.md +158 -0
- package/ai/skills/stacks-git/SKILL.md +158 -0
- package/ai/skills/stacks-guard/SKILL.md +98 -0
- package/ai/skills/stacks-health/SKILL.md +75 -0
- package/ai/skills/stacks-http/SKILL.md +316 -0
- package/ai/skills/stacks-i18n/SKILL.md +198 -0
- package/ai/skills/stacks-image-to-code/SKILL.md +629 -0
- package/ai/skills/stacks-imagegen-mobile/SKILL.md +1474 -0
- package/ai/skills/stacks-imagegen-web/SKILL.md +998 -0
- package/ai/skills/stacks-investigate/SKILL.md +119 -0
- package/ai/skills/stacks-jobs/SKILL.md +136 -0
- package/ai/skills/stacks-lint/SKILL.md +184 -0
- package/ai/skills/stacks-listeners/SKILL.md +99 -0
- package/ai/skills/stacks-logging/SKILL.md +78 -0
- package/ai/skills/stacks-mail/SKILL.md +120 -0
- package/ai/skills/stacks-middleware/SKILL.md +249 -0
- package/ai/skills/stacks-migrations/SKILL.md +105 -0
- package/ai/skills/stacks-models/SKILL.md +267 -0
- package/ai/skills/stacks-new-feature/SKILL.md +206 -0
- package/ai/skills/stacks-notifications/SKILL.md +210 -0
- package/ai/skills/stacks-objects/SKILL.md +205 -0
- package/ai/skills/stacks-office-hours/SKILL.md +134 -0
- package/ai/skills/stacks-orm/SKILL.md +342 -0
- package/ai/skills/stacks-path/SKILL.md +118 -0
- package/ai/skills/stacks-payments/SKILL.md +373 -0
- package/ai/skills/stacks-plan-review/SKILL.md +152 -0
- package/ai/skills/stacks-plugins/SKILL.md +149 -0
- package/ai/skills/stacks-push/SKILL.md +376 -0
- package/ai/skills/stacks-query-builder/SKILL.md +128 -0
- package/ai/skills/stacks-queue/SKILL.md +559 -0
- package/ai/skills/stacks-realtime/SKILL.md +442 -0
- package/ai/skills/stacks-redesign/SKILL.md +182 -0
- package/ai/skills/stacks-registry/SKILL.md +58 -0
- package/ai/skills/stacks-repl/SKILL.md +80 -0
- package/ai/skills/stacks-retro/SKILL.md +122 -0
- package/ai/skills/stacks-review/SKILL.md +135 -0
- package/ai/skills/stacks-router/SKILL.md +231 -0
- package/ai/skills/stacks-routes/SKILL.md +105 -0
- package/ai/skills/stacks-scaffolding/SKILL.md +162 -0
- package/ai/skills/stacks-scheduler/SKILL.md +363 -0
- package/ai/skills/stacks-search-engine/SKILL.md +112 -0
- package/ai/skills/stacks-security/SKILL.md +129 -0
- package/ai/skills/stacks-security-audit/SKILL.md +128 -0
- package/ai/skills/stacks-server/SKILL.md +458 -0
- package/ai/skills/stacks-shell/SKILL.md +62 -0
- package/ai/skills/stacks-slug/SKILL.md +87 -0
- package/ai/skills/stacks-sms/SKILL.md +385 -0
- package/ai/skills/stacks-socials/SKILL.md +303 -0
- package/ai/skills/stacks-storage/SKILL.md +576 -0
- package/ai/skills/stacks-strings/SKILL.md +416 -0
- package/ai/skills/stacks-stx/SKILL.md +176 -0
- package/ai/skills/stacks-technical-diagrams/LICENSE +22 -0
- package/ai/skills/stacks-technical-diagrams/SKILL.md +290 -0
- package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +4 -0
- package/ai/skills/stacks-technical-diagrams/assets/template.html +1252 -0
- package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +289 -0
- package/ai/skills/stacks-technical-diagrams/bunfig.toml +1 -0
- package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +64 -0
- package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +88 -0
- package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +75 -0
- package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +70 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +41 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +62 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +371 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +82 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +295 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +91 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +384 -0
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +84 -0
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +257 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +42 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +13 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +230 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +40 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +88 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +40 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +103 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +481 -0
- package/ai/skills/stacks-technical-diagrams/schemas/README.md +68 -0
- package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +121 -0
- package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +43 -0
- package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +221 -0
- package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +241 -0
- package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +186 -0
- package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +332 -0
- package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +297 -0
- package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +28 -0
- package/ai/skills/stacks-testing/SKILL.md +129 -0
- package/ai/skills/stacks-tunnel/SKILL.md +125 -0
- package/ai/skills/stacks-types/SKILL.md +178 -0
- package/ai/skills/stacks-ui/SKILL.md +246 -0
- package/ai/skills/stacks-utils/SKILL.md +444 -0
- package/ai/skills/stacks-validation/SKILL.md +371 -0
- package/ai/skills/stacks-whois/SKILL.md +315 -0
- package/assets/world-map-data.ts +121 -0
- package/bootstrap.ts +114 -0
- package/docs/api-examples.md +46 -0
- package/docs/index.md +24 -0
- package/docs/markdown-examples.md +87 -0
- package/functions/analytics.ts +97 -0
- package/functions/api.ts +143 -0
- package/functions/auth.ts +208 -0
- package/functions/billing/payments.ts +149 -0
- package/functions/blog.ts +120 -0
- package/functions/cms/categorizables.ts +107 -0
- package/functions/cms/pages.ts +108 -0
- package/functions/cms/posts.ts +100 -0
- package/functions/cms/taggables.ts +102 -0
- package/functions/commerce/coupons.ts +128 -0
- package/functions/commerce/customers.ts +128 -0
- package/functions/commerce/gift-cards.ts +119 -0
- package/functions/commerce/orders.ts +112 -0
- package/functions/commerce/payments.ts +119 -0
- package/functions/commerce/products/categories.ts +123 -0
- package/functions/commerce/products/manufacturers.ts +123 -0
- package/functions/commerce/products/products.ts +123 -0
- package/functions/commerce/products/reviews.ts +123 -0
- package/functions/commerce/products/units.ts +123 -0
- package/functions/commerce/products/variants.ts +123 -0
- package/functions/commerce/shippings/delivery-routes.ts +123 -0
- package/functions/commerce/shippings/digital-deliveries.ts +119 -0
- package/functions/commerce/shippings/drivers.ts +123 -0
- package/functions/commerce/shippings/license-keys.ts +123 -0
- package/functions/commerce/shippings/shipping-methods.ts +123 -0
- package/functions/commerce/shippings/shipping-rates.ts +119 -0
- package/functions/commerce/shippings/shipping-zones.ts +119 -0
- package/functions/commerce/taxes.ts +119 -0
- package/functions/commerce/waitlist/products.ts +123 -0
- package/functions/commerce/waitlist/restaurants.ts +123 -0
- package/functions/craft.ts +107 -0
- package/functions/dashboard.ts +153 -0
- package/functions/inbox.ts +95 -0
- package/functions/jobs.ts +86 -0
- package/functions/monitoring/errors.ts +295 -0
- package/functions/queue.ts +111 -0
- package/functions/realtime/websockets.ts +96 -0
- package/functions/realtime.ts +52 -0
- package/functions/releases.ts +49 -0
- package/functions/search.ts +126 -0
- package/functions/toasts.ts +95 -0
- package/functions/uploads.ts +216 -0
- package/ide/.gitignore +4 -0
- package/ide/cursor/rules/auto-imports.mdc +574 -0
- package/ide/cursor/rules/code-style.mdc +12 -0
- package/ide/cursor/rules/documentation.mdc +9 -0
- package/ide/cursor/rules/error-handling-validation.mdc +11 -0
- package/ide/cursor/rules/key-conventions.mdc +11 -0
- package/ide/cursor/rules/project-structure.mdc +169 -0
- package/ide/cursor/rules/readme.mdc +318 -0
- package/ide/cursor/rules/syntax-formatting.mdc +9 -0
- package/ide/cursor/rules/testing.mdc +10 -0
- package/ide/cursor/rules/typescript.mdc +9 -0
- package/ide/cursor/rules/ui-styling.mdc +0 -0
- package/ide/dictionary.txt +397 -0
- package/ide/jetbrains/.fleet/settings.json +4 -0
- package/ide/jetbrains/.idea/.gitignore +5 -0
- package/ide/jetbrains/.idea/.name +1 -0
- package/ide/jetbrains/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/ide/jetbrains/.idea/jsLinters/eslint.xml +7 -0
- package/ide/jetbrains/.idea/modules.xml +8 -0
- package/ide/jetbrains/.idea/stacks.iml +22 -0
- package/ide/jetbrains/.idea/vcs.xml +6 -0
- package/ide/jetbrains/.idea/webResources.xml +14 -0
- package/ide/jetbrains/IntelliJ IDEA Global Settings +0 -0
- package/ide/jetbrains/installed.txt +4 -0
- package/ide/jetbrains/options/github-copilot.xml +5 -0
- package/ide/jetbrains/options/nodejs.xml +7 -0
- package/ide/jetbrains/options/templates.xml +124 -0
- package/ide/jetbrains/templates/JavaScript.xml +6 -0
- package/ide/jetbrains/templates/Stacks.xml +8 -0
- package/ide/vscode/.vscode/README.md +23 -0
- package/ide/vscode/.vscode/extensions.json +5 -0
- package/ide/vscode/.vscode/settings.json +176 -0
- package/ide/vscode/.vscode/stacks.code-snippets +181 -0
- package/ide/vscode/LICENSE.md +21 -0
- package/ide/vscode/README.md +36 -0
- package/ide/vscode/logo.png +0 -0
- package/ide/vscode/package.json +79 -0
- package/ide/zed/.zed/settings.json +139 -0
- package/lang/README.md +28 -0
- package/lang/de.yml +14 -0
- package/lang/en.yml +15 -0
- package/package.json +19 -3
- package/project/bootstrap +161 -0
- package/project/buddy +175 -0
- package/project/storage/framework/tsconfig.app.json +50 -0
- package/project/storage/framework/tsconfig.base.json +58 -0
- package/routes/core.ts +78 -0
- package/routes/dashboard-api.ts +233 -0
- package/routes/dashboard.ts +739 -0
- package/routes/email.ts +129 -0
- package/stores/[name].stx +16 -0
- package/stores/git.ts +71 -0
- package/stores/payment.ts +474 -0
- package/stores/queue.ts +43 -0
- package/stores/user.ts +29 -0
- package/stx-components-plugin.ts +25 -0
- package/styles/design-tokens.css +597 -0
- package/types/dashboard.ts +70 -0
- package/types/defaults.ts +438 -0
- package/vcs/github/CODE_OF_CONDUCT.md +83 -0
- package/vcs/github/CONTRIBUTING.md +187 -0
- package/vcs/github/EXPLANATIONS.md +41 -0
- package/vcs/github/FUNDING.yml +1 -0
- package/vcs/github/ISSUE_TEMPLATE/bug_report.yml +49 -0
- package/vcs/github/ISSUE_TEMPLATE/config.yml +10 -0
- package/vcs/github/ISSUE_TEMPLATE/feature_request.yml +44 -0
- package/vcs/github/SECURITY.md +70 -0
- package/vcs/github/renovate.json +5 -0
- package/vcs/github/stale.yml +14 -0
- package/vcs/github/workflows/README.md +16 -0
- package/vcs/github/workflows/ci.yml +114 -0
- package/vcs/github/workflows/export-size.yml +25 -0
- package/vcs/github/workflows/labeler.yml +13 -0
- package/vcs/github/workflows/release.yml +219 -0
- package/views/[...all].stx +8 -0
- package/views/about.md +3 -0
- package/views/auth/login.stx +1 -0
- package/views/auth/signup.stx +85 -0
- package/views/dashboard/.discovered-models.json +542 -0
- package/views/dashboard/AUDIT.md +904 -0
- package/views/dashboard/[...all].stx +89 -0
- package/views/dashboard/access-tokens/index.stx +177 -0
- package/views/dashboard/actions/index.stx +129 -0
- package/views/dashboard/analytics/blog/index.stx +356 -0
- package/views/dashboard/analytics/browsers/index.stx +263 -0
- package/views/dashboard/analytics/commerce/sales/index.stx +329 -0
- package/views/dashboard/analytics/commerce/web/index.stx +379 -0
- package/views/dashboard/analytics/countries/index.stx +289 -0
- package/views/dashboard/analytics/devices/index.stx +305 -0
- package/views/dashboard/analytics/events/index.stx +536 -0
- package/views/dashboard/analytics/index.stx +88 -0
- package/views/dashboard/analytics/marketing/index.stx +315 -0
- package/views/dashboard/analytics/pages/index.stx +271 -0
- package/views/dashboard/analytics/referrers/index.stx +319 -0
- package/views/dashboard/analytics/web/index.stx +356 -0
- package/views/dashboard/buddy/index.stx +198 -0
- package/views/dashboard/ci/index.stx +462 -0
- package/views/dashboard/cloud/index.stx +452 -0
- package/views/dashboard/commands/index.stx +295 -0
- package/views/dashboard/commerce/categories/index.stx +406 -0
- package/views/dashboard/commerce/coupons/index.stx +485 -0
- package/views/dashboard/commerce/customers/index.stx +410 -0
- package/views/dashboard/commerce/dashboard/index.stx +287 -0
- package/views/dashboard/commerce/delivery/delivery-routes.stx +305 -0
- package/views/dashboard/commerce/delivery/digital-delivery.stx +332 -0
- package/views/dashboard/commerce/delivery/drivers.stx +397 -0
- package/views/dashboard/commerce/delivery/index.stx +18 -0
- package/views/dashboard/commerce/delivery/license-keys.stx +380 -0
- package/views/dashboard/commerce/delivery/shipping-methods.stx +346 -0
- package/views/dashboard/commerce/delivery/shipping-rates.stx +338 -0
- package/views/dashboard/commerce/delivery/shipping-zones.stx +302 -0
- package/views/dashboard/commerce/delivery/tabs.ts +25 -0
- package/views/dashboard/commerce/gift-cards/index.stx +561 -0
- package/views/dashboard/commerce/manufacturers/index.stx +308 -0
- package/views/dashboard/commerce/orders/index.stx +247 -0
- package/views/dashboard/commerce/payments/index.stx +535 -0
- package/views/dashboard/commerce/pos/index.stx +653 -0
- package/views/dashboard/commerce/printers/devices/index.stx +273 -0
- package/views/dashboard/commerce/printers/receipts/index.stx +308 -0
- package/views/dashboard/commerce/products/detail.stx +563 -0
- package/views/dashboard/commerce/products/index.stx +370 -0
- package/views/dashboard/commerce/reviews/index.stx +656 -0
- package/views/dashboard/commerce/reviews/index.ts +0 -0
- package/views/dashboard/commerce/taxes/index.stx +377 -0
- package/views/dashboard/commerce/units/index.stx +317 -0
- package/views/dashboard/commerce/variants/index.stx +319 -0
- package/views/dashboard/commerce/waitlist/products.stx +942 -0
- package/views/dashboard/commerce/waitlist/restaurant.stx +1024 -0
- package/views/dashboard/components/index.stx +330 -0
- package/views/dashboard/composables/index.ts +19 -0
- package/views/dashboard/composables/useChart.ts +77 -0
- package/views/dashboard/composables/useNavigation.ts +99 -0
- package/views/dashboard/composables/useRole.ts +58 -0
- package/views/dashboard/composables/useTheme.ts +74 -0
- package/views/dashboard/content/authors/index.stx +171 -0
- package/views/dashboard/content/blog/index.stx +493 -0
- package/views/dashboard/content/categories/index.stx +169 -0
- package/views/dashboard/content/comments/index.stx +189 -0
- package/views/dashboard/content/dashboard.stx +383 -0
- package/views/dashboard/content/files/index.stx +706 -0
- package/views/dashboard/content/pages/index.stx +174 -0
- package/views/dashboard/content/posts/index.stx +269 -0
- package/views/dashboard/content/seo/index.stx +240 -0
- package/views/dashboard/content/tags/index.stx +167 -0
- package/views/dashboard/data/activity/index.stx +261 -0
- package/views/dashboard/data/dashboard/index.stx +116 -0
- package/views/dashboard/data/subscribers.stx +270 -0
- package/views/dashboard/data/teams.stx +275 -0
- package/views/dashboard/data/users.stx +279 -0
- package/views/dashboard/dependencies/index.stx +178 -0
- package/views/dashboard/deployments/[id].stx +234 -0
- package/views/dashboard/deployments/index.stx +9 -0
- package/views/dashboard/dns/index.stx +163 -0
- package/views/dashboard/environment/index.stx +283 -0
- package/views/dashboard/errors/index.stx +274 -0
- package/views/dashboard/forgot-password.stx +57 -0
- package/views/dashboard/functions/index.stx +159 -0
- package/views/dashboard/health/index.stx +248 -0
- package/views/dashboard/inbox/activity.stx +350 -0
- package/views/dashboard/inbox/index.stx +452 -0
- package/views/dashboard/inbox/settings.stx +290 -0
- package/views/dashboard/index.stx +252 -0
- package/views/dashboard/insights/index.stx +529 -0
- package/views/dashboard/jobs/[id].stx +430 -0
- package/views/dashboard/jobs/history.stx +222 -0
- package/views/dashboard/jobs/index.stx +214 -0
- package/views/dashboard/kanban/[id].stx +899 -0
- package/views/dashboard/kanban/index.stx +260 -0
- package/views/dashboard/layouts/default.stx +565 -0
- package/views/dashboard/layouts/guest.stx +10 -0
- package/views/dashboard/login.stx +48 -0
- package/views/dashboard/logs/index.stx +540 -0
- package/views/dashboard/mailboxes/index.stx +149 -0
- package/views/dashboard/management/permissions/index.stx +490 -0
- package/views/dashboard/marketing/campaigns/index.stx +604 -0
- package/views/dashboard/marketing/lists/index.stx +514 -0
- package/views/dashboard/marketing/reviews/index.stx +475 -0
- package/views/dashboard/marketing/social-posts/index.stx +560 -0
- package/views/dashboard/models/[model].stx +195 -0
- package/views/dashboard/models/index.stx +159 -0
- package/views/dashboard/monitoring/errors/index.stx +415 -0
- package/views/dashboard/notifications/dashboard.stx +383 -0
- package/views/dashboard/notifications/email.stx +336 -0
- package/views/dashboard/notifications/history.stx +373 -0
- package/views/dashboard/notifications/sms.stx +332 -0
- package/views/dashboard/packages/index.stx +195 -0
- package/views/dashboard/queries/[id].stx +381 -0
- package/views/dashboard/queries/history.stx +227 -0
- package/views/dashboard/queries/index.stx +335 -0
- package/views/dashboard/queries/slow.stx +318 -0
- package/views/dashboard/queue/index.stx +427 -0
- package/views/dashboard/realtime/index.stx +429 -0
- package/views/dashboard/register.stx +46 -0
- package/views/dashboard/releases/index.stx +389 -0
- package/views/dashboard/requests/index.stx +462 -0
- package/views/dashboard/serverless/index.stx +475 -0
- package/views/dashboard/servers/[id].stx +299 -0
- package/views/dashboard/servers/index.stx +483 -0
- package/views/dashboard/settings/billing.stx +50 -0
- package/views/dashboard/settings/index.stx +256 -0
- package/views/dashboard/settings/mail.stx +252 -0
- package/views/dashboard/stores/auth.ts +123 -0
- package/views/dashboard/stores/ci.ts +342 -0
- package/views/dashboard/stores/index.ts +129 -0
- package/views/dashboard/stores/kanban.ts +723 -0
- package/views/dashboard/stores/rbac.ts +350 -0
- package/views/dashboard/stx.config.ts +34 -0
- package/views/dashboard/teams/[id].stx +170 -0
- package/views/forum/category.stx +92 -0
- package/views/forum/index.stx +209 -0
- package/views/forum/new-topic.stx +113 -0
- package/views/forum/profile.stx +1 -0
- package/views/forum/topic.stx +143 -0
- package/views/hello-world/[name].stx +47 -0
- package/views/index.stx +21 -0
- package/views/system-tray/index.stx +147 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
@extends('layouts/default')
|
|
2
|
+
|
|
3
|
+
@section('content')
|
|
4
|
+
<div class="p-6 space-y-6">
|
|
5
|
+
<div class="flex items-center justify-between">
|
|
6
|
+
<PageHeader title="SEO" description="Optimize each post for search engines" />
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<dl class="grid gap-5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4">
|
|
10
|
+
<div class="overflow-hidden px-4 py-5 sm:p-6 bg-white dark:bg-neutral-800 rounded-lg shadow">
|
|
11
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-300">Avg SEO Score</dt>
|
|
12
|
+
<dd class="mt-1 font-semibold text-3xl text-gray-900 dark:text-white">{{ avgScore }}</dd>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="overflow-hidden px-4 py-5 sm:p-6 bg-white dark:bg-neutral-800 rounded-lg shadow">
|
|
15
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-300">Posts Optimized</dt>
|
|
16
|
+
<dd class="mt-1 font-semibold text-3xl text-green-600 dark:text-green-400">{{ optimizedCount }}</dd>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="overflow-hidden px-4 py-5 sm:p-6 bg-white dark:bg-neutral-800 rounded-lg shadow">
|
|
19
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-300">Needs Work</dt>
|
|
20
|
+
<dd class="mt-1 font-semibold text-3xl text-yellow-600 dark:text-yellow-400">{{ needsWorkCount }}</dd>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="overflow-hidden px-4 py-5 sm:p-6 bg-white dark:bg-neutral-800 rounded-lg shadow">
|
|
23
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-300">Total Posts</dt>
|
|
24
|
+
<dd class="mt-1 font-semibold text-3xl text-gray-900 dark:text-white">{{ totalPosts }}</dd>
|
|
25
|
+
</div>
|
|
26
|
+
</dl>
|
|
27
|
+
|
|
28
|
+
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow">
|
|
29
|
+
<div class="p-6">
|
|
30
|
+
<h3 class="font-semibold text-base text-gray-900 dark:text-white">Score Distribution</h3>
|
|
31
|
+
<div class="mt-2 h-80"><canvas id="seo_distribution_chart"></canvas></div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
36
|
+
<div class="relative max-w-sm">
|
|
37
|
+
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
|
|
38
|
+
<div class="h-5 w-5 text-gray-400 i-hugeicons-search-01"></div>
|
|
39
|
+
</div>
|
|
40
|
+
<input x-model="searchQuery" type="text" class="block pl-10 pr-3 py-1.5 w-full text-gray-900 sm:text-sm dark:text-white dark:bg-neutral-800 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-700 focus:ring-2 focus:ring-blue-600" placeholder="Search posts..." />
|
|
41
|
+
</div>
|
|
42
|
+
<select x-model="scoreFilter" class="block pl-3 pr-10 py-1.5 text-gray-900 sm:text-sm dark:text-white dark:bg-neutral-800 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-700 focus:ring-2 focus:ring-blue-600">
|
|
43
|
+
<option value="all">All Scores</option>
|
|
44
|
+
<option value="good">Good (80+)</option>
|
|
45
|
+
<option value="medium">Medium (50-79)</option>
|
|
46
|
+
<option value="poor">Poor (< 50)</option>
|
|
47
|
+
</select>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div class="overflow-x-auto border border-gray-200 rounded-lg dark:border-neutral-700 shadow">
|
|
51
|
+
<table class="min-w-full divide-gray-200 divide-y dark:divide-neutral-700">
|
|
52
|
+
<thead class="bg-gray-50 dark:bg-neutral-700/50">
|
|
53
|
+
<tr>
|
|
54
|
+
<th class="px-4 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Post</th>
|
|
55
|
+
<th class="px-4 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Focus Keyword</th>
|
|
56
|
+
<th class="px-4 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Meta Description</th>
|
|
57
|
+
<th class="px-4 py-3.5 font-semibold text-gray-900 text-right text-sm dark:text-white">Score</th>
|
|
58
|
+
<th class="px-4 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Last Updated</th>
|
|
59
|
+
<th class="relative px-4 py-3.5"><span class="sr-only">Actions</span></th>
|
|
60
|
+
</tr>
|
|
61
|
+
</thead>
|
|
62
|
+
<tbody class="bg-white dark:bg-neutral-800 divide-gray-200 divide-y dark:divide-neutral-700">
|
|
63
|
+
<template :for="entry in filteredEntries">
|
|
64
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-neutral-700">
|
|
65
|
+
<td class="px-4 py-4 whitespace-nowrap">
|
|
66
|
+
<div class="font-medium text-gray-900 text-sm dark:text-white">{{ entry.title }}</div>
|
|
67
|
+
<div class="text-gray-500 text-xs dark:text-neutral-400">/{{ entry.slug }}</div>
|
|
68
|
+
</td>
|
|
69
|
+
<td class="px-4 py-4 whitespace-nowrap"><div class="text-gray-500 text-sm dark:text-neutral-300">{{ entry.focusKeyword }}</div></td>
|
|
70
|
+
<td class="px-4 py-4">
|
|
71
|
+
<div class="max-w-md text-gray-500 text-sm truncate dark:text-neutral-300">{{ entry.metaDescription }}</div>
|
|
72
|
+
</td>
|
|
73
|
+
<td class="px-4 py-4 text-right whitespace-nowrap">
|
|
74
|
+
<span :class="scoreBadgeClass(entry.score)">{{ entry.score }}</span>
|
|
75
|
+
</td>
|
|
76
|
+
<td class="px-4 py-4 whitespace-nowrap"><div class="text-gray-500 text-sm dark:text-neutral-300">{{ entry.lastUpdated }}</div></td>
|
|
77
|
+
<td class="px-4 py-4 font-medium text-right text-sm whitespace-nowrap">
|
|
78
|
+
<button @click="openEdit(entry)" class="text-blue-600 hover:text-blue-500">Edit</button>
|
|
79
|
+
</td>
|
|
80
|
+
</tr>
|
|
81
|
+
</template>
|
|
82
|
+
<tr :show="filteredEntries().length === 0">
|
|
83
|
+
<td colspan="6" class="px-4 py-8 text-center text-gray-500 text-sm dark:text-neutral-400">No posts found.</td>
|
|
84
|
+
</tr>
|
|
85
|
+
</tbody>
|
|
86
|
+
</table>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div :show="editingEntry() !== null" class="relative z-10">
|
|
90
|
+
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 dark:bg-neutral-900 dark:bg-opacity-75"></div>
|
|
91
|
+
<div class="fixed inset-0 overflow-y-auto z-10">
|
|
92
|
+
<div class="flex items-end justify-center sm:items-center p-4 min-h-full">
|
|
93
|
+
<div class="overflow-hidden relative pb-4 pt-5 px-4 sm:p-6 sm:max-w-lg sm:w-full bg-white dark:bg-neutral-800 rounded-lg shadow-xl">
|
|
94
|
+
<h3 class="font-semibold leading-6 text-base text-gray-900 dark:text-white">Edit SEO</h3>
|
|
95
|
+
<p :show="editingEntry() !== null" class="mt-1 text-gray-500 text-sm dark:text-neutral-400">{{ editingEntry()?.title }}</p>
|
|
96
|
+
<div class="mt-4 space-y-4">
|
|
97
|
+
<div>
|
|
98
|
+
<label class="block font-medium text-gray-900 text-sm dark:text-white">Focus Keyword</label>
|
|
99
|
+
<input x-model="editFocusKeyword" type="text" class="block mt-2 py-1.5 w-full text-gray-900 sm:text-sm dark:text-white dark:bg-neutral-700 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:ring-2 focus:ring-blue-600 shadow-sm" />
|
|
100
|
+
</div>
|
|
101
|
+
<div>
|
|
102
|
+
<label class="block font-medium text-gray-900 text-sm dark:text-white">Meta Description</label>
|
|
103
|
+
<textarea x-model="editMetaDescription" rows="3" class="block mt-2 py-1.5 w-full text-gray-900 sm:text-sm dark:text-white dark:bg-neutral-700 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:ring-2 focus:ring-blue-600 shadow-sm"></textarea>
|
|
104
|
+
</div>
|
|
105
|
+
<div>
|
|
106
|
+
<label class="block font-medium text-gray-900 text-sm dark:text-white">Canonical URL</label>
|
|
107
|
+
<input x-model="editCanonical" type="text" class="block mt-2 py-1.5 w-full text-gray-900 sm:text-sm dark:text-white dark:bg-neutral-700 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:ring-2 focus:ring-blue-600 shadow-sm" />
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="sm:grid sm:gap-3 sm:grid-cols-2 mt-5">
|
|
111
|
+
<button @click="closeEdit()" class="inline-flex justify-center sm:col-start-1 px-3 py-2 w-full font-semibold text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-700 hover:bg-gray-50 dark:hover:bg-neutral-600 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 shadow-sm">Cancel</button>
|
|
112
|
+
<button @click="saveEdit()" class="inline-flex justify-center sm:col-start-2 px-3 py-2 w-full font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md shadow-sm">Save</button>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<script client>
|
|
121
|
+
interface SeoEntry {
|
|
122
|
+
id: number
|
|
123
|
+
title: string
|
|
124
|
+
slug: string
|
|
125
|
+
focusKeyword: string
|
|
126
|
+
metaDescription: string
|
|
127
|
+
canonicalUrl: string
|
|
128
|
+
score: number
|
|
129
|
+
lastUpdated: string
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
interface ChartLike { destroy: () => void }
|
|
133
|
+
type ChartCtor = new (canvas: HTMLElement, config: { type: string, data: unknown, options?: unknown }) => ChartLike
|
|
134
|
+
|
|
135
|
+
const initialEntries: SeoEntry[] = [
|
|
136
|
+
{ id: 1, title: '10 Tips for Better Code Quality', slug: '10-tips-for-better-code-quality', focusKeyword: 'code quality', metaDescription: 'Learn 10 essential tips to improve your code quality.', canonicalUrl: '', score: 87, lastUpdated: '2023-12-01' },
|
|
137
|
+
{ id: 2, title: 'The Future of JavaScript Frameworks', slug: 'future-of-javascript-frameworks', focusKeyword: 'javascript frameworks', metaDescription: 'Where JS frameworks are headed in 2024 and beyond.', canonicalUrl: '', score: 72, lastUpdated: '2023-11-28' },
|
|
138
|
+
{ id: 3, title: 'Getting Started with STX', slug: 'getting-started-with-stx', focusKeyword: 'STX', metaDescription: 'A comprehensive guide to STX composition API.', canonicalUrl: '', score: 91, lastUpdated: '2023-11-25' },
|
|
139
|
+
{ id: 4, title: 'Review: Latest MacBook Pro', slug: 'review-latest-macbook-pro', focusKeyword: 'macbook pro review', metaDescription: 'In-depth MacBook Pro review for developers.', canonicalUrl: '', score: 64, lastUpdated: '2023-11-20' },
|
|
140
|
+
{ id: 5, title: 'Understanding Web Accessibility', slug: 'understanding-web-accessibility', focusKeyword: 'web accessibility', metaDescription: 'Why accessibility matters and how to implement it.', canonicalUrl: '', score: 88, lastUpdated: '2023-11-15' },
|
|
141
|
+
{ id: 6, title: 'Introduction to TypeScript', slug: 'introduction-to-typescript', focusKeyword: 'typescript', metaDescription: '', canonicalUrl: '', score: 42, lastUpdated: '2023-11-10' },
|
|
142
|
+
{ id: 7, title: 'Building a REST API with Node.js', slug: 'building-rest-api-nodejs', focusKeyword: 'rest api nodejs', metaDescription: 'Step-by-step guide to building REST APIs in Node.', canonicalUrl: '', score: 35, lastUpdated: '2023-11-05' },
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
const entries = state<SeoEntry[]>(initialEntries)
|
|
146
|
+
const searchQuery = state('')
|
|
147
|
+
const scoreFilter = state<'all' | 'good' | 'medium' | 'poor'>('all')
|
|
148
|
+
const editingEntry = state<SeoEntry | null>(null)
|
|
149
|
+
const editFocusKeyword = state('')
|
|
150
|
+
const editMetaDescription = state('')
|
|
151
|
+
const editCanonical = state('')
|
|
152
|
+
|
|
153
|
+
const filteredEntries = derived(() => {
|
|
154
|
+
let list = [...entries()]
|
|
155
|
+
const filter = scoreFilter()
|
|
156
|
+
if (filter === 'good')
|
|
157
|
+
list = list.filter(e => e.score >= 80)
|
|
158
|
+
else if (filter === 'medium')
|
|
159
|
+
list = list.filter(e => e.score >= 50 && e.score < 80)
|
|
160
|
+
else if (filter === 'poor')
|
|
161
|
+
list = list.filter(e => e.score < 50)
|
|
162
|
+
const q = searchQuery().toLowerCase()
|
|
163
|
+
if (q)
|
|
164
|
+
list = list.filter(e => e.title.toLowerCase().includes(q) || e.focusKeyword.toLowerCase().includes(q))
|
|
165
|
+
return list
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const totalPosts = derived(() => entries().length)
|
|
169
|
+
const optimizedCount = derived(() => entries().filter(e => e.score >= 80).length)
|
|
170
|
+
const needsWorkCount = derived(() => entries().filter(e => e.score < 50).length)
|
|
171
|
+
const avgScore = derived(() => {
|
|
172
|
+
if (entries().length === 0)
|
|
173
|
+
return 0
|
|
174
|
+
return Math.round(entries().reduce((s, e) => s + e.score, 0) / entries().length)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
function scoreBadgeClass(score: number) {
|
|
178
|
+
const base = 'inline-flex items-center px-2 py-0.5 font-semibold text-xs rounded-full'
|
|
179
|
+
if (score >= 80)
|
|
180
|
+
return `${base} bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200`
|
|
181
|
+
if (score >= 50)
|
|
182
|
+
return `${base} bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200`
|
|
183
|
+
return `${base} bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200`
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function openEdit(entry: SeoEntry) {
|
|
187
|
+
editingEntry.set(entry)
|
|
188
|
+
editFocusKeyword.set(entry.focusKeyword)
|
|
189
|
+
editMetaDescription.set(entry.metaDescription)
|
|
190
|
+
editCanonical.set(entry.canonicalUrl)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function closeEdit() {
|
|
194
|
+
editingEntry.set(null)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function saveEdit() {
|
|
198
|
+
const target = editingEntry()
|
|
199
|
+
if (!target)
|
|
200
|
+
return
|
|
201
|
+
entries.set(entries().map(e => e.id === target.id ? {
|
|
202
|
+
...e,
|
|
203
|
+
focusKeyword: editFocusKeyword(),
|
|
204
|
+
metaDescription: editMetaDescription(),
|
|
205
|
+
canonicalUrl: editCanonical(),
|
|
206
|
+
lastUpdated: new Date().toISOString().slice(0, 10),
|
|
207
|
+
} : e))
|
|
208
|
+
closeEdit()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let chartHandle: ChartLike | null = null
|
|
212
|
+
|
|
213
|
+
onMount(async () => {
|
|
214
|
+
const { Chart, registerables } = await import('@stacksjs/charts')
|
|
215
|
+
Chart.register(...registerables)
|
|
216
|
+
const Ctor = Chart as unknown as ChartCtor
|
|
217
|
+
|
|
218
|
+
const good = entries().filter(e => e.score >= 80).length
|
|
219
|
+
const med = entries().filter(e => e.score >= 50 && e.score < 80).length
|
|
220
|
+
const poor = entries().filter(e => e.score < 50).length
|
|
221
|
+
|
|
222
|
+
chartHandle = useChart({
|
|
223
|
+
Chart: Ctor,
|
|
224
|
+
id: 'seo_distribution_chart',
|
|
225
|
+
type: 'bar',
|
|
226
|
+
data: {
|
|
227
|
+
labels: ['Good (80+)', 'Medium (50-79)', 'Poor (<50)'],
|
|
228
|
+
datasets: [{ label: 'Posts', backgroundColor: ['rgba(34, 197, 94, 0.8)', 'rgba(245, 158, 11, 0.8)', 'rgba(239, 68, 68, 0.8)'], borderRadius: 4, data: [good, med, poor] }],
|
|
229
|
+
},
|
|
230
|
+
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, ticks: { precision: 0 } } } },
|
|
231
|
+
}) as unknown as ChartLike
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
onDestroy(() => {
|
|
235
|
+
chartHandle?.destroy()
|
|
236
|
+
chartHandle = null
|
|
237
|
+
})
|
|
238
|
+
</script>
|
|
239
|
+
|
|
240
|
+
@endsection
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
@extends('layouts/default')
|
|
2
|
+
|
|
3
|
+
@section('content')
|
|
4
|
+
<div class="p-6 min-h-screen text-neutral-950 dark:text-neutral-100 bg-neutral-50 dark:bg-neutral-950">
|
|
5
|
+
<div class="flex flex-col gap-6 mx-auto max-w-6xl">
|
|
6
|
+
<header class="flex flex-col gap-4 md:flex-row md:items-end md:justify-between pb-5 border-b border-neutral-200 dark:border-neutral-800">
|
|
7
|
+
<div>
|
|
8
|
+
<p class="font-semibold text-blue-600 text-xs tracking-wide uppercase dark:text-blue-400">CMS</p>
|
|
9
|
+
<h1 class="mt-1 font-semibold text-2xl tracking-tight">Tags</h1>
|
|
10
|
+
<p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Manage the tags used by blog posts and markdown content.</p>
|
|
11
|
+
</div>
|
|
12
|
+
<button id="new-tag-button" type="button" class="inline-flex gap-2 items-center px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md shadow-sm">
|
|
13
|
+
<span class="h-4 w-4 i-hugeicons-plus-sign"></span>
|
|
14
|
+
New Tag
|
|
15
|
+
</button>
|
|
16
|
+
</header>
|
|
17
|
+
|
|
18
|
+
<!-- Two tiles, not three: the third used to be an "Active" count filtered on
|
|
19
|
+
`is_active`, a column the tags table does not have. Every tag passed, so
|
|
20
|
+
it always rendered the same number as Total Tags. -->
|
|
21
|
+
<section class="grid gap-4 md:grid-cols-2">
|
|
22
|
+
<div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
|
|
23
|
+
<p class="text-neutral-500 text-sm dark:text-neutral-400">Total Tags</p>
|
|
24
|
+
<p id="tag-total" class="mt-2 font-semibold text-3xl">0</p>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
|
|
27
|
+
<p class="text-neutral-500 text-sm dark:text-neutral-400">Newest</p>
|
|
28
|
+
<p id="tag-newest" class="mt-2 font-semibold text-2xl truncate">-</p>
|
|
29
|
+
</div>
|
|
30
|
+
</section>
|
|
31
|
+
|
|
32
|
+
<section class="bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
|
|
33
|
+
<div class="p-4 border-b border-neutral-200 dark:border-neutral-800">
|
|
34
|
+
<input id="tag-search" type="search" placeholder="Search tags..." class="px-3 py-2 max-w-md w-full text-sm bg-white dark:bg-neutral-950 border border-neutral-300 outline-none rounded-md dark:border-neutral-700 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20" />
|
|
35
|
+
</div>
|
|
36
|
+
<div class="overflow-x-auto">
|
|
37
|
+
<table class="min-w-full text-sm divide-neutral-200 divide-y dark:divide-neutral-800">
|
|
38
|
+
<thead class="font-semibold text-left text-neutral-500 text-xs uppercase dark:text-neutral-400 bg-neutral-50 dark:bg-neutral-900">
|
|
39
|
+
<tr>
|
|
40
|
+
<th class="px-4 py-3">Name</th>
|
|
41
|
+
<th class="px-4 py-3">Slug</th>
|
|
42
|
+
<th class="px-4 py-3">Description</th>
|
|
43
|
+
<th class="px-4 py-3 text-right">Actions</th>
|
|
44
|
+
</tr>
|
|
45
|
+
</thead>
|
|
46
|
+
<tbody id="tags-table" class="divide-neutral-200 divide-y dark:divide-neutral-800"></tbody>
|
|
47
|
+
</table>
|
|
48
|
+
</div>
|
|
49
|
+
<div id="tags-empty" class="hidden p-10 text-center text-neutral-500 text-sm dark:text-neutral-400">No tags found.</div>
|
|
50
|
+
</section>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<dialog id="tag-dialog" class="p-0 max-w-lg w-full text-neutral-950 dark:text-neutral-100 bg-white backdrop:bg-black/40 dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-xl">
|
|
55
|
+
<form id="tag-form" method="dialog">
|
|
56
|
+
<div class="px-5 py-4 border-b border-neutral-200 dark:border-neutral-800">
|
|
57
|
+
<h2 class="font-semibold text-lg">New Tag</h2>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="grid gap-4 p-5">
|
|
60
|
+
<label class="grid gap-1 font-medium text-sm">Name <input id="tag-name" required class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700" /></label>
|
|
61
|
+
<label class="grid gap-1 font-medium text-sm">Slug <input id="tag-slug" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700" /></label>
|
|
62
|
+
<label class="grid gap-1 font-medium text-sm">Description <textarea id="tag-description" rows="3" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700"></textarea></label>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="flex gap-2 justify-end px-5 py-4 border-neutral-200 border-t dark:border-neutral-800">
|
|
65
|
+
<button id="tag-cancel" type="button" class="px-3 py-2 font-medium text-sm border border-neutral-300 rounded-md dark:border-neutral-700">Cancel</button>
|
|
66
|
+
<button type="submit" class="px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md">Create Tag</button>
|
|
67
|
+
</div>
|
|
68
|
+
</form>
|
|
69
|
+
</dialog>
|
|
70
|
+
|
|
71
|
+
<script>
|
|
72
|
+
;(function () {
|
|
73
|
+
var api = '/api/dashboard/tags'
|
|
74
|
+
var tags = []
|
|
75
|
+
var table = document.getElementById('tags-table')
|
|
76
|
+
var empty = document.getElementById('tags-empty')
|
|
77
|
+
var dialog = document.getElementById('tag-dialog')
|
|
78
|
+
var search = document.getElementById('tag-search')
|
|
79
|
+
function $(id) { return document.getElementById(id) }
|
|
80
|
+
// CSRF is default-on for POST/PATCH/DELETE via a stateless double-submit
|
|
81
|
+
// cookie: the server seeds a non-HttpOnly X-CSRF-Token cookie on safe
|
|
82
|
+
// responses and the client echoes it back in the header. The list request on
|
|
83
|
+
// page load seeds it, so the token is present by the time a save can happen.
|
|
84
|
+
// See defaults/app/Middleware/Csrf.ts.
|
|
85
|
+
function writeHeaders() {
|
|
86
|
+
var headers = { 'content-type': 'application/json' }
|
|
87
|
+
var match = document.cookie.match(/(?:^|;\s*)X-CSRF-Token=([^;]*)/)
|
|
88
|
+
if (match) headers['X-CSRF-Token'] = decodeURIComponent(match[1])
|
|
89
|
+
return headers
|
|
90
|
+
}
|
|
91
|
+
function escapeHtml(value) {
|
|
92
|
+
return String(value || '').replace(/[&<>"']/g, function (ch) {
|
|
93
|
+
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[ch]
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
function slug(value) {
|
|
97
|
+
return String(value || '').trim().toLowerCase().replace(/['"]/g, '').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')
|
|
98
|
+
}
|
|
99
|
+
function render() {
|
|
100
|
+
var q = search.value.toLowerCase()
|
|
101
|
+
var rows = tags.filter(function (tag) {
|
|
102
|
+
return !q || [tag.name, tag.slug, tag.description].join(' ').toLowerCase().indexOf(q) !== -1
|
|
103
|
+
})
|
|
104
|
+
$('tag-total').textContent = String(tags.length)
|
|
105
|
+
$('tag-newest').textContent = tags[0] ? tags[0].name : '-'
|
|
106
|
+
empty.classList.toggle('hidden', rows.length > 0)
|
|
107
|
+
table.innerHTML = rows.map(function (tag) {
|
|
108
|
+
return '<tr class="hover:bg-neutral-50 dark:hover:bg-neutral-950/70">' +
|
|
109
|
+
'<td class="px-4 py-3 font-medium">' + escapeHtml(tag.name) + '</td>' +
|
|
110
|
+
'<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + escapeHtml(tag.slug) + '</td>' +
|
|
111
|
+
'<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + escapeHtml(tag.description) + '</td>' +
|
|
112
|
+
'<td class="px-4 py-3 text-right"><button type="button" data-id="' + tag.id + '" class="p-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/40 rounded"><span class="block h-4 w-4 i-hugeicons-waste"></span></button></td>' +
|
|
113
|
+
'</tr>'
|
|
114
|
+
}).join('')
|
|
115
|
+
}
|
|
116
|
+
async function load() {
|
|
117
|
+
var res = await fetch(api)
|
|
118
|
+
if (!res.ok) throw new Error('Unable to load CMS tags (' + res.status + ').')
|
|
119
|
+
var json = await res.json()
|
|
120
|
+
tags = Array.isArray(json.tags) ? json.tags : []
|
|
121
|
+
render()
|
|
122
|
+
}
|
|
123
|
+
document.getElementById('new-tag-button').addEventListener('click', function () {
|
|
124
|
+
$('tag-name').value = ''
|
|
125
|
+
$('tag-slug').value = ''
|
|
126
|
+
$('tag-description').value = ''
|
|
127
|
+
dialog.showModal()
|
|
128
|
+
})
|
|
129
|
+
$('tag-cancel').addEventListener('click', function () { dialog.close() })
|
|
130
|
+
$('tag-name').addEventListener('input', function () {
|
|
131
|
+
if (!$('tag-slug').dataset.touched) $('tag-slug').value = slug($('tag-name').value)
|
|
132
|
+
})
|
|
133
|
+
$('tag-slug').addEventListener('input', function () { $('tag-slug').dataset.touched = '1' })
|
|
134
|
+
document.getElementById('tag-form').addEventListener('submit', async function (event) {
|
|
135
|
+
event.preventDefault()
|
|
136
|
+
var res = await fetch(api, {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
headers: writeHeaders(),
|
|
139
|
+
body: JSON.stringify({ name: $('tag-name').value, slug: $('tag-slug').value, description: $('tag-description').value }),
|
|
140
|
+
})
|
|
141
|
+
if (!res.ok) {
|
|
142
|
+
var err = await res.json().catch(function () { return {} })
|
|
143
|
+
return alert(err.message || err.error || 'Could not create tag')
|
|
144
|
+
}
|
|
145
|
+
dialog.close()
|
|
146
|
+
await load()
|
|
147
|
+
})
|
|
148
|
+
table.addEventListener('click', async function (event) {
|
|
149
|
+
var button = event.target.closest('button[data-id]')
|
|
150
|
+
if (!button || !confirm('Delete this tag?')) return
|
|
151
|
+
var res = await fetch(api + '/' + button.getAttribute('data-id'), { method: 'DELETE', headers: writeHeaders() })
|
|
152
|
+
if (!res.ok) {
|
|
153
|
+
var err = await res.json().catch(function () { return {} })
|
|
154
|
+
return alert(err.message || err.error || 'Could not delete tag')
|
|
155
|
+
}
|
|
156
|
+
await load()
|
|
157
|
+
})
|
|
158
|
+
search.addEventListener('input', render)
|
|
159
|
+
|
|
160
|
+
load().catch(function (error) {
|
|
161
|
+
empty.classList.remove('hidden')
|
|
162
|
+
empty.textContent = error.message || 'Unable to load CMS tags.'
|
|
163
|
+
})
|
|
164
|
+
})()
|
|
165
|
+
</script>
|
|
166
|
+
|
|
167
|
+
@endsection
|