Explorar el Código

Sauvegarde automatique (browser) des modifs de l'éditeur

Benoît Hubert hace 8 años
padre
commit
d2508bdbc2

BIN
css/fonts/icomoon.eot


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 0
css/fonts/icomoon.svg


BIN
css/fonts/icomoon.ttf


BIN
css/fonts/icomoon.woff


+ 11 - 5
css/styles.css

@@ -3,11 +3,11 @@
  */
 @font-face {
   font-family: 'icomoon';
-  src:  url('fonts/icomoon.eot?vcoix9');
-  src:  url('fonts/icomoon.eot?vcoix9#iefix') format('embedded-opentype'),
-    url('fonts/icomoon.ttf?vcoix9') format('truetype'),
-    url('fonts/icomoon.woff?vcoix9') format('woff'),
-    url('fonts/icomoon.svg?vcoix9#icomoon') format('svg');
+  src:  url('fonts/icomoon.eot?xjk54g');
+  src:  url('fonts/icomoon.eot?xjk54g#iefix') format('embedded-opentype'),
+    url('fonts/icomoon.ttf?xjk54g') format('truetype'),
+    url('fonts/icomoon.woff?xjk54g') format('woff'),
+    url('fonts/icomoon.svg?xjk54g#icomoon') format('svg');
   font-weight: normal;
   font-style: normal;
 }
@@ -27,6 +27,9 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-cloud-upload:before {
+  content: "\e9c3";
+}
 .icon-plus:before {
   content: "\ea0a";
 }
@@ -117,6 +120,9 @@ button a {
 button.active {
   background: #9ad;
 }
+button:hover {
+  opacity: 0.6;
+}
 #editor-js,#editor-html {
   /*display: none;*/
   position: absolute;

+ 2 - 1
exemples/selecteurs-basiques/script.js

@@ -7,7 +7,8 @@ $('#ex1-button1').click(function() {
 $('.ex1-buttons').click(function() {
   var clickedButton = $(this);
   clickedButton.toggleClass('blue');
-  var text = clickedButton.hasClass('blue') ? 'Bleu' : 'Gris';
+  var text = clickedButton.hasClass('blue') ?
+    'Bleu' : 'Gris';
   clickedButton.html(text);
 });
 

+ 3 - 0
index.html

@@ -31,6 +31,7 @@
                 <li><button id="show-javascript">JavaScript</button></li>
             </ul>
         </nav>
+        <button id="save-changes" class="icon-cloud-upload green"></button>
     </div>
     <div id="editor"></div>
     <script type="text/html" id="editor-javascript"></script>
@@ -43,10 +44,12 @@
 
 <script src="js/vendor/modernizr-3.5.0.min.js"></script>
 <script src="js/vendor/jquery-3.2.1.min.js" ></script>
+<script src="js/vendor/lodash.min.js" ></script>
 <script src="js/vendor/loadJS.js" ></script>
 <script src="js/plugins.js"></script>
 <script src="js/main.js"></script>
 <script src="js/vendor/ace/ace.js" type="text/javascript" charset="utf-8"></script>
+<script src="js/editor-local-storage.js"></script>
 <script src="js/editor.js"></script>
 </body>
 </html>

+ 69 - 0
js/editor-local-storage.js

@@ -0,0 +1,69 @@
+function LocalStorageDraft() {
+  this.storageKey = 'ace_sandbox_draft';
+  this.supportedTypes = ['html', 'javascript', 'css'];
+}
+
+
+
+LocalStorageDraft.prototype.init = function(slug, sources) {
+  this.slug = slug;
+  this.sources = sources || {};
+}
+
+LocalStorageDraft.prototype.getSources = function() {
+  return this.sources;
+}
+
+LocalStorageDraft.prototype.restore = function(slug) {
+  var draft = this.unserialize();
+  if(! draft) {
+    console.log('no draft found for slug `' + slug + '`');
+    return false;
+  }
+  if(draft.slug !== slug) {
+    console.log('Saved draft id `' + draft.slug + '` differs from required `' + slug + '`');
+    return false;
+  }
+  console.log('restore', draft);
+  this.init(draft.slug, draft.sources);
+  return draft;
+}
+
+LocalStorageDraft.prototype.unserialize = function() {
+  var draftJSON = localStorage.getItem(this.storageKey);
+  var draft;
+  if(! draftJSON) {
+    return false;
+  }
+  try {
+    draft = JSON.parse(draftJSON);
+  }
+  catch(e) {
+    console.log('could not unserialize!! ' + e.message);
+    return false;
+  }
+  return draft;
+}
+
+LocalStorageDraft.prototype.reset = function() {
+  this.slug = undefined;
+  this.sources = undefined;
+  localStorage.removeItem(this.storageKey);
+}
+
+LocalStorageDraft.prototype.serialize = function() {
+  return JSON.stringify({
+    slug: this.slug, sources: this.sources
+  });
+}
+
+
+LocalStorageDraft.prototype.saveSource = function(type, source) {
+  console.log(this);
+  if(this.supportedTypes.indexOf(type) === -1) {
+    throw new Error('Unsupported type `' + type +
+      '`, allowed types are: ' + this.supportedTypes.join(', '));
+  }
+  this.sources[type] = source;
+  localStorage.setItem(this.storageKey, this.serialize());
+}

+ 136 - 28
js/editor.js

@@ -9,49 +9,111 @@ $(document).ready(function() {
   var $exampleForm   = $('#add-example-form');
   var $exampleSave   = $('#add-example-save');
   var $exampleCancel = $('#add-example-cancel');
+  var $saveChanges   = $('#save-changes');
   var $notification  = $('#notification');
+  var activeMode     = 'html';
+  var currentHash; 
+  var editor;
+  var editorStorage = new LocalStorageDraft();
+  var saveTimeout;
 
-  var activeTab = 'show-html';
 
-  var editor;
+  editor = ace.edit("editor");
+  editor.setTheme("ace/theme/eclipse");
+  editor.$blockScrolling = Infinity;
+  editor.getSession().setUseWrapMode(true);
+
+  function setCurrentHash(slug) {
+    if(slug) {
+      console.log('save current hash', slug);
+      window.location.hash = currentHash = slug;
+    }
+    else {
+      currentHash = window.location.hash ?
+          window.location.hash.substr(1) : undefined;
+      if(currentHash) console.log('restored current hash', currentHash);
+    }
+  }
 
-  function initEditor(mode) {
-    editor = ace.edit("editor");
-    editor.setTheme("ace/theme/eclipse");
+  function setEditorMode(mode) {
     editor.getSession().setMode("ace/mode/" + mode);
-    editor.getSession().setUseWrapMode(true);
   }
-  // editor.getSession().on('change', function() {
-  //   console.log(arguments)
-  // });
-
-  function setActiveTab(which) {
-    var elementId = 'show-' + which;
-    $('#' + activeTab).removeClass('active');
-    activeTab = elementId;
+
+  function saveToLocalStorage() {
+    var editorContent =  editor.getSession().getValue();
+    console.log('saveToLocalStorage', activeMode, editorContent.substr(0, 10) + '[...]');
+    editorStorage.saveSource(activeMode, editorContent);
+    saveTimeout = undefined;
+  }
+
+  function editorContentChanged() {
+    if(saveTimeout) {
+      clearTimeout(saveTimeout);
+    }
+    saveTimeout = setTimeout(saveToLocalStorage, 1000);
+  }
+
+  editor.getSession().on('change', editorContentChanged);
+
+  function setActiveTab(mode) {
+    console.log('setting mode', mode);
+    var elementId = 'show-' + mode;
+    $('#show-' + activeMode).removeClass('active');
+    activeMode = mode;
     $('#' + elementId).addClass('active');
-    var ed = $('#editor-' + which);
-    initEditor(which);
+    var ed = $('#editor-' + mode);
+    setEditorMode(mode);
     editor.getSession().setValue(ed[0].innerHTML);
   }
 
   $('#tabs button').click(function() {
-    var which = $(this).prop('id').substr(5);
-    setActiveTab(which);
+    var mode = $(this).prop('id').substr(5);
+    setActiveTab(mode);
   })
 
-  function loadExample() {
-    var exampleName = $(this).val();
-    var serverPath = 'exemples/' + exampleName + '/';
-    $.get(serverPath + 'script.js', function(javascript) {
-      $editorJs.html(javascript);
-    }, 'text');
-    $.get(serverPath + '/contenu.html', function(html) {
+  function loadAsync(url, dataType) {
+    return new Promise(function(resolve, reject) {
+      $.ajax({
+        type: 'GET',
+        url: url,
+        success: function(data) {
+          resolve(data);
+        },
+        error: function(jqXHR) {
+          reject(new Error(jqXHR.responseText));
+        }
+      }, dataType);
+    });
+  }
+
+  function loadExample(exampleSlug) {
+    var serverPath = 'exemples/' + exampleSlug + '/';
+    // $.get(serverPath + 'script.js', function(javascript) {
+    //   $editorJs.html(javascript);
+    // }, 'text');
+    // $.get(serverPath + '/contenu.html', function(html) {
+    //   $editorHtml.html(html);
+    //   $htmlContent.html(html);
+    //   setActiveTab('html');
+    //   setCurrentHash(exampleSlug);
+    //   loadJS(serverPath + 'script.js');
+    // }, 'text');
+    loadAsync(serverPath + 'script.js', 'text')
+    .then(javascript => $editorJs.html(javascript))
+    .then(() => loadAsync(serverPath + '/contenu.html', 'text'))
+    .then(html => {
       $editorHtml.html(html);
       $htmlContent.html(html);
       setActiveTab('html');
+      setCurrentHash(exampleSlug);
+      var sources = {
+        html: $editorHtml.html(),
+        javascript: $editorJs.html()
+      };
+      editorStorage.init(exampleSlug, sources);
       loadJS(serverPath + 'script.js');
-    }, 'text');
+    });
+
   }
 
   function addFileSelectItem(item) {
@@ -64,7 +126,29 @@ $(document).ready(function() {
 
   function loadExampleList() {
     $.get('exemples/liste.json', function(liste) {
+      var didRestore;
       liste.forEach(addFileSelectItem);
+      if(currentHash) {
+        console.log('I have current hash', currentHash);
+        $fileSelect.val(currentHash);
+        var item = _.find(liste, { slug: currentHash });
+        if( ! item) {
+          return;
+        }
+        console.log('I have current item', item);
+        restoredDraft = editorStorage.restore(item.slug);
+        if(! restoredDraft) {
+          console.log('I did not restore');
+          loadExample(item.slug);
+        }
+        else {
+          console.log('I did restore');
+          $editorHtml.html(restoredDraft.sources.html);
+          $editorJs.html(restoredDraft.sources.javascript);
+          setActiveTab('html');
+        }
+      }
+
     }, 'json');
   }
 
@@ -99,7 +183,7 @@ $(document).ready(function() {
       success: function(newExample) {
         clearAndCloseEditor();
         addFileSelectItem(newExample);
-        notify('success', "Exemple sauvegardé !");
+        notify('success', "Exemple créé !");
       },
       error: function(jqXHR, textStatus, errorThrown ) {
         console.log(jqXHR, textStatus, errorThrown);
@@ -115,9 +199,33 @@ $(document).ready(function() {
     toggleEditor();
   }
 
-  $fileSelect.change(loadExample);
+  function saveChanges() {
+    var payload = editorStorage.getSources();
+
+    $.ajax({
+      type: 'PUT',
+      url: '/examples/' + currentHash,
+      data: JSON.stringify(payload),
+      success: function(newExample) {
+        notify('success', "Exemple sauvegardé !");
+      },
+      error: function(jqXHR, textStatus, errorThrown ) {
+        console.log(jqXHR, textStatus, errorThrown);
+        notify('error', 'Erreur: ' + jqXHR.responseText);
+      },
+      contentType: 'application/json',
+      dataType: 'json'
+    });
+  }
+
+  setCurrentHash();
+
+  $fileSelect.change(function() {
+    loadExample($(this).val());
+  });
   $addExampleBtn.click(toggleEditor);
   $exampleCancel.click(clearAndCloseEditor);
+  $saveChanges.click(saveChanges);
   $exampleForm.submit(saveExample);
   loadExampleList();
   // setActiveTab('html');

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 136 - 0
js/vendor/lodash.min.js


+ 18 - 0
server.js

@@ -34,4 +34,22 @@ app.post('/examples', function(req, res) {
   res.json({ slug: exampleSlug, title: title });
 });
 
+app.put('/examples/:slug', function(req, res) {
+  var slug = req.params.slug;
+  var existing = _.find(examples, { slug: slug });
+  if(! existing) {
+    res.status(404).send("L'exemple avec l'identifiant '" + slug + "' est introuvable !");
+  }
+  var targetDir = __dirname + '/exemples/' + slug;
+  if(req.body.html) {
+    fs.writeFileSync(targetDir + '/contenu.html', req.body.html);
+  }
+  if(req.body.javascript) {
+    fs.writeFileSync(targetDir + '/script.js', req.body.javascript);
+  }
+  res.json({ success: true });
+});
+
+
+
 app.listen(3000);