diff --git a/main.go b/main.go index e69bb90..cfb70b7 100644 --- a/main.go +++ b/main.go @@ -174,6 +174,29 @@ func main() { // {{{ 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) { // {{{ logger.Info("webserver", "request", "/node/tree") @@ -760,20 +783,8 @@ func scheduleList(w http.ResponseWriter, r *http.Request, sess *session.T) { // var err error 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 - schedules, err = FutureSchedules(sess.UserID, request.NodeID) + schedules, err = FutureSchedules(sess.UserID) if err != nil { responseError(w, err) return diff --git a/node.go b/node.go index 66b5a3e..f453392 100644 --- a/node.go +++ b/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) { // {{{ var timezone string - row := service.Db.Conn.QueryRow(`SELECT timezone FROM _webservice.user WHERE id=$1`, userID) + row := service.Db.Conn.QueryRow(`SELECT timezone FROM public.user WHERE id=$1`, userID) err = row.Scan(&timezone) if err != nil { err = werr.Wrap(err).WithCode("002-000F") diff --git a/request_response.go b/request_response.go index 5eaea5b..c2c6ba5 100644 --- a/request_response.go +++ b/request_response.go @@ -1,9 +1,6 @@ package main import ( - // External - werr "git.gibonuddevalla.se/go/wrappederror" - // Standard "encoding/json" "io" @@ -21,8 +18,6 @@ func responseError(w http.ResponseWriter, err error) { } resJSON, _ := json.Marshal(res) - - werr.Wrap(err).Log() w.Header().Add("Content-Type", "application/json") w.Write(resJSON) } diff --git a/schedule.go b/schedule.go index 823126a..3520283 100644 --- a/schedule.go +++ b/schedule.go @@ -28,32 +28,6 @@ type Schedule struct { 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) { // {{{ schedules = []Schedule{} @@ -115,7 +89,7 @@ func RetrieveSchedules(userID int, nodeID int) (schedules []Schedule, err error) s.description, s.acknowledged FROM schedule s - INNER JOIN _webservice.user u ON s.user_id = u.id + INNER JOIN public.user u ON s.user_id = u.id WHERE user_id=$1 AND CASE @@ -146,7 +120,6 @@ func (a Schedule) IsEqual(b Schedule) bool { // {{{ return a.UserID == b.UserID && a.Node.ID == b.Node.ID && a.Time.Equal(b.Time) && - a.RemindMinutes == b.RemindMinutes && a.Description == b.Description } // }}} func (s *Schedule) Insert(queryable Queryable) error { // {{{ @@ -193,9 +166,9 @@ func ExpiredSchedules() (schedules []Schedule) { // {{{ (s.time - MAKE_INTERVAL(mins => s.remind_minutes)) AT TIME ZONE u.timezone AS time, s.description FROM schedule s - INNER JOIN _webservice.user u ON s.user_id = u.id + INNER JOIN public.user u ON s.user_id = u.id WHERE - (time - MAKE_INTERVAL(mins => remind_minutes)) AT TIME ZONE u.timezone < NOW() AND + (time - MAKE_INTERVAL(mins => remind_minutes)) < NOW() AND NOT acknowledged ORDER BY time ASC @@ -216,7 +189,7 @@ func ExpiredSchedules() (schedules []Schedule) { // {{{ } return } // }}} -func FutureSchedules(userID int, nodeID int) (schedules []Schedule, err error) {// {{{ +func FutureSchedules(userID int) (schedules []Schedule, err error) {// {{{ schedules = []Schedule{} res := service.Db.Conn.QueryRow(` @@ -224,27 +197,16 @@ func FutureSchedules(userID int, nodeID int) (schedules []Schedule, err error) { SELECT s.id, s.user_id, - jsonb_build_object( - 'id', n.id, - 'name', n.name, - 'updated', n.updated - ) AS node, + to_jsonb(n.*) AS node, s.schedule_uuid, - time AT TIME ZONE 'UTC' AS time, + (time - MAKE_INTERVAL(mins => s.remind_minutes)) AT TIME ZONE u.timezone AS time, s.description, - s.acknowledged, - s.remind_minutes AS RemindMinutes + s.acknowledged FROM schedule s - INNER JOIN _webservice.user u ON s.user_id = u.id + INNER JOIN public.user u ON s.user_id = u.id INNER JOIN node n ON s.node_id = n.id WHERE - s.user_id = $1 AND - ( - CASE - WHEN $2 > 0 THEN n.id = $2 - ELSE true - END - ) AND + s.user_id=$1 AND time >= NOW() AND NOT acknowledged ) @@ -253,7 +215,6 @@ func FutureSchedules(userID int, nodeID int) (schedules []Schedule, err error) { FROM schedule_events s `, userID, - nodeID, ) var j []byte err = res.Scan(&j) diff --git a/sql/00021.sql b/sql/00021.sql deleted file mode 100644 index 88d7364..0000000 --- a/sql/00021.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE public.node ALTER COLUMN updated TYPE timestamptz USING updated::timestamptz; diff --git a/static/css/main.css b/static/css/main.css index b4a783f..edc0cd1 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -29,17 +29,13 @@ body { height: 100%; } h1 { - font-size: 1.25em; + font-size: 1.5em; color: #518048; - border-bottom: 1px solid #ccc; } h2 { - font-size: 1em; + font-size: 1.25em; color: #518048; } -h3 { - font-size: 1em; -} button { font-size: 1em; padding: 6px; @@ -207,7 +203,6 @@ header .menu { padding: 16px; background-color: #333; color: #ddd; - z-index: 100; } #tree .node { display: grid; @@ -526,7 +521,7 @@ header .menu { grid-area: 1 / 1 / 2 / 2; } /* ============================================================= */ -#schedule-section { +#file-section { grid-area: files; justify-self: center; width: calc(100% - 32px); @@ -536,23 +531,6 @@ header .menu { border-radius: 8px; margin-top: 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 { font-weight: bold; @@ -651,9 +629,9 @@ header .menu { } .layout-tree { display: grid; - grid-template-areas: "header header" "tree crumbs" "tree child-nodes" "tree name" "tree content" "tree checklist" "tree schedule" "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 /* checklist */ min-content /* schedule */ 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%; @@ -686,9 +664,9 @@ header .menu { display: block; } .layout-crumbs { - grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "checklist" "schedule" "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 /* checklist */ min-content /* schedule */ 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 { @@ -735,17 +713,17 @@ header .menu { } #app.node { display: grid; - grid-template-areas: "header header" "tree crumbs" "tree child-nodes" "tree name" "tree content" "tree checklist" "tree schedule" "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 /* checklist */ min-content /* schedule */ 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" "checklist" "schedule" "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 /* checklist */ min-content /* schedule */ 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 { @@ -767,22 +745,11 @@ header .menu { #profile-settings .passwords div { 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) { #app.node { - grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "checklist" "schedule" "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 /* checklist */ min-content /* schedule */ 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 { diff --git a/static/js/app.mjs b/static/js/app.mjs index ff2e7b8..8a486bf 100644 --- a/static/js/app.mjs +++ b/static/js/app.mjs @@ -113,7 +113,7 @@ class App extends Component { this.websocket.register('open', ()=>console.log('websocket connected')) this.websocket.register('close', ()=>console.log('websocket disconnected')) this.websocket.register('error', msg=>console.log(msg)) - this.websocket.register('message', msg=>this.websocketMessage(msg)) + this.websocket.register('message', this.websocketMessage) this.websocket.start() }//}}} websocketMessage(data) {//{{{ @@ -121,7 +121,7 @@ class App extends Component { switch (msg.Op) { case 'css_reload': - this.websocket.refreshCSS() + refreshCSS() break; } }//}}} diff --git a/static/js/node.mjs b/static/js/node.mjs index be03626..3608ab7 100644 --- a/static/js/node.mjs +++ b/static/js/node.mjs @@ -14,6 +14,7 @@ export class NodeUI extends Component { this.nodeContent = createRef() this.nodeProperties = createRef() this.keys = signal([]) + this.page = signal('node') window.addEventListener('popstate', evt => { if (evt.state && evt.state.hasOwnProperty('nodeID')) @@ -58,7 +59,7 @@ export class NodeUI extends Component { case 'node': if (node.ID == 0) { page = html` -
this.page.value = 'schedule-events'}>Schedule events
+
this.page.value = 'schedule-events'}>Schedule events
${children.length > 0 ? html`
${children}
Notes version ${window._VERSION}
` : html``} ` } else { @@ -72,7 +73,6 @@ export class NodeUI extends Component { ${node.Name} ${padlock} <${NodeContent} key=${node.ID} node=${node} ref=${this.nodeContent} /> - <${NodeEvents} events=${node.ScheduleEvents.value} /> <${Checklist} ui=${this} groups=${node.ChecklistGroups} /> <${NodeFiles} node=${this.node.value} /> ` @@ -250,12 +250,15 @@ export class NodeUI extends Component { }) }//}}} saveNode() {//{{{ + /* + let nodeContent = this.nodeContent.current + if (this.page.value != 'node' || nodeContent === null) + return + */ + let content = this.node.value.content() this.node.value.setContent(content) - this.node.value.save(() => { - this.props.app.nodeModified.value = false - this.node.value.retrieve() - }) + this.node.value.save(() => this.props.app.nodeModified.value = false) }//}}} renameNode() {//{{{ let name = prompt("New name") @@ -386,24 +389,6 @@ 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`
${dt[0]} ${dt[1].slice(0, 5)}
` - }) - return html` -
-
Schedule events
- ${eventElements} -
- ` - }//}}} -} - class NodeFiles extends Component { render({ node }) {//{{{ if (node.Files === null || node.Files.length == 0) @@ -458,16 +443,10 @@ export class Node { this._decrypted = false this._expanded = false // start value for the TreeNode component, this.ChecklistGroups = {} - this.ScheduleEvents = signal([]) // it doesn't control it afterwards. // Used to expand the crumbs upon site loading. }//}}} 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 }) .then(res => { this.ParentID = res.Node.ParentID @@ -986,50 +965,18 @@ class ProfileSettings extends Component { } class ScheduleEventList extends Component { - constructor() {//{{{ + constructor() { super() - this.events = signal(null) 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` - } - const nodeLink = () => html`${evt.Node.Name}` - - - return html` -
${dt[0]}
-
${dt[1].slice(0, 5)}
-
<${remind} />
-
${evt.Description}
-
<${nodeLink} />
- ` - }) - - return html` -
-
Date
-
Time
-
Reminder
-
Event
-
Node
- ${events} -
- ` - }//}}} - retrieveFutureEvents() {//{{{ + } + render() { + } + retrieveFutureEvents() { _app.current.request('/schedule/list') - .then(data => { - this.events.value = data.ScheduleEvents - }) - }//}}} + .then(foo=>{ + console.log(foo) + }) + } } diff --git a/static/less/main.less b/static/less/main.less index ed13f38..8058b08 100644 --- a/static/less/main.less +++ b/static/less/main.less @@ -27,20 +27,15 @@ html, body { } h1 { - font-size: 1.25em; + font-size: 1.5em; color: @header_1; - border-bottom: 1px solid #ccc; } h2 { - font-size: 1.0em; + font-size: 1.25em; color: @header_1; } -h3 { - font-size: 1.0em; -} - button { font-size: 1em; padding: 6px; @@ -227,7 +222,6 @@ header { padding: 16px; background-color: #333; color: #ddd; - z-index: 100; // Over crumbs shadow .node { display: grid; @@ -617,7 +611,7 @@ header { } /* ============================================================= */ -#schedule-section { +#file-section { grid-area: files; justify-self: center; width: calc(100% - 32px); @@ -627,25 +621,6 @@ header { border-radius: 8px; margin-top: 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 { font-weight: bold; @@ -770,7 +745,6 @@ header { "tree name" "tree content" "tree checklist" - "tree schedule" "tree files" "tree blank" ; @@ -782,7 +756,6 @@ header { min-content /* name */ min-content /* content */ min-content /* checklist */ - min-content /* schedule */ min-content /* files */ 1fr; /* blank */ color: #fff; @@ -816,7 +789,6 @@ header { "name" "content" "checklist" - "schedule" "files" "blank" ; @@ -828,7 +800,6 @@ header { min-content /* name */ min-content /* content */ min-content /* checklist */ - min-content /* schedule */ min-content /* files */ 1fr; /* blank */ #tree { display: none } @@ -892,19 +863,6 @@ 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) { #app.node { .layout-crumbs();