Merge branch 'develop' of github.com:ether/etherpad-lite into support-images

This commit is contained in:
John McLear 2013-12-17 20:32:47 +00:00
commit 3f8e6faec0
30 changed files with 390 additions and 267 deletions

View file

@ -294,15 +294,14 @@ returns the text of a pad formatted as HTML
* `{code: 0, message:"ok", data: {html:"Welcome Text<br>More Text"}}`
* `{code: 1, message:"padID does not exist", data: null}`
#### setHTML(padID, text)
#### setHTML(padID, html)
* API >= 1
sets the html of a pad
sets the text of a pad based on HTML, HTML must be well formed. Malformed HTML will send a warning to the API log.
*Example returns:*
* `{code: 0, message:"ok", data: null}`
* `{code: 1, message:"padID does not exist", data: null}`
* `{code: 1, message:"text too long", data: null}`
#### getAttributePool(padID)
* API >= 1.2.8

View file

@ -1,8 +1,8 @@
{
"@metadata": {
"authors": {
"1": "Xuacu"
}
"authors": [
"Xuacu"
]
},
"index.newPad": "Nuevu bloc",
"index.createOpenPad": "o crear/abrir un bloc col nome:",

View file

@ -1,10 +1,10 @@
{
"@metadata": {
"authors": {
"0": "Christian List",
"1": "Peter Alberti",
"3": "Steenth"
}
"authors": [
"Christian List",
"Peter Alberti",
"Steenth"
]
},
"index.newPad": "Ny Pad",
"index.createOpenPad": "eller opret/åbn en Pad med navnet:",

View file

@ -1,11 +1,11 @@
{
"@metadata": {
"authors": {
"0": "Metalhead64",
"1": "Mklehr",
"2": "Nipsky",
"4": "Wikinaut"
}
"authors": [
"Metalhead64",
"Mklehr",
"Nipsky",
"Wikinaut"
]
},
"index.newPad": "Neues Pad",
"index.createOpenPad": "Pad mit folgendem Namen öffnen:",

View file

@ -1,16 +1,16 @@
{
"@metadata": {
"authors": {
"0": "Armando-Martin",
"1": "Jacobo",
"2": "Joker",
"3": "Larjona",
"4": "Mklehr",
"5": "Rubenwap",
"7": "VegaDark",
"8": "Vivaelcelta",
"9": "Xuacu"
}
"authors": [
"Armando-Martin",
"Jacobo",
"Joker",
"Larjona",
"Mklehr",
"Rubenwap",
"VegaDark",
"Vivaelcelta",
"Xuacu"
]
},
"index.newPad": "Nuevo Pad",
"index.createOpenPad": "o crea/abre un Pad con el nombre:",

View file

@ -1,13 +1,13 @@
{
"@metadata": {
"authors": {
"0": "BMRG14",
"1": "Dalba",
"2": "Ebraminio",
"3": "Reza1615",
"5": "ZxxZxxZ",
"6": "الناز"
}
"authors": [
"BMRG14",
"Dalba",
"Ebraminio",
"Reza1615",
"ZxxZxxZ",
"الناز"
]
},
"index.newPad": "دفترچه یادداشت تازه",
"index.createOpenPad": "یا ایجاد/بازکردن یک دفترچه یادداشت با نام:",

View file

@ -1,15 +1,16 @@
{
"@metadata": {
"authors": {
"0": "Artnay",
"1": "Jl",
"2": "Lliehu",
"3": "Nedergard",
"4": "Nike",
"6": "Stryn",
"7": "Veikk0.ma",
"8": "VezonThunder"
}
"authors": [
"Artnay",
"Jl",
"Lliehu",
"Nedergard",
"Nike",
"Stryn",
"Tomi Toivio",
"Veikk0.ma",
"VezonThunder"
]
},
"index.newPad": "Uusi muistio",
"index.createOpenPad": "tai luo tai avaa muistio nimellä:",
@ -19,8 +20,8 @@
"pad.toolbar.strikethrough.title": "Yliviivaus",
"pad.toolbar.ol.title": "Numeroitu lista",
"pad.toolbar.ul.title": "Numeroimaton lista",
"pad.toolbar.indent.title": "Sisennä",
"pad.toolbar.unindent.title": "Ulonna",
"pad.toolbar.indent.title": "Sisennä (TAB)",
"pad.toolbar.unindent.title": "Ulonna (Shift+TAB)",
"pad.toolbar.undo.title": "Kumoa (Ctrl-Z)",
"pad.toolbar.redo.title": "Tee uudelleen (Ctrl-Y)",
"pad.toolbar.clearAuthorship.title": "Poista kirjoittajavärit",

View file

@ -1,22 +1,22 @@
{
"@metadata": {
"authors": {
"0": "Cquoi",
"1": "Crochet.david",
"2": "Gomoko",
"3": "Goofy",
"4": "Goofy-bz",
"5": "Jean-Frédéric",
"6": "Leviathan",
"7": "McDutchie",
"8": "Metroitendo",
"9": "Od1n",
"10": "Peter17",
"11": "Quenenni",
"12": "Rastus Vernon",
"14": "Stephane Cottin",
"15": "Tux-tn"
}
"authors": [
"Cquoi",
"Crochet.david",
"Gomoko",
"Goofy",
"Goofy-bz",
"Jean-Frédéric",
"Leviathan",
"McDutchie",
"Metroitendo",
"Od1n",
"Peter17",
"Quenenni",
"Rastus Vernon",
"Stephane Cottin",
"Tux-tn"
]
},
"index.newPad": "Nouveau pad",
"index.createOpenPad": "ou créer/ouvrir un pad intitulé :",

View file

@ -1,8 +1,8 @@
{
"@metadata": {
"authors": {
"1": "Toliño"
}
"authors": [
"Toliño"
]
},
"index.newPad": "Novo documento",
"index.createOpenPad": "ou cree/abra un documento co nome:",

View file

@ -1,11 +1,11 @@
{
"@metadata": {
"authors": {
"0": "Amire80",
"1": "Ofrahod",
"3": "YaronSh",
"4": "תומר ט"
}
"authors": [
"Amire80",
"Ofrahod",
"YaronSh",
"תומר ט"
]
},
"index.newPad": "פנקס חדש",
"index.createOpenPad": "ליצור או לפתוח פנקס בשם:",

View file

@ -1,11 +1,11 @@
{
"@metadata": {
"authors": {
"0": "Dj",
"1": "Misibacsi",
"2": "R-Joe",
"4": "Tgr"
}
"authors": [
"Dj",
"Misibacsi",
"R-Joe",
"Tgr"
]
},
"index.newPad": "Új notesz",
"index.createOpenPad": "vagy notesz létrehozása ezen a néven:",

View file

@ -1,11 +1,11 @@
{
"@metadata": {
"authors": {
"0": "Beta16",
"1": "Gianfranco",
"2": "Muxator",
"4": "Vituzzu"
}
"authors": [
"Beta16",
"Gianfranco",
"Muxator",
"Vituzzu"
]
},
"index.newPad": "Nuovo Pad",
"index.createOpenPad": "o creare o aprire un Pad con il nome:",

View file

@ -1,9 +1,9 @@
{
"@metadata": {
"authors": {
"0": "Hym411",
"2": "아라"
}
"authors": [
"Hym411",
"아라"
]
},
"index.newPad": "새 패드",
"index.createOpenPad": "또는 다음 이름으로 패드 만들기/열기:",

View file

@ -1,8 +1,8 @@
{
"@metadata": {
"authors": {
"1": "Purodha"
}
"authors": [
"Purodha"
]
},
"index.newPad": "Neu Padd",
"index.createOpenPad": "udder maach e Padd op med däm Naame:",

View file

@ -1,9 +1,9 @@
{
"@metadata": {
"authors": {
"0": "Robby",
"2": "Soued031"
}
"authors": [
"Robby",
"Soued031"
]
},
"index.newPad": "Neie Pad",
"pad.toolbar.ol.title": "Numeréiert Lëscht",

View file

@ -12,8 +12,8 @@
"pad.toolbar.strikethrough.title": "Garis lorek",
"pad.toolbar.ol.title": "Senarai tertib",
"pad.toolbar.ul.title": "Senarai tak tertib",
"pad.toolbar.indent.title": "Engsot ke dalam",
"pad.toolbar.unindent.title": "Engsot ke luar",
"pad.toolbar.indent.title": "Engsot ke dalam (TAB)",
"pad.toolbar.unindent.title": "Engsot ke luar (Shift + TAB)",
"pad.toolbar.undo.title": "Buat asal (Ctrl-Z)",
"pad.toolbar.redo.title": "Buat semula (Ctrl-Y)",
"pad.toolbar.clearAuthorship.title": "Padamkan Warna Pengarang",
@ -66,11 +66,15 @@
"pad.modals.initsocketfail.cause": "Ini mungkin disebabkan oleh masalah dengan pelayar atau sambungan internet anda.",
"pad.modals.slowcommit.explanation": "Pelayan tidak membalas.",
"pad.modals.slowcommit.cause": "Ini mungkin disebabkan oleh masalah dengan kesambungan rangkaian anda.",
"pad.modals.badChangeset.explanation": "Suntingan yang telah anda lakukan telah dikira sebagai terlarang oleh pelayan penyegerakan.",
"pad.modals.badChangeset.cause": "Ini mungkin disebabkan oleh konfigurasi pelayan salah atau sesuatu kelakuan yang tidak dijangka. Sila hubungi penyelia servis anda jika anda merasakan ini adalah satu kesilapan. Cuba sambungkan semula talian untuk terus menyuntung.",
"pad.modals.corruptPad.explanation": "Pad yang anda cuba akses itu telah tercemar.",
"pad.modals.corruptPad.cause": "Ini mungkin disebabkan oleh konfigurasi pelayan salah atau sesuatu kelakuan yang tidak dijangka. Sila hubungi penyelia servis anda.",
"pad.modals.deleted": "Dihapuskan.",
"pad.modals.deleted.explanation": "Pad ini telah dibuang.",
"pad.modals.disconnected": "Sambungan anda telah diputuskan.",
"pad.modals.disconnected.explanation": "Sambungan ke pelayan terputus",
"pad.modals.disconnected.cause": "Pelayan mungkin tidak dapat dicapai. Sila beritahu kami jika masalah ini berterusan.",
"pad.modals.disconnected.cause": "Pelayan mungkin tidak dapat dicapai. Sila beritahu penyelia servis jika masalah ini berterusan.",
"pad.share": "Kongsikan pad ini",
"pad.share.readonly": "Baca sahaja",
"pad.share.link": "Pautan",

86
src/locales/ne.json Normal file
View file

@ -0,0 +1,86 @@
{
"@metadata": {
"authors": [
"सरोज कुमार ढकाल"
]
},
"index.newPad": "नयाँ प्याड",
"index.createOpenPad": "नाम सहितको नयाँ प्याड सिर्जना गर्ने / खोल्ने :",
"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.timeslider.title": "टाइमस्लाइडर",
"pad.toolbar.savedRevision.title": "पुनरावलोकन संग्रहगर्ने",
"pad.toolbar.settings.title": "सेटिङ्गहरू",
"pad.toolbar.embed.title": "यस प्याडलाई बाड्ने या इम्बेड गर्ने",
"pad.toolbar.showusers.title": "यस प्याडमा रहेका प्रयोगकर्ता देखाउने",
"pad.colorpicker.save": "संग्रह गर्ने",
"pad.colorpicker.cancel": "रद्द",
"pad.loading": "लोड हुदैछ...",
"pad.passwordRequired": "यो प्यड खोल्न पासवर्ड चाहिन्छ",
"pad.permissionDenied": "तपाईँलाई यस प्याड खोल्न अनुमति छैन",
"pad.wrongPassword": "तपाईँको पासवर्ड गलत थियो",
"pad.settings.padSettings": "प्याड सेटिङ्गहरू",
"pad.settings.myView": "मेरो दृष्य",
"pad.settings.stickychat": "पर्दामा सधै च्याट गर्ने",
"pad.settings.colorcheck": "लेखकीय रङ्ग",
"pad.settings.linenocheck": "हरफ संख्या",
"pad.settings.rtlcheck": "के सामग्री दाहिने देखि देब्रे पढ्ने हो ?",
"pad.settings.fontType": "फन्ट प्रकार:",
"pad.settings.fontType.normal": "सामान्य",
"pad.settings.fontType.monospaced": "मोनोस्पेस",
"pad.settings.globalView": "विश्वव्यापी दृष्य",
"pad.settings.language": "भाषा:",
"pad.importExport.import_export": "आयात/निर्यात",
"pad.importExport.import": "कुनै पनि पाठ रहेको फाइल या कागजात अपलोड गर्नुहोस्",
"pad.importExport.importSuccessful": "सफल भयो!",
"pad.importExport.export": "निम्न रुपमा प्याड निर्यात गर्ने :",
"pad.importExport.exporthtml": "HTML",
"pad.importExport.exportplain": "साधारण पाठ",
"pad.importExport.exportword": "माइक्रोसफ्ट वर्ड",
"pad.importExport.exportpdf": "पिडिएफ",
"pad.importExport.exportopen": "ओडिएफ(खुल्ला कागजात ढाँचा)",
"pad.importExport.exportdokuwiki": "डकुविकि",
"pad.modals.connected": "जोडीएको।",
"pad.modals.reconnecting": "तपाईँको प्याडमा पुन: जडान गर्दै",
"pad.modals.deleted": "मेटिएको ।",
"pad.modals.deleted.explanation": "यो प्याड हटाइसकेको छ ।",
"pad.modals.disconnected": "तपाईँको जडान अवरुद्ध भयो ।",
"pad.modals.disconnected.explanation": "तपाईँको सर्भरसँगको जडान अवरुद्ध भयो",
"pad.share": "यस प्यडलाई बाड्ने",
"pad.share.readonly": "पढ्ने मात्र",
"pad.share.link": "लिङ्क",
"pad.chat": "कुराकानी",
"pad.chat.title": "यस प्याडको लागि कुराकानी खोल्ने",
"pad.chat.loadmessages": "थप सन्देशहरू खोल्ने",
"timeslider.toolbar.returnbutton": "प्याडमा फर्कनुहोस्",
"timeslider.toolbar.authors": "लेखकहरु:",
"timeslider.toolbar.authorsList": "कुनै पनि लेखकहरू छैनन्",
"timeslider.toolbar.exportlink.title": "निर्यात",
"timeslider.exportCurrent": "हालको संस्करण निम्म रुपमा निर्यात गर्ने :",
"timeslider.version": "संस्करण {{version}}",
"timeslider.saved": "सङ्ग्रह गरिएको {{month}} {{day}}, {{year}}",
"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.savedrevs.marked": "यस संस्करणलाई संग्रहितको रुपमा चिनो लगाइएको छैन",
"pad.userlist.entername": "तपाईँको नाम लेख्नुहोस्",
"pad.userlist.unnamed": "नाम नखुलाइएको",
"pad.userlist.guest": "पाहुना",
"pad.userlist.deny": "अस्वीकार गर्ने",
"pad.userlist.approve": "स्वीकृत गर्ने"
}

View file

@ -1,8 +1,8 @@
{
"@metadata": {
"authors": {
"1": "Unhammer"
}
"authors": [
"Unhammer"
]
},
"index.newPad": "Ny blokk",
"index.createOpenPad": "eller opprett/opna ei blokk med namnet:",

View file

@ -1,11 +1,11 @@
{
"@metadata": {
"authors": {
"0": "Rezonansowy",
"2": "Ty221",
"3": "WTM",
"4": "Woytecr"
}
"authors": [
"Rezonansowy",
"Ty221",
"WTM",
"Woytecr"
]
},
"index.newPad": "Nowy dokument",
"index.createOpenPad": "lub stwórz/otwórz dokument o nazwie:",
@ -15,8 +15,8 @@
"pad.toolbar.strikethrough.title": "Przekreślenie",
"pad.toolbar.ol.title": "Lista uporządkowana",
"pad.toolbar.ul.title": "Lista nieuporządkowana",
"pad.toolbar.indent.title": "Wcięcie",
"pad.toolbar.unindent.title": "Zmniejsz wcięcie",
"pad.toolbar.indent.title": "Wcięcie (TAB)",
"pad.toolbar.unindent.title": "Wcięcie (Shift + TAB)",
"pad.toolbar.undo.title": "Cofnij (Ctrl-Z)",
"pad.toolbar.redo.title": "Ponów (Ctrl-Y)",
"pad.toolbar.clearAuthorship.title": "Usuń kolory autorów",

View file

@ -1,11 +1,11 @@
{
"@metadata": {
"authors": {
"0": "Hamilton Abreu",
"1": "Luckas",
"3": "Tuliouel",
"4": "Waldir"
}
"authors": [
"Hamilton Abreu",
"Luckas",
"Tuliouel",
"Waldir"
]
},
"index.newPad": "Nova Nota",
"index.createOpenPad": "ou crie/abra uma Nota com o nome:",

View file

@ -1,9 +1,9 @@
{
"@metadata": {
"authors": {
"0": "Lokal Profil",
"2": "WikiPhoenix"
}
"authors": [
"Lokal Profil",
"WikiPhoenix"
]
},
"index.newPad": "Nytt block",
"index.createOpenPad": "eller skapa/öppna ett block med namnet:",

View file

@ -1,10 +1,10 @@
{
"@metadata": {
"authors": {
"0": "JVRKPRASAD",
"1": "Malkum",
"3": "Veeven"
}
"authors": [
"JVRKPRASAD",
"Malkum",
"Veeven"
]
},
"index.newPad": "కొత్త పలక",
"index.createOpenPad": "ఒక పేరుతో పలకని సృష్టించండి లేదా అదే పేరుతో ఉన్న పలకని తెరవండి",

View file

@ -3,6 +3,7 @@
"authors": [
"Emperyan",
"Erdemaslancan",
"Joseph",
"Meelo"
]
},
@ -14,8 +15,8 @@
"pad.toolbar.strikethrough.title": "Üstü Çizili",
"pad.toolbar.ol.title": "Sıralı liste",
"pad.toolbar.ul.title": "Sırasız Liste",
"pad.toolbar.indent.title": "Girintiyi arttır",
"pad.toolbar.unindent.title": "Girintiyi azalt",
"pad.toolbar.indent.title": "Girintiyi arttır (TAB)",
"pad.toolbar.unindent.title": "Girintiyi azalt (Shift+TAB)",
"pad.toolbar.undo.title": "Geri Al (Ctrl-Z)",
"pad.toolbar.redo.title": "Yenile (Ctrl-Y)",
"pad.toolbar.clearAuthorship.title": "Yazarlık Renklerini Temizle",

View file

@ -1,12 +1,12 @@
{
"@metadata": {
"authors": {
"0": "Andriykopanytsia",
"1": "Base",
"2": "Olvin",
"4": "Steve.rusyn",
"5": "SteveR"
}
"authors": [
"Andriykopanytsia",
"Base",
"Olvin",
"Steve.rusyn",
"SteveR"
]
},
"index.newPad": "Створити",
"index.createOpenPad": "або створити/відкрити документ з назвою:",

View file

@ -21,8 +21,8 @@
"pad.toolbar.strikethrough.title": "删除线",
"pad.toolbar.ol.title": "有序列表",
"pad.toolbar.ul.title": "无序列表",
"pad.toolbar.indent.title": "增加缩进",
"pad.toolbar.unindent.title": "减少缩进",
"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.clearAuthorship.title": "清除作者颜色",

View file

@ -1,12 +1,12 @@
{
"@metadata": {
"authors": {
"0": "Justincheng12345",
"1": "Liuxinyu970226",
"2": "Shangkuanlc",
"3": "Shirayuki",
"5": "Simon Shek"
}
"authors": [
"Justincheng12345",
"Liuxinyu970226",
"Shangkuanlc",
"Shirayuki",
"Simon Shek"
]
},
"index.newPad": "新Pad",
"index.createOpenPad": "或創建開啟以下名稱的pad",

View file

@ -361,6 +361,8 @@ exports.getHTML = function(padID, rev, callback)
exportHtml.getPadHTML(pad, rev, function(err, html)
{
if(ERR(err, callback)) return;
html = "<!DOCTYPE HTML><html><body>" +html; // adds HTML head
html += "</body></html>";
data = {html: html};
callback(null, data);
});
@ -371,6 +373,8 @@ exports.getHTML = function(padID, rev, callback)
exportHtml.getPadHTML(pad, undefined, function (err, html)
{
if(ERR(err, callback)) return;
html = "<!DOCTYPE HTML><html><body>" +html; // adds HTML head
html += "</body></html>";
data = {html: html};
callback(null, data);
});
@ -378,15 +382,30 @@ exports.getHTML = function(padID, rev, callback)
});
}
/**
setHTML(padID, html) sets the text of a pad based on HTML
Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.setHTML = function(padID, html, callback)
{
//html is required
if(typeof html != "string")
{
callback(new customError("html is no string","apierror"));
return;
}
//get the pad
getPadSafe(padID, true, function(err, pad)
{
if(ERR(err, callback)) return;
// add a new changeset with the new html to the pad
importHtml.setPadHTML(pad, cleanText(html));
importHtml.setPadHTML(pad, cleanText(html), callback);
//update the clients on the pad
padMessageHandler.updatePadClients(pad, callback);

View file

@ -203,7 +203,11 @@ Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(targe
{
curRev++;
var cs = changesets[curRev];
atext = Changeset.applyToAText(cs, atext, apool);
try{
atext = Changeset.applyToAText(cs, atext, apool);
}catch(e) {
return callback(e)
}
}
callback(null);

View file

@ -1,6 +1,6 @@
/**
* The MessageHandler handles all Messages that comes from Socket.IO and controls the sessions
*/
* The MessageHandler handles all Messages that comes from Socket.IO and controls the sessions
*/
/*
* Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
@ -106,12 +106,12 @@ exports.kickSessionsFromPad = function(padID)
* @param client the client that leaves
*/
exports.handleDisconnect = function(client)
{
{
stats.meter('disconnects').mark();
//save the padname of this session
var session = sessioninfos[client.id];
//if this connection was already etablished with a handshake, send a disconnect message to the others
if(session && session.author)
{
@ -128,7 +128,7 @@ exports.handleDisconnect = function(client)
authorManager.getAuthorColorId(session.author, function(err, color)
{
ERR(err);
//prepare the notification for the other users on the pad, that this user left
var messageToTheOtherUsers = {
"type": "COLLABROOM",
@ -142,14 +142,14 @@ exports.handleDisconnect = function(client)
}
}
};
//Go trough all user that are still on the pad, and send them the USER_LEAVE message
client.broadcast.to(session.padId).json.send(messageToTheOtherUsers);
});
});
}
//Delete the sessioninfos entrys of this session
delete sessioninfos[client.id];
delete sessioninfos[client.id];
}
/**
@ -158,7 +158,7 @@ exports.handleDisconnect = function(client)
* @param message the message from the client
*/
exports.handleMessage = function(client, message)
{
{
if(message == null)
{
return;
@ -174,7 +174,7 @@ exports.handleMessage = function(client, message)
var handleMessageHook = function(callback){
var dropMessage = false;
// Call handleMessage hook. If a plugin returns null, the message will be dropped. Note that for all messages
// Call handleMessage hook. If a plugin returns null, the message will be dropped. Note that for all messages
// handleMessage will be called, even if the client is not authorized
hooks.aCallAll("handleMessage", { client: client, message: message }, function ( err, messages ) {
if(ERR(err, callback)) return;
@ -183,7 +183,7 @@ exports.handleMessage = function(client, message)
dropMessage = true;
}
});
// If no plugins explicitly told us to drop the message, its ok to proceed
if(!dropMessage){ callback() };
});
@ -259,7 +259,7 @@ exports.handleMessage = function(client, message)
var checkAccessCallback = function(err, statusObject)
{
if(ERR(err, callback)) return;
//access was granted
if(statusObject.accessStatus == "grant")
{
@ -297,17 +297,17 @@ exports.handleMessage = function(client, message)
function handleSaveRevisionMessage(client, message){
var padId = sessioninfos[client.id].padId;
var userId = sessioninfos[client.id].author;
padManager.getPad(padId, function(err, pad)
{
if(ERR(err)) return;
pad.addSavedRevision(pad.head, userId);
});
}
/**
* Handles a custom message, different to the function below as it handles objects not strings and you can
* Handles a custom message, different to the function below as it handles objects not strings and you can
* direct the message to specific sessionID
*
* @param msg {Object} the message we're sending
@ -356,10 +356,10 @@ function handleChatMessage(client, message)
var userId = sessioninfos[client.id].author;
var text = message.data.text;
var padId = sessioninfos[client.id].padId;
var pad;
var userName;
async.series([
//get the pad
function(callback)
@ -385,7 +385,7 @@ function handleChatMessage(client, message)
{
//save the chat message
pad.appendChatMessage(text, userId, time);
var msg = {
type: "COLLABROOM",
data: {
@ -396,10 +396,10 @@ function handleChatMessage(client, message)
text: text
}
};
//broadcast the chat message to everyone on the pad
socketio.sockets.in(padId).json.send(msg);
callback();
}
], function(err)
@ -425,20 +425,20 @@ function handleGetChatMessages(client, message)
messageLogger.warn("Dropped message, GetChatMessages Message has no start!");
return;
}
var start = message.data.start;
var end = message.data.end;
var count = start - count;
if(count < 0 && count > 100)
{
messageLogger.warn("Dropped message, GetChatMessages Message, client requested invalid amout of messages!");
return;
}
var padId = sessioninfos[client.id].padId;
var pad;
async.series([
//get the pad
function(callback)
@ -488,10 +488,10 @@ function handleSuggestUserName(client, message)
messageLogger.warn("Dropped message, suggestUserName Message has no unnamedId!");
return;
}
var padId = sessioninfos[client.id].padId,
clients = socketio.sockets.clients(padId);
//search the author and send him this message
for(var i = 0; i < clients.length; i++) {
var session = sessioninfos[clients[i].id];
@ -520,14 +520,14 @@ function handleUserInfoUpdate(client, message)
messageLogger.warn("Dropped message, USERINFO_UPDATE Message has no colorId!");
return;
}
//Find out the author name of this session
var author = sessioninfos[client.id].author;
//Tell the authorManager about the new attributes
authorManager.setAuthorColorId(author, message.data.userInfo.colorId);
authorManager.setAuthorName(author, message.data.userInfo.name);
var padId = sessioninfos[client.id].padId;
var infoMsg = {
@ -545,7 +545,7 @@ function handleUserInfoUpdate(client, message)
}
}
};
//Send the other clients on the pad the update message
client.broadcast.to(padId).json.send(infoMsg);
}
@ -588,7 +588,7 @@ function handleUserChanges(data, cb)
messageLogger.warn("Dropped message, USER_CHANGES Message has no changeset!");
return cb();
}
//get all Vars we need
var baseRev = message.data.baseRev;
var wireApool = (new AttributePool()).fromJsonable(message.data.apool);
@ -596,12 +596,12 @@ function handleUserChanges(data, cb)
// The client might disconnect between our callbacks. We should still
// finish processing the changeset, so keep a reference to the session.
var thisSession = sessioninfos[client.id];
var r, apool, pad;
// Measure time to process edit
var stopWatch = stats.timer('edits').start();
async.series([
//get the pad
function(callback)
@ -617,7 +617,7 @@ function handleUserChanges(data, cb)
function(callback)
{
//ex. _checkChangesetAndPool
try
{
// Verify that the changeset has valid syntax and is in canonical form
@ -644,9 +644,9 @@ function handleUserChanges(data, cb)
if('author' == attr[0] && attr[1] != thisSession.author) throw new Error("Trying to submit changes as another author in changeset "+changeset);
})
}
//ex. adoptChangesetAttribs
//Afaik, it copies the new attributes from the changeset, to the global Attribute Pool
changeset = Changeset.moveOpsToNewPool(changeset, wireApool, pad.pool);
}
@ -657,7 +657,7 @@ function handleUserChanges(data, cb)
stats.meter('failedChangesets').mark();
return callback(new Error("Can't apply USER_CHANGES, because "+e.message));
}
//ex. applyUserChanges
apool = pad.pool;
r = baseRev;
@ -671,7 +671,7 @@ function handleUserChanges(data, cb)
function(callback)
{
r++;
pad.getRevisionChangeset(r, function(err, c)
{
if(ERR(err, callback)) return;
@ -704,16 +704,16 @@ function handleUserChanges(data, cb)
function (callback)
{
var prevText = pad.text();
if (Changeset.oldLen(changeset) != prevText.length)
if (Changeset.oldLen(changeset) != prevText.length)
{
client.json.send({disconnect:"badChangeset"});
stats.meter('failedChangesets').mark();
return callback(new Error("Can't apply USER_CHANGES "+changeset+" with oldLen " + Changeset.oldLen(changeset) + " to document of length " + prevText.length));
}
pad.appendRevision(changeset, thisSession.author);
var correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool);
if (correctionChangeset) {
pad.appendRevision(correctionChangeset);
@ -724,7 +724,7 @@ function handleUserChanges(data, cb)
var nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length-1, 0, "\n");
pad.appendRevision(nlChangeset);
}
exports.updatePadClients(pad, function(er) {
ERR(er)
});
@ -739,16 +739,16 @@ function handleUserChanges(data, cb)
}
exports.updatePadClients = function(pad, callback)
{
{
//skip this step if noone is on this pad
var roomClients = socketio.sockets.clients(pad.id);
if(roomClients.length==0)
return callback();
// since all clients usually get the same set of changesets, store them in local cache
// to remove unnecessary roundtrip to the datalayer
// TODO: in REAL world, if we're working without datalayer cache, all requests to revisions will be fired
// BEFORE first result will be landed to our cache object. The solution is to replace parallel processing
// BEFORE first result will be landed to our cache object. The solution is to replace parallel processing
// via async.forEach with sequential for() loop. There is no real benefits of running this in parallel,
// but benefit of reusing cached revision object is HUGE
var revCache = {};
@ -763,7 +763,7 @@ exports.updatePadClients = function(pad, callback)
async.whilst(
function (){ return sessioninfos[sid] && sessioninfos[sid].rev < pad.getHeadRevisionNumber()},
function(callback)
{
{
var r = sessioninfos[sid].rev + 1;
async.waterfall([
@ -772,7 +772,7 @@ exports.updatePadClients = function(pad, callback)
callback(null, revCache[r]);
else
pad.getRevision(r, callback);
},
},
function(revision, callback)
{
revCache[r] = revision;
@ -800,8 +800,8 @@ exports.updatePadClients = function(pad, callback)
author: author,
currentTime: currentTime,
timeDelta: currentTime - sessioninfos[sid].time
}};
}};
client.json.send(wireMsg);
}
@ -814,7 +814,7 @@ exports.updatePadClients = function(pad, callback)
},
callback
);
},callback);
},callback);
}
/**
@ -830,11 +830,11 @@ function _correctMarkersInPad(atext, apool) {
var offset = 0;
while (iter.hasNext()) {
var op = iter.next();
var hasMarker = _.find(AttributeManager.lineAttributes, function(attribute){
return Changeset.opAttributeValue(op, attribute, apool);
}) !== undefined;
if (hasMarker) {
for(var i=0;i<op.chars;i++) {
if (offset > 0 && text.charAt(offset-1) != '\n') {
@ -864,7 +864,7 @@ function _correctMarkersInPad(atext, apool) {
}
/**
* Handles a CLIENT_READY. A CLIENT_READY is the first message from the client to the server. The Client sends his token
* Handles a CLIENT_READY. A CLIENT_READY is the first message from the client to the server. The Client sends his token
* and the pad it wants to enter. The Server answers with the inital values (clientVars) of the pad
* @param client the client that send this message
* @param message the message from the client
@ -922,7 +922,7 @@ function handleClientReady(client, message)
securityManager.checkAccess (padIds.padId, message.sessionID, message.token, message.password, function(err, statusObject)
{
if(ERR(err, callback)) return;
//access was granted
if(statusObject.accessStatus == "grant")
{
@ -935,7 +935,7 @@ function handleClientReady(client, message)
client.json.send({accessStatus: statusObject.accessStatus})
}
});
},
},
//get all authordata of this new user, and load the pad-object from the database
function(callback)
{
@ -967,7 +967,7 @@ function handleClientReady(client, message)
function(callback)
{
var authors = pad.getAllAuthors();
async.parallel([
//get timestamp of latest revission needed for timeslider
function(callback)
@ -993,7 +993,7 @@ function handleClientReady(client, message)
}, callback);
}
], callback);
},
//glue the clientVars together, send them and tell the other clients that a new one is there
function(callback)
@ -1013,12 +1013,12 @@ function handleClientReady(client, message)
roomClients[i].json.send({disconnect:"userdup"});
}
}
//Save in sessioninfos that this session belonges to this pad
sessioninfos[client.id].padId = padIds.padId;
sessioninfos[client.id].readOnlyPadId = padIds.readOnlyPadId;
sessioninfos[client.id].readonly = padIds.readonly;
//Log creation/(re-)entering of a pad
client.get('remoteAddress', function(er, ip) {
//Anonymize the IP address if IP logging is disabled
@ -1056,7 +1056,7 @@ function handleClientReady(client, message)
client.json.send({disconnect:"corruptPad"});// pull the breaks
return callback();
}
// Warning: never ever send padIds.padId to the client. If the
// client is read only you would open a security hole 1 swedish
// mile wide...
@ -1085,7 +1085,7 @@ function handleClientReady(client, message)
"padId": message.padId,
"initialTitle": "Pad: " + message.padId,
"opts": {},
// tell the client the number of the latest chat-message, which will be
// tell the client the number of the latest chat-message, which will be
// used to request the latest 100 chat-messages later (GET_CHAT_MESSAGES)
"chatHead": pad.chatHead,
"numConnectedUsers": roomClients.length,
@ -1093,7 +1093,7 @@ function handleClientReady(client, message)
"readonly": padIds.readonly,
"serverTimestamp": new Date().getTime(),
"userId": author,
"abiwordAvailable": settings.abiwordAvailable(),
"abiwordAvailable": settings.abiwordAvailable(),
"plugins": {
"plugins": plugins.plugins,
"parts": plugins.parts,
@ -1106,18 +1106,18 @@ function handleClientReady(client, message)
{
clientVars.userName = authorName;
}
//call the clientVars-hook so plugins can modify them before they get sent to the client
hooks.aCallAll("clientVars", { clientVars: clientVars, pad: pad }, function ( err, messages ) {
if(ERR(err, callback)) return;
_.each(messages, function(newVars) {
//combine our old object with the new attributes from the hook
for(var attr in newVars) {
clientVars[attr] = newVars[attr];
}
});
//Join the pad and start receiving updates
client.join(padIds.padId);
//Send the clientVars to the Client
@ -1126,9 +1126,9 @@ function handleClientReady(client, message)
sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
});
}
sessioninfos[client.id].author = author;
//prepare the notification for the other users on the pad, that this user joined
var messageToTheOtherUsers = {
"type": "COLLABROOM",
@ -1142,7 +1142,7 @@ function handleClientReady(client, message)
}
}
};
//Add the authorname of this new User, if avaiable
if(authorName != null)
{
@ -1151,7 +1151,7 @@ function handleClientReady(client, message)
// notify all existing users about new user
client.broadcast.to(padIds.padId).json.send(messageToTheOtherUsers);
//Run trough all sessions of this pad
async.forEach(socketio.sockets.clients(padIds.padId), function(roomClient, callback)
{
@ -1160,9 +1160,9 @@ function handleClientReady(client, message)
//Jump over, if this session is the connection session
if(roomClient.id == client.id)
return callback();
//Since sessioninfos might change while being enumerated, check if the
//Since sessioninfos might change while being enumerated, check if the
//sessionID is still assigned to a valid session
if(sessioninfos[roomClient.id] !== undefined)
author = sessioninfos[roomClient.id].author;
@ -1178,7 +1178,7 @@ function handleClientReady(client, message)
callback(null, historicalAuthorData[author]);
else
authorManager.getAuthor(author, callback);
},
},
function (authorInfo, callback)
{
//Send the new User a Notification about this other user
@ -1207,7 +1207,7 @@ function handleClientReady(client, message)
}
/**
* Handles a request for a rough changeset, the timeslider client needs it
* Handles a request for a rough changeset, the timeslider client needs it
*/
function handleChangesetRequest(client, message)
{
@ -1237,7 +1237,7 @@ function handleChangesetRequest(client, message)
messageLogger.warn("Dropped message, changeset request has no requestID!");
return;
}
var granularity = message.data.granularity;
var start = message.data.start;
var end = start + (100 * granularity);
@ -1255,7 +1255,7 @@ function handleChangesetRequest(client, message)
//build the requested rough changesets and send them back
getChangesetInfo(padIds.padId, start, end, granularity, function(err, changesetInfo)
{
ERR(err);
if(err) return console.error('Error while handling a changeset request for '+padIds.padId, err, message.data);
var data = changesetInfo;
data.requestID = message.data.requestID;
@ -1281,47 +1281,49 @@ function getChangesetInfo(padId, startNum, endNum, granularity, callback)
var composedChangesets = {};
var revisionDate = [];
var lines;
var head_revision = 0;
async.series([
//get the pad from the database
function(callback)
{
padManager.getPad(padId, function(err, _pad)
{
{
if(ERR(err, callback)) return;
pad = _pad;
head_revision = pad.getHeadRevisionNumber();
callback();
});
},
function(callback)
{
{
//calculate the last full endnum
var lastRev = pad.getHeadRevisionNumber();
if (endNum > lastRev+1) {
endNum = lastRev+1;
}
endNum = Math.floor(endNum / granularity)*granularity;
var compositesChangesetNeeded = [];
var revTimesNeeded = [];
//figure out which composite Changeset and revTimes we need, to load them in bulk
var compositeStart = startNum;
while (compositeStart < endNum)
while (compositeStart < endNum)
{
var compositeEnd = compositeStart + granularity;
//add the composite Changeset we needed
compositesChangesetNeeded.push({start: compositeStart, end: compositeEnd});
//add the t1 time we need
revTimesNeeded.push(compositeStart == 0 ? 0 : compositeStart - 1);
//add the t2 time we need
revTimesNeeded.push(compositeEnd - 1);
compositeStart += granularity;
}
//get all needed db values parallel
async.parallel([
function(callback)
@ -1358,58 +1360,57 @@ function getChangesetInfo(padId, startNum, endNum, granularity, callback)
if(ERR(err, callback)) return;
lines = _lines;
callback();
});
});
}
], callback);
},
//doesn't know what happens here excatly :/
function(callback)
{
{
var compositeStart = startNum;
while (compositeStart < endNum)
while (compositeStart < endNum)
{
if (compositeStart + granularity > endNum)
var compositeEnd = compositeStart + granularity;
if (compositeEnd > endNum || compositeEnd > head_revision)
{
break;
}
var compositeEnd = compositeStart + granularity;
var forwards = composedChangesets[compositeStart + "/" + compositeEnd];
var backwards = Changeset.inverse(forwards, lines.textlines, lines.alines, pad.apool());
Changeset.mutateAttributionLines(forwards, lines.alines, pad.apool());
Changeset.mutateTextLines(forwards, lines.textlines);
var forwards2 = Changeset.moveOpsToNewPool(forwards, pad.apool(), apool);
var backwards2 = Changeset.moveOpsToNewPool(backwards, pad.apool(), apool);
var t1, t2;
if (compositeStart == 0)
if (compositeStart == 0)
{
t1 = revisionDate[0];
}
else
else
{
t1 = revisionDate[compositeStart - 1];
}
t2 = revisionDate[compositeEnd - 1];
timeDeltas.push(t2 - t1);
forwardsChangesets.push(forwards2);
backwardsChangesets.push(backwards2);
compositeStart += granularity;
}
callback();
}
], function(err)
{
if(ERR(err, callback)) return;
callback(null, {forwardsChangesets: forwardsChangesets,
backwardsChangesets: backwardsChangesets,
apool: apool.toJsonable(),
@ -1424,7 +1425,7 @@ function getChangesetInfo(padId, startNum, endNum, granularity, callback)
* Tries to rebuild the getPadLines function of the original Etherpad
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L263
*/
function getPadLines(padId, revNum, callback)
function getPadLines(padId, revNum, callback)
{
var atext;
var result = {};
@ -1435,7 +1436,7 @@ function getPadLines(padId, revNum, callback)
function(callback)
{
padManager.getPad(padId, function(err, _pad)
{
{
if(ERR(err, callback)) return;
pad = _pad;
callback();
@ -1479,7 +1480,7 @@ function getPadLines(padId, revNum, callback)
function composePadChangesets(padId, startNum, endNum, callback)
{
var pad;
var changesets = [];
var changesets = {};
var changeset;
async.series([
@ -1497,14 +1498,19 @@ function composePadChangesets(padId, startNum, endNum, callback)
function(callback)
{
var changesetsNeeded=[];
//create a array for all changesets, we will
var headNum = pad.getHeadRevisionNumber();
if (endNum > headNum)
endNum = headNum;
if (startNum < 0)
startNum = 0;
//create a array for all changesets, we will
//replace the values with the changeset later
for(var r=startNum;r<endNum;r++)
{
changesetsNeeded.push(r);
}
//get all changesets
async.forEach(changesetsNeeded, function(revNum,callback)
{
@ -1521,13 +1527,13 @@ function composePadChangesets(padId, startNum, endNum, callback)
{
changeset = changesets[startNum];
var pool = pad.apool();
for(var r=startNum+1;r<endNum;r++)
{
var cs = changesets[r];
changeset = Changeset.compose(changeset, cs, pool);
}
callback(null);
}
],

View file

@ -25,11 +25,6 @@ function setPadHTML(pad, html, callback)
{
var apiLogger = log4js.getLogger("ImportHtml");
// Clean the pad. This makes the rest of the code easier
// by several orders of magnitude.
pad.setText("");
var padText = pad.text();
// Parse the incoming HTML with jsdom
try{
var doc = jsdom(html.replace(/>\n+</g, '><'));
@ -44,8 +39,15 @@ function setPadHTML(pad, html, callback)
// Convert a dom tree into a list of lines and attribute liens
// using the content collector object
var cc = contentcollector.makeContentCollector(true, null, pad.pool);
cc.collectContent(doc.childNodes[0]);
try{ // we use a try here because if the HTML is bad it will blow up
cc.collectContent(doc.childNodes[0]);
}catch(e){
apiLogger.warn("HTML was not properly formed", e);
return; // We don't process the HTML because it was bad..
}
var result = cc.finish();
apiLogger.debug('Lines:');
var i;
for (i = 0; i < result.lines.length; i += 1)
@ -90,6 +92,7 @@ function setPadHTML(pad, html, callback)
// the changeset is ready!
var theChangeset = builder.toString();
apiLogger.debug('The changeset: ' + theChangeset);
pad.setText("");
pad.appendRevision(theChangeset);
}