Introduce a commercial license option alongside AGPL-3.0-only, require a CLA for contributors, and document the terms in COMMERCIAL.md and NOTICE. Add a script to stamp SPDX headers on Go files and apply it across the tree.
118 lines
5.4 KiB
Go
118 lines
5.4 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
|
|
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
|
|
|
|
package web
|
|
|
|
import "git.coopcloud.tech/wiki-cafe/member-console/internal/forms"
|
|
|
|
// The site-create form (spec fedwiki-sites "The site create form is one
|
|
// declaration"; spec form-library; design D2, D12; findings FA-17, FA-22).
|
|
// A create form rendered inside the create-site modal: the modal is a
|
|
// pre-existing container this change does not restructure (form-conventions'
|
|
// container-size rule is prospective; the site-create modal predates it),
|
|
// so only the form's own content (its fields, its 422 handling, its
|
|
// select's placeholder) moves onto the declaration.
|
|
//
|
|
// Field order departs from the template it replaces (domain, then a
|
|
// domain-suffix picker, then the custom-domain checkbox below): here the
|
|
// checkbox comes right after the site name, and the suffix picker (renamed
|
|
// selectedDomain, a real Select field now) follows it, gated by ShowIf so
|
|
// it is not on the page, and not read on submission, while a custom
|
|
// domain is in play. That reordering is required, not cosmetic: the
|
|
// picker's first option is disabled (below), so an unbound submit with an
|
|
// unchosen picker is refused by design (FA-8's rule, applied here per
|
|
// fedwiki-sites' "the domain select SHALL open with a disabled, selected
|
|
// first option, so a site cannot be created on a domain the member did not
|
|
// choose"); a custom-domain submission does not choose from this picker at
|
|
// all, so the only way to keep that submission from being wrongly refused
|
|
// is to keep the picker out of Values entirely while isCustomDomain is
|
|
// checked (ShowIf's own doc: a field not shown is "neither validated nor
|
|
// carried into Values"). ShowIf.Field must be declared earlier than the
|
|
// field reading it, which forces this order.
|
|
//
|
|
// isCustomDomain itself declares ShowIf against a hidden field the render
|
|
// sets from CustomDomainsEnabled, so the checkbox and its explanation exist
|
|
// in the DOM only when the affordance is actually open, matching the
|
|
// template this replaces; the field must therefore be declared before it
|
|
// in Fields (design D2, ShowIf's trust model).
|
|
//
|
|
// This file is the form's home; the registry is only the index (design
|
|
// D1). It sits beside partials.go, which parses through the declaration
|
|
// and never through r.FormValue.
|
|
const siteCreateFormName = "fedwiki.site.create"
|
|
|
|
// domainHint is the site-name label grammar, shown under the field. It is
|
|
// not a Pattern on the field: the same input also carries a full custom
|
|
// domain FQDN when isCustomDomain is checked, and a label-only regex would
|
|
// wrongly refuse that submission. The label grammar is instead enforced
|
|
// handler-side, on the hosted path only, by dnsname.ValidateDNSLabel, the
|
|
// same split the template this replaces already made.
|
|
const domainHint = "Lowercase letters, numbers and hyphens only; must start and end with one."
|
|
|
|
// siteCreateForm is the registered declaration.
|
|
var siteCreateForm = forms.Register(forms.FormSpec{
|
|
Name: siteCreateFormName,
|
|
Kind: forms.KindCreate,
|
|
Family: forms.Stacked,
|
|
Method: "POST",
|
|
Path: "/partials/fedwiki/sites",
|
|
Target: "#createSiteModalBody",
|
|
Swap: "innerHTML",
|
|
Fields: []forms.Field{
|
|
{
|
|
Name: "domain",
|
|
Label: "Site name",
|
|
Control: forms.Text,
|
|
Placeholder: "mysite",
|
|
Hint: domainHint,
|
|
Autocomplete: "off",
|
|
},
|
|
// customDomainsEnabled carries CustomDomainsEnabled into the DOM so
|
|
// isCustomDomain's ShowIf can read it back on a refused
|
|
// resubmission; it writes nothing of its own (finding FA-10's
|
|
// lesson: a hidden field is a convenience, never an authority).
|
|
{
|
|
Name: "customDomainsEnabled",
|
|
Label: "Custom domains enabled",
|
|
Control: forms.Hidden,
|
|
Optional: true,
|
|
},
|
|
{
|
|
Name: "isCustomDomain",
|
|
Label: "Use a domain I own",
|
|
Control: forms.Checkbox,
|
|
Value: "true",
|
|
Hint: "Enter the full domain above (e.g., wiki.example.org) once checked.",
|
|
Help: forms.Help("Use a domain I own", "A domain your workspace has already verified, or one it sits "+
|
|
"under, creates the site right away; otherwise verification starts first and the DNS records show here."),
|
|
ShowIf: forms.ShowIf{Field: "customDomainsEnabled", Equals: []string{"true"}},
|
|
},
|
|
// selectedDomain is shown only while isCustomDomain is unchecked (its
|
|
// raw value is "" both before any choice is made and when the
|
|
// checkbox is off): a custom-domain submission never chooses from
|
|
// this picker, so keeping it out of the DOM keeps it out of Parse's
|
|
// requiredness check too (see the field-order comment above).
|
|
{
|
|
Name: "selectedDomain",
|
|
Label: "Domain",
|
|
Control: forms.Select,
|
|
RuntimeOptions: true,
|
|
ShowIf: forms.ShowIf{Field: "isCustomDomain", Equals: []string{""}},
|
|
},
|
|
},
|
|
Commit: "Create site",
|
|
WayOut: forms.DismissModal("Cancel"),
|
|
})
|
|
|
|
// siteCreateOptions is the option map both the render and the parse read
|
|
// for the domain select, so what the control offered and what the handler
|
|
// accepts are one list (spec fedwiki-sites, "the domain select SHALL open
|
|
// with a disabled, selected first option").
|
|
func siteCreateOptions(allowed []string) map[string][]forms.Option {
|
|
options := []forms.Option{forms.ChooseOption("a domain")}
|
|
for _, d := range allowed {
|
|
options = append(options, forms.Option{Value: d, Label: "." + d})
|
|
}
|
|
return map[string][]forms.Option{"selectedDomain": options}
|
|
}
|