From 8127bfb5d02bfe4b883c8f990c6b98781fb90ab0 Mon Sep 17 00:00:00 2001 From: benweet Date: Sun, 12 Oct 2014 12:14:03 +0100 Subject: [PATCH] Implemented sync/publish location links --- couchdb/{setup-db.js => setup.js} | 96 +++-- doc/couchdb-setup.md | 42 +++ package.json | 3 +- .../{manifest.json => manifest.webapp} | 0 public/res-min/img/button.svg | 41 +-- public/res/classes/FileDescriptor.js | 312 +++++++++------- public/res/core.js | 4 + public/res/eventMgr.js | 1 + .../res/extensions/dialogManagePublication.js | 95 ++--- public/res/extensions/dialogManageSharing.js | 111 +++--- .../extensions/dialogManageSynchronization.js | 97 ++--- public/res/extensions/documentTitle.js | 2 +- public/res/helpers/couchdbHelper.js | 15 +- public/res/helpers/githubHelper.js | 4 +- public/res/helpers/googleHelper.js | 8 +- public/res/helpers/wordpressHelper.js | 343 +++++++++--------- public/res/html/bodyEditor.html | 34 +- public/res/html/dialogAbout.html | 4 + public/res/html/dialogExportGdrive.html | 16 +- .../html/dialogManagePublicationLocation.html | 19 +- .../dialogManageSynchronizationLocation.html | 24 +- public/res/html/findReplaceSettingsBlock.html | 2 +- public/res/img/menu.png | Bin 20397 -> 22202 bytes public/res/main.js | 3 +- public/res/providers/bloggerPageProvider.js | 11 +- public/res/providers/bloggerProvider.js | 11 +- public/res/providers/couchdbProvider.js | 33 +- public/res/providers/dropboxProvider.js | 13 +- public/res/providers/gdriveProviderBuilder.js | 10 + public/res/providers/gistProvider.js | 7 + public/res/providers/githubProvider.js | 62 ++-- public/res/providers/tumblrProvider.js | 11 +- public/res/providers/wordpressProvider.js | 16 +- public/res/settings.js | 1 + public/res/sharing.js | 13 +- public/res/styles/base.less | 4 +- public/res/styles/main.less | 51 ++- public/res/styles/solarized.less | 1 - public/res/themes/default.less | 10 +- public/res/themes/school.less | 2 +- 40 files changed, 909 insertions(+), 623 deletions(-) rename couchdb/{setup-db.js => setup.js} (66%) create mode 100644 doc/couchdb-setup.md rename public/firefox-app/{manifest.json => manifest.webapp} (100%) diff --git a/couchdb/setup-db.js b/couchdb/setup.js similarity index 66% rename from couchdb/setup-db.js rename to couchdb/setup.js index 549dfd38..2f1f8267 100644 --- a/couchdb/setup-db.js +++ b/couchdb/setup.js @@ -1,6 +1,3 @@ -var request = require('request'); -var async = require('async'); - var validate = function(newDoc) { Object.keys(newDoc).forEach(function(key) { if(key[0] !== '_' && [ @@ -88,14 +85,6 @@ var byTagAndUpdate = function(doc) { }); }; - -if(process.argv.length < 3) { - console.error('Missing URL parameter'); - process.exit(-1); -} - -var url = process.argv[2]; - var ddocs = [ { path: '/_design/validate', @@ -125,31 +114,66 @@ var ddocs = [ } ]; -async.each(ddocs, function(ddoc, cb) { +if(process.argv.length < 3) { + console.error('Missing URL parameter'); + process.exit(-1); +} - request.get(url + ddoc.path, function(err, res) { - if(res && res.body) { - ddoc.body._rev = JSON.parse(res.body)._rev; - } - request.put({ - url: url + ddoc.path, - json: true, - body: ddoc.body - }, function(err, res) { - if(err) { - return cb(res); - } - if(res.statusCode >= 300) { - return cb(res.body); - } - cb(); - }); - }); +var url = require('url').parse(process.argv[2]); +var request = require(url.protocol === 'https:' ? 'https' : 'http').request; -}, function(err) { - if(err) { - console.error(err); - } else { - console.log('All design documents updated successfully'); +function onError(err, body) { + console.error(err); + body && console.error(body); + process.exit(1); +} +function uploadDdoc() { + if(ddocs.length === 0) { + return console.log('All design documents updated successfully.'); } -}); + var ddoc = ddocs.shift(); + var options = { + hostname: url.hostname, + port: url.port, + path: url.path + ddoc.path, + auth: url.auth, + method: 'GET' + }; + request(options, function(res) { + var body = ''; + res + .on('data', function(chunk) { + body += chunk; + }) + .on('end', function() { + res.statusCode >= 300 && onError('Status code: ' + res.statusCode, body); + ddoc.body._rev = JSON.parse(body)._rev; + var options = { + hostname: url.hostname, + port: url.port, + path: url.path + ddoc.path, + auth: url.auth, + method: 'PUT', + headers: { + 'Content-type': 'application/json' + } + }; + request(options, function(res) { + var body = ''; + res + .on('data', function(chunk) { + body += chunk; + }) + .on('end', function() { + res.statusCode >= 300 && onError('Status code: ' + res.statusCode, body); + uploadDdoc(); + }); + }) + .on('error', onError) + .end(JSON.stringify(ddoc.body)); + }); + }) + .on('error', onError) + .end(); +} +uploadDdoc(); diff --git a/doc/couchdb-setup.md b/doc/couchdb-setup.md new file mode 100644 index 00000000..30581cbd --- /dev/null +++ b/doc/couchdb-setup.md @@ -0,0 +1,42 @@ +### Pre-requisites + +- CouchDB 1.5 or later, because of the use of `POST /{db}/_changes`, +- Node.js, to load the design documents in the database. + +> **Note:** +> +> - In order to work with https://stackedit.io, your database has to be accessible through HTTPS. You can use a free hosting service like [Couchappy](https://www.couchappy.com/) or [configure your own instance to use SSL](http://docs.couchdb.org/en/latest/config/http.html#ssl). +> +> - StackEdit doesn't deal with user access rights, but you can still set permissions for your database and configure StackEdit to connect to it using URL like this: `https://username:password@instance.couchappy.com/documents`. +> +> - It's up to you to trigger the database compaction, or to keep the full history of your documents. + + +### Enable CORS + +Add the following key/value pairs to your CouchDB configuration: + +``` +[httpd] +enable_cors = true + +[cors] +origins = http://localhost, https://stackedit.io +``` + + +### Create the database + +```bash +curl -X PUT https://instance.couchappy.com/documents +``` + +### Insert the design documents + +```bash +curl https://raw.githubusercontent.com/benweet/stackedit/master/couchdb/setup.js | node /dev/stdin https://instance.couchappy.com/documents +``` + +### Update StackEdit settings + +To configure StackEdit to use your CouchDB instance, change the in URL in `Menu` > `Settings` > `Advanced` > `CouchDB URL` to `https://instance.couchappy.com/documents`. \ No newline at end of file diff --git a/package.json b/package.json index f185578a..06359318 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,7 @@ "gulp-util": "^3.0.1", "knox": "^0.9.1", "mime": "^1.2.11", - "event-stream": "^3.1.7", - "async": "^0.9.0" + "event-stream": "^3.1.7" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/public/firefox-app/manifest.json b/public/firefox-app/manifest.webapp similarity index 100% rename from public/firefox-app/manifest.json rename to public/firefox-app/manifest.webapp diff --git a/public/res-min/img/button.svg b/public/res-min/img/button.svg index 9cb3f0d4..63b81154 100644 --- a/public/res-min/img/button.svg +++ b/public/res-min/img/button.svg @@ -1,40 +1,37 @@ - + - + - - - - + - - - - - + + + + + - - - - - - - + + + + + + + - - + + - - + + diff --git a/public/res/classes/FileDescriptor.js b/public/res/classes/FileDescriptor.js index 7c984349..08049d30 100644 --- a/public/res/classes/FileDescriptor.js +++ b/public/res/classes/FileDescriptor.js @@ -1,145 +1,187 @@ define([ - "underscore", - "utils", - "storage", + "underscore", + "utils", + "storage", ], function(_, utils, storage) { - function FileDescriptor(fileIndex, title, syncLocations, publishLocations) { - this.fileIndex = fileIndex; - this._title = title || storage[fileIndex + ".title"]; - this._editorScrollTop = parseInt(storage[fileIndex + ".editorScrollTop"]) || 0; - this._editorStart = parseInt(storage[fileIndex + ".editorEnd"]) || 0; - this._editorEnd = parseInt(storage[fileIndex + ".editorEnd"]) || 0; - this._previewScrollTop = parseInt(storage[fileIndex + ".previewScrollTop"]) || 0; - this._selectTime = parseInt(storage[fileIndex + ".selectTime"]) || 0; - this._discussionList = JSON.parse(storage[fileIndex + ".discussionList"] || '{}'); - this.syncLocations = syncLocations || {}; - this.publishLocations = publishLocations || {}; - Object.defineProperty(this, 'title', { - get: function() { - return this._title; - }, - set: function(title) { - this._title = title; - storage[this.fileIndex + ".title"] = title; - } - }); - Object.defineProperty(this, 'content', { - get: function() { - return storage[this.fileIndex + ".content"]; - }, - set: function(content) { - storage[this.fileIndex + ".content"] = content; - } - }); - Object.defineProperty(this, 'editorScrollTop', { - get: function() { - return this._editorScrollTop; - }, - set: function(editorScrollTop) { - this._editorScrollTop = editorScrollTop; - storage[this.fileIndex + ".editorScrollTop"] = editorScrollTop; - } - }); - Object.defineProperty(this, 'editorStart', { - get: function() { - return this._editorStart; - }, - set: function(editorStart) { - this._editorStart = editorStart; - storage[this.fileIndex + ".editorStart"] = editorStart; - } - }); - Object.defineProperty(this, 'editorEnd', { - get: function() { - return this._editorEnd; - }, - set: function(editorEnd) { - this._editorEnd = editorEnd; - storage[this.fileIndex + ".editorEnd"] = editorEnd; - } - }); - Object.defineProperty(this, 'previewScrollTop', { - get: function() { - return this._previewScrollTop; - }, - set: function(previewScrollTop) { - this._previewScrollTop = previewScrollTop; - storage[this.fileIndex + ".previewScrollTop"] = previewScrollTop; - } - }); - Object.defineProperty(this, 'selectTime', { - get: function() { - return this._selectTime; - }, - set: function(selectTime) { - this._selectTime = selectTime; - storage[this.fileIndex + ".selectTime"] = selectTime; - } - }); - Object.defineProperty(this, 'discussionList', { - get: function() { - return this._discussionList; - }, - set: function(discussionList) { - this._discussionList = discussionList; - storage[this.fileIndex + ".discussionList"] = JSON.stringify(discussionList); - } - }); - Object.defineProperty(this, 'discussionListJSON', { - get: function() { - return storage[this.fileIndex + ".discussionList"] || '{}'; - }, - set: function(discussionList) { - this._discussionList = JSON.parse(discussionList); - storage[this.fileIndex + ".discussionList"] = discussionList; - } - }); - } + function FileDescriptor(fileIndex, title, syncLocations, publishLocations) { + this.fileIndex = fileIndex; + this._title = title || storage[fileIndex + ".title"]; + this._editorScrollTop = parseInt(storage[fileIndex + ".editorScrollTop"]) || 0; + this._editorStart = parseInt(storage[fileIndex + ".editorEnd"]) || 0; + this._editorEnd = parseInt(storage[fileIndex + ".editorEnd"]) || 0; + this._previewScrollTop = parseInt(storage[fileIndex + ".previewScrollTop"]) || 0; + this._selectTime = parseInt(storage[fileIndex + ".selectTime"]) || 0; + this._discussionList = JSON.parse(storage[fileIndex + ".discussionList"] || '{}'); + this.syncLocations = syncLocations || {}; + this.publishLocations = publishLocations || {}; + Object.defineProperty(this, 'title', { + get: function() { + return this._title; + }, + set: function(title) { + this._title = title; + storage[this.fileIndex + ".title"] = title; + } + }); + Object.defineProperty(this, 'content', { + get: function() { + return storage[this.fileIndex + ".content"]; + }, + set: function(content) { + storage[this.fileIndex + ".content"] = content; + } + }); + Object.defineProperty(this, 'editorScrollTop', { + get: function() { + return this._editorScrollTop; + }, + set: function(editorScrollTop) { + this._editorScrollTop = editorScrollTop; + storage[this.fileIndex + ".editorScrollTop"] = editorScrollTop; + } + }); + Object.defineProperty(this, 'editorStart', { + get: function() { + return this._editorStart; + }, + set: function(editorStart) { + this._editorStart = editorStart; + storage[this.fileIndex + ".editorStart"] = editorStart; + } + }); + Object.defineProperty(this, 'editorEnd', { + get: function() { + return this._editorEnd; + }, + set: function(editorEnd) { + this._editorEnd = editorEnd; + storage[this.fileIndex + ".editorEnd"] = editorEnd; + } + }); + Object.defineProperty(this, 'previewScrollTop', { + get: function() { + return this._previewScrollTop; + }, + set: function(previewScrollTop) { + this._previewScrollTop = previewScrollTop; + storage[this.fileIndex + ".previewScrollTop"] = previewScrollTop; + } + }); + Object.defineProperty(this, 'selectTime', { + get: function() { + return this._selectTime; + }, + set: function(selectTime) { + this._selectTime = selectTime; + storage[this.fileIndex + ".selectTime"] = selectTime; + } + }); + Object.defineProperty(this, 'discussionList', { + get: function() { + return this._discussionList; + }, + set: function(discussionList) { + this._discussionList = discussionList; + storage[this.fileIndex + ".discussionList"] = JSON.stringify(discussionList); + } + }); + Object.defineProperty(this, 'discussionListJSON', { + get: function() { + return storage[this.fileIndex + ".discussionList"] || '{}'; + }, + set: function(discussionList) { + this._discussionList = JSON.parse(discussionList); + storage[this.fileIndex + ".discussionList"] = discussionList; + } + }); + } - FileDescriptor.prototype.addSyncLocation = function(syncAttributes) { - utils.storeAttributes(syncAttributes); - utils.appendIndexToArray(this.fileIndex + ".sync", syncAttributes.syncIndex); - this.syncLocations[syncAttributes.syncIndex] = syncAttributes; - }; + FileDescriptor.prototype.addSyncLocation = function(syncAttributes) { + utils.storeAttributes(syncAttributes); + utils.appendIndexToArray(this.fileIndex + ".sync", syncAttributes.syncIndex); + this.syncLocations[syncAttributes.syncIndex] = syncAttributes; + }; - FileDescriptor.prototype.removeSyncLocation = function(syncAttributes) { - utils.removeIndexFromArray(this.fileIndex + ".sync", syncAttributes.syncIndex); - delete this.syncLocations[syncAttributes.syncIndex]; - }; + FileDescriptor.prototype.removeSyncLocation = function(syncAttributes) { + utils.removeIndexFromArray(this.fileIndex + ".sync", syncAttributes.syncIndex); + delete this.syncLocations[syncAttributes.syncIndex]; + }; - FileDescriptor.prototype.addPublishLocation = function(publishAttributes) { - utils.storeAttributes(publishAttributes); - utils.appendIndexToArray(this.fileIndex + ".publish", publishAttributes.publishIndex); - this.publishLocations[publishAttributes.publishIndex] = publishAttributes; - }; + FileDescriptor.prototype.addPublishLocation = function(publishAttributes) { + utils.storeAttributes(publishAttributes); + utils.appendIndexToArray(this.fileIndex + ".publish", publishAttributes.publishIndex); + this.publishLocations[publishAttributes.publishIndex] = publishAttributes; + }; - FileDescriptor.prototype.removePublishLocation = function(publishAttributes) { - utils.removeIndexFromArray(this.fileIndex + ".publish", publishAttributes.publishIndex); - delete this.publishLocations[publishAttributes.publishIndex]; - }; + FileDescriptor.prototype.removePublishLocation = function(publishAttributes) { + utils.removeIndexFromArray(this.fileIndex + ".publish", publishAttributes.publishIndex); + delete this.publishLocations[publishAttributes.publishIndex]; + }; - FileDescriptor.prototype.composeTitle = function() { - var result = []; - _.chain(this.syncLocations).sortBy(function(attributes) { - return attributes.provider.providerId; - }).each(function(attributes) { - result.push(''); - }); - if(_.size(this.syncLocations) !== 0) { - result.push(''); - } - _.chain(this.publishLocations).sortBy(function(attributes) { - return attributes.provider.providerId; - }).each(function(attributes) { - result.push(''); - }); - if(_.size(this.publishLocations) !== 0) { - result.push(''); - } - result.push(_.escape(this.title)); - return result.join(''); - }; + function addIcon(result, attributes) { + result.push(''); + } - return FileDescriptor; + function addSyncIconWithLink(result, attributes) { + if(attributes.provider.getSyncLocationLink) { + var syncLocationLink = attributes.provider.getSyncLocationLink(attributes); + result.push([ + '' + ].join('')); + } + else { + addIcon(result, attributes); + } + } + + function addPublishIconWithLink(result, attributes) { + if(attributes.provider.getPublishLocationLink) { + var publishLocationLink = attributes.provider.getPublishLocationLink(attributes); + result.push([ + '' + ].join('')); + } + else { + addIcon(result, attributes); + } + } + + FileDescriptor.prototype.composeTitle = function(createLinks) { + var result = []; + var addSyncIcon = createLinks ? addSyncIconWithLink : addIcon; + var addPublishIcon = createLinks ? addPublishIconWithLink : addIcon; + _.chain(this.syncLocations).sortBy(function(attributes) { + return attributes.provider.providerId; + }).each(function(attributes) { + addSyncIcon(result, attributes); + }); + if(_.size(this.syncLocations) !== 0) { + result.push(''); + } + _.chain(this.publishLocations).sortBy(function(attributes) { + return attributes.provider.providerId; + }).each(function(attributes) { + addPublishIcon(result, attributes); + }); + if(_.size(this.publishLocations) !== 0) { + result.push(''); + } + result.push(_.escape(this.title)); + return result.join(''); + }; + + return FileDescriptor; }); diff --git a/public/res/core.js b/public/res/core.js index 2cf5e6a4..b78b3a33 100644 --- a/public/res/core.js +++ b/public/res/core.js @@ -145,6 +145,8 @@ define([ utils.setInputValue("#textarea-settings-pdf-template", settings.pdfTemplate); // PDF options utils.setInputValue("#textarea-settings-pdf-options", settings.pdfOptions); + // CouchDB URL + utils.setInputValue("#input-settings-couchdb-url", settings.couchdbUrl); // Load extension settings eventMgr.onLoadSettings(); @@ -190,6 +192,8 @@ define([ newSettings.pdfTemplate = utils.getInputTextValue("#textarea-settings-pdf-template", event); // PDF options newSettings.pdfOptions = utils.getInputJSONValue("#textarea-settings-pdf-options", event); + // CouchDB URL + newSettings.couchdbUrl = utils.getInputValue("#input-settings-couchdb-url", event); // Save extension settings newSettings.extensionSettings = {}; diff --git a/public/res/eventMgr.js b/public/res/eventMgr.js index 1356b498..94f0c299 100644 --- a/public/res/eventMgr.js +++ b/public/res/eventMgr.js @@ -172,6 +172,7 @@ define([ addEventHook("onFileMgrCreated"); addEventHook("onSynchronizerCreated"); addEventHook("onPublisherCreated"); + addEventHook("onSharingCreated"); addEventHook("onEventMgrCreated"); // Operations on files diff --git a/public/res/extensions/dialogManagePublication.js b/public/res/extensions/dialogManagePublication.js index 402f9b70..ba5bc542 100644 --- a/public/res/extensions/dialogManagePublication.js +++ b/public/res/extensions/dialogManagePublication.js @@ -1,61 +1,62 @@ define([ - "jquery", - "underscore", - "classes/Extension", - "text!html/dialogManagePublicationLocation.html", + "jquery", + "underscore", + "classes/Extension", + "text!html/dialogManagePublicationLocation.html", ], function($, _, Extension, dialogManagePublicationLocationHTML) { - var dialogManagePublication = new Extension("dialogManagePublication", 'Dialog "Manage publication"', false, true); + var dialogManagePublication = new Extension("dialogManagePublication", 'Dialog "Manage publication"', false, true); - var eventMgr; - dialogManagePublication.onEventMgrCreated = function(eventMgrParameter) { - eventMgr = eventMgrParameter; - }; + var eventMgr; + dialogManagePublication.onEventMgrCreated = function(eventMgrParameter) { + eventMgr = eventMgrParameter; + }; - var fileDesc; - var publishListElt; - var $showAlreadyPublishedElt; - var refreshDialog = function(fileDescParameter) { - if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { - return; - } + var fileDesc; + var publishListElt; + var $showAlreadyPublishedElt; + var refreshDialog = function(fileDescParameter) { + if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { + return; + } - $showAlreadyPublishedElt.toggleClass("hide", _.size(fileDesc.publishLocations) === 0); + $showAlreadyPublishedElt.toggleClass("hide", _.size(fileDesc.publishLocations) === 0); - var publishListHtml = _.reduce(fileDesc.publishLocations, function(result, publishAttributes) { - var formattedAttributes = _.omit(publishAttributes, "provider", "publishIndex", "sharingLink"); - formattedAttributes.password && (formattedAttributes.password = "********"); - formattedAttributes = JSON.stringify(formattedAttributes).replace(/{|}|"/g, "").replace(/,/g, ", "); - return result + _.template(dialogManagePublicationLocationHTML, { - publishAttributes: publishAttributes, - publishDesc: formattedAttributes - }); - }, ''); - - publishListElt.innerHTML = publishListHtml; - }; + var publishListHtml = _.reduce(fileDesc.publishLocations, function(result, publishAttributes) { + var formattedAttributes = _.omit(publishAttributes, "provider", "publishIndex"); + formattedAttributes.password && (formattedAttributes.password = "********"); + formattedAttributes = JSON.stringify(formattedAttributes).replace(/{|}|"/g, "").replace(/,/g, ", "); + return result + _.template(dialogManagePublicationLocationHTML, { + publishAttributes: publishAttributes, + publishDesc: formattedAttributes, + publishLocationLink: publishAttributes.provider.getPublishLocationLink && publishAttributes.provider.getPublishLocationLink(publishAttributes) + }); + }, ''); - dialogManagePublication.onFileSelected = function(fileDescParameter) { - fileDesc = fileDescParameter; - refreshDialog(fileDescParameter); - }; + publishListElt.innerHTML = publishListHtml; + }; - dialogManagePublication.onNewPublishSuccess = refreshDialog; - dialogManagePublication.onPublishRemoved = refreshDialog; + dialogManagePublication.onFileSelected = function(fileDescParameter) { + fileDesc = fileDescParameter; + refreshDialog(fileDescParameter); + }; - dialogManagePublication.onReady = function() { - var modalElt = document.querySelector(".modal-manage-publish"); - publishListElt = modalElt.querySelector(".publish-list"); + dialogManagePublication.onNewPublishSuccess = refreshDialog; + dialogManagePublication.onPublishRemoved = refreshDialog; - $showAlreadyPublishedElt = $(document.querySelectorAll(".show-already-published")); + dialogManagePublication.onReady = function() { + var modalElt = document.querySelector(".modal-manage-publish"); + publishListElt = modalElt.querySelector(".publish-list"); - $(publishListElt).on('click', '.remove-button', function() { - var $removeButtonElt = $(this); - var publishAttributes = fileDesc.publishLocations[$removeButtonElt.data('publishIndex')]; - fileDesc.removePublishLocation(publishAttributes); - eventMgr.onPublishRemoved(fileDesc, publishAttributes); - }); - }; + $showAlreadyPublishedElt = $(document.querySelectorAll(".show-already-published")); - return dialogManagePublication; + $(publishListElt).on('click', '.remove-button', function() { + var $removeButtonElt = $(this); + var publishAttributes = fileDesc.publishLocations[$removeButtonElt.data('publishIndex')]; + fileDesc.removePublishLocation(publishAttributes); + eventMgr.onPublishRemoved(fileDesc, publishAttributes); + }); + }; + + return dialogManagePublication; }); diff --git a/public/res/extensions/dialogManageSharing.js b/public/res/extensions/dialogManageSharing.js index ecb400cd..96dddeee 100644 --- a/public/res/extensions/dialogManageSharing.js +++ b/public/res/extensions/dialogManageSharing.js @@ -1,63 +1,70 @@ define([ - "jquery", - "underscore", - "classes/Extension", - "text!html/dialogManageSharingLocation.html" -], function($, _, Extension, dialogManageSharingLocationHTML) { + "jquery", + "underscore", + "constants", + "classes/Extension", + "text!html/dialogManageSharingLocation.html" +], function($, _, constants, Extension, dialogManageSharingLocationHTML) { - var dialogManageSharing = new Extension("dialogManageSharing", 'Button "Share"', false, true); + var dialogManageSharing = new Extension("dialogManageSharing", 'Button "Share"', false, true); - var eventMgr; - dialogManageSharing.onEventMgrCreated = function(eventMgrParam) { - eventMgr = eventMgrParam; - }; + var eventMgr; + dialogManageSharing.onEventMgrCreated = function(eventMgrParam) { + eventMgr = eventMgrParam; + }; + var sharing; + dialogManageSharing.onSharingCreated = function(sharingParam) { + sharing = sharingParam; + }; - var fileDesc; - var shareListElt; - var $msgShareListElt; - var $msgNoShareElt; - var refreshDocumentSharing = function(fileDescParameter) { - if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { - return; - } + var fileDesc; + var shareListElt; + var $msgShareListElt; + var $msgNoShareElt; + var refreshDocumentSharing = function(fileDescParameter) { + if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { + return; + } - var linkListHtml = _.reduce(fileDesc.publishLocations, function(result, attributes) { - if(attributes.sharingLink) { - result += _.template(dialogManageSharingLocationHTML, { - link: attributes.sharingLink, - title: fileDesc.title - }); - } - return result; - }, ''); - shareListElt.innerHTML = linkListHtml; - - $msgShareListElt.toggleClass('hide', linkListHtml.length === 0); - $msgNoShareElt.toggleClass('hide', linkListHtml.length !== 0); - }; + var linkListHtml = _.reduce(fileDesc.publishLocations, function(result, attributes) { + var params = sharing.getViewerParams(attributes); + if(params) { + var link = constants.MAIN_URL + 'viewer?' + $.param(params); + result += _.template(dialogManageSharingLocationHTML, { + link: link, + title: fileDesc.title + }); + } + return result; + }, ''); + shareListElt.innerHTML = linkListHtml; - dialogManageSharing.onFileSelected = function(fileDescParameter) { - fileDesc = fileDescParameter; - refreshDocumentSharing(fileDescParameter); - }; + $msgShareListElt.toggleClass('hide', linkListHtml.length === 0); + $msgNoShareElt.toggleClass('hide', linkListHtml.length !== 0); + }; - dialogManageSharing.onNewPublishSuccess = function(fileDescParameter, publishAttributes) { - refreshDocumentSharing(fileDescParameter); - if(publishAttributes.sharingLink) { - $('.modal').modal('hide'); - $('.modal-manage-sharing').modal('show'); - } - }; + dialogManageSharing.onFileSelected = function(fileDescParameter) { + fileDesc = fileDescParameter; + refreshDocumentSharing(fileDescParameter); + }; - dialogManageSharing.onPublishRemoved = refreshDocumentSharing; - - dialogManageSharing.onReady = function() { - var modalElt = document.querySelector('.modal-manage-sharing'); - shareListElt = modalElt.querySelector('.share-list'); - $msgShareListElt = $(modalElt.querySelectorAll('.msg-share-list')); - $msgNoShareElt = $(modalElt.querySelectorAll('.msg-no-share')); - }; + dialogManageSharing.onNewPublishSuccess = function(fileDescParameter, publishAttributes) { + refreshDocumentSharing(fileDescParameter); + if(sharing.getViewerParams(publishAttributes)) { + $('.modal').modal('hide'); + $('.modal-manage-sharing').modal('show'); + } + }; - return dialogManageSharing; + dialogManageSharing.onPublishRemoved = refreshDocumentSharing; + + dialogManageSharing.onReady = function() { + var modalElt = document.querySelector('.modal-manage-sharing'); + shareListElt = modalElt.querySelector('.share-list'); + $msgShareListElt = $(modalElt.querySelectorAll('.msg-share-list')); + $msgNoShareElt = $(modalElt.querySelectorAll('.msg-no-share')); + }; + + return dialogManageSharing; }); \ No newline at end of file diff --git a/public/res/extensions/dialogManageSynchronization.js b/public/res/extensions/dialogManageSynchronization.js index afca092c..0dc2cc90 100644 --- a/public/res/extensions/dialogManageSynchronization.js +++ b/public/res/extensions/dialogManageSynchronization.js @@ -1,64 +1,65 @@ define([ - "jquery", - "underscore", - "classes/Extension", - "text!html/dialogManageSynchronizationLocation.html", + "jquery", + "underscore", + "classes/Extension", + "text!html/dialogManageSynchronizationLocation.html", ], function($, _, Extension, dialogManageSynchronizationLocationHTML) { - var dialogManageSynchronization = new Extension("dialogManageSynchronization", 'Dialog "Manage synchronization"', false, true); + var dialogManageSynchronization = new Extension("dialogManageSynchronization", 'Dialog "Manage synchronization"', false, true); - var eventMgr; - dialogManageSynchronization.onEventMgrCreated = function(eventMgrParameter) { - eventMgr = eventMgrParameter; - }; + var eventMgr; + dialogManageSynchronization.onEventMgrCreated = function(eventMgrParameter) { + eventMgr = eventMgrParameter; + }; - var synchronizer; - dialogManageSynchronization.onSynchronizerCreated = function(synchronizerParameter) { - synchronizer = synchronizerParameter; - }; + var synchronizer; + dialogManageSynchronization.onSynchronizerCreated = function(synchronizerParameter) { + synchronizer = synchronizerParameter; + }; - var fileDesc; - var syncListElt; - var $showAlreadySynchronizedElt; - var refreshDialog = function(fileDescParameter) { - if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { - return; - } + var fileDesc; + var syncListElt; + var $showAlreadySynchronizedElt; + var refreshDialog = function(fileDescParameter) { + if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { + return; + } - $showAlreadySynchronizedElt.toggleClass("hide", _.size(fileDesc.syncLocations) === 0); + $showAlreadySynchronizedElt.toggleClass("hide", _.size(fileDesc.syncLocations) === 0); - var syncListHtml = _.reduce(fileDesc.syncLocations, function(result, syncAttributes) { - return result + _.template(dialogManageSynchronizationLocationHTML, { - syncAttributes: syncAttributes, - syncDesc: syncAttributes.id || syncAttributes.path - }); - }, ''); - - syncListElt.innerHTML = syncListHtml; - }; + var syncListHtml = _.reduce(fileDesc.syncLocations, function(result, syncAttributes) { + return result + _.template(dialogManageSynchronizationLocationHTML, { + syncAttributes: syncAttributes, + syncDesc: syncAttributes.id || syncAttributes.path, + syncLocationLink: syncAttributes.provider.getSyncLocationLink && syncAttributes.provider.getSyncLocationLink(syncAttributes) + }); + }, ''); - dialogManageSynchronization.onFileSelected = function(fileDescParameter) { - fileDesc = fileDescParameter; - refreshDialog(fileDescParameter); - }; + syncListElt.innerHTML = syncListHtml; + }; - dialogManageSynchronization.onSyncExportSuccess = refreshDialog; - dialogManageSynchronization.onSyncRemoved = refreshDialog; + dialogManageSynchronization.onFileSelected = function(fileDescParameter) { + fileDesc = fileDescParameter; + refreshDialog(fileDescParameter); + }; - dialogManageSynchronization.onReady = function() { - var modalElt = document.querySelector(".modal-manage-sync"); - syncListElt = modalElt.querySelector(".sync-list"); + dialogManageSynchronization.onSyncExportSuccess = refreshDialog; + dialogManageSynchronization.onSyncRemoved = refreshDialog; - $showAlreadySynchronizedElt = $(document.querySelectorAll(".show-already-synchronized")); + dialogManageSynchronization.onReady = function() { + var modalElt = document.querySelector(".modal-manage-sync"); + syncListElt = modalElt.querySelector(".sync-list"); - $(syncListElt).on('click', '.remove-button', function() { - var $removeButtonElt = $(this); - var syncAttributes = fileDesc.syncLocations[$removeButtonElt.data('syncIndex')]; - fileDesc.removeSyncLocation(syncAttributes); - eventMgr.onSyncRemoved(fileDesc, syncAttributes); - }); - }; + $showAlreadySynchronizedElt = $(document.querySelectorAll(".show-already-synchronized")); - return dialogManageSynchronization; + $(syncListElt).on('click', '.remove-button', function() { + var $removeButtonElt = $(this); + var syncAttributes = fileDesc.syncLocations[$removeButtonElt.data('syncIndex')]; + fileDesc.removeSyncLocation(syncAttributes); + eventMgr.onSyncRemoved(fileDesc, syncAttributes); + }); + }; + + return dialogManageSynchronization; }); diff --git a/public/res/extensions/documentTitle.js b/public/res/extensions/documentTitle.js index 1140f515..eaac368f 100644 --- a/public/res/extensions/documentTitle.js +++ b/public/res/extensions/documentTitle.js @@ -14,7 +14,7 @@ define([ } var title = fileDesc.title; - $fileTitleNavbar.html(fileDesc.composeTitle()); + $fileTitleNavbar.html(fileDesc.composeTitle(true)); $(".file-title").text(title); $(".input-file-title").val(title); }, 50); diff --git a/public/res/helpers/couchdbHelper.js b/public/res/helpers/couchdbHelper.js index 475a193d..f8fcf168 100644 --- a/public/res/helpers/couchdbHelper.js +++ b/public/res/helpers/couchdbHelper.js @@ -1,7 +1,6 @@ define([ "jquery", "underscore", - "constants", "core", "utils", "storage", @@ -9,7 +8,7 @@ define([ "settings", "eventMgr", "classes/AsyncTask" -], function($, _, constants, core, utils, storage, logger, settings, eventMgr, AsyncTask) { +], function($, _, core, utils, storage, logger, settings, eventMgr, AsyncTask) { var couchdbHelper = {}; @@ -26,7 +25,7 @@ define([ if(tags) { // Has to be an array if(!_.isArray(tags)) { - tags = _.chain(('' + tags).split(/\s+/)) + tags = _.chain(('' + tags).split(/,/)) .compact() .unique() .value(); @@ -43,7 +42,7 @@ define([ } $.ajax({ type: 'POST', - url: constants.COUCHDB_URL, + url: settings.couchdbUrl, contentType: 'application/json', dataType: 'json', data: JSON.stringify({ @@ -82,7 +81,7 @@ define([ task.onRun(function() { $.ajax({ type: 'POST', - url: constants.COUCHDB_URL + '/_changes?' + $.param({ + url: settings.couchdbUrl + '/_changes?' + $.param({ filter: '_doc_ids', since: newChangeId, include_docs: true, @@ -130,7 +129,7 @@ define([ return task.chain(recursiveDownloadContent); } $.ajax({ - url: constants.COUCHDB_URL + '/' + encodeURIComponent(document._id), + url: settings.couchdbUrl + '/' + encodeURIComponent(document._id), headers: { Accept: 'application/json' }, @@ -172,7 +171,7 @@ define([ tag ]) : undefined; $.ajax({ - url: constants.COUCHDB_URL + ddoc, + url: settings.couchdbUrl + ddoc, data: { start_key: startKey, end_key: endKey, @@ -203,7 +202,7 @@ define([ task.onRun(function() { $.ajax({ type: 'POST', - url: constants.COUCHDB_URL + '/_bulk_docs', + url: settings.couchdbUrl + '/_bulk_docs', data: JSON.stringify({ docs: docs.map(function(doc) { return { diff --git a/public/res/helpers/githubHelper.js b/public/res/helpers/githubHelper.js index d49fe4bc..8e156360 100644 --- a/public/res/helpers/githubHelper.js +++ b/public/res/helpers/githubHelper.js @@ -8,7 +8,7 @@ define([ "logger", "settings", "eventMgr", - "classes/AsyncTask", + "classes/AsyncTask" ], function($, constants, core, utils, storage, logger, settings, eventMgr, AsyncTask) { var connected; @@ -162,7 +162,7 @@ define([ } }); task.onSuccess(function() { - callback(); + callback(undefined, username); }); task.onError(function(error) { callback(error); diff --git a/public/res/helpers/googleHelper.js b/public/res/helpers/googleHelper.js index d2acc04d..79241441 100644 --- a/public/res/helpers/googleHelper.js +++ b/public/res/helpers/googleHelper.js @@ -108,12 +108,17 @@ define([ ] }; - function authenticate(task, permission, accountId) { + googleHelper.getAuthorizationMgr = function(accountId) { var authorizationMgr = authorizationMgrMap[accountId]; if(!authorizationMgr) { authorizationMgr = new AuthorizationMgr(accountId); authorizationMgrMap[accountId] = authorizationMgr; } + return authorizationMgr; + }; + + function authenticate(task, permission, accountId) { + var authorizationMgr = googleHelper.getAuthorizationMgr(accountId); task.onRun(function() { var currentToken = gapi.auth.getToken(); var newToken; @@ -185,6 +190,7 @@ define([ } else { // Success but we need to check the user id + authorizationMgr.authuser = authuser; immediate === true && authuser++; task.chain(getTokenInfo); } diff --git a/public/res/helpers/wordpressHelper.js b/public/res/helpers/wordpressHelper.js index b5cd46aa..779b304d 100644 --- a/public/res/helpers/wordpressHelper.js +++ b/public/res/helpers/wordpressHelper.js @@ -1,182 +1,183 @@ define([ - "jquery", - "constants", - "core", - "utils", - "storage", - "logger", - "eventMgr", - "classes/AsyncTask" + "jquery", + "constants", + "core", + "utils", + "storage", + "logger", + "eventMgr", + "classes/AsyncTask" ], function($, constants, core, utils, storage, logger, eventMgr, AsyncTask) { - var token; + var token; - var wordpressHelper = {}; + var wordpressHelper = {}; - // Listen to offline status changes - var isOffline = false; - eventMgr.addListener("onOfflineChanged", function(isOfflineParam) { - isOffline = isOfflineParam; - }); + // Listen to offline status changes + var isOffline = false; + eventMgr.addListener("onOfflineChanged", function(isOfflineParam) { + isOffline = isOfflineParam; + }); - // Only used to check the offline status - function connect(task) { - task.onRun(function() { - if(isOffline === true) { - task.error(new Error("Operation not available in offline mode.|stopPublish")); - return; - } - task.chain(); - }); - } + // Only used to check the offline status + function connect(task) { + task.onRun(function() { + if(isOffline === true) { + return task.error(new Error("Operation not available in offline mode.|stopPublish")); + } + task.chain(); + }); + } - // Try to authenticate with OAuth - function authenticate(task) { - var authWindow; - var intervalId; - task.onRun(function() { - token = storage.wordpressToken; - if(token !== undefined) { - task.chain(); - return; - } - var errorMsg = "Failed to retrieve a token from Wordpress."; - // We add time for user to enter his credentials - task.timeout = constants.ASYNC_TASK_LONG_TIMEOUT; - var code; - function oauthRedirect() { - utils.redirectConfirm('You are being redirected to WordPress authorization page.', function() { - task.chain(getCode); - }, function() { - task.error(new Error('Operation canceled.')); - }); - } - function getCode() { - storage.removeItem("wordpressCode"); - authWindow = utils.popupWindow('html/wordpress-oauth-client.html?client_id=' + constants.WORDPRESS_CLIENT_ID, 'stackedit-wordpress-oauth', 960, 600); - authWindow.focus(); - intervalId = setInterval(function() { - if(authWindow.closed === true) { - clearInterval(intervalId); - authWindow = undefined; - intervalId = undefined; - code = storage.wordpressCode; - if(code === undefined) { - task.error(new Error(errorMsg)); - return; - } - storage.removeItem("wordpressCode"); - task.chain(getToken); - } - }, 500); - } - function getToken() { - $.getJSON(constants.WORDPRESS_PROXY_URL + "authenticate/" + code, function(data) { - if(data.token !== undefined) { - token = data.token; - storage.wordpressToken = token; - task.chain(); - } - else { - task.error(new Error(errorMsg)); - } - }); - } - task.chain(oauthRedirect); - }); - task.onError(function() { - if(intervalId !== undefined) { - clearInterval(intervalId); - } - if(authWindow !== undefined) { - authWindow.close(); - } - }); - } + // Try to authenticate with OAuth + function authenticate(task) { + var authWindow; + var intervalId; + task.onRun(function() { + token = storage.wordpressToken; + if(token !== undefined) { + return task.chain(); + } + var errorMsg = "Failed to retrieve a token from Wordpress."; + // We add time for user to enter his credentials + task.timeout = constants.ASYNC_TASK_LONG_TIMEOUT; + var code; - wordpressHelper.upload = function(site, postId, tags, status, date, title, content, callback) { - var task = new AsyncTask(); - connect(task); - authenticate(task); - task.onRun(function() { - var url = constants.WORDPRESS_PROXY_URL + "post"; - var data = { - token: token, - site: site, - postId: postId, - tags: tags, - status: status, - date: date, - title: title, - content: content - }; - $.ajax({ - url: url, - data: data, - type: "POST", - dataType: "json", - timeout: constants.AJAX_TIMEOUT - }).done(function(response) { - if(response.body.ID) { - postId = response.body.ID; - task.chain(); - return; - } - var error = { - code: response.code, - message: response.body.error - }; - // Handle error - if(error.code === 404) { - if(error.message == "unknown_blog") { - error = 'Site "' + site + '" not found on WordPress.|removePublish'; - } - else if(error.message == "unknown_post") { - error = 'Post ' + postId + ' not found on WordPress.|removePublish'; - } - } - handleError(error, task); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - handleError(error, task); - }); - }); - task.onSuccess(function() { - callback(undefined, postId); - }); - task.onError(function(error) { - callback(error); - }); - task.enqueue(); - }; + function oauthRedirect() { + utils.redirectConfirm('You are being redirected to WordPress authorization page.', function() { + task.chain(getCode); + }, function() { + task.error(new Error('Operation canceled.')); + }); + } - function handleError(error, task) { - var errorMsg; - if(error) { - logger.error(error); - // Try to analyze the error - if(typeof error === "string") { - errorMsg = error; - } - else { - errorMsg = "Could not publish on WordPress."; - if((error.code === 400 && error.message == "invalid_token") || error.code === 401 || error.code === 403) { - storage.removeItem("wordpressToken"); - errorMsg = "Access to WordPress account is not authorized."; - task.retry(new Error(errorMsg), 1); - return; - } - else if(error.code <= 0) { - core.setOffline(); - errorMsg = "|stopPublish"; - } - } - } - task.error(new Error(errorMsg)); - } + function getCode() { + storage.removeItem("wordpressCode"); + authWindow = utils.popupWindow('html/wordpress-oauth-client.html?client_id=' + constants.WORDPRESS_CLIENT_ID, 'stackedit-wordpress-oauth', 960, 600); + authWindow.focus(); + intervalId = setInterval(function() { + if(authWindow.closed === true) { + clearInterval(intervalId); + authWindow = undefined; + intervalId = undefined; + code = storage.wordpressCode; + if(code === undefined) { + return task.error(new Error(errorMsg)); + } + storage.removeItem("wordpressCode"); + task.chain(getToken); + } + }, 500); + } - return wordpressHelper; + function getToken() { + $.getJSON(constants.WORDPRESS_PROXY_URL + "authenticate/" + code, function(data) { + if(data.token !== undefined) { + token = data.token; + storage.wordpressToken = token; + task.chain(); + } + else { + task.error(new Error(errorMsg)); + } + }); + } + + task.chain(oauthRedirect); + }); + task.onError(function() { + if(intervalId !== undefined) { + clearInterval(intervalId); + } + if(authWindow !== undefined) { + authWindow.close(); + } + }); + } + + wordpressHelper.upload = function(site, postId, tags, status, date, title, content, callback) { + var task = new AsyncTask(); + connect(task); + authenticate(task); + var siteId; + task.onRun(function() { + var url = constants.WORDPRESS_PROXY_URL + "post"; + var data = { + token: token, + site: site, + postId: postId, + tags: tags, + status: status, + date: date, + title: title, + content: content + }; + $.ajax({ + url: url, + data: data, + type: "POST", + dataType: "json", + timeout: constants.AJAX_TIMEOUT + }).done(function(response) { + if(response.body.ID) { + postId = response.body.ID; + siteId = response.body.site_ID; + return task.chain(); + } + var error = { + code: response.code, + message: response.body.error + }; + // Handle error + if(error.code === 404) { + if(error.message == "unknown_blog") { + error = 'Site "' + site + '" not found on WordPress.|removePublish'; + } + else if(error.message == "unknown_post") { + error = 'Post ' + postId + ' not found on WordPress.|removePublish'; + } + } + handleError(error, task); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + handleError(error, task); + }); + }); + task.onSuccess(function() { + callback(undefined, siteId, postId); + }); + task.onError(function(error) { + callback(error); + }); + task.enqueue(); + }; + + function handleError(error, task) { + var errorMsg; + if(error) { + logger.error(error); + // Try to analyze the error + if(typeof error === "string") { + errorMsg = error; + } + else { + errorMsg = "Could not publish on WordPress."; + if((error.code === 400 && error.message == "invalid_token") || error.code === 401 || error.code === 403) { + storage.removeItem("wordpressToken"); + errorMsg = "Access to WordPress account is not authorized."; + return task.retry(new Error(errorMsg), 1); + } + else if(error.code <= 0) { + core.setOffline(); + errorMsg = "|stopPublish"; + } + } + } + task.error(new Error(errorMsg)); + } + + return wordpressHelper; }); diff --git a/public/res/html/bodyEditor.html b/public/res/html/bodyEditor.html index 72c3fc6d..d5c619bd 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, collaborate in the Cloud
+ Create + your own extension...
diff --git a/public/res/html/dialogAbout.html b/public/res/html/dialogAbout.html index e17eb623..8c65af8e 100644 --- a/public/res/html/dialogAbout.html +++ b/public/res/html/dialogAbout.html @@ -36,6 +36,7 @@ Pete Eigel (contributor)
wiibaa (contributor)
Daniel Hug (contributor)
+ Kevin Brey (themer)
@@ -44,6 +45,9 @@
+

+ A special thanks to Couchappy for hosting StackEdit's public CouchDB instance. +

StackEdit <%= version %> – Privacy Policy
Copyright 2013-2014 Benoit diff --git a/public/res/html/dialogExportGdrive.html b/public/res/html/dialogExportGdrive.html index 4cd53eb4..02c1ba5d 100644 --- a/public/res/html/dialogExportGdrive.html +++ b/public/res/html/dialogExportGdrive.html @@ -19,15 +19,13 @@ for="input-sync-export-<%= providerId %>-parentid">Folder ID (optional)

- + + If no folder ID is supplied, the file will be created in your root folder.
diff --git a/public/res/html/dialogManagePublicationLocation.html b/public/res/html/dialogManagePublicationLocation.html index a246d37a..57e45e4a 100644 --- a/public/res/html/dialogManagePublicationLocation.html +++ b/public/res/html/dialogManagePublicationLocation.html @@ -1,11 +1,16 @@ -
+
+
-
- -
+ value="<%= publishDesc %>" disabled/> +
+
diff --git a/public/res/html/dialogManageSynchronizationLocation.html b/public/res/html/dialogManageSynchronizationLocation.html index c1d93564..9dec79fd 100644 --- a/public/res/html/dialogManageSynchronizationLocation.html +++ b/public/res/html/dialogManageSynchronizationLocation.html @@ -1,12 +1,16 @@ -
+
+
- - -
- -
+ title="<%= syncAttributes.provider.providerName %>"> + + +
+
diff --git a/public/res/html/findReplaceSettingsBlock.html b/public/res/html/findReplaceSettingsBlock.html index 616a1a89..f6b2afba 100644 --- a/public/res/html/findReplaceSettingsBlock.html +++ b/public/res/html/findReplaceSettingsBlock.html @@ -1,4 +1,4 @@ -

Helps to find and replace text in the current document.

+

Helps find and replace text in the current document.

', - '
', + '
', '<%= document.title %>
', '' ].join(''); @@ -334,7 +355,7 @@ define([ .value(); storage[PROVIDER_COUCHDB + '.tagList'] = JSON.stringify(tagList); updateTagList(); - $selectTagElt.val(tag); + $selectTagElt.val(tag).change(); }, "Tag"); }) .on('click', '.action-remove-tag', function() { @@ -344,9 +365,9 @@ define([ tagList = _.without(tagList, tag); storage[PROVIDER_COUCHDB + '.tagList'] = JSON.stringify(tagList); updateTagList(); - $selectTagElt.val(''); + $selectTagElt.val('').change(); } - }, "Tag"); + }); }) .on('click', '.action-delete-items', function() { if($selectedElts.length) { diff --git a/public/res/providers/dropboxProvider.js b/public/res/providers/dropboxProvider.js index dcdab590..e655550a 100644 --- a/public/res/providers/dropboxProvider.js +++ b/public/res/providers/dropboxProvider.js @@ -14,7 +14,18 @@ define([ var dropboxProvider = new Provider(PROVIDER_DROPBOX, "Dropbox"); dropboxProvider.defaultPublishFormat = "template"; - function checkPath(path) { + dropboxProvider.getSyncLocationLink = dropboxProvider.getPublishLocationLink = function(attributes) { + var pathComponents = attributes.path.split('/').map(encodeURIComponent); + var filename = pathComponents.pop(); + return [ + 'https://www.dropbox.com/home', + pathComponents.join('/'), + '?select=', + filename + ].join(''); + }; + + function checkPath(path) { if(path === undefined) { return undefined; } diff --git a/public/res/providers/gdriveProviderBuilder.js b/public/res/providers/gdriveProviderBuilder.js index 94a29828..5c63eff3 100644 --- a/public/res/providers/gdriveProviderBuilder.js +++ b/public/res/providers/gdriveProviderBuilder.js @@ -24,6 +24,16 @@ define([ providerId + "-parentid" ]; + gdriveProvider.getSyncLocationLink = gdriveProvider.getPublishLocationLink = function(attributes) { + var authuser = googleHelper.getAuthorizationMgr(accountId).authuser; + return [ + 'https://docs.google.com/file/d/', + attributes.id, + '/edit', + authuser ? '?authuser=' + authuser : '' + ].join(''); + }; + function createSyncIndex(id) { return "sync." + providerId + "." + id; } diff --git a/public/res/providers/gistProvider.js b/public/res/providers/gistProvider.js index 5262d2e8..81191846 100644 --- a/public/res/providers/gistProvider.js +++ b/public/res/providers/gistProvider.js @@ -13,6 +13,13 @@ define([ "filename" ]; + gistProvider.getPublishLocationLink = function(attributes) { + return [ + 'https://gist.github.com/', + attributes.gistId + ].join(''); + }; + gistProvider.publish = function(publishAttributes, frontMatter, title, content, callback) { githubHelper.uploadGist(publishAttributes.gistId, publishAttributes.filename, publishAttributes.isPublic, title, content, function(error, gistId) { if(error) { diff --git a/public/res/providers/githubProvider.js b/public/res/providers/githubProvider.js index a262bfe4..a723ce8f 100644 --- a/public/res/providers/githubProvider.js +++ b/public/res/providers/githubProvider.js @@ -1,36 +1,50 @@ define([ - "utils", - "classes/Provider", - "settings", - "helpers/githubHelper" + "utils", + "classes/Provider", + "settings", + "helpers/githubHelper" ], function(utils, Provider, settings, githubHelper) { - var githubProvider = new Provider("github", "GitHub"); - githubProvider.publishPreferencesInputIds = [ - "github-repo", - "github-branch" - ]; + var githubProvider = new Provider("github", "GitHub"); + githubProvider.publishPreferencesInputIds = [ + "github-repo", + "github-branch" + ]; - githubProvider.publish = function(publishAttributes, frontMatter, title, content, callback) { - var commitMsg = settings.commitMsg; - githubHelper.upload(publishAttributes.repository, publishAttributes.username, publishAttributes.branch, publishAttributes.path, content, commitMsg, callback); - }; + githubProvider.getPublishLocationLink = function(attributes) { + var result = [ + 'https://github.com', + attributes.username, + attributes.repository, + 'blob', + attributes.branch + ]; + return result.concat(attributes.path.split('/').map(encodeURIComponent)).join('/'); + }; - githubProvider.newPublishAttributes = function(event) { - var publishAttributes = {}; - publishAttributes.repository = utils.getInputTextValue("#input-publish-github-repo", event); - publishAttributes.branch = utils.getInputTextValue("#input-publish-github-branch", event); - publishAttributes.path = utils.getInputTextValue("#input-publish-file-path", event); - if(event.isPropagationStopped()) { - return undefined; - } + githubProvider.publish = function(publishAttributes, frontMatter, title, content, callback) { + var commitMsg = settings.commitMsg; + githubHelper.upload(publishAttributes.repository, publishAttributes.username, publishAttributes.branch, publishAttributes.path, content, commitMsg, function(err, username) { + publishAttributes.username = username; + callback(err); + }); + }; + + githubProvider.newPublishAttributes = function(event) { + var publishAttributes = {}; + publishAttributes.repository = utils.getInputTextValue("#input-publish-github-repo", event); + publishAttributes.branch = utils.getInputTextValue("#input-publish-github-branch", event); + publishAttributes.path = utils.getInputTextValue("#input-publish-file-path", event); + if(event.isPropagationStopped()) { + return undefined; + } var parsedRepository = publishAttributes.repository.match(/[\/:]?([^\/:]+)\/([^\/]+?)(?:\.git)?$/); if(parsedRepository) { publishAttributes.repository = parsedRepository[2]; publishAttributes.username = parsedRepository[1]; } - return publishAttributes; - }; + return publishAttributes; + }; - return githubProvider; + return githubProvider; }); \ No newline at end of file diff --git a/public/res/providers/tumblrProvider.js b/public/res/providers/tumblrProvider.js index 63d5fa9e..b87c872c 100644 --- a/public/res/providers/tumblrProvider.js +++ b/public/res/providers/tumblrProvider.js @@ -11,7 +11,16 @@ define([ "tumblr-hostname" ]; - tumblrProvider.publish = function(publishAttributes, frontMatter, title, content, callback) { + tumblrProvider.getPublishLocationLink = function(attributes) { + return [ + 'http://', + attributes.blogHostname, + '/post/', + attributes.postId + ].join(''); + }; + + tumblrProvider.publish = function(publishAttributes, frontMatter, title, content, callback) { var labelList = publishAttributes.tags || []; if(frontMatter) { frontMatter.tags !== undefined && (labelList = frontMatter.tags); diff --git a/public/res/providers/wordpressProvider.js b/public/res/providers/wordpressProvider.js index 2f3863ec..c0ca3115 100644 --- a/public/res/providers/wordpressProvider.js +++ b/public/res/providers/wordpressProvider.js @@ -11,7 +11,15 @@ define([ "wordpress-site" ]; - wordpressProvider.publish = function(publishAttributes, frontMatter, title, content, callback) { + wordpressProvider.getPublishLocationLink = function(attributes) { + return attributes.siteId && [ + 'https://wordpress.com/post', + attributes.siteId, + attributes.postId + ].join('/'); + }; + + wordpressProvider.publish = function(publishAttributes, frontMatter, title, content, callback) { var labelList = publishAttributes.tags || []; if(frontMatter) { frontMatter.tags !== undefined && (labelList = frontMatter.tags); @@ -19,11 +27,11 @@ define([ var status = (frontMatter && frontMatter.published === false) ? 'draft' : 'publish'; var date = frontMatter && frontMatter.date; _.isString(labelList) && (labelList = _.compact(labelList.split(/[\s,]/))); - wordpressHelper.upload(publishAttributes.site, publishAttributes.postId, labelList.join(','), status, date, title, content, function(error, postId) { + wordpressHelper.upload(publishAttributes.site, publishAttributes.postId, labelList.join(','), status, date, title, content, function(error, siteId, postId) { if(error) { - callback(error); - return; + return callback(error); } + publishAttributes.siteId = siteId; publishAttributes.postId = postId; callback(); }); diff --git a/public/res/settings.js b/public/res/settings.js index 648daed3..b304749f 100644 --- a/public/res/settings.js +++ b/public/res/settings.js @@ -56,6 +56,7 @@ define([ ' "pageSize": "A4"', '}' ].join('\n'), + couchdbUrl: constants.COUCHDB_URL, extensionSettings: {} }; diff --git a/public/res/sharing.js b/public/res/sharing.js index f372f672..dc28454f 100644 --- a/public/res/sharing.js +++ b/public/res/sharing.js @@ -28,10 +28,7 @@ define([ }); sharing.getEditorParams = function(attributes) { - var provider = providerMap[attributes.provider.providerId]; - if(provider === undefined) { - return; - } + var provider = attributes.provider; var params = { provider: provider.providerId }; @@ -45,10 +42,9 @@ define([ }; sharing.getViewerParams = function(attributes) { - var provider = providerMap[attributes.provider.providerId]; - if(provider === undefined || - // Or document is not published in markdown format - attributes.format != "markdown") { + var provider = attributes.provider; + // If document is not published in markdown format + if(attributes.format != "markdown") { return; } var params = { @@ -98,5 +94,6 @@ define([ } }); + eventMgr.onSharingCreated(sharing); return sharing; }); diff --git a/public/res/styles/base.less b/public/res/styles/base.less index 3fcaa175..2bf80021 100644 --- a/public/res/styles/base.less +++ b/public/res/styles/base.less @@ -199,7 +199,7 @@ h5 { font-size: @title-base-size; } h6 { font-size: @title-base-size * 0.85; } h1, h2, h3, h4, h5, h6 { - margin: 1.3em 0; + margin: 1.8em 0; text-align: start; } @@ -416,7 +416,7 @@ kbd { width: 18px; height: 16px; margin-top: -2px; - margin-left: 1px; + margin-left: 2px; } .icon-provider-stackedit { diff --git a/public/res/styles/main.less b/public/res/styles/main.less index a90c3081..73437a78 100644 --- a/public/res/styles/main.less +++ b/public/res/styles/main.less @@ -93,10 +93,10 @@ @list-group-bg: @secondary-bg-light; @list-group-border: @transparent; @list-group-active-color: darken(@primary-desaturated, 25%); -@list-group-active-bg: @primary-bg; +@list-group-active-bg: @secondary-bg-dark; @list-group-active-border: fade(@secondary, 5%); @list-group-hover-bg: @btn-default-hover-bg; -@list-group-hover-border-color: fade(@secondary, 10%); +@list-group-hover-border-color: @btn-default-hover-border; @input-color: @secondary-color-darkest; @input-color-placeholder: @disabled-color; @gray-lighter: @body-bg; @@ -300,13 +300,14 @@ a { @btn-default-bg: @transparent; @btn-default-border: @transparent; @btn-default-hover-bg: fade(@secondary-desaturated, 4%); +@btn-default-hover-border: fade(@secondary, 10%); .btn-default, .alertify-button-cancel { &:hover, &:focus, &:active, .open &.dropdown-toggle { color: darken(@secondary, 30%); - border-color: fade(@secondary, 10%); + border-color: @btn-default-hover-border; background-color: @btn-default-hover-bg !important; // important to override .nav > li > a:hover } } @@ -315,13 +316,14 @@ 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-hover-border: fade(@secondary, 10%); .btn-primary, .alertify-button-ok { &:hover, &:focus, &:active, .open &.dropdown-toggle { color: darken(@secondary, 30%); - border-color: fade(@secondary, 10%); + border-color: @btn-primary-hover-border; background-color: @btn-primary-hover-bg !important; // important to override .nav > li > a:hover } } @@ -330,13 +332,14 @@ a { @btn-success-color: darken(@primary-desaturated, 20%); @btn-success-bg: @transparent; @btn-success-border: @transparent; +@btn-success-hover-color: darken(@primary, 30%); @btn-success-hover-bg: fade(@primary-desaturated, 5%); .btn-success { &:hover, &:focus, &:active, .open &.dropdown-toggle { - color: darken(@primary, 30%) !important; + color: @btn-success-hover-color !important; border-color: @primary-border-color; background-color: @btn-success-hover-bg !important; // important to override .nav > li > a:hover } @@ -522,6 +525,27 @@ a { font-weight: 200; overflow: hidden; white-space: nowrap; + a { + i { + .transition(~"all ease-in-out .15s"); + } + .icon-link-ext-alt { + color: transparent; + position: relative; + font-size: 12px; + top: -12px; + right: 6px; + width: 0; + } + &:hover { + [class^="icon-provider-"], [class*=" icon-provider-"] { + .opacity(0.5); + } + .icon-link-ext-alt { + color: @btn-success-hover-color; + } + } + } } .input-file-title-container { @@ -547,7 +571,7 @@ a { height: 6px; border-radius: 1px; margin: 0 2px; - opacity: 0.25; + .opacity(0.25); background-color: fade(@btn-success-color, 75%); } } @@ -807,7 +831,12 @@ a { padding: 9px 20px 9px 15px; } .name i { - margin-right: 8px; + &.icon-file, &.icon-folder { + margin-right: 8px; + font-size: 20px; + line-height: 15px; + margin-top: -2px; + } } .date { font-weight: normal; @@ -959,7 +988,7 @@ a { & > li > a { &:hover, &:focus { color: darken(@secondary, 30%); - border-color: fade(@secondary, 10%); + border-color: @btn-default-hover-border; background-color: @btn-default-hover-bg; border-bottom-color: @transparent; } @@ -987,7 +1016,7 @@ a { .modal-manage-publish .publish-list, .modal-manage-sharing .share-list { margin-bottom: 20px; - .input-group { + .entry { margin-bottom: 10px; } } @@ -1381,7 +1410,7 @@ div.dropdown-menu, { a:focus { color: @link-hover-color; } - h1, h2, h3, h4, h5, h6 { + h1, h2, h3 { margin: 1em 0; } } @@ -1567,7 +1596,7 @@ div.jGrowl { div.jGrowl-notification, div.jGrowl-closer { background-color: @jgrowl-bg-color; width: @jgrowl-width; - margin: 10px 0; + margin: 6px 0; padding: 10px 12px; -ms-filter: none; filter: none; diff --git a/public/res/styles/solarized.less b/public/res/styles/solarized.less index 5b1bc5fd..1f3ce925 100644 --- a/public/res/styles/solarized.less +++ b/public/res/styles/solarized.less @@ -45,7 +45,6 @@ @btn-success-color: @A1; @btn-success-bg: @transparent; @btn-success-border: @transparent; -@btn-success-hover-color: @transparent; @btn-success-hover-bg: @blue; /* Buttons over preview */ diff --git a/public/res/themes/default.less b/public/res/themes/default.less index b1b85ff1..26ed6e9d 100644 --- a/public/res/themes/default.less +++ b/public/res/themes/default.less @@ -16,14 +16,9 @@ // Navbar buttons @btn-success-color: #ddd; +@btn-success-hover-color: #fff; @btn-success-hover-bg: darken(@navbar-default-bg, 10%); .btn-success { - &:hover, - &:focus, - &:active, - .open &.dropdown-toggle { - color: #fff !important; - } .buttons-dropdown .dropdown-menu & { color: darken(@primary-desaturated, 20%) !important; &:hover, @@ -53,8 +48,9 @@ @btn-default-color: @text-color; @btn-default-bg: #fff; -@btn-default-border: @input-border; +@btn-default-border: fade(@input-border, 75%); @btn-default-hover-bg: fade(#000, 2%); +@btn-default-hover-border: fade(@secondary, 5%); @secondary-bg: #fcfcfc; @secondary-bg-light: #f6f6f6; diff --git a/public/res/themes/school.less b/public/res/themes/school.less index 08b52f08..fe0375f4 100644 --- a/public/res/themes/school.less +++ b/public/res/themes/school.less @@ -20,6 +20,7 @@ @btn-success-color: #ddd; @btn-success-bg: @transparent; @btn-success-border: @transparent; +@btn-success-hover-color: #fff; @btn-success-hover-bg: lighten(@navbar-default-bg, 10%); @panel-button-bg-color: #eee; @@ -30,7 +31,6 @@ &:focus, &:active, .open &.dropdown-toggle { - color: #fff; border-color: lighten(@navbar-default-bg, 10%); } }