;; 后台选择文档来源仓库和分支。
(defmodule lamina_docs_admin
(export all))
(defun show (req)
(page req (picked-id req) ""))
(defun save (req)
(if (not (lamina_admin_pages:csrf-valid? req))
(lamina_admin_pages:html 403 "CSRF token 无效")
(let ((repo-id (param req "repo_id"))
(branch (param req "branch")))
(case (lamina_docs:save repo-id branch)
('ok (page req repo-id "Documentation source saved."))
(`#(error ,message) (page req repo-id message))
(_ (page req repo-id "Could not save the documentation source."))))))
(defun page (req repo-id message)
(let* ((repos (lamina_docs:list-repos))
(selected (selected-repo repos repo-id))
(branch (current-branch req selected)))
(lamina_admin_pages:html
200
(lamina_admin_pages:layout
(lamina_admin_pages:current-username req)
"Docs"
(lamina_docs:encode-html (body req repos selected branch message))))))
(defun body (req repos selected branch message)
(let* ((repo-id (if (=:= 'none selected) "" (to-str (maps:get 'id selected))))
(names (branch-names selected branch)))
(list
"<div class=\"settings-page\"><h1>Docs</h1>"
"<p class=\"lede\">Choose a repository and branch. The public Docs page reads Markdown from that check-in.</p>"
(if (=:= "" message) "" (list "<div class=\"notice\">" (lamina_ui:escape message) "</div>"))
"<form method=\"GET\" action=\"/admin/docs\" class=\"settings-card\">"
"<h2>Repository</h2>"
"<p class=\"hint\">Changing the repository reloads its branches. Save to publish the choice.</p>"
"<label>Source repository<select name=\"repo_id\" onchange=\"this.form.submit()\">"
"<option value=\"\">Select a repository</option>"
(lists:map (lambda (repo) (repo-option repo repo-id)) repos)
"</select></label></form>"
"<form method=\"POST\" action=\"/admin/docs\">"
(lamina_admin_pages:csrf-field req)
"<input type=\"hidden\" name=\"repo_id\" value=\"" (lamina_ui:escape repo-id) "\">"
"<section class=\"settings-card\"><h2>Branch</h2>"
"<p class=\"hint\">Articles are read from this branch. The default is trunk.</p>"
"<label>Branch"
(if (=:= '() names)
(list "<input name=\"branch\" value=\"" (lamina_ui:escape branch) "\">")
(list "<select name=\"branch\">"
(lists:map (lambda (name) (branch-option name branch)) names)
"</select>"))
"</label>"
"<div class=\"settings-actions\"><button class=\"btn\" type=\"submit\">Save</button></div>"
"</section></form>"
(summary selected branch)
"<section class=\"settings-card\"><h2>How files become pages</h2>"
"<p class=\"hint\">A Markdown file in the repository root is an article under General. A folder name is a category. Nested folders are shown as Parent / Child, and the Markdown files inside are that category's articles. The first heading is the article title. README and index are listed first.</p>"
"</section></div>")))
(defun summary (selected branch)
(if (=:= 'none selected)
"<section class=\"settings-card\"><h2>Bound repository</h2><p class=\"hint\">No repository is bound. Choose one above and save.</p></section>"
(let* ((path (maps:get 'path selected))
(url (lamina_site_mgr:repo-base-url (maps:get 'org selected "") (maps:get 'name selected)))
(description (maps:get 'description selected ""))
(counts (case (lamina_docs:list-files path branch)
(`#(ok ,files) `#(ok ,(lamina_docs:doc-counts files)))
(error error))))
(list "<section class=\"settings-card\"><h2>Bound repository</h2>"
"<p class=\"hint\">" (lamina_ui:escape (lamina_docs:repo-label selected))
" · " (lamina_ui:escape branch) "</p>"
(if (=:= "" description)
""
(list "<p class=\"hint\">" (lamina_ui:escape description) "</p>"))
(if (bound? selected)
""
"<p class=\"hint\">Save to publish this repository on the Docs page.</p>")
"<div class=\"meta-item\"><span>Address</span><a href=\""
(lamina_ui:escape url) "\">" (lamina_ui:escape url) "</a></div>"
(case counts
(`#(ok ,stats)
(list "<div class=\"meta-row\">"
(fact "Articles" (integer_to_list (maps:get 'articles stats 0)))
(fact "Categories" (integer_to_list (maps:get 'categories stats 0)))
(fact "Repository size" (lamina_docs:format-bytes (lamina_docs:file-size path)))
(fact "Timeline" (integer_to_list (lamina_docs:timeline-count path)))
"</div>"))
(`#(error ,message)
(list "<div class=\"err\">" (lamina_ui:escape message) "</div>"))
(_ "<div class=\"err\">Could not read this repository.</div>"))
"</section>"))))
(defun fact (label value)
(list "<div class=\"meta-item\"><span>" label "</span>" (lamina_ui:escape value) "</div>"))
(defun bound? (selected)
(=:= (to-str (maps:get 'id selected)) (lamina_docs:saved-repo-id)))
(defun repo-option (repo selected-id)
(let ((id (to-str (maps:get 'id repo))))
(list "<option value=\"" (lamina_ui:escape id) "\""
(if (=:= id selected-id) " selected" "")
">" (lamina_ui:escape (lamina_docs:repo-label repo)) "</option>")))
(defun branch-option (name selected)
(list "<option"
(if (=:= name selected) " selected" "")
">" (lamina_ui:escape name) "</option>"))
(defun branch-names (selected fallback)
(if (=:= 'none selected)
'()
(let ((names (lamina_docs:branches (maps:get 'path selected))))
(if (lists:member fallback names)
names
(if (=:= "" fallback) names (lists:append names (list fallback)))))))
(defun selected-repo (repos repo-id)
(let ((id (lamina_docs:to-int repo-id)))
(case (lists:search (lambda (repo) (=:= id (maps:get 'id repo))) repos)
(`#(value ,repo) repo)
(_ 'none))))
(defun picked-id (req)
(let ((from-query (param req "repo_id")))
(if (=:= "" from-query)
(lamina_docs:saved-repo-id)
from-query)))
(defun current-branch (req selected)
(let ((posted (param req "branch")))
;; LFE 的 and 两边都会算。selected 为 none 时不能再 maps:get。
(if (/= "" posted)
posted
(if (=:= 'none selected)
"trunk"
(if (=:= (to-str (maps:get 'id selected)) (lamina_docs:saved-repo-id))
(lamina_docs:saved-branch)
"trunk")))))
(defun param (req name)
(let ((params (maps:get 'params req #M()))
(key (list_to_binary name)))
(case (maps:find key params)
(`#(ok ,value) (lamina_ui:to-str value))
(_
(case (lists:keyfind key 1 (cowboy_req:parse_qs req))
(`#(,_ ,value) (lamina_ui:to-str value))
(_ ""))))))
(defun to-str (value)
(lamina_ui:to-str value))