]> git.mar77i.info Git - chat/commitdiff
add jslint and more fixes
authormar77i <mar77i@protonmail.ch>
Sun, 20 Oct 2024 09:45:23 +0000 (11:45 +0200)
committermar77i <mar77i@protonmail.ch>
Sun, 20 Oct 2024 09:45:23 +0000 (11:45 +0200)
.gitignore
.pre-commit-config.yaml
chat/apps.py
chat/static/chat/chat.html
chat/static/chat/chat.js
chat/static/chat/underscore.js
chat/websocket.py
pyjslint.py [new file with mode: 0755]
setup_venv.sh

index 72858697cbf6da37b9af34af3517f290dfc1d623..78d5c6ee2990ea047d12d8de670990b5ad928eba 100644 (file)
@@ -1,6 +1,7 @@
 chat/settings_local.py
 .coverage
 .idea/
+jslint.js
 __pycache__/
 staticfiles
 venv/
index 8bc47c1b4e2f2afb653981218cda903a70917614..06d93574c78fb4140454aa1a6278e9cc91079bc4 100644 (file)
@@ -14,3 +14,20 @@ repos:
     - id: ruff
       args: [ --fix, --extend-select, I ]
     - id: ruff-format
+- repo: local
+  hooks:
+    - id: pyjslint
+      name: pyjslint
+      language: script
+      entry: pyjslint.py
+      files: "\\.js$"
+      args: [
+          -o, "maxerr:20",
+          -o, "for:true",
+          -o, "devel:true",
+          -o, "browser:true",
+          -o, "this:true",
+          -g, _,
+          -g, WebSocket,
+          -j, jslint.js
+      ]
index 580e57a490d14f964c7c83e0a5f86b36df3e1182..7bcedb34fed54b6ad9021b88b12fadec8dff9f07 100644 (file)
@@ -6,6 +6,7 @@ from .utils import get_app_configs
 
 class ChatConfig(AppConfig):
     default_auto_field = "django.db.models.BigAutoField"
+    name = __module__[: -len(".apps")]
 
     @staticmethod
     def autodiscover_models():
index c546f243a3fa255bc4502a3786627919c480b885..891af213df7a17dbebb734b49d8bfc9263676336 100644 (file)
@@ -13,6 +13,7 @@
     <body>
         <nav>
             <div class="user">
+                User
             </div>
             <div class="channels">
                 Channels
index 391ba39fe66115b1402122f70fd8ff5683ffe0e1..737ee08c2ee2b515d8445d8e75a9fadbe8bb08f3 100644 (file)
@@ -1,28 +1,55 @@
 (function () {
-    var current_channel = null, private_manager = null, channel_manager = null;
-
+    var current_channel = null;
+    var private_manager = null;
+    var channel_manager = null;
     var bottom_callback;
+    var ws_receive_msg;
+    var ws_schemas = {"http:": "ws:", "https:": "wss:"};
+
     function no_bottom_callback() {
         bottom_callback = no_bottom_callback;
-    };
+    }
     bottom_callback = no_bottom_callback;
 
     function xhr(method, url, callback, data) {
         return _.xhr(
             method,
             url,
-            callback
-            ? function () { callback(JSON.parse(this.responseText)); }
-            : null,
-            function (msg) { add_msg(null, "xhr_error", msg); },
-            function (msg) { add_msg(null, "xhr_abort", msg); },
-            data,
+            {
+                "abort": function (msg) {
+                    add_msg(null, "xhr_abort", msg);
+                },
+                "error": function (msg) {
+                    add_msg(null, "xhr_error", msg);
+                },
+                "load": (
+                    callback
+                    ? function () {
+                        callback(JSON.parse(this.responseText));
+                    }
+                    : null
+                ),
+                "readystatechange": function () {
+                    if (this.readyState === this.DONE) {
+                        if (this.readyState === this.DONE) {
+                            console.log("DONE url?", this.responseURL, url);
+                        } else {
+                            console.log("url?", this.responseURL, url);
+                        }
+                    }
+                }
+            },
+            data
         );
     }
 
     function get_data_id(element) {
         var attr = element.getAttribute("data-id");
-        return attr !== null ? Number(attr) : null;
+        return (
+            attr !== null
+            ? Number(attr)
+            : null
+        );
     }
 
     function get_current_user_id() {
@@ -31,7 +58,7 @@
 
     function set_msg(p, sender, msg) {
         _.clear_children(p);
-        if (typeof sender === 'number') {
+        if (typeof sender === "number") {
             sender = private_manager.items_per_id[sender].username;
         }
         p.appendChild(_.dcTN(sender));
     }
 
     function make_tag_with_data_id(tag_name, data_id) {
-        var i, tag = document.createElement(tag_name);
+        var tag = document.createElement(tag_name);
         if (data_id !== null) {
             tag.setAttribute("data-id", data_id);
         }
-        for (i = 2; i < arguments.length; i += 1) {
-            tag.appendChild(arguments[i]);
-        }
         return tag;
     }
 
     function make_callback_a(callback) {
-        var a = document.createElement("a"), i;
+        var a = document.createElement("a");
         a.setAttribute("href", "javascript:void(0)");
         a.addEventListener("click", callback);
-        for (i = 1; i < arguments.length; i += 1) {
-            a.appendChild(arguments[i]);
-        }
         return a;
     }
 
     function find_existing_msg(msg_id, callback) {
-        var i, messages = _.dgEBCN0("messages");
+        var messages = _.dgEBCN0("messages");
         return _.foreach_arr(
             messages.children,
             function (item) {
         current_channel.setup_channel_header();
         xhr(
             "get",
-            this.msg_url + "?" + this.msg_dest_field + "=" + field_value.toString(),
+            (
+                this.msg_url
+                + "?" + this.msg_dest_field
+                + "=" + field_value.toString()
+            ),
             function (msgs) {
                 var messages = _.dgEBCN0("messages");
                 // add a "load previous" button here
                 _.foreach_arr(msgs.result, add_msg_callback);
-                messages.scrollTop = messages.scrollHeight - messages.clientHeight;
+                messages.scrollTop = (
+                    messages.scrollHeight - messages.clientHeight
+                );
             }
         );
     }
 
     function items_reload_callback(item) {
         var t = this;
+        var li;
+        var a;
         this.items_per_id[item.id] = item;
-        _.dgEBCN0(this.items_classname).children[0].appendChild(
-            make_tag_with_data_id(
-                "li",
-                item.id,
-                make_callback_a(
-                    function () { t.set_channel(item.id); },
-                    _.dcTN(this.item_to_str(item)),
-                ),
-            ),
+        li = make_tag_with_data_id("li", item.id);
+        a = make_callback_a(
+            function () {
+                t.set_channel(item.id);
+            }
         );
+        a.appendChild(this.item_to_tn(item));
+        li.appendChild(a);
+        _.dgEBCN0(this.items_classname).children[0].appendChild(li);
     }
 
     function PrivateMessageManager() {
             var messages_header = _.dgEBCN0("messages_header");
             clear_messages_header();
             messages_header.children[0].appendChild(
-                _.dcTN(this.item_to_str(this.items_per_id[this.msg_data.recipient_id]))
+                this.item_to_tn(this.items_per_id[this.msg_data.recipient_id])
             );
             messages_header.style.display = "block";
         };
         this.items_classname = "users";
         this.items_per_id = {};
         this.items_reload = function () {
-            console.log("a");
             _.clear_children(_.dgEBCN0(this.items_classname).children[0]);
             _.clear_attributes(this.items_per_id);
-            console.log("b");
             xhr(
                 "get",
                 this.items_url,
                 function (data) {
-                    console.log("items_reload callback", data);
                     _.foreach_arr(
                         data.result,
                         function (item) {
+                            var p;
                             items_reload_callback.call(t, item);
                             if (!item.is_authenticated) {
                                 return;
                             }
                             _.clear_children(_.dgEBCN0("user"));
-                            _.dgEBCN0("user").appendChild(
-                                make_tag_with_data_id(
-                                    "p", item.id, _.dcTN(item.username)
-                                ),
-                            );
+                            p = make_tag_with_data_id("p", item.id);
+                            p.appendChild(_.dcTN(item.username));
+                            _.dgEBCN0("user").appendChild(p);
                         }
                     );
                     update_channel_header.call(t);
-                },
+                }
             );
-            console.log("c");
-        }
-        this.item_to_str = function (item) {
-            return item.username +  "\xa0(" + item.email + ")";
+        };
+        this.item_to_tn = function (item) {
+            return _.dcTN(item.username + "\u00a0(" + item.email + ")");
         };
         this.match_channel = function (data) {
-            var user_id, recipient_id;
+            var other_id;
+            var user_id;
             if (data.table !== this.msg_table) {
                 return false;
             }
             user_id = get_current_user_id();
             other_id = current_channel.msg_data.recipient_id;
             return (
-                data.obj.sender_id === user_id && data.obj.recipient_id === other_id
+                data.obj.sender_id === user_id
+                && data.obj.recipient_id === other_id
             ) || (
-                data.obj.sender_id === other_id && data.obj.recipient_id === user_id
+                data.obj.sender_id === other_id
+                && data.obj.recipient_id === user_id
             );
         };
         this.items_reload();
             var btn = document.createElement("button");
             clear_messages_header();
             messages_header.children[0].appendChild(
-                _.dcTN(this.item_to_str(this.items_per_id[this.msg_data.channel_id]))
+                this.item_to_tn(this.items_per_id[this.msg_data.channel_id])
             );
             _.foreach_arr(
                 this.items_per_id[this.msg_data.channel_id].users,
                 function (user_id) {
                     var item = private_manager.items_per_id[user_id];
-                    messages_header.children[1].appendChild(
-                        make_tag_with_data_id(
-                            "li",
-                            item.id,
-                            make_callback_a(
-                                function () { private_manager.set_channel(item.id); },
-                                _.dcTN(private_manager.item_to_str(item)),
-                            ),
-                        ),
+                    var li = make_tag_with_data_id("li", item.id);
+                    var a = make_callback_a(
+                        function () {
+                            private_manager.set_channel(item.id);
+                        }
                     );
-                },
+                    a.appendChild(private_manager.item_to_tn(item));
+                    li.appendChild(a);
+                    messages_header.children[1].appendChild(li);
+                }
             );
             btn.appendChild(_.dcTN("Users"));
             messages_header.addEventListener("click", function () {
                 messages_header.children[1].style.display = "none";
             });
             btn.addEventListener("click", function (event) {
+                var offset;
                 event = event || window.event;
                 if (messages_header.children[1].style.display === "none") {
                     messages_header.children[1].style.display = "block";
-                    messages_header.children[1].style.top = (btn.offsetTop + btn.offsetHeight).toString() + "px";
+                    offset = btn.offsetTop + btn.offsetHeight;
+                    messages_header.children[1].style.top = (
+                        offset.toString() + "px"
+                    );
                 } else {
                     messages_header.children[1].style.display = "none";
                 }
                 function (data) {
                     _.foreach_arr(data.result, items_reload_callback.bind(t));
                     update_channel_header.call(t);
-                },
+                }
             );
-        }
-        this.item_to_str = function (item) { return item.name; };
+        };
+        this.item_to_tn = function (item) {
+            return _.dcTN(item.name);
+        };
         this.match_channel = function (data) {
-            var obj;
             if (data.table !== this.msg_table) {
                 return false;
             }
             bottom_callback = function () {
                 element.scrollTop = element.scrollHeight - element.clientHeight;
                 no_bottom_callback();
-            }
+            };
         } else {
             no_bottom_callback();
         }
     }
 
-    var ws_receive_msg = {
+    ws_receive_msg = {
+        DELETE: function (data) {
+            find_existing_msg(
+                data.obj.id,
+                function (tag) {
+                    tag.parentElement.removeChild(tag);
+                }
+            );
+        },
         INSERT: function (data) {
             stick_to_bottom(_.dgEBCN0("messages"));
             xhr(
                 }
             );
         },
+        TRUNCATE: function () {
+            _.clear_children(_.dgEBCN0("messages"));
+        },
         UPDATE: function (data) {
             var messages = _.dgEBCN0("messages");
             stick_to_bottom(messages);
                     stick_to_bottom(messages);
                     xhr(
                         "get",
-                        current_channel.msg_url + get_data_id(tag).toString() + "/",
+                        (
+                            current_channel.msg_url
+                            + get_data_id(tag).toString()
+                            + "/"
+                        ),
                         function (msg) {
-                            set_msg(tag, msg[current_channel.msg_user_field], msg.text);
+                            set_msg(
+                                tag,
+                                msg[current_channel.msg_user_field],
+                                msg.text
+                            );
                             bottom_callback();
                         }
                     );
-                },
-            );
-        },
-        DELETE: function (data) {
-            find_existing_msg(
-                data.obj.id, function (tag) { tag.parentElement.removeChild(tag); }
+                }
             );
-        },
-        TRUNCATE: function (data) {
-            _.clear_children(_.dgEBCN0("messages"));
-        },
+        }
     };
 
     function ws_receive(msg) {
         var data = JSON.parse(msg.data);
-        // here we could add indication for the pm or channel that is being modified
-        if (data.table === "chat_channel" || data.table === "chat_channeluser") {
+        if (data.hasOwnProperty("Location")) {
+            location.href = data.Location;
+        }
+        // here we could add indication for the pm or channel being modified
+        if (
+            data.table === "chat_channel" || data.table === "chat_channeluser"
+        ) {
             channel_manager.items_reload();
         } else if (data.table === "chat_user") {
             private_manager.items_reload();
     }
 
     function send_msg() {
-        var ta = _.dgEBCN0("messages_footer").children[0], data, name;
-        data = {"text": ta.value};
+        var data = {"text": ta.value};
+        var ta = _.dgEBCN0("messages_footer").children[0];
         _.foreach_obj(
-            current_channel.msg_data, function (name, value) { data[name] = value; }
+            current_channel.msg_data,
+            function (name, value) {
+                data[name] = value;
+            }
         );
         if (current_channel) {
             xhr("post", current_channel.msg_url, null, JSON.stringify(data));
         event = event || window.event;
         keycode = event.code || event.key;
         if (keycode === "Enter" || keycode === "NumpadEnter") {
-            // todo: make it possible to add newlines using ctrl+enter
+            // nth: make it possible to add newlines using ctrl+enter
             send_msg();
             event.preventDefault();
             event.stopPropagation();
     window.addEventListener(
         "load",
         function (event) {
-            var ws, schema, messages_footer;
+            var messages_footer;
+            var ws;
             messages_footer = _.dgEBCN0("messages_footer");
             set_channel.call(window);
             event = event || window.event;
             }
             private_manager = new PrivateMessageManager();
             channel_manager = new ChannelManager();
-            schema = {"http:": "ws:", "https:": "wss:"}[window.location.protocol];
-            ws = new WebSocket(schema + "//" + window.location.host + "/");
+            ws = new WebSocket(
+                ws_schemas[window.location.protocol]
+                + "//"
+                + window.location.host
+                + "/"
+            );
             ws.addEventListener("message", ws_receive);
-            messages_footer.children[0].addEventListener("keydown", input_onkeydown);
+            messages_footer.children[0].addEventListener(
+                "keydown",
+                input_onkeydown
+            );
             messages_footer.children[0].value = "";
             messages_footer.children[1].addEventListener("click", send_msg);
         }
index f97bc828276750e02b88daabfbc1a779d0335804..26d410f311fc3c3f17fafea76fdc652b091b4b68 100644 (file)
@@ -1,8 +1,8 @@
 (function () {
-    function foreach_arr(arr, callback) {
+    function foreach_arr(arr, callback, start) {
         var i;
-        for (i = arguments.length === 3 ? arguments[2] : 0; i < arr.length; i += 1) {
-            if (callback(arr[i]) === false) {
+        for (i = start || 0; i < arr.length; i += 1) {
+            if (callback(arr[i]) === true) {
                 return true;
             }
         }
     }
 
     function foreach_obj(obj, callback) {
-        var name;
-        for (name in obj) {
-            if (obj.hasOwnProperty(name) && callback(name, obj[name]) === true) {
-                return true;
+        foreach_arr(
+            Object.keys(obj),
+            function (key) {
+                return callback(key, obj[key]);
             }
-        }
+        );
         return false;
     }
 
     }
 
     function clear_attributes(obj) {
-        foreach_obj(obj, function (name) { delete obj[name]; })
+        foreach_obj(
+            obj,
+            function (name) {
+                delete obj[name];
+            }
+        );
     }
 
-    function xhr(method, url, callback, error_callback, abort_callback, data) {
-        var xhr = new XMLHttpRequest();
-        if (!error_callback) {
-            error_callback = function (msg) { console.log("xhr_error", msg) };
+    function xhr(method, url, callbacks, data) {
+        var request = new XMLHttpRequest();
+        if (!callbacks.hasOwnProperty("error")) {
+            request.addEventListener(
+                "error",
+                function (msg) {
+                    console.log("xhr_error", msg);
+                }
+            );
         }
-        if (!abort_callback) {
-            abort_callback = function (msg) { console.log("xhr_abort", msg) };
+        if (!callbacks.hasOwnProperty("abort")) {
+            request.addEventListener(
+                "abort",
+                function (msg) {
+                    console.log("xhr_abort", msg);
+                }
+            );
         }
-        xhr.addEventListener("error", error_callback);
-        xhr.addEventListener("abort", abort_callback);
-        if (callback) {
-            xhr.addEventListener("load", callback);
-        }
-        xhr.open(method, url);
+        foreach_arr(
+            callbacks,
+            function (event_name, callback) {
+                request.addEventListener(event_name, callback);
+            }
+        );
+        request.open(method, url);
         if (method.toLowerCase() === "put" || method.toLowerCase() === "post") {
-            xhr.send(data);
+            request.send(data);
         } else {
-            xhr.send();
+            request.send();
         }
-        return xhr;
+        return request;
     }
 
-    window._ = {
-        "dcTN": function (t) { return document.createTextNode(t); },
-        "dgEBCN0": function (cn) { return document.getElementsByClassName(cn)[0]; },
-        "foreach_arr": foreach_arr,
-        "foreach_obj": foreach_obj,
-        "clear_children": clear_children,
+    window._ = { //jslint-ignore-line
         "clear_after": clear_after,
         "clear_attributes": clear_attributes,
-        "xhr": xhr,
+        "clear_children": clear_children,
+        "dcTN": function (t) {
+            return document.createTextNode(t);
+        },
+        "dgEBCN0": function (cn) {
+            return document.getElementsByClassName(cn)[0];
+        },
+        "foreach_arr": foreach_arr,
+        "foreach_obj": foreach_obj,
+        "xhr": xhr
     };
 }());
index 9fb95a8587bf34a1773c94c998d3239962d3bce7..96af0d5fef318d7219fa5acafd4332de6570338d 100644 (file)
@@ -6,7 +6,7 @@ from asyncio import (
     get_running_loop,
     run_coroutine_threadsafe,
 )
-from contextlib import contextmanager
+from contextlib import contextmanager, nullcontext
 from functools import partial
 from io import BytesIO
 from traceback import print_exception
@@ -133,15 +133,27 @@ async def handle_websocket(scope, receive, send):
         event = await receive()
         if event["type"] == "websocket.connect":
             await send({"type": "websocket.accept"})
-        await send({"type": "websocket.close"})
-        return
-
-    await sync_to_async(connection.connect)()
-    user_channels = await sync_to_async(get_user_channels)(request.user)
-    with listen_notify_handler(
-        connection.connection,
-        partial(process_triggers, send, request.user, user_channels),
-    ):
+        (
+            await send(
+                {
+                    "type": "websocket.send",
+                    "text": json.dumps({"Location": settings.LOGIN_URL}),
+                },
+            ),
+        )
+        context = nullcontext()
+    else:
+        await sync_to_async(connection.connect)()
+        context = listen_notify_handler(
+            connection.connection,
+            partial(
+                process_triggers,
+                send,
+                request.user,
+                await sync_to_async(get_user_channels)(request.user),
+            ),
+        )
+    with context:
         try:
             await process_ws(receive, send)
         except CancelledError:
diff --git a/pyjslint.py b/pyjslint.py
new file mode 100755 (executable)
index 0000000..b035296
--- /dev/null
@@ -0,0 +1,125 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2011  Alejandro Blanco <ablanco@yaco.es>
+# Copyright (C) 2024  mar77i <mar77i@protonmail.ch>
+
+import json
+from argparse import ArgumentParser
+from contextlib import contextmanager
+from pathlib import Path
+from subprocess import call
+from tempfile import NamedTemporaryFile
+from urllib.request import urlopen
+
+node_script = r"""
+var jslint = require({jslint}).default.jslint;
+var read = require("fs").readFileSync;
+var result;
+
+function print_warning(warning) {
+    if (warning === null) {
+        return;
+    }
+    if (warning.evidence !== undefined) {
+        console.log(warning.evidence);
+    }
+    console.log(warning.formatted_message);
+}
+
+console.log("Analyzing file " + process.argv[2]);
+result = jslint(read(process.argv[2], "utf8"), {jsoptions}, {jsglobals});
+result.warnings.forEach(print_warning);
+if (result.warnings.length > 0) {
+    console.error(result.warnings.length + " Error(s) found.");
+    process.exit(1);
+} else {
+    console.log("No errors found.");
+    process.exit(0);
+}
+"""
+
+
+def try_json_loads(value):
+    try:
+        return json.loads(value)
+    except json.JSONDecodeError:
+        return value
+
+
+@contextmanager
+def get_lint(jslint, jsoptions, jsglobals):
+    jsoptions_dict = {
+        key: try_json_loads(value)
+        for key, value in (item.split(":", 1) for item in jsoptions)
+    }
+    tmp_dir = Path("/dev/shm")
+    if not tmp_dir.exists():
+        tmp_dir = None
+    with NamedTemporaryFile("w", dir=tmp_dir, delete_on_close=False) as lint:
+        lint.write(
+            node_script.replace("{jslint}", json.dumps(str(jslint.absolute())))
+            .replace("{jsoptions}", json.dumps(jsoptions_dict))
+            .replace("{jsglobals}", json.dumps(jsglobals))
+        )
+        lint.close()
+        yield lint.name
+
+
+def main():
+    parser = ArgumentParser("Usage: %(prog)s")
+    parser.add_argument(
+        "-u",
+        "--upgrade",
+        dest="upgrade",
+        help="Upgrade JSLint",
+        action="store_true",
+        default=False,
+    )
+    parser.add_argument(
+        "-j",
+        "--jslint",
+        dest="jslint",
+        help="JSLint location",
+        type=Path,
+        default=Path(__file__).parent / "jslint.js",
+    )
+    parser.add_argument(
+        "-o",
+        "--option",
+        dest="jsoptions",
+        help="JSLint options",
+        default=["maxerr: 100"],
+        action="append",
+    )
+    parser.add_argument(
+        "-g",
+        "--global",
+        dest="jsglobals",
+        help="JSLint globals",
+        default=[],
+        action="append",
+    )
+    parser.add_argument(
+        "-n", "--node", dest="node", help="Node location", default="node"
+    )
+    parser.add_argument("files", nargs="+")
+    args = parser.parse_args()
+    if not args.jslint.exists() or args.upgrade:
+        with urlopen(
+            (
+                "https://raw.githubusercontent.com/jslint-org/"
+                "jslint/refs/heads/beta/jslint.mjs"
+            )
+        ) as response, open(args.jslint, "wb") as fh:
+            fh.write(response.read())
+    returncode = 0
+    with get_lint(args.jslint, args.jsoptions, args.jsglobals) as lint_name:
+        for file in args.files:
+            ret = call([args.node, lint_name, file])
+            if ret:
+                returncode = ret
+    exit(returncode)
+
+
+if __name__ == "__main__":
+    main()
index 8327f26e873b6950db881173a7831e96a1b585c6..07e5cf92c5d0ae076c4ad7b125e8c273248c6d54 100644 (file)
@@ -6,4 +6,4 @@ export PYTHON="${PYTHON:-python}"
 . venv/bin/activate
 "${PYTHON}" -m pip install -qU pip
 "${PYTHON}" -m pip install -qU \
-    django 'uvicorn[standard]' 'psycopg[c]' django-pgtrigger pre-commit ruff
+    django 'uvicorn[standard]' 'psycopg[c]' django-pgtrigger pre-commit ruff coverage