mirror of
https://github.com/filamentphp/filament.git
synced 2026-09-24 15:42:09 +08:00
wip: New assets system
This commit is contained in:
+39
-2
@@ -1,7 +1,11 @@
|
||||
const esbuild = require('esbuild')
|
||||
const shouldWatch = process.argv.includes('--watch')
|
||||
|
||||
const packages = ['forms', 'notifications']
|
||||
const packages = [
|
||||
'forms',
|
||||
'notifications',
|
||||
'support',
|
||||
]
|
||||
|
||||
packages.forEach((package) => {
|
||||
esbuild
|
||||
@@ -12,11 +16,44 @@ packages.forEach((package) => {
|
||||
: `'development'`,
|
||||
},
|
||||
entryPoints: [`packages/${package}/resources/js/index.js`],
|
||||
outfile: `packages/${package}/dist/module.esm.js`,
|
||||
outfile: `packages/${package}/dist/index.js`,
|
||||
bundle: true,
|
||||
platform: 'neutral',
|
||||
mainFields: ['module', 'main'],
|
||||
watch: shouldWatch,
|
||||
minify: true,
|
||||
})
|
||||
.catch(() => process.exit(1))
|
||||
})
|
||||
|
||||
const formComponents = [
|
||||
'color-picker',
|
||||
'date-time-picker',
|
||||
'file-upload',
|
||||
'key-value',
|
||||
'markdown-editor',
|
||||
'rich-editor',
|
||||
'select',
|
||||
'tags-input',
|
||||
'text-input',
|
||||
'textarea',
|
||||
]
|
||||
|
||||
formComponents.forEach((component) => {
|
||||
esbuild
|
||||
.build({
|
||||
define: {
|
||||
'process.env.NODE_ENV': shouldWatch
|
||||
? `'production'`
|
||||
: `'development'`,
|
||||
},
|
||||
entryPoints: [`packages/forms/resources/js/components/${component}.js`],
|
||||
outfile: `packages/forms/dist/components/${component}.js`,
|
||||
bundle: true,
|
||||
platform: 'neutral',
|
||||
mainFields: ['module', 'main'],
|
||||
watch: shouldWatch,
|
||||
minify: true,
|
||||
})
|
||||
.catch(() => process.exit(1))
|
||||
})
|
||||
|
||||
Generated
+13
@@ -18,6 +18,7 @@
|
||||
"@tailwindcss/forms": "^0.5.2",
|
||||
"@tailwindcss/typography": "^0.5.0",
|
||||
"alpinejs": "^3.9.0",
|
||||
"async-alpine": "^0.5.3",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"chart.js": "^3.6.0",
|
||||
"choices.js": "^10.1.0",
|
||||
@@ -2739,6 +2740,12 @@
|
||||
"inherits": "2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/async-alpine": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/async-alpine/-/async-alpine-0.5.3.tgz",
|
||||
"integrity": "sha512-xg6HzfYzzmD68sY5okWMmK5yukdQ2kMvEONtqLhA/PgQ9YbO2kwRrSsFpJQixo+FDx23vZWBz+BYWob6yKNOtw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.8",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.8.tgz",
|
||||
@@ -12572,6 +12579,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"async-alpine": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/async-alpine/-/async-alpine-0.5.3.tgz",
|
||||
"integrity": "sha512-xg6HzfYzzmD68sY5okWMmK5yukdQ2kMvEONtqLhA/PgQ9YbO2kwRrSsFpJQixo+FDx23vZWBz+BYWob6yKNOtw==",
|
||||
"dev": true
|
||||
},
|
||||
"autoprefixer": {
|
||||
"version": "10.4.8",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.8.tgz",
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@tailwindcss/forms": "^0.5.2",
|
||||
"@tailwindcss/typography": "^0.5.0",
|
||||
"alpinejs": "^3.9.0",
|
||||
"async-alpine": "^0.5.3",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"chart.js": "^3.6.0",
|
||||
"choices.js": "^10.1.0",
|
||||
|
||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+5
-59
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+2
-2
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"/app.js": "/app.js?id=607fd92a23f9f410572ed7ef87051a20",
|
||||
"/app.js": "/app.js?id=e74a8988f18c007aef6877ee19dfa3f6",
|
||||
"/echo.js": "/echo.js?id=77a99c0e4c69337c7910facf3da252aa",
|
||||
"/app.css": "/app.css?id=11de9cc2e15849297fd09638257ddb61"
|
||||
"/app.css": "/app.css?id=ec35dc7910ed78a45910e992c29d0a9f"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../forms/dist/module.esm.css';
|
||||
@import '../../../forms/dist/index.css';
|
||||
|
||||
@import 'tippy.js/dist/tippy.css';
|
||||
@import 'tippy.js/themes/light.css';
|
||||
|
||||
@@ -2,17 +2,13 @@ import Alpine from 'alpinejs'
|
||||
import Chart from 'chart.js/auto'
|
||||
import Collapse from '@alpinejs/collapse'
|
||||
import Focus from '@alpinejs/focus'
|
||||
import FormsAlpinePlugin from '../../../forms/dist/module.esm'
|
||||
import Mousetrap from '@danharrin/alpine-mousetrap'
|
||||
import NotificationsAlpinePlugin from '../../../notifications/dist/module.esm'
|
||||
import Persist from '@alpinejs/persist'
|
||||
import Tooltip from '@ryangjchandler/alpine-tooltip'
|
||||
|
||||
Alpine.plugin(Collapse)
|
||||
Alpine.plugin(Focus)
|
||||
Alpine.plugin(FormsAlpinePlugin)
|
||||
Alpine.plugin(Mousetrap)
|
||||
Alpine.plugin(NotificationsAlpinePlugin)
|
||||
Alpine.plugin(Persist)
|
||||
Alpine.plugin(Tooltip)
|
||||
|
||||
|
||||
@@ -45,17 +45,7 @@
|
||||
<link href="{{ $fontsUrl }}" rel="stylesheet" />
|
||||
@endif
|
||||
|
||||
@foreach (\Filament\Facades\Filament::getStyles() as $name => $path)
|
||||
@if (\Illuminate\Support\Str::of($path)->startsWith(['http://', 'https://']))
|
||||
<link rel="stylesheet" href="{{ $path }}" />
|
||||
@elseif (\Illuminate\Support\Str::of($path)->startsWith('<'))
|
||||
{!! $path !!}
|
||||
@else
|
||||
<link rel="stylesheet" href="{{ route('filament.asset', [
|
||||
'file' => "{$name}.css",
|
||||
]) }}" />
|
||||
@endif
|
||||
@endforeach
|
||||
@filamentStyles
|
||||
|
||||
{{ \Filament\Facades\Filament::getThemeLink() }}
|
||||
|
||||
@@ -90,19 +80,9 @@
|
||||
window.filamentData = @json(\Filament\Facades\Filament::getScriptData());
|
||||
</script>
|
||||
|
||||
@foreach (\Filament\Facades\Filament::getBeforeCoreScripts() as $name => $path)
|
||||
@if (\Illuminate\Support\Str::of($path)->startsWith(['http://', 'https://']))
|
||||
<script defer src="{{ $path }}"></script>
|
||||
@elseif (\Illuminate\Support\Str::of($path)->startsWith('<'))
|
||||
{!! $path !!}
|
||||
@else
|
||||
<script defer src="{{ route('filament.asset', [
|
||||
'file' => "{$name}.js",
|
||||
]) }}"></script>
|
||||
@endif
|
||||
@endforeach
|
||||
@filamentScripts
|
||||
|
||||
@stack('beforeCoreScripts')
|
||||
@stack('scripts')
|
||||
|
||||
<script defer src="{{ route('filament.asset', [
|
||||
'id' => Filament\get_asset_id('app.js'),
|
||||
@@ -124,20 +104,6 @@
|
||||
</script>
|
||||
@endif
|
||||
|
||||
@foreach (\Filament\Facades\Filament::getScripts() as $name => $path)
|
||||
@if (\Illuminate\Support\Str::of($path)->startsWith(['http://', 'https://']))
|
||||
<script defer src="{{ $path }}"></script>
|
||||
@elseif (\Illuminate\Support\Str::of($path)->startsWith('<'))
|
||||
{!! $path !!}
|
||||
@else
|
||||
<script defer src="{{ route('filament.asset', [
|
||||
'file' => "{$name}.js",
|
||||
]) }}"></script>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
@stack('scripts')
|
||||
|
||||
{{ \Filament\Facades\Filament::renderHook('scripts.end') }}
|
||||
|
||||
{{ \Filament\Facades\Filament::renderHook('body.end') }}
|
||||
|
||||
@@ -35,14 +35,8 @@ class FilamentManager
|
||||
|
||||
protected array $resources = [];
|
||||
|
||||
protected array $beforeCoreScripts = [];
|
||||
|
||||
protected array $scripts = [];
|
||||
|
||||
protected array $scriptData = [];
|
||||
|
||||
protected array $styles = [];
|
||||
|
||||
protected array $meta = [];
|
||||
|
||||
protected string | Htmlable | null $theme = null;
|
||||
@@ -120,25 +114,11 @@ class FilamentManager
|
||||
$this->resources = array_merge($this->resources, $resources);
|
||||
}
|
||||
|
||||
public function registerScripts(array $scripts, bool $shouldBeLoadedBeforeCoreScripts = false): void
|
||||
{
|
||||
if ($shouldBeLoadedBeforeCoreScripts) {
|
||||
$this->beforeCoreScripts = array_merge($this->beforeCoreScripts, $scripts);
|
||||
} else {
|
||||
$this->scripts = array_merge($this->scripts, $scripts);
|
||||
}
|
||||
}
|
||||
|
||||
public function registerScriptData(array $data): void
|
||||
{
|
||||
$this->scriptData = array_merge($this->scriptData, $data);
|
||||
}
|
||||
|
||||
public function registerStyles(array $styles): void
|
||||
{
|
||||
$this->styles = array_merge($this->styles, $styles);
|
||||
}
|
||||
|
||||
public function registerTheme(string | Htmlable | null $theme): void
|
||||
{
|
||||
$this->theme = $theme;
|
||||
@@ -295,26 +275,11 @@ class FilamentManager
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getScripts(): array
|
||||
{
|
||||
return $this->scripts;
|
||||
}
|
||||
|
||||
public function getBeforeCoreScripts(): array
|
||||
{
|
||||
return $this->beforeCoreScripts;
|
||||
}
|
||||
|
||||
public function getScriptData(): array
|
||||
{
|
||||
return $this->scriptData;
|
||||
}
|
||||
|
||||
public function getStyles(): array
|
||||
{
|
||||
return $this->styles;
|
||||
}
|
||||
|
||||
public function getThemeLink(): Htmlable
|
||||
{
|
||||
if (Str::of($this->theme)->contains('<link')) {
|
||||
|
||||
@@ -67,9 +67,9 @@ abstract class PluginServiceProvider extends PackageServiceProvider
|
||||
|
||||
Facades\Filament::serving(function () {
|
||||
Facades\Filament::registerUserMenuItems($this->getUserMenuItems());
|
||||
Facades\Filament::registerScripts($this->getBeforeCoreScripts(), true);
|
||||
Facades\Filament::registerScripts($this->getScripts());
|
||||
Facades\Filament::registerStyles($this->getStyles());
|
||||
// Facades\Filament::registerScripts($this->getBeforeCoreScripts(), true);
|
||||
// Facades\Filament::registerScripts($this->getScripts());
|
||||
// Facades\Filament::registerStyles($this->getStyles());
|
||||
Facades\Filament::registerScriptData($this->getScriptData());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -127,7 +127,8 @@ class EditRecord extends Page implements HasFormActions
|
||||
|
||||
return Notification::make()
|
||||
->success()
|
||||
->title($this->getSavedNotificationTitle());
|
||||
->title($this->getSavedNotificationTitle())
|
||||
->persistent();
|
||||
}
|
||||
|
||||
protected function getSavedNotificationTitle(): ?string
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+84
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
function o({state:i}){return{state:i,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0&&this.addRow(),this.shouldUpdateRows=!0,this.$watch("state",()=>{if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState(),this.shouldUpdateRows=!0},reorderRows:function(t){let e=Alpine.raw(this.rows),s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.rows=e,this.updateState()},updateRows:function(){let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{o as default};
|
||||
File diff suppressed because one or more lines are too long
+143
File diff suppressed because one or more lines are too long
+2
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
function t({state:e}){return{newTag:"",state:e,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(n){this.state=this.state.filter(i=>i!==n)}}}export{t as default};
|
||||
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
function t(){return{init:function(){this.$nextTick(()=>{this.render()})},render:function(){this.$el.scrollHeight>0&&(this.$el.style.height="150px",this.$el.style.height=this.$el.scrollHeight+2+"px")}}}export{t as default};
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
Vendored
-1820
File diff suppressed because it is too large
Load Diff
Vendored
-35353
File diff suppressed because it is too large
Load Diff
@@ -3,55 +3,48 @@ import 'vanilla-colorful/hsl-string-color-picker'
|
||||
import 'vanilla-colorful/rgb-string-color-picker'
|
||||
import 'vanilla-colorful/rgba-string-color-picker'
|
||||
|
||||
import '../../css/components/color-picker.css'
|
||||
export default function colorPickerFormComponent({ isAutofocused, isDisabled, state }) {
|
||||
return {
|
||||
state,
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.data(
|
||||
'colorPickerFormComponent',
|
||||
({ isAutofocused, isDisabled, 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)
|
||||
},
|
||||
)
|
||||
},
|
||||
|
||||
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'
|
||||
},
|
||||
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)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
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'
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,479 +11,474 @@ dayjs.extend(utc)
|
||||
|
||||
window.dayjs = dayjs
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.data(
|
||||
'dateTimePickerFormComponent',
|
||||
({
|
||||
displayFormat,
|
||||
firstDayOfWeek,
|
||||
isAutofocused,
|
||||
locale,
|
||||
shouldCloseOnDateSelection,
|
||||
state,
|
||||
}) => {
|
||||
const timezone = dayjs.tz.guess()
|
||||
export default function dateTimePickerFormComponent({
|
||||
displayFormat,
|
||||
firstDayOfWeek,
|
||||
isAutofocused,
|
||||
locale,
|
||||
shouldCloseOnDateSelection,
|
||||
state,
|
||||
}) {
|
||||
const timezone = dayjs.tz.guess()
|
||||
|
||||
return {
|
||||
daysInFocusedMonth: [],
|
||||
return {
|
||||
daysInFocusedMonth: [],
|
||||
|
||||
displayText: '',
|
||||
displayText: '',
|
||||
|
||||
emptyDaysInFocusedMonth: [],
|
||||
emptyDaysInFocusedMonth: [],
|
||||
|
||||
focusedDate: null,
|
||||
focusedDate: null,
|
||||
|
||||
focusedMonth: null,
|
||||
focusedMonth: null,
|
||||
|
||||
focusedYear: null,
|
||||
focusedYear: null,
|
||||
|
||||
hour: null,
|
||||
hour: null,
|
||||
|
||||
isClearingState: false,
|
||||
isClearingState: false,
|
||||
|
||||
minute: null,
|
||||
minute: null,
|
||||
|
||||
second: null,
|
||||
second: null,
|
||||
|
||||
state,
|
||||
state,
|
||||
|
||||
dayLabels: [],
|
||||
dayLabels: [],
|
||||
|
||||
months: [],
|
||||
months: [],
|
||||
|
||||
init: function () {
|
||||
dayjs.locale(locales[locale] ?? locales['en'])
|
||||
init: function () {
|
||||
dayjs.locale(locales[locale] ?? locales['en'])
|
||||
|
||||
this.focusedDate = dayjs().tz(timezone)
|
||||
this.focusedDate = dayjs().tz(timezone)
|
||||
|
||||
let date =
|
||||
this.getSelectedDate() ??
|
||||
dayjs().tz(timezone).hour(0).minute(0).second(0)
|
||||
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
|
||||
}
|
||||
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.hour = date?.hour() ?? 0
|
||||
this.minute = date?.minute() ?? 0
|
||||
this.second = date?.second() ?? 0
|
||||
|
||||
this.setDisplayText()
|
||||
this.setMonths()
|
||||
this.setDayLabels()
|
||||
this.setDisplayText()
|
||||
this.setMonths()
|
||||
this.setDayLabels()
|
||||
|
||||
if (isAutofocused) {
|
||||
this.$nextTick(() =>
|
||||
this.togglePanelVisibility(this.$refs.button),
|
||||
)
|
||||
}
|
||||
if (isAutofocused) {
|
||||
this.$nextTick(() =>
|
||||
this.togglePanelVisibility(this.$refs.button),
|
||||
)
|
||||
}
|
||||
|
||||
this.$watch('focusedMonth', () => {
|
||||
this.focusedMonth = +this.focusedMonth
|
||||
this.$watch('focusedMonth', () => {
|
||||
this.focusedMonth = +this.focusedMonth
|
||||
|
||||
if (this.focusedDate.month() === this.focusedMonth) {
|
||||
return
|
||||
}
|
||||
if (this.focusedDate.month() === this.focusedMonth) {
|
||||
return
|
||||
}
|
||||
|
||||
this.focusedDate = this.focusedDate.month(
|
||||
this.focusedMonth,
|
||||
)
|
||||
})
|
||||
this.focusedDate = this.focusedDate.month(
|
||||
this.focusedMonth,
|
||||
)
|
||||
})
|
||||
|
||||
this.$watch('focusedYear', () => {
|
||||
if (this.focusedYear?.length > 4) {
|
||||
this.focusedYear = this.focusedYear.substring(0, 4)
|
||||
}
|
||||
this.$watch('focusedYear', () => {
|
||||
if (this.focusedYear?.length > 4) {
|
||||
this.focusedYear = this.focusedYear.substring(0, 4)
|
||||
}
|
||||
|
||||
if (
|
||||
!this.focusedYear ||
|
||||
this.focusedYear?.length !== 4
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (
|
||||
!this.focusedYear ||
|
||||
this.focusedYear?.length !== 4
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
let year = +this.focusedYear
|
||||
let year = +this.focusedYear
|
||||
|
||||
if (!Number.isInteger(year)) {
|
||||
year = dayjs().tz(timezone).year()
|
||||
if (!Number.isInteger(year)) {
|
||||
year = dayjs().tz(timezone).year()
|
||||
|
||||
this.focusedYear = year
|
||||
}
|
||||
this.focusedYear = year
|
||||
}
|
||||
|
||||
if (this.focusedDate.year() === year) {
|
||||
return
|
||||
}
|
||||
if (this.focusedDate.year() === year) {
|
||||
return
|
||||
}
|
||||
|
||||
this.focusedDate = this.focusedDate.year(year)
|
||||
})
|
||||
this.focusedDate = this.focusedDate.year(year)
|
||||
})
|
||||
|
||||
this.$watch('focusedDate', () => {
|
||||
let month = this.focusedDate.month()
|
||||
let year = this.focusedDate.year()
|
||||
this.$watch('focusedDate', () => {
|
||||
let month = this.focusedDate.month()
|
||||
let year = this.focusedDate.year()
|
||||
|
||||
if (this.focusedMonth !== month) {
|
||||
this.focusedMonth = month
|
||||
}
|
||||
if (this.focusedMonth !== month) {
|
||||
this.focusedMonth = month
|
||||
}
|
||||
|
||||
if (this.focusedYear !== year) {
|
||||
this.focusedYear = year
|
||||
}
|
||||
if (this.focusedYear !== year) {
|
||||
this.focusedYear = year
|
||||
}
|
||||
|
||||
this.setupDaysGrid()
|
||||
})
|
||||
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.$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
|
||||
}
|
||||
|
||||
this.$nextTick(() => (this.isClearingState = false))
|
||||
},
|
||||
if (this.isClearingState) {
|
||||
return
|
||||
}
|
||||
|
||||
dateIsDisabled: function (date) {
|
||||
if (
|
||||
JSON.parse(this.$refs.disabledDates?.value ?? []).some(
|
||||
(disabledDate) => {
|
||||
disabledDate = dayjs(disabledDate)
|
||||
let date = this.getSelectedDate() ?? this.focusedDate
|
||||
|
||||
if (!disabledDate.isValid()) {
|
||||
return false
|
||||
}
|
||||
this.setState(date.second(this.second ?? 0))
|
||||
})
|
||||
|
||||
return disabledDate.isSame(date, 'day')
|
||||
},
|
||||
)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
this.$watch('state', () => {
|
||||
if (this.state === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.getMaxDate() && date.isAfter(this.getMaxDate())) {
|
||||
return true
|
||||
}
|
||||
if (this.getMinDate() && date.isBefore(this.getMinDate())) {
|
||||
return true
|
||||
}
|
||||
let date = this.getSelectedDate()
|
||||
|
||||
return false
|
||||
},
|
||||
if (date === null) {
|
||||
this.clearState()
|
||||
|
||||
dayIsDisabled: function (day) {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
return
|
||||
}
|
||||
|
||||
return this.dateIsDisabled(this.focusedDate.date(day))
|
||||
},
|
||||
if (
|
||||
this.getMaxDate() !== null &&
|
||||
date?.isAfter(this.getMaxDate())
|
||||
) {
|
||||
date = null
|
||||
}
|
||||
if (
|
||||
this.getMinDate() !== null &&
|
||||
date?.isBefore(this.getMinDate())
|
||||
) {
|
||||
date = null
|
||||
}
|
||||
|
||||
dayIsSelected: function (day) {
|
||||
let selectedDate = this.getSelectedDate()
|
||||
const newHour = date?.hour() ?? 0
|
||||
if (this.hour !== newHour) {
|
||||
this.hour = newHour
|
||||
}
|
||||
|
||||
if (selectedDate === null) {
|
||||
return false
|
||||
}
|
||||
const newMinute = date?.minute() ?? 0
|
||||
if (this.minute !== newMinute) {
|
||||
this.minute = newMinute
|
||||
}
|
||||
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
const newSecond = date?.second() ?? 0
|
||||
if (this.second !== newSecond) {
|
||||
this.second = newSecond
|
||||
}
|
||||
|
||||
return (
|
||||
selectedDate.date() === day &&
|
||||
selectedDate.month() === this.focusedDate.month() &&
|
||||
selectedDate.year() === this.focusedDate.year()
|
||||
)
|
||||
},
|
||||
this.setDisplayText()
|
||||
})
|
||||
},
|
||||
|
||||
dayIsToday: function (day) {
|
||||
let date = dayjs().tz(timezone)
|
||||
this.focusedDate ??= date
|
||||
clearState: function () {
|
||||
this.isClearingState = true
|
||||
|
||||
return (
|
||||
date.date() === day &&
|
||||
date.month() === this.focusedDate.month() &&
|
||||
date.year() === this.focusedDate.year()
|
||||
)
|
||||
},
|
||||
this.setState(null)
|
||||
|
||||
focusPreviousDay: function () {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
this.hour = 0
|
||||
this.minute = 0
|
||||
this.second = 0
|
||||
|
||||
this.focusedDate = this.focusedDate.subtract(1, 'day')
|
||||
},
|
||||
this.$nextTick(() => (this.isClearingState = false))
|
||||
},
|
||||
|
||||
focusPreviousWeek: function () {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
dateIsDisabled: function (date) {
|
||||
if (
|
||||
JSON.parse(this.$refs.disabledDates?.value ?? []).some(
|
||||
(disabledDate) => {
|
||||
disabledDate = dayjs(disabledDate)
|
||||
|
||||
this.focusedDate = this.focusedDate.subtract(1, 'week')
|
||||
},
|
||||
if (!disabledDate.isValid()) {
|
||||
return false
|
||||
}
|
||||
|
||||
focusNextDay: function () {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
return disabledDate.isSame(date, 'day')
|
||||
},
|
||||
)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
this.focusedDate = this.focusedDate.add(1, 'day')
|
||||
},
|
||||
if (this.getMaxDate() && date.isAfter(this.getMaxDate())) {
|
||||
return true
|
||||
}
|
||||
if (this.getMinDate() && date.isBefore(this.getMinDate())) {
|
||||
return true
|
||||
}
|
||||
|
||||
focusNextWeek: function () {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
return false
|
||||
},
|
||||
|
||||
this.focusedDate = this.focusedDate.add(1, 'week')
|
||||
},
|
||||
dayIsDisabled: function (day) {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
|
||||
getDayLabels: function () {
|
||||
const labels = dayjs.weekdaysShort()
|
||||
return this.dateIsDisabled(this.focusedDate.date(day))
|
||||
},
|
||||
|
||||
if (firstDayOfWeek === 0) {
|
||||
return labels
|
||||
}
|
||||
dayIsSelected: function (day) {
|
||||
let selectedDate = this.getSelectedDate()
|
||||
|
||||
return [
|
||||
...labels.slice(firstDayOfWeek),
|
||||
...labels.slice(0, firstDayOfWeek),
|
||||
]
|
||||
},
|
||||
if (selectedDate === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
getMaxDate: function () {
|
||||
let date = dayjs(this.$refs.maxDate?.value)
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
|
||||
return date.isValid() ? date : null
|
||||
},
|
||||
return (
|
||||
selectedDate.date() === day &&
|
||||
selectedDate.month() === this.focusedDate.month() &&
|
||||
selectedDate.year() === this.focusedDate.year()
|
||||
)
|
||||
},
|
||||
|
||||
getMinDate: function () {
|
||||
let date = dayjs(this.$refs.minDate?.value)
|
||||
dayIsToday: function (day) {
|
||||
let date = dayjs().tz(timezone)
|
||||
this.focusedDate ??= date
|
||||
|
||||
return date.isValid() ? date : null
|
||||
},
|
||||
return (
|
||||
date.date() === day &&
|
||||
date.month() === this.focusedDate.month() &&
|
||||
date.year() === this.focusedDate.year()
|
||||
)
|
||||
},
|
||||
|
||||
getSelectedDate: function () {
|
||||
if (this.state === undefined) {
|
||||
return null
|
||||
}
|
||||
focusPreviousDay: function () {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
|
||||
if (this.state === null) {
|
||||
return null
|
||||
}
|
||||
this.focusedDate = this.focusedDate.subtract(1, 'day')
|
||||
},
|
||||
|
||||
let date = dayjs(this.state)
|
||||
focusPreviousWeek: function () {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
|
||||
if (!date.isValid()) {
|
||||
return null
|
||||
}
|
||||
this.focusedDate = this.focusedDate.subtract(1, 'week')
|
||||
},
|
||||
|
||||
return date
|
||||
},
|
||||
focusNextDay: function () {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
|
||||
togglePanelVisibility: function () {
|
||||
if (!this.isOpen()) {
|
||||
this.focusedDate =
|
||||
this.getSelectedDate() ??
|
||||
this.getMinDate() ??
|
||||
dayjs().tz(timezone)
|
||||
this.focusedDate = this.focusedDate.add(1, 'day')
|
||||
},
|
||||
|
||||
this.setupDaysGrid()
|
||||
}
|
||||
focusNextWeek: function () {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
|
||||
this.$refs.panel.toggle(this.$refs.button)
|
||||
},
|
||||
this.focusedDate = this.focusedDate.add(1, 'week')
|
||||
},
|
||||
|
||||
selectDate: function (day = null) {
|
||||
if (day) {
|
||||
this.setFocusedDay(day)
|
||||
}
|
||||
getDayLabels: function () {
|
||||
const labels = dayjs.weekdaysShort()
|
||||
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
if (firstDayOfWeek === 0) {
|
||||
return labels
|
||||
}
|
||||
|
||||
this.setState(this.focusedDate)
|
||||
return [
|
||||
...labels.slice(firstDayOfWeek),
|
||||
...labels.slice(0, firstDayOfWeek),
|
||||
]
|
||||
},
|
||||
|
||||
if (shouldCloseOnDateSelection) {
|
||||
this.togglePanelVisibility()
|
||||
}
|
||||
},
|
||||
getMaxDate: function () {
|
||||
let date = dayjs(this.$refs.maxDate?.value)
|
||||
|
||||
setDisplayText: function () {
|
||||
this.displayText = this.getSelectedDate()
|
||||
? this.getSelectedDate().format(displayFormat)
|
||||
: ''
|
||||
},
|
||||
return date.isValid() ? date : null
|
||||
},
|
||||
|
||||
setMonths: function () {
|
||||
this.months = dayjs.months()
|
||||
},
|
||||
getMinDate: function () {
|
||||
let date = dayjs(this.$refs.minDate?.value)
|
||||
|
||||
setDayLabels: function () {
|
||||
this.dayLabels = this.getDayLabels()
|
||||
},
|
||||
return date.isValid() ? date : null
|
||||
},
|
||||
|
||||
setupDaysGrid: function () {
|
||||
this.focusedDate ??= dayjs().tz(timezone)
|
||||
getSelectedDate: function () {
|
||||
if (this.state === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
this.emptyDaysInFocusedMonth = Array.from(
|
||||
{
|
||||
length: this.focusedDate
|
||||
.date(8 - firstDayOfWeek)
|
||||
.day(),
|
||||
},
|
||||
(_, i) => i + 1,
|
||||
)
|
||||
if (this.state === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
this.daysInFocusedMonth = Array.from(
|
||||
{
|
||||
length: this.focusedDate.daysInMonth(),
|
||||
},
|
||||
(_, i) => i + 1,
|
||||
)
|
||||
},
|
||||
let date = dayjs(this.state)
|
||||
|
||||
setFocusedDay: function (day) {
|
||||
this.focusedDate = (
|
||||
this.focusedDate ?? dayjs().tz(timezone)
|
||||
).date(day)
|
||||
},
|
||||
if (!date.isValid()) {
|
||||
return null
|
||||
}
|
||||
|
||||
setState: function (date) {
|
||||
if (date === null) {
|
||||
this.state = null
|
||||
this.setDisplayText()
|
||||
return date
|
||||
},
|
||||
|
||||
return
|
||||
}
|
||||
togglePanelVisibility: function () {
|
||||
if (!this.isOpen()) {
|
||||
this.focusedDate =
|
||||
this.getSelectedDate() ??
|
||||
this.getMinDate() ??
|
||||
dayjs().tz(timezone)
|
||||
|
||||
if (this.dateIsDisabled(date)) {
|
||||
return
|
||||
}
|
||||
this.setupDaysGrid()
|
||||
}
|
||||
|
||||
this.state = date
|
||||
.hour(this.hour ?? 0)
|
||||
.minute(this.minute ?? 0)
|
||||
.second(this.second ?? 0)
|
||||
.format('YYYY-MM-DD HH:mm:ss')
|
||||
this.$refs.panel.toggle(this.$refs.button)
|
||||
},
|
||||
|
||||
this.setDisplayText()
|
||||
},
|
||||
selectDate: function (day = null) {
|
||||
if (day) {
|
||||
this.setFocusedDay(day)
|
||||
}
|
||||
|
||||
isOpen: function () {
|
||||
return this.$refs.panel?.style.display === 'block'
|
||||
},
|
||||
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 = {
|
||||
|
||||
@@ -8,11 +8,6 @@ import FilePondPluginImageResize from 'filepond-plugin-image-resize'
|
||||
import FilePondPluginImageTransform from 'filepond-plugin-image-transform'
|
||||
import FilePondPluginMediaPreview from 'filepond-plugin-media-preview'
|
||||
|
||||
import 'filepond/dist/filepond.min.css'
|
||||
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css'
|
||||
import 'filepond-plugin-media-preview/dist/filepond-plugin-media-preview.css'
|
||||
import '../../css/components/file-upload.css'
|
||||
|
||||
FilePond.registerPlugin(FilePondPluginFileValidateSize)
|
||||
FilePond.registerPlugin(FilePondPluginFileValidateType)
|
||||
FilePond.registerPlugin(FilePondPluginImageCrop)
|
||||
@@ -24,354 +19,349 @@ FilePond.registerPlugin(FilePondPluginMediaPreview)
|
||||
|
||||
window.FilePond = FilePond
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.data(
|
||||
'fileUploadFormComponent',
|
||||
({
|
||||
acceptedFileTypes,
|
||||
canDownload,
|
||||
canOpen,
|
||||
canPreview,
|
||||
canReorder,
|
||||
deleteUploadedFileUsing,
|
||||
getUploadedFileUrlsUsing,
|
||||
imageCropAspectRatio,
|
||||
imagePreviewHeight,
|
||||
imageResizeMode,
|
||||
imageResizeTargetHeight,
|
||||
imageResizeTargetWidth,
|
||||
isAvatar,
|
||||
loadingIndicatorPosition,
|
||||
locale,
|
||||
panelAspectRatio,
|
||||
panelLayout,
|
||||
placeholder,
|
||||
maxSize,
|
||||
minSize,
|
||||
removeUploadedFileButtonPosition,
|
||||
removeUploadedFileUsing,
|
||||
reorderUploadedFilesUsing,
|
||||
shouldAppendFiles,
|
||||
shouldTransformImage,
|
||||
state,
|
||||
uploadButtonPosition,
|
||||
uploadProgressIndicatorPosition,
|
||||
uploadUsing,
|
||||
}) => {
|
||||
return {
|
||||
fileKeyIndex: {},
|
||||
export default function fileUploadFormComponent({
|
||||
acceptedFileTypes,
|
||||
canDownload,
|
||||
canOpen,
|
||||
canPreview,
|
||||
canReorder,
|
||||
deleteUploadedFileUsing,
|
||||
getUploadedFileUrlsUsing,
|
||||
imageCropAspectRatio,
|
||||
imagePreviewHeight,
|
||||
imageResizeMode,
|
||||
imageResizeTargetHeight,
|
||||
imageResizeTargetWidth,
|
||||
isAvatar,
|
||||
loadingIndicatorPosition,
|
||||
locale,
|
||||
panelAspectRatio,
|
||||
panelLayout,
|
||||
placeholder,
|
||||
maxSize,
|
||||
minSize,
|
||||
removeUploadedFileButtonPosition,
|
||||
removeUploadedFileUsing,
|
||||
reorderUploadedFilesUsing,
|
||||
shouldAppendFiles,
|
||||
shouldTransformImage,
|
||||
state,
|
||||
uploadButtonPosition,
|
||||
uploadProgressIndicatorPosition,
|
||||
uploadUsing,
|
||||
}) {
|
||||
return {
|
||||
fileKeyIndex: {},
|
||||
|
||||
pond: null,
|
||||
pond: null,
|
||||
|
||||
shouldUpdateState: true,
|
||||
shouldUpdateState: true,
|
||||
|
||||
state,
|
||||
state,
|
||||
|
||||
lastState: null,
|
||||
lastState: null,
|
||||
|
||||
uploadedFileUrlIndex: {},
|
||||
uploadedFileUrlIndex: {},
|
||||
|
||||
init: async function () {
|
||||
FilePond.setOptions(locales[locale] ?? locales['en'])
|
||||
init: async function () {
|
||||
FilePond.setOptions(locales[locale] ?? locales['en'])
|
||||
|
||||
this.pond = FilePond.create(this.$refs.input, {
|
||||
acceptedFileTypes,
|
||||
allowPaste: false,
|
||||
allowReorder: canReorder,
|
||||
allowImagePreview: canPreview,
|
||||
allowVideoPreview: canPreview,
|
||||
allowAudioPreview: canPreview,
|
||||
allowImageTransform: shouldTransformImage,
|
||||
credits: false,
|
||||
files: await this.getFiles(),
|
||||
imageCropAspectRatio,
|
||||
imagePreviewHeight,
|
||||
imageResizeTargetHeight,
|
||||
imageResizeTargetWidth,
|
||||
imageResizeMode,
|
||||
itemInsertLocation: shouldAppendFiles
|
||||
? 'after'
|
||||
: 'before',
|
||||
...(placeholder && { labelIdle: placeholder }),
|
||||
maxFileSize: maxSize,
|
||||
minFileSize: minSize,
|
||||
styleButtonProcessItemPosition: uploadButtonPosition,
|
||||
styleButtonRemoveItemPosition:
|
||||
removeUploadedFileButtonPosition,
|
||||
styleLoadIndicatorPosition: loadingIndicatorPosition,
|
||||
stylePanelAspectRatio: panelAspectRatio,
|
||||
stylePanelLayout: panelLayout,
|
||||
styleProgressIndicatorPosition:
|
||||
uploadProgressIndicatorPosition,
|
||||
server: {
|
||||
load: async (source, load) => {
|
||||
let response = await fetch(source)
|
||||
let blob = await response.blob()
|
||||
this.pond = FilePond.create(this.$refs.input, {
|
||||
acceptedFileTypes,
|
||||
allowPaste: false,
|
||||
allowReorder: canReorder,
|
||||
allowImagePreview: canPreview,
|
||||
allowVideoPreview: canPreview,
|
||||
allowAudioPreview: canPreview,
|
||||
allowImageTransform: shouldTransformImage,
|
||||
credits: false,
|
||||
files: await this.getFiles(),
|
||||
imageCropAspectRatio,
|
||||
imagePreviewHeight,
|
||||
imageResizeTargetHeight,
|
||||
imageResizeTargetWidth,
|
||||
imageResizeMode,
|
||||
itemInsertLocation: shouldAppendFiles
|
||||
? 'after'
|
||||
: 'before',
|
||||
...(placeholder && { labelIdle: placeholder }),
|
||||
maxFileSize: maxSize,
|
||||
minFileSize: minSize,
|
||||
styleButtonProcessItemPosition: uploadButtonPosition,
|
||||
styleButtonRemoveItemPosition:
|
||||
removeUploadedFileButtonPosition,
|
||||
styleLoadIndicatorPosition: loadingIndicatorPosition,
|
||||
stylePanelAspectRatio: panelAspectRatio,
|
||||
stylePanelLayout: panelLayout,
|
||||
styleProgressIndicatorPosition:
|
||||
uploadProgressIndicatorPosition,
|
||||
server: {
|
||||
load: async (source, load) => {
|
||||
let response = await fetch(source)
|
||||
let blob = await response.blob()
|
||||
|
||||
load(blob)
|
||||
},
|
||||
process: (
|
||||
fieldName,
|
||||
file,
|
||||
metadata,
|
||||
load,
|
||||
error,
|
||||
progress,
|
||||
) => {
|
||||
this.shouldUpdateState = false
|
||||
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.uploadedFileUrlIndex[source] ?? null
|
||||
|
||||
if (!fileKey) {
|
||||
return
|
||||
}
|
||||
|
||||
await deleteUploadedFileUsing(fileKey)
|
||||
|
||||
load()
|
||||
},
|
||||
revert: async (uniqueFileId, load) => {
|
||||
await removeUploadedFileUsing(uniqueFileId)
|
||||
|
||||
load()
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
this.$watch('state', async () => {
|
||||
if (!this.shouldUpdateState) {
|
||||
return
|
||||
}
|
||||
|
||||
// We don't want to overwrite the files that are already in the input, if they haven't been saved yet.
|
||||
if (
|
||||
Object.values(this.state).filter((file) =>
|
||||
file.startsWith('livewire-file:'),
|
||||
).length
|
||||
) {
|
||||
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.uploadedFileUrlIndex[file.source] ??
|
||||
null,
|
||||
) // file.serverId is null for a file that is not yet uploaded
|
||||
.filter((fileKey) => fileKey)
|
||||
|
||||
await reorderUploadedFilesUsing(
|
||||
shouldAppendFiles
|
||||
? orderedFileKeys
|
||||
: orderedFileKeys.reverse(),
|
||||
let fileKey = (
|
||||
[1e7] +
|
||||
-1e3 +
|
||||
-4e3 +
|
||||
-8e3 +
|
||||
-1e11
|
||||
).replace(/[018]/g, (c) =>
|
||||
(
|
||||
c ^
|
||||
(crypto.getRandomValues(
|
||||
new Uint8Array(1),
|
||||
)[0] &
|
||||
(15 >> (c / 4)))
|
||||
).toString(16),
|
||||
)
|
||||
})
|
||||
|
||||
this.pond.on('initfile', async (fileItem) => {
|
||||
if (!canDownload) {
|
||||
return
|
||||
}
|
||||
uploadUsing(
|
||||
fileKey,
|
||||
file,
|
||||
(fileKey) => {
|
||||
this.shouldUpdateState = true
|
||||
|
||||
if (isAvatar) {
|
||||
return
|
||||
}
|
||||
|
||||
this.insertDownloadLink(fileItem)
|
||||
})
|
||||
|
||||
this.pond.on('initfile', async (fileItem) => {
|
||||
if (!canOpen) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isAvatar) {
|
||||
return
|
||||
}
|
||||
|
||||
this.insertOpenLink(fileItem)
|
||||
})
|
||||
|
||||
this.pond.on('processfilestart', async () => {
|
||||
this.dispatchFormEvent('file-upload-started')
|
||||
})
|
||||
|
||||
this.pond.on('processfileprogress', async () => {
|
||||
this.dispatchFormEvent('file-upload-started')
|
||||
})
|
||||
|
||||
this.pond.on('processfile', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
|
||||
this.pond.on('processfiles', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
|
||||
this.pond.on('processfileabort', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
|
||||
this.pond.on('processfilerevert', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
},
|
||||
|
||||
dispatchFormEvent: function (name) {
|
||||
this.$el.closest('form')?.dispatchEvent(
|
||||
new CustomEvent(name, {
|
||||
composed: true,
|
||||
cancelable: true,
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
||||
getUploadedFileUrls: async function () {
|
||||
const uploadedFileUrls = await getUploadedFileUrlsUsing()
|
||||
|
||||
this.fileKeyIndex = uploadedFileUrls ?? {}
|
||||
|
||||
this.uploadedFileUrlIndex = Object.entries(
|
||||
this.fileKeyIndex,
|
||||
)
|
||||
.filter((value) => value)
|
||||
.reduce((obj, [key, value]) => {
|
||||
obj[value] = key
|
||||
|
||||
return obj
|
||||
}, {})
|
||||
},
|
||||
|
||||
getFiles: async function () {
|
||||
await this.getUploadedFileUrls()
|
||||
|
||||
let files = []
|
||||
|
||||
for (const uploadedFileUrl of Object.values(
|
||||
this.fileKeyIndex,
|
||||
)) {
|
||||
if (!uploadedFileUrl) {
|
||||
continue
|
||||
}
|
||||
|
||||
files.push({
|
||||
source: uploadedFileUrl,
|
||||
options: {
|
||||
type: 'local',
|
||||
load(fileKey)
|
||||
},
|
||||
})
|
||||
}
|
||||
error,
|
||||
progress,
|
||||
)
|
||||
},
|
||||
remove: async (source, load) => {
|
||||
let fileKey =
|
||||
this.uploadedFileUrlIndex[source] ?? null
|
||||
|
||||
return shouldAppendFiles ? files : files.reverse()
|
||||
if (!fileKey) {
|
||||
return
|
||||
}
|
||||
|
||||
await deleteUploadedFileUsing(fileKey)
|
||||
|
||||
load()
|
||||
},
|
||||
revert: async (uniqueFileId, load) => {
|
||||
await removeUploadedFileUsing(uniqueFileId)
|
||||
|
||||
load()
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
insertDownloadLink: function (file) {
|
||||
if (file.origin !== FilePond.FileOrigin.LOCAL) {
|
||||
return
|
||||
}
|
||||
this.$watch('state', async () => {
|
||||
if (!this.shouldUpdateState) {
|
||||
return
|
||||
}
|
||||
|
||||
const anchor = this.getDownloadLink(file)
|
||||
// We don't want to overwrite the files that are already in the input, if they haven't been saved yet.
|
||||
if (
|
||||
Object.values(this.state).filter((file) =>
|
||||
file.startsWith('livewire-file:'),
|
||||
).length
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!anchor) {
|
||||
return
|
||||
}
|
||||
// Don't do anything if the state hasn't changed
|
||||
if (JSON.stringify(this.state) === this.lastState) {
|
||||
return
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById(`filepond--item-${file.id}`)
|
||||
.querySelector('.filepond--file-info-main')
|
||||
.prepend(anchor)
|
||||
},
|
||||
this.lastState = JSON.stringify(this.state)
|
||||
|
||||
insertOpenLink: function (file) {
|
||||
if (file.origin !== FilePond.FileOrigin.LOCAL) {
|
||||
return
|
||||
}
|
||||
this.pond.files = await this.getFiles()
|
||||
})
|
||||
|
||||
const anchor = this.getOpenLink(file)
|
||||
this.pond.on('reorderfiles', async (files) => {
|
||||
const orderedFileKeys = files
|
||||
.map((file) =>
|
||||
file.source instanceof File
|
||||
? file.serverId
|
||||
: this.uploadedFileUrlIndex[file.source] ??
|
||||
null,
|
||||
) // file.serverId is null for a file that is not yet uploaded
|
||||
.filter((fileKey) => fileKey)
|
||||
|
||||
if (!anchor) {
|
||||
return
|
||||
}
|
||||
await reorderUploadedFilesUsing(
|
||||
shouldAppendFiles
|
||||
? orderedFileKeys
|
||||
: orderedFileKeys.reverse(),
|
||||
)
|
||||
})
|
||||
|
||||
document
|
||||
.getElementById(`filepond--item-${file.id}`)
|
||||
.querySelector('.filepond--file-info-main')
|
||||
.prepend(anchor)
|
||||
},
|
||||
this.pond.on('initfile', async (fileItem) => {
|
||||
if (!canDownload) {
|
||||
return
|
||||
}
|
||||
|
||||
getDownloadLink: function (file) {
|
||||
let fileSource = file.source
|
||||
if (isAvatar) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!fileSource) {
|
||||
return
|
||||
}
|
||||
this.insertDownloadLink(fileItem)
|
||||
})
|
||||
|
||||
const anchor = document.createElement('a')
|
||||
anchor.className = 'filepond--download-icon'
|
||||
anchor.href = fileSource
|
||||
anchor.download = file.file.name
|
||||
this.pond.on('initfile', async (fileItem) => {
|
||||
if (!canOpen) {
|
||||
return
|
||||
}
|
||||
|
||||
return anchor
|
||||
},
|
||||
if (isAvatar) {
|
||||
return
|
||||
}
|
||||
|
||||
getOpenLink: function (file) {
|
||||
let fileSource = file.source
|
||||
this.insertOpenLink(fileItem)
|
||||
})
|
||||
|
||||
if (!fileSource) {
|
||||
return
|
||||
}
|
||||
this.pond.on('processfilestart', async () => {
|
||||
this.dispatchFormEvent('file-upload-started')
|
||||
})
|
||||
|
||||
const anchor = document.createElement('a')
|
||||
anchor.className = 'filepond--open-icon'
|
||||
anchor.href = fileSource
|
||||
anchor.target = '_blank'
|
||||
this.pond.on('processfileprogress', async () => {
|
||||
this.dispatchFormEvent('file-upload-started')
|
||||
})
|
||||
|
||||
return anchor
|
||||
},
|
||||
}
|
||||
this.pond.on('processfile', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
|
||||
this.pond.on('processfiles', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
|
||||
this.pond.on('processfileabort', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
|
||||
this.pond.on('processfilerevert', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
dispatchFormEvent: function (name) {
|
||||
this.$el.closest('form')?.dispatchEvent(
|
||||
new CustomEvent(name, {
|
||||
composed: true,
|
||||
cancelable: true,
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
||||
getUploadedFileUrls: async function () {
|
||||
const uploadedFileUrls = await getUploadedFileUrlsUsing()
|
||||
|
||||
this.fileKeyIndex = uploadedFileUrls ?? {}
|
||||
|
||||
this.uploadedFileUrlIndex = Object.entries(
|
||||
this.fileKeyIndex,
|
||||
)
|
||||
.filter((value) => value)
|
||||
.reduce((obj, [key, value]) => {
|
||||
obj[value] = key
|
||||
|
||||
return obj
|
||||
}, {})
|
||||
},
|
||||
|
||||
getFiles: async function () {
|
||||
await this.getUploadedFileUrls()
|
||||
|
||||
let files = []
|
||||
|
||||
for (const uploadedFileUrl of Object.values(
|
||||
this.fileKeyIndex,
|
||||
)) {
|
||||
if (!uploadedFileUrl) {
|
||||
continue
|
||||
}
|
||||
|
||||
files.push({
|
||||
source: uploadedFileUrl,
|
||||
options: {
|
||||
type: 'local',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
import ar from 'filepond/locale/ar-ar'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default (Alpine) => {
|
||||
Alpine.data('keyValueFormComponent', ({ state }) => ({
|
||||
export default function keyValueFormComponent ({ state }) {
|
||||
return {
|
||||
state,
|
||||
|
||||
rows: [],
|
||||
@@ -88,5 +88,5 @@ export default (Alpine) => {
|
||||
|
||||
this.state = state
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,86 +4,84 @@ import DOMPurify from 'dompurify'
|
||||
import { marked } from 'marked'
|
||||
import { highlight } from 'mdhl'
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.data('markdownEditorFormComponent', ({ state, tab }) => {
|
||||
return {
|
||||
attachment: null,
|
||||
export default function markdownEditorFormComponent ({ state, tab }) {
|
||||
return {
|
||||
attachment: null,
|
||||
|
||||
overlay: null,
|
||||
overlay: null,
|
||||
|
||||
preview: '',
|
||||
preview: '',
|
||||
|
||||
state,
|
||||
state,
|
||||
|
||||
tab,
|
||||
tab,
|
||||
|
||||
init: function () {
|
||||
if (this.state !== null) {
|
||||
this.$nextTick(() => {
|
||||
this.render()
|
||||
})
|
||||
}
|
||||
|
||||
this.$watch('state', () => {
|
||||
init: function () {
|
||||
if (this.state !== null) {
|
||||
this.$nextTick(() => {
|
||||
this.render()
|
||||
})
|
||||
},
|
||||
|
||||
render: function () {
|
||||
if (this.$refs.textarea.scrollHeight > 0) {
|
||||
this.$refs.overlay.style.height = '150px'
|
||||
this.$refs.overlay.style.height =
|
||||
this.$refs.textarea.scrollHeight + 'px'
|
||||
}
|
||||
|
||||
this.state = this.state.replace('\r\n', '\n')
|
||||
|
||||
this.overlay = null
|
||||
this.overlay = highlight(this.state)
|
||||
|
||||
this.preview = null
|
||||
this.preview = DOMPurify.sanitize(marked(this.state))
|
||||
},
|
||||
|
||||
checkForAutoInsertion: function () {
|
||||
const lines = this.$refs.textarea.value.split('\n')
|
||||
|
||||
const currentLine = this.$refs.textarea.value
|
||||
.substring(0, this.$refs.textarea.value.selectionStart)
|
||||
.split('\n').length
|
||||
const previousLine = lines[currentLine - 2]
|
||||
|
||||
if (!previousLine.match(/^(\*\s|-\s)|^(\d)+\./)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (previousLine.match(/^(\*\s)/)) {
|
||||
if (previousLine.trim().length > 1) {
|
||||
lines[currentLine - 1] = '* '
|
||||
} else {
|
||||
delete lines[currentLine - 2]
|
||||
}
|
||||
} else if (previousLine.match(/^(-\s)/)) {
|
||||
if (previousLine.trim().length > 1) {
|
||||
lines[currentLine - 1] = '- '
|
||||
} else {
|
||||
delete lines[currentLine - 2]
|
||||
}
|
||||
} else {
|
||||
const matches = previousLine.match(/^(\d)+/)
|
||||
const number = matches[0]
|
||||
|
||||
if (previousLine.trim().length > number.length + 2) {
|
||||
lines[currentLine - 1] = `${parseInt(number) + 1}. `
|
||||
} else {
|
||||
delete lines[currentLine - 2]
|
||||
}
|
||||
}
|
||||
|
||||
this.state = lines.join('\n')
|
||||
}
|
||||
|
||||
this.$watch('state', () => {
|
||||
this.render()
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
render: function () {
|
||||
if (this.$refs.textarea.scrollHeight > 0) {
|
||||
this.$refs.overlay.style.height = '150px'
|
||||
this.$refs.overlay.style.height =
|
||||
this.$refs.textarea.scrollHeight + 'px'
|
||||
}
|
||||
|
||||
this.state = this.state.replace('\r\n', '\n')
|
||||
|
||||
this.overlay = null
|
||||
this.overlay = highlight(this.state)
|
||||
|
||||
this.preview = null
|
||||
this.preview = DOMPurify.sanitize(marked(this.state))
|
||||
},
|
||||
|
||||
checkForAutoInsertion: function () {
|
||||
const lines = this.$refs.textarea.value.split('\n')
|
||||
|
||||
const currentLine = this.$refs.textarea.value
|
||||
.substring(0, this.$refs.textarea.value.selectionStart)
|
||||
.split('\n').length
|
||||
const previousLine = lines[currentLine - 2]
|
||||
|
||||
if (!previousLine.match(/^(\*\s|-\s)|^(\d)+\./)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (previousLine.match(/^(\*\s)/)) {
|
||||
if (previousLine.trim().length > 1) {
|
||||
lines[currentLine - 1] = '* '
|
||||
} else {
|
||||
delete lines[currentLine - 2]
|
||||
}
|
||||
} else if (previousLine.match(/^(-\s)/)) {
|
||||
if (previousLine.trim().length > 1) {
|
||||
lines[currentLine - 1] = '- '
|
||||
} else {
|
||||
delete lines[currentLine - 2]
|
||||
}
|
||||
} else {
|
||||
const matches = previousLine.match(/^(\d)+/)
|
||||
const number = matches[0]
|
||||
|
||||
if (previousLine.trim().length > number.length + 2) {
|
||||
lines[currentLine - 1] = `${parseInt(number) + 1}. `
|
||||
} else {
|
||||
delete lines[currentLine - 2]
|
||||
}
|
||||
}
|
||||
|
||||
this.state = lines.join('\n')
|
||||
|
||||
this.render()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import Trix from 'trix/dist/trix'
|
||||
|
||||
import 'trix/dist/trix.css'
|
||||
import '../../css/components/rich-editor.css'
|
||||
|
||||
Trix.config.blockAttributes.default.tagName = 'p'
|
||||
|
||||
Trix.config.blockAttributes.default.breakOnReturn = true
|
||||
@@ -42,22 +39,20 @@ Trix.LineBreakInsertion.prototype.shouldInsertBlockBreak = function () {
|
||||
}
|
||||
}
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.data('richEditorFormComponent', ({ state }) => {
|
||||
return {
|
||||
state,
|
||||
export default function richEditorFormComponent({ state }) {
|
||||
return {
|
||||
state,
|
||||
|
||||
init: function () {
|
||||
this.$refs.trix?.editor?.loadHTML(this.state)
|
||||
|
||||
this.$watch('state', () => {
|
||||
if (document.activeElement === this.$refs.trix) {
|
||||
return
|
||||
}
|
||||
|
||||
init: function () {
|
||||
this.$refs.trix?.editor?.loadHTML(this.state)
|
||||
|
||||
this.$watch('state', () => {
|
||||
if (document.activeElement === this.$refs.trix) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$refs.trix?.editor?.loadHTML(this.state)
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,259 +1,252 @@
|
||||
import Choices from 'choices.js'
|
||||
|
||||
import '../../css/components/select.css'
|
||||
export default function selectFormComponent({
|
||||
isHtmlAllowed,
|
||||
getOptionLabelUsing,
|
||||
getOptionLabelsUsing,
|
||||
getOptionsUsing,
|
||||
getSearchResultsUsing,
|
||||
isAutofocused,
|
||||
isMultiple,
|
||||
hasDynamicOptions,
|
||||
hasDynamicSearchResults,
|
||||
loadingMessage,
|
||||
maxItems,
|
||||
maxItemsMessage,
|
||||
noSearchResultsMessage,
|
||||
options,
|
||||
optionsLimit,
|
||||
placeholder,
|
||||
searchDebounce,
|
||||
searchingMessage,
|
||||
searchPrompt,
|
||||
state,
|
||||
}) {
|
||||
return {
|
||||
isSearching: false,
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.data(
|
||||
'selectFormComponent',
|
||||
({
|
||||
isHtmlAllowed,
|
||||
getOptionLabelUsing,
|
||||
getOptionLabelsUsing,
|
||||
getOptionsUsing,
|
||||
getSearchResultsUsing,
|
||||
isAutofocused,
|
||||
isMultiple,
|
||||
hasDynamicOptions,
|
||||
hasDynamicSearchResults,
|
||||
loadingMessage,
|
||||
maxItems,
|
||||
maxItemsMessage,
|
||||
noSearchResultsMessage,
|
||||
options,
|
||||
optionsLimit,
|
||||
placeholder,
|
||||
searchDebounce,
|
||||
searchingMessage,
|
||||
searchPrompt,
|
||||
state,
|
||||
}) => {
|
||||
return {
|
||||
isSearching: false,
|
||||
select: null,
|
||||
|
||||
select: null,
|
||||
selectedOptions: [],
|
||||
|
||||
selectedOptions: [],
|
||||
isStateBeingUpdated: false,
|
||||
|
||||
isStateBeingUpdated: false,
|
||||
state,
|
||||
|
||||
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,
|
||||
removeItemButton: true,
|
||||
renderChoiceLimit: optionsLimit,
|
||||
searchFields: ['label'],
|
||||
searchResultLimit: optionsLimit,
|
||||
shouldSort: false,
|
||||
})
|
||||
|
||||
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,
|
||||
removeItemButton: true,
|
||||
renderChoiceLimit: optionsLimit,
|
||||
searchFields: ['label'],
|
||||
searchResultLimit: optionsLimit,
|
||||
shouldSort: false,
|
||||
})
|
||||
await this.refreshChoices({ withInitialOptions: true })
|
||||
|
||||
await this.refreshChoices({ withInitialOptions: true })
|
||||
if (![null, undefined, ''].includes(this.state)) {
|
||||
this.select.setChoiceByValue(
|
||||
this.formatState(this.state),
|
||||
)
|
||||
}
|
||||
|
||||
if (![null, undefined, ''].includes(this.state)) {
|
||||
this.select.setChoiceByValue(
|
||||
this.formatState(this.state),
|
||||
)
|
||||
}
|
||||
this.refreshPlaceholder()
|
||||
|
||||
this.refreshPlaceholder()
|
||||
if (isAutofocused) {
|
||||
this.select.showDropdown()
|
||||
}
|
||||
|
||||
if (isAutofocused) {
|
||||
this.select.showDropdown()
|
||||
}
|
||||
this.$refs.input.addEventListener('change', () => {
|
||||
this.refreshPlaceholder()
|
||||
|
||||
this.$refs.input.addEventListener('change', () => {
|
||||
this.refreshPlaceholder()
|
||||
if (this.isStateBeingUpdated) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.isStateBeingUpdated) {
|
||||
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()
|
||||
|
||||
if ([null, undefined, ''].includes(search)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isStateBeingUpdated = true
|
||||
this.state = this.select.getValue(true) ?? null
|
||||
this.$nextTick(() => (this.isStateBeingUpdated = false))
|
||||
})
|
||||
this.isSearching = true
|
||||
|
||||
if (hasDynamicOptions) {
|
||||
this.$refs.input.addEventListener(
|
||||
'showDropdown',
|
||||
async () => {
|
||||
this.select.clearChoices()
|
||||
await this.select.setChoices([
|
||||
{
|
||||
label: loadingMessage,
|
||||
value: '',
|
||||
disabled: true,
|
||||
},
|
||||
])
|
||||
|
||||
await this.refreshChoices()
|
||||
this.select.clearChoices()
|
||||
await this.select.setChoices([
|
||||
{
|
||||
label: searchingMessage,
|
||||
value: '',
|
||||
disabled: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
])
|
||||
},
|
||||
)
|
||||
|
||||
if (hasDynamicSearchResults) {
|
||||
this.$refs.input.addEventListener(
|
||||
'search',
|
||||
async (event) => {
|
||||
let search = event.detail.value?.trim()
|
||||
|
||||
if ([null, undefined, ''].includes(search)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isSearching = true
|
||||
|
||||
this.select.clearChoices()
|
||||
await this.select.setChoices([
|
||||
{
|
||||
label: 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),
|
||||
)
|
||||
}
|
||||
|
||||
this.$watch('state', async () => {
|
||||
this.refreshPlaceholder()
|
||||
|
||||
if (this.isStateBeingUpdated) {
|
||||
return
|
||||
}
|
||||
|
||||
const choices = await this.getChoices({
|
||||
withInitialOptions: !hasDynamicOptions,
|
||||
this.$refs.input.addEventListener(
|
||||
'search',
|
||||
Alpine.debounce(async (event) => {
|
||||
await this.refreshChoices({
|
||||
search: event.detail.value?.trim(),
|
||||
})
|
||||
|
||||
this.select.clearStore()
|
||||
|
||||
this.setChoices(choices)
|
||||
|
||||
if (![null, undefined, ''].includes(this.state)) {
|
||||
this.select.setChoiceByValue(
|
||||
this.formatState(this.state),
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
refreshChoices: async function (config = {}) {
|
||||
this.setChoices(await this.getChoices(config))
|
||||
},
|
||||
|
||||
setChoices: function (choices) {
|
||||
this.select.setChoices(choices, 'value', 'label', true)
|
||||
},
|
||||
|
||||
getChoices: async function (config = {}) {
|
||||
const existingOptions = await this.getOptions(config)
|
||||
|
||||
return existingOptions.concat(
|
||||
await this.getMissingOptions(existingOptions),
|
||||
)
|
||||
},
|
||||
|
||||
getOptions: async function ({ search, withInitialOptions }) {
|
||||
if (withInitialOptions) {
|
||||
return options
|
||||
}
|
||||
|
||||
if (
|
||||
search !== '' &&
|
||||
search !== null &&
|
||||
search !== undefined
|
||||
) {
|
||||
return await getSearchResultsUsing(search)
|
||||
}
|
||||
|
||||
return await getOptionsUsing()
|
||||
},
|
||||
|
||||
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.length
|
||||
? existingOptions.map((option) => option.value)
|
||||
: [],
|
||||
)
|
||||
|
||||
if (isMultiple) {
|
||||
if (
|
||||
state.every((value) =>
|
||||
existingOptionValues.has(value),
|
||||
)
|
||||
) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return await getOptionLabelsUsing()
|
||||
}
|
||||
|
||||
if (existingOptionValues.has(state)) {
|
||||
return existingOptionValues
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: await getOptionLabelUsing(),
|
||||
value: state,
|
||||
},
|
||||
]
|
||||
},
|
||||
this.isSearching = false
|
||||
}, searchDebounce),
|
||||
)
|
||||
}
|
||||
|
||||
this.$watch('state', async () => {
|
||||
this.refreshPlaceholder()
|
||||
|
||||
if (this.isStateBeingUpdated) {
|
||||
return
|
||||
}
|
||||
|
||||
const choices = await this.getChoices({
|
||||
withInitialOptions: !hasDynamicOptions,
|
||||
})
|
||||
|
||||
this.select.clearStore()
|
||||
|
||||
this.setChoices(choices)
|
||||
|
||||
if (![null, undefined, ''].includes(this.state)) {
|
||||
this.select.setChoiceByValue(
|
||||
this.formatState(this.state),
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
refreshChoices: async function (config = {}) {
|
||||
this.setChoices(await this.getChoices(config))
|
||||
},
|
||||
|
||||
setChoices: function (choices) {
|
||||
this.select.setChoices(choices, 'value', 'label', true)
|
||||
},
|
||||
|
||||
getChoices: async function (config = {}) {
|
||||
const existingOptions = await this.getOptions(config)
|
||||
|
||||
return existingOptions.concat(
|
||||
await this.getMissingOptions(existingOptions),
|
||||
)
|
||||
},
|
||||
|
||||
getOptions: async function ({ search, withInitialOptions }) {
|
||||
if (withInitialOptions) {
|
||||
return options
|
||||
}
|
||||
|
||||
if (
|
||||
search !== '' &&
|
||||
search !== null &&
|
||||
search !== undefined
|
||||
) {
|
||||
return await getSearchResultsUsing(search)
|
||||
}
|
||||
|
||||
return await getOptionsUsing()
|
||||
},
|
||||
|
||||
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.length
|
||||
? existingOptions.map((option) => option.value)
|
||||
: [],
|
||||
)
|
||||
|
||||
if (isMultiple) {
|
||||
if (
|
||||
state.every((value) =>
|
||||
existingOptionValues.has(value),
|
||||
)
|
||||
) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return await getOptionLabelsUsing()
|
||||
}
|
||||
|
||||
if (existingOptionValues.has(state)) {
|
||||
return existingOptionValues
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: await getOptionLabelUsing(),
|
||||
value: state,
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,29 @@
|
||||
import '../../css/components/tags-input.css'
|
||||
export default function tagsInputFormComponent ({ state }) {
|
||||
return {
|
||||
newTag: '',
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.data('tagsInputFormComponent', ({ state }) => {
|
||||
return {
|
||||
newTag: '',
|
||||
state,
|
||||
|
||||
state,
|
||||
createTag: function () {
|
||||
this.newTag = this.newTag.trim()
|
||||
|
||||
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)
|
||||
if (this.newTag === '') {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.state.includes(this.newTag)) {
|
||||
this.newTag = ''
|
||||
},
|
||||
|
||||
deleteTag: function (tagToDelete) {
|
||||
this.state = this.state.filter((tag) => tag !== tagToDelete)
|
||||
},
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.state.push(this.newTag)
|
||||
|
||||
this.newTag = ''
|
||||
},
|
||||
|
||||
deleteTag: function (tagToDelete) {
|
||||
this.state = this.state.filter((tag) => tag !== tagToDelete)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,40 @@
|
||||
import IMask from 'imask'
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.data('textInputFormComponent', ({ getMaskOptionsUsing, state }) => {
|
||||
return {
|
||||
isStateBeingUpdated: false,
|
||||
export default function textInputFormComponent({ getMaskOptionsUsing, state }) {
|
||||
return {
|
||||
isStateBeingUpdated: false,
|
||||
|
||||
mask: null,
|
||||
mask: null,
|
||||
|
||||
state,
|
||||
state,
|
||||
|
||||
init: function () {
|
||||
if (!getMaskOptionsUsing) {
|
||||
init: function () {
|
||||
if (!getMaskOptionsUsing) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.state) {
|
||||
this.$el.value = this.state?.valueOf()
|
||||
}
|
||||
|
||||
this.mask = IMask(this.$el, getMaskOptionsUsing(IMask)).on(
|
||||
'accept',
|
||||
() => {
|
||||
this.isStateBeingUpdated = true
|
||||
|
||||
this.state = this.mask.unmaskedValue
|
||||
|
||||
this.$nextTick(() => (this.isStateBeingUpdated = false))
|
||||
},
|
||||
)
|
||||
|
||||
this.$watch('state', () => {
|
||||
if (this.isStateBeingUpdated) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.state) {
|
||||
this.$el.value = this.state?.valueOf()
|
||||
}
|
||||
|
||||
this.mask = IMask(this.$el, getMaskOptionsUsing(IMask)).on(
|
||||
'accept',
|
||||
() => {
|
||||
this.isStateBeingUpdated = true
|
||||
|
||||
this.state = this.mask.unmaskedValue
|
||||
|
||||
this.$nextTick(() => (this.isStateBeingUpdated = false))
|
||||
},
|
||||
)
|
||||
|
||||
this.$watch('state', () => {
|
||||
if (this.isStateBeingUpdated) {
|
||||
return
|
||||
}
|
||||
|
||||
this.mask.unmaskedValue = this.state?.valueOf() ?? ''
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
||||
this.mask.unmaskedValue = this.state?.valueOf() ?? ''
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default (Alpine) => {
|
||||
Alpine.data('textareaFormComponent', () => ({
|
||||
export default function textareaFormComponent() {
|
||||
return {
|
||||
init: function () {
|
||||
this.$nextTick(() => {
|
||||
this.render()
|
||||
@@ -12,5 +12,5 @@ export default (Alpine) => {
|
||||
this.$el.style.height = this.$el.scrollHeight + 2 + 'px'
|
||||
}
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,131 +1,13 @@
|
||||
import ColorPickerFormComponentAlpinePlugin from './components/color-picker'
|
||||
import DateTimePickerFormComponentAlpinePlugin from './components/date-time-picker'
|
||||
import FileUploadFormComponentAlpinePlugin from './components/file-upload'
|
||||
import KeyValueFormComponentAlpinePlugin from './components/key-value'
|
||||
import MarkdownEditorFormComponentAlpinePlugin from './components/markdown-editor'
|
||||
import RichEditorFormComponentAlpinePlugin from './components/rich-editor'
|
||||
import SelectFormComponentAlpinePlugin from './components/select'
|
||||
import TagsInputFormComponentAlpinePlugin from './components/tags-input'
|
||||
import TextInputFormComponentAlpinePlugin from './components/text-input'
|
||||
import TextareaFormComponentAlpinePlugin from './components/textarea'
|
||||
import SortableAlpinePlugin from './sortable'
|
||||
import AlpineFloatingUI from '@awcodes/alpine-floating-ui'
|
||||
import '../css/components/color-picker.css'
|
||||
|
||||
// https://github.com/laravel/framework/blob/5299c22321c0f1ea8ff770b84a6c6469c4d6edec/src/Illuminate/Translation/MessageSelector.php#L15
|
||||
window.pluralize = function (text, number, variables) {
|
||||
function extract(segments, number) {
|
||||
for (const part of segments) {
|
||||
const line = extractFromString(part, number)
|
||||
import 'filepond/dist/filepond.min.css'
|
||||
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css'
|
||||
import 'filepond-plugin-media-preview/dist/filepond-plugin-media-preview.css'
|
||||
import '../css/components/file-upload.css'
|
||||
|
||||
if (line !== null) {
|
||||
return line
|
||||
}
|
||||
}
|
||||
}
|
||||
import 'trix/dist/trix.css'
|
||||
import '../css/components/rich-editor.css'
|
||||
|
||||
function extractFromString(part, number) {
|
||||
const matches = part.match(/^[\{\[]([^\[\]\{\}]*)[\}\]](.*)/s)
|
||||
import '../css/components/select.css'
|
||||
|
||||
if (matches === null || matches.length !== 3) {
|
||||
return null
|
||||
}
|
||||
|
||||
const condition = matches[1]
|
||||
|
||||
const value = matches[2]
|
||||
|
||||
if (condition.includes(',')) {
|
||||
const [from, to] = condition.split(',', 2)
|
||||
|
||||
if (to === '*' && number >= from) {
|
||||
return value
|
||||
} else if (from === '*' && number <= to) {
|
||||
return value
|
||||
} else if (number >= from && number <= to) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
return condition == number ? value : null
|
||||
}
|
||||
|
||||
function ucfirst(string) {
|
||||
return (
|
||||
string.toString().charAt(0).toUpperCase() +
|
||||
string.toString().slice(1)
|
||||
)
|
||||
}
|
||||
|
||||
function replace(line, replace) {
|
||||
if (replace.length === 0) {
|
||||
return line
|
||||
}
|
||||
|
||||
const shouldReplace = {}
|
||||
|
||||
for (let [key, value] of Object.entries(replace)) {
|
||||
shouldReplace[':' + ucfirst(key ?? '')] = ucfirst(value ?? '')
|
||||
shouldReplace[':' + key.toUpperCase()] = value
|
||||
.toString()
|
||||
.toUpperCase()
|
||||
shouldReplace[':' + key] = value
|
||||
}
|
||||
|
||||
Object.entries(shouldReplace).forEach(([key, value]) => {
|
||||
line = line.replaceAll(key, value)
|
||||
})
|
||||
|
||||
return line
|
||||
}
|
||||
|
||||
function stripConditions(segments) {
|
||||
return segments.map((part) =>
|
||||
part.replace(/^[\{\[]([^\[\]\{\}]*)[\}\]]/, ''),
|
||||
)
|
||||
}
|
||||
|
||||
let segments = text.split('|')
|
||||
|
||||
const value = extract(segments, number)
|
||||
|
||||
if (value !== null && value !== undefined) {
|
||||
return replace(value.trim(), variables)
|
||||
}
|
||||
|
||||
segments = stripConditions(segments)
|
||||
|
||||
return replace(
|
||||
segments.length > 1 && number > 1 ? segments[1] : segments[0],
|
||||
variables,
|
||||
)
|
||||
}
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.plugin(ColorPickerFormComponentAlpinePlugin)
|
||||
Alpine.plugin(DateTimePickerFormComponentAlpinePlugin)
|
||||
Alpine.plugin(FileUploadFormComponentAlpinePlugin)
|
||||
Alpine.plugin(KeyValueFormComponentAlpinePlugin)
|
||||
Alpine.plugin(MarkdownEditorFormComponentAlpinePlugin)
|
||||
Alpine.plugin(RichEditorFormComponentAlpinePlugin)
|
||||
Alpine.plugin(SelectFormComponentAlpinePlugin)
|
||||
Alpine.plugin(TagsInputFormComponentAlpinePlugin)
|
||||
Alpine.plugin(TextInputFormComponentAlpinePlugin)
|
||||
Alpine.plugin(TextareaFormComponentAlpinePlugin)
|
||||
Alpine.plugin(SortableAlpinePlugin)
|
||||
Alpine.plugin(AlpineFloatingUI)
|
||||
}
|
||||
|
||||
export {
|
||||
ColorPickerFormComponentAlpinePlugin,
|
||||
DateTimePickerFormComponentAlpinePlugin,
|
||||
FileUploadFormComponentAlpinePlugin,
|
||||
KeyValueFormComponentAlpinePlugin,
|
||||
MarkdownEditorFormComponentAlpinePlugin,
|
||||
RichEditorFormComponentAlpinePlugin,
|
||||
SelectFormComponentAlpinePlugin,
|
||||
TagsInputFormComponentAlpinePlugin,
|
||||
TextInputFormComponentAlpinePlugin,
|
||||
TextareaFormComponentAlpinePlugin,
|
||||
SortableAlpinePlugin,
|
||||
AlpineFloatingUI,
|
||||
}
|
||||
import '../css/components/tags-input.css'
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
@endif
|
||||
|
||||
<div
|
||||
x-ignore
|
||||
ax-load="visible"
|
||||
ax-load-src="/js/filament/forms/components/color-picker.js"
|
||||
x-data="colorPickerFormComponent({
|
||||
isAutofocused: @js($isAutofocused()),
|
||||
isDisabled: @js($isDisabled()),
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
:state-path="$getStatePath()"
|
||||
>
|
||||
<div
|
||||
x-ignore
|
||||
ax-load="visible"
|
||||
ax-load-src="/js/filament/forms/components/date-time-picker.js"
|
||||
x-data="dateTimePickerFormComponent({
|
||||
displayFormat: '{{ convert_date_format($getDisplayFormat())->to('day.js') }}',
|
||||
firstDayOfWeek: {{ $getFirstDayOfWeek() }},
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
$shouldTransformImage = $imageCropAspectRatio || $imageResizeTargetHeight || $imageResizeTargetWidth;
|
||||
@endphp
|
||||
<div
|
||||
x-ignore
|
||||
ax-load="visible"
|
||||
ax-load-src="/js/filament/forms/components/file-upload.js"
|
||||
x-data="fileUploadFormComponent({
|
||||
acceptedFileTypes: {{ json_encode($getAcceptedFileTypes()) }},
|
||||
canDownload: {{ $canDownload() ? 'true' : 'false' }},
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
:state-path="$getStatePath()"
|
||||
>
|
||||
<div
|
||||
x-ignore
|
||||
ax-load="visible"
|
||||
ax-load-src="/js/filament/forms/components/key-value.js"
|
||||
x-data="keyValueFormComponent({
|
||||
state: $wire.{{ $applyStateBindingModifiers('entangle(\'' . $getStatePath() . '\')') }},
|
||||
})"
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
:state-path="$getStatePath()"
|
||||
>
|
||||
<div
|
||||
x-ignore
|
||||
ax-load="visible"
|
||||
ax-load-src="/js/filament/forms/components/markdown-editor.js"
|
||||
x-data="markdownEditorFormComponent({
|
||||
state: $wire.{{ $applyStateBindingModifiers('entangle(\'' . $getStatePath() . '\')') }},
|
||||
tab: '{{ $isDisabled() ? 'preview' : 'edit' }}',
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
:state-path="$getStatePath()"
|
||||
>
|
||||
<div
|
||||
x-ignore
|
||||
ax-load="visible"
|
||||
ax-load-src="/js/filament/forms/components/rich-editor.js"
|
||||
x-data="richEditorFormComponent({
|
||||
state: $wire.{{ $applyStateBindingModifiers('entangle(\'' . $getStatePath() . '\')') }},
|
||||
})"
|
||||
|
||||
@@ -68,6 +68,9 @@
|
||||
</select>
|
||||
@else
|
||||
<div
|
||||
x-ignore
|
||||
ax-load="visible"
|
||||
ax-load-src="/js/filament/forms/components/select.js"
|
||||
x-data="selectFormComponent({
|
||||
isHtmlAllowed: @js($isHtmlAllowed()),
|
||||
getOptionLabelUsing: async () => {
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
:state-path="$getStatePath()"
|
||||
>
|
||||
<div
|
||||
x-ignore
|
||||
ax-load="visible"
|
||||
ax-load-src="/js/filament/forms/components/tags-input.js"
|
||||
x-data="tagsInputFormComponent({
|
||||
state: $wire.{{ $applyStateBindingModifiers('entangle(\'' . $getStatePath() . '\')') }},
|
||||
})"
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
{{ $applyStateBindingModifiers('wire:model') }}="{{ $getStatePath() }}"
|
||||
type="{{ $getType() }}"
|
||||
@else
|
||||
x-ignore
|
||||
ax-load="visible"
|
||||
ax-load-src="/js/filament/forms/components/text-input.js"
|
||||
x-data="textInputFormComponent({
|
||||
{{ $hasMask() ? "getMaskOptionsUsing: (IMask) => ({$getJsonMaskConfiguration()})," : null }}
|
||||
state: $wire.{{ $applyStateBindingModifiers('entangle(\'' . $getStatePath() . '\')', lazilyEntangledModifiers: ['defer']) }},
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
])
|
||||
}}
|
||||
@if ($shouldAutosize())
|
||||
x-ignore
|
||||
ax-load="visible"
|
||||
ax-load-src="/js/filament/forms/components/textarea.js"
|
||||
x-data="textareaFormComponent()"
|
||||
x-on:input="render()"
|
||||
style="height: 150px"
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
namespace Filament\Forms;
|
||||
|
||||
use Filament\Forms\Testing\TestsForms;
|
||||
use Filament\Support\Assets\AlpineComponent;
|
||||
use Filament\Support\Assets\Js;
|
||||
use Filament\Support\Facades\Asset;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Livewire\Testing\TestableLivewire;
|
||||
use Spatie\LaravelPackageTools\Package;
|
||||
@@ -45,6 +48,19 @@ class FormsServiceProvider extends PackageServiceProvider
|
||||
|
||||
public function packageBooted(): void
|
||||
{
|
||||
Asset::register([
|
||||
AlpineComponent::make('color-picker', __DIR__ . '/../dist/components/color-picker.js'),
|
||||
AlpineComponent::make('date-time-picker', __DIR__ . '/../dist/components/date-time-picker.js'),
|
||||
AlpineComponent::make('file-upload', __DIR__ . '/../dist/components/file-upload.js'),
|
||||
AlpineComponent::make('key-value', __DIR__ . '/../dist/components/key-value.js'),
|
||||
AlpineComponent::make('markdown-editor', __DIR__ . '/../dist/components/markdown-editor.js'),
|
||||
AlpineComponent::make('rich-editor', __DIR__ . '/../dist/components/rich-editor.js'),
|
||||
AlpineComponent::make('select', __DIR__ . '/../dist/components/select.js'),
|
||||
AlpineComponent::make('tags-input', __DIR__ . '/../dist/components/tags-input.js'),
|
||||
AlpineComponent::make('text-input', __DIR__ . '/../dist/components/text-input.js'),
|
||||
AlpineComponent::make('textarea', __DIR__ . '/../dist/components/textarea.js'),
|
||||
], 'forms');
|
||||
|
||||
if ($this->app->runningInConsole()) {
|
||||
foreach (app(Filesystem::class)->files(__DIR__ . '/../stubs/') as $file) {
|
||||
$this->publishes([
|
||||
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
@@ -9,13 +9,6 @@ window.NotificationAction = NotificationAction
|
||||
window.NotificationActionGroup = NotificationActionGroup
|
||||
window.Notification = Notification
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.plugin(NotificationComponentAlpinePlugin)
|
||||
}
|
||||
|
||||
export {
|
||||
NotificationAction,
|
||||
NotificationActionGroup,
|
||||
Notification,
|
||||
NotificationComponentAlpinePlugin,
|
||||
}
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.Alpine.plugin(NotificationComponentAlpinePlugin)
|
||||
})
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace Filament\Notifications;
|
||||
|
||||
use Filament\Notifications\Http\Livewire\Notifications;
|
||||
use Filament\Notifications\Testing\TestsNotifications;
|
||||
use Filament\Support\Assets\Js;
|
||||
use Filament\Support\Facades\Asset;
|
||||
use Livewire\Component;
|
||||
use Livewire\Livewire;
|
||||
use Livewire\Response;
|
||||
@@ -25,6 +27,10 @@ class NotificationsServiceProvider extends PackageServiceProvider
|
||||
|
||||
public function packageBooted(): void
|
||||
{
|
||||
Asset::register([
|
||||
Js::make('index', __DIR__ . '/../dist/index.js'),
|
||||
], 'notifications');
|
||||
|
||||
Livewire::component('notifications', Notifications::class);
|
||||
|
||||
Livewire::listen('component.dehydrate', function (Component $component, Response $response): Response {
|
||||
|
||||
Vendored
+7
File diff suppressed because one or more lines are too long
Vendored
+4202
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,102 @@
|
||||
import SortableAlpinePlugin from './sortable'
|
||||
import AlpineFloatingUI from '@awcodes/alpine-floating-ui'
|
||||
import AsyncAlpine from 'async-alpine/dist/async-alpine.esm'
|
||||
|
||||
// https://github.com/laravel/framework/blob/5299c22321c0f1ea8ff770b84a6c6469c4d6edec/src/Illuminate/Translation/MessageSelector.php#L15
|
||||
const pluralize = function (text, number, variables) {
|
||||
function extract(segments, number) {
|
||||
for (const part of segments) {
|
||||
const line = extractFromString(part, number)
|
||||
|
||||
if (line !== null) {
|
||||
return line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function extractFromString(part, number) {
|
||||
const matches = part.match(/^[\{\[]([^\[\]\{\}]*)[\}\]](.*)/s)
|
||||
|
||||
if (matches === null || matches.length !== 3) {
|
||||
return null
|
||||
}
|
||||
|
||||
const condition = matches[1]
|
||||
|
||||
const value = matches[2]
|
||||
|
||||
if (condition.includes(',')) {
|
||||
const [from, to] = condition.split(',', 2)
|
||||
|
||||
if (to === '*' && number >= from) {
|
||||
return value
|
||||
} else if (from === '*' && number <= to) {
|
||||
return value
|
||||
} else if (number >= from && number <= to) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
return condition == number ? value : null
|
||||
}
|
||||
|
||||
function ucfirst(string) {
|
||||
return (
|
||||
string.toString().charAt(0).toUpperCase() +
|
||||
string.toString().slice(1)
|
||||
)
|
||||
}
|
||||
|
||||
function replace(line, replace) {
|
||||
if (replace.length === 0) {
|
||||
return line
|
||||
}
|
||||
|
||||
const shouldReplace = {}
|
||||
|
||||
for (let [key, value] of Object.entries(replace)) {
|
||||
shouldReplace[':' + ucfirst(key ?? '')] = ucfirst(value ?? '')
|
||||
shouldReplace[':' + key.toUpperCase()] = value
|
||||
.toString()
|
||||
.toUpperCase()
|
||||
shouldReplace[':' + key] = value
|
||||
}
|
||||
|
||||
Object.entries(shouldReplace).forEach(([key, value]) => {
|
||||
line = line.replaceAll(key, value)
|
||||
})
|
||||
|
||||
return line
|
||||
}
|
||||
|
||||
function stripConditions(segments) {
|
||||
return segments.map((part) =>
|
||||
part.replace(/^[\{\[]([^\[\]\{\}]*)[\}\]]/, ''),
|
||||
)
|
||||
}
|
||||
|
||||
let segments = text.split('|')
|
||||
|
||||
const value = extract(segments, number)
|
||||
|
||||
if (value !== null && value !== undefined) {
|
||||
return replace(value.trim(), variables)
|
||||
}
|
||||
|
||||
segments = stripConditions(segments)
|
||||
|
||||
return replace(
|
||||
segments.length > 1 && number > 1 ? segments[1] : segments[0],
|
||||
variables,
|
||||
)
|
||||
}
|
||||
|
||||
window.pluralize = pluralize
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.Alpine.plugin(SortableAlpinePlugin)
|
||||
window.Alpine.plugin(AlpineFloatingUI)
|
||||
|
||||
AsyncAlpine.init(window.Alpine)
|
||||
AsyncAlpine.start()
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
@foreach ($assets as $asset)
|
||||
<script
|
||||
src="{{ $asset->getSrc() }}"
|
||||
@if ($asset->isAsync()) async @endif
|
||||
@if ($asset->isDeferred()) defer @endif
|
||||
></script>
|
||||
@endforeach
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
@foreach ($assets as $asset)
|
||||
<link rel="stylesheet" href="{{ $asset->getHref() }}" />
|
||||
@endforeach
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Support\Assets;
|
||||
|
||||
class AlpineComponent extends Asset
|
||||
{
|
||||
public function getPublicPath(): string
|
||||
{
|
||||
$path = '';
|
||||
|
||||
$package = $this->getPackage();
|
||||
|
||||
if (filled($package)) {
|
||||
$path .= $package;
|
||||
$path .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
$path .= 'components';
|
||||
$path .= DIRECTORY_SEPARATOR;
|
||||
$path .= "{$this->getName()}.js";
|
||||
|
||||
return public_path("js/filament/{$path}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Support\Assets;
|
||||
|
||||
abstract class Asset
|
||||
{
|
||||
protected string $name;
|
||||
|
||||
protected string $path;
|
||||
|
||||
protected ?string $package = null;
|
||||
|
||||
final public function __construct(string $name, string $path)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
public static function make(string $name, string $path): static
|
||||
{
|
||||
return new static($name, $path);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function package(?string $package): static
|
||||
{
|
||||
$this->package = $package;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPackage(): ?string
|
||||
{
|
||||
return $this->package;
|
||||
}
|
||||
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
abstract public function getPublicPath(): string;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Support\Assets;
|
||||
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class AssetManager
|
||||
{
|
||||
protected array $alpineComponents = [];
|
||||
|
||||
protected array $scripts = [];
|
||||
|
||||
protected array $styles = [];
|
||||
|
||||
public function register(array $assets, ?string $package = null): static
|
||||
{
|
||||
foreach ($assets as $asset) {
|
||||
$asset->package($package);
|
||||
|
||||
match ($asset::class) {
|
||||
AlpineComponent::class => $this->alpineComponents[$package][] = $asset,
|
||||
Css::class => $this->styles[$package][] = $asset,
|
||||
Js::class => $this->scripts[$package][] = $asset,
|
||||
};
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAlpineComponents(?array $packages = null): array
|
||||
{
|
||||
return $this->getAssets($this->alpineComponents, $packages);
|
||||
}
|
||||
|
||||
public function getScripts(?array $packages = null): array
|
||||
{
|
||||
return $this->getAssets($this->scripts, $packages);
|
||||
}
|
||||
|
||||
public function renderScripts(?array $packages = null): string
|
||||
{
|
||||
return view('filament-support::assets.scripts', [
|
||||
'assets' => $this->getScripts($packages),
|
||||
])->render();
|
||||
}
|
||||
|
||||
public function getStyles(?array $packages = null): array
|
||||
{
|
||||
return $this->getAssets($this->styles, $packages);
|
||||
}
|
||||
|
||||
public function renderStyles(?array $packages = null): string
|
||||
{
|
||||
return view('filament-support::assets.styles', [
|
||||
'assets' => $this->getStyles($packages),
|
||||
])->render();
|
||||
}
|
||||
|
||||
protected function getAssets(array $assets, ?array $packages = null): array
|
||||
{
|
||||
if ($packages === null) {
|
||||
return Arr::flatten($assets);
|
||||
}
|
||||
|
||||
$packages[] = null;
|
||||
|
||||
return Arr::only($assets, $packages);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Support\Assets;
|
||||
|
||||
class Css extends Asset
|
||||
{
|
||||
public function getHref(): string
|
||||
{
|
||||
$href = '/css/filament/';
|
||||
|
||||
$package = $this->getPackage();
|
||||
|
||||
if (filled($package)) {
|
||||
$href .= "{$package}/";
|
||||
}
|
||||
|
||||
$href .= "{$this->getName()}.css";
|
||||
|
||||
return $href;
|
||||
}
|
||||
|
||||
public function getPublicPath(): string
|
||||
{
|
||||
$path = '';
|
||||
|
||||
$package = $this->getPackage();
|
||||
|
||||
if (filled($package)) {
|
||||
$path .= $package;
|
||||
$path .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
$path .= "{$this->getName()}.css";
|
||||
|
||||
return public_path("css/filament/{$path}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Support\Assets;
|
||||
|
||||
class Js extends Asset
|
||||
{
|
||||
protected bool $isAsync = false;
|
||||
|
||||
protected bool $isDeferred = true;
|
||||
|
||||
public function async(bool $condition = true): static
|
||||
{
|
||||
$this->isAsync = $condition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function defer(bool $condition = true): static
|
||||
{
|
||||
$this->isDeferred = $condition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isAsync(): bool
|
||||
{
|
||||
return $this->isAsync;
|
||||
}
|
||||
|
||||
public function isDeferred(): bool
|
||||
{
|
||||
return $this->isDeferred;
|
||||
}
|
||||
|
||||
public function getSrc(): string
|
||||
{
|
||||
$href = '/js/filament/';
|
||||
|
||||
$package = $this->getPackage();
|
||||
|
||||
if (filled($package)) {
|
||||
$href .= "{$package}/";
|
||||
}
|
||||
|
||||
$href .= "{$this->getName()}.js";
|
||||
|
||||
return $href;
|
||||
}
|
||||
|
||||
public function getPublicPath(): string
|
||||
{
|
||||
$path = '';
|
||||
|
||||
$package = $this->getPackage();
|
||||
|
||||
if (filled($package)) {
|
||||
$path .= $package;
|
||||
$path .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
$path .= "{$this->getName()}.js";
|
||||
|
||||
return public_path("js/filament/{$path}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Support\Commands;
|
||||
|
||||
use Filament\Support\Commands\Concerns\CanManipulateFiles;
|
||||
use Filament\Support\Facades\Asset;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class AssetsCommand extends Command
|
||||
{
|
||||
use CanManipulateFiles;
|
||||
|
||||
protected $description = 'Set up Filament assets.';
|
||||
|
||||
protected $signature = 'filament:assets';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
foreach (Asset::getAlpineComponents() as $asset) {
|
||||
$this->copyAsset($asset->getPath(), $asset->getPublicPath());
|
||||
}
|
||||
|
||||
foreach (Asset::getScripts() as $asset) {
|
||||
$this->copyAsset($asset->getPath(), $asset->getPublicPath());
|
||||
}
|
||||
|
||||
foreach (Asset::getStyles() as $asset) {
|
||||
$this->copyAsset($asset->getPath(), $asset->getPublicPath());
|
||||
}
|
||||
|
||||
$this->components->info('Successfully published assets!');
|
||||
|
||||
return static::SUCCESS;
|
||||
}
|
||||
|
||||
protected function copyAsset(string $from, string $to): void
|
||||
{
|
||||
$filesystem = app(Filesystem::class);
|
||||
|
||||
$filesystem->ensureDirectoryExists(
|
||||
(string) Str::of($to)
|
||||
->beforeLast(DIRECTORY_SEPARATOR),
|
||||
);
|
||||
|
||||
$filesystem->copy($from, $to);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ class UpgradeCommand extends Command
|
||||
public function handle(): int
|
||||
{
|
||||
foreach ([
|
||||
'filament:assets',
|
||||
'config:clear',
|
||||
'livewire:discover',
|
||||
'route:clear',
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Support\Facades;
|
||||
|
||||
use Filament\Support\Assets\AssetManager;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class Asset extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor()
|
||||
{
|
||||
return AssetManager::class;
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,12 @@
|
||||
namespace Filament\Support;
|
||||
|
||||
use Composer\InstalledVersions;
|
||||
use Filament\Support\Assets\AssetManager;
|
||||
use Filament\Support\Assets\Js;
|
||||
use Filament\Support\Commands\AssetsCommand;
|
||||
use Filament\Support\Commands\CheckTranslationsCommand;
|
||||
use Filament\Support\Commands\UpgradeCommand;
|
||||
use Filament\Support\Facades\Asset;
|
||||
use Filament\Support\Testing\TestsActions;
|
||||
use HtmlSanitizer\Sanitizer;
|
||||
use HtmlSanitizer\SanitizerInterface;
|
||||
@@ -23,6 +27,7 @@ class SupportServiceProvider extends PackageServiceProvider
|
||||
$package
|
||||
->name('filament-support')
|
||||
->hasCommands([
|
||||
AssetsCommand::class,
|
||||
CheckTranslationsCommand::class,
|
||||
UpgradeCommand::class,
|
||||
])
|
||||
@@ -33,6 +38,13 @@ class SupportServiceProvider extends PackageServiceProvider
|
||||
|
||||
public function packageRegistered()
|
||||
{
|
||||
$this->app->scoped(
|
||||
AssetManager::class,
|
||||
function () {
|
||||
return new AssetManager();
|
||||
},
|
||||
);
|
||||
|
||||
$this->app->scoped(
|
||||
SanitizerInterface::class,
|
||||
function () {
|
||||
@@ -45,10 +57,22 @@ class SupportServiceProvider extends PackageServiceProvider
|
||||
|
||||
public function packageBooted()
|
||||
{
|
||||
Asset::register([
|
||||
Js::make('index', __DIR__ . '/../dist/index.js'),
|
||||
]);
|
||||
|
||||
Blade::directive('captureSlots', function (string $expression): string {
|
||||
return "<?php \$slotContents = get_defined_vars(); \$slots = collect({$expression})->mapWithKeys(fn (string \$slot): array => [\$slot => \$slotContents[\$slot] ?? null])->all(); unset(\$slotContents) ?>";
|
||||
});
|
||||
|
||||
Blade::directive('filamentScripts', function (string $expression): string {
|
||||
return "<?php echo \Filament\Support\Facades\Asset::renderScripts({$expression}) ?>";
|
||||
});
|
||||
|
||||
Blade::directive('filamentStyles', function (string $expression): string {
|
||||
return "<?php echo \Filament\Support\Facades\Asset::renderStyles({$expression}) ?>";
|
||||
});
|
||||
|
||||
Str::macro('lcfirst', function (string $string): string {
|
||||
return Str::lower(Str::substr($string, 0, 1)) . Str::substr($string, 1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user