Skip to content

Commit

Permalink
Add vanity input with @ symbol
Browse files Browse the repository at this point in the history
  • Loading branch information
ChiriVulpes committed Dec 29, 2024
1 parent e9645ff commit 06e7812
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 55 deletions.
84 changes: 84 additions & 0 deletions src/ui/component/VanityInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import Component from "ui/Component"
import type { InputExtensions } from "ui/component/core/ext/Input"
import Input from "ui/component/core/ext/Input"
import type { PopoverInitialiser } from "ui/component/core/Popover"
import type { TextInputExtensions } from "ui/component/core/TextInput"
import TextInput from "ui/component/core/TextInput"

interface VanityInputExtensions {
readonly input: TextInput
}

interface VanityInput extends TextInput, VanityInputExtensions { }

const VanityInput = Object.assign(
Component.Builder((component): VanityInput => {
const input = TextInput()
.style("vanity-input-input")
.filter(filterVanity)
.appendTo(component)

return component.and(Input)
.style("vanity-input")
.append(Component()
.style("vanity-input-prefix")
.text.set("@"))
.extend<VanityInputExtensions & TextInputExtensions & InputExtensions>(component => ({
// vanity input
input,

// input
required: input.required,
hint: input.hint.rehost(component),
maxLength: input.maxLength,
length: input.length,
setMaxLength (maxLength?: number) {
input.setMaxLength(maxLength)
return component
},
setRequired (required?: boolean) {
input.setRequired(required)
return component
},
setLabel (label) {
input.setLabel(label)
return component
},
tweakPopover (initialiser: PopoverInitialiser<typeof component>) {
input.tweakPopover(initialiser as never)
return component
},

// text input
state: input.state,
get value () { return input.value },
set value (value: string) { input.value = value },
default: input.default.rehost(component),
placeholder: input.placeholder.rehost(component),
ignoreInputEvent (ignore = true) {
input.ignoreInputEvent(ignore)
return component
},
filter (filter) {
input.filter(filter)
return component
},
}))
}),
{
filter: filterVanity,
}
)

export default VanityInput

function filterVanity (vanity: string, textBefore = "", isFullText = true) {
vanity = vanity.replace(/[\W_]+/g, "-")
if (isFullText)
vanity = vanity.replace(/^-|-$/g, "")

if (textBefore.endsWith("-") && vanity.startsWith("-"))
return vanity.slice(1)

return vanity
}
2 changes: 1 addition & 1 deletion src/ui/component/core/TextInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import State from "utility/State"
*/
type FilterFunction = (text: string, textBefore: string, isFullText: boolean) => string

interface TextInputExtensions {
export interface TextInputExtensions {
readonly state: State<string>
value: string
readonly default: StringApplicator.Optional<TextInput>
Expand Down
84 changes: 45 additions & 39 deletions src/ui/utility/StringApplicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ interface StringApplicator<HOST> {
bind (state: State<string>): HOST
unbind (): HOST
refresh (): void
rehost<NEW_HOST> (newHost: NEW_HOST): StringApplicator<NEW_HOST>
}

function StringApplicator<HOST> (host: HOST, apply: (value?: string) => any): StringApplicator.Optional<HOST>
Expand All @@ -82,49 +83,53 @@ function StringApplicator<HOST> (host: HOST, defaultValueOrApply: string | undef

let translationHandler: Quilt.Handler | undefined
let unbind: UnsubscribeState | undefined
const result: StringApplicator.Optional<HOST> = {
state: State(defaultValue),
set: value => {
unbind?.()
translationHandler = undefined
setInternal(value)
return host
},
use: translation => {
unbind?.()
if (typeof translation === "string") {
const result = makeApplicator(host)
return result

function makeApplicator<HOST> (host: HOST): StringApplicator.Optional<HOST> {
return {
state: State(defaultValue),
set: value => {
unbind?.()
translationHandler = undefined
setInternal(quilt[translation]().toString())
setInternal(value)
return host
}

translationHandler = translation
result.refresh()
return host
},
bind: state => {
translationHandler = undefined
unbind?.()
unbind = state?.use(host as Component, setInternal)
if (!state)
},
use: translation => {
unbind?.()
if (typeof translation === "string") {
translationHandler = undefined
setInternal(quilt[translation]().toString())
return host
}

translationHandler = translation
result.refresh()
return host
},
bind: state => {
translationHandler = undefined
unbind?.()
unbind = state?.use(host as Component, setInternal)
if (!state)
setInternal(defaultValue)
return host
},
unbind: () => {
unbind?.()
setInternal(defaultValue)
return host
},
unbind: () => {
unbind?.()
setInternal(defaultValue)
return host
},
refresh: () => {
if (!translationHandler)
return

setInternal(translationHandler(quilt, QuiltHelper).toString())
},
return host
},
refresh: () => {
if (!translationHandler)
return

setInternal(translationHandler(quilt, QuiltHelper).toString())
},
rehost: makeApplicator,
}
}

return result

function setInternal (value?: string | Weave) {
if (typeof value === "object")
value = value.toString()
Expand All @@ -138,10 +143,11 @@ function StringApplicator<HOST> (host: HOST, defaultValueOrApply: string | undef

namespace StringApplicator {

export interface Optional<HOST> extends Omit<StringApplicator<HOST>, "state" | "set" | "bind"> {
export interface Optional<HOST> extends Omit<StringApplicator<HOST>, "state" | "set" | "bind" | "rehost"> {
state: State<string | undefined>
set (value?: string): HOST
bind (state?: State<string | Weave | undefined>): HOST
rehost<NEW_HOST> (newHost: NEW_HOST): StringApplicator.Optional<NEW_HOST>
}

}
Expand Down
16 changes: 3 additions & 13 deletions src/ui/view/account/AccountViewForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import LabelledTable from "ui/component/core/LabelledTable"
import LabelledTextInputBlock from "ui/component/core/LabelledTextInputBlock"
import TextEditor from "ui/component/core/TextEditor"
import TextInput from "ui/component/core/TextInput"
import VanityInput from "ui/component/VanityInput"

type AccountViewFormType =
| "create"
Expand Down Expand Up @@ -37,11 +38,10 @@ export default Component.Builder((component, type: AccountViewFormType) => {
table.label(label => label.text.use("view/account/name/label"))
.content((content, label) => content.append(nameInput.setLabel(label)))

const vanityInput = TextInput()
const vanityInput = VanityInput()
.placeholder.bind(nameInput.state
.map(component, name => filterVanity(name)))
.map(component, name => VanityInput.filter(name)))
.default.bind(Session.Auth.author.map(component, author => author?.vanity))
.filter(filterVanity)
.hint.use("view/account/vanity/hint")
.setMaxLength(FormInputLengths.manifest?.author.vanity)
table.label(label => label.text.use("view/account/vanity/label"))
Expand Down Expand Up @@ -99,14 +99,4 @@ export default Component.Builder((component, type: AccountViewFormType) => {

return form

function filterVanity (vanity: string, textBefore = "", isFullText = true) {
vanity = vanity.replace(/[\W_]+/g, "-")
if (isFullText)
vanity = vanity.replace(/^-|-$/g, "")

if (textBefore.endsWith("-") && vanity.startsWith("-"))
return vanity.slice(1)

return vanity
}
})
2 changes: 2 additions & 0 deletions style/component/core/input.chiri
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
$input-divider-border-colour: color-mix(in lch, $border-colour, transparent 90%)

.input:

&-popover:
Expand Down
3 changes: 1 addition & 2 deletions style/component/core/text-input-block.chiri
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
.text-input-block:
$divider-border-colour: color-mix(in lch, $border-colour, transparent 90%)

&-input:
#after: .text-input
Expand Down Expand Up @@ -31,7 +30,7 @@
%bottom-0
%inset-inline-2
%border-bottom-1
$border-colour: $divider-border-colour
$border-colour: $input-divider-border-colour

&--last:
#after: .text-input-block-input-wrapper
Expand Down
22 changes: 22 additions & 0 deletions style/component/vanity-input.chiri
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.vanity-input:
%grid
%stack

&-input:
%stack-self
%padding-left-4
; padding-left: calc($space-4 + $space-2)

&-prefix:
%stack-self
%width-4
%grid
%align-content-centre
%justify-content-end
%padding-right-1
%relative
%margin-block-1
%border-box
%no-pointer-events
%opacity-50
top: -.06rem
1 change: 1 addition & 0 deletions style/index.chiri
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
component/core/text-editor
component/core/form
component/core/paginator
component/vanity-input
component/flag
component/masthead
component/sidebar
Expand Down

0 comments on commit 06e7812

Please sign in to comment.