diff --git a/.gitignore b/.gitignore index 1202234..7d5585e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ notes2 +untracked diff --git a/config.go b/config.go index 0ba4ea2..71d5f5d 100644 --- a/config.go +++ b/config.go @@ -3,7 +3,6 @@ package main import ( // Standard "encoding/json" - "fmt" "os" ) @@ -27,9 +26,8 @@ type Config struct { } } -func readConfig() (err error) { +func readConfig(fname string) (err error) { var configData []byte - fname := fmt.Sprintf("%s/.config/notes2.json", os.Getenv("HOME")) configData, err = os.ReadFile(fname) if err != nil { return diff --git a/html_template/pkg.go b/html_template/pkg.go index 4140f89..9abfc12 100644 --- a/html_template/pkg.go +++ b/html_template/pkg.go @@ -66,8 +66,6 @@ func (e *Engine) ReloadTemplates() { // {{{ } // }}} func (e *Engine) StaticResource(w http.ResponseWriter, r *http.Request) { // {{{ - var err error - // URLs with pattern /(css|images)/v1.0.0/foobar are stripped of the version. // To get rid of problems with cached content in browser on a new version release, // while also not disabling cache altogether. @@ -83,11 +81,7 @@ func (e *Engine) StaticResource(w http.ResponseWriter, r *http.Request) { // {{{ r.URL.Path = fmt.Sprintf("/%s/%s", comp[1], comp[2]) if e.DevMode { - p := fmt.Sprintf("static/%s/%s", comp[1], comp[2]) - _, err = os.Stat(p) - if err == nil { - e.staticLocalFS.ServeHTTP(w, r) - } + e.staticLocalFS.ServeHTTP(w, r) return } } diff --git a/main.go b/main.go index d25f72f..8edd01c 100644 --- a/main.go +++ b/main.go @@ -23,7 +23,7 @@ import ( "text/template" ) -const VERSION = "v2" +const VERSION = "v5" const CONTEXT_USER = 1 const SYNC_PAGINATION = 200 @@ -75,7 +75,7 @@ func initLog() { // {{{ } // }}} func main() { // {{{ initLog() - err := readConfig() + err := readConfig(FlagConfig) if err != nil { Log.Error("config", "error", err) os.Exit(1) @@ -129,7 +129,6 @@ func main() { // {{{ } http.HandleFunc("/", rootHandler) - http.HandleFunc("/notes2", pageNotes2) http.HandleFunc("/login", pageLogin) http.HandleFunc("/sync", pageSync) http.HandleFunc("/offline", pageOffline) @@ -189,7 +188,13 @@ func rootHandler(w http.ResponseWriter, r *http.Request) { // {{{ // All URLs not specifically handled are routed to this function. // Everything going here should be a static resource. if r.URL.Path == "/" { - http.Redirect(w, r, "/notes2", http.StatusSeeOther) + page := NewPage("notes2") + + err := Webengine.Render(page, w, r) + if err != nil { + w.Write([]byte(err.Error())) + return + } return } @@ -245,15 +250,6 @@ func pageLogin(w http.ResponseWriter, r *http.Request) { // {{{ return } } // }}} -func pageNotes2(w http.ResponseWriter, r *http.Request) { // {{{ - page := NewPage("notes2") - - err := Webengine.Render(page, w, r) - if err != nil { - w.Write([]byte(err.Error())) - return - } -} // }}} func pageSync(w http.ResponseWriter, r *http.Request) { // {{{ page := NewPage("sync") @@ -280,9 +276,9 @@ func actionSyncFromServer(w http.ResponseWriter, r *http.Request) { // {{{ } /* - Log.Debug("/sync/from_server", "num_nodes", len(nodes), "maxSeq", maxSeq) - foo, _ := json.Marshal(nodes) - os.WriteFile(fmt.Sprintf("/tmp/nodes-%d.json", offset), foo, 0644) + Log.Debug("/sync/from_server", "num_nodes", len(nodes), "maxSeq", maxSeq) + foo, _ := json.Marshal(nodes) + os.WriteFile(fmt.Sprintf("/tmp/nodes-%d.json", offset), foo, 0644) */ j, _ := json.Marshal(struct { diff --git a/sql/00001.sql b/sql/00001.sql index 08d5266..7eb8273 100644 --- a/sql/00001.sql +++ b/sql/00001.sql @@ -1,48 +1,218 @@ -CREATE EXTENSION IF NOT EXISTS pg_trgm; -CREATE EXTENSION IF NOT EXISTS pgcrypto; +-- +-- Name: pg_trgm; Type: EXTENSION; Schema: -; Owner: - +-- -CREATE SEQUENCE node_updates; +CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public; -CREATE TABLE public."user" ( - id serial4 NOT NULL, - username varchar NOT NULL, - "name" varchar NOT NULL, - "password" varchar NOT NULL, - last_login timestamp DEFAULT now() NOT NULL, - timezone varchar DEFAULT 'UTC'::character varying NOT NULL, - CONSTRAINT user_pk PRIMARY KEY (id) + +-- +-- Name: EXTENSION pg_trgm; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION pg_trgm IS 'text similarity measurement and index searching based on trigrams'; + + +-- +-- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public; + + +-- +-- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions'; + + +-- +-- Name: json_ancestor_array; Type: TYPE; Schema: public; Owner: postgres +-- + +CREATE TYPE public.json_ancestor_array AS ( + "Ancestors" character varying[] ); -CREATE TABLE public.node ( - id serial4 NOT NULL, - user_id int4 NOT NULL, - "uuid" bpchar(36) DEFAULT gen_random_uuid() NOT NULL, - parent_uuid bpchar(36) NULL, +-- +-- Name: add_nodes(integer, character varying, jsonb); Type: PROCEDURE; Schema: public; Owner: postgres +-- - created timestamptz DEFAULT NOW() NOT NULL, - updated timestamptz DEFAULT NOW() NOT NULL, - deleted timestamptz NULL, +CREATE PROCEDURE public.add_nodes(IN p_user_id integer, IN p_client_uuid character varying, IN p_nodes jsonb) + LANGUAGE plpgsql + AS $$ - created_seq bigint NOT NULL DEFAULT nextval('node_updates'), - updated_seq bigint NOT NULL DEFAULT nextval('node_updates'), - deleted_seq bigint NULL, +DECLARE + node_data jsonb; + node_updated timestamptz; + db_updated timestamptz; + db_uuid bpchar; + db_client bpchar; + db_client_seq int; + node_uuid bpchar; + parent_uuid_nullable bpchar; - "name" varchar(256) DEFAULT ''::character varying NOT NULL, - "content" text DEFAULT ''::text NOT NULL, - content_encrypted text DEFAULT ''::text NOT NULL, - markdown bool DEFAULT false NOT NULL, +BEGIN + RAISE NOTICE '--------------------------'; + FOR node_data IN SELECT * FROM jsonb_array_elements(p_nodes) + LOOP + node_uuid = (node_data->>'UUID')::bpchar; + node_updated = (node_data->>'Updated')::timestamptz; - CONSTRAINT name_length CHECK ((length(TRIM(BOTH FROM name)) > 0)), - CONSTRAINT node_pk PRIMARY KEY (id), - CONSTRAINT user_fk FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE RESTRICT ON UPDATE RESTRICT -); -CREATE UNIQUE INDEX node_uuid_idx ON public.node USING btree (uuid); -CREATE INDEX node_search_index ON public.node USING gin (name gin_trgm_ops, content gin_trgm_ops); + IF node_data->>'ParentUUID' = '00000000-0000-0000-0000-000000000000' THEN + parent_uuid_nullable = NULL; + ELSE + parent_uuid_nullable = node_data->>'ParentUUID'; + END IF; -CREATE OR REPLACE FUNCTION node_update_timestamp() -RETURNS TRIGGER -LANGUAGE PLPGSQL -AS $$ + /* Retrieve the current modified timestamp for this node from the database. */ + SELECT + uuid, updated, client, client_sequence + INTO + db_uuid, db_updated, db_client, db_client_seq + FROM public."node" + WHERE + user_id = p_user_id AND + uuid = node_uuid; + + /* Is the node not in database? It needs to be created. */ + IF db_uuid IS NULL THEN + RAISE NOTICE '01 New node %', node_uuid; + INSERT INTO public."node" ( + user_id, "uuid", parent_uuid, created, updated, + "name", "content", markdown, "content_encrypted", + client, client_sequence + ) + VALUES( + p_user_id, + node_uuid, + parent_uuid_nullable, + (node_data->>'Created')::timestamptz, + (node_data->>'Updated')::timestamptz, + (node_data->>'Name')::varchar, + (node_data->>'Content')::text, + (node_data->>'Markdown')::bool, + '', /* content_encrypted */ + p_client_uuid, + (node_data->>'ClientSequence')::int + ); + CONTINUE; + END IF; + + + /* The client could send a specific node again if it didn't receive the OK from this procedure before. */ + IF db_updated = node_updated AND db_client = p_client_uuid AND db_client_seq = (node_data->>'ClientSequence')::int THEN + RAISE NOTICE '04, already recorded, %, %', db_client, db_client_seq; + CONTINUE; + END IF; + + /* Determine if the incoming node data is to go into history or replace the current node. */ + IF db_updated > node_updated THEN + RAISE NOTICE '02 DB newer, % > % (%))', db_updated, node_updated, node_uuid; + /* Incoming node is going straight to history since it is older than the current node. */ + INSERT INTO node_history( + user_id, "uuid", parents, created, updated, + "name", "content", markdown, "content_encrypted", + client, client_sequence + ) + VALUES( + p_user_id, + node_uuid, + (jsonb_populate_record(null::json_ancestor_array, node_data))."Ancestors", + (node_data->>'Created')::timestamptz, + (node_data->>'Updated')::timestamptz, + (node_data->>'Name')::varchar, + (node_data->>'Content')::text, + (node_data->>'Markdown')::bool, + '', /* content_encrypted */ + p_client_uuid, + (node_data->>'ClientSequence')::int + ) + ON CONFLICT (client, client_sequence) + DO NOTHING; + ELSE + RAISE NOTICE '03 Client newer, % > % (%, %)', node_updated, db_updated, node_uuid, (node_data->>'ClientSequence'); + /* Incoming node is newer and will replace the current node. + * + * The current node is copied to the node_history table and then modified in place + * with the incoming data. */ + INSERT INTO node_history( + user_id, "uuid", parents, + created, updated, "name", "content", markdown, "content_encrypted", + client, client_sequence + ) + SELECT + user_id, + "uuid", + ( + WITH RECURSIVE nodes AS ( + SELECT + uuid, + COALESCE(parent_uuid, '') AS parent_uuid, + name, + 0 AS depth + FROM node + WHERE + uuid = node_uuid + + UNION + + SELECT + n.uuid, + COALESCE(n.parent_uuid, '') AS parent_uuid, + n.name, + nr.depth+1 AS depth + FROM node n + INNER JOIN nodes nr ON n.uuid = nr.parent_uuid + ) + SELECT ARRAY ( + SELECT name + FROM nodes + ORDER BY depth DESC + OFFSET 1 /* discard itself */ + ) + ), + created, + updated, + name, + content, + markdown, + content_encrypted, + client, + client_sequence + FROM public."node" + WHERE + user_id = p_user_id AND + uuid = node_uuid + ON CONFLICT (client, client_sequence) + DO NOTHING; + + /* Current node in database is updated with incoming data. */ + UPDATE public."node" + SET + updated = (node_data->>'Updated')::timestamptz, + updated_seq = nextval('node_updates'), + name = (node_data->>'Name')::varchar, + content = (node_data->>'Content')::text, + markdown = (node_data->>'Markdown')::bool, + client = p_client_uuid, + client_sequence = (node_data->>'ClientSequence')::int + WHERE + user_id = p_user_id AND + uuid = node_uuid; + END IF; + + END LOOP; +END +$$; + +-- +-- Name: node_update_timestamp(); Type: FUNCTION; Schema: public; Owner: postgres +-- + +CREATE FUNCTION public.node_update_timestamp() RETURNS trigger + LANGUAGE plpgsql + AS $$ BEGIN IF NEW.updated = OLD.updated THEN UPDATE node @@ -56,6 +226,335 @@ BEGIN END; $$; -CREATE OR REPLACE TRIGGER node_update AFTER UPDATE ON node -FOR EACH ROW -EXECUTE PROCEDURE node_update_timestamp(); + +-- +-- Name: password_hash(character, bytea); Type: FUNCTION; Schema: public; Owner: postgres +-- + +CREATE FUNCTION public.password_hash(salt_hex character, pass bytea) RETURNS character + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN ( + SELECT + salt_hex || + encode( + sha256( + decode(salt_hex, 'hex') || /* salt in binary */ + pass /* password */ + ), + 'hex' + ) + ); +END; +$$; + + +-- +-- Name: client; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.client ( + id integer NOT NULL, + user_id integer NOT NULL, + client_uuid character(36) DEFAULT ''::bpchar NOT NULL, + created timestamp with time zone DEFAULT now() NOT NULL, + description character varying DEFAULT ''::character varying NOT NULL +); + + +-- +-- Name: client_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.client_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: client_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres +-- + +ALTER SEQUENCE public.client_id_seq OWNED BY public.client.id; + + +-- +-- Name: node_updates; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.node_updates + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: node; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.node ( + id integer NOT NULL, + user_id integer NOT NULL, + uuid character(36) DEFAULT gen_random_uuid() NOT NULL, + parent_uuid character(36), + created timestamp with time zone DEFAULT now() NOT NULL, + updated timestamp with time zone DEFAULT now() NOT NULL, + deleted timestamp with time zone, + created_seq bigint DEFAULT nextval('public.node_updates'::regclass) NOT NULL, + updated_seq bigint DEFAULT nextval('public.node_updates'::regclass) NOT NULL, + deleted_seq bigint, + name character varying(256) DEFAULT ''::character varying NOT NULL, + content text DEFAULT ''::text NOT NULL, + content_encrypted text DEFAULT ''::text NOT NULL, + markdown boolean DEFAULT false NOT NULL, + history boolean DEFAULT false NOT NULL, + client character(36) DEFAULT ''::bpchar NOT NULL, + client_sequence integer, + CONSTRAINT name_length CHECK ((length(TRIM(BOTH FROM name)) > 0)) +); + + +-- +-- Name: node_history; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.node_history ( + id integer NOT NULL, + user_id integer NOT NULL, + uuid character(36) NOT NULL, + parents character varying[], + created timestamp with time zone NOT NULL, + updated timestamp with time zone NOT NULL, + name character varying(256) NOT NULL, + content text NOT NULL, + content_encrypted text NOT NULL, + markdown boolean DEFAULT false NOT NULL, + client character(36) DEFAULT ''::bpchar NOT NULL, + client_sequence integer +); + + +-- +-- Name: node_history_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.node_history_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: node_history_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres +-- + +ALTER SEQUENCE public.node_history_id_seq OWNED BY public.node_history.id; + + +-- +-- Name: node_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.node_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: node_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres +-- + +ALTER SEQUENCE public.node_id_seq OWNED BY public.node.id; + + +-- +-- Name: test_data; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.test_data + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: user; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public."user" ( + id integer NOT NULL, + username character varying NOT NULL, + name character varying NOT NULL, + password character varying NOT NULL, + last_login timestamp without time zone DEFAULT now() NOT NULL, + timezone character varying DEFAULT 'UTC'::character varying NOT NULL +); + + +-- +-- Name: user_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.user_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: user_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres +-- + +ALTER SEQUENCE public.user_id_seq OWNED BY public."user".id; + + +-- +-- Name: client id; Type: DEFAULT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.client ALTER COLUMN id SET DEFAULT nextval('public.client_id_seq'::regclass); + + +-- +-- Name: node id; Type: DEFAULT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.node ALTER COLUMN id SET DEFAULT nextval('public.node_id_seq'::regclass); + + +-- +-- Name: node_history id; Type: DEFAULT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.node_history ALTER COLUMN id SET DEFAULT nextval('public.node_history_id_seq'::regclass); + + +-- +-- Name: user id; Type: DEFAULT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public."user" ALTER COLUMN id SET DEFAULT nextval('public.user_id_seq'::regclass); + + +-- +-- Name: client client_pk; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.client + ADD CONSTRAINT client_pk PRIMARY KEY (id); + + +-- +-- Name: node_history node_history_pk; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.node_history + ADD CONSTRAINT node_history_pk PRIMARY KEY (id); + + +-- +-- Name: node node_pk; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.node + ADD CONSTRAINT node_pk PRIMARY KEY (id); + + +-- +-- Name: user user_pk; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public."user" + ADD CONSTRAINT user_pk PRIMARY KEY (id); + + +-- +-- Name: client_uuid_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE UNIQUE INDEX client_uuid_idx ON public.client USING btree (client_uuid); + + +-- +-- Name: node_history_client_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE UNIQUE INDEX node_history_client_idx ON public.node_history USING btree (client, client_sequence); + + +-- +-- Name: node_history_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX node_history_idx ON public.node USING btree (history); + + +-- +-- Name: node_history_uuid_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX node_history_uuid_idx ON public.node USING btree (uuid); + + +-- +-- Name: node_search_index; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX node_search_index ON public.node USING gin (name public.gin_trgm_ops, content public.gin_trgm_ops); + + +-- +-- Name: node_uuid_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE UNIQUE INDEX node_uuid_idx ON public.node USING btree (uuid); + + +-- +-- Name: node node_update; Type: TRIGGER; Schema: public; Owner: postgres +-- + +CREATE TRIGGER node_update AFTER UPDATE ON public.node FOR EACH ROW EXECUTE FUNCTION public.node_update_timestamp(); + + +-- +-- Name: node_history node_history_user_fk; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.node_history + ADD CONSTRAINT node_history_user_fk FOREIGN KEY (user_id) REFERENCES public."user"(id) ON UPDATE RESTRICT ON DELETE RESTRICT; + + +-- +-- Name: node node_node_fk; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.node + ADD CONSTRAINT node_node_fk FOREIGN KEY (parent_uuid) REFERENCES public.node(uuid) ON UPDATE SET NULL ON DELETE SET NULL; + + +-- +-- Name: node user_fk; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.node + ADD CONSTRAINT user_fk FOREIGN KEY (user_id) REFERENCES public."user"(id) ON UPDATE RESTRICT ON DELETE RESTRICT; diff --git a/sql/00002.sql b/sql/00002.sql deleted file mode 100644 index 9d7bd8a..0000000 --- a/sql/00002.sql +++ /dev/null @@ -1,19 +0,0 @@ -CREATE FUNCTION public.password_hash(salt_hex char(32), pass bytea) -RETURNS char(96) -LANGUAGE plpgsql -AS -$$ -BEGIN - RETURN ( - SELECT - salt_hex || - encode( - sha256( - decode(salt_hex, 'hex') || /* salt in binary */ - pass /* password */ - ), - 'hex' - ) - ); -END; -$$; diff --git a/sql/00003.sql b/sql/00003.sql deleted file mode 100644 index 0fb2a51..0000000 --- a/sql/00003.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE public.node ADD CONSTRAINT node_node_fk FOREIGN KEY (parent_uuid) REFERENCES public.node("uuid") ON DELETE SET NULL ON UPDATE SET NULL; diff --git a/sql/00004.sql b/sql/00004.sql deleted file mode 100644 index 7ac464d..0000000 --- a/sql/00004.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE public.node ADD COLUMN history bool NOT NULL DEFAULT false; -CREATE INDEX node_history_idx ON public.node (history); diff --git a/sql/00005.sql b/sql/00005.sql deleted file mode 100644 index a366070..0000000 --- a/sql/00005.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE public.node ADD COLUMN client bpchar(36) NOT NULL DEFAULT ''; diff --git a/sql/00006.sql b/sql/00006.sql deleted file mode 100644 index 453b260..0000000 --- a/sql/00006.sql +++ /dev/null @@ -1 +0,0 @@ -DROP INDEX public.node_uuid_idx; diff --git a/sql/00007.sql b/sql/00007.sql deleted file mode 100644 index 40ce48e..0000000 --- a/sql/00007.sql +++ /dev/null @@ -1,162 +0,0 @@ -CREATE TYPE json_ancestor_array as ("Ancestors" varchar[]); - - -CREATE OR REPLACE PROCEDURE add_nodes(p_user_id int4, p_client_uuid varchar, p_nodes jsonb) -LANGUAGE PLPGSQL AS $$ - -DECLARE - node_data jsonb; - node_updated timestamptz; - db_updated timestamptz; - db_uuid bpchar; - db_client bpchar; - db_client_seq int; - node_uuid bpchar; - -BEGIN - RAISE NOTICE '--------------------------'; - FOR node_data IN SELECT * FROM jsonb_array_elements(p_nodes) - LOOP - node_uuid = (node_data->>'UUID')::bpchar; - node_updated = (node_data->>'Updated')::timestamptz; - - /* Retrieve the current modified timestamp for this node from the database. */ - SELECT - uuid, updated, client, client_sequence - INTO - db_uuid, db_updated, db_client, db_client_seq - FROM public."node" - WHERE - user_id = p_user_id AND - uuid = node_uuid; - - /* Is the node not in database? It needs to be created. */ - IF db_uuid IS NULL THEN - RAISE NOTICE '01 New node %', node_uuid; - INSERT INTO public."node" ( - user_id, "uuid", parent_uuid, created, updated, - "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - VALUES( - p_user_id, - node_uuid, - (node_data->>'ParentUUID')::bpchar, - (node_data->>'Created')::timestamptz, - (node_data->>'Updated')::timestamptz, - (node_data->>'Name')::varchar, - (node_data->>'Content')::text, - (node_data->>'Markdown')::bool, - '', /* content_encrypted */ - p_client_uuid, - (node_data->>'ClientSequence')::int - ); - CONTINUE; - END IF; - - - /* The client could send a specific node again if it didn't receive the OK from this procedure before. */ - IF db_updated = node_updated AND db_client = p_client_uuid AND db_client_seq = (node_data->>'ClientSequence')::int THEN - RAISE NOTICE '04, already recorded, %, %', db_client, db_client_seq; - CONTINUE; - END IF; - - /* Determine if the incoming node data is to go into history or replace the current node. */ - IF db_updated > node_updated THEN - RAISE NOTICE '02 DB newer, % > % (%))', db_updated, node_updated, node_uuid; - /* Incoming node is going straight to history since it is older than the current node. */ - INSERT INTO node_history( - user_id, "uuid", parents, created, updated, - "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - VALUES( - p_user_id, - node_uuid, - (jsonb_populate_record(null::json_ancestor_array, node_data))."Ancestors", - (node_data->>'Created')::timestamptz, - (node_data->>'Updated')::timestamptz, - (node_data->>'Name')::varchar, - (node_data->>'Content')::text, - (node_data->>'Markdown')::bool, - '', /* content_encrypted */ - p_client_uuid, - (node_data->>'ClientSequence')::int - ) - ON CONFLICT (client, client_sequence) - DO NOTHING; - ELSE - RAISE NOTICE '03 Client newer, % > % (%, %)', node_updated, db_updated, node_uuid, (node_data->>'ClientSequence'); - /* Incoming node is newer and will replace the current node. - * - * The current node is copied to the node_history table and then modified in place - * with the incoming data. */ - INSERT INTO node_history( - user_id, "uuid", parents, - created, updated, "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - SELECT - user_id, - "uuid", - ( - WITH RECURSIVE nodes AS ( - SELECT - uuid, - COALESCE(parent_uuid, '') AS parent_uuid, - name, - 0 AS depth - FROM node - WHERE - uuid = node_uuid - - UNION - - SELECT - n.uuid, - COALESCE(n.parent_uuid, '') AS parent_uuid, - n.name, - nr.depth+1 AS depth - FROM node n - INNER JOIN nodes nr ON n.uuid = nr.parent_uuid - ) - SELECT ARRAY ( - SELECT name - FROM nodes - ORDER BY depth DESC - OFFSET 1 /* discard itself */ - ) - ), - created, - updated, - name, - content, - markdown, - content_encrypted, - client, - client_sequence - FROM public."node" - WHERE - user_id = p_user_id AND - uuid = node_uuid - ON CONFLICT (client, client_sequence) - DO NOTHING; - - /* Current node in database is updated with incoming data. */ - UPDATE public."node" - SET - updated = (node_data->>'Updated')::timestamptz, - updated_seq = nextval('node_updates'), - name = (node_data->>'Name')::varchar, - content = (node_data->>'Content')::text, - markdown = (node_data->>'Markdown')::bool, - client = p_client_uuid, - client_sequence = (node_data->>'ClientSequence')::int - WHERE - user_id = p_user_id AND - uuid = node_uuid; - END IF; - - END LOOP; -END -$$; diff --git a/sql/00008.sql b/sql/00008.sql deleted file mode 100644 index a91d54c..0000000 --- a/sql/00008.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE node ADD COLUMN Client_sequence int NULL; -ALTER TABLE node_history ADD COLUMN Client_sequence int NULL; diff --git a/sql/00009.sql b/sql/00009.sql deleted file mode 100644 index 332af3a..0000000 --- a/sql/00009.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE UNIQUE INDEX node_history_client_idx ON public.node_history (client,client_sequence); diff --git a/sql/00010.sql b/sql/00010.sql deleted file mode 100644 index c0f14ee..0000000 --- a/sql/00010.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE public.client ( - id serial NOT NULL, - user_id int4 NOT NULL, - client_uuid bpchar(36) DEFAULT '' NOT NULL, - created timestamptz DEFAULT NOW() NOT NULL, - description varchar DEFAULT '' NOT NULL, - CONSTRAINT client_pk PRIMARY KEY (id) -); - -CREATE UNIQUE INDEX client_uuid_idx ON public.client (client_uuid); diff --git a/sql/00011.sql b/sql/00011.sql deleted file mode 100644 index 5b67839..0000000 --- a/sql/00011.sql +++ /dev/null @@ -1,166 +0,0 @@ -CREATE OR REPLACE PROCEDURE add_nodes(p_user_id int4, p_client_uuid varchar, p_nodes jsonb) -LANGUAGE PLPGSQL AS $$ - -DECLARE - node_data jsonb; - node_updated timestamptz; - db_updated timestamptz; - db_uuid bpchar; - db_client bpchar; - db_client_seq int; - node_uuid bpchar; - parent_uuid bpchar; - -BEGIN - RAISE NOTICE '--------------------------'; - FOR node_data IN SELECT * FROM jsonb_array_elements(p_nodes) - LOOP - node_uuid = (node_data->>'UUID')::bpchar; - node_updated = (node_data->>'Updated')::timestamptz; - - IF node_data->>'ParentUUID' = '00000000-0000-0000-0000-000000000000' THEN - parent_uuid = NULL; - ELSE - parent_uuid = node_data->>'ParentUUID'; - END IF; - - /* Retrieve the current modified timestamp for this node from the database. */ - SELECT - uuid, updated, client, client_sequence - INTO - db_uuid, db_updated, db_client, db_client_seq - FROM public."node" - WHERE - user_id = p_user_id AND - uuid = node_uuid; - - /* Is the node not in database? It needs to be created. */ - IF db_uuid IS NULL THEN - RAISE NOTICE '01 New node %', node_uuid; - INSERT INTO public."node" ( - user_id, "uuid", parent_uuid, created, updated, - "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - VALUES( - p_user_id, - node_uuid, - parent_uuid, - (node_data->>'Created')::timestamptz, - (node_data->>'Updated')::timestamptz, - (node_data->>'Name')::varchar, - (node_data->>'Content')::text, - (node_data->>'Markdown')::bool, - '', /* content_encrypted */ - p_client_uuid, - (node_data->>'ClientSequence')::int - ); - CONTINUE; - END IF; - - - /* The client could send a specific node again if it didn't receive the OK from this procedure before. */ - IF db_updated = node_updated AND db_client = p_client_uuid AND db_client_seq = (node_data->>'ClientSequence')::int THEN - RAISE NOTICE '04, already recorded, %, %', db_client, db_client_seq; - CONTINUE; - END IF; - - /* Determine if the incoming node data is to go into history or replace the current node. */ - IF db_updated > node_updated THEN - RAISE NOTICE '02 DB newer, % > % (%))', db_updated, node_updated, node_uuid; - /* Incoming node is going straight to history since it is older than the current node. */ - INSERT INTO node_history( - user_id, "uuid", parents, created, updated, - "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - VALUES( - p_user_id, - node_uuid, - (jsonb_populate_record(null::json_ancestor_array, node_data))."Ancestors", - (node_data->>'Created')::timestamptz, - (node_data->>'Updated')::timestamptz, - (node_data->>'Name')::varchar, - (node_data->>'Content')::text, - (node_data->>'Markdown')::bool, - '', /* content_encrypted */ - p_client_uuid, - (node_data->>'ClientSequence')::int - ) - ON CONFLICT (client, client_sequence) - DO NOTHING; - ELSE - RAISE NOTICE '03 Client newer, % > % (%, %)', node_updated, db_updated, node_uuid, (node_data->>'ClientSequence'); - /* Incoming node is newer and will replace the current node. - * - * The current node is copied to the node_history table and then modified in place - * with the incoming data. */ - INSERT INTO node_history( - user_id, "uuid", parents, - created, updated, "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - SELECT - user_id, - "uuid", - ( - WITH RECURSIVE nodes AS ( - SELECT - uuid, - COALESCE(parent_uuid, '') AS parent_uuid, - name, - 0 AS depth - FROM node - WHERE - uuid = node_uuid - - UNION - - SELECT - n.uuid, - COALESCE(n.parent_uuid, '') AS parent_uuid, - n.name, - nr.depth+1 AS depth - FROM node n - INNER JOIN nodes nr ON n.uuid = nr.parent_uuid - ) - SELECT ARRAY ( - SELECT name - FROM nodes - ORDER BY depth DESC - OFFSET 1 /* discard itself */ - ) - ), - created, - updated, - name, - content, - markdown, - content_encrypted, - client, - client_sequence - FROM public."node" - WHERE - user_id = p_user_id AND - uuid = node_uuid - ON CONFLICT (client, client_sequence) - DO NOTHING; - - /* Current node in database is updated with incoming data. */ - UPDATE public."node" - SET - updated = (node_data->>'Updated')::timestamptz, - updated_seq = nextval('node_updates'), - name = (node_data->>'Name')::varchar, - content = (node_data->>'Content')::text, - markdown = (node_data->>'Markdown')::bool, - client = p_client_uuid, - client_sequence = (node_data->>'ClientSequence')::int - WHERE - user_id = p_user_id AND - uuid = node_uuid; - END IF; - - END LOOP; -END -$$; diff --git a/sql/00012.sql b/sql/00012.sql deleted file mode 100644 index e62f011..0000000 --- a/sql/00012.sql +++ /dev/null @@ -1,166 +0,0 @@ -CREATE OR REPLACE PROCEDURE add_nodes(p_user_id int4, p_client_uuid varchar, p_nodes jsonb) -LANGUAGE PLPGSQL AS $$ - -DECLARE - node_data jsonb; - node_updated timestamptz; - db_updated timestamptz; - db_uuid bpchar; - db_client bpchar; - db_client_seq int; - node_uuid bpchar; - parent_uuid_nullable bpchar; - -BEGIN - RAISE NOTICE '--------------------------'; - FOR node_data IN SELECT * FROM jsonb_array_elements(p_nodes) - LOOP - node_uuid = (node_data->>'UUID')::bpchar; - node_updated = (node_data->>'Updated')::timestamptz; - - IF node_data->>'ParentUUID' = '00000000-0000-0000-0000-000000000000' THEN - parent_uuid_nullable = NULL; - ELSE - parent_uuid_nullable = node_data->>'ParentUUID'; - END IF; - - /* Retrieve the current modified timestamp for this node from the database. */ - SELECT - uuid, updated, client, client_sequence - INTO - db_uuid, db_updated, db_client, db_client_seq - FROM public."node" - WHERE - user_id = p_user_id AND - uuid = node_uuid; - - /* Is the node not in database? It needs to be created. */ - IF db_uuid IS NULL THEN - RAISE NOTICE '01 New node %', node_uuid; - INSERT INTO public."node" ( - user_id, "uuid", parent_uuid, created, updated, - "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - VALUES( - p_user_id, - node_uuid, - parent_uuid_nullable, - (node_data->>'Created')::timestamptz, - (node_data->>'Updated')::timestamptz, - (node_data->>'Name')::varchar, - (node_data->>'Content')::text, - (node_data->>'Markdown')::bool, - '', /* content_encrypted */ - p_client_uuid, - (node_data->>'ClientSequence')::int - ); - CONTINUE; - END IF; - - - /* The client could send a specific node again if it didn't receive the OK from this procedure before. */ - IF db_updated = node_updated AND db_client = p_client_uuid AND db_client_seq = (node_data->>'ClientSequence')::int THEN - RAISE NOTICE '04, already recorded, %, %', db_client, db_client_seq; - CONTINUE; - END IF; - - /* Determine if the incoming node data is to go into history or replace the current node. */ - IF db_updated > node_updated THEN - RAISE NOTICE '02 DB newer, % > % (%))', db_updated, node_updated, node_uuid; - /* Incoming node is going straight to history since it is older than the current node. */ - INSERT INTO node_history( - user_id, "uuid", parents, created, updated, - "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - VALUES( - p_user_id, - node_uuid, - (jsonb_populate_record(null::json_ancestor_array, node_data))."Ancestors", - (node_data->>'Created')::timestamptz, - (node_data->>'Updated')::timestamptz, - (node_data->>'Name')::varchar, - (node_data->>'Content')::text, - (node_data->>'Markdown')::bool, - '', /* content_encrypted */ - p_client_uuid, - (node_data->>'ClientSequence')::int - ) - ON CONFLICT (client, client_sequence) - DO NOTHING; - ELSE - RAISE NOTICE '03 Client newer, % > % (%, %)', node_updated, db_updated, node_uuid, (node_data->>'ClientSequence'); - /* Incoming node is newer and will replace the current node. - * - * The current node is copied to the node_history table and then modified in place - * with the incoming data. */ - INSERT INTO node_history( - user_id, "uuid", parents, - created, updated, "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - SELECT - user_id, - "uuid", - ( - WITH RECURSIVE nodes AS ( - SELECT - uuid, - COALESCE(parent_uuid, '') AS parent_uuid, - name, - 0 AS depth - FROM node - WHERE - uuid = node_uuid - - UNION - - SELECT - n.uuid, - COALESCE(n.parent_uuid, '') AS parent_uuid, - n.name, - nr.depth+1 AS depth - FROM node n - INNER JOIN nodes nr ON n.uuid = nr.parent_uuid - ) - SELECT ARRAY ( - SELECT name - FROM nodes - ORDER BY depth DESC - OFFSET 1 /* discard itself */ - ) - ), - created, - updated, - name, - content, - markdown, - content_encrypted, - client, - client_sequence - FROM public."node" - WHERE - user_id = p_user_id AND - uuid = node_uuid - ON CONFLICT (client, client_sequence) - DO NOTHING; - - /* Current node in database is updated with incoming data. */ - UPDATE public."node" - SET - updated = (node_data->>'Updated')::timestamptz, - updated_seq = nextval('node_updates'), - name = (node_data->>'Name')::varchar, - content = (node_data->>'Content')::text, - markdown = (node_data->>'Markdown')::bool, - client = p_client_uuid, - client_sequence = (node_data->>'ClientSequence')::int - WHERE - user_id = p_user_id AND - uuid = node_uuid; - END IF; - - END LOOP; -END -$$; diff --git a/static/css/notes2.css b/static/css/notes2.css index 71737dd..31e1f1f 100644 --- a/static/css/notes2.css +++ b/static/css/notes2.css @@ -52,51 +52,51 @@ html { #tree { grid-area: tree; display: grid; - background-color: #fafafa; + background-color: #ffffff; color: #444; z-index: 100; - border-right: 1px solid #ddd; - - n2-tree { - /*border: 2px solid #f8f8f8;*/ - padding: 16px 48px 16px 24px; - } - - &:focus-within { - n2-tree { - /* - border: 2px solid #fe5f55; - */ - } - - } - + border-right: 2px solid #ddd; #logo { display: grid; - position: relative; - justify-items: center; - margin-top: 8px; - margin-bottom: 8px; - margin-left: 24px; - margin-right: 24px; + grid-template-columns: min-content 1fr min-content; + align-items: center; + justify-items: start; cursor: pointer; + padding: 16px; + border-bottom: 1px solid #ccc; - img { - width: 128px; - left: -20px; + .el-search { + justify-self: end; + } + img:first-child { + height: 24px; + margin-right: 8px; } } .icons { display: flex; justify-content: center; - margin-bottom: 32px; + margin: 16px 0px 32px 0px; gap: 8px; } + n2-tree { + .el-treenodes { + margin: 32px; + } + } + + &:focus-within { + n2-tree { + } + + } + + .node { display: grid; grid-template-columns: 40px min-content; @@ -145,14 +145,27 @@ html { } } -#tree-nodes { - padding: 16px 32px; - /* - border-radius: 8px; -*/ - /* - box-shadow: 5px 5px 10px -5px rgba(0, 0, 0, 0.75); - */ +[id^="page-"] { + display: none; +} + +#main-page { + display: contents; + + &.node { + #page-node { + display: contents; + } + } + + &.storage { + #page-storage { + display: contents; + n2-pagestorage { + grid-area: content; + } + } + } } #crumbs { @@ -316,7 +329,6 @@ n2-nodeui { margin-bottom: 32px; &:invalid { - background: #f5f5f5; padding-top: 16px; } } diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..299310f Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/images/collapsed.svg b/static/images/collapsed.svg index d93f4ca..db06415 100644 --- a/static/images/collapsed.svg +++ b/static/images/collapsed.svg @@ -8,7 +8,7 @@ version="1.1" id="svg1" sodipodi:docname="collapsed.svg" - inkscape:version="1.4.2 (ebf0e94, 2025-05-08)" + inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" @@ -23,13 +23,13 @@ inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="px" - inkscape:zoom="4.8373092" - inkscape:cx="6.201795" - inkscape:cy="-12.40359" - inkscape:window-width="1916" - inkscape:window-height="1161" - inkscape:window-x="0" - inkscape:window-y="18" + inkscape:zoom="19.349237" + inkscape:cx="11.809251" + inkscape:cy="6.3051583" + inkscape:window-width="1093" + inkscape:window-height="1401" + inkscape:window-x="2560" + inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="layer1" showguides="false" /> @@ -42,9 +42,13 @@ transform="translate(-102.39375,-146.31458)">
${content}
\n` + return `${content}
\n` }, list(token) { @@ -134,7 +138,7 @@ export class MarkedPosition { }, listitem(token) { - return ``
+ return ``
+ (token.escaped ? code : escapeHtmlEntities(code, true))
+ '
\n'
}
- return `'
+ (token.escaped ? code : escapeHtmlEntities(code, true))
@@ -157,7 +161,7 @@ export class MarkedPosition {
blockquote(token) {
const body = this.parser.parse(token.tokens)
- return `\n${body}
\n`
+ return `\n${body}
\n`
},
html(token) {
@@ -169,11 +173,11 @@ export class MarkedPosition {
},
hr(token) {
- return `
\n`
+ return `
\n`
},
checkbox(token) {
- return ` '
},
@@ -218,7 +222,7 @@ export class MarkedPosition {
if (token.tokens.length > 0) {
const start = token.tokens[0].position.start.offset
const end = token.tokens[0].position.end.offset
- ofs = `onclick="setpos(event)" data-offset-start="${start}" data-offset-end="${end}"`
+ ofs = `ondblclick="setpos(event)" data-offset-start="${start}" data-offset-end="${end}"`
}
const content = this.parser.parseInline(token.tokens);
@@ -230,23 +234,23 @@ export class MarkedPosition {
},
strong(token) {
- return `${this.parser.parseInline(token.tokens)}`
+ return `${this.parser.parseInline(token.tokens)}`
},
em(token) {
- return `${this.parser.parseInline(token.tokens)}`
+ return `${this.parser.parseInline(token.tokens)}`
},
codespan(token) {
- return `${escapeHtmlEntities(token.text, true)}`
+ return `${escapeHtmlEntities(token.text, true)}`
},
br(token) {
- return `
`
+ return `
`
},
del(token) {
- return `${this.parser.parseInline(token.tokens)}`
+ return `${this.parser.parseInline(token.tokens)}`
},
link(token) {
@@ -256,7 +260,7 @@ export class MarkedPosition {
return text
}
token.href = cleanHref
- let out = ''
+ out += '>'
return out
},
@@ -291,8 +293,34 @@ export class MarkedPosition {
}
})
- }// }}}
+ }// }}}}}}
parse(text) {// {{{
return this.marked.parse(text)
}// }}}
+ async whenElementExist(id) {// {{{
+ // The element could have already been created.
+ const element = document.getElementById(id)
+ if (element) {
+ return element
+ }
+
+ const observer = new MutationObserver((_mutations, observer) => {
+ const target = document.getElementById(id)
+ if (target) {
+ observer.disconnect()
+ return target
+ }
+ })
+
+ observer.observe(document.documentElement, {
+ childList: true,
+ subtree: true
+ })
+ }// }}}
+ async populateImg(fileID, elementID) {// {{{
+ let img = await globalThis.nodeStore.files.get(fileID)
+ const el = await this.whenElementExist(elementID)
+
+ el.src = URL.createObjectURL(img.file)
+ }// }}}
}
diff --git a/static/js/node_store.mjs b/static/js/node_store.mjs
index e849e29..d29923f 100644
--- a/static/js/node_store.mjs
+++ b/static/js/node_store.mjs
@@ -12,10 +12,11 @@ export class NodeStore {
this.nodes = {}
this.sendQueue = null
this.nodesHistory = null
+ this.files = null
}//}}}
initializeDB() {//{{{
return new Promise((resolve, reject) => {
- const req = indexedDB.open('notes', 7)
+ const req = indexedDB.open('notes', 8)
// Schema upgrades for IndexedDB.
// These can start from different points depending on updates to Notes2 since a device was online.
@@ -24,6 +25,7 @@ export class NodeStore {
let appState
let sendQueue
let nodesHistory
+ let files
const db = event.target.result
const trx = event.target.transaction
@@ -61,6 +63,10 @@ export class NodeStore {
case 7:
trx.objectStore('nodes_history').createIndex('byUUID', 'UUID', { unique: false })
break
+
+ case 8:
+ files = db.createObjectStore('files', { keyPath: 'UUID' })
+ break
}
}
}
@@ -69,6 +75,7 @@ export class NodeStore {
this.db = event.target.result
this.sendQueue = new SimpleNodeStore(this.db, 'send_queue')
this.nodesHistory = new SimpleNodeStore(this.db, 'nodes_history')
+ this.files = new SimpleNodeStore(this.db, 'files')
this.initializeRootNode()
.then(() => resolve())
}
@@ -159,39 +166,6 @@ export class NodeStore {
})
}//}}}
- /*
- upsertNodeRecords(records) {//{{{
- return new Promise((resolve, reject) => {
- const t = this.db.transaction('nodes', 'readwrite')
- const nodeStore = t.objectStore('nodes')
- t.onerror = (event) => {
- console.log('transaction error', event.target.error)
- reject(event.target.error)
- }
- t.oncomplete = () => {
- resolve()
- }
-
- // records is an object, not an array.
- for (const i in records) {
- const record = records[i]
-
- let addReq
- let op
- if (record.Deleted) {
- op = 'deleting'
- addReq = nodeStore.delete(record.UUID)
- } else {
- op = 'upserting'
- // 'modified' is a local property for tracking
- // nodes needing to be synced to backend.
- record.modified = 0
- addReq = nodeStore.put(record)
- }
- }
- })
- }//}}}
- */
getTreeNodes(parent, newLevel) {//{{{
return new Promise((resolve, reject) => {
// Parent of toplevel nodes is ROOT_NODE in indexedDB.
@@ -376,8 +350,20 @@ class SimpleNodeStore {
}
})
}//}}}
+ get(key) {//{{{
+ return new Promise((resolve, _reject) => {
+ const req = this.db
+ .transaction(['nodes', this.storeName], 'readonly')
+ .objectStore(this.storeName)
+ .get(key)
+
+ req.onsuccess = (event) => {
+ resolve(event.target.result)
+ }
+ })
+ }//}}}
retrieve(limit) {//{{{
- return new Promise((resolve, reject) => {
+ return new Promise((resolve, _reject) => {
const cursorReq = this.db
.transaction(['nodes', this.storeName], 'readonly')
.objectStore(this.storeName)
@@ -433,4 +419,51 @@ class SimpleNodeStore {
}//}}}
}
+export class StoreFile {
+ static createFromFileObject(f) {
+ const obj = new StoreFile()
+ obj.name = f.name
+ obj.size = f.size
+ obj.mime = f.type
+ return obj
+ }
+ constructor() {
+ this.name = ''
+ this.size = 0
+ this.mime = ''
+
+ this.objectURL = null // URL.createObjectURL(blob)
+ }
+ data() {
+ return {
+ }
+ }
+}
+
+export function uuidv7() {
+ // random bytes
+ const value = new Uint8Array(16)
+ crypto.getRandomValues(value)
+
+ // current timestamp in ms
+ const timestamp = BigInt(Date.now())
+
+ // timestamp
+ value[0] = Number((timestamp >> 40n) & 0xffn)
+ value[1] = Number((timestamp >> 32n) & 0xffn)
+ value[2] = Number((timestamp >> 24n) & 0xffn)
+ value[3] = Number((timestamp >> 16n) & 0xffn)
+ value[4] = Number((timestamp >> 8n) & 0xffn)
+ value[5] = Number(timestamp & 0xffn)
+
+ // version and variant
+ value[6] = (value[6] & 0x0f) | 0x70
+ value[8] = (value[8] & 0x3f) | 0x80
+
+ const str = Array.from(value)
+ .map((b) => b.toString(16).padStart(2, "0"))
+ .join("")
+ return `${str.slice(0, 8)}-${str.slice(8, 12)}-${str.slice(12, 16)}-${str.slice(16, 20)}-${str.slice(20)}`
+}
+
// vim: foldmethod=marker
diff --git a/static/js/notes2.mjs b/static/js/notes2.mjs
index 7bef2ad..ddaf891 100644
--- a/static/js/notes2.mjs
+++ b/static/js/notes2.mjs
@@ -69,7 +69,7 @@ export class Notes2 extends Component {
}
if (!dontPush)
- history.pushState({ nodeUUID }, '', `/notes2#${nodeUUID}`)
+ history.pushState({ nodeUUID }, '', `/#${nodeUUID}`)
// New node is fetched in order to retrieve content and files.
// Such data is unnecessary to transfer for tree/navigational purposes.
@@ -86,404 +86,6 @@ export class Notes2 extends Component {
}//}}}
}
-class Tree extends Component {
- constructor(props) {//{{{
- super(props)
- console.log('new tree')
- this.treeNodeComponents = {}
- this.treeTrunk = []
- this.selectedNode = null
- this.expandedNodes = {} // keyed on UUID
- this.treeDiv = createRef()
-
- // childrenFetchedCallbacks is keyed on a UUID and each
- // item is an array with callbacks called when a UUID has
- // had all children fetched.
- this.childrenFetchedCallbacks = {}
-
- this.props.app.tree = this
-
- this.populateFirstLevel()
- }//}}}
- render({ app }) {//{{{
- const renderedTreeTrunk = this.treeTrunk.map(node => {
- this.treeNodeComponents[node.UUID] = createRef()
- return html`<${TreeNode} key=${`treenode_${node.UUID}`} tree=${this} node=${node} ref=${this.treeNodeComponents[node.UUID]} selected=${node.UUID === app.state.startNode?.UUID} />`
- })
-
- return html`
-
- _notes2.current.goToNode(ROOT_NODE)}>
-
-
_mbus.dispatch('op-search')} />
-
_sync.run()} />
-
- ${renderedTreeTrunk}
- `
- }//}}}
- componentDidMount() {//{{{
- //this.treeDiv.current.addEventListener('keydown', event => this.keyHandler(event))
-
- // This will show and select the treenode that is selected in the node UI.
- const node = _notes2.current?.nodeUI.current?.node.value
- if (node === null)
- return
- _notes2.current.tree.expandToTrunk(node)
- this.setSelected(node)
- }//}}}
-
- fetchChildrenNotify(uuid, fn) {//{{{
- if (this.childrenFetchedCallbacks[uuid] === undefined)
- this.childrenFetchedCallbacks[uuid] = [fn]
- else
- this.childrenFetchedCallbacks[uuid].push(fn)
- }//}}}
- fetchChildrenOn(uuid) {//{{{
- if (this.childrenFetchedCallbacks[uuid] === undefined)
- return
- for (const fn of this.childrenFetchedCallbacks[uuid])
- fn(uuid)
- delete this.childrenFetchedCallbacks[uuid]
- }//}}}
-
- populateFirstLevel(callback = null) {//{{{
- nodeStore.get(ROOT_NODE)
- .then(node => node.fetchChildren())
- .then(children => {
- this.treeNodeComponents = {}
- this.treeTrunk = []
- for (const node of children) {
- // The root node isn't supposed to be shown in the tree.
- if (node.UUID === ROOT_NODE)
- continue
- if (node.ParentUUID === ROOT_NODE)
- this.treeTrunk.push(node)
- }
- this.forceUpdate()
- if (callback)
- callback()
-
- })
- .catch(e => { console.log(e); console.log(e.type, e.error); alert(e.error) })
- }//}}}
- setSelected(node, dontExpand) {//{{{
- // The previously selected node, if any, needs to be rerendered
- // to not retain its 'selected' class.
- const prevUUID = this.selectedNode?.UUID
- this.selectedNode = node
- if (prevUUID)
- this.treeNodeComponents[prevUUID]?.current.forceUpdate()
-
- // And now the newly selected node is rerendered.
- this.treeNodeComponents[node.UUID]?.current.forceUpdate()
-
- if (!dontExpand)
- this.setNodeExpanded(node, true)
- }//}}}
- isSelected(node) {//{{{
- return this.selectedNode?.UUID === node.UUID
- }//}}}
- async expandToTrunk(node) {//{{{
- // Get all ancestors from a certain node up to the highest grandparent.
- const ancestry = await nodeStore.getNodeAncestry(node, [])
- for (const i in ancestry) {
- await nodeStore.node(ancestry[i].UUID).fetchChildren()
- this.setNodeExpanded(ancestry[i], true)
- }
-
- // Already a top node, no need to expand anything.
- if (ancestry.length === 0)
- return
-
- // Start the chain of by expanding the top node.
- this.setNodeExpanded(ancestry[ancestry.length - 1], true)
- }//}}}
- getNodeExpanded(UUID) {//{{{
- if (this.expandedNodes[UUID] === undefined)
- this.expandedNodes[UUID] = signal(false)
- return this.expandedNodes[UUID].value
- }//}}}
- async setNodeExpanded(node, value) {//{{{
- return new Promise((resolve, reject) => {
- const work = uuid => {
- // Creating a default value if it doesn't exist already.
- this.getNodeExpanded(uuid)
- this.expandedNodes[uuid].value = value
- resolve()
- }
-
- if (node.hasFetchedChildren()) {
- work(node.UUID)
- return
- } else {
- this.fetchChildrenNotify(node.UUID, uuid => work(uuid))
- }
- })
- }//}}}
- getParentWithNextSibling(node) {//{{{
- let currNode = node
- while (currNode !== null && currNode.UUID !== ROOT_NODE && currNode.getSiblingAfter() === null) {
- currNode = currNode.getParent()
- }
- return currNode?.getSiblingAfter()
- }//}}}
- getLastExpandedNode(node) {//{{{
- let currNode = node
- while (this.getNodeExpanded(currNode.UUID) && currNode.hasChildren()) {
- currNode = currNode.Children[currNode.Children.length - 1]
- }
- return currNode
- }//}}}
-
- async recursiveExpand(node, state) {//{{{
- if (state)
- await this.setNodeExpanded(node, true)
-
- for (const child of node.Children)
- await this.recursiveExpand(child, state)
-
- if (!state)
- await this.setNodeExpanded(node, false)
- }//}}}
-
- async keyHandler(event) {//{{{
- let handled = true
- const n = this.selectedNode
- const Space = ' '
-
- // This handler would otherwise react to stuff like Ctrl+L.
- if (event.ctrlKey || event.altKey)
- return
-
- switch (event.key) {
- // Space and enter is toggling expansion.
- // Holding shift down does it recursively.
- case Space:
- case 'Enter':
- const expanded = this.getNodeExpanded(n.UUID)
- if (event.shiftKey) {
- this.recursiveExpand(n, !expanded)
- } else {
- this.setNodeExpanded(n, !expanded)
- }
- break
-
- case 'g':
- case 'Home':
- this.navigateTop()
- break
-
- case 'G':
- case 'End':
- this.navigateBottom()
- break
-
- case 'j':
- case 'ArrowDown':
- await this.navigateDown(this.selectedNode)
- break
-
- case 'k':
- case 'ArrowUp':
- await this.navigateUp(this.selectedNode)
- break
-
- case 'h':
- case 'ArrowLeft':
- await this.navigateLeft(this.selectedNode)
- break
-
- case 'l':
- case 'ArrowRight':
- await this.navigateRight(this.selectedNode)
- break
-
- default:
- // nonsole.log(event.key)
- handled = false
- }
-
- if (handled) {
- event.preventDefault()
- event.stopPropagation()
- }
- }//}}}
- async navigateLeft(n) {//{{{
- if (n === null)
- return
-
- const expanded = this.getNodeExpanded(n.UUID)
- if (expanded && n.hasChildren()) {
- this.setNodeExpanded(n, false)
- return
- }
-
- if (n.isFirstSibling() && n.getParent().UUID !== ROOT_NODE) {
- await _notes2.current.goToNode(n.getParent()?.UUID, true, true)
- return
- }
-
- const siblingBefore = n.getSiblingBefore()
- const siblingExpanded = this.getNodeExpanded(siblingBefore?.UUID)
- if (siblingBefore !== null && siblingExpanded && siblingBefore.hasChildren()) {
- const siblingAbove = this.getLastExpandedNode(siblingBefore)
- await _notes2.current.goToNode(siblingAbove?.UUID, true, true)
- return
- }
-
- await _notes2.current.goToNode(n.getSiblingBefore()?.UUID, true, true)
- }//}}}
- async navigateRight(n) {//{{{
- if (n === null)
- return
-
- const siblingAfter = n.getSiblingAfter()
- const expanded = this.getNodeExpanded(n.UUID)
-
- if (!expanded && n.hasChildren()) {
- this.setNodeExpanded(n, true)
- return
- }
-
- if (expanded && n.hasChildren()) {
- await _notes2.current.goToNode(n.Children[0]?.UUID, true, true)
- return
- }
-
- if (n.isLastSibling()) {
- const nextNode = this.getParentWithNextSibling(n)
- await _notes2.current.goToNode(nextNode?.UUID, true, true)
- return
- }
-
- await _notes2.current.goToNode(n.getSiblingAfter()?.UUID, true, true)
- }//}}}
- async navigateUp(n) {//{{{
- if (n === null)
- return
-
- let parent = null
- const siblingBefore = n.getSiblingBefore()
- let siblingExpanded = false
- if (siblingBefore !== null)
- siblingExpanded = this.getNodeExpanded(siblingBefore.UUID)
-
- if (n.isFirstSibling()) {
- parent = n.getParent()
- if (parent?.UUID === ROOT_NODE)
- return
- await _notes2.current.goToNode(parent?.UUID, true, true)
- return
- }
-
- if (siblingBefore !== null && siblingExpanded && siblingBefore.hasChildren()) {
- await _notes2.current.goToNode(siblingBefore.Children[siblingBefore.Children.length - 1]?.UUID, true, true)
- return
- }
-
- if (siblingBefore) {
- await _notes2.current.goToNode(siblingBefore.UUID, true, true)
- return
- }
- }//}}}
- async navigateDown(n) {//{{{
- if (n === null)
- return
-
- const nodeExpanded = this.getNodeExpanded(n.UUID)
-
- // Last node, not expanded, so it matters not whether it has children or not.
- // Traverse upward to nearest parent with next sibling.
- if (!nodeExpanded && n.isLastSibling()) {
- const wantedNode = this.getParentWithNextSibling(n)
- await _notes2.current.goToNode(wantedNode?.UUID, true, true)
- return
- }
-
- if (nodeExpanded && n.isLastSibling() && !n.hasChildren()) {
- const wantedNode = this.getParentWithNextSibling(n)
- await _notes2.current.goToNode(wantedNode?.UUID, true, true)
- return
- }
-
- // Node not expanded. Go to this node's next sibling.
- // GoToNode will abort if given null.
- if (!nodeExpanded || !n.hasChildren()) {
- await _notes2.current.goToNode(n.getSiblingAfter()?.UUID, true, true)
- return
- }
-
- // Node is expanded.
- // Children will be visually beneath this node, if any.
- if (nodeExpanded && n.hasChildren()) {
- await _notes2.current.goToNode(n.Children[0].UUID, true, true)
- return
- }
- }//}}}
- async navigateTop() {//{{{
- const root = await nodeStore.get(ROOT_NODE)
- if (root.Children.length === 0)
- return
- await _notes2.current.goToNode(root.Children[0]?.UUID, true, true)
- }//}}}
- async navigateBottom() {//{{{
- const root = await nodeStore.get(ROOT_NODE)
- if (root.Children.length === 0)
- return
-
- const toplevel = root.Children[root.Children.length - 1]
- const toplevelExpanded = this.getNodeExpanded(toplevel?.UUID)
-
- if (toplevelExpanded) {
- const lastnode = this.getLastExpandedNode(toplevel)
- await _notes2.current.goToNode(lastnode?.UUID, true, true)
- } else
- await _notes2.current.goToNode(root.Children[root.Children.length - 1]?.UUID, true, true)
- }//}}}
-}
-
-class TreeNode extends Component {
- constructor(props) {//{{{
- super(props)
- this.children_populated = signal(false)
- if (this.props.node.Level === 0 || this.props.tree.getNodeExpanded(this.props.node.UUID))
- this.fetchChildren()
- }//}}}
- render({ tree, node, parent }) {//{{{
- // Fetch the next level of children if the parent tree node is expanded and our children thus will be visible.
- const selected = tree.isSelected(node) ? 'selected' : ''
-
- if (!this.children_populated.value && tree.getNodeExpanded(parent?.props.node.UUID))
- this.fetchChildren()
-
- const children = node.Children.map(node => {
- tree.treeNodeComponents[node.UUID] = createRef()
- return html`<${TreeNode} key=${`treenode_${node.UUID}`} tree=${tree} node=${node} parent=${this} ref=${tree.treeNodeComponents[node.UUID]} selected=${node.UUID === tree.props.app.startNode?.UUID} />`
- })
-
- let expandImg = ''
- if (node.Children.length === 0)
- expandImg = html`
`
- else {
- if (tree.getNodeExpanded(node.UUID))
- expandImg = html`
`
- else
- expandImg = html`
`
- }
-
- return html`
-
-
- window._notes2.current.goToNode(node.UUID)}>${node.get('Name')}
-
- `
- }//}}}
- async fetchChildren() {//{{{
- await this.props.node.fetchChildren()
- this.children_populated.value = true
- }//}}}
-}
-
class Op {
constructor(id) {
this.id = id
diff --git a/static/js/node.mjs b/static/js/page_node.mjs
similarity index 82%
rename from static/js/node.mjs
rename to static/js/page_node.mjs
index acebbb7..86c29c0 100644
--- a/static/js/node.mjs
+++ b/static/js/page_node.mjs
@@ -1,8 +1,8 @@
-import { ROOT_NODE } from 'node_store'
+import { ROOT_NODE, uuidv7, StoreFile } from 'node_store'
import { CustomHTMLElement } from './lib/custom_html_element.mjs'
import { MarkedPosition } from './marked_position.mjs'
-export class N2NodeUI extends CustomHTMLElement {
+export class N2PageNodeUI extends CustomHTMLElement {
static {// {{{
this.tmpl = document.createElement('template')
this.tmpl.innerHTML = `
@@ -48,7 +48,7 @@ export class N2NodeUI extends CustomHTMLElement {
_mbus.subscribe('NODE_MODIFIED', () => {
document.querySelector('#crumbs .crumbs')?.classList.add('node-modified')
this.elIconSave.src = `/images/${_VERSION}/icon_save.svg`
- this.render()
+ this.renderName()
})
_mbus.subscribe('NODE_UNMODIFIED', () => {
@@ -72,10 +72,14 @@ export class N2NodeUI extends CustomHTMLElement {
}
})
this.elNodeContent.addEventListener('input', event => this.contentChanged(event))
+ this.elNodeContent.addEventListener('paste', async (event) => this.pasteHandler(event))
this.elIconMarkdown.addEventListener('click', () => this.showMarkdown(!this.showMarkdown()))
this.showMarkdown(true)
}// }}}
+ renderName() {// {{{
+ this.elName.innerText = this.node?.get('Name') ?? ''
+ }// }}}
render() {// {{{
this.elName.innerText = this.node?.get('Name') ?? ''
this.elNodeContent.value = this.node?.get('Content') ?? ''
@@ -115,6 +119,47 @@ export class N2NodeUI extends CustomHTMLElement {
return this.classList.contains('show-markdown')
}
}// }}}
+ async pasteHandler(event) {
+ const clipboardItems = event.clipboardData?.items
+ if (!clipboardItems)
+ return
+
+ for (const item of clipboardItems) {
+ switch (item.kind) {
+ case 'string':
+ continue
+
+ case 'file':
+ const file = item.getAsFile()
+ if (!file)
+ throw new Error("Couldn't convert image to file object.")
+ const uuid = uuidv7()
+ await globalThis.nodeStore.files.add({ data: { UUID: uuid, file: file }})
+
+ const [start, end] = [this.elNodeContent.selectionStart, this.elNodeContent.selectionEnd]
+ this.elNodeContent.setRangeText(``, start, end, 'select');
+
+ break
+
+ default:
+ alert(`Unknown paste type of '${item.kind}'`)
+ }
+ }
+ }
+
+ // Example usage: Displaying the image or preparing it for upload
+ handleImageBlob(blob) {
+ // 1. Create a local URL to preview it in an
tag if needed
+ const localUrl = URL.createObjectURL(blob)
+ console.log('Local preview URL:', localUrl)
+
+ // 2. Or prepare it for a FormData upload
+ const formData = new FormData()
+ formData.append('image', blob, 'pasted-image.png')
+
+ // fetch('/upload', { method: 'POST', body: formData })
+ }
+
editMarkdown(data) {// {{{
this.showMarkdown(false)
this.elNodeContent.selectionStart = data.position.start
@@ -122,7 +167,7 @@ export class N2NodeUI extends CustomHTMLElement {
this.elNodeContent.focus()
}// }}}
}
-customElements.define('n2-nodeui', N2NodeUI)
+customElements.define('n2-nodeui', N2PageNodeUI)
export class Node {
static sort(a, b) {//{{{
@@ -257,30 +302,4 @@ export class Node {
}//}}}
}
-function uuidv7() {
- // random bytes
- const value = new Uint8Array(16)
- crypto.getRandomValues(value)
-
- // current timestamp in ms
- const timestamp = BigInt(Date.now())
-
- // timestamp
- value[0] = Number((timestamp >> 40n) & 0xffn)
- value[1] = Number((timestamp >> 32n) & 0xffn)
- value[2] = Number((timestamp >> 24n) & 0xffn)
- value[3] = Number((timestamp >> 16n) & 0xffn)
- value[4] = Number((timestamp >> 8n) & 0xffn)
- value[5] = Number(timestamp & 0xffn)
-
- // version and variant
- value[6] = (value[6] & 0x0f) | 0x70
- value[8] = (value[8] & 0x3f) | 0x80
-
- const str = Array.from(value)
- .map((b) => b.toString(16).padStart(2, "0"))
- .join("")
- return `${str.slice(0, 8)}-${str.slice(8, 12)}-${str.slice(12, 16)}-${str.slice(16, 20)}-${str.slice(20)}`
-}
-
// vim: foldmethod=marker
diff --git a/static/js/page_storage.mjs b/static/js/page_storage.mjs
new file mode 100644
index 0000000..931a718
--- /dev/null
+++ b/static/js/page_storage.mjs
@@ -0,0 +1,28 @@
+import { CustomHTMLElement } from "./lib/custom_html_element.mjs"
+
+export class N2PageStorage extends CustomHTMLElement {
+ static {
+ this.tmpl = document.createElement('template')
+ this.tmpl.innerHTML = `
+ Local storage
+
+
+
+ `
+ }
+ constructor() {
+ super()
+
+ window._mbus.subscribe('SHOW_PAGE', () => this.render())
+ }
+ async render() {
+ const countNodes = await globalThis.nodeStore.nodeCount()
+ const countQueuedNodes = await globalThis.nodeStore.sendQueue.count()
+ const countHistoryNodes = await globalThis.nodeStore.nodesHistory.count()
+
+ this.elCountNodes.innerText = countNodes
+ this.elCountQueuedNodes.innerText = countQueuedNodes
+ this.elCountHistoryNodes.innerText = countHistoryNodes
+ }
+}
+customElements.define('n2-pagestorage', N2PageStorage)
diff --git a/static/js/sync.mjs b/static/js/sync.mjs
index 9b58cf7..e432f15 100644
--- a/static/js/sync.mjs
+++ b/static/js/sync.mjs
@@ -9,6 +9,9 @@ export class Sync {
}//}}}
async run() {//{{{
+ // XXX - Delete me
+ return
+
try {
let duration = 0 // in ms
@@ -163,13 +166,13 @@ export class Sync {
}
export class N2SyncProgress extends CustomHTMLElement {
- static {
+ static {// {{{
this.tmpl = document.createElement('template')
this.tmpl.innerHTML = `
0 / 0
`
- }
+ }// }}}
constructor() {//{{{
super()
diff --git a/static/js/tree.mjs b/static/js/tree.mjs
index 3732fc5..b672742 100644
--- a/static/js/tree.mjs
+++ b/static/js/tree.mjs
@@ -1,14 +1,19 @@
import { ROOT_NODE } from 'node_store'
import { CustomHTMLElement } from './lib/custom_html_element.mjs'
+import { Color, Solver } from './lib/css_colorize.mjs'
export class N2Tree extends CustomHTMLElement {
static {// {{{
this.tmpl = document.createElement('template')
this.tmpl.innerHTML = `
-
-
+
+
+
-
+
+
+
+
`
@@ -31,7 +36,7 @@ export class N2Tree extends CustomHTMLElement {
this.elSync.addEventListener('click', () => _sync.run())
this.elLogo.addEventListener('click', () => _app.goToNode(ROOT_NODE, false, false))
- _mbus.subscribe('NODE_MODIFIED', ({ detail })=>{
+ _mbus.subscribe('NODE_MODIFIED', ({ detail }) => {
const node = detail.data.node
const treenode = this.treeNodeComponents[node.get('UUID')]
@@ -43,6 +48,12 @@ export class N2Tree extends CustomHTMLElement {
})
this.populateFirstLevel()
+
+ /* XXX - set color */
+ let color = new Color(255, 96, 80)
+ let solver = new Solver(color)
+ let result = solver.solve()
+ this.elSettings.style.filter = result.filter
}// }}}
render() {// {{{
if (this.rendered)
diff --git a/static/service_worker.js b/static/service_worker.js
index 806eaad..80b79a1 100644
--- a/static/service_worker.js
+++ b/static/service_worker.js
@@ -1,7 +1,6 @@
const CACHE_NAME = 'notes2-{{ .VERSION }}'
const CACHED_ASSETS = [
'/',
- '/notes2',
'/offline',
'/css/{{ .VERSION }}/main.css',
@@ -25,13 +24,13 @@ const CACHED_ASSETS = [
'/js/{{ .VERSION }}/crypto.mjs',
'/js/{{ .VERSION }}/key.mjs',
'/js/{{ .VERSION }}/lib/custom_html_element.mjs',
- '/js/{{ .VERSION }}/lib/fullcalendar.min.js',
'/js/{{ .VERSION }}/lib/node_modules/marked/lib/marked.esm.js',
'/js/{{ .VERSION }}/lib/node_modules/marked-token-position/lib/index.esm.js',
'/js/{{ .VERSION }}/lib/sjcl.js',
'/js/{{ .VERSION }}/marked_position.mjs',
'/js/{{ .VERSION }}/mbus.mjs',
- '/js/{{ .VERSION }}/node.mjs',
+ '/js/{{ .VERSION }}/page_node.mjs',
+ '/js/{{ .VERSION }}/page_storage.mjs',
'/js/{{ .VERSION }}/node_store.mjs',
'/js/{{ .VERSION }}/notes2.mjs',
'/js/{{ .VERSION }}/sync.mjs',
diff --git a/views/layouts/main.gotmpl b/views/layouts/main.gotmpl
index 8ffa278..ead95a9 100644
--- a/views/layouts/main.gotmpl
+++ b/views/layouts/main.gotmpl
@@ -14,12 +14,8 @@
"checklist": "/js/{{ .VERSION }}/checklist.mjs",
"crypto": "/js/{{ .VERSION }}/crypto.mjs",
"node_store": "/js/{{ .VERSION }}/node_store.mjs",
- "node": "/js/{{ .VERSION }}/node.mjs",
+ "node": "/js/{{ .VERSION }}/page_node.mjs",
"tree": "/js/{{ .VERSION }}/tree.mjs"
- {{/*
- "session": "/js/{{ .VERSION }}/session.mjs",
- "ws": "/_js/{{ .VERSION }}/websocket.mjs"
- */}}
}
}
@@ -33,8 +29,6 @@
import { MessageBus } from '/js/{{ .VERSION }}/mbus.mjs'
window._mbus = new MessageBus()
-
-
{{ block "page" . }}{{ end }}
diff --git a/views/pages/login.gotmpl b/views/pages/login.gotmpl
index 3f4406e..3e2235f 100644
--- a/views/pages/login.gotmpl
+++ b/views/pages/login.gotmpl
@@ -29,7 +29,7 @@ class Login {
const password = document.getElementById('password').value
API.authenticate(username, password)
.then(ans=>{
- location.href = '/notes2'
+ location.href = '/'
})
.catch(e=>{
setTimeout(()=>this.errorDiv.innerText = e, 75)
diff --git a/views/pages/notes2.gotmpl b/views/pages/notes2.gotmpl
index 77b74a6..80c084a 100644
--- a/views/pages/notes2.gotmpl
+++ b/views/pages/notes2.gotmpl
@@ -1,18 +1,34 @@
{{ define "page" }}
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+