Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions app/Livewire/FileManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace App\Livewire;

use App\Models\Page;
use Flux\Flux;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Livewire\Attributes\Computed;
Expand Down Expand Up @@ -210,7 +209,7 @@ public function createFileAndClose(): void

$this->disk()->put($full, '');
$this->newFileName = '';
Flux::modal('new-file')->close();
$this->dispatch('close-modal');
$this->openFile($relative);
unset($this->fileTree);
}
Expand Down Expand Up @@ -238,7 +237,7 @@ public function createFolderAndClose(): void
$this->disk()->makeDirectory($this->page->storagePath($relative));

$this->newFolderName = '';
Flux::modal('new-folder')->close();
$this->dispatch('close-modal');
$this->showFlash("Folder '{$name}' created.");
unset($this->fileTree);
}
Expand All @@ -250,7 +249,7 @@ public function prepareDelete(string $path, string $type = 'file'): void
$this->deleteTarget = $path;
$this->deleteTargetName = basename($path);
$this->deleteTargetType = $type;
Flux::modal('delete-confirm')->show();
$this->dispatch('open-modal', name: 'delete-confirm');
}

/** Execute the pending delete after modal confirmation. */
Expand All @@ -275,7 +274,7 @@ public function executeDelete(): void
$this->showFlash("Deleted {$this->deleteTargetName}.");
$this->deleteTarget = '';
$this->deleteTargetName = '';
Flux::modal('delete-confirm')->close();
$this->dispatch('close-modal');
unset($this->fileTree);
}

Expand Down Expand Up @@ -339,7 +338,7 @@ public function uploadFile(): void

$this->upload = null;
$this->showFlash("Uploaded {$name}.");
Flux::modal('upload')->close();
$this->dispatch('close-modal');
unset($this->fileTree);
}

Expand All @@ -360,7 +359,7 @@ public function saveSettings(): void
]);

$this->page->refresh();
Flux::modal('settings')->close();
$this->dispatch('close-modal');
$this->showFlash('Settings saved.');

if ($this->page->wasChanged('slug')) {
Expand Down
92 changes: 59 additions & 33 deletions resources/js/file-manager.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/**
* file-manager.js — loaded only on the file manager page.
* file-manager.js — loaded only on the file manager page.
*
* Alpine is provided by @fluxScripts (bundled with Livewire/Flux).
* We must NOT import or start our own Alpine — Flux registers its modal
* plugin on its own Alpine instance. We hook into it via alpine:init.
* Alpine is provided by @livewireScripts (Livewire 4 bundles Alpine 3).
* We register our data component via alpine:init — that hooks into
* Livewire's Alpine instance after it is created but before DOM init.
*
* CodeMirror 6 is tree-shaken and bundled here by Vite.
* Modals are plain Alpine x-show panels — no Flux Pro required.
*/

import { EditorState, Compartment } from '@codemirror/state';
import { EditorState, Compartment } from '@codemirror/state';
import {
EditorView, keymap, lineNumbers,
highlightActiveLineGutter, highlightActiveLine,
Expand All @@ -22,14 +22,14 @@ import {
indentOnInput, bracketMatching, foldGutter,
syntaxHighlighting, defaultHighlightStyle,
} from '@codemirror/language';
import { html } from '@codemirror/lang-html';
import { css } from '@codemirror/lang-css';
import { javascript } from '@codemirror/lang-javascript';
import { json } from '@codemirror/lang-json';
import { xml } from '@codemirror/lang-xml';
import { oneDark } from '@codemirror/theme-one-dark';
import { html } from '@codemirror/lang-html';
import { css } from '@codemirror/lang-css';
import { javascript } from '@codemirror/lang-javascript';
import { json } from '@codemirror/lang-json';
import { xml } from '@codemirror/lang-xml';
import { oneDark } from '@codemirror/theme-one-dark';

// ── CodeMirror state ──────────────────────────────────────────────────────────
// ── CodeMirror ────────────────────────────────────────────────────────────────

const langCompartment = new Compartment();
let editorView = null;
Expand All @@ -39,7 +39,7 @@ function publishCursor(state) {
const head = state.selection.main.head;
const line = state.doc.lineAt(head);
window.dispatchEvent(new CustomEvent('cm-cursor', {
detail: { line: line.number, col: head - line.from + 1, total: state.doc.lines },
detail: { line: line.number, col: head - line.from + 1 },
}));
}

Expand All @@ -64,10 +64,9 @@ function triggerSave() {
getWire()?.call('saveFile', editorView.state.doc.toString());
}

export function createEditor(content, lang) {
function createEditor(content, lang) {
const host = document.getElementById('cm-host');
if (!host) return;

editorView?.destroy();
editorView = null;

Expand All @@ -78,7 +77,7 @@ export function createEditor(content, lang) {
oneDark,
EditorView.theme({
'&': { height: '100%' },
'.cm-scroller': { overflow: 'auto', fontFamily: "ui-monospace, 'Cascadia Code', 'JetBrains Mono', Menlo, monospace" },
'.cm-scroller': { overflow: 'auto', fontFamily: "ui-monospace,'Cascadia Code','JetBrains Mono',Menlo,monospace" },
'.cm-content': { padding: '8px 0' },
'.cm-gutters': { background: 'transparent', border: 'none' },
}),
Expand Down Expand Up @@ -117,45 +116,72 @@ export function createEditor(content, lang) {
publishCursor(editorView.state);
}

// ── Alpine component — registered on Flux/Livewire's Alpine instance ──────────
//
// We use alpine:init so our data component is registered BEFORE Alpine
// processes the DOM, but AFTER Flux has registered its own plugins
// (including x-flux-modal and $dispatch helpers).
// ── Alpine component ──────────────────────────────────────────────────────────

document.addEventListener('alpine:init', () => {
// Safety guard: window.Alpine is set by @fluxScripts before this fires.
if (!window.Alpine) return;

window.Alpine.data('fileManager', () => ({
// Modal state — name of currently open modal, or null
modal: null,

// File tree — which directories are expanded
openDirs: [],

// Context-menu state
ctx: { show: false, x: 0, y: 0, item: null },

// Status-bar cursor info
cursor: { line: 1, col: 1 },

isOpen(path) { return this.openDirs.includes(path); },
toggleDir(path) {
// Drag-and-drop state
dragOver: false,

// ── Modal helpers ────────────────────────────────────────────────
openModal(name) { this.modal = name; },
closeModal() { this.modal = null; },
isModal(name) { return this.modal === name; },

// ── File-tree helpers ────────────────────────────────────────────
isOpen(path) { return this.openDirs.includes(path); },
toggleDir(path) {
this.openDirs = this.isOpen(path)
? this.openDirs.filter(p => p !== path)
: [...this.openDirs, path];
},

openContext(e, item) {
// ── Context menu ─────────────────────────────────────────────────
openCtx(e, item) {
e.preventDefault();
this.ctx = { show: true, x: e.clientX, y: e.clientY, item };
},
closeContext() {
this.ctx = { show: false, x: 0, y: 0, item: null };
closeCtx() { this.ctx.show = false; },

// ── Drag-and-drop upload ─────────────────────────────────────────
onDrop(e) {
this.dragOver = false;
const files = Array.from(e.dataTransfer?.files ?? []);
if (!files.length) return;
files.forEach(file => {
const wire = getWire();
if (wire) wire.upload('upload', file, () => {}, () => {}, () => {});
});
},

// ── Init ─────────────────────────────────────────────────────────
init() {
// CodeMirror events
window.addEventListener('cm-cursor', e => { this.cursor = e.detail; });

window.addEventListener('load-editor', e => {
createEditor(e.detail.content, e.detail.language);
});

window.addEventListener('click', () => this.closeContext());
// Livewire → Alpine modal control
window.addEventListener('open-modal', e => this.openModal(e.detail?.name));
window.addEventListener('close-modal', () => this.closeModal());

// Close context menu on click/Escape
window.addEventListener('click', () => this.closeCtx());
window.addEventListener('keydown', e => {
if (e.key === 'Escape') this.closeContext();
if (e.key === 'Escape') { this.closeCtx(); this.closeModal(); }
});
},

Expand Down
Binary file added resources/views/.DS_Store
Binary file not shown.
Loading
Loading