diff --git a/bower.json b/bower.json
index 13bb431b..c12e07d7 100644
--- a/bower.json
+++ b/bower.json
@@ -32,7 +32,8 @@
"js-sequence-diagrams": "https://github.com/benweet/js-sequence-diagrams.git#d60c973aa0ff148dc588c7ceee0b41e59dff3f9f",
"flowchart": "https://github.com/adrai/flowchart.js.git#~1.2.10",
"monetizejs": "~0.2.0",
- "MathJax": "~2.4.0"
+ "MathJax": "~2.4.0",
+ "alertify.js": "https://github.com/fabien-d/alertify.js.git#fc2e06fa39873363dda199204b8544119ab060bf"
},
"main": [
"/public/res/main.js",
diff --git a/couchdb/setup-db.js b/couchdb/setup-db.js
index abb9d841..549dfd38 100644
--- a/couchdb/setup-db.js
+++ b/couchdb/setup-db.js
@@ -18,6 +18,15 @@ var validate = function(newDoc) {
if(!newDoc._id.match(/[a-zA-Z0-9]{24}/)) {
throw({forbidden: 'Invalid ID format.'});
}
+ if(newDoc._deleted) {
+ if(newDoc.updated !== undefined ||
+ newDoc.tags !== undefined ||
+ newDoc.title !== undefined ||
+ newDoc._attachments !== undefined) {
+ throw({forbidden: 'Deleted document must be empty.'});
+ }
+ return;
+ }
if(toString.call(newDoc.updated) !== '[object Number]') {
throw({forbidden: 'Update time must be an integer.'});
}
@@ -65,7 +74,9 @@ var validate = function(newDoc) {
};
var byUpdate = function(doc) {
- emit(doc.updated, null);
+ if(!doc.tags || !doc.tags.length) {
+ emit(doc.updated, null);
+ }
};
var byTagAndUpdate = function(doc) {
diff --git a/public/res/core.js b/public/res/core.js
index 09894979..2cf5e6a4 100644
--- a/public/res/core.js
+++ b/public/res/core.js
@@ -393,7 +393,7 @@ define([
editor.focus();
// Revert to current theme when settings modal is closed
applyTheme(window.theme);
- }).on('keyup', '.modal', function(e) {
+ }).on('keypress', '.modal', function(e) {
// Handle enter key in modals
if(e.which == 13 && !$(e.target).is("textarea")) {
$(this).find(".modal-footer a:last").click();
diff --git a/public/res/html/bodyEditor.html b/public/res/html/bodyEditor.html
index dd2be731..ec971711 100644
--- a/public/res/html/bodyEditor.html
+++ b/public/res/html/bodyEditor.html
@@ -89,7 +89,7 @@
Synchronize
- Open/save in the Cloud
+ Open/Save in the Cloud
',
- ' ',
- '<%= document.title %>
',
- ''
- ].join('');
-
-
eventMgr.addListener("onReady", function() {
+ var documentEltTmpl = [
+ '',
+ '<%= date %>
',
+ ' ',
+ '<%= document.title %>
',
+ ''
+ ].join('');
+
+ $documentIdsElt = $('#input-sync-import-couchdb-documentid');
var modalElt = document.querySelector('.modal-download-couchdb');
- var $documentListElt = $(modalElt.querySelector('.list-group.document-list'));
+ var $documentListElt = $(modalElt.querySelector('.document-list'));
var $selectedDocumentListElt = $(modalElt.querySelector('.selected-document-list'));
var $pleaseWaitElt = $(modalElt.querySelector('.please-wait'));
+ var $noDocumentElt = $(modalElt.querySelector('.no-document'));
var $moreDocumentsElt = $(modalElt.querySelector('.more-documents'));
var documentMap, lastDocument;
var selectedDocuments, $selectedElts;
function doSelect() {
$selectedElts = $documentListElt.children('.active').clone();
selectedDocuments = [];
+ var selectedDocumentIds = [];
$selectedElts.each(function() {
- selectedDocuments.push(documentMap[$(this).data('documentId')]);
+ var documentId = $(this).data('documentId');
+ selectedDocumentIds.push(documentId);
+ selectedDocuments.push(documentMap[documentId]);
});
+ $documentIdsElt.val(selectedDocumentIds.join(' '));
$selectedDocumentListElt.empty().append($selectedElts);
$(modalElt.querySelectorAll('.action-delete-items')).parent().toggleClass('disabled', selectedDocuments.length === 0);
}
@@ -237,12 +245,14 @@ define([
doSelect();
}
clear();
- function deleteMode(enabled) {
- $(modalElt.querySelectorAll('.confirm-delete')).toggleClass('hide', !enabled);
- $(modalElt.querySelectorAll('.document-list')).toggleClass('hide', enabled);
+ function setMode(mode) {
+ $(modalElt.querySelectorAll('.list-mode')).toggleClass('hide', mode != 'list');
+ $(modalElt.querySelectorAll('.delete-mode')).toggleClass('hide', mode != 'delete');
+ $(modalElt.querySelectorAll('.byid-mode')).toggleClass('hide', mode != 'byid');
}
function updateDocumentList() {
$pleaseWaitElt.removeClass('hide');
+ $noDocumentElt.addClass('hide');
$moreDocumentsElt.addClass('hide');
couchdbHelper.listDocuments(undefined, lastDocument && lastDocument.updated, function(err, result) {
if(err) {
@@ -262,11 +272,32 @@ define([
}, '');
$documentListElt.append(documentListHtml);
+ if($documentListElt.children().length === 0) {
+ $noDocumentElt.removeClass('hide');
+ }
+ });
+ setMode('list');
+ }
+
+ var tagList = utils.retrieveIgnoreError(PROVIDER_COUCHDB + '.tagList') || [];
+ var $selectTagElt = $('#select-sync-import-couchdb-tag');
+ function updateTagList() {
+ $selectTagElt.empty().append(crel('option', {
+ value: ''
+ }, 'none'));
+ _.sortBy(tagList, function(tag) {
+ return tag.toLowerCase();
+ }).forEach(function(tag) {
+ $selectTagElt.append(crel('option', {
+ value: tag
+ }, tag));
});
- deleteMode(false);
}
$(modalElt)
- .on('show.bs.modal', updateDocumentList)
+ .on('show.bs.modal', function() {
+ updateDocumentList();
+ updateTagList();
+ })
.on('hidden.bs.modal', clear)
.on('click', '.document-list .document', function() {
$(this).toggleClass('active');
@@ -277,10 +308,40 @@ define([
$documentListElt.children().removeClass('active');
doSelect();
})
+ .on('click', '.action-byid-mode', function() {
+ setMode('byid');
+ })
+ .on('click', '.action-add-tag', function() {
+ alertify.prompt("Enter a tag:", function (e, tag) {
+ if(!e || !tag) {
+ return;
+ }
+ tagList.push(tag);
+ tagList = _.chain(tagList)
+ .sortBy(function(tag) {
+ return tag.toLowerCase();
+ })
+ .unique(true)
+ .value();
+ storage[PROVIDER_COUCHDB + '.tagList'] = JSON.stringify(tagList);
+ updateTagList();
+ $selectTagElt.val(tag);
+ }, "Tag");
+ })
+ .on('click', '.action-remove-tag', function() {
+ var tag = $selectTagElt.val();
+ tag && alertify.confirm('You are removing ' + $selectTagElt.val() + ' from your list of filters.', function (e) {
+ if(e) {
+ tagList = _.without(tagList, tag);
+ storage[PROVIDER_COUCHDB + '.tagList'] = JSON.stringify(tagList);
+ updateTagList();
+ $selectTagElt.val('');
+ }
+ }, "Tag");
+ })
.on('click', '.action-delete-items', function() {
- doSelect();
if($selectedElts.length) {
- deleteMode(true);
+ setMode('delete');
}
})
.on('click', '.action-delete-items-confirm', function() {
@@ -289,7 +350,7 @@ define([
updateDocumentList();
})
.on('click', '.action-cancel', function() {
- deleteMode(false);
+ setMode('list');
});
});
diff --git a/public/res/styles/alertify.less b/public/res/styles/alertify.less
new file mode 100644
index 00000000..dd5d22a2
--- /dev/null
+++ b/public/res/styles/alertify.less
@@ -0,0 +1,239 @@
+.alertify,
+.alertify-show,
+.alertify-log {
+ -webkit-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275);
+ -moz-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275);
+ -ms-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275);
+ -o-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275);
+ transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); /* easeOutBack */
+}
+.alertify-hide {
+ -webkit-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
+ -moz-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
+ -ms-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
+ -o-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
+ transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); /* easeInBack */
+}
+.alertify-log-hide {
+ -webkit-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
+ -moz-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
+ -ms-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
+ -o-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
+ transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); /* easeInBack */
+}
+.alertify-cover {
+ position: fixed; z-index: 99999;
+ top: 0; right: 0; bottom: 0; left: 0;
+ background-color:white;
+ filter:alpha(opacity=0);
+ opacity:0;
+}
+.alertify-cover-hidden {
+ display: none;
+}
+.alertify {
+ position: fixed; z-index: 99999;
+ top: 50px; left: 50%;
+ width: 550px;
+ margin-left: -275px;
+ opacity: 1;
+}
+.alertify-hidden {
+ opacity: 0;
+ display: none;
+}
+/* overwrite display: none; for everything except IE6-8 */
+:root *> .alertify-hidden {
+ display: block;
+ visibility: hidden;
+}
+.alertify-logs {
+ position: fixed;
+ z-index: 5000;
+ bottom: 10px;
+ right: 10px;
+ width: 300px;
+}
+.alertify-logs-hidden {
+ display: none;
+}
+.alertify-log {
+ display: block;
+ margin-top: 10px;
+ position: relative;
+ right: -300px;
+ opacity: 0;
+}
+.alertify-log-show {
+ right: 0;
+ opacity: 1;
+}
+.alertify-log-hide {
+ -webkit-transform: translate(300px, 0);
+ -moz-transform: translate(300px, 0);
+ -ms-transform: translate(300px, 0);
+ -o-transform: translate(300px, 0);
+ transform: translate(300px, 0);
+ opacity: 0;
+}
+.alertify-dialog {
+ padding: 25px;
+}
+.alertify-resetFocus {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+}
+.alertify-inner {
+ text-align: center;
+}
+.alertify-text {
+ margin-bottom: 15px;
+ width: 100%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ font-size: 100%;
+}
+
+.alertify-isHidden {
+ display: none;
+}
+
+@media only screen and (max-width: 680px) {
+ .alertify,
+ .alertify-logs {
+ width: 90%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .alertify {
+ left: 5%;
+ margin: 0;
+ }
+}
+
+/**
+ * Twitter Bootstrap Look and Feel
+ * Based on http://twitter.github.com/bootstrap/
+ */
+.alertify,
+.alertify-log {
+ font-family: @font-family-base;
+}
+.alertify {
+ background: @secondary-bg-lighter;
+ border: 1px solid @secondary-border-color;
+ border-radius: 6px;
+ width: 398px;
+ margin-left: -200px;
+ -webkit-background-clip: padding; /* Safari 4? Chrome 6? */
+ -moz-background-clip: padding; /* Firefox 3.6 */
+ background-clip: padding-box; /* Firefox 4, Safari 5, Opera 10, IE 9 */
+}
+.alertify-dialog {
+ padding: 0;
+}
+.alertify-inner {
+ text-align: left;
+}
+.alertify-message {
+ padding: 15px;
+ margin: 0;
+}
+.alertify-text-wrapper {
+ padding: 0 15px;
+}
+.alertify-text {
+ display: block;
+ width: 100%;
+ height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)
+ padding: @padding-base-vertical @padding-base-horizontal;
+ font-size: @font-size-base;
+ line-height: @line-height-base;
+ color: @input-color;
+ vertical-align: middle;
+ background-color: @input-bg;
+ background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214
+ border: 1px solid @input-border;
+ border-radius: @input-border-radius;
+ .box-shadow(~"@{form-control-inset-shadow}");
+}
+.alertify-text:focus {
+ .form-control-focus();
+}
+
+.alertify-buttons {
+ padding: 14px 15px 15px;
+ background:@secondary-bg-light;
+ border-top: 1px solid @secondary-border-color-light;
+ text-align: right;
+}
+.alertify-button {
+ margin-left: 10px;
+}
+
+.alertify-button-cancel,
+.alertify-button-ok {
+ display: inline-block;
+ margin-bottom: 0; // For input.btn
+ font-weight: @btn-font-weight;
+ text-align: center;
+ vertical-align: middle;
+ cursor: pointer;
+ background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214
+ border: 1px solid transparent;
+ white-space: nowrap;
+ .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base);
+ .user-select(none);
+
+ &:focus {
+ .tab-focus();
+ }
+
+ &:hover,
+ &:focus {
+ color: @btn-default-color;
+ text-decoration: none;
+ }
+
+ &:active,
+ &.active {
+ outline: 0;
+ background-image: none;
+ .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));
+ }
+}
+
+.alertify-button-cancel {
+ .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);
+}
+.alertify-button-ok {
+ .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);
+}
+
+.alertify-log {
+ background: #D9EDF7;
+ padding: 8px 14px;
+ border-radius: 4px;
+ color: #3A8ABF;
+ text-shadow: 0 1px 0 rgba(255,255,255,.5);
+ border: 1px solid #BCE8F1;
+}
+.alertify-log-error {
+ color: #B94A48;
+ background: #F2DEDE;
+ border: 1px solid #EED3D7;
+}
+.alertify-log-success {
+ color: #468847;
+ background: #DFF0D8;
+ border: 1px solid #D6E9C6;
+}
+
diff --git a/public/res/styles/main.less b/public/res/styles/main.less
index 06e21cc6..325443bd 100644
--- a/public/res/styles/main.less
+++ b/public/res/styles/main.less
@@ -1,4 +1,5 @@
@import "base.less";
+@import "alertify.less";
@import "../bower-libs/bootstrap-tour/src/less/bootstrap-tour.less";
@import (less) "jquery.jgrowl.css";
@@ -144,7 +145,7 @@ body {
.user-select(none);
}
-.dropdown-menu, .modal-content, .panel-content, .search-bar, .popover, .find-replace {
+.dropdown-menu, .modal-content, .panel-content, .search-bar, .popover, .find-replace, .alertify {
.box-shadow(0 4px 16px rgba(0,0,0,.225));
}
@@ -299,7 +300,7 @@ a {
@btn-default-bg: @transparent;
@btn-default-border: @transparent;
@btn-default-hover-bg: fade(@secondary-desaturated, 4%);
-.btn-default {
+.btn-default, .alertify-button-cancel {
&:hover,
&:focus,
&:active,
@@ -314,7 +315,7 @@ a {
@btn-primary-bg: @primary-bg;
@btn-primary-border: fade(@secondary, 5%);
@btn-primary-hover-bg: mix(@primary-desaturated, @btn-primary-bg, 7.5%);
-.btn-primary {
+.btn-primary, .alertify-button-ok {
&:hover,
&:focus,
&:active,
@@ -386,23 +387,21 @@ a {
.box-shadow(~"@{form-control-inset-shadow}");
}
-// Fix form-inline broken since Bootstrap v3
.form-inline {
- .form-control {
- display: inline-block;
- }
- .col-sm-1 { width: percentage((1 / @grid-columns)); }
- .col-sm-2 { width: percentage((2 / @grid-columns)); }
- .col-sm-3 { width: percentage((3 / @grid-columns)); }
- .col-sm-4 { width: percentage((4 / @grid-columns)); }
- .col-sm-5 { width: percentage((5 / @grid-columns)); }
- .col-sm-6 { width: percentage((6 / @grid-columns)); }
- .col-sm-7 { width: percentage((7 / @grid-columns)); }
- .col-sm-8 { width: percentage((8 / @grid-columns)); }
- .col-sm-9 { width: percentage((9 / @grid-columns)); }
- .col-sm-10 { width: percentage((10/ @grid-columns)); }
- .col-sm-11 { width: percentage((11/ @grid-columns)); }
- .col-sm-12 { width: 100%; }
+ @media (min-width: @screen-sm) {
+ .col-sm-1 { width: percentage((1 / @grid-columns)) !important; }
+ .col-sm-2 { width: percentage((2 / @grid-columns)) !important; }
+ .col-sm-3 { width: percentage((3 / @grid-columns)) !important; }
+ .col-sm-4 { width: percentage((4 / @grid-columns)) !important; }
+ .col-sm-5 { width: percentage((5 / @grid-columns)) !important; }
+ .col-sm-6 { width: percentage((6 / @grid-columns)) !important; }
+ .col-sm-7 { width: percentage((7 / @grid-columns)) !important; }
+ .col-sm-8 { width: percentage((8 / @grid-columns)) !important; }
+ .col-sm-9 { width: percentage((9 / @grid-columns)) !important; }
+ .col-sm-10 { width: percentage((10/ @grid-columns)) !important; }
+ .col-sm-11 { width: percentage((11/ @grid-columns)) !important; }
+ .col-sm-12 { width: 100% !important; }
+ }
* {float:none;}
}
@@ -803,6 +802,9 @@ a {
.name, .date, .file-count {
padding: 9px 20px 9px 15px;
}
+ .name i {
+ margin-right: 8px;
+ }
.date {
font-weight: normal;
}
diff --git a/public/res/themes/default.less b/public/res/themes/default.less
index 7d069253..b1b85ff1 100644
--- a/public/res/themes/default.less
+++ b/public/res/themes/default.less
@@ -42,7 +42,7 @@
@btn-primary-color: #fff;
@btn-primary-bg: @link-color;
@btn-primary-hover-bg: darken(@btn-primary-bg, 8%);
-.btn-primary {
+.btn-primary, .alertify-button-ok {
&:hover,
&:focus,
&:active,