brut 0.0.20 → 0.0.22
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.
- checksums.yaml +4 -4
- data/.gitignore +24 -3
- data/.nvim.lua +1 -0
- data/Dockerfile.dx +12 -3
- data/Gemfile.lock +9 -7
- data/README.md +0 -7
- data/Rakefile +6 -4
- data/bin/dev +20 -0
- data/bin/docs +27 -0
- data/bin/setup +47 -1
- data/brut-css/.nvim.lua +1 -0
- data/brut-css/README.md +28 -0
- data/brut-css/bin/build +31 -0
- data/brut-css/bin/dev +1 -0
- data/brut-css/bin/docs +15 -0
- data/brut-css/bin/setup +5 -0
- data/brut-css/config/media-queries-all.css +15 -0
- data/brut-css/config/media-queries-minimal.css +5 -0
- data/brut-css/config/postcss.config.cjs +7 -0
- data/brut-css/config/pseudo-classes-all.css +9 -0
- data/brut-css/dx +1 -0
- data/brut-css/package-lock.json +3217 -0
- data/brut-css/package.json +36 -0
- data/brut-css/src/css/appearance.css +145 -0
- data/brut-css/src/css/border.css +522 -0
- data/brut-css/src/css/colors.css +3502 -0
- data/brut-css/src/css/dimensions.css +548 -0
- data/brut-css/src/css/flex.css +179 -0
- data/brut-css/src/css/index.css +13 -0
- data/brut-css/src/css/layout.css +120 -0
- data/brut-css/src/css/list.css +41 -0
- data/brut-css/src/css/positioning.css +354 -0
- data/brut-css/src/css/properties/colors.css +455 -0
- data/brut-css/src/css/properties/index.css +3 -0
- data/brut-css/src/css/properties/spacing.css +140 -0
- data/brut-css/src/css/properties/typography.css +224 -0
- data/brut-css/src/css/reset.css +107 -0
- data/brut-css/src/css/spacing.css +585 -0
- data/brut-css/src/css/typography.css +519 -0
- data/brut-css/src/css/utils.css +104 -0
- data/brut-css/src/docs/1_getting-started/1_overview.md +46 -0
- data/brut-css/src/docs/1_getting-started/2_installation.md +25 -0
- data/brut-css/src/docs/1_getting-started/3_core-concepts.md +75 -0
- data/brut-css/src/docs/1_getting-started/4_simple-example.md +132 -0
- data/brut-css/src/docs/1_getting-started/page.html.ejs +10 -0
- data/brut-css/src/docs/2_properties/page.html.ejs +71 -0
- data/brut-css/src/docs/3_classes/color-demo.html.ejs +31 -0
- data/brut-css/src/docs/3_classes/page.html.ejs +87 -0
- data/brut-css/src/docs/4_customization/1_design-system.md +36 -0
- data/brut-css/src/docs/4_customization/2_breakpoints.md +75 -0
- data/brut-css/src/docs/4_customization/3_pseudo-classes.md +74 -0
- data/brut-css/src/docs/4_customization/4_advanced-configuration.md +40 -0
- data/brut-css/src/docs/4_customization/page.html.ejs +10 -0
- data/brut-css/src/docs/docs.css +98 -0
- data/brut-css/src/docs/includes/body-and-header.html.ejs +30 -0
- data/brut-css/src/docs/includes/footer-and-rest.html.ejs +9 -0
- data/brut-css/src/docs/includes/head.html.ejs +5 -0
- data/brut-css/src/docs/includes/nav.html.ejs +10 -0
- data/brut-css/src/docs/index.html.ejs +32 -0
- data/brut-css/src/docs/prism-twilight.min.css +1 -0
- data/brut-css/src/js/Logger.js +71 -0
- data/brut-css/src/js/build.js +111 -0
- data/brut-css/src/js/cli/CLIArgError.js +7 -0
- data/brut-css/src/js/cli/Debug.js +27 -0
- data/brut-css/src/js/cli/DocsDir.js +16 -0
- data/brut-css/src/js/cli/DocsTemplateSourceDir.js +16 -0
- data/brut-css/src/js/cli/InputFile.js +31 -0
- data/brut-css/src/js/cli/MediaQueryConfigFile.js +10 -0
- data/brut-css/src/js/cli/OutputFile.js +22 -0
- data/brut-css/src/js/cli/ParsedArg.js +17 -0
- data/brut-css/src/js/cli/PathToBrutCSSRoot.js +19 -0
- data/brut-css/src/js/cli/PseudoClassConfigFile.js +11 -0
- data/brut-css/src/js/cli.js +108 -0
- data/brut-css/src/js/docGenerator.js +467 -0
- data/brut-css/src/js/mediaQueryConfigParser.js +98 -0
- data/brut-css/src/js/post-css-plugins/addMediaQueriesPlugin.js +49 -0
- data/brut-css/src/js/post-css-plugins/addPseudoClassesPlugin.js +42 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Category.js +9 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/DocState.js +185 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Documentable.js +8 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Group.js +7 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/ParsedComment.js +73 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Property.js +9 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyCategory.js +4 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyGroup.js +8 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Rule.js +12 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleCategory.js +4 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleGroup.js +8 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeRef.js +5 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeURL.js +9 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin.js +49 -0
- data/brut-css/src/js/post-css-plugins/generateRootCustomPropertiesPlugin.js +45 -0
- data/brut-css/src/js/pseudoClassConfigParser.js +145 -0
- data/brut-js/.projections.json +10 -0
- data/brut-js/README.md +118 -0
- data/brut-js/bin/build +10 -0
- data/brut-js/bin/ci +5 -0
- data/brut-js/bin/setup +5 -0
- data/brut-js/docs/README.md +8 -0
- data/brut-js/docs/jsdoc-plugins/customElementTag.js +8 -0
- data/brut-js/docs/jsdoc-theme/publish.js +692 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/linenumber.js +25 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/prettify/Apache-License-2.0.txt +202 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/prettify/lang-css.js +2 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/prettify/prettify.js +28 -0
- data/brut-js/docs/jsdoc-theme/static/styles/jsdoc-default.css +327 -0
- data/brut-js/docs/jsdoc-theme/static/styles/prettify-jsdoc.css +111 -0
- data/brut-js/docs/jsdoc-theme/static/styles/prettify-tomorrow.css +132 -0
- data/brut-js/docs/jsdoc-theme/tmpl/augments.tmpl +10 -0
- data/brut-js/docs/jsdoc-theme/tmpl/container.tmpl +199 -0
- data/brut-js/docs/jsdoc-theme/tmpl/details.tmpl +143 -0
- data/brut-js/docs/jsdoc-theme/tmpl/example.tmpl +2 -0
- data/brut-js/docs/jsdoc-theme/tmpl/examples.tmpl +13 -0
- data/brut-js/docs/jsdoc-theme/tmpl/exceptions.tmpl +32 -0
- data/brut-js/docs/jsdoc-theme/tmpl/layout.tmpl +38 -0
- data/brut-js/docs/jsdoc-theme/tmpl/mainpage.tmpl +14 -0
- data/brut-js/docs/jsdoc-theme/tmpl/members.tmpl +38 -0
- data/brut-js/docs/jsdoc-theme/tmpl/method.tmpl +131 -0
- data/brut-js/docs/jsdoc-theme/tmpl/modifies.tmpl +14 -0
- data/brut-js/docs/jsdoc-theme/tmpl/params.tmpl +131 -0
- data/brut-js/docs/jsdoc-theme/tmpl/properties.tmpl +108 -0
- data/brut-js/docs/jsdoc-theme/tmpl/returns.tmpl +19 -0
- data/brut-js/docs/jsdoc-theme/tmpl/source.tmpl +8 -0
- data/brut-js/docs/jsdoc-theme/tmpl/tutorial.tmpl +19 -0
- data/brut-js/docs/jsdoc-theme/tmpl/type.tmpl +7 -0
- data/brut-js/docs/jsdoc.config.json +23 -0
- data/brut-js/docs/package-lock.json +343 -0
- data/brut-js/docs/package.json +7 -0
- data/brut-js/package-lock.json +2171 -0
- data/brut-js/package.json +32 -0
- data/brut-js/specs/AjaxSubmit.spec.js +256 -0
- data/brut-js/specs/Autosubmit.spec.js +127 -0
- data/brut-js/specs/ConfirmSubmit.spec.js +193 -0
- data/brut-js/specs/ConstraintViolationMessage.spec.js +33 -0
- data/brut-js/specs/ConstraintViolationMessages.spec.js +29 -0
- data/brut-js/specs/CopyToClipboard.spec.js +35 -0
- data/brut-js/specs/Form.spec.js +181 -0
- data/brut-js/specs/I18nTranslation.spec.js +19 -0
- data/brut-js/specs/LocaleDetection.spec.js +22 -0
- data/brut-js/specs/Message.spec.js +15 -0
- data/brut-js/specs/SpecHelper.js +23 -0
- data/brut-js/specs/Tabs.spec.js +41 -0
- data/brut-js/specs/config/asset_metadata.json +7 -0
- data/brut-js/src/AjaxSubmit.js +384 -0
- data/brut-js/src/Autosubmit.js +63 -0
- data/brut-js/src/BaseCustomElement.js +261 -0
- data/brut-js/src/ConfirmSubmit.js +116 -0
- data/brut-js/src/ConfirmationDialog.js +143 -0
- data/brut-js/src/ConstraintViolationMessage.js +125 -0
- data/brut-js/src/ConstraintViolationMessages.js +98 -0
- data/brut-js/src/CopyToClipboard.js +96 -0
- data/brut-js/src/Form.js +151 -0
- data/brut-js/src/I18nTranslation.js +61 -0
- data/brut-js/src/LocaleDetection.js +117 -0
- data/brut-js/src/Logger.js +90 -0
- data/brut-js/src/Message.js +56 -0
- data/brut-js/src/RichString.js +113 -0
- data/brut-js/src/Tabs.js +168 -0
- data/brut-js/src/Tracing.js +247 -0
- data/brut-js/src/appForTestingOnly.js +15 -0
- data/brut-js/src/index.js +130 -0
- data/brut-js/src/testing/AssetMetadata.js +35 -0
- data/brut-js/src/testing/AssetMetadataLoader.js +25 -0
- data/brut-js/src/testing/CustomElementTest.js +235 -0
- data/brut-js/src/testing/DOMCreator.js +45 -0
- data/brut-js/src/testing/index.js +48 -0
- data/brutrb.com/.vitepress/config.mjs +106 -0
- data/brutrb.com/.vitepress/plugins/jsdocLinker.js +34 -0
- data/brutrb.com/.vitepress/plugins/rdocLinker.js +18 -0
- data/brutrb.com/.vitepress/theme/custom.css +7 -0
- data/brutrb.com/.vitepress/theme/index.js +18 -0
- data/brutrb.com/.vitepress/theme/style.css +149 -0
- data/brutrb.com/ai.md +68 -0
- data/brutrb.com/assets.md +138 -0
- data/brutrb.com/bin/build +5 -0
- data/brutrb.com/bin/deploy +7 -0
- data/brutrb.com/bin/dev +5 -0
- data/brutrb.com/bin/setup +5 -0
- data/brutrb.com/brut-js.md +117 -0
- data/brutrb.com/business-logic.md +55 -0
- data/brutrb.com/cli.md +278 -0
- data/brutrb.com/components.md +243 -0
- data/brutrb.com/configuration.md +257 -0
- data/brutrb.com/css.md +103 -0
- data/brutrb.com/custom-element-tests.md +149 -0
- data/brutrb.com/database-access.md +201 -0
- data/brutrb.com/database-schema.md +312 -0
- data/brutrb.com/deployment.md +66 -0
- data/brutrb.com/dev-environment.md +179 -0
- data/brutrb.com/doc-conventions.md +39 -0
- data/brutrb.com/end-to-end-tests.md +174 -0
- data/brutrb.com/flash-and-session.md +224 -0
- data/brutrb.com/forms.md +866 -0
- data/brutrb.com/getting-started.md +66 -0
- data/brutrb.com/handlers.md +153 -0
- data/brutrb.com/hooks.md +178 -0
- data/brutrb.com/i18n.md +188 -0
- data/brutrb.com/images/Makefile +10 -0
- data/brutrb.com/images/dev-env-overview.dot +54 -0
- data/brutrb.com/images/dev-env-overview.png +0 -0
- data/brutrb.com/images/dev-env-protocol.dot +37 -0
- data/brutrb.com/images/dev-env-protocol.png +0 -0
- data/brutrb.com/images/logo-300.png +0 -0
- data/brutrb.com/images/logo.png +0 -0
- data/brutrb.com/images/overview.graffle +0 -0
- data/brutrb.com/images/overview.png +0 -0
- data/brutrb.com/images/spa.dot +19 -0
- data/brutrb.com/images/spa.png +0 -0
- data/brutrb.com/images/workspace-protocol.dot +44 -0
- data/brutrb.com/images/workspace-protocol.png +0 -0
- data/brutrb.com/index.md +36 -0
- data/brutrb.com/instrumentation.md +183 -0
- data/brutrb.com/javascript.md +122 -0
- data/brutrb.com/jobs.md +14 -0
- data/{doc-src → brutrb.com}/keyword-injection.md +122 -68
- data/brutrb.com/markdown-examples.md +85 -0
- data/brutrb.com/middleware.md +80 -0
- data/brutrb.com/not-released.md +5 -0
- data/brutrb.com/overview.md +404 -0
- data/brutrb.com/package-lock.json +2404 -0
- data/brutrb.com/package.json +11 -0
- data/brutrb.com/pages.md +378 -0
- data/brutrb.com/public/images/logo-300.png +0 -0
- data/brutrb.com/public/images/logo.png +0 -0
- data/brutrb.com/routes.md +215 -0
- data/brutrb.com/security.md +105 -0
- data/brutrb.com/seed-data.md +63 -0
- data/brutrb.com/space-time-continuum.md +85 -0
- data/brutrb.com/tutorial.md +3 -0
- data/brutrb.com/unit-tests.md +148 -0
- data/docker-compose.dx.yml +6 -3
- data/docs/404.html +21 -0
- data/docs/CNAME +1 -0
- data/docs/ai.html +24 -0
- data/docs/api/Brut/BackEnd/SeedData.html +493 -0
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +214 -0
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +125 -0
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +125 -0
- data/docs/api/Brut/BackEnd/Sidekiq.html +125 -0
- data/docs/api/Brut/BackEnd/Validators/FormValidator.html +414 -0
- data/docs/api/Brut/BackEnd/Validators.html +128 -0
- data/docs/api/Brut/BackEnd.html +132 -0
- data/docs/api/Brut/CLI/App.html +1576 -0
- data/docs/api/Brut/CLI/AppRunner.html +491 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +264 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +306 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +262 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +314 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +183 -0
- data/docs/api/Brut/CLI/Apps/DB/Create.html +365 -0
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +357 -0
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +383 -0
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +335 -0
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +329 -0
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +347 -0
- data/docs/api/Brut/CLI/Apps/DB/Status.html +383 -0
- data/docs/api/Brut/CLI/Apps/DB.html +183 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +303 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +512 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +398 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +374 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +410 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +262 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +303 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +480 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +450 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +380 -0
- data/docs/api/Brut/CLI/Apps/Scaffold.html +253 -0
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +464 -0
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +407 -0
- data/docs/api/Brut/CLI/Apps/Test/JS.html +262 -0
- data/docs/api/Brut/CLI/Apps/Test/Run.html +578 -0
- data/docs/api/Brut/CLI/Apps/Test.html +253 -0
- data/docs/api/Brut/CLI/Apps.html +125 -0
- data/docs/api/Brut/CLI/Command.html +2342 -0
- data/docs/api/Brut/CLI/Error.html +139 -0
- data/docs/api/Brut/CLI/ExecutionResults/Result.html +664 -0
- data/docs/api/Brut/CLI/ExecutionResults.html +675 -0
- data/docs/api/Brut/CLI/Executor.html +430 -0
- data/docs/api/Brut/CLI/InvalidOption.html +245 -0
- data/docs/api/Brut/CLI/Options.html +753 -0
- data/docs/api/Brut/CLI/Output.html +699 -0
- data/docs/api/Brut/CLI/SystemExecError.html +451 -0
- data/docs/api/Brut/CLI.html +263 -0
- data/docs/api/Brut/FactoryBot.html +225 -0
- data/docs/api/Brut/Framework/App.html +1097 -0
- data/docs/api/Brut/Framework/Config.html +1045 -0
- data/docs/api/Brut/Framework/Container.html +1379 -0
- data/docs/api/Brut/Framework/Error.html +140 -0
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +144 -0
- data/docs/api/Brut/Framework/Errors/Bug.html +234 -0
- data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +257 -0
- data/docs/api/Brut/Framework/Errors/MissingParameter.html +273 -0
- data/docs/api/Brut/Framework/Errors/NoClassForPath.html +471 -0
- data/docs/api/Brut/Framework/Errors/NotFound.html +308 -0
- data/docs/api/Brut/Framework/Errors/NotImplemented.html +234 -0
- data/docs/api/Brut/Framework/Errors.html +328 -0
- data/docs/api/Brut/Framework/FussyTypeEnforcement.html +392 -0
- data/docs/api/Brut/Framework/MCP.html +861 -0
- data/docs/api/Brut/Framework/ProjectEnvironment.html +648 -0
- data/docs/api/Brut/Framework.html +129 -0
- data/docs/api/Brut/FrontEnd/AssetPathResolver.html +317 -0
- data/docs/api/Brut/FrontEnd/Component/Helpers.html +326 -0
- data/docs/api/Brut/FrontEnd/Component.html +365 -0
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +470 -0
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +518 -0
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +317 -0
- data/docs/api/Brut/FrontEnd/Components/Input.html +195 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +339 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +660 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +417 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +918 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +651 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +125 -0
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +367 -0
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +336 -0
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +655 -0
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +352 -0
- data/docs/api/Brut/FrontEnd/Components.html +135 -0
- data/docs/api/Brut/FrontEnd/Download.html +467 -0
- data/docs/api/Brut/FrontEnd/Flash.html +1150 -0
- data/docs/api/Brut/FrontEnd/Form.html +1157 -0
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +634 -0
- data/docs/api/Brut/FrontEnd/Forms/Input.html +615 -0
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +547 -0
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1318 -0
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +609 -0
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +587 -0
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +613 -0
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +582 -0
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +609 -0
- data/docs/api/Brut/FrontEnd/Forms.html +127 -0
- data/docs/api/Brut/FrontEnd/GenericResponse.html +377 -0
- data/docs/api/Brut/FrontEnd/Handler.html +442 -0
- data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +318 -0
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +336 -0
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +399 -0
- data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +354 -0
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +151 -0
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +315 -0
- data/docs/api/Brut/FrontEnd/Handlers.html +125 -0
- data/docs/api/Brut/FrontEnd/HandlingResults.html +339 -0
- data/docs/api/Brut/FrontEnd/HttpMethod.html +661 -0
- data/docs/api/Brut/FrontEnd/HttpStatus.html +496 -0
- data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +284 -0
- data/docs/api/Brut/FrontEnd/Layout.html +318 -0
- data/docs/api/Brut/FrontEnd/Middleware.html +135 -0
- data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +288 -0
- data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +292 -0
- data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +324 -0
- data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +372 -0
- data/docs/api/Brut/FrontEnd/Middlewares.html +125 -0
- data/docs/api/Brut/FrontEnd/Page.html +773 -0
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +797 -0
- data/docs/api/Brut/FrontEnd/Pages.html +125 -0
- data/docs/api/Brut/FrontEnd/RequestContext.html +1312 -0
- data/docs/api/Brut/FrontEnd/RouteHook.html +424 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +242 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +249 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +264 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +261 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +284 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +252 -0
- data/docs/api/Brut/FrontEnd/RouteHooks.html +115 -0
- data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +227 -0
- data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +305 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +324 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +319 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +315 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +315 -0
- data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +327 -0
- data/docs/api/Brut/FrontEnd/Routing/Route.html +761 -0
- data/docs/api/Brut/FrontEnd/Routing.html +927 -0
- data/docs/api/Brut/FrontEnd/Session.html +1195 -0
- data/docs/api/Brut/FrontEnd.html +134 -0
- data/docs/api/Brut/I18n/BaseMethods.html +931 -0
- data/docs/api/Brut/I18n/ForBackEnd.html +302 -0
- data/docs/api/Brut/I18n/ForCLI.html +302 -0
- data/docs/api/Brut/I18n/ForHTML.html +296 -0
- data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +316 -0
- data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +930 -0
- data/docs/api/Brut/I18n.html +127 -0
- data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +435 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +286 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +302 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry.html +864 -0
- data/docs/api/Brut/Instrumentation.html +126 -0
- data/docs/api/Brut/SinatraHelpers/ClassMethods.html +532 -0
- data/docs/api/Brut/SinatraHelpers.html +281 -0
- data/docs/api/Brut/SpecSupport/ClockSupport.html +383 -0
- data/docs/api/Brut/SpecSupport/ComponentSupport.html +502 -0
- data/docs/api/Brut/SpecSupport/E2ETestServer.html +503 -0
- data/docs/api/Brut/SpecSupport/E2eSupport.html +142 -0
- data/docs/api/Brut/SpecSupport/EnhancedNode.html +403 -0
- data/docs/api/Brut/SpecSupport/FlashSupport.html +278 -0
- data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +401 -0
- data/docs/api/Brut/SpecSupport/GeneralSupport.html +195 -0
- data/docs/api/Brut/SpecSupport/HandlerSupport.html +160 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +553 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +439 -0
- data/docs/api/Brut/SpecSupport/Matchers.html +125 -0
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +335 -0
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +602 -0
- data/docs/api/Brut/SpecSupport/SessionSupport.html +196 -0
- data/docs/api/Brut/SpecSupport.html +129 -0
- data/docs/api/Brut.html +225 -0
- data/docs/api/Clock.html +603 -0
- data/docs/api/RichString.html +968 -0
- data/docs/api/SemanticLogger/Appender/Async.html +219 -0
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +115 -0
- data/docs/api/Sequel/Extensions/BrutMigrations.html +533 -0
- data/docs/api/Sequel/Extensions.html +117 -0
- data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +105 -0
- data/docs/api/Sequel/Plugins/CreatedAt.html +125 -0
- data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +207 -0
- data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +186 -0
- data/docs/api/Sequel/Plugins/ExternalId.html +218 -0
- data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +202 -0
- data/docs/api/Sequel/Plugins/FindBang.html +125 -0
- data/docs/api/Sequel/Plugins.html +117 -0
- data/docs/api/Sequel.html +117 -0
- data/docs/api/_index.html +1553 -0
- data/docs/api/class_list.html +54 -0
- data/docs/api/css/common.css +1 -0
- data/docs/api/css/full_list.css +58 -0
- data/docs/api/css/style.css +503 -0
- data/docs/api/file.README.html +127 -0
- data/docs/api/file_list.html +59 -0
- data/docs/api/frames.html +22 -0
- data/docs/api/index.html +127 -0
- data/docs/api/js/app.js +344 -0
- data/docs/api/js/full_list.js +242 -0
- data/docs/api/js/jquery.js +4 -0
- data/docs/api/method_list.html +3998 -0
- data/docs/api/top-level-namespace.html +112 -0
- data/docs/assets/ai.md.tZrjP9im.js +1 -0
- data/docs/assets/ai.md.tZrjP9im.lean.js +1 -0
- data/docs/assets/app.D_yaTITQ.js +1 -0
- data/docs/assets/assets.md.D3wunzLx.js +19 -0
- data/docs/assets/assets.md.D3wunzLx.lean.js +1 -0
- data/docs/assets/brut-js.md.o2DAO2s2.js +12 -0
- data/docs/assets/brut-js.md.o2DAO2s2.lean.js +1 -0
- data/docs/assets/business-logic.md.BY4hGy0m.js +1 -0
- data/docs/assets/business-logic.md.BY4hGy0m.lean.js +1 -0
- data/docs/assets/chunks/@localSearchIndexroot.BsN5i0Fi.js +1 -0
- data/docs/assets/chunks/VPLocalSearchBox.B2-ZzyTY.js +8 -0
- data/docs/assets/chunks/framework.1L-BeKqY.js +18 -0
- data/docs/assets/chunks/theme.CfGFVRvE.js +2 -0
- data/docs/assets/cli.md.RmeA2b0i.js +127 -0
- data/docs/assets/cli.md.RmeA2b0i.lean.js +1 -0
- data/docs/assets/components.md.eCttGlN-.js +104 -0
- data/docs/assets/components.md.eCttGlN-.lean.js +1 -0
- data/docs/assets/configuration.md.BRriU0cL.js +78 -0
- data/docs/assets/configuration.md.BRriU0cL.lean.js +1 -0
- data/docs/assets/css.md.DJgj2clw.js +21 -0
- data/docs/assets/css.md.DJgj2clw.lean.js +1 -0
- data/docs/assets/custom-element-tests.md.BrYJQEl3.js +69 -0
- data/docs/assets/custom-element-tests.md.BrYJQEl3.lean.js +1 -0
- data/docs/assets/database-access.md.C7l-Vuvb.js +63 -0
- data/docs/assets/database-access.md.C7l-Vuvb.lean.js +1 -0
- data/docs/assets/database-schema.md.BUjR0VS1.js +63 -0
- data/docs/assets/database-schema.md.BUjR0VS1.lean.js +1 -0
- data/docs/assets/deployment.md.Dbka4OTr.js +1 -0
- data/docs/assets/deployment.md.Dbka4OTr.lean.js +1 -0
- data/docs/assets/dev-env-overview.Gj7NWM8-.png +0 -0
- data/docs/assets/dev-env-protocol.DysDAtnz.png +0 -0
- data/docs/assets/dev-environment.md.BNc8AYiK.js +11 -0
- data/docs/assets/dev-environment.md.BNc8AYiK.lean.js +1 -0
- data/docs/assets/doc-conventions.md.DCfRXXi-.js +1 -0
- data/docs/assets/doc-conventions.md.DCfRXXi-.lean.js +1 -0
- data/docs/assets/end-to-end-tests.md.yfQHC0b5.js +26 -0
- data/docs/assets/end-to-end-tests.md.yfQHC0b5.lean.js +1 -0
- data/docs/assets/flash-and-session.md.BXY8RvT0.js +93 -0
- data/docs/assets/flash-and-session.md.BXY8RvT0.lean.js +1 -0
- data/docs/assets/forms.md.CBTYQ_Cz.js +379 -0
- data/docs/assets/forms.md.CBTYQ_Cz.lean.js +1 -0
- data/docs/assets/getting-started.md.Bz2s1Vjb.js +2 -0
- data/docs/assets/getting-started.md.Bz2s1Vjb.lean.js +1 -0
- data/docs/assets/handlers.md.089DVD3v.js +69 -0
- data/docs/assets/handlers.md.089DVD3v.lean.js +1 -0
- data/docs/assets/hooks.md.C4-moMny.js +80 -0
- data/docs/assets/hooks.md.C4-moMny.lean.js +1 -0
- data/docs/assets/i18n.md.Do9i1qWl.js +23 -0
- data/docs/assets/i18n.md.Do9i1qWl.lean.js +1 -0
- data/docs/assets/index.md.B28EwVpq.js +1 -0
- data/docs/assets/index.md.B28EwVpq.lean.js +1 -0
- data/docs/assets/instrumentation.md.CL6ax7nT.js +35 -0
- data/docs/assets/instrumentation.md.CL6ax7nT.lean.js +1 -0
- data/docs/assets/javascript.md.GWbhRS51.js +31 -0
- data/docs/assets/javascript.md.GWbhRS51.lean.js +1 -0
- data/docs/assets/jobs.md.S-2amAYp.js +1 -0
- data/docs/assets/jobs.md.S-2amAYp.lean.js +1 -0
- data/docs/assets/keyword-injection.md.Dt2tKREs.js +25 -0
- data/docs/assets/keyword-injection.md.Dt2tKREs.lean.js +1 -0
- data/docs/assets/markdown-examples.md.CCFEQO44.js +33 -0
- data/docs/assets/markdown-examples.md.CCFEQO44.lean.js +1 -0
- data/docs/assets/middleware.md.Czz_UlJN.js +20 -0
- data/docs/assets/middleware.md.Czz_UlJN.lean.js +1 -0
- data/docs/assets/not-released.md.BBy28McC.js +1 -0
- data/docs/assets/not-released.md.BBy28McC.lean.js +1 -0
- data/docs/assets/overview.Da81cB9R.png +0 -0
- data/docs/assets/overview.md.CDalkuxV.js +133 -0
- data/docs/assets/overview.md.CDalkuxV.lean.js +1 -0
- data/docs/assets/pages.md.BE3kfOc5.js +122 -0
- data/docs/assets/pages.md.BE3kfOc5.lean.js +1 -0
- data/docs/assets/routes.md.BMM7peut.js +29 -0
- data/docs/assets/routes.md.BMM7peut.lean.js +1 -0
- data/docs/assets/security.md.C668yXCi.js +1 -0
- data/docs/assets/security.md.C668yXCi.lean.js +1 -0
- data/docs/assets/seed-data.md.BvFZlqIk.js +14 -0
- data/docs/assets/seed-data.md.BvFZlqIk.lean.js +1 -0
- data/docs/assets/spa.qejUdp-5.png +0 -0
- data/docs/assets/space-time-continuum.md.KPUIKysQ.js +1 -0
- data/docs/assets/space-time-continuum.md.KPUIKysQ.lean.js +1 -0
- data/docs/assets/style.D73IYGCX.css +1 -0
- data/docs/assets/tutorial.md.BnoGjrdK.js +1 -0
- data/docs/assets/tutorial.md.BnoGjrdK.lean.js +1 -0
- data/docs/assets/unit-tests.md.DUGrnLj5.js +13 -0
- data/docs/assets/unit-tests.md.DUGrnLj5.lean.js +1 -0
- data/docs/assets/workspace-protocol.C0gXsoDb.png +0 -0
- data/docs/assets.html +42 -0
- data/docs/brut-css/brut.css +1 -0
- data/docs/brut-css/brut.max.css +22372 -0
- data/docs/brut-css/classes/appearances.html +783 -0
- data/docs/brut-css/classes/background-colors.html +3529 -0
- data/docs/brut-css/classes/border-colors.html +3529 -0
- data/docs/brut-css/classes/borders.html +2293 -0
- data/docs/brut-css/classes/dimensions.html +2581 -0
- data/docs/brut-css/classes/flex.html +917 -0
- data/docs/brut-css/classes/foreground-colors.html +3261 -0
- data/docs/brut-css/classes/junk-drawer.html +431 -0
- data/docs/brut-css/classes/layout.html +668 -0
- data/docs/brut-css/classes/lists.html +331 -0
- data/docs/brut-css/classes/positioning.html +1751 -0
- data/docs/brut-css/classes/spacings.html +2633 -0
- data/docs/brut-css/classes/typography.html +2206 -0
- data/docs/brut-css/customization/advanced-configuration.html +204 -0
- data/docs/brut-css/customization/breakpoints.html +227 -0
- data/docs/brut-css/customization/design-system.html +197 -0
- data/docs/brut-css/customization/pseudo-classes.html +228 -0
- data/docs/brut-css/docs.css +98 -0
- data/docs/brut-css/getting-started/core-concepts.html +234 -0
- data/docs/brut-css/getting-started/installation.html +190 -0
- data/docs/brut-css/getting-started/overview.html +210 -0
- data/docs/brut-css/getting-started/simple-example.html +285 -0
- data/docs/brut-css/index.html +193 -0
- data/docs/brut-css/prism-twilight.min.css +1 -0
- data/docs/brut-css/properties/colors.html +1548 -0
- data/docs/brut-css/properties/spacings.html +614 -0
- data/docs/brut-css/properties/typography.html +777 -0
- data/docs/brut-js/api/AjaxSubmit.html +374 -0
- data/docs/brut-js/api/AjaxSubmit.js.html +435 -0
- data/docs/brut-js/api/Autosubmit.html +192 -0
- data/docs/brut-js/api/Autosubmit.js.html +114 -0
- data/docs/brut-js/api/BaseCustomElement.html +1091 -0
- data/docs/brut-js/api/BaseCustomElement.js.html +312 -0
- data/docs/brut-js/api/BrutCustomElements.html +172 -0
- data/docs/brut-js/api/BufferedLogger.html +173 -0
- data/docs/brut-js/api/ConfirmSubmit.html +278 -0
- data/docs/brut-js/api/ConfirmSubmit.js.html +167 -0
- data/docs/brut-js/api/ConfirmationDialog.html +425 -0
- data/docs/brut-js/api/ConfirmationDialog.js.html +194 -0
- data/docs/brut-js/api/ConstraintViolationMessage.html +448 -0
- data/docs/brut-js/api/ConstraintViolationMessage.js.html +176 -0
- data/docs/brut-js/api/ConstraintViolationMessages.html +590 -0
- data/docs/brut-js/api/ConstraintViolationMessages.js.html +149 -0
- data/docs/brut-js/api/CopyToClipboard.html +345 -0
- data/docs/brut-js/api/CopyToClipboard.js.html +147 -0
- data/docs/brut-js/api/Form.html +294 -0
- data/docs/brut-js/api/Form.js.html +202 -0
- data/docs/brut-js/api/I18nTranslation.html +409 -0
- data/docs/brut-js/api/I18nTranslation.js.html +112 -0
- data/docs/brut-js/api/LocaleDetection.html +312 -0
- data/docs/brut-js/api/LocaleDetection.js.html +168 -0
- data/docs/brut-js/api/Logger.html +702 -0
- data/docs/brut-js/api/Logger.js.html +141 -0
- data/docs/brut-js/api/Message.html +238 -0
- data/docs/brut-js/api/Message.js.html +107 -0
- data/docs/brut-js/api/PrefixedLogger.html +369 -0
- data/docs/brut-js/api/RichString.html +1049 -0
- data/docs/brut-js/api/RichString.js.html +164 -0
- data/docs/brut-js/api/Tabs.html +295 -0
- data/docs/brut-js/api/Tabs.js.html +219 -0
- data/docs/brut-js/api/Tracing.html +277 -0
- data/docs/brut-js/api/Tracing.js.html +298 -0
- data/docs/brut-js/api/external-CustomElementRegistry.html +140 -0
- data/docs/brut-js/api/external-Performance.html +138 -0
- data/docs/brut-js/api/external-Promise.html +138 -0
- data/docs/brut-js/api/external-ValidityState.html +138 -0
- data/docs/brut-js/api/external-Window.html +233 -0
- data/docs/brut-js/api/external-fetch.html +138 -0
- data/docs/brut-js/api/global.html +400 -0
- data/docs/brut-js/api/index.html +168 -0
- data/docs/brut-js/api/index.js.html +181 -0
- data/docs/brut-js/api/module-testing.html +383 -0
- data/docs/brut-js/api/scripts/linenumber.js +25 -0
- data/docs/brut-js/api/scripts/prettify/Apache-License-2.0.txt +202 -0
- data/docs/brut-js/api/scripts/prettify/lang-css.js +2 -0
- data/docs/brut-js/api/scripts/prettify/prettify.js +28 -0
- data/docs/brut-js/api/styles/jsdoc-default.css +327 -0
- data/docs/brut-js/api/styles/prettify-jsdoc.css +111 -0
- data/docs/brut-js/api/styles/prettify-tomorrow.css +132 -0
- data/docs/brut-js/api/testing.AssetMetadata.html +172 -0
- data/docs/brut-js/api/testing.AssetMetadataLoader.html +171 -0
- data/docs/brut-js/api/testing.CustomElementTest.html +679 -0
- data/docs/brut-js/api/testing.DOMCreator.html +171 -0
- data/docs/brut-js/api/testing_AssetMetadata.js.html +86 -0
- data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +76 -0
- data/docs/brut-js/api/testing_CustomElementTest.js.html +286 -0
- data/docs/brut-js/api/testing_DOMCreator.js.html +96 -0
- data/docs/brut-js/api/testing_index.js.html +99 -0
- data/docs/brut-js.html +35 -0
- data/docs/business-logic.html +24 -0
- data/docs/cli.html +150 -0
- data/docs/components.html +127 -0
- data/docs/configuration.html +101 -0
- data/docs/css.html +44 -0
- data/docs/custom-element-tests.html +92 -0
- data/docs/database-access.html +86 -0
- data/docs/database-schema.html +86 -0
- data/docs/deployment.html +24 -0
- data/docs/dev-environment.html +34 -0
- data/docs/doc-conventions.html +24 -0
- data/docs/end-to-end-tests.html +49 -0
- data/docs/flash-and-session.html +116 -0
- data/docs/forms.html +402 -0
- data/docs/getting-started.html +25 -0
- data/docs/handlers.html +92 -0
- data/docs/hashmap.json +1 -0
- data/docs/hooks.html +103 -0
- data/docs/i18n.html +46 -0
- data/docs/images/logo-300.png +0 -0
- data/docs/images/logo.png +0 -0
- data/docs/index.html +24 -0
- data/docs/instrumentation.html +58 -0
- data/docs/javascript.html +54 -0
- data/docs/jobs.html +24 -0
- data/docs/keyword-injection.html +48 -0
- data/docs/markdown-examples.html +56 -0
- data/docs/middleware.html +43 -0
- data/docs/not-released.html +24 -0
- data/docs/overview.html +156 -0
- data/docs/pages.html +145 -0
- data/docs/routes.html +52 -0
- data/docs/security.html +24 -0
- data/docs/seed-data.html +37 -0
- data/docs/space-time-continuum.html +24 -0
- data/docs/tutorial.html +24 -0
- data/docs/unit-tests.html +36 -0
- data/docs/vp-icons.css +1 -0
- data/lib/brut/back_end/seed_data.rb +19 -2
- data/lib/brut/back_end/sidekiq/middlewares/server.rb +2 -1
- data/lib/brut/back_end/sidekiq/middlewares.rb +2 -1
- data/lib/brut/back_end/sidekiq.rb +2 -1
- data/lib/brut/back_end/validator.rb +5 -1
- data/lib/brut/back_end.rb +4 -2
- data/lib/brut/cli/app_runner.rb +1 -1
- data/lib/brut/cli/apps/test.rb +5 -0
- data/lib/brut/cli.rb +4 -3
- data/lib/brut/factory_bot.rb +0 -5
- data/lib/brut/framework/app.rb +70 -5
- data/lib/brut/framework/config.rb +5 -3
- data/lib/brut/framework/container.rb +3 -2
- data/lib/brut/framework/errors.rb +12 -4
- data/lib/brut/framework/mcp.rb +58 -1
- data/lib/brut/framework/project_environment.rb +6 -2
- data/lib/brut/framework.rb +1 -1
- data/lib/brut/front_end/component.rb +69 -71
- data/lib/brut/front_end/components/constraint_violations.rb +1 -4
- data/lib/brut/front_end/components/form_tag.rb +1 -1
- data/lib/brut/front_end/components/input.rb +3 -3
- data/lib/brut/front_end/components/inputs/csrf_token.rb +1 -1
- data/lib/brut/front_end/components/inputs/{text_field.rb → input_tag.rb} +7 -9
- data/lib/brut/front_end/components/inputs/radio_button.rb +1 -1
- data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +187 -0
- data/lib/brut/front_end/components/inputs/{textarea.rb → textarea_tag.rb} +2 -2
- data/lib/brut/front_end/components/time_tag.rb +2 -1
- data/lib/brut/front_end/form.rb +4 -4
- data/lib/brut/front_end/forms/input.rb +2 -1
- data/lib/brut/front_end/forms/input_definition.rb +5 -2
- data/lib/brut/front_end/forms/radio_button_group_input.rb +2 -1
- data/lib/brut/front_end/forms/radio_button_group_input_definition.rb +2 -2
- data/lib/brut/front_end/forms/select_input.rb +2 -4
- data/lib/brut/front_end/forms/select_input_definition.rb +2 -2
- data/lib/brut/front_end/handler.rb +28 -26
- data/lib/brut/front_end/handlers/csp_reporting_handler.rb +5 -2
- data/lib/brut/front_end/handlers/instrumentation_handler.rb +8 -4
- data/lib/brut/front_end/handlers/locale_detection_handler.rb +9 -5
- data/lib/brut/front_end/handlers/missing_handler.rb +5 -2
- data/lib/brut/front_end/layout.rb +16 -0
- data/lib/brut/front_end/page.rb +52 -29
- data/lib/brut/front_end/request_context.rb +3 -2
- data/lib/brut/front_end/routing.rb +5 -1
- data/lib/brut/front_end.rb +4 -13
- data/lib/brut/i18n/base_methods.rb +167 -79
- data/lib/brut/i18n/for_back_end.rb +4 -0
- data/lib/brut/i18n/for_cli.rb +4 -0
- data/lib/brut/i18n/for_html.rb +32 -4
- data/lib/brut/i18n/http_accept_language.rb +47 -0
- data/lib/brut/instrumentation/open_telemetry.rb +36 -1
- data/lib/brut/instrumentation.rb +3 -5
- data/lib/brut/sinatra_helpers.rb +11 -3
- data/lib/brut/spec_support/component_support.rb +30 -16
- data/lib/brut/spec_support/e2e_support.rb +1 -1
- data/lib/brut/spec_support/e2e_test_server.rb +3 -0
- data/lib/brut/spec_support/general_support.rb +3 -0
- data/lib/brut/spec_support/handler_support.rb +6 -1
- data/lib/brut/spec_support/matcher.rb +1 -0
- data/lib/brut/spec_support/matchers/be_page_for.rb +1 -0
- data/lib/brut/spec_support/matchers/have_html_attribute.rb +1 -0
- data/lib/brut/spec_support/matchers/have_i18n_string.rb +2 -5
- data/lib/brut/spec_support/matchers/have_link_to.rb +1 -0
- data/lib/brut/spec_support/matchers/have_redirected_to.rb +1 -0
- data/lib/brut/spec_support/matchers/have_rendered.rb +1 -0
- data/lib/brut/spec_support/matchers/have_returned_rack_response.rb +44 -0
- data/lib/brut/spec_support.rb +1 -1
- data/lib/brut/version.rb +1 -1
- data/lib/brut.rb +5 -4
- data/lib/sequel/extensions/brut_migrations.rb +1 -1
- metadata +648 -13
- data/doc-src/architecture.md +0 -102
- data/doc-src/assets.md +0 -98
- data/doc-src/forms.md +0 -214
- data/doc-src/handlers.md +0 -83
- data/doc-src/javascript.md +0 -265
- data/doc-src/pages.md +0 -210
- data/doc-src/route-hooks.md +0 -59
- data/lib/brut/front_end/components/inputs/select.rb +0 -117
@@ -0,0 +1,187 @@
|
|
1
|
+
# Renders an HTML `<select>`.
|
2
|
+
class Brut::FrontEnd::Components::Inputs::SelectTagWithOptions < Brut::FrontEnd::Components::Input
|
3
|
+
# Creates the appropriate select input for the given {Brut::FrontEnd::Form} and input name.
|
4
|
+
# Generally, you want to use this method over the initializer.
|
5
|
+
#
|
6
|
+
# @param [Brut::FrontEnd::Form} form The form that is being rendered.
|
7
|
+
# This method will consult this class to understand the requirements
|
8
|
+
# on this select so its HTML is generated correctly.
|
9
|
+
# @param [String] input_name the name of the input, which should be a member of `form`
|
10
|
+
# @param [Array<Object>] options An array of objects represented what is being selected.
|
11
|
+
# These can be any object and are ideally whatever domain object or
|
12
|
+
# data type you want on the backend to represent this selection.
|
13
|
+
# @param [Symbol|String] value_attribute the name of an attribute to determine an option's value.
|
14
|
+
# This will be called on each element of `options` to get the value used for the `<option>`'s
|
15
|
+
# `value` attribute. The value returned by `value_attribute` should be unique amongst the
|
16
|
+
# `options` provided *and* be distinct from whatever `value` is used for `include_blank`.
|
17
|
+
# @param [Symbol|String] option_text_attribute the name of an attribute to determine the text for an option.
|
18
|
+
# This will be called on each element of `options` to get the value used for the `<option>`'s
|
19
|
+
# text content. The value returned by `option_text_attribute` need not be unique, though if it
|
20
|
+
# is not unique, it will certainly be confusing.
|
21
|
+
# @param [Integer] index if this input is part of an array, this is the index into that array.
|
22
|
+
# This is used to get the input's value.
|
23
|
+
# @param [Hash] html_attributes any additional HTML attributes to include on the `<select>` element.
|
24
|
+
# @param [false|true|Hash] include_blank configure how and if to include a blank element in the select.
|
25
|
+
# If this is false, there will be no blank element. If it's `true`, there will be one with
|
26
|
+
# no value or text. If this is a `Hash` it must contain a `value:` key and a `text_content:` key
|
27
|
+
# to be used as the `value` attribute and option text content, respectively.
|
28
|
+
#
|
29
|
+
# @return [Brut::FrontEnd::Components::Inputs::SelectTagWithOptions] the select input ready to be placed into a view.
|
30
|
+
def self.for_form_input(form:,
|
31
|
+
input_name:,
|
32
|
+
options:,
|
33
|
+
include_blank: false,
|
34
|
+
value_attribute:,
|
35
|
+
option_text_attribute:,
|
36
|
+
index: nil,
|
37
|
+
html_attributes: {})
|
38
|
+
html_attributes = html_attributes.map { |key,value| [ key.to_sym, value ] }.to_h
|
39
|
+
default_html_attributes = {}
|
40
|
+
index ||= 0
|
41
|
+
input = form.input(input_name, index:)
|
42
|
+
default_html_attributes[:required] = input.required
|
43
|
+
if !form.new? && !input.valid?
|
44
|
+
default_html_attributes["data-invalid"] = true
|
45
|
+
input.validity_state.each do |constraint,violated|
|
46
|
+
if violated
|
47
|
+
default_html_attributes["data-#{constraint}"] = true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
name = if input.array?
|
52
|
+
"#{input.name}[]"
|
53
|
+
else
|
54
|
+
input.name
|
55
|
+
end
|
56
|
+
|
57
|
+
Brut::FrontEnd::Components::Inputs::SelectTagWithOptions.new(
|
58
|
+
name: name,
|
59
|
+
options:,
|
60
|
+
selected_value: input.value,
|
61
|
+
value_attribute:,
|
62
|
+
option_text_attribute:,
|
63
|
+
include_blank:,
|
64
|
+
html_attributes: default_html_attributes.merge(html_attributes)
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Create the element.
|
69
|
+
#
|
70
|
+
# @param [String] name the name of the input
|
71
|
+
# @param [Array<Object>] options An array of objects represented what is being selected.
|
72
|
+
# These can be any object and are ideally whatever domain object or
|
73
|
+
# data type you want on the backend to represent this selection.
|
74
|
+
# @param [Symbol|String] value_attribute the name of an attribute to determine an option's value.
|
75
|
+
# This will be called on each element of `options` to get the value used for the `<option>`'s
|
76
|
+
# `value` attribute. The value returned by `value_attribute` should be unique amongst the
|
77
|
+
# `options` provided *and* be distinct from whatever `value` is used for `include_blank`.
|
78
|
+
# @param [String] selected_value the value of the selected option. Note that this is the *value*
|
79
|
+
# of the selected option, not the selected option itself. To set the selected value
|
80
|
+
# based on a selected option, omit this and use `selected_option`
|
81
|
+
# @param [String] selected_option the selected option. Note that `value_attribute` will be called
|
82
|
+
# on this to determine the selected value to use when generating HTML. Also note that
|
83
|
+
# this object must be in `options` or an exeception is raised.
|
84
|
+
# @param [Symbol|String] option_text_attribute the name of an attribute to determine the text for an option.
|
85
|
+
# This will be called on each element of `options` to get the value used for the `<option>`'s
|
86
|
+
# text content. The value returned by `option_text_attribute` need not be unique, though if it
|
87
|
+
# is not unique, it will certainly be confusing.
|
88
|
+
# @param [Hash] html_attributes any additional HTML attributes to include on the `<select>` element.
|
89
|
+
# @param [false|true|Hash] include_blank configure how and if to include a blank element in the select.
|
90
|
+
# If this is false, there will be no blank element. If it's `true`, there will be one with
|
91
|
+
# no value or text. If this is a `Hash` it must contain a `value:` key and a `text_content:` key
|
92
|
+
# to be used as the `value` attribute and option text content, respectively.
|
93
|
+
#
|
94
|
+
# @raise ArgumentError if `selected_option` is present, but not in `options` or if `selected_value` is
|
95
|
+
# present, but no option's value for `value_attribute` is that value.
|
96
|
+
#
|
97
|
+
# XXX: Why does this not ask the form for the selected_value?
|
98
|
+
# XXX: This doesn't do well when values are strings
|
99
|
+
def initialize(name:,
|
100
|
+
options:,
|
101
|
+
value_attribute:,
|
102
|
+
selected_value: nil,
|
103
|
+
selected_option: nil,
|
104
|
+
option_text_attribute:,
|
105
|
+
include_blank: false,
|
106
|
+
html_attributes:)
|
107
|
+
@options = options
|
108
|
+
@include_blank = IncludeBlank.from_param(include_blank)
|
109
|
+
@value_attribute = value_attribute
|
110
|
+
@option_text_attribute = option_text_attribute
|
111
|
+
@html_attributes = html_attributes
|
112
|
+
@html_attributes[:name] = name
|
113
|
+
|
114
|
+
if selected_value.nil?
|
115
|
+
if selected_option.nil?
|
116
|
+
@selected_value = nil # explicitly nothing is selected
|
117
|
+
else
|
118
|
+
option = options.detect { |option|
|
119
|
+
option.send(@value_attribute) == selected_option.send(@value_attribute)
|
120
|
+
}
|
121
|
+
if option.nil?
|
122
|
+
raise ArgumentError, "selected_option #{selected_option} (with #{value_attribute} '#{selected_option.send(value_attribute)}') was not found in options"
|
123
|
+
end
|
124
|
+
@selected_value = option.send(@value_attribute)
|
125
|
+
end
|
126
|
+
else
|
127
|
+
if selected_value.kind_of?(Array)
|
128
|
+
raise "WTF: #{name}"
|
129
|
+
end
|
130
|
+
option = options.detect { |option|
|
131
|
+
selected_value == option.send(@value_attribute)
|
132
|
+
}
|
133
|
+
if option.nil?
|
134
|
+
raise ArgumentError, "selected_value #{selected_value} was not the value for #{value_attribute} on any of the options: #{options.map { |option| option.send(value_attribute) }.join(', ')}"
|
135
|
+
end
|
136
|
+
@selected_value = option.send(@value_attribute)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def view_template
|
141
|
+
select(**@html_attributes) {
|
142
|
+
if @include_blank
|
143
|
+
option(**@include_blank.option_attributes) {
|
144
|
+
@include_blank.text_content
|
145
|
+
}
|
146
|
+
end
|
147
|
+
options = @options.each do |option|
|
148
|
+
value = option.send(@value_attribute)
|
149
|
+
option_attributes = { value: value }
|
150
|
+
if value == @selected_value
|
151
|
+
option_attributes[:selected] = true
|
152
|
+
end
|
153
|
+
option(**option_attributes) {
|
154
|
+
option.send(@option_text_attribute)
|
155
|
+
}
|
156
|
+
end
|
157
|
+
}
|
158
|
+
end
|
159
|
+
private
|
160
|
+
|
161
|
+
# @!visibility private
|
162
|
+
class IncludeBlank
|
163
|
+
attr_reader :text_content, :option_attributes
|
164
|
+
def self.from_param(include_blank)
|
165
|
+
if !include_blank
|
166
|
+
return nil
|
167
|
+
else
|
168
|
+
self.new(include_blank)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
def initialize(include_blank)
|
172
|
+
if include_blank == true
|
173
|
+
@text_content = ""
|
174
|
+
@option_attributes = {}
|
175
|
+
elsif include_blank.kind_of?(Hash)
|
176
|
+
if include_blank.key?(:value) && include_blank.key?(:text_content)
|
177
|
+
@text_content = include_blank[:text_content]
|
178
|
+
@option_attributes = { value: include_blank[:value] }
|
179
|
+
else
|
180
|
+
raise ArgumentError, "when include_blank: is a Hash, it must include both :value and :text_content as keys. Got: #{include_blank.keys.join(", ")}"
|
181
|
+
end
|
182
|
+
else
|
183
|
+
raise ArgumentError,"include_blank: was a #{include_blank.class}. It should be true, false, nil, or a Hash"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# Generates an HTML `<textarea>` field.
|
2
|
-
class Brut::FrontEnd::Components::Inputs::
|
2
|
+
class Brut::FrontEnd::Components::Inputs::TextareaTag < Brut::FrontEnd::Components::Input
|
3
3
|
# Creates the appropriate textarea for the given {Brut::FrontEnd::Form} and input name.
|
4
4
|
# Generally, you want to use this method over the initializer.
|
5
5
|
#
|
@@ -35,7 +35,7 @@ class Brut::FrontEnd::Components::Inputs::Textarea < Brut::FrontEnd::Components:
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
value = input.value
|
38
|
-
Brut::FrontEnd::Components::Inputs::
|
38
|
+
Brut::FrontEnd::Components::Inputs::TextareaTag.new(default_html_attributes.merge(html_attributes), value)
|
39
39
|
end
|
40
40
|
# Create an instance
|
41
41
|
#
|
@@ -1,4 +1,5 @@
|
|
1
|
-
# Renders a date or timestamp accessibly, using the `<time>` element.
|
1
|
+
# Renders a date or timestamp accessibly, using the `<time>` element.
|
2
|
+
# This will account for the current request's time zone. See {Clock}.
|
2
3
|
class Brut::FrontEnd::Components::TimeTag < Brut::FrontEnd::Component
|
3
4
|
include Brut::I18n::ForHTML
|
4
5
|
# Creates the component
|
data/lib/brut/front_end/form.rb
CHANGED
@@ -68,12 +68,12 @@ class Brut::FrontEnd::Form
|
|
68
68
|
@inputs = self.class.input_definitions.map { |name,input_definition|
|
69
69
|
value = @params[name] || @params[name.to_sym]
|
70
70
|
inputs = if value.kind_of?(Array)
|
71
|
-
value.map { |one_value|
|
72
|
-
input_definition.make_input(value: one_value)
|
71
|
+
value.map.with_index { |one_value, index|
|
72
|
+
input_definition.make_input(value: one_value, index:)
|
73
73
|
}
|
74
74
|
else
|
75
75
|
[
|
76
|
-
input_definition.make_input(value:)
|
76
|
+
input_definition.make_input(value:, index: nil)
|
77
77
|
]
|
78
78
|
end
|
79
79
|
|
@@ -98,7 +98,7 @@ class Brut::FrontEnd::Form
|
|
98
98
|
input = inputs[index]
|
99
99
|
if input.nil?
|
100
100
|
input_definition = self.class.input_definitions.fetch(input_name.to_s)
|
101
|
-
input = input_definition.make_input(value:"")
|
101
|
+
input = input_definition.make_input(value:"", index:)
|
102
102
|
inputs[index] = input
|
103
103
|
end
|
104
104
|
input
|
@@ -13,9 +13,10 @@ class Brut::FrontEnd::Forms::Input
|
|
13
13
|
# Create the input with the given definition and value
|
14
14
|
# @param [Brut::FrontEnd::Forms::InputDefinition] input_definition
|
15
15
|
# @param [String] value
|
16
|
-
def initialize(input_definition:, value:)
|
16
|
+
def initialize(input_definition:, value:, index:)
|
17
17
|
@input_definition = input_definition
|
18
18
|
@validity_state = Brut::FrontEnd::Forms::ValidityState.new
|
19
|
+
@index = index
|
19
20
|
self.value=(value)
|
20
21
|
end
|
21
22
|
|
@@ -109,7 +109,10 @@ class Brut::FrontEnd::Forms::InputDefinition
|
|
109
109
|
|
110
110
|
|
111
111
|
# Create an Input based on this definition, initializing it with the given value.
|
112
|
-
|
113
|
-
|
112
|
+
# @param [String] value the value to give this input initially.
|
113
|
+
# @param [Integer] index the index of this input, if it is part of an array of
|
114
|
+
# inputs. `nil` is allowed only if the input definition is not for an array.
|
115
|
+
def make_input(value:, index:)
|
116
|
+
Brut::FrontEnd::Forms::Input.new(input_definition: self, value:, index:)
|
114
117
|
end
|
115
118
|
end
|
@@ -8,9 +8,10 @@ class Brut::FrontEnd::Forms::RadioButtonGroupInput
|
|
8
8
|
attr_reader :validity_state
|
9
9
|
|
10
10
|
# (see Brut::FrontEnd::Forms::Input#initialize)
|
11
|
-
def initialize(input_definition:, value:)
|
11
|
+
def initialize(input_definition:, value:, index:)
|
12
12
|
@input_definition = input_definition
|
13
13
|
@validity_state = Brut::FrontEnd::Forms::ValidityState.new
|
14
|
+
@index = index
|
14
15
|
if input_definition.array?
|
15
16
|
value ||= []
|
16
17
|
end
|
@@ -23,7 +23,7 @@ class Brut::FrontEnd::Forms::RadioButtonGroupInputDefinition
|
|
23
23
|
def array? = @array
|
24
24
|
|
25
25
|
# Create an Input based on this defitition, initializing it with the given value.
|
26
|
-
def make_input(value:)
|
27
|
-
Brut::FrontEnd::Forms::RadioButtonGroupInput.new(input_definition: self, value:
|
26
|
+
def make_input(value:, index:)
|
27
|
+
Brut::FrontEnd::Forms::RadioButtonGroupInput.new(input_definition: self, value:, index:)
|
28
28
|
end
|
29
29
|
end
|
@@ -9,12 +9,10 @@ class Brut::FrontEnd::Forms::SelectInput
|
|
9
9
|
attr_reader :validity_state
|
10
10
|
|
11
11
|
# (see Brut::FrontEnd::Forms::Input#initialize)
|
12
|
-
def initialize(input_definition:, value:)
|
12
|
+
def initialize(input_definition:, value:, index:)
|
13
13
|
@input_definition = input_definition
|
14
14
|
@validity_state = Brut::FrontEnd::Forms::ValidityState.new
|
15
|
-
|
16
|
-
value ||= []
|
17
|
-
end
|
15
|
+
@index = index
|
18
16
|
self.value=(value)
|
19
17
|
end
|
20
18
|
|
@@ -21,7 +21,7 @@ class Brut::FrontEnd::Forms::SelectInputDefinition
|
|
21
21
|
def array? = @array
|
22
22
|
|
23
23
|
# Create an Input based on this defitition, initializing it with the given value.
|
24
|
-
def make_input(value:)
|
25
|
-
Brut::FrontEnd::Forms::SelectInput.new(input_definition: self, value:
|
24
|
+
def make_input(value:, index:)
|
25
|
+
Brut::FrontEnd::Forms::SelectInput.new(input_definition: self, value:, index:)
|
26
26
|
end
|
27
27
|
end
|
@@ -1,12 +1,24 @@
|
|
1
1
|
module Brut::FrontEnd
|
2
|
-
# A handler responds to all HTTP requests other than those that render a page.
|
3
|
-
#
|
4
|
-
#
|
2
|
+
# A handler responds to all HTTP requests other than those that render a page.
|
3
|
+
# Like a page, the handler is initialized with any of the data it needs. The {#handle} method will
|
4
|
+
# be called to perform whatever action is needed, and its return value will determine what
|
5
|
+
# ther esponse will be.
|
5
6
|
#
|
6
|
-
#
|
7
|
-
#
|
7
|
+
# To create a handler, after defining a route or form,
|
8
|
+
# subclass this class (or, more likely, your app's `AppHandler`) and
|
9
|
+
# provide an initializer that accepts keyword arguments. The names of these arguments will be used
|
10
|
+
# to locate the values that Brut will pass in when creating your page object. If your handler
|
11
|
+
# is for a form, be sure to include the `form:` keyword argument.
|
8
12
|
#
|
9
|
-
#
|
13
|
+
# Consult Brut's documentation on keyword injection to know what values you may use and how values are located.
|
14
|
+
#
|
15
|
+
# Then, implement {#handle} to perform whatever logic is needed to handle the request.
|
16
|
+
#
|
17
|
+
# You may also define `before_handle` which will be called before {#handle} to potentially abort
|
18
|
+
# the request. This is mostly useful if you have a base class for some of your handlers and want to
|
19
|
+
# share cross-cutting logic.
|
20
|
+
#
|
21
|
+
# Note that the public API for handlers is {#handle!}, which is what you should call in a test.
|
10
22
|
class Handler
|
11
23
|
include Brut::FrontEnd::HandlingResults
|
12
24
|
include Brut::Framework::Errors
|
@@ -27,32 +39,22 @@ module Brut::FrontEnd
|
|
27
39
|
abstract_method!
|
28
40
|
end
|
29
41
|
|
42
|
+
# Override this to performa any checks before {#handle} is called. This should
|
43
|
+
# return `nil` if {#handle} should proceed to be called. Generally, you don't need to override
|
44
|
+
# this as {#handle} can include the logic. Where this is useful is to share cross-cutting logic
|
45
|
+
# across other handlers.
|
46
|
+
#
|
47
|
+
# @return [URI|Brut::FrontEnd::Component,Array,Brut::FrontEnd::HttpStatus,Brut::FrontEnd::Download] See
|
48
|
+
# {#handle} for what each return value means.
|
49
|
+
def before_handle = nil
|
50
|
+
|
30
51
|
# Called by Brut to handle the request. Do not override this. If your handler responds to `before_handle` that is called with the
|
31
52
|
# same args as you have defined for {#handle}. If `before_handle` returns anything other than `nil`, that value is returned and
|
32
53
|
# should be one of the values documented in {#handle}. If `before_handle` returns `nil`, {#handle} is called and whatever it
|
33
54
|
# returns is returned here.
|
34
55
|
def handle!(**args)
|
35
56
|
result = nil
|
36
|
-
|
37
|
-
before_handle_args = self.method(:before_handle).parameters.map { |(type,name)|
|
38
|
-
if type == :keyreq
|
39
|
-
if args.key?(name)
|
40
|
-
[ name, args[name] ]
|
41
|
-
else
|
42
|
-
raise ArgumentError,"before_handle requires keyword arg '#{name}' but `handle` did not receive it. It must"
|
43
|
-
end
|
44
|
-
elsif type == :key
|
45
|
-
if args.key?(name)
|
46
|
-
[ name, args[name] ]
|
47
|
-
else
|
48
|
-
nil
|
49
|
-
end
|
50
|
-
else
|
51
|
-
raise ArgumentError,"before_handle must only have keyword args. Got '#{name}' of type '#{type}'"
|
52
|
-
end
|
53
|
-
}.compact.to_h
|
54
|
-
result = self.before_handle(**before_handle_args)
|
55
|
-
end
|
57
|
+
result = self.before_handle
|
56
58
|
if result.nil?
|
57
59
|
result = self.handle(**args)
|
58
60
|
end
|
@@ -1,9 +1,12 @@
|
|
1
1
|
# Receives content security policy violations and logs them. This is set up in {Brut::Framework::MCP}, however CSP reporting is
|
2
2
|
# configured in {Brut::FrontEnd::RouteHooks::CSPNoInlineStylesOrScripts::ReportOnly}.
|
3
3
|
class Brut::FrontEnd::Handlers::CspReportingHandler < Brut::FrontEnd::Handler
|
4
|
-
def
|
4
|
+
def initialize(body:)
|
5
|
+
@body = body
|
6
|
+
end
|
7
|
+
def handle
|
5
8
|
begin
|
6
|
-
parsed = JSON.parse(body.read)
|
9
|
+
parsed = JSON.parse(@body.read)
|
7
10
|
Brut.container.instrumentation.add_attributes(parsed.merge(prefix: "brut.csp-reporting"))
|
8
11
|
rescue => ex
|
9
12
|
Brut.container.instrumentation.record_exception(ex)
|
@@ -65,12 +65,16 @@ class Brut::FrontEnd::Handlers::InstrumentationHandler < Brut::FrontEnd::Handler
|
|
65
65
|
def as_carrier = { "traceparent" => @value }
|
66
66
|
end
|
67
67
|
|
68
|
-
def
|
69
|
-
|
70
|
-
|
68
|
+
def initialize(http_tracestate:, http_traceparent:)
|
69
|
+
@http_tracestate = http_tracestate
|
70
|
+
@http_traceparent = http_traceparent
|
71
|
+
end
|
72
|
+
def handle
|
73
|
+
traceparent = TraceParent.from_header(@http_traceparent)
|
74
|
+
span = Span.from_header(@http_tracestate)
|
71
75
|
|
72
76
|
if span.nil? || traceparent.nil?
|
73
|
-
SemanticLogger[self.class].info "Missing traceparent or span: #{http_tracestate}, #{http_traceparent}"
|
77
|
+
SemanticLogger[self.class].info "Missing traceparent or span: #{@http_tracestate}, #{@http_traceparent}"
|
74
78
|
return http_status(400)
|
75
79
|
end
|
76
80
|
|
@@ -5,9 +5,13 @@
|
|
5
5
|
# {Brut::FrontEnd::Session#http_accept_language=} *only* if the `Accept-Language` header did not provide a value that is supported by
|
6
6
|
# the app.
|
7
7
|
class Brut::FrontEnd::Handlers::LocaleDetectionHandler < Brut::FrontEnd::Handler
|
8
|
-
def
|
8
|
+
def initialize(body:,session:)
|
9
|
+
@body = body
|
10
|
+
@session = session
|
11
|
+
end
|
12
|
+
def handle
|
9
13
|
begin
|
10
|
-
parsed = JSON.parse(body.read)
|
14
|
+
parsed = JSON.parse(@body.read)
|
11
15
|
|
12
16
|
Brut.container.instrumentation.add_attributes(
|
13
17
|
prefix: "brut.locale-detection",
|
@@ -19,9 +23,9 @@ class Brut::FrontEnd::Handlers::LocaleDetectionHandler < Brut::FrontEnd::Handler
|
|
19
23
|
locale = parsed["locale"]
|
20
24
|
timezone = parsed["timeZone"]
|
21
25
|
|
22
|
-
session.timezone_from_browser = timezone
|
23
|
-
if
|
24
|
-
session.http_accept_language = Brut::I18n::HTTPAcceptLanguage.from_browser(locale)
|
26
|
+
@session.timezone_from_browser = timezone
|
27
|
+
if !@session.http_accept_language.known?
|
28
|
+
@session.http_accept_language = Brut::I18n::HTTPAcceptLanguage.from_browser(locale)
|
25
29
|
end
|
26
30
|
end
|
27
31
|
rescue => ex
|
@@ -1,7 +1,10 @@
|
|
1
1
|
# Used in development to handle a defined route but a missing page. This arranges to render a nicer error page than the default.
|
2
2
|
class Brut::FrontEnd::Handlers::MissingHandler < Brut::FrontEnd::Handler
|
3
|
-
def
|
4
|
-
|
3
|
+
def initialize(route:)
|
4
|
+
@route = route
|
5
|
+
end
|
6
|
+
def handle
|
7
|
+
Brut::FrontEnd::Pages::MissingPage.new(route: @route)
|
5
8
|
end
|
6
9
|
|
7
10
|
class Form < Brut::FrontEnd::Form
|
@@ -1,3 +1,19 @@
|
|
1
|
+
# A layout is common HTML that surrounds different pages. For example, it would hold your
|
2
|
+
# DOCTYPE, `<head>`, and possibly any common `<body>` elements that every page needs.
|
3
|
+
#
|
4
|
+
# A layout is a Phlex component but it must contain a call to `yield` somewhere in the
|
5
|
+
# implementation of `view_template`.
|
6
|
+
#
|
7
|
+
# This base class contains helper methods needed for implementing a layout.
|
1
8
|
class Brut::FrontEnd::Layout < Brut::FrontEnd::Component
|
9
|
+
# Get the actual path of an asset managed by Brut. This handles
|
10
|
+
# locating the asset's URL as well as ensuring the hash is properly
|
11
|
+
# inserted into the filename.
|
12
|
+
#
|
13
|
+
# @param [String] path the path to an asset, such as `/css/styles.css`.
|
14
|
+
#
|
15
|
+
# @return [String] the actual path to the current version of that asset.
|
16
|
+
#
|
17
|
+
# @see Brut::FrontEnd::AssetPathResolver
|
2
18
|
def asset_path(path) = Brut.container.asset_path_resolver.resolve(path)
|
3
19
|
end
|
data/lib/brut/front_end/page.rb
CHANGED
@@ -1,48 +1,46 @@
|
|
1
|
-
# A
|
2
|
-
#
|
1
|
+
# A Page backs a web page, which handles rendering everything in a browser window when a URL is requested.
|
2
|
+
# Technically, a page is identical to a {Brut::FrontEnd::Component}, except that a page has a layout.
|
3
|
+
# A {Brut::FrontEnd::Layout} is common HTML that surrounds your page's HTML.
|
4
|
+
# Your page is a Phlex component, but instead of implementing `view_template`, you
|
5
|
+
# implement {#page_template} to ensure the layout is used.
|
3
6
|
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
+
# To create a page, after defining a route, subclass this class (or, more likely, your app's `AppPage`) and
|
8
|
+
# provide an initializer that accepts keyword arguments. The names of these arguments will be used to locate the
|
9
|
+
# values that Brut will pass in when creating your page object.
|
7
10
|
#
|
8
|
-
#
|
9
|
-
# app. For example, if you have a page named `Auth::LoginPage`, it would expected to be in
|
10
|
-
# `app/src/front_end/pages/auth/login_page.rb`. Thus, Brut will also expect
|
11
|
-
# `app/src/front_end/pages/auth/login_page.html.erb` to exist as well. That ERB file is used with an instance of your
|
12
|
-
# pages's class to render the page's HTML.
|
11
|
+
# Consult Brut's documentation on keyword injection to know what values you may use and how values are located.
|
13
12
|
#
|
14
13
|
# @see Brut::FrontEnd::Component
|
14
|
+
# @see Brut::FrontEnd::Layout
|
15
15
|
class Brut::FrontEnd::Page < Brut::FrontEnd::Component
|
16
16
|
include Brut::FrontEnd::HandlingResults
|
17
17
|
|
18
|
-
# Returns the name of the layout for this page. This string is used to find
|
19
|
-
#
|
18
|
+
# Returns the name of the layout for this page. This string is used to find a class named
|
19
|
+
# `«camelized-layout»Layout` in your app. The default value is "default", meaning that the class
|
20
|
+
# `DefaultLayout` will be used.
|
20
21
|
#
|
21
|
-
# Note that the layout can be dynamic. It is requested when {#
|
22
|
+
# Note that the layout can be dynamic. It is requested when {#page_template} is called, so you can override this
|
22
23
|
# method and use any ivar set in your constructor to change what layout is used.
|
23
24
|
#
|
25
|
+
# If your page does not need a layout, you have two options:
|
26
|
+
#
|
27
|
+
# * Create your own blank layout named, e.g. `BlankLayout` and have this method return `"blank"`.
|
28
|
+
# * Implement `view_template` instead of `page_template`, thus overriding this class' implementation that uses
|
29
|
+
# layouts.
|
30
|
+
#
|
24
31
|
# @return [String] The name of the layout. May not be `nil`.
|
25
32
|
def layout = "default"
|
26
33
|
|
27
|
-
# Called after the page is created, but before {#
|
34
|
+
# Called after the page is created, but before {#page_template} is called. This allows you to do any pre-flight checks and potentially
|
28
35
|
# redirect the user or produce an error.
|
29
36
|
#
|
30
|
-
# @return [URI|Brut::FrontEnd::HttpStatus|Object] If you return a `URI` (mostly likely by returning the result of calling {Brut::FrontEnd::HandlingResults#redirect_to}), the user is redirected and
|
31
|
-
def
|
32
|
-
|
33
|
-
def with_layout(&block)
|
34
|
-
layout_class = Module.const_get(
|
35
|
-
layout_class = RichString.new([
|
36
|
-
self.layout,
|
37
|
-
"layout"
|
38
|
-
].join("_")).camelize
|
39
|
-
)
|
40
|
-
render layout_class.new(page_name:,&block)
|
41
|
-
end
|
42
|
-
|
37
|
+
# @return [URI|Brut::FrontEnd::HttpStatus|Object] If you return a `URI` (mostly likely by returning the result of calling {Brut::FrontEnd::HandlingResults#redirect_to}), the user is redirected and no HTML is generated. If you return a {Brut::FrontEnd::HttpStatus} (mostly likely by returning the result of calling {Brut::FrontEnd::HandlingResults#http_status}), HTML generation is skipped and that status is returned with no content. If anything else is returned, HTML is generated normal.
|
38
|
+
def before_generate = nil
|
43
39
|
|
40
|
+
# Core method of this class. Do not override. This handles the use of {#before_generate} and is what Brut
|
41
|
+
# calls to possibly render the page.
|
44
42
|
def handle!
|
45
|
-
case
|
43
|
+
case before_generate
|
46
44
|
in URI => uri
|
47
45
|
uri
|
48
46
|
in Brut::FrontEnd::HttpStatus => http_status
|
@@ -52,6 +50,15 @@ class Brut::FrontEnd::Page < Brut::FrontEnd::Component
|
|
52
50
|
end
|
53
51
|
end
|
54
52
|
|
53
|
+
# Override this method to produce your page's HTML. You are intended to call Phlex
|
54
|
+
# methods here. Anything you can do inside the Phlex-standard `view_template` method, you can
|
55
|
+
# do here. The only difference is that this will all be rendered in the context of your configured
|
56
|
+
# {#layout}.
|
57
|
+
def page_template = abstract_method!
|
58
|
+
|
59
|
+
# Phlex's API to produce markup. Do not override this or you will lose your layout.
|
60
|
+
# This implementation locates the configured layout, renders it, and renders {#page_template}
|
61
|
+
# inside.
|
55
62
|
def view_template
|
56
63
|
with_layout do
|
57
64
|
page_template
|
@@ -65,8 +72,24 @@ class Brut::FrontEnd::Page < Brut::FrontEnd::Component
|
|
65
72
|
# Convienience method for {.page_name}.
|
66
73
|
def page_name = self.class.page_name
|
67
74
|
|
75
|
+
private
|
76
|
+
|
77
|
+
# Locates the layout class and uses it to render itself, along
|
78
|
+
# with the block given.
|
79
|
+
#
|
68
80
|
# @!visibility private
|
69
|
-
def
|
81
|
+
def with_layout(&block)
|
82
|
+
layout_class = Module.const_get(
|
83
|
+
layout_class = RichString.new([
|
84
|
+
self.layout,
|
85
|
+
"layout"
|
86
|
+
].join("_")).camelize
|
87
|
+
)
|
88
|
+
Brut.container.instrumentation.add_prefixed_attributes("brut", layout_class: layout_class)
|
89
|
+
render layout_class.new(page_name:,&block)
|
90
|
+
end
|
91
|
+
|
92
|
+
|
70
93
|
|
71
94
|
end
|
72
95
|
|
@@ -112,12 +112,13 @@ class Brut::FrontEnd::RequestContext
|
|
112
112
|
# @param [Class] klass a class that is to be instantiated entirely by the contents of this `RequestContext`.
|
113
113
|
# @param [Hash] request_params Query string parameters provided by Rack.
|
114
114
|
# @param [Brut::FrontEnd::Routing::Route] route the route that triggered the request.
|
115
|
+
# @param [Brut::FrontEnd::Form] form the form, if available
|
115
116
|
# @return [Hash] can be splatted to keyword arguments and passed to the constructor of `klass`
|
116
117
|
#
|
117
118
|
# @raise [ArgumentError] if the constructor has any non-keyword arguments, or if any required keyword argument is
|
118
119
|
# not present in this `RequestContext`.
|
119
|
-
def as_constructor_args(klass, request_params:, route:nil)
|
120
|
-
args_for_method(method: klass.instance_method(:initialize), request_params:, form:
|
120
|
+
def as_constructor_args(klass, request_params:, route:nil, form: nil)
|
121
|
+
args_for_method(method: klass.instance_method(:initialize), request_params:, form: , route:)
|
121
122
|
end
|
122
123
|
|
123
124
|
# Based on `object`' method, returns a Hash that maps all keywords it requires to the values stored in this
|
@@ -218,7 +218,11 @@ private
|
|
218
218
|
joined_path = joined_path + "#" + URI.encode_uri_component(anchor)
|
219
219
|
end
|
220
220
|
uri = URI(joined_path)
|
221
|
-
|
221
|
+
query_string = URI.encode_www_form(query_string_params)
|
222
|
+
if query_string.to_s.strip != ""
|
223
|
+
uri.query = query_string
|
224
|
+
end
|
225
|
+
|
222
226
|
uri.extend(Phlex::SGML::SafeObject)
|
223
227
|
end
|
224
228
|
|