Improve
This commit is contained in:
parent
4949324e05
commit
027077c97a
25 changed files with 123 additions and 217 deletions
.gitignoreMakefile
public/vendor
qlfile.locksrc
tailwind.config.js
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1 @@
|
|||
.qlot
|
||||
src/assets/css/dist.css
|
||||
.qlot
|
9
Makefile
9
Makefile
|
@ -1,15 +1,6 @@
|
|||
install: ## Install dependencies
|
||||
@qlot install
|
||||
|
||||
dev: ## Run dev mode
|
||||
@tailwindcss -i ./src/assets/css/global.css -o ./src/assets/css/dist.css --watch=always < /dev/null &
|
||||
|
||||
stop: ## Stop dev mode
|
||||
@pkill -f tailwind
|
||||
|
||||
build: ## Build
|
||||
@tailwindcss -i ./src/assets/css/global.css -o ./src/assets/css/dist.css
|
||||
|
||||
help: ## Show options
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
|
||||
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||
|
|
1
public/vendor/htmx@1.9.12.js
vendored
Normal file
1
public/vendor/htmx@1.9.12.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/vendor/ress.css
vendored
Normal file
1
public/vendor/ress.css
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
html{-webkit-text-size-adjust:100%;box-sizing:border-box;-moz-tab-size:4;tab-size:4;word-break:normal}*,:after,:before{background-repeat:no-repeat;box-sizing:inherit}:after,:before{text-decoration:inherit;vertical-align:inherit}*{margin:0;padding:0}hr{color:inherit;height:0;overflow:visible}details,main{display:block}summary{display:list-item}small{font-size:80%}[hidden]{display:none}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}a{background-color:transparent}a:active,a:hover{outline-width:0}code,kbd,pre,samp{font-family:monospace,monospace}pre{font-size:1em}b,strong{font-weight:bolder}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-color:inherit;text-indent:0}iframe{border-style:none}input{border-radius:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}textarea{overflow:auto;resize:vertical}button,input,optgroup,select,textarea{font:inherit}optgroup{font-weight:700}button{overflow:visible}button,select{text-transform:none}[role=button],[type=button],[type=reset],[type=submit],button{cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button:-moz-focusring{outline:1px dotted ButtonText}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}button,input,select,textarea{background-color:transparent;border-style:none}a:focus,button:focus,input:focus,select:focus,textarea:focus{outline-width:0}select{-moz-appearance:none;-webkit-appearance:none}select::-ms-expand{display:none}select::-ms-value{color:currentColor}legend{border:0;color:inherit;display:table;max-width:100%;white-space:normal}::-webkit-file-upload-button{-webkit-appearance:button;color:inherit;font:inherit}[disabled]{cursor:default}img{border-style:none}progress{vertical-align:baseline}[aria-busy=true]{cursor:progress}[aria-controls]{cursor:pointer}[aria-disabled=true]{cursor:default}
|
|
@ -17,7 +17,7 @@
|
|||
("piccolo" .
|
||||
(:class qlot/source/git:source-git
|
||||
:initargs (:remote-url "https://github.com/skyizwhite/piccolo.git")
|
||||
:version "git-1f588b8a62b408067f8ea3a2045058dd94fd8047"))
|
||||
:version "git-543e14b7743fb4fbc2ca515ab2009cb54a39646d"))
|
||||
("ningle-fbr" .
|
||||
(:class qlot/source/git:source-git
|
||||
:initargs (:remote-url "https://github.com/skyizwhite/ningle-fbr.git")
|
||||
|
|
12
src/app.lisp
12
src/app.lisp
|
@ -3,25 +3,20 @@
|
|||
(:use #:cl)
|
||||
(:local-nicknames (#:jg #:jingle))
|
||||
(:local-nicknames (#:fbr #:ningle-fbr))
|
||||
(:local-nicknames (#:mw #:hp/middlewares/*))
|
||||
(:local-nicknames (#:cfg #:hp/config))
|
||||
(:local-nicknames (#:mw #:hp/middlewares/*))
|
||||
(:export #:start
|
||||
#:stop
|
||||
#:update))
|
||||
(in-package #:hp)
|
||||
|
||||
(defparameter *app* (jg:make-app :address "localhost"
|
||||
:port 3000))
|
||||
:port cfg:*port*))
|
||||
|
||||
(defun start ()
|
||||
(uiop:run-program (if (cfg:dev-mode-p)
|
||||
"make dev"
|
||||
"make build"))
|
||||
(jg:start *app*))
|
||||
|
||||
(defun stop ()
|
||||
(when (cfg:dev-mode-p)
|
||||
(uiop:run-program "make stop"))
|
||||
(jg:stop *app*))
|
||||
|
||||
(defun setup ()
|
||||
|
@ -30,7 +25,8 @@
|
|||
(fbr:assign-routes *app*
|
||||
:system "hp"
|
||||
:directory "src/routes")
|
||||
(jg:static-path *app* "/assets/" "src/assets/")
|
||||
(jg:static-path *app* "/scripts/" "src/scripts/")
|
||||
(jg:static-path *app* "/styles/" "src/styles/")
|
||||
(jg:install-middleware *app* mw:*public-files*)
|
||||
(jg:install-middleware *app* mw:*recovery*)
|
||||
(jg:install-middleware *app* mw:*normalize-path*)
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
|
@ -1,141 +0,0 @@
|
|||
//==========================================================
|
||||
// head-support.js
|
||||
//
|
||||
// An extension to htmx 1.0 to add head tag merging.
|
||||
//==========================================================
|
||||
(function(){
|
||||
|
||||
var api = null;
|
||||
|
||||
function log() {
|
||||
//console.log(arguments);
|
||||
}
|
||||
|
||||
function mergeHead(newContent, defaultMergeStrategy) {
|
||||
|
||||
if (newContent && newContent.indexOf('<head') > -1) {
|
||||
const htmlDoc = document.createElement("html");
|
||||
// remove svgs to avoid conflicts
|
||||
var contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
|
||||
// extract head tag
|
||||
var headTag = contentWithSvgsRemoved.match(/(<head(\s[^>]*>|>)([\s\S]*?)<\/head>)/im);
|
||||
|
||||
// if the head tag exists...
|
||||
if (headTag) {
|
||||
|
||||
var added = []
|
||||
var removed = []
|
||||
var preserved = []
|
||||
var nodesToAppend = []
|
||||
|
||||
htmlDoc.innerHTML = headTag;
|
||||
var newHeadTag = htmlDoc.querySelector("head");
|
||||
var currentHead = document.head;
|
||||
|
||||
if (newHeadTag == null) {
|
||||
return;
|
||||
} else {
|
||||
// put all new head elements into a Map, by their outerHTML
|
||||
var srcToNewHeadNodes = new Map();
|
||||
for (const newHeadChild of newHeadTag.children) {
|
||||
srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// determine merge strategy
|
||||
var mergeStrategy = api.getAttributeValue(newHeadTag, "hx-head") || defaultMergeStrategy;
|
||||
|
||||
// get the current head
|
||||
for (const currentHeadElt of currentHead.children) {
|
||||
|
||||
// If the current head element is in the map
|
||||
var inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
|
||||
var isReAppended = currentHeadElt.getAttribute("hx-head") === "re-eval";
|
||||
var isPreserved = api.getAttributeValue(currentHeadElt, "hx-preserve") === "true";
|
||||
if (inNewContent || isPreserved) {
|
||||
if (isReAppended) {
|
||||
// remove the current version and let the new version replace it and re-execute
|
||||
removed.push(currentHeadElt);
|
||||
} else {
|
||||
// this element already exists and should not be re-appended, so remove it from
|
||||
// the new content map, preserving it in the DOM
|
||||
srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
|
||||
preserved.push(currentHeadElt);
|
||||
}
|
||||
} else {
|
||||
if (mergeStrategy === "append") {
|
||||
// we are appending and this existing element is not new content
|
||||
// so if and only if it is marked for re-append do we do anything
|
||||
if (isReAppended) {
|
||||
removed.push(currentHeadElt);
|
||||
nodesToAppend.push(currentHeadElt);
|
||||
}
|
||||
} else {
|
||||
// if this is a merge, we remove this content since it is not in the new head
|
||||
if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: currentHeadElt}) !== false) {
|
||||
removed.push(currentHeadElt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Push the tremaining new head elements in the Map into the
|
||||
// nodes to append to the head tag
|
||||
nodesToAppend.push(...srcToNewHeadNodes.values());
|
||||
log("to append: ", nodesToAppend);
|
||||
|
||||
for (const newNode of nodesToAppend) {
|
||||
log("adding: ", newNode);
|
||||
var newElt = document.createRange().createContextualFragment(newNode.outerHTML);
|
||||
log(newElt);
|
||||
if (api.triggerEvent(document.body, "htmx:addingHeadElement", {headElement: newElt}) !== false) {
|
||||
currentHead.appendChild(newElt);
|
||||
added.push(newElt);
|
||||
}
|
||||
}
|
||||
|
||||
// remove all removed elements, after we have appended the new elements to avoid
|
||||
// additional network requests for things like style sheets
|
||||
for (const removedElement of removed) {
|
||||
if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: removedElement}) !== false) {
|
||||
currentHead.removeChild(removedElement);
|
||||
}
|
||||
}
|
||||
|
||||
api.triggerEvent(document.body, "htmx:afterHeadMerge", {added: added, kept: preserved, removed: removed});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
htmx.defineExtension("head-support", {
|
||||
init: function(apiRef) {
|
||||
// store a reference to the internal API.
|
||||
api = apiRef;
|
||||
|
||||
htmx.on('htmx:afterSwap', function(evt){
|
||||
var serverResponse = evt.detail.xhr.response;
|
||||
if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
|
||||
mergeHead(serverResponse, evt.detail.boosted ? "merge" : "append");
|
||||
}
|
||||
})
|
||||
|
||||
htmx.on('htmx:historyRestore', function(evt){
|
||||
if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
|
||||
if (evt.detail.cacheMiss) {
|
||||
mergeHead(evt.detail.serverResponse, "merge");
|
||||
} else {
|
||||
mergeHead(evt.detail.item.head, "merge");
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
htmx.on('htmx:historyItemCreated', function(evt){
|
||||
var historyItem = evt.detail.item;
|
||||
historyItem.head = document.head.outerHTML;
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
})()
|
File diff suppressed because one or more lines are too long
|
@ -1,25 +0,0 @@
|
|||
(defpackage #:hp/components/document
|
||||
(:use #:cl)
|
||||
(:local-nicknames (#:pi #:piccolo))
|
||||
(:export #:document))
|
||||
(in-package #:hp/components/document)
|
||||
|
||||
(pi:define-element document (title description)
|
||||
(pi:h
|
||||
(html :lang "ja"
|
||||
(head
|
||||
(meta :charset "UTF-8")
|
||||
(script :src "/assets/js/htmx.js")
|
||||
(script :src "/assets/js/htmx-ext/head-support.js")
|
||||
(script :src "/assets/js/alpine.js" :defer t)
|
||||
(link :rel "stylesheet" :type "text/css" :href "/assets/css/dist.css")
|
||||
(link :rel "preconnect" :href "https://fonts.googleapis.com")
|
||||
(link :rel "preconnect" :href "https://fonts.gstatic.com" :crossorigin t)
|
||||
(link
|
||||
:rel "stylesheet"
|
||||
:href "https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap")
|
||||
(title (format nil "~@[~a - ~]skyizwhite.dev" title))
|
||||
(meta
|
||||
:name "description"
|
||||
:content (or description "pakuの個人サイト")))
|
||||
pi:children)))
|
|
@ -2,7 +2,8 @@
|
|||
(:use #:cl)
|
||||
(:import-from #:log4cl)
|
||||
(:export #:dev-mode-p
|
||||
#:prod-mode-p))
|
||||
#:prod-mode-p
|
||||
#:*port*))
|
||||
(in-package #:hp/config)
|
||||
|
||||
(defparameter *env* (or (uiop:getenv "HP_ENV") "dev"))
|
||||
|
@ -13,4 +14,6 @@
|
|||
(defun prod-mode-p ()
|
||||
(string= *env* "prod"))
|
||||
|
||||
(defparameter *port* (parse-integer (or (uiop:getenv "HP_PORT") "3000")))
|
||||
|
||||
(log:config :nofile)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
(defpackage #:hp/routes/about
|
||||
(:use #:cl)
|
||||
(:local-nicknames (#:pi #:piccolo))
|
||||
(:local-nicknames (#:view #:hp/view/*))
|
||||
(:local-nicknames (#:view #:hp/view/**/*))
|
||||
(:export #:handle-get))
|
||||
(in-package #:hp/routes/about)
|
||||
|
||||
|
@ -11,9 +11,9 @@
|
|||
|
||||
(pi:define-element page ()
|
||||
(pi:h
|
||||
(section
|
||||
(section :data-css "pages/about"
|
||||
(h1 "About")
|
||||
(a :href "/" :hx-boost "true"
|
||||
(a :href "/"
|
||||
"top"))))
|
||||
|
||||
(defun handle-get (params)
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
(defpackage #:hp/routes/index
|
||||
(:use #:cl)
|
||||
(:local-nicknames (#:pi #:piccolo))
|
||||
(:local-nicknames (#:view #:hp/view/*))
|
||||
(:local-nicknames (#:view #:hp/view/**/*))
|
||||
(:export #:handle-get))
|
||||
(in-package #:hp/routes/index)
|
||||
|
||||
(pi:define-element page ()
|
||||
(pi:h
|
||||
(section
|
||||
(h1 :class "text-red-400"
|
||||
(section :data-css "pages/index"
|
||||
(h1
|
||||
"Hello, World!")
|
||||
(a :href "/about" :hx-boost "true"
|
||||
(a :href "/about"
|
||||
"About"))))
|
||||
|
||||
(defun handle-get (params)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
(:use #:cl)
|
||||
(:local-nicknames (#:jg #:jingle))
|
||||
(:local-nicknames (#:pi #:piccolo))
|
||||
(:local-nicknames (#:view #:hp/view/*))
|
||||
(:local-nicknames (#:view #:hp/view/**/*))
|
||||
(:export #:handle-not-found))
|
||||
(in-package #:hp/routes/not-found)
|
||||
|
||||
|
|
0
src/scripts/components/.keep
Normal file
0
src/scripts/components/.keep
Normal file
0
src/scripts/pages/.keep
Normal file
0
src/scripts/pages/.keep
Normal file
0
src/styles/components/.keep
Normal file
0
src/styles/components/.keep
Normal file
8
src/styles/global.css
Normal file
8
src/styles/global.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
@charset "utf-8";
|
||||
|
||||
body {
|
||||
font-family: "Noto Sans JP", sans-serif;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
8
src/styles/pages/about.css
Normal file
8
src/styles/pages/about.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
@scope ([data-css='pages/about']) {
|
||||
:scope {
|
||||
height: 100svh;
|
||||
background-color: thistle;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
}
|
||||
}
|
8
src/styles/pages/index.css
Normal file
8
src/styles/pages/index.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
@scope ([data-css='pages/index']) {
|
||||
:scope {
|
||||
height: 100svh;
|
||||
background-color: aliceblue;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
}
|
||||
}
|
73
src/view/components/document.lisp
Normal file
73
src/view/components/document.lisp
Normal file
|
@ -0,0 +1,73 @@
|
|||
(defpackage #:hp/view/components/document
|
||||
(:use #:cl)
|
||||
(:local-nicknames (#:re #:cl-ppcre))
|
||||
(:local-nicknames (#:pi #:piccolo))
|
||||
(:export #:document))
|
||||
(in-package #:hp/view/components/document)
|
||||
|
||||
(defun detect-data-props (html-str data-prop-name)
|
||||
(remove-duplicates (re:all-matches-as-strings (format nil
|
||||
"(?<=~a=\")[^\"]*(?=\")"
|
||||
data-prop-name)
|
||||
html-str)
|
||||
:test #'string=))
|
||||
|
||||
(defun data-props->asset-links (parent-path extension data-props)
|
||||
(mapcar (lambda (data-prop)
|
||||
(concatenate 'string parent-path data-prop extension))
|
||||
data-props))
|
||||
|
||||
(defun get-css-links (html-str)
|
||||
(data-props->asset-links "/styles/"
|
||||
".css"
|
||||
(detect-data-props html-str "data-css")))
|
||||
|
||||
(defun get-js-links (html-str)
|
||||
(data-props->asset-links "/scripts/"
|
||||
".js"
|
||||
(detect-data-props html-str "data-js")))
|
||||
|
||||
(pi:define-element scripts (srcs)
|
||||
(pi:h
|
||||
(<>
|
||||
(mapcar (lambda (src)
|
||||
(script :src src))
|
||||
srcs))))
|
||||
|
||||
(pi:define-element stylesheets (hrefs)
|
||||
(pi:h
|
||||
(<>
|
||||
(mapcar (lambda (href)
|
||||
(link :rel "stylesheet" :type "text/css" :href href))
|
||||
hrefs))))
|
||||
|
||||
(pi:define-element on-demand-assets (component)
|
||||
(let* ((pi:*escape-html* nil)
|
||||
(html-str (pi:elem-str component))
|
||||
(css-links (get-css-links html-str))
|
||||
(js-links (get-js-links html-str)))
|
||||
(pi:h
|
||||
(<>
|
||||
(stylesheets :hrefs css-links)
|
||||
(scripts :srcs js-links)))))
|
||||
|
||||
(pi:define-element document (title description)
|
||||
(pi:h
|
||||
(html :lang "ja"
|
||||
(head
|
||||
(meta :charset "UTF-8")
|
||||
(link :rel "stylesheet" :type "text/css" :href "/vendor/ress.css")
|
||||
(link :rel "stylesheet" :type "text/css" :href "/styles/global.css")
|
||||
(link :rel "preconnect" :href "https://fonts.googleapis.com")
|
||||
(link :rel "preconnect" :href "https://fonts.gstatic.com" :crossorigin t)
|
||||
(link
|
||||
:rel "stylesheet"
|
||||
:href "https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap")
|
||||
(on-demand-assets :component pi:children)
|
||||
(script :src "/vendor/htmx@1.9.12.js")
|
||||
(script :src "/vendor/alpine@3.13.8.js" :defer t)
|
||||
(title (format nil "~@[~a - ~]skyizwhite.dev" title))
|
||||
(meta
|
||||
:name "description"
|
||||
:content (or description "pakuの個人サイト")))
|
||||
pi:children)))
|
|
@ -1,12 +1,12 @@
|
|||
(defpackage #:hp/components/layout
|
||||
(defpackage #:hp/view/components/layout
|
||||
(:use #:cl)
|
||||
(:local-nicknames (#:pi #:piccolo))
|
||||
(:export #:layout))
|
||||
(in-package #:hp/components/layout)
|
||||
(in-package #:hp/view/components/layout)
|
||||
|
||||
(pi:define-element layout ()
|
||||
(pi:h
|
||||
(body :hx-ext "head-support"
|
||||
(body
|
||||
; header
|
||||
(main pi:children)
|
||||
; footer
|
|
@ -3,18 +3,18 @@
|
|||
(:local-nicknames (#:jg #:jingle))
|
||||
(:local-nicknames (#:pi #:piccolo))
|
||||
(:local-nicknames (#:cfg #:hp/config))
|
||||
(:local-nicknames (#:cmp #:hp/components/*))
|
||||
(:local-nicknames (#:cmp #:hp/view/components/*))
|
||||
(:export #:render
|
||||
#:partial-render))
|
||||
(in-package #:hp/view/renderer)
|
||||
|
||||
(defun render (page &key status metadata)
|
||||
(jg:with-html-response
|
||||
(jg:set-response-status (or status :ok))
|
||||
(if status (jg:set-response-status status))
|
||||
(pi:elem-str (cmp:document metadata
|
||||
(cmp:layout page)))))
|
||||
|
||||
(defun partial-render (component &key status)
|
||||
(jg:with-html-response
|
||||
(jg:set-response-status (or status :ok))
|
||||
(if status (jg:set-response-status status))
|
||||
(pi:elem-str component)))
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/routes/**/*.lisp",
|
||||
"./src/components/**/*.lisp"
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue