├── .gitignore ├── LICENSE ├── README.markdown ├── app.lisp ├── cl-trello-clone-test.asd ├── cl-trello-clone.asd ├── cl-trello-demo.gif ├── db └── schema.sql ├── src ├── config.lisp ├── db.lisp ├── main.lisp ├── view.lisp └── web.lisp ├── static └── css │ └── main.css ├── templates ├── _add-card.html ├── _add-list.html ├── _board.html ├── _card.html ├── _edit-card.html ├── _errors │ └── 404.html ├── _new-card.html ├── _new-list.html ├── _toggle-add-card.html ├── index.html ├── layouts │ └── default.html ├── svg-close.html ├── svg-edit.html └── svg-plus.html └── tests └── cl-trello-clone.lisp /.gitignore: -------------------------------------------------------------------------------- 1 | *.fasl 2 | *.dx32fsl 3 | *.dx64fsl 4 | *.lx32fsl 5 | *.lx64fsl 6 | *.x86f 7 | *~ 8 | .#* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Rajasegar Chandran 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # cl-trello-clone 2 | 3 | A small Trello clone built in Common Lisp 4 | 5 | ![cl-trello-demo.gif](cl-trello-demo.gif) 6 | 7 | 8 | ## Usage 9 | ```lisp 10 | (ql:quickload :cl-trello-clone) 11 | (cl-trello-clone:start :port 3000) 12 | ``` 13 | 14 | Go to `http://localhost:3000` in your browser to see the demo in action. 15 | 16 | ## Installation 17 | ``` 18 | cd ~/quicklisp/local-projects 19 | git clone https://github.com/rajasegar/cl-trello-clone 20 | ``` 21 | 22 | ## Author 23 | 24 | * Rajasegar Chandran 25 | 26 | ## Copyright 27 | 28 | Copyright (c) 2021 Rajasegar Chandran 29 | 30 | -------------------------------------------------------------------------------- /app.lisp: -------------------------------------------------------------------------------- 1 | (ql:quickload :cl-trello-clone) 2 | 3 | (defpackage cl-trello-clone.app 4 | (:use :cl) 5 | (:import-from :lack.builder 6 | :builder) 7 | (:import-from :ppcre 8 | :scan 9 | :regex-replace) 10 | (:import-from :cl-trello-clone.web 11 | :*web*) 12 | (:import-from :cl-trello-clone.config 13 | :config 14 | :productionp 15 | :*static-directory*)) 16 | (in-package :cl-trello-clone.app) 17 | 18 | (builder 19 | (:static 20 | :path (lambda (path) 21 | (if (ppcre:scan "^(?:/images/|/css/|/js/|/robot\\.txt$|/favicon\\.ico$)" path) 22 | path 23 | nil)) 24 | :root *static-directory*) 25 | (if (productionp) 26 | nil 27 | :accesslog) 28 | (if (getf (config) :error-log) 29 | `(:backtrace 30 | :output ,(getf (config) :error-log)) 31 | nil) 32 | :session 33 | (if (productionp) 34 | nil 35 | (lambda (app) 36 | (lambda (env) 37 | (let ((datafly:*trace-sql* t)) 38 | (funcall app env))))) 39 | *web*) 40 | -------------------------------------------------------------------------------- /cl-trello-clone-test.asd: -------------------------------------------------------------------------------- 1 | (defsystem "cl-trello-clone-test" 2 | :defsystem-depends-on ("prove-asdf") 3 | :author "Rajasegar Chandran" 4 | :license "" 5 | :depends-on ("cl-trello-clone" 6 | "prove") 7 | :components ((:module "tests" 8 | :components 9 | ((:test-file "cl-trello-clone")))) 10 | :description "Test system for cl-trello-clone" 11 | :perform (test-op (op c) (symbol-call :prove-asdf :run-test-system c))) 12 | -------------------------------------------------------------------------------- /cl-trello-clone.asd: -------------------------------------------------------------------------------- 1 | (defsystem "cl-trello-clone" 2 | :version "0.1.0" 3 | :author "Rajasegar Chandran" 4 | :license "MIT" 5 | :depends-on ("clack" 6 | "lack" 7 | "caveman2" 8 | "envy" 9 | "cl-ppcre" 10 | "uiop" 11 | 12 | ;; for @route annotation 13 | "cl-syntax-annot" 14 | 15 | ;; HTML Template 16 | "djula" 17 | 18 | ;; for DB 19 | "datafly" 20 | "sxql") 21 | :components ((:module "src" 22 | :components 23 | ((:file "main" :depends-on ("config" "view" "db")) 24 | (:file "web" :depends-on ("view")) 25 | (:file "view" :depends-on ("config")) 26 | (:file "db" :depends-on ("config")) 27 | (:file "config")))) 28 | :description "" 29 | :in-order-to ((test-op (test-op "cl-trello-clone-test")))) 30 | -------------------------------------------------------------------------------- /cl-trello-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajasegar/cl-trello-clone/8e3f1769b2ecab374146470d6ecaac2635dd8376/cl-trello-demo.gif -------------------------------------------------------------------------------- /db/schema.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajasegar/cl-trello-clone/8e3f1769b2ecab374146470d6ecaac2635dd8376/db/schema.sql -------------------------------------------------------------------------------- /src/config.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-user) 2 | (defpackage cl-trello-clone.config 3 | (:use :cl) 4 | (:import-from :envy 5 | :config-env-var 6 | :defconfig) 7 | (:export :config 8 | :*application-root* 9 | :*static-directory* 10 | :*template-directory* 11 | :appenv 12 | :developmentp 13 | :productionp)) 14 | (in-package :cl-trello-clone.config) 15 | 16 | (setf (config-env-var) "APP_ENV") 17 | 18 | (defparameter *application-root* (asdf:system-source-directory :cl-trello-clone)) 19 | (defparameter *static-directory* (merge-pathnames #P"static/" *application-root*)) 20 | (defparameter *template-directory* (merge-pathnames #P"templates/" *application-root*)) 21 | 22 | (defconfig :common 23 | `(:databases ((:maindb :sqlite3 :database-name ":memory:")))) 24 | 25 | (defconfig |development| 26 | '()) 27 | 28 | (defconfig |production| 29 | '()) 30 | 31 | (defconfig |test| 32 | '()) 33 | 34 | (defun config (&optional key) 35 | (envy:config #.(package-name *package*) key)) 36 | 37 | (defun appenv () 38 | (uiop:getenv (config-env-var #.(package-name *package*)))) 39 | 40 | (defun developmentp () 41 | (string= (appenv) "development")) 42 | 43 | (defun productionp () 44 | (string= (appenv) "production")) 45 | -------------------------------------------------------------------------------- /src/db.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-user) 2 | (defpackage cl-trello-clone.db 3 | (:use :cl) 4 | (:import-from :cl-trello-clone.config 5 | :config) 6 | (:import-from :datafly 7 | :*connection*) 8 | (:import-from :cl-dbi 9 | :connect-cached) 10 | (:export :connection-settings 11 | :db 12 | :with-connection)) 13 | (in-package :cl-trello-clone.db) 14 | 15 | (defun connection-settings (&optional (db :maindb)) 16 | (cdr (assoc db (config :databases)))) 17 | 18 | (defun db (&optional (db :maindb)) 19 | (apply #'connect-cached (connection-settings db))) 20 | 21 | (defmacro with-connection (conn &body body) 22 | `(let ((*connection* ,conn)) 23 | ,@body)) 24 | -------------------------------------------------------------------------------- /src/main.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-user) 2 | (defpackage cl-trello-clone 3 | (:use :cl) 4 | (:import-from :cl-trello-clone.config 5 | :config) 6 | (:import-from :clack 7 | :clackup) 8 | (:export :start 9 | :stop)) 10 | (in-package :cl-trello-clone) 11 | 12 | (defvar *appfile-path* 13 | (asdf:system-relative-pathname :cl-trello-clone #P"app.lisp")) 14 | 15 | (defvar *handler* nil) 16 | 17 | (defun start (&rest args &key server port debug &allow-other-keys) 18 | (declare (ignore server port debug)) 19 | (when *handler* 20 | (restart-case (error "Server is already running.") 21 | (restart-server () 22 | :report "Restart the server" 23 | (stop)))) 24 | (setf *handler* 25 | (apply #'clackup *appfile-path* args))) 26 | 27 | (defun stop () 28 | (prog1 29 | (clack:stop *handler*) 30 | (setf *handler* nil))) 31 | -------------------------------------------------------------------------------- /src/view.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-user) 2 | (defpackage cl-trello-clone.view 3 | (:use :cl) 4 | (:import-from :cl-trello-clone.config 5 | :*template-directory*) 6 | (:import-from :caveman2 7 | :*response* 8 | :response-headers) 9 | (:import-from :djula 10 | :add-template-directory 11 | :compile-template* 12 | :render-template* 13 | :*djula-execute-package*) 14 | (:import-from :datafly 15 | :encode-json) 16 | (:export :render 17 | :render-json)) 18 | (in-package :cl-trello-clone.view) 19 | 20 | (djula:add-template-directory *template-directory*) 21 | 22 | (defparameter *template-registry* (make-hash-table :test 'equal)) 23 | 24 | (defun render (template-path &optional env) 25 | (let ((template (gethash template-path *template-registry*))) 26 | (unless template 27 | (setf template (djula:compile-template* (princ-to-string template-path))) 28 | (setf (gethash template-path *template-registry*) template)) 29 | (apply #'djula:render-template* 30 | template nil 31 | env))) 32 | 33 | (defun render-json (object) 34 | (setf (getf (response-headers *response*) :content-type) "application/json") 35 | (encode-json object)) 36 | 37 | 38 | ;; 39 | ;; Execute package definition 40 | 41 | (defpackage cl-trello-clone.djula 42 | (:use :cl) 43 | (:import-from :cl-trello-clone.config 44 | :config 45 | :appenv 46 | :developmentp 47 | :productionp) 48 | (:import-from :caveman2 49 | :url-for)) 50 | 51 | (setf djula:*djula-execute-package* (find-package :cl-trello-clone.djula)) 52 | -------------------------------------------------------------------------------- /src/web.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-user) 2 | (defpackage cl-trello-clone.web 3 | (:use :cl 4 | :caveman2 5 | :cl-trello-clone.config 6 | :cl-trello-clone.view 7 | :cl-trello-clone.db 8 | :datafly 9 | :sxql) 10 | (:export :*web*)) 11 | (in-package :cl-trello-clone.web) 12 | 13 | ;; for @route annotation 14 | (syntax:use-syntax :annot) 15 | 16 | ;; 17 | ;; Application 18 | 19 | (defclass () ()) 20 | (defvar *web* (make-instance ')) 21 | (clear-routing-rules *web*) 22 | 23 | ;; Boards 24 | (defvar *board* '((:name "To Do" :id 1 :cards ((:id 1 :label "First Card" :list 1) 25 | (:id 2 :label "Second Card" :list 1))) 26 | (:name "Doing" :id 2 :cards ((:id 3 :label "First Card" :list 2) 27 | (:id 4 :label "Second Card" :list 2))))) 28 | 29 | ;; Utils 30 | (defun find-list (id) 31 | "find list from board based on id" 32 | (car (remove-if #'(lambda (item) 33 | (if (= (getf item :id) (parse-integer id)) 34 | nil 35 | t)) *board*))) 36 | 37 | (defun find-card (list-id card-id) 38 | "Find card from list" 39 | (let* ((x-list (find-list list-id)) 40 | (cards (getf x-list :cards))) 41 | (car (remove-if #'(lambda (item) 42 | (if (= (getf item :id) (parse-integer card-id)) 43 | nil 44 | t 45 | )) cards)))) 46 | 47 | (defun get-param (parsed name) 48 | "Get param values from parsed" 49 | (cdr (assoc name parsed :test #'string=))) 50 | 51 | ;; 52 | ;; Routing rules 53 | 54 | ;; Home page 55 | (defroute "/" () 56 | (render #P"index.html" (list :board *board*))) 57 | 58 | ;; Show add list form 59 | (defroute "/lists/add" () 60 | (render #P"_add-list.html")) 61 | 62 | ;; Cancel add list 63 | (defroute "/lists/cancel" () 64 | (render #P"_new-list.html")) 65 | 66 | ;; Add new lists 67 | (defroute ("/lists" :method :POST) (&key _parsed) 68 | (let* ((name (get-param _parsed "name")) 69 | (new-list (list :name name :id (+ 1 (length *board*)) :cards ()))) 70 | (setf *board* (append *board* (list new-list))) 71 | (render #P"_board.html" (list :board *board*)))) 72 | 73 | ;; Delete lists 74 | (defroute ("/lists/:id" :method :DELETE) (&key id) 75 | (setf *board* (remove-if #'(lambda (item) 76 | (if (= (parse-integer id) (getf item :id)) 77 | t 78 | nil)) *board*)) 79 | (render #P"_board.html" (list :board *board*))) 80 | 81 | ;; New card 82 | (defroute ("/cards/new/:list-id" :method :POST) (&key list-id _parsed) 83 | (let* ((label (get-param _parsed (concatenate 'string "label-" list-id))) 84 | (new-card (list :label label :list-id list-id :id (get-universal-time))) 85 | (x-list (find-list list-id)) 86 | (cards (getf x-list :cards))) 87 | (setf cards (append cards (list new-card)) 88 | (getf x-list :cards) cards) 89 | (render #P"_new-card.html" (list :card new-card :list (find-list list-id))))) 90 | 91 | 92 | ;; Edit card 93 | (defroute "/cards/edit/:list-id/:id" (&key list-id id) 94 | (render #P"_edit-card.html" (list 95 | :id id 96 | :list (find-list list-id) 97 | :card (find-card list-id id)))) 98 | 99 | ;; Cancel edit card 100 | (defroute "/cards/cancel-edit/:list-id/:id" (&key list-id id) 101 | (render #P"_card.html" (list 102 | :id id 103 | :list (find-list list-id) 104 | :card (find-card list-id id)))) 105 | 106 | ;; Update card 107 | (defroute ("/cards/:list-id/:id" :method :PUT) (&key list-id id _parsed) 108 | (let ((label (get-param _parsed "label")) 109 | (card (find-card list-id id))) 110 | (setf (getf card :label) label) 111 | (render #P"_card.html" (list :card card)))) 112 | 113 | ;; Delete card 114 | (defroute ("/cards/:list-id/:id" :method :DELETE) (&key list-id id) 115 | (let* ((x-list (find-list list-id)) 116 | (cards (remove-if #'(lambda (item) 117 | (if (= (parse-integer id) (getf item :id)) 118 | t 119 | nil)) (getf x-list :cards)))) 120 | (setf (getf x-list :cards) cards) 121 | "")) 122 | 123 | ;; Move cards 124 | (defroute ("/cards/move" :method :POST) (&key _parsed) 125 | (let* ((from (second (cl-ppcre:split "-" (get-param _parsed "from")))) 126 | (to (second (cl-ppcre:split "-" (get-param _parsed "to")))) 127 | (card-id (second (cl-ppcre:split "-" (get-param _parsed "movedCard")))) 128 | (from-list (find-list from)) 129 | (to-list (find-list to)) 130 | (card (find-card from card-id))) 131 | ;; Point card to the new list 132 | (setf (getf card :list) to) 133 | ;; Remove card from old list 134 | (setf (getf from-list :cards) (remove-if #'(lambda (item) 135 | (if (= (parse-integer card-id) (getf item :id)) 136 | t 137 | nil)) (getf from-list :cards))) 138 | ;; Push card to new list 139 | (push card (getf to-list :cards)) 140 | (render #P"_board.html" (list :board *board*)))) 141 | 142 | ;; 143 | ;; Error pages 144 | 145 | (defmethod on-exception ((app ) (code (eql 404))) 146 | (declare (ignore app)) 147 | (merge-pathnames #P"_errors/404.html" 148 | *template-directory*)) 149 | -------------------------------------------------------------------------------- /static/css/main.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | body { 4 | margin: 0; 5 | padding: 0; 6 | font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale 9 | } 10 | 11 | code { 12 | font-family: source-code-pro,Menlo,Monaco,Consolas,"Courier New",monospace 13 | } 14 | 15 | .app { 16 | background: #3465a4; 17 | height: 100vh 18 | } 19 | 20 | .header { 21 | background: rgba(0,0,0,.15); 22 | color: #fff; 23 | padding: 5px 0; 24 | border-bottom: 1px solid rgba(0,0,0,.12); 25 | text-align: center; 26 | font-size: 40px; 27 | font-weight: 200 28 | } 29 | 30 | .board { 31 | height: 92%; 32 | display: -webkit-flex; 33 | display: flex; 34 | overflow-x: auto 35 | } 36 | 37 | .add-list { 38 | width: 272px; 39 | margin: 10px; 40 | -webkit-flex-shrink: 0; 41 | flex-shrink: 0 42 | } 43 | 44 | .add-list-button { 45 | background-color: rgba(0,0,0,.12); 46 | border-radius: 4px; 47 | cursor: pointer; 48 | color: #fff; 49 | display: -webkit-flex; 50 | display: flex; 51 | -webkit-align-items: center; 52 | align-items: center; 53 | min-height: 32px; 54 | padding: 5px 8px; 55 | transition: background-color 85ms ease-in,opacity 40ms ease-in,border-color 85ms ease-in; 56 | height: -webkit-fit-content; 57 | height: -moz-fit-content; 58 | height: fit-content 59 | } 60 | 61 | .add-list-button:hover { 62 | background-color: rgba(0,0,0,.24) 63 | } 64 | 65 | 66 | .list { 67 | background: #dfe3e6; 68 | -webkit-flex-shrink: 0; 69 | flex-shrink: 0; 70 | width: 272px; 71 | height: -webkit-fit-content; 72 | height: -moz-fit-content; 73 | height: fit-content; 74 | margin: 10px 0 10px 10px; 75 | border-radius: 4px; 76 | border: 1px solid rgba(0,0,0,.12) 77 | } 78 | 79 | .list-title { 80 | cursor: pointer; 81 | padding: 10px; 82 | overflow-wrap: break-word; 83 | display: flex; 84 | justify-content: space-between; 85 | position: relative; 86 | } 87 | 88 | .toggle-add-card { 89 | cursor: pointer; 90 | padding: 10px; 91 | color: #333; 92 | border-radius: 0 0 4px 4px; 93 | display: -webkit-flex; 94 | display: flex; 95 | -webkit-align-items: center; 96 | align-items: center; 97 | border: none; 98 | width: 100%; 99 | } 100 | 101 | .toggle-add-card:hover { 102 | background-color: rgba(9,45,66,.13); 103 | color: #17394d; 104 | text-decoration: underline 105 | } 106 | 107 | 108 | .card { 109 | position: relative; 110 | cursor: grab; 111 | background: #fff; 112 | margin: 5px; 113 | padding: 10px; 114 | border-radius: 5px; 115 | border: 1px solid rgba(0,0,0,.12); 116 | box-shadow: 0 1px 0 rgba(9,45,66,.25); 117 | font-size: 15px; 118 | overflow-wrap: break-word; 119 | min-height: 18px 120 | } 121 | 122 | .card:hover { 123 | background: #f5f6f7 124 | } 125 | 126 | .card-icons { 127 | position: absolute; 128 | top: 5px; 129 | right: 5px; 130 | -webkit-flex-direction: row; 131 | flex-direction: row; 132 | -webkit-justify-content: flex-end; 133 | justify-content: flex-end 134 | } 135 | 136 | .card-icon,.card-icons { 137 | display: -webkit-flex; 138 | display: flex 139 | } 140 | 141 | .card-icon { 142 | cursor: pointer; 143 | width: 24px; 144 | height: 24px; 145 | -webkit-align-items: center; 146 | align-items: center; 147 | -webkit-justify-content: center; 148 | justify-content: center; 149 | border-radius: 5px; 150 | margin: 1px; 151 | color: rgba(0,0,0,.5); 152 | background: #f5f6f7; 153 | opacity: .9; 154 | border: none; 155 | } 156 | 157 | .card-icon:hover { 158 | opacity: 1; 159 | background: #dcdcdc 160 | } 161 | 162 | .edit-card .card { 163 | min-height: 50px; 164 | padding-left: 8px; 165 | padding-right: 15px 166 | } 167 | 168 | .edit-card .card:hover { 169 | background: #fff 170 | } 171 | 172 | .edit-card-textarea { 173 | width: 100%; 174 | border: none; 175 | resize: none; 176 | outline: none; 177 | font-size: 15px; 178 | height:34px; 179 | } 180 | 181 | .edit-buttons { 182 | display: -webkit-flex; 183 | display: flex 184 | } 185 | 186 | .edit-button { 187 | cursor: pointer; 188 | box-shadow: 0 1px 0 0 #3f6f21; 189 | width: -webkit-fit-content; 190 | width: -moz-fit-content; 191 | width: fit-content; 192 | margin: 0 5px 10px; 193 | padding: 6px 12px; 194 | border-radius: 5px; 195 | border: none; 196 | color: #fff; 197 | outline: none; 198 | background-color: rgb(90,172,68); 199 | } 200 | 201 | .edit-button:hover { 202 | opacity: .7 203 | } 204 | 205 | .edit-button-cancel { 206 | cursor: pointer; 207 | margin-bottom: 10px; 208 | display: -webkit-flex; 209 | display: flex; 210 | -webkit-align-items: center; 211 | align-items: center; 212 | font-size: 20px; 213 | opacity: .5; 214 | outline: none; 215 | padding:0; 216 | border: none; 217 | } 218 | 219 | .edit-button-cancel:hover { 220 | opacity: 1 221 | } 222 | 223 | .list-title-edit { 224 | display: -webkit-flex; 225 | display: flex; 226 | -webkit-align-items: center; 227 | align-items: center 228 | } 229 | 230 | 231 | .list-title-textarea { 232 | margin: 6px 0 5px 6px; 233 | border-radius: 3px; 234 | border: none; 235 | resize: none; 236 | outline: none; 237 | font-size: 15px; 238 | padding: 5px; 239 | width: 245px; 240 | height: 17px; 241 | } 242 | 243 | .list-title-textarea:focus { 244 | box-shadow: inset 0 0 0 2px #0079bf 245 | } 246 | 247 | .add-list-editor { 248 | background: #dfe3e6; 249 | border-radius: 5px; 250 | padding: 2px 251 | } 252 | 253 | 254 | 255 | .hidden { 256 | display: none; 257 | } 258 | 259 | .list-close-icon { 260 | cursor: pointer; 261 | height: 24px; 262 | border-radius: 5px; 263 | color: black; 264 | font-weight: bolder; 265 | opacity: .9; 266 | border: none; 267 | padding: 0; 268 | margin: 0; 269 | position: absolute; 270 | top: 4px; 271 | right: 4px; 272 | } 273 | 274 | .delete-button { 275 | background-color: rgb(234, 37, 37); 276 | } 277 | -------------------------------------------------------------------------------- /templates/_add-card.html: -------------------------------------------------------------------------------- 1 | 35 | -------------------------------------------------------------------------------- /templates/_add-list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | 8 | 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /templates/_board.html: -------------------------------------------------------------------------------- 1 | {% for list in board %} 2 |
3 |
4 |
{{list.name}}
5 | 10 |
11 |
12 | {% for card in list.cards %} 13 |
21 | 32 | {{card.label}} 33 |
34 | {% endfor %} 35 |
36 | {% include "_toggle-add-card.html" %} 37 | {% include "_add-card.html" %} 38 |
39 | {% endfor %} 40 |
41 | {% include "_new-list.html" %} 42 |
43 | -------------------------------------------------------------------------------- /templates/_card.html: -------------------------------------------------------------------------------- 1 |
8 | {{card.label}} 9 | 14 |
15 | -------------------------------------------------------------------------------- /templates/_edit-card.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 |
7 | 12 | 18 |
19 | {% include "svg-close.html" %} 20 |
21 |
22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /templates/_errors/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 NOT FOUND 6 | 38 | 39 | 40 |
41 |
42 |
404
43 |
NOT FOUND
44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /templates/_new-card.html: -------------------------------------------------------------------------------- 1 |
2 |
9 | 14 | {{card.label}} 15 |
16 |
17 | -------------------------------------------------------------------------------- /templates/_new-list.html: -------------------------------------------------------------------------------- 1 |
2 | {% include "svg-plus.html" %} 3 | Add another list 4 |
5 | -------------------------------------------------------------------------------- /templates/_toggle-add-card.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/default.html" %} 2 | {% block title %}Common Lisp - Trello{% endblock %} 3 | {% block content %} 4 | {% include "_board.html" %} 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /templates/layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}{% endblock %} 6 | 7 | 8 | 9 |
10 |
Common Lisp - Trello Clone
11 |
12 | 13 | 14 | 15 |
16 | {% block content %}{% endblock %} 17 |
18 |
19 | 20 | 21 | 22 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /templates/svg-close.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /templates/svg-edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /templates/svg-plus.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/cl-trello-clone.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-user) 2 | (defpackage cl-trello-clone-test 3 | (:use :cl 4 | :cl-trello-clone 5 | :prove)) 6 | (in-package :cl-trello-clone-test) 7 | 8 | (plan nil) 9 | 10 | ;; blah blah blah. 11 | 12 | (finalize) 13 | --------------------------------------------------------------------------------