diff --git a/Makefile b/Makefile index 4e870a452..b656d5d9b 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ outdoc_files = $(addprefix out/,$(doc_sources:.md=.html)) docassets = $(addprefix out/,$(wildcard doc/assets/*)) VERSION = $(shell node -e "console.log( require('./src/package.json').version )") +UNAME := $(shell uname -s) docs: $(outdoc_files) $(docassets) @@ -14,7 +15,11 @@ out/doc/assets/%: doc/assets/% out/doc/%.html: doc/%.md mkdir -p $(@D) node tools/doc/generate.js --format=html --template=doc/template.html $< > $@ - cat $@ | sed 's/__VERSION__/${VERSION}/' > $@ +ifeq ($(UNAME),Darwin) + sed -i '' 's/__VERSION__/${VERSION}/' $@ +else + sed -i 's/__VERSION__/${VERSION}/' $@ +endif clean: rm -rf out/ diff --git a/doc/api/http_api.md b/doc/api/http_api.md index 0d2cc3756..a7fc0bedd 100644 --- a/doc/api/http_api.md +++ b/doc/api/http_api.md @@ -292,6 +292,34 @@ returns the text of a pad formatted as HTML * `{code: 0, message:"ok", data: {html:"Welcome Text
More Text"}}` * `{code: 1, message:"padID does not exist", data: null}` +### Chat +#### getChatHistory(padID, [start, end]) + * API >= 1.2.7 + +returns + +* a part of the chat history, when `start` and `end` are given +* the whole chat histroy, when no extra parameters are given + + +*Example returns:* + +* `{"code":0,"message":"ok","data":{"messages":[{"text":"foo","userId":"a.foo","time":1359199533759,"userName":"test"},{"text":"bar","userId":"a.foo","time":1359199534622,"userName":"test"}]}}` +* `{code: 1, message:"start is higher or equal to the current chatHead", data: null}` +* `{code: 1, message:"padID does not exist", data: null}` + +#### getChatHead(padID) + * API >= 1.2.7 + +returns the chatHead (last number of the last chat-message) of the pad + + +*Example returns:* + +* `{code: 0, message:"ok", data: {chatHead: 42}}` +* `{code: 1, message:"padID does not exist", data: null}` + + ### Pad Group pads are normal pads, but with the name schema GROUPID$PADNAME. A security manager controls access of them and its forbidden for normal pads to include a $ in the name. diff --git a/settings.json.template b/settings.json.template index 04c097517..4b18d7800 100644 --- a/settings.json.template +++ b/settings.json.template @@ -101,5 +101,5 @@ "loglevel": "INFO", // restrict socket.io transport methods - "socketTransportProtocols" : ['xhr-polling', 'jsonp-polling', 'htmlfile'] + "socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"] } diff --git a/src/locales/en.json b/src/locales/en.json index 37e07a154..eea35cc53 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1 +1,115 @@ -{"index.newPad":"New Pad","index.createOpenPad":"or create/open a Pad with the name:","pad.toolbar.bold.title":"Bold (Ctrl-B)","pad.toolbar.italic.title":"Italic (Ctrl-I)","pad.toolbar.underline.title":"Underline (Ctrl-U)","pad.toolbar.strikethrough.title":"Strikethrough","pad.toolbar.ol.title":"Ordered list","pad.toolbar.ul.title":"Unordered List","pad.toolbar.indent.title":"Indent","pad.toolbar.unindent.title":"Outdent","pad.toolbar.undo.title":"Undo (Ctrl-Z)","pad.toolbar.redo.title":"Redo (Ctrl-Y)","pad.toolbar.clearAuthorship.title":"Clear Authorship Colors","pad.toolbar.import_export.title":"Import/Export from/to different file formats","pad.toolbar.timeslider.title":"Timeslider","pad.toolbar.savedRevision.title":"Saved Revisions","pad.toolbar.settings.title":"Settings","pad.toolbar.embed.title":"Embed this pad","pad.toolbar.showusers.title":"Show the users on this pad","pad.colorpicker.save":"Save","pad.colorpicker.cancel":"Cancel","pad.loading":"Loading...","pad.passwordRequired":"You need a password to access this pad","pad.permissionDenied":"You do not have permission to access this pad","pad.wrongPassword":"Your password was wrong","pad.settings.padSettings":"Pad Settings","pad.settings.myView":"My View","pad.settings.stickychat":"Chat always on screen","pad.settings.colorcheck":"Authorship colors","pad.settings.linenocheck":"Line numbers","pad.settings.fontType":"Font type:","pad.settings.fontType.normal":"Normal","pad.settings.fontType.monospaced":"Monospace","pad.settings.globalView":"Global View","pad.settings.language":"Language:","pad.importExport.import_export":"Import/Export","pad.importExport.import":"Upload any text file or document","pad.importExport.importSuccessful":"Successful!","pad.importExport.export":"Export current pad as:","pad.importExport.exporthtml":"HTML","pad.importExport.exportplain":"Plain text","pad.importExport.exportword":"Microsoft Word","pad.importExport.exportpdf":"PDF","pad.importExport.exportopen":"ODF (Open Document Format)","pad.importExport.exportdokuwiki":"DokuWiki","pad.importExport.abiword.innerHTML":"You only can import from plain text or html formats. For more advanced import features please install abiword.","pad.modals.connected":"Connected.","pad.modals.reconnecting":"Reconnecting to your pad..","pad.modals.forcereconnect":"Force reconnect","pad.modals.userdup":"Opened in another window","pad.modals.userdup.explanation":"This pad seems to be opened in more than one browser window on this computer.","pad.modals.userdup.advice":"Reconnect to use this window instead.","pad.modals.unauth":"Not authorized","pad.modals.unauth.explanation":"Your permissions have changed while viewing this page. Try to reconnect.","pad.modals.looping":"Disconnected.","pad.modals.looping.explanation":"There are communication problems with the synchronization server.","pad.modals.looping.cause":"Perhaps you connected through an incompatible firewall or proxy.","pad.modals.initsocketfail":"Server is unreachable.","pad.modals.initsocketfail.explanation":"Couldn't connect to the synchronization server.","pad.modals.initsocketfail.cause":"This is probably due to a problem with your browser or your internet connection.","pad.modals.slowcommit":"Disconnected.","pad.modals.slowcommit.explanation":"The server is not responding.","pad.modals.slowcommit.cause":"This could be due to problems with network connectivity.","pad.modals.deleted":"Deleted.","pad.modals.deleted.explanation":"This pad has been removed.","pad.modals.disconnected":"You have been disconnected.","pad.modals.disconnected.explanation":"The connection to the server was lost","pad.modals.disconnected.cause":"The server may be unavailable. Please notify us if this continues to happen.","pad.share":"Share this pad","pad.share.readonly":"Read only","pad.share.link":"Link","pad.share.emebdcode":"Embed URL","pad.chat":"Chat","pad.chat.title":"Open the chat for this pad.","pad.chat.loadmessages": "Load more messages","timeslider.pageTitle":"{{appTitle}} Timeslider","timeslider.toolbar.returnbutton":"Return to pad","timeslider.toolbar.authors":"Authors:","timeslider.toolbar.authorsList":"No Authors","timeslider.toolbar.exportlink.title":"Export","timeslider.exportCurrent":"Export current version as:","timeslider.version":"Version {{version}}","timeslider.saved":"Saved {{month}} {{day}}, {{year}}","timeslider.dateformat":"{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}","timeslider.month.january":"January","timeslider.month.february":"February","timeslider.month.march":"March","timeslider.month.april":"April","timeslider.month.may":"May","timeslider.month.june":"June","timeslider.month.july":"July","timeslider.month.august":"August","timeslider.month.september":"September","timeslider.month.october":"October","timeslider.month.november":"November","timeslider.month.december":"December","pad.savedrevs.marked":"This revision is now marked as a saved revision","pad.userlist.entername":"Enter your name","pad.userlist.unnamed":"unnamed","pad.userlist.guest":"Guest","pad.userlist.deny":"Deny","pad.userlist.approve":"Approve","pad.editbar.clearcolors":"Clear authorship colors on entire document?","pad.impexp.importbutton":"Import Now","pad.impexp.importing":"Importing...","pad.impexp.confirmimport":"Importing a file will overwrite the current text of the pad. Are you sure you want to proceed?","pad.impexp.convertFailed":"We were not able to import this file. Please use a different document format or copy paste manually","pad.impexp.uploadFailed":"The upload failed, please try again","pad.impexp.importfailed":"Import failed","pad.impexp.copypaste":"Please copy paste","pad.impexp.exportdisabled":"Exporting as {{type}} format is disabled. Please contact your system administrator for details."} \ No newline at end of file +{ + "index.newPad": "New Pad", + "index.createOpenPad": "or create/open a Pad with the name:", + "pad.toolbar.bold.title": "Bold (Ctrl-B)", + "pad.toolbar.italic.title": "Italic (Ctrl-I)", + "pad.toolbar.underline.title": "Underline (Ctrl-U)", + "pad.toolbar.strikethrough.title": "Strikethrough", + "pad.toolbar.ol.title": "Ordered list", + "pad.toolbar.ul.title": "Unordered List", + "pad.toolbar.indent.title": "Indent", + "pad.toolbar.unindent.title": "Outdent", + "pad.toolbar.undo.title": "Undo (Ctrl-Z)", + "pad.toolbar.redo.title": "Redo (Ctrl-Y)", + "pad.toolbar.clearAuthorship.title": "Clear Authorship Colors", + "pad.toolbar.import_export.title": "Import/Export from/to different file formats", + "pad.toolbar.timeslider.title": "Timeslider", + "pad.toolbar.savedRevision.title": "Saved Revisions", + "pad.toolbar.settings.title": "Settings", + "pad.toolbar.embed.title": "Embed this pad", + "pad.toolbar.showusers.title": "Show the users on this pad", + "pad.colorpicker.save": "Save", + "pad.colorpicker.cancel": "Cancel", + "pad.loading": "Loading...", + "pad.passwordRequired": "You need a password to access this pad", + "pad.permissionDenied": "You do not have permission to access this pad", + "pad.wrongPassword": "Your password was wrong", + "pad.settings.padSettings": "Pad Settings", + "pad.settings.myView": "My View", + "pad.settings.stickychat": "Chat always on screen", + "pad.settings.colorcheck": "Authorship colors", + "pad.settings.linenocheck": "Line numbers", + "pad.settings.fontType": "Font type:", + "pad.settings.fontType.normal": "Normal", + "pad.settings.fontType.monospaced": "Monospace", + "pad.settings.globalView": "Global View", + "pad.settings.language": "Language:", + "pad.importExport.import_export": "Import/Export", + "pad.importExport.import": "Upload any text file or document", + "pad.importExport.importSuccessful": "Successful!", + "pad.importExport.export": "Export current pad as:", + "pad.importExport.exporthtml": "HTML", + "pad.importExport.exportplain": "Plain text", + "pad.importExport.exportword": "Microsoft Word", + "pad.importExport.exportpdf": "PDF", + "pad.importExport.exportopen": "ODF (Open Document Format)", + "pad.importExport.exportdokuwiki": "DokuWiki", + "pad.importExport.abiword.innerHTML": "You only can import from plain text or html formats. For more advanced import features please install abiword.", + "pad.modals.connected": "Connected.", + "pad.modals.reconnecting": "Reconnecting to your pad..", + "pad.modals.forcereconnect": "Force reconnect", + "pad.modals.userdup": "Opened in another window", + "pad.modals.userdup.explanation": "This pad seems to be opened in more than one browser window on this computer.", + "pad.modals.userdup.advice": "Reconnect to use this window instead.", + "pad.modals.unauth": "Not authorized", + "pad.modals.unauth.explanation": "Your permissions have changed while viewing this page. Try to reconnect.", + "pad.modals.looping": "Disconnected.", + "pad.modals.looping.explanation": "There are communication problems with the synchronization server.", + "pad.modals.looping.cause": "Perhaps you connected through an incompatible firewall or proxy.", + "pad.modals.initsocketfail": "Server is unreachable.", + "pad.modals.initsocketfail.explanation": "Couldn't connect to the synchronization server.", + "pad.modals.initsocketfail.cause": "This is probably due to a problem with your browser or your internet connection.", + "pad.modals.slowcommit": "Disconnected.", + "pad.modals.slowcommit.explanation": "The server is not responding.", + "pad.modals.slowcommit.cause": "This could be due to problems with network connectivity.", + "pad.modals.deleted": "Deleted.", + "pad.modals.deleted.explanation": "This pad has been removed.", + "pad.modals.disconnected": "You have been disconnected.", + "pad.modals.disconnected.explanation": "The connection to the server was lost", + "pad.modals.disconnected.cause": "The server may be unavailable. Please notify us if this continues to happen.", + "pad.share": "Share this pad", + "pad.share.readonly": "Read only", + "pad.share.link": "Link", + "pad.share.emebdcode": "Embed URL", + "pad.chat": "Chat", + "pad.chat.title": "Open the chat for this pad.", + "pad.chat.loadmessages": "Load more messages", + "timeslider.pageTitle": "{{appTitle}} Timeslider", + "timeslider.toolbar.returnbutton": "Return to pad", + "timeslider.toolbar.authors": "Authors:", + "timeslider.toolbar.authorsList": "No Authors", + "timeslider.toolbar.exportlink.title": "Export", + "timeslider.exportCurrent": "Export current version as:", + "timeslider.version": "Version {{version}}", + "timeslider.saved": "Saved {{month}} {{day}}, {{year}}", + "timeslider.dateformat": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}", + "timeslider.month.january": "January", + "timeslider.month.february": "February", + "timeslider.month.march": "March", + "timeslider.month.april": "April", + "timeslider.month.may": "May", + "timeslider.month.june": "June", + "timeslider.month.july": "July", + "timeslider.month.august": "August", + "timeslider.month.september": "September", + "timeslider.month.october": "October", + "timeslider.month.november": "November", + "timeslider.month.december": "December", + "timeslider.unnamedauthor": "{{num}} unnamed author", + "timeslider.unnamedauthors": "{{num}} unnamed authors", + "pad.savedrevs.marked": "This revision is now marked as a saved revision", + "pad.userlist.entername": "Enter your name", + "pad.userlist.unnamed": "unnamed", + "pad.userlist.guest": "Guest", + "pad.userlist.deny": "Deny", + "pad.userlist.approve": "Approve", + "pad.editbar.clearcolors": "Clear authorship colors on entire document?", + "pad.impexp.importbutton": "Import Now", + "pad.impexp.importing": "Importing...", + "pad.impexp.confirmimport": "Importing a file will overwrite the current text of the pad. Are you sure you want to proceed?", + "pad.impexp.convertFailed": "We were not able to import this file. Please use a different document format or copy paste manually", + "pad.impexp.uploadFailed": "The upload failed, please try again", + "pad.impexp.importfailed": "Import failed", + "pad.impexp.copypaste": "Please copy paste", + "pad.impexp.exportdisabled": "Exporting as {{type}} format is disabled. Please contact your system administrator for details." +} \ No newline at end of file diff --git a/src/node/db/API.js b/src/node/db/API.js index 0eb404e49..a700a4915 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -278,6 +278,77 @@ exports.setHTML = function(padID, html, callback) }); } +/******************/ +/**CHAT FUNCTIONS */ +/******************/ + +/** +getChatHistory(padId, start, end), returns a part of or the whole chat-history of this pad + +Example returns: + +{"code":0,"message":"ok","data":{"messages":[{"text":"foo","userId":"a.foo","time":1359199533759,"userName":"test"}, + {"text":"bar","userId":"a.foo","time":1359199534622,"userName":"test"}]}} + +{code: 1, message:"start is higher or equal to the current chatHead", data: null} + +{code: 1, message:"padID does not exist", data: null} +*/ +exports.getChatHistory = function(padID, start, end, callback) +{ + if(start && end) + { + if(start < 0) + { + callback(new customError("start is below zero","apierror")); + return; + } + if(end < 0) + { + callback(new customError("end is below zero","apierror")); + return; + } + if(start > end) + { + callback(new customError("start is higher than end","apierror")); + return; + } + } + + //get the pad + getPadSafe(padID, true, function(err, pad) + { + if(ERR(err, callback)) return; + var chatHead = pad.chatHead; + + // fall back to getting the whole chat-history if a parameter is missing + if(!start || !end) + { + start = 0; + end = pad.chatHead - 1; + } + + if(start >= chatHead) + { + callback(new customError("start is higher or equal to the current chatHead","apierror")); + return; + } + if(end >= chatHead) + { + callback(new customError("end is higher or equal to the current chatHead","apierror")); + return; + } + + // the the whole message-log and return it to the client + pad.getChatMessages(start, end, + function(err, msgs) + { + if(ERR(err, callback)) return; + callback(null, {messages: msgs}); + }); + }); +} + /*****************/ /**PAD FUNCTIONS */ /*****************/ @@ -568,6 +639,23 @@ exports.checkToken = function(callback) callback(); } +/** +getChatHead(padID) returns the chatHead (last number of the last chat-message) of the pad + +Example returns: + +{code: 0, message:"ok", data: {chatHead: 42}} +{code: 1, message:"padID does not exist", data: null} +*/ +exports.getChatHead = function(padID, callback) +{ + //get the pad + getPadSafe(padID, true, function(err, pad) + { + if(ERR(err, callback)) return; + callback(null, {chatHead: pad.chatHead}); + }); +} /** createDiff(padID, startRev, endRev) returns an object of diffs from 2 points in a pad diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index 9d017b280..1f91c737e 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -174,13 +174,13 @@ var version = , "listAllGroups" : [] , "checkToken" : [] } -, "1.2.2": +, "1.2.7": { "createGroup" : [] , "createGroupIfNotExistsFor" : ["groupMapper"] , "deleteGroup" : ["groupID"] , "listPads" : ["groupID"] , "listAllPads" : [] - , "createDiff" : ["padID", "startRev", "endRev"] + , "createDiff" : ["padID", "startRev", "endRev"] , "createPad" : ["padID", "text"] , "createGroupPad" : ["groupID", "padName", "text"] , "createAuthor" : ["name"] @@ -210,6 +210,9 @@ var version = , "sendClientsMessage" : ["padID", "msg"] , "listAllGroups" : [] , "checkToken" : [] + , "getChatHistory" : ["padID"] + , "getChatHistory" : ["padID", "start", "end"] + , "getChatHead" : ["padID"] } }; diff --git a/src/node/handler/ImportHandler.js b/src/node/handler/ImportHandler.js index b87001416..ac856a604 100644 --- a/src/node/handler/ImportHandler.js +++ b/src/node/handler/ImportHandler.js @@ -176,7 +176,7 @@ exports.doImport = function(req, res, padId) ERR(err); //close the connection - res.send("", 200); + res.send("", 200); }); } diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js index 97a0d602f..7e221cf1e 100644 --- a/src/node/hooks/express/adminplugins.js +++ b/src/node/hooks/express/adminplugins.js @@ -2,6 +2,9 @@ var path = require('path'); var eejs = require('ep_etherpad-lite/node/eejs'); var installer = require('ep_etherpad-lite/static/js/pluginfw/installer'); var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins'); +var _ = require('underscore'); +var semver = require('semver'); +var async = require('async'); exports.expressCreateServer = function (hook_name, args, cb) { args.app.get('/admin/plugins', function(req, res) { @@ -25,8 +28,26 @@ exports.socketio = function (hook_name, args, cb) { if (!socket.handshake.session.user || !socket.handshake.session.user.is_admin) return; socket.on("load", function (query) { + // send currently installed plugins socket.emit("installed-results", {results: plugins.plugins}); + socket.emit("progress", {progress:1}); }); + + socket.on("checkUpdates", function() { + socket.emit("progress", {progress:0, message:'Checking for plugin updates...'}); + // Check plugins for updates + installer.search({offset: 0, pattern: '', limit: 500}, /*useCache:*/true, function(data) { // hacky + if (!data.results) return; + var updatable = _(plugins.plugins).keys().filter(function(plugin) { + if(!data.results[plugin]) return false; + var latestVersion = data.results[plugin]['dist-tags'].latest + var currentVersion = plugins.plugins[plugin].package.version + return semver.gt(latestVersion, currentVersion) + }); + socket.emit("updatable", {updatable: updatable}); + socket.emit("progress", {progress:1}); + }); + }) socket.on("search", function (query) { socket.emit("progress", {progress:0, message:'Fetching results...'}); diff --git a/src/static/css/admin.css b/src/static/css/admin.css index b91850a65..3a7291516 100644 --- a/src/static/css/admin.css +++ b/src/static/css/admin.css @@ -7,17 +7,52 @@ body { background: -moz-radial-gradient(circle,#aaa,#eee 60%) center fixed; background: -ms-radial-gradient(circle,#aaa,#eee 60%) center fixed; background: -o-radial-gradient(circle,#aaa,#eee 60%) center fixed; - border-top: 8px solid rgba(51,51,51,.8); } -#wrapper { - margin-top: 160px; + +#topborder { + border-top: 8px solid rgba(51, 51, 51, 0.8); + position: fixed; + top: 0px; + width: 100%; +} + +div.menu { + background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.75); + box-shadow: 0px -4px 4px rgba(0, 0, 0, 0.3); + display: block; + float: left; + height: 100%; + padding: 15px; + position: fixed; + width: 220px; +} + +div.menu li { + list-style: none; + margin-left: 3px; + line-height: 1.6 +} + +div.innerwrapper { + display: block; + float: right; + opacity: 0.9; padding: 15px; - background: #fff; - opacity: .9; - box-shadow: 0px 1px 8px rgba(0,0,0,0.3); - max-width: 700px; - margin: auto; + max-width: 860px; border-radius: 0 0 7px 7px; + margin-left:250px; + min-width:400px; +} + +#wrapper { + background: none repeat scroll 0px 0px #FFFFFF; + box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.3); + margin: auto; + max-width: 1150px; + min-height: 100%; + overflow: auto; + padding-left: 15px; + opacity: .9; } h1 { font-size: 29px; @@ -49,13 +84,10 @@ input[type="button"] { padding: 4px 6px; margin: 0; } -input[type="button"].do-install, input[type="button"].do-uninstall { +table input[type="button"] { float: right; width: 100px; } -input[type="button"]#do-search { - display: block; -} input[type="text"] { border-radius: 3px; box-sizing: border-box; @@ -84,41 +116,9 @@ td, th { .template { display: none; } -.dialog { - display: none; +#progress { position: absolute; - left: 50%; - top: 50%; - width: 700px; - height: 500px; - margin-left: -350px; - margin-top: -250px; - border: 3px solid #999; - background: #eee; -} -.dialog .title { - margin: 0; - padding: 2px; - border-bottom: 3px solid #999; - font-size: 24px; - line-height: 24px; - height: 24px; - overflow: hidden; -} -.dialog .title .close { - float: right; - padding: 1px 10px; -} -.dialog .history { - background: #222; - color: #eee; - position: absolute; - top: 41px; - bottom: 10px; - left: 10px; - right: 10px; - padding: 2px; - overflow: auto; + bottom: 50px; } .settings { margin-top:10px; @@ -128,3 +128,13 @@ td, th { #response{ display:inline; } + +a:link, a:visited, a:hover, a:focus { + color: #333333; + text-decoration: none; + border-bottom: #333333 1px dotted; +} + +a:focus, a:hover { + border-bottom: #333333 1px solid; +} diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index ddff767bf..a973875ce 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -22,13 +22,10 @@ $(document).ready(function () { var search = function () { socket.emit("search", $('.search-results').data('query')); + tasks++; } function updateHandlers() { - $("#progress.dialog .close").unbind('click').click(function () { - $("#progress.dialog").hide(); - }); - $("form").submit(function(){ var query = $('.search-results').data('query'); query.pattern = $("#search-query").val(); @@ -36,24 +33,26 @@ $(document).ready(function () { search(); return false; }); - - $("#do-search").unbind('click').click(function () { + + $("#search-query").unbind('keyup').keyup(function () { var query = $('.search-results').data('query'); query.pattern = $("#search-query").val(); query.offset = 0; search(); }); - $(".do-install").unbind('click').click(function (e) { + $(".do-install, .do-update").unbind('click').click(function (e) { var row = $(e.target).closest("tr"); doUpdate = true; - socket.emit("install", row.find(".name").html()); + socket.emit("install", row.find(".name").text()); + tasks++; }); $(".do-uninstall").unbind('click').click(function (e) { var row = $(e.target).closest("tr"); doUpdate = true; - socket.emit("uninstall", row.find(".name").html()); + socket.emit("uninstall", row.find(".name").text()); + tasks++; }); $(".do-prev-page").unbind('click').click(function (e) { @@ -76,33 +75,37 @@ $(document).ready(function () { updateHandlers(); + var tasks = 0; socket.on('progress', function (data) { - if (data.progress > 0 && $('#progress.dialog').data('progress') > data.progress) return; - - $("#progress.dialog .close").hide(); - $("#progress.dialog").show(); - - $('#progress.dialog').data('progress', data.progress); + $("#progress").show(); + $('#progress').data('progress', data.progress); var message = "Unknown status"; if (data.message) { - message = "" + data.message.toString() + ""; + message = data.message.toString(); } if (data.error) { - message = "" + data.error.toString() + ""; + data.progress = 1; } - $("#progress.dialog .message").html(message); - $("#progress.dialog .history").append("
" + message + "
"); + + $("#progress .message").html(message); if (data.progress >= 1) { + tasks--; + if (tasks <= 0) { + // Hide the activity indicator once all tasks are done + $("#progress").hide(); + tasks = 0; + } + if (data.error) { - $("#progress.dialog .close").show(); - } else { + alert('An error occurred: '+data.error+' -- the server log might know more...'); + }else { if (doUpdate) { doUpdate = false; socket.emit("load"); + tasks++; } - $("#progress.dialog").hide(); } } }); @@ -114,21 +117,26 @@ $(document).ready(function () { widget.data('total', data.total); widget.find('.offset').html(data.query.offset); - widget.find('.limit').html(data.query.offset + data.query.limit); + if (data.query.offset + data.query.limit > data.total){ + widget.find('.limit').html(data.total); + }else{ + widget.find('.limit').html(data.query.offset + data.query.limit); + } widget.find('.total').html(data.total); widget.find(".results *").remove(); for (plugin_name in data.results) { var plugin = data.results[plugin_name]; var row = widget.find(".template tr").clone(); - var version = '0.0.0'; - // hack to access "versions" property of the npm package object - for (version in data.results[plugin_name].versions) break; - + for (attr in plugin) { - row.find("." + attr).html(plugin[attr]); + if(attr == "name"){ // Hack to rewrite URLS into name + row.find(".name").html(""+plugin[attr]+""); + }else{ + row.find("." + attr).html(plugin[attr]); + } } - row.find(".version").html(version); + row.find(".version").html( data.results[plugin_name]['dist-tags'].latest ); widget.find(".results").append(row); } @@ -138,20 +146,42 @@ $(document).ready(function () { socket.on('installed-results', function (data) { $("#installed-plugins *").remove(); + for (plugin_name in data.results) { if (plugin_name == "ep_etherpad-lite") continue; // Hack... var plugin = data.results[plugin_name]; var row = $("#installed-plugin-template").clone(); for (attr in plugin.package) { - row.find("." + attr).html(plugin.package[attr]); + if(attr == "name"){ // Hack to rewrite URLS into name + row.find(".name").html(""+plugin.package[attr]+""); + }else{ + row.find("." + attr).html(plugin.package[attr]); + } } $("#installed-plugins").append(row); } updateHandlers(); + + socket.emit('checkUpdates'); + tasks++; }); + + socket.on('updatable', function(data) { + $('#installed-plugins>tr').each(function(i,tr) { + var pluginName = $(tr).find('.name').text() + + if (data.updatable.indexOf(pluginName) >= 0) { + var actions = $(tr).find('.actions') + actions.append('') + actions.css('width', 200) + } + }) + updateHandlers(); + }) socket.emit("load"); + tasks++; + search(); - }); diff --git a/src/static/js/broadcast_slider.js b/src/static/js/broadcast_slider.js index d4fbef81c..221666de0 100644 --- a/src/static/js/broadcast_slider.js +++ b/src/static/js/broadcast_slider.js @@ -193,7 +193,12 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) }); if (numAnonymous > 0) { - var anonymousAuthorString = numAnonymous + " unnamed author" + (numAnonymous > 1 ? "s" : "") + var anonymousAuthorString; + if(numAnonymous == 1) + anonymousAuthorString = html10n.get("timeslider.unnamedauthor", { num: numAnonymous }); + else + anonymousAuthorString = html10n.get("timeslider.unnamedauthors", { num: numAnonymous }); + if (numNamed !== 0){ authorsList.append(' + ' + anonymousAuthorString); } else { @@ -361,7 +366,11 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) $(self).css('left', newloc); // if(getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2))) setSliderPosition(Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2))) - self.currentLoc = parseInt($(self).css('left')); + if(parseInt($(self).css('left')) < 2){ + $(self).css('left', '2px'); + }else{ + self.currentLoc = parseInt($(self).css('left')); + } }); }) diff --git a/src/static/js/html10n.js b/src/static/js/html10n.js index b2c4e6195..d0d14814b 100644 --- a/src/static/js/html10n.js +++ b/src/static/js/html10n.js @@ -21,6 +21,8 @@ * IN THE SOFTWARE. */ window.html10n = (function(window, document, undefined) { + + // fix console var console = window.console function interceptConsole(method){ if (!console) return function() {} @@ -44,6 +46,39 @@ window.html10n = (function(window, document, undefined) { , consoleError = interceptConsole('warn') + // fix Array.prototype.instanceOf in, guess what, IE! <3 + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { + "use strict"; + if (this == null) { + throw new TypeError(); + } + var t = Object(this); + var len = t.length >>> 0; + if (len === 0) { + return -1; + } + var n = 0; + if (arguments.length > 1) { + n = Number(arguments[1]); + if (n != n) { // shortcut for verifying if it's NaN + n = 0; + } else if (n != 0 && n != Infinity && n != -Infinity) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + } + if (n >= len) { + return -1; + } + var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); + for (; k < len; k++) { + if (k in t && t[k] === searchElement) { + return k; + } + } + return -1; + } + } /** * MicroEvent - to make any js object an event emitter (server or browser) diff --git a/src/static/js/pad_editor.js b/src/static/js/pad_editor.js index f4778802d..dd0cbbbb7 100644 --- a/src/static/js/pad_editor.js +++ b/src/static/js/pad_editor.js @@ -78,6 +78,16 @@ var padeditor = (function() html10n.bind('localized', function() { $("#languagemenu").val(html10n.getLanguage()); + // translate the value of 'unnamed' and 'Enter your name' textboxes in the userlist + // this does not interfere with html10n's normal value-setting because html10n just ingores s + // also, a value which has been set by the user will be not overwritten since a user-edited + // does *not* have the editempty-class + $('input[data-l10n-id]').each(function(key, input) + { + input = $(input); + if(input.hasClass("editempty")) + input.val(html10n.get(input.attr("data-l10n-id"))); + }); }) $("#languagemenu").val(html10n.getLanguage()); $("#languagemenu").change(function() { diff --git a/src/static/js/pad_impexp.js b/src/static/js/pad_impexp.js index 257dfa667..745642569 100644 --- a/src/static/js/pad_impexp.js +++ b/src/static/js/pad_impexp.js @@ -212,10 +212,8 @@ var padimpexp = (function() //get /p/padname var pad_root_path = new RegExp(/.*\/p\/[^\/]+/).exec(document.location.pathname); - //get http://example.com/p/padname - var pad_root_url = document.location.href.replace(document.location.pathname, pad_root_path); - //strip out params IE ?noColor=true - pad_root_url = pad_root_url.substring(0, pad_root_url.indexOf('?')); + //get http://example.com/p/padname without Params + var pad_root_url = document.location.protocol + '//' + document.location.host + document.location.pathname; //i10l buttom import $('#importsubmitinput').val(html10n.get("pad.impexp.importbutton")); diff --git a/src/static/js/pad_userlist.js b/src/static/js/pad_userlist.js index 40e87a4f7..d051182b9 100644 --- a/src/static/js/pad_userlist.js +++ b/src/static/js/pad_userlist.js @@ -118,7 +118,7 @@ var paduserlist = (function() } else { - nameHtml = ''; + nameHtml = ''; } return ['
 
', '', nameHtml, '', '', padutils.escapeHtml(data.activity), ''].join(''); @@ -710,8 +710,7 @@ var paduserlist = (function() { if (myUserInfo.name) { - $("#myusernameedit").removeClass("editempty").val( - myUserInfo.name); + $("#myusernameedit").removeClass("editempty").val(myUserInfo.name); } else { diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index e491f0771..eb10f8afd 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -15,7 +15,10 @@ var withNpm = function (npmfn, final, cb) { cb({progress: 0.5, message:message.msg + ": " + message.pref}); }); npmfn(function (er, data) { - if (er) return cb({progress:1, error:er.code + ": " + er.path}); + if (er) { + console.error(er); + return cb({progress:1, error: er.message}); + } if (!data) data = {}; data.progress = 1; data.message = "Done."; diff --git a/src/templates/admin/index.html b/src/templates/admin/index.html index 5f1d0188e..16ea84279 100644 --- a/src/templates/admin/index.html +++ b/src/templates/admin/index.html @@ -1,6 +1,6 @@ - Etherpad Lite Admin Dashboard + Admin Dashboard - Etherpad lite @@ -8,13 +8,14 @@
-

Etherpad Lite Admin Dashboard

-
- Install and Uninstall plugins -
-
- Modify Server and Plugin Settings + +
+
diff --git a/src/templates/admin/plugins-info.html b/src/templates/admin/plugins-info.html index 94f94e2ac..605b23d38 100644 --- a/src/templates/admin/plugins-info.html +++ b/src/templates/admin/plugins-info.html @@ -4,27 +4,36 @@ - Plugin information + Plugin information - Etherpad lite
-

Etherpad Lite

-
+ -

Installed plugins

-
<%- plugins.formatPlugins().replace(", ","\n") %>
+
-

Installed parts

-
<%= plugins.formatParts() %>
+

Installed plugins

+
<%- plugins.formatPlugins().replace(", ","\n") %>
-

Installed hooks

-

Server side hooks

-
<%- plugins.formatHooks() %>
+

Installed parts

+
<%= plugins.formatParts() %>
-

Client side hooks

-
<%- plugins.formatHooks("client_hooks") %>
+

Installed hooks

+

Server side hooks

+
<%- plugins.formatHooks() %>
+ +

Client side hooks

+
<%- plugins.formatHooks("client_hooks") %>
+ +
+
diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index 394cf0e06..a85db557a 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -1,6 +1,6 @@ - Plugin manager + Plugin manager - Etherpad lite @@ -18,12 +18,16 @@
<% } %> + -

Etherpad Lite

- - Technical information on installed plugins - -
+

Installed plugins

@@ -50,11 +54,12 @@
-

Search for plugins to install

+ +

Available plugins

- - + +
@@ -81,14 +86,9 @@ .. of . - -
-

- Please wait: - -

-
+
+
diff --git a/src/templates/admin/settings.html b/src/templates/admin/settings.html index c4f505788..be262f243 100644 --- a/src/templates/admin/settings.html +++ b/src/templates/admin/settings.html @@ -1,6 +1,6 @@ - Settings manager + Settings - Etherpad lite @@ -22,13 +22,23 @@ <% } %> -

Etherpad Lite Settings

- Example production settings template - Example development settings template - - - -
+ + + + +
diff --git a/src/templates/index.html b/src/templates/index.html index f0e1beb3f..c3c13db32 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -150,6 +150,7 @@
+ <% e.begin_block("indexWrapper"); %>
@@ -158,6 +159,7 @@
+ <% e.end_block(); %>
diff --git a/src/templates/pad.html b/src/templates/pad.html index 274a3b1a0..cb88c1c1a 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -3,7 +3,10 @@ , langs = require("ep_etherpad-lite/node/hooks/i18n").availableLangs %> +<% e.begin_block("htmlHead"); %> +<% e.end_block(); %> + <%=settings.title%>