diff --git a/CHANGELOG.md b/CHANGELOG.md index 75a9f327..bf27f292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# 1.5.2 + * NEW: Support for node version 0.12.x + * NEW: API endpoint saveRevision, getSavedRevisionCount and listSavedRevisions + * NEW: setting to allow load testing + * Fix: Rare scroll issue + * Fix: Handling of custom pad path + * Fix: Better error handling of imports and exports of type "etherpad" + * Fix: Walking caret in chrome + * Fix: Better handling for changeset problems + * SECURITY Fix: Information leak for etherpad exports (CVE-2015-2298) + # 1.5.1 * NEW: High resolution Icon * NEW: Use HTTPS for plugins.json download diff --git a/README.md b/README.md index 0cddb0b0..48ff434e 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,8 @@ Update to the latest version with `git pull origin`, then run `bin\installOnWind ## GNU/Linux and other UNIX-like systems You'll need gzip, git, curl, libssl develop libraries, python and gcc. -- *For Debian/Ubuntu*: `apt-get install gzip git-core curl python libssl-dev pkg-config build-essential` -- *For Fedora/CentOS*: `yum install gzip git-core curl python openssl-devel && yum groupinstall "Development Tools"` +- *For Debian/Ubuntu*: `apt-get install gzip git curl python libssl-dev pkg-config build-essential` +- *For Fedora/CentOS*: `yum install gzip git curl python openssl-devel && yum groupinstall "Development Tools"` - *For FreeBSD*: `portinstall node, npm, git (optional)` Additionally, you'll need [node.js](http://nodejs.org) installed, Ideally the latest stable version, we recommend installing/compiling nodejs from source (avoiding apt). diff --git a/bin/installDeps.sh b/bin/installDeps.sh index fcf213e4..04c4a02a 100755 --- a/bin/installDeps.sh +++ b/bin/installDeps.sh @@ -50,9 +50,9 @@ NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-2) if hash iojs 2>/dev/null; then IOJS_VERSION=$(iojs --version) fi -if [ ! $NODE_V_MINOR = "v0.8" ] && [ ! $NODE_V_MINOR = "v0.10" ] && [ ! $NODE_V_MINOR = "v0.11" ]; then +if [ ! $NODE_V_MINOR = "v0.8" ] && [ ! $NODE_V_MINOR = "v0.10" ] && [ ! $NODE_V_MINOR = "v0.11" ] && [ ! $NODE_V_MINOR = "v0.12" ]; then if [ ! $IOJS_VERSION ]; then - echo "You're running a wrong version of node, or io.js is not installed. You're using $NODE_VERSION, we need v0.8.x, v0.10.x or v0.11.x" >&2 + echo "You're running a wrong version of node, or io.js is not installed. You're using $NODE_VERSION, we need v0.8.x, v0.10.x, v0.11.x or v0.12.x" >&2 exit 1 fi fi diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md index ca429a07..8e2d3da7 100644 --- a/doc/api/hooks_client-side.md +++ b/doc/api/hooks_client-side.md @@ -203,6 +203,29 @@ Things in context: This hook is called before the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original. +## collectContentImage +Called from: src/static/js/contentcollector.js + +Things in context: + +1. cc - the contentcollector object +2. state - the current state of the change being made +3. tname - the tag name of this node currently being processed +4. style - the style applied to the node (probably CSS) +5. cls - the HTML class string of the node +6. node - the node being modified + +This hook is called before the content of an image node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. + +Example: + +``` +exports.collectContentImage = function(name, context){ + context.state.lineAttributes.img = context.node.outerHTML; +} + +``` + ## collectContentPost Called from: src/static/js/contentcollector.js diff --git a/doc/api/http_api.md b/doc/api/http_api.md index 6cbe6e6b..2ae674d8 100644 --- a/doc/api/http_api.md +++ b/doc/api/http_api.md @@ -61,7 +61,7 @@ Portal submits content into new blog post ## Usage ### API version -The latest version is `1.2.9` +The latest version is `1.2.11` The current version can be queried via /api. @@ -402,6 +402,33 @@ returns the number of revisions of this pad * `{code: 0, message:"ok", data: {revisions: 56}}` * `{code: 1, message:"padID does not exist", data: null}` +#### getSavedRevisionsCount(padID) + * API >= 1.2.11 + +returns the number of saved revisions of this pad + +*Example returns:* + * `{code: 0, message:"ok", data: {savedRevisions: 42}}` + * `{code: 1, message:"padID does not exist", data: null}` + +#### listSavedRevisions(padID) + * API >= 1.2.11 + +returns the list of saved revisions of this pad + +*Example returns:* + * `{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}}` + * `{code: 1, message:"padID does not exist", data: null}` + +#### saveRevision(padID [, rev]) + * API >= 1.2.11 + +saves a revision + +*Example returns:* + * `{code: 0, message:"ok", data: null}` + * `{code: 1, message:"padID does not exist", data: null}` + #### padUsersCount(padID) * API >= 1 diff --git a/settings.json.template b/settings.json.template index 3f84af9b..124345e2 100644 --- a/settings.json.template +++ b/settings.json.template @@ -108,6 +108,9 @@ // restrict socket.io transport methods "socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"], + + // Allow Load Testing tools to hit the Etherpad Instance. Warning this will disable security on the instance. + "loadTest": false, /* The toolbar buttons configuration. "toolbar": { diff --git a/src/locales/ar.json b/src/locales/ar.json index b0d19fcc..9d29b521 100644 --- a/src/locales/ar.json +++ b/src/locales/ar.json @@ -4,7 +4,8 @@ "Ali1", "Tux-tn", "Alami", - "Meno25" + "Meno25", + "Test Create account" ] }, "index.newPad": "باد جديد", @@ -29,6 +30,7 @@ "pad.colorpicker.save": "تسجيل", "pad.colorpicker.cancel": "إلغاء", "pad.loading": "جاري التحميل...", + "pad.noCookie": "الكوكيز غير متاحة. الرجاء السماح بتحميل الكوكيز على متصفحك!", "pad.passwordRequired": "تحتاج إلى كلمة مرور للوصول إلى هذا الباد", "pad.permissionDenied": "ليس لديك إذن لدخول هذا الباد", "pad.wrongPassword": "كانت كلمة المرور خاطئة", @@ -118,6 +120,7 @@ "pad.impexp.importing": "الاستيراد...", "pad.impexp.confirmimport": "استيراد ملف سيؤدي للكتابة فوق النص الحالي بالباد. هل أنت متأكد من أنك تريد المتابعة؟", "pad.impexp.convertFailed": "لم نتمكن من استيراد هذا الملف. يرجى استخدام تنسيق مستند مختلف، أو النسخ واللصق يدوياً", + "pad.impexp.padHasData": "لا يمكننا استيراد هذا الملف لأن هذه اللوحة تم بالفعل تغييره, الرجاء استيراد لوحة جديد", "pad.impexp.uploadFailed": "فشل التحميل، الرجاء المحاولة مرة أخرى", "pad.impexp.importfailed": "فشل الاستيراد", "pad.impexp.copypaste": "الرجاء نسخ/لصق", diff --git a/src/locales/awa.json b/src/locales/awa.json new file mode 100644 index 00000000..f8c0d501 --- /dev/null +++ b/src/locales/awa.json @@ -0,0 +1,69 @@ +{ + "@metadata": { + "authors": [ + "1AnuraagPandey" + ] + }, + "index.newPad": "नयाँ प्याड", + "pad.toolbar.bold.title": "मोट (Ctrl-B)", + "pad.toolbar.italic.title": "तिरछा (Ctrl+I)", + "pad.toolbar.underline.title": "निम्न रेखाङ्कन (Ctrl-U)", + "pad.toolbar.indent.title": "इन्डेन्ट (TAB)", + "pad.toolbar.unindent.title": "आउटडेन्ट (Shift+TAB)", + "pad.toolbar.undo.title": "रद्द (Ctrl-Z)", + "pad.toolbar.redo.title": "पुन:लागु (Ctrl-Y)", + "pad.toolbar.timeslider.title": "टाइमस्लाइडर", + "pad.toolbar.savedRevision.title": "पुनरावलोकन संग्रह किहा जाय", + "pad.toolbar.settings.title": "सेटिङ्ग", + "pad.colorpicker.save": "सहेजा जाय", + "pad.colorpicker.cancel": "रद्द करा जाय", + "pad.loading": "लोड होत है...", + "pad.wrongPassword": "आप कय पासवर्ड गलत रहा", + "pad.settings.padSettings": "प्याड सेटिङ्ग", + "pad.settings.myView": "हमार दृष्य", + "pad.settings.colorcheck": "लेखकीय रङ्ग", + "pad.settings.linenocheck": "हरफ संख्या", + "pad.settings.fontType": "फन्ट प्रकार:", + "pad.settings.fontType.normal": "साधारण", + "pad.settings.fontType.monospaced": "मोनोस्पेस", + "pad.settings.globalView": "विश्वव्यापी दृष्य", + "pad.settings.language": "भाषा", + "pad.importExport.import_export": "आयात/निर्यात", + "pad.importExport.importSuccessful": "सफल!", + "pad.importExport.exporthtml": "HTML", + "pad.importExport.exportplain": "सामान्य पाठ", + "pad.importExport.exportword": "माइक्रोसफ्ट वर्ड", + "pad.importExport.exportpdf": "पिडिएफ", + "pad.importExport.exportopen": "ओडिएफ(खुल्ला कागजात ढाँचा)", + "pad.modals.unauth": "अनाधिकृत", + "pad.modals.initsocketfail": "सर्भरमा पहुँच से बहरे है ।", + "pad.share.readonly": "पढय वाला खाली", + "pad.share.link": "लिङ्क", + "pad.share.emebdcode": "URL जोडा जाय", + "pad.chat": "बातचीत", + "timeslider.pageTitle": "{{appTitle}} समय रेखा", + "timeslider.toolbar.authors": "लेखक:", + "timeslider.toolbar.exportlink.title": "निर्यात", + "timeslider.version": "संस्करण {{version}}", + "timeslider.dateformat": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}", + "timeslider.month.january": "जनवरी", + "timeslider.month.february": "फेब्रुअरी", + "timeslider.month.march": "मार्च", + "timeslider.month.april": "अप्रैल", + "timeslider.month.may": "मई", + "timeslider.month.june": "जून", + "timeslider.month.july": "जुलाई", + "timeslider.month.august": "अगस्त", + "timeslider.month.september": "सेप्टेम्बर", + "timeslider.month.october": "अक्टूबर", + "timeslider.month.november": "नोभेम्बर", + "timeslider.month.december": "डिसेम्बर", + "timeslider.unnamedauthors": "{{num}} unnamed {[plural(num) one: author, other: authors ]}", + "pad.userlist.unnamed": "बेनामी", + "pad.userlist.guest": "पहुना", + "pad.userlist.deny": "अस्वीकार", + "pad.userlist.approve": "स्वीकृत", + "pad.impexp.importing": "आयात होत है...", + "pad.impexp.importfailed": "आयात असफल रहा", + "pad.impexp.copypaste": "कृपया कपी पेस्ट कीन जाय" +} diff --git a/src/locales/az.json b/src/locales/az.json index 99d3216a..f5968ee4 100644 --- a/src/locales/az.json +++ b/src/locales/az.json @@ -59,7 +59,7 @@ "pad.modals.forcereconnect": "Məcbur təkrarən bağlan", "pad.modals.userdup": "Başqa pəncərədə artıq açıqdır", "pad.modals.userdup.explanation": "Bu lövhə, ola bilsin ki, bu kompüterdəki brauzerin bir neçə pəncərəsində açılmışdır.", - "pad.modals.userdup.advice": "Bu pəncərədən istifadəylə yenidən qoşulun.", + "pad.modals.userdup.advice": "Bu pəncərəni istifadə etmək üçün yenidən qoşul.", "pad.modals.unauth": "İcazəli deyil", "pad.modals.unauth.explanation": "Bu səhifəyə baxdığınız vaxt sizin icazəniz dəyişilib. Bərpa etmək üşün yenidən cəhd edin.", "pad.modals.looping.explanation": "Sinxronlaşdırma serveri ilə kommunikasiya xətası var.", diff --git a/src/locales/be-tarask.json b/src/locales/be-tarask.json index f67d10fe..3c789858 100644 --- a/src/locales/be-tarask.json +++ b/src/locales/be-tarask.json @@ -35,6 +35,7 @@ "pad.settings.padSettings": "Налады дакумэнта", "pad.settings.myView": "Мой выгляд", "pad.settings.stickychat": "Заўсёды паказваць чат", + "pad.settings.chatandusers": "Паказаць чат і ўдзельнікаў", "pad.settings.colorcheck": "Колеры аўтарства", "pad.settings.linenocheck": "Нумары радкоў", "pad.settings.rtlcheck": "Тэкст справа-налева", @@ -108,6 +109,7 @@ "timeslider.month.december": "сьнежань", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: безыменны аўтар, few: безыменныя аўтары, many: безыменных аўтараў, other: безыменных аўтараў ]}", "pad.savedrevs.marked": "Гэтая вэрсія цяпер пазначаная як захаваная", + "pad.savedrevs.timeslider": "Вы можаце пабачыць захаваныя вэрсіі з дапамогай шкалы часу", "pad.userlist.entername": "Увядзіце вашае імя", "pad.userlist.unnamed": "безыменны", "pad.userlist.guest": "Госьць", diff --git a/src/locales/br.json b/src/locales/br.json index 6e455d23..6bbd56d2 100644 --- a/src/locales/br.json +++ b/src/locales/br.json @@ -29,6 +29,7 @@ "pad.colorpicker.save": "Enrollañ", "pad.colorpicker.cancel": "Nullañ", "pad.loading": "O kargañ...", + "pad.noCookie": "N'eus ket gallet kavout an toupin. Aotreit an toupinoù en ho merdeer, mar plij !", "pad.passwordRequired": "Ezhomm ho peus ur ger-tremen evit mont d'ar Pad-se", "pad.permissionDenied": "\nN'oc'h ket aotreet da vont d'ar pad-mañ", "pad.wrongPassword": "Fazius e oa ho ker-tremen", @@ -47,6 +48,7 @@ "pad.importExport.import": "Enkargañ un destenn pe ur restr", "pad.importExport.importSuccessful": "Deuet eo ganeoc'h !", "pad.importExport.export": "Ezporzhiañ ar pad bremañ evel :", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Testenn blaen", "pad.importExport.exportword": "Microsoft Word", @@ -117,6 +119,7 @@ "pad.impexp.importing": "Oc'h enporzhiañ...", "pad.impexp.confirmimport": "Ma vez enporzhiet ur restr e vo diverket ar pezh zo en teul a-vremañ. Ha sur oc'h e fell deoc'h mont betek penn ?", "pad.impexp.convertFailed": "N'eus ket bet gallet enporzhiañ ar restr. Ober gant ur furmad teul all pe eilañ/pegañ gant an dorn.", + "pad.impexp.padHasData": "N'hon eus ket gallet enporzhiañ ar restr-mañdre ma'z eus bet degaset kemmoù er bloc'h-se ; enporzhiit anezhi war-zu ur bloc'h nevez, mar plij.", "pad.impexp.uploadFailed": "C'hwitet eo bet an enporzhiañ. Klaskit en-dro.", "pad.impexp.importfailed": "C'hwitet eo an enporzhiadenn", "pad.impexp.copypaste": "Eilit/pegit, mar plij", diff --git a/src/locales/cs.json b/src/locales/cs.json index 2a6b5fec..19552ffd 100644 --- a/src/locales/cs.json +++ b/src/locales/cs.json @@ -13,11 +13,11 @@ "pad.toolbar.bold.title": "Tučný text (Ctrl-B)", "pad.toolbar.italic.title": "Kurzíva (Ctrl-I)", "pad.toolbar.underline.title": "Podtržené písmo (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Přeskrtnuté písmo", + "pad.toolbar.strikethrough.title": "Přeškrtnuto (Ctrl+5)", "pad.toolbar.ol.title": "Číslovaný seznam", "pad.toolbar.ul.title": "Nečíslovaný seznam", - "pad.toolbar.indent.title": "Odsazení", - "pad.toolbar.unindent.title": "Předsazení", + "pad.toolbar.indent.title": "Odsazení (TAB)", + "pad.toolbar.unindent.title": "Předsazení (Shift+TAB)", "pad.toolbar.undo.title": "Zpět (Ctrl-Z)", "pad.toolbar.redo.title": "Opakovat (Ctrl-Y)", "pad.toolbar.clearAuthorship.title": "Vymazat barvy autorů", @@ -30,12 +30,14 @@ "pad.colorpicker.save": "Uložit", "pad.colorpicker.cancel": "Zrušit", "pad.loading": "Načítání...", + "pad.noCookie": "Nelze nalézt cookie. Povolte prosím cookie ve Vašem prohlížeči.", "pad.passwordRequired": "Pro přístup k tomuto Padu je třeba znát heslo", "pad.permissionDenied": "Nemáte oprávnění pro přístup k tomuto Padu", "pad.wrongPassword": "Nesprávné heslo", "pad.settings.padSettings": "Nastavení Padu", "pad.settings.myView": "Vlastní pohled", "pad.settings.stickychat": "Chat vždy na obrazovce", + "pad.settings.chatandusers": "Ukázat Chat a Uživatele", "pad.settings.colorcheck": "Barvy autorů", "pad.settings.linenocheck": "Čísla řádků", "pad.settings.rtlcheck": "Číst obsah zprava doleva?", @@ -109,6 +111,7 @@ "timeslider.month.december": "prosinec", "timeslider.unnamedauthors": "{{num}} {[ plural(num) one: nejmenovaný Autor, few: nejmenovaní Autoři, other: nejmenovaných Autorů ]}", "pad.savedrevs.marked": "Tato revize je nyní označena jako uložená", + "pad.savedrevs.timeslider": "Návštěvou časové osy zobrazíte uložené revize", "pad.userlist.entername": "Zadejte své jméno", "pad.userlist.unnamed": "nejmenovaný", "pad.userlist.guest": "Host", @@ -119,6 +122,7 @@ "pad.impexp.importing": "Importování…", "pad.impexp.confirmimport": "Import souboru přepíše aktuální text v padu. Opravdu chcete tuto akci provést?", "pad.impexp.convertFailed": "Tento soubor nelze importovat. Použijte prosím jiný formát dokumentu nebo nakopírujte text ručně", + "pad.impexp.padHasData": "Tento soubor jsme nebyly schopni importovat, protože tento Pad již obsahoval změny. Importujte ho prosím do nového padu", "pad.impexp.uploadFailed": "Nahrávání selhalo, zkuste to znovu", "pad.impexp.importfailed": "Import selhal", "pad.impexp.copypaste": "Vložte prosím kopii", diff --git a/src/locales/de.json b/src/locales/de.json index a2bca723..4888b8e8 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -36,6 +36,7 @@ "pad.settings.padSettings": "Pad Einstellungen", "pad.settings.myView": "Eigene Ansicht", "pad.settings.stickychat": "Chat immer anzeigen", + "pad.settings.chatandusers": "Chat und Benutzer anzeigen", "pad.settings.colorcheck": "Autorenfarben anzeigen", "pad.settings.linenocheck": "Zeilennummern", "pad.settings.rtlcheck": "Inhalt von rechts nach links lesen?", @@ -109,6 +110,7 @@ "timeslider.month.december": "Dezember", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: unbenannter Autor, other: unbenannte Autoren ]}", "pad.savedrevs.marked": "Diese Version wurde jetzt als gespeicherte Version gekennzeichnet", + "pad.savedrevs.timeslider": "Du kannst gespeicherte Versionen durch das Besuchen der Pad-Versionsgeschichte ansehen", "pad.userlist.entername": "Geben Sie Ihren Namen ein", "pad.userlist.unnamed": "unbenannt", "pad.userlist.guest": "Gast", @@ -119,7 +121,7 @@ "pad.impexp.importing": "Importiere …", "pad.impexp.confirmimport": "Das Importieren einer Datei überschreibt den aktuellen Text des Pads. Wollen Sie wirklich fortfahren?", "pad.impexp.convertFailed": "Wir können diese Datei nicht importieren. Bitte verwenden Sie ein anderes Dokumentformat oder übertragen Sie den Text manuell.", - "pad.impexp.padHasData": "Wir konnten diese Datei nicht importieren, da dieses Pad bereits Änderungen hat. Bitte importiere zu einem neuen Pad.", + "pad.impexp.padHasData": "Wir konnten diese Datei nicht importieren, da dieses Pad bereits Änderungen enthält. Bitte importiere sie in ein neues Pad.", "pad.impexp.uploadFailed": "Der Upload ist fehlgeschlagen. Bitte versuchen Sie es erneut.", "pad.impexp.importfailed": "Import fehlgeschlagen", "pad.impexp.copypaste": "Bitte kopieren und einfügen", diff --git a/src/locales/el.json b/src/locales/el.json index 740da95c..f18c71e4 100644 --- a/src/locales/el.json +++ b/src/locales/el.json @@ -37,6 +37,7 @@ "pad.settings.padSettings": "Ρυθμίσεις Pad", "pad.settings.myView": "Η προβολή μου", "pad.settings.stickychat": "Να είναι πάντα ορατή η συνομιλία", + "pad.settings.chatandusers": "Εμφάνιση Συνομιλίας και Χρηστών", "pad.settings.colorcheck": "Χρώματα συντάκτη", "pad.settings.linenocheck": "Αριθμοί γραμμών", "pad.settings.rtlcheck": "Διαβάζεται το περιεχόμενο από δεξιά προς τα αριστερά;", @@ -110,6 +111,7 @@ "timeslider.month.december": "Δεκεμβρίου", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: ανώνυμος συντάκτης, other: ανώνυμοι συντάκτες]}", "pad.savedrevs.marked": "Αυτή η έκδοση επισημάνθηκε ως αποθηκευμένη έκδοση", + "pad.savedrevs.timeslider": "Μπορείτε να δείτε αποθηκευμένες αναθεωρήσεις στο χρονοδιάγραμμα", "pad.userlist.entername": "Εισάγετε το όνομά σας", "pad.userlist.unnamed": "ανώνυμος", "pad.userlist.guest": "Επισκέπτης", diff --git a/src/locales/en-gb.json b/src/locales/en-gb.json new file mode 100644 index 00000000..258b4331 --- /dev/null +++ b/src/locales/en-gb.json @@ -0,0 +1,127 @@ +{ + "@metadata": { + "authors": [ + "Chase me ladies, I'm the Cavalry", + "Shirayuki" + ] + }, + "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 (Ctrl+5)", + "pad.toolbar.ol.title": "Ordered list (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Unordered List (Ctrl+Shift+L)", + "pad.toolbar.indent.title": "Indent (Tab)", + "pad.toolbar.unindent.title": "Outdent (Shift+Tab)", + "pad.toolbar.undo.title": "Undo (Ctrl+Z)", + "pad.toolbar.redo.title": "Redo (Ctrl+Y)", + "pad.toolbar.clearAuthorship.title": "Clear Authorship Colours (Ctrl+Shift+C)", + "pad.toolbar.import_export.title": "Import/Export from/to different file formats", + "pad.toolbar.timeslider.title": "Timeslider", + "pad.toolbar.savedRevision.title": "Save Revision", + "pad.toolbar.settings.title": "Settings", + "pad.toolbar.embed.title": "Share and 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.noCookie": "Cookie could not be found. Please allow cookies in your browser!", + "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.chatandusers": "Show Chat and Users", + "pad.settings.colorcheck": "Authorship colours", + "pad.settings.linenocheck": "Line numbers", + "pad.settings.rtlcheck": "Read content from right to left?", + "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.exportetherpad": "Etherpad", + "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.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 authorised", + "pad.modals.unauth.explanation": "Your permissions have changed while viewing this page. Try to reconnect.", + "pad.modals.looping.explanation": "There are communication problems with the synchronisation 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 synchronisation server.", + "pad.modals.initsocketfail.cause": "This is probably due to a problem with your browser or your internet connection.", + "pad.modals.slowcommit.explanation": "The server is not responding.", + "pad.modals.slowcommit.cause": "This could be due to problems with network connectivity.", + "pad.modals.badChangeset.explanation": "An edit you have made was classified illegal by the synchronisation server.", + "pad.modals.badChangeset.cause": "This could be due to a wrong server configuration or some other unexpected behaviour. Please contact the service administrator, if you feel this is an error. Try to reconnect in order to continue editing.", + "pad.modals.corruptPad.explanation": "The pad you are trying to access is corrupt.", + "pad.modals.corruptPad.cause": "This may be due to a wrong server configuration or some other unexpected behaviour. Please contact the service administrator.", + "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 the service administrator 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": "{{day}}/{{month}}/{{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.unnamedauthors": "{{num}} unnamed {[plural(num) one: author, other: authors ]}", + "pad.savedrevs.marked": "This revision is now marked as a saved revision", + "pad.savedrevs.timeslider": "You can see saved revisions by visiting the timeslider", + "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 colours 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.padHasData": "We were not able to import this file because this Pad has already had changes, please import to a new pad", + "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." +} diff --git a/src/locales/es.json b/src/locales/es.json index 5547d327..21eb60a7 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -10,7 +10,8 @@ "VegaDark", "Vivaelcelta", "Xuacu", - "Macofe" + "Macofe", + "Fitoschido" ] }, "index.newPad": "Nuevo pad", @@ -42,6 +43,7 @@ "pad.settings.padSettings": "Configuración del pad", "pad.settings.myView": "Preferencias personales", "pad.settings.stickychat": "Chat siempre en pantalla", + "pad.settings.chatandusers": "Mostrar el chat y los usuarios", "pad.settings.colorcheck": "Colores de autoría", "pad.settings.linenocheck": "Números de línea", "pad.settings.rtlcheck": "¿Leer contenido de derecha a izquierda?", @@ -56,11 +58,11 @@ "pad.importExport.export": "Exporta el pad actual como:", "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", - "pad.importExport.exportplain": "Texto plano", + "pad.importExport.exportplain": "Texto sin formato", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.abiword.innerHTML": "Sólo puedes importar formatos de texto plano o html. Para funciones más avanzadas instala abiword.", + "pad.importExport.abiword.innerHTML": "Solo es posible importar texto sin formato o en HTML. Para obtener funciones de importación más avanzadas es necesario instalar AbiWord.", "pad.modals.connected": "Conectado.", "pad.modals.reconnecting": "Reconectando a tu pad..", "pad.modals.forcereconnect": "Forzar reconexión", @@ -115,6 +117,7 @@ "timeslider.month.december": "diciembre", "timeslider.unnamedauthors": "{{num}} {[ plural(num) one: autor desconocido, other: autores desconocidos]}", "pad.savedrevs.marked": "Revisión guardada", + "pad.savedrevs.timeslider": "Puedes ver revisiones guardadas visitando la línea de tiempo", "pad.userlist.entername": "Escribe tu nombre", "pad.userlist.unnamed": "anónimo", "pad.userlist.guest": "Invitado", diff --git a/src/locales/eu.json b/src/locales/eu.json index f12b8d21..9cd06076 100644 --- a/src/locales/eu.json +++ b/src/locales/eu.json @@ -1,7 +1,8 @@ { "@metadata": { "authors": [ - "Theklan" + "Theklan", + "Subi" ] }, "index.newPad": "Pad berria", @@ -44,6 +45,7 @@ "pad.importExport.import": "Igo edozein testu fitxategi edo dokumentu", "pad.importExport.importSuccessful": "Arrakastatsua!", "pad.importExport.export": "Oraingo pad hau honela esportatu:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Testu laua", "pad.importExport.exportword": "Microsoft Word", diff --git a/src/locales/fr.json b/src/locales/fr.json index 92fcb193..ba289e3b 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -50,6 +50,7 @@ "pad.settings.padSettings": "Paramètres du pad", "pad.settings.myView": "Ma vue", "pad.settings.stickychat": "Toujours afficher le chat", + "pad.settings.chatandusers": "Afficher la discussion et les utilisateurs", "pad.settings.colorcheck": "Couleurs d’identification", "pad.settings.linenocheck": "Numéros de lignes", "pad.settings.rtlcheck": "Le contenu doit-il être lu de droite à gauche ?", @@ -123,6 +124,7 @@ "timeslider.month.december": "décembre", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: auteur anonyme, other: auteurs anonymes ]}", "pad.savedrevs.marked": "Cette révision est maintenant marquée comme révision enregistrée", + "pad.savedrevs.timeslider": "Vous pouvez voir les révisions enregistrées en visitant l’ascenseur temporel", "pad.userlist.entername": "Entrez votre nom", "pad.userlist.unnamed": "anonyme", "pad.userlist.guest": "Invité", diff --git a/src/locales/gl.json b/src/locales/gl.json index b0ca6532..381296aa 100644 --- a/src/locales/gl.json +++ b/src/locales/gl.json @@ -27,13 +27,14 @@ "pad.colorpicker.save": "Gardar", "pad.colorpicker.cancel": "Cancelar", "pad.loading": "Cargando...", - "pad.noCookie": "A cookie non se puido atopar. Por favor, habilite as cookies no seu navegador!", + "pad.noCookie": "Non se puido atopar a cookie. Por favor, habilite as cookies no seu navegador!", "pad.passwordRequired": "Cómpre un contrasinal para acceder a este documento", "pad.permissionDenied": "Non ten permiso para acceder a este documento", "pad.wrongPassword": "O contrasinal era incorrecto", "pad.settings.padSettings": "Configuracións do documento", "pad.settings.myView": "A miña vista", "pad.settings.stickychat": "Chat sempre visible", + "pad.settings.chatandusers": "Mostrar o chat e os usuarios", "pad.settings.colorcheck": "Cores de identificación", "pad.settings.linenocheck": "Números de liña", "pad.settings.rtlcheck": "Quere ler o contido da dereita á esquerda?", @@ -107,6 +108,7 @@ "timeslider.month.december": "decembro", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: autor anónimo, other: autores anónimos ]}", "pad.savedrevs.marked": "Esta revisión está agora marcada como revisión gardada", + "pad.savedrevs.timeslider": "Pode consultar as revisións gardadas visitando a liña do tempo", "pad.userlist.entername": "Insira o seu nome", "pad.userlist.unnamed": "anónimo", "pad.userlist.guest": "Convidado", @@ -117,7 +119,7 @@ "pad.impexp.importing": "Importando...", "pad.impexp.confirmimport": "A importación dun ficheiro ha sobrescribir o texto actual do documento. Está seguro de querer continuar?", "pad.impexp.convertFailed": "Non somos capaces de importar o ficheiro. Utilice un formato de documento diferente ou copie e pegue manualmente", - "pad.impexp.padHasData": "Non puidemos importar este ficheiro porque este Pad xa tivo cambios, por favor, importe a un novo pad.", + "pad.impexp.padHasData": "Non puidemos importar este ficheiro porque este documento xa sufriu cambios; importe a un novo documento.", "pad.impexp.uploadFailed": "Houbo un erro ao cargar o ficheiro; inténteo de novo", "pad.impexp.importfailed": "Fallou a importación", "pad.impexp.copypaste": "Copie e pegue", diff --git a/src/locales/he.json b/src/locales/he.json index 573bc5f6..4222e153 100644 --- a/src/locales/he.json +++ b/src/locales/he.json @@ -29,12 +29,14 @@ "pad.colorpicker.save": "שמירה", "pad.colorpicker.cancel": "ביטול", "pad.loading": "טעינה...", + "pad.noCookie": "העוגייה לא נמצאה. נא לאפשר עוגיות בדפדפן שלך!", "pad.passwordRequired": "דרושה ססמה כדי לגשת לפנקס הזה", "pad.permissionDenied": "אין לך הרשאה לגשת לפנקס הזה", "pad.wrongPassword": "ססמתך הייתה שגויה", "pad.settings.padSettings": "הגדרות פנקס", "pad.settings.myView": "התצוגה שלי", "pad.settings.stickychat": "השיחה תמיד על המסך", + "pad.settings.chatandusers": "הצגת צ'אט ומשתמשים", "pad.settings.colorcheck": "צביעה לפי מחבר", "pad.settings.linenocheck": "מספרי שורות", "pad.settings.rtlcheck": "לקרוא את התוכן מימין לשמאל?", @@ -47,6 +49,7 @@ "pad.importExport.import": "העלאת כל קובץ טקסט או מסמך", "pad.importExport.importSuccessful": "זה עבד!", "pad.importExport.export": "ייצוא הפנקס הנוכחי בתור:", + "pad.importExport.exportetherpad": "את'רפד", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "טקסט רגיל", "pad.importExport.exportword": "מיקרוסופט וורד", @@ -107,6 +110,7 @@ "timeslider.month.december": "דצמבר", "timeslider.unnamedauthors": "{[plural(num) one: יוצר אחד, other: {{num}} יוצרים ]} ללא שם", "pad.savedrevs.marked": "גרסה זו מסומנת כגרסה שמורה", + "pad.savedrevs.timeslider": "אפשר להציג גרסאות שמורות באמצעות ביקור בגולל הזמן", "pad.userlist.entername": "נא להזין את שמך", "pad.userlist.unnamed": "ללא שם", "pad.userlist.guest": "אורח", @@ -117,6 +121,7 @@ "pad.impexp.importing": "ייבוא...", "pad.impexp.confirmimport": "ייבוא של קובץ יבטל את הטקסט הנוכחי בפנקס. האם ברצונך להמשיך?", "pad.impexp.convertFailed": "לא הצלחנו לייבא את הקובץ הזה. נא להשתמש בתסדיר מסמך שונה או להעתיק ולהדביק ידנית", + "pad.impexp.padHasData": "לא הצלחנו לייבא את הקובץ הזה, כי בפנקס הזה כבר יש שינויים. נא לייבא לפנקס חדש.", "pad.impexp.uploadFailed": "ההעלאה נכשלה, נא לנסות שוב", "pad.impexp.importfailed": "הייבוא נכשל", "pad.impexp.copypaste": "נא להעתיק ולהדביק", diff --git a/src/locales/hu.json b/src/locales/hu.json index 3102790d..287e7954 100644 --- a/src/locales/hu.json +++ b/src/locales/hu.json @@ -30,6 +30,7 @@ "pad.colorpicker.save": "Mentés", "pad.colorpicker.cancel": "Mégsem", "pad.loading": "Betöltés…", + "pad.noCookie": "Nem található a süti. Engedélyezd a böngésződben a sütik használatát!", "pad.passwordRequired": "Jelszóra van szükséged ezen notesz eléréséhez", "pad.permissionDenied": "Nincs engedélyed ezen notesz eléréséhez", "pad.wrongPassword": "A jelszó rossz volt", @@ -48,6 +49,7 @@ "pad.importExport.import": "Tetszőleges szövegfájl vagy dokumentum feltöltése", "pad.importExport.importSuccessful": "Siker!", "pad.importExport.export": "Jelenlegi notesz exportálása így:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Sima szöveg", "pad.importExport.exportword": "Microsoft Word", diff --git a/src/locales/it.json b/src/locales/it.json index 02ae8a9f..a6c30d96 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -5,7 +5,8 @@ "Gianfranco", "Muxator", "Vituzzu", - "Macofe" + "Macofe", + "Nivit" ] }, "index.newPad": "Nuovo Pad", @@ -36,6 +37,7 @@ "pad.settings.padSettings": "Impostazioni del Pad", "pad.settings.myView": "Mia visualizzazione", "pad.settings.stickychat": "Chat sempre sullo schermo", + "pad.settings.chatandusers": "Mostra chat e utenti", "pad.settings.colorcheck": "Colori che indicano gli autori", "pad.settings.linenocheck": "Numeri di riga", "pad.settings.rtlcheck": "Leggere il contenuto da destra a sinistra?", @@ -48,6 +50,7 @@ "pad.importExport.import": "Carica un file di testo o un documento", "pad.importExport.importSuccessful": "Riuscito!", "pad.importExport.export": "Esportare il Pad corrente come:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Solo testo", "pad.importExport.exportword": "Microsoft Word", diff --git a/src/locales/ksh.json b/src/locales/ksh.json index 8443e0ec..f851bbf1 100644 --- a/src/locales/ksh.json +++ b/src/locales/ksh.json @@ -26,12 +26,14 @@ "pad.colorpicker.save": "Faßhallde", "pad.colorpicker.cancel": "Ophüüre", "pad.loading": "Ben aam Laade …", + "pad.noCookie": "Dat Pläzje wood nit jevonge. Don dat en Dingem Brauser zohlohße!", "pad.passwordRequired": "Do bruchs e Paßwoot för heh dat Pädd.", "pad.permissionDenied": "Do häs nit dat Rääsch, op heh dat Pädd zohzejriife.", "pad.wrongPassword": "Ding Paßwoot wohr verkeht.", "pad.settings.padSettings": "Däm Pädd sing Enschtällonge", "pad.settings.myView": "Aanseesch", "pad.settings.stickychat": "Donn der Klaaf emmer aanzeije", + "pad.settings.chatandusers": "Dunn de Metmaacher un der Klaaf aanzeije", "pad.settings.colorcheck": "Färve för de Schriiver", "pad.settings.linenocheck": "Nommere för de Reije", "pad.settings.rtlcheck": "Schreff vun Rääschß noh Lenks?", @@ -44,6 +46,7 @@ "pad.importExport.import": "Donn jeede Täx udder jeede Zoot Dokemänt huhlaade", "pad.importExport.importSuccessful": "Jeschaff!", "pad.importExport.export": "Don dat Pädd äxpoteere alß:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Eijfach Täx", "pad.importExport.exportword": "Microsoft Word", @@ -104,6 +107,7 @@ "timeslider.month.december": "Dezämber", "timeslider.unnamedauthors": "{[plural(num) zero: keine, one: eine, other: {{num}} ]} nahmeloose Schriiver", "pad.savedrevs.marked": "Heh di Väsjohn es jäz faßjehallde.", + "pad.savedrevs.timeslider": "Mer kann de faßjehallde Väsjohne belohre beim Verjangeheid afschpelle", "pad.userlist.entername": "Jif Dinge Nahme en", "pad.userlist.unnamed": "nahmeloßß", "pad.userlist.guest": "Jaßß", @@ -114,6 +118,7 @@ "pad.impexp.importing": "Ben aam Empotteere …", "pad.impexp.confirmimport": "En Dattei ze empotteere määt der janze Täx em Pädd fott. Wells De dat verfaftesch hann?", "pad.impexp.convertFailed": "Mer kunnte di Dattei nit empoteere. Nemm en ander Dattei-Fommaat udder donn dä Täx vun Hand kopeere un ennföhje.", + "pad.impexp.padHasData": "Mer kunnte di Dattei nit empottehre weil et Pädd alt Veränderonge metjemaht hät. Donn se en e neu Pädd empottehre.", "pad.impexp.uploadFailed": "Dat Huhlaade es donävve jejange. Bes esu johd un probeer et norr_ens.", "pad.impexp.importfailed": "Et Empoteere es donävve jejange.", "pad.impexp.copypaste": "Bes esu johd un donn et koppeere un enfööje", diff --git a/src/locales/lv.json b/src/locales/lv.json index acdaaccd..ee402d33 100644 --- a/src/locales/lv.json +++ b/src/locales/lv.json @@ -9,14 +9,14 @@ "pad.toolbar.bold.title": "Treknrakstā (CTRL + B)", "pad.toolbar.italic.title": "Slīpraksta (Ctrl-es)", "pad.toolbar.underline.title": "Pasvītrojuma (CTRL + U)", - "pad.toolbar.strikethrough.title": "Pārsvītrojums", - "pad.toolbar.ol.title": "Sakārtots saraksts", - "pad.toolbar.ul.title": "Nesakārtots saraksts", - "pad.toolbar.indent.title": "Atkāpe", - "pad.toolbar.unindent.title": "Izkāpe", + "pad.toolbar.strikethrough.title": "Pārsvītrojums (Ctrl+5)", + "pad.toolbar.ol.title": "Sakārtots saraksts (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Nesakārtots saraksts (Ctrl+Shift+L)", + "pad.toolbar.indent.title": "Atkāpe (TAB)", + "pad.toolbar.unindent.title": "Izkāpe (Shift+TAB)", "pad.toolbar.undo.title": "Atsaukt (CTRL + Z)", "pad.toolbar.redo.title": "Atcelt atsaukšanu (CTRL + Y)", - "pad.toolbar.clearAuthorship.title": "Notīrit autoru krāsas", + "pad.toolbar.clearAuthorship.title": "Notīrit autoru krāsas (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importēšanas/eksportēšanas no un uz citu failu formātiem", "pad.toolbar.savedRevision.title": "Saglabāt pārskatīšanu", "pad.toolbar.settings.title": "Iestatījumi", @@ -46,6 +46,7 @@ "pad.importExport.exportword": "Programma Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open dokumenta formāts)", + "pad.modals.connected": "Pievienojies.", "pad.modals.userdup": "Atvērts citā logā", "pad.modals.unauth": "Nav atļauts", "pad.modals.looping.explanation": "Pastāv sakaru problēmas ar sinhronizācijas servera.", @@ -55,7 +56,7 @@ "pad.modals.deleted": "Dzēsts", "pad.modals.disconnected": "Jūs esat atvienots.", "pad.modals.disconnected.explanation": "Tika zaudēts savienojums ar serveri", - "pad.modals.disconnected.cause": "Iespējams, ka serveris nav pieejams. Lūgums paziņot mums, ja tas turpina notikt.", + "pad.modals.disconnected.cause": "Iespējams, ka serveris nav pieejams. Lūgums paziņot pakalpojuma administratoram, ja tas turpina notikt.", "pad.share": "Koplietot šo pad", "pad.share.readonly": "Tikai lasāms", "pad.share.link": "Saite", diff --git a/src/locales/mk.json b/src/locales/mk.json index 9fc6b817..a924875a 100644 --- a/src/locales/mk.json +++ b/src/locales/mk.json @@ -34,6 +34,7 @@ "pad.settings.padSettings": "Поставки на тетратката", "pad.settings.myView": "Мој поглед", "pad.settings.stickychat": "Разговорите секогаш на екранот", + "pad.settings.chatandusers": "Прикажи разговор и корисници", "pad.settings.colorcheck": "Авторски бои", "pad.settings.linenocheck": "Броеви на редовите", "pad.settings.rtlcheck": "Содржините да се читаат од десно на лево?", @@ -107,6 +108,7 @@ "timeslider.month.december": "декември", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: неименуван автор, other: неименувани автори ]}", "pad.savedrevs.marked": "Оваа преработка сега е означена како зачувана", + "pad.savedrevs.timeslider": "Можете да ги погледате зачуваните преработки посетувајќи го времеследниот лизгач", "pad.userlist.entername": "Внесете го вашето име", "pad.userlist.unnamed": "без име", "pad.userlist.guest": "Гостин", diff --git a/src/locales/nl.json b/src/locales/nl.json index ed69527e..183d0fa9 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -28,12 +28,14 @@ "pad.colorpicker.save": "Opslaan", "pad.colorpicker.cancel": "Annuleren", "pad.loading": "Bezig met laden…", + "pad.noCookie": "Er kon geen cookie gevonden worden. Zorg ervoor dat uw browser cookies accepteert.", "pad.passwordRequired": "U hebt een wachtwoord nodig om toegang te krijgen tot deze pad", "pad.permissionDenied": "U hebt geen rechten om deze pad te bekijken", "pad.wrongPassword": "U hebt een onjuist wachtwoord ingevoerd", "pad.settings.padSettings": "Padinstellingen", "pad.settings.myView": "Mijn overzicht", "pad.settings.stickychat": "Chat altijd zichtbaar", + "pad.settings.chatandusers": "Chat en gebruikers weergeven", "pad.settings.colorcheck": "Kleuren auteurs", "pad.settings.linenocheck": "Regelnummers", "pad.settings.rtlcheck": "Inhoud van rechts naar links lezen?", @@ -46,6 +48,7 @@ "pad.importExport.import": "Upload een tekstbestand of document", "pad.importExport.importSuccessful": "Afgerond", "pad.importExport.export": "Huidige pad exporteren als", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Tekst zonder opmaak", "pad.importExport.exportword": "Microsoft Word", @@ -106,6 +109,7 @@ "timeslider.month.december": "december", "timeslider.unnamedauthors": "{{num}} onbekende {[plural(num) one: auteur, other: auteurs ]}", "pad.savedrevs.marked": "Deze versie is nu gemarkeerd als opgeslagen versie", + "pad.savedrevs.timeslider": "U kunt opgeslagen versies bekijken via de tijdschuiver.", "pad.userlist.entername": "Geef uw naam op", "pad.userlist.unnamed": "zonder naam", "pad.userlist.guest": "Gast", @@ -116,6 +120,7 @@ "pad.impexp.importing": "Bezig met importeren…", "pad.impexp.confirmimport": "Door een bestand te importeren overschrijft u de huidige tekst van de pad. Wilt u echt doorgaan?", "pad.impexp.convertFailed": "Het was niet mogelijk dit bestand te importeren. Gebruik een andere documentopmaak of kopieer en plak de inhoud handmatig", + "pad.impexp.padHasData": "Het was niet mogelijk dit bestand te importeren omdat er al wijzigingen aan de etherpad zijn gemaakt. Importeer naar een nieuwe etherpad.", "pad.impexp.uploadFailed": "Het uploaden is mislukt. Probeer het opnieuw", "pad.impexp.importfailed": "Importeren is mislukt", "pad.impexp.copypaste": "Gebruik kopiëren en plakken", diff --git a/src/locales/oc.json b/src/locales/oc.json index 54a375ba..e62d387a 100644 --- a/src/locales/oc.json +++ b/src/locales/oc.json @@ -9,14 +9,14 @@ "pad.toolbar.bold.title": "Gras (Ctrl-B)", "pad.toolbar.italic.title": "Italica (Ctrl-I)", "pad.toolbar.underline.title": "Soslinhat (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Raiat", - "pad.toolbar.ol.title": "Lista ordenada", - "pad.toolbar.ul.title": "Lista amb de piuses", + "pad.toolbar.strikethrough.title": "Raiat (Ctrl+5)", + "pad.toolbar.ol.title": "Lista ordenada (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Lista pas ordenada (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Indentar (TAB)", "pad.toolbar.unindent.title": "Desindentar (Maj+TAB)", "pad.toolbar.undo.title": "Anullar (Ctrl-Z)", "pad.toolbar.redo.title": "Restablir (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Escafar las colors qu'identifican los autors", + "pad.toolbar.clearAuthorship.title": "Escafar las colors qu'identifican los autors (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importar/Exportar de/cap a un format de fichièr diferent", "pad.toolbar.timeslider.title": "Istoric dinamic", "pad.toolbar.savedRevision.title": "Enregistrar la revision", @@ -26,6 +26,7 @@ "pad.colorpicker.save": "Enregistrar", "pad.colorpicker.cancel": "Anullar", "pad.loading": "Cargament...", + "pad.noCookie": "Lo cookie a pas pogut èsser trobat. Autorizatz los cookies dins vòstre navigador !", "pad.passwordRequired": "Avètz besonh d'un senhal per accedir a aqueste Pad", "pad.permissionDenied": "Vos es pas permés d’accedir a aqueste Pad.", "pad.wrongPassword": "Senhal incorrècte", @@ -44,6 +45,7 @@ "pad.importExport.import": "Cargar un tèxte o un document", "pad.importExport.importSuccessful": "Capitat !", "pad.importExport.export": "Exportar lo Pad actual coma :", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Tèxte brut", "pad.importExport.exportword": "Microsoft Word", @@ -114,6 +116,7 @@ "pad.impexp.importing": "Impòrt en cors...", "pad.impexp.confirmimport": "Importar un fichièr espotirà lo tèxte actual del blòt. Sètz segur que lo volètz far ?", "pad.impexp.convertFailed": "Podèm pas importar aqueste fichièr. Utilizatz un autre format de document o fasètz un copiar/pegar manual", + "pad.impexp.padHasData": "Avèm pas pogut importar aqueste fichièr perque aqueste blòt a ja agut de modificacions ; importatz cap a un blòt novèl", "pad.impexp.uploadFailed": "Lo telecargament a fracassat, reensajatz", "pad.impexp.importfailed": "Fracàs de l'importacion", "pad.impexp.copypaste": "Copiatz/pegatz", diff --git a/src/locales/pt-br.json b/src/locales/pt-br.json index e8eb79ee..1fd145bc 100644 --- a/src/locales/pt-br.json +++ b/src/locales/pt-br.json @@ -11,7 +11,8 @@ "Dianakc", "Macofe", "Rodrigo codignoli", - "Webysther" + "Webysther", + "Fasouzafreitas" ] }, "index.newPad": "Nova Nota", @@ -43,6 +44,7 @@ "pad.settings.padSettings": "Configurações da Nota", "pad.settings.myView": "Minha Visão", "pad.settings.stickychat": "Conversa sempre visível", + "pad.settings.chatandusers": "Mostrar o chat e os usuários", "pad.settings.colorcheck": "Cores de autoria", "pad.settings.linenocheck": "Números de linha", "pad.settings.rtlcheck": "Ler conteúdo da direita para esquerda?", @@ -116,6 +118,7 @@ "timeslider.month.december": "Dezembro", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: autor anônimo, other: autores anônimos ]}", "pad.savedrevs.marked": "Esta revisão foi marcada como salva", + "pad.savedrevs.timeslider": "Pode consultar as revisões salvas visitando a linha do tempo", "pad.userlist.entername": "Insira o seu nome", "pad.userlist.unnamed": "Sem título", "pad.userlist.guest": "Convidado", diff --git a/src/locales/ru.json b/src/locales/ru.json index f96f2338..bd2143a1 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -48,6 +48,7 @@ "pad.importExport.import": "Загрузить любой текстовый файл или документ", "pad.importExport.importSuccessful": "Успешно!", "pad.importExport.export": "Экспортировать текущий документ как:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Обычный текст", "pad.importExport.exportword": "Microsoft Word", diff --git a/src/locales/sr-ec.json b/src/locales/sr-ec.json new file mode 100644 index 00000000..9fd20b18 --- /dev/null +++ b/src/locales/sr-ec.json @@ -0,0 +1,59 @@ +{ + "@metadata": { + "authors": [ + "Aktron", + "Milicevic01", + "Милан Јелисавчић" + ] + }, + "index.newPad": "Нови Пад", + "pad.toolbar.bold.title": "Подебљано (Ctrl-B)", + "pad.toolbar.italic.title": "Искошено (Ctrl-I)", + "pad.toolbar.underline.title": "Подвучено (Ctrl-U)", + "pad.toolbar.strikethrough.title": "Прецртано", + "pad.toolbar.ol.title": "Уређен списак", + "pad.toolbar.ul.title": "Неуређен списак", + "pad.toolbar.indent.title": "Увлачење (TAB)", + "pad.toolbar.undo.title": "Опозови (Ctrl+Z)", + "pad.toolbar.settings.title": "Подешавања", + "pad.colorpicker.save": "Сачувај", + "pad.colorpicker.cancel": "Откажи", + "pad.loading": "Учитавање...", + "pad.wrongPassword": "Ваша лозинка није исправна", + "pad.settings.myView": "Мој приказ", + "pad.settings.fontType": "Врста фонта:", + "pad.settings.fontType.normal": "Нормално", + "pad.settings.fontType.monospaced": "Monospace", + "pad.settings.globalView": "Глобални приказ", + "pad.settings.language": "Језик:", + "pad.importExport.import_export": "Увоз/извоз", + "pad.importExport.import": "Отпремите било коју текстуалну датотеку или документ", + "pad.importExport.importSuccessful": "Успело!", + "pad.importExport.exporthtml": "HTML", + "pad.importExport.exportplain": "чист текст", + "pad.importExport.exportpdf": "PDF", + "pad.modals.connected": "Повезано.", + "pad.modals.slowcommit.explanation": "Сервер не одговара.", + "pad.modals.deleted": "Обрисано.", + "pad.share": "Дели овај пад", + "pad.share.readonly": "Само за читање", + "pad.share.link": "Веза", + "pad.chat": "Ћаскање", + "pad.chat.title": "Отворите ћаскање за овај пад.", + "pad.chat.loadmessages": "Учитајте више порука.", + "timeslider.month.january": "јануар", + "timeslider.month.february": "фебруар", + "timeslider.month.march": "март", + "timeslider.month.april": "април", + "timeslider.month.may": "мај", + "timeslider.month.june": "јун", + "timeslider.month.july": "јул", + "timeslider.month.august": "август", + "timeslider.month.september": "септембар", + "timeslider.month.october": "октобар", + "timeslider.month.november": "новембар", + "timeslider.month.december": "децембар", + "pad.userlist.approve": "одобрено", + "pad.impexp.importbutton": "Увези одмах", + "pad.impexp.importing": "Увожење..." +} diff --git a/src/locales/sv.json b/src/locales/sv.json index a53146bf..ae9b1f9a 100644 --- a/src/locales/sv.json +++ b/src/locales/sv.json @@ -35,6 +35,7 @@ "pad.settings.padSettings": "Blockinställningar", "pad.settings.myView": "Min vy", "pad.settings.stickychat": "Chatten alltid på skärmen", + "pad.settings.chatandusers": "Visa chatt och användare", "pad.settings.colorcheck": "Författarskapsfärger", "pad.settings.linenocheck": "Radnummer", "pad.settings.rtlcheck": "Vill du läsa innehållet från höger till vänster?", @@ -108,6 +109,7 @@ "timeslider.month.december": "december", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: namnlös författare, other: namnlösa författare]}", "pad.savedrevs.marked": "Denna version är nu markerad som en sparad version", + "pad.savedrevs.timeslider": "Du kan se sparade versioner med tidsreglaget", "pad.userlist.entername": "Ange ditt namn", "pad.userlist.unnamed": "namnlös", "pad.userlist.guest": "Gäst", diff --git a/src/locales/tr.json b/src/locales/tr.json index 54030fda..b0b6dbee 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -30,6 +30,7 @@ "pad.colorpicker.save": "Kaydet", "pad.colorpicker.cancel": "İptal", "pad.loading": "Yükleniyor...", + "pad.noCookie": "Çerez bulunamadı. Lütfen tarayıcınızda çerezlere izin veriniz!", "pad.passwordRequired": "Bu bloknota erişebilmeniz için parolaya ihtiyacınız var", "pad.permissionDenied": "Bu bloknota erişmeye izniniz yok", "pad.wrongPassword": "Parolanız yanlış", @@ -48,6 +49,7 @@ "pad.importExport.import": "Herhangi bir metin dosyası ya da belgesi yükle", "pad.importExport.importSuccessful": "Başarılı!", "pad.importExport.export": "Mevcut bloknotu şu olarak dışa aktar:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Düz metin", "pad.importExport.exportword": "Microsoft Word", diff --git a/src/locales/zh-hans.json b/src/locales/zh-hans.json index bc1c97b5..4af3cb48 100644 --- a/src/locales/zh-hans.json +++ b/src/locales/zh-hans.json @@ -42,6 +42,7 @@ "pad.settings.padSettings": "记事本设置", "pad.settings.myView": "我的视窗", "pad.settings.stickychat": "总是显示聊天屏幕", + "pad.settings.chatandusers": "显示聊天和用户", "pad.settings.colorcheck": "作者颜色", "pad.settings.linenocheck": "行号", "pad.settings.rtlcheck": "从右到左阅读内容吗?", @@ -100,9 +101,9 @@ "timeslider.exportCurrent": "当前版本导出为:", "timeslider.version": "版本 {{version}}", "timeslider.saved": "在{{year}}年{{month}}{{day}}日保存", - "timeslider.dateformat": "{{year}}年{{month}}{{day}}日 {{hours}}时:{{minutes}}分:{{seconds}}秒", - "timeslider.month.january": "一月", - "timeslider.month.february": "二月", + "timeslider.dateformat": "{{year}}年{{month}}月{{day}}日 {{hours}}时:{{minutes}}分:{{seconds}}秒", + "timeslider.month.january": "1月", + "timeslider.month.february": "2月", "timeslider.month.march": "三月", "timeslider.month.april": "四月", "timeslider.month.may": "五月", @@ -115,6 +116,7 @@ "timeslider.month.december": "十二月", "timeslider.unnamedauthors": "{{num}}个匿名作者", "pad.savedrevs.marked": "这一修订现在被标记为已保存的修订版本", + "pad.savedrevs.timeslider": "您可以使用时间滑块查阅已保存的版本", "pad.userlist.entername": "输入您的姓名", "pad.userlist.unnamed": "匿名", "pad.userlist.guest": "访客", diff --git a/src/node/db/API.js b/src/node/db/API.js index 81dedcfe..69a380c7 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -517,6 +517,117 @@ exports.getRevisionsCount = function(padID, callback) }); } +/** +getSavedRevisionsCount(padID) returns the number of saved revisions of this pad + +Example returns: + +{code: 0, message:"ok", data: {savedRevisions: 42}} +{code: 1, message:"padID does not exist", data: null} +*/ +exports.getSavedRevisionsCount = function(padID, callback) +{ + //get the pad + getPadSafe(padID, true, function(err, pad) + { + if(ERR(err, callback)) return; + + callback(null, {savedRevisions: pad.getSavedRevisionsNumber()}); + }); +} + +/** +listSavedRevisions(padID) returns the list of saved revisions of this pad + +Example returns: + +{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}} +{code: 1, message:"padID does not exist", data: null} +*/ +exports.listSavedRevisions = function(padID, callback) +{ + //get the pad + getPadSafe(padID, true, function(err, pad) + { + if(ERR(err, callback)) return; + + callback(null, {savedRevisions: pad.getSavedRevisionsList()}); + }); +} + +/** +saveRevision(padID) returns the list of saved revisions of this pad + +Example returns: + +{code: 0, message:"ok", data: null} +{code: 1, message:"padID does not exist", data: null} +*/ +exports.saveRevision = function(padID, rev, callback) +{ + //check if rev is set + if(typeof rev == "function") + { + callback = rev; + rev = undefined; + } + + //check if rev is a number + if(rev !== undefined && typeof rev != "number") + { + //try to parse the number + if(!isNaN(parseInt(rev))) + { + rev = parseInt(rev); + } + else + { + callback(new customError("rev is not a number", "apierror")); + return; + } + } + + //ensure this is not a negativ number + if(rev !== undefined && rev < 0) + { + callback(new customError("rev is a negativ number","apierror")); + return; + } + + //ensure this is not a float value + if(rev !== undefined && !is_int(rev)) + { + callback(new customError("rev is a float value","apierror")); + return; + } + + //get the pad + getPadSafe(padID, true, function(err, pad) + { + if(ERR(err, callback)) return; + + //the client asked for a special revision + if(rev !== undefined) + { + //check if this is a valid revision + if(rev > pad.getHeadRevisionNumber()) + { + callback(new customError("rev is higher than the head revision of the pad","apierror")); + return; + } + } else { + rev = pad.getHeadRevisionNumber(); + } + + authorManager.createAuthor('API', function(err, author) { + if(ERR(err, callback)) return; + + pad.addSavedRevision(rev, author.authorID, 'Saved through API call'); + callback(); + }); + }); +} + /** getLastEdited(padID) returns the timestamp of the last revision of the pad diff --git a/src/node/db/Pad.js b/src/node/db/Pad.js index 2f5860f8..53847600 100644 --- a/src/node/db/Pad.js +++ b/src/node/db/Pad.js @@ -54,6 +54,21 @@ Pad.prototype.getHeadRevisionNumber = function getHeadRevisionNumber() { return this.head; }; +Pad.prototype.getSavedRevisionsNumber = function getSavedRevisionsNumber() { + return this.savedRevisions.length; +}; + +Pad.prototype.getSavedRevisionsList = function getSavedRevisionsList() { + var savedRev = new Array(); + for(var rev in this.savedRevisions){ + savedRev.push(this.savedRevisions[rev].revNum); + } + savedRev.sort(function(a, b) { + return a - b; + }); + return savedRev; +}; + Pad.prototype.getPublicStatus = function getPublicStatus() { return this.publicStatus; }; diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index a26dd2cf..232b0b46 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -368,6 +368,9 @@ var version = , "setHTML" : ["padID", "html"] , "getAttributePool" : ["padID"] , "getRevisionsCount" : ["padID"] + , "getSavedRevisionsCount" : ["padID"] + , "listSavedRevisions" : ["padID"] + , "saveRevision" : ["padID", "rev"] , "getRevisionChangeset" : ["padID", "rev"] , "getLastEdited" : ["padID"] , "deletePad" : ["padID"] diff --git a/src/node/hooks/express.js b/src/node/hooks/express.js index e858b800..3275bd3f 100644 --- a/src/node/hooks/express.js +++ b/src/node/hooks/express.js @@ -10,24 +10,9 @@ var server; var serverName; exports.createServer = function () { - //try to get the git version - var version = ""; - try - { - var rootPath = path.resolve(npm.dir, '..'); - var ref = fs.readFileSync(rootPath + "/.git/HEAD", "utf-8"); - var refPath = rootPath + "/.git/" + ref.substring(5, ref.indexOf("\n")); - version = fs.readFileSync(refPath, "utf-8"); - version = version.substring(0, 7); - console.log("Your Etherpad git version is " + version); - } - catch(e) - { - console.warn("Can't get git version for server header\n" + e.message) - } console.log("Report bugs at https://github.com/ether/etherpad-lite/issues") - serverName = "Etherpad " + version + " (http://etherpad.org)"; + serverName = "Etherpad " + settings.getGitCommit() + " (http://etherpad.org)"; exports.restartServer(); @@ -38,7 +23,6 @@ exports.createServer = function () { else{ console.warn("Admin username and password not set in settings.json. To access admin please uncomment and edit 'users' in settings.json"); } - } exports.restartServer = function () { diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js index 34eafd0b..5015cc5a 100644 --- a/src/node/hooks/express/adminplugins.js +++ b/src/node/hooks/express/adminplugins.js @@ -1,4 +1,5 @@ var eejs = require('ep_etherpad-lite/node/eejs'); +var settings = require('ep_etherpad-lite/node/utils/Settings'); var installer = require('ep_etherpad-lite/static/js/pluginfw/installer'); var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins'); var _ = require('underscore'); @@ -15,7 +16,8 @@ exports.expressCreateServer = function (hook_name, args, cb) { res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins.html", render_args) ); }); args.app.get('/admin/plugins/info', function(req, res) { - res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", {}) ); + var gitCommit = settings.getGitCommit(); + res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", {gitCommit:gitCommit}) ); }); } diff --git a/src/node/hooks/express/socketio.js b/src/node/hooks/express/socketio.js index b70aa50e..35d6d074 100644 --- a/src/node/hooks/express/socketio.js +++ b/src/node/hooks/express/socketio.js @@ -23,8 +23,12 @@ exports.expressCreateServer = function (hook_name, args, cb) { io.use(function(socket, accept) { var data = socket.request; - if (!data.headers.cookie) return accept('No session cookie transmitted.', false); - + // Use a setting if we want to allow load Testing + if(!data.headers.cookie && settings.loadTest){ + accept(null, true); + }else{ + if (!data.headers.cookie) return accept('No session cookie transmitted.', false); + } // Use connect's cookie parser, because it knows how to parse signed cookies connect.cookieParser(webaccess.secret)(data, {}, function(err){ if(err) { diff --git a/src/node/utils/ExportEtherpad.js b/src/node/utils/ExportEtherpad.js index 4f91e4e3..46ae0d7a 100644 --- a/src/node/utils/ExportEtherpad.js +++ b/src/node/utils/ExportEtherpad.js @@ -23,9 +23,19 @@ exports.getPadRaw = function(padId, callback){ async.waterfall([ function(cb){ - // Get the Pad available content keys - db.findKeys("pad:"+padId+"*", null, function(err,records){ + // Get the Pad + db.findKeys("pad:"+padId, null, function(err,padcontent){ if(!err){ + cb(err, padcontent); + } + }) + }, + function(padcontent,cb){ + + // Get the Pad available content keys + db.findKeys("pad:"+padId+":*", null, function(err,records){ + if(!err){ + for (var key in padcontent) { records.push(padcontent[key]);} cb(err, records); } }) @@ -48,7 +58,7 @@ exports.getPadRaw = function(padId, callback){ // Get the author info db.get("globalAuthor:"+authorId, function(e, authorEntry){ - authorEntry.padIDs = padId; + if(authorEntry && authorEntry.padIDs) authorEntry.padIDs = padId; if(!e) data["globalAuthor:"+authorId] = authorEntry; }); diff --git a/src/node/utils/ImportEtherpad.js b/src/node/utils/ImportEtherpad.js index 1574a3a9..37863bff 100644 --- a/src/node/utils/ImportEtherpad.js +++ b/src/node/utils/ImportEtherpad.js @@ -21,9 +21,22 @@ var db = require("../db/DB").db; exports.setPadRaw = function(padId, records, callback){ records = JSON.parse(records); + // !! HACK !! + // If you have a really large pad it will cause a Maximum Range Stack crash + // This is a temporary patch for that so things are kept stable. + var recordCount = Object.keys(records).length; + if(recordCount >= 50000){ + console.warn("Etherpad file is too large to import.. We need to fix this. See https://github.com/ether/etherpad-lite/issues/2524"); + return callback("tooLarge", false); + } + async.eachSeries(Object.keys(records), function(key, cb){ var value = records[key] + if(!value){ + cb(); // null values are bad. + } + // Author data if(value.padIDs){ // rewrite author pad ids @@ -34,7 +47,9 @@ exports.setPadRaw = function(padId, records, callback){ db.get(key, function(err, author){ if(author){ // Yes, add the padID to the author.. - author.padIDs.push(padId); + if( Object.prototype.toString.call(author) === '[object Array]'){ + author.padIDs.push(padId); + } value = author; }else{ // No, create a new array with the author info in diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.js index 7b868307..da101f8d 100644 --- a/src/node/utils/Minify.js +++ b/src/node/utils/Minify.js @@ -23,7 +23,7 @@ var ERR = require("async-stacktrace"); var settings = require('./Settings'); var async = require('async'); var fs = require('fs'); -var cleanCSS = require('clean-css'); +var CleanCSS = require('clean-css'); var jsp = require("uglify-js").parser; var pro = require("uglify-js").uglify; var path = require('path'); @@ -410,7 +410,8 @@ function compressJS(values) function compressCSS(values) { var complete = values.join("\n"); - return cleanCSS.process(complete); + var minimized = new CleanCSS().minify(complete).styles; + return minimized; } exports.minify = minify; diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js index 05ae3bd8..5382d819 100644 --- a/src/node/utils/Settings.js +++ b/src/node/utils/Settings.js @@ -144,6 +144,11 @@ exports.loglevel = "INFO"; */ exports.disableIPlogging = false; +/** + * Disable Load Testing + */ +exports.loadTest = false; + /* * log4js appender configuration */ @@ -179,6 +184,25 @@ exports.abiwordAvailable = function() } }; +// Provide git version if available +exports.getGitCommit = function() { + var version = ""; + try + { + var rootPath = path.resolve(npm.dir, '..'); + var ref = fs.readFileSync(rootPath + "/.git/HEAD", "utf-8"); + var refPath = rootPath + "/.git/" + ref.substring(5, ref.indexOf("\n")); + version = fs.readFileSync(refPath, "utf-8"); + version = version.substring(0, 7); + console.log("Your Etherpad git version is " + version); + } + catch(e) + { + console.warn("Can't get git version for server header\n" + e.message) + } + return version; +} + exports.reloadSettings = function reloadSettings() { // Discover where the settings file lives var settingsFilename = argv.settings || "settings.json"; @@ -261,3 +285,5 @@ exports.reloadSettings = function reloadSettings() { // initially load settings exports.reloadSettings(); + + diff --git a/src/node/utils/padDiff.js b/src/node/utils/padDiff.js index 88fa5cba..24d5bb0c 100644 --- a/src/node/utils/padDiff.js +++ b/src/node/utils/padDiff.js @@ -101,8 +101,12 @@ PadDiff.prototype._createClearStartAtext = function(rev, callback){ return callback(err); } + try { //apply the clearAuthorship changeset var newAText = Changeset.applyToAText(changeset, atext, self._pad.pool); + } catch(err) { + return callback(err) + } callback(null, newAText); }); @@ -209,10 +213,14 @@ PadDiff.prototype._createDiffAtext = function(callback) { if(superChangeset){ var deletionChangeset = self._createDeletionChangeset(superChangeset,atext,self._pad.pool); - //apply the superChangeset, which includes all addings - atext = Changeset.applyToAText(superChangeset,atext,self._pad.pool); - //apply the deletionChangeset, which adds a deletions - atext = Changeset.applyToAText(deletionChangeset,atext,self._pad.pool); + try { + //apply the superChangeset, which includes all addings + atext = Changeset.applyToAText(superChangeset,atext,self._pad.pool); + //apply the deletionChangeset, which adds a deletions + atext = Changeset.applyToAText(deletionChangeset,atext,self._pad.pool); + } catch(err) { + return callback(err) + } } callback(err, atext); diff --git a/src/package.json b/src/package.json index 0598a4b6..e2d9e299 100644 --- a/src/package.json +++ b/src/package.json @@ -13,22 +13,21 @@ ], "dependencies" : { "etherpad-yajsml" : "0.0.2", - "request" : "2.51.0", - "etherpad-require-kernel" : "1.0.7", - "resolve" : "1.0.0", - "socket.io" : "1.3.2", - "ueberDB" : "0.2.11", + "request" : "2.53.0", + "etherpad-require-kernel" : "1.0.8", + "resolve" : "1.1.0", + "socket.io" : "1.3.3", + "ueberDB" : "0.2.13", "express" : "3.8.1", "async" : "0.9.0", "connect" : "2.7.11", - "clean-css" : "0.3.2", + "clean-css" : "3.0.8", "uglify-js" : "2.4.16", "formidable" : "1.0.16", "log4js" : "0.6.22", - "nodemailer" : "0.3.44", "cheerio" : "0.18.0", "async-stacktrace" : "0.0.2", - "npm" : "2.2.0", + "npm" : "2.4.1", "ejs" : "1.0.0", "graceful-fs" : "3.0.5", "slide" : "1.1.6", @@ -41,13 +40,13 @@ "swagger-node-express" : "2.1.3", "channels" : "0.0.4", "jsonminify" : "0.2.3", - "measured" : "0.1.6", + "measured" : "1.0.0", "mocha" : "2.1.0", "supertest" : "0.15.0" }, "bin": { "etherpad-lite": "./node/server.js" }, "devDependencies": { - "wd" : "0.0.31" + "wd" : "0.3.11" }, "engines" : { "node" : ">=0.6.3", "npm" : ">=1.0" @@ -55,5 +54,5 @@ "repository" : { "type" : "git", "url" : "http://github.com/ether/etherpad-lite.git" }, - "version" : "1.5.1" + "version" : "1.5.2" } diff --git a/src/static/css/iframe_editor.css b/src/static/css/iframe_editor.css index 1f247e59..eb69364b 100644 --- a/src/static/css/iframe_editor.css +++ b/src/static/css/iframe_editor.css @@ -11,7 +11,10 @@ span { cursor: auto; } background: #acf; } -a { cursor: pointer !important; } +a { + cursor: pointer !important; + white-space:pre-wrap; +} ul, ol, li { padding: 0; diff --git a/src/static/css/pad.css b/src/static/css/pad.css index 70c15b51..c9ebff4a 100644 --- a/src/static/css/pad.css +++ b/src/static/css/pad.css @@ -373,7 +373,7 @@ li[data-key=showusers] > a #online_count { border-radius: 5px; } #myusernameform { - margin-left: 35px + margin-left: 30px } #myusernameedit { font-size: 1.3em; @@ -382,7 +382,7 @@ li[data-key=showusers] > a #online_count { height: 18px; margin: 0; border: 0; - width: 117px; + width: 122px; background: transparent; } #myusernameform input.editable { @@ -897,7 +897,7 @@ input[type=checkbox] { #connectivity, #users { position: absolute; - top: 36px; + top: 38px; right: 20px; display: none; z-index: 500; @@ -919,7 +919,8 @@ input[type=checkbox] { right:0px !important; border-radius:0px !important; width:182px !important; - margin:2px 0 0 0 !important; +/* Below makes UI look weird when X makes editbar flow onto two lines */ +/* margin:2px 0 0 0 !important;*/ border: none !important; border-bottom: 1px solid #ccc !important; height:155px !important; @@ -935,9 +936,8 @@ input[type=checkbox] { .chatAndUsersChat{ bottom:0px !important; padding:0 !important; - margin:0 !important; + margin: 165px 0px 0px 0px; right:0 !important; - top: 200px !important; width:182px !important; border: none !important; padding:5px !important; @@ -1014,12 +1014,16 @@ input[type=checkbox] { top: 72px !important; } } + +/* Mobile devices */ @media only screen and (min-device-width: 320px) and (max-device-width: 720px) { #users { top: auto; right:0px !important; bottom: 33px; border-radius: 0px !important; + height: 55px !important; + overflow: auto; } #mycolorpicker { left: -73px; @@ -1099,7 +1103,8 @@ input[type=checkbox] { } #chatbox{ position:absolute; - bottom:33px; + bottom:33px !important; + margin: 65px 0 0 0; } #gritter-notice-wrapper{ bottom:43px !important; diff --git a/src/static/js/Changeset.js b/src/static/js/Changeset.js index 32da887d..df180f9c 100644 --- a/src/static/js/Changeset.js +++ b/src/static/js/Changeset.js @@ -507,6 +507,7 @@ exports.opAssembler = function () { */ exports.stringIterator = function (str) { var curIndex = 0; + // newLines is the number of \n between curIndex and str.length var newLines = str.split("\n").length - 1 function getnewLines(){ return newLines @@ -909,42 +910,43 @@ exports.pack = function (oldLen, newLen, opsStr, bank) { * @params str {string} String to which a Changeset should be applied */ exports.applyToText = function (cs, str) { - var totalNrOfLines = str.split("\n").length; - var removedLines = 0; var unpacked = exports.unpack(cs); exports.assert(str.length == unpacked.oldLen, "mismatched apply: ", str.length, " / ", unpacked.oldLen); var csIter = exports.opIterator(unpacked.ops); var bankIter = exports.stringIterator(unpacked.charBank); var strIter = exports.stringIterator(str); - var newlines = 0 - var newlinefail = false var assem = exports.stringAssembler(); while (csIter.hasNext()) { var op = csIter.next(); switch (op.opcode) { case '+': + //op is + and op.lines 0: no newlines must be in op.chars + //op is + and op.lines >0: op.chars must include op.lines newlines + if(op.lines != bankIter.peek(op.chars).split("\n").length - 1){ + throw new Error("newline count is wrong in op +; cs:"+cs+" and text:"+str); + } assem.append(bankIter.take(op.chars)); break; case '-': - removedLines += op.lines; - newlines = strIter.newlines() - strIter.skip(op.chars); - if(!(newlines - strIter.newlines() == 0) && (newlines - strIter.newlines() != op.lines)){ - newlinefail = true + //op is - and op.lines 0: no newlines must be in the deleted string + //op is - and op.lines >0: op.lines newlines must be in the deleted string + if(op.lines != strIter.peek(op.chars).split("\n").length - 1){ + throw new Error("newline count is wrong in op -; cs:"+cs+" and text:"+str); } + strIter.skip(op.chars); break; case '=': - newlines = strIter.newlines() - assem.append(strIter.take(op.chars)); - if(!(newlines - strIter.newlines() == op.lines)){ - newlinefail = true + //op is = and op.lines 0: no newlines must be in the copied string + //op is = and op.lines >0: op.lines newlines must be in the copied string + if(op.lines != strIter.peek(op.chars).split("\n").length - 1){ + throw new Error("newline count is wrong in op =; cs:"+cs+" and text:"+str); } + assem.append(strIter.take(op.chars)); break; } } - exports.assert(totalNrOfLines >= removedLines,"cannot remove ", removedLines, " lines from text with ", totalNrOfLines, " lines"); assem.append(strIter.take(strIter.remaining())); - return [assem.toString(),newlinefail]; + return assem.toString(); }; /** @@ -1236,7 +1238,7 @@ exports.mutateAttributionLines = function (cs, lines, pool) { } } - exports.assert(!lineAssem, "line assembler not finished"); + exports.assert(!lineAssem, "line assembler not finished:"+cs); mut.close(); //dmesg("-> "+lines.toSource()); @@ -1615,12 +1617,8 @@ exports.makeAText = function (text, attribs) { * @param pool {AttribPool} Attribute Pool to add to */ exports.applyToAText = function (cs, atext, pool) { - var text = exports.applyToText(cs, atext.text) - if(text[1]){ - throw new Error() - } return { - text: text[0], + text: exports.applyToText(cs, atext.text), attribs: exports.applyToAttribution(cs, atext.attribs, pool) }; }; diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index 4b84e784..aa4bf6c7 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -3313,6 +3313,15 @@ function Ace2Inner(){ return [rep.lines.offsetOfIndex(lineRange[0]), rep.lines.offsetOfIndex(lineRange[1])]; } + function handleCut(evt) + { + inCallStackIfNecessary("handleCut", function() + { + doDeleteKey(evt); + }); + return true; + } + function handleClick(evt) { inCallStackIfNecessary("handleClick", function() @@ -4854,6 +4863,7 @@ function Ace2Inner(){ $(document).on("keypress", handleKeyEvent); $(document).on("keyup", handleKeyEvent); $(document).on("click", handleClick); + $(document).on("cut", handleCut); $(root).on("blur", handleBlur); if (browser.msie) { diff --git a/src/static/js/chat.js b/src/static/js/chat.js index ce9a0033..811b1320 100644 --- a/src/static/js/chat.js +++ b/src/static/js/chat.js @@ -57,14 +57,18 @@ var chat = (function() }, chatAndUsers: function(fromInitialCall) { - if(!userAndChat || fromInitialCall){ + var toEnable = $('#options-chatandusers').is(":checked"); + if(toEnable || !userAndChat || fromInitialCall){ padcookie.setPref("chatAndUsers", true); chat.stickToScreen(true); $('#options-stickychat').prop('checked', true) + $('#options-chatandusers').prop('checked', true) $('#options-stickychat').prop("disabled", "disabled"); $('#users').addClass("chatAndUsers"); $("#chatbox").addClass("chatAndUsersChat"); + // redraw userAndChat = true; + padeditbar.redrawHeight() }else{ padcookie.setPref("chatAndUsers", false); $('#options-stickychat').prop("disabled", false); @@ -91,7 +95,9 @@ var chat = (function() { if($('#chatbox').css("display") != "none"){ if(!self.lastMessage || !self.lastMessage.position() || self.lastMessage.position().top < $('#chattext').height()) { - $('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, "slow"); + // if we use a slow animate here we can have a race condition when a users focus can not be moved away + // from the last message recieved. + $('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, { duration: 400, queue: false }); self.lastMessage = $('#chattext > p').eq(-1); } } diff --git a/src/static/js/contentcollector.js b/src/static/js/contentcollector.js index 1f0620fe..e428c63f 100644 --- a/src/static/js/contentcollector.js +++ b/src/static/js/contentcollector.js @@ -87,6 +87,10 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas "li": 1 }; + _.each(hooks.callAll('ccRegisterBlockElements'), function(element){ + _blockElems[element] = 1; + }); + function isBlockElement(n) { return !!_blockElems[(dom.nodeTagName(n) || "").toLowerCase()]; @@ -320,7 +324,6 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas return [key, value]; }) ); - lines.appendText('*', Changeset.makeAttribsString('+', attributes , apool)); } cc.startNewLine = function(state) @@ -458,8 +461,23 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas else { var tname = (dom.nodeTagName(node) || "").toLowerCase(); + + if (tname == "img"){ + var collectContentImage = hooks.callAll('collectContentImage', { + cc: cc, + state: state, + tname: tname, + styl: styl, + cls: cls, + node: node + }); + }else{ + // THIS SEEMS VERY HACKY! -- Please submit a better fix! + delete state.lineAttributes.img + } + if (tname == "br") - { + { this.breakLine = true; var tvalue = dom.nodeAttr(node, 'value'); var induceLineBreak = hooks.callAll('collectContentLineBreak', { @@ -483,7 +501,6 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas { var styl = dom.nodeAttr(node, "style"); var cls = dom.nodeProp(node, "className"); - var isPre = (tname == "pre"); if ((!isPre) && abrowser.safari) { diff --git a/src/static/js/pad_editbar.js b/src/static/js/pad_editbar.js index e874614f..7d0539af 100644 --- a/src/static/js/pad_editbar.js +++ b/src/static/js/pad_editbar.js @@ -186,9 +186,26 @@ var padeditbar = (function() $('#editbar').css("height", editbarHeight); $('#editorcontainer').css("top", containerTop); + + // make sure pop ups are in the right place + if($('#editorcontainer').offset()){ + $('.popup').css("top", $('#editorcontainer').offset().top + "px"); + } + + // If sticky chat is enabled.. if($('#options-stickychat').is(":checked")){ - $('#chatbox').css("top", $('#editorcontainer').offset().top + "px"); + if($('#editorcontainer').offset()){ + $('#chatbox').css("top", $('#editorcontainer').offset().top + "px"); + } }; + + // If chat and Users is enabled.. + if($('#options-chatandusers').is(":checked")){ + if($('#editorcontainer').offset()){ + $('#users').css("top", $('#editorcontainer').offset().top + "px"); + } + } + }, registerDropdownCommand: function (cmd, dropdown) { dropdown = dropdown || cmd; diff --git a/src/static/js/pad_impexp.js b/src/static/js/pad_impexp.js index 77f1eb28..96761570 100644 --- a/src/static/js/pad_impexp.js +++ b/src/static/js/pad_impexp.js @@ -188,7 +188,8 @@ var padimpexp = (function() pad = _pad; //get /p/padname - var pad_root_path = new RegExp(/.*\/p\/[^\/]+/).exec(document.location.pathname); + // if /p/ isn't available due to a rewrite we use the clientVars padId + var pad_root_path = new RegExp(/.*\/p\/[^\/]+/).exec(document.location.pathname) || clientVars.padId; //get http://example.com/p/padname without Params var pad_root_url = document.location.protocol + '//' + document.location.host + document.location.pathname; diff --git a/src/templates/admin/plugins-info.html b/src/templates/admin/plugins-info.html index 0ca6d010..1b328a89 100644 --- a/src/templates/admin/plugins-info.html +++ b/src/templates/admin/plugins-info.html @@ -22,6 +22,8 @@
+

Etherpad Git Commit

+

<%= gitCommit %>

Installed plugins

<%- plugins.formatPlugins().replace(", ","\n") %>
diff --git a/tests/backend/specs/api/pad.js b/tests/backend/specs/api/pad.js index 6010a11c..52849c2e 100644 --- a/tests/backend/specs/api/pad.js +++ b/tests/backend/specs/api/pad.js @@ -48,33 +48,38 @@ describe('Permission', function(){ -> deletePad -- This gives us a guaranteed clear environment -> createPad -> getRevisions -- Should be 0 - -> getHTML -- Should be the default pad text in HTML format - -> deletePad -- Should just delete a pad - -> getHTML -- Should return an error - -> createPad(withText) - -> getText -- Should have the text specified above as the pad text - -> setText - -> getText -- Should be the text set before - -> getRevisions -- Should be 0 still? - -> padUsersCount -- Should be 0 - -> getReadOnlyId -- Should be a value - -> listAuthorsOfPad(padID) -- should be empty array? - -> getLastEdited(padID) -- Should be when pad was made - -> setText(padId) - -> getLastEdited(padID) -- Should be when setText was performed - -> padUsers(padID) -- Should be when setText was performed - - -> setText(padId, "hello world") + -> getSavedRevisionsCount(padID) -- Should be 0 + -> listSavedRevisions(padID) -- Should be an empty array + -> getHTML -- Should be the default pad text in HTML format + -> deletePad -- Should just delete a pad + -> getHTML -- Should return an error + -> createPad(withText) + -> getText -- Should have the text specified above as the pad text + -> setText + -> getText -- Should be the text set before + -> getRevisions -- Should be 0 still? + -> saveRevision + -> getSavedRevisionsCount(padID) -- Should be 0 still? + -> listSavedRevisions(padID) -- Should be an empty array still ? + -> padUsersCount -- Should be 0 + -> getReadOnlyId -- Should be a value + -> listAuthorsOfPad(padID) -- should be empty array? -> getLastEdited(padID) -- Should be when pad was made - -> getText(padId) -- Should be "hello world" - -> movePad(padID, newPadId) -- Should provide consistant pad data - -> getText(newPadId) -- Should be "hello world" - -> movePad(newPadID, originalPadId) -- Should provide consistant pad data - -> getText(originalPadId) -- Should be "hello world" - -> getLastEdited(padID) -- Should not be 0 - -> setHTML(padID) -- Should fail on invalid HTML - -> setHTML(padID) *3 -- Should fail on invalid HTML - -> getHTML(padID) -- Should return HTML close to posted HTML + -> setText(padId) + -> getLastEdited(padID) -- Should be when setText was performed + -> padUsers(padID) -- Should be when setText was performed + + -> setText(padId, "hello world") + -> getLastEdited(padID) -- Should be when pad was made + -> getText(padId) -- Should be "hello world" + -> movePad(padID, newPadId) -- Should provide consistant pad data + -> getText(newPadId) -- Should be "hello world" + -> movePad(newPadID, originalPadId) -- Should provide consistant pad data + -> getText(originalPadId) -- Should be "hello world" + -> getLastEdited(padID) -- Should not be 0 + -> setHTML(padID) -- Should fail on invalid HTML + -> setHTML(padID) *3 -- Should fail on invalid HTML + -> getHTML(padID) -- Should return HTML close to posted HTML */ @@ -109,11 +114,35 @@ describe('getRevisionsCount', function(){ }); }) +describe('getSavedRevisionsCount', function(){ + it('gets saved revisions count of Pad', function(done) { + api.get(endPoint('getSavedRevisionsCount')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to get Saved Revisions Count"); + if(res.body.data.savedRevisions !== 0) throw new Error("Incorrect Saved Revisions Count"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('listSavedRevisions', function(){ + it('gets saved revision list of Pad', function(done) { + api.get(endPoint('listSavedRevisions')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to get Saved Revisions List"); + if(!res.body.data.savedRevisions.equals([])) throw new Error("Incorrect Saved Revisions List"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + describe('getHTML', function(){ it('get the HTML of Pad', function(done) { api.get(endPoint('getHTML')+"&padID="+testPadId) .expect(function(res){ - if(res.body.data.html.length <= 1) throw new Error("Unable to get Revision Count"); + if(res.body.data.html.length <= 1) throw new Error("Unable to get the HTML"); }) .expect('Content-Type', /json/) .expect(200, done) @@ -187,16 +216,50 @@ describe('getText', function(){ }) describe('getRevisionsCount', function(){ - it('gets Revision Coutn of a Pad', function(done) { + it('gets Revision Count of a Pad', function(done) { api.get(endPoint('getRevisionsCount')+"&padID="+testPadId) .expect(function(res){ - if(res.body.data.revisions !== 1) throw new Error("Unable to set text revision count") + if(res.body.data.revisions !== 1) throw new Error("Unable to get text revision count") }) .expect('Content-Type', /json/) .expect(200, done) }); }) +describe('saveRevision', function(){ + it('saves Revision', function(done) { + api.get(endPoint('saveRevision')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to save Revision"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getSavedRevisionsCount', function(){ + it('gets saved revisions count of Pad', function(done) { + api.get(endPoint('getSavedRevisionsCount')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to get Saved Revisions Count"); + if(res.body.data.savedRevisions !== 1) throw new Error("Incorrect Saved Revisions Count"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('listSavedRevisions', function(){ + it('gets saved revision list of Pad', function(done) { + api.get(endPoint('listSavedRevisions')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to get Saved Revisions List"); + if(!res.body.data.savedRevisions.equals([1])) throw new Error("Incorrect Saved Revisions List"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) describe('padUsersCount', function(){ it('gets User Count of a Pad', function(done) { api.get(endPoint('padUsersCount')+"&padID="+testPadId) @@ -461,3 +524,25 @@ function generateLongText(){ } return text; } + +// Need this to compare arrays (listSavedRevisions test) +Array.prototype.equals = function (array) { + // if the other array is a falsy value, return + if (!array) + return false; + // compare lengths - can save a lot of time + if (this.length != array.length) + return false; + for (var i = 0, l=this.length; i < l; i++) { + // Check if we have nested arrays + if (this[i] instanceof Array && array[i] instanceof Array) { + // recurse into the nested arrays + if (!this[i].equals(array[i])) + return false; + } else if (this[i] != array[i]) { + // Warning - two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + return true; +}