diff --git a/customize.dist/application_config.js b/customize.dist/application_config.js index 1ac8a8bab..8f190a9ad 100644 --- a/customize.dist/application_config.js +++ b/customize.dist/application_config.js @@ -32,6 +32,7 @@ define(function() { '#FF00C0', // hot pink '#800080', // purple ]; + config.enableTemplates = true; return config; }); diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index edc717b57..6d8b3899a 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -75,6 +75,11 @@ define(function () { out.newButton = 'Nouveau'; out.newButtonTitle = 'Créer un nouveau pad'; + out.saveTemplateButton = "Sauver en tant que modèle"; + out.saveTemplatePrompt = "Choisir un titre pour ce modèle"; + out.templateSaved = "Modèle enregistré !"; + out.selectTemplate = "Sélectionner un modèle ou appuyer sur Échap"; + out.presentButtonTitle = "Entrer en mode présentation"; out.presentSuccess = 'Appuyer sur Échap pour quitter le mode présentation'; @@ -146,8 +151,11 @@ define(function () { // Canvas out.canvas_clear = "Nettoyer"; + out.canvas_delete = "Supprimer la sélection"; out.canvas_disable = "Désactiver le dessin"; out.canvas_enable = "Activer le dessin"; + out.canvas_width = "Épaisseur"; + out.canvas_opacity = "Opacité"; // File manager diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 2af12e044..66f771033 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -77,6 +77,11 @@ define(function () { out.newButton = 'New'; out.newButtonTitle = 'Create a new pad'; + out.saveTemplateButton = "Save as template"; + out.saveTemplatePrompt = "Choose a title for the template"; + out.templateSaved = "Template saved!"; + out.selectTemplate = "Select a template or press escape"; + out.presentButtonTitle = "Enter presentation mode"; out.presentSuccess = 'Hit ESC to exit presentation mode'; @@ -148,8 +153,11 @@ define(function () { // Canvas out.canvas_clear = "Clear"; + out.canvas_delete = "Delete selection"; out.canvas_disable = "Disable draw"; out.canvas_enable = "Enable draw"; + out.canvas_width = "Width"; + out.canvas_opacity = "Opacity"; // File manager diff --git a/www/code/main.js b/www/code/main.js index 8127cb566..227c1e1fa 100644 --- a/www/code/main.js +++ b/www/code/main.js @@ -7,13 +7,14 @@ define([ 'json.sortify', '/bower_components/chainpad-json-validator/json-ot.js', '/common/cryptpad-common.js', + '/common/cryptget.js', '/common/modes.js', '/common/themes.js', '/common/visible.js', '/common/notify.js', '/bower_components/file-saver/FileSaver.min.js', '/bower_components/jquery/dist/jquery.min.js', -], function (Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Modes, Themes, Visible, Notify) { +], function (Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Modes, Themes, Visible, Notify) { var $ = window.jQuery; var saveAs = window.saveAs; var Messages = Cryptpad.Messages; @@ -401,6 +402,17 @@ define([ editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys); } + /* save as template */ + if (!Cryptpad.isTemplate(window.location.href)) { + var templateObj = { + rt: info.realtime, + Crypt: Cryptget, + getTitle: function () { return document.title; } + }; + var $templateButton = Cryptpad.createButton('template', true, templateObj); + $rightside.append($templateButton); + } + /* add an export button */ var $export = Cryptpad.createButton('export', true, {}, exportText); $rightside.append($export); @@ -532,6 +544,8 @@ define([ var userDoc = module.realtime.getUserDoc(); + var isNew = false; + if (userDoc === "" || userDoc === "{}") { isNew = true; } var newDoc = ""; if(userDoc !== "") { @@ -599,6 +613,9 @@ define([ onLocal(); module.$userNameButton.click(); } + if (isNew) { + Cryptpad.selectTemplate('code', info.realtime, Cryptget); + } }); }; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index daeb00eee..770326430 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -498,7 +498,7 @@ load pinpad dynamically only after you know that it will be needed */ }; var makePad = function (href, title) { - var now = ''+new Date(); + var now = +new Date(); return { href: href, atime: now, @@ -557,6 +557,41 @@ load pinpad dynamically only after you know that it will be needed */ getStore().addTemplate(href); }; + var isTemplate = common.isTemplate = function (href) { + var rhref = getRelativeHref(href); + var templates = listTemplates(); + return templates.some(function (t) { + return t.href === rhref; + }); + }; + var selectTemplate = common.selectTemplate = function (type, rt, Crypt) { + if (!AppConfig.enableTemplates) { return; } + var temps = listTemplates(type); + if (temps.length === 0) { return; } + var $content = $('
', {id:"selectTemplate"}).appendTo($content); + Cryptpad.alert($content.html(), null, true); + var $p = $('#selectTemplate'); + temps.forEach(function (t, i) { + $('', {href: t.href, title: t.title}).text(t.title).click(function (e) { + e.preventDefault(); + var parsed = parsePadUrl(t.href); + if(!parsed) { throw new Error("Cannot get template hash"); } + common.addLoadingScreen(null, true); + Crypt.get(parsed.hash, function (err, val) { + if (err) { throw new Error(err); } + var p = parsePadUrl(window.location.href); + Crypt.put(p.hash, val, function (e) { + common.findOKButton().click(); + common.removeLoadingScreen(); + }); + }); + }).appendTo($p); + if (i !== temps.length) { $('').appendTo($p); } + }); + common.findOKButton().text(Messages.cancelButton); + }; // STORAGE /* fetch and migrate your pad history from localStorage */ @@ -1118,6 +1153,56 @@ load pinpad dynamically only after you know that it will be needed */ })); } break; + case 'template': + if (!AppConfig.enableTemplates) { return; } + button = $('', { + title: Messages.saveTemplateButton, + }).append($('', {'class':'fa fa-bookmark', style: 'font:'+size+' FontAwesome'})); + if (data.rt && data.Crypt) { + button.click(function () { + var title = data.getTitle() || document.title; + var todo = function (val) { + if (typeof(val) !== "string") { return; } + var toSave = data.rt.getUserDoc(); + if (val.trim()) { + val = val.trim(); + title = val; + try { + var parsed = JSON.parse(toSave); + var meta; + if (Array.isArray(parsed) && typeof(parsed[3]) === "object") { + meta = parsed[3].metadata; // pad + } else if (parsed.info) { + meta = parsed.info; // poll + } else { + meta = parsed.metadata; + } + if (typeof(meta) === "object") { + meta.title = val; + meta.defaultTitle = val; + delete meta.users; + } + toSave = JSON.stringify(parsed); + } catch(e) { + console.error("Parse error while setting the title", e); + } + } + var p = parsePadUrl(window.location.href); + if (!p.type) { return; } + var hash = createRandomHash(); + var href = '/' + p.type + '/#' + hash; + data.Crypt.put(hash, toSave, function (e) { + if (e) { throw new Error(e); } + common.addTemplate(makePad(href, title)); + whenRealtimeSyncs(getStore().getProxy().info.realtime, function () { + common.alert(Messages.templateSaved); + }); + }); + }; + common.prompt(Messages.saveTemplatePrompt, title || document.title, todo); + }); + } + break; case 'forget': button = $('', { id: 'cryptpad-forget', diff --git a/www/common/fileObject.js b/www/common/fileObject.js index 22840cb8c..537ada142 100644 --- a/www/common/fileObject.js +++ b/www/common/fileObject.js @@ -999,7 +999,10 @@ define([ // first, we must add it to FILES_DATA, so the input has to be an fileDAta object var addTemplate = exp.addTemplate = function (fileData) { if (workgroup) { return; } - if (typeof fileData !== "object" || !fileData.href || !fileData.title) { return; } + if (typeof fileData !== "object" || !fileData.href || !fileData.title) { + console.error("filedata object expected to add a new template"); + return; + } var href = fileData.href; var test = files[FILES_DATA].some(function (o) { diff --git a/www/common/toolbar.js b/www/common/toolbar.js index af84108f4..75a3e4b55 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -242,20 +242,26 @@ define([ return $userlist[0]; }; - // TODO deduplicate users by uid var getOtherUsers = function(myUserName, userList, userData) { - var i = 0; - var list = []; - userList.forEach(function(user) { - if(user !== myUserName) { - var data = (userData) ? (userData[user] || null) : null; - var userName = (data) ? data.name : null; - if(userName) { - list.push(userName); - } - } - }); - return list; + var i = 0; // duplicates counter + var list = []; + var myUid = userData[myUserName] ? userData[myUserName].uid : undefined; + var uids = []; + userList.forEach(function(user) { + if(user !== myUserName) { + var data = (userData) ? (userData[user] || null) : null; + var userName = (data) ? data.name : null; + var userId = (data) ? data.uid : null; + if (userName && uids.indexOf(userId) === -1 && (!myUid || userId !== myUid)) { + uids.push(userId); + list.push(userName); + } else { i++; } + } + }); + return { + list: list, + duplicates: i + }; }; var arrayIntersect = function(a, b) { @@ -293,11 +299,13 @@ define([ // may contain data about users that have already left the channel. userList = readOnly === -1 ? userList : arrayIntersect(userList, Object.keys(userData)); - var numberOfEditUsers = userList.length; - var numberOfViewUsers = numberOfUsers - numberOfEditUsers; - // Names of editing users - var editUsersNames = getOtherUsers(myUserName, userList, userData); + var others = getOtherUsers(myUserName, userList, userData); + var editUsersNames = others.list; + var duplicates = others.duplicates; + + var numberOfEditUsers = userList.length - duplicates; + var numberOfViewUsers = numberOfUsers - numberOfEditUsers - duplicates; // Number of anonymous editing users var anonymous = numberOfEditUsers - editUsersNames.length; @@ -307,21 +315,19 @@ define([ var $editUsers = $userButtons.find('.' + USERLIST_CLS); $editUsers.html('').append($usersTitle); - var editUsersList = ''; var $editUsersList = $(''); - if (readOnly !== 1) { + if (readOnly !== 1) { // Yourself - edit $editUsers.append('' + Messages.yourself + ''); anonymous--; } - if (editUsersNames.length > 0) { - $editUsersList.text(editUsersNames.join('\n')); // .text() to avoid XSS - $editUsers.append($editUsersList); - } - if (anonymous > 0) { + // Editors + $editUsersList.text(editUsersNames.join('\n')); // .text() to avoid XSS + $editUsers.append($editUsersList); + if (anonymous > 0) { // Anonymous editors var text = anonymous === 1 ? Messages.anonymousUser : Messages.anonymousUsers; $editUsers.append('' + anonymous + ' ' + text + ''); } - if (numberOfViewUsers > 0) { + if (numberOfViewUsers > 0) { // Viewers var viewText = ''; if (numberOfEditUsers > 0) { $editUsers.append(''); diff --git a/www/drive/main.js b/www/drive/main.js index 220ee5c11..474ea4b06 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -220,6 +220,7 @@ define([ // Categories dislayed in the menu // _WORKGROUP_ : do not display unsorted var displayedCategories = [ROOT, UNSORTED, TRASH, SEARCH]; + if (AppConfig.enableTemplates) { displayedCategories.push(TEMPLATE); } if (isWorkgroup()) { displayedCategories = [ROOT, TRASH, SEARCH]; } var lastSelectTime; @@ -376,6 +377,10 @@ define([ paths.forEach(function (p, i) { var path = p.path; var $element = p.element; + if (path.length === 1) { + hide.push($menu.find('a.rename')); + hide.push($menu.find('a.delete')); + } if (!APP.editable) { hide.push($menu.find('a.editable')); } @@ -1709,6 +1714,7 @@ define([ (isRootOpened ? $folderOpenedIcon : $folderIcon); var $rootElement = createTreeElement(ROOT_NAME, $rootIcon.clone(), [ROOT], false, true, false, isRootOpened); $rootElement.addClass('root'); + $rootElement.find('>.element-row').contextmenu(openDirectoryContextMenu); var $root = $('').append($rootElement).appendTo($container); $container = $rootElement; } else if (filesOp.isFolderEmpty(root)) { return; } diff --git a/www/pad/main.js b/www/pad/main.js index ef8443685..1170e3008 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -10,6 +10,7 @@ define([ 'json.sortify', '/bower_components/textpatcher/TextPatcher.js', '/common/cryptpad-common.js', + '/common/cryptget.js', '/common/visible.js', '/common/notify.js', '/pad/links.js', @@ -17,7 +18,7 @@ define([ '/bower_components/diff-dom/diffDOM.js', '/bower_components/jquery/dist/jquery.min.js', ], function (Crypto, realtimeInput, Hyperjson, - Toolbar, Cursor, JsonOT, TypingTest, JSONSortify, TextPatcher, Cryptpad, + Toolbar, Cursor, JsonOT, TypingTest, JSONSortify, TextPatcher, Cryptpad, Cryptget, Visible, Notify, Links) { var $ = window.jQuery; var saveAs = window.saveAs; @@ -616,6 +617,17 @@ define([ $rightside.append($collapse); } + /* save as template */ + if (!Cryptpad.isTemplate(window.location.href)) { + var templateObj = { + rt: info.realtime, + Crypt: Cryptget, + getTitle: function () { return document.title; } + }; + var $templateButton = Cryptpad.createButton('template', true, templateObj); + $rightside.append($templateButton); + } + /* add an export button */ var $export = Cryptpad.createButton('export', true, {}, exportFile); $rightside.append($export); @@ -723,6 +735,7 @@ define([ editor.focus(); if (newPad) { + Cryptpad.selectTemplate('pad', info.realtime, Cryptget); cursor.setToEnd(); } else { cursor.setToStart(); diff --git a/www/poll/main.js b/www/poll/main.js index 23f93acb7..7225bee7f 100644 --- a/www/poll/main.js +++ b/www/poll/main.js @@ -3,6 +3,7 @@ define([ '/bower_components/chainpad-listmap/chainpad-listmap.js', '/bower_components/chainpad-crypto/crypto.js', '/common/cryptpad-common.js', + '/common/cryptget.js', '/bower_components/hyperjson/hyperjson.js', 'render.js', '/common/toolbar.js', @@ -10,7 +11,7 @@ define([ '/common/notify.js', '/bower_components/file-saver/FileSaver.min.js', '/bower_components/jquery/dist/jquery.min.js', -], function (TextPatcher, Listmap, Crypto, Cryptpad, Hyperjson, Renderer, Toolbar, Visible, Notify) { +], function (TextPatcher, Listmap, Crypto, Cryptpad, Cryptget, Hyperjson, Renderer, Toolbar, Visible, Notify) { var $ = window.jQuery; var Messages = Cryptpad.Messages; @@ -525,6 +526,11 @@ define([ debug('userid: %s', userid); var proxy = APP.proxy; + + var isNew = false; + var userDoc = JSON.stringify(proxy); + if (userDoc === "" || userDoc === "{}") { isNew = true; } + var uncommitted = APP.uncommitted = {}; prepareProxy(proxy, copyObject(Render.Example)); prepareProxy(uncommitted, copyObject(Render.Example)); @@ -676,6 +682,9 @@ define([ addToUserData(myData); APP.$userNameButton.click(); } + if (isNew) { + Cryptpad.selectTemplate('poll', info.realtime, Cryptget); + } }); }; @@ -747,6 +756,17 @@ define([ // set the hash if (!readOnly) { Cryptpad.replaceHash(editHash); } + /* save as template */ + if (!Cryptpad.isTemplate(window.location.href)) { + var templateObj = { + rt: info.realtime, + Crypt: Cryptget, + getTitle: function () { return document.title; } + }; + var $templateButton = Cryptpad.createButton('template', true, templateObj); + $rightside.append($templateButton); + } + Cryptpad.onDisplayNameChanged(setName); Cryptpad.getPadTitle(function (err, title) { diff --git a/www/slide/main.js b/www/slide/main.js index 45b80e1a9..25c367a85 100644 --- a/www/slide/main.js +++ b/www/slide/main.js @@ -7,6 +7,7 @@ define([ 'json.sortify', '/bower_components/chainpad-json-validator/json-ot.js', '/common/cryptpad-common.js', + '/common/cryptget.js', '/common/modes.js', '/common/themes.js', '/common/visible.js', @@ -14,7 +15,7 @@ define([ '/slide/slide.js', '/bower_components/file-saver/FileSaver.min.js', '/bower_components/jquery/dist/jquery.min.js', -], function (Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Modes, Themes, Visible, Notify, Slide) { +], function (Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Modes, Themes, Visible, Notify, Slide) { var $ = window.jQuery; var saveAs = window.saveAs; @@ -532,6 +533,17 @@ define([ editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys); } + /* save as template */ + if (!Cryptpad.isTemplate(window.location.href)) { + var templateObj = { + rt: info.realtime, + Crypt: Cryptget, + getTitle: function () { return document.title; } + }; + var $templateButton = Cryptpad.createButton('template', true, templateObj); + $rightside.append($templateButton); + } + /* add an export button */ var $export = Cryptpad.createButton('export', true, {}, exportText); $rightside.append($export); @@ -720,6 +732,9 @@ define([ var userDoc = module.realtime.getUserDoc(); + var isNew = false; + if (userDoc === "" || userDoc === "{}") { isNew = true; } + var newDoc = ""; if(userDoc !== "") { var hjson = JSON.parse(userDoc); @@ -792,6 +807,9 @@ define([ onLocal(); module.$userNameButton.click(); } + if (isNew) { + Cryptpad.selectTemplate('slide', info.realtime, Cryptget); + } }); }; diff --git a/www/whiteboard/index.html b/www/whiteboard/index.html index 746bfccfc..d9496c344 100644 --- a/www/whiteboard/index.html +++ b/www/whiteboard/index.html @@ -23,7 +23,9 @@ Clear - 5 + + 5 + 1 diff --git a/www/whiteboard/main.js b/www/whiteboard/main.js index 4b843dd3f..d5523104c 100644 --- a/www/whiteboard/main.js +++ b/www/whiteboard/main.js @@ -11,13 +11,15 @@ define([ 'json.sortify', '/bower_components/chainpad-json-validator/json-ot.js', '/common/cryptpad-common.js', + '/common/cryptget.js', + '/whiteboard/colors.js', '/common/visible.js', '/common/notify.js', '/customize/application_config.js', '/bower_components/secure-fabric.js/dist/fabric.min.js', '/bower_components/jquery/dist/jquery.min.js', '/bower_components/file-saver/FileSaver.min.js', -], function (Config, Realtime, Crypto, Toolbar, TextPatcher, JSONSortify, JsonOT, Cryptpad, Visible, Notify, AppConfig) { +], function (Config, Realtime, Crypto, Toolbar, TextPatcher, JSONSortify, JsonOT, Cryptpad, Cryptget, Colors, Visible, Notify, AppConfig) { var saveAs = window.saveAs; var Messages = Cryptpad.Messages; @@ -47,15 +49,23 @@ define([ var $pickers = $('#pickers'); var $colors = $('#colors'); var $cursors = $('#cursors'); + var $deleteButton = $('#delete'); + + var brush = { + color: '#000000', + opacity: 1 + }; var $toggle = $('#toggleDraw'); var $width = $('#width'); var $widthLabel = $('label[for="width"]'); - + var $opacity = $('#opacity'); + var $opacityLabel = $('label[for="opacity"]'); +window.canvas = canvas; var createCursor = function () { var w = canvas.freeDrawingBrush.width; var c = canvas.freeDrawingBrush.color; - var size = w > 30 ? w : w+30; + var size = w > 30 ? w+2 : w+32; $cursors.html(''); var $ccanvas = $cursors.find('canvas'); var ccanvas = $ccanvas[0]; @@ -69,7 +79,9 @@ define([ ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false); ctx.fillStyle = c; ctx.fill(); - ctx.lineWidth = 0; + ctx.lineWidth = 1; + ctx.strokeStyle = brush.color; + ctx.stroke(); ctx.beginPath(); ctx.moveTo(size/2, 0); ctx.lineTo(size/2, 10); @@ -79,9 +91,6 @@ define([ ctx.strokeStyle = '#000000'; ctx.stroke(); - //context.lineWidth = w/2; - //context.strokeStyle = '#000000'; - //context.stroke(); var img = ccanvas.toDataURL("image/png"); var $img = $('', { @@ -102,6 +111,17 @@ define([ $width.on('change', updateBrushWidth); + var updateBrushOpacity = function () { + var val = $opacity.val(); + brush.opacity = Number(val); + canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity); + $opacityLabel.text(val); + createCursor(); + }; + updateBrushOpacity(); + + $opacity.on('change', updateBrushOpacity); + var pickColor = function (current, cb) { var $picker = $('', { type: 'color', @@ -120,21 +140,15 @@ define([ }; var setColor = function (c) { - canvas.freeDrawingBrush.color = c; + c = Colors.rgb2hex(c); + brush.color = c; + canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity); module.$color.css({ 'color': c, }); createCursor(); }; - var rgb2hex = function (rgb) { - if (rgb.indexOf('#') === 0) { return rgb; } - rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); - var hex = function (x) { - return ("0" + parseInt(x).toString(16)).slice(-2); - }; - return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]); - }; var palette = AppConfig.whiteboardPalette || [ 'red', 'blue', 'green', 'white', 'black', 'purple', @@ -151,9 +165,29 @@ define([ module.draw = !module.draw; canvas.isDrawingMode = module.draw; $toggle.text(module.draw ? Messages.canvas_disable : Messages.canvas_enable); + if (module.draw) { $deleteButton.hide(); } + else { $deleteButton.show(); } }; $toggle.click(toggleDrawMode); + var deleteSelection = function () { + if (canvas.getActiveObject()) { + canvas.getActiveObject().remove(); + } + if (canvas.getActiveGroup()) { + canvas.getActiveGroup()._objects.forEach(function (el) { + el.remove(); + }); + canvas.discardActiveGroup(); + } + canvas.renderAll(); + onLocal(); + }; + $deleteButton.click(deleteSelection); + $(window).on('keyup', function (e) { + if (e.which === 46) { deleteSelection (); } + }); + var setEditable = function (bool) { if (readOnly && bool) { return; } if (bool) { $controls.show(); } @@ -233,12 +267,12 @@ define([ 'background-color': color, }) .click(function () { - var c = rgb2hex($color.css('background-color')); + var c = Colors.rgb2hex($color.css('background-color')); setColor(c); }) .on('dblclick', function (e) { e.preventDefault(); - pickColor(rgb2hex($color.css('background-color')), function (c) { + pickColor(Colors.rgb2hex($color.css('background-color')), function (c) { $color.css({ 'background-color': c, }); @@ -327,6 +361,17 @@ define([ var $rightside = $bar.find('.' + Toolbar.constants.rightside); module.$userNameButton = $($bar.find('.' + Toolbar.constants.changeUsername)); + /* save as template */ + if (!Cryptpad.isTemplate(window.location.href)) { + var templateObj = { + rt: info.realtime, + Crypt: Cryptget, + getTitle: function () { return document.title; } + }; + var $templateButton = Cryptpad.createButton('template', true, templateObj); + $rightside.append($templateButton); + } + var $export = Cryptpad.createButton('export', true, {}, saveImage); $rightside.append($export); @@ -498,6 +543,10 @@ define([ realtime: realtime }); + var isNew = false; + var userDoc = module.realtime.getUserDoc(); + if (userDoc === "" || userDoc === "{}") { isNew = true; } + Cryptpad.removeLoadingScreen(); setEditable(true); initializing = false; @@ -528,6 +577,9 @@ define([ onLocal(); module.$userNameButton.click(); } + if (isNew) { + Cryptpad.selectTemplate('whiteboard', info.realtime, Cryptget); + } }); }; diff --git a/www/whiteboard/whiteboard.css b/www/whiteboard/whiteboard.css index 5fe1b6841..a0619360b 100644 --- a/www/whiteboard/whiteboard.css +++ b/www/whiteboard/whiteboard.css @@ -33,11 +33,13 @@ body { line-height: 100px; padding-bottom: 5px; } -#controls #width { +#controls #width, +#controls #opacity { position: relative; vertical-align: middle; } #controls #clear, +#controls #delete, #controls #toggleDraw { display: inline; vertical-align: middle; diff --git a/www/whiteboard/whiteboard.less b/www/whiteboard/whiteboard.less index 75c979456..81c7e0bfe 100644 --- a/www/whiteboard/whiteboard.less +++ b/www/whiteboard/whiteboard.less @@ -42,10 +42,10 @@ body { line-height: 100px; padding-bottom: 5px; - #width { + #width, #opacity { .middle; } - #clear, #toggleDraw { + #clear, #delete, #toggleDraw { display: inline; vertical-align: middle; }
'); - if (readOnly !== 1) { + if (readOnly !== 1) { // Yourself - edit $editUsers.append('' + Messages.yourself + ''); anonymous--; } - if (editUsersNames.length > 0) { - $editUsersList.text(editUsersNames.join('\n')); // .text() to avoid XSS - $editUsers.append($editUsersList); - } - if (anonymous > 0) { + // Editors + $editUsersList.text(editUsersNames.join('\n')); // .text() to avoid XSS + $editUsers.append($editUsersList); + if (anonymous > 0) { // Anonymous editors var text = anonymous === 1 ? Messages.anonymousUser : Messages.anonymousUsers; $editUsers.append('' + anonymous + ' ' + text + ''); } - if (numberOfViewUsers > 0) { + if (numberOfViewUsers > 0) { // Viewers var viewText = ''; if (numberOfEditUsers > 0) { $editUsers.append(''); diff --git a/www/drive/main.js b/www/drive/main.js index 220ee5c11..474ea4b06 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -220,6 +220,7 @@ define([ // Categories dislayed in the menu // _WORKGROUP_ : do not display unsorted var displayedCategories = [ROOT, UNSORTED, TRASH, SEARCH]; + if (AppConfig.enableTemplates) { displayedCategories.push(TEMPLATE); } if (isWorkgroup()) { displayedCategories = [ROOT, TRASH, SEARCH]; } var lastSelectTime; @@ -376,6 +377,10 @@ define([ paths.forEach(function (p, i) { var path = p.path; var $element = p.element; + if (path.length === 1) { + hide.push($menu.find('a.rename')); + hide.push($menu.find('a.delete')); + } if (!APP.editable) { hide.push($menu.find('a.editable')); } @@ -1709,6 +1714,7 @@ define([ (isRootOpened ? $folderOpenedIcon : $folderIcon); var $rootElement = createTreeElement(ROOT_NAME, $rootIcon.clone(), [ROOT], false, true, false, isRootOpened); $rootElement.addClass('root'); + $rootElement.find('>.element-row').contextmenu(openDirectoryContextMenu); var $root = $('').append($rootElement).appendTo($container); $container = $rootElement; } else if (filesOp.isFolderEmpty(root)) { return; } diff --git a/www/pad/main.js b/www/pad/main.js index ef8443685..1170e3008 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -10,6 +10,7 @@ define([ 'json.sortify', '/bower_components/textpatcher/TextPatcher.js', '/common/cryptpad-common.js', + '/common/cryptget.js', '/common/visible.js', '/common/notify.js', '/pad/links.js', @@ -17,7 +18,7 @@ define([ '/bower_components/diff-dom/diffDOM.js', '/bower_components/jquery/dist/jquery.min.js', ], function (Crypto, realtimeInput, Hyperjson, - Toolbar, Cursor, JsonOT, TypingTest, JSONSortify, TextPatcher, Cryptpad, + Toolbar, Cursor, JsonOT, TypingTest, JSONSortify, TextPatcher, Cryptpad, Cryptget, Visible, Notify, Links) { var $ = window.jQuery; var saveAs = window.saveAs; @@ -616,6 +617,17 @@ define([ $rightside.append($collapse); } + /* save as template */ + if (!Cryptpad.isTemplate(window.location.href)) { + var templateObj = { + rt: info.realtime, + Crypt: Cryptget, + getTitle: function () { return document.title; } + }; + var $templateButton = Cryptpad.createButton('template', true, templateObj); + $rightside.append($templateButton); + } + /* add an export button */ var $export = Cryptpad.createButton('export', true, {}, exportFile); $rightside.append($export); @@ -723,6 +735,7 @@ define([ editor.focus(); if (newPad) { + Cryptpad.selectTemplate('pad', info.realtime, Cryptget); cursor.setToEnd(); } else { cursor.setToStart(); diff --git a/www/poll/main.js b/www/poll/main.js index 23f93acb7..7225bee7f 100644 --- a/www/poll/main.js +++ b/www/poll/main.js @@ -3,6 +3,7 @@ define([ '/bower_components/chainpad-listmap/chainpad-listmap.js', '/bower_components/chainpad-crypto/crypto.js', '/common/cryptpad-common.js', + '/common/cryptget.js', '/bower_components/hyperjson/hyperjson.js', 'render.js', '/common/toolbar.js', @@ -10,7 +11,7 @@ define([ '/common/notify.js', '/bower_components/file-saver/FileSaver.min.js', '/bower_components/jquery/dist/jquery.min.js', -], function (TextPatcher, Listmap, Crypto, Cryptpad, Hyperjson, Renderer, Toolbar, Visible, Notify) { +], function (TextPatcher, Listmap, Crypto, Cryptpad, Cryptget, Hyperjson, Renderer, Toolbar, Visible, Notify) { var $ = window.jQuery; var Messages = Cryptpad.Messages; @@ -525,6 +526,11 @@ define([ debug('userid: %s', userid); var proxy = APP.proxy; + + var isNew = false; + var userDoc = JSON.stringify(proxy); + if (userDoc === "" || userDoc === "{}") { isNew = true; } + var uncommitted = APP.uncommitted = {}; prepareProxy(proxy, copyObject(Render.Example)); prepareProxy(uncommitted, copyObject(Render.Example)); @@ -676,6 +682,9 @@ define([ addToUserData(myData); APP.$userNameButton.click(); } + if (isNew) { + Cryptpad.selectTemplate('poll', info.realtime, Cryptget); + } }); }; @@ -747,6 +756,17 @@ define([ // set the hash if (!readOnly) { Cryptpad.replaceHash(editHash); } + /* save as template */ + if (!Cryptpad.isTemplate(window.location.href)) { + var templateObj = { + rt: info.realtime, + Crypt: Cryptget, + getTitle: function () { return document.title; } + }; + var $templateButton = Cryptpad.createButton('template', true, templateObj); + $rightside.append($templateButton); + } + Cryptpad.onDisplayNameChanged(setName); Cryptpad.getPadTitle(function (err, title) { diff --git a/www/slide/main.js b/www/slide/main.js index 45b80e1a9..25c367a85 100644 --- a/www/slide/main.js +++ b/www/slide/main.js @@ -7,6 +7,7 @@ define([ 'json.sortify', '/bower_components/chainpad-json-validator/json-ot.js', '/common/cryptpad-common.js', + '/common/cryptget.js', '/common/modes.js', '/common/themes.js', '/common/visible.js', @@ -14,7 +15,7 @@ define([ '/slide/slide.js', '/bower_components/file-saver/FileSaver.min.js', '/bower_components/jquery/dist/jquery.min.js', -], function (Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Modes, Themes, Visible, Notify, Slide) { +], function (Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Modes, Themes, Visible, Notify, Slide) { var $ = window.jQuery; var saveAs = window.saveAs; @@ -532,6 +533,17 @@ define([ editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys); } + /* save as template */ + if (!Cryptpad.isTemplate(window.location.href)) { + var templateObj = { + rt: info.realtime, + Crypt: Cryptget, + getTitle: function () { return document.title; } + }; + var $templateButton = Cryptpad.createButton('template', true, templateObj); + $rightside.append($templateButton); + } + /* add an export button */ var $export = Cryptpad.createButton('export', true, {}, exportText); $rightside.append($export); @@ -720,6 +732,9 @@ define([ var userDoc = module.realtime.getUserDoc(); + var isNew = false; + if (userDoc === "" || userDoc === "{}") { isNew = true; } + var newDoc = ""; if(userDoc !== "") { var hjson = JSON.parse(userDoc); @@ -792,6 +807,9 @@ define([ onLocal(); module.$userNameButton.click(); } + if (isNew) { + Cryptpad.selectTemplate('slide', info.realtime, Cryptget); + } }); }; diff --git a/www/whiteboard/index.html b/www/whiteboard/index.html index 746bfccfc..d9496c344 100644 --- a/www/whiteboard/index.html +++ b/www/whiteboard/index.html @@ -23,7 +23,9 @@ Clear - 5 + + 5 + 1 diff --git a/www/whiteboard/main.js b/www/whiteboard/main.js index 4b843dd3f..d5523104c 100644 --- a/www/whiteboard/main.js +++ b/www/whiteboard/main.js @@ -11,13 +11,15 @@ define([ 'json.sortify', '/bower_components/chainpad-json-validator/json-ot.js', '/common/cryptpad-common.js', + '/common/cryptget.js', + '/whiteboard/colors.js', '/common/visible.js', '/common/notify.js', '/customize/application_config.js', '/bower_components/secure-fabric.js/dist/fabric.min.js', '/bower_components/jquery/dist/jquery.min.js', '/bower_components/file-saver/FileSaver.min.js', -], function (Config, Realtime, Crypto, Toolbar, TextPatcher, JSONSortify, JsonOT, Cryptpad, Visible, Notify, AppConfig) { +], function (Config, Realtime, Crypto, Toolbar, TextPatcher, JSONSortify, JsonOT, Cryptpad, Cryptget, Colors, Visible, Notify, AppConfig) { var saveAs = window.saveAs; var Messages = Cryptpad.Messages; @@ -47,15 +49,23 @@ define([ var $pickers = $('#pickers'); var $colors = $('#colors'); var $cursors = $('#cursors'); + var $deleteButton = $('#delete'); + + var brush = { + color: '#000000', + opacity: 1 + }; var $toggle = $('#toggleDraw'); var $width = $('#width'); var $widthLabel = $('label[for="width"]'); - + var $opacity = $('#opacity'); + var $opacityLabel = $('label[for="opacity"]'); +window.canvas = canvas; var createCursor = function () { var w = canvas.freeDrawingBrush.width; var c = canvas.freeDrawingBrush.color; - var size = w > 30 ? w : w+30; + var size = w > 30 ? w+2 : w+32; $cursors.html(''); var $ccanvas = $cursors.find('canvas'); var ccanvas = $ccanvas[0]; @@ -69,7 +79,9 @@ define([ ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false); ctx.fillStyle = c; ctx.fill(); - ctx.lineWidth = 0; + ctx.lineWidth = 1; + ctx.strokeStyle = brush.color; + ctx.stroke(); ctx.beginPath(); ctx.moveTo(size/2, 0); ctx.lineTo(size/2, 10); @@ -79,9 +91,6 @@ define([ ctx.strokeStyle = '#000000'; ctx.stroke(); - //context.lineWidth = w/2; - //context.strokeStyle = '#000000'; - //context.stroke(); var img = ccanvas.toDataURL("image/png"); var $img = $('', { @@ -102,6 +111,17 @@ define([ $width.on('change', updateBrushWidth); + var updateBrushOpacity = function () { + var val = $opacity.val(); + brush.opacity = Number(val); + canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity); + $opacityLabel.text(val); + createCursor(); + }; + updateBrushOpacity(); + + $opacity.on('change', updateBrushOpacity); + var pickColor = function (current, cb) { var $picker = $('', { type: 'color', @@ -120,21 +140,15 @@ define([ }; var setColor = function (c) { - canvas.freeDrawingBrush.color = c; + c = Colors.rgb2hex(c); + brush.color = c; + canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity); module.$color.css({ 'color': c, }); createCursor(); }; - var rgb2hex = function (rgb) { - if (rgb.indexOf('#') === 0) { return rgb; } - rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); - var hex = function (x) { - return ("0" + parseInt(x).toString(16)).slice(-2); - }; - return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]); - }; var palette = AppConfig.whiteboardPalette || [ 'red', 'blue', 'green', 'white', 'black', 'purple', @@ -151,9 +165,29 @@ define([ module.draw = !module.draw; canvas.isDrawingMode = module.draw; $toggle.text(module.draw ? Messages.canvas_disable : Messages.canvas_enable); + if (module.draw) { $deleteButton.hide(); } + else { $deleteButton.show(); } }; $toggle.click(toggleDrawMode); + var deleteSelection = function () { + if (canvas.getActiveObject()) { + canvas.getActiveObject().remove(); + } + if (canvas.getActiveGroup()) { + canvas.getActiveGroup()._objects.forEach(function (el) { + el.remove(); + }); + canvas.discardActiveGroup(); + } + canvas.renderAll(); + onLocal(); + }; + $deleteButton.click(deleteSelection); + $(window).on('keyup', function (e) { + if (e.which === 46) { deleteSelection (); } + }); + var setEditable = function (bool) { if (readOnly && bool) { return; } if (bool) { $controls.show(); } @@ -233,12 +267,12 @@ define([ 'background-color': color, }) .click(function () { - var c = rgb2hex($color.css('background-color')); + var c = Colors.rgb2hex($color.css('background-color')); setColor(c); }) .on('dblclick', function (e) { e.preventDefault(); - pickColor(rgb2hex($color.css('background-color')), function (c) { + pickColor(Colors.rgb2hex($color.css('background-color')), function (c) { $color.css({ 'background-color': c, }); @@ -327,6 +361,17 @@ define([ var $rightside = $bar.find('.' + Toolbar.constants.rightside); module.$userNameButton = $($bar.find('.' + Toolbar.constants.changeUsername)); + /* save as template */ + if (!Cryptpad.isTemplate(window.location.href)) { + var templateObj = { + rt: info.realtime, + Crypt: Cryptget, + getTitle: function () { return document.title; } + }; + var $templateButton = Cryptpad.createButton('template', true, templateObj); + $rightside.append($templateButton); + } + var $export = Cryptpad.createButton('export', true, {}, saveImage); $rightside.append($export); @@ -498,6 +543,10 @@ define([ realtime: realtime }); + var isNew = false; + var userDoc = module.realtime.getUserDoc(); + if (userDoc === "" || userDoc === "{}") { isNew = true; } + Cryptpad.removeLoadingScreen(); setEditable(true); initializing = false; @@ -528,6 +577,9 @@ define([ onLocal(); module.$userNameButton.click(); } + if (isNew) { + Cryptpad.selectTemplate('whiteboard', info.realtime, Cryptget); + } }); }; diff --git a/www/whiteboard/whiteboard.css b/www/whiteboard/whiteboard.css index 5fe1b6841..a0619360b 100644 --- a/www/whiteboard/whiteboard.css +++ b/www/whiteboard/whiteboard.css @@ -33,11 +33,13 @@ body { line-height: 100px; padding-bottom: 5px; } -#controls #width { +#controls #width, +#controls #opacity { position: relative; vertical-align: middle; } #controls #clear, +#controls #delete, #controls #toggleDraw { display: inline; vertical-align: middle; diff --git a/www/whiteboard/whiteboard.less b/www/whiteboard/whiteboard.less index 75c979456..81c7e0bfe 100644 --- a/www/whiteboard/whiteboard.less +++ b/www/whiteboard/whiteboard.less @@ -42,10 +42,10 @@ body { line-height: 100px; padding-bottom: 5px; - #width { + #width, #opacity { .middle; } - #clear, #toggleDraw { + #clear, #delete, #toggleDraw { display: inline; vertical-align: middle; }