diff --git a/.env.example b/.env.example index ec036c1..6c73e4a 100644 --- a/.env.example +++ b/.env.example @@ -64,10 +64,12 @@ MYSQL_USER=rustpad MYSQL_PASSWORD=rustpad MYSQL_ROOT_PASSWORD=rustpad_root -# Optional account password reset via SMTP +# Optional settings REGISTRATION_ENABLED=false ACCOUNT_CONFIRMATION_REQUIRED=false +SHARE_CONFIRMATION_REQUIRED=true +# smtp mailing PUBLIC_URL=https://pad.example.com # SMTP_HOST=smtp.example.com SMTP_PORT=587 diff --git a/Cargo.lock b/Cargo.lock index a704d39..848d56c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,15 @@ dependencies = [ "libc", ] +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + [[package]] name = "argon2" version = "0.5.3" @@ -34,7 +43,7 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", - "cpufeatures", + "cpufeatures 0.2.17", "password-hash", ] @@ -70,6 +79,480 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "aws-config" +version = "1.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33f815b73a3899c03b380d543532e5865f230dce9678d108dc10732a8682275" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 1.4.2", + "sha1 0.10.7", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-lc-rs" +version = "1.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "aws-runtime" +version = "1.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c9b9de216a988dd54b754a82a7660cfe14cee4f6782ae4524470972fa0ccb39" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "bytes-utils", + "fastrand", + "http 0.2.12", + "http 1.4.2", + "http-body 0.4.6", + "http-body 1.1.0", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-s3" +version = "1.137.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd7213994e2ff9382ff100403b78c30d1b74cdfcd8fa9d0d1dc3a94a5c4874" +dependencies = [ + "arc-swap", + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac 0.13.0", + "http 0.2.12", + "http 1.4.2", + "http-body 1.1.0", + "lru", + "percent-encoding", + "regex-lite", + "sha2 0.11.0", + "tracing", + "url", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.102.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c82b3ac19f1431854f7ace3a7531674633e286bfdde21976893bfee36fd493b" +dependencies = [ + "arc-swap", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.2", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.104.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321000d2b4c5519ee573f73167f612efd7329322d9b26969ad1979f0427f1913" +dependencies = [ + "arc-swap", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.2", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.107.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0d328ba962af23ecfa3c9f23b98d3d35e325fa218d7f13d17a6bf522f8a560" +dependencies = [ + "arc-swap", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "http 1.4.2", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae38512beae0ffee7010fc24e7a8a123c53efdfef42a61e80fda4882418dc71" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "crypto-bigint", + "form_urlencoded", + "hex", + "hmac 0.13.0", + "http 0.2.12", + "http 1.4.2", + "p256", + "percent-encoding", + "sha2 0.11.0", + "subtle", + "time", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffcaf626bdda484571968400c326a244598634dc75fd451325a54ad1a59acfc" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-checksums" +version = "0.64.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e8e65f4f81fcccdeb6c3eca2af17ac21d421a1786a26a394aecf421d616d3a" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "crc-fast", + "hex", + "http 1.4.2", + "http-body 1.1.0", + "http-body-util", + "md-5 0.11.0", + "pin-project-lite", + "sha1 0.11.0", + "sha2 0.11.0", + "tracing", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.60.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78d8391e65fcea47c586a22e1a41f173b38615b112b2c6b7a44e80cec3e6b706" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + +[[package]] +name = "aws-smithy-http" +version = "0.63.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 1.4.2", + "http-body 1.1.0", + "http-body-util", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3ef8931ad1c98aa6a55b4256f847f3116090819844e0dd41ea682cac5dd2d3" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.3.27", + "h2 0.4.15", + "http 0.2.12", + "http 1.4.2", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.10.1", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.9", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.42", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.62.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "701a947f4797e52a911e114a898667c746c39feea467bbd1abd7b3721f702ffa" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06c2315d173edbf1920da8ba3a7189695827002e4c0fc961973ab1c54abca9c" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a56d79744fb3edb5d722ef79d86081e121d3b9422cb209eb03aea6aa4f21ebd" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e6f5caf6fea86f8c2206541ab5857cfcda9013426cdbe8fa0098b9e2d32182" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.2", + "http-body 0.4.6", + "http-body 1.1.0", + "http-body-util", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9db177daa6ba8afb9ee1aefcf548c907abcf52065e394ee11a92780057fe0e8c" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api-macros", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.4.2", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-runtime-api-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d7396fd9500589e62e460e987ecb671bad374934e55ec3b5f498cc7a8a8a7b7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "aws-smithy-schema" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7442cb268338f0eb8278140a107c046756aa01093d8ef5e99628d34ae09c94f5" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "http 1.4.2", +] + +[[package]] +name = "aws-smithy-types" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b42fcf341259d85ca10fac9a2f6448a8ec691c6955a18e45bc3b71a85fab85" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.4.2", + "http-body 0.4.6", + "http-body 1.1.0", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16bf10b03a3c01e6b3b7d47cd964e873ffe9e7d4e80fad16bd4c077cb068531" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "rustc_version", + "tracing", +] + [[package]] name = "axum" version = "0.8.9" @@ -81,10 +564,10 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http", - "http-body", + "http 1.4.2", + "http-body 1.1.0", "http-body-util", - "hyper", + "hyper 1.10.1", "hyper-util", "itoa", "matchit", @@ -97,7 +580,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sha1", + "sha1 0.10.7", "sync_wrapper", "tokio", "tokio-tungstenite", @@ -115,8 +598,8 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.2", + "http-body 1.1.0", "http-body-util", "mime", "pin-project-lite", @@ -126,12 +609,28 @@ dependencies = [ "tracing", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "base64ct" version = "1.8.3" @@ -153,7 +652,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -165,6 +664,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -183,6 +691,16 @@ version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "cc" version = "1.2.67" @@ -190,6 +708,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -213,6 +733,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -228,6 +763,22 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -243,6 +794,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -258,6 +818,25 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" +[[package]] +name = "crc-fast" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75b2483e97a5a7da73ac68a05b629f9c53cff58d8ed1c77866079e18b00dba5" +dependencies = [ + "digest 0.10.7", + "spin 0.10.1", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-queue" version = "0.3.13" @@ -273,6 +852,18 @@ version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -283,6 +874,24 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "data-encoding" version = "2.11.0" @@ -295,11 +904,17 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", "pem-rfc7468", "zeroize", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + [[package]] name = "deunicode" version = "1.6.2" @@ -312,12 +927,24 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", +] + [[package]] name = "displaydoc" version = "0.2.6" @@ -335,6 +962,26 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "either" version = "1.16.0" @@ -344,6 +991,26 @@ dependencies = [ "serde", ] +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "email-encoding" version = "0.4.1" @@ -413,6 +1080,16 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -427,15 +1104,27 @@ checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", - "spin", + "spin 0.9.9", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -445,6 +1134,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures-channel" version = "0.3.32" @@ -536,6 +1231,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -557,10 +1253,70 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.2", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -569,7 +1325,18 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", ] [[package]] @@ -605,7 +1372,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac", + "hmac 0.12.1", ] [[package]] @@ -614,7 +1381,16 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", ] [[package]] @@ -626,6 +1402,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.4.2" @@ -636,6 +1423,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.1.0" @@ -643,7 +1441,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", - "http", + "http 1.4.2", ] [[package]] @@ -654,8 +1452,8 @@ checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.2", + "http-body 1.1.0", "pin-project-lite", ] @@ -677,6 +1475,39 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.10.1" @@ -687,14 +1518,47 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "http", - "http-body", + "h2 0.4.15", + "http 1.4.2", + "http-body 1.1.0", "httparse", "httpdate", "itoa", "pin-project-lite", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http 1.4.2", + "hyper 1.10.1", + "hyper-util", + "rustls 0.23.42", + "rustls-native-certs", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", ] [[package]] @@ -703,13 +1567,21 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64", "bytes", - "http", - "http-body", - "hyper", + "futures-channel", + "futures-util", + "http 1.4.2", + "http-body 1.1.0", + "hyper 1.10.1", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", + "socket2 0.6.5", "tokio", "tower-service", + "tracing", ] [[package]] @@ -848,12 +1720,28 @@ dependencies = [ "hashbrown 0.17.1", ] +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.103" @@ -871,7 +1759,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin", + "spin 0.9.9", ] [[package]] @@ -893,10 +1781,10 @@ dependencies = [ "nom", "percent-encoding", "quoted_printable", - "rustls", - "socket2", + "rustls 0.23.42", + "socket2 0.6.5", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "url", "webpki-roots 1.0.8", ] @@ -957,6 +1845,15 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +dependencies = [ + "hashbrown 0.16.1", +] + [[package]] name = "matchers" version = "0.2.0" @@ -979,7 +1876,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest 0.11.3", ] [[package]] @@ -1024,11 +1931,11 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 1.4.2", "httparse", "memchr", "mime", - "spin", + "spin 0.9.9", "version_check", ] @@ -1066,6 +1973,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-integer" version = "0.1.46" @@ -1101,6 +2014,30 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.9", +] + [[package]] name = "parking" version = "2.2.1" @@ -1162,6 +2099,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkcs1" version = "0.7.5" @@ -1204,6 +2147,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1213,6 +2162,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -1243,6 +2201,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.7" @@ -1331,12 +2295,28 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -1357,8 +2337,8 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid", - "digest", + "const-oid 0.9.6", + "digest 0.10.7", "num-bigint-dig", "num-integer", "num-traits", @@ -1371,21 +2351,55 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.13", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" version = "1.15.0" @@ -1395,12 +2409,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -1408,10 +2433,14 @@ dependencies = [ [[package]] name = "rustpad" -version = "0.0.9" +version = "0.0.11" dependencies = [ "argon2", + "aws-config", + "aws-credential-types", + "aws-sdk-s3", "axum", + "bytes", "chrono", "dotenvy", "futures-util", @@ -1421,7 +2450,7 @@ dependencies = [ "rand_core 0.6.4", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "slug", "sqlx", "tokio", @@ -1443,12 +2472,74 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.228" @@ -1522,8 +2613,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -1533,8 +2635,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -1568,7 +2681,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -1597,6 +2710,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.5" @@ -1616,6 +2739,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" + [[package]] name = "spki" version = "0.7.3" @@ -1663,10 +2792,10 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rustls", + "rustls 0.23.42", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", "thiserror", "tokio", @@ -1704,7 +2833,7 @@ dependencies = [ "quote", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "sqlx-core", "sqlx-mysql", "sqlx-postgres", @@ -1727,7 +2856,7 @@ dependencies = [ "bytes", "chrono", "crc", - "digest", + "digest 0.10.7", "dotenvy", "either", "futures-channel", @@ -1737,18 +2866,18 @@ dependencies = [ "generic-array", "hex", "hkdf", - "hmac", + "hmac 0.12.1", "itoa", "log", - "md-5", + "md-5 0.10.6", "memchr", "once_cell", "percent-encoding", "rand 0.8.7", "rsa", "serde", - "sha1", - "sha2", + "sha1 0.10.7", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", @@ -1776,17 +2905,17 @@ dependencies = [ "futures-util", "hex", "hkdf", - "hmac", + "hmac 0.12.1", "home", "itoa", "log", - "md-5", + "md-5 0.10.6", "memchr", "once_cell", "rand 0.8.7", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", @@ -1911,6 +3040,36 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.3" @@ -1947,7 +3106,7 @@ dependencies = [ "mio", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.5", "tokio-macros", "windows-sys 0.61.2", ] @@ -1963,13 +3122,23 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls", + "rustls 0.23.42", "tokio", ] @@ -2035,8 +3204,8 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", - "http-body", + "http 1.4.2", + "http-body 1.1.0", "http-body-util", "http-range-header", "httpdate", @@ -2125,6 +3294,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "tungstenite" version = "0.29.0" @@ -2133,11 +3308,11 @@ checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" dependencies = [ "bytes", "data-encoding", - "http", + "http 1.4.2", "httparse", "log", "rand 0.9.5", - "sha1", + "sha1 0.10.7", "thiserror", ] @@ -2198,12 +3373,28 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -2222,6 +3413,21 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2544,6 +3750,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "yoke" version = "0.8.3" diff --git a/Cargo.toml b/Cargo.toml index f4a75f3..e4dac3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustpad" -version = "0.0.10" +version = "0.0.11" edition = "2024" rust-version = "1.94" description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL" diff --git a/migrations/mysql/0011_share_invitations.sql b/migrations/mysql/0011_share_invitations.sql new file mode 100644 index 0000000..9cc19a1 --- /dev/null +++ b/migrations/mysql/0011_share_invitations.sql @@ -0,0 +1,15 @@ +CREATE TABLE resource_share_invitations ( + token_hash VARCHAR(64) PRIMARY KEY, + resource_kind VARCHAR(16) NOT NULL, + resource_slug VARCHAR(255) NOT NULL, + user_id BIGINT NOT NULL, + permission VARCHAR(2) NOT NULL, + created_by BIGINT NOT NULL, + expires_at TEXT NOT NULL, + accepted_at TEXT NULL, + created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), + UNIQUE KEY uq_resource_share_invitation (resource_kind, resource_slug, user_id), + CONSTRAINT fk_share_invitation_user FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + CONSTRAINT fk_share_invitation_creator FOREIGN KEY(created_by) REFERENCES users(id) ON DELETE CASCADE +); +CREATE INDEX idx_resource_share_invitations_user ON resource_share_invitations(user_id); diff --git a/migrations/postgres/0011_share_invitations.sql b/migrations/postgres/0011_share_invitations.sql new file mode 100644 index 0000000..479d8e3 --- /dev/null +++ b/migrations/postgres/0011_share_invitations.sql @@ -0,0 +1,13 @@ +CREATE TABLE resource_share_invitations ( + token_hash TEXT PRIMARY KEY, + resource_kind TEXT NOT NULL, + resource_slug TEXT NOT NULL, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + permission TEXT NOT NULL CHECK(permission IN ('ro','rw')), + created_by BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + expires_at TEXT NOT NULL, + accepted_at TEXT, + created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), + UNIQUE(resource_kind, resource_slug, user_id) +); +CREATE INDEX idx_resource_share_invitations_user ON resource_share_invitations(user_id); diff --git a/migrations/sqlite/0011_share_invitations.sql b/migrations/sqlite/0011_share_invitations.sql new file mode 100644 index 0000000..0c7d747 --- /dev/null +++ b/migrations/sqlite/0011_share_invitations.sql @@ -0,0 +1,13 @@ +CREATE TABLE resource_share_invitations ( + token_hash TEXT PRIMARY KEY, + resource_kind TEXT NOT NULL, + resource_slug TEXT NOT NULL, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + permission TEXT NOT NULL CHECK(permission IN ('ro','rw')), + created_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + expires_at TEXT NOT NULL, + accepted_at TEXT, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(resource_kind, resource_slug, user_id) +); +CREATE INDEX idx_resource_share_invitations_user ON resource_share_invitations(user_id); diff --git a/src/app.rs b/src/app.rs index 52ff6cb..b8dba8e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -52,6 +52,7 @@ pub fn router(state: SharedState, static_dir: &str, upload_max_size_bytes: usize .route("/api/auth/resources/privacy", post(auth::set_resource_privacy)) .route("/api/auth/resources/sharing", get(auth::resource_sharing).post(auth::share_resource_users).delete(auth::remove_resource_user)) .route("/api/auth/resources/share-links", post(auth::create_share_link).put(auth::update_share_link).delete(auth::revoke_share_link)) + .route("/share-invitations/{token}/accept", get(auth::accept_share_invitation)) .route("/api/auth/password-reset", post(auth::request_reset)) .route("/api/auth/password-reset/confirm", post(auth::confirm_reset)) .route("/api/public/{token}", get(api::public_page)) diff --git a/src/auth.rs b/src/auth.rs index 41d857b..d958b37 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,5 +1,5 @@ use argon2::{password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2}; -use axum::{extract::State, http::{HeaderMap, StatusCode}, Json}; +use axum::{extract::{Path as AxumPath, State}, http::{HeaderMap, StatusCode}, response::Redirect, Json}; use chrono::{Duration, Utc}; use lettre::{ message::{header::ContentType, Mailbox, MultiPart, SinglePart}, @@ -226,6 +226,9 @@ pub async fn share_resource_users(State(state): State, headers: Hea let owner = require_user(&state, &headers).await?; ensure_owner(&state, owner.id, &req.kind, &req.slug).await?; let permission = validate_permission(&req.permission)?; + if state.share_confirmation_required && state.smtp.is_none() { + return Err(AuthError::service_unavailable("Share confirmation requires SMTP configuration.")); + } let emails: Vec = req.emails.split(',').map(|v| normalize(v)).filter(|v| !v.is_empty()).collect(); if emails.is_empty() || emails.len() > 100 { return Err(AuthError::bad_request("Enter between 1 and 100 registered e-mail addresses.")); } let mut missing = Vec::new(); @@ -233,13 +236,52 @@ pub async fn share_resource_users(State(state): State, headers: Hea let user = find_user_by_email(&state, &email).await?; let Some(user) = user else { missing.push(email); continue; }; if user.id == owner.id { continue; } + sqlx::query(queries::get(state.db.kind(), "DELETE FROM resource_permissions WHERE resource_kind = ? AND resource_slug = ? AND user_id = ?")) .bind(&req.kind).bind(req.slug.trim()).bind(user.id).execute(state.db.pool()).await.map_err(AuthError::database)?; - sqlx::query(queries::get(state.db.kind(), "INSERT INTO resource_permissions (resource_kind, resource_slug, user_id, permission) VALUES (?, ?, ?, ?)")) - .bind(&req.kind).bind(req.slug.trim()).bind(user.id).bind(permission).execute(state.db.pool()).await.map_err(AuthError::database)?; + sqlx::query(queries::get(state.db.kind(), "DELETE FROM resource_share_invitations WHERE resource_kind = ? AND resource_slug = ? AND user_id = ?")) + .bind(&req.kind).bind(req.slug.trim()).bind(user.id).execute(state.db.pool()).await.map_err(AuthError::database)?; + + if state.share_confirmation_required { + let token = random_token(); + let token_hash = hash_token(&token); + let expires_at = (Utc::now() + Duration::days(7)).to_rfc3339(); + sqlx::query(queries::get(state.db.kind(), "INSERT INTO resource_share_invitations (token_hash, resource_kind, resource_slug, user_id, permission, created_by, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?)")) + .bind(&token_hash).bind(&req.kind).bind(req.slug.trim()).bind(user.id).bind(permission).bind(owner.id).bind(&expires_at) + .execute(state.db.pool()).await.map_err(AuthError::database)?; + if let Err(error) = send_share_invitation(state.smtp.as_ref().unwrap(), &owner, &user, &req.kind, req.slug.trim(), permission, &token).await { + let _ = sqlx::query(queries::get(state.db.kind(), "DELETE FROM resource_share_invitations WHERE token_hash = ?")) + .bind(&token_hash).execute(state.db.pool()).await; + return Err(error); + } + } else { + sqlx::query(queries::get(state.db.kind(), "INSERT INTO resource_permissions (resource_kind, resource_slug, user_id, permission) VALUES (?, ?, ?, ?)")) + .bind(&req.kind).bind(req.slug.trim()).bind(user.id).bind(permission).execute(state.db.pool()).await.map_err(AuthError::database)?; + } } if !missing.is_empty() { return Err(AuthError::bad_request(&format!("No registered account for: {}", missing.join(", ")))); } - Ok(Json(serde_json::json!({"ok":true}))) + Ok(Json(serde_json::json!({"ok":true,"confirmation_required":state.share_confirmation_required}))) +} + +pub async fn accept_share_invitation(State(state): State, AxumPath(token): AxumPath) -> Result { + let token_hash = hash_token(token.trim()); + let row: Option<(String, String, i64, String, String, Option)> = sqlx::query_as(queries::get(state.db.kind(), "SELECT resource_kind, resource_slug, user_id, permission, expires_at, accepted_at FROM resource_share_invitations WHERE token_hash = ?")) + .bind(&token_hash).fetch_optional(state.db.pool()).await.map_err(AuthError::database)?; + let (kind, slug, user_id, permission, expires_at, accepted_at) = row.ok_or_else(|| AuthError::bad_request("The sharing invitation is invalid or has expired."))?; + let expires = chrono::DateTime::parse_from_rfc3339(&expires_at).map_err(|_| AuthError::bad_request("The sharing invitation is invalid or has expired."))?.with_timezone(&Utc); + if accepted_at.is_none() { + if expires <= Utc::now() { return Err(AuthError::bad_request("The sharing invitation is invalid or has expired.")); } + let mut tx = state.db.pool().begin().await.map_err(AuthError::database)?; + sqlx::query(queries::get(state.db.kind(), "DELETE FROM resource_permissions WHERE resource_kind = ? AND resource_slug = ? AND user_id = ?")) + .bind(&kind).bind(&slug).bind(user_id).execute(&mut *tx).await.map_err(AuthError::database)?; + sqlx::query(queries::get(state.db.kind(), "INSERT INTO resource_permissions (resource_kind, resource_slug, user_id, permission) VALUES (?, ?, ?, ?)")) + .bind(&kind).bind(&slug).bind(user_id).bind(&permission).execute(&mut *tx).await.map_err(AuthError::database)?; + sqlx::query(queries::get(state.db.kind(), "UPDATE resource_share_invitations SET accepted_at = ? WHERE token_hash = ?")) + .bind(Utc::now().to_rfc3339()).bind(&token_hash).execute(&mut *tx).await.map_err(AuthError::database)?; + tx.commit().await.map_err(AuthError::database)?; + } + let target = if kind == "workspace" { format!("/w/{slug}") } else { format!("/p/{slug}") }; + Ok(Redirect::to(&target)) } pub async fn remove_resource_user(State(state): State, headers: HeaderMap, Json(req): Json) -> Result, AuthError> { @@ -249,6 +291,8 @@ pub async fn remove_resource_user(State(state): State, headers: Hea if let Some(user) = find_user_by_email(&state, &email).await? { sqlx::query(queries::get(state.db.kind(), "DELETE FROM resource_permissions WHERE resource_kind = ? AND resource_slug = ? AND user_id = ?")) .bind(&req.kind).bind(req.slug.trim()).bind(user.id).execute(state.db.pool()).await.map_err(AuthError::database)?; + sqlx::query(queries::get(state.db.kind(), "DELETE FROM resource_share_invitations WHERE resource_kind = ? AND resource_slug = ? AND user_id = ?")) + .bind(&req.kind).bind(req.slug.trim()).bind(user.id).execute(state.db.pool()).await.map_err(AuthError::database)?; } Ok(Json(serde_json::json!({"ok":true}))) } @@ -262,7 +306,9 @@ pub async fn resource_sharing(State(state): State, headers: HeaderM .bind(kind).bind(slug).fetch_all(state.db.pool()).await.map_err(AuthError::database)?; let links: Vec<(String,String,Option,String)> = sqlx::query_as(queries::get(state.db.kind(), "SELECT token_hash, permission, expires_at, created_at FROM resource_share_links WHERE resource_kind = ? AND resource_slug = ? AND revoked_at IS NULL ORDER BY created_at DESC")) .bind(kind).bind(slug).fetch_all(state.db.pool()).await.map_err(AuthError::database)?; - Ok(Json(serde_json::json!({"users":users.into_iter().map(|(email,nickname,permission)|serde_json::json!({"email":email,"nickname":nickname,"permission":permission})).collect::>(), "links":links.into_iter().map(|(token,permission,expires_at,created_at)|serde_json::json!({"token":token,"permission":permission,"expires_at":expires_at,"created_at":created_at})).collect::>() }))) + let pending: Vec<(String,String,String,String)> = sqlx::query_as(queries::get(state.db.kind(), "SELECT u.email, u.nickname, i.permission, i.expires_at FROM resource_share_invitations i JOIN users u ON u.id = i.user_id WHERE i.resource_kind = ? AND i.resource_slug = ? AND i.accepted_at IS NULL ORDER BY u.email")) + .bind(kind).bind(slug).fetch_all(state.db.pool()).await.map_err(AuthError::database)?; + Ok(Json(serde_json::json!({"users":users.into_iter().map(|(email,nickname,permission)|serde_json::json!({"email":email,"nickname":nickname,"permission":permission})).collect::>(), "pending":pending.into_iter().map(|(email,nickname,permission,expires_at)|serde_json::json!({"email":email,"nickname":nickname,"permission":permission,"expires_at":expires_at})).collect::>(), "links":links.into_iter().map(|(token,permission,expires_at,created_at)|serde_json::json!({"token":token,"permission":permission,"expires_at":expires_at,"created_at":created_at})).collect::>() }))) } pub async fn create_share_link(State(state): State, headers: HeaderMap, Json(req): Json) -> Result, AuthError> { @@ -609,6 +655,37 @@ async fn send_registration_email( send_message(smtp, message, "registration e-mail").await } +async fn send_share_invitation( + smtp: &SmtpConfig, + owner: &User, + recipient_user: &User, + kind: &str, + slug: &str, + permission: &str, + token: &str, +) -> Result<(), AuthError> { + let site = smtp.public_url.trim_end_matches('/'); + let accept_url = format!("{site}/share-invitations/{token}/accept"); + let resource_label = if kind == "workspace" { "workspace" } else { "note" }; + let access_label = if permission == "rw" { "view and edit" } else { "view" }; + let sender = smtp.from.parse::().map_err(|_| AuthError::internal("Invalid SMTP_FROM."))?; + let recipient = recipient_user.email.parse::().map_err(|_| AuthError::internal("Invalid recipient address."))?; + let subject = format!("{} shared a RustPad {} with you", owner.nickname, resource_label); + let text_body = format!( + "Hello {},\n\n{} shared the {} '{}' with you ({access_label}).\nAccept the invitation within 7 days:\n{}\n\nIf you were not expecting this invitation, ignore this message.", + recipient_user.nickname, owner.nickname, resource_label, slug, accept_url + ); + let html_body = format!(r#"

A RustPad {resource_label} was shared with you

Hello {},

{} shared {} with you. Permission: {access_label}.

Accept invitation

This link expires in 7 days.

"#, + recipient_user.nickname, owner.nickname, slug, accept_url + ); + let message = Message::builder().from(sender).to(recipient).subject(subject) + .multipart(MultiPart::alternative() + .singlepart(SinglePart::builder().header(ContentType::TEXT_PLAIN).body(text_body)) + .singlepart(SinglePart::builder().header(ContentType::TEXT_HTML).body(html_body))) + .map_err(|_| AuthError::internal("Failed to build sharing invitation e-mail."))?; + send_message(smtp, message, "sharing invitation e-mail").await +} + async fn send_message( smtp: &SmtpConfig, message: Message, diff --git a/src/config.rs b/src/config.rs index 35d0e65..dd725fc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,6 +16,7 @@ pub struct Config { pub smtp: Option, pub registration_enabled: bool, pub account_confirmation_required: bool, + pub share_confirmation_required: bool, pub frontend_log_level: String, pub anonymous_access_token_ttl_days: i64, pub user_session_ttl_days: i64, @@ -82,6 +83,7 @@ impl Config { smtp, registration_enabled: env_bool("REGISTRATION_ENABLED", false)?, account_confirmation_required: env_bool("ACCOUNT_CONFIRMATION_REQUIRED", false)?, + share_confirmation_required: env_bool("SHARE_CONFIRMATION_REQUIRED", false)?, frontend_log_level: env_log_level("FRONTEND_LOG_LEVEL", "warn")?, anonymous_access_token_ttl_days, user_session_ttl_days, diff --git a/src/main.rs b/src/main.rs index edc7f80..6d158cc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,6 +37,7 @@ async fn main() -> Result<(), Box> { file_cache_max_age_seconds = config.file_cache_max_age_seconds, registration_enabled = config.registration_enabled, account_confirmation_required = config.account_confirmation_required, + share_confirmation_required = config.share_confirmation_required, frontend_log_level = %config.frontend_log_level, anonymous_access_token_ttl_days = config.anonymous_access_token_ttl_days, user_session_ttl_days = config.user_session_ttl_days, @@ -64,6 +65,7 @@ async fn main() -> Result<(), Box> { config.smtp.clone(), config.registration_enabled, config.account_confirmation_required, + config.share_confirmation_required, config.frontend_log_level.clone(), config.anonymous_access_token_ttl_days, config.user_session_ttl_days, diff --git a/src/state.rs b/src/state.rs index 836a389..fa4013e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -42,6 +42,7 @@ pub struct AppState { pub smtp: Option, pub registration_enabled: bool, pub account_confirmation_required: bool, + pub share_confirmation_required: bool, pub frontend_log_level: String, pub anonymous_access_token_ttl_days: i64, pub user_session_ttl_days: i64, @@ -51,8 +52,8 @@ pub struct AppState { } impl AppState { - pub fn new(db: Database, asset_version: String, storage: crate::storage::Storage, upload_max_size_bytes: usize, file_cache_max_age_seconds: u64, smtp: Option, registration_enabled: bool, account_confirmation_required: bool, frontend_log_level: String, anonymous_access_token_ttl_days: i64, user_session_ttl_days: i64) -> Self { - Self { db, asset_version, storage, upload_max_size_bytes, file_cache_max_age_seconds, smtp, registration_enabled, account_confirmation_required, frontend_log_level, anonymous_access_token_ttl_days, user_session_ttl_days, channels: RwLock::new(HashMap::new()), presence: RwLock::new(HashMap::new()), next_connection_id: AtomicU64::new(1) } + pub fn new(db: Database, asset_version: String, storage: crate::storage::Storage, upload_max_size_bytes: usize, file_cache_max_age_seconds: u64, smtp: Option, registration_enabled: bool, account_confirmation_required: bool, share_confirmation_required: bool, frontend_log_level: String, anonymous_access_token_ttl_days: i64, user_session_ttl_days: i64) -> Self { + Self { db, asset_version, storage, upload_max_size_bytes, file_cache_max_age_seconds, smtp, registration_enabled, account_confirmation_required, share_confirmation_required, frontend_log_level, anonymous_access_token_ttl_days, user_session_ttl_days, channels: RwLock::new(HashMap::new()), presence: RwLock::new(HashMap::new()), next_connection_id: AtomicU64::new(1) } } async fn channel_for_key(&self, key: String) -> broadcast::Sender { if let Some(sender) = self.channels.read().await.get(&key) { return sender.clone(); } diff --git a/static/css/styles.css b/static/css/styles.css index e2f91dd..abe9899 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -20,666 +20,3640 @@ --success: #40d3a3; } -* { box-sizing: border-box; } -html { min-width: 320px; background: var(--bg); } -body { min-height: 100vh; margin: 0; background: var(--bg); color: var(--text); } -button, input, select, textarea { font: inherit; } -button, a, input, select, textarea { -webkit-tap-highlight-color: transparent; } -button { cursor: pointer; } -button:disabled { cursor: wait; opacity: .65; } -a { color: inherit; } +* { + box-sizing: border-box; +} -.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; } -.brand { color: var(--text); font-weight: 700; text-decoration: none; letter-spacing: -.02em; } +html { + min-width: 320px; + background: var(--bg); +} -.site-header { width: min(720px, calc(100% - 32px)); min-height: 64px; margin: 0 auto; display: flex; align-items: center; border-bottom: 1px solid var(--border); } +body { + min-height: 100vh; + margin: 0; + background: var(--bg); + color: var(--text); +} -.home-layout { width: min(520px, calc(100% - 32px)); margin: 0 auto; padding: 72px 0; } -.home-intro { margin-bottom: 28px; } -.home-intro h1 { margin: 0; font-size: clamp(2rem, 7vw, 3rem); letter-spacing: -.04em; } -.home-intro p { margin: 10px 0 0; color: var(--muted); line-height: 1.6; } +button, +input, +select, +textarea { + font: inherit; +} -.panel { border: 1px solid var(--border); border-radius: 12px; background: var(--surface); } -.create-panel { padding: 24px; } -#create-form { display: grid; gap: 20px; padding-top: 22px; } -.field { display: grid; gap: 8px; } -.field label { color: #dce1e8; font-size: .86rem; font-weight: 750; } -.label-row, .field-meta { display: flex; align-items: center; justify-content: space-between; gap: 12px; } -.label-row span, .field-meta, .field small { color: var(--muted-2); font-size: .73rem; } -input, select { border: 1px solid var(--border-strong); outline: none; background: #0e1116; color: var(--text); } -input { width: 100%; min-height: 46px; padding: 0 13px; border-radius: 10px; } -input:focus, select:focus, textarea:focus { border-color: #7567db; outline: 1px solid #7567db; outline-offset: 1px; } -.password-input { position: relative; } -.password-input input { padding-right: 72px; } -.text-button { position: static; transform: none; border: 0; background: transparent; color: #a99ef8; padding: 4px 2px; font-size: .78rem; } -.text-button:hover { color: #c3bbff; } -.password-toggle { position: absolute; top: 50%; right: 8px; transform: translateY(-50%); padding: 7px; } -[hidden] { display: none !important; } -.form-message { min-height: 1.2em; margin: -5px 0 0; font-size: .8rem; } -.error { color: var(--danger); } -.primary-button, .secondary-button, .inline-button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 40px; border-radius: 8px; font-weight: 650; text-decoration: none; } -.primary-button { width: 100%; border: 1px solid #8372ef; background: var(--accent); color: white; padding: 0 16px; } -.primary-button:hover { background: var(--accent-hover); } -.secondary-button { border: 1px solid var(--border-strong); background: var(--surface-2); color: #d2d8e1; padding: 0 13px; } -.secondary-button:hover { border-color: #4b5565; background: var(--surface-3); } -.inline-button { width: auto; margin-top: 18px; padding: 0 16px; } +button, +a, +input, +select, +textarea { + -webkit-tap-highlight-color: transparent; +} -.app-header { display: flex; align-items: center; justify-content: space-between; gap: 24px; min-height: 70px; padding: 0 20px; border-bottom: 1px solid var(--border); background: #0d1015; } -.app-header__main, .header-actions { display: flex; align-items: center; gap: 14px; min-width: 0; } -.header-divider { width: 1px; height: 30px; background: var(--border); } -.document-heading { min-width: 0; } -.document-heading h1 { overflow: hidden; margin: 0; font-size: 1rem; white-space: nowrap; text-overflow: ellipsis; } -.document-url { overflow: hidden; max-width: 360px; margin: 3px 0 0; color: var(--muted-2); font-size: .72rem; white-space: nowrap; text-overflow: ellipsis; } -.status { display: inline-flex; align-items: center; gap: 8px; min-height: 34px; padding: 0 8px; color: var(--muted); font-size: .78rem; } -.status__dot { width: 7px; height: 7px; border-radius: 50%; background: #e3a94d; } -.status__dot.is-online { background: var(--success); } -.status__dot.is-offline { background: var(--danger); } +button { + cursor: pointer; +} -.editor-layout { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 0; height: calc(100vh - 70px); overflow: hidden; transition: grid-template-columns .18s ease; } -.history-open .editor-layout { grid-template-columns: minmax(0, 1fr) 340px; } -.editor-panel { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; min-width: 0; background: var(--surface); } -.editor-toolbar { display: flex; align-items: center; gap: 8px; min-height: 52px; padding: 8px 12px; border-bottom: 1px solid var(--border); background: #101319; } -.toolbar-group, .view-switch { display: inline-flex; align-items: center; gap: 4px; padding-right: 8px; border-right: 1px solid var(--border); } -.toolbar-group:last-child, .view-switch { padding-right: 0; border-right: 0; } -.toolbar-fill { flex: 1; } -.editor-toolbar button, .editor-toolbar select { min-height: 34px; border: 1px solid transparent; border-radius: 7px; background: transparent; color: #b8c0cc; padding: 0 9px; font-size: .78rem; } -.editor-toolbar button:hover, .editor-toolbar select:hover { border-color: var(--border); background: var(--surface-2); color: white; } -.editor-toolbar select { border-color: var(--border); background: #11151c; } -.view-switch { padding: 3px; border: 1px solid var(--border); border-radius: 9px; background: #0d1015; } -.view-switch button.active { background: var(--surface-3); color: white; } +button:disabled { + cursor: wait; + opacity: .65; +} -.workspace { display: grid; min-height: 0; background: #0e1116; } -.workspace.view-split { grid-template-columns: 1fr 1fr; } -.workspace.view-edit { grid-template-columns: 1fr; } -.workspace.view-preview { grid-template-columns: 1fr; } -.workspace.view-edit .preview-column, .workspace.view-preview .editor-column { display: none; } -.editor-column, .preview-column { display: grid; grid-template-rows: 30px minmax(0, 1fr); min-width: 0; min-height: 0; } -.preview-column { border-left: 1px solid var(--border); } -.column-label { display: flex; align-items: center; padding: 0 18px; border-bottom: 1px solid #202631; background: #10141a; color: var(--muted-2); font-size: .7rem; font-weight: 750; letter-spacing: .08em; text-transform: uppercase; } -textarea { display: block; width: 100%; height: 100%; min-height: 0; resize: none; padding: 24px; border: 0; outline: none; background: #0d1015; color: #edf1f6; font: 400 17px/1.72 ui-monospace, SFMono-Regular, Consolas, monospace; caret-color: #9b89ff; } -textarea::placeholder { color: #515a68; } -textarea::selection { background: rgba(124,104,238,.35); } -.preview { overflow: auto; min-height: 0; padding: 24px; color: #dce2eb; line-height: 1.72; } -.markdown-body h1, .markdown-body h2, .markdown-body h3 { margin: 1.25em 0 .5em; letter-spacing: -.03em; } -.markdown-body h1:first-child, .markdown-body h2:first-child, .markdown-body h3:first-child { margin-top: 0; } -.markdown-body h1 { font-size: 2rem; } -.markdown-body h2 { font-size: 1.5rem; } -.markdown-body p { margin: .72em 0; } -.markdown-body code { padding: .16em .36em; border: 1px solid #303745; border-radius: 5px; background: #1a2029; } -.markdown-body pre { overflow: auto; padding: 16px; border: 1px solid var(--border); border-radius: 10px; background: #0a0d12; } -.markdown-body pre code { padding: 0; border: 0; background: transparent; } -.markdown-body blockquote { margin: 1em 0; padding: .2em 1em; border-left: 3px solid var(--accent); color: #abb5c3; } -.markdown-body a { color: #aa9df8; } -.markdown-body hr { border: 0; border-top: 1px solid var(--border); } -.editor-footer { display: flex; align-items: center; justify-content: space-between; gap: 20px; min-height: 38px; padding: 0 16px; border-top: 1px solid var(--border); color: var(--muted-2); font-size: .72rem; } -.document-stats { display: flex; gap: 14px; } +a { + color: inherit; +} -.history-panel { position: relative; width: 340px; overflow: hidden; border-left: 1px solid var(--border); background: #101319; transform: translateX(100%); transition: transform .18s ease; } -.history-panel.open { transform: translateX(0); } -.history-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 18px; } -.history-help { margin: 0; padding: 0 18px 16px; border-bottom: 1px solid var(--border); color: var(--muted); font-size: .8rem; line-height: 1.5; } -.icon-button { display: grid; place-items: center; width: 34px; height: 34px; border: 1px solid var(--border); border-radius: 8px; background: transparent; color: #c5ccd6; font-size: 1.2rem; } -.history-list { overflow: auto; height: calc(100vh - 185px); padding: 10px 18px 24px; } -.revision { position: relative; display: grid; grid-template-columns: 12px 1fr; gap: 10px; padding: 13px 0; } -.revision::after { content: ""; position: absolute; top: 25px; bottom: -13px; left: 5px; width: 1px; background: var(--border); } -.revision:last-child::after { display: none; } -.revision__marker { position: relative; z-index: 1; width: 11px; height: 11px; margin-top: 3px; border: 2px solid #8b7af4; border-radius: 50%; background: #101319; } -.revision time { display: block; color: #d1d7df; font-size: .8rem; } -.revision small { display: block; margin-top: 4px; color: var(--muted-2); } -.revision button { margin-top: 10px; border: 0; background: transparent; color: #9e92f5; padding: 0; font-size: .76rem; font-weight: 750; } -.empty { padding: 28px 0; color: var(--muted-2); text-align: center; font-size: .82rem; } +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} - dialog { width: min(420px, calc(100% - 24px)); padding: 0; border: 0; background: transparent; color: inherit; } -dialog::backdrop { background: rgba(4,6,9,.82); } -.dialog-panel { display: grid; gap: 12px; padding: 24px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); text-align: left; } -.dialog-panel input { margin-top: 6px; } -.dialog-panel .primary-button { margin-top: 2px; } -.dialog-link { color: var(--muted); font-size: .78rem; } -.toast { position: fixed; right: 20px; bottom: 20px; z-index: 20; padding: 11px 14px; border: 1px solid var(--border-strong); border-radius: 10px; background: #171b23; color: #e6eaf0; font-size: .8rem; opacity: 0; transform: translateY(8px); pointer-events: none; transition: .16s ease; } -.toast.visible { opacity: 1; transform: translateY(0); } -.error-page { display: grid; place-items: center; min-height: 100vh; padding: 24px; text-align: center; } -.error-page h1 { margin: 0; font-size: clamp(2rem, 6vw, 4rem); letter-spacing: -.05em; } -.error-page p { color: var(--muted); } +.brand { + color: var(--text); + font-weight: 700; + text-decoration: none; + letter-spacing: -.02em; +} + +.site-header { + width: min(720px, calc(100% - 32px)); + min-height: 64px; + margin: 0 auto; + display: flex; + align-items: center; + border-bottom: 1px solid var(--border); +} + +.home-layout { + width: min(520px, calc(100% - 32px)); + margin: 0 auto; + padding: 72px 0; +} + +.home-intro { + margin-bottom: 28px; +} + +.home-intro h1 { + margin: 0; + font-size: clamp(2rem, 7vw, 3rem); + letter-spacing: -.04em; +} + +.home-intro p { + margin: 10px 0 0; + color: var(--muted); + line-height: 1.6; +} + +.panel { + border: 1px solid var(--border); + border-radius: 12px; + background: var(--surface); +} + +.create-panel { + padding: 24px; +} + +#create-form { + display: grid; + gap: 20px; + padding-top: 22px; +} + +.field { + display: grid; + gap: 8px; +} + +.field label { + color: #dce1e8; + font-size: .86rem; + font-weight: 750; +} + +.label-row, +.field-meta { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.label-row span, +.field-meta, +.field small { + color: var(--muted-2); + font-size: .73rem; +} + +input, +select { + border: 1px solid var(--border-strong); + outline: none; + background: #0e1116; + color: var(--text); +} + +input { + width: 100%; + min-height: 46px; + padding: 0 13px; + border-radius: 10px; +} + +input:focus, +select:focus, +textarea:focus { + border-color: #7567db; + outline: 1px solid #7567db; + outline-offset: 1px; +} + +.password-input { + position: relative; +} + +.password-input input { + padding-right: 72px; +} + +.text-button { + position: static; + transform: none; + border: 0; + background: transparent; + color: #a99ef8; + padding: 4px 2px; + font-size: .78rem; +} + +.text-button:hover { + color: #c3bbff; +} + +.password-toggle { + position: absolute; + top: 50%; + right: 8px; + transform: translateY(-50%); + padding: 7px; +} + +[hidden] { + display: none !important; +} + +.form-message { + min-height: 1.2em; + margin: -5px 0 0; + font-size: .8rem; +} + +.error { + color: var(--danger); +} + +.primary-button, +.secondary-button, +.inline-button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + min-height: 40px; + border-radius: 8px; + font-weight: 650; + text-decoration: none; +} + +.primary-button { + width: 100%; + border: 1px solid #8372ef; + background: var(--accent); + color: white; + padding: 0 16px; +} + +.primary-button:hover { + background: var(--accent-hover); +} + +.secondary-button { + border: 1px solid var(--border-strong); + background: var(--surface-2); + color: #d2d8e1; + padding: 0 13px; +} + +.secondary-button:hover { + border-color: #4b5565; + background: var(--surface-3); +} + +.inline-button { + width: auto; + margin-top: 18px; + padding: 0 16px; +} + +.app-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; + min-height: 70px; + padding: 0 20px; + border-bottom: 1px solid var(--border); + background: #0d1015; +} + +.app-header__main, +.header-actions { + display: flex; + align-items: center; + gap: 14px; + min-width: 0; +} + +.header-divider { + width: 1px; + height: 30px; + background: var(--border); +} + +.document-heading { + min-width: 0; +} + +.document-heading h1 { + overflow: hidden; + margin: 0; + font-size: 1rem; + white-space: nowrap; + text-overflow: ellipsis; +} + +.document-url { + overflow: hidden; + max-width: 360px; + margin: 3px 0 0; + color: var(--muted-2); + font-size: .72rem; + white-space: nowrap; + text-overflow: ellipsis; +} + +.status { + display: inline-flex; + align-items: center; + gap: 8px; + min-height: 34px; + padding: 0 8px; + color: var(--muted); + font-size: .78rem; +} + +.status__dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: #e3a94d; +} + +.status__dot.is-online { + background: var(--success); +} + +.status__dot.is-offline { + background: var(--danger); +} + +.editor-layout { + position: relative; + display: grid; + grid-template-columns: minmax(0, 1fr) 0; + height: calc(100vh - 70px); + overflow: hidden; + transition: grid-template-columns .18s ease; +} + +.history-open .editor-layout { + grid-template-columns: minmax(0, 1fr) 340px; +} + +.editor-panel { + display: grid; + grid-template-rows: auto minmax(0, 1fr) auto; + min-width: 0; + background: var(--surface); +} + +.editor-toolbar { + display: flex; + align-items: center; + gap: 8px; + min-height: 52px; + padding: 8px 12px; + border-bottom: 1px solid var(--border); + background: #101319; +} + +.toolbar-group, +.view-switch { + display: inline-flex; + align-items: center; + gap: 4px; + padding-right: 8px; + border-right: 1px solid var(--border); +} + +.toolbar-group:last-child, +.view-switch { + padding-right: 0; + border-right: 0; +} + +.toolbar-fill { + flex: 1; +} + +.editor-toolbar button, +.editor-toolbar select { + min-height: 34px; + border: 1px solid transparent; + border-radius: 7px; + background: transparent; + color: #b8c0cc; + padding: 0 9px; + font-size: .78rem; +} + +.editor-toolbar button:hover, +.editor-toolbar select:hover { + border-color: var(--border); + background: var(--surface-2); + color: white; +} + +.editor-toolbar select { + border-color: var(--border); + background: #11151c; +} + +.view-switch { + padding: 3px; + border: 1px solid var(--border); + border-radius: 9px; + background: #0d1015; +} + +.view-switch button.active { + background: var(--surface-3); + color: white; +} + +.workspace { + display: grid; + min-height: 0; + background: #0e1116; +} + +.workspace.view-split { + grid-template-columns: 1fr 1fr; +} + +.workspace.view-edit { + grid-template-columns: 1fr; +} + +.workspace.view-preview { + grid-template-columns: 1fr; +} + +.workspace.view-edit .preview-column, +.workspace.view-preview .editor-column { + display: none; +} + +.editor-column, +.preview-column { + display: grid; + grid-template-rows: 30px minmax(0, 1fr); + min-width: 0; + min-height: 0; +} + +.preview-column { + border-left: 1px solid var(--border); +} + +.column-label { + display: flex; + align-items: center; + padding: 0 18px; + border-bottom: 1px solid #202631; + background: #10141a; + color: var(--muted-2); + font-size: .7rem; + font-weight: 750; + letter-spacing: .08em; + text-transform: uppercase; +} + +textarea { + display: block; + width: 100%; + height: 100%; + min-height: 0; + resize: none; + padding: 24px; + border: 0; + outline: none; + background: #0d1015; + color: #edf1f6; + font: 400 17px/1.72 ui-monospace, SFMono-Regular, Consolas, monospace; + caret-color: #9b89ff; +} + +textarea::placeholder { + color: #515a68; +} + +textarea::selection { + background: rgba(124, 104, 238, .35); +} + +.preview { + overflow: auto; + min-height: 0; + padding: 24px; + color: #dce2eb; + line-height: 1.72; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3 { + margin: 1.25em 0 .5em; + letter-spacing: -.03em; +} + +.markdown-body h1:first-child, +.markdown-body h2:first-child, +.markdown-body h3:first-child { + margin-top: 0; +} + +.markdown-body h1 { + font-size: 2rem; +} + +.markdown-body h2 { + font-size: 1.5rem; +} + +.markdown-body p { + margin: .72em 0; +} + +.markdown-body code { + padding: .16em .36em; + border: 1px solid #303745; + border-radius: 5px; + background: #1a2029; +} + +.markdown-body pre { + overflow: auto; + padding: 16px; + border: 1px solid var(--border); + border-radius: 10px; + background: #0a0d12; +} + +.markdown-body pre code { + padding: 0; + border: 0; + background: transparent; +} + +.markdown-body blockquote { + margin: 1em 0; + padding: .2em 1em; + border-left: 3px solid var(--accent); + color: #abb5c3; +} + +.markdown-body a { + color: #aa9df8; +} + +.markdown-body hr { + border: 0; + border-top: 1px solid var(--border); +} + +.editor-footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + min-height: 38px; + padding: 0 16px; + border-top: 1px solid var(--border); + color: var(--muted-2); + font-size: .72rem; +} + +.document-stats { + display: flex; + gap: 14px; +} + +.history-panel { + position: relative; + width: 340px; + overflow: hidden; + border-left: 1px solid var(--border); + background: #101319; + transform: translateX(100%); + transition: transform .18s ease; +} + +.history-panel.open { + transform: translateX(0); +} + +.history-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 18px; +} + +.history-help { + margin: 0; + padding: 0 18px 16px; + border-bottom: 1px solid var(--border); + color: var(--muted); + font-size: .8rem; + line-height: 1.5; +} + +.icon-button { + display: grid; + place-items: center; + width: 34px; + height: 34px; + border: 1px solid var(--border); + border-radius: 8px; + background: transparent; + color: #c5ccd6; + font-size: 1.2rem; +} + +.history-list { + overflow: auto; + height: calc(100vh - 185px); + padding: 10px 18px 24px; +} + +.revision { + position: relative; + display: grid; + grid-template-columns: 12px 1fr; + gap: 10px; + padding: 13px 0; +} + +.revision::after { + content: ""; + position: absolute; + top: 25px; + bottom: -13px; + left: 5px; + width: 1px; + background: var(--border); +} + +.revision:last-child::after { + display: none; +} + +.revision__marker { + position: relative; + z-index: 1; + width: 11px; + height: 11px; + margin-top: 3px; + border: 2px solid #8b7af4; + border-radius: 50%; + background: #101319; +} + +.revision time { + display: block; + color: #d1d7df; + font-size: .8rem; +} + +.revision small { + display: block; + margin-top: 4px; + color: var(--muted-2); +} + +.revision button { + margin-top: 10px; + border: 0; + background: transparent; + color: #9e92f5; + padding: 0; + font-size: .76rem; + font-weight: 750; +} + +.empty { + padding: 28px 0; + color: var(--muted-2); + text-align: center; + font-size: .82rem; +} + +dialog { + width: min(420px, calc(100% - 24px)); + padding: 0; + border: 0; + background: transparent; + color: inherit; +} + +dialog::backdrop { + background: rgba(4, 6, 9, .82); +} + +.dialog-panel { + display: grid; + gap: 12px; + padding: 24px; + border: 1px solid var(--border); + border-radius: 12px; + background: var(--surface); + text-align: left; +} + +.dialog-panel input { + margin-top: 6px; +} + +.dialog-panel .primary-button { + margin-top: 2px; +} + +.dialog-link { + color: var(--muted); + font-size: .78rem; +} + +.toast { + position: fixed; + right: 20px; + bottom: 20px; + z-index: 20; + padding: 11px 14px; + border: 1px solid var(--border-strong); + border-radius: 10px; + background: #171b23; + color: #e6eaf0; + font-size: .8rem; + opacity: 0; + transform: translateY(8px); + pointer-events: none; + transition: .16s ease; +} + +.toast.visible { + opacity: 1; + transform: translateY(0); +} + +.error-page { + display: grid; + place-items: center; + min-height: 100vh; + padding: 24px; + text-align: center; +} + +.error-page h1 { + margin: 0; + font-size: clamp(2rem, 6vw, 4rem); + letter-spacing: -.05em; +} + +.error-page p { + color: var(--muted); +} @media (max-width: 980px) { - .app-header { align-items: flex-start; min-height: auto; padding: 12px 14px; } - .app-header__main, .header-actions { flex-wrap: wrap; } - .editor-layout { height: calc(100vh - 92px); } - .toolbar-settings { display: none; } - .history-open .editor-layout { grid-template-columns: minmax(0, 1fr); } - .history-panel { position: absolute; top: 0; right: 0; bottom: 0; z-index: 10; } + .app-header { + align-items: flex-start; + min-height: auto; + padding: 12px 14px; + } + + .app-header__main, + .header-actions { + flex-wrap: wrap; + } + + .editor-layout { + height: calc(100vh - 92px); + } + + .toolbar-settings { + display: none; + } + + .history-open .editor-layout { + grid-template-columns: minmax(0, 1fr); + } + + .history-panel { + position: absolute; + top: 0; + right: 0; + bottom: 0; + z-index: 10; + } } @media (max-width: 720px) { - .site-header, .home-layout { width: min(100% - 24px, 520px); } - .document-url, .header-divider { display: none; } - .home-layout { padding: 44px 0; } - .app-header { gap: 10px; } - .status { display: none; } - .editor-toolbar { overflow-x: auto; flex-wrap: nowrap; } - .toolbar-fill { display: none; } - .workspace.view-split { grid-template-columns: 1fr; } - .workspace.view-split .preview-column { display: none; } - .view-switch button[data-view="split"] { display: none; } - .preview-column { border-left: 0; } - textarea, .preview { padding: 18px; } - .editor-footer { align-items: flex-start; flex-direction: column; justify-content: center; gap: 2px; padding: 7px 12px; } + + .site-header, + .home-layout { + width: min(100% - 24px, 520px); + } + + .document-url, + .header-divider { + display: none; + } + + .home-layout { + padding: 44px 0; + } + + .app-header { + gap: 10px; + } + + .status { + display: none; + } + + .editor-toolbar { + overflow-x: auto; + flex-wrap: nowrap; + } + + .toolbar-fill { + display: none; + } + + .workspace.view-split { + grid-template-columns: 1fr; + } + + .workspace.view-split .preview-column { + display: none; + } + + .view-switch button[data-view="split"] { + display: none; + } + + .preview-column { + border-left: 0; + } + + textarea, + .preview { + padding: 18px; + } + + .editor-footer { + align-items: flex-start; + flex-direction: column; + justify-content: center; + gap: 2px; + padding: 7px 12px; + } } @media (max-width: 480px) { - .create-panel { padding: 20px; } - .header-actions { gap: 6px; } - .secondary-button { min-height: 36px; padding: 0 10px; font-size: .75rem; } - .history-panel { width: 100%; } + .create-panel { + padding: 20px; + } + + .header-actions { + gap: 6px; + } + + .secondary-button { + min-height: 36px; + padding: 0 10px; + font-size: .75rem; + } + + .history-panel { + width: 100%; + } } -.markdown-toggle { border: 1px solid var(--border) !important; } -.markdown-toggle.active { background: var(--surface-3) !important; color: white !important; } -.preview--raw { white-space: pre-wrap; overflow-wrap: anywhere; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; } +.markdown-toggle { + border: 1px solid var(--border) !important; +} -.workspace-page { width: min(1100px, calc(100% - 32px)); margin: 0 auto; padding: 44px 0 80px; } -.workspace-top { display: flex; align-items: end; justify-content: space-between; gap: 24px; padding-bottom: 24px; border-bottom: 1px solid var(--border); } -.workspace-top h2 { margin: 0; font-size: 2rem; } -.workspace-top p { margin: 7px 0 0; color: var(--muted); } -.notes-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; padding-top: 20px; } -.note-card { min-height: 130px; padding: 18px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); text-decoration: none; transition: border-color .15s, background .15s; } -.note-card:hover { border-color: var(--border-strong); background: var(--surface-2); } -.note-card h3 { margin: 0; font-size: 1rem; } -.note-card p { margin: 36px 0 0; color: var(--muted); font-size: .75rem; } -.dialog-actions { display: flex; justify-content: flex-end; gap: 8px; } -.inline-button { width: auto; padding-inline: 18px; } +.markdown-toggle.active { + background: var(--surface-3) !important; + color: white !important; +} -.error-card { width: min(560px, 100%); padding: 32px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); } -.error-code { margin: 0 0 10px; color: var(--muted-2); font: 700 .78rem/1 ui-monospace, SFMono-Regular, Consolas, monospace; letter-spacing: .12em; } -.error-card h1 { font-size: clamp(1.8rem, 6vw, 3rem); } -.error-card > p:not(.error-code) { max-width: 46ch; margin: 14px auto 0; line-height: 1.6; } -.error-actions { display: flex; justify-content: center; gap: 8px; margin-top: 24px; } -@media (max-width: 480px) { .error-card { padding: 24px 18px; } .error-actions { flex-direction: column; } .error-actions .inline-button { width: 100%; } } +.preview--raw { + white-space: pre-wrap; + overflow-wrap: anywhere; + font-family: ui-monospace, SFMono-Regular, Consolas, monospace; +} + +.workspace-page { + width: min(1100px, calc(100% - 32px)); + margin: 0 auto; + padding: 44px 0 80px; +} + +.workspace-top { + display: flex; + align-items: end; + justify-content: space-between; + gap: 24px; + padding-bottom: 24px; + border-bottom: 1px solid var(--border); +} + +.workspace-top h2 { + margin: 0; + font-size: 2rem; +} + +.workspace-top p { + margin: 7px 0 0; + color: var(--muted); +} + +.notes-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + gap: 12px; + padding-top: 20px; +} + +.note-card { + min-height: 130px; + padding: 18px; + border: 1px solid var(--border); + border-radius: 10px; + background: var(--surface); + text-decoration: none; + transition: border-color .15s, background .15s; +} + +.note-card:hover { + border-color: var(--border-strong); + background: var(--surface-2); +} + +.note-card h3 { + margin: 0; + font-size: 1rem; +} + +.note-card p { + margin: 36px 0 0; + color: var(--muted); + font-size: .75rem; +} + +.dialog-actions { + display: flex; + justify-content: flex-end; + gap: 8px; +} + +.inline-button { + width: auto; + padding-inline: 18px; +} + +.error-card { + width: min(560px, 100%); + padding: 32px; + border: 1px solid var(--border); + border-radius: 12px; + background: var(--surface); +} + +.error-code { + margin: 0 0 10px; + color: var(--muted-2); + font: 700 .78rem/1 ui-monospace, SFMono-Regular, Consolas, monospace; + letter-spacing: .12em; +} + +.error-card h1 { + font-size: clamp(1.8rem, 6vw, 3rem); +} + +.error-card>p:not(.error-code) { + max-width: 46ch; + margin: 14px auto 0; + line-height: 1.6; +} + +.error-actions { + display: flex; + justify-content: center; + gap: 8px; + margin-top: 24px; +} + +@media (max-width: 480px) { + .error-card { + padding: 24px 18px; + } + + .error-actions { + flex-direction: column; + } + + .error-actions .inline-button { + width: 100%; + } +} /* Home: standalone note and workspace are separate choices. */ -.home-layout--wide { width: min(1040px, calc(100% - 32px)); } -.create-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; align-items: start; } -.create-panel__heading { margin-bottom: 22px; } -.create-panel__heading h2 { margin: 0; font-size: 1.35rem; } -.create-panel__heading p { margin: 7px 0 0; color: var(--muted); line-height: 1.5; } -.create-panel form { display: grid; gap: 18px; } -.create-panel .primary-button { width: 100%; } +.home-layout--wide { + width: min(1040px, calc(100% - 32px)); +} + +.create-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 20px; + align-items: start; +} + +.create-panel__heading { + margin-bottom: 22px; +} + +.create-panel__heading h2 { + margin: 0; + font-size: 1.35rem; +} + +.create-panel__heading p { + margin: 7px 0 0; + color: var(--muted); + line-height: 1.5; +} + +.create-panel form { + display: grid; + gap: 18px; +} + +.create-panel .primary-button { + width: 100%; +} + @media (max-width: 760px) { - .home-layout--wide { width: min(100% - 24px, 560px); } - .create-grid { grid-template-columns: 1fr; } + .home-layout--wide { + width: min(100% - 24px, 560px); + } + + .create-grid { + grid-template-columns: 1fr; + } } /* Collaborative editor additions */ -.editor-shell { display: grid; grid-template-columns: auto minmax(0, 1fr); min-height: 0; overflow: hidden; background: #0d1015; } -.line-gutter { width: 62px; overflow: hidden; padding: 24px 8px 24px 0; border-right: 1px solid var(--border); color: var(--muted-2); font: 400 17px/1.72 ui-monospace, SFMono-Regular, Consolas, monospace; text-align: right; user-select: none; } -.line-gutter div { height: 1.72em; padding-right: 8px; } -.hide-line-numbers .editor-shell { grid-template-columns: 0 minmax(0, 1fr); } -.hide-line-numbers .line-gutter { width: 0; padding: 0; border: 0; } -.editor-shell textarea { padding-left: 18px; } -.line-toggle { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); font-size: .78rem; white-space: nowrap; } -.line-toggle input { width: auto; min-height: auto; margin: 0; accent-color: var(--accent); } -.user-color-control { position: relative; display: inline-flex; align-items: center; } -.user-chip { display: inline-flex; align-items: center; gap: 7px; padding: 4px 6px; border: 0; border-radius: 7px; background: transparent; color: #dce2eb; font: inherit; font-size: .78rem; } -.user-chip:hover, .user-chip:focus-visible { background: var(--surface-2); outline: none; } -.user-chip__dot { width: 10px; height: 10px; flex: 0 0 auto; border: 1px solid color-mix(in srgb, var(--owner, var(--accent)) 72%, white); border-radius: 50%; background: var(--owner, var(--accent)); box-shadow: 0 0 0 2px color-mix(in srgb, var(--owner, var(--accent)) 18%, transparent); } -.user-color-picker { position: absolute; top: calc(100% + 4px); left: 0; width: 1px; height: 1px; padding: 0; border: 0; opacity: 0; pointer-events: none; } -.dialog-copy { margin: 0 0 4px; color: var(--muted); line-height: 1.5; } -.history-header h2 { margin: 0; } -.history-header p { margin: 4px 0 0; color: var(--muted-2); font-size: .75rem; } -.revision__marker { border-color: var(--owner, #8b7af4); background: var(--owner, #8b7af4); } -.revision__meta { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; } -.revision__meta strong { font-size: .82rem; } -.revision__snippet { margin: 8px 0 0; color: var(--muted); font-size: .76rem; line-height: 1.45; } -.revision__preview { max-height: 180px; overflow: auto; margin-top: 10px; padding: 10px; border: 1px solid var(--border); border-radius: 7px; background: #0b0e13; color: #c9d0da; font: .72rem/1.5 ui-monospace, monospace; white-space: pre-wrap; } -.revision button + button { margin-left: 12px; } -.mermaid { overflow: auto; padding: 12px; border: 1px solid var(--border); border-radius: 10px; background: #0a0d12; } -@media (max-width: 720px) { .line-gutter { width: 48px; padding-top: 18px; font-size: 15px; } .editor-shell textarea { padding: 18px 12px; font-size: 15px; } .user-color-control { display: none; } } +.editor-shell { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + min-height: 0; + overflow: hidden; + background: #0d1015; +} -.markdown-body img { display: block; max-width: 100%; height: auto; margin: 16px auto; border-radius: 10px; } -.markdown-body a { overflow-wrap: anywhere; } -.public-page { min-height: 100vh; background: var(--background); } -.public-header { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; justify-content: space-between; min-height: 64px; padding: 0 max(20px, calc((100vw - 900px) / 2)); border-bottom: 1px solid var(--border); background: rgba(13,16,21,.92); backdrop-filter: blur(12px); } -.public-document { width: min(900px, calc(100% - 32px)); margin: 0 auto; padding: 56px 0 96px; } -.public-document > h1 { margin: 0; font-size: clamp(2rem, 6vw, 4rem); line-height: 1.08; } -.public-meta { margin: 12px 0 36px; color: var(--muted); font-size: .8rem; } -.public-content { min-height: 240px; padding: 32px; border: 1px solid var(--border); border-radius: 14px; background: var(--surface); } -.public-content img { cursor: zoom-in; } -@media (max-width: 600px) { .public-document { padding-top: 32px; } .public-content { padding: 20px; } } +.line-gutter { + width: 62px; + overflow: hidden; + padding: 24px 8px 24px 0; + border-right: 1px solid var(--border); + color: var(--muted-2); + font: 400 17px/1.72 ui-monospace, SFMono-Regular, Consolas, monospace; + text-align: right; + user-select: none; +} + +.line-gutter div { + height: 1.72em; + padding-right: 8px; +} + +.hide-line-numbers .editor-shell { + grid-template-columns: 0 minmax(0, 1fr); +} + +.hide-line-numbers .line-gutter { + width: 0; + padding: 0; + border: 0; +} + +.editor-shell textarea { + padding-left: 18px; +} + +.line-toggle { + display: inline-flex; + align-items: center; + gap: 6px; + color: var(--muted); + font-size: .78rem; + white-space: nowrap; +} + +.line-toggle input { + width: auto; + min-height: auto; + margin: 0; + accent-color: var(--accent); +} + +.user-color-control { + position: relative; + display: inline-flex; + align-items: center; +} + +.user-chip { + display: inline-flex; + align-items: center; + gap: 7px; + padding: 4px 6px; + border: 0; + border-radius: 7px; + background: transparent; + color: #dce2eb; + font: inherit; + font-size: .78rem; +} + +.user-chip:hover, +.user-chip:focus-visible { + background: var(--surface-2); + outline: none; +} + +.user-chip__dot { + width: 10px; + height: 10px; + flex: 0 0 auto; + border: 1px solid color-mix(in srgb, var(--owner, var(--accent)) 72%, white); + border-radius: 50%; + background: var(--owner, var(--accent)); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--owner, var(--accent)) 18%, transparent); +} + +.user-color-picker { + position: absolute; + top: calc(100% + 4px); + left: 0; + width: 1px; + height: 1px; + padding: 0; + border: 0; + opacity: 0; + pointer-events: none; +} + +.dialog-copy { + margin: 0 0 4px; + color: var(--muted); + line-height: 1.5; +} + +.history-header h2 { + margin: 0; +} + +.history-header p { + margin: 4px 0 0; + color: var(--muted-2); + font-size: .75rem; +} + +.revision__marker { + border-color: var(--owner, #8b7af4); + background: var(--owner, #8b7af4); +} + +.revision__meta { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 8px; +} + +.revision__meta strong { + font-size: .82rem; +} + +.revision__snippet { + margin: 8px 0 0; + color: var(--muted); + font-size: .76rem; + line-height: 1.45; +} + +.revision__preview { + max-height: 180px; + overflow: auto; + margin-top: 10px; + padding: 10px; + border: 1px solid var(--border); + border-radius: 7px; + background: #0b0e13; + color: #c9d0da; + font: .72rem/1.5 ui-monospace, monospace; + white-space: pre-wrap; +} + +.revision button+button { + margin-left: 12px; +} + +.mermaid { + overflow: auto; + padding: 12px; + border: 1px solid var(--border); + border-radius: 10px; + background: #0a0d12; +} + +@media (max-width: 720px) { + .line-gutter { + width: 48px; + padding-top: 18px; + font-size: 15px; + } + + .editor-shell textarea { + padding: 18px 12px; + font-size: 15px; + } + + .user-color-control { + display: none; + } +} + +.markdown-body img { + display: block; + max-width: 100%; + height: auto; + margin: 16px auto; + border-radius: 10px; +} + +.markdown-body a { + overflow-wrap: anywhere; +} + +.public-page { + min-height: 100vh; + background: var(--background); +} + +.public-header { + position: sticky; + top: 0; + z-index: 5; + display: flex; + align-items: center; + justify-content: space-between; + min-height: 64px; + padding: 0 max(20px, calc((100vw - 900px) / 2)); + border-bottom: 1px solid var(--border); + background: rgba(13, 16, 21, .92); + backdrop-filter: blur(12px); +} + +.public-document { + width: min(900px, calc(100% - 32px)); + margin: 0 auto; + padding: 56px 0 96px; +} + +.public-document>h1 { + margin: 0; + font-size: clamp(2rem, 6vw, 4rem); + line-height: 1.08; +} + +.public-meta { + margin: 12px 0 36px; + color: var(--muted); + font-size: .8rem; +} + +.public-content { + min-height: 240px; + padding: 32px; + border: 1px solid var(--border); + border-radius: 14px; + background: var(--surface); +} + +.public-content img { + cursor: zoom-in; +} + +@media (max-width: 600px) { + .public-document { + padding-top: 32px; + } + + .public-content { + padding: 20px; + } +} /* Layout safeguards and home footer */ -.app-header__main, .document-heading { min-width: 0; } -.document-url { overflow-wrap: anywhere; } -.home-page { display: flex; min-height: 100vh; flex-direction: column; } -.home-page .home-layout { flex: 1 0 auto; } -.home-footer { flex: 0 0 auto; width: min(1040px, calc(100% - 32px)); margin: auto auto 28px; padding-top: 24px; color: var(--muted-2); font-size: .76rem; text-align: center; } -.home-footer__inner { display: flex; align-items: center; justify-content: center; gap: 10px; flex-wrap: wrap; } -.home-footer__separator { color: var(--border-strong); } -.home-footer .text-button { font-size: inherit; text-decoration: underline; text-underline-offset: 3px; } -.home-footer a { color: var(--muted); text-decoration: none; } -.home-footer a:hover { color: white; } -.home-header { border-bottom-color: rgba(195, 91, 54, .28); background: linear-gradient(135deg, rgba(126, 48, 27, .28), rgba(51, 25, 18, .08) 58%, transparent); } -.home-brand { background: linear-gradient(110deg, #f0aa74 0%, #cf633d 48%, #8f3827 100%); -webkit-background-clip: text; background-clip: text; color: transparent; } -@media (max-width: 760px) { .home-footer { width: min(100% - 24px, 560px); margin-bottom: 20px; } } +.app-header__main, +.document-heading { + min-width: 0; +} + +.document-url { + overflow-wrap: anywhere; +} + +.home-page { + display: flex; + min-height: 100vh; + flex-direction: column; +} + +.home-page .home-layout { + flex: 1 0 auto; +} + +.home-footer { + flex: 0 0 auto; + width: min(1040px, calc(100% - 32px)); + margin: auto auto 28px; + padding-top: 24px; + color: var(--muted-2); + font-size: .76rem; + text-align: center; +} + +.home-footer__inner { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + flex-wrap: wrap; +} + +.home-footer__separator { + color: var(--border-strong); +} + +.home-footer .text-button { + font-size: inherit; + text-decoration: underline; + text-underline-offset: 3px; +} + +.home-footer a { + color: var(--muted); + text-decoration: none; +} + +.home-footer a:hover { + color: white; +} + +.home-header { + border-bottom-color: rgba(195, 91, 54, .28); + background: linear-gradient(135deg, rgba(126, 48, 27, .28), rgba(51, 25, 18, .08) 58%, transparent); +} + +.home-brand { + background: linear-gradient(110deg, #f0aa74 0%, #cf633d 48%, #8f3827 100%); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +@media (max-width: 760px) { + .home-footer { + width: min(100% - 24px, 560px); + margin-bottom: 20px; + } +} /* Show an author once at the start of each contiguous ownership block. */ -.line-gutter div { position: relative; } -.line-owner-label { position: absolute; left: 7px; top: 50%; max-width: 88px; overflow: hidden; padding: 2px 6px; border: 1px solid color-mix(in srgb, var(--owner) 65%, transparent); border-radius: 999px; background: color-mix(in srgb, var(--owner) 18%, #0d1015); color: #eef1f5; font: 600 10px/1.2 system-ui, sans-serif; text-overflow: ellipsis; white-space: nowrap; transform: translateY(-50%); } -@media (min-width: 721px) { .line-gutter { width: 132px; } } -@media (max-width: 720px) { .line-owner-label { display: none; } } +.line-gutter div { + position: relative; +} + +.line-owner-label { + position: absolute; + left: 7px; + top: 50%; + max-width: 88px; + overflow: hidden; + padding: 2px 6px; + border: 1px solid color-mix(in srgb, var(--owner) 65%, transparent); + border-radius: 999px; + background: color-mix(in srgb, var(--owner) 18%, #0d1015); + color: #eef1f5; + font: 600 10px/1.2 system-ui, sans-serif; + text-overflow: ellipsis; + white-space: nowrap; + transform: translateY(-50%); +} + +@media (min-width: 721px) { + .line-gutter { + width: 132px; + } +} + +@media (max-width: 720px) { + .line-owner-label { + display: none; + } +} /* Compact line numbers; author labels sit over the edited text, not in the gutter. */ -.line-gutter { width: 48px; } -.line-gutter div { position: static; } -.owner-labels { position: absolute; z-index: 2; top: 0; right: 12px; left: 48px; height: 100%; overflow: hidden; pointer-events: none; } -.owner-label { position: absolute; right: 8px; max-width: min(180px, 45%); overflow: hidden; padding: 2px 7px; border: 1px solid color-mix(in srgb, var(--owner) 62%, transparent); border-radius: 999px; background: color-mix(in srgb, var(--owner) 18%, #0d1015); color: #eef1f5; font: 600 10px/1.25 system-ui, sans-serif; opacity: .82; text-overflow: ellipsis; white-space: nowrap; } -.hide-line-numbers .owner-labels { left: 0; } -.line-owner-label { display: none !important; } -@media (min-width: 721px) { .line-gutter { width: 48px; } } -@media (max-width: 720px) { .line-gutter { width: 42px; } .owner-labels { left: 42px; right: 4px; } } +.line-gutter { + width: 48px; +} + +.line-gutter div { + position: static; +} + +.owner-labels { + position: absolute; + z-index: 2; + top: 0; + right: 12px; + left: 48px; + height: 100%; + overflow: hidden; + pointer-events: none; +} + +.owner-label { + position: absolute; + right: 8px; + max-width: min(180px, 45%); + overflow: hidden; + padding: 2px 7px; + border: 1px solid color-mix(in srgb, var(--owner) 62%, transparent); + border-radius: 999px; + background: color-mix(in srgb, var(--owner) 18%, #0d1015); + color: #eef1f5; + font: 600 10px/1.25 system-ui, sans-serif; + opacity: .82; + text-overflow: ellipsis; + white-space: nowrap; +} + +.hide-line-numbers .owner-labels { + left: 0; +} + +.line-owner-label { + display: none !important; +} + +@media (min-width: 721px) { + .line-gutter { + width: 48px; + } +} + +@media (max-width: 720px) { + .line-gutter { + width: 42px; + } + + .owner-labels { + left: 42px; + right: 4px; + } +} /* The rust gradient belongs only to the wordmark. */ -.home-header { background: transparent; } +.home-header { + background: transparent; +} /* Editor display preferences and author overlay */ -.editor-shell { position: relative; } -.owner-labels { z-index: 4; top: 0; bottom: 0; height: auto; } -.owner-line { position: absolute; right: 0; left: 0; height: var(--editor-line-height, 31px); border-left: 3px solid var(--owner); background: transparent; } -.owner-label { z-index: 1; transform: translateY(2px); } -.editor-controls { display: inline-flex; align-items: center; gap: 7px; } -.editor-controls label { display: inline-flex; align-items: center; gap: 4px; color: var(--muted); font-size: .72rem; } -.editor-controls select { width: auto; min-height: 30px; padding: 3px 24px 3px 7px; border-radius: 6px; font-size: .74rem; } -.editor-workspace-font-system textarea, .editor-workspace-font-system .preview { font-family: Inter, ui-sans-serif, system-ui, sans-serif; } -.editor-workspace-font-serif textarea, .editor-workspace-font-serif .preview { font-family: ui-serif, Georgia, Cambria, "Times New Roman", serif; } -.editor-workspace-font-arial textarea, .editor-workspace-font-arial .preview { font-family: Arial, Helvetica, sans-serif; } -.editor-workspace-font-georgia textarea, .editor-workspace-font-georgia .preview { font-family: Georgia, "Times New Roman", serif; } -.editor-workspace-font-mono textarea, .editor-workspace-font-mono .preview { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; } -.compact-editor .editor-toolbar { min-height: 42px; padding-top: 5px; padding-bottom: 5px; } -.compact-editor .column-label { min-height: 30px; font-size: .68rem; } -.compact-editor textarea, .compact-editor .preview { padding-top: 14px; padding-bottom: 14px; font-size: calc(var(--editor-font-size, 18px) - 2px); line-height: 1.45; } -.workspace textarea, .workspace .preview { font-size: var(--editor-font-size, 18px); } -@media (max-width: 900px) { .editor-controls { order: 3; width: 100%; } .editor-toolbar { flex-wrap: wrap; } } +.editor-shell { + position: relative; +} -.editor-shell textarea { white-space: pre; overflow: auto; overflow-wrap: normal; word-break: normal; } +.owner-labels { + z-index: 4; + top: 0; + bottom: 0; + height: auto; +} + +.owner-line { + position: absolute; + right: 0; + left: 0; + height: var(--editor-line-height, 31px); + border-left: 3px solid var(--owner); + background: transparent; +} + +.owner-label { + z-index: 1; + transform: translateY(2px); +} + +.editor-controls { + display: inline-flex; + align-items: center; + gap: 7px; +} + +.editor-controls label { + display: inline-flex; + align-items: center; + gap: 4px; + color: var(--muted); + font-size: .72rem; +} + +.editor-controls select { + width: auto; + min-height: 30px; + padding: 3px 24px 3px 7px; + border-radius: 6px; + font-size: .74rem; +} + +.editor-workspace-font-system textarea, +.editor-workspace-font-system .preview { + font-family: Inter, ui-sans-serif, system-ui, sans-serif; +} + +.editor-workspace-font-serif textarea, +.editor-workspace-font-serif .preview { + font-family: ui-serif, Georgia, Cambria, "Times New Roman", serif; +} + +.editor-workspace-font-arial textarea, +.editor-workspace-font-arial .preview { + font-family: Arial, Helvetica, sans-serif; +} + +.editor-workspace-font-georgia textarea, +.editor-workspace-font-georgia .preview { + font-family: Georgia, "Times New Roman", serif; +} + +.editor-workspace-font-mono textarea, +.editor-workspace-font-mono .preview { + font-family: ui-monospace, SFMono-Regular, Consolas, monospace; +} + +.compact-editor .editor-toolbar { + min-height: 42px; + padding-top: 5px; + padding-bottom: 5px; +} + +.compact-editor .column-label { + min-height: 30px; + font-size: .68rem; +} + +.compact-editor textarea, +.compact-editor .preview { + padding-top: 14px; + padding-bottom: 14px; + font-size: calc(var(--editor-font-size, 18px) - 2px); + line-height: 1.45; +} + +.workspace textarea, +.workspace .preview { + font-size: var(--editor-font-size, 18px); +} + +@media (max-width: 900px) { + .editor-controls { + order: 3; + width: 100%; + } + + .editor-toolbar { + flex-wrap: wrap; + } +} + +.editor-shell textarea { + white-space: pre; + overflow: auto; + overflow-wrap: normal; + word-break: normal; +} /* Keep line numbers exactly aligned with the logical text lines. */ -.line-gutter { box-sizing: border-box; font-size: var(--editor-font-size, 18px); line-height: normal; } -.line-gutter div { box-sizing: border-box; padding-right: 8px; line-height: inherit; } -.compact-editor .line-gutter { font-size: calc(var(--editor-font-size, 18px) - 2px); } +.line-gutter { + box-sizing: border-box; + font-size: var(--editor-font-size, 18px); + line-height: normal; +} + +.line-gutter div { + box-sizing: border-box; + padding-right: 8px; + line-height: inherit; +} + +.compact-editor .line-gutter { + font-size: calc(var(--editor-font-size, 18px) - 2px); +} /* Image crop/resize dialog. */ -.image-editor-dialog { width: min(920px, calc(100% - 28px)); max-width: none; padding: 0; border: 1px solid var(--border); border-radius: 14px; background: #11151b; color: #eef2f7; } -.image-editor-dialog::backdrop { background: rgba(4,6,9,.78); backdrop-filter: blur(4px); } -.image-editor-panel { display: grid; gap: 16px; padding: 18px; } -.image-editor-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; } -.image-editor-head h2 { margin: 0; } -.image-editor-head p { margin: 5px 0 0; color: var(--muted); } -.image-crop-stage { display: grid; place-items: center; min-height: 360px; max-height: 58vh; overflow: hidden; border: 1px solid var(--border); border-radius: 10px; background: #080b10; } -.image-crop-stage canvas { display: block; max-width: 100%; max-height: 58vh; cursor: grab; touch-action: none; } -.image-crop-stage canvas:active { cursor: grabbing; } -.image-editor-controls { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 12px; } -.image-editor-controls label { display: grid; gap: 6px; color: var(--muted); font-size: .78rem; } -.image-editor-controls select,.image-editor-controls input { width: 100%; } -.image-editor-actions { display: flex; justify-content: flex-end; gap: 10px; } -@media (max-width: 680px) { .image-editor-controls { grid-template-columns: 1fr; } .image-crop-stage { min-height: 260px; } } +.image-editor-dialog { + width: min(920px, calc(100% - 28px)); + max-width: none; + padding: 0; + border: 1px solid var(--border); + border-radius: 14px; + background: #11151b; + color: #eef2f7; +} + +.image-editor-dialog::backdrop { + background: rgba(4, 6, 9, .78); + backdrop-filter: blur(4px); +} + +.image-editor-panel { + display: grid; + gap: 16px; + padding: 18px; +} + +.image-editor-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 18px; +} + +.image-editor-head h2 { + margin: 0; +} + +.image-editor-head p { + margin: 5px 0 0; + color: var(--muted); +} + +.image-crop-stage { + display: grid; + place-items: center; + min-height: 360px; + max-height: 58vh; + overflow: hidden; + border: 1px solid var(--border); + border-radius: 10px; + background: #080b10; +} + +.image-crop-stage canvas { + display: block; + max-width: 100%; + max-height: 58vh; + cursor: grab; + touch-action: none; +} + +.image-crop-stage canvas:active { + cursor: grabbing; +} + +.image-editor-controls { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; +} + +.image-editor-controls label { + display: grid; + gap: 6px; + color: var(--muted); + font-size: .78rem; +} + +.image-editor-controls select, +.image-editor-controls input { + width: 100%; +} + +.image-editor-actions { + display: flex; + justify-content: flex-end; + gap: 10px; +} + +@media (max-width: 680px) { + .image-editor-controls { + grid-template-columns: 1fr; + } + + .image-crop-stage { + min-height: 260px; + } +} /* Wider published page workspace. */ -.public-header { padding-inline: max(20px, calc((100vw - 1180px) / 2)); } -.public-document { width: min(1180px, calc(100% - 32px)); } -.public-content { padding: clamp(24px, 4vw, 52px); } +.public-header { + padding-inline: max(20px, calc((100vw - 1180px) / 2)); +} + +.public-document { + width: min(1180px, calc(100% - 32px)); +} + +.public-content { + padding: clamp(24px, 4vw, 52px); +} /* Preview and attachment management */ -.preview { overscroll-behavior: contain; scrollbar-gutter: stable; } -.markdown-body img { display: block; width: auto; max-width: 100%; height: auto; object-fit: contain; border-radius: 8px; } -.workspace.view-preview .markdown-body img { max-height: calc(100vh - 210px); } -.note-card-wrap { position: relative; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); overflow: hidden; } -.note-card-wrap .note-card { border: 0; border-radius: 0; } -.note-card-title { display: flex; align-items: center; justify-content: space-between; gap: 10px; } -.protect-badge, .file-flag { display: inline-flex; padding: 3px 7px; border: 1px solid var(--border-strong); border-radius: 999px; color: var(--muted); font-size: .68rem; white-space: nowrap; } -.file-flag.detached { border-color: rgba(255,123,145,.45); color: var(--danger); } -.note-delete-button { width: 100%; min-height: 36px; border: 0; border-top: 1px solid var(--border); background: transparent; color: var(--danger); } -.danger-button { color: var(--danger); } -.dialog-check { display: flex; align-items: center; gap: 9px; color: var(--muted); font-size: .82rem; } -.dialog-check input { width: auto; min-height: auto; } -.footer-link { border: 0; background: transparent; color: var(--muted); padding: 0; font-size: inherit; text-decoration: underline; text-underline-offset: 2px; } -.files-dialog { overflow: hidden; } -.files-panel { grid-template-rows: auto minmax(0, 1fr); min-height: min(590px, calc(100vh - 28px)); max-height: calc(100vh - 28px); } -.files-list { min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; } -.files-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; } -.files-head h2, .files-head p { margin: 0; } -.files-head p { margin-top: 5px; color: var(--muted); font-size: .8rem; } -.files-list { display: grid; gap: 9px; overflow: auto; min-height: 80px; } -.file-row { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 12px; padding: 12px; border: 1px solid var(--border); border-radius: 9px; background: #0e1116; } -.file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 650; } -.file-meta { margin-top: 4px; color: var(--muted-2); font-size: .72rem; } -.file-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px; } -.file-actions button { min-height: 30px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface-2); color: var(--text); padding: 0 8px; font-size: .72rem; } -@media (max-width: 620px) { .file-row { grid-template-columns: 1fr; } .file-actions { justify-content: flex-start; } } +.preview { + overscroll-behavior: contain; + scrollbar-gutter: stable; +} + +.markdown-body img { + display: block; + width: auto; + max-width: 100%; + height: auto; + object-fit: contain; + border-radius: 8px; +} + +.workspace.view-preview .markdown-body img { + max-height: calc(100vh - 210px); +} + +.note-card-wrap { + position: relative; + border: 1px solid var(--border); + border-radius: 12px; + background: var(--surface); + overflow: hidden; +} + +.note-card-wrap .note-card { + border: 0; + border-radius: 0; +} + +.note-card-title { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.protect-badge, +.file-flag { + display: inline-flex; + padding: 3px 7px; + border: 1px solid var(--border-strong); + border-radius: 999px; + color: var(--muted); + font-size: .68rem; + white-space: nowrap; +} + +.file-flag.detached { + border-color: rgba(255, 123, 145, .45); + color: var(--danger); +} + +.note-delete-button { + width: 100%; + min-height: 36px; + border: 0; + border-top: 1px solid var(--border); + background: transparent; + color: var(--danger); +} + +.danger-button { + color: var(--danger); +} + +.dialog-check { + display: flex; + align-items: center; + gap: 9px; + color: var(--muted); + font-size: .82rem; +} + +.dialog-check input { + width: auto; + min-height: auto; +} + +.footer-link { + border: 0; + background: transparent; + color: var(--muted); + padding: 0; + font-size: inherit; + text-decoration: underline; + text-underline-offset: 2px; +} + +.files-dialog { + overflow: hidden; +} + +.files-panel { + grid-template-rows: auto minmax(0, 1fr); + min-height: min(590px, calc(100vh - 28px)); + max-height: calc(100vh - 28px); +} + +.files-list { + min-height: 0; + overflow-y: auto; + overscroll-behavior: contain; + scrollbar-gutter: stable; +} + +.files-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 18px; +} + +.files-head h2, +.files-head p { + margin: 0; +} + +.files-head p { + margin-top: 5px; + color: var(--muted); + font-size: .8rem; +} + +.files-list { + display: grid; + gap: 9px; + overflow: auto; + min-height: 80px; +} + +.file-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 12px; + padding: 12px; + border: 1px solid var(--border); + border-radius: 9px; + background: #0e1116; +} + +.file-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 650; +} + +.file-meta { + margin-top: 4px; + color: var(--muted-2); + font-size: .72rem; +} + +.file-actions { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 6px; +} + +.file-actions button { + min-height: 30px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--surface-2); + color: var(--text); + padding: 0 8px; + font-size: .72rem; +} + +@media (max-width: 620px) { + .file-row { + grid-template-columns: 1fr; + } + + .file-actions { + justify-content: flex-start; + } +} + +.files-panel { + min-height: 0; + height: min(590px, calc(100vh - 28px)); +} + +.files-list { + align-content: start; +} + +.file-row { + align-items: start; +} + +.file-row-main { + min-width: 0; +} + +.file-actions { + align-items: center; + align-self: start; + flex-wrap: nowrap; +} + +.file-actions button { + min-height: 34px; + height: 34px; + white-space: nowrap; +} + +.file-code { + grid-column: 1 / -1; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 8px; + align-items: stretch; +} + +.file-code[hidden] { + display: none; +} + +.file-code textarea { + width: 100%; + min-height: 76px; + max-height: 150px; + resize: vertical; + box-sizing: border-box; + font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; +} + +.file-code button { + align-self: stretch; + min-width: 74px; +} + +.file-delete { + color: var(--danger) !important; + border-color: color-mix(in srgb, var(--danger) 45%, var(--border)) !important; +} -.files-panel { min-height: 0; height: min(590px, calc(100vh - 28px)); } -.files-list { align-content: start; } -.file-row { align-items: start; } -.file-row-main { min-width: 0; } -.file-actions { align-items: center; align-self: start; flex-wrap: nowrap; } -.file-actions button { min-height: 34px; height: 34px; white-space: nowrap; } -.file-code { grid-column: 1 / -1; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: stretch; } -.file-code[hidden] { display: none; } -.file-code textarea { width: 100%; min-height: 76px; max-height: 150px; resize: vertical; box-sizing: border-box; font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; } -.file-code button { align-self: stretch; min-width: 74px; } -.file-delete { color: var(--danger) !important; border-color: color-mix(in srgb, var(--danger) 45%, var(--border)) !important; } @media (max-width: 760px) { - .file-actions { flex-wrap: wrap; justify-content: flex-start; } - .file-code { grid-template-columns: 1fr; } + .file-actions { + flex-wrap: wrap; + justify-content: flex-start; + } + + .file-code { + grid-template-columns: 1fr; + } +} + +.notes-toolbar { + display: flex; + align-items: center; + gap: 10px; + margin-top: 18px; + padding-bottom: 14px; + border-bottom: 1px solid var(--border); +} + +.notes-toolbar__label { + color: var(--muted); + font-size: .82rem; + font-weight: 600; +} + +.workspace-actions { + display: flex; + align-items: center; + gap: 10px; +} + +.notes-view-switch { + display: inline-flex; + padding: 3px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--surface); +} + +.notes-view-switch button { + min-height: 32px; + padding: 0 11px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--muted); +} + +.notes-view-switch button.active { + background: var(--surface-3); + color: var(--text); +} + +.note-card { + display: block; + min-width: 0; +} + +.note-card-title { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 10px; + min-width: 0; +} + +.note-card-title h3 { + min-width: 0; + overflow-wrap: anywhere; + word-break: break-word; + line-height: 1.4; +} + +.note-card p { + overflow-wrap: anywhere; +} + +.notes-table { + display: block; + padding-top: 20px; +} + +.notes-table-scroll { + width: 100%; + overflow-x: auto; + border: 1px solid var(--border); + border-radius: 10px; + background: var(--surface); +} + +.notes-table table { + width: 100%; + min-width: 680px; + border-collapse: collapse; +} + +.notes-table th, +.notes-table td { + padding: 13px 15px; + border-bottom: 1px solid var(--border); + text-align: left; + vertical-align: middle; +} + +.notes-table th { + color: var(--muted); + font-size: .72rem; + text-transform: uppercase; + letter-spacing: .05em; +} + +.notes-table tbody tr:last-child td { + border-bottom: 0; +} + +.notes-table tbody tr:hover { + background: var(--surface-2); +} + +.note-table-link { + display: block; + max-width: 52ch; + color: var(--text); + font-weight: 650; + text-decoration: none; + overflow-wrap: anywhere; +} + +.note-table-link:hover { + text-decoration: underline; +} + +.note-status { + color: var(--muted); + font-size: .75rem; +} + +.notes-table-actions { + width: 1%; + white-space: nowrap; + text-align: right !important; +} + +.note-delete-button--inline { + width: auto; + min-height: 32px; + padding: 0 10px; + border: 1px solid var(--border); + border-radius: 6px; } -.notes-toolbar { display: flex; align-items: center; gap: 10px; margin-top: 18px; padding-bottom: 14px; border-bottom: 1px solid var(--border); } -.notes-toolbar__label { color: var(--muted); font-size: .82rem; font-weight: 600; } -.workspace-actions { display: flex; align-items: center; gap: 10px; } -.notes-view-switch { display: inline-flex; padding: 3px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); } -.notes-view-switch button { min-height: 32px; padding: 0 11px; border: 0; border-radius: 6px; background: transparent; color: var(--muted); } -.notes-view-switch button.active { background: var(--surface-3); color: var(--text); } -.note-card { display: block; min-width: 0; } -.note-card-title { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; min-width: 0; } -.note-card-title h3 { min-width: 0; overflow-wrap: anywhere; word-break: break-word; line-height: 1.4; } -.note-card p { overflow-wrap: anywhere; } -.notes-table { display: block; padding-top: 20px; } -.notes-table-scroll { width: 100%; overflow-x: auto; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); } -.notes-table table { width: 100%; min-width: 680px; border-collapse: collapse; } -.notes-table th, .notes-table td { padding: 13px 15px; border-bottom: 1px solid var(--border); text-align: left; vertical-align: middle; } -.notes-table th { color: var(--muted); font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; } -.notes-table tbody tr:last-child td { border-bottom: 0; } -.notes-table tbody tr:hover { background: var(--surface-2); } -.note-table-link { display: block; max-width: 52ch; color: var(--text); font-weight: 650; text-decoration: none; overflow-wrap: anywhere; } -.note-table-link:hover { text-decoration: underline; } -.note-status { color: var(--muted); font-size: .75rem; } -.notes-table-actions { width: 1%; white-space: nowrap; text-align: right !important; } -.note-delete-button--inline { width: auto; min-height: 32px; padding: 0 10px; border: 1px solid var(--border); border-radius: 6px; } @media (max-width: 700px) { - .workspace-top { align-items: flex-start; } - .workspace-actions { align-items: stretch; flex-direction: column-reverse; } - .notes-view-switch button { flex: 1; } + .workspace-top { + align-items: flex-start; + } + + .workspace-actions { + align-items: stretch; + flex-direction: column-reverse; + } + + .notes-view-switch button { + flex: 1; + } } -.note-card-meta { display: grid; gap: 5px; margin-top: 28px; color: var(--muted); font-size: .75rem; } -.note-card p { margin-top: 0; } -.note-author { color: var(--muted); overflow-wrap: anywhere; } -.note-delete-button:disabled { cursor: not-allowed; color: var(--muted-2); opacity: .55; } -.notes-view-switch button { cursor: pointer; } -.notes-view-switch button.active { cursor: default; } +.note-card-meta { + display: grid; + gap: 5px; + margin-top: 28px; + color: var(--muted); + font-size: .75rem; +} + +.note-card p { + margin-top: 0; +} + +.note-author { + color: var(--muted); + overflow-wrap: anywhere; +} + +.note-delete-button:disabled { + cursor: not-allowed; + color: var(--muted-2); + opacity: .55; +} + +.notes-view-switch button { + cursor: pointer; +} + +.notes-view-switch button.active { + cursor: default; +} /* Preview editing and source line numbers. */ -.preview { padding: 12px 24px 12px 62px; line-height: 1.32; } -.preview-source-line { position: relative; min-height: 1.32em; } -.preview-source-line::before { content: attr(data-source-line); position: absolute; right: calc(100% + 18px); width: 32px; color: #596270; text-align: right; font: 400 .72rem/1.32 ui-monospace, SFMono-Regular, Consolas, monospace; user-select: none; } -.preview-editable { border-radius: 4px; outline: none; cursor: text; } -.preview-editable:hover { background: rgba(255,255,255,.025); } -.preview-editable:focus { background: rgba(124,104,238,.08); box-shadow: 0 0 0 1px rgba(124,104,238,.25); } -.markdown-body p { margin: .18em 0; } -.markdown-body h1, .markdown-body h2, .markdown-body h3 { margin: .48em 0 .18em; } -.markdown-body blockquote { margin: .3em 0; } -.markdown-body ul, .markdown-body ol { margin: .22em 0; } -.compact-editor .preview { line-height: 1.24; } -.hide-line-numbers .preview { padding-left: 24px; } -.hide-line-numbers .preview-source-line::before { display: none; } - - - -.markdown-body .table-wrap { overflow-x: auto; margin: .7em 0; } -.markdown-body table { width: 100%; border-collapse: collapse; } -.markdown-body th, .markdown-body td { padding: .55em .7em; border: 1px solid var(--border); vertical-align: top; } -.markdown-body th { background: var(--surface-2); color: var(--text); } -.markdown-body mark { padding: .05em .18em; border-radius: 3px; background: #6d5b16; color: #fff2a8; } -.markdown-body sub, .markdown-body sup { line-height: 0; } -.markdown-body dl { margin: .7em 0; } -.markdown-body dt { font-weight: 750; } -.markdown-body dd { margin: .25em 0 .65em 1.5em; color: #b9c2cf; } -.markdown-body .task-list{margin:0;padding:0;list-style:none} -.markdown-body .task-list-item{position:relative;display:grid;grid-template-columns:1em minmax(0,1fr);grid-template-rows:1.32em;column-gap:.45em;align-items:center;min-height:1.32em;margin:0;padding:0;line-height:1.32} -.markdown-body .task-list-item::before{position:absolute;top:0;right:calc(100% + 18px);width:32px;height:1.32em;line-height:1.32em;transform:none} -.markdown-body .task-checkbox{grid-column:1;grid-row:1;width:1em;height:1em;margin:0;align-self:center;accent-color:var(--accent);cursor:pointer} -.markdown-body .task-list-item>span{grid-column:2;grid-row:1;display:block;min-width:0;margin:0;padding:0;line-height:1.32} -.hide-line-numbers .markdown-body .task-list-item::before{display:none} -.compact-editor .markdown-body .task-list-item{grid-template-rows:1.24em;min-height:1.24em;line-height:1.24} -.compact-editor .markdown-body .task-list-item::before,.compact-editor .markdown-body .task-list-item>span{height:1.24em;line-height:1.24} -.markdown-body .footnotes { margin-top: 1.5em; color: var(--muted); font-size: .88em; } -.markdown-body .footnotes ol { padding-left: 1.5em; } -.markdown-body .footnote-backref { text-decoration: none; } -.shortcuts-panel { width: 100%; } -.shortcut-grid { display: grid; grid-template-columns: max-content 1fr; gap: 10px 18px; align-items: center; } -.shortcut-grid kbd { padding: 5px 8px; border: 1px solid var(--border-strong); border-bottom-width: 2px; border-radius: 6px; background: #0d1015; color: #e6eaf0; font: 600 .76rem/1.2 ui-monospace, SFMono-Regular, Consolas, monospace; } -.shortcut-grid span { color: #c3cad4; font-size: .82rem; } -@media (max-width: 520px) { - .shortcut-grid { grid-template-columns: 1fr; gap: 5px; } - .shortcut-grid span { margin-bottom: 7px; } +.preview { + padding: 12px 24px 12px 62px; + line-height: 1.32; } -.footer-left, .footer-right { display:flex; align-items:center; gap:6px; min-width:0; } -.footer-status.status { min-height:0; padding:0; font-size:inherit; } -.markdown-more { position:relative; } -.markdown-more > summary { display:flex; align-items:center; min-height:34px; padding:0 9px; border:1px solid transparent; border-radius:7px; color:#b8c0cc; font-size:.78rem; list-style:none; cursor:pointer; } -.markdown-more > summary::-webkit-details-marker { display:none; } -.markdown-more > summary:hover, .markdown-more[open] > summary { border-color:var(--border); background:var(--surface-2); color:white; } -.markdown-more-menu { position:absolute; top:calc(100% + 8px); left:0; z-index:30; display:grid; grid-template-columns:repeat(2,minmax(120px,1fr)); gap:4px; width:290px; padding:8px; border:1px solid var(--border-strong); border-radius:9px; background:#11151c; box-shadow:0 14px 40px rgba(0,0,0,.4); } -.editor-toolbar .markdown-more-menu button { justify-content:flex-start; text-align:left; } -.hljs { color:#d7dae0; } -.hljs-keyword,.hljs-selector-tag,.hljs-literal { color:#c792ea; } -.hljs-string,.hljs-attr { color:#c3e88d; } -.hljs-number,.hljs-symbol { color:#f78c6c; } -.hljs-comment { color:#697383; font-style:italic; } -.public-content .task-checkbox { pointer-events:auto; } -@media (max-width:720px){.footer-left,.footer-right{flex-wrap:wrap}.markdown-more-menu{position:fixed;left:12px;right:12px;top:auto;bottom:58px;width:auto;}} -.public-task-toggle { display:inline-flex; align-items:center; gap:7px; color:var(--muted); font-size:.76rem; white-space:nowrap; } -.public-task-toggle input { width:15px; min-height:15px; height:15px; margin:0; accent-color:var(--accent); } -.public-page .preview-source-line { cursor:default; } -.public-page .task-checkbox:not(:disabled) { cursor:pointer; } -.public-page .task-checkbox:disabled { cursor:not-allowed; opacity:.55; } +.preview-source-line { + position: relative; + min-height: 1.32em; +} -.identity-actions, .identity-links { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; } -.identity-actions .primary-button { flex: 1 0 100%; } -.identity-panel { position: relative; } -.identity-panel__close { position: absolute; top: 14px; right: 14px; } -.identity-links { justify-content: space-between; } -.identity-links .text-button { padding: 3px 0; } -.auth-panel { display: grid; gap: 12px; padding-top: 14px; border-top: 1px solid var(--border); } -.auth-panel h3 { margin: 0; } -.auth-panel label { display: grid; gap: 6px; } +.preview-source-line::before { + content: attr(data-source-line); + position: absolute; + right: calc(100% + 18px); + width: 32px; + color: #596270; + text-align: right; + font: 400 .72rem/1.32 ui-monospace, SFMono-Regular, Consolas, monospace; + user-select: none; +} + +.preview-editable { + border-radius: 4px; + outline: none; + cursor: text; +} + +.preview-editable:hover { + background: rgba(255, 255, 255, .025); +} + +.preview-editable:focus { + background: rgba(124, 104, 238, .08); + box-shadow: 0 0 0 1px rgba(124, 104, 238, .25); +} + +.markdown-body p { + margin: .18em 0; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3 { + margin: .48em 0 .18em; +} + +.markdown-body blockquote { + margin: .3em 0; +} + +.markdown-body ul, +.markdown-body ol { + margin: .22em 0; +} + +.compact-editor .preview { + line-height: 1.24; +} + +.hide-line-numbers .preview { + padding-left: 24px; +} + +.hide-line-numbers .preview-source-line::before { + display: none; +} + + + +.markdown-body .table-wrap { + overflow-x: auto; + margin: .7em 0; +} + +.markdown-body table { + width: 100%; + border-collapse: collapse; +} + +.markdown-body th, +.markdown-body td { + padding: .55em .7em; + border: 1px solid var(--border); + vertical-align: top; +} + +.markdown-body th { + background: var(--surface-2); + color: var(--text); +} + +.markdown-body mark { + padding: .05em .18em; + border-radius: 3px; + background: #6d5b16; + color: #fff2a8; +} + +.markdown-body sub, +.markdown-body sup { + line-height: 0; +} + +.markdown-body dl { + margin: .7em 0; +} + +.markdown-body dt { + font-weight: 750; +} + +.markdown-body dd { + margin: .25em 0 .65em 1.5em; + color: #b9c2cf; +} + +.markdown-body .task-list { + margin: 0; + padding: 0; + list-style: none +} + +.markdown-body .task-list-item { + position: relative; + display: grid; + grid-template-columns: 1em minmax(0, 1fr); + grid-template-rows: 1.32em; + column-gap: .45em; + align-items: center; + min-height: 1.32em; + margin: 0; + padding: 0; + line-height: 1.32 +} + +.markdown-body .task-list-item::before { + position: absolute; + top: 0; + right: calc(100% + 18px); + width: 32px; + height: 1.32em; + line-height: 1.32em; + transform: none +} + +.markdown-body .task-checkbox { + grid-column: 1; + grid-row: 1; + width: 1em; + height: 1em; + margin: 0; + align-self: center; + accent-color: var(--accent); + cursor: pointer +} + +.markdown-body .task-list-item>span { + grid-column: 2; + grid-row: 1; + display: block; + min-width: 0; + margin: 0; + padding: 0; + line-height: 1.32 +} + +.hide-line-numbers .markdown-body .task-list-item::before { + display: none +} + +.compact-editor .markdown-body .task-list-item { + grid-template-rows: 1.24em; + min-height: 1.24em; + line-height: 1.24 +} + +.compact-editor .markdown-body .task-list-item::before, +.compact-editor .markdown-body .task-list-item>span { + height: 1.24em; + line-height: 1.24 +} + +.markdown-body .footnotes { + margin-top: 1.5em; + color: var(--muted); + font-size: .88em; +} + +.markdown-body .footnotes ol { + padding-left: 1.5em; +} + +.markdown-body .footnote-backref { + text-decoration: none; +} + +.shortcuts-panel { + width: 100%; +} + +.shortcut-grid { + display: grid; + grid-template-columns: max-content 1fr; + gap: 10px 18px; + align-items: center; +} + +.shortcut-grid kbd { + padding: 5px 8px; + border: 1px solid var(--border-strong); + border-bottom-width: 2px; + border-radius: 6px; + background: #0d1015; + color: #e6eaf0; + font: 600 .76rem/1.2 ui-monospace, SFMono-Regular, Consolas, monospace; +} + +.shortcut-grid span { + color: #c3cad4; + font-size: .82rem; +} + +@media (max-width: 520px) { + .shortcut-grid { + grid-template-columns: 1fr; + gap: 5px; + } + + .shortcut-grid span { + margin-bottom: 7px; + } +} + +.footer-left, +.footer-right { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; +} + +.footer-status.status { + min-height: 0; + padding: 0; + font-size: inherit; +} + +.markdown-more { + position: relative; +} + +.markdown-more>summary { + display: flex; + align-items: center; + min-height: 34px; + padding: 0 9px; + border: 1px solid transparent; + border-radius: 7px; + color: #b8c0cc; + font-size: .78rem; + list-style: none; + cursor: pointer; +} + +.markdown-more>summary::-webkit-details-marker { + display: none; +} + +.markdown-more>summary:hover, +.markdown-more[open]>summary { + border-color: var(--border); + background: var(--surface-2); + color: white; +} + +.markdown-more-menu { + position: absolute; + top: calc(100% + 8px); + left: 0; + z-index: 30; + display: grid; + grid-template-columns: repeat(2, minmax(120px, 1fr)); + gap: 4px; + width: 290px; + padding: 8px; + border: 1px solid var(--border-strong); + border-radius: 9px; + background: #11151c; + box-shadow: 0 14px 40px rgba(0, 0, 0, .4); +} + +.editor-toolbar .markdown-more-menu button { + justify-content: flex-start; + text-align: left; +} + +.hljs { + color: #d7dae0; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal { + color: #c792ea; +} + +.hljs-string, +.hljs-attr { + color: #c3e88d; +} + +.hljs-number, +.hljs-symbol { + color: #f78c6c; +} + +.hljs-comment { + color: #697383; + font-style: italic; +} + +.public-content .task-checkbox { + pointer-events: auto; +} + +@media (max-width:720px) { + + .footer-left, + .footer-right { + flex-wrap: wrap + } + + .markdown-more-menu { + position: fixed; + left: 12px; + right: 12px; + top: auto; + bottom: 58px; + width: auto; + } +} + +.public-task-toggle { + display: inline-flex; + align-items: center; + gap: 7px; + color: var(--muted); + font-size: .76rem; + white-space: nowrap; +} + +.public-task-toggle input { + width: 15px; + min-height: 15px; + height: 15px; + margin: 0; + accent-color: var(--accent); +} + +.public-page .preview-source-line { + cursor: default; +} + +.public-page .task-checkbox:not(:disabled) { + cursor: pointer; +} + +.public-page .task-checkbox:disabled { + cursor: not-allowed; + opacity: .55; +} + +.identity-actions, +.identity-links { + display: flex; + gap: 10px; + align-items: center; + flex-wrap: wrap; +} + +.identity-actions .primary-button { + flex: 1 0 100%; +} + +.identity-panel { + position: relative; +} + +.identity-panel__close { + position: absolute; + top: 14px; + right: 14px; +} + +.identity-links { + justify-content: space-between; +} + +.identity-links .text-button { + padding: 3px 0; +} + +.auth-panel { + display: grid; + gap: 12px; + padding-top: 14px; + border-top: 1px solid var(--border); +} + +.auth-panel h3 { + margin: 0; +} + +.auth-panel label { + display: grid; + gap: 6px; +} /* Account footer and authentication dialog. */ -.home-footer { width: min(1040px, calc(100% - 32px)); margin: auto auto 24px; padding-top: 24px; color: var(--muted); font-size: .78rem; } -.home-footer__inner { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding-top: 18px; border-top: 1px solid var(--border); } -.home-footer__account { display: inline-flex; align-items: center; gap: 8px; } -.home-footer__user { color: var(--text); font-weight: 600; } -.home-footer__author { margin-left: auto; } -.footer-action { min-height: 34px; padding: 0 12px; border: 1px solid var(--border); border-radius: 8px; background: transparent; color: var(--muted); font: inherit; cursor: pointer; } -.footer-action:hover { border-color: var(--border-strong); background: var(--surface-2); color: var(--text); } -.footer-action--primary { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); color: #d9d2ff; } -.app-dialog { width: min(440px, calc(100% - 28px)); padding: 0; border: 0; background: transparent; } -.app-dialog::backdrop { background: rgba(4, 6, 10, .72); backdrop-filter: blur(3px); } -.identity-panel { position: relative; display: grid; gap: 18px; width: 100%; padding: 28px; } -.identity-panel__header { padding-right: 38px; } -.identity-panel__header h2 { margin: 0 0 7px; } -.identity-panel__header p { margin: 0; } -.identity-fields { display: grid; gap: 14px; } -.identity-fields label { display: grid; gap: 7px; color: var(--muted); font-size: .78rem; } -.modal-close { position: absolute; top: 12px; right: 12px; display: grid; width: 34px; height: 34px; padding: 0; place-items: center; border: 1px solid transparent; border-radius: 8px; background: transparent; color: var(--muted); font: 400 1.45rem/1 system-ui; cursor: pointer; } -.modal-close:hover { border-color: var(--border); background: var(--surface-2); color: white; } -.identity-links { display: flex; align-items: center; justify-content: space-between; gap: 12px; } -.identity-links .text-button { padding: 2px 0; } -.form-message.success { color: #8ed9a4; } -@media (max-width: 600px) { - .home-footer__inner { align-items: stretch; flex-direction: column; text-align: center; } - .home-footer__account { justify-content: center; } - .home-footer__author { margin-left: 0; } - .identity-panel { padding: 24px 20px 20px; } - .identity-links { align-items: flex-start; flex-direction: column; } +.home-footer { + width: min(1040px, calc(100% - 32px)); + margin: auto auto 24px; + padding-top: 24px; + color: var(--muted); + font-size: .78rem; } -.home-footer__account[hidden], .footer-action[hidden], .identity-links [hidden], .identity-fields [hidden], .auth-panel[hidden] { display: none !important; } -.system-dialog-panel { position: relative; } -.dialog-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 4px; } -.secondary-button, .danger-button { min-height: 42px; padding: 0 16px; border: 1px solid var(--border); border-radius: 9px; background: var(--surface-2); color: var(--text); font: inherit; cursor: pointer; } -.secondary-button:hover { border-color: var(--muted); } -.danger-button { border-color: #7f3340; background: #6e2935; color: white; } -.danger-button:hover { background: #7c3040; } +.home-footer__inner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding-top: 18px; + border-top: 1px solid var(--border); +} + +.home-footer__account { + display: inline-flex; + align-items: center; + gap: 8px; +} + +.home-footer__user { + color: var(--text); + font-weight: 600; +} + +.home-footer__author { + margin-left: auto; +} + +.footer-action { + min-height: 34px; + padding: 0 12px; + border: 1px solid var(--border); + border-radius: 8px; + background: transparent; + color: var(--muted); + font: inherit; + cursor: pointer; +} + +.footer-action:hover { + border-color: var(--border-strong); + background: var(--surface-2); + color: var(--text); +} + +.footer-action--primary { + border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); + color: #d9d2ff; +} + +.app-dialog { + width: min(440px, calc(100% - 28px)); + padding: 0; + border: 0; + background: transparent; +} + +.app-dialog::backdrop { + background: rgba(4, 6, 10, .72); + backdrop-filter: blur(3px); +} + +.identity-panel { + position: relative; + display: grid; + gap: 18px; + width: 100%; + padding: 28px; +} + +.identity-panel__header { + padding-right: 38px; +} + +.identity-panel__header h2 { + margin: 0 0 7px; +} + +.identity-panel__header p { + margin: 0; +} + +.identity-fields { + display: grid; + gap: 14px; +} + +.identity-fields label { + display: grid; + gap: 7px; + color: var(--muted); + font-size: .78rem; +} + +.modal-close { + position: absolute; + top: 12px; + right: 12px; + display: grid; + width: 34px; + height: 34px; + padding: 0; + place-items: center; + border: 1px solid transparent; + border-radius: 8px; + background: transparent; + color: var(--muted); + font: 400 1.45rem/1 system-ui; + cursor: pointer; +} + +.modal-close:hover { + border-color: var(--border); + background: var(--surface-2); + color: white; +} + +.identity-links { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.identity-links .text-button { + padding: 2px 0; +} + +.form-message.success { + color: #8ed9a4; +} + +@media (max-width: 600px) { + .home-footer__inner { + align-items: stretch; + flex-direction: column; + text-align: center; + } + + .home-footer__account { + justify-content: center; + } + + .home-footer__author { + margin-left: 0; + } + + .identity-panel { + padding: 24px 20px 20px; + } + + .identity-links { + align-items: flex-start; + flex-direction: column; + } +} + +.home-footer__account[hidden], +.footer-action[hidden], +.identity-links [hidden], +.identity-fields [hidden], +.auth-panel[hidden] { + display: none !important; +} + +.system-dialog-panel { + position: relative; +} + +.dialog-actions { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 4px; +} + +.secondary-button, +.danger-button { + min-height: 42px; + padding: 0 16px; + border: 1px solid var(--border); + border-radius: 9px; + background: var(--surface-2); + color: var(--text); + font: inherit; + cursor: pointer; +} + +.secondary-button:hover { + border-color: var(--muted); +} + +.danger-button { + border-color: #7f3340; + background: #6e2935; + color: white; +} + +.danger-button:hover { + background: #7c3040; +} /* Native hidden must win over component display declarations. */ -[hidden] { display: none !important; } -.identity-panel #auth-panel:not([hidden]) { margin-top: 4px; } -.identity-panel #nickname:disabled { opacity: .55; cursor: not-allowed; } +[hidden] { + display: none !important; +} -#resources-dialog { width: min(760px, calc(100% - 28px)); max-width: 760px; } -.resources-panel { position: relative; box-sizing: border-box; width: 100%; min-width: 0; max-height: 80vh; overflow-x: hidden; overflow-y: auto; } -.resources-panel__header { min-width: 0; padding-right: 38px; } -.resources-panel__header h2 { margin: 0 0 7px; overflow-wrap: anywhere; } -.resources-panel__header p { margin: 0; overflow-wrap: anywhere; } -.resources-list { display: grid; min-width: 0; gap: 10px; margin-top: 18px; } -.resource-row { display:flex; min-width:0; justify-content:space-between; gap:16px; align-items:center; padding:12px; border:1px solid var(--border); border-radius:10px; } -.resource-row > :first-child { min-width: 0; } -.resource-row a { display: block; max-width: 100%; font-weight:700; overflow-wrap:anywhere; word-break:break-word; } -.resource-row small { display:block; max-width:100%; margin-top:4px; color:var(--muted-2); overflow-wrap:anywhere; } -.resource-actions { display:flex; flex:0 0 auto; gap:8px; flex-wrap:wrap; } -.resource-actions button { padding:7px 10px; } -@media (max-width:640px){.resource-row{align-items:flex-start;flex-direction:column}.resource-actions{width:100%}} +.identity-panel #auth-panel:not([hidden]) { + margin-top: 4px; +} -.resource-main { display: flex; align-items: center; justify-content: space-between; gap: 16px; width: 100%; } -.resource-inline { width: 100%; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); } -.resource-password-form label { display: grid; gap: 6px; font-size: .8rem; } -.resource-password-form input { width: 100%; } -.resource-inline-help { margin: 6px 0 0; color: var(--muted); font-size: .75rem; } -.resource-inline-message { margin: 8px 0 0; } -.resource-inline-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px; } -.resource-delete-confirm > p:first-child { margin: 0; } -@media (max-width: 640px) { .resource-main { align-items: flex-start; flex-direction: column; } .resource-actions { width: 100%; } } -.resource-row { align-items: stretch; flex-direction: column; } +.identity-panel #nickname:disabled { + opacity: .55; + cursor: not-allowed; +} + +#resources-dialog { + width: min(760px, calc(100% - 28px)); + max-width: 760px; +} + +.resources-panel { + position: relative; + box-sizing: border-box; + width: 100%; + min-width: 0; + max-height: 80vh; + overflow-x: hidden; + overflow-y: auto; +} + +.resources-panel__header { + min-width: 0; + padding-right: 38px; +} + +.resources-panel__header h2 { + margin: 0 0 7px; + overflow-wrap: anywhere; +} + +.resources-panel__header p { + margin: 0; + overflow-wrap: anywhere; +} + +.resources-list { + display: grid; + min-width: 0; + gap: 10px; + margin-top: 18px; +} + +.resource-row { + display: flex; + min-width: 0; + justify-content: space-between; + gap: 16px; + align-items: center; + padding: 12px; + border: 1px solid var(--border); + border-radius: 10px; +} + +.resource-row> :first-child { + min-width: 0; +} + +.resource-row a { + display: block; + max-width: 100%; + font-weight: 700; + overflow-wrap: anywhere; + word-break: break-word; +} + +.resource-row small { + display: block; + max-width: 100%; + margin-top: 4px; + color: var(--muted-2); + overflow-wrap: anywhere; +} + +.resource-actions { + display: flex; + flex: 0 0 auto; + gap: 8px; + flex-wrap: wrap; +} + +.resource-actions button { + padding: 7px 10px; +} + +@media (max-width:640px) { + .resource-row { + align-items: flex-start; + flex-direction: column + } + + .resource-actions { + width: 100% + } +} + +.resource-main { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + width: 100%; +} + +.resource-inline { + width: 100%; + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid var(--border); +} + +.resource-password-form label { + display: grid; + gap: 6px; + font-size: .8rem; +} + +.resource-password-form input { + width: 100%; +} + +.resource-inline-help { + margin: 6px 0 0; + color: var(--muted); + font-size: .75rem; +} + +.resource-inline-message { + margin: 8px 0 0; +} + +.resource-inline-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 10px; +} + +.resource-delete-confirm>p:first-child { + margin: 0; +} + +@media (max-width: 640px) { + .resource-main { + align-items: flex-start; + flex-direction: column; + } + + .resource-actions { + width: 100%; + } +} + +.resource-row { + align-items: stretch; + flex-direction: column; +} /* Stable scrolling and collapsible Markdown sections. */ -.editor-shell { width: 100%; max-width: 100%; } -.editor-shell textarea { min-width: 0; max-width: 100%; overscroll-behavior: contain; overflow-anchor: none; } -.line-gutter { position: relative; z-index: 5; flex: none; } -.preview, .editor-shell textarea { scrollbar-gutter: stable; } -.markdown-details { margin: .65em 0; border: 1px solid var(--border); border-radius: 8px; background: rgba(255,255,255,.015); } -.markdown-details > summary { padding: .55em .8em; color: var(--text); font-weight: 650; cursor: pointer; user-select: none; } -.markdown-details[open] > summary { border-bottom: 1px solid var(--border); } -.markdown-details__content { padding: .35em .8em .7em; } -.markdown-details__content > :first-child { margin-top: 0; } -.markdown-details__content > :last-child { margin-bottom: 0; } +.editor-shell { + width: 100%; + max-width: 100%; +} + +.editor-shell textarea { + min-width: 0; + max-width: 100%; + overscroll-behavior: contain; + overflow-anchor: none; +} + +.line-gutter { + position: relative; + z-index: 5; + flex: none; +} + +.preview, +.editor-shell textarea { + scrollbar-gutter: stable; +} + +.markdown-details { + margin: .65em 0; + border: 1px solid var(--border); + border-radius: 8px; + background: rgba(255, 255, 255, .015); +} + +.markdown-details>summary { + padding: .55em .8em; + color: var(--text); + font-weight: 650; + cursor: pointer; + user-select: none; +} + +.markdown-details[open]>summary { + border-bottom: 1px solid var(--border); +} + +.markdown-details__content { + padding: .35em .8em .7em; +} + +.markdown-details__content> :first-child { + margin-top: 0; +} + +.markdown-details__content> :last-child { + margin-bottom: 0; +} /* Full-height editor: keep header/footer visible and scroll only the work area. */ -.pad-page { height: 100dvh; min-height: 0; overflow: hidden; display: grid; grid-template-rows: auto minmax(0, 1fr); } -.pad-page .app-header { min-width: 0; } -.pad-page .editor-layout { height: auto; min-height: 0; overflow: hidden; } -.pad-page .editor-panel { min-height: 0; overflow: hidden; } -.pad-page .workspace { min-height: 0; overflow: hidden; } +.pad-page { + height: 100dvh; + min-height: 0; + overflow: hidden; + display: grid; + grid-template-rows: auto minmax(0, 1fr); +} + +.pad-page .app-header { + min-width: 0; +} + +.pad-page .editor-layout { + height: auto; + min-height: 0; + overflow: hidden; +} + +.pad-page .editor-panel { + min-height: 0; + overflow: hidden; +} + +.pad-page .workspace { + min-height: 0; + overflow: hidden; +} + .pad-page .editor-column, .pad-page .preview-column, -.pad-page .editor-shell { min-height: 0; overflow: hidden; } +.pad-page .editor-shell { + min-height: 0; + overflow: hidden; +} + .pad-page .editor-shell textarea, -.pad-page .preview { height: 100%; min-height: 0; overflow: auto; } -.pad-page .editor-footer { position: relative; z-index: 8; flex: none; background: var(--surface); } +.pad-page .preview { + height: 100%; + min-height: 0; + overflow: auto; +} + +.pad-page .editor-footer { + position: relative; + z-index: 8; + flex: none; + background: var(--surface); +} + @media (max-width: 980px) { - .pad-page .editor-layout { height: auto; } + .pad-page .editor-layout { + height: auto; + } } /* A collapsible section has one source-line marker; its rendered Markdown does not repeat line numbers inside. */ -.markdown-details__content .preview-source-line::before { display: none; } -.markdown-details__content { overflow: visible; } -.markdown-details__content pre { overflow: auto; } +.markdown-details__content .preview-source-line::before { + display: none; +} + +.markdown-details__content { + overflow: visible; +} + +.markdown-details__content pre { + overflow: auto; +} /* Ephemeral room presence and chat */ -.room-details { position: relative; display: inline-block; } -.room-details > summary { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; color: var(--text); list-style: none; } -.room-details > summary::-webkit-details-marker { display: none; } -.chat-unread { min-width: 17px; height: 17px; padding: 0 5px; border-radius: 999px; background: var(--accent); color: #fff; font-size: 10px; line-height: 17px; text-align: center; } -.room-popover { position: absolute; bottom: calc(100% + 10px); left: 0; z-index: 40; display: grid; grid-template-columns: 150px minmax(280px, 380px); width: min(560px, calc(100vw - 24px)); max-height: min(430px, 70vh); overflow: hidden; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); box-shadow: 0 18px 50px rgb(0 0 0 / .38); color: var(--text); } -.room-users { padding: 14px; overflow: auto; border-right: 1px solid var(--border); } -.room-users strong, .room-chat__head strong { display: block; margin-bottom: 8px; font-size: .75rem; } -.room-users ul { display: grid; gap: 7px; margin: 0; padding: 0; list-style: none; } -.room-users li { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); } -.room-chat { display: grid; grid-template-rows: auto minmax(120px, 1fr) auto; min-height: 280px; } -.room-chat__head { padding: 12px 14px 8px; border-bottom: 1px solid var(--border); } -.room-chat__head strong { margin: 0; } -.room-chat__head span { color: var(--muted-2); font-size: .66rem; } -.chat-messages { display: flex; flex-direction: column; gap: 8px; overflow-y: auto; padding: 12px 14px; } -.chat-message { margin: 0; overflow-wrap: anywhere; line-height: 1.35; } -.chat-message strong { margin-right: 6px; color: var(--text); } -.chat-message span { color: var(--muted); } -.chat-empty { margin: auto; color: var(--muted-2); } -.chat-form { display: grid; grid-template-columns: 1fr auto; gap: 8px; padding: 10px; border-top: 1px solid var(--border); } -.chat-form input { min-width: 0; padding: 8px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface-2); color: var(--text); } -.chat-form button { padding: 8px 12px; border: 1px solid var(--border); border-radius: 6px; background: var(--accent); color: #fff; cursor: pointer; } -@media (max-width: 700px) { .room-popover { grid-template-columns: 1fr; left: auto; right: -120px; } .room-users { max-height: 110px; border-right: 0; border-bottom: 1px solid var(--border); } } -.room-user { display: flex; align-items: center; gap: 8px; min-width: 0; } -.room-user__dot { width: 9px; height: 9px; flex: 0 0 auto; border-radius: 50%; background: var(--owner, var(--accent)); box-shadow: 0 0 0 2px color-mix(in srgb, var(--owner, var(--accent)) 18%, transparent); } -.room-user > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.chat-message a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; overflow-wrap: anywhere; } -.chat-message a:hover { filter: brightness(1.15); } +.room-details { + position: relative; + display: inline-block; +} + +.room-details>summary { + display: inline-flex; + align-items: center; + gap: 6px; + cursor: pointer; + color: var(--text); + list-style: none; +} + +.room-details>summary::-webkit-details-marker { + display: none; +} + +.chat-unread { + min-width: 17px; + height: 17px; + padding: 0 5px; + border-radius: 999px; + background: var(--accent); + color: #fff; + font-size: 10px; + line-height: 17px; + text-align: center; +} + +.room-popover { + position: absolute; + bottom: calc(100% + 10px); + left: 0; + z-index: 40; + display: grid; + grid-template-columns: 150px minmax(280px, 380px); + width: min(560px, calc(100vw - 24px)); + max-height: min(430px, 70vh); + overflow: hidden; + border: 1px solid var(--border); + border-radius: 10px; + background: var(--surface); + box-shadow: 0 18px 50px rgb(0 0 0 / .38); + color: var(--text); +} + +.room-users { + padding: 14px; + overflow: auto; + border-right: 1px solid var(--border); +} + +.room-users strong, +.room-chat__head strong { + display: block; + margin-bottom: 8px; + font-size: .75rem; +} + +.room-users ul { + display: grid; + gap: 7px; + margin: 0; + padding: 0; + list-style: none; +} + +.room-users li { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--muted); +} + +.room-chat { + display: grid; + grid-template-rows: auto minmax(120px, 1fr) auto; + min-height: 280px; +} + +.room-chat__head { + padding: 12px 14px 8px; + border-bottom: 1px solid var(--border); +} + +.room-chat__head strong { + margin: 0; +} + +.room-chat__head span { + color: var(--muted-2); + font-size: .66rem; +} + +.chat-messages { + display: flex; + flex-direction: column; + gap: 8px; + overflow-y: auto; + padding: 12px 14px; +} + +.chat-message { + margin: 0; + overflow-wrap: anywhere; + line-height: 1.35; +} + +.chat-message strong { + margin-right: 6px; + color: var(--text); +} + +.chat-message span { + color: var(--muted); +} + +.chat-empty { + margin: auto; + color: var(--muted-2); +} + +.chat-form { + display: grid; + grid-template-columns: 1fr auto; + gap: 8px; + padding: 10px; + border-top: 1px solid var(--border); +} + +.chat-form input { + min-width: 0; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--surface-2); + color: var(--text); +} + +.chat-form button { + padding: 8px 12px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--accent); + color: #fff; + cursor: pointer; +} + +@media (max-width: 700px) { + .room-popover { + grid-template-columns: 1fr; + left: auto; + right: -120px; + } + + .room-users { + max-height: 110px; + border-right: 0; + border-bottom: 1px solid var(--border); + } +} + +.room-user { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; +} + +.room-user__dot { + width: 9px; + height: 9px; + flex: 0 0 auto; + border-radius: 50%; + background: var(--owner, var(--accent)); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--owner, var(--accent)) 18%, transparent); +} + +.room-user>span:last-child { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.chat-message a { + color: var(--accent); + text-decoration: underline; + text-underline-offset: 2px; + overflow-wrap: anywhere; +} + +.chat-message a:hover { + filter: brightness(1.15); +} /* Sharing panel */ -.share-panel { display: grid; gap: 18px; } -.share-panel-head { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; } -.share-panel-head h3, .share-section h4 { margin:0; } -.share-panel-head p { margin:4px 0 0; color:var(--muted); } -.share-section { display:grid; gap:10px; padding:14px; border:1px solid var(--border); border-radius:10px; background:var(--surface); } -.share-form-grid { display:grid; grid-template-columns:minmax(220px,1fr) minmax(140px,auto) auto; gap:10px; align-items:end; } -.share-form-grid label { display:grid; gap:5px; } -.share-link-form { grid-template-columns:minmax(140px,auto) minmax(110px,140px) auto auto; } -.share-forever { display:flex !important; align-items:center; gap:7px !important; min-height:38px; white-space:nowrap; } -.share-forever input { width:auto; } -.share-list { display:grid; gap:8px; } -.share-list-row { display:grid; grid-template-columns:minmax(180px,1fr) auto auto; gap:8px; align-items:center; padding:9px 0; border-top:1px solid var(--border); } -.share-list-row:first-child { border-top:0; } -.share-list-row strong, .share-list-row small { display:block; overflow-wrap:anywhere; } -.share-list-row small { color:var(--muted); margin-top:2px; } -.share-role { color:var(--muted); font-size:.82rem; } -.share-link-row { grid-template-columns:minmax(170px,1fr) minmax(130px,auto) 100px auto auto auto; } -.share-link-row input, .share-link-row select { min-width:0; } -.share-empty { margin:0; color:var(--muted); } +.share-panel { + display: grid; + gap: 18px; +} + +.share-panel-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; +} + +.share-panel-head h3, +.share-section h4 { + margin: 0; +} + +.share-panel-head p { + margin: 4px 0 0; + color: var(--muted); +} + +.share-section { + display: grid; + gap: 10px; + padding: 14px; + border: 1px solid var(--border); + border-radius: 10px; + background: var(--surface); +} + +.share-form-grid { + display: grid; + grid-template-columns: minmax(220px, 1fr) minmax(140px, auto) auto; + gap: 10px; + align-items: end; +} + +.share-form-grid label { + display: grid; + gap: 5px; +} + +.share-link-form { + grid-template-columns: minmax(140px, auto) minmax(110px, 140px) auto auto; +} + +.share-forever { + display: flex !important; + align-items: center; + gap: 7px !important; + min-height: 38px; + white-space: nowrap; +} + +.share-forever input { + width: auto; +} + +.share-list { + display: grid; + gap: 8px; +} + +.share-list-row { + display: grid; + grid-template-columns: minmax(180px, 1fr) auto auto; + gap: 8px; + align-items: center; + padding: 9px 0; + border-top: 1px solid var(--border); +} + +.share-list-row:first-child { + border-top: 0; +} + +.share-list-row strong, +.share-list-row small { + display: block; + overflow-wrap: anywhere; +} + +.share-list-row small { + color: var(--muted); + margin-top: 2px; +} + +.share-role { + color: var(--muted); + font-size: .82rem; +} + +.share-link-row { + grid-template-columns: minmax(170px, 1fr) minmax(130px, auto) 100px auto auto auto; +} + +.share-link-row input, +.share-link-row select { + min-width: 0; +} + +.share-empty { + margin: 0; + color: var(--muted); +} + @media (max-width:800px) { - .share-form-grid, .share-link-form, .share-list-row, .share-link-row { grid-template-columns:1fr; align-items:stretch; } - .share-panel-head { flex-direction:column; } - .share-panel-head > button { align-self:flex-end; } + + .share-form-grid, + .share-link-form, + .share-list-row, + .share-link-row { + grid-template-columns: 1fr; + align-items: stretch; + } + + .share-panel-head { + flex-direction: column; + } + + .share-panel-head>button { + align-self: flex-end; + } } /* Share dialog */ -.share-dialog { width: min(900px, calc(100% - 28px)); max-width: 900px; max-height: min(88dvh, 860px); padding: 0; overflow: hidden; } -.share-dialog::backdrop { background: rgb(0 0 0 / .68); backdrop-filter: blur(3px); } -.share-dialog .share-panel { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; max-height: min(88dvh, 860px); gap: 0; } -.share-dialog .share-panel-head { padding: 20px 22px 16px; border-bottom: 1px solid var(--border); background: var(--surface); } -.share-dialog .share-panel-head strong { color: var(--text); } -.share-dialog-body { display: grid; gap: 16px; padding: 18px 22px 22px; overflow-y: auto; background: color-mix(in srgb, var(--surface) 92%, black); } -.share-dialog-footer { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 64px; padding: 12px 22px; border-top: 1px solid var(--border); background: var(--surface); } -.share-dialog-footer .form-message { margin: 0; } -.dialog-close { width: 34px; height: 34px; flex: 0 0 auto; border: 1px solid var(--border); border-radius: 8px; background: var(--surface-2); color: var(--muted); font-size: 1.35rem; line-height: 1; cursor: pointer; } -.dialog-close:hover { color: var(--text); border-color: var(--border-strong); } -.share-section { padding: 16px; background: var(--surface); } -.share-section-head h4 { margin: 0; } -.share-section-head p { margin: 4px 0 2px; color: var(--muted); font-size: .82rem; } -.share-form-grid label > span:not(.sr-only), .share-link-row label > span:not(.sr-only) { color: var(--muted); font-size: .76rem; } -.share-dialog select { width: 100%; min-height: 42px; padding: 0 38px 0 12px; border: 1px solid var(--border-strong); border-radius: 8px; background-color: #10141a; color: var(--text); appearance: none; background-image: linear-gradient(45deg, transparent 50%, #9aa3b2 50%), linear-gradient(135deg, #9aa3b2 50%, transparent 50%); background-position: calc(100% - 16px) 17px, calc(100% - 11px) 17px; background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; cursor: pointer; } -.share-dialog select:hover { border-color: #596273; } -.share-dialog select:focus { border-color: #7567db; box-shadow: 0 0 0 3px rgb(117 103 219 / .18); outline: none; } -.share-dialog input[type="text"], .share-dialog input[type="number"] { min-height: 42px; border-radius: 8px; padding: 0 12px; } -.share-hours-field { display: grid; grid-template-columns: minmax(70px, 1fr) auto; align-items: center; min-height: 42px; border: 1px solid var(--border-strong); border-radius: 8px; background: #10141a; overflow: hidden; } -.share-hours-field:focus-within { border-color: #7567db; box-shadow: 0 0 0 3px rgb(117 103 219 / .18); } -.share-hours-field input { width: 100%; min-width: 0; border: 0 !important; background: transparent; box-shadow: none !important; outline: 0 !important; } -.share-hours-field span { padding: 0 11px; color: var(--muted); font-size: .76rem; } -.share-list-row { min-height: 58px; padding: 10px 0; } -.share-list-identity { display: flex; align-items: center; gap: 10px; min-width: 0; } -.share-avatar { display: grid; place-items: center; width: 34px; height: 34px; flex: 0 0 auto; border-radius: 50%; background: color-mix(in srgb, var(--accent) 20%, var(--surface-2)); color: #d8d3ff; font-weight: 700; } -.share-list-identity > div, .share-link-info { min-width: 0; } -.share-row-actions { display: flex; gap: 7px; justify-content: flex-end; } -.compact-button { min-height: 36px; padding: 7px 11px; } -.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; } +.share-dialog { + width: min(900px, calc(100% - 28px)); + max-width: 900px; + max-height: min(88dvh, 860px); + padding: 0; + overflow: hidden; +} + +.share-dialog::backdrop { + background: rgb(0 0 0 / .68); + backdrop-filter: blur(3px); +} + +.share-dialog .share-panel { + display: grid; + grid-template-rows: auto minmax(0, 1fr) auto; + max-height: min(88dvh, 860px); + gap: 0; +} + +.share-dialog .share-panel-head { + padding: 20px 22px 16px; + border-bottom: 1px solid var(--border); + background: var(--surface); +} + +.share-dialog .share-panel-head strong { + color: var(--text); +} + +.share-dialog-body { + display: grid; + gap: 16px; + padding: 18px 22px 22px; + overflow-y: auto; + background: color-mix(in srgb, var(--surface) 92%, black); +} + +.share-dialog-footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + min-height: 64px; + padding: 12px 22px; + border-top: 1px solid var(--border); + background: var(--surface); +} + +.share-dialog-footer .form-message { + margin: 0; +} + +.dialog-close { + width: 34px; + height: 34px; + flex: 0 0 auto; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--surface-2); + color: var(--muted); + font-size: 1.35rem; + line-height: 1; + cursor: pointer; +} + +.dialog-close:hover { + color: var(--text); + border-color: var(--border-strong); +} + +.share-section { + padding: 16px; + background: var(--surface); +} + +.share-section-head h4 { + margin: 0; +} + +.share-section-head p { + margin: 4px 0 2px; + color: var(--muted); + font-size: .82rem; +} + +.share-form-grid label>span:not(.sr-only), +.share-link-row label>span:not(.sr-only) { + color: var(--muted); + font-size: .76rem; +} + +.share-dialog select { + width: 100%; + min-height: 42px; + padding: 0 38px 0 12px; + border: 1px solid var(--border-strong); + border-radius: 8px; + background-color: #10141a; + color: var(--text); + appearance: none; + background-image: linear-gradient(45deg, transparent 50%, #9aa3b2 50%), linear-gradient(135deg, #9aa3b2 50%, transparent 50%); + background-position: calc(100% - 16px) 17px, calc(100% - 11px) 17px; + background-size: 5px 5px, 5px 5px; + background-repeat: no-repeat; + cursor: pointer; +} + +.share-dialog select:hover { + border-color: #596273; +} + +.share-dialog select:focus { + border-color: #7567db; + box-shadow: 0 0 0 3px rgb(117 103 219 / .18); + outline: none; +} + +.share-dialog input[type="text"], +.share-dialog input[type="number"] { + min-height: 42px; + border-radius: 8px; + padding: 0 12px; +} + +.share-hours-field { + display: grid; + grid-template-columns: minmax(70px, 1fr) auto; + align-items: center; + min-height: 42px; + border: 1px solid var(--border-strong); + border-radius: 8px; + background: #10141a; + overflow: hidden; +} + +.share-hours-field:focus-within { + border-color: #7567db; + box-shadow: 0 0 0 3px rgb(117 103 219 / .18); +} + +.share-hours-field input { + width: 100%; + min-width: 0; + border: 0 !important; + background: transparent; + box-shadow: none !important; + outline: 0 !important; +} + +.share-hours-field span { + padding: 0 11px; + color: var(--muted); + font-size: .76rem; +} + +.share-list-row { + min-height: 58px; + padding: 10px 0; +} + +.share-list-identity { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; +} + +.share-avatar { + display: grid; + place-items: center; + width: 34px; + height: 34px; + flex: 0 0 auto; + border-radius: 50%; + background: color-mix(in srgb, var(--accent) 20%, var(--surface-2)); + color: #d8d3ff; + font-weight: 700; +} + +.share-list-identity>div, +.share-link-info { + min-width: 0; +} + +.share-row-actions { + display: flex; + gap: 7px; + justify-content: flex-end; +} + +.compact-button { + min-height: 36px; + padding: 7px 11px; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + @media (max-width: 800px) { - .share-dialog { width: calc(100% - 16px); max-height: calc(100dvh - 16px); } - .share-dialog .share-panel { max-height: calc(100dvh - 16px); } - .share-dialog .share-panel-head, .share-dialog-body, .share-dialog-footer { padding-left: 14px; padding-right: 14px; } - .share-dialog-footer { align-items: stretch; flex-direction: column; } - .share-dialog-footer > button { align-self: flex-end; } - .share-row-actions { justify-content: stretch; } - .share-row-actions button { flex: 1; } + .share-dialog { + width: calc(100% - 16px); + max-height: calc(100dvh - 16px); + } + + .share-dialog .share-panel { + max-height: calc(100dvh - 16px); + } + + .share-dialog .share-panel-head, + .share-dialog-body, + .share-dialog-footer { + padding-left: 14px; + padding-right: 14px; + } + + .share-dialog-footer { + align-items: stretch; + flex-direction: column; + } + + .share-dialog-footer>button { + align-self: flex-end; + } + + .share-row-actions { + justify-content: stretch; + } + + .share-row-actions button { + flex: 1; + } } /* Refined sharing dialog and shared-resource attribution. */ @@ -689,18 +3663,78 @@ dialog::backdrop { background: rgba(4,6,9,.82); } background: var(--surface); box-shadow: 0 28px 90px rgb(0 0 0 / .58); } -.share-dialog .share-panel { overflow: hidden; border-radius: inherit; } -.share-dialog .share-panel-head { border-radius: 18px 18px 0 0; } -.share-dialog-footer { border-radius: 0 0 18px 18px; } -.share-dialog-body { scrollbar-gutter: stable; } -.share-section { border-radius: 12px; box-shadow: inset 0 1px 0 rgb(255 255 255 / .025); } -.resource-copy { min-width: 0; } -.resource-title-line { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; min-width: 0; } -.resource-title-line a { min-width: 0; } -.resource-shared-badge { display: inline-flex; align-items: center; min-height: 22px; padding: 3px 8px; border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 999px; background: color-mix(in srgb, var(--accent) 12%, transparent); color: color-mix(in srgb, var(--accent) 70%, white); font-size: .68rem; font-weight: 650; line-height: 1.2; } -.resource-row--shared { border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); background: color-mix(in srgb, var(--accent) 4%, transparent); } -@media (max-width: 640px) { - .share-dialog, .share-dialog .share-panel, .share-dialog .share-panel-head, .share-dialog-footer { border-radius: 14px; } - .share-dialog .share-panel-head { border-radius: 14px 14px 0 0; } - .share-dialog-footer { border-radius: 0 0 14px 14px; } + +.share-dialog .share-panel { + overflow: hidden; + border-radius: inherit; } + +.share-dialog .share-panel-head { + border-radius: 18px 18px 0 0; +} + +.share-dialog-footer { + border-radius: 0 0 18px 18px; +} + +.share-dialog-body { + scrollbar-gutter: stable; +} + +.share-section { + border-radius: 12px; + box-shadow: inset 0 1px 0 rgb(255 255 255 / .025); +} + +.resource-copy { + min-width: 0; +} + +.resource-title-line { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + min-width: 0; +} + +.resource-title-line a { + min-width: 0; +} + +.resource-shared-badge { + display: inline-flex; + align-items: center; + min-height: 22px; + padding: 3px 8px; + border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border)); + border-radius: 999px; + background: color-mix(in srgb, var(--accent) 12%, transparent); + color: color-mix(in srgb, var(--accent) 70%, white); + font-size: .68rem; + font-weight: 650; + line-height: 1.2; +} + +.resource-row--shared { + border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); + background: color-mix(in srgb, var(--accent) 4%, transparent); +} + +@media (max-width: 640px) { + + .share-dialog, + .share-dialog .share-panel, + .share-dialog .share-panel-head, + .share-dialog-footer { + border-radius: 14px; + } + + .share-dialog .share-panel-head { + border-radius: 14px 14px 0 0; + } + + .share-dialog-footer { + border-radius: 0 0 14px 14px; + } +} \ No newline at end of file diff --git a/static/error.html b/static/error.html index ea466bc..e77e4f6 100644 --- a/static/error.html +++ b/static/error.html @@ -1,5 +1,6 @@ + @@ -8,6 +9,7 @@ __ERROR_TITLE__ · RustPad +
@@ -21,4 +23,5 @@
- + + \ No newline at end of file diff --git a/static/home.html b/static/home.html index c9330ac..63562b5 100644 --- a/static/home.html +++ b/static/home.html @@ -1,13 +1,17 @@ + RustPad - + + +
@@ -26,11 +30,15 @@
-
/p/meeting-notes0/80
+
/p/meeting-notes0/80
-
optional, min. 8 characters
-
+
optional, min. 8 characters +
+
@@ -46,11 +54,15 @@
-
/w/my-project0/80
+
/w/my-project0/80
-
optional, min. 8 characters
-
+
optional, min. 8 + characters
+
@@ -63,14 +75,16 @@ @@ -82,9 +96,12 @@

- - - + + +
- + + \ No newline at end of file diff --git a/static/workspace.html b/static/workspace.html index 3a3e770..369c7d5 100644 --- a/static/workspace.html +++ b/static/workspace.html @@ -1,5 +1,61 @@ -__WORKSPACE_TITLE__ · RustPad -
RustPad

__WORKSPACE_TITLE__

-

Notes

Select a note or create a new one.

View

-

Protected workspace

Cancel
-

New note

+ + + + + + + + __WORKSPACE_TITLE__ · RustPad + + + + + + +
+
RustPad +
+

__WORKSPACE_TITLE__

+

+
+
+
+
+
+
+
+

Notes

+

Select a note or create a new one.

+
+
+
View +
+
+

+
+
+ +
+

Protected workspace

+

Cancel +
+
+ +
+

New note

+

+
+
+
+
+ + + \ No newline at end of file