diff --git a/customize.dist/src/less2/include/app-noscroll.less b/customize.dist/src/less2/include/app-noscroll.less index 81b1a725c..c66019d61 100644 --- a/customize.dist/src/less2/include/app-noscroll.less +++ b/customize.dist/src/less2/include/app-noscroll.less @@ -1,5 +1,5 @@ // html -.noscroll_main () { +.app-noscroll_main () { height: 100%; width: 100%; padding: 0px; diff --git a/customize.dist/src/less2/include/app-print.less b/customize.dist/src/less2/include/app-print.less new file mode 100644 index 000000000..d25110056 --- /dev/null +++ b/customize.dist/src/less2/include/app-print.less @@ -0,0 +1,46 @@ +.app-print_main () { + // Current scope is + @media print { + height: auto; + max-height: none; + overflow: visible; + display:block; + @page { + margin: 0; + size: landscape; + } + // Slide app + body.cp-app-slide { + display: block; + .CodeMirror, #cme_toolbox { + display: none; + } + * { + visibility: hidden; + height: auto; + max-height: none; + } + .cp-app-slide-viewer #cp-app-slide-print { + display: block; + visibility: visible; + * { + visibility: visible; + } + } + .cp-app-slide-viewer #cp-app-slide-modal { + display: none !important; + } + .cp-app-slide-viewer { + flex: 1 !important; + overflow: visible !important; + } + .cp-toolbar-userlist-drawer { + display: none !important; + } + #cp-app-slide-editor { + height: auto; + display: block; + } + } + } +} diff --git a/customize.dist/src/less2/main.less b/customize.dist/src/less2/main.less index 9b4f18ffe..7b7f8229a 100644 --- a/customize.dist/src/less2/main.less +++ b/customize.dist/src/less2/main.less @@ -14,10 +14,16 @@ body.cp-page-terms { @import "./pages/page-terms.less"; } // Set the HTML style for the apps which shouldn't have a body scrollbar html.cp-app-noscroll { @import "./include/app-noscroll.less"; - .noscroll_main(); + .app-noscroll_main(); +} +// Set the HTML style for printing slides +html.cp-app-print { + @import "./include/app-print.less"; + .app-print_main(); } body.cp-app-pad { @import "../../../pad/app-pad.less"; } body.cp-app-code { @import "../../../code/app-code.less"; } +body.cp-app-slide { @import "../../../slide/app-slide.less"; } body.cp-app-filepicker { @import "../../../filepicker/app-filepicker.less"; } diff --git a/www/code/app-code.less b/www/code/app-code.less index b3fab41b5..4ed6e2837 100644 --- a/www/code/app-code.less +++ b/www/code/app-code.less @@ -24,7 +24,7 @@ resize: horizontal; font-size: initial; } - .CodeMirror.fullPage { + .CodeMirror.cp-app-code-fullpage { //min-width: 100%; max-width: 100%; resize: none; diff --git a/www/code/inner.js b/www/code/inner.js index 721e162aa..dc541e736 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -86,6 +86,39 @@ define([ var isHistoryMode = false; + var setEditable = APP.setEditable = function (bool) { + if (readOnly && bool) { return; } + editor.setOption('readOnly', !bool); + }; + + var Title; + + var config = { + readOnly: readOnly, + transformFunction: JsonOT.validate, + // cryptpad debug logging (default is 1) + // logLevel: 0, + validateContent: function (content) { + try { + JSON.parse(content); + return true; + } catch (e) { + console.log("Failed to parse, rejecting patch"); + return false; + } + } + }; + + var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); }; + + var setHistory = function (bool, update) { + isHistoryMode = bool; + setEditable(!bool); + if (!bool && update) { + config.onRemote(); + } + }; + var $contentContainer = $('#cp-app-code-editor'); var $previewContainer = $('#cp-app-code-preview'); var $preview = $('#cp-app-code-preview-content'); @@ -120,39 +153,6 @@ define([ typeof(useTabs) === 'boolean'? useTabs: false); }; - var setEditable = APP.setEditable = function (bool) { - if (readOnly && bool) { return; } - editor.setOption('readOnly', !bool); - }; - - var Title; - - var config = { - readOnly: readOnly, - transformFunction: JsonOT.validate, - // cryptpad debug logging (default is 1) - // logLevel: 0, - validateContent: function (content) { - try { - JSON.parse(content); - return true; - } catch (e) { - console.log("Failed to parse, rejecting patch"); - return false; - } - } - }; - - var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); }; - - var setHistory = function (bool, update) { - isHistoryMode = bool; - setEditable(!bool); - if (!bool && update) { - config.onRemote(); - } - }; - CommonRealtime.onInfiniteSpinner(function () { setEditable(false); }); setEditable(false); @@ -336,14 +336,14 @@ define([ $previewContainer.toggle(); if ($previewContainer.is(':visible')) { forceDrawPreview(); - $codeMirror.removeClass('fullPage'); - $previewButton.addClass('active'); + $codeMirror.removeClass('cp-ap-code-fullpage'); + $previewButton.addClass('cp-toolbar-button-active'); common.setPadAttribute('previewMode', true, function (e) { if (e) { return console.log(e); } }); } else { - $codeMirror.addClass('fullPage'); - $previewButton.removeClass('active'); + $codeMirror.addClass('cp-app-code-fullpage'); + $previewButton.removeClass('cp-toolbar-button-active'); common.setPadAttribute('previewMode', false, function (e) { if (e) { return console.log(e); } }); @@ -574,30 +574,7 @@ define([ Cryptpad.onLogout(function () { setEditable(false); }); }; -/* - var interval = 100; - var second = function (CM) { - Cryptpad.ready(function () { - andThen(CM); - Cryptpad.reportAppUsage(); - }); - Cryptpad.onError(function (info) { - if (info && info.type === "store") { - onConnectError(); - } - }); - }; - - var first = function () { - if (ifrw.CodeMirror) { - second(ifrw.CodeMirror); - } else { - console.log("CodeMirror was not defined. Trying again in %sms", interval); - setTimeout(first, interval); - } - }; - first();*/ var CMEDITOR_CHECK_INTERVAL = 100; var cmEditorAvailable = function (cb) { var intr; @@ -628,7 +605,7 @@ define([ })); SFCommon.create(waitFor(function (c) { APP.common = common = c; })); }).nThen(function (/*waitFor*/) { - CodeMirror = Cryptpad.createCodemirror(window, Cryptpad, null, CM); + CodeMirror = common.initCodeMirrorApp(null, CM); $('.CodeMirror').addClass('fullPage'); editor = CodeMirror.editor; Cryptpad.onError(function (info) { diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index d90f586ea..3f8e3ba2c 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -493,11 +493,6 @@ define([ if (cb) { cb(err, data); } }); }; - /*common.setAttribute = function (attr, value, cb) { - getStore().set(["cryptpad", attr].join('.'), value, function (err, data) { - if (cb) { cb(err, data); } - }); - };*/ common.setLSAttribute = function (attr, value) { localStorage[attr] = value; }; @@ -512,11 +507,6 @@ define([ cb(err, data); }); }; - /*common.getAttribute = function (attr, cb) { - getStore().get(["cryptpad", attr].join('.'), function (err, data) { - cb(err, data); - }); - };*/ /* this returns a reference to your proxy. changing it will change your drive. */ @@ -1717,7 +1707,7 @@ define([ var setActive = function ($el) { if ($el.length !== 1) { return; } - $innerblock.find('.cp-dropdown-element-active').removeClass('cp-dropdown-element(active'); + $innerblock.find('.cp-dropdown-element-active').removeClass('cp-dropdown-element-active'); $el.addClass('cp-dropdown-element-active'); var scroll = $el.position().top + $innerblock.scrollTop(); if (scroll < $innerblock.scrollTop()) { diff --git a/www/common/migrate-user-object.js b/www/common/migrate-user-object.js index dbf7b5727..cc088c7c5 100644 --- a/www/common/migrate-user-object.js +++ b/www/common/migrate-user-object.js @@ -8,7 +8,7 @@ define([], function () { var version = userObject.version || 0; // Migration 1: pad attributes moved to filesData - var migrateAttributes = function () { + var migratePadAttributesToData = function () { var files = userObject && userObject.drive; if (!files) { return; } @@ -39,7 +39,7 @@ define([], function () { // Migration done }; if (version < 1) { - migrateAttributes(); + migratePadAttributesToData(); Cryptpad.feedback('Migrate-1', true); userObject.version = version = 1; } diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js new file mode 100644 index 000000000..4c0fe5cfc --- /dev/null +++ b/www/common/sframe-common-codemirror.js @@ -0,0 +1,312 @@ +define([ + 'jquery', + '/common/modes.js', + '/common/themes.js', + '/common/cryptpad-common.js', + + '/bower_components/file-saver/FileSaver.min.js' +], function ($, Modes, Themes, Cryptpad) { + var saveAs = window.saveAs; + var module = {}; + + module.create = function (Common, defaultMode, CMeditor) { + var exp = {}; + var Messages = Cryptpad.Messages; + + var CodeMirror = exp.CodeMirror = CMeditor; + CodeMirror.modeURL = "cm/mode/%N/%N"; + + var $pad = $('#pad-iframe'); + var $textarea = exp.$textarea = $('#editor1'); + if (!$textarea.length) { $textarea = exp.$textarea = $pad.contents().find('#editor1'); } + + var Title; + var onLocal = function () {}; + var $rightside; + var $drawer; + exp.init = function (local, title, toolbar) { + if (typeof local === "function") { + onLocal = local; + } + Title = title; + $rightside = toolbar.$rightside; + $drawer = toolbar.$drawer; + }; + + 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: defaultMode || "javascript", + readOnly: true + }); + editor.setValue(Messages.codeInitialState); + + var setMode = exp.setMode = function (mode, cb) { + exp.highlightMode = mode; + if (mode !== "text") { + CMeditor.autoLoadMode(editor, mode); + } + editor.setOption('mode', mode); + if (exp.$language) { + var name = exp.$language.find('a[data-value="' + mode + '"]').text() || undefined; + name = name ? Messages.languageButton + ' ('+name+')' : Messages.languageButton; + exp.$language.setValue(mode, name); + } + if(cb) { cb(mode); } + }; + + var setTheme = exp.setTheme = (function () { + var path = '/common/theme/'; + + var $head = $(window.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) { + var name = theme || undefined; + name = name ? Messages.themeButton + ' ('+theme+')' : Messages.themeButton; + $select.setValue(theme, name); + } + }; + }()); + + exp.getHeadingText = function () { + var lines = editor.getValue().split(/\n/); + + var text = ''; + lines.some(function (line) { + // 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; + } + + // 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; + } + + // TODO make one more pass for multiline comments + }); + + return text.trim(); + }; + + exp.configureLanguage = function (cb, onModeChanged) { + 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, + feedback: 'CODE_LANGUAGE', + }; + var $block = exp.$language = Cryptpad.createDropdown(dropdownConfig); + $block.find('button').attr('title', Messages.languageButtonTitle); + $block.find('a').click(function () { + setMode($(this).attr('data-value'), onModeChanged); + onLocal(); + }); + + if ($drawer) { $drawer.append($block); } + if (cb) { cb(); } + }; + + exp.configureTheme = function (cb) { + /* Remember the user's last choice of theme using localStorage */ + var themeKey = ['codemirror', 'theme']; + + var todo = function (err, lastTheme) { + lastTheme = lastTheme || '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, + feedback: 'CODE_THEME', + }; + var $block = exp.$theme = Cryptpad.createDropdown(dropdownConfig); + $block.find('button').attr('title', Messages.themeButtonTitle); + + setTheme(lastTheme, $block); + + $block.find('a').click(function () { + var theme = $(this).attr('data-value'); + setTheme(theme, $block); + Common.setAttribute(themeKey, theme); + }); + + if ($drawer) { $drawer.append($block); } + if (cb) { cb(); } + }; + Common.getAttribute(themeKey, todo); + }; + + 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 = $('#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]); + mode = mode && mode.mode || null; + } + } 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 cursorToPos = function(cursor, oldText) { + var cLine = cursor.line; + var cCh = cursor.ch; + var pos = 0; + var textLines = oldText.split("\n"); + for (var line = 0; line <= cLine; line++) { + if(line < cLine) { + pos += textLines[line].length+1; + } + else if(line === cLine) { + pos += cCh; + } + } + return pos; + }; + + var posToCursor = function(position, newText) { + var cursor = { + line: 0, + ch: 0 + }; + var textLines = newText.substr(0, position).split("\n"); + cursor.line = textLines.length - 1; + cursor.ch = textLines[cursor.line].length; + return cursor; + }; + + exp.setValueAndCursor = function (oldDoc, remoteDoc, TextPatcher) { + var scroll = editor.getScrollInfo(); + //get old cursor here + var oldCursor = {}; + oldCursor.selectionStart = cursorToPos(editor.getCursor('from'), oldDoc); + oldCursor.selectionEnd = cursorToPos(editor.getCursor('to'), oldDoc); + + editor.setValue(remoteDoc); + editor.save(); + + var op = TextPatcher.diff(oldDoc, remoteDoc); + var selects = ['selectionStart', 'selectionEnd'].map(function (attr) { + return TextPatcher.transformCursor(oldCursor[attr], op); + }); + + if(selects[0] === selects[1]) { + editor.setCursor(posToCursor(selects[0], remoteDoc)); + } + else { + editor.setSelection(posToCursor(selects[0], remoteDoc), posToCursor(selects[1], remoteDoc)); + } + + editor.scrollTo(scroll.left, scroll.top); + }; + + return exp; + }; + + return module; +}); + diff --git a/www/common/sframe-common-interface.js b/www/common/sframe-common-interface.js index 69060b3b1..29a852435 100644 --- a/www/common/sframe-common-interface.js +++ b/www/common/sframe-common-interface.js @@ -26,6 +26,7 @@ define([ var AppConfig = common.getAppConfig(); var button; var size = "17px"; + var sframeChan = common.getSframeChannel(); switch (type) { case 'export': button = $('