Merge branch 'main' into theme-switcher-revised
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user