[增添]添加了datasource的setting数据库以及默认值
This commit is contained in:
90
vendor/filament/forms/resources/js/components/color-picker.js
vendored
Normal file
90
vendor/filament/forms/resources/js/components/color-picker.js
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
import 'vanilla-colorful/hex-color-picker.js'
|
||||
import 'vanilla-colorful/hsl-string-color-picker.js'
|
||||
import 'vanilla-colorful/rgb-string-color-picker.js'
|
||||
import 'vanilla-colorful/rgba-string-color-picker.js'
|
||||
|
||||
export default function colorPickerFormComponent({
|
||||
isAutofocused,
|
||||
isDisabled,
|
||||
isLive,
|
||||
isLiveDebounced,
|
||||
isLiveOnBlur,
|
||||
liveDebounce,
|
||||
state,
|
||||
}) {
|
||||
return {
|
||||
state,
|
||||
|
||||
init: function () {
|
||||
if (!(this.state === null || this.state === '')) {
|
||||
this.setState(this.state)
|
||||
}
|
||||
|
||||
if (isAutofocused) {
|
||||
this.togglePanelVisibility(this.$refs.input)
|
||||
}
|
||||
|
||||
this.$refs.input.addEventListener('change', (event) => {
|
||||
this.setState(event.target.value)
|
||||
})
|
||||
|
||||
this.$refs.panel.addEventListener('color-changed', (event) => {
|
||||
this.setState(event.detail.value)
|
||||
|
||||
if (isLiveOnBlur || !(isLive || isLiveDebounced)) {
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
if (this.state !== event.detail.value) {
|
||||
return
|
||||
}
|
||||
|
||||
this.commitState()
|
||||
},
|
||||
isLiveDebounced ? liveDebounce : 250,
|
||||
)
|
||||
})
|
||||
|
||||
if (isLive || isLiveDebounced || isLiveOnBlur) {
|
||||
new MutationObserver(() =>
|
||||
this.isOpen() ? null : this.commitState(),
|
||||
).observe(this.$refs.panel, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
togglePanelVisibility: function () {
|
||||
if (isDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$refs.panel.toggle(this.$refs.input)
|
||||
},
|
||||
|
||||
setState: function (value) {
|
||||
this.state = value
|
||||
|
||||
this.$refs.input.value = value
|
||||
this.$refs.panel.color = value
|
||||
},
|
||||
|
||||
isOpen: function () {
|
||||
return this.$refs.panel.style.display === 'block'
|
||||
},
|
||||
|
||||
commitState: function () {
|
||||
if (
|
||||
JSON.stringify(this.$wire.__instance.canonical) ===
|
||||
JSON.stringify(this.$wire.__instance.ephemeral)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$wire.$commit()
|
||||
},
|
||||
}
|
||||
}
|
||||
516
vendor/filament/forms/resources/js/components/date-time-picker.js
vendored
Normal file
516
vendor/filament/forms/resources/js/components/date-time-picker.js
vendored
Normal file
@@ -0,0 +1,516 @@
|
||||
import dayjs from 'dayjs/esm'
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
||||
import localeData from 'dayjs/plugin/localeData'
|
||||
import timezone from 'dayjs/plugin/timezone'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(localeData)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(utc)
|
||||
|
||||
window.dayjs = dayjs
|
||||
|
||||
export default function dateTimePickerFormComponent({
|
||||
displayFormat,
|
||||
firstDayOfWeek,
|
||||
isAutofocused,
|
||||
locale,
|
||||
shouldCloseOnDateSelection,
|
||||
state,
|
||||
}) {
|
||||
const timezone = dayjs.tz.guess()
|
||||
|
||||
return {
|
||||
daysInFocusedMonth: [],
|
||||
|
||||
displayText: '',
|
||||
|
||||
emptyDaysInFocusedMonth: [],
|
||||
|
||||
focusedDate: null,
|
||||
|
||||
focusedMonth: null,
|
||||
|
||||
focusedYear: null,
|
||||
|
||||
hour: null,
|
||||
|
||||
isClearingState: false,
|
||||
|
||||
minute: null,
|
||||
|
||||
second: null,
|
||||
|
||||
state,
|
||||
|
||||
dayLabels: [],
|
||||
|
||||
months: [],
|
||||
|
||||
init: function () {
|
||||
dayjs.locale(locales[locale] ?? locales['en'])
|
||||
|
||||
this.focusedDate = dayjs().tz(timezone)
|
||||
|
||||
let date =
|
||||
this.getSelectedDate() ??
|
||||
dayjs().tz(timezone).hour(0).minute(0).second(0)
|
||||
|
||||
if (this.getMaxDate() !== null && date.isAfter(this.getMaxDate())) {
|
||||
date = null
|
||||
} else if (
|
||||
this.getMinDate() !== null &&
|
||||
date.isBefore(this.getMinDate())
|
||||
) {
|
||||
date = null
|
||||
}
|
||||
|
||||
this.hour = date?.hour() ?? 0
|
||||
this.minute = date?.minute() ?? 0
|
||||
this.second = date?.second() ?? 0
|
||||
|
||||
this.setDisplayText()
|
||||
this.setMonths()
|
||||
this.setDayLabels()
|
||||
|
||||
if (isAutofocused) {
|
||||
this.$nextTick(() =>
|
||||
this.togglePanelVisibility(this.$refs.button),
|
||||
)
|
||||
}
|
||||
|
||||
this.$watch('focusedMonth', () => {
|
||||
this.focusedMonth = +this.focusedMonth
|
||||
|
||||
if (this.focusedDate.month() === this.focusedMonth) {
|
||||
return
|
||||
}
|
||||
|
||||
this.focusedDate = this.focusedDate.month(this.focusedMonth)
|
||||
})
|
||||
|
||||
this.$watch('focusedYear', () => {
|
||||
if (this.focusedYear?.length > 4) {
|
||||
this.focusedYear = this.focusedYear.substring(0, 4)
|
||||
}
|
||||
|
||||
if (!this.focusedYear || this.focusedYear?.length !== 4) {
|
||||
return
|
||||
}
|
||||
|
||||
let year = +this.focusedYear
|
||||
|
||||
if (!Number.isInteger(year)) {
|
||||
year = dayjs().tz(timezone).year()
|
||||
|
||||
this.focusedYear = year
|
||||
}
|
||||
|
||||
if (this.focusedDate.year() === year) {
|
||||
return
|
||||
}
|
||||
|
||||
this.focusedDate = this.focusedDate.year(year)
|
||||
})
|
||||
|
||||
this.$watch('focusedDate', () => {
|
||||
let month = this.focusedDate.month()
|
||||
let year = this.focusedDate.year()
|
||||
|
||||
if (this.focusedMonth !== month) {
|
||||
this.focusedMonth = month
|
||||
}
|
||||
|
||||
if (this.focusedYear !== year) {
|
||||
this.focusedYear = year
|
||||
}
|
||||
|
||||
this.setupDaysGrid()
|
||||
})
|
||||
|
||||
this.$watch('hour', () => {
|
||||
let hour = +this.hour
|
||||
|
||||
if (!Number.isInteger(hour)) {
|
||||
this.hour = 0
|
||||
} else if (hour > 23) {
|
||||
this.hour = 0
|
||||
} else if (hour < 0) {
|
||||
this.hour = 23
|
||||
} else {
|
||||
this.hour = hour
|
||||
}
|
||||
|
||||
if (this.isClearingState) {
|
||||
return
|
||||
}
|
||||
|
||||
let date = this.getSelectedDate() ?? this.focusedDate
|
||||
|
||||
this.setState(date.hour(this.hour ?? 0))
|
||||
})
|
||||
|
||||
this.$watch('minute', () => {
|
||||
let minute = +this.minute
|
||||
|
||||
if (!Number.isInteger(minute)) {
|
||||
this.minute = 0
|
||||
} else if (minute > 59) {
|
||||
this.minute = 0
|
||||
} else if (minute < 0) {
|
||||
this.minute = 59
|
||||
} else {
|
||||
this.minute = minute
|
||||
}
|
||||
|
||||
if (this.isClearingState) {
|
||||
return
|
||||
}
|
||||
|
||||
let date = this.getSelectedDate() ?? this.focusedDate
|
||||
|
||||
this.setState(date.minute(this.minute ?? 0))
|
||||
})
|
||||
|
||||
this.$watch('second', () => {
|
||||
let second = +this.second
|
||||
|
||||
if (!Number.isInteger(second)) {
|
||||
this.second = 0
|
||||
} else if (second > 59) {
|
||||
this.second = 0
|
||||
} else if (second < 0) {
|
||||
this.second = 59
|
||||
} else {
|
||||
this.second = second
|
||||
}
|
||||
|
||||
if (this.isClearingState) {
|
||||
return
|
||||
}
|
||||
|
||||
let date = this.getSelectedDate() ?? this.focusedDate
|
||||
|
||||
this.setState(date.second(this.second ?? 0))
|
||||
})
|
||||
|
||||
this.$watch('state', () => {
|
||||
if (this.state === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
let date = this.getSelectedDate()
|
||||
|
||||
if (date === null) {
|
||||
this.clearState()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
this.getMaxDate() !== null &&
|
||||
date?.isAfter(this.getMaxDate())
|
||||
) {
|
||||
date = null
|
||||
}
|
||||
if (
|
||||
this.getMinDate() !== null &&
|
||||
date?.isBefore(this.getMinDate())
|
||||
) {
|
||||
date = null
|
||||
}
|
||||
|
||||
const newHour = date?.hour() ?? 0
|
||||
if (this.hour !== newHour) {
|
||||
this.hour = newHour
|
||||
}
|
||||
|
||||
const newMinute = date?.minute() ?? 0
|
||||
if (this.minute !== newMinute) {
|
||||
this.minute = newMinute
|
||||
}
|
||||
|
||||
const newSecond = date?.second() ?? 0
|
||||
if (this.second !== newSecond) {
|
||||
this.second = newSecond
|
||||
}
|
||||
|
||||
this.setDisplayText()
|
||||
})
|
||||
},
|
||||
|
||||
clearState: function () {
|
||||
this.isClearingState = true
|
||||
|
||||
this.setState(null)
|
||||
|
||||
this.hour = 0
|
||||
this.minute = 0
|
||||
this.second = 0
|
||||
|
||||
this.$nextTick(() => (this.isClearingState = false))
|
||||
},
|
||||
|
||||
dateIsDisabled: function (date) {
|
||||
if (
|
||||
this.$refs?.disabledDates &&
|
||||
JSON.parse(this.$refs.disabledDates.value ?? []).some(
|
||||
(disabledDate) => {
|
||||
disabledDate = dayjs(disabledDate)
|
||||
|
||||
if (!disabledDate.isValid()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return disabledDate.isSame(date, 'day')
|
||||
},
|
||||
)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (this.getMaxDate() && date.isAfter(this.getMaxDate(), 'day')) {
|
||||
return true
|
||||
}
|
||||
if (this.getMinDate() && date.isBefore(this.getMinDate(), 'day')) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
dayIsDisabled: function (day) {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
|
||||
return this.dateIsDisabled(this.focusedDate.date(day))
|
||||
},
|
||||
|
||||
dayIsSelected: function (day) {
|
||||
let selectedDate = this.getSelectedDate()
|
||||
|
||||
if (selectedDate === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
|
||||
return (
|
||||
selectedDate.date() === day &&
|
||||
selectedDate.month() === this.focusedDate.month() &&
|
||||
selectedDate.year() === this.focusedDate.year()
|
||||
)
|
||||
},
|
||||
|
||||
dayIsToday: function (day) {
|
||||
let date = dayjs().tz(timezone)
|
||||
this.focusedDate ??= date
|
||||
|
||||
return (
|
||||
date.date() === day &&
|
||||
date.month() === this.focusedDate.month() &&
|
||||
date.year() === this.focusedDate.year()
|
||||
)
|
||||
},
|
||||
|
||||
focusPreviousDay: function () {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
|
||||
this.focusedDate = this.focusedDate.subtract(1, 'day')
|
||||
},
|
||||
|
||||
focusPreviousWeek: function () {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
|
||||
this.focusedDate = this.focusedDate.subtract(1, 'week')
|
||||
},
|
||||
|
||||
focusNextDay: function () {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
|
||||
this.focusedDate = this.focusedDate.add(1, 'day')
|
||||
},
|
||||
|
||||
focusNextWeek: function () {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
|
||||
this.focusedDate = this.focusedDate.add(1, 'week')
|
||||
},
|
||||
|
||||
getDayLabels: function () {
|
||||
const labels = dayjs.weekdaysShort()
|
||||
|
||||
if (firstDayOfWeek === 0) {
|
||||
return labels
|
||||
}
|
||||
|
||||
return [
|
||||
...labels.slice(firstDayOfWeek),
|
||||
...labels.slice(0, firstDayOfWeek),
|
||||
]
|
||||
},
|
||||
|
||||
getMaxDate: function () {
|
||||
let date = dayjs(this.$refs.maxDate?.value)
|
||||
|
||||
return date.isValid() ? date : null
|
||||
},
|
||||
|
||||
getMinDate: function () {
|
||||
let date = dayjs(this.$refs.minDate?.value)
|
||||
|
||||
return date.isValid() ? date : null
|
||||
},
|
||||
|
||||
getSelectedDate: function () {
|
||||
if (this.state === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (this.state === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
let date = dayjs(this.state)
|
||||
|
||||
if (!date.isValid()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return date
|
||||
},
|
||||
|
||||
togglePanelVisibility: function () {
|
||||
if (!this.isOpen()) {
|
||||
this.focusedDate =
|
||||
this.getSelectedDate() ??
|
||||
this.getMinDate() ??
|
||||
dayjs().tz(timezone)
|
||||
|
||||
this.setupDaysGrid()
|
||||
}
|
||||
|
||||
this.$refs.panel.toggle(this.$refs.button)
|
||||
},
|
||||
|
||||
selectDate: function (day = null) {
|
||||
if (day) {
|
||||
this.setFocusedDay(day)
|
||||
}
|
||||
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
|
||||
this.setState(this.focusedDate)
|
||||
|
||||
if (shouldCloseOnDateSelection) {
|
||||
this.togglePanelVisibility()
|
||||
}
|
||||
},
|
||||
|
||||
setDisplayText: function () {
|
||||
this.displayText = this.getSelectedDate()
|
||||
? this.getSelectedDate().format(displayFormat)
|
||||
: ''
|
||||
},
|
||||
|
||||
setMonths: function () {
|
||||
this.months = dayjs.months()
|
||||
},
|
||||
|
||||
setDayLabels: function () {
|
||||
this.dayLabels = this.getDayLabels()
|
||||
},
|
||||
|
||||
setupDaysGrid: function () {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
|
||||
this.emptyDaysInFocusedMonth = Array.from(
|
||||
{
|
||||
length: this.focusedDate.date(8 - firstDayOfWeek).day(),
|
||||
},
|
||||
(_, i) => i + 1,
|
||||
)
|
||||
|
||||
this.daysInFocusedMonth = Array.from(
|
||||
{
|
||||
length: this.focusedDate.daysInMonth(),
|
||||
},
|
||||
(_, i) => i + 1,
|
||||
)
|
||||
},
|
||||
|
||||
setFocusedDay: function (day) {
|
||||
this.focusedDate = (this.focusedDate ?? dayjs().tz(timezone)).date(
|
||||
day,
|
||||
)
|
||||
},
|
||||
|
||||
setState: function (date) {
|
||||
if (date === null) {
|
||||
this.state = null
|
||||
this.setDisplayText()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (this.dateIsDisabled(date)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.state = date
|
||||
.hour(this.hour ?? 0)
|
||||
.minute(this.minute ?? 0)
|
||||
.second(this.second ?? 0)
|
||||
.format('YYYY-MM-DD HH:mm:ss')
|
||||
|
||||
this.setDisplayText()
|
||||
},
|
||||
|
||||
isOpen: function () {
|
||||
return this.$refs.panel?.style.display === 'block'
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const locales = {
|
||||
ar: require('dayjs/locale/ar'),
|
||||
bs: require('dayjs/locale/bs'),
|
||||
ca: require('dayjs/locale/ca'),
|
||||
ckb: require('dayjs/locale/ku'),
|
||||
cs: require('dayjs/locale/cs'),
|
||||
cy: require('dayjs/locale/cy'),
|
||||
da: require('dayjs/locale/da'),
|
||||
de: require('dayjs/locale/de'),
|
||||
en: require('dayjs/locale/en'),
|
||||
es: require('dayjs/locale/es'),
|
||||
et: require('dayjs/locale/et'),
|
||||
fa: require('dayjs/locale/fa'),
|
||||
fi: require('dayjs/locale/fi'),
|
||||
fr: require('dayjs/locale/fr'),
|
||||
hi: require('dayjs/locale/hi'),
|
||||
hu: require('dayjs/locale/hu'),
|
||||
hy: require('dayjs/locale/hy-am'),
|
||||
id: require('dayjs/locale/id'),
|
||||
it: require('dayjs/locale/it'),
|
||||
ja: require('dayjs/locale/ja'),
|
||||
ka: require('dayjs/locale/ka'),
|
||||
km: require('dayjs/locale/km'),
|
||||
ku: require('dayjs/locale/ku'),
|
||||
lt: require('dayjs/locale/lt'),
|
||||
lv: require('dayjs/locale/lv'),
|
||||
ms: require('dayjs/locale/ms'),
|
||||
my: require('dayjs/locale/my'),
|
||||
nl: require('dayjs/locale/nl'),
|
||||
no: require('dayjs/locale/nb'),
|
||||
pl: require('dayjs/locale/pl'),
|
||||
pt_BR: require('dayjs/locale/pt-br'),
|
||||
pt_PT: require('dayjs/locale/pt'),
|
||||
ro: require('dayjs/locale/ro'),
|
||||
ru: require('dayjs/locale/ru'),
|
||||
sv: require('dayjs/locale/sv'),
|
||||
tr: require('dayjs/locale/tr'),
|
||||
uk: require('dayjs/locale/uk'),
|
||||
vi: require('dayjs/locale/vi'),
|
||||
zh_CN: require('dayjs/locale/zh-cn'),
|
||||
zh_TW: require('dayjs/locale/zh-tw'),
|
||||
}
|
||||
776
vendor/filament/forms/resources/js/components/file-upload.js
vendored
Normal file
776
vendor/filament/forms/resources/js/components/file-upload.js
vendored
Normal file
@@ -0,0 +1,776 @@
|
||||
import * as FilePond from 'filepond'
|
||||
import Cropper from 'cropperjs'
|
||||
import FilePondPluginFileValidateSize from 'filepond-plugin-file-validate-size'
|
||||
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type'
|
||||
import FilePondPluginImageCrop from 'filepond-plugin-image-crop'
|
||||
import FilePondPluginImageEdit from 'filepond-plugin-image-edit'
|
||||
import FilePondPluginImageExifOrientation from 'filepond-plugin-image-exif-orientation'
|
||||
import FilePondPluginImagePreview from 'filepond-plugin-image-preview'
|
||||
import FilePondPluginImageResize from 'filepond-plugin-image-resize'
|
||||
import FilePondPluginImageTransform from 'filepond-plugin-image-transform'
|
||||
import FilePondPluginMediaPreview from 'filepond-plugin-media-preview'
|
||||
|
||||
FilePond.registerPlugin(FilePondPluginFileValidateSize)
|
||||
FilePond.registerPlugin(FilePondPluginFileValidateType)
|
||||
FilePond.registerPlugin(FilePondPluginImageCrop)
|
||||
FilePond.registerPlugin(FilePondPluginImageEdit)
|
||||
FilePond.registerPlugin(FilePondPluginImageExifOrientation)
|
||||
FilePond.registerPlugin(FilePondPluginImagePreview)
|
||||
FilePond.registerPlugin(FilePondPluginImageResize)
|
||||
FilePond.registerPlugin(FilePondPluginImageTransform)
|
||||
FilePond.registerPlugin(FilePondPluginMediaPreview)
|
||||
|
||||
window.FilePond = FilePond
|
||||
|
||||
export default function fileUploadFormComponent({
|
||||
acceptedFileTypes,
|
||||
imageEditorEmptyFillColor,
|
||||
imageEditorMode,
|
||||
imageEditorViewportHeight,
|
||||
imageEditorViewportWidth,
|
||||
deleteUploadedFileUsing,
|
||||
isDeletable,
|
||||
isDisabled,
|
||||
getUploadedFilesUsing,
|
||||
imageCropAspectRatio,
|
||||
imagePreviewHeight,
|
||||
imageResizeMode,
|
||||
imageResizeTargetHeight,
|
||||
imageResizeTargetWidth,
|
||||
imageResizeUpscale,
|
||||
isAvatar,
|
||||
hasImageEditor,
|
||||
hasCircleCropper,
|
||||
canEditSvgs,
|
||||
isSvgEditingConfirmed,
|
||||
confirmSvgEditingMessage,
|
||||
disabledSvgEditingMessage,
|
||||
isDownloadable,
|
||||
isMultiple,
|
||||
isOpenable,
|
||||
isPreviewable,
|
||||
isReorderable,
|
||||
itemPanelAspectRatio,
|
||||
loadingIndicatorPosition,
|
||||
locale,
|
||||
maxFiles,
|
||||
maxSize,
|
||||
minSize,
|
||||
panelAspectRatio,
|
||||
panelLayout,
|
||||
placeholder,
|
||||
removeUploadedFileButtonPosition,
|
||||
removeUploadedFileUsing,
|
||||
reorderUploadedFilesUsing,
|
||||
shouldAppendFiles,
|
||||
shouldOrientImageFromExif,
|
||||
shouldTransformImage,
|
||||
state,
|
||||
uploadButtonPosition,
|
||||
uploadingMessage,
|
||||
uploadProgressIndicatorPosition,
|
||||
uploadUsing,
|
||||
}) {
|
||||
return {
|
||||
fileKeyIndex: {},
|
||||
|
||||
pond: null,
|
||||
|
||||
shouldUpdateState: true,
|
||||
|
||||
state,
|
||||
|
||||
lastState: null,
|
||||
|
||||
uploadedFileIndex: {},
|
||||
|
||||
isEditorOpen: false,
|
||||
|
||||
editingFile: {},
|
||||
|
||||
currentRatio: '',
|
||||
|
||||
editor: {},
|
||||
|
||||
init: async function () {
|
||||
FilePond.setOptions(locales[locale] ?? locales['en'])
|
||||
|
||||
this.pond = FilePond.create(this.$refs.input, {
|
||||
acceptedFileTypes,
|
||||
allowImageExifOrientation: shouldOrientImageFromExif,
|
||||
allowPaste: false,
|
||||
allowRemove: isDeletable,
|
||||
allowReorder: isReorderable,
|
||||
allowImagePreview: isPreviewable,
|
||||
allowVideoPreview: isPreviewable,
|
||||
allowAudioPreview: isPreviewable,
|
||||
allowImageTransform: shouldTransformImage,
|
||||
credits: false,
|
||||
files: await this.getFiles(),
|
||||
imageCropAspectRatio,
|
||||
imagePreviewHeight,
|
||||
imageResizeTargetHeight,
|
||||
imageResizeTargetWidth,
|
||||
imageResizeMode,
|
||||
imageResizeUpscale,
|
||||
itemInsertLocation: shouldAppendFiles ? 'after' : 'before',
|
||||
...(placeholder && { labelIdle: placeholder }),
|
||||
maxFiles,
|
||||
maxFileSize: maxSize,
|
||||
minFileSize: minSize,
|
||||
styleButtonProcessItemPosition: uploadButtonPosition,
|
||||
styleButtonRemoveItemPosition: removeUploadedFileButtonPosition,
|
||||
styleItemPanelAspectRatio: itemPanelAspectRatio,
|
||||
styleLoadIndicatorPosition: loadingIndicatorPosition,
|
||||
stylePanelAspectRatio: panelAspectRatio,
|
||||
stylePanelLayout: panelLayout,
|
||||
styleProgressIndicatorPosition: uploadProgressIndicatorPosition,
|
||||
server: {
|
||||
load: async (source, load) => {
|
||||
let response = await fetch(source, {
|
||||
cache: 'no-store',
|
||||
})
|
||||
let blob = await response.blob()
|
||||
|
||||
load(blob)
|
||||
},
|
||||
process: (
|
||||
fieldName,
|
||||
file,
|
||||
metadata,
|
||||
load,
|
||||
error,
|
||||
progress,
|
||||
) => {
|
||||
this.shouldUpdateState = false
|
||||
|
||||
let fileKey = (
|
||||
[1e7] +
|
||||
-1e3 +
|
||||
-4e3 +
|
||||
-8e3 +
|
||||
-1e11
|
||||
).replace(/[018]/g, (c) =>
|
||||
(
|
||||
c ^
|
||||
(crypto.getRandomValues(new Uint8Array(1))[0] &
|
||||
(15 >> (c / 4)))
|
||||
).toString(16),
|
||||
)
|
||||
|
||||
uploadUsing(
|
||||
fileKey,
|
||||
file,
|
||||
(fileKey) => {
|
||||
this.shouldUpdateState = true
|
||||
|
||||
load(fileKey)
|
||||
},
|
||||
error,
|
||||
progress,
|
||||
)
|
||||
},
|
||||
remove: async (source, load) => {
|
||||
let fileKey = this.uploadedFileIndex[source] ?? null
|
||||
|
||||
if (!fileKey) {
|
||||
return
|
||||
}
|
||||
|
||||
await deleteUploadedFileUsing(fileKey)
|
||||
|
||||
load()
|
||||
},
|
||||
revert: async (uniqueFileId, load) => {
|
||||
await removeUploadedFileUsing(uniqueFileId)
|
||||
|
||||
load()
|
||||
},
|
||||
},
|
||||
allowImageEdit: hasImageEditor,
|
||||
imageEditEditor: {
|
||||
open: (file) => this.loadEditor(file),
|
||||
onconfirm: () => {},
|
||||
oncancel: () => this.closeEditor(),
|
||||
onclose: () => this.closeEditor(),
|
||||
},
|
||||
})
|
||||
|
||||
this.$watch('state', async () => {
|
||||
if (!this.pond) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.shouldUpdateState) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.state === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
// We don't want to overwrite the files that are already in the input, if they haven't been saved yet.
|
||||
if (
|
||||
this.state !== null &&
|
||||
Object.values(this.state).filter((file) =>
|
||||
file.startsWith('livewire-file:'),
|
||||
).length
|
||||
) {
|
||||
this.lastState = null
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Don't do anything if the state hasn't changed
|
||||
if (JSON.stringify(this.state) === this.lastState) {
|
||||
return
|
||||
}
|
||||
|
||||
this.lastState = JSON.stringify(this.state)
|
||||
|
||||
this.pond.files = await this.getFiles()
|
||||
})
|
||||
|
||||
this.pond.on('reorderfiles', async (files) => {
|
||||
const orderedFileKeys = files
|
||||
.map((file) =>
|
||||
file.source instanceof File
|
||||
? file.serverId
|
||||
: this.uploadedFileIndex[file.source] ?? null,
|
||||
) // file.serverId is null for a file that is not yet uploaded
|
||||
.filter((fileKey) => fileKey)
|
||||
|
||||
await reorderUploadedFilesUsing(
|
||||
shouldAppendFiles
|
||||
? orderedFileKeys
|
||||
: orderedFileKeys.reverse(),
|
||||
)
|
||||
})
|
||||
|
||||
this.pond.on('initfile', async (fileItem) => {
|
||||
if (!isDownloadable) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isAvatar) {
|
||||
return
|
||||
}
|
||||
|
||||
this.insertDownloadLink(fileItem)
|
||||
})
|
||||
|
||||
this.pond.on('initfile', async (fileItem) => {
|
||||
if (!isOpenable) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isAvatar) {
|
||||
return
|
||||
}
|
||||
|
||||
this.insertOpenLink(fileItem)
|
||||
})
|
||||
|
||||
this.pond.on('addfilestart', async (file) => {
|
||||
if (file.status !== FilePond.FileStatus.PROCESSING_QUEUED) {
|
||||
return
|
||||
}
|
||||
|
||||
this.dispatchFormEvent('form-processing-started', {
|
||||
message: uploadingMessage,
|
||||
})
|
||||
})
|
||||
|
||||
const handleFileProcessing = async () => {
|
||||
if (
|
||||
this.pond
|
||||
.getFiles()
|
||||
.filter(
|
||||
(file) =>
|
||||
file.status ===
|
||||
FilePond.FileStatus.PROCESSING ||
|
||||
file.status ===
|
||||
FilePond.FileStatus.PROCESSING_QUEUED,
|
||||
).length
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
this.dispatchFormEvent('form-processing-finished')
|
||||
}
|
||||
|
||||
this.pond.on('processfile', handleFileProcessing)
|
||||
|
||||
this.pond.on('processfileabort', handleFileProcessing)
|
||||
|
||||
this.pond.on('processfilerevert', handleFileProcessing)
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
this.destroyEditor()
|
||||
|
||||
FilePond.destroy(this.$refs.input)
|
||||
this.pond = null
|
||||
},
|
||||
|
||||
dispatchFormEvent: function (name, detail = {}) {
|
||||
this.$el.closest('form')?.dispatchEvent(
|
||||
new CustomEvent(name, {
|
||||
composed: true,
|
||||
cancelable: true,
|
||||
detail,
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
||||
getUploadedFiles: async function () {
|
||||
const uploadedFiles = await getUploadedFilesUsing()
|
||||
|
||||
this.fileKeyIndex = uploadedFiles ?? {}
|
||||
|
||||
this.uploadedFileIndex = Object.entries(this.fileKeyIndex)
|
||||
.filter(([key, value]) => value?.url)
|
||||
.reduce((obj, [key, value]) => {
|
||||
obj[value.url] = key
|
||||
|
||||
return obj
|
||||
}, {})
|
||||
},
|
||||
|
||||
getFiles: async function () {
|
||||
await this.getUploadedFiles()
|
||||
|
||||
let files = []
|
||||
|
||||
for (const uploadedFile of Object.values(this.fileKeyIndex)) {
|
||||
if (!uploadedFile) {
|
||||
continue
|
||||
}
|
||||
|
||||
files.push({
|
||||
source: uploadedFile.url,
|
||||
options: {
|
||||
type: 'local',
|
||||
...(!uploadedFile.type ||
|
||||
(isPreviewable &&
|
||||
(/^audio/.test(uploadedFile.type) ||
|
||||
/^image/.test(uploadedFile.type) ||
|
||||
/^video/.test(uploadedFile.type)))
|
||||
? {}
|
||||
: {
|
||||
file: {
|
||||
name: uploadedFile.name,
|
||||
size: uploadedFile.size,
|
||||
type: uploadedFile.type,
|
||||
},
|
||||
}),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return shouldAppendFiles ? files : files.reverse()
|
||||
},
|
||||
|
||||
insertDownloadLink: function (file) {
|
||||
if (file.origin !== FilePond.FileOrigin.LOCAL) {
|
||||
return
|
||||
}
|
||||
|
||||
const anchor = this.getDownloadLink(file)
|
||||
|
||||
if (!anchor) {
|
||||
return
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById(`filepond--item-${file.id}`)
|
||||
.querySelector('.filepond--file-info-main')
|
||||
.prepend(anchor)
|
||||
},
|
||||
|
||||
insertOpenLink: function (file) {
|
||||
if (file.origin !== FilePond.FileOrigin.LOCAL) {
|
||||
return
|
||||
}
|
||||
|
||||
const anchor = this.getOpenLink(file)
|
||||
|
||||
if (!anchor) {
|
||||
return
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById(`filepond--item-${file.id}`)
|
||||
.querySelector('.filepond--file-info-main')
|
||||
.prepend(anchor)
|
||||
},
|
||||
|
||||
getDownloadLink: function (file) {
|
||||
let fileSource = file.source
|
||||
|
||||
if (!fileSource) {
|
||||
return
|
||||
}
|
||||
|
||||
const anchor = document.createElement('a')
|
||||
anchor.className = 'filepond--download-icon'
|
||||
anchor.href = fileSource
|
||||
anchor.download = file.file.name
|
||||
|
||||
return anchor
|
||||
},
|
||||
|
||||
getOpenLink: function (file) {
|
||||
let fileSource = file.source
|
||||
|
||||
if (!fileSource) {
|
||||
return
|
||||
}
|
||||
|
||||
const anchor = document.createElement('a')
|
||||
anchor.className = 'filepond--open-icon'
|
||||
anchor.href = fileSource
|
||||
anchor.target = '_blank'
|
||||
|
||||
return anchor
|
||||
},
|
||||
|
||||
initEditor: function () {
|
||||
if (isDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!hasImageEditor) {
|
||||
return
|
||||
}
|
||||
|
||||
this.editor = new Cropper(this.$refs.editor, {
|
||||
aspectRatio:
|
||||
imageEditorViewportWidth / imageEditorViewportHeight,
|
||||
autoCropArea: 1,
|
||||
center: true,
|
||||
crop: (event) => {
|
||||
this.$refs.xPositionInput.value = Math.round(event.detail.x)
|
||||
this.$refs.yPositionInput.value = Math.round(event.detail.y)
|
||||
this.$refs.heightInput.value = Math.round(
|
||||
event.detail.height,
|
||||
)
|
||||
this.$refs.widthInput.value = Math.round(event.detail.width)
|
||||
this.$refs.rotationInput.value = event.detail.rotate
|
||||
},
|
||||
cropBoxResizable: true,
|
||||
guides: true,
|
||||
highlight: true,
|
||||
responsive: true,
|
||||
toggleDragModeOnDblclick: true,
|
||||
viewMode: imageEditorMode,
|
||||
wheelZoomRatio: 0.02,
|
||||
})
|
||||
},
|
||||
|
||||
closeEditor: function () {
|
||||
this.editingFile = {}
|
||||
|
||||
this.isEditorOpen = false
|
||||
|
||||
this.destroyEditor()
|
||||
},
|
||||
|
||||
fixImageDimensions: function (file, callback) {
|
||||
if (file.type !== 'image/svg+xml') {
|
||||
return callback(file)
|
||||
}
|
||||
|
||||
const svgReader = new FileReader()
|
||||
|
||||
svgReader.onload = (event) => {
|
||||
const svgElement = new DOMParser()
|
||||
.parseFromString(event.target.result, 'image/svg+xml')
|
||||
?.querySelector('svg')
|
||||
|
||||
if (!svgElement) {
|
||||
return callback(file)
|
||||
}
|
||||
|
||||
const viewBoxAttribute = ['viewBox', 'ViewBox', 'viewbox'].find(
|
||||
(attribute) => svgElement.hasAttribute(attribute),
|
||||
)
|
||||
|
||||
if (!viewBoxAttribute) {
|
||||
return callback(file)
|
||||
}
|
||||
|
||||
const viewBox = svgElement
|
||||
.getAttribute(viewBoxAttribute)
|
||||
.split(' ')
|
||||
|
||||
if (!viewBox || viewBox.length !== 4) {
|
||||
return callback(file)
|
||||
}
|
||||
|
||||
svgElement.setAttribute('width', parseFloat(viewBox[2]) + 'pt')
|
||||
svgElement.setAttribute('height', parseFloat(viewBox[3]) + 'pt')
|
||||
|
||||
return callback(
|
||||
new File(
|
||||
[
|
||||
new Blob(
|
||||
[
|
||||
new XMLSerializer().serializeToString(
|
||||
svgElement,
|
||||
),
|
||||
],
|
||||
{ type: 'image/svg+xml' },
|
||||
),
|
||||
],
|
||||
file.name,
|
||||
{
|
||||
type: 'image/svg+xml',
|
||||
_relativePath: '',
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
svgReader.readAsText(file)
|
||||
},
|
||||
|
||||
loadEditor: function (file) {
|
||||
if (isDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!hasImageEditor) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!file) {
|
||||
return
|
||||
}
|
||||
|
||||
const isFileSvg = file.type === 'image/svg+xml'
|
||||
|
||||
if (!canEditSvgs && isFileSvg) {
|
||||
alert(disabledSvgEditingMessage)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
isSvgEditingConfirmed &&
|
||||
isFileSvg &&
|
||||
!confirm(confirmSvgEditingMessage)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
this.fixImageDimensions(file, (editingFile) => {
|
||||
this.editingFile = editingFile
|
||||
|
||||
this.initEditor()
|
||||
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onload = (event) => {
|
||||
this.isEditorOpen = true
|
||||
|
||||
setTimeout(
|
||||
() => this.editor.replace(event.target.result),
|
||||
200,
|
||||
)
|
||||
}
|
||||
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
},
|
||||
|
||||
getRoundedCanvas: function (sourceCanvas) {
|
||||
let width = sourceCanvas.width
|
||||
let height = sourceCanvas.height
|
||||
|
||||
let canvas = document.createElement('canvas')
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
|
||||
let context = canvas.getContext('2d')
|
||||
context.imageSmoothingEnabled = true
|
||||
context.drawImage(sourceCanvas, 0, 0, width, height)
|
||||
context.globalCompositeOperation = 'destination-in'
|
||||
context.beginPath()
|
||||
context.ellipse(
|
||||
width / 2,
|
||||
height / 2,
|
||||
width / 2,
|
||||
height / 2,
|
||||
0,
|
||||
0,
|
||||
2 * Math.PI,
|
||||
)
|
||||
context.fill()
|
||||
|
||||
return canvas
|
||||
},
|
||||
|
||||
saveEditor: function () {
|
||||
if (isDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!hasImageEditor) {
|
||||
return
|
||||
}
|
||||
|
||||
let croppedCanvas = this.editor.getCroppedCanvas({
|
||||
fillColor: imageEditorEmptyFillColor ?? 'transparent',
|
||||
height: imageResizeTargetHeight,
|
||||
imageSmoothingEnabled: true,
|
||||
imageSmoothingQuality: 'high',
|
||||
width: imageResizeTargetWidth,
|
||||
})
|
||||
|
||||
if (hasCircleCropper) {
|
||||
croppedCanvas = this.getRoundedCanvas(croppedCanvas)
|
||||
}
|
||||
|
||||
croppedCanvas.toBlob(
|
||||
(croppedImage) => {
|
||||
if (isMultiple) {
|
||||
this.pond.removeFile(
|
||||
this.pond
|
||||
.getFiles()
|
||||
.find(
|
||||
(uploadedFile) =>
|
||||
uploadedFile.filename ===
|
||||
this.editingFile.name,
|
||||
)?.id,
|
||||
{ revert: true },
|
||||
)
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.shouldUpdateState = false
|
||||
|
||||
let editingFileName = this.editingFile.name.slice(
|
||||
0,
|
||||
this.editingFile.name.lastIndexOf('.'),
|
||||
)
|
||||
let editingFileExtension = this.editingFile.name
|
||||
.split('.')
|
||||
.pop()
|
||||
|
||||
if (editingFileExtension === 'svg') {
|
||||
editingFileExtension = 'png'
|
||||
}
|
||||
|
||||
const fileNameVersionRegex = /-v(\d+)/
|
||||
|
||||
if (fileNameVersionRegex.test(editingFileName)) {
|
||||
editingFileName = editingFileName.replace(
|
||||
fileNameVersionRegex,
|
||||
(match, number) => {
|
||||
const newNumber = Number(number) + 1
|
||||
|
||||
return `-v${newNumber}`
|
||||
},
|
||||
)
|
||||
} else {
|
||||
editingFileName += '-v1'
|
||||
}
|
||||
|
||||
this.pond
|
||||
.addFile(
|
||||
new File(
|
||||
[croppedImage],
|
||||
`${editingFileName}.${editingFileExtension}`,
|
||||
{
|
||||
type:
|
||||
this.editingFile.type ===
|
||||
'image/svg+xml' ||
|
||||
hasCircleCropper
|
||||
? 'image/png'
|
||||
: this.editingFile.type,
|
||||
lastModified: new Date().getTime(),
|
||||
},
|
||||
),
|
||||
)
|
||||
.then(() => {
|
||||
this.closeEditor()
|
||||
})
|
||||
.catch(() => {
|
||||
this.closeEditor()
|
||||
})
|
||||
})
|
||||
},
|
||||
hasCircleCropper ? 'image/png' : this.editingFile.type,
|
||||
)
|
||||
},
|
||||
|
||||
destroyEditor: function () {
|
||||
if (this.editor && typeof this.editor.destroy === 'function') {
|
||||
this.editor.destroy()
|
||||
}
|
||||
|
||||
this.editor = null
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
import ar from 'filepond/locale/ar-ar'
|
||||
import ca from 'filepond/locale/ca-ca'
|
||||
import ckb from 'filepond/locale/ku-ckb'
|
||||
import cs from 'filepond/locale/cs-cz'
|
||||
import da from 'filepond/locale/da-dk'
|
||||
import de from 'filepond/locale/de-de'
|
||||
import en from 'filepond/locale/en-en'
|
||||
import es from 'filepond/locale/es-es'
|
||||
import fa from 'filepond/locale/fa_ir'
|
||||
import fi from 'filepond/locale/fi-fi'
|
||||
import fr from 'filepond/locale/fr-fr'
|
||||
import hu from 'filepond/locale/hu-hu'
|
||||
import id from 'filepond/locale/id-id'
|
||||
import it from 'filepond/locale/it-it'
|
||||
import km from 'filepond/locale/km-km'
|
||||
import nl from 'filepond/locale/nl-nl'
|
||||
import no from 'filepond/locale/no_nb'
|
||||
import pl from 'filepond/locale/pl-pl'
|
||||
import pt_BR from 'filepond/locale/pt-br'
|
||||
import pt_PT from 'filepond/locale/pt-br'
|
||||
import ro from 'filepond/locale/ro-ro'
|
||||
import ru from 'filepond/locale/ru-ru'
|
||||
import sv from 'filepond/locale/sv_se'
|
||||
import tr from 'filepond/locale/tr-tr'
|
||||
import uk from 'filepond/locale/uk-ua'
|
||||
import vi from 'filepond/locale/vi-vi'
|
||||
import zh_CN from 'filepond/locale/zh-cn'
|
||||
import zh_TW from 'filepond/locale/zh-tw'
|
||||
|
||||
const locales = {
|
||||
ar,
|
||||
ca,
|
||||
ckb,
|
||||
cs,
|
||||
da,
|
||||
de,
|
||||
en,
|
||||
es,
|
||||
fa,
|
||||
fi,
|
||||
fr,
|
||||
hu,
|
||||
id,
|
||||
it,
|
||||
km,
|
||||
nl,
|
||||
no,
|
||||
pl,
|
||||
pt_BR,
|
||||
pt_PT,
|
||||
ro,
|
||||
ru,
|
||||
sv,
|
||||
tr,
|
||||
uk,
|
||||
vi,
|
||||
zh_CN,
|
||||
zh_TW,
|
||||
}
|
||||
114
vendor/filament/forms/resources/js/components/key-value.js
vendored
Normal file
114
vendor/filament/forms/resources/js/components/key-value.js
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
export default function keyValueFormComponent({ state }) {
|
||||
return {
|
||||
state,
|
||||
|
||||
rows: [],
|
||||
|
||||
shouldUpdateRows: true,
|
||||
|
||||
init: function () {
|
||||
this.updateRows()
|
||||
|
||||
if (this.rows.length <= 0) {
|
||||
this.rows.push({ key: '', value: '' })
|
||||
} else {
|
||||
this.updateState()
|
||||
}
|
||||
|
||||
this.$watch('state', (state, oldState) => {
|
||||
const getLength = (value) => {
|
||||
if (value === null) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.length
|
||||
}
|
||||
|
||||
if (typeof value !== 'object') {
|
||||
return 0
|
||||
}
|
||||
|
||||
return Object.keys(value).length
|
||||
}
|
||||
|
||||
if (getLength(state) === 0 && getLength(oldState) === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.updateRows()
|
||||
})
|
||||
},
|
||||
|
||||
addRow: function () {
|
||||
this.rows.push({ key: '', value: '' })
|
||||
|
||||
this.updateState()
|
||||
},
|
||||
|
||||
deleteRow: function (index) {
|
||||
this.rows.splice(index, 1)
|
||||
|
||||
if (this.rows.length <= 0) {
|
||||
this.addRow()
|
||||
}
|
||||
|
||||
this.updateState()
|
||||
},
|
||||
|
||||
reorderRows: function (event) {
|
||||
const rows = Alpine.raw(this.rows)
|
||||
|
||||
this.rows = []
|
||||
|
||||
const reorderedRow = rows.splice(event.oldIndex, 1)[0]
|
||||
rows.splice(event.newIndex, 0, reorderedRow)
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.rows = rows
|
||||
|
||||
this.updateState()
|
||||
})
|
||||
},
|
||||
|
||||
updateRows: function () {
|
||||
if (!this.shouldUpdateRows) {
|
||||
this.shouldUpdateRows = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let rows = []
|
||||
|
||||
for (let [key, value] of Object.entries(this.state ?? {})) {
|
||||
rows.push({
|
||||
key,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
this.rows = rows
|
||||
},
|
||||
|
||||
updateState: function () {
|
||||
let state = {}
|
||||
|
||||
this.rows.forEach((row) => {
|
||||
if (row.key === '' || row.key === null) {
|
||||
return
|
||||
}
|
||||
|
||||
state[row.key] = row.value
|
||||
})
|
||||
|
||||
// This is a hack to prevent the component from updating rows again
|
||||
// after a state update, which would otherwise be done by the `state`
|
||||
// watcher. If rows are updated again, duplicate keys are removed.
|
||||
//
|
||||
// https://github.com/filamentphp/filament/issues/1107
|
||||
this.shouldUpdateRows = false
|
||||
|
||||
this.state = state
|
||||
},
|
||||
}
|
||||
}
|
||||
375
vendor/filament/forms/resources/js/components/markdown-editor.js
vendored
Normal file
375
vendor/filament/forms/resources/js/components/markdown-editor.js
vendored
Normal file
@@ -0,0 +1,375 @@
|
||||
window.CodeMirror = require('codemirror/lib/codemirror')
|
||||
|
||||
require('codemirror')
|
||||
require('codemirror/addon/mode/overlay')
|
||||
require('codemirror/addon/edit/continuelist')
|
||||
require('codemirror/addon/display/placeholder')
|
||||
require('codemirror/addon/selection/mark-selection')
|
||||
require('codemirror/addon/search/searchcursor')
|
||||
require('codemirror/mode/clike/clike')
|
||||
require('codemirror/mode/cmake/cmake')
|
||||
require('codemirror/mode/css/css')
|
||||
require('codemirror/mode/diff/diff')
|
||||
require('codemirror/mode/django/django')
|
||||
require('codemirror/mode/dockerfile/dockerfile')
|
||||
require('codemirror/mode/gfm/gfm')
|
||||
require('codemirror/mode/go/go')
|
||||
require('codemirror/mode/htmlmixed/htmlmixed')
|
||||
require('codemirror/mode/http/http')
|
||||
require('codemirror/mode/javascript/javascript')
|
||||
require('codemirror/mode/jinja2/jinja2')
|
||||
require('codemirror/mode/jsx/jsx')
|
||||
require('codemirror/mode/markdown/markdown')
|
||||
require('codemirror/mode/nginx/nginx')
|
||||
require('codemirror/mode/pascal/pascal')
|
||||
require('codemirror/mode/perl/perl')
|
||||
require('codemirror/mode/php/php')
|
||||
require('codemirror/mode/protobuf/protobuf')
|
||||
require('codemirror/mode/python/python')
|
||||
require('codemirror/mode/ruby/ruby')
|
||||
require('codemirror/mode/rust/rust')
|
||||
require('codemirror/mode/sass/sass')
|
||||
require('codemirror/mode/shell/shell')
|
||||
require('codemirror/mode/sql/sql')
|
||||
require('codemirror/mode/stylus/stylus')
|
||||
require('codemirror/mode/swift/swift')
|
||||
require('codemirror/mode/vue/vue')
|
||||
require('codemirror/mode/xml/xml')
|
||||
require('codemirror/mode/yaml/yaml')
|
||||
|
||||
require('./markdown-editor/EasyMDE')
|
||||
|
||||
CodeMirror.commands.tabAndIndentMarkdownList = function (codemirror) {
|
||||
var ranges = codemirror.listSelections()
|
||||
var pos = ranges[0].head
|
||||
var eolState = codemirror.getStateAfter(pos.line)
|
||||
var inList = eolState.list !== false
|
||||
|
||||
if (inList) {
|
||||
codemirror.execCommand('indentMore')
|
||||
return
|
||||
}
|
||||
|
||||
if (codemirror.options.indentWithTabs) {
|
||||
codemirror.execCommand('insertTab')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var spaces = Array(codemirror.options.tabSize + 1).join(' ')
|
||||
codemirror.replaceSelection(spaces)
|
||||
}
|
||||
|
||||
CodeMirror.commands.shiftTabAndUnindentMarkdownList = function (codemirror) {
|
||||
var ranges = codemirror.listSelections()
|
||||
var pos = ranges[0].head
|
||||
var eolState = codemirror.getStateAfter(pos.line)
|
||||
var inList = eolState.list !== false
|
||||
|
||||
if (inList) {
|
||||
codemirror.execCommand('indentLess')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (codemirror.options.indentWithTabs) {
|
||||
codemirror.execCommand('insertTab')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var spaces = Array(codemirror.options.tabSize + 1).join(' ')
|
||||
codemirror.replaceSelection(spaces)
|
||||
}
|
||||
|
||||
export default function markdownEditorFormComponent({
|
||||
canAttachFiles,
|
||||
isLiveDebounced,
|
||||
isLiveOnBlur,
|
||||
liveDebounce,
|
||||
maxHeight,
|
||||
minHeight,
|
||||
placeholder,
|
||||
setUpUsing,
|
||||
state,
|
||||
translations,
|
||||
toolbarButtons,
|
||||
uploadFileAttachmentUsing,
|
||||
}) {
|
||||
return {
|
||||
editor: null,
|
||||
|
||||
state,
|
||||
|
||||
init: async function () {
|
||||
if (this.$root._editor) {
|
||||
this.$root._editor.toTextArea()
|
||||
this.$root._editor = null
|
||||
}
|
||||
|
||||
this.$root._editor = this.editor = new EasyMDE({
|
||||
autoDownloadFontAwesome: false,
|
||||
autoRefresh: true,
|
||||
autoSave: false,
|
||||
element: this.$refs.editor,
|
||||
imageAccept: 'image/png, image/jpeg, image/gif, image/avif',
|
||||
imageUploadFunction: uploadFileAttachmentUsing,
|
||||
initialValue: this.state ?? '',
|
||||
maxHeight,
|
||||
minHeight,
|
||||
placeholder,
|
||||
previewImagesInEditor: true,
|
||||
spellChecker: false,
|
||||
status: [
|
||||
{
|
||||
className: 'upload-image',
|
||||
defaultValue: '',
|
||||
},
|
||||
],
|
||||
toolbar: this.getToolbar(),
|
||||
uploadImage: canAttachFiles,
|
||||
})
|
||||
|
||||
this.editor.codemirror.setOption(
|
||||
'direction',
|
||||
document.documentElement?.dir ?? 'ltr',
|
||||
)
|
||||
|
||||
// When creating a link, highlight the URL instead of the label:
|
||||
this.editor.codemirror.on('changes', (instance, changes) => {
|
||||
try {
|
||||
const lastChange = changes[changes.length - 1]
|
||||
|
||||
if (lastChange.origin === '+input') {
|
||||
const urlPlaceholder = '(https://)'
|
||||
const urlLineText =
|
||||
lastChange.text[lastChange.text.length - 1]
|
||||
|
||||
if (
|
||||
urlLineText.endsWith(urlPlaceholder) &&
|
||||
urlLineText !== '[]' + urlPlaceholder
|
||||
) {
|
||||
const from = lastChange.from
|
||||
const to = lastChange.to
|
||||
const isSelectionMultiline =
|
||||
lastChange.text.length > 1
|
||||
const baseIndex = isSelectionMultiline ? 0 : from.ch
|
||||
|
||||
setTimeout(() => {
|
||||
instance.setSelection(
|
||||
{
|
||||
line: to.line,
|
||||
ch:
|
||||
baseIndex +
|
||||
urlLineText.lastIndexOf('(') +
|
||||
1,
|
||||
},
|
||||
{
|
||||
line: to.line,
|
||||
ch:
|
||||
baseIndex +
|
||||
urlLineText.lastIndexOf(')'),
|
||||
},
|
||||
)
|
||||
}, 25)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Revert to original behavior.
|
||||
}
|
||||
})
|
||||
|
||||
this.editor.codemirror.on(
|
||||
'change',
|
||||
Alpine.debounce(() => {
|
||||
if (!this.editor) {
|
||||
return
|
||||
}
|
||||
|
||||
this.state = this.editor.value()
|
||||
|
||||
if (isLiveDebounced) {
|
||||
this.$wire.call('$refresh')
|
||||
}
|
||||
}, liveDebounce ?? 300),
|
||||
)
|
||||
|
||||
if (isLiveOnBlur) {
|
||||
this.editor.codemirror.on('blur', () =>
|
||||
this.$wire.call('$refresh'),
|
||||
)
|
||||
}
|
||||
|
||||
this.$watch('state', () => {
|
||||
if (!this.editor) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.editor.codemirror.hasFocus()) {
|
||||
return
|
||||
}
|
||||
|
||||
Alpine.raw(this.editor).value(this.state ?? '')
|
||||
})
|
||||
|
||||
if (setUpUsing) {
|
||||
setUpUsing(this)
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
this.editor.cleanup()
|
||||
this.editor = null
|
||||
},
|
||||
|
||||
getToolbar: function () {
|
||||
let toolbar = []
|
||||
|
||||
if (toolbarButtons.includes('bold')) {
|
||||
toolbar.push({
|
||||
name: 'bold',
|
||||
action: EasyMDE.toggleBold,
|
||||
title: translations.toolbar_buttons?.bold,
|
||||
})
|
||||
}
|
||||
|
||||
if (toolbarButtons.includes('italic')) {
|
||||
toolbar.push({
|
||||
name: 'italic',
|
||||
action: EasyMDE.toggleItalic,
|
||||
title: translations.toolbar_buttons?.italic,
|
||||
})
|
||||
}
|
||||
|
||||
if (toolbarButtons.includes('strike')) {
|
||||
toolbar.push({
|
||||
name: 'strikethrough',
|
||||
action: EasyMDE.toggleStrikethrough,
|
||||
title: translations.toolbar_buttons?.strike,
|
||||
})
|
||||
}
|
||||
|
||||
if (toolbarButtons.includes('link')) {
|
||||
toolbar.push({
|
||||
name: 'link',
|
||||
action: EasyMDE.drawLink,
|
||||
title: translations.toolbar_buttons?.link,
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
['bold', 'italic', 'strike', 'link'].some((button) =>
|
||||
toolbarButtons.includes(button),
|
||||
) &&
|
||||
['heading'].some((button) => toolbarButtons.includes(button))
|
||||
) {
|
||||
toolbar.push('|')
|
||||
}
|
||||
|
||||
if (toolbarButtons.includes('heading')) {
|
||||
toolbar.push({
|
||||
name: 'heading',
|
||||
action: EasyMDE.toggleHeadingSmaller,
|
||||
title: translations.toolbar_buttons?.heading,
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
['heading'].some((button) => toolbarButtons.includes(button)) &&
|
||||
['blockquote', 'codeBlock', 'bulletList', 'orderedList'].some(
|
||||
(button) => toolbarButtons.includes(button),
|
||||
)
|
||||
) {
|
||||
toolbar.push('|')
|
||||
}
|
||||
|
||||
if (toolbarButtons.includes('blockquote')) {
|
||||
toolbar.push({
|
||||
name: 'quote',
|
||||
action: EasyMDE.toggleBlockquote,
|
||||
title: translations.toolbar_buttons?.blockquote,
|
||||
})
|
||||
}
|
||||
|
||||
if (toolbarButtons.includes('codeBlock')) {
|
||||
toolbar.push({
|
||||
name: 'code',
|
||||
action: EasyMDE.toggleCodeBlock,
|
||||
title: translations.toolbar_buttons?.code_block,
|
||||
})
|
||||
}
|
||||
|
||||
if (toolbarButtons.includes('bulletList')) {
|
||||
toolbar.push({
|
||||
name: 'unordered-list',
|
||||
action: EasyMDE.toggleUnorderedList,
|
||||
title: translations.toolbar_buttons?.bullet_list,
|
||||
})
|
||||
}
|
||||
|
||||
if (toolbarButtons.includes('orderedList')) {
|
||||
toolbar.push({
|
||||
name: 'ordered-list',
|
||||
action: EasyMDE.toggleOrderedList,
|
||||
title: translations.toolbar_buttons?.ordered_list,
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
['blockquote', 'codeBlock', 'bulletList', 'orderedList'].some(
|
||||
(button) => toolbarButtons.includes(button),
|
||||
) &&
|
||||
['table', 'attachFiles'].some((button) =>
|
||||
toolbarButtons.includes(button),
|
||||
)
|
||||
) {
|
||||
toolbar.push('|')
|
||||
}
|
||||
|
||||
if (toolbarButtons.includes('table')) {
|
||||
toolbar.push({
|
||||
name: 'table',
|
||||
action: EasyMDE.drawTable,
|
||||
title: translations.toolbar_buttons?.table,
|
||||
})
|
||||
}
|
||||
|
||||
if (toolbarButtons.includes('attachFiles')) {
|
||||
toolbar.push({
|
||||
name: 'upload-image',
|
||||
action: EasyMDE.drawUploadedImage,
|
||||
title: translations.toolbar_buttons?.attach_files,
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
['table', 'attachFiles'].some((button) =>
|
||||
toolbarButtons.includes(button),
|
||||
) &&
|
||||
['undo', 'redo'].some((button) =>
|
||||
toolbarButtons.includes(button),
|
||||
)
|
||||
) {
|
||||
toolbar.push('|')
|
||||
}
|
||||
|
||||
if (toolbarButtons.includes('undo')) {
|
||||
toolbar.push({
|
||||
name: 'undo',
|
||||
action: EasyMDE.undo,
|
||||
title: translations.toolbar_buttons?.undo,
|
||||
})
|
||||
}
|
||||
|
||||
if (toolbarButtons.includes('redo')) {
|
||||
toolbar.push({
|
||||
name: 'redo',
|
||||
action: EasyMDE.redo,
|
||||
title: translations.toolbar_buttons?.redo,
|
||||
})
|
||||
}
|
||||
|
||||
return toolbar
|
||||
},
|
||||
}
|
||||
}
|
||||
3467
vendor/filament/forms/resources/js/components/markdown-editor/EasyMDE.js
vendored
Normal file
3467
vendor/filament/forms/resources/js/components/markdown-editor/EasyMDE.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
69
vendor/filament/forms/resources/js/components/rich-editor.js
vendored
Normal file
69
vendor/filament/forms/resources/js/components/rich-editor.js
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
import Trix from 'trix'
|
||||
|
||||
Trix.config.blockAttributes.default.tagName = 'p'
|
||||
|
||||
Trix.config.blockAttributes.default.breakOnReturn = true
|
||||
|
||||
Trix.config.blockAttributes.heading = {
|
||||
tagName: 'h2',
|
||||
terminal: true,
|
||||
breakOnReturn: true,
|
||||
group: false,
|
||||
}
|
||||
|
||||
Trix.config.blockAttributes.subHeading = {
|
||||
tagName: 'h3',
|
||||
terminal: true,
|
||||
breakOnReturn: true,
|
||||
group: false,
|
||||
}
|
||||
|
||||
Trix.config.textAttributes.underline = {
|
||||
style: { textDecoration: 'underline' },
|
||||
inheritable: true,
|
||||
parser: (element) => {
|
||||
const style = window.getComputedStyle(element)
|
||||
|
||||
return style.textDecoration.includes('underline')
|
||||
},
|
||||
}
|
||||
|
||||
Trix.Block.prototype.breaksOnReturn = function () {
|
||||
const lastAttribute = this.getLastAttribute()
|
||||
const blockConfig =
|
||||
Trix.config.blockAttributes[lastAttribute ? lastAttribute : 'default']
|
||||
|
||||
return blockConfig?.breakOnReturn ?? false
|
||||
}
|
||||
|
||||
Trix.LineBreakInsertion.prototype.shouldInsertBlockBreak = function () {
|
||||
if (
|
||||
this.block.hasAttributes() &&
|
||||
this.block.isListItem() &&
|
||||
!this.block.isEmpty()
|
||||
) {
|
||||
return this.startLocation.offset > 0
|
||||
} else {
|
||||
return !this.shouldBreakFormattedBlock() ? this.breaksOnReturn : false
|
||||
}
|
||||
}
|
||||
|
||||
export default function richEditorFormComponent({ state }) {
|
||||
return {
|
||||
state,
|
||||
|
||||
init: function () {
|
||||
this.$refs.trixValue.value = this.state
|
||||
this.$refs.trix.editor?.loadHTML(this.state)
|
||||
|
||||
this.$watch('state', () => {
|
||||
if (document.activeElement === this.$refs.trix) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$refs.trixValue.value = this.state
|
||||
this.$refs.trix.editor?.loadHTML(this.state)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
314
vendor/filament/forms/resources/js/components/select.js
vendored
Normal file
314
vendor/filament/forms/resources/js/components/select.js
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
import Choices from 'choices.js'
|
||||
|
||||
export default function selectFormComponent({
|
||||
canSelectPlaceholder,
|
||||
isHtmlAllowed,
|
||||
getOptionLabelUsing,
|
||||
getOptionLabelsUsing,
|
||||
getOptionsUsing,
|
||||
getSearchResultsUsing,
|
||||
isAutofocused,
|
||||
isMultiple,
|
||||
isSearchable,
|
||||
hasDynamicOptions,
|
||||
hasDynamicSearchResults,
|
||||
livewireId,
|
||||
loadingMessage,
|
||||
maxItems,
|
||||
maxItemsMessage,
|
||||
noSearchResultsMessage,
|
||||
options,
|
||||
optionsLimit,
|
||||
placeholder,
|
||||
position,
|
||||
searchDebounce,
|
||||
searchingMessage,
|
||||
searchPrompt,
|
||||
searchableOptionFields,
|
||||
state,
|
||||
statePath,
|
||||
}) {
|
||||
return {
|
||||
isSearching: false,
|
||||
|
||||
select: null,
|
||||
|
||||
selectedOptions: [],
|
||||
|
||||
isStateBeingUpdated: false,
|
||||
|
||||
state,
|
||||
|
||||
init: async function () {
|
||||
this.select = new Choices(this.$refs.input, {
|
||||
allowHTML: isHtmlAllowed,
|
||||
duplicateItemsAllowed: false,
|
||||
itemSelectText: '',
|
||||
loadingText: loadingMessage,
|
||||
maxItemCount: maxItems ?? -1,
|
||||
maxItemText: (maxItemCount) =>
|
||||
window.pluralize(maxItemsMessage, maxItemCount, {
|
||||
count: maxItemCount,
|
||||
}),
|
||||
noChoicesText: searchPrompt,
|
||||
noResultsText: noSearchResultsMessage,
|
||||
placeholderValue: placeholder,
|
||||
position: position ?? 'auto',
|
||||
removeItemButton: canSelectPlaceholder,
|
||||
renderChoiceLimit: optionsLimit,
|
||||
searchEnabled: isSearchable,
|
||||
searchFields: searchableOptionFields ?? ['label'],
|
||||
searchPlaceholderValue: searchPrompt,
|
||||
searchResultLimit: optionsLimit,
|
||||
shouldSort: false,
|
||||
searchFloor: hasDynamicSearchResults ? 0 : 1,
|
||||
})
|
||||
|
||||
await this.refreshChoices({ withInitialOptions: true })
|
||||
|
||||
if (![null, undefined, ''].includes(this.state)) {
|
||||
this.select.setChoiceByValue(this.formatState(this.state))
|
||||
}
|
||||
|
||||
this.refreshPlaceholder()
|
||||
|
||||
if (isAutofocused) {
|
||||
this.select.showDropdown()
|
||||
}
|
||||
|
||||
this.$refs.input.addEventListener('change', () => {
|
||||
this.refreshPlaceholder()
|
||||
|
||||
if (this.isStateBeingUpdated) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isStateBeingUpdated = true
|
||||
this.state = this.select.getValue(true) ?? null
|
||||
this.$nextTick(() => (this.isStateBeingUpdated = false))
|
||||
})
|
||||
|
||||
if (hasDynamicOptions) {
|
||||
this.$refs.input.addEventListener('showDropdown', async () => {
|
||||
this.select.clearChoices()
|
||||
await this.select.setChoices([
|
||||
{
|
||||
label: loadingMessage,
|
||||
value: '',
|
||||
disabled: true,
|
||||
},
|
||||
])
|
||||
|
||||
await this.refreshChoices()
|
||||
})
|
||||
}
|
||||
|
||||
if (hasDynamicSearchResults) {
|
||||
this.$refs.input.addEventListener('search', async (event) => {
|
||||
let search = event.detail.value?.trim()
|
||||
|
||||
this.isSearching = true
|
||||
|
||||
this.select.clearChoices()
|
||||
await this.select.setChoices([
|
||||
{
|
||||
label: [null, undefined, ''].includes(search)
|
||||
? loadingMessage
|
||||
: searchingMessage,
|
||||
value: '',
|
||||
disabled: true,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
this.$refs.input.addEventListener(
|
||||
'search',
|
||||
Alpine.debounce(async (event) => {
|
||||
await this.refreshChoices({
|
||||
search: event.detail.value?.trim(),
|
||||
})
|
||||
|
||||
this.isSearching = false
|
||||
}, searchDebounce),
|
||||
)
|
||||
}
|
||||
|
||||
if (!isMultiple) {
|
||||
window.addEventListener(
|
||||
'filament-forms::select.refreshSelectedOptionLabel',
|
||||
async (event) => {
|
||||
if (event.detail.livewireId !== livewireId) {
|
||||
return
|
||||
}
|
||||
|
||||
if (event.detail.statePath !== statePath) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.refreshChoices({
|
||||
withInitialOptions: false,
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
this.$watch('state', async () => {
|
||||
if (!this.select) {
|
||||
return
|
||||
}
|
||||
|
||||
this.refreshPlaceholder()
|
||||
|
||||
if (this.isStateBeingUpdated) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.refreshChoices({
|
||||
withInitialOptions: !hasDynamicOptions,
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
this.select.destroy()
|
||||
this.select = null
|
||||
},
|
||||
|
||||
refreshChoices: async function (config = {}) {
|
||||
const choices = await this.getChoices(config)
|
||||
|
||||
if (!this.select) {
|
||||
return
|
||||
}
|
||||
|
||||
this.select.clearStore()
|
||||
|
||||
this.refreshPlaceholder()
|
||||
|
||||
this.setChoices(choices)
|
||||
|
||||
if (![null, undefined, ''].includes(this.state)) {
|
||||
this.select.setChoiceByValue(this.formatState(this.state))
|
||||
}
|
||||
},
|
||||
|
||||
setChoices: function (choices) {
|
||||
this.select.setChoices(choices, 'value', 'label', true)
|
||||
},
|
||||
|
||||
getChoices: async function (config = {}) {
|
||||
const existingOptions = await this.getExistingOptions(config)
|
||||
|
||||
return existingOptions.concat(
|
||||
await this.getMissingOptions(existingOptions),
|
||||
)
|
||||
},
|
||||
|
||||
getExistingOptions: async function ({ search, withInitialOptions }) {
|
||||
if (withInitialOptions) {
|
||||
return options
|
||||
}
|
||||
|
||||
let results = []
|
||||
|
||||
if (search !== '' && search !== null && search !== undefined) {
|
||||
results = await getSearchResultsUsing(search)
|
||||
} else {
|
||||
results = await getOptionsUsing()
|
||||
}
|
||||
|
||||
return results.map((result) => {
|
||||
if (result.choices) {
|
||||
result.choices = result.choices.map((groupedOption) => {
|
||||
groupedOption.selected = Array.isArray(this.state)
|
||||
? this.state.includes(groupedOption.value)
|
||||
: this.state === groupedOption.value
|
||||
|
||||
return groupedOption
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
result.selected = Array.isArray(this.state)
|
||||
? this.state.includes(result.value)
|
||||
: this.state === result.value
|
||||
|
||||
return result
|
||||
})
|
||||
},
|
||||
|
||||
refreshPlaceholder: function () {
|
||||
if (isMultiple) {
|
||||
return
|
||||
}
|
||||
|
||||
this.select._renderItems()
|
||||
|
||||
if (![null, undefined, ''].includes(this.state)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$el.querySelector('.choices__list--single').innerHTML =
|
||||
`<div class="choices__placeholder choices__item">${
|
||||
placeholder ?? ''
|
||||
}</div>`
|
||||
},
|
||||
|
||||
formatState: function (state) {
|
||||
if (isMultiple) {
|
||||
return (state ?? []).map((item) => item?.toString())
|
||||
}
|
||||
|
||||
return state?.toString()
|
||||
},
|
||||
|
||||
getMissingOptions: async function (existingOptions) {
|
||||
let state = this.formatState(this.state)
|
||||
|
||||
if ([null, undefined, '', [], {}].includes(state)) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const existingOptionValues = new Set()
|
||||
|
||||
existingOptions.forEach((existingOption) => {
|
||||
if (existingOption.choices) {
|
||||
existingOption.choices.forEach((groupedExistingOption) =>
|
||||
existingOptionValues.add(groupedExistingOption.value),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
existingOptionValues.add(existingOption.value)
|
||||
})
|
||||
|
||||
if (isMultiple) {
|
||||
if (state.every((value) => existingOptionValues.has(value))) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return (await getOptionLabelsUsing())
|
||||
.filter((option) => !existingOptionValues.has(option.value))
|
||||
.map((option) => {
|
||||
option.selected = true
|
||||
|
||||
return option
|
||||
})
|
||||
}
|
||||
|
||||
if (existingOptionValues.has(state)) {
|
||||
return existingOptionValues
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: await getOptionLabelUsing(),
|
||||
value: state,
|
||||
selected: true,
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
72
vendor/filament/forms/resources/js/components/tags-input.js
vendored
Normal file
72
vendor/filament/forms/resources/js/components/tags-input.js
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
export default function tagsInputFormComponent({ state, splitKeys }) {
|
||||
return {
|
||||
newTag: '',
|
||||
|
||||
state,
|
||||
|
||||
createTag: function () {
|
||||
this.newTag = this.newTag.trim()
|
||||
|
||||
if (this.newTag === '') {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.state.includes(this.newTag)) {
|
||||
this.newTag = ''
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.state.push(this.newTag)
|
||||
|
||||
this.newTag = ''
|
||||
},
|
||||
|
||||
deleteTag: function (tagToDelete) {
|
||||
this.state = this.state.filter((tag) => tag !== tagToDelete)
|
||||
},
|
||||
|
||||
reorderTags: function (event) {
|
||||
const reordered = this.state.splice(event.oldIndex, 1)[0]
|
||||
this.state.splice(event.newIndex, 0, reordered)
|
||||
|
||||
this.state = [...this.state]
|
||||
},
|
||||
|
||||
input: {
|
||||
['x-on:blur']: 'createTag()',
|
||||
['x-model']: 'newTag',
|
||||
['x-on:keydown'](event) {
|
||||
if (['Enter', ...splitKeys].includes(event.key)) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
this.createTag()
|
||||
}
|
||||
},
|
||||
['x-on:paste']() {
|
||||
this.$nextTick(() => {
|
||||
if (splitKeys.length === 0) {
|
||||
this.createTag()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const pattern = splitKeys
|
||||
.map((key) =>
|
||||
key.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'),
|
||||
)
|
||||
.join('|')
|
||||
|
||||
this.newTag
|
||||
.split(new RegExp(pattern, 'g'))
|
||||
.forEach((tag) => {
|
||||
this.newTag = tag
|
||||
|
||||
this.createTag()
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
14
vendor/filament/forms/resources/js/components/textarea.js
vendored
Normal file
14
vendor/filament/forms/resources/js/components/textarea.js
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export default function textareaFormComponent({ initialHeight }) {
|
||||
return {
|
||||
init: function () {
|
||||
this.render()
|
||||
},
|
||||
|
||||
render: function () {
|
||||
if (this.$el.scrollHeight > 0) {
|
||||
this.$el.style.height = initialHeight + 'rem'
|
||||
this.$el.style.height = this.$el.scrollHeight + 'px'
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
6
vendor/filament/forms/resources/js/index.js
vendored
Normal file
6
vendor/filament/forms/resources/js/index.js
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import '../css/components/date-time-picker.css'
|
||||
import '../css/components/file-upload.css'
|
||||
import '../css/components/markdown-editor.css'
|
||||
import '../css/components/rich-editor.css'
|
||||
import '../css/components/select.css'
|
||||
import '../css/components/tags-input.css'
|
||||
Reference in New Issue
Block a user