From d2845b95b613df77af3ff5782fe04abd7f4390d5 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 3 Jan 2018 11:42:23 +0100 Subject: [PATCH 01/56] Fix multiple file upload prompt --- www/common/sframe-common-file.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/www/common/sframe-common-file.js b/www/common/sframe-common-file.js index 4d0370122..bc0dd3374 100644 --- a/www/common/sframe-common-file.js +++ b/www/common/sframe-common-file.js @@ -240,7 +240,7 @@ define([ inProgress: false }; var handleFile = File.handleFile = function (file, e) { - //if (handleFileState.inProgress) { return void handleFileState.queue.push(file); } + if (handleFileState.inProgress) { return void handleFileState.queue.push([file, e]); } handleFileState.inProgress = true; var thumb; @@ -258,7 +258,10 @@ define([ dropEvent: e }); handleFileState.inProgress = false; - if (handleFileState.queue.length) { handleFile(handleFileState.queue.shift()); } + if (handleFileState.queue.length) { + var next = handleFileState.queue.shift(); + handleFile(next[0], next[1]); + } }; var getName = function () { if (!showNamePrompt) { return void finish(); } From 33e3c6a0c0c222249175ea204d6612d83bc8f96a Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 3 Jan 2018 11:53:14 +0100 Subject: [PATCH 02/56] update dependency versions --- bower.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index fad124ba9..c95e466cd 100644 --- a/bower.json +++ b/bower.json @@ -30,9 +30,9 @@ "secure-fabric.js": "secure-v1.7.9", "hyperjson": "~1.4.0", "chainpad-crypto": "^0.1.3", - "chainpad-listmap": "^0.4.0", + "chainpad-listmap": "^0.4.1", "chainpad": "^5.0.0", - "chainpad-netflux": "^0.6.0", + "chainpad-netflux": "^0.6.1", "file-saver": "1.3.1", "alertifyjs": "1.0.11", "scrypt-async": "1.2.0", From 63899c94f33ddfcca75e59bb0821e70b0809b24d Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 3 Jan 2018 11:56:58 +0100 Subject: [PATCH 03/56] bump bower again --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index c95e466cd..f9501dfeb 100644 --- a/bower.json +++ b/bower.json @@ -30,7 +30,7 @@ "secure-fabric.js": "secure-v1.7.9", "hyperjson": "~1.4.0", "chainpad-crypto": "^0.1.3", - "chainpad-listmap": "^0.4.1", + "chainpad-listmap": "^0.4.2", "chainpad": "^5.0.0", "chainpad-netflux": "^0.6.1", "file-saver": "1.3.1", From 0f02d76b56cfb72cb9ccf60b4443ae7c8ffe7a6d Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 3 Jan 2018 15:08:26 +0100 Subject: [PATCH 04/56] Fix missing callback when uploading files to the drive --- www/common/cryptpad-common.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index ac81b7bc3..775c725ad 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -276,6 +276,7 @@ define([ }; common.setPadAttribute = function (attr, value, cb, href) { + cb = cb || function () {}; href = Hash.getRelativeHref(href || window.location.href); postMessage("SET_PAD_ATTRIBUTE", { href: href, @@ -297,6 +298,7 @@ define([ }); }; common.setAttribute = function (attr, value, cb) { + cb = cb || function () {}; postMessage("SET_ATTRIBUTE", { attr: attr, value: value From 59c4df3d9bdb6c0103efd65a399bf60fe9fb6034 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 3 Jan 2018 17:14:35 +0100 Subject: [PATCH 05/56] Fix 'NaN' txid for RPC when using IE --- www/common/sframe-boot2.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/www/common/sframe-boot2.js b/www/common/sframe-boot2.js index 818e3b0a0..fe614ba33 100644 --- a/www/common/sframe-boot2.js +++ b/www/common/sframe-boot2.js @@ -13,6 +13,11 @@ define([ }; } + // RPC breaks if you don't support Number.MAX_SAFE_INTEGER + if (Number && !Number.MAX_SAFE_INTEGER) { + Number.MAX_SAFE_INTEGER = 9007199254740991; + } + var mkFakeStore = function () { var fakeStorage = { getItem: function (k) { return fakeStorage[k]; }, From 1ec41f4e097908ff6ecfc59de6577693cfee2161 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 4 Jan 2018 17:22:01 +0100 Subject: [PATCH 06/56] Ability to pick a background image in slides --- customize.dist/translations/messages.fr.js | 5 ++ customize.dist/translations/messages.js | 5 ++ www/common/sframe-app-framework.js | 4 +- www/filepicker/inner.js | 8 ++- www/slide/app-slide.less | 65 ++++++++++++++++++++ www/slide/inner.js | 70 +++++++++++++++++++++- www/slide/slide.js | 6 +- 7 files changed, 154 insertions(+), 9 deletions(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 58babab3f..a79cb382f 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -153,6 +153,11 @@ define(function () { out.printTitle = "Afficher le titre du pad"; out.printCSS = "Personnaliser l'apparence (CSS):"; out.printTransition = "Activer les animations de transition"; + out.printBackground = "Utiliser une image d'arrière-plan"; + out.printBackgroundButton = "Choisir une image"; + out.printBackgroundValue = "Arrière-plan actuel: {0}"; + out.printBackgroundNoValue = "Aucun arrière-plan affiché"; + out.printBackgroundRemove = "Supprimer cet arrière-plan"; out.filePickerButton = "Intégrer un fichier stocké dans CryptDrive"; out.filePicker_close = "Fermer"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index ecc4060ec..1d7fd8b15 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -156,6 +156,11 @@ define(function () { out.printTitle = "Display the pad title"; out.printCSS = "Custom style rules (CSS):"; out.printTransition = "Enable transition animations"; + out.printBackground = "Use a background image"; + out.printBackgroundButton = "Pick an image"; + out.printBackgroundValue = "Current background: {0}"; + out.printBackgroundNoValue = "No background image displayed"; + out.printBackgroundRemove = "Remove this background image"; out.filePickerButton = "Embed a file stored in CryptDrive"; out.filePicker_close = "Close"; diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index cfb3462c3..9c6f7e4c9 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -368,9 +368,9 @@ define([ if (data.type !== 'file') { console.log('unhandled embed type ' + data.type); return; } var privateDat = cpNfInner.metadataMgr.getPrivateData(); var origin = privateDat.fileHost || privateDat.origin; - var src = origin + data.src; + var src = data.src = origin + data.src; mediaTagEmbedder($('')); + '" data-crypto-key="cryptpad:' + data.key + '">'), data); } }); $embedButton = $(' - - -
- -
- - - - - - - - - - - diff --git a/www/todo/example/js/app.js b/www/todo/example/js/app.js deleted file mode 100644 index c37e2e6a2..000000000 --- a/www/todo/example/js/app.js +++ /dev/null @@ -1,25 +0,0 @@ -/*global app, $on */ -(function () { - 'use strict'; - - /** - * Sets up a brand new Todo list. - * - * @param {string} name The name of your new to do list. - */ - function Todo(name) { - this.storage = new app.Store(name); - this.model = new app.Model(this.storage); - this.template = new app.Template(); - this.view = new app.View(this.template); - this.controller = new app.Controller(this.model, this.view); - } - - var todo = new Todo('todos-vanillajs'); - - function setView() { - todo.controller.setView(document.location.hash); - } - $on(window, 'load', setView); - $on(window, 'hashchange', setView); -})(); diff --git a/www/todo/example/js/controller.js b/www/todo/example/js/controller.js deleted file mode 100644 index 0a3fb1d83..000000000 --- a/www/todo/example/js/controller.js +++ /dev/null @@ -1,270 +0,0 @@ -(function (window) { - 'use strict'; - - /** - * Takes a model and view and acts as the controller between them - * - * @constructor - * @param {object} model The model instance - * @param {object} view The view instance - */ - function Controller(model, view) { - var self = this; - self.model = model; - self.view = view; - - self.view.bind('newTodo', function (title) { - self.addItem(title); - }); - - self.view.bind('itemEdit', function (item) { - self.editItem(item.id); - }); - - self.view.bind('itemEditDone', function (item) { - self.editItemSave(item.id, item.title); - }); - - self.view.bind('itemEditCancel', function (item) { - self.editItemCancel(item.id); - }); - - self.view.bind('itemRemove', function (item) { - self.removeItem(item.id); - }); - - self.view.bind('itemToggle', function (item) { - self.toggleComplete(item.id, item.completed); - }); - - self.view.bind('removeCompleted', function () { - self.removeCompletedItems(); - }); - - self.view.bind('toggleAll', function (status) { - self.toggleAll(status.completed); - }); - } - - /** - * Loads and initialises the view - * - * @param {string} '' | 'active' | 'completed' - */ - Controller.prototype.setView = function (locationHash) { - var route = locationHash.split('/')[1]; - var page = route || ''; - this._updateFilterState(page); - }; - - /** - * An event to fire on load. Will get all items and display them in the - * todo-list - */ - Controller.prototype.showAll = function () { - var self = this; - self.model.read(function (data) { - self.view.render('showEntries', data); - }); - }; - - /** - * Renders all active tasks - */ - Controller.prototype.showActive = function () { - var self = this; - self.model.read({ completed: false }, function (data) { - self.view.render('showEntries', data); - }); - }; - - /** - * Renders all completed tasks - */ - Controller.prototype.showCompleted = function () { - var self = this; - self.model.read({ completed: true }, function (data) { - self.view.render('showEntries', data); - }); - }; - - /** - * An event to fire whenever you want to add an item. Simply pass in the event - * object and it'll handle the DOM insertion and saving of the new item. - */ - Controller.prototype.addItem = function (title) { - var self = this; - - if (title.trim() === '') { - return; - } - - self.model.create(title, function () { - self.view.render('clearNewTodo'); - self._filter(true); - }); - }; - - /* - * Triggers the item editing mode. - */ - Controller.prototype.editItem = function (id) { - var self = this; - self.model.read(id, function (data) { - self.view.render('editItem', {id: id, title: data[0].title}); - }); - }; - - /* - * Finishes the item editing mode successfully. - */ - Controller.prototype.editItemSave = function (id, title) { - var self = this; - title = title.trim(); - - if (title.length !== 0) { - self.model.update(id, {title: title}, function () { - self.view.render('editItemDone', {id: id, title: title}); - }); - } else { - self.removeItem(id); - } - }; - - /* - * Cancels the item editing mode. - */ - Controller.prototype.editItemCancel = function (id) { - var self = this; - self.model.read(id, function (data) { - self.view.render('editItemDone', {id: id, title: data[0].title}); - }); - }; - - /** - * By giving it an ID it'll find the DOM element matching that ID, - * remove it from the DOM and also remove it from storage. - * - * @param {number} id The ID of the item to remove from the DOM and - * storage - */ - Controller.prototype.removeItem = function (id) { - var self = this; - self.model.remove(id, function () { - self.view.render('removeItem', id); - }); - - self._filter(); - }; - - /** - * Will remove all completed items from the DOM and storage. - */ - Controller.prototype.removeCompletedItems = function () { - var self = this; - self.model.read({ completed: true }, function (data) { - data.forEach(function (item) { - self.removeItem(item.id); - }); - }); - - self._filter(); - }; - - /** - * Give it an ID of a model and a checkbox and it will update the item - * in storage based on the checkbox's state. - * - * @param {number} id The ID of the element to complete or uncomplete - * @param {object} checkbox The checkbox to check the state of complete - * or not - * @param {boolean|undefined} silent Prevent re-filtering the todo items - */ - Controller.prototype.toggleComplete = function (id, completed, silent) { - var self = this; - self.model.update(id, { completed: completed }, function () { - self.view.render('elementComplete', { - id: id, - completed: completed - }); - }); - - if (!silent) { - self._filter(); - } - }; - - /** - * Will toggle ALL checkboxes' on/off state and completeness of models. - * Just pass in the event object. - */ - Controller.prototype.toggleAll = function (completed) { - var self = this; - self.model.read({ completed: !completed }, function (data) { - data.forEach(function (item) { - self.toggleComplete(item.id, completed, true); - }); - }); - - self._filter(); - }; - - /** - * Updates the pieces of the page which change depending on the remaining - * number of todos. - */ - Controller.prototype._updateCount = function () { - var self = this; - self.model.getCount(function (todos) { - self.view.render('updateElementCount', todos.active); - self.view.render('clearCompletedButton', { - completed: todos.completed, - visible: todos.completed > 0 - }); - - self.view.render('toggleAll', {checked: todos.completed === todos.total}); - self.view.render('contentBlockVisibility', {visible: todos.total > 0}); - }); - }; - - /** - * Re-filters the todo items, based on the active route. - * @param {boolean|undefined} force forces a re-painting of todo items. - */ - Controller.prototype._filter = function (force) { - var activeRoute = this._activeRoute.charAt(0).toUpperCase() + this._activeRoute.substr(1); - - // Update the elements on the page, which change with each completed todo - this._updateCount(); - - // If the last active route isn't "All", or we're switching routes, we - // re-create the todo item elements, calling: - // this.show[All|Active|Completed](); - if (force || this._lastActiveRoute !== 'All' || this._lastActiveRoute !== activeRoute) { - this['show' + activeRoute](); - } - - this._lastActiveRoute = activeRoute; - }; - - /** - * Simply updates the filter nav's selected states - */ - Controller.prototype._updateFilterState = function (currentPage) { - // Store a reference to the active route, allowing us to re-filter todo - // items as they are marked complete or incomplete. - this._activeRoute = currentPage; - - if (currentPage === '') { - this._activeRoute = 'All'; - } - - this._filter(); - - this.view.render('setFilter', currentPage); - }; - - // Export to window - window.app = window.app || {}; - window.app.Controller = Controller; -})(window); diff --git a/www/todo/example/js/helpers.js b/www/todo/example/js/helpers.js deleted file mode 100644 index d59a72eff..000000000 --- a/www/todo/example/js/helpers.js +++ /dev/null @@ -1,52 +0,0 @@ -/*global NodeList */ -(function (window) { - 'use strict'; - - // Get element(s) by CSS selector: - window.qs = function (selector, scope) { - return (scope || document).querySelector(selector); - }; - window.qsa = function (selector, scope) { - return (scope || document).querySelectorAll(selector); - }; - - // addEventListener wrapper: - window.$on = function (target, type, callback, useCapture) { - target.addEventListener(type, callback, !!useCapture); - }; - - // Attach a handler to event for all elements that match the selector, - // now or in the future, based on a root element - window.$delegate = function (target, selector, type, handler) { - function dispatchEvent(event) { - var targetElement = event.target; - var potentialElements = window.qsa(selector, target); - var hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0; - - if (hasMatch) { - handler.call(targetElement, event); - } - } - - // https://developer.mozilla.org/en-US/docs/Web/Events/blur - var useCapture = type === 'blur' || type === 'focus'; - - window.$on(target, type, dispatchEvent, useCapture); - }; - - // Find the element's parent with the given tag name: - // $parent(qs('a'), 'div'); - window.$parent = function (element, tagName) { - if (!element.parentNode) { - return; - } - if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase()) { - return element.parentNode; - } - return window.$parent(element.parentNode, tagName); - }; - - // Allow for looping on nodes by chaining: - // qsa('.foo').forEach(function () {}) - NodeList.prototype.forEach = Array.prototype.forEach; -})(window); diff --git a/www/todo/example/js/model.js b/www/todo/example/js/model.js deleted file mode 100644 index 9da766abc..000000000 --- a/www/todo/example/js/model.js +++ /dev/null @@ -1,120 +0,0 @@ -(function (window) { - 'use strict'; - - /** - * Creates a new Model instance and hooks up the storage. - * - * @constructor - * @param {object} storage A reference to the client side storage class - */ - function Model(storage) { - this.storage = storage; - } - - /** - * Creates a new todo model - * - * @param {string} [title] The title of the task - * @param {function} [callback] The callback to fire after the model is created - */ - Model.prototype.create = function (title, callback) { - title = title || ''; - callback = callback || function () {}; - - var newItem = { - title: title.trim(), - completed: false - }; - - this.storage.save(newItem, callback); - }; - - /** - * Finds and returns a model in storage. If no query is given it'll simply - * return everything. If you pass in a string or number it'll look that up as - * the ID of the model to find. Lastly, you can pass it an object to match - * against. - * - * @param {string|number|object} [query] A query to match models against - * @param {function} [callback] The callback to fire after the model is found - * - * @example - * model.read(1, func); // Will find the model with an ID of 1 - * model.read('1'); // Same as above - * //Below will find a model with foo equalling bar and hello equalling world. - * model.read({ foo: 'bar', hello: 'world' }); - */ - Model.prototype.read = function (query, callback) { - var queryType = typeof query; - callback = callback || function () {}; - - if (queryType === 'function') { - callback = query; - return this.storage.findAll(callback); - } else if (queryType === 'string' || queryType === 'number') { - query = parseInt(query, 10); - this.storage.find({ id: query }, callback); - } else { - this.storage.find(query, callback); - } - }; - - /** - * Updates a model by giving it an ID, data to update, and a callback to fire when - * the update is complete. - * - * @param {number} id The id of the model to update - * @param {object} data The properties to update and their new value - * @param {function} callback The callback to fire when the update is complete. - */ - Model.prototype.update = function (id, data, callback) { - this.storage.save(data, callback, id); - }; - - /** - * Removes a model from storage - * - * @param {number} id The ID of the model to remove - * @param {function} callback The callback to fire when the removal is complete. - */ - Model.prototype.remove = function (id, callback) { - this.storage.remove(id, callback); - }; - - /** - * WARNING: Will remove ALL data from storage. - * - * @param {function} callback The callback to fire when the storage is wiped. - */ - Model.prototype.removeAll = function (callback) { - this.storage.drop(callback); - }; - - /** - * Returns a count of all todos - */ - Model.prototype.getCount = function (callback) { - var todos = { - active: 0, - completed: 0, - total: 0 - }; - - this.storage.findAll(function (data) { - data.forEach(function (todo) { - if (todo.completed) { - todos.completed++; - } else { - todos.active++; - } - - todos.total++; - }); - callback(todos); - }); - }; - - // Export to window - window.app = window.app || {}; - window.app.Model = Model; -})(window); diff --git a/www/todo/example/js/store.js b/www/todo/example/js/store.js deleted file mode 100644 index ea09816c8..000000000 --- a/www/todo/example/js/store.js +++ /dev/null @@ -1,141 +0,0 @@ -/*jshint eqeqeq:false */ -(function (window) { - 'use strict'; - - /** - * Creates a new client side storage object and will create an empty - * collection if no collection already exists. - * - * @param {string} name The name of our DB we want to use - * @param {function} callback Our fake DB uses callbacks because in - * real life you probably would be making AJAX calls - */ - function Store(name, callback) { - callback = callback || function () {}; - - this._dbName = name; - - if (!localStorage[name]) { - var data = { - todos: [] - }; - - localStorage[name] = JSON.stringify(data); - } - - callback.call(this, JSON.parse(localStorage[name])); - } - - /** - * Finds items based on a query given as a JS object - * - * @param {object} query The query to match against (i.e. {foo: 'bar'}) - * @param {function} callback The callback to fire when the query has - * completed running - * - * @example - * db.find({foo: 'bar', hello: 'world'}, function (data) { - * // data will return any items that have foo: bar and - * // hello: world in their properties - * }); - */ - Store.prototype.find = function (query, callback) { - if (!callback) { - return; - } - - var todos = JSON.parse(localStorage[this._dbName]).todos; - - callback.call(this, todos.filter(function (todo) { - for (var q in query) { - if (query[q] !== todo[q]) { - return false; - } - } - return true; - })); - }; - - /** - * Will retrieve all data from the collection - * - * @param {function} callback The callback to fire upon retrieving data - */ - Store.prototype.findAll = function (callback) { - callback = callback || function () {}; - callback.call(this, JSON.parse(localStorage[this._dbName]).todos); - }; - - /** - * Will save the given data to the DB. If no item exists it will create a new - * item, otherwise it'll simply update an existing item's properties - * - * @param {object} updateData The data to save back into the DB - * @param {function} callback The callback to fire after saving - * @param {number} id An optional param to enter an ID of an item to update - */ - Store.prototype.save = function (updateData, callback, id) { - var data = JSON.parse(localStorage[this._dbName]); - var todos = data.todos; - - callback = callback || function () {}; - - // If an ID was actually given, find the item and update each property - if (id) { - for (var i = 0; i < todos.length; i++) { - if (todos[i].id === id) { - for (var key in updateData) { - todos[i][key] = updateData[key]; - } - break; - } - } - - localStorage[this._dbName] = JSON.stringify(data); - callback.call(this, todos); - } else { - // Generate an ID - updateData.id = new Date().getTime(); - - todos.push(updateData); - localStorage[this._dbName] = JSON.stringify(data); - callback.call(this, [updateData]); - } - }; - - /** - * Will remove an item from the Store based on its ID - * - * @param {number} id The ID of the item you want to remove - * @param {function} callback The callback to fire after saving - */ - Store.prototype.remove = function (id, callback) { - var data = JSON.parse(localStorage[this._dbName]); - var todos = data.todos; - - for (var i = 0; i < todos.length; i++) { - if (todos[i].id == id) { - todos.splice(i, 1); - break; - } - } - - localStorage[this._dbName] = JSON.stringify(data); - callback.call(this, todos); - }; - - /** - * Will drop all storage and start fresh - * - * @param {function} callback The callback to fire after dropping the data - */ - Store.prototype.drop = function (callback) { - var data = {todos: []}; - localStorage[this._dbName] = JSON.stringify(data); - callback.call(this, data.todos); - }; - - // Export to window - window.app = window.app || {}; - window.app.Store = Store; -})(window); diff --git a/www/todo/example/js/template.js b/www/todo/example/js/template.js deleted file mode 100644 index a5587731f..000000000 --- a/www/todo/example/js/template.js +++ /dev/null @@ -1,114 +0,0 @@ -/*jshint laxbreak:true */ -(function (window) { - 'use strict'; - - var htmlEscapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - '\'': ''', - '`': '`' - }; - - var escapeHtmlChar = function (chr) { - return htmlEscapes[chr]; - }; - - var reUnescapedHtml = /[&<>"'`]/g; - var reHasUnescapedHtml = new RegExp(reUnescapedHtml.source); - - var escape = function (string) { - return (string && reHasUnescapedHtml.test(string)) - ? string.replace(reUnescapedHtml, escapeHtmlChar) - : string; - }; - - /** - * Sets up defaults for all the Template methods such as a default template - * - * @constructor - */ - function Template() { - this.defaultTemplate - = '
  • ' - + '
    ' - + '' - + '' - + '' - + '
    ' - + '
  • '; - } - - /** - * Creates an
  • HTML string and returns it for placement in your app. - * - * NOTE: In real life you should be using a templating engine such as Mustache - * or Handlebars, however, this is a vanilla JS example. - * - * @param {object} data The object containing keys you want to find in the - * template to replace. - * @returns {string} HTML String of an
  • element - * - * @example - * view.show({ - * id: 1, - * title: "Hello World", - * completed: 0, - * }); - */ - Template.prototype.show = function (data) { - var i = 0, l = data.length; - var view = ''; - - for (; i < l; i++) { - var template = this.defaultTemplate; - var completed = ''; - var checked = ''; - - if (data[i].completed) { - completed = 'completed'; - checked = 'checked'; - } - - template = template.replace('{{id}}', data[i].id); - template = template.replace('{{title}}', escape(data[i].title)); - template = template.replace('{{completed}}', completed); - template = template.replace('{{checked}}', checked); - - view = view + template; - } - - return view; - }; - - /** - * Displays a counter of how many to dos are left to complete - * - * @param {number} activeTodos The number of active todos. - * @returns {string} String containing the count - */ - Template.prototype.itemCounter = function (activeTodos) { - var plural = activeTodos === 1 ? '' : 's'; - - return '' + activeTodos + ' item' + plural + ' left'; - }; - - /** - * Updates the text within the "Clear completed" button - * - * @param {[type]} completedTodos The number of completed todos. - * @returns {string} String containing the count - */ - Template.prototype.clearCompletedButton = function (completedTodos) { - if (completedTodos > 0) { - return 'Clear completed'; - } else { - return ''; - } - }; - - // Export to window - window.app = window.app || {}; - window.app.Template = Template; -})(window); diff --git a/www/todo/example/js/view.js b/www/todo/example/js/view.js deleted file mode 100644 index d9f59611e..000000000 --- a/www/todo/example/js/view.js +++ /dev/null @@ -1,219 +0,0 @@ -/*global qs, qsa, $on, $parent, $delegate */ - -(function (window) { - 'use strict'; - - /** - * View that abstracts away the browser's DOM completely. - * It has two simple entry points: - * - * - bind(eventName, handler) - * Takes a todo application event and registers the handler - * - render(command, parameterObject) - * Renders the given command with the options - */ - function View(template) { - this.template = template; - - this.ENTER_KEY = 13; - this.ESCAPE_KEY = 27; - - this.$todoList = qs('.todo-list'); - this.$todoItemCounter = qs('.todo-count'); - this.$clearCompleted = qs('.clear-completed'); - this.$main = qs('.main'); - this.$footer = qs('.footer'); - this.$toggleAll = qs('.toggle-all'); - this.$newTodo = qs('.new-todo'); - } - - View.prototype._removeItem = function (id) { - var elem = qs('[data-id="' + id + '"]'); - - if (elem) { - this.$todoList.removeChild(elem); - } - }; - - View.prototype._clearCompletedButton = function (completedCount, visible) { - this.$clearCompleted.innerHTML = this.template.clearCompletedButton(completedCount); - this.$clearCompleted.style.display = visible ? 'block' : 'none'; - }; - - View.prototype._setFilter = function (currentPage) { - qs('.filters .selected').className = ''; - qs('.filters [href="#/' + currentPage + '"]').className = 'selected'; - }; - - View.prototype._elementComplete = function (id, completed) { - var listItem = qs('[data-id="' + id + '"]'); - - if (!listItem) { - return; - } - - listItem.className = completed ? 'completed' : ''; - - // In case it was toggled from an event and not by clicking the checkbox - qs('input', listItem).checked = completed; - }; - - View.prototype._editItem = function (id, title) { - var listItem = qs('[data-id="' + id + '"]'); - - if (!listItem) { - return; - } - - listItem.className = listItem.className + ' editing'; - - var input = document.createElement('input'); - input.className = 'edit'; - - listItem.appendChild(input); - input.focus(); - input.value = title; - }; - - View.prototype._editItemDone = function (id, title) { - var listItem = qs('[data-id="' + id + '"]'); - - if (!listItem) { - return; - } - - var input = qs('input.edit', listItem); - listItem.removeChild(input); - - listItem.className = listItem.className.replace('editing', ''); - - qsa('label', listItem).forEach(function (label) { - label.textContent = title; - }); - }; - - View.prototype.render = function (viewCmd, parameter) { - var self = this; - var viewCommands = { - showEntries: function () { - self.$todoList.innerHTML = self.template.show(parameter); - }, - removeItem: function () { - self._removeItem(parameter); - }, - updateElementCount: function () { - self.$todoItemCounter.innerHTML = self.template.itemCounter(parameter); - }, - clearCompletedButton: function () { - self._clearCompletedButton(parameter.completed, parameter.visible); - }, - contentBlockVisibility: function () { - self.$main.style.display = self.$footer.style.display = parameter.visible ? 'block' : 'none'; - }, - toggleAll: function () { - self.$toggleAll.checked = parameter.checked; - }, - setFilter: function () { - self._setFilter(parameter); - }, - clearNewTodo: function () { - self.$newTodo.value = ''; - }, - elementComplete: function () { - self._elementComplete(parameter.id, parameter.completed); - }, - editItem: function () { - self._editItem(parameter.id, parameter.title); - }, - editItemDone: function () { - self._editItemDone(parameter.id, parameter.title); - } - }; - - viewCommands[viewCmd](); - }; - - View.prototype._itemId = function (element) { - var li = $parent(element, 'li'); - return parseInt(li.dataset.id, 10); - }; - - View.prototype._bindItemEditDone = function (handler) { - var self = this; - $delegate(self.$todoList, 'li .edit', 'blur', function () { - if (!this.dataset.iscanceled) { - handler({ - id: self._itemId(this), - title: this.value - }); - } - }); - - $delegate(self.$todoList, 'li .edit', 'keypress', function (event) { - if (event.keyCode === self.ENTER_KEY) { - // Remove the cursor from the input when you hit enter just like if it - // were a real form - this.blur(); - } - }); - }; - - View.prototype._bindItemEditCancel = function (handler) { - var self = this; - $delegate(self.$todoList, 'li .edit', 'keyup', function (event) { - if (event.keyCode === self.ESCAPE_KEY) { - this.dataset.iscanceled = true; - this.blur(); - - handler({id: self._itemId(this)}); - } - }); - }; - - View.prototype.bind = function (event, handler) { - var self = this; - if (event === 'newTodo') { - $on(self.$newTodo, 'change', function () { - handler(self.$newTodo.value); - }); - - } else if (event === 'removeCompleted') { - $on(self.$clearCompleted, 'click', function () { - handler(); - }); - - } else if (event === 'toggleAll') { - $on(self.$toggleAll, 'click', function () { - handler({completed: this.checked}); - }); - - } else if (event === 'itemEdit') { - $delegate(self.$todoList, 'li label', 'dblclick', function () { - handler({id: self._itemId(this)}); - }); - - } else if (event === 'itemRemove') { - $delegate(self.$todoList, '.destroy', 'click', function () { - handler({id: self._itemId(this)}); - }); - - } else if (event === 'itemToggle') { - $delegate(self.$todoList, '.toggle', 'click', function () { - handler({ - id: self._itemId(this), - completed: this.checked - }); - }); - - } else if (event === 'itemEditDone') { - self._bindItemEditDone(handler); - - } else if (event === 'itemEditCancel') { - self._bindItemEditCancel(handler); - } - }; - - // Export to window - window.app = window.app || {}; - window.app.View = View; -}(window)); From 65a1decd73ba1a89979ec520937f8d5740f333a0 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 10 Jan 2018 10:14:49 +0100 Subject: [PATCH 17/56] step towards customizable login functionality --- {www/common => customize.dist}/credential.js | 0 {www/common => customize.dist}/login.js | 2 +- customize.dist/store.js | 97 -------------------- www/login/main.js | 2 +- www/register/main.js | 4 +- 5 files changed, 4 insertions(+), 101 deletions(-) rename {www/common => customize.dist}/credential.js (100%) rename {www/common => customize.dist}/login.js (99%) delete mode 100644 customize.dist/store.js diff --git a/www/common/credential.js b/customize.dist/credential.js similarity index 100% rename from www/common/credential.js rename to customize.dist/credential.js diff --git a/www/common/login.js b/customize.dist/login.js similarity index 99% rename from www/common/login.js rename to customize.dist/login.js index 847ca74a7..b7a9d4e84 100644 --- a/www/common/login.js +++ b/customize.dist/login.js @@ -4,7 +4,7 @@ define([ '/bower_components/chainpad-crypto/crypto.js', '/common/common-util.js', '/common/outer/network-config.js', - '/common/credential.js', + '/customize/credential.js', '/bower_components/chainpad/chainpad.dist.js', '/bower_components/tweetnacl/nacl-fast.min.js', diff --git a/customize.dist/store.js b/customize.dist/store.js deleted file mode 100644 index 5b82921ed..000000000 --- a/customize.dist/store.js +++ /dev/null @@ -1,97 +0,0 @@ -define(function () { - /* - This module uses localStorage, which is synchronous, but exposes an - asyncronous API. This is so that we can substitute other storage - methods. - - To override these methods, create another file at: - /customize/storage.js - */ - - var Store = {}; - - // Store uses nodebacks... - Store.set = function (key, val, cb) { - localStorage.setItem(key, JSON.stringify(val)); - cb(); - }; - - // implement in alternative store - Store.setBatch = function (map, cb) { - Object.keys(map).forEach(function (key) { - localStorage.setItem(key, JSON.stringify(map[key])); - }); - cb(void 0, map); - }; - - var safeGet = window.safeGet = function (key) { - var val = localStorage.getItem(key); - try { - return JSON.parse(val); - } catch (err) { - console.log(val); - console.error(err); - return val; - } - }; - - Store.get = function (key, cb) { - cb(void 0, safeGet(key)); - }; - - // implement in alternative store - Store.getBatch = function (keys, cb) { - var res = {}; - keys.forEach(function (key) { - res[key] = safeGet(key); - }); - cb(void 0, res); - }; - - Store.remove = function (key, cb) { - localStorage.removeItem(key); - cb(); - }; - - // implement in alternative store - Store.removeBatch = function (keys, cb) { - keys.forEach(function (key) { - localStorage.removeItem(key); - }); - cb(); - }; - - Store.keys = function (cb) { - cb(void 0, Object.keys(localStorage)); - }; - - Store.ready = function (f) { - if (typeof(f) === 'function') { - f(void 0, Store); - } - }; - - var changeHandlers = Store.changeHandlers = []; - - Store.change = function (f) { - if (typeof(f) !== 'function') { - throw new Error('[Store.change] callback must be a function'); - } - changeHandlers.push(f); - - if (changeHandlers.length === 1) { - // start listening for changes - window.addEventListener('storage', function (e) { - changeHandlers.forEach(function (f) { - f({ - key: e.key, - oldValue: e.oldValue, - newValue: e.newValue, - }); - }); - }); - } - }; - - return Store; -}); diff --git a/www/login/main.js b/www/login/main.js index ce3aa8835..24f0e905a 100644 --- a/www/login/main.js +++ b/www/login/main.js @@ -1,7 +1,7 @@ define([ 'jquery', '/common/cryptpad-common.js', - '/common/login.js', + '/customize/login.js', '/common/common-interface.js', '/common/common-realtime.js', '/common/common-feedback.js', diff --git a/www/register/main.js b/www/register/main.js index 5337dfa87..a6c31509f 100644 --- a/www/register/main.js +++ b/www/register/main.js @@ -1,9 +1,9 @@ define([ 'jquery', - '/common/login.js', + '/customize/login.js', '/common/cryptpad-common.js', '/common/test.js', - '/common/credential.js', // preloaded for login.js + '/customize/credential.js', // preloaded for login.js '/common/common-interface.js', '/common/common-util.js', '/common/common-realtime.js', From 864e24b2ce9d3a5e475970940ff0aad00fcc8d5f Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Wed, 10 Jan 2018 11:42:48 +0100 Subject: [PATCH 18/56] Fixed typo in test --- www/assert/main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/assert/main.js b/www/assert/main.js index c792c780c..eb470eb35 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -134,12 +134,12 @@ define([ // check that old hashes parse correctly assert(function (cb) { - if (1) { return cb(true); } // TODO(cjd): This is a test failure which is a known bug + //if (1) { return cb(true); } // TODO(cjd): This is a test failure which is a known bug var secret = Hash.parsePadUrl('/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy'); return cb(secret.hashData.channel === "67b8385b07352be53e40746d2be6ccd7" && secret.hashData.key === "XAYSuJYYqa9NfmInyHci7LNy" && secret.hashData.version === 0 && - typeof(secret.hashData.getURL) === 'function'); + typeof(secret.getUrl) === 'function'); }, "Old hash failed to parse"); // make sure version 1 hashes parse correctly From e5b9f089fb95d6f3c48110c89b2d7b904b889b5c Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 10 Jan 2018 11:47:52 +0100 Subject: [PATCH 19/56] Fix undefined webchannel when uploading a file --- www/common/outer/async-store.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index e48452b6d..7d3d15dfd 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -525,11 +525,11 @@ define([ var h = p.hashData; var owners; - if (Store.channel && Util.base64ToHex(h.channel) === Store.channel.wc.id) { + if (Store.channel && Store.channel.wc && Util.base64ToHex(h.channel) === Store.channel.wc.id) { owners = Store.channel.data.owners || undefined; } var expire; - if (Store.channel && Util.base64ToHex(h.channel) === Store.channel.wc.id) { + if (Store.channel && Store.channel.wc && Util.base64ToHex(h.channel) === Store.channel.wc.id) { expire = +Store.channel.data.expire || undefined; } From 1ab1f7187159902ac874ed5f9d5f6dba11d85cc6 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 10 Jan 2018 11:49:16 +0100 Subject: [PATCH 20/56] implement extension point for share menu --- www/common/common-ui-elements.js | 6 ++++++ www/common/toolbar3.js | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 90e554d31..75c4fe31a 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -859,9 +859,14 @@ define([ if (typeof config !== "object" || !Array.isArray(config.options)) { return; } if (config.feedback && !config.common) { return void console.error("feedback in a dropdown requires sframe-common"); } + var isElement = function (o) { + return /HTML/.test(Object.prototype.toString.call(o)) && + typeof(o.tagName) === 'string'; + }; var allowedTags = ['a', 'p', 'hr']; var isValidOption = function (o) { if (typeof o !== "object") { return false; } + if (isElement(o)) { return true; } if (!o.tag || allowedTags.indexOf(o.tag) === -1) { return false; } return true; }; @@ -893,6 +898,7 @@ define([ config.options.forEach(function (o) { if (!isValidOption(o)) { return; } + if (isElement(o)) { return $innerblock.append($(o)); } $('<' + o.tag + '>', o.attributes || {}).html(o.content || '').appendTo($innerblock); }); diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 2d4dccd63..6a0016fcf 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -8,7 +8,8 @@ define([ '/common/common-feedback.js', '/customize/messages.js', '/common/clipboard.js', -], function ($, Config, ApiConfig, UIElements, UI, Hash, Feedback, Messages, Clipboard) { + '/common/hyperscript.js', +], function ($, Config, ApiConfig, UIElements, UI, Hash, Feedback, Messages, Clipboard, h) { var Common; var Bar = { @@ -496,6 +497,9 @@ define([ content: ' ' + Messages.getEmbedCode }); } + if (typeof(Config.customizeShareOptions) === 'function') { + Config.customizeShareOptions(hashes, options); + } var dropdownConfigShare = { text: $('
    ').append($shareIcon).html(), options: options, From 480ba32d78233ac7b1583e410a6797991df47d4e Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 10 Jan 2018 11:59:06 +0100 Subject: [PATCH 21/56] make extension point cover share menu for files --- www/common/toolbar3.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 6a0016fcf..76062a5c3 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -8,8 +8,7 @@ define([ '/common/common-feedback.js', '/customize/messages.js', '/common/clipboard.js', - '/common/hyperscript.js', -], function ($, Config, ApiConfig, UIElements, UI, Hash, Feedback, Messages, Clipboard, h) { +], function ($, Config, ApiConfig, UIElements, UI, Hash, Feedback, Messages, Clipboard) { var Common; var Bar = { @@ -498,7 +497,9 @@ define([ }); } if (typeof(Config.customizeShareOptions) === 'function') { - Config.customizeShareOptions(hashes, options); + Config.customizeShareOptions(hashes, options, { + type: 'DEFAULT', + }); } var dropdownConfigShare = { text: $('
    ').append($shareIcon).html(), @@ -586,6 +587,13 @@ define([ attributes: {title: Messages.fileEmbedTitle, 'class': 'cp-toolbar-share-file-embed'}, content: ' ' + Messages.getEmbedCode }); + + if (typeof(Config.customizeShareOptions) === 'function') { + Config.customizeShareOptions(hashes, options, { + type: 'FILE' + }); + } + var dropdownConfigShare = { text: $('
    ').append($shareIcon).html(), options: options, From 3075840e6c851e2c108409704ebb057fc819c74c Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 10 Jan 2018 12:52:18 +0100 Subject: [PATCH 22/56] add more config parameters to share menu extension --- www/common/toolbar3.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 76062a5c3..52b414194 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -499,6 +499,8 @@ define([ if (typeof(Config.customizeShareOptions) === 'function') { Config.customizeShareOptions(hashes, options, { type: 'DEFAULT', + origin: origin, + pathname: pathname }); } var dropdownConfigShare = { @@ -590,7 +592,9 @@ define([ if (typeof(Config.customizeShareOptions) === 'function') { Config.customizeShareOptions(hashes, options, { - type: 'FILE' + type: 'FILE', + origin: origin, + pathname: pathname }); } From cd7c5abc3edbea95caeee9d92fdfb7c4cac6abea Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 10 Jan 2018 16:46:46 +0100 Subject: [PATCH 23/56] Fix a missing callback in closeChannel --- storage/file.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/file.js b/storage/file.js index 31b58aa94..a1348d427 100644 --- a/storage/file.js +++ b/storage/file.js @@ -45,7 +45,7 @@ var getChannelMetadata = function (Env, channelId, cb) { }; var closeChannel = function (env, channelName, cb) { - if (!env.channels[channelName]) { return; } + if (!env.channels[channelName]) { return void cb(); } try { env.channels[channelName].writeStream.close(); delete env.channels[channelName]; From 8c9490868b64eb078bade7666792cfceb2fcbf95 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 10 Jan 2018 17:57:40 +0100 Subject: [PATCH 24/56] improve owned pads management in the drive --- customize.dist/translations/messages.fr.js | 1 + customize.dist/translations/messages.js | 1 + www/common/common-ui-elements.js | 8 +++ www/common/sframe-common-outer.js | 7 +-- www/drive/inner.html | 3 +- www/drive/inner.js | 58 ++++++++++++++++++---- 6 files changed, 63 insertions(+), 15 deletions(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index c26333b2b..ee8a1a4d2 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -419,6 +419,7 @@ define(function () { out.fc_open = "Ouvrir"; out.fc_open_ro = "Ouvrir (lecture seule)"; out.fc_delete = "Déplacer vers la corbeille"; + out.fc_delete_owned = "Supprimer du serveur"; out.fc_restore = "Restaurer"; out.fc_remove = "Supprimer de votre CryptDrive"; out.fc_empty = "Vider la corbeille"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index cd3f966a9..84dbdde0e 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -422,6 +422,7 @@ define(function () { out.fc_open = "Open"; out.fc_open_ro = "Open (read-only)"; out.fc_delete = "Move to trash"; + out.fc_delete_owned = "Delete from the server"; out.fc_restore = "Restore"; out.fc_remove = "Remove from your CryptDrive"; out.fc_empty = "Empty the trash"; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 90e554d31..f49491ebf 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -107,9 +107,11 @@ define([ .appendTo($d); var owners = Messages.creation_noOwner; var edPublic = common.getMetadataMgr().getPrivateData().edPublic; + var owned = false; if (data.owners && data.owners.length) { if (data.owners.indexOf(edPublic) !== -1) { owners = Messages.yourself; + owned = true; } else { owners = Messages.creation_ownedByOther; } @@ -117,6 +119,12 @@ define([ $d.append(UI.dialog.selectable(owners, { id: 'cp-app-prop-owners', })); + /* TODO + if (owned) { + var $deleteOwned = $('button').text(Messages.fc_delete_owned).click(function () { + }); + $d.append($deleteOwned); + }*/ var expire = Messages.creation_expireFalse; if (data.expire && typeof (data.expire) === "number") { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 64b9df509..31a29b623 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -494,6 +494,10 @@ define([ Cryptpad.setLanguage(data, cb); }); + sframeChan.on('Q_CONTACTS_CLEAR_OWNED_CHANNEL', function (channel, cb) { + Cryptpad.clearOwnedChannel(channel, cb); + }); + if (cfg.addRpc) { cfg.addRpc(sframeChan, Cryptpad, Utils); } @@ -530,9 +534,6 @@ define([ sframeChan.on('Q_CONTACTS_SET_CHANNEL_HEAD', function (opt, cb) { Cryptpad.messenger.setChannelHead(opt, cb); }); - sframeChan.on('Q_CONTACTS_CLEAR_OWNED_CHANNEL', function (channel, cb) { - Cryptpad.clearOwnedChannel(channel, cb); - }); Cryptpad.messenger.onMessageEvent.reg(function (data) { sframeChan.event('EV_CONTACTS_MESSAGE', data); diff --git a/www/drive/inner.html b/www/drive/inner.html index d2f7a2f55..93d4688ef 100644 --- a/www/drive/inner.html +++ b/www/drive/inner.html @@ -24,6 +24,7 @@
  • Open (read-only)
  • Rename
  • Delete
  • +
  • Delete permanently
  • New folder
  • Properties
  • Tags
  • @@ -44,7 +45,7 @@
  • Open
  • Open (read-only)
  • Delete
  • -
  • Delete permanently
  • +
  • Delete permanently
  • Properties
  • Tags
  • diff --git a/www/drive/inner.js b/www/drive/inner.js index 0878449ab..381eb5e94 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -658,11 +658,12 @@ define([ if (!isOwnDrive()) { hide.push($menu.find('a.cp-app-drive-context-own')); } - if ($element.is('.cp-app-drive-element-owned')) { - hide.push($menu.find('a.cp-app-drive-context-delete')); - } else { + if (!$element.is('.cp-app-drive-element-owned')) { hide.push($menu.find('a.cp-app-drive-context-deleteowned')); } + if ($element.is('.cp-app-drive-element-notrash')) { + hide.push($menu.find('a.cp-app-drive-context-delete')); + } if ($element.is('.cp-app-drive-element-file')) { // No folder in files hide.push($menu.find('a.cp-app-drive-context-newfolder')); @@ -1183,6 +1184,7 @@ define([ if (data.owners && data.owners.indexOf(edPublic) !== -1) { var $owned = $ownedIcon.clone().appendTo($state); $owned.attr('title', Messages.fm_padIsOwned); + $span.addClass('cp-app-drive-element-owned'); } else if (data.owners && data.owners.length) { var $owner = $ownerIcon.clone().appendTo($state); $owner.attr('title', Messages.fm_padIsOwnedOther); @@ -2065,7 +2067,7 @@ define([ var roClass = typeof(ro) === 'undefined' ? ' cp-app-drive-element-noreadonly' : ro ? ' cp-app-drive-element-readonly' : ''; var $element = $('
  • ', { - 'class': 'cp-app-drive-element cp-app-drive-element-file cp-app-drive-element-row' + roClass, + 'class': 'cp-app-drive-element cp-app-drive-element-notrash cp-app-drive-element-file cp-app-drive-element-row' + roClass, }); $element.prepend($icon).dblclick(function () { openFile(id); @@ -2103,7 +2105,8 @@ define([ var roClass = typeof(ro) === 'undefined' ? ' cp-app-drive-element-noreadonly' : ro ? ' cp-app-drive-element-readonly' : ''; var $element = $('
  • ', { - 'class': 'cp-app-drive-element cp-app-drive-element-owned cp-app-drive-element-file cp-app-drive-element-row' + roClass + 'class': 'cp-app-drive-element cp-app-drive-element-notrash ' + + 'cp-app-drive-element-file cp-app-drive-element-row' + roClass }); $element.prepend($icon).dblclick(function () { openFile(id); @@ -2600,6 +2603,30 @@ define([ paths.forEach(function (p) { pathsList.push(p.path); }); moveElements(pathsList, [TRASH], false, refresh); } + else if ($(this).hasClass('cp-app-drive-context-deleteowned')) { + var pathsListD = []; + var msgD = Messages.fm_deleteOwnedPads; + UI.confirm(msgD, function(res) { + $(window).focus(); + if (!res) { return; } + // Try to delete each selected pad from server, and delete from drive if no error + var n = nThen(function () {}); + paths.forEach(function (p) { + var el = filesOp.find(p.path); + var data = filesOp.getFileData(el); + var parsed = Hash.parsePadUrl(data.href); + var channel = Util.base64ToHex(parsed.hashData.channel); + n = n.nThen(function (waitFor) { + sframeChan.query('Q_CONTACTS_CLEAR_OWNED_CHANNEL', channel, + waitFor(function (e) { + if (e) { return void console.error(e); } + filesOp.delete([p.path], refresh); + })); + }); + }); + }); + return; + } else if ($(this).hasClass('cp-app-drive-context-open')) { paths.forEach(function (p) { var $element = p.element; @@ -2689,17 +2716,26 @@ define([ moveElements(pathsList, [TRASH], false, refresh); } else if ($(this).hasClass('cp-app-drive-context-deleteowned')) { - // TODO - // Remove owned pad from drive and remove from server var pathsListD = []; - paths.forEach(function (p) { pathsListD.push(p.path); }); var msgD = Messages.fm_deleteOwnedPads; UI.confirm(msgD, function(res) { $(window).focus(); if (!res) { return; } - filesOp.delete(pathsListD, refresh); - // TODO XXX HERE - // RPC to delete from server + // Try to delete each selected pad from server, and delete from drive if no error + var n = nThen(function () {}); + paths.forEach(function (p) { + var el = filesOp.find(p.path); + var data = filesOp.getFileData(el); + var parsed = Hash.parsePadUrl(data.href); + var channel = Util.base64ToHex(parsed.hashData.channel); + n = n.nThen(function (waitFor) { + sframeChan.query('Q_CONTACTS_CLEAR_OWNED_CHANNEL', channel, + waitFor(function (e) { + if (e) { return void console.error(e); } + filesOp.delete([p.path], refresh); + })); + }); + }); }); return; } From 151d33db180d44d41c89309002b150895e1e0206 Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Thu, 11 Jan 2018 15:45:12 +0100 Subject: [PATCH 25/56] Added testing of /poll/ :D --- TestSelenium.js | 3 ++ www/common/test.js | 19 ++++++++---- www/poll/inner.js | 76 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 91 insertions(+), 7 deletions(-) diff --git a/TestSelenium.js b/TestSelenium.js index 59bdd427a..cd34b1986 100644 --- a/TestSelenium.js +++ b/TestSelenium.js @@ -49,6 +49,9 @@ var nt = nThen(function (waitFor) { ['/slide/#/1/edit/uwKqgj8Ezh2dRaFUWSlrRQ/JkJtAb-hNzfESZEHreAeULU1/', {}], ['/slide/#/1/view/uwKqgj8Ezh2dRaFUWSlrRQ/Xa8jXl+jWMpwep41mlrhkqbRuVKGxlueH80Pbgeu5Go/', {}], + ['/poll/#/1/edit/lHhnKHSs0HBsl2UGfSJoLw/ZXSsAq4BORIixuFaLVBFcxoq/', {}], + ['/poll/#/1/view/lHhnKHSs0HBsl2UGfSJoLw/TGul8PhswwLh1klHpBto6yEntWtKES2+tetYrrYec4M/', {}] + ].forEach(function (x) { if (failed) { return; } var url = 'http://localhost:3000' + x[0]; diff --git a/www/common/test.js b/www/common/test.js index ed48750a5..5265c2311 100644 --- a/www/common/test.js +++ b/www/common/test.js @@ -34,6 +34,11 @@ define([], function () { if (locks.length === 1) { runLock(locks.shift()); } + }, + assert: function (expr) { + if (expr || failed) { return; } + failed = true; + out.failed("Failed assertion"); } }); }; @@ -131,10 +136,12 @@ define([], function () { }; var enableManual = function () { out.testing = 'manual'; + console.log('manual testing enabled'); out.passed = function () { window.alert("Test passed"); }; out.failed = function (reason) { + try { throw new Error(reason); } catch (err) { console.log(err.stack); } window.alert("Test failed [" + reason + "]"); }; out.registerInner = function () { }; @@ -146,18 +153,18 @@ define([], function () { out.registerInner = function () { }; out.registerOuter = function () { }; - if (window.location.hash.indexOf("test=auto") > -1) { - enableAuto(); - } else if (window.location.hash.indexOf("test=manual") > -1) { - enableManual(); - } else if (document.cookie.indexOf('test=') === 0) { + if (document.cookie.indexOf('test=') === 0) { try { var x = JSON.parse(decodeURIComponent(document.cookie.replace('test=', ''))); if (x.test === 'auto') { out.options = x.opts; enableAuto('auto'); + console.log("Enable auto testing " + window.origin); + } else if (x.test === 'manual') { + out.options = x.opts; + enableManual(); + console.log("Enable manual testing " + window.origin); } - console.log("Enable auto testing " + window.origin); } catch (e) { } } diff --git a/www/poll/inner.js b/www/poll/inner.js index c693a643d..b08b66097 100644 --- a/www/poll/inner.js +++ b/www/poll/inner.js @@ -17,6 +17,7 @@ define([ '/common/common-interface.js', '/customize/messages.js', 'cm/lib/codemirror', + '/common/test.js', 'cm/addon/display/placeholder', 'cm/mode/markdown/markdown', @@ -45,7 +46,8 @@ define([ ChainPad, UI, Messages, - CMeditor) + CMeditor, + Test) { var saveAs = window.saveAs; @@ -965,6 +967,7 @@ define([ }); APP.$createRow = $('#cp-app-poll-create-option').click(function () { var uncommittedCopy = { content: getUncommitted('row') }; + console.log(uncommittedCopy); mergeUncommitted(proxy, uncommittedCopy, true); change(null, null, null, null, function() { var newId = APP.uncommitted.content.rowsOrder[0]; @@ -1045,6 +1048,77 @@ define([ publish(true); } + var passIfOk = function (t) { + t.assert($('#cp-app-poll-description-published').text().indexOf( + "Content for the description") === 0); + t.assert($('.cp-app-poll-comments-list-data-name').text().indexOf( + "Mr.Me") === 0); + t.assert($('.cp-app-poll-comments-list-msg-text').text().indexOf( + "Example comment yay") === 0); + t.assert($('input[value="Candy"]').length === 1); + t.assert($('input[value="IceCream"]').length === 1); + t.assert($('input[value="Soda"]').length === 1); + t.assert($('input[value="Meeee"]').length === 1); + t.pass(); + }; + + if (!APP.readOnly) { + console.log("Here is the test"); + Test(function (t) { + if ($('input[value="Candy"]').length) { + t.fail("Test has already been performed"); + return; + } + nThen(function (waitFor) { + console.log("Here is the test1"); + APP.editor.setValue("Content for the description"); + $('.cp-app-poll-table-editing .cp-app-poll-table-text-cell input').val( + 'Candy').keyup(); + $('#cp-app-poll-create-option').click(); + // TODO(cjd): Need to click outside to lock the first option we create.. bug? + $(window).trigger({ type: "click", which: 1 }); + setTimeout(waitFor()); + }).nThen(function (waitFor) { + $('.cp-app-poll-table-editing .cp-app-poll-table-text-cell input').val( + 'IceCream').keyup(); + $('#cp-app-poll-create-option').click(); + setTimeout(waitFor()); + }).nThen(function (waitFor) { + $('.cp-app-poll-table-editing .cp-app-poll-table-text-cell input').val( + 'Soda').keyup(); + $('#cp-app-poll-create-option').click(); + setTimeout(waitFor()); + }).nThen(function (waitFor) { + // Switch to non-admin mode + $('.cp-toolbar-rightside-button.fa-check').click(); + setTimeout(waitFor()); + }).nThen(function (waitFor) { + $('.cp-app-poll-comments-add-name').val("Mr.Me").keyup(); + $('.cp-app-poll-comments-add-msg').val("Example comment yay").keyup(); + setTimeout(waitFor()); + }).nThen(function (waitFor) { + $('.cp-app-poll-comments-add-submit').click(); + setTimeout(waitFor()); + }).nThen(function (waitFor) { + $('#cp-app-poll-create-user').parent().find('input').val('Meeee').keyup(); + [1,3,2].forEach(function (num, i) { + var x = $($('.cp-app-poll-table-checkbox-contain label')[i]); + for (var ii = 0; ii < num; ii++) { + x.trigger({ type: 'click', which: 1 }); + } + }); + setTimeout(waitFor()); + }).nThen(function (waitFor) { + $('#cp-app-poll-create-user').click(); + setTimeout(waitFor()); + }).nThen(function (waitFor) { + passIfOk(t); + }); + }); + } else { + Test(passIfOk); + } + UI.removeLoadingScreen(); if (isNew) { common.openTemplatePicker(); From ae2517fcc698aa0ae90ed27e0c72b5e4969f8f24 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 10 Jan 2018 09:54:10 +0100 Subject: [PATCH 26/56] remove dead code from home page --- customize.dist/main.js | 123 +---------------------------------------- 1 file changed, 3 insertions(+), 120 deletions(-) diff --git a/customize.dist/main.js b/customize.dist/main.js index 7aaa77675..008226211 100644 --- a/customize.dist/main.js +++ b/customize.dist/main.js @@ -1,25 +1,12 @@ define([ 'jquery', - '/customize/application_config.js', - '/common/cryptpad-common.js', - '/common/common-interface.js', - '/common/common-realtime.js', - '/common/common-constants.js', '/common/outer/local-store.js', '/customize/messages.js', -], function ($, Config, Cryptpad, UI, Realtime, Constants, LocalStore, Messages) { - - window.APP = { - Cryptpad: Cryptpad, - }; +], function ($, LocalStore, Messages) { $(function () { var $main = $('#mainBlock'); - $(window).click(function () { - $('.cp-dropdown-content').hide(); - }); - // main block is hidden in case javascript is disabled $main.removeClass('hidden'); @@ -34,113 +21,9 @@ define([ $main.find('a[href="/drive/"] div.pad-button-text h4') .text(Messages.main_yourCryptDrive); - - var name = localStorage[Constants.userNameKey] || sessionStorage[Constants.userNameKey]; - var $loggedInBlock = $main.find('#loggedIn'); - var $hello = $loggedInBlock.find('#loggedInHello'); - var $logout = $loggedInBlock.find('#loggedInLogOut'); - - if (name) { - $hello.text(Messages._getKey('login_hello', [name])); - } else { - $hello.text(Messages.login_helloNoName); - } - $('#buttons').find('.nologin').hide(); - - $logout.click(function () { - LocalStore.logout(function () { - window.location.reload(); - }); - }); - - $loggedInBlock.removeClass('hidden'); - } - else { - $main.find('#userForm').removeClass('hidden'); - $('#name').focus(); } - - /* Log in UI */ - var Login; - // deferred execution to avoid unnecessary asset loading - var loginReady = function (cb) { - if (Login) { - if (typeof(cb) === 'function') { cb(); } - return; - } - require([ - '/common/login.js', - ], function (_Login) { - Login = Login || _Login; - if (typeof(cb) === 'function') { cb(); } - }); - }; - - var $uname = $('#name').on('focus', loginReady); - - var $passwd = $('#password') - // background loading of login assets - .on('focus', loginReady) - // enter key while on password field clicks signup - .on('keyup', function (e) { - if (e.which !== 13) { return; } // enter - $('button.login').click(); - }); - - $('button.login').click(function () { - // setTimeout 100ms to remove the keyboard on mobile devices before the loading screen pops up - window.setTimeout(function () { - UI.addLoadingScreen({loadingText: Messages.login_hashing}); - // We need a setTimeout(cb, 0) otherwise the loading screen is only displayed after hashing the password - window.setTimeout(function () { - loginReady(function () { - var uname = $uname.val(); - var passwd = $passwd.val(); - Login.loginOrRegister(uname, passwd, false, function (err, result) { - if (!err) { - var proxy = result.proxy; - - // successful validation and user already exists - // set user hash in localStorage and redirect to drive - if (proxy && !proxy.login_name) { - proxy.login_name = result.userName; - } - - proxy.edPrivate = result.edPrivate; - proxy.edPublic = result.edPublic; - - Realtime.whenRealtimeSyncs(result.realtime, function () { - LocalStore.login(result.userHash, result.userName, function () { - document.location.href = '/drive/'; - }); - }); - return; - } - switch (err) { - case 'NO_SUCH_USER': - UI.removeLoadingScreen(function () { - UI.alert(Messages.login_noSuchUser); - }); - break; - case 'INVAL_USER': - UI.removeLoadingScreen(function () { - UI.alert(Messages.login_invalUser); - }); - break; - case 'INVAL_PASS': - UI.removeLoadingScreen(function () { - UI.alert(Messages.login_invalPass); - }); - break; - default: // UNHANDLED ERROR - UI.errorLoadingScreen(Messages.login_unhandledError); - } - }); - }); - }, 0); - }, 100); + $(window).click(function () { + $('.cp-dropdown-content').hide(); }); - /* End Log in UI */ - console.log("ready"); }); }); From 1fba82540ad8ec40ff09ba29e8da047da79abfe8 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 10 Jan 2018 10:00:11 +0100 Subject: [PATCH 27/56] remove example code from todo app --- .../example/assets/todomvc-app-css/index.css | 378 ------------------ .../example/assets/todomvc-common/base.css | 141 ------- .../example/assets/todomvc-common/base.js | 249 ------------ www/todo/example/index.html | 49 --- www/todo/example/js/app.js | 25 -- www/todo/example/js/controller.js | 270 ------------- www/todo/example/js/helpers.js | 52 --- www/todo/example/js/model.js | 120 ------ www/todo/example/js/store.js | 141 ------- www/todo/example/js/template.js | 114 ------ www/todo/example/js/view.js | 219 ---------- 11 files changed, 1758 deletions(-) delete mode 100644 www/todo/example/assets/todomvc-app-css/index.css delete mode 100644 www/todo/example/assets/todomvc-common/base.css delete mode 100644 www/todo/example/assets/todomvc-common/base.js delete mode 100644 www/todo/example/index.html delete mode 100644 www/todo/example/js/app.js delete mode 100644 www/todo/example/js/controller.js delete mode 100644 www/todo/example/js/helpers.js delete mode 100644 www/todo/example/js/model.js delete mode 100644 www/todo/example/js/store.js delete mode 100644 www/todo/example/js/template.js delete mode 100644 www/todo/example/js/view.js diff --git a/www/todo/example/assets/todomvc-app-css/index.css b/www/todo/example/assets/todomvc-app-css/index.css deleted file mode 100644 index e6e089cbf..000000000 --- a/www/todo/example/assets/todomvc-app-css/index.css +++ /dev/null @@ -1,378 +0,0 @@ -html, -body { - margin: 0; - padding: 0; -} - -button { - margin: 0; - padding: 0; - border: 0; - background: none; - font-size: 100%; - vertical-align: baseline; - font-family: inherit; - font-weight: inherit; - color: inherit; - -webkit-appearance: none; - appearance: none; - -webkit-font-smoothing: antialiased; - -moz-font-smoothing: antialiased; - font-smoothing: antialiased; -} - -body { - font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; - line-height: 1.4em; - background: #f5f5f5; - color: #4d4d4d; - min-width: 230px; - max-width: 550px; - margin: 0 auto; - -webkit-font-smoothing: antialiased; - -moz-font-smoothing: antialiased; - font-smoothing: antialiased; - font-weight: 300; -} - -button, -input[type="checkbox"] { - outline: none; -} - -.hidden { - display: none; -} - -.todoapp { - background: #fff; - margin: 130px 0 40px 0; - position: relative; - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), - 0 25px 50px 0 rgba(0, 0, 0, 0.1); -} - -.todoapp input::-webkit-input-placeholder { - font-style: italic; - font-weight: 300; - color: #e6e6e6; -} - -.todoapp input::-moz-placeholder { - font-style: italic; - font-weight: 300; - color: #e6e6e6; -} - -.todoapp input::input-placeholder { - font-style: italic; - font-weight: 300; - color: #e6e6e6; -} - -.todoapp h1 { - position: absolute; - top: -155px; - width: 100%; - font-size: 100px; - font-weight: 100; - text-align: center; - color: rgba(175, 47, 47, 0.15); - -webkit-text-rendering: optimizeLegibility; - -moz-text-rendering: optimizeLegibility; - text-rendering: optimizeLegibility; -} - -.new-todo, -.edit { - position: relative; - margin: 0; - width: 100%; - font-size: 24px; - font-family: inherit; - font-weight: inherit; - line-height: 1.4em; - border: 0; - outline: none; - color: inherit; - padding: 6px; - border: 1px solid #999; - box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); - box-sizing: border-box; - -webkit-font-smoothing: antialiased; - -moz-font-smoothing: antialiased; - font-smoothing: antialiased; -} - -.new-todo { - padding: 16px 16px 16px 60px; - border: none; - background: rgba(0, 0, 0, 0.003); - box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); -} - -.main { - position: relative; - z-index: 2; - border-top: 1px solid #e6e6e6; -} - -label[for='toggle-all'] { - display: none; -} - -.toggle-all { - position: absolute; - top: -55px; - left: -12px; - width: 60px; - height: 34px; - text-align: center; - border: none; /* Mobile Safari */ -} - -.toggle-all:before { - content: '❯'; - font-size: 22px; - color: #e6e6e6; - padding: 10px 27px 10px 27px; -} - -.toggle-all:checked:before { - color: #737373; -} - -.todo-list { - margin: 0; - padding: 0; - list-style: none; -} - -.todo-list li { - position: relative; - font-size: 24px; - border-bottom: 1px solid #ededed; -} - -.todo-list li:last-child { - border-bottom: none; -} - -.todo-list li.editing { - border-bottom: none; - padding: 0; -} - -.todo-list li.editing .edit { - display: block; - width: 506px; - padding: 13px 17px 12px 17px; - margin: 0 0 0 43px; -} - -.todo-list li.editing .view { - display: none; -} - -.todo-list li .toggle { - text-align: center; - width: 40px; - /* auto, since non-WebKit browsers doesn't support input styling */ - height: auto; - position: absolute; - top: 0; - bottom: 0; - margin: auto 0; - border: none; /* Mobile Safari */ - -webkit-appearance: none; - appearance: none; -} - -.todo-list li .toggle:after { - content: url('data:image/svg+xml;utf8,'); -} - -.todo-list li .toggle:checked:after { - content: url('data:image/svg+xml;utf8,'); -} - -.todo-list li label { - white-space: pre-line; - word-break: break-all; - padding: 15px 60px 15px 15px; - margin-left: 45px; - display: block; - line-height: 1.2; - transition: color 0.4s; -} - -.todo-list li.completed label { - color: #d9d9d9; - text-decoration: line-through; -} - -.todo-list li .destroy { - display: none; - position: absolute; - top: 0; - right: 10px; - bottom: 0; - width: 40px; - height: 40px; - margin: auto 0; - font-size: 30px; - color: #cc9a9a; - margin-bottom: 11px; - transition: color 0.2s ease-out; -} - -.todo-list li .destroy:hover { - color: #af5b5e; -} - -.todo-list li .destroy:after { - content: '×'; -} - -.todo-list li:hover .destroy { - display: block; -} - -.todo-list li .edit { - display: none; -} - -.todo-list li.editing:last-child { - margin-bottom: -1px; -} - -.footer { - color: #777; - padding: 10px 15px; - height: 20px; - text-align: center; - border-top: 1px solid #e6e6e6; -} - -.footer:before { - content: ''; - position: absolute; - right: 0; - bottom: 0; - left: 0; - height: 50px; - overflow: hidden; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), - 0 8px 0 -3px #f6f6f6, - 0 9px 1px -3px rgba(0, 0, 0, 0.2), - 0 16px 0 -6px #f6f6f6, - 0 17px 2px -6px rgba(0, 0, 0, 0.2); -} - -.todo-count { - float: left; - text-align: left; -} - -.todo-count strong { - font-weight: 300; -} - -.filters { - margin: 0; - padding: 0; - list-style: none; - position: absolute; - right: 0; - left: 0; -} - -.filters li { - display: inline; -} - -.filters li a { - color: inherit; - margin: 3px; - padding: 3px 7px; - text-decoration: none; - border: 1px solid transparent; - border-radius: 3px; -} - -.filters li a.selected, -.filters li a:hover { - border-color: rgba(175, 47, 47, 0.1); -} - -.filters li a.selected { - border-color: rgba(175, 47, 47, 0.2); -} - -.clear-completed, -html .clear-completed:active { - float: right; - position: relative; - line-height: 20px; - text-decoration: none; - cursor: pointer; - position: relative; -} - -.clear-completed:hover { - text-decoration: underline; -} - -.info { - margin: 65px auto 0; - color: #bfbfbf; - font-size: 10px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - text-align: center; -} - -.info p { - line-height: 1; -} - -.info a { - color: inherit; - text-decoration: none; - font-weight: 400; -} - -.info a:hover { - text-decoration: underline; -} - -/* - Hack to remove background from Mobile Safari. - Can't use it globally since it destroys checkboxes in Firefox -*/ -@media screen and (-webkit-min-device-pixel-ratio:0) { - .toggle-all, - .todo-list li .toggle { - background: none; - } - - .todo-list li .toggle { - height: 40px; - } - - .toggle-all { - -webkit-transform: rotate(90deg); - transform: rotate(90deg); - -webkit-appearance: none; - appearance: none; - } -} - -@media (max-width: 430px) { - .footer { - height: 50px; - } - - .filters { - bottom: 10px; - } -} diff --git a/www/todo/example/assets/todomvc-common/base.css b/www/todo/example/assets/todomvc-common/base.css deleted file mode 100644 index da65968a7..000000000 --- a/www/todo/example/assets/todomvc-common/base.css +++ /dev/null @@ -1,141 +0,0 @@ -hr { - margin: 20px 0; - border: 0; - border-top: 1px dashed #c5c5c5; - border-bottom: 1px dashed #f7f7f7; -} - -.learn a { - font-weight: normal; - text-decoration: none; - color: #b83f45; -} - -.learn a:hover { - text-decoration: underline; - color: #787e7e; -} - -.learn h3, -.learn h4, -.learn h5 { - margin: 10px 0; - font-weight: 500; - line-height: 1.2; - color: #000; -} - -.learn h3 { - font-size: 24px; -} - -.learn h4 { - font-size: 18px; -} - -.learn h5 { - margin-bottom: 0; - font-size: 14px; -} - -.learn ul { - padding: 0; - margin: 0 0 30px 25px; -} - -.learn li { - line-height: 20px; -} - -.learn p { - font-size: 15px; - font-weight: 300; - line-height: 1.3; - margin-top: 0; - margin-bottom: 0; -} - -#issue-count { - display: none; -} - -.quote { - border: none; - margin: 20px 0 60px 0; -} - -.quote p { - font-style: italic; -} - -.quote p:before { - content: '“'; - font-size: 50px; - opacity: .15; - position: absolute; - top: -20px; - left: 3px; -} - -.quote p:after { - content: '”'; - font-size: 50px; - opacity: .15; - position: absolute; - bottom: -42px; - right: 3px; -} - -.quote footer { - position: absolute; - bottom: -40px; - right: 0; -} - -.quote footer img { - border-radius: 3px; -} - -.quote footer a { - margin-left: 5px; - vertical-align: middle; -} - -.speech-bubble { - position: relative; - padding: 10px; - background: rgba(0, 0, 0, .04); - border-radius: 5px; -} - -.speech-bubble:after { - content: ''; - position: absolute; - top: 100%; - right: 30px; - border: 13px solid transparent; - border-top-color: rgba(0, 0, 0, .04); -} - -.learn-bar > .learn { - position: absolute; - width: 272px; - top: 8px; - left: -300px; - padding: 10px; - border-radius: 5px; - background-color: rgba(255, 255, 255, .6); - transition-property: left; - transition-duration: 500ms; -} - -@media (min-width: 899px) { - .learn-bar { - width: auto; - padding-left: 300px; - } - - .learn-bar > .learn { - left: 8px; - } -} diff --git a/www/todo/example/assets/todomvc-common/base.js b/www/todo/example/assets/todomvc-common/base.js deleted file mode 100644 index 3c6723f39..000000000 --- a/www/todo/example/assets/todomvc-common/base.js +++ /dev/null @@ -1,249 +0,0 @@ -/* global _ */ -(function () { - 'use strict'; - - /* jshint ignore:start */ - // Underscore's Template Module - // Courtesy of underscorejs.org - var _ = (function (_) { - _.defaults = function (object) { - if (!object) { - return object; - } - for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { - var iterable = arguments[argsIndex]; - if (iterable) { - for (var key in iterable) { - if (object[key] == null) { - object[key] = iterable[key]; - } - } - } - } - return object; - } - - // By default, Underscore uses ERB-style template delimiters, change the - // following template settings to use alternative delimiters. - _.templateSettings = { - evaluate : /<%([\s\S]+?)%>/g, - interpolate : /<%=([\s\S]+?)%>/g, - escape : /<%-([\s\S]+?)%>/g - }; - - // When customizing `templateSettings`, if you don't want to define an - // interpolation, evaluation or escaping regex, we need one that is - // guaranteed not to match. - var noMatch = /(.)^/; - - // Certain characters need to be escaped so that they can be put into a - // string literal. - var escapes = { - "'": "'", - '\\': '\\', - '\r': 'r', - '\n': 'n', - '\t': 't', - '\u2028': 'u2028', - '\u2029': 'u2029' - }; - - var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; - - // JavaScript micro-templating, similar to John Resig's implementation. - // Underscore templating handles arbitrary delimiters, preserves whitespace, - // and correctly escapes quotes within interpolated code. - _.template = function(text, data, settings) { - var render; - settings = _.defaults({}, settings, _.templateSettings); - - // Combine delimiters into one regular expression via alternation. - var matcher = new RegExp([ - (settings.escape || noMatch).source, - (settings.interpolate || noMatch).source, - (settings.evaluate || noMatch).source - ].join('|') + '|$', 'g'); - - // Compile the template source, escaping string literals appropriately. - var index = 0; - var source = "__p+='"; - text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { - source += text.slice(index, offset) - .replace(escaper, function(match) { return '\\' + escapes[match]; }); - - if (escape) { - source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; - } - if (interpolate) { - source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; - } - if (evaluate) { - source += "';\n" + evaluate + "\n__p+='"; - } - index = offset + match.length; - return match; - }); - source += "';\n"; - - // If a variable is not specified, place data values in local scope. - if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; - - source = "var __t,__p='',__j=Array.prototype.join," + - "print=function(){__p+=__j.call(arguments,'');};\n" + - source + "return __p;\n"; - - try { - render = new Function(settings.variable || 'obj', '_', source); - } catch (e) { - e.source = source; - throw e; - } - - if (data) return render(data, _); - var template = function(data) { - return render.call(this, data, _); - }; - - // Provide the compiled function source as a convenience for precompilation. - template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; - - return template; - }; - - return _; - })({}); - - if (location.hostname === 'todomvc.com') { - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); - ga('create', 'UA-31081062-1', 'auto'); - ga('send', 'pageview'); - } - /* jshint ignore:end */ - - function redirect() { - if (location.hostname === 'tastejs.github.io') { - location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); - } - } - - function findRoot() { - var base = location.href.indexOf('examples/'); - return location.href.substr(0, base); - } - - function getFile(file, callback) { - if (!location.host) { - return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); - } - - var xhr = new XMLHttpRequest(); - - xhr.open('GET', findRoot() + file, true); - xhr.send(); - - xhr.onload = function () { - if (xhr.status === 200 && callback) { - callback(xhr.responseText); - } - }; - } - - function Learn(learnJSON, config) { - if (!(this instanceof Learn)) { - return new Learn(learnJSON, config); - } - - var template, framework; - - if (typeof learnJSON !== 'object') { - try { - learnJSON = JSON.parse(learnJSON); - } catch (e) { - return; - } - } - - if (config) { - template = config.template; - framework = config.framework; - } - - if (!template && learnJSON.templates) { - template = learnJSON.templates.todomvc; - } - - if (!framework && document.querySelector('[data-framework]')) { - framework = document.querySelector('[data-framework]').dataset.framework; - } - - this.template = template; - - if (learnJSON.backend) { - this.frameworkJSON = learnJSON.backend; - this.frameworkJSON.issueLabel = framework; - this.append({ - backend: true - }); - } else if (learnJSON[framework]) { - this.frameworkJSON = learnJSON[framework]; - this.frameworkJSON.issueLabel = framework; - this.append(); - } - - this.fetchIssueCount(); - } - - Learn.prototype.append = function (opts) { - var aside = document.createElement('aside'); - aside.innerHTML = _.template(this.template, this.frameworkJSON); - aside.className = 'learn'; - - if (opts && opts.backend) { - // Remove demo link - var sourceLinks = aside.querySelector('.source-links'); - var heading = sourceLinks.firstElementChild; - var sourceLink = sourceLinks.lastElementChild; - // Correct link path - var href = sourceLink.getAttribute('href'); - sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); - sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; - } else { - // Localize demo links - var demoLinks = aside.querySelectorAll('.demo-link'); - Array.prototype.forEach.call(demoLinks, function (demoLink) { - if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { - demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); - } - }); - } - - document.body.className = (document.body.className + ' learn-bar').trim(); - document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); - }; - - Learn.prototype.fetchIssueCount = function () { - var issueLink = document.getElementById('issue-count-link'); - if (issueLink) { - var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, true); - xhr.onload = function (e) { - var parsedResponse = JSON.parse(e.target.responseText); - if (parsedResponse instanceof Array) { - var count = parsedResponse.length; - if (count !== 0) { - issueLink.innerHTML = 'This app has ' + count + ' open issues'; - document.getElementById('issue-count').style.display = 'inline'; - } - } - }; - xhr.send(); - } - }; - - redirect(); - getFile('learn.json', Learn); -})(); diff --git a/www/todo/example/index.html b/www/todo/example/index.html deleted file mode 100644 index 09070b71d..000000000 --- a/www/todo/example/index.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - Crypt Todo - - - - -
    -
    -

    todos

    - -
    -
    - - -
      -
      - -
      -
      - -
      - - - - - - - - - - - diff --git a/www/todo/example/js/app.js b/www/todo/example/js/app.js deleted file mode 100644 index c37e2e6a2..000000000 --- a/www/todo/example/js/app.js +++ /dev/null @@ -1,25 +0,0 @@ -/*global app, $on */ -(function () { - 'use strict'; - - /** - * Sets up a brand new Todo list. - * - * @param {string} name The name of your new to do list. - */ - function Todo(name) { - this.storage = new app.Store(name); - this.model = new app.Model(this.storage); - this.template = new app.Template(); - this.view = new app.View(this.template); - this.controller = new app.Controller(this.model, this.view); - } - - var todo = new Todo('todos-vanillajs'); - - function setView() { - todo.controller.setView(document.location.hash); - } - $on(window, 'load', setView); - $on(window, 'hashchange', setView); -})(); diff --git a/www/todo/example/js/controller.js b/www/todo/example/js/controller.js deleted file mode 100644 index 0a3fb1d83..000000000 --- a/www/todo/example/js/controller.js +++ /dev/null @@ -1,270 +0,0 @@ -(function (window) { - 'use strict'; - - /** - * Takes a model and view and acts as the controller between them - * - * @constructor - * @param {object} model The model instance - * @param {object} view The view instance - */ - function Controller(model, view) { - var self = this; - self.model = model; - self.view = view; - - self.view.bind('newTodo', function (title) { - self.addItem(title); - }); - - self.view.bind('itemEdit', function (item) { - self.editItem(item.id); - }); - - self.view.bind('itemEditDone', function (item) { - self.editItemSave(item.id, item.title); - }); - - self.view.bind('itemEditCancel', function (item) { - self.editItemCancel(item.id); - }); - - self.view.bind('itemRemove', function (item) { - self.removeItem(item.id); - }); - - self.view.bind('itemToggle', function (item) { - self.toggleComplete(item.id, item.completed); - }); - - self.view.bind('removeCompleted', function () { - self.removeCompletedItems(); - }); - - self.view.bind('toggleAll', function (status) { - self.toggleAll(status.completed); - }); - } - - /** - * Loads and initialises the view - * - * @param {string} '' | 'active' | 'completed' - */ - Controller.prototype.setView = function (locationHash) { - var route = locationHash.split('/')[1]; - var page = route || ''; - this._updateFilterState(page); - }; - - /** - * An event to fire on load. Will get all items and display them in the - * todo-list - */ - Controller.prototype.showAll = function () { - var self = this; - self.model.read(function (data) { - self.view.render('showEntries', data); - }); - }; - - /** - * Renders all active tasks - */ - Controller.prototype.showActive = function () { - var self = this; - self.model.read({ completed: false }, function (data) { - self.view.render('showEntries', data); - }); - }; - - /** - * Renders all completed tasks - */ - Controller.prototype.showCompleted = function () { - var self = this; - self.model.read({ completed: true }, function (data) { - self.view.render('showEntries', data); - }); - }; - - /** - * An event to fire whenever you want to add an item. Simply pass in the event - * object and it'll handle the DOM insertion and saving of the new item. - */ - Controller.prototype.addItem = function (title) { - var self = this; - - if (title.trim() === '') { - return; - } - - self.model.create(title, function () { - self.view.render('clearNewTodo'); - self._filter(true); - }); - }; - - /* - * Triggers the item editing mode. - */ - Controller.prototype.editItem = function (id) { - var self = this; - self.model.read(id, function (data) { - self.view.render('editItem', {id: id, title: data[0].title}); - }); - }; - - /* - * Finishes the item editing mode successfully. - */ - Controller.prototype.editItemSave = function (id, title) { - var self = this; - title = title.trim(); - - if (title.length !== 0) { - self.model.update(id, {title: title}, function () { - self.view.render('editItemDone', {id: id, title: title}); - }); - } else { - self.removeItem(id); - } - }; - - /* - * Cancels the item editing mode. - */ - Controller.prototype.editItemCancel = function (id) { - var self = this; - self.model.read(id, function (data) { - self.view.render('editItemDone', {id: id, title: data[0].title}); - }); - }; - - /** - * By giving it an ID it'll find the DOM element matching that ID, - * remove it from the DOM and also remove it from storage. - * - * @param {number} id The ID of the item to remove from the DOM and - * storage - */ - Controller.prototype.removeItem = function (id) { - var self = this; - self.model.remove(id, function () { - self.view.render('removeItem', id); - }); - - self._filter(); - }; - - /** - * Will remove all completed items from the DOM and storage. - */ - Controller.prototype.removeCompletedItems = function () { - var self = this; - self.model.read({ completed: true }, function (data) { - data.forEach(function (item) { - self.removeItem(item.id); - }); - }); - - self._filter(); - }; - - /** - * Give it an ID of a model and a checkbox and it will update the item - * in storage based on the checkbox's state. - * - * @param {number} id The ID of the element to complete or uncomplete - * @param {object} checkbox The checkbox to check the state of complete - * or not - * @param {boolean|undefined} silent Prevent re-filtering the todo items - */ - Controller.prototype.toggleComplete = function (id, completed, silent) { - var self = this; - self.model.update(id, { completed: completed }, function () { - self.view.render('elementComplete', { - id: id, - completed: completed - }); - }); - - if (!silent) { - self._filter(); - } - }; - - /** - * Will toggle ALL checkboxes' on/off state and completeness of models. - * Just pass in the event object. - */ - Controller.prototype.toggleAll = function (completed) { - var self = this; - self.model.read({ completed: !completed }, function (data) { - data.forEach(function (item) { - self.toggleComplete(item.id, completed, true); - }); - }); - - self._filter(); - }; - - /** - * Updates the pieces of the page which change depending on the remaining - * number of todos. - */ - Controller.prototype._updateCount = function () { - var self = this; - self.model.getCount(function (todos) { - self.view.render('updateElementCount', todos.active); - self.view.render('clearCompletedButton', { - completed: todos.completed, - visible: todos.completed > 0 - }); - - self.view.render('toggleAll', {checked: todos.completed === todos.total}); - self.view.render('contentBlockVisibility', {visible: todos.total > 0}); - }); - }; - - /** - * Re-filters the todo items, based on the active route. - * @param {boolean|undefined} force forces a re-painting of todo items. - */ - Controller.prototype._filter = function (force) { - var activeRoute = this._activeRoute.charAt(0).toUpperCase() + this._activeRoute.substr(1); - - // Update the elements on the page, which change with each completed todo - this._updateCount(); - - // If the last active route isn't "All", or we're switching routes, we - // re-create the todo item elements, calling: - // this.show[All|Active|Completed](); - if (force || this._lastActiveRoute !== 'All' || this._lastActiveRoute !== activeRoute) { - this['show' + activeRoute](); - } - - this._lastActiveRoute = activeRoute; - }; - - /** - * Simply updates the filter nav's selected states - */ - Controller.prototype._updateFilterState = function (currentPage) { - // Store a reference to the active route, allowing us to re-filter todo - // items as they are marked complete or incomplete. - this._activeRoute = currentPage; - - if (currentPage === '') { - this._activeRoute = 'All'; - } - - this._filter(); - - this.view.render('setFilter', currentPage); - }; - - // Export to window - window.app = window.app || {}; - window.app.Controller = Controller; -})(window); diff --git a/www/todo/example/js/helpers.js b/www/todo/example/js/helpers.js deleted file mode 100644 index d59a72eff..000000000 --- a/www/todo/example/js/helpers.js +++ /dev/null @@ -1,52 +0,0 @@ -/*global NodeList */ -(function (window) { - 'use strict'; - - // Get element(s) by CSS selector: - window.qs = function (selector, scope) { - return (scope || document).querySelector(selector); - }; - window.qsa = function (selector, scope) { - return (scope || document).querySelectorAll(selector); - }; - - // addEventListener wrapper: - window.$on = function (target, type, callback, useCapture) { - target.addEventListener(type, callback, !!useCapture); - }; - - // Attach a handler to event for all elements that match the selector, - // now or in the future, based on a root element - window.$delegate = function (target, selector, type, handler) { - function dispatchEvent(event) { - var targetElement = event.target; - var potentialElements = window.qsa(selector, target); - var hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0; - - if (hasMatch) { - handler.call(targetElement, event); - } - } - - // https://developer.mozilla.org/en-US/docs/Web/Events/blur - var useCapture = type === 'blur' || type === 'focus'; - - window.$on(target, type, dispatchEvent, useCapture); - }; - - // Find the element's parent with the given tag name: - // $parent(qs('a'), 'div'); - window.$parent = function (element, tagName) { - if (!element.parentNode) { - return; - } - if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase()) { - return element.parentNode; - } - return window.$parent(element.parentNode, tagName); - }; - - // Allow for looping on nodes by chaining: - // qsa('.foo').forEach(function () {}) - NodeList.prototype.forEach = Array.prototype.forEach; -})(window); diff --git a/www/todo/example/js/model.js b/www/todo/example/js/model.js deleted file mode 100644 index 9da766abc..000000000 --- a/www/todo/example/js/model.js +++ /dev/null @@ -1,120 +0,0 @@ -(function (window) { - 'use strict'; - - /** - * Creates a new Model instance and hooks up the storage. - * - * @constructor - * @param {object} storage A reference to the client side storage class - */ - function Model(storage) { - this.storage = storage; - } - - /** - * Creates a new todo model - * - * @param {string} [title] The title of the task - * @param {function} [callback] The callback to fire after the model is created - */ - Model.prototype.create = function (title, callback) { - title = title || ''; - callback = callback || function () {}; - - var newItem = { - title: title.trim(), - completed: false - }; - - this.storage.save(newItem, callback); - }; - - /** - * Finds and returns a model in storage. If no query is given it'll simply - * return everything. If you pass in a string or number it'll look that up as - * the ID of the model to find. Lastly, you can pass it an object to match - * against. - * - * @param {string|number|object} [query] A query to match models against - * @param {function} [callback] The callback to fire after the model is found - * - * @example - * model.read(1, func); // Will find the model with an ID of 1 - * model.read('1'); // Same as above - * //Below will find a model with foo equalling bar and hello equalling world. - * model.read({ foo: 'bar', hello: 'world' }); - */ - Model.prototype.read = function (query, callback) { - var queryType = typeof query; - callback = callback || function () {}; - - if (queryType === 'function') { - callback = query; - return this.storage.findAll(callback); - } else if (queryType === 'string' || queryType === 'number') { - query = parseInt(query, 10); - this.storage.find({ id: query }, callback); - } else { - this.storage.find(query, callback); - } - }; - - /** - * Updates a model by giving it an ID, data to update, and a callback to fire when - * the update is complete. - * - * @param {number} id The id of the model to update - * @param {object} data The properties to update and their new value - * @param {function} callback The callback to fire when the update is complete. - */ - Model.prototype.update = function (id, data, callback) { - this.storage.save(data, callback, id); - }; - - /** - * Removes a model from storage - * - * @param {number} id The ID of the model to remove - * @param {function} callback The callback to fire when the removal is complete. - */ - Model.prototype.remove = function (id, callback) { - this.storage.remove(id, callback); - }; - - /** - * WARNING: Will remove ALL data from storage. - * - * @param {function} callback The callback to fire when the storage is wiped. - */ - Model.prototype.removeAll = function (callback) { - this.storage.drop(callback); - }; - - /** - * Returns a count of all todos - */ - Model.prototype.getCount = function (callback) { - var todos = { - active: 0, - completed: 0, - total: 0 - }; - - this.storage.findAll(function (data) { - data.forEach(function (todo) { - if (todo.completed) { - todos.completed++; - } else { - todos.active++; - } - - todos.total++; - }); - callback(todos); - }); - }; - - // Export to window - window.app = window.app || {}; - window.app.Model = Model; -})(window); diff --git a/www/todo/example/js/store.js b/www/todo/example/js/store.js deleted file mode 100644 index ea09816c8..000000000 --- a/www/todo/example/js/store.js +++ /dev/null @@ -1,141 +0,0 @@ -/*jshint eqeqeq:false */ -(function (window) { - 'use strict'; - - /** - * Creates a new client side storage object and will create an empty - * collection if no collection already exists. - * - * @param {string} name The name of our DB we want to use - * @param {function} callback Our fake DB uses callbacks because in - * real life you probably would be making AJAX calls - */ - function Store(name, callback) { - callback = callback || function () {}; - - this._dbName = name; - - if (!localStorage[name]) { - var data = { - todos: [] - }; - - localStorage[name] = JSON.stringify(data); - } - - callback.call(this, JSON.parse(localStorage[name])); - } - - /** - * Finds items based on a query given as a JS object - * - * @param {object} query The query to match against (i.e. {foo: 'bar'}) - * @param {function} callback The callback to fire when the query has - * completed running - * - * @example - * db.find({foo: 'bar', hello: 'world'}, function (data) { - * // data will return any items that have foo: bar and - * // hello: world in their properties - * }); - */ - Store.prototype.find = function (query, callback) { - if (!callback) { - return; - } - - var todos = JSON.parse(localStorage[this._dbName]).todos; - - callback.call(this, todos.filter(function (todo) { - for (var q in query) { - if (query[q] !== todo[q]) { - return false; - } - } - return true; - })); - }; - - /** - * Will retrieve all data from the collection - * - * @param {function} callback The callback to fire upon retrieving data - */ - Store.prototype.findAll = function (callback) { - callback = callback || function () {}; - callback.call(this, JSON.parse(localStorage[this._dbName]).todos); - }; - - /** - * Will save the given data to the DB. If no item exists it will create a new - * item, otherwise it'll simply update an existing item's properties - * - * @param {object} updateData The data to save back into the DB - * @param {function} callback The callback to fire after saving - * @param {number} id An optional param to enter an ID of an item to update - */ - Store.prototype.save = function (updateData, callback, id) { - var data = JSON.parse(localStorage[this._dbName]); - var todos = data.todos; - - callback = callback || function () {}; - - // If an ID was actually given, find the item and update each property - if (id) { - for (var i = 0; i < todos.length; i++) { - if (todos[i].id === id) { - for (var key in updateData) { - todos[i][key] = updateData[key]; - } - break; - } - } - - localStorage[this._dbName] = JSON.stringify(data); - callback.call(this, todos); - } else { - // Generate an ID - updateData.id = new Date().getTime(); - - todos.push(updateData); - localStorage[this._dbName] = JSON.stringify(data); - callback.call(this, [updateData]); - } - }; - - /** - * Will remove an item from the Store based on its ID - * - * @param {number} id The ID of the item you want to remove - * @param {function} callback The callback to fire after saving - */ - Store.prototype.remove = function (id, callback) { - var data = JSON.parse(localStorage[this._dbName]); - var todos = data.todos; - - for (var i = 0; i < todos.length; i++) { - if (todos[i].id == id) { - todos.splice(i, 1); - break; - } - } - - localStorage[this._dbName] = JSON.stringify(data); - callback.call(this, todos); - }; - - /** - * Will drop all storage and start fresh - * - * @param {function} callback The callback to fire after dropping the data - */ - Store.prototype.drop = function (callback) { - var data = {todos: []}; - localStorage[this._dbName] = JSON.stringify(data); - callback.call(this, data.todos); - }; - - // Export to window - window.app = window.app || {}; - window.app.Store = Store; -})(window); diff --git a/www/todo/example/js/template.js b/www/todo/example/js/template.js deleted file mode 100644 index a5587731f..000000000 --- a/www/todo/example/js/template.js +++ /dev/null @@ -1,114 +0,0 @@ -/*jshint laxbreak:true */ -(function (window) { - 'use strict'; - - var htmlEscapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - '\'': ''', - '`': '`' - }; - - var escapeHtmlChar = function (chr) { - return htmlEscapes[chr]; - }; - - var reUnescapedHtml = /[&<>"'`]/g; - var reHasUnescapedHtml = new RegExp(reUnescapedHtml.source); - - var escape = function (string) { - return (string && reHasUnescapedHtml.test(string)) - ? string.replace(reUnescapedHtml, escapeHtmlChar) - : string; - }; - - /** - * Sets up defaults for all the Template methods such as a default template - * - * @constructor - */ - function Template() { - this.defaultTemplate - = '
    • ' - + '
      ' - + '' - + '' - + '' - + '
      ' - + '
    • '; - } - - /** - * Creates an
    • HTML string and returns it for placement in your app. - * - * NOTE: In real life you should be using a templating engine such as Mustache - * or Handlebars, however, this is a vanilla JS example. - * - * @param {object} data The object containing keys you want to find in the - * template to replace. - * @returns {string} HTML String of an
    • element - * - * @example - * view.show({ - * id: 1, - * title: "Hello World", - * completed: 0, - * }); - */ - Template.prototype.show = function (data) { - var i = 0, l = data.length; - var view = ''; - - for (; i < l; i++) { - var template = this.defaultTemplate; - var completed = ''; - var checked = ''; - - if (data[i].completed) { - completed = 'completed'; - checked = 'checked'; - } - - template = template.replace('{{id}}', data[i].id); - template = template.replace('{{title}}', escape(data[i].title)); - template = template.replace('{{completed}}', completed); - template = template.replace('{{checked}}', checked); - - view = view + template; - } - - return view; - }; - - /** - * Displays a counter of how many to dos are left to complete - * - * @param {number} activeTodos The number of active todos. - * @returns {string} String containing the count - */ - Template.prototype.itemCounter = function (activeTodos) { - var plural = activeTodos === 1 ? '' : 's'; - - return '' + activeTodos + ' item' + plural + ' left'; - }; - - /** - * Updates the text within the "Clear completed" button - * - * @param {[type]} completedTodos The number of completed todos. - * @returns {string} String containing the count - */ - Template.prototype.clearCompletedButton = function (completedTodos) { - if (completedTodos > 0) { - return 'Clear completed'; - } else { - return ''; - } - }; - - // Export to window - window.app = window.app || {}; - window.app.Template = Template; -})(window); diff --git a/www/todo/example/js/view.js b/www/todo/example/js/view.js deleted file mode 100644 index d9f59611e..000000000 --- a/www/todo/example/js/view.js +++ /dev/null @@ -1,219 +0,0 @@ -/*global qs, qsa, $on, $parent, $delegate */ - -(function (window) { - 'use strict'; - - /** - * View that abstracts away the browser's DOM completely. - * It has two simple entry points: - * - * - bind(eventName, handler) - * Takes a todo application event and registers the handler - * - render(command, parameterObject) - * Renders the given command with the options - */ - function View(template) { - this.template = template; - - this.ENTER_KEY = 13; - this.ESCAPE_KEY = 27; - - this.$todoList = qs('.todo-list'); - this.$todoItemCounter = qs('.todo-count'); - this.$clearCompleted = qs('.clear-completed'); - this.$main = qs('.main'); - this.$footer = qs('.footer'); - this.$toggleAll = qs('.toggle-all'); - this.$newTodo = qs('.new-todo'); - } - - View.prototype._removeItem = function (id) { - var elem = qs('[data-id="' + id + '"]'); - - if (elem) { - this.$todoList.removeChild(elem); - } - }; - - View.prototype._clearCompletedButton = function (completedCount, visible) { - this.$clearCompleted.innerHTML = this.template.clearCompletedButton(completedCount); - this.$clearCompleted.style.display = visible ? 'block' : 'none'; - }; - - View.prototype._setFilter = function (currentPage) { - qs('.filters .selected').className = ''; - qs('.filters [href="#/' + currentPage + '"]').className = 'selected'; - }; - - View.prototype._elementComplete = function (id, completed) { - var listItem = qs('[data-id="' + id + '"]'); - - if (!listItem) { - return; - } - - listItem.className = completed ? 'completed' : ''; - - // In case it was toggled from an event and not by clicking the checkbox - qs('input', listItem).checked = completed; - }; - - View.prototype._editItem = function (id, title) { - var listItem = qs('[data-id="' + id + '"]'); - - if (!listItem) { - return; - } - - listItem.className = listItem.className + ' editing'; - - var input = document.createElement('input'); - input.className = 'edit'; - - listItem.appendChild(input); - input.focus(); - input.value = title; - }; - - View.prototype._editItemDone = function (id, title) { - var listItem = qs('[data-id="' + id + '"]'); - - if (!listItem) { - return; - } - - var input = qs('input.edit', listItem); - listItem.removeChild(input); - - listItem.className = listItem.className.replace('editing', ''); - - qsa('label', listItem).forEach(function (label) { - label.textContent = title; - }); - }; - - View.prototype.render = function (viewCmd, parameter) { - var self = this; - var viewCommands = { - showEntries: function () { - self.$todoList.innerHTML = self.template.show(parameter); - }, - removeItem: function () { - self._removeItem(parameter); - }, - updateElementCount: function () { - self.$todoItemCounter.innerHTML = self.template.itemCounter(parameter); - }, - clearCompletedButton: function () { - self._clearCompletedButton(parameter.completed, parameter.visible); - }, - contentBlockVisibility: function () { - self.$main.style.display = self.$footer.style.display = parameter.visible ? 'block' : 'none'; - }, - toggleAll: function () { - self.$toggleAll.checked = parameter.checked; - }, - setFilter: function () { - self._setFilter(parameter); - }, - clearNewTodo: function () { - self.$newTodo.value = ''; - }, - elementComplete: function () { - self._elementComplete(parameter.id, parameter.completed); - }, - editItem: function () { - self._editItem(parameter.id, parameter.title); - }, - editItemDone: function () { - self._editItemDone(parameter.id, parameter.title); - } - }; - - viewCommands[viewCmd](); - }; - - View.prototype._itemId = function (element) { - var li = $parent(element, 'li'); - return parseInt(li.dataset.id, 10); - }; - - View.prototype._bindItemEditDone = function (handler) { - var self = this; - $delegate(self.$todoList, 'li .edit', 'blur', function () { - if (!this.dataset.iscanceled) { - handler({ - id: self._itemId(this), - title: this.value - }); - } - }); - - $delegate(self.$todoList, 'li .edit', 'keypress', function (event) { - if (event.keyCode === self.ENTER_KEY) { - // Remove the cursor from the input when you hit enter just like if it - // were a real form - this.blur(); - } - }); - }; - - View.prototype._bindItemEditCancel = function (handler) { - var self = this; - $delegate(self.$todoList, 'li .edit', 'keyup', function (event) { - if (event.keyCode === self.ESCAPE_KEY) { - this.dataset.iscanceled = true; - this.blur(); - - handler({id: self._itemId(this)}); - } - }); - }; - - View.prototype.bind = function (event, handler) { - var self = this; - if (event === 'newTodo') { - $on(self.$newTodo, 'change', function () { - handler(self.$newTodo.value); - }); - - } else if (event === 'removeCompleted') { - $on(self.$clearCompleted, 'click', function () { - handler(); - }); - - } else if (event === 'toggleAll') { - $on(self.$toggleAll, 'click', function () { - handler({completed: this.checked}); - }); - - } else if (event === 'itemEdit') { - $delegate(self.$todoList, 'li label', 'dblclick', function () { - handler({id: self._itemId(this)}); - }); - - } else if (event === 'itemRemove') { - $delegate(self.$todoList, '.destroy', 'click', function () { - handler({id: self._itemId(this)}); - }); - - } else if (event === 'itemToggle') { - $delegate(self.$todoList, '.toggle', 'click', function () { - handler({ - id: self._itemId(this), - completed: this.checked - }); - }); - - } else if (event === 'itemEditDone') { - self._bindItemEditDone(handler); - - } else if (event === 'itemEditCancel') { - self._bindItemEditCancel(handler); - } - }; - - // Export to window - window.app = window.app || {}; - window.app.View = View; -}(window)); From 5346afe51f42fc13d1e1b1a993a3989a7d45ee82 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 10 Jan 2018 10:14:49 +0100 Subject: [PATCH 28/56] step towards customizable login functionality --- {www/common => customize.dist}/credential.js | 0 {www/common => customize.dist}/login.js | 2 +- customize.dist/store.js | 97 -------------------- www/login/main.js | 2 +- www/register/main.js | 4 +- 5 files changed, 4 insertions(+), 101 deletions(-) rename {www/common => customize.dist}/credential.js (100%) rename {www/common => customize.dist}/login.js (99%) delete mode 100644 customize.dist/store.js diff --git a/www/common/credential.js b/customize.dist/credential.js similarity index 100% rename from www/common/credential.js rename to customize.dist/credential.js diff --git a/www/common/login.js b/customize.dist/login.js similarity index 99% rename from www/common/login.js rename to customize.dist/login.js index 847ca74a7..b7a9d4e84 100644 --- a/www/common/login.js +++ b/customize.dist/login.js @@ -4,7 +4,7 @@ define([ '/bower_components/chainpad-crypto/crypto.js', '/common/common-util.js', '/common/outer/network-config.js', - '/common/credential.js', + '/customize/credential.js', '/bower_components/chainpad/chainpad.dist.js', '/bower_components/tweetnacl/nacl-fast.min.js', diff --git a/customize.dist/store.js b/customize.dist/store.js deleted file mode 100644 index 5b82921ed..000000000 --- a/customize.dist/store.js +++ /dev/null @@ -1,97 +0,0 @@ -define(function () { - /* - This module uses localStorage, which is synchronous, but exposes an - asyncronous API. This is so that we can substitute other storage - methods. - - To override these methods, create another file at: - /customize/storage.js - */ - - var Store = {}; - - // Store uses nodebacks... - Store.set = function (key, val, cb) { - localStorage.setItem(key, JSON.stringify(val)); - cb(); - }; - - // implement in alternative store - Store.setBatch = function (map, cb) { - Object.keys(map).forEach(function (key) { - localStorage.setItem(key, JSON.stringify(map[key])); - }); - cb(void 0, map); - }; - - var safeGet = window.safeGet = function (key) { - var val = localStorage.getItem(key); - try { - return JSON.parse(val); - } catch (err) { - console.log(val); - console.error(err); - return val; - } - }; - - Store.get = function (key, cb) { - cb(void 0, safeGet(key)); - }; - - // implement in alternative store - Store.getBatch = function (keys, cb) { - var res = {}; - keys.forEach(function (key) { - res[key] = safeGet(key); - }); - cb(void 0, res); - }; - - Store.remove = function (key, cb) { - localStorage.removeItem(key); - cb(); - }; - - // implement in alternative store - Store.removeBatch = function (keys, cb) { - keys.forEach(function (key) { - localStorage.removeItem(key); - }); - cb(); - }; - - Store.keys = function (cb) { - cb(void 0, Object.keys(localStorage)); - }; - - Store.ready = function (f) { - if (typeof(f) === 'function') { - f(void 0, Store); - } - }; - - var changeHandlers = Store.changeHandlers = []; - - Store.change = function (f) { - if (typeof(f) !== 'function') { - throw new Error('[Store.change] callback must be a function'); - } - changeHandlers.push(f); - - if (changeHandlers.length === 1) { - // start listening for changes - window.addEventListener('storage', function (e) { - changeHandlers.forEach(function (f) { - f({ - key: e.key, - oldValue: e.oldValue, - newValue: e.newValue, - }); - }); - }); - } - }; - - return Store; -}); diff --git a/www/login/main.js b/www/login/main.js index ce3aa8835..24f0e905a 100644 --- a/www/login/main.js +++ b/www/login/main.js @@ -1,7 +1,7 @@ define([ 'jquery', '/common/cryptpad-common.js', - '/common/login.js', + '/customize/login.js', '/common/common-interface.js', '/common/common-realtime.js', '/common/common-feedback.js', diff --git a/www/register/main.js b/www/register/main.js index 5337dfa87..a6c31509f 100644 --- a/www/register/main.js +++ b/www/register/main.js @@ -1,9 +1,9 @@ define([ 'jquery', - '/common/login.js', + '/customize/login.js', '/common/cryptpad-common.js', '/common/test.js', - '/common/credential.js', // preloaded for login.js + '/customize/credential.js', // preloaded for login.js '/common/common-interface.js', '/common/common-util.js', '/common/common-realtime.js', From ca3697ae3aed79c73de1afca94d4bf8dc229cd3d Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 10 Jan 2018 11:49:16 +0100 Subject: [PATCH 29/56] implement extension point for share menu --- www/common/common-ui-elements.js | 6 ++++++ www/common/toolbar3.js | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 90e554d31..75c4fe31a 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -859,9 +859,14 @@ define([ if (typeof config !== "object" || !Array.isArray(config.options)) { return; } if (config.feedback && !config.common) { return void console.error("feedback in a dropdown requires sframe-common"); } + var isElement = function (o) { + return /HTML/.test(Object.prototype.toString.call(o)) && + typeof(o.tagName) === 'string'; + }; var allowedTags = ['a', 'p', 'hr']; var isValidOption = function (o) { if (typeof o !== "object") { return false; } + if (isElement(o)) { return true; } if (!o.tag || allowedTags.indexOf(o.tag) === -1) { return false; } return true; }; @@ -893,6 +898,7 @@ define([ config.options.forEach(function (o) { if (!isValidOption(o)) { return; } + if (isElement(o)) { return $innerblock.append($(o)); } $('<' + o.tag + '>', o.attributes || {}).html(o.content || '').appendTo($innerblock); }); diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 2d4dccd63..6a0016fcf 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -8,7 +8,8 @@ define([ '/common/common-feedback.js', '/customize/messages.js', '/common/clipboard.js', -], function ($, Config, ApiConfig, UIElements, UI, Hash, Feedback, Messages, Clipboard) { + '/common/hyperscript.js', +], function ($, Config, ApiConfig, UIElements, UI, Hash, Feedback, Messages, Clipboard, h) { var Common; var Bar = { @@ -496,6 +497,9 @@ define([ content: ' ' + Messages.getEmbedCode }); } + if (typeof(Config.customizeShareOptions) === 'function') { + Config.customizeShareOptions(hashes, options); + } var dropdownConfigShare = { text: $('
      ').append($shareIcon).html(), options: options, From 13b704d9f66f672c844da569fa33f7ddfc414723 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 10 Jan 2018 11:47:52 +0100 Subject: [PATCH 30/56] Fix undefined webchannel when uploading a file --- www/common/outer/async-store.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index e48452b6d..7d3d15dfd 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -525,11 +525,11 @@ define([ var h = p.hashData; var owners; - if (Store.channel && Util.base64ToHex(h.channel) === Store.channel.wc.id) { + if (Store.channel && Store.channel.wc && Util.base64ToHex(h.channel) === Store.channel.wc.id) { owners = Store.channel.data.owners || undefined; } var expire; - if (Store.channel && Util.base64ToHex(h.channel) === Store.channel.wc.id) { + if (Store.channel && Store.channel.wc && Util.base64ToHex(h.channel) === Store.channel.wc.id) { expire = +Store.channel.data.expire || undefined; } From c31b5aa6c03ecf9b958cf17616eb9a988ef734d3 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 10 Jan 2018 11:59:06 +0100 Subject: [PATCH 31/56] make extension point cover share menu for files --- www/common/toolbar3.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 6a0016fcf..76062a5c3 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -8,8 +8,7 @@ define([ '/common/common-feedback.js', '/customize/messages.js', '/common/clipboard.js', - '/common/hyperscript.js', -], function ($, Config, ApiConfig, UIElements, UI, Hash, Feedback, Messages, Clipboard, h) { +], function ($, Config, ApiConfig, UIElements, UI, Hash, Feedback, Messages, Clipboard) { var Common; var Bar = { @@ -498,7 +497,9 @@ define([ }); } if (typeof(Config.customizeShareOptions) === 'function') { - Config.customizeShareOptions(hashes, options); + Config.customizeShareOptions(hashes, options, { + type: 'DEFAULT', + }); } var dropdownConfigShare = { text: $('
      ').append($shareIcon).html(), @@ -586,6 +587,13 @@ define([ attributes: {title: Messages.fileEmbedTitle, 'class': 'cp-toolbar-share-file-embed'}, content: ' ' + Messages.getEmbedCode }); + + if (typeof(Config.customizeShareOptions) === 'function') { + Config.customizeShareOptions(hashes, options, { + type: 'FILE' + }); + } + var dropdownConfigShare = { text: $('
      ').append($shareIcon).html(), options: options, From 05ad38cfe101e1313d464d21ef308012be2579de Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 10 Jan 2018 12:52:18 +0100 Subject: [PATCH 32/56] add more config parameters to share menu extension --- www/common/toolbar3.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 76062a5c3..52b414194 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -499,6 +499,8 @@ define([ if (typeof(Config.customizeShareOptions) === 'function') { Config.customizeShareOptions(hashes, options, { type: 'DEFAULT', + origin: origin, + pathname: pathname }); } var dropdownConfigShare = { @@ -590,7 +592,9 @@ define([ if (typeof(Config.customizeShareOptions) === 'function') { Config.customizeShareOptions(hashes, options, { - type: 'FILE' + type: 'FILE', + origin: origin, + pathname: pathname }); } From 4808c8b3747ce07a8b94df0c95e5daac27fbc37f Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Thu, 11 Jan 2018 15:57:09 +0100 Subject: [PATCH 33/56] Fixed poll which was not working with automated testing --- www/poll/inner.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/poll/inner.js b/www/poll/inner.js index b08b66097..139ced8b4 100644 --- a/www/poll/inner.js +++ b/www/poll/inner.js @@ -967,7 +967,6 @@ define([ }); APP.$createRow = $('#cp-app-poll-create-option').click(function () { var uncommittedCopy = { content: getUncommitted('row') }; - console.log(uncommittedCopy); mergeUncommitted(proxy, uncommittedCopy, true); change(null, null, null, null, function() { var newId = APP.uncommitted.content.rowsOrder[0]; @@ -1111,7 +1110,7 @@ define([ }).nThen(function (waitFor) { $('#cp-app-poll-create-user').click(); setTimeout(waitFor()); - }).nThen(function (waitFor) { + }).nThen(function (/*waitFor*/) { passIfOk(t); }); }); @@ -1257,6 +1256,7 @@ define([ }).nThen(function (waitFor) { common.getSframeChannel().onReady(waitFor()); }).nThen(function (/* waitFor */) { + Test.registerInner(common.getSframeChannel()); var metadataMgr = common.getMetadataMgr(); APP.locked = APP.readOnly = metadataMgr.getPrivateData().readOnly; APP.loggedIn = common.isLoggedIn(); From ef480fea79c6e1b6eaa761af16c6a413a2f1c746 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 11 Jan 2018 16:02:05 +0100 Subject: [PATCH 34/56] Add a settings category for the pad creation screen --- .../src/less2/include/sidebar-layout.less | 3 + customize.dist/translations/messages.fr.js | 9 + customize.dist/translations/messages.js | 11 +- www/common/common-ui-elements.js | 33 +- www/common/sframe-app-framework.js | 11 +- www/common/sframe-common.js | 8 + www/drive/inner.js | 2 - www/settings/app-settings.less | 76 +- www/settings/inner.js | 655 ++++++++++++------ 9 files changed, 595 insertions(+), 213 deletions(-) diff --git a/customize.dist/src/less2/include/sidebar-layout.less b/customize.dist/src/less2/include/sidebar-layout.less index 89d23d946..ede03942e 100644 --- a/customize.dist/src/less2/include/sidebar-layout.less +++ b/customize.dist/src/less2/include/sidebar-layout.less @@ -48,6 +48,9 @@ display: block; color: @description-color; margin-bottom: 5px; + p { + margin-bottom: 0; + } } margin-bottom: 20px; } diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index ee8a1a4d2..c79e07708 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -487,6 +487,7 @@ define(function () { out.settings_cat_drive = "CryptDrive"; out.settings_cat_code = "Code"; out.settings_cat_pad = "Documents texte"; + out.settings_cat_creation = "Nouveau pad"; out.settings_title = "Préférences"; out.settings_save = "Sauver"; @@ -547,6 +548,14 @@ define(function () { out.settings_padWidthHint = "L'éditeur de documents texte occupe toute la largeur de l'écran disponible par défaut, ce qui peut rendre le texte difficile à lire. Vous pouvez ici réduire la largeur de l'éditeur."; out.settings_padWidthLabel = "Réduire la largeur de l'éditeur"; + out.settings_creationSkip = "Passer l'écran de création de pad"; + out.settings_creationSkipHint = "L'écran de création de pad offre de nouvelles options pour créer un pad, permettant d'avoir plus de contrôle et de sécurité concernant vos données. Toutefois, il peut ralentir votre travail en ajoutant une étape supplémentaire et donc, ici, vous avez la possibilité de choisir de passer cet écran et d'utiliser les paramètres par défaut choisis au-dessus."; + out.settings_creationSkipTrue = "Passer"; + out.settings_creationSkipFalse = "Afficher"; + + out.settings_templateSkip = "Passer la fenêtre de choix d'un modèle"; + out.settings_templateSkipHint = "Quand vous créez un nouveau pad, et si vous possédez des modèles pour ce type de pad, une fenêtre peut apparaître pour demander si vous souhaitez importer un modèle. Ici vous pouvez choisir de ne jamais montrer cette fenêtre et donc de ne jamais utiliser de modèle."; + out.upload_title = "Hébergement de fichiers"; out.upload_rename = "Souhaitez-vous renommer {0} avant son stockage en ligne ?
      " + "L'extension du fichier ({1}) sera ajoutée automatiquement. "+ diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 84dbdde0e..3261f4b3b 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -493,6 +493,7 @@ define(function () { out.settings_cat_drive = "CryptDrive"; out.settings_cat_code = "Code"; out.settings_cat_pad = "Rich text"; + out.settings_cat_creation = "New pad"; out.settings_title = "Settings"; out.settings_save = "Save"; @@ -553,6 +554,14 @@ define(function () { out.settings_padWidthHint = "Rich text pads use by default the maximum available width on your screen and it can be difficult to read. You can reduce the editor's width here."; out.settings_padWidthLabel = "Reduce the editor's width"; + out.settings_creationSkip = "Skip the pad creation screen"; + out.settings_creationSkipHint = "The pad creation screen offers new options to create a pad, providing you more control and security over your data. However, it may slow down your workflow by adding one additionnal step so, here, you have the option to skip this screen and use the default settings selected above."; + out.settings_creationSkipTrue = "Skip"; + out.settings_creationSkipFalse = "Display"; + + out.settings_templateSkip = "Skip the template selection modal"; + out.settings_templateSkipHint = "When you create a new empty pad, if you have stored templates for this type of pad, a modal appears to ask if you want to use a template. Here you can choose to never show this modal and so to never use a template."; + out.upload_title = "File upload"; out.upload_rename = "Do you want to rename {0} before uploading it to the server?
      " + "The file extension ({1}) will be added automatically. "+ @@ -819,7 +828,7 @@ define(function () { out.creation_expireHours = "Hours"; out.creation_expireDays = "Days"; out.creation_expireMonths = "Months"; - out.creation_expire1 = "By default, a pad stored by a registered users will never be removed from the server, unless it is requested by its owner."; + out.creation_expire1 = "By default, a pad stored by a registered user will never be removed from the server, unless it is requested by its owner."; out.creation_expire2 = "If you prefer, you can set a life time to make sure the pad will be permanently deleted from the server and unavailable after the specified date."; out.creation_createTitle = "Create a pad"; out.creation_createFromTemplate = "From template"; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index f49491ebf..2c1c38efb 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1317,7 +1317,29 @@ define([ }); }; - UIElements.getPadCreationScreen = function (common, cb) { + UIElements.setExpirationValue = function (val, $expire) { + if (val && typeof (val) === "number") { + $expire.find('#cp-creation-expire-true').attr('checked', true); + if (val % (3600 * 24 * 30) === 0) { + $expire.find('#cp-creation-expire-unit').val("month"); + $expire.find('#cp-creation-expire-val').val(val / (3600 * 24 * 30)); + return; + } + if (val % (3600 * 24) === 0) { + $expire.find('#cp-creation-expire-unit').val("day"); + $expire.find('#cp-creation-expire-val').val(val / (3600 * 24)); + return; + } + if (val % 3600 === 0) { + $expire.find('#cp-creation-expire-unit').val("hour"); + $expire.find('#cp-creation-expire-val').val(val / 3600); + return; + } + // if we're here, it means we don't have a valid value so we should check unlimited + $expire.find('#cp-creation-expire-false').attr('checked', true); + } + }; + UIElements.getPadCreationScreen = function (common, cfg, cb) { if (!common.isLoggedIn()) { return void cb(); } var sframeChan = common.getSframeChannel(); var metadataMgr = common.getMetadataMgr(); @@ -1372,6 +1394,11 @@ define([ ]); $creation.append(owned); + // If set to "open pad" or not set, check "open pad" + if (!cfg.owned && typeof cfg.owned !== "undefined") { + $creation.find('#cp-creation-owned-false').attr('checked', true); + } + // Life time var expire = h('div.cp-creation-expire', [ h('h2', [ @@ -1413,6 +1440,8 @@ define([ ]); $creation.append(expire); + UIElements.setExpirationValue(cfg.expire, $creation); + // Create the pad var create = function (template) { // Type of pad @@ -1430,7 +1459,7 @@ define([ expireVal = ($('#cp-creation-expire-val').val() || 0) * unit; } - sframeChan.query("Q_CREATE_PAD", { + common.createPad({ owned: ownedVal, expire: expireVal, template: template diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 04f924c14..b5fb42466 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -300,7 +300,9 @@ define([ } } - if (newPad && !AppConfig.displayCreationScreen) { + var skipTemp = Util.find(privateDat, ['settings', 'general', 'creation', 'noTemplate']); + var skipCreation = Util.find(privateDat, ['settings', 'general', 'creation', 'skip']); + if (newPad && (!skipTemp && skipCreation)) { common.openTemplatePicker(); } }; @@ -402,8 +404,11 @@ define([ }).nThen(function (waitFor) { Test.registerInner(common.getSframeChannel()); if (!AppConfig.displayCreationScreen) { return; } - if (common.getMetadataMgr().getPrivateData().isNewFile) { - common.getPadCreationScreen(waitFor()); + var priv = common.getMetadataMgr().getPrivateData(); + if (priv.isNewFile) { + var c = (priv.settings.general && priv.settings.general.creation) || {}; + if (c.skip) { return void common.createPad(c, waitFor()); } + common.getPadCreationScreen(c, waitFor()); } }).nThen(function (waitFor) { cpNfInner = common.startRealtime({ diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 634b0506a..103359da8 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -165,6 +165,14 @@ define([ }; // Store + funcs.createPad = function (cfg, cb) { + ctx.sframeChan.query("Q_CREATE_PAD", { + owned: cfg.owned, + expire: cfg.expire, + template: cfg.template + }, cb); + }; + funcs.sendAnonRpcMsg = function (msg, content, cb) { ctx.sframeChan.query('Q_ANON_RPC_MESSAGE', { msg: msg, diff --git a/www/drive/inner.js b/www/drive/inner.js index 381eb5e94..afa12fdec 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -2604,7 +2604,6 @@ define([ moveElements(pathsList, [TRASH], false, refresh); } else if ($(this).hasClass('cp-app-drive-context-deleteowned')) { - var pathsListD = []; var msgD = Messages.fm_deleteOwnedPads; UI.confirm(msgD, function(res) { $(window).focus(); @@ -2716,7 +2715,6 @@ define([ moveElements(pathsList, [TRASH], false, refresh); } else if ($(this).hasClass('cp-app-drive-context-deleteowned')) { - var pathsListD = []; var msgD = Messages.fm_deleteOwnedPads; UI.confirm(msgD, function(res) { $(window).focus(); diff --git a/www/settings/app-settings.less b/www/settings/app-settings.less index 072c37e91..b6fec0158 100644 --- a/www/settings/app-settings.less +++ b/www/settings/app-settings.less @@ -5,6 +5,7 @@ @import (once) '../../customize/src/less2/include/alertify.less'; @import (once) '../../customize/src/less2/include/sidebar-layout.less'; @import (once) "../../customize/src/less2/include/limit-bar.less"; +@import (once) "../../customize/src/less2/include/creation.less"; .toolbar_main( @bg-color: @colortheme_settings-bg, @@ -14,6 +15,7 @@ .alertify_main(); .sidebar-layout_main(); .limit-bar_main(); +.creation_main(); // body &.cp-app-settings { @@ -55,7 +57,7 @@ width: @sidebar_button-width; } } - .cp-settings-backup-drive { + .cp-settings-drive-backup { button { span.fa { margin-right: 5px; @@ -63,6 +65,78 @@ margin-right: 5px; } } + .cp-settings-creation-owned, .cp-settings-creation-expire, + .cp-settings-creation-skip, .cp-settings-creation-template { + input[type="radio"] { + display: none; + &:checked { + & + label { + font-weight: bold; + background-color: lighten(@colortheme_loading-bg, 20%); + cursor: default; + border: 1px solid #c1158e; + color: @colortheme_loading-color; + &:hover { + background-color: lighten(@colortheme_loading-bg, 20%); + } + } + } + } + input[type="radio"] + label { + .tools_unselectable(); + display: inline-flex; + align-items: center; + justify-content: center; + width: 200px; + height: 50px; + padding: 5px; + margin: 0 20px; + border: 1px solid black; + cursor: pointer; + &:hover { + background-color: lighten(@colortheme_loading-bg, 10%); + } + } + .fa { + display: none; + margin-left: 50px; + } + } + .cp-settings-creation-skipped { + display: none !important; // we have to override an inline style attribute + } + .cp-settings-creation-expire { + #cp-creation-expire-true { + display: none; + &:checked { + & + label { + height: 100px; + .cp-creation-expire-picker { + display: inline; + } + } + } + } + label[for="cp-creation-expire-true"] { + flex-wrap: wrap; + .cp-creation-expire-picker { + display: none; + } + input { + width: 70px; + } + select { + width: 100px; + } + input, select { + border: none; + height: 30px; + background: @colortheme_loading-bg; + color: @colortheme_loading-color; + border-radius: 3px; + } + } + } } } } diff --git a/www/settings/inner.js b/www/settings/inner.js index f2c4fb483..22d4b4be5 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -4,9 +4,11 @@ define([ '/bower_components/nthen/index.js', '/common/sframe-common.js', '/common/common-interface.js', + '/common/common-ui-elements.js', '/common/common-util.js', '/common/common-hash.js', '/customize/messages.js', + '/common/hyperscript.js', '/bower_components/file-saver/FileSaver.min.js', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', @@ -18,9 +20,11 @@ define([ nThen, SFCommon, UI, + UIElements, Util, Hash, - Messages + Messages, + h ) { var saveAs = window.saveAs; @@ -41,21 +45,31 @@ define([ 'cp-settings-thumbnails', 'cp-settings-userfeedback' ], + 'creation': [ + 'cp-settings-creation-owned', + 'cp-settings-creation-expire', + 'cp-settings-creation-skip', + 'cp-settings-creation-template' + ], 'drive': [ - 'cp-settings-backup-drive', - 'cp-settings-import-local-pads', - 'cp-settings-reset-drive' + 'cp-settings-drive-backup', + 'cp-settings-drive-import-local', + 'cp-settings-drive-reset' ], 'pad': [ 'cp-settings-pad-width', ], 'code': [ - 'cp-settings-indent-unit', - 'cp-settings-indent-type' + 'cp-settings-code-indent-unit', + 'cp-settings-code-indent-type' ] }; - var createInfoBlock = function () { + var create = {}; + + // Account settings + + create['info-block'] = function () { var $div = $('
      ', {'class': 'cp-settings-info-block'}); var $account = $('
      ', {'class': 'cp-sidebarlayout-element'}).appendTo($div); @@ -81,7 +95,7 @@ define([ }; // Create the block containing the display name field - var createDisplayNameInput = function () { + create['displayname'] = function () { var $div = $('
      ', {'class': 'cp-settings-displayname cp-sidebarlayout-element'}); $('