Added checklists to database, rendering and toggling items
This commit is contained in:
parent
5c27f9ed1c
commit
f98a6ab863
24
main.go
24
main.go
@ -104,6 +104,7 @@ func main() { // {{{
|
||||
service.Register("/node/delete", true, true, nodeDelete)
|
||||
service.Register("/node/download", true, true, nodeDownload)
|
||||
service.Register("/node/search", true, true, nodeSearch)
|
||||
service.Register("/node/checklist_item/state", true, true, nodeChecklistItemState)
|
||||
service.Register("/key/retrieve", true, true, keyRetrieve)
|
||||
service.Register("/key/create", true, true, keyCreate)
|
||||
service.Register("/key/counter", true, true, keyCounter)
|
||||
@ -477,6 +478,29 @@ func nodeSearch(w http.ResponseWriter, r *http.Request, sess *session.T) { // {{
|
||||
"Nodes": nodes,
|
||||
})
|
||||
} // }}}
|
||||
func nodeChecklistItemState(w http.ResponseWriter, r *http.Request, sess *session.T) { // {{{
|
||||
logger.Info("webserver", "request", "/node/checklist_item/state")
|
||||
var err error
|
||||
|
||||
req := struct {
|
||||
ChecklistItemID int
|
||||
State bool
|
||||
}{}
|
||||
if err = parseRequest(r, &req); err != nil {
|
||||
responseError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = ChecklistItemState(sess.UserID, req.ChecklistItemID, req.State)
|
||||
if err != nil {
|
||||
responseError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
responseData(w, map[string]interface{}{
|
||||
"OK": true,
|
||||
})
|
||||
} // }}}
|
||||
|
||||
func keyRetrieve(w http.ResponseWriter, r *http.Request, sess *session.T) { // {{{
|
||||
logger.Info("webserver", "request", "/key/retrieve")
|
||||
|
111
node.go
111
node.go
@ -8,6 +8,22 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ChecklistItem struct {
|
||||
ID int
|
||||
GroupID int `db:"checklist_group_id"`
|
||||
Order int
|
||||
Label string
|
||||
Checked bool
|
||||
}
|
||||
|
||||
type ChecklistGroup struct {
|
||||
ID int
|
||||
NodeID int `db:"node_id"`
|
||||
Order int
|
||||
Label string
|
||||
Items []ChecklistItem
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
ID int
|
||||
UserID int `db:"user_id"`
|
||||
@ -22,6 +38,8 @@ type Node struct {
|
||||
Complete bool
|
||||
Level int
|
||||
|
||||
ChecklistGroups []ChecklistGroup
|
||||
|
||||
ContentEncrypted string `db:"content_encrypted" json:"-"`
|
||||
Markdown bool
|
||||
}
|
||||
@ -211,6 +229,8 @@ func RetrieveNode(userID, nodeID int) (node Node, err error) { // {{{
|
||||
} else {
|
||||
node.Content = row.Content
|
||||
}
|
||||
|
||||
node.retrieveChecklist()
|
||||
}
|
||||
|
||||
if row.Level == 1 {
|
||||
@ -421,5 +441,96 @@ func SearchNodes(userID int, search string) (nodes []Node, err error) { // {{{
|
||||
|
||||
return
|
||||
} // }}}
|
||||
func ChecklistItemState(userID, checklistItemID int, state bool) (err error) {// {{{
|
||||
_, err = service.Db.Conn.Exec(
|
||||
`
|
||||
UPDATE checklist_item i
|
||||
SET checked = $3
|
||||
FROM checklist_group g, node n
|
||||
WHERE
|
||||
i.checklist_group_id = g.id AND
|
||||
g.node_id = n.id AND
|
||||
n.user_id = $1 AND
|
||||
i.id = $2;
|
||||
`,
|
||||
userID,
|
||||
checklistItemID,
|
||||
state,
|
||||
)
|
||||
return
|
||||
}// }}}
|
||||
|
||||
func (node *Node) retrieveChecklist() (err error) { // {{{
|
||||
var rows *sqlx.Rows
|
||||
rows, err = service.Db.Conn.Queryx(`
|
||||
SELECT
|
||||
g.id AS group_id,
|
||||
g.order AS group_order,
|
||||
g.label AS group_label,
|
||||
|
||||
i.id AS item_id,
|
||||
i.order AS item_order,
|
||||
i.label AS item_label,
|
||||
i.checked
|
||||
|
||||
FROM public.checklist_group g
|
||||
LEFT JOIN public.checklist_item i ON i.checklist_group_id = g.id
|
||||
WHERE
|
||||
g.node_id = $1
|
||||
ORDER BY
|
||||
g.order DESC,
|
||||
i.order DESC
|
||||
`, node.ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
groups := make(map[int]*ChecklistGroup)
|
||||
var found bool
|
||||
var group *ChecklistGroup
|
||||
var item ChecklistItem
|
||||
for rows.Next() {
|
||||
row := struct {
|
||||
GroupID int `db:"group_id"`
|
||||
GroupOrder int `db:"group_order"`
|
||||
GroupLabel string `db:"group_label"`
|
||||
|
||||
ItemID int `db:"item_id"`
|
||||
ItemOrder int `db:"item_order"`
|
||||
ItemLabel string `db:"item_label"`
|
||||
Checked bool
|
||||
}{}
|
||||
err = rows.StructScan(&row)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if group, found = groups[row.GroupID]; !found {
|
||||
group = new(ChecklistGroup)
|
||||
group.ID = row.GroupID
|
||||
group.NodeID = node.ID
|
||||
group.Order = row.GroupOrder
|
||||
group.Label = row.GroupLabel
|
||||
group.Items = []ChecklistItem{}
|
||||
groups[group.ID] = group
|
||||
}
|
||||
|
||||
item = ChecklistItem{}
|
||||
item.ID = row.ItemID
|
||||
item.GroupID = row.GroupID
|
||||
item.Order = row.ItemOrder
|
||||
item.Label = row.ItemLabel
|
||||
item.Checked = row.Checked
|
||||
group.Items = append(group.Items, item)
|
||||
}
|
||||
|
||||
node.ChecklistGroups = []ChecklistGroup{}
|
||||
for _, group := range groups {
|
||||
node.ChecklistGroups = append(node.ChecklistGroups, *group)
|
||||
}
|
||||
|
||||
return
|
||||
} // }}}
|
||||
|
||||
// vim: foldmethod=marker
|
||||
|
18
sql/00014.sql
Normal file
18
sql/00014.sql
Normal file
@ -0,0 +1,18 @@
|
||||
CREATE TABLE checklist_group (
|
||||
id serial NOT NULL,
|
||||
node_id int4 NOT NULL,
|
||||
"order" int NOT NULL DEFAULT 0,
|
||||
label varchar NOT NULL,
|
||||
CONSTRAINT checklist_group_pk PRIMARY KEY (id),
|
||||
CONSTRAINT checklist_group_node_fk FOREIGN KEY (node_id) REFERENCES public."node"(id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE checklist_item (
|
||||
id serial NOT NULL,
|
||||
checklist_group_id int4 NOT NULL,
|
||||
"order" int NOT NULL DEFAULT 0,
|
||||
label varchar NOT NULL,
|
||||
checked bool NOT NULL DEFAULT false,
|
||||
CONSTRAINT checklist_item_pk PRIMARY KEY (id),
|
||||
CONSTRAINT checklist_group_item_fk FOREIGN KEY (checklist_group_id) REFERENCES public."checklist_group"(id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
)
|
@ -26,12 +26,12 @@ body {
|
||||
height: 100%;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 0px;
|
||||
font-size: 1.5em;
|
||||
color: #518048;
|
||||
}
|
||||
h2 {
|
||||
margin-top: 32px;
|
||||
font-size: 1.25em;
|
||||
color: #518048;
|
||||
}
|
||||
button {
|
||||
font-size: 1em;
|
||||
@ -307,10 +307,15 @@ header .menu {
|
||||
padding-top: 16px;
|
||||
}
|
||||
#markdown {
|
||||
padding: 16px;
|
||||
color: #333;
|
||||
grid-area: content;
|
||||
justify-self: center;
|
||||
width: calc(100% - 32px);
|
||||
max-width: 900px;
|
||||
padding: 0.5rem;
|
||||
border-radius: 8px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
#markdown table {
|
||||
border-collapse: collapse;
|
||||
@ -330,6 +335,68 @@ header .menu {
|
||||
padding: 0px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
#checklist {
|
||||
grid-area: checklist;
|
||||
color: #333;
|
||||
justify-self: center;
|
||||
width: calc(100% - 32px);
|
||||
max-width: 900px;
|
||||
padding: 0.5rem;
|
||||
border-radius: 8px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
#checklist .checklist-group {
|
||||
margin-top: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
#checklist .checklist-item {
|
||||
display: grid;
|
||||
grid-template-columns: min-content 1fr;
|
||||
align-items: center;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
#checklist .checklist-item.checked {
|
||||
text-decoration: line-through;
|
||||
color: #888;
|
||||
}
|
||||
#checklist .checklist-item input[type="checkbox"] {
|
||||
margin-left: 0px;
|
||||
margin-right: 8px;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background-color: #fff;
|
||||
margin: 0 8px 0 0;
|
||||
font: inherit;
|
||||
color: currentColor;
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
border: 0.15em solid currentColor;
|
||||
border-radius: 0.15em;
|
||||
transform: translateY(-0.075em);
|
||||
display: grid;
|
||||
place-content: center;
|
||||
}
|
||||
#checklist .checklist-item input[type="checkbox"].ok {
|
||||
border: 0.15em solid #54b356;
|
||||
}
|
||||
#checklist .checklist-item input[type="checkbox"].ok::before {
|
||||
box-shadow: inset 1em 1em #54b356;
|
||||
}
|
||||
#checklist .checklist-item input[type="checkbox"]::before {
|
||||
content: "";
|
||||
width: 0.7em;
|
||||
height: 0.7em;
|
||||
transform: scale(0);
|
||||
transition: 120ms transform ease-in-out;
|
||||
box-shadow: inset 1em 1em #666;
|
||||
}
|
||||
#checklist .checklist-item input[type="checkbox"]:checked::before {
|
||||
transform: scale(1);
|
||||
}
|
||||
#checklist .checklist-item label {
|
||||
user-select: none;
|
||||
}
|
||||
/* ============================================================= *
|
||||
* Textarea replicates the height of an element expanding height *
|
||||
* ============================================================= */
|
||||
@ -476,9 +543,9 @@ header .menu {
|
||||
}
|
||||
.layout-tree {
|
||||
display: grid;
|
||||
grid-template-areas: "header header" "tree crumbs" "tree child-nodes" "tree name" "tree content" "tree files" "tree blank";
|
||||
grid-template-areas: "header header" "tree crumbs" "tree child-nodes" "tree name" "tree content" "tree checklist" "tree files" "tree blank";
|
||||
grid-template-columns: min-content 1fr;
|
||||
grid-template-rows: min-content /* header */ min-content /* crumbs */ min-content /* child-nodes */ min-content /* name */ min-content /* content */ min-content /* files */ 1fr;
|
||||
grid-template-rows: min-content /* header */ min-content /* crumbs */ min-content /* child-nodes */ min-content /* name */ min-content /* content */ min-content /* checklist */ min-content /* files */ 1fr;
|
||||
/* blank */
|
||||
color: #fff;
|
||||
min-height: 100%;
|
||||
@ -511,14 +578,17 @@ header .menu {
|
||||
display: block;
|
||||
}
|
||||
.layout-crumbs {
|
||||
grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "files" "blank";
|
||||
grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "checklist" "files" "blank";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: min-content /* header */ min-content /* crumbs */ min-content /* child-nodes */ min-content /* name */ min-content /* content */ min-content /* files */ 1fr;
|
||||
grid-template-rows: min-content /* header */ min-content /* crumbs */ min-content /* child-nodes */ min-content /* name */ min-content /* content */ min-content /* checklist */ min-content /* files */ 1fr;
|
||||
/* blank */
|
||||
}
|
||||
.layout-crumbs #tree {
|
||||
display: none;
|
||||
}
|
||||
.layout-crumbs #checklist {
|
||||
padding: 16px;
|
||||
}
|
||||
.layout-keys {
|
||||
display: grid;
|
||||
grid-template-areas: "header" "keys";
|
||||
@ -557,22 +627,25 @@ header .menu {
|
||||
}
|
||||
#app.node {
|
||||
display: grid;
|
||||
grid-template-areas: "header header" "tree crumbs" "tree child-nodes" "tree name" "tree content" "tree files" "tree blank";
|
||||
grid-template-areas: "header header" "tree crumbs" "tree child-nodes" "tree name" "tree content" "tree checklist" "tree files" "tree blank";
|
||||
grid-template-columns: min-content 1fr;
|
||||
grid-template-rows: min-content /* header */ min-content /* crumbs */ min-content /* child-nodes */ min-content /* name */ min-content /* content */ min-content /* files */ 1fr;
|
||||
grid-template-rows: min-content /* header */ min-content /* crumbs */ min-content /* child-nodes */ min-content /* name */ min-content /* content */ min-content /* checklist */ min-content /* files */ 1fr;
|
||||
/* blank */
|
||||
color: #fff;
|
||||
min-height: 100%;
|
||||
}
|
||||
#app.node.toggle-tree {
|
||||
grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "files" "blank";
|
||||
grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "checklist" "files" "blank";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: min-content /* header */ min-content /* crumbs */ min-content /* child-nodes */ min-content /* name */ min-content /* content */ min-content /* files */ 1fr;
|
||||
grid-template-rows: min-content /* header */ min-content /* crumbs */ min-content /* child-nodes */ min-content /* name */ min-content /* content */ min-content /* checklist */ min-content /* files */ 1fr;
|
||||
/* blank */
|
||||
}
|
||||
#app.node.toggle-tree #tree {
|
||||
display: none;
|
||||
}
|
||||
#app.node.toggle-tree #checklist {
|
||||
padding: 16px;
|
||||
}
|
||||
#profile-settings {
|
||||
color: #333;
|
||||
padding: 16px;
|
||||
@ -588,14 +661,17 @@ header .menu {
|
||||
}
|
||||
@media only screen and (max-width: 932px) {
|
||||
#app.node {
|
||||
grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "files" "blank";
|
||||
grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "checklist" "files" "blank";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: min-content /* header */ min-content /* crumbs */ min-content /* child-nodes */ min-content /* name */ min-content /* content */ min-content /* files */ 1fr;
|
||||
grid-template-rows: min-content /* header */ min-content /* crumbs */ min-content /* child-nodes */ min-content /* name */ min-content /* content */ min-content /* checklist */ min-content /* files */ 1fr;
|
||||
/* blank */
|
||||
}
|
||||
#app.node #tree {
|
||||
display: none;
|
||||
}
|
||||
#app.node #checklist {
|
||||
padding: 16px;
|
||||
}
|
||||
#app.node.toggle-tree {
|
||||
display: grid;
|
||||
grid-template-areas: "header" "tree";
|
||||
@ -628,7 +704,9 @@ header .menu {
|
||||
padding: 16px;
|
||||
justify-self: start;
|
||||
}
|
||||
#file-section {
|
||||
#file-section,
|
||||
#checklist,
|
||||
#markdown {
|
||||
width: calc(100% - 32px);
|
||||
padding: 16px;
|
||||
margin-left: 16px;
|
||||
|
@ -20,6 +20,7 @@
|
||||
"node": "/js/{{ .VERSION }}/node.mjs",
|
||||
"key": "/js/{{ .VERSION }}/key.mjs",
|
||||
"crypto": "/js/{{ .VERSION }}/crypto.mjs",
|
||||
"checklist": "/js/{{ .VERSION }}/checklist.mjs",
|
||||
"ws": "/_js/{{ .VERSION }}/websocket.mjs"
|
||||
}
|
||||
}
|
||||
|
94
static/js/checklist.mjs
Normal file
94
static/js/checklist.mjs
Normal file
@ -0,0 +1,94 @@
|
||||
import { h, Component, createRef } from 'preact'
|
||||
import htm from 'htm'
|
||||
import { signal } from 'preact/signals'
|
||||
const html = htm.bind(h)
|
||||
|
||||
export class ChecklistGroup {
|
||||
static sort(a, b) {//{{{
|
||||
if (a.Order < b.Order) return -1
|
||||
if (a.Order > b.Order) return 1
|
||||
return 0
|
||||
}//}}}
|
||||
constructor(data) {//{{{
|
||||
Object.keys(data).forEach(key => {
|
||||
if (key == 'Items')
|
||||
this.items = data[key].map(itemData =>
|
||||
new ChecklistItem(itemData)
|
||||
).sort(ChecklistItem.sort)
|
||||
else
|
||||
this[key] = data[key]
|
||||
})
|
||||
}//}}}
|
||||
}
|
||||
|
||||
export class ChecklistItem {
|
||||
static sort(a, b) {//{{{
|
||||
if (a.Order < b.Order) return -1
|
||||
if (a.Order > b.Order) return 1
|
||||
return 0
|
||||
}//}}}
|
||||
constructor(data) {//{{{
|
||||
Object.keys(data).forEach(key => {
|
||||
this[key] = data[key]
|
||||
})
|
||||
}//}}}
|
||||
}
|
||||
|
||||
export class Checklist extends Component {
|
||||
render({ groups }) {//{{{
|
||||
if (groups.length == 0)
|
||||
return
|
||||
|
||||
groups.sort(ChecklistGroup.sort)
|
||||
|
||||
let groupElements = groups.map(group => html`<${ChecklistGroupElement} group=${group} />`)
|
||||
|
||||
return html`
|
||||
<div id="checklist">
|
||||
<h1>Checklist</h1>
|
||||
${groupElements}
|
||||
</div>
|
||||
`
|
||||
}//}}}
|
||||
}
|
||||
|
||||
class ChecklistGroupElement extends Component {
|
||||
render({ group }) {//{{{
|
||||
let items = group.items.map(item => html`<${ChecklistItemElement} item=${item} />`)
|
||||
return html`
|
||||
<div class="checklist-group">${group.Label}</div>
|
||||
${items}
|
||||
`
|
||||
}//}}}
|
||||
}
|
||||
|
||||
class ChecklistItemElement extends Component {
|
||||
constructor(props) {//{{{
|
||||
super(props)
|
||||
this.state = {
|
||||
checked: props.item.Checked,
|
||||
}
|
||||
this.checkbox = createRef()
|
||||
}//}}}
|
||||
render({ item }, { checked }) {//{{{
|
||||
return html`
|
||||
<div class="checklist-item ${checked ? 'checked' : ''}">
|
||||
<input type="checkbox" ref=${this.checkbox} key="checkbox-${item.ID}" id="checkbox-${item.ID}" checked=${checked} onchange=${evt => this.update(evt.target.checked)} />
|
||||
<label for="checkbox-${item.ID}">${item.Label}</label>
|
||||
</div>
|
||||
`
|
||||
}//}}}
|
||||
update(checked) {//{{{
|
||||
this.setState({ checked })
|
||||
window._app.current.request('/node/checklist_item/state', {
|
||||
ChecklistItemID: this.props.item.ID,
|
||||
State: checked,
|
||||
})
|
||||
.then(res => {
|
||||
this.checkbox.current.classList.add('ok')
|
||||
setTimeout(()=>this.checkbox.current.classList.remove('ok'), 500)
|
||||
|
||||
})
|
||||
.catch(window._app.current.responseError)
|
||||
}//}}}
|
||||
}
|
@ -3,7 +3,7 @@ import htm from 'htm'
|
||||
import { signal } from 'preact/signals'
|
||||
import { Keys, Key } from 'key'
|
||||
import Crypto from 'crypto'
|
||||
//import { marked } from 'marked'
|
||||
import { Checklist, ChecklistGroup } from 'checklist'
|
||||
const html = htm.bind(h)
|
||||
|
||||
export class NodeUI extends Component {
|
||||
@ -65,12 +65,14 @@ export class NodeUI extends Component {
|
||||
let padlock = ''
|
||||
if (node.CryptoKeyID > 0)
|
||||
padlock = html`<img src="/images/${window._VERSION}/padlock-black.svg" style="height: 24px;" />`
|
||||
|
||||
page = html`
|
||||
${children.length > 0 ? html`<div class="child-nodes">${children}</div>` : html``}
|
||||
<div class="node-name">
|
||||
${node.Name} ${padlock}
|
||||
</div>
|
||||
<${NodeContent} key=${node.ID} node=${node} ref=${this.nodeContent} />
|
||||
<${Checklist} groups=${node.ChecklistGroups} />
|
||||
<${NodeFiles} node=${this.node.value} />
|
||||
`
|
||||
}
|
||||
@ -342,16 +344,6 @@ class NodeContent extends Component {
|
||||
let textarea = document.getElementById('node-content')
|
||||
if (textarea)
|
||||
textarea.parentNode.dataset.replicatedValue = textarea.value
|
||||
|
||||
let crumbsEl = document.getElementById('crumbs')
|
||||
let markdown = document.getElementById('markdown')
|
||||
if (markdown) {
|
||||
let margins = (crumbsEl.clientWidth - 900) / 2.0
|
||||
if (margins < 0)
|
||||
margins = 0
|
||||
markdown.style.marginLeft = `${margins}px`
|
||||
markdown.style.marginRight = `${margins}px`
|
||||
}
|
||||
}//}}}
|
||||
unlock() {//{{{
|
||||
let pass = prompt(`Password for "${this.props.model.description}"`)
|
||||
@ -368,9 +360,9 @@ class NodeContent extends Component {
|
||||
}
|
||||
|
||||
class MarkdownContent extends Component {
|
||||
render({ content }) {
|
||||
render({ content }) {//{{{
|
||||
return html`<div id="markdown"></div>`
|
||||
}
|
||||
}//}}}
|
||||
componentDidMount() {//{{{
|
||||
const markdown = document.getElementById('markdown')
|
||||
if (markdown)
|
||||
@ -430,6 +422,7 @@ export class Node {
|
||||
this.Files = []
|
||||
this._decrypted = false
|
||||
this._expanded = false // start value for the TreeNode component,
|
||||
this.ChecklistGroups = {}
|
||||
// it doesn't control it afterwards.
|
||||
// Used to expand the crumbs upon site loading.
|
||||
}//}}}
|
||||
@ -446,6 +439,7 @@ export class Node {
|
||||
this.Files = res.Node.Files
|
||||
this.Markdown = res.Node.Markdown
|
||||
this.RenderMarkdown.value = this.Markdown
|
||||
this.initChecklist(res.Node.ChecklistGroups)
|
||||
callback(this)
|
||||
})
|
||||
.catch(this.app.responseError)
|
||||
@ -605,6 +599,13 @@ export class Node {
|
||||
this._decrypted = false
|
||||
return this._content
|
||||
}//}}}
|
||||
initChecklist(checklistData) {//{{{
|
||||
if (checklistData === undefined || checklistData === null)
|
||||
return
|
||||
this.ChecklistGroups = checklistData.map(groupData=>{
|
||||
return new ChecklistGroup(groupData)
|
||||
})
|
||||
}//}}}
|
||||
}
|
||||
|
||||
class Menu extends Component {
|
||||
|
@ -23,13 +23,13 @@ html, body {
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 0px;
|
||||
font-size: 1.5em;
|
||||
color: @header_1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 32px;
|
||||
font-size: 1.25em;
|
||||
color: @header_1;
|
||||
}
|
||||
|
||||
button {
|
||||
@ -352,10 +352,17 @@ header {
|
||||
}
|
||||
|
||||
#markdown {
|
||||
padding: 16px;
|
||||
color: #333;
|
||||
grid-area: content;
|
||||
|
||||
justify-self: center;
|
||||
width: calc(100% - 32px);
|
||||
max-width: 900px;
|
||||
padding: 0.5rem;
|
||||
border-radius: 8px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 0px;
|
||||
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
@ -379,6 +386,83 @@ header {
|
||||
}
|
||||
}
|
||||
|
||||
#checklist {
|
||||
grid-area: checklist;
|
||||
color: #333;
|
||||
|
||||
justify-self: center;
|
||||
width: calc(100% - 32px);
|
||||
max-width: 900px;
|
||||
padding: 0.5rem;
|
||||
border-radius: 8px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 0px;
|
||||
|
||||
.checklist-group {
|
||||
margin-top: 1em;
|
||||
font-weight: bold;
|
||||
|
||||
}
|
||||
|
||||
.checklist-item {
|
||||
display: grid;
|
||||
grid-template-columns: min-content 1fr;
|
||||
align-items: center;
|
||||
margin-top: 0.50em;
|
||||
|
||||
&.checked {
|
||||
text-decoration: line-through;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-left: 0px;
|
||||
margin-right: 8px;
|
||||
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background-color: #fff;
|
||||
margin: 0 8px 0 0;
|
||||
font: inherit;
|
||||
color: currentColor;
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
border: 0.15em solid currentColor;
|
||||
border-radius: 0.15em;
|
||||
transform: translateY(-0.075em);
|
||||
|
||||
display: grid;
|
||||
place-content: center;
|
||||
}
|
||||
|
||||
input[type="checkbox"].ok {
|
||||
border: 0.15em solid #54b356;
|
||||
}
|
||||
|
||||
input[type="checkbox"].ok::before {
|
||||
box-shadow: inset 1em 1em #54b356;
|
||||
}
|
||||
|
||||
|
||||
input[type="checkbox"]::before {
|
||||
content: "";
|
||||
width: 0.70em;
|
||||
height: 0.70em;
|
||||
transform: scale(0);
|
||||
transition: 120ms transform ease-in-out;
|
||||
box-shadow: inset 1em 1em @checkbox_1;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked::before {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
label {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================= *
|
||||
* Textarea replicates the height of an element expanding height *
|
||||
* ============================================================= */
|
||||
@ -555,6 +639,7 @@ header {
|
||||
"tree child-nodes"
|
||||
"tree name"
|
||||
"tree content"
|
||||
"tree checklist"
|
||||
"tree files"
|
||||
"tree blank"
|
||||
;
|
||||
@ -565,6 +650,7 @@ header {
|
||||
min-content /* child-nodes */
|
||||
min-content /* name */
|
||||
min-content /* content */
|
||||
min-content /* checklist */
|
||||
min-content /* files */
|
||||
1fr; /* blank */
|
||||
color: #fff;
|
||||
@ -597,6 +683,7 @@ header {
|
||||
"child-nodes"
|
||||
"name"
|
||||
"content"
|
||||
"checklist"
|
||||
"files"
|
||||
"blank"
|
||||
;
|
||||
@ -607,9 +694,14 @@ header {
|
||||
min-content /* child-nodes */
|
||||
min-content /* name */
|
||||
min-content /* content */
|
||||
min-content /* checklist */
|
||||
min-content /* files */
|
||||
1fr; /* blank */
|
||||
#tree { display: none }
|
||||
|
||||
#checklist {
|
||||
padding: 16px;
|
||||
}
|
||||
}// }}}
|
||||
.layout-keys {
|
||||
display: grid;
|
||||
@ -681,7 +773,7 @@ header {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
#file-section {
|
||||
#file-section, #checklist, #markdown {
|
||||
width: calc(100% - 32px);
|
||||
padding: 16px;
|
||||
margin-left: 16px;
|
||||
|
@ -4,6 +4,11 @@
|
||||
@accent_2: #ecbf00;
|
||||
@accent_3: #c84a37;
|
||||
|
||||
@header_1: #518048;
|
||||
@header_2: #518048;
|
||||
|
||||
@checkbox_1: #666;
|
||||
|
||||
/*
|
||||
@theme_gradient: linear-gradient(to right, #009fff, #ec2f4b);
|
||||
@theme_gradient: linear-gradient(to right, #f5af19, #f12711);
|
||||
|
Loading…
Reference in New Issue
Block a user