couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From benk...@apache.org
Subject fauxton commit: updated refs/heads/master to 040a606
Date Tue, 13 Jan 2015 20:31:23 GMT
Repository: couchdb-fauxton
Updated Branches:
  refs/heads/master 15d49fcb2 -> 040a60696


Update Document Editor page

This PR contains various updates to the Document Editor page.

Closes COUCHDB-2507


Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/040a6069
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/040a6069
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/040a6069

Branch: refs/heads/master
Commit: 040a60696397571867fddea727c23dab3e02108d
Parents: 15d49fc
Author: Benjamin Keen <ben.keen@gmail.com>
Authored: Wed Dec 10 16:25:57 2014 -0800
Committer: Benjamin Keen <ben.keen@gmail.com>
Committed: Tue Jan 13 10:18:26 2015 -0800

----------------------------------------------------------------------
 app/addons/documents/assets/less/documents.less |  23 +-
 app/addons/documents/helpers.js                 |  25 ++
 app/addons/documents/routes-doc-editor.js       |  64 +--
 app/addons/documents/templates/code_editor.html |  69 +--
 .../tests/nightwatch/editDocumentsFromView.js   |   2 +-
 app/addons/documents/views-doceditor.js         | 415 +++++++++++--------
 app/addons/documents/views.js                   |   2 +-
 app/addons/fauxton/components.js                |   9 +-
 app/templates/layouts/doc_editor.html           |  28 ++
 assets/js/libs/ace/theme-idle_fingers.js        | 102 +++++
 assets/less/codeeditor.less                     | 131 ++++++
 assets/less/fauxton.less                        |  29 ++
 assets/less/templates.less                      |  10 +-
 13 files changed, 652 insertions(+), 257 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/040a6069/app/addons/documents/assets/less/documents.less
----------------------------------------------------------------------
diff --git a/app/addons/documents/assets/less/documents.less b/app/addons/documents/assets/less/documents.less
index b444044..ced2dc6 100644
--- a/app/addons/documents/assets/less/documents.less
+++ b/app/addons/documents/assets/less/documents.less
@@ -58,21 +58,8 @@ button.beautify {
 
 #map-function, #reduce-function {
   width: 100%;
-  font-size: 16px;
-}
-
-#doc-actions {
-  height: 42px;
-}
-
-#editor-container {
-  height: 688px;
-  width: 100%;
-  font-size: 16px;
-}
-
-.editor-content-page {
-  padding-right: 20px;
+  font-size: 13px;
+  line-height: 22px;
 }
 
 .metadata-page {
@@ -83,8 +70,12 @@ button.string-edit {
   position: absolute;
   padding: 0;
   z-index: 1000;
-  width: 16px;
+  width: 20px;
   left: 22px;
+
+  i.icon {
+    margin-right: 0px;
+  }
 }
 
 button.string-edit[disabled] {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/040a6069/app/addons/documents/helpers.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/helpers.js b/app/addons/documents/helpers.js
new file mode 100644
index 0000000..fa21e00
--- /dev/null
+++ b/app/addons/documents/helpers.js
@@ -0,0 +1,25 @@
+define([
+  'api'
+], function(FauxtonAPI) {
+
+  var Helpers = {};
+
+  Helpers.getPreviousPage = function (database, wasCloned) {
+    var previousPage = database.url('index'), // default to the current database's all_docs
page
+        lastPages = FauxtonAPI.router.lastPages;
+
+    if (!wasCloned && lastPages.length >= 2) {
+
+      // if we came from "/new", we don't want to link the user there
+      if (/new$/.test(lastPages[1])) {
+        previousPage = lastPages[0];
+      } else {
+        previousPage = lastPages[1];
+      }
+    }
+
+    return previousPage;
+  };
+
+  return Helpers;
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/040a6069/app/addons/documents/routes-doc-editor.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/routes-doc-editor.js b/app/addons/documents/routes-doc-editor.js
index 4efed7c..c23d8e6 100644
--- a/app/addons/documents/routes-doc-editor.js
+++ b/app/addons/documents/routes-doc-editor.js
@@ -12,22 +12,22 @@
 
 define([
   "app",
-
   "api",
 
   // Modules
-  //views
+  "addons/documents/helpers",
   "addons/documents/views",
   "addons/documents/views-doceditor",
   "addons/databases/base"
 ],
 
-function(app, FauxtonAPI, Documents, DocEditor, Databases) {
+function(app, FauxtonAPI, Helpers, Documents, DocEditor, Databases) {
+
 
   var DocEditorRouteObject = FauxtonAPI.RouteObject.extend({
-    layout: "one_pane",
+    layout: 'doc_editor',
     disableLoader: true,
-    selectedHeader: "Databases",
+    selectedHeader: 'Databases',
 
     initialize: function(route, masterLayout, options) {
       this.databaseName = options[0];
@@ -38,20 +38,22 @@ function(app, FauxtonAPI, Documents, DocEditor, Databases) {
     },
 
     routes: {
-      "database/:database/:doc/code_editor": "code_editor",
-      "database/:database/:doc": "code_editor",
-      "database/:database/_design/:ddoc" :"showDesignDoc"
+      'database/:database/:doc/code_editor': 'code_editor',
+      'database/:database/:doc': 'code_editor',
+      'database/:database/_design/:ddoc': 'showDesignDoc'
     },
 
     events: {
-      "route:reRenderDoc": "reRenderDoc",
-      "route:duplicateDoc": "duplicateDoc"
+      'route:reRenderDoc': 'reRenderDoc',
+      'route:duplicateDoc': 'duplicateDoc'
     },
 
-    crumbs: function() {
+    crumbs: function () {
+      var previousPage = Helpers.getPreviousPage(this.database, this.wasCloned);
+
       return [
-        {"name": this.database.id, "link": Databases.databaseUrl(this.database)},
-        {"name": this.docID, "link": "#"}
+        { type: 'back', link: previousPage },
+        { name: this.docID, link: '#' }
       ];
     },
 
@@ -67,11 +69,11 @@ function(app, FauxtonAPI, Documents, DocEditor, Databases) {
         this.doc = new Documents.Doc({ _id: this.docID }, { database: this.database });
       }
 
-      this.docView = this.setView("#dashboard-content", new DocEditor.CodeEditor({
+      this.docView = this.setView('#dashboard-content', new DocEditor.CodeEditor({
         model: this.doc,
-        database: this.database
+        database: this.database,
+        previousPage: Helpers.getPreviousPage(this.database)
       }));
-
     },
 
     showDesignDoc: function (database, ddoc) {
@@ -88,25 +90,28 @@ function(app, FauxtonAPI, Documents, DocEditor, Databases) {
       database = this.database;
       this.docID = newId;
 
+      var that = this;
       doc.copy(newId).then(function () {
-        doc.set({_id: newId});
+        doc.set({ _id: newId });
+        that.wasCloned = true;
+
         docView.forceRender();
-        FauxtonAPI.navigate('/database/' + database.safeID() + '/' + app.utils.safeURLName(newId),
{trigger: true});
+        FauxtonAPI.navigate('/database/' + database.safeID() + '/' + app.utils.safeURLName(newId),
{ trigger: true });
         FauxtonAPI.addNotification({
-          msg: "Document has been duplicated."
+          msg: 'Document has been duplicated.'
         });
 
       }, function (error) {
-        var errorMsg = "Could not duplicate document, reason: " + error.responseText + ".";
+        var errorMsg = 'Could not duplicate document, reason: ' + error.responseText + '.';
         FauxtonAPI.addNotification({
           msg: errorMsg,
-          type: "error"
+          type: 'error'
         });
       });
     },
 
     apiUrl: function() {
-      return [this.doc.url("apiurl"), this.doc.documentation()];
+      return [this.doc.url('apiurl'), this.doc.documentation()];
     }
   });
 
@@ -120,19 +125,22 @@ function(app, FauxtonAPI, Documents, DocEditor, Databases) {
       });
     },
 
-    crumbs: function() {
+    crumbs: function () {
+      var previousPage = Helpers.getPreviousPage(this.database);
       return [
-        {"name": this.database.id, "link": Databases.databaseUrl(this.database)},
-        {"name": "New", "link": "#"}
+        { type: 'back', link: 'previousPage' },
+        { name: 'New', link: '#' }
       ];
     },
+
     routes: {
-      "database/:database/new": "code_editor"
+      'database/:database/new': 'code_editor'
     },
-    selectedHeader: "Databases"
+
+    selectedHeader: 'Databases'
   });
 
-  
+
   return {
     NewDocEditorRouteObject: NewDocEditorRouteObject,
     DocEditorRouteObject: DocEditorRouteObject

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/040a6069/app/addons/documents/templates/code_editor.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/code_editor.html b/app/addons/documents/templates/code_editor.html
index 8f0ab6d..4933847 100644
--- a/app/addons/documents/templates/code_editor.html
+++ b/app/addons/documents/templates/code_editor.html
@@ -12,44 +12,65 @@ License for the specific language governing permissions and limitations
under
 the License.
 */%>
 
-<div id="doc">
-  <div class="errors-container"></div>
-   
-<div id="doc-actions" class="nav">
-  <div class="span3">
+<div id="doc-editor-actions-panel">
+  <div class="doc-actions-left">
     <button class="save-doc btn btn-success save" type="button"><i class="icon fonticon-ok-circled"></i>
Save</button>
-    <button class="btn js-back">Back</button>
+    <div>
+      <a href="#" class="js-back cancel-button">Cancel</a>
+    </div>
   </div>
 
-  <div class="span7">
-    <button class="btn string-edit" title="Edit line" disabled="true"><i class="icon
icon-edit"></i></button>  
+  <div class="alignRight">
+    <button class="btn string-edit" title="Edit line" disabled="true"><i class="icon
icon-edit"></i></button>
+
     <% if (attachments) { %>
-    <div class="btn-group">
-      <button class="dropdown-toggle btn" data-bypass="true" data-toggle="dropdown">
-        View Attachments
+    <div class="panel-section btn-group">
+      <button class="panel-button dropdown-toggle btn" data-bypass="true" data-toggle="dropdown"
title="View Attachments"
+        id="view-attachments-menu">
+        <i class="icon fonticon-picture"></i>
+        <span>View Attachments</span>
         <span class="caret"></span>
       </button>
-      <ul class="dropdown-menu">
+
+      <ul class="dropdown-menu" role="menu" aria-labelledby="view-attachments-menu">
         <%_.each(attachments, function (att) { %>
         <li>
-        <a href="<%- att.url %>" target="_blank" data-bypass="true"> <strong>
<%- att.fileName %> </strong> -
-          <span> <%- att.contentType %>, <%- formatSize(att.size)%> </span>
-        </a>
+          <a href="<%- att.url %>" target="_blank" data-bypass="true"> <strong>
<%- att.fileName %> </strong> -
+            <span> <%- att.contentType %>, <%- formatSize(att.size)%> </span>
+          </a>
         </li>
         <% }) %>
       </ul>
     </div>
-    <% } %> 
-    <button class="btn upload"><i class="icon icon-circle-arrow-up"></i>
Upload Attachment</button>
-    <button class="btn duplicate"><i class="icon icon-repeat"></i> Clone
document</button>
-    <button class="btn btn-danger delete"><i class="icon icon-trash"></i></button>
-  </div>
+    <% } %>
 
-<div id="upload-modal"> </div>
-<div id="duplicate-modal"> </div> 
-<div id="string-edit-modal"> </div> 
+    <div class="panel-section">
+      <button class="panel-button upload" title="Upload attachment">
+        <i class="icon icon-circle-arrow-up"></i>
+        <span>Upload Attachment</span>
+      </button>
+    </div>
+    <div class="panel-section">
+      <button class="panel-button duplicate" title="Clone document">
+        <i class="icon icon-repeat"></i>
+        <span>Clone Document</span>
+      </button>
+    </div>
+    <div class="panel-section">
+      <button class="panel-button delete" title="Delete">
+        <i class="icon icon-trash"></i>
+        <span>Delete</span>
+      </button>
+    </div>
+  </div>
 </div>
 
+<div class="scrollable">
+  <div class="bgEditorGutter"></div>
   <div id="editor-container" class="doc-code"><%- JSON.stringify(doc.attributes,
null, "  ") %></div>
-
 </div>
+
+<div id="upload-modal"> </div>
+<div id="duplicate-modal"> </div>
+<div id="delete-doc-modal"> </div>
+<div id="string-edit-modal"> </div>

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/040a6069/app/addons/documents/tests/nightwatch/editDocumentsFromView.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/nightwatch/editDocumentsFromView.js b/app/addons/documents/tests/nightwatch/editDocumentsFromView.js
index 5d0f96e..3936a9a 100644
--- a/app/addons/documents/tests/nightwatch/editDocumentsFromView.js
+++ b/app/addons/documents/tests/nightwatch/editDocumentsFromView.js
@@ -47,7 +47,7 @@ module.exports = {
     var waitTime = 10000;
 
     client
-      .clickWhenVisible('#doc-actions .js-back')
+      .clickWhenVisible('#doc-editor-actions-panel .js-back')
       .clickWhenVisible('#toggle-query')
       .clickWhenVisible('#query-options-tray label[for="qoReduce"]')
       .clickWhenVisible('#button-options button[type="submit"]')

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/040a6069/app/addons/documents/views-doceditor.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/views-doceditor.js b/app/addons/documents/views-doceditor.js
index cca890d..49e3da7 100644
--- a/app/addons/documents/views-doceditor.js
+++ b/app/addons/documents/views-doceditor.js
@@ -15,7 +15,6 @@ define([
 
   'api',
   'addons/fauxton/components',
-
   'addons/documents/resources',
   'addons/databases/resources',
 
@@ -99,7 +98,7 @@ function (app, FauxtonAPI, Components, Documents, Databases, prettify) {
     },
 
     events: {
-      'click #string-edit-save-btn':'saveString'
+      'click #string-edit-save-btn': 'saveString'
     },
 
     saveString: function (event) {
@@ -198,8 +197,7 @@ function (app, FauxtonAPI, Components, Documents, Databases, prettify)
{
     }
   });
 
-  /* Document editor*/
-   Views.CodeEditor = FauxtonAPI.View.extend({
+  Views.CodeEditor = FauxtonAPI.View.extend({
     template: 'addons/documents/templates/code_editor',
     className: 'editor-content-page',
     events: {
@@ -207,8 +205,9 @@ function (app, FauxtonAPI, Components, Documents, Databases, prettify)
{
       'click button.delete': 'destroy',
       'click button.duplicate': 'duplicate',
       'click button.upload': 'upload',
-      'click button.js-back': 'goback',
-      'click button.string-edit': 'stringEditing'
+      'click button.string-edit': 'stringEditing',
+      'click a.js-back': 'onClickGoBack',
+      'click .scrollable': 'focusOnLastLine'
     },
 
     disableLoader: true,
@@ -218,173 +217,77 @@ function (app, FauxtonAPI, Components, Documents, Databases, prettify)
{
       _.bindAll(this);
     },
 
-    goback: function () {
-      var lastPages = FauxtonAPI.router.lastPages;
+    onClickGoBack: function (e) {
+      e.preventDefault();
+      e.stopPropagation();
 
-      // we copy/pasted the url into the browser or came from
-      // creating a document with "/new" in the end of the path
-      if (lastPages.length < 2 || /\/new$/.test(lastPages[0])) {
-        FauxtonAPI.navigate(this.database.url('index') + '?limit=100');
-      } else {
-        window.history.back();
-      }
+      this.goBack();
     },
 
-    determineStringEditMatch: function (event) {
-      var selStart = this.editor.getSelectionStart().row;
-      var selEnd = this.editor.getSelectionEnd().row;
-      /* one JS(ON) string can't span more than one line - we edit one string, so ensure
we don't select several lines */
-      if (selStart >=0 && selEnd >= 0 && selStart === selEnd &&
this.editor.isRowExpanded(selStart)) {
-        var editLine = this.editor.getLine(selStart),
-            editMatch = editLine.match(/^([ \t]*)(["|'][a-zA-Z0-9_]*["|']: )?(["|'].*["|'],?[
\t]*)$/);
-
-        if (editMatch) {
-          return editMatch;
-        }
-      }
-
-      return null;
-    },
-
-    showHideEditDocString: function (event) {
-      this.$('button.string-edit').attr('disabled', 'true');
-      if (!this.hasValidCode()) {
-        return false;
-      }
-      var editMatch = this.determineStringEditMatch(event);
-      if (editMatch) {
-        this.$('button.string-edit').removeAttr('disabled');
-        /* remove the following line (along with CSS) to go back to the toolbar: take the
offset top of the editor, go down as many lines as we are positioned including fold and adjust
by two pixels as the button is slightly larger than a line */
-        var positionFromTop = (this.$('#editor-container').offset().top - 2 + this.editor.getRowHeight()
* this.editor.documentToScreenRow(this.editor.getSelectionStart().row));
-        this.$('button.string-edit').css('top', positionFromTop + 'px');
-        return true;
-      }
-      return false;
-    },
-
-    stringEditing: function (event) {
-      event.preventDefault();
-      if (!this.hasValidCode()) {
-        return;
-      }
-      var editMatch = this.determineStringEditMatch(event);
-      if (editMatch) {
-        var indent = editMatch[1] || '',
-              hashKey = editMatch[2] || '',
-              editText = editMatch[3],
-              comma = '';
-        if (editText.substring(editText.length - 1) === ',') {
-          editText = editText.substring(0, editText.length - 1);
-          comma = ',';
-        }
-        this.stringEditModal.openWin(this.editor, indent, hashKey, editText, comma);
-      }
+    goBack: function () {
+      FauxtonAPI.navigate(this.previousPage);
     },
 
-    destroy: function (event) {
+    destroy: function () {
       if (this.model.isNewDoc()) {
         FauxtonAPI.addNotification({
           msg: 'This document has not been saved yet.',
           type: 'warning',
-          clear:  true
+          clear: true
         });
         return;
       }
+      this.confirmDeleteModal.showModal();
+    },
 
-      if (!window.confirm('Are you sure you want to delete this doc?')) {
-        return false;
-      }
-
+    deleteDocument: function () {
       var database = this.model.database;
 
-      this.model.destroy().then(function (resp) {
+      this.model.destroy().then(function () {
         FauxtonAPI.addNotification({
-          msg: 'Succesfully deleted your doc',
-          clear:  true
+          msg: 'Your document has been successfully deleted.',
+          clear: true
         });
         FauxtonAPI.navigate(database.url('index'));
-      }, function (resp) {
+      }, function () {
         FauxtonAPI.addNotification({
-          msg: 'Failed to delete your doc!',
+          msg: 'Failed to delete your document!',
           type: 'error',
-          clear:  true
+          clear: true
         });
       });
     },
 
-    beforeRender: function () {
-      this.uploadModal = this.setView('#upload-modal', new Views.UploadModal({model: this.model}));
-      this.duplicateModal = this.setView('#duplicate-modal', new Views.DuplicateDocModal({model:
this.model}));
-
-      /* initialization is automatic - and make sure ONCE */
-      this.stringEditModal = this.stringEditModal || this.setView('#string-edit-modal', new
Views.StringEditModal());
-    },
-
-    upload: function (event) {
-      event.preventDefault();
+    upload: function (e) {
+      e.preventDefault();
       if (this.model.isNewDoc()) {
         FauxtonAPI.addNotification({
           msg: 'Please save the document before uploading an attachment.',
           type: 'warning',
-          clear:  true
+          clear: true
         });
         return;
       }
       this.uploadModal.showModal();
     },
 
-    duplicate: function (event) {
+    duplicate: function (e) {
       if (this.model.isNewDoc()) {
         FauxtonAPI.addNotification({
           msg: 'Please save the document before duplicating it.',
           type: 'warning',
-          clear:  true
+          clear: true
         });
         return;
       }
-      event.preventDefault();
+      e.preventDefault();
       this.duplicateModal.showModal();
     },
 
-    updateValues: function () {
-      if (this.model.changedAttributes()) {
-        FauxtonAPI.addNotification({
-          msg: 'Document saved successfully.',
-          type: 'success',
-          clear: true
-        });
-        this.editor.setValue(this.model.prettyJSON());
-      }
-    },
-
-    establish: function () {
-      var promise = this.model.fetch(),
-          databaseId = this.database.safeID(),
-          deferred = $.Deferred(),
-          that = this;
-
-      promise.then(function () {
-        deferred.resolve();
-      }, function (xhr, reason, msg) {
-        if (xhr.status === 404) {
-          FauxtonAPI.addNotification({
-            msg: 'The document does not exist',
-            type: 'error',
-            clear: true
-          });
-          that.goback();
-        }
-        deferred.reject();
-     });
-
-      return deferred;
-    },
-
     saveDoc: function (event) {
-      var json,
-          that = this,
-          editor = this.editor,
-          validDoc = this.getDocFromEditor();
+      var that = this,
+        editor = this.editor,
+        validDoc = this.getDocFromEditor();
 
       if (validDoc) {
         FauxtonAPI.addNotification({msg: 'Saving document.'});
@@ -398,79 +301,89 @@ function (app, FauxtonAPI, Components, Documents, Databases, prettify)
{
             msg: 'Save failed: ' + responseText,
             type: 'error',
             fade: false,
-            clear: true,
-            selector: '#doc .errors-container'
+            clear: true
           });
         });
       } else if(this.model.validationError && this.model.validationError === 'Cannot
change a documents id.') {
-          FauxtonAPI.addNotification({
-            msg: 'Cannot save: ' + 'Cannot change a documents _id, try Duplicate doc instead!',
-            type: 'error',
-            selector: '#doc .errors-container',
-            clear:  true
-          });
+        FauxtonAPI.addNotification({
+          msg: 'Cannot save. Cannot change a documents _id, try Clone Document instead!',
+          type: 'error',
+          clear:  true
+        });
         delete this.model.validationError;
       } else {
         FauxtonAPI.addNotification({
-          msg: 'Please fix the JSON errors and try again.',
+          msg: 'Please fix the JSON errors and try saving again.',
           type: 'error',
-          selector: '#doc .errors-container',
           clear:  true
         });
       }
     },
 
     getDocFromEditor: function () {
-      var json;
-
       if (!this.hasValidCode()) {
         return false;
       }
-
-      json = JSON.parse(this.editor.getValue());
-
+      var json = JSON.parse(this.editor.getValue());
       this.model.clear().set(json, {validate: true});
       if (this.model.validationError) {
         return false;
       }
-
       return this.model;
     },
 
-    hasValidCode: function () {
-      var errors = this.editor.getAnnotations();
-      return errors.length === 0;
+    beforeRender: function () {
+      this.uploadModal = this.setView('#upload-modal', new Views.UploadModal({ model: this.model
}));
+      this.duplicateModal = this.setView('#duplicate-modal', new Views.DuplicateDocModal({
model: this.model }));
+      this.confirmDeleteModal = this.setView('#delete-doc-modal', new Components.ConfirmationModal({
+        text: 'Are you sure you want to delete this document?',
+        action: this.deleteDocument
+      }));
+
+      // ensures it's initialized only once
+      this.stringEditModal = this.stringEditModal || this.setView('#string-edit-modal', new
Views.StringEditModal());
     },
 
-    serialize: function () {
-      return {
-        doc: this.model,
-        attachments: this.getAttachments()
-      };
+    updateValues: function () {
+      if (this.model.changedAttributes()) {
+        FauxtonAPI.addNotification({
+          msg: 'Document saved successfully.',
+          type: 'success',
+          clear: true
+        });
+        this.editor.setValue(this.model.prettyJSON());
+      }
     },
 
-    getAttachments: function () {
-      var attachments = this.model.get('_attachments');
+    establish: function () {
+      var promise = this.model.fetch(),
+          deferred = $.Deferred(),
+          goBack = _.bind(this.goBack, this);
 
-      if (!attachments) { return false; }
+      promise.then(function () {
+        deferred.resolve();
+      }, function (xhr, reason, msg) {
+        if (xhr.status === 404) {
+          FauxtonAPI.addNotification({
+            msg: 'The document does not exist',
+            type: 'error',
+            clear: true
+          });
+          goBack();
+        }
+        deferred.reject();
+     });
 
-      return _.map(attachments, function (att, key) {
-        return {
-          fileName: key,
-          size: att.length,
-          contentType: att.content_type,
-          url: this.model.url() + '/' + app.utils.safeURLName(key)
-        };
-      }, this);
+      return deferred;
     },
 
-    afterRender: function () {
-      var saveDoc = this.saveDoc,
-          editor,
-          model;
+    hasValidCode: function () {
+      var errors = this.editor.getAnnotations();
+      return errors.length === 0;
+    },
 
+    afterRender: function () {
       this.listenTo(this.model, 'sync', this.updateValues);
-
       this.editor = new Components.Editor({
         editorId: 'editor-container',
         forceMissingId: true,
@@ -478,7 +391,7 @@ function (app, FauxtonAPI, Components, Documents, Databases, prettify)
{
           name: 'save',
           bindKey: {win: 'Ctrl-S',  mac: 'Ctrl-S'},
           exec: function (editor) {
-            saveDoc();
+            this.saveDoc();
           },
           readOnly: true // false if this command should not apply in readOnly mode
         }]
@@ -486,16 +399,17 @@ function (app, FauxtonAPI, Components, Documents, Databases, prettify)
{
 
       this.editor.render();
 
-      editor = this.editor;
-      model = this.model;
-      //only start listening to editor once it has been rendered
+      var editor = this.editor;
+      var model = this.model;
+
+      // only start listening to editor once it has been rendered
       this.editor.promise().then(function () {
 
         this.listenTo(editor.editor, 'change', function (event) {
           var changedDoc;
           try {
             changedDoc = JSON.parse(editor.getValue());
-          } catch(exception) {
+          } catch (exception) {
             //not complete doc. Cannot work with it
             return;
           }
@@ -505,23 +419,25 @@ function (app, FauxtonAPI, Components, Documents, Databases, prettify)
{
             keyChecked.push('_rev');
           }
 
-          //check the changedDoc has all the required standard keys
-          if (_.isEmpty(_.difference(keyChecked, _.keys(changedDoc)))) { return; }
+          // check the changedDoc has all the required standard keys
+          if (_.isEmpty(_.difference(keyChecked, _.keys(changedDoc)))) {
+            return;
+          }
 
           editor.setReadOnly(true);
           setTimeout(function () { editor.setReadOnly(false) ;}, 400);
+
           // use extend so that _id stays at the top of the object with displaying the doc
           changedDoc = _.extend({_id: model.id, _rev: model.get('_rev')}, changedDoc);
           editor.setValue(JSON.stringify(changedDoc, null, '  '));
           FauxtonAPI.addNotification({
             type: 'error',
-            msg: 'Cannot remove a documents Id or Revision.',
-            clear:  true
+            msg: "Cannot remove a document's id or revision.",
+            clear: true
           });
         });
 
         var showHideEditDocString = _.bind(this.showHideEditDocString, this);
-
         this.listenTo(editor.editor, 'changeSelection', function (event) {
           showHideEditDocString(event);
         });
@@ -529,13 +445,154 @@ function (app, FauxtonAPI, Components, Documents, Databases, prettify)
{
           showHideEditDocString(event);
         });
 
+        // place focus on the editor
+        editor.editor.focus();
+
       }.bind(this));
     },
 
+    focusOnLastLine: function (e) {
+      var clickedInEditor = $(e.target).closest('#editor-container');
+      if (clickedInEditor.length === 0) {
+        this.editor.editor.focus();
+        var session = this.editor.editor.getSession();
+        var count = session.getLength();
+        this.editor.editor.gotoLine(count, session.getLine(count-1).length);
+      }
+    },
+
+    serialize: function () {
+      return {
+        doc: this.model,
+        attachments: this.getAttachments()
+      };
+    },
+
+    getAttachments: function () {
+      var attachments = this.model.get('_attachments');
+      if (!attachments) { return false; }
+
+      return _.map(attachments, function (att, key) {
+        return {
+          fileName: key,
+          size: att.length,
+          contentType: att.content_type,
+          url: this.model.url() + '/' + app.utils.safeURLName(key)
+        };
+      }, this);
+    },
+
+    determineStringEditMatch: function (event) {
+      var selStart = this.editor.getSelectionStart().row;
+      var selEnd = this.editor.getSelectionEnd().row;
+
+      // one JS(ON) string can't span more than one line - we edit one string, so ensure
we don't select several lines
+      if (selStart >=0 && selEnd >= 0 && selStart === selEnd &&
this.editor.isRowExpanded(selStart)) {
+        var editLine = this.editor.getLine(selStart),
+            editMatch = editLine.match(/^([ \t]*)(["|'][a-zA-Z0-9_]*["|']: )?(["|'].*["|'],?[
\t]*)$/);
+
+        if (editMatch) {
+          return editMatch;
+        }
+      }
+      return null;
+    },
+
+    showHideEditDocString: function (event) {
+      this.$('button.string-edit').attr('disabled', 'true');
+      if (!this.hasValidCode()) {
+        return false;
+      }
+      var editMatch = this.determineStringEditMatch(event);
+      if (editMatch) {
+        this.$('button.string-edit').removeAttr('disabled');
+        /* remove the following line (along with CSS) to go back to the toolbar: take the
offset top of the editor, go down as many lines as we are positioned including fold and adjust
by two pixels as the button is slightly larger than a line */
+        var positionFromTop = (this.$('#editor-container').offset().top - 2 + this.editor.getRowHeight()
* this.editor.documentToScreenRow(this.editor.getSelectionStart().row)) - 62;
+        this.$('button.string-edit').css('top', positionFromTop + 'px');
+        return true;
+      }
+      return false;
+    },
+
+    stringEditing: function (event) {
+      event.preventDefault();
+      if (!this.hasValidCode()) {
+        return;
+      }
+      var editMatch = this.determineStringEditMatch(event);
+      if (editMatch) {
+        var indent = editMatch[1] || '',
+          hashKey = editMatch[2] || '',
+          editText = editMatch[3],
+          comma = '';
+        if (editText.substring(editText.length - 1) === ',') {
+          editText = editText.substring(0, editText.length - 1);
+          comma = ',';
+        }
+        this.stringEditModal.openWin(this.editor, indent, hashKey, editText, comma);
+      }
+    },
+
+    cleanup: function () {
+      this.editor && this.editor.remove();
+      $('#dashboard').off('click');
+    }
+  });
+
+
+  Views.StringEditModal = Components.ModalView.extend({
+    template: 'addons/documents/templates/string_edit_modal',
+
+    events: {
+      'click #string-edit-save-btn': 'saveString'
+    },
+
+    saveString: function (event) {
+      event.preventDefault();
+      var newStr = this.subEditor.getValue();
+      this.subEditor.editSaved();
+      this.editor.replaceCurrentLine(this.indent + this.hashKey + JSON.stringify(newStr)
+ this.comma + '\n');
+      this.hideModal();
+    },
+
+    _showModal: function () {
+      this.$('.bar').css({width: '0%'});
+      this.$('.progress').addClass('hide');
+      this.clear_error_msg();
+    },
+
+    openWin: function (editor, indent, hashKey, jsonString, comma) {
+      this.editor = editor;
+      this.indent = indent;
+      this.hashKey = hashKey;
+      this.$('#string-edit-header').text(hashKey);
+      this.subEditor.setValue(JSON.parse(jsonString));
+      /* make sure we don't have save warnings w/out change */
+      this.subEditor.editSaved();
+      this.comma = comma;
+      this.showModal();
+    },
+
+    afterRender: function () {
+      /* make sure we init only ONCE */
+      if (!this.subEditor) {
+        this.subEditor = new Components.Editor({
+          editorId: 'string-editor-container',
+          mode: 'plain'
+        });
+
+        this.subEditor.render().promise().then(function () {
+          /* optimize by disabling auto sizing (35 is the lines fitting into the pop-up)
*/
+          this.subEditor.configureFixedHeightEditor(35);
+        }.bind(this));
+      }
+    },
+
     cleanup: function () {
-      if (this.editor) this.editor.remove();
+      if (this.subEditor) { this.subEditor.remove(); }
     }
   });
 
+
   return Views;
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/040a6069/app/addons/documents/views.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/views.js b/app/addons/documents/views.js
index 23a5b5c..1e3d039 100644
--- a/app/addons/documents/views.js
+++ b/app/addons/documents/views.js
@@ -360,7 +360,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, Views, QueryOptions)
         return;
       }
 
-      showError('Failed to delete your doc!');
+      showError('Failed to delete your document!');
     },
 
     toggleDocument: function (event) {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/040a6069/app/addons/fauxton/components.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/components.js b/app/addons/fauxton/components.js
index 24b1de8..2915db3 100644
--- a/app/addons/fauxton/components.js
+++ b/app/addons/fauxton/components.js
@@ -536,7 +536,6 @@ function(app, FauxtonAPI, ace, spin, ZeroClipboard) {
   });
 
   Components.ModalView = FauxtonAPI.View.extend({
-
     disableLoader: true,
 
     initialize: function (options) {
@@ -554,6 +553,7 @@ function(app, FauxtonAPI, ace, spin, ZeroClipboard) {
       if (this._showModal){ this._showModal();}
       this.clear_error_msg();
       this.$('.modal').modal();
+
       // hack to get modal visible
       $('.modal-backdrop').css('z-index', FauxtonAPI.constants.MISC.MODAL_BACKDROP_Z_INDEX);
     },
@@ -773,7 +773,7 @@ function(app, FauxtonAPI, ace, spin, ZeroClipboard) {
       this.editorId = options.editorId;
       this.mode = options.mode || "json";
       this.commands = options.commands;
-      this.theme = options.theme || 'crimson_editor';
+      this.theme = options.theme || 'idle_fingers';
       this.couchJSHINT = options.couchJSHINT;
       this.edited = false;
 
@@ -783,14 +783,14 @@ function(app, FauxtonAPI, ace, spin, ZeroClipboard) {
     afterRender: function () {
       this.editor = ace.edit(this.editorId);
       this.setHeightToLineCount();
-
       this.editor.setTheme("ace/theme/" + this.theme);
 
       if (this.mode != "plain") {
         this.editor.getSession().setMode("ace/mode/" + this.mode);
       }
-      
+
       this.editor.setShowPrintMargin(false);
+      this.editor.autoScrollEditorIntoView = true;
       this.addCommands();
 
       if (this.couchJSHINT) {
@@ -821,7 +821,6 @@ function(app, FauxtonAPI, ace, spin, ZeroClipboard) {
     
       $(window).resize(resizeEditor);
       this.listenTo(FauxtonAPI.Events, FauxtonAPI.constants.EVENTS.BURGER_CLICKED, resizeEditor);
-
     },
 
     cleanup: function () {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/040a6069/app/templates/layouts/doc_editor.html
----------------------------------------------------------------------
diff --git a/app/templates/layouts/doc_editor.html b/app/templates/layouts/doc_editor.html
new file mode 100644
index 0000000..1da8d0a
--- /dev/null
+++ b/app/templates/layouts/doc_editor.html
@@ -0,0 +1,28 @@
+<%/*
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+*/%>
+
+<div class="wrapper">
+  <div id="primary-navbar"></div>
+
+  <div class="pusher">
+    <div id="dashboard" class="one-pane doc-editor-page">
+      <header class="fixed-header">
+        <div id="breadcrumbs"></div>
+        <div id="api-navbar"></div>
+      </header>
+
+      <div id="dashboard-content"></div>
+    </div>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/040a6069/assets/js/libs/ace/theme-idle_fingers.js
----------------------------------------------------------------------
diff --git a/assets/js/libs/ace/theme-idle_fingers.js b/assets/js/libs/ace/theme-idle_fingers.js
new file mode 100644
index 0000000..8e872c2
--- /dev/null
+++ b/assets/js/libs/ace/theme-idle_fingers.js
@@ -0,0 +1,102 @@
+define("ace/theme/idle_fingers",["require","exports","module","ace/lib/dom"], function(require,
exports, module) {
+
+  exports.isDark = true;
+  exports.cssClass = "ace-idle-fingers";
+  exports.cssText = ".ace-idle-fingers .ace_gutter {\
+background: #3A3A3A;\
+color: rgb(153,153,153)\
+}\
+.ace-idle-fingers .ace_print-margin {\
+width: 1px;\
+background: #3b3b3b\
+}\
+.ace-idle-fingers {\
+background-color: #4d4d4d;\
+color: #FFFFFF\
+}\
+.ace-idle-fingers .ace_cursor {\
+color: #ffffff\
+}\
+.ace-idle-fingers .ace_marker-layer .ace_selection {\
+background: rgba(90, 100, 126, 0.88)\
+}\
+.ace-idle-fingers.ace_multiselect .ace_selection.ace_start {\
+box-shadow: 0 0 3px 0px #4d4d4d;\
+border-radius: 2px\
+}\
+.ace-idle-fingers .ace_marker-layer .ace_step {\
+background: rgb(102, 82, 0)\
+}\
+.ace-idle-fingers .ace_marker-layer .ace_bracket {\
+margin: -1px 0 0 -1px;\
+border: 1px solid #404040\
+}\
+.ace-idle-fingers .ace_marker-layer .ace_active-line {\
+background: #000000;\
+opacity: 0.4;\
+}\
+.ace-idle-fingers .ace_gutter-active-line {\
+background-color: #000000;\
+opacity: 0.4;\
+}\
+.ace-idle-fingers .ace_marker-layer .ace_selected-word {\
+border: 1px solid rgba(90, 100, 126, 0.88)\
+}\
+.ace-idle-fingers .ace_invisible {\
+color: #404040\
+}\
+.ace-idle-fingers .ace_keyword,\
+.ace-idle-fingers .ace_meta {\
+color: #CC7833\
+}\
+.ace-idle-fingers .ace_constant,\
+.ace-idle-fingers .ace_constant.ace_character,\
+.ace-idle-fingers .ace_constant.ace_character.ace_escape,\
+.ace-idle-fingers .ace_constant.ace_other,\
+.ace-idle-fingers .ace_support.ace_constant {\
+color: #72cdf4\
+}\
+.ace-idle-fingers .ace_boolean {\
+color: #ff6532\
+}\
+.ace-idle-fingers .ace_invalid {\
+color: #FFFFFF;\
+background-color: #FF0000\
+}\
+.ace-idle-fingers .ace_fold {\
+background-color: #CC7833;\
+border-color: #FFFFFF\
+}\
+.ace-idle-fingers .ace_support.ace_function {\
+color: #B83426\
+}\
+.ace-idle-fingers .ace_variable.ace_parameter {\
+font-style: italic\
+}\
+.ace-idle-fingers .ace_string {\
+color: #29be9d\
+}\
+.ace-idle-fingers .ace_string.ace_regexp {\
+color: #CCCC33\
+}\
+.ace-idle-fingers .ace_comment {\
+font-style: italic;\
+color: #BC9458\
+}\
+.ace-idle-fingers .ace_meta.ace_tag {\
+color: #FFE5BB\
+}\
+.ace-idle-fingers .ace_entity.ace_name {\
+color: #FFC66D\
+}\
+.ace-idle-fingers .ace_collab.ace_user1 {\
+color: #4d4d4d;\
+background-color: #FFF980\
+}\
+.ace-idle-fingers .ace_indent-guide {\
+background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWMwMjLyZYiPj/8PAAreAwAI1+g0AAAAAElFTkSuQmCC)
right repeat-y\
+}";
+
+  var dom = require("../lib/dom");
+  dom.importCssString(exports.cssText, exports.cssClass);
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/040a6069/assets/less/codeeditor.less
----------------------------------------------------------------------
diff --git a/assets/less/codeeditor.less b/assets/less/codeeditor.less
new file mode 100644
index 0000000..c4a2670
--- /dev/null
+++ b/assets/less/codeeditor.less
@@ -0,0 +1,131 @@
+@import "mixins.less";
+
+
+#dashboard.doc-editor-page {
+  background-color: #4d4d4d;
+
+  #dashboard-content {
+    padding-bottom: 0px;
+    top: 0px;
+  }
+}
+
+.doc-editor-page {
+  border-left: none;
+
+  .fixed-header {
+    height: 65px;
+    .box-shadow(none);
+  }
+
+  #breadcrumbs {
+    white-space: nowrap;
+  }
+
+  #doc-editor-actions-panel {
+    position: absolute;
+    top: 64px;
+    right: 0px;
+    left: 0px;
+    width: 100%;
+    background-color: #f1f1f1;
+    height: 61px;
+  }
+
+  #editor-container {
+    margin-top: 0px;
+    font-size: 13px;
+    line-height: 22px;
+    padding-bottom: 15px;
+  }
+
+  .doc-actions-left {
+    float: left;
+    padding: 9px 0px;
+    button {
+      margin: 0px 10px 0px 30px;
+    }
+    div {
+      display: inline-block;
+    }
+  }
+
+  .cancel-button {
+    font-size: 14px;
+  }
+
+  .bgEditorGutter {
+    width: 49px;
+    position: absolute;
+    top: 0px;
+    bottom: 0px;
+    background-color: #3b3b3b;
+  }
+
+  .panel-button {
+    border: 0px;
+    background-color: #f1f1f1;
+    padding: 11px;
+    color: #555555;
+
+    span:first-of-type {
+      margin-left: 4px;
+    }
+
+    .icon {
+      font-size: 18px;
+    }
+  }
+  .panel-section {
+    border-left: 1px solid #cccccc;
+    text-align: center;
+    padding: 9px 0px;
+    display: inline-block;
+
+    &.open .dropdown-toggle {
+      box-shadow: none;
+      background-color: white;
+    }
+  }
+  .alignRight {
+    text-align: right;
+    font-size: 0; // prevents whitespace gaps between elements
+  }
+  .row-fluid.content-area {
+    background-color: #4d4d4d;
+  }
+
+  // overriding the ace editor inline styles
+  .ace_gutter-layer {
+    min-width: 49px;
+  }
+  .ace_gutter-cell {
+    min-width: 49px;
+  }
+  .ace_scrollbar-h {
+    overflow: hidden !important;
+  }
+
+  .ace_marker-layer .ace_bracket {
+    margin: 0px;
+    border: 1px solid #999999;
+  }
+
+  // hide the labels on the buttons when the screen is shrunk too small,
+  @media screen and (max-width: 1000px) {
+    .panel-button span {
+      display: none;
+    }
+  }
+
+  // hides the API Url header link when the page is too small (prevents wrapping)
+  @media screen and (max-width: 835px) {
+    #api-navbar {
+      display: none;
+    }
+  }
+
+  #dashboard-content .scrollable {
+    top: 125px;
+  }
+}

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/040a6069/assets/less/fauxton.less
----------------------------------------------------------------------
diff --git a/assets/less/fauxton.less b/assets/less/fauxton.less
index 3f686e0..6b27976 100644
--- a/assets/less/fauxton.less
+++ b/assets/less/fauxton.less
@@ -21,6 +21,7 @@
 @import "bootstrap/mixins.less";
 @import "prettyprint.less";
 @import "icons.less";
+@import "codeeditor.less";
 @import "templates.less";
 @import "formstyles.less";
 @import "pagination.less";
@@ -222,6 +223,29 @@ table.databases {
   }
 }
 
+#sidebar-content {
+  position: absolute;
+  top: 60px;
+  width: @sidebarWidth;
+  left: 0;
+  background-color: @secondarySidebar;
+  > div.inner {
+    display: block;
+  }
+}
+
+#dashboard-content .scrollable {
+  height: auto;
+  overflow-y: scroll;
+  overflow-x: hidden;
+  width: 100%;
+  position: absolute;
+  padding: 0;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+}
 
 /*ONE PANEL TEMPLATE ONLY STYLES  AKA _all_dbs */
 
@@ -428,12 +452,17 @@ div.spinner {
 
   .breadcrumb-back-link {
     border-right: 1px solid #ccc;
+    a {
+      font-size: 20px;
+      margin-top: 1px;
+    }
   }
 
   .breadcrumb {
     margin-bottom: 0;
     background-color: transparent;
     padding: 0;
+
     li {
       padding: 22px 10px;
       &:first-child {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/040a6069/assets/less/templates.less
----------------------------------------------------------------------
diff --git a/assets/less/templates.less b/assets/less/templates.less
index 94b5bd7..a2db779 100644
--- a/assets/less/templates.less
+++ b/assets/less/templates.less
@@ -382,6 +382,7 @@ with_tabs_sidebar.html
   position: fixed;
   z-index: 11;
   display: block;
+
   .topmenu-defaults;
   background-color: #CBCBCB;
   .closeMenu & {
@@ -395,9 +396,12 @@ with_tabs_sidebar.html
 }
 
 #dashboard-content {
-  padding-left: 15px;
-  padding-right: 15px;
-  padding-top: 20px;
+
+  & > div {
+    padding-left: 15px;
+    padding-right: 15px;
+    padding-top: 20px;
+  }
 
   &.row-fluid {
     /*remove gutter without rewriting variable*/


Mime
View raw message