Compare commits
5 Commits
83e1ce5ffe
...
3b8c6432b6
Author | SHA1 | Date | |
---|---|---|---|
|
3b8c6432b6 | ||
|
d186489f28 | ||
|
6a757c94b0 | ||
|
3669b7e6ec | ||
|
566cff5e94 |
37
main.go
37
main.go
@ -174,29 +174,6 @@ func main() { // {{{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
} // }}}
|
} // }}}
|
||||||
func scheduleHandler() { // {{{
|
|
||||||
// Wait for the approximate minute.
|
|
||||||
wait := 60000 - time.Now().Sub(time.Now().Truncate(time.Minute)).Milliseconds()
|
|
||||||
logger.Info("schedule", "wait", wait/1000)
|
|
||||||
time.Sleep(time.Millisecond * time.Duration(wait))
|
|
||||||
tick := time.NewTicker(time.Minute)
|
|
||||||
for {
|
|
||||||
for _, event := range ExpiredSchedules() {
|
|
||||||
notificationManager.Send(
|
|
||||||
event.UserID,
|
|
||||||
event.ScheduleUUID,
|
|
||||||
[]byte(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"%s\n%s",
|
|
||||||
event.Time.Format("2006-01-02 15:04"),
|
|
||||||
event.Description,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<-tick.C
|
|
||||||
}
|
|
||||||
} // }}}
|
|
||||||
|
|
||||||
func nodeTree(w http.ResponseWriter, r *http.Request, sess *session.T) { // {{{
|
func nodeTree(w http.ResponseWriter, r *http.Request, sess *session.T) { // {{{
|
||||||
logger.Info("webserver", "request", "/node/tree")
|
logger.Info("webserver", "request", "/node/tree")
|
||||||
@ -783,8 +760,20 @@ func scheduleList(w http.ResponseWriter, r *http.Request, sess *session.T) { //
|
|||||||
var err error
|
var err error
|
||||||
w.Header().Add("Access-Control-Allow-Origin", "*")
|
w.Header().Add("Access-Control-Allow-Origin", "*")
|
||||||
|
|
||||||
|
request := struct {
|
||||||
|
NodeID int
|
||||||
|
}{}
|
||||||
|
body, _ := io.ReadAll(r.Body)
|
||||||
|
if len(body) > 0 {
|
||||||
|
err = json.Unmarshal(body, &request)
|
||||||
|
if err != nil {
|
||||||
|
responseError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var schedules []Schedule
|
var schedules []Schedule
|
||||||
schedules, err = FutureSchedules(sess.UserID)
|
schedules, err = FutureSchedules(sess.UserID, request.NodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
responseError(w, err)
|
responseError(w, err)
|
||||||
return
|
return
|
||||||
|
2
node.go
2
node.go
@ -326,7 +326,7 @@ func CreateNode(userID, parentID int, name string) (node Node, err error) { // {
|
|||||||
} // }}}
|
} // }}}
|
||||||
func UpdateNode(userID, nodeID, timeOffset int, content string, cryptoKeyID int, markdown bool) (err error) { // {{{
|
func UpdateNode(userID, nodeID, timeOffset int, content string, cryptoKeyID int, markdown bool) (err error) { // {{{
|
||||||
var timezone string
|
var timezone string
|
||||||
row := service.Db.Conn.QueryRow(`SELECT timezone FROM public.user WHERE id=$1`, userID)
|
row := service.Db.Conn.QueryRow(`SELECT timezone FROM _webservice.user WHERE id=$1`, userID)
|
||||||
err = row.Scan(&timezone)
|
err = row.Scan(&timezone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = werr.Wrap(err).WithCode("002-000F")
|
err = werr.Wrap(err).WithCode("002-000F")
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// External
|
||||||
|
werr "git.gibonuddevalla.se/go/wrappederror"
|
||||||
|
|
||||||
// Standard
|
// Standard
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
@ -18,6 +21,8 @@ func responseError(w http.ResponseWriter, err error) {
|
|||||||
}
|
}
|
||||||
resJSON, _ := json.Marshal(res)
|
resJSON, _ := json.Marshal(res)
|
||||||
|
|
||||||
|
|
||||||
|
werr.Wrap(err).Log()
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
w.Write(resJSON)
|
w.Write(resJSON)
|
||||||
}
|
}
|
||||||
|
55
schedule.go
55
schedule.go
@ -28,6 +28,32 @@ type Schedule struct {
|
|||||||
Acknowledged bool
|
Acknowledged bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scheduleHandler() { // {{{
|
||||||
|
// Wait for the approximate minute.
|
||||||
|
wait := 60000 - time.Now().Sub(time.Now().Truncate(time.Minute)).Milliseconds()
|
||||||
|
logger.Info("schedule", "wait", wait/1000)
|
||||||
|
time.Sleep(time.Millisecond * time.Duration(wait))
|
||||||
|
tick := time.NewTicker(time.Minute)
|
||||||
|
for {
|
||||||
|
schedules := ExpiredSchedules()
|
||||||
|
logger.Info("FOO", "schedules", schedules)
|
||||||
|
for _, event := range schedules {
|
||||||
|
notificationManager.Send(
|
||||||
|
event.UserID,
|
||||||
|
event.ScheduleUUID,
|
||||||
|
[]byte(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"%s\n%s",
|
||||||
|
event.Time.Format("2006-01-02 15:04"),
|
||||||
|
event.Description,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<-tick.C
|
||||||
|
}
|
||||||
|
} // }}}
|
||||||
|
|
||||||
func ScanForSchedules(timezone string, content string) (schedules []Schedule) { // {{{
|
func ScanForSchedules(timezone string, content string) (schedules []Schedule) { // {{{
|
||||||
schedules = []Schedule{}
|
schedules = []Schedule{}
|
||||||
|
|
||||||
@ -89,7 +115,7 @@ func RetrieveSchedules(userID int, nodeID int) (schedules []Schedule, err error)
|
|||||||
s.description,
|
s.description,
|
||||||
s.acknowledged
|
s.acknowledged
|
||||||
FROM schedule s
|
FROM schedule s
|
||||||
INNER JOIN public.user u ON s.user_id = u.id
|
INNER JOIN _webservice.user u ON s.user_id = u.id
|
||||||
WHERE
|
WHERE
|
||||||
user_id=$1 AND
|
user_id=$1 AND
|
||||||
CASE
|
CASE
|
||||||
@ -120,6 +146,7 @@ func (a Schedule) IsEqual(b Schedule) bool { // {{{
|
|||||||
return a.UserID == b.UserID &&
|
return a.UserID == b.UserID &&
|
||||||
a.Node.ID == b.Node.ID &&
|
a.Node.ID == b.Node.ID &&
|
||||||
a.Time.Equal(b.Time) &&
|
a.Time.Equal(b.Time) &&
|
||||||
|
a.RemindMinutes == b.RemindMinutes &&
|
||||||
a.Description == b.Description
|
a.Description == b.Description
|
||||||
} // }}}
|
} // }}}
|
||||||
func (s *Schedule) Insert(queryable Queryable) error { // {{{
|
func (s *Schedule) Insert(queryable Queryable) error { // {{{
|
||||||
@ -166,9 +193,9 @@ func ExpiredSchedules() (schedules []Schedule) { // {{{
|
|||||||
(s.time - MAKE_INTERVAL(mins => s.remind_minutes)) AT TIME ZONE u.timezone AS time,
|
(s.time - MAKE_INTERVAL(mins => s.remind_minutes)) AT TIME ZONE u.timezone AS time,
|
||||||
s.description
|
s.description
|
||||||
FROM schedule s
|
FROM schedule s
|
||||||
INNER JOIN public.user u ON s.user_id = u.id
|
INNER JOIN _webservice.user u ON s.user_id = u.id
|
||||||
WHERE
|
WHERE
|
||||||
(time - MAKE_INTERVAL(mins => remind_minutes)) < NOW() AND
|
(time - MAKE_INTERVAL(mins => remind_minutes)) AT TIME ZONE u.timezone < NOW() AND
|
||||||
NOT acknowledged
|
NOT acknowledged
|
||||||
ORDER BY
|
ORDER BY
|
||||||
time ASC
|
time ASC
|
||||||
@ -189,7 +216,7 @@ func ExpiredSchedules() (schedules []Schedule) { // {{{
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
} // }}}
|
} // }}}
|
||||||
func FutureSchedules(userID int) (schedules []Schedule, err error) {// {{{
|
func FutureSchedules(userID int, nodeID int) (schedules []Schedule, err error) {// {{{
|
||||||
schedules = []Schedule{}
|
schedules = []Schedule{}
|
||||||
|
|
||||||
res := service.Db.Conn.QueryRow(`
|
res := service.Db.Conn.QueryRow(`
|
||||||
@ -197,16 +224,27 @@ func FutureSchedules(userID int) (schedules []Schedule, err error) {// {{{
|
|||||||
SELECT
|
SELECT
|
||||||
s.id,
|
s.id,
|
||||||
s.user_id,
|
s.user_id,
|
||||||
to_jsonb(n.*) AS node,
|
jsonb_build_object(
|
||||||
|
'id', n.id,
|
||||||
|
'name', n.name,
|
||||||
|
'updated', n.updated
|
||||||
|
) AS node,
|
||||||
s.schedule_uuid,
|
s.schedule_uuid,
|
||||||
(time - MAKE_INTERVAL(mins => s.remind_minutes)) AT TIME ZONE u.timezone AS time,
|
time AT TIME ZONE 'UTC' AS time,
|
||||||
s.description,
|
s.description,
|
||||||
s.acknowledged
|
s.acknowledged,
|
||||||
|
s.remind_minutes AS RemindMinutes
|
||||||
FROM schedule s
|
FROM schedule s
|
||||||
INNER JOIN public.user u ON s.user_id = u.id
|
INNER JOIN _webservice.user u ON s.user_id = u.id
|
||||||
INNER JOIN node n ON s.node_id = n.id
|
INNER JOIN node n ON s.node_id = n.id
|
||||||
WHERE
|
WHERE
|
||||||
s.user_id = $1 AND
|
s.user_id = $1 AND
|
||||||
|
(
|
||||||
|
CASE
|
||||||
|
WHEN $2 > 0 THEN n.id = $2
|
||||||
|
ELSE true
|
||||||
|
END
|
||||||
|
) AND
|
||||||
time >= NOW() AND
|
time >= NOW() AND
|
||||||
NOT acknowledged
|
NOT acknowledged
|
||||||
)
|
)
|
||||||
@ -215,6 +253,7 @@ func FutureSchedules(userID int) (schedules []Schedule, err error) {// {{{
|
|||||||
FROM schedule_events s
|
FROM schedule_events s
|
||||||
`,
|
`,
|
||||||
userID,
|
userID,
|
||||||
|
nodeID,
|
||||||
)
|
)
|
||||||
var j []byte
|
var j []byte
|
||||||
err = res.Scan(&j)
|
err = res.Scan(&j)
|
||||||
|
1
sql/00021.sql
Normal file
1
sql/00021.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE public.node ALTER COLUMN updated TYPE timestamptz USING updated::timestamptz;
|
@ -29,12 +29,16 @@ body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 1.5em;
|
|
||||||
color: #518048;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
color: #518048;
|
color: #518048;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 1em;
|
||||||
|
color: #518048;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
@ -203,6 +207,7 @@ header .menu {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
|
z-index: 100;
|
||||||
}
|
}
|
||||||
#tree .node {
|
#tree .node {
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -521,7 +526,7 @@ header .menu {
|
|||||||
grid-area: 1 / 1 / 2 / 2;
|
grid-area: 1 / 1 / 2 / 2;
|
||||||
}
|
}
|
||||||
/* ============================================================= */
|
/* ============================================================= */
|
||||||
#file-section {
|
#schedule-section {
|
||||||
grid-area: files;
|
grid-area: files;
|
||||||
justify-self: center;
|
justify-self: center;
|
||||||
width: calc(100% - 32px);
|
width: calc(100% - 32px);
|
||||||
@ -531,6 +536,23 @@ header .menu {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
#schedule-section .header {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
#file-section {
|
||||||
|
grid-area: schedule;
|
||||||
|
justify-self: center;
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
max-width: 900px;
|
||||||
|
padding: 32px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 32px;
|
||||||
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
#file-section .header {
|
#file-section .header {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -629,9 +651,9 @@ header .menu {
|
|||||||
}
|
}
|
||||||
.layout-tree {
|
.layout-tree {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas: "header header" "tree crumbs" "tree child-nodes" "tree name" "tree content" "tree checklist" "tree files" "tree blank";
|
grid-template-areas: "header header" "tree crumbs" "tree child-nodes" "tree name" "tree content" "tree checklist" "tree schedule" "tree files" "tree blank";
|
||||||
grid-template-columns: min-content 1fr;
|
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 /* checklist */ 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 /* schedule */ min-content /* files */ 1fr;
|
||||||
/* blank */
|
/* blank */
|
||||||
color: #fff;
|
color: #fff;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
@ -664,9 +686,9 @@ header .menu {
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.layout-crumbs {
|
.layout-crumbs {
|
||||||
grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "checklist" "files" "blank";
|
grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "checklist" "schedule" "files" "blank";
|
||||||
grid-template-columns: 1fr;
|
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 /* checklist */ 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 /* schedule */ min-content /* files */ 1fr;
|
||||||
/* blank */
|
/* blank */
|
||||||
}
|
}
|
||||||
.layout-crumbs #tree {
|
.layout-crumbs #tree {
|
||||||
@ -713,17 +735,17 @@ header .menu {
|
|||||||
}
|
}
|
||||||
#app.node {
|
#app.node {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas: "header header" "tree crumbs" "tree child-nodes" "tree name" "tree content" "tree checklist" "tree files" "tree blank";
|
grid-template-areas: "header header" "tree crumbs" "tree child-nodes" "tree name" "tree content" "tree checklist" "tree schedule" "tree files" "tree blank";
|
||||||
grid-template-columns: min-content 1fr;
|
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 /* checklist */ 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 /* schedule */ min-content /* files */ 1fr;
|
||||||
/* blank */
|
/* blank */
|
||||||
color: #fff;
|
color: #fff;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
#app.node.toggle-tree {
|
#app.node.toggle-tree {
|
||||||
grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "checklist" "files" "blank";
|
grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "checklist" "schedule" "files" "blank";
|
||||||
grid-template-columns: 1fr;
|
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 /* checklist */ 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 /* schedule */ min-content /* files */ 1fr;
|
||||||
/* blank */
|
/* blank */
|
||||||
}
|
}
|
||||||
#app.node.toggle-tree #tree {
|
#app.node.toggle-tree #tree {
|
||||||
@ -745,11 +767,22 @@ header .menu {
|
|||||||
#profile-settings .passwords div {
|
#profile-settings .passwords div {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
#schedule-events {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, min-content);
|
||||||
|
grid-gap: 4px 12px;
|
||||||
|
margin: 32px;
|
||||||
|
color: #000;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#schedule-events .header {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
@media only screen and (max-width: 932px) {
|
@media only screen and (max-width: 932px) {
|
||||||
#app.node {
|
#app.node {
|
||||||
grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "checklist" "files" "blank";
|
grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "checklist" "schedule" "files" "blank";
|
||||||
grid-template-columns: 1fr;
|
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 /* checklist */ 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 /* schedule */ min-content /* files */ 1fr;
|
||||||
/* blank */
|
/* blank */
|
||||||
}
|
}
|
||||||
#app.node #tree {
|
#app.node #tree {
|
||||||
|
@ -113,7 +113,7 @@ class App extends Component {
|
|||||||
this.websocket.register('open', ()=>console.log('websocket connected'))
|
this.websocket.register('open', ()=>console.log('websocket connected'))
|
||||||
this.websocket.register('close', ()=>console.log('websocket disconnected'))
|
this.websocket.register('close', ()=>console.log('websocket disconnected'))
|
||||||
this.websocket.register('error', msg=>console.log(msg))
|
this.websocket.register('error', msg=>console.log(msg))
|
||||||
this.websocket.register('message', this.websocketMessage)
|
this.websocket.register('message', msg=>this.websocketMessage(msg))
|
||||||
this.websocket.start()
|
this.websocket.start()
|
||||||
}//}}}
|
}//}}}
|
||||||
websocketMessage(data) {//{{{
|
websocketMessage(data) {//{{{
|
||||||
@ -121,7 +121,7 @@ class App extends Component {
|
|||||||
|
|
||||||
switch (msg.Op) {
|
switch (msg.Op) {
|
||||||
case 'css_reload':
|
case 'css_reload':
|
||||||
refreshCSS()
|
this.websocket.refreshCSS()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}//}}}
|
}//}}}
|
||||||
|
@ -14,7 +14,6 @@ export class NodeUI extends Component {
|
|||||||
this.nodeContent = createRef()
|
this.nodeContent = createRef()
|
||||||
this.nodeProperties = createRef()
|
this.nodeProperties = createRef()
|
||||||
this.keys = signal([])
|
this.keys = signal([])
|
||||||
|
|
||||||
this.page = signal('node')
|
this.page = signal('node')
|
||||||
window.addEventListener('popstate', evt => {
|
window.addEventListener('popstate', evt => {
|
||||||
if (evt.state && evt.state.hasOwnProperty('nodeID'))
|
if (evt.state && evt.state.hasOwnProperty('nodeID'))
|
||||||
@ -73,6 +72,7 @@ export class NodeUI extends Component {
|
|||||||
${node.Name} ${padlock}
|
${node.Name} ${padlock}
|
||||||
</div>
|
</div>
|
||||||
<${NodeContent} key=${node.ID} node=${node} ref=${this.nodeContent} />
|
<${NodeContent} key=${node.ID} node=${node} ref=${this.nodeContent} />
|
||||||
|
<${NodeEvents} events=${node.ScheduleEvents.value} />
|
||||||
<${Checklist} ui=${this} groups=${node.ChecklistGroups} />
|
<${Checklist} ui=${this} groups=${node.ChecklistGroups} />
|
||||||
<${NodeFiles} node=${this.node.value} />
|
<${NodeFiles} node=${this.node.value} />
|
||||||
`
|
`
|
||||||
@ -250,15 +250,12 @@ export class NodeUI extends Component {
|
|||||||
})
|
})
|
||||||
}//}}}
|
}//}}}
|
||||||
saveNode() {//{{{
|
saveNode() {//{{{
|
||||||
/*
|
|
||||||
let nodeContent = this.nodeContent.current
|
|
||||||
if (this.page.value != 'node' || nodeContent === null)
|
|
||||||
return
|
|
||||||
*/
|
|
||||||
|
|
||||||
let content = this.node.value.content()
|
let content = this.node.value.content()
|
||||||
this.node.value.setContent(content)
|
this.node.value.setContent(content)
|
||||||
this.node.value.save(() => this.props.app.nodeModified.value = false)
|
this.node.value.save(() => {
|
||||||
|
this.props.app.nodeModified.value = false
|
||||||
|
this.node.value.retrieve()
|
||||||
|
})
|
||||||
}//}}}
|
}//}}}
|
||||||
renameNode() {//{{{
|
renameNode() {//{{{
|
||||||
let name = prompt("New name")
|
let name = prompt("New name")
|
||||||
@ -389,6 +386,24 @@ class MarkdownContent extends Component {
|
|||||||
}//}}}
|
}//}}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NodeEvents extends Component {
|
||||||
|
render({ events }) {//{{{
|
||||||
|
if (events.length == 0)
|
||||||
|
return html``
|
||||||
|
|
||||||
|
const eventElements = events.map(evt => {
|
||||||
|
const dt = evt.Time.split('T')
|
||||||
|
return html`<div>${dt[0]} ${dt[1].slice(0, 5)}</div>`
|
||||||
|
})
|
||||||
|
return html`
|
||||||
|
<div id="schedule-section">
|
||||||
|
<div class="header">Schedule events</div>
|
||||||
|
${eventElements}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}//}}}
|
||||||
|
}
|
||||||
|
|
||||||
class NodeFiles extends Component {
|
class NodeFiles extends Component {
|
||||||
render({ node }) {//{{{
|
render({ node }) {//{{{
|
||||||
if (node.Files === null || node.Files.length == 0)
|
if (node.Files === null || node.Files.length == 0)
|
||||||
@ -443,10 +458,16 @@ export class Node {
|
|||||||
this._decrypted = false
|
this._decrypted = false
|
||||||
this._expanded = false // start value for the TreeNode component,
|
this._expanded = false // start value for the TreeNode component,
|
||||||
this.ChecklistGroups = {}
|
this.ChecklistGroups = {}
|
||||||
|
this.ScheduleEvents = signal([])
|
||||||
// it doesn't control it afterwards.
|
// it doesn't control it afterwards.
|
||||||
// Used to expand the crumbs upon site loading.
|
// Used to expand the crumbs upon site loading.
|
||||||
}//}}}
|
}//}}}
|
||||||
retrieve(callback) {//{{{
|
retrieve(callback) {//{{{
|
||||||
|
this.app.request('/schedule/list', { NodeID: this.ID })
|
||||||
|
.then(res => {
|
||||||
|
this.ScheduleEvents.value = res.ScheduleEvents
|
||||||
|
})
|
||||||
|
|
||||||
this.app.request('/node/retrieve', { ID: this.ID })
|
this.app.request('/node/retrieve', { ID: this.ID })
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.ParentID = res.Node.ParentID
|
this.ParentID = res.Node.ParentID
|
||||||
@ -965,18 +986,50 @@ class ProfileSettings extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ScheduleEventList extends Component {
|
class ScheduleEventList extends Component {
|
||||||
constructor() {
|
constructor() {//{{{
|
||||||
super()
|
super()
|
||||||
|
this.events = signal(null)
|
||||||
this.retrieveFutureEvents()
|
this.retrieveFutureEvents()
|
||||||
|
}//}}}
|
||||||
|
render() {//{{{
|
||||||
|
if (this.events.value === null)
|
||||||
|
return
|
||||||
|
|
||||||
|
let events = this.events.value.map(evt => {
|
||||||
|
const dt = evt.Time.split('T')
|
||||||
|
const remind = () => {
|
||||||
|
if (evt.RemindMinutes > 0)
|
||||||
|
return html`${evt.RemindMinutes} min`
|
||||||
}
|
}
|
||||||
render() {
|
const nodeLink = () => html`<a href="/?node=${evt.Node.ID}">${evt.Node.Name}</a>`
|
||||||
}
|
|
||||||
retrieveFutureEvents() {
|
|
||||||
_app.current.request('/schedule/list')
|
return html`
|
||||||
.then(foo=>{
|
<div class="date">${dt[0]}</div>
|
||||||
console.log(foo)
|
<div class="time">${dt[1].slice(0, 5)}</div>
|
||||||
|
<div class="remind"><${remind} /></div>
|
||||||
|
<div class="description">${evt.Description}</div>
|
||||||
|
<div class="node"><${nodeLink} /></div>
|
||||||
|
`
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
return html`
|
||||||
|
<div id="schedule-events">
|
||||||
|
<div class="header">Date</div>
|
||||||
|
<div class="header">Time</div>
|
||||||
|
<div class="header">Reminder</div>
|
||||||
|
<div class="header">Event</div>
|
||||||
|
<div class="header">Node</div>
|
||||||
|
${events}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}//}}}
|
||||||
|
retrieveFutureEvents() {//{{{
|
||||||
|
_app.current.request('/schedule/list')
|
||||||
|
.then(data => {
|
||||||
|
this.events.value = data.ScheduleEvents
|
||||||
|
})
|
||||||
|
}//}}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,15 +27,20 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 1.5em;
|
font-size: 1.25em;
|
||||||
color: @header_1;
|
color: @header_1;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 1.25em;
|
font-size: 1.0em;
|
||||||
color: @header_1;
|
color: @header_1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.0em;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
@ -222,6 +227,7 @@ header {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
|
z-index: 100; // Over crumbs shadow
|
||||||
|
|
||||||
.node {
|
.node {
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -611,7 +617,7 @@ header {
|
|||||||
}
|
}
|
||||||
/* ============================================================= */
|
/* ============================================================= */
|
||||||
|
|
||||||
#file-section {
|
#schedule-section {
|
||||||
grid-area: files;
|
grid-area: files;
|
||||||
justify-self: center;
|
justify-self: center;
|
||||||
width: calc(100% - 32px);
|
width: calc(100% - 32px);
|
||||||
@ -621,6 +627,25 @@ header {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
|
color: #000;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#file-section {
|
||||||
|
grid-area: schedule;
|
||||||
|
justify-self: center;
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
max-width: 900px;
|
||||||
|
padding: 32px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 32px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -745,6 +770,7 @@ header {
|
|||||||
"tree name"
|
"tree name"
|
||||||
"tree content"
|
"tree content"
|
||||||
"tree checklist"
|
"tree checklist"
|
||||||
|
"tree schedule"
|
||||||
"tree files"
|
"tree files"
|
||||||
"tree blank"
|
"tree blank"
|
||||||
;
|
;
|
||||||
@ -756,6 +782,7 @@ header {
|
|||||||
min-content /* name */
|
min-content /* name */
|
||||||
min-content /* content */
|
min-content /* content */
|
||||||
min-content /* checklist */
|
min-content /* checklist */
|
||||||
|
min-content /* schedule */
|
||||||
min-content /* files */
|
min-content /* files */
|
||||||
1fr; /* blank */
|
1fr; /* blank */
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@ -789,6 +816,7 @@ header {
|
|||||||
"name"
|
"name"
|
||||||
"content"
|
"content"
|
||||||
"checklist"
|
"checklist"
|
||||||
|
"schedule"
|
||||||
"files"
|
"files"
|
||||||
"blank"
|
"blank"
|
||||||
;
|
;
|
||||||
@ -800,6 +828,7 @@ header {
|
|||||||
min-content /* name */
|
min-content /* name */
|
||||||
min-content /* content */
|
min-content /* content */
|
||||||
min-content /* checklist */
|
min-content /* checklist */
|
||||||
|
min-content /* schedule */
|
||||||
min-content /* files */
|
min-content /* files */
|
||||||
1fr; /* blank */
|
1fr; /* blank */
|
||||||
#tree { display: none }
|
#tree { display: none }
|
||||||
@ -863,6 +892,19 @@ header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#schedule-events {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, min-content);
|
||||||
|
grid-gap: 4px 12px;
|
||||||
|
margin: 32px;
|
||||||
|
color: #000;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 932px) {
|
@media only screen and (max-width: 932px) {
|
||||||
#app.node {
|
#app.node {
|
||||||
.layout-crumbs();
|
.layout-crumbs();
|
||||||
|
Loading…
Reference in New Issue
Block a user