]> _ Git - fluidbook-toolbox.git/commitdiff
wip #7634 @6:00
authorsoufiane <soufiane@cubedesigners.com>
Mon, 21 Jul 2025 16:59:36 +0000 (18:59 +0200)
committersoufiane <soufiane@cubedesigners.com>
Mon, 21 Jul 2025 16:59:36 +0000 (18:59 +0200)
app/Http/Controllers/Admin/Operations/FluidbookPublication/MarkdownOperation.php
resources/markdowneditor/js/markdowneditor.js
resources/markdowneditor/js/markdowneditor.save.js [new file with mode: 0644]
resources/markdowneditor/js/markdowneditor.undo.js [new file with mode: 0644]
resources/views/fluidbook_publication/markdown_editor.blade.php

index feaa43cdec5a782a277ff13b7691f7fd0c9ed79d..11af21df2d0c776232e53cc4533c06a653915ac3 100644 (file)
@@ -6,13 +6,14 @@ use App\Models\FluidbookPublication;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Route;
 use Illuminate\Support\Str;
+use Cubist\Util\Files\Files;
 
 trait MarkdownOperation
 {
     protected function setupMarkdownRoutes($segment, $routeName, $controller)
     {
         Route::match(['get'], $segment . '/{id}/edit/markdown', $controller . '@markdown')->name('fluidbook_markdowneditor');
-        Route::match(['post'], $segment . '/{id}/markdown', $controller . '@getFilesById');
+        Route::match(['get'], $segment . '/{id}/markdown', $controller . '@getFilesById');
     }
 
     public function markdown($id)
@@ -22,17 +23,20 @@ trait MarkdownOperation
         }
 
         $token = Str::random(10);
+        $files = $this->getFilesById($id);
 
-        return view('fluidbook_publication.markdown_editor', ['version' => 'stable', 'id' => $id, 'fluidbook' => FluidbookPublication::find($id), 'access' => "", 'token' => $token]);
+        return view('fluidbook_publication.markdown_editor', ['files' => $files, 'version' => 'stable', 'id' => $id, 'fluidbook' => FluidbookPublication::find($id), 'access' => "", 'token' => $token]);
     }
 
-    public static function getFilesById(Request $request,$id)
+    public function getFilesById($id)
     {
-        $p = $request->page;
-        $path = public_path("/markdown_files/$id/p$p.md");
+        //$p = $request->page;;
+        $files = Files::getRecursiveDirectoryIterator(public_path("/markdown_files/$id"), true);
 
-        if(!file_exists($path)) return false;
+        return $files;
 
-        return file_get_contents(public_path("/markdown_files/$id/p$p.md"));
+        //if(!file_exists($path)) return false;
+
+        //return file_get_contents(public_path("/markdown_files/$id/p$p.md"));
     }
 }
index 730eadbade6f86aff9fd2fc12bb2cc7446cf2ce6..f7903601aafbb1a9c51d3f8378e818f50e67604d 100644 (file)
@@ -1,5 +1,6 @@
 import Editor from '@toast-ui/editor';
 import MarkdowneditorToolbar from "./markdowneditor.toolbar";
+import MarkdowneditorUndo from "./markdowneditor.undo";
 window.$ = window.jQuery = require('jquery');
 
 $.ajaxSetup({
@@ -17,17 +18,20 @@ function MarkdownEditor() {
     this.noCache = '?t=' + (new Date(FLUIDBOOK_DATA.composition_updated_at)).getTime();
     this.currentPage = Math.max(1,parseInt(window.location.hash.substring(1))) || 1;
     this.editor = null;
+    this.contentMarkdown = null;
     this.init()
 }
 
 MarkdownEditor.prototype = {
     init: function() {
-        this.toolbar = new MarkdowneditorToolbar(this);
+        new MarkdowneditorToolbar(this);
+        this.undo = new MarkdowneditorUndo(this);
 
         const $this = this
         this.initIcons();
         this.markdown();
         this.changePage();
+        this.undo.initState();
 
         $(window).on('hashchange', function () {
             /*if ($this.maskHashEvent) {
@@ -72,6 +76,11 @@ MarkdownEditor.prototype = {
             $(".handle").removeClass("dragging")
             $("body").removeClass("user-select-none")
         })
+
+        $(document).on('keyup', function (e) {
+            $this.undo.pushState()
+            console.log($this.undo.states)
+        })
     },
 
     initIcons: function () {
@@ -81,10 +90,7 @@ MarkdownEditor.prototype = {
     },
 
     loadPage: function() {
-        if (!this.single) {
-            this.loadPageHtml(this.currentPage);
-        }
-
+        this.loadPageHtml(this.currentPage);
         this.setContentMarkdown()
     },
 
@@ -132,17 +138,22 @@ MarkdownEditor.prototype = {
         return res;
     },
 
+    getCurrentPage: function () {
+        return this.currentPage;
+    },
+
     setContentMarkdown: function() {
         const $this = this
-        $.ajax({
-            url: '/fluidbook-publication/' + FLUIDBOOK_DATA.id +'/markdown',
-            type: 'post',
-            data: {page:this.currentPage},
-            success: function (response) {
-                $this.editor.setMarkdown(response)
-                $this.editor.moveCursorToStart(true)
-            }
-        });
+        if(this.undo.states[this.getCurrentPage()] !== undefined || this.undo.states[this.getCurrentPage()] === null) {
+            const state = this.undo.states[this.getCurrentPage()]
+            const lastKey = state.length;
+            this.editor.setMarkdown(state[lastKey])
+            this.editor.moveCursorToStart(true)
+        }else {
+            this.contentMarkdown = MARKDOWN_DATA["p"+this.currentPage]
+            this.editor.setMarkdown(this.contentMarkdown)
+            this.editor.moveCursorToStart(true)
+        }
     },
 
     markdown: function() {
@@ -162,6 +173,11 @@ MarkdownEditor.prototype = {
         this.editor.getMarkdown();
     },
 
+    setCurrentState: function (state) {
+        this.editor.setMarkdown(state)
+        this.editor.moveCursorToStart(true)
+    },
+
     changePage: function(page) {
         if (page === undefined) {
             let h = window.location.hash;
diff --git a/resources/markdowneditor/js/markdowneditor.save.js b/resources/markdowneditor/js/markdowneditor.save.js
new file mode 100644 (file)
index 0000000..610e3bc
--- /dev/null
@@ -0,0 +1,92 @@
+function MarkdowneditorSave(linkeditor) {
+    this.linkeditor = linkeditor;
+    this.init();
+}
+
+MarkdowneditorSave.prototype = {
+    init: function () {
+        let $this = this;
+
+        this.automaticSaveFrequency = 1000 * 5 * 60;
+        this.unsavedChanges = false;
+        this.automaticSaveTimeout = null;
+        this.runningAutomaticSaveTimeout = false;
+
+        $(window).on('beforeunload', function () {
+            if ($this.unsavedChanges) {
+                return TRANSLATIONS.warning_unsaved_changes;
+            }
+        })
+    },
+
+    hasChanged: function () {
+        let $this = this;
+        this.unsavedChanges = true;
+        if (this.runningAutomaticSaveTimeout === false) {
+            this.runningAutomaticSaveTimeout = true;
+            this.automaticSaveTimeout = setTimeout(function () {
+                $this.automaticSave();
+            }, this.automaticSaveFrequency);
+        }
+    },
+
+    saveIfUnsavedChanges: function (message, notify, callback) {
+        if (this.unsavedChanges) {
+            this.save(message, false, function () {
+                setTimeout(function () {
+                    callback();
+                }, 1000);
+            });
+        } else {
+            callback();
+        }
+    },
+
+    save: function (message, notify, callback) {
+        if (notify === undefined) {
+            notify = true;
+        }
+        if (callback === undefined) {
+            callback = function () {
+
+            };
+        }
+        var $this = this;
+        if (message === undefined) {
+            message = TRANSLATIONS.manual_save_message;
+        }
+
+        $.ajax({
+            url: '/fluidbook-publication/' + FLUIDBOOK_DATA.id + '/save/links', method: 'post', data: {
+                _method: 'put',
+                message: message,
+                rulers: JSON.stringify(window.RULERS),
+                links: JSON.stringify(window.LINKS),
+            },
+            success: function (data) {
+                if (notify) {
+                    $this.linkeditor.notification(TRANSLATIONS.success_save);
+                }
+                clearTimeout($this.automaticSaveTimeout);
+                $this.unsavedChanges = false;
+                $this.runningAutomaticSaveTimeout = false;
+
+                window.ASSETS = data.assets;
+                $this.linkeditor.versions.setVersions(data.versions);
+                callback();
+            },
+            error: function (jqXHR, status, error) {
+                $this.linkeditor.hasChanged();
+                $this.linkeditor.notification(TRANSLATIONS.error_save + ' : ' + error, 'error');
+            },
+        });
+
+        $this.linkeditor.links.loadFontSize();
+    },
+
+    automaticSave: function () {
+        this.save(TRANSLATIONS.automatic_save_message);
+    },
+};
+
+export default MarkdowneditorSave;
diff --git a/resources/markdowneditor/js/markdowneditor.undo.js b/resources/markdowneditor/js/markdowneditor.undo.js
new file mode 100644 (file)
index 0000000..eff3553
--- /dev/null
@@ -0,0 +1,133 @@
+function MarkdowneditorUndo(markdowneditor) {
+    this.markdowneditor = markdowneditor;
+    this.ignoreStatesChanges = false;
+    this.states = [];
+    this.indexes = [];
+    this.init();
+}
+
+MarkdowneditorUndo.prototype = {
+    init: function () {
+    },
+
+    initState: function () {
+        if (this.states[this.markdowneditor.getCurrentPage()] === undefined || this.states[this.markdowneditor.getCurrentPage()] === null) {
+            this.indexes[this.markdowneditor.getCurrentPage()] = 0;
+            this.pushState();
+        }
+    },
+
+    _states: function () {
+        let redo = false;
+        let undo = false;
+        let nb = this.states[this.markdowneditor.getCurrentPage()].length;
+        let idx = this.indexes[this.markdowneditor.getCurrentPage()];
+        if (nb > 1) {
+            if (idx < nb) {
+                redo = true;
+            }
+            if (idx > 1) {
+                undo = true;
+            }
+        }
+        return {redo: redo, undo: undo, index: idx, nb: nb};
+    },
+
+    updateIconsStates: function () {
+        let s = this._states();
+        if (s.redo) {
+            $('[data-icon=redo]').removeClass('disabled');
+        } else {
+            $('[data-icon=redo]').addClass('disabled');
+        }
+
+        if (s.undo) {
+            $('[data-icon=undo]').removeClass('disabled');
+        } else {
+            $('[data-icon=undo]').addClass('disabled');
+        }
+    },
+
+    canRedo: function () {
+        return this._states().redo;
+    },
+
+    canUndo: function () {
+        return this._states().undo;
+    },
+
+
+    pushState: function () {
+        if (this.ignoreStatesChanges) {
+            console.log('ignore states changes');
+            return;
+        }
+
+        let index = this.indexes[this.markdowneditor.getCurrentPage()];
+        if (index === 0) {
+            this.states[this.markdowneditor.getCurrentPage()] = [
+                this.markdowneditor.contentMarkdown
+            ];
+            console.log('ok',this.states,this.markdowneditor.editor.getMarkdown(),'ok')
+        }
+
+        let cs = this.markdowneditor.editor.getMarkdown();
+        let ps = this.states[this.markdowneditor.getCurrentPage()][index - 1];
+        if (ps == cs) {
+            console.log('skipped : no change');
+            return;
+        }
+
+        if (index > 0 && index < this.states[this.markdowneditor.getCurrentPage()].length) {
+            this.states[this.markdowneditor.getCurrentPage()] = this.states[this.markdowneditor.getCurrentPage()].slice(0, index);
+        }
+        this.states[this.markdowneditor.getCurrentPage()].push(cs);
+        this.indexes[this.markdowneditor.getCurrentPage()]++;
+
+        //console.log('push current index', index, 'states length', this.states[this.markdowneditor.getCurrentPage()].length);
+
+        this.updateIconsStates();
+    },
+
+    undo: function () {
+        if (!this.canUndo()) {
+            return;
+        }
+        let index = this.indexes[this.markdowneditor.getCurrentPage()];
+        index--;
+        let state = this.states[this.markdowneditor.getCurrentPage()][index - 1];
+        this.ignoreStatesChanges = true;
+        this.markdowneditor.setCurrentState(state);
+        var $this = this;
+        setTimeout(function () {
+            $this.ignoreStatesChanges = false;
+        }, 500);
+        this.indexes[this.markdowneditor.getCurrentPage()] = index;
+
+        //console.log('undo : current index', index, 'states length', this.states[this.markdowneditor.getCurrentPage()].length);
+
+        this.updateIconsStates();
+    },
+
+    redo: function () {
+        if (!this.canRedo()) {
+            return;
+        }
+        let index = this.indexes[this.markdowneditor.getCurrentPage()];
+        let state = this.states[this.markdowneditor.getCurrentPage()][index];
+        this.ignoreStatesChanges = true;
+        this.markdowneditor.setCurrentState(state);
+        var $this = this;
+        setTimeout(function () {
+            $this.ignoreStatesChanges = false;
+        }, 500);
+        index++;
+        this.indexes[this.markdowneditor.getCurrentPage()] = index;
+        //console.log('redo : current index', index, 'states length', this.states[this.markdowneditor.getCurrentPage()].length);
+
+        this.updateIconsStates();
+    }
+}
+
+
+export default MarkdowneditorUndo;
index ebc6f76209b3a015d6d49ab1955ca44633e38236..1af5f0535155b9ce6d84f17ce9f75b23708d69c8 100644 (file)
@@ -4,6 +4,13 @@
     $fbdata['settings']['pages']=$fbdata['pages']=$fluidbook->getPagesNumber();
     $fbdata['settings']['imageFormat']=$fluidbook->getImageFormat();
     $fbdata['page_dimensions']=[];
+
+    $collection = collect($files);
+    $filesdata = $collection->flatMap(function($item) {
+        $filename = $item->getFilename();
+        return [$filename => file_get_contents($item)];
+    } );
+    $sorted = $filesdata->sortKeys();
 @endphp
 
 @extends('layouts.markdowneditor')
     </div>
 @endsection
 
-@push('markdown_styles')
-    <link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/toastui-editor.min.css" />
-    <link rel="stylesheet"
-          href="/packages/markdowneditor/css/style.css?v={{filemtime(public_path('packages/markdowneditor/css/style.css'))}}"/>
-@endpush
 @push('markdown_scripts')
     <script>
         var FLUIDBOOK_DATA = @json($fbdata);
+        var MARKDOWN_DATA = {}
+        @foreach($filesdata as $key => $data)
+            @if(strstr($key,".md"))
+                MARKDOWN_DATA["{{explode('.',$key)[0]}}"] = `{!! $data !!}`;
+                console.log(MARKDOWN_DATA)
+            @endif
+        @endforeach
     </script>
     <script
         src="/packages/markdowneditor/js/markdowneditor.js?v={{filemtime(public_path('packages/markdowneditor/js/markdowneditor.js'))}}"></script>
@@ -80,3 +89,8 @@
 
     </script>
 @endpush
+@push('markdown_styles')
+    <link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/toastui-editor.min.css" />
+    <link rel="stylesheet"
+          href="/packages/markdowneditor/css/style.css?v={{filemtime(public_path('packages/markdowneditor/css/style.css'))}}"/>
+@endpush