Added more if-member-of checks; added simplemde support.

This commit is contained in:
Simon Brooke 2018-07-05 11:15:25 +01:00
parent ac070b537f
commit 66d4b2af4d
2 changed files with 274 additions and 143 deletions

View file

@ -0,0 +1,17 @@
var simplemde = new SimpleMDE({
autosave: {
enabled: true,
uniqueId: "Smeagol-{{page}}",
delay: 1000,
},
indentWithTabs: true,
insertTexts: {
horizontalRule: ["", "\n\n-----\n\n"],
image: ["![](http://", ")"],
link: ["[", "](http://)"],
table: ["", "\n\n| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Text | Text | Text |\n\n"],
},
showIcons: ["code"], //, "table"], - sadly, markdown-clj does not support tables
spellChecker: true,
status: ["autosave", "lines", "words", "cursor"]
});

View file

@ -59,27 +59,49 @@
(defn emit-content (defn emit-content
([content]
(cond
(nil? content)
nil
(string? content)
content
(and (map? content) (:tag content))
(with-out-str
(x/emit-element content))
(seq? content)
(map emit-content content)
true
(str "<!-- don't know what to do with '" content "' -->")))
([filename application k] ([filename application k]
(emit-content filename nil nil application k)) (emit-content filename nil nil application k))
([filename spec entity application k] ([filename spec entity application k]
(let [content (let [content
(:content (:content
(first (first
(or (children-with-tag spec k) (or (children-with-tag spec k)
(children-with-tag entity k) (children-with-tag entity k)
(children-with-tag (children-with-tag
(child-with-tag application :content) (child-with-tag application :content)
k))))] k))))]
(if (if
content content
(flatten (flatten
(list (list
(str "{% block " (name k) " %}") (str "{% block " (name k) " %}")
(doall (doall
(map (map
#(with-out-str (x/emit-element %)) emit-content
content)) content))
"{% endblock %}")))))) "{% endblock %}"))))))
;; {:tag :div, :content
;; [{:tag :div, :attrs {:class big-link-container}, :content
;; [{:tag :a, :attrs {:id next-selector, :role button, :class big-link},
;; :content [Next]}]}
;; [{% ifmemberof admin %}
;; {:tag :div, :attrs {:class big-link-container}, :content
;; [{:tag :a, :attrs {:href form-electors-Elector, :class big-link}, :content [Add a new Elector]}]}
;; {% endifmemberof %}]]}
(defn file-header (defn file-header
@ -141,22 +163,56 @@
"{% csrf-field %}") "{% csrf-field %}")
(defn compose-if-member-of-tag
[writable? & elts]
(let
[all-permissions (distinct (apply find-permissions elts))
permissions (map
s/lower-case
(if
writable?
(writable-by all-permissions)
(visible-to all-permissions)))]
(s/join
" "
(flatten
(list
"{% ifmemberof"
permissions
"%}")))))
(defn wrap-in-if-member-of
"Wrap this `content` in an if-member-of tag; if `writable?` is true,
allow those groups by whom it is writable, else those by whom it is
readable. `context` should be a sequence of adl elements from which
permissions may be obtained."
[content writable? & context]
[(apply compose-if-member-of-tag (cons writable? context))
content
"{% endifmemberof %}"])
(defn save-widget (defn save-widget
"Return an appropriate 'save' widget for this `form` operating on this `entity` taken "Return an appropriate 'save' widget for this `form` operating on this `entity` taken
from this `application`. from this `application`.
TODO: should be suppressed unless a member of a group which can insert or edit." TODO: should be suppressed unless a member of a group which can insert or edit."
[form entity application] [form entity application]
{:tag :p (wrap-in-if-member-of
:attrs {:class "widget action-safe"} {:tag :p
:content [{:tag :label :attrs {:class "widget action-safe"}
:attrs {:for "save-button" :class "action-safe"} :content [{:tag :label
:content [(str "To save this " (:name (:attrs entity)) " record")]} :attrs {:for "save-button" :class "action-safe"}
{:tag :input :content [(str "To save this " (:name (:attrs entity)) " record")]}
:attrs {:id "save-button" {:tag :input
:name "save-button" :attrs {:id "save-button"
:class "action-safe" :name "save-button"
:type "submit" :class "action-safe"
:value (str "Save!")}}]}) :type "submit"
:value (str "Save!")}}]}
true
entity
application))
(defn delete-widget (defn delete-widget
@ -164,17 +220,21 @@
from this `application`. from this `application`.
TODO: should be suppressed unless member of a group which can delete." TODO: should be suppressed unless member of a group which can delete."
[form entity application] [form entity application]
{:tag :p (wrap-in-if-member-of
:attrs {:class "widget action-dangerous"} {:tag :p
:content [{:tag :label :attrs {:class "widget action-dangerous"}
:attrs {:for "delete-button" :class "action-dangerous"} :content [{:tag :label
:content [(str "To delete this " (:name (:attrs entity)) " record")]} :attrs {:for "delete-button" :class "action-dangerous"}
{:tag :input :content [(str "To delete this " (:name (:attrs entity)) " record")]}
:attrs {:id "delete-button" {:tag :input
:name "delete-button" :attrs {:id "delete-button"
:class "action-dangerous" :name "delete-button"
:type "submit" :class "action-dangerous"
:value (str "Delete!")}}]}) :type "submit"
:value (str "Delete!")}}]}
true
entity
application))
(defn get-options (defn get-options
@ -224,7 +284,8 @@
"boolean" "checkbox" "boolean" "checkbox"
"date" "date" "date" "date"
"time" "time" "time" "time"
"text" ;; default "text" "text-area"
"string" ;; default
))))) )))))
@ -275,25 +336,6 @@
:content (apply vector (get-options property form entity application))}))))})) :content (apply vector (get-options property form entity application))}))))}))
(defn compose-if-member-of-tag
[property entity application writable?]
(let
[all-permissions (find-permissions property entity application)
permissions (map
s/lower-case
(if
writable?
(writable-by all-permissions)
(visible-to all-permissions)))]
(s/join
" "
(flatten
(list
"{% ifmemberof"
permissions
"%}")))))
(defn compose-widget-para (defn compose-widget-para
[p f e a w content] [p f e a w content]
{:tag :p {:tag :p
@ -301,10 +343,10 @@
:content [{:tag :label :content [{:tag :label
:attrs {:for w} :attrs {:for w}
:content [(prompt p f e a)]} :content [(prompt p f e a)]}
(compose-if-member-of-tag p e a true) (compose-if-member-of-tag true p e a)
content content
"{% else %}" "{% else %}"
(compose-if-member-of-tag p e a false) (compose-if-member-of-tag false p e a)
{:tag :span {:tag :span
:attrs {:id w :attrs {:id w
:name w :name w
@ -352,6 +394,12 @@
"select" "select"
(compose-widget-para field-or-property form entity application widget-name (compose-widget-para field-or-property form entity application widget-name
(select-widget property form entity application)) (select-widget property form entity application))
"text-area"
(compose-widget-para
field-or-property form entity application widget-name
{:tag :textarea
:attrs {:rows "8" :cols "60" :id widget-name :name widget-name}
:content [(str "{{record." widget-name "}}")]})
;; all others ;; all others
(compose-widget-para (compose-widget-para
field-or-property form entity application widget-name field-or-property form entity application widget-name
@ -380,18 +428,23 @@
{:max (:maximum (:attrs typedef))}))})))) {:max (:maximum (:attrs typedef))}))}))))
(defn compose-select-script-header [entity application] (defn embed-script-fragment
{:tag :script :attrs {:type "text/javascript"} "Return the content of the file at `fielpath`, with these `substitutions`
:content made into it in order. Substitutions should be pairss [`pattern` `value`],
[(slurp "resources/js/select-widget-support.js")]}) where `pattern` is a string, a char, or a regular expression."
([filepath substitutions]
(let [v (slurp filepath)]
(reduce
(fn [s [pattern value]]
(s/replace s pattern value))
v
substitutions)))
([filepath]
(embed-script-fragment filepath [])))
(defn form-to-template (defn compose-form-content
"Generate a template as specified by this `form` element for this `entity`,
taken from this `application`. If `form` is nill, generate a default form
template for the entity."
[form entity application] [form entity application]
(merge
{:content {:content
{:tag :div {:tag :div
:attrs {:id "content" :class "edit"} :attrs {:id "content" :class "edit"}
@ -415,11 +468,48 @@
(= (:distict (:attrs property)) :system)) (= (:distict (:attrs property)) :system))
(children-with-tag form :field))) (children-with-tag form :field)))
(save-widget form entity application) (save-widget form entity application)
(delete-widget form entity application)))}]}} (delete-widget form entity application)))}]}})
(if
(some #(= "select" (widget-type % application)) (properties entity))
{:extra-head (compose-select-script-header entity application)} (defn compose-form-extra-head
{}))) [form entity application]
{:extra-head
(if
(some
#(= "text-area" (widget-type % application)) (properties entity))
"{% script \"js/lib/node_modules/simplemde/dist/simplemde.min.js\" %}
{% style \"js/lib/node_modules/simplemde/dist/simplemde.min.css\" %}")})
(defn compose-form-extra-tail
[form entity application]
{:extra-tail
{:tag :script :attrs {:type "text/javascript"}
:content
(apply
vector
(remove
nil?
(list
(if
(some
#(= "select" (widget-type % application)) (properties entity))
(embed-script-fragment "resources/js/select-widget-support.js"))
(if
(some
#(= "text-area" (widget-type % application)) (properties entity))
(embed-script-fragment "resources/js/text-area-md-support.js")))))}})
(defn form-to-template
"Generate a template as specified by this `form` element for this `entity`,
taken from this `application`. If `form` is nill, generate a default form
template for the entity."
[form entity application]
(merge
(compose-form-extra-head form entity application)
(compose-form-content form entity application)
(compose-form-extra-tail form entity application)))
(defn page-to-template (defn page-to-template
@ -552,33 +642,43 @@
[list-spec entity application] [list-spec entity application]
(let [form-name (let [form-name
(str (str
"list-" "list-"
(:name (:attrs entity)) (:name (:attrs entity))
"-" "-"
(:name (:attrs list-spec)))] (:name (:attrs list-spec)))]
{:back-links {:back-links
{:tag :div {:tag :div
:content :content
[ [
{:tag :div :attrs {:class "back-link-container"} {:tag :div :attrs {:class "back-link-container"}
:content :content
["{% ifequal params.offset \"0\" %}" ["{% ifequal params.offset \"0\" %}"
{:tag :a {:tag :a
:attrs {:id "back-link" :class "back-link" :href "{{servlet-context}}/admin"} :attrs {:id "back-link" :class "back-link" :href "{{servlet-context}}/admin"}
:content ["Back"]} :content ["Back"]}
"{% else %}" "{% else %}"
{:tag :a :attrs {:id "prev-selector" :class "back-link"} {:tag :a :attrs {:id "prev-selector" :class "back-link"}
:content ["Previous"]} :content ["Previous"]}
"{% endifunequal %}"]} "{% endifunequal %}"]}
]} ]}
:big-links :big-links
{:tag :div {:tag :div
:content :content
[{:tag :div :attrs {:class "big-link-container"} (apply
:content vector
[{:tag :a :attrs {:id "next-selector" :role "button" :class "big-link"} (remove
:content ["Next"]}]} nil?
(big-link (str "Add a new " (pretty-name entity)) (editor-name entity application))]} (flatten
(list
{:tag :div :attrs {:class "big-link-container"}
:content
[{:tag :a :attrs {:id "next-selector" :role "button" :class "big-link"}
:content ["Next"]}]}
(wrap-in-if-member-of
(big-link (str "Add a new " (pretty-name entity)) (editor-name entity application))
true
entity
application)))))}
:content :content
{:tag :form {:tag :form
:attrs {:id form-name :class "list" :attrs {:id form-name :class "list"
@ -591,30 +691,30 @@
{:tag :table {:tag :table
:attrs {:caption (:name (:attrs entity))} :attrs {:caption (:name (:attrs entity))}
:content :content
[(list-thead list-spec entity application) [(list-thead list-spec entity application)
(list-tbody list-spec entity application) (list-tbody list-spec entity application)
]}]} ]}]}
:extra-script :extra-script
(str " (str "
var form = document.getElementById('" form-name "'); var form = document.getElementById('" form-name "');
var ow = document.getElementById('offset'); var ow = document.getElementById('offset');
var lw = document.getElementById('limit'); var lw = document.getElementById('limit');
form.addEventListener('submit', function() { form.addEventListener('submit', function() {
ow.value='0'; ow.value='0';
}); });
{% ifunequal params.offset \"0\" %} {% ifunequal params.offset \"0\" %}
document.getElementById('prev-selector').addEventListener('click', function () { document.getElementById('prev-selector').addEventListener('click', function () {
ow.value=(parseInt(ow.value)-parseInt(lw.value)); ow.value=(parseInt(ow.value)-parseInt(lw.value));
console.log('Updated offset to ' + ow.value); console.log('Updated offset to ' + ow.value);
form.submit(); form.submit();
}); });
{% endifunequal %} {% endifunequal %}
document.getElementById('next-selector').addEventListener('click', function () { document.getElementById('next-selector').addEventListener('click', function () {
ow.value=(parseInt(ow.value)+parseInt(lw.value)); ow.value=(parseInt(ow.value)+parseInt(lw.value));
console.log('Updated offset to ' + ow.value); console.log('Updated offset to ' + ow.value);
form.submit(); form.submit();
});")})) });")}))
@ -654,43 +754,63 @@
(form-to-template nil entity application)}))))) (form-to-template nil entity application)})))))
(defn application-to-template (defn emit-entity-dt
[application] [entity application]
(let (wrap-in-if-member-of
[first-class-entities {:tag :dt
(sort-by :content
#(:name (:attrs %)) [{:tag :a
(filter :attrs {:href (path-part :list entity application)}
#(children-with-tag % :list) :content [(pretty-name entity)]}]}
(children-with-tag application :entity)))] false
{:application-index entity
{:content application))
{:tag :dl
:attrs {:class "index"}
:content (defn emit-entity-dd
(apply [entity application]
vector (wrap-in-if-member-of
(interleave {:tag :dd
(map :content
#(hash-map (apply
:tag :dt vector
:content (map
[{:tag :a (fn [d]
:attrs {:href (path-part :list % application)} (hash-map
:content [(pretty-name %)]}]) :tag :p
first-class-entities) :content (:content d)))
(map (children-with-tag entity :documentation)))}
#(hash-map false
:tag :dd entity
:content (apply application))
vector
(map
(fn [d] (defn application-to-template
(hash-map [application]
:tag :p (let
:content (:content d))) [first-class-entities
(children-with-tag % :documentation)))) (sort-by
first-class-entities)))}}})) #(:name (:attrs %))
(filter
#(children-with-tag % :list)
(children-with-tag application :entity)))]
{:application-index
{:content
{:tag :dl
:attrs {:class "index"}
:content
(apply
vector
(remove
nil?
(flatten
(interleave
(map
#(emit-entity-dt % application)
first-class-entities)
(map
#(emit-entity-dd % application)
first-class-entities)))))}}}))
(defn write-template-file (defn write-template-file
@ -712,13 +832,7 @@
#(let [content (template %)] #(let [content (template %)]
(list (list
(str "{% block " (name %) " %}") (str "{% block " (name %) " %}")
(cond (string? content) (emit-content content)
content
(map? content)
(with-out-str
(x/emit-element content))
true
(str "<!-- don't know what to do with '" content "' -->"))
"{% endblock %}")) "{% endblock %}"))
(keys template))) (keys template)))
(file-footer filename application))))) (file-footer filename application)))))