From 63710e1cccba59dc84dfa89b2fe01fb1b1204a1b Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 9 May 2017 12:45:18 +0200 Subject: [PATCH 1/4] Move the code related to the title in another file --- www/code/main.js | 82 ++++++++--------------------------- www/common/common-title.js | 82 +++++++++++++++++++++++++++++++++++ www/common/cryptpad-common.js | 6 ++- 3 files changed, 106 insertions(+), 64 deletions(-) create mode 100644 www/common/common-title.js diff --git a/www/code/main.js b/www/code/main.js index c382b38e4..a21c67273 100644 --- a/www/code/main.js +++ b/www/code/main.js @@ -48,8 +48,6 @@ define([ var $textarea = $pad.contents().find('#editor1'); var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox'); - var parsedHash = Cryptpad.parsePadUrl(window.location.href); - var defaultName = Cryptpad.getDefaultName(parsedHash); var isHistoryMode = false; @@ -120,6 +118,7 @@ define([ editor.setOption('readOnly', !bool); }; + var Title; var UserList; var config = { @@ -144,11 +143,6 @@ define([ } }; -/* var isDefaultTitle = function () { - var parsed = Cryptpad.parsePadUrl(window.location.href); - return Cryptpad.isDefaultName(parsed, document.title); - };*/ - var initializing = true; var stringifyInner = function (textValue) { @@ -156,11 +150,11 @@ define([ content: textValue, metadata: { users: UserList.userData, - defaultTitle: defaultName + defaultTitle: Title.defaultTitle } }; if (!initializing) { - obj.metadata.title = document.title; + obj.metadata.title = Title.title; } // set mode too... obj.highlightMode = module.highlightMode; @@ -226,20 +220,12 @@ define([ return text.trim(); }; - var suggestName = function (fallback) { - if (document.title === defaultName) { - return getHeadingText() || fallback || ""; - } else { - return document.title || getHeadingText() || defaultName; - } - }; - var exportText = module.exportText = function () { var text = editor.getValue(); var ext = Modes.extensionOf(module.highlightMode); - var title = Cryptpad.fixFileName(suggestName('cryptpad')) + (ext || '.txt'); + var title = Cryptpad.fixFileName(Title.suggestTitle('cryptpad')) + (ext || '.txt'); Cryptpad.prompt(Messages.exportPrompt, title, function (filename) { if (filename === null) { return; } @@ -276,35 +262,6 @@ define([ onLocal(); }; - var renameCb = function (err, title) { - if (err) { return; } - document.title = title; - onLocal(); - }; - - var updateTitle = function (newTitle) { - if (newTitle === document.title) { return; } - // Change the title now, and set it back to the old value if there is an error - var oldTitle = document.title; - document.title = newTitle; - Cryptpad.renamePad(newTitle, function (err, data) { - if (err) { - console.log("Couldn't set pad title"); - console.error(err); - document.title = oldTitle; - return; - } - document.title = data; - $bar.find('.' + Toolbar.constants.title).find('span.title').text(data); - $bar.find('.' + Toolbar.constants.title).find('input').val(data); - }); - }; - - var updateDefaultTitle = function (defaultTitle) { - defaultName = defaultTitle; - $bar.find('.' + Toolbar.constants.title).find('input').attr("placeholder", defaultName); - }; - var updateMetadata = function(shjson) { // Extract the user list (metadata) from the hyperjson var json = (shjson === "") ? "" : JSON.parse(shjson); @@ -316,21 +273,27 @@ define([ UserList.addToUserData(userData); } if (json.metadata.defaultTitle) { - updateDefaultTitle(json.metadata.defaultTitle); + Title.updateDefaultTitle(json.metadata.defaultTitle); } if (typeof json.metadata.title !== "undefined") { - updateTitle(json.metadata.title || defaultName); + Title.updateTitle(json.metadata.title || Title.defaultTitle); titleUpdated = true; } } if (!titleUpdated) { - updateTitle(defaultName); + Title.updateTitle(Title.defaultTitle); } }; config.onInit = function (info) { UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad); + var titleCfg = { + $bar: $bar, + getHeadingText: getHeadingText + }; + Title = Cryptpad.createTitle(titleCfg, config.onLocal, Cryptpad); + var configTb = { displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'], userList: UserList.getToolbarConfig(), @@ -338,11 +301,7 @@ define([ secret: secret, channel: info.channel }, - title: { - onRename: renameCb, - defaultName: defaultName, - suggestName: suggestName - }, + title: Title.getTitleConfig(), common: Cryptpad, readOnly: readOnly, ifrw: ifrw, @@ -352,6 +311,8 @@ define([ }; toolbar = module.toolbar = Toolbar.create(configTb); + Title.setToolbar(toolbar); + var $rightside = toolbar.$rightside; var editHash; @@ -396,7 +357,7 @@ define([ var templateObj = { rt: info.realtime, Crypt: Cryptget, - getTitle: function () { return document.title; } + getTitle: Title.getTitle }; var $templateButton = Cryptpad.createButton('template', true, templateObj); $rightside.append($templateButton); @@ -410,10 +371,6 @@ define([ /* add an import button */ var $import = Cryptpad.createButton('import', true, {}, importText); $rightside.append($import); - - /* add a rename button */ - //var $setTitle = Cryptpad.createButton('rename', true, {suggestName: suggestName}, renameCb); - //$rightside.append($setTitle); } /* add a forget button */ @@ -559,9 +516,8 @@ define([ editor.setValue(newDoc); } - if (Cryptpad.initialName && document.title === defaultName) { - updateTitle(Cryptpad.initialName); - onLocal(); + if (Cryptpad.initialName && Title.isDefaultTitle()) { + Title.updateTitle(Cryptpad.initialName); } if (Visible.isSupported()) { diff --git a/www/common/common-title.js b/www/common/common-title.js new file mode 100644 index 000000000..7ceb2741e --- /dev/null +++ b/www/common/common-title.js @@ -0,0 +1,82 @@ +define(function () { + var module = {}; + + module.create = function (cfg, onLocal, Cryptpad) { + var exp = {}; + + var parsed = exp.parsedHref = Cryptpad.parsePadUrl(window.location.href); + exp.defaultTitle = Cryptpad.getDefaultName(parsed); + + exp.title = document.title; // TOOD slides + + var getHeadingText = cfg.getHeadingText || function () { return; }; + var updateLocalTitle = function (newTitle) { + exp.title = newTitle; + if (typeof cfg.updateLocalTitle === "function") { + cfg.updateLocalTitle(newTitle); + } else { + document.title = newTitle; + } + }; + + var $title; + exp.setToolbar = function (toolbar) { + $title = toolbar && toolbar.title; + } + + exp.getTitle = function () { return exp.title; }; + var isDefaultTitle = exp.isDefaultTitle = function (){return exp.title === exp.defaultTitle;}; + + var suggestTitle = exp.suggestTitle = function (fallback) { + if (isDefaultTitle()) { + return getHeadingText() || fallback || ""; + } else { + return exp.title || getHeadingText() || exp.defaultTitle; + } + }; + + var renameCb = function (err, newTitle) { + if (err) { return; } + updateLocalTitle(newTitle); + console.log('here'); + onLocal(); + }; + + exp.updateTitle = function (newTitle) { + if (newTitle === exp.title) { return; } + // Change the title now, and set it back to the old value if there is an error + var oldTitle = exp.title; + Cryptpad.renamePad(newTitle, function (err, data) { + if (err) { + console.log("Couldn't set pad title"); + console.error(err); + updateLocalTitle(oldTitle); + return; + } + updateLocalTitle(data); + if (!$title) { return; } + $title.find('span.title').text(data); + $title.find('input').val(data); + }); + }; + + exp.updateDefaultTitle = function (newDefaultTitle) { + exp.defaultTitle = newDefaultTitle; + if (!$title) { return; } + $title.find('input').attr("placeholder", exp.defaultTitle); + }; + + exp.getTitleConfig = function () { + return { + onRename: renameCb, + suggestName: suggestTitle, + defaultName: exp.defaultTitle + } + }; + + return exp; + }; + + return module; +}); + diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index b58209029..29d3e0ddb 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -8,11 +8,12 @@ define([ '/common/common-interface.js', '/common/common-history.js', '/common/common-userlist.js', + '/common/common-title.js', '/common/clipboard.js', '/common/pinpad.js', '/customize/application_config.js' -], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Clipboard, Pinpad, AppConfig) { +], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Title, Clipboard, Pinpad, AppConfig) { /* This file exposes functionality which is specific to Cryptpad, but not to any particular pad type. This includes functions for committing metadata @@ -88,6 +89,9 @@ define([ // Userlist common.createUserList = UserList.create; + // Title + common.createTitle = Title.create; + // History common.getHistory = function (config) { return History.create(common, config); }; From c79a6e3b2bce4c98a055e282b48765b05fc04ff1 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 9 May 2017 17:47:47 +0200 Subject: [PATCH 2/4] Move Metadata, CodeMirror and title functions in separate files --- www/code/main.js | 312 +++---------------------- www/common/common-codemirror.js | 247 ++++++++++++++++++++ www/common/common-interface.js | 28 ++- www/common/common-metadata.js | 51 +++++ www/common/common-title.js | 2 + www/common/cryptpad-common.js | 12 +- www/pad/main.js | 117 ++-------- www/poll/main.js | 90 ++------ www/slide/main.js | 391 ++++++-------------------------- www/whiteboard/main.js | 116 ++-------- 10 files changed, 486 insertions(+), 880 deletions(-) create mode 100644 www/common/common-codemirror.js create mode 100644 www/common/common-metadata.js diff --git a/www/code/main.js b/www/code/main.js index a21c67273..ea9a66001 100644 --- a/www/code/main.js +++ b/www/code/main.js @@ -8,13 +8,7 @@ define([ '/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' -], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Modes, Themes, Visible, Notify) { - var saveAs = window.saveAs; +], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget) { var Messages = Cryptpad.Messages; var module = window.APP = { @@ -30,6 +24,7 @@ define([ }; var toolbar; + var editor; var secret = Cryptpad.getSecrets(); var readOnly = secret.keys && !secret.keys.editKeyStr; @@ -42,77 +37,13 @@ define([ }; var andThen = function (CMeditor) { - var CodeMirror = module.CodeMirror = CMeditor; - CodeMirror.modeURL = "/bower_components/codemirror/mode/%N/%N.js"; - var $pad = $('#pad-iframe'); - var $textarea = $pad.contents().find('#editor1'); + var CodeMirror = Cryptpad.createCodemirror(CMeditor, ifrw, Cryptpad); + editor = CodeMirror.editor; var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox'); var isHistoryMode = false; - var editor = module.editor = CMeditor.fromTextArea($textarea[0], { - lineNumbers: true, - lineWrapping: true, - autoCloseBrackets: true, - matchBrackets : true, - showTrailingSpace : true, - styleActiveLine : true, - search: true, - highlightSelectionMatches: {showToken: /\w+/}, - extraKeys: {"Shift-Ctrl-R": undefined}, - foldGutter: true, - gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], - mode: "javascript", - readOnly: true - }); - editor.setValue(Messages.codeInitialState); - - var setMode = module.setMode = function (mode, $select) { - module.highlightMode = mode; - if (mode === 'text') { - editor.setOption('mode', 'text'); - return; - } - CodeMirror.autoLoadMode(editor, mode); - editor.setOption('mode', mode); - if ($select) { - var name = $select.find('a[data-value="' + mode + '"]').text() || 'Mode'; - $select.setValue(name); - } - }; - - var setTheme = module.setTheme = (function () { - var path = '/common/theme/'; - - var $head = $(ifrw.document.head); - - var themeLoaded = module.themeLoaded = function (theme) { - return $head.find('link[href*="'+theme+'"]').length; - }; - - var loadTheme = module.loadTheme = function (theme) { - $head.append($('', { - rel: 'stylesheet', - href: path + theme + '.css', - })); - }; - - return function (theme, $select) { - if (!theme) { - editor.setOption('theme', 'default'); - } else { - if (!themeLoaded(theme)) { - loadTheme(theme); - } - editor.setOption('theme', theme); - } - if ($select) { - $select.setValue(theme || 'Theme'); - } - }; - }()); - var setEditable = module.setEditable = function (bool) { if (readOnly && bool) { return; } editor.setOption('readOnly', !bool); @@ -120,6 +51,7 @@ define([ var Title; var UserList; + var Metadata; var config = { initialState: '{}', @@ -157,7 +89,7 @@ define([ obj.metadata.title = Title.title; } // set mode too... - obj.highlightMode = module.highlightMode; + obj.highlightMode = CodeMirror.highlightMode; // stringify the json and send it into chainpad return stringify(obj); @@ -170,7 +102,7 @@ define([ editor.save(); - var textValue = canonicalize($textarea.val()); + var textValue = canonicalize(CodeMirror.$textarea.val()); var shjson = stringifyInner(textValue); module.patchText(shjson); @@ -180,120 +112,16 @@ define([ } }; - var getHeadingText = function () { - var lines = editor.getValue().split(/\n/); - - var text = ''; - lines.some(function (line) { - // lisps? - var lispy = /^\s*(;|#\|)(.*?)$/; - if (lispy.test(line)) { - line.replace(lispy, function (a, one, two) { - text = two; - }); - return true; - } - - // lines beginning with a hash are potentially valuable - // works for markdown, python, bash, etc. - var hash = /^#(.*?)$/; - if (hash.test(line)) { - line.replace(hash, function (a, one) { - text = one; - }); - return true; - } - - // lines including a c-style comment are also valuable - var clike = /^\s*(\/\*|\/\/)(.*)?(\*\/)*$/; - if (clike.test(line)) { - line.replace(clike, function (a, one, two) { - if (!(two && two.replace)) { return; } - text = two.replace(/\*\/\s*$/, '').trim(); - }); - return true; - } - - // TODO make one more pass for multiline comments - }); - - return text.trim(); - }; - - var exportText = module.exportText = function () { - var text = editor.getValue(); - - var ext = Modes.extensionOf(module.highlightMode); - - var title = Cryptpad.fixFileName(Title.suggestTitle('cryptpad')) + (ext || '.txt'); - - Cryptpad.prompt(Messages.exportPrompt, title, function (filename) { - if (filename === null) { return; } - var blob = new Blob([text], { - type: 'text/plain;charset=utf-8' - }); - saveAs(blob, filename); - }); - }; - var importText = function (content, file) { - var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox'); - var mode; - var mime = CodeMirror.findModeByMIME(file.type); - - if (!mime) { - var ext = /.+\.([^.]+)$/.exec(file.name); - if (ext[1]) { - mode = CodeMirror.findModeByExtension(ext[1]); - } - } else { - mode = mime && mime.mode || null; - } - - if (mode && Modes.list.some(function (o) { return o.mode === mode; })) { - setMode(mode); - $bar.find('#language-mode').val(mode); - } else { - console.log("Couldn't find a suitable highlighting mode: %s", mode); - setMode('text'); - $bar.find('#language-mode').val('text'); - } - - editor.setValue(content); - onLocal(); - }; - var updateMetadata = function(shjson) { - // Extract the user list (metadata) from the hyperjson - var json = (shjson === "") ? "" : JSON.parse(shjson); - var titleUpdated = false; - if (json && json.metadata) { - if (json.metadata.users) { - var userData = json.metadata.users; - // Update the local user data - UserList.addToUserData(userData); - } - if (json.metadata.defaultTitle) { - Title.updateDefaultTitle(json.metadata.defaultTitle); - } - if (typeof json.metadata.title !== "undefined") { - Title.updateTitle(json.metadata.title || Title.defaultTitle); - titleUpdated = true; - } - } - if (!titleUpdated) { - Title.updateTitle(Title.defaultTitle); - } - }; - config.onInit = function (info) { + config.onInit = function (info) { UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad); - var titleCfg = { - $bar: $bar, - getHeadingText: getHeadingText - }; + var titleCfg = { getHeadingText: CodeMirror.getHeadingText }; Title = Cryptpad.createTitle(titleCfg, config.onLocal, Cryptpad); + Metadata = Cryptpad.createMetadata(UserList, Title); + var configTb = { displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'], userList: UserList.getToolbarConfig(), @@ -312,6 +140,7 @@ define([ toolbar = module.toolbar = Toolbar.create(configTb); Title.setToolbar(toolbar); + CodeMirror.init(config.onLocal, Title, toolbar); var $rightside = toolbar.$rightside; @@ -364,12 +193,12 @@ define([ } /* add an export button */ - var $export = Cryptpad.createButton('export', true, {}, exportText); + var $export = Cryptpad.createButton('export', true, {}, CodeMirror.exportText); $rightside.append($export); if (!readOnly) { /* add an import button */ - var $import = Cryptpad.createButton('import', true, {}, importText); + var $import = Cryptpad.createButton('import', true, {}, CodeMirror.importText); $rightside.append($import); } @@ -381,98 +210,17 @@ define([ var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb); $rightside.append($forgetPad); - var configureLanguage = function (cb) { - // FIXME this is async so make it happen as early as possible - var options = []; - Modes.list.forEach(function (l) { - options.push({ - tag: 'a', - attributes: { - 'data-value': l.mode, - 'href': '#', - }, - content: l.language // Pretty name of the language value - }); - }); - var dropdownConfig = { - text: 'Mode', // Button initial text - options: options, // Entries displayed in the menu - left: true, // Open to the left of the button - isSelect: true, - }; - var $block = module.$language = Cryptpad.createDropdown(dropdownConfig); - $block.find('a').click(function () { - setMode($(this).attr('data-value'), $block); - onLocal(); - }); - - $rightside.append($block); - cb(); - }; - - var configureTheme = function () { - /* Remember the user's last choice of theme using localStorage */ - var themeKey = 'CRYPTPAD_CODE_THEME'; - var lastTheme = localStorage.getItem(themeKey) || 'default'; - - var options = []; - Themes.forEach(function (l) { - options.push({ - tag: 'a', - attributes: { - 'data-value': l.name, - 'href': '#', - }, - content: l.name // Pretty name of the language value - }); - }); - var dropdownConfig = { - text: 'Theme', // Button initial text - options: options, // Entries displayed in the menu - left: true, // Open to the left of the button - isSelect: true, - initialValue: lastTheme - }; - var $block = module.$theme = Cryptpad.createDropdown(dropdownConfig); - - setTheme(lastTheme, $block); - - $block.find('a').click(function () { - var theme = $(this).attr('data-value'); - setTheme(theme, $block); - localStorage.setItem(themeKey, theme); - }); - - $rightside.append($block); - }; - if (!readOnly) { - configureLanguage(function () { - configureTheme(); - }); + CodeMirror.configureLanguage(CodeMirror.configureTheme); } else { - configureTheme(); + CodeMirror.configureTheme(); } // set the hash if (!readOnly) { Cryptpad.replaceHash(editHash); } }; - var unnotify = module.unnotify = function () { - if (module.tabNotification && - typeof(module.tabNotification.cancel) === 'function') { - module.tabNotification.cancel(); - } - }; - - var notify = module.notify = function () { - if (Visible.isSupported() && !Visible.currently()) { - unnotify(); - module.tabNotification = Notify.tab(1000, 10); - } - }; - config.onReady = function (info) { if (module.realtime !== info.realtime) { var realtime = module.realtime = info.realtime; @@ -500,17 +248,17 @@ define([ newDoc = hjson.content; if (hjson.highlightMode) { - setMode(hjson.highlightMode, module.$language); + CodeMirror.setMode(hjson.highlightMode); } } - if (!module.highlightMode) { - setMode('javascript', module.$language); - console.log("%s => %s", module.highlightMode, module.$language.val()); + if (!CodeMirror.highlightMode) { + CodeMirror.setMode('javascript'); + console.log("%s => %s", CodeMirror.highlightMode, CodeMirror.$language.val()); } // Update the user list (metadata) from the hyperjson - updateMetadata(userDoc); + Metadata.update(userDoc); if (newDoc) { editor.setValue(newDoc); @@ -520,12 +268,6 @@ define([ Title.updateTitle(Cryptpad.initialName); } - if (Visible.isSupported()) { - Visible.onChange(function (yes) { - if (yes) { unnotify(); } - }); - } - Cryptpad.removeLoadingScreen(); setEditable(true); initializing = false; @@ -568,18 +310,18 @@ define([ if (isHistoryMode) { return; } var scroll = editor.getScrollInfo(); - var oldDoc = canonicalize($textarea.val()); + var oldDoc = canonicalize(CodeMirror.$textarea.val()); var shjson = module.realtime.getUserDoc(); // Update the user list (metadata) from the hyperjson - updateMetadata(shjson); + Metadata.update(shjson); var hjson = JSON.parse(shjson); var remoteDoc = hjson.content; var highlightMode = hjson.highlightMode; if (highlightMode && highlightMode !== module.highlightMode) { - setMode(highlightMode, module.$language); + CodeMirror.setMode(highlightMode); } //get old cursor here @@ -605,7 +347,7 @@ define([ editor.scrollTo(scroll.left, scroll.top); if (!readOnly) { - var textValue = canonicalize($textarea.val()); + var textValue = canonicalize(CodeMirror.$textarea.val()); var shjson2 = stringifyInner(textValue); if (shjson2 !== shjson) { console.error("shjson2 !== shjson"); @@ -613,9 +355,7 @@ define([ module.patchText(shjson2); } } - if (oldDoc !== remoteDoc) { - notify(); - } + if (oldDoc !== remoteDoc) { Cryptpad.notify(); } }; config.onAbort = function () { diff --git a/www/common/common-codemirror.js b/www/common/common-codemirror.js new file mode 100644 index 000000000..bbaad49df --- /dev/null +++ b/www/common/common-codemirror.js @@ -0,0 +1,247 @@ +define([ + 'jquery', + '/common/modes.js', + '/common/themes.js', + '/bower_components/file-saver/FileSaver.min.js' +], function ($, Modes, Themes) { + var saveAs = window.saveAs; + var module = {}; + + module.create = function (CMeditor, ifrw, Cryptpad) { + var exp = {}; + + var Messages = Cryptpad.Messages; + + var CodeMirror = exp.CodeMirror = CMeditor; + CodeMirror.modeURL = "/bower_components/codemirror/mode/%N/%N.js"; + + var $pad = $('#pad-iframe'); + var $textarea = exp.$textarea = $pad.contents().find('#editor1'); + + var Title; + var onLocal = function () {}; + var $rightside; + exp.init = function (local, title, toolbar) { + if (typeof local === "function") { + onLocal = local; + } + Title = title; + $rightside = toolbar.$rightside; + }; + + var editor = exp.editor = CMeditor.fromTextArea($textarea[0], { + lineNumbers: true, + lineWrapping: true, + autoCloseBrackets: true, + matchBrackets : true, + showTrailingSpace : true, + styleActiveLine : true, + search: true, + highlightSelectionMatches: {showToken: /\w+/}, + extraKeys: {"Shift-Ctrl-R": undefined}, + foldGutter: true, + gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], + mode: "javascript", + readOnly: true + }); + editor.setValue(Messages.codeInitialState); + + var setMode = exp.setMode = function (mode) { + exp.highlightMode = mode; + if (mode === 'text') { + editor.setOption('mode', 'text'); + return; + } + CMeditor.autoLoadMode(editor, mode); + editor.setOption('mode', mode); + if (exp.$language) { + var name = exp.$language.find('a[data-value="' + mode + '"]').text() || 'Mode'; + exp.$language.setValue(name); + } + }; + + var setTheme = exp.setTheme = (function () { + var path = '/common/theme/'; + + var $head = $(ifrw.document.head); + + var themeLoaded = exp.themeLoaded = function (theme) { + return $head.find('link[href*="'+theme+'"]').length; + }; + + var loadTheme = exp.loadTheme = function (theme) { + $head.append($('', { + rel: 'stylesheet', + href: path + theme + '.css', + })); + }; + + return function (theme, $select) { + if (!theme) { + editor.setOption('theme', 'default'); + } else { + if (!themeLoaded(theme)) { + loadTheme(theme); + } + editor.setOption('theme', theme); + } + if ($select) { + $select.setValue(theme || 'Theme'); + } + }; + }()); + + var getHeadingText = function () { + var lines = editor.getValue().split(/\n/); + + var text = ''; + lines.some(function (line) { + // lisps? + var lispy = /^\s*(;|#\|)(.*?)$/; + if (lispy.test(line)) { + line.replace(lispy, function (a, one, two) { + text = two; + }); + return true; + } + + // lines beginning with a hash are potentially valuable + // works for markdown, python, bash, etc. + var hash = /^#(.*?)$/; + if (hash.test(line)) { + line.replace(hash, function (a, one) { + text = one; + }); + return true; + } + + // lines including a c-style comment are also valuable + var clike = /^\s*(\/\*|\/\/)(.*)?(\*\/)*$/; + if (clike.test(line)) { + line.replace(clike, function (a, one, two) { + if (!(two && two.replace)) { return; } + text = two.replace(/\*\/\s*$/, '').trim(); + }); + return true; + } + + // TODO make one more pass for multiline comments + }); + + return text.trim(); + }; + + exp.configureLanguage = function (cb) { + var options = []; + Modes.list.forEach(function (l) { + options.push({ + tag: 'a', + attributes: { + 'data-value': l.mode, + 'href': '#', + }, + content: l.language // Pretty name of the language value + }); + }); + var dropdownConfig = { + text: 'Mode', // Button initial text + options: options, // Entries displayed in the menu + left: true, // Open to the left of the button + isSelect: true, + }; + console.log('here'); + var $block = exp.$language = Cryptpad.createDropdown(dropdownConfig); + console.log(exp); + $block.find('a').click(function () { + setMode($(this).attr('data-value'), $block); + onLocal(); + }); + + if ($rightside) { $rightside.append($block); } + cb(); + }; + + exp.configureTheme = function () { + /* Remember the user's last choice of theme using localStorage */ + var themeKey = 'CRYPTPAD_CODE_THEME'; + var lastTheme = localStorage.getItem(themeKey) || 'default'; + + var options = []; + Themes.forEach(function (l) { + options.push({ + tag: 'a', + attributes: { + 'data-value': l.name, + 'href': '#', + }, + content: l.name // Pretty name of the language value + }); + }); + var dropdownConfig = { + text: 'Theme', // Button initial text + options: options, // Entries displayed in the menu + left: true, // Open to the left of the button + isSelect: true, + initialValue: lastTheme + }; + var $block = exp.$theme = Cryptpad.createDropdown(dropdownConfig); + + setTheme(lastTheme, $block); + + $block.find('a').click(function () { + var theme = $(this).attr('data-value'); + setTheme(theme, $block); + localStorage.setItem(themeKey, theme); + }); + + if ($rightside) { $rightside.append($block); } + }; + + exp.exportText = function () { + var text = editor.getValue(); + + var ext = Modes.extensionOf(exp.highlightMode); + + var title = Cryptpad.fixFileName(Title ? Title.suggestTitle('cryptpad') : "?") + (ext || '.txt'); + + Cryptpad.prompt(Messages.exportPrompt, title, function (filename) { + if (filename === null) { return; } + var blob = new Blob([text], { + type: 'text/plain;charset=utf-8' + }); + saveAs(blob, filename); + }); + }; + exp.importText = function (content, file) { + var $bar = ifrw.$('#cme_toolbox'); + var mode; + var mime = CodeMirror.findModeByMIME(file.type); + + if (!mime) { + var ext = /.+\.([^.]+)$/.exec(file.name); + if (ext[1]) { + mode = CMeditor.findModeByExtension(ext[1]); + } + } else { + mode = mime && mime.mode || null; + } + + if (mode && Modes.list.some(function (o) { return o.mode === mode; })) { + setMode(mode); + $bar.find('#language-mode').val(mode); + } else { + console.log("Couldn't find a suitable highlighting mode: %s", mode); + setMode('text'); + $bar.find('#language-mode').val('text'); + } + + editor.setValue(content); + onLocal(); + }; + + return exp; + }; + + return module; +}); + diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 979ffcf67..fb965a5d9 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -3,8 +3,10 @@ define([ '/customize/messages.js', '/common/common-util.js', '/customize/application_config.js', - '/bower_components/alertifyjs/dist/js/alertify.js' -], function ($, Messages, Util, AppConfig, Alertify) { + '/bower_components/alertifyjs/dist/js/alertify.js', + '/common/notify.js', + '/common/visible.js' +], function ($, Messages, Util, AppConfig, Alertify, Notify, Visible) { var UI = {}; @@ -204,6 +206,28 @@ define([ $('#' + LOADING).find('p').html(error || Messages.error); }; + // Notify + var notify = {}; + UI.unnotify = function () { + if (notify.tabNotification && + typeof(notify.tabNotification.cancel) === 'function') { + notify.tabNotification.cancel(); + } + }; + + UI.notify = function () { + if (Visible.isSupported() && !Visible.currently()) { + UI.unnotify(); + notify.tabNotification = Notify.tab(1000, 10); + } + }; + + if (Visible.isSupported()) { + Visible.onChange(function (yes) { + if (yes) { UI.unnotify(); } + }); + } + UI.importContent = function (type, f) { return function () { var $files = $('').click(); diff --git a/www/common/common-metadata.js b/www/common/common-metadata.js new file mode 100644 index 000000000..2198156d5 --- /dev/null +++ b/www/common/common-metadata.js @@ -0,0 +1,51 @@ +define(function () { + var module = {}; + + module.create = function (UserList, Title, cfg) { + var exp = {}; + + var updateMetadata = exp.update = function (shjson) { + // Extract the user list (metadata) from the hyperjson + var json = (!shjson || typeof shjson !== "string") ? "" : JSON.parse(shjson); + var titleUpdated = false; + var metadata; + if (Array.isArray(json)) { + metadata = json[3] && json[3].metadata; + } else { + metadata = json.metadata; + } + if (typeof metadata === "object") { + if (metadata.users) { + var userData = metadata.users; + // Update the local user data + UserList.addToUserData(userData); + } + if (metadata.defaultTitle) { + Title.updateDefaultTitle(metadata.defaultTitle); + } + if (typeof metadata.title !== "undefined") { + Title.updateTitle(metadata.title || Title.defaultTitle); + titleUpdated = true; + } + if (metadata.slideOptions && cfg.slideOptions) { + cfg.slideOptions(metadata.slideOptions); + } + if (metadata.color && cfg.slideColors) { + cfg.slideColors(metadata.color, metadata.backColor); + } + if (typeof(metadata.palette) !== 'undefined' && cfg.updatePalette) { + cfg.updatePalette(metadata.palette); + } + } + if (!titleUpdated) { + Title.updateTitle(Title.defaultTitle); + } + }; + + return exp; + }; + + return module; +}); + + diff --git a/www/common/common-title.js b/www/common/common-title.js index 7ceb2741e..056205bfb 100644 --- a/www/common/common-title.js +++ b/www/common/common-title.js @@ -9,6 +9,8 @@ define(function () { exp.title = document.title; // TOOD slides + cfg = cfg || {}; + var getHeadingText = cfg.getHeadingText || function () { return; }; var updateLocalTitle = function (newTitle) { exp.title = newTitle; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 29d3e0ddb..0c777b9c8 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -9,11 +9,13 @@ define([ '/common/common-history.js', '/common/common-userlist.js', '/common/common-title.js', + '/common/common-metadata.js', + '/common/common-codemirror.js', '/common/clipboard.js', '/common/pinpad.js', '/customize/application_config.js' -], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Title, Clipboard, Pinpad, AppConfig) { +], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Title, Metadata, CodeMirror, Clipboard, Pinpad, AppConfig) { /* This file exposes functionality which is specific to Cryptpad, but not to any particular pad type. This includes functions for committing metadata @@ -54,6 +56,8 @@ define([ common.addLoadingScreen = UI.addLoadingScreen; common.removeLoadingScreen = UI.removeLoadingScreen; common.errorLoadingScreen = UI.errorLoadingScreen; + common.notify = UI.notify; + common.unnotify = UI.unnotify; // import common utilities for export common.find = Util.find; @@ -92,6 +96,12 @@ define([ // Title common.createTitle = Title.create; + // Metadata + common.createMetadata = Metadata.create; + + // CodeMirror + common.createCodemirror = CodeMirror.create; + // History common.getHistory = function (config) { return History.create(common, config); }; diff --git a/www/pad/main.js b/www/pad/main.js index 5e679f05f..bf34b9328 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -11,14 +11,11 @@ define([ '/bower_components/textpatcher/TextPatcher.js', '/common/cryptpad-common.js', '/common/cryptget.js', - '/common/visible.js', - '/common/notify.js', '/pad/links.js', '/bower_components/file-saver/FileSaver.min.js', '/bower_components/diff-dom/diffDOM.js' ], function ($, Crypto, realtimeInput, Hyperjson, - Toolbar, Cursor, JsonOT, TypingTest, JSONSortify, TextPatcher, Cryptpad, Cryptget, - Visible, Notify, Links) { + Toolbar, Cursor, JsonOT, TypingTest, JSONSortify, TextPatcher, Cryptpad, Cryptget, Links) { var saveAs = window.saveAs; var Messages = Cryptpad.Messages; @@ -105,8 +102,6 @@ define([ editor.on('instanceReady', Links.addSupportForOpeningLinksInNewTab(Ckeditor)); editor.on('instanceReady', function () { var $bar = $('#pad-iframe')[0].contentWindow.$('#cke_1_toolbox'); - var parsedHash = Cryptpad.parsePadUrl(window.location.href); - var defaultName = Cryptpad.getDefaultName(parsedHash); var isHistoryMode = false; @@ -277,7 +272,10 @@ define([ }; var initializing = true; + + var Title; var UserList; + var Metadata; var getHeadingText = function () { var text; @@ -290,14 +288,6 @@ define([ })) { return text; } }; - var suggestName = function (fallback) { - if (document.title === defaultName) { - return getHeadingText() || fallback || ""; - } else { - return document.title || getHeadingText() || defaultName; - } - }; - var DD = new DiffDom(diffOptions); // apply patches, and try not to lose the cursor in the process! @@ -316,11 +306,11 @@ define([ hjson[3] = { metadata: { users: UserList.userData, - defaultTitle: defaultName + defaultTitle: Title.defaultTitle } }; if (!initializing) { - hjson[3].metadata.title = document.title; + hjson[3].metadata.title = Title.title; } else if (Cryptpad.initialName && !hjson[3].metadata.title) { hjson[3].metadata.title = Cryptpad.initialName; } @@ -369,68 +359,6 @@ define([ } }; - var updateTitle = function (newTitle) { - if (newTitle === document.title) { return; } - // Change the title now, and set it back to the old value if there is an error - var oldTitle = document.title; - document.title = newTitle; - Cryptpad.renamePad(newTitle, function (err, data) { - if (err) { - console.log("Couldn't set pad title"); - console.error(err); - document.title = oldTitle; - return; - } - document.title = data; - $bar.find('.' + Toolbar.constants.title).find('span.title').text(data); - $bar.find('.' + Toolbar.constants.title).find('input').val(data); - }); - }; - - var updateDefaultTitle = function (defaultTitle) { - defaultName = defaultTitle; - $bar.find('.' + Toolbar.constants.title).find('input').attr("placeholder", defaultName); - }; - - var updateMetadata = function(shjson) { - // Extract the user list (metadata) from the hyperjson - if (!shjson || typeof (shjson) !== "string") { updateTitle(defaultName); return; } - var hjson = JSON.parse(shjson); - var peerMetadata = hjson[3]; - var titleUpdated = false; - if (peerMetadata && peerMetadata.metadata) { - if (peerMetadata.metadata.users) { - var userData = peerMetadata.metadata.users; - // Update the local user data - UserList.addToUserData(userData); - } - if (peerMetadata.metadata.defaultTitle) { - updateDefaultTitle(peerMetadata.metadata.defaultTitle); - } - if (typeof peerMetadata.metadata.title !== "undefined") { - updateTitle(peerMetadata.metadata.title || defaultName); - titleUpdated = true; - } - } - if (!titleUpdated) { - updateTitle(defaultName); - } - }; - - var unnotify = function () { - if (module.tabNotification && - typeof(module.tabNotification.cancel) === 'function') { - module.tabNotification.cancel(); - } - }; - - var notify = function () { - if (Visible.isSupported() && !Visible.currently()) { - unnotify(); - module.tabNotification = Notify.tab(1000, 10); - } - }; - realtimeOptions.onRemote = function () { if (initializing) { return; } if (isHistoryMode) { return; } @@ -443,7 +371,7 @@ define([ cursor.update(); // Update the user list (metadata) from the hyperjson - updateMetadata(shjson); + Metadata.update(shjson); var newInner = JSON.parse(shjson); var newSInner; @@ -488,7 +416,7 @@ define([ // Notify only when the content has changed, not when someone has joined/left var oldSInner = stringify(JSON.parse(oldShjson)[2]); if (newSInner && newSInner !== oldSInner) { - notify(); + Cryptpad.notify(); } }; @@ -516,15 +444,14 @@ define([ realtimeOptions.onLocal(); }; - var renameCb = function (err, title) { - if (err) { return; } - document.title = title; - editor.fire('change'); - }; - realtimeOptions.onInit = function (info) { UserList = Cryptpad.createUserList(info, realtimeOptions.onLocal, Cryptget, Cryptpad); + var titleCfg = { getHeadingText: getHeadingText }; + Title = Cryptpad.createTitle(titleCfg, realtimeOptions.onLocal, Cryptpad); + + Metadata = Cryptpad.createMetadata(UserList, Title); + var configTb = { displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'], userList: UserList.getToolbarConfig(), @@ -532,11 +459,7 @@ define([ secret: secret, channel: info.channel }, - title: { - onRename: renameCb, - defaultName: defaultName, - suggestName: suggestName - }, + title: Title.getTitleConfig(), common: Cryptpad, readOnly: readOnly, ifrw: ifrw, @@ -546,6 +469,8 @@ define([ }; toolbar = info.realtime.toolbar = Toolbar.create(configTb); + Title.setToolbar(toolbar); + var $rightside = toolbar.$rightside; var editHash; @@ -666,13 +591,7 @@ define([ applyHjson(shjson); // Update the user list (metadata) from the hyperjson - updateMetadata(shjson); - - if (Visible.isSupported()) { - Visible.onChange(function (yes) { - if (yes) { unnotify(); } - }); - } + Metadata.update(shjson); if (!readOnly) { var shjson2 = stringifyDOM(inner); @@ -686,7 +605,7 @@ define([ } } } else { - updateTitle(Cryptpad.initialName || defaultName); + Title.updateTitle(Cryptpad.initialName || Title.defaultTitle); documentBody.innerHTML = Messages.initialState; } diff --git a/www/poll/main.js b/www/poll/main.js index 3881ddd87..9f1af0a2a 100644 --- a/www/poll/main.js +++ b/www/poll/main.js @@ -8,10 +8,8 @@ define([ '/bower_components/hyperjson/hyperjson.js', 'render.js', '/common/toolbar2.js', - '/common/visible.js', - '/common/notify.js', '/bower_components/file-saver/FileSaver.min.js' -], function ($, TextPatcher, Listmap, Crypto, Cryptpad, Cryptget, Hyperjson, Renderer, Toolbar, Visible, Notify) { +], function ($, TextPatcher, Listmap, Crypto, Cryptpad, Cryptget, Hyperjson, Renderer, Toolbar) { var Messages = Cryptpad.Messages; @@ -186,20 +184,6 @@ define([ } }; - var unnotify = function () { - if (APP.tabNotification && - typeof(APP.tabNotification.cancel) === 'function') { - APP.tabNotification.cancel(); - } - }; - - var notify = function () { - if (Visible.isSupported() && !Visible.currently()) { - unnotify(); - APP.tabNotification = Notify.tab(1000, 10); - } - }; - /* Any time the realtime object changes, call this function */ var change = function (o, n, path, throttle, cb) { if (path && !Cryptpad.isArray(path)) { @@ -228,7 +212,7 @@ define([ https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion */ - notify(); + Cryptpad.notify(); var getFocus = function () { var active = document.activeElement; @@ -442,43 +426,9 @@ define([ }); }; + var Title; var UserList; - var updateTitle = function (newTitle) { - if (newTitle === document.title) { return; } - // Change the title now, and set it back to the old value if there is an error - var oldTitle = document.title; - document.title = newTitle; - Cryptpad.renamePad(newTitle, function (err, data) { - if (err) { - debug("Couldn't set pad title"); - error(err); - document.title = oldTitle; - return; - } - document.title = data; - APP.$bar.find('.' + Toolbar.constants.title).find('span.title').text(data); - APP.$bar.find('.' + Toolbar.constants.title).find('input').val(data); - }); - }; - - var updateDefaultTitle = function (defaultTitle) { - defaultName = defaultTitle; - APP.$bar.find('.' + Toolbar.constants.title).find('input').attr("placeholder", defaultName); - }; - var renameCb = function (err, title) { - if (err) { return; } - document.title = title; - APP.proxy.info.title = title === defaultName ? "" : title; - }; - - var suggestName = function (fallback) { - if (document.title === defaultName) { - return fallback || ""; - } - return document.title || defaultName || ""; - }; - var copyObject = function (obj) { return JSON.parse(JSON.stringify(obj)); }; @@ -550,15 +500,15 @@ var ready = function (info, userid, readOnly) { // Title if (APP.proxy.info.defaultTitle) { - updateDefaultTitle(APP.proxy.info.defaultTitle); + Title.updateDefaultTitle(APP.proxy.info.defaultTitle); } else { - APP.proxy.info.defaultTitle = defaultName; + APP.proxy.info.defaultTitle = Title.defaultTitle; } if (Cryptpad.initialName && !APP.proxy.info.title) { APP.proxy.info.title = Cryptpad.initialName; - updateTitle(Cryptpad.initialName); + Title.updateTitle(Cryptpad.initialName); } else { - updateTitle(APP.proxy.info.title || defaultName); + Title.updateTitle(APP.proxy.info.title || Title.defaultTitle); } // Description @@ -586,8 +536,8 @@ var ready = function (info, userid, readOnly) { proxy .on('change', ['info'], function (o, n, p) { if (p[1] === 'title') { - updateTitle(n); - notify(); + Title.updateTitle(n); + Cryptpad.notify(); } else if (p[1] === "userData") { UserList.addToUserData(APP.proxy.info.userData); } else if (p[1] === 'description') { @@ -602,7 +552,7 @@ var ready = function (info, userid, readOnly) { el.selectionStart = selects[0]; el.selectionEnd = selects[1]; } - notify(); + Cryptpad.notify(); } debug("change: (%s, %s, [%s])", o, n, p.join(', ')); @@ -612,13 +562,6 @@ var ready = function (info, userid, readOnly) { UserList.addToUserData(APP.proxy.info.userData); - if (Visible.isSupported()) { - Visible.onChange(function (yes) { - if (yes) { unnotify(); } - }); - } - - APP.ready = true; if (!proxy.published) { publish(false); @@ -664,6 +607,11 @@ var create = function (info) { }; UserList = Cryptpad.createUserList(info, onLocal, Cryptget, Cryptpad); + var onLocalTitle = function () { + APP.proxy.info.title = Title.isDefaultTitle() ? "" : Title.title; + }; + Title = Cryptpad.createTitle({}, onLocalTitle, Cryptpad); + var configTb = { displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'], userList: UserList.getToolbarConfig(), @@ -671,11 +619,7 @@ var create = function (info) { secret: secret, channel: info.channel }, - title: { - onRename: renameCb, - defaultName: defaultName, - suggestName: suggestName - }, + title: Title.getTitleConfig(), common: Cryptpad, readOnly: readOnly, ifrw: window, @@ -685,6 +629,8 @@ var create = function (info) { }; APP.toolbar = Toolbar.create(configTb); + Title.setToolbar(APP.toolbar); + var $rightside = APP.toolbar.$rightside; /* add a forget button */ diff --git a/www/slide/main.js b/www/slide/main.js index 2b229c14b..0d7de7758 100644 --- a/www/slide/main.js +++ b/www/slide/main.js @@ -8,15 +8,8 @@ define([ '/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', '/slide/slide.js', - '/bower_components/file-saver/FileSaver.min.js' -], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Modes, Themes, Visible, Notify, Slide) { - var saveAs = window.saveAs; - +], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Slide) { var Messages = Cryptpad.Messages; var module = window.APP = { @@ -30,23 +23,15 @@ define([ var SLIDE_COLOR_ID = "cryptpad-color"; - var stringify = function (obj) { - return JSONSortify(obj); - }; - - var setTabTitle = function () { - var slideNumber = ''; - if (Slide.index && Slide.content.length) { - slideNumber = ' (' + Slide.index + '/' + Slide.content.length + ')'; - } - document.title = APP.title + slideNumber; - }; - $(function () { Cryptpad.addLoadingScreen(); + var stringify = function (obj) { + return JSONSortify(obj); + }; var ifrw = module.ifrw = $('#pad-iframe')[0].contentWindow; var toolbar; + var editor; var secret = Cryptpad.getSecrets(); var readOnly = secret.keys && !secret.keys.editKeyStr; @@ -62,77 +47,32 @@ define([ }; var andThen = function (CMeditor) { - var CodeMirror = module.CodeMirror = CMeditor; - CodeMirror.modeURL = "/bower_components/codemirror/mode/%N/%N.js"; - var $pad = $('#pad-iframe'); - var $textarea = $pad.contents().find('#editor1'); + var CodeMirror = Cryptpad.createCodemirror(CMeditor, ifrw, Cryptpad); + editor = CodeMirror.editor; var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox'); - var parsedHash = Cryptpad.parsePadUrl(window.location.href); - var defaultName = Cryptpad.getDefaultName(parsedHash); - var initialState = Messages.slideInitialState; + var $pad = $('#pad-iframe'); var isHistoryMode = false; - var editor = module.editor = CMeditor.fromTextArea($textarea[0], { - lineNumbers: true, - lineWrapping: true, - autoCloseBrackets: true, - matchBrackets : true, - showTrailingSpace : true, - styleActiveLine : true, - search: true, - highlightSelectionMatches: {showToken: /\w+/}, - extraKeys: {"Shift-Ctrl-R": undefined}, - foldGutter: true, - gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], - mode: "javascript", - readOnly: true - }); - editor.setValue(initialState); - - var setMode = module.setMode = function (mode, $select) { - module.highlightMode = mode; - if (mode === 'text') { - editor.setOption('mode', 'text'); - return; - } - CodeMirror.autoLoadMode(editor, mode); - editor.setOption('mode', mode); - if ($select && $select.val) { $select.val(mode); } + var setEditable = module.setEditable = function (bool) { + if (readOnly && bool) { return; } + editor.setOption('readOnly', !bool); }; - setMode('markdown'); - - var setTheme = module.setTheme = (function () { - var path = '/common/theme/'; - var $head = $(ifrw.document.head); - - var themeLoaded = module.themeLoaded = function (theme) { - return $head.find('link[href*="'+theme+'"]').length; - }; + var Title; + var UserList; + var Metadata; - var loadTheme = module.loadTheme = function (theme) { - $head.append($('', { - rel: 'stylesheet', - href: path + theme + '.css', - })); - }; + var setTabTitle = function (title) { + var slideNumber = ''; + if (Slide.index && Slide.content.length) { + slideNumber = ' (' + Slide.index + '/' + Slide.content.length + ')'; + } + document.title = title + slideNumber; + }; - return function (theme, $select) { - if (!theme) { - editor.setOption('theme', 'default'); - } else { - if (!themeLoaded(theme)) { - loadTheme(theme); - } - editor.setOption('theme', theme); - } - if ($select) { - $select.setValue(theme || 'Theme'); - } - }; - }()); + var initialState = Messages.slideInitialState; var $modal = $pad.contents().find('#modal'); var $content = $pad.contents().find('#content'); @@ -162,18 +102,10 @@ define([ enterPresentationMode(true); } - var setEditable = module.setEditable = function (bool) { - if (readOnly && bool) { return; } - editor.setOption('readOnly', !bool); - }; - - var UserList; - var textColor; var backColor; var config = { - //initialState: Messages.codeInitialState, initialState: '{}', websocketURL: Cryptpad.getWebsocketURL(), channel: secret.channel, @@ -202,12 +134,12 @@ define([ content: textValue, metadata: { users: UserList.userData, - defaultTitle: defaultName, + defaultTitle: Title.defaultTitle, slideOptions: slideOptions } }; if (!initializing) { - obj.metadata.title = APP.title; + obj.metadata.title = Title.title; } if (textColor) { obj.metadata.color = textColor; @@ -226,7 +158,7 @@ define([ editor.save(); - var textValue = canonicalize($textarea.val()); + var textValue = canonicalize(CodeMirror.$textarea.val()); var shjson = stringifyInner(textValue); module.patchText(shjson); @@ -237,156 +169,29 @@ define([ } }; - var getHeadingText = function () { - var lines = editor.getValue().split(/\n/); - - var text = ''; - lines.some(function (line) { - // lines beginning with a hash are potentially valuable - // works for markdown, python, bash, etc. - var hash = /^#(.*?)$/; - if (hash.test(line)) { - line.replace(hash, function (a, one) { - text = one; - }); - return true; + var metadataCfg = { + slideColors: function (text, back) { + if (text) { + textColor = text; + $modal.css('color', text); + $modal.css('border-color', text); + $pad.contents().find('#' + SLIDE_COLOR_ID).css('color', text); } - }); - - return text.trim(); - }; - - var suggestName = function () { - if (APP.title === defaultName) { - return getHeadingText() || ""; - } else { - return APP.title || getHeadingText() || defaultName; - } - }; - - var exportText = module.exportText = function () { - var text = editor.getValue(); - - var ext = Modes.extensionOf(module.highlightMode); - - var title = Cryptpad.fixFileName(suggestName()) + ext; - - Cryptpad.prompt(Messages.exportPrompt, title, function (filename) { - if (filename === null) { return; } - var blob = new Blob([text], { - type: 'text/plain;charset=utf-8' - }); - saveAs(blob, filename); - }); - }; - var importText = function (content, file) { - var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox'); - var mode; - var mime = CodeMirror.findModeByMIME(file.type); - - if (!mime) { - var ext = /.+\.([^.]+)$/.exec(file.name); - if (ext[1]) { - mode = CodeMirror.findModeByExtension(ext[1]); + if (back) { + backColor = back; + $modal.css('background-color', back); + $pad.contents().find('#' + SLIDE_COLOR_ID).css('background', back); + $pad.contents().find('#' + SLIDE_BACKCOLOR_ID).css('color', back); } - } else { - mode = mime && mime.mode || null; - } - - if (mode && Modes.list.some(function (o) { return o.mode === mode; })) { - setMode(mode); - $bar.find('#language-mode').val(mode); - } else { - console.log("Couldn't find a suitable highlighting mode: %s", mode); - setMode('text'); - $bar.find('#language-mode').val('text'); - } - - editor.setValue(content); - onLocal(); - }; - - var updateTitle = function (newTitle) { - if (newTitle === APP.title) { return; } - // Change the title now, and set it back to the old value if there is an error - var oldTitle = APP.title; - APP.title = newTitle; - setTabTitle(); - Cryptpad.renamePad(newTitle, function (err, data) { - if (err) { - console.log("Couldn't set pad title"); - console.error(err); - APP.title = oldTitle; - setTabTitle(); - return; + }, + slideOptions: function (newOpt) { + if (stringify(newOpt) !== stringify(slideOptions)) { + $.extend(slideOptions, newOpt); + // TODO: manage realtime + cursor in the "options" modal ?? + Slide.updateOptions(); } - APP.title = data; - setTabTitle(); - $bar.find('.' + Toolbar.constants.title).find('span.title').text(data); - $bar.find('.' + Toolbar.constants.title).find('input').val(data); - if (slideOptions.title) { Slide.updateOptions(); } - }); - }; - - var updateColors = function (text, back) { - if (text) { - textColor = text; - $modal.css('color', text); - $modal.css('border-color', text); - $pad.contents().find('#' + SLIDE_COLOR_ID).css('color', text); - } - if (back) { - backColor = back; - $modal.css('background-color', back); - $pad.contents().find('#' + SLIDE_COLOR_ID).css('background', back); - $pad.contents().find('#' + SLIDE_BACKCOLOR_ID).css('color', back); - } - }; - - var updateOptions = function (newOpt) { - if (stringify(newOpt) !== stringify(slideOptions)) { - $.extend(slideOptions, newOpt); - // TODO: manage realtime + cursor in the "options" modal ?? - Slide.updateOptions(); } - }; - - var updateDefaultTitle = function (defaultTitle) { - defaultName = defaultTitle; - $bar.find('.' + Toolbar.constants.title).find('input').attr("placeholder", defaultName); - }; - - var updateMetadata = function(shjson) { - // Extract the user list (metadata) from the hyperjson - var json = (shjson === "") ? "" : JSON.parse(shjson); - var titleUpdated = false; - if (json && json.metadata) { - if (json.metadata.users) { - var userData = json.metadata.users; - // Update the local user data - UserList.addToUserData(userData); - } - if (json.metadata.defaultTitle) { - updateDefaultTitle(json.metadata.defaultTitle); - } - if (typeof json.metadata.title !== "undefined") { - updateTitle(json.metadata.title || defaultName); - titleUpdated = true; - } - updateOptions(json.metadata.slideOptions); - updateColors(json.metadata.color, json.metadata.backColor); - } - if (!titleUpdated) { - updateTitle(defaultName); - } - }; - - var renameCb = function (err, title) { - if (err) { return; } - APP.title = title; - setTabTitle(); - onLocal(); - }; + } var createPrintDialog = function () { var slideOptionsTmp = { @@ -461,6 +266,14 @@ define([ config.onInit = function (info) { UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad); + var titleCfg = { + updateLocalTitle: setTabTitle, + getHeadingText: CodeMirror.getHeadingText + }; + Title = Cryptpad.createTitle(titleCfg, config.onLocal, Cryptpad); + + Metadata = Cryptpad.createMetadata(UserList, Title, metadataCfg); + var configTb = { displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'], userList: UserList.getToolbarConfig(), @@ -468,11 +281,7 @@ define([ secret: secret, channel: info.channel }, - title: { - onRename: renameCb, - defaultName: defaultName, - suggestName: suggestName - }, + title: Title.getTitleConfig(), common: Cryptpad, readOnly: readOnly, ifrw: ifrw, @@ -482,6 +291,9 @@ define([ }; toolbar = module.toolbar = Toolbar.create(configTb); + Title.setToolbar(toolbar); + CodeMirror.init(config.onLocal, Title, toolbar); + var $rightside = toolbar.$rightside; var editHash; @@ -534,12 +346,12 @@ define([ } /* add an export button */ - var $export = Cryptpad.createButton('export', true, {}, exportText); + var $export = Cryptpad.createButton('export', true, {}, CodeMirror.exportText); $rightside.append($export); if (!readOnly) { /* add an import button */ - var $import = Cryptpad.createButton('import', true, {}, importText); + var $import = Cryptpad.createButton('import', true, {}, CodeMirror.importText); $rightside.append($import); } @@ -586,49 +398,6 @@ define([ } $rightside.append($present); - var $leavePresent = Cryptpad.createButton('source', true) - .click(leavePresentationMode); - if (!presentMode) { - $leavePresent.hide(); - } - $rightside.append($leavePresent); - - var configureTheme = function () { - /* Remember the user's last choice of theme using localStorage */ - var themeKey = 'CRYPTPAD_CODE_THEME'; - var lastTheme = localStorage.getItem(themeKey) || 'default'; - - var options = []; - Themes.forEach(function (l) { - options.push({ - tag: 'a', - attributes: { - 'data-value': l.name, - 'href': '#', - }, - content: l.name // Pretty name of the language value - }); - }); - var dropdownConfig = { - text: 'Theme', // Button initial text - options: options, // Entries displayed in the menu - left: true, // Open to the left of the button - isSelect: true, - initialValue: lastTheme - }; - var $block = module.$theme = Cryptpad.createDropdown(dropdownConfig); - - setTheme(lastTheme, $block); - - $block.find('a').click(function () { - var theme = $(this).attr('data-value'); - setTheme(theme, $block); - localStorage.setItem(themeKey, theme); - }); - - $rightside.append($block); - }; - var configureColors = function () { var $back = $('