From c4a0d45adec5bd572d99f671f7171192152a9343 Mon Sep 17 00:00:00 2001
From: simon
Date: Tue, 12 Sep 2017 17:07:31 +0100
Subject: [PATCH 01/83] upversion to 1.0.1-SNAPSHOT
---
project.clj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/project.clj b/project.clj
index 38c96f5..efd7bc2 100644
--- a/project.clj
+++ b/project.clj
@@ -1,4 +1,4 @@
-(defproject smeagol "1.0.0"
+(defproject smeagol "1.0.1-SNAPSHOT"
:description "A simple Git-backed Wiki inspired by Gollum"
:url "https://github.com/simon-brooke/smeagol"
:license {:name "GNU General Public License,version 2.0 or (at your option) any later version"
From 80bb64465b27a02c81b75aa23c5193ffb9e96b1f Mon Sep 17 00:00:00 2001
From: simon
Date: Tue, 12 Sep 2017 17:31:55 +0100
Subject: [PATCH 02/83] Bringing the default content and the GitHub wiki into
line
---
resources/public/content/Deploying Smeagol.md | 21 +------------------
resources/public/content/Docker Image.md | 6 +++---
2 files changed, 4 insertions(+), 23 deletions(-)
diff --git a/resources/public/content/Deploying Smeagol.md b/resources/public/content/Deploying Smeagol.md
index 4bcd304..222d5fe 100644
--- a/resources/public/content/Deploying Smeagol.md
+++ b/resources/public/content/Deploying Smeagol.md
@@ -50,25 +50,6 @@ Smeagol will run as a web-app with the default configuration perfectly satisfact
## Experimental Docker image
-You can now run Smeagol as a [Docker](http://www.docker.com) image. Read more about [[Using the Docker Image]].
+You can now run Smeagol as a [Docker](http://www.docker.com) image. Read more about using the [[Docker Image]].
-To run my Docker image, use
- docker run simonbrooke/smeagol
-
-Smeagol will run, obviously, on the IP address of your Docker image, on port 8080. To find the IP address, start the image using the command above and then use
-
- docker inspect --format '{{ .NetworkSettings.IPAddress }}' $(docker ps -q)
-
-Suppose this prints '10.10.10.10', then the URL to browse to will be http://10.10.10.10:8080/smeagol/
-
-This image is _experimental_, but it does seem to work fairly well. What it does **not** yet do, however, is push the git repository to a remote location, so when you tear the Docker image down your edits will be lost. My next objective for this image is for it to have a cammand line parameter being the git address of a repository from which it can initialise the Wiki content, and to which it will periodically push local changes to the Wiki content.
-
-To build your own Docker image, run:
-
- lein clean
- lein bower install
- lein ring uberwar
- lein docker build
-
-This will build a new Docker image locally; you can, obviously, push it to your own Docker repository if you wish.
diff --git a/resources/public/content/Docker Image.md b/resources/public/content/Docker Image.md
index c4e4b9a..9341628 100644
--- a/resources/public/content/Docker Image.md
+++ b/resources/public/content/Docker Image.md
@@ -3,7 +3,7 @@ Smeagol is available as a Docker image
To run my Docker image, use
docker run -p 127.0.0.1:80:80 simonbrooke/smeagol
-
+
Where 127.0.0.1 is the IP address through which you want to forward port 80 (in real life it wouldn't be 127.0.0.1, but that's safe for testing).
You can then browse to Smeagol by pointing your browser at http://localhost/.
@@ -24,7 +24,7 @@ This works for play purposes. However, it means that any edits made to either th
## Mounting real file systems
-It's possible to mount external file systems, and to override environment variables, with arguments to Docker's extraordinarily complex [run command](https://docs.docker.com/engine/reference/commandline/run/).
+It's possible to mount external file systems, and to override environment variables, with arguments to Docker's extraordinarily complex [run command](https://docs.docker.com/engine/reference/commandline/run/).
I'm currently working with a recipe:
@@ -41,7 +41,7 @@ This works, and uses the default values of the environment variables which are s
## Status
-This image is _experimental_, but it does seem to work fairly well. What it does **not** yet do, however, is push the git repository to a remote location, so when you tear the Docker image down your edits will be lost. My next objective for this image is for it to have a cammand line parameter being the git address of a repository from which it can initialise the Wiki content, and to which it will periodically push local changes to the Wiki content.
+This image is _experimental_, but it does seem to work fairly well. What it does **not** yet do, however, is push the git repository to a remote location, so unless you have mounted an external file store, when you tear the Docker image down your edits will be lost. My next objective for this image is for it to have a cammand line parameter being the git address of a repository from which it can initialise the Wiki content, and to which it will periodically push local changes to the Wiki content.
## Building the Docker image
From 1cfc3876e03876f1eae1bd67cc2ddf325bb3e587 Mon Sep 17 00:00:00 2001
From: simon
Date: Fri, 15 Sep 2017 19:23:31 +0100
Subject: [PATCH 03/83] Translations provided by Soukyan
---
resources/i18n/de-DE.edn | 144 +++++++++++++++++++++++++++++++++++++++
resources/i18n/en-GB.edn | 2 +-
resources/i18n/lt_LT.edn | 143 ++++++++++++++++++++++++++++++++++++++
resources/i18n/ru_RU.edn | 144 +++++++++++++++++++++++++++++++++++++++
4 files changed, 432 insertions(+), 1 deletion(-)
create mode 100644 resources/i18n/de-DE.edn
create mode 100644 resources/i18n/lt_LT.edn
create mode 100644 resources/i18n/ru_RU.edn
diff --git a/resources/i18n/de-DE.edn b/resources/i18n/de-DE.edn
new file mode 100644
index 0000000..e65114d
--- /dev/null
+++ b/resources/i18n/de-DE.edn
@@ -0,0 +1,144 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Smeagol: a very simple Wiki engine.
+;;;;
+;;;; This program is free software; you can redistribute it and/or
+;;;; modify it under the terms of the GNU General Public License
+;;;; as published by the Free Software Foundation; either version 2
+;;;; of the License, or (at your option) any later version.
+;;;;
+;;;; This program is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with this program; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;;; USA.
+;;;;
+;;;; German language translation contributed by and
+;;;; Copyright (C) 2017 Soukyan Blackwood
+;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;; en-GB.edn: English-language messages.
+;;; This is essentially all the text in the chrome - that which isn't editable
+;;; through the wiki itself; and the test in the sanity check report.
+
+;; ; ; ; ; ; ; ; ; ;
+{:add-user-label "Neuen Benutzer zufügen" ;; label for the add user link on edit users page
+ :change-pass-label "Kennwort ändern!"
+ ;; text of the change password widget itself on the
+ ;; change password page
+ :change-pass-link "Kennwort ändern"
+ ;; text of the change password link on the menu
+ :change-pass-prompt "Um Ihr Kennwort zu ändern"
+ ;; text of the change password widget prompt on the
+ ;; change password page
+ :change-col-hdr "Änderungen" ;; header for the changes column in history
+ :chpass-bad-match "Die von Ihnen vorgeschlagenen Kennwörter stimmen nicht überein"
+ ;; error text if proposed passwords don't match
+ :chpass-fail "Ihr Kennwort wurde nicht geändert"
+ ;; error text on fail other htan too short or bad match
+ :chpass-success "Ihr Kennwort wurde geändert"
+ ;; confirmation text on password change
+ :chpass-too-short "Das von Ihnen vorgeschlagene Kennwort war zu kurz: 8 Zeichen erforderlich"
+ ;; error text if proposed password is too short
+ :chpass-title-prefix "Kennwort ändern für"
+ ;; prefix for title of change password page
+ :content-dir "Das Inhaltsverzeichnis"
+ ;; used in sanity check report
+ :content-dir-exists "Das Inhaltsverzeichnis existiert"
+ ;; used in sanity check report
+ :content-dir-is-dir "Das Inhaltsverzeichnis ist ein Verzeichnis"
+ ;; used in sanity check report
+ :cookies-about "Über Cookies" ;; about cookies text
+ :cookies-more "Es wird von Ihrem Browser gelöscht, sobald Sie diese Seite verlassen. Diese Webseite benutzt keine Cookies von dritten Parteien, daher kann Ihr Besuch hier nicht von anderen Seiten verfolgt werden."
+ ;; more about cookies text
+ :default-page-title "Einleitung" ;; title of the default page in this wiki
+ :del-col-hdr "Löschen" ;; header for delete column on edit users page
+ :del-user-fail "Könnte Benutzer nicht löschen"
+ ;; error message on failure to delete user
+ :del-user-success "Benutzer wurde erfolgreich gelöscht"
+ ;; confirmation message on deletion of user
+ :diff-title-prefix "Änderungen seit der Version"
+ ;; prefix for the header of the changes page
+ :does-not-exist "existiert nicht"
+ ;; (of a file or directory); used in sanity check report
+ :edit-col-hdr "Bearbeiten" ;; header for edit column on edit users page
+ :edit-page-link "Diese Seite bearbeiten"
+ ;; text of the edit page link on the content frame
+ :edit-title-prefix "Bearbeiten" ;; prefix for title of edit content page
+ :edit-users-link "Benutzer bearbeiten" ;; text of the edit users link on the menu
+ :edit-users-title "Benutzer zum Bearbeiten auswählen"
+ ;; title of edit users page
+ :email-prompt "E-Mail Adresse" ;; text of the email widget prompt on edit user page
+ :file-or-directory "Datei oder Verzeichnis"
+ ;; used in sanity check report
+ :file-summary-prompt "Beschreibung/was wurde gändert"
+ ;; prompt for the file upload summary input
+ :file-upload-link-text "Sie können auf diese Datei verweisen, mit Hilfe von einer Verknüpfung von der Form"
+ ;; Text introducing the link to an uploaded file
+ :file-upload-prompt "Datei zum Hochladen" ;; prompt string for the file upload widget
+ :file-upload-title "Upload a file" ;; title for the file upload page
+ :is-admin-prompt "Ist Administrator?"
+ :here "hier" ;; used in sanity check report
+ :home-link "Startseite" ;; text of the home link on the menu
+ :is-not-directory "ist kein Verzeichnis"
+ ;; (of a file or directory) used in sanity check report
+ :is-not-readable "ist nicht lesbar"
+ ;; (of a file or directory) used in sanity check report
+ :is-not-writable "ist nicht schreibbar"
+ ;; (of a file or directory) used in sanity check report
+ :login-label "Log in!" ;; text of the login widget on the login page
+ :login-link "Log in" ;; text of the login link on the menu
+ :login-prompt "Um dieses Wiki zu bearbeiten"
+ ;; text of the action widget prompt on the login page
+ :logout-label "Abmelden!" ;; text of the logout widget on the logout page
+ :logout-link "Abmelden" ;; text of the logout link on the menu
+ :logged-in-as "Sie sind angemeldet als"
+ ;; text of the 'logged in as' label on the menu
+ :history-link "Historie" ;; text of the history link on the content frame
+ :history-title-prefix "Historie von" ;; prefix of the title on the history page
+ :new-pass-prompt "Neues Kennwort" ;; text of the new password widget prompt on the change
+ ;; password and edit user pages
+ :no-admin-users "In der Datei 'passwd' gibt es keine Benutzer mit Administratorrechten"
+ ;; used in sanity check report
+ :old-pass-prompt "Ihr Kennwort"
+ ;; text of the old password widget prompt on the change
+ ;; password page, and password widget on login page
+ :password-file "Die Kennwort ('passwd') Datei"
+ ;; used in sanity check report
+ :problems-found "Probleme gefunden"
+ ;; used in sanity check report
+ :rpt-pass-prompt "und noch einmal" ;; text of the new password widget prompt on the change
+ ;; password and edit user pages
+ :save-prompt "Wenn Sie mit dem Bearbeiten fertig sind"
+ ;; text of the save widget label on edit content
+ ;; and edit user page
+ :save-label "Speichern!" ;; text of the save widget itself
+ :save-user-fail "Benutzer konnte nicht gespeichert werden"
+ :save-user-success "Benutzer erfolgreich gespeichert"
+ :see-documentation "Für mehr Information sehen Sie bitte die Dokumentation "
+ ;; used in sanity check report
+ :smeagol-not-initialised
+ "Smeagol ist nicht richtig initialisiert"
+ ;; title of the sanity check report
+ :smeagol-misconfiguration
+ "Smeagol konnte einige der Ressourcen, von denen es abhängt,
+nicht finden; wahrscheinlich wegen einer Fehlkonfiguration oder fehlenden Umgebungsvariablen."
+ ;; used in sanity check report
+ :user-lacks-field "Das Feld für den Benutzereintrag in die passwd Datei fehlt"
+ ;; used in sanity check report
+ :username-prompt "Benutzername" ;; text of the username widget prompt on edit user page
+ ;; text of the is admin widget prompt on edit user page
+ :user-title-prefix "Benutzer bearbeiten" ;; prefix for title of edit user page
+ :vers-col-hdr "Version" ;; header for the version column in history
+ :what-col-hdr "Wann" ;; header for the what column in history
+ :what-changed-prompt "Was haben Sie verändert?"
+ ;; text of the summary widget prompt on edit
+ ;; content page
+ :when-col-hdr "When" ;; header for the when column in history
+ :your-uname-prompt "Ihr Benutzername" ;; text of the username widget prompt on the login page
+ }
diff --git a/resources/i18n/en-GB.edn b/resources/i18n/en-GB.edn
index a7b8789..64c164c 100644
--- a/resources/i18n/en-GB.edn
+++ b/resources/i18n/en-GB.edn
@@ -42,7 +42,7 @@
;; error text on fail other htan too short or bad match
:chpass-success "Your password was changed"
;; confirmation text on password change
- :chpass-too-short "You proposed password wasn't long enough: eight characters required"
+ :chpass-too-short "Your proposed password wasn't long enough: eight characters required"
;; error text if proposed password is too short
:chpass-title-prefix "Change password for"
;; prefix for title of change password page
diff --git a/resources/i18n/lt_LT.edn b/resources/i18n/lt_LT.edn
new file mode 100644
index 0000000..9d585c9
--- /dev/null
+++ b/resources/i18n/lt_LT.edn
@@ -0,0 +1,143 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Smeagol: a very simple Wiki engine.
+;;;;
+;;;; This program is free software; you can redistribute it and/or
+;;;; modify it under the terms of the GNU General Public License
+;;;; as published by the Free Software Foundation; either version 2
+;;;; of the License, or (at your option) any later version.
+;;;;
+;;;; This program is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with this program; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;;; USA.
+;;;;
+;;;; Lithuanian language translation contributed by and
+;;;; Copyright (C) 2017 Soukyan Blackwood
+;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;; en-GB.edn: English-language messages.
+;;; This is essentially all the text in the chrome - that which isn't editable
+;;; through the wiki itself; and the test in the sanity check report.
+
+;; ; ; ; ; ; ; ; ; ;
+{:add-user-label "Pridėti naują naudotoją" ;; label for the add user link on edit users page
+ :change-pass-label "Pakeiskite slaptažodį!"
+ ;; text of the change password widget itself on the
+ ;; change password page
+ :change-pass-link "Pakeiskite slaptažodį"
+ ;; text of the change password link on the menu
+ :change-pass-prompt "Pakeisti slaptažodį"
+ ;; text of the change password widget prompt on the
+ ;; change password page
+ :change-col-hdr "Pakeitimai" ;; header for the changes column in history
+ :chpass-bad-match "Jūsų siūlomi slaptažodžiai - nesutampa"
+ ;; error text if proposed passwords don't match
+ :chpass-fail "Jūsų slaptažodis nebuvo pakeistas"
+ ;; error text on fail other htan too short or bad match
+ :chpass-success "Jūsų slaptažodis buvo pakeistas"
+ ;; confirmation text on password change
+ :chpass-too-short "Jūsų siūlomas slaptožodis – per trumpas: reikia bent aštuonių ženklų"
+ ;; error text if proposed password is too short
+ :chpass-title-prefix "Pakeisti slaptažodį, dėl"
+ ;; prefix for title of change password page
+ :content-dir "Turinio katalogas"
+ ;; used in sanity check report
+ :content-dir-exists "Turinio katalogas egzistuoja"
+ ;; used in sanity check report
+ :content-dir-is-dir "Turinio katalogas yra katalogas"
+ ;; used in sanity check report
+ :cookies-about "About cookies" ;; about cookies text
+ :cookies-more "Šis puslapis saugo jūsų sesijų informaciją jūsų naršyklėje “slapukų” forma. Tai mums leidžia jums rodyti tik tai, ką norite matyti. Slapukai neturi jokių jūsų identifikavimo duomenų ir kiti puslapiai negali jų “perskaityti”. Šie slapukai ištrinami iš karto, vos jūs išjungiate šį puslapį. Šis puslapis nenaudoja jokių trečiųjų asmenų slapukų, ir jūsų apsilankymas čia negali būti atsektas jokio kito puslapio."
+ ;; more about cookies text
+ :default-page-title "Pristatymas" ;; title of the default page in this wiki
+ :del-col-hdr "Ištrinti" ;; header for delete column on edit users page
+ :del-user-fail "Naudotojas negalėjo būti ištrintas"
+ ;; error message on failure to delete user
+ :del-user-success "Naudotojas sėkmingai ištrintas"
+ ;; confirmation message on deletion of user
+ :diff-title-prefix "Pakeitimai nuo versijos"
+ ;; prefix for the header of the changes page
+ :does-not-exist "neegzsituoja"
+ ;; (of a file or directory); used in sanity check report
+ :edit-col-hdr "Keisti" ;; header for edit column on edit users page
+ :edit-page-link "Keisti šį puslapį"
+ ;; text of the edit page link on the content frame
+ :edit-title-prefix "Keisti" ;; prefix for title of edit content page
+ :edit-users-link "Keisti naudotojus" ;; text of the edit users link on the menu
+ :edit-users-title "Pasirinkti naudotojus keitimui"
+ ;; title of edit users page
+ :email-prompt "el. Paštas" ;; text of the email widget prompt on edit user page
+ :file-or-directory "Failas ar katalogas"
+ ;; used in sanity check report
+ :file-summary-prompt "Aprašymas/kas pakeista"
+ ;; prompt for the file upload summary input
+ :file-upload-link-text "Galite nukreipti į šį failą naudodami formos nuorodą"
+ ;; Text introducing the link to an uploaded file
+ :file-upload-prompt "Failas įkėlimui" ;; prompt string for the file upload widget
+ :file-upload-title "Įkelti failą" ;; title for the file upload page
+ :is-admin-prompt "Administratorius?"
+ :here "čia" ;; used in sanity check report
+ :home-link "Pradinis" ;; text of the home link on the menu
+ :is-not-directory "ne katalogas"
+ ;; (of a file or directory) used in sanity check report
+ :is-not-readable "neperskaitomas"
+ ;; (of a file or directory) used in sanity check report
+ :is-not-writable "nerašomas"
+ ;; (of a file or directory) used in sanity check report
+ :login-label "Prisijunkite!" ;; text of the login widget on the login page
+ :login-link "Prisijunkite" ;; text of the login link on the menu
+ :login-prompt "Pakeisti šį viki"
+ ;; text of the action widget prompt on the login page
+ :logout-label "Atsijunkite!" ;; text of the logout widget on the logout page
+ :logout-link "Atsijunkite" ;; text of the logout link on the menu
+ :logged-in-as "Jūs prisijungęs, kaip"
+ ;; text of the 'logged in as' label on the menu
+ :history-link "Istorija" ;; text of the history link on the content frame
+ :history-title-prefix "Istorija apie" ;; prefix of the title on the history page
+ :new-pass-prompt "Naujas slaptažodis" ;; text of the new password widget prompt on the change
+ ;; password and edit user pages
+ :no-admin-users "Naudotojų ‘passwd’ faile su administatoriaus privilegijomis nėra"
+ ;; used in sanity check report
+ :old-pass-prompt "Jūsų slaptažodis"
+ ;; text of the old password widget prompt on the change
+ ;; password page, and password widget on login page
+ :password-file "slaptažodžio (‘passwd’) failas"
+ ;; used in sanity check report
+ :problems-found "rasta problemų"
+ ;; used in sanity check report
+ :rpt-pass-prompt "Ir dar kartą" ;; text of the new password widget prompt on the change
+ ;; password and edit user pages
+ :save-prompt "Kai baigsite redaguoti"
+ ;; text of the save widget label on edit content
+ ;; and edit user page
+ :save-label "Išsaugokite!" ;; text of the save widget itself
+ :save-user-fail "Nepavyko išsaugoti naudotojo"
+ :save-user-success "Naudotojas sėkmingai išsaugotas"
+ :see-documentation "Daugiau informacijos ieškokite dokumentacijoje "
+ ;; used in sanity check report
+ :smeagol-not-initialised
+ "Smygolas buvo blogai paleistas"
+ ;; title of the sanity check report
+ :smeagol-misconfiguration
+ "Smygolas nerado kai kurių jam reikalingų resursų, taip nutikti galėjo dėl neteisingų nustatymų, arba trūkstamų aplinkos kintamųjų"
+ ;; used in sanity check report
+ :user-lacks-field "Naudotojo passwd failo įraše trūksta laukelio"
+ ;; used in sanity check report
+ :username-prompt "Naudotojo vardas" ;; text of the username widget prompt on edit user page
+ ;; text of the is admin widget prompt on edit user page
+ :user-title-prefix "Pakeisti naudotoją" ;; prefix for title of edit user page
+ :vers-col-hdr "Versija" ;; header for the version column in history
+ :what-col-hdr "Kas" ;; header for the what column in history
+ :what-changed-prompt "Ką pakeitėte?"
+ ;; text of the summary widget prompt on edit
+ ;; content page
+ :when-col-hdr "Kada" ;; header for the when column in history
+ :your-uname-prompt "Jūsų naudotojo vardas" ;; text of the username widget prompt on the login page
+ }
diff --git a/resources/i18n/ru_RU.edn b/resources/i18n/ru_RU.edn
new file mode 100644
index 0000000..73400d6
--- /dev/null
+++ b/resources/i18n/ru_RU.edn
@@ -0,0 +1,144 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Smeagol: a very simple Wiki engine.
+;;;;
+;;;; This program is free software; you can redistribute it and/or
+;;;; modify it under the terms of the GNU General Public License
+;;;; as published by the Free Software Foundation; either version 2
+;;;; of the License, or (at your option) any later version.
+;;;;
+;;;; This program is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with this program; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;;; USA.
+;;;;
+;;;; Russian language translation contributed by and
+;;;; Copyright (C) 2017 Soukyan Blackwood
+;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;; en-GB.edn: English-language messages.
+;;; This is essentially all the text in the chrome - that which isn't editable
+;;; through the wiki itself; and the test in the sanity check report.
+
+;; ; ; ; ; ; ; ; ; ;
+{:add-user-label "добавить нового пользователя" ;; label for the add user link on edit users page
+ :change-pass-label "изменить пароль!"
+ ;; text of the change password widget itself on the
+ ;; change password page
+ :change-pass-link "изменить пароль"
+ ;; text of the change password link on the menu
+ :change-pass-prompt "Чтобы изменить пароль"
+ ;; text of the change password widget prompt on the
+ ;; change password page
+ :change-col-hdr "изменения" ;; header for the changes column in history
+ :chpass-bad-match "Ваши предложенные пароли не совпадают"
+ ;; error text if proposed passwords don't match
+ :chpass-fail "Ваш пароль не был изменён"
+ ;; error text on fail other htan too short or bad match
+ :chpass-success "Ваш пароль был изменён"
+ ;; confirmation text on password change
+ :chpass-too-short "Вы предложили пароль недостаточно длины: требуется восемь символов"
+ ;; error text if proposed password is too short
+ :chpass-title-prefix "Изменить пароль для"
+ ;; prefix for title of change password page
+ :content-dir "Каталог содержимого"
+ ;; used in sanity check report
+ :content-dir-exists "Каталог содержимого существуе"
+ ;; used in sanity check report
+ :content-dir-is-dir "Каталог содержимого - это каталог"
+ ;; used in sanity check report
+ :cookies-about "Об кукисах" ;; about cookies text
+ :cookies-more "Этот сайт хранит информацию о сеансе как «cookie» в вашем браузере. Это поможет нам показать вам контент, который вы хотите увидеть. Этот файл cookie не идентифицирует вас и не может быть прочитан другими веб-сайтами. Он удаляется браузером, как только вы покидаете этот сайт. Этот веб-сайт не использует сторонние файлы cookie, поэтому ваш визит здесь не может быть отслежен другими веб-сайтами"
+ ;; more about cookies text
+ :default-page-title "представление" ;; title of the default page in this wiki
+ :del-col-hdr "Удалить" ;; header for delete column on edit users page
+ :del-user-fail "Не удалось удалить пользователя"
+ ;; error message on failure to delete user
+ :del-user-success "успешно удалён пользователь"
+ ;; confirmation message on deletion of user
+ :diff-title-prefix "Изменения с версии"
+ ;; prefix for the header of the changes page
+ :does-not-exist "не существует"
+ ;; (of a file or directory); used in sanity check report
+ :edit-col-hdr "редактировать" ;; header for edit column on edit users page
+ :edit-page-link "Редактировать эту страницу"
+ ;; text of the edit page link on the content frame
+ :edit-title-prefix "редактировать" ;; prefix for title of edit content page
+ :edit-users-link "Редактировать пользователей" ;; text of the edit users link on the menu
+ :edit-users-title "Выберите пользователя для редактирования"
+ ;; title of edit users page
+ :email-prompt "Адрес электронной почты" ;; text of the email widget prompt on edit user page
+ :file-or-directory "Файл или каталог"
+ ;; used in sanity check report
+ :file-summary-prompt "Описание / что изменилось"
+ ;; prompt for the file upload summary input
+ :file-upload-link-text "Вы можете ссылать этот файл, используя ссылку формы"
+ ;; Text introducing the link to an uploaded file
+ :file-upload-prompt "Файл для загрузки" ;; prompt string for the file upload widget
+ :file-upload-title "Загрузить файл" ;; title for the file upload page
+ :is-admin-prompt "Администратор?"
+ :here "здесь" ;; used in sanity check report
+ :home-link "Главная" ;; text of the home link on the menu
+ :is-not-directory "не является каталогом"
+ ;; (of a file or directory) used in sanity check report
+ :is-not-readable "не читаемый"
+ ;; (of a file or directory) used in sanity check report
+ :is-not-writable "недоступен для записи"
+ ;; (of a file or directory) used in sanity check report
+ :login-label "Вход!" ;; text of the login widget on the login page
+ :login-link "Вход" ;; text of the login link on the menu
+ :login-prompt "Чтобы отредактировать эту вики"
+ ;; text of the action widget prompt on the login page
+ :logout-label "Выйти!" ;; text of the logout widget on the logout page
+ :logout-link "Выйти" ;; text of the logout link on the menu
+ :logged-in-as "Вы вошли как"
+ ;; text of the 'logged in as' label on the menu
+ :history-link "история" ;; text of the history link on the content frame
+ :history-title-prefix "История об" ;; prefix of the title on the history page
+ :new-pass-prompt "Новый пароль" ;; text of the new password widget prompt on the change
+ ;; password and edit user pages
+ :no-admin-users "В файле 'passwd' нет пользователей с правами администратора"
+ ;; used in sanity check report
+ :old-pass-prompt "Ваш пароль"
+ ;; text of the old password widget prompt on the change
+ ;; password page, and password widget on login page
+ :password-file "файл пароля ('passwd')"
+ ;; used in sanity check report
+ :problems-found "проблемы были найдены"
+ ;; used in sanity check report
+ :rpt-pass-prompt "И опять" ;; text of the new password widget prompt on the change
+ ;; password and edit user pages
+ :save-prompt "Когда вы закончили редактирование"
+ ;; text of the save widget label on edit content
+ ;; and edit user page
+ :save-label "Сохранить!" ;; text of the save widget itself
+ :save-user-fail "Не удалось сохранить пользователя"
+ :save-user-success "Успешно сохранённый пользователь"
+ :see-documentation "для получения дополнительной информации смотрите документацию "
+ ;; used in sanity check report
+ :smeagol-not-initialised
+ "Смеаголь неправильно инициализирован"
+ ;; title of the sanity check report
+ :smeagol-misconfiguration
+ "Смеаголь не смог найти некоторые ресурсы, от которых это зависит,
+ возможно, из-за неправильной конфигурации или отсутствующих переменных среды."
+ ;; used in sanity check report
+ :user-lacks-field "В пользовательской записи в файле passwd отсутствует поле"
+ ;; used in sanity check report
+ :username-prompt "Имя пользователя" ;; text of the username widget prompt on edit user page
+ ;; text of the is admin widget prompt on edit user page
+ :user-title-prefix "Изменить пользователя" ;; prefix for title of edit user page
+ :vers-col-hdr "Версия" ;; header for the version column in history
+ :what-col-hdr "Что" ;; header for the what column in history
+ :what-changed-prompt "Что вы изменили?"
+ ;; text of the summary widget prompt on edit
+ ;; content page
+ :when-col-hdr "когда" ;; header for the when column in history
+ :your-uname-prompt "Ваш логин" ;; text of the username widget prompt on the login page
+ }
From fbb04e558acbdac9898c2b026e129a141f76830b Mon Sep 17 00:00:00 2001
From: simon
Date: Fri, 15 Sep 2017 21:32:28 +0100
Subject: [PATCH 04/83] Minor fixes to translations
---
resources/config.edn | 4 ++--
resources/i18n/de-DE.edn | 4 ++--
resources/i18n/de.edn | 1 +
resources/i18n/lt.edn | 1 +
resources/i18n/ru.edn | 1 +
resources/public/content/Introduction.md | 3 +++
src/smeagol/routes/wiki.clj | 2 +-
7 files changed, 11 insertions(+), 5 deletions(-)
create mode 120000 resources/i18n/de.edn
create mode 120000 resources/i18n/lt.edn
create mode 120000 resources/i18n/ru.edn
diff --git a/resources/config.edn b/resources/config.edn
index 0fcb152..23b1f58 100644
--- a/resources/config.edn
+++ b/resources/config.edn
@@ -29,12 +29,12 @@
{
:content-dir "resources/public/content"
;; where content is served from.
- :default-locale "en-GB" ;; default language used for messages
+ :default-locale "lt-LT" ;; default language used for messages
:formatters {"vega" smeagol.formatting/process-vega
"vis" smeagol.formatting/process-vega
"mermaid" smeagol.formatting/process-mermaid
"backticks" smeagol.formatting/process-backticks}
- :log-level :info ;; the minimum logging level; one of
+ :log-level :trace ;; the minimum logging level; one of
;; :trace :debug :info :warn :error :fatal
:passwd "resources/passwd"
;; where the password file is stored
diff --git a/resources/i18n/de-DE.edn b/resources/i18n/de-DE.edn
index e65114d..5457d10 100644
--- a/resources/i18n/de-DE.edn
+++ b/resources/i18n/de-DE.edn
@@ -135,10 +135,10 @@ nicht finden; wahrscheinlich wegen einer Fehlkonfiguration oder fehlenden Umgebu
;; text of the is admin widget prompt on edit user page
:user-title-prefix "Benutzer bearbeiten" ;; prefix for title of edit user page
:vers-col-hdr "Version" ;; header for the version column in history
- :what-col-hdr "Wann" ;; header for the what column in history
+ :what-col-hdr "Wass" ;; header for the what column in history
:what-changed-prompt "Was haben Sie verändert?"
;; text of the summary widget prompt on edit
;; content page
- :when-col-hdr "When" ;; header for the when column in history
+ :when-col-hdr "Was" ;; header for the when column in history
:your-uname-prompt "Ihr Benutzername" ;; text of the username widget prompt on the login page
}
diff --git a/resources/i18n/de.edn b/resources/i18n/de.edn
new file mode 120000
index 0000000..d74964e
--- /dev/null
+++ b/resources/i18n/de.edn
@@ -0,0 +1 @@
+de-DE.edn
\ No newline at end of file
diff --git a/resources/i18n/lt.edn b/resources/i18n/lt.edn
new file mode 120000
index 0000000..ef91fe5
--- /dev/null
+++ b/resources/i18n/lt.edn
@@ -0,0 +1 @@
+lt_LT.edn
\ No newline at end of file
diff --git a/resources/i18n/ru.edn b/resources/i18n/ru.edn
new file mode 120000
index 0000000..4a740ce
--- /dev/null
+++ b/resources/i18n/ru.edn
@@ -0,0 +1 @@
+ru_RU.edn
\ No newline at end of file
diff --git a/resources/public/content/Introduction.md b/resources/public/content/Introduction.md
index 6709957..07d263f 100644
--- a/resources/public/content/Introduction.md
+++ b/resources/public/content/Introduction.md
@@ -17,6 +17,9 @@ Smeagol uses the Markdown format as provided by [markdown-clj](https://github.co
## Security and authentication
Smeagol now has good security and authentication. While the initial password supplied with the system is not encrypted, when it is changed it will be; and passwords for new users added through the user administration pages are encrypted. Read more about [[Security and authentication]].
+## Internationalisation
+Smeagol has built in internationalisation. Currently it has translation files for English, German, Lithuanian and Russian. We'd welcome volunteers to translate it into other languages.
+
## Images
You can (if you're logged in) upload files, including images, using the **Upload a file** link on the top menu bar. You can link to an uploaded image, or to other images already available on the web, like this:
diff --git a/src/smeagol/routes/wiki.clj b/src/smeagol/routes/wiki.clj
index fe80349..28e97fb 100644
--- a/src/smeagol/routes/wiki.clj
+++ b/src/smeagol/routes/wiki.clj
@@ -141,7 +141,7 @@
(timbre/info (format "Showing history of page '%s'" page))
(layout/render "history.html"
(merge (util/standard-params request)
- {:title (str "History of " page)
+ {:title (util/get-message :history-title-prefix request)
:page page
:history (hist/find-history repo-path file-name)}))))
From 45dc3017dd7407425bcb3da6e34f20d47a00f629 Mon Sep 17 00:00:00 2001
From: simon
Date: Fri, 15 Sep 2017 21:37:03 +0100
Subject: [PATCH 05/83] And one translation I'd missed...
---
resources/i18n/lt_LT.edn | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/i18n/lt_LT.edn b/resources/i18n/lt_LT.edn
index 9d585c9..13185d0 100644
--- a/resources/i18n/lt_LT.edn
+++ b/resources/i18n/lt_LT.edn
@@ -53,7 +53,7 @@
;; used in sanity check report
:content-dir-is-dir "Turinio katalogas yra katalogas"
;; used in sanity check report
- :cookies-about "About cookies" ;; about cookies text
+ :cookies-about "Apie slapukus" ;; about cookies text
:cookies-more "Šis puslapis saugo jūsų sesijų informaciją jūsų naršyklėje “slapukų” forma. Tai mums leidžia jums rodyti tik tai, ką norite matyti. Slapukai neturi jokių jūsų identifikavimo duomenų ir kiti puslapiai negali jų “perskaityti”. Šie slapukai ištrinami iš karto, vos jūs išjungiate šį puslapį. Šis puslapis nenaudoja jokių trečiųjų asmenų slapukų, ir jūsų apsilankymas čia negali būti atsektas jokio kito puslapio."
;; more about cookies text
:default-page-title "Pristatymas" ;; title of the default page in this wiki
From 1e2e9ea49b0d88ddebc80ea0d18059dfe369e408 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Sat, 23 Sep 2017 15:18:06 +0100
Subject: [PATCH 06/83] Enabled committing during lein release with an unsigned
tag.
---
project.clj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/project.clj b/project.clj
index efd7bc2..2ba7ad2 100644
--- a/project.clj
+++ b/project.clj
@@ -59,7 +59,7 @@
:release-tasks [["vcs" "assert-committed"]
["change" "version" "leiningen.release/bump-version" "release"]
["vcs" "commit"]
- ;; ["vcs" "tag"] -- not working, problems with secret key
+ ["vcs" "tag" "v." "--no-sign"]
["clean"]
["bower" "install"]
["ring" "uberjar"]
From cd25d8bd761adac650035a801f3d9f9a351c40f1 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Sat, 23 Sep 2017 15:18:35 +0100
Subject: [PATCH 07/83] Version 1.0.1
---
project.clj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/project.clj b/project.clj
index 2ba7ad2..51fb2f3 100644
--- a/project.clj
+++ b/project.clj
@@ -1,4 +1,4 @@
-(defproject smeagol "1.0.1-SNAPSHOT"
+(defproject smeagol "1.0.1"
:description "A simple Git-backed Wiki inspired by Gollum"
:url "https://github.com/simon-brooke/smeagol"
:license {:name "GNU General Public License,version 2.0 or (at your option) any later version"
From 6be21214b0267c18d203bef0c44768804e25bb0b Mon Sep 17 00:00:00 2001
From: jem
Date: Tue, 15 May 2018 18:50:54 +0200
Subject: [PATCH 08/83] start with some thinking about the problem ...
---
doc/include.md | 14 ++++++++++++++
1 file changed, 14 insertions(+)
create mode 100644 doc/include.md
diff --git a/doc/include.md b/doc/include.md
new file mode 100644
index 0000000..8614fc9
--- /dev/null
+++ b/doc/include.md
@@ -0,0 +1,14 @@
+# Include Feature
+## Requirements
+The user can include page title, abstract or the whole content in a given page. Headings and enumerations can be indented by a given include-level.
+
+## Thoughts & Questions
+* Which include syntax should be used?
+ * page include can be definde alongsite of image includes - sth. like `#[indent-level](relative or absolute url)`
+* Which kind of urls should we accept for page includes?
+ * relative local urls (we will need some care to prohibit directory traversal ...)
+ * absolute github / gitlab / gitblit urls without authentication.
+* Which metadata can be used for title / abstract ?
+ * MultiMarcdown-Metadata is supported by clj-markdown :-)
+* How can we test page includes?
+ * we will need a content resolver component for testing and at least one for production resolving.
From 464e9af7d63a89c2625fee34bf3cc5cacc541df1 Mon Sep 17 00:00:00 2001
From: jem
Date: Tue, 15 May 2018 19:31:24 +0200
Subject: [PATCH 09/83] use dependency injection in order to make include
resolving testable
---
project.clj | 3 ++-
src/smeagol/include.clj | 11 +++++++++++
test/smeagol/test/include.clj | 16 ++++++++++++++++
3 files changed, 29 insertions(+), 1 deletion(-)
create mode 100644 src/smeagol/include.clj
create mode 100644 test/smeagol/test/include.clj
diff --git a/project.clj b/project.clj
index 38c96f5..86806dd 100644
--- a/project.clj
+++ b/project.clj
@@ -29,7 +29,8 @@
[prone "1.1.4"]
[ring/ring-anti-forgery "1.1.0"]
[ring-server "0.4.0"]
- [selmer "1.11.0"]]
+ [selmer "1.11.0"]
+ [com.stuartsierra/component "0.3.2"]]
:repl-options {:init-ns smeagol.repl}
diff --git a/src/smeagol/include.clj b/src/smeagol/include.clj
new file mode 100644
index 0000000..adc361f
--- /dev/null
+++ b/src/smeagol/include.clj
@@ -0,0 +1,11 @@
+(ns smeagol.include
+ (:require
+ [com.stuartsierra.component :as component]))
+
+(defrecord Resolver [resolver-fn])
+
+(defn new-resolver [resolver-fn]
+ (map->Resolver {:resolver-fn resolver-fn}))
+
+(defn resolve-include [resolver uri]
+ (apply (:resolver-fn resolver) [uri]))
diff --git a/test/smeagol/test/include.clj b/test/smeagol/test/include.clj
new file mode 100644
index 0000000..143eb61
--- /dev/null
+++ b/test/smeagol/test/include.clj
@@ -0,0 +1,16 @@
+(ns smeagol.test.include
+ (:require [clojure.test :refer :all]
+ [com.stuartsierra.component :as component]
+ [smeagol.include :as sut]))
+
+(defn test-include-resolver [uri]
+ (cond
+ (= uri "./simple.md") "Simple content."))
+
+(def system-under-test
+ (component/system-map
+ :resolver (sut/new-resolver test-include-resolver)))
+
+(deftest test-local-links
+ (testing "Rewriting of local links"
+ (is (= "Simple content." (sut/resolve-include (:resolver system-under-test) "./simple.md")))))
From 9607657cc12558f3ba1d49a26ad15fb0bc5d4699 Mon Sep 17 00:00:00 2001
From: jem
Date: Wed, 16 May 2018 18:41:54 +0200
Subject: [PATCH 10/83] added schema & use separated resolver & includer
component.
---
project.clj | 3 +-
src/smeagol/include.clj | 27 ++++++++++++++----
src/smeagol/include/resolver.clj | 38 ++++++++++++++++++++++++++
test/smeagol/test/include.clj | 24 +++++++++++++---
test/smeagol/test/include/resolver.clj | 8 ++++++
5 files changed, 89 insertions(+), 11 deletions(-)
create mode 100644 src/smeagol/include/resolver.clj
create mode 100644 test/smeagol/test/include/resolver.clj
diff --git a/project.clj b/project.clj
index 86806dd..9f20be0 100644
--- a/project.clj
+++ b/project.clj
@@ -30,7 +30,8 @@
[ring/ring-anti-forgery "1.1.0"]
[ring-server "0.4.0"]
[selmer "1.11.0"]
- [com.stuartsierra/component "0.3.2"]]
+ [com.stuartsierra/component "0.3.2"]
+ [prismatic/schema "1.1.9"]]
:repl-options {:init-ns smeagol.repl}
diff --git a/src/smeagol/include.clj b/src/smeagol/include.clj
index adc361f..80d9640 100644
--- a/src/smeagol/include.clj
+++ b/src/smeagol/include.clj
@@ -1,11 +1,26 @@
(ns smeagol.include
(:require
- [com.stuartsierra.component :as component]))
+ [schema.core :as s]
+ [com.stuartsierra.component :as component]
+ [smeagol.include.resolver :as resolver]))
-(defrecord Resolver [resolver-fn])
+(s/defrecord Includer
+ [resolver])
-(defn new-resolver [resolver-fn]
- (map->Resolver {:resolver-fn resolver-fn}))
+(defprotocol IncludeMd
+ (expand-include-md
+ [includer md-src]
+ "return a markfown file content for given uri."))
-(defn resolve-include [resolver uri]
- (apply (:resolver-fn resolver) [uri]))
+(extend-type Includer
+ IncludeMd
+ (expand-include-md [includer md-src]
+ ;parse md-src
+ ;resolve found includes
+ ;indent & integrate
+ md-src))
+
+(s/defn
+ new-includer
+ []
+ (map->Includer {}))
diff --git a/src/smeagol/include/resolver.clj b/src/smeagol/include/resolver.clj
new file mode 100644
index 0000000..cca0934
--- /dev/null
+++ b/src/smeagol/include/resolver.clj
@@ -0,0 +1,38 @@
+(ns smeagol.include.resolver
+ (:require
+ [schema.core :as s]
+ [com.stuartsierra.component :as component]))
+
+(s/defrecord Resolver
+ [type :- s/Keyword])
+
+;As schema does'nt support s/defprotocol we use the dispatcher for annotation & validation.
+(s/defn dispatch-by-resolver-type :- s/Keyword
+ "Dispatcher for different resolver implementations."
+ [resolver :- Resolver
+ uri :- s/Str]
+ (:type resolver))
+
+(defmulti do-resolve-md
+ "Multimethod return a markfown file content for given uri."
+ dispatch-by-resolver-type)
+(s/defmethod do-resolve-md :default
+ [resolver :- Resolver
+ uri :- s/Str]
+ (throw (Exception. (str "No implementation for " resolver))))
+
+(defprotocol ResolveMd
+ (resolve-md
+ [resolver uri]
+ "return a markfown file content for given uru."))
+
+(extend-type Resolver
+ ResolveMd
+ (resolve-md [resolver uri]
+ (s/validate s/Str uri)
+ (s/validate s/Str (do-resolve-md resolver uri))))
+
+(s/defn
+ new-resolver
+ [type :- s/Keyword]
+ (map->Resolver {:type type}))
diff --git a/test/smeagol/test/include.clj b/test/smeagol/test/include.clj
index 143eb61..749482b 100644
--- a/test/smeagol/test/include.clj
+++ b/test/smeagol/test/include.clj
@@ -1,16 +1,32 @@
(ns smeagol.test.include
(:require [clojure.test :refer :all]
+ [schema.core :as s]
[com.stuartsierra.component :as component]
+ [smeagol.include.resolver :as resolver]
[smeagol.include :as sut]))
-(defn test-include-resolver [uri]
+(s/defmethod resolver/do-resolve-md :test-mock
+ [resolver
+ uri :- s/Str]
(cond
(= uri "./simple.md") "Simple content."))
(def system-under-test
(component/system-map
- :resolver (sut/new-resolver test-include-resolver)))
+ :resolver (resolver/new-resolver :test-mock)
+ :includer (component/using
+ (sut/new-includer)
+ {:resolver :resolver})))
-(deftest test-local-links
+(deftest test-expand-include-md
(testing "Rewriting of local links"
- (is (= "Simple content." (sut/resolve-include (:resolver system-under-test) "./simple.md")))))
+ (is
+ (= "# Heading"
+ (sut/expand-include-md (:includer system-under-test) "# Heading")))
+ (is
+ (= "# Heading 1
+ Simple content."
+ (sut/expand-include-md
+ (:includer system-under-test)
+ "# Heading1
+#[](.simple.md)")))))
diff --git a/test/smeagol/test/include/resolver.clj b/test/smeagol/test/include/resolver.clj
new file mode 100644
index 0000000..fd080d9
--- /dev/null
+++ b/test/smeagol/test/include/resolver.clj
@@ -0,0 +1,8 @@
+(ns smeagol.test.include.resolver
+ (:require [clojure.test :refer :all]
+ [smeagol.include.resolver :as sut]))
+
+(deftest test-local-links
+ (testing "Rewriting of local links"
+ (is (thrown? Exception
+ (sut/resolve-md (sut/new-resolver (:default)) "./some-uri.md")))))
From 6918ba27e8198cb069b5212b0d252469f436285b Mon Sep 17 00:00:00 2001
From: jem
Date: Thu, 17 May 2018 09:08:09 +0200
Subject: [PATCH 11/83] implemented simple parsing
---
doc/include.md | 3 ++-
src/smeagol/include.clj | 16 +++++++++++
test/smeagol/test/include.clj | 50 ++++++++++++++++++++++++++++++++---
3 files changed, 64 insertions(+), 5 deletions(-)
diff --git a/doc/include.md b/doc/include.md
index 8614fc9..f8b115c 100644
--- a/doc/include.md
+++ b/doc/include.md
@@ -4,7 +4,8 @@ The user can include page title, abstract or the whole content in a given page.
## Thoughts & Questions
* Which include syntax should be used?
- * page include can be definde alongsite of image includes - sth. like `#[indent-level](relative or absolute url)`
+ * page include can be definde alongsite of image includes - sth. like
+ `&[:indent-heading s/Num :indent-list s/Num](relative or absolute url s/Str)`
* Which kind of urls should we accept for page includes?
* relative local urls (we will need some care to prohibit directory traversal ...)
* absolute github / gitlab / gitblit urls without authentication.
diff --git a/src/smeagol/include.clj b/src/smeagol/include.clj
index 80d9640..6052ff6 100644
--- a/src/smeagol/include.clj
+++ b/src/smeagol/include.clj
@@ -24,3 +24,19 @@
new-includer
[]
(map->Includer {}))
+
+(def IncludeLink
+ {:uri s/Str
+ :indent-heading s/Num
+ :indent-list s/Num})
+
+(s/defn
+ parse-include-md :- [IncludeLink]
+ [md-src :- s/Str]
+ (vec
+ (map
+ (fn [parse-element]
+ {:uri (nth parse-element 5)
+ :indent-heading 0
+ :indent-list 0})
+ (re-seq #"&\[(:indent-heading (d*))?w*(:indent-list (d*))?\]\((.*)\)" md-src))))
diff --git a/test/smeagol/test/include.clj b/test/smeagol/test/include.clj
index 749482b..99ee31d 100644
--- a/test/smeagol/test/include.clj
+++ b/test/smeagol/test/include.clj
@@ -5,6 +5,49 @@
[smeagol.include.resolver :as resolver]
[smeagol.include :as sut]))
+(def include-simple
+ "# Heading1
+&[](./simple.md)")
+
+(def include-surounding-simple
+ "# Heading1
+Some surounding &[](./simple.md) text")
+
+(def include-heading-0
+ "# Heading1
+&[:indent-heading 0](./with-heading.md)")
+
+(def include-heading-list-1
+ "# Heading1
+&[:indent-heading 1 :indent-list 1](./with-heading-and-list.md)")
+
+(def include-heading-list-0
+ "# Heading1
+&[:indent-heading 0 :indent-list 0](./with-heading-and-list.md)")
+
+(def include-invalid-indent
+ "# Heading1
+&[ invalid input should default to indent 0 ](./simple.md)")
+
+(def include-spaced-indent
+ "# Heading1
+&[ :indent-heading 0 :indent-list 0 ](./with-heading-and-list.md)")
+
+
+(deftest test-parse-include-md
+ (testing "parse include links"
+ (is
+ (= []
+ (sut/parse-include-md "# Heading")))
+ (is
+ (= [{:uri "./simple.md", :indent-heading 0, :indent-list 0}]
+ (sut/parse-include-md
+ include-simple)))
+ (is
+ (= [{:uri "./simple.md", :indent-heading 1, :indent-list 1}]
+ (sut/parse-include-md
+ include-heading-list-1)))))
+
(s/defmethod resolver/do-resolve-md :test-mock
[resolver
uri :- s/Str]
@@ -19,14 +62,13 @@
{:resolver :resolver})))
(deftest test-expand-include-md
- (testing "Rewriting of local links"
+ (testing "The whole integration of include"
(is
(= "# Heading"
(sut/expand-include-md (:includer system-under-test) "# Heading")))
(is
(= "# Heading 1
- Simple content."
+Simple content."
(sut/expand-include-md
(:includer system-under-test)
- "# Heading1
-#[](.simple.md)")))))
+ include-simple)))))
From 4025b1e29caf8bb7d1146503ee8b59941d981cb0 Mon Sep 17 00:00:00 2001
From: jem
Date: Fri, 18 May 2018 12:05:13 +0200
Subject: [PATCH 12/83] include link parsing now works
---
src/smeagol/include.clj | 32 ++++++++++++++++++++++++++++----
test/smeagol/test/include.clj | 28 ++++++++++++++++++++++++----
2 files changed, 52 insertions(+), 8 deletions(-)
diff --git a/src/smeagol/include.clj b/src/smeagol/include.clj
index 6052ff6..74e8cdb 100644
--- a/src/smeagol/include.clj
+++ b/src/smeagol/include.clj
@@ -30,13 +30,37 @@
:indent-heading s/Num
:indent-list s/Num})
+(s/defn
+ convert-indent-to-int :- s/Num
+ [indents :- [s/Str]]
+ (if (some? indents)
+ (Integer/valueOf (nth indents 2))
+ 0))
+
+(s/defn
+ parse-indent-list
+ [md-src :- s/Str]
+ (re-matches #".*(:indent-list (\d)).*" md-src))
+
+(s/defn
+ parse-indent-heading
+ [md-src :- s/Str]
+ (re-matches #".*(:indent-heading (\d)).*" md-src))
+
+(s/defn
+ parse-include-link
+ [md-src :- s/Str]
+ (re-seq #".*&\[\w*(.*)\w*\]\((.*)\).*" md-src))
+
(s/defn
parse-include-md :- [IncludeLink]
[md-src :- s/Str]
(vec
(map
(fn [parse-element]
- {:uri (nth parse-element 5)
- :indent-heading 0
- :indent-list 0})
- (re-seq #"&\[(:indent-heading (d*))?w*(:indent-list (d*))?\]\((.*)\)" md-src))))
+ (let [uri (nth parse-element 2)
+ indents-text (nth parse-element 1)]
+ {:uri uri
+ :indent-heading (convert-indent-to-int (parse-indent-heading indents-text))
+ :indent-list (convert-indent-to-int (parse-indent-list indents-text))}))
+ (parse-include-link md-src))))
diff --git a/test/smeagol/test/include.clj b/test/smeagol/test/include.clj
index 99ee31d..ae602b2 100644
--- a/test/smeagol/test/include.clj
+++ b/test/smeagol/test/include.clj
@@ -23,7 +23,7 @@ Some surounding &[](./simple.md) text")
(def include-heading-list-0
"# Heading1
-&[:indent-heading 0 :indent-list 0](./with-heading-and-list.md)")
+&[:indent-list 0 :indent-heading 0](./with-heading-and-list.md)")
(def include-invalid-indent
"# Heading1
@@ -31,7 +31,7 @@ Some surounding &[](./simple.md) text")
(def include-spaced-indent
"# Heading1
-&[ :indent-heading 0 :indent-list 0 ](./with-heading-and-list.md)")
+&[ :indent-heading 2 :indent-list 33 ](./with-heading-and-list.md)")
(deftest test-parse-include-md
@@ -44,9 +44,29 @@ Some surounding &[](./simple.md) text")
(sut/parse-include-md
include-simple)))
(is
- (= [{:uri "./simple.md", :indent-heading 1, :indent-list 1}]
+ (= [{:uri "./simple.md", :indent-heading 0, :indent-list 0}]
(sut/parse-include-md
- include-heading-list-1)))))
+ include-surounding-simple)))
+ (is
+ (= [{:uri "./with-heading.md", :indent-heading 0, :indent-list 0}]
+ (sut/parse-include-md
+ include-heading-0)))
+ (is
+ (= [{:uri "./with-heading-and-list.md", :indent-heading 1, :indent-list 1}]
+ (sut/parse-include-md
+ include-heading-list-1)))
+ (is
+ (= [{:uri "./with-heading-and-list.md", :indent-heading 0, :indent-list 0}]
+ (sut/parse-include-md
+ include-heading-list-0)))
+ (is
+ (= [{:uri "./simple.md", :indent-heading 0, :indent-list 0}]
+ (sut/parse-include-md
+ include-invalid-indent)))
+ (is
+ (= [{:uri "./with-heading-and-list.md", :indent-heading 2, :indent-list 0}]
+ (sut/parse-include-md
+ include-spaced-indent)))))
(s/defmethod resolver/do-resolve-md :test-mock
[resolver
From 7d479e95b16a9fcca6b1af49f62799c0847be690 Mon Sep 17 00:00:00 2001
From: jem
Date: Fri, 18 May 2018 12:09:16 +0200
Subject: [PATCH 13/83] add one more parsing testcase
---
test/smeagol/test/include.clj | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/test/smeagol/test/include.clj b/test/smeagol/test/include.clj
index ae602b2..860fcf6 100644
--- a/test/smeagol/test/include.clj
+++ b/test/smeagol/test/include.clj
@@ -33,6 +33,13 @@ Some surounding &[](./simple.md) text")
"# Heading1
&[ :indent-heading 2 :indent-list 33 ](./with-heading-and-list.md)")
+(def multi
+ "# Heading1
+&[ :indent-heading 2 :indent-list 33 ](./with-heading-and-list.md)
+some text
+&[](./simple.md)
+more text.")
+
(deftest test-parse-include-md
(testing "parse include links"
@@ -64,9 +71,16 @@ Some surounding &[](./simple.md) text")
(sut/parse-include-md
include-invalid-indent)))
(is
- (= [{:uri "./with-heading-and-list.md", :indent-heading 2, :indent-list 0}]
+ (= [{:uri "./with-heading-and-list.md", :indent-heading 2, :indent-list 3}]
(sut/parse-include-md
- include-spaced-indent)))))
+ include-spaced-indent)))
+ (is
+ (= [{:uri "./with-heading-and-list.md",
+ :indent-heading 2,
+ :indent-list 3}
+ {:uri "./simple.md", :indent-heading 0, :indent-list 0}]
+ (sut/parse-include-md
+ multi)))))
(s/defmethod resolver/do-resolve-md :test-mock
[resolver
From 4a4269d2022945ea64ad612c72a5685d63fe162e Mon Sep 17 00:00:00 2001
From: jem
Date: Fri, 18 May 2018 12:32:49 +0200
Subject: [PATCH 14/83] renamed resolver -> resolve
---
src/smeagol/include/{resolver.clj => resolve.clj} | 2 +-
test/smeagol/test/include/{resolver.clj => resolve.clj} | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
rename src/smeagol/include/{resolver.clj => resolve.clj} (97%)
rename test/smeagol/test/include/{resolver.clj => resolve.clj} (71%)
diff --git a/src/smeagol/include/resolver.clj b/src/smeagol/include/resolve.clj
similarity index 97%
rename from src/smeagol/include/resolver.clj
rename to src/smeagol/include/resolve.clj
index cca0934..13a67ce 100644
--- a/src/smeagol/include/resolver.clj
+++ b/src/smeagol/include/resolve.clj
@@ -1,4 +1,4 @@
-(ns smeagol.include.resolver
+(ns smeagol.include.resolve
(:require
[schema.core :as s]
[com.stuartsierra.component :as component]))
diff --git a/test/smeagol/test/include/resolver.clj b/test/smeagol/test/include/resolve.clj
similarity index 71%
rename from test/smeagol/test/include/resolver.clj
rename to test/smeagol/test/include/resolve.clj
index fd080d9..4da32ed 100644
--- a/test/smeagol/test/include/resolver.clj
+++ b/test/smeagol/test/include/resolve.clj
@@ -1,6 +1,6 @@
-(ns smeagol.test.include.resolver
+(ns smeagol.test.include.resolve
(:require [clojure.test :refer :all]
- [smeagol.include.resolver :as sut]))
+ [smeagol.include.resolve :as sut]))
(deftest test-local-links
(testing "Rewriting of local links"
From 6714dc04bfcb0f1041b8677949abe6abc2e1324e Mon Sep 17 00:00:00 2001
From: jem
Date: Fri, 18 May 2018 12:33:07 +0200
Subject: [PATCH 15/83] refactor parse out of include ns
---
src/smeagol/include.clj | 51 +++---------------
src/smeagol/include/parse.clj | 43 +++++++++++++++
test/smeagol/test/include.clj | 48 ++---------------
test/smeagol/test/include/parse.clj | 81 +++++++++++++++++++++++++++++
4 files changed, 133 insertions(+), 90 deletions(-)
create mode 100644 src/smeagol/include/parse.clj
create mode 100644 test/smeagol/test/include/parse.clj
diff --git a/src/smeagol/include.clj b/src/smeagol/include.clj
index 74e8cdb..14a97da 100644
--- a/src/smeagol/include.clj
+++ b/src/smeagol/include.clj
@@ -2,7 +2,8 @@
(:require
[schema.core :as s]
[com.stuartsierra.component :as component]
- [smeagol.include.resolver :as resolver]))
+ [smeagol.include.parse :as parse]
+ [smeagol.include.resolve :as resolve]))
(s/defrecord Includer
[resolver])
@@ -15,52 +16,12 @@
(extend-type Includer
IncludeMd
(expand-include-md [includer md-src]
- ;parse md-src
- ;resolve found includes
- ;indent & integrate
- md-src))
+ (let [includes (parse/parse-include-md md-src)]
+ ;resolve found includes
+ ;indent & integrate
+ md-src)))
(s/defn
new-includer
[]
(map->Includer {}))
-
-(def IncludeLink
- {:uri s/Str
- :indent-heading s/Num
- :indent-list s/Num})
-
-(s/defn
- convert-indent-to-int :- s/Num
- [indents :- [s/Str]]
- (if (some? indents)
- (Integer/valueOf (nth indents 2))
- 0))
-
-(s/defn
- parse-indent-list
- [md-src :- s/Str]
- (re-matches #".*(:indent-list (\d)).*" md-src))
-
-(s/defn
- parse-indent-heading
- [md-src :- s/Str]
- (re-matches #".*(:indent-heading (\d)).*" md-src))
-
-(s/defn
- parse-include-link
- [md-src :- s/Str]
- (re-seq #".*&\[\w*(.*)\w*\]\((.*)\).*" md-src))
-
-(s/defn
- parse-include-md :- [IncludeLink]
- [md-src :- s/Str]
- (vec
- (map
- (fn [parse-element]
- (let [uri (nth parse-element 2)
- indents-text (nth parse-element 1)]
- {:uri uri
- :indent-heading (convert-indent-to-int (parse-indent-heading indents-text))
- :indent-list (convert-indent-to-int (parse-indent-list indents-text))}))
- (parse-include-link md-src))))
diff --git a/src/smeagol/include/parse.clj b/src/smeagol/include/parse.clj
new file mode 100644
index 0000000..0c4dbae
--- /dev/null
+++ b/src/smeagol/include/parse.clj
@@ -0,0 +1,43 @@
+(ns smeagol.include.parse
+ (:require
+ [schema.core :as s]))
+
+(def IncludeLink
+ {:uri s/Str
+ :indent-heading s/Num
+ :indent-list s/Num})
+
+(s/defn
+ convert-indent-to-int :- s/Num
+ [indents :- [s/Str]]
+ (if (some? indents)
+ (Integer/valueOf (nth indents 2))
+ 0))
+
+(s/defn
+ parse-indent-list
+ [md-src :- s/Str]
+ (re-matches #".*(:indent-list (\d)).*" md-src))
+
+(s/defn
+ parse-indent-heading
+ [md-src :- s/Str]
+ (re-matches #".*(:indent-heading (\d)).*" md-src))
+
+(s/defn
+ parse-include-link
+ [md-src :- s/Str]
+ (re-seq #".*&\[\w*(.*)\w*\]\((.*)\).*" md-src))
+
+(s/defn
+ parse-include-md :- [IncludeLink]
+ [md-src :- s/Str]
+ (vec
+ (map
+ (fn [parse-element]
+ (let [uri (nth parse-element 2)
+ indents-text (nth parse-element 1)]
+ {:uri uri
+ :indent-heading (convert-indent-to-int (parse-indent-heading indents-text))
+ :indent-list (convert-indent-to-int (parse-indent-list indents-text))}))
+ (parse-include-link md-src))))
diff --git a/test/smeagol/test/include.clj b/test/smeagol/test/include.clj
index 860fcf6..45219e1 100644
--- a/test/smeagol/test/include.clj
+++ b/test/smeagol/test/include.clj
@@ -2,7 +2,7 @@
(:require [clojure.test :refer :all]
[schema.core :as s]
[com.stuartsierra.component :as component]
- [smeagol.include.resolver :as resolver]
+ [smeagol.include.resolve :as resolve]
[smeagol.include :as sut]))
(def include-simple
@@ -40,49 +40,7 @@ some text
&[](./simple.md)
more text.")
-
-(deftest test-parse-include-md
- (testing "parse include links"
- (is
- (= []
- (sut/parse-include-md "# Heading")))
- (is
- (= [{:uri "./simple.md", :indent-heading 0, :indent-list 0}]
- (sut/parse-include-md
- include-simple)))
- (is
- (= [{:uri "./simple.md", :indent-heading 0, :indent-list 0}]
- (sut/parse-include-md
- include-surounding-simple)))
- (is
- (= [{:uri "./with-heading.md", :indent-heading 0, :indent-list 0}]
- (sut/parse-include-md
- include-heading-0)))
- (is
- (= [{:uri "./with-heading-and-list.md", :indent-heading 1, :indent-list 1}]
- (sut/parse-include-md
- include-heading-list-1)))
- (is
- (= [{:uri "./with-heading-and-list.md", :indent-heading 0, :indent-list 0}]
- (sut/parse-include-md
- include-heading-list-0)))
- (is
- (= [{:uri "./simple.md", :indent-heading 0, :indent-list 0}]
- (sut/parse-include-md
- include-invalid-indent)))
- (is
- (= [{:uri "./with-heading-and-list.md", :indent-heading 2, :indent-list 3}]
- (sut/parse-include-md
- include-spaced-indent)))
- (is
- (= [{:uri "./with-heading-and-list.md",
- :indent-heading 2,
- :indent-list 3}
- {:uri "./simple.md", :indent-heading 0, :indent-list 0}]
- (sut/parse-include-md
- multi)))))
-
-(s/defmethod resolver/do-resolve-md :test-mock
+(s/defmethod resolve/do-resolve-md :test-mock
[resolver
uri :- s/Str]
(cond
@@ -90,7 +48,7 @@ more text.")
(def system-under-test
(component/system-map
- :resolver (resolver/new-resolver :test-mock)
+ :resolver (resolve/new-resolver :test-mock)
:includer (component/using
(sut/new-includer)
{:resolver :resolver})))
diff --git a/test/smeagol/test/include/parse.clj b/test/smeagol/test/include/parse.clj
new file mode 100644
index 0000000..60a22bf
--- /dev/null
+++ b/test/smeagol/test/include/parse.clj
@@ -0,0 +1,81 @@
+(ns smeagol.test.include.parse
+ (:require [clojure.test :refer :all]
+ [schema.core :as s]
+ [smeagol.include.parse :as sut]))
+
+(def include-simple
+ "# Heading1
+&[](./simple.md)")
+
+(def include-surounding-simple
+ "# Heading1
+Some surounding &[](./simple.md) text")
+
+(def include-heading-0
+ "# Heading1
+&[:indent-heading 0](./with-heading.md)")
+
+(def include-heading-list-1
+ "# Heading1
+&[:indent-heading 1 :indent-list 1](./with-heading-and-list.md)")
+
+(def include-heading-list-0
+ "# Heading1
+&[:indent-list 0 :indent-heading 0](./with-heading-and-list.md)")
+
+(def include-invalid-indent
+ "# Heading1
+&[ invalid input should default to indent 0 ](./simple.md)")
+
+(def include-spaced-indent
+ "# Heading1
+&[ :indent-heading 2 :indent-list 33 ](./with-heading-and-list.md)")
+
+(def multi
+ "# Heading1
+&[ :indent-heading 2 :indent-list 33 ](./with-heading-and-list.md)
+some text
+&[](./simple.md)
+more text.")
+
+
+(deftest test-parse-include-md
+ (testing "parse include links"
+ (is
+ (= []
+ (sut/parse-include-md "# Heading")))
+ (is
+ (= [{:uri "./simple.md", :indent-heading 0, :indent-list 0}]
+ (sut/parse-include-md
+ include-simple)))
+ (is
+ (= [{:uri "./simple.md", :indent-heading 0, :indent-list 0}]
+ (sut/parse-include-md
+ include-surounding-simple)))
+ (is
+ (= [{:uri "./with-heading.md", :indent-heading 0, :indent-list 0}]
+ (sut/parse-include-md
+ include-heading-0)))
+ (is
+ (= [{:uri "./with-heading-and-list.md", :indent-heading 1, :indent-list 1}]
+ (sut/parse-include-md
+ include-heading-list-1)))
+ (is
+ (= [{:uri "./with-heading-and-list.md", :indent-heading 0, :indent-list 0}]
+ (sut/parse-include-md
+ include-heading-list-0)))
+ (is
+ (= [{:uri "./simple.md", :indent-heading 0, :indent-list 0}]
+ (sut/parse-include-md
+ include-invalid-indent)))
+ (is
+ (= [{:uri "./with-heading-and-list.md", :indent-heading 2, :indent-list 3}]
+ (sut/parse-include-md
+ include-spaced-indent)))
+ (is
+ (= [{:uri "./with-heading-and-list.md",
+ :indent-heading 2,
+ :indent-list 3}
+ {:uri "./simple.md", :indent-heading 0, :indent-list 0}]
+ (sut/parse-include-md
+ multi)))))
From 78a534349b047c8695ecfeee432199960410e557 Mon Sep 17 00:00:00 2001
From: jem
Date: Fri, 18 May 2018 14:31:32 +0200
Subject: [PATCH 16/83] add the replacement parameter
---
src/smeagol/include/parse.clj | 13 ++++++++-----
test/smeagol/test/include/parse.clj | 28 +++++++++++++++++++---------
2 files changed, 27 insertions(+), 14 deletions(-)
diff --git a/src/smeagol/include/parse.clj b/src/smeagol/include/parse.clj
index 0c4dbae..2212e0d 100644
--- a/src/smeagol/include/parse.clj
+++ b/src/smeagol/include/parse.clj
@@ -3,7 +3,8 @@
[schema.core :as s]))
(def IncludeLink
- {:uri s/Str
+ {:replace s/Str
+ :uri s/Str
:indent-heading s/Num
:indent-list s/Num})
@@ -27,7 +28,7 @@
(s/defn
parse-include-link
[md-src :- s/Str]
- (re-seq #".*&\[\w*(.*)\w*\]\((.*)\).*" md-src))
+ (re-seq #".*(&\[\w*(.*)\w*\]\((.*)\)).*" md-src))
(s/defn
parse-include-md :- [IncludeLink]
@@ -35,9 +36,11 @@
(vec
(map
(fn [parse-element]
- (let [uri (nth parse-element 2)
- indents-text (nth parse-element 1)]
- {:uri uri
+ (let [replace (nth parse-element 1)
+ uri (nth parse-element 3)
+ indents-text (nth parse-element 2)]
+ {:replace replace
+ :uri uri
:indent-heading (convert-indent-to-int (parse-indent-heading indents-text))
:indent-list (convert-indent-to-int (parse-indent-list indents-text))}))
(parse-include-link md-src))))
diff --git a/test/smeagol/test/include/parse.clj b/test/smeagol/test/include/parse.clj
index 60a22bf..af27abf 100644
--- a/test/smeagol/test/include/parse.clj
+++ b/test/smeagol/test/include/parse.clj
@@ -45,37 +45,47 @@ more text.")
(= []
(sut/parse-include-md "# Heading")))
(is
- (= [{:uri "./simple.md", :indent-heading 0, :indent-list 0}]
+ (= [{:replace "&[](./simple.md)" :uri "./simple.md", :indent-heading 0, :indent-list 0}]
(sut/parse-include-md
include-simple)))
(is
- (= [{:uri "./simple.md", :indent-heading 0, :indent-list 0}]
+ (= [{:replace "&[](./simple.md)" :uri "./simple.md", :indent-heading 0, :indent-list 0}]
(sut/parse-include-md
include-surounding-simple)))
(is
- (= [{:uri "./with-heading.md", :indent-heading 0, :indent-list 0}]
+ (= [{:replace "&[:indent-heading 0](./with-heading.md)" :uri "./with-heading.md", :indent-heading 0, :indent-list 0}]
(sut/parse-include-md
include-heading-0)))
(is
- (= [{:uri "./with-heading-and-list.md", :indent-heading 1, :indent-list 1}]
+ (= [{:replace
+ "&[:indent-heading 1 :indent-list 1](./with-heading-and-list.md)"
+ :uri "./with-heading-and-list.md", :indent-heading 1, :indent-list 1}]
(sut/parse-include-md
include-heading-list-1)))
(is
- (= [{:uri "./with-heading-and-list.md", :indent-heading 0, :indent-list 0}]
+ (= [{:replace
+ "&[:indent-list 0 :indent-heading 0](./with-heading-and-list.md)"
+ :uri "./with-heading-and-list.md", :indent-heading 0, :indent-list 0}]
(sut/parse-include-md
include-heading-list-0)))
(is
- (= [{:uri "./simple.md", :indent-heading 0, :indent-list 0}]
+ (= [{:replace
+ "&[ invalid input should default to indent 0 ](./simple.md)"
+ :uri "./simple.md", :indent-heading 0, :indent-list 0}]
(sut/parse-include-md
include-invalid-indent)))
(is
- (= [{:uri "./with-heading-and-list.md", :indent-heading 2, :indent-list 3}]
+ (= [{:replace
+ "&[ :indent-heading 2 :indent-list 33 ](./with-heading-and-list.md)"
+ :uri "./with-heading-and-list.md", :indent-heading 2, :indent-list 3}]
(sut/parse-include-md
include-spaced-indent)))
(is
- (= [{:uri "./with-heading-and-list.md",
+ (= [{:replace
+ "&[ :indent-heading 2 :indent-list 33 ](./with-heading-and-list.md)"
+ :uri "./with-heading-and-list.md",
:indent-heading 2,
:indent-list 3}
- {:uri "./simple.md", :indent-heading 0, :indent-list 0}]
+ {:replace "&[](./simple.md)" :uri "./simple.md", :indent-heading 0, :indent-list 0}]
(sut/parse-include-md
multi)))))
From 6768d71429e53aae58e26ed2e6a3db820e9e4d39 Mon Sep 17 00:00:00 2001
From: jem
Date: Fri, 18 May 2018 14:31:46 +0200
Subject: [PATCH 17/83] add replacement
---
src/smeagol/include.clj | 38 ++++++++++++++++++++++++++++++-----
test/smeagol/test/include.clj | 13 ++++++------
2 files changed, 40 insertions(+), 11 deletions(-)
diff --git a/src/smeagol/include.clj b/src/smeagol/include.clj
index 14a97da..440f1ab 100644
--- a/src/smeagol/include.clj
+++ b/src/smeagol/include.clj
@@ -1,5 +1,6 @@
(ns smeagol.include
(:require
+ [clojure.string :as cs]
[schema.core :as s]
[com.stuartsierra.component :as component]
[smeagol.include.parse :as parse]
@@ -11,15 +12,42 @@
(defprotocol IncludeMd
(expand-include-md
[includer md-src]
- "return a markfown file content for given uri."))
+ "return a markdown containing resolved includes"))
+
+(s/defn
+ do-expand-one-include :- s/Str
+ [includer :- Includer
+ include :- parse/IncludeLink
+ md-src :- s/Str]
+ (let [{:keys [uri replace]} include]
+ (cs/replace
+ md-src
+ (re-pattern (cs/escape
+ replace
+ {\[ "\\["
+ \] "\\]"
+ \( "\\("
+ \) "\\)"}))
+ (resolve/resolve-md (:resolver includer) uri))))
+ ;indent
+
+(s/defn
+ do-expand-includes :- s/Str
+ [includer :- Includer
+ includes :- [parse/IncludeLink]
+ md-src :- s/Str]
+ (loop [loop-includes includes
+ result md-src]
+ (if (empty? loop-includes)
+ result
+ (recur
+ (rest loop-includes)
+ (do-expand-one-include includer (first loop-includes) result)))))
(extend-type Includer
IncludeMd
(expand-include-md [includer md-src]
- (let [includes (parse/parse-include-md md-src)]
- ;resolve found includes
- ;indent & integrate
- md-src)))
+ (do-expand-includes includer (parse/parse-include-md md-src) md-src)))
(s/defn
new-includer
diff --git a/test/smeagol/test/include.clj b/test/smeagol/test/include.clj
index 45219e1..628ad75 100644
--- a/test/smeagol/test/include.clj
+++ b/test/smeagol/test/include.clj
@@ -47,11 +47,12 @@ more text.")
(= uri "./simple.md") "Simple content."))
(def system-under-test
- (component/system-map
- :resolver (resolve/new-resolver :test-mock)
- :includer (component/using
- (sut/new-includer)
- {:resolver :resolver})))
+ (component/start
+ (component/system-map
+ :resolver (resolve/new-resolver :test-mock)
+ :includer (component/using
+ (sut/new-includer)
+ [:resolver]))))
(deftest test-expand-include-md
(testing "The whole integration of include"
@@ -59,7 +60,7 @@ more text.")
(= "# Heading"
(sut/expand-include-md (:includer system-under-test) "# Heading")))
(is
- (= "# Heading 1
+ (= "# Heading1
Simple content."
(sut/expand-include-md
(:includer system-under-test)
From 07342b5ac4387584097caecee7d05107cf9d3fa8 Mon Sep 17 00:00:00 2001
From: jem
Date: Fri, 18 May 2018 17:13:33 +0200
Subject: [PATCH 18/83] implemented includes-resolving for tests
---
test/smeagol/test/include.clj | 29 +++++++++++++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/test/smeagol/test/include.clj b/test/smeagol/test/include.clj
index 628ad75..dc477c9 100644
--- a/test/smeagol/test/include.clj
+++ b/test/smeagol/test/include.clj
@@ -44,7 +44,15 @@ more text.")
[resolver
uri :- s/Str]
(cond
- (= uri "./simple.md") "Simple content."))
+ (= uri "./simple.md") "Simple content."
+ (= uri "./with-heading-and-list.md") "# Heading2
+some text
+* List
+
+## Heading 3
+more text"))
+
+
(def system-under-test
(component/start
@@ -64,4 +72,21 @@ more text.")
Simple content."
(sut/expand-include-md
(:includer system-under-test)
- include-simple)))))
+ include-simple)))
+ (is
+ (= "# Heading1
+Some surounding Simple content. text"
+ (sut/expand-include-md
+ (:includer system-under-test)
+ include-surounding-simple)))
+ (is
+ (= "# Heading1
+# Heading2
+some text
+* List
+
+## Heading 3
+more text"
+ (sut/expand-include-md
+ (:includer system-under-test)
+ include-heading-list-0)))))
From 12d4661db9844492d65847ffdc652da24f9178b4 Mon Sep 17 00:00:00 2001
From: jem
Date: Fri, 18 May 2018 17:17:15 +0200
Subject: [PATCH 19/83] expectation for header & list indent
---
test/smeagol/test/include.clj | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/test/smeagol/test/include.clj b/test/smeagol/test/include.clj
index dc477c9..ff3e8e6 100644
--- a/test/smeagol/test/include.clj
+++ b/test/smeagol/test/include.clj
@@ -89,4 +89,18 @@ some text
more text"
(sut/expand-include-md
(:includer system-under-test)
- include-heading-list-0)))))
+ include-heading-list-0)))
+ (is
+ (= "# Heading1
+### Heading2
+some text
+ * List
+
+## Heading 3
+more text
+some text
+Simple content.
+more text."
+ (sut/expand-include-md
+ (:includer system-under-test)
+ multi)))))
From 535465c362ace052d311c64d1f3455aec0ecc0f6 Mon Sep 17 00:00:00 2001
From: jem
Date: Fri, 18 May 2018 20:47:15 +0200
Subject: [PATCH 20/83] implement indention
---
src/smeagol/include.clj | 14 +++++---
src/smeagol/include/indent.clj | 54 ++++++++++++++++++++++++++++
test/smeagol/test/include.clj | 2 +-
test/smeagol/test/include/indent.clj | 35 ++++++++++++++++++
4 files changed, 99 insertions(+), 6 deletions(-)
create mode 100644 src/smeagol/include/indent.clj
create mode 100644 test/smeagol/test/include/indent.clj
diff --git a/src/smeagol/include.clj b/src/smeagol/include.clj
index 440f1ab..41aaf99 100644
--- a/src/smeagol/include.clj
+++ b/src/smeagol/include.clj
@@ -4,7 +4,8 @@
[schema.core :as s]
[com.stuartsierra.component :as component]
[smeagol.include.parse :as parse]
- [smeagol.include.resolve :as resolve]))
+ [smeagol.include.resolve :as resolve]
+ [smeagol.include.indent :as indent]))
(s/defrecord Includer
[resolver])
@@ -19,8 +20,8 @@
[includer :- Includer
include :- parse/IncludeLink
md-src :- s/Str]
- (let [{:keys [uri replace]} include]
- (cs/replace
+ (let [{:keys [uri replace indent-heading indent-list]} include]
+ (cs/replace-first
md-src
(re-pattern (cs/escape
replace
@@ -28,8 +29,11 @@
\] "\\]"
\( "\\("
\) "\\)"}))
- (resolve/resolve-md (:resolver includer) uri))))
- ;indent
+ (indent/do-indent-list
+ indent-list
+ (indent/do-indent-heading
+ indent-heading
+ (resolve/resolve-md (:resolver includer) uri))))))
(s/defn
do-expand-includes :- s/Str
diff --git a/src/smeagol/include/indent.clj b/src/smeagol/include/indent.clj
new file mode 100644
index 0000000..6967d19
--- /dev/null
+++ b/src/smeagol/include/indent.clj
@@ -0,0 +1,54 @@
+(ns smeagol.include.indent
+ (:require
+ [clojure.string :as cs]
+ [schema.core :as s]))
+
+(s/defn
+ parse-list
+ [md-resolved :- s/Str]
+ (distinct
+ (into
+ (re-seq #"((^|\R? *)([\*\+-] ))" md-resolved)
+ (re-seq #"((^|\R? *)([0-9]+\. ))" md-resolved))))
+
+(s/defn
+ parse-heading
+ [md-resolved :- s/Str]
+ (distinct
+ (re-seq #"((^|\R?)(#+ ))" md-resolved)))
+
+(s/defn
+ do-indent :- s/Str
+ [indent :- s/Num
+ indentor :- s/Str
+ elements
+ md-resolved :- s/Str]
+ (loop [result md-resolved
+ elements elements]
+ (if (empty? elements)
+ result
+ (let [element (first elements)
+ replace (nth element 1)
+ start (nth element 2)
+ end (nth element 3)]
+ (recur
+ (cs/replace
+ result
+ (re-pattern (cs/escape
+ replace
+ {\* "\\*"
+ \n "\\n"}))
+ (str start (apply str (repeat indent indentor)) end))
+ (rest elements))))))
+
+(s/defn
+ do-indent-heading :- s/Str
+ [indent :- s/Num
+ md-resolved :- s/Str]
+ (do-indent indent "#" (parse-heading md-resolved) md-resolved))
+
+(s/defn
+ do-indent-list :- s/Str
+ [indent :- s/Num
+ md-resolved :- s/Str]
+ (do-indent indent " " (parse-list md-resolved) md-resolved))
diff --git a/test/smeagol/test/include.clj b/test/smeagol/test/include.clj
index ff3e8e6..3a037f1 100644
--- a/test/smeagol/test/include.clj
+++ b/test/smeagol/test/include.clj
@@ -96,7 +96,7 @@ more text"
some text
* List
-## Heading 3
+#### Heading 3
more text
some text
Simple content.
diff --git a/test/smeagol/test/include/indent.clj b/test/smeagol/test/include/indent.clj
new file mode 100644
index 0000000..b4ca363
--- /dev/null
+++ b/test/smeagol/test/include/indent.clj
@@ -0,0 +1,35 @@
+(ns smeagol.test.include.indent
+ (:require [clojure.test :refer :all]
+ [smeagol.include.indent :as sut]))
+
+(deftest test-parse-heading
+ (testing
+ (is (= '(["# " "# " "" "# "])
+ (sut/parse-heading "# h1")))
+ (is (= '(["\n# " "\n# " "\n" "# "])
+ (sut/parse-heading "\n# h1")))))
+
+(deftest test-indent-heading
+ (testing
+ (is (= "# h1"
+ (sut/do-indent-heading 0 "# h1")))
+ (is (= "### h1"
+ (sut/do-indent-heading 2 "# h1")))
+ (is (= "\n### h1"
+ (sut/do-indent-heading 2 "\n# h1")))))
+
+(deftest test-parse-list
+ (testing
+ (is (= '([" * " " * " " " "* "])
+ (sut/parse-list " * list")))
+ (is (= '(["\n * " "\n * " "\n " "* "])
+ (sut/parse-list "\n * list")))))
+
+(deftest test-indent-list
+ (testing
+ (is (= " * list"
+ (sut/do-indent-list 0 " * list")))
+ (is (= " * list"
+ (sut/do-indent-list 2 " * list")))
+ (is (= "\n * list"
+ (sut/do-indent-list 2 "\n * list")))))
From 3668b26df13c5907dbbb1dac2cb160ea003514cd Mon Sep 17 00:00:00 2001
From: jem
Date: Tue, 22 May 2018 16:59:43 +0200
Subject: [PATCH 21/83] add doc to namespaces
---
src/smeagol/include.clj | 4 +++-
src/smeagol/include/indent.clj | 6 +++++-
src/smeagol/include/parse.clj | 6 +++++-
src/smeagol/include/resolve.clj | 6 +++++-
4 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/src/smeagol/include.clj b/src/smeagol/include.clj
index 41aaf99..8f8a017 100644
--- a/src/smeagol/include.clj
+++ b/src/smeagol/include.clj
@@ -1,4 +1,6 @@
-(ns smeagol.include
+(ns ^{:doc "Functions related to the include of markdown-paged in a given markdown."
+ :author "Michael Jerger"}
+ smeagol.include
(:require
[clojure.string :as cs]
[schema.core :as s]
diff --git a/src/smeagol/include/indent.clj b/src/smeagol/include/indent.clj
index 6967d19..f92a69c 100644
--- a/src/smeagol/include/indent.clj
+++ b/src/smeagol/include/indent.clj
@@ -1,4 +1,8 @@
-(ns smeagol.include.indent
+(ns ^{:doc "Functions related to the include of markdown-paged - handling the
+list & heading indents of includes. This namespaces is implementation detail for
+smeagol.include and not inteded for direct usage."
+ :author "Michael Jerger"}
+ smeagol.include.indent
(:require
[clojure.string :as cs]
[schema.core :as s]))
diff --git a/src/smeagol/include/parse.clj b/src/smeagol/include/parse.clj
index 2212e0d..0016252 100644
--- a/src/smeagol/include/parse.clj
+++ b/src/smeagol/include/parse.clj
@@ -1,4 +1,8 @@
-(ns smeagol.include.parse
+(ns ^{:doc "Functions related to the include of markdown-paged - parsing of
+include links. This namespaces is implementation detail for
+smeagol.include and not inteded for direct usage."
+ :author "Michael Jerger"}
+ smeagol.include.parse
(:require
[schema.core :as s]))
diff --git a/src/smeagol/include/resolve.clj b/src/smeagol/include/resolve.clj
index 13a67ce..95b0ec2 100644
--- a/src/smeagol/include/resolve.clj
+++ b/src/smeagol/include/resolve.clj
@@ -1,4 +1,8 @@
-(ns smeagol.include.resolve
+(ns ^{:doc "Functions related to the include of markdown-paged - providing
+a plugable load-content componet. This namespaces is implementation detail for
+smeagol.include and not inteded for direct usage."
+ :author "Michael Jerger"}
+ smeagol.include.resolve
(:require
[schema.core :as s]
[com.stuartsierra.component :as component]))
From 7674a4c30593d86007ec2dd0961b6a6f3da6a8ba Mon Sep 17 00:00:00 2001
From: jem
Date: Tue, 22 May 2018 18:01:22 +0200
Subject: [PATCH 22/83] stick system together
---
src/smeagol/include/resolve.clj | 12 ++++++----
src/smeagol/include/resolve_local_file.clj | 27 ++++++++++++++++++++++
src/smeagol/routes/wiki.clj | 18 +++++++++++++--
3 files changed, 51 insertions(+), 6 deletions(-)
create mode 100644 src/smeagol/include/resolve_local_file.clj
diff --git a/src/smeagol/include/resolve.clj b/src/smeagol/include/resolve.clj
index 95b0ec2..7a2b3b1 100644
--- a/src/smeagol/include/resolve.clj
+++ b/src/smeagol/include/resolve.clj
@@ -1,5 +1,5 @@
(ns ^{:doc "Functions related to the include of markdown-paged - providing
-a plugable load-content componet. This namespaces is implementation detail for
+a plugable load-content componet. This namespaces is implementation detail for
smeagol.include and not inteded for direct usage."
:author "Michael Jerger"}
smeagol.include.resolve
@@ -8,7 +8,8 @@ smeagol.include and not inteded for direct usage."
[com.stuartsierra.component :as component]))
(s/defrecord Resolver
- [type :- s/Keyword])
+ [type :- s/Keyword
+ local-base-dir :- s/Str])
;As schema does'nt support s/defprotocol we use the dispatcher for annotation & validation.
(s/defn dispatch-by-resolver-type :- s/Keyword
@@ -38,5 +39,8 @@ smeagol.include and not inteded for direct usage."
(s/defn
new-resolver
- [type :- s/Keyword]
- (map->Resolver {:type type}))
+ ([type :- s/Keyword]
+ (map->Resolver {:type type :local-base-dir nil}))
+ ([type :- s/Keyword
+ local-base-dir :- s/Str]
+ (map->Resolver {:type type :local-base-dir local-base-dir})))
diff --git a/src/smeagol/include/resolve_local_file.clj b/src/smeagol/include/resolve_local_file.clj
new file mode 100644
index 0000000..4daaac2
--- /dev/null
+++ b/src/smeagol/include/resolve_local_file.clj
@@ -0,0 +1,27 @@
+(ns ^{:doc "Functions related to the include of markdown-paged - providing
+a plugable load-local-include-links componet. This namespaces is implementation detail for
+smeagol.include and not inteded for direct usage."
+ :author "Michael Jerger"}
+ smeagol.include.resolve-local-file
+ (:require
+ [schema.core :as s]
+ [smeagol.include.resolve :as resolve]
+ [com.stuartsierra.component :as component]
+ [clojure.java.io :as cjio]
+ [taoensso.timbre :as timbre]))
+
+(s/defmethod resolve/do-resolve-md :local-file
+ [resolver
+ uri :- s/Str]
+ (let [file-name (uri)
+ file-path (cjio/file (:local-base-dir resolver) file-name)
+ exists? (.exists (clojure.java.io/as-file file-path))]
+ (cond exists?
+ (do
+ (timbre/info (format "Including page '%s' from file '%s'" uri file-path))
+ (slurp file-path)))))
+
+(s/defn
+ new-resolver
+ [local-base-dir :- s/Str]
+ (resolve/new-resolver :local-file local-base-dir))
diff --git a/src/smeagol/routes/wiki.clj b/src/smeagol/routes/wiki.clj
index fe80349..109a2cc 100644
--- a/src/smeagol/routes/wiki.clj
+++ b/src/smeagol/routes/wiki.clj
@@ -20,7 +20,10 @@
[smeagol.sanity :refer [show-sanity-check-error]]
[smeagol.util :as util]
[smeagol.uploads :as ul]
- [taoensso.timbre :as timbre]))
+ [taoensso.timbre :as timbre]
+ [com.stuartsierra.component :as component]
+ [smeagol.include.resolve-local-file :as resolve]
+ [smeagol.include :as include]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
@@ -108,6 +111,14 @@
(edit-page request "stylesheet" ".css" "edit-css.html" "_edit-side-bar.md"))
+(def md-include-system
+ (component/start
+ (component/system-map
+ :resolver (resolve/new-resolver util/content-dir)
+ :includer (component/using
+ (include/new-includer)
+ [:resolver]))))
+
(defn wiki-page
"Render the markdown page specified in this `request`, if any. If none found, redirect to edit-page"
[request]
@@ -125,7 +136,10 @@
(merge (util/standard-params request)
{:title page
:page page
- :content (md->html (slurp file-path))
+ :content (md->html
+ (include/expand-include-md
+ (:includer md-include-system)
+ (slurp file-path)))
:editable true})))
true (response/redirect (str "/edit?page=" page))))))
From 01c954fc2f1e404b6ce14e4777f43a97c0058193 Mon Sep 17 00:00:00 2001
From: jem
Date: Tue, 22 May 2018 21:22:58 +0200
Subject: [PATCH 23/83] fix uri resolving
---
src/smeagol/include/resolve_local_file.clj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/smeagol/include/resolve_local_file.clj b/src/smeagol/include/resolve_local_file.clj
index 4daaac2..c35e3e5 100644
--- a/src/smeagol/include/resolve_local_file.clj
+++ b/src/smeagol/include/resolve_local_file.clj
@@ -13,7 +13,7 @@ smeagol.include and not inteded for direct usage."
(s/defmethod resolve/do-resolve-md :local-file
[resolver
uri :- s/Str]
- (let [file-name (uri)
+ (let [file-name uri
file-path (cjio/file (:local-base-dir resolver) file-name)
exists? (.exists (clojure.java.io/as-file file-path))]
(cond exists?
From 778c0a84e24a94a29802c8e5957e9dadddde85af Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Thu, 24 May 2018 07:23:11 +0100
Subject: [PATCH 24/83] i18n
---
resources/i18n/de-DE.edn | 143 ++++++++++++++++++++++++++++++++++++
resources/i18n/de.edn | 1 +
resources/i18n/en-GB.edn | 2 +-
resources/i18n/lt.edn | 1 +
resources/i18n/lt_LT.edn | 143 ++++++++++++++++++++++++++++++++++++
resources/i18n/ru.edn | 1 +
resources/i18n/ru_RU.edn | 144 +++++++++++++++++++++++++++++++++++++
resources/translations.edn | 0
8 files changed, 434 insertions(+), 1 deletion(-)
create mode 100644 resources/i18n/de-DE.edn
create mode 120000 resources/i18n/de.edn
create mode 120000 resources/i18n/lt.edn
create mode 100644 resources/i18n/lt_LT.edn
create mode 120000 resources/i18n/ru.edn
create mode 100644 resources/i18n/ru_RU.edn
create mode 100644 resources/translations.edn
diff --git a/resources/i18n/de-DE.edn b/resources/i18n/de-DE.edn
new file mode 100644
index 0000000..a7b8789
--- /dev/null
+++ b/resources/i18n/de-DE.edn
@@ -0,0 +1,143 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Smeagol: a very simple Wiki engine.
+;;;;
+;;;; This program is free software; you can redistribute it and/or
+;;;; modify it under the terms of the GNU General Public License
+;;;; as published by the Free Software Foundation; either version 2
+;;;; of the License, or (at your option) any later version.
+;;;;
+;;;; This program is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with this program; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;;; USA.
+;;;;
+;;;; Copyright (C) 2017 Simon Brooke
+;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;; en-GB.edn: English-language messages.
+;;; This is essentially all the text in the chrome - that which isn't editable
+;;; through the wiki itself; and the test in the sanity check report.
+
+;; ; ; ; ; ; ; ; ; ;
+{:add-user-label "Add new user" ;; label for the add user link on edit users page
+ :change-pass-label "Change password!"
+ ;; text of the change password widget itself on the
+ ;; change password page
+ :change-pass-link "Change password"
+ ;; text of the change password link on the menu
+ :change-pass-prompt "To change your password"
+ ;; text of the change password widget prompt on the
+ ;; change password page
+ :change-col-hdr "Changes" ;; header for the changes column in history
+ :chpass-bad-match "Your proposed passwords don't match"
+ ;; error text if proposed passwords don't match
+ :chpass-fail "Your password was not changed"
+ ;; error text on fail other htan too short or bad match
+ :chpass-success "Your password was changed"
+ ;; confirmation text on password change
+ :chpass-too-short "You proposed password wasn't long enough: eight characters required"
+ ;; error text if proposed password is too short
+ :chpass-title-prefix "Change password for"
+ ;; prefix for title of change password page
+ :content-dir "The content directory"
+ ;; used in sanity check report
+ :content-dir-exists "The content directory exists"
+ ;; used in sanity check report
+ :content-dir-is-dir "The content directory is a directory"
+ ;; used in sanity check report
+ :cookies-about "About cookies" ;; about cookies text
+ :cookies-more "This website stores session information as a 'cookie' on your browser. This helps us show you the content you want to see. This cookie does not identify you, and cannot be read by other websites. It is deleted by your browser as soon as you leave this site. This website does not use any third party cookies, so your visit here cannot be tracked by other websites."
+ ;; more about cookies text
+ :default-page-title "Introduction" ;; title of the default page in this wiki
+ :del-col-hdr "Delete" ;; header for delete column on edit users page
+ :del-user-fail "Could not delete user"
+ ;; error message on failure to delete user
+ :del-user-success "Successfully deleted user"
+ ;; confirmation message on deletion of user
+ :diff-title-prefix "Changes since version"
+ ;; prefix for the header of the changes page
+ :does-not-exist "does not exist"
+ ;; (of a file or directory); used in sanity check report
+ :edit-col-hdr "Edit" ;; header for edit column on edit users page
+ :edit-page-link "Edit this page"
+ ;; text of the edit page link on the content frame
+ :edit-title-prefix "Edit" ;; prefix for title of edit content page
+ :edit-users-link "Edit users" ;; text of the edit users link on the menu
+ :edit-users-title "Select user to edit"
+ ;; title of edit users page
+ :email-prompt "Email address" ;; text of the email widget prompt on edit user page
+ :file-or-directory "File or directory"
+ ;; used in sanity check report
+ :file-summary-prompt "Description/what's changed"
+ ;; prompt for the file upload summary input
+ :file-upload-link-text "You may link to this file using a link of the form"
+ ;; Text introducing the link to an uploaded file
+ :file-upload-prompt "File to upload" ;; prompt string for the file upload widget
+ :file-upload-title "Upload a file" ;; title for the file upload page
+ :is-admin-prompt "Is administrator?"
+ :here "here" ;; used in sanity check report
+ :home-link "Home" ;; text of the home link on the menu
+ :is-not-directory "is not a directory"
+ ;; (of a file or directory) used in sanity check report
+ :is-not-readable "is not readable"
+ ;; (of a file or directory) used in sanity check report
+ :is-not-writable "is not writable"
+ ;; (of a file or directory) used in sanity check report
+ :login-label "Log in!" ;; text of the login widget on the login page
+ :login-link "Log in" ;; text of the login link on the menu
+ :login-prompt "To edit this wiki"
+ ;; text of the action widget prompt on the login page
+ :logout-label "Log out!" ;; text of the logout widget on the logout page
+ :logout-link "Log out" ;; text of the logout link on the menu
+ :logged-in-as "You are logged in as"
+ ;; text of the 'logged in as' label on the menu
+ :history-link "History" ;; text of the history link on the content frame
+ :history-title-prefix "History of" ;; prefix of the title on the history page
+ :new-pass-prompt "New password" ;; text of the new password widget prompt on the change
+ ;; password and edit user pages
+ :no-admin-users "There are no users in the 'passwd' file with administrative privileges"
+ ;; used in sanity check report
+ :old-pass-prompt "Your password"
+ ;; text of the old password widget prompt on the change
+ ;; password page, and password widget on login page
+ :password-file "the password ('passwd') file"
+ ;; used in sanity check report
+ :problems-found "problems were found"
+ ;; used in sanity check report
+ :rpt-pass-prompt "And again" ;; text of the new password widget prompt on the change
+ ;; password and edit user pages
+ :save-prompt "When you have finished editing"
+ ;; text of the save widget label on edit content
+ ;; and edit user page
+ :save-label "Save!" ;; text of the save widget itself
+ :save-user-fail "Failed to store user"
+ :save-user-success "Successfully stored user"
+ :see-documentation "For more information please see documentation "
+ ;; used in sanity check report
+ :smeagol-not-initialised
+ "Smeagol is not initialised correctly"
+ ;; title of the sanity check report
+ :smeagol-misconfiguration
+ "Smeagol has been unable to find some of the resources on which it depends,
+ possibly because of misconfiguration or missing environment variables."
+ ;; used in sanity check report
+ :user-lacks-field "User record in the passwd file lacks a field"
+ ;; used in sanity check report
+ :username-prompt "Username" ;; text of the username widget prompt on edit user page
+ ;; text of the is admin widget prompt on edit user page
+ :user-title-prefix "Edit user" ;; prefix for title of edit user page
+ :vers-col-hdr "Version" ;; header for the version column in history
+ :what-col-hdr "What" ;; header for the what column in history
+ :what-changed-prompt "What have you changed?"
+ ;; text of the summary widget prompt on edit
+ ;; content page
+ :when-col-hdr "When" ;; header for the when column in history
+ :your-uname-prompt "Your username" ;; text of the username widget prompt on the login page
+ }
diff --git a/resources/i18n/de.edn b/resources/i18n/de.edn
new file mode 120000
index 0000000..d74964e
--- /dev/null
+++ b/resources/i18n/de.edn
@@ -0,0 +1 @@
+de-DE.edn
\ No newline at end of file
diff --git a/resources/i18n/en-GB.edn b/resources/i18n/en-GB.edn
index a7b8789..64c164c 100644
--- a/resources/i18n/en-GB.edn
+++ b/resources/i18n/en-GB.edn
@@ -42,7 +42,7 @@
;; error text on fail other htan too short or bad match
:chpass-success "Your password was changed"
;; confirmation text on password change
- :chpass-too-short "You proposed password wasn't long enough: eight characters required"
+ :chpass-too-short "Your proposed password wasn't long enough: eight characters required"
;; error text if proposed password is too short
:chpass-title-prefix "Change password for"
;; prefix for title of change password page
diff --git a/resources/i18n/lt.edn b/resources/i18n/lt.edn
new file mode 120000
index 0000000..ef91fe5
--- /dev/null
+++ b/resources/i18n/lt.edn
@@ -0,0 +1 @@
+lt_LT.edn
\ No newline at end of file
diff --git a/resources/i18n/lt_LT.edn b/resources/i18n/lt_LT.edn
new file mode 100644
index 0000000..13185d0
--- /dev/null
+++ b/resources/i18n/lt_LT.edn
@@ -0,0 +1,143 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Smeagol: a very simple Wiki engine.
+;;;;
+;;;; This program is free software; you can redistribute it and/or
+;;;; modify it under the terms of the GNU General Public License
+;;;; as published by the Free Software Foundation; either version 2
+;;;; of the License, or (at your option) any later version.
+;;;;
+;;;; This program is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with this program; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;;; USA.
+;;;;
+;;;; Lithuanian language translation contributed by and
+;;;; Copyright (C) 2017 Soukyan Blackwood
+;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;; en-GB.edn: English-language messages.
+;;; This is essentially all the text in the chrome - that which isn't editable
+;;; through the wiki itself; and the test in the sanity check report.
+
+;; ; ; ; ; ; ; ; ; ;
+{:add-user-label "Pridėti naują naudotoją" ;; label for the add user link on edit users page
+ :change-pass-label "Pakeiskite slaptažodį!"
+ ;; text of the change password widget itself on the
+ ;; change password page
+ :change-pass-link "Pakeiskite slaptažodį"
+ ;; text of the change password link on the menu
+ :change-pass-prompt "Pakeisti slaptažodį"
+ ;; text of the change password widget prompt on the
+ ;; change password page
+ :change-col-hdr "Pakeitimai" ;; header for the changes column in history
+ :chpass-bad-match "Jūsų siūlomi slaptažodžiai - nesutampa"
+ ;; error text if proposed passwords don't match
+ :chpass-fail "Jūsų slaptažodis nebuvo pakeistas"
+ ;; error text on fail other htan too short or bad match
+ :chpass-success "Jūsų slaptažodis buvo pakeistas"
+ ;; confirmation text on password change
+ :chpass-too-short "Jūsų siūlomas slaptožodis – per trumpas: reikia bent aštuonių ženklų"
+ ;; error text if proposed password is too short
+ :chpass-title-prefix "Pakeisti slaptažodį, dėl"
+ ;; prefix for title of change password page
+ :content-dir "Turinio katalogas"
+ ;; used in sanity check report
+ :content-dir-exists "Turinio katalogas egzistuoja"
+ ;; used in sanity check report
+ :content-dir-is-dir "Turinio katalogas yra katalogas"
+ ;; used in sanity check report
+ :cookies-about "Apie slapukus" ;; about cookies text
+ :cookies-more "Šis puslapis saugo jūsų sesijų informaciją jūsų naršyklėje “slapukų” forma. Tai mums leidžia jums rodyti tik tai, ką norite matyti. Slapukai neturi jokių jūsų identifikavimo duomenų ir kiti puslapiai negali jų “perskaityti”. Šie slapukai ištrinami iš karto, vos jūs išjungiate šį puslapį. Šis puslapis nenaudoja jokių trečiųjų asmenų slapukų, ir jūsų apsilankymas čia negali būti atsektas jokio kito puslapio."
+ ;; more about cookies text
+ :default-page-title "Pristatymas" ;; title of the default page in this wiki
+ :del-col-hdr "Ištrinti" ;; header for delete column on edit users page
+ :del-user-fail "Naudotojas negalėjo būti ištrintas"
+ ;; error message on failure to delete user
+ :del-user-success "Naudotojas sėkmingai ištrintas"
+ ;; confirmation message on deletion of user
+ :diff-title-prefix "Pakeitimai nuo versijos"
+ ;; prefix for the header of the changes page
+ :does-not-exist "neegzsituoja"
+ ;; (of a file or directory); used in sanity check report
+ :edit-col-hdr "Keisti" ;; header for edit column on edit users page
+ :edit-page-link "Keisti šį puslapį"
+ ;; text of the edit page link on the content frame
+ :edit-title-prefix "Keisti" ;; prefix for title of edit content page
+ :edit-users-link "Keisti naudotojus" ;; text of the edit users link on the menu
+ :edit-users-title "Pasirinkti naudotojus keitimui"
+ ;; title of edit users page
+ :email-prompt "el. Paštas" ;; text of the email widget prompt on edit user page
+ :file-or-directory "Failas ar katalogas"
+ ;; used in sanity check report
+ :file-summary-prompt "Aprašymas/kas pakeista"
+ ;; prompt for the file upload summary input
+ :file-upload-link-text "Galite nukreipti į šį failą naudodami formos nuorodą"
+ ;; Text introducing the link to an uploaded file
+ :file-upload-prompt "Failas įkėlimui" ;; prompt string for the file upload widget
+ :file-upload-title "Įkelti failą" ;; title for the file upload page
+ :is-admin-prompt "Administratorius?"
+ :here "čia" ;; used in sanity check report
+ :home-link "Pradinis" ;; text of the home link on the menu
+ :is-not-directory "ne katalogas"
+ ;; (of a file or directory) used in sanity check report
+ :is-not-readable "neperskaitomas"
+ ;; (of a file or directory) used in sanity check report
+ :is-not-writable "nerašomas"
+ ;; (of a file or directory) used in sanity check report
+ :login-label "Prisijunkite!" ;; text of the login widget on the login page
+ :login-link "Prisijunkite" ;; text of the login link on the menu
+ :login-prompt "Pakeisti šį viki"
+ ;; text of the action widget prompt on the login page
+ :logout-label "Atsijunkite!" ;; text of the logout widget on the logout page
+ :logout-link "Atsijunkite" ;; text of the logout link on the menu
+ :logged-in-as "Jūs prisijungęs, kaip"
+ ;; text of the 'logged in as' label on the menu
+ :history-link "Istorija" ;; text of the history link on the content frame
+ :history-title-prefix "Istorija apie" ;; prefix of the title on the history page
+ :new-pass-prompt "Naujas slaptažodis" ;; text of the new password widget prompt on the change
+ ;; password and edit user pages
+ :no-admin-users "Naudotojų ‘passwd’ faile su administatoriaus privilegijomis nėra"
+ ;; used in sanity check report
+ :old-pass-prompt "Jūsų slaptažodis"
+ ;; text of the old password widget prompt on the change
+ ;; password page, and password widget on login page
+ :password-file "slaptažodžio (‘passwd’) failas"
+ ;; used in sanity check report
+ :problems-found "rasta problemų"
+ ;; used in sanity check report
+ :rpt-pass-prompt "Ir dar kartą" ;; text of the new password widget prompt on the change
+ ;; password and edit user pages
+ :save-prompt "Kai baigsite redaguoti"
+ ;; text of the save widget label on edit content
+ ;; and edit user page
+ :save-label "Išsaugokite!" ;; text of the save widget itself
+ :save-user-fail "Nepavyko išsaugoti naudotojo"
+ :save-user-success "Naudotojas sėkmingai išsaugotas"
+ :see-documentation "Daugiau informacijos ieškokite dokumentacijoje "
+ ;; used in sanity check report
+ :smeagol-not-initialised
+ "Smygolas buvo blogai paleistas"
+ ;; title of the sanity check report
+ :smeagol-misconfiguration
+ "Smygolas nerado kai kurių jam reikalingų resursų, taip nutikti galėjo dėl neteisingų nustatymų, arba trūkstamų aplinkos kintamųjų"
+ ;; used in sanity check report
+ :user-lacks-field "Naudotojo passwd failo įraše trūksta laukelio"
+ ;; used in sanity check report
+ :username-prompt "Naudotojo vardas" ;; text of the username widget prompt on edit user page
+ ;; text of the is admin widget prompt on edit user page
+ :user-title-prefix "Pakeisti naudotoją" ;; prefix for title of edit user page
+ :vers-col-hdr "Versija" ;; header for the version column in history
+ :what-col-hdr "Kas" ;; header for the what column in history
+ :what-changed-prompt "Ką pakeitėte?"
+ ;; text of the summary widget prompt on edit
+ ;; content page
+ :when-col-hdr "Kada" ;; header for the when column in history
+ :your-uname-prompt "Jūsų naudotojo vardas" ;; text of the username widget prompt on the login page
+ }
diff --git a/resources/i18n/ru.edn b/resources/i18n/ru.edn
new file mode 120000
index 0000000..4a740ce
--- /dev/null
+++ b/resources/i18n/ru.edn
@@ -0,0 +1 @@
+ru_RU.edn
\ No newline at end of file
diff --git a/resources/i18n/ru_RU.edn b/resources/i18n/ru_RU.edn
new file mode 100644
index 0000000..73400d6
--- /dev/null
+++ b/resources/i18n/ru_RU.edn
@@ -0,0 +1,144 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Smeagol: a very simple Wiki engine.
+;;;;
+;;;; This program is free software; you can redistribute it and/or
+;;;; modify it under the terms of the GNU General Public License
+;;;; as published by the Free Software Foundation; either version 2
+;;;; of the License, or (at your option) any later version.
+;;;;
+;;;; This program is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with this program; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;;; USA.
+;;;;
+;;;; Russian language translation contributed by and
+;;;; Copyright (C) 2017 Soukyan Blackwood
+;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;; en-GB.edn: English-language messages.
+;;; This is essentially all the text in the chrome - that which isn't editable
+;;; through the wiki itself; and the test in the sanity check report.
+
+;; ; ; ; ; ; ; ; ; ;
+{:add-user-label "добавить нового пользователя" ;; label for the add user link on edit users page
+ :change-pass-label "изменить пароль!"
+ ;; text of the change password widget itself on the
+ ;; change password page
+ :change-pass-link "изменить пароль"
+ ;; text of the change password link on the menu
+ :change-pass-prompt "Чтобы изменить пароль"
+ ;; text of the change password widget prompt on the
+ ;; change password page
+ :change-col-hdr "изменения" ;; header for the changes column in history
+ :chpass-bad-match "Ваши предложенные пароли не совпадают"
+ ;; error text if proposed passwords don't match
+ :chpass-fail "Ваш пароль не был изменён"
+ ;; error text on fail other htan too short or bad match
+ :chpass-success "Ваш пароль был изменён"
+ ;; confirmation text on password change
+ :chpass-too-short "Вы предложили пароль недостаточно длины: требуется восемь символов"
+ ;; error text if proposed password is too short
+ :chpass-title-prefix "Изменить пароль для"
+ ;; prefix for title of change password page
+ :content-dir "Каталог содержимого"
+ ;; used in sanity check report
+ :content-dir-exists "Каталог содержимого существуе"
+ ;; used in sanity check report
+ :content-dir-is-dir "Каталог содержимого - это каталог"
+ ;; used in sanity check report
+ :cookies-about "Об кукисах" ;; about cookies text
+ :cookies-more "Этот сайт хранит информацию о сеансе как «cookie» в вашем браузере. Это поможет нам показать вам контент, который вы хотите увидеть. Этот файл cookie не идентифицирует вас и не может быть прочитан другими веб-сайтами. Он удаляется браузером, как только вы покидаете этот сайт. Этот веб-сайт не использует сторонние файлы cookie, поэтому ваш визит здесь не может быть отслежен другими веб-сайтами"
+ ;; more about cookies text
+ :default-page-title "представление" ;; title of the default page in this wiki
+ :del-col-hdr "Удалить" ;; header for delete column on edit users page
+ :del-user-fail "Не удалось удалить пользователя"
+ ;; error message on failure to delete user
+ :del-user-success "успешно удалён пользователь"
+ ;; confirmation message on deletion of user
+ :diff-title-prefix "Изменения с версии"
+ ;; prefix for the header of the changes page
+ :does-not-exist "не существует"
+ ;; (of a file or directory); used in sanity check report
+ :edit-col-hdr "редактировать" ;; header for edit column on edit users page
+ :edit-page-link "Редактировать эту страницу"
+ ;; text of the edit page link on the content frame
+ :edit-title-prefix "редактировать" ;; prefix for title of edit content page
+ :edit-users-link "Редактировать пользователей" ;; text of the edit users link on the menu
+ :edit-users-title "Выберите пользователя для редактирования"
+ ;; title of edit users page
+ :email-prompt "Адрес электронной почты" ;; text of the email widget prompt on edit user page
+ :file-or-directory "Файл или каталог"
+ ;; used in sanity check report
+ :file-summary-prompt "Описание / что изменилось"
+ ;; prompt for the file upload summary input
+ :file-upload-link-text "Вы можете ссылать этот файл, используя ссылку формы"
+ ;; Text introducing the link to an uploaded file
+ :file-upload-prompt "Файл для загрузки" ;; prompt string for the file upload widget
+ :file-upload-title "Загрузить файл" ;; title for the file upload page
+ :is-admin-prompt "Администратор?"
+ :here "здесь" ;; used in sanity check report
+ :home-link "Главная" ;; text of the home link on the menu
+ :is-not-directory "не является каталогом"
+ ;; (of a file or directory) used in sanity check report
+ :is-not-readable "не читаемый"
+ ;; (of a file or directory) used in sanity check report
+ :is-not-writable "недоступен для записи"
+ ;; (of a file or directory) used in sanity check report
+ :login-label "Вход!" ;; text of the login widget on the login page
+ :login-link "Вход" ;; text of the login link on the menu
+ :login-prompt "Чтобы отредактировать эту вики"
+ ;; text of the action widget prompt on the login page
+ :logout-label "Выйти!" ;; text of the logout widget on the logout page
+ :logout-link "Выйти" ;; text of the logout link on the menu
+ :logged-in-as "Вы вошли как"
+ ;; text of the 'logged in as' label on the menu
+ :history-link "история" ;; text of the history link on the content frame
+ :history-title-prefix "История об" ;; prefix of the title on the history page
+ :new-pass-prompt "Новый пароль" ;; text of the new password widget prompt on the change
+ ;; password and edit user pages
+ :no-admin-users "В файле 'passwd' нет пользователей с правами администратора"
+ ;; used in sanity check report
+ :old-pass-prompt "Ваш пароль"
+ ;; text of the old password widget prompt on the change
+ ;; password page, and password widget on login page
+ :password-file "файл пароля ('passwd')"
+ ;; used in sanity check report
+ :problems-found "проблемы были найдены"
+ ;; used in sanity check report
+ :rpt-pass-prompt "И опять" ;; text of the new password widget prompt on the change
+ ;; password and edit user pages
+ :save-prompt "Когда вы закончили редактирование"
+ ;; text of the save widget label on edit content
+ ;; and edit user page
+ :save-label "Сохранить!" ;; text of the save widget itself
+ :save-user-fail "Не удалось сохранить пользователя"
+ :save-user-success "Успешно сохранённый пользователь"
+ :see-documentation "для получения дополнительной информации смотрите документацию "
+ ;; used in sanity check report
+ :smeagol-not-initialised
+ "Смеаголь неправильно инициализирован"
+ ;; title of the sanity check report
+ :smeagol-misconfiguration
+ "Смеаголь не смог найти некоторые ресурсы, от которых это зависит,
+ возможно, из-за неправильной конфигурации или отсутствующих переменных среды."
+ ;; used in sanity check report
+ :user-lacks-field "В пользовательской записи в файле passwd отсутствует поле"
+ ;; used in sanity check report
+ :username-prompt "Имя пользователя" ;; text of the username widget prompt on edit user page
+ ;; text of the is admin widget prompt on edit user page
+ :user-title-prefix "Изменить пользователя" ;; prefix for title of edit user page
+ :vers-col-hdr "Версия" ;; header for the version column in history
+ :what-col-hdr "Что" ;; header for the what column in history
+ :what-changed-prompt "Что вы изменили?"
+ ;; text of the summary widget prompt on edit
+ ;; content page
+ :when-col-hdr "когда" ;; header for the when column in history
+ :your-uname-prompt "Ваш логин" ;; text of the username widget prompt on the login page
+ }
diff --git a/resources/translations.edn b/resources/translations.edn
new file mode 100644
index 0000000..e69de29
From 40f299b43d7c05a870a3848f508887ab4bb2c810 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Thu, 24 May 2018 07:26:39 +0100
Subject: [PATCH 25/83] Version 1.0.1
---
project.clj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/project.clj b/project.clj
index efd7bc2..7e86d29 100644
--- a/project.clj
+++ b/project.clj
@@ -1,4 +1,4 @@
-(defproject smeagol "1.0.1-SNAPSHOT"
+(defproject smeagol "1.0.1"
:description "A simple Git-backed Wiki inspired by Gollum"
:url "https://github.com/simon-brooke/smeagol"
:license {:name "GNU General Public License,version 2.0 or (at your option) any later version"
From 05eafe603ff29a8144def4cb6a9111173cc457e7 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Thu, 24 May 2018 08:26:37 +0100
Subject: [PATCH 26/83] Minor: alphorder requirements, documentation spelling.
---
project.clj | 6 +++---
src/smeagol/include/resolve.clj | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/project.clj b/project.clj
index 375538d..8769cba 100644
--- a/project.clj
+++ b/project.clj
@@ -7,6 +7,7 @@
[clj-yaml "0.4.0"]
[com.cemerick/url "0.1.1"]
[com.fzakaria/slf4j-timbre "0.3.7"]
+ [com.stuartsierra/component "0.3.2"]
[com.taoensso/encore "2.92.0"]
[com.taoensso/timbre "4.10.0"]
[com.taoensso/tower "3.0.2" :exclusions [com.taoensso/encore]]
@@ -26,12 +27,11 @@
[org.slf4j/log4j-over-slf4j "1.7.25"]
[org.slf4j/jul-to-slf4j "1.7.25"]
[org.slf4j/jcl-over-slf4j "1.7.25"]
+ [prismatic/schema "1.1.9"]
[prone "1.1.4"]
[ring/ring-anti-forgery "1.1.0"]
[ring-server "0.4.0"]
- [selmer "1.11.0"]
- [com.stuartsierra/component "0.3.2"]
- [prismatic/schema "1.1.9"]]
+ [selmer "1.11.0"]]
:repl-options {:init-ns smeagol.repl}
diff --git a/src/smeagol/include/resolve.clj b/src/smeagol/include/resolve.clj
index 7a2b3b1..266a276 100644
--- a/src/smeagol/include/resolve.clj
+++ b/src/smeagol/include/resolve.clj
@@ -11,7 +11,7 @@ smeagol.include and not inteded for direct usage."
[type :- s/Keyword
local-base-dir :- s/Str])
-;As schema does'nt support s/defprotocol we use the dispatcher for annotation & validation.
+;As schema doesn't support s/defprotocol we use the dispatcher for annotation & validation.
(s/defn dispatch-by-resolver-type :- s/Keyword
"Dispatcher for different resolver implementations."
[resolver :- Resolver
@@ -19,7 +19,7 @@ smeagol.include and not inteded for direct usage."
(:type resolver))
(defmulti do-resolve-md
- "Multimethod return a markfown file content for given uri."
+ "Multimethod return a markdown file content for given uri."
dispatch-by-resolver-type)
(s/defmethod do-resolve-md :default
[resolver :- Resolver
@@ -29,7 +29,7 @@ smeagol.include and not inteded for direct usage."
(defprotocol ResolveMd
(resolve-md
[resolver uri]
- "return a markfown file content for given uru."))
+ "return a markfown file content for given uri."))
(extend-type Resolver
ResolveMd
From f3456d819cbcd65ab8d1091539e31ffda8d55434 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Thu, 24 May 2018 08:28:37 +0100
Subject: [PATCH 27/83] Upversion to 1.0.2-SNAPSHOT
---
project.clj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/project.clj b/project.clj
index 8769cba..1de6005 100644
--- a/project.clj
+++ b/project.clj
@@ -1,4 +1,4 @@
-(defproject smeagol "1.0.1"
+(defproject smeagol "1.0.2-SNAPSHOT"
:description "A simple Git-backed Wiki inspired by Gollum"
:url "https://github.com/simon-brooke/smeagol"
:license {:name "GNU General Public License,version 2.0 or (at your option) any later version"
From 7b284eb13fd95dd2a96b6cbb8a5be2ac808d2ae0 Mon Sep 17 00:00:00 2001
From: jem
Date: Fri, 25 May 2018 17:15:46 +0200
Subject: [PATCH 28/83] Added include doc
---
README.md | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/README.md b/README.md
index 03cd9d1..46d38e1 100644
--- a/README.md
+++ b/README.md
@@ -98,6 +98,17 @@ You can (if you're logged in) upload files, including images, using the **Upload

+## Includes
+You can include pages into the current page youre working on. To do so, you can add a include link:
+
+`&[:indent-heading s/Num :indent-list s/Num](relative or absolute uri s/Str)`
+
+Parameters semantics:
+ * uri: The page to include. At the moment only pages of the current wiki are allowed. A page called "PageToBeIncluded" will result in a uri "PageToBeIncluded.md".
+ * indent-heading: You can indent headings of included page to adjust the included content to your surrounding structure. Indents 0-9 are supported.
+ * indent-list: In Same manner you can indent lists of included page to adjust the included content to your surrounding structure. Indents 0-9 are supported.
+ Security warning: At the moment there is no check against directory traversal attack. So include feature may expose files outside of your wiki.
+
## Advertisement
If you like what you see here, I am available for work on open source Clojure projects.
From 7e5b3d74dac2e0c675dcec024c544a6f36a2cbb0 Mon Sep 17 00:00:00 2001
From: jem
Date: Fri, 25 May 2018 17:19:33 +0200
Subject: [PATCH 29/83] spelling & format fixes for README
---
README.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 46d38e1..2dfc62b 100644
--- a/README.md
+++ b/README.md
@@ -99,14 +99,15 @@ You can (if you're logged in) upload files, including images, using the **Upload

## Includes
-You can include pages into the current page youre working on. To do so, you can add a include link:
+You can include pages into the current page you're working on. To do so, you can add a include link:
`&[:indent-heading s/Num :indent-list s/Num](relative or absolute uri s/Str)`
Parameters semantics:
- * uri: The page to include. At the moment only pages of the current wiki are allowed. A page called "PageToBeIncluded" will result in a uri "PageToBeIncluded.md".
+ * uri: The page to include. At the moment only pages from the current wiki are allowed. A page called "PageToBeIncluded" will result in a uri "PageToBeIncluded.md".
* indent-heading: You can indent headings of included page to adjust the included content to your surrounding structure. Indents 0-9 are supported.
* indent-list: In Same manner you can indent lists of included page to adjust the included content to your surrounding structure. Indents 0-9 are supported.
+
Security warning: At the moment there is no check against directory traversal attack. So include feature may expose files outside of your wiki.
## Advertisement
From 77d77ed3343ccfd65f57576728b1c2e1af69a082 Mon Sep 17 00:00:00 2001
From: jem
Date: Fri, 25 May 2018 17:21:32 +0200
Subject: [PATCH 30/83] minor doc add in README
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 2dfc62b..6cc0691 100644
--- a/README.md
+++ b/README.md
@@ -108,7 +108,7 @@ Parameters semantics:
* indent-heading: You can indent headings of included page to adjust the included content to your surrounding structure. Indents 0-9 are supported.
* indent-list: In Same manner you can indent lists of included page to adjust the included content to your surrounding structure. Indents 0-9 are supported.
- Security warning: At the moment there is no check against directory traversal attack. So include feature may expose files outside of your wiki.
+ Security warning: At the moment there is no check against directory traversal attack. So include feature may expose files outside of your wiki content-dir.
## Advertisement
If you like what you see here, I am available for work on open source Clojure projects.
From 1136e792d4810fd9af8b443fdea6d6be4aeda79e Mon Sep 17 00:00:00 2001
From: jem
Date: Tue, 8 Jan 2019 17:42:24 +0100
Subject: [PATCH 31/83] fix rings & noirs resource upload & serve location
---
src/smeagol/middleware.clj | 14 ++++++++++++--
src/smeagol/routes/wiki.clj | 4 +++-
src/smeagol/uploads.clj | 14 ++++++++------
3 files changed, 23 insertions(+), 9 deletions(-)
diff --git a/src/smeagol/middleware.clj b/src/smeagol/middleware.clj
index 82ccb59..aa14cb2 100644
--- a/src/smeagol/middleware.clj
+++ b/src/smeagol/middleware.clj
@@ -6,7 +6,12 @@
[selmer.middleware :refer [wrap-error-page]]
[prone.middleware :refer [wrap-exceptions]]
[ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
- [noir-exception.core :refer [wrap-internal-error]]))
+ [ring.middleware.file :refer [wrap-file]]
+ [ring.middleware.resource :refer [wrap-resource]]
+ [ring.middleware.content-type :refer [wrap-content-type]]
+ [ring.middleware.not-modified :refer [wrap-not-modified]]
+ [noir-exception.core :refer [wrap-internal-error]]
+ [smeagol.util :as util]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
@@ -44,7 +49,12 @@
(def production-middleware
- [#(wrap-internal-error % :log (fn [e] (timbre/error e)))])
+ [#(wrap-internal-error % :log (fn [e] (timbre/error e)))
+ #(wrap-file % util/content-dir
+ {:index-files? false :prefer-handler? true})
+ #(wrap-resource % "public")
+ #(wrap-content-type %)
+ #(wrap-not-modified %)])
(defn load-middleware []
diff --git a/src/smeagol/routes/wiki.clj b/src/smeagol/routes/wiki.clj
index 467275d..1002b7b 100644
--- a/src/smeagol/routes/wiki.clj
+++ b/src/smeagol/routes/wiki.clj
@@ -52,6 +52,7 @@
"Process `source-text` and save it to the specified `file-path`, committing it
to Git and finally redirecting to wiki-page."
[params suffix request]
+ (timbre/trace (format "process-source: '%s'" request))
(let [source-text (:src params)
page (:page params)
file-name (str page suffix)
@@ -122,6 +123,7 @@
(defn wiki-page
"Render the markdown page specified in this `request`, if any. If none found, redirect to edit-page"
[request]
+ (timbre/trace (format "wiki-page: '%s'" request))
(or
(show-sanity-check-error)
(let [params (keywordize-keys (:params request))
@@ -163,7 +165,7 @@
"Render a form to allow the upload of a file."
[request]
(let [params (keywordize-keys (:params request))
- data-path (str (io/resource-path) "/content/uploads/")
+ data-path (str util/content-dir "/content/uploads/")
git-repo (hist/load-or-init-repo util/content-dir)
upload (:upload params)
uploaded (if upload (ul/store-upload params data-path))
diff --git a/src/smeagol/uploads.clj b/src/smeagol/uploads.clj
index 27ddceb..cdac04a 100644
--- a/src/smeagol/uploads.clj
+++ b/src/smeagol/uploads.clj
@@ -56,9 +56,11 @@
filename (:filename upload)]
(timbre/info
(str "Storing upload file: " upload))
- (if tmp-file
- (do
- (.renameTo tmp-file
- (File. (str path filename)))
- filename)
- (throw (Exception. "No file found?")))))
+ (timbre/debug
+ (str "store-upload mv file: " tmp-file " to: " path filename))
+ (if tmp-file
+ (do
+ (.renameTo tmp-file
+ (File. (str path filename)))
+ filename)
+ (throw (Exception. "No file found?")))))
From fad1fcfea5b1f0ad4dfff7473afeb3c2981854d7 Mon Sep 17 00:00:00 2001
From: jem
Date: Tue, 8 Jan 2019 19:59:16 +0100
Subject: [PATCH 32/83] make start page configurable
---
resources/config.edn | 4 ++--
src/smeagol/routes/wiki.clj | 2 +-
src/smeagol/util.clj | 3 +++
3 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/resources/config.edn b/resources/config.edn
index 0fcb152..80d0738 100644
--- a/resources/config.edn
+++ b/resources/config.edn
@@ -28,6 +28,7 @@
;; ; ; ; ; ; ; ; ; ;
{
:content-dir "resources/public/content"
+ :start-page "README"
;; where content is served from.
:default-locale "en-GB" ;; default language used for messages
:formatters {"vega" smeagol.formatting/process-vega
@@ -38,6 +39,5 @@
;; :trace :debug :info :warn :error :fatal
:passwd "resources/passwd"
;; where the password file is stored
- :site-title "Smeagol" ;; overall title of the site, used in
+ :site-title "Smeagol"} ;; overall title of the site, used in
;; page headings
-}
diff --git a/src/smeagol/routes/wiki.clj b/src/smeagol/routes/wiki.clj
index 467275d..91174b2 100644
--- a/src/smeagol/routes/wiki.clj
+++ b/src/smeagol/routes/wiki.clj
@@ -125,7 +125,7 @@
(or
(show-sanity-check-error)
(let [params (keywordize-keys (:params request))
- page (or (:page params) (util/get-message :default-page-title "Introduction" request))
+ page (or (:page params) util/start-page (util/get-message :default-page-title "Introduction" request))
file-name (str page ".md")
file-path (cjio/file util/content-dir file-name)
exists? (.exists (clojure.java.io/as-file file-path))]
diff --git a/src/smeagol/util.clj b/src/smeagol/util.clj
index 015a5db..8ab537d 100644
--- a/src/smeagol/util.clj
+++ b/src/smeagol/util.clj
@@ -35,6 +35,9 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(def start-page
+ (:start-page config))
+
(def content-dir
(or
(:content-dir config)
From 3542ac914636fbf53a4d29fa079ab700fc3a38f4 Mon Sep 17 00:00:00 2001
From: Vlad Bokov
Date: Fri, 18 Jan 2019 07:05:19 +0700
Subject: [PATCH 33/83] Prefer content-dir files over classpath (like
/content/stylesheet.css)
---
src/smeagol/handler.clj | 1 +
src/smeagol/middleware.clj | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/smeagol/handler.clj b/src/smeagol/handler.clj
index d25e415..9cbaca5 100644
--- a/src/smeagol/handler.clj
+++ b/src/smeagol/handler.clj
@@ -97,6 +97,7 @@
[xss-protection?]
(-> site-defaults
(update-in [:session] merge session-defaults)
+ (dissoc :static)
(assoc-in [:security :anti-forgery] xss-protection?)))
diff --git a/src/smeagol/middleware.clj b/src/smeagol/middleware.clj
index aa14cb2..92cfac0 100644
--- a/src/smeagol/middleware.clj
+++ b/src/smeagol/middleware.clj
@@ -50,9 +50,9 @@
(def production-middleware
[#(wrap-internal-error % :log (fn [e] (timbre/error e)))
+ #(wrap-resource % "public")
#(wrap-file % util/content-dir
{:index-files? false :prefer-handler? true})
- #(wrap-resource % "public")
#(wrap-content-type %)
#(wrap-not-modified %)])
From 4f8c4b8925d3596ff42c2906958cbf21efbd1992 Mon Sep 17 00:00:00 2001
From: jem
Date: Fri, 18 Jan 2019 15:06:33 +0100
Subject: [PATCH 34/83] fixed test - Now we are getting a 200 response again.
---
resources/config.edn | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/config.edn b/resources/config.edn
index 80d0738..d5f7017 100644
--- a/resources/config.edn
+++ b/resources/config.edn
@@ -28,7 +28,7 @@
;; ; ; ; ; ; ; ; ; ;
{
:content-dir "resources/public/content"
- :start-page "README"
+ :start-page "Introduction"
;; where content is served from.
:default-locale "en-GB" ;; default language used for messages
:formatters {"vega" smeagol.formatting/process-vega
From 5b785389b404ac8169191dfec3096afa54473c9b Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Sat, 19 Jan 2019 17:20:50 +0000
Subject: [PATCH 35/83] Version 1.0.2
---
project.clj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/project.clj b/project.clj
index 49977fc..91a13a1 100644
--- a/project.clj
+++ b/project.clj
@@ -1,4 +1,4 @@
-(defproject smeagol "1.0.2-SNAPSHOT"
+(defproject smeagol "1.0.2"
:description "A simple Git-backed Wiki inspired by Gollum"
:url "https://github.com/simon-brooke/smeagol"
:license {:name "GNU General Public License,version 2.0 or (at your option) any later version"
From 8374432a168f01f8cba581d2dbfdcbd4f4d15730 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Sat, 19 Jan 2019 17:46:33 +0000
Subject: [PATCH 36/83] Added change log
---
resources/public/content/Change log.md | 7 +++++++
resources/public/content/_side-bar.md | 1 +
2 files changed, 8 insertions(+)
create mode 100644 resources/public/content/Change log.md
diff --git a/resources/public/content/Change log.md b/resources/public/content/Change log.md
new file mode 100644
index 0000000..c948455
--- /dev/null
+++ b/resources/public/content/Change log.md
@@ -0,0 +1,7 @@
+# 1.0.2
+Two fixes contributed by a user:
+
+* [Configurable start page](https://github.com/journeyman-cc/smeagol/commit/b1eeecde1d0555e9b708807c63e28fac21de58c4)
+* [Fixed uploads](https://github.com/journeyman-cc/smeagol/commit/1136e792d4810fd9af8b443fdea6d6be4aeda79e)
+
+Many thanks to [M Jerger](https://github.com/jerger) for these.
\ No newline at end of file
diff --git a/resources/public/content/_side-bar.md b/resources/public/content/_side-bar.md
index a675893..ad01779 100644
--- a/resources/public/content/_side-bar.md
+++ b/resources/public/content/_side-bar.md
@@ -1,4 +1,5 @@
* [[Introduction]]
+* [[Change log]]
* [[User Documentation]]
* [[Deploying Smeagol]]
* [[Developing Smeagol]]
From 95ee7e13eb4aaae31aae14531a7d52c67f2c0a47 Mon Sep 17 00:00:00 2001
From: jem
Date: Fri, 25 Jan 2019 17:53:27 +0100
Subject: [PATCH 37/83] make error handling more user friendly
---
src/smeagol/include/resolve_local_file.clj | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/smeagol/include/resolve_local_file.clj b/src/smeagol/include/resolve_local_file.clj
index c35e3e5..a603f8a 100644
--- a/src/smeagol/include/resolve_local_file.clj
+++ b/src/smeagol/include/resolve_local_file.clj
@@ -19,7 +19,11 @@ smeagol.include and not inteded for direct usage."
(cond exists?
(do
(timbre/info (format "Including page '%s' from file '%s'" uri file-path))
- (slurp file-path)))))
+ (slurp file-path))
+ :else
+ (do
+ (timbre/info (format "Page '%s' not found at '%s'" uri file-path))
+ (str "include not found at " file-path)))))
(s/defn
new-resolver
From 4d5f1d553d477e73a815db11fc1ecd0590ef044c Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Thu, 23 Jan 2020 08:26:57 +0000
Subject: [PATCH 38/83] Updated some obsolete dependencies
(probably more need to be updated).
---
project.clj | 4 ++--
src/smeagol/formatting.clj | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/project.clj b/project.clj
index 91a13a1..877f474 100644
--- a/project.clj
+++ b/project.clj
@@ -7,7 +7,7 @@
[clj-yaml "0.4.0"]
[com.cemerick/url "0.1.1"]
[com.fzakaria/slf4j-timbre "0.3.7"]
- [com.stuartsierra/component "0.3.2"]
+ [com.stuartsierra/component "0.4.0"]
[com.taoensso/encore "2.92.0"]
[com.taoensso/timbre "4.10.0"]
[com.taoensso/tower "3.0.2" :exclusions [com.taoensso/encore]]
@@ -43,7 +43,7 @@
[io.sarnowski/lein-docker "1.0.0"]
[lein-environ "1.0.0"]
[lein-marginalia "0.7.1" :exclusions [org.clojure/clojure]]
- [lein-ring "0.8.13" :exclusions [org.clojure/clojure]]]
+ [lein-ring "0.12.5" :exclusions [org.clojure/clojure]]]
:bower-dependencies [[simplemde "1.11.2"]
;; [vega-embed "3.0.0-beta.20"] ;; vega-embed currently not loaded from Bower because of
diff --git a/src/smeagol/formatting.clj b/src/smeagol/formatting.clj
index 6a874c8..94fd134 100644
--- a/src/smeagol/formatting.clj
+++ b/src/smeagol/formatting.clj
@@ -49,7 +49,7 @@
[^String html-src]
(if html-src
(cs/replace html-src #"\[\[[^\[\]]*\]\]"
- #(let [text (clojure.string/replace %1 #"[\[\]]" "")
+ #(let [text (cs/replace %1 #"[\[\]]" "")
encoded (url-encode text)
;; I use '\_' to represent '_' in wiki markup, because
;; '_' is meaningful in Markdown. However, this needs to
From 6e907cc85d93b81b8393278bf003085790aa336a Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Thu, 23 Jan 2020 08:43:49 +0000
Subject: [PATCH 39/83] Upversioned to '1.0.3-SNAPSHOT'; amended README.
---
README.md | 63 ++++++++++++++++++++++++++++++++---------------------
project.clj | 2 +-
2 files changed, 39 insertions(+), 26 deletions(-)
diff --git a/README.md b/README.md
index 6cc0691..84b1577 100644
--- a/README.md
+++ b/README.md
@@ -110,38 +110,29 @@ Parameters semantics:
Security warning: At the moment there is no check against directory traversal attack. So include feature may expose files outside of your wiki content-dir.
-## Advertisement
-If you like what you see here, I am available for work on open source Clojure projects.
-
-### Phoning home
-Smeagol currently requests the WEFT logo in the page footer from my home site. This is mainly so I can get a feel for how many people are using the product. If you object to this, edit the file
-
- resources/templates/base.html
-
-and replace the line
-
- Developed by WEFT
-
-with the line
-
- Developed by WEFT
-
-## License
-Copyright © 2014-2015 Simon Brooke. Licensed under the GNU General Public License,
-version 2.0 or (at your option) any later version. If you wish to incorporate
-parts of Smeagol into another open source project which uses a less restrictive
-license, please contact me; I'm open to dual licensing it.
-
## Prerequisites
You will need [Leiningen](https://github.com/technomancy/leiningen) 2.0 or above installed.
You will need [node](https://nodejs.org/en/) and [bower](https://bower.io/) installed.
-## Running
-To start a web server for the application, run:
+## Development
+To start a development web server for the application, run:
lein bower install
- lein ring server
+ lein repl
+
+And then, when the repl starts up,
+
+ (start-server)
+
+## Running
+
+To build a standalone Smeagol jar file, run:
+
+ lein bower install
+ lein ring uberjar
+
+**HOWEVER**, this will not run without [configuration](https://github.com/journeyman-cc/smeagol/blob/develop/resources/public/content/Environment%20Variables.md).
Alternatively, if you want to deploy to a servlet container (which I would strongly recommend), the simplest thing is to run:
@@ -172,3 +163,25 @@ To build your own Docker image, run:
lein docker build
This will build a new Docker image locally; you can, obviously, push it to your own Docker repository if you wish.
+
+## Advertisement
+If you like what you see here, I am available for work on open source Clojure projects.
+
+### Phoning home
+Smeagol currently requests the WEFT logo in the page footer from my home site. This is mainly so I can get a feel for how many people are using the product. If you object to this, edit the file
+
+ resources/templates/base.html
+
+and replace the line
+
+ Developed by WEFT
+
+with the line
+
+ Developed by WEFT
+
+## License
+Copyright © 2014-2020 Simon Brooke. Licensed under the GNU General Public License,
+version 2.0 or (at your option) any later version. If you wish to incorporate
+parts of Smeagol into another open source project which uses a less restrictive
+license, please contact me; I'm open to dual licensing it.
diff --git a/project.clj b/project.clj
index 877f474..4af209d 100644
--- a/project.clj
+++ b/project.clj
@@ -1,4 +1,4 @@
-(defproject smeagol "1.0.2"
+(defproject smeagol "1.0.3-SNAPSHOT"
:description "A simple Git-backed Wiki inspired by Gollum"
:url "https://github.com/simon-brooke/smeagol"
:license {:name "GNU General Public License,version 2.0 or (at your option) any later version"
From 5b01945c58ad1f7f985e358b3b3d7215733c56f5 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 5 Feb 2020 10:35:25 +0000
Subject: [PATCH 40/83] Trying to sort out the release process, which is not
working
---
project.clj | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/project.clj b/project.clj
index 4af209d..09f1b5d 100644
--- a/project.clj
+++ b/project.clj
@@ -59,12 +59,13 @@
:destroy smeagol.handler/destroy}
:release-tasks [["vcs" "assert-committed"]
+ ["clean"]
+ ["codox"]
["change" "version" "leiningen.release/bump-version" "release"]
["vcs" "commit"]
- ["vcs" "tag" "v." "--no-sign"]
- ["clean"]
["bower" "install"]
["ring" "uberjar"]
+ ["deploy" "clojars"]
["docker" "build"]
["docker" "push"]
["change" "version" "leiningen.release/bump-version"]
From a821f8d9888a9830497ac37ccd42a44a0ce84960 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 5 Feb 2020 10:47:13 +0000
Subject: [PATCH 41/83] lein-release plugin: preparing 1.0.3 release
---
project.clj | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/project.clj b/project.clj
index 09f1b5d..b147784 100644
--- a/project.clj
+++ b/project.clj
@@ -1,4 +1,4 @@
-(defproject smeagol "1.0.3-SNAPSHOT"
+(defproject smeagol "1.0.3"
:description "A simple Git-backed Wiki inspired by Gollum"
:url "https://github.com/simon-brooke/smeagol"
:license {:name "GNU General Public License,version 2.0 or (at your option) any later version"
@@ -58,6 +58,11 @@
:init smeagol.handler/init
:destroy smeagol.handler/destroy}
+ ;; for the time being, I'm not sure that I want to formally deploy this anywhere, and I certainly don't feel
+ ;; it's fair to clutter clojars.org with it.
+ :deploy-repositories [["releases" "file:/tmp"]
+ ["snapshots" "file:/tmp"]]
+
:release-tasks [["vcs" "assert-committed"]
["clean"]
["codox"]
@@ -65,7 +70,7 @@
["vcs" "commit"]
["bower" "install"]
["ring" "uberjar"]
- ["deploy" "clojars"]
+ ["deploy"]
["docker" "build"]
["docker" "push"]
["change" "version" "leiningen.release/bump-version"]
From 1105eacb05e64c66d1848bd95b4389bf95d793d4 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 5 Feb 2020 10:47:22 +0000
Subject: [PATCH 42/83] lein-release plugin: bumped version from 1.0.3 to
1.0.4-SNAPSHOT for next development cycle
---
project.clj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/project.clj b/project.clj
index b147784..a015883 100644
--- a/project.clj
+++ b/project.clj
@@ -1,4 +1,4 @@
-(defproject smeagol "1.0.3"
+(defproject smeagol "1.0.4-SNAPSHOT"
:description "A simple Git-backed Wiki inspired by Gollum"
:url "https://github.com/simon-brooke/smeagol"
:license {:name "GNU General Public License,version 2.0 or (at your option) any later version"
From 38e4207d4a19fe679b843f06dcc27f1ab8afc670 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 5 Feb 2020 17:45:31 +0000
Subject: [PATCH 43/83] Minor change to log message
---
src/smeagol/util.clj | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/smeagol/util.clj b/src/smeagol/util.clj
index 8ab537d..c290e91 100644
--- a/src/smeagol/util.clj
+++ b/src/smeagol/util.clj
@@ -66,8 +66,9 @@
(timbre/error
any
(str
- "Failed to parse accept-language header "
- specifier))
+ "Failed to parse accept-language header '"
+ specifier
+ "'"))
{}))]
(merge
messages
From 3f667afb355f9694932fb6e5756268369ba50770 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 5 Feb 2020 18:21:02 +0000
Subject: [PATCH 44/83] #44: fixed
---
src/smeagol/routes/wiki.clj | 2 +-
src/smeagol/uploads.clj | 12 ++++++++----
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/smeagol/routes/wiki.clj b/src/smeagol/routes/wiki.clj
index b455d78..72df1af 100644
--- a/src/smeagol/routes/wiki.clj
+++ b/src/smeagol/routes/wiki.clj
@@ -165,7 +165,7 @@
"Render a form to allow the upload of a file."
[request]
(let [params (keywordize-keys (:params request))
- data-path (str util/content-dir "/content/uploads/")
+ data-path (str util/content-dir "/uploads/")
git-repo (hist/load-or-init-repo util/content-dir)
upload (:upload params)
uploaded (if upload (ul/store-upload params data-path))
diff --git a/src/smeagol/uploads.clj b/src/smeagol/uploads.clj
index cdac04a..7fd8c18 100644
--- a/src/smeagol/uploads.clj
+++ b/src/smeagol/uploads.clj
@@ -59,8 +59,12 @@
(timbre/debug
(str "store-upload mv file: " tmp-file " to: " path filename))
(if tmp-file
- (do
- (.renameTo tmp-file
- (File. (str path filename)))
- filename)
+ (try
+ (do
+ (.renameTo tmp-file
+ (File. (str path filename)))
+ filename)
+ (catch Exception x
+ (timbre/error (str "Failed to move " tmp-file " to " path filename "; " (type x) ": " (.getMessage x)))
+ (throw x)))
(throw (Exception. "No file found?")))))
From d6d0a5fc4050ff52b37c0574b1505bde419050a8 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 5 Feb 2020 23:15:48 +0000
Subject: [PATCH 45/83] Another bit of #44, which got missed somehow.
---
resources/templates/upload.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/templates/upload.html b/resources/templates/upload.html
index a91865e..46a48be 100644
--- a/resources/templates/upload.html
+++ b/resources/templates/upload.html
@@ -8,7 +8,7 @@
{% i18n file-upload-link-text %}:
- 
+ 
{% else %}
From a6fb7583149cd0d36cf7ca31c40f6cee72ba027c Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 5 Feb 2020 23:49:02 +0000
Subject: [PATCH 46/83] Separate menu icon onto its own line, relativise the
URL
---
resources/public/content/User Documentation.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/resources/public/content/User Documentation.md b/resources/public/content/User Documentation.md
index 92435ff..51489af 100644
--- a/resources/public/content/User Documentation.md
+++ b/resources/public/content/User Documentation.md
@@ -1,6 +1,7 @@
## If you're using a small device
If you're using a small device, like a mobile phone, the top menu isn't usually displayed. Instead there will be an image like this:
-
+
+
at the top left of the page. Touching this image will cause the top menu to be displayed, and it will have all the options described in this documentation.
From dad380e0d9192b33cbc3607a7ae49b6679abf472 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Thu, 6 Feb 2020 15:23:59 +0000
Subject: [PATCH 47/83] Attempting to understand why the configuration doesn't
load.
---
src/smeagol/configuration.clj | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/src/smeagol/configuration.clj b/src/smeagol/configuration.clj
index f4631f9..1f9735e 100644
--- a/src/smeagol/configuration.clj
+++ b/src/smeagol/configuration.clj
@@ -111,9 +111,19 @@
file is read (if it is specified and present), but that individual
values can be overridden by environment variables."
(try
+ (timbre/info (str "Reading configuration from " config-file-path))
(let [file-contents (try
(read-string (slurp config-file-path))
- (catch Exception _ {}))
+ (catch Exception x
+ (timbre/error
+ (str
+ "Failed to read configuration from "
+ config-file-path
+ " because: "
+ (type x)
+ "; "
+ (.getMessage x)))
+ {}))
config (merge
file-contents
(transform-map
From 84360110fc964986f27d47b50beb820565fb8ff2 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Fri, 7 Feb 2020 14:20:32 +0000
Subject: [PATCH 48/83] Fixes to two minor bugs
* Wrong URL printed in upload page;
* 'New file Foo' instead of 'New file Edit Foo' as default git commit message for a new file.
---
resources/templates/edit.html | 2 +-
resources/templates/upload.html | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/resources/templates/edit.html b/resources/templates/edit.html
index 3213d34..3379dc5 100644
--- a/resources/templates/edit.html
+++ b/resources/templates/edit.html
@@ -13,7 +13,7 @@
{% i18n what-changed-prompt %}
+ value="{%if exists%}{%else%}New file {{page}}{%endif%}" required/>
{% i18n save-prompt %}
diff --git a/resources/templates/upload.html b/resources/templates/upload.html
index 46a48be..53284ec 100644
--- a/resources/templates/upload.html
+++ b/resources/templates/upload.html
@@ -4,7 +4,7 @@
{% if uploaded %}
{% if is-image %}
-
+
{% i18n file-upload-link-text %}:
From da3bde16d0885beafe292057259808f69714778a Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Fri, 7 Feb 2020 19:01:30 +0000
Subject: [PATCH 49/83] Added 'list uploaded files' page, only accessible if
logged in.
---
.gitignore | 2 +
project.clj | 2 +
resources/i18n/en-GB.edn | 7 +-
resources/public/content/_edit-side-bar.md | 4 +-
resources/templates/list-uploads.html | 34 +++++++
resources/templates/upload.html | 3 +
src/smeagol/routes/wiki.clj | 102 +++++++++++++++++----
src/smeagol/uploads.clj | 6 +-
8 files changed, 138 insertions(+), 22 deletions(-)
create mode 100644 resources/templates/list-uploads.html
diff --git a/.gitignore b/.gitignore
index 2a76665..95c58ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,5 @@ smeagol.log*
/node_modules/
.DS_Store
+
+resources/public/content/uploads/
diff --git a/project.clj b/project.clj
index a015883..d9b5cda 100644
--- a/project.clj
+++ b/project.clj
@@ -5,6 +5,7 @@
:url "https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html"}
:dependencies [[clj-jgit "0.8.10"]
[clj-yaml "0.4.0"]
+ [clojure.java-time "0.3.2"]
[com.cemerick/url "0.1.1"]
[com.fzakaria/slf4j-timbre "0.3.7"]
[com.stuartsierra/component "0.4.0"]
@@ -17,6 +18,7 @@
[im.chit/cronj "1.4.4"]
[lib-noir "0.9.9" :exclusions [org.clojure/tools.reader]]
[markdown-clj "0.9.99" :exclusions [com.keminglabs/cljx]]
+ [me.raynes/fs "1.4.6"]
[noir-exception "0.2.5"]
[org.clojars.simon_brooke/internationalisation "1.0.3"]
[org.clojure/clojure "1.8.0"]
diff --git a/resources/i18n/en-GB.edn b/resources/i18n/en-GB.edn
index 64c164c..6f4d8b2 100644
--- a/resources/i18n/en-GB.edn
+++ b/resources/i18n/en-GB.edn
@@ -83,6 +83,8 @@
:file-upload-title "Upload a file" ;; title for the file upload page
:is-admin-prompt "Is administrator?"
:here "here" ;; used in sanity check report
+ :history-link "History" ;; text of the history link on the content frame
+ :history-title-prefix "History of" ;; prefix of the title on the history page
:home-link "Home" ;; text of the home link on the menu
:is-not-directory "is not a directory"
;; (of a file or directory) used in sanity check report
@@ -90,6 +92,8 @@
;; (of a file or directory) used in sanity check report
:is-not-writable "is not writable"
;; (of a file or directory) used in sanity check report
+ :list-files "List uploaded files"
+ ;; title of the 'List uploaded Files' page
:login-label "Log in!" ;; text of the login widget on the login page
:login-link "Log in" ;; text of the login link on the menu
:login-prompt "To edit this wiki"
@@ -98,8 +102,7 @@
:logout-link "Log out" ;; text of the logout link on the menu
:logged-in-as "You are logged in as"
;; text of the 'logged in as' label on the menu
- :history-link "History" ;; text of the history link on the content frame
- :history-title-prefix "History of" ;; prefix of the title on the history page
+ :matching "matching" ;; 'matching' in e.g. 'list files matching fred'
:new-pass-prompt "New password" ;; text of the new password widget prompt on the change
;; password and edit user pages
:no-admin-users "There are no users in the 'passwd' file with administrative privileges"
diff --git a/resources/public/content/_edit-side-bar.md b/resources/public/content/_edit-side-bar.md
index bbd61ca..ccec0c0 100644
--- a/resources/public/content/_edit-side-bar.md
+++ b/resources/public/content/_edit-side-bar.md
@@ -10,4 +10,6 @@
+ \*\***bold**\*\*
+ \__italic_\_
-More documentation [here](http://daringfireball.net/projects/markdown/syntax)
\ No newline at end of file
+More documentation [here](http://daringfireball.net/projects/markdown/syntax)
+
+Your uploaded files are listed here .
diff --git a/resources/templates/list-uploads.html b/resources/templates/list-uploads.html
new file mode 100644
index 0000000..761e4a8
--- /dev/null
+++ b/resources/templates/list-uploads.html
@@ -0,0 +1,34 @@
+{% extends "templates/base.html" %}
+
+{% block content %}
+
+
+
+
+ Name
+ Uploaded
+ Type this
+ To get this
+
+ {% for entry in files %}
+
+ {{entry.base-name}}
+ {{entry.modified}}
+
+ {% if entry.is-image %}  {% else %} [{{entry.name|capitalize}}](uploads/{{entry.base-name}}) {% endif %}
+
+
+ {% if entry.is-image %} {% else %} link {% endif %}
+
+
+
+ {% endfor %}
+
+
+{% endblock %}
diff --git a/resources/templates/upload.html b/resources/templates/upload.html
index 53284ec..7c0c373 100644
--- a/resources/templates/upload.html
+++ b/resources/templates/upload.html
@@ -34,5 +34,8 @@
{% endif %}
+
+ Your uploaded files are listed here .
+
{% endblock %}
diff --git a/src/smeagol/routes/wiki.clj b/src/smeagol/routes/wiki.clj
index 72df1af..5457fa1 100644
--- a/src/smeagol/routes/wiki.clj
+++ b/src/smeagol/routes/wiki.clj
@@ -7,6 +7,8 @@
[clojure.string :as cs]
[clojure.walk :refer :all]
[compojure.core :refer :all]
+ [java-time :as jt]
+ [me.raynes.fs :as fs]
[noir.io :as io]
[noir.response :as response]
[noir.util.route :as route]
@@ -161,6 +163,75 @@
:page page
:history (hist/find-history repo-path file-name)}))))
+(def image-extns #{".gif" ".jpg" ".jpeg" ".png"})
+
+(defn format-instant
+ "Format this `unix-time`, expected to be a Long, into something human readable.
+ If `template` is supplied, use that as the formatting template as specified for
+ java.time.Formatter. Assumes system default timezone. Returns a string."
+ ([^Long unix-time]
+ (format-instant unix-time "EEEE, dd MMMM YYYY"))
+ ([^Long unix-time ^String template]
+ (jt/format
+ (java-time/formatter template)
+ (java.time.LocalDateTime/ofInstant
+ (java-time/instant unix-time)
+ (java.time.ZoneOffset/systemDefault)))))
+
+(defn list-uploads-page
+ "Render a list of all uploaded files"
+ [request]
+ (let
+ [params (keywordize-keys (:params request))
+ data-path (str util/content-dir "/uploads/")
+ files
+ (map
+ #(zipmap
+ [:base-name :is-image :modified :name]
+ [(fs/base-name %)
+ (if
+ (and (fs/extension %)
+ (image-extns (cs/lower-case (fs/extension %))))
+ true false)
+ (if
+ (fs/mod-time %)
+ (format-instant (fs/mod-time %)))
+ (fs/name %)])
+ (remove
+ #(or (cs/starts-with? (fs/name %) ".")
+ (fs/directory? %))
+ (file-seq (clojure.java.io/file data-path))))]
+ (layout/render
+ "list-uploads.html"
+ (merge (util/standard-params request)
+ {:title (str
+ (util/get-message :list-files request)
+ (if
+ (:search params)
+ (str " " (util/get-message :matching request))))
+ :search (:search params)
+ :files (if
+ (:search params)
+ (try
+ (let [pattern (re-pattern (:search params))]
+ (filter
+ #(re-find pattern (:base-name %))
+ files))
+ (catch Exception _ files))
+ files)
+ }))))
+
+(map
+ #(zipmap
+ [:base-name :is-image :modified :name]
+ [(fs/base-name %)
+ (if
+ (and (fs/extension %) (image-extns (cs/lower-case (fs/extension %))))
+ true false)
+ (fs/mod-time %)
+ (fs/name %)])
+ (file-seq (clojure.java.io/file "resources/public/content/uploads")))
+
(defn upload-page
"Render a form to allow the upload of a file."
[request]
@@ -174,23 +245,17 @@
(if
uploaded
(do
- (git/git-add git-repo uploaded)
+ (git/git-add git-repo (str data-path (fs/name uploaded)))
(git/git-commit git-repo summary {:name user :email (auth/get-email user)})))
(layout/render "upload.html"
(merge (util/standard-params request)
{:title (util/get-message :file-upload-title request)
- :uploaded uploaded
- :is-image (and
+ :uploaded (if uploaded (fs/base-name uploaded))
+ :is-image (if
uploaded
- (or
- (cs/ends-with? uploaded ".gif")
- (cs/ends-with? uploaded ".jpg")
- (cs/ends-with? uploaded ".jpeg")
- (cs/ends-with? uploaded ".png")
- (cs/ends-with? uploaded ".GIF")
- (cs/ends-with? uploaded ".JPG")
- (cs/ends-with? uploaded ".PNG")))}))))
-
+ (image-extns
+ (cs/lower-case
+ (fs/extension uploaded))))}))))
(defn version-page
"Render a specific historical version of a page"
@@ -286,8 +351,10 @@
(defroutes wiki-routes
- (GET "/wiki" request (wiki-page request))
(GET "/" request (wiki-page request))
+ (GET "/auth" request (auth-page request))
+ (POST "/auth" request (auth-page request))
+ (GET "/changes" request (diff-page request))
(GET "/delete-user" request (route/restricted (admin/delete-user request)))
(GET "/edit" request (route/restricted (edit-page request)))
(POST "/edit" request (route/restricted (edit-page request)))
@@ -297,11 +364,12 @@
(GET "/edit-user" request (route/restricted (admin/edit-user request)))
(POST "/edit-user" request (route/restricted (admin/edit-user request)))
(GET "/history" request (history-page request))
+ (GET "/list-uploads" request (route/restricted (list-uploads-page request)))
+ (POST "/list-uploads" request (route/restricted (list-uploads-page request)))
(GET "/version" request (version-page request))
- (GET "/changes" request (diff-page request))
- (GET "/auth" request (auth-page request))
- (POST "/auth" request (auth-page request))
(GET "/passwd" request (passwd-page request))
(POST "/passwd" request (passwd-page request))
(GET "/upload" request (route/restricted (upload-page request)))
- (POST "/upload" request (route/restricted (upload-page request))))
+ (POST "/upload" request (route/restricted (upload-page request)))
+ (GET "/wiki" request (wiki-page request))
+ )
diff --git a/src/smeagol/uploads.clj b/src/smeagol/uploads.clj
index 7fd8c18..7cadaea 100644
--- a/src/smeagol/uploads.clj
+++ b/src/smeagol/uploads.clj
@@ -49,7 +49,9 @@
"Store an upload both to the file system and to the database.
The issue with storing an upload is moving it into place.
If `params` are passed as a map, it is expected that this is a map from
- an HTTP POST operation of a form with type `multipart/form-data`."
+ an HTTP POST operation of a form with type `multipart/form-data`.
+
+ On success, returns the file object uploaded."
[params path]
(let [upload (:upload params)
tmp-file (:tempfile upload)
@@ -63,7 +65,7 @@
(do
(.renameTo tmp-file
(File. (str path filename)))
- filename)
+ (File. (str path filename)))
(catch Exception x
(timbre/error (str "Failed to move " tmp-file " to " path filename "; " (type x) ": " (.getMessage x)))
(throw x)))
From 59f86925e84d10f746fa2eb2f4b23a5ab0e2a199 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Fri, 7 Feb 2020 22:47:46 +0000
Subject: [PATCH 50/83] Corrected link rot (hadn't I done this before?)
---
resources/public/content/Extensible Markup.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/resources/public/content/Extensible Markup.md b/resources/public/content/Extensible Markup.md
index e0b1fed..129a375 100644
--- a/resources/public/content/Extensible Markup.md
+++ b/resources/public/content/Extensible Markup.md
@@ -4,7 +4,7 @@ A system of pluggable, extensible formatters is supported. In normal markdown, c
## The Vega formatter
-Inspired by [visdown](http://visdown.amitkaps.com/) and [vega-lite](https://vega.github.io/vega-lite/docs/), the Vega formatter allows you to embed vega data visualisations into Smeagol pages. The graph description should start with a line comprising three back-ticks and then the word '`vega`', and end with a line comprising just three backticks.
+Inspired by [visdown](https://visdown.com/) and [vega-lite](https://vega.github.io/vega-lite/docs/), the Vega formatter allows you to embed vega data visualisations into Smeagol pages. The graph description should start with a line comprising three back-ticks and then the word '`vega`', and end with a line comprising just three backticks.
Here's an example cribbed in its entirety from [here](http://visdown.amitkaps.com/london):
@@ -34,7 +34,7 @@ Data files can be uploaded in the same way as images, by using the **upload a fi
## The Mermaid formatter
-Graphs can now be embedded in a page using the [Mermaid](http://knsv.github.io/mermaid/index.html) graph description language. The graph description should start with a line comprising three back-ticks and then the word `mermaid`, and end with a line comprising just three backticks.
+Graphs can now be embedded in a page using the [Mermaid](https://mermaid-js.github.io/mermaid/#/) graph description language. The graph description should start with a line comprising three back-ticks and then the word `mermaid`, and end with a line comprising just three backticks.
Here's an example culled from the Mermaid documentation.
From ecd9d5a2707bee6c782c4540eb677794099432de Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Fri, 7 Feb 2020 22:49:00 +0000
Subject: [PATCH 51/83] Unfinished (but non-breaking) work on after-auth
redirect
---
src/smeagol/routes/wiki.clj | 42 +++++++++++++++++++++++--------------
1 file changed, 26 insertions(+), 16 deletions(-)
diff --git a/src/smeagol/routes/wiki.clj b/src/smeagol/routes/wiki.clj
index 5457fa1..a8e13ce 100644
--- a/src/smeagol/routes/wiki.clj
+++ b/src/smeagol/routes/wiki.clj
@@ -163,6 +163,9 @@
:page page
:history (hist/find-history repo-path file-name)}))))
+;;;; this next section is all stuff supporting the list-uploads page, and maybe
+;;;; should be moved to its own file.
+
(def image-extns #{".gif" ".jpg" ".jpeg" ".png"})
(defn format-instant
@@ -221,16 +224,7 @@
files)
}))))
-(map
- #(zipmap
- [:base-name :is-image :modified :name]
- [(fs/base-name %)
- (if
- (and (fs/extension %) (image-extns (cs/lower-case (fs/extension %))))
- true false)
- (fs/mod-time %)
- (fs/name %)])
- (file-seq (clojure.java.io/file "resources/public/content/uploads")))
+;;;; end of list-uploads section ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn upload-page
"Render a form to allow the upload of a file."
@@ -302,12 +296,14 @@
[request]
(or
(show-sanity-check-error)
- (let [params (keywordize-keys (:form-params request))
- username (:username params)
- password (:password params)
- action (:action params)
+ (let [params (keywordize-keys (:params request))
+ form-params (keywordize-keys (:form-params request))
+ username (:username form-params)
+ password (:password form-params)
+ action (:action form-params)
user (session/get :user)
- redirect-to (or (:redirect-to params) "/wiki")]
+ redirect-to (:redirect-to params)]
+ (if redirect-to (timbre/info (str "After auth, redirect to: " redirect-to)))
(cond
(= action (util/get-message :logout-label request))
(do
@@ -324,8 +320,22 @@
{:title (if user
(str (util/get-message :logout-link request) " " user)
(util/get-message :login-link request))
- :redirect-to ((:headers request) "referer")}))))))
+ :redirect-to redirect-to}))))))
+(defn wrap-restricted-redirect
+ ;; TODO: this is not idiomatic, and it's too late to write something idiomatic just now
+ [f request]
+ (route/restricted
+ (apply
+ f
+ (if
+ (-> request :params :redirect-to) ;; a redirect target has already been set
+ request
+ ;; else merge a redirect target into the params
+ (let
+ [redirect-to (if (:uri request)
+ (cs/join "?" [(:uri request) (:query-string request)]))]
+ (assoc-in request [:params :redirect-to] redirect-to))))))
(defn passwd-page
"Render a page to change the user password"
From e00beaf790cea5e7f3ba146d617fad63163d8317 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Sat, 8 Feb 2020 10:42:29 +0000
Subject: [PATCH 52/83] Trying to get JavaScript switchable between local and
cloudflare
Not working for two reasons:
1. `lein npm install` does not build packages on MacOS;
2. `{% ifequal js-from ":cloudflare" %}` breaks Selmer in wiki.html but not in edit.html - WTF?
---
package-lock.json | 1339 +++++++++++++++++++++++++++++++++
project.clj | 14 +-
resources/config.edn | 4 +-
resources/templates/edit.html | 9 +-
resources/templates/wiki.html | 19 +-
src/smeagol/configuration.clj | 2 +
src/smeagol/formatting.clj | 2 +-
src/smeagol/util.clj | 1 +
8 files changed, 1371 insertions(+), 19 deletions(-)
create mode 100644 package-lock.json
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..e5aa9b7
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1339 @@
+{
+ "name": "smeagol",
+ "version": "1.0.4-SNAPSHOT",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@braintree/sanitize-url": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-3.1.0.tgz",
+ "integrity": "sha512-GcIY79elgB+azP74j8vqkiXz8xLFfIzbQJdlwOPisgbKT00tviJQuEghOXSMVxJ00HoYJbGswr4kcllUc4xCcg=="
+ },
+ "@types/clone": {
+ "version": "0.1.30",
+ "resolved": "https://registry.npmjs.org/@types/clone/-/clone-0.1.30.tgz",
+ "integrity": "sha1-5zZWSMG0ITalnH1QQGN7O1yDthQ="
+ },
+ "@types/color-name": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
+ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
+ },
+ "@types/fast-json-stable-stringify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@types/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+ "integrity": "sha512-mky/O83TXmGY39P1H9YbUpjV6l6voRYlufqfFCvel8l1phuy8HRjdWc1rrPuN53ITBJlbyMSV6z3niOySO5pgQ=="
+ },
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
+ },
+ "ansi-styles": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+ "requires": {
+ "@types/color-name": "1.1.1",
+ "color-convert": "2.0.1"
+ }
+ },
+ "array-flat-polyfill": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/array-flat-polyfill/-/array-flat-polyfill-1.0.1.tgz",
+ "integrity": "sha512-hfJmKupmQN0lwi0xG6FQ5U8Rd97RnIERplymOv/qpq8AoNKPPAnxJadjFA23FNWm88wykh9HmpLJUUwUtNU/iw=="
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
+ },
+ "camel-case": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
+ "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=",
+ "requires": {
+ "no-case": "2.3.2",
+ "upper-case": "1.1.3"
+ }
+ },
+ "camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
+ },
+ "clean-css": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
+ "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==",
+ "requires": {
+ "source-map": "0.6.1"
+ }
+ },
+ "cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "requires": {
+ "string-width": "4.2.0",
+ "strip-ansi": "6.0.0",
+ "wrap-ansi": "6.2.0"
+ }
+ },
+ "clone": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+ "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18="
+ },
+ "codemirror": {
+ "version": "5.51.0",
+ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.51.0.tgz",
+ "integrity": "sha512-vyuYYRv3eXL0SCuZA4spRFlKNzQAewHcipRQCOKgRy7VNAvZxTKzbItdbCl4S5AgPZ5g3WkHp+ibWQwv9TLG7Q=="
+ },
+ "codemirror-spell-checker": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz",
+ "integrity": "sha1-HGYPkIlIPMtRE7m6nKGcP0mTNx4=",
+ "requires": {
+ "typo-js": "1.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "requires": {
+ "color-name": "1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+ },
+ "crypto-random-string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-3.1.0.tgz",
+ "integrity": "sha512-Tip3yGB+bA7B0W8E4K4mNf2rZhu5r2G5Tb89/utEl5tP1QuLjTF/S9a1b8ifDrR4ORc9Utf6tscpSEtBY3YcPQ==",
+ "requires": {
+ "type-fest": "0.8.1"
+ }
+ },
+ "css-b64-images": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/css-b64-images/-/css-b64-images-0.2.5.tgz",
+ "integrity": "sha1-QgBdgyBLK0pdk7axpWRBM7WSegI="
+ },
+ "d3": {
+ "version": "5.15.0",
+ "resolved": "https://registry.npmjs.org/d3/-/d3-5.15.0.tgz",
+ "integrity": "sha512-C+E80SL2nLLtmykZ6klwYj5rPqB5nlfN5LdWEAVdWPppqTD8taoJi2PxLZjPeYT8FFRR2yucXq+kBlOnnvZeLg==",
+ "requires": {
+ "d3-array": "1.2.4",
+ "d3-axis": "1.0.12",
+ "d3-brush": "1.1.5",
+ "d3-chord": "1.0.6",
+ "d3-collection": "1.0.7",
+ "d3-color": "1.4.0",
+ "d3-contour": "1.3.2",
+ "d3-dispatch": "1.0.6",
+ "d3-drag": "1.2.5",
+ "d3-dsv": "1.2.0",
+ "d3-ease": "1.0.6",
+ "d3-fetch": "1.1.2",
+ "d3-force": "1.2.1",
+ "d3-format": "1.4.3",
+ "d3-geo": "1.11.9",
+ "d3-hierarchy": "1.1.9",
+ "d3-interpolate": "1.4.0",
+ "d3-path": "1.0.9",
+ "d3-polygon": "1.0.6",
+ "d3-quadtree": "1.0.7",
+ "d3-random": "1.1.2",
+ "d3-scale": "2.2.2",
+ "d3-scale-chromatic": "1.5.0",
+ "d3-selection": "1.4.1",
+ "d3-shape": "1.3.7",
+ "d3-time": "1.1.0",
+ "d3-time-format": "2.2.3",
+ "d3-timer": "1.0.10",
+ "d3-transition": "1.3.2",
+ "d3-voronoi": "1.1.4",
+ "d3-zoom": "1.8.3"
+ }
+ },
+ "d3-array": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
+ "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
+ },
+ "d3-axis": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz",
+ "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ=="
+ },
+ "d3-brush": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.5.tgz",
+ "integrity": "sha512-rEaJ5gHlgLxXugWjIkolTA0OyMvw8UWU1imYXy1v642XyyswmI1ybKOv05Ft+ewq+TFmdliD3VuK0pRp1VT/5A==",
+ "requires": {
+ "d3-dispatch": "1.0.6",
+ "d3-drag": "1.2.5",
+ "d3-interpolate": "1.4.0",
+ "d3-selection": "1.4.1",
+ "d3-transition": "1.3.2"
+ }
+ },
+ "d3-chord": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz",
+ "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==",
+ "requires": {
+ "d3-array": "1.2.4",
+ "d3-path": "1.0.9"
+ }
+ },
+ "d3-collection": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz",
+ "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
+ },
+ "d3-color": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.0.tgz",
+ "integrity": "sha512-TzNPeJy2+iEepfiL92LAAB7fvnp/dV2YwANPVHdDWmYMm23qIJBYww3qT8I8C1wXrmrg4UWs7BKc2tKIgyjzHg=="
+ },
+ "d3-contour": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz",
+ "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==",
+ "requires": {
+ "d3-array": "1.2.4"
+ }
+ },
+ "d3-delaunay": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.2.1.tgz",
+ "integrity": "sha512-ZZdeJl6cKRyqYVFYK+/meXvWIrAvZsZTD7WSxl4OPXCmuXNgDyACAClAJHD63zL25TA+IJGURUNO7rFseNFCYw==",
+ "requires": {
+ "delaunator": "4.0.1"
+ }
+ },
+ "d3-dispatch": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz",
+ "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA=="
+ },
+ "d3-drag": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz",
+ "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==",
+ "requires": {
+ "d3-dispatch": "1.0.6",
+ "d3-selection": "1.4.1"
+ }
+ },
+ "d3-dsv": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz",
+ "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==",
+ "requires": {
+ "commander": "2.20.3",
+ "iconv-lite": "0.4.24",
+ "rw": "1.3.3"
+ }
+ },
+ "d3-ease": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.6.tgz",
+ "integrity": "sha512-SZ/lVU7LRXafqp7XtIcBdxnWl8yyLpgOmzAk0mWBI9gXNzLDx5ybZgnRbH9dN/yY5tzVBqCQ9avltSnqVwessQ=="
+ },
+ "d3-fetch": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz",
+ "integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==",
+ "requires": {
+ "d3-dsv": "1.2.0"
+ }
+ },
+ "d3-force": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz",
+ "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==",
+ "requires": {
+ "d3-collection": "1.0.7",
+ "d3-dispatch": "1.0.6",
+ "d3-quadtree": "1.0.7",
+ "d3-timer": "1.0.10"
+ }
+ },
+ "d3-format": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.3.tgz",
+ "integrity": "sha512-mm/nE2Y9HgGyjP+rKIekeITVgBtX97o1nrvHCWX8F/yBYyevUTvu9vb5pUnKwrcSw7o7GuwMOWjS9gFDs4O+uQ=="
+ },
+ "d3-geo": {
+ "version": "1.11.9",
+ "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.9.tgz",
+ "integrity": "sha512-9edcH6J3s/Aa3KJITWqFJbyB/8q3mMlA9Fi7z6yy+FAYMnRaxmC7jBhUnsINxVWD14GmqX3DK8uk7nV6/Ekt4A==",
+ "requires": {
+ "d3-array": "1.2.4"
+ }
+ },
+ "d3-hierarchy": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz",
+ "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ=="
+ },
+ "d3-interpolate": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz",
+ "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==",
+ "requires": {
+ "d3-color": "1.4.0"
+ }
+ },
+ "d3-path": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
+ "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
+ },
+ "d3-polygon": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz",
+ "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ=="
+ },
+ "d3-quadtree": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz",
+ "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA=="
+ },
+ "d3-random": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz",
+ "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ=="
+ },
+ "d3-scale": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz",
+ "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==",
+ "requires": {
+ "d3-array": "1.2.4",
+ "d3-collection": "1.0.7",
+ "d3-format": "1.4.3",
+ "d3-interpolate": "1.4.0",
+ "d3-time": "1.1.0",
+ "d3-time-format": "2.2.3"
+ }
+ },
+ "d3-scale-chromatic": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz",
+ "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==",
+ "requires": {
+ "d3-color": "1.4.0",
+ "d3-interpolate": "1.4.0"
+ }
+ },
+ "d3-selection": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.1.tgz",
+ "integrity": "sha512-BTIbRjv/m5rcVTfBs4AMBLKs4x8XaaLkwm28KWu9S2vKNqXkXt2AH2Qf0sdPZHjFxcWg/YL53zcqAz+3g4/7PA=="
+ },
+ "d3-shape": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
+ "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
+ "requires": {
+ "d3-path": "1.0.9"
+ }
+ },
+ "d3-time": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
+ "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="
+ },
+ "d3-time-format": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.2.3.tgz",
+ "integrity": "sha512-RAHNnD8+XvC4Zc4d2A56Uw0yJoM7bsvOlJR33bclxq399Rak/b9bhvu/InjxdWhPtkgU53JJcleJTGkNRnN6IA==",
+ "requires": {
+ "d3-time": "1.1.0"
+ }
+ },
+ "d3-timer": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz",
+ "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw=="
+ },
+ "d3-transition": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz",
+ "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==",
+ "requires": {
+ "d3-color": "1.4.0",
+ "d3-dispatch": "1.0.6",
+ "d3-ease": "1.0.6",
+ "d3-interpolate": "1.4.0",
+ "d3-selection": "1.4.1",
+ "d3-timer": "1.0.10"
+ }
+ },
+ "d3-voronoi": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz",
+ "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg=="
+ },
+ "d3-zoom": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz",
+ "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==",
+ "requires": {
+ "d3-dispatch": "1.0.6",
+ "d3-drag": "1.2.5",
+ "d3-interpolate": "1.4.0",
+ "d3-selection": "1.4.1",
+ "d3-transition": "1.3.2"
+ }
+ },
+ "dagre": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz",
+ "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==",
+ "requires": {
+ "graphlib": "2.1.8",
+ "lodash": "4.17.15"
+ }
+ },
+ "dagre-d3": {
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/dagre-d3/-/dagre-d3-0.6.4.tgz",
+ "integrity": "sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ==",
+ "requires": {
+ "d3": "5.15.0",
+ "dagre": "0.8.5",
+ "graphlib": "2.1.8",
+ "lodash": "4.17.15"
+ }
+ },
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
+ },
+ "delaunator": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz",
+ "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag=="
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "escaper": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/escaper/-/escaper-2.5.3.tgz",
+ "integrity": "sha512-QGb9sFxBVpbzMggrKTX0ry1oiI4CSDAl9vIL702hzl1jGW8VZs7qfqTRX7WDOjoNDoEVGcEtu1ZOQgReSfT2kQ=="
+ },
+ "fast-deep-equal": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
+ "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
+ },
+ "fast-json-patch": {
+ "version": "3.0.0-1",
+ "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.0.0-1.tgz",
+ "integrity": "sha512-6pdFb07cknxvPzCeLsFHStEy+MysPJPgZQ9LbQ/2O67unQF93SNqfdSqnPPl71YMHX+AD8gbl7iuoGFzHEdDuw=="
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+ },
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "requires": {
+ "locate-path": "5.0.0",
+ "path-exists": "4.0.0"
+ }
+ },
+ "get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
+ },
+ "graphlib": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz",
+ "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==",
+ "requires": {
+ "lodash": "4.17.15"
+ }
+ },
+ "he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
+ },
+ "html-minifier": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz",
+ "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==",
+ "requires": {
+ "camel-case": "3.0.0",
+ "clean-css": "4.2.3",
+ "commander": "2.20.3",
+ "he": "1.2.0",
+ "param-case": "2.1.1",
+ "relateurl": "0.2.7",
+ "uglify-js": "3.7.7"
+ }
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "requires": {
+ "safer-buffer": "2.1.2"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+ },
+ "is-regexp": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
+ "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk="
+ },
+ "json-stringify-pretty-compact": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-2.0.0.tgz",
+ "integrity": "sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ=="
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "requires": {
+ "p-locate": "4.1.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
+ },
+ "lower-case": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
+ "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw="
+ },
+ "marked": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.0.tgz",
+ "integrity": "sha512-MyUe+T/Pw4TZufHkzAfDj6HarCBWia2y27/bhuYkTaiUnfDYFnCP3KUN+9oM7Wi6JA2rymtVYbQu3spE0GCmxQ=="
+ },
+ "mermaid": {
+ "version": "8.4.6",
+ "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.4.6.tgz",
+ "integrity": "sha512-6YQBkXfvhfjKIzRhtqbCics3pJurGrJAYEeqgyRcDZeTHQ/WCB2Bh/4wdAOho1Uffe0jXB+HjmHT5kEUOxudJw==",
+ "requires": {
+ "@braintree/sanitize-url": "3.1.0",
+ "crypto-random-string": "3.1.0",
+ "d3": "5.15.0",
+ "dagre": "0.8.5",
+ "dagre-d3": "0.6.4",
+ "graphlib": "2.1.8",
+ "he": "1.2.0",
+ "lodash": "4.17.15",
+ "minify": "4.1.3",
+ "moment-mini": "2.22.1",
+ "scope-css": "1.2.1"
+ }
+ },
+ "minify": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/minify/-/minify-4.1.3.tgz",
+ "integrity": "sha512-ykuscavxivSmVpcCzsXmsVTukWYLUUtPhHj0w2ILvHDGqC+hsuTCihBn9+PJBd58JNvWTNg9132J9nrrI2anzA==",
+ "requires": {
+ "clean-css": "4.2.3",
+ "css-b64-images": "0.2.5",
+ "debug": "4.1.1",
+ "html-minifier": "4.0.0",
+ "terser": "4.6.3",
+ "try-catch": "2.0.1",
+ "try-to-catch": "1.1.1"
+ }
+ },
+ "moment-mini": {
+ "version": "2.22.1",
+ "resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.22.1.tgz",
+ "integrity": "sha512-OUCkHOz7ehtNMYuZjNciXUfwTuz8vmF1MTbAy59ebf+ZBYZO5/tZKuChVWCX+uDo+4idJBpGltNfV8st+HwsGw=="
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "no-case": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
+ "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
+ "requires": {
+ "lower-case": "1.1.4"
+ }
+ },
+ "node-fetch": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
+ "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
+ },
+ "p-limit": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
+ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==",
+ "requires": {
+ "p-try": "2.2.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "requires": {
+ "p-limit": "2.2.2"
+ }
+ },
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
+ },
+ "param-case": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
+ "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=",
+ "requires": {
+ "no-case": "2.3.2"
+ }
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
+ },
+ "relateurl": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
+ "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk="
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
+ },
+ "require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
+ },
+ "rw": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+ "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q="
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "scope-css": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/scope-css/-/scope-css-1.2.1.tgz",
+ "integrity": "sha512-UjLRmyEYaDNiOS673xlVkZFlVCtckJR/dKgr434VMm7Lb+AOOqXKdAcY7PpGlJYErjXXJzKN7HWo4uRPiZZG0Q==",
+ "requires": {
+ "escaper": "2.5.3",
+ "slugify": "1.3.6",
+ "strip-css-comments": "3.0.0"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
+ },
+ "simplemde": {
+ "version": "1.11.2",
+ "resolved": "https://registry.npmjs.org/simplemde/-/simplemde-1.11.2.tgz",
+ "integrity": "sha1-ojo12XjSxA7wfewAjJLwcNjggOM=",
+ "requires": {
+ "codemirror": "5.51.0",
+ "codemirror-spell-checker": "1.1.2",
+ "marked": "0.8.0"
+ }
+ },
+ "slugify": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.3.6.tgz",
+ "integrity": "sha512-wA9XS475ZmGNlEnYYLPReSfuz/c3VQsEMoU43mi6OnKMCdbnFXd4/Yg7J0lBv8jkPolacMpOrWEaoYxuE1+hoQ=="
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ },
+ "source-map-support": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
+ "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
+ "requires": {
+ "buffer-from": "1.1.1",
+ "source-map": "0.6.1"
+ }
+ },
+ "string-width": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+ "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+ "requires": {
+ "emoji-regex": "8.0.0",
+ "is-fullwidth-code-point": "3.0.0",
+ "strip-ansi": "6.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "requires": {
+ "ansi-regex": "5.0.0"
+ }
+ },
+ "strip-css-comments": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-css-comments/-/strip-css-comments-3.0.0.tgz",
+ "integrity": "sha1-elYl7/iisibPiUehElTaluE9rok=",
+ "requires": {
+ "is-regexp": "1.0.0"
+ }
+ },
+ "tablesort": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/tablesort/-/tablesort-5.2.0.tgz",
+ "integrity": "sha512-uiG1z/yvuZPod3uesK78bNMtLisnbQzNeD+CDQSZsrZTK2AzMjCmrTa1NUPBVLwD43VxJnIUYlrTM1jYl+HZFQ=="
+ },
+ "terser": {
+ "version": "4.6.3",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.3.tgz",
+ "integrity": "sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ==",
+ "requires": {
+ "commander": "2.20.3",
+ "source-map": "0.6.1",
+ "source-map-support": "0.5.16"
+ }
+ },
+ "topojson-client": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz",
+ "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==",
+ "requires": {
+ "commander": "2.20.3"
+ }
+ },
+ "try-catch": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/try-catch/-/try-catch-2.0.1.tgz",
+ "integrity": "sha512-LsOrmObN/2WdM+y2xG+t16vhYrQsnV8wftXIcIOWZhQcBJvKGYuamJGwnU98A7Jxs2oZNkJztXlphEOoA0DWqg=="
+ },
+ "try-to-catch": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-1.1.1.tgz",
+ "integrity": "sha512-ikUlS+/BcImLhNYyIgZcEmq4byc31QpC+46/6Jm5ECWkVFhf8SM2Fp/0pMVXPX6vk45SMCwrP4Taxucne8I0VA=="
+ },
+ "tslib": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
+ "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
+ },
+ "type-fest": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="
+ },
+ "typo-js": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.1.0.tgz",
+ "integrity": "sha512-W3kLbx+ML9PBl5Bzso/lTvVxk4BCveSNAtQeht59FEtxCdGThmn6wSHA4Xq3eQYAK24NHdisMM4JmsK0GFy/pg=="
+ },
+ "uglify-js": {
+ "version": "3.7.7",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.7.tgz",
+ "integrity": "sha512-FeSU+hi7ULYy6mn8PKio/tXsdSXN35lm4KgV2asx00kzrLU9Pi3oAslcJT70Jdj7PHX29gGUPOT6+lXGBbemhA==",
+ "requires": {
+ "commander": "2.20.3",
+ "source-map": "0.6.1"
+ }
+ },
+ "upper-case": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
+ "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg="
+ },
+ "vega": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/vega/-/vega-5.8.0.tgz",
+ "integrity": "sha512-9bxZ62Ra+M4ZaEnVqw9+DhN2ttGjKTLpKVjvfhuXqXAXmCqBPXfcez1/9ZNFoYHc0s2Xf6+uYzHj7aLg9G0zMw==",
+ "requires": {
+ "vega-crossfilter": "4.0.1",
+ "vega-dataflow": "5.4.1",
+ "vega-encode": "4.5.0",
+ "vega-event-selector": "2.0.1",
+ "vega-expression": "2.6.2",
+ "vega-force": "4.0.3",
+ "vega-functions": "5.5.0",
+ "vega-geo": "4.2.0",
+ "vega-hierarchy": "4.0.3",
+ "vega-loader": "4.1.3",
+ "vega-parser": "5.11.0",
+ "vega-projection": "1.3.0",
+ "vega-regression": "1.0.2",
+ "vega-runtime": "5.0.2",
+ "vega-scale": "5.0.0",
+ "vega-scenegraph": "4.4.0",
+ "vega-statistics": "1.7.0",
+ "vega-time": "1.0.0",
+ "vega-transforms": "4.5.0",
+ "vega-typings": "0.11.0",
+ "vega-util": "1.12.0",
+ "vega-view": "5.3.2",
+ "vega-view-transforms": "4.5.0",
+ "vega-voronoi": "4.1.1",
+ "vega-wordcloud": "4.0.3"
+ },
+ "dependencies": {
+ "vega-event-selector": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/vega-event-selector/-/vega-event-selector-2.0.1.tgz",
+ "integrity": "sha512-FGU1PefYhW9An6zVs6TE5f/XGYsIispxFErG/p9KThxL22IC90WVZzMQXKN9M8OcARq5OyWjHg3qa9Qp/Z6OJw=="
+ },
+ "vega-expression": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-2.6.2.tgz",
+ "integrity": "sha512-vh8GVkAL/KtsgcdrdKdEnysZn/InIuRrkF7U+CG1eAmupMucPY/Rpu0nCdYb4CLC/xNRHx/NMFidLztQUjZJQg==",
+ "requires": {
+ "vega-util": "1.12.0"
+ }
+ },
+ "vega-typings": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/vega-typings/-/vega-typings-0.11.0.tgz",
+ "integrity": "sha512-i67WNmzm1ymswDT8t54dOQusDUO+95Lzxmum0YkNrBIXso1hwFWUT1les0swNlKzSJPjhTofN2OrPXa6C+uf6w==",
+ "requires": {
+ "vega-util": "1.12.0"
+ }
+ },
+ "vega-util": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.12.0.tgz",
+ "integrity": "sha512-eN1PAQVDyEOcwild2Fk1gbkzkqgDHNujG2/akYRtBzkhtz2EttrVIDwBkWqV/Q+VvEINEksb7TI3Wv7qVQFR5g=="
+ }
+ }
+ },
+ "vega-canvas": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/vega-canvas/-/vega-canvas-1.2.1.tgz",
+ "integrity": "sha512-k/S3EPeJ37D7fYDhv4sEg7fNWVpLheQY7flfLyAmJU7aSwCMgw8cZJi0CKHchJeculssfH+41NCqvRB1QtaJnw=="
+ },
+ "vega-crossfilter": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/vega-crossfilter/-/vega-crossfilter-4.0.1.tgz",
+ "integrity": "sha512-wLNS4JzKaOLj8EAzI/v8XBJjUWMRWYSu6EeQF4o9Opq/78u87Ol9Lc5I27UHsww5dNNH/tHubAV4QPIXnGOp5Q==",
+ "requires": {
+ "d3-array": "2.4.0",
+ "vega-dataflow": "5.4.1",
+ "vega-util": "1.12.2"
+ },
+ "dependencies": {
+ "d3-array": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.4.0.tgz",
+ "integrity": "sha512-KQ41bAF2BMakf/HdKT865ALd4cgND6VcIztVQZUTt0+BH3RWy6ZYnHghVXf6NFjt2ritLr8H1T8LreAAlfiNcw=="
+ }
+ }
+ },
+ "vega-dataflow": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/vega-dataflow/-/vega-dataflow-5.4.1.tgz",
+ "integrity": "sha512-NZASrIGel2ZD+HiJsozMPO7qNB3INLFWQez6KI+gPpKQIhsz7jWzG/TBK57A8NOLJYPc6VBLiygCmdJbr5E+sA==",
+ "requires": {
+ "vega-loader": "4.1.3",
+ "vega-util": "1.12.2"
+ }
+ },
+ "vega-embed": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/vega-embed/-/vega-embed-6.2.2.tgz",
+ "integrity": "sha512-u9CX9jpIbu2DY2lqs0J7CLA7+VXNlR+qkzxOwaVKfS0ivB54dxQXKvE59VHV02Nwb5XhaDGTEMN0FRCAvgkdLQ==",
+ "requires": {
+ "fast-json-patch": "3.0.0-1",
+ "json-stringify-pretty-compact": "2.0.0",
+ "semver": "6.3.0",
+ "vega-schema-url-parser": "1.1.0",
+ "vega-themes": "2.6.1",
+ "vega-tooltip": "0.20.1"
+ }
+ },
+ "vega-encode": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/vega-encode/-/vega-encode-4.5.0.tgz",
+ "integrity": "sha512-oFwsYOvqwVokyY7sVCSvJr2aqJQpKQ5rbpjia/9lRMgluZUFkfLidZ1foQ++/UHISjvC/mWVNAGq3hsTV06xCQ==",
+ "requires": {
+ "d3-array": "2.4.0",
+ "d3-format": "1.4.3",
+ "d3-interpolate": "1.4.0",
+ "vega-dataflow": "5.4.1",
+ "vega-scale": "5.0.0",
+ "vega-time": "1.0.0",
+ "vega-util": "1.12.2"
+ },
+ "dependencies": {
+ "d3-array": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.4.0.tgz",
+ "integrity": "sha512-KQ41bAF2BMakf/HdKT865ALd4cgND6VcIztVQZUTt0+BH3RWy6ZYnHghVXf6NFjt2ritLr8H1T8LreAAlfiNcw=="
+ }
+ }
+ },
+ "vega-event-selector": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/vega-event-selector/-/vega-event-selector-2.0.2.tgz",
+ "integrity": "sha512-Uv72vBfM0lrlI2belKHFMZuVnW2uJl2ShqWPwGSXPVe6p+PzgqoPJYC8A/i5N8B54UA4UMDzlbBeo3x7q2W9Yg=="
+ },
+ "vega-expression": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-2.6.3.tgz",
+ "integrity": "sha512-sME1+45BToTGsftb1Q6Ubs2iRYEoXkD2NRGnJuKS9YJ2ITzZwPHF/jy2kHW3iLpuNjj54meaO7HMQ/hUKrciUw==",
+ "requires": {
+ "vega-util": "1.12.2"
+ }
+ },
+ "vega-force": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/vega-force/-/vega-force-4.0.3.tgz",
+ "integrity": "sha512-4stItN4jD9H1CENaCz4jXRNS1Bi9cozMOUjX2824FeJENi2RZSiAZAaGbscgerZQ/jbNcOHD8PHpC2pWldEvGA==",
+ "requires": {
+ "d3-force": "2.0.1",
+ "vega-dataflow": "5.4.1",
+ "vega-util": "1.12.2"
+ },
+ "dependencies": {
+ "d3-force": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-2.0.1.tgz",
+ "integrity": "sha512-zh73/N6+MElRojiUG7vmn+3vltaKon7iD5vB/7r9nUaBeftXMzRo5IWEG63DLBCto4/8vr9i3m9lwr1OTJNiCg==",
+ "requires": {
+ "d3-dispatch": "1.0.6",
+ "d3-quadtree": "1.0.7",
+ "d3-timer": "1.0.10"
+ }
+ }
+ }
+ },
+ "vega-functions": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/vega-functions/-/vega-functions-5.5.0.tgz",
+ "integrity": "sha512-kWNrwWr8Gphm3uZn58OaNT0eUdkZvOUhVUK1ec0NjfZofeVI2V6DzFrQNRvTlvUz269l7G88v1cdsQeXOJcZmg==",
+ "requires": {
+ "d3-array": "2.4.0",
+ "d3-color": "1.4.0",
+ "d3-format": "1.4.3",
+ "d3-geo": "1.11.9",
+ "d3-time-format": "2.2.3",
+ "vega-dataflow": "5.4.1",
+ "vega-expression": "2.6.3",
+ "vega-scale": "5.0.0",
+ "vega-scenegraph": "4.4.0",
+ "vega-selections": "5.1.0",
+ "vega-statistics": "1.7.0",
+ "vega-time": "1.0.0",
+ "vega-util": "1.12.2"
+ },
+ "dependencies": {
+ "d3-array": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.4.0.tgz",
+ "integrity": "sha512-KQ41bAF2BMakf/HdKT865ALd4cgND6VcIztVQZUTt0+BH3RWy6ZYnHghVXf6NFjt2ritLr8H1T8LreAAlfiNcw=="
+ }
+ }
+ },
+ "vega-geo": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/vega-geo/-/vega-geo-4.2.0.tgz",
+ "integrity": "sha512-umhG1645fB1iRsKAP/ZsS0b+gcW/i8DzRUihWJ83VHnlUS+Py/oocSOtx4QqfRW8xVGy/cGzflj52NhLsVW+HQ==",
+ "requires": {
+ "d3-array": "2.4.0",
+ "d3-color": "1.4.0",
+ "d3-geo": "1.11.9",
+ "vega-canvas": "1.2.1",
+ "vega-dataflow": "5.4.1",
+ "vega-projection": "1.3.0",
+ "vega-statistics": "1.7.0",
+ "vega-util": "1.12.2"
+ },
+ "dependencies": {
+ "d3-array": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.4.0.tgz",
+ "integrity": "sha512-KQ41bAF2BMakf/HdKT865ALd4cgND6VcIztVQZUTt0+BH3RWy6ZYnHghVXf6NFjt2ritLr8H1T8LreAAlfiNcw=="
+ }
+ }
+ },
+ "vega-hierarchy": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/vega-hierarchy/-/vega-hierarchy-4.0.3.tgz",
+ "integrity": "sha512-9wNe+KyKqZW1S4++jCC38HuAhZbqNhfY7gOvwiMLjsp65tMtRETrtvYfHkULClm3UokUIX54etAXREAGW7znbw==",
+ "requires": {
+ "d3-hierarchy": "1.1.9",
+ "vega-dataflow": "5.4.1",
+ "vega-util": "1.12.2"
+ }
+ },
+ "vega-lite": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/vega-lite/-/vega-lite-4.1.1.tgz",
+ "integrity": "sha512-D2seO6ZbY8aZQ8+ZQfU+5NYwot3ryIDyvdQdcVoupMSgJ/oGv4QqEwL3rmu8abdSG6NhFiac0trsI+wBb0F6vQ==",
+ "requires": {
+ "@types/clone": "0.1.30",
+ "@types/fast-json-stable-stringify": "2.0.0",
+ "array-flat-polyfill": "1.0.1",
+ "clone": "2.1.2",
+ "fast-deep-equal": "3.1.1",
+ "fast-json-stable-stringify": "2.1.0",
+ "json-stringify-pretty-compact": "2.0.0",
+ "tslib": "1.10.0",
+ "vega-event-selector": "2.0.2",
+ "vega-expression": "2.6.3",
+ "vega-typings": "0.12.3",
+ "vega-util": "1.12.2",
+ "yargs": "15.1.0"
+ }
+ },
+ "vega-loader": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/vega-loader/-/vega-loader-4.1.3.tgz",
+ "integrity": "sha512-50aetjuct4WqU7LctwnZqF/NCyya9aZ1HDQZ9unFi++62vOQgRfbXLNL/dZavqwnWX3S9i0ltCznLyFMG4ck8g==",
+ "requires": {
+ "d3-dsv": "1.2.0",
+ "d3-time-format": "2.2.3",
+ "node-fetch": "2.6.0",
+ "topojson-client": "3.1.0",
+ "vega-util": "1.12.2"
+ }
+ },
+ "vega-parser": {
+ "version": "5.11.0",
+ "resolved": "https://registry.npmjs.org/vega-parser/-/vega-parser-5.11.0.tgz",
+ "integrity": "sha512-5JJTHtA7DkH8aMQQCiveAaFp3qwSApGfj3BKV1dxc/i3/bUB0xqaEeXju2fcBIo9UlPrpX+dj+DAz3Qb4kMfog==",
+ "requires": {
+ "vega-dataflow": "5.4.1",
+ "vega-event-selector": "2.0.2",
+ "vega-expression": "2.6.3",
+ "vega-functions": "5.5.0",
+ "vega-scale": "5.0.0",
+ "vega-util": "1.12.2"
+ }
+ },
+ "vega-projection": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/vega-projection/-/vega-projection-1.3.0.tgz",
+ "integrity": "sha512-BFOc/XSVVW96WIAAyiUcppCeegniibiKGX0OLbGpQ5WIbeDHsbCXqnkeBpD5wsjvPXaiQRHTZ0PZ8VvCoCQV+g==",
+ "requires": {
+ "d3-geo": "1.11.9"
+ }
+ },
+ "vega-regression": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/vega-regression/-/vega-regression-1.0.2.tgz",
+ "integrity": "sha512-wWGurNFzkF0kxeM4bT3bZXplJYzyvuPDS2jWPx7r08l5VHcf7qc4xBetpCnNtrdG2Pg3hXGZrUWqA3/ufrfUdA==",
+ "requires": {
+ "d3-array": "2.4.0",
+ "vega-dataflow": "5.4.1",
+ "vega-statistics": "1.7.0",
+ "vega-util": "1.12.2"
+ },
+ "dependencies": {
+ "d3-array": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.4.0.tgz",
+ "integrity": "sha512-KQ41bAF2BMakf/HdKT865ALd4cgND6VcIztVQZUTt0+BH3RWy6ZYnHghVXf6NFjt2ritLr8H1T8LreAAlfiNcw=="
+ }
+ }
+ },
+ "vega-runtime": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/vega-runtime/-/vega-runtime-5.0.2.tgz",
+ "integrity": "sha512-Cuv+RY6kprH+vtNERg6xP4dgcdYGD2ZnxPxJNEtGi7dmtQQTBa1s7jQ0VDXTolsO6lKJ3B7np2GzKJYwevgj1A==",
+ "requires": {
+ "vega-dataflow": "5.4.1",
+ "vega-util": "1.12.2"
+ }
+ },
+ "vega-scale": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/vega-scale/-/vega-scale-5.0.0.tgz",
+ "integrity": "sha512-HlCDdWPK1U769JTIkrlad3W/AVrZP/9dmeBBqG13djgv5l2RLUdUgDQ8EIrkPt7lOMMNlKKp4tJn0U8UlYfpfg==",
+ "requires": {
+ "d3-array": "2.4.0",
+ "d3-interpolate": "1.4.0",
+ "d3-scale": "3.2.1",
+ "vega-util": "1.12.2"
+ },
+ "dependencies": {
+ "d3-array": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.4.0.tgz",
+ "integrity": "sha512-KQ41bAF2BMakf/HdKT865ALd4cgND6VcIztVQZUTt0+BH3RWy6ZYnHghVXf6NFjt2ritLr8H1T8LreAAlfiNcw=="
+ },
+ "d3-scale": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.2.1.tgz",
+ "integrity": "sha512-huz5byJO/6MPpz6Q8d4lg7GgSpTjIZW/l+1MQkzKfu2u8P6hjaXaStOpmyrD6ymKoW87d2QVFCKvSjLwjzx/rA==",
+ "requires": {
+ "d3-array": "2.4.0",
+ "d3-format": "1.4.3",
+ "d3-interpolate": "1.4.0",
+ "d3-time": "1.1.0",
+ "d3-time-format": "2.2.3"
+ }
+ }
+ }
+ },
+ "vega-scenegraph": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/vega-scenegraph/-/vega-scenegraph-4.4.0.tgz",
+ "integrity": "sha512-w81YLlwEfHv1i0LIj2bT+eL7jhvl9baFkymCTDW3ZB9LTOulK6/CmyB52dIswY6ploFVcnYi8dYQfpGSdWZ9wA==",
+ "requires": {
+ "d3-path": "1.0.9",
+ "d3-shape": "1.3.7",
+ "vega-canvas": "1.2.1",
+ "vega-loader": "4.1.3",
+ "vega-util": "1.12.2"
+ }
+ },
+ "vega-schema-url-parser": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/vega-schema-url-parser/-/vega-schema-url-parser-1.1.0.tgz",
+ "integrity": "sha512-Tc85J2ofMZZOsxiqDM9sbvfsa+Vdo3GwNLjEEsPOsCDeYqsUHKAlc1IpbbhPLZ6jusyM9Lk0e1izF64GGklFDg=="
+ },
+ "vega-selections": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/vega-selections/-/vega-selections-5.1.0.tgz",
+ "integrity": "sha512-Gm+16RaCMkWbimqKh9kuIGMK91vutJsTbIDKBXxmq0c3pTvf+Djy6KfBoFsipEJ9wkwhXHSqpLqS1tExV93E9g==",
+ "requires": {
+ "vega-expression": "2.6.3",
+ "vega-util": "1.12.2"
+ }
+ },
+ "vega-statistics": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/vega-statistics/-/vega-statistics-1.7.0.tgz",
+ "integrity": "sha512-rsgVXnd8247KYYe5lUBS3mLD0C8hCgKoFOlD4Iolv1eKedZq0FjC+xan9ejQPVV7BlLsuuoYUNuaUOkQXHudcQ==",
+ "requires": {
+ "d3-array": "2.4.0"
+ },
+ "dependencies": {
+ "d3-array": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.4.0.tgz",
+ "integrity": "sha512-KQ41bAF2BMakf/HdKT865ALd4cgND6VcIztVQZUTt0+BH3RWy6ZYnHghVXf6NFjt2ritLr8H1T8LreAAlfiNcw=="
+ }
+ }
+ },
+ "vega-themes": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/vega-themes/-/vega-themes-2.6.1.tgz",
+ "integrity": "sha512-KsV48l1eBEiEr6OOTTWUQCwnPTSPfNU/5tb1iyrLtAe2B0V3Xk7YCKgsjtlIsXhs7AAOHtM/2HBsJjvQ3HeDtQ=="
+ },
+ "vega-time": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-1.0.0.tgz",
+ "integrity": "sha512-r0yOFr/VklJwD3ew1+fEcB7E0LBCLChYlwh0KoO6cTIWMdlC4KhIIUN3/FuBfUZ4qx4V/xp71xH2YYYZTH6izg==",
+ "requires": {
+ "d3-array": "2.4.0",
+ "d3-time": "1.1.0",
+ "d3-time-format": "2.2.3",
+ "vega-util": "1.12.2"
+ },
+ "dependencies": {
+ "d3-array": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.4.0.tgz",
+ "integrity": "sha512-KQ41bAF2BMakf/HdKT865ALd4cgND6VcIztVQZUTt0+BH3RWy6ZYnHghVXf6NFjt2ritLr8H1T8LreAAlfiNcw=="
+ }
+ }
+ },
+ "vega-tooltip": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/vega-tooltip/-/vega-tooltip-0.20.1.tgz",
+ "integrity": "sha512-kk1p2VRDAZRdoi9C6UdItOO8GCFbtVfUNT1g3XPpHCYuQ4Lrjffa0SNcT/i69luC3n6qd9VyrceFoPBGM4YvTw==",
+ "requires": {
+ "vega-util": "1.12.2"
+ }
+ },
+ "vega-transforms": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/vega-transforms/-/vega-transforms-4.5.0.tgz",
+ "integrity": "sha512-ajgXTXYLMFZAduQL3rFsBtZcBsCHsGbYy5xJDyFATpNO31e4LkAey4b0D20dPoWwSLaSd+nps8m7HOAqm13rqg==",
+ "requires": {
+ "d3-array": "2.4.0",
+ "vega-dataflow": "5.4.1",
+ "vega-statistics": "1.7.0",
+ "vega-time": "1.0.0",
+ "vega-util": "1.12.2"
+ },
+ "dependencies": {
+ "d3-array": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.4.0.tgz",
+ "integrity": "sha512-KQ41bAF2BMakf/HdKT865ALd4cgND6VcIztVQZUTt0+BH3RWy6ZYnHghVXf6NFjt2ritLr8H1T8LreAAlfiNcw=="
+ }
+ }
+ },
+ "vega-typings": {
+ "version": "0.12.3",
+ "resolved": "https://registry.npmjs.org/vega-typings/-/vega-typings-0.12.3.tgz",
+ "integrity": "sha512-ubOekVm60PjzP0b+FAakbti9PkoGu/as2SPX7iBo5IPp8pt+Ej5SNpQJPdCl9rSpKCzU4QNWz1ptZcXL3WSTcg==",
+ "requires": {
+ "vega-util": "1.12.2"
+ }
+ },
+ "vega-util": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.12.2.tgz",
+ "integrity": "sha512-p02+oQ/XU/gzY9S/CTZinym2NKWEMIneLc+FYdUeJZZnDGa3DvcNgUDlVR90JlwLcYZNs5dBdfYLfdRHsKZKiw=="
+ },
+ "vega-view": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/vega-view/-/vega-view-5.3.2.tgz",
+ "integrity": "sha512-W1+iQf2VB6a4U55gEI9eaWHq95jdClQS0vAksSAMrS5FjnZs0oE8L178Qrv1NxDvLjSJFlpAE8r0MlckD8+eug==",
+ "requires": {
+ "d3-array": "2.4.0",
+ "d3-timer": "1.0.10",
+ "vega-dataflow": "5.4.1",
+ "vega-functions": "5.5.0",
+ "vega-runtime": "5.0.2",
+ "vega-scenegraph": "4.4.0",
+ "vega-util": "1.12.2"
+ },
+ "dependencies": {
+ "d3-array": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.4.0.tgz",
+ "integrity": "sha512-KQ41bAF2BMakf/HdKT865ALd4cgND6VcIztVQZUTt0+BH3RWy6ZYnHghVXf6NFjt2ritLr8H1T8LreAAlfiNcw=="
+ }
+ }
+ },
+ "vega-view-transforms": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/vega-view-transforms/-/vega-view-transforms-4.5.0.tgz",
+ "integrity": "sha512-8n52147HxNSjQ23NeHN//AWt99zZP+Ukiy4kSbkCJGPZ3dW3NYdunEYNvZWyMmOKSrHIMtgdcHUM9FmPTQpE9w==",
+ "requires": {
+ "vega-dataflow": "5.4.1",
+ "vega-scenegraph": "4.4.0",
+ "vega-util": "1.12.2"
+ }
+ },
+ "vega-voronoi": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/vega-voronoi/-/vega-voronoi-4.1.1.tgz",
+ "integrity": "sha512-agLmr+UGxJs5KB9D8GeZqxgeWWGoER/eVHPcFFPgVuoNBsrqf2bdoltmIkRnpiRsQnGCibGixhFEDCc9GGNAww==",
+ "requires": {
+ "d3-delaunay": "5.2.1",
+ "vega-dataflow": "5.4.1",
+ "vega-util": "1.12.2"
+ }
+ },
+ "vega-wordcloud": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/vega-wordcloud/-/vega-wordcloud-4.0.3.tgz",
+ "integrity": "sha512-1fVy3r+ty3ddZEidsfby5jDnLi3hNezEM86cS4xc1yxsvE1a1A379GKQKg89vTPWEunmZv15ya+2tQ4fFsU8lg==",
+ "requires": {
+ "vega-canvas": "1.2.1",
+ "vega-dataflow": "5.4.1",
+ "vega-scale": "5.0.0",
+ "vega-statistics": "1.7.0",
+ "vega-util": "1.12.2"
+ }
+ },
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
+ },
+ "wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "requires": {
+ "ansi-styles": "4.2.1",
+ "string-width": "4.2.0",
+ "strip-ansi": "6.0.0"
+ }
+ },
+ "y18n": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
+ },
+ "yargs": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.1.0.tgz",
+ "integrity": "sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg==",
+ "requires": {
+ "cliui": "6.0.0",
+ "decamelize": "1.2.0",
+ "find-up": "4.1.0",
+ "get-caller-file": "2.0.5",
+ "require-directory": "2.1.1",
+ "require-main-filename": "2.0.0",
+ "set-blocking": "2.0.0",
+ "string-width": "4.2.0",
+ "which-module": "2.0.0",
+ "y18n": "4.0.0",
+ "yargs-parser": "16.1.0"
+ }
+ },
+ "yargs-parser": {
+ "version": "16.1.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-16.1.0.tgz",
+ "integrity": "sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg==",
+ "requires": {
+ "camelcase": "5.3.1",
+ "decamelize": "1.2.0"
+ }
+ }
+ }
+}
diff --git a/project.clj b/project.clj
index d9b5cda..b270587 100644
--- a/project.clj
+++ b/project.clj
@@ -40,18 +40,20 @@
:jvm-opts ["-server"]
:plugins [[lein-ancient "0.5.5" :exclusions [org.clojure/clojure org.clojure/data.xml]]
- [lein-bower "0.5.1"]
[lein-codox "0.10.3"]
[io.sarnowski/lein-docker "1.0.0"]
[lein-environ "1.0.0"]
[lein-marginalia "0.7.1" :exclusions [org.clojure/clojure]]
+ [lein-npm "0.6.2"]
[lein-ring "0.12.5" :exclusions [org.clojure/clojure]]]
- :bower-dependencies [[simplemde "1.11.2"]
- ;; [vega-embed "3.0.0-beta.20"] ;; vega-embed currently not loaded from Bower because of
- ;; dependency conflict which will hopefully be resolved soon.
- [vega-lite "2.0.0-beta.11"]
- [mermaid "6.0.0"]]
+ :npm {:dependencies [[simplemde "1.11.2"]
+ [vega "5.8.0"]
+ [vega-embed "6.2.2"]
+ [vega-lite "4.1.1"]
+ [mermaid "8.4.6"]
+ [tablesort "5.2.0"]]
+ :root "resources/public/vendor"}
:docker {:image-name "simonbrooke/smeagol"
:dockerfile "Dockerfile"}
diff --git a/resources/config.edn b/resources/config.edn
index d5f7017..5ceccd7 100644
--- a/resources/config.edn
+++ b/resources/config.edn
@@ -37,7 +37,9 @@
"backticks" smeagol.formatting/process-backticks}
:log-level :info ;; the minimum logging level; one of
;; :trace :debug :info :warn :error :fatal
+ :js-from :cloudflare ;; where to load JavaScript libraries
+ ;; from: options are :local, :cloudflare
:passwd "resources/passwd"
;; where the password file is stored
- :site-title "Smeagol"} ;; overall title of the site, used in
+ :site-title "Smeagol"} ;; overall title of the site, used in
;; page headings
diff --git a/resources/templates/edit.html b/resources/templates/edit.html
index 3379dc5..36426dc 100644
--- a/resources/templates/edit.html
+++ b/resources/templates/edit.html
@@ -1,7 +1,12 @@
{% extends "templates/base.html" %}
{% block extra-headers %}
-{% style "/vendor/simplemde/dist/simplemde.min.css" %}
-{% script "/vendor/simplemde/dist/simplemde.min.js" %}
+ {% ifequal js-from ":cloudflare" %}
+
+
+ {% else %}
+ {% style "/vendor/simplemde/dist/simplemde.min.css" %}
+ {% script "/vendor/simplemde/dist/simplemde.min.js" %}
+ {% endifequal %}
{% endblock %}
{% block content %}
diff --git a/resources/templates/wiki.html b/resources/templates/wiki.html
index ca87b3d..7dfa3c8 100644
--- a/resources/templates/wiki.html
+++ b/resources/templates/wiki.html
@@ -1,15 +1,16 @@
{% extends "templates/base.html" %}
{% block extra-headers %}
- {% style "vendor/mermaid/dist/mermaid.css" %}
-
-
- {% script "/vendor/vega-lite/build/vega-lite.js" %}
-
-
- {% script "vendor/mermaid/dist/mermaid.js" %}
+
+
+
+
+
+
+
+
{% endblock %}
{% block content %}
diff --git a/src/smeagol/configuration.clj b/src/smeagol/configuration.clj
index 1f9735e..025292a 100644
--- a/src/smeagol/configuration.clj
+++ b/src/smeagol/configuration.clj
@@ -100,6 +100,7 @@
'( {:from :smeagol-content-dir :to :content-dir}
{:from :smeagol-default-locale :to :default-locale}
{:from :smeagol-formatters :to :formatters :transform read-string}
+ {:from :smeagol-js-from :to :js-from :transform to-keyword}
{:from :smeagol-log-level :to :log-level :transform to-keyword}
{:from :smeagol-passwd :to :passwd}
{:from :smeagol-site-title :to :site-title}))
@@ -131,6 +132,7 @@
:smeagol-content-dir
:smeagol-default-locale
:smeagol-formatters
+ :smeagol-js-from
:smeagol-log-level
:smeagol-passwd
:smeagol-site-title)
diff --git a/src/smeagol/formatting.clj b/src/smeagol/formatting.clj
index 94fd134..8fe3697 100644
--- a/src/smeagol/formatting.clj
+++ b/src/smeagol/formatting.clj
@@ -78,7 +78,7 @@
index
" = "
(yaml->json (str "$schema: https://vega.github.io/schema/vega-lite/v2.json\n" vega-src))
- ";\nvega.embed('#vis"
+ ";\nvegaEmbed('#vis"
index
"', vl"
index
diff --git a/src/smeagol/util.clj b/src/smeagol/util.clj
index c290e91..c3ce6d4 100644
--- a/src/smeagol/util.clj
+++ b/src/smeagol/util.clj
@@ -50,6 +50,7 @@
(let [user (session/get :user)]
{:user user
:admin (auth/get-admin user)
+ :js-from (:js-from config)
:side-bar (md->html (slurp (cjio/file content-dir "_side-bar.md")))
:header (md->html (slurp (cjio/file content-dir "_header.md")))
:version (System/getProperty "smeagol.version")}))
From 54b82931b22429f716b37b654e5e21eed7075ec7 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Sun, 9 Feb 2020 01:42:31 +0000
Subject: [PATCH 53/83] Major restructuring of extension processors, not yet
complete
---
resources/config.edn | 2 +-
resources/public/content/Extensible Markup.md | 10 ++-
resources/public/data/classes.mermaid | 14 +++
resources/templates/wiki.html | 2 +-
src/smeagol/extensions/mermaid.clj | 85 +++++++++++++++++++
src/smeagol/extensions/utils.clj | 72 ++++++++++++++++
src/smeagol/extensions/vega.clj | 0
src/smeagol/formatting.clj | 11 +--
8 files changed, 185 insertions(+), 11 deletions(-)
create mode 100644 resources/public/data/classes.mermaid
create mode 100644 src/smeagol/extensions/mermaid.clj
create mode 100644 src/smeagol/extensions/utils.clj
create mode 100644 src/smeagol/extensions/vega.clj
diff --git a/resources/config.edn b/resources/config.edn
index 5ceccd7..c2c9161 100644
--- a/resources/config.edn
+++ b/resources/config.edn
@@ -33,7 +33,7 @@
:default-locale "en-GB" ;; default language used for messages
:formatters {"vega" smeagol.formatting/process-vega
"vis" smeagol.formatting/process-vega
- "mermaid" smeagol.formatting/process-mermaid
+ "mermaid" smeagol.extensions.mermaid/process-mermaid
"backticks" smeagol.formatting/process-backticks}
:log-level :info ;; the minimum logging level; one of
;; :trace :debug :info :warn :error :fatal
diff --git a/resources/public/content/Extensible Markup.md b/resources/public/content/Extensible Markup.md
index 129a375..aec9e82 100644
--- a/resources/public/content/Extensible Markup.md
+++ b/resources/public/content/Extensible Markup.md
@@ -36,7 +36,7 @@ Data files can be uploaded in the same way as images, by using the **upload a fi
Graphs can now be embedded in a page using the [Mermaid](https://mermaid-js.github.io/mermaid/#/) graph description language. The graph description should start with a line comprising three back-ticks and then the word `mermaid`, and end with a line comprising just three backticks.
-Here's an example culled from the Mermaid documentation.
+Here's an example culled from the Mermaid documentation. Edit this page to see the specification.
### GANTT Chart
@@ -58,6 +58,14 @@ gantt
Add to mermaid :1d
```
+Mermaid graph specifications can also be loaded from URLs. Here's another example; again, edit this page to see how the trick is done.
+
+### Class Diagram
+
+```mermaid
+data/classes.mermaid
+```
+
## Writing your own custom formatters
A custom formatter is simply a Clojure function which takes a string and an integer as arguments and produces a string as output. The string is the text the user has typed into their markdown; the integer is simply a number you can use to keep track of which addition to the page this is, in order, for example, to fix up some JavaScript to render it.
diff --git a/resources/public/data/classes.mermaid b/resources/public/data/classes.mermaid
new file mode 100644
index 0000000..e71885a
--- /dev/null
+++ b/resources/public/data/classes.mermaid
@@ -0,0 +1,14 @@
+classDiagram
+Class01 <|-- AveryLongClass : Cool
+Class03 *-- Class04
+Class05 o-- Class06
+Class07 .. Class08
+Class09 --> C2 : Where am i?
+Class09 --* C3
+Class09 --|> Class07
+Class07 : equals()
+Class07 : Object[] elementData
+Class01 : size()
+Class01 : int chimp
+Class01 : int gorilla
+Class08 <--> C2: Cool label
diff --git a/resources/templates/wiki.html b/resources/templates/wiki.html
index 7dfa3c8..ffc9b1a 100644
--- a/resources/templates/wiki.html
+++ b/resources/templates/wiki.html
@@ -9,7 +9,7 @@
-
{% endblock %}
diff --git a/src/smeagol/extensions/mermaid.clj b/src/smeagol/extensions/mermaid.clj
new file mode 100644
index 0000000..9fe271b
--- /dev/null
+++ b/src/smeagol/extensions/mermaid.clj
@@ -0,0 +1,85 @@
+(ns ^{:doc "Format Semagol's extended markdown format."
+ :author "Simon Brooke"}
+ smeagol.extensions.mermaid
+ (:require [smeagol.extensions.utils :refer :all]
+ [taoensso.timbre :as log]))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Smeagol: a very simple Wiki engine.
+;;;;
+;;;; This program is free software; you can redistribute it and/or
+;;;; modify it under the terms of the GNU General Public License
+;;;; as published by the Free Software Foundation; either version 2
+;;;; of the License, or (at your option) any later version.
+;;;;
+;;;; This program is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with this program; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;;; USA.
+;;;;
+;;;; Copyright (C) 2017 Simon Brooke
+;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Graphs can now be embedded in a page using the
+;;;; [Mermaid](https://mermaid-js.github.io/mermaid/#/) graph description
+;;;; language. The graph description should start with a line comprising three
+;;;; back-ticks and then the word `mermaid`, and end with a line comprising just
+;;;; three backticks.
+;;;;
+;;;; Here's an example culled from the Mermaid documentation.
+;;;;
+;;;; ### GANTT Chart
+;;;;
+;;;; ```mermaid
+;;;; gantt
+;;;; dateFormat YYYY-MM-DD
+;;;; title Adding GANTT diagram functionality to mermaid
+;;;; section A section
+;;;; Completed task :done, des1, 2014-01-06,2014-01-08
+;;;; Active task :active, des2, 2014-01-09, 3d
+;;;; Future task : des3, after des2, 5d
+;;;; Future task2 : des4, after des3, 5d
+;;;; section Critical tasks
+;;;; Completed task in the critical line :crit, done, 2014-01-06,24h
+;;;; Implement parser and jison :crit, done, after des1, 2d
+;;;; Create tests for parser :crit, active, 3d
+;;;; Future task in critical line :crit, 5d
+;;;; Create tests for renderer :2d
+;;;; Add to mermaid :1d
+;;;; ```
+;;;;
+;;;; Mermaid graph specifications can also be loaded from URLs. Here's another
+;;;; example.
+;;;;
+;;;; ### Class Diagram
+;;;;
+;;;; ```mermaid
+;;;; http://localhost:3000/data/classes.mermaid
+;;;; ```
+;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+(defn process-mermaid
+ "If this `url-or-graph-spec` is a valid URL, it is assumed to point to a plain
+ text file pointing to a valid `graph-spec`; otherwise, it is expected to BE a
+ valid `graph-spec`.
+
+ Lightly mung this `graph-spec`, assumed to be a mermaid specification."
+ [^String url-or-graph-spec ^Integer index]
+ (let [data (resource-url-or-data->data url-or-graph-spec)
+ graph-spec (:data data)]
+ (log/info "Retrieved graph-spec from " (:from data) " `" ((:from data) data) "`")
+ (str "\n"
+ graph-spec
+ "\n
")))
+
+;; (fs/file? (str (nio/resource-path) "data/classes.mermaid"))
+;; (slurp (str (nio/resource-path) "data/classes.mermaid"))
diff --git a/src/smeagol/extensions/utils.clj b/src/smeagol/extensions/utils.clj
new file mode 100644
index 0000000..06ed74c
--- /dev/null
+++ b/src/smeagol/extensions/utils.clj
@@ -0,0 +1,72 @@
+(ns ^{:doc "Utility functions useful to extension processors."
+ :author "Simon Brooke"}
+ smeagol.extensions.utils
+ (:require [cemerick.url :refer (url url-encode url-decode)]
+ [clojure.string :as cs]
+ [me.raynes.fs :as fs]
+ [noir.io :as io]
+ [taoensso.timbre :as log]))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Smeagol: a very simple Wiki engine.
+;;;;
+;;;; This program is free software; you can redistribute it and/or
+;;;; modify it under the terms of the GNU General Public License
+;;;; as published by the Free Software Foundation; either version 2
+;;;; of the License, or (at your option) any later version.
+;;;;
+;;;; This program is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with this program; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;;; USA.
+;;;;
+;;;; Copyright (C) 2017 Simon Brooke
+;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defn resource-url-or-data->data
+ "Interpret this `resource-url-or-data` string as data to be digested by a
+ `process-extension` function. It may be a URL or the pathname of a local
+ resource, in which case the content should be fetched; or it may just be
+ the data itself.
+
+ Returns a map with a key `:from` whose value may be `:url`, `:resource` or
+ `:text`, and a key `:data` whose value is the data. There will be an
+ additional key being the value of the `:from` key, whose value will be the
+ source of the data."
+ [^String resource-url-or-data]
+ (let [default {:from :text
+ :text resource-url-or-data
+ :data resource-url-or-data}]
+ (try
+ (try
+ ;; is it a URL?
+ (let [url (str (url resource-url-or-data))
+ result (slurp url)]
+ {:from :url
+ :url url
+ :data result})
+ (catch java.net.MalformedURLException _
+ ;; no. So is it a path to a local resource?
+ (let [t (cs/trim resource-url-or-data)
+ r (str (io/resource-path) t)]
+ (if
+ (fs/file? r)
+ {:from :resource
+ :resource t
+ :data (slurp r)}
+ default))))
+ (catch Exception x
+ (log/error
+ "Could not read mermaid graph specification from `"
+ (cs/trim resource-url-or-data)
+ "` because "
+ (.getName (.getClass x))
+ (.getMessage x) )
+ default))))
diff --git a/src/smeagol/extensions/vega.clj b/src/smeagol/extensions/vega.clj
new file mode 100644
index 0000000..e69de29
diff --git a/src/smeagol/formatting.clj b/src/smeagol/formatting.clj
index 8fe3697..eca5277 100644
--- a/src/smeagol/formatting.clj
+++ b/src/smeagol/formatting.clj
@@ -1,4 +1,4 @@
-(ns ^{:doc "Format Semagol's enhanced markdown format."
+(ns ^{:doc "Format Semagol's extended markdown format."
:author "Simon Brooke"}
smeagol.formatting
(:require [clojure.data.json :as json]
@@ -6,7 +6,8 @@
[cemerick.url :refer (url url-encode url-decode)]
[clj-yaml.core :as yaml]
[markdown.core :as md]
- [smeagol.configuration :refer [config]]))
+ [smeagol.configuration :refer [config]]
+ [smeagol.extensions.mermaid :refer [process-mermaid]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
@@ -85,12 +86,6 @@
");\n//]]\n"))
-(defn process-mermaid
- "Lightly mung this `graph-spec`, assumed to be a mermaid specification."
- [^String graph-spec ^Integer index]
- (str "\n"
- graph-spec
- "\n
"))
(defn process-backticks
From 1df78111cd7ef2028ae4accb8e2fbd3483f4a1b7 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Sun, 9 Feb 2020 09:23:56 +0000
Subject: [PATCH 54/83] Added separate Vega extension file
---
src/smeagol/extensions/vega.clj | 84 +++++++++++++++++++++++++++++++++
1 file changed, 84 insertions(+)
diff --git a/src/smeagol/extensions/vega.clj b/src/smeagol/extensions/vega.clj
index e69de29..14cf285 100644
--- a/src/smeagol/extensions/vega.clj
+++ b/src/smeagol/extensions/vega.clj
@@ -0,0 +1,84 @@
+(ns ^{:doc "Format Semagol's extended markdown format."
+ :author "Simon Brooke"}
+ smeagol.extensions.vega
+ (:require [smeagol.extensions.utils :refer :all]
+ [taoensso.timbre :as log]))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Smeagol: a very simple Wiki engine.
+;;;;
+;;;; This program is free software; you can redistribute it and/or
+;;;; modify it under the terms of the GNU General Public License
+;;;; as published by the Free Software Foundation; either version 2
+;;;; of the License, or (at your option) any later version.
+;;;;
+;;;; This program is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with this program; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;;; USA.
+;;;;
+;;;; Copyright (C) 2017 Simon Brooke
+;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Inspired by [visdown](https://visdown.com/) and
+;;;; [vega-lite](https://vega.github.io/vega-lite/docs/), the Vega formatter
+;;;; allows you to embed vega data visualisations into Smeagol pages. The graph
+;;;; description should start with a line comprising three back-ticks and then
+;;;; the word '`vega`', and end with a line comprising just three backticks.
+;;;;
+;;;; Here's an example cribbed in its entirety from
+;;;; [here](http://visdown.amitkaps.com/london):
+;;;;
+;;;; ### Flight punctuality at London airports
+;;;;
+;;;; ```vega
+;;;; data:
+;;;; url: "data/london.csv"
+;;;; transform:
+;;;; -
+;;;; filter: datum.year == 2016
+;;;; mark: rect
+;;;; encoding:
+;;;; x:
+;;;; type: nominal
+;;;; field: source
+;;;; y:
+;;;; type: nominal
+;;;; field: dest
+;;;; color:
+;;;; type: quantitative
+;;;; field: flights
+;;;; aggregate: sum
+;;;; ```
+;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defn process-vega
+ "If this `src-resource-or-url` is a valid URL, it is assumed to point to a
+ plain text file pointing to valid `vega-src`; otherwise, it is expected to
+ BE a valid `vega-src`.
+
+ Process this `vega-src` string, assumed to be in YAML format, into a
+ specification of a Vega chart, and add the plumbing to render it."
+ [^String src-resource-or-url ^Integer index]
+ (let [data (resource-url-or-data->data url-or-graph-spec)
+ vega-src (:data data)]
+ (log/info "Retrieved vega-src from " (:from data) " `" ((:from data) data) "`")
+ (str
+ "
\n"
+ "")))
From 719222195e4d4cd12d072ddf3309686797ae63e7 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Mon, 10 Feb 2020 11:53:39 +0000
Subject: [PATCH 55/83] Working, but not finished.
---
project.clj | 1 +
resources/config.edn | 3 +-
resources/public/content/Example gallery.md | 59 +++++++++++++++++
resources/public/content/Extensible Markup.md | 6 ++
.../html-includes/photoswipe-boilerplate.html | 65 +++++++++++++++++++
resources/public/vendor/README.md | 1 -
resources/templates/wiki.html | 4 ++
src/smeagol/extensions/vega.clj | 11 +++-
src/smeagol/formatting.clj | 23 ++++++-
9 files changed, 168 insertions(+), 5 deletions(-)
create mode 100644 resources/public/content/Example gallery.md
create mode 100644 resources/public/html-includes/photoswipe-boilerplate.html
delete mode 100644 resources/public/vendor/README.md
diff --git a/project.clj b/project.clj
index b270587..1ab812a 100644
--- a/project.clj
+++ b/project.clj
@@ -52,6 +52,7 @@
[vega-embed "6.2.2"]
[vega-lite "4.1.1"]
[mermaid "8.4.6"]
+ [photoswipe "4.1.3"]
[tablesort "5.2.0"]]
:root "resources/public/vendor"}
diff --git a/resources/config.edn b/resources/config.edn
index c2c9161..4f9532d 100644
--- a/resources/config.edn
+++ b/resources/config.edn
@@ -34,7 +34,8 @@
:formatters {"vega" smeagol.formatting/process-vega
"vis" smeagol.formatting/process-vega
"mermaid" smeagol.extensions.mermaid/process-mermaid
- "backticks" smeagol.formatting/process-backticks}
+ "backticks" smeagol.formatting/process-backticks
+ "pswp" smeagol.formatting/process-photoswipe}
:log-level :info ;; the minimum logging level; one of
;; :trace :debug :info :warn :error :fatal
:js-from :cloudflare ;; where to load JavaScript libraries
diff --git a/resources/public/content/Example gallery.md b/resources/public/content/Example gallery.md
new file mode 100644
index 0000000..8379206
--- /dev/null
+++ b/resources/public/content/Example gallery.md
@@ -0,0 +1,59 @@
+## The Gallery
+
+This page holds an example Photoswipe gallery.
+
+```pswp
+{
+ slides: [
+ { src: 'content/uploads/g1.jpg', w: 2592, h:1944,
+ title: 'Frost on a gate, Laurieston' },
+ { src: 'content/uploads/g2.jpg', w: 2560, h:1920,
+ title: 'Feathered crystals on snow surface, Taliesin' },
+ { src: 'content/uploads/g3.jpg', w: 2560, h:1920,
+ title: 'Feathered snow on log, Taliesin' },
+ { src: 'content/uploads/g4.jpg', w: 2560, h:1920,
+ title: 'Crystaline growth on seed head, Taliesin' }],
+ options: {
+ timeToIdle: 100
+ },
+ openImmediately: true
+}
+
+```
+
+## How this works
+
+The specification for this gallery is as follows:
+
+```
+{
+ slides: [
+ { src: 'content/uploads/g1.jpg', w: 2592, h:1944,
+ title: 'Frost on a gate, Laurieston' },
+ { src: 'content/uploads/g2.jpg', w: 2560, h:1920,
+ title: 'Feathered crystals on snow surface, Taliesin' },
+ { src: 'content/uploads/g3.jpg', w: 2560, h:1920,
+ title: 'Feathered snow on log, Taliesin' },
+ { src: 'content/uploads/g4.jpg', w: 2560, h:1920,
+ title: 'Crystaline growth on seed head, Taliesin' }],
+ options: {
+ timeToIdle: 100
+ },
+ openImmediately: true
+}
+
+```
+
+The format of the specification is [JSON](https://www.json.org/json-en.html); there are (at present) three keys, as follows
+
+### slides
+
+Most be present. The value of `slides` is a list delimited by square brackets of slide objects. For more information, see the [authoritative documentation](https://photoswipe.com/documentation/getting-started.html) under the sub heading **'Creating an Array of Slide Objects'**.
+
+### options
+
+Optional. The value of `options` is a JSON object [as documented here](https://photoswipe.com/documentation/options.html).
+
+### openImmediately
+
+Optional. If the value of `openImmediately` is `true`, the gallery will open immediately, covering the whole page. If false, only a button with the label 'Open the gallery' will be shown. Selecting this button will cause the gallery to open.
diff --git a/resources/public/content/Extensible Markup.md b/resources/public/content/Extensible Markup.md
index aec9e82..3bc8301 100644
--- a/resources/public/content/Extensible Markup.md
+++ b/resources/public/content/Extensible Markup.md
@@ -66,6 +66,12 @@ Mermaid graph specifications can also be loaded from URLs. Here's another exampl
data/classes.mermaid
```
+## Photoswipe galleries
+
+Not so much a formatter, this is an extension to allow you to embed image galleries in your markdown. To specify a gallery, use three backticks followed by `pswp`, followed on the following lines by a Photoswipe specification in [JSON](https://www.json.org/json-en.html)
+followed by three backticks on a line by themselves. There is an [[Example gallery]] so that you can see how this works.
+
+
## Writing your own custom formatters
A custom formatter is simply a Clojure function which takes a string and an integer as arguments and produces a string as output. The string is the text the user has typed into their markdown; the integer is simply a number you can use to keep track of which addition to the page this is, in order, for example, to fix up some JavaScript to render it.
diff --git a/resources/public/html-includes/photoswipe-boilerplate.html b/resources/public/html-includes/photoswipe-boilerplate.html
new file mode 100644
index 0000000..2dec488
--- /dev/null
+++ b/resources/public/html-includes/photoswipe-boilerplate.html
@@ -0,0 +1,65 @@
+
+
+
+
+
+
diff --git a/resources/public/vendor/README.md b/resources/public/vendor/README.md
deleted file mode 100644
index c3ab41c..0000000
--- a/resources/public/vendor/README.md
+++ /dev/null
@@ -1 +0,0 @@
-This folder must exist in order that the Bower package manager can deploy JavaScript packages to it.
diff --git a/resources/templates/wiki.html b/resources/templates/wiki.html
index ffc9b1a..9413902 100644
--- a/resources/templates/wiki.html
+++ b/resources/templates/wiki.html
@@ -2,6 +2,10 @@
{% block extra-headers %}
+ {% script "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js" %}
+ {% script "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js" %}
+ {% style "vendor/node_modules/photoswipe/dist/photoswipe.css" %}
+ {% style "vendor/node_modules/photoswipe/dist/default-skin/default-skin.css" %}
diff --git a/src/smeagol/extensions/vega.clj b/src/smeagol/extensions/vega.clj
index 14cf285..1b9e2de 100644
--- a/src/smeagol/extensions/vega.clj
+++ b/src/smeagol/extensions/vega.clj
@@ -1,7 +1,9 @@
(ns ^{:doc "Format Semagol's extended markdown format."
:author "Simon Brooke"}
smeagol.extensions.vega
- (:require [smeagol.extensions.utils :refer :all]
+ (:require [clojure.data.json :as json]
+ [clj-yaml.core :as yaml]
+ [smeagol.extensions.utils :refer :all]
[taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -60,6 +62,11 @@
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defn yaml->json
+ "Rewrite this string, assumed to be in YAML format, as JSON."
+ [^String yaml-src]
+ (json/write-str (yaml/parse-string yaml-src)))
+
(defn process-vega
"If this `src-resource-or-url` is a valid URL, it is assumed to point to a
plain text file pointing to valid `vega-src`; otherwise, it is expected to
@@ -68,7 +75,7 @@
Process this `vega-src` string, assumed to be in YAML format, into a
specification of a Vega chart, and add the plumbing to render it."
[^String src-resource-or-url ^Integer index]
- (let [data (resource-url-or-data->data url-or-graph-spec)
+ (let [data (resource-url-or-data->data src-resource-or-url)
vega-src (:data data)]
(log/info "Retrieved vega-src from " (:from data) " `" ((:from data) data) "`")
(str
diff --git a/src/smeagol/formatting.clj b/src/smeagol/formatting.clj
index eca5277..357f6aa 100644
--- a/src/smeagol/formatting.clj
+++ b/src/smeagol/formatting.clj
@@ -6,6 +6,7 @@
[cemerick.url :refer (url url-encode url-decode)]
[clj-yaml.core :as yaml]
[markdown.core :as md]
+ [noir.io :as io] ;; used by photoswipe, only
[smeagol.configuration :refer [config]]
[smeagol.extensions.mermaid :refer [process-mermaid]]))
@@ -85,7 +86,27 @@
index
");\n//]]\n"))
-
+(defn process-photoswipe
+ "Process specification for a photoswipe gallery"
+ [^String spec ^Integer index]
+ (str
+ "\n"
+ (slurp (str (io/resource-path) "html-includes/photoswipe-boilerplate.html"))
+ "
+
+ Open the gallery
+ "))
(defn process-backticks
From ad5e41c23a0eaee8d48398ba66d080b1eed86740 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Mon, 10 Feb 2020 17:39:24 +0000
Subject: [PATCH 56/83] Progress on thumbnailing, but not working yet.
---
project.clj | 1 +
resources/config.edn | 7 +++-
src/smeagol/uploads.clj | 89 +++++++++++++++++++++++++++++++----------
3 files changed, 75 insertions(+), 22 deletions(-)
diff --git a/project.clj b/project.clj
index 1ab812a..c92f098 100644
--- a/project.clj
+++ b/project.clj
@@ -16,6 +16,7 @@
[environ "1.1.0"]
[hiccup "1.0.5"]
[im.chit/cronj "1.4.4"]
+ [image-resizer "0.1.10"]
[lib-noir "0.9.9" :exclusions [org.clojure/tools.reader]]
[markdown-clj "0.9.99" :exclusions [com.keminglabs/cljx]]
[me.raynes/fs "1.4.6"]
diff --git a/resources/config.edn b/resources/config.edn
index 4f9532d..78390bb 100644
--- a/resources/config.edn
+++ b/resources/config.edn
@@ -42,5 +42,10 @@
;; from: options are :local, :cloudflare
:passwd "resources/passwd"
;; where the password file is stored
- :site-title "Smeagol"} ;; overall title of the site, used in
+ :site-title "Smeagol" ;; overall title of the site, used in
;; page headings
+ :thumbnails {:small 64 ;; maximum dimension of thumbnails
+ ;; stored in the /small directory
+ :med 400 ;; maximum dimension of thumbnails
+ ;; stored in the /med directory
+ }}
diff --git a/src/smeagol/uploads.clj b/src/smeagol/uploads.clj
index 7cadaea..24f2c71 100644
--- a/src/smeagol/uploads.clj
+++ b/src/smeagol/uploads.clj
@@ -1,10 +1,18 @@
(ns ^{:doc "Handle file uploads."
:author "Simon Brooke"}
smeagol.uploads
- (:import [java.io File])
(:require [clojure.string :as cs]
- [noir.io :as io]
- [taoensso.timbre :as timbre]))
+ [clojure.java.io :as io]
+ [image-resizer.core :refer [resize]]
+ [image-resizer.util :refer :all]
+ [me.raynes.fs :as fs]
+ [smeagol.configuration :refer [config]]
+ [taoensso.timbre :as log])
+ (:import [java.io File]
+ [java.awt Image]
+ [java.awt.image RenderedImage BufferedImageOp]
+ [javax.imageio ImageIO ImageWriter ImageWriteParam IIOImage]
+ [javax.imageio.stream FileImageOutputStream]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
@@ -29,21 +37,59 @@
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; No longer used as uploaded files now go into Git.
-;; (defn avoid-name-collisions
-;; "Find a filename within this `path`, based on this `file-name`, that does not
-;; reference an existing file. It is assumed that `path` ends with a path separator.
-;; Returns a filename hwich does not currently reference a file within the path."
-;; [path file-name]
-;; (if (.exists (File. (str path file-name)))
-;; (let [parts (cs/split file-name #"\.")
-;; prefix (cs/join "." (butlast parts))
-;; suffix (last parts)]
-;; (first
-;; (filter #(not (.exists (File. (str path %))))
-;; (map #(str prefix "." % "." suffix) (range)))))
-;; file-name))
+(def image-file-extns
+ "Extensions of file types we will attempt to thumbnail. GIF is excluded
+ because by default the javax.imageio package can read GIF, PNG, and JPEG
+ images but can only write PNG and JPEG images."
+ #{".jpg" ".jpeg" ".png"})
+(defn read-image
+ "Reads a BufferedImage from source, something that can be turned into
+ a file with clojure.java.io/file"
+ [source]
+ (ImageIO/read (io/file source)))
+
+(defn write-image
+ "Writes img, a RenderedImage, to dest, something that can be turned into
+ a file with clojure.java.io/file.
+ Takes the following keys as options:
+ :format - :gif, :jpg, :png or anything supported by ImageIO
+ :quality - for JPEG images, a number between 0 and 100"
+ [^RenderedImage img dest & {:keys [format quality] :or {format :jpg}}]
+ (if (or (not quality) (not (contains? #{:jpg :jpeg} format)))
+ (ImageIO/write img (name format) (io/file dest))
+ (let [fmt (rest (fs/extension (cs/lower-case dest)))
+ iw (doto ^ImageWriter (first
+ (iterator-seq
+ (ImageIO/getImageWritersByFormatName
+ "jpeg")))
+ (.setOutput (FileImageOutputStream. (io/file dest))))
+ iw-param (doto ^ImageWriteParam (.getDefaultWriteParam iw)
+ (.setCompressionMode ImageWriteParam/MODE_EXPLICIT)
+ (.setCompressionQuality (float (/ quality 100))))
+ iio-img (IIOImage. img nil nil)]
+ (.write iw nil iio-img iw-param))))
+
+(defn auto-thumbnail
+ "For each of the thumbnail sizes in the configuration, create a thumbnail
+ for the file with this `filename` on this `path`, provided that it is a
+ scalable image and is larger than the size."
+ ([^String path ^String filename]
+ (if
+ (image-file-extns (fs/extension (cs/lower-case filename)))
+ (let [original (buffered-image (.File (str path filename)))] ;; fs/file?
+ (map
+ #(auto-thumbnail path filename % original)
+ (keys (config :thumbnails))))
+ (log/info filename " cannot be thumbnailed.")))
+ ([^String path ^String filename size ^RenderedImage image]
+ (let [s (-> config :thumbnails size)
+ d (dimensions image)]
+ (if (and (integer? s) (some #(> % s) d))
+ (do
+ (write-image (resize image s s) (io/file path (name size) filename))
+ (log/info "Created a " size " thumbnail of " filename))
+ (log/info filename "is smaller than " s "x" s " and was not scaled to " size)))))
(defn store-upload
"Store an upload both to the file system and to the database.
@@ -56,17 +102,18 @@
(let [upload (:upload params)
tmp-file (:tempfile upload)
filename (:filename upload)]
- (timbre/info
+ (log/info
(str "Storing upload file: " upload))
- (timbre/debug
+ (log/debug
(str "store-upload mv file: " tmp-file " to: " path filename))
(if tmp-file
(try
(do
(.renameTo tmp-file
- (File. (str path filename)))
+ (File. (str path filename))) ;; TODO: fs/file
+ (auto-thumbnail path filename)
(File. (str path filename)))
(catch Exception x
- (timbre/error (str "Failed to move " tmp-file " to " path filename "; " (type x) ": " (.getMessage x)))
+ (log/error (str "Failed to move " tmp-file " to " path filename "; " (type x) ": " (.getMessage x)))
(throw x)))
(throw (Exception. "No file found?")))))
From 40f4f13667d1f2a193768d118e1cd85d6c754362 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Mon, 10 Feb 2020 21:36:49 +0000
Subject: [PATCH 57/83] Tactical commit: I'm fairly sure this is close to good.
---
resources/templates/list-uploads.html | 4 +-
resources/templates/upload.html | 29 +++++++------
src/smeagol/routes/wiki.clj | 52 +++++++++++-----------
src/smeagol/uploads.clj | 62 ++++++++++++++++-----------
src/smeagol/util.clj | 10 +++--
5 files changed, 88 insertions(+), 69 deletions(-)
diff --git a/resources/templates/list-uploads.html b/resources/templates/list-uploads.html
index 761e4a8..e759dac 100644
--- a/resources/templates/list-uploads.html
+++ b/resources/templates/list-uploads.html
@@ -21,10 +21,10 @@
{{entry.base-name}}
{{entry.modified}}
- {% if entry.is-image %}  {% else %} [{{entry.name|capitalize}}](uploads/{{entry.base-name}}) {% endif %}
+ {% if entry.is-image %}  {% else %} [{{entry.name|capitalize}}](uploads/{{entry.resource}}) {% endif %}
- {% if entry.is-image %} {% else %} link {% endif %}
+ {% if entry.is-image %} {% else %} link {% endif %}
diff --git a/resources/templates/upload.html b/resources/templates/upload.html
index 7c0c373..bc469b4 100644
--- a/resources/templates/upload.html
+++ b/resources/templates/upload.html
@@ -1,22 +1,25 @@
{% extends "templates/base.html" %}
{% block content %}
- {% if uploaded %}
- {% if is-image %}
-
-
+ {% if has-uploaded %}
+ {% for upload in uploaded %}
+ {{upload.filename}}
+ {% if upload.is-image %)
+
+
- {% i18n file-upload-link-text %}:
+ {% i18n file-upload-link-text %}:
- 
-
- {% else %}
-
- {% i18n file-upload-link-text %}:
+ 
+
+ {% else %}
+
+ {% i18n file-upload-link-text %}:
- [Uploaded file](uploads/{{uploaded}})
-
- {% endif %}
+
[{{upload.filename}}]({{upload.resource}})
+
+ {% endif %}
+ {% endfor %}
{% else %}
"))
-
(defn process-backticks
"Effectively, escape the backticks surrounding this `text`, by protecting them
diff --git a/src/smeagol/util.clj b/src/smeagol/util.clj
index 002556f..1ef44df 100644
--- a/src/smeagol/util.clj
+++ b/src/smeagol/util.clj
@@ -46,6 +46,9 @@
(:content-dir config)
(cjio/file (io/resource-path) "content")))))
+(def upload-dir
+ (str (cjio/file content-dir "uploads")))
+
(defn standard-params
"Return a map of standard parameters to pass to the template renderer."
[request]
From b191f40d05c3dd64141ff59ecb9b13dd31db7373 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Tue, 11 Feb 2020 17:01:14 +0000
Subject: [PATCH 61/83] #47: warn if dimensions cannot be established.
---
src/smeagol/extensions/photoswipe.clj | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/smeagol/extensions/photoswipe.clj b/src/smeagol/extensions/photoswipe.clj
index 764a7a4..31c6e42 100644
--- a/src/smeagol/extensions/photoswipe.clj
+++ b/src/smeagol/extensions/photoswipe.clj
@@ -110,7 +110,9 @@
(catch Exception x (.getMessage x)))]
(if dimensions
(assoc slide :w (first dimensions) :h (nth dimensions 1))
- slide)))
+ (do
+ (log/warn "Failed to fetch dimensions of image " url)
+ slide))))
;; (slide-merge-dimensions
;; {:title "Frost on a gate, Laurieston",
From 0d686a9b6338f000219dfd5ef0cb1ac33f7abc71 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 12 Feb 2020 12:35:18 +0000
Subject: [PATCH 62/83] #47: Something well broken here, but I'm on the right
path
Fragment indices are being returned instead of fragments, and it does not seem that the extension formatters are being called at all. But... config is definitely improved in the
right direction.
---
project.clj | 1 +
resources/config.edn | 11 ++++++-----
.../content/Simplified example gallery.md | 2 +-
src/smeagol/extensions/photoswipe.clj | 17 +++++++++++------
src/smeagol/formatting.clj | 15 +++++++++++++--
5 files changed, 32 insertions(+), 14 deletions(-)
diff --git a/project.clj b/project.clj
index b753cd1..3898e1c 100644
--- a/project.clj
+++ b/project.clj
@@ -27,6 +27,7 @@
[org.clojure/core.memoize "0.5.9"]
[org.clojure/data.json "0.2.6"]
[org.clojure/tools.logging "0.4.0"]
+ [org.clojure/tools.trace "0.7.10"]
[org.slf4j/slf4j-api "1.7.25"]
[org.slf4j/log4j-over-slf4j "1.7.25"]
[org.slf4j/jul-to-slf4j "1.7.25"]
diff --git a/resources/config.edn b/resources/config.edn
index 059dfeb..6ff0faf 100644
--- a/resources/config.edn
+++ b/resources/config.edn
@@ -32,11 +32,12 @@
:default-locale "en-GB" ;; default language used for messages
:formatters ;; formatters for processing markdown
;; extensions.
- {"vega" smeagol.formatting/process-vega
- "vis" smeagol.formatting/process-vega
- "mermaid" smeagol.extensions.mermaid/process-mermaid
- "backticks" smeagol.formatting/process-backticks
- "pswp" smeagol.extensions.photoswipe/process-photoswipe}
+ {:vega {:formatter "smeagol.extensions.vega/process-vega" }
+ :vis {:formatter "smeagol.extensions.vega/process-vega" }
+ :mermaid {:formatter "smeagol.extensions.mermaid/process-mermaid" }
+ :backticks {:formatter "smeagol.formatting/process-backticks" }
+ :pswp {:formatter "smeagol.extensions.photoswipe/process-photoswipe" }
+ }
:log-level :info ;; the minimum logging level; one of
;; :trace :debug :info :warn :error :fatal
:js-from :cdnjs ;; where to load JavaScript libraries
diff --git a/resources/public/content/Simplified example gallery.md b/resources/public/content/Simplified example gallery.md
index 4cff2da..eac0394 100644
--- a/resources/public/content/Simplified example gallery.md
+++ b/resources/public/content/Simplified example gallery.md
@@ -13,7 +13,7 @@ That's all there is to it - a sequence of image links just as you'd write them a
## The Gallery
-This page holds another example Photoswipe gallery, this time using a simpler, Markdown-based specification. Processing this specification takes more work than the full syntax used in the other [Example gallery], so the gallery may be slower to load; but it's much easier to configure.
+This page holds another example Photoswipe gallery, this time using a simpler, Markdown-based specification. Processing this specification takes more work than the full syntax used in the other [[Example gallery]], so the gallery may be slower to load; but it's much easier to configure.
```pswp

diff --git a/src/smeagol/extensions/photoswipe.clj b/src/smeagol/extensions/photoswipe.clj
index 31c6e42..2df235e 100644
--- a/src/smeagol/extensions/photoswipe.clj
+++ b/src/smeagol/extensions/photoswipe.clj
@@ -174,10 +174,15 @@
;; 1)
(defn process-photoswipe
- [^String url-or-pswp-spec ^Integer index]
+ [^String url-or-pswp-spec ^Integer index]
+ (log/info "process-photoswipe called with arg1 `"
+ url-or-pswp-spec "`; arg2 `" index "`.")
(let [data (resource-url-or-data->data url-or-pswp-spec)
- spec (cs/trim (:data data))]
- (if
- (cs/starts-with? spec "![")
- (process-simple-photoswipe spec index)
- (process-full-photoswipe spec index))))
+ spec (cs/trim (:data data))
+ result
+ (if
+ (cs/starts-with? spec "![")
+ (process-simple-photoswipe spec index)
+ (process-full-photoswipe spec index))]
+ (log/info "process-photoswipe returning `" result "`.")
+ ))
diff --git a/src/smeagol/formatting.clj b/src/smeagol/formatting.clj
index fa4f2f6..87d8f45 100644
--- a/src/smeagol/formatting.clj
+++ b/src/smeagol/formatting.clj
@@ -8,7 +8,8 @@
[markdown.core :as md]
[smeagol.configuration :refer [config]]
[smeagol.extensions.mermaid :refer [process-mermaid]]
- [smeagol.extensions.photoswipe :refer [process-photoswipe]]))
+ [smeagol.extensions.photoswipe :refer [process-photoswipe]]
+ [taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
@@ -149,7 +150,17 @@
;; I need to put the backticks back in.
remarked (if (odd? index) (str "```" fragment "\n```") fragment)
first-token (get-first-token fragment)
- formatter (eval ((:formatters config) first-token))]
+ formatter (if-not
+ (empty? first-token)
+ (try
+ (let [kw (keyword first-token)]
+ (read-string (-> config :formatters kw :formatter)))
+ (catch Exception _
+ (do
+ (log/info "No formatter found for extension `" first-token "`")
+ ;; no extension registered - there sometimes won't be,
+ ;; and it doesn't matter
+ nil))))]
(cond
(empty? fragments)
(assoc result :text
From 2f22b733c1800ee6c07a23e866355c9932602982 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 12 Feb 2020 21:13:36 +0000
Subject: [PATCH 63/83] Tidy up
alphordered includes, standardised on use 'log' as alias for timbre.
---
src/smeagol/authenticate.clj | 20 ++++++++++----------
src/smeagol/configuration.clj | 12 ++++++------
src/smeagol/formatting.clj | 30 +++++++++++++++++++++++++-----
src/smeagol/handler.clj | 12 ++++++------
src/smeagol/history.clj | 9 +++++----
src/smeagol/layout.clj | 3 +--
src/smeagol/middleware.clj | 14 +++++++-------
src/smeagol/util.clj | 4 ++--
8 files changed, 62 insertions(+), 42 deletions(-)
diff --git a/src/smeagol/authenticate.clj b/src/smeagol/authenticate.clj
index e4219e9..fec7e7a 100644
--- a/src/smeagol/authenticate.clj
+++ b/src/smeagol/authenticate.clj
@@ -5,7 +5,7 @@
[environ.core :refer [env]]
[noir.io :as io]
[smeagol.configuration :refer [config]]
- [taoensso.timbre :as timbre]))
+ [taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
@@ -52,7 +52,7 @@
"Return `true` if this `username`/`password` pair match, `false` otherwise"
[username password]
(let [user ((keyword username) (get-users))]
- (timbre/info (str "Authenticating " username " against " password-file-path))
+ (log/info (str "Authenticating " username " against " password-file-path))
(and user
(:password user)
(or
@@ -92,7 +92,7 @@
Return `true` if password was successfully changed. Subsequent to user change, their
password will be encrypted."
[username oldpass newpass]
- (timbre/info (format "Changing password for user %s" username))
+ (log/info (format "Changing password for user %s" username))
(let [users (get-users)
keywd (keyword username)
user (keywd users)
@@ -110,10 +110,10 @@
{keywd
(merge user
{:password (password/encrypt newpass)})})))
- (timbre/info (str "Successfully changed password for user " username))
+ (log/info (str "Successfully changed password for user " username))
true))
(catch Exception any
- (timbre/error any
+ (log/error any
(format "Changing password failed for user %s failed: %s (%s)"
username (.getName (.getClass any)) (.getMessage any)))
false))))
@@ -138,7 +138,7 @@
`email` address and `admin` flag; *or*, modify an existing user. Return true
if user is successfully stored, false otherwise."
[username newpass email admin]
- (timbre/info "Trying to add user " username)
+ (log/info "Trying to add user " username)
(cond
(not (string? username)) (throw (Exception. "Username must be a string."))
(zero? (count username)) (throw (Exception. "Username cannot be zero length"))
@@ -160,10 +160,10 @@
(locking password-file-path
(spit password-file-path
(assoc users (keyword username) (merge user full-details)))
- (timbre/info "Successfully added user " username)
+ (log/info "Successfully added user " username)
true)
(catch Exception any
- (timbre/error any
+ (log/error any
(format "Adding user %s failed: %s (%s)"
username (.getName (.getClass any)) (.getMessage any)))
false)))))
@@ -177,10 +177,10 @@
(locking password-file-path
(spit password-file-path
(dissoc users (keyword username)))
- (timbre/info (str "Successfully deleted user " username))
+ (log/info (str "Successfully deleted user " username))
true)
(catch Exception any
- (timbre/error any
+ (log/error any
(format "Deleting user %s failed: %s (%s)"
username (.getName (.getClass any)) (.getMessage any)))
false))))
diff --git a/src/smeagol/configuration.clj b/src/smeagol/configuration.clj
index 025292a..24d1e55 100644
--- a/src/smeagol/configuration.clj
+++ b/src/smeagol/configuration.clj
@@ -5,7 +5,7 @@
[clojure.string :as s]
[environ.core :refer [env]]
[noir.io :as io]
- [taoensso.timbre :as timbre]))
+ [taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
@@ -73,7 +73,7 @@
and optionally a key :transform, whose value is a function of one
argument to be used to transform the value of that key."
[m tuples]
- (timbre/debug
+ (log/debug
"transform-map:\n"
(with-out-str (clojure.pprint/pprint m)))
(reduce
@@ -112,11 +112,11 @@
file is read (if it is specified and present), but that individual
values can be overridden by environment variables."
(try
- (timbre/info (str "Reading configuration from " config-file-path))
+ (log/info (str "Reading configuration from " config-file-path))
(let [file-contents (try
(read-string (slurp config-file-path))
(catch Exception x
- (timbre/error
+ (log/error
(str
"Failed to read configuration from "
config-file-path
@@ -138,12 +138,12 @@
:smeagol-site-title)
config-env-transforms))]
(if (env :dev)
- (timbre/debug
+ (log/debug
"Loaded configuration\n"
(with-out-str (clojure.pprint/pprint config))))
config)
(catch Exception any
- (timbre/error any "Could not load configuration")
+ (log/error any "Could not load configuration")
{})))
(def config (build-config))
diff --git a/src/smeagol/formatting.clj b/src/smeagol/formatting.clj
index 87d8f45..8eff3ec 100644
--- a/src/smeagol/formatting.clj
+++ b/src/smeagol/formatting.clj
@@ -44,6 +44,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Error to show if text to be rendered is nil.
+;; TODO: this should go through i18n
(def no-text-error "No text: does the file exist?")
@@ -150,28 +151,43 @@
;; I need to put the backticks back in.
remarked (if (odd? index) (str "```" fragment "\n```") fragment)
first-token (get-first-token fragment)
+ kw (if-not (empty? first-token) (keyword first-token))
formatter (if-not
(empty? first-token)
(try
- (let [kw (keyword first-token)]
- (read-string (-> config :formatters kw :formatter)))
+ (read-string (-> config :formatters kw :formatter))
(catch Exception _
(do
- (log/info "No formatter found for extension `" first-token "`")
+ (log/info "No formatter found for extension `" kw "`")
;; no extension registered - there sometimes won't be,
;; and it doesn't matter
nil))))]
(cond
(empty? fragments)
+ ;; We've come to the end of the list of fragments. Reassemble them into
+ ;; a single HTML text and pass it back.
(assoc result :text
(local-links
(md/md-to-html-string
(cs/join "\n\n" (reverse processed))
:heading-anchors true)))
formatter
- (apply-formatter index result fragments processed fragment first-token formatter)
+ ;; We've found a formatter to apply to the current fragment, and recurse
+ ;; on down the list
+ (let [result (apply-formatter
+ index
+ result
+ fragments
+ processed
+ fragment
+ first-token
+ formatter)]
+ (assoc result :extensions (cons kw (:extensions result))))
true
- (process-markdown-fragment index result remarked (rest fragments) processed)))))
+ ;; Otherwise process the current fragment as markdown and recurse on
+ ;; down the list
+ (process-markdown-fragment
+ index result remarked (rest fragments) processed)))))
(defn reintegrate-inclusions
@@ -182,6 +198,10 @@
([inclusions text]
(let [ks (keys inclusions)]
(if (empty? (keys inclusions))
+ ;; TODO: this is one opportunity to add scripts at the end of the
+ ;; constructed text. I've a feeling that that would be a mistake and
+ ;; that instead we should hand back a map comprising the text and the
+ ;; keys of the extensions
text
(let [kw (first ks)]
(reintegrate-inclusions
diff --git a/src/smeagol/handler.clj b/src/smeagol/handler.clj
index 9cbaca5..db580e6 100644
--- a/src/smeagol/handler.clj
+++ b/src/smeagol/handler.clj
@@ -16,7 +16,7 @@
[smeagol.routes.wiki :refer [wiki-routes]]
[smeagol.middleware :refer [load-middleware]]
[smeagol.session-manager :as session-manager]
- [taoensso.timbre :as timbre]
+ [taoensso.timbre :as log]
[taoensso.timbre.appenders.3rd-party.rotor :as rotor]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -55,9 +55,9 @@
"destroy will be called when your application
shuts down, put any clean up code here"
[]
- (timbre/info "smeagol is shutting down...")
+ (log/info "smeagol is shutting down...")
(cronj/shutdown! session-manager/cleanup-job)
- (timbre/info "shutdown complete!"))
+ (log/info "shutdown complete!"))
(defn init
@@ -67,7 +67,7 @@
put any initialization code here"
[]
(try
- (timbre/merge-config!
+ (log/merge-config!
{:appenders
{:rotor (rotor/rotor-appender
{:path "smeagol.log"
@@ -80,10 +80,10 @@
(cronj/start! session-manager/cleanup-job)
(if (env :dev) (parser/cache-off!))
;;start the expired session cleanup job
- (timbre/info "\n-=[ smeagol started successfully"
+ (log/info "\n-=[ smeagol started successfully"
(when (env :dev) "using the development profile") "]=-")
(catch Exception any
- (timbre/error any "Failure during startup")
+ (log/error any "Failure during startup")
(destroy))))
;; timeout sessions after 30 minutes
diff --git a/src/smeagol/history.clj b/src/smeagol/history.clj
index aca6dbe..e567db3 100644
--- a/src/smeagol/history.clj
+++ b/src/smeagol/history.clj
@@ -1,10 +1,10 @@
(ns ^{:doc "Explore the history of a page."
:author "Simon Brooke"}
smeagol.history
- (:require [taoensso.timbre :as timbre]
- [clj-jgit.porcelain :as git]
+ (:require [clj-jgit.porcelain :as git]
[clj-jgit.internal :as i]
- [clj-jgit.querying :as q])
+ [clj-jgit.querying :as q]
+ [taoensso.timbre :as log])
(:import [org.eclipse.jgit.api Git]
[org.eclipse.jgit.lib Repository ObjectId]
[org.eclipse.jgit.revwalk RevCommit RevTree RevWalk]
@@ -39,7 +39,7 @@
"If this `log-entry` contains a reference to this `file-path`, return the entry;
else nil."
[^String log-entry ^String file-path]
- (timbre/info (format "searching '%s' for '%s'" log-entry file-path))
+ (log/info (format "searching '%s' for '%s'" log-entry file-path))
(cond
(seq (filter (fn* [p1__341301#] (= (first p1__341301#) file-path)) (:changed_files log-entry)))
log-entry))
@@ -54,6 +54,7 @@
(try
(git/load-repo git-directory-path)
(catch java.io.FileNotFoundException fnf
+ (log/info "Initialising Git repository at" git-directory-path)
(git/git-init git-directory-path)
(let [repo (git/load-repo git-directory-path)]
(git/git-add-and-commit repo "Initial commit")
diff --git a/src/smeagol/layout.clj b/src/smeagol/layout.clj
index 34a7d0a..920a9b1 100644
--- a/src/smeagol/layout.clj
+++ b/src/smeagol/layout.clj
@@ -12,8 +12,7 @@
[selmer.parser :as parser]
[smeagol.configuration :refer [config]]
[smeagol.sanity :refer :all]
- [smeagol.util :as util]
- [taoensso.timbre :as timbre]))
+ [smeagol.util :as util]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
diff --git a/src/smeagol/middleware.clj b/src/smeagol/middleware.clj
index 92cfac0..4ca288d 100644
--- a/src/smeagol/middleware.clj
+++ b/src/smeagol/middleware.clj
@@ -1,17 +1,17 @@
(ns ^{:doc "In truth, boilerplate provided by LuminusWeb."
:author "Simon Brooke"}
smeagol.middleware
- (:require [taoensso.timbre :as timbre]
- [environ.core :refer [env]]
- [selmer.middleware :refer [wrap-error-page]]
+ (:require [environ.core :refer [env]]
+ [noir-exception.core :refer [wrap-internal-error]]
[prone.middleware :refer [wrap-exceptions]]
[ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
[ring.middleware.file :refer [wrap-file]]
[ring.middleware.resource :refer [wrap-resource]]
[ring.middleware.content-type :refer [wrap-content-type]]
[ring.middleware.not-modified :refer [wrap-not-modified]]
- [noir-exception.core :refer [wrap-internal-error]]
- [smeagol.util :as util]))
+ [selmer.middleware :refer [wrap-error-page]]
+ [smeagol.util :as util]
+ [taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
@@ -39,7 +39,7 @@
(defn log-request [handler]
(fn [req]
- (timbre/debug req)
+ (log/debug req)
(handler req)))
@@ -49,7 +49,7 @@
(def production-middleware
- [#(wrap-internal-error % :log (fn [e] (timbre/error e)))
+ [#(wrap-internal-error % :log (fn [e] (log/error e)))
#(wrap-resource % "public")
#(wrap-file % util/content-dir
{:index-files? false :prefer-handler? true})
diff --git a/src/smeagol/util.clj b/src/smeagol/util.clj
index 1ef44df..0a5863a 100644
--- a/src/smeagol/util.clj
+++ b/src/smeagol/util.clj
@@ -10,7 +10,7 @@
[smeagol.authenticate :as auth]
[smeagol.configuration :refer [config]]
[smeagol.formatting :refer [md->html]]
- [taoensso.timbre :as timbre]))
+ [taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
@@ -69,7 +69,7 @@
messages (try
(i18n/get-messages specifier "i18n" "en-GB")
(catch Exception any
- (timbre/error
+ (log/error
any
(str
"Failed to parse accept-language header '"
From 40ab296d1a4c7130323e48f0363cea8e424d9b4d Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Thu, 13 Feb 2020 14:44:52 +0000
Subject: [PATCH 64/83] #45: OK, it doesn't work, but it's close.
Still getting fragment index instead of fragment text.
---
resources/config.edn | 28 ++++++++++++++---
resources/templates/wiki.html | 20 ++++--------
src/smeagol/formatting.clj | 14 +++++++--
src/smeagol/routes/wiki.clj | 59 +++++++++++++++++++++++++++--------
src/smeagol/util.clj | 4 +--
5 files changed, 88 insertions(+), 37 deletions(-)
diff --git a/resources/config.edn b/resources/config.edn
index 6ff0faf..7c462b3 100644
--- a/resources/config.edn
+++ b/resources/config.edn
@@ -32,11 +32,29 @@
:default-locale "en-GB" ;; default language used for messages
:formatters ;; formatters for processing markdown
;; extensions.
- {:vega {:formatter "smeagol.extensions.vega/process-vega" }
- :vis {:formatter "smeagol.extensions.vega/process-vega" }
- :mermaid {:formatter "smeagol.extensions.mermaid/process-mermaid" }
- :backticks {:formatter "smeagol.formatting/process-backticks" }
- :pswp {:formatter "smeagol.extensions.photoswipe/process-photoswipe" }
+ {:vega {:formatter "smeagol.extensions.vega/process-vega"
+ :scripts {:core {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega/5.9.1/vega.min.js"}
+ :lite {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-lite/4.1.1/vega-lite.min.js"}
+ :embed {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-embed/6.2.2/vega-embed.min.js"}
+ :styles {}}}
+ :vis {:formatter "smeagol.extensions.vega/process-vega"
+ :scripts {:core {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega/5.9.1/vega.min.js"}
+ :lite {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-lite/4.1.1/vega-lite.min.js"}
+ :embed {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-embed/6.2.2/vega-embed.min.js"}
+ :styles {}}}
+ :mermaid {:formatter "smeagol.extensions.mermaid/process-mermaid"
+ :scripts {:core {:local "vendor/mermaid/dist/mermaid.js"}}
+ :styles {}}
+ :backticks {:formatter "smeagol.formatting/process-backticks"
+ :scripts {}
+ :styles {}}
+ :pswp {:formatter "smeagol.extensions.photoswipe/process-photoswipe"
+ :scripts {:core {:local "/vendor/node_modules/photoswipe/dist/photoswipe.min.js"
+ :remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"}
+ :ui {:local "/vendor/node_modules/photoswipe/dist/photoswipe-ui-default.min.js"
+ :remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"}}
+ :styles {:core {:local "/vendor/node_modules/photoswipe/dist/photoswipe.css"
+ :remote "/vendor/node_modules/photoswipe/dist/default-skin/default-skin.css"}}}
}
:log-level :info ;; the minimum logging level; one of
;; :trace :debug :info :warn :error :fatal
diff --git a/resources/templates/wiki.html b/resources/templates/wiki.html
index cf0655e..7342854 100644
--- a/resources/templates/wiki.html
+++ b/resources/templates/wiki.html
@@ -1,20 +1,12 @@
{% extends "templates/base.html" %}
{% block extra-headers %}
-
-
-
- {% style "/vendor/node_modules/photoswipe/dist/photoswipe.css" %}
- {% style "/vendor/node_modules/photoswipe/dist/default-skin/default-skin.css" %}
-
-
-
-
-
-
-
-
+ {% for script in scripts %}
+
+ {% endfor %}
+ {% for style in styles %}
+
+ {% endfor %}
{% endblock %}
{% block content %}
diff --git a/src/smeagol/formatting.clj b/src/smeagol/formatting.clj
index 8eff3ec..115a2ca 100644
--- a/src/smeagol/formatting.clj
+++ b/src/smeagol/formatting.clj
@@ -182,7 +182,7 @@
fragment
first-token
formatter)]
- (assoc result :extensions (cons kw (:extensions result))))
+ (assoc-in result [:extensions kw] (-> config :formatters kw)))
true
;; Otherwise process the current fragment as markdown and recurse on
;; down the list
@@ -194,7 +194,12 @@
"Given a map of the form produced by `process-text`, return a string of HTML text
with the inclusions (if any) reintegrated."
([processed-text]
- (reintegrate-inclusions (:inclusions processed-text) (:text processed-text)))
+ (assoc
+ processed-text
+ :content
+ (reintegrate-inclusions
+ (:inclusions processed-text)
+ (:text processed-text))))
([inclusions text]
(let [ks (keys inclusions)]
(if (empty? (keys inclusions))
@@ -213,7 +218,10 @@
(defn md->html
- "Take this markdown source, and return HTML."
+ "Take this `md-src` markdown source, and return a map in which:
+ 1. the key `:content` is bound to the equivalent HTML source;
+ 2. the key `:extensions`. is bound to details of the extensions
+ used."
[md-src]
(reintegrate-inclusions (process-text md-src)))
diff --git a/src/smeagol/routes/wiki.clj b/src/smeagol/routes/wiki.clj
index e33cbdc..e4ca7ba 100644
--- a/src/smeagol/routes/wiki.clj
+++ b/src/smeagol/routes/wiki.clj
@@ -25,6 +25,7 @@
[smeagol.uploads :as ul]
[taoensso.timbre :as log]
[com.stuartsierra.component :as component]
+ [smeagol.configuration :refer [config]]
[smeagol.include.resolve-local-file :as resolve]
[smeagol.include :as include]))
@@ -123,6 +124,34 @@
(include/new-includer)
[:resolver]))))
+(defn preferred-source
+ "Here, `component` is expected to be a map with two keys, `:local` and
+ `:remote`. If the value of `:extensions-from` in `config.edn` is remote
+ AND the value of `:remote` is not nil, then the value of `:remote` will
+ be returned. Otherwise, if the value of `:local` is nil and the value of
+ `:remote` is non-nil, the value of `:remote` will be returned. By default,
+ the value of `:local` will be returned."
+ [component]
+ (let [l (:local component) ;; TODO: look at the trick in Selmer to get relative URL
+ r (:remote component)]
+ (cond
+ (= (:extensions-from config) :remote) (if (empty? r) l r)
+ (empty? l) r
+ :else l)))
+
+(defn collect-preferred
+ "From extensions referenced in this `processed-text`, extract the preferred
+ URLs for this keyword `k`, expected to be either `:scripts` or `:styles`."
+ [processed-text k]
+ (set
+ (remove
+ nil?
+ (map
+ preferred-source
+ (apply
+ concat
+ (map vals (map k (vals (:extensions processed-text)))))))))
+
(defn wiki-page
"Render the markdown page specified in this `request`, if any. If none found, redirect to edit-page"
[request]
@@ -134,19 +163,23 @@
file-name (str page ".md")
file-path (cjio/file util/content-dir file-name)
exists? (.exists (clojure.java.io/as-file file-path))]
- (cond exists?
- (do
- (log/info (format "Showing page '%s' from file '%s'" page file-path))
- (layout/render "wiki.html"
- (merge (util/standard-params request)
- {:title page
- :page page
- :content (md->html
- (include/expand-include-md
- (:includer md-include-system)
- (slurp file-path)))
- :editable true})))
- true (response/redirect (str "/edit?page=" page))))))
+ (if exists?
+ (do
+ (log/info (format "Showing page '%s' from file '%s'" page file-path))
+ (let [processed-text (md->html
+ (include/expand-include-md
+ (:includer md-include-system)
+ (slurp file-path)))]
+ (layout/render "wiki.html"
+ (merge (util/standard-params request)
+ processed-text
+ {:title page
+ :scripts (collect-preferred processed-text :scripts)
+ :styles (collect-preferred processed-text :styles)
+ :page page
+ :editable true}))))
+ ;else
+ (response/redirect (str "/edit?page=" page))))))
(defn history-page
diff --git a/src/smeagol/util.clj b/src/smeagol/util.clj
index 0a5863a..972a0a7 100644
--- a/src/smeagol/util.clj
+++ b/src/smeagol/util.clj
@@ -56,8 +56,8 @@
{:user user
:admin (auth/get-admin user)
:js-from (:js-from config)
- :side-bar (md->html (slurp (cjio/file content-dir "_side-bar.md")))
- :header (md->html (slurp (cjio/file content-dir "_header.md")))
+ :side-bar (:content (md->html (slurp (cjio/file content-dir "_side-bar.md"))))
+ :header (:content (md->html (slurp (cjio/file content-dir "_header.md"))))
:version (System/getProperty "smeagol.version")}))
From 8032ad60af1de27bb8a273c38ce3a4c5f00aacfe Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Thu, 13 Feb 2020 19:46:58 +0000
Subject: [PATCH 65/83] No actual progress.
---
src/smeagol/extensions/photoswipe.clj | 5 ++--
src/smeagol/formatting.clj | 43 ++++++++-------------------
2 files changed, 15 insertions(+), 33 deletions(-)
diff --git a/src/smeagol/extensions/photoswipe.clj b/src/smeagol/extensions/photoswipe.clj
index 2df235e..ee80792 100644
--- a/src/smeagol/extensions/photoswipe.clj
+++ b/src/smeagol/extensions/photoswipe.clj
@@ -175,8 +175,6 @@
(defn process-photoswipe
[^String url-or-pswp-spec ^Integer index]
- (log/info "process-photoswipe called with arg1 `"
- url-or-pswp-spec "`; arg2 `" index "`.")
(let [data (resource-url-or-data->data url-or-pswp-spec)
spec (cs/trim (:data data))
result
@@ -184,5 +182,6 @@
(cs/starts-with? spec "![")
(process-simple-photoswipe spec index)
(process-full-photoswipe spec index))]
- (log/info "process-photoswipe returning `" result "`.")
+;; (log/info "process-photoswipe returning `" result "`.")
+ result
))
diff --git a/src/smeagol/formatting.clj b/src/smeagol/formatting.clj
index 115a2ca..3bb0d63 100644
--- a/src/smeagol/formatting.clj
+++ b/src/smeagol/formatting.clj
@@ -63,32 +63,9 @@
no-text-error))
-(defn yaml->json
- "Rewrite this string, assumed to be in YAML format, as JSON."
- [^String yaml-src]
- (json/write-str (yaml/parse-string yaml-src)))
-
-
(declare process-text)
-(defn process-vega
- "Process this `vega-src` string, assumed to be in YAML format, into a specification
- of a Vega chart, and add the plumbing to render it."
- [^String vega-src ^Integer index]
- (str
- "
\n"
- ""))
-
-
(defn process-backticks
"Effectively, escape the backticks surrounding this `text`, by protecting them
from the `md->html` filter."
@@ -117,7 +94,7 @@
(cons fragment processed)))
-(defn- apply-formatter
+(defn apply-formatter
"Within the context of `process-text`, process a fragment for which an explicit
§formatter has been identified.
@@ -128,11 +105,14 @@
[index result fragments processed fragment token formatter]
(let
[kw (keyword (str "inclusion-" index))]
- (process-text
- (inc index)
- (assoc-in result [:inclusions kw] (apply formatter (list (subs fragment (count token)) index)))
- (rest fragments)
- (cons kw processed))))
+ (assoc-in
+ (process-text
+ (inc index)
+ result
+ (rest fragments)
+ (cons kw processed))
+ [:inclusions kw]
+ (apply formatter (list (subs fragment (count token)) index)))))
(defn process-text
@@ -182,8 +162,11 @@
fragment
first-token
formatter)]
+ ;; TODO: consistency: either these things are `extensions`, or
+ ;; they're `formatters`. I incline to the view that they're
+ ;; `:extensions`
(assoc-in result [:extensions kw] (-> config :formatters kw)))
- true
+ :else
;; Otherwise process the current fragment as markdown and recurse on
;; down the list
(process-markdown-fragment
From 37d850d30afaef02ecc440ba799e4874fb05e9a9 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Thu, 13 Feb 2020 21:07:36 +0000
Subject: [PATCH 66/83] Still no actual progress.
---
resources/config.edn | 29 +++++------
src/smeagol/extensions/photoswipe.clj | 8 +--
src/smeagol/extensions/test.clj | 10 ++++
src/smeagol/extensions/vega.clj | 2 +-
src/smeagol/formatting.clj | 70 ++++++++++++++++++---------
src/smeagol/util.clj | 36 +++++++-------
test/smeagol/test/formatting.clj | 10 +++-
7 files changed, 102 insertions(+), 63 deletions(-)
create mode 100644 src/smeagol/extensions/test.clj
diff --git a/resources/config.edn b/resources/config.edn
index 7c462b3..87d1361 100644
--- a/resources/config.edn
+++ b/resources/config.edn
@@ -32,7 +32,21 @@
:default-locale "en-GB" ;; default language used for messages
:formatters ;; formatters for processing markdown
;; extensions.
- {:vega {:formatter "smeagol.extensions.vega/process-vega"
+ {:backticks {:formatter "smeagol.formatting/process-backticks"
+ :scripts {}
+ :styles {}}
+ :mermaid {:formatter "smeagol.extensions.mermaid/process-mermaid"
+ :scripts {:core {:local "vendor/mermaid/dist/mermaid.js"}}
+ :styles {}}
+ :pswp {:formatter "smeagol.extensions.photoswipe/process-photoswipe"
+ :scripts {:core {:local "/vendor/node_modules/photoswipe/dist/photoswipe.min.js"
+ :remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"}
+ :ui {:local "/vendor/node_modules/photoswipe/dist/photoswipe-ui-default.min.js"
+ :remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"}}
+ :styles {:core {:local "/vendor/node_modules/photoswipe/dist/photoswipe.css"
+ :remote "/vendor/node_modules/photoswipe/dist/default-skin/default-skin.css"}}}
+ :test {:formatter "smeagol.extensions.test/process-test" }
+ :vega {:formatter "smeagol.extensions.vega/process-vega"
:scripts {:core {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega/5.9.1/vega.min.js"}
:lite {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-lite/4.1.1/vega-lite.min.js"}
:embed {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-embed/6.2.2/vega-embed.min.js"}
@@ -42,19 +56,6 @@
:lite {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-lite/4.1.1/vega-lite.min.js"}
:embed {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-embed/6.2.2/vega-embed.min.js"}
:styles {}}}
- :mermaid {:formatter "smeagol.extensions.mermaid/process-mermaid"
- :scripts {:core {:local "vendor/mermaid/dist/mermaid.js"}}
- :styles {}}
- :backticks {:formatter "smeagol.formatting/process-backticks"
- :scripts {}
- :styles {}}
- :pswp {:formatter "smeagol.extensions.photoswipe/process-photoswipe"
- :scripts {:core {:local "/vendor/node_modules/photoswipe/dist/photoswipe.min.js"
- :remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"}
- :ui {:local "/vendor/node_modules/photoswipe/dist/photoswipe-ui-default.min.js"
- :remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"}}
- :styles {:core {:local "/vendor/node_modules/photoswipe/dist/photoswipe.css"
- :remote "/vendor/node_modules/photoswipe/dist/default-skin/default-skin.css"}}}
}
:log-level :info ;; the minimum logging level; one of
;; :trace :debug :info :warn :error :fatal
diff --git a/src/smeagol/extensions/photoswipe.clj b/src/smeagol/extensions/photoswipe.clj
index ee80792..7b16a09 100644
--- a/src/smeagol/extensions/photoswipe.clj
+++ b/src/smeagol/extensions/photoswipe.clj
@@ -176,12 +176,8 @@
(defn process-photoswipe
[^String url-or-pswp-spec ^Integer index]
(let [data (resource-url-or-data->data url-or-pswp-spec)
- spec (cs/trim (:data data))
- result
+ spec (cs/trim (:data data))]
(if
(cs/starts-with? spec "![")
(process-simple-photoswipe spec index)
- (process-full-photoswipe spec index))]
-;; (log/info "process-photoswipe returning `" result "`.")
- result
- ))
+ (process-full-photoswipe spec index))))
diff --git a/src/smeagol/extensions/test.clj b/src/smeagol/extensions/test.clj
new file mode 100644
index 0000000..ffe5c1a
--- /dev/null
+++ b/src/smeagol/extensions/test.clj
@@ -0,0 +1,10 @@
+(ns ^{:doc "Very simple extension for testing the extension processing flow."
+ :author "Simon Brooke"}
+ smeagol.extensions.test)
+
+
+(def process-test-return-value "")
+
+(defn process-test
+ [^String fragment ^Integer index]
+ process-test-return-value)
diff --git a/src/smeagol/extensions/vega.clj b/src/smeagol/extensions/vega.clj
index 1b9e2de..49edea0 100644
--- a/src/smeagol/extensions/vega.clj
+++ b/src/smeagol/extensions/vega.clj
@@ -1,4 +1,4 @@
-(ns ^{:doc "Format Semagol's extended markdown format."
+(ns ^{:doc "Format vega/vis extensions to Semagol's extended markdown format."
:author "Simon Brooke"}
smeagol.extensions.vega
(:require [clojure.data.json :as json]
diff --git a/src/smeagol/formatting.clj b/src/smeagol/formatting.clj
index 3bb0d63..bbcaaa7 100644
--- a/src/smeagol/formatting.clj
+++ b/src/smeagol/formatting.clj
@@ -93,10 +93,20 @@
fragments
(cons fragment processed)))
+(defn deep-merge [v & vs]
+ "Cripped in its entirety from https://clojuredocs.org/clojure.core/merge."
+ (letfn [(rec-merge [v1 v2]
+ (if (and (map? v1) (map? v2))
+ (merge-with deep-merge v1 v2)
+ v2))]
+ (if (some identity vs)
+ (reduce #(rec-merge %1 %2) v vs)
+ (last vs))))
+
(defn apply-formatter
"Within the context of `process-text`, process a fragment for which an explicit
- §formatter has been identified.
+ `formatter` has been identified.
As with `process-text`, this function returns a map with two top-level keys:
`:inclusions`, a map of constructed keywords to inclusion specifications,
@@ -104,16 +114,28 @@
corresponding inclusion should be inserted."
[index result fragments processed fragment token formatter]
(let
- [kw (keyword (str "inclusion-" index))]
- (assoc-in
- (process-text
- (inc index)
+ [ident (keyword (str "inclusion-" index))]
+ (process-text
+ (inc index)
+ (deep-merge
result
- (rest fragments)
- (cons kw processed))
- [:inclusions kw]
- (apply formatter (list (subs fragment (count token)) index)))))
+ {:inclusions {ident (apply formatter (list (subs fragment (count token)) index))}
+ :extensions (cons (keyword token) (:extensions result))})
+ fragments
+ (cons ident processed))))
+(apply-formatter
+ 3
+ {:inclusions {}}
+ '()
+ '()
+ "pswp
+ 
+ 
+ 
+ "
+ "pswp"
+ smeagol.extensions.photoswipe/process-photoswipe)
(defn process-text
"Process this `text`, assumed to be markdown potentially containing both local links
@@ -124,7 +146,7 @@
inclusion specifications, and `:text`, an HTML text string with the keywords
present where the corresponding inclusion should be inserted."
([^String text]
- (process-text 0 {:inclusions {}} (cs/split (or text "") #"```") '()))
+ (process-text 0 {} (cs/split (or text "") #"```") '()))
([index result fragments processed]
(let [fragment (first fragments)
;; if I didn't find a formatter for a back-tick marked fragment,
@@ -154,24 +176,28 @@
formatter
;; We've found a formatter to apply to the current fragment, and recurse
;; on down the list
- (let [result (apply-formatter
- index
- result
- fragments
- processed
- fragment
- first-token
- formatter)]
- ;; TODO: consistency: either these things are `extensions`, or
- ;; they're `formatters`. I incline to the view that they're
- ;; `:extensions`
- (assoc-in result [:extensions kw] (-> config :formatters kw)))
+ (let
+ [ident (keyword (str "inclusion-" index))]
+ (deep-merge
+ (process-text
+ (inc index)
+ result
+ (rest fragments)
+ (cons ident processed))
+ {:inclusions {ident (apply formatter (list (subs fragment (count first-token)) index))}
+ :extensions (cons kw (:extensions result))}))
:else
;; Otherwise process the current fragment as markdown and recurse on
;; down the list
(process-markdown-fragment
index result remarked (rest fragments) processed)))))
+(process-text
+ "pswp
+ 
+ 
+ 
+ " )
(defn reintegrate-inclusions
"Given a map of the form produced by `process-text`, return a string of HTML text
diff --git a/src/smeagol/util.clj b/src/smeagol/util.clj
index 972a0a7..9e0a6ba 100644
--- a/src/smeagol/util.clj
+++ b/src/smeagol/util.clj
@@ -61,33 +61,31 @@
:version (System/getProperty "smeagol.version")}))
-(defn- raw-get-messages
+(def get-messages
"Return the most acceptable messages collection we have given the
`Accept-Language` header in this `request`."
- [request]
- (let [specifier ((:headers request) "accept-language")
- messages (try
- (i18n/get-messages specifier "i18n" "en-GB")
- (catch Exception any
- (log/error
- any
- (str
- "Failed to parse accept-language header '"
- specifier
- "'"))
- {}))]
+ (memoize
+ (fn [request]
+ (let [specifier ((:headers request) "accept-language")
+ messages (try
+ (i18n/get-messages specifier "i18n" "en-GB")
+ (catch Exception any
+ (log/error
+ any
+ (str
+ "Failed to parse accept-language header '"
+ specifier
+ "'"))
+ {}))]
(merge
messages
- config)))
-
-
-(def get-messages (memoize raw-get-messages))
+ config)))))
(defn get-message
"Return the message with this `message-key` from this `request`.
- if not found, return this `default`, if provided; else return the
- `message-key`."
+ if not found, return this `default`, if provided; else return the
+ `message-key`."
([message-key request]
(get-message message-key message-key request))
([message-key default request]
diff --git a/test/smeagol/test/formatting.clj b/test/smeagol/test/formatting.clj
index 2887047..9cb4956 100644
--- a/test/smeagol/test/formatting.clj
+++ b/test/smeagol/test/formatting.clj
@@ -1,6 +1,7 @@
(ns smeagol.test.formatting
(:require [clojure.test :refer :all]
- [smeagol.formatting :refer [local-links no-text-error]]))
+ [smeagol.formatting :refer [local-links no-text-error]]
+ [smeagol.extensions.test :refer :all]))
(deftest test-local-links
(testing "Rewriting of local links"
@@ -10,3 +11,10 @@
(let [text (str "# This is a heading"
"[This is a foreign link](http://to.somewhere)")]
(is (= (local-links text) text) "Foreign links should be unchanged"))))
+
+(deftest test-process-text
+ (testing "The process-text flow"
+ (let [expected process-test-return-value
+ actual (process-text "```test
+ ```")]
+ (is (= actual expected)))))
From 0417fda9107234ff9aa04e3394403a0b0612638b Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Fri, 14 Feb 2020 08:51:14 +0000
Subject: [PATCH 67/83] Added TODO note, only.
---
src/smeagol/formatting.clj | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/smeagol/formatting.clj b/src/smeagol/formatting.clj
index fa4f2f6..750cf02 100644
--- a/src/smeagol/formatting.clj
+++ b/src/smeagol/formatting.clj
@@ -43,6 +43,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Error to show if text to be rendered is nil.
+;; TODO: should go through i18n
(def no-text-error "No text: does the file exist?")
@@ -187,3 +188,4 @@
(reintegrate-inclusions (process-text md-src)))
+
From 2e106256f85d605a6360f45c179f9a9ae936ddb1 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Fri, 14 Feb 2020 11:43:07 +0000
Subject: [PATCH 68/83] More Ginny stylesheet
---
resources/public/content/stylesheet.css | 101 ++++++++++++++++--------
1 file changed, 67 insertions(+), 34 deletions(-)
diff --git a/resources/public/content/stylesheet.css b/resources/public/content/stylesheet.css
index 81a661c..62690e0 100644
--- a/resources/public/content/stylesheet.css
+++ b/resources/public/content/stylesheet.css
@@ -24,6 +24,16 @@
## html elements generally in alphabetic order
*/
+a {
+ color: darkgray;
+ font-weight: bold;
+}
+
+a:hover {
+ color: darkgray;
+ background:rgba(200,200,200,0.8);
+}
+
body {
margin: 0;
padding: 0;
@@ -52,14 +62,15 @@ dd {
/* footer of the page - not-editable, provided by Smeagol */
footer {
- border-top: thin solid gray;
+ border-top: thin solid silver;
+ color: gray;
+ background:rgba(200,200,200,0.8);
clear: both;
font-size: smaller;
text-align: center;
- color: gray;
- background: rgba(224,224,224,0.95);
width: 100%;
margin: 0;
+ min-height: 4px;
padding: 0.25em 0;
bottom:0;
position:fixed;
@@ -71,9 +82,14 @@ footer {
}
footer div {
+ display: none;
padding: 0.1em;
}
+footer:hover div {
+ display: block;
+}
+
form {
border: thin solid silver;
}
@@ -83,8 +99,6 @@ header {
margin-top: 0;
width:100%;
max-width: 100%;
- background-color: gray;
- color: white;
}
header h1 {
@@ -93,7 +107,6 @@ header h1 {
header a {
font-weight: bold;
- color: white;
}
header a:hover {
@@ -131,12 +144,12 @@ ins {
label {
width: 20%;
min-width: 20em;
- border-right: thin solid gray;
+ border-right: thin solid silver;
display: inline-block;
}
table {
- border: 2px solid black;
+ border: thin solid silver;
border-collapse: collapse;
}
@@ -148,7 +161,7 @@ th, td {
text-align: left;
vertical-align: top;
padding: 0.15em 1.5em;
- border: 1px solid gray;
+ border: 1px solid silver;
}
th {
@@ -166,6 +179,7 @@ th {
/* left bar for all pages in the Wiki - editable, provided by users. Within main-container */
#side-bar {
+ display: none;
width: 17%;
height: 100%;
float: left;
@@ -173,10 +187,10 @@ th {
/* cookies information box, fixed, in right margin, just above footer */
#cookies {
- width: 30%;
+ width: 20%;
float: right;
position: fixed;
- bottom: 3.5em;
+ bottom: 8px;
right: 0;
z-index: 175;
background: transparent;
@@ -190,8 +204,8 @@ th {
text-align: right;
padding: 0.25em 2em;
border-radius: 0.25em;
- color: white;
- background:rgba(40,40,40,0.8);
+ color: gray;
+ background:rgba(200,200,200,0.8);
}
/* more-about-cookies box, normally hidden */
@@ -199,9 +213,9 @@ th {
display: none;
padding: 0.5em 2em;
border-radius: 0.5em;
- color: white;
- background:rgba(40,40,40,0.8);
- border-bottom: thin solid white;
+ color: gray;
+ background:rgba(200,200,200,0.8);
+ border-bottom: thin solid gray;
}
/* but magically appears on mouseover */
@@ -242,8 +256,8 @@ th {
right: 0;
padding: 0.25em 2em;
border-radius: 0.25em;
- color: white;
- background:rgba(40,40,40,0.8);
+ color: gray;
+ background:rgba(200,200,200,0.8);
font-size: 66%;
}
@@ -254,7 +268,11 @@ th {
.minor-controls a {
float: right;
padding: 0.25em 2em;
- color: white;
+ color: gray;
+}
+
+.minor-controls a:hover {
+ color: darkgray;
}
.pseudo-input {
@@ -303,8 +321,7 @@ th {
/* content of the current page in the Wiki - editable, provided by users. Within main-container */
#content {
border: thin solid silver;
- width: 80%;
- float: right;
+ width: 100%;
padding-bottom: 5em;
}
@@ -312,16 +329,29 @@ th {
display: none;
}
+ #header {
+ font-size: smaller;
+ }
+
/* top-of-page navigation, not editable, provided by Smeagol */
#nav{
margin: 0;
padding: 0;
top: 0;
- width: 100%;
+ min-height: 4px;
_position: absolute;
_top: expression(document.documentElement.scrollTop);
z-index: 149;
- background:rgba(40,40,40,0.8);
+ color: gray;
+ background:rgba(200,200,200,0.8);
+ }
+
+ #nav #nav-menu {
+ display: none;
+ }
+
+ #nav:hover #nav-menu {
+ display: block;
}
/* only needed for fly-out menu effect on tablet and phone stylesheets */
@@ -341,14 +371,14 @@ th {
}
#nav menu li a {
- color: white;
+ color: gray;
text-decoration: none;
font-weight: bold;
padding: 0.1em 0.75em;
margin: 0;
}
- #nav menu li.active a { background: gray;}
+ #nav menu li.active a { background: gray; color: white;}
li.nav-item a:hover { background: rgb( 240, 240, 240) }
li.nav-item a:active { background: gray; color: white; }
@@ -379,17 +409,15 @@ th {
padding: 0;
position: fixed;
z-index: 149;
- color: silver;
- background:rgba(40,40,40,0.9);
+ color: black;
+ background:rgba(200,200,200,0.9);
}
#nav a {
- color: white;
- text-decoration: none;
font-weight: bold;
}
- #nav:hover #nav-menu {
+ #nav:hover #nav-menu, #nav:hover #phone-side-bar {
display: block;
list-style-type: none;
width: 100%;
@@ -455,18 +483,21 @@ th {
display: none;
}
+ #header {
+ display: none;
+ }
+
#nav{
margin: 0;
padding: 0;
position: fixed;
z-index: 149;
- color: silver;
- background:rgba(40,40,40,0.9);
+ color: black;
+ background:rgba(200,200,200,0.9);
}
#nav a {
- color: white;
- text-decoration: none;
+ color: black;
font-weight: bold;
}
@@ -491,6 +522,8 @@ th {
}
#nav menu li a {
+ color: black;
+ font-weight: bold;
}
#nav ul li.active a { background: silver;}
From c06fce3007166b790e83ff775a8526004857bef7 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Fri, 14 Feb 2020 11:43:50 +0000
Subject: [PATCH 69/83] Bugfix: looking in the wrong directory for uploads.
---
resources/templates/base.html | 4 +++-
src/smeagol/routes/wiki.clj | 14 +++++++++++---
2 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/resources/templates/base.html b/resources/templates/base.html
index 7a31f07..01152af 100644
--- a/resources/templates/base.html
+++ b/resources/templates/base.html
@@ -46,7 +46,9 @@
{% i18n site-title %}: {{title}}
- {{header|safe}}
+
{% if message %}
{{message}}
diff --git a/src/smeagol/routes/wiki.clj b/src/smeagol/routes/wiki.clj
index e33cbdc..c87dbef 100644
--- a/src/smeagol/routes/wiki.clj
+++ b/src/smeagol/routes/wiki.clj
@@ -15,6 +15,7 @@
[noir.util.route :as route]
[noir.session :as session]
[smeagol.authenticate :as auth]
+ [smeagol.configuration :refer [config]]
[smeagol.diff2html :as d2h]
[smeagol.formatting :refer [md->html]]
[smeagol.history :as hist]
@@ -187,7 +188,6 @@
[request]
(let
[params (keywordize-keys (:params request))
- data-path (str util/content-dir "/uploads/")
cl (count (io/resource-path))
files
(map
@@ -202,11 +202,19 @@
(fs/mod-time %)
(format-instant (fs/mod-time %)))
(fs/name %)
- (subs (str (fs/absolute %)) cl)])
+ (try
+ (subs (str (fs/absolute %)) cl)
+ (catch StringIndexOutOfBoundsException x
+ (log/error "Could not resolve relative path for" %
+ ";\n resource-path is:" (io/resource-path)
+ ";\n absolute path is:" (fs/absolute %)
+ ";\n data-path is:" util/upload-dir
+ ";\n content path is:" (:content-dir config))
+ %))])
(remove
#(or (cs/starts-with? (fs/name %) ".")
(fs/directory? %))
- (file-seq (clojure.java.io/file data-path))))]
+ (file-seq (clojure.java.io/file util/upload-dir))))]
(log/info (with-out-str (pprint files)))
(layout/render
"list-uploads.html"
From 392a5f82ecbc6f824e4efee9ff4eeb5fca3540f2 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Fri, 14 Feb 2020 16:08:17 +0000
Subject: [PATCH 70/83] These are the genuine improvements out of today
1. Table sorting
2. Fixed edit page title bugette
3. SimpleMDE working again.
---
resources/i18n/en-GB.edn | 2 ++
resources/templates/edit-users.html | 23 ++++++++----
resources/templates/edit.html | 6 ++--
resources/templates/list-uploads.html | 19 +++++++---
src/smeagol/routes/wiki.clj | 51 ++++++++++++---------------
src/smeagol/util.clj | 50 ++++++++++++++++++++++++--
6 files changed, 107 insertions(+), 44 deletions(-)
diff --git a/resources/i18n/en-GB.edn b/resources/i18n/en-GB.edn
index 6f4d8b2..442f097 100644
--- a/resources/i18n/en-GB.edn
+++ b/resources/i18n/en-GB.edn
@@ -131,6 +131,8 @@
"Smeagol has been unable to find some of the resources on which it depends,
possibly because of misconfiguration or missing environment variables."
;; used in sanity check report
+ :sortable "You can sort this table by selecting column headers"
+ ;; used for sortable tables
:user-lacks-field "User record in the passwd file lacks a field"
;; used in sanity check report
:username-prompt "Username" ;; text of the username widget prompt on edit user page
diff --git a/resources/templates/edit-users.html b/resources/templates/edit-users.html
index 2621576..06d0d2e 100644
--- a/resources/templates/edit-users.html
+++ b/resources/templates/edit-users.html
@@ -1,10 +1,18 @@
{% extends "templates/base.html" %}
+{% block extra-headers %}
+ {% script "/vendor/node_modules/tablesort/dist/tablesort.min.js" %}
+{% endblock %}
{% block content %}
-
-
- {% i18n edit-col-hdr %} {% i18n del-col-hdr %}
+
+ {% i18n sortable %}
+
+
+
{% endblock %}
diff --git a/resources/templates/edit.html b/resources/templates/edit.html
index 36426dc..44ee54a 100644
--- a/resources/templates/edit.html
+++ b/resources/templates/edit.html
@@ -1,11 +1,11 @@
{% extends "templates/base.html" %}
{% block extra-headers %}
- {% ifequal js-from ":cloudflare" %}
+ {% ifequal js-from ":cdnjs" %}
{% else %}
- {% style "/vendor/simplemde/dist/simplemde.min.css" %}
- {% script "/vendor/simplemde/dist/simplemde.min.js" %}
+ {% style "vendor/simplemde/dist/simplemde.min.css" %}
+ {% script "vendor/simplemde/dist/simplemde.min.js" %}
{% endifequal %}
{% endblock %}
diff --git a/resources/templates/list-uploads.html b/resources/templates/list-uploads.html
index e759dac..4289485 100644
--- a/resources/templates/list-uploads.html
+++ b/resources/templates/list-uploads.html
@@ -1,4 +1,10 @@
{% extends "templates/base.html" %}
+{% block extra-headers %}
+ {% script "/vendor/node_modules/tablesort/dist/tablesort.min.js" %}
+ {% script "/vendor/node_modules/tablesort/dist/sorts/tablesort.number.min.js" %}
+ {% script "/vendor/node_modules/tablesort/dist/sorts/tablesort.date.min.js" %}
+ {% script "/vendor/node_modules/tablesort/dist/sorts/tablesort.monthname.min.js" %}
+{% endblock %}
{% block content %}
@@ -9,12 +15,15 @@
-
-
+
+ {% i18n sortable %}
+
+
+
Name
Uploaded
Type this
- To get this
+ To get this
{% for entry in files %}
@@ -26,9 +35,11 @@
{% if entry.is-image %} {% else %} link {% endif %}
-
{% endfor %}
+
{% endblock %}
diff --git a/src/smeagol/routes/wiki.clj b/src/smeagol/routes/wiki.clj
index c87dbef..104b6e0 100644
--- a/src/smeagol/routes/wiki.clj
+++ b/src/smeagol/routes/wiki.clj
@@ -161,7 +161,8 @@
(log/info (format "Showing history of page '%s'" page))
(layout/render "history.html"
(merge (util/standard-params request)
- {:title (util/get-message :history-title-prefix request)
+ {:title (str (util/get-message :history-title-prefix request)
+ " " page)
:page page
:history (hist/find-history repo-path file-name)}))))
@@ -175,7 +176,7 @@
If `template` is supplied, use that as the formatting template as specified for
java.time.Formatter. Assumes system default timezone. Returns a string."
([^Long unix-time]
- (format-instant unix-time "EEEE, dd MMMM YYYY"))
+ (format-instant unix-time "dd MMMM YYYY"))
([^Long unix-time ^String template]
(jt/format
(java-time/formatter template)
@@ -188,33 +189,26 @@
[request]
(let
[params (keywordize-keys (:params request))
- cl (count (io/resource-path))
files
- (map
- #(zipmap
- [:base-name :is-image :modified :name :resource]
- [(fs/base-name %)
- (if
- (and (fs/extension %)
- (image-extns (cs/lower-case (fs/extension %))))
- true false)
- (if
- (fs/mod-time %)
- (format-instant (fs/mod-time %)))
- (fs/name %)
- (try
- (subs (str (fs/absolute %)) cl)
- (catch StringIndexOutOfBoundsException x
- (log/error "Could not resolve relative path for" %
- ";\n resource-path is:" (io/resource-path)
- ";\n absolute path is:" (fs/absolute %)
- ";\n data-path is:" util/upload-dir
- ";\n content path is:" (:content-dir config))
- %))])
- (remove
- #(or (cs/starts-with? (fs/name %) ".")
- (fs/directory? %))
- (file-seq (clojure.java.io/file util/upload-dir))))]
+ (sort-by
+ (juxt :name (fn [x] (- 0 (count (:resource x)))))
+ (map
+ #(zipmap
+ [:base-name :is-image :modified :name :resource]
+ [(fs/base-name %)
+ (if
+ (and (fs/extension %)
+ (image-extns (cs/lower-case (fs/extension %))))
+ true false)
+ (if
+ (fs/mod-time %)
+ (format-instant (fs/mod-time %)))
+ (fs/name %)
+ (util/local-url %)])
+ (remove
+ #(or (cs/starts-with? (fs/name %) ".")
+ (fs/directory? %))
+ (file-seq (clojure.java.io/file util/upload-dir)))))]
(log/info (with-out-str (pprint files)))
(layout/render
"list-uploads.html"
@@ -236,6 +230,7 @@
files)
}))))
+
;;;; end of list-uploads section ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn upload-page
diff --git a/src/smeagol/util.clj b/src/smeagol/util.clj
index 1ef44df..d33ee32 100644
--- a/src/smeagol/util.clj
+++ b/src/smeagol/util.clj
@@ -2,6 +2,7 @@
:author "Simon Brooke"}
smeagol.util
(:require [clojure.java.io :as cjio]
+ [clojure.string :as cs]
[environ.core :refer [env]]
[me.raynes.fs :as fs]
[noir.io :as io]
@@ -10,7 +11,7 @@
[smeagol.authenticate :as auth]
[smeagol.configuration :refer [config]]
[smeagol.formatting :refer [md->html]]
- [taoensso.timbre :as timbre]))
+ [taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
@@ -49,6 +50,51 @@
(def upload-dir
(str (cjio/file content-dir "uploads")))
+(def local-url-base
+ (let [a (str (fs/absolute content-dir))]
+ (subs a 0 (- (count a) (count "content")))))
+
+(defn not-servable-reason
+ "As a string, the reason this `file-path` cannot safely be served, or `nil`
+ if it is safe to serve. This reason may be logged, but should *not* be
+ shown to remote users, as it would allow file system probing."
+ [file-path]
+ (let [path (fs/absolute file-path)]
+ (cond
+ (cs/includes? file-path "..")
+ (cs/join " " file-path
+ "Attempts to ascend the file hierarchy are disallowed.")
+ (not (cs/starts-with? path local-url-base))
+ (cs/join " " [path "is not servable"])
+ (not (fs/exists? path))
+ (cs/join " " [path "does not exist"])
+ (not (fs/readable? path))
+ (cs/join " " [path "is not readable"]))))
+
+(defn local-url?
+ "True if this `file-path` can be served as a local URL, else false."
+ [file-path]
+ (empty? (not-servable-reason file-path)))
+
+(defn local-url
+ "Return a local URL for this `file-path`, or a deliberate 404 if none
+ can be safely served."
+ [file-path]
+ (try
+ (let [path (fs/absolute file-path)
+ problem (not-servable-reason path)]
+ (if
+ (empty? problem)
+ (subs (str path) (count local-url-base))
+ (do
+ (log/error
+ "In `smeagol.util/local-url `" file-path "` is not a servable resource.")
+ (str "404-not-found?path=" file-path))))
+ (catch Exception any
+ (log/error
+ "In `smeagol.util/local-url `" file-path "` is not a servable resource:" any)
+ (str "404-not-found?path=" file-path))))
+
(defn standard-params
"Return a map of standard parameters to pass to the template renderer."
[request]
@@ -69,7 +115,7 @@
messages (try
(i18n/get-messages specifier "i18n" "en-GB")
(catch Exception any
- (timbre/error
+ (log/error
any
(str
"Failed to parse accept-language header '"
From 151987e598b5ffc6c2ee997110d0f5259511f73b Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Sun, 16 Feb 2020 13:51:41 +0000
Subject: [PATCH 71/83] #45,#46: done
---
resources/config.edn | 30 +++++++++-------
resources/templates/wiki.html | 6 ++--
src/smeagol/formatting.clj | 42 ++++++++++++----------
src/smeagol/routes/wiki.clj | 68 +++++++++++++++++++++++++----------
src/smeagol/util.clj | 48 ++++++++++++++++++++-----
5 files changed, 131 insertions(+), 63 deletions(-)
diff --git a/resources/config.edn b/resources/config.edn
index 87d1361..ecb1ea0 100644
--- a/resources/config.edn
+++ b/resources/config.edn
@@ -30,37 +30,35 @@
:content-dir "resources/public/content"
;; where content is served from.
:default-locale "en-GB" ;; default language used for messages
+ :extensions-from :local ;; where to load JavaScript libraries
+ ;; from: options are :local, :remote.
:formatters ;; formatters for processing markdown
;; extensions.
{:backticks {:formatter "smeagol.formatting/process-backticks"
:scripts {}
:styles {}}
:mermaid {:formatter "smeagol.extensions.mermaid/process-mermaid"
- :scripts {:core {:local "vendor/mermaid/dist/mermaid.js"}}
+ :scripts {:core {:local "vendor/mermaid/dist/mermaid.js"
+ :remote "https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.4.6/mermaid.min.js"}}
:styles {}}
:pswp {:formatter "smeagol.extensions.photoswipe/process-photoswipe"
- :scripts {:core {:local "/vendor/node_modules/photoswipe/dist/photoswipe.min.js"
+ :scripts {:core {:local "vendor/node_modules/photoswipe/dist/photoswipe.min.js"
:remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"}
- :ui {:local "/vendor/node_modules/photoswipe/dist/photoswipe-ui-default.min.js"
+ :ui {:local "vendor/node_modules/photoswipe/dist/photoswipe-ui-default.min.js"
:remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"}}
- :styles {:core {:local "/vendor/node_modules/photoswipe/dist/photoswipe.css"
- :remote "/vendor/node_modules/photoswipe/dist/default-skin/default-skin.css"}}}
+ :styles {:core {:local "vendor/node_modules/photoswipe/dist/photoswipe.css"}
+ :skin {:local "vendor/node_modules/photoswipe/dist/default-skin/default-skin.css"}}}
:test {:formatter "smeagol.extensions.test/process-test" }
:vega {:formatter "smeagol.extensions.vega/process-vega"
:scripts {:core {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega/5.9.1/vega.min.js"}
:lite {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-lite/4.1.1/vega-lite.min.js"}
- :embed {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-embed/6.2.2/vega-embed.min.js"}
- :styles {}}}
+ :embed {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-embed/6.2.2/vega-embed.min.js"}}}
:vis {:formatter "smeagol.extensions.vega/process-vega"
:scripts {:core {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega/5.9.1/vega.min.js"}
:lite {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-lite/4.1.1/vega-lite.min.js"}
- :embed {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-embed/6.2.2/vega-embed.min.js"}
- :styles {}}}
- }
+ :embed {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-embed/6.2.2/vega-embed.min.js"}}}}
:log-level :info ;; the minimum logging level; one of
;; :trace :debug :info :warn :error :fatal
- :js-from :cdnjs ;; where to load JavaScript libraries
- ;; from: options are :local, :cdnjs
:passwd "resources/passwd"
;; where the password file is stored
:site-title "Smeagol" ;; overall title of the site, used in
@@ -71,4 +69,10 @@
;; stored in the /small directory
:med 400 ;; maximum dimension of thumbnails
;; stored in the /med directory
- }}
+ ;; you can add as many extra keys and values as
+ ;; you like here for additional sizes of images.
+ ;; Images will only be scaled if their maximum
+ ;; dimension (in pixels) is greater than the value;
+ ;; only JPEG and PNG images will be scaled.
+ }
+ }
diff --git a/resources/templates/wiki.html b/resources/templates/wiki.html
index 7342854..81634ea 100644
--- a/resources/templates/wiki.html
+++ b/resources/templates/wiki.html
@@ -2,11 +2,9 @@
{% block extra-headers %}
{% for script in scripts %}
-
- {% endfor %}
+ {% endfor %}
{% for style in styles %}
-
- {% endfor %}
+ {% endfor %}
{% endblock %}
{% block content %}
diff --git a/src/smeagol/formatting.clj b/src/smeagol/formatting.clj
index 153c904..5891eeb 100644
--- a/src/smeagol/formatting.clj
+++ b/src/smeagol/formatting.clj
@@ -93,6 +93,7 @@
fragments
(cons fragment processed)))
+
(defn deep-merge [v & vs]
"Cripped in its entirety from https://clojuredocs.org/clojure.core/merge."
(letfn [(rec-merge [v1 v2]
@@ -124,18 +125,18 @@
fragments
(cons ident processed))))
-(apply-formatter
- 3
- {:inclusions {}}
- '()
- '()
- "pswp
- 
- 
- 
- "
- "pswp"
- smeagol.extensions.photoswipe/process-photoswipe)
+;; (apply-formatter
+;; 3
+;; {:inclusions {}}
+;; '()
+;; '()
+;; "pswp
+;; 
+;; 
+;; 
+;; "
+;; "pswp"
+;; smeagol.extensions.photoswipe/process-photoswipe)
(defn process-text
"Process this `text`, assumed to be markdown potentially containing both local links
@@ -145,6 +146,8 @@
The map has two top-level keys: `:inclusions`, a map of constructed keywords to
inclusion specifications, and `:text`, an HTML text string with the keywords
present where the corresponding inclusion should be inserted."
+
+ ;; TODO: the inclusion->index bug is in here somewhere.
([^String text]
(process-text 0 {} (cs/split (or text "") #"```") '()))
([index result fragments processed]
@@ -185,19 +188,19 @@
(rest fragments)
(cons ident processed))
{:inclusions {ident (apply formatter (list (subs fragment (count first-token)) index))}
- :extensions (cons kw (:extensions result))}))
+ :extensions (assoc (or (:extensions result) {}) kw (-> config :formatters kw))}))
:else
;; Otherwise process the current fragment as markdown and recurse on
;; down the list
(process-markdown-fragment
index result remarked (rest fragments) processed)))))
-(process-text
- "pswp
- 
- 
- 
- " )
+;; (process-text
+;; "pswp
+;; 
+;; 
+;; 
+;; " )
(defn reintegrate-inclusions
"Given a map of the form produced by `process-text`, return a string of HTML text
@@ -236,3 +239,4 @@
+
diff --git a/src/smeagol/routes/wiki.clj b/src/smeagol/routes/wiki.clj
index 0fa7cac..a3456c1 100644
--- a/src/smeagol/routes/wiki.clj
+++ b/src/smeagol/routes/wiki.clj
@@ -28,7 +28,8 @@
[com.stuartsierra.component :as component]
[smeagol.configuration :refer [config]]
[smeagol.include.resolve-local-file :as resolve]
- [smeagol.include :as include]))
+ [smeagol.include :as include]
+ [smeagol.util :refer [content-dir local-url]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
@@ -118,6 +119,9 @@
(def md-include-system
+ "Allowing Markdown includes. Unfortunately the contributor who contributed
+ this didn't document it, and I haven't yet worked out how it works. TODO:
+ investigate and document."
(component/start
(component/system-map
:resolver (resolve/new-resolver util/content-dir)
@@ -125,6 +129,7 @@
(include/new-includer)
[:resolver]))))
+
(defn preferred-source
"Here, `component` is expected to be a map with two keys, `:local` and
`:remote`. If the value of `:extensions-from` in `config.edn` is remote
@@ -132,26 +137,51 @@
be returned. Otherwise, if the value of `:local` is nil and the value of
`:remote` is non-nil, the value of `:remote` will be returned. By default,
the value of `:local` will be returned."
- [component]
- (let [l (:local component) ;; TODO: look at the trick in Selmer to get relative URL
- r (:remote component)]
- (cond
- (= (:extensions-from config) :remote) (if (empty? r) l r)
- (empty? l) r
- :else l)))
+ [component ks]
+ (try
+ (let [l (:local component)
+ l' (if-not (empty? l) (local-url l) l)
+ r (:remote component)]
+ (cond
+ (= (:extensions-from config) :remote)
+ (if (empty? r) l' r)
+ (empty? l') r
+ :else l'))
+ (catch Exception any
+ (log/error "Failed to find appropriate source for component" ks "because:" any)
+ nil)))
+
+;; (preferred-source {:local "vendor/node_modules/photoswipe/dist/photoswipe.min.js",
+;; :remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"} :core)
(defn collect-preferred
- "From extensions referenced in this `processed-text`, extract the preferred
- URLs for this keyword `k`, expected to be either `:scripts` or `:styles`."
- [processed-text k]
- (set
- (remove
- nil?
- (map
- preferred-source
- (apply
- concat
- (map vals (map k (vals (:extensions processed-text)))))))))
+ ([processed-text]
+ (concat
+ (collect-preferred processed-text :scripts)
+ (collect-preferred processed-text :styles)))
+ ([processed-text resource-type]
+ (reduce concat
+ (map
+ (fn [extension-key]
+ (map
+ (fn [requirement]
+ (let [r (preferred-source
+ (-> processed-text :extensions extension-key resource-type requirement)
+ requirement)]
+ (if (empty? r)
+ (log/warn "Found no valid URL for requirement"
+ requirement "of extension" extension-key))
+ r))
+ (keys (-> processed-text :extensions extension-key resource-type))))
+ (keys (:extensions processed-text))))))
+
+(cjio/file content-dir "vendor/node_modules/photoswipe/dist/photoswipe.min.js")
+
+(def processed-text (md->html (slurp "resources/public/content/Simplified example gallery.md" )))
+
+(preferred-source (-> processed-text :extensions :pswp :scripts :core) :pswp)
+
+(collect-preferred processed-text :scripts)
(defn wiki-page
"Render the markdown page specified in this `request`, if any. If none found, redirect to edit-page"
diff --git a/src/smeagol/util.clj b/src/smeagol/util.clj
index 64e0a7c..e92bafe 100644
--- a/src/smeagol/util.clj
+++ b/src/smeagol/util.clj
@@ -51,6 +51,8 @@
(str (cjio/file content-dir "uploads")))
(def local-url-base
+ "Essentially, the slash-terminated absolute path of the `public` resource
+ directory."
(let [a (str (fs/absolute content-dir))]
(subs a 0 (- (count a) (count "content")))))
@@ -59,7 +61,11 @@
if it is safe to serve. This reason may be logged, but should *not* be
shown to remote users, as it would allow file system probing."
[file-path]
- (let [path (fs/absolute file-path)]
+ (try
+ (let [path (if
+ (cs/starts-with? (str file-path) "/")
+ file-path
+ (cjio/file local-url-base file-path))]
(cond
(cs/includes? file-path "..")
(cs/join " " file-path
@@ -69,31 +75,57 @@
(not (fs/exists? path))
(cs/join " " [path "does not exist"])
(not (fs/readable? path))
- (cs/join " " [path "is not readable"]))))
+ (cs/join " " [path "is not readable"])))
+ (catch Exception any (cs/join " " file-path "is not servable because" (.getMessage any)))))
+
+
+;; (not-servable-reason "/home/simon/workspace/smeagol/resources/public/content/vendor/node_modules/photoswipe/dist/photoswipe.min.js")
+;; (not-servable-reason "/root/froboz")
(defn local-url?
"True if this `file-path` can be served as a local URL, else false."
[file-path]
- (empty? (not-servable-reason file-path)))
+ (try
+ (if
+ (empty? (not-servable-reason file-path))
+ true
+ (do
+ (log/error
+ "In `smeagol.util/local-url? `" file-path "` is not a servable resource.")
+ false))
+ (catch Exception any
+ (log/error
+ "In `smeagol.util/local-url `" file-path "` is not a servable resource:" any)
+ false)))
(defn local-url
"Return a local URL for this `file-path`, or a deliberate 404 if none
can be safely served."
+ ;; TODO: this actually returns a relative URL relative to local-url-base.
+ ;; That's not quite what we want because in Tomcat contexts the absolute
+ ;; URL may be different. We *ought* to be able to extract the offset from the
+ ;; servlet context, but it may be simpler to jam it in the config.
[file-path]
(try
- (let [path (fs/absolute file-path)
+ (let [path (if
+ (cs/starts-with? file-path local-url-base)
+ (subs file-path (count local-url-base))
+ file-path)
problem (not-servable-reason path)]
(if
(empty? problem)
- (subs (str path) (count local-url-base))
+ path
(do
(log/error
"In `smeagol.util/local-url `" file-path "` is not a servable resource.")
(str "404-not-found?path=" file-path))))
(catch Exception any
- (log/error
- "In `smeagol.util/local-url `" file-path "` is not a servable resource:" any)
- (str "404-not-found?path=" file-path))))
+ (log/error
+ "In `smeagol.util/local-url `" file-path "` is not a servable resource:" any)
+ (str "404-not-found?path=" file-path))))
+
+(local-url? "vendor/node_modules/photoswipe/dist/photoswipe.min.js")
+(local-url? "/home/simon/workspace/smeagol/resources/public/vendor/node_modules/photoswipe/dist/photoswipe.min.js")
(defn standard-params
"Return a map of standard parameters to pass to the template renderer."
From 0f0f2ecc48beda4b898adafc29b04e4169ae1cae Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 19 Feb 2020 12:50:52 +0000
Subject: [PATCH 72/83] Re-separated apply-formatter. Still doesn't work.
(but doesn't blow up)
---
src/smeagol/formatting.clj | 31 +++++++------------------------
1 file changed, 7 insertions(+), 24 deletions(-)
diff --git a/src/smeagol/formatting.clj b/src/smeagol/formatting.clj
index 5891eeb..fb94602 100644
--- a/src/smeagol/formatting.clj
+++ b/src/smeagol/formatting.clj
@@ -115,15 +115,12 @@
corresponding inclusion should be inserted."
[index result fragments processed fragment token formatter]
(let
- [ident (keyword (str "inclusion-" index))]
+ [kw (keyword (str "inclusion-" index))]
(process-text
(inc index)
- (deep-merge
- result
- {:inclusions {ident (apply formatter (list (subs fragment (count token)) index))}
- :extensions (cons (keyword token) (:extensions result))})
- fragments
- (cons ident processed))))
+ (assoc-in result [:inclusions kw] (apply formatter (list (subs fragment (count token)) index)))
+ (rest fragments)
+ (cons kw processed))))
;; (apply-formatter
;; 3
@@ -177,23 +174,9 @@
(cs/join "\n\n" (reverse processed))
:heading-anchors true)))
formatter
- ;; We've found a formatter to apply to the current fragment, and recurse
- ;; on down the list
- (let
- [ident (keyword (str "inclusion-" index))]
- (deep-merge
- (process-text
- (inc index)
- result
- (rest fragments)
- (cons ident processed))
- {:inclusions {ident (apply formatter (list (subs fragment (count first-token)) index))}
- :extensions (assoc (or (:extensions result) {}) kw (-> config :formatters kw))}))
- :else
- ;; Otherwise process the current fragment as markdown and recurse on
- ;; down the list
- (process-markdown-fragment
- index result remarked (rest fragments) processed)))))
+ (apply-formatter index result fragments processed fragment first-token formatter)
+ true
+ (process-markdown-fragment index result remarked (rest fragments) processed)))))
;; (process-text
;; "pswp
From 0649ecf1958010f17a39fd31c46c95ea66814c7b Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 19 Feb 2020 15:01:09 +0000
Subject: [PATCH 73/83] Thank fuck, it works. Now to remove all the crud.
---
src/smeagol/formatting.clj | 163 +++++++++++++++++++------------
src/smeagol/routes/wiki.clj | 19 ++--
src/smeagol/util.clj | 12 ++-
test/smeagol/test/formatting.clj | 12 +--
4 files changed, 125 insertions(+), 81 deletions(-)
diff --git a/src/smeagol/formatting.clj b/src/smeagol/formatting.clj
index fb94602..cacc9ca 100644
--- a/src/smeagol/formatting.clj
+++ b/src/smeagol/formatting.clj
@@ -9,6 +9,7 @@
[smeagol.configuration :refer [config]]
[smeagol.extensions.mermaid :refer [process-mermaid]]
[smeagol.extensions.photoswipe :refer [process-photoswipe]]
+ [smeagol.extensions.vega :refer [process-vega]]
[taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -68,7 +69,7 @@
(defn process-backticks
"Effectively, escape the backticks surrounding this `text`, by protecting them
- from the `md->html` filter."
+ from the `process-text` filter."
[^String text ^Integer index]
(str "```" (.trim text) "\n``` "))
@@ -76,7 +77,9 @@
(defn get-first-token
"Return the first space-separated token of this `string`."
[^String string]
- (if string (first (cs/split string #"[^a-zA-Z0-9]+"))))
+ (try
+ (if string (first (cs/split (first (cs/split-lines string)) #"[^a-zA-Z0-9]+")))
+ (catch NullPointerException _ nil)))
(defn- process-markdown-fragment
@@ -86,7 +89,7 @@
`:inclusions`, a map of constructed keywords to inclusion specifications,
and `:text`, an HTML text string with the keywords present where the
corresponding inclusion should be inserted."
- [index result fragment fragments processed]
+ [^Integer index ^clojure.lang.Associative result ^String fragment fragments processed]
(process-text
(inc index)
result
@@ -113,14 +116,29 @@
`:inclusions`, a map of constructed keywords to inclusion specifications,
and `:text`, an HTML text string with the keywords present where the
corresponding inclusion should be inserted."
- [index result fragments processed fragment token formatter]
+ [^Integer index
+ ^clojure.lang.Associative result
+ fragments
+ processed
+ ^String fragment
+ ^String token
+ formatter]
+ (log/info "index:" index "(type result):" (type result) "(type fragments):" (type fragments) "fragment:" fragment "token:" token ":formatter" formatter)
(let
- [kw (keyword (str "inclusion-" index))]
+ [inky (keyword (str "inclusion-" index))
+ fkey (keyword token)]
(process-text
(inc index)
- (assoc-in result [:inclusions kw] (apply formatter (list (subs fragment (count token)) index)))
- (rest fragments)
- (cons kw processed))))
+ (deep-merge
+ result
+ {:inclusions {inky (eval (list formatter (subs fragment (count token)) index))}
+ :extensions {fkey (-> config :formatters fkey)}})
+;; (assoc-in
+;; (assoc-in result [:inclusions inky] (eval (list formatter (subs fragment (count token)) index)))
+;; [:extensions fkey] (-> config :formatters fkey))
+ (rest fragments)
+ (cons inky processed))))
+
;; (apply-formatter
;; 3
@@ -135,55 +153,16 @@
;; "pswp"
;; smeagol.extensions.photoswipe/process-photoswipe)
-(defn process-text
- "Process this `text`, assumed to be markdown potentially containing both local links
- and YAML visualisation specifications, and return a map comprising JSON visualisation
- specification, and HTML text with markers for where those should be reinserted.
+(defn reassemble-text
+ "Reassemble these processed strings into a complete text, and process it as
+ Markdown."
+ [result processed]
+ (assoc result :text
+ (local-links
+ (md/md-to-html-string
+ (cs/join "\n\n" (reverse processed))
+ :heading-anchors true))))
- The map has two top-level keys: `:inclusions`, a map of constructed keywords to
- inclusion specifications, and `:text`, an HTML text string with the keywords
- present where the corresponding inclusion should be inserted."
-
- ;; TODO: the inclusion->index bug is in here somewhere.
- ([^String text]
- (process-text 0 {} (cs/split (or text "") #"```") '()))
- ([index result fragments processed]
- (let [fragment (first fragments)
- ;; if I didn't find a formatter for a back-tick marked fragment,
- ;; I need to put the backticks back in.
- remarked (if (odd? index) (str "```" fragment "\n```") fragment)
- first-token (get-first-token fragment)
- kw (if-not (empty? first-token) (keyword first-token))
- formatter (if-not
- (empty? first-token)
- (try
- (read-string (-> config :formatters kw :formatter))
- (catch Exception _
- (do
- (log/info "No formatter found for extension `" kw "`")
- ;; no extension registered - there sometimes won't be,
- ;; and it doesn't matter
- nil))))]
- (cond
- (empty? fragments)
- ;; We've come to the end of the list of fragments. Reassemble them into
- ;; a single HTML text and pass it back.
- (assoc result :text
- (local-links
- (md/md-to-html-string
- (cs/join "\n\n" (reverse processed))
- :heading-anchors true)))
- formatter
- (apply-formatter index result fragments processed fragment first-token formatter)
- true
- (process-markdown-fragment index result remarked (rest fragments) processed)))))
-
-;; (process-text
-;; "pswp
-;; 
-;; 
-;; 
-;; " )
(defn reintegrate-inclusions
"Given a map of the form produced by `process-text`, return a string of HTML text
@@ -212,13 +191,73 @@
(cs/replace (kw inclusions) "\\/" "/"))))))))
+(defn process-text
+ [^Integer index ^clojure.lang.Associative result fragments processed]
+ (let [fragment (first fragments)
+ ;; if I didn't find a formatter for a back-tick marked fragment,
+ ;; I need to put the backticks back in.
+ remarked (if (odd? index) (str "```" fragment "\n```") fragment)
+ first-token (get-first-token fragment)
+ kw (if-not (empty? first-token) (keyword first-token))
+ formatter (if
+ kw
+ (try
+ (read-string (-> config :formatters kw :formatter))
+ (catch Exception _
+ (do
+ (log/info "No formatter found for extension `" kw "`")
+ ;; no extension registered - there sometimes won't be,
+ ;; and it doesn't matter
+ nil))))]
+ (cond
+ (empty? fragments)
+ ;; We've come to the end of the list of fragments. Reassemble them into
+ ;; a single HTML text and pass it back.
+ (reassemble-text result processed)
+ formatter
+ (apply-formatter index result fragments processed fragment first-token formatter)
+ true
+ (process-markdown-fragment index result remarked (rest fragments) processed))))
+
(defn md->html
- "Take this `md-src` markdown source, and return a map in which:
- 1. the key `:content` is bound to the equivalent HTML source;
- 2. the key `:extensions`. is bound to details of the extensions
- used."
- [md-src]
- (reintegrate-inclusions (process-text md-src)))
+ "Process this `text`, assumed to be markdown potentially containing both local links
+ and YAML visualisation specifications, and return a map comprising JSON visualisation
+ specification, and HTML text with markers for where those should be reinserted.
+
+ The map has two top-level keys: `:inclusions`, a map of constructed keywords to
+ inclusion specifications, and `:text`, an HTML text string with the keywords
+ present where the corresponding inclusion should be inserted."
+ [^clojure.lang.Associative context]
+ (reintegrate-inclusions
+ (process-text
+ 0
+ (assoc context :extensions #{})
+ (cs/split (or (:source context) "") #"```")
+ '())))
+
+
+;; (def first-token "pswp")
+;; (def kw (keyword "pswp"))
+;; (def fragment "pswp
+;; 
+;; 
+;; 
+;; ")
+;; (def index 0)
+;; (def formatter (read-string (-> config :formatters kw :formatter)))
+;; formatter
+;; (eval (list formatter (subs fragment (count first-token)) index))
+;; (process-photoswipe (subs fragment (count first-token)) index)
+
+;; (process-text
+;; {:source "pswp
+;; 
+;; 
+;; 
+;; "} )
+
+;; (process-text {:source (slurp (clojure.java.io/file smeagol.util/content-dir "Extensible Markup.md"))})
+
diff --git a/src/smeagol/routes/wiki.clj b/src/smeagol/routes/wiki.clj
index a3456c1..a5d4fde 100644
--- a/src/smeagol/routes/wiki.clj
+++ b/src/smeagol/routes/wiki.clj
@@ -107,7 +107,7 @@
(merge (util/standard-params request)
{:title (str (util/get-message :edit-title-prefix request) " " page)
:page page
- :side-bar (md->html (slurp (cjio/file util/content-dir side-bar)))
+ :side-bar (md->html (assoc request :source (slurp (cjio/file util/content-dir side-bar))))
:content (if exists? (slurp file-path) "")
:exists exists?})))))))
@@ -175,13 +175,15 @@
(keys (-> processed-text :extensions extension-key resource-type))))
(keys (:extensions processed-text))))))
-(cjio/file content-dir "vendor/node_modules/photoswipe/dist/photoswipe.min.js")
+;; (cjio/file content-dir "vendor/node_modules/photoswipe/dist/photoswipe.min.js")
-(def processed-text (md->html (slurp "resources/public/content/Simplified example gallery.md" )))
+;; (def processed-text (md->html {:source (slurp "resources/public/content/Simplified example gallery.md" )}))
-(preferred-source (-> processed-text :extensions :pswp :scripts :core) :pswp)
+;; (preferred-source (-> processed-text :extensions :pswp :scripts :core) :pswp)
-(collect-preferred processed-text :scripts)
+;; (-> processed-text :extensions)
+
+;; (collect-preferred processed-text :scripts)
(defn wiki-page
"Render the markdown page specified in this `request`, if any. If none found, redirect to edit-page"
@@ -198,9 +200,10 @@
(do
(log/info (format "Showing page '%s' from file '%s'" page file-path))
(let [processed-text (md->html
- (include/expand-include-md
- (:includer md-include-system)
- (slurp file-path)))]
+ (assoc request :source
+ (include/expand-include-md
+ (:includer md-include-system)
+ (slurp file-path))))]
(layout/render "wiki.html"
(merge (util/standard-params request)
processed-text
diff --git a/src/smeagol/util.clj b/src/smeagol/util.clj
index e92bafe..653e056 100644
--- a/src/smeagol/util.clj
+++ b/src/smeagol/util.clj
@@ -4,13 +4,13 @@
(:require [clojure.java.io :as cjio]
[clojure.string :as cs]
[environ.core :refer [env]]
+ [markdown.core :as md]
[me.raynes.fs :as fs]
[noir.io :as io]
[noir.session :as session]
[scot.weft.i18n.core :as i18n]
[smeagol.authenticate :as auth]
[smeagol.configuration :refer [config]]
- [smeagol.formatting :refer [md->html]]
[taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -124,8 +124,8 @@
"In `smeagol.util/local-url `" file-path "` is not a servable resource:" any)
(str "404-not-found?path=" file-path))))
-(local-url? "vendor/node_modules/photoswipe/dist/photoswipe.min.js")
-(local-url? "/home/simon/workspace/smeagol/resources/public/vendor/node_modules/photoswipe/dist/photoswipe.min.js")
+;; (local-url? "vendor/node_modules/photoswipe/dist/photoswipe.min.js")
+;; (local-url? "/home/simon/workspace/smeagol/resources/public/vendor/node_modules/photoswipe/dist/photoswipe.min.js")
(defn standard-params
"Return a map of standard parameters to pass to the template renderer."
@@ -134,8 +134,10 @@
{:user user
:admin (auth/get-admin user)
:js-from (:js-from config)
- :side-bar (:content (md->html (slurp (cjio/file content-dir "_side-bar.md"))))
- :header (:content (md->html (slurp (cjio/file content-dir "_header.md"))))
+ :side-bar (md/md-to-html-string
+ (slurp (cjio/file content-dir "_side-bar.md")):heading-anchors true)
+ :header (md/md-to-html-string
+ (slurp (cjio/file content-dir "_header.md")) :heading-anchors true)
:version (System/getProperty "smeagol.version")}))
diff --git a/test/smeagol/test/formatting.clj b/test/smeagol/test/formatting.clj
index 9cb4956..5172403 100644
--- a/test/smeagol/test/formatting.clj
+++ b/test/smeagol/test/formatting.clj
@@ -12,9 +12,9 @@
"[This is a foreign link](http://to.somewhere)")]
(is (= (local-links text) text) "Foreign links should be unchanged"))))
-(deftest test-process-text
- (testing "The process-text flow"
- (let [expected process-test-return-value
- actual (process-text "```test
- ```")]
- (is (= actual expected)))))
+;; (deftest test-process-text
+;; (testing "The process-text flow"
+;; (let [expected process-test-return-value
+;; actual (process-text "```test
+;; ```")]
+;; (is (= actual expected)))))
From 03c63da19edff1ea66f78a8e2244dc46e7ffe95a Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 19 Feb 2020 15:24:08 +0000
Subject: [PATCH 74/83] Tactical commit
---
resources/public/content/stylesheet.css | 101 ++++++++----------------
src/smeagol/formatting.clj | 37 +--------
src/smeagol/local_links.clj | 50 ++++++++++++
src/smeagol/util.clj | 9 ++-
test/smeagol/test/formatting.clj | 38 +++++----
test/smeagol/test/local_links.clj | 14 ++++
6 files changed, 127 insertions(+), 122 deletions(-)
create mode 100644 src/smeagol/local_links.clj
create mode 100644 test/smeagol/test/local_links.clj
diff --git a/resources/public/content/stylesheet.css b/resources/public/content/stylesheet.css
index 62690e0..81a661c 100644
--- a/resources/public/content/stylesheet.css
+++ b/resources/public/content/stylesheet.css
@@ -24,16 +24,6 @@
## html elements generally in alphabetic order
*/
-a {
- color: darkgray;
- font-weight: bold;
-}
-
-a:hover {
- color: darkgray;
- background:rgba(200,200,200,0.8);
-}
-
body {
margin: 0;
padding: 0;
@@ -62,15 +52,14 @@ dd {
/* footer of the page - not-editable, provided by Smeagol */
footer {
- border-top: thin solid silver;
- color: gray;
- background:rgba(200,200,200,0.8);
+ border-top: thin solid gray;
clear: both;
font-size: smaller;
text-align: center;
+ color: gray;
+ background: rgba(224,224,224,0.95);
width: 100%;
margin: 0;
- min-height: 4px;
padding: 0.25em 0;
bottom:0;
position:fixed;
@@ -82,14 +71,9 @@ footer {
}
footer div {
- display: none;
padding: 0.1em;
}
-footer:hover div {
- display: block;
-}
-
form {
border: thin solid silver;
}
@@ -99,6 +83,8 @@ header {
margin-top: 0;
width:100%;
max-width: 100%;
+ background-color: gray;
+ color: white;
}
header h1 {
@@ -107,6 +93,7 @@ header h1 {
header a {
font-weight: bold;
+ color: white;
}
header a:hover {
@@ -144,12 +131,12 @@ ins {
label {
width: 20%;
min-width: 20em;
- border-right: thin solid silver;
+ border-right: thin solid gray;
display: inline-block;
}
table {
- border: thin solid silver;
+ border: 2px solid black;
border-collapse: collapse;
}
@@ -161,7 +148,7 @@ th, td {
text-align: left;
vertical-align: top;
padding: 0.15em 1.5em;
- border: 1px solid silver;
+ border: 1px solid gray;
}
th {
@@ -179,7 +166,6 @@ th {
/* left bar for all pages in the Wiki - editable, provided by users. Within main-container */
#side-bar {
- display: none;
width: 17%;
height: 100%;
float: left;
@@ -187,10 +173,10 @@ th {
/* cookies information box, fixed, in right margin, just above footer */
#cookies {
- width: 20%;
+ width: 30%;
float: right;
position: fixed;
- bottom: 8px;
+ bottom: 3.5em;
right: 0;
z-index: 175;
background: transparent;
@@ -204,8 +190,8 @@ th {
text-align: right;
padding: 0.25em 2em;
border-radius: 0.25em;
- color: gray;
- background:rgba(200,200,200,0.8);
+ color: white;
+ background:rgba(40,40,40,0.8);
}
/* more-about-cookies box, normally hidden */
@@ -213,9 +199,9 @@ th {
display: none;
padding: 0.5em 2em;
border-radius: 0.5em;
- color: gray;
- background:rgba(200,200,200,0.8);
- border-bottom: thin solid gray;
+ color: white;
+ background:rgba(40,40,40,0.8);
+ border-bottom: thin solid white;
}
/* but magically appears on mouseover */
@@ -256,8 +242,8 @@ th {
right: 0;
padding: 0.25em 2em;
border-radius: 0.25em;
- color: gray;
- background:rgba(200,200,200,0.8);
+ color: white;
+ background:rgba(40,40,40,0.8);
font-size: 66%;
}
@@ -268,11 +254,7 @@ th {
.minor-controls a {
float: right;
padding: 0.25em 2em;
- color: gray;
-}
-
-.minor-controls a:hover {
- color: darkgray;
+ color: white;
}
.pseudo-input {
@@ -321,7 +303,8 @@ th {
/* content of the current page in the Wiki - editable, provided by users. Within main-container */
#content {
border: thin solid silver;
- width: 100%;
+ width: 80%;
+ float: right;
padding-bottom: 5em;
}
@@ -329,29 +312,16 @@ th {
display: none;
}
- #header {
- font-size: smaller;
- }
-
/* top-of-page navigation, not editable, provided by Smeagol */
#nav{
margin: 0;
padding: 0;
top: 0;
- min-height: 4px;
+ width: 100%;
_position: absolute;
_top: expression(document.documentElement.scrollTop);
z-index: 149;
- color: gray;
- background:rgba(200,200,200,0.8);
- }
-
- #nav #nav-menu {
- display: none;
- }
-
- #nav:hover #nav-menu {
- display: block;
+ background:rgba(40,40,40,0.8);
}
/* only needed for fly-out menu effect on tablet and phone stylesheets */
@@ -371,14 +341,14 @@ th {
}
#nav menu li a {
- color: gray;
+ color: white;
text-decoration: none;
font-weight: bold;
padding: 0.1em 0.75em;
margin: 0;
}
- #nav menu li.active a { background: gray; color: white;}
+ #nav menu li.active a { background: gray;}
li.nav-item a:hover { background: rgb( 240, 240, 240) }
li.nav-item a:active { background: gray; color: white; }
@@ -409,15 +379,17 @@ th {
padding: 0;
position: fixed;
z-index: 149;
- color: black;
- background:rgba(200,200,200,0.9);
+ color: silver;
+ background:rgba(40,40,40,0.9);
}
#nav a {
+ color: white;
+ text-decoration: none;
font-weight: bold;
}
- #nav:hover #nav-menu, #nav:hover #phone-side-bar {
+ #nav:hover #nav-menu {
display: block;
list-style-type: none;
width: 100%;
@@ -483,21 +455,18 @@ th {
display: none;
}
- #header {
- display: none;
- }
-
#nav{
margin: 0;
padding: 0;
position: fixed;
z-index: 149;
- color: black;
- background:rgba(200,200,200,0.9);
+ color: silver;
+ background:rgba(40,40,40,0.9);
}
#nav a {
- color: black;
+ color: white;
+ text-decoration: none;
font-weight: bold;
}
@@ -522,8 +491,6 @@ th {
}
#nav menu li a {
- color: black;
- font-weight: bold;
}
#nav ul li.active a { background: silver;}
diff --git a/src/smeagol/formatting.clj b/src/smeagol/formatting.clj
index cacc9ca..3807cb0 100644
--- a/src/smeagol/formatting.clj
+++ b/src/smeagol/formatting.clj
@@ -10,6 +10,7 @@
[smeagol.extensions.mermaid :refer [process-mermaid]]
[smeagol.extensions.photoswipe :refer [process-photoswipe]]
[smeagol.extensions.vega :refer [process-vega]]
+ [smeagol.local-links :refer :all]
[taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -44,26 +45,6 @@
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Error to show if text to be rendered is nil.
-;; TODO: this should go through i18n
-(def no-text-error "No text: does the file exist?")
-
-
-(defn local-links
- "Rewrite text in `html-src` surrounded by double square brackets as a local link into this wiki."
- [^String html-src]
- (if html-src
- (cs/replace html-src #"\[\[[^\[\]]*\]\]"
- #(let [text (cs/replace %1 #"[\[\]]" "")
- encoded (url-encode text)
- ;; I use '\_' to represent '_' in wiki markup, because
- ;; '_' is meaningful in Markdown. However, this needs to
- ;; be stripped out when interpreting local links.
- munged (cs/replace encoded #"%26%2395%3B" "_")]
- (format "%s " munged text)))
- no-text-error))
-
-
(declare process-text)
@@ -133,26 +114,10 @@
result
{:inclusions {inky (eval (list formatter (subs fragment (count token)) index))}
:extensions {fkey (-> config :formatters fkey)}})
-;; (assoc-in
-;; (assoc-in result [:inclusions inky] (eval (list formatter (subs fragment (count token)) index)))
-;; [:extensions fkey] (-> config :formatters fkey))
(rest fragments)
(cons inky processed))))
-;; (apply-formatter
-;; 3
-;; {:inclusions {}}
-;; '()
-;; '()
-;; "pswp
-;; 
-;; 
-;; 
-;; "
-;; "pswp"
-;; smeagol.extensions.photoswipe/process-photoswipe)
-
(defn reassemble-text
"Reassemble these processed strings into a complete text, and process it as
Markdown."
diff --git a/src/smeagol/local_links.clj b/src/smeagol/local_links.clj
new file mode 100644
index 0000000..f1bed0b
--- /dev/null
+++ b/src/smeagol/local_links.clj
@@ -0,0 +1,50 @@
+(ns ^{:doc "Format Semagol's local links."
+ :author "Simon Brooke"}
+ smeagol.local-links
+ (:require [clojure.data.json :as json]
+ [clojure.string :as cs]
+ [cemerick.url :refer (url url-encode url-decode)]))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Smeagol: a very simple Wiki engine.
+;;;;
+;;;; This program is free software; you can redistribute it and/or
+;;;; modify it under the terms of the GNU General Public License
+;;;; as published by the Free Software Foundation; either version 2
+;;;; of the License, or (at your option) any later version.
+;;;;
+;;;; This program is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with this program; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+;;;; USA.
+;;;;
+;;;; Copyright (C) 2017 Simon Brooke
+;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; Error to show if text to be rendered is nil.
+;; TODO: this should go through i18n
+(def no-text-error "No text: does the file exist?")
+
+
+(defn local-links
+ "Rewrite text in `html-src` surrounded by double square brackets as a local link into this wiki."
+ [^String html-src]
+ (if html-src
+ (cs/replace html-src #"\[\[[^\[\]]*\]\]"
+ #(let [text (cs/replace %1 #"[\[\]]" "")
+ encoded (url-encode text)
+ ;; I use '\_' to represent '_' in wiki markup, because
+ ;; '_' is meaningful in Markdown. However, this needs to
+ ;; be stripped out when interpreting local links.
+ munged (cs/replace encoded #"%26%2395%3B" "_")]
+ (format "%s " munged text)))
+ no-text-error))
+
+
diff --git a/src/smeagol/util.clj b/src/smeagol/util.clj
index 653e056..86fdfac 100644
--- a/src/smeagol/util.clj
+++ b/src/smeagol/util.clj
@@ -11,6 +11,7 @@
[scot.weft.i18n.core :as i18n]
[smeagol.authenticate :as auth]
[smeagol.configuration :refer [config]]
+ [smeagol.local-links :refer :all]
[taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -135,9 +136,13 @@
:admin (auth/get-admin user)
:js-from (:js-from config)
:side-bar (md/md-to-html-string
- (slurp (cjio/file content-dir "_side-bar.md")):heading-anchors true)
+ (local-links
+ (slurp (cjio/file content-dir "_side-bar.md")))
+ :heading-anchors true)
:header (md/md-to-html-string
- (slurp (cjio/file content-dir "_header.md")) :heading-anchors true)
+ (local-links
+ (slurp (cjio/file content-dir "_header.md")))
+ :heading-anchors true)
:version (System/getProperty "smeagol.version")}))
diff --git a/test/smeagol/test/formatting.clj b/test/smeagol/test/formatting.clj
index 5172403..52e9817 100644
--- a/test/smeagol/test/formatting.clj
+++ b/test/smeagol/test/formatting.clj
@@ -1,20 +1,24 @@
(ns smeagol.test.formatting
(:require [clojure.test :refer :all]
- [smeagol.formatting :refer [local-links no-text-error]]
- [smeagol.extensions.test :refer :all]))
+ [smeagol.formatting :refer :all]
+ [smeagol.extensions.test :refer :all]
+ [smeagol.local-links :refer :all]))
-(deftest test-local-links
- (testing "Rewriting of local links"
- (is (= (local-links nil) no-text-error) "Should NOT fail with a no pointer exception!")
- (is (= (local-links "") "") "Empty string should pass through unchanged.")
- (is (= (local-links "[[froboz]]") "froboz ") "Local link should be rewritten.")
- (let [text (str "# This is a heading"
- "[This is a foreign link](http://to.somewhere)")]
- (is (= (local-links text) text) "Foreign links should be unchanged"))))
-
-;; (deftest test-process-text
-;; (testing "The process-text flow"
-;; (let [expected process-test-return-value
-;; actual (process-text "```test
-;; ```")]
-;; (is (= actual expected)))))
+(deftest test-apply-formatter
+ (testing "apply-formatter"
+ (let [actual (-> (apply-formatter
+ 3
+ {:inclusions {}}
+ '()
+ '()
+ "test
+ 
+ 
+ 
+ "
+ "test"
+ smeagol.extensions.test/process-test)
+ :inclusions
+ :inclusion-3)
+ expected ""]
+ (is (= actual expected)))))
diff --git a/test/smeagol/test/local_links.clj b/test/smeagol/test/local_links.clj
new file mode 100644
index 0000000..54d31e9
--- /dev/null
+++ b/test/smeagol/test/local_links.clj
@@ -0,0 +1,14 @@
+(ns smeagol.test.local-links
+ (:require [clojure.test :refer :all]
+ [smeagol.local-links :refer [local-links no-text-error]]
+ [smeagol.extensions.test :refer :all]
+ [smeagol.local-links :refer :all]))
+
+(deftest test-local-links
+ (testing "Rewriting of local links"
+ (is (= (local-links nil) no-text-error) "Should NOT fail with a no pointer exception!")
+ (is (= (local-links "") "") "Empty string should pass through unchanged.")
+ (is (= (local-links "[[froboz]]") "froboz ") "Local link should be rewritten.")
+ (let [text (str "# This is a heading"
+ "[This is a foreign link](http://to.somewhere)")]
+ (is (= (local-links text) text) "Foreign links should be unchanged"))))
From f82ad725c151feab74e1d1ae0e5b49bebe5d2bb6 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 19 Feb 2020 16:18:58 +0000
Subject: [PATCH 75/83] Working,, but regression in Mermaid stylesheet
---
resources/test/test_extension.md | 7 +++++++
resources/test/test_local_links.md | 6 ++++++
src/smeagol/formatting.clj | 1 -
test/smeagol/test/formatting.clj | 19 +++++++++++++++++++
test/smeagol/test/local_links.clj | 9 +++++++--
5 files changed, 39 insertions(+), 3 deletions(-)
create mode 100644 resources/test/test_extension.md
create mode 100644 resources/test/test_local_links.md
diff --git a/resources/test/test_extension.md b/resources/test/test_extension.md
new file mode 100644
index 0000000..1f43160
--- /dev/null
+++ b/resources/test/test_extension.md
@@ -0,0 +1,7 @@
+# This is a test
+
+```test
+the quick brown fox jumped over the lazy dog
+```
+
+This concludes the test.
diff --git a/resources/test/test_local_links.md b/resources/test/test_local_links.md
new file mode 100644
index 0000000..659f03a
--- /dev/null
+++ b/resources/test/test_local_links.md
@@ -0,0 +1,6 @@
+# This is a test
+
+[[Local link]]
+[Not a local link](http://nowhere.at.al)
+
+This concludes the test.
diff --git a/src/smeagol/formatting.clj b/src/smeagol/formatting.clj
index 3807cb0..3617514 100644
--- a/src/smeagol/formatting.clj
+++ b/src/smeagol/formatting.clj
@@ -104,7 +104,6 @@
^String fragment
^String token
formatter]
- (log/info "index:" index "(type result):" (type result) "(type fragments):" (type fragments) "fragment:" fragment "token:" token ":formatter" formatter)
(let
[inky (keyword (str "inclusion-" index))
fkey (keyword token)]
diff --git a/test/smeagol/test/formatting.clj b/test/smeagol/test/formatting.clj
index 52e9817..18769cd 100644
--- a/test/smeagol/test/formatting.clj
+++ b/test/smeagol/test/formatting.clj
@@ -1,5 +1,6 @@
(ns smeagol.test.formatting
(:require [clojure.test :refer :all]
+ [clojure.string :as cs]
[smeagol.formatting :refer :all]
[smeagol.extensions.test :refer :all]
[smeagol.local-links :refer :all]))
@@ -22,3 +23,21 @@
:inclusion-3)
expected ""]
(is (= actual expected)))))
+
+(deftest test-md->html
+ (let [actual (:content (md->html
+ {:source
+ (cs/join
+ "\n"
+ ["# This is a test"
+ ""
+ "```test"
+ ""
+ "```"
+ ""
+ "This concludes the test"])} ))
+ expected (str
+ "This is a test "
+ "
"
+ "This concludes the test
")]
+ (is (= expected actual))))
diff --git a/test/smeagol/test/local_links.clj b/test/smeagol/test/local_links.clj
index 54d31e9..dc3682d 100644
--- a/test/smeagol/test/local_links.clj
+++ b/test/smeagol/test/local_links.clj
@@ -1,5 +1,6 @@
(ns smeagol.test.local-links
(:require [clojure.test :refer :all]
+ [clojure.string :as cs]
[smeagol.local-links :refer [local-links no-text-error]]
[smeagol.extensions.test :refer :all]
[smeagol.local-links :refer :all]))
@@ -10,5 +11,9 @@
(is (= (local-links "") "") "Empty string should pass through unchanged.")
(is (= (local-links "[[froboz]]") "froboz ") "Local link should be rewritten.")
(let [text (str "# This is a heading"
- "[This is a foreign link](http://to.somewhere)")]
- (is (= (local-links text) text) "Foreign links should be unchanged"))))
+ "[This is a foreign link](http://to.somewhere)")]
+ (is (= (local-links text) text) "Foreign links should be unchanged"))
+ (let [text (cs/trim (slurp "resources/test/test_local_links.md"))
+ actual (local-links text)
+ expected "# This is a test\n\nLocal link \n[Not a local link](http://nowhere.at.al)\n\nThis concludes the test."]
+ (is (= actual expected)))))
From 019f2e8276c37af3d5f6e9295ab272e4c9995d2b Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 19 Feb 2020 16:42:58 +0000
Subject: [PATCH 76/83] Regression fixed.
---
resources/config.edn | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/resources/config.edn b/resources/config.edn
index ecb1ea0..96ab5ca 100644
--- a/resources/config.edn
+++ b/resources/config.edn
@@ -38,9 +38,8 @@
:scripts {}
:styles {}}
:mermaid {:formatter "smeagol.extensions.mermaid/process-mermaid"
- :scripts {:core {:local "vendor/mermaid/dist/mermaid.js"
- :remote "https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.4.6/mermaid.min.js"}}
- :styles {}}
+ :scripts {:core {:local "vendor/node_modules/mermaid/dist/mermaid.min.js"
+ :remote "https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.4.6/mermaid.min.js"}}}
:pswp {:formatter "smeagol.extensions.photoswipe/process-photoswipe"
:scripts {:core {:local "vendor/node_modules/photoswipe/dist/photoswipe.min.js"
:remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"}
From be3c9fc94697bc43306d3998888dd975364d5ff8 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 19 Feb 2020 16:43:43 +0000
Subject: [PATCH 77/83] lein-release plugin: preparing 1.0.4 release
---
project.clj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/project.clj b/project.clj
index 3898e1c..f3d5602 100644
--- a/project.clj
+++ b/project.clj
@@ -1,4 +1,4 @@
-(defproject smeagol "1.0.4-SNAPSHOT"
+(defproject smeagol "1.0.4"
:description "A simple Git-backed Wiki inspired by Gollum"
:url "https://github.com/simon-brooke/smeagol"
:license {:name "GNU General Public License,version 2.0 or (at your option) any later version"
From 256cf702cfb2b77e479d4a3e14059528572e6285 Mon Sep 17 00:00:00 2001
From: Simon Brooke
Date: Wed, 19 Feb 2020 16:52:37 +0000
Subject: [PATCH 78/83] Added files used by example galleries to repository
---
resources/public/content/uploads/g1.jpg | Bin 0 -> 1491611 bytes
resources/public/content/uploads/g2.jpg | Bin 0 -> 1212841 bytes
resources/public/content/uploads/g3.jpg | Bin 0 -> 1245145 bytes
resources/public/content/uploads/g4.jpg | Bin 0 -> 1112701 bytes
resources/public/content/uploads/med/g1.jpg | Bin 0 -> 42920 bytes
resources/public/content/uploads/med/g2.jpg | Bin 0 -> 12387 bytes
resources/public/content/uploads/med/g3.jpg | Bin 0 -> 17951 bytes
resources/public/content/uploads/med/g4.jpg | Bin 0 -> 24008 bytes
resources/public/content/uploads/small/g1.jpg | Bin 0 -> 1820 bytes
resources/public/content/uploads/small/g2.jpg | Bin 0 -> 1077 bytes
resources/public/content/uploads/small/g3.jpg | Bin 0 -> 1249 bytes
resources/public/content/uploads/small/g4.jpg | Bin 0 -> 1562 bytes
12 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 resources/public/content/uploads/g1.jpg
create mode 100644 resources/public/content/uploads/g2.jpg
create mode 100644 resources/public/content/uploads/g3.jpg
create mode 100644 resources/public/content/uploads/g4.jpg
create mode 100644 resources/public/content/uploads/med/g1.jpg
create mode 100644 resources/public/content/uploads/med/g2.jpg
create mode 100644 resources/public/content/uploads/med/g3.jpg
create mode 100644 resources/public/content/uploads/med/g4.jpg
create mode 100644 resources/public/content/uploads/small/g1.jpg
create mode 100644 resources/public/content/uploads/small/g2.jpg
create mode 100644 resources/public/content/uploads/small/g3.jpg
create mode 100644 resources/public/content/uploads/small/g4.jpg
diff --git a/resources/public/content/uploads/g1.jpg b/resources/public/content/uploads/g1.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c2ece83305a9004f445d9ccd366ed34e6875facf
GIT binary patch
literal 1491611
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 -twv$1#Lo|52{(
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
z