require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } }); define([ '/api/config?cb=' + Math.random().toString(16).substring(2), '/customize/messages.js', '/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-netflux/chainpad-netflux.js', '/bower_components/textpatcher/TextPatcher.amd.js', '/common/toolbar.js', 'json.sortify', '/bower_components/chainpad-json-validator/json-ot.js', '/common/cryptpad-common.js', '/code/modes.js', '/code/themes.js', '/common/visible.js', '/common/notify.js', '/bower_components/file-saver/FileSaver.min.js', '/bower_components/jquery/dist/jquery.min.js', '/customize/pad.js' ], function (Config, /*RTCode,*/ Messages, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Modes, Themes, Visible, Notify) { var $ = window.jQuery; var saveAs = window.saveAs; var module = window.APP = { Cryptpad: Cryptpad, spinner: Cryptpad.spinner(document.body), }; Cryptpad.styleAlerts(); module.spinner.show(); var ifrw = module.ifrw = $('#pad-iframe')[0].contentWindow; var stringify = function (obj) { return JSONSortify(obj); }; $(function () { var toolbar; var secret = Cryptpad.getSecrets(); var readOnly = secret.keys && !secret.keys.editKeyStr; if (!secret.keys) { secret.keys = secret.key; } var andThen = function (CMeditor) { var CodeMirror = module.CodeMirror = CMeditor; CodeMirror.modeURL = "/code/codemirror-5.16.0/mode/%N/%N.js"; var $pad = $('#pad-iframe'); var $textarea = $pad.contents().find('#editor1'); 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: {"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }}, foldGutter: true, gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], mode: "javascript", readOnly: true }); 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); } }; editor.setValue(Messages.codeInitialState); // HERE var setTheme = module.setTheme = (function () { var path = './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.val) { $select.val(theme || 'default'); } }; }()); var setEditable = module.setEditable = function (bool) { if (readOnly && bool) { return; } editor.setOption('readOnly', !bool); }; var userList = {}; // List of pretty name of all users (mapped with their server ID) var toolbarList; // List of users still connected to the channel (server IDs) var addToUserList = function(data) { for (var attrname in data) { userList[attrname] = data[attrname]; } if(toolbarList && typeof toolbarList.onChange === "function") { toolbarList.onChange(userList); } }; var myData = {}; var myUserName = ''; // My "pretty name" var myID; // My server ID var setMyID = function(info) { myID = info.myID || null; myUserName = myID; }; var config = { //initialState: Messages.codeInitialState, initialState: '{}', websocketURL: Config.websocketURL, channel: secret.channel, // our public key validateKey: secret.keys.validateKey || undefined, readOnly: readOnly, crypto: Crypto.createEncryptor(secret.keys), setMyID: setMyID, transformFunction: JsonOT.validate }; var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); }; var initializing = true; var onLocal = config.onLocal = function () { if (initializing) { return; } if (readOnly) { return; } editor.save(); var textValue = canonicalize($textarea.val()); var obj = {content: textValue}; // append the userlist to the hyperjson structure obj.metadata = { users: userList, title: document.title }; // set mode too... obj.highlightMode = module.highlightMode; // stringify the json and send it into chainpad var shjson = stringify(obj); module.patchText(shjson); if (module.realtime.getUserDoc() !== shjson) { console.error("realtime.getUserDoc() !== shjson"); } }; var setName = module.setName = function (newName) { if (typeof(newName) !== 'string') { return; } var myUserNameTemp = newName.trim(); if(newName.trim().length > 32) { myUserNameTemp = myUserNameTemp.substr(0, 32); } myUserName = myUserNameTemp; myData[myID] = { name: myUserName }; addToUserList(myData); Cryptpad.setAttribute('username', myUserName, function (err, data) { if (err) { console.log("Couldn't set username"); console.error(err); return; } onLocal(); }); }; var getLastName = function (cb) { Cryptpad.getAttribute('username', function (err, userName) { cb(err, userName || ''); }); }; 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) { text = two; }); return true; } }); return text.trim(); }; var suggestName = function () { var parsed = Cryptpad.parsePadUrl(window.location.href); var name = Cryptpad.getDefaultName(parsed, []); if (document.title.slice(0, name.length) === name) { return getHeadingText() || document.title; } else { return document.title || getHeadingText() || name; } }; 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 onInit = config.onInit = function (info) { var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox'); toolbarList = info.userList; var config = { userData: userList, readOnly: readOnly }; if (readOnly) {delete config.changeNameID; } toolbar = module.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, info.userList, config); var $rightside = $bar.find('.' + Toolbar.constants.rightside); var editHash; var viewHash = Cryptpad.getViewHashFromKeys(info.channel, secret.keys); if (!readOnly) { editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys); } getLastName(function (err, lastName) { var $username = Cryptpad.createButton('username', true) .click(function() { Cryptpad.prompt(Messages.changeNamePrompt, lastName, function (newName) { setName(newName); }); }); $rightside.append($username); }); /* add an export button */ var $export = Cryptpad.createButton('export', true).click(exportText); $rightside.append($export); if (!readOnly) { /* add an import button */ var $import = Cryptpad.createButton('import', true) .click(Cryptpad.importContent('text/plain', function (content, file) { 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(); })); $rightside.append($import); } /* add a rename button */ var $setTitle = Cryptpad.createButton('rename', true) .click(function () { var suggestion = suggestName(); Cryptpad.prompt(Messages.renamePrompt, suggestion, function (title, ev) { if (title === null) { return; } Cryptpad.causesNamingConflict(title, function (err, conflicts) { if (err) { console.log("Unable to determine if name caused a conflict"); console.error(err); return; } if (conflicts) { Cryptpad.alert(Messages.renameConflict); return; } Cryptpad.setPadTitle(title, function (err, data) { if (err) { console.log("unable to set pad title"); console.log(err); return; } document.title = title; onLocal(); }); }); }); }); $rightside.append($setTitle); /* add a forget button */ var $forgetPad = Cryptpad.createButton('forget', true) .click(function () { var href = window.location.href; Cryptpad.confirm(Messages.forgetPrompt, function (yes) { if (!yes) { return; } Cryptpad.forgetPad(href, function (err, data) { if (err) { console.log("unable to forget pad"); console.error(err); return; } var parsed = Cryptpad.parsePadUrl(href); document.title = Cryptpad.getDefaultName(parsed, []); }); }); }); $rightside.append($forgetPad); if (!readOnly && viewHash) { /* add a 'links' button */ var $links = Cryptpad.createButton('readonly', true) .click(function () { var baseUrl = window.location.origin + window.location.pathname + '#'; var content = '' + Messages.readonlyUrl + '
' + baseUrl + viewHash + '
'; Cryptpad.alert(content); }); $rightside.append($links); } var configureLanguage = function (cb) { // FIXME this is async so make it happen as early as possible /* Let the user select different syntax highlighting modes */ var $language = module.$language = $('', { title: 'color theme', id: 'display-theme', }); Themes.forEach(function (o) { $themeDropdown.append($('