CouchDB sync provider part 2

This commit is contained in:
benweet 2014-09-12 00:36:19 +01:00
parent 8f5da7998f
commit 5afefd7b21
10 changed files with 395 additions and 75 deletions

View File

@ -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",

View File

@ -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) {

View File

@ -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();

View File

@ -89,7 +89,7 @@
<a href="#" data-toggle="collapse" data-target=".collapse-synchronize"
class="list-group-item">
<div><i class="icon-refresh"></i> Synchronize</div>
<small>Open/save in the Cloud</small>
<small>Open/Save in the Cloud</small>
</a>
<div class="sub-menu collapse collapse-synchronize clearfix">
<ul class="nav alert alert-danger show-already-synchronized">
@ -102,9 +102,9 @@
</ul>
<ul class="nav">
<li><a href="#" class="action-sync-import-dialog-couchdb"><i
class="icon-provider-couchdb"></i> Open from CouchDB <sup class="text-danger">beta</sup></a></li>
class="icon-provider-couchdb"></i> Open from CouchDB <sup class="text-danger">new</sup></a></li>
<li><a href="#" class="action-sync-export-dialog-couchdb"><i
class="icon-provider-couchdb"></i> Save on CouchDB <sup class="text-danger">beta</sup></a></li>
class="icon-provider-couchdb"></i> Save on CouchDB <sup class="text-danger">new</sup></a></li>
<li><a href="#" class="action-sync-import-dropbox"><i
class="icon-provider-dropbox"></i> Open from Dropbox</a></li>
<li><a href="#" class="action-sync-export-dialog-dropbox"><i
@ -556,23 +556,23 @@
<button type="button" class="close" data-dismiss="modal"
aria-hidden="true">&times;</button>
<h2 class="modal-title">Open from CouchDB</h2>
<br>
<div class="form-horizontal">
<div class="form-group">
<div class="form-horizontal list-mode">
<br>
<div class="form-group form-inline">
<label for="select-sync-import-couchdb-tag" class="col-sm-3 control-label">Filter by tag</label>
<div class="col-sm-5">
<select id="select-sync-import-couchdb-tag" class="form-control">
<option value="">None</option>
</select>
</div>
<div class="col-sm-4">
<button class="btn btn-link"><i class="icon-tags"></i> Manage tags</button>
</div>
<select id="select-sync-import-couchdb-tag" class="col-sm-4 form-control">
</select>
<span class="col-sm-5">
<button class="btn btn-link action-add-tag"><i class="icon-tag"></i> Add
</button>
<button class="btn btn-link action-remove-tag"><i class="icon-tag"></i> Remove
</button>
</span>
</div>
</div>
</div>
<div class="modal-body">
<div class="form-horizontal hide">
<div class="form-horizontal byid-mode">
<div class="form-group">
<label for="input-sync-import-couchdb-documentid" class="col-sm-3 control-label">Document
ID</label>
@ -584,7 +584,7 @@
</div>
</div>
</div>
<ul class="document-list nav nav-pills">
<ul class="list-mode nav nav-pills">
<li class="pull-right dropdown"><a href="#"
data-toggle="dropdown"><i class="icon-check"></i> Selection
<b class="caret"></b></a>
@ -597,26 +597,31 @@
</ul>
</li>
</ul>
<p class="document-list">
<p class="list-mode">
</p>
<div class="list-group document-list"></div>
<div class="document-list">
<div class="please-wait">Please wait...</div>
<div class="list-group document-list list-mode"></div>
<div class="list-mode">
<div class="please-wait"><b>Please wait...</b></div>
<div class="no-document"><b>No document.</b></div>
<button class="more-documents btn btn-link">More documents!</button>
</div>
<p class="confirm-delete hide">The following documents will be
removed from the database:</p>
<p class="delete-mode hide">The following documents will be
removed from CouchDB:</p>
<div class="confirm-delete list-group selected-document-list hide"></div>
<div class="delete-mode list-group selected-document-list hide"></div>
</div>
<div class="modal-footer">
<a href="#" class="btn btn-default pull-left list-mode action-byid-mode">Open by ID...</a>
<a href="#"
class="btn btn-default confirm-delete action-cancel hide">Cancel</a>
class="btn btn-default delete-mode action-cancel hide">Cancel</a>
<a href="#"
class="btn btn-primary confirm-delete action-delete-items-confirm hide">Delete</a>
<a href="#" class="btn btn-default document-list" data-dismiss="modal">Cancel</a>
class="btn btn-primary delete-mode action-delete-items-confirm hide">Delete</a>
<a href="#" class="btn btn-default byid-mode action-cancel">Cancel</a>
<a href="#" data-dismiss="modal"
class="btn btn-primary action-sync-import-couchdb document-list">Open</a>
class="btn btn-primary action-sync-import-couchdb byid-mode">Open</a>
<a href="#" class="btn btn-default list-mode" data-dismiss="modal">Cancel</a>
<a href="#" data-dismiss="modal"
class="btn btn-primary action-sync-import-couchdb list-mode">Open</a>
</div>
</div>
</div>

View File

@ -402,14 +402,14 @@ define([
layout.init = function() {
var isModalShown = false;
var isModalShown = 0;
$(document.body).on('show.bs.modal', '.modal', function() {
// Close panel if open
menuPanel.toggle(false);
documentPanel.toggle(false);
isModalShown = true;
isModalShown++;
}).on('hidden.bs.modal', '.modal', function() {
isModalShown = false;
isModalShown--;
});
// Tweak the body element

View File

@ -70,7 +70,8 @@ requirejs.config({
monetizejs: 'bower-libs/monetizejs/src/monetize',
'to-markdown': 'bower-libs/to-markdown/src/to-markdown',
waitForImages: 'bower-libs/waitForImages/dist/jquery.waitforimages',
MathJax: 'bower-libs/MathJax/MathJax'
MathJax: 'bower-libs/MathJax/MathJax',
alertify: 'bower-libs/alertify.js/lib/alertify'
},
shim: {
underscore: {

View File

@ -1,6 +1,8 @@
define([
"jquery",
"underscore",
"crel",
"alertify",
"constants",
"utils",
"storage",
@ -12,7 +14,7 @@ define([
"fileSystem",
"editor",
"helpers/couchdbHelper"
], function($, _, constants, utils, storage, logger, Provider, settings, eventMgr, fileMgr, fileSystem, editor, couchdbHelper) {
], function($, _, crel, alertify, constants, utils, storage, logger, Provider, settings, eventMgr, fileMgr, fileSystem, editor, couchdbHelper) {
var PROVIDER_COUCHDB = "couchdb";
@ -71,10 +73,11 @@ define([
});
}
var $documentIdsElt;
couchdbProvider.importFiles = function() {
var tag = $('#select-sync-import-couchdb-tag').val();
if(!tag) {
var ids = _.chain(($('#input-sync-import-couchdb-documentid').val() || '').split(/\s+/))
var ids = _.chain(($documentIdsElt.val() || '').split(/\s+/))
.compact()
.unique()
.value();
@ -204,29 +207,34 @@ define([
});
};
var documentEltTmpl = [
'<a href="#" class="list-group-item document clearfix" data-document-id="<%= document._id %>">',
'<div class="date pull-right"><%= date %></div></div>',
'<div class="name"><i class="icon-provider-couchdb"></i> ',
'<%= document.title %></div>',
'</a>'
].join('');
eventMgr.addListener("onReady", function() {
var documentEltTmpl = [
'<a href="#" class="list-group-item document clearfix" data-document-id="<%= document._id %>">',
'<div class="date pull-right"><%= date %></div></div>',
'<div class="name"><i class="icon-provider-couchdb"></i> ',
'<%= document.title %></div>',
'</a>'
].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 <b>' + $selectTagElt.val() + '</b> 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');
});
});

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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,