Merge branch 'main' into theme-switcher-revised

This commit is contained in:
Jason Zimdars
2025-12-08 17:58:46 -06:00
committed by GitHub
129 changed files with 1708 additions and 564 deletions
@@ -1,13 +0,0 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static values = { fallbackDestination: String }
connect() {
if (history.state.turbo?.restorationIndex > 0) {
this.element.href = "javascript:history.back()"
} else {
this.element.href = this.fallbackDestinationValue
}
}
}
@@ -33,8 +33,7 @@ export default class extends Controller {
}
get #entropyCleanupInDays() {
this.entropyCleanupInDays ??= signedDifferenceInDays(new Date(), new Date(this.entropyValue.closesAt))
return this.entropyCleanupInDays
return signedDifferenceInDays(new Date(), new Date(this.entropyValue.closesAt))
}
#showEntropy() {
@@ -0,0 +1,33 @@
import { Controller } from "@hotwired/stimulus"
import { debounce } from "helpers/timing_helpers"
export default class extends Controller {
static targets = [ "item", "counter" ]
static values = {
propertyName: String,
maxValue: { type: Number, default: 20 }
}
initialize() {
this.#updateCounter = debounce(this.#updateCounter.bind(this), 50)
}
connect() {
this.#updateCounter()
}
itemTargetConnected() {
this.#updateCounter()
}
itemTargetDisconnected() {
this.#updateCounter()
}
#updateCounter = () => {
if (!this.hasCounterTarget) return
const count = Math.min(this.itemTargets.length, this.maxValueValue)
this.counterTarget.style.setProperty(this.propertyNameValue, count)
}
}
@@ -16,11 +16,14 @@ export default class extends Controller {
await nextFrame()
this.dragItem = this.#itemContaining(event.target)
this.sourceContainer = this.#containerContaining(this.dragItem)
this.originalDraggedItemCssVariable = this.#containerCssVariableFor(this.sourceContainer)
this.dragItem.classList.add(this.draggedItemClass)
}
dragOver(event) {
event.preventDefault()
if (!this.dragItem) { return }
const container = this.#containerContaining(event.target)
this.#clearContainerHoverClasses()
@@ -28,32 +31,39 @@ export default class extends Controller {
if (container !== this.sourceContainer) {
container.classList.add(this.hoverContainerClass)
this.#applyContainerCssVariableToDraggedItem(container)
} else {
this.#restoreOriginalDraggedItemCssVariable()
}
}
async drop(event) {
const container = this.#containerContaining(event.target)
const targetContainer = this.#containerContaining(event.target)
if (!container || container === this.sourceContainer) { return }
if (!targetContainer || targetContainer === this.sourceContainer) { return }
this.wasDropped = true
this.#increaseCounter(targetContainer)
this.#decreaseCounter(this.sourceContainer)
const sourceContainer = this.sourceContainer
await this.#submitDropRequest(this.dragItem, container)
this.#reloadSourceFrame(sourceContainer);
this.#insertDraggedItem(targetContainer, this.dragItem)
await this.#submitDropRequest(this.dragItem, targetContainer)
this.#reloadSourceFrame(sourceContainer)
}
dragEnd() {
this.dragItem.classList.remove(this.draggedItemClass)
this.#clearContainerHoverClasses()
if (this.wasDropped) {
this.dragItem.remove()
if (!this.wasDropped) {
this.#restoreOriginalDraggedItemCssVariable()
}
this.sourceContainer = null
this.dragItem = null
this.wasDropped = false
this.originalDraggedItemCssVariable = null
}
#itemContaining(element) {
@@ -68,6 +78,63 @@ export default class extends Controller {
this.containerTargets.forEach(container => container.classList.remove(this.hoverContainerClass))
}
#applyContainerCssVariableToDraggedItem(container) {
const cssVariable = this.#containerCssVariableFor(container)
if (cssVariable) {
this.dragItem.style.setProperty(cssVariable.name, cssVariable.value)
}
}
#restoreOriginalDraggedItemCssVariable() {
if (this.originalDraggedItemCssVariable) {
const { name, value } = this.originalDraggedItemCssVariable
this.dragItem.style.setProperty(name, value)
}
}
#containerCssVariableFor(container) {
const { dragAndDropCssVariableName, dragAndDropCssVariableValue } = container.dataset
if (dragAndDropCssVariableName && dragAndDropCssVariableValue) {
return { name: dragAndDropCssVariableName, value: dragAndDropCssVariableValue }
}
return null
}
#increaseCounter(container) {
this.#modifyCounter(container, count => count + 1)
}
#decreaseCounter(container) {
this.#modifyCounter(container, count => Math.max(0, count - 1))
}
#modifyCounter(container, fn) {
const counterElement = container.querySelector("[data-drag-and-drop-counter]")
if (counterElement) {
const currentValue = counterElement.textContent.trim()
if (!/^\d+$/.test(currentValue)) return
counterElement.textContent = fn(parseInt(currentValue))
}
}
#insertDraggedItem(container, item) {
const itemContainer = container.querySelector("[data-drag-drop-item-container]")
const topItems = itemContainer.querySelectorAll("[data-drag-and-drop-top]")
const firstTopItem = topItems[0]
const lastTopItem = topItems[topItems.length - 1]
const isTopItem = item.hasAttribute("data-drag-and-drop-top")
const referenceItem = isTopItem ? firstTopItem : lastTopItem
if (referenceItem) {
referenceItem[isTopItem ? "before" : "after"](item)
} else {
itemContainer.prepend(item)
}
}
async #submitDropRequest(item, container) {
const body = new FormData()
const id = item.dataset.id
@@ -80,18 +147,4 @@ export default class extends Controller {
const frame = sourceContainer.querySelector("[data-drag-and-drop-refresh]")
if (frame) frame.reload()
}
#decreaseCounter(sourceContainer) {
const counterElement = sourceContainer.querySelector("[data-drag-and-drop-counter]")
if (counterElement) {
const currentValue = counterElement.textContent.trim()
if (!/^\d+$/.test(currentValue)) return
const count = parseInt(currentValue)
if (count > 0) {
counterElement.textContent = count - 1
}
}
}
}
+23 -1
View File
@@ -8,10 +8,21 @@ export default class extends Controller {
debounceTimeout: { type: Number, default: 300 }
}
#isComposing = false
initialize() {
this.debouncedSubmit = debounce(this.debouncedSubmit.bind(this), this.debounceTimeoutValue)
}
// IME Composition tracking
compositionStart() {
this.#isComposing = true
}
compositionEnd() {
this.#isComposing = false
}
submit() {
this.element.requestSubmit()
}
@@ -21,12 +32,23 @@ export default class extends Controller {
if (input) {
const value = (input.value || "").trim()
if (value.length === 0) {
const isEmpty = value.length === 0
if (isEmpty) {
event.preventDefault()
input.setCustomValidity(input.dataset.validationMessage || "Please fill out this field")
input.reportValidity()
input.addEventListener("input", () => input.setCustomValidity(""), { once: true })
}
}
}
preventComposingSubmit(event) {
if (this.#isComposing) {
event.preventDefault()
}
}
debouncedSubmit(event) {
this.submit(event)
}
@@ -3,14 +3,6 @@ import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = [ "caption", "image", "dialog", "zoomedImage" ]
connect() {
this.dialogTarget.addEventListener('transitionend', this.handleTransitionEnd.bind(this))
}
disconnect() {
this.dialogTarget.removeEventListener('transitionend', this.handleTransitionEnd.bind(this))
}
open(event) {
this.dialogTarget.showModal()
this.#set(event.target.closest("a"))
@@ -4,12 +4,18 @@ import { onNextEventLoopTick } from "helpers/timing_helpers"
export default class extends Controller {
static targets = [ "input" ]
submitOnEnter(event) {
event.preventDefault()
this.submit()
}
submitOnPaste() {
onNextEventLoopTick(() => this.submit())
}
submit() {
onNextEventLoopTick(() => {
if (!this.inputTarget.disabled) {
this.element.submit()
this.inputTarget.disabled = true
}
})
if (this.inputTarget.disabled) return
this.element.submit()
this.inputTarget.disabled = true
}
}
@@ -245,6 +245,9 @@ export default class extends Controller {
}
},
Enter(event) {
// Skip handling during IME composition (e.g., Japanese input)
if (event.isComposing) { return }
if (event.shiftKey) {
this.#toggleCurrentItem(event)
} else {