commit
89ad3cb809
|
@ -1,3 +1,11 @@
|
|||
# 1.6.3
|
||||
* SECURITY: Update ejs
|
||||
* SECURITY: xss vulnerability when reading window.location.href
|
||||
* SECURITY: sanitize jsonp
|
||||
* NEW: Catch SIGTERM for graceful shutdown
|
||||
* NEW: Show actual applied text formatting for caret position
|
||||
* NEW: Add settings to improve scrolling of viewport on line changes
|
||||
|
||||
# 1.6.2
|
||||
* NEW: Added pad shortcut disabling feature
|
||||
* NEW: Create option to automatically reconnect after a few seconds
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"node": ">=0.6.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"marked": "~0.1.9"
|
||||
"marked": ">=0.3.6"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"optionalDependencies": {},
|
||||
|
|
|
@ -150,6 +150,34 @@
|
|||
/* Time (in seconds) to automatically reconnect pad when a "Force reconnect"
|
||||
message is shown to user. Set to 0 to disable automatic reconnection */
|
||||
"automaticReconnectionTimeout" : 0,
|
||||
/*
|
||||
* By default, when caret is moved out of viewport, it scrolls the minimum height needed to make this
|
||||
* line visible.
|
||||
*/
|
||||
"scrollWhenFocusLineIsOutOfViewport": {
|
||||
/*
|
||||
* Percentage of viewport height to be additionally scrolled.
|
||||
* E.g use "percentage.editionAboveViewport": 0.5, to place caret line in the
|
||||
* middle of viewport, when user edits a line above of the viewport
|
||||
* Set to 0 to disable extra scrolling
|
||||
*/
|
||||
"percentage": {
|
||||
"editionAboveViewport": 0,
|
||||
"editionBelowViewport": 0
|
||||
},
|
||||
/* Time (in milliseconds) used to animate the scroll transition. Set to 0 to disable animation */
|
||||
"duration": 0,
|
||||
/*
|
||||
* Flag to control if it should scroll when user places the caret in the last line of the viewport
|
||||
*/
|
||||
"scrollWhenCaretIsInTheLastLineOfViewport": false,
|
||||
/*
|
||||
* Percentage of viewport height to be additionally scrolled when user presses arrow up
|
||||
* in the line of the top of the viewport.
|
||||
* Set to 0 to let the scroll to be handled as default by the Etherpad
|
||||
*/
|
||||
"percentageToScrollWhenUserPressesArrowUp": 0
|
||||
},
|
||||
|
||||
/* Users for basic authentication. is_admin = true gives access to /admin.
|
||||
If you do not uncomment this, /admin will not be available! */
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"Mushviq Abdulla",
|
||||
"Wertuose",
|
||||
"Mastizada",
|
||||
"Archaeodontosaurus"
|
||||
"Archaeodontosaurus",
|
||||
"Neriman2003"
|
||||
]
|
||||
},
|
||||
"index.newPad": "Yeni lövhə",
|
||||
|
@ -61,6 +62,8 @@
|
|||
"pad.modals.connected": "Bağlandı.",
|
||||
"pad.modals.reconnecting": "Sizin lövhə yenidən qoşulur..",
|
||||
"pad.modals.forcereconnect": "Məcbur təkrarən bağlan",
|
||||
"pad.modals.reconnecttimer": "Yenidən qoşulur",
|
||||
"pad.modals.cancel": "Ləğv et",
|
||||
"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əni istifadə etmək üçün yenidən qoşul.",
|
||||
|
|
|
@ -57,10 +57,11 @@
|
|||
"pad.importExport.exportword": "Microsoft Word",
|
||||
"pad.importExport.exportpdf": "PDF",
|
||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "Teyna duz metini yana html formati şıma şenê azete dê. Dehana vêşi xısusiyetanê azere kerdışi rê grey <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">AbiWord'i bar kerên</a>.",
|
||||
"pad.importExport.abiword.innerHTML": "Şıma şenê tenya metınanê zelalan ya zi formatanê HTML-i biyarê. Seba vêşi xısusiyetanê arezekerdışi ra gırey <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">AbiWord-i bar kerên</a>.",
|
||||
"pad.modals.connected": "Gırediya.",
|
||||
"pad.modals.reconnecting": "Bloknot da şıma rê fına irtibat kewê no",
|
||||
"pad.modals.forcereconnect": "Mecbur anciya gırê de",
|
||||
"pad.modals.reconnecttimer": "Anciya gırê beno",
|
||||
"pad.modals.cancel": "Bıtexelne",
|
||||
"pad.modals.userdup": "Zewbina pençere de bi a",
|
||||
"pad.modals.userdup.explanation": "Ena bloknot ena komputer de yew ra zeder penceran dı akerde asena",
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
"pad.colorpicker.save": "Guardar",
|
||||
"pad.colorpicker.cancel": "Cancelar",
|
||||
"pad.loading": "Cargando...",
|
||||
"pad.noCookie": "La cookie no se pudo encontrar. ¡Habilita las cookies en tu navegador!",
|
||||
"pad.noCookie": "No se pudo encontrar la «cookie». Permite la utilización de «cookies» en el navegador.",
|
||||
"pad.passwordRequired": "Necesitas una contraseña para acceder a este pad",
|
||||
"pad.permissionDenied": "No tienes permiso para acceder a este pad",
|
||||
"pad.wrongPassword": "La contraseña era incorrecta",
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
"Macofe",
|
||||
"MrTapsa",
|
||||
"Silvonen",
|
||||
"Espeox"
|
||||
"Espeox",
|
||||
"Pyscowicz"
|
||||
]
|
||||
},
|
||||
"index.newPad": "Uusi muistio",
|
||||
|
@ -68,6 +69,8 @@
|
|||
"pad.modals.connected": "Yhdistetty.",
|
||||
"pad.modals.reconnecting": "Muodostetaan yhteyttä muistioon uudelleen...",
|
||||
"pad.modals.forcereconnect": "Pakota yhdistämään uudelleen",
|
||||
"pad.modals.reconnecttimer": "Yritetään yhdistää uudelleen",
|
||||
"pad.modals.cancel": "Peruuta",
|
||||
"pad.modals.userdup": "Avattu toisessa ikkunassa",
|
||||
"pad.modals.userdup.explanation": "Tämä muistio vaikuttaa olevan avoinna useammassa eri selainikkunassa tällä koneella.",
|
||||
"pad.modals.userdup.advice": "Yhdistä uudelleen, jos haluat käyttää tätä ikkunaa.",
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Robin van der Vliet"
|
||||
]
|
||||
},
|
||||
"pad.toolbar.bold.title": "Fet (Ctrl+B)",
|
||||
"pad.toolbar.italic.title": "Kursyf (Ctrl+I)",
|
||||
"pad.toolbar.underline.title": "Understreekje (Ctrl+U)",
|
||||
"pad.toolbar.settings.title": "Ynstellingen",
|
||||
"pad.colorpicker.save": "Bewarje",
|
||||
"pad.colorpicker.cancel": "Annulearje",
|
||||
"pad.settings.fontType.normal": "Normaal",
|
||||
"pad.settings.fontType.monospaced": "Monospace",
|
||||
"pad.settings.language": "Taal:",
|
||||
"pad.importExport.exporthtml": "HTML",
|
||||
"pad.importExport.exportword": "Microsoft Word",
|
||||
"pad.importExport.exportpdf": "PDF",
|
||||
"pad.modals.connected": "Ferbûn.",
|
||||
"pad.modals.deleted": "Fuortsmiten.",
|
||||
"pad.share.link": "Keppeling",
|
||||
"timeslider.toolbar.authors": "Auteurs:",
|
||||
"timeslider.toolbar.authorsList": "Gjin auteurs",
|
||||
"timeslider.toolbar.exportlink.title": "Eksportearje",
|
||||
"timeslider.version": "Ferzje {{version}}",
|
||||
"timeslider.dateformat": "{{day}}-{{month}}-{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
||||
"timeslider.month.january": "jannewaris",
|
||||
"timeslider.month.february": "febrewaris",
|
||||
"timeslider.month.march": "maart",
|
||||
"timeslider.month.april": "april",
|
||||
"timeslider.month.may": "maaie",
|
||||
"timeslider.month.june": "juny",
|
||||
"timeslider.month.july": "july",
|
||||
"timeslider.month.august": "augustus",
|
||||
"timeslider.month.september": "septimber",
|
||||
"timeslider.month.october": "oktober",
|
||||
"timeslider.month.november": "novimber",
|
||||
"timeslider.month.december": "desimber",
|
||||
"pad.userlist.unnamed": "sûnder namme",
|
||||
"pad.userlist.guest": "Gast",
|
||||
"pad.userlist.deny": "Wegerje",
|
||||
"pad.userlist.approve": "Goedkarre"
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Sfic"
|
||||
]
|
||||
},
|
||||
"pad.toolbar.bold.title": "गहरा (Ctrl+B)",
|
||||
"pad.toolbar.italic.title": "तिरछा (Ctrl+I)",
|
||||
"pad.toolbar.strikethrough.title": "काटें (Ctrl+5)",
|
||||
"pad.colorpicker.save": "सहेजें",
|
||||
"pad.colorpicker.cancel": "रद्द करें",
|
||||
"pad.loading": "लोड हो रहा है...",
|
||||
"pad.settings.language": "भाषा:",
|
||||
"pad.importExport.import_export": "आयात/निर्यात",
|
||||
"pad.importExport.exportpdf": "पीडीएफ़",
|
||||
"pad.modals.cancel": "रद्द करें",
|
||||
"timeslider.toolbar.authors": "लेखक:",
|
||||
"timeslider.toolbar.exportlink.title": "निर्यात",
|
||||
"timeslider.version": "संस्करण {{version}}",
|
||||
"timeslider.saved": "{{day}} {{month}} {{year}} सहेजा गया",
|
||||
"timeslider.dateformat": "{{day}}/{{month}}/{{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": "दिसम्बर",
|
||||
"pad.userlist.guest": "अतिथि",
|
||||
"pad.impexp.importbutton": "अभी आयात करें",
|
||||
"pad.impexp.importing": "आयात कर रहा...",
|
||||
"pad.impexp.importfailed": "आयात विफल हुआ",
|
||||
"pad.impexp.copypaste": "कृपया कॉपी पेस्ट करें"
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
"authors": [
|
||||
"Shirayuki",
|
||||
"Torinky",
|
||||
"Omotecho"
|
||||
"Omotecho",
|
||||
"Aefgh39622"
|
||||
]
|
||||
},
|
||||
"index.newPad": "新規作成",
|
||||
|
@ -54,7 +55,7 @@
|
|||
"pad.importExport.exportword": "Microsoft Word",
|
||||
"pad.importExport.exportpdf": "PDF",
|
||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "プレーンテキストまたは HTML ファイルからのみインポートできます。より高度なインポート機能を使用するには、<a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">abiword をインストール</a>してください。",
|
||||
"pad.importExport.abiword.innerHTML": "プレーンテキストまたは HTML ファイルからのみインポートできます。より高度なインポート機能を使用するには、<a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">AbiWord をインストール</a>してください。",
|
||||
"pad.modals.connected": "接続されました。",
|
||||
"pad.modals.reconnecting": "パッドに再接続中...",
|
||||
"pad.modals.forcereconnect": "強制的に再接続",
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Ernác"
|
||||
]
|
||||
},
|
||||
"pad.toolbar.settings.title": "Джарашдырыула",
|
||||
"pad.colorpicker.save": "Сакъла",
|
||||
"pad.loading": "Джюклениу...",
|
||||
"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": "Microsoft Word",
|
||||
"pad.importExport.exportpdf": "PDF",
|
||||
"pad.importExport.exportopen": "ODF (OpenOffice'ни документи)",
|
||||
"pad.chat": "Чат",
|
||||
"timeslider.toolbar.returnbutton": "Документге",
|
||||
"timeslider.toolbar.authors": "Авторла:",
|
||||
"timeslider.toolbar.exportlink.title": "Эспорт эт",
|
||||
"timeslider.version": "{{version}} версия",
|
||||
"timeslider.dateformat": "{{day}}/{{month}}/{{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": "декабрь",
|
||||
"pad.userlist.guest": "Къонакъ",
|
||||
"pad.impexp.importing": "Импорт этиу…"
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"StefanusRA"
|
||||
"StefanusRA",
|
||||
"Empu"
|
||||
]
|
||||
},
|
||||
"index.newPad": "Pad Anyar",
|
||||
|
@ -30,7 +31,7 @@
|
|||
"pad.permissionDenied": "Rika ora duwe idin kanggo ngakses pad kiye",
|
||||
"pad.wrongPassword": "Tembung sandhine Rika salah",
|
||||
"pad.settings.padSettings": "Pangaturan Pad",
|
||||
"pad.settings.myView": "Delengane Inyong",
|
||||
"pad.settings.myView": "Delengané Inyong",
|
||||
"pad.settings.stickychat": "Dopokan mesti nang layar",
|
||||
"pad.settings.colorcheck": "Authorship colors",
|
||||
"pad.settings.linenocheck": "Nomer baris",
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Akapochtli",
|
||||
"Taresi"
|
||||
]
|
||||
},
|
||||
"index.newPad": "Yancuic Pad",
|
||||
"index.createOpenPad": "auh xicchīhua/xictlapo cē Pad in ītōcā:",
|
||||
"pad.toolbar.bold.title": "Tilāhuac (Ctrl+B)",
|
||||
"pad.toolbar.italic.title": "Coltic (Ctrl+I)",
|
||||
"pad.toolbar.underline.title": "Tlahuahuantli (Ctrl+U)",
|
||||
"pad.toolbar.strikethrough.title": "Tlīlhuahuantli (Ctrl+5)",
|
||||
"pad.toolbar.undo.title": "Xicmācuepa (Ctrl+Z)",
|
||||
"pad.toolbar.redo.title": "Occeppa (Ctrl+Y)",
|
||||
"pad.toolbar.settings.title": "Tlatlālīliztli",
|
||||
"pad.colorpicker.save": "Xicpiya",
|
||||
"pad.colorpicker.cancel": "Xiccāhua",
|
||||
"pad.settings.padSettings": "Pad Ītlatlālīliz",
|
||||
"pad.settings.myView": "Notlachiyaliz",
|
||||
"pad.settings.language": "Tlahtōlli:",
|
||||
"pad.importExport.exportetherpad": "Etherpad",
|
||||
"pad.importExport.exporthtml": "HTML",
|
||||
"pad.importExport.exportword": "Microsoft Word",
|
||||
"pad.importExport.exportpdf": "PDF",
|
||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.modals.deleted": "Omopohpoloh.",
|
||||
"pad.modals.deleted.explanation": "Ōmopoloh inīn Pad.",
|
||||
"timeslider.version": "Inīc {{version}} Cuepaliztli",
|
||||
"timeslider.month.january": "Īccēmētztli",
|
||||
"timeslider.month.february": "Īcōmemētztli",
|
||||
"timeslider.month.march": "Īcyēyimētztli",
|
||||
"timeslider.month.april": "Īcnāhuimētztli",
|
||||
"timeslider.month.may": "Īcmācuīllimētztli",
|
||||
"timeslider.month.june": "Īcchicuacemmētztli",
|
||||
"timeslider.month.july": "Īcchicōmemētztli",
|
||||
"timeslider.month.august": "Īcchicuēyimētztli",
|
||||
"timeslider.month.september": "Īcchiucnāhuimētztli",
|
||||
"timeslider.month.october": "Īcmahtlactlimētztli",
|
||||
"timeslider.month.november": "Īcmahtlactlioncēmētztli",
|
||||
"timeslider.month.december": "Īcmahtlactliomōmemētztli"
|
||||
}
|
|
@ -56,7 +56,7 @@
|
|||
"pad.importExport.exportword": "Microsoft Word",
|
||||
"pad.importExport.exportpdf": "PDF",
|
||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "Du kan bare importere fra ren tekst eller HTML-formater. For mer avanserte importfunksjoner, <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">installer abiword</a>.",
|
||||
"pad.importExport.abiword.innerHTML": "Du kan bare importere fra ren tekst eller HTML-formater. For mer avanserte importfunksjoner, <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">installer AbiWord</a>.",
|
||||
"pad.modals.connected": "Tilkoblet.",
|
||||
"pad.modals.reconnecting": "Kobler til din blokk på nytt...",
|
||||
"pad.modals.forcereconnect": "Tving gjenoppkobling",
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
"Imperadeiro98",
|
||||
"Macofe",
|
||||
"Ti4goc",
|
||||
"Cainamarques"
|
||||
"Cainamarques",
|
||||
"Athena in Wonderland"
|
||||
]
|
||||
},
|
||||
"index.newPad": "Nova Nota",
|
||||
|
@ -33,12 +34,14 @@
|
|||
"pad.colorpicker.save": "Gravar",
|
||||
"pad.colorpicker.cancel": "Cancelar",
|
||||
"pad.loading": "A carregar…",
|
||||
"pad.noCookie": "O cookie não foi encontrado. Por favor, ative os cookies no seu navegador!",
|
||||
"pad.passwordRequired": "Precisa de uma senha para aceder a este pad",
|
||||
"pad.permissionDenied": "Não tem permissão para aceder a este pad.",
|
||||
"pad.wrongPassword": "A palavra-chave está errada",
|
||||
"pad.settings.padSettings": "Configurações da Nota",
|
||||
"pad.settings.myView": "Minha vista",
|
||||
"pad.settings.stickychat": "Bate-papo sempre no ecrã",
|
||||
"pad.settings.chatandusers": "Mostrar a conversação e os utilizadores",
|
||||
"pad.settings.colorcheck": "Cores de autoria",
|
||||
"pad.settings.linenocheck": "Números de linha",
|
||||
"pad.settings.rtlcheck": "Ler o conteúdo da direita para a esquerda?",
|
||||
|
@ -51,21 +54,34 @@
|
|||
"pad.importExport.import": "Carregar qualquer ficheiro de texto ou documento",
|
||||
"pad.importExport.importSuccessful": "Bem sucedido!",
|
||||
"pad.importExport.export": "Exportar a Nota atual como:",
|
||||
"pad.importExport.exportetherpad": "Etherpad",
|
||||
"pad.importExport.exporthtml": "HTML",
|
||||
"pad.importExport.exportplain": "Texto simples",
|
||||
"pad.importExport.exportword": "Microsoft Word",
|
||||
"pad.importExport.exportpdf": "PDF",
|
||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "Só é possível importar texto sem formatação ou HTML. Para obter funcionalidades de importação mais avançadas, por favor <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">instale o AbiWord</a>.",
|
||||
"pad.modals.connected": "Ligado.",
|
||||
"pad.modals.reconnecting": "Reconectando-se ao seu bloco…",
|
||||
"pad.modals.forcereconnect": "Forçar reconexão",
|
||||
"pad.modals.reconnecttimer": "A tentar religar",
|
||||
"pad.modals.cancel": "Cancelar",
|
||||
"pad.modals.userdup": "Aberto noutra janela",
|
||||
"pad.modals.userdup.explanation": "Este pad parece estar aberto em mais do que uma janela do navegador neste computador.",
|
||||
"pad.modals.userdup.advice": "Religar para utilizar esta janela.",
|
||||
"pad.modals.unauth": "Não autorizado",
|
||||
"pad.modals.unauth.explanation": "As suas permissões foram alteradas enquanto revia esta página. Tente religar.",
|
||||
"pad.modals.looping.explanation": "Existem problemas de comunicação com o servidor de sincronização.",
|
||||
"pad.modals.looping.cause": "Talvez tenha ligado por um firewall ou proxy incompatível.",
|
||||
"pad.modals.initsocketfail": "O servidor está inacessível.",
|
||||
"pad.modals.initsocketfail.explanation": "Não foi possível a conexão ao servidor de sincronização.",
|
||||
"pad.modals.initsocketfail.cause": "Isto provavelmente ocorreu por um problema no seu navegador ou na sua ligação de Internet.",
|
||||
"pad.modals.slowcommit.explanation": "O servidor não está a responder.",
|
||||
"pad.modals.slowcommit.cause": "Isto pode ser por problemas com a ligação de rede.",
|
||||
"pad.modals.badChangeset.explanation": "Uma edição que fez foi classificada como ilegal pelo servidor de sincronização.",
|
||||
"pad.modals.badChangeset.cause": "Isto pode ocorrer devido a uma configuração errada do servidor ou algum outro comportamento inesperado. Por favor contacte o administrador, se acredita que é um erro. Tente religar para continuar a editar.",
|
||||
"pad.modals.corruptPad.explanation": "A nota que está a tentar aceder está corrompida.",
|
||||
"pad.modals.corruptPad.cause": "Isto pode ocorrer devido a uma configuração errada do servidor ou algum outro comportamento inesperado. Por favor contacte o administrador.",
|
||||
"pad.modals.deleted": "Eliminado.",
|
||||
"pad.modals.deleted.explanation": "Este pad foi removido.",
|
||||
"pad.modals.disconnected": "Você foi desconectado.",
|
||||
|
@ -74,9 +90,11 @@
|
|||
"pad.share": "Compartilhar este pad",
|
||||
"pad.share.readonly": "Somente para leitura",
|
||||
"pad.share.link": "Ligação",
|
||||
"pad.share.emebdcode": "Incorporar o URL",
|
||||
"pad.chat": "Bate-papo",
|
||||
"pad.chat.title": "Abrir o bate-papo para este pad.",
|
||||
"pad.chat.loadmessages": "Carregar mais mensagens",
|
||||
"timeslider.pageTitle": "Linha do tempo de {{appTitle}}",
|
||||
"timeslider.toolbar.returnbutton": "Voltar ao pad",
|
||||
"timeslider.toolbar.authors": "Autores:",
|
||||
"timeslider.toolbar.authorsList": "Sem Autores",
|
||||
|
@ -84,6 +102,9 @@
|
|||
"timeslider.exportCurrent": "Exportar versão atual como:",
|
||||
"timeslider.version": "Versão {{version}}",
|
||||
"timeslider.saved": "Gravado a {{day}} de {{month}} de {{ano}}",
|
||||
"timeslider.playPause": "Reproduzir / Pausar conteúdo do Pad",
|
||||
"timeslider.backRevision": "Voltar a uma revisão anterior neste Pad",
|
||||
"timeslider.forwardRevision": "Ir a uma revisão posterior neste Pad",
|
||||
"timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
||||
"timeslider.month.january": "Janeiro",
|
||||
"timeslider.month.february": "Fevereiro",
|
||||
|
@ -97,7 +118,9 @@
|
|||
"timeslider.month.october": "Outubro",
|
||||
"timeslider.month.november": "Novembro",
|
||||
"timeslider.month.december": "Dezembro",
|
||||
"timeslider.unnamedauthors": "{{num}} {[plural(num) one: autor anónimo, other: autores anónimos ]}",
|
||||
"pad.savedrevs.marked": "Esta revisão está agora marcada como gravada",
|
||||
"pad.savedrevs.timeslider": "Pode consultar as revisões gravadas visitando a linha do tempo",
|
||||
"pad.userlist.entername": "Insira o seu nome",
|
||||
"pad.userlist.unnamed": "sem nome",
|
||||
"pad.userlist.guest": "Convidado",
|
||||
|
@ -107,8 +130,10 @@
|
|||
"pad.impexp.importbutton": "Importar agora",
|
||||
"pad.impexp.importing": "Importando...",
|
||||
"pad.impexp.confirmimport": "A importação de um ficheiro irá substituir o texto atual do pad. Tem certeza que deseja continuar?",
|
||||
"pad.impexp.padHasData": "Não fomos capazes de importar este arquivo porque este Pad já tinha alterações, por favor importe para um novo pad",
|
||||
"pad.impexp.convertFailed": "Não foi possível importar este ficheiro. Utilize outro formato ou copie e insira manualmente",
|
||||
"pad.impexp.padHasData": "Não fomos capazes de importar este ficheiro porque este Pad já tinha alterações, por favor importe para um novo pad",
|
||||
"pad.impexp.uploadFailed": "O upload falhou. Por favor, tente novamente",
|
||||
"pad.impexp.importfailed": "A importação falhou",
|
||||
"pad.impexp.copypaste": "Por favor, copie e cole"
|
||||
"pad.impexp.copypaste": "Por favor, copie e cole",
|
||||
"pad.impexp.exportdisabled": "A exportação no formato {{type}} está desativada. Por favor, contacte o administrador do sistema para mais informações."
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
]
|
||||
},
|
||||
"index.newPad": "Nov dokument",
|
||||
"index.createOpenPad": "ali pa odpri dokument z imenom:",
|
||||
"index.createOpenPad": "ali pa ustvari/odpri dokument z imenom:",
|
||||
"pad.toolbar.bold.title": "Krepko (Ctrl-B)",
|
||||
"pad.toolbar.italic.title": "Ležeče (Ctrl-I)",
|
||||
"pad.toolbar.underline.title": "Podčrtano (Ctrl-U)",
|
||||
|
@ -20,7 +20,7 @@
|
|||
"pad.toolbar.undo.title": "Razveljavi (Ctrl-Z)",
|
||||
"pad.toolbar.redo.title": "Ponovno uveljavi (Ctrl-Y)",
|
||||
"pad.toolbar.clearAuthorship.title": "Počisti barve avtorstva (Ctrl+Shift+C)",
|
||||
"pad.toolbar.import_export.title": "Izvozi/Uvozi različne oblike zapisov",
|
||||
"pad.toolbar.import_export.title": "Uvozi/Izvozi različne oblike zapisov",
|
||||
"pad.toolbar.timeslider.title": "Časovni trak",
|
||||
"pad.toolbar.savedRevision.title": "Shrani redakcijo",
|
||||
"pad.toolbar.settings.title": "Nastavitve",
|
||||
|
|
|
@ -5,21 +5,22 @@
|
|||
"Milicevic01",
|
||||
"Милан Јелисавчић",
|
||||
"Srdjan m",
|
||||
"Obsuser"
|
||||
"Obsuser",
|
||||
"Acamicamacaraca"
|
||||
]
|
||||
},
|
||||
"index.newPad": "Нови Пад",
|
||||
"index.createOpenPad": "или направите/отворите пад следећег назива:",
|
||||
"pad.toolbar.bold.title": "Подебљано (Ctrl-B)",
|
||||
"pad.toolbar.italic.title": "Искошено (Ctrl-I)",
|
||||
"pad.toolbar.underline.title": "Подвучено (Ctrl-U)",
|
||||
"pad.toolbar.bold.title": "Подебљано (Ctrl+B)",
|
||||
"pad.toolbar.italic.title": "Искошено (Ctrl+I)",
|
||||
"pad.toolbar.underline.title": "Подвучено (Ctrl+U)",
|
||||
"pad.toolbar.strikethrough.title": "Прецртано (Ctrl+5)",
|
||||
"pad.toolbar.ol.title": "Уређен списак (Ctrl+Shift+N)",
|
||||
"pad.toolbar.ul.title": "Неуређен списак (Ctrl+Shift+L)",
|
||||
"pad.toolbar.indent.title": "Увлачење (TAB)",
|
||||
"pad.toolbar.unindent.title": "Извлачење (Shift+TAB)",
|
||||
"pad.toolbar.undo.title": "Опозови (Ctrl+Z)",
|
||||
"pad.toolbar.redo.title": "Опозови (Ctrl+Z)",
|
||||
"pad.toolbar.redo.title": "Понови (Ctrl+Z)",
|
||||
"pad.toolbar.clearAuthorship.title": "Очисти ауторске боје (Ctrl+Shift+C)",
|
||||
"pad.toolbar.import_export.title": "Увези/извези из/на друге датотечне формате",
|
||||
"pad.toolbar.timeslider.title": "Временска линија",
|
||||
|
@ -29,9 +30,9 @@
|
|||
"pad.toolbar.showusers.title": "Прикажи кориснике на овом паду",
|
||||
"pad.colorpicker.save": "Сачувај",
|
||||
"pad.colorpicker.cancel": "Откажи",
|
||||
"pad.loading": "Учитавање...",
|
||||
"pad.loading": "Учитавам…",
|
||||
"pad.noCookie": "Колачић није пронађен. Молимо да укључите колачиће у вашем прегледавачу!",
|
||||
"pad.passwordRequired": "Требате лозинку како бисте приступили овом паду",
|
||||
"pad.passwordRequired": "Требате имати лозинку како бисте приступили овом паду",
|
||||
"pad.permissionDenied": "Немате дозволу да приступите овом паду",
|
||||
"pad.wrongPassword": "Ваша лозинка није исправна",
|
||||
"pad.settings.padSettings": "Подешавања пада",
|
||||
|
@ -48,15 +49,15 @@
|
|||
"pad.settings.language": "Језик:",
|
||||
"pad.importExport.import_export": "Увоз/извоз",
|
||||
"pad.importExport.import": "Отпремите било коју текстуалну датотеку или документ",
|
||||
"pad.importExport.importSuccessful": "Успело!",
|
||||
"pad.importExport.importSuccessful": "Успешно!",
|
||||
"pad.importExport.export": "Извези тренутни пад као:",
|
||||
"pad.importExport.exportetherpad": "Etherpad",
|
||||
"pad.importExport.exporthtml": "HTML",
|
||||
"pad.importExport.exportplain": "чист текст",
|
||||
"pad.importExport.exportplain": "Чист текст",
|
||||
"pad.importExport.exportword": "Microsoft Word",
|
||||
"pad.importExport.exportpdf": "PDF",
|
||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "Једино можете увести са једноставног текстуалног формата или HTML формата. За компликованије функције о увозу, молимо да <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">инсталирате AbiWord</a>.",
|
||||
"pad.importExport.abiword.innerHTML": "Једино можете увести са једноставног текстуалног формата или HTML формата. За компликованије функције о увозу, молимо да <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">инсталирате AbiWord</a>.",
|
||||
"pad.modals.connected": "Повезано.",
|
||||
"pad.modals.reconnecting": "Поново се повезујем на ваш пад..",
|
||||
"pad.modals.forcereconnect": "Присилно се поново повежи",
|
||||
|
@ -83,24 +84,24 @@
|
|||
"pad.modals.disconnected": "Веза је прекинута.",
|
||||
"pad.modals.disconnected.explanation": "Изгубљена је веза са сервером",
|
||||
"pad.modals.disconnected.cause": "Сервер није доступан. Обавестите сервисног администратора ако се ово настави дешавати.",
|
||||
"pad.share": "Дели овај пад",
|
||||
"pad.share": "Пофели овај пад",
|
||||
"pad.share.readonly": "Само за читање",
|
||||
"pad.share.link": "Веза",
|
||||
"pad.share.emebdcode": "Угради везу",
|
||||
"pad.chat": "Ћаскање",
|
||||
"pad.chat.title": "Отворите ћаскање за овај пад.",
|
||||
"pad.chat.loadmessages": "Учитајте више порука.",
|
||||
"pad.chat.loadmessages": "Учитај више порука",
|
||||
"timeslider.pageTitle": "{{appTitle}} временска линија",
|
||||
"timeslider.toolbar.returnbutton": "Врати се на пад",
|
||||
"timeslider.toolbar.authors": "Аутори:",
|
||||
"timeslider.toolbar.authorsList": "Нема аутора",
|
||||
"timeslider.toolbar.exportlink.title": "Извези",
|
||||
"timeslider.exportCurrent": "Извези тренутну верзију као:",
|
||||
"timeslider.version": "Верзија {{version}}",
|
||||
"timeslider.version": "Издање {{version}}",
|
||||
"timeslider.saved": "Сачувано на {{day}}. {{month}}. {{year}}",
|
||||
"timeslider.playPause": "Пусти/паузирај садржај пада",
|
||||
"timeslider.backRevision": "Иди на претходну верзију овог пада",
|
||||
"timeslider.forwardRevision": "Иди на следећу верзију овог пада",
|
||||
"timeslider.forwardRevision": "Иди на следеће издање пада",
|
||||
"timeslider.dateformat": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
||||
"timeslider.month.january": "јануар",
|
||||
"timeslider.month.february": "фебруар",
|
||||
|
@ -115,21 +116,21 @@
|
|||
"timeslider.month.november": "новембар",
|
||||
"timeslider.month.december": "децембар",
|
||||
"timeslider.unnamedauthors": "{{num}} неименован(и) {[plural(num) one: аутор, other: аутори ]}",
|
||||
"pad.savedrevs.marked": "Ова верзија је сада означена као сачувана",
|
||||
"pad.savedrevs.marked": "Ова измена је сада означена као сачувана",
|
||||
"pad.savedrevs.timeslider": "Можете видети сачуване измене користећи се временском линијом",
|
||||
"pad.userlist.entername": "Упишите своје име",
|
||||
"pad.userlist.unnamed": "нема имена",
|
||||
"pad.userlist.unnamed": "неименован",
|
||||
"pad.userlist.guest": "Гост",
|
||||
"pad.userlist.deny": "Одбиј",
|
||||
"pad.userlist.approve": "одобрено",
|
||||
"pad.userlist.approve": "Одобри",
|
||||
"pad.editbar.clearcolors": "Очисти ауторске боје за цели документ?",
|
||||
"pad.impexp.importbutton": "Увези одмах",
|
||||
"pad.impexp.importing": "Увожење...",
|
||||
"pad.impexp.importing": "Увозим...",
|
||||
"pad.impexp.confirmimport": "Увоз датотеке ће преписати тренутни текст пада. Да ли сте сигурни да желите наставити?",
|
||||
"pad.impexp.convertFailed": "Не можемо увести ову датотеку. Молимо да користите други формат документа или да документ копирате ручно",
|
||||
"pad.impexp.padHasData": "Не можемо да увеземо ову датотеку зато што је већ било промена на овом паду, молимо да увезете нови пад",
|
||||
"pad.impexp.uploadFailed": "Отпремање није успело, молимо да покушате поново",
|
||||
"pad.impexp.importfailed": "Увоз неуспешан",
|
||||
"pad.impexp.copypaste": "Молимо да ручно копирате",
|
||||
"pad.impexp.convertFailed": "Не могу да увезем ову датотеку. Молимо да користите други формат документа или да документ копирате ручно",
|
||||
"pad.impexp.padHasData": "Не могу да увезем ову датотеку зато што је већ било промена на овом паду, молимо да увезете нови пад",
|
||||
"pad.impexp.uploadFailed": "Нисам успео да отпремим, молимо покушате поново",
|
||||
"pad.impexp.importfailed": "Нисам успео да увезем",
|
||||
"pad.impexp.copypaste": "Копирајте и залепите",
|
||||
"pad.impexp.exportdisabled": "Извоз у формату {{type}} није дозвољен. Контактирајте системског администратора за детаље."
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"BHARATHESHA ALASANDEMAJALU",
|
||||
"VASANTH S.N."
|
||||
]
|
||||
},
|
||||
"index.newPad": "ಪೊಸ ಪ್ಯಾಡ್",
|
||||
"index.createOpenPad": "ಅತಂಡ ಈ ಪುದರ್ತ ಪ್ಯಾಡನ್ನು ಉಂಡು ಮನ್ಪು/ತೋಜಾಲ:",
|
||||
"pad.toolbar.bold.title": "ದಪ್ಪೊ(Ctrl+B)",
|
||||
"pad.toolbar.italic.title": "ಓರೆ (Ctrl-I)",
|
||||
"pad.toolbar.underline.title": "ಅಡಿಗೆರೆ(Ctrl-U)",
|
||||
"pad.toolbar.indent.title": "Indent (TAB)",
|
||||
"pad.toolbar.undo.title": "ಪಿರವುತ(Ctrl+Z)",
|
||||
"pad.toolbar.redo.title": "ದುಂಬುತ್ತ(Ctrl+Y)",
|
||||
"pad.toolbar.settings.title": "ಸಂಯೋಜನೆಲು",
|
||||
"pad.toolbar.showusers.title": "ಈ ಪ್ಯಾಡ್ ಟ್ ಗಲಸುನಾಯಾನ್ ತೋಜಾಲೆ",
|
||||
"pad.colorpicker.save": "ಒರಿಪಾಲೆ",
|
||||
"pad.colorpicker.cancel": "ವಜಾ ಮಲ್ಪುಲೆ",
|
||||
"pad.loading": "ದಿಂಜಾವೊಂದುಂಡು......",
|
||||
"pad.wrongPassword": "ಇರೇನಾ ಪಾಸ್ ವರ್ಡ್ ತಪ್ಪತುಂಡ್",
|
||||
"pad.settings.padSettings": "ಪ್ಯಾಡ್ ಸಂಯೋಜನೆ",
|
||||
"pad.settings.language": "ಬಾಸೆ:",
|
||||
"pad.importExport.exportetherpad": "Etherpad",
|
||||
"pad.importExport.exporthtml": "HTML",
|
||||
"pad.importExport.exportpdf": "PDF",
|
||||
"pad.modals.connected": "ನೆಟ್ ವರ್ಕ್ ತಿಕೊಂತುಂಡು.",
|
||||
"pad.modals.cancel": "ವಜಾ ಮಲ್ಪುಲೆ",
|
||||
"pad.modals.deleted": "ಮಾಜಾಯಿನ.",
|
||||
"pad.share.readonly": "ಓದ್ಯರಾ ಮಾತ್ರ",
|
||||
"pad.share.link": "ಕೊಂಡಿಲು",
|
||||
"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.entername": "ಈರೆನೆ ಪುದರ್ ಬರೆಲೆ",
|
||||
"pad.userlist.unnamed": "ಪುದರ್ ಇಜ್ಜಂತಿನವು",
|
||||
"pad.userlist.guest": "ಬಿನ್ನೆರ್",
|
||||
"pad.userlist.approve": "ಒಪ್ಪಂದ ಅಂಡ್"
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Aefgh39622"
|
||||
]
|
||||
},
|
||||
"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": "ขีดทับ (Ctrl+5)",
|
||||
"pad.toolbar.ol.title": "รายการที่เรียงลำดับ (Ctrl+Shift+N)",
|
||||
"pad.toolbar.ul.title": "รายการที่ไม่เรียงลำดับ (Ctrl+Shift+L)",
|
||||
"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": "ลบสีผู้เขียน (Ctrl+Shift+C)",
|
||||
"pad.toolbar.import_export.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.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": "อ่านเนื้อหาจากขวาไปซ้ายหรือไม่?",
|
||||
"pad.settings.fontType": "ชนิดแบบอักษร:",
|
||||
"pad.settings.globalView": "มุมมองสากล",
|
||||
"pad.settings.language": "ภาษา:",
|
||||
"pad.importExport.import_export": "นำเข้า/ส่งออก",
|
||||
"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",
|
||||
"pad.importExport.exportpdf": "PDF",
|
||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "คุณสามารถนำเข้าได้จากรูปแบบ HTML หรือข้อความธรรมดาเท่านั้น สำหรับคุณสมบัติการนำเข้าขั้นสูงเพิ่มเติม โปรด<a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">ติดตั้ง AbiWord</a>",
|
||||
"pad.modals.connected": "เชื่อมต่อแล้ว",
|
||||
"pad.modals.reconnecting": "กำลังเชื่อมต่อกับแผ่นจดบันทึกของคุณใหม่..",
|
||||
"pad.modals.forcereconnect": "บังคับเชื่อมต่อใหม่",
|
||||
"pad.modals.reconnecttimer": "กำลังพยายามเชื่อมต่อใหม่ใน",
|
||||
"pad.modals.cancel": "ยกเลิก",
|
||||
"pad.modals.userdup": "เปิดในหน้าต่างอื่นแล้ว",
|
||||
"pad.modals.userdup.explanation": "แผ่นจดบันทึกนี้ดูเหมือนว่าจะถูกเปิดในหน้าต่างเบราว์เซอร์มากกว่าหนึ่งหน้าต่างบนคอมพิวเตอร์นี้",
|
||||
"pad.modals.userdup.advice": "เชื่อมต่อใหม่เพื่อใช้หน้าต่างนี้แทน",
|
||||
"pad.modals.unauth": "ไม่ได้รับอนุญาต",
|
||||
"pad.modals.unauth.explanation": "สิทธิของคุณถูกเปลี่ยนขณะที่คุณดูหน้านี้อยู่ พยายามเชื่อมต่อใหม่",
|
||||
"pad.modals.looping.explanation": "มีปัญหาการสื่อสารกับเซิร์ฟเวอร์การซิงค์ข้อมูล",
|
||||
"pad.modals.looping.cause": "บางทีอาจเป็นเพราะคุณเชื่อมต่อกับไฟร์วอลล์หรือพร็อกซีที่เข้ากันไม่ได้",
|
||||
"pad.modals.initsocketfail": "เซิร์ฟเวอร์ไม่สามารถเข้าถึงได้",
|
||||
"pad.modals.initsocketfail.explanation": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์การซิงค์ข้อมูล",
|
||||
"pad.modals.initsocketfail.cause": "อาจเป็นเนื่องจากเบราว์เซอร์ของคุณหรือการเชื่อมต่ออินเทอร์เน็ตของคุณมีปัญหา",
|
||||
"pad.modals.slowcommit.explanation": "เซิร์ฟเวอร์ไม่ตอบสนอง",
|
||||
"pad.modals.slowcommit.cause": "อาจเป็นเนื่องจากปัญหาเกี่ยวกับการเชื่อมต่อเครือข่าย",
|
||||
"pad.modals.badChangeset.explanation": "การแก้ไขที่คุณกระทำถูกจัดว่าไม่เหมาะสมโดยเซิร์ฟเวอร์การซิงค์ข้อมูล",
|
||||
"pad.modals.badChangeset.cause": "อาจเป็นเนื่องจากการกำหนดค่าเซิร์ฟเวอร์ไม่ถูกต้องหรือมีลักษณะการทำงานอื่นๆ บางอย่างที่ไม่คาดคิด โปรดติดต่อผู้ดูแลการให้บริการ ถ้าคุณรู้สึกว่านี่คือข้อผิดพลาด โปรดทำการเชื่อมต่อใหม่อีกครั้งเพื่อทำการแก้ไขต่อไป",
|
||||
"pad.modals.corruptPad.explanation": "แผ่นจดบันทึกที่คุณกำลังพยายามเข้าถึงเสียหาย",
|
||||
"pad.modals.corruptPad.cause": "อาจเป็นเนื่องจากการกำหนดค่าเซิร์ฟเวอร์ไม่ถูกต้องหรือมีลักษณะการทำงานอื่นๆ บางอย่างที่ไม่คาดคิด โปรดติดต่อผู้ดูแลการให้บริการ",
|
||||
"pad.modals.deleted": "ลบแล้ว",
|
||||
"pad.modals.deleted.explanation": "แผ่นจดบันทึกนี้ได้ถูกลบออกแล้ว",
|
||||
"pad.modals.disconnected": "คุณได้ตัดการเชื่อมต่อแล้ว",
|
||||
"pad.modals.disconnected.explanation": "การเชื่อมต่อกับเซิร์ฟเวอร์ถูกตัด",
|
||||
"pad.modals.disconnected.cause": "เซิร์ฟเวอร์อาจใช้ไม่ได้ชั่วคราว โปรดแจ้งให้ผู้ดูแลการให้บริการทราบถ้าปัญหานี้ยังคงเกิดขึ้น",
|
||||
"pad.share": "แชร์แผ่นจดบันทึกนี้",
|
||||
"pad.share.readonly": "อ่านเท่านั้น",
|
||||
"pad.share.link": "ลิงก์",
|
||||
"pad.share.emebdcode": "URL แบบฝังตัว",
|
||||
"pad.chat": "แชท",
|
||||
"pad.chat.title": "เปิดการแชทสำหรับแผ่นจดบันทึกนี้",
|
||||
"pad.chat.loadmessages": "โหลดข้อความเพิ่มเติม",
|
||||
"timeslider.pageTitle": "ตัวเลื่อนเวลา {{appTitle}}",
|
||||
"timeslider.toolbar.returnbutton": "กลับไปแผ่นจดบันทึก",
|
||||
"timeslider.toolbar.authors": "ผู้เขียน:",
|
||||
"timeslider.toolbar.authorsList": "ไม่มีผู้เขียน",
|
||||
"timeslider.toolbar.exportlink.title": "ส่งออก",
|
||||
"timeslider.exportCurrent": "ส่งออกรุ่นปัจจุบันเป็น:",
|
||||
"timeslider.version": "รุ่น {{version}}",
|
||||
"timeslider.saved": "บันทึกแล้วเมื่อ {{day}} {{month}} {{year}}",
|
||||
"timeslider.playPause": "เล่น / พักเนื้อหาแผ่นจดบันทึก",
|
||||
"timeslider.backRevision": "กลับไปรุ่นแก้ไขเก่าของแผ่นจดบันทึกนี้",
|
||||
"timeslider.forwardRevision": "ไปยังรุ่นแก้ไขใหม่ของแผ่นจดบันทึกนี้",
|
||||
"timeslider.dateformat": "{{day}}/{{month}}/{{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}} ผู้เขียนที่ไม่มีชื่อ",
|
||||
"pad.savedrevs.marked": "รุ่นแก้ไขนี้ถูกทำเครื่องหมายเป็นรุ่นแก้ไขที่บันทึกแล้ว",
|
||||
"pad.savedrevs.timeslider": "คุณสามารถดูรุ่นแก้ไขที่บันทึกแล้วโดยเยี่ยมชมตัวเลื่อนเวลา",
|
||||
"pad.userlist.entername": "กรอกชื่อของคุณ",
|
||||
"pad.userlist.unnamed": "ไม่มีชื่อ",
|
||||
"pad.userlist.guest": "ผู้เยี่ยมชม",
|
||||
"pad.userlist.deny": "ปฏิเสธ",
|
||||
"pad.userlist.approve": "อนุมัติ",
|
||||
"pad.editbar.clearcolors": "ล้างสีผู้เขียนบนทั้งเอกสารหรือไม่?",
|
||||
"pad.impexp.importbutton": "นำเข้าเดี๋ยวนี้",
|
||||
"pad.impexp.importing": "กำลังนำเข้า...",
|
||||
"pad.impexp.confirmimport": "การนำเข้าไฟล์จะเป็นการเขียนทับข้อความปัจจุบันบนแผ่นจดบันทึก คุณแน่ใจหรือว่าคุณต้องการดำเนินการต่อ?",
|
||||
"pad.impexp.convertFailed": "เราไม่สามารถนำเข้าไฟล์นี้ได้ โปรดใช้รูปแบบเอกสารอื่นหรือคัดลอกแล้ววางด้วยตนเอง",
|
||||
"pad.impexp.padHasData": "เราไม่สามารถนำเข้าไฟล์นี้ได้เนื่องจากแผ่นจดบันทึกนี้มีการเปลี่ยนแปลงอยู่แล้ว โปรดนำเข้าไปแผ่นจดบันทึกใหม่แทน",
|
||||
"pad.impexp.uploadFailed": "การอัปโหลดล้มเหลว โปรดลองอีกครั้ง",
|
||||
"pad.impexp.importfailed": "การนำเข้าล้มเหลว",
|
||||
"pad.impexp.copypaste": "โปรดคัดลอกแล้ววาง",
|
||||
"pad.impexp.exportdisabled": "การส่งออกเป็นรูปแบบ {{type}} ถูกปิดใช้งาน โปรดติดต่อผู้ดูแลระบบของคุณสำหรับรายละเอียดเพิ่มเติม"
|
||||
}
|
|
@ -59,7 +59,7 @@
|
|||
"pad.importExport.exportword": "Microsoft Word",
|
||||
"pad.importExport.exportpdf": "PDF",
|
||||
"pad.importExport.exportopen": "ODF(開放文件格式)",
|
||||
"pad.importExport.abiword.innerHTML": "您只可以純文字或html格式檔匯入。<a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">安裝abiword</a>取得更多進階的匯入功能。",
|
||||
"pad.importExport.abiword.innerHTML": "您只可以純文字或 HTML 格式檔匯入。<a href=\"ttps://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">安裝\n AbiWord </a>取得更多進階的匯入功能。",
|
||||
"pad.modals.connected": "已連線。",
|
||||
"pad.modals.reconnecting": "重新連接到您的記事本...",
|
||||
"pad.modals.forcereconnect": "強制重新連線",
|
||||
|
|
|
@ -1216,6 +1216,15 @@ function handleClientReady(client, message)
|
|||
"parts": plugins.parts,
|
||||
},
|
||||
"indentationOnNewLine": settings.indentationOnNewLine,
|
||||
"scrollWhenFocusLineIsOutOfViewport": {
|
||||
"percentage" : {
|
||||
"editionAboveViewport": settings.scrollWhenFocusLineIsOutOfViewport.percentage.editionAboveViewport,
|
||||
"editionBelowViewport": settings.scrollWhenFocusLineIsOutOfViewport.percentage.editionBelowViewport,
|
||||
},
|
||||
"duration": settings.scrollWhenFocusLineIsOutOfViewport.duration,
|
||||
"scrollWhenCaretIsInTheLastLineOfViewport": settings.scrollWhenFocusLineIsOutOfViewport.scrollWhenCaretIsInTheLastLineOfViewport,
|
||||
"percentageToScrollWhenUserPressesArrowUp": settings.scrollWhenFocusLineIsOutOfViewport.percentageToScrollWhenUserPressesArrowUp,
|
||||
},
|
||||
"initialChangesets": [] // FIXME: REMOVE THIS SHIT
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ var apiLogger = log4js.getLogger("API");
|
|||
var clientLogger = log4js.getLogger("client");
|
||||
var formidable = require('formidable');
|
||||
var apiHandler = require('../../handler/APIHandler');
|
||||
var isVarName = require('is-var-name');
|
||||
|
||||
//This is for making an api call, collecting all post information and passing it to the apiHandler
|
||||
var apiCaller = function(req, res, fields) {
|
||||
|
@ -18,7 +19,7 @@ var apiCaller = function(req, res, fields) {
|
|||
apiLogger.info("RESPONSE, " + req.params.func + ", " + response);
|
||||
|
||||
//is this a jsonp call, if yes, add the function call
|
||||
if(req.query.jsonp)
|
||||
if(req.query.jsonp && isVarName(response))
|
||||
response = req.query.jsonp + "(" + response + ")";
|
||||
|
||||
res._____send(response);
|
||||
|
|
|
@ -49,5 +49,8 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
//sigint is so far not working on windows
|
||||
//https://github.com/joyent/node/issues/1553
|
||||
process.on('SIGINT', exports.gracefulShutdown);
|
||||
// when running as PID1 (e.g. in docker container)
|
||||
// allow graceful shutdown on SIGTERM c.f. #3265
|
||||
process.on('SIGTERM', exports.gracefulShutdown);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -247,6 +247,33 @@ exports.users = {};
|
|||
*/
|
||||
exports.showSettingsInAdminPage = true;
|
||||
|
||||
/*
|
||||
* By default, when caret is moved out of viewport, it scrolls the minimum height needed to make this
|
||||
* line visible.
|
||||
*/
|
||||
exports.scrollWhenFocusLineIsOutOfViewport = {
|
||||
/*
|
||||
* Percentage of viewport height to be additionally scrolled.
|
||||
*/
|
||||
"percentage": {
|
||||
"editionAboveViewport": 0,
|
||||
"editionBelowViewport": 0
|
||||
},
|
||||
/*
|
||||
* Time (in milliseconds) used to animate the scroll transition. Set to 0 to disable animation
|
||||
*/
|
||||
"duration": 0,
|
||||
/*
|
||||
* Flag to control if it should scroll when user places the caret in the last line of the viewport
|
||||
*/
|
||||
/*
|
||||
* Percentage of viewport height to be additionally scrolled when user presses arrow up
|
||||
* in the line of the top of the viewport.
|
||||
*/
|
||||
"percentageToScrollWhenUserPressesArrowUp": 0,
|
||||
"scrollWhenCaretIsInTheLastLineOfViewport": false
|
||||
};
|
||||
|
||||
//checks if abiword is avaiable
|
||||
exports.abiwordAvailable = function()
|
||||
{
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
"cheerio" : "0.20.0",
|
||||
"async-stacktrace" : "0.0.2",
|
||||
"npm" : "4.0.2",
|
||||
"ejs" : "2.4.1",
|
||||
"ejs" : "2.5.7",
|
||||
"graceful-fs" : "4.1.3",
|
||||
"slide" : "1.1.6",
|
||||
"semver" : "5.1.0",
|
||||
|
@ -43,7 +43,8 @@
|
|||
"jsonminify" : "0.4.1",
|
||||
"measured" : "1.1.0",
|
||||
"mocha" : "2.4.5",
|
||||
"supertest" : "1.2.0"
|
||||
"supertest" : "1.2.0",
|
||||
"is-var-name" : "1.0.0"
|
||||
},
|
||||
"bin": { "etherpad-lite": "./node/server.js" },
|
||||
"devDependencies": {
|
||||
|
@ -55,6 +56,6 @@
|
|||
"repository" : { "type" : "git",
|
||||
"url" : "http://github.com/ether/etherpad-lite.git"
|
||||
},
|
||||
"version" : "1.6.2",
|
||||
"version" : "1.6.3",
|
||||
"license" : "Apache-2.0"
|
||||
}
|
||||
|
|
|
@ -400,7 +400,19 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
this.removeAttributeOnLine(lineNum, attributeName) :
|
||||
this.setAttributeOnLine(lineNum, attributeName, attributeValue);
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
hasAttributeOnSelectionOrCaretPosition: function(attributeName) {
|
||||
var hasSelection = ((this.rep.selStart[0] !== this.rep.selEnd[0]) || (this.rep.selEnd[1] !== this.rep.selStart[1]));
|
||||
var hasAttrib;
|
||||
if (hasSelection) {
|
||||
hasAttrib = this.getAttributeOnSelection(attributeName);
|
||||
}else {
|
||||
var attributesOnCaretPosition = this.getAttributesOnCaret();
|
||||
hasAttrib = _.contains(_.flatten(attributesOnCaretPosition), attributeName);
|
||||
}
|
||||
return hasAttrib;
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = AttributeManager;
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
var _, $, jQuery, plugins, Ace2Common;
|
||||
|
||||
var browser = require('./browser');
|
||||
if(browser.msie){
|
||||
// Honestly fuck IE royally.
|
||||
|
@ -61,6 +60,7 @@ function Ace2Inner(){
|
|||
var SkipList = require('./skiplist');
|
||||
var undoModule = require('./undomodule').undoModule;
|
||||
var AttributeManager = require('./AttributeManager');
|
||||
var Scroll = require('./scroll');
|
||||
|
||||
var DEBUG = false; //$$ build script replaces the string "var DEBUG=true;//$$" with "var DEBUG=false;"
|
||||
// changed to false
|
||||
|
@ -75,6 +75,9 @@ function Ace2Inner(){
|
|||
var EDIT_BODY_PADDING_TOP = 8;
|
||||
var EDIT_BODY_PADDING_LEFT = 8;
|
||||
|
||||
var FORMATTING_STYLES = ['bold', 'italic', 'underline', 'strikethrough'];
|
||||
var SELECT_BUTTON_CLASS = 'selected';
|
||||
|
||||
var caughtErrors = [];
|
||||
|
||||
var thisAuthor = '';
|
||||
|
@ -82,6 +85,7 @@ function Ace2Inner(){
|
|||
var disposed = false;
|
||||
var editorInfo = parent.editorInfo;
|
||||
|
||||
|
||||
var iframe = window.frameElement;
|
||||
var outerWin = iframe.ace_outerWin;
|
||||
iframe.ace_outerWin = null; // prevent IE 6 memory leak
|
||||
|
@ -89,6 +93,8 @@ function Ace2Inner(){
|
|||
var lineMetricsDiv = sideDiv.nextSibling;
|
||||
initLineNumbers();
|
||||
|
||||
var scroll = Scroll.init(outerWin);
|
||||
|
||||
var outsideKeyDown = noop;
|
||||
|
||||
var outsideKeyPress = function(){return true;};
|
||||
|
@ -424,7 +430,7 @@ function Ace2Inner(){
|
|||
var undoWorked = false;
|
||||
try
|
||||
{
|
||||
if (evt.eventType == "setup" || evt.eventType == "importText" || evt.eventType == "setBaseText")
|
||||
if (isPadLoading(evt.eventType))
|
||||
{
|
||||
undoModule.clearHistory();
|
||||
}
|
||||
|
@ -1208,7 +1214,7 @@ function Ace2Inner(){
|
|||
updateLineNumbers(); // update line numbers if any time left
|
||||
if (isTimeUp()) return;
|
||||
|
||||
var visibleRange = getVisibleCharRange();
|
||||
var visibleRange = scroll.getVisibleCharRange(rep);
|
||||
var docRange = [0, rep.lines.totalWidth()];
|
||||
//console.log("%o %o", docRange, visibleRange);
|
||||
finishedImportantWork = true;
|
||||
|
@ -1670,7 +1676,7 @@ function Ace2Inner(){
|
|||
});
|
||||
|
||||
//p.mark("relex");
|
||||
//rep.lexer.lexCharRange(getVisibleCharRange(), function() { return false; });
|
||||
//rep.lexer.lexCharRange(scroll.getVisibleCharRange(rep), function() { return false; });
|
||||
//var isTimeUp = newTimeLimit(100);
|
||||
// do DOM inserts
|
||||
p.mark("insert");
|
||||
|
@ -2469,17 +2475,11 @@ function Ace2Inner(){
|
|||
}
|
||||
}
|
||||
|
||||
if (selectionAllHasIt)
|
||||
{
|
||||
documentAttributeManager.setAttributesOnRange(rep.selStart, rep.selEnd, [
|
||||
[attributeName, '']
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
documentAttributeManager.setAttributesOnRange(rep.selStart, rep.selEnd, [
|
||||
[attributeName, 'true']
|
||||
]);
|
||||
|
||||
var attributeValue = selectionAllHasIt ? '' : 'true';
|
||||
documentAttributeManager.setAttributesOnRange(rep.selStart, rep.selEnd, [[attributeName, attributeValue]]);
|
||||
if (attribIsFormattingStyle(attributeName)) {
|
||||
updateStyleButtonState(attributeName, !selectionAllHasIt); // italic, bold, ...
|
||||
}
|
||||
}
|
||||
editorInfo.ace_toggleAttributeOnSelection = toggleAttributeOnSelection;
|
||||
|
@ -2908,12 +2908,24 @@ function Ace2Inner(){
|
|||
rep.selFocusAtStart = newSelFocusAtStart;
|
||||
currentCallStack.repChanged = true;
|
||||
|
||||
// select the formatting buttons when there is the style applied on selection
|
||||
selectFormattingButtonIfLineHasStyleApplied(rep);
|
||||
|
||||
hooks.callAll('aceSelectionChanged', {
|
||||
rep: rep,
|
||||
callstack: currentCallStack,
|
||||
documentAttributeManager: documentAttributeManager,
|
||||
});
|
||||
|
||||
// we scroll when user places the caret at the last line of the pad
|
||||
// when this settings is enabled
|
||||
var docTextChanged = currentCallStack.docTextChanged;
|
||||
if(!docTextChanged){
|
||||
var isScrollableEvent = !isPadLoading(currentCallStack.type) && isScrollableEditEvent(currentCallStack.type);
|
||||
var innerHeight = getInnerHeight();
|
||||
scroll.scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary(rep, isScrollableEvent, innerHeight);
|
||||
}
|
||||
|
||||
return true;
|
||||
//console.log("selStart: %o, selEnd: %o, focusAtStart: %s", rep.selStart, rep.selEnd,
|
||||
//String(!!rep.selFocusAtStart));
|
||||
|
@ -2922,6 +2934,27 @@ function Ace2Inner(){
|
|||
//console.log("%o %o %s", rep.selStart, rep.selEnd, rep.selFocusAtStart);
|
||||
}
|
||||
|
||||
function isPadLoading(eventType)
|
||||
{
|
||||
return (eventType === 'setup') || (eventType === 'setBaseText') || (eventType === 'importText');
|
||||
}
|
||||
|
||||
function updateStyleButtonState(attribName, hasStyleOnRepSelection) {
|
||||
var $formattingButton = parent.parent.$('[data-key="' + attribName + '"]').find('a');
|
||||
$formattingButton.toggleClass(SELECT_BUTTON_CLASS, hasStyleOnRepSelection);
|
||||
}
|
||||
|
||||
function attribIsFormattingStyle(attributeName) {
|
||||
return _.contains(FORMATTING_STYLES, attributeName);
|
||||
}
|
||||
|
||||
function selectFormattingButtonIfLineHasStyleApplied (rep) {
|
||||
_.each(FORMATTING_STYLES, function (style) {
|
||||
var hasStyleOnRepSelection = documentAttributeManager.hasAttributeOnSelectionOrCaretPosition(style);
|
||||
updateStyleButtonState(style, hasStyleOnRepSelection);
|
||||
})
|
||||
}
|
||||
|
||||
function doCreateDomLine(nonEmpty)
|
||||
{
|
||||
if (browser.msie && (!nonEmpty))
|
||||
|
@ -3277,50 +3310,36 @@ function Ace2Inner(){
|
|||
return false;
|
||||
}
|
||||
|
||||
function getLineEntryTopBottom(entry, destObj)
|
||||
{
|
||||
var dom = entry.lineNode;
|
||||
var top = dom.offsetTop;
|
||||
var height = dom.offsetHeight;
|
||||
var obj = (destObj || {});
|
||||
obj.top = top;
|
||||
obj.bottom = (top + height);
|
||||
return obj;
|
||||
}
|
||||
|
||||
function getViewPortTopBottom()
|
||||
{
|
||||
var theTop = getScrollY();
|
||||
var theTop = scroll.getScrollY();
|
||||
var doc = outerWin.document;
|
||||
var height = doc.documentElement.clientHeight;
|
||||
var height = doc.documentElement.clientHeight; // includes padding
|
||||
|
||||
// we have to get the exactly height of the viewport. So it has to subtract all the values which changes
|
||||
// the viewport height (E.g. padding, position top)
|
||||
var viewportExtraSpacesAndPosition = getEditorPositionTop() + getPaddingTopAddedWhenPageViewIsEnable();
|
||||
return {
|
||||
top: theTop,
|
||||
bottom: (theTop + height)
|
||||
bottom: (theTop + height - viewportExtraSpacesAndPosition)
|
||||
};
|
||||
}
|
||||
|
||||
function getVisibleLineRange()
|
||||
|
||||
function getEditorPositionTop()
|
||||
{
|
||||
var viewport = getViewPortTopBottom();
|
||||
//console.log("viewport top/bottom: %o", viewport);
|
||||
var obj = {};
|
||||
var start = rep.lines.search(function(e)
|
||||
{
|
||||
return getLineEntryTopBottom(e, obj).bottom > viewport.top;
|
||||
});
|
||||
var end = rep.lines.search(function(e)
|
||||
{
|
||||
return getLineEntryTopBottom(e, obj).top >= viewport.bottom;
|
||||
});
|
||||
if (end < start) end = start; // unlikely
|
||||
//console.log(start+","+end);
|
||||
return [start, end];
|
||||
var editor = parent.document.getElementsByTagName('iframe');
|
||||
var editorPositionTop = editor[0].offsetTop;
|
||||
return editorPositionTop;
|
||||
}
|
||||
|
||||
function getVisibleCharRange()
|
||||
// ep_page_view adds padding-top, which makes the viewport smaller
|
||||
function getPaddingTopAddedWhenPageViewIsEnable()
|
||||
{
|
||||
var lineRange = getVisibleLineRange();
|
||||
return [rep.lines.offsetOfIndex(lineRange[0]), rep.lines.offsetOfIndex(lineRange[1])];
|
||||
var rootDocument = parent.parent.document;
|
||||
var aceOuter = rootDocument.getElementsByName("ace_outer");
|
||||
var aceOuterPaddingTop = parseInt($(aceOuter).css("padding-top"));
|
||||
return aceOuterPaddingTop;
|
||||
}
|
||||
|
||||
function handleCut(evt)
|
||||
|
@ -3966,12 +3985,12 @@ function Ace2Inner(){
|
|||
doDeleteKey();
|
||||
specialHandled = true;
|
||||
}
|
||||
if((evt.which == 36 && evt.ctrlKey == true) && padShortcutEnabled.ctrlHome){ setScrollY(0); } // Control Home send to Y = 0
|
||||
if((evt.which == 36 && evt.ctrlKey == true) && padShortcutEnabled.ctrlHome){ scroll.setScrollY(0); } // Control Home send to Y = 0
|
||||
if((evt.which == 33 || evt.which == 34) && type == 'keydown' && !evt.ctrlKey){
|
||||
|
||||
evt.preventDefault(); // This is required, browsers will try to do normal default behavior on page up / down and the default behavior SUCKS
|
||||
|
||||
var oldVisibleLineRange = getVisibleLineRange();
|
||||
var oldVisibleLineRange = scroll.getVisibleLineRange(rep);
|
||||
var topOffset = rep.selStart[0] - oldVisibleLineRange[0];
|
||||
if(topOffset < 0 ){
|
||||
topOffset = 0;
|
||||
|
@ -3981,7 +4000,7 @@ function Ace2Inner(){
|
|||
var isPageUp = evt.which === 33;
|
||||
|
||||
scheduler.setTimeout(function(){
|
||||
var newVisibleLineRange = getVisibleLineRange(); // the visible lines IE 1,10
|
||||
var newVisibleLineRange = scroll.getVisibleLineRange(rep); // the visible lines IE 1,10
|
||||
var linesCount = rep.lines.length(); // total count of lines in pad IE 10
|
||||
var numberOfLinesInViewport = newVisibleLineRange[1] - newVisibleLineRange[0]; // How many lines are in the viewport right now?
|
||||
|
||||
|
@ -4014,56 +4033,26 @@ function Ace2Inner(){
|
|||
// sometimes the first selection is -1 which causes problems (Especially with ep_page_view)
|
||||
// so use focusNode.offsetTop value.
|
||||
if(caretOffsetTop === -1) caretOffsetTop = myselection.focusNode.offsetTop;
|
||||
setScrollY(caretOffsetTop); // set the scrollY offset of the viewport on the document
|
||||
scroll.setScrollY(caretOffsetTop); // set the scrollY offset of the viewport on the document
|
||||
|
||||
}, 200);
|
||||
}
|
||||
/* Attempt to apply some sanity to cursor handling in Chrome after a copy / paste event
|
||||
We have to do this the way we do because rep. doesn't hold the value for keyheld events IE if the user
|
||||
presses and holds the arrow key .. Sorry if this is ugly, blame Chrome's weird handling of viewports after new content is added*/
|
||||
if((evt.which == 37 || evt.which == 38 || evt.which == 39 || evt.which == 40) && browser.chrome){
|
||||
var viewport = getViewPortTopBottom();
|
||||
var myselection = document.getSelection(); // get the current caret selection, can't use rep. here because that only gives us the start position not the current
|
||||
var caretOffsetTop = myselection.focusNode.parentNode.offsetTop || myselection.focusNode.offsetTop; // get the carets selection offset in px IE 214
|
||||
var lineHeight = $(myselection.focusNode.parentNode).parent("div").height(); // get the line height of the caret line
|
||||
// top.console.log("offsetTop", myselection.focusNode.parentNode.parentNode.offsetTop);
|
||||
try {
|
||||
lineHeight = $(myselection.focusNode).height() // needed for how chrome handles line heights of null objects
|
||||
// console.log("lineHeight now", lineHeight);
|
||||
}catch(e){}
|
||||
var caretOffsetTopBottom = caretOffsetTop + lineHeight;
|
||||
var visibleLineRange = getVisibleLineRange(); // the visible lines IE 1,10
|
||||
|
||||
if(caretOffsetTop){ // sometimes caretOffsetTop bugs out and returns 0, not sure why, possible Chrome bug? Either way if it does we don't wanna mess with it
|
||||
// top.console.log(caretOffsetTop, viewport.top, caretOffsetTopBottom, viewport.bottom);
|
||||
var caretIsNotVisible = (caretOffsetTop < viewport.top || caretOffsetTopBottom >= viewport.bottom); // Is the Caret Visible to the user?
|
||||
// Expect some weird behavior caretOffsetTopBottom is greater than viewport.bottom on a keypress down
|
||||
var offsetTopSamePlace = caretOffsetTop == viewport.top; // sometimes moving key left & up leaves the caret at the same point as the viewport.top, technically the caret is visible but it's not fully visible so we should move to it
|
||||
if(offsetTopSamePlace && (evt.which == 37 || evt.which == 38)){
|
||||
var newY = caretOffsetTop;
|
||||
setScrollY(newY);
|
||||
}
|
||||
// scroll to viewport when user presses arrow keys and caret is out of the viewport
|
||||
if((evt.which == 37 || evt.which == 38 || evt.which == 39 || evt.which == 40)){
|
||||
// we use arrowKeyWasReleased to avoid triggering the animation when a key is continuously pressed
|
||||
// this makes the scroll smooth
|
||||
if(!continuouslyPressingArrowKey(type)){
|
||||
// We use getSelection() instead of rep to get the caret position. This avoids errors like when
|
||||
// the caret position is not synchronized with the rep. For example, when an user presses arrow
|
||||
// down to scroll the pad without releasing the key. When the key is released the rep is not
|
||||
// synchronized, so we don't get the right node where caret is.
|
||||
var selection = getSelection();
|
||||
|
||||
if(caretIsNotVisible){ // is the cursor no longer visible to the user?
|
||||
// top.console.log("Caret is NOT visible to the user");
|
||||
// top.console.log(caretOffsetTop,viewport.top,caretOffsetTopBottom,viewport.bottom);
|
||||
// Oh boy the caret is out of the visible area, I need to scroll the browser window to lineNum.
|
||||
if(evt.which == 37 || evt.which == 38){ // If left or up arrow
|
||||
var newY = caretOffsetTop; // That was easy!
|
||||
}
|
||||
if(evt.which == 39 || evt.which == 40){ // if down or right arrow
|
||||
// only move the viewport if we're at the bottom of the viewport, if we hit down any other time the viewport shouldn't change
|
||||
// NOTE: This behavior only fires if Chrome decides to break the page layout after a paste, it's annoying but nothing I can do
|
||||
var selection = getSelection();
|
||||
// top.console.log("line #", rep.selStart[0]); // the line our caret is on
|
||||
// top.console.log("firstvisible", visibleLineRange[0]); // the first visiblel ine
|
||||
// top.console.log("lastVisible", visibleLineRange[1]); // the last visible line
|
||||
// top.console.log(rep.selStart[0], visibleLineRange[1], rep.selStart[0], visibleLineRange[0]);
|
||||
var newY = viewport.top + lineHeight;
|
||||
}
|
||||
if(newY){
|
||||
setScrollY(newY); // set the scrollY offset of the viewport on the document
|
||||
}
|
||||
if(selection){
|
||||
var arrowUp = evt.which === 38;
|
||||
var innerHeight = getInnerHeight();
|
||||
scroll.scrollWhenPressArrowKeys(arrowUp, rep, innerHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4121,6 +4110,19 @@ function Ace2Inner(){
|
|||
|
||||
var thisKeyDoesntTriggerNormalize = false;
|
||||
|
||||
var arrowKeyWasReleased = true;
|
||||
function continuouslyPressingArrowKey(type) {
|
||||
var firstTimeKeyIsContinuouslyPressed = false;
|
||||
|
||||
if (type == 'keyup') arrowKeyWasReleased = true;
|
||||
else if (type == 'keydown' && arrowKeyWasReleased) {
|
||||
firstTimeKeyIsContinuouslyPressed = true;
|
||||
arrowKeyWasReleased = false;
|
||||
}
|
||||
|
||||
return !firstTimeKeyIsContinuouslyPressed;
|
||||
}
|
||||
|
||||
function doUndoRedo(which)
|
||||
{
|
||||
// precond: normalized DOM
|
||||
|
@ -4837,9 +4839,6 @@ function Ace2Inner(){
|
|||
setIfNecessary(root.style, "height", "");
|
||||
}
|
||||
}
|
||||
// if near edge, scroll to edge
|
||||
var scrollX = getScrollX();
|
||||
var scrollY = getScrollY();
|
||||
var win = outerWin;
|
||||
var r = 20;
|
||||
|
||||
|
@ -4848,52 +4847,6 @@ function Ace2Inner(){
|
|||
$(sideDiv).addClass('sidedivdelayed');
|
||||
}
|
||||
|
||||
function getScrollXY()
|
||||
{
|
||||
var win = outerWin;
|
||||
var odoc = outerWin.document;
|
||||
if (typeof(win.pageYOffset) == "number")
|
||||
{
|
||||
return {
|
||||
x: win.pageXOffset,
|
||||
y: win.pageYOffset
|
||||
};
|
||||
}
|
||||
var docel = odoc.documentElement;
|
||||
if (docel && typeof(docel.scrollTop) == "number")
|
||||
{
|
||||
return {
|
||||
x: docel.scrollLeft,
|
||||
y: docel.scrollTop
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getScrollX()
|
||||
{
|
||||
return getScrollXY().x;
|
||||
}
|
||||
|
||||
function getScrollY()
|
||||
{
|
||||
return getScrollXY().y;
|
||||
}
|
||||
|
||||
function setScrollX(x)
|
||||
{
|
||||
outerWin.scrollTo(x, getScrollY());
|
||||
}
|
||||
|
||||
function setScrollY(y)
|
||||
{
|
||||
outerWin.scrollTo(getScrollX(), y);
|
||||
}
|
||||
|
||||
function setScrollXY(x, y)
|
||||
{
|
||||
outerWin.scrollTo(x, y);
|
||||
}
|
||||
|
||||
var _teardownActions = [];
|
||||
|
||||
function teardown()
|
||||
|
@ -5214,26 +5167,6 @@ function Ace2Inner(){
|
|||
return odoc.documentElement.clientWidth;
|
||||
}
|
||||
|
||||
function scrollNodeVerticallyIntoView(node)
|
||||
{
|
||||
// requires element (non-text) node;
|
||||
// if node extends above top of viewport or below bottom of viewport (or top of scrollbar),
|
||||
// scroll it the minimum distance needed to be completely in view.
|
||||
var win = outerWin;
|
||||
var odoc = outerWin.document;
|
||||
var distBelowTop = node.offsetTop + iframePadTop - win.scrollY;
|
||||
var distAboveBottom = win.scrollY + getInnerHeight() - (node.offsetTop + iframePadTop + node.offsetHeight);
|
||||
|
||||
if (distBelowTop < 0)
|
||||
{
|
||||
win.scrollBy(0, distBelowTop);
|
||||
}
|
||||
else if (distAboveBottom < 0)
|
||||
{
|
||||
win.scrollBy(0, -distAboveBottom);
|
||||
}
|
||||
}
|
||||
|
||||
function scrollXHorizontallyIntoView(pixelX)
|
||||
{
|
||||
var win = outerWin;
|
||||
|
@ -5255,8 +5188,8 @@ function Ace2Inner(){
|
|||
{
|
||||
if (!rep.selStart) return;
|
||||
fixView();
|
||||
var focusLine = (rep.selFocusAtStart ? rep.selStart[0] : rep.selEnd[0]);
|
||||
scrollNodeVerticallyIntoView(rep.lines.atIndex(focusLine).lineNode);
|
||||
var innerHeight = getInnerHeight();
|
||||
scroll.scrollNodeVerticallyIntoView(rep, innerHeight);
|
||||
if (!doesWrap)
|
||||
{
|
||||
var browserSelection = getSelection();
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
// One rep.line(div) can be broken in more than one line in the browser.
|
||||
// This function is useful to get the caret position of the line as
|
||||
// is represented by the browser
|
||||
exports.getPosition = function ()
|
||||
{
|
||||
var rect, line;
|
||||
var editor = $('#innerdocbody')[0];
|
||||
var range = getSelectionRange();
|
||||
var isSelectionInsideTheEditor = range && $(range.endContainer).closest('body')[0].id === 'innerdocbody';
|
||||
|
||||
if(isSelectionInsideTheEditor){
|
||||
// when we have the caret in an empty line, e.g. a line with only a <br>,
|
||||
// getBoundingClientRect() returns all dimensions value as 0
|
||||
var selectionIsInTheBeginningOfLine = range.endOffset > 0;
|
||||
if (selectionIsInTheBeginningOfLine) {
|
||||
var clonedRange = createSelectionRange(range);
|
||||
line = getPositionOfElementOrSelection(clonedRange);
|
||||
clonedRange.detach()
|
||||
}
|
||||
|
||||
// when there's a <br> or any element that has no height, we can't get
|
||||
// the dimension of the element where the caret is
|
||||
if(!rect || rect.height === 0){
|
||||
var clonedRange = createSelectionRange(range);
|
||||
|
||||
// as we can't get the element height, we create a text node to get the dimensions
|
||||
// on the position
|
||||
var shadowCaret = $(document.createTextNode("|"));
|
||||
clonedRange.insertNode(shadowCaret[0]);
|
||||
clonedRange.selectNode(shadowCaret[0]);
|
||||
|
||||
line = getPositionOfElementOrSelection(clonedRange);
|
||||
clonedRange.detach()
|
||||
shadowCaret.remove();
|
||||
}
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
var createSelectionRange = function (range) {
|
||||
clonedRange = range.cloneRange();
|
||||
|
||||
// we set the selection start and end to avoid error when user selects a text bigger than
|
||||
// the viewport height and uses the arrow keys to expand the selection. In this particular
|
||||
// case is necessary to know where the selections ends because both edges of the selection
|
||||
// is out of the viewport but we only use the end of it to calculate if it needs to scroll
|
||||
clonedRange.setStart(range.endContainer, range.endOffset);
|
||||
clonedRange.setEnd(range.endContainer, range.endOffset);
|
||||
return clonedRange;
|
||||
}
|
||||
|
||||
var getPositionOfRepLineAtOffset = function (node, offset) {
|
||||
// it is not a text node, so we cannot make a selection
|
||||
if (node.tagName === 'BR' || node.tagName === 'EMPTY') {
|
||||
return getPositionOfElementOrSelection(node);
|
||||
}
|
||||
|
||||
while (node.length === 0 && node.nextSibling) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
|
||||
var newRange = new Range();
|
||||
newRange.setStart(node, offset);
|
||||
newRange.setEnd(node, offset);
|
||||
var linePosition = getPositionOfElementOrSelection(newRange);
|
||||
newRange.detach(); // performance sake
|
||||
return linePosition;
|
||||
}
|
||||
|
||||
function getPositionOfElementOrSelection(element) {
|
||||
var rect = element.getBoundingClientRect();
|
||||
var linePosition = {
|
||||
bottom: rect.bottom,
|
||||
height: rect.height,
|
||||
top: rect.top
|
||||
}
|
||||
return linePosition;
|
||||
}
|
||||
|
||||
// here we have two possibilities:
|
||||
// [1] the line before the caret line has the same type, so both of them has the same margin, padding
|
||||
// height, etc. So, we can use the caret line to make calculation necessary to know where is the top
|
||||
// of the previous line
|
||||
// [2] the line before is part of another rep line. It's possible this line has different margins
|
||||
// height. So we have to get the exactly position of the line
|
||||
exports.getPositionTopOfPreviousBrowserLine = function(caretLinePosition, rep) {
|
||||
var previousLineTop = caretLinePosition.top - caretLinePosition.height; // [1]
|
||||
var isCaretLineFirstBrowserLine = caretLineIsFirstBrowserLine(caretLinePosition.top, rep);
|
||||
|
||||
// the caret is in the beginning of a rep line, so the previous browser line
|
||||
// is the last line browser line of the a rep line
|
||||
if (isCaretLineFirstBrowserLine) { //[2]
|
||||
var lineBeforeCaretLine = rep.selStart[0] - 1;
|
||||
var firstLineVisibleBeforeCaretLine = getPreviousVisibleLine(lineBeforeCaretLine, rep);
|
||||
var linePosition = getDimensionOfLastBrowserLineOfRepLine(firstLineVisibleBeforeCaretLine, rep);
|
||||
previousLineTop = linePosition.top;
|
||||
}
|
||||
return previousLineTop;
|
||||
}
|
||||
|
||||
function caretLineIsFirstBrowserLine(caretLineTop, rep)
|
||||
{
|
||||
var caretRepLine = rep.selStart[0];
|
||||
var lineNode = rep.lines.atIndex(caretRepLine).lineNode;
|
||||
var firstRootNode = getFirstRootChildNode(lineNode);
|
||||
|
||||
// to get the position of the node we get the position of the first char
|
||||
var positionOfFirstRootNode = getPositionOfRepLineAtOffset(firstRootNode, 1);
|
||||
return positionOfFirstRootNode.top === caretLineTop;
|
||||
}
|
||||
|
||||
// find the first root node, usually it is a text node
|
||||
function getFirstRootChildNode(node)
|
||||
{
|
||||
if(!node.firstChild){
|
||||
return node;
|
||||
}else{
|
||||
return getFirstRootChildNode(node.firstChild);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getPreviousVisibleLine(line, rep)
|
||||
{
|
||||
if (line < 0) {
|
||||
return 0;
|
||||
}else if (isLineVisible(line, rep)) {
|
||||
return line;
|
||||
}else{
|
||||
return getPreviousVisibleLine(line - 1, rep);
|
||||
}
|
||||
}
|
||||
|
||||
function getDimensionOfLastBrowserLineOfRepLine(line, rep)
|
||||
{
|
||||
var lineNode = rep.lines.atIndex(line).lineNode;
|
||||
var lastRootChildNode = getLastRootChildNode(lineNode);
|
||||
|
||||
// we get the position of the line in the last char of it
|
||||
var lastRootChildNodePosition = getPositionOfRepLineAtOffset(lastRootChildNode.node, lastRootChildNode.length);
|
||||
return lastRootChildNodePosition;
|
||||
}
|
||||
|
||||
function getLastRootChildNode(node)
|
||||
{
|
||||
if(!node.lastChild){
|
||||
return {
|
||||
node: node,
|
||||
length: node.length
|
||||
};
|
||||
}else{
|
||||
return getLastRootChildNode(node.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
// here we have two possibilities:
|
||||
// [1] The next line is part of the same rep line of the caret line, so we have the same dimensions.
|
||||
// So, we can use the caret line to calculate the bottom of the line.
|
||||
// [2] the next line is part of another rep line. It's possible this line has different dimensions, so we
|
||||
// have to get the exactly dimension of it
|
||||
exports.getBottomOfNextBrowserLine = function(caretLinePosition, rep)
|
||||
{
|
||||
var nextLineBottom = caretLinePosition.bottom + caretLinePosition.height; //[1]
|
||||
var isCaretLineLastBrowserLine = caretLineIsLastBrowserLineOfRepLine(caretLinePosition.top, rep);
|
||||
|
||||
// the caret is at the end of a rep line, so we can get the next browser line dimension
|
||||
// using the position of the first char of the next rep line
|
||||
if(isCaretLineLastBrowserLine){ //[2]
|
||||
var nextLineAfterCaretLine = rep.selStart[0] + 1;
|
||||
var firstNextLineVisibleAfterCaretLine = getNextVisibleLine(nextLineAfterCaretLine, rep);
|
||||
var linePosition = getDimensionOfFirstBrowserLineOfRepLine(firstNextLineVisibleAfterCaretLine, rep);
|
||||
nextLineBottom = linePosition.bottom;
|
||||
}
|
||||
return nextLineBottom;
|
||||
}
|
||||
|
||||
function caretLineIsLastBrowserLineOfRepLine(caretLineTop, rep)
|
||||
{
|
||||
var caretRepLine = rep.selStart[0];
|
||||
var lineNode = rep.lines.atIndex(caretRepLine).lineNode;
|
||||
var lastRootChildNode = getLastRootChildNode(lineNode);
|
||||
|
||||
// we take a rep line and get the position of the last char of it
|
||||
var lastRootChildNodePosition = getPositionOfRepLineAtOffset(lastRootChildNode.node, lastRootChildNode.length);
|
||||
return lastRootChildNodePosition.top === caretLineTop;
|
||||
}
|
||||
|
||||
function getPreviousVisibleLine(line, rep)
|
||||
{
|
||||
var firstLineOfPad = 0;
|
||||
if (line <= firstLineOfPad) {
|
||||
return firstLineOfPad;
|
||||
}else if (isLineVisible(line,rep)) {
|
||||
return line;
|
||||
}else{
|
||||
return getPreviousVisibleLine(line - 1, rep);
|
||||
}
|
||||
}
|
||||
exports.getPreviousVisibleLine = getPreviousVisibleLine;
|
||||
|
||||
function getNextVisibleLine(line, rep)
|
||||
{
|
||||
var lastLineOfThePad = rep.lines.length() - 1;
|
||||
if (line >= lastLineOfThePad) {
|
||||
return lastLineOfThePad;
|
||||
}else if (isLineVisible(line,rep)) {
|
||||
return line;
|
||||
}else{
|
||||
return getNextVisibleLine(line + 1, rep);
|
||||
}
|
||||
}
|
||||
exports.getNextVisibleLine = getNextVisibleLine;
|
||||
|
||||
function isLineVisible(line, rep)
|
||||
{
|
||||
return rep.lines.atIndex(line).lineNode.offsetHeight > 0;
|
||||
}
|
||||
|
||||
function getDimensionOfFirstBrowserLineOfRepLine(line, rep)
|
||||
{
|
||||
var lineNode = rep.lines.atIndex(line).lineNode;
|
||||
var firstRootChildNode = getFirstRootChildNode(lineNode);
|
||||
|
||||
// we can get the position of the line, getting the position of the first char of the rep line
|
||||
var firstRootChildNodePosition = getPositionOfRepLineAtOffset(firstRootChildNode, 1);
|
||||
return firstRootChildNodePosition;
|
||||
}
|
||||
|
||||
function getSelectionRange()
|
||||
{
|
||||
var selection;
|
||||
if (!window.getSelection) {
|
||||
return;
|
||||
}
|
||||
selection = window.getSelection();
|
||||
if (selection.rangeCount > 0) {
|
||||
return selection.getRangeAt(0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -524,7 +524,7 @@ function setupGlobalExceptionHandler() {
|
|||
$("#editorloadingbox").css("padding", "10px");
|
||||
$("#editorloadingbox").css("padding-top", "45px");
|
||||
$("#editorloadingbox").html("<div style='text-align:left;color:red;font-size:16px;'><b>An error occurred</b><br>The error was reported with the following id: '" + errorId + "'<br><br><span style='color:black;font-weight:bold;font-size:16px'>Please press and hold Ctrl and press F5 to reload this page, if the problem persists please send this error message to your webmaster: </span><div style='color:black;font-size:14px'>'"
|
||||
+ "ErrorId: " + errorId + "<br>URL: " + window.location.href + "<br>UserAgent: " + userAgent + "<br>" + msg + " in " + url + " at line " + linenumber + "'</div></div>");
|
||||
+ "ErrorId: " + errorId + "<br>URL: " + padutils.escapeHtml(window.location.href) + "<br>UserAgent: " + userAgent + "<br>" + msg + " in " + url + " at line " + linenumber + "'</div></div>");
|
||||
}
|
||||
|
||||
//send javascript errors to the server
|
||||
|
|
|
@ -0,0 +1,366 @@
|
|||
/*
|
||||
This file handles scroll on edition or when user presses arrow keys.
|
||||
In this file we have two representations of line (browser and rep line).
|
||||
Rep Line = a line in the way is represented by Etherpad(rep) (each <div> is a line)
|
||||
Browser Line = each vertical line. A <div> can be break into more than one
|
||||
browser line.
|
||||
*/
|
||||
var caretPosition = require('/caretPosition');
|
||||
|
||||
function Scroll(outerWin) {
|
||||
// scroll settings
|
||||
this.scrollSettings = parent.parent.clientVars.scrollWhenFocusLineIsOutOfViewport;
|
||||
|
||||
// DOM reference
|
||||
this.outerWin = outerWin;
|
||||
this.doc = this.outerWin.document;
|
||||
this.rootDocument = parent.parent.document;
|
||||
}
|
||||
|
||||
Scroll.prototype.scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary = function (rep, isScrollableEvent, innerHeight)
|
||||
{
|
||||
// are we placing the caret on the line at the bottom of viewport?
|
||||
// And if so, do we need to scroll the editor, as defined on the settings.json?
|
||||
var shouldScrollWhenCaretIsAtBottomOfViewport = this.scrollSettings.scrollWhenCaretIsInTheLastLineOfViewport;
|
||||
if (shouldScrollWhenCaretIsAtBottomOfViewport) {
|
||||
// avoid scrolling when selection includes multiple lines -- user can potentially be selecting more lines
|
||||
// than it fits on viewport
|
||||
var multipleLinesSelected = rep.selStart[0] !== rep.selEnd[0];
|
||||
|
||||
// avoid scrolling when pad loads
|
||||
if (isScrollableEvent && !multipleLinesSelected && this._isCaretAtTheBottomOfViewport(rep)) {
|
||||
// when scrollWhenFocusLineIsOutOfViewport.percentage is 0, pixelsToScroll is 0
|
||||
var pixelsToScroll = this._getPixelsRelativeToPercentageOfViewport(innerHeight);
|
||||
this._scrollYPage(pixelsToScroll);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scroll.prototype.scrollWhenPressArrowKeys = function(arrowUp, rep, innerHeight)
|
||||
{
|
||||
// if percentageScrollArrowUp is 0, let the scroll to be handled as default, put the previous
|
||||
// rep line on the top of the viewport
|
||||
if(this._arrowUpWasPressedInTheFirstLineOfTheViewport(arrowUp, rep)){
|
||||
var pixelsToScroll = this._getPixelsToScrollWhenUserPressesArrowUp(innerHeight);
|
||||
|
||||
// by default, the browser scrolls to the middle of the viewport. To avoid the twist made
|
||||
// when we apply a second scroll, we made it immediately (without animation)
|
||||
this._scrollYPageWithoutAnimation(-pixelsToScroll);
|
||||
}else{
|
||||
this.scrollNodeVerticallyIntoView(rep, innerHeight);
|
||||
}
|
||||
}
|
||||
|
||||
// Some plugins might set a minimum height to the editor (ex: ep_page_view), so checking
|
||||
// if (caretLine() === rep.lines.length() - 1) is not enough. We need to check if there are
|
||||
// other lines after caretLine(), and all of them are out of viewport.
|
||||
Scroll.prototype._isCaretAtTheBottomOfViewport = function(rep)
|
||||
{
|
||||
// computing a line position using getBoundingClientRect() is expensive.
|
||||
// (obs: getBoundingClientRect() is called on caretPosition.getPosition())
|
||||
// To avoid that, we only call this function when it is possible that the
|
||||
// caret is in the bottom of viewport
|
||||
var caretLine = rep.selStart[0];
|
||||
var lineAfterCaretLine = caretLine + 1;
|
||||
var firstLineVisibleAfterCaretLine = caretPosition.getNextVisibleLine(lineAfterCaretLine, rep);
|
||||
var caretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(caretLine, rep);
|
||||
var lineAfterCaretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(firstLineVisibleAfterCaretLine, rep);
|
||||
if (caretLineIsPartiallyVisibleOnViewport || lineAfterCaretLineIsPartiallyVisibleOnViewport) {
|
||||
// check if the caret is in the bottom of the viewport
|
||||
var caretLinePosition = caretPosition.getPosition();
|
||||
var viewportBottom = this._getViewPortTopBottom().bottom;
|
||||
var nextLineBottom = caretPosition.getBottomOfNextBrowserLine(caretLinePosition, rep);
|
||||
var nextLineIsBelowViewportBottom = nextLineBottom > viewportBottom;
|
||||
return nextLineIsBelowViewportBottom;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Scroll.prototype._isLinePartiallyVisibleOnViewport = function(lineNumber, rep)
|
||||
{
|
||||
var lineNode = rep.lines.atIndex(lineNumber);
|
||||
var linePosition = this._getLineEntryTopBottom(lineNode);
|
||||
var lineTop = linePosition.top;
|
||||
var lineBottom = linePosition.bottom;
|
||||
var viewport = this._getViewPortTopBottom();
|
||||
var viewportBottom = viewport.bottom;
|
||||
var viewportTop = viewport.top;
|
||||
|
||||
var topOfLineIsAboveOfViewportBottom = lineTop < viewportBottom;
|
||||
var bottomOfLineIsOnOrBelowOfViewportBottom = lineBottom >= viewportBottom;
|
||||
var topOfLineIsBelowViewportTop = lineTop >= viewportTop;
|
||||
var topOfLineIsAboveViewportBottom = lineTop <= viewportBottom;
|
||||
var bottomOfLineIsAboveViewportBottom = lineBottom <= viewportBottom;
|
||||
var bottomOfLineIsBelowViewportTop = lineBottom >= viewportTop;
|
||||
|
||||
return (topOfLineIsAboveOfViewportBottom && bottomOfLineIsOnOrBelowOfViewportBottom) ||
|
||||
(topOfLineIsBelowViewportTop && topOfLineIsAboveViewportBottom) ||
|
||||
(bottomOfLineIsAboveViewportBottom && bottomOfLineIsBelowViewportTop);
|
||||
}
|
||||
|
||||
Scroll.prototype._getViewPortTopBottom = function()
|
||||
{
|
||||
var theTop = this.getScrollY();
|
||||
var doc = this.doc;
|
||||
var height = doc.documentElement.clientHeight; // includes padding
|
||||
|
||||
// we have to get the exactly height of the viewport. So it has to subtract all the values which changes
|
||||
// the viewport height (E.g. padding, position top)
|
||||
var viewportExtraSpacesAndPosition = this._getEditorPositionTop() + this._getPaddingTopAddedWhenPageViewIsEnable();
|
||||
return {
|
||||
top: theTop,
|
||||
bottom: (theTop + height - viewportExtraSpacesAndPosition)
|
||||
};
|
||||
}
|
||||
|
||||
Scroll.prototype._getEditorPositionTop = function()
|
||||
{
|
||||
var editor = parent.document.getElementsByTagName('iframe');
|
||||
var editorPositionTop = editor[0].offsetTop;
|
||||
return editorPositionTop;
|
||||
}
|
||||
|
||||
// ep_page_view adds padding-top, which makes the viewport smaller
|
||||
Scroll.prototype._getPaddingTopAddedWhenPageViewIsEnable = function()
|
||||
{
|
||||
var aceOuter = this.rootDocument.getElementsByName("ace_outer");
|
||||
var aceOuterPaddingTop = parseInt($(aceOuter).css("padding-top"));
|
||||
return aceOuterPaddingTop;
|
||||
}
|
||||
|
||||
Scroll.prototype._getScrollXY = function()
|
||||
{
|
||||
var win = this.outerWin;
|
||||
var odoc = this.doc;
|
||||
if (typeof(win.pageYOffset) == "number")
|
||||
{
|
||||
return {
|
||||
x: win.pageXOffset,
|
||||
y: win.pageYOffset
|
||||
};
|
||||
}
|
||||
var docel = odoc.documentElement;
|
||||
if (docel && typeof(docel.scrollTop) == "number")
|
||||
{
|
||||
return {
|
||||
x: docel.scrollLeft,
|
||||
y: docel.scrollTop
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Scroll.prototype.getScrollX = function()
|
||||
{
|
||||
return this._getScrollXY().x;
|
||||
}
|
||||
|
||||
Scroll.prototype.getScrollY = function()
|
||||
{
|
||||
return this._getScrollXY().y;
|
||||
}
|
||||
|
||||
Scroll.prototype.setScrollX = function(x)
|
||||
{
|
||||
this.outerWin.scrollTo(x, this.getScrollY());
|
||||
}
|
||||
|
||||
Scroll.prototype.setScrollY = function(y)
|
||||
{
|
||||
this.outerWin.scrollTo(this.getScrollX(), y);
|
||||
}
|
||||
|
||||
Scroll.prototype.setScrollXY = function(x, y)
|
||||
{
|
||||
this.outerWin.scrollTo(x, y);
|
||||
}
|
||||
|
||||
Scroll.prototype._isCaretAtTheTopOfViewport = function(rep)
|
||||
{
|
||||
var caretLine = rep.selStart[0];
|
||||
var linePrevCaretLine = caretLine - 1;
|
||||
var firstLineVisibleBeforeCaretLine = caretPosition.getPreviousVisibleLine(linePrevCaretLine, rep);
|
||||
var caretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(caretLine, rep);
|
||||
var lineBeforeCaretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(firstLineVisibleBeforeCaretLine, rep);
|
||||
if (caretLineIsPartiallyVisibleOnViewport || lineBeforeCaretLineIsPartiallyVisibleOnViewport) {
|
||||
var caretLinePosition = caretPosition.getPosition(); // get the position of the browser line
|
||||
var viewportPosition = this._getViewPortTopBottom();
|
||||
var viewportTop = viewportPosition.top;
|
||||
var viewportBottom = viewportPosition.bottom;
|
||||
var caretLineIsBelowViewportTop = caretLinePosition.bottom >= viewportTop;
|
||||
var caretLineIsAboveViewportBottom = caretLinePosition.top < viewportBottom;
|
||||
var caretLineIsInsideOfViewport = caretLineIsBelowViewportTop && caretLineIsAboveViewportBottom;
|
||||
if (caretLineIsInsideOfViewport) {
|
||||
var prevLineTop = caretPosition.getPositionTopOfPreviousBrowserLine(caretLinePosition, rep);
|
||||
var previousLineIsAboveViewportTop = prevLineTop < viewportTop;
|
||||
return previousLineIsAboveViewportTop;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// By default, when user makes an edition in a line out of viewport, this line goes
|
||||
// to the edge of viewport. This function gets the extra pixels necessary to get the
|
||||
// caret line in a position X relative to Y% viewport.
|
||||
Scroll.prototype._getPixelsRelativeToPercentageOfViewport = function(innerHeight, aboveOfViewport)
|
||||
{
|
||||
var pixels = 0;
|
||||
var scrollPercentageRelativeToViewport = this._getPercentageToScroll(aboveOfViewport);
|
||||
if(scrollPercentageRelativeToViewport > 0 && scrollPercentageRelativeToViewport <= 1){
|
||||
pixels = parseInt(innerHeight * scrollPercentageRelativeToViewport);
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
// we use different percentages when change selection. It depends on if it is
|
||||
// either above the top or below the bottom of the page
|
||||
Scroll.prototype._getPercentageToScroll = function(aboveOfViewport)
|
||||
{
|
||||
var percentageToScroll = this.scrollSettings.percentage.editionBelowViewport;
|
||||
if(aboveOfViewport){
|
||||
percentageToScroll = this.scrollSettings.percentage.editionAboveViewport;
|
||||
}
|
||||
return percentageToScroll;
|
||||
}
|
||||
|
||||
Scroll.prototype._getPixelsToScrollWhenUserPressesArrowUp = function(innerHeight)
|
||||
{
|
||||
var pixels = 0;
|
||||
var percentageToScrollUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp;
|
||||
if(percentageToScrollUp > 0 && percentageToScrollUp <= 1){
|
||||
pixels = parseInt(innerHeight * percentageToScrollUp);
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
Scroll.prototype._scrollYPage = function(pixelsToScroll)
|
||||
{
|
||||
var durationOfAnimationToShowFocusline = this.scrollSettings.duration;
|
||||
if(durationOfAnimationToShowFocusline){
|
||||
this._scrollYPageWithAnimation(pixelsToScroll, durationOfAnimationToShowFocusline);
|
||||
}else{
|
||||
this._scrollYPageWithoutAnimation(pixelsToScroll);
|
||||
}
|
||||
}
|
||||
|
||||
Scroll.prototype._scrollYPageWithoutAnimation = function(pixelsToScroll)
|
||||
{
|
||||
this.outerWin.scrollBy(0, pixelsToScroll);
|
||||
}
|
||||
|
||||
Scroll.prototype._scrollYPageWithAnimation = function(pixelsToScroll, durationOfAnimationToShowFocusline)
|
||||
{
|
||||
var outerDocBody = this.doc.getElementById("outerdocbody");
|
||||
|
||||
// it works on later versions of Chrome
|
||||
var $outerDocBody = $(outerDocBody);
|
||||
this._triggerScrollWithAnimation($outerDocBody, pixelsToScroll, durationOfAnimationToShowFocusline);
|
||||
|
||||
// it works on Firefox and earlier versions of Chrome
|
||||
var $outerDocBodyParent = $outerDocBody.parent();
|
||||
this._triggerScrollWithAnimation($outerDocBodyParent, pixelsToScroll, durationOfAnimationToShowFocusline);
|
||||
}
|
||||
|
||||
// using a custom queue and clearing it, we avoid creating a queue of scroll animations. So if this function
|
||||
// is called twice quickly, only the last one runs.
|
||||
Scroll.prototype._triggerScrollWithAnimation = function($elem, pixelsToScroll, durationOfAnimationToShowFocusline)
|
||||
{
|
||||
// clear the queue of animation
|
||||
$elem.stop("scrollanimation");
|
||||
$elem.animate({
|
||||
scrollTop: '+=' + pixelsToScroll
|
||||
}, {
|
||||
duration: durationOfAnimationToShowFocusline,
|
||||
queue: "scrollanimation"
|
||||
}).dequeue("scrollanimation");
|
||||
}
|
||||
|
||||
// scrollAmountWhenFocusLineIsOutOfViewport is set to 0 (default), scroll it the minimum distance
|
||||
// needed to be completely in view. If the value is greater than 0 and less than or equal to 1,
|
||||
// besides of scrolling the minimum needed to be visible, it scrolls additionally
|
||||
// (viewport height * scrollAmountWhenFocusLineIsOutOfViewport) pixels
|
||||
Scroll.prototype.scrollNodeVerticallyIntoView = function(rep, innerHeight)
|
||||
{
|
||||
var viewport = this._getViewPortTopBottom();
|
||||
var isPartOfRepLineOutOfViewport = this._partOfRepLineIsOutOfViewport(viewport, rep);
|
||||
|
||||
// when the selection changes outside of the viewport the browser automatically scrolls the line
|
||||
// to inside of the viewport. Tested on IE, Firefox, Chrome in releases from 2015 until now
|
||||
// So, when the line scrolled gets outside of the viewport we let the browser handle it.
|
||||
var linePosition = caretPosition.getPosition();
|
||||
if(linePosition){
|
||||
var distanceOfTopOfViewport = linePosition.top - viewport.top;
|
||||
var distanceOfBottomOfViewport = viewport.bottom - linePosition.bottom;
|
||||
var caretIsAboveOfViewport = distanceOfTopOfViewport < 0;
|
||||
var caretIsBelowOfViewport = distanceOfBottomOfViewport < 0;
|
||||
if(caretIsAboveOfViewport){
|
||||
var pixelsToScroll = distanceOfTopOfViewport - this._getPixelsRelativeToPercentageOfViewport(innerHeight, true);
|
||||
this._scrollYPage(pixelsToScroll);
|
||||
}else if(caretIsBelowOfViewport){
|
||||
var pixelsToScroll = -distanceOfBottomOfViewport + this._getPixelsRelativeToPercentageOfViewport(innerHeight);
|
||||
this._scrollYPage(pixelsToScroll);
|
||||
}else{
|
||||
this.scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary(rep, true, innerHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scroll.prototype._partOfRepLineIsOutOfViewport = function(viewportPosition, rep)
|
||||
{
|
||||
var focusLine = (rep.selFocusAtStart ? rep.selStart[0] : rep.selEnd[0]);
|
||||
var line = rep.lines.atIndex(focusLine);
|
||||
var linePosition = this._getLineEntryTopBottom(line);
|
||||
var lineIsAboveOfViewport = linePosition.top < viewportPosition.top;
|
||||
var lineIsBelowOfViewport = linePosition.bottom > viewportPosition.bottom;
|
||||
|
||||
return lineIsBelowOfViewport || lineIsAboveOfViewport;
|
||||
}
|
||||
|
||||
Scroll.prototype._getLineEntryTopBottom = function(entry, destObj)
|
||||
{
|
||||
var dom = entry.lineNode;
|
||||
var top = dom.offsetTop;
|
||||
var height = dom.offsetHeight;
|
||||
var obj = (destObj || {});
|
||||
obj.top = top;
|
||||
obj.bottom = (top + height);
|
||||
return obj;
|
||||
}
|
||||
|
||||
Scroll.prototype._arrowUpWasPressedInTheFirstLineOfTheViewport = function(arrowUp, rep)
|
||||
{
|
||||
var percentageScrollArrowUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp;
|
||||
return percentageScrollArrowUp && arrowUp && this._isCaretAtTheTopOfViewport(rep);
|
||||
}
|
||||
|
||||
Scroll.prototype.getVisibleLineRange = function(rep)
|
||||
{
|
||||
var viewport = this._getViewPortTopBottom();
|
||||
//console.log("viewport top/bottom: %o", viewport);
|
||||
var obj = {};
|
||||
var self = this;
|
||||
var start = rep.lines.search(function(e)
|
||||
{
|
||||
return self._getLineEntryTopBottom(e, obj).bottom > viewport.top;
|
||||
});
|
||||
var end = rep.lines.search(function(e)
|
||||
{
|
||||
// return the first line that the top position is greater or equal than
|
||||
// the viewport. That is the first line that is below the viewport bottom.
|
||||
// So the line that is in the bottom of the viewport is the very previous one.
|
||||
return self._getLineEntryTopBottom(e, obj).top >= viewport.bottom;
|
||||
});
|
||||
if (end < start) end = start; // unlikely
|
||||
// top.console.log(start+","+(end -1));
|
||||
return [start, end - 1];
|
||||
}
|
||||
|
||||
Scroll.prototype.getVisibleCharRange = function(rep)
|
||||
{
|
||||
var lineRange = this.getVisibleLineRange(rep);
|
||||
return [rep.lines.offsetOfIndex(lineRange[0]), rep.lines.offsetOfIndex(lineRange[1])];
|
||||
}
|
||||
|
||||
exports.init = function(outerWin)
|
||||
{
|
||||
return new Scroll(outerWin);
|
||||
}
|
|
@ -0,0 +1,649 @@
|
|||
describe('scroll when focus line is out of viewport', function () {
|
||||
before(function (done) {
|
||||
helper.newPad(function(){
|
||||
cleanPad(function(){
|
||||
forceUseMonospacedFont();
|
||||
scrollWhenPlaceCaretInTheLastLineOfViewport();
|
||||
createPadWithSeveralLines(function(){
|
||||
resizeEditorHeight();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
this.timeout(20000);
|
||||
});
|
||||
|
||||
context('when user presses any arrow keys on a line above the viewport', function(){
|
||||
context('and scroll percentage config is set to 0.2 on settings.json', function(){
|
||||
var lineCloseOfTopOfPad = 10;
|
||||
before(function (done) {
|
||||
setScrollPercentageWhenFocusLineIsOutOfViewport(0.2, true);
|
||||
scrollEditorToBottomOfPad();
|
||||
|
||||
placeCaretInTheBeginningOfLine(lineCloseOfTopOfPad, function(){ // place caret in the 10th line
|
||||
// warning: even pressing right arrow, the caret does not change of position
|
||||
// the column where the caret is, it has not importance, only the line
|
||||
pressAndReleaseRightArrow();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps the focus line scrolled 20% from the top of the viewport', function (done) {
|
||||
// default behavior is to put the line in the top of viewport, but as
|
||||
// scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.2, we have an extra 20% of lines scrolled
|
||||
// (2 lines, which are the 20% of the 10 that are visible on viewport)
|
||||
var firstLineOfViewport = getFirstLineVisibileOfViewport();
|
||||
expect(lineCloseOfTopOfPad).to.be(firstLineOfViewport + 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('when user presses any arrow keys on a line below the viewport', function(){
|
||||
context('and scroll percentage config is set to 0.7 on settings.json', function(){
|
||||
var lineCloseToBottomOfPad = 50;
|
||||
before(function (done) {
|
||||
setScrollPercentageWhenFocusLineIsOutOfViewport(0.7);
|
||||
|
||||
// firstly, scroll to make the lineCloseToBottomOfPad visible. After that, scroll to make it out of viewport
|
||||
scrollEditorToTopOfPad();
|
||||
placeCaretAtTheEndOfLine(lineCloseToBottomOfPad); // place caret in the 50th line
|
||||
setTimeout(function() {
|
||||
// warning: even pressing right arrow, the caret does not change of position
|
||||
pressAndReleaseLeftArrow();
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
it('keeps the focus line scrolled 70% from the bottom of the viewport', function (done) {
|
||||
// default behavior is to put the line in the top of viewport, but as
|
||||
// scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.7, we have an extra 70% of lines scrolled
|
||||
// (7 lines, which are the 70% of the 10 that are visible on viewport)
|
||||
var lastLineOfViewport = getLastLineVisibleOfViewport();
|
||||
expect(lineCloseToBottomOfPad).to.be(lastLineOfViewport - 7);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('when user presses arrow up on the first line of the viewport', function(){
|
||||
context('and percentageToScrollWhenUserPressesArrowUp is set to 0.3', function () {
|
||||
var lineOnTopOfViewportWhenThePadIsScrolledDown;
|
||||
before(function (done) {
|
||||
setPercentageToScrollWhenUserPressesArrowUp(0.3);
|
||||
|
||||
// we need some room to make the scroll up
|
||||
scrollEditorToBottomOfPad();
|
||||
lineOnTopOfViewportWhenThePadIsScrolledDown = 91;
|
||||
placeCaretAtTheEndOfLine(lineOnTopOfViewportWhenThePadIsScrolledDown);
|
||||
setTimeout(function() {
|
||||
// warning: even pressing up arrow, the caret does not change of position
|
||||
pressAndReleaseUpArrow();
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
it('keeps the focus line scrolled 30% of the top of the viewport', function (done) {
|
||||
// default behavior is to put the line in the top of viewport, but as
|
||||
// PercentageToScrollWhenUserPressesArrowUp is set to 0.3, we have an extra 30% of lines scrolled
|
||||
// (3 lines, which are the 30% of the 10 that are visible on viewport)
|
||||
var firstLineOfViewport = getFirstLineVisibileOfViewport();
|
||||
expect(firstLineOfViewport).to.be(lineOnTopOfViewportWhenThePadIsScrolledDown - 3);
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
context('when user edits the last line of viewport', function(){
|
||||
context('and scroll percentage config is set to 0 on settings.json', function(){
|
||||
var lastLineOfViewportBeforeEnter = 10;
|
||||
before(function () {
|
||||
// the default value
|
||||
resetScrollPercentageWhenFocusLineIsOutOfViewport();
|
||||
|
||||
// make sure the last line on viewport is the 10th one
|
||||
scrollEditorToTopOfPad();
|
||||
placeCaretAtTheEndOfLine(lastLineOfViewportBeforeEnter);
|
||||
pressEnter();
|
||||
});
|
||||
|
||||
it('keeps the focus line on the bottom of the viewport', function (done) {
|
||||
var lastLineOfViewportAfterEnter = getLastLineVisibleOfViewport();
|
||||
expect(lastLineOfViewportAfterEnter).to.be(lastLineOfViewportBeforeEnter + 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
context('and scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.3', function(){ // this value is arbitrary
|
||||
var lastLineOfViewportBeforeEnter = 9;
|
||||
before(function () {
|
||||
setScrollPercentageWhenFocusLineIsOutOfViewport(0.3);
|
||||
|
||||
// make sure the last line on viewport is the 10th one
|
||||
scrollEditorToTopOfPad();
|
||||
placeCaretAtTheEndOfLine(lastLineOfViewportBeforeEnter);
|
||||
pressBackspace();
|
||||
});
|
||||
|
||||
it('scrolls 30% of viewport up', function (done) {
|
||||
var lastLineOfViewportAfterEnter = getLastLineVisibleOfViewport();
|
||||
// default behavior is to scroll one line at the bottom of viewport, but as
|
||||
// scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.3, we have an extra 30% of lines scrolled
|
||||
// (3 lines, which are the 30% of the 10 that are visible on viewport)
|
||||
expect(lastLineOfViewportAfterEnter).to.be(lastLineOfViewportBeforeEnter + 3);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
context('and it is set to a value that overflow the interval [0, 1]', function(){
|
||||
var lastLineOfViewportBeforeEnter = 10;
|
||||
before(function(){
|
||||
var scrollPercentageWhenFocusLineIsOutOfViewport = 1.5;
|
||||
scrollEditorToTopOfPad();
|
||||
placeCaretAtTheEndOfLine(lastLineOfViewportBeforeEnter);
|
||||
setScrollPercentageWhenFocusLineIsOutOfViewport(scrollPercentageWhenFocusLineIsOutOfViewport);
|
||||
pressEnter();
|
||||
});
|
||||
|
||||
it('keeps the default behavior of moving the focus line on the bottom of the viewport', function (done) {
|
||||
var lastLineOfViewportAfterEnter = getLastLineVisibleOfViewport();
|
||||
expect(lastLineOfViewportAfterEnter).to.be(lastLineOfViewportBeforeEnter + 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('when user edits a line above the viewport', function(){
|
||||
context('and scroll percentage config is set to 0 on settings.json', function(){
|
||||
var lineCloseOfTopOfPad = 10;
|
||||
before(function () {
|
||||
// the default value
|
||||
setScrollPercentageWhenFocusLineIsOutOfViewport(0);
|
||||
|
||||
// firstly, scroll to make the lineCloseOfTopOfPad visible. After that, scroll to make it out of viewport
|
||||
scrollEditorToTopOfPad();
|
||||
placeCaretAtTheEndOfLine(lineCloseOfTopOfPad); // place caret in the 10th line
|
||||
scrollEditorToBottomOfPad();
|
||||
pressBackspace(); // edit the line where the caret is, which is above the viewport
|
||||
});
|
||||
|
||||
it('keeps the focus line on the top of the viewport', function (done) {
|
||||
var firstLineOfViewportAfterEnter = getFirstLineVisibileOfViewport();
|
||||
expect(firstLineOfViewportAfterEnter).to.be(lineCloseOfTopOfPad);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
context('and scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.2', function(){ // this value is arbitrary
|
||||
var lineCloseToBottomOfPad = 50;
|
||||
before(function () {
|
||||
// we force the line edited to be above the top of the viewport
|
||||
setScrollPercentageWhenFocusLineIsOutOfViewport(0.2, true); // set scroll jump to 20%
|
||||
scrollEditorToTopOfPad();
|
||||
placeCaretAtTheEndOfLine(lineCloseToBottomOfPad);
|
||||
scrollEditorToBottomOfPad();
|
||||
pressBackspace(); // edit line
|
||||
});
|
||||
|
||||
it('scrolls 20% of viewport down', function (done) {
|
||||
// default behavior is to scroll one line at the top of viewport, but as
|
||||
// scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.2, we have an extra 20% of lines scrolled
|
||||
// (2 lines, which are the 20% of the 10 that are visible on viewport)
|
||||
var firstLineVisibileOfViewport = getFirstLineVisibileOfViewport();
|
||||
expect(lineCloseToBottomOfPad).to.be(firstLineVisibileOfViewport + 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('when user places the caret at the last line visible of viewport', function(){
|
||||
var lastLineVisible;
|
||||
context('and scroll percentage config is set to 0 on settings.json', function(){
|
||||
before(function (done) {
|
||||
// reset to the default value
|
||||
resetScrollPercentageWhenFocusLineIsOutOfViewport();
|
||||
|
||||
placeCaretInTheBeginningOfLine(0, function(){ // reset caret position
|
||||
scrollEditorToTopOfPad();
|
||||
lastLineVisible = getLastLineVisibleOfViewport();
|
||||
placeCaretInTheBeginningOfLine(lastLineVisible, done); // place caret in the 9th line
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('does not scroll', function(done){
|
||||
setTimeout(function() {
|
||||
var lastLineOfViewport = getLastLineVisibleOfViewport();
|
||||
var lineDoesNotScroll = lastLineOfViewport === lastLineVisible;
|
||||
expect(lineDoesNotScroll).to.be(true);
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
context('and scroll percentage config is set to 0.5 on settings.json', function(){
|
||||
before(function (done) {
|
||||
setScrollPercentageWhenFocusLineIsOutOfViewport(0.5);
|
||||
scrollEditorToTopOfPad();
|
||||
placeCaretInTheBeginningOfLine(0, function(){ // reset caret position
|
||||
// this timeout inside a callback is ugly but it necessary to give time to aceSelectionChange
|
||||
// realizes that the selection has been changed
|
||||
setTimeout(function() {
|
||||
lastLineVisible = getLastLineVisibleOfViewport();
|
||||
placeCaretInTheBeginningOfLine(lastLineVisible, done); // place caret in the 9th line
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
it('scrolls line to 50% of the viewport', function(done){
|
||||
helper.waitFor(function(){
|
||||
var lastLineOfViewport = getLastLineVisibleOfViewport();
|
||||
var lastLinesScrolledFiveLinesUp = lastLineOfViewport - 5 === lastLineVisible;
|
||||
return lastLinesScrolledFiveLinesUp;
|
||||
}).done(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// This is a special case. When user is selecting a text with arrow down or arrow left we have
|
||||
// to keep the last line selected on focus
|
||||
context('when the first line selected is out of the viewport and user presses shift arrow down', function(){
|
||||
var lastLineOfPad = 99;
|
||||
before(function (done) {
|
||||
scrollEditorToTopOfPad();
|
||||
|
||||
// make a selection bigger than the viewport height
|
||||
var $firstLineOfSelection = getLine(0);
|
||||
var $lastLineOfSelection = getLine(lastLineOfPad);
|
||||
var lengthOfLastLine = $lastLineOfSelection.text().length;
|
||||
helper.selectLines($firstLineOfSelection, $lastLineOfSelection, 0, lengthOfLastLine);
|
||||
|
||||
// place the last line selected on the viewport
|
||||
scrollEditorToBottomOfPad();
|
||||
|
||||
// press a key to make the selection goes down
|
||||
// although we can't simulate the extending of selection. It's possible to send a key event
|
||||
// which is captured on ace2_inner scroll function.
|
||||
pressAndReleaseLeftArrow(true);
|
||||
done();
|
||||
});
|
||||
|
||||
it('keeps the last line selected on focus', function (done) {
|
||||
var lastLineOfSelectionIsVisible = isLineOnViewport(lastLineOfPad);
|
||||
expect(lastLineOfSelectionIsVisible).to.be(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// In this scenario we avoid the bouncing scroll. E.g Let's suppose we have a big line that is
|
||||
// the size of the viewport, and its top is above the viewport. When user presses '<-', this line
|
||||
// will scroll down because the top is out of the viewport. When it scrolls down, the bottom of
|
||||
// line gets below the viewport so when user presses '<-' again it scrolls up to make the bottom
|
||||
// of line visible. If user presses arrow keys more than one time, the editor will keep scrolling up and down
|
||||
context('when the line height is bigger than the scroll amount percentage * viewport height', function(){
|
||||
var scrollOfEditorBeforePressKey;
|
||||
var BIG_LINE_NUMBER = 0;
|
||||
var MIDDLE_OF_BIG_LINE = 51;
|
||||
before(function (done) {
|
||||
createPadWithALineHigherThanViewportHeight(this, BIG_LINE_NUMBER, function(){
|
||||
setScrollPercentageWhenFocusLineIsOutOfViewport(0.5); // set any value to force scroll to outside to viewport
|
||||
var $bigLine = getLine(BIG_LINE_NUMBER);
|
||||
|
||||
// each line has about 5 chars, we place the caret in the middle of the line
|
||||
helper.selectLines($bigLine, $bigLine, MIDDLE_OF_BIG_LINE, MIDDLE_OF_BIG_LINE);
|
||||
|
||||
scrollEditorToLeaveTopAndBottomOfBigLineOutOfViewport($bigLine);
|
||||
scrollOfEditorBeforePressKey = getEditorScroll();
|
||||
|
||||
// press a key to force to scroll
|
||||
pressAndReleaseRightArrow();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// reset pad to the original text
|
||||
after(function (done) {
|
||||
this.timeout(5000);
|
||||
cleanPad(function(){
|
||||
createPadWithSeveralLines(function(){
|
||||
resetEditorWidth();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// as the editor.line is inside of the viewport, it should not scroll
|
||||
it('should not scroll', function (done) {
|
||||
var scrollOfEditorAfterPressKey = getEditorScroll();
|
||||
expect(scrollOfEditorAfterPressKey).to.be(scrollOfEditorBeforePressKey);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// Some plugins, for example the ep_page_view, change the editor dimensions. This plugin, for example,
|
||||
// adds padding-top to the ace_outer, which changes the viewport height
|
||||
describe('integration with plugins which changes the margin of editor', function(){
|
||||
context('when editor dimensions changes', function(){
|
||||
before(function () {
|
||||
// reset the size of editor. Now we show more than 10 lines as in the other tests
|
||||
resetResizeOfEditorHeight();
|
||||
scrollEditorToTopOfPad();
|
||||
|
||||
// height of the editor viewport
|
||||
var editorHeight = getEditorHeight();
|
||||
|
||||
// add a big padding-top, 50% of the viewport
|
||||
var paddingTopOfAceOuter = editorHeight/2;
|
||||
var chrome$ = helper.padChrome$;
|
||||
var $outerIframe = chrome$('iframe');
|
||||
$outerIframe.css('padding-top', paddingTopOfAceOuter);
|
||||
|
||||
// we set a big value to check if the scroll is made
|
||||
setScrollPercentageWhenFocusLineIsOutOfViewport(1);
|
||||
});
|
||||
|
||||
context('and user places the caret in the last line visible of the pad', function(){
|
||||
var lastLineVisible;
|
||||
beforeEach(function (done) {
|
||||
lastLineVisible = getLastLineVisibleOfViewport();
|
||||
placeCaretInTheBeginningOfLine(lastLineVisible, done);
|
||||
});
|
||||
|
||||
it('scrolls the line where caret is', function(done){
|
||||
helper.waitFor(function(){
|
||||
var firstLineVisibileOfViewport = getFirstLineVisibileOfViewport();
|
||||
var linesScrolled = firstLineVisibileOfViewport !== 0;
|
||||
return linesScrolled;
|
||||
}).done(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* ********************* Helper functions/constants ********************* */
|
||||
var TOP_OF_PAGE = 0;
|
||||
var BOTTOM_OF_PAGE = 5000; // we use a big value to force the page to be scrolled all the way down
|
||||
var LINES_OF_PAD = 100;
|
||||
var ENTER = 13;
|
||||
var BACKSPACE = 8;
|
||||
var LEFT_ARROW = 37;
|
||||
var UP_ARROW = 38;
|
||||
var RIGHT_ARROW = 39;
|
||||
var LINES_ON_VIEWPORT = 10;
|
||||
var WIDTH_OF_EDITOR_RESIZED = 100;
|
||||
var LONG_TEXT_CHARS = 100;
|
||||
|
||||
var cleanPad = function(callback) {
|
||||
var inner$ = helper.padInner$;
|
||||
var $padContent = inner$('#innerdocbody');
|
||||
$padContent.html('');
|
||||
|
||||
// wait for Etherpad to re-create first line
|
||||
helper.waitFor(function(){
|
||||
var lineNumber = inner$('div').length;
|
||||
return lineNumber === 1;
|
||||
}, 2000).done(callback);
|
||||
};
|
||||
|
||||
var createPadWithSeveralLines = function(done) {
|
||||
var line = '<span>a</span><br>';
|
||||
var $firstLine = helper.padInner$('div').first();
|
||||
var lines = line.repeat(LINES_OF_PAD); //arbitrary number, we need to create lines that is over the viewport
|
||||
$firstLine.html(lines);
|
||||
|
||||
helper.waitFor(function(){
|
||||
var linesCreated = helper.padInner$('div').length;
|
||||
return linesCreated === LINES_OF_PAD;
|
||||
}, 4000).done(done);
|
||||
};
|
||||
|
||||
var createPadWithALineHigherThanViewportHeight = function(test, line, done) {
|
||||
var viewportHeight = 160; //10 lines * 16px (height of line)
|
||||
test.timeout(5000);
|
||||
cleanPad(function(){
|
||||
// make the editor smaller to make test easier
|
||||
// with that width the each line has about 5 chars
|
||||
resizeEditorWidth();
|
||||
|
||||
// we create a line with 100 chars, which makes about 20 lines
|
||||
setLongTextOnLine(line);
|
||||
helper.waitFor(function () {
|
||||
var $firstLine = getLine(line);
|
||||
|
||||
var heightOfLine = $firstLine.get(0).getBoundingClientRect().height;
|
||||
return heightOfLine >= viewportHeight;
|
||||
}, 4000).done(done);
|
||||
});
|
||||
};
|
||||
|
||||
var setLongTextOnLine = function(line) {
|
||||
var $line = getLine(line);
|
||||
var longText = 'a'.repeat(LONG_TEXT_CHARS);
|
||||
$line.html(longText);
|
||||
};
|
||||
|
||||
// resize the editor to make the tests easier
|
||||
var resizeEditorHeight = function() {
|
||||
var chrome$ = helper.padChrome$;
|
||||
chrome$('#editorcontainer').css('height', getSizeOfViewport());
|
||||
};
|
||||
|
||||
// this makes about 5 chars per line
|
||||
var resizeEditorWidth = function() {
|
||||
var chrome$ = helper.padChrome$;
|
||||
chrome$('#editorcontainer').css('width', WIDTH_OF_EDITOR_RESIZED);
|
||||
};
|
||||
|
||||
var resetResizeOfEditorHeight = function() {
|
||||
var chrome$ = helper.padChrome$;
|
||||
chrome$('#editorcontainer').css('height', '');
|
||||
};
|
||||
|
||||
var resetEditorWidth = function () {
|
||||
var chrome$ = helper.padChrome$;
|
||||
chrome$('#editorcontainer').css('width', '');
|
||||
};
|
||||
|
||||
var getEditorHeight = function() {
|
||||
var chrome$ = helper.padChrome$;
|
||||
var $editor = chrome$('#editorcontainer');
|
||||
var editorHeight = $editor.get(0).clientHeight;
|
||||
return editorHeight;
|
||||
};
|
||||
|
||||
var getSizeOfViewport = function() {
|
||||
return getLinePositionOnViewport(LINES_ON_VIEWPORT) - getLinePositionOnViewport(0);
|
||||
};
|
||||
|
||||
var scrollPageTo = function(value) {
|
||||
var outer$ = helper.padOuter$;
|
||||
var $ace_outer = outer$('#outerdocbody').parent();
|
||||
$ace_outer.parent().scrollTop(value);
|
||||
};
|
||||
|
||||
var scrollEditorToTopOfPad = function() {
|
||||
scrollPageTo(TOP_OF_PAGE);
|
||||
};
|
||||
|
||||
var scrollEditorToBottomOfPad = function() {
|
||||
scrollPageTo(BOTTOM_OF_PAGE);
|
||||
};
|
||||
|
||||
var scrollEditorToLeaveTopAndBottomOfBigLineOutOfViewport = function ($bigLine) {
|
||||
var lineHeight = $bigLine.get(0).getBoundingClientRect().height;
|
||||
var middleOfLine = lineHeight/2;
|
||||
scrollPageTo(middleOfLine);
|
||||
};
|
||||
|
||||
var getLine = function(lineNum) {
|
||||
var inner$ = helper.padInner$;
|
||||
var $line = inner$('div').eq(lineNum);
|
||||
return $line;
|
||||
};
|
||||
|
||||
var placeCaretAtTheEndOfLine = function(lineNum) {
|
||||
var $targetLine = getLine(lineNum);
|
||||
var lineLength = $targetLine.text().length;
|
||||
helper.selectLines($targetLine, $targetLine, lineLength, lineLength);
|
||||
};
|
||||
|
||||
var placeCaretInTheBeginningOfLine = function(lineNum, cb) {
|
||||
var $targetLine = getLine(lineNum);
|
||||
helper.selectLines($targetLine, $targetLine, 0, 0);
|
||||
helper.waitFor(function() {
|
||||
var $lineWhereCaretIs = getLineWhereCaretIs();
|
||||
return $targetLine.get(0) === $lineWhereCaretIs.get(0);
|
||||
}).done(cb);
|
||||
};
|
||||
|
||||
var getLineWhereCaretIs = function() {
|
||||
var inner$ = helper.padInner$;
|
||||
var nodeWhereCaretIs = inner$.document.getSelection().anchorNode;
|
||||
var $lineWhereCaretIs = $(nodeWhereCaretIs).closest('div');
|
||||
return $lineWhereCaretIs;
|
||||
};
|
||||
|
||||
var getFirstLineVisibileOfViewport = function() {
|
||||
return _.find(_.range(0, LINES_OF_PAD - 1), isLineOnViewport);
|
||||
};
|
||||
|
||||
var getLastLineVisibleOfViewport = function() {
|
||||
return _.find(_.range(LINES_OF_PAD - 1, 0, -1), isLineOnViewport);
|
||||
};
|
||||
|
||||
var pressKey = function(keyCode, shiftIsPressed){
|
||||
var inner$ = helper.padInner$;
|
||||
var evtType;
|
||||
if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE
|
||||
evtType = 'keypress';
|
||||
}else{
|
||||
evtType = 'keydown';
|
||||
}
|
||||
var e = inner$.Event(evtType);
|
||||
e.shiftKey = shiftIsPressed;
|
||||
e.keyCode = keyCode;
|
||||
e.which = keyCode; // etherpad listens to 'which'
|
||||
inner$('#innerdocbody').trigger(e);
|
||||
};
|
||||
|
||||
var releaseKey = function(keyCode){
|
||||
var inner$ = helper.padInner$;
|
||||
var evtType = 'keyup';
|
||||
var e = inner$.Event(evtType);
|
||||
e.keyCode = keyCode;
|
||||
e.which = keyCode; // etherpad listens to 'which'
|
||||
inner$('#innerdocbody').trigger(e);
|
||||
};
|
||||
|
||||
var pressEnter = function() {
|
||||
pressKey(ENTER);
|
||||
};
|
||||
|
||||
var pressBackspace = function() {
|
||||
pressKey(BACKSPACE);
|
||||
};
|
||||
|
||||
var pressAndReleaseUpArrow = function() {
|
||||
pressKey(UP_ARROW);
|
||||
releaseKey(UP_ARROW);
|
||||
};
|
||||
|
||||
var pressAndReleaseRightArrow = function() {
|
||||
pressKey(RIGHT_ARROW);
|
||||
releaseKey(RIGHT_ARROW);
|
||||
};
|
||||
|
||||
var pressAndReleaseLeftArrow = function(shiftIsPressed) {
|
||||
pressKey(LEFT_ARROW, shiftIsPressed);
|
||||
releaseKey(LEFT_ARROW);
|
||||
};
|
||||
|
||||
var isLineOnViewport = function(lineNumber) {
|
||||
// in the function scrollNodeVerticallyIntoView from ace2_inner.js, iframePadTop is used to calculate
|
||||
// how much scroll is needed. Although the name refers to padding-top, this value is not set on the
|
||||
// padding-top.
|
||||
var iframePadTop = 8;
|
||||
var $line = getLine(lineNumber);
|
||||
var linePosition = $line.get(0).getBoundingClientRect();
|
||||
|
||||
// position relative to the current viewport
|
||||
var linePositionTopOnViewport = linePosition.top - getEditorScroll() + iframePadTop;
|
||||
var linePositionBottomOnViewport = linePosition.bottom - getEditorScroll();
|
||||
|
||||
var lineBellowTop = linePositionBottomOnViewport > 0;
|
||||
var lineAboveBottom = linePositionTopOnViewport < getClientHeightVisible();
|
||||
var isVisible = lineBellowTop && lineAboveBottom;
|
||||
|
||||
return isVisible;
|
||||
};
|
||||
|
||||
var getEditorScroll = function () {
|
||||
var outer$ = helper.padOuter$;
|
||||
var scrollTopFirefox = outer$('#outerdocbody').parent().scrollTop(); // works only on firefox
|
||||
var scrollTop = outer$('#outerdocbody').scrollTop() || scrollTopFirefox;
|
||||
return scrollTop;
|
||||
};
|
||||
|
||||
// clientHeight includes padding, so we have to subtract it and consider only the visible viewport
|
||||
var getClientHeightVisible = function () {
|
||||
var outer$ = helper.padOuter$;
|
||||
var $ace_outer = outer$('#outerdocbody').parent();
|
||||
var ace_outerHeight = $ace_outer.get(0).clientHeight;
|
||||
var ace_outerPaddingTop = getIntValueOfCSSProperty($ace_outer, 'padding-top');
|
||||
var paddingAddedWhenPageViewIsEnable = getPaddingAddedWhenPageViewIsEnable();
|
||||
var clientHeight = ace_outerHeight - ( ace_outerPaddingTop + paddingAddedWhenPageViewIsEnable);
|
||||
|
||||
return clientHeight;
|
||||
};
|
||||
|
||||
// ep_page_view changes the dimensions of the editor. We have to guarantee
|
||||
// the viewport height is calculated right
|
||||
var getPaddingAddedWhenPageViewIsEnable = function () {
|
||||
var chrome$ = helper.padChrome$;
|
||||
var $outerIframe = chrome$('iframe');
|
||||
var paddingAddedWhenPageViewIsEnable = parseInt($outerIframe.css('padding-top'));
|
||||
return paddingAddedWhenPageViewIsEnable;
|
||||
};
|
||||
|
||||
var getIntValueOfCSSProperty = function($element, property){
|
||||
var valueString = $element.css(property);
|
||||
return parseInt(valueString) || 0;
|
||||
};
|
||||
|
||||
var forceUseMonospacedFont = function () {
|
||||
helper.padChrome$.window.clientVars.padOptions.useMonospaceFont = true;
|
||||
};
|
||||
|
||||
var setScrollPercentageWhenFocusLineIsOutOfViewport = function(value, editionAboveViewport) {
|
||||
var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport;
|
||||
if (editionAboveViewport) {
|
||||
scrollSettings.percentage.editionAboveViewport = value;
|
||||
}else{
|
||||
scrollSettings.percentage.editionBelowViewport = value;
|
||||
}
|
||||
};
|
||||
|
||||
var resetScrollPercentageWhenFocusLineIsOutOfViewport = function() {
|
||||
var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport;
|
||||
scrollSettings.percentage.editionAboveViewport = 0;
|
||||
scrollSettings.percentage.editionBelowViewport = 0;
|
||||
};
|
||||
|
||||
var setPercentageToScrollWhenUserPressesArrowUp = function (value) {
|
||||
var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport;
|
||||
scrollSettings.percentageToScrollWhenUserPressesArrowUp = value;
|
||||
};
|
||||
|
||||
var scrollWhenPlaceCaretInTheLastLineOfViewport = function() {
|
||||
var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport;
|
||||
scrollSettings.scrollWhenCaretIsInTheLastLineOfViewport = true;
|
||||
};
|
||||
|
||||
var getLinePositionOnViewport = function(lineNumber) {
|
||||
var $line = getLine(lineNumber);
|
||||
var linePosition = $line.get(0).getBoundingClientRect();
|
||||
|
||||
// position relative to the current viewport
|
||||
return linePosition.top - getEditorScroll();
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
describe("select formatting buttons when selection has style applied", function(){
|
||||
var STYLES = ['italic', 'bold', 'underline', 'strikethrough'];
|
||||
var SHORTCUT_KEYS = ['I', 'B', 'U', '5']; // italic, bold, underline, strikethrough
|
||||
var FIRST_LINE = 0;
|
||||
|
||||
before(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
var applyStyleOnLine = function(style, line) {
|
||||
var chrome$ = helper.padChrome$;
|
||||
selectLine(line);
|
||||
var $formattingButton = chrome$('.buttonicon-' + style);
|
||||
$formattingButton.click();
|
||||
}
|
||||
|
||||
var isButtonSelected = function(style) {
|
||||
var chrome$ = helper.padChrome$;
|
||||
var $formattingButton = chrome$('.buttonicon-' + style);
|
||||
return $formattingButton.parent().hasClass('selected');
|
||||
}
|
||||
|
||||
var selectLine = function(lineNumber, offsetStart, offsetEnd) {
|
||||
var inner$ = helper.padInner$;
|
||||
var $line = inner$("div").eq(lineNumber);
|
||||
helper.selectLines($line, $line, offsetStart, offsetEnd);
|
||||
}
|
||||
|
||||
var placeCaretOnLine = function(lineNumber) {
|
||||
var inner$ = helper.padInner$;
|
||||
var $line = inner$("div").eq(lineNumber);
|
||||
$line.sendkeys('{leftarrow}');
|
||||
}
|
||||
|
||||
var undo = function() {
|
||||
var $undoButton = helper.padChrome$(".buttonicon-undo");
|
||||
$undoButton.click();
|
||||
}
|
||||
|
||||
var testIfFormattingButtonIsDeselected = function(style) {
|
||||
it('deselects the ' + style + ' button', function(done) {
|
||||
helper.waitFor(function(){
|
||||
return isButtonSelected(style) === false;
|
||||
}).done(done)
|
||||
});
|
||||
}
|
||||
|
||||
var testIfFormattingButtonIsSelected = function(style) {
|
||||
it('selects the ' + style + ' button', function(done) {
|
||||
helper.waitFor(function(){
|
||||
return isButtonSelected(style);
|
||||
}).done(done)
|
||||
});
|
||||
}
|
||||
|
||||
var applyStyleOnLineAndSelectIt = function(line, style, cb) {
|
||||
applyStyleOnLineOnFullLineAndRemoveSelection(line, style, selectLine, cb);
|
||||
}
|
||||
|
||||
var applyStyleOnLineAndPlaceCaretOnit = function(line, style, cb) {
|
||||
applyStyleOnLineOnFullLineAndRemoveSelection(line, style, placeCaretOnLine, cb);
|
||||
}
|
||||
|
||||
var applyStyleOnLineOnFullLineAndRemoveSelection = function(line, style, selectTarget, cb) {
|
||||
applyStyleOnLine(style, line);
|
||||
|
||||
// we have to give some time to Etherpad detects the selection changed
|
||||
setTimeout(function() {
|
||||
// remove selection from previous line
|
||||
selectLine(line + 1);
|
||||
setTimeout(function() {
|
||||
// select the text or place the caret on a position that
|
||||
// has the formatting text applied previously
|
||||
selectTarget(line);
|
||||
cb();
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
var pressFormattingShortcutOnSelection = function(key) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var $firstTextElement = inner$("div").first();
|
||||
|
||||
//select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
|
||||
if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE
|
||||
var evtType = "keypress";
|
||||
}else{
|
||||
var evtType = "keydown";
|
||||
}
|
||||
|
||||
var e = inner$.Event(evtType);
|
||||
e.ctrlKey = true; // Control key
|
||||
e.which = key.charCodeAt(0); // I, U, B, 5
|
||||
inner$("#innerdocbody").trigger(e);
|
||||
}
|
||||
|
||||
STYLES.forEach(function(style){
|
||||
context('when selection is in a text with ' + style + ' applied', function(){
|
||||
before(function (done) {
|
||||
this.timeout(4000);
|
||||
applyStyleOnLineAndSelectIt(FIRST_LINE, style, done);
|
||||
});
|
||||
|
||||
after(function () {
|
||||
undo();
|
||||
});
|
||||
|
||||
testIfFormattingButtonIsSelected(style);
|
||||
});
|
||||
|
||||
context('when caret is in a position with ' + style + ' applied', function(){
|
||||
before(function (done) {
|
||||
this.timeout(4000);
|
||||
applyStyleOnLineAndPlaceCaretOnit(FIRST_LINE, style, done);
|
||||
});
|
||||
|
||||
after(function () {
|
||||
undo();
|
||||
});
|
||||
|
||||
testIfFormattingButtonIsSelected(style)
|
||||
});
|
||||
});
|
||||
|
||||
context('when user applies a style and the selection does not change', function() {
|
||||
var style = STYLES[0]; // italic
|
||||
before(function () {
|
||||
applyStyleOnLine(style, FIRST_LINE);
|
||||
});
|
||||
|
||||
// clean the style applied
|
||||
after(function () {
|
||||
applyStyleOnLine(style, FIRST_LINE);
|
||||
});
|
||||
|
||||
it('selects the style button', function (done) {
|
||||
expect(isButtonSelected(style)).to.be(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
SHORTCUT_KEYS.forEach(function(key, index){
|
||||
var styleOfTheShortcut = STYLES[index]; // italic, bold, ...
|
||||
context('when user presses CMD + ' + key, function() {
|
||||
before(function () {
|
||||
pressFormattingShortcutOnSelection(key);
|
||||
});
|
||||
|
||||
testIfFormattingButtonIsSelected(styleOfTheShortcut);
|
||||
|
||||
context('and user presses CMD + ' + key + ' again', function() {
|
||||
before(function () {
|
||||
pressFormattingShortcutOnSelection(key);
|
||||
});
|
||||
|
||||
testIfFormattingButtonIsDeselected(styleOfTheShortcut);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue