brut 0.4.0 → 0.8.0
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 +4 -0
- data/CHANGELOG.md +36 -1
- data/Dockerfile.dx +19 -0
- data/Gemfile.lock +1 -1
- data/README.md +19 -0
- data/assets/MetroIcon.graffle +0 -0
- data/assets/YouTubeThumb.pxd +0 -0
- data/bin/build +86 -0
- data/bin/ci +36 -0
- data/bin/docs +39 -9
- data/bin/publish +61 -0
- data/bin/setup +6 -0
- data/brut-css/bin/build +19 -0
- data/brut-css/bin/ci +19 -0
- data/brut-css/bin/docs +19 -0
- data/brut-css/bin/publish +21 -0
- data/brut-css/bin/setup +1 -0
- data/brut-css/package-lock.json +2 -2
- data/brut-css/package.json +1 -1
- data/brut-css/src/css/flex.css +1 -1
- data/brut-css/src/docs/includes/body-and-header.html.ejs +1 -1
- data/brut-js/bin/build +15 -6
- data/brut-js/bin/docs +25 -0
- data/brut-js/bin/publish +21 -0
- data/brut-js/bin/setup +1 -0
- data/brut-js/dx +1 -0
- data/brut-js/package-lock.json +2 -2
- data/brut-js/package.json +1 -1
- data/brut-js/specs/AjaxSubmit.spec.js +100 -7
- data/brut-js/src/AjaxSubmit.js +76 -40
- data/brut.gemspec +2 -2
- data/brutrb.com/adrs.md +1 -0
- data/brutrb.com/bin/setup +1 -0
- data/brutrb.com/getting-started.md +3 -0
- data/brutrb.com/overview.md +6 -0
- data/brutrb.com/tutorial.md +7 -3
- data/docs/404.html +2 -2
- data/docs/adrs.html +5 -5
- data/docs/ai.html +3 -3
- data/docs/api/Brut/BackEnd/SeedData.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
- data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
- data/docs/api/Brut/BackEnd/Validators.html +1 -1
- data/docs/api/Brut/BackEnd.html +1 -1
- data/docs/api/Brut/CLI/App.html +1 -1
- data/docs/api/Brut/CLI/AppRunner.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps.html +1 -1
- data/docs/api/Brut/CLI/Command.html +1 -1
- data/docs/api/Brut/CLI/Error.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
- data/docs/api/Brut/CLI/Executor.html +1 -1
- data/docs/api/Brut/CLI/InvalidOption.html +1 -1
- data/docs/api/Brut/CLI/Options.html +1 -1
- data/docs/api/Brut/CLI/Output.html +1 -1
- data/docs/api/Brut/CLI/SystemExecError.html +1 -1
- data/docs/api/Brut/CLI.html +1 -1
- data/docs/api/Brut/FactoryBot.html +1 -1
- data/docs/api/Brut/Framework/App.html +1 -1
- data/docs/api/Brut/Framework/Config.html +1 -1
- data/docs/api/Brut/Framework/Container.html +1 -1
- data/docs/api/Brut/Framework/Error.html +1 -1
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
- data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
- data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
- data/docs/api/Brut/Framework/Errors.html +1 -1
- data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
- data/docs/api/Brut/Framework/MCP.html +1 -1
- data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
- data/docs/api/Brut/Framework.html +1 -1
- data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
- data/docs/api/Brut/FrontEnd/Component/Helpers.html +86 -2
- data/docs/api/Brut/FrontEnd/Component.html +78 -9
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Input.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +3 -3
- data/docs/api/Brut/FrontEnd/Components.html +1 -1
- data/docs/api/Brut/FrontEnd/Download.html +1 -1
- data/docs/api/Brut/FrontEnd/Flash.html +1 -1
- data/docs/api/Brut/FrontEnd/Form.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms.html +1 -1
- data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
- data/docs/api/Brut/FrontEnd/Handler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
- data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
- data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
- data/docs/api/Brut/FrontEnd/Layout.html +3 -3
- data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
- data/docs/api/Brut/FrontEnd/Page.html +31 -23
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +3 -3
- data/docs/api/Brut/FrontEnd/Pages.html +1 -1
- data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing.html +1 -1
- data/docs/api/Brut/FrontEnd/Session.html +1 -1
- data/docs/api/Brut/FrontEnd.html +1 -1
- data/docs/api/Brut/I18n/BaseMethods.html +1 -1
- data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
- data/docs/api/Brut/I18n/ForCLI.html +1 -1
- data/docs/api/Brut/I18n/ForHTML.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
- data/docs/api/Brut/I18n.html +1 -1
- data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
- data/docs/api/Brut/Instrumentation.html +1 -1
- data/docs/api/Brut/SinatraHelpers/ClassMethods.html +13 -11
- data/docs/api/Brut/SinatraHelpers.html +1 -1
- data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
- data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
- data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
- data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
- data/docs/api/Brut/SpecSupport.html +1 -1
- data/docs/api/Brut.html +1 -1
- data/docs/api/Clock.html +1 -1
- data/docs/api/ModuleName.html +1 -1
- data/docs/api/RichString.html +1 -1
- data/docs/api/SemanticLogger/Appender/Async.html +1 -1
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
- data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
- data/docs/api/Sequel/Extensions.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang.html +1 -1
- data/docs/api/Sequel/Plugins.html +1 -1
- data/docs/api/Sequel.html +1 -1
- data/docs/api/_index.html +1 -1
- data/docs/api/file.README.html +1 -1
- data/docs/api/index.html +1 -1
- data/docs/api/method_list.html +92 -76
- data/docs/api/top-level-namespace.html +1 -1
- data/docs/assets/{adrs.md.JRxZ5uYE.js → adrs.md.BxjHi9-8.js} +1 -1
- data/docs/assets/adrs.md.BxjHi9-8.lean.js +1 -0
- data/docs/assets/{app.AkS4fHzI.js → app.DyQLb4Ot.js} +1 -1
- data/docs/assets/chunks/@localSearchIndexroot.CmtZyrFA.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.By6un1FD.js → VPLocalSearchBox.T1iA-eJx.js} +1 -1
- data/docs/assets/chunks/{theme.DwiUPdci.js → theme.ChwsbWjK.js} +2 -2
- data/docs/assets/{components.md.BfeWD9sX.js → components.md.DHh-NwKs.js} +3 -3
- data/docs/assets/{configuration.md.DoSBNc0H.js → configuration.md.D8Wz3oJU.js} +1 -1
- data/docs/assets/{forms.md.DFveP5g_.js → forms.md.BRE85eju.js} +1 -1
- data/docs/assets/{getting-started.md.DZWjCVC0.js → getting-started.md.2ioiTe-B.js} +6 -3
- data/docs/assets/{getting-started.md.DZWjCVC0.lean.js → getting-started.md.2ioiTe-B.lean.js} +1 -1
- data/docs/assets/overview.md.DlKiRRG_.js +1 -0
- data/docs/assets/overview.md.DlKiRRG_.lean.js +1 -0
- data/docs/assets/tutorial.md.BIb7XT6j.js +1 -0
- data/docs/assets/tutorial.md.BIb7XT6j.lean.js +1 -0
- data/docs/assets.html +3 -3
- data/docs/brut-css/brut.max.css +1 -1
- data/docs/brut-css/classes/appearances.html +1 -1
- data/docs/brut-css/classes/background-colors.html +1 -1
- data/docs/brut-css/classes/border-colors.html +1 -1
- data/docs/brut-css/classes/borders.html +1 -1
- data/docs/brut-css/classes/dimensions.html +1 -1
- data/docs/brut-css/classes/flex.html +3 -3
- data/docs/brut-css/classes/foreground-colors.html +1 -1
- data/docs/brut-css/classes/junk-drawer.html +1 -1
- data/docs/brut-css/classes/layout.html +1 -1
- data/docs/brut-css/classes/lists.html +1 -1
- data/docs/brut-css/classes/positioning.html +1 -1
- data/docs/brut-css/classes/spacings.html +1 -1
- data/docs/brut-css/classes/typography.html +1 -1
- data/docs/brut-css/customization/advanced-configuration.html +1 -1
- data/docs/brut-css/customization/breakpoints.html +1 -1
- data/docs/brut-css/customization/design-system.html +1 -1
- data/docs/brut-css/customization/pseudo-classes.html +1 -1
- data/docs/brut-css/getting-started/core-concepts.html +1 -1
- data/docs/brut-css/getting-started/installation.html +1 -1
- data/docs/brut-css/getting-started/overview.html +1 -1
- data/docs/brut-css/getting-started/simple-example.html +1 -1
- data/docs/brut-css/index.html +1 -1
- data/docs/brut-css/properties/colors.html +1 -1
- data/docs/brut-css/properties/spacings.html +1 -1
- data/docs/brut-css/properties/typography.html +1 -1
- data/docs/brut-js/api/AjaxSubmit.html +56 -7
- data/docs/brut-js/api/AjaxSubmit.js.html +77 -41
- data/docs/brut-js/api/Autosubmit.html +1 -1
- data/docs/brut-js/api/Autosubmit.js.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
- data/docs/brut-js/api/BrutCustomElements.html +1 -1
- data/docs/brut-js/api/BufferedLogger.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
- data/docs/brut-js/api/Form.html +1 -1
- data/docs/brut-js/api/Form.js.html +1 -1
- data/docs/brut-js/api/I18nTranslation.html +1 -1
- data/docs/brut-js/api/I18nTranslation.js.html +1 -1
- data/docs/brut-js/api/LocaleDetection.html +1 -1
- data/docs/brut-js/api/LocaleDetection.js.html +1 -1
- data/docs/brut-js/api/Logger.html +1 -1
- data/docs/brut-js/api/Logger.js.html +1 -1
- data/docs/brut-js/api/Message.html +1 -1
- data/docs/brut-js/api/Message.js.html +1 -1
- data/docs/brut-js/api/PrefixedLogger.html +1 -1
- data/docs/brut-js/api/RichString.html +1 -1
- data/docs/brut-js/api/RichString.js.html +1 -1
- data/docs/brut-js/api/Tabs.html +1 -1
- data/docs/brut-js/api/Tabs.js.html +1 -1
- data/docs/brut-js/api/Tracing.html +1 -1
- data/docs/brut-js/api/Tracing.js.html +1 -1
- data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
- data/docs/brut-js/api/external-Performance.html +1 -1
- data/docs/brut-js/api/external-Promise.html +1 -1
- data/docs/brut-js/api/external-ValidityState.html +1 -1
- data/docs/brut-js/api/external-Window.html +1 -1
- data/docs/brut-js/api/external-fetch.html +1 -1
- data/docs/brut-js/api/global.html +1 -1
- data/docs/brut-js/api/index.html +1 -1
- data/docs/brut-js/api/index.js.html +1 -1
- data/docs/brut-js/api/module-testing.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
- data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
- data/docs/brut-js/api/testing.DOMCreator.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
- data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
- data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
- data/docs/brut-js/api/testing_index.js.html +1 -1
- data/docs/brut-js.html +3 -3
- data/docs/business-logic.html +3 -3
- data/docs/cli.html +3 -3
- data/docs/components.html +7 -7
- data/docs/configuration.html +5 -5
- data/docs/css.html +3 -3
- data/docs/custom-element-tests.html +3 -3
- data/docs/database-access.html +3 -3
- data/docs/database-schema.html +3 -3
- data/docs/deployment.html +3 -3
- data/docs/dev-environment.html +3 -3
- data/docs/dir-structure.html +3 -3
- data/docs/doc-conventions.html +3 -3
- data/docs/end-to-end-tests.html +3 -3
- data/docs/features.html +3 -3
- data/docs/flash-and-session.html +3 -3
- data/docs/form-constraints.html +3 -3
- data/docs/forms.html +5 -5
- data/docs/getting-started.html +9 -6
- data/docs/handlers.html +3 -3
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +3 -3
- data/docs/i18n.html +3 -3
- data/docs/index.html +3 -3
- data/docs/instrumentation.html +3 -3
- data/docs/javascript.html +3 -3
- data/docs/jobs.html +3 -3
- data/docs/keyword-injection.html +3 -3
- data/docs/layouts.html +3 -3
- data/docs/lsp.html +3 -3
- data/docs/markdown-examples.html +3 -3
- data/docs/middleware.html +3 -3
- data/docs/overview.html +5 -5
- data/docs/pages.html +3 -3
- data/docs/recipes/alternate-layouts.html +3 -3
- data/docs/recipes/authentication.html +3 -3
- data/docs/recipes/blank-layouts.html +3 -3
- data/docs/recipes/custom-flash.html +3 -3
- data/docs/recipes/indexed-forms.html +3 -3
- data/docs/recipes/migrations.html +3 -3
- data/docs/recipes/text-field-component.html +3 -3
- data/docs/roadmap.html +3 -3
- data/docs/routes.html +3 -3
- data/docs/security.html +3 -3
- data/docs/seed-data.html +3 -3
- data/docs/space-time-continuum.html +3 -3
- data/docs/tutorial.html +5 -5
- data/docs/unit-tests.html +3 -3
- data/docs/why.html +3 -3
- data/lib/brut/cli/apps/scaffold.rb +1 -1
- data/lib/brut/framework/mcp.rb +1 -1
- data/lib/brut/front_end/component.rb +19 -0
- data/lib/brut/front_end/components/form_tag.rb +2 -2
- data/lib/brut/front_end/page.rb +11 -7
- data/lib/brut/sinatra_helpers.rb +1 -0
- data/lib/brut/version.rb +1 -1
- data/mkbrut/.gitignore +16 -0
- data/mkbrut/CODE_OF_CONDUCT.txt +100 -0
- data/mkbrut/Gemfile +3 -0
- data/mkbrut/Gemfile.lock +19 -0
- data/mkbrut/LICENSE.txt +370 -0
- data/mkbrut/README.md +145 -0
- data/mkbrut/Rakefile +2 -0
- data/mkbrut/bin/build +36 -0
- data/mkbrut/bin/ci +19 -0
- data/mkbrut/bin/docs +19 -0
- data/mkbrut/bin/publish +129 -0
- data/mkbrut/bin/rake +16 -0
- data/mkbrut/bin/setup +30 -0
- data/mkbrut/brut-welcome.png +0 -0
- data/mkbrut/deploy/.dockerignore +2 -0
- data/mkbrut/deploy/Dockerfile +25 -0
- data/mkbrut/exe/mkbrut +5 -0
- data/mkbrut/lib/mkbrut/app.rb +79 -0
- data/mkbrut/lib/mkbrut/app_id.rb +8 -0
- data/mkbrut/lib/mkbrut/app_name.rb +29 -0
- data/mkbrut/lib/mkbrut/app_options.rb +36 -0
- data/mkbrut/lib/mkbrut/base.rb +57 -0
- data/mkbrut/lib/mkbrut/cli.rb +107 -0
- data/mkbrut/lib/mkbrut/erb_binding_delegate.rb +20 -0
- data/mkbrut/lib/mkbrut/internet_identifier.rb +32 -0
- data/mkbrut/lib/mkbrut/invalid_identifier.rb +4 -0
- data/mkbrut/lib/mkbrut/ops/add_css_import.rb +42 -0
- data/mkbrut/lib/mkbrut/ops/add_i18n_message.rb +74 -0
- data/mkbrut/lib/mkbrut/ops/add_method.rb +48 -0
- data/mkbrut/lib/mkbrut/ops/append_to_file.rb +20 -0
- data/mkbrut/lib/mkbrut/ops/base_op.rb +21 -0
- data/mkbrut/lib/mkbrut/ops/copy_file.rb +12 -0
- data/mkbrut/lib/mkbrut/ops/insert_code_in_method.rb +58 -0
- data/mkbrut/lib/mkbrut/ops/insert_route.rb +52 -0
- data/mkbrut/lib/mkbrut/ops/mkdir.rb +13 -0
- data/mkbrut/lib/mkbrut/ops/prism_parsing_op.rb +70 -0
- data/mkbrut/lib/mkbrut/ops/render_template.rb +26 -0
- data/mkbrut/lib/mkbrut/ops/skip_file.rb +10 -0
- data/mkbrut/lib/mkbrut/ops.rb +16 -0
- data/mkbrut/lib/mkbrut/organization.rb +5 -0
- data/mkbrut/lib/mkbrut/prefix.rb +26 -0
- data/mkbrut/lib/mkbrut/prefixed_io.rb +16 -0
- data/mkbrut/lib/mkbrut/segments/bare_bones.rb +185 -0
- data/mkbrut/lib/mkbrut/segments/demo.rb +121 -0
- data/mkbrut/lib/mkbrut/segments/heroku.rb +30 -0
- data/mkbrut/lib/mkbrut/segments/sidekiq.rb +3 -0
- data/mkbrut/lib/mkbrut/segments.rb +8 -0
- data/mkbrut/lib/mkbrut/version.rb +3 -0
- data/mkbrut/lib/mkbrut/versions.rb +13 -0
- data/mkbrut/lib/mkbrut.rb +18 -0
- data/mkbrut/mkbrut.gemspec +32 -0
- data/mkbrut/templates/Base/.dockerignore +25 -0
- data/mkbrut/templates/Base/.env.development.erb +60 -0
- data/mkbrut/templates/Base/.env.test.erb +8 -0
- data/mkbrut/templates/Base/.gitignore +31 -0
- data/mkbrut/templates/Base/.projections.json +59 -0
- data/mkbrut/templates/Base/Dockerfile.dx +205 -0
- data/mkbrut/templates/Base/Gemfile.erb +53 -0
- data/mkbrut/templates/Base/Procfile.development +5 -0
- data/mkbrut/templates/Base/Procfile.test +1 -0
- data/mkbrut/templates/Base/README.md +4 -0
- data/mkbrut/templates/Base/README.md.erb +40 -0
- data/mkbrut/templates/Base/app/bootstrap.rb +61 -0
- data/mkbrut/templates/Base/app/config/i18n/en/1_defaults.rb +128 -0
- data/mkbrut/templates/Base/app/config/i18n/en/2_app.rb +24 -0
- data/mkbrut/templates/Base/app/public/static/manifest.json.erb +33 -0
- data/mkbrut/templates/Base/app/src/app.rb.erb +37 -0
- data/mkbrut/templates/Base/app/src/back_end/data_models/app_data_model.rb +5 -0
- data/mkbrut/templates/Base/app/src/back_end/data_models/db.rb +19 -0
- data/mkbrut/templates/Base/app/src/back_end/data_models/migrations/20240101130000_citext.rb +6 -0
- data/mkbrut/templates/Base/app/src/back_end/data_models/seed/seed_data.rb +9 -0
- data/mkbrut/templates/Base/app/src/front_end/components/app_component.rb +8 -0
- data/mkbrut/templates/Base/app/src/front_end/components/custom_element_registration.rb.erb +7 -0
- data/mkbrut/templates/Base/app/src/front_end/css/index.css +2 -0
- data/mkbrut/templates/Base/app/src/front_end/css/svgs.css +12 -0
- data/mkbrut/templates/Base/app/src/front_end/forms/app_form.rb +4 -0
- data/mkbrut/templates/Base/app/src/front_end/handlers/app_handler.rb +4 -0
- data/mkbrut/templates/Base/app/src/front_end/images/LogoPylon.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/LogoTransit.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-120x120.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-152x152.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-167x167.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-180x180.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/favicon.ico +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/icon.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/mkicons.sh +6 -0
- data/mkbrut/templates/Base/app/src/front_end/js/index.js +6 -0
- data/mkbrut/templates/Base/app/src/front_end/layouts/default_layout.rb.erb +73 -0
- data/mkbrut/templates/Base/app/src/front_end/pages/app_page.rb +11 -0
- data/mkbrut/templates/Base/app/src/front_end/pages/home_page.rb +62 -0
- data/mkbrut/templates/Base/app/src/front_end/support/app_session.rb +6 -0
- data/mkbrut/templates/Base/app/src/front_end/svgs/README.md +5 -0
- data/mkbrut/templates/Base/app/src/front_end/svgs/comment-button.svg +59 -0
- data/mkbrut/templates/Base/bin/README.md.erb +5 -0
- data/mkbrut/templates/Base/bin/build-assets +7 -0
- data/mkbrut/templates/Base/bin/ci +39 -0
- data/mkbrut/templates/Base/bin/console +31 -0
- data/mkbrut/templates/Base/bin/db +9 -0
- data/mkbrut/templates/Base/bin/dbconsole +51 -0
- data/mkbrut/templates/Base/bin/dev +25 -0
- data/mkbrut/templates/Base/bin/release +26 -0
- data/mkbrut/templates/Base/bin/run +86 -0
- data/mkbrut/templates/Base/bin/scaffold +9 -0
- data/mkbrut/templates/Base/bin/setup +256 -0
- data/mkbrut/templates/Base/bin/startup-message +65 -0
- data/mkbrut/templates/Base/bin/test +9 -0
- data/mkbrut/templates/Base/bin/test-server +29 -0
- data/mkbrut/templates/Base/bin/watch-and-build-assets +37 -0
- data/mkbrut/templates/Base/config.ru +16 -0
- data/mkbrut/templates/Base/docker-compose.dx.yml +92 -0
- data/mkbrut/templates/Base/dx/README.md +28 -0
- data/mkbrut/templates/Base/dx/bash_customizations +12 -0
- data/mkbrut/templates/Base/dx/bash_customizations.local +8 -0
- data/mkbrut/templates/Base/dx/build +107 -0
- data/mkbrut/templates/Base/dx/docker-compose.env.erb +25 -0
- data/mkbrut/templates/Base/dx/dx.sh.lib +137 -0
- data/mkbrut/templates/Base/dx/exec +68 -0
- data/mkbrut/templates/Base/dx/prune +19 -0
- data/mkbrut/templates/Base/dx/show-help-in-app-container-then-wait.sh +38 -0
- data/mkbrut/templates/Base/dx/start +30 -0
- data/mkbrut/templates/Base/dx/stop +23 -0
- data/mkbrut/templates/Base/package.json.erb +37 -0
- data/mkbrut/templates/Base/puma.config.rb +53 -0
- data/mkbrut/templates/Base/specs/e2e/home_page.spec.rb.erb +23 -0
- data/mkbrut/templates/Base/specs/front_end/js/SpecHelper.js +24 -0
- data/mkbrut/templates/Base/specs/front_end/pages/home_page.spec.rb +22 -0
- data/mkbrut/templates/Base/specs/lint_factories.spec.rb +7 -0
- data/mkbrut/templates/Base/specs/spec_helper.rb +78 -0
- data/mkbrut/templates/Base/specs/support.rb +2 -0
- data/mkbrut/templates/segments/BareBones/app/src/front_end/handlers/trigger_exception_handler.rb +24 -0
- data/mkbrut/templates/segments/BareBones/app/src/front_end/js/Example.js.erb +49 -0
- data/mkbrut/templates/segments/BareBones/specs/front_end/handlers/trigger_exception_handler.spec.rb +41 -0
- data/mkbrut/templates/segments/BareBones/specs/front_end/js/Example.spec.js.erb +38 -0
- data/mkbrut/templates/segments/Demo/app/src/back_end/data_models/db/guestbook_message.rb +3 -0
- data/mkbrut/templates/segments/Demo/app/src/back_end/data_models/migrations/20250628194124_guestbook.rb +14 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/components/flash_component.rb +36 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/css/constraint-violations.css +18 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/css/fonts.css +19 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/fonts/monaspace-xenon.ttf +0 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/forms/guestbook_message_form.rb +4 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/handlers/guestbook_message_handler.rb +64 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/pages/guestbook_page/message_component.rb +41 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/pages/guestbook_page.rb +43 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/pages/new_guestbook_message_page.rb +64 -0
- data/mkbrut/templates/segments/Demo/specs/back_end/data_models/db/guestbook_message.spec.rb +5 -0
- data/mkbrut/templates/segments/Demo/specs/e2e/guest_message.spec.rb +54 -0
- data/mkbrut/templates/segments/Demo/specs/factories/db/guestbook_message.factory.rb +7 -0
- data/mkbrut/templates/segments/Demo/specs/front_end/components/flash_component.spec.rb +5 -0
- data/mkbrut/templates/segments/Demo/specs/front_end/handlers/guestbook_message_handler.spec.rb +122 -0
- data/mkbrut/templates/segments/Demo/specs/front_end/pages/guestbook_page/message_component.spec.rb +5 -0
- data/mkbrut/templates/segments/Demo/specs/front_end/pages/guestbook_page.spec.rb +52 -0
- data/mkbrut/templates/segments/Demo/specs/front_end/pages/new_guestbook_message_page.spec.rb +5 -0
- data/mkbrut/templates/segments/Heroku/bin/deploy +11 -0
- data/mkbrut/templates/segments/Heroku/deploy/Dockerfile +125 -0
- data/mkbrut/templates/segments/Heroku/deploy/docker-entrypoint +15 -0
- data/mkbrut/templates/segments/Heroku/deploy/heroku_config.rb +26 -0
- metadata +188 -24
- data/brut-js/CHANGELOG.md +0 -5
- data/docs/assets/adrs.md.JRxZ5uYE.lean.js +0 -1
- data/docs/assets/chunks/@localSearchIndexroot.C9FqcQxD.js +0 -1
- data/docs/assets/overview.md.iMnwLO4x.js +0 -1
- data/docs/assets/overview.md.iMnwLO4x.lean.js +0 -1
- data/docs/assets/tutorial.md.BYXj4cOu.js +0 -1
- data/docs/assets/tutorial.md.BYXj4cOu.lean.js +0 -1
- /data/docs/assets/{components.md.BfeWD9sX.lean.js → components.md.DHh-NwKs.lean.js} +0 -0
- /data/docs/assets/{configuration.md.DoSBNc0H.lean.js → configuration.md.D8Wz3oJU.lean.js} +0 -0
- /data/docs/assets/{forms.md.DFveP5g_.lean.js → forms.md.BRE85eju.lean.js} +0 -0
data/mkbrut/templates/segments/BareBones/specs/front_end/handlers/trigger_exception_handler.spec.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe TriggerExceptionHandler do
|
4
|
+
describe "#handle!" do
|
5
|
+
context "key is correct" do
|
6
|
+
context "http status given" do
|
7
|
+
it "returns that status" do
|
8
|
+
handler = described_class.new(status: 401, key: "test-trigger-exception")
|
9
|
+
result = handler.handle!
|
10
|
+
expect(result).to have_returned_http_status(401)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
context "no http status given" do
|
14
|
+
context "message provided" do
|
15
|
+
it "raises with that message" do
|
16
|
+
handler = described_class.new(message: "test message", key: "test-trigger-exception")
|
17
|
+
expect {
|
18
|
+
handler.handle!
|
19
|
+
}.to raise_error("test message")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
context "no message provided" do
|
23
|
+
it "raises with a default message" do
|
24
|
+
handler = described_class.new(key: "test-trigger-exception")
|
25
|
+
expect {
|
26
|
+
handler.handle!
|
27
|
+
}.to raise_error("no message provided")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
context "key is incorrect" do
|
33
|
+
it "returns 404" do
|
34
|
+
handler = described_class.new(status: 401, message: "test message")
|
35
|
+
result = handler.handle!
|
36
|
+
expect(result).to have_returned_http_status(404)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import { withHTML } from "./SpecHelper.js"
|
2
|
+
|
3
|
+
// Autonomouse custom element support is very basic.
|
4
|
+
// In this spec, we use `withHTML` which was defined in SpecHelper.js,
|
5
|
+
// which ultimately uses `createTestBasedOnHTML` from BrutJS.
|
6
|
+
// This will configure JSDom with the HTML you provide, then execute
|
7
|
+
// your test as if that HTML is the document.
|
8
|
+
describe("<<%= prefix %>-example>", () => {
|
9
|
+
|
10
|
+
// The example custom element will transform its contents to
|
11
|
+
// either upper or lower case. In this test, we assert that, by
|
12
|
+
// default, it transforms to upper case. To do that,
|
13
|
+
// create an HTML document you can see below. The element
|
14
|
+
// will have been connected (connectedCallback will have been
|
15
|
+
// called) by the time the test executes. Thus,
|
16
|
+
// document.querySelector(...) will find the element and its
|
17
|
+
// .textContent will have already been transformed.
|
18
|
+
withHTML(`
|
19
|
+
<<%= prefix %>-example>This is some Text</<%= prefix %>-example>
|
20
|
+
`).test("upper case by default", ({document,assert}) => {
|
21
|
+
const element = document.querySelector("<%= prefix %>-example")
|
22
|
+
assert.equal(element.textContent,"THIS IS SOME TEXT")
|
23
|
+
})
|
24
|
+
|
25
|
+
withHTML(`
|
26
|
+
<<%= prefix %>-example transform="lower">This is some Text</<%= prefix %>-example>
|
27
|
+
`).test("lower case when asked", ({document,assert}) => {
|
28
|
+
const element = document.querySelector("<%= prefix %>-example")
|
29
|
+
assert.equal(element.textContent,"this is some text")
|
30
|
+
// Remember that setAttribute is synchronous, so by the time
|
31
|
+
// its done executing, attributeChangedCallback will have been called,
|
32
|
+
// and thus the element's `update()` method will have been called and
|
33
|
+
// its textContent transformed.
|
34
|
+
element.setAttribute("transform","upper")
|
35
|
+
assert.equal(element.textContent,"THIS IS SOME TEXT")
|
36
|
+
})
|
37
|
+
})
|
38
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
up do
|
3
|
+
create_table :guestbook_messages,
|
4
|
+
comment: "Messages people have left in the guestbook",
|
5
|
+
external_id: true do
|
6
|
+
|
7
|
+
column :name, :text
|
8
|
+
column :message, :text
|
9
|
+
column :ip_address, :inet
|
10
|
+
|
11
|
+
key [ :ip_address ]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class FlashComponent < AppComponent
|
2
|
+
def initialize(flash:)
|
3
|
+
@flash = flash
|
4
|
+
end
|
5
|
+
|
6
|
+
CLASSES = [
|
7
|
+
"f-1",
|
8
|
+
"fw-bold",
|
9
|
+
"w-50",
|
10
|
+
"mv-3",
|
11
|
+
"shadow-3",
|
12
|
+
"pa-3",
|
13
|
+
"ba",
|
14
|
+
"br-3",
|
15
|
+
"tc"
|
16
|
+
]
|
17
|
+
COLORS = [
|
18
|
+
"%{color}-300",
|
19
|
+
"bg-%{color}-800",
|
20
|
+
"bc-%{color}-700",
|
21
|
+
]
|
22
|
+
|
23
|
+
def view_template
|
24
|
+
if @flash.notice?
|
25
|
+
div(role: "status",
|
26
|
+
class: CLASSES + COLORS.map { it % { color: "blue" } }) do
|
27
|
+
t(@flash.notice)
|
28
|
+
end
|
29
|
+
elsif @flash.alert?
|
30
|
+
div(role: "alert",
|
31
|
+
class: CLASSES + COLORS.map { it % { color: "orange" } }) do
|
32
|
+
t(@flash.alert)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
/* Hide <brut-cv> tags so that we don't see
|
2
|
+
* client-side errors before the form is submitted */
|
3
|
+
brut-cv {
|
4
|
+
display: none;
|
5
|
+
color: var(--red-400);
|
6
|
+
}
|
7
|
+
|
8
|
+
/* Server-side errors are always visible */
|
9
|
+
brut-cv[server-side] {
|
10
|
+
display: block;
|
11
|
+
}
|
12
|
+
|
13
|
+
/* Only when a form submission is attempted do
|
14
|
+
* we show the <brut-cv>s that are not server-side,
|
15
|
+
* and thus client-side */
|
16
|
+
brut-form[submitted-invalid] brut-cv {
|
17
|
+
display: block;
|
18
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
/* Example of font-management. */
|
2
|
+
@font-face {
|
3
|
+
font-family: "Monaspace Xenon";
|
4
|
+
/** url is relative to this file. When bin/build-assets runs,
|
5
|
+
* esbuild will see this and modify it to work with the bundle
|
6
|
+
* that is ultimately produced */
|
7
|
+
src: url("../fonts/monaspace-xenon.ttf") format("truetype");
|
8
|
+
font-display: swap;
|
9
|
+
}
|
10
|
+
|
11
|
+
:root {
|
12
|
+
/* Sets the mono font for BrutCSS */
|
13
|
+
--ff-mono: 'Monaspace Xenon', monospace;
|
14
|
+
}
|
15
|
+
|
16
|
+
code {
|
17
|
+
/* Uses the mono font for <code> elements */
|
18
|
+
font-family: var(--ff-mono);
|
19
|
+
}
|
@@ -0,0 +1,64 @@
|
|
1
|
+
class GuestbookMessageHandler < AppHandler
|
2
|
+
def initialize(form:, flash:, rack_request_ip:, session:)
|
3
|
+
@form = form
|
4
|
+
@flash = flash
|
5
|
+
@rack_request_ip = rack_request_ip
|
6
|
+
@session = session
|
7
|
+
end
|
8
|
+
def handle
|
9
|
+
# If client-side constraint violation checking was skipped,
|
10
|
+
# but there ARE such violations, the form will be able to check
|
11
|
+
# that server-side and return true for #constraint_violations?
|
12
|
+
if @form.valid?
|
13
|
+
save_message
|
14
|
+
end
|
15
|
+
|
16
|
+
if @form.constraint_violations?
|
17
|
+
@flash.alert = :guestbook_not_saved
|
18
|
+
return NewGuestbookMessagePage.new(form: @form,
|
19
|
+
session: @session)
|
20
|
+
else
|
21
|
+
@flash.notice = :guestbook_saved
|
22
|
+
redirect_to(
|
23
|
+
GuestbookPage,
|
24
|
+
anchor: @session.guestbook_message&.external_id
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def save_message
|
32
|
+
# While we don't recommend business logic be placed
|
33
|
+
# inside handlers, we're leaving it here to avoid
|
34
|
+
# anchoring you on any particular way of managing
|
35
|
+
# the logic in your app.
|
36
|
+
if @form.message.split(/\s+/).length < 2
|
37
|
+
@form.server_side_constraint_violation(
|
38
|
+
input_name: "message",
|
39
|
+
key: :not_enough_words
|
40
|
+
)
|
41
|
+
else
|
42
|
+
if DB::GuestbookMessage.find(ip_address: @rack_request_ip)
|
43
|
+
@form.server_side_constraint_violation(
|
44
|
+
input_name: "name",
|
45
|
+
key: :already_posted
|
46
|
+
)
|
47
|
+
else
|
48
|
+
begin
|
49
|
+
guestbook_message = DB::GuestbookMessage.create(
|
50
|
+
name: @form.name,
|
51
|
+
message: @form.message,
|
52
|
+
ip_address: @rack_request_ip,
|
53
|
+
)
|
54
|
+
@session.signed_guestbook(guestbook_message)
|
55
|
+
rescue Sequel::UniqueConstraintViolation => ex
|
56
|
+
@form.server_side_constraint_violation(
|
57
|
+
input_name: "name",
|
58
|
+
key: :already_posted
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/mkbrut/templates/segments/Demo/app/src/front_end/pages/guestbook_page/message_component.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
class GuestbookPage::MessageComponent < AppComponent
|
2
|
+
def initialize(message:, index:, current_visitor_message: false)
|
3
|
+
@message = message
|
4
|
+
@current_visitor_message = current_visitor_message
|
5
|
+
@background_color = [
|
6
|
+
"bg-gray-800",
|
7
|
+
"bg-blue-800",
|
8
|
+
"bg-green-800",
|
9
|
+
"bg-yellow-800",
|
10
|
+
][index % 4]
|
11
|
+
end
|
12
|
+
|
13
|
+
def view_template
|
14
|
+
div_classes = [
|
15
|
+
"measure",
|
16
|
+
"mh-auto",
|
17
|
+
"pa-3",
|
18
|
+
"br-4",
|
19
|
+
"br-tr-0",
|
20
|
+
"br-bl-0",
|
21
|
+
"mt-3",
|
22
|
+
@background_color,
|
23
|
+
]
|
24
|
+
border_class = if @current_visitor_message
|
25
|
+
div_classes << "ba" << "bc-gray-600" << "bw-2"
|
26
|
+
else
|
27
|
+
div_classes << "bn"
|
28
|
+
end
|
29
|
+
div(class: div_classes, id: @message.external_id) do
|
30
|
+
blockquote(class: "ff-cursive p mh-auto i f-4") do
|
31
|
+
@message.message
|
32
|
+
end
|
33
|
+
p(class: "p mh-auto tr f-2") do
|
34
|
+
plain("—")
|
35
|
+
plain(@message.name)
|
36
|
+
plain(" on ")
|
37
|
+
TimeTag(date: @message.created_at, format: :date, class: "gray-400 f-1 ttu")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class GuestbookPage < AppPage
|
2
|
+
|
3
|
+
def initialize(session:)
|
4
|
+
@session = session
|
5
|
+
end
|
6
|
+
|
7
|
+
def page_template
|
8
|
+
div(class: "flex flex-column items-center ff-sans") do
|
9
|
+
article(class: "w-50 pv-3 bt bc-gray-700", id: "guestbook") do
|
10
|
+
h2(class: "ff-sans ma-0 lh-title f-4 tc") do
|
11
|
+
"Check out the Guestbook!"
|
12
|
+
end
|
13
|
+
if !@session.signed_guestbook?
|
14
|
+
a(
|
15
|
+
href: NewGuestbookMessagePage.routing,
|
16
|
+
class: "db tc mv-3 f-2 fw-5 blue-300"
|
17
|
+
) do
|
18
|
+
"Sign it, yourself!"
|
19
|
+
end
|
20
|
+
else
|
21
|
+
a(
|
22
|
+
href: "##{@session.guestbook_message.external_id}",
|
23
|
+
class: "db tc mv-3 f-2 fw-5 blue-300"
|
24
|
+
) do
|
25
|
+
"Check out your message"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
DB::GuestbookMessage.order(
|
30
|
+
Sequel.function(:random)
|
31
|
+
).each_with_index do |message,index|
|
32
|
+
render(
|
33
|
+
GuestbookPage::MessageComponent.new(
|
34
|
+
message:,
|
35
|
+
index:,
|
36
|
+
current_visitor_message: message == @session.guestbook_message
|
37
|
+
)
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
class NewGuestbookMessagePage < AppPage
|
2
|
+
def initialize(form: nil, session:)
|
3
|
+
@form = form || GuestbookMessageForm.new
|
4
|
+
@session = session
|
5
|
+
end
|
6
|
+
|
7
|
+
def before_generate
|
8
|
+
if @session.signed_guestbook?
|
9
|
+
redirect_to(GuestbookPage)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def page_template
|
14
|
+
div(class: "flex flex-column items-center ff-sans") do
|
15
|
+
render global_component(FlashComponent)
|
16
|
+
div(class: "w-50 mv-5") do
|
17
|
+
brut_form(class: "w-100") do
|
18
|
+
FormTag(for: @form, class: "flex flex-column gap-3 items-end") do
|
19
|
+
h1(class: "ff-sans ma-0 lh-title f-5 self-start flex items-center gap-3") do
|
20
|
+
img(src: "/static/images/icon.png", class: "w-5")
|
21
|
+
span { "Sign The Guestbook" }
|
22
|
+
end
|
23
|
+
label(class: "flex flex-column gap-1 w-100") do
|
24
|
+
Inputs::InputTag(
|
25
|
+
form: @form,
|
26
|
+
input_name: "name",
|
27
|
+
placeholder: "e.g. Pat",
|
28
|
+
class: "f-3 pa-2 inset-shadow-1 br-2",
|
29
|
+
"data-1p-ignore": true
|
30
|
+
)
|
31
|
+
div(class: "flex items-start gap-3") do
|
32
|
+
span { "Your Message" }
|
33
|
+
ConstraintViolations(
|
34
|
+
form: @form,
|
35
|
+
input_name: "name"
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
label(class: "flex flex-column gap-1 w-100") do
|
41
|
+
Inputs::TextareaTag(
|
42
|
+
form: @form,
|
43
|
+
input_name: "message",
|
44
|
+
placeholder: "e.g. What a great site!",
|
45
|
+
rows: 6,
|
46
|
+
class: "f-3 pa-2 inset-shadow-1 br-2"
|
47
|
+
)
|
48
|
+
div(class: "flex items-start gap-3") do
|
49
|
+
span { "Your Message" }
|
50
|
+
ConstraintViolations(
|
51
|
+
form: @form,
|
52
|
+
input_name: "message"
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
button(class: "bg-green-900 green-300 ph-5 pv-3 f-3 ba bc-green-600 br-pill shadow-1 pointer") do
|
57
|
+
"Sign!"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe "You can leave a guestbook message" do
|
4
|
+
it "accepts your message after validation, then shows it with the others" do
|
5
|
+
page.goto(HomePage.routing)
|
6
|
+
|
7
|
+
link = page.locator("a[href='#{NewGuestbookMessagePage.routing}']")
|
8
|
+
|
9
|
+
link.click
|
10
|
+
|
11
|
+
expect(page).to be_page_for(NewGuestbookMessagePage)
|
12
|
+
|
13
|
+
button = page.locator("form button")
|
14
|
+
|
15
|
+
button.click
|
16
|
+
expect(page).to be_page_for(NewGuestbookMessagePage)
|
17
|
+
|
18
|
+
name_error_message = page.locator("brut-cv-messages[input-name='name']")
|
19
|
+
message_error_message = page.locator("brut-cv-messages[input-name='message']")
|
20
|
+
|
21
|
+
this_field = t("cv.this_field")
|
22
|
+
|
23
|
+
expect(name_error_message).to have_text(
|
24
|
+
t("cv.cs.valueMissing", field: this_field)
|
25
|
+
)
|
26
|
+
expect(message_error_message).to have_text(
|
27
|
+
t("cv.cs.valueMissing", field: this_field)
|
28
|
+
)
|
29
|
+
|
30
|
+
name_field = page.locator("form input[name='name']")
|
31
|
+
message_field = page.locator("form textarea[name='message']")
|
32
|
+
|
33
|
+
name_field.fill("Pat")
|
34
|
+
message_field.fill("FIRSTPOST!")
|
35
|
+
|
36
|
+
button.click
|
37
|
+
expect(page).to be_page_for(NewGuestbookMessagePage)
|
38
|
+
|
39
|
+
flash = page.locator("[role='alert']")
|
40
|
+
expect(flash).to have_text(t(:guestbook_not_saved))
|
41
|
+
expect(message_error_message).to have_text(
|
42
|
+
t("cv.ss.not_enough_words", field: this_field)
|
43
|
+
)
|
44
|
+
|
45
|
+
message_field.fill("OK, fine, this site is awesome!")
|
46
|
+
button.click
|
47
|
+
|
48
|
+
expect(page).to be_page_for(GuestbookPage)
|
49
|
+
|
50
|
+
quotes = page.locator("blockquote")
|
51
|
+
expect(quotes).to have_text("OK, fine, this site is awesome!")
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
data/mkbrut/templates/segments/Demo/specs/front_end/handlers/guestbook_message_handler.spec.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe GuestbookMessageHandler do
|
4
|
+
describe "#handle!" do
|
5
|
+
context "there are client-side constraint violations" do
|
6
|
+
it "generates the new guestbook message page" do
|
7
|
+
form = GuestbookMessageForm.new(params: {})
|
8
|
+
flash = empty_flash
|
9
|
+
session = empty_session
|
10
|
+
rack_request_ip = Faker::Internet.ip_v4_address
|
11
|
+
|
12
|
+
confidence_check do
|
13
|
+
expect(form).to have_constraint_violation(:name,key: :valueMissing)
|
14
|
+
end
|
15
|
+
|
16
|
+
handler = described_class.new(
|
17
|
+
form:,
|
18
|
+
flash:,
|
19
|
+
session:,
|
20
|
+
rack_request_ip:
|
21
|
+
)
|
22
|
+
result = nil
|
23
|
+
expect {
|
24
|
+
result = handler.handle
|
25
|
+
}.not_to change { DB::GuestbookMessage.count }
|
26
|
+
|
27
|
+
expect(result).to have_generated(NewGuestbookMessagePage)
|
28
|
+
expect(flash.alert).to eq("guestbook_not_saved")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
context "there are server-side constraint violations" do
|
32
|
+
it "indicates a server-side error and generates the new guestbook message page" do
|
33
|
+
form = GuestbookMessageForm.new(params: {
|
34
|
+
name: "Pat",
|
35
|
+
message: "FIRSTPOST!!!!",
|
36
|
+
})
|
37
|
+
flash = empty_flash
|
38
|
+
session = empty_session
|
39
|
+
rack_request_ip = Faker::Internet.ip_v4_address
|
40
|
+
|
41
|
+
handler = described_class.new(
|
42
|
+
form:,
|
43
|
+
flash:,
|
44
|
+
session:,
|
45
|
+
rack_request_ip:
|
46
|
+
)
|
47
|
+
result = nil
|
48
|
+
expect {
|
49
|
+
result = handler.handle
|
50
|
+
}.not_to change { DB::GuestbookMessage.count }
|
51
|
+
|
52
|
+
expect(result).to have_generated(NewGuestbookMessagePage)
|
53
|
+
expect(form).to have_constraint_violation(:message, key: :not_enough_words)
|
54
|
+
expect(flash.alert).to eq("guestbook_not_saved")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
context "the ip address has already been used" do
|
58
|
+
|
59
|
+
it "indicates a server-side error and generates the new guestbook message page" do
|
60
|
+
existing_message = create(:guestbook_message)
|
61
|
+
|
62
|
+
form = GuestbookMessageForm.new(params: {
|
63
|
+
name: "Pat",
|
64
|
+
message: "This website is amazing!",
|
65
|
+
})
|
66
|
+
flash = empty_flash
|
67
|
+
session = empty_session
|
68
|
+
rack_request_ip = existing_message.ip_address
|
69
|
+
|
70
|
+
handler = described_class.new(
|
71
|
+
form:,
|
72
|
+
flash:,
|
73
|
+
session:,
|
74
|
+
rack_request_ip:
|
75
|
+
)
|
76
|
+
result = nil
|
77
|
+
expect {
|
78
|
+
result = handler.handle
|
79
|
+
}.not_to change { DB::GuestbookMessage.count }
|
80
|
+
|
81
|
+
expect(result).to have_generated(NewGuestbookMessagePage)
|
82
|
+
expect(form).to have_constraint_violation(:name, key: :already_posted)
|
83
|
+
expect(flash.alert).to eq("guestbook_not_saved")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
context "no constraint violations, unused ip address" do
|
87
|
+
it "saves the message and redirects back to the guestbook page" do
|
88
|
+
form = GuestbookMessageForm.new(params: {
|
89
|
+
name: "Pat",
|
90
|
+
message: "This website is amazing!",
|
91
|
+
})
|
92
|
+
flash = empty_flash
|
93
|
+
session = empty_session
|
94
|
+
rack_request_ip = Faker::Internet.ip_v4_address
|
95
|
+
|
96
|
+
handler = described_class.new(
|
97
|
+
form:,
|
98
|
+
flash:,
|
99
|
+
session:,
|
100
|
+
rack_request_ip:
|
101
|
+
)
|
102
|
+
result = nil
|
103
|
+
expect {
|
104
|
+
result = handler.handle
|
105
|
+
}.to change { DB::GuestbookMessage.count }.by(1)
|
106
|
+
|
107
|
+
message = DB::GuestbookMessage.order(Sequel.desc(:created_at)).first
|
108
|
+
|
109
|
+
expect(message.name).to eq("Pat")
|
110
|
+
expect(message.message).to eq("This website is amazing!")
|
111
|
+
expect(message.ip_address).to eq(rack_request_ip)
|
112
|
+
|
113
|
+
expect(result).to have_redirected_to(GuestbookPage,
|
114
|
+
anchor: message.external_id)
|
115
|
+
expect(form).not_to have_constraint_violation(:name,
|
116
|
+
key: :already_posted)
|
117
|
+
expect(flash.notice).to eq("guestbook_saved")
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe GuestbookPage do
|
4
|
+
it "shows current guestbook messages" do
|
5
|
+
messages = 5.times.map {
|
6
|
+
create(:guestbook_message)
|
7
|
+
}
|
8
|
+
confidence_check { expect(DB::GuestbookMessage.all.length).to eq(5) }
|
9
|
+
|
10
|
+
result = generate_and_parse(self.described_class.new(session: empty_session))
|
11
|
+
|
12
|
+
message_elements = result.css("div:has(> blockquote)")
|
13
|
+
expect(message_elements.length).to eq(5)
|
14
|
+
messages.each do |message|
|
15
|
+
element = message_elements.select {
|
16
|
+
it.css("blockquote").text.strip == message.message
|
17
|
+
}
|
18
|
+
expect(element.size).to eq(1),
|
19
|
+
"Found #{element.size} blockquotes for message #{message.external_id}, instead of exactly 1"
|
20
|
+
expect(element[0].css("p").text).to include(message.name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
context "when you've signed the guestbook" do
|
24
|
+
it "shows a link to view your message and not a link to make one" do
|
25
|
+
message = create(:guestbook_message)
|
26
|
+
session = empty_session
|
27
|
+
|
28
|
+
session.signed_guestbook(message)
|
29
|
+
|
30
|
+
result = generate_and_parse(self.described_class.new(session:))
|
31
|
+
|
32
|
+
add_link = result.e("a[href='#{NewGuestbookMessagePage.routing}']")
|
33
|
+
expect(add_link).to eq(nil)
|
34
|
+
|
35
|
+
view_link = result.e("a[href='##{message.external_id}']")
|
36
|
+
expect(view_link).not_to eq(nil)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
context "when you have not signed the guestbook" do
|
40
|
+
it "shows a link to view your message and not a link to make one" do
|
41
|
+
message = create(:guestbook_message)
|
42
|
+
|
43
|
+
result = generate_and_parse(self.described_class.new(session: empty_session))
|
44
|
+
|
45
|
+
add_link = result.e("a[href='#{NewGuestbookMessagePage.routing}']")
|
46
|
+
expect(add_link).not_to eq(nil)
|
47
|
+
|
48
|
+
view_link = result.e("a[href='##{message.external_id}']")
|
49
|
+
expect(view_link).to eq(nil)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|