El RIDO 9 лет назад
Родитель
Сommit
996a22380e
21 измененных файлов с 1397 добавлено и 1284 удалено
  1. 13 5
      .eslintrc
  2. 1 1
      .travis.yml
  3. 2 1
      composer.json
  4. 10 0
      css/bootstrap/privatebin.css
  5. 22 17
      i18n/de.json
  6. 8 8
      i18n/es.json
  7. 8 8
      i18n/fr.json
  8. 8 8
      i18n/it.json
  9. 8 8
      i18n/no.json
  10. 8 8
      i18n/oc.json
  11. 8 8
      i18n/pl.json
  12. 11 11
      i18n/pt.json
  13. 9 9
      i18n/ru.json
  14. 7 7
      i18n/sl.json
  15. 8 8
      i18n/zh.json
  16. 1052 1010
      js/privatebin.js
  17. 56 40
      js/test.js
  18. 107 91
      tpl/bootstrap.php
  19. 20 5
      tpl/page.php
  20. 25 25
      tst/PrivateBinTest.php
  21. 6 6
      tst/ViewTest.php

+ 13 - 5
.eslintrc

@@ -15,7 +15,9 @@ globals:
 # http://eslint.org/docs/rules/
 rules:
   # Possible Errors
-  comma-dangle: [2, never]
+  comma-dangle:
+    - error
+    - never
   no-cond-assign: 2
   no-console: 0
   no-constant-condition: 2
@@ -31,7 +33,9 @@ rules:
   no-extra-parens: 0
   no-extra-semi: 2
   no-func-assign: 2
-  no-inner-declarations: [2, functions]
+  no-inner-declarations:
+    - error
+    - functions
   no-invalid-regexp: 2
   no-irregular-whitespace: 2
   no-negated-in-lhs: 2
@@ -47,7 +51,9 @@ rules:
   # Best Practices
   accessor-pairs: 2
   block-scoped-var: 0
-  complexity: [2, 6]
+  complexity:
+    - error
+    - 20
   consistent-return: 0
   curly: 0
   default-case: 0
@@ -99,7 +105,7 @@ rules:
   no-with: 2
   radix: 2
   vars-on-top: 0
-  wrap-iife: 2
+  wrap-iife: 0
   yoda: 0
 
   # Strict
@@ -152,7 +158,9 @@ rules:
   max-len: 0
   max-nested-callbacks: 0
   max-params: 0
-  max-statements: [2, 30]
+  max-statements:
+    - error
+    - 60
   new-cap: 0
   new-parens: 0
   newline-after-var: 0

+ 1 - 1
.travis.yml

@@ -9,7 +9,7 @@ before_script:
   - composer install -n
 
 script:
-  - cd tst && phpunit
+  - cd tst && ../vendor/bin/phpunit
 
 after_script:
   - cd ..

+ 2 - 1
composer.json

@@ -24,7 +24,8 @@
     },
     "require-dev": {
         "codacy/coverage": "dev-master",
-        "codeclimate/php-test-reporter": "dev-master"
+        "codeclimate/php-test-reporter": "dev-master",
+        "phpunit/phpunit": "^4.6 || ^5.0"
     },
     "autoload": {
         "psr-4": {

+ 10 - 0
css/bootstrap/privatebin.css

@@ -17,6 +17,10 @@ body.navbar-spacing {
 	padding-top: 70px;
 }
 
+body.loading {
+	cursor: wait;
+}
+
 .buttondisabled {
 	opacity: 0.3;
 }
@@ -102,6 +106,12 @@ body.navbar-spacing {
 	border-left: 1px solid #ccc;
 	padding: 5px 0 5px 10px;
 	white-space: pre-wrap;
+	transition: background-color 0.75s ease-out;
+}
+
+.comment.highlight {
+	background-color: #ffdd86;
+	transition: background-color 0.2s ease-in;
 }
 
 footer h4 {

+ 22 - 17
i18n/de.json

@@ -83,25 +83,25 @@
     "Could not decrypt data (Wrong key?)":
         "Konnte Daten nicht entschlüsseln (Falscher Schlüssel?)",
     "Could not delete the paste, it was not stored in burn after reading mode.":
-        "Konnte den Text nicht löschen, er wurde nicht im Einmal-Modus gespeichert.",
+        "Konnte das Paste nicht löschen, es wurde nicht im Einmal-Modus gespeichert.",
     "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.":
-        "DIESER TEXT IST NUR FÜR DICH GEDACHT. Schliesse das Fenster nicht, diese Nachricht kann nur einmal geöffnet werden.",
+        "DIESER TEXT IST NUR FÜR DICH GEDACHT. Schließe das Fenster nicht, diese Nachricht kann nur einmal geöffnet werden.",
     "Could not decrypt comment; Wrong key?":
         "Konnte Kommentar nicht entschlüsseln; Falscher Schlüssel?",
     "Reply":
         "Antworten",
     "Anonymous":
         "Anonym",
-    "Anonymous avatar (Vizhash of the IP address)":
-        "Anonymer Avatar (Vizhash der IP-Addresse)",
+    "Avatar generated from IP address":
+        "Avatar (generiert aus der IP-Adresse)",
     "Add comment":
         "Kommentar hinzufügen",
-    "Optional nickname...":
-        "Optionales Pseudonym...",
+    "Optional nickname":
+        "Optionales Pseudonym",
     "Post comment":
         "Kommentar absenden",
-    "Sending comment...":
-        "Sende Kommentar...",
+    "Sending comment":
+        "Sende Kommentar",
     "Comment posted.":
         "Kommentar gesendet.",
     "Could not refresh display: %s":
@@ -112,24 +112,25 @@
         "Fehler auf dem Server oder keine Antwort vom Server",
     "Could not post comment: %s":
         "Konnte Kommentar nicht senden: %s",
-    "Sending paste (Please move your mouse for more entropy)...":
-        "Sende Text (Bitte bewege Deine Maus um die Entropie zu erhöhen)...",
-    "Sending paste...":
-        "Sende Text...",
+    "Please move your mouse for more entropy…":
+        "Bitte bewege Deine Maus um die Entropie zu erhöhen",
+    "Sending paste":
+        "Sende Paste…",
     "Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>":
-        "Dein Text ist unter <a id=\"pasteurl\" href=\"%s\">%s</a> zu finden <span id=\"copyhint\">(Drücke [Strg]+[c] um den Link zu kopieren)</span>",
+        "Dein Paste ist unter <a id=\"pasteurl\" href=\"%s\">%s</a> zu finden <span id=\"copyhint\">(Drücke [Strg]+[c] um den Link zu kopieren)</span>",
     "Delete data":
         "Lösche Daten",
     "Could not create paste: %s":
-        "Konnte Text nicht erstellen: %s",
+        "Konnte Paste nicht erstellen: %s",
     "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)":
-        "Konnte Text nicht entschlüsseln: Der Schlüssel fehlt in der Adresse (Hast du eine Umleitung oder einen URL-Verkürzer benutzt, der Teile der Adresse entfernt?)",
+        "Konnte Paste nicht entschlüsseln: Der Schlüssel fehlt in der Adresse (Hast du eine Umleitung oder einen URL-Verkürzer benutzt, der Teile der Adresse entfernt?)",
     "Format": "Format",
     "Plain Text": "Nur Text",
     "Source Code": "Quellcode",
     "Markdown": "Markdown",
     "Download attachment": "Anhang herunterladen",
-    "Cloned file attached.": "Kopierte Datei angehängt.",
+    "Cloned: '%s'": "Geklont: '%s'",
+    "The cloned file '%s' was attached to this paste.": "Die geklonte Datei '%s' wurde angehängt.",
     "Attach a file": "Datei anhängen",
     "Remove attachment": "Anhang entfernen",
     "Your browser does not support uploading encrypted files. Please use a newer browser.":
@@ -146,6 +147,10 @@
     "Enter password":
         "Passwort eingeben",
     "Loading…": "Lädt…",
+    "Decrypting paste…": "Entschlüssle Paste…",
+    "Preparing new paste…": "Bereite neues Paste vor…",
     "In case this message never disappears please have a look at <a href=\"https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-not-the-loading-message-go-away\">this FAQ for information to troubleshoot</a>.":
-        "Wenn diese Nachricht nicht mehr verschwindet, schau bitte in <a href=\"https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-not-the-loading-message-go-away\">die FAQ</a> (englisch), um zu sehen, wie der Fehler behoben werden kann."
+        "Wenn diese Nachricht nicht mehr verschwindet, schau bitte in <a href=\"https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-not-the-loading-message-go-away\">die FAQ</a> (englisch), um zu sehen, wie der Fehler behoben werden kann.",
+    "+++ no paste text +++":
+        "+++ kein Paste-Text +++"
 }

+ 8 - 8
i18n/es.json

@@ -96,12 +96,12 @@
         "Avatar anónimo (Vizhash de la dirección IP)",
     "Add comment":
         "Añadir comentario",
-    "Optional nickname...":
-        "Seudónimo opcional...",
+    "Optional nickname":
+        "Seudónimo opcional",
     "Post comment":
         "Publicar comentario",
-    "Sending comment...":
-        "Enviando comentario...",
+    "Sending comment":
+        "Enviando comentario",
     "Comment posted.":
         "Comentario publicado.",
     "Could not refresh display: %s":
@@ -112,10 +112,10 @@
         "Error del servidor o el servidor no responde",
     "Could not post comment: %s":
         "No fue posible publicar comentario: %s",
-    "Sending paste (Please move your mouse for more entropy)...":
-        "Enviando texto (Por favor, mueva el ratón para mayor entropía)...",
-    "Sending paste...":
-        "Enviando texto...",
+    "Sending paste (Please move your mouse for more entropy)":
+        "Enviando texto (Por favor, mueva el ratón para mayor entropía)",
+    "Sending paste":
+        "Enviando texto",
     "Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>":
         "Su texto está en <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Presione [Ctrl]+[c] para copiar)</span>",
     "Delete data":

+ 8 - 8
i18n/fr.json

@@ -96,12 +96,12 @@
         "Avatar anonyme (Vizhash de l'adresse IP)",
     "Add comment":
         "Ajouter un commentaire",
-    "Optional nickname...":
-        "Pseudonyme optionnel...",
+    "Optional nickname":
+        "Pseudonyme optionnel",
     "Post comment":
         "Poster le commentaire",
-    "Sending comment...":
-        "Envoi du commentaire...",
+    "Sending comment":
+        "Envoi du commentaire",
     "Comment posted.":
         "Commentaire posté.",
     "Could not refresh display: %s":
@@ -112,10 +112,10 @@
         "Le serveur ne répond pas ou a rencontré une erreur",
     "Could not post comment: %s":
         "Impossible de poster le commentaire : %s",
-    "Sending paste (Please move your mouse for more entropy)...":
-        "Envoi du paste (Merci de bouger votre souris pour plus d'entropie)...",
-    "Sending paste...":
-        "Envoi du paste...",
+    "Sending paste (Please move your mouse for more entropy)":
+        "Envoi du paste (Merci de bouger votre souris pour plus d'entropie)",
+    "Sending paste":
+        "Envoi du paste",
     "Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>":
         "Votre paste est disponible à l'adresse <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Appuyez sur [Ctrl]+[c] pour copier)</span>",
     "Delete data":

+ 8 - 8
i18n/it.json

@@ -96,12 +96,12 @@
         "Avatar Anonino (Vizhash dell'indirizzo IP)",
     "Add comment":
         "Aggiungi un commento",
-    "Optional nickname...":
-        "Nickname opzionale...",
+    "Optional nickname":
+        "Nickname opzionale",
     "Post comment":
         "Invia commento",
-    "Sending comment...":
-        "Commento in fase di invio...",
+    "Sending comment":
+        "Commento in fase di invio",
     "Comment posted.":
         "Commento inviato.",
     "Could not refresh display: %s":
@@ -112,10 +112,10 @@
         "errore o mancata risposta dal server",
     "Could not post comment: %s":
         "Impossibile inviare il commento: %s",
-    "Sending paste (Please move your mouse for more entropy)...":
-        "Invio messaggio (Muovi il mouse in modo casuale, per generare maggior entropia)...",
-    "Sending paste...":
-        "Messaggio in fase di invio...",
+    "Sending paste (Please move your mouse for more entropy)":
+        "Invio messaggio (Muovi il mouse in modo casuale, per generare maggior entropia)",
+    "Sending paste":
+        "Messaggio in fase di invio",
     "Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>":
         "Il tuo messaggio è qui: <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">([CTRL | CMD]+[C] per copiare il link)</span>",
     "Delete data":

+ 8 - 8
i18n/no.json

@@ -96,12 +96,12 @@
         "Anonym avatar (Vizhash av IP adressen)",
     "Add comment":
         "Legg til kommentar",
-    "Optional nickname...":
-        "Valgfritt kallenavn...",
+    "Optional nickname":
+        "Valgfritt kallenavn",
     "Post comment":
         "Send kommentar",
-    "Sending comment...":
-        "Sender Kommentar...",
+    "Sending comment":
+        "Sender Kommentar",
     "Comment posted.":
         "Kommentar sendt.",
     "Could not refresh display: %s":
@@ -112,10 +112,10 @@
         "server feilet eller svarer ikke",
     "Could not post comment: %s":
         "Kunne ikke sende kommentar: %s",
-    "Sending paste (Please move your mouse for more entropy)...":
-        "Sender innlegg (Flytt musen for mere entropi)...",
-    "Sending paste...":
-        "Sender innlegg...",
+    "Sending paste (Please move your mouse for more entropy)":
+        "Sender innlegg (Flytt musen for mere entropi)",
+    "Sending paste":
+        "Sender innlegg",
     "Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>":
         "Ditt innlegg er <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Trykk [Ctrl]+[c] for å kopiere)</span>",
     "Delete data":

+ 8 - 8
i18n/oc.json

@@ -96,12 +96,12 @@
         "Avatar anonime (Vizhash de l'adreça IP)",
     "Add comment":
         "Apondre un comentari",
-    "Optional nickname...":
-        "Escais opcional...",
+    "Optional nickname":
+        "Escais opcional",
     "Post comment":
         "Mandar lo comentari",
-    "Sending comment...":
-        "Mandadís del comentari...",
+    "Sending comment":
+        "Mandadís del comentari",
     "Comment posted.":
         "Comentari mandat.",
     "Could not refresh display: %s":
@@ -112,10 +112,10 @@
         "Lo servidor respond pas o a rencontrat una error",
     "Could not post comment: %s":
         "Impossible de mandar lo comentari : %s",
-    "Sending paste (Please move your mouse for more entropy)...":
-        "Mandadís del tèxte (Mercés de bolegar vòstra mirga per mai entropia)...",
-    "Sending paste...":
-        "Mandadís del tèxte...",
+    "Sending paste (Please move your mouse for more entropy)":
+        "Mandadís del tèxte (Mercés de bolegar vòstra mirga per mai entropia)",
+    "Sending paste":
+        "Mandadís del tèxte",
     "Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>":
         "Vòstre tèxte es disponible a l'adreça <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Picatz sus [Ctrl]+[c] per copiar)</span>",
     "Delete data":

+ 8 - 8
i18n/pl.json

@@ -96,12 +96,12 @@
         "Anonimowy avatar (Vizhash z adresu IP)",
     "Add comment":
         "Dodaj komentarz",
-    "Optional nickname...":
-        "Opcjonalny nick...",
+    "Optional nickname":
+        "Opcjonalny nick",
     "Post comment":
         "Wyślij komentarz",
-    "Sending comment...":
-        "Wysyłanie komentarza...",
+    "Sending comment":
+        "Wysyłanie komentarza",
     "Comment posted.":
         "Wysłano komentarz.",
     "Could not refresh display: %s":
@@ -112,10 +112,10 @@
         "bląd serwera lub brak odpowiedzi",
     "Could not post comment: %s":
         "Nie udało się wysłać komentarza: %s",
-    "Sending paste (Please move your mouse for more entropy)...":
-        "Wysyłanie wklejki (proszę poruszać myszą aby uzyskać większą entropię)...",
-    "Sending paste...":
-        "Wysyłanie wklejki...",
+    "Sending paste (Please move your mouse for more entropy)":
+        "Wysyłanie wklejki (proszę poruszać myszą aby uzyskać większą entropię)",
+    "Sending paste":
+        "Wysyłanie wklejki",
     "Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>":
         "Twoja wklejka to <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(wciśnij [Ctrl]+[c] aby skopiować)</span>",
     "Delete data":

+ 11 - 11
i18n/pt.json

@@ -92,16 +92,16 @@
         "Responder",
     "Anonymous":
         "Anônimo",
-    "Anonymous avatar (Vizhash of the IP address)":
-        "Avatar anônimo (Vizhash do endereço IP)",
+    "Avatar generated from IP address":
+        "Avatar (do endereço IP)",
     "Add comment":
         "Adicionar comentário",
-    "Optional nickname...":
-        "Apelido opcional...",
+    "Optional nickname":
+        "Apelido opcional",
     "Post comment":
         "Publicar comentário",
-    "Sending comment...":
-        "Enviando comentário...",
+    "Sending comment":
+        "Enviando comentário",
     "Comment posted.":
         "Comentário publicado.",
     "Could not refresh display: %s":
@@ -112,10 +112,10 @@
         "Servidor em erro ou não responsivo",
     "Could not post comment: %s":
         "Não foi possível publicar o comentário: %s",
-    "Sending paste (Please move your mouse for more entropy)...":
-        "Enviando cópia (Por favor, mova o mouse para maior entropia)...",
-    "Sending paste...":
-        "Enviando cópia...",
+    "Please move your mouse for more entropy…":
+        "Por favor, mova o mouse para maior entropia…",
+    "Sending paste":
+        "Enviando cópia",
     "Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>":
         "Sua cópia é <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Pressione [Ctrl]+[c] para copiar)</span>",
     "Delete data":
@@ -129,7 +129,7 @@
     "Source Code": "Código fonte",
     "Markdown": "Markdown",
     "Download attachment": "Baixar anexo",
-    "Cloned file attached.": "Arquivo clonado anexado.",
+    "Cloned: '%s'": "Clonado: '%s'",
     "Attach a file": "Anexar um arquivo",
     "Remove attachment": "Remover anexo",
     "Your browser does not support uploading encrypted files. Please use a newer browser.":

+ 9 - 9
i18n/ru.json

@@ -96,12 +96,12 @@
         "Анонимный аватар (Vizhash IP адреса)",
     "Add comment":
         "Добавить комментарий",
-    "Optional nickname...":
-        "Опциональный никнейм...",
+    "Optional nickname":
+        "Опциональный никнейм",
     "Post comment":
         "Отправить комментарий",
-    "Sending comment...":
-        "Отправка комментария...",
+    "Sending comment":
+        "Отправка комментария",
     "Comment posted.":
         "Комментарий опубликован.",
     "Could not refresh display: %s":
@@ -112,10 +112,10 @@
         "ошибка сервера или нет ответа",
     "Could not post comment: %s":
         "Не удалось опубликовать комментарий: %s",
-    "Sending paste (Please move your mouse for more entropy)...":
-        "Отправка записи (Пожалуйста двигайте мышкой для большей энтропии)...",
-    "Sending paste...":
-        "Отправка записи...",
+    "Sending paste (Please move your mouse for more entropy)":
+        "Отправка записи (Пожалуйста двигайте мышкой для большей энтропии)",
+    "Sending paste":
+        "Отправка записи",
     "Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>":
         "Ссылка на запись <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Нажмите [Ctrl]+[c] чтобы скопировать ссылку)</span>",
     "Delete data":
@@ -155,5 +155,5 @@
     "Enter password":
         "Введите пароль",
     "Uploading paste… Please wait.":
-        "Отправка записи... Пожалуйста подождите."
+        "Отправка записи Пожалуйста подождите."
 }

+ 7 - 7
i18n/sl.json

@@ -96,12 +96,12 @@
         "Anonimen avatar (Vizhash IP naslova)",
     "Add comment":
         "Dodaj komentar",
-    "Optional nickname...":
+    "Optional nickname":
         "Uporabniško ime (lahko izpustiš)",
     "Post comment":
         "Objavi komentar",
-    "Sending comment...":
-        "Pošiljam komentar ...",
+    "Sending comment":
+        "Pošiljam komentar ",
     "Comment posted.":
         "Komentar poslan.",
     "Could not refresh display: %s":
@@ -112,10 +112,10 @@
         "napaka na strežniku, ali pa se strežnik ne odziva",
     "Could not post comment: %s":
         "Komentarja ni bilo mogoče objaviti : %s",
-    "Sending paste (Please move your mouse for more entropy)...":
-        "Pošiljam prilepek (prosim premakni svojo miško za več entropije) ...",
-    "Sending paste...":
-        "Pošiljam prilepek...",
+    "Sending paste (Please move your mouse for more entropy)":
+        "Pošiljam prilepek (prosim premakni svojo miško za več entropije) ",
+    "Sending paste":
+        "Pošiljam prilepek",
     "Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>":
         "Tvoj prilepek je dostopen na naslovu: <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Pritisni [Ctrl]+[c] ali [Cmd] + [c] in skopiraj)</span>",
     "Delete data":

+ 8 - 8
i18n/zh.json

@@ -96,12 +96,12 @@
         "匿名头像 (由IP地址生成Vizhash)",
     "Add comment":
         "添加评论",
-    "Optional nickname...":
-        "可选昵称...",
+    "Optional nickname":
+        "可选昵称",
     "Post comment":
         "评论",
-    "Sending comment...":
-        "评论发送中...",
+    "Sending comment":
+        "评论发送中",
     "Comment posted.":
         "评论已发送。",
     "Could not refresh display: %s":
@@ -112,10 +112,10 @@
         "服务器错误或无回应",
     "Could not post comment: %s":
         "无法发送评论: %s",
-    "Sending paste (Please move your mouse for more entropy)...":
-        "粘贴提交中 (请移动鼠标以产生更多熵)...",
-    "Sending paste...":
-        "粘贴提交中...",
+    "Sending paste (Please move your mouse for more entropy)":
+        "粘贴提交中 (请移动鼠标以产生更多熵)",
+    "Sending paste":
+        "粘贴提交中",
     "Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>":
         "您的粘贴的链接是<a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(按下 [Ctrl]+[c] 以复制)</span>",
     "Delete data":

Разница между файлами не показана из-за своего большого размера
+ 1052 - 1010
js/privatebin.js


+ 56 - 40
js/test.js

@@ -11,7 +11,9 @@ var jsc = require('jsverify'),
         a2zString.map(function(c) {
             return c.toUpperCase();
         })
-    );
+    ),
+    // schemas supported by the whatwg-url library
+    schemas = ['ftp','gopher','http','https','ws','wss'];
 
 global.$ = global.jQuery = require('./jquery-3.1.1');
 global.sjcl = require('./sjcl-1.0.6');
@@ -20,75 +22,101 @@ global.RawDeflate = require('./rawdeflate-0.5');
 require('./rawinflate-0.3');
 require('./privatebin');
 
-describe('helper', function () {
+describe('Helper', function () {
     describe('secondsToHuman', function () {
         after(function () {
             cleanup();
         });
 
         jsc.property('returns an array with a number and a word', 'integer', function (number) {
-            var result = $.PrivateBin.helper.secondsToHuman(number);
+            var result = $.PrivateBin.Helper.secondsToHuman(number);
             return Array.isArray(result) &&
                 result.length === 2 &&
                 result[0] === parseInt(result[0], 10) &&
                 typeof result[1] === 'string';
         });
         jsc.property('returns seconds on the first array position', 'integer 59', function (number) {
-            return $.PrivateBin.helper.secondsToHuman(number)[0] === number;
+            return $.PrivateBin.Helper.secondsToHuman(number)[0] === number;
         });
         jsc.property('returns seconds on the second array position', 'integer 59', function (number) {
-            return $.PrivateBin.helper.secondsToHuman(number)[1] === 'second';
+            return $.PrivateBin.Helper.secondsToHuman(number)[1] === 'second';
         });
         jsc.property('returns minutes on the first array position', 'integer 60 3599', function (number) {
-            return $.PrivateBin.helper.secondsToHuman(number)[0] === Math.floor(number / 60);
+            return $.PrivateBin.Helper.secondsToHuman(number)[0] === Math.floor(number / 60);
         });
         jsc.property('returns minutes on the second array position', 'integer 60 3599', function (number) {
-            return $.PrivateBin.helper.secondsToHuman(number)[1] === 'minute';
+            return $.PrivateBin.Helper.secondsToHuman(number)[1] === 'minute';
         });
         jsc.property('returns hours on the first array position', 'integer 3600 86399', function (number) {
-            return $.PrivateBin.helper.secondsToHuman(number)[0] === Math.floor(number / (60 * 60));
+            return $.PrivateBin.Helper.secondsToHuman(number)[0] === Math.floor(number / (60 * 60));
         });
         jsc.property('returns hours on the second array position', 'integer 3600 86399', function (number) {
-            return $.PrivateBin.helper.secondsToHuman(number)[1] === 'hour';
+            return $.PrivateBin.Helper.secondsToHuman(number)[1] === 'hour';
         });
         jsc.property('returns days on the first array position', 'integer 86400 5184000', function (number) {
-            return $.PrivateBin.helper.secondsToHuman(number)[0] === Math.floor(number / (60 * 60 * 24));
+            return $.PrivateBin.Helper.secondsToHuman(number)[0] === Math.floor(number / (60 * 60 * 24));
         });
         jsc.property('returns days on the second array position', 'integer 86400 5184000', function (number) {
-            return $.PrivateBin.helper.secondsToHuman(number)[1] === 'day';
+            return $.PrivateBin.Helper.secondsToHuman(number)[1] === 'day';
         });
         // max safe integer as per http://ecma262-5.com/ELS5_HTML.htm#Section_8.5
         jsc.property('returns months on the first array position', 'integer 5184000 9007199254740991', function (number) {
-            return $.PrivateBin.helper.secondsToHuman(number)[0] === Math.floor(number / (60 * 60 * 24 * 30));
+            return $.PrivateBin.Helper.secondsToHuman(number)[0] === Math.floor(number / (60 * 60 * 24 * 30));
         });
         jsc.property('returns months on the second array position', 'integer 5184000 9007199254740991', function (number) {
-            return $.PrivateBin.helper.secondsToHuman(number)[1] === 'month';
+            return $.PrivateBin.Helper.secondsToHuman(number)[1] === 'month';
         });
     });
 
-    describe('scriptLocation', function () {
+    describe('baseUri', function () {
+        before(function () {
+            $.PrivateBin.Helper.reset();
+        });
+
         jsc.property(
             'returns the URL without query & fragment',
-            jsc.nearray(jsc.elements(a2zString)),
+            jsc.elements(schemas),
             jsc.nearray(jsc.elements(a2zString)),
             jsc.array(jsc.elements(queryString)),
             'string',
             function (schema, address, query, fragment) {
-                var expected = schema.join('') + '://' + address.join('') + '/',
+                var expected = schema + '://' + address.join('') + '/',
                     clean = jsdom('', {url: expected + '?' + query.join('') + '#' + fragment}),
-                    result = $.PrivateBin.helper.scriptLocation();
+                    result = $.PrivateBin.Helper.baseUri();
+                $.PrivateBin.Helper.reset();
                 clean();
                 return expected === result;
             }
         );
     });
 
-    describe('pasteId', function () {
+    describe('htmlEntities', function () {
+        after(function () {
+            cleanup();
+        });
+
+        jsc.property(
+            'removes all HTML entities from any given string',
+            'string',
+            function (string) {
+                var result = $.PrivateBin.Helper.htmlEntities(string);
+                return !(/[<>"'`=\/]/.test(result)) && !(string.indexOf('&') > -1 && !(/&amp;/.test(result)));
+            }
+        );
+    });
+});
+
+describe('Model', function () {
+    describe('getPasteId', function () {
+        before(function () {
+            $.PrivateBin.Model.reset();
+        });
+
         jsc.property(
             'returns the query string without separator, if any',
             jsc.nearray(jsc.elements(a2zString)),
             jsc.nearray(jsc.elements(a2zString)),
-            jsc.array(jsc.elements(queryString)),
+            jsc.nearray(jsc.elements(queryString)),
             'string',
             function (schema, address, query, fragment) {
                 var queryString = query.join(''),
@@ -96,27 +124,29 @@ describe('helper', function () {
                         url: schema.join('') + '://' + address.join('') +
                              '/?' + queryString + '#' + fragment
                     }),
-                    result = $.PrivateBin.helper.pasteId();
+                    result = $.PrivateBin.Model.getPasteId();
+                $.PrivateBin.Model.reset();
                 clean();
                 return queryString === result;
             }
         );
     });
 
-    describe('pageKey', function () {
+    describe('getPasteKey', function () {
         jsc.property(
             'returns the fragment of the URL',
             jsc.nearray(jsc.elements(a2zString)),
             jsc.nearray(jsc.elements(a2zString)),
             jsc.array(jsc.elements(queryString)),
-            jsc.array(jsc.elements(base64String)),
+            jsc.nearray(jsc.elements(base64String)),
             function (schema, address, query, fragment) {
                 var fragmentString = fragment.join(''),
                     clean = jsdom('', {
                         url: schema.join('') + '://' + address.join('') +
                              '/?' + query.join('') + '#' + fragmentString
                     }),
-                    result = $.PrivateBin.helper.pageKey();
+                    result = $.PrivateBin.Model.getPasteKey();
+                $.PrivateBin.Model.reset();
                 clean();
                 return fragmentString === result;
             }
@@ -126,7 +156,7 @@ describe('helper', function () {
             jsc.nearray(jsc.elements(a2zString)),
             jsc.nearray(jsc.elements(a2zString)),
             jsc.array(jsc.elements(queryString)),
-            jsc.array(jsc.elements(base64String)),
+            jsc.nearray(jsc.elements(base64String)),
             jsc.array(jsc.elements(queryString)),
             function (schema, address, query, fragment, trail) {
                 var fragmentString = fragment.join(''),
@@ -134,25 +164,11 @@ describe('helper', function () {
                         url: schema.join('') + '://' + address.join('') + '/?' +
                              query.join('') + '#' + fragmentString + '&' + trail.join('')
                     }),
-                    result = $.PrivateBin.helper.pageKey();
+                    result = $.PrivateBin.Model.getPasteKey();
+                $.PrivateBin.Model.reset();
                 clean();
                 return fragmentString === result;
             }
         );
     });
-
-    describe('htmlEntities', function () {
-        after(function () {
-            cleanup();
-        });
-
-        jsc.property(
-            'removes all HTML entities from any given string',
-            'string',
-            function (string) {
-                var result = $.PrivateBin.helper.htmlEntities(string);
-                return !(/[<>"'`=\/]/.test(result)) && !(string.indexOf('&') > -1 && !(/&amp;/.test(result)));
-            }
-        );
-    });
 });

+ 107 - 91
tpl/bootstrap.php

@@ -4,7 +4,7 @@ $isCpct = substr($template, 9, 8) === '-compact';
 $isDark = substr($template, 9, 5) === '-dark';
 $isPage = substr($template, -5) === '-page';
 ?><!DOCTYPE html>
-<html lang="en">
+<html>
 	<head>
 		<meta charset="utf-8" />
 		<meta http-equiv="X-UA-Compatible" content="IE=edge">
@@ -69,7 +69,7 @@ if ($MARKDOWN):
 <?php
 endif;
 ?>
-		<script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-TETJUplZ4Gwfsyq+4b1auYItvlfa9BLoT+IzcQ6Xt2mlmMwKbso7b07/mTF2H2UqMy9I3j6nMAZGsPvIcAPzrQ==" crossorigin="anonymous"></script>
+		<script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-7/bLmiTErcH3lMJi1JxXk/lb20NUO7TCqasWeFybnNhnfOUnJ9GQGqPVJxPf+7fnw9dgLa1s18FXQuRanLD8Hw==" crossorigin="anonymous"></script>
 		<!--[if lt IE 10]>
 		<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
 		<![endif]-->
@@ -94,7 +94,7 @@ endif;
 						<form id="passwordform" role="form">
 							<div class="form-group">
 								<label for="passworddecrypt"><span class="glyphicon glyphicon-eye-open"></span> <?php echo I18n::_('Please enter the password for this paste:') ?></label>
-								<input id="passworddecrypt" type="password" class="form-control" placeholder="<?php echo I18n::_('Enter password') ?>" autofocus>
+								<input id="passworddecrypt" type="password" class="form-control" placeholder="<?php echo I18n::_('Enter password') ?>">
 							</div>
 							<button type="submit" class="btn btn-success btn-block"><span class="glyphicon glyphicon-off"></span> <?php echo I18n::_('Decrypt') ?></button>
 						</form>
@@ -121,8 +121,8 @@ endif;
 			<div id="navbar" class="navbar-collapse collapse">
 				<ul class="nav navbar-nav">
 					<li id="loadingindicator" class="navbar-text hidden">
-						<span class="glyphicon glyphicon-upload" aria-hidden="true"></span>
-						<?php echo I18n::_('Uploading paste… Please wait.'), PHP_EOL; ?>
+						<span class="glyphicon glyphicon-time" aria-hidden="true"></span>
+						<?php echo I18n::_('Loading…'), PHP_EOL; ?>
 					</li>
 					<li>
 <?php
@@ -132,7 +132,7 @@ if ($isPage):
 							<span class="glyphicon glyphicon-upload" aria-hidden="true"></span> <?php echo I18n::_('Send'), PHP_EOL;
 else:
 ?>
-						<button id="newbutton" type="button" class="reloadlink hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
+						<button id="newbutton" type="button" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
 							<span class="glyphicon glyphicon-file" aria-hidden="true"></span> <?php echo I18n::_('New'), PHP_EOL;
 endif;
 ?>
@@ -198,7 +198,7 @@ if ($isCpct):
 <?php
     if ($DISCUSSION):
 ?>
-							<li id="opendisc" class="checkbox hidden">
+							<li id="opendiscussionoption" class="checkbox hidden">
 								<label>
 									<input type="checkbox" id="opendiscussion" name="opendiscussion"<?php
         if ($OPENDISCUSSION):
@@ -230,17 +230,6 @@ if ($isCpct):
 ?>
 						</ul>
 						<select id="pasteFormatter" name="pasteFormatter" class="hidden">
-<?php
-    foreach ($FORMATTER as $key => $value):
-?>
-							<option value="<?php echo $key; ?>"<?php
-        if ($key == $FORMATTERDEFAULT):
-?> selected="selected"<?php
-        endif;
-?>><?php echo $value; ?></option>
-<?php
-    endforeach;
-?>
 						</select>
 					</li>
 <?php
@@ -262,7 +251,7 @@ else:
     if ($DISCUSSION):
 ?>
 					<li>
-						<div id="opendisc" class="navbar-text checkbox hidden">
+						<div id="opendiscussionoption" class="navbar-text checkbox hidden">
 							<label>
 								<input type="checkbox" id="opendiscussion" name="opendiscussion"<?php
         if ($OPENDISCUSSION):
@@ -295,6 +284,7 @@ if ($FILEUPLOAD):
 									<input type="file" id="file" name="file" />
 								</div>
 							</li>
+							<li id="customattachment" class="hidden"></li>
 							<li>
 								<a id="fileremovebutton"  href="#">
 									<?php echo I18n::_('Remove attachment'), PHP_EOL; ?>
@@ -383,98 +373,124 @@ if ($isCpct):
 ?></div><?php
 endif;
 ?></nav>
-		<header class="container">
+		<main>
+			<section class="container">
 <?php
 if (strlen($NOTICE)):
 ?>
-			<div role="alert" class="alert alert-info">
-				<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> <?php echo htmlspecialchars($NOTICE), PHP_EOL; ?>
-			</div>
+				<div role="alert" class="alert alert-info">
+					<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
+					<?php echo htmlspecialchars($NOTICE), PHP_EOL; ?>
+				</div>
 <?php
 endif;
 ?>
-			<div id="remainingtime" role="alert" class="hidden alert alert-info">
-				<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
-			</div>
+				<div id="remainingtime" role="alert" class="hidden alert alert-info">
+					<span class="glyphicon glyphicon-fire" aria-hidden="true"></span>
+				</div>
 <?php
 if ($FILEUPLOAD):
 ?>
-			<div id="attachment" role="alert" class="hidden alert alert-info">
-				<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> <a><?php echo I18n::_('Download attachment'); ?></a> <span id="clonedfile" class="hidden"><?php echo I18n::_('Cloned file attached.'); ?></span>
-			</div>
-<?php
-endif;
-if (strlen($STATUS)):
-?>
-			<div id="status" role="alert" class="alert alert-success">
-				<span class="glyphicon glyphicon-ok" aria-hidden="true"></span> <?php echo htmlspecialchars($STATUS), PHP_EOL; ?>
-			</div>
+				<div id="attachment" role="alert" class="hidden alert alert-info">
+					<span class="glyphicon glyphicon-download-alt" aria-hidden="true"></span>
+					<a class="alert-link"><?php echo I18n::_('Download attachment'), PHP_EOL; ?></a>
+				</div>
 <?php
 endif;
 ?>
-			<div id="errormessage" role="alert" class="<?php
-if (!strlen($ERROR)):
-?>hidden <?php
-endif;
-?>alert alert-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span> <?php echo htmlspecialchars($ERROR); ?></div>
-			<noscript><div id="noscript" role="alert" class="nonworking alert alert-<?php echo $isDark ? 'error' : 'warning'; ?>"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> <?php echo I18n::_('JavaScript is required for %s to work.<br />Sorry for the inconvenience.', I18n::_($NAME)); ?></div></noscript>
-			<div id="oldienotice" role="alert" class="hidden nonworking alert alert-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span> <?php echo I18n::_('%s requires a modern browser to work.', I18n::_($NAME)); ?></div>
-			<div id="ienotice" role="alert" class="hidden alert alert-<?php echo $isDark ? 'error' : 'warning'; ?>"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span> <?php echo I18n::_('Still using Internet Explorer? Do yourself a favor, switch to a modern browser:'), PHP_EOL; ?>
-				<a href="https://www.mozilla.org/firefox/">Firefox</a>,
-				<a href="https://www.opera.com/">Opera</a>,
-				<a href="https://www.google.com/chrome">Chrome</a>…
-			</div>
-			<div id="pasteresult" role="alert" class="hidden alert alert-success">
-				<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
-				<div id="deletelink"></div>
-				<div id="pastelink">
+				<div id="status" role="alert" class="statusmessage alert alert-info<?php echo empty($STATUS) ? ' hidden' : '' ?>">
+					<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
+					<?php echo htmlspecialchars($STATUS), PHP_EOL; ?>
+				</div>
+				<div id="errormessage" role="alert" class="statusmessage<?php echo empty($ERROR) ? ' hidden' : '' ?> alert alert-danger">
+					<span class="glyphicon glyphicon-alert" aria-hidden="true"></span>
+					<?php echo htmlspecialchars($ERROR), PHP_EOL; ?>
+				</div>
+				<noscript>
+					<div id="noscript" role="alert" class="nonworking alert alert-<?php echo $isDark ? 'error' : 'warning'; ?>">
+						<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+						<?php echo I18n::_('JavaScript is required for %s to work.<br />Sorry for the inconvenience.', I18n::_($NAME)), PHP_EOL; ?>
+					</div>
+				</noscript>
+				<div id="oldienotice" role="alert" class="hidden nonworking alert alert-danger">
+					<span class="glyphicon glyphicon-alert" aria-hidden="true"></span>
+					<?php echo I18n::_('%s requires a modern browser to work.', I18n::_($NAME)), PHP_EOL; ?>
+				</div>
+				<div id="ienotice" role="alert" class="hidden alert alert-<?php echo $isDark ? 'error' : 'warning'; ?>">
+					<span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span>
+					<?php echo I18n::_('Still using Internet Explorer? Do yourself a favor, switch to a modern browser:'), PHP_EOL; ?>
+					<a href="https://www.mozilla.org/firefox/">Firefox</a>,
+					<a href="https://www.opera.com/">Opera</a>,
+					<a href="https://www.google.com/chrome">Chrome</a>…
+				</div>
+				<div id="pasteSuccess" role="alert" class="hidden alert alert-success">
+					<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
+					<div id="deletelink"></div>
+					<div id="pastelink">
 <?php
 if (strlen($URLSHORTENER)):
 ?>
-					<button id="shortenbutton" data-shortener="<?php echo htmlspecialchars($URLSHORTENER); ?>" type="button" class="btn btn-<?php echo $isDark ? 'warning' : 'primary'; ?>">
-						<span class="glyphicon glyphicon-send" aria-hidden="true"></span> <?php echo I18n::_('Shorten URL'), PHP_EOL; ?>
-					</button>
+						<button id="shortenbutton" data-shortener="<?php echo htmlspecialchars($URLSHORTENER); ?>" type="button" class="btn btn-<?php echo $isDark ? 'warning' : 'primary'; ?>">
+							<span class="glyphicon glyphicon-send" aria-hidden="true"></span> <?php echo I18n::_('Shorten URL'), PHP_EOL; ?>
+						</button>
 <?php
 endif;
 ?>
+					</div>
 				</div>
-			</div>
-			<ul id="preview" class="nav nav-tabs hidden">
-				<li role="presentation" class="active"><a id="messageedit" href="#"><?php echo I18n::_('Editor'); ?></a></li>
-				<li role="presentation"><a id="messagepreview" href="#"><?php echo I18n::_('Preview'); ?></a></li>
-			</ul>
-		</header>
-		<section class="container">
-			<article class="row">
-				<div id="image" class="col-md-12 text-center hidden"></div>
-				<div id="prettymessage" class="col-md-12 hidden">
-					<pre id="prettyprint" class="col-md-12 prettyprint linenums:1"></pre>
+				<ul id="editorTabs" class="nav nav-tabs hidden">
+					<li role="presentation" class="active"><a id="messageedit" href="#"><?php echo I18n::_('Editor'); ?></a></li>
+					<li role="presentation"><a id="messagepreview" href="#"><?php echo I18n::_('Preview'); ?></a></li>
+				</ul>
+			</section>
+			<section class="container">
+				<article class="row">
+					<div id="placeholder" class="col-md-12 hidden"><?php echo I18n::_('+++ no paste text +++'); ?></div>
+					<div id="attachmentPreview" class="col-md-12 text-center hidden"></div>
+					<div id="prettymessage" class="col-md-12 hidden">
+						<pre id="prettyprint" class="col-md-12 prettyprint linenums:1"></pre>
+					</div>
+					<div id="plaintext" class="col-md-12 hidden"></div>
+					<p class="col-md-12"><textarea id="message" name="message" cols="80" rows="25" class="form-control hidden"></textarea></p>
+				</article>
+			</section>
+			<section class="container">
+				<div id="discussion" class="hidden">
+					<h4><?php echo I18n::_('Discussion'); ?></h4>
+					<div id="commentcontainer"></div>
 				</div>
-				<div id="cleartext" class="col-md-12 hidden"></div>
-				<p class="col-md-12"><textarea id="message" name="message" cols="80" rows="25" class="form-control hidden"></textarea></p>
-			</article>
-		</section>
-		<section class="container">
-			<div id="discussion" class="hidden">
-				<h4><?php echo I18n::_('Discussion'); ?></h4>
-				<div id="comments"></div>
-			</div>
-		</section>
-		<section class="container">
-			<div id="noscript" role="alert" class="nonworking alert alert-info noscript-hide"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true">
-				<span> <?php echo I18n::_('Loading…'); ?></span><br>
-				<span class="small"><?php echo I18n::_('In case this message never disappears please have a look at <a href="https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-not-the-loading-message-go-away">this FAQ for information to troubleshoot</a>.'); ?></span>
-			</div>
-		</section>
-		<footer class="container">
-			<div class="row">
-				<h4 class="col-md-5 col-xs-8"><?php echo I18n::_($NAME); ?> <small>- <?php echo I18n::_('Because ignorance is bliss'); ?></small></h4>
-				<p class="col-md-1 col-xs-4 text-center"><?php echo $VERSION; ?></p>
-				<p id="aboutbox" class="col-md-6 col-xs-12">
-					<?php echo I18n::_('%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted <i>in the browser</i> using 256 bits AES. More information on the <a href="https://privatebin.info/">project page</a>.', I18n::_($NAME)), PHP_EOL; ?>
-				</p>
+			</section>
+			<section class="container">
+				<div id="noscript" role="alert" class="nonworking alert alert-info noscript-hide">
+					<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+					<?php echo I18n::_('Loading…'); ?><br />
+					<span class="small"><?php echo I18n::_('In case this message never disappears please have a look at <a href="https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-not-the-loading-message-go-away">this FAQ for information to troubleshoot</a>.'); ?></span>
+				</div>
+			</section>
+			<footer class="container">
+				<div class="row">
+					<h4 class="col-md-5 col-xs-8"><?php echo I18n::_($NAME); ?> <small>- <?php echo I18n::_('Because ignorance is bliss'); ?></small></h4>
+					<p class="col-md-1 col-xs-4 text-center"><?php echo $VERSION; ?></p>
+					<p id="aboutbox" class="col-md-6 col-xs-12">
+						<?php echo I18n::_('%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted <i>in the browser</i> using 256 bits AES. More information on the <a href="https://privatebin.info/">project page</a>.', I18n::_($NAME)), PHP_EOL; ?>
+					</p>
+				</div>
+			</footer>
+		</main>
+		<div id="serverdata" class="hidden" aria-hidden="true">
+			<div id="cipherdata"><?php echo htmlspecialchars($CIPHERDATA, ENT_NOQUOTES); ?></div>
+<?php
+if ($DISCUSSION):
+?>
+			<div id="templates">
+				<!-- @TODO: when I intend/structure this corrrectly Firefox adds whitespaces everywhere which completly destroy the layout. (same possible when you remove the template data below and show this area in the browser) -->
+				<article id="commenttemplate" class="comment"><div class="commentmeta"><span class="nickname">name</span><span class="commentdate">0000-00-00</span></div><div class="commentdata">c</div><button class="btn btn-default btn-sm"><?php echo I18n::_('Reply'); ?></button></article>
+				<p id="commenttailtemplate" class="comment"><button class="btn btn-default btn-sm"><?php echo I18n::_('Add comment'); ?></button></p>
+				<div id="replytemplate" class="reply hidden"><input type="text" id="nickname" class="form-control" title="<?php echo I18n::_('Optional nickname…'); ?>" placeholder="<?php echo I18n::_('Optional nickname…'); ?>" /><textarea id="replymessage" class="replymessage form-control" cols="80" rows="7"></textarea><br /><div id="replystatus" role="alert" class="statusmessage hidden alert"><span class="glyphicon" aria-hidden="true"></span> </div><button id="replybutton" class="btn btn-default btn-sm"><?php echo I18n::_('Post comment'); ?></button></div>
 			</div>
-		</footer>
-		<div id="cipherdata" class="hidden"><?php echo htmlspecialchars($CIPHERDATA, ENT_NOQUOTES); ?></div>
+<?php
+endif;
+?>
+		</div>
 	</body>
 </html>

+ 20 - 5
tpl/page.php

@@ -47,7 +47,7 @@ if ($MARKDOWN):
 <?php
 endif;
 ?>
-		<script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-TETJUplZ4Gwfsyq+4b1auYItvlfa9BLoT+IzcQ6Xt2mlmMwKbso7b07/mTF2H2UqMy9I3j6nMAZGsPvIcAPzrQ==" crossorigin="anonymous"></script>
+		<script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-7/bLmiTErcH3lMJi1JxXk/lb20NUO7TCqasWeFybnNhnfOUnJ9GQGqPVJxPf+7fnw9dgLa1s18FXQuRanLD8Hw==" crossorigin="anonymous"></script>
 		<!--[if lt IE 10]>
 		<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
 		<![endif]-->
@@ -84,6 +84,7 @@ endif;
 		</header>
 		<section>
 			<article>
+				<div id="loadingindicator" class="hidden"><?php echo I18n::_('Loading…'); ?></div>
 				<div id="status"><?php echo htmlspecialchars($STATUS); ?></div>
 				<div id="errormessage" class="hidden"><?php echo htmlspecialchars($ERROR); ?></div>
 				<div id="toolbar">
@@ -124,7 +125,7 @@ endif;
 <?php
 if ($DISCUSSION):
 ?>
-					<div id="opendisc" class="button hidden">
+					<div id="opendiscussionoption" class="button hidden">
 						<input type="checkbox" id="opendiscussion" name="opendiscussion"<?php
     if ($OPENDISCUSSION):
 ?> checked="checked"<?php
@@ -215,17 +216,31 @@ endif;
 				<div id="prettymessage" class="hidden">
 					<pre id="prettyprint" class="prettyprint linenums:1"></pre>
 				</div>
-				<div id="cleartext" class="hidden"></div>
+				<div id="plaintext" class="hidden"></div>
 				<textarea id="message" name="message" cols="80" rows="25" class="hidden"></textarea>
 			</article>
 		</section>
 		<section>
 			<div id="discussion" class="hidden">
 				<h4 class="title"><?php echo I18n::_('Discussion'); ?></h4>
-				<div id="comments"></div>
+				<div id="commentcontainer"></div>
 			</div>
 		</section>
-		<div id="cipherdata" class="hidden"><?php echo htmlspecialchars($CIPHERDATA, ENT_NOQUOTES); ?></div>
+		<div id="serverdata" class="hidden" aria-hidden="true">
+			<div id="cipherdata" class="hidden"><?php echo htmlspecialchars($CIPHERDATA, ENT_NOQUOTES); ?></div>
+<?php
+if ($DISCUSSION):
+?>
+			<div id="templates">
+				<!-- @TODO: when I intend/structure this corrrectly Firefox adds whitespaces everywhere which completly destroy the layout. (same possible when you remove the template data below and show this area in the browser) -->
+				<article id="commenttemplate" class="comment"><div class="commentmeta"><span class="nickname">name</span><span class="commentdate">0000-00-00</span></div><div class="commentdata">c</div><button class="btn btn-default btn-sm"><?php echo I18n::_('Reply'); ?></button></article>
+				<div id="commenttailtemplate" class="comment"><button class="btn btn-default btn-sm"><?php echo I18n::_('Add comment'); ?></button></div>
+				<div id="replytemplate" class="reply hidden"><input type="text" id="nickname" class="form-control" title="<?php echo I18n::_('Optional nickname…'); ?>" placeholder="<?php echo I18n::_('Optional nickname…'); ?>" /><textarea id="replymessage" class="replymessage form-control" cols="80" rows="7"></textarea><br /><div id="replystatus" role="alert" class="statusmessage hidden alert"><span class="glyphicon" aria-hidden="true"></span> </div><button id="replybutton" class="btn btn-default btn-sm"><?php echo I18n::_('Post comment'); ?></button></div>
+			</div>
+<?php
+endif;
+?>
+		</div>
         <section class="container">
 			<div id="noscript" role="alert" class="nonworking alert alert-info noscript-hide"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true">
 				<span> <?php echo I18n::_('Loading…'); ?></span><br>

+ 25 - 25
tst/PrivateBinTest.php

@@ -739,10 +739,10 @@ class PrivateBinTest extends PHPUnit_Framework_TestCase
         new PrivateBin;
         $content = ob_get_contents();
         ob_end_clean();
-        $this->assertContains(
-            '<div id="cipherdata" class="hidden">' .
-            htmlspecialchars(Helper::getPasteAsJson(), ENT_NOQUOTES) .
-            '</div>',
+        $this->assertRegExp(
+            '#<div id="cipherdata"[^>]*>' .
+            preg_quote(htmlspecialchars(Helper::getPasteAsJson(), ENT_NOQUOTES)) .
+            '</div>#',
             $content,
             'outputs data correctly'
         );
@@ -760,7 +760,7 @@ class PrivateBinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         ob_end_clean();
         $this->assertRegExp(
-            '#<div[^>]*id="errormessage"[^>]*>.*Invalid paste ID\.</div>#',
+            '#<div[^>]*id="errormessage"[^>]*>.*Invalid paste ID\.#s',
             $content,
             'outputs error correctly'
         );
@@ -778,7 +778,7 @@ class PrivateBinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         ob_end_clean();
         $this->assertRegExp(
-            '#<div[^>]*id="errormessage"[^>]*>.*Paste does not exist[^<]*</div>#',
+            '#<div[^>]*id="errormessage"[^>]*>.*Paste does not exist, has expired or has been deleted\.#s',
             $content,
             'outputs error correctly'
         );
@@ -798,7 +798,7 @@ class PrivateBinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         ob_end_clean();
         $this->assertRegExp(
-            '#<div[^>]*id="errormessage"[^>]*>.*Paste does not exist[^<]*</div>#',
+            '#<div[^>]*id="errormessage"[^>]*>.*Paste does not exist, has expired or has been deleted\.#s',
             $content,
             'outputs error correctly'
         );
@@ -818,10 +818,10 @@ class PrivateBinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         ob_end_clean();
         unset($burnPaste['meta']['salt']);
-        $this->assertContains(
-            '<div id="cipherdata" class="hidden">' .
-            htmlspecialchars(Helper::getPasteAsJson($burnPaste['meta']), ENT_NOQUOTES) .
-            '</div>',
+        $this->assertRegExp(
+            '#<div id="cipherdata"[^>]*>' .
+            preg_quote(htmlspecialchars(Helper::getPasteAsJson($burnPaste['meta']), ENT_NOQUOTES)) .
+            '</div>#',
             $content,
             'outputs data correctly'
         );
@@ -889,10 +889,10 @@ class PrivateBinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         ob_end_clean();
         $meta['formatter'] = 'syntaxhighlighting';
-        $this->assertContains(
-            '<div id="cipherdata" class="hidden">' .
-            htmlspecialchars(Helper::getPasteAsJson($meta), ENT_NOQUOTES) .
-            '</div>',
+        $this->assertRegExp(
+            '#<div id="cipherdata"[^>]*>' .
+            preg_quote(htmlspecialchars(Helper::getPasteAsJson($meta), ENT_NOQUOTES)) .
+            '</div>#',
             $content,
             'outputs data correctly'
         );
@@ -914,10 +914,10 @@ class PrivateBinTest extends PHPUnit_Framework_TestCase
         ob_end_clean();
         $oldPaste['meta']['formatter'] = 'plaintext';
         unset($oldPaste['meta']['salt']);
-        $this->assertContains(
-            '<div id="cipherdata" class="hidden">' .
-            htmlspecialchars(Helper::getPasteAsJson($oldPaste['meta']), ENT_NOQUOTES) .
-            '</div>',
+        $this->assertRegExp(
+            '#<div id="cipherdata"[^>]*>' .
+            preg_quote(htmlspecialchars(Helper::getPasteAsJson($oldPaste['meta']), ENT_NOQUOTES)) .
+            '</div>#',
             $content,
             'outputs data correctly'
         );
@@ -939,7 +939,7 @@ class PrivateBinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         ob_end_clean();
         $this->assertRegExp(
-            '#<div[^>]*id="status"[^>]*>.*Paste was properly deleted[^<]*</div>#s',
+            '#<div[^>]*id="status"[^>]*>.*Paste was properly deleted\.#s',
             $content,
             'outputs deleted status correctly'
         );
@@ -960,7 +960,7 @@ class PrivateBinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         ob_end_clean();
         $this->assertRegExp(
-            '#<div[^>]*id="errormessage"[^>]*>.*Invalid paste ID\.</div>#',
+            '#<div[^>]*id="errormessage"[^>]*>.*Invalid paste ID\.#s',
             $content,
             'outputs delete error correctly'
         );
@@ -980,7 +980,7 @@ class PrivateBinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         ob_end_clean();
         $this->assertRegExp(
-            '#<div[^>]*id="errormessage"[^>]*>.*Paste does not exist[^<]*</div>#',
+            '#<div[^>]*id="errormessage"[^>]*>.*Paste does not exist, has expired or has been deleted\.#s',
             $content,
             'outputs delete error correctly'
         );
@@ -1000,7 +1000,7 @@ class PrivateBinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         ob_end_clean();
         $this->assertRegExp(
-            '#<div[^>]*id="errormessage"[^>]*>.*Wrong deletion token[^<]*</div>#',
+            '#<div[^>]*id="errormessage"[^>]*>.*Wrong deletion token\. Paste was not deleted\.#s',
             $content,
             'outputs delete error correctly'
         );
@@ -1067,7 +1067,7 @@ class PrivateBinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         ob_end_clean();
         $this->assertRegExp(
-            '#<div[^>]*id="errormessage"[^>]*>.*Paste does not exist[^<]*</div>#',
+            '#<div[^>]*id="errormessage"[^>]*>.*Paste does not exist, has expired or has been deleted\.#s',
             $content,
             'outputs error correctly'
         );
@@ -1091,7 +1091,7 @@ class PrivateBinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         ob_end_clean();
         $this->assertRegExp(
-            '#<div[^>]*id="status"[^>]*>.*Paste was properly deleted[^<]*</div>#s',
+            '#<div[^>]*id="status"[^>]*>.*Paste was properly deleted\.#s',
             $content,
             'outputs deleted status correctly'
         );

+ 6 - 6
tst/ViewTest.php

@@ -96,15 +96,15 @@ class ViewTest extends PHPUnit_Framework_TestCase
     public function testTemplateRendersCorrectly()
     {
         foreach ($this->_content as $template => $content) {
-            $this->assertContains(
-                '<div id="cipherdata" class="hidden">' .
-                htmlspecialchars(Helper::getPaste()['data'], ENT_NOQUOTES) .
-                '</div>',
+            $this->assertRegExp(
+                '#<div[^>]+id="cipherdata"[^>]*>' .
+                preg_quote(htmlspecialchars(Helper::getPaste()['data'], ENT_NOQUOTES)) .
+                '</div>#',
                 $content,
                 $template . ': outputs data correctly'
             );
             $this->assertRegExp(
-                '#<div[^>]+id="errormessage"[^>]*>.*' . self::$error . '</div>#',
+                '#<div[^>]+id="errormessage"[^>]*>.*' . self::$error . '#s',
                 $content,
                 $template . ': outputs error correctly'
             );
@@ -119,7 +119,7 @@ class ViewTest extends PHPUnit_Framework_TestCase
                 $template . ': checked discussion if configured'
             );
             $this->assertRegExp(
-                '#<[^>]+id="opendisc"[^>]*>#',
+                '#<[^>]+id="opendiscussionoption"[^>]*>#',
                 $content,
                 $template . ': discussions available if configured'
             );

Некоторые файлы не были показаны из-за большого количества измененных файлов